前言
Node.js有很多优秀的框架,比较流行的有Express、Koa、Egg.js等等。
Express框架简洁而灵活,提供了丰富的API,历史悠久且非常成熟,但是有很多令人讨厌的callback。
Koa号称是基于Node.js平台的下一代web开发框架,基于ES6新特性重新开发,充分利用了async函数,摒弃了回调函数。Egg.js文档里面提到:“Koa 是一个非常优秀的框架,然而对于企业级应用来说,它还比较基础。”
Egg.js则是为企业级框架和应用而生,并奉行『约定优于配置』。继承于Koa并在它模型基础上,进一步对它进行了一些增强。
1、Egg.js的增强
1.1 扩展
可以通过定义 app/extend/{application,context,request,response}.js 来扩展 Koa 中对应的四个对象的原型。
通过扩展helper.js,举一个简单的栗子:
// app/extend/helper.js
const moment = require('moment')
const UUID = require('uuid')
const crypto = require('crypto')
module.exports = {
relativeTime:time => moment(new Date(time * 1000)).fromNow(),
genUUIDV1:() => UUID.v1(),
sha1:content => crypto.createHash('sha1').update(content).digest('hex')
}// app/service/wx_base.js
const Service = require('egg').Service
class WXBaseService extends Service{
validateWXToken(){
const {ctx,config} = this
// ...
let sha1Str = ctx.helper.sha1(str)
}
}
module.exports = WXBaseService扩展方式解析
框架会把 app/extend/{application|context|request|response|helper}.js 中定义的对象与内置 {application|context|request|response|helper} 的 prototype 对象进行合并,在处理请求时会基于扩展后的 prototype 生成 {application|context|request|response|helper} 对象。
1.2 插件
Egg 提供了一个更加强大的插件机制,来管理、编排那些相对独立的业务逻辑。
用法:
1)插件一般通过 npm 模块的方式进行复用
$ npm install egg-sequelize -S2)需要在应用或框架的 config/plugin.js 中声明:
// config.plugin.js
exports.sequelize:{
enable:true,
package:'egg-sequelize'
}3)在 config/config.default.js文件中对插件进行配置
userConfig.sequelize = {
...dbConfig, // 数据库的配置
dialect:'mysql',
timezone: '+08:00',
define: {
// 设置不需要默认的字段(默认创建表有 createAt, updateAt)
timestamps: false,
// 字段以下划线(_)来分割(默认是驼峰命名风格)
underscored: true,
// 禁止修改表名,默认情况下,sequelize将自动将所有传递的模型名称(define的第一个参数)转换为复数
freezeTableName: true
}
// connectionUri:'mysql://root:@127.0.0.1:3306/mydb'
}4)使用插件提供的功能(此处省略了model的定义和数据库表的创建等过程)
let userAuth = await ctx.model.UserAuth.create(userAuth)2、Egg的基础功能
2.1 目录结构
从它的目录约定规范体现『约定优于配置』,先来了解一下它的目录结构:
如上,由框架约定的目录:
app/router.js用于配置 URL 路由规则,框架约定了该文件用于统一所有路由规则。app/controller/**用于解析用户的输入,处理后返回相应的结果,具体参见 Controller。
app/service/**用于编写业务逻辑层,可选,建议使用,具体参见 Service。
app/middleware/**用于编写中间件,可选,具体参见 Middleware。
app/public/**用于放置静态资源,可选,具体参见内置插件 egg-static。app/extend/**用于框架的扩展,可选,具体参见框架扩展。config/config.{env}.js用于编写配置文件,具体参见配置。config/plugin.js用于配置需要加载的插件,具体参见插件。test/**用于单元测试,具体参见单元测试。app.js和agent.js用于自定义启动时的初始化工作,可选,具体参见启动自定义。关于agent.js的作用参见Agent机制。
由内置插件约定的目录:
app/public/**用于放置静态资源,可选,具体参见内置插件 egg-static。app/schedule/**用于定时任务,可选,具体参见定时任务。
若需自定义自己的目录规范,参见 Loader API
app/view/**用于放置模板文件,可选,由模板插件约定,具体参见模板渲染。app/model/**用于放置领域模型,可选,由领域类相关插件约定,如 egg-sequelize。
2.2 框架内置基础对象
从Koa继承而来的4个对象(Application、Context、Request、Response),以及框架扩展的一些对象(Controller、Service、Helper、Config、Logger)
2.3 加载规则
2.3.1 配置加载顺序 优先级(高>低)
- 应用>框架>插件
- 生产环境配置(
config.prod.js)>默认环境配置(config.default.js) //prod环境会加载config.prod.js和config.default.js文件,config.prod.js会覆盖config.default.js的同名配置
注意:插件之间也会有加载顺序,但大致顺序类似,具体逻辑可查看加载器。
2.3.2 文件加载顺序
2.4 中间件
2.4.1 大纲图
2.4.2 中间件的洋葱模型(官网)
- 中间件洋葱图:
- 中间件执行顺序图:
2.4.3 简单理解:
// config/config.default.js
exports.middleware = ['first','second']1) 请求的时候先经过first中间件,再到second中间件;
2) 响应的时候先经过second中间件,再到first中间件。
3、体验
上述两点需要理解的是框架的一些约定和内置对象的概念及其获取方式等,对此有深刻的理解,对于接下来的开发是非常有帮助的(更加详细的内容请参考官网)
3.1 目的
使用egg.js+mysql 新增一条用户的数据。
3.2 思路 & 呆码
1)需要配置路由(app/router.js),访问用户注册的url,使用相应的控制器进行请求处理(还可以按需使用中间件)
// app/router.js
router.get('/user/register',controller.user.register)2)在app/controller目录下定义控制器文件(app/controller/user.js),还可以使用一些校验(ctx.validate(createRule); 需要配置)
const Controller = require('egg').Controller
class UserController extends Controller {
async register(){
try{
const { ctx , app} = this;
const userInfo = {
uid:ctx.helper.genUUIDV1(),
identifier:1,
certificate:ctx.helper.md5((Math.random()*10000).toString()),
createTime:new Date().getTime(),
}
const user = await ctx.service.userAuth.create(userInfo)
ctx.status = 200
ctx.body = 'done'
}catch(e){
console.log(e)
}
}
}
module.exports = UserController3)在app/service目录下定义服务文件(app/service/user_auth.js)用于业务逻辑封装的一个抽象层,供控制器文件中调用
const Service = require('egg').Service
class UserAuthService extends Service {
async create(userAuth){
const {ctx, logger} = this
logger.info('UserAuthService.create 请求入参:',userAuth)
try{
userAuth = await ctx.model.UserAuth.create(userAuth)
if(!userAuth){
throw ('新增用户失败!')
}
logger.info('UserAuthService.create 新增用户成功!')
return userAuth
}catch(e){
logger.error('新增用户报错 堆栈信息:',e)
}
}
}
module.exports = UserAuthService4)引入egg-sequelize插件,在config/plugin.js和config/config.default.js中做相应的引入和配置
// config/plugin.js
exports.sequelize:{
enable:true,
package:'egg-sequelize'
}// config/config.default.js
userConfig.sequelize = {
...dbConfig, // 数据库的配置
dialect:'mysql',
timezone: '+08:00',
define: {
// 设置不需要默认的字段(默认创建表有 createAt, updateAt)
timestamps: false,
// 字段以下划线(_)来分割(默认是驼峰命名风格)
underscored: true,
// 禁止修改表名,默认情况下,sequelize将自动将所有传递的模型名称(define的第一个参数)转换为复数
freezeTableName: true
}
// connectionUri:'mysql://root:@127.0.0.1:3306/mydb'
}5)定义领域模型,与数据库字段做映射
// app/model/user_auth.js
module.exports = app => {
const {STRING, INTEGER, DATE } = app.Sequelize
const UserAuth = app.model.define('user_auth',{
uid:STRING(64),
userName:{type:STRING(32),field:'user_name'},
identityType:{type:INTEGER,field:'identity_type'},
identifier:STRING(50),
certificate: STRING(20),
createTime:{type:DATE,field:'create_time'},
updateTime:{type:DATE,field:'update_time'},
lastSignInTime:{type:DATE,field:'last_sign_in_time'}
})
return UserAuth
}6)安装mysql数据库,创建数据库表
CREATE TABLE `user_auth` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`uid` varchar(64) NOT NULL DEFAULT '0' COMMENT 'id',
`identity_type` tinyint(4) NOT NULL DEFAULT '1' COMMENT '1手机号 2邮箱 认证类型',
`identifier` varchar(50) NOT NULL DEFAULT '' COMMENT '手机号 邮箱',
`certificate` varchar(64) NOT NULL DEFAULT '' COMMENT '密码',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`last_sign_in_time` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT '上次登陆时间',
PRIMARY KEY (`id`),
UNIQUE KEY `only` (`uid`,`identity_type`),
KEY `idx_uid` (`uid`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '用户授权表';7)访问url,查看结果
4、总结
通过一个小小的demo,简单的体验了框架的一个开发流程,使用了框架的一些基本功能和插件。总的来说,egg框架比较容易上手,官网上有详细且清晰的文档,也有很多很实用的插件和扩展值得去探讨和研究。
个人认为理解框架所表达的思想尤为重要,不断学习其中的开发思路和技巧。小小demo还是挺皮毛的,框架还有很多高阶的玩法,还得继续探索探索~