「这是我参与2022首次更文挑战的第1天,活动详情查看:2022首次更文挑战」。
背景
最近在学习nodejs以及相关框架koa。后来发现学习时是学会了,但是没过多久再想使用时又忘了,于是决定通过一个小demo的形式将所学及所用的知识记录一下,便于日后回顾和使用。
案例介绍
本文将通过一个简单的小案例来搭建一个基于nodejs+koa的后端服务。主要涉及到的功能点有:
- token生成及校验
- token校验中间件
- 不同模块路由拆分及组合
- 用户登录及信息查询API
- db相关操作类封装
- 常用工具类封装
用到的知识点及第三方库
- jsonwebtoken: 用于生成及校验token
- koa: 服务端框架
- koa-router:处理路由
- koa-static:处理静态资源
- koa-bodyparser:解析post请求参数
- mssql:连接SqlServer数据库
- nodeman:自动重启服务
项目目录结构
配置公共类库
- 首先在utils目录下新建一个config.js用于添加一些通用的配置信息,在本文中我们先添加两个属性:PRIVATE_KEY和EXPIRED用于保存生成token时的秘钥和过期时间
// utils/config.js
module.exports = {
"PRIVATE_KEY":"TOKEN-DEMO-SECRET",
"EXPIRED": 60*60*24
}
- 在utils目录下新建一个HttpCode.js文件,用于封装一些自定义的常用的http错误码和错误信息(非必须,看个人喜好和业务需要)
// utils/HttpCode.js
module.exports = {
Success: {
success_ok: 2000,
success_ok_msg: '请求成功',
success_created: 2001,
success_created_msg: '请求已创建',
success_accepted: 2002,
success_accepted_msg: '请求已受理'
},
Redirect: {
redirect_moved_permanently: 3001,
redirect_moved_permanently_msg: '请求资源已被永久移动到新位置',
redirect_moved_temporarily: 3002,
redirect_moved_temporarily_msg: '请求资源临时从其它url相应请求',
redirect_not_modified: 3004,
redirect_not_modified_msg: '资源未更改,直接从协商缓存中获取'
},
RequestError: {
request_error_bad_request: 4000,
request_error_bad_request_msg: '无效的请求',
request_error_unauthorized: 4001,
request_error_unauthorized_msg: '请求未授权,检查请求头部是否包含token信息',
request_error_forbidden: 4003,
request_error_forbidden_msg: '请求被拒绝,token无效或已过期',
request_error_not_found: 4004,
request_error_not_found_msg: '请求资源在服务器上不存在',
request_error_method_not_allowed: 4005,
request_error_method_not_allowed_msg: '请求方式不被允许'
},
ServerError: {
server_error_internal_error: 5000,
server_error_internal_error_msg: '服务器内部错误',
server_error_not_imp: 5001,
server_error_not_imp_msg: '服务器不支持当前请求',
server_error_bad_gateway: 5002,
server_error_bad_gateway_msg: '无效的网关'
}
}
- 在utils目录下新建一个Jwt.js用于封装token的生成及校验方法。引入第三方库jsonwebtoken,借助jsonwebtoken的sign和verify两个方法来实现token的生成和校验
const jwt = require('jsonwebtoken');
const {PRIVATE_KEY,EXPIRED } = require('./config')
const HttpCode = require('./HttpCode')
class Jwt {
static generateToken(payload){
const token = jwt.sign(payload, PRIVATE_KEY,EXPIRED);
return token;
}
static verifyToken(token){
try{
let tokenInfo = jwt.verify(token,PRIVATE_KEY, {algorithms: ['HS256']} )
return HttpCode.Success.success_ok;
} catch(err){
return HttpCode.RequestError.request_error_forbidden;
}
}
}
module.exports = Jwt;
添加中间件
- 在middleware目录下添加一个tokenvalidate.js文件,在该文件中添加一个中间件,用于拦截除登录以外的所有请求进行token的有效性验证,如果token存在并且有效则正常向下执行,如果token不存在或已过期则直接拦停该请求并抛出错误提示。
const Jwt = require('../utils/Jwt')
const HttpCode = require('../utils/HttpCode')
module.exports = function (){
return async function(ctx, next){
if(ctx.req.url !== '/api/user/login'){
const token = ctx.request.header.authorization;//从请求头的authorization属性中获取token
if(token){
const status = Jwt.verifyToken(token);
if(status === HttpCode.RequestError.request_error_forbidden){
ctx.body = {
code: HttpCode.RequestError.request_error_forbidden,
message:HttpCode.RequestError.request_error_forbidden_msg
}
}else{
await next();
}
}else{
ctx.body = {
code: HttpCode.RequestError.request_error_unauthorized,
message:HttpCode.RequestError.request_error_unauthorized_msg
}
}
}else{
await next()
}
}
}
配置db操作
- 在db目录下新增db.js用于配置与数据库操作相关数据,本文以微软的SqlServer为例
// db/db.js
const sql = require('mssql')
const db_server = '127.0.0.l'
const db_name = 'demo'
const config = {
user:'sa',
password:'admin@123',
server:db_server,
database:db_name,
options:{
trustServerCertificate: true
}
}
//封装通用获取数据方法
async function dataGet(sql_text){
try{
await sql.connect(config);
const result = await sql.query(sql_text);
await sql.close();
return result;
}catch(err){
console.log(err)
}
}
module.exports = {
dataGet
}
- 在db目录下新增db_user.js文件用于封装一些与用户操作相关的方法,如用户信息获取等
// db/db_user.js
const db = require('./db')
function getUserInfo(userNo){
return db.dataGet("select * from userInfo where userno = `${userNo}`")
}
module.exports = {
getUserInfo
}
API 封装
API的封装需借助路由来实现,但一般在一个项目中会分为不同的业务模块,而不同的业务模块也会有各种各样不同的接口,因此我们不能将所有的API都一股脑的定义在同一个文件中,那么我们就需要通过定义多个路由(koa-router)为不同的业务模块封装不同的业务接口,最后在将所有的路由整合到一起变成一个大的路由使用。
- 在routers目录下新增user.js文件,在该文件中封装一些跟用户相关的业务接口,本文以登录路由为例
// routers/user.js
const Router = require('koa-router')
const Jwt = require('../utils/Jwt')
const HttpCode = require('../utils/HttpCode')
const router = new Router();
const dbUser = require('../db/db_user')
router.post('/login', async (ctx, next)=>{
let {name, pwd} = ctx.request.body;
//...此处省略用户密码验证
// 如果验证通过,则拿到用户信息返回给前台
const result = await dbUser.getUserInfo(name);
const token = Jwt.generateToken({userInfo:{userName:name}})
ctx.body = {
code: HttpCode.Success.success_ok,
msg:HttpCode.Success.success_ok_msg,
token:token,
data:result.recordset
}
})
module.exports = router;
- 在routers目录下新增index.js用于作为主路由来整合其它各个业务模块的所有子路由
// rouers/index.js
const Router = require('koa-router')
const user = require('./user')
const router = new Router({
prefix:'/api' //统一路由前缀
})
//整合业务模块子路由
router.use('/user',user.routes(), user.allowedMethods())
module.exports = router;
服务端主入口配置
- 所有的准备工作都已完成,接下来就是配置我们的主入口文件app.js了,在该文件中主要有如下几个操作:
- 引入类库
- 创建koa对象并调用listen方法启动服务
- 添加koastatic中间件处理静态资源
- 添加bodyParser中间件处理post请求参数解析
- 添加tokenValidate中间件拦截所有请求进行token有效性校验
- 添加路由中间件处理路由信息
const Koa = require('koa')
const koastatic = require('koa-static')
const bodyParser = require('koa-bodyparser')
const router = require('./routers')
const TokenValidate = require('./middleware/TokenValidate')
const app = new Koa();
app.listen(3000, function(){
console.log(`The server started at port: 3000, you can access it by http://localhost:3000`)
});
app.use(koastatic('./statics/'));
app.use(bodyParser());
app.use(TokenValidate());
app.use(router.routes());
app.use(router.allowedMethods());
总结
到此一个简单的基于nodejs+koa的token生成及校验的小demo就实现了,虽然功能不多但是服务端一些基本的功能点都涉及到了,有需要的小伙伴可以自行扩展。 喜欢的小伙伴欢迎点赞评论加留言哦。