豪翔天下

Change My World by Program

0%

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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.pwd() # 返回当前所在目录
ftp.rmd(dirname) # 删除远程目录
ftp.delete(filename) # 删除远程文件
ftp.rename(fromname, toname) # 给远程文件重命名
ftp.close() # 关闭连接

## 上传文件
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

帮助函数

用户相关函数

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
npm install --save-dev sequelize-cli	# 安装命令行工具npx

数据库连接

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

// 直接执行SQL row命令
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
// 定义方式一,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'
})
}

}

// 定义方式二
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
    public getUsers: BelongsToManyGetAssociationsMixin<UserModel>

One to One一对一

1
2
3
4
5
6
7
8
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: {} // 填入的字段
})
阅读全文 »

常用语法

基本标签

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 转义,显示原始html内容
{!! $name !!}

# 转义花括号,因为花括号是特殊字符,如果要直接显示{{}}等内容需要在前面加入@符号
@{{ $name }} // 会直接显示{{ $name }}

# 使用or简化三目运算符
{{ $name or 'Default'}} // {{ isset($name) ? $name : 'Default' }}

# 时间格式转换
{{ $user->created_at->format('d/m/Y') }}

# 获取当前路由
{{ url()->current() == route('/user') }}

# 获取路由参数
{{ request()->get('abc') }}

# 带路由参数的路由生成
{{ route('/users', $id)}}
{{ route('/users', [$id])}}
{{ route('/users', ['id' => $id])}}

{{-- 模板的注释语法 --}}
阅读全文 »

Request

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
// 获取请求指定字段
$request->name; // 直接获取
$request->input('name');
$request->input('name', 'Sally'); // 指定默认值
$request->input('user.name'); // 可以直接用点号获取JSON格式的请求体
$request->input('products.0.name'); // 如果请求数据是数组可以用这个方法获取数组内部元素
$request->input('products.*.name'); // 同上
$request->query('name'); // 获取查询参数
$request->query('name', 'Helen'); // 带默认值
$request->all(); // 获取所有请求参数为一个数组
$request->input(); // 同上
$request->query(); // 所有查询参数转换为数组
$request->boolean('archived'); // 获取布尔值,能够自动判断1/"1"/true/"true"/"on"/"yes",6.x开始
$request->only(['username', 'password']); // 仅获取指定字段的请求
$request->except(['credit_card']); // 仅排除指定字段的请求

// 判断请求是否包含某个key
$request->has('name');
$request->has(['name', 'email']);
$request->hasAny(['name', 'email']);
$request->filled('name'); // 是否包含并且不为空
$request->missing('name'); // 不包含

// 获取请求地址
$request->path(); // 获取请求路径,例如https://domain.com/foo/bar就会返回foo/bar
$request->is('admin/*'); // 正则匹配请求路径
$request->fullUrl(); // 包含请求参数的完整url,例如https://dmoain.com/foo/bar?abc=def
$request->url(); // 不包含请求参数的完整url,例如https://dmoain.com/foo/bar
$request->root(); // 获取域名部分,包括http,例如https://domain.com
$request()->getHost(); // 获取纯域名部分,例如domain.com

// 获取请求方法
$request->method();
$request->isMethod('post');// 判断请求方法

$request->route(); # 通过request获取Route对象

$request->cookie('name'); // 获取cookie,同Cookie::get('name');

// 动态改变或新增request的值
$request->merge([
'keyword' => $request->search,
'page' => 2
]);

// 判断请求类型
request()->ajax(); // 判断请求是否是ajax请求
request()->expectsJson(); // 判断客户端是否希望得到JSON响应

// 去掉路由参数
$request->route()->forgetParameter('param');
阅读全文 »

  • 默认启用了TrimStringsConvertEmptyStringsToNull两个中间件的,一个自动去除前后空白,一个将空字符串转换为null

直接验证

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
$validatedData = $request->validate([
'title' => 'required|unique:posts|max:255',
'title' => ['required', 'unique:posts', 'max:255'], // 也可以写成数组的形式
'person.*.email' => 'email|unique:users', // 校验数组
'person.*.first_name' => 'required_with:person.*.last_name'
'title' => ['required', function ($attribute, $value, $fail) { // 简单的自定义验证规则可以不用建验证类,直接用匿名函数
if ($value === 'foo') {
$fail($attribute.' is invalid.');
}
},],
]);

// 或者这样创建
$validator = Validator::make($request->all(), [
'title' => 'required|unique:posts|max:255',
'body' => 'required',
]);

// 判断请求类型,如果是ajax请求,那么返回json数据和422,如果非ajax那么重定向刷新页面
if ($validator->fails()) {
if ($request->ajax()) {
return response()->json($validator->messages(), Response::HTTP_BAD_REQUEST);
} else {
return redirect('post/create')
->withErrors($validator) // 刷新session中存储的错误信息,可用在view中
->withInput();
}
}

// 同样可以自定义错误信息
$messages = [
'required' => 'The :attribute field is required.',
];
$validator = Validator::make($input, $rules, $messages);

// 满足某个条件时才验证,例如下面当游戏>=100的时候才验证指定字段
$v->sometimes('reason', 'required|max:500', function ($input) {
return $input->games >= 100;
});
$v->sometimes(['reason', 'cost'], 'required', function ($input) {
return $input->games >= 100;
});
阅读全文 »

帮助方法

  • Laravel中的帮助方法无法重写,如果要重写,只能自己单独写放在app/helpers.php里面去,还需要在composer.json中添加配置:

    1
    2
    3
    4
    5
    6
    "autoload": {
    ...
    "files": [
    "app/helpers.php"
    ]
    }

数组和对象

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
# 返回第一个或最后一个元素
head($array);
last($array);

# 合并多个数组为一个单一的数组
Arr::collapse([[1,2,3], [4,5,6], [7,8,9]]); // [1,2,3,4,5,6,7,8,9]

# 从数组/字典移除指定的key
$array = ['name' => 'Desk', 'price' => 100, 'products' => ['desk' => ['price' => 100]]];
Arr::except($array, ['price']); // ['name' => 'Desk']
# 还可以通过点号的方式来移出多级key
Arr::forget($array 'products.desk'); // ['name' => 'Desk', 'price' => 100]
# 使用点号获取多级的值,第三个参数为默认值
$array = ['products' => ['desk' => ['price' => 100]]];
Arr::get($array, 'products.desk.price', $defaultValue); // 100
data_get($array, 'products.desk.price', $defaultValue); // 同上
data_get($array, '*.name');// 设置支持通配符
# 可使用has来查看是否有某个key,或多个key
Arr::has($array, ['product.price', 'product.discount']);
# 查看是否包含多个key中的一个
Arr::hasAny($array, ['product.name', 'product.discount']);
# 给数组添加元素
Arr::prepend($array, 'zero');
Arr::prepend($array, 'Desk', 'name'); // 会添加一个'name' => 'Desk'
# 获取并移除一个元素
Arr::pull($array, 'name', $defaultValue)

# 检查数组是否存在某个key
Arr::exists($array, 'key');

# 查找第一个回调函数返回true的元素,第三个参数可以指定默认值
Arr::first($array, function($value, $key) {return $value >= 2;}, $defaultValue);

# 仅返回指定key组成的数组
$array = ['name' => 'Desk', 'price' => 100, 'orders' => 10];
Arr::only($array, ['name', 'price']); // ['name' => 'Desk', 'price' => 100]

# 将数组/字典所有的value组成一个单一的数组
$array = ['name' => 'Joe', 'languages' => ['PHP', 'Ruby']];
$flattened = Arr::flatten($array); // ['Joe', 'PHP', 'Ruby']
# 将指定的key的value组成一个单一的数组
$array = [
['developer' => ['id' => 1, 'name' => 'Taylor']],
['developer' => ['id' => 2, 'name' => 'Abigail']],
];
Arr::pluck($array, 'developer.name'); // ['Taylor', 'Abigail']
Arr::pluck($array, 'developer.name', 'developer.id'); // [1 => 'Taylor', 2 => 'Abigail']

// 将数组作为url的查询参数
$array = ['name' => 'Taylor', 'order' => ['column' => 'created_at', 'direction' => 'desc']];
Arr::query($array); // name=Taylor&order[column]=created_at&order[direction]=desc

// 从数组中随机取一个元素
Arr::random($array);
Arr::random($array, 2); // 第二个参数表示随机取N个元素

// 数组排序
Arr::sort($array);
array_values(Arr::sort($array, function ($value) { // 如果是字段需要指定排序方式
return $value['name'];
}));

// 返回符合条件的元素组成的数组,类似于js里面的filter
Arr::where($array, function ($value, $key) {
return is_string($value);
});
阅读全文 »

前两天团队接到一个新项目,我需要为其配置Circle CI,于是就又折腾了一下,不过还好,毕竟之前做过运维的,CI的基本原理是知道的,所以很快就弄好了,这里也简单的记录一下。

1. 为项目开启Circle CI

这一步需要在circleci后台项目列表里面为指定的项目开启配置Set Up Project,因为第一步已经编写好了配置文件,所以这里可以直接选择start building。这一步circleci会自动在对应的项目里面添加Deploy Key并且会自动配置到circleci的项目管理里面,如下:

阅读全文 »