豪翔天下

Change My World by Program

0%

本来以为现在写去年的年终总结已经太晚了,可是copy去年的总结的时候发现去年是2月19日写的,还不算太晚,毕竟今年比去年更忙。

Ok,接下来是每年的总结及计划模版。

2020总结

这是2020年初列的计划

阅读全文 »

  • Aws的密钥只能下载一次,下载后请小心保存
  • AWS的命令行或者代码的环境变量是: AWS_ACCESS_KEY_ID(或AWS_ACCESS_KEY)/AWS_SECRET_ACCESS_KEY(或AWS_SECRET_KEY)/AWS_DEFAULT_REGION/AWS_REGION,除了放到环境变量,它一般也可以存储在~/.ssh/aws/credentials文件里
  • AWS现在的API版本是V3了,感觉文档清晰了不少:https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/ec2/

AWS CLI

  • 不同操作系统安装方式见Installing, updating, and uninstalling the AWS CLI version 2 on Linux,包括mac都可以直接安装的

  • 可以在~/.aws/config中配置多个profile

    1
    2
    3
    4
    5
    6
    7
    8
    [default]
    aws_access_key_id =
    aws_secret_access_key =

    [profile user1]
    aws_access_key_id =
    aws_secret_access_key =
    region = us-east-1

    使用的时候可以这样指定

    1
    aws ec2 describe-instances --profile user1

EC2

  • ubuntu系统默认用户为ubuntu,amazon系统默认的用户名为ec2-user
  • 默认会有12个月750小时的免费套餐,但是仅限个别低配类型
  • 要想查看在所有region下的所有的ec2实例,可以在VPC dashboard中查看,Running Instances -> See all regions
  • EC2实力类型列表,注意t2、t3是突发性能实例,CPU的使用采用积分制(CPU credits),如果某一时间发现CPU不行或者网站很卡,有可能是因为CPU资源无法使用了,这种情况要么等积分恢复,要么升级实例

如何删除EC2实例

  • 先选中要删除的实例,Stop,再Terminate(终止实例),这个时候虽然实例还在,但其实已经删除了,大概等个10分钟左右就没了

EC2实例升级/修改实例类型

  • IP会变更,请注意是否启用弹性IP或者负载均衡器
  • 关机,需要接近一分钟
  • 操作->实例设置->更改实例类型
  • 开机

EC2实例扩容

关机扩容

  1. 关机扩容很简单,但是IP会变更,请注意是否启用弹性IP或者负载均衡器
  2. 首先关机Actions -> Instance State -> Stop
  3. 进入卷管理: Elastic Block Store -> Volumes
  4. 选择需要更改的磁盘: Modify Volume,然后输入大小
  5. 重启实例,并进入终端
  6. 使用df -h查看当前磁盘容量

不关机扩容

  1. 在实例详里面找到root volumn,进入volumn详情

  2. Actions -> Modify Volume,输入扩容后的大小点击确定

  3. 进入实例,此时用df -h查看依然是原来的大小,使用lsblk命令可以查看有新的大小,该命令用于查看是否具有必须扩展的分区,例如:

    1
    2
    xvda    202:0    0   30G  0 disk
    └─xvda1 202:1 0 20G 0 part / # df -h只能看到这个分区
  4. 执行扩容命令

    1
    2
    3
    4
    5
    6
    # 有时候lsblk看到的磁盘名称和df -h显示的磁盘名称不一致,没关系,下面的命令按照lsblk的来就行

    sudo growpart /dev/xvda 1
    lsblk # 验证xvda1的大小是否已经变化,不过此时用df -h依然看不出变化

    sudo resize2fs /dev/xvda1 # 此时用df -h就能看到变化了,扩容过程也完成了

EC2增加磁盘

  • 步骤

    1. 创建卷

    2. 操作->连接卷,默认会挂载到/dev/sdf

    3. 进入实例,执行lsblk可以看到附加的卷(磁盘)

    4. 新卷默认是没有文件系统的,可以这样确定:

      1
      2
      sudo file -s /dev/xvdf # 如果输出是/dev/xvdf: data表示没有文件系统
      sudo mkfs -t xfs /dev/xvdf # 创建文件系统,如果找不到mkfs命令,可以安装xfsprogs
    5. 挂载

      1
      2
      3
      sudo mkdir /data	# 创建挂载点
      sudo mount /dev/xvdf /data # 挂载
      df -h # 确认是否挂载成功

Ec2绑定Elastic IP弹性IP

  • 弹性IP只要是绑定在运行中的ec2实例上就是免费的,所以如果仅仅是要一个不会随着机器状态变化的IP那么推荐用弹性IP而不是用负载均衡器
  • 当一个新建的弹性IP被关联到一个实例上的时候,该实例的公有IP地址也会变成一样的,不过之后如果实例重启公有IP会改变,弹性IP则不会了
  • 一个账号最多绑定5个弹性IP,超过了需要单独提交申请,所以有时候还是用elb代替吧

安全组

  • 注意如果安全组的target设置为另外一个安全组,那么在访问另外一个安全组的实例的时候不能使用外网IP,只能用内网IP才行

EC2配置Cloudwatch监控

添加自定义指标

阅读全文 »

Model定义

  • 如果不定义collection的名称,mongoose会自动将Model名转换为小写复数形式作为collection
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
import mongoose from 'mongoose';
var Schema = mongoose.Schema;

var UserSchema = new Schema(
{
name: String,
is_active: {
type: Boolean,
default: false
},
created_at: {
type: Date,
default: Date.now
},
friends: [{ // 可以直接用.populate查出关联对象
type: Schema.Types.ObjectId,
ref: 'User'
}],
father: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
meta: Object,
meta1: { // 也可以这样定义Object
name: String,
age: Number
},
tags: {
type: [String],
index: true // 定义某个字段为索引
}
}, {
collection: "自定义collection名称",
toJSON: {virtuals: true},
toObject: {virtuals: true}
}
)
UserSchema.index({name: 1, type: -1}); // 在最后指定索引

// 给Model添加查询帮助方法
UserShchema.query.byName = function(name) {
return this.where({ name: new RegExp(name, 'i')})
};
UserSchema.find().byName('name').exec((err, animals) => {});

// 给实例添加自定义的方法
UserSchema.methods.myFunc = function() {
mongoose.model('UserSchema') // 在实例定义里面可以通过这个方法获取Schema Model
return this // this就是该实例本身
}

// 给Model添加静态方法
UserSchema.statics.findByName = function(name) {
return this.find({name: new RegExp(name, 'i')});
}
// 或者这样定义,不要用ES6里面的=>,因为用=>无法通过this获取到Schema
UserSchema.static('findByName', function(name) { return ... });
const user = await UserSchema.findByName('name')

export default mongoose.model('User', UserSchema);

// 甚至可以不定义每个字段,直接用
mongoose.model('User', new Schema(), 'UserSchema');
阅读全文 »

Datatables 安装

简单的HTML方式使用

使用NPM的方式安装datatables

官网的示例大全,可以在里面搜索到很多种使用场景

Datatables 配置大全

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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
$('#exampleTable').dataTable({
ajax: {
url: '/getData',
type: 'get',
headers: {},
data: function (data) { // 修改或添加请求的参数,常用于自己写的搜索框
data.searchName = 'xxxx';
},
dataSrc: function (res) { // 修改接口返回的数据
res.data.map(item => {
item.name = '<button>abc</button>';
})
return res.data;
}
},
buttons: [{
text: "Bulk Make as Completed",
className: 'marked-as-complate-btn'
}],
columns: [ // 从数据源dataSrc中取哪些列进行展示
{ data: 'name' },
{ data: 'status' },
{
className: 'test', // 给某一列添加类
data: 'id', // 列的数据名
orderable: true, // 是否允许排序
render: function (data, type, row) { // 给某一列单独添加渲染方式,而不是直接展示值
return '<input type="checkbox" class="form-control" value=' + row.value +'>';
},
searchable: false, // 是否允许过滤
type: 'date', // 设置该列的类型,例如date、num、num-fmt(比如货币等$100,000)、html-num、html-num-fmt、html、string
visible: true, // 设置列是否可见
width: '20%', // 强行设置列的宽度,支持数字和CSS写法
},
],
columnDefs: [{ // 相当于批量设置columns
targets: [0, 1], // 多少列,这里表示第0列和第1列
orderable: false // 定义是否可以拖动排序
}],
createdRow: function (row, data, index) {
$(row).addClass('test');
},
data: {}, // 以数组的方式设置初始化数据,当然一般还是用的ajax
displayLength: 10, // 默认展示的每页的记录数
/* dom:定义表哥的控制元素以什么样式显示
l - length:长度改变输入控制
f - filtering:过滤输入框
t - table:表格本身
i - information:信息概览元素
p - pagination:翻页控制元素
r - processing:处理中显示元素
Bootstrap 3的样式默认值为
"<'row'<'col-sm-6'l><'col-sm-6'f>>" +
"<'row'<'col-sm-12'tr>>" +
"<'row'<'col-sm-5'i><'col-sm-7'p>>"
Bootstrap 4的样式默认值为
"<'row'<'col-sm-12 col-md-6'l><'col-sm-12 col-md-6'f>>" +
"<'row'<'col-sm-12'tr>>" +
"<'row'<'col-sm-12 col-md-5'i><'col-sm-12 col-md-7'p>>"
jQuery UI的样式默认值为
'<"fg-toolbar ui-toolbar ui-widget-header ui-helper-clearfix ui-corner-tl ui-corner-tr"lfr>'+
't'+
'<"fg-toolbar ui-toolbar ui-widget-header ui-helper-clearfix ui-corner-bl ui-corner-br"ip>',
*/
dom: "<'row'<'col-sm-12 col-md-6'f>>" +
"<'row'<'col-sm-12'tr>>" +
"<'row'<'col-sm-12 col-md-2'l><'col-sm-12 col-md-2'i><'col-sm-12 col-md-8'p>>", // 这里分三个部分进行设置,第一个row表示标题头,第二个row表示table内容,第三个row表示底部信息栏
drawCallback: function (settings) { // 重新渲染完成后执行
var api = this.api();
console.log( api.rows( {page:'current'} ).data() ); // 获取当前页码
},
info: true, // 是否显示总数信息
language: { // 对表格进行国际化
emptyTable: '表中没有可用数据了',
info: "_START_ - _END_ of _TOTAL_", // 修改10 - 20 of 30文字
infoEmpty: "没有记录",
infoFiltered: "从 _MAX_ 条记录中过滤"
lengthMenu: '每页显示 _MENU_ 条', // show xx entries
loadingRecords: '加载中',
processing: '处理中',
search: '搜索',
zeroRecords: '没有找到符合提交的数据',
paginate: {
first: '首页',
last: '尾页',
next: '下一页',
previous: '上一页'
}
},
lengthMenu: [10, 20, 50, 100], // 允许用户选择每页的现实数量
ordering: false, // 全局控制整个列表所有列的排序功能
paging: false, // 是否开启分页
/*
分页按钮样式
numbers:仅显示数字
simple:只显示Previous和Next
simple_numbers: 显示Previous、Next、总页数
full:显示First、Previous、Next、Last
full_number:显示First、Previous、Next、Last、总页数
first_last_numbers:显示First、Last、总页数
*/
pagintType: 'simple_numbers', // 分页按钮的样式
processing: true, // 是否在数据加载时出现“Processing”的提示
responsive: true,
rowReorder: true, // 和下面这个不同的是这个只是简单的允许拖动排序,仅仅是第一列可以拖动
rowReorder: { // 根据指定字段自动进行排序
dataSrc: 'order', // 指定排序字段
selector: 'tr td:not(:last-child)' // 指定可拖动的行元素,如果仅仅是'tr',那么整行都是可以直接拖动的
}
scrollX: true,
searching: false, // 屏蔽搜索框
serverSide: true, // 是否服务器端分页
});
阅读全文 »

Django自带了表单功能,可以与前端及后端完美集成,能非常方便地提供创建、更新或删除模型。

表单定义

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
class Post(models.Model):
name = models.CharField()
subtitle = models.CharField(blank=True)
content = models.TextField()
created_at = models.DatetimeField()
user = models.ForeignField(User)

class PostForm(forms.ModelForm):
error_css_class = 'error' # 统一定义错误行的类
required_css_class = 'required' # 统一定义必填项的样式

another_field = forms.DateField(
required=True, # 是否必填,如果是模型本身字段在Model定义时用blank=True
label="Custom Field Name", # 在表单中该字段展示的的label
label_suffix=":", # 默认情况label后面是冒号,这个参数可以指定后缀
initial="abc", # 字段显示的初始值
help_text="除Model本身字段以外的额外的字段",
error_messages=[] # 字段的错误消息列表
validators=[] # 验证函数列表,也可以在下面定义clean_字段名进行验证
disabled=False # 是否设置为disabled
widget={ # 扩展小部件,Meta里面也能批量进行控制
}
)
choice_field = forms.ModelChoiceField(queryset=MyChoices.objects.all())

class Meta:
model = Post # 对应的Model
fields = ("name", "subtitle", "content", "another_field") # 定义前端能够编辑的字段
labels = {"name": "Input the Name"} # 批量定义label
help_texts = {"name": "Enter a correct name"} # 批量定义提示信息
error_messages = {"name": {
"max_length": "字段太长了" # 定义指定错误的错误提示信息
}}
field_classes = { # 批量定义字段的css类
"name": "my_text"
}
widgets = {
"created_at": forms.TextInput(attrs={ # 设置html的一些属性
"type": "date",
"class": "my-class custom-class"
"placeholder": "设置placeholder"
}),
"description": forms.Textarea(attrs={"rows": 1, "cols": 20}) # 设置textarea的默认行数和列数
}

def __init__(self, *args, **kwargs):
super(PostForm, self).__init__(*args, **kwargs)
self.fields["name"].label = "Post Name" # 可以在这里修改model或者form class的字段的label或者其他值
self.fields['user'].queryset = User.objects.filter(id__in[1,2,3]) # 自定义外键的queryset,这个queryset结果不仅用于显示,还用于验证,表单提交的时候该字段也必须在这些值里面
self.fields['my_choice_field'] = forms.ChoiceField(choices=[1,2,3]) # 定义choice字段的可选值

def clean_name(self): # 自定义指定字段的验证,这里验证的是name字段
data = self.cleaned_data['name']
if 'test' in data:
raise ValidationError(_('Invalid name'))
return data
阅读全文 »

python自带库ftplib对ftp的连接提供了支持。下面是其基本的语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
ftp = ftplib.FTP(timeout=300)	# 连接基本的FTP
ftp = ftplib.FTP_TLS(timeout=300) # 连接FTPS

ftp.connect(host, port) # 连接目标服务器
ftp.login(username, password) # 登陆目标服务器
ftp.mkd('/abc') # 创建远程目录
ftp.dir() # 显示当前目录下的文件及目录,会直接打印到标准输出
ftp.nlst() # 返回当前目录下的文件及目录
ftp.cwd('') # 切换目录
ftp.pwd() # 返回当前所在目录
ftp.rmd(dirname) # 删除远程目录
ftp.delete(filename) # 删除远程文件
ftp.rename(fromname, toname) # 给远程文件重命名
ftp.close() # 关闭连接
ftp.voidcmd('NOOP') # 判断连接是否存活

## 上传文件
local_file_handler = open('test.txt', 'rb')
ftp.storbinary("STOR " + os.path.join(remote_path, filename), local_file_handler)

## 下载文件
local_file_handler = open('test.txt', 'rb')
ftp.retrbinary("RETR " + os.path.join(remote_path, filename), file_handler.write)
local_file_handler.seek(0)
阅读全文 »

  • 在本地开发的时候,由于有很多东西依赖于cookie,有些插件在写入cookie的时候可能没有判断服务端口导致无法写入cookie,功能无法正常使用,所以在开发和使用过程中最好用正常的http端口,即80443

  • 开发时最好打开调试模式:

    1
    2
    3
    4
    # vim wp-config.php
    define('WP_DEBUG', true);
    define('WP_DEBUG_LOG', true);
    define('SCRIPT_DEBUG', true);

插件开发基本概念

插件目录结构

  • wordpress源码的/wp-content/plugins下,一个目录就是一个插件

  • 插件内部的目录结构一般是这样的:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    test-plugin
    ├── assets
    │   ├── css
    │ │ └── test-plugin.css
    │ └── js
    │ └── test-plugin.js
    ├── include
    │ └── test-plugin.php
    └── readme.txt

帮助函数

用户相关函数

wp_signon

  • 通过用户名密码获取用户信息
1
2
$user = wp_signon(['user_login' => 'xxx', 'user_password' => 'xxx'], false);
echo $user->id

wp_update_user

  • 更新用户指定字段,但不知道为什么,就是不能更新用户的user_status字段,最后我只能$wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->users} SET user_status = 1 WHERE ID = %d", $user->ID ))来吧用户spam了
1
2
3
4
wp_update_user([
'ID' => $userId,
'user_url => 'xxx',
])

get_user_by

  • 通过制定字段获取用户
1
get_user_by( 'id', $userId );

get_user_meta

阅读全文 »

目前项目要为所有的请求添加调用频率限制,使用的是node-rate-limiter-flexible插件,后端api项目可以直接在将其作为一个Koa Middleware,但是前端却不能直接这样引用,因为我们前端使用了nuxtjs,如果直接将该插件作为koa middleware插入app中,那么每一个请求都会经过该插件的统计,包括页面中所有的静态文件请求等,但这其实并不是我们想要的,我们其实只想针对路由route进行统计和过滤。这时候可以将该插件插入nuxtjsserverMiddleware中去,作为nuxtjs的服务端中间件使用。

  • serverMiddleware的执行时机是服务端开始渲染页面之前,所以是服务端渲染的中间件

  • serverMiddleware可以针对指定的路由,如果不指定,则表示针对所有的路由

其配置是在nuxt.config.js中的,例如:

1
2
3
4
5
6
7
8
9
serverMiddleware: [
'~/serverMiddleware/rate-limiter.js', // 这里定义我们编写的rate-limiter插件,针对的是所有路由,注意是路由,不是每个页面请求

// Will register file from project api directory to handle /api/* requires
{ path: '/api', handler: '~/api/index.js' },

// We can create custom instances too
{ path: '/static2', handler: serveStatic(__dirname + '/static2') }
]
阅读全文 »

官方中文文档

常用命令CLI

1
2
3
4
5
6
7
npm install --save-dev @types/node @types/validator
npm install sequelize reflect-metadata sequelize-typescript

npm install mysql2 --save

npm install --save-dev sequelize-cli # 安装命令行工具npx
npx sequelize-cli init # 初始化,会创建config/migrations/seeders/models目录

注意上一步创建的config目录默认是json格式的,我们一般会想从.env文件中读取配置,通常要将它改成config.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
require('dotenv').config()

module.exports = {
development: {
username: process.env.DB_USERNAME,
password: null,
database: 'database_development',
host: '127.0.0.1',
dialect: 'mysql',
},
test: {
username: 'root',
password: null,
database: 'database_test',
host: '127.0.0.1',
dialect: 'mysql',
},
production: {
username: 'root',
password: null,
database: 'database_production',
host: '127.0.0.1',
dialect: 'mysql',
},
};

数据库连接

1
2
3
4
5
6
7
var sequelize = new Sequelize('mysql://用户名:密码@HOST:3306/数据库', {
dialect: 'mysql', // 如果不指定这个参数,可能会报错Dialect needs to be explicitly supplied as of v4.0.0
logging: false // 默认会将sql查询都输出到console.log中,设置为false可以不用输出,不输出sql语句
})

// 直接执行SQL raw queries
const records = await sequelize.query("SELECT * FROM `users`", { type: QueryTypes.SELECT });

模型定义

  • 数据类型包括:
    • 字符串:STRING、STRING(1024)、STRING.BINARY、TEXT、TEXT(‘tiny’)、CITEXT(仅PostgreSQL和SQLite)、TSVECTOR(仅PostgreSQL)
    • 布尔:BOOLEAN
    • 数字:INTEGER、INTEGER.UNSIGNED、INTEGER.ZEROFILL、INTEGER.UNSIGNED.ZEROFILL、BIGING、BIGING(11)、FLOAT、FLOAT(11)、FLOAT(11, 10)、REAL(仅PostgreSQL)、REAL(11)(PostgreSQL)、REAL(11, 10)(仅PostgreSQL)、DOUBLE、DOUBLE(11)、DOUBLE(11, 10)、DECIMAL、DECIMAL(10, 2)
    • 日期:DATE、DATE(6)(仅MySQL)、DATEONLY
    • UUID:可以自动为字段生成UUID,type为UUID,defaultValue为UUIDV1或者UUIDV4
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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
// 定义方式零,纯typescript的方式可以使用https://github.com/RobinBuschmann/sequelize-typescript
@Column(DataType.VIRTUAL)
accessToken: string; // 添加virtual额外的字段

@Column(DataType.JSON) data: any // JSON字段

@Column({field: 'user_id'})
userID: number; // 自定义字段名称

// 定义方式一,typescript方式
class PostModel extends Model {
// 定义一些值可以让其增加typescript声明
public id: number
public name: string

// 为typescript增加获取关联对象的方法声明
public getUser! BelongsToGetAssociationMixin<BandModel>

static initModel (sequelize: Sequelize): void {
id: {
autoIncrement: true,
primaryKey: true,
type: INTEGER
},
name: {
type: STRING,
allowNull: false,
defaultValue: 'test',
validate: { // 数据校验
is: /^[a-z]+$/i, // 满足某个正则
is: ["^[a-z]+$",'i'], // 同上,只是正则用数组来写
not: /^[a-z]+$/i,
not: ["^[a-z]+$",'i'],
isEmail: true,
isUrl: true,
isIP: true,
isIPv4: true,
isIPv6: true,
isAlpha: true,
isAlphanumeric: true,
isNumeric: true,
isInt: true,
isFloat: true,
isDecimal: true,
isLowercase: true,
isUppercase: true,
notNull: true,
isNull: true, // 只允许null
notEmpty: true, // 不能是空字符串
equals: 'specific value',
contains: 'foo',
notIn: [['foo', 'bar']],
isIn: [['foo', 'bar']],
notContains: 'bar',
len: [2,10],
isUUID: 4,
isDate: true,
isAfter: "2011-11-05",
isBefore: "2011-11-05",
max: 23,
min: 23,
isCreditCard: true, // 是否是信用卡数字
isEven(value) { // 自定义校验
if (parseInt(value) % 2 !== 0) {
throw new Error('Only even values are allowed!');
}
}
},
isIn: {
args: [['en', 'zh']],
msg: "Must be English or Chinese" // 上面的校验方式都能够自定义,但是min和max不知道为啥不行,如果是min或者max就改成用len吧
}
}
}

static relate (): void {
PostModel.belongsTo(UserModel, {
as: 'user',
foreignKey: 'user_id'
})
}

@AfterUpdate
static createUser(user: UserModel) {
console.log(user.id);
}

@AfterDestroy // 软删除也是这里
static async afterDestroy(use: UserModel) {}
}

// 定义方式二
const Post = sequelize.define('post', {
id: {
autoIncrement: true,
primaryKey: true,
type: INTEGER
},
name: {
type: STRING,
allowNull: false,
defaultValue: 'test'
},
firstName: {
type: STRING,
field: 'first_name' // 自定义列名称,
comment: '列注释' // 注释仅针对MySQL、MariaDB、PostgreSQL、MSSQL
},
fullName: {
type: VIRTUAL, // 定义virtual字段,即实际不存在数据库中的字段
get: function (this: UserModel) {
return this.firstName + this.name
},
set: function (val) {
this.setDataValue('name', val)
}
},
date: {
type: DATE,
defaultValue: NOW
},
// unique参数的值可以是不二值或者字符串,如果多格列具有相同的字符串unique,他们会组成一个复合唯一键
uniqueOne: { type: DataTypes.STRING, unique: 'compositeIndex' },
uniqueTwo: { type: DataTypes.INTEGER, unique: 'compositeIndex' },

data: {
type: JSON
},
created_at: {
type: DATE
}
}, {
indexes: [{ unique: true, fields: ['field1']}], // 也可以在最后创建索引
timestamps: true, // 是否自动添加createdAt和updatedAt
tableName: 'MyPosts' // 自定义table name,如果不提供,sequelize会根据模型名称自动以复数形式设置表名
paranoid: true, // 定义该表为偏执表,即自带软删除,使用destroy能自动软删除
deletedAt: 'mydelete', // 偏执表软删除字段默认为deletedAt,这里可以指定自定义的字段名
validate: { // 基于model的校验,可以同时校验多个字段
bothCoordsOrNone() {
if ((this.latitude === null) !== (this.longitude === null)) {
throw new Error('Either both latitude and longitude, or neither!');
}
}
}
}
);

// 定义模型关系
Post.associate = () => {
Post.User = Post.belongsTo(app.model.Post)
}

User.associate = () => {
User.hasMany(Post)
}

// 定义模型类方法
Post.customQuery = () => {}

// 定义模型实例方法
Post.prototype.customQuery = () => {}

关联关系定义

  • sequelize默认会给关联关系添加对应的读取方法,例如如果和user关联,那么会有getUser方法,而如果是一对多,或者多对多,那么会有getUsers方法,但是如果是typescript,就需要我们先将该方法声明一下

    1
    2
    3
    public getUsers: BelongsToManyGetAssociationsMixin<UserModel>	// 懒加载

    await fooInstance.$get('bar'); // sequelize-typescript不使用include手动获取关联对象

One to One一对一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 装饰器定义
@ForeignKey(() => Person)
@Column
authorId: number;
@BelongsTo(() => Person)
author: Person;

@BelongsTo(() => Person, 'person_id') // 指定外键
author: Person;

Post.User = Post.belongsTo(app.model.Post, { foreignKey: 'post_id', as: 'Post' }),
Post.PostOwn = User.belongsTo(app.model.Post, {'foreignKey': 'id', as: 'PostOwn'}) // 如果要与当前表自身做join等操作,那么也需要定义一个与自身的关联
PostModel.belongsTo(UserModel)

// hasOne自动添加的方法
fooInstance.getBar()
fooInstance.setBar()
fooInstance.createBar()

One to Many 一对多

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
const Foo = sequelize.define('foo', { name: DataTypes.STRING });
const Bar = sequelize.define('bar', { status: DataTypes.STRING });
Foo.hasMany(Bar, {
scope: { // 可以通过scope限制某个关联表的字段
status: 'open'
},
as: 'openBars'
});

// hasMany自动添加的方法
fooInstance.getBars()
fooInstance.countBars()
fooInstance.hasBar()
fooInstance.hasBars()
fooInstance.setBars()
fooInstance.addBar()
fooInstance.addBars()
fooInstance.removeBar()
fooInstance.removeBars()
fooInstance.createBar()

// belongsToMany自动添加的方法
fooInstance.getBars()
fooInstance.countBars()
fooInstance.hasBar()
fooInstance.hasBars()
fooInstance.setBars()
fooInstance.addBar()
fooInstance.addBars()
fooInstance.removeBar()
fooInstance.removeBars()
fooInstance.createBar()

Many to Many 多对多

多态多对多
  • 有中间表,且使用target_idtarget_type来表示关联的表的类型
  • 除了我下面这个例子,还可以参考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
// 例如一个用户有多篇文章,多辆车,一篇文章或者一辆车也能同时属于多个用户,那么就有这么几张表: Car, Post, User, UserThings关联表
CarModel.belongsToMany(UserModel, {
through: {
model: UserThingModel, // 中间表
unique: false, // 如果unique为true,那么表示只有一个
scope: {
targetType: 'car', // 当关联的是car时,其`target_type`字段为car
},
foreighKey: 'target_id',
constraints: false
}
})

PostModel.belongsToMany(UserModel, {
through: {
model: UserThingModel, // 中间表
unique: false, // 如果unique为true,那么表示只有一个
scope: {
targetType: 'post', // 当关联的是post时,其`target_type`字段为post
},
foreighKey: 'target_id',
constraints: false
}
})

增删改查

创建操作

1
2
3
4
5
6
7
8
9
10
11
12
const user = await User.create({ firstName: "Jane", lastName: "Doe" });

// 批量创建
const captains = await Captain.bulkCreate([
{ name: 'Jack Sparrow' },
{ name: 'Davy Jones' }
]);

await User.findOrCreate({
where:{}, // 比较的字段
defaults: {} // 填入的字段
})
阅读全文 »