# 介绍

基于 Egg.js 的后端脚手架。

# 功能列表

模块 功能介绍 进度
数据库操作 内置 ORM egg-sequelize 插件作为数据库操作库,可适配 Postgres、MySQL、MariaDB、SQLite 数据库
接口路由配置 内置 egg-router-plus 插件解决了路由命名空间问题
自动生成接口文档 内置 egg-swagger-doc 插件,按照约定书写接口注释即可自动生成接口使用文档
字段校验 内置 egg-validate-plus 插件作为字段校验,即可以单独使用,也可以配合 egg-swagger-doc 插件使用,既自动生成接口文档又实现了字段校验
多环境配置 内置了本地开发环境、测试环境和生产环境配置,切换和扩展都是非常便捷的
跨域配置 多环境下设置了不同的跨域配置,应对各个环境下对跨域策略的不同诉求
异常处理 统一封装处理抛出的错误,不将错误直接抛出给前端,提高安全性和比较好的前端使用体验
工具库 封装了菜单树处理、版本号对比、uid 生成等常用的工具函数,开发更便捷
单元测试 完整的单元测试,代码更健壮
微信小程序登录 内置了微信小程序授权登录和获取手机号的全流程,可与微信小程序无缝对接
eslint 内置了比较严格和完整的 eslint 规则,保证了代码规范,协同开发更高效

# 项目结构

.
├── LICENSE
├── README.md              # 搞事之前请先读我
├── app                    # 主程序代码目录
│   ├── contract           # 自动生成接口文档需要的数据结构
│   ├── controller         # 控制器
│   ├── extend             # 框架扩展
│   ├── middleware         # 中间件
│   ├── model              # 数据模型
│   ├── public             # 公共静态资源
│   ├── router.js          # 路由配置
│   └── service            # 服务
├── app.js                 # 入口
├── appveyor.yml
├── config
│   ├── config.default.js  # 默认配置,包含自定义的应用配置、安全策略、cookie、跨域、权限等
│   ├── config.local.js    # 本地环境特有的配置项
│   ├── config.prod.js     # 生产环境特有的配置项
│   ├── config.test.js     # 测试环境特有的配置项
│   ├── config.unittest.js # 单元测试环境特有的配置项
│   └── plugin.js          # 插件,可在里面自定义启用禁用某些插件
├── jsconfig.json
├── db
│   ├── database.sql       # 创建数据库脚本
│   └── tables.sql         # 创建数据表脚本
├── docs
│   ├── note.md            # 项目开发随记
├── package.json
├── test                   # 单元测试
│   └── app
└── yarn.lock

# 常用命令

推荐使用 yarn 来管理依赖。

# 安装
yarn install

# 启动开发
yarn run dev

# 生产环境启动
yarn run start

# 生产环境停止
yarn run stop

# 项目配置

# 数据库配置

若没有数据库,可执行下面的 sql 新建一个数据库:

CREATE DATABASE IF NOT EXISTS template_node_egg DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci;

然后导入 user 数据表:

/*
 Navicat Premium Data Transfer

 Source Server         : 本地
 Source Server Type    : MySQL
 Source Server Version : 50722
 Source Host           : localhost:3306
 Source Schema         : template_node_egg

 Target Server Type    : MySQL
 Target Server Version : 50722
 File Encoding         : 65001

 Date: 02/01/2022 23:22:25
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for user
-- ----------------------------
CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `open_id` varchar(255) NOT NULL COMMENT '同微信的 openid',
  `union_id` varchar(255) DEFAULT NULL COMMENT '同微信的 unionid,作为预留字段,不一定有值',
  `nick_name` varchar(255) CHARACTER SET utf8mb4 NOT NULL COMMENT '昵称,同微信的 nickName',
  `password` varchar(255) DEFAULT NULL COMMENT '登录密码,作为预留字段,不一定有值',
  `avatar_url` text NOT NULL COMMENT '头像,同微信的 avatarUrl',
  `phone` varchar(255) DEFAULT NULL COMMENT '手机号码,可能为空',
  `gender` int(11) DEFAULT NULL COMMENT '性别,可能为空',
  `country` varchar(255) DEFAULT NULL COMMENT '国家,可能为空',
  `province` varchar(255) DEFAULT NULL COMMENT '省份,可能为空',
  `city` varchar(255) DEFAULT NULL COMMENT '城市,可能为空',
  `language` varchar(255) DEFAULT NULL COMMENT '语言,可能为空',
  `logged_at` datetime DEFAULT NULL COMMENT '最后登录时间',
  `created_at` datetime NOT NULL,
  `updated_at` datetime NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1000 DEFAULT CHARSET=utf8mb4;

-- ----------------------------
-- Records of user
-- ----------------------------
BEGIN;
INSERT INTO `user` VALUES (1000, 'o8FXk5E4u7hwaguN6kSq-KPXApJ1', NULL, '测试账号', NULL, 'https://gimg2.baidu.com/image_search/src=http%3A%2F%2F5b0988e595225.cdn.sohucs.com%2Fimages%2F20180520%2F0473e00bdfd2476fbe0c228a45a1652c.jpeg&refer=http%3A%2F%2F5b0988e595225.cdn.sohucs.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1628131130&t=05ee794a54bad8edd2fd8bb2536db5b9', NULL, NULL, NULL, NULL, NULL, NULL, '2022-01-01 23:59:59', '2022-01-01 23:59:59', '2022-01-01 23:59:59');
COMMIT;

SET FOREIGN_KEY_CHECKS = 1;

然后 yarn run dev 启动开发,若是第一次启动,可能需要在 config/config.local.js 修改一下密码配置:

config.sequelize = {
  // 连接用户
  username: 'root',
  // 连接密码
  password: '',
  // 连接的数据库,可根据需要改成已有的数据库
  database: 'template_node_egg',
  // 连接地址
  host: '127.0.0.1',
  // 数据库类型
  dialect: 'mysql'
}

提示

如果已有的数据库里面 user 表字段跟 model/user.js 中定义的对不上,需要自行修改,否则会报错

# 应用配置

自定义的应用配置,修改后全局生效。

/**
 * 自定义的应用配置,修改后全局生效
 */
// 接口前缀名称,跟随业务系统修改
const apiPrefixName = 'api'
// 接口完整前缀
const apiPrefix = `/${apiPrefixName}`
const userConfig = {
  // 应用名称,用于日志文件目录指定、cookie 的 key 指定,具有唯一性,默认是 app.name,也可以改成其他字符串
  appName: app.name,
  apiPrefixName,
  apiPrefix,
  // 默认的 code 码和错误提示信息配置,只需要改这一个地方即可
  resCode: {
    // 服务器异常的 code 标识和提示,一般都不需要改
    serverError: { code: 500, message: '服务器异常' },
    // 成功的 code 标识
    success: { code: 0 },
    // 出错的 code 标识和提示
    error: { code: 602, message: '参数异常' },
    // 未登录的 code 标识和提示
    notLogged: { code: 601, message: '请先登录后再操作' }
  }
}

# 安全策略配置

  • 脚手架默认启用了 Egg.js 提供的安全策略。

  • 脚手架默认允许跨域,而在生产环境关闭此设置,同时自动添加了白名单,开发启动更加便捷:

// 前端端口,跟随实际情况修改
const port = 9001
const domainWhiteList = [
  ...new Set([
    `http://127.0.0.1:${port}`,
    `http://localhost:${port}`,
    // 服务启动时尝试自动获取本机 IP 设置白名单
    `http://${getLocalhost()}:${port}`
  ])
]
config.security = {
  domainWhiteList
}
// 默认允许跨域,生产环境关闭此设置
config.cors = {
  origin: '*',
  allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH'
}

# 权限配置

带登录体系的接口服务,基本都需要指定一部分不需要登录就能访问的接口,脚手架已内置了跟登录相关的白名单接口:

[
  `${apiPrefix}/user/mock`,
  `${apiPrefix}/user/login`,
  `${apiPrefix}/user/logout`,
  `${apiPrefix}/user/phone`
]

# 自动生成文档配置

内置 egg-swagger-doc 插件,按照约定书写接口注释即可自动生成接口使用文档。所有的数据模型结构都在 app/contract 中定义。

具体使用文档参见这里egg-swagger-doc 使用文档 (opens new window),这里就不赘述了。

若不需要此功能,也可在 config/plugin.js 中禁用自动生成文档功能:

{
  swaggerdoc: {
    enable: false,
    package: 'egg-swagger-doc'
  }
}

# 开发

一般开发一个全新接口,需要完成以下四步:

# 新增模型

app/model 中新增一个 log.js 文件并添加数据模型:

'use strict'

/**
 * 日志表
 */

module.exports = app => {
  // Sequelize.js 提供的数据类型
  const { STRING } = app.Sequelize

  const Log = app.model.define(/* 数据表名称 */ 'log', {
    // 数据表字段
    content: {
      // 字段类型
      type: STRING(255),
      // 字段注释
      comment: '操作内容'
    },
    remarks: {
      type: STRING(255),
      allowNull: false,
      comment: '备注'
    },
    actionType: {
      type: STRING(255),
      comment: '操作类型'
    }
  })

  return Log
}

# 新增服务

app/service 中新增一个 log.js 文件并添加一个获取列表的数据库操作方法:

'use strict'

const Service = require('egg').Service

class LogService extends Service {
  // 获取日志列表
  async logs(actionType) {
    const { model } = this.ctx

    return await model.Log.findAll({
      where: {
        actionType
      }
    })
      .then(res => {
        if (res) return res.map(item => item.toJSON())
        return res
      })
  }
}

module.exports = LogService

# 新增控制器

app/contract/dto.js 中新增一个 logInfo 模型:

{
  // ...

  logInfo: {
    content: { type: 'string', description: '操作内容' },
    remarks: { type: 'string', description: '备注' },
    actionType: { type: 'string', description: '操作类型' }
  }
}

然后在 app/controller 中新增一个 log.js 文件并添加一个获取列表的方法:

'use strict'

const Controller = require('egg').Controller

/**
 * @controller Log 日志模块
 */
class LogController extends Controller {
  /**
   * @summary 获取操作记录列表
   * @description 根据指定的筛选条件查询用户操作列表
   * @router get /log/logs
   * @response 0 logInfo 日志列表
   */
  async logs() {
    const { ctx } = this
    const { service, helper } = ctx
    const { actionType } = ctx.request.query

    // 参数校验
    const rules = {
      actionType: { required: true, message: '操作类型不能为空' }
    }
    const passed = await ctx.validate(rules, ctx.request.query)
    if (!passed) return

    const data = await service.log.logs(actionType)
    if (data) {
      helper.success(data)
    } else {
      helper.error(null, '日志列表失败')
    }
  }
}

module.exports = LogController

# 新增接口

app/router.js 中新增一个路由接口:

// 获取日志列表
subRouter.get('/log/logs', controller.log.logs)

以上例子当然是比较简单的,但大体步骤就是这样。

当然一般新增一个接口时,是不需要再新增一个控制器文件的,基本都是往某个控制器里面添加一个方法,然后在服务里面添加一个数据库操作方法即可。

# 发布

发布之前,需要在 config/config.prod.js 中配置好数据库连接配置:

config.sequelize = {
  username: 'root',
  password: '',
  database: 'template_node_egg',
  host: '127.0.0.1',
  dialect: 'mysql'
}

启动:

yarn run start

停止:

yarn run stop

# Sequelize 使用文档

Sequelize 官方文档 (opens new window)

# 快捷语法

针对开发中高频出现的代码块封装了一个 VSCode 插件,目前支持的模板有:

  • egg - controller 骨架
  • egg - controller 基础结构
  • egg - controller 方法骨架
  • egg - controller 参数校验
  • egg - controller swagger 注释
  • egg - service 骨架
  • egg - service 方法骨架
  • egg - service 分页骨架
  • egg - sequelize 数据转对象
  • egg - sequelize 数据转数组
  • egg - sequelize 分页数据字段转换
  • egg - sequelize 事务

模板库支持在 Egg + Sequelize 组合的技术栈中使用,欢迎下载体验:WY VSCODE SNIPPETS (opens new window)

在 JS 文件中输入 wy-egg 即可选择需要的模板代码:

# 多表查询使用 required 参数

async info() {
  const { model } = this.ctx

  return await model.School.findOne({
    where: { id },
    include: [{
      model: model.College,
      as: 'colleges',
      attributes: [ 'id', 'name' ],
      // https://sequelize.org/master/manual/eager-loading.html#required-eager-loading
      // 查不到数据的时候,返回带结构的数据,例如返回空数组、空对象
      // 当复杂的多表查询中有对嵌套的 include 查询设置 where 条件时,返回的结果是所有查询条件都满足的数据
      required: false
    }]
  })
}

# 分页查询使用 distinct 参数

async info() {
  const { model } = this.ctx

  return await ctx.model.School.findAndCountAll({
    limit: parseInt(values.pageSize),
    where: { id },
    // 分页条数不计算 include 里面的数据
    distinct: true,
    include: [{
      model: ctx.model.College,
      as: 'colleges',
      attributes: [ 'id', 'name' ]
    }]
  })
}

# GitHub

后端脚手架 GitHub 地址 (opens new window)

最后更新时间: 2022-09-15 21:37:51