阿里的云小店

最近在把分散在各地的项目进行了整合,计划合并到一个服务器上,搬家工作搞了整整一天。

研究了一番,最后把去年项目剩的服务器全给取消了,买了一个阿里云的云小站 https://www.aliyun.com/minisite/goods 的ECS。

配置有两种:

  1. 2核2G,3M固定带宽,40G ESSD Entry云盘 ¥99.00/1年起 官网折扣价: ¥956.64/1年

  2. 2核4G,5M固定带宽,80G ESSD Entry云盘 ¥199.00/1年起 官网折扣价: ¥2507.70/1年

物超所值,几乎不到1折的价格,更重要的是,能不限年限的续费,一个账户只能嫖一个。

直接199的配置走起,2核4G完全够用。跑了两个Python Flask, 两个Python FastAPI, 两个静态site,还能剩余2.2G内存,妥妥够用。

感谢阿里云~

prettier格式化代码

PyCharm里如果有js, html 和css文件,社区版本的IDE是不能直接格式化这些静态文件,需要专业版的来实现。
所以,可以尝试在terminal里直接使用prettier格式化代码。

1
2
3
4
5
6
7
# 用node全局安装
npm install -g prettier

# 格式化项目里的所有的js,jsx,ts,tsx,css,html,json
# 在宽屏幕下,可以设置代码的折行宽度大一些,在我的屏幕上180是个不错的选择。
npx prettier --write "**/*.{js,jsx,ts,tsx,css,html,json}" --print-width 180

@lru_cached装饰器实现全局配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from functools import lru_cache

from fastapi import Depends, FastAPI
from typing_extensions import Annotated

from . import config

app = FastAPI()


@lru_cache
def get_settings():
return config.Settings()


@app.get("/info")
async def info(settings: Annotated[config.Settings, Depends(get_settings)]):
return {
"app_name": settings.app_name,
"admin_email": settings.admin_email,
"items_per_user": settings.items_per_user,
}

使用了 @lru_cache 装饰器,因此只有在第一次调用它时,才会创建 Settings 对象一次。
然后,在下一次请求的依赖项中对 get_settings() 进行任何后续调用时,它不会执行 get_settings() 的内部代码并创建新的 Settings 对象,而是返回在第一次调用时返回的相同对象,一次又一次。

@lru_cache 技术细节

@lru_cache 修改了它所装饰的函数,以返回第一次返回的相同值,而不是再次计算它,每次都执行函数的代码。
因此,下面的函数将对每个参数组合执行一次。然后,每个参数组合返回的值将在使用完全相同的参数组合调用函数时再次使用。

对于我们的依赖项 get_settings(),该函数甚至不接受任何参数,因此它始终返回相同的值。

这样,它的行为几乎就像是一个全局变量。但是由于它使用了依赖项函数,因此我们可以轻松地进行测试时的覆盖。

@lru_cache 是 functools 的一部分,它是 Python 标准库的一部分,可以在 Python 文档 lru_cache中了解有关 @lru_cache 的更多信息。

时差攻击

今天学习FastAPI时,细致了解了一下时差攻击。

时差攻击

什么是时差攻击?

假设攻击者试图猜出用户名与密码。

他们发送用户名为 johndoe,密码为 love123 的请求。

然后,Python 代码执行如下操作:

1
2
if "johndoe" == "stanleyjobson" and "love123" == "swordfish":
...

但就在 Python 比较完 johndoe 的第一个字母 j 与 stanleyjobson 的 s 时,Python 就已经知道这两个字符串不相同了,它会这么想,没必要浪费更多时间执行剩余字母的对比计算了。应用立刻就会返回错误的用户或密码。

但接下来,攻击者继续尝试 stanleyjobsox 和 密码 love123。

应用代码会执行类似下面的操作:

1
2
if "stanleyjobsox" == "stanleyjobson" and "love123" == "swordfish":
...

此时,Python 要对比 stanleyjobsox 与 stanleyjobson 中的 stanleyjobso,才能知道这两个字符串不一样。因此会多花费几微秒来返回错误的用户或密码。

反应时间对攻击者的帮助

通过服务器花费了更多微秒才发送错误的用户或密码响应,攻击者会知道猜对了一些内容,起码开头字母是正确的。

然后,他们就可以放弃 johndoe,再用类似 stanleyjobsox 的内容进行尝试。

专业攻击

当然,攻击者不用手动操作,而是编写每秒能执行成千上万次测试的攻击程序,每次都会找到更多正确字符。

但是,攻击者利用时间差,就能在几分钟或几小时内,以这种方式猜出正确的用户名和密码。

使用 secrets.compare_digest() 修补

在此,代码中使用了 secrets.compare_digest()。

简单的说,它使用相同的时间对比 stanleyjobsox 和 stanleyjobson,还有 johndoe 和 stanleyjobson。对比密码时也一样。

在代码中使用 secrets.compare_digest() ,就可以安全地防御全面攻击了。

1
2
3
4
5
6
7
8
9
10
current_username_bytes = credentials.username.encode("utf8")
correct_username_bytes = b"stanleyjobson"
is_correct_username = secrets.compare_digest(
current_username_bytes, correct_username_bytes
)
current_password_bytes = credentials.password.encode("utf8")
correct_password_bytes = b"swordfish"
is_correct_password = secrets.compare_digest(
current_password_bytes, correct_password_bytes
)

Ruff格式化Python代码可真好用

发现了一个非常好用的代码格式化工具:Ruff

Ruff是一个基于Python的代码格式化工具,它可以帮助开发者格式化Python代码,使其符合PEP 8标准。

Pip安装:

1
pip install ruff

使用:

1
ruff format <file>

把所有的项目都用ruff格式化了一遍,快到难以想象,几百个文件的项目,一两秒钟就格式化好了。

CS自动购买配置

闲来无事打了一会CS,发现自动购买按了F1以后,只买了主武器,甚至子弹都没有补满,研究了一下发现游戏文件里有个/cstrike/autobuy.txt文件。

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
// This list of "buy aliases" is used by the AutoBuy system.
// The system begins with the first alias in the list, and attempts to purchase it.
// If a primary weapon is successfully purchased, all later primary weapon aliases are skipped.
// Similarly, secondary weapon buy alias are skipped once a seconary weapon has been purchased.
// You can customize this file to reflect your weapon and equipment preferences and priorities.
//
// The console command for autobuy is "autobuy"
//
// The available buy aliases and their names as shown on the buy menu are:
// (Many weapons have more than one buy alias)
//
// galil - IDF Defender
// defender - IDF Defender
// ak47 - CV-47
// cv47 - CV-47
// scout - Schmidt Scout
// sg552 - Krieg 552
// krieg552 - Krieg 552
// awp - Magnum Sniper Rifle
// magnum - Magnum Sniper Rifle
// g3sg1 - D3/AU1
// d3au1 - D3/AU1
// famas - Clarion 5.56
// clarion - Clarion 5.56
// m4a1 - Maverick M4A1 Carbine
// aug - Bullpup
// bullpup - Bullpup
// sg550 - Krieg 550 Commando
// krieg550 - Krieg 550 Commando
// glock - 9x19mm Sidearm
// 9x19mm - 9x19mm Sidearm
// usp - KM .45 Tactical
// km45 - KM .45 Tactical
// p228 - 228 Compact
// 228compact - 228 Compact
// deagle - Night Hawk .50C
// nighthawk - Night Hawk .50C
// elites - .40 Dual Elites
// fn57 - ES Five-Seven
// fiveseven - ES Five-Seven
// m3 - Leone 12 Gauge Super
// 12gauge - Leone 12 Gauge Super
// xm1014 - Leone YG1265 Auto Shotgun
// autoshotgun - Leone YG1265 Auto Shotgun
// mac10 - Ingram MAC-10
// tmp - Schmidt Machine Pistol
// mp - Schmidt Machine Pistol
// mp5 - KM Sub-Machine Gun
// smg - KM Sub-Machine Gun
// ump45 - KM UMP45
// p90 - ES C90
// c90 - ES C90
// m249 - M249
// primammo - Primary Ammo
// secammo - Secondary Ammo
// vest - Kevlar
// vesthelm - Kevlar+Helmet
// flash - Flashbang
// hegren - HE Grenade
// sgren - Smoke Grenade
// nvgs - Nightvision
// defuser - Defusal Kit
// shield - Tactical Shield

m4a1
ak47
famas
galil
p90
mp5

primammo
secammo

defuser
flash
flash
hegren
sgren

vesthelm
vest

这里面定义了自动购买的时候要买的武器和装备,直接改一下,增加一下需要的配置就可以了,闪光灯这里买两个。
保存文件,重新启动游戏就可以了。

页面烟花特效

在一个历史项目上有一个新需求,当客流到达一定整万数的时候,来一个页面特殊效果,于是决定做一个烟花的效果出来。
研究了一下,找到一个项目 https://www.kirilv.com/canvas-confetti/ ,最后的实现效果还不错。

点这里查看演示效果

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

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>烟花效果</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
</head>
<style>
#message {
font-size: 100px;
font-weight: bold;
color: #fff;
position: fixed; /* 全屏覆盖 */
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
z-index: 50;

/* 半透明背景 */
background-color: rgba(0, 0, 0, 0.7); /* 黑色半透明背景 */
/* 文字变色效果 */
animation: color-change 2s infinite; /* 3秒变色循环 */
}

/* 文字颜色变换动画 */
@keyframes color-change {
0% {
color: #ff3333; /* 鲜红 */
}
25% {
color: #DC143C; /* 浅红 */
}
50% {
color: #FF1493; /* 桃红 */
}
75% {
color: #8B0000; /* 深红 */
}
100% {
color: #ff3333; /* 回到鲜红 */
}
}
</style>
<body>

<div id="message" style="display: none;"></div>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/canvas-confetti@1.9.3/dist/confetti.browser.min.js"></script>

<script>

function showCelebration(number) {
const duration = 43000;
const endTime = Date.now() + duration;
const colors = ['#bb0000', '#ff00ff', '#00bbff', '#00ff00', '#ffff00', '#ff8800'];

// 显示文字
const messageDiv = document.getElementById('message');
messageDiv.innerHTML = `热烈庆贺客流总数突破<br><br>${number}`;
$(messageDiv).fadeIn(500);

function randomInRange(min, max) {
return Math.random() * (max - min) + min;
}

function launchFireworks() {
(function frame() {
confetti({
particleCount: 6,
angle: 60,
spread: 55,
origin: { x: 0 },
colors: colors
});
confetti({
particleCount: 6,
angle: 120,
spread: 55,
origin: { x: 1 },
colors: colors
});

if (Date.now() < endTime) {
requestAnimationFrame(frame);
}
})();
}

function launchCentralExplosions() {
const interval = setInterval(() => {
for (let i = 0; i < 3; i++) {
setTimeout(() => {
confetti({
spread: 360,
ticks: 120,
gravity: 0,
decay: 0.94,
startVelocity: 20,
colors: ['#ff4500', '#ff6347', '#ffd700'], // 橙红、番茄红、金色
particleCount: 60,
scalar: 1.2,
origin: {
x: randomInRange(0.4, 0.6), // 随机横向位置
y: randomInRange(0.4, 0.6), // 随机纵向位置
},
shapes: ['star', 'circle']
});
}, i * 200);
}
}, 2000);

setTimeout(() => {
clearInterval(interval);
}, duration);
}

// 启动特效
launchFireworks();
launchCentralExplosions();

// 在25秒后隐藏文字和停止特效
setTimeout(() => {
$(messageDiv).fadeOut(500);
}, duration);
}

showCelebration(60000000);

</script>
</body>
</html>


做完这个以后,后面发现还有一个朋友 Caleb Miller 做的几乎一比一仿真的烟花效果,记录一下。
https://codepen.io/MillerTime/pen/XgpNwb

常用的git alias

重装了一次电脑,记录一下git alias的配置。

1
2
3
4
5
6
7
8
9
$ cat ~/.gitconfig 
[user]
name = Jinloh WU
email = i@tiaobug.com
[alias]
lg = log --color --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit
ac = !git add . && git commit -m
acp = "!f() { git add . && git commit -m \"$1\" && git push; }; f"
acpg = "!f() { git add . && git commit -m \"$1\" && git push && git push github; }; f"

顺手记录一下:
Thinkbook 14+ 使用U盘启动的时候,F12以后引导选择后,无法正常启动。需要F1选择把安全启动模式给关掉就可以了。

CaptchaCracker训练模型识别验证码

新的项目需要去一个网站上获取一些数据,做一个爬虫,网站上有验证码,以前的时候简单的验证码可以直接使用tesseract直接识别,但是这次的验证码长这个样子:
Captcha1
Captcha2
Captcha3
尝试了几次OCR的解决方案,发现不能成功,这里面有斜线干扰,数字是彩色的,且歪歪扭扭的。

所以计划使用tensorflow训练个模型,在尝试的过程中发现了一个参数都已经调好的python lib, CaptchaCracker

训练的时候,需要调整的参数不是很多,只需要给定验证码的图片宽度高度就可以了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import glob
import CaptchaCracker as cc

# Training image data path
train_img_path_list = glob.glob("./image/*.png")

# Training image data size
img_width = 120
img_height = 50

# Creating an instance that creates a model
CM = cc.CreateModel(train_img_path_list, img_width, img_height)

# Performing model training
model = CM.train_model(epochs=100)

# Saving the weights learned by the model to a file
model.save_weights("./weights.h5")

利用脚本从网站上获取了1000多的验证码图片,批量命令后,利用excel记录标记的结果,这个过程比较辛苦,需要一点一点的标记所有的图片。
标记了1500张图,基本上样本就够了,因为这个验证码的范围是0-8,且只是4位数字。
开始运行训练代码后,我的电脑ntel(R) Core(TM) Ultra 5 (16核), 32G内存,跑了大概十来分钟,生成了一个.h5的模型文件。

下面的代码是使用模型识别新的验证码图片,同样也是需要设置一下验证码图片的高度宽度,数字长度,字符的识别范围。
应用模型后,就可以识别出来结果了。

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
import os

import CaptchaCracker as cc

# Target image data size
img_width = 120
img_height = 50
# Target image label length
max_length = 4
# Target image label component
characters = {'0', '1', '2', '3', '4', '5', '6', '7', '8'}

# Model weight file path
weights_path = "./weights.h5"
# Creating a model application instance
AM = cc.ApplyModel(weights_path, img_width, img_height, max_length, characters)


# Target image path
target_img_path = "../img_2.png"

# Predicted value
pred = AM.predict(target_img_path)
print(pred)

最后的识别效果还是非常棒的,基本能到95%以上的成功率。

在Ubuntu里启动docker mysql,导入来自阿里云的RDS数据库

项目需要低功耗运转,暂时没有太大的访问量,不太需要购买阿里云的RDS mysql,所以计划把数据移动到一个ECS里。

登录阿里云,从DMS页面的右上角的一排按钮中,找到导出,勾选所有表,然后导出一个sql文件。在本地打开,发现文字没有乱码,正常可用。

尝试在Ubuntu系统里安装mysql8,但是修改lower_case_table_names=1的时候,mysql无法启动,所以没有办法只能尝试用docker启动以后,后面发现docker启动真的很方便,即使是操作失误,也可以里面恢复,重新再试。

安装docker-ce

添加 Docker 的官方 GPG 密钥:

1
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

提示OK后, 增加官方安装源

1
2
3
4
sudo add-apt-repository \
"deb [arch=amd64] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) \
stable"

更新 apt 包索引,安装最新版本的 Docker Engine-Community

1
2
sudo apt update
sudo apt install docker-ce

拉取镜像

直接从一个镜像加速站点,拉取mysql的最新镜像。

1
2
3
4
5
docker pull dockerproxy.cn/mysql:latest

# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
dockerproxy.cn/mysql latest be960704dfac 9 days ago 602MB

启动镜像

先创建几个目录,用于挂载,然后启动docker的mysql instance。

1
2
3
mkdir -p /mydata/mysql/log /mydata/mysql/data /mydata/mysql/conf

docker run --name mysql -v /mydata/mysql/log:/var/log/mysql -v /mydata/mysql/data:/var/lib/mysql -v /mydata/mysql/conf:/etc/mysql/conf.d -p 3306:3306 -e MYSQL_ROOT_PASSWORD=123456 -d mysql --lower_case_table_names=1 --init-connect="SET collation_connection=utf8mb4_0900_ai_ci" --init-connect="SET NAMES utf8mb4"

创建用户

1
2
3
4
5
6
7
8
9
10
docker exec -it mysql /bin/bash

mysql -u root -p

# 密码 123456, 在docker的启动命令里。

CREATE USER 'a_new_user'@'%' IDENTIFIED BY 'password_need_to_change';
GRANT ALL PRIVILEGES ON *.* TO 'a_new_user'@'%';
flush privileges;

到这里,就可以远程连接这个数据库了。

不推荐在这里直接创建数据库,会有编码,困扰了我好几次。

导入数据库

用Navicat输入创建的用户名和密码(a_new_user / password_need_to_change),连接数据库。
创建一个新的数据库,编码保持默认,然后执行从阿里云导出的sql,编码也保持默认,这样数据就导入进去了。


Powered by Hexo and Hexo-theme-hiker

Copyright © 2012 - 2025 tiaobug.com All Rights Reserved.

鲁ICP备2024124237号-1