Sequelize ORM

Sequelize ORM

安装sequelize命令行工具:

1
npm i -g sequelize-cli

安装当前项目的sequelize、mysql2包:

1
npm i sequelize mysql2

初始化项目:

1
sequelize init

可以看到项目目录下多出了一些文件夹:

  • config
  • migrations
  • models
  • seeders

配置config.js

config.js

注意修改timezone时区

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
{
"development": {
"username": "root",
"password": "your-password",
"database": "your-db-development",
"host": "127.0.0.1",
"dialect": "mysql",
"timezone": "+08:00"
},
"test": {
"username": "root",
"password": null,
"database": "your-dbe-duck-home-test",
"host": "127.0.0.1",
"dialect": "mysql",
"timezone": "+08:00"
},
"production": {
"username": "root",
"password": null,
"database": "your-db-production",
"host": "127.0.0.1",
"dialect": "mysql",
"timezone": "+08:00"
}
}

创建模型

命名规则

  • 模型名为单数:Article
  • 表名为复数:Articles

sequelize创建模型

使用命令:

1
sequelize model:generate --name Article --attributes title:string,view:integer

发现在models和migrations中都创建了对应的文件:

  • models/web.js 模型所在文件
  • migrations/20250201024243-create-web.js 建表迁移文件

在migration迁移文件中,对一些属性手动进行修改:

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';
/** @type {import('sequelize-cli').Migration} */
module.exports = {
async up(queryInterface, Sequelize) {
await queryInterface.createTable('Articles', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER.UNSIGNED
},
title: {
type: Sequelize.STRING,
allowNull: false,
},
view: {
type: Sequelize.INTEGER.UNSIGNED,
defaultValue: 0, // 添加默认值
allowNull: false,
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
async down(queryInterface, Sequelize) {
// 反向迁移操作
await queryInterface.dropTable('Articles');
}
};

运行迁移:

1
sequelize db:migrate

种子文件

seed

命令行创建种子文件:

1
sequelize seed:generate --name article

编写种子文件脚本:

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
'use strict';

/** @type {import('sequelize-cli').Migration} */
module.exports = {
async up (queryInterface, Sequelize) {
const webs = []
const counts = 50
for(let i = 1; i<=counts; i++){
const web = {
href:'https://ant.design',
name:`This is website ${i}`,
webIcon:`https://api.dicebear.com/7.x/miniavs/svg?seed=${i}`,
webCover:'https://gw.alipayobjects.com/zos/rmsportal/mqaQswcyDLcXyDKnZfES.png',
content:'一个使用react、antd搭建的网站,后台使用nodejs搭建,用来作为一个导航站点,后续还会继续完善',
categoryId: 0,
view:0,
github:'https://github.com/PUYIXIU/CoffeeShop',
blogHref: 'https://puyixiu.github.io/',
createdAt:new Date(),
updatedAt:new Date(),
}
webs.push(web)
}
// 种子数据写入
await queryInterface.bulkInsert('Webs', webs, {});
},

async down (queryInterface, Sequelize) {
await queryInterface.bulkDelete('Webs',null, {})
}
};

运行种子文件:

1
sequelize db:seed --seed xxx-article

模型验证表单数据

表单验证

通过修改model模型中的字段规则,来实现表单验证:

  • 表单中访问其它表使用 sequelize.models.XXX
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
Web.init({
href: {
type:DataTypes.STRING,
allowNull:false,
validate:{
notNull:{msg:'站点URL必须存在'},
notEmpty:{msg:'站点URL不能为空'},
isUrl:{msg:'请填写正确的URL'},
async isUnique(value){
const web = await Web.findOne({where:{name:value}})
if(web){
throw new Error(`名称为:${value} 的web已存在`)
}
}
}
},
name: {
type:DataTypes.STRING,
allowNull:false,
validate:{
notNull:{msg:'站点名称必须存在'},
notEmpty:{msg:'站点名称不能为空'},
len:{
args:[2,50],
msg:'站点名称需要在2-50个字符之间'
}
}
},
categoryId: {
type:DataTypes.INTEGER,
allowNull:false,
validate:{
notNull:{msg:'分类ID必须存在'},
notEmpty:{msg:'分类ID不能为空'},
async isPresent(value){
const category = await sequelize.models.Category.findByPk(value)
if(!category){
throw new Error(`ID为:${value} 的分类不存在`)
}
if(category.typeId != this.typeId){
throw new Error(`此应用类型无法选择ID为:${value}的分类`)
}
}
}
},
}, {
sequelize,
modelName: 'Web',
});

同时,try/catch捕获的错误处理也要进行修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
try{
//...
}catch(error){
if (error.name === 'SequelizeValidationError') {
const errors = error.errors.map(e => e.message);
res.status(400).json({
status: false,
message: '请求参数错误。',
errors
});
} else {
res.status(500).json({
status: false,
message: '创建站点失败。',
errors: [error.message]
});
}
}

迁移相关

migration

回滚上一次迁移:

1
sequelize db:migrate:undo

运行迁移:

1
sequelize db:migrate

新建迁移:

1
sequelize migration:create --name add-avatar-to-user

迁移文件中向数据库补充数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
'use strict';

/** @type {import('sequelize-cli').Migration} */
module.exports = {
async up (queryInterface, Sequelize) {
await queryInterface.addColumn('Users','avatar',{
type:Sequelize.STRING
})
},

async down (queryInterface, Sequelize) {
await queryInterface.removeColumn('Users','avatar' )
}
};