前言
- 我第一次了解egg.js时候,感觉非常nb。稍微学了一下,结果后来又忘差不多了。
- 我现在养成了一种不写博客就感觉自己不会的习惯。就算当时会了,过段时间也忘了。
- 官网
- 插件
- egg的构造有点像jekins里那个worker和master的感觉。node是单进程单线程,所以只使用一个cpu核,egg会做一个node集群,通过master来控制worker达到多核的利用。当客户端请求来时,会先发给master,master看哪个空闲,把请求发给哪个。如果哪个worker宕机了,就立即重启它。
安装
- 先不用脚手架,熟悉下里面东西。
cnpm i egg --save
cnpm i egg-bin --save-dev
- 这个egg-bin就相当于我们经常配的package.json里script命令。通过script去执行egg-bin,这个再去执行egg。
- 所以package.json里加上:
"scripts": {
"dev": "egg-bin dev"
},
编写controller
- 如果你熟悉 Web 开发或 MVC,肯定猜到我们第一步需要编写的是 Controller 和 Router。
- 新建个文件夹app,里面建个router.js
module.exports = (app) => {
const { router, controller } = app
router.get('/mypage', controller.mypage.index)
}
- 这个会传入一个app,app上面挂了一堆东西,这里只需要router,和controller。controller是个对象,里面是文件夹controller下文件的实例,这里做了个文件叫mypage。
- 在app下的controller文件夹下编写mypage.js文件:
const { Controller } = require('egg')
module.exports = class extends Controller {
async index() {
this.ctx.body = 'hello'
}
}
- 这个类继承egg的controller,就是可以拿到它的一些方法。我们自己写的这个函数名,就是index,对应着上面传给router的controller.mypage.index。
- 另外需要建立个默认配置。在app文件夹同级下建个config文件夹,里面建个config.default.js文件。
config/config.default.js
exports.keys = 'xx'
- 随便给个key,优点像加盐算法里面的盐值的意思。
- 然后使用npm run dev 启动,就能看见页面渲染的hello了。
- 查看cookie,发现cookie里面有个csrf_token,这些都帮你搞定了。
静态文件
- app文件夹下面会有个public文件夹,没有这个文件夹会帮你建。
- 在这个文件夹下随便建个文件,里面写上自己的内容,然后访问
http://127.0.0.1:7001/public/你的文件名
就可以访问了。
使用模板引擎
- 这里使用nunjucks。
- 更多模板引擎
cnpm install egg-view-nunjucks --save
- 先随意建个模板。需要在app下的view文件夹下。
app/view/aaa.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div>aaaaaaaaaaaaaaaaaaaaa</div>
<div>
{% for item in data %}
<div> {{item.id}}</div>
<div> {{item.title}}</div>
{% endfor%}
</div>
</body>
</html>
- config.default.js需要改一下view的配置:
config/config.default.js
module.exports = app => {
let config = {}
config.keys = app.name + Date.now()
config.view = {
mapping: {
defaultExtension: '.html',
defaultViewEngine: 'nunjucks',
'.html': 'nunjucks'
}
}
return config
}
- 这个defaultExtension就是查找文件没给扩展名会默认加上扩展名html。
- defaultViewEngine就是默认模板引擎是nunjucks。
- .html就是扩展名为这个的使用nunjucks渲染。如果想用别的比如ejs就是
'.html':'ejs'
。 - config文件夹下再建个文件plugin.js:
config/plugin.js
exports.nunjucks = {
enable: true,
package: 'egg-view-nunjucks'
}
- 这个就是把插件开启。
- 最后,还要修改controller,将前面写的mypage.js修改下,让其渲染页面。
const { Controller } = require('egg')
module.exports = class extends Controller {
async index() {
let { ctx } = this
const data = [{ id: '111', title: '333' }, { id: '11221', title: '3341' }]
await ctx.render('aaa', { data })
}
}
- 这里的aaa就没给扩展名,因为配置了默认的。
- 打开页面全部渲染出来就ok了。
- 总结下就是配插件,配默认设置,ctx.render。
调用远程接口
- 在实际应用中,Controller 一般不会自己产出数据,也不会包含复杂的逻辑,复杂的过程应抽象为业务逻辑层 Service。
- 先模拟个接口,新建个service.js,然后安装express和mockjs。
let Mock = require('mockjs')
let express = require('express')
let app = express()
app.get('/data', function (req, res) {
let result = Mock.mock(
{
"code": 0,
[`data|${req.query.limit}`]: [
{ "id": "@id" },
{ "title": "@csentence" }
]
}
)
res.json(result)
})
app.listen(3000)
- 访问 3000端口
/data
能有假数据,且使用查询?limit等于几就能显示几条就对了。 - 然后,为了方便配置,我们可以在config.default.js里面的config对象上挂个属性,里面挂上url。
module.exports = app => {
let config = {}
config.keys = app.name + Date.now()
config.view = {
mapping: {
defaultExtension: '.html',
defaultViewEngine: 'nunjucks',
'.html': 'nunjucks'
}
}
config.mypage = { url: '127.0.0.1:3000/data' }
return config
}
- 注意,config上不能直接挂字面量只能对象。上面配置的属性应该是被调用后传给真正的config对象的。另外使用他的curl方法会自己加http://,所以配置不用加。
- 然后在app下建立service文件夹,写个mypageservice.js。文件夹名字是定死的,文件名没定死。
const { Service } = require('egg')
module.exports = class extends Service {
async myback(limit) {
let { ctx } = this
let url = this.config.mypage.url
let res = await ctx.curl(url, {
method: 'GET',
data: { limit },
dataType: 'json'
})
return res.data.data
}
}
- 这个service用来处理复杂逻辑,控制器调用service来获取结果。
- 我们取的第一个data是数据外层大括号里面的data,第二个data是前面定义的假数据的data那个键。因为我们controller需要获取到数组嘛,这样取就能拿到数组了。
- 这里需要继承egg的类,后面会调它。把他导出就行了。
- 最后编写controller,调用service。
const { Controller } = require('egg')
module.exports = class extends Controller {
async index() {
let { ctx } = this
let limit = ctx.query ? ctx.query.limit : 5;
let data = await this.service.mypageservice.myback(limit)
await ctx.render('aaa', { data })
}
}
- 这里是获取data是异步的,所以要用await。
- this.service.xxx就是去找service文件夹下xxx文件实例。
- 最后那个myback就是类里面的原型的方法名。
- 刷新页面看是不是5条数据。修改查询参数,看是不是能返回对应条数的数据。
计划任务
- 我感觉计划任务真的是一个很6的设计。有需求建议看一下文档对于计划任务的说明。
- 一般指有计划的做的任务,常见的是定时任务。
- 计划任务放在app下的schedule文件夹。
- 比如我有个数据需要每天改一次。那么我那个数据就不需要每次要时都去请求,而是应该每天去请求一次,然后缓存下,下次就直接从缓存拿就可以了。
- 先写个接口,可以获取到数据,为了方便查看取数据没有,加个时间戳:
app.get('/title', function (req, res) {
res.json({ title: `每日更换标题${Date.now()}` })
})
- 在app下建立schedule文件夹,写个文件updateTitle.js。文件名不是定死的,文件夹名是定死的。
app/schedule/updateTitle.js
const { Subscription } = require('egg')
module.exports = class extends Subscription {
static get schedule() {
return {
interval: '5s',
type: 'all'
}
}
async subscribe() {
let res = await this.ctx.curl(this.config.cache.url, {
method: 'GET',
dataType: 'json'
})
this.ctx.app.cache = res.data
}
}
- 这里要继承egg的subscription基类。里面静态方法schedule和subscribe方法是定死必须要有的,不然报错。
- 其中,schedule相当于任务配置,返回个对象,里面interval代表执行间隔,为了看出效果,我们设定为5s执行一次。
- 执行逻辑就是subscribe,名字定死的。就是请求数据,把数据放到this.ctx.app.cache上。
const { Controller } = require('egg')
module.exports = class extends Controller {
async index() {
let { ctx } = this
let limit = ctx.query ? ctx.query.limit : 5;
let data = await this.service.mypageservice.myback(limit)
await ctx.render('aaa', { data, title: ctx.app.cache ? ctx.app.cache.title : '默认' })
}
}
- 在控制器中去获取缓存,如果有缓存,那么就拿缓存的,否则就走默认的。
- 做到这里,实际计划任务第一次执行不会执行。所以需要让其在启动时拿到cache,这样渲染出的页面就一直不可能是默认了。
- 第一次启动执行需要配置app.js。在app同目录下建立app.js。
module.exports = app => {
app.beforeStart(async () => {
await app.runSchedule('updateTitle')
})
}
- 这个updateTitle就是文件名,这样就会启动走一遍计划任务那个文件。