安装egg

我们推荐直接使用脚手架,只需几条简单指令,即可快速生成项目(npm >=6.1.0):

  1. mkdir egg-example && cd egg-example
  2. npm init egg --type=simple
  3. npm i

启动项目:

  1. npm run dev
  2. open http://localhost:7001

目录结构

  1. egg-project
  2. ├── package.json
  3. ├── app.js (可选)
  4. ├── agent.js (可选)
  5. ├── app(-----------核心------------)
  6. | ├── router.js(路由)
  7. ├── controller(控制器)
  8. | └── home.js
  9. ├── service (模型)
  10. | └── user.js
  11. ├── middleware (中间件)
  12. | └── response_time.js
  13. ├── schedule (可选)
  14. | └── my_task.js
  15. ├── public (静态资源)
  16. | └── reset.css
  17. ├── view (模板视图)
  18. | └── home.tpl
  19. └── extend (扩展)
  20. ├── helper.js (可选)
  21. ├── request.js (可选)
  22. ├── response.js (可选)
  23. ├── context.js (可选)
  24. ├── application.js (可选)
  25. └── agent.js (可选)
  26. ├── config
  27. | ├── plugin.js
  28. | ├── config.default.js
  29. ├── config.prod.js
  30. | ├── config.test.js (可选)
  31. | ├── config.local.js (可选)
  32. | └── config.unittest.js (可选)
  33. └── test
  34. ├── middleware
  35. | └── response_time.test.js
  36. └── controller
  37. └── home.test.js

路由相关

1. get传值

  1. // router.js
  2. router.get('/admin/:id', controller.admin.index);
  3. // controller
  4. async index(ctx) {
  5. // 获取路由get传值参数(路由:id)
  6. ctx.params;
  7. // 获取url的问号get传值参数
  8. ctx.query;
  9. }

2. 4种配置方法

  1. router.verb('path-match', app.controller.action);
  2. router.verb('router-name', 'path-match', app.controller.action);// 第一个参数可以给name
  3. router.verb('path-match', middleware1, ..., middlewareN, app.controller.action);
  4. router.verb('router-name', 'path-match', middleware1, ..., middlewareN, app.controller.action);

重定向

1. ctx

  1. async index() {
  2. this.ctx.status = 301; // 把重定向改为301
  3. this.ctx.redirect('/admin/add'); // 默认临时重定向 302
  4. }

2. 路由重定向

  1. app.router.redirect('/', '/home/index', 302);

3.路由分组

  1. // app/router.js
  2. module.exports = app => {
  3. require('./router/news')(app);
  4. require('./router/admin')(app);
  5. };
  6. // app/router/news.js
  7. module.exports = app => {
  8. app.router.get('/news/list', app.controller.news.list);
  9. app.router.get('/news/detail', app.controller.news.detail);
  10. };
  11. // app/router/admin.js
  12. module.exports = app => {
  13. app.router.get('/admin/user', app.controller.admin.user);
  14. app.router.get('/admin/log', app.controller.admin.log);
  15. };

控制器

自定义 Controller 基类

  1. // app/core/base_controller.js
  2. const { Controller } = require('egg');
  3. class BaseController extends Controller {
  4. get user() {
  5. return this.ctx.session.user;
  6. }
  7. success(data) {
  8. this.ctx.body = {
  9. success: true,
  10. data,
  11. };
  12. }
  13. notFound(msg) {
  14. msg = msg || 'not found';
  15. this.ctx.throw(404, msg);
  16. }
  17. }
  18. module.exports = BaseController;

此时在编写应用的 Controller 时,可以继承 BaseController,直接使用基类上的方法:

  1. //app/controller/post.js
  2. const Controller = require('../core/base_controller');
  3. class PostController extends Controller {
  4. async list() {
  5. const posts = await this.service.listByUser(this.user);
  6. this.success(posts);
  7. }
  8. }

模板引擎

1. 安装和使用ejs

(1)安装:

  1. npm i egg-view-ejs --save

(2)配置:/config

config/config.default.js

  1. module.exports = appInfo => {
  2. ...
  3. config.view = {
  4. mapping: {
  5. '.html': 'ejs',
  6. },
  7. };
  8. ...
  9. };

config/plugin.js

  1. module.exports = {
  2. // 配置ejs
  3. ejs: {
  4. enable: true,
  5. package: 'egg-view-ejs',
  6. }
  7. };

(3)使用

app/controller

  1. async index() {
  2. const { ctx } = this;
  3. // 渲染变量
  4. let msg = "测试内容";
  5. let list = [1, 2, 3, 4, 5, 6];
  6. // 渲染模板(render需要加await)
  7. await ctx.render('index', {
  8. msg,
  9. list
  10. });
  11. }

app/view/index.html

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <meta http-equiv="X-UA-Compatible" content="ie=edge">
  7. <title>Document</title>
  8. </head>
  9. <body>
  10. <!--渲染变量-->
  11. <%=msg%>
  12. <ul>
  13. <% for(var i=0; i < list.length; i++){ %>
  14. <li>
  15. <%=list[i]%>
  16. </li>
  17. <% } %>
  18. </ul>
  19. <!--加载 app/public 下的资源文件-->
  20. <img src="/public/images/find.png">
  21. </body>
  22. </html>

服务(模型)

控制器调用 home 模型的 ceshi 方法

  1. await this.service.home.ceshi();

模型之间相互调用(同上)

模型和数据库

配置和创建迁移文件

配置

  1. 安装并配置egg-sequelize插件(它会辅助我们将定义好的 Model 对象加载到 app 和 ctx 上)和mysql2模块:
  1. npm install --save egg-sequelize mysql2
  1. config/plugin.js中引入 egg-sequelize 插件
  1. exports.sequelize = {
  2. enable: true,
  3. package: 'egg-sequelize',
  4. };
  1. config/config.default.js
  1. config.sequelize = {
  2. dialect: 'mysql',
  3. host: '127.0.0.1',
  4. username: 'root',
  5. password: 'root',
  6. port: 3306,
  7. database: 'friends',
  8. // 中国时区
  9. timezone: '+08:00',
  10. define: {
  11. // 取消数据表名复数
  12. freezeTableName: true,
  13. // 自动写入时间戳 created_at updated_at
  14. timestamps: true,
  15. // 字段生成软删除时间戳 deleted_at
  16. paranoid: true,
  17. createdAt: 'created_at',
  18. updatedAt: 'updated_at',
  19. deletedAt: 'deleted_at',
  20. // 所有驼峰命名格式化
  21. underscored: true
  22. }
  23. };
  1. sequelize 提供了sequelize-cli工具来实现Migrations,我们也可以在 egg 项目中引入 sequelize-cli。
  1. npm install --save-dev sequelize-cli
  1. egg 项目中,我们希望将所有数据库 Migrations 相关的内容都放在database目录下,所以我们在项目根目录下新建一个.sequelizerc配置文件:
  1. 'use strict';
  2. const path = require('path');
  3. module.exports = {
  4. config: path.join(__dirname, 'database/config.json'),
  5. 'migrations-path': path.join(__dirname, 'database/migrations'),
  6. 'seeders-path': path.join(__dirname, 'database/seeders'),
  7. 'models-path': path.join(__dirname, 'app/model'),
  8. };
  1. 初始化 Migrations 配置文件和目录
  1. npx sequelize init:config
  2. npx sequelize init:migrations
  3. // npx sequelize init:models
  1. 行完后会生成database/config.json文件和database/migrations目录,我们修改一下database/config.json中的内容,将其改成我们项目中使用的数据库配置:
  1. {
  2. "development": {
  3. "username": "root",
  4. "password": null,
  5. "database": "test",
  6. "host": "127.0.0.1",
  7. "dialect": "mysql",
  8. "timezone": "+08:00"
  9. }
  10. }
  1. 创建数据库
  1. npx sequelize db:create

创建数据迁移表

  1. npx sequelize migration:generate --name=init-users

1.执行完命令后,会在database / migrations / 目录下生成数据表迁移文件,然后定义

  1. 'use strict';
  2. module.exports = {
  3. up: async (queryInterface, Sequelize) => {
  4. const { INTEGER, STRING, DATE, ENUM } = Sequelize;
  5. // 创建表
  6. await queryInterface.createTable('users', {
  7. id: { type: INTEGER(20).UNSIGNED, primaryKey: true, autoIncrement: true },
  8. username: { type: STRING(30), allowNull: false, defaultValue: '', comment: '用户名称', unique: true},
  9. email: { type: STRING(160), allowNull: false, defaultValue: '', comment: '用户邮箱', unique: true },
  10. password: { type: STRING(200), allowNull: false, defaultValue: '' },
  11. avatar_url: { type: STRING(200), allowNull: true, defaultValue: '' },
  12. mobile: { type: STRING(20), allowNull: false, defaultValue: '', comment: '用户手机', unique: true },
  13. prifix: { type: STRING(32), allowNull: false, defaultValue: '' },
  14. abstract: { type: STRING(255), allowNull: true, defaultValue: '' },
  15. role_id:{
  16. type: INTEGER,
  17. // 定义外键(重要)
  18. references: {
  19. model: 'users', // 对应表名称(数据表名称)
  20. key: 'id' // 对应表的主键
  21. },
  22. onUpdate: 'restrict', // 更新时操作
  23. onDelete: 'cascade' // 删除时操作
  24. },
  25. gender: { type: ENUM, values: ['男','女','保密'], allowNull: true, defaultValue: '男', comment: '用户性别'},
  26. created_at: DATE,
  27. updated_at: DATE
  28. }, { engine: 'MYISAM' });
  29. // 添加索引
  30. queryInterface.addIndex('users', ['gender']);
  31. // 添加唯一索引
  32. queryInterface.addIndex('users', {
  33. name: "name", // 索引名称
  34. unique: true, // 唯一索引
  35. fields: ['name'] // 索引对应字段
  36. });
  37. },
  38. down: async queryInterface => {
  39. await queryInterface.dropTable('users')
  40. }
  41. };
  • 执行 migrate 进行数据库变更
  1. # 升级数据库
  2. npx sequelize db:migrate
  3. # 如果有问题需要回滚,可以通过 `db:migrate:undo` 回退一个变更
  4. # npx sequelize db:migrate:undo
  5. # 可以通过 `db:migrate:undo:all` 回退到初始状态
  6. # npx sequelize db:migrate:undo:all

已创建新增字段

1.创建迁移文件:

  1. npx sequelize migration:generate --name=user-addcolumn

2.执行完命令后,会在database / migrations / 目录下生成数据表迁移文件,然后定义

  1. 'use strict';
  2. module.exports = {
  3. up: (queryInterface, Sequelize) => {
  4. return queryInterface.sequelize.transaction((t) => {
  5. return Promise.all([
  6. queryInterface.addColumn('user', 'role_id', {
  7. type: Sequelize.INTEGER
  8. }, { transaction: t }),
  9. queryInterface.addColumn('user', 'ceshi', {
  10. type: Sequelize.STRING,
  11. }, { transaction: t })
  12. ])
  13. })
  14. },
  15. down: (queryInterface, Sequelize) => {
  16. return queryInterface.sequelize.transaction((t) => {
  17. return Promise.all([
  18. queryInterface.removeColumn('user', 'role_id', { transaction: t }),
  19. queryInterface.removeColumn('user', 'ceshi', { transaction: t })
  20. ])
  21. })
  22. }
  23. };

3.执行 migrate 进行数据库变更

  1. npx sequelize db:migrate

创建模型

  1. // app / model / user.js
  2. 'use strict';
  3. module.exports = app => {
  4. const { STRING, INTEGER, DATE } = app.Sequelize;
  5. // 配置(重要:一定要配置详细,一定要!!!)
  6. const User = app.model.define('user', {
  7. id: { type: INTEGER, primaryKey: true, autoIncrement: true },
  8. name: STRING(30),
  9. age: INTEGER,
  10. created_at: DATE,
  11. updated_at: DATE,
  12. },{
  13. timestamps: true, // 是否自动写入时间戳
  14. tableName: 'users', // 自定义数据表名称
  15. });
  16. return User;
  17. };

这个 Model 就可以在 Controller 和 Service 中通过 app.model.User 或者 ctx.model.User 访问到了,例如我们编写 app/controller/users.js

  1. // app/controller/users.js
  2. const Controller = require('egg').Controller;
  3. function toInt(str) {
  4. if (typeof str === 'number') return str;
  5. if (!str) return str;
  6. return parseInt(str, 10) || 0;
  7. }
  8. class UserController extends Controller {
  9. async index() {
  10. const ctx = this.ctx;
  11. const query = { limit: toInt(ctx.query.limit), offset: toInt(ctx.query.offset) };
  12. ctx.body = await ctx.model.User.findAll(query);
  13. }
  14. async show() {
  15. const ctx = this.ctx;
  16. ctx.body = await ctx.model.User.findByPk(toInt(ctx.params.id));
  17. }
  18. async create() {
  19. const ctx = this.ctx;
  20. const { name, age } = ctx.request.body;
  21. const user = await ctx.model.User.create({ name, age });
  22. ctx.status = 201;
  23. ctx.body = user;
  24. }
  25. async update() {
  26. const ctx = this.ctx;
  27. const id = toInt(ctx.params.id);
  28. const user = await ctx.model.User.findByPk(id);
  29. if (!user) {
  30. ctx.status = 404;
  31. return;
  32. }
  33. const { name, age } = ctx.request.body;
  34. await user.update({ name, age });
  35. ctx.body = user;
  36. }
  37. async destroy() {
  38. const ctx = this.ctx;
  39. const id = toInt(ctx.params.id);
  40. const user = await ctx.model.User.findByPk(id);
  41. if (!user) {
  42. ctx.status = 404;
  43. return;
  44. }
  45. await user.destroy();
  46. ctx.status = 200;
  47. }
  48. }
  49. module.exports = UserController;

最后我们将这个 controller 挂载到路由上:

  1. // app/router.js
  2. module.exports = app => {
  3. const { router, controller } = app;
  4. router.resources('users', '/users', controller.users);
  5. };

针对 users 表的 CURD 操作的接口就开发完了

模型其他参数

  1. // 配置(重要)
  2. const User = app.model.define('user', {
  3. id: { type: INTEGER, primaryKey: true, autoIncrement: true },
  4. name: STRING(30),
  5. age: INTEGER,
  6. created_at: DATE,
  7. updated_at: DATE,
  8. },{
  9. // 自定义表名
  10. 'freezeTableName': true,
  11. 'tableName': 'xyz_users',
  12. // 是否需要增加createdAt、updatedAt、deletedAt字段
  13. 'timestamps': true,
  14. // 不需要createdAt字段
  15. 'createdAt': false,
  16. // 将updatedAt字段改个名
  17. 'updatedAt': 'utime',
  18. // 将deletedAt字段改名
  19. // 同时需要设置paranoid为true(此种模式下,删除数据时不会进行物理删除,而是设置deletedAt为当前时间
  20. 'deletedAt': 'dtime',
  21. 'paranoid': true,
  22. });

sequelize 命令

命令 含义
sequelize db:migrate 运行迁移文件
sequelize db:migrate:status 列出所有迁移的状态
sequelize db:migrate:undo 隔离数据库:迁移:撤消
sequelize db:migrate:undo:all 还原所有运行的迁移
sequelize db:create 创建由配置指定的数据库
sequelize db:drop 删除由配置指定的数据库

外键约束(重要)

  1. // 迁移文件
  2. queryInterface.addConstraint('tableName', ['user_id'], {
  3. type: 'foreign key',
  4. name: 'user_id',
  5. references: { //Required field
  6. table: 'users',
  7. field: 'id'
  8. },
  9. onDelete: 'cascade',
  10. onUpdate: 'cascade'
  11. });

创建第一个种子

假设我们希望在默认情况下将一些数据插入到几个表中. 如果我们跟进前面的例子,我们可以考虑为 User 表创建演示用户.

要管理所有数据迁移,你可以使用 seeders. 种子文件是数据的一些变化,可用于使用样本数据或测试数据填充数据库表.

让我们创建一个种子文件,它会将一个演示用户添加到我们的 User 表中.

  1. npx sequelize seed:generate --name demo-user

这个命令将会在 seeders 文件夹中创建一个种子文件.文件名看起来像是 XXXXXXXXXXXXXX-demo-user.js,它遵循相同的 up/down 语义,如迁移文件.

现在我们应该编辑这个文件,将演示用户插入User表.

  1. 'use strict';
  2. module.exports = {
  3. up: (queryInterface, Sequelize) => {
  4. return queryInterface.bulkInsert('Users', [{
  5. firstName: 'John',
  6. lastName: 'Doe',
  7. email: 'demo@demo.com',
  8. createdAt: new Date(),
  9. updatedAt: new Date()
  10. }], {});
  11. },
  12. down: (queryInterface, Sequelize) => {
  13. return queryInterface.bulkDelete('Users', null, {});
  14. }
  15. };

运行种子

在上一步中,你创建了一个种子文件. 但它还没有保存到数据库. 为此,我们需要运行一个简单的命令.

  1. npx sequelize db:seed:all

这将执行该种子文件,你将有一个演示用户插入 User 表.

注意: seeders 执行不会存储在任何使用 SequelizeMeta 表的迁移的地方. 如果你想覆盖这个,请阅读 存储 部分

撤销种子

Seeders 如果使用了任何存储那么就可以被撤消. 有两个可用的命令

如果你想撤消最近的种子

  1. npx sequelize db:seed:undo

如果你想撤消特定的种子

  1. npx sequelize db:seed:undo --seed name-of-seed-as-in-data

如果你想撤消所有的种子

  1. npx sequelize db:seed:undo:all

关联操作

一对一

模型层:

  1. // 一个用户对应一个用户资料
  2. // app/model/user.js
  3. module.exports = app => {
  4. const { STRING, INTEGER, DATE } = app.Sequelize;
  5. const User = app.model.define('user', {
  6. id: { type: INTEGER, primaryKey: true, autoIncrement: true },
  7. name: STRING(30),
  8. age: INTEGER,
  9. created_at: DATE,
  10. updated_at: DATE,
  11. });
  12. // 关联关系
  13. User.associate = function(models) {
  14. // 关联用户资料 一对一
  15. User.hasOne(app.model.Userinfo);
  16. }
  17. return User;
  18. };
  19. // app/model/userinfo.js
  20. module.exports = app => {
  21. const { STRING, INTEGER, DATE } = app.Sequelize;
  22. const userinfo = app.model.define('userinfo', {
  23. nickname: STRING,
  24. user_id: INTEGER
  25. }, {});
  26. // 关联用户表
  27. userinfo.associate = function(models) {
  28. app.model.Userinfo.belongsTo(app.model.User);
  29. };
  30. return userinfo;
  31. };

控制器调用:

  1. // app/controller/users.js
  2. // 显示单条
  3. async show() {
  4. // 根据主键查询 查询一条用findOne
  5. this.ctx.body = await this.ctx.model.User.findOne({
  6. // 主表查询字段限制
  7. attributes:['name'],
  8. // 关联查询
  9. include: [{
  10. // 需要查询的模型
  11. model: this.app.model.Userinfo,
  12. // 副表查询的字段
  13. attributes: ['nickname']
  14. }],
  15. // 主表条件
  16. where: {
  17. id: 3
  18. }
  19. });
  20. }

一对多

  1. class City extends Model {}
  2. City.init({ countryCode: Sequelize.STRING }, { sequelize, modelName: 'city' });
  3. class Country extends Model {}
  4. Country.init({ isoCode: Sequelize.STRING }, { sequelize, modelName: 'country' });
  5. // 在这里,我们可以根据国家代码连接国家和城市
  6. Country.hasMany(City, {foreignKey: 'countryCode', sourceKey: 'isoCode'});
  7. City.belongsTo(Country, {foreignKey: 'countryCode', targetKey: 'isoCode'});

多对多

  1. User.belongsToMany(Project, { as: 'Tasks', through: 'worker_tasks', foreignKey: 'userId' })
  2. Project.belongsToMany(User, { as: 'Workers', through: 'worker_tasks', foreignKey: 'projectId' })

关联常用操作

  1. // 获取关联模型对象,n对一不需要加s
  2. let userinfo = await user.getUserinfo();
  3. // n对多需要加s
  4. await user.getPosts({
  5. attributes: ['title'],
  6. where: {
  7. id: 3
  8. }
  9. });
  10. // 关联操作
  11. // 1:用户创建文章(一对多)
  12. await this.ctx.model.Post.create({
  13. title: "第一篇文章",
  14. user_id: user.id
  15. });
  16. // 2.获取当前用户所有文章
  17. await user.getPosts();
  18. await user.getPosts({
  19. attributes: ['id'],
  20. where:{
  21. title:"测试"
  22. }
  23. });
  24. // 3:用户删除文章(一对多)
  25. // (1) 获取当前用户的所有文章
  26. let posts = await user.getPosts({
  27. attributes: ['id']
  28. });
  29. posts = posts.map(v => v.id);
  30. await this.ctx.model.Post.destroy({
  31. where: {
  32. id: posts
  33. }
  34. });
  35. // 场景三:用户关注话题(多对多)
  36. await this.ctx.model.TopicUser.bulkCreate([{
  37. user_id: user.id,
  38. topic_id: 1
  39. },{
  40. user_id: user.id,
  41. topic_id: 2
  42. }]);
  43. // 用户关注话题(多对多)
  44. await this.ctx.model.TopicUser.destroy({
  45. where: {
  46. user_id: user.id,
  47. topic_id: [1, 2]
  48. }
  49. });

获取器和修改器

模型层

  1. // app/model/user.js
  2. module.exports = app => {
  3. const { STRING, INTEGER, DATE } = app.Sequelize;
  4. const User = app.model.define('user', {
  5. id: { type: INTEGER, primaryKey: true, autoIncrement: true },
  6. name: {
  7. type: STRING(30),
  8. // 单独字段的getter,查询时都会调用
  9. // this.getDataValue('name') 获取原始值
  10. get() {
  11. const age = this.getDataValue('age');
  12. return this.getDataValue('name') + '年龄:' + age;
  13. }
  14. },
  15. age: {
  16. type: INTEGER,
  17. // 单独字段的setter,新增和更新时调用
  18. // this.setDataValue('name') 设置原始值
  19. set(val) {
  20. this.setDataValue('age', val * 10);
  21. }
  22. },
  23. created_at: DATE,
  24. updated_at: DATE,
  25. });
  26. // 关联用户资料
  27. User.associate = function(models) {
  28. app.model.User.hasOne(app.model.Userinfo);
  29. }
  30. return User;
  31. };

控制器层

  1. async show() {
  2. // 根据主键查询
  3. let user = await this.ctx.model.User.findOne({
  4. where: {
  5. id: 3
  6. }
  7. });
  8. // 获取原始值 user.getDataValue('name')
  9. this.ctx.body = user.getDataValue('name')
  10. }

模型钩子

模型层

  1. module.exports = app => {
  2. ...
  3. // 钩子
  4. // 查询前
  5. User.beforeFind((user, option) => {
  6. console.log('查询前');
  7. });
  8. // 查询后
  9. User.afterFind((user, option) => {
  10. console.log('查询后');
  11. });
  12. // 新增前
  13. User.beforeCreate((user, option) => {
  14. console.log('新增前');
  15. });
  16. // 新增后
  17. User.afterCreate((user, option) => {
  18. console.log('新增后');
  19. });
  20. // 修改前
  21. User.beforeUpdate((user, option) => {
  22. console.log('修改前');
  23. });
  24. // 修改后
  25. User.afterUpdate((user, option) => {
  26. console.log('修改后'); // 真正修改才会触发,数据相同不会触发
  27. });
  28. // 删除前
  29. User.beforeDestroy((user, option) => {
  30. console.log('删除前');
  31. });
  32. // 删除后
  33. User.afterDestroy((user, option) => {
  34. console.log('删除后');
  35. });
  36. return User;
  37. };

查询

主键查询

  1. Model.findByPk(1)

查找不存在则创建

方法 findOrCreate 可用于检查数据库中是否已存在某个元素. 如果是这种情况,则该方法将生成相应的实例. 如果元素不存在,将会被创建.

如果是这种情况,则该方法将导致相应的实例. 如果元素不存在,将会被创建.

假设我们有一个空的数据库,一个 User 模型有一个 usernamejob.

  1. User
  2. .findOrCreate({
  3. where: {
  4. username: 'sdepold'
  5. },
  6. defaults: {
  7. job: 'Technical Lead JavaScript'
  8. }
  9. })
  10. . then(([user, created]) => {
  11. console.log(user.get({
  12. plain: true
  13. }))
  14. console.log(created)
  15. /*
  16. findOrCreate 返回一个包含已找到或创建的对象的数组,找到或创建的对象和一个布尔值,如果创建一个新对象将为true,否则为false,像这样:
  17. [ {
  18. username: 'sdepold',
  19. job: 'Technical Lead JavaScript',
  20. id: 1,
  21. createdAt: Fri Mar 22 2013 21: 28: 34 GMT + 0100(CET),
  22. updatedAt: Fri Mar 22 2013 21: 28: 34 GMT + 0100(CET)
  23. },
  24. true ]
  25. 在上面的例子中,第三行的数组将分成2部分,并将它们作为参数传递给回调函数,在这种情况下将它们视为 "user" 和 "created" .(所以“user”将是返回数组的索引0的对象,并且 "created" 将等于 "true".)
  26. */
  27. })

代码创建了一个新的实例. 所以当我们已经有一个实例了 …

  1. User.create({ username: 'fnord', job: 'omnomnom' })
  2. .then(() => User.findOrCreate({
  3. where: {
  4. username: 'fnord'
  5. },
  6. defaults: {
  7. job: 'something else'
  8. }
  9. }))
  10. .then(([user, created]) => {
  11. console.log(user.get({
  12. plain: true
  13. }))
  14. console.log(created)
  15. /*
  16. 在这个例子中,findOrCreate 返回一个如下的数组:
  17. [ {
  18. username: 'fnord',
  19. job: 'omnomnom',
  20. id: 2,
  21. createdAt: Fri Mar 22 2013 21: 28: 34 GMT + 0100(CET),
  22. updatedAt: Fri Mar 22 2013 21: 28: 34 GMT + 0100(CET)
  23. },
  24. false
  25. ]
  26. 由findOrCreate返回的数组通过第三行的数组扩展为两部分,并且这些部分将作为2个参数传递给回调函数,在这种情况下将其视为 "user" 和 "created" .(所以“user”将是返回数组的索引0的对象,并且 "created" 将等于 "false".)
  27. */
  28. })

…现有条目将不会更改. 看到第二个用户的 “job”,并且实际上创建操作是假的.

查找并计数

findAndCountAll - 在数据库中搜索多个元素,返回数据和总计数

这是一个方便的方法,它结合了 findAllcount(见下文),当处理与分页相关的查询时,这是有用的,你想用 limitoffset 检索数据,但也需要知道总数与查询匹配的记录数:

处理程序成功将始终接收具有两个属性的对象:

  • count - 一个整数,总数记录匹配where语句和关联的其它过滤器
  • rows - 一个数组对象,记录在limit和offset范围内匹配where语句和关联的其它过滤器,
  1. Project
  2. .findAndCountAll({
  3. where: {
  4. title: {
  5. [Op.like]: 'foo%'
  6. }
  7. },
  8. offset: 10,
  9. limit: 2
  10. })
  11. .then(result => {
  12. console.log(result.count);
  13. console.log(result.rows);
  14. });

它支持 include. 只有标记为 required 的 include 将被添加到计数部分:

假设你想查找附有个人资料的所有用户:

  1. User.findAndCountAll({
  2. include: [
  3. { model: Profile, required: true}
  4. ],
  5. limit: 3
  6. });

因为 Profile 的 include 有 required 设置,这将导致内部连接,并且只有具有 profile 的用户将被计数. 如果我们从 include 中删除required,那么有和没有 profile 的用户都将被计数. 在include中添加一个 where 语句会自动使它成为 required:

  1. User.findAndCountAll({
  2. include: [
  3. { model: Profile, where: { active: true }}
  4. ],
  5. limit: 3
  6. });

上面的查询只会对具有 active profile 的用户进行计数,因为在将 where 语句添加到 include 时,required 被隐式设置为 true.

传递给 findAndCountAll 的 options 对象与 findAll 相同(如下所述).

查询多个(常用)

  1. // 找到多个条目
  2. Project.findAll().then(projects => {
  3. // projects 将是所有 Project 实例的数组
  4. })
  5. // 搜索特定属性 - 使用哈希
  6. Project.findAll({ where: { name: 'A Project' } }).then(projects => {
  7. // projects将是一个具有指定 name 的 Project 实例数组
  8. })
  9. // 在特定范围内进行搜索
  10. Project.findAll({ where: { id: [1,2,3] } }).then(projects => {
  11. // projects将是一系列具有 id 1,2 或 3 的项目
  12. // 这实际上是在做一个 IN 查询
  13. })
  14. Project.findAll({
  15. where: {
  16. id: {
  17. [Op.and]: {a: 5}, // 且 (a = 5)
  18. [Op.or]: [{a: 5}, {a: 6}], // (a = 5 或 a = 6)
  19. [Op.gt]: 6, // id > 6
  20. [Op.gte]: 6, // id >= 6
  21. [Op.lt]: 10, // id < 10
  22. [Op.lte]: 10, // id <= 10
  23. [Op.ne]: 20, // id != 20
  24. [Op.between]: [6, 10], // 在 6 和 10 之间
  25. [Op.notBetween]: [11, 15], // 不在 11 和 15 之间
  26. [Op.in]: [1, 2], // 在 [1, 2] 之中
  27. [Op.notIn]: [1, 2], // 不在 [1, 2] 之中
  28. [Op.like]: '%hat', // 包含 '%hat'
  29. [Op.notLike]: '%hat', // 不包含 '%hat'
  30. [Op.iLike]: '%hat', // 包含 '%hat' (不区分大小写) (仅限 PG)
  31. [Op.notILike]: '%hat', // 不包含 '%hat' (仅限 PG)
  32. [Op.overlap]: [1, 2], // && [1, 2] (PG数组重叠运算符)
  33. [Op.contains]: [1, 2], // @> [1, 2] (PG数组包含运算符)
  34. [Op.contained]: [1, 2], // <@ [1, 2] (PG数组包含于运算符)
  35. [Op.any]: [2,3], // 任何数组[2, 3]::INTEGER (仅限 PG)
  36. },
  37. status: {
  38. [Op.not]: false, // status 不为 FALSE
  39. }
  40. }
  41. })

复合过滤 / OR / NOT 查询

你可以使用多层嵌套的 AND,OR 和 NOT 条件进行一个复合的 where 查询. 为了做到这一点,你可以使用 or , andnot 运算符:

  1. Project.findOne({
  2. where: {
  3. name: 'a project',
  4. [Op.or]: [
  5. { id: [1,2,3] },
  6. { id: { [Op.gt]: 10 } }
  7. ]
  8. }
  9. })
  10. Project.findOne({
  11. where: {
  12. name: 'a project',
  13. id: {
  14. [Op.or]: [
  15. [1,2,3],
  16. { [Op.gt]: 10 }
  17. ]
  18. }
  19. }
  20. })

这两段代码将生成以下内容:

  1. SELECT *
  2. FROM `Projects`
  3. WHERE (
  4. `Projects`.`name` = 'a project'
  5. AND (`Projects`.`id` IN (1,2,3) OR `Projects`.`id` > 10)
  6. )
  7. LIMIT 1;

not 示例:

  1. Project.findOne({
  2. where: {
  3. name: 'a project',
  4. [Op.not]: [
  5. { id: [1,2,3] },
  6. { array: { [Op.contains]: [3,4,5] } }
  7. ]
  8. }
  9. });

将生成:

  1. SELECT *
  2. FROM `Projects`
  3. WHERE (
  4. `Projects`.`name` = 'a project'
  5. AND NOT (`Projects`.`id` IN (1,2,3) OR `Projects`.`array` @> ARRAY[3,4,5]::INTEGER[])
  6. )
  7. LIMIT 1;

用限制,偏移,顺序和分组操作数据集

要获取更多相关数据,可以使用限制,偏移,顺序和分组:

  1. // 限制查询的结果
  2. Project.findAll({ limit: 10 })
  3. // 跳过前10个元素
  4. Project.findAll({ offset: 10 })
  5. // 跳过前10个元素,并获取2个
  6. Project.findAll({ offset: 10, limit: 2 })

分组和排序的语法是相同的,所以下面只用一个单独的例子来解释分组,而其余的则是排序. 你下面看到的所有内容也可以对分组进行

  1. Project.findAll({order: [['title', 'DESC']]})
  2. // 生成 ORDER BY title DESC
  3. Project.findAll({group: 'name'})
  4. // 生成 GROUP BY name

请注意,在上述两个示例中,提供的字符串逐字插入到查询中,所以不会转义列名称. 当你向 order / group 提供字符串时,将始终如此. 如果要转义列名,你应该提供一个参数数组,即使你只想通过单个列进行 order / group

  1. something.findOne({
  2. order: [
  3. // 将返回 `name`
  4. ['name'],
  5. // 将返回 `username` DESC
  6. ['username', 'DESC'],
  7. // 将返回 max(`age`)
  8. sequelize.fn('max', sequelize.col('age')),
  9. // 将返回 max(`age`) DESC
  10. [sequelize.fn('max', sequelize.col('age')), 'DESC'],
  11. // 将返回 otherfunction(`col1`, 12, 'lalala') DESC
  12. [sequelize.fn('otherfunction', sequelize.col('col1'), 12, 'lalala'), 'DESC'],
  13. // 将返回 otherfunction(awesomefunction(`col`)) DESC,这个嵌套是可以无限的!
  14. [sequelize.fn('otherfunction', sequelize.fn('awesomefunction', sequelize.col('col'))), 'DESC']
  15. ]
  16. })

回顾一下,order / group数组的元素可以是以下内容:

  • String - 将被引用
  • Array - 第一个元素将被引用,第二个将被逐字地追加
  • Object -

    • raw 将被添加逐字引用
    • 如果未设置 raw,一切都被忽略,查询将失败
  • Sequelize.fn 和 Sequelize.col 返回函数和引用的列名

字段过滤

想要只选择某些属性,可以使用 attributes 选项. 通常是传递一个数组:

  1. Model.findAll({
  2. attributes: ['foo', 'bar']
  3. });

SELECT foo, bar …

属性可以使用嵌套数组来重命名:

  1. Model.findAll({
  2. attributes: ['foo', ['bar', 'baz']]
  3. });

SELECT foo, bar AS baz …

也可以使用 sequelize.fn 来进行聚合:

  1. Model.findAll({
  2. attributes: [[sequelize.fn('COUNT', sequelize.col('hats')), 'no_hats']]
  3. });

SELECT COUNT(hats) AS no_hats …

使用聚合功能时,必须给它一个别名,以便能够从模型中访问它. 在上面的例子中,你可以使用 instance.get('no_hats') 获得帽子数量.

有时,如果你只想添加聚合,则列出模型的所有属性可能令人厌烦:

  1. // This is a tiresome way of getting the number of hats...
  2. Model.findAll({
  3. attributes: ['id', 'foo', 'bar', 'baz', 'quz', [sequelize.fn('COUNT', sequelize.col('hats')), 'no_hats']]
  4. });
  5. // This is shorter, and less error prone because it still works if you add / remove attributes
  6. Model.findAll({
  7. attributes: { include: [[sequelize.fn('COUNT', sequelize.col('hats')), 'no_hats']] }
  8. });
  9. SELECT id, foo, bar, baz, quz, COUNT(hats) AS no_hats ...

同样,它也可以排除一些指定的表字段:

  1. Model.findAll({
  2. attributes: { exclude: ['baz'] }
  3. });
  4. SELECT id, foo, bar, quz ...

Where

无论你是通过 findAll/find 或批量 updates/destroys 进行查询,都可以传递一个 where 对象来过滤查询.

where 通常用 attribute:value 键值对获取一个对象,其中 value 可以是匹配等式的数据或其他运算符的键值对象.

也可以通过嵌套 orand 运算符 的集合来生成复杂的 AND/OR 条件.

基础

  1. const Op = Sequelize.Op;
  2. Post.findAll({
  3. where: {
  4. authorId: 2
  5. }
  6. });
  7. // SELECT * FROM post WHERE authorId = 2
  8. Post.findAll({
  9. where: {
  10. authorId: 12,
  11. status: 'active'
  12. }
  13. });
  14. // SELECT * FROM post WHERE authorId = 12 AND status = 'active';
  15. Post.findAll({
  16. where: {
  17. [Op.or]: [{authorId: 12}, {authorId: 13}]
  18. }
  19. });
  20. // SELECT * FROM post WHERE authorId = 12 OR authorId = 13;
  21. Post.findAll({
  22. where: {
  23. authorId: {
  24. [Op.or]: [12, 13]
  25. }
  26. }
  27. });
  28. // SELECT * FROM post WHERE authorId = 12 OR authorId = 13;
  29. Post.destroy({
  30. where: {
  31. status: 'inactive'
  32. }
  33. });
  34. // DELETE FROM post WHERE status = 'inactive';
  35. Post.update({
  36. updatedAt: null,
  37. }, {
  38. where: {
  39. deletedAt: {
  40. [Op.ne]: null
  41. }
  42. }
  43. });
  44. // UPDATE post SET updatedAt = null WHERE deletedAt NOT NULL;
  45. Post.findAll({
  46. where: sequelize.where(sequelize.fn('char_length', sequelize.col('status')), 6)
  47. });
  48. // SELECT * FROM post WHERE char_length(status) = 6;

操作符

Sequelize 可用于创建更复杂比较的符号运算符 -

  1. const Op = Sequelize.Op
  2. [Op.and]: {a: 5} // 且 (a = 5)
  3. [Op.or]: [{a: 5}, {a: 6}] // (a = 5 或 a = 6)
  4. [Op.gt]: 6, // id > 6
  5. [Op.gte]: 6, // id >= 6
  6. [Op.lt]: 10, // id < 10
  7. [Op.lte]: 10, // id <= 10
  8. [Op.ne]: 20, // id != 20
  9. [Op.eq]: 3, // = 3
  10. [Op.not]: true, // 不是 TRUE
  11. [Op.between]: [6, 10], // 在 6 和 10 之间
  12. [Op.notBetween]: [11, 15], // 不在 11 和 15 之间
  13. [Op.in]: [1, 2], // 在 [1, 2] 之中
  14. [Op.notIn]: [1, 2], // 不在 [1, 2] 之中
  15. [Op.like]: '%hat', // 包含 '%hat'
  16. [Op.notLike]: '%hat' // 不包含 '%hat'
  17. [Op.iLike]: '%hat' // 包含 '%hat' (不区分大小写) (仅限 PG)
  18. [Op.notILike]: '%hat' // 不包含 '%hat' (仅限 PG)
  19. [Op.startsWith]: 'hat' // 类似 'hat%'
  20. [Op.endsWith]: 'hat' // 类似 '%hat'
  21. [Op.substring]: 'hat' // 类似 '%hat%'
  22. [Op.regexp]: '^[h|a|t]' // 匹配正则表达式/~ '^[h|a|t]' (仅限 MySQL/PG)
  23. [Op.notRegexp]: '^[h|a|t]' // 不匹配正则表达式/!~ '^[h|a|t]' (仅限 MySQL/PG)
  24. [Op.iRegexp]: '^[h|a|t]' // ~* '^[h|a|t]' (仅限 PG)
  25. [Op.notIRegexp]: '^[h|a|t]' // !~* '^[h|a|t]' (仅限 PG)
  26. [Op.like]: { [Op.any]: ['cat', 'hat']} // 包含任何数组['cat', 'hat'] - 同样适用于 iLike 和 notLike
  27. [Op.overlap]: [1, 2] // && [1, 2] (PG数组重叠运算符)
  28. [Op.contains]: [1, 2] // @> [1, 2] (PG数组包含运算符)
  29. [Op.contained]: [1, 2] // <@ [1, 2] (PG数组包含于运算符)
  30. [Op.any]: [2,3] // 任何数组[2, 3]::INTEGER (仅限PG)
  31. [Op.col]: 'user.organization_id' // = 'user'.'organization_id', 使用数据库语言特定的列标识符, 本例使用 PG

范围选项

所有操作符都支持支持的范围类型查询.

请记住,提供的范围值也可以定义绑定的 inclusion/exclusion.

  1. // 所有上述相等和不相等的操作符加上以下内容:
  2. [Op.contains]: 2 // @> '2'::integer (PG range contains element operator)
  3. [Op.contains]: [1, 2] // @> [1, 2) (PG range contains range operator)
  4. [Op.contained]: [1, 2] // <@ [1, 2) (PG range is contained by operator)
  5. [Op.overlap]: [1, 2] // && [1, 2) (PG range overlap (have points in common) operator)
  6. [Op.adjacent]: [1, 2] // -|- [1, 2) (PG range is adjacent to operator)
  7. [Op.strictLeft]: [1, 2] // << [1, 2) (PG range strictly left of operator)
  8. [Op.strictRight]: [1, 2] // >> [1, 2) (PG range strictly right of operator)
  9. [Op.noExtendRight]: [1, 2] // &< [1, 2) (PG range does not extend to the right of operator)
  10. [Op.noExtendLeft]: [1, 2] // &> [1, 2) (PG range does not extend to the left of operator)

组合

  1. {
  2. rank: {
  3. [Op.or]: {
  4. [Op.lt]: 1000,
  5. [Op.eq]: null
  6. }
  7. }
  8. }
  9. // rank < 1000 OR rank IS NULL
  10. {
  11. createdAt: {
  12. [Op.lt]: new Date(),
  13. [Op.gt]: new Date(new Date() - 24 * 60 * 60 * 1000)
  14. }
  15. }
  16. // createdAt < [timestamp] AND createdAt > [timestamp]
  17. {
  18. [Op.or]: [
  19. {
  20. title: {
  21. [Op.like]: 'Boat%'
  22. }
  23. },
  24. {
  25. description: {
  26. [Op.like]: '%boat%'
  27. }
  28. }
  29. ]
  30. }
  31. // title LIKE 'Boat%' OR description LIKE '%boat%'

关系 / 关联

  1. // 找到所有具有至少一个 task 的 project,其中 task.state === project.state
  2. Project.findAll({
  3. include: [{
  4. model: Task,
  5. where: { state: Sequelize.col('project.state') }
  6. }]
  7. })

分页 / 限制

  1. // 获取10个实例/行
  2. Project.findAll({ limit: 10 })
  3. // 跳过8个实例/行
  4. Project.findAll({ offset: 8 })
  5. // 跳过5个实例,然后取5个
  6. Project.findAll({ offset: 5, limit: 5 })

排序

order 需要一个条目的数组来排序查询或者一个 sequelize 方法.一般来说,你将要使用任一属性的 tuple/array,并确定排序的正反方向.

  1. Subtask.findAll({
  2. order: [
  3. // 将转义标题,并根据有效的方向参数列表验证DESC
  4. ['title', 'DESC'],
  5. // 将按最大值排序(age)
  6. sequelize.fn('max', sequelize.col('age')),
  7. // 将按最大顺序(age) DESC
  8. [sequelize.fn('max', sequelize.col('age')), 'DESC'],
  9. // 将按 otherfunction 排序(`col1`, 12, 'lalala') DESC
  10. [sequelize.fn('otherfunction', sequelize.col('col1'), 12, 'lalala'), 'DESC'],
  11. // 将使用模型名称作为关联的名称排序关联模型的 created_at.
  12. [Task, 'createdAt', 'DESC'],
  13. // Will order through an associated model's created_at using the model names as the associations' names.
  14. [Task, Project, 'createdAt', 'DESC'],
  15. // 将使用关联的名称由关联模型的created_at排序.
  16. ['Task', 'createdAt', 'DESC'],
  17. // Will order by a nested associated model's created_at using the names of the associations.
  18. ['Task', 'Project', 'createdAt', 'DESC'],
  19. // Will order by an associated model's created_at using an association object. (优选方法)
  20. [Subtask.associations.Task, 'createdAt', 'DESC'],
  21. // Will order by a nested associated model's created_at using association objects. (优选方法)
  22. [Subtask.associations.Task, Task.associations.Project, 'createdAt', 'DESC'],
  23. // Will order by an associated model's created_at using a simple association object.
  24. [{model: Task, as: 'Task'}, 'createdAt', 'DESC'],
  25. // 嵌套关联模型的 created_at 简单关联对象排序
  26. [{model: Task, as: 'Task'}, {model: Project, as: 'Project'}, 'createdAt', 'DESC']
  27. ]
  28. // 将按年龄最大值降序排列
  29. order: sequelize.literal('max(age) DESC')
  30. // 按最年龄大值升序排列,当省略排序条件时默认是升序排列
  31. order: sequelize.fn('max', sequelize.col('age'))
  32. // 按升序排列是省略排序条件的默认顺序
  33. order: sequelize.col('age')
  34. // 将根据方言随机排序 (而不是 fn('RAND') 或 fn('RANDOM'))
  35. order: sequelize.random()
  36. })

count - 计算数据库中元素的出现次数

还有一种数据库对象计数的方法:

  1. Project.count().then(c => {
  2. console.log("There are " + c + " projects!")
  3. })
  4. Project.count({ where: {'id': {[Op.gt]: 25}} }).then(c => {
  5. console.log("There are " + c + " projects with an id greater than 25.")
  6. })

max - 获取特定表中特定属性的最大值

这里是获取属性的最大值的方法:

  1. /*
  2. 我们假设3个具有属性年龄的对象.
  3. 第一个是10岁,
  4. 第二个是5岁,
  5. 第三个是40岁.
  6. */
  7. Project.max('age').then(max => {
  8. // 将返回 40
  9. })
  10. Project.max('age', { where: { age: { [Op.lt]: 20 } } }).then(max => {
  11. // 将会是 10
  12. })

min - 获取特定表中特定属性的最小值

这里是获取属性的最小值的方法:

  1. /*
  2. 我们假设3个具有属性年龄的对象.
  3. 第一个是10岁,
  4. 第二个是5岁,
  5. 第三个是40岁.
  6. */
  7. Project.min('age').then(min => {
  8. // 将返回 5
  9. })
  10. Project.min('age', { where: { age: { [Op.gt]: 5 } } }).then(min => {
  11. // 将会是 10
  12. })

sum - 特定属性的值求和

为了计算表的特定列的总和,可以使用“sum”方法.

  1. /*
  2. 我们假设3个具有属性年龄的对象.
  3. 第一个是10岁,
  4. 第二个是5岁,
  5. 第三个是40岁.
  6. */
  7. Project.sum('age').then(sum => {
  8. // 将返回 55
  9. })
  10. Project.sum('age', { where: { age: { [Op.gt]: 5 } } }).then(sum => {
  11. // 将会是 50
  12. })

预加载

当你从数据库检索数据时,也想同时获得与之相关联的查询,这被称为预加载.这个基本思路就是当你调用 findfindAll 时使用 include 属性.让我们假设以下设置:

  1. class User extends Model {}
  2. User.init({ name: Sequelize.STRING }, { sequelize, modelName: 'user' })
  3. class Task extends Model {}
  4. Task.init({ name: Sequelize.STRING }, { sequelize, modelName: 'task' })
  5. class Tool extends Model {}
  6. Tool.init({ name: Sequelize.STRING }, { sequelize, modelName: 'tool' })
  7. Task.belongsTo(User)
  8. User.hasMany(Task)
  9. User.hasMany(Tool, { as: 'Instruments' })
  10. sequelize.sync().then(() => {
  11. // 这是我们继续的地方 ...
  12. })

首先,让我们用它们的关联 user 加载所有的 task.

  1. Task.findAll({ include: [ User ] }).then(tasks => {
  2. console.log(JSON.stringify(tasks))
  3. /*
  4. [{
  5. "name": "A Task",
  6. "id": 1,
  7. "createdAt": "2013-03-20T20:31:40.000Z",
  8. "updatedAt": "2013-03-20T20:31:40.000Z",
  9. "userId": 1,
  10. "user": {
  11. "name": "John Doe",
  12. "id": 1,
  13. "createdAt": "2013-03-20T20:31:45.000Z",
  14. "updatedAt": "2013-03-20T20:31:45.000Z"
  15. }
  16. }]
  17. */
  18. })

请注意,访问者(结果实例中的 User 属性)是单数形式,因为关联是一对一的.

接下来的事情:用多对一的关联加载数据!

  1. User.findAll({ include: [ Task ] }).then(users => {
  2. console.log(JSON.stringify(users))
  3. /*
  4. [{
  5. "name": "John Doe",
  6. "id": 1,
  7. "createdAt": "2013-03-20T20:31:45.000Z",
  8. "updatedAt": "2013-03-20T20:31:45.000Z",
  9. "tasks": [{
  10. "name": "A Task",
  11. "id": 1,
  12. "createdAt": "2013-03-20T20:31:40.000Z",
  13. "updatedAt": "2013-03-20T20:31:40.000Z",
  14. "userId": 1
  15. }]
  16. }]
  17. */
  18. })

请注意,访问者(结果实例中的 Tasks 属性)是复数形式,因为关联是多对一的.

如果关联是别名的(使用 as 参数),则在包含模型时必须指定此别名. 注意用户的 Tool 如何被别名为 Instruments. 为了获得正确的权限,你必须指定要加载的模型以及别名:

  1. User.findAll({ include: [{ model: Tool, as: 'Instruments' }] }).then(users => {
  2. console.log(JSON.stringify(users))
  3. /*
  4. [{
  5. "name": "John Doe",
  6. "id": 1,
  7. "createdAt": "2013-03-20T20:31:45.000Z",
  8. "updatedAt": "2013-03-20T20:31:45.000Z",
  9. "Instruments": [{
  10. "name": "Toothpick",
  11. "id": 1,
  12. "createdAt": null,
  13. "updatedAt": null,
  14. "userId": 1
  15. }]
  16. }]
  17. */
  18. })

你还可以通过指定与关联别名匹配的字符串来包含别名:

  1. User.findAll({ include: ['Instruments'] }).then(users => {
  2. console.log(JSON.stringify(users))
  3. /*
  4. [{
  5. "name": "John Doe",
  6. "id": 1,
  7. "createdAt": "2013-03-20T20:31:45.000Z",
  8. "updatedAt": "2013-03-20T20:31:45.000Z",
  9. "Instruments": [{
  10. "name": "Toothpick",
  11. "id": 1,
  12. "createdAt": null,
  13. "updatedAt": null,
  14. "userId": 1
  15. }]
  16. }]
  17. */
  18. })
  19. User.findAll({ include: [{ association: 'Instruments' }] }).then(users => {
  20. console.log(JSON.stringify(users))
  21. /*
  22. [{
  23. "name": "John Doe",
  24. "id": 1,
  25. "createdAt": "2013-03-20T20:31:45.000Z",
  26. "updatedAt": "2013-03-20T20:31:45.000Z",
  27. "Instruments": [{
  28. "name": "Toothpick",
  29. "id": 1,
  30. "createdAt": null,
  31. "updatedAt": null,
  32. "userId": 1
  33. }]
  34. }]
  35. */
  36. })

当预加载时,我们也可以使用 where 过滤关联的模型. 这将返回 Tool 模型中所有与 where 语句匹配的行的User.

  1. User.findAll({
  2. include: [{
  3. model: Tool,
  4. as: 'Instruments',
  5. where: { name: { [Op.like]: '%ooth%' } }
  6. }]
  7. }).then(users => {
  8. console.log(JSON.stringify(users))
  9. /*
  10. [{
  11. "name": "John Doe",
  12. "id": 1,
  13. "createdAt": "2013-03-20T20:31:45.000Z",
  14. "updatedAt": "2013-03-20T20:31:45.000Z",
  15. "Instruments": [{
  16. "name": "Toothpick",
  17. "id": 1,
  18. "createdAt": null,
  19. "updatedAt": null,
  20. "userId": 1
  21. }]
  22. }],
  23. [{
  24. "name": "John Smith",
  25. "id": 2,
  26. "createdAt": "2013-03-20T20:31:45.000Z",
  27. "updatedAt": "2013-03-20T20:31:45.000Z",
  28. "Instruments": [{
  29. "name": "Toothpick",
  30. "id": 1,
  31. "createdAt": null,
  32. "updatedAt": null,
  33. "userId": 1
  34. }]
  35. }],
  36. */
  37. })

当使用 include.where 过滤一个预加载的模型时,include.required 被隐式设置为 true. 这意味着内部联接完成返回具有任何匹配子项的父模型.

使用预加载模型的顶层 WHERE

将模型的 WHERE 条件从 ON 条件的 include 模式移动到顶层,你可以使用 '$nested.column$' 语法:

  1. User.findAll({
  2. where: {
  3. '$Instruments.name$': { [Op.iLike]: '%ooth%' }
  4. },
  5. include: [{
  6. model: Tool,
  7. as: 'Instruments'
  8. }]
  9. }).then(users => {
  10. console.log(JSON.stringify(users));
  11. /*
  12. [{
  13. "name": "John Doe",
  14. "id": 1,
  15. "createdAt": "2013-03-20T20:31:45.000Z",
  16. "updatedAt": "2013-03-20T20:31:45.000Z",
  17. "Instruments": [{
  18. "name": "Toothpick",
  19. "id": 1,
  20. "createdAt": null,
  21. "updatedAt": null,
  22. "userId": 1
  23. }]
  24. }],
  25. [{
  26. "name": "John Smith",
  27. "id": 2,
  28. "createdAt": "2013-03-20T20:31:45.000Z",
  29. "updatedAt": "2013-03-20T20:31:45.000Z",
  30. "Instruments": [{
  31. "name": "Toothpick",
  32. "id": 1,
  33. "createdAt": null,
  34. "updatedAt": null,
  35. "userId": 1
  36. }]
  37. }],
  38. */

包括所有

要包含所有属性,你可以使用 all:true 传递单个对象:

  1. User.findAll({ include: [{ all: true }]});

包括软删除的记录

如果想要加载软删除的记录,可以通过将 include.paranoid 设置为 false 来实现

  1. User.findAll({
  2. include: [{
  3. model: Tool,
  4. where: { name: { [Op.like]: '%ooth%' } },
  5. paranoid: false // query and loads the soft deleted records
  6. }]
  7. });

排序预加载关联

在一对多关系的情况下.

  1. Company.findAll({ include: [ Division ], order: [ [ Division, 'name' ] ] });
  2. Company.findAll({ include: [ Division ], order: [ [ Division, 'name', 'DESC' ] ] });
  3. Company.findAll({
  4. include: [ { model: Division, as: 'Div' } ],
  5. order: [ [ { model: Division, as: 'Div' }, 'name' ] ]
  6. });
  7. Company.findAll({
  8. include: [ { model: Division, as: 'Div' } ],
  9. order: [ [ { model: Division, as: 'Div' }, 'name', 'DESC' ] ]
  10. });
  11. Company.findAll({
  12. include: [ { model: Division, include: [ Department ] } ],
  13. order: [ [ Division, Department, 'name' ] ]
  14. });

在多对多关系的情况下,你还可以通过表中的属性进行排序.

  1. Company.findAll({
  2. include: [ { model: Division, include: [ Department ] } ],
  3. order: [ [ Division, DepartmentDivision, 'name' ] ]
  4. });

嵌套预加载

你可以使用嵌套的预加载来加载相关模型的所有相关模型:

  1. User.findAll({
  2. include: [
  3. {model: Tool, as: 'Instruments', include: [
  4. {model: Teacher, include: [ /* etc */]}
  5. ]}
  6. ]
  7. }).then(users => {
  8. console.log(JSON.stringify(users))
  9. /*
  10. [{
  11. "name": "John Doe",
  12. "id": 1,
  13. "createdAt": "2013-03-20T20:31:45.000Z",
  14. "updatedAt": "2013-03-20T20:31:45.000Z",
  15. "Instruments": [{ // 1:M and N:M association
  16. "name": "Toothpick",
  17. "id": 1,
  18. "createdAt": null,
  19. "updatedAt": null,
  20. "userId": 1,
  21. "Teacher": { // 1:1 association
  22. "name": "Jimi Hendrix"
  23. }
  24. }]
  25. }]
  26. */
  27. })

这将产生一个外连接. 但是,相关模型上的 where 语句将创建一个内部连接,并仅返回具有匹配子模型的实例. 要返回所有父实例,你应该添加 required: false.

  1. User.findAll({
  2. include: [{
  3. model: Tool,
  4. as: 'Instruments',
  5. include: [{
  6. model: Teacher,
  7. where: {
  8. school: "Woodstock Music School"
  9. },
  10. required: false
  11. }]
  12. }]
  13. }).then(users => {
  14. /* ... */
  15. })

以上查询将返回所有用户及其所有乐器,但只会返回与 Woodstock Music School 相关的老师.

包括所有也支持嵌套加载:

  1. User.findAll({ include: [{ all: true, nested: true }]});

新增

字段限制

  1. await User.create({ username: 'barfooz', isAdmin: true }, { fields: [ 'username' ] });
  2. // 只有username有效
  3. User.bulkCreate([
  4. { username: 'foo' },
  5. { username: 'bar', admin: true}
  6. ], { fields: ['username'] }).then(() => {
  7. // admin 将不会被构建
  8. })

新增单个

  1. // create
  2. this.ctx.body = await this.ctx.model.User.create({
  3. name: "哈哈哈",
  4. age: 12
  5. });

批量新增

  1. // 批量新增 bulkCreate
  2. this.ctx.body = await this.ctx.model.User.bulkCreate([
  3. {
  4. name: "第一个",
  5. age: 15
  6. },
  7. {
  8. name: "第二个",
  9. age: 15
  10. },
  11. {
  12. name: "第三个",
  13. age: 15
  14. },
  15. ]);

修改

字段限制

  1. task.title = 'foooo'
  2. task.description = 'baaaaaar'
  3. await task.save({fields: ['title']});
  4. // title 现在将是 “foooo”,而 description 与以前一样
  5. // 使用等效的 update 调用如下所示:
  6. await task.update({ title: 'foooo', description: 'baaaaaar'}, {fields: ['title']});
  7. // title 现在将是 “foooo”,而 description 与以前一样

单个修改

  1. // 找出当前记录
  2. const user = await this.ctx.model.User.findByPk(1);
  3. await user.update({
  4. name: "我被修改了",
  5. age: 30
  6. });

批量修改

  1. // 批量修改
  2. await this.ctx.model.User.update({
  3. name: "批量修改"
  4. }, {
  5. // 条件
  6. where: {
  7. name: "第一个"
  8. }
  9. });

递增

  1. // 找出当前记录 increment
  2. const user = await this.ctx.model.User.findByPk(2);
  3. this.ctx.body = await user.increment({
  4. age: 3, // age每次递增3
  5. other:2 // other每次递增2
  6. });

递减

  1. // 找出当前记录 decrement
  2. const user = await this.ctx.model.User.findByPk(2);
  3. this.ctx.body = await user.decrement({
  4. age: 3, // age每次递减3
  5. other:2 // other每次递减2
  6. });

删除

软删除

模型中配置

  1. // 配置(重要)
  2. const User = app.model.define('user', { /* bla */},{
  3. // 同时需要设置paranoid为true(此种模式下,删除数据时不会进行物理删除,而是设置deletedAt为当前时间
  4. 'paranoid': true
  5. });

查询包括软删除内容

  1. let user = await ctx.model.User.findOne({
  2. include:{
  3. model:ctx.model.Video,
  4. // 包括软删除
  5. paranoid: false
  6. },
  7. where: {
  8. id: 33
  9. },
  10. // 包括软删除
  11. paranoid: false
  12. });

彻底删除

如果 paranoid 选项为 true,则不会删除该对象,而将 deletedAt 列设置为当前时间戳. 要强制删除,可以将 force: true 传递给 destroy 调用:

  1. task.destroy({ force: true })

paranoid 模式下对象被软删除后,在强制删除旧实例之前,你将无法使用相同的主键创建新实例.

恢复软删除的实例

如果你使用 paranoid:true 软删除了模型的实例,之后想要撤消删除,请使用 restore 方法:

  1. // 进行软删除...
  2. task.destroy();
  3. // 恢复软删除...
  4. task.restore();

条件删除

  1. await this.ctx.model.User.destroy({
  2. where: {
  3. name: "批量修改"
  4. }
  5. });

批量删除

  1. await this.ctx.model.Post.destroy({
  2. where: {
  3. id: posts
  4. }
  5. });

重载实例

如果你需要让你的实例同步,你可以使用 reload 方法. 它将从数据库中获取当前数据,并覆盖调用该方法的模型的属性.

  1. Person.findOne({ where: { name: 'john' } }).then(person => {
  2. person.name = 'jane'
  3. console.log(person.name) // 'jane'
  4. person.reload().then(() => {
  5. console.log(person.name) // 'john'
  6. })
  7. })

模型自定义方法

  1. // 模型
  2. // 模型自定义方法
  3. topic_user.ceshi = (param) => {
  4. console.log('模型自定义方法');
  5. console.log(param);
  6. return param;
  7. }
  8. // 控制器
  9. await this.ctx.model.TopicUser.ceshi(123);

Scopes - 作用域(重点)

作用域允许你定义常用查询,以便以后轻松使用. 作用域可以包括与常规查找器 where, include, limit 等所有相同的属性.

定义

作用域在模型定义中定义,可以是finder对象或返回finder对象的函数,除了默认作用域,该作用域只能是一个对象:

  1. class Project extends Model {}
  2. Project.init({
  3. // 属性
  4. }, {
  5. defaultScope: {
  6. where: {
  7. active: true
  8. }
  9. },
  10. scopes: {
  11. deleted: {
  12. where: {
  13. deleted: true
  14. }
  15. },
  16. activeUsers: {
  17. include: [
  18. { model: User, where: { active: true }}
  19. ]
  20. },
  21. random () {
  22. return {
  23. where: {
  24. someNumber: Math.random()
  25. }
  26. }
  27. },
  28. accessLevel (value) {
  29. return {
  30. where: {
  31. accessLevel: {
  32. [Op.gte]: value
  33. }
  34. }
  35. }
  36. }
  37. sequelize,
  38. modelName: 'project'
  39. }
  40. });

通过调用 addScope 定义模型后,还可以添加作用域. 这对于具有包含的作用域特别有用,其中在定义其他模型时可能不会定义 include 中的模型.

始终应用默认作用域. 这意味着,通过上面的模型定义,Project.findAll() 将创建以下查询:

  1. SELECT * FROM projects WHERE active = true

可以通过调用 .unscoped(), .scope(null) 或通过调用另一个作用域来删除默认作用域:

  1. Project.scope('deleted').findAll(); // 删除默认作用域
  2. SELECT * FROM projects WHERE deleted = true

还可以在作用域定义中包含作用域模型. 这让你避免重复 include,attributeswhere 定义.

使用上面的例子,并在包含的用户模型中调用 active 作用域(而不是直接在该 include 对象中指定条件):

  1. activeUsers: {
  2. include: [
  3. { model: User.scope('active')}
  4. ]
  5. }

使用

通过在模型定义上调用 .scope 来应用作用域,传递一个或多个作用域的名称. .scope 返回一个全功能的模型实例,它具有所有常规的方法:.findAll,.update,.count,.destroy等等.你可以保存这个模型实例并稍后再次使用:

  1. const DeletedProjects = Project.scope('deleted');
  2. DeletedProjects.findAll();
  3. // 过一段时间
  4. // 让我们再次寻找被删除的项目!
  5. DeletedProjects.findAll();

作用域适用于 .find, .findAll, .count, .update, .increment.destroy.

可以通过两种方式调用作为函数的作用域. 如果作用域没有任何参数,它可以正常调用. 如果作用域采用参数,则传递一个对象:

  1. Project.scope('random', { method: ['accessLevel', 19]}).findAll();
  2. SELECT * FROM projects WHERE someNumber = 42 AND accessLevel >= 19

合并

通过将作用域数组传递到 .scope 或通过将作用域作为连续参数传递,可以同时应用多个作用域.

  1. // 这两个是等价的
  2. Project.scope('deleted', 'activeUsers').findAll();
  3. Project.scope(['deleted', 'activeUsers']).findAll();
  4. SELECT * FROM projects
  5. INNER JOIN users ON projects.userId = users.id
  6. WHERE projects.deleted = true
  7. AND users.active = true

如果要将其他作用域与默认作用域一起应用,请将键 defaultScope 传递给 .scope:

  1. Project.scope('defaultScope', 'deleted').findAll();
  2. SELECT * FROM projects WHERE active = true AND deleted = true

当调用多个作用域时,后续作用域的键将覆盖以前的作用域(类似于 Object.assign),除了whereinclude,它们将被合并. 考虑两个作用域:

  1. {
  2. scope1: {
  3. where: {
  4. firstName: 'bob',
  5. age: {
  6. [Op.gt]: 20
  7. }
  8. },
  9. limit: 2
  10. },
  11. scope2: {
  12. where: {
  13. age: {
  14. [Op.gt]: 30
  15. }
  16. },
  17. limit: 10
  18. }
  19. }

调用 .scope('scope1', 'scope2') 将产生以下查询

  1. WHERE firstName = 'bob' AND age > 30 LIMIT 10

注意 scope2 将覆盖 limitage,而 firstName 被保留. limit,offset,order,paranoid,lockraw字段被覆盖,而where被浅层合并(意味着相同的键将被覆盖). include 的合并策略将在后面讨论.

请注意,多个应用作用域的 attributes 键以这样的方式合并,即始终保留 attributes.exclude. 这允许合并多个作用域,并且永远不会泄漏最终作用域内的敏感字段.

将查找对象直接传递给作用域模型上的findAll(和类似的查找程序)时,适用相同的合并逻辑:

  1. Project.scope('deleted').findAll({
  2. where: {
  3. firstName: 'john'
  4. }
  5. })
  6. WHERE deleted = true AND firstName = 'john'

这里的 deleted 作用域与 finder 合并. 如果我们要将 where: { firstName: 'john', deleted: false } 传递给 finder,那么 deleted 作用域将被覆盖.

合并 include

Include 是根据包含的模型递归合并的. 这是一个非常强大的合并,在 v5 上添加,并通过示例更好地理解.

考虑四种模型:Foo,Bar,Baz和Qux,具有如下多种关联:

  1. class Foo extends Model {}
  2. class Bar extends Model {}
  3. class Baz extends Model {}
  4. class Qux extends Model {}
  5. Foo.init({ name: Sequelize.STRING }, { sequelize });
  6. Bar.init({ name: Sequelize.STRING }, { sequelize });
  7. Baz.init({ name: Sequelize.STRING }, { sequelize });
  8. Qux.init({ name: Sequelize.STRING }, { sequelize });
  9. Foo.hasMany(Bar, { foreignKey: 'fooId' });
  10. Bar.hasMany(Baz, { foreignKey: 'barId' });
  11. Baz.hasMany(Qux, { foreignKey: 'bazId' });

现在,考虑Foo上定义的以下四个作用域:

  1. {
  2. includeEverything: {
  3. include: {
  4. model: this.Bar,
  5. include: [{
  6. model: this.Baz,
  7. include: this.Qux
  8. }]
  9. }
  10. },
  11. limitedBars: {
  12. include: [{
  13. model: this.Bar,
  14. limit: 2
  15. }]
  16. },
  17. limitedBazs: {
  18. include: [{
  19. model: this.Bar,
  20. include: [{
  21. model: this.Baz,
  22. limit: 2
  23. }]
  24. }]
  25. },
  26. excludeBazName: {
  27. include: [{
  28. model: this.Bar,
  29. include: [{
  30. model: this.Baz,
  31. attributes: {
  32. exclude: ['name']
  33. }
  34. }]
  35. }]
  36. }
  37. }

这四个作用域可以很容易地深度合并,例如通过调用 Foo.scope('includeEverything', 'limitedBars', 'limitedBazs', 'excludeBazName').findAll(),这完全等同于调用以下内容:

  1. Foo.findAll({
  2. include: {
  3. model: this.Bar,
  4. limit: 2,
  5. include: [{
  6. model: this.Baz,
  7. limit: 2,
  8. attributes: {
  9. exclude: ['name']
  10. },
  11. include: this.Qux
  12. }]
  13. }
  14. });

观察四个作用域如何合并为一个. 根据所包含的模型合并作用域的include. 如果一个作用域包括模型A而另一个作用域包括模型B,则合并结果将包括模型A和B.另一方面,如果两个作用域包括相同的模型A,但具有不同的参数(例如嵌套include或其他属性) ,这些将以递归方式合并,如上所示.

无论应用于作用域的顺序如何,上面说明的合并都以完全相同的方式工作. 如果某个参数由两个不同的作用域设置,那么只会该顺序产生差异 - 这不是上述示例的情况,因为每个作用域都做了不同的事情.

这种合并策略的工作方式与传递给.findAll,.findOne等的参数完全相同.

关联

Sequelize 与关联有两个不同但相关的作用域概念. 差异是微妙但重要的:

  • 关联作用域 允许你在获取和设置关联时指定默认属性 - 在实现多态关联时很有用. 当使用get,set,addcreate相关联的模型函数时,这个作用域仅在两个模型之间的关联上被调用
  • 关联模型上的作用域 允许你在获取关联时应用默认和其他作用域,并允许你在创建关联时传递作用域模型. 这些作用域都适用于模型上的常规查找和通过关联查找.

举个例子,思考模型Post和Comment. Comment与其他几个模型(图像,视频等)相关联,Comment和其他模型之间的关联是多态的,这意味着除了外键 commentable_id 之外,注释还存储一个commentable列.

可以使用 association scope 来实现多态关联:

  1. this.Post.hasMany(this.Comment, {
  2. foreignKey: 'commentable_id',
  3. scope: {
  4. commentable: 'post'
  5. }
  6. });

当调用 post.getComments() 时,这将自动添加 WHERE commentable = 'post'. 类似地,当向帖子添加新的注释时,commentable 会自动设置为 'post'. 关联作用域是为了存活于后台,没有程序员不必担心 - 它不能被禁用. 有关更完整的多态性示例,请参阅 关联作用域

那么考虑那个Post的默认作用域只显示活动的帖子:where: { active: true }. 该作用域存在于相关联的模型(Post)上,而不是像commentable 作用域那样在关联上. 就像在调用Post.findAll() 时一样应用默认作用域,当调用 User.getPosts() 时,它也会被应用 - 这只会返回该用户的活动帖子.

要禁用默认作用域,将 scope: null 传递给 getter: User.getPosts({ scope: null }). 同样,如果要应用其他作用域,请像这样:

  1. User.getPosts({ scope: ['scope1', 'scope2']});

如果要为关联模型上的作用域创建快捷方式,可以将作用域模型传递给关联. 考虑一个快捷方式来获取用户所有已删除的帖子:

  1. class Post extends Model {}
  2. Post.init(attributes, {
  3. defaultScope: {
  4. where: {
  5. active: true
  6. }
  7. },
  8. scopes: {
  9. deleted: {
  10. where: {
  11. deleted: true
  12. }
  13. }
  14. },
  15. sequelize,
  16. });
  17. User.hasMany(Post); // 常规 getPosts 关联
  18. User.hasMany(Post.scope('deleted'), { as: 'deletedPosts' });
  19. User.getPosts(); // WHERE active = true
  20. User.getDeletedPosts(); // WHERE deleted = true

扩展

extend/helper.js

  1. // app/extend/helper.js
  2. module.exports = {
  3. // 扩展一个格式日期的方法
  4. formatTime(val) {
  5. let d = new Date(val * 1000);
  6. return d.getFullYear();
  7. },
  8. };

模板中调用

  1. <%=helper.formatTime(dateline)%>

其他地方调用

  1. this.ctx.helper.formatTime(dateline)

中间件

1. 定义

app/middleware/getIp.js

  1. /*
  2. options: 中间件的配置项,框架会将 app.config[${middlewareName}] 传递进来。
  3. app: 当前应用 Application 的实例。
  4. */
  5. module.exports = (option, app) => {
  6. // 返回一个异步的方法
  7. return async function(ctx, next) {
  8. // 通过option传入额外参数
  9. console.log(option);
  10. console.log(ctx.request.ip);
  11. await next();
  12. }
  13. };

2. 配置

config/config.default.js(配置全局中间件,所有路由都会调用)

  1. module.exports = appInfo => {
  2. ...
  3. // 配置全局中间件
  4. config.middleware = ['getIp']; // 注意驼峰式写法,如果中间件是a_bc.js,则要写成aBc
  5. // 配置中间件参数
  6. config.getIp = {
  7. ceshi: 123,
  8. // 通用配置(以下是重点)
  9. enable:true, // 控制中间件是否开启。
  10. match:'/news', // 设置只有符合某些规则的请求才会经过这个中间件(匹配路由)
  11. ignore:'/shop' // 设置符合某些规则的请求不经过这个中间件。
  12. /**
  13. 注意:
  14. 1. match 和 ignore 不允许同时配置
  15. 2. 例如:match:'/news',只要包含/news的任何页面都生效
  16. **/
  17. // match 和 ignore 支持多种类型的配置方式:字符串、正则、函数(推荐)
  18. match(ctx) {
  19. // 只有 ios 设备才开启
  20. const reg = /iphone|ipad|ipod/i;
  21. return reg.test(ctx.get('user-agent'));
  22. },
  23. };
  24. ...
  25. };

3. 使用

路由中使用

app/router.js

  1. module.exports = app => {
  2. // 局部中间件(如果只需要局部调用,则不需要在config.default.js中配置)
  3. router.get('/admin/:id', app.middleware.getIp({
  4. ceshi: "我是admin"
  5. }), controller.admin.index);
  6. };

使用 Koa 的中间件(gzip压缩)

大大提高网站的访问速度(非常有效)

koa-compress 为例,在 Koa 中使用时:

  1. const koa = require('koa');
  2. const compress = require('koa-compress');
  3. const app = koa();
  4. const options = { threshold: 2048 };
  5. app.use(compress(options));

我们按照框架的规范来在应用中加载这个 Koa 的中间件:

  1. // app/middleware/compress.js
  2. // koa-compress 暴露的接口(`(options) => middleware`)和框架对中间件要求一致
  3. module.exports = require('koa-compress');
  4. // config/config.default.js
  5. module.exports = {
  6. middleware: [ 'compress' ],
  7. compress: {
  8. threshold: 2048,
  9. },
  10. };

表单提交

post

app/controller/home.js

  1. async addInput(ctx) {
  2. await ctx.render('post');
  3. }
  4. async add(ctx) {
  5. // 通过ctx.request.body获取post提交数据
  6. console.log(ctx.request.body);
  7. }

app/view/post.html

  1. <!--
  2. 需要定义:?_csrf=<%=ctx.csrf%>
  3. -->
  4. <form action="/add?_csrf=<%=ctx.csrf%>" method="post">
  5. <input type="text" name="username" id="username">
  6. <input type="password" name="password" id="password">
  7. <input type="submit" value="提交">
  8. </form>

app/router.js

  1. router.get('/post', controller.home.addInput);
  2. router.post('/add', controller.home.add);

cookie

  1. // 1.设置
  2. ctx.cookies.set('username', 'ceshi');
  3. // 2.获取
  4. ctx.cookies.get('username');
  5. // 3.设置中文(加密操作 encrypt: true)
  6. // 4.设置(其他参数配置)
  7. ctx.cookies.set('username', 'ceshi', {
  8. maxAge: 1000 * 3600 * 24, // 存储24小时,单位毫秒,关闭浏览器cookie还存在
  9. httpOnly: true, // 设置键值对是否可以被 js 访问,默认为 true,不允许被 js 访问。
  10. signed: true, // 签名,防止用户前台修改
  11. encrypt: true // 加密,注意:get获取时需要解密
  12. });
  13. // 5.获取时解密
  14. ctx.cookies.get('username',{
  15. encrypt: true
  16. });
  17. // 6.清除cookie
  18. ctx.cookies.set('username', null);

session

  1. // 1.设置
  2. ctx.session.username = '测试';
  3. // 2.获取
  4. ctx.session.username
  5. // 3.默认配置(全局配置,config/config.default.js)
  6. exports.session = {
  7. key: 'EGG_SESS', // 设置cookies的key值
  8. maxAge: 24 * 3600 * 1000, // 1 天,过期时间
  9. httpOnly: true, // 设置键值对是否可以被 js 访问,默认为 true,不允许被 js 访问。
  10. encrypt: true,// 加密
  11. renew:true // 每次刷新页面都会被延期
  12. };
  13. // 4.动态配置
  14. ctx.session.maxAge = 5000; // 5秒的过期时间
  15. ctx.session.username = '测试';
  16. // 5.清空session
  17. ctx.session.username = null;

定时任务

  1. // app/schedule/ceshi.js
  2. var i = 1;
  3. module.exports = {
  4. // 设置定时任务的执行间隔等配置
  5. schedule: {
  6. interval: '5s', // 每5秒执行一次
  7. type: 'all' // 指定所有的 worker 都需要执行
  8. },
  9. // 任务
  10. async task(ctx) {
  11. ++i;
  12. console.log(i);
  13. }
  14. };

API

1. context

curl

  1. async ceshi() {
  2. // 通过ctx中的curl方法获取数据
  3. let r = await this.ctx.curl('http://www.phonegap100.com/appapi.php?a=getPortalList&catid=20&page=1');
  4. // 将buffer类型数据转为json类型
  5. let { result } = JSON.parse(r.data)
  6. return result;
  7. }

常用插件

缓存

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头添加:

  1. // Authorization:"Bearer token值"
  2. Authorization:"Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6MTIzLCJpYXQiOjE1NzkxOTQxNTN9.Ml5B02ZPfYo78QwJic-Jdp2LUi2_AU0RGNgPhhJH--o"