代码仓库:https://github.com/changeclass/koa2-weibo

用户数据

  1. 视图路由层

    router.get('/profile', loginRedirect, async (ctx, next) => {
      const { userName } = ctx.session.userInfo
      ctx.redirect(`/profile/${userName}`)
    })

    当访问路由没有用户名时,默认跳转到自己用户名下。

    router.get('/profile/:userName', loginRedirect, async (ctx, next) => {
      const { userName: curUserName } = ctx.params
      // 获取微博第一页数据
      // controller
      const result = await getProfileBlogList(curUserName, 0)
      const { isEmpty, blogList, pageSize, pageIndex, count } = result.data
      await ctx.render('profile', {
        blogData: {
          isEmpty,
          blogList,
          pageSize,
          pageIndex,
          count
        },
        userData: {
          userInfo: ctx.session.userInfo,
          fansData: {
            count: 0,
            list: []
          },
          followersData: {
            count: 0,
            list: []
          }
        }
      })
    })
  2. 控制器层

    /**
     * @description: 个人主页
     * @author: 小康
     * @url: https://xiaokang.me
     * @Date: 2020-12-19 09:47:36
     * @LastEditTime: 2020-12-19 09:47:36
     * @LastEditors: 小康
     */
    
    const { getBlogListByUser } = require('../services/blog')
    const { PAGE_SIZE } = require('../config/constant')
    const { SuccessModel } = require('../model/ResModel')
    /**
     * @author: 小康
     * @url: https://xiaokang.me
     * @param {string} userName 用户名
     * @param {number} pageIndex 当前页面索引 默认0
     * @description: 创建微博
     */
    async function getProfileBlogList(userName, pageIndex = 0) {
      const result = await getBlogListByUser({ userName, pageIndex, PAGE_SIZE })
      const blogList = result.blogList
      return new SuccessModel({
        isEmpty: blogList.length === 0,
        blogList,
        pageSize: PAGE_SIZE,
        pageIndex,
        count: result.count
      })
    }
    
    module.exports = { getProfileBlogList }
  3. 服务层

    const { Blog, User } = require('../db/model/index')
    const { formatUser } = require('./_format')
    /**
     * @author: 小康
     * @url: https://xiaokang.me
     * @param {*} userName 用户名
     * @param {*} pageIndex 页面索引
     * @param {*} pageSize 页面大小
     * @description: 根据用户名查询微博
     */
    async function getBlogListByUser({ userName, pageIndex = 0, pageSize = 10 }) {
      // 拼接查询条件
      const userWhereOpts = {}
      if (userName) {
        userWhereOpts.userName = userName
      }
      // 执行查询
      const result = await Blog.findAndCountAll({
        limit: pageSize, // 每页多少条
        offset: pageSize * pageIndex, // 跳过多少条
        order: [['id', 'desc']],
        include: [
          {
            model: User,
            attributes: ['userName', 'nickName', 'picture'],
            where: userWhereOpts
          }
        ]
      })
      // 获取dataValues
      let blogList = result.rows.map((row) => row.dataValues)
      blogList = blogList.map((blogItem) => {
        const user = blogItem.user.dataValues
        blogItem.user = formatUser(user)
        return blogItem
      })
      return {
        count: result.count,
        blogList
      }
    }

格式化时间、用户数据

用户数据主页是判断访问的用户是否存在。

const { isExist } = require('../../controller/user')

router.get('/profile/:userName', loginRedirect, async (ctx, next) => {
  // 已登录用户的信息
  const myUserInfo = ctx.session.userInfo
  const myUserName = myUserInfo.userName

  let curUserInfo
  const { userName: curUserName } = ctx.params
  const isMe = myUserName === curUserName
  if (isMe) {
    // 是当前登录用户
    curUserInfo = myUserInfo
  } else {
    // 不是当前登录用户
    const existResult = await isExist(curUserName)
    if (existResult.errno !== 0) {
      // 用户名不存在
      return
    }
    // 用户名存在
    curUserInfo = existResult.data
  }
  // ....
})

格式化时间主要是格式化微博内容,处理在服务层。

  1. blog.js
const { formatUser, formatBlog } = require('./_format')

// 获取dataValues
let blogList = result.rows.map((row) => row.dataValues)

// 格式化
blogList = formatBlog(blogList)
blogList = blogList.map((blogItem) => {
  const user = blogItem.user.dataValues
  blogItem.user = formatUser(user)
  return blogItem
})
  1. _format
/**
 * @description: 数据格式化
 * @author: 小康
 * @url: https://xiaokang.me
 * @Date: 2020-12-17 14:32:10
 * @LastEditTime: 2020-12-17 14:32:10
 * @LastEditors: 小康
 */
const { DEFAULT_PICTURE, REG_FOR_AT_WHO } = require('../config/constant')
const { timeFormat } = require('../utils/dt')
/**
 * 格式化头像
 * @author 小康
 * @date 2020-12-17
 * @param {Object} obj 用户对象
 * @returns {Object} 处理后的结果
 */
function _formatUserPicture(obj) {
  if (obj.picture == null) {
    obj.picture = DEFAULT_PICTURE
  }
  return obj
}

/**
 * 格式化用户
 * @author 小康
 * @date 2020-12-17
 * @param {Array|Object} list 用户列表或单个用户对象
 * @returns {any}
 */
function formatUser(list) {
  if (list == null) {
    return list
  }
  if (list instanceof Array) {
    // 数组 用户列表
    return list.map(_formatUserPicture)
  }
  // 单个对象
  let result = list
  result = _formatUserPicture(result)
  return result
}

/**
 * 格式化数据的时间
 * @param {Object} obj 数据
 */
function _formatDBTime(obj) {
  obj.createdAtFormat = timeFormat(obj.createdAt)
  obj.updatedAtFormat = timeFormat(obj.updatedAt)
  return obj
}

/**
 * 格式化微博内容
 * @param {Object} obj 微博数据对象
 */
function _formatContent(obj) {
  obj.contentFormat = obj.content

  // 格式化 @
  // from '哈喽 @张三 - zhangsan 你好'
  // to '哈喽 <a href="/profile/zhangsan">张三</a> 你好'
  obj.contentFormat = obj.contentFormat.replace(
    REG_FOR_AT_WHO,
    (matchStr, nickName, userName) => {
      return `<a href="/profile/${userName}">@${nickName}</a>`
    }
  )

  return obj
}

/**
 * 格式化微博信息
 * @param {Array|Object} list 微博列表或者单个微博对象
 */
function formatBlog(list) {
  if (list == null) {
    return list
  }

  if (list instanceof Array) {
    // 数组
    return list.map(_formatDBTime).map(_formatContent)
  }
  // 对象
  let result = list
  result = _formatDBTime(result)
  result = _formatContent(result)
  return result
}

module.exports = { formatUser, formatBlog }
  1. dt.js
/**
 * @description:
 * @author: 小康
 * @url: https://xiaokang.me
 * @Date: 2020-12-19 10:54:11
 * @LastEditTime: 2020-12-19 10:54:12
 * @LastEditors: 小康
 */

const { format } = require('date-fns')

/**
 * 格式化时间,如 09.05 23:02
 * @param {string} str 时间字符串
 */
function timeFormat(str) {
  return format(new Date(str), 'MM.dd HH:mm')
}

module.exports = {
  timeFormat
}

加载更多

  1. api路由层

    /**
     * @description: 个人主页 API 路由
     * @author: 小康
     * @url: https://xiaokang.me
     * @Date: 2020-12-19 11:20:02
     * @LastEditTime: 2020-12-19 11:20:02
     * @LastEditors: 小康
     */
    
    const router = require('koa-router')()
    
    const { getProfileBlogList } = require('../../controller/blog-profile')
    const { loginCheck } = require('../../middlewares/loginChecks')
    const { getBlogListStr } = require('../../utils/blog')
    
    router.prefix('/api/profile')
    
    // 加载更多
    router.get('/loadMore/:userName/:pageIndex', loginCheck, async (ctx, next) => {
      let { userName, pageIndex } = ctx.params
      pageIndex = parseInt(pageIndex)
      const result = await getProfileBlogList(userName, pageIndex)
      result.data.blogListTpl = getBlogListStr(result.data.blogList)
      ctx.body = result
    })
    
    module.exports = router
    
  2. 工具方法

    /**
     * @description: 微博数据相关的工具方法
     * @author: 小康
     * @url: https://xiaokang.me
     * @Date: 2020-12-19 11:30:22
     * @LastEditTime: 2020-12-19 11:30:22
     * @LastEditors: 小康
     */
    
    const fs = require('fs')
    const path = require('path')
    const ejs = require('ejs')
    
    // 获取 blog-list.ejs 文件内容
    const BLOG_LIST_TPL = fs
      .readFileSync(path.join(__dirname, '..', 'views', 'widgets', 'blog-list.ejs'))
      .toString()
    
    /**
     * @author: 小康
     * @url: https://xiaokang.me
     * @param {Array} blogList 微博列表
     * @param {Boolean} canReply 是否可以回复
     * @description: 根据blogList渲染出HTML字符串
     */
    function getBlogListStr(blogList = [], canReply = false) {
      return ejs.render(BLOG_LIST_TPL, {
        blogList,
        canReply
      })
    }
    
    module.exports = { getBlogListStr }