晨曦's Blog

This is a window to the soul

前言

希望能像 npm install 那样自动的将安装的包加入到 requirements.txt 文件中,但是同时又不希望把子依赖加入其中

虽然我们能通过 pip freeze > requirements.txt 将依赖导出,但是这样导出的依赖,会把包的其他子依赖也导出,导致重新安装的时候总是提示包的版本不对

解决

通过 bashalias 或者函数来解决,在.zshrc 中添加以下函数

1
2
3
function pip-install {
pip install $1 && pip freeze | grep -w "${1}=" >> requirements.txt
}

使用

1
pip-install sanic

效果

1
2
cat requirements.txt
sanic==21.6.2

从上面文件可以看出 requirements.txt 中并没有 sanic 的其他子依赖,至于 sanic 的其他子依赖会在安装 sanic 时自动安装就不用管它了

最后

最后我们在其他地方使用项目的时候只需要安装 requirements.txt 中的包就行了

1
pip install -r requirements.txt

前言

Mac 系统中,如果利用 Homebrew 安装 LuaRocks,默认只会安装最新版本的 Lua。鉴于 lapisluajit 都只兼容 lua@5.1 版本,所以就需要自行安装 lua@5.1

兼容

第一步:利用 Homebrew 安装 luarocks

1
brew install luarocks

第二步:利用 Homebrew 安装 lua@5.1

1
brew install lua@5.1

第三步:查看 lua@5.1 的安装目录

1
2
3
brew info lua@5.1

/usr/local/Cellar/lua@5.1/5.1.5_8

第四步:利用参数 --lua-dir 以及 --lua-version 使用 5.1 版本,两个参数可以同时设置,也可以只设置一个

1
2
3
luarocks --lua-dir=/usr/local/Cellar/lua@5.1/5.1.5_8 --lua-version=5.1 install lapis

luarocks --lua-version=5.1 install lapis

通过上面设置就能兼容不同版本的 Lua

错误

安装 luaossl 时出现以下错误:

1
2
3
4
5
6
7
8
Installing https://luarocks.org/luaossl-20200709-0.src.rock

Error: Failed installing dependency: https://luarocks.org/luaossl-20200709-0.src.rock - Could not find header file for CRYPTO
No file openssl/crypto.h in /usr/local/include
No file openssl/crypto.h in /usr/include
No file openssl/crypto.h in /include
You may have to install CRYPTO in your system and/or pass CRYPTO_DIR or CRYPTO_INCDIR to the luarocks command.
Example: luarocks install luaossl CRYPTO_DIR=/usr/local

解决如下:
设置 OPENSSL_DIR 以及 CRYPTO_DIR

1
luarocks --lua-version=5.1 OPENSSL_DIR=/usr/local/Cellar/openssl@1.1/1.1.1k/ CRYPTO_DIR=/usr/local/Cellar/openssl@1.1/1.1.1k/ install lapis

最后

配置 LUA_PATHLUA_CPATH 以及 PATH,在终端中输入

1
luarocks --lua-version=5.1 path --bin

取得 LUA_PATHLUA_CPATH 写入到 ~/.zshrc

1
2
export LUA_PATH=''
export LUA_CPATH=''

最后再将.luarocks/bin 导入 PATH

1
export PATH="$HOME/.luarocks/bin:$PATH"

如果不做上面操作就会出现下面错误

1
lua entry thread aborted: runtime error: content_by_lua(nginx.conf.compiled:22):2: module 'lapis' not found:

Python 框架 Sanic 实现文件上传功能

实现

判断允许上传的类型,同时利用 UUID 生成新的文件名存储到对应的文件夹中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@app.route("/upload", methods=['POST'])
async def upload(request):
allow_type = ['.jpg', '.png', '.gif'] # 允许上传的类型
file = request.files.get('file')
type = os.path.splitext(file.name)

if type[1] not in allow_type:
return json({"code": -1, "msg": "只允许上传.jpg.png.gif类型文件"})

name = str(uuid4())+type[1]
path = "/user/data/web/upload" # 这里注意path是绝对路径

async with aiofiles.open(path+"/"+name, 'wb') as f:
await f.write(file.body)
f.close()
return json({"code": 0, "msg": "上传成功", "data": {
"name": name,
"url": "/upload/"+name
}})

上传

可以利用 Postman 上传测试,需要注意的是 header 头中的 Content-Type:multipart/form-data; 必须设置

访问

需要访问上传过后的文件,这就需要用到 Sanic 静态文件代理

1
2
path = "/user/data/web/upload" # 这里注意path是绝对路径
app.static("/upload", path)

最后访问路径为:

1
https://www.域名.com/upload/uuid.png

前记

从用 HexoNext 开始都不知道换了多少次评论系统了,最开始的多说,后来的 Valine,再后来的 Disqus。换来换去最后还是决定用 utterances

安装

utterances

第一步只需要访问:https://github.com/apps/utterances 进行安装

第二步选择存放 issues 的项目,可以跟 Github pages 放在同一个项目

配置

1
2
3
4
5
utterances:
enable: true
repo: jakehu/jakehu.github.io
issue_term: pathname
theme: github-light

最后只需要在主题配置文件中进行如上配置即可

前记

在设计数据库的时候有一个 IP 字段,是用来存多个 IP 地址。于是设计成了 Json 类型,记一下如何在 Tortoise Orm 中使用 JSON_CONTAINS

使用

Model

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from tortoise import Model, fields
from uuid import uuid4

class ag_ip_group(Model):
id = fields.IntField(pk=True)
uuid = fields.UUIDField(default=uuid4)
name = fields.CharField(64)
ip = fields.JSONField()
remark = fields.CharField(64,default='_')
status = fields.IntField(default=1)
created_time = fields.DatetimeField(null=True, auto_now_add=True)
updated_time = fields.DatetimeField(null=True, auto_now=True)

def __str__(self):
return str(self.id)

Function

1
2
3
4
5
from tortoise.functions import Function
from pypika import CustomFunction

class JsonContains(Function):
database_func = CustomFunction("JSON_CONTAINS", ["field", "value"])

Service

1
2
3
4
5
6
filter = {}
annotate = {}
annotate['json_len'] = JsonContains('ip', '"'+row['ip']+'"')
filter['json_len__gt'] = 0

await self.model.all().annotate(**annotate).filter(**filter).limit(limit).offset(offset).values()

扩展

其他的 Json 函数也可自行扩展,这里我们再扩展一种 Json 模糊查询,即:

1
select * from table where json_extract(field, '$') LIKE '%value%'

实现如下:

1
2
3
4
5
6
7
8
9
class JsonExtract(Function):
database_func = CustomFunction("JSON_EXTRACT", ["field", 'value'])

filter = {}
annotate = {}
annotate['json_str'] = JsonExtract('ip','$')
filter['json_str__icontains'] = row['ip']

await self.model.all().annotate(**annotate).filter(**filter).limit(limit).offset(offset).values()

其他

JSONField 类型字段中使用 encoder 更改 json.dumps() 参数 ensure_ascii=False,实现也非常的简单

1
2
import json
field = fields.JSONField(encoder=lambda x: json.dumps(x,ensure_ascii=False))

锦城虽云乐,不如早还家

前言

需求很简单,通过域名证书的私钥分析证书的 DNS 域名以及有效期

实现

分析后决定用 pyOpenSSL 来实现,具体代码如下

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
from OpenSSL import crypto
from dateutil import parser

class Ssl():
def parse(self, certificate):
res = {
"domain": []
}
cert = crypto.load_certificate(crypto.FILETYPE_PEM, certificate)
# 有效期
datetime_struct_start = parser.parse(cert.get_notBefore().decode("UTF-8"))
datetime_struct_end = parser.parse(cert.get_notAfter().decode("UTF-8"))
res['start_time'] = datetime_struct_start.strftime('%Y-%m-%d %H:%M:%S')
res['end_time'] = datetime_struct_end.strftime('%Y-%m-%d %H:%M:%S')
# 扩展
count = cert.get_extension_count()
for i in range(count):
crt = cert.get_extension(i)
name = str(crt.get_short_name(), encoding="utf-8")
if name == "subjectAltName":
_list = str(crt).split(',')
_it = iter(_list) # 创建迭代器对象
for x in _it:
res["domain"].append(x.strip().replace("DNS:", ""))
break
return res

if __name__ == '__main__':
_certificate="""证书私钥"""
ssl = Ssl()
print(ssl.parse(_certificate))
# {'domain': ['*.jakehu.me', 'jakehu.me'], 'start_time': '2021-07-12 18:01:16', 'end_time': '2021-10-10 18:01:15'}

最后

通过 get_subject()get_issuer() 获取证书的其他信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
subject = cert.get_subject()
issuer = cert.get_issuer()

print("证书版本:", cert.get_version() + 1)
print("证书序列号:", hex(cert.get_serial_number()))
print("证书中使用的签名算法:", cert.get_signature_algorithm().decode("UTF-8"))
print("颁发者:", issuer.commonName)
datetime_struct = parser.parse(cert.get_notBefore().decode("UTF-8"))
print("有效期从:", datetime_struct.strftime('%Y-%m-%d %H:%M:%S'))
datetime_struct= parser.parse(cert.get_notAfter().decode("UTF-8"))
print("到:", datetime_struct.strftime('%Y-%m-%d %H:%M:%S'))
print("证书是否已经过期:", cert.has_expired())
print("公钥长度", cert.get_pubkey().bits())
print("公钥:\n", crypto.dump_publickey(crypto.FILETYPE_PEM, cert.get_pubkey()).decode("utf-8"))
print("主体信息:")
print("CN:通用名称 OU:机构单元名称")
print("O:机构名 L:地理位置")
print("S:州/省名 C:国名")
for item in issuer.get_components():
print(item[0].decode("utf-8"), " —— ", item[1].decode("utf-8"))

前记

初次接触 Sanic 框架,在使用的过程中,并没有在文档中发现如何使用应用上下文

使用

通过 issues 咨询后记录如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from sanic import Sanic
from sanic.response import text

app = Sanic('jakehu')

# 应用上下文
app.ctx.db = "msyql://..."

@app.get("/")
async def hello_world(request):
# 使用应用上下文
print(request.app.ctx)
return text("Hello, world.")

app.run(debug=True,auto_reload=True)

参考


题外话:
申明全局的 Json 序列化函数

1
2
from orjson import dumps
app = Sanic('jakehu', dumps=dumps)

前记

鉴于 brew uninstall 只会卸载软件包本身而不会卸载其依赖包,所以我们用 homebrew-rmtree 来解决完全卸载

homebrew-rmtree

安装

brew tap beeftornado/rmtree

使用

1
2
brew rmtree pyenv-virtualenv
brew cleanup

效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
brew rmtree pyenv-virtualenv
==> Examining installed formulae required by pyenv-virtualenv...
| 3 / 5

Can safely be removed
----------------------
pyenv-virtualenv
pyenv
autoconf
pkg-config

Proceed?[y/N]: y
==> Cleaning up packages safe to remove

Uninstalling /usr/local/Cellar/pyenv-virtualenv/1.1.5... (22 files, 65.4KB)
Uninstalling /usr/local/Cellar/pyenv/2.0.0... (756 files, 2.6MB)
Removing: /usr/local/Cellar/autoconf/2.69... (67 files, 3.0MB)
Uninstalling /usr/local/Cellar/autoconf/2.71... (71 files, 3.2MB)
Uninstalling /usr/local/Cellar/pkg-config/0.29.2_3... (11 files, 623.7KB)

介绍

pyenvPython 版本管理工具。pyenv 可以改变全局的 Python 版本,在系统中安装多个版本 Python,设置目录级别的 Python 版本,还能创建和管理 virtual python environments

安装

利用 pyenv-installer 安装

1
curl https://pyenv.run | bash

安装后会自动安装如下插件
pyenv-doctor pyenv-installer pyenv-update pyenv-virtualenv pyenv-which-ext python-build
~/.zshrc 中添加如下配置

1
2
3
export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init --path)"

命令

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
# 查看可以安装的包和版本
$ pyenv install -l

# 安装3.9.5
$ pyenv install 3.9.5

# 创建可执行文件的shims
$ pyenv rehash

# 查看已安装的版本
$ pyenv versions

# 设置全局的Python版本,版本号写入 ~/.pyenv/version
$ pyenv global 3.9.5

# 设置本地的Python版本,版本号写入前目录下的 .python-version
$ pyenv local 3.9.5

# 卸载python版本
$ pyenv uninstall {版本号}

# 列出所有虚拟环境
$ pyenv virtualenvs

# 创建一个3.9.5版本的虚拟环境
$ pyenv virtualenv 3.9.5 V395

# 激活虚拟环境
$ pyenv activate V395

# 关闭虚拟环境
$ pyenv deactivate V395

# 删除虚拟环境
$ pyenv uninstall V395

使用

比如我们现在有一个场景,为 sanic 项目创建一个虚拟环境
第一步:
我们需要安装需要的 Python 版本比如 3.9.5
pyenv install 3.9.5
第二步:
创建一个 3.9.5 版本的虚拟环境
pyenv virtualenv 3.9.5 sanic
第三步:
去到 sanic 项目目录
pyenv local sanic
第四步
取消设定
pyenv local --unset
第五步
导出 requirements.txt
pip freeze > requirements.txt


到这里就可以在 sanic 项目下愉快的玩耍了

前记

当我们需要统计累积量的时候就需要计算同一业务以及相邻两行的差值

业务

接下来我们模拟一个业务,就是统计每天数据表的行数增加量。首先我们需要每天将当天表的数据行数写入到数据库

表结构如下:

1
2
3
4
5
6
7
8
9
CREATE TABLE `table_logs` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`table_name` char(64) NOT NULL DEFAULT '_' COMMENT 'table_name',
`row_num` bigint(20) NOT NULL DEFAULT 0 COMMENT '行数',
`created_at` datetime NOT NULL DEFAULT current_timestamp() COMMENT '创建时间',
`updated_at` datetime NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp(),
PRIMARY KEY (`id`),
KEY `uuid` (`uuid`),
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8

实现

方法:t1.rownum = t2.rownum - 1

代码:

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
SELECT * FROM
(
SELECT
(@rownum := @rownum + 1) AS rownum,
`id`,
`table_name`,
`row_num`
FROM
table_logs,
(SELECT @rownum := 0) t
WHERE
1 = 1
ORDER BY
`table_name` desc, `id` desc
) as t1
LEFT JOIN (
SELECT
(@INDEX := @INDEX + 1) AS rownum,
`id`,
`table_name`,
`row_num`
FROM
table_logs,
(SELECT @rownum := 0) t
WHERE
1 = 1
ORDER BY
`table_name` desc, `id` desc
) as t2 on t1.rownum = t2.rownum - 1 and t1.table_name = t2.table_name

最后只需要将 t1.row_num-t2.row_num 就可以实现我们的需求