豪翔天下

Change My World by Program

0%

官方文档

使用方法

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
var swiper = new Swiper('.swiper-container', {
slidesPerView: 'auto', // 每一页的slide数量自动确定,这样可以做到不用整数个显示,也不会导致左右空白
spaceBetween: 10, // 每两个slide之间的间隔
centeredSlides: false, // 不用从居中开始,否则左边是空白的
pagination: {
el: '.swiper-pagination', // 分页元素位置
clickable: true,
},
direction: 'vertical', // horizontal滚动方向
width: 500, // 设置slide的高度和宽度,单位职能是px
height: 500,
breakpoints: { // 可以自定义不同的breakpoints里面的不同参数
1280: { // 屏幕宽度>=1280时候的breakpoings
width: 1000,
height: 1000,
direction: 'vertical', // 任何参数都可以breakpoint
}
}
scrollbar: {
el: '.swiper-scrollbar', // scrollbar的位置
clickable: true,
hide: true
},
navigation: { // 指定翻页按钮
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
},
on: { // 使用对应的事件
afterInit: function () {}
activeIndexChange: function () {}, // 这个事件触发时active图片还没改变
slideChange: function() {}, // 这个事件触发时图片就改变了
slideChangeTransitionEnd: function() {}, // 感觉这个方法才是真正的改变完成了
}
});
阅读全文 »

安装

1
pip install django-celery-beat

配置

  1. settings.py中添加如下配置
1
2
3
4
5
6
7
8
9
10
INSTALLED_APPS = (
...,
'django_celery_beat',
)

CELERY_BROKER_URL = 'sqla+sqlite:///' + os.path.join(BASE_DIR, 'db.sqlite3')
CELERY_IMPORTS = ['schedules'] # 指定需要在哪些目录扫描定时任务,如果不添加则会出现 Received unregistered task of type 错误
CELERY_TIMEZONE = 'Asia/Shanghai'
CELERYBEAT_SCHEDULER = 'djcelery.schedulers.DatabaseScheduler'
CELERY_RESULT_BACKEND = 'django-db' # 这是下面的django-celery-results保存结果使用的
  1. 使用命令python manage.py migrate创建数据库迁移,会生成这几张表:
1
2
3
django_celery_beat.models.PeriodicTask # 周期性任务
django_celery_beat.models.IntervalSchedule # 间隔性任务
django_celery_beat.models.CrontabSchedule # 定时任务
  1. 创建启动文件,在manage.py所在目录创建一个启动文件celery.py:
1
2
3
4
5
6
7
8
9
10
import os

from celery import Celery

from life_console import settings

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "项目名.settings")
celery_app = Celery("项目名", broker=settings.CELERY_BROKER_URL)
celery_app.config_from_object("django.conf:settings", namespace="CELERY")
celery_app.autodiscover_tasks()
阅读全文 »

本来以为现在写去年的年终总结已经太晚了,可是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
  • 新用户默认会有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

阅读全文 »