fly-common 0.1.0__tar.gz
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.
- fly_common-0.1.0/LICENSE +21 -0
- fly_common-0.1.0/PKG-INFO +197 -0
- fly_common-0.1.0/README.md +158 -0
- fly_common-0.1.0/pyproject.toml +56 -0
- fly_common-0.1.0/setup.cfg +4 -0
- fly_common-0.1.0/src/fly_common/__init__.py +0 -0
- fly_common-0.1.0/src/fly_common/safety/__init__.py +0 -0
- fly_common-0.1.0/src/fly_common/safety/jwt_token.py +290 -0
- fly_common-0.1.0/src/fly_common/safety/md5.py +40 -0
- fly_common-0.1.0/src/fly_common/tools/__init__.py +0 -0
- fly_common-0.1.0/src/fly_common/tools/single.py +14 -0
- fly_common-0.1.0/src/fly_common.egg-info/PKG-INFO +197 -0
- fly_common-0.1.0/src/fly_common.egg-info/SOURCES.txt +14 -0
- fly_common-0.1.0/src/fly_common.egg-info/dependency_links.txt +1 -0
- fly_common-0.1.0/src/fly_common.egg-info/requires.txt +14 -0
- fly_common-0.1.0/src/fly_common.egg-info/top_level.txt +1 -0
fly_common-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 fly
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fly-common
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: 通用模块
|
|
5
|
+
Author-email: fly <fzkf117@163.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: utils,tools
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.6
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Requires-Python: >=3.9
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: annotated-types==0.7.0
|
|
25
|
+
Requires-Dist: cffi==2.0.0
|
|
26
|
+
Requires-Dist: cryptography==46.0.5
|
|
27
|
+
Requires-Dist: ecdsa==0.19.1
|
|
28
|
+
Requires-Dist: passlib==1.7.4
|
|
29
|
+
Requires-Dist: pyasn1==0.6.3
|
|
30
|
+
Requires-Dist: pycparser==2.23
|
|
31
|
+
Requires-Dist: pydantic==2.12.5
|
|
32
|
+
Requires-Dist: pydantic-core==2.41.5
|
|
33
|
+
Requires-Dist: python-jose==3.5.0
|
|
34
|
+
Requires-Dist: rsa==4.9.1
|
|
35
|
+
Requires-Dist: six==1.17.0
|
|
36
|
+
Requires-Dist: typing-extensions==4.15.0
|
|
37
|
+
Requires-Dist: typing-inspection==0.4.2
|
|
38
|
+
Dynamic: license-file
|
|
39
|
+
|
|
40
|
+
# fly-common
|
|
41
|
+
|
|
42
|
+
> 一个轻量级的 Python 公共工具库,专注于安全认证和常用工具函数
|
|
43
|
+
|
|
44
|
+
## 📖 介绍
|
|
45
|
+
|
|
46
|
+
fly-common 是一个功能精简但实用的 Python 公共库,主要提供以下核心功能:
|
|
47
|
+
|
|
48
|
+
- **JWT Token 管理**:支持对称加密(HS256)和非对称加密(RS256)的 JWT Token 生成、验证和刷新
|
|
49
|
+
- **密码加密**:提供 MD5、PBKDF2 等多种加密方式
|
|
50
|
+
- **工具函数**:提供单例模式、随机字符串生成等实用工具
|
|
51
|
+
|
|
52
|
+
## ✨ 功能特性
|
|
53
|
+
|
|
54
|
+
### 安全模块 (safety)
|
|
55
|
+
|
|
56
|
+
#### JWT Token
|
|
57
|
+
- 支持对称加密(HS256)和非对称加密(RS256)
|
|
58
|
+
- 提供 Access Token 和 Refresh Token 的生成
|
|
59
|
+
- Token 验证与刷新机制
|
|
60
|
+
- 支持自定义过期时间、签发者(issuer)和受众(audience)
|
|
61
|
+
- 单例模式实现,确保全局唯一实例
|
|
62
|
+
|
|
63
|
+
#### 密码加密
|
|
64
|
+
- MD5 加密
|
|
65
|
+
- PBKDF2 加密(推荐用于密码存储)
|
|
66
|
+
- 密码验证功能
|
|
67
|
+
- 随机字符串生成
|
|
68
|
+
- 数字验证码生成
|
|
69
|
+
|
|
70
|
+
### 工具模块 (tools)
|
|
71
|
+
|
|
72
|
+
- 线程安全的单例模式装饰器
|
|
73
|
+
|
|
74
|
+
## 📦 安装
|
|
75
|
+
|
|
76
|
+
### 环境要求
|
|
77
|
+
|
|
78
|
+
- Python >= 3.9
|
|
79
|
+
|
|
80
|
+
### 安装依赖
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
pip install -r requirements.txt
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## 🚀 使用说明
|
|
87
|
+
|
|
88
|
+
### JWT Token 使用示例
|
|
89
|
+
|
|
90
|
+
#### 对称加密(HS256)
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
from fly_common.safety.jwt_token import JWTSymmetry
|
|
94
|
+
|
|
95
|
+
# 初始化(单例模式)
|
|
96
|
+
jwt_sym = JWTSymmetry(
|
|
97
|
+
secret_key="your-secret-key",
|
|
98
|
+
access_token_expire_seconds=86400, # 24小时
|
|
99
|
+
refresh_token_expire_seconds=604800, # 7天
|
|
100
|
+
issuer="your-app",
|
|
101
|
+
audience="your-users"
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
# 生成 Token
|
|
105
|
+
result = jwt_sym.create_at_rf_token(payload={"sub": "user123"})
|
|
106
|
+
if result.ok:
|
|
107
|
+
access_token = result.data["access_token"]
|
|
108
|
+
refresh_token = result.data["refresh_token"]
|
|
109
|
+
|
|
110
|
+
# 验证 Token
|
|
111
|
+
verify_result = jwt_sym.verify_token(access_token)
|
|
112
|
+
if verify_result.ok:
|
|
113
|
+
print("Token 有效:", verify_result.data)
|
|
114
|
+
|
|
115
|
+
# 刷新 Token
|
|
116
|
+
refresh_result = jwt_sym.refresh_token(access_token, refresh_token)
|
|
117
|
+
if refresh_result.ok:
|
|
118
|
+
new_tokens = refresh_result.data
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
#### 非对称加密(RS256)
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
from fly_common.safety.jwt_token import JWTAsymmetry
|
|
125
|
+
|
|
126
|
+
# 初始化(单例模式)
|
|
127
|
+
jwt_asym = JWTAsymmetry(
|
|
128
|
+
private_key="your-private-key",
|
|
129
|
+
public_key="your-public-key",
|
|
130
|
+
algorithm="RS256"
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
# 使用方式与对称加密相同
|
|
134
|
+
result = jwt_asym.create_at_rf_token(payload={"sub": "user123"})
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### 密码加密使用示例
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
from fly_common.safety.md5 import md5, en_password, check_password, code_number
|
|
141
|
+
|
|
142
|
+
# MD5 加密
|
|
143
|
+
hashed = md5("your-string")
|
|
144
|
+
|
|
145
|
+
# 密码加密(PBKDF2)
|
|
146
|
+
password_hash = en_password("user-password")
|
|
147
|
+
|
|
148
|
+
# 验证密码
|
|
149
|
+
is_valid = check_password("user-password", password_hash)
|
|
150
|
+
|
|
151
|
+
# 生成验证码
|
|
152
|
+
code = code_number(6) # 生成6位数字验证码
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### 单例模式使用示例
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
from fly_common.tools.single import Singleton
|
|
159
|
+
|
|
160
|
+
@Singleton
|
|
161
|
+
class MyService:
|
|
162
|
+
def __init__(self):
|
|
163
|
+
self.data = []
|
|
164
|
+
|
|
165
|
+
# 获取实例
|
|
166
|
+
service1 = MyService()
|
|
167
|
+
service2 = MyService()
|
|
168
|
+
|
|
169
|
+
# service1 和 service2 是同一个实例
|
|
170
|
+
assert service1 is service2
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## 📁 项目结构
|
|
174
|
+
|
|
175
|
+
```
|
|
176
|
+
fly-common/
|
|
177
|
+
├── safety/ # 安全模块
|
|
178
|
+
│ ├── jwt_token.py # JWT Token 管理
|
|
179
|
+
│ └── md5.py # 密码加密工具
|
|
180
|
+
├── tools/ # 工具模块
|
|
181
|
+
│ └── single.py # 单例模式
|
|
182
|
+
└── __init__.py
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## 🤝 参与贡献
|
|
186
|
+
|
|
187
|
+
欢迎提交 Issue 和 Pull Request!
|
|
188
|
+
|
|
189
|
+
1. Fork 本仓库
|
|
190
|
+
2. 新建特性分支 (`git checkout -b feature/AmazingFeature`)
|
|
191
|
+
3. 提交更改 (`git commit -m 'Add some AmazingFeature'`)
|
|
192
|
+
4. 推送到分支 (`git push origin feature/AmazingFeature`)
|
|
193
|
+
5. 提交 Pull Request
|
|
194
|
+
|
|
195
|
+
## 📄 许可证
|
|
196
|
+
|
|
197
|
+
本项目采用 MIT 许可证
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# fly-common
|
|
2
|
+
|
|
3
|
+
> 一个轻量级的 Python 公共工具库,专注于安全认证和常用工具函数
|
|
4
|
+
|
|
5
|
+
## 📖 介绍
|
|
6
|
+
|
|
7
|
+
fly-common 是一个功能精简但实用的 Python 公共库,主要提供以下核心功能:
|
|
8
|
+
|
|
9
|
+
- **JWT Token 管理**:支持对称加密(HS256)和非对称加密(RS256)的 JWT Token 生成、验证和刷新
|
|
10
|
+
- **密码加密**:提供 MD5、PBKDF2 等多种加密方式
|
|
11
|
+
- **工具函数**:提供单例模式、随机字符串生成等实用工具
|
|
12
|
+
|
|
13
|
+
## ✨ 功能特性
|
|
14
|
+
|
|
15
|
+
### 安全模块 (safety)
|
|
16
|
+
|
|
17
|
+
#### JWT Token
|
|
18
|
+
- 支持对称加密(HS256)和非对称加密(RS256)
|
|
19
|
+
- 提供 Access Token 和 Refresh Token 的生成
|
|
20
|
+
- Token 验证与刷新机制
|
|
21
|
+
- 支持自定义过期时间、签发者(issuer)和受众(audience)
|
|
22
|
+
- 单例模式实现,确保全局唯一实例
|
|
23
|
+
|
|
24
|
+
#### 密码加密
|
|
25
|
+
- MD5 加密
|
|
26
|
+
- PBKDF2 加密(推荐用于密码存储)
|
|
27
|
+
- 密码验证功能
|
|
28
|
+
- 随机字符串生成
|
|
29
|
+
- 数字验证码生成
|
|
30
|
+
|
|
31
|
+
### 工具模块 (tools)
|
|
32
|
+
|
|
33
|
+
- 线程安全的单例模式装饰器
|
|
34
|
+
|
|
35
|
+
## 📦 安装
|
|
36
|
+
|
|
37
|
+
### 环境要求
|
|
38
|
+
|
|
39
|
+
- Python >= 3.9
|
|
40
|
+
|
|
41
|
+
### 安装依赖
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install -r requirements.txt
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## 🚀 使用说明
|
|
48
|
+
|
|
49
|
+
### JWT Token 使用示例
|
|
50
|
+
|
|
51
|
+
#### 对称加密(HS256)
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
from fly_common.safety.jwt_token import JWTSymmetry
|
|
55
|
+
|
|
56
|
+
# 初始化(单例模式)
|
|
57
|
+
jwt_sym = JWTSymmetry(
|
|
58
|
+
secret_key="your-secret-key",
|
|
59
|
+
access_token_expire_seconds=86400, # 24小时
|
|
60
|
+
refresh_token_expire_seconds=604800, # 7天
|
|
61
|
+
issuer="your-app",
|
|
62
|
+
audience="your-users"
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# 生成 Token
|
|
66
|
+
result = jwt_sym.create_at_rf_token(payload={"sub": "user123"})
|
|
67
|
+
if result.ok:
|
|
68
|
+
access_token = result.data["access_token"]
|
|
69
|
+
refresh_token = result.data["refresh_token"]
|
|
70
|
+
|
|
71
|
+
# 验证 Token
|
|
72
|
+
verify_result = jwt_sym.verify_token(access_token)
|
|
73
|
+
if verify_result.ok:
|
|
74
|
+
print("Token 有效:", verify_result.data)
|
|
75
|
+
|
|
76
|
+
# 刷新 Token
|
|
77
|
+
refresh_result = jwt_sym.refresh_token(access_token, refresh_token)
|
|
78
|
+
if refresh_result.ok:
|
|
79
|
+
new_tokens = refresh_result.data
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
#### 非对称加密(RS256)
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
from fly_common.safety.jwt_token import JWTAsymmetry
|
|
86
|
+
|
|
87
|
+
# 初始化(单例模式)
|
|
88
|
+
jwt_asym = JWTAsymmetry(
|
|
89
|
+
private_key="your-private-key",
|
|
90
|
+
public_key="your-public-key",
|
|
91
|
+
algorithm="RS256"
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
# 使用方式与对称加密相同
|
|
95
|
+
result = jwt_asym.create_at_rf_token(payload={"sub": "user123"})
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### 密码加密使用示例
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
from fly_common.safety.md5 import md5, en_password, check_password, code_number
|
|
102
|
+
|
|
103
|
+
# MD5 加密
|
|
104
|
+
hashed = md5("your-string")
|
|
105
|
+
|
|
106
|
+
# 密码加密(PBKDF2)
|
|
107
|
+
password_hash = en_password("user-password")
|
|
108
|
+
|
|
109
|
+
# 验证密码
|
|
110
|
+
is_valid = check_password("user-password", password_hash)
|
|
111
|
+
|
|
112
|
+
# 生成验证码
|
|
113
|
+
code = code_number(6) # 生成6位数字验证码
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### 单例模式使用示例
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
from fly_common.tools.single import Singleton
|
|
120
|
+
|
|
121
|
+
@Singleton
|
|
122
|
+
class MyService:
|
|
123
|
+
def __init__(self):
|
|
124
|
+
self.data = []
|
|
125
|
+
|
|
126
|
+
# 获取实例
|
|
127
|
+
service1 = MyService()
|
|
128
|
+
service2 = MyService()
|
|
129
|
+
|
|
130
|
+
# service1 和 service2 是同一个实例
|
|
131
|
+
assert service1 is service2
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## 📁 项目结构
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
fly-common/
|
|
138
|
+
├── safety/ # 安全模块
|
|
139
|
+
│ ├── jwt_token.py # JWT Token 管理
|
|
140
|
+
│ └── md5.py # 密码加密工具
|
|
141
|
+
├── tools/ # 工具模块
|
|
142
|
+
│ └── single.py # 单例模式
|
|
143
|
+
└── __init__.py
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## 🤝 参与贡献
|
|
147
|
+
|
|
148
|
+
欢迎提交 Issue 和 Pull Request!
|
|
149
|
+
|
|
150
|
+
1. Fork 本仓库
|
|
151
|
+
2. 新建特性分支 (`git checkout -b feature/AmazingFeature`)
|
|
152
|
+
3. 提交更改 (`git commit -m 'Add some AmazingFeature'`)
|
|
153
|
+
4. 推送到分支 (`git push origin feature/AmazingFeature`)
|
|
154
|
+
5. 提交 Pull Request
|
|
155
|
+
|
|
156
|
+
## 📄 许可证
|
|
157
|
+
|
|
158
|
+
本项目采用 MIT 许可证
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "fly-common" # 项目名称
|
|
7
|
+
version = "0.1.0" # 项目版本号
|
|
8
|
+
description = "通用模块" # 项目备注
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9" # 最低python版本支持
|
|
11
|
+
authors = [
|
|
12
|
+
{ name = "fly", email = "fzkf117@163.com" }
|
|
13
|
+
]
|
|
14
|
+
license = { text = "MIT" }
|
|
15
|
+
|
|
16
|
+
dependencies = [
|
|
17
|
+
"annotated-types==0.7.0",
|
|
18
|
+
"cffi==2.0.0",
|
|
19
|
+
"cryptography==46.0.5",
|
|
20
|
+
"ecdsa==0.19.1",
|
|
21
|
+
"passlib==1.7.4",
|
|
22
|
+
"pyasn1==0.6.3",
|
|
23
|
+
"pycparser==2.23",
|
|
24
|
+
"pydantic==2.12.5",
|
|
25
|
+
"pydantic-core==2.41.5",
|
|
26
|
+
"python-jose==3.5.0",
|
|
27
|
+
"rsa==4.9.1",
|
|
28
|
+
"six==1.17.0",
|
|
29
|
+
"typing-extensions==4.15.0",
|
|
30
|
+
"typing-inspection==0.4.2",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
keywords = ["utils", "tools"]
|
|
34
|
+
|
|
35
|
+
classifiers = [
|
|
36
|
+
"Development Status :: 4 - Beta",
|
|
37
|
+
"Intended Audience :: Developers",
|
|
38
|
+
"Operating System :: OS Independent",
|
|
39
|
+
"Programming Language :: Python :: 3",
|
|
40
|
+
"Programming Language :: Python :: 3.6",
|
|
41
|
+
"Programming Language :: Python :: 3.7",
|
|
42
|
+
"Programming Language :: Python :: 3.8",
|
|
43
|
+
"Programming Language :: Python :: 3.9",
|
|
44
|
+
"Programming Language :: Python :: 3.10",
|
|
45
|
+
"Programming Language :: Python :: 3.11",
|
|
46
|
+
"Programming Language :: Python :: 3.12",
|
|
47
|
+
"Programming Language :: Python :: 3.13",
|
|
48
|
+
"Topic :: Software Development :: Libraries :: Python Modules"
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
[tool.setuptools]
|
|
52
|
+
package-dir = {"" = "src"}
|
|
53
|
+
|
|
54
|
+
[tool.setuptools.packages.find]
|
|
55
|
+
where = ["src"]
|
|
56
|
+
include = ["fly_common*"] # 需要打包的项目
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import uuid
|
|
2
|
+
from datetime import datetime, timedelta, UTC
|
|
3
|
+
from typing import Union, Dict, Any, List
|
|
4
|
+
from pydantic import BaseModel
|
|
5
|
+
from jose import jwt, JWTError, ExpiredSignatureError
|
|
6
|
+
from ..tools.single import Singleton
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Response(BaseModel):
|
|
10
|
+
ok: bool
|
|
11
|
+
msg: str = ""
|
|
12
|
+
data: Union[Dict[str, Any], List[Dict[str, Any]], None] = None
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class BaseJWT:
|
|
16
|
+
"""
|
|
17
|
+
JWT 基类(模板类)
|
|
18
|
+
子类只需要实现:
|
|
19
|
+
- _encode
|
|
20
|
+
- _decode
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
access_token_expire_seconds: int = 86400,
|
|
26
|
+
refresh_token_expire_seconds: int = 604800,
|
|
27
|
+
issuer: str = None,
|
|
28
|
+
audience: str = None
|
|
29
|
+
):
|
|
30
|
+
self.access_token_expire_seconds = access_token_expire_seconds
|
|
31
|
+
self.refresh_token_expire_seconds = refresh_token_expire_seconds
|
|
32
|
+
self.issuer = issuer
|
|
33
|
+
self.audience = audience
|
|
34
|
+
|
|
35
|
+
# =========================
|
|
36
|
+
# 子类必须实现
|
|
37
|
+
# =========================
|
|
38
|
+
def _encode(self, payload: dict) -> str:
|
|
39
|
+
raise NotImplementedError
|
|
40
|
+
|
|
41
|
+
def _decode(self, token: str, verify_exp: bool = True) -> dict:
|
|
42
|
+
raise NotImplementedError
|
|
43
|
+
|
|
44
|
+
# ==========================================================
|
|
45
|
+
# 创建 Access + Refresh Token
|
|
46
|
+
# ==========================================================
|
|
47
|
+
def create_at_rf_token(self, payload: dict) -> Response:
|
|
48
|
+
"""
|
|
49
|
+
payload 建议:
|
|
50
|
+
{
|
|
51
|
+
"sub": user_id # 标准字段(用户唯一标识)
|
|
52
|
+
}
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
now = datetime.now(UTC)
|
|
56
|
+
jti = uuid.uuid4().hex # token 唯一ID
|
|
57
|
+
|
|
58
|
+
base_payload = {
|
|
59
|
+
**payload,
|
|
60
|
+
"jti": jti,
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
# 可选字段(只有配置才加)
|
|
64
|
+
if self.issuer:
|
|
65
|
+
base_payload["iss"] = self.issuer
|
|
66
|
+
|
|
67
|
+
if self.audience:
|
|
68
|
+
base_payload["aud"] = self.audience
|
|
69
|
+
|
|
70
|
+
# Access Token
|
|
71
|
+
access_payload = {
|
|
72
|
+
**base_payload,
|
|
73
|
+
"token_type": "access",
|
|
74
|
+
"iat": now,
|
|
75
|
+
"exp": now + timedelta(seconds=self.access_token_expire_seconds),
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
# Refresh Token
|
|
79
|
+
refresh_payload = {
|
|
80
|
+
**base_payload,
|
|
81
|
+
"token_type": "refresh",
|
|
82
|
+
"iat": now,
|
|
83
|
+
"exp": now + timedelta(seconds=self.refresh_token_expire_seconds),
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return Response(
|
|
87
|
+
ok=True,
|
|
88
|
+
msg="生成成功",
|
|
89
|
+
data={
|
|
90
|
+
"access_token": self._encode(access_payload),
|
|
91
|
+
"refresh_token": self._encode(refresh_payload),
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
# ==========================================================
|
|
95
|
+
# 验证 Token(严格校验)
|
|
96
|
+
# ==========================================================
|
|
97
|
+
def verify_token(self, token: str) -> Response:
|
|
98
|
+
try:
|
|
99
|
+
payload = self._decode(token, verify_exp=True)
|
|
100
|
+
return Response(ok=True, msg="验证成功", data=payload)
|
|
101
|
+
|
|
102
|
+
except ExpiredSignatureError:
|
|
103
|
+
return Response(ok=False, msg="Token 过期")
|
|
104
|
+
|
|
105
|
+
except JWTError:
|
|
106
|
+
return Response(ok=False, msg="Token 非法")
|
|
107
|
+
|
|
108
|
+
except Exception as e:
|
|
109
|
+
return Response(ok=False, msg=str(e))
|
|
110
|
+
|
|
111
|
+
# ==========================================================
|
|
112
|
+
# 解析 Token(允许过期)
|
|
113
|
+
# ==========================================================
|
|
114
|
+
def parse_token(self, token: str) -> Response:
|
|
115
|
+
"""
|
|
116
|
+
用于 refresh 场景
|
|
117
|
+
"""
|
|
118
|
+
try:
|
|
119
|
+
payload = self._decode(token, verify_exp=False)
|
|
120
|
+
return Response(ok=True, msg="解析成功", data=payload)
|
|
121
|
+
|
|
122
|
+
except JWTError:
|
|
123
|
+
return Response(ok=False, msg="Token 非法")
|
|
124
|
+
|
|
125
|
+
except Exception as e:
|
|
126
|
+
return Response(ok=False, msg=str(e))
|
|
127
|
+
|
|
128
|
+
# ==========================================================
|
|
129
|
+
# 刷新 Token(仅基础逻辑)
|
|
130
|
+
# ==========================================================
|
|
131
|
+
def refresh_token(self, access_token: str, refresh_token: str) -> Response:
|
|
132
|
+
"""
|
|
133
|
+
⚠️ 注意:
|
|
134
|
+
✔ 不做黑名单
|
|
135
|
+
✔ 不做真正“轮换控制”
|
|
136
|
+
✔ 只负责生成新 token
|
|
137
|
+
"""
|
|
138
|
+
|
|
139
|
+
# 1. 校验 refresh_token
|
|
140
|
+
rf_res = self.verify_token(refresh_token)
|
|
141
|
+
if not rf_res.ok:
|
|
142
|
+
return rf_res
|
|
143
|
+
|
|
144
|
+
rf_payload = rf_res.data
|
|
145
|
+
|
|
146
|
+
if rf_payload.get("token_type") != "refresh":
|
|
147
|
+
return Response(ok=False, msg="RefreshToken 非法")
|
|
148
|
+
|
|
149
|
+
# 2. 解析 access_token(允许过期)
|
|
150
|
+
at_res = self.parse_token(access_token)
|
|
151
|
+
if not at_res.ok:
|
|
152
|
+
return at_res
|
|
153
|
+
|
|
154
|
+
at_payload = at_res.data
|
|
155
|
+
|
|
156
|
+
# 3. 校验 jti 一致
|
|
157
|
+
if at_payload.get("jti") != rf_payload.get("jti"):
|
|
158
|
+
return Response(ok=False, msg="Token 不一致")
|
|
159
|
+
|
|
160
|
+
# 4. 提取业务 payload(去掉系统字段)
|
|
161
|
+
base_payload = {
|
|
162
|
+
k: v for k, v in at_payload.items()
|
|
163
|
+
if k not in ["exp", "iat", "token_type"]
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
# 5. 生成新 token(⚠️这里只是“重新签发”,不是完整轮换策略)
|
|
167
|
+
return Response(ok=True, msg="刷新成功", data=self.create_at_rf_token(base_payload).data)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
@Singleton
|
|
171
|
+
class JWTSymmetry(BaseJWT):
|
|
172
|
+
"""
|
|
173
|
+
对称 JWT(HS256)
|
|
174
|
+
|
|
175
|
+
✔ 支持 issuer / audience 可选(None 不校验)
|
|
176
|
+
✔ 标准 JWT 字段(sub / jti / token_type)
|
|
177
|
+
✔ 只负责 token 处理(不包含黑名单、轮换策略)
|
|
178
|
+
"""
|
|
179
|
+
|
|
180
|
+
def __init__(self, secret_key: str, algorithm="HS256", **kwargs):
|
|
181
|
+
super().__init__(**kwargs)
|
|
182
|
+
self.secret_key = secret_key
|
|
183
|
+
self.algorithm = algorithm
|
|
184
|
+
|
|
185
|
+
# ==========================================================
|
|
186
|
+
# 内部:编码
|
|
187
|
+
# ==========================================================
|
|
188
|
+
def _encode(self, payload: dict) -> str:
|
|
189
|
+
return jwt.encode(payload, self.secret_key, algorithm=self.algorithm)
|
|
190
|
+
|
|
191
|
+
# ==========================================================
|
|
192
|
+
# 内部:解码(关键:动态校验 issuer / audience)
|
|
193
|
+
# ==========================================================
|
|
194
|
+
def _decode(self, token: str, verify_exp: bool = True) -> dict:
|
|
195
|
+
"""
|
|
196
|
+
支持 issuer / audience 为 None 时自动关闭校验
|
|
197
|
+
"""
|
|
198
|
+
|
|
199
|
+
options = {
|
|
200
|
+
"verify_exp": verify_exp,
|
|
201
|
+
"verify_aud": self.audience is not None,
|
|
202
|
+
"verify_iss": self.issuer is not None,
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
decode_kwargs = {
|
|
206
|
+
"key": self.secret_key,
|
|
207
|
+
"algorithms": [self.algorithm],
|
|
208
|
+
"options": options,
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
# 只有配置了才传(否则会报错)
|
|
212
|
+
if self.audience is not None:
|
|
213
|
+
decode_kwargs["audience"] = self.audience
|
|
214
|
+
|
|
215
|
+
if self.issuer is not None:
|
|
216
|
+
decode_kwargs["issuer"] = self.issuer
|
|
217
|
+
|
|
218
|
+
return jwt.decode(token, **decode_kwargs)
|
|
219
|
+
|
|
220
|
+
def create_at_rf_token(self, payload: dict) -> Response:
|
|
221
|
+
return super().create_at_rf_token(payload)
|
|
222
|
+
|
|
223
|
+
def verify_token(self, token: str) -> Response:
|
|
224
|
+
# 直接调用父类实现
|
|
225
|
+
return super().verify_token(token)
|
|
226
|
+
|
|
227
|
+
def parse_token(self, token: str) -> Response:
|
|
228
|
+
return super().parse_token(token)
|
|
229
|
+
|
|
230
|
+
def refresh_token(self, access_token: str, refresh_token: str) -> Response:
|
|
231
|
+
return super().refresh_token(access_token, refresh_token)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
@Singleton
|
|
235
|
+
class JWTAsymmetry(BaseJWT):
|
|
236
|
+
|
|
237
|
+
def __init__(self, private_key: str, public_key: str, algorithm="RS256", **kwargs):
|
|
238
|
+
super().__init__(**kwargs)
|
|
239
|
+
self.private_key = private_key
|
|
240
|
+
self.public_key = public_key
|
|
241
|
+
self.algorithm = algorithm
|
|
242
|
+
|
|
243
|
+
# ==========================================================
|
|
244
|
+
# 内部:编码(用私钥签名)
|
|
245
|
+
# ==========================================================
|
|
246
|
+
def _encode(self, payload: dict) -> str:
|
|
247
|
+
return jwt.encode(payload, self.private_key, algorithm=self.algorithm)
|
|
248
|
+
|
|
249
|
+
# ==========================================================
|
|
250
|
+
# 内部:解码(用公钥验证 + 动态校验 iss / aud)
|
|
251
|
+
# ==========================================================
|
|
252
|
+
def _decode(self, token: str, verify_exp: bool = True) -> dict:
|
|
253
|
+
"""
|
|
254
|
+
✔ 使用公钥验证签名
|
|
255
|
+
✔ 支持 issuer / audience 为 None 时不校验
|
|
256
|
+
"""
|
|
257
|
+
|
|
258
|
+
options = {
|
|
259
|
+
"verify_exp": verify_exp,
|
|
260
|
+
"verify_aud": self.audience is not None,
|
|
261
|
+
"verify_iss": self.issuer is not None,
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
decode_kwargs = {
|
|
265
|
+
"key": self.public_key,
|
|
266
|
+
"algorithms": [self.algorithm],
|
|
267
|
+
"options": options,
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
# 只有配置了才传,否则会报错
|
|
271
|
+
if self.audience is not None:
|
|
272
|
+
decode_kwargs["audience"] = self.audience
|
|
273
|
+
|
|
274
|
+
if self.issuer is not None:
|
|
275
|
+
decode_kwargs["issuer"] = self.issuer
|
|
276
|
+
|
|
277
|
+
return jwt.decode(token, **decode_kwargs)
|
|
278
|
+
|
|
279
|
+
def create_at_rf_token(self, payload: dict) -> Response:
|
|
280
|
+
return super().create_at_rf_token(payload)
|
|
281
|
+
|
|
282
|
+
def verify_token(self, token: str) -> Response:
|
|
283
|
+
# 直接调用父类实现
|
|
284
|
+
return super().verify_token(token)
|
|
285
|
+
|
|
286
|
+
def parse_token(self, token: str) -> Response:
|
|
287
|
+
return super().parse_token(token)
|
|
288
|
+
|
|
289
|
+
def refresh_token(self, access_token: str, refresh_token: str) -> Response:
|
|
290
|
+
return super().refresh_token(access_token, refresh_token)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
import random
|
|
3
|
+
import uuid
|
|
4
|
+
from passlib.handlers.pbkdf2 import pbkdf2_sha256
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def md5(s: str) -> str:
|
|
8
|
+
"""返回字符串的 MD5 值"""
|
|
9
|
+
return hashlib.md5(s.encode('utf-8')).hexdigest()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def random_str() -> str:
|
|
13
|
+
"""返回唯一随机字符串(基于 UUID + MD5)"""
|
|
14
|
+
return hashlib.md5(uuid.uuid1().bytes).hexdigest()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def en_password(psw: str) -> str:
|
|
18
|
+
"""使用 PBKDF2 进行加密"""
|
|
19
|
+
return pbkdf2_sha256.hash(psw)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def check_password(password: str, hashed: str) -> bool:
|
|
23
|
+
"""验证密码"""
|
|
24
|
+
try:
|
|
25
|
+
return pbkdf2_sha256.verify(password, hashed)
|
|
26
|
+
except Exception as e:
|
|
27
|
+
return False
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def code_number(length: int) -> str:
|
|
31
|
+
"""生成指定长度的纯数字验证码"""
|
|
32
|
+
return ''.join(random.choices('0123456789', k=length))
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
if __name__ == '__main__':
|
|
37
|
+
password = "25d55ad283aa400af464c76d713c07ad" # "12345678"
|
|
38
|
+
other = en_password(md5(password))
|
|
39
|
+
print(other)
|
|
40
|
+
print(check_password(md5(password), other))
|
|
File without changes
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from threading import Lock
|
|
2
|
+
|
|
3
|
+
class Singleton:
|
|
4
|
+
def __init__(self, cls):
|
|
5
|
+
self._cls = cls
|
|
6
|
+
self._instance = None
|
|
7
|
+
self._lock = Lock()
|
|
8
|
+
|
|
9
|
+
def __call__(self, *args, **kwargs):
|
|
10
|
+
if self._instance is None:
|
|
11
|
+
with self._lock:
|
|
12
|
+
if self._instance is None:
|
|
13
|
+
self._instance = self._cls(*args, **kwargs)
|
|
14
|
+
return self._instance
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fly-common
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: 通用模块
|
|
5
|
+
Author-email: fly <fzkf117@163.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: utils,tools
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.6
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Requires-Python: >=3.9
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: annotated-types==0.7.0
|
|
25
|
+
Requires-Dist: cffi==2.0.0
|
|
26
|
+
Requires-Dist: cryptography==46.0.5
|
|
27
|
+
Requires-Dist: ecdsa==0.19.1
|
|
28
|
+
Requires-Dist: passlib==1.7.4
|
|
29
|
+
Requires-Dist: pyasn1==0.6.3
|
|
30
|
+
Requires-Dist: pycparser==2.23
|
|
31
|
+
Requires-Dist: pydantic==2.12.5
|
|
32
|
+
Requires-Dist: pydantic-core==2.41.5
|
|
33
|
+
Requires-Dist: python-jose==3.5.0
|
|
34
|
+
Requires-Dist: rsa==4.9.1
|
|
35
|
+
Requires-Dist: six==1.17.0
|
|
36
|
+
Requires-Dist: typing-extensions==4.15.0
|
|
37
|
+
Requires-Dist: typing-inspection==0.4.2
|
|
38
|
+
Dynamic: license-file
|
|
39
|
+
|
|
40
|
+
# fly-common
|
|
41
|
+
|
|
42
|
+
> 一个轻量级的 Python 公共工具库,专注于安全认证和常用工具函数
|
|
43
|
+
|
|
44
|
+
## 📖 介绍
|
|
45
|
+
|
|
46
|
+
fly-common 是一个功能精简但实用的 Python 公共库,主要提供以下核心功能:
|
|
47
|
+
|
|
48
|
+
- **JWT Token 管理**:支持对称加密(HS256)和非对称加密(RS256)的 JWT Token 生成、验证和刷新
|
|
49
|
+
- **密码加密**:提供 MD5、PBKDF2 等多种加密方式
|
|
50
|
+
- **工具函数**:提供单例模式、随机字符串生成等实用工具
|
|
51
|
+
|
|
52
|
+
## ✨ 功能特性
|
|
53
|
+
|
|
54
|
+
### 安全模块 (safety)
|
|
55
|
+
|
|
56
|
+
#### JWT Token
|
|
57
|
+
- 支持对称加密(HS256)和非对称加密(RS256)
|
|
58
|
+
- 提供 Access Token 和 Refresh Token 的生成
|
|
59
|
+
- Token 验证与刷新机制
|
|
60
|
+
- 支持自定义过期时间、签发者(issuer)和受众(audience)
|
|
61
|
+
- 单例模式实现,确保全局唯一实例
|
|
62
|
+
|
|
63
|
+
#### 密码加密
|
|
64
|
+
- MD5 加密
|
|
65
|
+
- PBKDF2 加密(推荐用于密码存储)
|
|
66
|
+
- 密码验证功能
|
|
67
|
+
- 随机字符串生成
|
|
68
|
+
- 数字验证码生成
|
|
69
|
+
|
|
70
|
+
### 工具模块 (tools)
|
|
71
|
+
|
|
72
|
+
- 线程安全的单例模式装饰器
|
|
73
|
+
|
|
74
|
+
## 📦 安装
|
|
75
|
+
|
|
76
|
+
### 环境要求
|
|
77
|
+
|
|
78
|
+
- Python >= 3.9
|
|
79
|
+
|
|
80
|
+
### 安装依赖
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
pip install -r requirements.txt
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## 🚀 使用说明
|
|
87
|
+
|
|
88
|
+
### JWT Token 使用示例
|
|
89
|
+
|
|
90
|
+
#### 对称加密(HS256)
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
from fly_common.safety.jwt_token import JWTSymmetry
|
|
94
|
+
|
|
95
|
+
# 初始化(单例模式)
|
|
96
|
+
jwt_sym = JWTSymmetry(
|
|
97
|
+
secret_key="your-secret-key",
|
|
98
|
+
access_token_expire_seconds=86400, # 24小时
|
|
99
|
+
refresh_token_expire_seconds=604800, # 7天
|
|
100
|
+
issuer="your-app",
|
|
101
|
+
audience="your-users"
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
# 生成 Token
|
|
105
|
+
result = jwt_sym.create_at_rf_token(payload={"sub": "user123"})
|
|
106
|
+
if result.ok:
|
|
107
|
+
access_token = result.data["access_token"]
|
|
108
|
+
refresh_token = result.data["refresh_token"]
|
|
109
|
+
|
|
110
|
+
# 验证 Token
|
|
111
|
+
verify_result = jwt_sym.verify_token(access_token)
|
|
112
|
+
if verify_result.ok:
|
|
113
|
+
print("Token 有效:", verify_result.data)
|
|
114
|
+
|
|
115
|
+
# 刷新 Token
|
|
116
|
+
refresh_result = jwt_sym.refresh_token(access_token, refresh_token)
|
|
117
|
+
if refresh_result.ok:
|
|
118
|
+
new_tokens = refresh_result.data
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
#### 非对称加密(RS256)
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
from fly_common.safety.jwt_token import JWTAsymmetry
|
|
125
|
+
|
|
126
|
+
# 初始化(单例模式)
|
|
127
|
+
jwt_asym = JWTAsymmetry(
|
|
128
|
+
private_key="your-private-key",
|
|
129
|
+
public_key="your-public-key",
|
|
130
|
+
algorithm="RS256"
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
# 使用方式与对称加密相同
|
|
134
|
+
result = jwt_asym.create_at_rf_token(payload={"sub": "user123"})
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### 密码加密使用示例
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
from fly_common.safety.md5 import md5, en_password, check_password, code_number
|
|
141
|
+
|
|
142
|
+
# MD5 加密
|
|
143
|
+
hashed = md5("your-string")
|
|
144
|
+
|
|
145
|
+
# 密码加密(PBKDF2)
|
|
146
|
+
password_hash = en_password("user-password")
|
|
147
|
+
|
|
148
|
+
# 验证密码
|
|
149
|
+
is_valid = check_password("user-password", password_hash)
|
|
150
|
+
|
|
151
|
+
# 生成验证码
|
|
152
|
+
code = code_number(6) # 生成6位数字验证码
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### 单例模式使用示例
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
from fly_common.tools.single import Singleton
|
|
159
|
+
|
|
160
|
+
@Singleton
|
|
161
|
+
class MyService:
|
|
162
|
+
def __init__(self):
|
|
163
|
+
self.data = []
|
|
164
|
+
|
|
165
|
+
# 获取实例
|
|
166
|
+
service1 = MyService()
|
|
167
|
+
service2 = MyService()
|
|
168
|
+
|
|
169
|
+
# service1 和 service2 是同一个实例
|
|
170
|
+
assert service1 is service2
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## 📁 项目结构
|
|
174
|
+
|
|
175
|
+
```
|
|
176
|
+
fly-common/
|
|
177
|
+
├── safety/ # 安全模块
|
|
178
|
+
│ ├── jwt_token.py # JWT Token 管理
|
|
179
|
+
│ └── md5.py # 密码加密工具
|
|
180
|
+
├── tools/ # 工具模块
|
|
181
|
+
│ └── single.py # 单例模式
|
|
182
|
+
└── __init__.py
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## 🤝 参与贡献
|
|
186
|
+
|
|
187
|
+
欢迎提交 Issue 和 Pull Request!
|
|
188
|
+
|
|
189
|
+
1. Fork 本仓库
|
|
190
|
+
2. 新建特性分支 (`git checkout -b feature/AmazingFeature`)
|
|
191
|
+
3. 提交更改 (`git commit -m 'Add some AmazingFeature'`)
|
|
192
|
+
4. 推送到分支 (`git push origin feature/AmazingFeature`)
|
|
193
|
+
5. 提交 Pull Request
|
|
194
|
+
|
|
195
|
+
## 📄 许可证
|
|
196
|
+
|
|
197
|
+
本项目采用 MIT 许可证
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/fly_common/__init__.py
|
|
5
|
+
src/fly_common.egg-info/PKG-INFO
|
|
6
|
+
src/fly_common.egg-info/SOURCES.txt
|
|
7
|
+
src/fly_common.egg-info/dependency_links.txt
|
|
8
|
+
src/fly_common.egg-info/requires.txt
|
|
9
|
+
src/fly_common.egg-info/top_level.txt
|
|
10
|
+
src/fly_common/safety/__init__.py
|
|
11
|
+
src/fly_common/safety/jwt_token.py
|
|
12
|
+
src/fly_common/safety/md5.py
|
|
13
|
+
src/fly_common/tools/__init__.py
|
|
14
|
+
src/fly_common/tools/single.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
annotated-types==0.7.0
|
|
2
|
+
cffi==2.0.0
|
|
3
|
+
cryptography==46.0.5
|
|
4
|
+
ecdsa==0.19.1
|
|
5
|
+
passlib==1.7.4
|
|
6
|
+
pyasn1==0.6.3
|
|
7
|
+
pycparser==2.23
|
|
8
|
+
pydantic==2.12.5
|
|
9
|
+
pydantic-core==2.41.5
|
|
10
|
+
python-jose==3.5.0
|
|
11
|
+
rsa==4.9.1
|
|
12
|
+
six==1.17.0
|
|
13
|
+
typing-extensions==4.15.0
|
|
14
|
+
typing-inspection==0.4.2
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
fly_common
|