egg基础教程

安装egg.js

全局切换镜像:
npm config set registry https://registry.npm.taobao.org

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

mkdir egg-example && cd egg-example
npm init egg --type=simple --registry https://registry.npm.taobao.org
npm i

启动项目:

npm run dev
open http://localhost:7001

写第一个api接口

安装vscode扩展

创建控制器

async index() {const { ctx } = this;// 获取路由get传值参数(路由:id)ctx.params;// 获取url的问号get传值参数ctx.query;// 响应ctx.body = '响应';// 状态码ctx.status = 201;
}

编写路由

基础用法

// router.js
router.get('/admin/:id', controller.admin.index);// controller
async index() {const { ctx } = this;// 获取路由get传值参数(路由:id)ctx.params;// 获取url的问号get传值参数ctx.query;
}

资源路由

// app/router.js
module.exports = app => {const { router, controller } = app;router.resources('posts', '/api/posts', controller.posts);// app/controller/v1/users.jsrouter.resources('users', '/api/v1/users', controller.v1.users);
};

上面代码就在 /posts 路径上部署了一组 CRUD 路径结构,对应的 Controller 为 app/controller/posts.js 接下来, 你只需要在 posts.js 里面实现对应的函数就可以了。

Method Path Route Name Controller.Action
GET /posts posts app.controllers.posts.index
GET /posts/new new_post app.controllers.posts.new
GET /posts/:id post app.controllers.posts.show
GET /posts/:id/edit edit_post app.controllers.posts.edit
POST /posts posts app.controllers.posts.create
PUT /posts/:id post app.controllers.posts.update
DELETE /posts/:id post app.controllers.posts.destroy
// app/controller/posts.js// 列表页
exports.index = async () => {};
// 新增表单页
exports.new = async () => {};
// 新增逻辑
exports.create = async () => {};
// 详情页
exports.show = async () => {};
// 编辑表单页
exports.edit = async () => {};
// 更新逻辑
exports.update = async () => {};
// 删除逻辑
exports.destroy = async () => {};

路由分组

// app/router.js
module.exports = app => {require('./router/news')(app);require('./router/admin')(app);
};// app/router/news.js
module.exports = app => {app.router.get('/news/list', app.controller.news.list);app.router.get('/news/detail', app.controller.news.detail);
};// app/router/admin.js
module.exports = app => {app.router.get('/admin/user', app.controller.admin.user);app.router.get('/admin/log', app.controller.admin.log);
};

关闭csrf开启跨域

文档:https://www.npmjs.com/package/egg-cors

  • 安装 npm i egg-cors --save
  • 配置插件
// {app_root}/config/plugin.js
exports.cors = {enable: true,package: 'egg-cors',
};
  • config / config.default.js 目录下配置
  config.security = {// 关闭 csrfcsrf: {enable: false,},// 跨域白名单domainWhiteList: [ 'http://localhost:3000' ],};// 允许跨域的方法config.cors = {origin: '*',allowMethods: 'GET, PUT, POST, DELETE, PATCH'};

数据库

配置和创建迁移文件

配置

  1. 安装并配置egg-sequelize插件(它会辅助我们将定义好的 Model 对象加载到 app 和 ctx 上)和mysql2模块:
npm install --save egg-sequelize mysql2
  1. config/plugin.js中引入 egg-sequelize 插件
exports.sequelize = {enable: true,package: 'egg-sequelize',
};
  1. config/config.default.js
config.sequelize = {dialect:  'mysql',host:  '127.0.0.1',username: 'root',password:  'root',port:  3306,database:  'eggapi',// 中国时区timezone:  '+08:00',define: {// 取消数据表名复数freezeTableName: true,// 自动写入时间戳 created_at updated_attimestamps: true,// 字段生成软删除时间戳 deleted_atparanoid: true,createdAt: 'created_at',updatedAt: 'updated_at',deletedAt: 'deleted_at',// 所有驼峰命名格式化underscored: true}
};
  1. sequelize 提供了sequelize-cli工具来实现Migrations,我们也可以在 egg 项目中引入 sequelize-cli。
npm install --save-dev sequelize-cli
  1. egg 项目中,我们希望将所有数据库 Migrations 相关的内容都放在database目录下,所以我们在项目根目录下新建一个.sequelizerc配置文件:
'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'),
};
  1. 初始化 Migrations 配置文件和目录
npx sequelize init:config
npx sequelize init:migrations
// npx sequelize init:models
  1. 行完后会生成database/config.json文件和database/migrations目录,我们修改一下database/config.json中的内容,将其改成我们项目中使用的数据库配置:
{"development": {"username": "root","password": null,"database": "eggapi","host": "127.0.0.1","dialect": "mysql","timezone": "+08:00"}
}
  1. 创建数据库
npx sequelize db:create

创建数据迁移表

npx sequelize migration:generate --name=init-user

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

'use strict';module.exports = {up: async (queryInterface, Sequelize) => {const { INTEGER, STRING, DATE, ENUM } = Sequelize;// 创建表await queryInterface.createTable('user', {id: { type: INTEGER(20).UNSIGNED, primaryKey: true, autoIncrement: true },username: { type: STRING(30), allowNull: false, defaultValue: '', comment: '用户名称', unique: true},password: { type: STRING(200), allowNull: false, defaultValue: '' },avatar_url: { type: STRING(200), allowNull: true, defaultValue: '' },sex: { type: ENUM, values: ['男','女','保密'], allowNull: true, defaultValue: '男', comment: '用户性别'},created_at: DATE,updated_at: DATE});},down: async queryInterface => {await queryInterface.dropTable('user')}
};
  • 执行 migrate 进行数据库变更
# 升级数据库
npx sequelize db:migrate
# 如果有问题需要回滚,可以通过 `db:migrate:undo` 回退一个变更
# npx sequelize db:migrate:undo
# 可以通过 `db:migrate:undo:all` 回退到初始状态
# npx sequelize db:migrate:undo:all

模型

创建模型

// app / model / user.js'use strict';
module.exports = app => {const { STRING, INTEGER, DATE } = app.Sequelize;// 配置(重要:一定要配置详细,一定要!!!)const User = app.model.define('user', {id: { type: INTEGER(20).UNSIGNED, primaryKey: true, autoIncrement: true },username: { type: STRING(30), allowNull: false, defaultValue: '', comment: '用户名称', unique: true},password: { type: STRING(200), allowNull: false, defaultValue: '' },avatar_url: { type: STRING(200), allowNull: true, defaultValue: '' },sex: { type: ENUM, values: ['男','女','保密'], allowNull: true, defaultValue: '男', comment: '用户性别'},created_at: DATE,updated_at: DATE},{timestamps: true, // 是否自动写入时间戳tableName: 'user', // 自定义数据表名称});return User;
};

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

// app/controller/user.js
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;let keyword = this.ctx.params.keyword;let Op = this.app.Sequelize.Op;ctx.body = await ctx.model.User.findAll({ where:{id:{[Op.gt]:6},username: {[Op.like]:'%'+keyword+'%'}},attributes:['id','username','sex'],order:[['id','DESC']],limit: toInt(ctx.query.limit), offset: toInt(ctx.query.offset) });}async show() {let id = parseInt(this.ctx.params.id);// 通过主键查询单个数据// let detail = await this.app.model.User.findByPk(id);// if (!detail) {//     return this.ctx.body = {//         msg: "fail",//         data: "用户不存在"//     }// }// 查询单个let detail = await this.app.model.User.findOne({where: {id,sex: "女"}});this.ctx.body = {msg: 'ok',data: detail};}async create() {const ctx = this.ctx;const { name, age } = ctx.request.body;// 创建单条记录const user = await ctx.model.User.create({ name, age });// 批量创建await ctx.model.User.bulkCreate([{name: "第一个",age: 15},{name: "第二个",age: 15},{name: "第三个",age: 15}]);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 挂载到路由上:

// app/router.js
module.exports = app => {const { router, controller } = app;router.resources('user', '/user', controller.user);
};

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

错误和异常处理

// app/middleware/error_handler.js
module.exports = (option, app) => {return async function errorHandler(ctx, next) {try {await next();} catch (err) {// 所有的异常都在 app 上触发一个 error 事件,框架会记录一条错误日志ctx.app.emit('error', err, ctx);const status = err.status || 500;// 生产环境时 500 错误的详细错误内容不返回给客户端,因为可能包含敏感信息const error = status === 500 && ctx.app.config.env === 'prod'? 'Internal Server Error': err.message;// 从 error 对象上读出各个属性,设置到响应中ctx.body = { error };if (status === 422) {ctx.body.detail = err.errors;}ctx.status = status;}};};

中间件

config.middleware = ['errorHandler'];config.errorHandler = {ceshi: 123,// 通用配置(以下是重点)enable:true, // 控制中间件是否开启。match:'/news', // 设置只有符合某些规则的请求才会经过这个中间件(匹配路由)ignore:'/shop' // 设置符合某些规则的请求不经过这个中间件。/**注意:1. match 和 ignore 不允许同时配置2. 例如:match:'/news',只要包含/news的任何页面都生效**/// match 和 ignore 支持多种类型的配置方式:字符串、正则、函数(推荐)match(ctx) {// 只有 ios 设备才开启const reg = /iphone|ipad|ipod/i;return reg.test(ctx.get('user-agent'));},
};

参数验证

https://www.npmjs.com/package/egg-valparams

npm i egg-valparams --save
// config/plugin.js
valparams : {enable : true,package: 'egg-valparams'
},
// config/config.default.js
config.valparams = {locale    : 'zh-cn',throwError: false
};
class XXXController extends app.Controller {// ...async XXX() {const {ctx} = this;ctx.validate({system  : {type: 'string', required: false, defValue: 'account', desc: '系统名称'},token   : {type: 'string', required: true, desc: 'token 验证'},redirect: {type: 'string', required: false, desc: '登录跳转'}});// if (config.throwError === false)if(ctx.paramErrors) {// get error infos from `ctx.paramErrors`;}let params = ctx.params;let {query, body} = ctx.request;// ctx.params        = validater.ret.params;// ctx.request.query = validater.ret.query;// ctx.request.body  = validater.ret.body;// ...ctx.body = query;}// ...
}

# 安装egg

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

mkdir egg-example && cd egg-example
npm init egg --type=simple
npm i

启动项目:

npm run dev
open http://localhost:7001

目录结构

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. get传值

// router.js
router.get('/admin/:id', controller.admin.index);// controller
async index(ctx) {// 获取路由get传值参数(路由:id)ctx.params;// 获取url的问号get传值参数ctx.query;
}

2. 4种配置方法

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

重定向

1. ctx

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

2. 路由重定向

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

3.路由分组

// app/router.js
module.exports = app => {require('./router/news')(app);require('./router/admin')(app);
};// app/router/news.js
module.exports = app => {app.router.get('/news/list', app.controller.news.list);app.router.get('/news/detail', app.controller.news.detail);
};// app/router/admin.js
module.exports = app => {app.router.get('/admin/user', app.controller.admin.user);app.router.get('/admin/log', app.controller.admin.log);
};

控制器

自定义 Controller 基类

// app/core/base_controller.js
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,直接使用基类上的方法:

//app/controller/post.js
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)安装:

npm i egg-view-ejs --save

(2)配置:/config

config/config.default.js

module.exports = appInfo => {...config.view = {mapping: {'.html': 'ejs',},};...
};

config/plugin.js

module.exports = {// 配置ejsejs: {enable: true,package: 'egg-view-ejs',}};

(3)使用

app/controller

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

app/view/index.html

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>Document</title>
</head><body><!--渲染变量--><%=msg%><ul><% for(var i=0; i < list.length; i++){ %><li><%=list[i]%></li><% } %></ul><!--加载 app/public 下的资源文件--><img src="/public/images/find.png">
</body></html>

服务(模型)

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

await this.service.home.ceshi();

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

模型和数据库

配置和创建迁移文件

配置

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

创建数据迁移表

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

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

'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')}
};
  • 执行 migrate 进行数据库变更
# 升级数据库
npx sequelize db:migrate
# 如果有问题需要回滚,可以通过 `db:migrate:undo` 回退一个变更
# npx sequelize db:migrate:undo
# 可以通过 `db:migrate:undo:all` 回退到初始状态
# npx sequelize db:migrate:undo:all

已创建新增字段

1.创建迁移文件:

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

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

'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 进行数据库变更

npx sequelize db:migrate

创建模型

// app / model / user.js'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

// app/controller/users.js
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 挂载到路由上:

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

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

模型其他参数

// 配置(重要)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',// 是否需要增加createdAt、updatedAt、deletedAt字段'timestamps': true,// 不需要createdAt字段'createdAt': false,// 将updatedAt字段改个名'updatedAt': 'utime',// 将deletedAt字段改名// 同时需要设置paranoid为true(此种模式下,删除数据时不会进行物理删除,而是设置deletedAt为当前时间'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 删除由配置指定的数据库

外键约束(重要)

// 迁移文件
queryInterface.addConstraint('tableName', ['user_id'], {type: 'foreign key',name: 'user_id',references: { //Required fieldtable: 'users',field: 'id'},onDelete: 'cascade',onUpdate: 'cascade'
});

创建第一个种子

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

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

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

npx sequelize seed:generate --name demo-user

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

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

'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, {});}
};

运行种子

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

npx sequelize db:seed:all

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

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

撤销种子

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

如果你想撤消最近的种子

npx sequelize db:seed:undo

如果你想撤消特定的种子

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

如果你想撤消所有的种子

npx sequelize db:seed:undo:all

关联操作

一对一

模型层:

// 一个用户对应一个用户资料// app/model/user.js
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;
};// app/model/userinfo.js
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;
};

控制器调用:

// app/controller/users.js
// 显示单条
async show() {// 根据主键查询 查询一条用findOnethis.ctx.body = await this.ctx.model.User.findOne({// 主表查询字段限制attributes:['name'],// 关联查询include: [{// 需要查询的模型model: this.app.model.Userinfo,// 副表查询的字段attributes: ['nickname']}],// 主表条件where: {id: 3}});
}

一对多

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'});

多对多

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

关联常用操作

// 获取关联模型对象,n对一不需要加s
let userinfo = await user.getUserinfo();
// n对多需要加s
await user.getPosts({attributes: ['title'],where: {id: 3}
});
// 关联操作
// 1:用户创建文章(一对多)
await this.ctx.model.Post.create({title: "第一篇文章",user_id: user.id
});// 2.获取当前用户所有文章
await user.getPosts();
await user.getPosts({attributes: ['id'],where:{title:"测试"}
});// 3:用户删除文章(一对多)
// (1) 获取当前用户的所有文章
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]}
});

获取器和修改器

模型层

// app/model/user.jsmodule.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),// 单独字段的getter,查询时都会调用// this.getDataValue('name') 获取原始值get() {const age = this.getDataValue('age');return this.getDataValue('name') + '年龄:' + age;}},age: {type: INTEGER,// 单独字段的setter,新增和更新时调用// this.setDataValue('name') 设置原始值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;
};

控制器层

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

模型钩子

模型层

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('删除后');});return User;
};

查询

主键查询

Model.findByPk(1)

查找不存在则创建

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

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

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

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

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

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)/*在这个例子中,findOrCreate 返回一个如下的数组:[ {username: 'fnord',job: 'omnomnom',id: 2,createdAt: Fri Mar 22 2013 21: 28: 34 GMT + 0100(CET),updatedAt: Fri Mar 22 2013 21: 28: 34 GMT + 0100(CET)},false]由findOrCreate返回的数组通过第三行的数组扩展为两部分,并且这些部分将作为2个参数传递给回调函数,在这种情况下将其视为 "user" 和 "created" .(所以“user”将是返回数组的索引0的对象,并且 "created" 将等于 "false".)*/})

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

查找并计数

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

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

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

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

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

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

User.findAndCountAll({include: [{ model: Profile, required: true}],limit: 3
});

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

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

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

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

查询多个(常用)

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

复合过滤 / OR / NOT 查询

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

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 }]}}
})

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

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

not 示例:

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

将生成:

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;

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

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

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

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

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

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

something.findOne({order: [// 将返回 `name`['name'],// 将返回 `username` DESC['username', 'DESC'],// 将返回 max(`age`)sequelize.fn('max', sequelize.col('age')),// 将返回 max(`age`) DESC[sequelize.fn('max', sequelize.col('age')), 'DESC'],// 将返回 otherfunction(`col1`, 12, 'lalala') DESC[sequelize.fn('otherfunction', sequelize.col('col1'), 12, 'lalala'), 'DESC'],// 将返回 otherfunction(awesomefunction(`col`)) DESC,这个嵌套是可以无限的![sequelize.fn('otherfunction', sequelize.fn('awesomefunction', sequelize.col('col'))), 'DESC']]
})

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

  • String - 将被引用
  • Array - 第一个元素将被引用,第二个将被逐字地追加
  • Object -
    • raw 将被添加逐字引用
    • 如果未设置 raw,一切都被忽略,查询将失败
  • Sequelize.fn 和 Sequelize.col 返回函数和引用的列名

字段过滤

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

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

SELECT foo, bar …

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

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

SELECT foo, bar AS baz …

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

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

SELECT COUNT(hats) AS no_hats …

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

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

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

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

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

Where

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

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

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

基础

const Op = Sequelize.Op;Post.findAll({where: {authorId: 2}
});
// SELECT * FROM post WHERE authorId = 2Post.findAll({where: {authorId: 12,status: 'active'}
});
// SELECT * FROM post WHERE authorId = 12 AND status = 'active';Post.findAll({where: {[Op.or]: [{authorId: 12}, {authorId: 13}]}
});
// SELECT * FROM post WHERE authorId = 12 OR authorId = 13;Post.findAll({where: {authorId: {[Op.or]: [12, 13]}}
});
// SELECT * FROM post WHERE authorId = 12 OR authorId = 13;Post.destroy({where: {status: 'inactive'}
});
// DELETE FROM post WHERE status = 'inactive';Post.update({updatedAt: null,
}, {where: {deletedAt: {[Op.ne]: null}}
});
// UPDATE post SET updatedAt = null WHERE deletedAt NOT NULL;Post.findAll({where: sequelize.where(sequelize.fn('char_length', sequelize.col('status')), 6)
});
// SELECT * FROM post WHERE char_length(status) = 6;

操作符

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

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

范围选项

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

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

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

组合

{rank: {[Op.or]: {[Op.lt]: 1000,[Op.eq]: null}}
}
// rank < 1000 OR rank IS NULL{createdAt: {[Op.lt]: new Date(),[Op.gt]: new Date(new Date() - 24 * 60 * 60 * 1000)}
}
// createdAt < [timestamp] AND createdAt > [timestamp]{[Op.or]: [{title: {[Op.like]: 'Boat%'}},{description: {[Op.like]: '%boat%'}}]
}
// title LIKE 'Boat%' OR description LIKE '%boat%'

关系 / 关联

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

分页 / 限制

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

排序

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

Subtask.findAll({order: [// 将转义标题,并根据有效的方向参数列表验证DESC['title', 'DESC'],// 将按最大值排序(age)sequelize.fn('max', sequelize.col('age')),// 将按最大顺序(age) DESC[sequelize.fn('max', sequelize.col('age')), 'DESC'],// 将按 otherfunction 排序(`col1`, 12, 'lalala') DESC[sequelize.fn('otherfunction', sequelize.col('col1'), 12, 'lalala'), 'DESC'],// 将使用模型名称作为关联的名称排序关联模型的 created_at.[Task, 'createdAt', 'DESC'],// Will order through an associated model's created_at using the model names as the associations' names.[Task, Project, 'createdAt', 'DESC'],// 将使用关联的名称由关联模型的created_at排序.['Task', 'createdAt', 'DESC'],// Will order by a nested associated model's created_at using the names of the associations.['Task', 'Project', 'createdAt', 'DESC'],// Will order by an associated model's created_at using an association object. (优选方法)[Subtask.associations.Task, 'createdAt', 'DESC'],// Will order by a nested associated model's created_at using association objects. (优选方法)[Subtask.associations.Task, Task.associations.Project, 'createdAt', 'DESC'],// Will order by an associated model's created_at using a simple association object.[{model: Task, as: 'Task'}, 'createdAt', 'DESC'],// 嵌套关联模型的 created_at 简单关联对象排序[{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')// 将根据方言随机排序 (而不是 fn('RAND') 或 fn('RANDOM'))order: sequelize.random()
})

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

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

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 - 获取特定表中特定属性的最大值

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

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

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

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

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

sum - 特定属性的值求和

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

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

预加载

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

class User extends Model {}
User.init({ name: Sequelize.STRING }, { sequelize, modelName: 'user' })
class Task extends Model {}
Task.init({ name: Sequelize.STRING }, { sequelize, modelName: 'task' })
class Tool extends Model {}
Tool.init({ name: Sequelize.STRING }, { sequelize, modelName: 'tool' })Task.belongsTo(User)
User.hasMany(Task)
User.hasMany(Tool, { as: 'Instruments' })sequelize.sync().then(() => {// 这是我们继续的地方 ...
})

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

Task.findAll({ include: [ User ] }).then(tasks => {console.log(JSON.stringify(tasks))/*[{"name": "A Task","id": 1,"createdAt": "2013-03-20T20:31:40.000Z","updatedAt": "2013-03-20T20:31:40.000Z","userId": 1,"user": {"name": "John Doe","id": 1,"createdAt": "2013-03-20T20:31:45.000Z","updatedAt": "2013-03-20T20:31:45.000Z"}}]*/
})

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

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

User.findAll({ include: [ Task ] }).then(users => {console.log(JSON.stringify(users))/*[{"name": "John Doe","id": 1,"createdAt": "2013-03-20T20:31:45.000Z","updatedAt": "2013-03-20T20:31:45.000Z","tasks": [{"name": "A Task","id": 1,"createdAt": "2013-03-20T20:31:40.000Z","updatedAt": "2013-03-20T20:31:40.000Z","userId": 1}]}]*/
})

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

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

User.findAll({ include: [{ model: Tool, as: 'Instruments' }] }).then(users => {console.log(JSON.stringify(users))/*[{"name": "John Doe","id": 1,"createdAt": "2013-03-20T20:31:45.000Z","updatedAt": "2013-03-20T20:31:45.000Z","Instruments": [{"name": "Toothpick","id": 1,"createdAt": null,"updatedAt": null,"userId": 1}]}]*/
})

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

User.findAll({ include: ['Instruments'] }).then(users => {console.log(JSON.stringify(users))/*[{"name": "John Doe","id": 1,"createdAt": "2013-03-20T20:31:45.000Z","updatedAt": "2013-03-20T20:31:45.000Z","Instruments": [{"name": "Toothpick","id": 1,"createdAt": null,"updatedAt": null,"userId": 1}]}]*/
})User.findAll({ include: [{ association: 'Instruments' }] }).then(users => {console.log(JSON.stringify(users))/*[{"name": "John Doe","id": 1,"createdAt": "2013-03-20T20:31:45.000Z","updatedAt": "2013-03-20T20:31:45.000Z","Instruments": [{"name": "Toothpick","id": 1,"createdAt": null,"updatedAt": null,"userId": 1}]}]*/
})

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

User.findAll({include: [{model: Tool,as: 'Instruments',where: { name: { [Op.like]: '%ooth%' } }}]
}).then(users => {console.log(JSON.stringify(users))/*[{"name": "John Doe","id": 1,"createdAt": "2013-03-20T20:31:45.000Z","updatedAt": "2013-03-20T20:31:45.000Z","Instruments": [{"name": "Toothpick","id": 1,"createdAt": null,"updatedAt": null,"userId": 1}]}],[{"name": "John Smith","id": 2,"createdAt": "2013-03-20T20:31:45.000Z","updatedAt": "2013-03-20T20:31:45.000Z","Instruments": [{"name": "Toothpick","id": 1,"createdAt": null,"updatedAt": null,"userId": 1}]}],*/})

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

使用预加载模型的顶层 WHERE

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

User.findAll({where: {'$Instruments.name$': { [Op.iLike]: '%ooth%' }},include: [{model: Tool,as: 'Instruments'}]
}).then(users => {console.log(JSON.stringify(users));/*[{"name": "John Doe","id": 1,"createdAt": "2013-03-20T20:31:45.000Z","updatedAt": "2013-03-20T20:31:45.000Z","Instruments": [{"name": "Toothpick","id": 1,"createdAt": null,"updatedAt": null,"userId": 1}]}],[{"name": "John Smith","id": 2,"createdAt": "2013-03-20T20:31:45.000Z","updatedAt": "2013-03-20T20:31:45.000Z","Instruments": [{"name": "Toothpick","id": 1,"createdAt": null,"updatedAt": null,"userId": 1}]}],*/

包括所有

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

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

包括软删除的记录

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

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

排序预加载关联

在一对多关系的情况下.

Company.findAll({ include: [ Division ], order: [ [ Division, 'name' ] ] });
Company.findAll({ include: [ Division ], order: [ [ Division, 'name', 'DESC' ] ] });
Company.findAll({include: [ { model: Division, as: 'Div' } ],order: [ [ { model: Division, as: 'Div' }, 'name' ] ]
});
Company.findAll({include: [ { model: Division, as: 'Div' } ],order: [ [ { model: Division, as: 'Div' }, 'name', 'DESC' ] ]
});
Company.findAll({include: [ { model: Division, include: [ Department ] } ],order: [ [ Division, Department, 'name' ] ]
});

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

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

嵌套预加载

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

User.findAll({include: [{model: Tool, as: 'Instruments', include: [{model: Teacher, include: [ /* etc */]}]}]
}).then(users => {console.log(JSON.stringify(users))/*[{"name": "John Doe","id": 1,"createdAt": "2013-03-20T20:31:45.000Z","updatedAt": "2013-03-20T20:31:45.000Z","Instruments": [{ // 1:M and N:M association"name": "Toothpick","id": 1,"createdAt": null,"updatedAt": null,"userId": 1,"Teacher": { // 1:1 association"name": "Jimi Hendrix"}}]}]*/
})

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

User.findAll({include: [{model: Tool,as: 'Instruments',include: [{model: Teacher,where: {school: "Woodstock Music School"},required: false}]}]
}).then(users => {/* ... */
})

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

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

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

新增

字段限制

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

新增单个

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

批量新增

// 批量新增 bulkCreate
this.ctx.body = await this.ctx.model.User.bulkCreate([{name: "第一个",age: 15},{name: "第二个",age: 15},{name: "第三个",age: 15},
]);

修改

字段限制

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

单个修改

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

批量修改

// 批量修改
await this.ctx.model.User.update({name: "批量修改"
}, {// 条件where: {name: "第一个"}
});

递增

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

递减

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

删除

软删除

模型中配置

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

查询包括软删除内容

let user = await ctx.model.User.findOne({include:{model:ctx.model.Video,// 包括软删除paranoid: false},where: {id: 33},// 包括软删除paranoid: false
});

彻底删除

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

task.destroy({ force: true })

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

恢复软删除的实例

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

// 进行软删除...
task.destroy();
// 恢复软删除...
task.restore();

条件删除

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

批量删除

await this.ctx.model.Post.destroy({where: {id: posts}
});

重载实例

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

Person.findOne({ where: { name: 'john' } }).then(person => {person.name = 'jane'console.log(person.name) // 'jane'person.reload().then(() => {console.log(person.name) // 'john'})
})

模型自定义方法

// 模型
// 模型自定义方法
topic_user.ceshi = (param) => {console.log('模型自定义方法');console.log(param);return param;
}// 控制器
await this.ctx.model.TopicUser.ceshi(123);

Scopes - 作用域(重点)

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

定义

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

class Project extends Model {}
Project.init({// 属性
}, {defaultScope: {where: {active: true}},scopes: {deleted: {where: {deleted: true}},activeUsers: {include: [{ model: User, where: { active: true }}]},random () {return {where: {someNumber: Math.random()}}},accessLevel (value) {return {where: {accessLevel: {[Op.gte]: value}}}}sequelize,modelName: 'project'}
});

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

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

SELECT * FROM projects WHERE active = true

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

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

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

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

activeUsers: {include: [{ model: User.scope('active')}]
}

使用

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

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

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

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

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

合并

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

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

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

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

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

{scope1: {where: {firstName: 'bob',age: {[Op.gt]: 20}},limit: 2},scope2: {where: {age: {[Op.gt]: 30}},limit: 10}
}

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

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

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

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

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

Project.scope('deleted').findAll({where: {firstName: 'john'}
})
WHERE deleted = true AND firstName = 'john'

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

合并 include

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

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

class Foo extends Model {}
class Bar extends Model {}
class Baz extends Model {}
class Qux extends Model {}
Foo.init({ name: Sequelize.STRING }, { sequelize });
Bar.init({ name: Sequelize.STRING }, { sequelize });
Baz.init({ name: Sequelize.STRING }, { sequelize });
Qux.init({ name: Sequelize.STRING }, { sequelize });
Foo.hasMany(Bar, { foreignKey: 'fooId' });
Bar.hasMany(Baz, { foreignKey: 'barId' });
Baz.hasMany(Qux, { foreignKey: 'bazId' });

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

{includeEverything: {include: {model: this.Bar,include: [{model: this.Baz,include: this.Qux}]}},limitedBars: {include: [{model: this.Bar,limit: 2}]},limitedBazs: {include: [{model: this.Bar,include: [{model: this.Baz,limit: 2}]}]},excludeBazName: {include: [{model: this.Bar,include: [{model: this.Baz,attributes: {exclude: ['name']}}]}]}
}

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

Foo.findAll({include: {model: this.Bar,limit: 2,include: [{model: this.Baz,limit: 2,attributes: {exclude: ['name']},include: this.Qux}]}
});

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

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

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

关联

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

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

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

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

this.Post.hasMany(this.Comment, {foreignKey: 'commentable_id',scope: {commentable: 'post'}
});

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

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

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

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

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

class Post extends Model {}
Post.init(attributes, {defaultScope: {where: {active: true}},scopes: {deleted: {where: {deleted: true}}},sequelize,
});User.hasMany(Post); // 常规 getPosts 关联
User.hasMany(Post.scope('deleted'), { as: 'deletedPosts' });
User.getPosts(); // WHERE active = true
User.getDeletedPosts(); // WHERE deleted = true

扩展

extend/helper.js

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

模板中调用

 <%=helper.formatTime(dateline)%>

其他地方调用

this.ctx.helper.formatTime(dateline)

中间件

1. 定义

app/middleware/getIp.js

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

2. 配置

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

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

3. 使用

路由中使用

app/router.js

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

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

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

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

const koa = require('koa');
const compress = require('koa-compress');const app = koa();const options = { threshold: 2048 };
app.use(compress(options));

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

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

表单提交

post

app/controller/home.js

async addInput(ctx) {await ctx.render('post');
}async add(ctx) {// 通过ctx.request.body获取post提交数据console.log(ctx.request.body);
}

app/view/post.html

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

app/router.js

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

cookie

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

session

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

定时任务

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

API

1. context

curl

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

常用插件

缓存

https://www.npmjs.com/package/egg-cache
https://www.npmjs.com/package/egg-redis

验证

https://github.com/temool/egg-validate-plus

加密

https://www.npmjs.com/package/egg-jwt

前端访问:header头添加:

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

egg.js部署上线

第一步:解析域名,创建网站
第二步:上传解压
第三步:安装pm2(node环境),切换node版本到最新版本,安装redis
第四步:打开命令行,切换到根目录下
第五步:如果是国内服务器,先切换镜像:
npm config set registry https://registry.npm.taobao.org
如果是国外服务器就不需要了,例如香港服务器
第六步:执行 npm install
第七步:安装数据库迁移工具 npm install --save-dev sequelize-cli
第八步:修改配置信息:
config/config.default.js
sequelize配置
oss配置
database/config.json
数据库相关信息
第九步:执行迁移命令 npx sequelize db:migrate
第十步:npm start
第十一步:添加反向代理

添加配置:

 location /ws{proxy_pass http://127.0.0.1:7001;proxy_http_version 1.1;proxy_set_header Upgrade $http_upgrade;proxy_set_header Connection "Upgrade";proxy_set_header X-Real-IP $remote_addr;}

第十二步:修改前端项目的 /common/lib/config.js和manifest.json里面的域名即可

uni-app实战仿微信app开发相关推荐

  1. ReactNative聊天APP实战|仿微信聊天/朋友圈/红包界面

    项目简介 最近一直在研究RN技术,想着找个项目练练手,之前就有使用vue.react技术开发过聊天项目,这次就使用reactNative技术开发个仿微信RN版.是基于 react-native+rea ...

  2. uni-app实战仿小米商城app开发

    uni-app实战仿小米商城app开发 关注公众号:码农那些年 回复"uni-app商城",免费获取. 微信公众号: 打开微信扫一扫

  3. 高仿微信APP实战(一)-Actionbar的制作与应用

    高仿微信APP实战(一)-Actionbar的制作与应用 本文是仿微信app实战系列的第一部分,先从简单的做起,边做边学习.从顶部actionbar开始,先看一下效果图: 一.定义主题样式 <r ...

  4. uniapp 即时通讯_uni-App 仿微信 App 即时通讯|vue+uniapp 聊天

    项目介绍 基于uni-app+vue+vuex+uniPop+swiper等技术开发仿微信App聊天室实战项目,实现了发送消息.表情(gif动图),图片预览.地图位置.红包.仿微信朋友圈等功能. 项目 ...

  5. uniapp+nvue实现仿微信App界面+功能 —— uni-app实现聊天+语音+视频+图片消息

    基于uniapp + nvue实现的uniapp仿微信界面功能聊天应用 txim 实例项目,实现了以下功能. 1: 聊天会话管理 2: 好友列表 3: 文字.语音.视频.表情.位置等聊天消息收发 4: ...

  6. Android studio心得——用fragment仿微信APP

    前言 今天我想与大家分享一些关于如何利用fragment实现仿微信APP的经验.作为社交领域最受欢迎.功能齐全且可扩展性强的应用之一,微信APP在浏览器首页和个人中心之上还有一个重要部分:底部导航栏. ...

  7. vue移动端过渡动画_Vue仿微信app页面跳转动画效果

    10:14:11独立开发者在开发移动端产品时,为了更高效,通常会使用Web技术来开发移动端项目,可以同时适配Android.iOS.H5,稍加改动还可适配微信小程序. 在使用Vue.js开发移动端页面 ...

  8. vue 移动端 跳转页面_Vue 仿微信 app 页面跳转动画

    独立开发者在开发移动端产品时,为了更高效,通常会使用 Web 技术来开发移动端项目,可以同时适配 Android.iOS.H5,稍加改动还可适配微信小程序. 在使用 Vue.js 开发移动端页面的时候 ...

  9. Vue仿微信app页面跳转动画

    独立开发者在开发移动端产品时,为了更高效,通常会使用Web技术来开发移动端项目,可以同时适配Android.iOS.H5,稍加改动还可适配微信小程序. 在使用Vue.js开发移动端页面的时候,默认的组 ...

  10. Android中使用ExpandableListView实现微信通讯录界面(完善仿微信APP)

    之前的博文<Android中使用ExpandableListView实现好友分组>我简单介绍了使用ExpandableListView实现简单的好友分组功能,今天我们针对之前的所做的仿微信 ...

最新文章

  1. python装饰器作用-什么是Python装饰器,有什么作用?
  2. LeetCode: Convert Sorted Array to Binary Search Tree 解题报告
  3. MongoDB shell 操作
  4. mysql中难以理解的sql
  5. es6语法-对象拓展运算符
  6. Linux系统编程:简单实现ls -R 功能
  7. 常用配置文件格式比较
  8. 数据结构 3-0 栈与队列总结
  9. oracle 10g dg参数配置,Windows平台之Oracle10g DG配置
  10. 厂商为什么喜欢使用堆叠?
  11. 基于springboot+vue的大学生健康档案管理系统
  12. Latex笔记:IEEE Access模板 图片排版问题汇总
  13. AndroidStudio中的NDK开发初探
  14. ABB ACS 510 1.5-5.5kw驱动板图纸 PDF格式
  15. 阿里云服务器 ECS 数据盘与系统盘是什么?
  16. 携程一面(2021-1-26):凉经。面试职位:后台开发工程师(2021届应届生)
  17. 手机怎样识别图片中的文字?
  18. FPGA--有限状态机(FSM)的设计
  19. Tomcat with muti instance
  20. 计算机怎么没有word文档,word没了怎么回事 为什么电脑没有了word

热门文章

  1. oracle库sql根据拼音查汉字,根据拼音首字母模糊查询数据库中文字段
  2. java 分页读取数据
  3. inode客户端连接成功上不了网_Inode客户端上网常见问题及解决办法
  4. win10安装iNode客户端软件就不能连接无线网络的问题解决
  5. html5font标签菜鸟教程,菜鸟教程
  6. c语言编程投影仪,当贝投影F1C评测 投影仪的2019年圆满收官之作
  7. IBM x3850 x5U盘启动或光驱启动不起作用
  8. volatility内存取证
  9. JFlash烧录SPI FLASH
  10. Restsharp 与 unity3D WWW