# coderhub **Repository Path**: NevermoreYu/coderhub ## Basic Information - **Project Name**: coderhub - **Description**: node.js koa框架项目实战 - **Primary Language**: NodeJS - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2023-06-08 - **Last Updated**: 2023-06-08 ## Categories & Tags **Categories**: Uncategorized **Tags**: Koa, MySQL, Nodejs ## README # coderhub node.js koa框架项目实战笔记 ## 全局配置类 ### 1.错误信息配置 ```JavaScript // config/error.js const NAME_OR_PASSWORD_IS_EMPTY = 'name_or_password_is_empty' const USER_ALREADY_EXISTS = 'user_already_exists' const USER_IS_NOT_EXISTS = 'user_is_not_exists' const PASSWORD_IS_INCORRECT = 'password_is_incorrect' const UNAUTHORIZATION = 'unauthorization' const OPERATION_IS_NOT_ALLOWED = 'operation_is_not_allowed' const QUERY_IS_FAIL = 'query_is_fail' module.exports = { NAME_OR_PASSWORD_IS_EMPTY, USER_ALREADY_EXISTS, USER_IS_NOT_EXISTS, PASSWORD_IS_INCORRECT, UNAUTHORIZATION, OPERATION_IS_NOT_ALLOWED, QUERY_IS_FAIL } ``` ### 2.文件上传路径配置 ```JavaScript // config/path.js const UPLOAD_PATH = './uploads' module.exports = { UPLOAD_PATH } ``` ### 3.服务主机端口号配置 安装:`npm install dotenv ` ```JavaScript // config/server.js const dotenv = require('dotenv') dotenv.config() module.exports = { SERVER_PORT, SERVER_HOST } = process.env // 从环境变量中解构获取 ``` ```JavaScript // .env SERVER_HOST = http://localhost SERVER_PORT = 8000 ``` ### 4.密钥加密配置 ```JavaScript // config/srect.js const fs = require('fs') // 默认情况下相对目录和node程序的启动目录有关系("start": "nodemon ./src/main.js") const PRIVATE_KEY = fs.readFileSync('./src/config/keys/private.key') const PUBLIC_KEY = fs.readFileSync('./src/config/keys/public.key') module.exports = { PRIVATE_KEY, PUBLIC_KEY } ``` ### 5.数据库配置 ```JavaScript // app/database.js const mysql = require('mysql2') // 1.创建连接池 const connectionPool = mysql.createPool({ host: 'localhost', port: 3306, database: 'coderhub', user: 'root', password: '123456', connectionLimit: 5 }) // 2.获取连接是否成功 connectionPool.getConnection((err, connection) => { // 1.判断是否有错误信息 if(err) { console.log("连接失败", err); return } // 2.获取connection,尝试和数据库建立一下连接 connection.connect((err) => { if(err) { console.log("和数据库交互失败", err); }else { console.log("数据库连接成功,可以操作数据库"); } }) }) // 3.获取到连接池中连接对象(promise) const connection = connectionPool.promise() module.exports = connection ``` ### 6.app启动配置 ```JavaScript // app/index.js // 抽取app const Koa = require('koa') const bodyParser = require('koa-bodyparser') const registerRouters = require('../router') // 1.创建app const app = new Koa() // 2.对app使用中间件 app.use(bodyParser()) // 动态加载路由 registerRouters(app) // 3.导出app module.exports = app ``` ### 7.动态加载路由(在app中自动注册) ```JavaScript // router/index.js const fs = require('fs') function registerRouters(app) { // 1.读取当前文件下的所有文件 const files = fs.readdirSync(__dirname) // 2.遍历所有文件 for(const file of files) { // 只留下带router.js结尾的文件 if(!file.endsWith('_router.js')) continue const router =require(`./${file}`) app.use(router.routes()) app.use(router.allowedMethods()) } } module.exports = registerRouters ``` ### 8. 主程序启动配置 ```JavaScript // main.js const app = require('./app') const { SERVER_PORT } = require('./config/server') require('./utils/handle-error') // 2.将app启动 app.listen(SERVER_PORT, () => { console.log('koa服务器启动成功') }) ``` ## 工具类 ### 1.异常处理 ```JavaScript // utils/handle-error.js const app = require("../app"); const { 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"); // 注意:在main.js中需要引入一下 app.on('error', (error, ctx) => { let code = 0 let message ='' switch(error) { case NAME_OR_PASSWORD_IS_EMPTY: code = -1001, message = '账户或密码为空' break case USER_ALREADY_EXISTS: code = -1002, message = '用户已存在' break case USER_IS_NOT_EXISTS: code = -1003, message = '用户不存在' break case PASSWORD_IS_INCORRECT: code = -1004, message = '密码不正确' break case UNAUTHORIZATION: code = -1005, message = '无效的token' break case QUERY_IS_FAIL: code = -1006, message = '查询失败,请检查数据是否存在' break case OPERATION_IS_NOT_ALLOWED: code = -2001, message = '本次操作没有权限' break } ctx.body = {code, message} }) ``` ### 2.md5加密 ```JavaScript // md5.js const crypto = require('crypto') function md5password(password) { const md5 = crypto.createHash('md5') // 使用md5 16进制加密 const md5pwd = md5.update(password).digest('hex') return md5pwd } module.exports = md5password ``` ## 用户模块 - 用户注册接口 - 用户头像查看接口 ### 1.编写router层 ```JavaScript // router/user_router.js const KoaRouter = require('@koa/router') const { create, showAvatarImage } = require('../controller/user_controller') const { verifyUser, handlePassword } = require('../middleware/user_middleware') // 1.创建路由对象 const userRouter = new KoaRouter({ prefix: '/users'}) // 添加路由前缀 // 2.定义路由中映射(这里只做映射处理,不做具体的业务处理) // 2.1.用户注册接口 userRouter.post('/', verifyUser, handlePassword, create) // 2.2用户头像查看 userRouter.get('/avatar/:userId', showAvatarImage) // 3.导出路由 module.exports = userRouter ``` ### 2.编写`verifyUser` `handlePassword`中间件 - 验证账户 - 加密 ```JavaScript // middleware/user_middleware.js const { NAME_OR_PASSWORD_IS_EMPTY, USER_ALREADY_EXISTS } = require("../config/error") const userService = require("../service/user_service") const md5password = require('../utils/md5') const verifyUser = async (ctx, next) => { // 1.验证用户名和密码是否为空 const { username, password } = ctx.request.body if(!username || !password) { return ctx.app.emit('error', NAME_OR_PASSWORD_IS_EMPTY, ctx) } // 2.验证用户名是否已存在 const users = await userService.findUserByName(username) if(users.length) { return ctx.app.emit('error', USER_ALREADY_EXISTS, ctx) } // 3.执行下一个中间件(异步) await next() } const handlePassword = async (ctx, next) => { // 1.取出密码 const { password } = ctx.request.body // 2.对密码进行加密 ctx.request.body.password = md5password(password) // 3.执行下一个中间件 await next() } module.exports = { verifyUser, handlePassword } ``` ### 3.编写controller层 ```JavaScript // controller/user_controller.js const { UPLOAD_PATH } = require("../config/path"); const fileService = require("../service/file_service"); const userService = require("../service/user_service"); const fs = require('fs') class UserController { async create(ctx, next) { // 1.获取用户传递过来的数据 const user = ctx.request.body // 2.将用户数据保存到数据库中 const result = await userService.create(user) // 3.查看存储结果,告知前端创建成功 ctx.body = { message: '用户创建成功', data: result } } async showAvatarImage(ctx, next) { // 1.获取用户id const { userId } = ctx.params // 2.获取userId对应的头像 const avatarInfo = await fileService.queryAvatarWithUserId(userId) // 3.读取头像所在的文件 const { filename, mimetype } = avatarInfo // 若不加这一句,浏览器会以文件方式下载图片 ctx.type = mimetype ctx.body = fs.createReadStream(`${UPLOAD_PATH}/${filename}`) } } module.exports = new UserController() ``` ### 4.编写service层 ```JavaScript // service/user_service.js const connection = require('../app/database') class UserService { // 添加用户 async create(user) { // 1.获取用户user const { username, password } = user // 2.拼接statement sql语句 const statement = 'insert into `user` (username, password) values (?, ?);' // 3.执行statement const [result] = await connection.execute(statement, [username, password]) return result } // 根据用户名获取用户信息 async findUserByName(username) { const statement = 'select * from `user` where username = ?;' const [values] = await connection.execute(statement, [username]) return values } // 修改头像 async updateUserAvatar(avatarUrl, userId) { const statement = `update user set avatar_url = ? where id = ?` const [result] = await connection.execute(statement, [avatarUrl, userId]) return result } } module.exports = new UserService() ``` ## 登录模块 ### 1.编写router层 ```JavaScript // router/login_router.js const KoaRouter = require('@koa/router') const { sign, test } = require('../controller/login_controller') const { verifyLogin, verifyAuth} = require('../middleware/login_middleware') const loginRouter = new KoaRouter({ prefix: '/login'}) // 添加路由前缀 loginRouter.post('/', verifyLogin, sign) loginRouter.get('/verify', verifyAuth, test) module.exports = loginRouter ``` ### 2.编写`verifyLogin` `verifyAuth`中间件 ```JavaScript // middleware/login_middleware.js const jwt = require('jsonwebtoken') const { NAME_OR_PASSWORD_IS_EMPTY, USER_IS_NOT_EXISTS, PASSWORD_IS_INCORRECT, UNAUTHORIZATION } = require('../config/error') const { PUBLIC_KEY } = require('../config/srect') const userService = require('../service/user_service') const md5password = require('../utils/md5') const verifyLogin = async (ctx, next) => { const { username, password } = ctx.request.body // 1.验证用户名和密码是否为空 if(!username || !password) { return ctx.app.emit('error', NAME_OR_PASSWORD_IS_EMPTY, ctx) } // 2.验证用户名在数据可中是否已存在 const users = await userService.findUserByName(username) const user = users[0] if(!user) { return ctx.app.emit('error', USER_IS_NOT_EXISTS, ctx) } // 3.验证密码是否正确 if(user.password !== md5password(password)) { return ctx.app.emit('error', PASSWORD_IS_INCORRECT, ctx) } // 4.将user对象保存在ctx中 ctx.user = user // 执行下一个中间件(颁发token) await next() } // 很多请求都要用到验证用户,所用封装到中间件中 const verifyAuth = async (ctx, next) => { // 1.获取token const authorization =ctx.headers.authorization if(!authorization) { return ctx.app.emit('error', UNAUTHORIZATION, ctx) } const token = authorization.replace('Bearer ', '') // 2.验证token是否有效 try{ // 2.1获取token信息 const result = jwt.verify(token, PUBLIC_KEY, { algorithms: ['RS256'] }) // 2.2将token信息保存在ctx中 ctx.user = result // 3.执行下一个中间件 await next() }catch (error) { ctx.app.emit('error', UNAUTHORIZATION, ctx) } } module.exports = { verifyLogin, verifyAuth } ``` ### 3.编写controller层 ```JavaScript // controller/login_controller.js const jwt = require('jsonwebtoken') const { PRIVATE_KEY } = require('../config/srect') class LoginRouter{ sign(ctx, next) { // 1.获取用户id和密码 const { id, username } = ctx.user // 2.颁发令牌 const token = jwt.sign({ id, username}, PRIVATE_KEY, { expiresIn: 60 * 60 * 24, algorithm: 'RS256' }) // 3.返回用户信息 ctx.body = { code: 0, data: { id, username, token }} } // 验证token test(ctx, next) { ctx.body = { code: 0, data: '验证成功' } } } module.exports = new LoginRouter() ``` ## 权限模块 ### 1.编写权限中间件 ```JavaScript // middleware/permission_middleware.js const { OPERATION_IS_NOT_ALLOWED } = require("../config/error"); const permissionService = require("../service/permission_service") // 验证:只能验证用户是否有操作moment的权限(不能验证操作其他的权限,可扩展性不强) // 方法一 const verifyMomentPermission = async (ctx, next) => { // 获取修改动态的id const { momentId } = ctx.params // 获取登录用户id const { id } = ctx.user // 查询user的id是否有修改momentId的权限 const isPermission = await permissionService.checkMoment(momentId, id) if(!isPermission) { return ctx.app.emit('error', OPERATION_IS_NOT_ALLOWED, ctx) } // 执行下一个中间件 await next() } // 方法二(传入参数,实现动态权限认证) const verifyPermission = function(resource) { return async (ctx, next) => { // 获取修改动态的id const { momentId } = ctx.params // 获取登录用户id const { id } = ctx.user // 查询user的id是否有修改momentId的权限 const isPermission = await permissionService.checkMoment(momentId, id) if(!isPermission) { return ctx.app.emit('error', OPERATION_IS_NOT_ALLOWED, ctx) } // 执行下一个中间件 await next() } } // 方法三 const verifyPermission = async (ctx, next) => { // 1.获取登录用户id const { id } = ctx.user // 2.获取中资源的name/id // name => moment/user/comment // params: { momentId: 7} // keyName => momentId const keyName = Object.keys(ctx.params)[0] const resourceId = ctx.params[keyName] const resourceName = keyName.replace('Id', '') // 查询user的id是否有修改momentId的权限 const isPermission = await permissionService.checkResource(resourceName, resourceId, id) if(!isPermission) { return ctx.app.emit('error', OPERATION_IS_NOT_ALLOWED, ctx) } // 执行下一个中间件 await next() } module.exports = { verifyMomentPermission, verifyPermission } ``` ### 2.编写service层 ```JavaScript // service/permission_service.js const connection = require("../app/database") class PermissionService { // 方法一 async checkMoment(momentId, userId) { const statement = `select * from moment where id = ? and user_id = ?` const [result] = await connection.execute(statement, [momentId, userId]) // 长度大于0时说明有权限(能查出对应的moment) // !!:转化为boolean类型 // return !!result.length return !!result.length } // 方法三 async checkResource(resourceName, resourceId, userId) { const statement = `select * from ${resourceName} where id = ? and user_id = ?` const [result] = await connection.execute(statement, [resourceId, userId]) return !!result.length } } module.exports = new PermissionService() ``` ## 动态模块 - 添加动态(登录后) - 动态列表查询 - 动态详情查询 - 修改动态(登录后且有操作权限) - 为动态添加标签(多对多) ### 1.编写router层 ```JavaScript // router/moment_router.js const KoaRouter = require('@koa/router') const { verifyAuth } = require('../middleware/login_middleware') const { create, list, detail, update, remove, addLabels } = require('../controller/moment_controller') const { verifyMomentPermission, verifyPermission} = require('../middleware/permission_middleware') const verifyLabelExists = require('../middleware/label_middleware') const momentRouter = new KoaRouter({ prefix: '/moment'}) // 增 momentRouter.post('/', verifyAuth, create) // 查 momentRouter.get('/', list) momentRouter.get('/:momentId', detail) // 改(只有登录【verifyAuth】且有权限【verifyMomentPermission】才能改) momentRouter.patch('/:momentId', verifyAuth, verifyPermission, update) // 删 momentRouter.delete('/:momentId', verifyAuth, verifyPermission, remove) // 添加标签 // 中间件: // 1.是否登录 // 2.验证是否有操作这个动态的权限 // 3.额外中间件:验证label的name是否已经存在于label表中 // * 如果存在,那么直接使用即可 // * 如果不存在,那么需要先将label的name添加label表 // 4.最终步骤 // * 所有的labels都已经在label表 // * 动态2和label关系,添加到关系表 momentRouter.post('/:momentId/labels', verifyLabelExists, addLabels) module.exports = momentRouter ``` ### 2.编写`verifyLabelExists`中间件 ```JavaScript const { OPERATION_IS_NOT_ALLOWED } = require("../config/error"); const permissionService = require("../service/permission_service") // 验证:只能验证用户是否有操作moment的权限(不能验证操作其他的权限,可扩展性不强) // 方法一 const verifyMomentPermission = async (ctx, next) => { // 获取修改动态的id const { momentId } = ctx.params // 获取登录用户id const { id } = ctx.user // 查询user的id是否有修改momentId的权限 const isPermission = await permissionService.checkMoment(momentId, id) if(!isPermission) { return ctx.app.emit('error', OPERATION_IS_NOT_ALLOWED, ctx) } // 执行下一个中间件 await next() } // 方法二 const verifyPermission = async (ctx, next) => { // 1.获取登录用户id const { id } = ctx.user // 2.获取中资源的name/id // name => moment/user/comment // params: { momentId: 7} // keyName => momentId const keyName = Object.keys(ctx.params)[0] const resourceId = ctx.params[keyName] const resourceName = keyName.replace('Id', '') // 查询user的id是否有修改momentId的权限 const isPermission = await permissionService.checkResource(resourceName, resourceId, id) if(!isPermission) { return ctx.app.emit('error', OPERATION_IS_NOT_ALLOWED, ctx) } // 执行下一个中间件 await next() } module.exports = { verifyMomentPermission, verifyPermission } ``` ### 3.编写controller层 ```JavaScript // controller/moment_controller.js const { QUERY_IS_FAIL } = require("../config/error"); const momentService = require("../service/moment_service") class MomentController{ async create(ctx, next) { // 1.获取动态内容 const { content } = ctx.request.body // 2.动态由谁发布(token => id/username) // 由上一个中间件传递过来的ctx.user确定 const { id } = ctx.user // 3.将动态相关数据保存到数据库中 const result = await momentService.create(content, id) ctx.body = { code: 0, message: '创建用户动态成功', data: result } } async list(ctx, next) { // 获取分页条件(moment?offset=0&size=10 -> query) const { offset, size } = ctx.query // 从数据库中查询动态表 const result = await momentService.queryList(offset, size) // 返回结果 ctx.body = { code: 0, message: '查询动态成功', data: result } } async detail(ctx, next) { // 1.获取动态的id(moment/1) const { momentId } = ctx.params // 2.根据id查询某一条动态 const result = await momentService.queryById(momentId) // 3.若没有查到,返回错误信息 if(!result.length) { return ctx.app.emit('error', QUERY_IS_FAIL, ctx) } // 4.成功则返回数据 ctx.body = { code: 0, message: '查询1条动态成功', data: result[0] } } async update(ctx, next) { // 1.获取要修改的动态的id const { momentId } = ctx.params // 2.获取修改的内容 const { content } = ctx.request.body const result = await momentService.updateMoment(momentId, content) ctx.body = { code: 0, message: '修改动态成功', data: result } } async remove(ctx, next) { const { momentId } = ctx.params const result = await momentService.removeMoment(momentId) ctx.body = { code: 0, message: '删除动态成功', data: result } } // 标签接口,给moment添加label async addLabels(ctx, next) { // 1.获取参数 const { labels } = ctx const { momentId } = ctx.params // 2.将moment_id和label_id添加到moment_label表中 try{ for(const label of labels) { // 2.1判断label_id是否已经和moment_id已经存在该数据 const isExists = await momentService.hasLabel(momentId, label.id) console.log(isExists); if(!isExists) { // 2.2不存在moment_id和label_id关系,则插入 const result = await momentService.addLabel(momentId, label.id) } } ctx.body = { code: 0, message: '为动态添加标签成功', } }catch (error) { console.log(error); ctx.body = { code: -3001, message: '为动态添加标签失败' } } } } module.exports = new MomentController() ``` ### 4.编写service层 - 学习SQL的写法 ```JavaScript // service/moment_service.js const connection = require("../app/database") class MomentService { async create(content, userId) { const statement = `insert into moment (content, user_id) values (?, ?)` const [result] = await connection.execute(statement, [content, userId]) return result } // 默认offset = 0 ,size = 10 // 查询动态列表,包含评论个数,标签个数 async queryList(offset = 0, size = 10) { const statement = ` 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, (select count(*) from comment where comment.moment_id = m.id) commentCount, (select count(*) from moment_label ml where ml.moment_id = m.id) labelCount from moment m left join user u on u.id = m.user_id limit ? offset ? ` const [result] = await connection.execute(statement, [String(size), String(offset)]) return result } // 查询动态详情,包含评论列表,用户列表 async queryById(id) { const statement = ` 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, ( select json_arrayagg(json_object( 'id', c.id, 'content', c.content, 'commentId', c.comment_id, 'user', json_object('id', cu.id, 'name', cu.username, 'avatarUrl', u.avatar_url) )) from comment c left join user cu on c.user_id = cu.id where c.moment_id = m.id ) comments, ( json_arrayagg(json_object( 'id', l.id, 'name', l.name )) ) labels from moment m left join user u on u.id = m.user_id left join moment_label ml on ml.moment_id = m.id left join label l on ml.label_id = l.id where m.id = ? group by m.id; ` const [result] = await connection.execute(statement, [id]) return result } async updateMoment(id, content) { const statement = `update moment set content = ? where id = ?` const [result] = await connection.execute(statement, [content, id]) return result } async removeMoment(id) { const statement = `delete from moment where id = ?` const [result] = await connection.execute(statement, [id]) return result } // 判断标签和动态表关系是否存在 async hasLabel(momentId, labelId) { const statement = `select * from moment_label where moment_id = ? and label_id = ?` const [result] = await connection.execute(statement, [momentId, labelId]) return !!result.length } async addLabel(momentId, labelId) { const statement = `insert into moment_label (moment_id, label_id) values (?, ?)` const [result] = await connection.execute(statement, [momentId, labelId]) return result } } module.exports = new MomentService() ``` ## 评论模块 - 发表评论 - 回复评论 ### 1.编写router层 ```JavaScript // router/comment_router.js const KoaRouter = require('@koa/router') const { verifyAuth } = require('../middleware/login_middleware') const { create, reply } = require('../controller/comment_controller') const commentRouter = new KoaRouter({ prefix: '/comment'}) // 添加路由前缀 // 增:新增评论 commentRouter.post('/', verifyAuth, create) // 增:回复评论 commentRouter.post('/reply', verifyAuth, reply) module.exports = commentRouter ``` ### 2.编写controller层 ```JavaScript // controller/comment_controller.js const commentService = require("../service/comment_service") class commentController { // 发表评论 async create(ctx, next) { // 1.从body中获取参数 const { content, momentId } = ctx.request.body const { id } = ctx.user console.log(content, momentId, id); // 2.操作数据库 const result = await commentService.create(content, momentId, id) console.log(result); ctx.body = { code: 0, message: '发表评论成功', data: result } } // 回复评论 async reply(ctx, next) { // 1.从body中获取参数 const { content, momentId, commentId } = ctx.request.body const { id } = ctx.user // 2.操作数据库 const result = await commentService.reply(content, momentId, commentId, id) ctx.body = { code: 0, message: '回复评论成功', data: result } } } module.exports = new commentController() ``` ### 3.编写service层 ```JavaScript // service/comment_service.js const connection = require("../app/database") class CommentService { async create(content, momentId, userId) { const statement = `insert into comment (content, moment_id, user_id) values (?, ?, ?)` const [result] = await connection.execute(statement, [content, momentId, userId]) return result } async reply(content, momentId, commentId, userId) { const statement = `insert into comment (content, moment_id, comment_id, user_id) values (?, ?, ?, ?)` const [result] = await connection.execute(statement, [content, momentId, commentId, userId]) return result } } module.exports = new CommentService() ``` ## 标签模块 - 新增标签 - 判断标签是否存在 ### 1.编写router层 ```JavaScript // router/label_router.js const KoaRouter = require('@koa/router') const { verifyAuth } = require('../middleware/login_middleware') const { create, list } = require('../controller/label_controller') const labelRouter = new KoaRouter({ prefix: '/label'}) // 添加路由前缀 labelRouter.post('/', verifyAuth, create) labelRouter.get('/', list) module.exports = labelRouter ``` ### 2.编写`verifyLabelExists` 中间件 ```JavaScript // middleware/label_middleware.js const labelService = require("../service/label_service") // 传入label时,不确定labels是否有name已经存在label表中 // 所以需要将labels都保存在label中,获取labels的id // 将获取的数据传递给下一个中间件 const verifyLabelExists = async (ctx, next) => { // 1.获取客户端传递过来的所有labels const { labels } = ctx.request.body // 2.判断所有的labels中name是否已经存在于label表 const newLabels = [] for(const name of labels) { const result = await labelService.queryLabelByName(name) const labelObj = { name } if(result) { // 获取name对应的label的id => { name: "篮球", id: 1 } labelObj.id = result.id }else { // 插入,并且获取插入之后的id => { name: "游戏", id: 7 } const insertResult = await labelService.create(name) labelObj.id = insertResult.insertId } newLabels.push(labelObj) } // 3.所有的label都变成[{ name: "哲学", id: 7}, { name: "爱情", id: 8 }] ctx.labels = newLabels await next() } module.exports = verifyLabelExists ``` ### 3.编写controller层 ```JavaScript // controller/label_controller.js const labelService = require("../service/label_service") class labelRouter { async create(ctx, next) { // 1.获取数据 const { name } = ctx.request.body // 2.操作数据库 const result = await labelService.create(name) ctx.body = { code: 0, message: '创建标签成功', data: result } } async list(ctx, next) { ctx.body = { code: 0, message: '获取标签成功', data: result } } } module.exports = new labelRouter() ``` ### 4.编写service层 ```JavaScript // service/label_service.js const connection = require("../app/database") class LabelService { async create(name) { const statement = `insert into label (name) values (?)` const [result] = await connection.execute(statement, [name]) return result } async queryLabelByName(name) { const statement = `select * from label where name = ?` const [result] = await connection.execute(statement, [name]) return result[0] } } module.exports = new LabelService() ``` ## 头像上传 ### 1.编写router层 ```JavaScript // router/file_router.js const KoaRouter = require('@koa/router') const { verifyAuth } = require('../middleware/login_middleware'); const { handleAvatar } = require('../middleware/file_middleware'); const { create } = require('../controller/file_controller'); const fileRouter = new KoaRouter({ prefix: '/file'}) // 头像上传 fileRouter.post('/avatar', verifyAuth, handleAvatar, create) module.exports = fileRouter ``` ### 2.编写`handleAvatar` 中间件 ```JavaScript // middleware/file_middleware.js const multer = require('@koa/multer') const { UPLOAD_PATH } = require('../config/path') // 上传头像中间件 const uploadAvatar = multer({ dest: UPLOAD_PATH }) const handleAvatar = uploadAvatar.single('avatar') module.exports = { handleAvatar } ``` ### 3.编写controller层 ```JavaScript // controller/file_controller.js const fileService = require("../service/file_service") const userService = require("../service/user_service") const { SERVER_HOST, SERVER_PORT } = require('../config/server') class fileController { async create(ctx, next) { // 1.获取对应的信息 const { filename, mimetype, size } =ctx.request.file const { id } = ctx.user // 2.将图片信息和id结合起来进行数据库存储 const result = await fileService.create(filename, mimetype, size, id) // 3.将头像的url,保存在user表 const avatarUrl = `${SERVER_HOST}:${SERVER_PORT}/users/avatar/${id}` const result2 = await userService.updateUserAvatar(avatarUrl, id) // 4.返回结果 ctx.body = { code: 0, message: '头像上传成功', data: avatarUrl } } } module.exports = new fileController() ``` ### 4.编写service层 ```JavaScript // service/file_service.js const connection = require("../app/database") class FileService { async create(filename, mimetype, size, userId) { const statement = `insert into avatar (filename, mimetype, size, user_id) values (?, ?, ?, ?)` const [result] = await connection.execute(statement, [filename, mimetype, size, userId]) return result } async queryAvatarWithUserId(userId) { const statement = `select * from avatar where user_id = ?` const [result] = await connection.execute(statement, [userId]) // 拿到最新的头像 return result.pop() } } module.exports = new FileService() ```