写这个项目的原因:已经基于PHP写过一个Vue的后台了,点这里可以查看(基于thinkPHP5.1写的Vue后台),然后换个语言写一个类似的后台,点这里查看(基于node.js的egg.js框架开发)。目的纯粹是个人兴趣,想在各语言寻找各语言之间有何优势,以便以后工作中需要时有多一种选择。
用egg.js一个重要原因是因为我看中了它的内置多进程和毫秒级定时任务。
内置多进程就不需要我再用pm2了(egg-cluster多进程模块默认是根据服务器有多少核 CPU 启动多少个这样的 worker 进程 ),内置毫秒级定时任务可以快速实现很多骚操作了
假设你已经egg.js结构已经有所了解,不熟悉的东西去官网文档看看
下面是一些我个人对egg.js框架需要注意爬坑地方的理解,以帮助正在爬坑的你
可以直接基于我已经创建好的基础demo开发,里面包含MongoDB/MySQL/Redis等插件
这个坑,我猜上手的人第一步都会遇到
需要禁用安全防范
在config/config.default.js里加入下面这句话
方式一:
config.security = {
csrf: { // 安全防范
enable: false
},
};
方式二:
写到用户配置里去
const userConfig = {
// myAppName: 'egg',
security: {
csrf: {
enable: false,
},
},
};
如果想防止前端请求接口跨域在这个文件里加下面的代码(也可以写在userConfig里):
cors: {
origin: '*',
allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH',
},
控制器和service,路由,模型都没啥好说的,和其他类似的MVC框架一样。
这个框架的中间件实现方式和express稍微有些不一样,官方是这么说的:
egg.js基于Koa这个框架
和 Express 只有 Request 和 Response 两个对象不同,Koa 增加了一个 Context
的对象,作为这次请求的上下文对象(在 Koa 1 中为中间件的 this,在 Koa 2
中作为中间件的第一个参数传入)。我们可以将一次请求相关的上下文都挂载到这个对象上。类似 traceId
这种需要贯穿整个请求(在后续任何一个地方进行其他调用都需要用到)的属性就可以挂载上去。相较于 request 和 response
而言更加符合语义。同时 Context 上也挂载了 Request 和 Response 两个对象。和 Express
类似,这两个对象都提供了大量的便捷方法辅助开发,例如get request.query
get request.hostname
set response.body
set response.status
express我也在用,就我个人使用来感受来说,其实区别就是这一点:
我们可以将一次请求相关的上下文都挂载到这个对象上。类似 traceId
这种需要贯穿整个请求(在后续任何一个地方进行其他调用都需要用到)的属性就可以挂载上去
废话不多说了,直接写个demo
在config/config.default.js加上:
config.middleware = [
'test', // 中间件test
];
config.test = {
enable: true, // test中间件开启
// enable: false, // test中间件如果true为全局开启,如果false时,可以在router里单独为路由配置中间件
};
新建app/middleware/test.js 文件,middleware目录下的所有js文件要与你定义的中间件名称一样,否则报错。
test.js文件代码:
module.exports = () => { // 中间件test
return async function (ctx, next) {
await next();
// 打印当前时间
let timeToDate = new Date().toLocaleString();
console.log(timeToDate);
}
};
在路由文件router.js里可以单独为指定路由添加中间件
'use strict';
/**
* @param {Egg.Application} app - egg application
*/
module.exports = app => {
const {router, controller, middleware} = app;
//--------------------------------------------------
// 中间件
//--------------------------------------------------
const test = middleware.test();
router.get('/:id', controller.home.index);
router.post('/test', test, controller.test.test); // 单独为路由引入中间件
router.post('/api/posts', controller.post.create);
};
以MySQL为例:
建议使用egg-sequelize插件,因为它集成了ORM操作,它支持 MySQL、PostgreSQL、SQLite 和 MSSQL 等多个数据源
首先确保你本地/服务器已经有数据库环境了
安装
npm install --save egg-sequelize mysql2
在 config/plugin.js 中引入 egg-sequelize 插件
module.exports = {
// 使用egg-sequelize mysql2
sequelize: {
enable: true,
package: 'egg-sequelize',
}
}
在 config/config.default.js 中编写 sequelize 配置
方式一:
module.exports = appInfo => {
const config = exports = {};
// MySQL配置
config.sequelize = {
dialect: 'mysql',
host: '127.0.0.1',
port: 3306,
username: 'root',
password: 'xxx',
database: 'xxx',
};
return {
...config
};
}
方式二:
const userConfig = {
// myAppName: 'egg',
// 所有的插件配置都可以写在这里
sequelize: {
sync: true, // whether sync when app init
dialect: 'mysql',
host: '127.0.0.1',
port: '3306',
database: 'egg_sequelize_dev',
username: 'xxx',
password: 'xxx',
}
}
这个框架如果不使用构造函数,你会发现会经常使用到定义如下代码
const { ctx } = this;
如果我们在类文件里加上构造函数,就不需要再写重复代码了
// 构造函数
constructor(ctx) {
super(ctx); // 如果需要在构造函数做一些处理,一定要有这句话,才能保证后面 `this.ctx`的使用。
// 就可以直接通过 this.ctx 获取 ctx 了
// 还可以直接通过 this.app 获取 app 了
}
这个是我在service/user.js里写的
const Service = require('egg').Service;
class UserService extends Service {
// 构造函数
constructor(ctx) {
super(ctx); // 如果需要在构造函数做一些处理,一定要有这句话,才能保证后面 `this.ctx`的使用。
// 就可以直接通过 this.ctx 获取 ctx 了
// 还可以直接通过 this.app 获取 app 了
}
/**
* 通过id获取用户信息
* @param id
* @return {Promise<{}>}
*/
async getUserById(id) {
// const { ctx } = this;
let userInfo = {};
try {
userInfo = await this.ctx.model.User.findAll({
where: { id },
// 查询操作的时候,加入这个参数可以直接拿到对象类型的查询结果,否则还需要通过方法调用解析
raw: true,
attributes:["id","name","age"], //返回的指定字段
});
} catch (err) {
this.ctx.logger.error(err);
}
return userInfo;
}
// 根据id更新数据
async update(id) {
console.log(id)
let result = {};
try {
result = await this.ctx.model.User.update({
age: 20
}, {
where: {
id: id
}
});
} catch (err) {
this.ctx.logger.error(err);
}
return result;
}
}
module.exports = UserService;
会发现下面不再需要定义ctx,app等这些了,直接用this.app,this.ctx即可
框架自身就支持毫秒级定时任务也是我非常看重的一点
定时任务写在 schedule/task.js里
module.exports = {
schedule: {
interval: '1s', // 1s间隔,默认单位是ms,比如只写个1,就变成1毫秒执行一次
type: 'all', // 指定所有的 worker 都需要执行
},
async task(ctx) {
console.log(new Date()) // 打印当前时间
},
};
可以加一些类似助手函数之类的方法
框架本身内置支持:
Application
Context
Request
Response
Helper
扩展文件都放在app/extend/xxx.js
举例:app/extend/helper.js
module.exports = {
// 字符串转整形
parseInt(string) {
if (typeof string === 'number') return string;
if (!string) return string;
return parseInt(string) || 0;
}
}
使用方式:
async destroy() {
const {ctx} = this;
const {params, service, helper} = ctx;
const id = helper.parseInt(params.id); // 使用方式
await service.wbUrl.destroy(id);
ctx.status = 200;
ctx.body = {
code: 0,
success: true,
};
}
之前爬坑的时候没有记录,有的地方我已经爬完了却想不起来哪些需要记录。后面我会把我使用egg.js遇到的问题都记录下来,如果有幸我们在这里相遇,也请把你的问题抛出来,我们一起研究解决