Node.js+Koa2+MySQL实现小程序接口

Node.js+Koa2+MySQL实现小程序接口

文件目录

image.png

安装依赖

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');  // 获取校验字段(存在自动转换)
})
复制代码

数据库操作

连接数据库

sequelize官网文档

// 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
喜欢就支持一下吧
点赞0 分享