mysql2
1. 项目功能接口说明
完整的项目接口包括:
面向用户的业务接口;
面向企业或者内部的后台管理接口;
课堂上完成的功能如下:
用户管理系统
内容管理系统
内容评论管理
内容标签管理
文件管理系统
等等
2. 项目的搭建 2.1. 初始化项目 项目基于koa框架开发
终端命令
1 2 3 4 5 6 7 npm init -y npm i nodemon -D npm i koa npm i koa-router npm i mysql2 npm i dotenv npm i koa-bodyparser
2.2. 目录结构的划分
该项目按照功能模块划分:
1 2 3 4 5 6 7 8 9 10 |- node_modules |- src |- app |- controller |- router 路由设计 |- service 与数据库相关处理 |- utils |- index.js |- package-lock.js |- package.js
2.3. 创建和启动服务器 2.3.1. 应用配置信息写到环境变量
2.3.2. 创建服务器 在 /src/app
中添加 index.js
1 2 3 4 const koa = require ("koa" );const app = new koa ();module .exports = app;
在 src/index.js
中添加代码
1 2 3 4 5 6 7 8 9 const {APP_PORT } = require ("./app/config" );const app = require ("./app" );app.listen (APP_PORT ,()=> { console .log (`开启 ${APP_PORT} 端口` ); });
2.3.3. 添加命令 在 package.json
中添加命令
1 2 3 4 "scripts" : { "test" : "echo \"Error: no test specified\" && exit 1" , "start" : "nodemon ./src/index.js" }
2.3.4. 启动服务器 终端输入
在终端输出
1 2 3 4 5 6 7 8 9 > coderhub@1.0.0 start > nodemon ./src/index.js [nodemon] 2.0.19 [nodemon] to restart at any time, enter `rs` [nodemon] watching path(s): *.* [nodemon] watching extensions: js,mjs,json [nodemon] starting `node ./src/index.js` 开启 8000 端口
表示服务正常启动
3. 用户管理系统 3.1. 用户注册接口编写流程 3.1.1. 注册用户路由router编写 在 src/router
下新建 user.router.js
1 2 3 4 5 6 7 8 9 10 const Router = require ("koa-router" );const useRouter = new Router ({prefix : "/user" });const { create } = require ("../controller/user.controller" );const { verifyUser } = require ("../middleware/user.middleware" );useRouter.post ("/" ,verifyUser,create); module .exports = useRouter;
src/app/index.js
中引入 user.router.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 const koa = require ("koa" );const app = new koa ();const bodyparser = require ("koa-bodyparser" );const errorHandler = require ("./errorHandler" );const useRouter = require ("../router/user.router" );app.use (bodyparser ()); app.use (useRouter.routes ()); app.use (useRouter.allowedMethods ()); app.on ("error" ,errorHandler); module .exports = app;
3.1.2. 处理函数的控制器controller编写 在 src/controller
下新建 user.controller.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 const {create} = require ("../service/user.service" );class UserController { async create (ctx,next ){ const user = ctx.request .body ; const result = await create (user); ctx.body = result; }; } module .exports = new UserController ();
3.1.3. 创建数据库连接 在 .env
中添加数据库配置
1 2 3 4 5 DATABASE_HOST = localhost DATABASE_PORT = 3306 DATABASE_USER = root DATABASE_PASSWORD = 密码 DATABASE = coderhub
在 src/app/config.js
中添加数据
1 2 3 4 5 6 7 8 9 10 11 const env = require ("dotenv" );env.config (); module .exports = { APP_PORT , DATABASE_HOST , DATABASE_PORT , DATABASE_USER , DATABASE_PASSWORD , DATABASE } = process.env ;
在 src/app
下新建database.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 const mysql = require ("mysql2" );const config = require ("./config" );const connection = mysql.createPool ({ host : config.DATABASE_HOST , port : config.DATABASE_PORT , user : config.DATABASE_USER , password : config.DATABASE_PASSWORD , database : config.DATABASE , connectionLimit : 10 }); connection.getConnection ((err,con )=> { if (err){ console .log ("数据库连接失败: " +err); }else { console .log ("数据库连接成功" ); } }) module .exports = connection.promise ();
3.1.4. 测试数据库连接是否成功 在 /index.js
中导入数据库连接
1 require ("./app/database" );
运行 npm start
终端输出 数据库连接成功
表示连接成功
3.1.5. 操作数据库的service编写 在 src/service
下新建 user.service.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 const mysql = require ("../app/database" );class UserService { async create (user ){ const {name,password} = user; const statement = 'insert into users (name,password) values (?,?)' ; const result = await mysql.execute (statement,[name,password]); return result; } async getUserByName (name ){ const statement = 'select * from users where name = ?' ; const result = await mysql.execute (statement,[name]); return result[0 ]; } } module .exports = new UserService ();
3.1.6. 错误统一处理 HTTP响应状态码 https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Status
封装错误处理函数
在 src
下新建 constants
文件夹,新建 errorType.js
1 2 3 4 5 6 7 const REQUIRED = 'field is required' ;const NAME_EXIST = 'username is exist' ;module .exports = { REQUIRED , NAME_EXIST }
在 src/app
下新建 errorHandler.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 const { REQUIRED , NAME_EXIST } = require ("../constants/errorType" );const errorHandler = (error,ctx )=>{ let status,message; switch (error.message ){ case REQUIRED : status = 400 ; message = '值不能为空' ; break ; case NAME_EXIST : status = 409 ; message = '该用户名已存在' ; break ; default : status = 404 ; message = 'NOT FOUND' ; } ctx.status = status; ctx.body = message; } module .exports = errorHandler;
app监听错误事件
在 src/app/index.js
中对应错误监听
1 2 3 const errorHandler = require ("./app/errorHandler" );app.on ("error" ,errorHandler);
3.1.7. 注册用户校验 在 src
下新建 middleware
文件夹,新建 user.middleware.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 const { REQUIRED , NAME_EXIST } = require ("../constants/errorType" );const { getUserByName } = require ("../service/user.service" );const verifyUser = async (ctx,next )=>{ const {name,password} = ctx.request .body ; if (!name || !password){ const error = new Error (REQUIRED ); return ctx.app .emit ("error" ,error,ctx); } const nameExist = await getUserByName (name); if (nameExist.length >0 ){ const error = new Error (NAME_EXIST ); return ctx.app .emit ("error" ,error,ctx); } await next (); } module .exports = { verifyUser }
3.1.8. 密码加密存储 在 src/utils
下新建 passwordHandler.js
1 2 3 4 5 6 7 8 9 const crypto = require ("crypto" );const md5Password = (password )=>{ const md5 = crypto.createHash ("md5" ); const result = md5.update (password).digest ('hex' ); return result; } module .exports = md5Password;
在 src/middleware/user.middleware.js
中添加方法
1 2 3 4 5 6 7 8 9 10 const md5Password = require ("../utils/passwordHandler" );const handlePassword = async (ctx,next )=>{ let { password } = ctx.request .body ; ctx.request .body .password = md5Password (password); await next (); } module .exports = { verifyUser, handlePassword }
在 src/router/user.router.js
中添加 中间件
1 2 3 const { verifyUser, handlePassword } = require ("../middleware/user.middleware" );useRouter.post ("/" ,verifyUser,handlePassword,create);
3.2. 用户登录接口编写流程 3.2.1. 授权router的编写 在 src/router
中新建auth.router.js
1 2 3 4 5 6 7 8 9 const Router = require ("koa-router" );const { login } = require ("../controller/auth.controller" );const { verifyLogin } = require ("../middleware/auth.middleware" );const authRouter = new Router ({prefix : "/login" });authRouter.post ("/" ,verifyLogin,login); module .exports = authRouter;
3.2.2. 处理函数的controller编写 在 src/controller
中新建 auth.controller.js
1 2 3 4 5 6 7 class AuthController { async login (ctx,next ){ const {name,password} = ctx.request .body ; ctx.body = `登录成功,欢迎${name} ` ; } } module .exports = new AuthController ();
3.2.3. 验证的中间件
账号和密码是否为空;
用户名是否存在;
校验密码是否一致;
编写错误代码 src/constans/errorType.js
1 2 3 4 5 6 7 8 9 10 11 const REQUIRED = 'field is required' ;const NAME_EXIST = 'username is exist' ;const NAME_NOT_EXISTS = 'username is not exist' ;const PASSWORD_NOT_SAME = 'password is not same' ;module .exports = { REQUIRED , NAME_EXIST , NAME_NOT_EXISTS , PASSWORD_NOT_SAME }
在 src/middleware
中新建 auth.middleware.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 const { REQUIRED , NAME_NOT_EXISTS , PASSWORD_NOT_SAME } = require ("../constants/errorType" );const { getUserByName } = require ("../service/user.service" );const md5Password = require ("../utils/passwordHandler" );const verifyLogin = async (ctx,next )=>{ const {name,password} = ctx.request .body ; if (!name||!password){ const error = new Error (REQUIRED ); return ctx.app .emit ("error" ,err,ctx); } const result = await getUserByName (name); if (result.length == 0 ){ const error = new Error (NAME_NOT_EXISTS ); return ctx.app .emit ("error" ,error,ctx); } if (md5Password (password) != result[0 ].password ){ const error = new Error (PASSWORD_NOT_SAME ); return ctx.app .emit ("error" ,error,ctx); } await next (); } module .exports = { verifyLogin }
处理错误信息 src/app/errorHandle.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 const { REQUIRED , NAME_EXIST , NAME_NOT_EXISTS , PASSWORD_NOT_SAME } = require ("../constants/errorType" );const errorHandler = (error,ctx )=>{ let status,message; switch (error.message ){ case REQUIRED : status = 400 ; message = '值不能为空' ; break ; case NAME_EXIST : status = 409 ; message = '该用户名已存在' ; break ; case NAME_NOT_EXISTS : status = 400 ; message = '该用户名不存在' ; break ; case PASSWORD_NOT_SAME : status = 400 ; message = '密码不正确' ; break ; default : status = 404 ; message = 'NOT FOUND' ; } ctx.status = status; ctx.body = message; } module .exports = errorHandler;
3.2.4. 整合路由 src/router
中新建 index.js
,这样就可以自动扫描所有路由
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const fs = require ("fs" );const useRoutes = function ( ){ fs.readdirSync (__dirname).forEach (file => { if (file == 'index.js' ){ return ; } const router = require (`./${file} ` ); this .use (router.routes ()); this .use (router.allowedMethods ()); }) } module .exports = useRoutes;
注册路由 src/app/index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 const koa = require ("koa" );const app = new koa ();const bodyparser = require ("koa-bodyparser" );const useRoutes = require ("../router" );const errorHandler = require ("./errorHandler" );app.use (bodyparser ()); app.useRoutes = useRoutes; app.useRoutes (); app.on ("error" ,errorHandler); module .exports = app;
3.3. 登录成功返回凭证 3.3.1. 为什么需要登录凭证呢?
3.3.2. 认识cookie
Cookie(复数形态Cookies),又称为“小甜饼”。类型为“小型文本文件,某些网站为了辨别用户身份而存储在用户本地终端(Client Side)上的数据
浏览器会在特定的情况下携带上cookie来发送请求,通过cookie来获取一些信息;
Cookie总是保存在客户端中,按在客户端中的存储位置,Cookie可以分为内存Cookie和硬盘Cookie
内存Cookie由浏览器维护,保存在内存中,浏览器关闭时Cookie就会消失,其存在时间是短暂的;
硬盘Cookie保存在硬盘中,有一个过期时间,用户手动清理或者过期时间到时,才会被清理;
如果判断一个cookie是内存cookie还是硬盘cookie呢?
没有设置过期时间,默认情况下cookie是内存cookie,在关闭浏览器时会自动删除;
有设置过期时间,并且过期时间不为0或者负数的cookie,是硬盘cookie,需要手动或者到期时,才会删除;
3.3.3. cookie常见的属性
cookie的生命周期
默认情况下的cookie是内存cookie,也称之为会话cookie,也就是在浏览器关闭时会自动被删除;
通过设置expires或者max-age来设置过期的时间
expires:设置的是Date.toUTCString(),设置格式是;expires=date-in-GMTString-format;
max-age:设置过期的秒钟; max-age=max-age-in-seconds (例如一年为6060 24*365);
cookie的作用域:(允许cookie发送给哪些URL)
Domain:指定哪些主机可以接受cookie
如果不指定,那么默认是 origin,不包括子域名。
如果指定Domain,则包含子域名。例如,如果设置 Domain=mozilla.org,则 Cookie 也包含在子域名中(如developer.mozilla.org)。
Path:指定主机下哪些路径可以接受cookie
例如,设置 Path=/docs,则以下地址都会匹配:
/docs
/docs/Web/
/docs/Web/HTTP
3.3.4. 客户端设置cookie
3.3.5. 服务器设置cookie
Koa中默认支持直接操作cookie
/login 请求中设置cookie
/use 请求中获取cookie
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 const koa = require ("koa" );const app = new koa ();const Router = require ("koa-router" );const loginRouter = new Router ();loginRouter.get ("/login" ,(ctx,next )=> { ctx.cookies .set ("name" ,"lilei" ,{ maxAge : 5 * 1000 }); ctx.body = "设置cookie" ; }); const useRouter = new Router ();useRouter.get ("/use" ,(ctx,next )=> { const name = ctx.cookies .get ("name" ); ctx.body = name; }); app.use (loginRouter.routes ()); app.use (loginRouter.allowedMethods ()); app.use (useRouter.routes ()); app.use (useRouter.allowedMethods ()); app.listen (8989 ,()=> { console .log ("开启 8989" ); });
3.3.6. Session是基于cookie实现机制 在koa中,我们可以借助于 koa-session 来实现session认证
实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 const koa = require ("koa" );const app = new koa ();const Router = require ("koa-router" );const loginRouter = new Router ();const Session = require ("koa-session" );const session = Session ({ key : "sessionid" , maxAge : 10 * 1000 , signed : true },app); app.keys = ["test" ]; app.use (session); loginRouter.get ("/login" ,(ctx,next )=> { const user = { id : 1 , name : 'root' } ctx.session .user = user; ctx.body = "设置session" ; }); const useRouter = new Router ();useRouter.get ("/use" ,(ctx,next )=> { const user = ctx.session .user ; ctx.body = user; }); app.use (loginRouter.routes ()); app.use (loginRouter.allowedMethods ()); app.use (useRouter.routes ()); app.use (useRouter.allowedMethods ()); app.listen (8990 ,()=> { console .log ("开启 8990" ); });
session是存在cookie中的
3.3.7. 认识token
cookie和session的方式有很多的缺点
Cookie会被附加在每个HTTP请求中,所以无形中增加了流量(事实上某些请求是不需要的);
Cookie是明文传递的,所以存在安全性的问题;
Cookie的大小限制是4KB,对于复杂的需求来说是不够的;
对于浏览器外的其他客户端(比如iOS、Android),必须手动的设置cookie和session;
对于分布式系统和服务器集群中如何可以保证其他系统也可以正确的解析session?
不同系统分属于不同服务器
session属于一次会话,request.getSession(),在不同服务器中,request请求对象不同,session不同
所以,在目前的前后端分离的开发过程中,使用token来进行身份验证的是最多的情况:
token可以翻译为令牌;
也就是在验证了用户账号和密码正确的情况,给用户颁发一个令牌;
这个令牌作为后续用户访问一些接口或者资源的凭证;
根据这个凭证来判断用户是否有权限来访问;
所以token的使用应该分成两个重要的步骤:
生成token:登录的时候,颁发token;
验证token:访问某些资源或者接口时,验证token;
3.3.8. JWT实现Token机制
JWT生成的Token由三部分组成
header
alg:采用的加密算法,默认是 HMAC SHA256(HS256),采用同一个密钥进行 加密和解密;
typ:JWT,固定值,通常都写成JWT即可;
会通过base64Url算法进行编码;
payload
携带的数据,比如我们可以将用户的id和name放到payload中;
默认也会携带iat(issued at),令牌的签发时间;
也可以设置过期时间:exp(expiration time);
会通过base64Url算法进行编码
signature
设置一个secretKey,通过将前两个的结果合并后进行HMACSHA256的算法;
HMACSHA256(base64Url(header)+.+base64Url(payload), secretKey);
但是如果secretKey暴露是一件非常危险的事情,因为之后就可以模拟颁发token, 也可以解密token;
3.3.9. Token的使用 当然,在真实开发中,我们可以直接使用一个库来完成: npm i jsonwebtoken
;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 const koa = require ("koa" );const app = new koa ();const Router = require ("koa-router" );const loginRouter = new Router ();const jwt = require ("jsonwebtoken" );const privateKey = "coderHub" ;loginRouter.get ("/login" ,(ctx,next )=> { const user = { id : 1 , name : 'root' } const token = jwt.sign (user,privateKey,{ expiresIn : 60 }) ctx.body = "设置token " +token; }); const useRouter = new Router ();useRouter.get ("/use" ,(ctx,next )=> { try { const authorization = ctx.headers .authorization ; const token = authorization.replace ("Bearer " ,"" ); const user = jwt.verify (token,privateKey); ctx.body = user; } catch (err){ return ctx.app .emit ("error" ,err,ctx); } }); app.use (loginRouter.routes ()); app.use (loginRouter.allowedMethods ()); app.use (useRouter.routes ()); app.use (useRouter.allowedMethods ()); app.on ("error" ,(error,ctx )=> { ctx.body = error.message ; }) app.listen (8990 ,()=> { console .log ("开启 8990" ); });
3.3.10. 非对称加密
HS256加密算法一单密钥暴露就是非常危险的事情
比如在分布式系统中,每一个子系统都需要获取到密钥;
那么拿到这个密钥后这个子系统既可以发布另外,也可以验证令牌;
但是对于一些资源服务器来说,它们只需要有验证令牌的能力就可以了;
可以使用非对称加密,RS256:
私钥(private key):用于发布令牌;
公钥(public key):用于验证令牌;
我们可以使用openssl来生成一对私钥和公钥:
Mac直接使用terminal终端即可;
Windows默认的cmd终端是不能直接使用的,建议直接使用git bash终端;
1 2 3 openssl > genrsa -out private.key 1024 > rsa -in private.key -pubout -out public.key
genrsa -out private.key 1024
使用RS256算法生成大小为1024字节的私钥,输出到private.key文件
rsa -in private.key -pubout -out public.key
以私钥为参考生成公钥
3.3.11. 使用公钥和私钥签发和验证签名 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 const koa = require ("koa" );const app = new koa ();const Router = require ("koa-router" );const loginRouter = new Router ();const jwt = require ("jsonwebtoken" );const fs = require ("fs" );const path = require ("path" );const privateKey = fs.readFileSync (path.resolve (__dirname,"private.key" ));const publicKey = fs.readFileSync (path.resolve (__dirname,"public.key" ));loginRouter.get ("/login" ,(ctx,next )=> { const user = { id : 1 , name : 'root' } const token = jwt.sign (user,privateKey,{ expiresIn : 60 , algorithm : 'RS256' }) ctx.body = "设置token " +token; }); const useRouter = new Router ();useRouter.get ("/use" ,(ctx,next )=> { try { const authorization = ctx.headers .authorization ; const token = authorization.replace ("Bearer " ,"" ); const user = jwt.verify (token,publicKey,{ algorithms : ['RS256' ] }); ctx.body = user; } catch (err){ return ctx.app .emit ("error" ,err,ctx); } }); app.use (loginRouter.routes ()); app.use (loginRouter.allowedMethods ()); app.use (useRouter.routes ()); app.use (useRouter.allowedMethods ()); app.on ("error" ,(error,ctx )=> { ctx.body = error.message ; }) app.listen (8990 ,()=> { console .log ("开启 8990" ); });
3.3.12. 项目中加入token 在 src/app
中新建文件夹 keys
,导入 private.key
和 public.key
在 src/app/config.js
中引入变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 const env = require ("dotenv" );env.config (); const fs = require ("fs" );const path = require ("path" );const PRIVATE_KEY = fs.readFileSync (path.resolve (__dirname,"keys/private.key" ));const PUBLIC_KEY = fs.readFileSync (path.resolve (__dirname,"keys/public.key" ));module .exports = { APP_PORT , DATABASE_HOST , DATABASE_PORT , DATABASE_USER , DATABASE_PASSWORD , DATABASE } = process.env ; module .exports .PRIVATE_KEY = PRIVATE_KEY ;module .exports .PUBLIC_KEY = PUBLIC_KEY ;
在 src/constants/errorType.js
中添加错误信息
1 2 3 4 5 6 7 8 9 10 11 12 13 const REQUIRED = 'field is required' ;const NAME_EXIST = 'username is exist' ;const NAME_NOT_EXISTS = 'username is not exist' ;const PASSWORD_NOT_SAME = 'password is not same' ;const UNAUTHORIZED = "Unauthorized" ;module .exports = { REQUIRED , NAME_EXIST , NAME_NOT_EXISTS , PASSWORD_NOT_SAME , UNAUTHORIZED }
在 src/app/errorHandler.js
中处理错误信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 const { REQUIRED , NAME_EXIST , NAME_NOT_EXISTS , PASSWORD_NOT_SAME , UNAUTHORIZED } = require ("../constants/errorType" );const errorHandler = (error,ctx )=>{ let status,message; switch (error.message ){ case REQUIRED : status = 400 ; message = '值不能为空' ; break ; case NAME_EXIST : status = 409 ; message = '该用户名已存在' ; break ; case NAME_NOT_EXISTS : status = 400 ; message = '该用户名不存在' ; break ; case PASSWORD_NOT_SAME : status = 400 ; message = '密码不正确' ; break ; case UNAUTHORIZED : status = 401 ; message = '用户没有访问权限' ; break ; default : status = 404 ; message = 'NOT FOUND' ; } ctx.status = status; ctx.body = message; } module .exports = errorHandler;
在 src/middleware/auth.middleware.js
中添加验证令牌代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 const { REQUIRED , NAME_NOT_EXISTS , PASSWORD_NOT_SAME , UNAUTHORIZED } = require ("../constants/errorType" );const { getUserByName } = require ("../service/user.service" );const md5Password = require ("../utils/passwordHandler" );const jwt = require ("jsonwebtoken" );const { PUBLIC_KEY } = require ("../app/config" );const verifyLogin = async (ctx,next )=>{ const {name,password} = ctx.request .body ; if (!name||!password){ const error = new Error (REQUIRED ); return ctx.app .emit ("error" ,err,ctx); } const result = await getUserByName (name); if (result.length == 0 ){ const error = new Error (NAME_NOT_EXISTS ); return ctx.app .emit ("error" ,error,ctx); } const user = result[0 ]; if (md5Password (password) != user.password ){ const error = new Error (PASSWORD_NOT_SAME ); return ctx.app .emit ("error" ,error,ctx); } ctx.user = user; await next (); } const verifyAuth = async (ctx,next )=>{ try { const authorization = ctx.headers .authorization ; const token = authorization.replace ("Bearer " ,"" ); const user = jwt.verify (token,PUBLIC_KEY ,{ algorithms : ['RS256' ] }); ctx.user = user; await next (); } catch (error) { const err = new Error (UNAUTHORIZED ); return ctx.app .emit ("error" ,err,ctx); } } module .exports = { verifyLogin, verifyAuth }
在 src/controller/auth.controller.js
中颁发令牌
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 const jwt = require ("jsonwebtoken" );const { PRIVATE_KEY } = require ("../app/config" );class AuthController { async login (ctx,next ){ const {id,name} = ctx.user ; const token = jwt.sign ({id,name},PRIVATE_KEY ,{ expiresIn : 60 * 60 * 24 , algorithm : "RS256" }); ctx.body = { id, name, token }; } async test (ctx,next ){ ctx.body = ctx.user ; } } module .exports = new AuthController ();
在 src/router/auth.router.js
中添加测试 /test
1 2 3 4 5 6 7 8 9 10 11 const Router = require ("koa-router" );const { login, test } = require ("../controller/auth.controller" );const { verifyLogin, verifyAuth } = require ("../middleware/auth.middleware" );const authRouter = new Router ({prefix : "/login" });authRouter.post ("/" ,verifyLogin,login); authRouter.get ("/test" ,verifyAuth,test); module .exports = authRouter;
3.3.13. Postman自动添加token
在 Postman
中添加全局变量
1 2 const res = pm.response .json ();pm.globals .set ("token" ,res.token )
添加token
测试 /test
4. 内容管理系统 4.1. 创建新的表 moment 1 2 3 4 5 6 7 8 create table if not exists comments( id int primary key auto_increment, content varchar (1000 ) not null , user_id int not null , createAt timestamp default current_timestamp , updateAt timestamp default current_timestamp on update current_timestamp , foreign key(user_id) references users(id) );
4.2. 实现功能
4.3. moment.router.js 在 src/router
中新建 moment.router.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 const Router = require ("koa-router" );const momentRouter = new Router ({prefix :"/moment" });const { verifyAuth, verifyPermission } = require ("../middleware/auth.middleware" );const { create, getById, list, update, deleteById } = require ("../controller/moment.controller" );momentRouter.post ("/upload" ,verifyAuth,create); momentRouter.get ("/get/:momentId" ,getById); momentRouter.get ("/list" ,list); momentRouter.patch ("/update/:momentId" ,verifyAuth,verifyPermission,update); momentRouter.delete ("/delete/:momentId" ,verifyAuth,verifyPermission,deleteById); module .exports = momentRouter;
4.4. moment.controller.js 在 src/controller
中新建 moment.controller.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 const { create, getMomentById, getMomentList, update, deleteById } = require ("../service/moment.service" );class MomentController { async create (ctx,next ){ const {id} = ctx.user ; const content = ctx.request .body .content ; const result = await create ({id,content}); ctx.body = result; } async getById (ctx,next ){ const id = ctx.params .id ; const result = await getMomentById (id); ctx.body = result; } async list (ctx,next ){ const {offset,size} = ctx.query ; const result = await getMomentList (offset,size); ctx.body = result; } async update (ctx,next ){ const id = ctx.params .id ; const content = ctx.request .body .content ; const userId = ctx.user .id ; const result = await update (id,content,userId); ctx.body = result; } async deleteById (ctx,next ){ const id = ctx.params .id ; const userId = ctx.user .id ; const result = await deleteById (id,userId); ctx.body = result; } } module .exports = new MomentController ();
4.5. moment.service.js 在 src/service
中新建 moment.service.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 const mysql = require ("../app/database" );const mysqlFragment = ` select m.id as id, m.content as content, json_object("id",u.id,"name",u.name) as user from moments m left join users u on m.user_id = u.id ` ;class MomentService { async create (user ){ const statement = "insert into moments (content,user_id) values (?,?)" ; const [result] = await mysql.execute (statement,[user.content ,user.id ]); return result; } async getMomentById (id ){ const statement = `${mysqlFragment} where m.id = ? ` ; const [result] = await mysql.execute (statement,[id]); return result[0 ]; } async getMomentList (offest,size ){ const statement = `${mysqlFragment} limit ?,?` ; const [result] = await mysql.execute (statement,[offest,size]); return result; } async update (id,content,userId ){ const statement = `update moments set content = ? where id = ? and user_id = ?` ; const [result] = await mysql.execute (statement,[content,id,userId]); return result; } async deleteById (id,userId ){ const statement = `delete from moments where id = ? and user_id = ?` ; const [result] = await mysql.execute (statement,[id,userId]); return result; } } module .exports = new MomentService ();
4.6. 重点 - 用户权限功能 面对不同的登录用户,应该因为user_id不同,限制登录用户进行其他的user_id的更新,删除内容
在 src/middleware/auth.middleware.js
中添加代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 const { checkMoment } = require ("../service/auth.service" );const verifyPermission = async (ctx,next )=>{ const result = await checkMoment (ctx.params .id ,ctx.user .id ); if (result){ await next (); }else { const error = new Error (UNPERMISSION ); return ctx.app .emit ("error" ,error,ctx); } } module .exports = { verifyLogin, verifyAuth, verifyPermission }
在 src/service/auth.service.js
中添加验证代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const mysql = require ("../app/database" );class AuthService { async checkMoment (momentId,userId ){ const statement = `select * from moments where id = ? and user_id = ?` ; const [result] = await mysql.execute (statement,[momentId,userId]); if (result.length == 0 ){ return false ; } return true ; } } module .exports = new AuthService ();
在 src/constans/errorType.js
中添加错误代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const REQUIRED = 'field is required' ;const NAME_EXIST = 'username is exist' ;const NAME_NOT_EXISTS = 'username is not exist' ;const PASSWORD_NOT_SAME = 'password is not same' ;const UNAUTHORIZED = "Unauthorized" ;const UNPERMISSION = "Unpermission" ;module .exports = { REQUIRED , NAME_EXIST , NAME_NOT_EXISTS , PASSWORD_NOT_SAME , UNAUTHORIZED , UNPERMISSION }
在 src/app/errorHandler.js
中添加处理错误代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 const { REQUIRED , NAME_EXIST , NAME_NOT_EXISTS , PASSWORD_NOT_SAME , UNAUTHORIZED , UNPERMISSION } = require ("../constants/errorType" );const errorHandler = (error,ctx )=>{ let status,message; switch (error.message ){ case REQUIRED : status = 400 ; message = '值不能为空' ; break ; case NAME_EXIST : status = 409 ; message = '该用户名已存在' ; break ; case NAME_NOT_EXISTS : status = 400 ; message = '该用户名不存在' ; break ; case PASSWORD_NOT_SAME : status = 400 ; message = '密码不正确' ; break ; case UNAUTHORIZED : status = 401 ; message = 'token无效' ; break ; case UNPERMISSION : status = 403 ; message = '用户没有操作权限' ; break ; default : status = 404 ; message = 'NOT FOUND' ; } ctx.status = status; ctx.body = message; } module .exports = errorHandler;
5. 评论管理系统 1 2 3 4 5 6 7 8 9 10 11 12 13 create table if not exists comments( id int primary key auto_increment, content varchar(1000) not null, moment_id int not null, user_id int not null, comment_id int default null, createAt timestamp default current_timestamp, updateAt timestamp default current_timestamp on update current_timestamp, foreign key(moment_id) references moments(id) on update cascade on delete cascade, foreign key(user_id) references users(id) on update cascade on delete cascade, foreign key(comment_id) references comments(id) on update cascade on delete cascade );
5.2. 实现功能
定义发布评论内容的接口
定义路由接口
验证用户登录
Controller和Service中处理内容
定义修改评论内容的接口
定义路由接口
验证用户登录
验证用户的权限
Controller和Service中的处理
定义删除评论内容的接口
定义路由接口
验证用户登录
验证用户权限
Controller和Service的处理
查询动态的时候,同时显示评论信息
查询多个动态时,显示评论的个数
查询单个动态时,显示评论的列表
src/router
中新建comment.router.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 const Router = require ("koa-router" );const { create, reply, update, deleteById, getByMomentId } = require ("../controller/comment.controller" );const commentRouter = new Router ({prefix : "/comment" });const { verifyAuth, verifyPermission } = require ("../middleware/auth.middleware" );commentRouter.post ("/" ,verifyAuth,create); commentRouter.post ("/:commentId/reply" ,verifyAuth,reply); commentRouter.patch ("/:commentId" ,verifyAuth,verifyPermission,update); commentRouter.delete ("/:commentId" ,verifyAuth,verifyPermission,deleteById); commentRouter.get ("/" ,getByMomentId); module .exports = commentRouter;
src/router
中新建 comment.controller.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 const { create, reply, update, deleteById, getByMomentId } = require ("../service/comment.service" );class CommentController { async create (ctx,next ){ const {momentId,content} = ctx.request .body ; const userId = ctx.user .id ; const result = await create (userId,momentId,content); ctx.body = result; } async reply (ctx,next ){ const userId = ctx.user .id ; const commentId = ctx.params .commentId ; const {momentId,content} = ctx.request .body ; const result = await reply (userId,momentId,commentId,content); ctx.body = result; } async update (ctx,next ){ const commentId = ctx.params .commentId ; const content = ctx.request .body .content ; const userId = ctx.user .id ; const result = await update (commentId,userId,content); ctx.body = result; } async deleteById (ctx,next ){ const commentId = ctx.params .commentId ; const userId = ctx.user .id ; const result = await deleteById (commentId,userId); ctx.body = result; } async getByMomentId (ctx,next ){ const momentId = ctx.query .momentId ; const result = await getByMomentId (momentId); ctx.body = result; } } module .exports = new CommentController ();
在 src/service
中新建 comment.service.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 const mysql = require ("../app/database" );class CommentService { async create (userId,momentId,content ){ const statement = `insert into comments (moment_id, user_id,content) values (?,?,?)` ; const [result] = await mysql.execute (statement,[momentId,userId,content]); return result; } async reply (userId,momentId,commentId,content ){ const statement = `insert into comments (comment_id,moment_id, user_id,content) values (?,?,?,?)` ; const [result] = await mysql.execute (statement,[commentId,momentId,userId,content]); return result; } async update (commentId,userId,content ){ const statement = `update comments set content = ? where id = ? and user_id = ?` ; const [result] = await mysql.execute (statement,[content,commentId,userId]); return result; } async deleteById (commentId,userId ){ const statement = `delete from comments where id = ? and user_id = ?` ; const [result] = await mysql.execute (statement,[commentId,userId]); return result; } async getByMomentId (momentId ){ const statement = ` select c.id id,c.content content,c.comment_id commentId,c.createAt createAt,c.updateAt updateAt, json_object("id",u.id,"name",u.name) user from comments c left join users u on c.user_id = u.id where c.moment_id = ? ` ; const [result] = await mysql.execute (statement,[momentId]); return result; } } module .exports = new CommentService ();
5.6. 修改auth验证权限 src/middleware/auth.middleware.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 const verifyPermission = async (ctx,next )=>{ try { const [key] = Object .keys (ctx.params ); const tableName = key.replace ("Id" ,"s" ); const id = ctx.params [key]; const userId = ctx.user .id ; const result = await checkResource (tableName,id,userId); if (result){ await next (); }else { throw new Error (); } }catch (err){ const error = new Error (UNPERMISSION ); return ctx.app .emit ("error" ,error,ctx); } }
src/service/auth.service.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 const mysql = require ("../app/database" );class AuthService { async checkResource (tableName,id,userId ){ const statement = `select * from ${tableName} where id = ? and user_id = ?` ; const [result] = await mysql.execute (statement,[id,userId]); if (result.length == 0 ){ return false ; } return true ; } } module .exports = new AuthService ();
5.7. 修改内容获取列表 src/service/moment.service.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 const mysqlFragment = ` select m.id id, m.content content,m.createAt createAt,m.updateAt updateAt, json_object("id",u.id,"name",u.name) author, json_arrayagg( json_object("id",c.id,"content",c.content,"commentId",c.comment_id, "createAt",m.createAt,"updateAt",m.updateAt, "user",json_object("id",us.id,"name",us.name) ) ) comments from moments m left join users u on m.user_id = u.id left join comments c on c.moment_id = m.id left join users us on us.id = c.user_id group by m.id ` ;
6. 内容标签管理系统 6.1. 创建标签的表 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 create table if not exists labels( id int primary key auto_increment, name varchar(255) not null unique, user_id int not null, createAt timestamp default current_timestamp, updateAt timestamp default current_timestamp on update current_timestamp, foreign key(user_id) references users(id) on delete cascade on update cascade ); create table if not exists moment_label( moment_id int not null, label_id int not null, createAt timestamp default current_timestamp, updateAt timestamp default current_timestamp on update current_timestamp, primary key(moment_id,label_id), foreign key(moment_id) references moments(id) on delete cascade on update cascade, foreign key(label_id) references labels(id) on delete cascade on update cascade );
6.2. 实现功能
定义创建标签接口
路由配置Router
验证用户登录
创建标签
创建标签和动态关系表
定义给动态添加标签的接口
给动态添加新的接口
查询标签接口
查询动态列表,展示标签数量
查询动态详情,展示标签列表
6.3. label.router.js src/router
中新建 label.router.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 const Router = require ("koa-router" );const labelRouter = new Router ({prefix :"/label" });const { create, list, update, deleteById } = require ("../controller/label.controller" );const {verifyAuth, verifyPermission} = require ("../middleware/auth.middleware" );labelRouter.post ("/" ,verifyAuth,create); labelRouter.get ("/" ,list); labelRouter.patch ("/:labelId" ,verifyAuth, verifyPermission ,update); labelRouter.delete ("/:labelId" ,verifyAuth, verifyPermission ,deleteById); module .exports = labelRouter;
6.4. label.controller.js src/controller
中新建 label.controller.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 const { create, list, update, deleteById } = require ("../service/label.service" );class LabelController { async create (ctx,next ){ const {name} = ctx.request .body ; const userId = ctx.user .id ; const result = await create (name,userId); ctx.body = result; } async list (ctx,next ){ const {offset,size} = ctx.query ; const result = await list (offset,size); ctx.body = result; } async update (ctx,next ){ const labelId = ctx.params .labelId ; const name = ctx.request .body .name ; const userId = ctx.user .id ; const result = await update (name,labelId,userId); ctx.body = result; } async deleteById (ctx,next ){ const labelId = ctx.params .labelId ; const userId = ctx.user .id ; const result = await deleteById (labelId,userId); ctx.body = result; } } module .exports = new LabelController ();
6.5. label.middleware.js src/middleware
中新建 label.middleware.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 const { create, getLabelByName } = require ("../service/label.service" );const verifyLabelExists = async (ctx,next )=>{ const userId = ctx.user .id ; const {labels} = ctx.request .body ; const newLabels = []; for (let name of labels){ const labelResult = await getLabelByName (name); const label = {name}; if (!labelResult){ const result = await create (name,userId); label.id = result.insertId ; }else { label.id = labelResult.id ; } newLabels.push (label); } ctx.labels = newLabels; await next (); } module .exports = { verifyLabelExists }
6.6. label.service.js src/service
中新建 label.service.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 const mysql = require ("../app/database" );class LabelService { async create (name,userId ){ const statement = `insert into labels (name,user_id) values (?,?)` ; const [result] = await mysql.execute (statement,[name,userId]); return result; } async getLabelByName (name ){ const statement = `select * from labels where name = ?` ; const [result] = await mysql.execute (statement,[name]); return result[0 ]; } async hasLabel (momentId,labelId ){ const statement = `select * from moment_label where moment_id = ? and label_id = ?` ; const [result] = await mysql.execute (statement,[momentId,labelId]); return result[0 ] ? true : false ; } async createMomentLabel (momentId,labelId ){ const statement = `insert into moment_label (moment_id,label_id) values (?,?)` ; const [result] = await mysql.execute (statement,[momentId,labelId]); return result; } async list (offset,size ){ const statement = `select * from labels limit ?,?` ; const [result] = await mysql.execute (statement,[offset,size]); return result; } async update (name,labelId,userId ){ const statement = `update labels set name = ? where id = ? and user_id = ?` ; const [result] = await mysql.execute (statement,[name,labelId,userId]); return result; } async deleteById (labelId,userId ){ const statement = `delete from labels where id = ? and user_id = ?` ; const [result] = await mysql.execute (statement,[labelId,userId]); return result; } } module .exports =new LabelService ();
6.7. moment添加标签 src/router/moment.router.js
1 2 momentRouter.post ("/:momentId/labels" ,verifyAuth,verifyPermission,verifyLabelExists,addLabels);
src/controller/moment.controller.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 async addLabels (ctx,next ){ const labels = ctx.labels ; const momentId = ctx.params .momentId ; for (let label of labels){ const exist = await hasLabel (momentId,label.id ); if (!exist){ const result = await createMomentLabel (momentId,label.id ); } } ctx.body = "内容添加标签" ; }
src/service/moment.service.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 const mysqlFragment = ` select m.id id, m.content content,m.createAt createAt,m.updateAt updateAt, json_object("id",u.id,"name",u.name) author, (select count(*) from comments c where c.moment_id = m.id) commentCount, (select count(*) from moment_label ml where ml.moment_id = m.id) labelCount, (select if(count(c.id), json_arrayagg( json_object("id",c.id,"content",c.content,"commentId",c.comment_id,"createAt",m.createAt,"updateAt",m.updateAt, "user",json_object("id",us.id,"name",us.name) ) ),null) from comments c left join users us on us.id = c.user_id where c.moment_id = m.id )comments, if(count(l.id),json_arrayagg( json_object("id",l.id,"name",l.name) ) ,null) labels from moments m left join users u on u.id = m.user_id left join moment_label ml on ml.moment_id = m.id left join labels l on l.id = ml.label_id group by m.id ` ;
7. 文件管理系统 7.1. 创建头像表 1 2 3 4 5 6 7 8 9 10 create table if not exists avatars( id int primary key auto_increment, filename varchar(1000) not null, mimetype varchar(255) not null, size int, user_id int, createAt timestamp default current_timestamp, updateAt timestamp default current_timestamp on update current_timestamp, foreign key(user_id) references users(id) on delete cascade on update cascade );
7.2. 创建文件表 1 2 3 4 5 6 7 8 9 10 11 12 create table if not exists files( id int primary key auto_increment, filename varchar(1000) not null, mimetype varchar(255) not null, size int, moment_id int, user_id int, createAt timestamp default current_timestamp, updateAt timestamp default current_timestamp on update current_timestamp, foreign key(user_id) references users(id) on delete cascade on update cascade, foreign key(moment_id) references moments(id) on delete cascade on update cascade );
7.3. 更新用户头像列 1 alter table users add avatar_url varchar(255);
7.4. 实现功能
上传头像逻辑
定义上传图像的接口
定义获取图像的接口
请求用户信息时,获取头像
实现思路
图片(文件)上传 /upload/avatar
提供一个接口,可以让用户获取图片
/1/avatar -> 找到图片 -> 读取图片content-type: image/jpeg -> 返回图像的信息
将URL存储到用户信息中
获取信息时,获取用户的头像
上传动态的配图
定义上传动态配图的接口
定义获取动态配图的接口
获取动态时,获取配图信息
7.5. 下载插件 1 2 npm i @koa/multer npm i jimp
7.6. file.router.js src/router
中新建file.router.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 const Router = require ("koa-router" );const { saveAvatarInfo, savePictureInfo } = require ("../controller/file.controller" );const { verifyAuth } = require ("../middleware/auth.middleware" );const { avatarHandler, pictureHandler, pictureResize } = require ("../middleware/file.middleware" );const fileRouter = new Router ({prefix :"/upload" });fileRouter.post ("/avatar" ,verifyAuth,avatarHandler,saveAvatarInfo); fileRouter.post ("/picture" ,verifyAuth,pictureHandler,pictureResize,savePictureInfo); module .exports = fileRouter;
7.7. file.controller.js src/controller
中新建 file.controller.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 const { createAvatar, createFiles } = require ("../service/file.service" );const { updateAvatarUrl } = require ("../service/user.service" );const { APP_HOST , APP_PORT } = require ("../app/config" );class FileController { async saveAvatarInfo (ctx,next ){ const {mimetype,filename,size} = ctx.request .file ; const userId = ctx.user .id ; const result = await createAvatar (mimetype,filename,size,userId); const avatarUrl = `${APP_HOST} :${APP_PORT} /user/${userId} /avatar` ; await updateAvatarUrl (avatarUrl,userId); ctx.body = result; } async savePictureInfo (ctx,next ){ const files = ctx.request .files ; const {momentId} = ctx.query ; const userId = ctx.user .id ; for (let file of files){ const {mimetype,filename,size} = file; await createFiles (mimetype,filename,size,userId,momentId); } ctx.body = "内容配图上传成功" ; } } module .exports = new FileController ();
7.8. 更新用户头像链接 src/service/user.service.js
1 2 3 4 5 6 7 async updateAvatarUrl (avatarUrl,userId ){ const statement = `update users set avatar_url = ? where id = ?` ; const [result] = await mysql.execute (statement,[avatarUrl,userId]); return result; }
7.9. 文件存储路径 src/constants/filePath.js
1 2 3 4 5 6 7 const AVATAR_PATH = "./uploads/avatar/" ;const PICTURE_PATH = "./uploads/picture/" ;module .exports = { AVATAR_PATH , PICTURE_PATH };
7.10. file.middle.js src/middleware
中新建file.middleware.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 const path = require ("path" );const Multer = require ("@koa/multer" );const Jimp = require ("jimp" );const {AVATAR_PATH ,PICTURE_PATH } = require ("../constants/filePath" );const avatar = Multer ({ dest : AVATAR_PATH }); const avatarHandler = avatar.single ("avatar" );const picture = Multer ({ dest : PICTURE_PATH }); const pictureHandler = picture.array ("picture" ,9 );const pictureResize = async (ctx,next )=>{ const files = ctx.request .files ; for (let file of files){ const filePath = path.join (file.destination ,file.filename ); Jimp .read (file.path ).then (image => { image.resize (1280 ,Jimp .AUTO ).write (`${filePath} -large` ); image.resize (640 ,Jimp .AUTO ).write (`${filePath} -middle` ); image.resize (320 ,Jimp .AUTO ).write (`${filePath} -small` ); }) } await next (); } module .exports = { avatarHandler, pictureHandler, pictureResize };
7.11. file.service.js src/service
中新建 file.service.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 const mysql = require ("../app/database" );class FileService { async createAvatar (mimetype,filename,size,userId ){ const statement = `insert into avatars (mimetype,filename,size,user_id) values (?,?,?,?)` ; const [result] = await mysql.execute (statement,[mimetype,filename,size,userId]); return result; } async getAvatarById (userId ){ const statement = `select * from avatars where user_id = ?` ; const [result] = await mysql.execute (statement,[userId]); return result[0 ]; } async createFiles (mimetype,filename,size,userId,momentId ){ const statement = `insert into files (mimetype,filename,size,user_id,moment_id) values (?,?,?,?,?)` ; const [result] = await mysql.execute (statement,[mimetype,filename,size,userId,momentId]); return result; } async getFileByName (name ){ const statement = `select * from files where filename = ?` ; const [result] = await mysql.execute (statement,[name]); return result[0 ]; } } module .exports = new FileService ();
7.12. 添加头像访问链接 src/router/user.router.js
1 2 useRouter.get ("/:userId/avatar" ,getAvatarInfo);
src/controller/user.controller.js
1 2 3 4 5 6 7 8 9 10 11 12 const { getAvatarById } = require ("../service/file.service" );async getAvatarInfo (ctx,next ){ const {userId} = ctx.params ; const result = await getAvatarById (userId); ctx.response .set ("content-type" ,result.mimetype ); ctx.body = fs.createReadStream (AVATAR_PATH +result.filename ); }
7.13. 添加图片访问链接 src/router/moment.router.js
1 2 momentRouter.get ("/images/:filename" ,fileInfo);
src/controller/moment.controller.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const { getFileByName } = require ("../service/file.service" );async fileInfo (ctx,next ){ const {filename} = ctx.params ; const {type} = ctx.query ; const result = await getFileByName (filename); ctx.response .set ("content-type" ,result.mimetype ); let path = PICTURE_PATH ; if (!type){ path += result.filename ; }else { path += result.filename + "-" +type; } ctx.body = fs.createReadStream (path); }
7.14. moment添加内容配图 src/service/moment.service.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 const sql = `${APP_HOST} :${APP_PORT} /moment/images/` ;const mysqlFragment = ` select m.id id, m.content content,m.createAt createAt,m.updateAt updateAt, json_object("id",u.id,"name",u.name,"avatarUrl",u.avatar_url) author, (select count(*) from comments c where c.moment_id = m.id) commentCount, (select count(*) from moment_label ml where ml.moment_id = m.id) labelCount, (select if(count(c.id), json_arrayagg( json_object("id",c.id,"content",c.content,"commentId",c.comment_id,"createAt",m.createAt,"updateAt",m.updateAt, "user",json_object("id",us.id,"name",us.name,"avatarUrl",us.avatar_url) ) ),null) from comments c left join users us on us.id = c.user_id where c.moment_id = m.id )comments, if(count(l.id),json_arrayagg( json_object("id",l.id,"name",l.name) ) ,null) labels, (select json_arrayagg(concat("${sql} ",f.filename)) from files f where f.moment_id = m.id ) images from moments m left join users u on u.id = m.user_id left join moment_label ml on ml.moment_id = m.id left join labels l on l.id = ml.label_id group by m.id ` ;
8. 部署云服务器 8.1. 购买云服务器 8.1.1. 注册阿里云的账号
8.1.2. 购买云服务器 购买云服务器其实是购买一个实例
来到控制台
创建实例,选择类型和配置
配置网络安全组
创建实例
8.2. 配置云服务器 8.2.1. 连接云服务器
通常情况下,通过ssh连接云服务器:
知识点补充:如果在计算机中想要更改主机名
修改之后需要重启服务器
1 hostnamectl --static set-hostname coderhub
8.2.2. vscode连接云服务器 安装插件 【remote-ssh】
连接云服务器,输入 ssh root@<<公网ip>>
输入密码
输入文件地址
8.2.3. 安装Node.js
安装软件使用工具:dnf
DNF
,全称 Dandified (时髦的、华丽的) Yum;
是Yum的下一个版本,也被称之为Yum的替代品;
如果是centos7的版本,需要通过yum进行安装(这个自行安装一下)
出现 ImportError: No module named _conf
出现 UnicodeDecodeError: 'ascii' codec can't decode byte 0xe5 in position 4: ordinal not in range(128)
使用vscode连接云服务器,进入 /usr/lib/python2.7/site-packages/
该文件目录下新建 sitecustomize.py
输入
1 2 3 4 import sysreload(sys) sys.setdefaultencoding('utf8' )
重启服务器
检查dnf是否可用
安装一个软件包,可以进行如下的操作:
1 2 3 4 5 6 dnf search nodejs dnf info nodejs dnf install nodejs
版本切换工具 n:
8.2.4. 安装MySQL 8.2.4.1. 配置MySQL 官网文件下载安装
或者使用dnf安装MySQL
1 2 3 4 5 6 7 8 9 10 11 12 dnf search mysql-server dnf info mysql-server dnf install mysql-server -y systemctl start mysqld systemctl status mysql systemctl enable mysqld
配置MySQL账号和密码
1 2 3 4 mysql_secure_installation
8.2.4.2. 操作mysql
8.2.4.3. 建立远程连接 1 2 3 4 5 6 use mysql; select host, user from user; update user set host = '%' where user = 'root' ;
配置阿里云服务器 3306的安全组
Navicat工具中连接MySQL
8.2.4.4. 数据迁移 转储sql文件
创建数据库
1 create database if not exists coderhub default character set utf8mb4;
执行sql文件
8.3. 部署Node项目 8.3.1. 手动部署
代码托管到Git仓库
代码clone到服务器
VSCode打开代码,使用remote-ssh
openssl
1 2 3 openssl > genrsa -out private.key 1024 > rsa -in private.key -pubout -out public.key
.env
1 2 3 4 5 6 7 8 APP_HOST = APP_PORT = DATABASE_HOST = DATABASE_PORT = DATABASE_USER = DATABASE_PASSWORD = DATABASE =
安装插件
开放端口
运行node
8.3.2. pm2启动node程序
安装pm2:
pm2常用的命令:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 pm2 start app.js --name my-api pm2 list pm2 stop 0 pm2 stop all pm2 restart all pm2 restart 0 pm2 delete 0 pm2 delete all pm2 start app.js -i 4