| this.ctx - 当前请求的上下文 Content对象的实例ctx,Request、Response
| this.app - 当前应用对象Application 对象的实例
| this.service - 定义的Service,等价于 this.ctx.service
| this.config - 应用配置
| this.logger - 日志对象 方法debug, info, warn, error
| app.cache - 缓存
Egg介绍、安装
Egg.js属于MVC类型的NodeJS框架
一、下载脚手架:
$ npm i egg-init -g
$ egg-init egg-example --type=simple
$ cd egg-example
$ npm i
二、启动项目:
$ npm run dev
$ open localhost:7001
配置
config
|- config.default.js // 默认配置文件, 所有环境都会加载这个配置文件, 一般用于开发环境的默认配置文件
|- config.test.js // 开发环境配置
|- config.prod.js // 生产环境配置
|- config.unittest.js
|- plugin.js // 控制插件
`- config.local.js
一、引入插件
module.exports = {
mysql: {
enable: true, // 开启或关闭插件
package: 'egg-mysql' // package为一个npm模块,package.json中的dependencies中,框架会在node_modules目录中找到这个插件入口
},
};
也可以指定path 来替代 package
const path = require('path');
module.exports = {
mysql: {
enable: true,
path: path.join(__dirname, '../app/plugin/egg-mysql'),
},
};
Router 路由
用来接收请求URL,在交给controller处理
app.get(路由名,路由URL路由,middleware(可以配置多个),controller)
一、controller的两个写法
# router
module.exports = app => {
// 1、controller目录的home.js进行处理
app.get('/', 'home');
// 2、payTool目录、billquery.js文件、getLoanUser方法
app.post('/getLoanUser', 'payTool.billquery.getLoanUser');
};
# controller/home.js
module.exports = function* () {
this.body = 'Hello World'; // 响应给客户端
};
# controller/payTool/billquery.js
module.exports = app => {
class BillqueryController extends app.Controller {
async getLoanUser(){
const { ctx, app, service } = this;
const response = await service.payTool.billquery.getRepayPlan(ctx.request.body);
ctx.body = response;
}
}
}
二、路由中间件 - 在访问路由之前通过中间件来对路由进行一些处理
app.get('router-name', 'middleware1', 'middleware2', 'controlll');
1、创建中间件 checkAuth.js middleware
module.exports = app => {
return async function checkAuth(ctx, next){
const user = ctx.session.user;
console.log(`UserSession ------ ${JSON.stringify(user)}`)
if(user){
console.log(`visited true`);
await next();
}
else{
console.log(`visited false`);
ctx.body = {
data:{},
status: 10001,
message: 'Session失效'
}
}
}
}
2、路由中调用
注意如果路由中使用中间件分为全局和和单个路由第一次两种,如果配置到config.default.js中的就为全局
// 引用中间件
const checkAuth = app.middleware.checkAuth();
// 不需要验证
app.get('/', 'home.index');
app.get('/query', 'query.query.queryPage');
// 需要验证是否登录的
app.post('/web/getUserList', checkAuth, 'user.user.getUserList'); // 获取用户列表
三、参数获取
1、获取url参数 ctx.query
http://127.0.0.1:7001/search?name=egg 取出name的值
// app/router.js
module.exports = app => {
app.get('/search', app.controller.search);
};
// app/controller/search.js
module.exports = function* (ctx) {
tx.body = `search: ${this.query.name}`;
};
2、路由参数 ctx.params
http://127.0.0.1:7001/user/123/xiaoming 取出 123、xiaoming
// app/router.js
module.exports = app => {
app.get('/user/:id/:name', app.controller.user.info);
};
// app/controller/user.js
exports.info = function* (ctx) {
ctx.body = `user: ${ctx.params.id}, ${ctx.params.name}`;
};
四、重定向 - 当路由不存在时会走一个默认的路由
1、内部重定向
// app/router.js
module.exports = app => {
app.get('index', '/home/index', 'home.index');
app.redirect('/', '/home/index', 302);
};
2、外部重定义 controller中来执行路由的跳转 redirect
// app/router.js
module.exports = app => {
app.get('/search', 'search');
};
// app/controller/search.js
module.exports = function* () {
const type = this.query.type;
const q = this.query.q || 'nodejs';
// 这里跳转
ctx.redirect('/admin/home');
};
五、多路由映射 - 将路由分类并用一个路由主文件加载其它的路由文件
// router.js 通过一个主文件将两个路由加载进来
module.exports = app => {
require('./router/news')(app);
require('./router/admin')(app);
};
// router/news.js
module.exports = app => {
app.get('/news/list', app.controller.news.list);
app.get('/news/detail', app.controller.news.detail);
};
// router/admin.js
module.exports = app => {
app.get('/admin/user', app.controller.admin.user);
app.get('/admin/log', app.controller.admin.log);
};
六、路由规则
// app/router.js
module.exports = app => {
app.get(/^\/package\/([\w-.]+\/[\w-.]+)$/, app.controller.package.detail);
};
Controller
controller继承于app.controller, 负责解析用户输入,处理后返回相应的结果, 框架推荐Controller层主要对用户请求参数进行处理(校验、转换),然后用service处理业务结果并返回
用户HTTP请求(路由处理) <=> 校验组装(controller处理)<=> 业务处理(service)
一、Controller的两种写法
1、函数写法
// app/controller/posts.js
exports.index = function* () {};
exports.new = function* () {};
exports.create = function* () {};
// router 调用
app.get('/index', 'posts/index');
app.get('/new', 'posts/new');
app.get('/create', 'posts/create');
2、类写法
// app/controller/post.js
module.exports = app => {
class PostController extends app.Controller { // 定义一个PostController的类,继承了app.Controller
* 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 });
// 调用 Service 进行业务处理
const res = yield service.post.create(req);
// 设置响应内容和响应状态码
ctx.body = { id: res.id };
ctx.status = 201;
}
}
return PostController;
}
// router 调用 app/router.js
module.exports = app => {
app.post('createPost', '/api/posts', 'post.create');
}
二、app.Controller下this上挂载的几个属性
1、this.ctx: 当前请求的上下文Context对象的实例
2、this.app: Application对象的实例,可以取到框架的全局对象和方法
3、this.service: 取到service下的方法
4、this.config: 获取或添加修改config的配置
5、this.logger: 日志打印
三、获取请求
ctx.method: 请求方法
ctx.path: 请求路径
ctx.host: 请求IP
1、query 获取GET请求中传递参数
url: /posts?category=egg&language=node 通过context.query拿到url的参数
class QueryController extends app.Controller{
async getQuery(app){
const { ctx, app, service } = this;
const queryName = ctx.query; // {category: 'egg', language: 'node'}
ctx.body = queryName;
}
}
2、queries 能取到重复的参数key
// posts?category=egg&id=1&id=2&id=3 参数解析成对象,id有多个
class QueryController extends app.Controller{
async getQuery(app){
const { ctx, app, service } = this;
const queryName = ctx.queries; // {category: 'egg', id: [1,2,3]}
ctx.body = queryName;
}
}
3、params 参数
路由: app.get('/projects/:projectId/app/:appId', 'app.listApp');
// 请求: GET /projects/1/app/2
class QueryController extends app.Controller{
async getParams(app){
const { ctx, app, service } = this;
const params = {
id: ctx.params.projectId,
appid: ctx.params.appId
};
ctx.body = queryName;
}
}
4、body 获取post传的数据
框架内置bodyParser中间件,挂载到 context.request.body上,来取post的内容
ctx.request.body 来获取post传过来的参数 {"username":"","password":"1111"}
exports.listPosts = function* (ctx) {
assert.equal(ctx.request.body.title, 'controller');
assert.equal(ctx.request.body.content, 'what is controller');
};
<input type="text" name="title" value="" />
<input type="text" name="content" value="" />
config.default.js设置, 默认body最大长度为100kb
bodyParser: {
jsonLimit: '1mb',
formLimit: '1mb',
}
5、获取上传文件
浏览器上都是通过 Multipart/form-data 格式发送文件的,框架通过内置 Multipart 插件来支持获取用户上传的文件
<form method="POST" action="/upload?_csrf={{ ctx.csrf | safe }}" enctype="multipart/form-data">
title: <input name="title" />
file: <input name="file" type="file" />
<button type="submit">上传</button>
</form>
const path = require('path');
const sendToWormhole = require('stream-wormhole');
module.exports = function* (ctx) {
const stream = yield ctx.getFileStream();
const name = 'egg-multipart-test/' + path.basename(stream.filename);
// 文件处理,上传到云存储等等
let result;
try {
result = yield ctx.oss.put(name, stream);
} catch (err) {
// 必须将上传的文件流消费掉,要不然浏览器响应会卡死
yield sendToWormhole(stream);
throw err;
}
ctx.body = {
url: result.url,
// 所有表单字段都能通过 `stream.fields` 获取到
fields: stream.fields,
};
};
在config/config.default.js 中配置来新增支持的文件扩展名,或者重写整个白名单
新增支持的文件扩展名
module.exports = {
multipart: {
fileExtensions: [ '.apk' ], // 增加对 .apk 扩展名的支持
},
};
覆盖整个白名单
module.exports = {
multipart: {
whitelist: [ '.png' ], // 覆盖整个白名单,只允许上传 '.png' 格式
},
};
6、发送HTTP响应
1)设置status
exports.create = function* (ctx) {
// 设置状态码为 201
ctx.status = 201;
};
2)设置body 响应给请求方
exports.show = function* (ctx) {
ctx.body = {
name: 'egg',
category: 'framework',
language: 'Node.js',
};
};
exports.page = function* (ctx) {
ctx.body = '<html><h1>Hello</h1></html>';
};
获取整个 header 对象的方法: context.headers、context.header、context.request.headers、context.request.header
get()方法获取请求 header 中的一个字段的值、字段不存在返回null: context.get(name)、context.request.get(name)
一、获取header
1、ctx.header 取出头信息
async from(ctx){
console.log(ctx.header);
}
返回结果
{
host: '127.0.0.1:7001',
connection: 'keep-alive',
'content-length': '21',
'postman-token': 'a7e094b5-21ce-eb94-e403-3d517c832f30',
'cache-control': 'no-cache',
origin: 'chrome-extension://aicmkgpgakddgnaphhhpliifpcfhicfo',
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36',
'content-type': 'application/json',
accept: '*/*',
'accept-encoding': 'gzip, deflate, br',
'accept-language': 'zh-CN,zh;q=0.8,en;q=0.6',
cookie: 'csrfToken=RNHUjeOf0Zsl9jNl2f5BhreT'
}
2、ctx.protocol 协议
3、ctx.ips 返回ip 一个数组
4、ctx.ip 返回请求方的ip
5)、ctx.host
二、设置header
context.set(key, value) 方法可以设置一个响应头,context.set(headers) 设置多个 Header。
exports.show = function* (ctx) {
const start = Date.now();
ctx.body = yield ctx.service.post.get();
const used = Date.now() - start;
// 设置一个响应头
ctx.set('show-response-time', userd.toString());
};
Cookie
可以通过context.cookies来获取和设置cookie值
一、cookie操作
获取 ctx.cookies.get('count');
设置 ctx.cookies.set('count', ++count);
删除 ctx.cookies.set('count', null);
二、设置options
context.cookies.set(key, value, options)
1、maxAge: nunber - 在浏览器保存最长时间
2、expires: date - 设置这个键值的失效时间
3、path: string - 设置生效的URL路径
4、domain: string - 设置生效的域名
5、httpOnly: boolean - 设置是否可以被js访问
6、secure: boolean - 设置键值只在HTTPS连接上传输
7、overwrite: boolean - 设置key相当键值如何处理,true为后设置的覆盖前面设置的,false发送两个
8、sign: boolean - 对cookie是否进行签名,true防止前端对这个值进行篡改
9、encrypt: boolean - 对cookie进行加密
ctx.cookies.set(key, value, {
httpOnly: false,
sign: false,
});
三、Cookie 密钥
config/config.default.js
module.exports = {
keys: 'key1,key2',
};
Session
context.session 来访问或者修改当前用户 Session
1、读取和设置 Session
exports.fetchPosts = function* (ctx) {
// 获取 Session 上的内容
const user = ctx.session.user;
const posts = yield ctx.service.post.fetch(userId);
// 修改 Session 的值
ctx.session.user = ctx.session.user ? ctx.session.user++ : 1;
ctx.body = {
success: true,
posts,
};
};
2、删除session
exports.deleteSession = function* (ctx) {
ctx.session = null;
};
3、设置 session配置
// config.default.js
exports.session = {
key: 'EGG_SESS',
maxAge: 24 * 3600 * 1000, // 1 天
httpOnly: true,
encrypt: true,
};
service 处理业务逻辑
service 需要继承于 app.Service,service在复杂业务场景下做业务逻辑封装的抽象层
一、service的ctx
1、this.ctx.curl - 发起网络调用
2、this.ctx.service.otherService - 调用service
3、this.ctx.db - 发起数据库调用
二、Example
// 调用服务 app/controller/user.js
module.exports = app => {
class User extends app.controller {
async addUser(){
let userData = {
name: 'siguang',
password: 'xxxxxx'
}
let isAddUser = yield ctx.service.user.createUser(userData)
if(setUser){
ctx.body = '添加成功'
}
ctx.body = isAddUser ? '添加成功' : '添加失败';
}
}
}
// 定义一个Service app/service/user.js
module.exports = app => { // 中间件支持两个参数 options中间件配置,app当前应用Application的实例
class User extends app.Service {
async createUser(params){
const url = '/account/blance/createUser';
let result = await this.ctx.curl(url, {
method: 'POST',
dataType: 'json', // 自动解析 JSON response
data: params,
timeout: 15000, // 15 秒超时
}
this.ctx.logger.info('响应结果:', result.data);
return result.data;
}
}
return User;
};
中间件 Middleware
一、config.default.js 配置全局
module.exports = {
// 配置需要的中间件,数组顺序即为中间件的加载顺序
middleware: [ 'gzip' ],
// 配置 gzip 中间件的配置
gzip: {
threshold: 1024, // 小于 1k 的响应体不压缩
},
};
// 框架中默认的中间件
module.exports = {
bodyParser: {
jsonLimit: '10mb',
},
};
二、通用配置
enable:控制中间件是否开启。
match:设置只有符合某些规则的请求才会经过这个中间件。
ignore:设置符合某些规则的请求不经过这个中间件。
bodyParser: {
enable: false,
jsonLimit: '10mb',
},
三、Example:
1、创建中间件 checkAuth.js middleware
module.exports = app => {
return async function checkAuth(ctx, next){
const user = ctx.session.user;
console.log(`UserSession ------ ${JSON.stringify(user)}`)
if(user){
console.log(`visited true`);
await next();
}
else{
console.log(`visited false`);
ctx.body = {
data:{},
status: 10001,
message: 'Session失效'
}
}
}
}
2、路由中调用
注意如果路由中使用中间件分为全局和和单个路由第一次两种,如果配置到config.default.js中的就为全局
// 引用中间件
const checkAuth = app.middleware.checkAuth();
// 不需要验证
app.get('/', 'home.index');
app.get('/query', 'query.query.queryPage');
// 需要验证是否登录的
app.post('/web/getUserList', checkAuth, 'user.user.getUserList'); // 获取用户列表
Validate 参数校验插件
validate只用于post请求的参数校验,get请求取出的都是字符串
一、在config/plugin.js配置中添加
exports.validate = {
enable: true,
package: 'egg-validate',
};
二、Exmaple
async refundApply() {
const { ctx, app, service } = this;
const paramRule = {
partnerUserId: { type: 'string' },
applyUserName: { type: 'string' },
amount: { type: 'string' },
sourceAccount: { type: 'string' },
};
const paramErrors = app.validator.validate(paramRule, ctx.request.body);
if (paramErrors) {
ctx.body = app.renderBody({
statusType: app.statusType.paramsError,
error: paramErrors,
});
return;
}
const response = await service.payTool.billquery.refundApply(ctx.request.body);
ctx.body = response;
}
三、验证规则
1、required - 是否当前字段必须有,required: false 可以为空
2、allowEmpty - 允许为空
2、int - 只能为整数
3、number - 可以是整数和浮点数
4、date - 日期 'YYYY-MM-DD' birthoday: 'date'
5、dateTime - 日期 YYYY-MM-DD HH:mm:ss
6、boolean - 是否是布尔值 working: 'boolean'
7、string - 是否是字符串, 字符串的四个规则:
allowEmpty - 允许为空字符串
format - 使用正则来验证字符串的格式
max - 字符串最大长度
min - 字符串最小长度
const rule = {
username: { allowEmpty: true, min: 10, max: 100, format: /^\d+$/ }
}
8、email - 是否是email格式
9、password - 密码验证规则 max最大、min最小、compare比较 pass: {type: password, max: 32, min: 6}
10、url - 是否是url
11、enum - 如果是枚举需要加一个规则
// operateType的值必须是values数组中的一项,values必须为数组
const paramRule = {
operateType: { type: 'enum', values: ['REPAYMENT', 'CHARGE'] },
}
const paramErrors = app.validator.validate(paramRule, ctx.request.body);
12、object - 如果是对象,需要加一个规则
13、array - 如果是数组,需要加一个规则
itemType 数组中每一个元素的规则
rule - An object that validate the items of the array. Only work with itemType.
max - 数组最大长度
min - 数组最小长度
https://github.com/node-modules/parameter#rule
https://github.com/node-modules/parameter/blob/master/benchmark.js
https://github.com/node-modules/parameter/blob/master/example.js
jsonp
jsonp只能get请求
// config/config.default.js jsonp的配置
exports.jsonp = {
callback: 'callback', // 识别 query 中的 `callback` 参数
limit: 100, // 函数名最长为 100 个字符
};
// app/router.js 路由加jsonp中间件
module.exports = app => {
const jsonp = app.jsonp();
app.get('/api/posts/:id', jsonp, 'posts.show'); // 通过jsonp中间件来让路由支付一个中间件
app.get('/api/posts', jsonp, 'posts.list');
};
// app/controller/posts.js
exports.show = function* (ctx) {
ctx.body = {
name: 'egg',
category: 'framework',
language: 'Node.js',
};
};
用户请求 /api/posts/1?callback=fn,响应为 JSONP 格式,如果用户请求 /api/posts/1
HttpClient
Node模拟客户端请求,curl请求默认 content-type: application/x-www-form-urlencoded,
app.curl(url, options) 和 app.httpclient.request(url, options) 两种方法相同
一、Example:
module.exports = app => {
class HttpClientController extends app.Controller{
async getHttpClient(){
const {ctx, app, service} = this;
let result = await this.ctx.curl('http://goucai.diyicai.com/lottery/getissue.action?lotteryId=001&issueLen=100&d=1502966960306', {
methods: 'get'
});
let phones = [];
let resultObj = JSON.parse(result.data.toString());
resultObj.forEach((data, idx, arr)=>{
console.log('data------', data);
phones.push(data.endTime);
})
ctx.body = phones.join(',')
}
}
return HttpClientController;
}
二、config配置 // config/config.default.js
exports.httpclient = {
// 默认开启 http/https KeepAlive 功能
keepAlive: true,
// 空闲的 KeepAlive socket 最长可以存活 4 秒
freeSocketKeepAliveTimeout: 4000,
// 当 socket 超过 30 秒都没有任何活动,就会被当作超时处理掉
timeout: 30000,
// 允许创建的最大 socket 数
maxSockets: Infinity,
// 最大空闲 socket 数
maxFreeSockets: 256,
// 是否开启本地 DNS 缓存,默认关闭
// 一旦设置开启,则每个域名的 DNS 查询结果将在进程内缓存 10 秒
enableDNSCache: false,
};
三、options对象
let options = {};
const result = yield ctx.curl('https://httpbin.org/get?foo=bar', options);
1、mothod: 请求方法
2、data: 需要发送的数据 { foo: 'bar' }
3、dataAsQueryString: Boolean 如果为 true 即使在post情况下,也会强制将options.data以 querystringstringify处理后拼接到url的query参数上
4、content(String|Buffer): 发送请求的正文,如果设置了此参数会忽略data参数
ctx.curl(url, {
method: 'POST',
// 直接发送原始 xml 数据,不需要 HttpClient 做特殊处理
content: '<xml><hello>world</hello></xml>',
headers: {
'content-type': 'text/html',
},
});
5、stream(ReadStream): 发送请求正文的可读数据流
ctx.curl(url, {
method: 'POST',
stream: fs.createReadStream('/path/to/read'),
});
6、writeStream: 接受响应数据的可写数据流
ctx.curl(url, {
writeStream: fs.createWriteStream('/path/to/store'),
});
7、consumeWriteStream: Boolean 是否等待 writeStream 完全写完才算响应全部接收完毕,默认是 true
8、contentType: 请求数据的格式,默认undefined
9、dataType: 响应数据格式
10、headers: 自定义请求头
11、timeout: 请求超时时间 默认是 [ 5000, 5000 ],即创建连接超时是 5 秒,接收响应超时是 5 秒。
12、agent:
13、httpsAgent
14、auth、digestAuth
15、followRedirect:Boolean 是否自动跟进3xx的跳转响应
16、maxRedirects: number 最大自动跳转次数
17、formatRedirectUrl: 自定义实现302、301
18、beforeRequest: 请求发送前会调用beforeRequest钩子
ctx.curl(url, {
beforeRequest: options => {
// 例如我们可以设置全局请求 id,方便日志跟踪
options.headers['x-request-id'] = uuid.v1();
}
});
19、streaming:是否直接返回响应流
20、gzip: Boolean是否开始gzip
21、timing: Boolean 是否开启请求各阶段的时间没是
四、Get请求
1、options.method = 'post' 设置请求参数,默认是get请求可以不用加
const result = yield ctx.curl('https://httpbin.org/get?foo=bar', {method: post});
2、status: 响应状态码
3、headers: 响应头信息 有{'conent-type': 'text/html'}
4、data: 响应body, 返回的是Buffer类型, 如果设置了options.dataType会根据参数来处理
五、Post请求
module.exports = function* post(ctx) {
const result = yield ctx.curl('https://httpbin.org/post', {
// 必须指定 method
method: 'POST',
// 通过 contentType 告诉 HttpClient 以 JSON 格式发送
contentType: 'json',
// 要传的数据
data: {
hello: 'world',
now: Date.now(),
},
// 明确告诉 HttpClient 以 JSON 格式处理返回的响应 body
dataType: 'json',
});
ctx.body = result.data;
};
六、From表单提交
以 ontent-type: application/x-www-form-urlencoded 的格式提交请求数据
// app/controller/form.js
module.exports = function* form(ctx) {
const result = yield ctx.curl('https://httpbin.org/post', {
// 必须指定 method,支持 POST,PUT 和 DELETE
method: 'POST',
// 不需要设置 contentType,HttpClient 会默认以 application/x-www-form-urlencoded 格式发送请求
data: {
now: Date.now(),
foo: 'bar',
},
// 明确告诉 HttpClient 以 JSON 格式处理响应 body
dataType: 'json',
});
ctx.body = result.data.form;
// 响应最终会是类似以下的结果:
// {
// "foo": "bar",
// "now": "1483864184348"
// }
};
七、Multipart 方式上传
From 表单提交时包含文件的时候,需要用到 multipart/form-data 进行提交了
引用fromstream 第三方模块
// app/controller/multipart.js
const FormStream = require('formstream');
module.exports = function* multipart(ctx) {
const form = new FormStream();
// 设置普通的 key value
form.field('foo', 'bar');
// 上传当前文件本身用于测试
form.file('file', __filename);
const result = yield ctx.curl('https://httpbin.org/post', {
// 必须指定 method,支持 POST,PUT
method: 'POST',
// 生成符合 multipart/form-data 要求的请求 headers
headers: form.headers(),
// 以 stream 模式提交
stream: form,
// 明确告诉 HttpClient 以 JSON 格式处理响应 body
dataType: 'json',
});
ctx.body = result.data.files;
// 响应最终会是类似以下的结果:
// {
// "file": "'use strict';\n\nconst For...."
// }
};
// 添加更多文件
form.file('file1', file1);
form.file('file2', file2);
八、以Stream 方式上传文件
Stream 实际会以 Transfer-Encoding: chunked 传输编码格式发送
// app/controller/stream.js
const fs = require('fs');
module.exports = function* stream(ctx) {
// 上传当前文件本身用于测试
const fileStream = fs.createReadStream(__filename);
// httpbin.org 不支持 stream 模式,使用本地 stream 接口代替
const url = `${ctx.protocol}://${ctx.host}/stream`;
const result = yield ctx.curl(url, {
// 必须指定 method,支持 POST,PUT
method: 'POST',
// 以 stream 模式提交
stream: fileStream,
});
ctx.status = result.status;
ctx.set(result.headers);
ctx.body = result.data;
// 响应最终会是类似以下的结果:
// {"streamSize":574}
};
模板渲染
读取数据渲染模板,呈现给用户
一、使用
1、$ npm i egg-view-ejs --save // 安装
2、配置
// config/plugin.js
exports.nunjucks = {
enable: true,
package: 'egg-view-nunjucks' // 开启插件使用numjucks模板
};
// config/config.default.js
exports.view = {
defaultViewEngine: 'nunjucks',
mapping: {
'.tpl': 'nunjucks',
},
};
3、render、renderString两个方法
render(fileName, locals, viewOptions): filenName: 文件路径、locals: 渲染的数据、viewOptions: 用户传入的配置
render(name, locals) 渲染模板文件, 并赋值给 ctx.body
renderView(name, locals) 渲染模板文件, 仅返回不赋值
renderString(tpl, locals) 渲染模板字符串, 仅返回不赋值
- numjucks模板语法: http://mozilla.github.io/nunjucks/cn/templating.html 见模板md -
静态资源
Egg内置static插件,static默认映射到 public目录
app/public
├── css
│ └── news.css
└── js
├── lib.js
└── news.js
内置对象
一、Application - 全局的方法和对象挂载到Application中
// extend/application.js 创建全局对象
module.exports = {
// 全局返回状态类型
statusType: 'STATUS_TYPE',
// 生成返回报文
renderBody(params) {
if (typeof params.statusType === 'undefined') {
throw new Error('statusType error');
}
const response = {
data: params.data || {},
message: params.message || this.statusMessage[params.statusType],
status: params.statusType,
};
if (params.error) response.error = params.error;
return response;
}
}
// 调用 controller、service都可以
module.exports = app => {
return class UserController extends app.Controller {
async fetch() {
this.ctx.body = app.cache.get(this.ctx.query.id);
}
};
};
定时任务
有些任务需要定时来完成,如定时上报,定时远程更新本地缓存,定时任务都统一存放在 app/schedule 目录下
创建一个定时任务 app/schedule/update_cache.js
module.exports = {
// 通过 schedule 属性来设置定时任务的执行间隔等配置
schedule: {
interval: '1m', // 1 分钟间隔
type: 'all', // 指定所有的 worker 都需要执行
},
// task 是真正定时任务执行时被运行的函数,第一个参数是一个匿名的 Context 实例
* task(ctx) {
const res = yield ctx.curl('http://www.api.com/cache', {
dataType: 'json',
});
ctx.app.cache = res.data;
}
};
框架扩展
框架扩展自身功能: Application、Context、Request、Response、Helper,在这几个对象上扩展就在extend目录里创建相应的js文件,application.js
Helper用来提供一些实用的utility函数
一、Application 全局应用对象扩展
访问 ctx.app, Controller,Middleware,Helper,Service 中都可以通过 this.app 访问到 Application 对象
// app.js
module.exports = app => {
app.config
};
// 扩展 app.foo() 方法 app/extend/application.js
module.exports = {
foo(param) {
// this 就是 app 对象,在其中可以调用 app 上的其他方法,或访问属性
},
};
二 、Context 扩展
extend/content.js
module.export = {
async foo(param){
...
}
}
引用 await this.ctx.foo({name: 'siguang'});
三、Helper 用来编写一些实用函数
// app/extend/helper.js
const moment = require('moment');
exports.relativeTime = time => moment(new Date(time * 1000)).fromNow();
// 也可以在模板里面使用:
<!-- app/views/news/list.tpl -->
{{ helper.relativeTime(item.time) }}
启动自定义
用来进行应用启动时进行初始化工作
// 通过入口文件app.js
module.exports = app => {
app.beforeStart(function* () {
// 应用会等待这个函数执行完成才启动
app.cities = yield app.curl('http://example.com/city.json', {
method: 'GET',
dataType: 'json',
});
});
};
logger 日志
this.ctx.logger.info('xxxxxx');
logger.debug()、logger.info()、logger.warn()、logger.error()
config.deubgger 文件配置
logger: {
level: 'DEBUG',
dir: '../app/logger',
}
本地开发
egg-bin模块(用于本地开发和单元测试)
package.json:
{
"scripts": {
"dev": "egg-bin dev --port 7001"
}
}
插件的开发
自定义插件存储目录: lib/plugin/...
egg-ua插件
1、创建package.json
{
"eggPlugin": {
"name": "ua"
}
}
2、egg-ua/us.js插件文件
module.exports = {
get isIOS() {
const iosReg = /iphone|ipad|ipod/i;
return iosReg.test(this.get('user-agent'));
},
};
3、config/plugin.js 通过path来挂载插件
const path = require('path');
exports.ua = {
enable: true,
path: path.join(__dirname, '../lib/plugin/egg-ua'),
};
项目目录
├── package.json
├── app.js (可选) // 启动初始化
├── agent.js (可选)
├── app
| ├── router.js // 路由规则
│ ├── 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 (可选) // ctx扩展
│ ├── 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
参考资料
http://koa.bootcss.com/