rainycode 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,130 @@
1
+ import base64
2
+ import random
3
+ import string
4
+ from io import BytesIO
5
+ from PIL import Image, ImageDraw, ImageFont
6
+
7
+
8
+ class CaptchaUtil:
9
+ """
10
+ 验证码工具类
11
+ """
12
+ @classmethod
13
+ def generate_captcha(cls) -> tuple[str, str]:
14
+ """
15
+ 生成带有噪声和干扰的验证码图片:4位随机字符
16
+ :return: [base64编码的图片字符串, 验证码值]
17
+ """
18
+ # 生成4位随机验证码
19
+ chars = string.digits + string.ascii_letters
20
+ captcha_value = ''.join(random.sample(chars, 4))
21
+
22
+ # 创建一张随机颜色背景的图片
23
+ width, height = 160, 60
24
+ background_color = tuple(random.randint(230, 255) for _ in range(3))
25
+ image = Image.new('RGB', (width, height), color=background_color)
26
+ draw = ImageDraw.Draw(image)
27
+
28
+ # 使用指定字体
29
+ font = ImageFont.truetype(font='static/assets/font/Arial.ttf', size=40)
30
+
31
+ # 计算文本总宽度和高度
32
+ total_width = sum(draw.textbbox((0, 0), char, font=font)[2] for char in captcha_value)
33
+ text_height = draw.textbbox((0, 0), captcha_value[0], font=font)[3]
34
+
35
+ # 计算起始位置,使文字居中
36
+ x_start = (width - total_width) / 2
37
+ y_start = (height - text_height) / 2 - draw.textbbox((0, 0), captcha_value[0], font=font)[1]
38
+
39
+ # 绘制字符
40
+ x = x_start
41
+ for char in captcha_value:
42
+ # 使用深色文字,增加对比度
43
+ text_color = tuple(random.randint(0, 80) for _ in range(3))
44
+
45
+ # 随机偏移,增加干扰
46
+ x_offset = x + random.uniform(-2, 2)
47
+ y_offset = y_start + random.uniform(-2, 2)
48
+
49
+ # 绘制字符
50
+ draw.text((x_offset, y_offset), char, font=font, fill=text_color)
51
+
52
+ # 更新x坐标,增加字符间距的随机性
53
+ x += draw.textbbox((0, 0), char, font=font)[2] + random.uniform(1, 5)
54
+
55
+ # 添加干扰线
56
+ for _ in range(4):
57
+ line_color = tuple(random.randint(150, 200) for _ in range(3))
58
+ points = [(i, int(random.uniform(0, height))) for i in range(0, width, 20)]
59
+ draw.line(points, fill=line_color, width=1)
60
+
61
+ # 添加随机噪点
62
+ for _ in range(width * height // 60):
63
+ point_color = tuple(random.randint(0, 255) for _ in range(3))
64
+ draw.point(
65
+ (random.randint(0, width), random.randint(0, height)),
66
+ fill=point_color
67
+ )
68
+
69
+ # 将图像数据保存到内存中并转换为base64
70
+ buffer = BytesIO()
71
+ image.save(buffer, format='PNG', optimize=True)
72
+ base64_string = base64.b64encode(buffer.getvalue()).decode()
73
+
74
+ return base64_string, captcha_value
75
+
76
+ @classmethod
77
+ def captcha_arithmetic(cls) -> tuple[str, int]:
78
+ """
79
+ 创建验证码图片: 加减乘运算
80
+ :return: [base64编码的图片字符串, 计算结果]
81
+ """
82
+ # 创建空白图像,使用随机浅色背景
83
+ background_color = tuple(random.randint(230, 255) for _ in range(3))
84
+ image = Image.new('RGB', (160, 60), color=background_color)
85
+ draw = ImageDraw.Draw(image)
86
+
87
+ # 设置字体
88
+ font = ImageFont.truetype(font='static/assets/font/Arial.ttf', size=40)
89
+
90
+ # 生成运算数字和运算符
91
+ operators = ['+', '-', '*']
92
+ operator = random.choice(operators)
93
+
94
+ # 对于减法,确保num1大于num2
95
+ if operator == '-':
96
+ num1 = random.randint(6, 10)
97
+ num2 = random.randint(1, 5)
98
+ else:
99
+ num1 = random.randint(1, 9)
100
+ num2 = random.randint(1, 9)
101
+
102
+ # 计算结果
103
+ result_map = {
104
+ '+': lambda x, y: x + y,
105
+ '-': lambda x, y: x - y,
106
+ '*': lambda x, y: x * y
107
+ }
108
+ captcha_value = result_map[operator](num1, num2)
109
+
110
+ # 绘制文本,使用深色增加对比度
111
+ text = f'{num1} {operator} {num2} = ?'
112
+ text_bbox = draw.textbbox((0, 0), text, font=font)
113
+ text_width = text_bbox[2] - text_bbox[0]
114
+ x = (160 - text_width) // 2
115
+ draw.text((x, 15), text, fill=(0, 0, 139), font=font)
116
+
117
+ # 添加干扰线
118
+ for _ in range(3):
119
+ line_color = tuple(random.randint(150, 200) for _ in range(3))
120
+ draw.line([
121
+ (random.randint(0, 160), random.randint(0, 60)),
122
+ (random.randint(0, 160), random.randint(0, 60))
123
+ ], fill=line_color, width=1)
124
+
125
+ # 将图像数据保存到内存中并转换为base64
126
+ buffer = BytesIO()
127
+ image.save(buffer, format='PNG', optimize=True)
128
+ base64_string = base64.b64encode(buffer.getvalue()).decode()
129
+
130
+ return base64_string, captcha_value
@@ -0,0 +1,83 @@
1
+ import re
2
+
3
+ from common import aiorequests
4
+
5
+
6
+ class IpUtil:
7
+ """
8
+ 获取IP归属地工具类
9
+ """
10
+
11
+ @classmethod
12
+ def is_valid_ip(cls, ip: str) -> bool:
13
+ """
14
+ 校验IP格式是否合法
15
+
16
+ :param ip: IP地址
17
+ :return: 是否合法
18
+ """
19
+ ip_pattern = r'^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$'
20
+ return bool(re.match(ip_pattern, ip))
21
+
22
+ @classmethod
23
+ def is_private_ip(cls, ip: str) -> bool:
24
+ """
25
+ 校验IP是否为内网IP
26
+
27
+ :param ip: IP地址
28
+ :return: 是否为内网IP
29
+ """
30
+ ip_parts = list(map(int, ip.split('.')))
31
+
32
+ # 检查是否为 10.0.0.0/8
33
+ if ip_parts[0] == 10:
34
+ return True
35
+
36
+ # 检查是否为 172.16.0.0/12
37
+ if ip_parts[0] == 172 and 16 <= ip_parts[1] <= 31:
38
+ return True
39
+
40
+ # 检查是否为 192.168.0.0/16
41
+ if ip_parts[0] == 192 and ip_parts[1] == 168:
42
+ return True
43
+
44
+ # 检查是否为 127.0.0.0/8
45
+ if ip_parts[0] == 127:
46
+ return True
47
+
48
+ return False
49
+
50
+
51
+ @classmethod
52
+ async def get_ip_location(cls, ip: str) -> str | None:
53
+ """
54
+ 获取IP归属地信息
55
+
56
+ :param ip: IP地址
57
+ :return: IP归属地信息
58
+ """
59
+ # 校验IP格式
60
+ if not cls.is_valid_ip(ip):
61
+ # IP格式不合法
62
+ return "未知"
63
+
64
+ # 内网IP直接返回
65
+ if cls.is_private_ip(ip):
66
+ return '内网IP'
67
+
68
+ try:
69
+ # 使用ip-api.com API获取IP归属地信息
70
+ response = await aiorequests.get(f'http://ip-api.com/json/{ip}')
71
+ if response and response.json().get('ret') == 200:
72
+ result = response.json().get('data', {})
73
+ return f"{result.get('country','')}-{result.get('prov','')}-{result.get('city','')}-{result.get('area','')}-{result.get('isp','')}"
74
+
75
+ response = await aiorequests.get(f'https://qifu-api.baidubce.com/ip/geo/v1/district?ip={ip}')
76
+ if response and response.json().get('code') == "Success":
77
+ data = response.json().get('data', {})
78
+ # 修正原代码中的格式错误
79
+ return f"{data.get('country','')}-{data.get('prov','')}-{data.get('city','')}-{data.get('district','')}-{data.get('isp','')}"
80
+
81
+ except Exception as e:
82
+ # 获取IP归属地失败
83
+ return "未知"
@@ -0,0 +1,47 @@
1
+ import jwt
2
+ from datetime import datetime, timedelta
3
+ from typing import Any
4
+ from core.base_config import base_config
5
+
6
+ class JwtUtil:
7
+ """
8
+ JWT 工具类
9
+ """
10
+ SECRET_KEY = base_config.jwt_secret_key
11
+ ALGORITHM = "HS256"
12
+
13
+ @classmethod
14
+ def create_access_token(cls, data: dict, expires_delta: timedelta) -> str:
15
+ """
16
+ 生成访问令牌
17
+
18
+ Args:
19
+ data: 数据载荷
20
+ expires_delta: 过期时间差 (必须指定)
21
+
22
+ Returns:
23
+ str: JWT Token
24
+ """
25
+ to_encode = data.copy()
26
+ expire = datetime.now() + expires_delta
27
+
28
+ to_encode.update({"exp": expire})
29
+ encoded_jwt = jwt.encode(to_encode, cls.SECRET_KEY, algorithm=cls.ALGORITHM)
30
+ return encoded_jwt
31
+
32
+ @classmethod
33
+ def decode_token(cls, token: str) -> dict[str, Any] | None:
34
+ """
35
+ 解析令牌
36
+
37
+ Args:
38
+ token: JWT Token
39
+
40
+ Returns:
41
+ dict | None: 解析后的数据,解析失败返回 None
42
+ """
43
+ try:
44
+ payload = jwt.decode(token, cls.SECRET_KEY, algorithms=[cls.ALGORITHM])
45
+ return payload
46
+ except jwt.PyJWTError:
47
+ return None
@@ -0,0 +1,205 @@
1
+ import os
2
+ import time
3
+ import socket
4
+ import uuid
5
+ import hashlib
6
+
7
+
8
+ class SnowflakeIDGenerator:
9
+ """
10
+ 雪花算法ID生成器
11
+
12
+ 雪花ID结构:
13
+ 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000
14
+ 1位符号位 - 41位时间戳 - 5位数据中心ID - 5位机器ID - 12位序列号
15
+ """
16
+
17
+ def __init__(self, datacenter_id: int = 0, machine_id: int = 0):
18
+ """
19
+ 初始化雪花ID生成器
20
+
21
+ Args:
22
+ datacenter_id: 数据中心ID,范围0-31
23
+ machine_id: 机器ID,范围0-31
24
+ """
25
+ # 起始时间戳:2025-07-01 00:00:00
26
+ self.twepoch = 1751328000000
27
+
28
+ # 各部分占位长度
29
+ self.datacenter_id_bits = 5 # 数据中心ID长度
30
+ self.machine_id_bits = 5 # 机器ID长度
31
+ self.sequence_bits = 12 # 序列号长度
32
+
33
+ # 各部分最大值
34
+ self.max_datacenter_id = -1 ^ (-1 << self.datacenter_id_bits) # 31
35
+ self.max_machine_id = -1 ^ (-1 << self.machine_id_bits) # 31
36
+ self.max_sequence = -1 ^ (-1 << self.sequence_bits) # 4095
37
+
38
+ # 各部分偏移量
39
+ self.machine_id_shift = self.sequence_bits
40
+ self.datacenter_id_shift = self.sequence_bits + self.machine_id_bits
41
+ self.timestamp_shift = self.sequence_bits + self.machine_id_bits + self.datacenter_id_bits
42
+
43
+ # 参数检查
44
+ if datacenter_id > self.max_datacenter_id or datacenter_id < 0:
45
+ raise ValueError(f"数据中心ID不能大于{self.max_datacenter_id}或小于0")
46
+ if machine_id > self.max_machine_id or machine_id < 0:
47
+ raise ValueError(f"机器ID不能大于{self.max_machine_id}或小于0")
48
+
49
+ self.datacenter_id = datacenter_id
50
+ self.machine_id = machine_id
51
+ self.sequence = 0
52
+ self.last_timestamp = -1
53
+
54
+ def _gen_timestamp(self) -> int:
55
+ """
56
+ 生成当前时间戳
57
+
58
+ Returns:
59
+ 当前时间戳(毫秒)
60
+ """
61
+ return int(time.time() * 1000)
62
+
63
+ def _til_next_millis(self, last_timestamp: int) -> int:
64
+ """
65
+ 等待到下一个毫秒
66
+
67
+ Args:
68
+ last_timestamp: 上一个时间戳
69
+
70
+ Returns:
71
+ 下一个时间戳
72
+ """
73
+ timestamp = self._gen_timestamp()
74
+ while timestamp <= last_timestamp:
75
+ timestamp = self._gen_timestamp()
76
+ return timestamp
77
+
78
+ def get_id(self) -> int:
79
+ """
80
+ 生成下一个ID
81
+
82
+ Returns:
83
+ 雪花算法生成的ID
84
+
85
+ Raises:
86
+ RuntimeError: 当时钟回拨时抛出异常
87
+ """
88
+ timestamp = self._gen_timestamp()
89
+
90
+ # 时钟回拨检查
91
+ if timestamp < self.last_timestamp:
92
+ raise RuntimeError(f"时钟回拨,拒绝生成ID,上一时间戳: {self.last_timestamp}, 当前时间戳: {timestamp}")
93
+
94
+ # 同一毫秒内
95
+ if timestamp == self.last_timestamp:
96
+ self.sequence = (self.sequence + 1) & self.max_sequence
97
+ # 同一毫秒内序列号用完
98
+ if self.sequence == 0:
99
+ timestamp = self._til_next_millis(self.last_timestamp)
100
+ else:
101
+ # 不同毫秒,序列号重置
102
+ self.sequence = 0
103
+
104
+ self.last_timestamp = timestamp
105
+
106
+ # 生成ID
107
+ return ((timestamp - self.twepoch) << self.timestamp_shift) | \
108
+ (self.datacenter_id << self.datacenter_id_shift) | \
109
+ (self.machine_id << self.machine_id_shift) | \
110
+ self.sequence
111
+
112
+
113
+ def get_machine_info() -> tuple[int, int]:
114
+ """
115
+ 获取机器信息,用于生成数据中心ID和机器ID
116
+
117
+ 使用多种机器特征信息生成稳定且唯一的ID:
118
+ - 主机名
119
+ - 完整IP地址
120
+ - 完整MAC地址
121
+ - 进程ID
122
+
123
+ Returns:
124
+ Tuple[int, int]: (datacenter_id, machine_id)
125
+ """
126
+ try:
127
+ # 收集多种机器特征信息
128
+ hostname = socket.gethostname()
129
+ ip = socket.gethostbyname(hostname)
130
+ mac = uuid.UUID(int=uuid.getnode()).hex
131
+ pid = os.getpid()
132
+
133
+ # 为数据中心ID创建特征字符串
134
+ # 使用主机名和IP地址
135
+ datacenter_feature = f"{hostname}:{ip}"
136
+ datacenter_hash = int(hashlib.md5(datacenter_feature.encode()).hexdigest(), 16)
137
+ datacenter_id = datacenter_hash % 32 # 确保在0-31范围内
138
+
139
+ # 为机器ID创建特征字符串
140
+ # 使用MAC地址和进程ID
141
+ machine_feature = f"{mac}:{pid}"
142
+ machine_hash = int(hashlib.md5(machine_feature.encode()).hexdigest(), 16)
143
+ machine_id = machine_hash % 32 # 确保在0-31范围内
144
+
145
+ return datacenter_id, machine_id
146
+ except Exception as e:
147
+ raise RuntimeError(f"获取机器信息失败: {e}")
148
+
149
+
150
+ # 单例模式,默认实例
151
+ _default_snowflake_generator: SnowflakeIDGenerator | None = None
152
+
153
+
154
+ def get_snowflake_id(datacenter_id: int | None = None, machine_id: int | None = None) -> int:
155
+ """
156
+ 获取雪花算法生成的ID
157
+
158
+ Args:
159
+ datacenter_id: 数据中心ID,范围0-31,默认自动获取
160
+ machine_id: 机器ID,范围0-31,默认自动获取
161
+
162
+ Returns:
163
+ 雪花算法生成的ID
164
+ """
165
+ global _default_snowflake_generator
166
+
167
+ if _default_snowflake_generator is None:
168
+ # 如果未指定datacenter_id或machine_id,则自动获取
169
+ if datacenter_id is None or machine_id is None:
170
+ auto_datacenter_id, auto_machine_id = get_machine_info()
171
+ datacenter_id = datacenter_id if datacenter_id is not None else auto_datacenter_id
172
+ machine_id = machine_id if machine_id is not None else auto_machine_id
173
+
174
+ _default_snowflake_generator = SnowflakeIDGenerator(datacenter_id, machine_id)
175
+
176
+ return _default_snowflake_generator.get_id()
177
+
178
+
179
+ def test_snowflake_id():
180
+ """
181
+ 测试雪花算法ID生成
182
+
183
+ Returns:
184
+ dict: 测试结果,包含生成的ID和相关信息
185
+ """
186
+ # 获取机器信息
187
+ datacenter_id, machine_id = get_machine_info()
188
+
189
+ # 生成ID
190
+ snowflake_id = get_snowflake_id()
191
+
192
+ # 返回测试结果
193
+ return {
194
+ "snowflake_id": snowflake_id,
195
+ "datacenter_id": datacenter_id,
196
+ "machine_id": machine_id,
197
+ "binary": bin(snowflake_id),
198
+ "hex": hex(snowflake_id),
199
+ "timestamp": time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time()))
200
+ }
201
+
202
+
203
+ if __name__ == '__main__':
204
+ result = test_snowflake_id()
205
+ print(result)
core/base_config.py ADDED
@@ -0,0 +1,28 @@
1
+ from pydantic_settings import BaseSettings, SettingsConfigDict
2
+
3
+
4
+ class BaseConfig(BaseSettings):
5
+ app_debug: bool = False
6
+ app_title: str = ''
7
+ app_name: str = ''
8
+
9
+ redis_url: str = ''
10
+ mysql_url: str = ''
11
+
12
+ # 跨域
13
+ cors_origins: list[str] = ['*']
14
+ cors_allow_credentials: bool = True
15
+ cors_allow_methods: list[str] = ['GET', 'POST', 'PUT', 'DELETE']
16
+ cors_allow_headers: list[str] = ['*']
17
+
18
+ # jwt密钥
19
+ jwt_secret_key: str = "bgb0tnl9d58+6n-6h-ea&u^1#s0ccp!794=krylacjq75vzps$"
20
+
21
+ model_config = SettingsConfigDict(
22
+ env_file=('envs/dev.env', 'envs/pro.env'),
23
+ env_file_encoding="utf-8",
24
+ case_sensitive=False,
25
+ extra='ignore'
26
+ )
27
+
28
+ base_config = BaseConfig()