预加载
当你从数据库检索数据时,也想同时获得与之相关联的查询,这被称为预加载.这个基本思路就是当你调用 find 或 findAll 时使用 include 属性.让我们假设以下设置:
class User extends Model {} User.init({ name: Sequelize.STRING }, { sequelize, modelName: 'user' }) class Task extends Model {} Task.init({ name: Sequelize.STRING }, { sequelize, modelName: 'task' }) class Tool extends Model {} Tool.init({ name: Sequelize.STRING }, { sequelize, modelName: 'tool' }) Task.belongsTo(User) User.hasMany(Task) User.hasMany(Tool, { as: 'Instruments' }) sequelize.sync().then(() => { // 这是我们继续的地方 ... })
首先,让我们用它们的关联 user 加载所有的 task.
Task.findAll({ include: [ User ] }).then(tasks => { console.log(JSON.stringify(tasks)) /* [{ "name": "A Task", "id": 1, "createdAt": "2013-03-20T20:31:40.000Z", "updatedAt": "2013-03-20T20:31:40.000Z", "userId": 1, "user": { "name": "John Doe", "id": 1, "createdAt": "2013-03-20T20:31:45.000Z", "updatedAt": "2013-03-20T20:31:45.000Z" } }] */ })
请注意,访问者(结果实例中的 User 属性)是单数形式,因为关联是一对一的.
接下来的事情:用多对一的关联加载数据!
User.findAll({ include: [ Task ] }).then(users => { console.log(JSON.stringify(users)) /* [{ "name": "John Doe", "id": 1, "createdAt": "2013-03-20T20:31:45.000Z", "updatedAt": "2013-03-20T20:31:45.000Z", "tasks": [{ "name": "A Task", "id": 1, "createdAt": "2013-03-20T20:31:40.000Z", "updatedAt": "2013-03-20T20:31:40.000Z", "userId": 1 }] }] */ })
请注意,访问者(结果实例中的 Tasks 属性)是复数形式,因为关联是多对一的.
如果关联是别名的(使用 as 参数),则在包含模型时必须指定此别名. 注意用户的 Tool 如何被别名为 Instruments. 为了获得正确的权限,你必须指定要加载的模型以及别名:
User.findAll({ include: [{ model: Tool, as: 'Instruments' }] }).then(users => { console.log(JSON.stringify(users)) /* [{ "name": "John Doe", "id": 1, "createdAt": "2013-03-20T20:31:45.000Z", "updatedAt": "2013-03-20T20:31:45.000Z", "Instruments": [{ "name": "Toothpick", "id": 1, "createdAt": null, "updatedAt": null, "userId": 1 }] }] */ })
你还可以通过指定与关联别名匹配的字符串来包含别名:
User.findAll({ include: ['Instruments'] }).then(users => { console.log(JSON.stringify(users)) /* [{ "name": "John Doe", "id": 1, "createdAt": "2013-03-20T20:31:45.000Z", "updatedAt": "2013-03-20T20:31:45.000Z", "Instruments": [{ "name": "Toothpick", "id": 1, "createdAt": null, "updatedAt": null, "userId": 1 }] }] */ }) User.findAll({ include: [{ association: 'Instruments' }] }).then(users => { console.log(JSON.stringify(users)) /* [{ "name": "John Doe", "id": 1, "createdAt": "2013-03-20T20:31:45.000Z", "updatedAt": "2013-03-20T20:31:45.000Z", "Instruments": [{ "name": "Toothpick", "id": 1, "createdAt": null, "updatedAt": null, "userId": 1 }] }] */ })
当预加载时,我们也可以使用 where 过滤关联的模型. 这将返回 Tool 模型中所有与 where 语句匹配的行的User.
User.findAll({ include: [{ model: Tool, as: 'Instruments', where: { name: { [Op.like]: '%ooth%' } } }] }).then(users => { console.log(JSON.stringify(users)) /* [{ "name": "John Doe", "id": 1, "createdAt": "2013-03-20T20:31:45.000Z", "updatedAt": "2013-03-20T20:31:45.000Z", "Instruments": [{ "name": "Toothpick", "id": 1, "createdAt": null, "updatedAt": null, "userId": 1 }] }], [{ "name": "John Smith", "id": 2, "createdAt": "2013-03-20T20:31:45.000Z", "updatedAt": "2013-03-20T20:31:45.000Z", "Instruments": [{ "name": "Toothpick", "id": 1, "createdAt": null, "updatedAt": null, "userId": 1 }] }], */ })
当使用 include.where 过滤一个预加载的模型时,include.required 被隐式设置为 true. 这意味着内部联接完成返回具有任何匹配子项的父模型.
使用预加载模型的顶层 WHERE
将模型的 WHERE 条件从 ON 条件的 include 模式移动到顶层,你可以使用 '$nested.column$' 语法:
User.findAll({ where: { '$Instruments.name$': { [Op.iLike]: '%ooth%' } }, include: [{ model: Tool, as: 'Instruments' }] }).then(users => { console.log(JSON.stringify(users)); /* [{ "name": "John Doe", "id": 1, "createdAt": "2013-03-20T20:31:45.000Z", "updatedAt": "2013-03-20T20:31:45.000Z", "Instruments": [{ "name": "Toothpick", "id": 1, "createdAt": null, "updatedAt": null, "userId": 1 }] }], [{ "name": "John Smith", "id": 2, "createdAt": "2013-03-20T20:31:45.000Z", "updatedAt": "2013-03-20T20:31:45.000Z", "Instruments": [{ "name": "Toothpick", "id": 1, "createdAt": null, "updatedAt": null, "userId": 1 }] }], */
包括所有
要包含所有属性,你可以使用 all:true 传递单个对象:
User.findAll({ include: [{ all: true }]});
包括软删除的记录
如果想要加载软删除的记录,可以通过将 include.paranoid 设置为 false 来实现
User.findAll({ include: [{ model: Tool, where: { name: { [Op.like]: '%ooth%' } }, paranoid: false // query and loads the soft deleted records }] });
排序预加载关联
在一对多关系的情况下.
Company.findAll({ include: [ Division ], order: [ [ Division, 'name' ] ] }); Company.findAll({ include: [ Division ], order: [ [ Division, 'name', 'DESC' ] ] }); Company.findAll({ include: [ { model: Division, as: 'Div' } ], order: [ [ { model: Division, as: 'Div' }, 'name' ] ] }); Company.findAll({ include: [ { model: Division, as: 'Div' } ], order: [ [ { model: Division, as: 'Div' }, 'name', 'DESC' ] ] }); Company.findAll({ include: [ { model: Division, include: [ Department ] } ], order: [ [ Division, Department, 'name' ] ] });
在多对多关系的情况下,你还可以通过表中的属性进行排序.
Company.findAll({ include: [ { model: Division, include: [ Department ] } ], order: [ [ Division, DepartmentDivision, 'name' ] ] });
嵌套预加载
你可以使用嵌套的预加载来加载相关模型的所有相关模型:
User.findAll({ include: [ {model: Tool, as: 'Instruments', include: [ {model: Teacher, include: [ /* etc */]} ]} ] }).then(users => { console.log(JSON.stringify(users)) /* [{ "name": "John Doe", "id": 1, "createdAt": "2013-03-20T20:31:45.000Z", "updatedAt": "2013-03-20T20:31:45.000Z", "Instruments": [{ // 1:M and N:M association "name": "Toothpick", "id": 1, "createdAt": null, "updatedAt": null, "userId": 1, "Teacher": { // 1:1 association "name": "Jimi Hendrix" } }] }] */ })
这将产生一个外连接. 但是,相关模型上的 where 语句将创建一个内部连接,并仅返回具有匹配子模型的实例. 要返回所有父实例,你应该添加 required: false.
User.findAll({ include: [{ model: Tool, as: 'Instruments', include: [{ model: Teacher, where: { school: "Woodstock Music School" }, required: false }] }] }).then(users => { /* ... */ })
以上查询将返回所有用户及其所有乐器,但只会返回与 Woodstock Music School 相关的老师.
包括所有也支持嵌套加载:
User.findAll({ include: [{ all: true, nested: true }]});
新增
字段限制
await User.create({ username: 'barfooz', isAdmin: true }, { fields: [ 'username' ] }); // 只有username有效 User.bulkCreate([ { username: 'foo' }, { username: 'bar', admin: true} ], { fields: ['username'] }).then(() => { // admin 将不会被构建 })
新增单个
// create this.ctx.body = await this.ctx.model.User.create({ name: "哈哈哈", age: 12 });
批量新增
// 批量新增 bulkCreate this.ctx.body = await this.ctx.model.User.bulkCreate([ { name: "第一个", age: 15 }, { name: "第二个", age: 15 }, { name: "第三个", age: 15 }, ]);
修改
字段限制
task.title = 'foooo' task.description = 'baaaaaar' await task.save({fields: ['title']}); // title 现在将是 “foooo”,而 description 与以前一样 // 使用等效的 update 调用如下所示: await task.update({ title: 'foooo', description: 'baaaaaar'}, {fields: ['title']}); // title 现在将是 “foooo”,而 description 与以前一样
单个修改
// 找出当前记录 const user = await this.ctx.model.User.findByPk(1); await user.update({ name: "我被修改了", age: 30 });
批量修改
// 批量修改 await this.ctx.model.User.update({ name: "批量修改" }, { // 条件 where: { name: "第一个" } });
递增
// 找出当前记录 increment const user = await this.ctx.model.User.findByPk(2); this.ctx.body = await user.increment({ age: 3, // age每次递增3 other:2 // other每次递增2 });
递减
// 找出当前记录 decrement const user = await this.ctx.model.User.findByPk(2); this.ctx.body = await user.decrement({ age: 3, // age每次递减3 other:2 // other每次递减2 });
删除
软删除
模型中配置
// 配置(重要) const User = app.model.define('user', { /* bla */},{ // 同时需要设置paranoid为true(此种模式下,删除数据时不会进行物理删除,而是设置deletedAt为当前时间 'paranoid': true });
查询包括软删除内容
let user = await ctx.model.User.findOne({ include:{ model:ctx.model.Video, // 包括软删除 paranoid: false }, where: { id: 33 }, // 包括软删除 paranoid: false });
彻底删除
如果 paranoid 选项为 true,则不会删除该对象,而将 deletedAt 列设置为当前时间戳. 要强制删除,可以将 force: true 传递给 destroy 调用:
task.destroy({ force: true })
在 paranoid 模式下对象被软删除后,在强制删除旧实例之前,你将无法使用相同的主键创建新实例.
恢复软删除的实例
如果你使用 paranoid:true 软删除了模型的实例,之后想要撤消删除,请使用 restore 方法:
// 进行软删除... task.destroy(); // 恢复软删除... task.restore();
条件删除
await this.ctx.model.User.destroy({ where: { name: "批量修改" } });
批量删除
await this.ctx.model.Post.destroy({ where: { id: posts } });
重载实例
如果你需要让你的实例同步,你可以使用 reload 方法. 它将从数据库中获取当前数据,并覆盖调用该方法的模型的属性.
Person.findOne({ where: { name: 'john' } }).then(person => { person.name = 'jane' console.log(person.name) // 'jane' person.reload().then(() => { console.log(person.name) // 'john' }) })
模型自定义方法
// 模型 // 模型自定义方法 topic_user.ceshi = (param) => { console.log('模型自定义方法'); console.log(param); return param; } // 控制器 await this.ctx.model.TopicUser.ceshi(123);
Scopes - 作用域(重点)
作用域允许你定义常用查询,以便以后轻松使用. 作用域可以包括与常规查找器 where, include, limit 等所有相同的属性.
定义
作用域在模型定义中定义,可以是finder对象或返回finder对象的函数,除了默认作用域,该作用域只能是一个对象:
class Project extends Model {} Project.init({ // 属性 }, { defaultScope: { where: { active: true } }, scopes: { deleted: { where: { deleted: true } }, activeUsers: { include: [ { model: User, where: { active: true }} ] }, random () { return { where: { someNumber: Math.random() } } }, accessLevel (value) { return { where: { accessLevel: { [Op.gte]: value } } } } sequelize, modelName: 'project' } });
通过调用 addScope 定义模型后,还可以添加作用域. 这对于具有包含的作用域特别有用,其中在定义其他模型时可能不会定义 include 中的模型.
始终应用默认作用域. 这意味着,通过上面的模型定义,Project.findAll() 将创建以下查询:
SELECT * FROM projects WHERE active = true
可以通过调用 .unscoped(), .scope(null) 或通过调用另一个作用域来删除默认作用域:
Project.scope('deleted').findAll(); // 删除默认作用域 SELECT * FROM projects WHERE deleted = true
还可以在作用域定义中包含作用域模型. 这让你避免重复 include,attributes 或 where 定义.
使用上面的例子,并在包含的用户模型中调用 active 作用域(而不是直接在该 include 对象中指定条件):
activeUsers: { include: [ { model: User.scope('active')} ] }
使用
通过在模型定义上调用 .scope 来应用作用域,传递一个或多个作用域的名称. .scope 返回一个全功能的模型实例,它具有所有常规的方法:.findAll,.update,.count,.destroy等等.你可以保存这个模型实例并稍后再次使用:
const DeletedProjects = Project.scope('deleted'); DeletedProjects.findAll(); // 过一段时间 // 让我们再次寻找被删除的项目! DeletedProjects.findAll();
作用域适用于 .find, .findAll, .count, .update, .increment 和 .destroy.
可以通过两种方式调用作为函数的作用域. 如果作用域没有任何参数,它可以正常调用. 如果作用域采用参数,则传递一个对象:
Project.scope('random', { method: ['accessLevel', 19]}).findAll(); SELECT * FROM projects WHERE someNumber = 42 AND accessLevel >= 19
合并
通过将作用域数组传递到 .scope 或通过将作用域作为连续参数传递,可以同时应用多个作用域.
// 这两个是等价的 Project.scope('deleted', 'activeUsers').findAll(); Project.scope(['deleted', 'activeUsers']).findAll(); SELECT * FROM projects INNER JOIN users ON projects.userId = users.id WHERE projects.deleted = true AND users.active = true
如果要将其他作用域与默认作用域一起应用,请将键 defaultScope 传递给 .scope:
Project.scope('defaultScope', 'deleted').findAll(); SELECT * FROM projects WHERE active = true AND deleted = true
当调用多个作用域时,后续作用域的键将覆盖以前的作用域(类似于 Object.assign),除了where和include,它们将被合并. 考虑两个作用域:
{ scope1: { where: { firstName: 'bob', age: { [Op.gt]: 20 } }, limit: 2 }, scope2: { where: { age: { [Op.gt]: 30 } }, limit: 10 } }
调用 .scope('scope1', 'scope2') 将产生以下查询
WHERE firstName = 'bob' AND age > 30 LIMIT 10
注意 scope2 将覆盖 limit 和 age,而 firstName 被保留.
limit,offset,order,paranoid,lock和raw字段被覆盖,而where被浅层合并(意味着相同的键将被覆盖). include 的合并策略将在后面讨论.
请注意,多个应用作用域的 attributes 键以这样的方式合并,即始终保留
attributes.exclude. 这允许合并多个作用域,并且永远不会泄漏最终作用域内的敏感字段.
将查找对象直接传递给作用域模型上的findAll(和类似的查找程序)时,适用相同的合并逻辑:
Project.scope('deleted').findAll({ where: { firstName: 'john' } }) WHERE deleted = true AND firstName = 'john'
这里的 deleted 作用域与 finder 合并. 如果我们要将 where: { firstName: 'john',
deleted: false } 传递给 finder,那么 deleted 作用域将被覆盖.
合并 include
Include 是根据包含的模型递归合并的. 这是一个非常强大的合并,在 v5 上添加,并通过示例更好地理解.
考虑四种模型:Foo,Bar,Baz和Qux,具有如下多种关联:
class Foo extends Model {} class Bar extends Model {} class Baz extends Model {} class Qux extends Model {} Foo.init({ name: Sequelize.STRING }, { sequelize }); Bar.init({ name: Sequelize.STRING }, { sequelize }); Baz.init({ name: Sequelize.STRING }, { sequelize }); Qux.init({ name: Sequelize.STRING }, { sequelize }); Foo.hasMany(Bar, { foreignKey: 'fooId' }); Bar.hasMany(Baz, { foreignKey: 'barId' }); Baz.hasMany(Qux, { foreignKey: 'bazId' });
现在,考虑Foo上定义的以下四个作用域:
{ includeEverything: { include: { model: this.Bar, include: [{ model: this.Baz, include: this.Qux }] } }, limitedBars: { include: [{ model: this.Bar, limit: 2 }] }, limitedBazs: { include: [{ model: this.Bar, include: [{ model: this.Baz, limit: 2 }] }] }, excludeBazName: { include: [{ model: this.Bar, include: [{ model: this.Baz, attributes: { exclude: ['name'] } }] }] } }
这四个作用域可以很容易地深度合并,例如通过调用 Foo.scope('includeEverything', 'limitedBars', 'limitedBazs', 'excludeBazName').findAll(),这完全等同于调用以下内容:
Foo.findAll({ include: { model: this.Bar, limit: 2, include: [{ model: this.Baz, limit: 2, attributes: { exclude: ['name'] }, include: this.Qux }] } });
观察四个作用域如何合并为一个. 根据所包含的模型合并作用域的include. 如果一个作用域包括模型A而另一个作用域包括模型B,则合并结果将包括模型A和B.另一方面,如果两个作用域包括相同的模型A,但具有不同的参数(例如嵌套include或其他属性) ,这些将以递归方式合并,如上所示.
无论应用于作用域的顺序如何,上面说明的合并都以完全相同的方式工作. 如果某个参数由两个不同的作用域设置,那么只会该顺序产生差异 - 这不是上述示例的情况,因为每个作用域都做了不同的事情.
这种合并策略的工作方式与传递给.findAll,.findOne等的参数完全相同.
关联
Sequelize 与关联有两个不同但相关的作用域概念. 差异是微妙但重要的:
- 关联作用域 允许你在获取和设置关联时指定默认属性 - 在实现多态关联时很有用. 当使用get,set,add和create相关联的模型函数时,这个作用域仅在两个模型之间的关联上被调用
- 关联模型上的作用域 允许你在获取关联时应用默认和其他作用域,并允许你在创建关联时传递作用域模型. 这些作用域都适用于模型上的常规查找和通过关联查找.
举个例子,思考模型Post和Comment. Comment与其他几个模型(图像,视频等)相关联,Comment和其他模型之间的关联是多态的,这意味着除了外键 commentable_id 之外,注释还存储一个commentable列.
可以使用 association scope 来实现多态关联:
this.Post.hasMany(this.Comment, { foreignKey: 'commentable_id', scope: { commentable: 'post' } });
当调用 post.getComments() 时,这将自动添加 WHERE commentable = 'post'. 类似地,当向帖子添加新的注释时,commentable 会自动设置为 'post'. 关联作用域是为了存活于后台,没有程序员不必担心 - 它不能被禁用. 有关更完整的多态性示例,请参阅 关联作用域
那么考虑那个Post的默认作用域只显示活动的帖子:where: { active: true }. 该作用域存在于相关联的模型(Post)上,而不是像commentable 作用域那样在关联上. 就像在调用Post.findAll() 时一样应用默认作用域,当调用 User.getPosts() 时,它也会被应用 - 这只会返回该用户的活动帖子.
要禁用默认作用域,将 scope: null 传递给 getter: User.getPosts({ scope: null }). 同样,如果要应用其他作用域,请像这样:
User.getPosts({ scope: ['scope1', 'scope2']});
如果要为关联模型上的作用域创建快捷方式,可以将作用域模型传递给关联. 考虑一个快捷方式来获取用户所有已删除的帖子:
class Post extends Model {} Post.init(attributes, { defaultScope: { where: { active: true } }, scopes: { deleted: { where: { deleted: true } } }, sequelize, }); User.hasMany(Post); // 常规 getPosts 关联 User.hasMany(Post.scope('deleted'), { as: 'deletedPosts' }); User.getPosts(); // WHERE active = true User.getDeletedPosts(); // WHERE deleted = true
扩展
extend/helper.js
// app/extend/helper.js module.exports = { // 扩展一个格式日期的方法 formatTime(val) { let d = new Date(val * 1000); return d.getFullYear(); }, };
模板中调用
<%=helper.formatTime(dateline)%>
其他地方调用
this.ctx.helper.formatTime(dateline)
中间件
1. 定义
app/middleware/getIp.js
/* options: 中间件的配置项,框架会将 app.config[${middlewareName}] 传递进来。 app: 当前应用 Application 的实例。 */ module.exports = (option, app) => { // 返回一个异步的方法 return async function(ctx, next) { // 通过option传入额外参数 console.log(option); console.log(ctx.request.ip); await next(); } };
2. 配置
config/config.default.js(配置全局中间件,所有路由都会调用)
module.exports = appInfo => { ... // 配置全局中间件 config.middleware = ['getIp']; // 注意驼峰式写法,如果中间件是a_bc.js,则要写成aBc // 配置中间件参数 config.getIp = { ceshi: 123, // 通用配置(以下是重点) enable:true, // 控制中间件是否开启。 match:'/news', // 设置只有符合某些规则的请求才会经过这个中间件(匹配路由) ignore:'/shop' // 设置符合某些规则的请求不经过这个中间件。 /** 注意: 1. match 和 ignore 不允许同时配置 2. 例如:match:'/news',只要包含/news的任何页面都生效 **/ // match 和 ignore 支持多种类型的配置方式:字符串、正则、函数(推荐) match(ctx) { // 只有 ios 设备才开启 const reg = /iphone|ipad|ipod/i; return reg.test(ctx.get('user-agent')); }, }; ... };
3. 使用
路由中使用
app/router.js
module.exports = app => { // 局部中间件(如果只需要局部调用,则不需要在config.default.js中配置) router.get('/admin/:id', app.middleware.getIp({ ceshi: "我是admin" }), controller.admin.index); };
使用 Koa 的中间件(gzip压缩)
大大提高网站的访问速度(非常有效)
以 koa-compress 为例,在 Koa 中使用时:
const koa = require('koa'); const compress = require('koa-compress'); const app = koa(); const options = { threshold: 2048 }; app.use(compress(options));
我们按照框架的规范来在应用中加载这个 Koa 的中间件:
// app/middleware/compress.js // koa-compress 暴露的接口(`(options) => middleware`)和框架对中间件要求一致 module.exports = require('koa-compress'); // config/config.default.js module.exports = { middleware: [ 'compress' ], compress: { threshold: 2048, }, };
表单提交
post
app/controller/home.js
async addInput(ctx) { await ctx.render('post'); } async add(ctx) { // 通过ctx.request.body获取post提交数据 console.log(ctx.request.body); }
app/view/post.html
<!-- 需要定义:?_csrf=<%=ctx.csrf%> --> <form action="/add?_csrf=<%=ctx.csrf%>" method="post"> <input type="text" name="username" id="username"> <input type="password" name="password" id="password"> <input type="submit" value="提交"> </form>
app/router.js
router.get('/post', controller.home.addInput); router.post('/add', controller.home.add);
cookie
// 1.设置 ctx.cookies.set('username', 'ceshi'); // 2.获取 ctx.cookies.get('username'); // 3.设置中文(加密操作 encrypt: true) // 4.设置(其他参数配置) ctx.cookies.set('username', 'ceshi', { maxAge: 1000 * 3600 * 24, // 存储24小时,单位毫秒,关闭浏览器cookie还存在 httpOnly: true, // 设置键值对是否可以被 js 访问,默认为 true,不允许被 js 访问。 signed: true, // 签名,防止用户前台修改 encrypt: true // 加密,注意:get获取时需要解密 }); // 5.获取时解密 ctx.cookies.get('username',{ encrypt: true }); // 6.清除cookie ctx.cookies.set('username', null);
session
// 1.设置 ctx.session.username = '测试'; // 2.获取 ctx.session.username // 3.默认配置(全局配置,config/config.default.js) exports.session = { key: 'EGG_SESS', // 设置cookies的key值 maxAge: 24 * 3600 * 1000, // 1 天,过期时间 httpOnly: true, // 设置键值对是否可以被 js 访问,默认为 true,不允许被 js 访问。 encrypt: true,// 加密 renew:true // 每次刷新页面都会被延期 }; // 4.动态配置 ctx.session.maxAge = 5000; // 5秒的过期时间 ctx.session.username = '测试'; // 5.清空session ctx.session.username = null;
定时任务
// app/schedule/ceshi.js var i = 1; module.exports = { // 设置定时任务的执行间隔等配置 schedule: { interval: '5s', // 每5秒执行一次 type: 'all' // 指定所有的 worker 都需要执行 }, // 任务 async task(ctx) { ++i; console.log(i); } };
API
1. context
curl
async ceshi() { // 通过ctx中的curl方法获取数据 let r = await this.ctx.curl('http://www.phonegap100.com/appapi.php?a=getPortalList&catid=20&page=1'); // 将buffer类型数据转为json类型 let { result } = JSON.parse(r.data) return result; }
常用插件
缓存
https://www.npmjs.com/package/egg-cache
https://www.npmjs.com/package/egg-redis
验证
https://github.com/temool/egg-validate-plus
加密
https://www.npmjs.com/package/egg-jwt
前端访问:header头添加:
// Authorization:"Bearer token值" Authorization:"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6MTIzLCJpYXQiOjE1NzkxOTQxNTN9.Ml5B02ZPfYo78QwJic-Jdp2LUi2_AU0RGNgPhhJH--o"