Koa2 build API service
Getting started with koa2
https://www.bilibili.com/video/BV18h411H7GE?spm_id_from=333.999.0.0
Node+koa2 build API service
Tutorial source:https://www.bilibili.com/video/BV13A411w79h?spm_id_from=333.999.0.0
Initialization
npm init -y
git init
,create a new.gitignore
file,and addnode_modules
- After submitting the version, view the records through
git log
- After submitting the version, view the records through
- Create a new
readme.md
document
Project initialization
npm i koa
Create new
src/main.js
file in rootdirectory1
2
3
4
5
6
7
8
9
10
11const Koa = require('koa') // import koa, the exported class is generally capitalized
const app = new Koa() // instantiation
app.use((ctx, next) => { // middleware
ctx.body = 'hello world' // test code
})
app.listen(3000, () => { // run server
console.log('server is running on http://localhost:3000 !');
})Start development service:
node .\src\main.js
:Node
mode start, is resident memory, not hot loaded
Development optimization
Automatic restart service
npm i nodemon
Configure
dev
script: ifnodemon
is installed globally, thennpx
is not requiredpackage.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18{
"name": "01",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "npx nodemon ./src/main.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"koa": "^2.13.4",
"nodemon": "^2.0.19"
}
}npm run dev
run server1
2
3
4
5
6
7
8
9
10
11PS D:\workspace\github\code\project-workshop\code-prac\koa\01> npm run dev
> 01@1.0.0 dev
> npx nodemon ./src/main.js
[nodemon] 2.0.19
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,json // Listen to these three files
[nodemon] starting `node ./src/main.js` // Start with node
server is running on http://localhost:3000 ! // Print out content
Read configuration file
Install
dotenv
(you can check the introduction on theNPM
official website):load the configuration file of.Env
in the root directory, and load the key value pair into the environment variable ofprocess.env
npm i dotenv
项目根目录,新建
.env
配置文件并添加配置.env
1
APP_PORT = 8000
读取配置
新建
src/config/config.default.js
1
2
3require('dotenv').config() // 导入dotenv,调用config方法,读取配置并写入到`process.env`中
module.export = process.env改写
main.js
,使用解构赋值的方式,获取到APP_PORT
的配置1
2
3
4
5
6
7
8
9
10
11const Koa = require('koa')
const {APP_PORT} = require('./config/config.default') // 导入process.env环境变量中的APP_PORT字段
const app = new Koa()
app.use((ctx, next) => {
ctx.body = 'hello world'
})
app.listen(APP_PORT, () => {
console.log(`server is running on http://localhost:${APP_PORT} !`); // 使用模板字符串
})启动开发服务
添加路由
所谓的api
,就是根据不同url
返回不同的数据
目前http://localhost:8000
和http://localhost:8000/users
返回的内容都是一样的
需要使用路由,来根据不同的url
,调用不同的处理函数
安装koa-router
- ``api官网:`router/API.md at master · koajs/router (github.com)
npm install @koa/router
官网案例:
1 | const Koa = require('koa'); |
配置多个路由
1 | const Koa = require('koa') // 导入Koa,由于导出的是类,一般大写 |
但是代码都写在一起肯定不行,需要拆分一下路由
新建src/router
文件夹
新建user.routes.js
1 | const Router = require('@koa/router') |
修改main.js
1 | const Koa = require('koa') // 导入Koa,由于导出的是类,一般大写 |
目录结构优化
拆分http
服务和业务代码
我们在main.js
里面写了太多的功能
拆分
http
服务与业务相关代码- 新建
src/app/index.js
,专门放业务代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16const Koa = require('koa') // 导入Koa,由于导出的是类,一般大写
const Router = require('@koa/router') // 导入router
const userRouter = require('../router/user.route')
const app = new Koa() // 实例化
const indexRouter = new Router()
indexRouter.get('/', (ctx, next) => {
ctx.body = 'hello world'
})
app.use(indexRouter.routes())
app.use(userRouter.routes())
module.exports = app修改
man.js
1
2
3
4
5
6
7
8
9const {APP_PORT} = require('./config/config.default')
const app = require('./app')
app.listen(APP_PORT, () => { // 开启服务
console.log(`server is running on http://localhost:${APP_PORT} !`);
})- 新建
抽离控制层
将路由router
中的处理函数,单独抽离成控制层controller
新建
src/controller
文件夹新建
user.controller.js
1
2
3
4
5
6
7class UserController{
async register(ctx, next) {
ctx.body = '用户注册成功'
}
}
module.exports = new UserController()修改
router/user.route.js
1
2
3
4
5
6
7
8
9const Router = require('@koa/router')
const router = new Router({prefix: '/user'})
const {register} = require('../controller/user.conroller')
// 注册接口
router.post('/register', register)
module.exports = router测试接口
这里我们写成了
post
请求,使用postman
或者是Apifox
(国内的)来测试post
请求这里以
apifox
为例,下载后,新建项目 > 新建接口按图示配置
配置好路由后,点击运行,可以拿到数据了:
我们再写一个登录的接口
user.router.js
1
2
3
4
5
6
7
8
9
10
11
12const Router = require('@koa/router')
const router = new Router({prefix: '/user'})
const {register, login} = require('../controller/user.conroller')
// 注册接口
router.post('/register', register)
// 登录接口
router.post('/login', login)
module.exports = routeruser.controller.js
1
2
3
4
5
6
7
8
9
10
11class UserController{
async register(ctx, next) {
ctx.body = '用户注册成功'
}
async login(ctx, next) {
ctx.body = '用户登录成功'
}
}
module.exports = new UserController()
解析body
、拆分service
层
解析body
完整的注册接口
1 | POST /user/register |
请求参数
1 | user_name, password |
响应
成功:
1 | { |
原型图:
koa
需要借助中间件,来解析参数
koa-body
:https://www.npmjs.com/package/koa-body1
A full-featured koa body parser middleware. Supports multipart, urlencoded, and json request bodies. Provides the same functionality as Express's bodyParser - multer.
官方基础样例:
1
2
3
4
5
6
7
8
9
10
11const Koa = require('koa');
const koaBody = require('koa-body'); // 1.引入中间件
const app = new Koa();
app.use(koaBody()); // 在所有请求之前,注册这个中间件,就把所有的内容写到了ctx.request.body里面
app.use(ctx => {
ctx.body = `Request Body: ${JSON.stringify(ctx.request.body)}`; // 3.看下request.body
});
app.listen(3000);- 更推荐,相比较于
koa-bodyparser
,还支持文件上传
- 更推荐,相比较于
koa-bodyparser
将之前apifox
的注册接口完善下,由于是post
请求,我们在body
里面,设置参数
安装koa-body
1 | npm i koa-body |
补充,将nodemon
安装到开发时依赖,先卸载:npm uninstall nodemon
,再重新安装到开发依赖:npm i nodemon -D
在app/index.js
中添加koa-body
相关代码
app/index.js
1 | const Koa = require('koa') |
控制层(处理函数)user.config.js
1 | class UserController{ |
回到apifox
中,我们生成下body
请求体,发送下注册的请求
后台也打印结果了
控制器里一般做这些事
- 1.获取数据
- 2.操作数据库
- 如果操作数据库的逻辑很复杂,也会单独抽出这一部分(
service
层)
- 如果操作数据库的逻辑很复杂,也会单独抽出这一部分(
- 3.返回结果
抽取servcie
层
和数据库相关的,根据客户端传递的不同参数,来操作数据库
新建src/service
目录
新建user.service.js
1 | class UserService { |
修改user.controller.js
1 | const {createUser} = require('../service/user.service') |
后台打印结果:
ORM
工具集成
sequelize
介绍
ORM
:对象关系映射
- 数据表映射(对应)一个类
- 数据表中的数据行(记录)对应一个对象
- 数据表字段对应对象的属性
- 数据表的操作,对应对象的方法
- 就是使用面向对象的方式,来操作数据库
使用sequelize
ORM
数据库工具:https://github.com/demopark/sequelize-docs-Zh-CN/tree/master
基于
Promise
的ORM
工具Sequelize 是一个基于
promise
的Node.js ORM
工具, 目前支持Postgres, MySQL, MariaDB, SQLite 以及 Microsoft SQL Server, Amazon Redshift 和 Snowflake’s Data Cloud
. 它具有强大的事务支持, 关联关系, 预读和延迟加载,读取复制等功能.安装
sequelize
和mysql2
(支持Promise
)1
npm i sequelize mysql2
得注意下安装的
sequelize
支持的最低版本的mysql
,目前默认安装的sequlize
版本是6.21.3
,对应的mysql
版本至少是5.7
及以上:https://github.com/demopark/sequelize-docs-Zh-CN/tree/v6
安装数据库
在正式连接之前,我们需要装下mysql
数据库,这里暂时安装windows
版本,参照:https://blog.csdn.net/jsugs/article/details/124143762
启动mysql
服务
1 | 输入net start mysql或sc start mysql |
改密码后再用navicat连接
,会报错Authentication plugin 'caching_sha2_password' cannot be loaded
,参照:https://www.jianshu.com/p/465a444ad846
1 | ALTER USER 'root'@'localhost' IDENTIFIED BY '123123' PASSWORD EXPIRE NEVER; #修改加密规则 |
查询mysql进程,并杀掉
1 | netstat -aon|findstr "3306" |
成功进入后,新建数据库
一开始是没有选中下面两个的,设置名称后直接确定
连接数据库
官方示例:https://github.com/demopark/sequelize-docs-Zh-CN/blob/v6/core-concepts/getting-started.md
1 | const { Sequelize } = require('sequelize'); |
新建src/db/seq.js
该文件中实现数据库的连接,并导出
1 | const {Sequelize} = require('sequelize') |
在db
目录下,使用node
测试下:
开发环境我们这样搞没事,生产环境可能会用连接池
配置文件
使用dotenv
将参数提取成配置文件
修改.env
1 | APP_PORT = 8000 |
seq.js
中导入并使用
1 | const { Sequelize } = require('sequelize') |
此时需要在根目录下测试,不然读不到.env
文件
测试完将测试代码注释掉
创建User
模型
模型创建
新建src/model
文件夹
service
层通过model
层来具体操作数据库
新建user.model.js
,使用define
方法来创建模型:https://www.sequelize.com.cn/core-concepts/model-basics#%E4%BD%BF%E7%94%A8-sequelizedefine
全局定义表名等于模型名,seq.js
:
1 | const { Sequelize } = require('sequelize') |
根据表设计文档,定义模型属性:
用户表
表名:sai_users
字段名 | 字段类型 | 说明 |
---|---|---|
id | int | 主键,自增(sequelize会自动维护) |
user_name | varchar(255) | 用户名,unique |
password | char(64) | 密码 |
is_admin | tinyint(1) | 0:不是管理员,1:是管理员 |
定义模型属性时的数据类型,参见:https://www.sequelize.com.cn/core-concepts/model-basics#%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B
user.model.js
1 | const { DataTypes } = require("sequelize") // 不要相信vscode的自动导入,坑!! |
有关模型同步:https://www.sequelize.com.cn/core-concepts/model-basics#%E6%A8%A1%E5%9E%8B%E5%90%8C%E6%AD%A5
根目录下,执行node src/model/user.model.js
就是执行了sql
语句
1 | PS D:\workspace\github\code\project-workshop\code-prac\koa\01> node .\src\model\user.model.js |
可以看到,数据库中多了一个表:
其中,createAt
和updatedAt
是sequelize
自动给我们维护的,如果不需要时间戳,在define
函数中,添加配置项:{timestamps: false}
,但是一般情况下,都是保留的
导出User
模型,并注释掉sync
的代码
1 | const { DataTypes } = require("sequelize") |
添加用户
我们继续完善写入数据库的代码
需要通过ORM
实现标准的CRUD
:https://www.sequelize.com.cn/core-concepts/model-querying-basics
user.service.js
1 | const User = require('../model/user.model') |
使用apifox
发送register
接口,成功后查看数据库
成功注册,注意下时区慢8小时
再看下后台打印:
执行的了insert
语句
User.create
返回的是一个sai_user
的表模型对象,dataValues
对应着表里面的一条记录
1 | Executing (default): INSERT INTO `sai_user` (`id`,`user_name`,`password`,`is_admin`,`createdAt`,`updatedAt`) VALUES (DEFAULT,?,?,?,?,?); |
我们要返回给用户dataValues
的结果
对于其他的值,在service
层就可以直接过滤掉,直接返回res.dataValues
user.service.js
1 | const User = require('../model/user.model') |
那么controller
层拿到返回的数据后,再根据接口文档,构建最终要返回给客户端的数据格式
注册接口:
成功
1 | { |
失败
1 | { |
修改控制层
user.controller.js
1 | const {createUser} = require('../service/user.service') |
再次使用apifox
测试下register
接口,注意要使用新的样例
整个的流程小结:
用户发送请求,koa
服务接受到请求,先导入各种中间件,然后处理路由,根据路由调用处理函数(控制层),处理函数中涉及业务逻辑及数据库操作(服务层),服务层根据模型层,返回给控制层操作数据库的结果,控制层根据该结果封装接口数据,返回给路由,最后koa
将路由的结果,作为接口响应发送到服务端
错误处理
重复注册和没有用户名,目前都会返回500
,错误类型不够细致
后台是可以看到两次操作的错误提示的
对于不同的错误类型,我们要分别处理
在控制层接受到用户参数时,要进行验证
合法性验证
user.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
38const {createUser} = require('../service/user.service')
class UserController{
async register(ctx, next) {
const {user_name, password} = ctx.request.body
// 合法性验证
if(!user_name || !password) {
// 记录错误信息,后续可以记录到错误日志中
console.error('用户名或密码为空')
ctx.status = 400
ctx.body = {
code: '10001', // 自定义的,公司一般会有开发规范
message: '用户名或者密码为空',
result: ''
}
return // 合法性验证不通过的话,直接返回
}
// 验证通过后,再去操作数据库
const res = await createUser(user_name, password)
console.log(res)
ctx.body = {
code: 0,
message: '用户注册成功',
result: {
id: res.id,
user_name: res.user_name
}
}
}
async login(ctx, next) {
ctx.body = '用户登录成功'
}
}
module.exports = new UserController()参数只写一个字段,测试一下注册接口:
可以看到后台,打印的错误日志
合理性验证
controller
层,需要根据传入的参数,查询数据库user.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
48const {createUser, getUserInfo} = require('../service/user.service')
class UserController{
async register(ctx, next) {
const {user_name, password} = ctx.request.body
if(!user_name || !password) {
console.error('用户名或密码为空')
ctx.status = 400
ctx.body = {
code: '10001',
message: '用户名或者密码为空',
result: ''
}
return
}
// 合理性验证
// 需要再次查询数据库 getUserInfo
if(getUserInfo({user_name})) { // 根据用户名来查询,参数使用对象,这样可以让查询参数不受顺序影响
ctx.status = 409 // 状态完成冲突,不熟悉的话,可以去MDN上看下常见状态码
ctx.body = {
code: '10002',
message: '用户名已经存在',
result: ''
}
return
}
const res = await createUser(user_name, password)
console.log(res)
ctx.body = {
code: 0,
message: '用户注册成功',
result: {
id: res.id,
user_name: res.user_name
}
}
}
async login(ctx, next) {
ctx.body = '用户登录成功'
}
}
module.exports = new UserController()service
层中新增getUserInfo
方法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
27const User = require('../model/user.model')
class UserService {
async createUser(user_name, password) {
const res = await User.create({ user_name, password })
return res.dataValues
}
async getUserInfo({id, user_name, password, is_admin}) { // 参数设计成一个对象,因为查询用户,有可能根据id、user_name、password、is_admin字段去查询
// 判断参数是否存在,拿到实参
const whereOpt = {}
id && Object.assign(whereOpt, {id})
user_name && Object.assign(whereOpt, {user_name})
password && Object.assign(whereOpt, {password})
is_admin && Object.assign(whereOpt, {is_admin})
// 调用ORM查询接口:findOne,这是一个异步函数
const res = User.findOne({
attributes: ['id', 'user_name', 'password', 'is_admin'],
where: whereOpt
})
return res ? res.dataValues : null
}
}
module.exports = new UserService()测试下接口返回值
选一个数据库中已经有的用户名进行测试
后代打印的
sql
错误处理函数封装
我们可以把格式的验证,单独封装成一个中间件(处理函数)
在controller
层拆分中间件
新建middleware/user.middleware.js
,中间件里定义各种函数,然后导出
user.middleware.js
从controller
里把合法性验证的代码抽离出来
1 | const userValidator = async (ctx, next) => { |
那么什么时候执行中间件呢?
在路由匹配的时候,只要路由一匹配上,就立刻调用校验的中间件
user.route.js
1 | const Router = require('@koa/router') |
用apifox
测试一下,可以正常打印错误日志
再抽离查询用户的代码(合理性验证)
user.middleware.js
1 | const { getUserInfo } = require('../service/user.service') |
user.route.js
1 | const Router = require('@koa/router') |
测试下用户名已存在的情况,正常
至此,逻辑已经很清晰了,但我们还可以进一步管理错误信息
统一错误管理
在koa
中,怎么进行错误管理呢?
- 中间件里提交错误类型
通过ctx.app
可以拿到实例化的koa
对象,它有一个emit
方法用来提交错误,我们可以对此做一个检测
ctx.app.emit('error', {}, ctx)
- 该对象是自定义的错误类型,可以统一放在一个新的文件里管理
新建src/constant
常量文件夹
新建error.type.js
1 | // 定义错误类型 |
修改user.middle.js
1 | const { getUserInfo } = require('../service/user.service') |
在app/index.js
中统一进行错误处理
1 | const Koa = require('koa') |
同级目录下,新建errHandler.js
错误处理函数
errHandler.js
1 | module.exports = (err, ctx) => { |
小问题
verifyUser
中间件调用的getUserInfo
返回的是一个Promise
对象,恒为真,正常注册流程也走不下去了
user.middleware.js
1 | const verifyUser = async (ctx, next) => { |
修改
添加
await
,相当于判断条件是一个表达式1
2
3
4
5
6
7
8
9
10
11const verifyUser = async (ctx, next) => {
const { user_name } = ctx.request.body
if (await getUserInfo({ user_name })) {
ctx.app.emit('error', userAlreadyExists, ctx)
return
}
await next()
}
另外假设中间件都没问题,到了控制层createUser
这一步了,目前我们对这一步做任何的异常处理,使用try-catch
来处理下
虽说这一步是sequelize
自己操作的数据库,不大可能会出错,但为了代码的健壮性,还是要处理下的
user.controller.js
1 | const { createUser } = require('../service/user.service') |
定义错误类型
constant/error.type.js
1 | // 定义错误类型 |
我们在`createUser方法中,模拟下写入数据库时发生了错误
user.service.js
1 | const User = require('../model/user.model') |
测试下
后台日志
建议调用service
层所有的函数时,都加上错误处理
继续完善verifyUser
1 | const verifyUser = async (ctx, next) => { |
error.type.js
1 | // 定义错误类型 |
可以看到,真正要写代码的部分,其实是比较少的
很重要的一部分,在于提高代码的质量上,都在一些异常捕获和错误处理上,这一块是需要下功夫的,平时写代码的时候,要有这样的意识
这样当代码上线后,如果有问题,我们调试错误会很方便
ctx.app.emit
提交的错误,最后会以接口的形式返回给客户端,如果服务器自己想记录错误信息,可以使用console.error()
来记录日志信息
目前来说,这里还是有一个问题,就是重复注册直接走到了register
,验证重复用户名的中间件直接就通过了!!
原因是getUserInfo
函数里的User.findOne
是一个异步函数,需要加一个await
,否则会出错
1 | async getUserInfo({id, user_name, password, is_admin}) { |
这样重复注册走到verifyUser
中间件时,就不会被当做异常处理
加密
在将密码保存到数据库之前,要对密码进行加密处理
md5
加密还是有可能被破解的,使用bcrypt
加密
第一个依赖也多,这里我们使用第二个bcryptjs
:在 JavaScript 中优化了 bcrypt
,零依赖关系。
安装
1 | npm i bcryptjs |
用法:有同步和异步的用法:https://www.npmjs.com/package/bcryptjs
使用:
- 将代码加密功能,也抽离成一个中间件(单一职责原则)
- 不想
bcrypt
这种加密方式,耦合到代码中 - 后面有可能会换成其他加密方式:
hash
、md5
- 不想
user.middleware.js
1 | const bcrypt = require('bcryptjs') |
user.route.js
导入并使用
测试注册接口成功后,查看user
表password
字段,已加密
仍存在的问题:密码在前端传给后端的过程中,是明文的,应该前端先加密,后端存储密文;而登录时,后面采用和前端一样的加密算法解密即可
小结:注册接口
梳理一下整理流程,及每个模块的功能
登录接口
user.controller.js
1 | async login(ctx, next) { |
完善apifox
添加样例,应为数据库有已有的数据
但事实上,现在login
对任何的输入都是可以的,我们需要对数据进行一个校验
是否为空(合法性校验)
- 复用
是否存在(合理性校验)
- 复用
验证登录
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
23const verifyLogin = async (ctx, next) => {
// 1.判断用户是否存在(不存在:报错)
const { user_name, password } = ctx.request.body
try {
const res = await getUserInfo({ user_name })
if (!res) {
console.log('用户不存在', res)
return ctx.app.emit('error', userNotFound, ctx)
}
// 2.找到了用户,比对密码是否匹配(不匹配:报错)
if (!bcrypt.compareSync(password, res.password)) {
return ctx.app.emit('error', userInvalidPassword, ctx)
}
} catch (err) {
console.error(err)
return ctx.app.emit('error', userLoginFailed, ctx) // getUserInfo出错,在不同场景下,抛出的错误应该是不同的
}
//通过
await next()
}登录成功后记录用户状态
- 用户认证与授权
用户认证和授权
颁发token
登录成功后,给用户颁发一个令牌token
,用户在以后的每一次请求中,携带这个令牌
前后端分离中,使用jwt
:json web token
header
:头部payload
:载荷signature
:签名
如何使用
使用
jsonwebtoken
包:https://www.npmjs.com/package/jsonwebtoken- 安装:
npm i jsonwebtoken
sign
方法生成token
- 安装:
user.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
32const jwt = require('jsonwebtoken')
// ...
class UserController {
// ...
async login(ctx, next) {
const { user_name } = ctx.request.body
// 获取用户信息(在paylaod中,记录id、user_name、is_admin)
try {
// 从返回结果中,过滤掉password,将剩下的属性,放在新的res对象中
const { password, ...res } = await getUserInfo({ user_name })
ctx.body = {
code: 0,
message: '用户登录成功',
result: {
/*
* @params1:配置对象
* @params2:秘钥
* @params3:过期时间,一天
*/
token: jwt.sign(res, JWT_SECRET, {expiresIn: '1d'})
}
}
} catch (err) {
console.error('用户登录失败', err)
return
}
}
}测试:
用户认证
我们新建一个修改密码的接口
修改的操作
PUT
:全量修改PATCH
:部分修改
写对应的路由
user.route.js
1 | const Router = require('@koa/router') |
先简单测试下
我们在上面颁发token
后,后续的请求(如修改密码)需要携带这个token
请求头新增Authorization
,值一开始固定的是Bearer
,有一个空格
后面发送请求时,需要加上签发的token
新建auth.middleware.js
文件,将认证相关的验证放在这里面
1 | const jwt = require('jsonwebtoken') |
error.type.js
1 | // 定义错误类型 |
在路由上,加上token
认证的中间件
user.route.js
1 | const Router = require('@koa/router') |
接口测试工具中,将登录成功后颁发的token
,存为全局变量
这样在修改密码的接口里,就可以不用复制token
了
但这里有个不好的地方,我想测试下错误token
的响应,是不支持修改的,测试的时候还是需要手动复制
正常接口返回的user
1 | user { |
测试无效的token
把token
失效时间改为5s
,测试过期token
(记得测试完改回去)
重新登录后,测试下
修改密码
测试没问题后,我们再加上加密的中间件,并正式写修改密码接口对应的处理函数
user.route.js
1 | // 修改密码接口 |
user.controller.js
1 | const { createUser, getUserInfo, updateById } = require('../service/user.service') |
user.service.js
1 | async updateById({ id, user_name, password, is_admin }) { // 接口的设计要考虑到复用性,不要这次只是根据id修改密码,就只写这一个功能 |
小问题:入参为空对象时,应该做处理;修改密码还是原密码时,应该做处理
商品模块
整体流程打通
路由
新建router/goods.route.js
1 | const Router = require('@koa/router') |
控制器
新建controller/goods/controller.js
1 | class GoodsController { |
app/index.js
1 | const Koa = require('koa') |
测试接口
至此,整个流程已经打通
自动加载路由
上面我们在app/index.js
注册路由时,需要手动一个个导入
可以使用fs
模块实现自动导入
新建router/index.js
1 | const fs = require('fs') |
改写app/index.js
1 | const Koa = require('koa') |
use(router.allowedMethods())
,请求类型不支持的话,会更友好的响应