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
})
function whenLoadModule(obj) {
if (obj instanceof Router) {
InitManager.app.use(obj.routes())
}
}
}
}
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
}
}
}
校验器
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');
})
数据库操作
连接数据库
sequelize官网文档
const sequelize = new Sequelize(database, username, password,{host,port})
sequelize.sync({
force: false
})
建立用户模型
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)
const psw = bcrypt.hashSync(val, salt)
this.setDataValue("password", psw)
}
},
....
},{
sequelize,
tableName: 'user'
})
数据库操作
User.create()
User.destroy()
User.update()
User.findAll()
登陆验证(不同登陆方式)
账号登陆
const correct = bcrypt.compareSync(plainPassword, user.password)
const generateToken = function (uid, scope) {
const secretKey = global.config.security.secretKey
const expiresIn = global.config.security.expiresIn
const token = jwt.sign({
uid,
scope
}, secretKey, {
expiresIn
})
return token
}
小程序登录
const url = util.format(global.config.wx.loginUrl,
global.config.wx.appId,
global.config.wx.appSecret,
code)
const result = await axios.get(url)
let user = await User.getUserByOpenid(result.data.openid)
if (!user) {
user = await User.createUserByOpenid(result.data.openid)
}
...
接口权限认证(中间件)
- 在每个接口前使用该中间件,如果用户权限不足将不能访问该接口
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() {
return async (ctx, next) => {
const tokenToken = basicAuth(ctx.req)
let errMsg = "token不合法"
if (!tokenToken || !tokenToken.name) {
throw new global.errs.Forbidden(errMsg)
}
try {
var decode = jwt.verify(tokenToken.name, global.config.security.secretKey)
} catch (error) {
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
}
}
}
static verifyToken(token) {
try {
jwt.verify(token, global.config.security.secretKey)
return true;
} catch (e) {
return false
}
}
}
module.exports = {
Auth
}