核心思想: 约定大于配置
基础用法 建议直接去这里: 官方快速入门
目录结构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 egg-project ├── package.json ├── app.js (可选) ├── agent.js (可选) ├── app | ├── router.js │ ├── controller │ | └── home.js │ ├── service (可选) │ | └── user.js │ ├── middleware (可选) │ | └── response_time.js │ ├── schedule (可选) │ | └── my_task.js │ ├── public (可选) │ | └── reset.css │ ├── view (可选) │ | └── home.tpl │ └── extend (可选) │ ├── helper.js (可选) │ ├── request.js (可选) │ ├── response.js (可选) │ ├── context.js (可选) │ ├── application.js (可选) │ └── agent.js (可选) ├── config | ├── plugin.js | ├── config.default.js │ ├── config.prod.js | ├── config.test.js (可选) | ├── config.local.js (可选) | └── config.unittest.js (可选) └── test ├── middleware | └── response_time.test.js └── controller └── home.test.js
初始化脚本 1 pnpm create egg --type=simple -r=https://registry.npmmirror.com/
模块设计 model 层: 放置一些要往数据库中存储的数据类型的模型.主要是Schema
的实例. service 层: 对 model 层中对应数据库的数据的操作,增删改查之类. controller 层: 设计的业务逻辑,通过调用 service 的一些方法实现,返回给前端. middleware: 在多处使用的方法可以抽离出,比如auth
,error
等模块. config: 配置基础参数. extend: 扩展方法,主要是一些和业务逻辑关联性不大的方法.比如md5
加密. schedule: 定时任务.
内置基础对象 Application,Context,Request,Response,Controller,Service,Helper,Config,Logger,Subscription. 其中前 4 个是从 Koa 继承而来.其他是框架自身的.
Application Application 是全局应用对象。在一个应用中,每个进程只会实例化一个 Application 实例。在它上面我们可以挂载一些全局的方法和对象。
Controller 框架提供了一个 Controller 基类,并推荐所有的 Controller 都继承于该基类实现。这个 Controller 基类有下列属性:
ctx
- 当前请求的 Context 实例。
app
- 应用的 Application 实例。
config
- 应用的配置。
service
- 应用所有的 service。
logger
- 为当前 controller 封装的 logger 对象。
路由相关 1. get 传值 1 2 3 4 5 6 7 8 9 10 router.get ('/admin/:id' , controller.admin .index ); async index (ctx ) { ctx.params ; ctx.query ; }
2. 4 种配置方法 1 2 3 4 router.verb ('path-match' , app.controller .action ); router.verb ('router-name' , 'path-match' , app.controller .action ); router.verb ('path-match' , middleware1, ..., middlewareN, app.controller .action ); router.verb ('router-name' , 'path-match' , middleware1, ..., middlewareN, app.controller .action );
重定向 1. ctx 1 2 3 4 async index ( ) { this .ctx .status = 301 ; this .ctx .redirect ('/admin/add' ); }
2. 路由重定向 1 app.router .redirect ("/" , "/home/index" , 302 );
3.路由分组 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 module .exports = (app ) => { require ("./router/news" )(app); require ("./router/admin" )(app); }; module .exports = (app ) => { app.router .get ("/news/list" , app.controller .news .list ); app.router .get ("/news/detail" , app.controller .news .detail ); }; module .exports = (app ) => { app.router .get ("/admin/user" , app.controller .admin .user ); app.router .get ("/admin/log" , app.controller .admin .log ); };
控制器 自定义 Controller 基类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 const { Controller } = require ("egg" );class BaseController extends Controller { get user () { return this .ctx .session .user ; } success (data ) { this .ctx .body = { success : true , data, }; } notFound (msg ) { msg = msg || "not found" ; this .ctx .throw (404 , msg); } } module .exports = BaseController ;
此时在编写应用的 Controller 时,可以继承 BaseController,直接使用基类上的方法:
1 2 3 4 5 6 7 8 const Controller = require ("../core/base_controller" );class PostController extends Controller { async list ( ) { const posts = await this .service .listByUser (this .user ); this .success (posts); } }
模板引擎 1. 安装和使用 ejs (1)安装: 1 npm i egg-view-ejs --save
(2)配置:/config config/config.default.js
1 2 3 4 5 6 7 8 9 10 11 module .exports = appInfo => { ... config.view = { mapping : { '.html' : 'ejs' , }, }; ... };
config/plugin.js
1 2 3 4 5 6 7 module .exports = { ejs : { enable : true , package : "egg-view-ejs" , }, };
(3)使用 app/controller
1 2 3 4 5 6 7 8 9 10 11 async index ( ) { const { ctx } = this ; let msg = "测试内容" ; let list = [1 , 2 , 3 , 4 , 5 , 6 ]; await ctx.render ('index' , { msg, list }); }
app/view/index.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <!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 > <%=msg%> <ul > <% for(var i=0; i < list.length; i++){ %> <li > <%=list[i]%></li > <% } %> </ul > <img src ="/public/images/find.png" /> </body > </html >
模型和数据库 配置和创建迁移文件 配置 文档地址: https://github.com/demopark/sequelize-docs-Zh-CN/blob/v5/migrations.md
安装并配置egg-sequelize 插件(它会辅助我们将定义好的 Model 对象加载到 app 和 ctx 上)和mysql2 模块:
1 npm install --save egg-sequelize mysql2
在config/plugin.js
中引入 egg-sequelize 插件
1 2 3 4 exports.sequelize = { enable: true, package: 'egg-sequelize', };
在config/config.default.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 config.sequelize = { dialect : "mysql" , host : "127.0.0.1" , username : "root" , password : "root" , port : 3306 , database : "friends" , timezone : "+08:00" , define : { freezeTableName : true , timestamps : true , paranoid : true , createdAt : "created_at" , updatedAt : "updated_at" , deletedAt : "deleted_at" , underscored : true , }, };
sequelize 提供了sequelize-cli 工具来实现Migrations ,我们也可以在 egg 项目中引入 sequelize-cli。
1 npm install --save-dev sequelize-cli
egg 项目中,我们希望将所有数据库 Migrations 相关的内容都放在database
目录下,所以我们在项目根目录下新建一个.sequelizerc
配置文件:
1 2 3 4 5 6 7 8 9 10 "use strict" ;const path = require ("path" );module .exports = { config : path.join (__dirname, "database/config.json" ), "migrations-path" : path.join (__dirname, "database/migrations" ), "seeders-path" : path.join (__dirname, "database/seeders" ), "models-path" : path.join (__dirname, "app/model" ), };
初始化 Migrations 配置文件和目录
1 2 3 npx sequelize init :config npx sequelize init :migrations
行完后会生成database/config.json
文件和database/migrations
目录,我们修改一下database/config.json
中的内容,将其改成我们项目中使用的数据库配置:
1 2 3 4 5 6 7 8 9 10 { "development" : { "username" : "root" , "password" : null , "database" : "test" , "host" : "127.0.0.1" , "dialect" : "mysql" , "timezone" : "+08:00" } }
创建数据库
创建数据迁移表 1 npx sequelize migration :generate --name=init-users
1.执行完命令后,会在 database / migrations / 目录下生成数据表迁移文件,然后定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 "use strict" ;module .exports = { up : async (queryInterface, Sequelize ) => { const { INTEGER , STRING , DATE , ENUM } = Sequelize ; await queryInterface.createTable ( "users" , { id : { type : INTEGER (20 ).UNSIGNED , primaryKey : true , autoIncrement : true , }, username : { type : STRING (30 ), allowNull : false , defaultValue : "" , comment : "用户名称" , unique : true , }, email : { type : STRING (160 ), allowNull : false , defaultValue : "" , comment : "用户邮箱" , unique : true , }, password : { type : STRING (200 ), allowNull : false , defaultValue : "" }, avatar_url : { type : STRING (200 ), allowNull : true , defaultValue : "" }, mobile : { type : STRING (20 ), allowNull : false , defaultValue : "" , comment : "用户手机" , unique : true , }, prifix : { type : STRING (32 ), allowNull : false , defaultValue : "" }, abstract : { type : STRING (255 ), allowNull : true , defaultValue : "" }, role_id : { type : INTEGER , references : { model : "users" , key : "id" , }, onUpdate : "restrict" , onDelete : "cascade" , }, gender : { type : ENUM , values : ["男" , "女" , "保密" ], allowNull : true , defaultValue : "男" , comment : "用户性别" , }, created_at : DATE , updated_at : DATE , }, { engine : "MYISAM" } ); queryInterface.addIndex ("users" , ["gender" ]); queryInterface.addIndex ("users" , { name : "name" , unique : true , fields : ["name" ], }); }, down : async (queryInterface) => { await queryInterface.dropTable ("users" ); }, };
1 2 3 4 5 6 npx sequelize db:migrate
已创建新增字段 1.创建迁移文件:
1 npx sequelize migration:generate --name=user-addcolumn
2.执行完命令后,会在 database / migrations / 目录下生成数据表迁移文件,然后定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 "use strict" ;module .exports = { up : (queryInterface, Sequelize ) => { return queryInterface.sequelize .transaction ((t ) => { return Promise .all ([ queryInterface.addColumn ( "user" , "role_id" , { type : Sequelize .INTEGER , }, { transaction : t } ), queryInterface.addColumn ( "user" , "ceshi" , { type : Sequelize .STRING , }, { transaction : t } ), ]); }); }, down : (queryInterface, Sequelize ) => { return queryInterface.sequelize .transaction ((t ) => { return Promise .all ([ queryInterface.removeColumn ("user" , "role_id" , { transaction : t }), queryInterface.removeColumn ("user" , "ceshi" , { transaction : t }), ]); }); }, };
3.执行 migrate 进行数据库变更
1 npx sequelize db:migrate
创建模型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 "use strict" ;module .exports = (app ) => { const { STRING , INTEGER , DATE } = app.Sequelize ; const User = app.model .define ( "user" , { id : { type : INTEGER , primaryKey : true , autoIncrement : true }, name : STRING (30 ), age : INTEGER , created_at : DATE , updated_at : DATE , }, { timestamps : true , tableName : "users" , } ); return User ; };
这个 Model 就可以在 Controller 和 Service 中通过 app.model.User
或者 ctx.model.User
访问到了,例如我们编写 app/controller/users.js
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 const Controller = require ("egg" ).Controller ;function toInt (str ) { if (typeof str === "number" ) return str; if (!str) return str; return parseInt (str, 10 ) || 0 ; } class UserController extends Controller { async index ( ) { const ctx = this .ctx ; const query = { limit : toInt (ctx.query .limit ), offset : toInt (ctx.query .offset ), }; ctx.body = await ctx.model .User .findAll (query); } async show ( ) { const ctx = this .ctx ; ctx.body = await ctx.model .User .findByPk (toInt (ctx.params .id )); } async create ( ) { const ctx = this .ctx ; const { name, age } = ctx.request .body ; const user = await ctx.model .User .create ({ name, age }); ctx.status = 201 ; ctx.body = user; } async update ( ) { const ctx = this .ctx ; const id = toInt (ctx.params .id ); const user = await ctx.model .User .findByPk (id); if (!user) { ctx.status = 404 ; return ; } const { name, age } = ctx.request .body ; await user.update ({ name, age }); ctx.body = user; } async destroy ( ) { const ctx = this .ctx ; const id = toInt (ctx.params .id ); const user = await ctx.model .User .findByPk (id); if (!user) { ctx.status = 404 ; return ; } await user.destroy (); ctx.status = 200 ; } } module .exports = UserController ;
最后我们将这个 controller 挂载到路由上:
1 2 3 4 5 module .exports = (app ) => { const { router, controller } = app; router.resources ("users" , "/users" , controller.users ); };
针对 users
表的 CURD 操作的接口就开发完了
模型其他参数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 const User = app.model .define ( "user" , { id : { type : INTEGER , primaryKey : true , autoIncrement : true }, name : STRING (30 ), age : INTEGER , created_at : DATE , updated_at : DATE , }, { freezeTableName : true , tableName : "xyz_users" , timestamps : true , createdAt : false , updatedAt : "utime" , deletedAt : "dtime" , paranoid : true , } );
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 3 4 5 6 7 8 9 10 11 12 queryInterface.addConstraint ("tableName" , ["user_id" ], { type : "foreign key" , name : "user_id" , references : { table : "users" , field : "id" , }, onDelete : "cascade" , onUpdate : "cascade" , });
创建第一个种子 假设我们希望在默认情况下将一些数据插入到几个表中. 如果我们跟进前面的例子,我们可以考虑为 User
表创建演示用户.
要管理所有数据迁移,你可以使用 seeders
. 种子文件是数据的一些变化,可用于使用样本数据或测试数据填充数据库表.
让我们创建一个种子文件,它会将一个演示用户添加到我们的 User
表中.
1 npx sequelize seed:generate --name demo-user
这个命令将会在 seeders
文件夹中创建一个种子文件.文件名看起来像是 XXXXXXXXXXXXXX-demo-user.js
,它遵循相同的 up/down
语义,如迁移文件.
现在我们应该编辑这个文件,将演示用户插入User
表.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 "use strict" ;module .exports = { up : (queryInterface, Sequelize ) => { return queryInterface.bulkInsert ( "Users" , [ { firstName : "John" , lastName : "Doe" , email : "demo@demo.com" , createdAt : new Date (), updatedAt : new Date (), }, ], {} ); }, down : (queryInterface, Sequelize ) => { return queryInterface.bulkDelete ("Users" , null , {}); }, };
运行种子 在上一步中,你创建了一个种子文件. 但它还没有保存到数据库. 为此,我们需要运行一个简单的命令.
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 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 module .exports = (app ) => { const { STRING , INTEGER , DATE } = app.Sequelize ; const User = app.model .define ("user" , { id : { type : INTEGER , primaryKey : true , autoIncrement : true }, name : STRING (30 ), age : INTEGER , created_at : DATE , updated_at : DATE , }); User .associate = function (models ) { User .hasOne (app.model .Userinfo ); }; return User ; }; module .exports = (app ) => { const { STRING , INTEGER , DATE } = app.Sequelize ; const userinfo = app.model .define ( "userinfo" , { nickname : STRING , user_id : INTEGER , }, {} ); userinfo.associate = function (models ) { app.model .Userinfo .belongsTo (app.model .User ); }; return userinfo; };
控制器调用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 async show ( ) { this .ctx .body = await this .ctx .model .User .findOne ({ attributes :['name' ], include : [{ model : this .app .model .Userinfo , attributes : ['nickname' ] }], where : { id : 3 } }); }
一对多 1 2 3 4 5 6 7 8 9 10 11 class City extends Model {}City .init ({ countryCode : Sequelize .STRING }, { sequelize, modelName : "city" });class Country extends Model {}Country .init ( { isoCode : Sequelize .STRING }, { sequelize, modelName : "country" } ); Country .hasMany (City , { foreignKey : "countryCode" , sourceKey : "isoCode" });City .belongsTo (Country , { foreignKey : "countryCode" , targetKey : "isoCode" });
多对多 1 2 3 4 5 6 7 8 9 10 User .belongsToMany (Project , { as : "Tasks" , through : "worker_tasks" , foreignKey : "userId" , }); Project .belongsToMany (User , { as : "Workers" , through : "worker_tasks" , foreignKey : "projectId" , });
关联常用操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 let userinfo = await user.getUserinfo ();await user.getPosts ({ attributes : ["title" ], where : { id : 3 , }, }); await this .ctx .model .Post .create ({ title : "第一篇文章" , user_id : user.id , }); await user.getPosts ();await user.getPosts ({ attributes : ["id" ], where : { title : "测试" , }, }); let posts = await user.getPosts ({ attributes : ["id" ], }); posts = posts.map ((v ) => v.id ); await this .ctx .model .Post .destroy ({ where : { id : posts, }, }); await this .ctx .model .TopicUser .bulkCreate ([ { user_id : user.id , topic_id : 1 , }, { user_id : user.id , topic_id : 2 , }, ]); await this .ctx .model .TopicUser .destroy ({ where : { user_id : user.id , topic_id : [1 , 2 ], }, });
获取器和修改器 模型层
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 module .exports = (app ) => { const { STRING , INTEGER , DATE } = app.Sequelize ; const User = app.model .define ("user" , { id : { type : INTEGER , primaryKey : true , autoIncrement : true }, name : { type : STRING (30 ), get ( ) { const age = this .getDataValue ("age" ); return this .getDataValue ("name" ) + "年龄:" + age; }, }, age : { type : INTEGER , set (val ) { this .setDataValue ("age" , val * 10 ); }, }, created_at : DATE , updated_at : DATE , }); User .associate = function (models ) { app.model .User .hasOne (app.model .Userinfo ); }; return User ; };
控制器层
1 2 3 4 5 6 7 8 9 10 async show ( ) { let user = await this .ctx .model .User .findOne ({ where : { id : 3 } }); this .ctx .body = user.getDataValue ('name' ) }
模型钩子 模型层
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 module .exports = app => { ... User .beforeFind ((user, option ) => { console .log ('查询前' ); }); User .afterFind ((user, option ) => { console .log ('查询后' ); }); User .beforeCreate ((user, option ) => { console .log ('新增前' ); }); User .afterCreate ((user, option ) => { console .log ('新增后' ); }); User .beforeUpdate ((user, option ) => { console .log ('修改前' ); }); User .afterUpdate ((user, option ) => { console .log ('修改后' ); }); User .beforeDestroy ((user, option ) => { console .log ('删除前' ); }); User .afterDestroy ((user, option ) => { console .log ('删除后' ); }); User .beforeBulkDestroy ((user, option ) => { console .log ('批量删除前' ); }); User .afterBulkDestroy ((user, option ) => { console .log ('批量删除后' ); }); return User ; };
查询 主键查询
查找不存在则创建 方法 findOrCreate
可用于检查数据库中是否已存在某个元素. 如果是这种情况,则该方法将生成相应的实例. 如果元素不存在,将会被创建.
如果是这种情况,则该方法将导致相应的实例. 如果元素不存在,将会被创建.
假设我们有一个空的数据库,一个 User
模型有一个 username
和 job
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 User .findOrCreate ({ where : { username : "sdepold" , }, defaults : { job : "Technical Lead JavaScript" , }, }).then (([user, created] ) => { console .log ( user.get ({ plain : true , }) ); console .log (created); });
代码创建了一个新的实例. 所以当我们已经有一个实例了 …
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 User .create ({ username : "fnord" , job : "omnomnom" }) .then (() => User .findOrCreate ({ where : { username : "fnord" , }, defaults : { job : "something else" , }, }) ) .then (([user, created] ) => { console .log ( user.get ({ plain : true , }) ); console .log (created); });
…现有条目将不会更改. 看到第二个用户的 “job”,并且实际上创建操作是假的.
查找并计数 findAndCountAll
- 在数据库中搜索多个元素,返回数据和总计数
这是一个方便的方法,它结合了 findAll
和 count
(见下文),当处理与分页相关的查询时,这是有用的,你想用 limit
和 offset
检索数据,但也需要知道总数与查询匹配的记录数:
处理程序成功将始终接收具有两个属性的对象:
count
- 一个整数,总数记录匹配 where 语句和关联的其它过滤器
rows
- 一个数组对象,记录在 limit 和 offset 范围内匹配 where 语句和关联的其它过滤器,
1 2 3 4 5 6 7 8 9 10 11 12 Project .findAndCountAll ({ where : { title : { [Op .like ]: "foo%" , }, }, offset : 10 , limit : 2 , }).then ((result ) => { console .log (result.count ); console .log (result.rows ); });
它支持 include. 只有标记为 required
的 include 将被添加到计数部分:
假设你想查找附有个人资料的所有用户:
1 2 3 4 User .findAndCountAll ({ include : [{ model : Profile , required : true }], limit : 3 , });
因为 Profile
的 include 有 required
设置,这将导致内部连接,并且只有具有 profile 的用户将被计数. 如果我们从 include 中删除required
,那么有和没有 profile 的用户都将被计数. 在 include 中添加一个 where
语句会自动使它成为 required:
1 2 3 4 User .findAndCountAll ({ include : [{ model : Profile , where : { active : true } }], limit : 3 , });
上面的查询只会对具有 active profile 的用户进行计数,因为在将 where 语句添加到 include 时,required
被隐式设置为 true.
传递给 findAndCountAll
的 options 对象与 findAll
相同(如下所述).
查询多个(常用) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 Project .findAll ().then ((projects ) => { }); Project .findAll ({ where : { name : "A Project" } }).then ((projects ) => { }); Project .findAll ({ where : { id : [1 , 2 , 3 ] } }).then ((projects ) => { }); Project .findAll ({ where : { id : { [Op .and ]: { a : 5 }, [Op .or ]: [{ a : 5 }, { a : 6 }], [Op .gt ]: 6 , [Op .gte ]: 6 , [Op .lt ]: 10 , [Op .lte ]: 10 , [Op .ne ]: 20 , [Op .between ]: [6 , 10 ], [Op .notBetween ]: [11 , 15 ], [Op .in ]: [1 , 2 ], [Op .notIn ]: [1 , 2 ], [Op .like ]: "%hat" , [Op .notLike ]: "%hat" , [Op .iLike ]: "%hat" , [Op .notILike ]: "%hat" , [Op .overlap ]: [1 , 2 ], [Op .contains ]: [1 , 2 ], [Op .contained ]: [1 , 2 ], [Op .any ]: [2 , 3 ], }, status : { [Op .not ]: false , }, }, });
复合过滤 / OR / NOT 查询 你可以使用多层嵌套的 AND,OR 和 NOT 条件进行一个复合的 where 查询. 为了做到这一点,你可以使用 or
, and
或 not
运算符
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Project .findOne ({ where : { name : "a project" , [Op .or ]: [{ id : [1 , 2 , 3 ] }, { id : { [Op .gt ]: 10 } }], }, }); Project .findOne ({ where : { name : "a project" , id : { [Op .or ]: [[1 , 2 , 3 ], { [Op .gt ]: 10 }], }, }, });
这两段代码将生成以下内容:
1 2 3 4 5 6 7 SELECT *FROM `Projects` WHERE ( `Projects` .`name` = 'a project' AND (`Projects` .`id` IN (1 ,2 ,3 ) OR `Projects` .`id` > 10 ) ) LIMIT 1 ;
not
示例:
1 2 3 4 5 6 Project .findOne ({ where : { name : "a project" , [Op .not ]: [{ id : [1 , 2 , 3 ] }, { array : { [Op .contains ]: [3 , 4 , 5 ] } }], }, });
将生成:
1 2 3 4 5 6 7 SELECT *FROM `Projects` WHERE ( `Projects` .`name` = 'a project' AND NOT (`Projects` .`id` IN (1 ,2 ,3 ) OR `Projects` .`array` @> ARRAY [3 ,4 ,5 ]::INTEGER []) ) LIMIT 1 ;
用限制,偏移,顺序和分组操作数据集 要获取更多相关数据,可以使用限制,偏移,顺序和分组:
1 2 3 4 5 6 7 8 Project .findAll ({ limit : 10 });Project .findAll ({ offset : 10 });Project .findAll ({ offset : 10 , limit : 2 });
分组和排序的语法是相同的,所以下面只用一个单独的例子来解释分组,而其余的则是排序. 你下面看到的所有内容也可以对分组进行
1 2 3 4 5 Project .findAll ({ order : [["title" , "DESC" ]] });Project .findAll ({ group : "name" });
请注意,在上述两个示例中,提供的字符串逐字插入到查询中,所以不会转义列名称. 当你向 order / group 提供字符串时,将始终如此. 如果要转义列名,你应该提供一个参数数组,即使你只想通过单个列进行 order / group
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 something.findOne ({ order : [ ["name" ], ["username" , "DESC" ], sequelize.fn ("max" , sequelize.col ("age" )), [sequelize.fn ("max" , sequelize.col ("age" )), "DESC" ], [ sequelize.fn ("otherfunction" , sequelize.col ("col1" ), 12 , "lalala" ), "DESC" , ], [ sequelize.fn ( "otherfunction" , sequelize.fn ("awesomefunction" , sequelize.col ("col" )) ), "DESC" , ], ], });
回顾一下,order / group 数组的元素可以是以下内容:
String - 将被引用
Array - 第一个元素将被引用,第二个将被逐字地追加
Object -
raw 将被添加逐字引用
如果未设置 raw,一切都被忽略,查询将失败
Sequelize.fn 和 Sequelize.col 返回函数和引用的列名
字段过滤 想要只选择某些属性,可以使用 attributes
选项. 通常是传递一个数组:
1 2 3 Model .findAll ({ attributes : ["foo" , "bar" ], });
SELECT foo, bar …
属性可以使用嵌套数组来重命名:
1 2 3 Model .findAll ({ attributes : ["foo" , ["bar" , "baz" ]], });
SELECT foo, bar AS baz …
也可以使用 sequelize.fn
来进行聚合:
1 2 3 Model .findAll ({ attributes : [[sequelize.fn ("COUNT" , sequelize.col ("hats" )), "no_hats" ]], });
SELECT COUNT(hats) AS no_hats …
使用聚合功能时,必须给它一个别名,以便能够从模型中访问它. 在上面的例子中,你可以使用 instance.get('no_hats')
获得帽子数量.
有时,如果你只想添加聚合,则列出模型的所有属性可能令人厌烦:
1 2 3 4 5 6 7 8 9 10 Model .findAll ({ attributes : ['id' , 'foo' , 'bar' , 'baz' , 'quz' , [sequelize.fn ('COUNT' , sequelize.col ('hats' )), 'no_hats' ]] }); Model .findAll ({ attributes : { include : [[sequelize.fn ('COUNT' , sequelize.col ('hats' )), 'no_hats' ]] } }); SELECT id, foo, bar, baz, quz, COUNT (hats) AS no_hats ...
同样,它也可以排除一些指定的表字段:
1 2 3 4 Model .findAll ({ attributes : { exclude : ['baz' ] } }); SELECT id, foo, bar, quz ...
Where 无论你是通过 findAll/find 或批量 updates/destroys 进行查询,都可以传递一个 where
对象来过滤查询.
where
通常用 attribute:value 键值对获取一个对象,其中 value 可以是匹配等式的数据或其他运算符的键值对象.
也可以通过嵌套 or
和 and
运算符
的集合来生成复杂的 AND/OR 条件.
基础 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 const Op = Sequelize .Op ;Post .findAll ({ where : { authorId : 2 , }, }); Post .findAll ({ where : { authorId : 12 , status : "active" , }, }); Post .findAll ({ where : { [Op .or ]: [{ authorId : 12 }, { authorId : 13 }], }, }); Post .findAll ({ where : { authorId : { [Op .or ]: [12 , 13 ], }, }, }); Post .destroy ({ where : { status : "inactive" , }, }); Post .update ( { updatedAt : null , }, { where : { deletedAt : { [Op .ne ]: null , }, }, } ); Post .findAll ({ where : sequelize.where ( sequelize.fn ("char_length" , sequelize.col ("status" )), 6 ), });
操作符 Sequelize 可用于创建更复杂比较的符号运算符 -
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 const Op = Sequelize .Op [Op .and ]: {a : 5 } [Op .or ]: [{a : 5 }, {a : 6 }] [Op .gt ]: 6 , [Op .gte ]: 6 , [Op .lt ]: 10 , [Op .lte ]: 10 , [Op .ne ]: 20 , [Op .eq ]: 3 , [Op .not ]: true , [Op .between ]: [6 , 10 ], [Op .notBetween ]: [11 , 15 ], [Op .in ]: [1 , 2 ], [Op .notIn ]: [1 , 2 ], [Op .like ]: '%hat' , [Op .notLike ]: '%hat' [Op .iLike ]: '%hat' [Op .notILike ]: '%hat' [Op .startsWith ]: 'hat' [Op .endsWith ]: 'hat' [Op .substring ]: 'hat' [Op .regexp ]: '^[h|a|t]' [Op .notRegexp ]: '^[h|a|t]' [Op .iRegexp ]: '^[h|a|t]' [Op .notIRegexp ]: '^[h|a|t]' [Op .like ]: { [Op .any ]: ['cat' , 'hat' ]} [Op .overlap ]: [1 , 2 ] [Op .contains ]: [1 , 2 ] [Op .contained ]: [1 , 2 ] [Op .any ]: [2 ,3 ] [Op .col ]: 'user.organization_id'
范围选项 所有操作符都支持支持的范围类型查询.
请记住,提供的范围值也可以定义绑定的 inclusion/exclusion .
1 2 3 4 5 6 7 8 9 10 11 [Op .contains ]: 2 [Op .contains ]: [1 , 2 ] [Op .contained ]: [1 , 2 ] [Op .overlap ]: [1 , 2 ] [Op .adjacent ]: [1 , 2 ] [Op .strictLeft ]: [1 , 2 ] [Op .strictRight ]: [1 , 2 ] [Op .noExtendRight ]: [1 , 2 ] [Op .noExtendLeft ]: [1 , 2 ]
组合 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 { rank : { [Op .or ]: { [Op .lt ]: 1000 , [Op .eq ]: null } } } { createdAt : { [Op .lt ]: new Date (), [Op .gt ]: new Date (new Date () - 24 * 60 * 60 * 1000 ) } } { [Op .or ]: [ { title : { [Op .like ]: 'Boat%' } }, { description : { [Op .like ]: '%boat%' } } ] }
关系 / 关联 1 2 3 4 5 6 7 8 9 Project .findAll ({ include : [ { model : Task , where : { state : Sequelize .col ("project.state" ) }, }, ], });
分页 / 限制 1 2 3 4 5 6 7 8 Project .findAll ({ limit : 10 });Project .findAll ({ offset : 8 });Project .findAll ({ offset : 5 , limit : 5 });
排序 order
需要一个条目的数组来排序查询或者一个 sequelize 方法.一般来说,你将要使用任一属性的 tuple/array,并确定排序的正反方向.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 Subtask .findAll ({ order : [ ['title' , 'DESC' ], sequelize.fn ('max' , sequelize.col ('age' )), [sequelize.fn ('max' , sequelize.col ('age' )), 'DESC' ], [sequelize.fn ('otherfunction' , sequelize.col ('col1' ), 12 , 'lalala' ), 'DESC' ], [Task , 'createdAt' , 'DESC' ], [Task , Project , 'createdAt' , 'DESC' ], ['Task' , 'createdAt' , 'DESC' ], ['Task' , 'Project' , 'createdAt' , 'DESC' ], [Subtask .associations .Task , 'createdAt' , 'DESC' ], [Subtask .associations .Task , Task .associations .Project , 'createdAt' , 'DESC' ], [{model : Task , as : 'Task' }, 'createdAt' , 'DESC' ], [{model : Task , as : 'Task' }, {model : Project , as : 'Project' }, 'createdAt' , 'DESC' ] ] order : sequelize.literal ('max(age) DESC' ) order : sequelize.fn ('max' , sequelize.col ('age' )) order : sequelize.col ('age' ) order : sequelize.random () })
count
- 计算数据库中元素的出现次数还有一种数据库对象计数的方法:
1 2 3 4 5 6 7 Project .count ().then ((c ) => { console .log ("There are " + c + " projects!" ); }); Project .count ({ where : { id : { [Op .gt ]: 25 } } }).then ((c ) => { console .log ("There are " + c + " projects with an id greater than 25." ); });
max
- 获取特定表中特定属性的最大值这里是获取属性的最大值的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 Project .max ("age" ).then ((max ) => { }); Project .max ("age" , { where : { age : { [Op .lt ]: 20 } } }).then ((max ) => { });
min
- 获取特定表中特定属性的最小值这里是获取属性的最小值的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 Project .min ("age" ).then ((min ) => { }); Project .min ("age" , { where : { age : { [Op .gt ]: 5 } } }).then ((min ) => { });
sum
- 特定属性的值求和为了计算表的特定列的总和,可以使用“sum”方法.
1 2 3 4 5 6 7 8 9 10 11 12 13 Project .sum ("age" ).then ((sum ) => { }); Project .sum ("age" , { where : { age : { [Op .gt ]: 5 } } }).then ((sum ) => { });
预加载 当你从数据库检索数据时,也想同时获得与之相关联的查询,这被称为预加载.这个基本思路就是当你调用 find
或 findAll
时使用 include
属性.让我们假设以下设置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 Task .findAll ({ include : [User ] }).then ((tasks ) => { console .log (JSON .stringify (tasks)); });
请注意,访问者(结果实例中的 User
属性)是单数形式,因为关联是一对一的.
接下来的事情:用多对一的关联加载数据!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 User .findAll ({ include : [Task ] }).then ((users ) => { console .log (JSON .stringify (users)); });
请注意,访问者(结果实例中的 Tasks
属性)是复数形式,因为关联是多对一的.
如果关联是别名的(使用 as
参数),则在包含模型时必须指定此别名. 注意用户的 Tool
如何被别名为 Instruments
. 为了获得正确的权限,你必须指定要加载的模型以及别名:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 User .findAll ({ include : [{ model : Tool , as : "Instruments" }] }).then ( (users ) => { console .log (JSON .stringify (users)); } );
你还可以通过指定与关联别名匹配的字符串来包含别名:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 User .findAll ({ include : ["Instruments" ] }).then ((users ) => { console .log (JSON .stringify (users)); }); User .findAll ({ include : [{ association : "Instruments" }] }).then ((users ) => { console .log (JSON .stringify (users)); });
当预加载时,我们也可以使用 where
过滤关联的模型. 这将返回 Tool
模型中所有与 where
语句匹配的行的User
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 User .findAll ({ include : [ { model : Tool , as : "Instruments" , where : { name : { [Op .like ]: "%ooth%" } }, }, ], }).then ((users ) => { console .log (JSON .stringify (users)); });
当使用 include.where
过滤一个预加载的模型时,include.required
被隐式设置为 true
. 这意味着内部联接完成返回具有任何匹配子项的父模型.
使用预加载模型的顶层 WHERE 将模型的 WHERE
条件从 ON
条件的 include 模式移动到顶层,你可以使用 '$nested.column$'
语法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 User .findAll ({ where : { '$Instruments.name$' : { [Op .iLike ]: '%ooth%' } }, include : [{ model : Tool , as : 'Instruments' }] }).then (users => { console .log (JSON .stringify (users));
包括所有 要包含所有属性,你可以使用 all:true
传递单个对象:
1 User .findAll ({ include : [{ all : true }] });
包括软删除的记录 如果想要加载软删除的记录,可以通过将 include.paranoid
设置为 false
来实现
1 2 3 4 5 6 7 8 9 User .findAll ({ include : [ { model : Tool , where : { name : { [Op .like ]: "%ooth%" } }, paranoid : false , }, ], });
排序预加载关联 在一对多关系的情况下.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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" ]], });
在多对多关系的情况下,你还可以通过表中的属性进行排序.
1 2 3 4 Company .findAll ({ include : [{ model : Division , include : [Department ] }], order : [[Division , DepartmentDivision , "name" ]], });
嵌套预加载 你可以使用嵌套的预加载来加载相关模型的所有相关模型:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 User .findAll ({ include : [ { model : Tool , as : "Instruments" , include : [ { model : Teacher , include : [ ], }, ], }, ], }).then ((users ) => { console .log (JSON .stringify (users)); });
这将产生一个外连接. 但是,相关模型上的 where
语句将创建一个内部连接,并仅返回具有匹配子模型的实例. 要返回所有父实例,你应该添加 required: false
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 User .findAll ({ include : [ { model : Tool , as : "Instruments" , include : [ { model : Teacher , where : { school : "Woodstock Music School" , }, required : false , }, ], }, ], }).then ((users ) => { });
以上查询将返回所有用户及其所有乐器,但只会返回与 Woodstock Music School
相关的老师.
包括所有也支持嵌套加载:
1 User .findAll ({ include : [{ all : true , nested : true }] });
新增 字段限制 1 2 3 4 5 6 7 8 9 10 11 await User .create ( { username : "barfooz" , isAdmin : true }, { fields : ["username" ] } ); User .bulkCreate ([{ username : "foo" }, { username : "bar" , admin : true }], { fields : ["username" ], }).then (() => { });
新增单个 1 2 3 4 5 this .ctx .body = await this .ctx .model .User .create ({ name : "哈哈哈" , age : 12 , });
批量新增 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 this .ctx .body = await this .ctx .model .User .bulkCreate ([ { name : "第一个" , age : 15 , }, { name : "第二个" , age : 15 , }, { name : "第三个" , age : 15 , }, ]);
修改 字段限制 1 2 3 4 5 6 7 8 9 10 11 task.title = "foooo" ; task.description = "baaaaaar" ; await task.save ({ fields : ["title" ] });await task.update ( { title : "foooo" , description : "baaaaaar" }, { fields : ["title" ] } );
单个修改 1 2 3 4 5 6 const user = await this .ctx .model .User .findByPk (1 );await user.update ({ name : "我被修改了" , age : 30 , });
批量修改 1 2 3 4 5 6 7 8 9 10 11 12 await this .ctx .model .User .update ( { name : "批量修改" , }, { where : { name : "第一个" , }, } );
递增 1 2 3 4 5 6 const user = await this .ctx .model .User .findByPk (2 );this .ctx .body = await user.increment ({ age : 3 , other : 2 , });
递减 1 2 3 4 5 6 const user = await this .ctx .model .User .findByPk (2 );this .ctx .body = await user.decrement ({ age : 3 , other : 2 , });
删除 软删除 模型中配置
1 2 3 4 5 6 7 8 9 10 11 const User = app.model .define ( "user" , { }, { paranoid : true , } );
查询包括软删除内容 1 2 3 4 5 6 7 8 9 10 11 12 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 调用:
1 task.destroy ({ force : true });
在 paranoid
模式下对象被软删除后,在强制删除旧实例之前,你将无法使用相同的主键创建新实例.
恢复软删除的实例 如果你使用 paranoid:true
软删除了模型的实例,之后想要撤消删除,请使用 restore
方法:
1 2 3 4 task.destroy (); task.restore ();
条件删除 1 2 3 4 5 await this .ctx .model .User .destroy ({ where : { name : "批量修改" , }, });
批量删除 1 2 3 4 5 await this .ctx .model .Post .destroy ({ where : { id : posts, }, });
重载实例 如果你需要让你的实例同步,你可以使用 reload
方法. 它将从数据库中获取当前数据,并覆盖调用该方法的模型的属性.
1 2 3 4 5 6 7 8 Person .findOne ({ where : { name : "john" } }).then ((person ) => { person.name = "jane" ; console .log (person.name ); person.reload ().then (() => { console .log (person.name ); }); });
模型自定义方法 1 2 3 4 5 6 7 8 9 10 topic_user.ceshi = (param ) => { console .log ("模型自定义方法" ); console .log (param); return param; }; await this .ctx .model .TopicUser .ceshi (123 );
Scopes - 作用域(重点) 作用域允许你定义常用查询,以便以后轻松使用. 作用域可以包括与常规查找器 where
, include
, limit
等所有相同的属性.
定义 作用域在模型定义中定义,可以是 finder 对象或返回 finder 对象的函数,除了默认作用域,该作用域只能是一个对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 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()
将创建以下查询:
1 SELECT * FROM projects WHERE active = true
可以通过调用 .unscoped()
, .scope(null)
或通过调用另一个作用域来删除默认作用域:
1 2 Project .scope ('deleted' ).findAll (); SELECT * FROM projects WHERE deleted = true
还可以在作用域定义中包含作用域模型. 这让你避免重复 include
,attributes
或 where
定义.
使用上面的例子,并在包含的用户模型中调用 active
作用域(而不是直接在该 include 对象中指定条件):
1 2 3 activeUsers : { include : [{ model : User .scope ("active" ) }]; }
使用 通过在模型定义上调用 .scope
来应用作用域,传递一个或多个作用域的名称. .scope
返回一个全功能的模型实例,它具有所有常规的方法:.findAll
,.update
,.count
,.destroy
等等.你可以保存这个模型实例并稍后再次使用:
1 2 3 4 5 6 7 const DeletedProjects = Project .scope ("deleted" );DeletedProjects .findAll ();DeletedProjects .findAll ();
作用域适用于 .find
, .findAll
, .count
, .update
, .increment
和 .destroy
.
可以通过两种方式调用作为函数的作用域. 如果作用域没有任何参数,它可以正常调用. 如果作用域采用参数,则传递一个对象:
1 2 Project .scope ('random' , { method : ['accessLevel' , 19 ]}).findAll ();SELECT * FROM projects WHERE someNumber = 42 AND accessLevel >= 19
合并 通过将作用域数组传递到 .scope
或通过将作用域作为连续参数传递,可以同时应用多个作用域.
1 2 3 4 5 6 7 Project .scope ('deleted' , 'activeUsers' ).findAll ();Project .scope (['deleted' , 'activeUsers' ]).findAll ();SELECT * FROM projectsINNER JOIN users ON projects.userId = users.id WHERE projects.deleted = true AND users.active = true
如果要将其他作用域与默认作用域一起应用,请将键 defaultScope
传递给 .scope
:
1 2 Project .scope ('defaultScope' , 'deleted' ).findAll ();SELECT * FROM projects WHERE active = true AND deleted = true
当调用多个作用域时,后续作用域的键将覆盖以前的作用域(类似于 Object.assign ),除了where
和include
,它们将被合并. 考虑两个作用域:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 { scope1 : { where : { firstName : 'bob' , age : { [Op .gt ]: 20 } }, limit : 2 }, scope2 : { where : { age : { [Op .gt ]: 30 } }, limit : 10 } }
调用 .scope('scope1', 'scope2')
将产生以下查询
1 WHERE firstName = 'bob' AND age > 30 LIMIT 10
注意 scope2
将覆盖 limit
和 age
,而 firstName
被保留. limit
,offset
,order
,paranoid
,lock
和raw
字段被覆盖,而where
被浅层合并(意味着相同的键将被覆盖). include
的合并策略将在后面讨论.
请注意,多个应用作用域的 attributes
键以这样的方式合并,即始终保留 attributes.exclude
. 这允许合并多个作用域,并且永远不会泄漏最终作用域内的敏感字段.
将查找对象直接传递给作用域模型上的findAll
(和类似的查找程序)时,适用相同的合并逻辑:
1 2 3 4 5 6 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,具有如下多种关联:
1 2 3 4 5 6 7 8 9 10 11 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 上定义的以下四个作用域:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 { 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()
,这完全等同于调用以下内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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 来实现多态关联:
1 2 3 4 5 6 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 })
. 同样,如果要应用其他作用域,请像这样:
1 User .getPosts ({ scope : ["scope1" , "scope2" ] });
如果要为关联模型上的作用域创建快捷方式,可以将作用域模型传递给关联. 考虑一个快捷方式来获取用户所有已删除的帖子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class Post extends Model {}Post .init (attributes, { defaultScope : { where : { active : true , }, }, scopes : { deleted : { where : { deleted : true , }, }, }, sequelize, }); User .hasMany (Post ); User .hasMany (Post .scope ("deleted" ), { as : "deletedPosts" });User .getPosts (); User .getDeletedPosts ();
扩展 extend/helper.js
1 2 3 4 5 6 7 8 module .exports = { formatTime (val ) { let d = new Date (val * 1000 ); return d.getFullYear (); }, };
模板中调用
1 <%=helper.formatTime(dateline)%>
其他地方调用
1 this .ctx .helper .formatTime (dateline);
中间件 1. 定义 app/middleware/getIp.js
1 2 3 4 5 6 7 8 9 10 11 12 13 module .exports = (option, app ) => { return async function (ctx, next ) { console .log (option); console .log (ctx.request .ip ); await next (); }; };
2. 配置 config/config.default.js(配置全局中间件,所有路由都会调用)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 module .exports = appInfo => { ... config.middleware = ['getIp' ]; config.getIp = { ceshi : 123 , enable :true , match :'/news' , ignore :'/shop' match (ctx ) { const reg = /iphone|ipad|ipod/i ; return reg.test (ctx.get ('user-agent' )); }, }; ... };
3. 使用 路由中使用 app/router.js
1 2 3 4 5 6 7 8 9 10 module .exports = (app ) => { router.get ( "/admin/:id" , app.middleware .getIp ({ ceshi : "我是admin" , }), controller.admin .index ); };
使用 Koa 的中间件(gzip 压缩) 大大提高网站的访问速度(非常有效)
以 koa-compress 为例,在 Koa 中使用时:
1 2 3 4 5 6 7 const koa = require ("koa" );const compress = require ("koa-compress" );const app = koa ();const options = { threshold : 2048 };app.use (compress (options));
我们按照框架的规范来在应用中加载这个 Koa 的中间件:
1 2 3 4 5 6 7 8 9 10 11 module .exports = require ("koa-compress" );module .exports = { middleware : ["compress" ], compress : { threshold : 2048 , }, };
表单提交 post app/controller/home.js
1 2 3 4 5 6 7 8 async addInput (ctx ) { await ctx.render ('post' ); } async add (ctx ) { console .log (ctx.request .body ); }
app/view/post.html
1 2 3 4 5 6 7 8 <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
1 2 router.get ("/post" , controller.home .addInput ); router.post ("/add" , controller.home .add );
cookie 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 ctx.cookies .set ("username" , "ceshi" ); ctx.cookies .get ("username" ); ctx.cookies .set ("username" , "ceshi" , { maxAge : 1000 * 3600 * 24 , httpOnly : true , signed : true , encrypt : true , }); ctx.cookies .get ("username" , { encrypt : true , }); ctx.cookies .set ("username" , null );
session 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ctx.session .username = "测试" ; ctx.session .username ; exports .session = { key : "EGG_SESS" , maxAge : 24 * 3600 * 1000 , httpOnly : true , encrypt : true , renew : true , }; ctx.session .maxAge = 5000 ; ctx.session .username = "测试" ; ctx.session .username = null ;
定时任务 1 2 3 4 5 6 7 8 9 10 11 12 13 14 var i = 1 ;module .exports = { schedule : { interval : "5s" , type : "all" , }, async task (ctx ) { ++i; console .log (i); }, };
API 1. context curl 1 2 3 4 5 6 7 async ceshi ( ) { let r = await this .ctx .curl ('http://www.phonegap100.com/appapi.php?a=getPortalList&catid=20&page=1' ); 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 头添加:
1 2 Authorization : "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6MTIzLCJpYXQiOjE1NzkxOTQxNTN9.Ml5B02ZPfYo78QwJic-Jdp2LUi2_AU0RGNgPhhJH--o" ;