背景 之前基于 Egg.js 开发了几个项目,发现每个项目中都有配置文件、数据库连接操作、数据模型定义、微信登录授权处理等功能,而做新项目时总会复制之前的项目来删删改改,有时候在 A 项目中添加了一个实用的功能,B 项目中添加一个很酷的工具函数,但是过后就忘了,没有一个可以统一沉淀的地方,故做了一个脚手架,基于 Egg.js 封装了数据库连接、自动生成接口文档、字段校验等功能,一来新项目开发可开箱即用,二来也便于技术沉淀。
定位 基于 Egg.js 的后端脚手架。
基于 Egg.js 封装了一层,将常用的功能聚合在一起,更靠近业务,如果 Egg.js 是面,那这个后端脚手架就是面条。
功能介绍
模块
功能介绍
进度
数据库操作
内置 ORM egg-sequelize 插件作为数据库操作库,可适配 Postgres、MySQL、MariaDB、SQLite 数据库
√
接口路由配置
内置 egg-router-plus 插件解决了路由命名空间问题
√
自动生成接口文档
内置 egg-swagger-doc 插件,按照约定书写接口注释即可自动生成接口使用文档
√
字段校验
内置 egg-validate-plus 插件作为字段校验,即可以单独使用,也可以配合 egg-swagger-doc 插件使用,既自动生成接口文档又实现了字段校验
√
多环境配置
内置了本地开发环境、测试环境和生产环境配置,切换和扩展都是非常便捷的
√
跨域配置
多环境下设置了不同的跨域配置,应对各个环境下对跨域策略的不同诉求
√
异常处理
统一封装处理抛出的错误,不将错误直接抛出给前端,提高安全性和比较好的前端使用体验
√
工具库
封装了菜单树处理、版本号对比、uid 生成等常用的工具函数,开发更便捷
√
单元测试
完整的单元测试,代码更健壮
√
微信小程序登录
内置了微信小程序授权登录和获取手机号的全流程,可与微信小程序无缝对接
√
eslint
内置了比较严格和完整的 eslint 规则,保证了代码规范,协同开发更高效
√
项目结构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 . ├── LICENSE ├── README.md ├── app │ ├── contract │ ├── controller │ ├── extend │ ├── middleware │ ├── model │ ├── public │ ├── router.js │ └── service ├── app.js ├── appveyor.yml ├── config │ ├── config.default.js │ ├── config.local.js │ ├── config.prod.js │ ├── config.test.js │ ├── config.unittest.js │ └── plugin.js ├── jsconfig.json ├── note │ ├── database.sql │ ├── note.md │ └── tables.sql ├── package.json ├── test │ └── app └── yarn.lock
项目配置 若没有数据库,可执行下面的 sql 新建一个数据库:
1 CREATE DATABASE IF NOT EXISTS template_node_egg DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci;
然后导入 user
数据表:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 SET NAMES utf8mb4;SET FOREIGN_KEY_CHECKS = 0 ;DROP TABLE IF EXISTS `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 ) 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= 2 DEFAULT CHARSET= utf8mb4; BEGIN ;INSERT INTO `user ` VALUES (1 , '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 dev
启动开发,若是第一次启动,可能需要在 config/config.local.js
修改一下密码配置:
1 2 3 4 5 6 7 8 9 10 11 12 config.sequelize = { username : 'root' , password : '' , database : 'template_node_egg' , host : '127.0.0.1' , dialect : 'mysql' }
如果已有的数据库里面 user 表字段跟 model/user.js
中定义的对不上,需要自行修改,否则会报错
应用配置 自定义的应用配置,修改后全局生效。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 const apiPrefixName = 'api' const apiPrefix = `/${apiPrefixName} ` const manageApiPrefixName = 'manage' const manageApiPrefix = `/${manageApiPrefixName} ` const userConfig = { appName : app.name , apiPrefixName, apiPrefix, manageApiPrefixName, manageApiPrefix, resCode : { success : { code : 0 }, error : { code : 602 , message : '参数异常' }, serverError : { code : 500 , message : '服务器异常' }, notLogged : { code : 601 , message : '请先登录后再操作' } } }
Cookie 配置 脚手架内置了应用(例如小程序)接口服务和中后台接口服务,其中的字段配置基本不需要改。中后台接口服务是可选的,若是不想要放在一起,可直接忽略。
应用接口服务使用 ctx.session.[keyName]
来设置和获取值,例如要将 id 字段设置为 cookie,那么设置 cookie 的写法就是:
获取 cookie 就是:
1 ctx.service .user .info (ctx.session .id )
而中后台接口服务获取需要使用 ctx.cookies.get(key, cookie)
方法,具体用法参见这里ctx.cookies 使用文档 。
安全策略配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 const port = 9001 const domainWhiteList = [ ...new Set ([ `http://127.0.0.1:${port} ` , `http://localhost:${port} ` , `http://${getLocalhost()} :${port} ` ]) ] config.security = { domainWhiteList } config.cors = { origin : '*' , allowMethods : 'GET,HEAD,PUT,POST,DELETE,PATCH' }
权限配置 带登录体系的接口服务,基本都需要指定一部分不需要登录就能访问的接口,脚手架已内置了跟登录相关的白名单接口:
1 2 3 4 5 6 [ `${apiPrefix} /user/mock` , `${apiPrefix} /user/login` , `${apiPrefix} /user/logout` , `${apiPrefix} /user/phone` ]
自动生成文档配置 内置 egg-swagger-doc 插件,按照约定书写接口注释即可自动生成接口使用文档。所有的数据模型结构都在 app/contract
中定义。
具体使用文档参见这里egg-swagger-doc 使用文档 ,这里就不赘述了。
若不需要此功能,也可在 config/plugin.js
中禁用自动生成文档功能:
1 2 3 4 5 6 { swaggerdoc : { enable : false , package : 'egg-swagger-doc' } }
开发 一般开发一个全新接口,需要完成以下四步:
新增模型 在 app/model
中新增一个 log.js
文件并添加数据模型:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 'use strict' module .exports = app => { 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
文件并添加一个获取列表的数据库操作方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 'use strict' const Service = require ('egg' ).Service class LogService extends Service { async logs (actionType ) { const { ctx } = this try { const data = await ctx.model .Log .findAll ({ where : { actionType } }) return ctx.helper .clone (data) } catch (error) { ctx.logger .error (error) } return false } } module .exports = LogService
新增控制器 在 app/contract/dto.js
中新增一个 logInfo
模型:
1 2 3 4 5 6 7 8 9 { logInfo : { content : { type : 'string' , description : '操作内容' }, remarks : { type : 'string' , description : '备注' }, actionType : { type : 'string' , description : '操作类型' } } }
然后在 app/controller
中新增一个 log.js
文件并添加一个获取列表的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 'use strict' const Controller = require ('egg' ).Controller class LogController extends Controller { async logs ( ) { const { ctx } = this 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 ctx.service .log .logs (actionType) if (data) { this .ctx .helper .success (data) } else { this .ctx .helper .error (null , '日志列表失败' ) } } } module .exports = LogController
新增接口 在 app/router.js
中新增一个路由接口:
1 2 subRouter.get ('/log/logs' , controller.log .logs )
以上例子当然是比较简单的,但大体步骤就是这样。
当然一般新增一个接口时,是不需要再新增一个控制器文件的,基本都是往某个控制器里面添加一个方法,然后在服务里面添加一个数据库操作方法即可。
发布 发布之前,需要在 config/config.prod.js
中配置好数据库连接配置:
1 2 3 4 5 6 7 config.sequelize = { username : 'root' , password : '' , database : 'template_node_egg' , host : '127.0.0.1' , dialect : 'mysql' }
启动:
停止:
使用 wy-cli 创建脚手架 全局安装:
然后执行 wy init project-name
命令安装脚手架,在列表中选择后端脚手架:
1 ❯ 后端脚手架(Node.js + Egg.js + Sequelize)
脚手架常用命令:
1 2 3 4 5 6 7 8 9 10 11 yarn install yarn dev yarn start yarn stop
代码和文档 感谢阅读,脚手架以后会长期维护,欢迎下载使用:后端脚手架 GitHub 地址
脚手架使用文档 也会保持同步更新。
公众号