├── .DS_Store ├── .gitignore ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── src ├── app │ ├── config.js │ ├── database.js │ ├── error-handle.js │ ├── index.js │ └── keys │ │ ├── private.key │ │ └── public.key ├── constants │ ├── error-types.js │ └── file-path.js ├── controller │ ├── auth.controller.js │ ├── comment.controller.js │ ├── file.controller.js │ ├── label.controller.js │ ├── moment.controller.js │ └── user.controller.js ├── main.js ├── middleware │ ├── auth.middleware.js │ ├── file.middleware.js │ ├── label.middleware.js │ └── user.middleware.js ├── router │ ├── auth.router.js │ ├── comment.router.js │ ├── file.router.js │ ├── index.js │ ├── label.router.js │ ├── moment.router.js │ └── user.router.js ├── service │ ├── auth.service.js │ ├── comment.service.js │ ├── file.service.js │ ├── label.service.js │ ├── moment.service.js │ └── user.service.js └── utils │ └── password-handle.js └── test.js /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwhy/coderhub/894a297e3081c72af10680162bb589aa59bb96fe/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | 106 | # uploads files 107 | uploads/ 108 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 coderwhy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # coderhub 2 | A coderhub developed using node koa 3 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "coderhub", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "nodemon ./src/main.js" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "dotenv": "^8.2.0", 15 | "jimp": "^0.16.1", 16 | "jsonwebtoken": "^8.5.1", 17 | "koa": "^2.13.0", 18 | "koa-bodyparser": "^4.3.0", 19 | "koa-multer": "^1.0.2", 20 | "koa-router": "^10.0.0", 21 | "mysql2": "^2.2.5" 22 | }, 23 | "devDependencies": { 24 | "nodemon": "^2.0.6" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/app/config.js: -------------------------------------------------------------------------------- 1 | const dotenv = require('dotenv'); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | 5 | dotenv.config(); 6 | 7 | const PRIVATE_KEY = fs.readFileSync(path.resolve(__dirname, './keys/private.key')); 8 | const PUBLIC_KEY = fs.readFileSync(path.resolve(__dirname, './keys/public.key')); 9 | 10 | module.exports = { 11 | APP_HOST, 12 | APP_PORT, 13 | MYSQL_HOST, 14 | MYSQL_PORT, 15 | MYSQL_DATABASE, 16 | MYSQL_USER, 17 | MYSQL_PASSWORD, 18 | } = process.env; 19 | 20 | module.exports.PRIVATE_KEY = PRIVATE_KEY; 21 | module.exports.PUBLIC_KEY = PUBLIC_KEY; -------------------------------------------------------------------------------- /src/app/database.js: -------------------------------------------------------------------------------- 1 | const mysql = require('mysql2'); 2 | 3 | const config = require('./config'); 4 | 5 | const connections = mysql.createPool({ 6 | host: config.MYSQL_HOST, 7 | port: config.MYSQL_PORT, 8 | database: config.MYSQL_DATABASE, 9 | user: config.MYSQL_USER, 10 | password: config.MYSQL_PASSWORD 11 | }); 12 | 13 | connections.getConnection((err, conn) => { 14 | conn.connect((err) => { 15 | if (err) { 16 | console.log("连接失败:", err); 17 | } else { 18 | console.log("数据库连接成功~"); 19 | } 20 | }) 21 | }); 22 | 23 | module.exports = connections.promise(); 24 | 25 | -------------------------------------------------------------------------------- /src/app/error-handle.js: -------------------------------------------------------------------------------- 1 | const errorTypes = require('../constants/error-types'); 2 | 3 | const errorHandler = (error, ctx) => { 4 | let status, message; 5 | 6 | switch (error.message) { 7 | case errorTypes.NAME_OR_PASSWORD_IS_REQUIRED: 8 | status = 400; // Bad Request 9 | message = "用户名或者密码不能为空~"; 10 | break; 11 | case errorTypes.USER_ALREADY_EXISTS: 12 | status = 409; // conflict 13 | message = "用户名已经存在~"; 14 | break; 15 | case errorTypes.USER_DOES_NOT_EXISTS: 16 | status = 400; // 参数错误 17 | message = "用户名不存在~"; 18 | break; 19 | case errorTypes.PASSWORD_IS_INCORRENT: 20 | status = 400; // 参数错误 21 | message = "密码是错误的~"; 22 | break; 23 | case errorTypes.UNAUTHORIZATION: 24 | status = 401; // 参数错误 25 | message = "无效的token~"; 26 | break; 27 | case errorTypes.UNPERMISSION: 28 | status = 401; // 参数错误 29 | message = "您不具备操作的权限~"; 30 | break; 31 | default: 32 | status = 404; 33 | message = "NOT FOUND"; 34 | } 35 | 36 | ctx.status = status; 37 | ctx.body = message; 38 | } 39 | 40 | module.exports = errorHandler; 41 | -------------------------------------------------------------------------------- /src/app/index.js: -------------------------------------------------------------------------------- 1 | const Koa = require('koa'); 2 | const bodyParser = require('koa-bodyparser'); 3 | const errorHandler = require('./error-handle'); 4 | const useRoutes = require('../router'); 5 | 6 | const app = new Koa(); 7 | 8 | app.useRoutes = useRoutes; 9 | 10 | app.use(bodyParser()); 11 | app.useRoutes(); 12 | app.on('error', errorHandler); 13 | 14 | module.exports = app; 15 | -------------------------------------------------------------------------------- /src/app/keys/private.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICWwIBAAKBgQDD8cZ0CgrectPM2QizGOJRej5M2UkyqZW7Qh8XmZU2vAU/KCzk 3 | 6CwVAfLGBWoiuQW8fS6j9CiscNg3pAT4EFOr4x3X3tcCEjKzJsdnKYkYeWeaFPEc 4 | tonMs9MXbCKBO1JLk+clV8jgBX7nAAMZp3njxgnvYP0Yd/xLve/w/GtONQIDAQAB 5 | AoGAUDbRBD38NxaQ6EJNEmx0ceB2UqV9FrVf65nk+pdQA2kzSKicwFTffvYeObyL 6 | t41A8OnaRxoz8Gv9x8Fom1irt3A3xxWZnhMBNyFjR2eTBlB9hgU2eDt3qKr65L3y 7 | Chc5KCH7EzpFWG618PCsLJ2FvMgy1bG/xhlDmUXN3UYL8KECQQD/k6EkteF44iR6 8 | +uiMmekQI8QU1Q+EyHQKxQqKZcrlGyVSiStFwONeU5loZXeXzgsWfeY0UhoADKkI 9 | k/ac7ar5AkEAxETcOltCgUG6VrM6xXu9GBpfF3Q27mjTH+6miWjlTyIKETUC7AoU 10 | VeM3nvPQGCQg/rXb69EO48gbnDSbesJwHQJAIgVw6gYcIY9Y89X6ptzGHZPlQjCq 11 | hC565AQexxWN0LmwWjBQRlTK73+JaFA5f0e6SiAwSL61H1SQG8g2h+VLYQJAdnDL 12 | qde6uX/tsDZ2qAg1I59+dQvnvlW52pJNI60OfIOhKaMhAUpP3UjCHwUpNfhPhQZA 13 | Ef3WC3WN2+UPUrSVrQJAemOD6Xi0iTUvVto2mYE9IlOEMSxms4gl/e7h34VHfoyN 14 | 3u51hFjmYxINSPoSFIKQuzIp+U2lt6AFLOAJ+B2Y+g== 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /src/app/keys/public.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDD8cZ0CgrectPM2QizGOJRej5M 3 | 2UkyqZW7Qh8XmZU2vAU/KCzk6CwVAfLGBWoiuQW8fS6j9CiscNg3pAT4EFOr4x3X 4 | 3tcCEjKzJsdnKYkYeWeaFPEctonMs9MXbCKBO1JLk+clV8jgBX7nAAMZp3njxgnv 5 | YP0Yd/xLve/w/GtONQIDAQAB 6 | -----END PUBLIC KEY----- 7 | -------------------------------------------------------------------------------- /src/constants/error-types.js: -------------------------------------------------------------------------------- 1 | const NAME_OR_PASSWORD_IS_REQUIRED = 'name_or_password_is_required'; 2 | const USER_ALREADY_EXISTS = 'user_already_exists'; 3 | const USER_DOES_NOT_EXISTS = 'user_does_not_exists'; 4 | const PASSWORD_IS_INCORRENT = 'password_is_incorrent'; 5 | const UNAUTHORIZATION = 'UNAUTHORIZATION'; 6 | const UNPERMISSION = 'unpermission'; 7 | 8 | module.exports = { 9 | NAME_OR_PASSWORD_IS_REQUIRED, 10 | USER_ALREADY_EXISTS, 11 | USER_DOES_NOT_EXISTS, 12 | PASSWORD_IS_INCORRENT, 13 | UNAUTHORIZATION, 14 | UNPERMISSION 15 | } 16 | -------------------------------------------------------------------------------- /src/constants/file-path.js: -------------------------------------------------------------------------------- 1 | const AVATAR_PATH = './uploads/avatar'; 2 | const PICTURE_PATH = './uploads/picture'; 3 | 4 | module.exports = { 5 | AVATAR_PATH, 6 | PICTURE_PATH 7 | } 8 | -------------------------------------------------------------------------------- /src/controller/auth.controller.js: -------------------------------------------------------------------------------- 1 | const jwt = require('jsonwebtoken'); 2 | const { PRIVATE_KEY } = require('../app/config'); 3 | 4 | class AuthController { 5 | async login(ctx, next) { 6 | const { id, name } = ctx.user; 7 | const token = jwt.sign({ id, name }, PRIVATE_KEY, { 8 | expiresIn: 60 * 60 * 24, 9 | algorithm: 'RS256' 10 | }); 11 | 12 | ctx.body = { id, name, token } 13 | } 14 | 15 | async success(ctx, next) { 16 | ctx.body = "授权成功~"; 17 | } 18 | } 19 | 20 | module.exports = new AuthController(); 21 | -------------------------------------------------------------------------------- /src/controller/comment.controller.js: -------------------------------------------------------------------------------- 1 | const service = require('../service/comment.service.js'); 2 | 3 | class CommentController { 4 | async create(ctx, next) { 5 | const { momentId, content } = ctx.request.body; 6 | const { id } = ctx.user; 7 | const result = await service.create(momentId, content, id); 8 | ctx.body = result; 9 | } 10 | 11 | async reply(ctx, next) { 12 | const { momentId, content } = ctx.request.body; 13 | const { commentId } = ctx.params; 14 | const { id } = ctx.user; 15 | const result = await service.reply(momentId, content, id, commentId); 16 | ctx.body = result; 17 | } 18 | 19 | async update(ctx, next) { 20 | const { commentId } = ctx.params; 21 | const { content } = ctx.request.body; 22 | const result = await service.update(commentId, content); 23 | ctx.body = result; 24 | } 25 | 26 | async remove(ctx, next) { 27 | const { commentId } = ctx.params; 28 | const result = await service.remove(commentId); 29 | ctx.body = result; 30 | } 31 | 32 | async list(ctx, next) { 33 | const { momentId } = ctx.query; 34 | const result = await service.getCommentsByMomentId(momentId); 35 | ctx.body = result; 36 | } 37 | } 38 | 39 | module.exports = new CommentController(); -------------------------------------------------------------------------------- /src/controller/file.controller.js: -------------------------------------------------------------------------------- 1 | const fileService = require('../service/file.service'); 2 | const userService = require('../service/user.service'); 3 | const { AVATAR_PATH } = require('../constants/file-path'); 4 | const { APP_HOST, APP_PORT } = require('../app/config'); 5 | 6 | class FileController { 7 | async saveAvatarInfo(ctx, next) { 8 | // 1.获取图像相关的信息 9 | const { filename, mimetype, size } = ctx.req.file; 10 | const { id } = ctx.user; 11 | 12 | // 2.将图像信息数据保存到数据库中 13 | const result = await fileService.createAvatar(filename, mimetype, size, id); 14 | 15 | // 3.将图片地址保存到user表中 16 | const avatarUrl = `${APP_HOST}:${APP_PORT}/users/${id}/avatar`; 17 | await userService.updateAvatarUrlById(avatarUrl, id); 18 | 19 | // 4.返回结果 20 | ctx.body = '上传头像成功~'; 21 | } 22 | 23 | async savePictureInfo(ctx, next) { 24 | // 1.获取图像信息 25 | const files = ctx.req.files; 26 | const { id } = ctx.user; 27 | const { momentId } = ctx.query; 28 | 29 | // 2.将所有的文件信息保存到数据库中 30 | for (let file of files) { 31 | const { filename, mimetype, size } = file; 32 | await fileService.createFile(filename, mimetype, size, id, momentId); 33 | } 34 | 35 | ctx.body = '动态配图上传完成~' 36 | } 37 | } 38 | 39 | module.exports = new FileController(); -------------------------------------------------------------------------------- /src/controller/label.controller.js: -------------------------------------------------------------------------------- 1 | const service = require('../service/label.service'); 2 | 3 | class LabelController { 4 | async create(ctx, next) { 5 | const { name } = ctx.request.body; 6 | const result = await service.create(name); 7 | ctx.body = result; 8 | } 9 | 10 | async list(ctx, next) { 11 | const { limit, offset } = ctx.query; 12 | const result = await service.getLabels(limit, offset); 13 | ctx.body = result; 14 | } 15 | } 16 | 17 | module.exports = new LabelController(); -------------------------------------------------------------------------------- /src/controller/moment.controller.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | const fileService = require('../service/file.service'); 4 | const momentService = require('../service/moment.service'); 5 | const { PICTURE_PATH } = require('../constants/file-path'); 6 | 7 | class MomentController { 8 | async create(ctx, next) { 9 | // 1.获取数据(user_id, content) 10 | const userId = ctx.user.id; 11 | const content = ctx.request.body.content; 12 | 13 | // 2.将数据插入到数据库 14 | const result = await momentService.create(userId, content); 15 | ctx.body = result; 16 | } 17 | 18 | async detail(ctx, next) { 19 | // 1.获取数据(momentId) 20 | const momentId = ctx.params.momentId; 21 | 22 | // 2.根据id去查询这条数据 23 | const result = await momentService.getMomentById(momentId); 24 | ctx.body = result; 25 | } 26 | 27 | async list(ctx, next) { 28 | // 1.获取数据(offset/size) 29 | const { offset, size } = ctx.query; 30 | 31 | // 2.查询列表 32 | const result = await momentService.getMomentList(offset, size); 33 | ctx.body = result; 34 | } 35 | 36 | async update(ctx, next) { 37 | // 1.获取参数 38 | const { momentId } = ctx.params; 39 | const { content } = ctx.request.body; 40 | 41 | // 2.修改内容 42 | const result = await momentService.update(content, momentId); 43 | ctx.body = result; 44 | } 45 | 46 | async remove(ctx, next) { 47 | // 1.获取momentId 48 | const { momentId } = ctx.params; 49 | 50 | // 2.删除内容 51 | const result = await momentService.remove(momentId); 52 | ctx.body = result; 53 | } 54 | 55 | async addLabels(ctx, next) { 56 | // 1.获取标签和动态id 57 | const { labels } = ctx; 58 | const { momentId } = ctx.params; 59 | 60 | // 2.添加所有的标签 61 | for (let label of labels) { 62 | // 2.1.判断标签是否已经和动态有关系 63 | const isExist = await momentService.hasLabel(momentId, label.id); 64 | if (!isExist) { 65 | await momentService.addLabel(momentId, label.id); 66 | } 67 | } 68 | 69 | ctx.body = "给动态添加标签成功~"; 70 | } 71 | 72 | async fileInfo(ctx, next) { 73 | let { filename } = ctx.params; 74 | const fileInfo = await fileService.getFileByFilename(filename); 75 | const { type } = ctx.query; 76 | const types = ["small", "middle", "large"]; 77 | if (types.some(item => item === type)) { 78 | filename = filename + '-' + type; 79 | } 80 | 81 | ctx.response.set('content-type', fileInfo.mimetype); 82 | ctx.body = fs.createReadStream(`${PICTURE_PATH}/${filename}`); 83 | } 84 | } 85 | 86 | module.exports = new MomentController(); -------------------------------------------------------------------------------- /src/controller/user.controller.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | const userService = require('../service/user.service'); 4 | const fileService = require('../service/file.service'); 5 | const { AVATAR_PATH } = require('../constants/file-path'); 6 | 7 | 8 | class UserController { 9 | async create(ctx, next) { 10 | // 获取用户请求传递的参数 11 | const user = ctx.request.body; 12 | 13 | // 查询数据 14 | const result = await userService.create(user); 15 | 16 | // 返回数据 17 | ctx.body = result; 18 | } 19 | 20 | async avatarInfo(ctx, next) { 21 | // 1.用户的头像是哪一个文件呢? 22 | const { userId } = ctx.params; 23 | const avatarInfo = await fileService.getAvatarByUserId(userId); 24 | 25 | // 2.提供图像信息 26 | ctx.response.set('content-type', avatarInfo.mimetype); 27 | ctx.body = fs.createReadStream(`${AVATAR_PATH}/${avatarInfo.filename}`); 28 | } 29 | } 30 | 31 | module.exports = new UserController(); 32 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | const app = require('./app'); 2 | require('./app/database'); 3 | 4 | const config = require('./app/config'); 5 | 6 | console.log("test jenkins"); 7 | 8 | app.listen(config.APP_PORT, () => { 9 | console.log(`服务器在${config.APP_PORT}端口启动成功~`); 10 | }); 11 | -------------------------------------------------------------------------------- /src/middleware/auth.middleware.js: -------------------------------------------------------------------------------- 1 | const jwt = require('jsonwebtoken'); 2 | 3 | const errorTypes = require('../constants/error-types'); 4 | const userService = require('../service/user.service'); 5 | const authService = require('../service/auth.service'); 6 | const md5password = require('../utils/password-handle'); 7 | const { PUBLIC_KEY } = require('../app/config'); 8 | 9 | const verifyLogin = async (ctx, next) => { 10 | console.log("验证登录的middleware~"); 11 | 12 | // 1.获取用户名和密码 13 | const { name, password } = ctx.request.body; 14 | 15 | // 2.判断用户名和密码是否为空 16 | if (!name || !password) { 17 | const error = new Error(errorTypes.NAME_OR_PASSWORD_IS_REQUIRED); 18 | return ctx.app.emit('error', error, ctx); 19 | } 20 | 21 | // 3.判断用户是否存在的 22 | const result = await userService.getUserByName(name); 23 | const user = result[0]; 24 | if (!user) { 25 | const error = new Error(errorTypes.USER_DOES_NOT_EXISTS); 26 | return ctx.app.emit('error', error, ctx); 27 | } 28 | 29 | // 4.判断密码是否和数据库中的密码是一致(加密) 30 | if (md5password(password) !== user.password) { 31 | const error = new Error(errorTypes.PASSWORD_IS_INCORRENT); 32 | return ctx.app.emit('error', error, ctx); 33 | } 34 | 35 | ctx.user = user; 36 | await next(); 37 | } 38 | 39 | const verifyAuth = async (ctx, next) => { 40 | console.log("验证授权的middleware~"); 41 | // 1.获取token 42 | const authorization = ctx.headers.authorization; 43 | if (!authorization) { 44 | const error = new Error(errorTypes.UNAUTHORIZATION); 45 | return ctx.app.emit('error', error, ctx); 46 | } 47 | const token = authorization.replace('Bearer ', ''); 48 | 49 | // 2.验证token(id/name/iat/exp) 50 | try { 51 | const result = jwt.verify(token, PUBLIC_KEY, { 52 | algorithms: ["RS256"] 53 | }); 54 | ctx.user = result; 55 | await next(); 56 | } catch (err) { 57 | const error = new Error(errorTypes.UNAUTHORIZATION); 58 | ctx.app.emit('error', error, ctx); 59 | } 60 | } 61 | 62 | /** 63 | * 1.很多的内容都需要验证权限: 修改/删除动态, 修改/删除评论 64 | * 2.接口: 业务接口系统/后端管理系统 65 | * 一对一: user -> role 66 | * 多对多: role -> menu(删除动态/修改动态) 67 | */ 68 | const verifyPermission = async (ctx, next) => { 69 | console.log("验证权限的middleware~"); 70 | 71 | // 1.获取参数 { commentId: '1' } 72 | const [resourceKey] = Object.keys(ctx.params); 73 | const tableName = resourceKey.replace('Id', ''); 74 | const resourceId = ctx.params[resourceKey]; 75 | const { id } = ctx.user; 76 | 77 | // 2.查询是否具备权限 78 | try { 79 | const isPermission = await authService.checkResource(tableName, resourceId, id); 80 | if (!isPermission) throw new Error(); 81 | await next(); 82 | } catch (err) { 83 | const error = new Error(errorTypes.UNPERMISSION); 84 | return ctx.app.emit('error', error, ctx); 85 | } 86 | } 87 | 88 | // const verifyPermission = (tableName) => { 89 | // return async (ctx, next) => { 90 | // console.log("验证权限的middleware~"); 91 | 92 | // // 1.获取参数 93 | // const { momentId } = ctx.params; 94 | // const { id } = ctx.user; 95 | 96 | // // 2.查询是否具备权限 97 | // try { 98 | // const isPermission = await authService.checkResource(tableName, momentId, id); 99 | // if (!isPermission) throw new Error(); 100 | // await next(); 101 | // } catch (err) { 102 | // const error = new Error(errorTypes.UNPERMISSION); 103 | // return ctx.app.emit('error', error, ctx); 104 | // } 105 | // } 106 | // } 107 | 108 | 109 | 110 | module.exports = { 111 | verifyLogin, 112 | verifyAuth, 113 | verifyPermission 114 | } 115 | -------------------------------------------------------------------------------- /src/middleware/file.middleware.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const Multer = require('koa-multer'); 4 | const Jimp = require('jimp'); 5 | const { AVATAR_PATH, PICTURE_PATH } = require('../constants/file-path'); 6 | 7 | const avatarUpload = Multer({ 8 | dest: AVATAR_PATH 9 | }); 10 | const avatarHandler = avatarUpload.single('avatar'); 11 | 12 | const pictureUpload = Multer({ 13 | dest: PICTURE_PATH 14 | }); 15 | const pictureHandler = pictureUpload.array('picture', 9); 16 | 17 | const pictureResize = async (ctx, next) => { 18 | try { 19 | // 1.获取所有的图像信息 20 | const files = ctx.req.files; 21 | 22 | // 2.对图像进行处理(sharp/jimp) 23 | for (let file of files) { 24 | const destPath = path.join(file.destination, file.filename); 25 | console.log(destPath); 26 | Jimp.read(file.path).then(image => { 27 | image.resize(1280, Jimp.AUTO).write(`${destPath}-large`); 28 | image.resize(640, Jimp.AUTO).write(`${destPath}-middle`); 29 | image.resize(320, Jimp.AUTO).write(`${destPath}-small`); 30 | }); 31 | } 32 | 33 | await next(); 34 | } catch (error) { 35 | console.log(error); 36 | } 37 | } 38 | 39 | module.exports = { 40 | avatarHandler, 41 | pictureHandler, 42 | pictureResize 43 | } -------------------------------------------------------------------------------- /src/middleware/label.middleware.js: -------------------------------------------------------------------------------- 1 | const service = require('../service/label.service'); 2 | 3 | const verifyLabelExists = async (ctx, next) => { 4 | // 1.取出要添加的所有的标签 5 | const { labels } = ctx.request.body; 6 | 7 | // 2.判断每一个标签在label表中是否存在 8 | const newLabels = []; 9 | for (let name of labels) { 10 | const labelResult = await service.getLabelByName(name); 11 | const label = { name }; 12 | if (!labelResult) { 13 | // 创建标签数据 14 | const result = await service.create(name); 15 | label.id = result.insertId; 16 | } else { 17 | label.id = labelResult.id; 18 | } 19 | newLabels.push(label); 20 | } 21 | ctx.labels = newLabels; 22 | await next(); 23 | } 24 | 25 | module.exports = { 26 | verifyLabelExists 27 | } -------------------------------------------------------------------------------- /src/middleware/user.middleware.js: -------------------------------------------------------------------------------- 1 | const errorTypes = require('../constants/error-types'); 2 | const service = require('../service/user.service'); 3 | const md5password = require('../utils/password-handle'); 4 | 5 | const verifyUser = async (ctx, next) => { 6 | // 1.获取用户名和密码 7 | const { name, password } = ctx.request.body; 8 | 9 | // 2.判断用户名或者密码不能空 10 | if (!name || !password) { 11 | const error = new Error(errorTypes.NAME_OR_PASSWORD_IS_REQUIRED); 12 | return ctx.app.emit('error', error, ctx); 13 | } 14 | 15 | // 3.判断这次注册的用户名是没有被注册过 16 | const result = await service.getUserByName(name); 17 | if (result.length) { 18 | const error = new Error(errorTypes.USER_ALREADY_EXISTS); 19 | return ctx.app.emit('error', error, ctx); 20 | } 21 | 22 | await next(); 23 | } 24 | 25 | const handlePassword = async (ctx, next) => { 26 | const { password } = ctx.request.body; 27 | ctx.request.body.password = md5password(password) 28 | 29 | await next(); 30 | } 31 | 32 | module.exports = { 33 | verifyUser, 34 | handlePassword 35 | } 36 | -------------------------------------------------------------------------------- /src/router/auth.router.js: -------------------------------------------------------------------------------- 1 | const Router = require('koa-router'); 2 | 3 | const authRouter = new Router(); 4 | 5 | const { 6 | login, 7 | success 8 | } = require('../controller/auth.controller'); 9 | const { 10 | verifyLogin, 11 | verifyAuth 12 | } = require('../middleware/auth.middleware'); 13 | 14 | authRouter.post('/login', verifyLogin, login); 15 | authRouter.get('/test', verifyAuth, success); 16 | 17 | module.exports = authRouter; 18 | -------------------------------------------------------------------------------- /src/router/comment.router.js: -------------------------------------------------------------------------------- 1 | const { verify } = require('jsonwebtoken'); 2 | const Router = require('koa-router'); 3 | 4 | const { 5 | verifyAuth, 6 | verifyPermission 7 | } = require('../middleware/auth.middleware'); 8 | const { 9 | create, 10 | reply, 11 | update, 12 | remove, 13 | list 14 | } = require('../controller/comment.controller.js') 15 | 16 | const commentRouter = new Router({prefix: '/comment'}); 17 | 18 | // 发表评论 19 | commentRouter.post('/', verifyAuth, create); 20 | commentRouter.post('/:commentId/reply', verifyAuth, reply); 21 | 22 | // 修改评论 23 | commentRouter.patch('/:commentId', verifyAuth, verifyPermission, update); 24 | // 删除评论 25 | commentRouter.delete('/:commentId', verifyAuth, verifyPermission, remove); 26 | 27 | // 获取评论列表 28 | commentRouter.get('/', list); 29 | 30 | module.exports = commentRouter; -------------------------------------------------------------------------------- /src/router/file.router.js: -------------------------------------------------------------------------------- 1 | const Router = require('koa-router'); 2 | 3 | const { 4 | verifyAuth 5 | } = require('../middleware/auth.middleware'); 6 | const { 7 | avatarHandler, 8 | pictureHandler, 9 | pictureResize 10 | } = require('../middleware/file.middleware'); 11 | const { 12 | saveAvatarInfo, 13 | savePictureInfo 14 | } = require('../controller/file.controller'); 15 | 16 | const fileRouter = new Router({prefix: '/upload'}); 17 | 18 | fileRouter.post('/avatar', verifyAuth, avatarHandler, saveAvatarInfo); 19 | fileRouter.post('/picture', verifyAuth, pictureHandler, pictureResize, savePictureInfo); 20 | 21 | module.exports = fileRouter; -------------------------------------------------------------------------------- /src/router/index.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | 4 | const useRoutes = function() { 5 | fs.readdirSync(__dirname).forEach(file => { 6 | if (file === 'index.js') return; 7 | const router = require(`./${file}`); 8 | this.use(router.routes()); 9 | this.use(router.allowedMethods()); 10 | }) 11 | } 12 | 13 | module.exports = useRoutes; 14 | -------------------------------------------------------------------------------- /src/router/label.router.js: -------------------------------------------------------------------------------- 1 | const Router = require('koa-router'); 2 | 3 | const { 4 | verifyAuth 5 | } = require('../middleware/auth.middleware'); 6 | const { 7 | create, 8 | list 9 | } = require('../controller/label.controller.js') 10 | 11 | const labelRouter = new Router({prefix: '/label'}); 12 | 13 | labelRouter.post('/', verifyAuth, create); 14 | labelRouter.get('/', list); 15 | 16 | module.exports = labelRouter; 17 | -------------------------------------------------------------------------------- /src/router/moment.router.js: -------------------------------------------------------------------------------- 1 | const { verify } = require('jsonwebtoken'); 2 | const Router = require('koa-router'); 3 | 4 | const momentRouter = new Router({prefix: '/moment'}); 5 | 6 | const { 7 | create, 8 | detail, 9 | list, 10 | update, 11 | remove, 12 | addLabels, 13 | fileInfo 14 | } = require('../controller/moment.controller.js'); 15 | const { 16 | verifyAuth, 17 | verifyPermission 18 | } = require('../middleware/auth.middleware'); 19 | const { 20 | verifyLabelExists 21 | } = require('../middleware/label.middleware'); 22 | 23 | momentRouter.post('/', verifyAuth, create); 24 | 25 | momentRouter.get('/', list); 26 | momentRouter.get('/:momentId', detail); 27 | 28 | // 1.用户必须登录 2.用户具备权限 29 | momentRouter.patch('/:momentId', verifyAuth, verifyPermission, update); 30 | momentRouter.delete('/:momentId', verifyAuth, verifyPermission, remove); 31 | 32 | // 给动态添加标签 33 | momentRouter.post('/:momentId/labels', verifyAuth, verifyPermission, verifyLabelExists, addLabels); 34 | 35 | // 动态配图的服务 36 | momentRouter.get('/images/:filename', fileInfo); 37 | 38 | module.exports = momentRouter; 39 | -------------------------------------------------------------------------------- /src/router/user.router.js: -------------------------------------------------------------------------------- 1 | const Router = require('koa-router'); 2 | const { 3 | create, 4 | avatarInfo 5 | } = require('../controller/user.controller'); 6 | const { 7 | verifyUser, 8 | handlePassword 9 | } = require('../middleware/user.middleware'); 10 | 11 | const userRouter = new Router({prefix: '/users'}); 12 | 13 | userRouter.post('/', verifyUser, handlePassword, create); 14 | userRouter.get('/:userId/avatar', avatarInfo); 15 | 16 | module.exports = userRouter; 17 | -------------------------------------------------------------------------------- /src/service/auth.service.js: -------------------------------------------------------------------------------- 1 | const connection = require('../app/database'); 2 | 3 | class AuthService { 4 | async checkResource(tableName, id, userId) { 5 | const statement = `SELECT * FROM ${tableName} WHERE id = ? AND user_id = ?;`; 6 | const [result] = await connection.execute(statement, [id, userId]); 7 | return result.length === 0 ? false: true; 8 | } 9 | } 10 | 11 | module.exports = new AuthService(); -------------------------------------------------------------------------------- /src/service/comment.service.js: -------------------------------------------------------------------------------- 1 | const connection = require('../app/database'); 2 | const commentRouter = require('../router/comment.router'); 3 | 4 | class CommentService { 5 | async create(momentId, content, userId) { 6 | const statement = `INSERT INTO comment (content, moment_id, user_id) VALUES (?, ?, ?);`; 7 | `INSERT INTO comment SET ?` 8 | const [result] = await connection.execute(statement, [content, momentId, userId]); 9 | return result; 10 | } 11 | 12 | async reply(momentId, content, userId, commentId) { 13 | const statement = `INSERT INTO comment (content, moment_id, user_id, comment_id) VALUES (?, ?, ?, ?);`; 14 | const [result] = await connection.execute(statement, [content, momentId, userId, commentId]); 15 | return result; 16 | } 17 | 18 | async update(commentId, content) { 19 | const statement = `UPDATE comment SET content = ? WHERE id = ?`; 20 | const [result] = await connection.execute(statement, [content, commentId]); 21 | return result; 22 | } 23 | 24 | async remove(commentId) { 25 | const statement = `DELETE FROM comment WHERE id = ?`; 26 | const [result] = await connection.execute(statement, [commentId]); 27 | return result; 28 | } 29 | 30 | async getCommentsByMomentId(momentId) { 31 | const statement = ` 32 | SELECT 33 | m.id, m.content, m.comment_id commendId, m.createAt createTime, 34 | JSON_OBJECT('id', u.id, 'name', u.name) user 35 | FROM comment m 36 | LEFT JOIN user u ON u.id = m.user_id 37 | WHERE moment_id = ?; 38 | `; 39 | const [result] = await connection.execute(statement, [momentId]); 40 | return result; 41 | } 42 | } 43 | 44 | module.exports = new CommentService(); -------------------------------------------------------------------------------- /src/service/file.service.js: -------------------------------------------------------------------------------- 1 | const connection = require('../app/database'); 2 | 3 | class FileService { 4 | async createAvatar(filename, mimetype, size, userId) { 5 | const statement = `INSERT INTO avatar (filename, mimetype, size, user_id) VALUES (?, ?, ?, ?)`; 6 | const [result] = await connection.execute(statement, [filename, mimetype, size, userId]); 7 | return result; 8 | } 9 | 10 | async getAvatarByUserId(userId) { 11 | const statement = `SELECT * FROM avatar WHERE user_id = ?;`; 12 | const [result] = await connection.execute(statement, [userId]); 13 | return result[0]; 14 | } 15 | 16 | async createFile(filename, mimetype, size, userId, momentId) { 17 | const statement = `INSERT INTO file (filename, mimetype, size, user_id, moment_id) VALUES (?, ?, ?, ?, ?)`; 18 | const [result] = await connection.execute(statement, [filename, mimetype, size, userId, momentId]); 19 | return result; 20 | } 21 | 22 | async getFileByFilename(filename) { 23 | const statement = `SELECT * FROM file WHERE filename = ?;`; 24 | const [result] = await connection.execute(statement, [filename]); 25 | return result[0]; 26 | } 27 | } 28 | 29 | module.exports = new FileService(); -------------------------------------------------------------------------------- /src/service/label.service.js: -------------------------------------------------------------------------------- 1 | const connection = require('../app/database'); 2 | 3 | class LabelService { 4 | async create(name) { 5 | const statement = `INSERT INTO label (name) VALUES (?);`; 6 | const [result] = await connection.execute(statement, [name]); 7 | return result; 8 | } 9 | 10 | async getLabelByName(name) { 11 | const statement = `SELECT * FROM label WHERE name = ?;`; 12 | const [result] = await connection.execute(statement, [name]); 13 | return result[0]; 14 | } 15 | 16 | async getLabels(limit, offset) { 17 | const statement = `SELECT * FROM label LIMIT ?, ?;`; 18 | const [result] = await connection.execute(statement, [offset, limit]); 19 | return result; 20 | } 21 | } 22 | 23 | module.exports = new LabelService(); -------------------------------------------------------------------------------- /src/service/moment.service.js: -------------------------------------------------------------------------------- 1 | const connection = require('../app/database'); 2 | 3 | // const sqlFragment = ` 4 | // SELECT 5 | // m.id id, m.content content, m.createAt createTime, m.updateAt updateTime, 6 | // JSON_OBJECT('id', u.id, 'name', u.name) author 7 | // FROM moment m 8 | // LEFT JOIN user u ON m.user_id = u.id 9 | // ` 10 | 11 | class MomentService { 12 | async create(userId, content) { 13 | const statement = `INSERT INTO moment (content, user_id) VALUES (?, ?);`; 14 | const [result] = await connection.execute(statement, [content, userId]); 15 | return result; 16 | } 17 | 18 | async getMomentById(id) { 19 | const statement = ` 20 | SELECT 21 | m.id id, m.content content, m.createAt createTime, m.updateAt updateTime, 22 | JSON_OBJECT('id', u.id, 'name', u.name, 'avatarUrl', u.avatar_url) author, 23 | IF(COUNT(l.id),JSON_ARRAYAGG( 24 | JSON_OBJECT('id', l.id, 'name', l.name) 25 | ),NULL) labels, 26 | (SELECT IF(COUNT(c.id),JSON_ARRAYAGG( 27 | JSON_OBJECT('id', c.id, 'content', c.content, 'commentId', c.comment_id, 'createTime', c.createAt, 28 | 'user', JSON_OBJECT('id', cu.id, 'name', cu.name, 'avatarUrl', cu.avatar_url)) 29 | ),NULL) FROM comment c LEFT JOIN user cu ON c.user_id = cu.id WHERE m.id = c.moment_id) comments, 30 | (SELECT JSON_ARRAYAGG(CONCAT('http://localhost:8000/moment/images/', file.filename)) 31 | FROM file WHERE m.id = file.moment_id) images 32 | FROM moment m 33 | LEFT JOIN user u ON m.user_id = u.id 34 | LEFT JOIN moment_label ml ON m.id = ml.moment_id 35 | LEFT JOIN label l ON ml.label_id = l.id 36 | WHERE m.id = ? 37 | GROUP BY m.id; 38 | `; 39 | try { 40 | const [result] = await connection.execute(statement, [id]); 41 | return result[0]; 42 | } catch (error) { 43 | console.log(error) 44 | } 45 | } 46 | 47 | async getMomentList(offset, size) { 48 | const statement = ` 49 | SELECT 50 | m.id id, m.content content, m.createAt createTime, m.updateAt updateTime, 51 | JSON_OBJECT('id', u.id, 'name', u.name) author, 52 | (SELECT COUNT(*) FROM comment c WHERE c.moment_id = m.id) commentCount, 53 | (SELECT COUNT(*) FROM moment_label ml WHERE ml.moment_id = m.id) labelCount, 54 | (SELECT JSON_ARRAYAGG(CONCAT('http://localhost:8000/moment/images/', file.filename)) 55 | FROM file WHERE m.id = file.moment_id) images 56 | FROM moment m 57 | LEFT JOIN user u ON m.user_id = u.id 58 | LIMIT ?, ?; 59 | `; 60 | 61 | const [result] = await connection.execute(statement, [offset, size]); 62 | return result; 63 | } 64 | 65 | async update(content, momentId) { 66 | const statement = `UPDATE moment SET content = ? WHERE id = ?;`; 67 | const [result] = await connection.execute(statement, [content, momentId]); 68 | return result; 69 | } 70 | 71 | async remove(momentId) { 72 | const statement = `DELETE FROM moment WHERE id = ?`; 73 | const [result] = await connection.execute(statement, [momentId]); 74 | return result; 75 | } 76 | 77 | async hasLabel(momentId, labelId) { 78 | const statement = `SELECT * FROM moment_label WHERE moment_id = ? AND label_id = ?`; 79 | const [result] = await connection.execute(statement, [momentId, labelId]); 80 | return result[0] ? true: false; 81 | } 82 | 83 | async addLabel(momentId, labelId) { 84 | const statement = `INSERT INTO moment_label (moment_id, label_id) VALUES (?, ?);`; 85 | const [result] = await connection.execute(statement, [momentId, labelId]); 86 | return result; 87 | } 88 | } 89 | 90 | module.exports = new MomentService(); 91 | 92 | -------------------------------------------------------------------------------- /src/service/user.service.js: -------------------------------------------------------------------------------- 1 | const connection = require('../app/database'); 2 | 3 | class UserService { 4 | async create(user) { 5 | const { name, password } = user; 6 | const statement = `INSERT INTO user (name, password) VALUES (?, ?);`; 7 | const result = await connection.execute(statement, [name, password]); 8 | 9 | return result[0]; 10 | } 11 | 12 | async getUserByName(name) { 13 | const statement = `SELECT * FROM user WHERE name = ?;`; 14 | const result = await connection.execute(statement, [name]); 15 | 16 | return result[0]; 17 | } 18 | 19 | async updateAvatarUrlById(avatarUrl, userId) { 20 | const statement = `UPDATE user SET avatar_url = ? WHERE id = ?;`; 21 | const [result] = await connection.execute(statement, [avatarUrl, userId]); 22 | return result; 23 | } 24 | } 25 | 26 | module.exports = new UserService(); 27 | -------------------------------------------------------------------------------- /src/utils/password-handle.js: -------------------------------------------------------------------------------- 1 | const crypto = require('crypto'); 2 | 3 | const md5password = (password) => { 4 | const md5 = crypto.createHash('md5'); 5 | const result = md5.update(password).digest('hex'); 6 | return result; 7 | } 8 | 9 | module.exports = md5password; 10 | 11 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | 2 | const name = ''; 3 | console.log(!name); 4 | --------------------------------------------------------------------------------