Node.js+Koa2+MySQL实现小程序接口
文件目录
安装依赖
koa koa-bodyparser koa-router koa-static require-directory
validator lodash jsonwebtoken sequelize mysql2 bcryptjs
basic-auth axios
复制代码
自动注册路由
- 定义入口方法,方便注册全局属性,方法,或异常处理等
- 依靠require-directory依赖,进行自动路由注册
const Router = require('koa-router')
const requireDirectory = require('require-directory')
class InitManager {
static initCore(app) {
// 入口方法
InitManager.app = app;
InitManager.initLoadRouters()
}
// 加载全部路由
static initLoadRouters() {
// 绝对路径
const apiDirectory = `${process.cwd()}/app/api`
// 路由自动加载
requireDirectory(module, apiDirectory, {
visit: whenLoadModule
})
// 判断 requireDirectory 加载的模块是否为路由
function whenLoadModule(obj) {
if (obj instanceof Router) {
InitManager.app.use(obj.routes())
}
}
}
}
// app.js 里入口方法使用
InitManager.initCore(app)
复制代码
全局异常处理
- http-exception.js 定义不同异常类型类
class HttpException extends Error {
constructor(msg = '服务器异常', errorCode = 10000, code = 400) {
super()
this.errorCode = errorCode
this.code = code
this.msg = msg
}
}
class ParameterException extends HttpException {
constructor(msg, errorCode) {
super()
this.code = 400
this.msg = msg || '参数错误'
this.errorCode = errorCode || 10000
}
}
复制代码
- exception.js 利用洋葱路由方式进行错误处理(路由注册放在最前面)
const {HttpException} = require('../core/http-exception')
const catchError = async (ctx, next) => {
try {
await next()
} catch (error) {
// 开发环境
const isHttpException = error instanceof HttpException
const isDev = global.config.environment === 'dev'
if (isDev && !isHttpException) {
throw error
}
// 生成环境
if (isHttpException) {
ctx.body = {
msg: error.msg,
error_code: error.errorCode,
request: `${ctx.method} ${ctx.path}`
}
ctx.status = error.code
} else {
ctx.body = {
msg: "未知错误!",
error_code: 9999,
request: `${ctx.method} ${ctx.path}`
}
ctx.status = 500
}
}
}
复制代码
校验器
//分模板定义规则,使用lin-validator
const { Rule, LinValidator } = require('../../core/lin-validator-v2')
class PositiveIntegerValidator extends LinValidator {
constructor() {
super()
this.id = [
new Rule('isInt', '需要正整数', {min: 1})
]
}
}
复制代码
//使用校验
router.get('/v1/book/:index/latest', async (ctx, next) => {
const v = await new PositiveIntegerValidator().validate(ctx, {
id: 'index'
})
const index = v.get('path.index'); // 获取校验字段(存在自动转换)
})
复制代码
数据库操作
连接数据库
// db.js
// 连接数据库(sequelize)
const sequelize = new Sequelize(database, username, password,{host,port}) // 官方文档具体参数
sequelize.sync({
force: false // 是否重新建立数据库
})
复制代码
建立用户模型
//user.js
// 定义用户模型
class User extends Model{
// 可以定义静态方法,实现具体功能,如登陆验证
}
// 初始化用户模型
User,init({
// 表字段,具体看官网
id: {
type: Sequelize.INTEGER,
primaryKey: true, // 主键
autoIncrement: true // 自动递增
},
password: {
type: Sequelize.STRING,
set(val) {
// 加密
const salt = bcrypt.genSaltSync(10) // bcryptjs库
// 生成加密密码
const psw = bcrypt.hashSync(val, salt)
this.setDataValue("password", psw)
}
},
....
},{
sequelize,
tableName: 'user' // 表名
})
复制代码
数据库操作
// 增删改查 具体看官网
User.create()
User.destroy()
User.update()
User.findAll()
复制代码
登陆验证(不同登陆方式)
账号登陆
// 账号密码验证,数据库对比(bcryptjs库)解密 (注册账号保存到数据库密码经过加密)
const correct = bcrypt.compareSync(plainPassword, user.password)
复制代码
// 颁发令牌,获取token
const generateToken = function (uid, scope) {
const secretKey = global.config.security.secretKey
const expiresIn = global.config.security.expiresIn
const token = jwt.sign({
uid, // 用户id
scope // 定义用户权限
}, secretKey, {
expiresIn
})
return token
}
复制代码
小程序登录
// 调用小程序api主要为了获取用户openid
const url = util.format(global.config.wx.loginUrl,
global.config.wx.appId,
global.config.wx.appSecret,
code)
const result = await axios.get(url)
// 判断数据库是否存在微信用户 opendid
let user = await User.getUserByOpenid(result.data.openid)
// 如果不存在,就创建一个微信小程序用户
if (!user) {
user = await User.createUserByOpenid(result.data.openid)
}
// 颁发令牌,获取token (同上)
...
复制代码
接口权限认证(中间件)
- 在每个接口前使用该中间件,如果用户权限不足将不能访问该接口
const basicAuth = require('basic-auth')
const jwt = require('jsonwebtoken')
class Auth {
constructor(level) {
this.level = level || 1 // 传递过来接口权限
// 给用户分配的权限
Auth.AUSE = 8
Auth.ADMIN = 16
Auth.SPUSER_ADMIN = 32
}
get m() {
// HTTP 规定 身份验证机制 HttpBasicAuth
return async (ctx, next) => {
const tokenToken = basicAuth(ctx.req)
let errMsg = "token不合法"
// 如果不带token
if (!tokenToken || !tokenToken.name) {
throw new global.errs.Forbidden(errMsg)
}
//
try {
var decode = jwt.verify(tokenToken.name, global.config.security.secretKey)
} catch (error) {
// token 不合法 过期
if (error.name === 'TokenExpiredError') {
errMsg = "token已过期"
}
throw new global.errs.Forbidden(errMsg)
}
// 判断用户是否有权限访问该接口
if (decode.scope <= this.level) {
errMsg = "权限不足"
throw new global.errs.Forbidden(errMsg)
}
ctx.auth = {
uid: decode.uid,
scope: decode.scope
}
}
}
// 验证token是否有效
static verifyToken(token) {
try {
jwt.verify(token, global.config.security.secretKey)
return true;
} catch (e) {
return false
}
}
}
module.exports = {
Auth
}
复制代码
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END