js循环组建一维数组

数组版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 let a = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]

let res = a[0];
let i = 1;
while (a[i]) {
let temp = [];
res.forEach(n => {
a[i].forEach(m => {
temp.push(n + '|' + m);
})
});
res = temp
i++;
}
console.log(res)

对象版

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
let spec = {
color: ['红', '蓝'],
memory: ['128G', '256G', '512G'],
version: ['常规'],
},
let data = [];
for (let k in spec) {
// 如果属性值为空,则不循环这个属性
if (spec[k].length) {
// 判断data长度
if (data.length) {
// 如果data不为空,创建容器,循环data和当前属性,放入容器,赋值给data
let temp = [];
for (let i = 0; i < data.length; i++) {
for (let j = 0; j < spec[k].length; j++) {
// 先序列化,再反序列化,即深拷贝
var field = JSON.parse(JSON.stringify(data[i]));
field[k] = spec[k][j] //拷贝好了之后,附加当前的键与值
temp.push(field)
}
}
data = temp
} else {
// 如果data为空,将第一个属性塞进去
for (let i in spec[k]) {
let field = {}
field[k] = spec[k][i]
data.push(field)
}
}
}
}

python常用技巧

开启一个http服务

1
python -m http.server 8000

开启一个ftp服务

1
python -m pyftpdlib

时间

获取时间

1
2
time.localtime()  # 结构时间
# time.struct_time(tm_year=2020, tm_mon=4, tm_mday=8, tm_hour=14, tm_min=29, tm_sec=53, tm_wday=2, tm_yday=99, tm_isdst=0) 结构时间

时间格式化

1
time.strftime('%Y-%M-%D %H:%M:%S', time.localtime())

peewee 用法详解

1
p = Person.create(Name='张三', Age=30, Birthday=date(1990, 1, 1))
1
2
3
p1 = Person(Name='王五', Age=50, Birthday=date(1970, 1, 1))
p1.save()
#save(force_insert=False, only=None)
1
Person.insert(Name='李四', Age=40, Birthday=date(1980, 1, 1)).execute()
1
2
3
4
5
6
7
8
9
10
11
12
13
Person.insert_many([
('张三', 30, date(1990, 1, 1)),
('李四', 40, date(1980, 1, 1)),
('王五', 50, date(1970, 1, 1))
],
['Name', 'Age', 'Birthday']
).execute()
Person.insert_many([
{'Name': '张三', 'Age': 30, 'Birthday': date(1990, 1, 1)},
{'Name': '李四', 'Age': 40, 'Birthday': date(1980, 1, 1)},
{'Name': '王五', 'Age': 50, 'Birthday': date(1970, 1, 1)}
]
).execute()

1
2
3
4
5
Person.delete().where(Person.Name=='王五').execute()

p = Person.get(Person.Name=='张三')
p.delete_instance()

1
2
3
4
5
6
p = Person(Name='王五', Age=50, Birthday=date(1970, 1, 1))
p.save()

Person.update({Person.Name: '赵六', Person.Remarks: 'abc'}).where(Person.Name=='王五').execute()
Person.update({'Name': '赵六', 'Remarks': 'abc'}).where(Person.Name=='张三').execute()
Person.update(Name='赵六', Remarks='abc').where(Person.Name=='李四').execute()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
p2 = Person.get(Person.Name == '李四')

Person.get_or_none(Person.Name == 'none') # none

Person.get_by_id(1)

p, created = Person.get_or_create(Name='赵六', defaults={'Age': 80, 'Birthday': date(1940, 1, 1)})
# created表示是否新增

Person.select() # ModelSelect

Person.select().count()

Person.select().order_by(+Person.Age)
Person.select().order_by(-Person.Age) #降序
Person.select().order_by(Person.Age.asc())

# 将查询结果转化为字典列表
dicts = Person.select().dicts()
print(list(dicts))

# 将查询结果转化为元组列表
tuples = Person.select().tuples()
print(list(tuples))

peewee 中有很多方法是延时执行的,需要调用 execute() 方法使其执行。

本文中代码样例所使用的 Person 模型如下:

1
2
3
4
5
6
7
8
9
10
11
class Person(Model):
Name = CharField()
Age = IntegerField()
Birthday = DateTimeField()
Remarks = CharField(null=True)
class Meta:
setting = {'host': 'localhost', 'user': 'root', 'password': 'rootroot', 'database': 'datang'}
database = MySQLDatabase(**setting)
# database = SqliteDatabase('my_app.db')
def set_database(self):
self._meta.database = SqliteDatabase('new_app.db')

新增

1、Model.create 向数据库中插入一条记录,并返回一个新的实例。

1
p = Person.create(Name='张三', Age=30, Birthday=date(1990, 1, 1))

2、p.save 保存实例
示例:

1
2
3
p1 = Person(Name='王五', Age=50, Birthday=date(1970, 1, 1))
p1.save()
#save(force_insert=False, only=None)

参数:

  • force_insert:是否强制插入
  • only(list):需要持久化的字段,当提供此参数时,只有提供的字段被持久化。

3、insert
insert 只插入数据而不创建模型实例,返回新行的主键。

1
Person.insert(Name='李四', Age=40, Birthday=date(1980, 1, 1)).execute()

4、insert_many
语法:

1
insert_many(rows, fields=None)

参数:

  • rows:元组或字典列表,要插入的数据
  • fields(list):需要插入的字段名列表。

    说明:
    1、当 rows 传递的是字典列表时,fields 是不需要传的,如果传了,那么,rows 中的字段在字典中必须存在,否则报错。如果没有传递 fields 参数,那么默认取所有字典的交集作为插入字段。这个也好理解,比如一个字典的键是a、b、c,一个是 b、c、d,那么就取 b、c 作为需要插入的字段。peewee 不会为缺失的字段做默认处理。
    2、当 rows 传递的是元组列表时,必须指定 fields,并且 fields 中字段名的顺序跟元组一致。元组中值的数量必须大于等于 fields 中字段的数量,一般建议是保持一致。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Person.insert_many([
('张三', 30, date(1990, 1, 1)),
('李四', 40, date(1980, 1, 1)),
('王五', 50, date(1970, 1, 1))
],
['Name', 'Age', 'Birthday']
).execute()

Person.insert_many([
{'Name': '张三', 'Age': 30, 'Birthday': date(1990, 1, 1)},
{'Name': '李四', 'Age': 40, 'Birthday': date(1980, 1, 1)},
{'Name': '王五', 'Age': 50, 'Birthday': date(1970, 1, 1)}
]
).execute()

对于批量操作,应该放在事务中执行:

1
2
with db.atomic():
Person.insert_many(data, fields=fields).execute()

在使用批量插入时,如果是 SQLiteSQLite3 版本必须为 3.7.11.0 或更高版本才能利用批量插入API。此外,默认情况下,SQLiteSQL 查询中的绑定变量数限制为 999。

SQLite 中,当批量插入的行数超过 999 时,就需要使用循环来将数据批量分组:

1
2
3
with db.atomic():
for idx in range(0, len(data), 100):
Person.insert_many(data[idx: idx+100], fields=fields).execute()

Peewee 中带有一个分块辅助函数 chunked(),使用它可以有效地将通用迭代块分块为一系列批量迭代的迭代:

1
2
3
4
5
6
from peewee import chunked

# 一次插入 100 行.
with db.atomic():
for batch in chunked(data, 100):
Person.insert_many(batch).execute()

5、bulk_create
语法:

bulk_create(model_list, batch_size=None)
1
2
3
4
5
6
7
8
9
10
11
参数:

- `model_list (iterable)`:未保存的模型实例的列表或其他可迭代对象。
- `batch_size (int)`:每次批量插入的行数。如果未指定,则一次性全部插入。
示例:
简单来说,`insert_many` 使用字典或元组列表作为参数,而 `model_list` 使用模型实例列表作为参数,就这区别。
```python
data = [Person(Name='张三~', Age=30, Birthday=date(1990, 1, 1)),
Person(Name='李四~', Age=40, Birthday=date(1980, 1, 1))]
with db.atomic():
Person.bulk_create(data)
> 注意:如果使用的是 `Postgresql`(支持该`RETURNING`子句),则先前未保存的模型实例将自动填充其新的主键值。 例如用的是 `SQLite`,执行上述代码之后,`print(data[0].id)` 显示的结果是 `None`。 6、`batch_commit` 这不是一个好的方法,来看下面的例子
1
2
3
4
5
6
data_dict = [{'Name': '张三', 'Age': 30, 'Birthday': date(1990, 1, 1)},
{'Name': '李四', 'Age': 40, 'Birthday': date(1980, 1, 1)},
{'Name': '王五', 'Age': 50, 'Birthday': date(1970, 1, 1)}]

for row in db.batch_commit(data_dict, 100):
p = Person.create(**row)
查看 SQL 语句如下:
1
2
3
4
('BEGIN', None)
('INSERT INTO "person" ("Name", "Age", "Birthday") VALUES (?, ?, ?)', ['张三', 30, datetime.date(1990, 1, 1)])
('INSERT INTO "person" ("Name", "Age", "Birthday") VALUES (?, ?, ?)', ['李四', 40, datetime.date(1980, 1, 1)])
('INSERT INTO "person" ("Name", "Age", "Birthday") VALUES (?, ?, ?)', ['王五', 50, datetime.date(1970, 1, 1)])
其实,batch_commit 就是自动添加了一个事务,然后一条条的插入,所以返回的模型实例中能获取到主键。 参数第一个是字典列表,第二个就是每多少条启用一个事务,大家可以把它改成 1 看下 SQL 语句就明白了。 7、`insert_from` 使用 SELECT 查询作为源 INSERT 数据。此 API 应用于 INSERT INTO … SELECT FROM … 形式的查询。 语法:
1
insert_from(query, fields)
参数: query:SELECT查询用作数据源 fields:要将数据插入的字段,此参数必须要的 示例:我们将 Person 表按原结构复制一个 Person2 表出来,以做演示。
1
2
data = Person.select(Person.Name, Person.Age, Person.Birthday)
Person2.insert_from(data, ['Name', 'Age', 'Birthday']).execute()
注意: 因为是 INSERT INTO … SELECT FROM … 形式的,所以数据源的列跟要插入的列必须保持一致。 # 二、删除 1、`delete` delete 后加 where 删除指定记录,如果不加 where,则删除全部记录。
1
Person.delete().where(Person.Name=='王五').execute()
2、delete_instance 删除给定的实例。 语法:
1
delete_instance(recursive=False, delete_nullable=False)
1
2
p = Person.get(Person.Name=='张三')
p.delete_instance()
`delete_instance` 直接执行删除了,不用调用execute() 方法。 参数: 一般我都是先讲参数再讲示例的,这次倒过来,示例其实很简单,一看就明白。但是这个参数缺需要好好讲下。 这两个参数都跟外键有关。我们修改一下测试用的模型。假设有这样两个模型,一个人员,一个部门,人员属于部门。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Department(Model):
Name = CharField()

class Meta:
database = db


class Person(Model):
Name = CharField()
Age = IntegerField()
Birthday = DateTimeField()
Remarks = CharField(null=True)
Department = ForeignKeyField(Department, null=True) # 这里外键可为空和不可为空是不一样的,下面说明

class Meta:
database = db
① 当 recursive=False 时,只删除了【部门】,【人员】没有影响,从 SQL 语句中可以看出。
1
2
d = Department.get(1)
d.delete_instance(recursive=False)
1
2
3
# 执行的 SQL 语句
('SELECT "t1"."id", "t1"."Name" FROM "department" AS "t1" WHERE ? LIMIT ? OFFSET ?', [1, 1, 0])
('DELETE FROM "department" WHERE ("department"."id" = ?)', [1])
② 当 recursive=True ,并且外键不可为空时,会先删除【部门】下的【人员】,再删除【部门】。
1
2
3
4
5
6
d = Department.get(1)
d.delete_instance(recursive=True)
# 执行的 SQL 语句
('SELECT "t1"."id", "t1"."Name" FROM "department" AS "t1" WHERE ? LIMIT ? OFFSET ?', [1, 1, 0])
('DELETE FROM "person" WHERE ("person"."Department_id" = ?)', [1])
('DELETE FROM "department" WHERE ("department"."id" = ?)', [1])
③ 当 recursive=True ,并且外键可为空时,先将【人员】的【部门ID(外键字段)】置为了 NULL,再删除【部门】。
1
2
3
4
5
6
d = Department.get(1)
d.delete_instance(recursive=True)
# 执行的 SQL 语句
('SELECT "t1"."id", "t1"."Name" FROM "department" AS "t1" WHERE ? LIMIT ? OFFSET ?', [1, 1, 0])
('UPDATE "person" SET "Department_id" = ? WHERE ("person"."Department_id" = ?)', [None, 1])
('DELETE FROM "department" WHERE ("department"."id" = ?)', [1])
④ delete_nullable 仅在 recursive=True 且外键可为空时有效,和 ③ 一样,当 delete_nullable=True 时,会删除【人员】,而不是将【人员的部门ID】置为 NULL。
1
2
3
4
5
6
7
d = Department.get(1)
d.delete_instance(recursive=True, delete_nullable=True)

# 执行的 SQL 语句
('SELECT "t1"."id", "t1"."Name" FROM "department" AS "t1" WHERE ? LIMIT ? OFFSET ?', [1, 1, 0])
('DELETE FROM "person" WHERE ("person"."Department_id" = ?)', [1])
('DELETE FROM "department" WHERE ("department"."id" = ?)', [1])
# 三、修改 1、save 之前说过,save() 方法可以插入一条记录,一旦模型实例具有主键,任何后续调用 save() 都将导致 UPDATE 而不是另一个 INSERT。模型的主键不会改变。
1
2
3
4
5
p = Person(Name='王五', Age=50, Birthday=date(1970, 1, 1))
p.save()
print(p1.id)
p.Remarks = 'abc'
p.save()
这个例子,第一次执行的 save 是 INSERT,第二次是 UPDATE。 这里解释一下,Person 这个模型,我并没有指定主键,peewee 会自动增加一个名为 id 的自增列作为主键。在执行第一个 save() 方法的时候,主键没值,所以执行 INSERT,save() 方法执行之后,自增列的值就返回并赋给了模型实例,所以第二次调用 save() 执行的是 UPDATE。 如果模型中一开始就用 PrimaryKeyField 或 primary_key 指定了主键,那么 save 执行的永远都是 update,所以什么主键不存在则 INSERT,存在则 UPDATE 这种操作根本不存在,只能自己来写判断。 2、update update 用于批量更新,方法相对简单,以下三种写法都可以
1
2
3
4
5
6
7
8
# 方法一
Person.update({Person.Name: '赵六', Person.Remarks: 'abc'}).where(Person.Name=='王五').execute()

# 方法二
Person.update({'Name': '赵六', 'Remarks': 'abc'}).where(Person.Name=='张三').execute()

# 方法三
Person.update(Name='赵六', Remarks='abc').where(Person.Name=='李四').execute()
3、原子更新 看这样的一个需求,有一张表,记录博客的访问量,每次有人访问博客的时候,访问量+1。 因为懒得新建模型,我们就以 Person 模型的 Age + 1 来演示。 我们可以这样来写:
1
2
3
for p in Person.select():
p.Age += 1
p.save()
这样当然是可以实现的,但是这不仅速度慢,而且如果多个进程同时更新计数器,它也容易受到竞争条件的影响。 我们可以用 update 方法来实现。
1
Person.update(Age=Person.Age+1).execute()
四、查询 1、get Model.get() 方法检索与给定查询匹配的单个实例。 语法:
1
get(*query, **filters)
参数: query:查询条件 filters:Mapping of field-name to value for Django-style filter. 我翻遍网上文章和官方文档都没找到这玩意怎么用! 示例:
1
p1 = Person.get(Name='张三')
或者
1
p2 = Person.get(Person.Name == '李四')
当获取的结果不存在时,报 Model.DoesNotExist 异常。如果有多条记录满足条件,则返回第一条。 2、get_or_none 如果当获取的结果不存在时,不想报错,可以使用 Model.get_or_none() 方法,会返回 None,参数和 get 方法一致。 3、get_by_id 对于主键查找,还可以使用快捷方法Model.get_by_id()。
1
Person.get_by_id(1)
4、get_or_create Peewee 有一个辅助方法来执行“获取/创建”类型的操作: Model.get_or_create() 首先尝试检索匹配的行。如果失败,将创建一个新行。
1
2
3
4
5
6
p, created = Person.get_or_create(Name='赵六', defaults={'Age': 80, 'Birthday': date(1940, 1, 1)})
print(p, created)
# SQL 语句
('SELECT "t1"."id", "t1"."Name", "t1"."Age", "t1"."Birthday", "t1"."Remarks" FROM "person" AS "t1" WHERE ("t1"."Name" = ?) LIMIT ? OFFSET ?', ['赵六', 1, 0])
('BEGIN', None)
('INSERT INTO "person" ("Name", "Age", "Birthday") VALUES (?, ?, ?)', ['赵六', 80, datetime.date(1940, 1, 1)])
参数: get_or_create 的参数是 **kwargs,其中 defaults 为非查询条件的参数,剩余的为尝试检索匹配的条件,这个看执行时的 SQL 语句就一目了然了。对于“创建或获取”类型逻辑,通常会依赖唯一 约束或主键来防止创建重复对象。但这并不是强制的,比如例子中,我以 Name 为条件,而 Name 并非主键。只是最好不要这样做。 返回值: get_or_create 方法有两个返回值,第一个是“获取/创建”的模型实例,第二个是是否新创建。 5、select 使用 Model.select() 查询获取多条数据。select 后可以添加 where 条件,如果不加则查询整个表。 语法:
1
select(*fields)
参数: fields:需要查询的字段,不传时返回所有字段。传递方式如下例所示。 示例:
1
ps = Person.select(Person.Name, Person.Age).where(Person.Name == '张三')
`select()` 返回结果是一个 `ModelSelect` 对象,该对象可迭代、索引、切片。当查询不到结果时,不报错,返回 `None`。并且 `select()` 结果是延时返回的。如果想立即执行,可以调用 `execute()` 方法。 注意:`where` 中的条件不支持 `Name='张三'` 这种写法,只能是 `Person.Name == '张三'`。 6、获取记录条数 `count` 方法 使用 `.count()` 方法可以获取记录条数。
1
Person.select().count()
也许你会问,用 `len()` 方法可以吗?当然也是可以的,但是是一种不可取的方法。
1
len(Person.select())
这两者的实现方式天差地远。用 count() 方法,执行的 SQL 语句是:
1
('SELECT COUNT(1) FROM (SELECT 1 FROM "person" AS "t1") AS "_wrapped"', [])
而用 len() 方法执行的 SQL 语句却是:
1
('SELECT "t1"."id", "t1"."Name", "t1"."Age", "t1"."Birthday", "t1"."Remarks" FROM "person" AS "t1"', [])
直接返回所有记录然后获取长度,这种方法是非常不可取的。 7、排序 order_by 方法
1
Person.select().order_by(Person.Age)
排序默认是升序排列,也可以用 + 或 asc() 来明确表示是升序排列:
1
2
Person.select().order_by(+Person.Age)
Person.select().order_by(Person.Age.asc())
用 - 或 desc() 来表示降序:
1
2
Person.select().order_by(-Person.Age)
Person.select().order_by(Person.Age.desc())
如要对多个字段进行排序,逗号分隔写就可以了。 五、查询条件 当查询条件不止一个,需要使用逻辑运算符连接,而 Python 中的 and、or 在 Peewee 中是不支持的,此时我们需要使用 Peewee 封装好的运算符,如下: 逻辑符 含义 样例 & and Person.select().where((Person.Name == '张三') & (Person.Age == 30)) | or Person.select().where((Person.Name == '张三') \| (Person.Age == 30)) ~ not Person.select().where(~Person.Name == '张三') 特别注意:有多个条件时,每个条件必须用 () 括起来。 当条件全为 and 时,也可以用逗号分隔,get 和 select 中都可以:
1
Person.get(Person.Name == '张三', Person.Age == 30)
六、支持的比较符 运算符 含义 == 等于 < 小于 <= 小于等于 > 大于 >= 大于等于 != 不等于 << x in y,其中 y 是列表或查询 >> x is y, 其中 y 可以是 None % x like y ** x like y 注意:由于 SQLite 的 LIKE 操作默认情况下不区分大小写,因此 peewee 将使用 SQLite GLOB 操作进行区分大小写的搜索。glob 操作使用星号表示通配符,而不是通常的百分号。如果您正在使用 SQLite 并希望区分大小写的部分字符串匹配,请记住使用星号作为通配符。 解释一下,在 SQLite 中,如果希望 like 的时候区分大小写,可以这么写:
1
Person.select().where(Person.Remarks % 'a*')
如果不希望区分大小写,这么写:
1
Person.select().where(Person.Remarks ** 'a%')

python常用方法

字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
name = 'hello world'
name.title()
# Hello World
name.upper()
# HELLO WORLD
name.lower()
# hello world
'hello '.rstrip()
# hello 右边空格
' hello'.lstrip()
# 左边空格
' hello '.strip()
# 两边空格

python GUI工具书籍

python测试开发_AI命理

python测试开发项目实战-目录
python工具书籍下载-持续更新
python 3.7极速入门教程 - 目录
Python GUI Programming Cookbook 2nd - 2017.pdf

图片.png
https://github.com/PacktPublishing/Python-GUI-Programming-Cookbook-Second-Edition 60星左右

WxPython_Application_Development_Cookbook - 2015.pdf

图片.png
https://github.com/driscollis/wxPython_recipes_book_code 20星左右

wxPython Recipes - 2017.pdf

图片.png
Tkinter GUI Programming By Example(conv) - 2018.pdf

图片.png
https://github.com/PacktPublishing/Tkinter-GUI-Programming-by-Example 不到10星

Tkinter GUI Application Development Cookbook - 2018.pdf

图片.png
https://github.com/PacktPublishing/Tkinter-GUI-Application-Development-Cookbook 不到10星

Tkinter GUI Application Development Blueprints 2nd - 2018.pdf

图片.png
https://github.com/PacktPublishing/Tkinter-GUI-Application-Development-Blueprints-Second-Edition 30星左右

参考资料
项目实战讨论QQ群630011153 144081101
本文最新版本地址
本文涉及的python测试开发库 谢谢点赞!
本文相关海量书籍下载
QT5 Python GUI Programming Cookbook - 2018.pdf

图片.png
https://github.com/PacktPublishing/Qt5-Python-GUI-Programming-Cookbook 20星左右

Python GUI programming with Tkinter - 2018.pdf

图片.png
https://github.com/PacktPublishing/Python-GUI-Programming-with-Tkinter/ 20星左右

Pyside GUI Application Development(2nd) - 2016.pdf

图片.png

centos 安装 node 和 npm

安装gcc

1
yum install gcc gcc-c++

下载node国内镜像(推荐)

1
wget https://npm.taobao.org/mirrors/node/v10.14.1/node-v10.14.1-linux-x64.tar.gz

解压并重命名文件夹

1
2
tar -xvf  node-v10.14.1-linux-x64.tar.gz
mv node-v10.14.1-linux-x64 /usr/local/node

添加环境变量

1
vi /etc/profile

在文件最后添加以下配置:

1
2
export NODE_HOME=/usr/local/node  
export PATH=$NODE_HOME/bin:$PATH

刷新配置

1
source /etc/profile

验证结果:

1
2
3
node -v

npm -v

composer阿里云镜像

全局配置(推荐)

所有项目都会使用该镜像地址:

1
composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/

取消配置:

1
composer config -g --unset repos.packagist

项目配置

仅修改当前工程配置,仅当前工程可使用该镜像地址:

1
composer config repo.packagist composer https://mirrors.aliyun.com/composer/

取消配置:

1
composer config --unset repos.packagist

调试

composer 命令增加 -vvv 可输出详细的信息,命令如下:

1
composer -vvv require alibabacloud/sdk

遇到问题?

  1. 建议先将Composer版本升级到最新:
    1
    composer self-update
  2. 执行诊断命令:
    1
    composer diagnose
  3. 清除缓存:
    1
    composer clear
  4. 若项目之前已通过其他源安装,则需要更新 composer.lock 文件,执行命令:
    1
    composer update --lock

nginx搭建websocket服务器与客户端,websocket 从入门到放弃

1
2
3
4
5
6
7
8
9
10
server{
listen 80;
server_name wss.test;
location / {
proxy_pass http://127.0.0.1:9501;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}

websocket_server
守护进程方式运行 nohup php artisan swoole:server &

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
<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;

class SocketServer extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'swoole:server';

/**
* The console command description.
*
* @var string
*/
protected $description = 'swoole服务端,用于开单的消息转发';

/**
* Create a new command instance.
*
* @return void
*/
public function __construct()
{
parent::__construct();
}

/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
//创建websocket服务器对象,监听0.0.0.0:9502端口
$ws = new \Swoole\WebSocket\Server("127.0.0.1", 9501);
//监听WebSocket连接打开事件
$ws->on('open', function ($ws, $request) {
logger()->channel('wss_open')->info($request->fd . ' socket 连接');
$get = $request->get;
if ($get) {
foreach ($get as $key => $value) {
logger()->channel('wss_open')->info($value);
$this->setFid($value, $request->fd);
}
}
$ws->push($request->fd, json_encode(['status' => 1, 'message' => '连接成功'], 256));
});

//监听WebSocket消息事件
$ws->on('message', function ($ws, $frame) {
$data = json_decode($frame->data, true);
logger()->channel('wss_message')->info($frame->fd . ' socket 连接', $data);
if ($data) {
$su = $this->getFid($data['to']);
if ($ws->exist($su)) {
logger()->channel('wss_message')->info('发送成功');
$ws->push($su, json_encode([
'data' => $data['data'],
'status' => 1
], 256));
} else {
logger()->channel('wss_message')->info('发送失败');
$ws->push($frame->fd, json_encode(['status' => 0, 'message' => '找不到目标连接'], 256));
}
}
});

//监听WebSocket连接关闭事件
$ws->on('close', function ($ws, $fd) {
$this->delFid($fd);
});
$ws->start();
}

public function setFid($uid, $fid)
{
$client = new \Predis\Client();
$client->hset('uid', $uid, $fid);
}

public function getFid($uid)
{
$client = new \Predis\Client();
return $client->hget('uid', $uid);
}

public function delFid($fid)
{
$client = new \Predis\Client();
$uids = $client->hgetall('uid');
foreach ($uids as $uid => $fd) {
if ($fid == $fd) {
$client->hdel('uid', $uid);
echo '删除' . $uid;
}
}
}
}

前端 client

1
2
3
4
5
6
7
8
9
10
11
12
13
 const socket = new WebSocket('ws://wss.test?uid=customOrder.cashier.2008');

// Connection opened
socket.addEventListener('open', function (event) {
app.start_message = 'Hello Server!'
socket.send('Hello Server!');
});

// Listen for messages
socket.addEventListener('message', function (event) {
app.messages.push(event.data)
console.log('Message from server ', event.data);
});

php-fpm client 基于 textalk/websocket

1
2
$client = new  \WebSocket\Client($uri);
$client->send(json_encode(['data' => $data, 'to' => $uid]));

创建自己的python模块并上传到pypi

摘要:这篇文章描述了自己动手把需要的代码封装成python模块的方法。完成后,我们可以直接使用pip install xxx下载自己的模块并使用内置的函数。

介绍
python作为一门鼎鼎大名的语言,最有优势的地方就是它拥有无数第三方的模块可以直接拿来使用。以numpy(一个用于实现科学计算的模块)为例,常见的用法一般是这样,相信写过python的肯定是见得多了。

1
2
import numpy
from numpy import core

name 必须是唯一的,允许使用数字和字母,推荐使用中划线 -而不是下划线_,因为pip安装只支持中划线,比如pip install my-pkg,为了不给自己找麻烦请听话。
version推荐遵循语义化版本号规则,简单说就像这样:1.2.0
作者姓名和邮箱地址不一定要和你的PyPI账号一致。

创建pip目录结构

选定的包名在pypi网站上没有使用过,可以访问官网,搜索一下想用的名字有没有被占用。
我的python包名叫做clinicaltrial,目录结构如下:我们先创建空文件,指定以下的名字

1
2
3
4
clinicaltrial/
clinicaltrial/
__init__.py
setup.py

其中主目录是整个包的根目录,setup.py是部署到pip需要的配置文件,子目录clinicaltrial是真正的python代码位置。
我们在__init__.py中写入代码:

1
2
def start():
print("import successful")

然后在setup.py文件中写入以下配置信息(参数里的内容可以修改,可以添加的参数具体可以参考python官方文档)

1
2
3
4
5
6
7
8
9
10
11
from setuptools import setup
setup(name='clinicaltrial',
version='0.1',
description='clinical trial information retriver',
url='http://github.com/tongling/clinicaltrial',
author='Ling',
author_email='tonglingacademic@gmail.com',
license='MIT',
packages=['clinicaltrial','filter'],
zip_safe=False)

然后打开命令行,切换到当前目录,输入

1
pip install .

然后,在我们的系统上,就可以导入这个包了:

1
2
import clinicaltrial
clinicaltrial.start()

测试本地打包命令
如果上面的都没问题,在本地目录执行以下命令应该能成功在dist目录下生成*.tar.gz的包文件。

1
python setup.py sdist

上传并发布包文件到PyPI
创建 PyPI账号
非常简单,直接通过官网注册 https://pypi.python.org/, 但是需要验证邮件并确认激活。

创建用户验证文件 ~/.pypirc
在自己的用户目录下新建一个空白文件命名为.pypirc,内容如下:

1
2
3
4
5
6
7
[distutils]
index-servers=pypi

[pypi]
repository = https://upload.pypi.org/legacy/
username = <username>
password = <password>

用户名和密码就是上一步骤所创建的,直接明文输入。如果你觉得明文密码不安全也可以留空,在后面的上传过程中会提示你手动输入。

注册你的包
你需要到PyPI注册并验证你的包,之后才能开始真正上传,注册的方式有以下几种。

使用命令python setup.py register,最简单但官网不推荐,因为使用的是HTTP未加密,有可能会被攻击人嗅探到你的密码。
通过PyPI网站提交表单完成注册验证。
安装 pip install twine 然后在通过命令 twine register dist/mypkg.whl 完成注册。
上传并完成发布
你可以任选以下两种方式之一发布你的轮子。

使用命令:python setup.py sdist upload,还是和上面一样,简单但有安全隐患,目前已淘汰。
使用 twinetwine upload dist/*
管理你的包
如果你的包已经上传成功,那么当你登录PyPI网站后应该能在右侧导航栏看到管理入口。

1
pypi_manage

点击包名进去后你可以对你的包进行管理,当然你也可以从这里删除这个包。

让别人使用你的包
包发布完成后,其他人只需要使用pip就可以安装你的包文件。比如:

1
pip install package-name

如果你更新了包,别人可以可以通过–update参数来更新:

1
pip install package-name --update