【NodeJs】基于nodejs加koa2搭建一个的后端服务框架并实现一个简单的token生成及校验功能

1,669 阅读5分钟

「这是我参与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:自动重启服务

项目目录结构

image.png

配置公共类库
  • 首先在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就实现了,虽然功能不多但是服务端一些基本的功能点都涉及到了,有需要的小伙伴可以自行扩展。 喜欢的小伙伴欢迎点赞评论加留言哦。