Node.js+Koa2+MySQL implement small program interface

File directory

Install dependencies

koa koa-bodyparser koa-router koa-static require-directory
validator lodash jsonwebtoken sequelize mysql2 bcryptjs
basic-auth axios
Copy the code

Automatic route registration

  • Define entry methods for registering global properties, methods, or exception handling, etc
  • Rely on require-directory dependencies for automatic route registration
const Router = require('koa-router')
const requireDirectory = require('require-directory')

  class InitManager {
    static initCore(app) {
        // The entry method
        InitManager.app = app;
        InitManager.initLoadRouters()
    }

    // Load all routes
    static initLoadRouters() {
        // Absolute path
        const apiDirectory = `${process.cwd()}/app/api`
        // Routes are automatically loaded
        requireDirectory(module, apiDirectory, {
            visit: whenLoadModule
        })

        // Determine whether the module loaded by requireDirectory is a route
        function whenLoadModule(obj) {
            if (obj instanceof Router) {
                InitManager.app.use(obj.routes())
            }
        }
    }
}

    // use the entry method in app.js
    InitManager.initCore(app)
Copy the code

Global exception handling

  • Http-exception.js defines different exception type classes
class HttpException extends Error {
    constructor(msg = 'Server exception', errorCode = 10000, code = 400) {
        super(a)this.errorCode = errorCode
        this.code = code
        this.msg = msg
    }
}

class ParameterException extends HttpException {
    constructor(msg, errorCode) {
        super(a)this.code = 400
        this.msg = msg || 'Parameter error'
        this.errorCode = errorCode || 10000}}Copy the code
  • Exception. Js uses onion routing for error handling (route registration comes first)
const {HttpException} = require('.. /core/http-exception')

const catchError = async (ctx, next) => {
    try {
        await next()

    } catch (error) {
        // Development environment
        const isHttpException = error instanceof HttpException
        const isDev = global.config.environment === 'dev'

        if(isDev && ! isHttpException) {throw error
        }

        // Build the environment
        if (isHttpException) {
            ctx.body = {
                msg: error.msg,
                error_code: error.errorCode,
                request: `${ctx.method} ${ctx.path}`
            }
            ctx.status = error.code

        } else {
            ctx.body = {
                msg: "Unknown error!".error_code: 9999.request: `${ctx.method} ${ctx.path}`
            }
            ctx.status = 500}}}Copy the code

validators

// Define rules using lin-validator
const { Rule, LinValidator } = require('.. /.. /core/lin-validator-v2')
class PositiveIntegerValidator extends LinValidator {
  constructor() {
      super(a)this.id = [
          new Rule('isInt'.I need a positive integer., {min: 1}}})]Copy the code
// Use checksum
router.get('/v1/book/:index/latest'.async (ctx, next) => {
  const v = await new PositiveIntegerValidator().validate(ctx, {
    id: 'index'
  })
  const index = v.get('path.index');  // Get the checksum field (automatic conversion exists)
})
Copy the code

Database operations

Connecting to a Database

Sequelize official document

// db.js
// Connect to the database (sequelize)
const sequelize = new Sequelize(database, username, password,{host,port})  // Official document specific parameters
sequelize.sync({
  force: false  // Whether to re-establish the database
})

Copy the code

Build a user model

//user.js
// Define the user model
class User extends Model{
    Static methods can be defined to implement specific functions, such as login authentication
}
// Initialize the user model
User,init({
  // Table field, see official website
  id: {
    type: Sequelize.INTEGER,
    primaryKey: true./ / the primary key
    autoIncrement: true  // Auto-increment
  },
  password: {
    type: Sequelize.STRING,
    set(val) {
      / / encryption
      const salt = bcrypt.genSaltSync(10)  / / bcryptjs library
      // Generate an encrypted password
      const psw = bcrypt.hashSync(val, salt)
      this.setDataValue("password", psw)
    }
  },
  ....
},{
  sequelize,
  tableName: 'user' / / the name of the table
})
Copy the code

Database operations

// See the official website for details
User.create()
User.destroy()
User.update()
User.findAll()
Copy the code

Login verification (different login methods)

Account login

// Account password verification, database comparison (bcryptJS library) decryption (registered account saved to the database password encrypted)
const correct = bcrypt.compareSync(plainPassword, user.password)
Copy the code
// Issue a token to obtain a token
const generateToken = function (uid, scope) {
    const secretKey = global.config.security.secretKey
    const expiresIn = global.config.security.expiresIn
    const token = jwt.sign({
        uid,  / / user id
        scope  // Define user permissions
    }, secretKey, {
        expiresIn
    })

    return token
}
Copy the code

Applets login

// The applets API is called mainly to get the user openID
const url = util.format(global.config.wx.loginUrl,
      global.config.wx.appId,
      global.config.wx.appSecret,
      code)
const result = await axios.get(url)

// Check whether wechat user OpenDID exists in the database
let user = await User.getUserByOpenid(result.data.openid)
// If not, create a wechat applet user
if(! user) { user =await User.createUserByOpenid(result.data.openid)
}
// Issue a token to obtain a token (as above).Copy the code

Interface authorization (Middleware)

  • Use the middleware in front of each interface, and if the user has insufficient permissions, the interface will not be accessible
const basicAuth = require('basic-auth')
const jwt = require('jsonwebtoken')

class Auth {
  constructor(level) {
    this.level = level || 1  // Pass in the interface permissions
    // Assign permissions to users
    Auth.AUSE = 8
    Auth.ADMIN = 16
    Auth.SPUSER_ADMIN = 32
  }
  get m() {
     // HTTP specifies the authentication mechanism HttpBasicAuth
    return async (ctx, next) => {
      const tokenToken = basicAuth(ctx.req)
      let errMsg = "Token is illegal"
      // If no token is required
      if(! tokenToken || ! tokenToken.name) {throw new global.errs.Forbidden(errMsg)
      }
      // 
      try {
        var decode = jwt.verify(tokenToken.name, global.config.security.secretKey)
      } catch (error) {
        // Token is invalid and expires
        if (error.name === 'TokenExpiredError') {
          errMsg = "Token has expired"
        }
        throw new global.errs.Forbidden(errMsg)
      }
      // Check whether the user has permission to access the interface
      if (decode.scope <= this.level) {
        errMsg = "Insufficient authority"
        throw new global.errs.Forbidden(errMsg)
      }
      ctx.auth = {
          uid: decode.uid,
          scope: decode.scope
      }
    }
  }
  // Verify the token is valid
  static verifyToken(token) {
    try {
      jwt.verify(token, global.config.security.secretKey)
      return true;
    } catch (e) {
        return false}}}module.exports = {
  Auth
}
Copy the code