:::warning

概述

技术团队博客系统
技术栈
Vue + Node.js + Egg/Koa + MongoDB + Pug + 语雀 API :::

主要功能

  1. 用户管理
    a. 管理员登录/登出
    b. 游客访问(无须注册登录,匿名 ID 访问)
  2. 文章发布(将语雀文章同步到网站)
    a. 拉取全部文章清单
    ⅰ. 后台点击同步按钮、手动触发拉取文章列表任务
    b. 选择目标文章发布
    ⅰ. 后台选择文章点击按钮,完成文章的持久化
  3. 文章展示
    a. 网站首页
    ⅰ. 放文章的列表,支持分页
    ⅱ. 支持现有文章检索
    ⅲ. 点击单条文章记录进入文章详情
    b. 文章详情
    ⅰ. 点击进入文章详情
  4. 后台统计
    a. 每个页面的访问 PV
    b. 每个页面的访问 UV
    ⅰ. 用户首次进入网站时,种一个 uuid 的 cookie
    1. 不做严格意义用户去重,只根据浏览器的 cookie 识别是否是老用户
      c. 在后台展示已发布页面的 PV/UV

      页面访问链路

      用户流程

      image.png
      管理员流程

image.png

技术架构

image.png

代码举例

表结构 - Schema

如用户表的设计,需要给用户密码加权加密,增加破解难度,用户表需要存储账号密码这些基本信息,以及匿名用户的访问 ID,也可以分拆成 admin 表和 user 两张表,如下以一张表来举例部分代码:

  1. const mongoose = require('mongoose')
  2. const bcrypt = require('bcrypt')
  3. // 加密权重
  4. const SALT_WORK_FACTOR = 10
  5. const Schema = mongoose.Schema
  6. const UserSchema = new Schema({
  7. // user admin superAdmin
  8. role: {
  9. type: String,
  10. default: 'user'
  11. },
  12. uuid: String,
  13. username: String,
  14. password: String,
  15. meta: {
  16. createdAt: {
  17. type: Date,
  18. default: Date.now()
  19. },
  20. updatedAt: {
  21. type: Date,
  22. default: Date.now()
  23. }
  24. }
  25. })
  26. // 中间件
  27. UserSchema.pre('save', function (next) {
  28. if (this.isNew) {
  29. this.meta.createdAt = this.meta.updatedAt = Date.now()
  30. } else {
  31. this.meta.updatedAt = Date.now()
  32. }
  33. next()
  34. })
  35. UserSchema.pre('save', function (next) {
  36. let user = this
  37. if (!user.isModified('password')) return next()
  38. bcrypt.genSalt(SALT_WORK_FACTOR, (err, salt) => {
  39. if (err) return next(err)
  40. bcrypt.hash(user.password, salt, (error, hash) => {
  41. if (error) return next(error)
  42. user.password = hash
  43. next()
  44. })
  45. })
  46. })
  47. // 静态方法
  48. UserSchema.methods = {
  49. comparePassword: function (_password, password) {
  50. return new Promise((resolve, reject) => {
  51. bcrypt.compare(_password, password, function (err, isMatch) {
  52. if (!err) resolve(isMatch)
  53. else reject(err)
  54. })
  55. })
  56. }
  57. }
  58. mongoose.model('User', UserSchema)

控制器 - Controller

如管理员登录的判断,是放到控制器里面

  1. // 增加一个登录的校验/判断 signin
  2. exports.signin = async (ctx, next) => {
  3. const { username, password } = ctx.request.body.user
  4. const user = await User.findOne({ username })
  5. if (!user) return ctx.redirect('/user/signup')
  6. const isMatch = await user.comparePassword(password, user.password)
  7. if (isMatch) {
  8. ctx.session.user = {
  9. _id: user._id,
  10. role: user.role,
  11. nickname: user.nickname
  12. }
  13. ctx.redirect('/')
  14. } else {
  15. ctx.redirect('/user/signin')
  16. }
  17. }

路由 - Router

  1. // 管理员的注册登录路由
  2. router.get('/user/signup', User.showSignup)
  3. router.get('/user/signin', User.showSignin)
  4. router.post('/user/signup', User.signup)
  5. router.post('/user/signin', User.signin)
  6. router.get('/logout', User.logout)