文中完整示例代码已上传至 GitHub, guchongxi/egg-sequelize-demo。html
2019.10.11mysql
直接使用 egg-sequelize 插件便可git
yarn add egg-sequelize mysql2 -S
复制代码
启用插件github
// config/plugin.js
exports.sequelize = {
enable: true,
package: 'egg-sequelize'
}
复制代码
添加配置sql
// config/config.{env}.js
exports.sequelize = {
// 数据库类型
dialect: 'mysql',
// 数据库地址
host: '127.0.0.1',
// 端口
port: 3306,
// 数据库名
database: 'test-database',
// 用户名,默认为 root
username: 'root',
// 密码,默认为空
password: '',
// sequelize 配置
define: {
// 添加createAt,updateAt,deleteAt时间戳
timestamps: true,
// 使用软删除,即仅更新 deleteAt 时间戳 而不删除数据
paranoid: true,
// 不容许修改表名
freezeTableName: true,
// 禁止驼峰式字段默认转为下划线
underscored: false,
},
// 因为orm用的UTC时间,这里必须加上东八区,不然设置的时间相差8小时
timezone: '+08:00',
// mysql2 配置
dialectOptions: {
// 让读取date类型数据时返回时间戳而不是UTC时间
dateStrings: true,
typeCast(field, next) {
if (field.type === 'DATETIME') {
return new Date(field.string()).valueOf();
}
return next();
},
},
}
复制代码
若是仅使用 docker 启动 mysql 可参考 mysqlchrome
docker 安装与其余操做请自行 googledocker
建议使用 docker compose,更方便扩展其余容器数据库
新建 docker-compose.yml
json
version: '3.7'
services:
mysql:
image: mysql:5.7
volumes:
- data-mysql:/var/lib/mysql
environment:
MYSQL_ALLOW_EMPTY_PASSWORD: 'yes'
MYSQL_DATABASE: 'test-database'
ports:
- '3306:3306'
restart: unless-stopped
volumes:
data-mysql:
复制代码
启动容器bash
docker-compose up -d
复制代码
中止容器
docker-compose down
复制代码
中止并销毁数据
docker-compose dowm -v
复制代码
实体
表结构
users
-> 存储用户infos
-> 存储用户信息,使用 userId 关联 userapps
-> 存储应用,经过 userId 关联 usertasks
-> 存储任务,经过 appId 关联 app,经过 userId 关联 usergroups
-> 存在群组group_app
-> 存储群组和应用关系,经过 appId 关联 app,经过 groupId 关联 group表关系
为方便以后对数据模型进行变动和简化操做,咱们使用 sequelize-cli 来作数据库初始化
yarn add sequelize-cli -D
npx sequelize init
复制代码
更改配置
{
"development": {
"username": "root",
"password": null,
"database": "test-database",
"host": "127.0.0.1",
"port": 3306,
"dialect": "mysql",
"timezone": "+08:00",
"define": {
"charset": "utf8",
"dialectOptions": {
"collate": "utf8_general_ci"
}
}
}
}
复制代码
'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')
};
复制代码
npx sequelize model:generate --name users --attributes username:string,email:string
复制代码
// database/migrations/{time}-create-users.js
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable(
'users',
{
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER,
},
username: {
allowNull: false,
type: Sequelize.STRING,
},
email: {
allowNull: false,
type: Sequelize.STRING,
},
createdAt: {
allowNull: false,
type: Sequelize.DATE,
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE,
},
deletedAt: {
type: Sequelize.DATE,
},
},
{
charset: 'utf8',
},
);
},
down: (queryInterface) => {
return queryInterface.dropTable('users');
},
};
复制代码
npx sequelize model:generate --name tasks --attributes appId:integer,userId:integer,status:integer,description:string
复制代码
// database/migrations/{time}-create-tasks.js
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable(
'tasks',
{
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER,
},
appId: {
allowNull: false,
type: Sequelize.INTEGER,
},
// 由于可能不是用户操做,容许为 null
userId: {
type: Sequelize.INTEGER,
},
status: {
allowNull: false,
type: Sequelize.INTEGER,
},
description: {
type: Sequelize.STRING,
},
createdAt: {
allowNull: false,
type: Sequelize.DATE,
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE,
},
deletedAt: {
type: Sequelize.DATE,
},
},
{
charset: 'utf8',
},
);
},
down: (queryInterface) => {
return queryInterface.dropTable('tasks');
},
};
复制代码
其余表参照 users
表生成便可
为方便测试,咱们能够预先将模拟数据一并生成
npx sequelize seed:generate --name users
复制代码
// database/seeders/{time}-users.js
'use strict';
module.exports = {
up: (queryInterface) => {
return queryInterface.bulkInsert(
'users',
[
{
username: 'test user',
email: 'test@test.com',
createdAt: new Date(),
updatedAt: new Date(),
},
],
{},
);
},
down: (queryInterface) => {
return queryInterface.bulkDelete('users', { email: 'test@test.com' }, {});
},
};
复制代码
其余表按照相同步骤生成便可
# 初始化表结构
npx sequelize db:migrate
# 初始化表数据
npx sequelize db:seed:all
复制代码
这样数据库中的表和数据就建好了
Mac OS 用户可以使用 Sequel Pro 可视化查看数据库
在初始化数据模型时,同时生成的还有 app/model/*.js
users.js
'use strict';
module.exports = (app) => {
const { Sequelize, model } = app;
const Users = model.define(
'users',
{
// 用户名
username: Sequelize.STRING,
// 邮箱
email: Sequelize.STRING,
},
{
// 默认不输出 deletedAt 字段
// 参考 https://sequelize.org/master/class/lib/model.js~Model.html#static-method-scope
defaultScope: {
attributes: {
exclude: ['deletedAt'],
},
},
},
);
Users.associate = function () {
// 与应用是 一对多
app.model.Users.hasMany(app.model.Apps, {
foreignKey: 'userId',
});
// 与任务是 一对多
app.model.Users.hasMany(app.model.Tasks, {
foreignKey: 'userId',
});
// 与信息是 一对一
app.model.Users.hasOne(app.model.Infos, {
foreignKey: 'userId',
});
};
return Users;
};
复制代码
apps.js
'use strict';
module.exports = (app) => {
const { Sequelize, model } = app;
const Apps = model.define(
'apps',
{
// 建立用户 id
userId: Sequelize.INTEGER,
// 应用中文名
name: Sequelize.STRING,
// 应用仓库地址,ssh 风格
gitUrl: Sequelize.STRING,
},
{
defaultScope: {
attributes: {
exclude: ['deletedAt'],
},
},
},
);
Apps.associate = function () {
// 与用户是 多对一
app.model.Apps.belongsTo(app.model.Users, {
foreignKey: 'userId',
});
// 与任务是 一对多
app.model.Apps.hasMany(app.model.Tasks, {
foreignKey: 'appId',
});
// 与群组是 多对多
app.model.Apps.belongsToMany(app.model.Groups, {
through: app.model.GroupApp,
foreignKey: 'appId',
otherKey: 'groupId'
});
};
return Apps;
};
复制代码
其余 model
参考 egg-sequelize-demo/app/model/
参考:associations
在 model
中咱们定义了表间的关联关系能够看出主要是三种关系:一对一,一对多,多对多
在一对一中,通常会有主从关系,示例为 user(主) 和 info(从)
对于主使用 hasOne(Model, { foreignKey: '从表中关联主表的字段名,示例为 userId' })
对于从使用 belongsTo(Model ,{ foreignKey: '从表中关联主表的字段名,示例为 userId' })
在一对多中,一为主,多为从,示例为 user(主) 和 app(从)
对于主使用 hasMany(Model, { foreignKey: '从表中关联主表的字段名,示例为 userId' })
对于从使用 belongsTo(Model, { foreignKey: '从表中关联主表的字段名,示例为 userId' })
在多对多中,不须要区分主从的概念
双方都使用 belongsToMany(Model, { through: MiddleModel, foreignKey: '中间表中关联本身的字段名,若是在 group 中定义则为 groupId', otherKey: '中间表中关联另外一方的字段名,若是在 group 中定义则为 appId' })
一般,咱们会在 service 中处理与数据库的交互
// app/service/apps.js
'use strict';
const Service = require('egg').Service;
class Apps extends Service {
async list() {
return this.ctx.model.Apps.findAll({
include: [{
model: this.ctx.model.Users,
as: 'user',
}, {
model: this.ctx.model.Tasks,
as: 'tasks',
}, {
model: this.ctx.model.Groups,
as: 'groups',
// 配置 groups 元素输出 name,desc 字段
attributes: ['name', 'desc'],
}],
});
}
}
module.exports = Apps;
复制代码
// app/service/groups.js
'use strict';
const Service = require('egg').Service;
class Groups extends Service {
async list() {
return this.ctx.model.Groups.findAll({
include: [{
model: this.ctx.model.Apps,
as: 'apps',
include: [{
model: this.ctx.model.Users,
as: 'user'
}]
}],
});
}
}
module.exports = Groups;
复制代码
// app/service/tasks.js
'use strict';
const Service = require('egg').Service;
class Tasks extends Service {
async list() {
return this.ctx.model.Tasks.findAll({
include: [{
model: this.ctx.model.Users,
as: 'user',
}, {
model: this.ctx.model.Apps,
as: 'app',
}],
});
}
}
module.exports = Tasks;
复制代码
// app/service/users.js
'use strict';
const Service = require('egg').Service;
class Users extends Service {
async list() {
return this.ctx.model.Users.findAll({
include: [{
model: this.ctx.model.Apps,
as: 'apps',
}, {
model: this.ctx.model.Tasks,
as: 'tasks',
}, {
model: this.ctx.model.Infos,
as: 'info',
}],
});
}
}
module.exports = Users;
复制代码
END