{"id":15097797,"url":"https://github.com/xumingyu2018/coderhub","last_synced_at":"2026-02-02T12:03:23.537Z","repository":{"id":183128795,"uuid":"628865601","full_name":"xumingyu2018/coderhub","owner":"xumingyu2018","description":"node.js koa框架项目实战","archived":false,"fork":false,"pushed_at":"2023-05-23T12:37:54.000Z","size":77,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-02-01T17:44:31.902Z","etag":null,"topics":["koa","nodejs"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/xumingyu2018.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2023-04-17T06:28:48.000Z","updated_at":"2023-05-23T12:39:24.000Z","dependencies_parsed_at":"2023-07-23T03:15:43.431Z","dependency_job_id":null,"html_url":"https://github.com/xumingyu2018/coderhub","commit_stats":null,"previous_names":["xumingyu2018/coderhub"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xumingyu2018%2Fcoderhub","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xumingyu2018%2Fcoderhub/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xumingyu2018%2Fcoderhub/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xumingyu2018%2Fcoderhub/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/xumingyu2018","download_url":"https://codeload.github.com/xumingyu2018/coderhub/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245853868,"owners_count":20683263,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["koa","nodejs"],"created_at":"2024-09-25T16:40:31.656Z","updated_at":"2026-02-02T12:03:18.502Z","avatar_url":"https://github.com/xumingyu2018.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# coderhub\nnode.js koa框架项目实战笔记\n## 全局配置类\n\n### 1.错误信息配置\n\n```JavaScript\n// config/error.js\nconst NAME_OR_PASSWORD_IS_EMPTY = 'name_or_password_is_empty'\nconst USER_ALREADY_EXISTS = 'user_already_exists'\nconst USER_IS_NOT_EXISTS = 'user_is_not_exists'\nconst PASSWORD_IS_INCORRECT = 'password_is_incorrect'\nconst UNAUTHORIZATION = 'unauthorization'\nconst OPERATION_IS_NOT_ALLOWED = 'operation_is_not_allowed'\nconst QUERY_IS_FAIL = 'query_is_fail'\n\nmodule.exports = {\n  NAME_OR_PASSWORD_IS_EMPTY,\n  USER_ALREADY_EXISTS,\n  USER_IS_NOT_EXISTS,\n  PASSWORD_IS_INCORRECT,\n  UNAUTHORIZATION,\n  OPERATION_IS_NOT_ALLOWED,\n  QUERY_IS_FAIL\n}\n```\n\n### 2.文件上传路径配置\n\n```JavaScript\n// config/path.js\nconst UPLOAD_PATH = './uploads'\n\nmodule.exports = {\n  UPLOAD_PATH\n}\n\n```\n\n### 3.服务主机端口号配置\n\n  安装：`npm install dotenv `\n\n```JavaScript\n// config/server.js\nconst dotenv = require('dotenv')\n\ndotenv.config()\n\nmodule.exports = {\n  SERVER_PORT,\n  SERVER_HOST\n} = process.env // 从环境变量中解构获取\n```\n\n```JavaScript\n// .env\nSERVER_HOST = http://localhost\nSERVER_PORT = 8000\n```\n\n### 4.密钥加密配置\n\n```JavaScript\n// config/srect.js\nconst fs = require('fs')\n\n// 默认情况下相对目录和node程序的启动目录有关系（\"start\": \"nodemon ./src/main.js\"）\nconst PRIVATE_KEY = fs.readFileSync('./src/config/keys/private.key')\nconst PUBLIC_KEY = fs.readFileSync('./src/config/keys/public.key')\n\nmodule.exports = {\n  PRIVATE_KEY,\n  PUBLIC_KEY\n}\n```\n\n### 5.数据库配置\n\n```JavaScript\n// app/database.js\nconst mysql = require('mysql2')\n\n// 1.创建连接池\nconst connectionPool = mysql.createPool({\n  host: 'localhost',\n  port: 3306,\n  database: 'coderhub',\n  user: 'root',\n  password: '123456',\n  connectionLimit: 5 \n})\n\n// 2.获取连接是否成功\nconnectionPool.getConnection((err, connection) =\u003e {\n  // 1.判断是否有错误信息\n  if(err) {\n    console.log(\"连接失败\", err);\n    return\n  }\n\n  // 2.获取connection，尝试和数据库建立一下连接\n  connection.connect((err) =\u003e {\n    if(err) {\n      console.log(\"和数据库交互失败\", err);\n    }else {\n      console.log(\"数据库连接成功，可以操作数据库\");\n    }\n  })\n})\n\n// 3.获取到连接池中连接对象（promise）\nconst connection = connectionPool.promise()\n\nmodule.exports = connection\n\n```\n\n### 6.app启动配置\n\n```JavaScript\n// app/index.js\n// 抽取app\nconst Koa = require('koa')\nconst bodyParser = require('koa-bodyparser')\nconst registerRouters = require('../router')\n\n// 1.创建app\nconst app = new Koa()\n\n// 2.对app使用中间件\napp.use(bodyParser())\n\n// 动态加载路由\nregisterRouters(app)\n\n// 3.导出app\nmodule.exports = app\n\n```\n\n### 7.动态加载路由（在app中自动注册）\n\n```JavaScript\n// router/index.js\nconst fs = require('fs')\n\nfunction registerRouters(app) {\n  // 1.读取当前文件下的所有文件\n  const files = fs.readdirSync(__dirname)\n\n  // 2.遍历所有文件\n  for(const file of files) {\n    // 只留下带router.js结尾的文件\n    if(!file.endsWith('_router.js')) continue\n    const router =require(`./${file}`)\n    app.use(router.routes()) \n    app.use(router.allowedMethods())\n  }\n}\n\nmodule.exports = registerRouters\n```\n\n### 8. 主程序启动配置\n\n```JavaScript\n// main.js\nconst app = require('./app')\nconst { SERVER_PORT } = require('./config/server')\nrequire('./utils/handle-error')\n\n// 2.将app启动\napp.listen(SERVER_PORT, () =\u003e {\n  console.log('koa服务器启动成功')\n})\n```\n\n\n\n## 工具类\n\n### 1.异常处理\n\n```JavaScript\n// utils/handle-error.js\nconst app = require(\"../app\");\nconst { NAME_OR_PASSWORD_IS_EMPTY, USER_ALREADY_EXISTS, USER_IS_NOT_EXISTS, PASSWORD_IS_INCORRECT, UNAUTHORIZATION, QUERY_IS_FAIL, OPERATION_IS_NOT_ALLOWED } = require(\"../config/error\");\n\n// 注意：在main.js中需要引入一下\napp.on('error', (error, ctx) =\u003e {\n  let code = 0\n  let message =''\n\n  switch(error) {\n    case NAME_OR_PASSWORD_IS_EMPTY:\n      code = -1001,\n      message = '账户或密码为空'\n      break\n    case USER_ALREADY_EXISTS:\n      code = -1002,\n      message = '用户已存在'\n      break\n    case USER_IS_NOT_EXISTS:\n      code = -1003,\n      message = '用户不存在'\n      break\n    case PASSWORD_IS_INCORRECT:\n      code = -1004,\n      message = '密码不正确'\n      break\n    case UNAUTHORIZATION:\n      code = -1005,\n      message = '无效的token'\n      break\n    case QUERY_IS_FAIL:\n      code = -1006,\n      message = '查询失败，请检查数据是否存在'\n      break\n    case OPERATION_IS_NOT_ALLOWED:\n      code = -2001,\n      message = '本次操作没有权限'\n      break\n  }\n\n  ctx.body = {code, message}\n})\n\n```\n\n### 2.md5加密\n\n```JavaScript\n// md5.js\nconst crypto = require('crypto')\n\nfunction md5password(password) {\n  const md5 = crypto.createHash('md5')\n  // 使用md5 16进制加密\n  const md5pwd = md5.update(password).digest('hex')\n  return md5pwd\n}\n\nmodule.exports = md5password\n```\n\n\n\n## 用户模块\n\n- 用户注册接口\n- 用户头像查看接口\n\n### 1.编写router层\n\n```JavaScript\n// router/user_router.js\nconst KoaRouter = require('@koa/router')\nconst { create, showAvatarImage } = require('../controller/user_controller')\nconst { verifyUser, handlePassword } = require('../middleware/user_middleware')\n// 1.创建路由对象\nconst userRouter = new KoaRouter({ prefix: '/users'}) // 添加路由前缀\n\n// 2.定义路由中映射(这里只做映射处理，不做具体的业务处理)\n// 2.1.用户注册接口\nuserRouter.post('/', verifyUser, handlePassword, create)\n// 2.2用户头像查看\nuserRouter.get('/avatar/:userId', showAvatarImage)\n\n// 3.导出路由\nmodule.exports = userRouter\n```\n\n### 2.编写`verifyUser` `handlePassword`中间件\n  - 验证账户\n  - 加密\n\n```JavaScript\n// middleware/user_middleware.js\nconst { NAME_OR_PASSWORD_IS_EMPTY, USER_ALREADY_EXISTS } = require(\"../config/error\")\nconst userService = require(\"../service/user_service\")\nconst md5password = require('../utils/md5')\n\nconst verifyUser = async (ctx, next) =\u003e {\n  // 1.验证用户名和密码是否为空\n  const { username, password } = ctx.request.body\n  if(!username || !password) {\n    return ctx.app.emit('error', NAME_OR_PASSWORD_IS_EMPTY, ctx)\n  }\n\n  // 2.验证用户名是否已存在\n  const users = await userService.findUserByName(username)\n  if(users.length) {\n    return ctx.app.emit('error', USER_ALREADY_EXISTS, ctx)\n  }\n \n  // 3.执行下一个中间件（异步）\n  await next()\n}\n\nconst handlePassword = async (ctx, next) =\u003e {\n  // 1.取出密码\n  const { password } = ctx.request.body\n  // 2.对密码进行加密\n  ctx.request.body.password = md5password(password)\n  // 3.执行下一个中间件\n  await next()\n}\n\nmodule.exports = {\n  verifyUser,\n  handlePassword\n}\n\n```\n\n\n\n### 3.编写controller层\n\n```JavaScript\n// controller/user_controller.js\nconst { UPLOAD_PATH } = require(\"../config/path\");\nconst fileService = require(\"../service/file_service\");\nconst userService = require(\"../service/user_service\");\nconst fs = require('fs')\n\nclass UserController {\n  async create(ctx, next) {\n    // 1.获取用户传递过来的数据\n    const user = ctx.request.body\n\n    // 2.将用户数据保存到数据库中\n    const result = await userService.create(user)\n\n    // 3.查看存储结果，告知前端创建成功 \n    ctx.body = {\n      message: '用户创建成功',\n      data: result\n    }\n  }\n\n  async showAvatarImage(ctx, next) {\n    // 1.获取用户id\n    const { userId } = ctx.params\n\n    // 2.获取userId对应的头像\n    const avatarInfo = await fileService.queryAvatarWithUserId(userId)\n\n    // 3.读取头像所在的文件\n    const { filename, mimetype } = avatarInfo\n    // 若不加这一句，浏览器会以文件方式下载图片\n    ctx.type = mimetype\n    ctx.body = fs.createReadStream(`${UPLOAD_PATH}/${filename}`)\n  }\n}\n\nmodule.exports = new UserController()\n```\n\n### 4.编写service层\n\n```JavaScript\n// service/user_service.js\nconst connection = require('../app/database')\n\nclass UserService {\n  // 添加用户\n  async create(user) {\n    // 1.获取用户user\n    const { username, password } = user\n    // 2.拼接statement sql语句\n    const statement = 'insert into `user` (username, password) values (?, ?);'\n    // 3.执行statement\n    const [result] = await connection.execute(statement, [username, password])\n    return result\n  }\n\n  // 根据用户名获取用户信息\n  async findUserByName(username) {\n    const statement = 'select * from `user` where username = ?;'\n    const [values] = await connection.execute(statement, [username])\n    return values\n  }\n\n  // 修改头像\n  async updateUserAvatar(avatarUrl, userId) {\n    const statement = `update user set avatar_url = ? where id = ?`\n    const [result] = await connection.execute(statement, [avatarUrl, userId])\n    return result\n  }\n}\n\nmodule.exports = new UserService()\n```\n\n## 登录模块\n\n### 1.编写router层\n\n```JavaScript\n// router/login_router.js\nconst KoaRouter = require('@koa/router')\nconst { sign, test } = require('../controller/login_controller')\nconst { verifyLogin, verifyAuth} = require('../middleware/login_middleware')\n\nconst loginRouter = new KoaRouter({ prefix: '/login'}) // 添加路由前缀\n\nloginRouter.post('/', verifyLogin, sign)\nloginRouter.get('/verify', verifyAuth, test)\n\nmodule.exports = loginRouter\n\n```\n\n### 2.编写`verifyLogin` `verifyAuth`中间件\n\n```JavaScript\n// middleware/login_middleware.js\nconst jwt = require('jsonwebtoken')\nconst { NAME_OR_PASSWORD_IS_EMPTY, USER_IS_NOT_EXISTS, PASSWORD_IS_INCORRECT, UNAUTHORIZATION } = require('../config/error')\nconst { PUBLIC_KEY } = require('../config/srect')\nconst userService = require('../service/user_service')\nconst md5password = require('../utils/md5')\n\nconst verifyLogin = async (ctx, next) =\u003e {\n  const { username, password } = ctx.request.body\n  // 1.验证用户名和密码是否为空\n  if(!username || !password) {\n    return ctx.app.emit('error', NAME_OR_PASSWORD_IS_EMPTY, ctx)\n  }\n\n  // 2.验证用户名在数据可中是否已存在\n  const users = await userService.findUserByName(username)\n  const user = users[0]\n  \n  if(!user) {\n    return ctx.app.emit('error', USER_IS_NOT_EXISTS, ctx)\n  }\n\n  // 3.验证密码是否正确\n  if(user.password !== md5password(password)) {\n    return ctx.app.emit('error', PASSWORD_IS_INCORRECT, ctx)\n  }\n\n  // 4.将user对象保存在ctx中\n  ctx.user = user\n\n  // 执行下一个中间件(颁发token)\n  await next()\n}\n\n// 很多请求都要用到验证用户，所用封装到中间件中\nconst verifyAuth = async (ctx, next) =\u003e {\n    // 1.获取token\n    const authorization =ctx.headers.authorization\n\n    if(!authorization) {\n      return ctx.app.emit('error', UNAUTHORIZATION, ctx)\n    }\n    const token = authorization.replace('Bearer ', '')\n    // 2.验证token是否有效\n    try{\n      // 2.1获取token信息\n      const result = jwt.verify(token, PUBLIC_KEY, {\n        algorithms: ['RS256']\n      })\n\n      // 2.2将token信息保存在ctx中\n      ctx.user = result\n  \n      // 3.执行下一个中间件\n      await next()\n    }catch (error) {\n      ctx.app.emit('error', UNAUTHORIZATION, ctx)\n    }\n}\n\nmodule.exports = {\n  verifyLogin,\n  verifyAuth\n}\n```\n\n\n\n### 3.编写controller层\n\n```JavaScript\n// controller/login_controller.js\nconst jwt = require('jsonwebtoken')\nconst { PRIVATE_KEY } = require('../config/srect')\n\nclass LoginRouter{\n  sign(ctx, next) {\n    // 1.获取用户id和密码\n    const { id, username } = ctx.user\n\n    // 2.颁发令牌\n    const token = jwt.sign({ id, username}, PRIVATE_KEY, { \n      expiresIn: 60 * 60 * 24, \n      algorithm: 'RS256' \n    })\n\n    // 3.返回用户信息\n    ctx.body = { code: 0, data: { id, username, token }}\n\n  }\n\n  // 验证token\n  test(ctx, next) {\n    ctx.body = { code: 0, data: '验证成功' }\n  }\n}\n\nmodule.exports = new LoginRouter()\n```\n\n\n\n## 权限模块\n\n### 1.编写权限中间件\n\n```JavaScript\n// middleware/permission_middleware.js\nconst { OPERATION_IS_NOT_ALLOWED } = require(\"../config/error\");\nconst permissionService = require(\"../service/permission_service\")\n\n// 验证：只能验证用户是否有操作moment的权限（不能验证操作其他的权限，可扩展性不强）\n// 方法一\nconst verifyMomentPermission = async (ctx, next) =\u003e {\n  // 获取修改动态的id\n  const { momentId } = ctx.params\n  // 获取登录用户id\n  const { id } = ctx.user\n\n  // 查询user的id是否有修改momentId的权限\n  const isPermission = await permissionService.checkMoment(momentId, id)\n  if(!isPermission) {\n    return ctx.app.emit('error', OPERATION_IS_NOT_ALLOWED, ctx)\n  }\n\n  // 执行下一个中间件\n  await next()\n}\n\n// 方法二(传入参数，实现动态权限认证)\nconst verifyPermission = function(resource) {\n  return async (ctx, next) =\u003e {\n    // 获取修改动态的id\n    const { momentId } = ctx.params\n    // 获取登录用户id\n    const { id } = ctx.user\n  \n    // 查询user的id是否有修改momentId的权限\n    const isPermission = await permissionService.checkMoment(momentId, id)\n    if(!isPermission) {\n      return ctx.app.emit('error', OPERATION_IS_NOT_ALLOWED, ctx)\n    }\n  \n    // 执行下一个中间件\n    await next()\n  }\n}\n\n// 方法三\nconst verifyPermission = async (ctx, next) =\u003e {\n  // 1.获取登录用户id\n  const { id } = ctx.user\n\n  // 2.获取中资源的name/id\n  // name =\u003e moment/user/comment\n  // params: { momentId: 7}\n  // keyName =\u003e momentId\n  const keyName = Object.keys(ctx.params)[0]\n  const resourceId = ctx.params[keyName]\n  const resourceName = keyName.replace('Id', '')\n\n  // 查询user的id是否有修改momentId的权限\n  const isPermission = await permissionService.checkResource(resourceName, resourceId, id)\n  if(!isPermission) {\n    return ctx.app.emit('error', OPERATION_IS_NOT_ALLOWED, ctx)\n  }\n\n  // 执行下一个中间件\n  await next()\n}\n\nmodule.exports = {\n  verifyMomentPermission,\n  verifyPermission\n}\n```\n\n### 2.编写service层\n\n```JavaScript\n// service/permission_service.js\nconst connection = require(\"../app/database\")\n\nclass PermissionService {\n  // 方法一\n  async checkMoment(momentId, userId) {\n    const statement = `select * from moment where id = ? and user_id = ?`\n    const [result] = await connection.execute(statement, [momentId, userId])\n    // 长度大于0时说明有权限（能查出对应的moment）\n    // !!：转化为boolean类型\n    // return !!result.length\n    return !!result.length\n  }\n\n  // 方法三\n  async checkResource(resourceName, resourceId, userId) {\n    const statement = `select * from ${resourceName} where id = ? and user_id = ?`\n    const [result] = await connection.execute(statement, [resourceId, userId])\n    return !!result.length\n  }\n}\n\nmodule.exports = new PermissionService()\n```\n\n\n\n## 动态模块\n\n- 添加动态（登录后）\n- 动态列表查询\n- 动态详情查询\n- 修改动态（登录后且有操作权限）\n- 为动态添加标签（多对多）\n\n### 1.编写router层\n\n```JavaScript\n// router/moment_router.js\nconst KoaRouter = require('@koa/router')\nconst { verifyAuth } = require('../middleware/login_middleware')\nconst { create, list, detail, update, remove, addLabels } = require('../controller/moment_controller')\nconst { verifyMomentPermission, verifyPermission} = require('../middleware/permission_middleware')\nconst verifyLabelExists = require('../middleware/label_middleware')\n\nconst momentRouter = new KoaRouter({ prefix: '/moment'}) \n\n// 增\nmomentRouter.post('/', verifyAuth, create)\n// 查\nmomentRouter.get('/', list)\nmomentRouter.get('/:momentId', detail)\n// 改（只有登录【verifyAuth】且有权限【verifyMomentPermission】才能改）\nmomentRouter.patch('/:momentId', verifyAuth, verifyPermission, update)\n// 删\nmomentRouter.delete('/:momentId', verifyAuth, verifyPermission, remove)\n\n// 添加标签\n// 中间件：\n//   1.是否登录\n//   2.验证是否有操作这个动态的权限\n//   3.额外中间件：验证label的name是否已经存在于label表中\n//   * 如果存在，那么直接使用即可\n//   * 如果不存在，那么需要先将label的name添加label表\n//   4.最终步骤\n//   * 所有的labels都已经在label表\n//   * 动态2和label关系，添加到关系表\nmomentRouter.post('/:momentId/labels', verifyLabelExists, addLabels)\n \nmodule.exports = momentRouter\n\n```\n\n### 2.编写`verifyLabelExists`中间件\n\n```JavaScript\nconst { OPERATION_IS_NOT_ALLOWED } = require(\"../config/error\");\nconst permissionService = require(\"../service/permission_service\")\n\n// 验证：只能验证用户是否有操作moment的权限（不能验证操作其他的权限，可扩展性不强）\n// 方法一\nconst verifyMomentPermission = async (ctx, next) =\u003e {\n  // 获取修改动态的id\n  const { momentId } = ctx.params\n  // 获取登录用户id\n  const { id } = ctx.user\n\n  // 查询user的id是否有修改momentId的权限\n  const isPermission = await permissionService.checkMoment(momentId, id)\n  if(!isPermission) {\n    return ctx.app.emit('error', OPERATION_IS_NOT_ALLOWED, ctx)\n  }\n\n  // 执行下一个中间件\n  await next()\n}\n// 方法二\nconst verifyPermission = async (ctx, next) =\u003e {\n  // 1.获取登录用户id\n  const { id } = ctx.user\n\n  // 2.获取中资源的name/id\n  // name =\u003e moment/user/comment\n  // params: { momentId: 7}\n  // keyName =\u003e momentId\n  const keyName = Object.keys(ctx.params)[0]\n  const resourceId = ctx.params[keyName]\n  const resourceName = keyName.replace('Id', '')\n\n  // 查询user的id是否有修改momentId的权限\n  const isPermission = await permissionService.checkResource(resourceName, resourceId, id)\n  if(!isPermission) {\n    return ctx.app.emit('error', OPERATION_IS_NOT_ALLOWED, ctx)\n  }\n\n  // 执行下一个中间件\n  await next()\n}\n\nmodule.exports = {\n  verifyMomentPermission,\n  verifyPermission\n}\n```\n\n\n\n\n\n### 3.编写controller层\n\n```JavaScript\n// controller/moment_controller.js\nconst { QUERY_IS_FAIL } = require(\"../config/error\");\nconst momentService = require(\"../service/moment_service\")\n\nclass MomentController{\n  async create(ctx, next) {\n    // 1.获取动态内容\n    const { content } = ctx.request.body\n\n    // 2.动态由谁发布（token =\u003e id/username）\n    // 由上一个中间件传递过来的ctx.user确定\n    const { id } = ctx.user\n\n    // 3.将动态相关数据保存到数据库中\n    const result = await momentService.create(content, id)\n\n    ctx.body = {\n      code: 0,\n      message: '创建用户动态成功',\n      data: result \n    }\n  }\n\n  async list(ctx, next) {\n    // 获取分页条件(moment?offset=0\u0026size=10 -\u003e query)\n    const { offset, size } = ctx.query\n\n    // 从数据库中查询动态表\n    const result = await momentService.queryList(offset, size)\n\n    // 返回结果\n    ctx.body = {\n      code: 0,\n      message: '查询动态成功',\n      data: result\n    }\n  }\n\n  async detail(ctx, next) {\n    // 1.获取动态的id(moment/1)\n    const { momentId } = ctx.params\n\n    // 2.根据id查询某一条动态\n    const result = await momentService.queryById(momentId)\n\n    // 3.若没有查到，返回错误信息\n    if(!result.length) {\n      return ctx.app.emit('error', QUERY_IS_FAIL, ctx)\n    }\n\n    // 4.成功则返回数据\n    ctx.body = {\n      code: 0,\n      message: '查询1条动态成功',\n      data: result[0]\n    }\n  }\n\n  async update(ctx, next) {\n    // 1.获取要修改的动态的id\n    const { momentId } = ctx.params \n\n    // 2.获取修改的内容\n    const { content } = ctx.request.body\n\n    const result = await momentService.updateMoment(momentId, content)\n\n    ctx.body = {\n      code: 0,\n      message: '修改动态成功',\n      data: result\n    }\n  }\n\n  async remove(ctx, next) {\n    const { momentId } = ctx.params\n    const result = await momentService.removeMoment(momentId)\n    ctx.body = {\n      code: 0,\n      message: '删除动态成功',\n      data: result\n    }\n  }\n\n  // 标签接口，给moment添加label\n  async addLabels(ctx, next) {\n    // 1.获取参数\n    const { labels } = ctx\n    const { momentId } = ctx.params\n\n    // 2.将moment_id和label_id添加到moment_label表中\n    try{\n      for(const label of labels) {\n        // 2.1判断label_id是否已经和moment_id已经存在该数据\n        const isExists = await momentService.hasLabel(momentId, label.id)\n        console.log(isExists);\n        if(!isExists) {\n          // 2.2不存在moment_id和label_id关系，则插入\n          const result = await momentService.addLabel(momentId, label.id)\n        }\n      }\n      ctx.body = {\n        code: 0,\n        message: '为动态添加标签成功',\n      }\n    }catch (error) {\n      console.log(error);\n      ctx.body = {\n        code: -3001,\n        message: '为动态添加标签失败'\n      }\n    }\n  }\n}\n\nmodule.exports = new MomentController()\n```\n\n### 4.编写service层\n  - 学习SQL的写法\n\n```JavaScript\n// service/moment_service.js\nconst connection = require(\"../app/database\")\n\nclass MomentService {\n  async create(content, userId) {\n    const statement = `insert into moment (content, user_id) values (?, ?)`\n    const [result] = await connection.execute(statement, [content, userId])\n    return result\n  }\n\n  // 默认offset = 0 ,size = 10\n  // 查询动态列表，包含评论个数，标签个数\n  async queryList(offset = 0, size = 10) {\n    const statement = `\n      select m.id id, m.content content, m.createAt createTime, m.updateAt updateTime, json_object('id', u.id, 'username', u.username, 'avatarUrl', u.avatar_url, 'createTime', u.createAt, 'updateTime', u.updateAt) user,\n      (select count(*) from comment where comment.moment_id = m.id) commentCount,\n      (select count(*) from moment_label ml where ml.moment_id = m.id) labelCount\n      from moment m\n      left join user u on u.id = m.user_id\n      limit ? offset ?\n    `\n    const [result] = await connection.execute(statement, [String(size), String(offset)])\n    return result\n  }\n  \n  // 查询动态详情，包含评论列表，用户列表\n  async queryById(id) {\n    const statement = `\n      select\n        m.id id, m.content content, m.createAt createTime, m.updateAt updateTime,\n        json_object('id', u.id, 'username', u.username, 'avatarUrl', u.avatar_url, 'createTime', u.createAt, 'updateTime', u.updateAt) user,\n        (\n          select \n            json_arrayagg(json_object(\n              'id', c.id, 'content', c.content, 'commentId', c.comment_id,\n              'user', json_object('id', cu.id, 'name', cu.username, 'avatarUrl', u.avatar_url)\n            ))\n          from comment c \n          left join user cu on c.user_id = cu.id\n          where c.moment_id = m.id\n        ) comments,\n        (\n          json_arrayagg(json_object(\n            'id', l.id, 'name', l.name\n          ))\n        ) labels\n      from moment m\n      left join user u on u.id = m.user_id \n      left join moment_label ml on ml.moment_id = m.id\n      left join label l on ml.label_id = l.id\n      where m.id = ?\n      group by m.id;\n    `\n    const [result] = await connection.execute(statement, [id])\n    return result\n  }\n\n  async updateMoment(id, content) {\n    const statement = `update moment set content = ? where id = ?`\n    const [result] = await connection.execute(statement, [content, id])\n    return result \n  }\n\n  async removeMoment(id) {\n    const statement = `delete from moment where id = ?`\n    const [result] = await connection.execute(statement, [id])\n    return result\n  }\n\n  // 判断标签和动态表关系是否存在\n  async hasLabel(momentId, labelId) {\n    const statement = `select * from moment_label where moment_id = ? and label_id = ?`\n    const [result] = await connection.execute(statement, [momentId, labelId])\n    return !!result.length\n  }\n\n  async addLabel(momentId, labelId) {\n    const statement = `insert into moment_label (moment_id, label_id) values (?, ?)`\n    const [result] = await connection.execute(statement, [momentId, labelId])\n    return result\n  }\n}\n\nmodule.exports = new MomentService()\n```\n\n\n\n## 评论模块\n\n- 发表评论\n- 回复评论\n\n### 1.编写router层\n\n```JavaScript\n// router/comment_router.js\nconst KoaRouter = require('@koa/router')\nconst { verifyAuth } = require('../middleware/login_middleware')\nconst { create, reply } = require('../controller/comment_controller')\n\nconst commentRouter = new KoaRouter({ prefix: '/comment'}) // 添加路由前缀 \n\n// 增：新增评论\ncommentRouter.post('/', verifyAuth, create)\n// 增：回复评论\ncommentRouter.post('/reply', verifyAuth, reply)\n\nmodule.exports = commentRouter\n\n```\n\n### 2.编写controller层\n\n```JavaScript\n// controller/comment_controller.js\nconst commentService = require(\"../service/comment_service\")\n\nclass commentController {\n  // 发表评论\n  async create(ctx, next) {\n    // 1.从body中获取参数\n    const { content, momentId } = ctx.request.body\n    const { id } = ctx.user\n    console.log(content, momentId, id);\n\n    // 2.操作数据库\n    const result = await commentService.create(content, momentId, id)\n    console.log(result);\n\n    ctx.body = {\n      code: 0,\n      message: '发表评论成功',\n      data: result \n    }\n  }\n\n  // 回复评论\n  async reply(ctx, next) {\n    // 1.从body中获取参数\n    const { content, momentId, commentId } = ctx.request.body\n    const { id } = ctx.user\n\n    // 2.操作数据库\n    const result = await commentService.reply(content, momentId, commentId, id)\n\n    ctx.body = {\n      code: 0,\n      message: '回复评论成功',\n      data: result \n    }\n  }\n}\n\nmodule.exports = new commentController()\n```\n\n### 3.编写service层\n\n```JavaScript\n// service/comment_service.js\nconst connection = require(\"../app/database\")\n\nclass CommentService {\n  async create(content, momentId, userId) {\n    const statement = `insert into comment (content, moment_id, user_id) values (?, ?, ?)`\n    const [result] = await connection.execute(statement, [content, momentId, userId])\n    return result\n  }\n\n  async reply(content, momentId, commentId, userId) {\n    const statement = `insert into comment (content, moment_id, comment_id, user_id) values (?, ?, ?, ?)`\n    const [result] = await connection.execute(statement, [content, momentId, commentId, userId])\n    return result\n  }\n}\n\nmodule.exports = new CommentService()\n\n```\n\n\n\n## 标签模块\n\n- 新增标签\n- 判断标签是否存在\n\n### 1.编写router层\n\n```JavaScript\n// router/label_router.js\nconst KoaRouter = require('@koa/router')\nconst { verifyAuth } = require('../middleware/login_middleware')\nconst { create, list } = require('../controller/label_controller')\n\nconst labelRouter = new KoaRouter({ prefix: '/label'}) // 添加路由前缀 \n\nlabelRouter.post('/', verifyAuth, create)\nlabelRouter.get('/', list)\n\nmodule.exports = labelRouter\n\n```\n\n### 2.编写`verifyLabelExists` 中间件\n\n```JavaScript\n// middleware/label_middleware.js\nconst labelService = require(\"../service/label_service\")\n\n// 传入label时，不确定labels是否有name已经存在label表中\n// 所以需要将labels都保存在label中，获取labels的id\n// 将获取的数据传递给下一个中间件\nconst verifyLabelExists = async (ctx, next) =\u003e {\n  // 1.获取客户端传递过来的所有labels\n  const { labels } = ctx.request.body\n\n  // 2.判断所有的labels中name是否已经存在于label表\n  const newLabels = []\n  for(const name of labels) {\n    const result = await labelService.queryLabelByName(name)\n    const labelObj = { name }\n    if(result) {\n      // 获取name对应的label的id =\u003e { name: \"篮球\", id: 1 }\n      labelObj.id = result.id\n    }else { \n      // 插入，并且获取插入之后的id =\u003e { name: \"游戏\", id: 7 }\n      const insertResult = await labelService.create(name)\n      labelObj.id = insertResult.insertId\n    } \n    newLabels.push(labelObj)\n  }\n\n  // 3.所有的label都变成[{ name: \"哲学\", id: 7}, { name: \"爱情\", id: 8 }]\n  ctx.labels = newLabels\n \n  await next()\n}\n\nmodule.exports = verifyLabelExists\n\n```\n\n\n\n### 3.编写controller层\n\n```JavaScript\n// controller/label_controller.js\nconst labelService = require(\"../service/label_service\")\n\nclass labelRouter {\n  async create(ctx, next) {\n    // 1.获取数据\n    const { name } = ctx.request.body\n\n    // 2.操作数据库\n    const result = await labelService.create(name)\n\n    ctx.body = {\n      code: 0,\n      message: '创建标签成功',\n      data: result \n    }\n  }\n  async list(ctx, next) {\n    ctx.body = {\n      code: 0,\n      message: '获取标签成功',\n      data: result \n    }\n  }\n}\n\nmodule.exports = new labelRouter()\n```\n\n### 4.编写service层\n\n```JavaScript\n// service/label_service.js\nconst connection = require(\"../app/database\")\n\nclass LabelService {\n  async create(name) {\n    const statement = `insert into label (name) values (?)`\n    const [result] = await connection.execute(statement, [name])\n    return result\n  }\n\n  async queryLabelByName(name) {\n    const statement = `select * from label where name = ?`\n    const [result] = await connection.execute(statement, [name])\n    return result[0]\n  }\n\n}\nmodule.exports = new LabelService()\n```\n\n## 头像上传\n\n### 1.编写router层\n\n```JavaScript\n// router/file_router.js\nconst KoaRouter = require('@koa/router')\nconst { verifyAuth } = require('../middleware/login_middleware');\nconst { handleAvatar } = require('../middleware/file_middleware');\nconst { create } = require('../controller/file_controller');\n\nconst fileRouter = new KoaRouter({ prefix: '/file'})  \n\n// 头像上传\nfileRouter.post('/avatar', verifyAuth, handleAvatar, create)\n\nmodule.exports = fileRouter\n\n```\n\n### 2.编写`handleAvatar` 中间件\n\n```JavaScript\n// middleware/file_middleware.js\nconst multer = require('@koa/multer')\nconst { UPLOAD_PATH } = require('../config/path')\n\n// 上传头像中间件\nconst uploadAvatar = multer({\n  dest: UPLOAD_PATH\n})\n\nconst handleAvatar = uploadAvatar.single('avatar')\n\nmodule.exports = {\n  handleAvatar\n}\n```\n\n### 3.编写controller层\n\n```JavaScript\n// controller/file_controller.js\nconst fileService = require(\"../service/file_service\")\nconst userService = require(\"../service/user_service\")\nconst { SERVER_HOST, SERVER_PORT } = require('../config/server')\n\nclass fileController {\n  async create(ctx, next) {\n    // 1.获取对应的信息\n    const { filename, mimetype, size } =ctx.request.file\n    const { id } = ctx.user\n\n    // 2.将图片信息和id结合起来进行数据库存储\n    const result = await fileService.create(filename, mimetype, size, id)\n\n    // 3.将头像的url，保存在user表\n    const avatarUrl = `${SERVER_HOST}:${SERVER_PORT}/users/avatar/${id}`\n    const result2 = await userService.updateUserAvatar(avatarUrl, id)\n\n    // 4.返回结果\n    ctx.body = {\n      code: 0,\n      message: '头像上传成功',\n      data: avatarUrl\n    }\n  }\n}\n\nmodule.exports = new fileController()\n\n```\n\n### 4.编写service层\n\n```JavaScript\n// service/file_service.js\nconst connection = require(\"../app/database\")\n\nclass FileService {\n  async create(filename, mimetype, size, userId) {\n    const statement = `insert into avatar (filename, mimetype, size, user_id) values (?, ?, ?, ?)`\n    const [result] = await connection.execute(statement, [filename, mimetype, size, userId])\n    return result\n  }\n\n  async queryAvatarWithUserId(userId) {\n    const statement = `select * from avatar where user_id = ?`\n    const [result] = await connection.execute(statement, [userId])\n    // 拿到最新的头像\n    return result.pop()\n  }\n}\n\nmodule.exports = new FileService()\n\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxumingyu2018%2Fcoderhub","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fxumingyu2018%2Fcoderhub","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxumingyu2018%2Fcoderhub/lists"}