快速初始化
用npm
| 1
 | npm init egg --type=simple
 | 
目录结构
| 12
 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
 
 | egg-project├── package.json
 ├── app.js (可选) 			# 用于自定义启动时的初始化工作
 ├── agent.js (可选) 			# 用于自定义启动时的初始化工作
 ├── app
 |   ├── router.js 			 # 用于配置 URL 路由规则
 │   ├── controller 			 # 用于解析用户的输入,处理后返回相应的结果
 │   |   └── home.js
 │   ├── service (可选) 		# 用于编写业务逻辑层
 │   |   └── user.js
 │   ├── middleware (可选) 	# 用于编写中间件
 │   |   └── response_time.js
 │   ├── schedule (可选)		# 用于定时任务
 │   |   └── my_task.js
 │   ├── public (可选) 		# 用于放置静态资源
 │   |   └── reset.css
 │   ├── view (可选)			# 用于放置模板文件
 │   |   └── home.tpl
 │   └── extend (可选) 		# 用于框架的扩展
 │       ├── helper.js (可选)
 │       ├── request.js (可选)
 │       ├── response.js (可选)
 │       ├── context.js (可选)
 │       ├── application.js (可选)
 │       └── agent.js (可选)
 ├── config 					# 用于编写配置文件
 |   ├── plugin.js 			 # 用于配置需要加载的插件
 |   ├── config.default.js
 │   ├── config.prod.js
 |   ├── config.test.js (可选)
 |   ├── config.local.js (可选)
 |   └── config.unittest.js (可选)
 └── test 					# 用于单元测试
 ├── middleware
 |   └── response_time.test.js
 └── controller
 └── home.test.js
 
 | 
路由(Router)
将用户的请求基于 method 和 URL 分发到了对应的 Controller 上,感觉api和Koa差不多。
参数获取
query
就列一个query参数的获取,需要注意的是,app.controller.search与文件相对应(也就是app/controller/search.js)
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 
 | 
 
 module.exports = app => {
 app.router.get('/search', app.controller.search.index);
 };
 
 
 exports.index = async ctx => {
 ctx.body = `search: ${ctx.query.name}`;
 };
 
 | 
为了避免用户恶意传递重复key的query参数导致系统报错,ctx.query只取第一次出现的值,并且保证一定是字符串类型
如果必须要重复的key,比如需要传递数组:id=1&id=2&id=3,可以用ctx.queries,其也保证了一定是数组类型
body
框架内置了 bodyParser 中间件来对JSON和Form-Data的请求 body 解析成 object 挂载到 ctx.request.body 上。
body 最大长度为 100kb,超出长度413状态码,可以在 config/config.default.js 中覆盖框架的默认值
| 12
 3
 4
 5
 6
 
 | module.exports = {bodyParser: {
 jsonLimit: '1mb',
 formLimit: '1mb',
 },
 };
 
 | 
ctx.body是ctx.response.body的简写
可以通过ctx.get(name)获取,其会自动处理大小写
Cookie
获取、设置方式和Koa一样,配置方式如下
| 12
 3
 4
 5
 6
 
 | module.exports = {cookies: {
 
 
 },
 };
 
 | 
Session
框架内置 Session 插件,可以通过ctx.session,具体配置可以看 Cookie 与 Session
参数校验
借助 Validate 插件提供便捷的参数校验机制,配置方式如下
| 12
 3
 4
 5
 
 | exports.validate = {
 enable: true,
 package: 'egg-validate',
 };
 
 | 
ctx上有一个validate方法,可以传入校验配置来对参数进行校验
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 
 | 
 
 module.exports = app => {
 app.router.post('/user', app.controller.user);
 };
 
 
 const createRule = {
 username: {
 type: 'email',
 },
 password: {
 type: 'password',
 compare: 're-password',
 },
 };
 
 exports.create = async ctx => {
 
 ctx.validate(createRule);
 ctx.body = ctx.request.body;
 };
 
 | 
重定向
内部重定向
| 1
 | app.router.redirect('/', '/home/index', 302);
 | 
外部重定向
| 1
 | ctx.redirect(`http://cn.bing.com`);
 | 
resources生成CRUD路由结构
| 12
 3
 4
 5
 
 | module.exports = app => {
 const { router, controller } = app;
 router.resources('posts', '/api/posts', controller.posts);
 };
 
 | 
接下来在对应文件下实现对应函数就可以了
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 
 | exports.index = async () => {};
 
 exports.new = async () => {};
 
 exports.create = async () => {};
 
 exports.show = async () => {};
 
 exports.edit = async () => {};
 
 exports.update = async () => {};
 
 exports.destroy = async () => {};
 
 | 
| Method | Path | Route Name | Controller.Action | 
| GET | /posts | posts | app.controllers.posts.index | 
| GET | /posts/new | new_post | app.controllers.posts.new | 
| GET | /posts/:id | post | app.controllers.posts.show | 
| GET | /posts/:id/edit | edit_post | app.controllers.posts.edit | 
| POST | /posts | posts | app.controllers.posts.create | 
| PUT | /posts/:id | post | app.controllers.posts.update | 
| DELETE | /posts/:id | post | app.controllers.posts.destroy | 
模块化
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 
 | module.exports = app => {
 require('./router/news')(app);
 require('./router/admin')(app);
 };
 
 
 module.exports = app => {
 app.router.get('/news/list', app.controller.news.list);
 };
 
 
 module.exports = app => {
 app.router.get('/admin/user', app.controller.admin.user);
 };
 
 | 
控制器(Controller)
解析用户的输入,处理后返回相应的结果,框架建议在 controller 对请求参数进行处理(校验、转换),然后调用对应的 service 方法处理业务
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 
 | const Controller = require('egg').Controller;
 
 class PostController extends Controller {
 async create() {
 const { ctx, service } = this;
 const createRule = {
 title: { type: 'string' },
 content: { type: 'string' },
 };
 
 
 ctx.validate(createRule);
 
 const author = ctx.session.userId;
 const req = Object.assign(ctx.request.body, { author });
 
 const res = await service.post.create(req);
 
 ctx.body = { id: res.id };
 ctx.status = 201;
 }
 }
 
 module.exports = PostController;
 
 | 
获取上传的文件
file模式
在config中配置file模式
| 12
 3
 4
 
 | exports.multipart = {
 mode: 'file',
 };
 
 | 
controller层代码
| 12
 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
 
 | const Controller = require('egg').Controller;
 const fs = require('mz/fs');
 
 module.exports = class extends Controller {
 async upload() {
 const { ctx } = this;
 console.log(ctx.request.body);
 console.log('got %d files', ctx.request.files.length);
 for (const file of ctx.request.files) {
 console.log('field: ' + file.fieldname);
 console.log('filename: ' + file.filename);
 console.log('encoding: ' + file.encoding);
 console.log('mime: ' + file.mime);
 console.log('tmp filepath: ' + file.filepath);
 let result;
 try {
 
 result = await ctx.oss.put('egg-multipart-test/' + file.filename, file.filepath);
 } finally {
 
 await fs.unlink(file.filepath);
 }
 console.log(result);
 }
 }
 };
 
 | 
stream模式
| 12
 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
 
 | const path = require('path');const sendToWormhole = require('stream-wormhole');
 const Controller = require('egg').Controller;
 
 class UploaderController extends Controller {
 async upload() {
 const ctx = this.ctx;
 const stream = await ctx.getFileStream();
 const name = 'egg-multipart-test/' + path.basename(stream.filename);
 
 let result;
 try {
 result = await ctx.oss.put(name, stream);
 } catch (err) {
 
 await sendToWormhole(stream);
 throw err;
 }
 
 ctx.body = {
 url: result.url,
 
 fields: stream.fields,
 };
 }
 }
 
 module.exports = UploaderController;
 
 | 
Service层
其不是单例,在每次请求时访问ctx.service.xx 时延迟实例化,所以可以用this.ctx获取每一次请求的上下文
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 
 | const Service = require('egg').Service;
 
 class UserService extends Service {
 async find(uid) {
 const user = await this.ctx.db.query('select * from user where uid = ?', uid);
 return user;
 }
 }
 
 module.exports = UserService;
 
 | 
函数return的值会返回给前端
定时任务
所有的定时任务都统一存放在 app/schedule 目录下
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 
 | const Subscription = require('egg').Subscription;
 class UpdateCache extends Subscription {
 
 static get schedule() {
 return {
 interval: '1m',
 type: 'all',
 };
 }
 
 
 async subscribe() {
 const res = await this.ctx.curl('http://www.api.com/cache', {
 dataType: 'json',
 });
 this.ctx.app.cache = res.data;
 }
 }
 
 module.exports = UpdateCache;
 
 | 
还可以简写为
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 
 | module.exports = {schedule: {
 interval: '1m',
 type: 'all',
 },
 async task(ctx) {
 const res = await ctx.curl('http://www.api.com/cache', {
 dataType: 'json',
 });
 ctx.app.cache = res.data;
 },
 };
 
 | 
没有用过,具体配置看官方文档 定时任务
egg-mysql
安装与配置
开启插件
| 12
 3
 4
 5
 
 | exports.mysql = {
 enable: true,
 package: 'egg-mysql',
 };
 
 | 
数据库配置
| 12
 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
 
 | exports.mysql = {
 
 clients: {
 
 db1: {
 host: 'mysql.com',
 port: '3306',
 user: 'test_user',
 password: 'test_password',
 database: 'test',
 },
 db2: {
 host: 'mysql2.com',
 port: '3307',
 user: 'test_user',
 password: 'test_password',
 database: 'test',
 },
 }
 
 default: {},
 
 app: true,
 
 agent: false,
 };
 
 | 
使用方式:
| 12
 3
 4
 5
 
 | const client1 = app.mysql.get('db1');await client1.query(sql, values);
 
 const client2 = app.mysql.get('db2');
 await client2.query(sql, values);
 
 |