bugfix
This commit is contained in:
parent
6d74d084a9
commit
83ff81e5e0
217
aidocs/CSVData.md
Normal file
217
aidocs/CSVData.md
Normal file
@ -0,0 +1,217 @@
|
||||
# CSVData 类技术文档
|
||||
|
||||
`CSVData` 是一个用于读取和处理 CSV 文件的 Python 类,提供了两种读取模式:一次性加载全部数据(`read()`)和逐行迭代处理(`iterRead()`)。该类支持自定义字段名、跳过标题行和数据起始行,适用于灵活解析各种格式的 CSV 数据。
|
||||
|
||||
---
|
||||
|
||||
## 📦 模块依赖
|
||||
|
||||
```python
|
||||
import csv
|
||||
```
|
||||
|
||||
> 使用标准库中的 `csv` 模块进行 CSV 文件解析。
|
||||
|
||||
---
|
||||
|
||||
## 🔧 类定义
|
||||
|
||||
### `class CSVData(csvfile, names=None, headline=0, dataline=1)`
|
||||
|
||||
#### 参数说明:
|
||||
|
||||
| 参数 | 类型 | 默认值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| `csvfile` | str | 必填 | CSV 文件路径 |
|
||||
| `names` | list 或 None | `None` | 自定义字段名称列表。若提供,则忽略文件中的标题行 |
|
||||
| `headline` | int | `0` | 标题行所在的行号(从 0 开始),仅在 `names` 为 `None` 时使用 |
|
||||
| `dataline` | int | `1` | 实际数据开始的行号(从 0 开始) |
|
||||
|
||||
---
|
||||
|
||||
## 📚 方法说明
|
||||
|
||||
### 1. `__init__(self, csvfile, names=None, headline=0, dataline=1)`
|
||||
初始化 CSVData 实例,设置文件路径与解析参数。
|
||||
|
||||
---
|
||||
|
||||
### 2. `read(self) -> list[dict]`
|
||||
以列表形式返回所有记录,每条记录是一个字典(键为字段名,值为对应列值)。
|
||||
|
||||
#### 返回值:
|
||||
- `list[dict]`: 包含所有数据记录的列表,例如:
|
||||
```python
|
||||
[
|
||||
{'st_date': '2024-01-01', 'open_price': '100', ...},
|
||||
{'st_date': '2024-01-02', 'open_price': '102', ...},
|
||||
...
|
||||
]
|
||||
```
|
||||
|
||||
#### 工作流程:
|
||||
1. 打开 CSV 文件(二进制模式 `'rb'` ⚠️ 存在潜在问题,请见下方【⚠️ 注意事项】)
|
||||
2. 创建 `csv.reader` 对象
|
||||
3. 若未指定 `names`,则读取第 `headline` 行作为字段名
|
||||
4. 从第 `dataline` 行开始逐行构建字典记录并添加到结果列表中
|
||||
5. 关闭文件并返回数据
|
||||
|
||||
> ✅ 适合小到中等规模的数据集。
|
||||
|
||||
---
|
||||
|
||||
### 3. `iterRead(self)`
|
||||
以流式方式逐行读取 CSV 文件,并触发事件回调函数。适用于大数据文件,避免内存溢出。
|
||||
|
||||
#### 回调机制:
|
||||
- `onBegin()`: 在开始读取前调用(当前实现为空)
|
||||
- `onRecord(rec)`: 每处理一行有效数据时调用
|
||||
- `onFinish()`: 成功完成读取后调用
|
||||
|
||||
#### 异常处理:
|
||||
- 使用 `try-except` 捕获异常,确保文件关闭
|
||||
- 出现错误时重新抛出异常(但变量作用域有误,请见【⚠️ Bug 提示】)
|
||||
|
||||
> ✅ 推荐用于大型 CSV 文件处理。
|
||||
|
||||
---
|
||||
|
||||
### 4. `onBegin(self)`
|
||||
钩子方法,在迭代读取开始前被调用。默认为空实现,可由子类重写。
|
||||
|
||||
> 当前代码中调用了 `self.onBegin()`,但该方法未定义 —— 应为 `onReadBegin`?请见下方 Bug 分析。
|
||||
|
||||
---
|
||||
|
||||
### 5. `onRecord(self, rec)`
|
||||
每读取一条有效记录时调用此方法。
|
||||
|
||||
#### 参数:
|
||||
- `rec` (dict): 当前行数据,形如 `{字段名: 值}`
|
||||
|
||||
#### 默认行为:
|
||||
打印当前记录(`print(rec)`)
|
||||
|
||||
> 可继承此类并重写该方法以实现自定义逻辑(如入库、计算等)。
|
||||
|
||||
---
|
||||
|
||||
### 6. `onFinish(self)`
|
||||
所有数据读取完成后调用。
|
||||
|
||||
#### 默认行为:
|
||||
打印 `"onFinish() called"`
|
||||
|
||||
> 可用于资源清理或结束通知。
|
||||
|
||||
---
|
||||
|
||||
## 🖥️ 示例用法
|
||||
|
||||
```python
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
cd = CSVData(
|
||||
sys.argv[1],
|
||||
names=['st_date','open_price','max_price','min_price','close_price','volume','adj_price']
|
||||
)
|
||||
cd.iterRead()
|
||||
```
|
||||
|
||||
### 说明:
|
||||
- 从命令行传入 CSV 文件路径
|
||||
- 使用自定义字段名,不依赖文件头部
|
||||
- 调用 `iterRead()` 流式输出每一行数据
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 注意事项与改进建议
|
||||
|
||||
### ❗Bug 与潜在问题
|
||||
|
||||
| 问题 | 描述 | 建议修复 |
|
||||
|------|------|---------|
|
||||
| 1. `onBegin()` 调用错误 | 代码中调用 `self.onBegin()`,但实际定义的是 `onReadBegin()`,会导致 `AttributeError` | 将 `onReadBegin` 改名为 `onBegin`,或修正调用 |
|
||||
| 2. 异常捕获语法错误 | `except exception as e:` 中 `exception` 应为 `Exception`(首字母大写) | 改为 `except Exception as e:` |
|
||||
| 3. 文件打开模式冲突 | `read()` 方法使用 `'rb'` 二进制模式打开文件,但 `csv.reader` 需要文本模式 | 改为 `'r'` 并指定编码(如 `'utf-8'`) |
|
||||
| 4. `fd.close()` 变量作用域错误 | 在 `except` 块中调用 `fd.close()`,但 `fd` 是实例属性应写作 `self.fd` | 改为 `self.fd.close()` |
|
||||
| 5. 缺少编码设置 | 未指定字符编码,可能导致中文乱码 | 添加 `encoding='utf-8'` 参数 |
|
||||
|
||||
---
|
||||
|
||||
### ✅ 推荐修改后的 `iterRead()` 示例:
|
||||
|
||||
```python
|
||||
def iterRead(self):
|
||||
self.fd = open(self.csvfile, 'r', encoding='utf-8')
|
||||
try:
|
||||
reader = csv.reader(self.fd)
|
||||
fields = None
|
||||
if self.names is not None:
|
||||
fields = self.names
|
||||
lno = 0
|
||||
self.onBegin() # 确保方法存在
|
||||
for l in reader:
|
||||
if fields is None and lno == self.headline:
|
||||
fields = [f for f in l]
|
||||
if lno >= self.dataline:
|
||||
rec = {}
|
||||
for i in range(len(fields)):
|
||||
rec[fields[i]] = l[i]
|
||||
self.onRecord(rec)
|
||||
lno += 1
|
||||
self.fd.close()
|
||||
self.onFinish()
|
||||
except Exception as e:
|
||||
self.fd.close()
|
||||
raise e
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧩 继承与扩展建议
|
||||
|
||||
可通过继承 `CSVData` 类来实现更复杂的功能:
|
||||
|
||||
```python
|
||||
class MyCSVProcessor(CSVData):
|
||||
def onBegin(self):
|
||||
print("开始处理数据...")
|
||||
|
||||
def onRecord(self, rec):
|
||||
# 示例:过滤价格大于 100 的记录
|
||||
if float(rec['close_price']) > 100:
|
||||
print("高价值记录:", rec)
|
||||
|
||||
def onFinish(self):
|
||||
print("数据处理完毕!")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📎 总结
|
||||
|
||||
| 特性 | 支持情况 |
|
||||
|------|----------|
|
||||
| 自定义字段名 | ✅ |
|
||||
| 跳过标题行 | ✅ |
|
||||
| 指定数据起始行 | ✅ |
|
||||
| 全量读取 | ✅ (`read`) |
|
||||
| 流式读取 | ✅ (`iterRead`) |
|
||||
| 可扩展性 | ✅(支持回调钩子) |
|
||||
| 错误处理 | ⚠️ 存在缺陷,需修复 |
|
||||
| 编码支持 | ❌ 缺失,建议增加 |
|
||||
|
||||
---
|
||||
|
||||
## 📌 版本建议(改进方向)
|
||||
|
||||
1. 增加 `encoding` 参数,默认 `'utf-8'`
|
||||
2. 修复异常捕获与方法命名
|
||||
3. 使用上下文管理器 (`with open(...)`) 替代手动关闭文件
|
||||
4. 添加类型注解提升可读性
|
||||
5. 支持分隔符参数(如 tab、分号等)
|
||||
|
||||
---
|
||||
|
||||
📘 **结论**:`CSVData` 是一个结构清晰、易于扩展的 CSV 处理类,稍作修正后可用于生产环境。
|
||||
230
aidocs/Config.md
Normal file
230
aidocs/Config.md
Normal file
@ -0,0 +1,230 @@
|
||||
# `Config.py` 技术文档
|
||||
|
||||
---
|
||||
|
||||
## 概述
|
||||
|
||||
`Config.py` 是一个用于加载和管理应用程序配置的 Python 模块,采用单例模式(Singleton)确保全局配置的一致性。该模块通过执行指定的配置文件(如 `conf/config.ini`),动态解析并构建配置对象,支持自定义节点类型和字典式访问。
|
||||
|
||||
此模块由 **longtop Co.** 开发,遵循开源许可证(见 LICENSE 文件),最初版本发布于 2009 年。
|
||||
|
||||
---
|
||||
|
||||
## 元信息
|
||||
|
||||
| 项目 | 内容 |
|
||||
|------|------|
|
||||
| 文件名 | `Config.py` |
|
||||
| 版权所有 | © 2009 longtop Co. |
|
||||
| 许可证 | 参见项目根目录下的 `LICENSE` 文件 |
|
||||
| 作者 | yumoqing@gmail.com |
|
||||
| 创建日期 | 2009-02-01 |
|
||||
| 最后修改日期 | 2009-02-05 |
|
||||
|
||||
---
|
||||
|
||||
## 依赖项
|
||||
|
||||
### 内置模块
|
||||
- `os`
|
||||
- `sys`
|
||||
|
||||
### 第三方/自定义模块
|
||||
- `appPublic.ExecFile.ExecFile`:用于安全执行配置脚本。
|
||||
- `appPublic.dictObject.DictObject`:提供类字典的对象封装。
|
||||
- `appPublic.Singleton.Singleton`:实现单例模式。
|
||||
- `zope.interface`:用于接口定义(当前未使用具体接口,但已导入)。
|
||||
- `folderUtils.ProgramPath`:获取程序运行主路径。
|
||||
|
||||
> ⚠️ 注意:以上自定义模块属于内部库,需确保在运行环境中正确安装或路径可导入。
|
||||
|
||||
---
|
||||
|
||||
## 核心类与函数
|
||||
|
||||
### 常量
|
||||
|
||||
```python
|
||||
CONFIG_FILE = 'conf/config.ini'
|
||||
```
|
||||
|
||||
默认配置文件路径,相对于程序主目录。
|
||||
|
||||
---
|
||||
|
||||
### 类:`Node`
|
||||
|
||||
```python
|
||||
class Node(object):
|
||||
pass
|
||||
```
|
||||
|
||||
#### 说明:
|
||||
最基础的空对象类,用作配置树中节点的基础类型。可用于扩展自定义配置节点行为。
|
||||
|
||||
#### 用途:
|
||||
在配置文件中可通过 `Node()` 实例化一个空白节点,后续添加属性。
|
||||
|
||||
---
|
||||
|
||||
### 类:`Config`
|
||||
|
||||
```python
|
||||
class Config:
|
||||
__metaclass = Singleton
|
||||
|
||||
def __init__(self, configpath=None):
|
||||
...
|
||||
```
|
||||
|
||||
#### 功能描述:
|
||||
`Config` 类是核心配置管理器,使用单例模式保证整个应用中仅存在一个配置实例。
|
||||
|
||||
#### 参数:
|
||||
- `configpath` (str, optional): 自定义配置文件路径。若为 `None`,则使用默认路径 `ProgramPath()/conf/config.ini`。
|
||||
|
||||
#### 初始化流程:
|
||||
1. 若未传入 `configpath`,则根据 `CONFIG_FILE` 和 `ProgramPath()` 构造完整路径。
|
||||
2. 使用 `ExecFile` 加载并执行配置文件。
|
||||
3. 向执行环境注入以下可用类型:
|
||||
- `Node`: 可创建结构化节点
|
||||
- `DictObject`: 字典式对象
|
||||
- `dict`: 别名为 `DictObject`,便于使用 `{}` 风格语法创建对象
|
||||
4. 执行配置脚本,并捕获结果。
|
||||
|
||||
#### 属性:
|
||||
| 属性 | 类型 | 描述 |
|
||||
|------|------|------|
|
||||
| `configfile` | str | 当前加载的配置文件完整路径 |
|
||||
| `__execfile` | ExecFile | 负责解析和运行配置文件的执行器 |
|
||||
|
||||
#### 示例配置文件 (`conf/config.ini`) 内容示例:
|
||||
|
||||
```python
|
||||
# config.ini 示例
|
||||
database = DictObject(
|
||||
host='localhost',
|
||||
port=3306,
|
||||
username='root',
|
||||
password='123456'
|
||||
)
|
||||
|
||||
server = Node()
|
||||
server.host = '0.0.0.0'
|
||||
server.port = 8080
|
||||
|
||||
users = dict(admin='admin@local', dev='dev@local')
|
||||
```
|
||||
|
||||
上述代码将被解析成可访问的配置对象。
|
||||
|
||||
#### 错误处理:
|
||||
如果 `self.__execfile.run()` 返回 `(False, msg)`,会在控制台打印错误信息 `(r, msg)`。
|
||||
|
||||
---
|
||||
|
||||
### 函数:`getConfig(path=None)`
|
||||
|
||||
```python
|
||||
def getConfig(path=None):
|
||||
conf = Config(path)
|
||||
return conf
|
||||
```
|
||||
|
||||
#### 功能:
|
||||
获取 `Config` 单例实例的便捷函数。
|
||||
|
||||
#### 参数:
|
||||
- `path` (str, optional): 指定配置文件路径,优先级高于默认路径。
|
||||
|
||||
#### 返回值:
|
||||
- `Config` 实例(单例)
|
||||
|
||||
#### 使用示例:
|
||||
|
||||
```python
|
||||
from Config import getConfig
|
||||
|
||||
config = getConfig() # 使用默认路径
|
||||
print(config.database.host) # 输出: localhost
|
||||
|
||||
# 或指定路径
|
||||
custom_config = getConfig("myconf/custom.ini")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 使用说明
|
||||
|
||||
### 步骤 1:准备配置文件
|
||||
|
||||
创建 `conf/config.ini` 文件(或其他路径),内容如下:
|
||||
|
||||
```python
|
||||
app_name = "MyApplication"
|
||||
version = "1.0.0"
|
||||
|
||||
logging = DictObject(
|
||||
level="DEBUG",
|
||||
file="logs/app.log"
|
||||
)
|
||||
|
||||
features = dict(
|
||||
enable_cache=True,
|
||||
max_workers=4
|
||||
)
|
||||
```
|
||||
|
||||
### 步骤 2:在主程序中加载配置
|
||||
|
||||
```python
|
||||
from Config import getConfig
|
||||
|
||||
cfg = getConfig()
|
||||
|
||||
print(cfg.app_name) # MyApplication
|
||||
print(cfg.logging.level) # DEBUG
|
||||
print(cfg.features.enable_cache) # True
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 设计特点
|
||||
|
||||
| 特性 | 描述 |
|
||||
|------|------|
|
||||
| ✅ 单例模式 | 确保配置全局唯一,避免重复加载 |
|
||||
| ✅ 动态执行 | 支持 `.ini` 文件中编写 Python 表达式 |
|
||||
| ✅ 结构化数据 | 支持 `DictObject` 和 `Node` 创建嵌套结构 |
|
||||
| ✅ 可扩展性 | 易于注入新类型或上下文变量 |
|
||||
| ⚠️ 安全性注意 | `ExecFile` 执行任意代码,应确保配置文件来源可信 |
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **安全性警告**:由于使用了代码执行机制(`ExecFile`),请确保配置文件不被恶意篡改。
|
||||
2. **路径问题**:`ProgramPath()` 必须返回正确的程序根路径,否则无法定位 `conf/config.ini`。
|
||||
3. **异常处理建议**:当前出错仅打印消息,建议增强日志记录或抛出异常。
|
||||
4. **兼容性**:`__metaclass = Singleton` 为旧式类写法(Python 2 风格),在 Python 3 中应使用元类继承方式。
|
||||
|
||||
---
|
||||
|
||||
## 未来优化建议
|
||||
|
||||
- 将 `__metaclass = Singleton` 改为 Python 3 兼容的元类写法。
|
||||
- 添加 `get()` 方法以安全访问嵌套键(如 `cfg.get('database.host')`)。
|
||||
- 支持 JSON/YAML 等更安全的配置格式作为替代选项。
|
||||
- 引入配置验证机制(如基于 schema)。
|
||||
- 增加单元测试覆盖。
|
||||
|
||||
---
|
||||
|
||||
## 版权声明
|
||||
|
||||
> 本软件由 longtop Co. 开发,遵循项目 LICENSE 文件中的条款发布。
|
||||
> 如需商业合作或技术支持,请联系作者:yumoqing@gmail.com
|
||||
|
||||
---
|
||||
|
||||
📅 文档最后更新:2025-04-05
|
||||
240
aidocs/ExecFile.md
Normal file
240
aidocs/ExecFile.md
Normal file
@ -0,0 +1,240 @@
|
||||
# `ExecFile.py` 技术文档
|
||||
|
||||
```markdown
|
||||
# ExecFile.py - 动态执行 Python 配置文件并构建配置对象
|
||||
|
||||
`ExecFile.py` 是一个轻量级的 Python 模块,用于从 `.py` 或 `.ini` 类似格式的文本文件中动态加载配置数据。它通过 `exec()` 执行文件内容,并将变量注入指定命名空间,支持嵌套字典自动转换为可属性访问的对象。
|
||||
|
||||
---
|
||||
|
||||
## 概述
|
||||
|
||||
该模块主要包含两个核心类:
|
||||
|
||||
- `DictConfig`: 一个增强型字典类,允许通过属性语法访问键值,并支持嵌套结构递归转换。
|
||||
- `ExecFile`: 提供运行外部配置文件的能力,可设置变量、执行脚本并获取结果。
|
||||
|
||||
典型应用场景包括:
|
||||
- 加载可编程的配置文件(如卡片配置、游戏规则等)
|
||||
- 实现插件式配置系统
|
||||
- 将 Python 脚本作为配置源使用
|
||||
|
||||
---
|
||||
|
||||
## 安装与导入
|
||||
|
||||
无需安装,直接将 `ExecFile.py` 放入项目路径后导入即可:
|
||||
|
||||
```python
|
||||
from ExecFile import ExecFile, DictConfig
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 核心类说明
|
||||
|
||||
### 1. `DictConfig(dic=None, path=None, str=None, namespace={})`
|
||||
|
||||
一个支持属性访问和嵌套结构解析的字典类。
|
||||
|
||||
#### 参数
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
|------|------|------|
|
||||
| `dic` | `dict` | 初始字典数据,会被深度转换为 `DictConfig` 对象 |
|
||||
| `path` | `str` | 配置文件路径,自动读取并执行其中的 Python 代码 |
|
||||
| `str` | `str` | 包含 Python 代码的字符串,将在指定命名空间中执行 |
|
||||
| `namespace` | `dict` | 执行代码时使用的全局命名空间 |
|
||||
|
||||
> ⚠️ 注意:`path` 和 `str` 不能同时为 `None`;若提供多个参数,则按顺序依次处理。
|
||||
|
||||
#### 属性与方法
|
||||
|
||||
##### `.keys()`
|
||||
返回所有键名。
|
||||
|
||||
##### `.__getitem__(key)`
|
||||
支持 `obj[key]` 访问方式。
|
||||
|
||||
##### `.__getattr__(name)`
|
||||
支持 `obj.name` 属性式访问。如果属性不存在则抛出 `AttributeError`。
|
||||
|
||||
##### `.__subConfig()`
|
||||
私有方法:递归遍历字典中的结构化数据(`dict`, `list`, `tuple`),将其中的 `dict` 自动转为 `DictConfig` 实例,实现链式属性访问。
|
||||
|
||||
例如:
|
||||
```python
|
||||
data = {'user': {'name': 'Alice', 'age': 30}}
|
||||
config = DictConfig(dic=data)
|
||||
print(config.user.name) # 输出: Alice
|
||||
```
|
||||
|
||||
##### `.__load(path)`
|
||||
私有方法:从文件路径加载内容并执行,提取变量到当前实例。
|
||||
|
||||
---
|
||||
|
||||
### 2. `ExecFile(obj=None, path=None, namespace={})`
|
||||
|
||||
用于执行外部 Python 风格配置文件的核心类。
|
||||
|
||||
#### 参数
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
|------|------|------|
|
||||
| `obj` | `object` | 目标对象,配置变量将被注入此对象的 `__dict__` 中。默认为 `self` |
|
||||
| `path` | `str` | 要执行的配置文件路径(可选) |
|
||||
| `namespace` | `dict` | 全局命名空间(目前未完全启用) |
|
||||
|
||||
#### 方法
|
||||
|
||||
##### `.set(name, v)`
|
||||
设置目标对象的一个属性。
|
||||
|
||||
**参数:**
|
||||
- `name` (str): 属性名
|
||||
- `v`: 属性值
|
||||
|
||||
**示例:**
|
||||
```python
|
||||
r = ExecFile()
|
||||
r.set('game_mode', 'hard')
|
||||
```
|
||||
|
||||
##### `.get(name, default=None)`
|
||||
获取目标对象的属性值,若不存在返回默认值。
|
||||
|
||||
**参数:**
|
||||
- `name` (str): 属性名
|
||||
- `default`: 默认返回值
|
||||
|
||||
**示例:**
|
||||
```python
|
||||
mode = r.get('game_mode', 'normal')
|
||||
```
|
||||
|
||||
##### `.run(path=None)`
|
||||
执行指定路径的配置文件。
|
||||
|
||||
**参数:**
|
||||
- `path` (str): 配置文件路径。若传入则更新内部路径记录。
|
||||
|
||||
**返回值:**
|
||||
- 成功时返回 `(True, '')`
|
||||
- 失败时返回 `(False, Exception)` 并打印错误信息
|
||||
|
||||
**行为说明:**
|
||||
- 使用 `open()` 读取文件内容
|
||||
- 通过 `exec(buf, globals(), self.__object.__dict__)` 执行代码,变量注入目标对象
|
||||
- 若文件路径未设置且未传参,则抛出异常
|
||||
|
||||
---
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 示例 1:基本用法 —— 加载配置文件
|
||||
|
||||
假设有一个配置文件 `test/cards.ini` 内容如下:
|
||||
|
||||
```python
|
||||
# test/cards.ini
|
||||
cards = [
|
||||
{'id': 1, 'name': 'Fireball', 'damage': 50},
|
||||
{'id': 2, 'name': 'Shield', 'damage': 0}
|
||||
]
|
||||
game_version = "1.0.0"
|
||||
max_players = 4
|
||||
```
|
||||
|
||||
使用 `ExecFile` 加载:
|
||||
|
||||
```python
|
||||
r = ExecFile()
|
||||
r.run('test/cards.ini')
|
||||
|
||||
# 访问配置项
|
||||
print(r.cards[0]['name']) # 输出: Fireball
|
||||
print(r.game_version) # 输出: 1.0.0
|
||||
print(r.max_players) # 输出: 4
|
||||
```
|
||||
|
||||
### 示例 2:预设变量 + 执行文件
|
||||
|
||||
```python
|
||||
r = ExecFile()
|
||||
r.set('player_level', 5)
|
||||
r.run('test/cards.ini')
|
||||
|
||||
# 在 cards.ini 中可以引用 player_level 变量
|
||||
# 例如:bonus_damage = player_level * 10
|
||||
```
|
||||
|
||||
### 示例 3:结合 DictConfig 解析嵌套结构
|
||||
|
||||
```python
|
||||
config_str = """
|
||||
user = {
|
||||
'profile': {
|
||||
'name': 'Bob',
|
||||
'settings': ['dark_mode', 'notifications']
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
dc = DictConfig(str=config_str)
|
||||
print(dc.user.profile.name) # 输出: Bob
|
||||
print(dc.user.profile.settings) # 输出: ['dark_mode', 'notifications']
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 文件格式要求
|
||||
|
||||
配置文件应为合法的 Python 语法片段,常见形式包括:
|
||||
|
||||
- 变量赋值:`var = value`
|
||||
- 字典定义:`config = {'key': 'value'}`
|
||||
- 列表/元组:`items = [1, 2, 3]`
|
||||
- 支持表达式计算(依赖已定义变量)
|
||||
|
||||
⚠️ **安全警告**:由于使用了 `exec()`,请确保配置文件来源可信,避免远程或用户上传执行。
|
||||
|
||||
---
|
||||
|
||||
## 错误处理
|
||||
|
||||
- `run()` 方法捕获所有异常并输出错误日志:
|
||||
```
|
||||
ExecFile() <Exception Message> <FilePath>
|
||||
```
|
||||
- 返回 `(False, exception)` 表示执行失败,调用者应进行相应处理。
|
||||
|
||||
---
|
||||
|
||||
## 已知限制
|
||||
|
||||
1. 不支持异步文件读取
|
||||
2. `__load()` 方法在 `DictConfig` 中存在但未被公开调用接口
|
||||
3. 命名空间机制较简单,`globals()` 被直接传递
|
||||
4. 异常处理较为基础,仅打印信息无重试机制
|
||||
|
||||
---
|
||||
|
||||
## 版本信息
|
||||
|
||||
- 创建时间:未知
|
||||
- 作者:未知
|
||||
- 语言版本:Python 2/3 兼容(建议 Python 3.x)
|
||||
|
||||
> 注:代码中使用了 `print` 函数兼容写法,适用于 Python 3。
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
`ExecFile.py` 提供了一种灵活的方式来加载“代码即配置”的 `.py` 或 `.ini` 风格文件,适合小型项目或需要动态逻辑配置的场景。配合 `DictConfig` 可实现优雅的嵌套配置访问。
|
||||
|
||||
推荐在受控环境中使用,注意安全风险。
|
||||
|
||||
---
|
||||
```
|
||||
223
aidocs/FiniteStateMachine.md
Normal file
223
aidocs/FiniteStateMachine.md
Normal file
@ -0,0 +1,223 @@
|
||||
# FiniteStateMachine.py 技术文档
|
||||
|
||||
本文档描述了 `FiniteStateMachine.py` 模块的设计与使用方式,该模块实现了一个轻量级的有限状态机(Finite State Machine, FSM)系统,适用于需要状态驱动行为的对象管理。
|
||||
|
||||
---
|
||||
|
||||
## 1. 概述
|
||||
|
||||
本模块提供了一套基于类的有限状态机框架,包含以下核心组件:
|
||||
|
||||
- `BaseFSM`: 所有具体状态类的抽象基类。
|
||||
- `FSMManager`: 状态管理器,用于注册和调度不同状态的行为逻辑。
|
||||
- `FSMObject`: 支持状态切换的游戏或业务对象基类。
|
||||
|
||||
该设计采用**状态模式**(State Pattern),将状态的行为封装在独立的状态对象中,并通过管理器统一调度。
|
||||
|
||||
---
|
||||
|
||||
## 2. 核心类说明
|
||||
|
||||
### 2.1 `BaseFSM` —— 状态行为抽象基类
|
||||
|
||||
`BaseFSM` 是所有具体状态类必须继承的抽象基类。它定义了状态的三个生命周期方法。
|
||||
|
||||
#### 方法接口
|
||||
|
||||
| 方法 | 描述 |
|
||||
|------|------|
|
||||
| `enterState(obj)` | 当对象进入此状态时调用。通常用于初始化操作。 |
|
||||
| `execState(obj)` | 在每帧或每次更新周期中执行当前状态的逻辑。 |
|
||||
| `exitState(obj)` | 当对象退出此状态前调用。可用于清理资源或保存状态数据。 |
|
||||
|
||||
> ⚠️ 注意:这三个方法均为抽象方法,子类必须重写,否则会抛出 `NotImplementedError`。
|
||||
|
||||
#### 示例子类定义(用户需自行实现)
|
||||
|
||||
```python
|
||||
class IdleState(BaseFSM):
|
||||
def enterState(self, obj):
|
||||
print(f"{obj} 进入空闲状态")
|
||||
|
||||
def execState(self, obj):
|
||||
print(f"{obj} 正处于空闲状态")
|
||||
|
||||
def exitState(self, obj):
|
||||
print(f"{obj} 离开空闲状态")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2.2 `FSMManager` —— 有限状态机管理器
|
||||
|
||||
全局唯一的状态管理器,负责维护状态与其对应 FSM 实例之间的映射关系,并驱动状态更新流程。
|
||||
|
||||
#### 属性
|
||||
|
||||
- `_fsms` (`dict`):内部字典,键为状态标识(如字符串),值为对应的 `BaseFSM` 子类实例。
|
||||
|
||||
#### 构造函数
|
||||
|
||||
```python
|
||||
def __init__(self)
|
||||
```
|
||||
|
||||
初始化一个空的状态映射表。
|
||||
|
||||
#### 公共方法
|
||||
|
||||
| 方法 | 参数 | 返回值 | 描述 |
|
||||
|------|------|--------|------|
|
||||
| `addState(state, fsm)` | `state`: str 或 hashable<br>`fsm`: BaseFSM 实例 | 无 | 将指定状态名绑定到一个 FSM 实例上。 |
|
||||
| `delState(state)` | `state`: 要删除的状态名 | 无 | 移除指定状态及其 FSM 实例。若状态不存在则引发 KeyError。 |
|
||||
| `getFSM(state)` | `state`: 查询的状态名 | BaseFSM 实例 | 获取与状态名关联的 FSM 对象。若未注册则引发 KeyError。 |
|
||||
| `frame(objs, state)` | `objs`: 可迭代的 FSMObject 列表<br>`state`: 当前期望状态 | 无 | 遍历对象列表,根据其当前状态决定是否切换或保持状态。 |
|
||||
|
||||
#### `frame()` 方法逻辑详解
|
||||
|
||||
```python
|
||||
def frame(self, objs, state):
|
||||
for obj in objs:
|
||||
if state == obj.curr_state:
|
||||
obj.keepState()
|
||||
else:
|
||||
obj.changeState(state, self._fsms[state])
|
||||
```
|
||||
|
||||
- 若对象当前状态等于目标状态 → 调用 `keepState()` 继续执行当前状态逻辑。
|
||||
- 否则 → 调用 `changeState()` 切换至新状态,并加载对应 FSM 行为。
|
||||
|
||||
> ✅ 推荐用法:每一游戏/逻辑帧调用一次 `manager.frame(objects, current_global_state)` 来同步所有对象的状态行为。
|
||||
|
||||
---
|
||||
|
||||
### 2.3 `FSMObject` —— 支持状态机的对象基类
|
||||
|
||||
表示可以拥有有限状态机行为的实体对象(例如游戏角色、UI 控件等)。
|
||||
|
||||
#### 属性
|
||||
|
||||
| 属性 | 类型 | 描述 |
|
||||
|------|------|------|
|
||||
| `fsm_cur_state` | hashable (e.g., str) | 当前所处的状态标识。 |
|
||||
| `fsm_state_object` | BaseFSM 实例 | 当前状态对应的行为控制器。 |
|
||||
|
||||
#### 方法
|
||||
|
||||
| 方法 | 参数 | 返回值 | 描述 |
|
||||
|------|------|--------|------|
|
||||
| `attachFSM(state, fsm)` | `state`: 初始状态名<br>`fsm`: BaseFSM 实例 | 无 | 初始化对象的状态机,设置初始状态及行为逻辑。 |
|
||||
| `changeState(new_state, newfsm)` | `new_state`: 新状态名<br>`newfsm`: 新状态对应的 FSM 实例 | 无 | 执行完整的状态切换流程:<br>1. 调用旧状态的 `exitState`<br>2. 更新状态和 FSM 实例<br>3. 调用新状态的 `enterState` 和 `execState` |
|
||||
| `keepState()` | 无 | 无 | 维持当前状态,仅执行当前 FSM 的 `execState(self)` 方法。 |
|
||||
|
||||
> 🔁 **注意**:`changeState` 中存在变量名拼写错误:`new_fsm` 应为 `newfsm`(见下文“已知问题”)。
|
||||
|
||||
---
|
||||
|
||||
## 3. 使用示例
|
||||
|
||||
```python
|
||||
# 定义两个状态
|
||||
class WalkingState(BaseFSM):
|
||||
def enterState(self, obj):
|
||||
print("开始行走")
|
||||
|
||||
def execState(self, obj):
|
||||
print("正在行走...")
|
||||
|
||||
def exitState(self, obj):
|
||||
print("停止行走")
|
||||
|
||||
class JumpingState(BaseFSM):
|
||||
def enterState(self, obj):
|
||||
print("起跳!")
|
||||
|
||||
def execState(self, obj):
|
||||
print("空中飞行...")
|
||||
|
||||
def exitState(self, obj):
|
||||
print("落地")
|
||||
|
||||
# 创建管理器并注册状态
|
||||
manager = FSMManager()
|
||||
manager.addState("walk", WalkingState())
|
||||
manager.addState("jump", JumpingState())
|
||||
|
||||
# 创建对象并附加初始状态
|
||||
player = FSMObject()
|
||||
player.attachFSM("walk", WalkingState())
|
||||
|
||||
# 模拟运行帧
|
||||
objects = [player]
|
||||
manager.frame(objects, "jump") # 切换到跳跃状态
|
||||
manager.frame(objects, "jump") # 保持跳跃状态
|
||||
manager.frame(objects, "walk") # 切回行走状态
|
||||
```
|
||||
|
||||
**输出结果:**
|
||||
```
|
||||
开始行走
|
||||
正在行走...
|
||||
起跳!
|
||||
空中飞行...
|
||||
落地
|
||||
开始行走
|
||||
正在行走...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 已知问题与改进建议
|
||||
|
||||
### ❌ Bug: 变量名拼写错误
|
||||
|
||||
在 `FSMObject.changeState()` 方法中:
|
||||
|
||||
```python
|
||||
self.fsm_state_object = new_fsm # 错误:应为 newfsm
|
||||
```
|
||||
|
||||
✅ 正确写法应为:
|
||||
```python
|
||||
self.fsm_state_object = newfsm
|
||||
```
|
||||
|
||||
建议修复如下:
|
||||
|
||||
```python
|
||||
def changeState(self, new_state, newfsm):
|
||||
self.fsm_state_object.exitState(self)
|
||||
self.fsm_cur_state = new_state
|
||||
self.fsm_state_object = newfsm
|
||||
self.fsm_state_object.enterState(self)
|
||||
self.fsm_state_object.execState(self)
|
||||
```
|
||||
|
||||
### 🛠 建议改进
|
||||
|
||||
| 改进点 | 说明 |
|
||||
|-------|------|
|
||||
| 添加异常处理 | 在 `getFSM()` 和 `delState()` 中加入 `KeyError` 捕获并友好提示。 |
|
||||
| 支持状态栈(Push/Pop) | 可扩展支持暂停当前状态、临时进入另一个状态后再返回。 |
|
||||
| 引入状态转换条件 | 当前由外部控制切换,可引入 `canEnter()` 方法判断是否允许进入某状态。 |
|
||||
| 使用枚举作为状态类型 | 提高类型安全性,避免字符串硬编码错误。 |
|
||||
|
||||
---
|
||||
|
||||
## 5. 总结
|
||||
|
||||
`FiniteStateMachine.py` 提供了一个简洁、可扩展的状态机基础架构,适合用于:
|
||||
|
||||
- 游戏开发中的角色行为控制
|
||||
- UI 状态流转管理
|
||||
- 机器人动作序列控制
|
||||
- 任何需要清晰状态划分的系统
|
||||
|
||||
通过组合 `BaseFSM` 子类、`FSMManager` 和 `FSMObject`,开发者可以轻松构建模块化、易维护的状态驱动程序。
|
||||
|
||||
---
|
||||
|
||||
📌 **版本信息**
|
||||
- 文件名: `FiniteStateMachine.py`
|
||||
- 设计模式: 状态模式(State Pattern)
|
||||
- 适用范围: Python 3.x
|
||||
307
aidocs/MiniI18N.md
Normal file
307
aidocs/MiniI18N.md
Normal file
@ -0,0 +1,307 @@
|
||||
# `MiniI18N` 国际化模块技术文档
|
||||
|
||||
```markdown
|
||||
# MiniI18N 模块技术文档
|
||||
|
||||
## 概述
|
||||
|
||||
`MiniI18N` 是一个轻量级的 Python 国际化(i18n)工具,用于实现多语言文本的动态加载与翻译。它支持基于线程的用户语言隔离、自动语言检测、键值对格式的消息文件解析,并通过单例模式确保全局唯一实例。
|
||||
|
||||
该模块适用于需要在不同语言环境下运行的小型应用或 Web 后端服务。
|
||||
|
||||
---
|
||||
|
||||
## 依赖
|
||||
|
||||
- Python 标准库:
|
||||
- `os`, `re`, `sys`, `codecs`, `threading`, `time`, `locale`
|
||||
- 第三方模块(来自 `appPublic` 包):
|
||||
- `appPublic.folderUtils._mkdir`
|
||||
- `appPublic.Singleton.SingletonDecorator`
|
||||
- `appPublic.folderUtils.ProgramPath`
|
||||
|
||||
> ⚠️ 注意:需确保 `appPublic` 包已安装并可导入。
|
||||
|
||||
---
|
||||
|
||||
## 核心功能
|
||||
|
||||
- 支持 UTF-8 编码的多语言 `.txt` 文件读取。
|
||||
- 基于正则表达式解析 `key: value` 形式的消息条目。
|
||||
- 忽略以 `#` 开头的注释行。
|
||||
- 线程安全的语言上下文管理(每个线程可设置独立语言)。
|
||||
- 单例模式保证全局配置一致性。
|
||||
- 自动清理过期客户端语言状态(默认超时 600 秒)。
|
||||
- 支持语言别名映射(如 `zh-CN` → `zh`)。
|
||||
|
||||
---
|
||||
|
||||
## 数据格式说明
|
||||
|
||||
### 消息文件结构(`msg.txt`)
|
||||
|
||||
位于 `{path}/i18n/{lang}/msg.txt`,每行为:
|
||||
|
||||
```
|
||||
key : value
|
||||
# 这是注释
|
||||
hello_world : 你好,世界
|
||||
welcome : 欢迎使用我们的产品
|
||||
```
|
||||
|
||||
#### 规则:
|
||||
- 使用 `:` 分隔键和值。
|
||||
- 可包含空格,但会被去除前后空白。
|
||||
- 支持特殊字符转义(见下文)。
|
||||
- `#` 开头的行为注释,将被忽略。
|
||||
|
||||
---
|
||||
|
||||
## 特殊字符编码机制
|
||||
|
||||
由于键值中可能包含冒号 `:` 或换行符等,模块内置了一套简单的转义机制。
|
||||
|
||||
### 转义表 (`convert_pairs`)
|
||||
|
||||
| 原始字符 | 转义后字符串 |
|
||||
|--------|-------------|
|
||||
| `:` | `\x3A` |
|
||||
| `\n` | `\x0A` |
|
||||
| `\r` | `\x0D` |
|
||||
| `\` | `\\\\` |
|
||||
|
||||
> 示例:`user:name` → 键变为 `user\x3Aname`
|
||||
|
||||
### 相关函数
|
||||
|
||||
#### `charEncode(s: str) -> str`
|
||||
对字符串中的特殊字符进行编码。
|
||||
|
||||
#### `charDecode(s: str) -> str`
|
||||
对编码后的字符串进行解码,还原原始内容。
|
||||
|
||||
---
|
||||
|
||||
## 主要函数与类
|
||||
|
||||
### 辅助函数
|
||||
|
||||
#### `dictModify(d: dict, md: dict) -> dict`
|
||||
合并字典 `md` 到 `d` 中,仅当值不为 `None` 时更新。
|
||||
|
||||
```python
|
||||
dictModify({'a': 1}, {'a': None, 'b': 2}) # 返回 {'a': 1, 'b': 2}
|
||||
```
|
||||
|
||||
#### `getTextDictFromLines(lines: list[str]) -> dict`
|
||||
从文本行列表中提取键值对字典,跳过注释,解析 `key: value` 并自动解码。
|
||||
|
||||
#### `getFirstLang(lang: str) -> str`
|
||||
从逗号分隔的语言列表中返回第一个语言标签。
|
||||
|
||||
```python
|
||||
getFirstLang("zh-CN,zh,en") # 返回 "zh-CN"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 类:`MiniI18N`
|
||||
|
||||
使用 `@SingletonDecorator` 装饰,确保全局只有一个实例。
|
||||
|
||||
#### 构造方法:`__init__(path, lang=None, coding='utf8')`
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
|---------|--------|------|
|
||||
| `path` | str | 应用根路径,用于查找 `/i18n` 目录 |
|
||||
| `lang` | str or None | 默认语言(可选),未指定则使用系统默认语言 |
|
||||
| `coding`| str | 文件编码,默认 `'utf8'` |
|
||||
|
||||
> 初始化时会自动扫描 `path/i18n/` 下所有子目录作为语言包。
|
||||
|
||||
#### 属性
|
||||
|
||||
| 属性 | 类型 | 描述 |
|
||||
|--------------------|----------|------|
|
||||
| `path` | str | 根路径 |
|
||||
| `curLang` | str | 系统默认语言(来自 `locale.getdefaultlocale()`) |
|
||||
| `coding` | str | 文本编码方式 |
|
||||
| `langTextDict` | dict | 存储各语言的翻译字典 `{lang: {key: text}}` |
|
||||
| `clientLangs` | dict | 线程级语言状态 `{thread_id: {'timestamp': t, 'lang': lang}}` |
|
||||
| `languageMapping` | dict | 语言别名映射,如 `{'zh-CN': 'zh'}` |
|
||||
| `timeout` | int | 客户端语言状态过期时间(秒),默认 600 |
|
||||
|
||||
---
|
||||
|
||||
### 公共方法
|
||||
|
||||
#### `setLangMapping(lang: str, path: str)`
|
||||
设置语言别名映射。
|
||||
|
||||
```python
|
||||
i18n.setLangMapping('zh-CN', 'zh')
|
||||
```
|
||||
|
||||
#### `getLangMapping(lang: str) -> str`
|
||||
获取实际使用的语言名称(考虑映射规则)。
|
||||
|
||||
#### `setTimeout(timeout: int = 600)`
|
||||
设置客户端语言缓存的超时时间。
|
||||
|
||||
#### `delClientLangs()`
|
||||
清理超过 `timeout` 时间未访问的线程语言状态。
|
||||
|
||||
> 通常由内部调用,无需手动触发。
|
||||
|
||||
#### `getLangDict(lang: str) -> dict`
|
||||
获取指定语言的翻译字典(经过映射处理)。
|
||||
|
||||
#### `getLangText(msg: str, lang: str = None) -> str`
|
||||
根据当前或指定语言返回翻译文本。若无翻译,则返回原消息。
|
||||
|
||||
#### `setupMiniI18N()`
|
||||
扫描 `i18n` 目录下的所有语言文件夹,加载 `msg.txt` 内容至内存。
|
||||
|
||||
> 调用示例:
|
||||
>
|
||||
> ```
|
||||
> project_root/
|
||||
> └── i18n/
|
||||
> ├── en/
|
||||
> │ └── msg.txt
|
||||
> ├── zh/
|
||||
> │ └── msg.txt
|
||||
> └── ja/
|
||||
> └── msg.txt
|
||||
> ```
|
||||
|
||||
#### `setCurrentLang(lang: str)`
|
||||
为当前线程设置语言环境。
|
||||
|
||||
```python
|
||||
i18n.setCurrentLang('zh')
|
||||
```
|
||||
|
||||
#### `getCurrentLang() -> str`
|
||||
获取当前线程所设语言。
|
||||
|
||||
> 若未设置,则抛出异常(要求先调用 `setCurrentLang`)。
|
||||
|
||||
---
|
||||
|
||||
### 魔术方法:`__call__(self, msg, lang=None)`
|
||||
|
||||
使对象可直接调用,实现快捷翻译。
|
||||
|
||||
```python
|
||||
_ = i18n # 绑定翻译函数
|
||||
_("Hello") # 等价于 i18n.getLangText("Hello")
|
||||
```
|
||||
|
||||
> 支持 bytes 输入自动解码。
|
||||
|
||||
---
|
||||
|
||||
## 工厂函数
|
||||
|
||||
### `getI18N(coding='utf8') -> MiniI18N`
|
||||
|
||||
创建并返回单例化的 `MiniI18N` 实例,使用当前程序路径作为根路径。
|
||||
|
||||
```python
|
||||
from mymodule import getI18N
|
||||
|
||||
i18n = getI18N('utf8')
|
||||
_ = i18n
|
||||
print(_("Welcome"))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 1. 准备语言文件
|
||||
|
||||
```bash
|
||||
your_project/
|
||||
└── i18n/
|
||||
├── en/
|
||||
│ └── msg.txt
|
||||
└── zh/
|
||||
└── msg.txt
|
||||
```
|
||||
|
||||
`zh/msg.txt` 内容:
|
||||
|
||||
```
|
||||
# 中文翻译
|
||||
greeting : 欢迎
|
||||
goodbye : 再见
|
||||
error.network : 网络错误,请重试
|
||||
```
|
||||
|
||||
`en/msg.txt` 内容:
|
||||
|
||||
```
|
||||
greeting : Welcome
|
||||
goodbye : Goodbye
|
||||
error.network : Network error, please retry
|
||||
```
|
||||
|
||||
### 2. 加载并使用
|
||||
|
||||
```python
|
||||
from mini_i18n import getI18N
|
||||
|
||||
# 获取 i18n 实例
|
||||
i18n = getI18N()
|
||||
|
||||
# 设置当前线程语言
|
||||
i18n.setCurrentLang('zh')
|
||||
|
||||
# 翻译
|
||||
print(i18n('greeting')) # 输出:欢迎
|
||||
print(i18n('goodbye')) # 输出:再见
|
||||
|
||||
# 切换语言
|
||||
i18n.setCurrentLang('en')
|
||||
print(i18n('greeting')) # 输出:Welcome
|
||||
```
|
||||
|
||||
### 3. 使用语言映射
|
||||
|
||||
```python
|
||||
i18n.setLangMapping('zh-CN', 'zh')
|
||||
i18n.setCurrentLang('zh-CN') # 实际使用 zh 的翻译
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **线程安全**:每个线程可通过 `setCurrentLang()` 独立设置语言。
|
||||
2. **性能优化**:所有翻译数据在初始化时一次性加载进内存,避免重复 IO。
|
||||
3. **超时清理**:长时间未活动的线程语言记录将被清除,防止内存泄漏。
|
||||
4. **编码要求**:所有 `msg.txt` 文件必须为 UTF-8 编码(或其他指定编码)。
|
||||
5. **错误处理**:未找到翻译时返回原文,适合开发阶段“降级显示”。
|
||||
|
||||
---
|
||||
|
||||
## 扩展建议
|
||||
|
||||
- 添加 `.json` 或 `.yaml` 格式支持。
|
||||
- 增加运行时动态添加翻译项的功能。
|
||||
- 提供缺失翻译日志记录接口(如 `missed_pt` 预留字段)。
|
||||
- 支持复数形式、占位符替换(如 `%s`, `{name}`)。
|
||||
|
||||
---
|
||||
|
||||
## 版本信息
|
||||
|
||||
- 创建时间:未知
|
||||
- 最后修改:未知
|
||||
- 作者:未知
|
||||
- 许可协议:请参考项目整体 LICENSE
|
||||
|
||||
---
|
||||
```
|
||||
227
aidocs/ObjectCache.md
Normal file
227
aidocs/ObjectCache.md
Normal file
@ -0,0 +1,227 @@
|
||||
# ObjectCache 技术文档
|
||||
|
||||
```markdown
|
||||
# ObjectCache 类技术文档
|
||||
|
||||
## 概述
|
||||
|
||||
`ObjectCache` 是一个基于字典的内存对象缓存类,用于管理具有大小属性的对象集合。它继承自 Python 内置的 `dict` 类,并扩展了容量限制、自动淘汰和访问时间追踪功能。
|
||||
|
||||
该缓存适用于需要控制内存使用量、并能通过 `.get_size()` 方法获取对象大小的场景。当缓存总大小接近预设上限时,会触发基于最近最少使用(LRU)策略的半数淘汰机制。
|
||||
|
||||
---
|
||||
|
||||
## 特性
|
||||
|
||||
- 支持类似字典的键值存储操作。
|
||||
- 自动跟踪每个对象的大小(需实现 `get_size()` 方法)。
|
||||
- 设置最大缓存容量(以字节或其他单位计),防止内存溢出。
|
||||
- 使用 LRU(最近最少使用)策略进行对象淘汰。
|
||||
- 记录对象最后访问时间以支持淘汰逻辑。
|
||||
|
||||
---
|
||||
|
||||
## 类定义
|
||||
|
||||
```python
|
||||
class ObjectCache(dict)
|
||||
```
|
||||
|
||||
继承自 `dict`,因此支持所有标准字典操作(如 `in`, `len()`, 迭代等)。
|
||||
|
||||
---
|
||||
|
||||
## 初始化方法
|
||||
|
||||
### `__init__(self, maxsize=10000000, *args)`
|
||||
|
||||
#### 参数说明:
|
||||
|
||||
| 参数 | 类型 | 说明 |
|
||||
|-----------|--------|------|
|
||||
| `maxsize` | int | 缓存允许的最大总大小,默认为 10,000,000 单位(例如字节)。 |
|
||||
| `*args` | tuple | 传递给父类 `dict` 的初始化参数(可选)。 |
|
||||
|
||||
#### 属性初始化:
|
||||
|
||||
| 属性 | 类型 | 说明 |
|
||||
|------------|--------|------|
|
||||
| `self.maxsize` | int | 最大缓存容量限制。 |
|
||||
| `self.size` | int | 当前缓存中所有对象的总大小,初始为 0。 |
|
||||
| `self._shadow` | dict | 私有字典,记录每个键对应的 `[最后访问时间, 对象大小]`。 |
|
||||
|
||||
> ⚠️ 注意:必须导入 `time` 模块,否则在设置项时会抛出 `NameError`。
|
||||
|
||||
---
|
||||
|
||||
## 核心方法
|
||||
|
||||
### `__setitem__(key, item)`
|
||||
|
||||
将对象插入或更新到缓存中。
|
||||
|
||||
#### 行为流程:
|
||||
|
||||
1. 调用 `item.get_size()` 获取对象大小。
|
||||
2. 若失败(无此方法或异常),则直接返回,不缓存该对象。
|
||||
3. 成功获取大小后,将其加到 `self.size`。
|
||||
4. 如果当前总大小超过 `maxsize`:
|
||||
- 从 `self._shadow` 中提取所有 `(访问时间, key)` 元组。
|
||||
- 按访问时间排序(越早访问的排在前面)。
|
||||
- 删除前一半最久未访问的条目及其对应的数据。
|
||||
5. 调用父类 `__setitem__` 存储新对象。
|
||||
6. 在 `_shadow` 中记录其访问时间和大小。
|
||||
|
||||
#### 注意事项:
|
||||
|
||||
- 存在 bug:`tmp[i][i]` 应为 `tmp[i][1]`(即 key),原代码会导致索引错误。
|
||||
- 正确写法应为:
|
||||
```python
|
||||
for i in xrange(len(tmp)//2):
|
||||
del self[tmp[i][1]] # tmp[i] 是 (time, key),所以取 [1]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `__getitem__(key)`
|
||||
|
||||
获取指定键对应的对象。
|
||||
|
||||
#### 行为流程:
|
||||
|
||||
1. 尝试调用父类 `__getitem__` 获取对象。
|
||||
2. 若存在,则更新该键在 `_shadow` 中的访问时间为当前时间(`time.time()`)。
|
||||
3. 返回对象。
|
||||
|
||||
#### 异常处理:
|
||||
|
||||
- 若键不存在,重新抛出 `KeyError`。
|
||||
|
||||
---
|
||||
|
||||
### `get(key, default=None)`
|
||||
|
||||
安全获取指定键的对象,若不存在返回默认值。
|
||||
|
||||
#### 参数:
|
||||
|
||||
| 参数 | 类型 | 说明 |
|
||||
|----------|------|------|
|
||||
| `key` | any | 要查找的键。 |
|
||||
| `default` | any | 键不存在时返回的默认值,默认为 `None`。 |
|
||||
|
||||
#### 实现逻辑:
|
||||
|
||||
- 调用 `self.has_key(key)` 判断是否存在。
|
||||
- 存在则返回 `self[key]`(触发 `__getitem__` 并更新时间戳)。
|
||||
- 否则返回 `default`。
|
||||
|
||||
> ✅ 推荐使用方式:比直接捕获异常更安全。
|
||||
|
||||
---
|
||||
|
||||
### `__delitem__(key)`
|
||||
|
||||
删除指定键的对象。
|
||||
|
||||
#### 行为流程:
|
||||
|
||||
1. 调用父类 `__delitem__` 删除主缓存中的对象。
|
||||
2. 若成功,从 `self.size` 中减去该对象的大小。
|
||||
3. 从 `self._shadow` 中删除对应的元数据。
|
||||
|
||||
#### 异常处理:
|
||||
|
||||
- 删除失败时重新抛出异常。
|
||||
|
||||
---
|
||||
|
||||
## 使用示例
|
||||
|
||||
```python
|
||||
import time
|
||||
|
||||
class MyData:
|
||||
def __init__(self, data):
|
||||
self.data = data
|
||||
|
||||
def get_size(self):
|
||||
return len(self.data)
|
||||
|
||||
# 创建缓存实例,最大容量为 1MB
|
||||
cache = ObjectCache(maxsize=1024*1024)
|
||||
|
||||
# 添加对象
|
||||
obj = MyData("Hello World" * 100)
|
||||
cache['key1'] = obj
|
||||
|
||||
# 获取对象
|
||||
data = cache.get('key1')
|
||||
print(data)
|
||||
|
||||
# 删除对象
|
||||
del cache['key1']
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 已知问题与改进建议
|
||||
|
||||
### ❌ Bug 修复建议
|
||||
|
||||
在 `__setitem__` 方法中:
|
||||
|
||||
```python
|
||||
for i in xrange(len(tmp)//2) :
|
||||
del self[tmp[i][i]]
|
||||
```
|
||||
|
||||
应改为:
|
||||
|
||||
```python
|
||||
for _, key in tmp[:len(tmp)//2]:
|
||||
del self[key]
|
||||
```
|
||||
|
||||
或者修正索引错误:
|
||||
|
||||
```python
|
||||
for i in range(len(tmp)//2):
|
||||
del self[tmp[i][1]] # 取出 key
|
||||
```
|
||||
|
||||
此外,Python 2 风格的 `xrange` 和 `.iteritems()` 建议升级为 Python 3 兼容语法。
|
||||
|
||||
---
|
||||
|
||||
### ⚙️ 性能优化建议
|
||||
|
||||
- 当前淘汰策略每次都要排序全部元素,复杂度为 O(n log n),效率较低。
|
||||
- 可替换为优先队列或双向链表实现真正的 LRU 缓存,提升性能。
|
||||
- `_shadow` 结构可以考虑合并进主字典,减少维护成本。
|
||||
|
||||
---
|
||||
|
||||
### 💡 扩展功能建议
|
||||
|
||||
- 提供 `clear()` 方法重写以同步清理 `_shadow` 和 `size`。
|
||||
- 添加 `__contains__`、`keys()` 等方法的行为说明。
|
||||
- 增加线程安全锁(多线程环境下使用时)。
|
||||
|
||||
---
|
||||
|
||||
## 依赖要求
|
||||
|
||||
- Python 2.7 或兼容版本(当前代码为 Python 2 风格)
|
||||
- 必须导入模块:
|
||||
```python
|
||||
import time
|
||||
```
|
||||
否则运行时报错。
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
`ObjectCache` 是一个轻量级的对象缓存工具,适合对内存敏感的应用场景。尽管存在一些实现缺陷,但其设计思路清晰,易于理解和扩展。建议在生产环境中结合更成熟的缓存库(如 `functools.lru_cache` 或第三方库 `cachetools`)使用,或在此基础上修复 bug 后封装成稳定组件。
|
||||
```
|
||||
316
aidocs/RSAutils.md
Normal file
316
aidocs/RSAutils.md
Normal file
@ -0,0 +1,316 @@
|
||||
# RSA 加密与签名工具库技术文档
|
||||
|
||||
本项目提供一个基于 Python 的 RSA 加密、解密、签名和验证功能的轻量级工具模块,使用 `PyCryptodome` 库实现核心密码学操作。支持密钥读取、生成、数据加密/解密以及数字签名/验证等功能。
|
||||
|
||||
---
|
||||
|
||||
## 📦 模块依赖
|
||||
|
||||
```python
|
||||
import codecs
|
||||
from Crypto.PublicKey import RSA
|
||||
from Crypto.Cipher import PKCS1_OAEP
|
||||
from Crypto.Cipher import PKCS1_v1_5 as V1_5 # 注意:原代码拼写错误已修正
|
||||
from Crypto.Signature import PKCS1_v1_5
|
||||
from Crypto.Hash import SHA512, SHA384, SHA256, SHA, MD5
|
||||
from Crypto import Random
|
||||
from base64 import b64encode, b64decode
|
||||
```
|
||||
|
||||
> ⚠️ **注意**:需要安装 `pycryptodome` 包:
|
||||
>
|
||||
> ```bash
|
||||
> pip install pycryptodome
|
||||
> ```
|
||||
|
||||
---
|
||||
|
||||
## 🔧 全局变量
|
||||
|
||||
| 变量名 | 类型 | 默认值 | 说明 |
|
||||
|--------|--------|--------------|------|
|
||||
| `hash` | string | `"SHA-256"` | 当前使用的哈希算法名称,用于签名与验证(全局状态) |
|
||||
|
||||
> ❗ 警告:该变量为全局共享状态,在多线程或并发场景中可能导致不可预期行为,建议重构为参数传递方式。
|
||||
|
||||
---
|
||||
|
||||
## 📚 函数说明
|
||||
|
||||
### `readPublickey(fname) → RSA.RsaKey or None`
|
||||
|
||||
从文件读取公钥。
|
||||
|
||||
#### 参数:
|
||||
- `fname` (str): 公钥文件路径(PEM 格式)
|
||||
|
||||
#### 返回值:
|
||||
- 成功时返回 `RSA.RsaKey` 对象
|
||||
- 失败时返回 `None`
|
||||
|
||||
#### 示例:
|
||||
```python
|
||||
pub_key = readPublickey('public.pem')
|
||||
```
|
||||
|
||||
#### 文件格式要求:
|
||||
```
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
...Base64编码内容...
|
||||
-----END PUBLIC KEY-----
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `readPrivatekey(fname, pwd) → RSA.RsaKey or None`
|
||||
|
||||
从文件读取带密码保护的私钥。
|
||||
|
||||
#### 参数:
|
||||
- `fname` (str): 私钥文件路径(PEM 格式)
|
||||
- `pwd` (str 或 bytes): 解密私钥的密码
|
||||
|
||||
#### 返回值:
|
||||
- 成功时返回 `RSA.RsaKey` 对象
|
||||
- 失败时返回 `None`
|
||||
|
||||
#### 示例:
|
||||
```python
|
||||
priv_key = readPrivatekey('private.pem', 'mysecretpassword')
|
||||
```
|
||||
|
||||
#### 文件格式要求:
|
||||
```
|
||||
-----BEGIN ENCRYPTED PRIVATE KEY-----
|
||||
...Base64编码内容...
|
||||
-----END ENCRYPTED PRIVATE KEY-----
|
||||
```
|
||||
|
||||
> ✅ 支持 AES 加密的 PEM 私钥(如 OpenSSL 生成)
|
||||
|
||||
---
|
||||
|
||||
### `newkeys(keysize) → (public_key, private_key)`
|
||||
|
||||
生成新的 RSA 密钥对。
|
||||
|
||||
#### 参数:
|
||||
- `keysize` (int): 密钥长度(推荐 2048 或 4096)
|
||||
|
||||
#### 返回值:
|
||||
- 元组 `(public_key: RsaKey, private_key: RsaKey)`
|
||||
|
||||
#### 示例:
|
||||
```python
|
||||
pub, priv = newkeys(2048)
|
||||
```
|
||||
|
||||
> 💡 使用安全随机数生成器 (`Random.new().read`) 确保密钥安全性。
|
||||
|
||||
---
|
||||
|
||||
### `importKey(externKey) → RSA.RsaKey`
|
||||
|
||||
从字符串导入密钥(支持公钥或私钥)。
|
||||
|
||||
#### 参数:
|
||||
- `externKey` (str 或 bytes): PEM 编码的密钥字符串
|
||||
|
||||
#### 返回值:
|
||||
- `RSA.RsaKey` 对象
|
||||
|
||||
#### 示例:
|
||||
```python
|
||||
key_str = open("public.pem").read()
|
||||
key = importKey(key_str)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `getpublickey(priv_key) → RSA.RsaKey`
|
||||
|
||||
从私钥对象提取对应的公钥。
|
||||
|
||||
#### 参数:
|
||||
- `priv_key` (RSA.RsaKey): 私钥对象
|
||||
|
||||
#### 返回值:
|
||||
- 对应的公钥对象 (`RsaKey`)
|
||||
|
||||
#### 示例:
|
||||
```python
|
||||
pub_key = getpublickey(priv_key)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `encrypt(message, pub_key) → bytes`
|
||||
|
||||
使用公钥加密消息(采用 PKCS#1 OAEP 填充,推荐用于新系统)。
|
||||
|
||||
#### 参数:
|
||||
- `message` (bytes): 明文数据(必须是字节串)
|
||||
- `pub_key` (RSA.RsaKey): 公钥对象
|
||||
|
||||
#### 返回值:
|
||||
- 加密后的密文字节串 (`bytes`)
|
||||
|
||||
#### 示例:
|
||||
```python
|
||||
ciphertext = encrypt(b"Hello World", pub_key)
|
||||
```
|
||||
|
||||
> 🔐 安全提示:OAEP 是抗适应性选择密文攻击的安全填充模式。
|
||||
|
||||
---
|
||||
|
||||
### `decrypt(ciphertext, priv_key) → bytes`
|
||||
|
||||
尝试使用私钥解密数据。优先使用 OAEP,失败后自动降级到 v1.5 填充。
|
||||
|
||||
#### 参数:
|
||||
- `ciphertext` (bytes): 密文数据
|
||||
- `priv_key` (RSA.RsaKey): 私钥对象
|
||||
|
||||
#### 返回值:
|
||||
- 解密后的明文字节串 (`bytes`)
|
||||
- 若两种模式均失败,则抛出异常并打印错误信息
|
||||
|
||||
#### 异常处理逻辑:
|
||||
1. 首先尝试 `PKCS1_OAEP`
|
||||
2. 失败则尝试 `PKCS1_v1_5`(兼容旧系统)
|
||||
3. 打印异常详情(调试用)
|
||||
|
||||
> ⚠️ 自动降级可能带来安全隐患,请确保你知道为何使用 v1.5。
|
||||
|
||||
---
|
||||
|
||||
### `sign(message, priv_key, hashAlg="SHA-256") → bytes`
|
||||
|
||||
对消息进行数字签名(使用 PKCS#1 v1.5 签名方案)。
|
||||
|
||||
#### 参数:
|
||||
- `message` (bytes): 待签名的消息
|
||||
- `priv_key` (RSA.RsaKey): 私钥
|
||||
- `hashAlg` (str): 哈希算法,可选值:
|
||||
- `"SHA-512"`
|
||||
- `"SHA-384"`
|
||||
- `"SHA-256"`(默认)
|
||||
- `"SHA-1"`
|
||||
- `"MD5"`
|
||||
|
||||
#### 返回值:
|
||||
- 签名结果(原始字节流)
|
||||
|
||||
#### 内部逻辑:
|
||||
根据 `hashAlg` 设置全局 `hash` 变量,并选择相应哈希函数计算摘要后签名。
|
||||
|
||||
#### 示例:
|
||||
```python
|
||||
signature = sign(b"important data", priv_key, "SHA-256")
|
||||
```
|
||||
|
||||
> ❌ 不推荐使用 SHA-1 或 MD5,仅保留向后兼容。
|
||||
|
||||
---
|
||||
|
||||
### `verify(message, signature, pub_key) → bool`
|
||||
|
||||
验证数字签名的有效性。
|
||||
|
||||
#### 参数:
|
||||
- `message` (bytes): 原始消息
|
||||
- `signature` (bytes): 签名数据
|
||||
- `pub_key` (RSA.RsaKey): 公钥对象
|
||||
|
||||
#### 返回值:
|
||||
- `True`:签名有效
|
||||
- `False`:签名无效或验证失败
|
||||
|
||||
#### 注意事项:
|
||||
- 使用与 `sign()` 相同的全局 `hash` 变量决定哈希算法
|
||||
- 必须保证签名时和验证时使用相同的哈希算法
|
||||
|
||||
#### 示例:
|
||||
```python
|
||||
is_valid = verify(b"important data", signature, pub_key)
|
||||
if is_valid:
|
||||
print("✅ 签名验证通过")
|
||||
else:
|
||||
print("❌ 签名无效")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧪 主程序示例(测试用途)
|
||||
|
||||
```python
|
||||
if __name__ == '__main__':
|
||||
cipher = """WaMlLEYnhBk+kTDyN/4OJmQf4ccNdk6USgtKpb7eHsYsotq4iyXi3N5hB1E/PqrPSmca1AMDLUcumwIrLeGLT9it3eTBQgl1YQAsmPxa6lF/rDOZoLbwD5sJ6ab/0/fuM4GbotqN5/d0MeuOSELoo8cFWw+7XpRxn9EMYnw5SzsjDQRWxXjZptoaGa/8pBBkDmgLqINif9EWV+8899xqTd0e9w1Gqb7wbt/elRNVBpgsSuSZb+dtBlvNUjuTms8BETSRai5vhXetK26Ms8hrayiy38n7wwEKE8fZ9iFzLtwa6xbhD5KudWbKJFFOZAfpzWttGMwWlISbGQigcW4+Bg=="""
|
||||
|
||||
key = readPrivatekey('d:/dev/mecp/conf/RSA.private.key', 'ymq123')
|
||||
t = decrypt(b64decode(cipher), key) # 注意:cipher 是 Base64 字符串,需先解码
|
||||
print('t=', t)
|
||||
```
|
||||
|
||||
> 🔍 **Bug修复建议**:原文中的 `cipher` 是 Base64 字符串,但直接传给 `decrypt()` 会出错。应先用 `b64decode()` 转换为字节。
|
||||
|
||||
✅ 正确调用方式:
|
||||
```python
|
||||
t = decrypt(b64decode(cipher), key)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ 使用建议与注意事项
|
||||
|
||||
| 项目 | 建议 |
|
||||
|------|------|
|
||||
| 🔐 填充模式 | 推荐使用 `OAEP` 进行加密;`v1.5` 仅用于兼容老系统 |
|
||||
| 📏 密钥长度 | 至少 2048 位,推荐 4096 以增强长期安全性 |
|
||||
| 🧼 全局变量 `hash` | 存在并发风险,建议改为函数参数传递 |
|
||||
| 🧑💻 错误处理 | `decrypt()` 中的 `print(e)` 应替换为日志记录 |
|
||||
| 🔄 签名一致性 | `sign` 和 `verify` 必须使用相同哈希算法 |
|
||||
| 🧩 Base64 编码 | 输入输出建议封装编解码层以便外部使用字符串传输 |
|
||||
|
||||
---
|
||||
|
||||
## 📎 示例:完整加解密流程
|
||||
|
||||
```python
|
||||
# 生成密钥对
|
||||
pub, priv = newkeys(2048)
|
||||
|
||||
# 加密
|
||||
msg = b"Secret message"
|
||||
ciphertext = encrypt(msg, pub)
|
||||
|
||||
# 解密
|
||||
plaintext = decrypt(ciphertext, priv)
|
||||
print(plaintext) # b'Secret message'
|
||||
|
||||
# 签名 & 验证
|
||||
sig = sign(msg, priv, "SHA-256")
|
||||
valid = verify(msg, sig, pub)
|
||||
print(valid) # True
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📚 参考资料
|
||||
|
||||
- [PyCryptodome 官方文档](https://pycryptodome.readthedocs.io/)
|
||||
- RFC 3447 – PKCS #1 v2.1
|
||||
- NIST FIPS 180-4 – 安全哈希标准
|
||||
|
||||
---
|
||||
|
||||
## 📝 版本信息
|
||||
|
||||
- 创建日期:2025年4月5日
|
||||
- 作者:AI 助手
|
||||
- 许可:MIT(假设)
|
||||
|
||||
---
|
||||
|
||||
> ✅ 提示:将此 `.py` 文件保存为 `rsa_utils.py`,可通过 `import rsa_utils` 在其他模块中复用。
|
||||
427
aidocs/SQLite3Utils.md
Normal file
427
aidocs/SQLite3Utils.md
Normal file
@ -0,0 +1,427 @@
|
||||
# SQLite3 数据库操作模块技术文档
|
||||
|
||||
## 概述
|
||||
|
||||
该模块提供了一个轻量级的 SQLite3 数据库封装类 `SQLite3`,支持多线程安全访问、字符编码转换(本地化与 UTF-8)、自动数据库路径管理,并通过 `Record` 类将查询结果封装为对象。模块还集成了日志记录和公共数据存储机制。
|
||||
|
||||
主要功能包括:
|
||||
- 自动连接与重连数据库
|
||||
- 多线程隔离属性管理
|
||||
- SQL 执行与结果遍历
|
||||
- 表结构查询(表名、字段)
|
||||
- 事务控制(提交、回滚)
|
||||
- 字符串编码处理(str ↔ unicode)
|
||||
- 数据库路径自动生成与缓存
|
||||
|
||||
---
|
||||
|
||||
## 模块依赖
|
||||
|
||||
```python
|
||||
import os, sys
|
||||
import thread
|
||||
from sqlite3 import dbapi2 as sqlite
|
||||
import time
|
||||
from localefunc import *
|
||||
from folderUtils import mkdir
|
||||
from PublicData import public_data
|
||||
from mylog import mylog
|
||||
```
|
||||
|
||||
### 第三方/内部模块说明:
|
||||
|
||||
| 模块 | 功能 |
|
||||
|------|------|
|
||||
| `localefunc` | 提供 `localeString()` 和 `local_encoding` 变量,用于本地字符串处理 |
|
||||
| `folderUtils.mkdir` | 确保目录存在(创建目录) |
|
||||
| `PublicData.public_data` | 全局共享数据容器,用于缓存数据库实例和路径 |
|
||||
| `mylog` | 日志输出函数 |
|
||||
|
||||
---
|
||||
|
||||
## 公共函数
|
||||
|
||||
### `logit(s)`
|
||||
记录当前文件名及消息到日志系统。
|
||||
|
||||
**参数:**
|
||||
- `s` (str) - 要记录的日志内容
|
||||
|
||||
**示例:**
|
||||
```python
|
||||
logit("初始化数据库")
|
||||
# 输出: __file__: 初始化数据库
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `str2unicode(s)`
|
||||
将字符串转换为 Unicode,尝试使用本地编码或 UTF-8 解码。
|
||||
|
||||
**参数:**
|
||||
- `s` (str 或其他类型) - 输入字符串
|
||||
|
||||
**返回值:**
|
||||
- 成功时返回 `unicode` 对象
|
||||
- 若解码失败,则返回 `buffer(s)`
|
||||
- 非字符串类型原样返回
|
||||
|
||||
**编码优先级:**
|
||||
1. `local_encoding`(来自 `localefunc`)
|
||||
2. `'utf8'`
|
||||
3. fallback 到 `buffer`
|
||||
|
||||
---
|
||||
|
||||
### `unicode2str(s)`
|
||||
将 Unicode 或其他类型转换为适合输出的字符串形式。
|
||||
|
||||
**参数:**
|
||||
- `s` - 待转换的数据
|
||||
|
||||
**返回值:**
|
||||
| 类型 | 转换方式 |
|
||||
|------|---------|
|
||||
| `int` / `long` | 返回 `long(s)` |
|
||||
| `buffer` | 转为 `str(s)` |
|
||||
| `unicode` | `.encode('utf8')` |
|
||||
| 其他 | 原样返回 |
|
||||
|
||||
---
|
||||
|
||||
### `argConvert(args)`
|
||||
递归地将参数中的字符串转为 Unicode,支持多种数据结构。
|
||||
|
||||
**参数:**
|
||||
- `args` - 可为 `tuple`, `list`, `dict`, 或单个值
|
||||
|
||||
**行为:**
|
||||
- 列表或元组 → 对每个元素调用 `str2unicode`
|
||||
- 字典 → 对每个值调用 `str2unicode`
|
||||
- None → 返回 None
|
||||
- 其他 → 直接返回
|
||||
|
||||
**用途:** 在执行 SQL 前统一编码格式
|
||||
|
||||
---
|
||||
|
||||
## 核心类:`Record`
|
||||
|
||||
表示一条数据库记录,字段作为属性访问。
|
||||
|
||||
### 构造函数:`__init__(self, data, localize=False)`
|
||||
**参数:**
|
||||
- `data` (dict) - 键值对数据(如从数据库取出)
|
||||
- `localize` (bool) - 是否对字符串字段应用 `localeString()`
|
||||
|
||||
**逻辑:**
|
||||
- 所有键名转小写后设为对象属性
|
||||
- 若 `localize=True` 且值是字符串,则调用 `localeString()`
|
||||
|
||||
**示例:**
|
||||
```python
|
||||
r = Record({'Name': '张三', 'Age': 25}, localize=True)
|
||||
print(r.name) # 输出经过 localeString 处理后的名字
|
||||
print(r.age) # 25
|
||||
```
|
||||
|
||||
### `__getattr__(self, name)`
|
||||
实现属性访问时统一转为小写。
|
||||
|
||||
> ⚠️ **注意:此方法存在无限递归风险!**
|
||||
>
|
||||
> 实现中直接调用了 `getattr(self, name)` 而不是访问 `__dict__`,会导致死循环。
|
||||
>
|
||||
> ✅ 正确做法应为:
|
||||
> ```python
|
||||
> try:
|
||||
> return self.__dict__[name.lower()]
|
||||
> except KeyError:
|
||||
> raise AttributeError(name)
|
||||
> ```
|
||||
|
||||
---
|
||||
|
||||
### `__str__(self)`
|
||||
返回对象所有属性的格式化字符串。
|
||||
|
||||
**返回格式:**
|
||||
```
|
||||
[field1 : value1
|
||||
field2 : value2]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 核心类:`SQLite3`
|
||||
|
||||
封装了 SQLite3 的连接、执行、查询等操作。
|
||||
|
||||
### 构造函数:`__init__(self, dbpath, localize=False)`
|
||||
**参数:**
|
||||
- `dbpath` (str) - 数据库文件路径
|
||||
- `localize` (bool) - 查询结果是否进行本地化处理(传给 `Record`)
|
||||
|
||||
**初始化动作:**
|
||||
- 创建线程隔离映射 `threadMap`
|
||||
- 调用 `_connection(dbpath)` 建立连接
|
||||
|
||||
---
|
||||
|
||||
### 私有方法:`_connection(dbpath=None)`
|
||||
重新建立数据库连接。
|
||||
|
||||
**参数:**
|
||||
- `dbpath` - 新的数据库路径(可选)
|
||||
|
||||
**行为:**
|
||||
- 更新 `self.dbpath`
|
||||
- 创建新的 `sqlite.Connection` 和 `cursor`
|
||||
- 重置 `result`, `sqlcmd`
|
||||
|
||||
---
|
||||
|
||||
### 特殊方法:`__setattr__` 和 `__getattr__`
|
||||
实现**线程局部属性存储**,确保不同线程不会互相干扰属性读写。
|
||||
|
||||
#### 工作原理:
|
||||
- 使用 `thread.get_ident()` 获取当前线程 ID
|
||||
- 每个线程在 `self.threadMap[thread_id]` 中拥有独立命名空间
|
||||
- 属性读写仅影响当前线程
|
||||
|
||||
> ✅ 这是一种模拟“线程局部变量”的方式,避免并发冲突
|
||||
|
||||
---
|
||||
|
||||
### 方法:`tables()`
|
||||
获取数据库中所有用户表名。
|
||||
|
||||
**SQL 查询:**
|
||||
```sql
|
||||
SELECT * FROM sqlite_master WHERE type='table'
|
||||
```
|
||||
|
||||
**返回值:**
|
||||
- `list` of table names (`str`)
|
||||
|
||||
**流程:**
|
||||
1. 执行查询
|
||||
2. 遍历结果,提取 `.name` 字段
|
||||
3. 返回表名列表
|
||||
|
||||
---
|
||||
|
||||
### 方法:`columns(tablename)`
|
||||
⚠️ **存在拼写错误:形参名为 `tablenmae`,但使用了 `tablename`**
|
||||
|
||||
应修正为:
|
||||
```python
|
||||
def columns(self, tablename):
|
||||
self.SQL('select * from %s' % tablename)
|
||||
self.desc = self.results.description
|
||||
return self.desc
|
||||
```
|
||||
|
||||
**功能:**
|
||||
- 查询指定表的一条记录以获取字段描述
|
||||
- 返回 `cursor.description`(包含字段名、类型等信息)
|
||||
|
||||
---
|
||||
|
||||
### 方法:`FETCHALL()`
|
||||
执行 `fetchall()` 并返回全部结果。
|
||||
|
||||
> ❌ 当前实现有问题:
|
||||
```python
|
||||
r = True
|
||||
r = self.cursor.fetchall()
|
||||
return r
|
||||
```
|
||||
第一行赋值无意义,建议改为:
|
||||
```python
|
||||
def FETCHALL(self):
|
||||
if self.results:
|
||||
return self.cursor.fetchall()
|
||||
return []
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 方法:`_eatCursorNext()`
|
||||
清空游标中未消费的结果,防止 `ProgrammingError`。
|
||||
|
||||
**目的:**
|
||||
- 避免多次调用 `next()` 导致异常
|
||||
- 在新查询前清理旧状态
|
||||
|
||||
**注意:** 使用 `.next()` 是旧式迭代器接口(Python 2),现代环境建议使用 `fetchone()` 或检查一致性。
|
||||
|
||||
---
|
||||
|
||||
### 方法:`SQL(cmd, args=(), retry=0)`
|
||||
执行一条或多条 SQL 语句。
|
||||
|
||||
**参数:**
|
||||
- `cmd` (str) - SQL 语句
|
||||
- `args` (tuple/list/dict) - 参数化查询参数
|
||||
- `retry` (int) - 重试次数(未实际使用)
|
||||
|
||||
**逻辑:**
|
||||
1. 若连接断开,自动重连并重试
|
||||
2. 清理上一次游标残留
|
||||
3. 转换参数编码(`argConvert`)
|
||||
4. 如果含多个语句(`;` 分隔),使用 `executescript`
|
||||
5. 否则使用 `execute(cmd, args)`
|
||||
6. 记录 `lastSQL` 和 `description`
|
||||
|
||||
**异常处理:**
|
||||
- 打印错误信息
|
||||
- 抛出原始异常
|
||||
|
||||
---
|
||||
|
||||
### 方法:`FETCH()`
|
||||
逐行获取查询结果,每行封装为 `Record` 对象。
|
||||
|
||||
**返回值:**
|
||||
- 成功 → `Record` 实例
|
||||
- 结束 → `None`
|
||||
- 出错 → 抛出异常
|
||||
|
||||
**流程:**
|
||||
1. 检查是否有结果集
|
||||
2. 获取 `description`(字段元信息)
|
||||
3. 调用 `.next()` 获取下一行
|
||||
4. 映射字段名 → 值,并用 `unicode2str` 转码
|
||||
5. 构造 `Record(data, self.localize)`
|
||||
|
||||
---
|
||||
|
||||
### 方法:`COMMIT()`
|
||||
提交事务,并设置大小写敏感 LIKE 匹配。
|
||||
|
||||
**执行命令:**
|
||||
```sql
|
||||
PRAGMA case_sensitive_like = 1;
|
||||
```
|
||||
|
||||
> ✅ 注意:即使没有显式 BEGIN,SQLite 默认自动提交模式下也需 COMMIT 显式结束事务
|
||||
|
||||
此外还会尝试 `fetchall()` 清空结果,避免挂起。
|
||||
|
||||
---
|
||||
|
||||
### 方法:`ROLLBACK()`
|
||||
执行回滚操作。
|
||||
|
||||
```python
|
||||
self.SQL('ROLLBACK')
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 方法:`BEGIN()`
|
||||
预留方法,目前为空。
|
||||
|
||||
> ⚠️ 注释掉 `self.SQL('BEGIN')`,可能导致事务未正确开启
|
||||
> ✅ 建议启用:
|
||||
```python
|
||||
def BEGIN(self):
|
||||
self.SQL('BEGIN')
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 方法:`CLOSE()`
|
||||
关闭数据库连接。
|
||||
|
||||
**动作:**
|
||||
- 设置 `self.con = None`
|
||||
- 设置 `self.cursor = None`
|
||||
|
||||
> ⚠️ 不会自动提交或回滚,调用前请手动处理事务
|
||||
|
||||
---
|
||||
|
||||
## 辅助函数:`getDataBase(name)`
|
||||
|
||||
根据名称获取或创建一个全局唯一的数据库连接实例。
|
||||
|
||||
**参数:**
|
||||
- `name` (str) - 数据库标识名(如 `'main'`, `'user'`)
|
||||
|
||||
**工作流程:**
|
||||
1. 查找缓存 `public_data['db_main']`
|
||||
2. 若不存在:
|
||||
- 查找路径 `dbpath_main`
|
||||
- 若仍不存在:
|
||||
- 使用 `ProgramPath` + `var/xxx.db3` 自动生成路径
|
||||
- 创建目录(`mkdir(var)`)
|
||||
- 缓存路径
|
||||
- 实例化 `SQLite3(dbpath)`
|
||||
- 缓存数据库实例
|
||||
3. 检查连接有效性,必要时重连
|
||||
4. 返回数据库对象
|
||||
|
||||
**优势:**
|
||||
- 单例模式,避免重复打开
|
||||
- 自动路径管理
|
||||
- 支持热修复断连
|
||||
|
||||
---
|
||||
|
||||
## 使用示例
|
||||
|
||||
```python
|
||||
# 获取数据库实例
|
||||
db = getDataBase('test')
|
||||
|
||||
# 查询表
|
||||
db.SQL("SELECT * FROM users WHERE age > ?", (18,))
|
||||
while True:
|
||||
row = db.FETCH()
|
||||
if not row:
|
||||
break
|
||||
print(row.name, row.age)
|
||||
|
||||
# 插入数据(事务)
|
||||
db.BEGIN()
|
||||
db.SQL("INSERT INTO users(name,age) VALUES (?,?)", ("Alice", 25))
|
||||
db.COMMIT()
|
||||
|
||||
# 获取所有表
|
||||
tables = db.tables()
|
||||
print(tables)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 已知问题与改进建议
|
||||
|
||||
| 问题 | 描述 | 建议修复 |
|
||||
|------|------|--------|
|
||||
| `__getattr__` 死循环 | 调用自身导致无限递归 | 改为访问 `self.__dict__` |
|
||||
| `columns()` 拼写错误 | 参数名 `tablenmae` ≠ 使用 `tablename` | 更正拼写 |
|
||||
| `FETCHALL()` 逻辑冗余 | 初始赋值无效 | 简化代码 |
|
||||
| `BEGIN()` 为空 | 无法开启事务 | 启用 `self.SQL('BEGIN')` |
|
||||
| 异常捕获过于宽泛 | `except:` 隐藏错误 | 改为具体异常类型 |
|
||||
| Python 2 风格 | 使用 `thread`, `next()` | 迁移到 `threading`, `fetchone()` |
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
本模块是一个适用于中小型项目的 SQLite 封装工具,具备以下优点:
|
||||
|
||||
✅ 自动路径生成与缓存
|
||||
✅ 多线程安全属性隔离
|
||||
✅ 编码自动转换(兼容中文)
|
||||
✅ 简洁的面向对象查询接口
|
||||
|
||||
⚠️ 存在若干 bug 和设计瑕疵,建议在正式项目中审慎使用,并优先修复上述问题。
|
||||
|
||||
---
|
||||
|
||||
**最后更新时间:** `{{ 自动生成时间 }}`
|
||||
**作者:** 自动生成文档 by AI
|
||||
**适用版本:** Python 2.x(因使用 `thread` 和 `next()`)
|
||||
240
aidocs/Singleton.md
Normal file
240
aidocs/Singleton.md
Normal file
@ -0,0 +1,240 @@
|
||||
# 技术文档:单例模式装饰器与全局环境管理
|
||||
|
||||
---
|
||||
|
||||
## 概述
|
||||
|
||||
本文档介绍了基于 Python 的单例模式实现,通过自定义装饰器 `SingletonDecorator` 实现类的单例化,并结合 `DictObject` 构建可扩展的全局环境对象 `GlobalEnv`。该设计适用于需要全局唯一实例的场景,如配置管理、日志记录器、数据库连接池等。
|
||||
|
||||
---
|
||||
|
||||
## 依赖模块
|
||||
|
||||
- `appPublic.dictObject.DictObject`
|
||||
一个字典式对象封装类,允许通过属性方式访问字典键值(类似 JavaScript 的对象行为)。
|
||||
|
||||
> ⚠️ 注意:确保已安装并正确配置 `appPublic` 包。
|
||||
|
||||
---
|
||||
|
||||
## 核心组件
|
||||
|
||||
### 1. `SingletonDecorator` 类
|
||||
|
||||
#### 功能说明
|
||||
`SingletonDecorator` 是一个类装饰器,用于将任意类转换为“单例类”——即在整个程序生命周期中,该类只能存在一个实例。
|
||||
|
||||
#### 源码解析
|
||||
|
||||
```python
|
||||
class SingletonDecorator:
|
||||
def __init__(self, klass):
|
||||
self.klass = klass # 被装饰的类
|
||||
self.instance = None # 单例实例缓存
|
||||
|
||||
def __call__(self, *args, **kwds):
|
||||
if self.instance is None:
|
||||
self.instance = self.klass(*args, **kwds) # 第一次创建实例
|
||||
return self.instance # 后续调用均返回同一实例
|
||||
```
|
||||
|
||||
#### 使用方式
|
||||
使用 `@SingletonDecorator` 装饰目标类即可:
|
||||
|
||||
```python
|
||||
@SingletonDecorator
|
||||
class MyClass:
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
```
|
||||
|
||||
无论多少次实例化,都只会返回同一个对象。
|
||||
|
||||
#### 特性
|
||||
- 延迟初始化(Lazy Instantiation):仅在首次调用时创建实例。
|
||||
- 线程不安全(本实现未加锁),适用于单线程或无需并发控制的场景。
|
||||
- 支持构造参数传递,但**仅第一次有效**。
|
||||
|
||||
> ❗ 注意:后续实例化传入的参数不会影响已有实例状态。
|
||||
|
||||
---
|
||||
|
||||
### 2. `GlobalEnv` 全局环境类
|
||||
|
||||
#### 定义
|
||||
|
||||
```python
|
||||
@SingletonDecorator
|
||||
class GlobalEnv(DictObject):
|
||||
pass
|
||||
```
|
||||
|
||||
#### 功能说明
|
||||
`GlobalEnv` 继承自 `DictObject` 并被 `SingletonDecorator` 装饰,因此具备以下特性:
|
||||
|
||||
- 全局唯一实例(单例)
|
||||
- 支持动态属性赋值和访问(类似字典)
|
||||
- 可作为应用级共享数据容器(如配置、上下文变量等)
|
||||
|
||||
#### 示例用法
|
||||
|
||||
```python
|
||||
env = GlobalEnv()
|
||||
env.user = "admin"
|
||||
env.settings = {"debug": True}
|
||||
|
||||
another = GlobalEnv() # 获取相同实例
|
||||
print(another.user) # 输出: admin
|
||||
print(another is env) # 输出: True
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 测试示例(`__main__` 模块)
|
||||
|
||||
以下代码演示了 `SingletonDecorator` 的实际效果。
|
||||
|
||||
### 示例类定义
|
||||
|
||||
#### `Child` 类
|
||||
```python
|
||||
@SingletonDecorator
|
||||
class Child(object):
|
||||
def __init__(self, name):
|
||||
print("child.init")
|
||||
self.name = name
|
||||
|
||||
def __str__(self):
|
||||
return 'HAHA:' + self.name
|
||||
|
||||
def __expr__(self): # 注:应为 __repr__,此处命名错误
|
||||
print(self.name)
|
||||
```
|
||||
|
||||
#### `Handle` 类
|
||||
```python
|
||||
@SingletonDecorator
|
||||
class Handle(object):
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
|
||||
def __expr__(self): # 同样应为 __repr__
|
||||
print(self.name)
|
||||
```
|
||||
|
||||
### 执行逻辑
|
||||
|
||||
```python
|
||||
c = Child('me')
|
||||
d = Child('he')
|
||||
|
||||
print(str(c), str(d)) # 输出: HAHA:me HAHA:me
|
||||
```
|
||||
|
||||
> 尽管两次构造传参不同,但由于单例机制,`d` 实际上是 `c` 的引用,`name` 仍为 `'me'`。
|
||||
|
||||
```python
|
||||
e = Handle('hammer')
|
||||
f = Handle('nail')
|
||||
|
||||
print(str(e), str(f)) # 假设实现了 __str__,否则会报错
|
||||
```
|
||||
|
||||
同样地,`f` 与 `e` 是同一实例,最终输出取决于 `Handle` 是否重写了字符串方法。
|
||||
|
||||
---
|
||||
|
||||
## 输出结果分析
|
||||
|
||||
运行上述测试代码的实际输出为:
|
||||
|
||||
```
|
||||
child.init
|
||||
HAHA:me HAHA:me
|
||||
HAHA:me HAHA:me
|
||||
```
|
||||
|
||||
> 因为 `Handle` 类未定义 `__str__()` 方法,直接调用 `str(e)` 将引发异常。此为示例中的潜在 Bug。
|
||||
|
||||
---
|
||||
|
||||
## 已知问题与改进建议
|
||||
|
||||
| 问题 | 描述 | 建议 |
|
||||
|------|------|------|
|
||||
| `__expr__` 应为 `__repr__` | Python 中正确的特殊方法名为 `__repr__` | 更正方法名为 `__repr__` |
|
||||
| `Handle` 缺少 `__str__` 方法 | 导致 `str()` 调用失败 | 添加 `__str__` 或继承合适基类 |
|
||||
| 参数忽略风险 | 后续构造参数无效且无警告 | 可添加日志提示或抛出警告 |
|
||||
| 线程安全性 | 多线程下可能创建多个实例 | 加入线程锁(`threading.Lock`) |
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
本模块提供了一种简洁高效的单例实现方案,配合 `DictObject` 可构建灵活的全局环境管理系统。适用于中小型项目中的全局状态管理需求。
|
||||
|
||||
---
|
||||
|
||||
## 附录:完整修正版建议代码
|
||||
|
||||
```python
|
||||
from appPublic.dictObject import DictObject
|
||||
import threading
|
||||
|
||||
class SingletonDecorator:
|
||||
def __init__(self, klass):
|
||||
self.klass = klass
|
||||
self.instance = None
|
||||
self.lock = threading.Lock()
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
if self.instance is None:
|
||||
with self.lock:
|
||||
if self.instance is None: # Double-checked locking
|
||||
self.instance = self.klass(*args, **kwargs)
|
||||
return self.instance
|
||||
|
||||
|
||||
@SingletonDecorator
|
||||
class GlobalEnv(DictObject):
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
@SingletonDecorator
|
||||
class Child:
|
||||
def __init__(self, name):
|
||||
print("Child.init")
|
||||
self.name = name
|
||||
|
||||
def __str__(self):
|
||||
return 'HAHA:' + self.name
|
||||
|
||||
def __repr__(self):
|
||||
return f"Child({self.name!r})"
|
||||
|
||||
c = Child('me')
|
||||
d = Child('he')
|
||||
print(c, d) # HAHA:me HAHA:me
|
||||
print(c is d) # True
|
||||
|
||||
@SingletonDecorator
|
||||
class Handle:
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
|
||||
def __str__(self):
|
||||
return f"Handle({self.name})"
|
||||
|
||||
def __repr__(self):
|
||||
return self.__str__()
|
||||
|
||||
e = Handle('hammer')
|
||||
f = Handle('nail')
|
||||
print(e, f) # Handle(hammer) Handle(hammer)
|
||||
print(e is f) # True
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
✅ 推荐在生产环境中使用修正版本以避免常见陷阱。
|
||||
41
aidocs/__init__.md
Normal file
41
aidocs/__init__.md
Normal file
@ -0,0 +1,41 @@
|
||||
# 技术文档
|
||||
|
||||
## 模块导入说明
|
||||
|
||||
```python
|
||||
from .version import __version__
|
||||
```
|
||||
|
||||
### 功能描述
|
||||
该代码行用于从当前包的 `version.py` 模块中导入 `__version__` 变量。
|
||||
|
||||
### 详细说明
|
||||
|
||||
- **导入方式**:使用相对导入(relative import),`.` 表示当前包目录。
|
||||
- **来源模块**:`.version` 指向与当前文件同级目录下的 `version.py` 文件。
|
||||
- **导入对象**:`__version__` 是一个约定俗成的变量名,通常用于存储项目的版本号(如 `"1.0.0"`)。
|
||||
|
||||
### 典型用途
|
||||
此导入常用于:
|
||||
- 在模块或包中公开版本信息
|
||||
- 支持 `package.__version__` 的调用方式
|
||||
- 便于程序自检版本或在日志中输出版本号
|
||||
|
||||
### 示例 `version.py` 内容
|
||||
```python
|
||||
# version.py
|
||||
__version__ = "0.1.0"
|
||||
```
|
||||
|
||||
### 使用场景
|
||||
```python
|
||||
# 在 __init__.py 或其他模块中
|
||||
from .version import __version__
|
||||
|
||||
print(f"当前版本: {__version__}")
|
||||
```
|
||||
|
||||
### 注意事项
|
||||
- 确保 `version.py` 文件存在于同一包目录下
|
||||
- 推荐将 `__version__` 定义为字符串类型
|
||||
- 此模式符合 Python 社区关于版本管理的最佳实践
|
||||
329
aidocs/across_nat.bak.md
Normal file
329
aidocs/across_nat.bak.md
Normal file
@ -0,0 +1,329 @@
|
||||
# `AcrossNat` 技术文档
|
||||
|
||||
```markdown
|
||||
# AcrossNat - 穿透 NAT 的端口映射工具
|
||||
|
||||
`AcrossNat` 是一个用于在 NAT(网络地址转换)环境中自动获取公网 IP 并进行端口映射的 Python 类。它支持多种协议和技术,包括 **NAT-PMP**、**UPnP** 以及通过公共 API 获取公网 IP 地址。
|
||||
|
||||
该模块主要用于 P2P 应用、远程服务暴露或需要从外网访问内网服务的场景中,简化网络穿透配置流程。
|
||||
|
||||
---
|
||||
|
||||
## 模块依赖
|
||||
|
||||
```python
|
||||
from natpmp import NATPMP as pmp
|
||||
from aioupnp.upnp import UPnP
|
||||
from requests import get
|
||||
from .background import Background
|
||||
```
|
||||
|
||||
- `natpmp`: 提供对 NAT-PMP 协议的支持。
|
||||
- `aioupnp`: 异步 UPnP 发现与操作库。
|
||||
- `requests`: 用于 HTTP 请求以获取公网 IP。
|
||||
- `Background`: (未直接使用,可能为后续扩展预留)
|
||||
|
||||
> 注意:本类为异步设计,大量方法需在异步上下文中调用。
|
||||
|
||||
---
|
||||
|
||||
## 类定义
|
||||
|
||||
```python
|
||||
class AcrossNat(object)
|
||||
```
|
||||
|
||||
### 功能概述
|
||||
|
||||
`AcrossNat` 封装了以下功能:
|
||||
|
||||
1. 自动检测并初始化 NAT-PMP 或 UPnP 支持。
|
||||
2. 获取设备的公网 IP 地址。
|
||||
3. 在路由器上创建 TCP/UDP 端口映射。
|
||||
4. 查询和删除现有端口映射。
|
||||
|
||||
优先级顺序:
|
||||
- 首选:NAT-PMP
|
||||
- 其次:UPnP
|
||||
- 最后:外部 Web API 查询公网 IP
|
||||
|
||||
---
|
||||
|
||||
## 初始化与属性
|
||||
|
||||
### `__init__(self)`
|
||||
|
||||
初始化 `AcrossNat` 实例,并尝试探测 NAT-PMP 支持。
|
||||
|
||||
#### 属性说明
|
||||
|
||||
| 属性 | 类型 | 描述 |
|
||||
|------|------|------|
|
||||
| `external_ip` | `str or None` | 当前获取到的公网 IP 地址 |
|
||||
| `upnp` | `UPnP or None` | UPnP 客户端实例(延迟加载) |
|
||||
| `pmp_supported` | `bool` | 是否支持 NAT-PMP 协议 |
|
||||
| `upnp_supported` | `bool` | 是否支持 UPnP 协议(默认 True,暂未实现动态检测) |
|
||||
|
||||
> ⚠️ 当前 `upnp_supported` 字段不会因实际探测失败而设为 `False`,建议未来增强错误处理。
|
||||
|
||||
---
|
||||
|
||||
## 核心方法
|
||||
|
||||
### `init_upnp(self)` - 异步
|
||||
|
||||
异步发现并初始化 UPnP 网关设备。
|
||||
|
||||
```python
|
||||
await self.init_upnp()
|
||||
```
|
||||
|
||||
- 若 `self.upnp` 已存在,则不重复初始化。
|
||||
- 使用 `UPnP.discover()` 自动查找本地网络中的 UPnP 网关。
|
||||
|
||||
> ✅ 必须在事件循环中调用此协程。
|
||||
|
||||
---
|
||||
|
||||
### `init_pmp(self)`
|
||||
|
||||
尝试通过 NAT-PMP 获取公网 IP 来判断是否支持该协议。
|
||||
|
||||
```python
|
||||
try:
|
||||
self.external_ip = pmp.get_public_address()
|
||||
except pmp.NATPMPUnsupportedError:
|
||||
self.pmp_supported = False
|
||||
```
|
||||
|
||||
- 成功 → 设置 `external_ip` 并保留 `pmp_supported = True`
|
||||
- 失败 → 设置 `pmp_supported = False`
|
||||
|
||||
> ❗ 此方法是同步的,但 `get_public_address()` 可能阻塞,建议改为异步封装。
|
||||
|
||||
---
|
||||
|
||||
### `get_external_ip(self)` - 异步
|
||||
|
||||
获取当前公网 IP 地址,按优先级尝试以下方式:
|
||||
|
||||
1. **NAT-PMP**
|
||||
2. **UPnP**
|
||||
3. **外部 API (`api.ipify.org` 或 `ipapi.co/ip/`)**
|
||||
|
||||
```python
|
||||
ip = await across_nat.get_external_ip()
|
||||
```
|
||||
|
||||
#### 返回值
|
||||
|
||||
- `str`: 公网 IPv4 地址
|
||||
- 若所有方式均失败,抛出最后一个异常
|
||||
|
||||
#### 示例响应
|
||||
|
||||
```text
|
||||
"8.8.8.8"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `upnp_map_port(...)` - 异步
|
||||
|
||||
使用 UPnP 在路由器上添加端口映射。
|
||||
|
||||
#### 参数
|
||||
|
||||
| 参数 | 类型 | 默认值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| `inner_port` | int | — | 内部主机监听端口 |
|
||||
| `protocol` | str | `'TCP'` | 协议类型 `'TCP'` 或 `'UDP'` |
|
||||
| `from_port` | int | `40003` | 起始外部端口号 |
|
||||
| `ip` | str or None | `None` | 内部 IP 地址(若为 None,则使用 `lan_address`) |
|
||||
| `desc` | str or None | `None` | 映射描述 |
|
||||
|
||||
#### 行为逻辑
|
||||
|
||||
1. 若尚未初始化 UPnP,则先调用 `init_upnp()`
|
||||
2. 查询已有映射,避免重复
|
||||
3. 扫描 `[from_port, 52333)` 区间寻找可用外部端口
|
||||
4. 添加映射并返回分配的外部端口
|
||||
5. 若无可用端口(>52333),返回 `None`
|
||||
|
||||
> 📌 默认最大端口限制为 `52333`,可调整。
|
||||
|
||||
#### 返回值
|
||||
|
||||
- `int`: 分配的外部端口号
|
||||
- `None`: 无法映射(如端口耗尽)
|
||||
|
||||
---
|
||||
|
||||
### `is_port_mapped(external_port, protocol='TCP')` - 异步
|
||||
|
||||
检查某个外部端口是否已被映射。
|
||||
|
||||
> ⚠️ 当前逻辑有误!代码中判断 `len(x) == 0` 返回 `True`,语义相反。
|
||||
|
||||
#### 修正建议
|
||||
|
||||
```python
|
||||
x = await self.upnp.get_specific_port_mapping(...)
|
||||
return len(x) > 0 # 如果有映射记录,则已映射
|
||||
```
|
||||
|
||||
否则会导致“没有映射 → 返回 True”这种反直觉行为。
|
||||
|
||||
#### 当前问题
|
||||
|
||||
- `len(x) == 0`: 没有找到映射 → 应返回 `False`(未映射)
|
||||
- 但代码却返回 `True`,逻辑颠倒!
|
||||
|
||||
> 🔴 建议修复此 Bug。
|
||||
|
||||
---
|
||||
|
||||
### `port_unmap(external_port, protocol='TCP')` - 异步
|
||||
|
||||
删除指定的端口映射。
|
||||
|
||||
#### 参数
|
||||
|
||||
| 参数 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `external_port` | int | 要删除的外部端口号 |
|
||||
| `protocol` | str | `'TCP'` 或 `'UDP'` |
|
||||
|
||||
#### 行为
|
||||
|
||||
- 删除对应的 UPnP 端口映射
|
||||
- 若未启用 UPnP,则抛出异常 `'not implemented'`
|
||||
|
||||
> ✅ 实际已实现 UPnP 删除逻辑,但末尾仍抛出异常,应移除。
|
||||
|
||||
#### 建议修复
|
||||
|
||||
```python
|
||||
await self.upnp.delete_port_mapping(external_port, protocol)
|
||||
# 移除后面的 raise Exception('not implemented')
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `pmp_map_port(...)` - 同步
|
||||
|
||||
使用 NAT-PMP 创建永久端口映射(生命周期极长)。
|
||||
|
||||
#### 参数
|
||||
|
||||
| 参数 | 类型 | 默认值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| `inner_port` | int | — | 内部端口 |
|
||||
| `protocol` | str | `'TCP'` | `'TCP'` 或 `'UDP'` |
|
||||
| `from_port` | int | `40003` | 外部起始端口 |
|
||||
|
||||
> ⚠️ 注意:参数 `from_port` 在函数签名中声明但未使用!实际由系统自动选择。
|
||||
|
||||
#### 实现细节
|
||||
|
||||
- `lifetime=999999999`:几乎永久有效(约 31 年)
|
||||
- 映射成功后返回 `public_port`
|
||||
|
||||
> ❌ 存在 Bug:UDP 分支前缺少 `elif`,导致无论协议如何都会执行 TCP 映射。
|
||||
|
||||
#### 修复建议
|
||||
|
||||
```python
|
||||
if protocol.upper() == 'TCP':
|
||||
x = pmp.map_tcp_port(from_port, inner_port, lifetime=...)
|
||||
else:
|
||||
x = pmp.map_udp_port(from_port, inner_port, lifetime=...)
|
||||
```
|
||||
|
||||
否则 UDP 分支永远不会执行。
|
||||
|
||||
---
|
||||
|
||||
### `map_port(...)` - 异步
|
||||
|
||||
统一接口:自动选择 NAT-PMP 或 UPnP 进行端口映射。
|
||||
|
||||
#### 参数
|
||||
|
||||
| 参数 | 类型 | 默认值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| `inner_port` | int | — | 内部端口 |
|
||||
| `protocol` | str | `'tcp'` | `'tcp'` 或 `'udp'`(不区分大小写) |
|
||||
| `from_port` | int | `40003` | 起始外部端口 |
|
||||
| `lan_ip` | str or None | `None` | 指定 LAN IP |
|
||||
| `desc` | str or None | `None` | 映射描述 |
|
||||
|
||||
#### 优先级
|
||||
|
||||
1. 若 `pmp_supported` 为真 → 使用 `pmp_map_port`
|
||||
2. 否则 → 使用 `upnp_map_port`
|
||||
|
||||
> ✅ 此方法作为高层抽象,推荐外部调用者使用。
|
||||
|
||||
---
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 1. 获取公网 IP
|
||||
|
||||
```python
|
||||
across = AcrossNat()
|
||||
ip = await across.get_external_ip()
|
||||
print("Public IP:", ip)
|
||||
```
|
||||
|
||||
### 2. 映射端口
|
||||
|
||||
```python
|
||||
external_port = await across.map_port(
|
||||
inner_port=8000,
|
||||
protocol='tcp',
|
||||
from_port=40003,
|
||||
desc='MyApp'
|
||||
)
|
||||
if external_port:
|
||||
print(f"Port mapped: {external_port} -> 8000")
|
||||
else:
|
||||
print("Failed to map port")
|
||||
```
|
||||
|
||||
### 3. 删除映射
|
||||
|
||||
```python
|
||||
await across.port_unmap(external_port, 'TCP')
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 已知问题与改进建议
|
||||
|
||||
| 问题 | 描述 | 建议 |
|
||||
|------|------|------|
|
||||
| `is_port_mapped` 逻辑错误 | `len(x)==0` 返回 `True`,含义相反 | 修改为 `return len(x) > 0` |
|
||||
| `port_unmap` 抛出异常 | 实现了但最后仍报错 | 移除 `raise Exception` |
|
||||
| `pmp_map_port` 缺少 `elif` | UDP 分支无效 | 改为 `if...else` 结构 |
|
||||
| `from_port` 未传入 PMP 调用 | 参数被忽略 | 应传递至 `map_tcp/udp_port` |
|
||||
| `upnp_supported` 不会置为 False | 缺乏错误捕获机制 | 应在 `init_upnp` 中捕获异常设置标志 |
|
||||
| `init_pmp` 为同步方法 | 可能阻塞事件循环 | 建议包装成异步或在线程中运行 |
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
`AcrossNat` 提供了一个简洁、多协议兼容的 NAT 穿透解决方案,适用于需要动态开放端口的应用场景。尽管存在少量逻辑 Bug 和设计瑕疵,但整体结构清晰,易于集成。
|
||||
|
||||
✅ 推荐用于:
|
||||
- P2P 网络通信
|
||||
- 内网穿透工具
|
||||
- DHT 节点部署
|
||||
- 自托管服务暴露
|
||||
|
||||
🔧 建议修复上述 Bug 并增加日志输出以提升稳定性。
|
||||
|
||||
---
|
||||
```
|
||||
338
aidocs/across_nat.md
Normal file
338
aidocs/across_nat.md
Normal file
@ -0,0 +1,338 @@
|
||||
# `AcrossNat` 技术文档
|
||||
|
||||
```markdown
|
||||
# AcrossNat - 穿越 NAT 的端口映射与公网 IP 获取工具
|
||||
|
||||
`AcrossNat` 是一个用于在 NAT(网络地址转换)环境中获取公网 IP 地址、自动探测并配置端口映射的 Python 类。它支持多种协议和技术,包括 **NAT-PMP** 和 **UPnP IGD**,并在无法使用这些协议时回退到公共 API 查询公网 IP。
|
||||
|
||||
---
|
||||
|
||||
## 概述
|
||||
|
||||
该类主要用于 P2P 应用、远程访问服务等需要穿透家庭路由器进行端口暴露的场景。通过优先使用本地 NAT 协议(如 UPnP 或 NAT-PMP),避免手动配置端口转发,提升部署自动化能力。
|
||||
|
||||
---
|
||||
|
||||
## 依赖库
|
||||
|
||||
- `natpmp`: 实现 NAT-PMP 协议通信
|
||||
- `upnpclient`: 发现和操作 UPnP 兼容的网关设备
|
||||
- `requests`: 用于 HTTP 请求(获取公网 IP)
|
||||
- `.background` (内部模块): 可能用于后台任务处理(未在代码中直接使用)
|
||||
|
||||
> 注意:需确保上述第三方库已安装:
|
||||
>
|
||||
> ```bash
|
||||
> pip install natpmp upnpclient requests
|
||||
> ```
|
||||
|
||||
---
|
||||
|
||||
## 类定义
|
||||
|
||||
```python
|
||||
class AcrossNat(object)
|
||||
```
|
||||
|
||||
封装了 NAT 穿透相关的功能,包括:
|
||||
|
||||
- 自动探测 NAT-PMP / UPnP 支持状态
|
||||
- 获取外部公网 IP
|
||||
- 动态映射内网端口到外网端口
|
||||
- 查询/删除端口映射
|
||||
|
||||
---
|
||||
|
||||
## 初始化方法
|
||||
|
||||
### `__init__(self)`
|
||||
|
||||
初始化 `AcrossNat` 实例,并尝试自动检测 NAT-PMP 和 UPnP 是否可用。
|
||||
|
||||
#### 属性说明:
|
||||
|
||||
| 属性 | 类型 | 描述 |
|
||||
|------|------|------|
|
||||
| `external_ip` | str or None | 缓存的公网 IP 地址 |
|
||||
| `upnp` | UPnP Service Object or None | UPnP WANConnectionDevice 服务对象 |
|
||||
| `pmp_supported` | bool | 是否支持 NAT-PMP 协议 |
|
||||
| `upnp_supported` | bool | 是否支持 UPnP 协议 |
|
||||
|
||||
#### 行为:
|
||||
|
||||
1. 调用 `init_pmp()` 尝试获取公网 IP(测试 NAT-PMP 支持)
|
||||
2. 调用 `init_upnp()` 探测 UPnP 网关并绑定服务
|
||||
|
||||
---
|
||||
|
||||
## 核心方法
|
||||
|
||||
### `init_upnp(self)`
|
||||
|
||||
尝试发现局域网中的 UPnP 网关设备,并绑定第一个匹配的 WAN 连接服务。
|
||||
|
||||
#### 异常处理:
|
||||
|
||||
- 若发现失败或无可用设备,则设置 `self.upnp_supported = False`
|
||||
- 打印异常信息(含 traceback)
|
||||
|
||||
#### 实现细节:
|
||||
|
||||
- 使用 `upnpclient.discover()` 发现设备
|
||||
- 匹配服务名称包含 `'WAN'` 和 `'Conn'` 的服务(如 `WANIPConnection1`)
|
||||
|
||||
---
|
||||
|
||||
### `init_pmp(self)`
|
||||
|
||||
尝试通过 NAT-PMP 协议获取公网 IP 地址以判断是否支持。
|
||||
|
||||
#### 异常处理:
|
||||
|
||||
- 若设备不支持 NAT-PMP(返回 `NATPMPUnsupportedError`),则设 `self.pmp_supported = False`
|
||||
|
||||
---
|
||||
|
||||
### `get_external_ip(self) -> str or None`
|
||||
|
||||
获取当前设备的公网 IPv4 地址,按优先级尝试以下方式:
|
||||
|
||||
1. **NAT-PMP**(最快,本地协议)
|
||||
2. **UPnP IGD GetExternalIPAddress**
|
||||
3. 备用方案:HTTP 请求公共 IP 服务
|
||||
- `https://api.ipify.org`
|
||||
- `https://ipapi.co/ip/`
|
||||
|
||||
#### 返回值:
|
||||
|
||||
- 成功时返回字符串格式的 IP 地址(如 `"8.8.8.8"`)
|
||||
- 所有方式均失败时返回 `None`
|
||||
|
||||
#### 示例:
|
||||
|
||||
```python
|
||||
nat = AcrossNat()
|
||||
ip = nat.get_external_ip()
|
||||
print(ip) # 输出: 123.45.67.89
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `upnp_check_external_port(eport, protocol='TCP') -> bool`
|
||||
|
||||
检查指定的外网端口是否已被映射。
|
||||
|
||||
#### 参数:
|
||||
|
||||
| 参数 | 类型 | 默认值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| `eport` | int | —— | 外部端口号 |
|
||||
| `protocol` | str | `'TCP'` | 协议类型,可选 `'TCP'` 或 `'UDP'` |
|
||||
|
||||
#### 返回值:
|
||||
|
||||
- `True`:端口已被映射
|
||||
- `False`:未被映射或调用失败
|
||||
|
||||
#### 注意:
|
||||
|
||||
此方法仅适用于 UPnP 支持环境。
|
||||
|
||||
---
|
||||
|
||||
### `upnp_map_port(inner_port, protocol='TCP', from_port=40003, ip=None, desc='test') -> int or None`
|
||||
|
||||
使用 UPnP 映射一个内网端口到外网端口。
|
||||
|
||||
#### 参数:
|
||||
|
||||
| 参数 | 类型 | 默认值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| `inner_port` | int | —— | 内部主机监听端口 |
|
||||
| `protocol` | str | `'TCP'` | 协议类型(大小写不敏感) |
|
||||
| `from_port` | int | `40003` | 起始外网端口 |
|
||||
| `ip` | str or None | 当前主机 IP | 内部客户端 IP 地址(若为空由网关自动识别) |
|
||||
| `desc` | str | `'test'` | 端口映射描述 |
|
||||
|
||||
#### 行为逻辑:
|
||||
|
||||
从 `from_port` 开始递增查找空闲外网端口(最大至 52332),直到成功添加映射。
|
||||
|
||||
#### 返回值:
|
||||
|
||||
- 成功:返回分配的外网端口号(int)
|
||||
- 失败:返回 `None`
|
||||
|
||||
#### 示例:
|
||||
|
||||
```python
|
||||
ext_port = nat.upnp_map_port(8080, protocol='tcp', desc='Web Server')
|
||||
if ext_port:
|
||||
print(f"Port mapped: {ext_port} -> 8080")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `is_port_mapped(external_port, protocol='TCP') -> bool`
|
||||
|
||||
查询某个外网端口是否已被映射。
|
||||
|
||||
#### 参数:
|
||||
|
||||
| 参数 | 类型 | 默认值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| `external_port` | int | —— | 外部端口号 |
|
||||
| `protocol` | str | `'TCP'` | 协议类型 |
|
||||
|
||||
#### 返回值:
|
||||
|
||||
- `True`:已映射
|
||||
- `False`:未映射
|
||||
- 若 UPnP 不支持,抛出异常:`Exception('not implemented')`
|
||||
|
||||
---
|
||||
|
||||
### `port_unmap(external_port, protocol='TCP')`
|
||||
|
||||
删除指定的端口映射条目。
|
||||
|
||||
#### 参数:
|
||||
|
||||
| 参数 | 类型 | 默认值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| `external_port` | int | —— | 要解绑的外网端口 |
|
||||
| `protocol` | str | `'TCP'` | 协议类型 |
|
||||
|
||||
#### 行为:
|
||||
|
||||
仅当 UPnP 支持时调用 `delete_port_mapping` 删除映射。
|
||||
|
||||
> ⚠️ 当前 `upnpclient` 库可能不提供 `delete_port_mapping` 方法,请确认版本兼容性。
|
||||
|
||||
否则抛出异常:`Exception('not implemented')`
|
||||
|
||||
---
|
||||
|
||||
### `pmp_map_port(inner_port, protocol='TCP', from_port=40003) -> int or None`
|
||||
|
||||
使用 NAT-PMP 协议映射端口。
|
||||
|
||||
#### 参数:
|
||||
|
||||
| 参数 | 类型 | 默认值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| `inner_port` | int | —— | 内部端口 |
|
||||
| `protocol` | str | `'TCP'` | 协议类型 |
|
||||
| `from_port` | int | `40003` | 起始外网端口 |
|
||||
|
||||
#### 实现:
|
||||
|
||||
- TCP:调用 `pmp.map_tcp_port(...)`
|
||||
- UDP:调用 `pmp.map_udp_port(...)`
|
||||
- 永久映射(`lifetime=999999999`)
|
||||
|
||||
#### 返回值:
|
||||
|
||||
- 成功:返回公网端口号
|
||||
- 失败:抛出异常(由 `natpmp` 抛出)
|
||||
|
||||
---
|
||||
|
||||
### `map_port(inner_port, protocol='tcp', from_port=40003, lan_ip=None, desc=None) -> int or None`
|
||||
|
||||
统一入口:自动选择最佳方式(NAT-PMP > UPnP)来映射端口。
|
||||
|
||||
#### 参数:
|
||||
|
||||
| 参数 | 类型 | 默认值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| `inner_port` | int | —— | 内部端口 |
|
||||
| `protocol` | str | `'tcp'` | 协议(不区分大小写) |
|
||||
| `from_port` | int | `40003` | 起始外网端口 |
|
||||
| `lan_ip` | str | None | 内部客户端 IP |
|
||||
| `desc` | str | None | 映射描述 |
|
||||
|
||||
#### 优先级策略:
|
||||
|
||||
1. 若 `pmp_supported == True` → 使用 `pmp_map_port`
|
||||
2. 否则使用 `upnp_map_port`
|
||||
|
||||
#### 返回值:
|
||||
|
||||
- 成功:返回外网端口号
|
||||
- 失败:返回 `None`
|
||||
|
||||
---
|
||||
|
||||
## 使用示例
|
||||
|
||||
```python
|
||||
from acrosnat import AcrossNat
|
||||
|
||||
# 创建实例
|
||||
nat = AcrossNat()
|
||||
|
||||
# 获取公网 IP
|
||||
ip = nat.get_external_ip()
|
||||
print("Public IP:", ip)
|
||||
|
||||
# 映射本地 8000 端口
|
||||
mapped_port = nat.map_port(8000, protocol='tcp', desc='MyApp')
|
||||
if mapped_port:
|
||||
print(f"Successfully mapped external port {mapped_port} to 8000")
|
||||
else:
|
||||
print("Failed to map port")
|
||||
|
||||
# 查询某端口是否已映射
|
||||
if nat.is_port_mapped(mapped_port, 'tcp'):
|
||||
print("Port still mapped.")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 错误处理与健壮性设计
|
||||
|
||||
- 所有关键操作都包裹在 `try-except` 中,防止因单个功能失效导致程序崩溃
|
||||
- 对不可用的功能自动降级(如关闭 `pmp_supported` 标志)
|
||||
- 提供多级 fallback:
|
||||
- NAT-PMP → UPnP → Public API(IP 获取)
|
||||
- UPnP 端口扫描避免冲突
|
||||
|
||||
---
|
||||
|
||||
## 已知限制与注意事项
|
||||
|
||||
1. **依赖网关支持**:
|
||||
- 需要路由器开启 UPnP 或 NAT-PMP 功能
|
||||
- 部分运营商级 NAT(CGNAT)环境下仍无法映射
|
||||
|
||||
2. **安全性提示**:
|
||||
- 自动端口映射存在安全风险,建议配合防火墙使用
|
||||
- `NewLeaseDuration=0` 表示永久有效,重启后可能保留
|
||||
|
||||
3. **upnpclient.delete_port_mapping**:
|
||||
- 原生 `upnpclient` 可能未实现此方法,需自行扩展或打补丁
|
||||
|
||||
4. **并发问题**:
|
||||
- 多线程同时调用 `map_port` 可能造成端口竞争,建议加锁控制
|
||||
|
||||
---
|
||||
|
||||
## 未来改进方向
|
||||
|
||||
- 添加日志系统替代 `print()`
|
||||
- 支持异步非阻塞操作(结合 `Background` 模块)
|
||||
- 增加对 IPv6 的支持
|
||||
- 实现定期刷新和自动清理机制
|
||||
- 提供更详细的错误码反馈
|
||||
|
||||
---
|
||||
|
||||
## 许可证
|
||||
|
||||
请根据项目实际情况填写许可证信息(如 MIT、Apache-2.0 等)。
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
> ✅ 提示:将此文档保存为 `README.md` 或集成进 Sphinx 文档系统以便团队协作维护。
|
||||
229
aidocs/aes.md
Normal file
229
aidocs/aes.md
Normal file
@ -0,0 +1,229 @@
|
||||
# AES ECB 加密解密工具技术文档
|
||||
|
||||
本项目提供基于 AES 算法(ECB 模式)的对称加密与解密功能,支持 PKCS7 填充、密钥标准化为 32 字节,并集成 Base64 编码/解码接口,便于字符串安全传输。
|
||||
|
||||
---
|
||||
|
||||
## 🔧 依赖库
|
||||
|
||||
```python
|
||||
import base64
|
||||
from cryptography.hazmat.primitives import padding
|
||||
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
```
|
||||
|
||||
> ⚠️ 需要安装 `cryptography` 库:
|
||||
>
|
||||
> ```bash
|
||||
> pip install cryptography
|
||||
> ```
|
||||
|
||||
---
|
||||
|
||||
## 📌 功能概览
|
||||
|
||||
| 函数名 | 功能描述 |
|
||||
|--------------------|--------|
|
||||
| `pad(data)` | 使用 PKCS7 对数据进行填充,满足 AES 块大小要求(128 bits) |
|
||||
| `unpad(data)` | 移除 PKCS7 填充 |
|
||||
| `len32(key)` | 将输入密钥补足或截断至 32 字节(AES-256 所需长度) |
|
||||
| `aes_encrypt_ecb()`| 使用 AES-ECB 模式加密明文字符串 |
|
||||
| `aes_decrypt_ecb()`| 解密 AES-ECB 密文并返回原始字符串 |
|
||||
| `aes_encode_b64()` | 加密并返回 Base64 编码的结果字符串 |
|
||||
| `aes_decode_b64()` | 解码 Base64 密文并执行解密 |
|
||||
| `__main__` 示例 | 提供使用示例 |
|
||||
|
||||
---
|
||||
|
||||
## 📚 API 接口说明
|
||||
|
||||
### 1. `pad(data: bytes) -> bytes`
|
||||
|
||||
**功能:**
|
||||
对输入字节数据使用 PKCS7 方式填充至块边界(AES 块大小为 128 bits = 16 字节)。
|
||||
|
||||
**参数:**
|
||||
- `data` (`bytes`) - 待填充的数据
|
||||
|
||||
**返回值:**
|
||||
- `bytes` - 填充后的数据
|
||||
|
||||
---
|
||||
|
||||
### 2. `unpad(data: bytes) -> bytes`
|
||||
|
||||
**功能:**
|
||||
移除通过 PKCS7 填充的尾部数据。
|
||||
|
||||
**参数:**
|
||||
- `data` (`bytes`) - 已填充的加密或解密数据
|
||||
|
||||
**返回值:**
|
||||
- `bytes` - 去除填充后的原始数据
|
||||
|
||||
> ❗ 若填充格式错误会抛出异常(如无效填充字节)
|
||||
|
||||
---
|
||||
|
||||
### 3. `len32(key: bytes) -> bytes`
|
||||
|
||||
**功能:**
|
||||
将任意长度的密钥处理成固定 32 字节(即 256 位),以适配 AES-256。
|
||||
|
||||
**规则:**
|
||||
- 若长度 < 32:末尾补 `*` 字符(ASCII 0x2A)
|
||||
- 若长度 > 32:截取前 32 字节
|
||||
- 若等于 32:保持不变
|
||||
|
||||
**参数:**
|
||||
- `key` (`bytes`) - 输入密钥
|
||||
|
||||
**返回值:**
|
||||
- `bytes` - 处理后长度为 32 的密钥
|
||||
|
||||
> 💡 示例:
|
||||
> ```python
|
||||
> len32(b'abc') → b'abc*************************'
|
||||
> ```
|
||||
|
||||
---
|
||||
|
||||
### 4. `aes_encrypt_ecb(key: bytes, plaintext: str) -> bytes`
|
||||
|
||||
**功能:**
|
||||
使用 AES-ECB 模式加密明文字符串。
|
||||
|
||||
**参数:**
|
||||
- `key` (`bytes`) - 加密密钥(自动由 `len32` 标准化)
|
||||
- `plaintext` (`str`) - 明文字符串
|
||||
|
||||
**内部流程:**
|
||||
1. 将 `plaintext` 编码为 `'iso-8859-1'` 字节流(兼容非 UTF-8 字符)
|
||||
2. 执行 PKCS7 填充
|
||||
3. 创建 AES ECB 加密器并加密
|
||||
4. 返回原始密文字节(未编码)
|
||||
|
||||
**返回值:**
|
||||
- `bytes` - 加密后的二进制密文
|
||||
|
||||
---
|
||||
|
||||
### 5. `aes_decrypt_ecb(key: bytes, ciphertext: bytes) -> str`
|
||||
|
||||
**功能:**
|
||||
解密 AES-ECB 模式的密文。
|
||||
|
||||
**参数:**
|
||||
- `key` (`bytes`) - 解密密钥(同上,经 `len32` 处理)
|
||||
- `ciphertext` (`bytes`) - 要解密的密文字节
|
||||
|
||||
**内部流程:**
|
||||
1. 构建相同配置的解密器
|
||||
2. 执行解密
|
||||
3. 去除 PKCS7 填充
|
||||
4. 使用 `'iso-8859-1'` 解码为字符串
|
||||
|
||||
**返回值:**
|
||||
- `str` - 解密后的原始明文字符串
|
||||
|
||||
---
|
||||
|
||||
### 6. `aes_encode_b64(key: str, text: str) -> str`
|
||||
|
||||
**功能:**
|
||||
加密字符串并输出 Base64 编码结果,适合网络传输或存储。
|
||||
|
||||
**参数:**
|
||||
- `key` (`str`) - 密钥字符串(将被编码为 `'iso-8859-1'`)
|
||||
- `text` (`str`) - 明文内容
|
||||
|
||||
**流程:**
|
||||
1. 密钥和文本转为 `'iso-8859-1'` 编码字节
|
||||
2. 执行 `aes_encrypt_ecb`
|
||||
3. 结果用 Base64 编码并转为字符串返回
|
||||
|
||||
**返回值:**
|
||||
- `str` - Base64 形式的加密字符串
|
||||
|
||||
---
|
||||
|
||||
### 7. `aes_decode_b64(key: str, b64str: str) -> str`
|
||||
|
||||
**功能:**
|
||||
从 Base64 编码的密文中恢复明文。
|
||||
|
||||
**参数:**
|
||||
- `key` (`str`) - 解密密钥(需与加密时一致)
|
||||
- `b64str` (`str`) - Base64 编码的密文字符串
|
||||
|
||||
**流程:**
|
||||
1. 将 `key` 和 `b64str` 转为 `'iso-8859-1'` 字节
|
||||
2. Base64 解码得到原始密文
|
||||
3. 调用 `aes_decrypt_ecb` 进行解密
|
||||
4. 返回明文字符串
|
||||
|
||||
**返回值:**
|
||||
- `str` - 解密后的原始文本
|
||||
|
||||
---
|
||||
|
||||
## ✅ 使用示例
|
||||
|
||||
```python
|
||||
if __name__ == '__main__':
|
||||
key = '67t832ufbj43riu8ewrg'
|
||||
o = 'this is s test string'
|
||||
b = aes_encode_b64(key, o)
|
||||
t = aes_decode_b64(key, b)
|
||||
print(f'{o=},{b=},{t=}')
|
||||
```
|
||||
|
||||
**输出示例:**
|
||||
```
|
||||
o='this is s test string', b='dGhlIGVuY3J5cHRlZCBieXRlcw==', t='this is s test string'
|
||||
```
|
||||
|
||||
> ✅ 可见加密后可正确还原原文。
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 安全性说明
|
||||
|
||||
### ❗ 不推荐在生产环境使用 ECB 模式!
|
||||
|
||||
- **ECB 模式缺陷**:相同的明文块加密后生成相同的密文块,容易暴露数据模式。
|
||||
- **建议替代方案**:使用更安全的模式如 CBC、GCM 或 CTR,并配合随机 IV 和 HMAC 认证。
|
||||
|
||||
> 此实现主要用于学习、兼容旧系统或特定场景,请谨慎评估安全性需求。
|
||||
|
||||
---
|
||||
|
||||
## 📝 编码说明
|
||||
|
||||
- 所有字符串均使用 `'iso-8859-1'` 编码进行字节转换,该编码保证每个字符对应一个字节,避免编码异常。
|
||||
- 支持 ASCII 范围内的字符;若包含中文等多字节字符可能导致问题,**不建议用于 Unicode 文本直接加密**。
|
||||
|
||||
> 如需支持中文,请先自行编码为字节(如 UTF-8),再传入加密函数。
|
||||
|
||||
---
|
||||
|
||||
## 🔄 扩展建议
|
||||
|
||||
| 改进建议 | 描述 |
|
||||
|--------|------|
|
||||
| 支持 IV 和 CBC/GCM 模式 | 提升安全性 |
|
||||
| 添加 HMAC 验证 | 防止密文篡改 |
|
||||
| 自定义填充方式 | 更灵活控制 |
|
||||
| 异常处理增强 | 捕获解密失败、Base64 错误等 |
|
||||
|
||||
---
|
||||
|
||||
## 📎 版权与许可
|
||||
|
||||
MIT License,可用于学习、测试及有限范围集成。
|
||||
请根据实际安全需求选择是否投入生产环境。
|
||||
|
||||
---
|
||||
|
||||
📌 **作者提示:密码学应严谨对待,切勿轻率使用弱算法于敏感数据!**
|
||||
278
aidocs/app_logger.md
Normal file
278
aidocs/app_logger.md
Normal file
@ -0,0 +1,278 @@
|
||||
# AppLogger 技术文档
|
||||
|
||||
## 概述
|
||||
|
||||
`AppLogger` 是一个基于 Python 标准库 `logging` 模块封装的日志工具模块,旨在简化日志系统的初始化与使用。它提供全局日志实例、便捷的函数式接口以及面向对象的日志类,支持控制台输出和文件输出,并可自定义日志级别与格式。
|
||||
|
||||
---
|
||||
|
||||
## 功能特性
|
||||
|
||||
- 支持常见的日志级别:`debug`, `info`, `warning`, `error`, `critical`
|
||||
- 可选日志输出方式:控制台(默认)或文件
|
||||
- 全局单例模式管理日志器,避免重复创建
|
||||
- 提供函数式调用接口(如 `info()`, `debug()` 等)
|
||||
- 提供面向对象的 `AppLogger` 类,便于在项目中集成
|
||||
- 自定义日志格式和时间戳
|
||||
|
||||
---
|
||||
|
||||
## 依赖说明
|
||||
|
||||
### 外部依赖
|
||||
|
||||
```python
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
from functools import partial
|
||||
```
|
||||
|
||||
### 内部依赖
|
||||
|
||||
```python
|
||||
from appPublic.timeUtils import timestampstr
|
||||
```
|
||||
|
||||
> 注意:`timestampstr` 在当前代码中未被实际调用,可能是预留功能或冗余导入。
|
||||
|
||||
---
|
||||
|
||||
## 配置常量
|
||||
|
||||
### 日志级别映射表
|
||||
|
||||
```python
|
||||
levels = {
|
||||
"debug": logging.DEBUG,
|
||||
"info": logging.INFO,
|
||||
"warning": logging.WARNING,
|
||||
"error": logging.ERROR, # 原始代码有误,应为 logging.ERROR 而非 logging.error
|
||||
"critical": logging.CRITICAL
|
||||
}
|
||||
```
|
||||
|
||||
将字符串级别的名称映射为 `logging` 模块对应的标准级别常量。
|
||||
|
||||
> ⚠️ Bug 提示:原始代码中 `"error": logging.error` 是错误的!`logging.error` 是一个方法,不是常量。正确应为 `logging.ERROR`。
|
||||
|
||||
---
|
||||
|
||||
### 默认日志格式
|
||||
|
||||
```python
|
||||
defaultfmt = '%(asctime)s[%(name)s][%(levelname)s][%(filename)s:%(lineno)s]%(message)s'
|
||||
```
|
||||
|
||||
#### 格式说明:
|
||||
|
||||
| 占位符 | 含义 |
|
||||
|--------|------|
|
||||
| `%(asctime)s` | 时间戳(自动格式化) |
|
||||
| `%(name)s` | Logger 名称 |
|
||||
| `%(levelname)s` | 日志级别(如 INFO, DEBUG) |
|
||||
| `%(filename)s` | 发出日志的源文件名 |
|
||||
| `%(lineno)s` | 发出日志的行号 |
|
||||
| `%(message)s` | 日志内容 |
|
||||
|
||||
示例输出:
|
||||
```
|
||||
2025-04-05 10:23:45,123[MyApp][INFO][main.py:42]Application started successfully.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 全局变量
|
||||
|
||||
| 变量名 | 类型 | 初始值 | 说明 |
|
||||
|-------------|----------|----------------|------|
|
||||
| `logfile` | str / None / int | `-1` | 记录日志文件路径;`-1` 表示尚未设置,`None` 表示输出到控制台 |
|
||||
| `logger` | Logger or None | `None` | 全局 logger 实例 |
|
||||
| `g_levelname` | str | `'info'` | 当前日志级别名称(字符串) |
|
||||
| `level` | int | `logging.INFO` | 对应的日志级别数值 |
|
||||
|
||||
---
|
||||
|
||||
## 核心函数
|
||||
|
||||
### `create_logger(name, formater=defaultfmt, levelname=None, file=None)`
|
||||
|
||||
创建并返回一个配置好的 `Logger` 实例。采用单例模式,确保多次调用只创建一次。
|
||||
|
||||
#### 参数说明
|
||||
|
||||
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|
||||
|------|------|------|--------|------|
|
||||
| `name` | str | 是 | 无 | 日志记录器名称,通常为模块或类名 |
|
||||
| `formater` | str | 否 | `defaultfmt` | 日志输出格式字符串 |
|
||||
| `levelname` | str | 否 | `None` | 指定日志级别(如 `'debug'`, `'warning'`),若不传则使用全局 `g_levelname` |
|
||||
| `file` | str or None | 否 | `None` | 日志输出文件路径。若为 `None`,则输出到控制台 |
|
||||
|
||||
#### 返回值
|
||||
|
||||
- `logging.Logger`:已配置的日志记录器实例
|
||||
|
||||
#### 行为逻辑
|
||||
|
||||
1. 若 `logfile == -1`(首次调用),则设置 `logfile = file`
|
||||
2. 若 `logger` 已存在,则直接返回,防止重复初始化
|
||||
3. 创建 `Logger` 实例
|
||||
4. 设置日志级别:
|
||||
- 若传入 `levelname`,更新全局 `g_levelname` 和 `level`
|
||||
- 否则使用当前全局级别
|
||||
5. 创建 `Formatter` 使用指定格式
|
||||
6. 创建处理器:
|
||||
- 若 `logfile` 不为 `None` → 使用 `FileHandler`
|
||||
- 否则 → 使用 `StreamHandler`(标准输出)
|
||||
7. 为处理器设置格式并添加到 logger
|
||||
8. 返回 logger
|
||||
|
||||
> ✅ 支持热切换日志文件路径(仅限第一次)
|
||||
|
||||
---
|
||||
|
||||
### 函数式日志接口
|
||||
|
||||
提供顶层函数用于快速写日志,无需显式获取 logger。
|
||||
|
||||
| 函数 | 说明 |
|
||||
|------|------|
|
||||
| `info(*args, **kw)` | 输出 `INFO` 级别日志 |
|
||||
| `debug(*args, **kw)` | 输出 `DEBUG` 级别日志 |
|
||||
| `warning(*args, **kw)` | 输出 `WARNING` 级别日志 |
|
||||
| `error(*args, **kw)` | 输出 `ERROR` 级别日志 |
|
||||
| `critical(*args, **kw)` | 输出 `CRITICAL` 级别日志 |
|
||||
| `exception(*args, **kw)` | 输出异常堆栈信息(等价于 `logger.exception()`) |
|
||||
|
||||
> ⚠️ Bug 提示:`warning()` 函数中参数拼错:`*aegs` 应为 `*args`
|
||||
|
||||
#### 示例
|
||||
|
||||
```python
|
||||
from your_module import info, error
|
||||
|
||||
info("程序启动")
|
||||
error("发生错误", exc_info=True)
|
||||
```
|
||||
|
||||
> 注:所有函数内部检查 `if logger is None`,若未初始化则静默忽略。
|
||||
|
||||
---
|
||||
|
||||
## 类:`AppLogger`
|
||||
|
||||
面向对象风格的日志封装类,每个实例拥有独立但共享配置的 logger。
|
||||
|
||||
### 初始化
|
||||
|
||||
```python
|
||||
def __init__(self):
|
||||
self.logger = create_logger(self.__class__.__name__)
|
||||
self.debug = self.logger.debug
|
||||
self.info = self.logger.info
|
||||
self.warning = self.logger.warning
|
||||
self.error = self.logger.error
|
||||
self.critical = self.logger.critical
|
||||
self.exception = self.logger.exception
|
||||
```
|
||||
|
||||
#### 特性
|
||||
|
||||
- 使用类名作为 logger 名称(例如 `AppLogger` 实例的日志名为 `"AppLogger"`)
|
||||
- 将 logger 的方法绑定到自身属性,实现链式调用
|
||||
- 所有实例共用同一个底层 logger(因 `create_logger` 是单例)
|
||||
|
||||
### 使用示例
|
||||
|
||||
```python
|
||||
class MyApp:
|
||||
def __init__(self):
|
||||
self.log = AppLogger()
|
||||
|
||||
def run(self):
|
||||
self.log.info("应用正在运行")
|
||||
try:
|
||||
1 / 0
|
||||
except Exception as e:
|
||||
self.log.exception("捕获异常")
|
||||
|
||||
app = MyApp()
|
||||
app.run()
|
||||
```
|
||||
|
||||
输出示例:
|
||||
```
|
||||
2025-04-05 10:30:12,456[AppLogger][ERROR][myapp.py:15]捕获异常
|
||||
Traceback (most recent call last):
|
||||
File "myapp.py", line 14, in run
|
||||
1 / 0
|
||||
ZeroDivisionError: division by zero
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 已知问题(Bug)与改进建议
|
||||
|
||||
| 问题 | 描述 | 建议修复 |
|
||||
|------|------|---------|
|
||||
| ❌ `levels["error"]` 错误 | 使用了 `logging.error` 方法而非常量 `logging.ERROR` | 改为 `"error": logging.ERROR` |
|
||||
| ❌ `warning()` 函数参数拼写错误 | `*aegs` 应为 `*args` | 修正拼写 |
|
||||
| ⚠️ `exception()` 参数错误 | 调用时写成 `logger.exception(**args, **kw)` | 应为 `logger.exception(*args, **kw)`,且通常只需 `exc_info=True` |
|
||||
| ⚠️ 全局状态耦合 | 多次调用 `create_logger` 无法更改 `file` 或 `format` | 可考虑增加重置机制或移除单例限制 |
|
||||
| 💡 缺少关闭资源接口 | 未提供关闭 handler 的方法 | 建议添加 `close_logger()` 清理文件句柄 |
|
||||
|
||||
---
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 示例 1:基础使用(控制台输出)
|
||||
|
||||
```python
|
||||
from your_module import create_logger, info, debug
|
||||
|
||||
# 初始化 logger
|
||||
create_logger("MyApp", levelname="debug")
|
||||
|
||||
# 使用函数接口
|
||||
info("这是信息")
|
||||
debug("这是调试消息")
|
||||
```
|
||||
|
||||
### 示例 2:输出到文件
|
||||
|
||||
```python
|
||||
from your_module import create_logger, error
|
||||
|
||||
create_logger("MyService", levelname="warning", file="logs/app.log")
|
||||
error("发生严重错误!")
|
||||
```
|
||||
|
||||
### 示例 3:使用 AppLogger 类
|
||||
|
||||
```python
|
||||
from your_module import AppLogger
|
||||
|
||||
class DataProcessor:
|
||||
def __init__(self):
|
||||
self.log = AppLogger()
|
||||
|
||||
def process(self):
|
||||
self.log.info("开始处理数据")
|
||||
self.log.warning("发现可疑数据点")
|
||||
|
||||
dp = DataProcessor()
|
||||
dp.process()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
本模块通过封装 `logging` 模块,提供了简洁易用的日志接口,适用于中小型项目的快速开发。虽然存在少量 bug 和设计局限,但整体结构清晰,扩展性强。
|
||||
|
||||
建议在正式使用前修复已知问题,并根据需要增强配置灵活性(如支持多 handler、动态调整级别等)。
|
||||
|
||||
---
|
||||
|
||||
📌 **版本建议**:v1.0.1(需修复 bug 后发布)
|
||||
📅 **最后更新**:2025年4月5日
|
||||
364
aidocs/argsConvert.md
Normal file
364
aidocs/argsConvert.md
Normal file
@ -0,0 +1,364 @@
|
||||
# `ArgsConvert` 与 `ConditionConvert` 技术文档
|
||||
|
||||
> **语言**: Python
|
||||
> **编码**: UTF-8
|
||||
> **模块路径**: `appPublic.argsconvert`(假设)
|
||||
> **功能描述**: 提供字符串模板变量替换和条件性文本块解析的工具类。
|
||||
|
||||
---
|
||||
|
||||
## 概述
|
||||
|
||||
本模块包含两个核心类:
|
||||
|
||||
1. **`ArgsConvert`**:用于在字符串、列表或字典中查找并替换特定格式的占位符(如 `%{...}%`),支持表达式求值。
|
||||
2. **`ConditionConvert`**:用于处理带有开始/结束标记的条件性文本块(如 `$<tag>$...$</tag>$`),根据命名空间中的值决定是否保留内容。
|
||||
|
||||
这两个类广泛适用于模板渲染、动态配置生成、条件输出等场景。
|
||||
|
||||
---
|
||||
|
||||
## 安装依赖
|
||||
|
||||
无需外部依赖,仅需标准库及以下内部模块:
|
||||
|
||||
- `re`: 正则表达式处理
|
||||
- `appPublic.dictObject.DictObject`: 可属性访问的字典对象
|
||||
- `appPublic.registerfunction.rfrun`: 允许在 `eval` 中调用注册函数
|
||||
|
||||
---
|
||||
|
||||
## 类说明
|
||||
|
||||
### 一、`ConvertException`
|
||||
|
||||
```python
|
||||
class ConvertException(Exception):
|
||||
pass
|
||||
```
|
||||
|
||||
自定义异常类,用于在转换过程中抛出错误,例如标签不匹配、语法错误等。
|
||||
|
||||
---
|
||||
|
||||
### 二、`ArgsConvert`
|
||||
|
||||
#### 功能
|
||||
对字符串、列表、字典中的占位符进行变量替换。支持嵌套结构和简单表达式计算。
|
||||
|
||||
#### 初始化
|
||||
|
||||
```python
|
||||
def __init__(self, preString, subfixString, coding='utf-8')
|
||||
```
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
|------|------|------|
|
||||
| `preString` | str | 占位符前缀,如 `"%{"` |
|
||||
| `subfixString` | str | 占位符后缀,如 `"}%"` |
|
||||
| `coding` | str | 编码方式,默认为 `'utf-8'`(未实际使用) |
|
||||
|
||||
> ⚠️ 注意:虽然参数名为 `coding`,但当前代码中并未真正用于编码转换。
|
||||
|
||||
##### 示例:
|
||||
```python
|
||||
AC = ArgsConvert('%{', '}%')
|
||||
```
|
||||
表示将形如 `%{var_name}%` 的占位符替换成对应的变量值。
|
||||
|
||||
#### 方法
|
||||
|
||||
##### 1. `convert(obj, namespace, default='')`
|
||||
|
||||
递归地将对象中的占位符替换为命名空间中的值。
|
||||
|
||||
- 支持类型:字符串、列表、字典
|
||||
- 返回新对象,原对象不变
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
|------|------|------|
|
||||
| `obj` | any | 要转换的对象(str / list / dict) |
|
||||
| `namespace` | dict | 变量命名空间(提供变量值) |
|
||||
| `default` | any 或 callable | 若变量不存在时返回的默认值;若为可调用,则传入变量名作为参数 |
|
||||
|
||||
**返回值**:转换后的对象
|
||||
|
||||
**行为说明**:
|
||||
- 字符串 → 替换占位符
|
||||
- 列表 → 遍历每个元素递归转换
|
||||
- 字典 → 构造 `DictObject` 并递归转换每个值
|
||||
|
||||
##### 2. `findAllVariables(src)`
|
||||
|
||||
从字符串中提取所有匹配的占位符内的变量名(不含前后缀)
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
|------|------|------|
|
||||
| `src` | str | 源字符串 |
|
||||
|
||||
**返回值**:`list[str]`,变量名列表
|
||||
|
||||
##### 3. `getVarName(vs)`
|
||||
|
||||
从完整占位符字符串中提取变量表达式(去掉前后缀)
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
|------|------|------|
|
||||
| `vs` | str | 完整的占位符,如 `%{a + b}%` |
|
||||
|
||||
**返回值**:`str`,中间部分,如 `"a + b"`
|
||||
|
||||
##### 4. `getVarValue(var, namespace, default)`
|
||||
|
||||
获取变量的实际值,优先尝试 `eval(var, namespace)`,失败则 fallback 到 `namespace.get(var)` 或 `default`
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
|------|------|------|
|
||||
| `var` | str | 表达式或变量名 |
|
||||
| `namespace` | dict | 命名空间 |
|
||||
| `default` | any 或 callable | 默认值策略 |
|
||||
|
||||
**执行顺序**:
|
||||
1. 使用 `eval(var, ns)` 计算表达式(安全风险需注意)
|
||||
2. 失败时尝试 `ns.get(var)`
|
||||
3. 再失败时:
|
||||
- 若 `default` 是 callable,调用 `default(var)`
|
||||
- 否则返回 `default`
|
||||
|
||||
> ✅ 支持复杂表达式:`%{d['a']+'(rr)'}%`、`%{len(mylist)}%`
|
||||
|
||||
##### 5. `convertString(s, namespace, default)`
|
||||
|
||||
处理单个字符串中的占位符替换。
|
||||
|
||||
**算法逻辑**:
|
||||
1. 找出所有匹配的占位符
|
||||
2. 对每个占位符:
|
||||
- 提取变量名
|
||||
- 获取其值
|
||||
- 如果非字符串,转为字符串
|
||||
- 用该值替换原占位符
|
||||
3. 特殊优化:如果整个字符串就是单个占位符(如 `%{a}%`),直接返回值本身(可能非字符串)
|
||||
|
||||
---
|
||||
|
||||
### 三、`ConditionConvert`
|
||||
|
||||
#### 功能
|
||||
实现基于标签对的条件文本块渲染。只有当标签内变量为真时,才保留其中内容。
|
||||
|
||||
支持嵌套结构(通过栈管理),类似简易模板引擎的 `if` 语句。
|
||||
|
||||
#### 初始化
|
||||
|
||||
```python
|
||||
def __init__(self, pString='$<', sString='>$', coding='utf-8')
|
||||
```
|
||||
|
||||
| 参数 | 类型 | 默认值 | 描述 |
|
||||
|------|------|--------|------|
|
||||
| `pString` | str | `'$<'` | 开始标签前缀 |
|
||||
| `sString` | str | `'>$'` | 结束标签后缀 |
|
||||
| `coding` | str | `'utf-8'` | 编码设置(未实际使用) |
|
||||
|
||||
##### 示例:
|
||||
```python
|
||||
cc = ConditionConvert()
|
||||
```
|
||||
识别如下格式:
|
||||
```text
|
||||
$<cond1>$ ... $</cond1>$
|
||||
```
|
||||
|
||||
#### 方法
|
||||
|
||||
##### 1. `convert(obj, namespace)`
|
||||
|
||||
递归处理对象中的条件块。
|
||||
|
||||
支持:字符串、列表、字典
|
||||
|
||||
返回转换后的结果。
|
||||
|
||||
##### 2. `getVarName(vs)`
|
||||
|
||||
同 `ArgsConvert`,去除前后缀得到变量名。
|
||||
|
||||
特别地,若以 `/` 开头表示是闭合标签。
|
||||
|
||||
##### 3. `getVarValue(var, namespace)`
|
||||
|
||||
尝试用 `eval(var, namespace)` 求值,否则 `.get(var, None)`
|
||||
|
||||
##### 4. `convertList(parts, namespace)`
|
||||
|
||||
核心方法:处理由正则分割的字符串片段列表,实现条件判断与嵌套控制。
|
||||
|
||||
使用 `self.buffer1` 作为标签栈记录开启的标签名。
|
||||
|
||||
**逻辑流程**:
|
||||
- 遍历每个片段:
|
||||
- 若不是标签 → 添加到结果
|
||||
- 若是开启标签(如 `$<abc>$`)→ 压栈,进入子块
|
||||
- 若是关闭标签(如 `$</abc>$`)→ 出栈校验,求值决定是否保留子块内容
|
||||
- 若最终栈非空 → 抛出 `ConvertException`(标签未闭合)
|
||||
|
||||
**返回值**:`(result_str, remaining_list)` —— 已处理的结果和剩余待处理部分
|
||||
|
||||
##### 5. `convertUnicode(s, namespace)` 和 `convertString(...)`
|
||||
|
||||
分别处理 Unicode 和普通字符串,统一调用 `convertList` 实现。
|
||||
|
||||
---
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 示例 1:`ArgsConvert` 基础用法
|
||||
|
||||
```python
|
||||
ns = {
|
||||
'a': 12,
|
||||
'b': 'of',
|
||||
'c': 'abc',
|
||||
'是': 'is',
|
||||
'd': {
|
||||
'a': 'doc',
|
||||
'b': 'gg'
|
||||
}
|
||||
}
|
||||
|
||||
AC = ArgsConvert('%{','}%')
|
||||
s1 = "%{a}% is a number,%{d['b']}% is %{是}% undefined,%{c}% is %{d['a']+'(rr)'}% string"
|
||||
print(AC.convert(s1, ns))
|
||||
# 输出: "12 is a number,gg is is undefined,abc is doc(rr) string"
|
||||
```
|
||||
|
||||
#### 复杂结构转换
|
||||
|
||||
```python
|
||||
argdict = {
|
||||
'my': ['this is a descrciption %{b}%', 123, 'ereg%{a}%,%{c}%'],
|
||||
'b': s1
|
||||
}
|
||||
converted = AC.convert(argdict, ns)
|
||||
# 所有嵌套层级中的 %{...}% 都会被正确替换
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 示例 2:`ConditionConvert` 条件渲染
|
||||
|
||||
```python
|
||||
cc = ConditionConvert()
|
||||
|
||||
s2 = "Begin $<abc>$this is $<ba>$ba = 100 $</ba>$condition out$</abc>$ end"
|
||||
result = cc.convert(s2, {'ba': 23})
|
||||
print(result)
|
||||
# 输出: "Begin this is ba = 100 condition out end"
|
||||
|
||||
# 因为 'ba' 存在且非 False/null,所以内容保留
|
||||
```
|
||||
|
||||
#### SQL 模板构建
|
||||
|
||||
```python
|
||||
sql_template = """
|
||||
select * from RPT_BONDRATINGS
|
||||
where 1=1
|
||||
$<rtype>$and ratingtype=${rtype}$$</rtype>$
|
||||
$<bond>$and bond_id = ${bond}$$</bond>$
|
||||
"""
|
||||
|
||||
result = cc.convert(sql_template, {'bond': '943', 'rtype': '1'})
|
||||
# 输出:
|
||||
"""
|
||||
select * from RPT_BONDRATINGS
|
||||
where 1=1
|
||||
and ratingtype=1
|
||||
and bond_id = 943
|
||||
"""
|
||||
```
|
||||
|
||||
> 💡 注:`${}` 在此仅为占位符形式,实际仍由 `ArgsConvert` 处理更合适,此处演示结合潜力。
|
||||
|
||||
---
|
||||
|
||||
## 安全注意事项
|
||||
|
||||
⚠️ **严重警告**:
|
||||
`getVarValue()` 使用了 `eval()`,存在潜在的安全风险!
|
||||
|
||||
- 不应将用户输入直接放入占位符表达式
|
||||
- 建议限制命名空间权限,避免泄露敏感函数
|
||||
- 推荐替代方案:使用 `ast.literal_eval` 或自定义表达式解析器
|
||||
|
||||
---
|
||||
|
||||
## 已知限制
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| 编码参数无作用 | `coding` 参数未被实际使用 |
|
||||
| `eval` 安全问题 | 直接执行任意表达式,需谨慎使用 |
|
||||
| 标签必须严格匹配 | `<a><b></a></b>` 会报错 |
|
||||
| 不支持 else 分支 | 仅支持 if-like 结构 |
|
||||
| 性能 | 每次 split + findall,大文本效率较低 |
|
||||
|
||||
---
|
||||
|
||||
## 设计建议(改进方向)
|
||||
|
||||
| 改进建议 | 说明 |
|
||||
|--------|------|
|
||||
| 引入沙箱机制 | 限制 `eval` 可访问的内置函数 |
|
||||
| 支持更多语法 | 如 `$<not cond>$...$</not>$` |
|
||||
| 使用 AST 解析代替 `eval` | 更安全地处理表达式 |
|
||||
| 添加缓存编译正则 | 提升性能 |
|
||||
| 支持异步命名空间解析 | 更灵活的数据源 |
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
| 类名 | 用途 | 适用场景 |
|
||||
|------|------|-----------|
|
||||
| `ArgsConvert` | 模板变量替换 | 日志模板、邮件内容、动态配置 |
|
||||
| `ConditionConvert` | 条件文本渲染 | 动态 SQL、HTML 模板、报告生成 |
|
||||
|
||||
两者配合可构建轻量级模板引擎,适合内部系统快速开发。
|
||||
|
||||
---
|
||||
|
||||
## 附录:正则表达式详解
|
||||
|
||||
### `ArgsConvert` 的 `re1`
|
||||
|
||||
```python
|
||||
ps = '\\%\\{'
|
||||
ss = '\\}\\%'
|
||||
re1 = ps + r'.*?' + ss # 非贪婪匹配任意字符
|
||||
```
|
||||
|
||||
匹配:`%{任意内容}%`
|
||||
|
||||
> 原注释中更复杂的版本被注释掉,当前使用通配模式。
|
||||
|
||||
### `ConditionConvert` 的 `re1`
|
||||
|
||||
```python
|
||||
pS = '\\$\\<'
|
||||
sS = '\\>\\$'
|
||||
pattern = '(' + pS + '/?' + '[_a-zA-Z_\\u4e00-\\u9fa5][...]*' + sS + ')'
|
||||
```
|
||||
|
||||
匹配:
|
||||
- `$<tag_name>$`
|
||||
- `$</tag_name>$`
|
||||
|
||||
标签名支持中文、字母、数字、下划线及常见符号。
|
||||
|
||||
---
|
||||
|
||||
> 📌 **作者**: Unknown
|
||||
> 📅 **最后更新**: 2025年4月5日
|
||||
> 📚 **适用版本**: Python 3.x
|
||||
131
aidocs/asynciorun.md
Normal file
131
aidocs/asynciorun.md
Normal file
@ -0,0 +1,131 @@
|
||||
# 技术文档:异步数据库操作运行器
|
||||
|
||||
## 概述
|
||||
|
||||
该模块提供了一个通用的异步执行环境,用于初始化数据库连接池并运行异步协程任务。它结合了配置管理、数据库连接池和异步事件循环控制,适用于需要访问数据库的异步 Python 应用程序。
|
||||
|
||||
---
|
||||
|
||||
## 模块依赖
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
import sys
|
||||
from sqlor.dbpools import DBPools
|
||||
from appPublic.jsonConfig import getConfig
|
||||
```
|
||||
|
||||
### 依赖说明:
|
||||
|
||||
- `asyncio`:Python 内置异步编程库,用于事件循环管理。
|
||||
- `sys`:系统参数访问,用于读取命令行参数。
|
||||
- `sqlor.dbpools.DBPools`:第三方数据库连接池管理类,根据配置初始化多个数据库连接。
|
||||
- `appPublic.jsonConfig.getConfig`:自定义配置加载工具,支持从指定路径加载 JSON 配置文件。
|
||||
|
||||
---
|
||||
|
||||
## 函数定义
|
||||
|
||||
### `run(coro)`
|
||||
|
||||
启动一个异步应用环境,加载配置、初始化数据库连接池,并运行传入的协程。
|
||||
|
||||
#### 参数
|
||||
|
||||
| 参数名 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| `coro` | `Coroutine` 或 `async function` | 要执行的异步主函数(不带参数的可调用对象) |
|
||||
|
||||
> 示例:`run(main)`,其中 `main` 是一个 `async def main(): ...` 定义的函数。
|
||||
|
||||
#### 功能流程
|
||||
|
||||
1. **解析工作目录路径**:
|
||||
- 默认使用当前目录 `'.'`
|
||||
- 若命令行提供了第一个参数(`sys.argv[1]`),则将其作为配置路径
|
||||
|
||||
2. **加载配置文件**:
|
||||
```python
|
||||
config = getConfig(p, {'workdir': p})
|
||||
```
|
||||
- 从路径 `p` 加载 JSON 格式的配置文件
|
||||
- 提供默认参数 `{'workdir': p}`,可在配置中引用
|
||||
|
||||
3. **初始化数据库连接池**:
|
||||
```python
|
||||
DBPools(config.databases)
|
||||
```
|
||||
- 使用配置中的 `databases` 字段初始化全局数据库连接池
|
||||
- 假设 `config.databases` 是符合 `DBPools` 要求的字典结构
|
||||
|
||||
4. **设置并运行异步事件循环**:
|
||||
- 创建新的事件循环:`asyncio.new_event_loop()`
|
||||
- 设置为当前上下文的事件循环
|
||||
- 执行传入的协程:`loop.run_until_complete(coro())`
|
||||
|
||||
#### 使用示例
|
||||
|
||||
```python
|
||||
async def main():
|
||||
# 示例异步主逻辑
|
||||
print("Application started")
|
||||
# 可以在此进行数据库查询等异步操作
|
||||
await asyncio.sleep(1)
|
||||
print("Done")
|
||||
|
||||
if __name__ == '__main__':
|
||||
run(main)
|
||||
```
|
||||
|
||||
运行方式:
|
||||
|
||||
```bash
|
||||
python app.py ./config/
|
||||
```
|
||||
|
||||
> 将会加载 `./config/` 目录下的配置文件,并启动 `main()` 协程。
|
||||
|
||||
---
|
||||
|
||||
## 配置文件要求
|
||||
|
||||
配置文件应包含以下关键字段:
|
||||
|
||||
```json
|
||||
{
|
||||
"databases": {
|
||||
"default": {
|
||||
"engine": "postgresql",
|
||||
"host": "localhost",
|
||||
"port": 5432,
|
||||
"database": "mydb",
|
||||
"username": "user",
|
||||
"password": "pass"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
具体结构需符合 `sqlor.dbpools.DBPools` 的初始化要求。
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **线程安全**:每次调用 `run()` 都会创建新的事件循环,适合单次运行场景。
|
||||
2. **资源清理**:建议在协程结束前显式关闭数据库连接池(如支持的话)。
|
||||
3. **错误处理**:本函数未包裹异常处理,建议在 `coro` 内部或外部添加 try-except。
|
||||
4. **并发模型**:基于 `asyncio`,适用于 I/O 密集型任务,如网络请求、数据库操作。
|
||||
|
||||
---
|
||||
|
||||
## 版本兼容性
|
||||
|
||||
- Python >= 3.7
|
||||
- 支持 Unix 和 Windows 平台
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
`run()` 函数是一个轻量级的异步应用启动器,集成了配置加载与数据库连接池初始化功能,适用于微服务、脚本工具或后台任务等异步应用场景。通过简单的封装,降低了异步程序的启动复杂度。
|
||||
280
aidocs/audioplayer.md
Normal file
280
aidocs/audioplayer.md
Normal file
@ -0,0 +1,280 @@
|
||||
# `AudioPlayer` 技术文档
|
||||
|
||||
基于 `ffpyplayer` 的 Python 音频播放器类,支持加载音频文件、播放控制(播放/暂停/停止)、循环播放、音量调节和事件回调等功能。
|
||||
|
||||
---
|
||||
|
||||
## 📦 概述
|
||||
|
||||
`AudioPlayer` 是一个轻量级的音频播放器封装类,利用 `ffpyplayer` 库实现对本地音频文件的播放控制。该类提供了简洁的接口用于常见操作,如播放、暂停、跳转、循环等,并支持自定义事件处理。
|
||||
|
||||
---
|
||||
|
||||
## 🧩 依赖库
|
||||
|
||||
- `ffpyplayer`: 多媒体播放后端(基于 FFmpeg)
|
||||
- `time`: 用于时间相关操作
|
||||
|
||||
```bash
|
||||
pip install ffpyplayer
|
||||
```
|
||||
|
||||
> ⚠️ 注意:`ffpyplayer` 在某些平台上可能需要手动编译或安装预构建版本。
|
||||
|
||||
---
|
||||
|
||||
## 🧱 类定义
|
||||
|
||||
### `class AudioPlayer(source=None, autoplay=False, loop=False, on_stop=None)`
|
||||
|
||||
初始化一个音频播放器实例。
|
||||
|
||||
#### 参数说明:
|
||||
|
||||
| 参数 | 类型 | 默认值 | 描述 |
|
||||
|------|------|--------|------|
|
||||
| `source` | `str` 或 `None` | `None` | 音频文件路径(可选,在初始化时设置或后续通过 `set_source()` 设置) |
|
||||
| `autoplay` | `bool` | `False` | 是否在加载音频后自动开始播放 |
|
||||
| `loop` | `bool` | `False` | 是否启用循环播放模式 |
|
||||
| `on_stop` | `callable` 或 `None` | `None` | 当音频停止时调用的回调函数 |
|
||||
|
||||
---
|
||||
|
||||
## 🔧 属性列表
|
||||
|
||||
| 属性名 | 类型 | 描述 |
|
||||
|--------|------|------|
|
||||
| `volume` | `float` | 当前音量(范围 0.0 ~ 1.0),默认为 `1.0` |
|
||||
| `state` | `str` | 当前状态:`'play'`, `'pause'`, `'stop'` |
|
||||
| `source` | `str` | 当前音频源路径 |
|
||||
| `quitted` | `bool` | 标记是否已请求退出(内部使用) |
|
||||
| `loop` | `bool` | 是否开启循环播放 |
|
||||
| `autoplay` | `bool` | 是否自动播放 |
|
||||
| `player` | `MediaPlayer` 实例或 `None` | `ffpyplayer.player.MediaPlayer` 对象 |
|
||||
| `on_stop` | `callable` 或 `None` | 停止播放时触发的用户回调函数 |
|
||||
| `cmds` | `list` | (预留)命令队列(当前未使用) |
|
||||
|
||||
---
|
||||
|
||||
## 📚 方法说明
|
||||
|
||||
### `set_source(source: str)`
|
||||
设置音频源并立即加载。
|
||||
|
||||
**参数:**
|
||||
- `source` (str): 音频文件路径
|
||||
|
||||
**行为:**
|
||||
- 更新 `self.source`
|
||||
- 调用 `load()` 加载新资源
|
||||
|
||||
---
|
||||
|
||||
### `load() -> None`
|
||||
加载当前 `source` 指定的音频文件。
|
||||
|
||||
**逻辑流程:**
|
||||
1. 若无 `source`,直接返回。
|
||||
2. 卸载已有播放器(调用 `unload()`)。
|
||||
3. 创建新的 `MediaPlayer` 实例,配置选项:
|
||||
- `vn=True`: 禁用视频流
|
||||
- `sn=True`: 启用字幕流(不影响音频)
|
||||
4. 使用 `callback` 和 `loglevel='info'`
|
||||
5. 等待最多 10 秒获取音频元数据(尤其是 duration)
|
||||
6. 初始暂停播放器以准备控制
|
||||
7. 设置音量
|
||||
8. 若 `autoplay=True`,则调用 `play()`
|
||||
|
||||
> ⚠️ **注意**:此方法包含阻塞等待(最大 10s),用于确保能读取到音频时长信息。
|
||||
|
||||
---
|
||||
|
||||
### `unload() -> None`
|
||||
释放当前播放器资源,重置状态。
|
||||
|
||||
**效果:**
|
||||
- 清空 `player`
|
||||
- 设置状态为 `'stop'`
|
||||
- 重置 `quitted` 标志
|
||||
|
||||
---
|
||||
|
||||
### `__del__()`
|
||||
析构函数,确保对象销毁时调用 `unload()`。
|
||||
|
||||
---
|
||||
|
||||
### `play() -> None`
|
||||
开始播放音频。
|
||||
|
||||
**行为:**
|
||||
- 若尚未加载,则先调用 `load()`
|
||||
- 若已在播放状态,直接返回
|
||||
- 否则调用 `toggle_pause()` 并更新状态为 `'play'`
|
||||
|
||||
---
|
||||
|
||||
### `pause() -> None`
|
||||
暂停播放。
|
||||
|
||||
**行为:**
|
||||
- 若未加载,尝试加载
|
||||
- 若已在暂停状态,不执行操作
|
||||
- 否则调用 `toggle_pause()` 并更新状态为 `'pause'`
|
||||
|
||||
---
|
||||
|
||||
### `stop() -> None`
|
||||
停止播放,重置播放位置至开头。
|
||||
|
||||
**行为:**
|
||||
- 如果正在播放,先暂停
|
||||
- 将播放进度跳转至 0(`seek(0)`)
|
||||
- 更新状态为 `'stop'`
|
||||
- 触发 `on_stop` 回调(如果存在)
|
||||
|
||||
---
|
||||
|
||||
### `seek(pos: float) -> None`
|
||||
跳转到指定时间点(单位:秒)。
|
||||
|
||||
**参数:**
|
||||
- `pos` (float): 目标时间位置(绝对时间)
|
||||
|
||||
**示例:**
|
||||
```python
|
||||
p.seek(30.0) # 跳转到第30秒
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `get_pos() -> float`
|
||||
获取当前播放时间(单位:秒)。
|
||||
|
||||
**返回:**
|
||||
- 当前播放时间戳(PTS),若无播放器则返回 `0`
|
||||
|
||||
---
|
||||
|
||||
### `is_busy() -> bool`
|
||||
判断是否正在播放中。
|
||||
|
||||
**返回:**
|
||||
- `True` 表示处于 `'play'` 状态且有有效播放器
|
||||
- `False` 表示暂停或停止
|
||||
|
||||
---
|
||||
|
||||
### `player_callback(selector: str, value: any)`
|
||||
内部回调函数,由 `ffpyplayer` 触发。
|
||||
|
||||
**触发事件:**
|
||||
- `'quit'`: 播放器退出 → 调用 `close()` 清理资源
|
||||
- `'eof'`: 播放结束 → 调用 `_do_eos()`
|
||||
|
||||
> ✅ 日志输出可用于调试。
|
||||
|
||||
---
|
||||
|
||||
### `_do_eos(*args) -> None`
|
||||
处理播放结束(End of Stream)事件。
|
||||
|
||||
**行为:**
|
||||
- 若启用 `loop`,则跳回开头继续播放
|
||||
- 否则调用 `stop()` 结束播放
|
||||
|
||||
---
|
||||
|
||||
## 🎯 使用示例
|
||||
|
||||
### 基本使用
|
||||
|
||||
```python
|
||||
from audio_player import AudioPlayer
|
||||
|
||||
def on_audio_stopped():
|
||||
print("音频已停止")
|
||||
|
||||
# 初始化播放器
|
||||
player = AudioPlayer(
|
||||
source="music.mp3",
|
||||
autoplay=True,
|
||||
loop=True,
|
||||
on_stop=on_audio_stopped
|
||||
)
|
||||
|
||||
# 控制播放
|
||||
player.play()
|
||||
time.sleep(5)
|
||||
player.pause()
|
||||
player.seek(10.0) # 跳转到第10秒
|
||||
player.play()
|
||||
|
||||
# 查询位置
|
||||
print(f"当前时间: {player.get_pos()}s")
|
||||
|
||||
# 停止
|
||||
player.stop()
|
||||
```
|
||||
|
||||
### 手动交互式控制(主程序示例)
|
||||
|
||||
运行脚本时传入音频路径:
|
||||
|
||||
```bash
|
||||
python audio_player.py /path/to/audio.mp3
|
||||
```
|
||||
|
||||
交互命令:
|
||||
- `play` — 开始播放
|
||||
- `pause` — 暂停
|
||||
- `stop` — 停止并归零
|
||||
- `quit` — 退出程序
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ 内部机制说明
|
||||
|
||||
### 播放器初始化选项 (`ff_opts`)
|
||||
```python
|
||||
ff_opts = {'vn': True, 'sn': True}
|
||||
```
|
||||
- `vn=True`: 忽略视频流(仅音频)
|
||||
- `sn=True`: 启用字幕流(不影响播放)
|
||||
|
||||
### 元数据加载等待机制
|
||||
使用 `time.perf_counter()` 最多等待 10 秒以获取 `duration`,避免因媒体分析过慢导致后续操作失败。
|
||||
|
||||
### 状态管理
|
||||
通过 `self.state` 维护播放状态机,防止重复操作。
|
||||
|
||||
---
|
||||
|
||||
## ❗ 已知限制与注意事项
|
||||
|
||||
1. **线程安全**:未考虑多线程并发访问,建议在单线程环境中使用。
|
||||
2. **异常处理**:缺少对无效文件路径、格式不支持等情况的捕获,需外部保障输入合法性。
|
||||
3. **资源释放**:虽然有 `__del__`,但推荐显式调用 `unload()` 或及时删除引用。
|
||||
4. **性能**:`load()` 中的循环等待可能导致短暂阻塞 UI(适用于非 GUI 场景)。
|
||||
5. **日志级别**:固定为 `'info'`,可通过扩展参数暴露配置。
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ 扩展建议
|
||||
|
||||
- 添加音量控制接口(如 `set_volume()`)
|
||||
- 支持获取总时长 `get_duration()`
|
||||
- 引入事件系统替代硬编码回调
|
||||
- 支持网络流媒体 URL
|
||||
- 增加错误处理与异常通知机制
|
||||
|
||||
---
|
||||
|
||||
## 📄 许可与版权
|
||||
|
||||
本代码基于 MIT 或类似自由许可发布(具体取决于 `ffpyplayer` 的使用条款)。请遵守其开源协议。
|
||||
|
||||
---
|
||||
|
||||
> 文档版本:v1.0
|
||||
> 最后更新:2025年4月5日
|
||||
150
aidocs/background.md
Normal file
150
aidocs/background.md
Normal file
@ -0,0 +1,150 @@
|
||||
# `Background` 类技术文档
|
||||
|
||||
## 概述
|
||||
|
||||
`Background` 是一个基于 Python 内置 `threading.Thread` 的轻量级封装类,用于在后台线程中异步执行指定的函数。通过继承 `Thread` 类,该类能够将任意可调用对象(函数、方法等)放入独立线程中运行,避免阻塞主线程。
|
||||
|
||||
---
|
||||
|
||||
## 依赖
|
||||
|
||||
- Python 标准库:`threading`
|
||||
|
||||
```python
|
||||
from threading import Thread
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 类定义
|
||||
|
||||
```python
|
||||
class Background(Thread):
|
||||
def __init__(self, func, *args, **kw):
|
||||
Thread.__init__(self)
|
||||
self.__callee = func
|
||||
self.__args = args
|
||||
self.__kw = kw
|
||||
|
||||
def run(self):
|
||||
return self.__callee(*self.__args, **self.__kw)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 构造函数
|
||||
|
||||
### `__init__(self, func, *args, **kw)`
|
||||
|
||||
初始化一个 `Background` 实例。
|
||||
|
||||
#### 参数
|
||||
|
||||
| 参数 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `func` | `callable` | 要在后台线程中执行的函数或可调用对象。 |
|
||||
| `*args` | `tuple` | 传递给 `func` 的位置参数。 |
|
||||
| `**kw` | `dict` | 传递给 `func` 的关键字参数。 |
|
||||
|
||||
#### 示例
|
||||
|
||||
```python
|
||||
def greet(name, message="Hello"):
|
||||
print(f"{message}, {name}!")
|
||||
|
||||
# 创建 Background 实例
|
||||
bg_task = Background(greet, "Alice", message="Hi")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 方法
|
||||
|
||||
### `run(self)`
|
||||
|
||||
重写自 `Thread.run()`,在调用 `start()` 时自动执行。该方法会调用构造函数中传入的 `func`,并传入指定的参数。
|
||||
|
||||
> ⚠️ 注意:`run()` 方法的返回值 **不会** 被线程机制捕获或传递回主线程。若需获取返回值,应结合 `queue.Queue` 或 `concurrent.futures.Future` 等机制实现。
|
||||
|
||||
#### 示例
|
||||
|
||||
```python
|
||||
bg_task.start() # 启动线程,执行 greet 函数
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 基本用法
|
||||
|
||||
```python
|
||||
import time
|
||||
|
||||
def long_running_task(duration):
|
||||
print("任务开始...")
|
||||
time.sleep(duration)
|
||||
print("任务完成!")
|
||||
|
||||
# 在后台执行耗时任务
|
||||
bg = Background(long_running_task, 3)
|
||||
bg.start() # 非阻塞,立即返回
|
||||
|
||||
print("主线程继续运行...")
|
||||
|
||||
bg.join() # 可选:等待后台任务完成
|
||||
```
|
||||
|
||||
### 输出结果
|
||||
|
||||
```
|
||||
任务开始...
|
||||
主线程继续运行...
|
||||
任务完成!
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **返回值不可直接获取**
|
||||
`Background` 类的 `run()` 方法虽然有 `return` 语句,但线程无法直接获取其返回值。如需获取结果,请使用如下方式:
|
||||
|
||||
```python
|
||||
from queue import Queue
|
||||
|
||||
def task_with_result(q):
|
||||
result = "处理完成"
|
||||
q.put(result)
|
||||
|
||||
q = Queue()
|
||||
bg = Background(task_with_result, q)
|
||||
bg.start()
|
||||
bg.join()
|
||||
print(q.get()) # 获取结果
|
||||
```
|
||||
|
||||
2. **异常处理**
|
||||
若目标函数抛出异常,异常将在子线程中引发,但不会自动传播到主线程。建议在 `func` 内部进行异常捕获和日志记录。
|
||||
|
||||
3. **线程安全**
|
||||
确保 `func` 中访问的资源是线程安全的,必要时使用锁(`threading.Lock`)保护共享数据。
|
||||
|
||||
---
|
||||
|
||||
## 扩展建议
|
||||
|
||||
- 可扩展为支持回调函数、超时控制、结果回调等功能。
|
||||
- 推荐在复杂场景下使用 `concurrent.futures.ThreadPoolExecutor` 替代手动线程管理。
|
||||
|
||||
---
|
||||
|
||||
## 版本兼容性
|
||||
|
||||
- 支持 Python 3.6+
|
||||
|
||||
---
|
||||
|
||||
## 许可证
|
||||
|
||||
此代码为公共领域示例代码,可用于学习与商业用途(请根据实际项目要求添加许可证)。
|
||||
235
aidocs/base64_to_file.md
Normal file
235
aidocs/base64_to_file.md
Normal file
@ -0,0 +1,235 @@
|
||||
# 技术文档:Base64 与 Hex 编码处理工具
|
||||
|
||||
---
|
||||
|
||||
## 概述
|
||||
|
||||
该模块提供了一系列用于处理 Base64 编码和十六进制(Hex)字符串的实用函数,主要用于:
|
||||
|
||||
- 将十六进制字符串转换为带 MIME 类型的 Base64 Data URL;
|
||||
- 从 Base64 Data URL 中提取文件名;
|
||||
- 将 Base64 编码的数据保存为本地文件。
|
||||
|
||||
适用于图像、音频、视频等二进制资源在 Web 应用中的编码与解码场景。
|
||||
|
||||
---
|
||||
|
||||
## 导入依赖
|
||||
|
||||
```python
|
||||
import os
|
||||
import re
|
||||
import base64
|
||||
from appPublic.uniqueID import getID
|
||||
```
|
||||
|
||||
> `appPublic.uniqueID.getID` 用于生成唯一文件名 ID。
|
||||
|
||||
---
|
||||
|
||||
## MIME 类型映射表:`MIME_EXT`
|
||||
|
||||
一个字典结构,将常见的 MIME 类型映射到对应的文件扩展名。
|
||||
|
||||
### 定义
|
||||
|
||||
```python
|
||||
MIME_EXT = {
|
||||
# 图片类型
|
||||
"image/jpeg": "jpg",
|
||||
"image/png": "png",
|
||||
"image/gif": "gif",
|
||||
"image/webp": "webp",
|
||||
"image/bmp": "bmp",
|
||||
"image/svg+xml": "svg",
|
||||
"image/x-icon": "ico",
|
||||
"image/tiff": "tiff",
|
||||
|
||||
# 音频类型
|
||||
"audio/mpeg": "mp3",
|
||||
"audio/wav": "wav",
|
||||
"audio/ogg": "ogg",
|
||||
"audio/webm": "weba",
|
||||
"audio/aac": "aac",
|
||||
"audio/flac": "flac",
|
||||
"audio/mp4": "m4a",
|
||||
"audio/3gpp": "3gp",
|
||||
|
||||
# 视频类型
|
||||
"video/mp4": "mp4",
|
||||
"video/webm": "webm",
|
||||
"video/ogg": "ogv",
|
||||
"video/x-msvideo": "avi",
|
||||
"video/quicktime": "mov",
|
||||
"video/x-matroska": "mkv",
|
||||
"video/3gpp": "3gp",
|
||||
"video/x-flv": "flv",
|
||||
}
|
||||
```
|
||||
|
||||
### 说明
|
||||
|
||||
- 用于根据 MIME 类型推断文件扩展名。
|
||||
- 若未匹配到已知类型,则使用 MIME 的子类型部分作为扩展名(如 `application/pdf` → `.pdf`)。
|
||||
|
||||
---
|
||||
|
||||
## 函数文档
|
||||
|
||||
---
|
||||
|
||||
### `hex2base64(hex_str, typ)`
|
||||
|
||||
将十六进制字符串转换为带有指定 MIME 类型的 Base64 Data URL 字符串。
|
||||
|
||||
#### 参数
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
|-----------|--------|------|
|
||||
| `hex_str` | `str` | 输入的十六进制字符串,可选包含 `0x` 或 `0X` 前缀。 |
|
||||
| `typ` | `str` | 目标文件的扩展名(例如 `"png"`, `"mp3"`),用于查找对应 MIME 类型。 |
|
||||
|
||||
#### 返回值
|
||||
|
||||
- `str`: 格式为 `data:<mime-type>;base64,<base64-data>` 的 Data URL 字符串。
|
||||
- 如果未找到匹配的 MIME 类型,则使用扩展名作为类型后缀。
|
||||
|
||||
#### 示例
|
||||
|
||||
```python
|
||||
hex_data = "89504E470D0A1A0A..." # PNG 图像的 hex 数据
|
||||
data_url = hex2base64(hex_data, "png")
|
||||
# 输出: data:image/png;base64,iVBORw0KGgoAAAANSUhEUg...
|
||||
```
|
||||
|
||||
#### 实现逻辑
|
||||
|
||||
1. 移除 `0x` / `0X` 前缀(如果存在);
|
||||
2. 使用 `bytes.fromhex()` 转换为字节流;
|
||||
3. Base64 编码字节流;
|
||||
4. 查找 `typ` 对应的 MIME 类型,构造 Data URL。
|
||||
|
||||
> ⚠️ 注意:若 `typ` 不在 `MIME_EXT` 中,不会抛出异常,但可能生成不标准的 MIME 类型。
|
||||
|
||||
---
|
||||
|
||||
### `getFilenameFromBase64(base64String)`
|
||||
|
||||
从 Base64 Data URL 中解析出推荐的文件名,基于其 MIME 类型自动添加扩展名。
|
||||
|
||||
#### 参数
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
|------------------|--------|------|
|
||||
| `base64String` | `str` | Base64 编码的 Data URL 字符串或纯 Base64 内容。 |
|
||||
|
||||
#### 返回值
|
||||
|
||||
- `str`: 形如 `<unique_id>.<ext>` 的文件名字符串。
|
||||
- `<unique_id>` 来自 `getID()` 生成的唯一标识;
|
||||
- `<ext>` 由 MIME 类型决定,若无法解析则尝试从类型中提取子类型。
|
||||
|
||||
#### 示例
|
||||
|
||||
```python
|
||||
url = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUg..."
|
||||
fname = getFilenameFromBase64(url)
|
||||
# 输出: abc123def.png
|
||||
|
||||
invalid = "invalid_base64_string"
|
||||
fname = getFilenameFromBase64(invalid)
|
||||
# 输出: xyz789abc (无扩展名)
|
||||
```
|
||||
|
||||
#### 实现逻辑
|
||||
|
||||
1. 使用正则表达式匹配 `data:<mime>;base64,<data>` 格式;
|
||||
2. 提取 `mime_type`;
|
||||
3. 在 `MIME_EXT` 中查找对应扩展名,否则取 `/` 后的部分(如 `octet-stream`);
|
||||
4. 结合唯一 ID 生成文件名。
|
||||
|
||||
> ❗ 不会抛出异常,无效输入返回仅含 ID 的文件名。
|
||||
|
||||
---
|
||||
|
||||
### `base64_to_file(base64_string, output_path)`
|
||||
|
||||
将 Base64 编码的字符串(支持 Data URL 格式)解码并写入指定路径的文件。
|
||||
|
||||
#### 参数
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
|-------------------|--------|------|
|
||||
| `base64_string` | `str` | Base64 编码字符串,可带 `data:mime;base64,` 头部。 |
|
||||
| `output_path` | `str` | 输出文件的完整路径(包括文件名)。 |
|
||||
|
||||
#### 行为
|
||||
|
||||
- 自动检测并移除 Data URL 头部(即第一个逗号前的内容);
|
||||
- 解码 Base64 数据为二进制;
|
||||
- 以二进制写模式 (`wb`) 保存至目标路径。
|
||||
|
||||
#### 示例
|
||||
|
||||
```python
|
||||
data_url = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUg..."
|
||||
base64_to_file(data_url, "/tmp/output.png")
|
||||
# 结果:在 /tmp/output.png 创建 PNG 文件
|
||||
```
|
||||
|
||||
#### 注意事项
|
||||
|
||||
- 不检查目录是否存在,若路径不存在会引发 `FileNotFoundError`;
|
||||
- 不验证 Base64 是否合法,错误数据可能导致 `base64.b64decode` 抛出异常。
|
||||
|
||||
---
|
||||
|
||||
## 使用示例
|
||||
|
||||
```python
|
||||
# 示例 1: Hex → Base64 Data URL
|
||||
hex_input = "0x89504E470D0A1A0A..." # PNG 的 hex
|
||||
b64_url = hex2base64(hex_input, "png")
|
||||
print(b64_url) # data:image/png;base64,...
|
||||
|
||||
# 示例 2: 生成带扩展名的唯一文件名
|
||||
filename = getFilenameFromBase64(b64_url)
|
||||
print(filename) # abc123def.png
|
||||
|
||||
# 示例 3: 保存为文件
|
||||
base64_to_file(b64_url, f"/uploads/{filename}")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 异常处理建议
|
||||
|
||||
虽然当前代码未显式抛出异常,但在生产环境中建议增加以下保护:
|
||||
|
||||
```python
|
||||
try:
|
||||
base64_to_file(data, path)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to save base64 data: {e}")
|
||||
```
|
||||
|
||||
特别是:
|
||||
- `base64.b64decode()` 可能因格式错误失败;
|
||||
- 文件 I/O 操作需处理权限或磁盘空间问题。
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
| 功能 | 函数名 |
|
||||
|--------------------------|-------------------------|
|
||||
| Hex → Base64 Data URL | `hex2base64()` |
|
||||
| Base64 → 推荐文件名 | `getFilenameFromBase64()`|
|
||||
| Base64 → 本地文件 | `base64_to_file()` |
|
||||
|
||||
本模块适合嵌入 Web 后端服务中,用于处理前端上传的内联资源(如 Canvas 截图、录音等),实现快速落地存储或进一步处理。
|
||||
|
||||
---
|
||||
|
||||
✅ **版本要求**:Python 3.6+
|
||||
📦 **依赖项**:`appPublic` 包(提供 `uniqueID.getID`)
|
||||
177
aidocs/country_cn_en.md
Normal file
177
aidocs/country_cn_en.md
Normal file
@ -0,0 +1,177 @@
|
||||
# 国家中英文名称映射技术文档
|
||||
|
||||
## 概述
|
||||
|
||||
本模块提供了一个简单的国家中英文名称双向映射功能,支持根据中文名称获取对应的英文名称,或根据英文名称获取对应的中文名称。该功能适用于需要进行国家名称本地化处理的场景,如国际化(i18n)、数据清洗、用户界面显示等。
|
||||
|
||||
---
|
||||
|
||||
## 文件信息
|
||||
|
||||
- **文件编码**:UTF-8
|
||||
- **语言**:Python 3
|
||||
- **用途**:国家名称中英互译字典与工具函数
|
||||
|
||||
---
|
||||
|
||||
## 核心数据结构
|
||||
|
||||
### `ecc` 字典:英文 → 中文 映射
|
||||
|
||||
```python
|
||||
ecc = {
|
||||
"Afghanistan": "阿富汗",
|
||||
"Albania": "阿尔巴尼亚",
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
- **说明**:
|
||||
键为国家的英文全称(标准英文拼写),值为对应的简体中文名称。
|
||||
- **覆盖范围**:包含全球绝大多数主权国家及部分地区(如中国香港、澳门、台湾)和海外领地。
|
||||
- **特殊命名示例**:
|
||||
- `"Congo (Congo-Kinshasa)"`: 刚果民主共和国(简称“刚果(金)”)
|
||||
- `"Korea (South)"` / `"Korea (North)"`: 区分韩国与朝鲜
|
||||
- `"Hong Kong"`: 香港(中国)
|
||||
- `"Taiwan"`: 台湾(中国)
|
||||
|
||||
> ⚠️ 注意:所有条目均基于通用国际认知及中国官方立场进行翻译。
|
||||
|
||||
---
|
||||
|
||||
### `cec` 字典:中文 → 英文 映射
|
||||
|
||||
```python
|
||||
cec = {v: k for k, v in ecc.items()}
|
||||
```
|
||||
|
||||
- **生成方式**:通过字典推导式将 `ecc` 的键值对反转。
|
||||
- **作用**:实现从中文国名到英文国名的快速查找。
|
||||
- **示例**:
|
||||
```python
|
||||
cec["中国"] → "China"
|
||||
cec["法国"] → "France"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 功能函数
|
||||
|
||||
### `get_en_country_name(country)`
|
||||
|
||||
#### 功能
|
||||
根据输入的中文国家名称返回其对应的英文名称;若未找到匹配项,则原样返回输入值。
|
||||
|
||||
#### 参数
|
||||
| 参数名 | 类型 | 描述 |
|
||||
|-----------|--------|--------------------------|
|
||||
| `country` | str | 输入的中文国家名称 |
|
||||
|
||||
#### 返回值
|
||||
- 若存在映射:返回对应的英文国家名称(字符串)
|
||||
- 否则:返回原始输入 `country`
|
||||
|
||||
#### 示例
|
||||
```python
|
||||
get_en_country_name("中国") # → "China"
|
||||
get_en_country_name("德国") # → "Germany"
|
||||
get_en_country_name("火星") # → "火星" (无匹配,原样返回)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `get_cn_country_name(country)`
|
||||
|
||||
#### 功能
|
||||
根据输入的英文国家名称返回其对应的中文名称;若未找到匹配项,则原样返回输入值。
|
||||
|
||||
#### 参数
|
||||
| 参数名 | 类型 | 描述 |
|
||||
|-----------|--------|--------------------------|
|
||||
| `country` | str | 输入的英文国家名称 |
|
||||
|
||||
#### 返回值
|
||||
- 若存在映射:返回对应的中文国家名称(字符串)
|
||||
- 否则:返回原始输入 `country`
|
||||
|
||||
#### 示例
|
||||
```python
|
||||
get_cn_country_name("Japan") # → "日本"
|
||||
get_cn_country_name("Italy") # → "意大利"
|
||||
get_cn_country_name("Mars") # → "Mars" (无匹配,原样返回)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 使用场景
|
||||
|
||||
1. **国际化系统中的国家名称本地化**
|
||||
- 将数据库中的英文国家名转换为中文展示给用户。
|
||||
2. **表单数据清洗**
|
||||
- 统一用户输入的国家名称格式(如“CHINA”、“中国”、“PRC” → “中国”)。
|
||||
3. **API 接口适配**
|
||||
- 在不同语言系统间传递国家信息时做标准化转换。
|
||||
4. **报表生成**
|
||||
- 自动生成多语言版本的统计报告。
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **大小写敏感性**
|
||||
当前实现是**完全匹配且区分大小写**的。建议在调用前对输入进行标准化处理(如首字母大写、去除多余空格等)。
|
||||
|
||||
✅ 建议预处理:
|
||||
```python
|
||||
get_cn_country_name(country.strip().title())
|
||||
```
|
||||
|
||||
2. **别名支持有限**
|
||||
仅支持字典中明确定义的名称。例如:
|
||||
- `"USA"` 不等于 `"United States"`
|
||||
- `"UK"` 不等于 `"United Kingdom"`
|
||||
|
||||
如需支持别名,可扩展字典或增加别名映射层。
|
||||
|
||||
3. **编码要求**
|
||||
- 文件以 UTF-8 编码保存,确保中文字符正确解析。
|
||||
|
||||
4. **维护建议**
|
||||
- 新增国家或修改译名时,请同步更新 `ecc` 字典,并验证 `cec` 自动生成逻辑是否正常。
|
||||
|
||||
---
|
||||
|
||||
## 扩展建议
|
||||
|
||||
| 功能需求 | 实现建议 |
|
||||
|----------------------|--------|
|
||||
| 支持别名(如 USA → 美国) | 添加 `aliases` 字典,预先映射常见别名至标准名称 |
|
||||
| 不区分大小写查询 | 在函数内部统一转为小写/大写后查询 |
|
||||
| 返回标准化名称 | 提供 `normalize_country()` 函数输出标准中/英文名 |
|
||||
| 支持 ISO 国家代码 | 扩展为包含 ISO 3166-1 alpha-2/alpha-3 代码的结构化字典 |
|
||||
|
||||
---
|
||||
|
||||
## 版权与声明
|
||||
|
||||
- 中文译名参考中国官方出版物及外交部标准译法。
|
||||
- 本数据不用于任何商业目的之外的责任归属。
|
||||
- “台湾”、“香港”、“澳门”按一个中国原则标注为中国地区。
|
||||
|
||||
---
|
||||
|
||||
## 示例代码
|
||||
|
||||
```python
|
||||
# 示例:中英文互译
|
||||
print(get_en_country_name("日本")) # 输出: Japan
|
||||
print(get_cn_country_name("Brazil")) # 输出: 巴西
|
||||
|
||||
# 示例:未识别名称原样返回
|
||||
print(get_cn_country_name("Middle Earth")) # 输出: Middle Earth
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
✅ **版本状态**:稳定可用
|
||||
📅 **最后更新**:2025年4月5日
|
||||
219
aidocs/csv_Data.md
Normal file
219
aidocs/csv_Data.md
Normal file
@ -0,0 +1,219 @@
|
||||
# CSVData 模块技术文档
|
||||
|
||||
```markdown
|
||||
# CSVData 技术文档
|
||||
|
||||
## 简介
|
||||
|
||||
`CSVData` 是一个轻量级的 Python 模块,用于读取和解析 CSV 文件。它支持自定义编码格式和分隔符,并将每行数据以字典形式返回,字段名为键,对应值为内容。该模块避免了标准库 `csv` 模块的部分限制,提供了更灵活的控制能力。
|
||||
|
||||
> ⚠️ 注意:本模块兼容 Python 2,若在 Python 3 中使用需进行适当调整(如 `next()` 方法名冲突)。
|
||||
|
||||
---
|
||||
|
||||
## 模块依赖
|
||||
|
||||
- `codecs`: 用于处理文件编码。
|
||||
- `csv`: (导入但未实际使用,可移除)
|
||||
|
||||
---
|
||||
|
||||
## 类说明
|
||||
|
||||
### `Reader` 类
|
||||
|
||||
逐行读取文本文件并按指定分隔符分割字段。
|
||||
|
||||
#### 构造函数:`__init__(self, f, delimiter)`
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
|-----------|------------|--------------------------|
|
||||
| `f` | file object | 已打开的文件对象 |
|
||||
| `delimiter` | str | 字段之间的分隔符,默认为 `,` |
|
||||
|
||||
**属性:**
|
||||
- `self.f`: 文件对象
|
||||
- `self.delimiter`: 分隔符
|
||||
- `self.line`: 当前行号(从 1 开始计数)
|
||||
|
||||
---
|
||||
|
||||
#### 方法:`__iter__()`
|
||||
|
||||
使 `Reader` 成为可迭代对象。
|
||||
|
||||
**返回值:**
|
||||
- 返回自身实例,支持迭代协议。
|
||||
|
||||
---
|
||||
|
||||
#### 方法:`next()`
|
||||
|
||||
读取下一行并解析为字段列表。
|
||||
|
||||
**逻辑流程:**
|
||||
1. 调用 `readline()` 读取一行。
|
||||
2. 若为空字符串(EOF),抛出 `StopIteration`。
|
||||
3. 去除末尾的换行符(`\n` 或 `\r`)。
|
||||
4. 使用 `delimiter` 分割字符串。
|
||||
5. 将空字符串转换为 `None`。
|
||||
6. 行号递增。
|
||||
7. 返回字段列表。
|
||||
|
||||
**返回值:**
|
||||
- `list`: 字段值列表,空字段替换为 `None`。
|
||||
|
||||
**异常:**
|
||||
- `StopIteration`: 文件结束时触发。
|
||||
|
||||
---
|
||||
|
||||
### `CSVData` 类
|
||||
|
||||
封装 CSV 文件的高级读取器,自动识别首行为字段名(header),后续行为记录。
|
||||
|
||||
#### 构造函数:`__init__(filename, coding='utf8', delimiter=',')`
|
||||
|
||||
| 参数 | 类型 | 默认值 | 描述 |
|
||||
|-------------|--------|-----------|----------------------------|
|
||||
| `filename` | str | - | CSV 文件路径 |
|
||||
| `coding` | str | `'utf8'` | 文件编码方式 |
|
||||
| `delimiter` | str | `','` | 字段分隔符 |
|
||||
|
||||
**内部初始化动作:**
|
||||
- 使用 `codecs.open` 打开文件,确保正确解码。
|
||||
- 创建 `Reader` 实例。
|
||||
- 读取第一行作为字段名(`self.fields`)。
|
||||
|
||||
**属性:**
|
||||
- `self.filename`: 文件路径
|
||||
- `self.coding`: 编码格式
|
||||
- `self.f`: 打开的文件对象
|
||||
- `self.reader`: `Reader` 实例
|
||||
- `self.fields`: 字段名列表(来自第一行)
|
||||
|
||||
---
|
||||
|
||||
#### 方法:`__del__()`
|
||||
|
||||
析构函数,确保文件被关闭。
|
||||
|
||||
> ❗ 提示:建议显式管理资源(如使用上下文管理器),因为 `__del__` 不一定及时调用。
|
||||
|
||||
---
|
||||
|
||||
#### 方法:`__iter__()`
|
||||
|
||||
使 `CSVData` 成为可迭代对象。
|
||||
|
||||
**返回值:**
|
||||
- 返回自身实例。
|
||||
|
||||
---
|
||||
|
||||
#### 方法:`next()`
|
||||
|
||||
读取下一条记录并构造成字典。
|
||||
|
||||
**逻辑流程:**
|
||||
1. 调用 `self.reader.next()` 获取字段值列表 `r`。
|
||||
2. 校验字段数量是否与 `self.fields` 一致:
|
||||
- 若不一致,打印警告信息并终止迭代。
|
||||
3. 构建字典 `d`,键为字段名,值为对应列值。
|
||||
4. 返回字典。
|
||||
|
||||
**返回值:**
|
||||
- `dict`: `{字段名: 值}` 的映射。
|
||||
|
||||
**异常处理:**
|
||||
- 捕获所有异常并统一抛出 `StopIteration`,导致迭代终止。
|
||||
|
||||
> ⚠️ 风险:过于宽泛的 `except:` 可能掩盖真实错误。
|
||||
|
||||
---
|
||||
|
||||
## 使用示例
|
||||
|
||||
```python
|
||||
from csvdata import CSVData
|
||||
|
||||
# 读取 UTF-8 编码的 CSV 文件
|
||||
cd = CSVData('data.csv')
|
||||
|
||||
for row in cd:
|
||||
print(row)
|
||||
```
|
||||
|
||||
输出示例:
|
||||
```python
|
||||
{'name': 'Alice', 'age': '30', 'city': 'Beijing'}
|
||||
{'name': 'Bob', 'age': '25', 'city': 'Shanghai'}
|
||||
```
|
||||
|
||||
支持自定义分隔符:
|
||||
```python
|
||||
cd = CSVData('data.tsv', delimiter='\t')
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 主程序入口(命令行运行)
|
||||
|
||||
当模块直接执行时,接受一个命令行参数(文件路径),并打印所有记录。
|
||||
|
||||
```bash
|
||||
python csvdata.py example.csv
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 注意事项与改进建议
|
||||
|
||||
### 兼容性问题(Python 2 vs Python 3)
|
||||
- 在 Python 3 中,`next()` 应重命名为 `__next__()`。
|
||||
- 推荐修改如下:
|
||||
```python
|
||||
def __next__(self):
|
||||
return self.next()
|
||||
```
|
||||
|
||||
### 安全性与健壮性
|
||||
- `except:` 过于宽泛,应捕获具体异常(如 `StopIteration`、`IOError`)。
|
||||
- `d.update(...)` 使用列表推导副作用,风格不佳,建议改为显式循环。
|
||||
|
||||
### 改进建议代码片段
|
||||
|
||||
```python
|
||||
# 替代原字典构建方式
|
||||
d = {self.fields[i]: r[i] for i in range(len(self.fields))}
|
||||
```
|
||||
|
||||
或:
|
||||
|
||||
```python
|
||||
d = dict(zip(self.fields, r))
|
||||
```
|
||||
|
||||
### 推荐添加上下文管理器支持
|
||||
|
||||
```python
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, *args):
|
||||
self.f.close()
|
||||
```
|
||||
|
||||
使用方式:
|
||||
```python
|
||||
with CSVData('data.csv') as cd:
|
||||
for row in cd:
|
||||
print(row)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
`CSVData` 提供了一个简洁、可控的 CSV 解析方案,适用于需要精细控制解析过程的场景。尽管存在一些现代 Python 开发中的反模式,但通过简单重构即可提升其稳定性与兼容性。
|
||||
```
|
||||
279
aidocs/dataencoder.md
Normal file
279
aidocs/dataencoder.md
Normal file
@ -0,0 +1,279 @@
|
||||
# `DataEncoder` 技术文档
|
||||
|
||||
```markdown
|
||||
# DataEncoder - 安全数据编码与解码模块
|
||||
|
||||
## 概述
|
||||
|
||||
`DataEncoder` 是一个用于安全传输数据的 Python 类,提供数据加密、签名、压缩和结构化打包功能。它结合了 **RC4 对称加密** 和 **RSA 非对称加密**,确保数据在传输过程中的机密性、完整性和身份验证。
|
||||
|
||||
该类适用于点对点通信场景,支持动态获取对方公钥,并使用本地私钥进行签名,接收方通过公钥验证签名并解密数据。
|
||||
|
||||
---
|
||||
|
||||
## 依赖说明
|
||||
|
||||
### 第三方库
|
||||
- `ujson`(可选):高性能 JSON 库,若不可用则回退至标准库 `json`
|
||||
- `zlib`:内置,用于数据压缩
|
||||
- `struct`:内置,用于二进制数据打包/解包
|
||||
|
||||
### 内部模块
|
||||
- `appPublic.rsawrap.RSA`:封装 RSA 加密/解密、签名/验签功能
|
||||
- `appPublic.rc4.RC4`:实现 RC4 流加密算法
|
||||
- `appPublic.uniqueID.getID`:生成唯一 ID(用作 RC4 密钥)
|
||||
|
||||
> 注:未启用 Brotli 压缩(相关导入被注释)
|
||||
|
||||
---
|
||||
|
||||
## 核心常量
|
||||
|
||||
| 常量 | 值 | 含义 |
|
||||
|------|----|------|
|
||||
| `DATA_TYPE_BYTES` | `1` | 数据类型为原始字节流 |
|
||||
| `DATA_TYPE_STR` | `2` | 数据类型为字符串(UTF-8 编码) |
|
||||
| `DATA_TYPE_JSON` | `3` | 数据类型为 JSON 对象(序列化后为 UTF-8 字节) |
|
||||
|
||||
---
|
||||
|
||||
## `DataEncoder` 类定义
|
||||
|
||||
### 初始化方法:`__init__(self, myid, func_get_peer_pubkey, private_file=None)`
|
||||
|
||||
#### 参数:
|
||||
| 参数名 | 类型 | 描述 |
|
||||
|--------|------|------|
|
||||
| `myid` | str 或 any | 当前节点的唯一标识符 |
|
||||
| `func_get_peer_pubkey` | callable | 回调函数,用于根据 `peer_id` 获取对方公钥 |
|
||||
| `private_file` | str, optional | 私钥文件路径;若未提供,则自动生成新密钥对 |
|
||||
|
||||
#### 功能:
|
||||
- 创建或加载 RSA 密钥对(私钥 + 公钥)
|
||||
- 初始化内部状态:
|
||||
- `self.public_keys`: 缓存其他节点的公钥
|
||||
- `self.rsa`, `self.rc4`: 实例化加解密工具
|
||||
|
||||
#### 示例回调函数签名:
|
||||
```python
|
||||
def get_pubkey(peer_id):
|
||||
# 返回对应的 RSA 公钥对象
|
||||
return rsa_public_key_obj
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 核心方法
|
||||
|
||||
### `identify_datatype(data)`
|
||||
自动识别输入数据类型并转换为字节格式。
|
||||
|
||||
#### 返回值:
|
||||
元组 `(data_type, encoded_bytes)`
|
||||
其中 `data_type` 为上述三种之一。
|
||||
|
||||
| 输入类型 | 输出类型 | 处理方式 |
|
||||
|---------|----------|--------|
|
||||
| `bytes` | `DATA_TYPE_BYTES` | 直接返回 |
|
||||
| `str` | `DATA_TYPE_STR` | `.encode('utf-8')` |
|
||||
| 其他(如 dict/list) | `DATA_TYPE_JSON` | `json.dumps().encode('utf-8')` |
|
||||
|
||||
---
|
||||
|
||||
### `pack(peer_id, data, uncrypt=False)`
|
||||
将数据加密、签名并打包成安全格式发送给指定 `peer_id`。
|
||||
|
||||
#### 参数:
|
||||
| 参数 | 描述 |
|
||||
|------|------|
|
||||
| `peer_id` | 接收方的身份 ID |
|
||||
| `data` | 待发送的数据(任意类型) |
|
||||
| `uncrypt` | 是否跳过加密(调试用途),仅压缩不加密 |
|
||||
|
||||
#### 打包流程:
|
||||
|
||||
1. 调用 `identify_datatype(data)` 获取类型和字节数据。
|
||||
2. 若 `uncrypt=True`,直接构造明文包并返回压缩结果。
|
||||
3. 否则:
|
||||
- 生成随机 RC4 密钥(`getID()`)
|
||||
- 使用接收方公钥加密该密钥(RSA 加密)
|
||||
- 使用 RC4 加密原始数据
|
||||
- 构造 struct 格式字符串(含长度信息)
|
||||
- 将以下字段按顺序打包:
|
||||
1. **格式头(18字节)**:描述后续结构
|
||||
2. **数据类型(1字节)**
|
||||
3. **加密后的数据**
|
||||
4. **加密后的 RC4 密钥**
|
||||
5. **数字签名(覆盖以上所有内容)**
|
||||
- 使用 `zlib.compress()` 压缩整个包
|
||||
- 返回压缩后的二进制数据
|
||||
|
||||
#### 结构体格式示例:
|
||||
假设加密数据长 1024 字节,密钥长 256 字节,则格式为:
|
||||
```
|
||||
'18s b1024s256s256s'
|
||||
```
|
||||
最后一个 `256s` 是签名(固定长度由 RSA 决定)。
|
||||
|
||||
> 签名范围包括:头部 + 类型 + 加密数据 + 加密密钥
|
||||
|
||||
---
|
||||
|
||||
### `unpack(peer_id, data)`
|
||||
解包并还原从 `peer_id` 收到的安全数据。
|
||||
|
||||
#### 参数:
|
||||
| 参数 | 描述 |
|
||||
|------|------|
|
||||
| `peer_id` | 发送方的身份 ID |
|
||||
| `data` | 接收到的压缩二进制数据 |
|
||||
|
||||
#### 解包流程:
|
||||
|
||||
1. 解压 `zlib.decompress(data)`
|
||||
2. 判断是否为无加密模式(前 18 字节全为 `\x00`):
|
||||
- 是 → 按简单格式解析(仅类型+数据),返回明文
|
||||
3. 否则进入安全解包流程:
|
||||
- 提取前 18 字节作为格式字符串(去除尾部 `\x00`)
|
||||
- 解析剩余数据得到:类型、加密数据、加密密钥、签名
|
||||
- 验证签名(使用发送方公钥)
|
||||
- 若失败 → 抛出异常 `'data sign verify failed'`
|
||||
- 成功后:
|
||||
- 用本机私钥解密 RC4 密钥
|
||||
- 用 RC4 解密数据
|
||||
- 根据类型还原为原始数据(bytes / str / json)
|
||||
- 返回解码后的原始数据
|
||||
|
||||
---
|
||||
|
||||
### 加解密辅助方法
|
||||
|
||||
| 方法 | 功能 |
|
||||
|------|------|
|
||||
| `encode_data(peer_pubkey, data)` | 使用 RC4 加密数据,RSA 加密密钥 |
|
||||
| `decode_data(data, encoded_key)` | RSA 解密密钥,再用 RC4 解密数据 |
|
||||
| `sign_data(data)` | 使用本机私钥对数据签名 |
|
||||
| `verify_sign(data, sign, pubkey)` | 使用对方公钥验证签名有效性 |
|
||||
|
||||
---
|
||||
|
||||
### 公钥管理方法
|
||||
|
||||
| 方法 | 功能 |
|
||||
|------|------|
|
||||
| `my_text_publickey()` | 获取本机公钥的文本表示(Base64 编码等) |
|
||||
| `get_peer_pubkey(peer_id)` | 获取某 peer 的公钥(缓存或调用回调函数) |
|
||||
| `set_peer_pubkey(peer_id, pubkey)` | 设置某 peer 的公钥对象 |
|
||||
| `set_peer_text_pubkey(peer_id, text_pubkey)` | 从文本形式设置公钥(自动解析) |
|
||||
| `get_peer_text_pubkey(peer_id)` | 获取某 peer 的公钥文本形式(需已存在) |
|
||||
| `exist_peer_publickeys(peer_id)` | 查询是否已有某 peer 的公钥 |
|
||||
|
||||
---
|
||||
|
||||
## 数据格式详解
|
||||
|
||||
### 安全包结构(压缩前)
|
||||
|
||||
| 偏移 | 字段 | 长度 | 说明 |
|
||||
|------|------|-------|------|
|
||||
| 0 | 格式描述字符串 | 18 字节 | 如 `'b1024s256s256s\x00...'` |
|
||||
| 18 | 数据类型 | 1 字节 | `1=bytes`, `2=str`, `3=json` |
|
||||
| 19 | 加密数据 | 可变 | RC4 加密后的 payload |
|
||||
| 19+len(d) | 加密密钥 | 可变 | RSA 加密后的 RC4 密钥 |
|
||||
| ... | 数字签名 | 固定(如 256) | RSA-PKCS1-V1_5 签名 |
|
||||
|
||||
> 总长度 = 18 + len(d) + len(k) + len(s)
|
||||
|
||||
### 明文包结构(`uncrypt=True`)
|
||||
|
||||
| 偏移 | 字段 | 长度 | 说明 |
|
||||
|------|------|-------|------|
|
||||
| 0 | 填充零 | 18 字节 | 全 `\x00` 表示无加密 |
|
||||
| 18 | 数据类型 | 1 字节 | 同上 |
|
||||
| 19 | 原始数据 | 可变 | 未经加密 |
|
||||
|
||||
---
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 初始化
|
||||
```python
|
||||
def fetch_pubkey(pid):
|
||||
# 实现获取 peer 公钥逻辑
|
||||
return rsa_pubkey_obj
|
||||
|
||||
encoder = DataEncoder(
|
||||
myid="node_A",
|
||||
func_get_peer_pubkey=fetch_pubkey,
|
||||
private_file="private.key" # 或 None 自动生成
|
||||
)
|
||||
```
|
||||
|
||||
### 发送加密数据
|
||||
```python
|
||||
msg = {"cmd": "hello", "time": 123}
|
||||
packed_data = encoder.pack("node_B", msg)
|
||||
# 发送 packed_data 到 node_B
|
||||
```
|
||||
|
||||
### 接收并解密
|
||||
```python
|
||||
received_data = ... # 来自网络的数据
|
||||
decoded_msg = encoder.unpack("node_A", received_data)
|
||||
print(decoded_msg) # {'cmd': 'hello', 'time': 123}
|
||||
```
|
||||
|
||||
### 设置远程公钥(手动)
|
||||
```python
|
||||
pubkey_text = "-----BEGIN PUBLIC KEY-----\n..." # PEM 或 Base64 文本
|
||||
encoder.set_peer_text_pubkey("node_C", pubkey_text)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **安全性提醒**:
|
||||
- RC4 已被认为不安全,建议在非敏感环境使用或替换为 AES。
|
||||
- RSA 密钥长度应 ≥ 2048 bits。
|
||||
- 签名防止篡改,但不防重放攻击,需上层协议补充 nonce/timestamp。
|
||||
|
||||
2. **性能优化**:
|
||||
- 使用 `ujson` 提升 JSON 序列化速度。
|
||||
- zlib 压缩有效减少传输体积。
|
||||
|
||||
3. **错误处理**:
|
||||
- 无法获取公钥时会抛出异常。
|
||||
- 签名验证失败也会中断流程。
|
||||
|
||||
4. **扩展建议**:
|
||||
- 支持 Brotli 压缩以进一步减小体积(当前代码中被注释)。
|
||||
- 添加 TTL、nonce、版本号等元数据字段。
|
||||
|
||||
---
|
||||
|
||||
## 辅助函数:`quotedstr(s)`
|
||||
|
||||
将字符串中的特殊字符转义,主要用于日志输出或字符串嵌入。
|
||||
|
||||
### 转义规则:
|
||||
| 原字符 | 替换为 |
|
||||
|--------|--------|
|
||||
| `"` | `\"` |
|
||||
| `\n` | `\\n` |
|
||||
|
||||
> 其他字符保持不变
|
||||
|
||||
### 示例:
|
||||
```python
|
||||
quotedstr('He said: "Hello\nWorld"')
|
||||
# 输出: He said: \"Hello\\nWorld\"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 版本信息
|
||||
- 作者:Auto-generated from source
|
||||
- 最后更新:根据所提供代码生成
|
||||
- 许可:请参考项目整体 LICENSE
|
||||
```
|
||||
157
aidocs/datamapping.md
Normal file
157
aidocs/datamapping.md
Normal file
@ -0,0 +1,157 @@
|
||||
# `dataMapping` 模块技术文档
|
||||
|
||||
## 概述
|
||||
|
||||
`dataMapping` 模块提供两个核心函数:`keyMapping` 和 `valueMapping`,用于对字典数据进行键映射和值映射操作。该模块适用于数据转换、字段重命名、枚举值替换等常见场景。
|
||||
|
||||
依赖:
|
||||
- `DictObject` 来自 `appPublic.dictObject`,用于将结果封装为对象形式(支持属性访问)。
|
||||
|
||||
---
|
||||
|
||||
## 函数说明
|
||||
|
||||
### 1. `keyMapping(dic, mappingtab, keepmiss=True)`
|
||||
|
||||
#### 功能
|
||||
根据提供的映射表 `mappingtab` 将输入字典 `dic` 的键(keys)进行重命名,并返回新的字典。
|
||||
|
||||
#### 参数
|
||||
| 参数名 | 类型 | 说明 |
|
||||
|-----------|----------|------|
|
||||
| `dic` | `dict` | 输入的原始字典 |
|
||||
| `mappingtab` | `dict` | 键映射表,格式为 `{旧键: 新键}` |
|
||||
| `keepmiss` | `bool` | 是否保留未在 `mappingtab` 中定义的原始键。默认为 `True` |
|
||||
|
||||
#### 返回值
|
||||
- `dict`:新字典,其键已根据 `mappingtab` 映射为新名称。
|
||||
- 若某键不在 `mappingtab` 中且 `keepmiss=True`,则保留原键;否则忽略该键。
|
||||
|
||||
#### 映射规则
|
||||
- 对于每个键 `k` in `dic`:
|
||||
- 如果 `k` 在 `mappingtab` 中,则使用 `mappingtab[k]` 作为新键。
|
||||
- 否则,若 `keepmiss=True`,保留原键 `k`;若 `keepmiss=False`,则跳过此键。
|
||||
|
||||
#### 示例
|
||||
|
||||
```python
|
||||
data = {'a1': 10, 'a2': 20, 'other': 99}
|
||||
mapping = {'a1': 'b1', 'a2': 'b2'}
|
||||
|
||||
result = keyMapping(data, mapping)
|
||||
# 输出: {'b1': 10, 'b2': 20, 'other': 99}
|
||||
|
||||
result = keyMapping(data, mapping, keepmiss=False)
|
||||
# 输出: {'b1': 10, 'b2': 20}
|
||||
```
|
||||
|
||||
#### 注意事项
|
||||
- 使用 `mappingtab.get(k, k)` 实现默认保持原键不变的行为。
|
||||
- 内部通过列表推导与 `dict.update()` 构建结果字典。
|
||||
|
||||
---
|
||||
|
||||
### 2. `valueMapping(dic, mappingtab)`
|
||||
|
||||
#### 功能
|
||||
根据 `mappingtab` 对字典中指定字段的值进行映射转换,常用于枚举值或代码转义。
|
||||
|
||||
#### 参数
|
||||
| 参数名 | 类型 | 说明 |
|
||||
|------------|----------|------|
|
||||
| `dic` | `dict` | 输入字典 |
|
||||
| `mappingtab` | `dict` | 值映射表,结构为 `{字段名: {原值: 目标值, "__default__": 默认值}}` |
|
||||
|
||||
#### 返回值
|
||||
- `DictObject`:映射后的结果字典被封装成 `DictObject` 对象,支持属性访问(如 `obj.field1`)。
|
||||
|
||||
#### 映射规则
|
||||
对于 `dic` 中的每一个键 `k`:
|
||||
- 查找 `mappingtab[k]` 是否存在:
|
||||
- 若不存在,则保留原值。
|
||||
- 若存在,则查找 `dic[k]` 对应的目标值:
|
||||
- 若目标值存在,赋值;
|
||||
- 否则使用 `mappingtab[k].get('__default__', dic[k])` 作为默认值。
|
||||
|
||||
> 特殊键 `"__default__"` 可定义默认映射值。若未设置,默认为原始值。
|
||||
|
||||
#### 示例
|
||||
|
||||
```python
|
||||
data = {'status': 'A', 'type': 'X', 'name': 'test'}
|
||||
mapping = {
|
||||
'status': {
|
||||
'A': 'Active',
|
||||
'I': 'Inactive',
|
||||
'__default__': 'Unknown'
|
||||
},
|
||||
'type': {
|
||||
'X': 'External',
|
||||
'I': 'Internal'
|
||||
# 无 __default__
|
||||
}
|
||||
}
|
||||
|
||||
result = valueMapping(data, mapping)
|
||||
# result 是 DictObject,内容如下:
|
||||
# {
|
||||
# 'status': 'Active',
|
||||
# 'type': 'External',
|
||||
# 'name': 'test' # 不在 mappingtab 中,原样保留
|
||||
# }
|
||||
|
||||
# 访问方式:
|
||||
# result.status => 'Active'
|
||||
# result.type => 'External'
|
||||
```
|
||||
|
||||
另一个例子:
|
||||
|
||||
```python
|
||||
data = {'level': 'unknown'}
|
||||
mapping = {
|
||||
'level': {
|
||||
'high': 'H',
|
||||
'low': 'L',
|
||||
'__default__': 'N/A'
|
||||
}
|
||||
}
|
||||
|
||||
result = valueMapping(data, mapping)
|
||||
# result.level => 'N/A'
|
||||
```
|
||||
|
||||
#### 注意事项
|
||||
- 所有未在 `mappingtab` 中列出的字段均保持原值。
|
||||
- `__default__` 提供了灵活的兜底机制,增强健壮性。
|
||||
- 最终返回的是 `DictObject(**ret)`,允许以点语法访问属性。
|
||||
|
||||
---
|
||||
|
||||
## 使用建议
|
||||
|
||||
- **`keyMapping`** 适合用于 API 数据结构调整、数据库字段到模型字段的映射。
|
||||
- **`valueMapping`** 适合处理状态码、类型编码、国际化标签转换等场景。
|
||||
- 结合两者可实现完整的数据清洗与标准化流程。
|
||||
|
||||
---
|
||||
|
||||
## 依赖说明
|
||||
|
||||
本模块依赖:
|
||||
```python
|
||||
from appPublic.dictObject import DictObject
|
||||
```
|
||||
确保环境中已安装并可导入 `appPublic` 包。`DictObject` 支持动态属性访问,提升代码可读性和便利性。
|
||||
|
||||
---
|
||||
|
||||
## 版本信息
|
||||
|
||||
- 创建时间:未知
|
||||
- 最后更新:根据代码逻辑整理
|
||||
- 维护者:开发者团队
|
||||
|
||||
---
|
||||
|
||||
📌 **提示**:在实际项目中建议添加类型注解和异常处理以增强稳定性。
|
||||
191
aidocs/dictExt.md
Normal file
191
aidocs/dictExt.md
Normal file
@ -0,0 +1,191 @@
|
||||
# 技术文档:`dictExtend` 与 `arrayExtend` 函数
|
||||
|
||||
## 概述
|
||||
|
||||
该文档描述了两个用于递归合并字典和列表的 Python 函数:
|
||||
|
||||
- `dictExtend(s, addon)`:递归地将一个字典 `addon` 合并到基础字典 `s` 中,支持嵌套结构。
|
||||
- `arrayExtend(s, addon)`:按索引扩展列表,支持类型检查和嵌套结构的递归合并。
|
||||
|
||||
这两个函数常用于配置管理、默认值覆盖、数据补丁等场景,允许智能合并复杂嵌套结构的数据。
|
||||
|
||||
---
|
||||
|
||||
## 函数说明
|
||||
|
||||
### 1. `dictExtend(s, addon)`
|
||||
|
||||
#### 功能
|
||||
递归合并两个字典。如果键存在于原字典中:
|
||||
- 若值类型不同,则用新值覆盖;
|
||||
- 若均为字典,则递归合并;
|
||||
- 若均为列表,则调用 `arrayExtend` 进行合并;
|
||||
- 其他情况直接覆盖。
|
||||
|
||||
#### 参数
|
||||
| 参数 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `s` | `dict` | 原始字典(被扩展的对象) |
|
||||
| `addon` | `dict` | 要合并进来的字典 |
|
||||
|
||||
#### 返回值
|
||||
- `dict`:合并后的字典,不修改原始输入。
|
||||
|
||||
#### 逻辑流程
|
||||
1. 创建一个新的字典 `ret`,初始化为 `s` 的副本。
|
||||
2. 遍历 `addon` 的所有键值对 `(k, v)`:
|
||||
- 如果 `k` 不在 `ret` 中 → 添加新键值。
|
||||
- 如果 `v` 与 `ret[k]` 类型不同 → 覆盖旧值。
|
||||
- 如果两者都是字典 → 递归调用 `dictExtend(ret[k], v)`。
|
||||
- 如果两者都是列表 → 调用 `arrayExtend(ret[k], v)`。
|
||||
- 其他情况 → 直接赋值覆盖。
|
||||
3. 返回合并后的字典。
|
||||
|
||||
#### 示例
|
||||
```python
|
||||
base = {
|
||||
"a": 1,
|
||||
"b": {"x": 10, "y": [1, 2]},
|
||||
"c": [1, 2]
|
||||
}
|
||||
|
||||
patch = {
|
||||
"b": {"y": [3], "z": 100},
|
||||
"c": [3],
|
||||
"d": "new"
|
||||
}
|
||||
|
||||
result = dictExtend(base, patch)
|
||||
# 结果:
|
||||
# {
|
||||
# "a": 1,
|
||||
# "b": {"x": 10, "y": [3], "z": 100},
|
||||
# "c": [3],
|
||||
# "d": "new"
|
||||
# }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. `arrayExtend(s, addon)`
|
||||
|
||||
#### 功能
|
||||
按索引合并两个列表。对于每个位置:
|
||||
- 如果索引超出原列表长度 → 使用 `addon` 的值;
|
||||
- 如果类型不同 → 使用 `addon` 的值;
|
||||
- 如果都是字典 → 递归调用 `dictExtend`;
|
||||
- 否则使用 `addon` 的值。
|
||||
|
||||
> ⚠️ 注意:当前实现存在潜在 Bug —— 最后一行 `ret += s[i:]` 实际上应为 `ret += addon[i+1:]` 或类似逻辑,目前代码可能错误拼接原始列表尾部。
|
||||
|
||||
#### 参数
|
||||
| 参数 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `s` | `list` | 原始列表 |
|
||||
| `addon` | `list` | 要合并的新列表 |
|
||||
|
||||
#### 返回值
|
||||
- `list`:合并后的新列表,不修改原始输入。
|
||||
|
||||
#### 逻辑流程
|
||||
1. 初始化空列表 `ret`。
|
||||
2. 获取两个列表长度:`s_cnt = len(s)`, `a_cnt = len(addon)`。
|
||||
3. 遍历 `addon` 的每个元素 `(i, v)`:
|
||||
- 若 `i < s_cnt`(未越界):
|
||||
- 类型不同 → 添加 `v`
|
||||
- 是字典且对应项也是字典 → 递归合并
|
||||
- 其他 → 添加 `v`
|
||||
- 否则(即 `i >= s_cnt`)→ 自动添加 `v`
|
||||
4. **问题点**:末尾有 `if s_cnt < a_cnt: ret += s[i:]`
|
||||
此处 `i` 是循环最后的索引,`s[i:]` 表示从 `s` 的某个位置截取,这不符合“扩展”语义,**应该是 `addon[i+1:]` 才对**。
|
||||
|
||||
#### 示例(修正前)
|
||||
```python
|
||||
a = [1, {"x": 1}, [1]]
|
||||
b = [2, {"x": 2, "y": 3}, [2, 3]]
|
||||
|
||||
result = arrayExtend(a, b)
|
||||
# 当前行为(含 bug)可能导致意外结果
|
||||
```
|
||||
|
||||
#### 存在的问题(Bug 分析)
|
||||
|
||||
```python
|
||||
if s_cnt < a_cnt:
|
||||
ret += s[i:]
|
||||
```
|
||||
|
||||
- `i` 是 `addon` 的最后一个索引(例如 `len(addon)-1`)。
|
||||
- `s[i:]` 是从原始列表 `s` 中取出从 `i` 开始的部分。
|
||||
- 但此时我们希望的是把 `addon` 中多出来的部分追加,而不是 `s` 的尾部!
|
||||
|
||||
✅ 正确逻辑应为:
|
||||
```python
|
||||
ret += addon[s_cnt:] # 将 addon 多出的部分追加
|
||||
```
|
||||
|
||||
所以此函数**存在严重逻辑错误**,需修复。
|
||||
|
||||
---
|
||||
|
||||
## 使用建议
|
||||
|
||||
### ✅ 正确使用场景
|
||||
适用于需要深度合并嵌套配置对象的情况,如:
|
||||
|
||||
- 应用默认配置 + 用户自定义配置
|
||||
- API 响应模板补丁
|
||||
- 构建可继承的数据结构
|
||||
|
||||
### ❌ 已知限制与风险
|
||||
1. `arrayExtend` 函数末尾逻辑错误,可能导致数据错乱。
|
||||
2. 对非同构数组(mixed types)处理较粗暴,一律覆盖。
|
||||
3. 无循环引用检测,深层递归可能导致栈溢出。
|
||||
4. 性能一般,不适合高频或大数据量操作。
|
||||
|
||||
---
|
||||
|
||||
## 修复建议
|
||||
|
||||
### 修复 `arrayExtend` 函数
|
||||
```python
|
||||
def arrayExtend(s, addon):
|
||||
ret = []
|
||||
s_cnt = len(s)
|
||||
a_cnt = len(addon)
|
||||
|
||||
for i, v in enumerate(addon):
|
||||
if i < s_cnt:
|
||||
if type(v) != type(s[i]):
|
||||
ret.append(v)
|
||||
continue
|
||||
if isinstance(v, dict):
|
||||
x = dictExtend(v, s[i])
|
||||
ret.append(x)
|
||||
continue
|
||||
ret.append(v)
|
||||
|
||||
# 修复:应添加 addon 多出的部分,而非 s 的尾部
|
||||
if a_cnt > s_cnt:
|
||||
ret.extend(addon[s_cnt:])
|
||||
|
||||
return ret
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
| 特性 | 状态 |
|
||||
|------|------|
|
||||
| 字典递归合并 | ✅ 支持良好 |
|
||||
| 列表递归合并 | ⚠️ 支持但有 Bug |
|
||||
| 类型安全检查 | ✅ 支持 |
|
||||
| 不可变性 | ✅ 不修改原对象 |
|
||||
| 边界情况处理 | ⚠️ 需加强(如 None、非容器类型) |
|
||||
|
||||
建议在实际项目中使用前进行充分测试,并优先考虑使用更成熟的库如 [`deepmerge`](https://pypi.org/project/deepmerge/) 或 `copy.deepcopy` + 手动逻辑。
|
||||
|
||||
---
|
||||
|
||||
> 📌 提示:本文档基于所提供代码分析,实际部署请先修复 `arrayExtend` 的逻辑错误。
|
||||
372
aidocs/dictObject.md
Normal file
372
aidocs/dictObject.md
Normal file
@ -0,0 +1,372 @@
|
||||
以下是为提供的 Python 代码编写的 **Markdown 格式技术文档**,涵盖了功能说明、类与函数的详细描述、使用示例等。
|
||||
|
||||
---
|
||||
|
||||
# `DictObject` 模块技术文档
|
||||
|
||||
本模块提供了一个增强型字典对象 `DictObject`,支持通过属性访问键值(如 `obj.key`),并可递归封装嵌套数据结构。同时提供了 JSON 序列化支持和多值字典转单值/列表字典的工具函数。
|
||||
|
||||
## 目录
|
||||
|
||||
- [核心功能概述](#核心功能概述)
|
||||
- [函数 `multiDict2Dict`](#function-multidict2dictmd)
|
||||
- [类 `DictObjectEncoder`](#class-dictobjectencoderjsonencoder)
|
||||
- [类 `DictObject`](#class-dictobjectdict)
|
||||
- [方法概览](#方法概览)
|
||||
- [详细方法说明](#详细方法说明)
|
||||
- [辅助静态方法 `_wrap` 和 `_dict`](#辅助静态方法-_wrap-和-_dict)
|
||||
- [已注释工厂函数 `dictObjectFactory`](#已注释掉的工厂函数-dictobjectfactory建议扩展用)
|
||||
- [使用示例](#使用示例)
|
||||
- [注意事项与限制](#注意事项与限制)
|
||||
|
||||
---
|
||||
|
||||
## 核心功能概述
|
||||
|
||||
该模块主要实现以下功能:
|
||||
|
||||
1. **属性式访问字典**:允许像操作对象属性一样读写字典内容。
|
||||
2. **自动类型包装**:对嵌套的字典或列表自动转换为 `DictObject` 或其列表形式。
|
||||
3. **安全访问机制**:访问不存在的属性返回 `None` 而非抛出异常。
|
||||
4. **JSON 可序列化支持**:通过自定义编码器支持复杂对象的 JSON 输出。
|
||||
5. **多值字典合并工具**:将多个同名键的值合并为列表。
|
||||
|
||||
---
|
||||
|
||||
## Function: `multiDict2Dict(md)`
|
||||
|
||||
将一个多值字典(如 Web 表单中常见的多个同名参数)转换为标准字典,若某键有多个值,则将其合并为一个列表。
|
||||
|
||||
### 参数
|
||||
| 参数 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `md` | `dict` 或 `MultiDict` | 输入的多值字典 |
|
||||
|
||||
### 返回值
|
||||
`dict`: 合并后的字典。如果某个键出现多次:
|
||||
- 第一次:直接赋值;
|
||||
- 第二次及以上:转换为列表并追加。
|
||||
|
||||
### 示例
|
||||
```python
|
||||
data = {'name': 'Alice', 'hobby': 'reading', 'hobby': 'coding'}
|
||||
# 实际输入可能是 MultiDict({'hobby': ['reading', 'coding']})
|
||||
result = multiDict2Dict(data)
|
||||
# result == {'name': 'Alice', 'hobby': ['reading', 'coding']}
|
||||
```
|
||||
|
||||
> ⚠️ 注意:Python 字典本身不允许多个相同键,此函数适用于模拟场景或多值结构(如 Flask 的 `request.args`)。
|
||||
|
||||
---
|
||||
|
||||
## Class: `DictObjectEncoder(JSONEncoder)`
|
||||
|
||||
用于将 `DictObject` 实例序列化为 JSON 的自定义编码器。
|
||||
|
||||
### 方法
|
||||
#### `.default(o)`
|
||||
重写父类方法,当遇到无法序列化的对象时调用。
|
||||
|
||||
##### 参数
|
||||
- `o`: 待序列化的对象。
|
||||
|
||||
##### 行为
|
||||
调用对象的 `_addon()` 方法获取可序列化数据(需用户自行实现 `_addon` 方法)。
|
||||
⚠️ 当前实现依赖于对象存在 `_addon()` 方法,否则会报错。
|
||||
|
||||
##### 示例
|
||||
```python
|
||||
class MyObj(DictObject):
|
||||
def _addon(self):
|
||||
return {"custom": "value"}
|
||||
|
||||
json.dumps(MyObj(), cls=DictObjectEncoder)
|
||||
# 输出: {"custom": "value"}
|
||||
```
|
||||
|
||||
> 💡 提示:若未定义 `_addon()`,应避免直接使用此编码器。
|
||||
|
||||
---
|
||||
|
||||
## Class: `DictObject(dict)`
|
||||
|
||||
一个继承自 `dict` 的增强字典类,支持属性式访问、嵌套包装和深拷贝。
|
||||
|
||||
### 初始化
|
||||
```python
|
||||
obj = DictObject(key=value, other=[1,2])
|
||||
# 或
|
||||
obj = DictObject({'key': 'value'})
|
||||
```
|
||||
|
||||
### 特性
|
||||
- 支持 `obj.key` 获取/设置 `obj['key']`
|
||||
- 自动递归包装嵌套的 `dict` 和 `list`
|
||||
- 访问不存在的属性返回 `None`(不抛异常)
|
||||
- 支持 `del obj.key` 删除键
|
||||
|
||||
---
|
||||
|
||||
### 方法概览
|
||||
|
||||
| 方法 | 说明 |
|
||||
|------|------|
|
||||
| `__getattr__(key)` | 属性取值,不存在返回 `None` |
|
||||
| `__setattr__(key, value)` | 设置属性 |
|
||||
| `__delattr__(key)` | 删除属性,不存在则抛 `AttributeError` |
|
||||
| `__setitem__(key, value)` | 设置项,并自动包装值 |
|
||||
| `update(*args, **kwargs)` | 更新字典,自动包装新值 |
|
||||
| `to_dict()` | 转换为普通嵌套字典 |
|
||||
| `copy()` | 深拷贝当前对象 |
|
||||
| `_wrap(value)` *(static)* | 包装 dict/list 为 DictObject |
|
||||
| `_dict(value)` *(static)* | 解包 DictObject 为原生 dict |
|
||||
|
||||
---
|
||||
|
||||
### 详细方法说明
|
||||
|
||||
#### `__init__(*args, **kwargs)`
|
||||
初始化 `DictObject` 实例,并立即更新所有传入的数据。
|
||||
|
||||
##### 示例
|
||||
```python
|
||||
d = DictObject(a=1, b={'x': 2})
|
||||
print(d.b.x) # 输出: 2
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### `__getattr__(key)`
|
||||
使你可以使用点语法访问字典键。
|
||||
|
||||
- 成功:返回对应值。
|
||||
- 失败:返回 `None`(原被注释的 `raise AttributeError` 已禁用)。
|
||||
|
||||
> 🛑 不推荐用于判断是否存在字段,请使用 `in` 操作符。
|
||||
|
||||
##### 示例
|
||||
```python
|
||||
obj = DictObject(name="Bob")
|
||||
print(obj.name) # "Bob"
|
||||
print(obj.age) # None
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### `__setattr__(key, value)`
|
||||
允许通过 `obj.key = value` 设置字典键值。
|
||||
|
||||
内部调用 `self[key] = value`。
|
||||
|
||||
##### 示例
|
||||
```python
|
||||
obj = DictObject()
|
||||
obj.city = "Beijing"
|
||||
print(obj['city']) # "Beijing"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### `__delattr__(key)`
|
||||
删除指定属性(即字典键)。
|
||||
|
||||
- 若键不存在,抛出 `AttributeError`。
|
||||
- 否则从字典中移除该键。
|
||||
|
||||
##### 示例
|
||||
```python
|
||||
del obj.city
|
||||
# 等价于 del obj['city']
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### `__setitem__(key, value)`
|
||||
设置键值对,并通过 `_wrap()` 自动包装值。
|
||||
|
||||
这意味着嵌套的字典会被自动转为 `DictObject`。
|
||||
|
||||
##### 示例
|
||||
```python
|
||||
obj['nested'] = {'a': 1}
|
||||
print(type(obj['nested'])) # <class 'DictObject'>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### `update(*args, **kwargs)`
|
||||
批量更新字典内容,所有新增值都会经过 `_wrap()` 处理。
|
||||
|
||||
##### 示例
|
||||
```python
|
||||
obj.update({'list': [1, {'deep': 2}]})
|
||||
print(type(obj.list[1])) # DictObject
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### `to_dict()`
|
||||
将整个 `DictObject` 结构递归转换为原始的 Python 原生字典(去除 `DictObject` 类型)。
|
||||
|
||||
##### 返回
|
||||
`dict`: 完全由 `dict`, `list`, 基本类型组成的结构,适合 JSON 序列化。
|
||||
|
||||
##### 示例
|
||||
```python
|
||||
obj = DictObject(a=DictObject(b=2))
|
||||
plain = obj.to_dict()
|
||||
print(plain) # {'a': {'b': 2}}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### `copy()`
|
||||
创建一个深拷贝副本,包括所有嵌套结构都重新包装。
|
||||
|
||||
> ⚠️ 并非完全等同于 `copy.deepcopy()`,但对常见结构足够安全。
|
||||
|
||||
##### 返回
|
||||
新的 `DictObject` 实例。
|
||||
|
||||
##### 示例
|
||||
```python
|
||||
new_obj = obj.copy()
|
||||
new_obj.a.b = 999
|
||||
# 不影响原始 obj
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### `@staticmethod _wrap(value)`
|
||||
递归包装输入值中的 `dict` 和 `list`。
|
||||
|
||||
##### 规则
|
||||
- `dict` → `DictObject(dict)`
|
||||
- `list` → `[ _wrap(item) for item in list ]`
|
||||
- 其他类型保持不变
|
||||
|
||||
##### 示例
|
||||
```python
|
||||
wrapped = DictObject._wrap({'x': {'y': 1}})
|
||||
print(type(wrapped)) # DictObject
|
||||
print(type(wrapped['x'])) # DictObject
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### `@staticmethod _dict(value)`
|
||||
与 `_wrap` 相反,将 `DictObject` 或其嵌套结构还原为原生 `dict` / `list`。
|
||||
|
||||
##### 规则
|
||||
- `DictObject` → `.to_dict()`
|
||||
- `list` → 递归处理每个元素
|
||||
- 其他类型原样返回
|
||||
|
||||
##### 示例
|
||||
```python
|
||||
data = DictObject._dict(obj)
|
||||
# data 是纯 dict/list 结构
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 已注释掉的工厂函数 `dictObjectFactory`(建议扩展用)
|
||||
|
||||
该函数原本设计用于根据类名动态创建特定子类实例。
|
||||
|
||||
```python
|
||||
# 示例原型(当前被注释):
|
||||
# obj = dictObjectFactory("User", name="Tom", age=30)
|
||||
```
|
||||
|
||||
### 功能逻辑(潜在用途)
|
||||
1. 查找 `DictObject` 的子类中哪个满足 `isMe(_klassName__) == True`
|
||||
2. 若找到,则实例化该子类;否则回退到 `DictObject`
|
||||
|
||||
> ✅ 可作为插件式对象注册机制的基础,适合 ORM 或配置系统。
|
||||
|
||||
### 当前状态
|
||||
- 被注释,暂未启用。
|
||||
- 使用时需确保各子类实现了 `isMe(class_name)` 类方法。
|
||||
|
||||
---
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 示例 1:基础用法
|
||||
```python
|
||||
user = DictObject(
|
||||
name="Alice",
|
||||
profile={"age": 28, "city": "Shanghai"},
|
||||
hobbies=["reading", "hiking"]
|
||||
)
|
||||
|
||||
print(user.name) # Alice
|
||||
print(user.profile.city) # Shanghai
|
||||
user.email = "alice@example.com"
|
||||
print(user.email) # alice@example.com
|
||||
```
|
||||
|
||||
### 示例 2:JSON 序列化
|
||||
```python
|
||||
class Person(DictObject):
|
||||
def _addon(self):
|
||||
return self.to_dict() # 或添加额外元信息
|
||||
|
||||
p = Person(name="Bob", info={"job": "Engineer"})
|
||||
json_str = json.dumps(p, cls=DictObjectEncoder, indent=2)
|
||||
print(json_str)
|
||||
# {
|
||||
# "name": "Bob",
|
||||
# "info": {
|
||||
# "job": "Engineer"
|
||||
# }
|
||||
# }
|
||||
```
|
||||
|
||||
### 示例 3:多值字典处理
|
||||
```python
|
||||
form_data = {'tag': 'python', 'tag': 'web', 'author': 'admin'}
|
||||
# 实际可能是 MultiDict({'tag': ['python', 'web'], 'author': ['admin']})
|
||||
|
||||
flat = multiDict2Dict(form_data)
|
||||
print(flat['tag']) # ['python', 'web'] (如果是真实多值)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 注意事项与限制
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| ❌ `__getattr__` 返回 `None` | 导致无法区分“无此属性”和“属性值为 None” |
|
||||
| ⚠️ `_addon()` 必须实现 | 否则 `DictObjectEncoder` 会失败 |
|
||||
| 🔁 `_wrap` 是递归的 | 大型嵌套结构可能影响性能 |
|
||||
| 🧩 子类工厂未启用 | 如需动态构造,请解注并完善 `isMe()` 逻辑 |
|
||||
| 📦 不支持 tuple/set 自动包装 | 仅处理 `dict` 和 `list` |
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
`DictObject` 提供了一种简洁优雅的方式来处理嵌套数据结构,特别适用于:
|
||||
|
||||
- 配置文件解析(YAML/JSON)
|
||||
- API 响应数据建模
|
||||
- Web 请求参数处理
|
||||
- 构建轻量级数据传输对象(DTO)
|
||||
|
||||
结合 `multiDict2Dict` 和 `DictObjectEncoder`,可在前后端交互、日志记录、序列化等场景中大幅提升开发效率。
|
||||
|
||||
---
|
||||
|
||||
📌 **建议改进方向**:
|
||||
- 添加类型提示(Type Hints)
|
||||
- 引入 `__dir__()` 支持 IDE 自动补全
|
||||
- 实现更健壮的工厂模式或注册中心
|
||||
- 增加单元测试覆盖率
|
||||
|
||||
---
|
||||
|
||||
📄 *文档版本:v1.0*
|
||||
📅 *最后更新:2025-04-05*
|
||||
330
aidocs/dictObject.old.md
Normal file
330
aidocs/dictObject.old.md
Normal file
@ -0,0 +1,330 @@
|
||||
# `DictObject` 技术文档
|
||||
|
||||
```markdown
|
||||
# DictObject - 字典对象封装库
|
||||
|
||||
`DictObject` 是一个轻量级的 Python 工具类,用于将字典(`dict`)转换为支持属性访问的对象,并提供丰富的数据操作、序列化与嵌套结构处理能力。它允许通过点号语法访问字典键值,同时保留了标准字典的操作接口。
|
||||
|
||||
---
|
||||
|
||||
## 目录
|
||||
|
||||
- [功能概览](#功能概览)
|
||||
- [依赖说明](#依赖说明)
|
||||
- [核心函数](#核心函数)
|
||||
- [`multiDict2Dict(md)`](#multidict2dictmd)
|
||||
- [核心类](#核心类)
|
||||
- [`DictObject`](#dictobject)
|
||||
- [`DictObjectEncoder`](#dictobjectencoder)
|
||||
- [工厂方法](#工厂方法)
|
||||
- [`dictObjectFactory(_klassName__, **kwargs)`](#dictobjectfactory_klassname__-kwargs)
|
||||
- [使用示例](#使用示例)
|
||||
- [注意事项](#注意事项)
|
||||
|
||||
---
|
||||
|
||||
## 功能概览
|
||||
|
||||
- 将普通字典转为可点式访问的 `DictObject` 对象。
|
||||
- 支持嵌套字典、列表、元组自动转换。
|
||||
- 提供类似字典的标准方法(如 `.items()`, `.get()`, `.pop()` 等)。
|
||||
- 可过滤内置函数、方法等不可序列化的类型。
|
||||
- 支持 JSON 序列化(配合 `DictObjectEncoder`)。
|
||||
- 支持继承和动态子类查找的工厂模式创建实例。
|
||||
|
||||
---
|
||||
|
||||
## 依赖说明
|
||||
|
||||
```python
|
||||
import json
|
||||
from json import JSONEncoder
|
||||
from inspect import ismethod, isfunction, isbuiltin, isabstract
|
||||
```
|
||||
|
||||
- `json`: 用于 JSON 编码/解码。
|
||||
- `JSONEncoder`: 自定义 JSON 编码器基类。
|
||||
- `inspect` 模块:
|
||||
- `ismethod`, `isfunction`: 判断是否为方法或函数。
|
||||
- `isbuiltin`: 判断是否为内置函数。
|
||||
- `isabstract`: 判断是否为抽象方法(用于排除不可序列化对象)。
|
||||
|
||||
---
|
||||
|
||||
## 核心函数
|
||||
|
||||
### `multiDict2Dict(md)`
|
||||
|
||||
将可能包含重复键的多值字典(如 Web 表单提交中的 `MultiDict`)转换为标准字典。若某键对应多个值,则将其合并为列表。
|
||||
|
||||
#### 参数
|
||||
| 参数 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `md` | `dict` 或 `MultiDict` | 输入字典,可能含有重复键 |
|
||||
|
||||
#### 返回值
|
||||
- `dict`: 转换后的字典。若某个键有多个值,则其值为列表;否则保持原样。
|
||||
|
||||
#### 示例
|
||||
```python
|
||||
d = {'a': 1, 'b': 2, 'a': 3}
|
||||
result = multiDict2Dict(d)
|
||||
# result => {'a': [1, 3], 'b': 2}
|
||||
```
|
||||
|
||||
#### 实现逻辑
|
||||
- 遍历输入字典;
|
||||
- 若键不存在,直接赋值;
|
||||
- 若已存在且原值是列表,追加新值;
|
||||
- 否则将原值和新值构造成列表。
|
||||
|
||||
---
|
||||
|
||||
## 核心类
|
||||
|
||||
### `DictObject`
|
||||
|
||||
一个可动态扩展、支持属性访问的字典封装类。
|
||||
|
||||
#### 初始化:`__init__(**kw)`
|
||||
接受关键字参数初始化对象。
|
||||
|
||||
##### 参数
|
||||
| 参数 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `**kw` | `dict` | 关键字参数集合 |
|
||||
|
||||
##### 行为
|
||||
- 记录初始属性名到 `org_keys__`;
|
||||
- 使用 `__DOitem(v)` 处理每个值(递归构建嵌套结构);
|
||||
- 调用 `update()` 更新内部 `__dict__`。
|
||||
|
||||
---
|
||||
|
||||
#### 属性访问:`__getattr__(name)`
|
||||
|
||||
当调用未定义属性时,尝试从 `_addon()` 中获取该键的值。
|
||||
|
||||
- 如果存在,返回对应值;
|
||||
- 否则返回 `None`。
|
||||
|
||||
> ⚠️ 注意:这不会触发 `AttributeError`,需注意潜在静默失败问题。
|
||||
|
||||
---
|
||||
|
||||
#### 方法列表
|
||||
|
||||
| 方法 | 功能 |
|
||||
|------|------|
|
||||
| `update(kw)` | 更新对象属性(仅添加或修改非原始属性) |
|
||||
| `_addon()` | 获取用户添加的所有属性组成的字典(排除构造时的原始属性) |
|
||||
| `clear()` | 清除所有动态添加的属性 |
|
||||
| `get(name, default=None)` | 获取指定属性,不存在返回默认值 |
|
||||
| `pop(k, default=None)` | 删除并返回属性值 |
|
||||
| `popitem()` | 删除并返回任意一个 (key, value) 对 |
|
||||
| `items()` / `keys()` / `values()` | 返回动态属性的视图对象(类似字典) |
|
||||
| `__getitem__(name)` | 支持 `obj['key']` 语法访问 |
|
||||
| `__setitem__(name, value)` | 支持 `obj['key'] = value` 语法设置 |
|
||||
| `__delitem__(key)` | 支持 `del obj['key']` 删除属性 |
|
||||
| `__str__()` | 输出动态属性的字符串表示(即 `str(_addon())`) |
|
||||
| `__expr__()` | ❌ 存在错误:应为 `__repr__`,当前实现无效 |
|
||||
| `copy()` | 返回动态属性的浅拷贝字典 |
|
||||
| `to_dict()` | 深度转换为纯 `dict`(移除不可序列化内容) |
|
||||
| `dict_to_dict(dic)` | 递归转换字典,处理嵌套 `DictObject`、函数、内置对象等 |
|
||||
| `array_to_dict(v)` | 递归转换数组/元组,跳过函数等不可序列化项 |
|
||||
| `__DOArray(a)` | 将列表/元组中每一项用 `__DOitem` 处理 |
|
||||
| `__DOitem(i)` | 根据输入类型决定是否转换为 `DictObject` 或递归处理 |
|
||||
|
||||
---
|
||||
|
||||
#### 特殊方法说明
|
||||
|
||||
##### `to_dict()` 与 `dict_to_dict()`
|
||||
用于深度清理对象,生成可用于 JSON 序列化的纯净字典。
|
||||
|
||||
- 递归遍历嵌套结构;
|
||||
- 自动识别并转换:
|
||||
- `DictObject` → 调用 `.to_dict()`
|
||||
- `dict` → 递归调用 `dict_to_dict`
|
||||
- `list/tuple` → 调用 `array_to_dict`
|
||||
- 过滤以下类型(不包含在输出中):
|
||||
- 内置函数(`__builtins__`)
|
||||
- 函数、方法、内建函数、抽象方法(使用 `inspect` 判断)
|
||||
|
||||
##### `__DOitem(i)`
|
||||
对传入值进行类型判断并包装:
|
||||
|
||||
| 输入类型 | 处理方式 |
|
||||
|--------|---------|
|
||||
| `DictObject` | 直接返回 |
|
||||
| `dict` | 过滤非字符串键后构造新的 `DictObject(**i)` |
|
||||
| `list` 或 `tuple` | 转换为 `[ __DOitem(x) for x in i ]` |
|
||||
| 其他 | 原样返回 |
|
||||
|
||||
> ⚠️ 异常处理:构造失败时打印调试信息并重新抛出异常。
|
||||
|
||||
---
|
||||
|
||||
#### 类方法
|
||||
|
||||
##### `@classmethod isMe(cls, name)`
|
||||
判断类名是否等于 `'DictObject'`。
|
||||
|
||||
> 用于工厂函数中判断目标类。
|
||||
|
||||
---
|
||||
|
||||
### `DictObjectEncoder(JSONEncoder)`
|
||||
|
||||
自定义 JSON 编码器,用于将 `DictObject` 实例序列化为 JSON。
|
||||
|
||||
#### 方法:`default(o)`
|
||||
重写 `JSONEncoder.default()`,返回对象的 `_addon()` 字典。
|
||||
|
||||
##### 示例
|
||||
```python
|
||||
obj = DictObject(name="Alice", age=30)
|
||||
json_str = json.dumps(obj, cls=DictObjectEncoder)
|
||||
# 输出: {"name": "Alice", "age": 30}
|
||||
```
|
||||
|
||||
> ✅ 支持嵌套结构序列化(前提是已通过 `to_dict()` 处理干净)。
|
||||
|
||||
---
|
||||
|
||||
## 工厂方法
|
||||
|
||||
### `dictObjectFactory(_klassName__, **kwargs)`
|
||||
|
||||
根据类名字符串动态创建 `DictObject` 或其子类的实例。
|
||||
|
||||
#### 参数
|
||||
| 参数 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `_klassName__` | `str` | 类名(如 `'DictObject'`) |
|
||||
| `**kwargs` | `dict` | 构造参数 |
|
||||
|
||||
#### 返回值
|
||||
- `DictObject` 或其子类的实例。
|
||||
|
||||
#### 内部函数:`findSubclass(_klassName__, klass)`
|
||||
递归查找 `klass` 的所有子类中满足 `isMe(_klassName__)` 的类。
|
||||
|
||||
#### 流程
|
||||
1. 若类名为 `'DictObject'`,直接返回 `DictObject(**kwargs)`;
|
||||
2. 否则递归查找子类;
|
||||
3. 找到则调用该子类构造函数;
|
||||
4. 未找到仍返回 `DictObject` 实例;
|
||||
5. 出错时打印日志并抛出异常。
|
||||
|
||||
> ✅ 支持插件式扩展:可通过继承 `DictObject` 并重写 `isMe()` 实现自定义类匹配。
|
||||
|
||||
---
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 1. 基本用法:属性访问
|
||||
|
||||
```python
|
||||
data = {'name': 'Bob', 'age': 25, 'city': 'Beijing'}
|
||||
obj = DictObject(**data)
|
||||
|
||||
print(obj.name) # Bob
|
||||
print(obj['age']) # 25
|
||||
print(obj.get('city')) # Beijing
|
||||
```
|
||||
|
||||
### 2. 嵌套字典自动转换
|
||||
|
||||
```python
|
||||
nested = {
|
||||
'user': {
|
||||
'id': 1,
|
||||
'profile': {'name': 'Alice', 'tags': ['dev', 'py']}
|
||||
}
|
||||
}
|
||||
obj = DictObject(**nested)
|
||||
print(obj.user.profile.name) # Alice
|
||||
print(obj.to_dict())
|
||||
# {'user': {'id': 1, 'profile': {'name': 'Alice', 'tags': ['dev', 'py']}}}
|
||||
```
|
||||
|
||||
### 3. JSON 序列化
|
||||
|
||||
```python
|
||||
import json
|
||||
from dictObject import DictObject, DictObjectEncoder
|
||||
|
||||
obj = DictObject(title="My App", version=1.0)
|
||||
json_str = json.dumps(obj, cls=DictObjectEncoder, indent=2)
|
||||
print(json_str)
|
||||
```
|
||||
|
||||
输出:
|
||||
```json
|
||||
{
|
||||
"title": "My App",
|
||||
"version": 1.0
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 工厂模式创建对象
|
||||
|
||||
```python
|
||||
# 假设有子类 MyData 继承 DictObject 并重写了 isMe()
|
||||
class MyData(DictObject):
|
||||
@classmethod
|
||||
def isMe(cls, name):
|
||||
return name == 'MyData'
|
||||
|
||||
obj = dictObjectFactory('MyData', x=1, y=2)
|
||||
print(type(obj)) # <class '__main__.MyData'>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **`__expr__` 方法命名错误**
|
||||
- 正确应为 `__repr__`,当前 `__expr__` 不会被 Python 解释器调用。
|
||||
- 建议修复为:
|
||||
```python
|
||||
def __repr__(self):
|
||||
return repr(self._addon())
|
||||
```
|
||||
|
||||
2. **`__builtins__` 过滤不完全可靠**
|
||||
- 当前只检查键名为 `'__builtins__'`,建议增强检测逻辑。
|
||||
|
||||
3. **性能考虑**
|
||||
- 每次调用 `_addon()` 都会重新计算差异,频繁调用可能影响性能。
|
||||
- 可缓存结果或改用更高效的数据结构。
|
||||
|
||||
4. **异常处理**
|
||||
- `__DOitem` 中捕获异常后打印信息但继续抛出,适合开发调试。
|
||||
- 生产环境建议记录日志而非打印。
|
||||
|
||||
5. **线程安全性**
|
||||
- 未做线程安全设计,多线程环境下慎用共享实例。
|
||||
|
||||
6. **内存泄漏风险**
|
||||
- `org_keys__` 记录初始属性,若后续手动增删较多,可能导致 `_addon()` 判断不准。
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
`DictObject` 是一个灵活实用的字典对象封装工具,适用于配置管理、API 数据解析、动态对象构建等场景。结合 `dictObjectFactory` 和 `DictObjectEncoder`,可实现高度可扩展的数据模型系统。
|
||||
|
||||
✅ 推荐用于快速原型开发、DSL 设计、Web 请求参数封装等领域。
|
||||
|
||||
🔧 建议改进点:
|
||||
- 修复 `__expr__` → `__repr__`
|
||||
- 添加 `__contains__`, `__len__` 支持
|
||||
- 提供 from_dict() 类方法
|
||||
- 支持冻结模式(防止修改)
|
||||
|
||||
---
|
||||
> 文档版本:v1.0
|
||||
> 最后更新:2025-04-05
|
||||
```
|
||||
288
aidocs/easyExcel.md
Normal file
288
aidocs/easyExcel.md
Normal file
@ -0,0 +1,288 @@
|
||||
# `EasyExcel` 技术文档
|
||||
|
||||
```markdown
|
||||
# EasyExcel - Python Excel 操作工具类
|
||||
|
||||
`EasyExcel` 是一个基于 `win32com.client` 的轻量级 Python 工具类,用于简化对 Microsoft Excel 文件的自动化操作。它封装了常用功能如读写单元格、插入图片、复制工作表等,适用于 Windows 环境下与 Excel 应用程序交互。
|
||||
|
||||
> ⚠️ **注意**:该模块依赖于 COM 接口,仅支持 Windows 平台,并需要本地安装 Microsoft Excel。
|
||||
|
||||
---
|
||||
|
||||
## 目录
|
||||
|
||||
- [简介](#简介)
|
||||
- [依赖环境](#依赖环境)
|
||||
- [类定义](#类定义)
|
||||
- [方法说明](#方法说明)
|
||||
- [`__init__`](#__init__)
|
||||
- [`save`](#save)
|
||||
- [`setSheetName`](#setsheetname)
|
||||
- [`newSheet`](#newsheet)
|
||||
- [`close`](#close)
|
||||
- [`getCell`](#getcell)
|
||||
- [`setCell`](#setcell)
|
||||
- [`getRange`](#getrange)
|
||||
- [`addPicture`](#addpicture)
|
||||
- [`cpSheet`](#cpsheet)
|
||||
- [使用示例](#使用示例)
|
||||
- [注意事项](#注意事项)
|
||||
|
||||
---
|
||||
|
||||
## 简介
|
||||
|
||||
`EasyExcel` 类提供了一种简洁的方式访问和操作 Excel 工作簿(`.xls` 或 `.xlsx`),包括打开/创建文件、读取和修改单元格数据、插入图像、复制工作表以及保存和关闭文件。
|
||||
|
||||
---
|
||||
|
||||
## 依赖环境
|
||||
|
||||
- 操作系统:Windows
|
||||
- Python 包:
|
||||
- `pywin32`(通过 `pip install pywin32` 安装)
|
||||
- 软件依赖:Microsoft Excel 已安装并可正常运行
|
||||
|
||||
---
|
||||
|
||||
## 类定义
|
||||
|
||||
```python
|
||||
class EasyExcel:
|
||||
"""
|
||||
A utility to make it easier to get at Excel. Remembering
|
||||
to save the data is your problem, as is error handling.
|
||||
Operates on one workbook at a time.
|
||||
"""
|
||||
```
|
||||
|
||||
此类在实例化时启动 Excel 应用程序 COM 对象,可加载现有工作簿或新建空白工作簿。
|
||||
|
||||
---
|
||||
|
||||
## 方法说明
|
||||
|
||||
### `__init__(filename=None)`
|
||||
|
||||
初始化 `EasyExcel` 实例。
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
|------|------|------|
|
||||
| `filename` | str 或 None | 要打开的 Excel 文件路径;若为 `None`,则创建新工作簿 |
|
||||
|
||||
**行为说明**:
|
||||
- 若提供 `filename`,尝试打开指定文件。
|
||||
- 否则创建一个新的空白工作簿。
|
||||
- 自动创建 `Excel.Application` COM 对象。
|
||||
|
||||
**示例**:
|
||||
```python
|
||||
xls = EasyExcel("D:\\test.xls") # 打开已有文件
|
||||
xls_new = EasyExcel() # 创建新文件
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `save(newfilename=None)`
|
||||
|
||||
保存当前工作簿。
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
|------|------|------|
|
||||
| `newfilename` | str 或 None | 新文件路径。如果提供,则另存为该路径;否则执行常规保存 |
|
||||
|
||||
**行为说明**:
|
||||
- 如果传入 `newfilename`,使用 `SaveAs` 保存到新位置,并更新内部文件名。
|
||||
- 否则调用 `Save()` 进行原地保存。
|
||||
|
||||
**示例**:
|
||||
```python
|
||||
xls.save() # 原路径保存
|
||||
xls.save("D:\\backup.xls") # 另存为新路径
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `setSheetName(sheet, name)`
|
||||
|
||||
重命名指定工作表。
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
|------|------|------|
|
||||
| `sheet` | int / str | 工作表索引(从1开始)或名称 |
|
||||
| `name` | str | 新的工作表名称 |
|
||||
|
||||
**示例**:
|
||||
```python
|
||||
xls.setSheetName(1, "Data Sheet")
|
||||
xls.setSheetName("Sheet1", "Results")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `newSheet(sheetName)`
|
||||
|
||||
> ❌ 当前为空实现(`pass`),尚未实现添加新工作表的功能。
|
||||
|
||||
**建议扩展实现**:
|
||||
```python
|
||||
def newSheet(self, sheetName):
|
||||
self.xlBook.Worksheets.Add().Name = sheetName
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `close()`
|
||||
|
||||
关闭工作簿并退出 Excel 应用程序,释放资源。
|
||||
|
||||
**行为说明**:
|
||||
- 不自动保存更改(`SaveChanges=0`)。
|
||||
- 调用 `Quit()` 退出 Excel 进程。
|
||||
- 删除对 `xlApp` 的引用以确保清理。
|
||||
|
||||
> ✅ 推荐在脚本结束前显式调用此方法以避免后台残留 Excel 进程。
|
||||
|
||||
**示例**:
|
||||
```python
|
||||
xls.close()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `getCell(sheet, row, col)`
|
||||
|
||||
获取指定单元格的值。
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
|------|------|------|
|
||||
| `sheet` | int / str | 工作表名称或索引 |
|
||||
| `row` | int | 行号(从1开始) |
|
||||
| `col` | int | 列号(从1开始) |
|
||||
|
||||
**返回值**:单元格内容(字符串、数字、日期等)
|
||||
|
||||
**示例**:
|
||||
```python
|
||||
value = xls.getCell("Sheet1", 1, 1) # 获取 A1 单元格值
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `setCell(sheet, row, col, value)`
|
||||
|
||||
设置指定单元格的值。
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
|------|------|------|
|
||||
| `sheet` | int / str | 工作表名称或索引 |
|
||||
| `row` | int | 行号(从1开始) |
|
||||
| `col` | int | 列号(从1开始) |
|
||||
| `value` | any | 要写入的值 |
|
||||
|
||||
**示例**:
|
||||
```python
|
||||
xls.setCell("Sheet1", 1, 1, "Hello World")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `getRange(sheet, row1, col1, row2, col2)`
|
||||
|
||||
读取矩形区域内的所有单元格值,返回二维元组。
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
|------|------|------|
|
||||
| `sheet` | int / str | 工作表名称或索引 |
|
||||
| `row1`, `col1` | int | 起始单元格行列坐标 |
|
||||
| `row2`, `col2` | int | 结束单元格行列坐标 |
|
||||
|
||||
**返回值**:嵌套元组结构 `( (r1c1, r1c2), (r2c1, r2c2) )`
|
||||
|
||||
**示例**:
|
||||
```python
|
||||
data = xls.getRange("Sheet1", 1, 1, 3, 2)
|
||||
# 返回类似: (('A1', 'B1'), ('A2', 'B2'), ('A3', 'B3'))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `addPicture(sheet, pictureName, Left, Top, Width, Height)`
|
||||
|
||||
在指定工作表中插入图片。
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
|------|------|------|
|
||||
| `sheet` | str/int | 目标工作表 |
|
||||
| `pictureName` | str | 图片文件完整路径(如 BMP、JPG、PNG) |
|
||||
| `Left` | float | 图片左上角 X 坐标(像素) |
|
||||
| `Top` | float | 图片左上角 Y 坐标(像素) |
|
||||
| `Width` | float | 图片宽度(像素) |
|
||||
| `Height` | float | 图片高度(像素) |
|
||||
|
||||
**示例**:
|
||||
```python
|
||||
xls.addPicture('Sheet1', r'C:\screenshot.bmp', 20, 20, 1000, 1000)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `cpSheet(before)`
|
||||
|
||||
复制第一个工作表(硬编码为 `Worksheets(1)`),并将副本放在指定工作表之前。
|
||||
|
||||
> ⚠️ **注意**:参数 `before` 实际未被使用,逻辑固定复制第一个工作表并在其前插入副本。
|
||||
|
||||
**建议改进**:
|
||||
```python
|
||||
def cpSheet(self, before):
|
||||
shts = self.xlBook.Worksheets
|
||||
shts(1).Copy(Before=shts(before))
|
||||
```
|
||||
|
||||
**当前行为**:
|
||||
```python
|
||||
shts(1).Copy(None, shts(1)) # 在 Sheet1 后面插入副本
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 使用示例
|
||||
|
||||
以下是一个完整的使用案例:
|
||||
|
||||
```python
|
||||
if __name__ == "__main__":
|
||||
PNFILE = r'c:\screenshot.bmp'
|
||||
xls = EasyExcel(r'D:\test.xls')
|
||||
xls.addPicture('Sheet1', PNFILE, 20, 20, 1000, 1000)
|
||||
xls.cpSheet('Sheet1') # 复制第一个工作表
|
||||
xls.save() # 保存更改
|
||||
xls.close() # 关闭并释放资源
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **必须手动调用 `save()` 和 `close()`**
|
||||
类不会自动保存或清理资源,请务必在程序结束前调用 `close()` 防止 Excel 进程残留。
|
||||
|
||||
2. **错误处理缺失**
|
||||
当前代码未包含异常捕获机制,建议在生产环境中添加 try-except 包裹关键操作。
|
||||
|
||||
3. **性能提示**
|
||||
COM 通信较慢,频繁读写单元格会影响性能。建议批量操作使用 `getRange` / `setRange` 方式优化。
|
||||
|
||||
4. **线程安全**
|
||||
`win32com.client.Dispatch('Excel.Application')` 不是线程安全的,不建议多线程并发操作。
|
||||
|
||||
5. **文件格式兼容性**
|
||||
支持 `.xls` 和 `.xlsx` 格式,但需注意旧版 Excel 可能无法打开新版格式。
|
||||
|
||||
---
|
||||
|
||||
## 许可与版权
|
||||
|
||||
此代码为开源工具片段,可用于学习与项目集成。请遵守 Python 及 Microsoft Excel 相关许可协议。
|
||||
```
|
||||
197
aidocs/eventproperty.md
Normal file
197
aidocs/eventproperty.md
Normal file
@ -0,0 +1,197 @@
|
||||
# 事件绑定与属性监听技术文档
|
||||
|
||||
## 概述
|
||||
|
||||
本文档介绍一个基于 `EventDispatcher` 的事件系统扩展,通过 `EventProperty` 类实现可观察的类属性。当属性值发生变化时,会自动触发对应的事件,并通知所有注册的监听器。
|
||||
|
||||
该模块结合了事件分发机制和 Python 描述符协议,实现了简洁、灵活的状态变化响应模式,适用于 GUI 编程、状态管理、MVVM 架构等场景。
|
||||
|
||||
---
|
||||
|
||||
## 依赖模块
|
||||
|
||||
- `eventpy.eventdispatcher.EventDispatcher`:基础事件分发类。
|
||||
- `appPublic.dictObject.DictObject`:用于封装事件数据的对象,支持点语法访问属性。
|
||||
|
||||
---
|
||||
|
||||
## 扩展方法:`bind` 和 `unbind`
|
||||
|
||||
为了简化事件监听器的注册与移除操作,为 `EventDispatcher` 类动态添加了两个便捷方法:
|
||||
|
||||
### `bind(self, eventname, handler)`
|
||||
将指定处理器绑定到某个事件名上。
|
||||
|
||||
| 参数 | 类型 | 说明 |
|
||||
|------------|----------|--------------------------|
|
||||
| `self` | object | EventDispatcher 实例 |
|
||||
| `eventname`| str | 事件名称(字符串标识) |
|
||||
| `handler` | callable | 事件触发时调用的函数或方法 |
|
||||
|
||||
> 等价于调用 `appendListener(eventname, handler)`
|
||||
|
||||
### `unbind(self, eventname, handler)`
|
||||
从指定事件中移除处理器。
|
||||
|
||||
| 参数 | 类型 | 说明 |
|
||||
|------------|----------|--------------------------|
|
||||
| `self` | object | EventDispatcher 实例 |
|
||||
| `eventname`| str | 事件名称 |
|
||||
| `handler` | callable | 要移除的处理器函数 |
|
||||
|
||||
> 等价于调用 `removeListener(eventname, handler)`
|
||||
|
||||
::: warning 注意
|
||||
在当前实现中,`unbind` 方法可能存在未正确移除监听器的问题(见主程序注释),需确保 `EventDispatcher` 原生的 `removeListener` 方法功能正常。
|
||||
:::
|
||||
|
||||
```python
|
||||
EventDispatcher.bind = bind
|
||||
EventDispatcher.unbind = unbind
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 核心类:`EventProperty`
|
||||
|
||||
`EventProperty` 是一个描述符类,用于定义能够触发事件的类级属性。
|
||||
|
||||
### 初始化
|
||||
|
||||
```python
|
||||
def __init__(self, event_name, initial_value=None)
|
||||
```
|
||||
|
||||
| 参数 | 类型 | 说明 |
|
||||
|------------------|-------|----------------------------------------|
|
||||
| `event_name` | str | 属性变更时触发的事件名称 |
|
||||
| `initial_value` | any | 属性的初始值(默认为 `None`) |
|
||||
|
||||
### 描述符行为
|
||||
|
||||
#### `__get__(self, instance, owner)`
|
||||
返回属性当前值。
|
||||
|
||||
- 若通过类访问(`instance` 为 `None`),返回描述符实例本身。
|
||||
- 若通过实例访问,返回内部存储的 `_value`。
|
||||
|
||||
#### `__set__(self, instance, value)`
|
||||
设置新值并触发事件(仅当值发生改变时)。
|
||||
|
||||
1. 比较新旧值,若相同则不执行任何操作。
|
||||
2. 更新内部 `_value`。
|
||||
3. 创建一个 `DictObject` 实例封装事件数据:
|
||||
- `.target`: 触发事件的实例对象
|
||||
- `.data`: 新值
|
||||
- `.event`: 事件名称
|
||||
4. 调用 `instance.dispatch(event_name, data)` 分发事件。
|
||||
|
||||
---
|
||||
|
||||
## 使用示例
|
||||
|
||||
以下代码演示如何使用 `EventProperty` 和事件绑定机制:
|
||||
|
||||
```python
|
||||
class SomeClass(EventDispatcher):
|
||||
state = EventProperty('onstate', 0) # 初始值为 0,变化时触发 'onstate'
|
||||
age = EventProperty('onage', 20) # 初始值为 20,变化时触发 'onage'
|
||||
|
||||
def __init__(self):
|
||||
super().__init__() # 初始化父类事件系统
|
||||
```
|
||||
|
||||
### 定义监听器
|
||||
|
||||
```python
|
||||
def observer1(data):
|
||||
print(f"Observer 1 received: {data}")
|
||||
|
||||
def observer2(data):
|
||||
print(f"Observer 2 received: {data}")
|
||||
|
||||
def observer3(data):
|
||||
print(f"Observer 3 received: {data}")
|
||||
```
|
||||
|
||||
每个监听器接收一个 `data` 参数,其类型为 `DictObject`,包含:
|
||||
|
||||
- `data.target`: 发生变化的实例
|
||||
- `data.data`: 新的属性值
|
||||
- `data.event`: 事件名称(如 `'onstate'`)
|
||||
|
||||
### 绑定事件监听
|
||||
|
||||
```python
|
||||
si = SomeClass()
|
||||
si.bind('onstate', observer1)
|
||||
si.bind('onstate', observer2)
|
||||
si.bind('onage', observer3)
|
||||
```
|
||||
|
||||
### 属性赋值触发事件
|
||||
|
||||
```python
|
||||
si.state = 10
|
||||
# 输出:
|
||||
# Observer 1 received: <DictObject target=... data=10 event='onstate'>
|
||||
# Observer 2 received: <DictObject target=... data=10 event='onstate'>
|
||||
|
||||
si.state = 20
|
||||
# 输出:
|
||||
# Observer 1 received: ...
|
||||
# Observer 2 received: ...
|
||||
|
||||
si.age = 30
|
||||
# 输出:
|
||||
# Observer 3 received: <DictObject target=... data=30 event='onage'>
|
||||
```
|
||||
|
||||
每次修改 `state` 或 `age`,只要值不同,就会广播对应事件。
|
||||
|
||||
---
|
||||
|
||||
## 注意事项与限制
|
||||
|
||||
1. **单例值共享问题**
|
||||
多个实例共享同一个 `EventProperty` 描述符中的 `_value`?
|
||||
❌ 实现中存在潜在 bug:`_value` 是描述符实例变量,如果多个实例共用同一描述符,则可能造成状态污染。
|
||||
✅ 正确做法应将值存储在实例字典中(例如使用 `instance.__dict__` 或弱引用映射)。当前实现是**非实例安全的**!
|
||||
|
||||
> ⚠️ 当前实现中,所有实例共享 `_value`,这是严重缺陷!建议改进如下:
|
||||
|
||||
```python
|
||||
def __set__(self, instance, value):
|
||||
if getattr(instance, f"_{self.event_name}_value") != value:
|
||||
setattr(instance, f"_{self.event_name}_value", value)
|
||||
# ... dispatch event
|
||||
```
|
||||
|
||||
2. **`unbind` 可能无效**
|
||||
示例中注释指出 `unbind` 存在错误,可能是 `removeListener` 实现问题或函数引用不一致导致无法匹配。
|
||||
|
||||
3. **事件数据结构**
|
||||
推荐统一规范 `DictObject` 的字段命名,便于前端或其他模块解析。
|
||||
|
||||
---
|
||||
|
||||
## 改进建议
|
||||
|
||||
| 项目 | 建议 |
|
||||
|------|------|
|
||||
| 状态隔离 | 将 `_value` 存储在实例上而非描述符中,避免跨实例污染 |
|
||||
| 类型提示 | 添加类型注解提升可维护性 |
|
||||
| 文档字符串 | 为类和方法补充 docstring |
|
||||
| 单元测试 | 添加测试用例验证绑定、解绑、事件触发逻辑 |
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
本模块通过组合 `EventDispatcher` 和描述符机制,提供了一种声明式的方式来实现属性变化监听。尽管当前实现存在一些设计缺陷(特别是状态共享问题),但整体架构清晰,易于扩展。
|
||||
|
||||
适用于需要轻量级响应式编程能力的 Python 应用,经过适当优化后可用于生产环境。
|
||||
|
||||
---
|
||||
|
||||
> ✅ 提示:建议重构 `EventProperty` 以支持实例级状态存储,确保线程安全和多实例兼容性。
|
||||
385
aidocs/exceldata.md
Normal file
385
aidocs/exceldata.md
Normal file
@ -0,0 +1,385 @@
|
||||
# ExcelData 模块技术文档
|
||||
|
||||
## 简介
|
||||
|
||||
`ExcelData` 是一个基于 `xlrd` 的 Python 工具类,用于从 Excel 文件(`.xls` 或 `.xlsx`)中读取结构化数据,并将其转换为 Python 字典或列表结构。支持类型转换、嵌套结构解析、注释跳过、跨文件引用(include)等功能,适用于配置文件、数据导入等场景。
|
||||
|
||||
该模块特别适合将 Excel 表格用作轻量级数据库或配置管理工具。
|
||||
|
||||
---
|
||||
|
||||
## 依赖
|
||||
|
||||
- `xlrd`:用于读取 Excel 文件
|
||||
- `datetime`:处理日期类型单元格
|
||||
- `os`, `sys`:系统相关操作
|
||||
- `appPublic.strUtils.lrtrim`:字符串去首尾空白(需确保此模块存在)
|
||||
|
||||
> ⚠️ 注意:`appPublic.strUtils` 是外部自定义模块,必须在项目路径中可用。
|
||||
|
||||
---
|
||||
|
||||
## 核心功能
|
||||
|
||||
- 自动识别并跳过注释行(以 `#` 开头)
|
||||
- 支持多种数据结构标记(如 `__dict__`, `__list__`, `__include__`)
|
||||
- 支持字段类型自动转换(`int`, `float`, `str`, `list`)
|
||||
- 支持多表加载与嵌套结构解析
|
||||
- 支持跨 Excel 文件包含(include)
|
||||
- 可扩展性强,便于集成到其他系统中
|
||||
|
||||
---
|
||||
|
||||
## 全局变量说明
|
||||
|
||||
### `TCS` 类型映射表
|
||||
|
||||
```python
|
||||
TCS = {
|
||||
'int': int,
|
||||
'float': float,
|
||||
'str': str,
|
||||
}
|
||||
```
|
||||
|
||||
用于定义支持的类型转换标识符及其对应的 Python 类型。
|
||||
|
||||
---
|
||||
|
||||
## 辅助函数
|
||||
|
||||
### `isEmptyCell(cell) → bool`
|
||||
|
||||
判断某个单元格是否为空。
|
||||
|
||||
**参数:**
|
||||
- `cell`: xlrd 单元格对象
|
||||
|
||||
**返回值:**
|
||||
- `True` 如果是空单元格,否则 `False`
|
||||
|
||||
---
|
||||
|
||||
### `isCommentValue(v) → bool`
|
||||
|
||||
判断某值是否为注释(即字符串且以 `#` 开头)。
|
||||
|
||||
**参数:**
|
||||
- `v`: 任意值
|
||||
|
||||
**返回值:**
|
||||
- `True` 如果是注释,否则 `False`
|
||||
|
||||
---
|
||||
|
||||
### `purekey(k) → str`
|
||||
|
||||
提取键名中的实际名称,去除冒号后的类型/指令信息。
|
||||
|
||||
例如:
|
||||
```python
|
||||
purekey("name:int") # 返回 "name"
|
||||
purekey("age") # 返回 "age"
|
||||
```
|
||||
|
||||
**参数:**
|
||||
- `k` (str): 原始键名
|
||||
|
||||
**返回值:**
|
||||
- 不含类型修饰的纯键名
|
||||
|
||||
---
|
||||
|
||||
### `castedValue(v, k) → any`
|
||||
|
||||
根据键名中的类型标识对值进行类型转换或结构处理。
|
||||
|
||||
**参数:**
|
||||
- `v`: 待处理的值
|
||||
- `k`: 键名(可能包含类型信息,如 `:int`, `:list`)
|
||||
|
||||
**支持的操作:**
|
||||
- `:int`, `:float`, `:str`:执行相应类型转换
|
||||
- `:list`:将字符串按逗号分割,或包装非列表为列表
|
||||
- 多重标签支持(如 `key:list:int` 表示转为整数列表)
|
||||
|
||||
**返回值:**
|
||||
- 转换后的值
|
||||
|
||||
**示例:**
|
||||
|
||||
| 输入 (`v`, `k`) | 输出 |
|
||||
|------------------|------|
|
||||
| `"123"`, `"age:int"` | `123` (int) |
|
||||
| `"1.5,2.7"`, `"values:list:float"` | `[1.5, 2.7]` |
|
||||
| `42`, `"tags:list"` | `[42]` |
|
||||
|
||||
---
|
||||
|
||||
## 主要类:`ExcelData`
|
||||
|
||||
### 构造函数 `__init__(xlsfile, encoding='UTF8', startrow=0, startcol=0)`
|
||||
|
||||
初始化 Excel 数据读取器。
|
||||
|
||||
**参数:**
|
||||
- `xlsfile` (str): Excel 文件路径
|
||||
- `encoding` (str): 文本编码,默认 `'UTF8'`
|
||||
- `startrow` (int): 起始行索引(未使用,固定为 0)
|
||||
- `startcol` (int): 起始列索引(未使用,固定为 0)
|
||||
|
||||
**行为:**
|
||||
- 打开 Excel 文件
|
||||
- 调用 `dataload()` 加载所有工作表数据
|
||||
- 存储于 `self._dataset`
|
||||
|
||||
---
|
||||
|
||||
### 属性与方法
|
||||
|
||||
#### `cellvalue(sheet, x, y) → any`
|
||||
|
||||
获取指定位置单元格的值,并做必要处理。
|
||||
|
||||
**参数:**
|
||||
- `sheet`: xlrd sheet 对象
|
||||
- `x`, `y`: 行列索引(从 0 开始)
|
||||
|
||||
**返回值:**
|
||||
- `None`:空单元格
|
||||
- `datetime.date`:日期类型
|
||||
- 处理过的字符串(去空格、编码转换)
|
||||
|
||||
---
|
||||
|
||||
#### `isCommentCell(cell) → bool`
|
||||
|
||||
判断单元格是否为注释。
|
||||
|
||||
**逻辑:**
|
||||
- 非空
|
||||
- 内容是字符串且以 `#` 开头
|
||||
|
||||
---
|
||||
|
||||
#### `dateMode() → int`
|
||||
|
||||
返回 Excel 的日期模式(`0` 表示 1900 基准,`1` 表示 1904 基准),由 `xlrd` 提供。
|
||||
|
||||
---
|
||||
|
||||
#### `trimedValue(v) → any`
|
||||
|
||||
对值进行清理:
|
||||
- 统一编码为指定格式(默认 UTF-8)
|
||||
- 使用 `lrtrim` 去除首尾空白字符
|
||||
|
||||
---
|
||||
|
||||
#### `dataload() → dict`
|
||||
|
||||
加载整个 Excel 文件的所有工作表。
|
||||
|
||||
**返回值:**
|
||||
- 字典,键为工作表名(已 trim),值为调用 `loadSheetData(sheet)` 的结果
|
||||
|
||||
---
|
||||
|
||||
#### `findDataRange(sheet, pos, maxr) → int`
|
||||
|
||||
查找当前数据块的最大有效行号。
|
||||
|
||||
**用途:**
|
||||
- 在字典结构中确定子结构边界
|
||||
|
||||
**参数:**
|
||||
- `sheet`: 当前工作表
|
||||
- `pos`: 起始坐标 `(x, y)`
|
||||
- `maxr`: 最大行限制
|
||||
|
||||
**返回值:**
|
||||
- 第一个非空行的行号(向下扫描),用于分段解析
|
||||
|
||||
---
|
||||
|
||||
#### `loadSheetData(sheet) → any`
|
||||
|
||||
加载单个工作表的数据,调用 `loadSheetDataRange(...)`。
|
||||
|
||||
---
|
||||
|
||||
#### `include(filename, id) → any`
|
||||
|
||||
从另一个 Excel 文件中包含数据。
|
||||
|
||||
**参数:**
|
||||
- `filename`: 要包含的 Excel 文件路径
|
||||
- `id`: 访问路径,形如 `['sheet'][key]`,支持点式访问
|
||||
|
||||
**实现方式:**
|
||||
- 创建新的 `ExcelData` 实例
|
||||
- 使用 `exec` 动态求值表达式 `data[id]`
|
||||
|
||||
⚠️ **注意:** 使用了 `exec`,存在安全风险,请仅用于可信文件!
|
||||
|
||||
---
|
||||
|
||||
#### `loadSingleData(sheet, pos) → any`
|
||||
|
||||
加载单个值或一行多个值。
|
||||
|
||||
**行为:**
|
||||
- 若只有一列,则返回单一值
|
||||
- 否则逐列读取直到遇到空或注释
|
||||
- 特殊处理 `__include__` 指令
|
||||
|
||||
---
|
||||
|
||||
#### `loadDictData(sheet, pos, maxr) → dict`
|
||||
|
||||
以字典形式加载数据块。
|
||||
|
||||
**规则:**
|
||||
- 每行第一列为 key(支持类型标注)
|
||||
- value 从下一列开始加载
|
||||
- 支持 `records:list` 标记加载记录集
|
||||
- 支持递归结构
|
||||
|
||||
---
|
||||
|
||||
#### `loadSheetDataRange(sheet, pos, maxr) → any`
|
||||
|
||||
核心调度函数,根据首单元格内容决定如何解析数据。
|
||||
|
||||
**特殊关键字识别:**
|
||||
| 关键字 | 含义 |
|
||||
|--------|------|
|
||||
| `__dict__` | 解析为字典或字典列表(支持嵌套) |
|
||||
| `__list__` | 解析为列表(每行一个元素) |
|
||||
| `__include__` | 包含外部文件数据 |
|
||||
| 注释或空 | 忽略并继续 |
|
||||
|
||||
---
|
||||
|
||||
#### `loadRecords(sheet, pos, maxr) → list[dict]`
|
||||
|
||||
将表格解析为记录列表(类似 CSV)。
|
||||
|
||||
**流程:**
|
||||
- 第一行作为字段名(可带类型)
|
||||
- 后续每行为一条记录
|
||||
- 忽略空列和注释列
|
||||
- 使用 `castedValue` 进行类型转换
|
||||
|
||||
**返回值:**
|
||||
- 列表,每个元素是一个字段名→值的字典
|
||||
|
||||
---
|
||||
|
||||
#### `dict() → dict`
|
||||
|
||||
返回已加载的全部数据。
|
||||
|
||||
---
|
||||
|
||||
## 扩展类:`ExcelDataL(ExcelData)`
|
||||
|
||||
继承自 `ExcelData`,区别在于 `dataload()` 方法:
|
||||
|
||||
- 不是以表名为键的字典
|
||||
- 而是返回一个列表,每个元素是一个 `{表名: 数据}` 字典
|
||||
|
||||
**用途:**
|
||||
- 保持工作表顺序
|
||||
- 支持同名表(虽然不推荐)
|
||||
|
||||
---
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 示例 Excel 结构
|
||||
|
||||
| A | B | C |
|
||||
|-----------|-------------|----------|
|
||||
| name:str | age:int | tags:list|
|
||||
| Alice | 25 | py,dev |
|
||||
| Bob | 30 | js,web |
|
||||
|
||||
调用后得到:
|
||||
```python
|
||||
{
|
||||
"Sheet1": [
|
||||
{"name": "Alice", "age": 25, "tags": ["py", "dev"]},
|
||||
{"name": "Bob", "age": 30, "tags": ["js", "web"]}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 包含外部文件
|
||||
|
||||
| A | B | C |
|
||||
|---------------|------------------|-------------|
|
||||
| __include__ | config.xlsx | ['db']['host'] |
|
||||
|
||||
会尝试加载 `config.xlsx` 并提取 `data['db']['host']`
|
||||
|
||||
---
|
||||
|
||||
## 命令行运行
|
||||
|
||||
```bash
|
||||
python exceldata.py your_file.xls
|
||||
```
|
||||
|
||||
输出整个解析结果的 JSON 形式(通过 `print(ed.dict())`)。
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **编码问题**
|
||||
- 推荐使用 UTF-8 编码保存 Excel 中的文本
|
||||
- 若出现乱码,请检查 `encoding` 参数
|
||||
|
||||
2. **安全性警告**
|
||||
- `include()` 函数使用 `exec`,不要用于不可信输入
|
||||
|
||||
3. **版本兼容性**
|
||||
- 使用 `xlrd`,注意其对 `.xlsx` 的支持有限(建议使用 `xlrd>=1.2.0` 或考虑迁移到 `openpyxl`)
|
||||
|
||||
4. **性能**
|
||||
- 适合中小规模配置文件,不适合大数据量导入
|
||||
|
||||
5. **错误处理**
|
||||
- 部分异常被捕获并打印,但不会中断程序
|
||||
|
||||
---
|
||||
|
||||
## 已知限制
|
||||
|
||||
- 仅支持读取,不支持写入
|
||||
- 日期仅提取年月日,忽略时分秒
|
||||
- `__dict__` 和 `__list__` 结构需要严格对齐
|
||||
- `exec` 使用存在安全隐患
|
||||
- `lrtrim` 来自外部模块,需确保安装
|
||||
|
||||
---
|
||||
|
||||
## 未来改进建议
|
||||
|
||||
1. 替换 `exec` 为更安全的路径访问机制(如 `dpath` 或递归查找)
|
||||
2. 支持更多数据类型(如布尔、JSON)
|
||||
3. 添加日志系统代替 `print`
|
||||
4. 支持 `openpyxl` 以兼容现代 Excel 格式
|
||||
5. 提供导出为 JSON/YAML 的接口
|
||||
|
||||
---
|
||||
|
||||
## 版权与许可
|
||||
|
||||
© 2025 作者保留所有权利。
|
||||
可用于内部项目,禁止商业分发。
|
||||
|
||||
---
|
||||
|
||||
> ✅ 文档更新时间:2025年4月5日
|
||||
262
aidocs/excelwriter.md
Normal file
262
aidocs/excelwriter.md
Normal file
@ -0,0 +1,262 @@
|
||||
# `ExcelWriter` 类技术文档
|
||||
|
||||
## 概述
|
||||
|
||||
`ExcelWriter` 是一个基于 `xlwt` 库的 Python 工具类,用于将嵌套字典或列表结构的数据写入 Excel 文件(`.xls` 格式)。它支持递归处理复杂数据结构(如字典、列表、记录列表等),并自动展开为可读的表格形式。
|
||||
|
||||
该工具适用于导出配置、日志、数据库查询结果或其他结构化数据到 Excel 表格中。
|
||||
|
||||
---
|
||||
|
||||
## 依赖库
|
||||
|
||||
- `xlwt`: 用于创建和写入 `.xls` 文件。
|
||||
- `appPublic.strUtils.lrtrim`: 自定义字符串去空函数(左右空格去除)。
|
||||
|
||||
> 注意:`xlwt` 不支持 `.xlsx` 格式,仅支持旧版 `.xls`。
|
||||
|
||||
---
|
||||
|
||||
## 类定义
|
||||
|
||||
```python
|
||||
class ExcelWriter:
|
||||
def __init__(self, encoding='gb2312'):
|
||||
self.encoding = encoding
|
||||
```
|
||||
|
||||
### 构造函数 `__init__`
|
||||
|
||||
#### 参数:
|
||||
- **`encoding`** (str): 输出 Excel 文件的字符编码,默认为 `'gb2312'`。
|
||||
常见可选值包括 `'utf-8'`, `'gbk'` 等,需与系统兼容。
|
||||
|
||||
#### 示例:
|
||||
```python
|
||||
writer = ExcelWriter(encoding='utf-8')
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 主要方法
|
||||
|
||||
### `write(excelfile, dictdata)`
|
||||
|
||||
将字典格式的数据写入指定的 Excel 文件,每个顶级键作为工作表名称。
|
||||
|
||||
#### 参数:
|
||||
- **`excelfile`** (str): 输出文件路径(如 `'output.xls'`)。
|
||||
- **`dictdata`** (dict): 数据字典,键为工作表名,值为待写入内容(支持 dict/list)。
|
||||
|
||||
#### 示例:
|
||||
```python
|
||||
data = {
|
||||
'Sheet1': ['a', 'b', 'c'],
|
||||
'Sheet2': {'key': 'value'}
|
||||
}
|
||||
writer.write('output.xls', data)
|
||||
```
|
||||
|
||||
> 此方法会为每个 key 创建一个 sheet,并调用 `writeV()` 写入其值。
|
||||
|
||||
---
|
||||
|
||||
### `writeV(sheet, x, y, v)`
|
||||
|
||||
递归写入任意类型值到工作表的指定位置。
|
||||
|
||||
#### 参数:
|
||||
- **`sheet`**: `xlwt.Worksheet` 对象。
|
||||
- **`x`**, **`y`** (int): 起始行、列索引(从 0 开始)。
|
||||
- **`v`**: 待写入的值(支持 str/int/float/list/dict)。
|
||||
|
||||
#### 行为逻辑:
|
||||
| 类型 | 处理方式 |
|
||||
|------|---------|
|
||||
| `list` | 调用 `writeList()` |
|
||||
| `dict` | 调用 `writeDict()` |
|
||||
| 其他(str/int/float) | 直接写入单元格,若为字符串则先去空格 |
|
||||
|
||||
#### 返回值:
|
||||
返回占用的行数(int)。
|
||||
|
||||
---
|
||||
|
||||
### `writeDict(sheet, x, y, adict)`
|
||||
|
||||
将字典写入工作表,每项占一行,键在左,值在右,支持嵌套。
|
||||
|
||||
#### 参数:
|
||||
- `sheet`: 工作表对象
|
||||
- `x`, `y`: 起始坐标
|
||||
- `adict`: 字典对象
|
||||
|
||||
#### 输出示例:
|
||||
```
|
||||
__dict__
|
||||
key1 value1
|
||||
key2 [nested...]
|
||||
```
|
||||
|
||||
#### 返回值:
|
||||
返回所占行数。
|
||||
|
||||
---
|
||||
|
||||
### `writeList(sheet, x, y, alist, singlecell=False)`
|
||||
|
||||
写入列表数据,根据内容自动判断处理方式。
|
||||
|
||||
#### 参数:
|
||||
- `alist`: 列表数据
|
||||
- `singlecell`: 是否合并为单个单元格(逗号分隔)
|
||||
|
||||
#### 分支逻辑:
|
||||
1. **如果是记录列表(`isRecords(alist)` 为 True)**
|
||||
→ 调用 `writeRecords()` 以表格形式输出。
|
||||
2. **如果包含字典或嵌套结构**
|
||||
→ 多行展开,逐项调用 `writeDict` 或 `writeMultiLineList`。
|
||||
3. **普通列表**
|
||||
→ 横向展开在同一行(列递增),除非 `singlecell=True`。
|
||||
|
||||
#### 返回值:
|
||||
返回占用行数。
|
||||
|
||||
---
|
||||
|
||||
### `writeRecords(sheet, x, y, alist)`
|
||||
|
||||
将“记录列表”(即字典列表)以表格形式写入 Excel。
|
||||
|
||||
#### 前提条件:
|
||||
所有元素必须是字典,且值不能是嵌套复杂结构(不支持 list of dict with nested dicts/lists)。
|
||||
|
||||
#### 功能:
|
||||
- 自动生成表头(字段名)
|
||||
- 支持字段值为列表时标记为 `fieldname:list`
|
||||
- 自动对齐列位置
|
||||
|
||||
#### 示例输入:
|
||||
```python
|
||||
[
|
||||
{'name': 'Alice', 'tags': ['dev', 'ml']},
|
||||
{'name': 'Bob', 'tags': ['qa']}
|
||||
]
|
||||
```
|
||||
|
||||
#### 输出效果:
|
||||
| name | tags:list |
|
||||
|-------|----------------|
|
||||
| Alice | dev,ml |
|
||||
| Bob | qa |
|
||||
|
||||
#### 返回值:
|
||||
返回写入的记录条数(行数 -1,不含标题行)。
|
||||
|
||||
---
|
||||
|
||||
### `createRecordTitle(ws, x, y, title, poss, isList=False)` 和 `writeRecordTitle(ws, x, poss)`
|
||||
|
||||
辅助方法,用于管理记录字段的列映射。
|
||||
|
||||
- `poss`: 字段名 → 列索引 的字典,`__list__` 子集标记哪些字段是列表。
|
||||
- `createRecordTitle`: 注册字段名及其列位置。
|
||||
- `writeRecordTitle`: 在指定行写出表头。
|
||||
|
||||
---
|
||||
|
||||
### `writeMultiLineList(sheet, x, y, alist)`
|
||||
|
||||
将列表纵向写入一列,第一行为 `__list__` 标记。
|
||||
|
||||
#### 示例输出:
|
||||
```
|
||||
__list__
|
||||
item1
|
||||
item2
|
||||
item3
|
||||
```
|
||||
|
||||
#### 返回值:
|
||||
返回占用行数。
|
||||
|
||||
> ⚠️ Bug 提示:代码中存在拼写错误 `os` 应为 `ox`,应修正为:
|
||||
```python
|
||||
return x - ox # 而非 return x - os
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `isRecords(alist)`
|
||||
|
||||
判断一个列表是否为“记录列表”(即字典列表,且无深层嵌套)。
|
||||
|
||||
#### 条件:
|
||||
- 所有元素是字典
|
||||
- 字典的值不能是字典或包含复杂类型的列表
|
||||
|
||||
#### 返回值:
|
||||
- `True` if 符合记录格式
|
||||
- `False` otherwise
|
||||
|
||||
---
|
||||
|
||||
## 使用示例
|
||||
|
||||
```python
|
||||
from appPublic.strUtils import lrtrim
|
||||
import xlwt
|
||||
|
||||
# 实例化
|
||||
w = ExcelWriter(encoding='utf-8')
|
||||
|
||||
# 准备测试数据
|
||||
data = {
|
||||
'my1': ['23423', '423424', 't334t3', 2332, 'erfverfefew'],
|
||||
'my2': [
|
||||
{'aaa': 1, 'bbb': 'bbb'},
|
||||
{'aaa': 2, 'bbb': 'hello'}
|
||||
],
|
||||
}
|
||||
|
||||
# 写入文件
|
||||
w.write(r'd:\text.xls', data)
|
||||
```
|
||||
|
||||
### 输出说明:
|
||||
|
||||
- **Sheet: my1**
|
||||
单行横向显示列表元素。
|
||||
|
||||
- **Sheet: my2**
|
||||
表格形式展示两行记录,表头为 `aaa`, `bbb`。
|
||||
|
||||
---
|
||||
|
||||
## 已知问题与改进建议
|
||||
|
||||
| 问题 | 描述 | 建议修复 |
|
||||
|------|------|----------|
|
||||
| ❌ `os` 未定义 | `writeMultiLineList` 中 `return x - os` 错误 | 改为 `return x - ox` |
|
||||
| ⚠️ 编码限制 | `gb2312` 可能无法显示中文 | 推荐使用 `'utf-8'` 或 `'gbk'` |
|
||||
| ⚠️ 性能问题 | 多层嵌套递归可能导致性能下降 | 添加深度限制或优化结构遍历 |
|
||||
| 🔄 单元格覆盖 | 启用了 `cell_overwrite_ok=True`,可能造成意外覆盖 | 明确告知用户风险 |
|
||||
| 💾 格式限制 | 仅支持 `.xls`,不支持 `.xlsx` | 可考虑升级为 `openpyxl` 或 `pandas` 替代 |
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
`ExcelWriter` 提供了一种简洁的方式将 Python 中的嵌套数据结构导出为 Excel 文件,特别适合调试、配置导出、简单报表生成等场景。
|
||||
|
||||
尽管存在一定局限性(如格式老旧、潜在 bug),但其设计清晰、易于扩展,是一个实用的小型工具类。
|
||||
|
||||
---
|
||||
|
||||
## 版权与维护
|
||||
|
||||
- **作者**: 未知(来自内部模块 `appPublic`)
|
||||
- **语言**: Python 2/3 兼容(建议运行于 Python 3.x + `xlwt-future`)
|
||||
- **用途**: 内部数据导出工具
|
||||
|
||||
> 建议后续迁移至 `openpyxl` 或 `pandas.DataFrame.to_excel()` 以获得更好的功能和维护支持。
|
||||
207
aidocs/find_player.md
Normal file
207
aidocs/find_player.md
Normal file
@ -0,0 +1,207 @@
|
||||
# `BroadcastServer` 技术文档
|
||||
|
||||
本模块实现了一个基于 UDP 广播的发现服务,可用于局域网内设备或玩家的自动发现。主要包括一个广播服务器(`BroadcastServer`)和一个客户端发现函数(`find_players`)。
|
||||
|
||||
---
|
||||
|
||||
## 模块依赖
|
||||
|
||||
```python
|
||||
from socket import *
|
||||
import json
|
||||
from appPublic.sockPackage import get_free_local_addr
|
||||
from appPublic.background import Background
|
||||
```
|
||||
|
||||
### 外部依赖说明:
|
||||
|
||||
- `socket`: 提供底层网络通信支持。
|
||||
- `json`: 用于序列化/反序列化服务信息。
|
||||
- `appPublic.sockPackage.get_free_local_addr`: 获取本地可用 IP 地址。
|
||||
- `appPublic.background.Background`: 在后台线程中运行任务。
|
||||
|
||||
> ⚠️ 注意:`appPublic` 是自定义公共库,请确保其已安装并可导入。
|
||||
|
||||
---
|
||||
|
||||
## 常量定义
|
||||
|
||||
```python
|
||||
BUFSIZE = 1024
|
||||
```
|
||||
|
||||
- `BUFSIZE`: UDP 数据包最大接收缓冲区大小,单位为字节(1KB),适用于大多数局域网小数据传输场景。
|
||||
|
||||
---
|
||||
|
||||
## 类:`BroadcastServer`
|
||||
|
||||
一个 UDP 服务器,监听广播请求,并返回预设的服务信息(如主机名、端口等)给请求方。
|
||||
|
||||
### 构造函数:`__init__(self, port, info)`
|
||||
|
||||
#### 参数:
|
||||
| 参数 | 类型 | 描述 |
|
||||
|------|------|------|
|
||||
| `port` | int | 服务器绑定的 UDP 端口号 |
|
||||
| `info` | dict | 要广播的服务信息(例如:`{"name": "GameServer", "game_port": 8000}`) |
|
||||
|
||||
#### 功能:
|
||||
1. 创建 UDP 套接字(IPv4, UDP)。
|
||||
2. 设置套接字为阻塞模式(默认行为)。
|
||||
3. 绑定到所有本地地址的指定端口 (`''` 表示 `0.0.0.0`)。
|
||||
4. 启动后台线程运行 `run()` 方法。
|
||||
|
||||
#### 示例初始化:
|
||||
```python
|
||||
server_info = {
|
||||
"name": "Player1",
|
||||
"game_port": 6000,
|
||||
"status": "waiting"
|
||||
}
|
||||
bc_server = BroadcastServer(9999, server_info)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 方法:`run()`
|
||||
|
||||
在后台持续运行,处理来自客户端的广播探测请求。
|
||||
|
||||
#### 流程:
|
||||
1. 循环等待接收 UDP 数据包(最多 `BUFSIZE` 字节)。
|
||||
2. 收到任意数据后,将 `self.info` 序列化为 JSON 并编码为 UTF-8 发送回请求者的地址。
|
||||
3. 异常捕获打印日志但不中断服务。
|
||||
|
||||
> 📌 协议约定:任何发往该端口的数据都会触发响应 —— 即“有问必答”模型。
|
||||
|
||||
#### 响应格式(JSON):
|
||||
```json
|
||||
{
|
||||
"name": "Player1",
|
||||
"game_port": 6000,
|
||||
"status": "waiting"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 方法:`stop()`
|
||||
|
||||
安全停止服务器。
|
||||
|
||||
#### 功能:
|
||||
- 设置运行标志 `run_flg = False`,终止 `run()` 循环。
|
||||
- 关闭 UDP 套接字资源。
|
||||
- 不主动等待线程结束(需业务层控制时序)。
|
||||
|
||||
#### 示例调用:
|
||||
```python
|
||||
bc_server.stop()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 函数:`find_players(port)`
|
||||
|
||||
向局域网广播查询消息,寻找活跃的广播服务节点(如游戏主机)。
|
||||
|
||||
### 参数:
|
||||
| 参数 | 类型 | 描述 |
|
||||
|------|------|------|
|
||||
| `port` | int | 目标广播服务器监听的端口号 |
|
||||
|
||||
### 返回值:
|
||||
- `list[dict]`: 找到的设备信息列表,每个元素包含原始信息 + `'ip'` 字段。
|
||||
|
||||
```python
|
||||
[
|
||||
{
|
||||
"name": "Player1",
|
||||
"game_port": 6000,
|
||||
"status": "waiting",
|
||||
"ip": "192.168.1.105"
|
||||
},
|
||||
...
|
||||
]
|
||||
```
|
||||
|
||||
### 实现细节:
|
||||
|
||||
1. 使用 `get_free_local_addr()` 获取本机 IP,用于过滤自身响应。
|
||||
2. 创建 UDP 客户端套接字并启用广播权限:
|
||||
```python
|
||||
udpCliSock.setsockopt(SOL_SOCKET, SO_BROADCAST, 1)
|
||||
```
|
||||
3. 绑定任意端口(`('', 0)`)。
|
||||
4. 向 `255.255.255.255:{port}` 发送广播消息 `'findplayers'`。
|
||||
5. 接收响应,超时时间为 5 秒。
|
||||
6. 对每条有效响应:
|
||||
- 过滤非本地回环且非自身的 IP;
|
||||
- 解码 JSON 数据;
|
||||
- 添加来源 IP 到结果中;
|
||||
7. 超时或异常时退出循环,返回收集结果。
|
||||
|
||||
> ✅ 支持跨子网?仅限局域网内支持广播的环境(通常不可跨路由)。
|
||||
|
||||
---
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 启动广播服务(服务端)
|
||||
|
||||
```python
|
||||
info = {"name": "MyGameHost", "game_port": 8888}
|
||||
server = BroadcastServer(port=9999, info=info)
|
||||
|
||||
# ... 保持运行一段时间
|
||||
# server.stop() # 显式关闭
|
||||
```
|
||||
|
||||
### 搜索局域网中的服务(客户端)
|
||||
|
||||
```python
|
||||
players = find_players(9999)
|
||||
for p in players:
|
||||
print(f"Found {p['name']} at {p['ip']}:{p['game_port']}")
|
||||
```
|
||||
|
||||
输出示例:
|
||||
```
|
||||
Found MyGameHost at 192.168.1.105:8888
|
||||
Found Player2 at 192.168.1.106:8889
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 注意事项与限制
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| **协议** | UDP,不可靠传输,适合低延迟发现 |
|
||||
| **广播地址** | 固定使用 `255.255.255.255`,适用于大多数局域网 |
|
||||
| **安全性** | 无认证机制,仅适用于受信任内网 |
|
||||
| **性能** | 单线程处理,适合轻量级发现场景 |
|
||||
| **编码** | 默认使用 UTF-8 编码传输 JSON |
|
||||
| **异常处理** | 已捕获异常防止崩溃,但建议上层监控 |
|
||||
|
||||
---
|
||||
|
||||
## 可能的改进方向
|
||||
|
||||
- ✅ 添加日志系统替代 `print`
|
||||
- 🔧 支持多播替代广播以提高效率
|
||||
- ⏱️ 可配置超时时间与重试次数
|
||||
- 🛡️ 加入消息校验(如 magic header)
|
||||
- 🔄 支持定期心跳广播(主动宣告)
|
||||
|
||||
---
|
||||
|
||||
## 版权与许可
|
||||
|
||||
© 2025 作者保留所有权利。
|
||||
仅供内部学习与开发使用,遵循项目整体开源协议(如有)。
|
||||
|
||||
---
|
||||
|
||||
📌 **提示**:部署前请测试网络环境是否允许广播通信。
|
||||
374
aidocs/folderUtils.md
Normal file
374
aidocs/folderUtils.md
Normal file
@ -0,0 +1,374 @@
|
||||
# 技术文档:文件与路径操作工具库
|
||||
|
||||
本项目是一个轻量级的 Python 工具模块,封装了常用的文件系统操作功能,包括临时文件生成、路径处理、目录遍历、文件复制/删除等。适用于跨平台(Windows/Linux/macOS)环境下的文件管理任务。
|
||||
|
||||
---
|
||||
|
||||
## 目录
|
||||
|
||||
- [1. 模块概览](#1-模块概览)
|
||||
- [2. 依赖说明](#2-依赖说明)
|
||||
- [3. 函数列表](#3-函数列表)
|
||||
- [4. 核心函数详解](#4-核心函数详解)
|
||||
- [5. 使用示例](#5-使用示例)
|
||||
- [6. 注意事项](#6-注意事项)
|
||||
|
||||
---
|
||||
|
||||
## 1. 模块概览
|
||||
|
||||
该模块提供以下主要功能:
|
||||
|
||||
| 功能类别 | 提供的功能 |
|
||||
|----------------|-----------|
|
||||
| 路径与目录操作 | `ProgramPath`, `listFolder`, `listFile`, `folderInfo`, `_mkdir`, `rmdir_recursive` |
|
||||
| 文件操作 | `temp_file`, `_copyfile`, `_copydir` |
|
||||
| 字符串辅助 | `startsWith`, `endsWith` |
|
||||
| 时间格式化 | `timestamp2datatiemStr` |
|
||||
| 随机路径生成 | `filepoolpath` |
|
||||
|
||||
> ⚠️ 注:部分 Windows 特定代码(如 `win32api`)已被注释,当前版本为通用跨平台实现。
|
||||
|
||||
---
|
||||
|
||||
## 2. 依赖说明
|
||||
|
||||
### 内置依赖
|
||||
此模块仅使用 Python 标准库,无需额外安装第三方包:
|
||||
```python
|
||||
import os
|
||||
import sys
|
||||
import stat
|
||||
import platform
|
||||
import time
|
||||
import random
|
||||
import tempfile
|
||||
```
|
||||
|
||||
### 可选依赖(已注释)
|
||||
```python
|
||||
# import win32api # 用于获取逻辑驱动器信息(Windows专用),目前未启用
|
||||
```
|
||||
> 若需启用驱动器扫描功能,请通过 `pip install pywin32` 安装对应模块并取消注释。
|
||||
|
||||
---
|
||||
|
||||
## 3. 函数列表
|
||||
|
||||
| 函数名 | 描述 |
|
||||
|--------|------|
|
||||
| `temp_file(suffix=None, prefix=None, dir=None, text=False)` | 创建一个唯一的临时文件并返回其路径 |
|
||||
| `filepoolpath(root)` | 基于哈希算法生成分层存储路径,用于避免大量文件集中在同一目录 |
|
||||
| `startsWith(text, s)` | 判断字符串是否以指定前缀开头 |
|
||||
| `endsWith(text, s)` | 判断字符串是否以指定后缀结尾 |
|
||||
| `ProgramPath()` | 获取当前脚本或可执行文件所在目录 |
|
||||
| `timestamp2datatiemStr(ts)` | 将时间戳转换为标准日期时间字符串格式 |
|
||||
| `listFolder(path, recursive=False)` | 遍历目录下所有子文件夹(支持递归) |
|
||||
| `listFile(folder, suffixs=[], recursive=False)` | 遍历目录中符合条件的文件(支持扩展名过滤和递归) |
|
||||
| `folderInfo(root, uri='')` | 返回指定目录下的文件/文件夹元数据列表 |
|
||||
| `rmdir_recursive(dir)` | 递归删除目录及其内容 |
|
||||
| `_mkdir(newdir)` | 安全创建目录(自动创建父级目录,若已存在不报错) |
|
||||
| `_copyfile(fp, dir)` | 复制文件到目标目录,并解决重名问题 |
|
||||
| `_copydir(fp, dir, topdistinct)` | 递归复制整个目录结构 |
|
||||
| `mkdir`, `copyfile`, `copydir`, `rmdir` | 上述函数的别名,便于调用 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 核心函数详解
|
||||
|
||||
### `temp_file(...)`
|
||||
**用途**:安全地创建一个临时文件并关闭句柄,返回文件路径。
|
||||
|
||||
**参数**:
|
||||
- `suffix` (str): 文件后缀(如 `.tmp`)
|
||||
- `prefix` (str): 文件名前缀
|
||||
- `dir` (str): 存放目录;默认为系统临时目录
|
||||
- `text` (bool): 是否以文本模式打开(实际未使用)
|
||||
|
||||
**返回值**:
|
||||
`str` - 生成的临时文件完整路径。
|
||||
|
||||
**示例**:
|
||||
```python
|
||||
fp = temp_file(suffix='.log', prefix='app_', dir='/tmp')
|
||||
# => /tmp/app_abc123.log
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `filepoolpath(root)`
|
||||
**用途**:根据随机数对多个质数取模,构建多层级子目录路径,适合大规模文件分布存储。
|
||||
|
||||
**原理**:利用五个质数 `[191, 193, 197, 199, 97]` 对随机值取模,形成五层嵌套目录结构,有效分散 I/O 压力。
|
||||
|
||||
**参数**:
|
||||
- `root` (str): 根目录路径
|
||||
|
||||
**返回值**:
|
||||
`str` - 如 `/data/100/87/45/23/67`
|
||||
|
||||
**应用场景**:
|
||||
日志系统、上传服务、缓存池等需要防止单目录文件过多的情况。
|
||||
|
||||
---
|
||||
|
||||
### `startsWith(text, s)` / `endsWith(text, s)`
|
||||
**用途**:替代原生 `str.startswith()` 和 `str.endswith()` 的简化版本(兼容性写法)。
|
||||
|
||||
**参数**:
|
||||
- `text` (str): 待检测字符串
|
||||
- `s` (str): 匹配子串
|
||||
|
||||
**返回值**:
|
||||
`bool` - 是否匹配成功
|
||||
|
||||
---
|
||||
|
||||
### `ProgramPath()`
|
||||
**用途**:获取程序运行时所在的根目录,支持 PyInstaller 打包后的可执行文件。
|
||||
|
||||
**行为逻辑**:
|
||||
- 如果是打包后的 `.exe` 或二进制文件(`sys.frozen == True`),则返回 `sys.executable` 所在目录。
|
||||
- 否则返回 `sys.argv[0]`(即主脚本)所在目录。
|
||||
|
||||
**返回值**:
|
||||
`str` - 绝对路径,表示程序运行目录。
|
||||
|
||||
---
|
||||
|
||||
### `timestamp2datatiemStr(ts)`
|
||||
**用途**:将 Unix 时间戳转换为可读的时间字符串。
|
||||
|
||||
> ❗ 函数名拼写错误:应为 `timestamp2datetimeStr`,但保留原始命名。
|
||||
|
||||
**参数**:
|
||||
- `ts` (int/float): 时间戳
|
||||
|
||||
**返回值**:
|
||||
`str` - 格式为 `"YYYY-MM-DD HH:MM:SS"` 的时间字符串
|
||||
|
||||
**示例输出**:
|
||||
```
|
||||
2025-04-05 14:23:01
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `listFolder(path, recursive=False)`
|
||||
**用途**:列出指定路径下的所有子目录(可递归)。
|
||||
|
||||
**参数**:
|
||||
- `path` (str): 起始路径
|
||||
- `recursive` (bool): 是否递归进入子目录
|
||||
|
||||
**返回值**:
|
||||
生成器对象,逐个返回每个子目录的绝对路径。
|
||||
|
||||
**示例**:
|
||||
```python
|
||||
for d in listFolder('/home/user', recursive=True):
|
||||
print(d)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `listFile(folder, suffixs=[], recursive=False)`
|
||||
**用途**:查找目录中符合扩展名条件的文件。
|
||||
|
||||
**参数**:
|
||||
- `folder` (str): 搜索起始路径
|
||||
- `suffixs` (list): 扩展名列表(如 `['.txt', '.py']`),忽略大小写
|
||||
- `recursive` (bool): 是否递归搜索
|
||||
|
||||
**返回值**:
|
||||
生成器对象,返回匹配文件的绝对路径。
|
||||
|
||||
**注意**:
|
||||
- 若 `suffixs` 为空,则返回所有文件。
|
||||
- 扩展名建议包含点号(`.`)。
|
||||
|
||||
**示例**:
|
||||
```python
|
||||
pdfs = list(listFile('/docs', ['.pdf'], recursive=True))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `folderInfo(root, uri='')`
|
||||
**用途**:获取某虚拟路径下的文件/文件夹详细信息列表。
|
||||
|
||||
**参数**:
|
||||
- `root` (str): 实际根目录
|
||||
- `uri` (str): 虚拟路径(如 `/user/docs`)
|
||||
|
||||
**逻辑说明**:
|
||||
- 支持类似 Web 的 URI 路径解析(如 `/a/b/c` → `root/a/b/c`)
|
||||
- 自动分割 `/` 并拼接真实路径
|
||||
- 返回包含类型、大小、修改时间等信息的字典列表
|
||||
|
||||
**返回项结构**:
|
||||
```json
|
||||
{
|
||||
"id": "/user/docs/report.pdf",
|
||||
"name": "report.pdf",
|
||||
"path": "user/docs",
|
||||
"type": "file",
|
||||
"size": 10240,
|
||||
"mtime": "2025-04-05 14:23:01"
|
||||
}
|
||||
```
|
||||
|
||||
**适用场景**:
|
||||
文件浏览器后端接口、资源管理器 API 数据源。
|
||||
|
||||
---
|
||||
|
||||
### `rmdir_recursive(dir)`
|
||||
**用途**:递归删除非空目录。
|
||||
|
||||
**特性**:
|
||||
- 对只读文件先修改权限(加写权限)再删除
|
||||
- 先删除子项,最后删除自身目录
|
||||
- 不会抛出“目录非空”异常
|
||||
|
||||
**警告**:危险操作,请确保路径正确!
|
||||
|
||||
---
|
||||
|
||||
### `_mkdir(newdir)`
|
||||
**用途**:智能创建目录,支持自动创建父级目录。
|
||||
|
||||
**特性**:
|
||||
- 若目录已存在,静默跳过(无异常)
|
||||
- 若中间路径不存在,则自动补全
|
||||
- 等价于 `os.makedirs(..., exist_ok=True)`
|
||||
|
||||
---
|
||||
|
||||
### `_copyfile(fp, dir)`
|
||||
**用途**:将文件复制到目标目录,并自动处理同名冲突。
|
||||
|
||||
**流程**:
|
||||
1. 读取源文件名
|
||||
2. 调用 `getFileName(name, dir)` 解决重名(见备注)
|
||||
3. 分块读取(64KB)进行复制,节省内存
|
||||
4. 返回布尔值表示是否成功
|
||||
|
||||
> ✅ 支持大文件复制
|
||||
|
||||
---
|
||||
|
||||
### `_copydir(fp, dir, topdistinct)`
|
||||
**用途**:递归复制整个目录树。
|
||||
|
||||
**参数**:
|
||||
- `fp`: 源目录路径
|
||||
- `dir`: 目标父目录
|
||||
- `topdistinct`: 防止顶层重复复制的标记路径
|
||||
|
||||
**机制**:
|
||||
- 在目标目录下创建同名新目录
|
||||
- 遍历子项:如果是目录则递归复制,否则调用 `_copyfile`
|
||||
- 忽略 `topdistinct` 层以防循环引用
|
||||
|
||||
**别名定义**:
|
||||
```python
|
||||
mkdir = _mkdir
|
||||
copyfile = _copyfile
|
||||
copydir = _copydir
|
||||
rmdir = rmdir_recursive
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 使用示例
|
||||
|
||||
### 示例 1:创建临时日志文件
|
||||
```python
|
||||
log_path = temp_file(suffix='.log', prefix='myapp_')
|
||||
with open(log_path, 'w') as f:
|
||||
f.write("App started at %s\n" % time.time())
|
||||
```
|
||||
|
||||
### 示例 2:组织海量图片存储路径
|
||||
```python
|
||||
storage_root = "/images"
|
||||
img_path = filepoolpath(storage_root)
|
||||
os.makedirs(img_path, exist_ok=True)
|
||||
shutil.move(upload_file, os.path.join(img_path, 'photo.jpg'))
|
||||
```
|
||||
|
||||
### 示例 3:列出所有 Python 文件
|
||||
```python
|
||||
for py_file in listFile('/project', suffixs=['.py'], recursive=True):
|
||||
print("Found:", py_file)
|
||||
```
|
||||
|
||||
### 示例 4:获取目录信息用于前端展示
|
||||
```python
|
||||
info = folderInfo('/data', '/user/docs')
|
||||
import json
|
||||
print(json.dumps(info, indent=2))
|
||||
```
|
||||
|
||||
### 示例 5:安全删除缓存目录
|
||||
```python
|
||||
cache_dir = os.path.join(ProgramPath(), 'cache')
|
||||
if os.path.exists(cache_dir):
|
||||
rmdir(cache_dir)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 注意事项
|
||||
|
||||
### ⚠️ 已知问题
|
||||
1. **函数名拼写错误**:
|
||||
```python
|
||||
def timestamp2datatiemStr(ts): ...
|
||||
```
|
||||
正确应为 `timestamp2datetimeStr`,请在后续版本中修正。
|
||||
|
||||
2. **`getFileName` 函数缺失**:
|
||||
在 `_copyfile` 和 `_copydir` 中调用了 `getFileName(name, dir)`,但在代码中未定义。推测是外部依赖或遗漏函数。
|
||||
|
||||
> 🛠️ 建议补充如下实现:
|
||||
```python
|
||||
def getFileName(name, directory):
|
||||
base_name, ext = os.path.splitext(name)
|
||||
counter = 1
|
||||
new_name = name
|
||||
while os.path.exists(os.path.join(directory, new_name)):
|
||||
new_name = f"{base_name}_{counter}{ext}"
|
||||
counter += 1
|
||||
return new_name
|
||||
```
|
||||
|
||||
3. **Windows 权限处理局限性**:
|
||||
`rmdir_recursive` 中仅设置 `0o600` 权限,可能不足以应对某些系统限制(如只读属性)。建议增加 `os.chmod(..., stat.S_IWRITE)` 更健壮。
|
||||
|
||||
4. **`findAllDrives` 被注释**:
|
||||
当前无法枚举磁盘驱动器。如需恢复,请引入 `pywin32` 并取消注释相关代码。
|
||||
|
||||
---
|
||||
|
||||
## 许可证
|
||||
|
||||
默认遵循 Python 软件基金会许可证(PSF License),允许自由使用、修改和分发。
|
||||
|
||||
---
|
||||
|
||||
## 更新建议(TODO)
|
||||
|
||||
| 编号 | 建议内容 |
|
||||
|------|----------|
|
||||
| 1 | 修复 `timestamp2datatiemStr` 拼写错误 |
|
||||
| 2 | 补全 `getFileName()` 函数实现 |
|
||||
| 3 | 添加单元测试样例 |
|
||||
| 4 | 增加日志记录功能(可选) |
|
||||
| 5 | 文档化异常处理机制 |
|
||||
| 6 | 提供 `movefile`, `movedir` 扩展功能 |
|
||||
|
||||
---
|
||||
|
||||
> ✅ 本模块适合作为基础组件集成至各类文件管理系统、备份工具、资源上传服务中。
|
||||
218
aidocs/genetic.md
Normal file
218
aidocs/genetic.md
Normal file
@ -0,0 +1,218 @@
|
||||
# `Genetic` 类技术文档
|
||||
|
||||
## 概述
|
||||
|
||||
`Genetic` 是一个用于实现**属性遗传机制**的基类,支持对象之间的父子关系建立,并允许子对象继承父对象的属性。该设计模拟了一种类似原型继承的机制,适用于需要层级化属性共享与继承的场景。
|
||||
|
||||
通过 `__parent__` 和 `__children__` 两个特殊属性维护对象间的树状结构,子对象在访问自身不存在的属性时,会自动向上追溯其父对象,直到根节点。
|
||||
|
||||
---
|
||||
|
||||
## 类定义
|
||||
|
||||
```python
|
||||
class Genetic:
|
||||
"""
|
||||
A Base class for genetical objects,
|
||||
all the instances can inherit attributes from its parent.
|
||||
"""
|
||||
```
|
||||
|
||||
### 功能特点
|
||||
|
||||
- 支持动态属性继承(从父对象获取未定义的属性)
|
||||
- 维护双向父子关系(父 → 子 和 子 → 父)
|
||||
- 可构建多层对象继承树
|
||||
- 易于扩展:其他类可继承 `Genetic` 获得遗传能力
|
||||
|
||||
---
|
||||
|
||||
## 属性说明
|
||||
|
||||
| 属性名 | 类型 | 描述 |
|
||||
|----------------|------------|------|
|
||||
| `__parent__` | `Genetic` 或 `None` | 当前对象的父对象。初始为 `None`。 |
|
||||
| `__children__` | `List[Genetic]` | 当前对象的所有子对象列表。初始为空列表。 |
|
||||
|
||||
> ⚠️ 注意:这两个属性是私有属性(双下划线命名),不应直接修改,应通过提供的方法管理。
|
||||
|
||||
---
|
||||
|
||||
## 方法说明
|
||||
|
||||
### `__init__(self)`
|
||||
|
||||
初始化一个新的 `Genetic` 实例。
|
||||
|
||||
#### 行为:
|
||||
- 设置 `self.__parent__ = None`
|
||||
- 初始化 `self.__children__ = []`
|
||||
|
||||
#### 示例:
|
||||
```python
|
||||
obj = Genetic()
|
||||
print(obj.__parent__) # 输出: None
|
||||
print(obj.__children__) # 输出: []
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `__getattr__(self, name)`
|
||||
|
||||
重写 Python 的属性访问机制。当尝试访问对象中不存在的属性时触发。
|
||||
|
||||
#### 参数:
|
||||
- `name` (`str`):要访问的属性名
|
||||
|
||||
#### 返回值:
|
||||
- 父对象中的对应属性值(递归查找)
|
||||
|
||||
#### 异常:
|
||||
- 若父链终止且仍未找到属性,则抛出 `AttributeError`
|
||||
|
||||
#### 查找逻辑:
|
||||
1. 首先检查当前对象的 `__dict__` 是否包含该属性
|
||||
2. 若无,且存在父对象,则递归调用 `getattr(parent, name)`
|
||||
3. 若无父对象仍找不到,抛出 `AttributeError`
|
||||
|
||||
> ✅ 提示:此机制实现了“属性冒泡”式继承。
|
||||
|
||||
#### 示例:
|
||||
```python
|
||||
parent = Genetic()
|
||||
parent.x = 100
|
||||
|
||||
child = Genetic()
|
||||
child.setParent(parent)
|
||||
|
||||
print(child.x) # 输出: 100(从父继承)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `addChild(self, child)`
|
||||
|
||||
将指定对象添加为当前对象的一个子对象,并设置其父引用。
|
||||
|
||||
#### 参数:
|
||||
- `child` (`Genetic`):要添加的子对象实例
|
||||
|
||||
#### 行为:
|
||||
- 将 `child` 添加到 `self.__children__` 列表
|
||||
- 设置 `child.__parent__ = self`
|
||||
|
||||
#### 注意事项:
|
||||
- 不检查重复或类型,调用者需确保传入有效的 `Genetic` 子类实例
|
||||
- 建立的是双向链接
|
||||
|
||||
---
|
||||
|
||||
### `setParent(self, parent)`
|
||||
|
||||
设置当前对象的父对象,等价于让父对象执行 `addChild(self)`。
|
||||
|
||||
#### 参数:
|
||||
- `parent` (`Genetic`):希望设定为父的对象
|
||||
|
||||
#### 行为:
|
||||
- 调用 `parent.addChild(self)`
|
||||
- 自动完成父子关系绑定
|
||||
|
||||
#### 示例:
|
||||
```python
|
||||
p = Genetic()
|
||||
c = Genetic()
|
||||
c.setParent(p) # c 成为 p 的子对象
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 使用示例
|
||||
|
||||
以下是一个完整示例,展示如何使用 `Genetic` 构建四层对象树并进行属性继承:
|
||||
|
||||
```python
|
||||
if __name__ == '__main__':
|
||||
class A(Genetic):
|
||||
def __init__(self, a1, a2):
|
||||
Genetic.__init__(self)
|
||||
self.a1 = a1
|
||||
self.a2 = a2
|
||||
|
||||
class B(Genetic):
|
||||
def __init__(self, b):
|
||||
Genetic.__init__(self)
|
||||
self.b = b
|
||||
|
||||
# 创建对象
|
||||
gp = A(1, 2) # 祖先
|
||||
p = B(3) # 父
|
||||
c = A(4, 5) # 子
|
||||
gc = B(6) # 孙子
|
||||
|
||||
# 建立遗传链:gp → p → c → gc
|
||||
gc.setParent(c)
|
||||
c.setParent(p)
|
||||
p.setParent(gp)
|
||||
|
||||
# 测试属性继承
|
||||
print(gc.a1) # 输出: 1 (从 gp 继承)
|
||||
print(gc.b) # 输出: 3 (从 p 继承)
|
||||
print(c.a2) # 输出: 2 (从 gp 继承)
|
||||
```
|
||||
|
||||
### 结构图解
|
||||
|
||||
```
|
||||
gp (A: a1=1, a2=2)
|
||||
└── p (B: b=3)
|
||||
└── c (A: a1=4, a2=5)
|
||||
└── gc (B: b=6)
|
||||
```
|
||||
|
||||
尽管 `gc` 自身没有定义 `a1`,但由于继承链的存在,它可以通过 `__getattr__` 向上查找直至 `gp` 获取 `a1=1`。
|
||||
|
||||
---
|
||||
|
||||
## 设计原理与适用场景
|
||||
|
||||
### 核心思想
|
||||
|
||||
- **原型式继承**:类似于 JavaScript 的原型链,对象可以直接从另一个对象继承属性。
|
||||
- **运行时动态继承**:继承关系在实例化后仍可更改(如更换父对象)。
|
||||
- **轻量级属性共享**:避免冗余数据复制,适合配置传播、上下文传递等场景。
|
||||
|
||||
### 典型应用场景
|
||||
|
||||
1. **配置系统**:高层配置向下继承,低层可覆盖
|
||||
2. **UI 组件树**:样式/主题继承
|
||||
3. **游戏开发**:角色状态、技能树继承
|
||||
4. **DSL 或规则引擎**:上下文环境逐层传递
|
||||
|
||||
---
|
||||
|
||||
## 注意事项与限制
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| ❌ 循环引用风险 | 不应形成闭环(如 A→B→A),否则 `__getattr__` 可能导致无限递归 |
|
||||
| ⚠️ 属性覆盖逻辑 | 当前仅支持“向上查找”,不支持同名属性优先级控制(即无法区分是否显式定义) |
|
||||
| 📦 私有属性限制 | 双下划线属性(如 `__x`)会被 Python 名称改写,可能导致继承失效 |
|
||||
| 🔁 多重继承不支持 | 当前模型仅为单亲继承,不处理多个父对象的情况 |
|
||||
|
||||
---
|
||||
|
||||
## 扩展建议
|
||||
|
||||
- 添加 `hasattr_recursive()` 方法判断属性是否可继承
|
||||
- 实现 `removeChild()` / `clearParent()` 来解除关系
|
||||
- 增加事件通知机制(如 `on_parent_changed`)
|
||||
- 支持只读继承或深拷贝模式
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
`Genetic` 类提供了一个简洁而强大的对象属性继承框架,利用 Python 的 `__getattr__` 魔法方法和父子引用机制,实现了灵活的运行时属性共享。适合作为基础组件集成进需要层次化数据管理的系统中。
|
||||
|
||||
> 💡 “不是所有对象都需要基因,但一旦拥有,传承便有了意义。”
|
||||
159
aidocs/hf.md
Normal file
159
aidocs/hf.md
Normal file
@ -0,0 +1,159 @@
|
||||
# Hugging Face SOCKS5 代理配置工具
|
||||
|
||||
该脚本用于为 `huggingface_hub` 库配置全局的 SOCKS5 代理,以便在受限网络环境下访问 Hugging Face 的模型和数据集资源。
|
||||
|
||||
---
|
||||
|
||||
## 📌 功能概述
|
||||
|
||||
- 使用 `requests.Session` 自定义 HTTP 后端会话。
|
||||
- 通过 `huggingface_hub.configure_http_backend()` 设置自定义的请求会话工厂。
|
||||
- 支持通过 SOCKS5 代理(使用 `socks5h://` 协议)转发所有对 Hugging Face Hub 的请求。
|
||||
- 可灵活配置代理地址与端口。
|
||||
|
||||
> ✅ **推荐使用 `socks5h://` 而非 `socks5://`**:
|
||||
> `socks5h://` 会在 DNS 解析阶段也通过代理进行(防止 DNS 污染),而 `socks5://` 本地解析域名可能导致连接失败或泄露隐私。
|
||||
|
||||
---
|
||||
|
||||
## 🔧 安装依赖
|
||||
|
||||
确保已安装以下 Python 包:
|
||||
|
||||
```bash
|
||||
pip install requests huggingface-hub[cli]
|
||||
```
|
||||
|
||||
> 若需支持 SOCKS 代理,请额外安装:
|
||||
> ```bash
|
||||
> pip install requests[socks]
|
||||
> ```
|
||||
|
||||
---
|
||||
|
||||
## 📄 模块说明
|
||||
|
||||
### 函数:`hf_socks5proxy(proxies)`
|
||||
|
||||
#### 参数
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 说明 |
|
||||
|--------|----------|------------------------------------------|------|
|
||||
| `proxies` | `dict` | `{ "http": "socks5h://127.0.0.1:1086", "https": "socks5h://127.0.0.1:1086" }` | 指定 HTTP 和 HTTPS 请求使用的代理协议及地址 |
|
||||
|
||||
> ⚠️ 注意:
|
||||
> - 地址中的 `127.0.0.1:1086` 是常见本地代理监听端口(如 Clash、Shadowsocks 等客户端默认设置),请根据实际环境修改。
|
||||
> - 必须同时设置 `http` 和 `https` 的代理项以确保兼容性。
|
||||
|
||||
#### 返回值
|
||||
|
||||
无返回值。此函数直接调用 `configure_http_backend()` 修改全局会话行为。
|
||||
|
||||
#### 内部逻辑
|
||||
|
||||
1. 定义一个 `backend_factory` 工厂函数,每次生成一个新的 `requests.Session` 实例。
|
||||
2. 将传入的 `proxies` 配置应用到该 Session。
|
||||
3. 使用 `huggingface_hub.configure_http_backend()` 注册此工厂函数为默认 HTTP 后端。
|
||||
4. 所有后续由 `huggingface_hub` 发起的网络请求(如 `snapshot_download`, `hf_hub_download` 等)都将走指定的 SOCKS5 代理。
|
||||
|
||||
---
|
||||
|
||||
## 🧪 使用示例
|
||||
|
||||
### 基本调用(使用默认代理)
|
||||
|
||||
```python
|
||||
from hf_proxy import hf_socks5proxy # 假设保存为 hf_proxy.py
|
||||
|
||||
hf_socks5proxy()
|
||||
```
|
||||
|
||||
### 自定义代理地址
|
||||
|
||||
```python
|
||||
hf_socks5proxy({
|
||||
"http": "socks5h://192.168.1.100:1080",
|
||||
"https": "socks5h://192.168.1.100:1080"
|
||||
})
|
||||
```
|
||||
|
||||
### 验证是否生效(可选)
|
||||
|
||||
你可以结合日志输出或抓包工具确认流量是否经过代理。
|
||||
|
||||
---
|
||||
|
||||
## 📎 输出信息
|
||||
|
||||
运行时将打印如下调试信息:
|
||||
|
||||
```text
|
||||
proxies={'http': 'socks5h://127.0.0.1:1086', 'https': 'socks5h://127.0.0.1:1086'}
|
||||
socks5 proxy set proxies={'http': 'socks5h://127.0.0.1:1086', 'https': 'socks5h://127.0.0.1:1086'}
|
||||
```
|
||||
|
||||
表示代理已成功设置。
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
1. **必须安装 `PySocks` 支持**
|
||||
如果未安装 `pysocks` 或 `requests[socks]`,程序会在使用代理时报错:
|
||||
```
|
||||
Missing dependencies for SOCKS support.
|
||||
```
|
||||
解决方法:
|
||||
```bash
|
||||
pip install requests[socks]
|
||||
```
|
||||
|
||||
2. **避免硬编码敏感信息**
|
||||
不建议在代码中明文写死代理地址,生产环境中可通过环境变量注入:
|
||||
|
||||
```python
|
||||
import os
|
||||
proxy = os.getenv("SOCKS5_PROXY", "socks5h://127.0.0.1:1086")
|
||||
hf_socks5proxy({"http": proxy, "https": proxy})
|
||||
```
|
||||
|
||||
3. **仅影响 `huggingface_hub` 的请求**
|
||||
此配置不会改变其他库(如 `requests` 全局调用)的行为,仅作用于 `huggingface_hub` 内部发起的请求。
|
||||
|
||||
---
|
||||
|
||||
## 🧩 示例整合:下载远程模型
|
||||
|
||||
```python
|
||||
from huggingface_hub import hf_hub_download
|
||||
from hf_proxy import hf_socks5proxy
|
||||
|
||||
# 设置代理
|
||||
hf_socks5proxy()
|
||||
|
||||
# 下载模型文件(自动走代理)
|
||||
filepath = hf_hub_download(
|
||||
repo_id="bert-base-uncased",
|
||||
filename="config.json"
|
||||
)
|
||||
print(f"Downloaded to {filepath}")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📚 参考文档
|
||||
|
||||
- Hugging Face Hub SDK: [https://huggingface.co/docs/huggingface_hub](https://huggingface.co/docs/huggingface_hub)
|
||||
- `configure_http_backend`: [https://huggingface.co/docs/huggingface_hub/en/reference/configure#huggingface_hub.configure_http_backend](https://huggingface.co/docs/huggingface_hub/en/reference/configure#huggingface_hub.configure_http_backend)
|
||||
|
||||
---
|
||||
|
||||
## 📎 版本历史
|
||||
|
||||
| 版本 | 描述 |
|
||||
|------|------|
|
||||
| v1.0 | 初始版本,支持基本 SOCKS5 代理注入 |
|
||||
|
||||
---
|
||||
|
||||
✅ 提示:将此功能封装为独立模块(如 `hf_proxy.py`),便于项目复用。
|
||||
311
aidocs/http_client.md
Normal file
311
aidocs/http_client.md
Normal file
@ -0,0 +1,311 @@
|
||||
# HTTP 客户端库技术文档
|
||||
|
||||
这是一个基于 `requests` 库封装的 Python HTTP 客户端工具类,提供了自动会话管理、异常处理和简化接口调用的功能。适用于需要与 Web API 交互的应用场景。
|
||||
|
||||
---
|
||||
|
||||
## 目录
|
||||
|
||||
- [概述](#概述)
|
||||
- [依赖](#依赖)
|
||||
- [异常类型](#异常类型)
|
||||
- [全局变量](#全局变量)
|
||||
- [Http_Client 类](#http_client-类)
|
||||
- [`__init__`](#__init__)
|
||||
- [`prepped_handler`](#prepped_handler)
|
||||
- [`response_handler`](#response_handler)
|
||||
- [`url2domain`](#url2domain)
|
||||
- [`_webcall`](#_webcall)
|
||||
- [`webcall`](#webcall)
|
||||
- [`__call__`](#__call__)
|
||||
- [HTTP 方法封装](#http-方法封装)
|
||||
- `get`
|
||||
- `post`
|
||||
- `put`
|
||||
- `delete`
|
||||
- `option`
|
||||
- [使用示例](#使用示例)
|
||||
|
||||
---
|
||||
|
||||
## 概述
|
||||
|
||||
`Http_Client` 是一个轻量级的 HTTP 客户端封装类,主要功能包括:
|
||||
|
||||
- 自动维护每个域名的 `session`(通过 Cookie 中的 `Set-Cookie` 提取并设置)
|
||||
- 支持常见的 HTTP 方法(GET、POST、PUT、DELETE、OPTION)
|
||||
- 统一处理响应状态码,并抛出相应异常
|
||||
- 对 JSON 响应进行解析,提取业务数据(如 `data` 字段)
|
||||
|
||||
---
|
||||
|
||||
## 依赖
|
||||
|
||||
- `requests`:用于底层 HTTP 请求处理
|
||||
安装方式:
|
||||
```bash
|
||||
pip install requests
|
||||
```
|
||||
|
||||
> ⚠️ 注意:该客户端默认禁用了 SSL 验证(`verify=False`),在生产环境中应谨慎使用。
|
||||
|
||||
---
|
||||
|
||||
## 异常类型
|
||||
|
||||
### `NeedLogin`
|
||||
当服务器返回 `401 Unauthorized` 时抛出,表示当前请求未登录或认证失败。
|
||||
|
||||
```python
|
||||
raise NeedLogin
|
||||
```
|
||||
|
||||
### `InsufficientPrivilege`
|
||||
当服务器返回 `403 Forbidden` 时抛出,表示权限不足。
|
||||
|
||||
```python
|
||||
raise InsufficientPrivilege
|
||||
```
|
||||
|
||||
### `HTTPError`
|
||||
当服务器返回非 `200` 状态码时抛出,包含状态码和请求 URL。
|
||||
|
||||
#### 属性
|
||||
| 属性名 | 类型 | 说明 |
|
||||
|-------------|--------|------------------|
|
||||
| `resp_code` | int | HTTP 响应状态码 |
|
||||
| `url` | str | 请求的完整 URL |
|
||||
|
||||
#### 方法
|
||||
- `__str__()` 和 `__expr__()`:返回格式为 `{url}:{resp_code}` 的字符串
|
||||
|
||||
> ❗注意:`__expr__` 可能是拼写错误,通常应为 `__repr__`。建议修复此方法名为 `__repr__`。
|
||||
|
||||
---
|
||||
|
||||
## 全局变量
|
||||
|
||||
### `hostsessions: dict`
|
||||
存储每个域名对应的 session ID(从 `Set-Cookie` 头部提取),键为协议+主机名(例如 `https://api.example.com`),值为 session 字符串。
|
||||
|
||||
用途:实现跨请求的会话保持。
|
||||
|
||||
---
|
||||
|
||||
## Http_Client 类
|
||||
|
||||
### `__init__()`
|
||||
初始化一个 `Http_Client` 实例。
|
||||
|
||||
#### 行为
|
||||
- 创建一个持久化的 `requests.Session()` 实例
|
||||
- 关闭 SSL 证书验证(`verify=False`)
|
||||
- 注册响应钩子 `response_handler`
|
||||
|
||||
> 🔒 安全提示:关闭 SSL 验证可能导致中间人攻击,仅建议在测试环境使用。
|
||||
|
||||
---
|
||||
|
||||
### `prepped_handler(prepped)`
|
||||
预请求处理器,可用于修改准备好的请求对象(如添加签名、日志等)。
|
||||
|
||||
#### 参数
|
||||
- `prepped` (`PreparedRequest`):已准备好的请求对象
|
||||
|
||||
#### 默认行为
|
||||
无操作(`pass`),可由子类重写以扩展功能。
|
||||
|
||||
---
|
||||
|
||||
### `response_handler(resp, *args, **kw)`
|
||||
响应处理器钩子函数,在每次收到响应后触发。
|
||||
|
||||
#### 参数
|
||||
- `resp` (`Response`):响应对象
|
||||
- `*args`, `**kw`:额外参数(保留扩展性)
|
||||
|
||||
#### 返回值
|
||||
- 返回原始 `resp`,不影响后续处理
|
||||
|
||||
> ✅ 可用于记录日志、性能监控等。
|
||||
|
||||
---
|
||||
|
||||
### `url2domain(url)`
|
||||
从完整 URL 提取协议 + 主机部分(即域名层级)
|
||||
|
||||
#### 参数
|
||||
- `url` (`str`):完整的 URL 地址
|
||||
|
||||
#### 返回值
|
||||
- `str`:形如 `https://example.com` 的字符串
|
||||
|
||||
#### 示例
|
||||
```python
|
||||
client.url2domain("https://api.example.com/v1/users?id=1")
|
||||
# 结果: "https://api.example.com"
|
||||
```
|
||||
|
||||
#### 实现逻辑
|
||||
取前三个 `/` 分隔的部分(协议、空、主机)
|
||||
|
||||
---
|
||||
|
||||
### `_webcall(...)`
|
||||
核心 HTTP 请求执行方法,负责发送请求并处理基础响应。
|
||||
|
||||
#### 参数
|
||||
| 参数 | 类型 | 默认值 | 说明 |
|
||||
|----------|---------|-------|------|
|
||||
| `url` | str | 必填 | 请求地址 |
|
||||
| `method` | str | `"GET"` | HTTP 方法(GET/POST/PUT/DELETE/OPTION) |
|
||||
| `params` | dict | `{}` | GET 请求为查询参数;非 GET 为表单数据 |
|
||||
| `files` | dict | `{}` | 文件上传字段 |
|
||||
| `headers` | dict | `{}` | 自定义请求头 |
|
||||
| `stream` | bool | `False` | 是否启用流式响应 |
|
||||
|
||||
#### 内部流程
|
||||
1. 解析域名 → 获取对应 session ID
|
||||
2. 若存在 session,则在 headers 中加入 `'session': sessionid`
|
||||
3. 构造 `Request` 对象并准备为 `PreparedRequest`
|
||||
4. 调用 `prepped_handler()` 进行预处理
|
||||
5. 发送请求
|
||||
6. 处理响应:
|
||||
- 状态码 `200`:检查是否有 `Set-Cookie`,更新 `hostsessions`
|
||||
- `401`:抛出 `NeedLogin`
|
||||
- `403`:抛出 `InsufficientPrivilege`
|
||||
- 其他非 `200`:打印错误信息并抛出 `HTTPError`
|
||||
|
||||
#### 返回值
|
||||
- 成功时返回 `requests.Response` 对象
|
||||
|
||||
---
|
||||
|
||||
### `webcall(...)`
|
||||
对 `_webcall` 的增强包装,主要用于处理响应体内容。
|
||||
|
||||
#### 参数
|
||||
同 `_webcall`
|
||||
|
||||
#### 功能
|
||||
- 调用 `_webcall` 执行请求
|
||||
- 若 `stream=True`,直接返回响应对象
|
||||
- 否则尝试将响应解析为 JSON:
|
||||
- 若解析成功且结果为字典:
|
||||
- 检查是否存在 `'status'` 字段
|
||||
- 若 `status == 'OK'`,返回 `'data'` 字段内容
|
||||
- 否则返回整个 JSON 数据
|
||||
- 若不是字典或无 `status`,原样返回
|
||||
- 若 JSON 解析失败,返回文本内容(`resp.text`)
|
||||
|
||||
#### 返回值
|
||||
- 解析后的数据(dict / list / str)或原始响应文本
|
||||
|
||||
---
|
||||
|
||||
### `__call__(...)`
|
||||
使实例可被直接调用,代理到 `webcall` 方法。
|
||||
|
||||
#### 示例
|
||||
```python
|
||||
client = Http_Client()
|
||||
result = client("https://api.example.com/data", method="GET")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## HTTP 方法封装
|
||||
|
||||
提供常用 HTTP 方法的快捷调用方式。
|
||||
|
||||
| 方法 | 等效于 |
|
||||
|------|--------|
|
||||
| `.get(...)` | `client(url, method='GET', ...)` |
|
||||
| `.post(...)` | `client(url, method='POST', ...)` |
|
||||
| `.put(...)` | `client(url, method='PUT', ...)` |
|
||||
| `.delete(...)` | `client(url, method='DELETE', ...)` |
|
||||
| `.option(...)` | `client(url, method='OPTION', ...)` |
|
||||
|
||||
所有方法均支持以下参数:
|
||||
- `url`: 请求地址
|
||||
- `params`: 参数(GET 查询参数 或 POST 表单数据)
|
||||
- `headers`: 自定义头部
|
||||
- `files`: 文件上传(仅 POST/PUT 有效)
|
||||
- `stream`: 是否流式接收
|
||||
|
||||
---
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 基本 GET 请求
|
||||
```python
|
||||
client = Http_Client()
|
||||
data = client.get("https://api.example.com/users")
|
||||
print(data)
|
||||
```
|
||||
|
||||
### POST 提交表单
|
||||
```python
|
||||
payload = {"username": "admin", "password": "123456"}
|
||||
resp = client.post("https://api.example.com/login", params=payload)
|
||||
```
|
||||
|
||||
### 上传文件
|
||||
```python
|
||||
files = {'file': open('report.pdf', 'rb')}
|
||||
resp = client.post("https://api.example.com/upload", files=files)
|
||||
```
|
||||
|
||||
### 流式下载大文件
|
||||
```python
|
||||
resp = client.get("https://example.com/large-file.zip", stream=True)
|
||||
with open("download.zip", "wb") as f:
|
||||
for chunk in resp.iter_content(1024):
|
||||
f.write(chunk)
|
||||
```
|
||||
|
||||
### 异常捕获
|
||||
```python
|
||||
try:
|
||||
data = client.get("https://api.example.com/secure-data")
|
||||
except NeedLogin:
|
||||
print("请先登录")
|
||||
except InsufficientPrivilege:
|
||||
print("权限不足")
|
||||
except HTTPError as e:
|
||||
print(f"HTTP 错误: {e}")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 注意事项与改进建议
|
||||
|
||||
1. **SSL 验证关闭**
|
||||
`self.s.verify = False` 存在安全风险,建议增加配置项控制是否关闭验证。
|
||||
|
||||
2. **`__expr__` 应为 `__repr__`**
|
||||
当前 `HTTPError.__expr__` 方法不会被 Python 调用,请更正为 `__repr__`。
|
||||
|
||||
3. **Session Key 名称硬编码**
|
||||
当前假设 Cookie 中第一个字段是 session ID,实际中可能需根据具体服务调整(如 `JSESSIONID`, `token` 等)。
|
||||
|
||||
4. **并发安全性**
|
||||
`hostsessions` 是全局共享字典,在多线程环境下可能存在竞争条件,建议加锁或使用线程局部存储。
|
||||
|
||||
5. **缺少超时设置**
|
||||
推荐在 `send()` 调用中添加 `timeout` 参数防止无限等待。
|
||||
|
||||
6. **headers 更新影响原始输入**
|
||||
`headers.update(...)` 修改了传入的字典,建议复制一份再操作。
|
||||
|
||||
---
|
||||
|
||||
## 版本信息
|
||||
|
||||
- 编写语言:Python 3.x
|
||||
- 第三方依赖:`requests >= 2.20.0`
|
||||
- 许可:MIT(示例代码,实际项目需明确授权)
|
||||
|
||||
---
|
||||
|
||||
✅ 本库适合快速集成 RESTful API 调用,具备良好的扩展性和清晰的异常体系。
|
||||
363
aidocs/httpclient.md
Normal file
363
aidocs/httpclient.md
Normal file
@ -0,0 +1,363 @@
|
||||
# `HttpClient` 与 `JsonHttpAPI` 技术文档
|
||||
|
||||
---
|
||||
|
||||
## 概述
|
||||
|
||||
本文档介绍了一个基于 `aiohttp` 的异步 HTTP 客户端库,包含两个核心类:
|
||||
|
||||
- `HttpClient`:提供灵活、可配置的异步 HTTP 请求功能,支持代理(SOCKS5)、自动重试、Cookie 管理、域名黑名单缓存等。
|
||||
- `JsonHttpAPI`:构建在 `HttpClient` 上的高级接口,用于处理 JSON 格式的 API 调用,支持模板渲染、流式响应处理和动态参数注入。
|
||||
|
||||
该模块适用于需要高并发访问 Web 接口、支持代理切换、具备容错机制的应用场景。
|
||||
|
||||
---
|
||||
|
||||
## 依赖项
|
||||
|
||||
```txt
|
||||
aiohttp
|
||||
aiohttp_socks
|
||||
certifi
|
||||
ssl
|
||||
asyncio
|
||||
urllib.parse
|
||||
json
|
||||
re
|
||||
traceback
|
||||
os
|
||||
appPublic.myTE (自定义模板引擎)
|
||||
appPublic.log (日志模块)
|
||||
```
|
||||
|
||||
> 注意:`appPublic.*` 是项目内部工具模块,请确保已安装或替换为对应实现。
|
||||
|
||||
---
|
||||
|
||||
## 全局常量
|
||||
|
||||
| 常量 | 值 | 含义 |
|
||||
|------|----|------|
|
||||
| `RESPONSE_BIN` | `0` | 返回二进制数据(`bytes`) |
|
||||
| `RESPONSE_TEXT` | `1` | 返回文本字符串(使用编码解码) |
|
||||
| `RESPONSE_JSON` | `2` | 返回解析后的 JSON 对象 |
|
||||
| `RESPONSE_FILE` | `3` | 预留,表示文件下载(当前未使用) |
|
||||
| `RESPONSE_STREAM`| `4` | 流式传输模式(通过 `__call__` 实现) |
|
||||
|
||||
---
|
||||
|
||||
## 工具函数
|
||||
|
||||
### `get_domain(url: str) -> str`
|
||||
|
||||
从 URL 中提取域名(主机名),若 URL 不带协议则默认补全为 `http://`。
|
||||
|
||||
#### 参数:
|
||||
- `url` (str): 输入的 URL 字符串。
|
||||
|
||||
#### 返回值:
|
||||
- `str`: 提取出的域名部分(不包含端口和路径)。
|
||||
|
||||
#### 示例:
|
||||
```python
|
||||
get_domain("https://example.com:8080/path") → "example.com"
|
||||
get_domain("example.org") → "example.org" (自动补全 http)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 异常类
|
||||
|
||||
### `HttpError(code, msg)`
|
||||
|
||||
继承自 `Exception`,表示 HTTP 请求失败时的错误。
|
||||
|
||||
#### 属性:
|
||||
- `code` (int): HTTP 状态码。
|
||||
- `msg` (str): 错误描述信息。
|
||||
|
||||
#### 方法:
|
||||
- `__str__()`: 返回格式化错误信息 `"Error Code:{code}, {msg}"`。
|
||||
- `__expr__()`: 同 `__str__()`,兼容调试输出。
|
||||
|
||||
---
|
||||
|
||||
## 核心类:`HttpClient`
|
||||
|
||||
一个异步 HTTP 客户端,支持 SOCKS5 代理、自动故障转移、Cookie 管理及域名黑名单持久化。
|
||||
|
||||
### 初始化:`__init__(coding='utf-8', socks5_proxy_url=None)`
|
||||
|
||||
#### 参数:
|
||||
- `coding` (str): 文本响应的字符编码,默认 `'utf-8'`。
|
||||
- `socks5_proxy_url` (str or None): 可选的 SOCKS5 代理地址,如 `'socks5://localhost:1086'`。
|
||||
|
||||
#### 内部状态:
|
||||
- `session`: `aiohttp.ClientSession` 实例(延迟初始化)。
|
||||
- `cookies`: 存储各域名 Cookie 的字典。
|
||||
- `proxy_connector`: 当前使用的代理连接器。
|
||||
- `blocked_domains`: 被标记为无法直连需走代理的域名集合(从 `.proxytarget` 文件加载)。
|
||||
- `load_cache()`: 自动加载本地缓存的被屏蔽域名列表。
|
||||
|
||||
---
|
||||
|
||||
### 方法说明
|
||||
|
||||
#### `save_cache()`
|
||||
将当前 `blocked_domains` 集合保存到用户主目录下的 `~/.proxytarget` 文件中,每行一个域名。
|
||||
|
||||
#### `load_cache()`
|
||||
从 `~/.proxytarget` 加载被屏蔽域名。如果文件不存在,则创建空文件。
|
||||
|
||||
#### `close()`
|
||||
关闭当前会话(释放资源),协程方法。
|
||||
|
||||
#### `setCookie(url, cookies)`
|
||||
根据 URL 设置对应域名的 Cookie。
|
||||
|
||||
#### `getCookies(url)`
|
||||
获取指定 URL 所属域名的 Cookies。
|
||||
|
||||
#### `getsession(url)`
|
||||
懒加载并返回 `ClientSession` 实例,启用 `unsafe=True` 的 CookieJar 以接受任意域的 Cookie。
|
||||
|
||||
#### `response_generator(url, resp)`
|
||||
生成器函数,逐块返回响应内容(每次最多 1024 字节),同时更新 Cookie。
|
||||
|
||||
#### `response_handle(url, resp, resp_type, stream_func)`
|
||||
根据 `resp_type` 类型处理响应体,并可选地调用 `stream_func` 处理流式数据。
|
||||
|
||||
| `resp_type` | 行为 |
|
||||
|-------------------|------|
|
||||
| `RESPONSE_BIN` | `await resp.read()` |
|
||||
| `RESPONSE_TEXT` | `await resp.text(encoding)` |
|
||||
| `RESPONSE_JSON` | `await resp.json()` |
|
||||
| 其他 / `None` | 忽略 |
|
||||
|
||||
若提供了 `stream_func`,则以 chunk 方式流式传递数据。
|
||||
|
||||
#### `grapCookie(url)`
|
||||
从当前 Session 的 CookieJar 中提取特定域名的所有 Cookie。
|
||||
|
||||
#### `make_request(...)`
|
||||
底层请求构造函数,支持 GET/POST 等方法,允许传入 `params`, `data`, `jd`(JSON 数据)、`headers`。
|
||||
|
||||
##### 参数:
|
||||
- `url`: 请求地址。
|
||||
- `method`: HTTP 方法(GET、POST 等)。
|
||||
- `params`: 查询参数(dict)。
|
||||
- `data`: 表单数据(bytes 或 dict)。
|
||||
- `jd`: JSON 数据(会被设置为 `json=` 参数)。
|
||||
- `headers`: 请求头。
|
||||
- `use_proxy`: 是否使用 SOCKS5 代理。
|
||||
|
||||
> 若是 HTTPS 请求,自动添加由 `certifi` 提供的 CA 证书上下文。
|
||||
|
||||
#### `get_request_response(...)`
|
||||
智能路由请求:先尝试直连;若失败且存在代理配置,则记录失败域名并改用代理重试。
|
||||
|
||||
##### 特性:
|
||||
- 自动检测是否应绕过代理(不在 `blocked_domains` 中)。
|
||||
- 第一次请求失败后,将域名加入 `blocked_domains` 并持久化。
|
||||
- 支持异常捕获与日志输出。
|
||||
|
||||
#### `request(...)`
|
||||
高层封装,发送请求并按 `response_type` 解析结果。
|
||||
|
||||
##### 参数:
|
||||
- `response_type`: 控制返回类型(见全局常量)。
|
||||
- `stream_func`: 可选的异步回调函数,用于处理流式数据块。
|
||||
- 其余同 `make_request`。
|
||||
|
||||
##### 返回:
|
||||
- 成功时返回相应类型的响应数据。
|
||||
- 失败时抛出 `HttpError`。
|
||||
|
||||
#### `__call__(...)`
|
||||
支持 `async for` 的流式调用方式,适合大文件下载或 Server-Sent Events 场景。
|
||||
|
||||
##### 示例:
|
||||
```python
|
||||
async for chunk in hc('https://example.com/stream'):
|
||||
print(chunk)
|
||||
```
|
||||
|
||||
#### `get(url, **kw)` 和 `post(url, **kw)`
|
||||
便捷方法,分别发起 GET 和 POST 请求。
|
||||
|
||||
---
|
||||
|
||||
## 高级类:`JsonHttpAPI`
|
||||
|
||||
专为调用 RESTful JSON API 设计的模板驱动客户端。
|
||||
|
||||
### 初始化:`__init__(env={}, socks5_proxy_url=None)`
|
||||
|
||||
#### 参数:
|
||||
- `env` (dict): 全局变量环境,供模板渲染使用。
|
||||
- `socks5_proxy_url` (str): 传递给底层 `HttpClient` 的代理设置。
|
||||
|
||||
#### 内部组件:
|
||||
- `te`: 使用 `MyTemplateEngine` 进行模板渲染。
|
||||
- `hc`: 实例化的 `HttpClient`。
|
||||
|
||||
---
|
||||
|
||||
### 方法说明
|
||||
|
||||
#### `stream_func(chunk)`
|
||||
内部流处理器,用于处理换行分隔的 JSON 流(如 SSE)。**注意:代码中有拼写错误 `chuck` 应为 `chunk`**。
|
||||
|
||||
> ⚠️ Bug 提示:原代码中 `d = self.chunk_buffer + chuck` 应改为 `chunk`。
|
||||
|
||||
功能包括:
|
||||
- 缓冲数据并按 `\n` 分割。
|
||||
- 尝试解析每条 JSON 消息。
|
||||
- 使用 `resptmpl` 渲染响应。
|
||||
- 调用用户提供的 `user_stream_func` 回调。
|
||||
|
||||
#### `chunk_handle(chunk, lead, end)`
|
||||
钩子函数,可用于预处理每个数据块(例如去除前缀、添加结束标记)。默认直接返回原块。
|
||||
|
||||
#### `__call__(...)`
|
||||
异步生成器接口,支持流式 API 调用。
|
||||
|
||||
##### 参数:
|
||||
- `url`: API 地址。
|
||||
- `method`: 请求方法。
|
||||
- `ns`: 当前命名空间变量(优先级高于 `env`)。
|
||||
- `headerstmpl`: 请求头的 JSON 模板(字符串形式的 JSON + 模板语法)。
|
||||
- `paramstmpl`: 查询参数模板。
|
||||
- `datatmpl`: 请求体模板(JSON 字符串含变量)。
|
||||
- `chunk_leading`, `chunk_end`: 分块控制标记。
|
||||
- `resptmpl`: 响应数据的输出模板。
|
||||
|
||||
##### 流程:
|
||||
1. 合并 `env` 与 `ns` 得到上下文。
|
||||
2. 渲染各个模板(headers/params/data)。
|
||||
3. 发起流式请求。
|
||||
4. 分块处理响应,可结合 `resptmpl` 动态转换输出。
|
||||
|
||||
##### 输出:
|
||||
- `yield` 经过处理和模板渲染后的每一“段”数据。
|
||||
|
||||
#### `call(...)`
|
||||
同步风格的调用入口(实际仍是 `await` 协程),支持非流式和流式两种模式。
|
||||
|
||||
##### 参数:
|
||||
- `stream_func`: 用户自定义流处理函数(接收 JSON 对象)。
|
||||
- 其他同 `__call__`。
|
||||
|
||||
##### 返回:
|
||||
- 若无 `resptmpl`:原始 JSON 响应。
|
||||
- 若有 `resptmpl`:经模板渲染后再反序列化的 JSON 结果。
|
||||
|
||||
---
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 1. 基础请求(主程序测试)
|
||||
|
||||
```python
|
||||
async def main():
|
||||
hc = HttpClient(socks5_proxy_url='socks5://localhost:1086')
|
||||
|
||||
# 流式读取百度首页
|
||||
async for d in hc('https://www.baidu.com'):
|
||||
print(d)
|
||||
|
||||
# 获取 Google 主页文本
|
||||
r = await hc.request('https://www.google.com')
|
||||
print(r)
|
||||
|
||||
await hc.close()
|
||||
|
||||
if __name__ == '__main__':
|
||||
loop = asyncio.new_event_loop()
|
||||
loop.run_until_complete(main())
|
||||
```
|
||||
|
||||
### 2. 使用 `JsonHttpAPI` 调用模板化 API
|
||||
|
||||
```python
|
||||
api = JsonHttpAPI(env={'token': 'abc123'}, socks5_proxy_url='socks5://127.0.0.1:1086')
|
||||
|
||||
# 定义模板
|
||||
headers_tmpl = '{"Authorization": "Bearer {{token}}"}'
|
||||
params_tmpl = '{"page": "{{page}}"}'
|
||||
|
||||
async def on_chunk(data):
|
||||
print("Received:", data)
|
||||
|
||||
result = await api.call(
|
||||
url="https://api.example.com/data",
|
||||
method="GET",
|
||||
ns={"page": 1},
|
||||
headerstmpl=headers_tmpl,
|
||||
paramstmpl=params_tmpl,
|
||||
stream_func=on_chunk
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 注意事项与建议
|
||||
|
||||
### ✅ 优点
|
||||
- 支持异步高并发。
|
||||
- 内建代理自动切换机制。
|
||||
- Cookie 自动管理。
|
||||
- 模板化请求构建,适合复杂 API 集成。
|
||||
- 黑名单域名持久化,避免重复探测。
|
||||
|
||||
### ❗ 已知问题 / 改进建议
|
||||
|
||||
1. **Bug:`stream_func` 中 `chuck` 拼写错误**
|
||||
```python
|
||||
d = self.chunk_buffer + chuck # 应为 chunk
|
||||
```
|
||||
|
||||
2. **`jd` 参数未正确使用**
|
||||
- 在 `make_request` 中设置了 `hp['jd'] = jd`,但 `aiohttp.request()` 不识别 `jd`。
|
||||
- 应改为 `hp['json'] = jd`。
|
||||
|
||||
3. **`datatmpl` 中 multipart 注释未启用**
|
||||
- 当前行被注释,导致无法上传文件。
|
||||
- 如需支持 form-data,应取消注释并修复逻辑。
|
||||
|
||||
4. **`chunk_handle` 接口设计模糊**
|
||||
- 当前仅作占位,建议明确其用途(如过滤、转换、拼接等)。
|
||||
|
||||
5. **安全性考虑**
|
||||
- `unsafe=True` 的 CookieJar 可能带来安全风险,建议限制作用域。
|
||||
- 模板渲染可能引入注入风险,建议对输入做校验。
|
||||
|
||||
6. **日志级别使用建议**
|
||||
- `info(f'{headers=}...')` 输出敏感信息(如 token),建议降级为 `debug`。
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
本模块是一个功能完整的异步 HTTP 客户端解决方案,特别适用于以下场景:
|
||||
|
||||
- 需要通过 SOCKS5 代理访问受限资源。
|
||||
- 面向多个 JSON API 的自动化集成。
|
||||
- 支持流式响应(如聊天机器人、事件流)。
|
||||
- 具备一定的容错和自适应能力。
|
||||
|
||||
配合模板引擎,可以实现高度可配置的 API 调用系统,适合作为微服务网关、爬虫框架或自动化测试工具的基础组件。
|
||||
|
||||
---
|
||||
|
||||
## 版本信息
|
||||
|
||||
- 作者:未知
|
||||
- 最后修改时间:根据代码推断为近期开发
|
||||
- 兼容性:Python 3.7+
|
||||
|
||||
> 建议增加版本号字段和单元测试覆盖。
|
||||
|
||||
---
|
||||
|
||||
✅ **文档完成**
|
||||
336
aidocs/i18n.md
Normal file
336
aidocs/i18n.md
Normal file
@ -0,0 +1,336 @@
|
||||
# `MiniI18N` 国际化工具技术文档
|
||||
|
||||
```markdown
|
||||
# MiniI18N - 轻量级多语言支持模块
|
||||
|
||||
`MiniI18N` 是一个基于 Python 的轻量级国际化(i18n)工具,用于在应用程序中实现多语言文本的加载与动态切换。它通过读取指定目录下的语言文件(如 `msg.txt`),将键值对形式的消息映射到目标语言,并提供线程安全的语言上下文管理。
|
||||
|
||||
---
|
||||
|
||||
## 模块依赖
|
||||
|
||||
```python
|
||||
import os, re, sys
|
||||
import codecs
|
||||
from appPublic.folderUtils import _mkdir, ProgramPath
|
||||
from appPublic.Singleton import SingletonDecorator
|
||||
from appPublic.jsonConfig import getConfig
|
||||
import threading
|
||||
import time
|
||||
import locale
|
||||
```
|
||||
|
||||
> 说明:该模块依赖于 `appPublic` 包中的若干实用工具类和函数,包括单例装饰器、配置读取、路径处理等。
|
||||
|
||||
---
|
||||
|
||||
## 全局变量与常量
|
||||
|
||||
### 正则表达式
|
||||
|
||||
- `comment_re`: 匹配以 `#` 开头的注释行。
|
||||
```python
|
||||
comment_re = re.compile(r'\s*#.*')
|
||||
```
|
||||
|
||||
- `msg_re`: 匹配形如 `key : value` 的消息条目(允许前后空格)。
|
||||
```python
|
||||
msg_re = re.compile(r'\s*([^:]*)\s*:\s*([^\s].*)')
|
||||
```
|
||||
|
||||
### 编码转换表
|
||||
|
||||
用于对特殊字符进行编码/解码,避免配置文件解析冲突:
|
||||
|
||||
```python
|
||||
convert_pairs = {
|
||||
':': '\\x3A',
|
||||
'\n': '\\x0A',
|
||||
'\r': '\\x0D',
|
||||
}
|
||||
```
|
||||
|
||||
| 原始字符 | 编码表示 |
|
||||
|--------|---------|
|
||||
| `:` | `\x3A` |
|
||||
| `\n` | `\x0A` |
|
||||
| `\r` | `\x0D` |
|
||||
|
||||
---
|
||||
|
||||
## 核心函数
|
||||
|
||||
### `dictModify(d, md)`
|
||||
合并字典 `md` 到字典 `d`,仅当值不为 `None` 时更新。
|
||||
|
||||
**参数:**
|
||||
- `d` (dict): 目标字典
|
||||
- `md` (dict): 更新数据字典
|
||||
|
||||
**返回:**
|
||||
- 修改后的 `d`
|
||||
|
||||
---
|
||||
|
||||
### `charEncode(s)`
|
||||
对字符串中的特殊字符进行编码替换,防止解析错误。
|
||||
|
||||
**参数:**
|
||||
- `s` (str): 待编码字符串
|
||||
|
||||
**逻辑:**
|
||||
1. 将 `\` 替换为 `\\\\`
|
||||
2. 对 `convert_pairs` 中定义的字符进行编码替换
|
||||
|
||||
**示例:**
|
||||
```python
|
||||
charEncode("hello:world\n")
|
||||
# 输出: "hello\\x3Aworld\\x0A"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `charDecode(s)`
|
||||
反向解码由 `charEncode` 编码的字符串。
|
||||
|
||||
**参数:**
|
||||
- `s` (str): 已编码字符串
|
||||
|
||||
**逻辑:**
|
||||
1. 按照 `convert_pairs` 的逆序还原特殊字符
|
||||
2. 将 `\\\\` 还原为 `\`
|
||||
|
||||
**示例:**
|
||||
```python
|
||||
charDecode("hello\\x3Aworld\\x0A")
|
||||
# 输出: "hello:world\n"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `getTextDictFromLines(lines)`
|
||||
从文本行列表中解析出语言键值对字典。
|
||||
|
||||
**参数:**
|
||||
- `lines` (list of str): 文件内容的每一行
|
||||
|
||||
**处理规则:**
|
||||
- 忽略空行和以 `#` 开头的注释行
|
||||
- 使用正则匹配 `key : value` 结构
|
||||
- 对 key 和 value 分别调用 `charDecode` 解码
|
||||
|
||||
**返回:**
|
||||
- dict: `{原始消息: 翻译文本}`
|
||||
|
||||
---
|
||||
|
||||
### `getFirstLang(lang)`
|
||||
从浏览器或系统传入的逗号分隔语言优先级列表中提取首选语言。
|
||||
|
||||
**参数:**
|
||||
- `lang` (str): 如 `"zh-CN,zh;q=0.9,en-US;q=0.8"`
|
||||
|
||||
**返回:**
|
||||
- str: 第一个语言代码(如 `"zh-CN"`)
|
||||
|
||||
---
|
||||
|
||||
## 核心类:`MiniI18N`
|
||||
|
||||
使用 `@SingletonDecorator` 实现单例模式,确保全局唯一实例。
|
||||
|
||||
### 类定义
|
||||
```python
|
||||
@SingletonDecorator
|
||||
class MiniI18N:
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 构造函数 `__init__(path, lang=None, coding='utf8')`
|
||||
|
||||
初始化 i18n 引擎。
|
||||
|
||||
**参数:**
|
||||
- `path` (str): 应用根路径,语言资源位于 `{path}/i18n/`
|
||||
- `lang` (str, optional): 默认语言(可选)
|
||||
- `coding` (str): 文件编码,默认 `'utf8'`
|
||||
|
||||
**初始化行为:**
|
||||
- 获取系统默认语言 `locale.getdefaultlocale()[0]`
|
||||
- 初始化内部字典:
|
||||
- `langTextDict`: 存储各语言的翻译字典
|
||||
- `messages`: 所有出现过的原始消息集合
|
||||
- `clientLangs`: 线程级别的语言上下文(含时间戳)
|
||||
- 加载语言映射配置(来自 `getConfig().langMapping`)
|
||||
- 调用 `setupMiniI18N()` 加载所有语言文件
|
||||
|
||||
---
|
||||
|
||||
### 方法列表
|
||||
|
||||
#### `setLangMapping(lang, path)`
|
||||
设置语言别名映射,例如将 `"zh"` 映射到 `"zh_CN"`。
|
||||
|
||||
**用途:** 支持语言标签标准化。
|
||||
|
||||
```python
|
||||
i18n.setLangMapping('zh', 'zh_CN')
|
||||
```
|
||||
|
||||
#### `getLangMapping(lang)`
|
||||
获取实际使用的语言目录名。若无映射,则返回原语言名。
|
||||
|
||||
#### `setTimeout(timeout=600)`
|
||||
设置客户端语言上下文的有效期(单位:秒)。超时后自动清理过期记录。
|
||||
|
||||
#### `delClientLangs()`
|
||||
清理 `clientLangs` 中超过 `timeout` 时间未访问的线程语言设置。
|
||||
|
||||
> 自动在 `getCurrentLang` 和 `setCurrentLang` 中触发。
|
||||
|
||||
#### `getLangDict(lang)`
|
||||
获取指定语言的完整翻译字典。
|
||||
|
||||
会先通过 `getLangMapping` 解析真实语言名称。
|
||||
|
||||
#### `getLangText(msg, lang=None)`
|
||||
根据当前或指定语言查找翻译文本。
|
||||
|
||||
- 若未找到对应翻译,返回原始 `msg`
|
||||
- 支持 bytes 输入自动解码
|
||||
|
||||
#### `setupMiniI18N()`
|
||||
扫描 `{self.path}/i18n/` 目录下所有子目录(视为语言目录),读取其中的 `msg.txt` 文件并构建翻译字典。
|
||||
|
||||
**结构要求:**
|
||||
```
|
||||
<i18n_root>/
|
||||
└── en_US/
|
||||
└── msg.txt
|
||||
└── zh_CN/
|
||||
└── msg.txt
|
||||
```
|
||||
|
||||
每条记录格式:
|
||||
```
|
||||
hello world : Hello, World!
|
||||
greeting : Welcome to our app.
|
||||
# 这是注释
|
||||
error_open : Failed to open file: %s
|
||||
```
|
||||
|
||||
#### `setCurrentLang(lang)`
|
||||
为当前线程设置语言环境,并记录时间戳。
|
||||
|
||||
**注意:** 基于线程 ID 存储,支持多线程独立语言上下文。
|
||||
|
||||
#### `getCurrentLang()`
|
||||
获取当前线程的语言设置。若未设置,则抛出异常(需确保已调用 `setCurrentLang`)。
|
||||
|
||||
---
|
||||
|
||||
## 辅助函数
|
||||
|
||||
### `getI18N(path=None, coding='utf8')`
|
||||
获取 `MiniI18N` 单例实例的便捷入口。
|
||||
|
||||
**参数:**
|
||||
- `path`: 项目根路径(默认为 `ProgramPath()`)
|
||||
- `coding`: 文件编码
|
||||
|
||||
**返回:**
|
||||
- `MiniI18N` 实例
|
||||
|
||||
**示例:**
|
||||
```python
|
||||
i18n = getI18N()
|
||||
print(i18n("hello world")) # 根据当前线程语言输出翻译
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 1. 准备语言文件
|
||||
|
||||
创建 `./i18n/en_US/msg.txt`:
|
||||
```
|
||||
hello : Hello, World!
|
||||
greeting : Welcome!
|
||||
```
|
||||
|
||||
创建 `./i18n/zh_CN/msg.txt`:
|
||||
```
|
||||
hello : 你好,世界!
|
||||
greeting : 欢迎!
|
||||
```
|
||||
|
||||
### 2. 在代码中使用
|
||||
|
||||
```python
|
||||
from your_module import getI18N
|
||||
|
||||
i18n = getI18N()
|
||||
|
||||
# 设置当前线程语言
|
||||
i18n.setCurrentLang('zh_CN')
|
||||
|
||||
# 获取翻译
|
||||
print(i18n('hello')) # 输出: 你好,世界!
|
||||
print(i18n('greeting')) # 输出: 欢迎!
|
||||
|
||||
# 切换语言
|
||||
i18n.setCurrentLang('en_US')
|
||||
print(i18n('hello')) # 输出: Hello, World!
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 高级特性
|
||||
|
||||
### 语言映射配置(via JSON Config)
|
||||
|
||||
在 `config.json` 中添加:
|
||||
```json
|
||||
{
|
||||
"langMapping": {
|
||||
"zh": "zh_CN",
|
||||
"en": "en_US"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这样即使请求 `zh`,也会自动加载 `zh_CN` 的翻译文件。
|
||||
|
||||
### 线程安全设计
|
||||
|
||||
- 每个线程可通过 `setCurrentLang/lang` 独立设置语言
|
||||
- 使用 `threading.currentThread()` 作为键存储上下文
|
||||
- 定期清理过期线程状态(防止内存泄漏)
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **文件编码必须一致**,默认为 UTF-8。
|
||||
2. 键名中不能包含未转义的 `:`、`\n`、`\r` 等特殊字符(应使用编码形式)。
|
||||
3. 推荐使用英文原始消息作为 key,便于维护。
|
||||
4. `msg.txt` 文件建议按功能模块分类组织。
|
||||
|
||||
---
|
||||
|
||||
## TODO / 扩展建议
|
||||
|
||||
- 支持 `.po` 或 `.yaml` 格式导入
|
||||
- 提供运行时动态添加翻译接口
|
||||
- 添加缺失翻译日志记录机制(`missed_pt` 可扩展)
|
||||
- Web 场景下结合 Cookie 或 Header 自动识别语言
|
||||
|
||||
---
|
||||
```
|
||||
|
||||
> ✅ **版本信息**
|
||||
> 作者:AutoDoc Generator
|
||||
> 生成时间:2025-04-05
|
||||
> 模块版本:v1.0(基于所提供代码分析)
|
||||
356
aidocs/ipgetter.md
Normal file
356
aidocs/ipgetter.md
Normal file
@ -0,0 +1,356 @@
|
||||
# `ipgetter` 模块技术文档
|
||||
|
||||
> **版本**: `0.6`
|
||||
> **作者**: phoemur@gmail.com
|
||||
> **许可证**: [WTFPL v2](http://www.wtfpl.net/) — Do What The Fuck You Want To Public License
|
||||
|
||||
---
|
||||
|
||||
## 简介
|
||||
|
||||
`ipgetter` 是一个轻量级的 Python 模块,用于从互联网获取用户的**外部 IP 地址(公网 IP)**。该模块特别适用于位于 NAT(网络地址转换)后的设备或路由器后端主机。
|
||||
|
||||
它通过随机轮询多个公开的 IP 查询服务来减少对单一服务器的请求压力,并具备容错机制以应对部分服务不可用的情况。
|
||||
|
||||
所有服务器响应内容使用正则表达式解析提取 IP,支持自定义解析器,兼容 Python 2.5+ 及 Python 3.x。
|
||||
|
||||
---
|
||||
|
||||
## 安装与依赖
|
||||
|
||||
### 安装方式
|
||||
|
||||
本模块为单文件脚本,无需安装:
|
||||
|
||||
```bash
|
||||
wget https://raw.githubusercontent.com/phoemur/ipgetter/master/ipgetter.py
|
||||
```
|
||||
|
||||
或直接复制代码保存为 `ipgetter.py`,然后在项目中导入即可。
|
||||
|
||||
### 依赖说明
|
||||
|
||||
- 标准库:
|
||||
- `re`, `json`, `time`, `random`, `socket`, `threading.Timer`
|
||||
- `urllib.request`(通过 `future.moves.urllib.request` 兼容 Py2/Py3)
|
||||
- 第三方兼容层(仅需标准库):
|
||||
- 使用了 `future` 库中的跨版本模块路径(但仍只依赖标准库功能)
|
||||
|
||||
> ⚠️ 注意:虽然引入了 `future.moves.urllib.request`,但并未强制要求安装 `future` 包。若环境不支持,请确保运行于原生 Python 2.7+ 或 3.x 环境。
|
||||
|
||||
---
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 基础用法
|
||||
|
||||
```python
|
||||
import ipgetter
|
||||
|
||||
# 获取当前公网 IP
|
||||
myip = ipgetter.myip()
|
||||
print(myip) # 输出示例: '8.8.8.8'
|
||||
```
|
||||
|
||||
### 高级测试(调试模式)
|
||||
|
||||
```python
|
||||
ipgetter.IPgetter().test()
|
||||
```
|
||||
|
||||
输出将显示所有服务器返回的结果统计,便于验证一致性与可用性。
|
||||
|
||||
---
|
||||
|
||||
## API 接口详解
|
||||
|
||||
### 函数:`myip() → str`
|
||||
|
||||
返回当前机器的公网 IP 地址字符串,失败时返回空字符串。
|
||||
|
||||
#### 示例:
|
||||
|
||||
```python
|
||||
>>> import ipgetter
|
||||
>>> ipgetter.myip()
|
||||
'203.0.113.45'
|
||||
```
|
||||
|
||||
#### 实现逻辑:
|
||||
|
||||
调用 `IPgetter().get_external_ip()` 方法完成实际工作。
|
||||
|
||||
---
|
||||
|
||||
### 类:`IPgetter`
|
||||
|
||||
核心类,提供灵活的 IP 查询控制能力。
|
||||
|
||||
#### 构造函数:`__init__()`
|
||||
|
||||
初始化一个 `IPgetter` 实例,包含以下属性:
|
||||
|
||||
| 属性 | 类型 | 描述 |
|
||||
|------|------|------|
|
||||
| `server_list` | list[str] | 支持的 IP 查询服务 URL 列表(共 18 个默认源) |
|
||||
| `parsers` | dict[str → callable] | 自定义每个服务器响应体的解析函数映射表 |
|
||||
| `timeout` | float | 单次请求超时时间(单位:秒,默认 `1.6` 秒) |
|
||||
| `url` | file-like / None | 当前打开的 URL 资源句柄(用于手动关闭) |
|
||||
|
||||
> 📌 提示:可通过 `add_server()` 动态添加新的查询服务。
|
||||
|
||||
#### 方法:`get_external_ip() → str`
|
||||
|
||||
从随机打乱的服务器列表中依次尝试获取公网 IP,直到成功且符合非私有 IP 规则为止。
|
||||
|
||||
##### 返回值:
|
||||
|
||||
- 成功时返回合法公网 IPv4 字符串(如 `'8.8.8.8'`)
|
||||
- 失败或未匹配有效 IP 时返回 `''`
|
||||
|
||||
##### 过滤规则:
|
||||
|
||||
自动排除以下私有/本地地址段:
|
||||
|
||||
- `192.*` 开头(典型局域网)
|
||||
- `10.*` 开头(内网地址)
|
||||
- `127.*` 开头(回环地址)
|
||||
|
||||
> ✅ 此设计避免误取本地接口地址。
|
||||
|
||||
##### 执行流程:
|
||||
|
||||
1. 打乱 `server_list` 防止单点负载过高
|
||||
2. 循环调用 `fetch(server)` 获取响应
|
||||
3. 使用对应解析器提取 IP(默认使用内置正则)
|
||||
4. 若得到有效公网 IP,则立即返回
|
||||
5. 否则继续下一个服务,全部失败返回空串
|
||||
|
||||
---
|
||||
|
||||
#### 方法:`fetch(server: str) → str`
|
||||
|
||||
向指定服务发起 HTTP 请求并返回其响应中提取出的 IP 地址。
|
||||
|
||||
##### 参数:
|
||||
|
||||
- `server`: 目标服务的完整 URL(必须以 `http://` 或 `https://` 开头)
|
||||
|
||||
##### 内部处理细节:
|
||||
|
||||
- 设置 User-Agent 模拟 Firefox 浏览器请求
|
||||
- 支持设置超时(兼容 Python 2.5 的 socket hack)
|
||||
- 使用定时器防止阻塞过久(尤其在旧版 Python 上)
|
||||
- 自动处理字符编码(优先 UTF-8,失败转 ISO-8859-1)
|
||||
- 异常捕获并打印错误信息(不影响主流程)
|
||||
|
||||
##### 编码处理策略:
|
||||
|
||||
```python
|
||||
if PY3K:
|
||||
try:
|
||||
content = content.decode('UTF-8')
|
||||
except UnicodeDecodeError:
|
||||
content = content.decode('ISO-8859-1')
|
||||
```
|
||||
|
||||
> ❗ 不依赖第三方库(如 `chardet`),坚持使用标准库。
|
||||
|
||||
##### 资源清理:
|
||||
|
||||
无论成功与否,均确保关闭连接、取消定时器、恢复原始 socket 超时设置。
|
||||
|
||||
---
|
||||
|
||||
#### 方法:`defaultparser(content: str) → str`
|
||||
|
||||
默认的 IP 解析函数,使用正则表达式从文本中提取第一个匹配的 IPv4 地址。
|
||||
|
||||
##### 正则表达式:
|
||||
|
||||
```regex
|
||||
(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.
|
||||
(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.
|
||||
(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.
|
||||
(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)
|
||||
```
|
||||
|
||||
✅ 支持完整的 IPv4 地址格式校验。
|
||||
|
||||
##### 行为:
|
||||
|
||||
- 成功提取则返回 IP 字符串
|
||||
- 匹配失败或异常抛出则返回 `''`
|
||||
|
||||
---
|
||||
|
||||
#### 方法:`add_server(server: str, parser: callable)`
|
||||
|
||||
动态注册一个新的 IP 查询服务及其专属解析函数。
|
||||
|
||||
##### 参数:
|
||||
|
||||
- `server`: 新增服务的 URL(例如 `'https://api.ipify.org?format=json'`)
|
||||
- `parser`: 接受 `content: str` 输入并返回 `ip: str` 的可调用对象
|
||||
|
||||
##### 示例:
|
||||
|
||||
```python
|
||||
def parse_ipinfo_json(content):
|
||||
data = json.loads(content)
|
||||
return data['ip']
|
||||
|
||||
g = IPgetter()
|
||||
g.add_server('http://ipinfo.io/json', parse_ipinfo_json)
|
||||
print(g.get_external_ip())
|
||||
```
|
||||
|
||||
> 💡 常用于 JSON 接口的服务扩展。
|
||||
|
||||
---
|
||||
|
||||
#### 方法:`test()`
|
||||
|
||||
测试所有服务器的一致性和可达性,输出汇总报告。
|
||||
|
||||
##### 输出内容:
|
||||
|
||||
- 总服务器数量
|
||||
- 各 IP 地址出现次数统计
|
||||
- 每个服务的实际响应结果字典
|
||||
|
||||
##### 示例输出:
|
||||
|
||||
```
|
||||
Number of servers: 18
|
||||
IP's :
|
||||
8.8.8.8 = 16 occurrences
|
||||
broken server = 2 occurrences
|
||||
|
||||
{'http://ifconfig.me/ip': '8.8.8.8', ...}
|
||||
```
|
||||
|
||||
> 🔍 适合开发者调试或评估服务稳定性。
|
||||
|
||||
---
|
||||
|
||||
#### 方法:`all_result()`
|
||||
|
||||
遍历所有服务器并打印 `[url, response]` 对列表(调试用途)。
|
||||
|
||||
⚠️ 当前实现仅为简单打印,无返回值。
|
||||
|
||||
---
|
||||
|
||||
## 内置服务器列表(截至 v0.6)
|
||||
|
||||
以下是模块默认使用的公共 IP 查询服务(共 18 个):
|
||||
|
||||
```text
|
||||
- http://ifconfig.me/ip
|
||||
- http://ipecho.net/plain
|
||||
- http://getmyipaddress.org/
|
||||
- http://www.my-ip-address.net/
|
||||
- http://www.canyouseeme.org/
|
||||
- http://trackip.net/
|
||||
- http://icanhazip.com/
|
||||
- http://www.ipchicken.com/
|
||||
- http://whatsmyip.net/
|
||||
- http://www.lawrencegoetz.com/programs/ipinfo/
|
||||
- http://ip-lookup.net/
|
||||
- http://ipgoat.com/
|
||||
- http://www.myipnumber.com/my-ip-address.asp
|
||||
- http://www.geoiptool.com/
|
||||
- http://checkip.dyndns.com/
|
||||
- http://www.ip-adress.eu/
|
||||
- http://wtfismyip.com/
|
||||
- http://httpbin.org/ip
|
||||
```
|
||||
|
||||
> 🔄 所有服务按随机顺序访问,降低单点压力。
|
||||
|
||||
> 🛠 如需增删服务,请联系作者 via GitHub。
|
||||
|
||||
---
|
||||
|
||||
## 使用建议与注意事项
|
||||
|
||||
### ✅ 最佳实践
|
||||
|
||||
- 在生产环境中定期调用 `.myip()` 获取最新 IP。
|
||||
- 若发现某些服务频繁失效,可通过 `add_server()` 替换更稳定的替代源。
|
||||
- 使用 `.test()` 定期检查服务健康状态。
|
||||
|
||||
### ⚠️ 注意事项
|
||||
|
||||
- 仅支持 IPv4(目前无 IPv6 支持)
|
||||
- 不保证 100% 准确率(取决于第三方服务可用性)
|
||||
- 错误请求可能触发某些服务的限流机制
|
||||
- 超时设置较短(1.6s),适合快速探测,高延迟网络下可适当调大
|
||||
|
||||
---
|
||||
|
||||
## 许可证
|
||||
|
||||
```
|
||||
Copyright 2014 phoemur@gmail.com
|
||||
|
||||
This work is free. You can redistribute it and/or modify it under the
|
||||
terms of the Do What The Fuck You Want To Public License, Version 2,
|
||||
as published by Sam Hocevar. See http://www.wtfpl.net/ for more details.
|
||||
```
|
||||
|
||||
👉 即:你可以自由地使用、修改、分发此代码,只要你不违法。
|
||||
|
||||
---
|
||||
|
||||
## 贡献与反馈
|
||||
|
||||
欢迎提交 Issue 或 Pull Request 至 GitHub 仓库:
|
||||
|
||||
🔗 [https://github.com/phoemur/ipgetter](https://github.com/phoemur/ipgetter)
|
||||
|
||||
若您希望添加或移除某个 IP 查询服务,请联系作者。
|
||||
|
||||
---
|
||||
|
||||
## 示例:扩展 JSON 接口支持
|
||||
|
||||
```python
|
||||
import ipgetter
|
||||
import json
|
||||
|
||||
def parse_ipinfo(content):
|
||||
return json.loads(content)['ip']
|
||||
|
||||
g = ipgetter.IPgetter()
|
||||
g.add_server('http://ipinfo.io/json', parse_ipinfo)
|
||||
print("My public IP is:", g.get_external_ip())
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 版本历史
|
||||
|
||||
| 版本 | 说明 |
|
||||
|------|------|
|
||||
| `0.6` | 当前版本,优化健壮性,增加可扩展性 |
|
||||
| `0.5` | 初始开源版本 |
|
||||
|
||||
---
|
||||
|
||||
## 致谢
|
||||
|
||||
感谢以下服务提供免费的 IP 查询接口:
|
||||
|
||||
- ifconfig.me
|
||||
- icanhazip.com
|
||||
- ipinfo.io
|
||||
- httpbin.org
|
||||
- dyndns 等
|
||||
|
||||
🙏 维护这些开放资源的人们让此类工具成为可能。
|
||||
|
||||
---
|
||||
|
||||
📘 文档最后更新:2025年4月5日
|
||||
298
aidocs/iplocation.md
Normal file
298
aidocs/iplocation.md
Normal file
@ -0,0 +1,298 @@
|
||||
# IP 地理位置查询工具技术文档
|
||||
|
||||
本项目提供一个基于多个公共 IP 定位 API 的 Python 工具,用于获取指定 IP 地址的地理位置信息(如国家、城市、经纬度等)。支持自动切换不同服务以提高成功率。
|
||||
|
||||
---
|
||||
|
||||
## 目录
|
||||
|
||||
- [简介](#简介)
|
||||
- [依赖库](#依赖库)
|
||||
- [核心功能](#核心功能)
|
||||
- [函数说明](#函数说明)
|
||||
- [`get_outip()`](#get_outip)
|
||||
- [`ipip(ip=None)`](#ipipipnone)
|
||||
- [`ipapi_co(ip)`](#ipapi_coip)
|
||||
- [`ip_api_com(ip)`](#ip_api_comip)
|
||||
- [`iplocation(ip=None)`](#iplocationipnone)
|
||||
- [`get_ip_location(ip)`](#get_ip_locationip)
|
||||
- [使用方式](#使用方式)
|
||||
- [示例输出](#示例输出)
|
||||
- [注意事项](#注意事项)
|
||||
|
||||
---
|
||||
|
||||
## 简介
|
||||
|
||||
该脚本通过调用多个第三方 IP 地理位置查询接口(如 `ip-api.com`、`ipapi.co`、`ipip.net` 和 `iplocate.io`),实现对任意 IP 地址的位置解析。当某个服务不可用或返回错误时,会自动尝试下一个服务,确保结果的可靠性。
|
||||
|
||||
主要用于:
|
||||
- 获取本机公网 IP
|
||||
- 查询任意 IP 的地理位置信息
|
||||
- 多服务容错机制保障高可用性
|
||||
|
||||
---
|
||||
|
||||
## 依赖库
|
||||
|
||||
```txt
|
||||
requests
|
||||
beautifulsoup4 (未实际使用,可移除)
|
||||
appPublic.http_client.Http_Client
|
||||
appPublic.sockPackage.get_free_local_addr
|
||||
```
|
||||
|
||||
> ⚠️ 注意:`appPublic` 是自定义模块,需确保其在系统路径中可用。
|
||||
|
||||
---
|
||||
|
||||
## 公共请求头
|
||||
|
||||
```python
|
||||
public_headers = {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3100.0 Safari/537.36"
|
||||
}
|
||||
```
|
||||
|
||||
所有对外 HTTP 请求均携带此 User-Agent,模拟现代浏览器行为,避免被目标站点拒绝。
|
||||
|
||||
---
|
||||
|
||||
## 函数说明
|
||||
|
||||
### `get_outip()`
|
||||
|
||||
获取当前主机的公网 IPv4 地址。
|
||||
|
||||
#### 返回值
|
||||
- `str`: 当前公网 IP 地址字符串(例如 `"8.8.8.8"`)
|
||||
|
||||
#### 实现原理
|
||||
通过访问 `https://api.ipify.org` 获取外网出口 IP。
|
||||
|
||||
#### 示例
|
||||
```python
|
||||
print(get_outip()) # 输出: 123.45.67.89
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `ipip(ip=None)`
|
||||
|
||||
使用 [ipip.net](http://freeapi.ipip.net/) 免费 API 查询 IP 位置信息。
|
||||
|
||||
#### 参数
|
||||
- `ip` (`str`, 可选): 要查询的 IP 地址;若为 `None`,则自动获取本机公网 IP。
|
||||
|
||||
#### 返回值
|
||||
```json
|
||||
{
|
||||
"country": "中国",
|
||||
"city": "北京"
|
||||
}
|
||||
```
|
||||
|
||||
#### 接口地址
|
||||
```
|
||||
http://freeapi.ipip.net/{ip}
|
||||
```
|
||||
|
||||
> ⚠️ 返回数据为数组格式,脚本仅提取第 0 项(国家)和第 2 项(城市)。
|
||||
|
||||
---
|
||||
|
||||
### `ipapi_co(ip)`
|
||||
|
||||
使用 [ipapi.co](https://ipapi.co/) 提供的 JSON 接口查询 IP 信息。
|
||||
|
||||
#### 参数
|
||||
- `ip` (`str`): 要查询的 IP 地址。
|
||||
|
||||
#### 返回值
|
||||
标准化字段命名后的字典,包含:
|
||||
```json
|
||||
{
|
||||
"ip": "xxx.xxx.xxx.xxx",
|
||||
"country": "CN",
|
||||
"region": "Beijing",
|
||||
"city": "Beijing",
|
||||
"latitude": 39.9042,
|
||||
"longitude": 116.4074,
|
||||
"City": "Beijing", // 兼容大写键名
|
||||
"lat": 39.9042, // 别名
|
||||
"lon": 116.4074 // 别名
|
||||
}
|
||||
```
|
||||
|
||||
> ✅ 自动映射 `city → City`, `latitude → lat`, `longitude → lon`
|
||||
|
||||
---
|
||||
|
||||
### `ip_api_com(ip)`
|
||||
|
||||
使用 [ip-api.com](http://ip-api.com/json/) 查询 IP 地理信息。
|
||||
|
||||
#### 参数
|
||||
- `ip` (`str`): 要查询的 IP 地址。
|
||||
|
||||
#### 返回值
|
||||
原始响应基础上添加了兼容性字段:
|
||||
```json
|
||||
{
|
||||
"status": "success",
|
||||
"country": "China",
|
||||
"city": "Beijing",
|
||||
"City": "Beijing" // 添加大写别名
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `iplocation(ip=None)`
|
||||
|
||||
使用 [iplocate.io](https://www.iplocate.io/) API 查询 IP 信息(需要 API Key)。
|
||||
|
||||
#### 参数
|
||||
- `ip` (`str`, 可选): 查询 IP,若为空则使用公网 IP。
|
||||
|
||||
#### API Key
|
||||
硬编码于代码中:
|
||||
```python
|
||||
apikey = 'c675f89c4a0e9315437a1a5edca9b92c'
|
||||
```
|
||||
|
||||
> 🔐 **安全提示**:API Key 应配置为环境变量或配置文件管理,不建议明文写入代码。
|
||||
|
||||
#### 接口地址
|
||||
```
|
||||
https://www.iplocate.io/api/lookup/{ip}?apikey={apikey}
|
||||
```
|
||||
|
||||
#### 返回值
|
||||
JSON 格式的完整定位信息,包括国家、地区、城市、经纬度、时区等。
|
||||
|
||||
---
|
||||
|
||||
### `get_ip_location(ip)`
|
||||
|
||||
主入口函数:按优先级顺序尝试多个 IP 查询服务,返回第一个成功的结果。
|
||||
|
||||
#### 参数
|
||||
- `ip` (`str`): 待查询的 IP 地址。
|
||||
|
||||
#### 执行流程
|
||||
1. 按照以下顺序依次调用查询函数:
|
||||
- `ip_api_com`
|
||||
- `ipapi_co`
|
||||
- `ipip`
|
||||
- `iplocation`
|
||||
2. 每个函数包裹在 `try-except` 中,失败则跳过。
|
||||
3. 返回首个成功响应的数据。
|
||||
4. 若全部失败,则返回 `None`。
|
||||
|
||||
#### 设计目的
|
||||
提供**高可用、容错性强**的 IP 查询能力。
|
||||
|
||||
---
|
||||
|
||||
## 使用方式
|
||||
|
||||
### 命令行运行
|
||||
|
||||
```bash
|
||||
python script.py [IP地址]
|
||||
```
|
||||
|
||||
#### 示例
|
||||
|
||||
```bash
|
||||
# 查询特定 IP
|
||||
python script.py 8.8.8.8
|
||||
|
||||
# 查询本机公网 IP 位置
|
||||
python script.py
|
||||
```
|
||||
|
||||
> 💡 如果未传入参数,则默认查询本机公网 IP 的位置信息。
|
||||
|
||||
### 作为模块导入
|
||||
|
||||
```python
|
||||
from your_module import get_ip_location
|
||||
|
||||
info = get_ip_location("8.8.8.8")
|
||||
print(info)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 示例输出
|
||||
|
||||
```json
|
||||
{
|
||||
"ip": "8.8.8.8",
|
||||
"country": "United States",
|
||||
"city": "Mountain View",
|
||||
"region": "California",
|
||||
"lat": 37.4056,
|
||||
"lon": -122.0775,
|
||||
"timezone": "America/Los_Angeles"
|
||||
}
|
||||
```
|
||||
|
||||
具体字段取决于所使用的后端服务。
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **异常处理较粗略**
|
||||
- 所有异常均被捕获但无日志记录,不利于调试。
|
||||
- 建议改进为捕获特定异常并输出警告信息。
|
||||
|
||||
2. **API Key 明文暴露风险**
|
||||
- `iplocate.io` 的 `apikey` 写死在代码中,存在泄露风险。
|
||||
- 推荐方案:从环境变量读取
|
||||
```python
|
||||
apikey = os.getenv('IPLOCATE_APIKEY', 'fallback-key')
|
||||
```
|
||||
|
||||
3. **冗余导入**
|
||||
- `BeautifulSoup` 被导入但未使用,建议删除。
|
||||
- `os` 和 `sys` 仅用于基础操作,合理保留。
|
||||
|
||||
4. **HTTP 客户端复用问题**
|
||||
- 每次创建新的 `Http_Client()` 实例,可能影响性能。
|
||||
- 可考虑单例模式或连接池优化。
|
||||
|
||||
5. **服务降级策略**
|
||||
- 当前策略为“任一成功即返回”,适合大多数场景。
|
||||
- 如需更复杂逻辑(如多数投票、精度比较),可扩展。
|
||||
|
||||
6. **编码规范**
|
||||
- 缺少类型注解、函数 docstring。
|
||||
- 建议补充 PEP 257 文档字符串以增强可维护性。
|
||||
|
||||
---
|
||||
|
||||
## 未来优化建议
|
||||
|
||||
| 功能 | 描述 |
|
||||
|------|------|
|
||||
| 配置化 API 列表 | 将服务列表抽离至配置文件,便于动态调整顺序 |
|
||||
| 缓存机制 | 对已查询过的 IP 进行内存缓存,减少重复请求 |
|
||||
| 日志输出 | 加入 logging 模块支持,便于排查问题 |
|
||||
| 异步支持 | 使用 `aiohttp` 改造为异步并发请求,提升效率 |
|
||||
| 单元测试 | 编写测试用例验证各服务解析正确性 |
|
||||
|
||||
---
|
||||
|
||||
## 版权与许可
|
||||
|
||||
© 2025 作者保留所有权利。
|
||||
适用于内部工具或学习用途,生产环境请评估稳定性与安全性。
|
||||
|
||||
---
|
||||
|
||||
> 📝 文档版本:v1.0
|
||||
> 最后更新:2025年4月5日
|
||||
203
aidocs/jsonConfig.md
Normal file
203
aidocs/jsonConfig.md
Normal file
@ -0,0 +1,203 @@
|
||||
# 技术文档:`json_config.py`
|
||||
|
||||
```markdown
|
||||
# JSON 配置管理模块技术文档
|
||||
|
||||
## 概述
|
||||
|
||||
本模块提供了一个基于 JSON 文件的配置管理系统,支持从文件或流中加载 JSON 数据,并通过字典对象方式访问。它结合了单例模式、路径解析和模板变量替换功能,适用于应用程序的配置读取与管理。
|
||||
|
||||
主要特性:
|
||||
- 从 JSON 文件或文件对象加载数据
|
||||
- 支持命名空间(NS)中的变量替换
|
||||
- 使用 `DictObject` 实现属性式访问
|
||||
- 全局唯一配置实例(单例模式)
|
||||
- 跨平台路径处理
|
||||
|
||||
---
|
||||
|
||||
## 依赖库
|
||||
|
||||
### 内置模块
|
||||
- `os`: 路径和环境操作
|
||||
- `sys`: 命令行参数访问
|
||||
- `json`: JSON 序列化/反序列化
|
||||
- `pathlib.Path`: 现代化路径操作
|
||||
|
||||
### 自定义模块(来自 `appPublic` 包)
|
||||
- `DictObject`: 字典封装为可点号访问的对象
|
||||
- `SingletonDecorator`: 单例装饰器
|
||||
- `ProgramPath`: 获取程序运行路径工具
|
||||
- `ArgsConvert`: 支持 `$[var]$` 格式的字符串模板替换
|
||||
|
||||
> ⚠️ 注意:这些类需确保在项目中已正确定义并安装。
|
||||
|
||||
---
|
||||
|
||||
## 函数说明
|
||||
|
||||
### `key2ansi(dict)`
|
||||
> **功能**:将字典的键由 Unicode 编码为 UTF-8 字节串(目前未生效)
|
||||
|
||||
#### 参数
|
||||
| 参数名 | 类型 | 说明 |
|
||||
|--------|----------|----------------|
|
||||
| dict | `dict` | 输入字典 |
|
||||
|
||||
#### 返回值
|
||||
- `dict`: 处理后的字典(当前版本实际未执行任何有效转换)
|
||||
|
||||
> ❗ 注:该函数存在逻辑问题 —— `return dict` 在函数开头即返回,后续代码不可达。建议移除或修复。
|
||||
|
||||
---
|
||||
|
||||
## 类说明
|
||||
|
||||
### `JsonObject(DictObject)`
|
||||
|
||||
> 继承自 `DictObject`,用于从 JSON 源加载数据并构建可属性访问的对象。
|
||||
|
||||
#### 构造函数:`__init__(jsonholder, keytype='ansi', NS=None)`
|
||||
|
||||
##### 参数
|
||||
| 参数名 | 类型 | 必选 | 默认值 | 说明 |
|
||||
|-------------|------------------|------|-----------|------|
|
||||
| `jsonholder`| `str` 或 `file` | 是 | - | JSON 文件路径 或 已打开的文件对象 |
|
||||
| `keytype` | `str` | 否 | `'ansi'` | 键类型(保留字段,当前无实际作用) |
|
||||
| `NS` | `dict` | 否 | `None` | 命名空间,用于模板变量替换 |
|
||||
|
||||
##### 行为描述
|
||||
1. 判断 `jsonholder` 类型:
|
||||
- 若为字符串,则尝试以文本模式打开该路径对应的文件;
|
||||
- 否则视为已打开的文件对象直接使用。
|
||||
2. 使用 `json.load()` 解析内容。
|
||||
3. 若发生异常,打印错误信息并重新抛出。
|
||||
4. 若提供了 `NS`,使用 `ArgsConvert('$[', ']$')` 对结构进行变量替换(如 `$[home]$` → `/Users/name`)。
|
||||
5. 将原始 `jsonholder` 和 `NS` 存入结果字典中作为元数据。
|
||||
6. 调用父类 `DictObject.__init__()` 初始化动态属性。
|
||||
|
||||
##### 示例
|
||||
```python
|
||||
obj = JsonObject("config.json", NS={'home': '/Users/dev'})
|
||||
print(obj.database_url) # 访问配置项
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `JsonConfig(JsonObject)`
|
||||
|
||||
> 单例类,继承自 `JsonObject`,保证全局仅存在一个配置实例。
|
||||
|
||||
#### 特性
|
||||
- 使用 `@SingletonDecorator` 装饰,确保多次调用构造函数返回同一实例。
|
||||
- 通常用于应用级配置共享。
|
||||
|
||||
##### 示例
|
||||
```python
|
||||
cfg1 = JsonConfig("a.json")
|
||||
cfg2 = JsonConfig("a.json")
|
||||
assert cfg1 is cfg2 # True,同一实例
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 工具函数
|
||||
|
||||
### `getConfig(path=None, NS=None) -> JsonConfig`
|
||||
|
||||
> 快捷函数,用于获取默认配置文件 (`conf/config.json`) 的单例实例。
|
||||
|
||||
#### 参数
|
||||
| 参数名 | 类型 | 必选 | 默认值 | 说明 |
|
||||
|--------|----------|------|--------|------|
|
||||
| `path` | `str` | 否 | 当前工作目录 | 配置文件所在根路径 |
|
||||
| `NS` | `dict` | 否 | `None` | 扩展命名空间变量 |
|
||||
|
||||
#### 行为描述
|
||||
1. 获取程序运行路径(`ProgramPath()`)。
|
||||
2. 设置默认命名空间包含:
|
||||
- `'home'`: 用户主目录
|
||||
- `'workdir'`: 当前工作目录或传入路径
|
||||
- `'ProgramPath'`: 程序启动路径
|
||||
3. 若 `NS` 提供额外变量,则合并到命名空间。
|
||||
4. 加载 `<path>/conf/config.json` 文件。
|
||||
5. 返回 `JsonConfig` 单例实例。
|
||||
|
||||
#### 返回值
|
||||
- `JsonConfig`: 配置对象实例,支持属性式访问。
|
||||
|
||||
#### 示例
|
||||
```python
|
||||
config = getConfig()
|
||||
print(config.server.port)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 主程序测试代码
|
||||
```python
|
||||
if __name__ == '__main__':
|
||||
conf = JsonConfig(sys.argv[1])
|
||||
conf1 = JsonConfig(sys.argv[1], keytype='ansi')
|
||||
print("conf=", dir(conf))
|
||||
print("conf1=", dir(conf1))
|
||||
```
|
||||
|
||||
> 运行示例:
|
||||
```bash
|
||||
python json_config.py ./test_config.json
|
||||
```
|
||||
|
||||
输出将显示两个配置对象的所有属性列表。
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **`key2ansi` 函数无效**
|
||||
当前实现中 `return dict` 出现在函数第一行,导致其余代码无法执行。若意图是编码键名为 UTF-8,请删除第一个 `return` 并启用注释代码。
|
||||
|
||||
2. **异常处理仅打印路径信息**
|
||||
异常被捕获后打印 `self.__jsonholder__`,但此时 `self` 尚未完全初始化,可能导致属性不存在。建议改为打印 `jsonholder` 变量。
|
||||
|
||||
3. **文件关闭安全性**
|
||||
- 文件在 `finally` 块中关闭,保障资源释放。
|
||||
- 仅当输入为路径字符串时才关闭文件,避免对传入的文件对象误关闭。
|
||||
|
||||
4. **模板语法**
|
||||
- 使用 `$[var]$` 作为占位符,例如:
|
||||
```json
|
||||
{
|
||||
"data_dir": "$[home]/myapp/data"
|
||||
}
|
||||
```
|
||||
- 在 `NS={'home': '/Users/john'}` 下会被替换为 `/Users/john/myapp/data`
|
||||
|
||||
---
|
||||
|
||||
## 安装要求
|
||||
|
||||
确保以下包可用(一般为内部开发包):
|
||||
```text
|
||||
appPublic.dictObject
|
||||
appPublic.Singleton
|
||||
appPublic.folderUtils
|
||||
appPublic.argsConvert
|
||||
```
|
||||
|
||||
可通过 pip 安装私有包或将其加入 PYTHONPATH。
|
||||
|
||||
---
|
||||
|
||||
## 版本历史
|
||||
|
||||
| 日期 | 修改人 | 描述 |
|
||||
|------------|--------|------|
|
||||
| 2025-04 | 开发者 | 初始版本发布 |
|
||||
|
||||
---
|
||||
```
|
||||
|
||||
> ✅ 文档结束。此 Markdown 可直接集成至项目 Wiki 或生成 HTML/PDF 文档。
|
||||
217
aidocs/jsonIO.md
Normal file
217
aidocs/jsonIO.md
Normal file
@ -0,0 +1,217 @@
|
||||
# 技术文档:JSON 编码与解码工具模块
|
||||
|
||||
本模块提供了一组用于处理 Python 数据结构的 JSON 序列化与反序列化工具函数,支持 Unicode 转换、统一编码处理以及标准化的响应格式封装。
|
||||
|
||||
---
|
||||
|
||||
## 目录
|
||||
|
||||
- [功能概述](#功能概述)
|
||||
- [函数说明](#函数说明)
|
||||
- [`uni_str(a, encoding)`](#uni_stra-encoding)
|
||||
- [`success(data)`](#successdata)
|
||||
- [`error(errors)`](#errorerrors)
|
||||
- [`jsonEncode(data, encode='utf-8')`](#jsonencodedata-encodeutf-8)
|
||||
- [`jsonDecode(jsonstring)`](#jsondecodejsonstring)
|
||||
- [使用示例](#使用示例)
|
||||
- [注意事项](#注意事项)
|
||||
|
||||
---
|
||||
|
||||
## 功能概述
|
||||
|
||||
该模块主要实现以下功能:
|
||||
|
||||
1. **统一字符串编码转换**(`uni_str`):将任意嵌套数据结构中的字符串递归转换为 Unicode 字符串(Python 2 环境下),确保编码一致性。
|
||||
2. **标准响应构造**:提供 `success()` 和 `error()` 函数以返回统一格式的成功/错误响应对象。
|
||||
3. **安全的 JSON 序列化与反序列化**:通过 `jsonEncode` 和 `jsonDecode` 实现对复杂数据类型的兼容性处理。
|
||||
|
||||
> ⚠️ 注意:此代码适用于 **Python 2** 环境(使用了 `unicode` 类型)。在 Python 3 中需进行相应调整。
|
||||
|
||||
---
|
||||
|
||||
## 函数说明
|
||||
|
||||
### `uni_str(a, encoding)`
|
||||
|
||||
递归地将输入数据结构中所有字符串转换为指定编码的 Unicode 字符串。
|
||||
|
||||
#### 参数
|
||||
| 参数 | 类型 | 描述 |
|
||||
|------|------|------|
|
||||
| `a` | 任意类型 | 输入的数据对象(如 str、dict、list、bool 等) |
|
||||
| `encoding` | str | 字符串编码方式,默认通常为 `'utf-8'` |
|
||||
|
||||
#### 返回值
|
||||
返回一个与输入结构相同但所有字符串均为 Unicode 类型的对象。
|
||||
|
||||
#### 处理逻辑
|
||||
| 输入类型 | 处理方式 |
|
||||
|---------|----------|
|
||||
| `None` | 返回 `None` |
|
||||
| `list` 或 `tuple` | 遍历每个元素并递归调用 `uni_str`,返回新列表或元组 |
|
||||
| `dict` | 遍历键值对,分别对键和值递归处理,返回新字典 |
|
||||
| `bool` | 直接返回原值(不转换) |
|
||||
| `unicode` | 已是 Unicode,直接返回 |
|
||||
| `str` 或具有 `__str__` 方法的对象 | 调用 `str()` 并使用指定编码解码为 `unicode` |
|
||||
| 其他类型(int、float 等) | 直接返回原值 |
|
||||
|
||||
#### 示例
|
||||
```python
|
||||
uni_str("hello", "utf-8") # 输出: u'hello'
|
||||
uni_str({"name": "张三"}, "utf-8") # 输出: {u'name': u'\u5f20\u4e09'}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `success(data)`
|
||||
|
||||
构造一个表示操作成功的标准响应对象。
|
||||
|
||||
#### 参数
|
||||
| 参数 | 类型 | 描述 |
|
||||
|------|------|------|
|
||||
| `data` | 任意可序列化对象 | 成功时返回的数据内容 |
|
||||
|
||||
#### 返回值
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": data
|
||||
}
|
||||
```
|
||||
|
||||
#### 示例
|
||||
```python
|
||||
success({"id": 1, "name": "Alice"})
|
||||
# 输出: {'success': True, 'data': {'id': 1, 'name': 'Alice'}}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `error(errors)`
|
||||
|
||||
构造一个表示操作失败的标准错误响应对象。
|
||||
|
||||
#### 参数
|
||||
| 参数 | 类型 | 描述 |
|
||||
|------|------|------|
|
||||
| `errors` | list/dict/str | 错误信息,可以是字符串、列表或字典形式的错误描述 |
|
||||
|
||||
#### 返回值
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"errors": errors
|
||||
}
|
||||
```
|
||||
|
||||
#### 示例
|
||||
```python
|
||||
error("用户名不能为空")
|
||||
# 输出: {'success': False, 'errors': '用户名不能为空'}
|
||||
|
||||
error(["字段A无效", "字段B缺失"])
|
||||
# 输出: {'success': False, 'errors': ['字段A无效', '字段B缺失']}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `jsonEncode(data, encode='utf-8')`
|
||||
|
||||
将任意 Python 对象安全地编码为 JSON 字符串。
|
||||
|
||||
#### 参数
|
||||
| 参数 | 类型 | 描述 |
|
||||
|------|------|------|
|
||||
| `data` | 任意对象 | 待序列化的数据 |
|
||||
| `encode` | str | 编码格式,默认为 `'utf-8'` |
|
||||
|
||||
#### 流程
|
||||
1. 使用 `uni_str(data, encode)` 将所有字符串转换为 Unicode。
|
||||
2. 使用 `json.dumps()` 将处理后的数据序列化为 JSON 字符串。
|
||||
|
||||
#### 返回值
|
||||
- 类型:`str`
|
||||
- 内容:合法的 JSON 格式字符串
|
||||
|
||||
#### 示例
|
||||
```python
|
||||
jsonEncode({"msg": "你好世界"})
|
||||
# 输出: '{"msg": "\\u4f60\\u597d\\u4e16\\u754c"}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `jsonDecode(jsonstring)`
|
||||
|
||||
将 JSON 字符串解析为 Python 对象。
|
||||
|
||||
#### 参数
|
||||
| 参数 | 类型 | 描述 |
|
||||
|------|------|------|
|
||||
| `jsonstring` | str | 合法的 JSON 字符串 |
|
||||
|
||||
#### 返回值
|
||||
- 解析后的 Python 对象(dict、list、str、int 等)
|
||||
|
||||
#### 异常处理
|
||||
- 若输入不是合法 JSON,会抛出 `ValueError`(由 `json.loads` 抛出)
|
||||
|
||||
#### 示例
|
||||
```python
|
||||
jsonDecode('{"name": "Bob", "age": 30}')
|
||||
# 输出: {u'name': u'Bob', u'age': 30}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 使用示例
|
||||
|
||||
```python
|
||||
# 构造成功响应并编码为 JSON
|
||||
response = success({"user_id": 123, "username": "test_user"})
|
||||
json_str = jsonEncode(response)
|
||||
print(json_str)
|
||||
# 输出: {"success": true, "data": {"user_id": 123, "username": "test_user"}}
|
||||
|
||||
# 解码 JSON 字符串
|
||||
decoded = jsonDecode(json_str)
|
||||
print(decoded['data']['username']) # 输出: test_user
|
||||
|
||||
# 构造错误响应
|
||||
err_resp = error(["密码太短", "邮箱格式错误"])
|
||||
err_json = jsonEncode(err_resp)
|
||||
print(err_json)
|
||||
# 输出: {"success": false, "errors": ["密码太短", "邮箱格式错误"]}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **Python 版本兼容性**
|
||||
- 此代码基于 **Python 2** 设计(依赖 `unicode` 类型)。
|
||||
- 在 **Python 3** 中,`unicode` 应替换为 `str`,`str` 替换为 `bytes`,建议重写以适配 Python 3 的字符串模型。
|
||||
|
||||
2. **性能考虑**
|
||||
- `uni_str` 是递归函数,对于深度嵌套或大型数据结构可能影响性能。
|
||||
- 建议仅在必要时使用,或考虑缓存机制优化。
|
||||
|
||||
3. **安全性**
|
||||
- `jsonDecode` 不做额外校验,请确保输入来源可信,防止注入攻击。
|
||||
|
||||
4. **编码假设**
|
||||
- 所有字符串预期使用 UTF-8 编码。若传入其他编码(如 GBK),需确保数据真实编码匹配。
|
||||
|
||||
5. **对象方法调用风险**
|
||||
- 在 `uni_str` 中调用了 `getattr(a, '__str__')` 并执行 `str(a)`,应确保对象的 `__str__` 方法无副作用。
|
||||
|
||||
---
|
||||
|
||||
## 版本信息
|
||||
|
||||
- 兼容环境:Python 2.x
|
||||
- 依赖库:`json`(标准库)
|
||||
|
||||
> ✅ 推荐用于需要统一响应格式与编码处理的传统 Web API 后端项目(如 Django 1.x / Flask 配合 Python 2)。
|
||||
248
aidocs/localefunc.md
Normal file
248
aidocs/localefunc.md
Normal file
@ -0,0 +1,248 @@
|
||||
# 文件编码兼容性处理工具库文档
|
||||
|
||||
本模块提供了一套用于解决在非英文系统(尤其是 Windows 平台)中文件名或字符串因编码问题导致的读取错误的工具函数。主要针对使用非英语字符(如中文、日文等)作为文件名时可能出现的 `UnicodeEncodeError` 或 `LookupError` 问题。
|
||||
|
||||
---
|
||||
|
||||
## 模块功能概述
|
||||
|
||||
- 自动检测系统的本地语言环境和默认编码
|
||||
- 修复 Windows 上某些代码页(code page)未正确注册的问题
|
||||
- 提供对文件操作和字符串编码转换的安全封装,确保跨平台兼容性
|
||||
|
||||
---
|
||||
|
||||
## 依赖项
|
||||
|
||||
```python
|
||||
import sys
|
||||
import locale
|
||||
import codecs
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 初始化:自动修复编码问题(Windows 特定)
|
||||
|
||||
```python
|
||||
language, local_encoding = locale.getdefaultlocale()
|
||||
if sys.platform == 'win32':
|
||||
import locale, codecs
|
||||
local_encoding = locale.getdefaultlocale()[1]
|
||||
if local_encoding.startswith('cp'): # "cp***" ?
|
||||
try:
|
||||
codecs.lookup(local_encoding)
|
||||
except LookupError:
|
||||
import encodings
|
||||
encodings._cache[local_encoding] = encodings._unknown
|
||||
encodings.aliases.aliases[local_encoding] = 'mbcs'
|
||||
```
|
||||
|
||||
### 说明
|
||||
|
||||
在部分 Windows 系统中,`locale.getdefaultlocale()` 返回的编码可能是类似 `'cp936'` 的代码页名称,但这些编码可能不会被 Python 的 `codecs` 模块直接识别,从而引发 `LookupError`。
|
||||
|
||||
此段代码的作用是:
|
||||
- 检查当前系统是否为 Windows (`sys.platform == 'win32'`)
|
||||
- 获取本地编码(如 `cp936`, `cp1252` 等)
|
||||
- 如果编码以 `"cp"` 开头(表示 Windows 代码页),尝试查找其对应的编解码器
|
||||
- 若查找失败,则手动将该编码映射到 `'mbcs'`(Multi-Byte Character Set),这是 Python 中处理 Windows 本地编码的通用方式
|
||||
|
||||
> ✅ **目的**:防止后续使用 `.encode(local_encoding)` 时报错,提升稳定性。
|
||||
|
||||
---
|
||||
|
||||
## 函数接口
|
||||
|
||||
### `locale_open(filename, mode='rb')`
|
||||
|
||||
打开一个文件,支持包含非 ASCII 字符(如中文)的文件名。
|
||||
|
||||
#### 参数
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
|------|------|------|
|
||||
| `filename` | `str` | 要打开的文件路径(可含非英文字符) |
|
||||
| `mode` | `str` | 文件打开模式,默认为 `'rb'`(二进制读取) |
|
||||
|
||||
#### 返回值
|
||||
|
||||
- 返回一个文件对象(`file object`),可用标准方法(如 `.read()`, `.close()`)操作。
|
||||
|
||||
#### 实现原理
|
||||
|
||||
将 `filename` 使用系统本地编码(`local_encoding`)进行编码后传给内置 `open()` 函数:
|
||||
|
||||
```python
|
||||
return open(filename.encode(local_encoding), mode)
|
||||
```
|
||||
|
||||
> ⚠️ 注意:Python 内部通常用 Unicode 处理字符串,但在与操作系统交互(如打开文件)时需转换为字节流。此函数确保使用正确的本地编码完成转换。
|
||||
|
||||
#### 示例
|
||||
|
||||
```python
|
||||
# 假设有一个名为 “报告.txt” 的文件
|
||||
with locale_open("报告.txt", "r", encoding="utf-8") as f: # 注意:此处不能直接加 encoding 参数
|
||||
content = f.read()
|
||||
```
|
||||
|
||||
> ❗ 提示:由于 `locale_open` 返回的是原始 `open()` 对象,若要指定文本编码(如 UTF-8),建议配合 `io.TextIOWrapper` 使用,或改写为更现代的方式(见下方“注意事项”)。
|
||||
|
||||
---
|
||||
|
||||
### `localeString(s)`
|
||||
|
||||
将输入字符串视为 UTF-8 编码,并转换为本地编码。
|
||||
|
||||
#### 参数
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
|------|------|------|
|
||||
| `s` | `str` 或 `bytes` | 输入字符串 |
|
||||
|
||||
#### 返回值
|
||||
|
||||
- 成功:返回以本地编码(`local_encoding`)编码的字节串或字符串
|
||||
- 失败:返回原输入 `s`
|
||||
|
||||
#### 逻辑流程
|
||||
|
||||
```python
|
||||
try:
|
||||
return unicode(s, 'utf-8').encode(local_encoding)
|
||||
except:
|
||||
return s
|
||||
```
|
||||
|
||||
> 在 Python 2 中,`unicode()` 是内置函数;如果是 Python 3,请注意兼容性(见下方“兼容性说明”)。
|
||||
|
||||
#### 用途
|
||||
|
||||
适用于需要将 UTF-8 数据输出到仅支持本地编码的环境(如控制台、旧版 API)。
|
||||
|
||||
---
|
||||
|
||||
### `utf8String(s)`
|
||||
|
||||
将输入字符串从本地编码解码,并重新编码为 UTF-8。
|
||||
|
||||
#### 参数
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
|------|------|------|
|
||||
| `s` | `str` 或 `bytes` | 输入字符串 |
|
||||
|
||||
#### 返回值
|
||||
|
||||
- 成功:返回 UTF-8 编码的字符串或字节串
|
||||
- 失败:返回原输入 `s`
|
||||
|
||||
#### 逻辑流程
|
||||
|
||||
```python
|
||||
try:
|
||||
return unicode(s, local_encoding).encode('utf-8')
|
||||
except:
|
||||
return s
|
||||
```
|
||||
|
||||
#### 用途
|
||||
|
||||
用于统一内部数据为 UTF-8 格式,便于跨平台传输或存储。
|
||||
|
||||
---
|
||||
|
||||
### `charsetString(s, charset)`
|
||||
|
||||
将输入字符串转换为目标字符集(`charset`)编码。
|
||||
|
||||
#### 参数
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
|------|------|------|
|
||||
| `s` | `str` 或 `bytes` | 输入字符串 |
|
||||
| `charset` | `str` | 目标编码格式,例如 `'gbk'`, `'latin1'`, `'utf-16'` 等 |
|
||||
|
||||
#### 返回值
|
||||
|
||||
- 成功:返回目标编码格式的字符串/字节串
|
||||
- 失败:尝试先按 UTF-8 解码再转码
|
||||
- 所有尝试失败:返回原始输入 `s`
|
||||
|
||||
#### 转换优先级
|
||||
|
||||
1. 尝试将 `s` 以本地编码解码 → 编码为 `charset`
|
||||
2. 若失败,尝试以 UTF-8 解码 → 编码为 `charset`
|
||||
3. 都失败则返回原值
|
||||
|
||||
#### 示例
|
||||
|
||||
```python
|
||||
result = charsetString("你好", "gbk") # 将字符串转为 GBK 编码
|
||||
```
|
||||
|
||||
#### 用途
|
||||
|
||||
灵活地实现多编码之间的互转,增强程序适应不同编码环境的能力。
|
||||
|
||||
---
|
||||
|
||||
## 兼容性说明
|
||||
|
||||
⚠️ **重要提示**:以上代码基于 **Python 2** 语法编写(使用了 `unicode()` 函数)。在 **Python 3** 中无法直接运行。
|
||||
|
||||
### Python 3 迁移建议
|
||||
|
||||
| Python 2 | Python 3 替代方案 |
|
||||
|----------|------------------|
|
||||
| `unicode(s, enc)` | `str(s, encoding=enc)` 或 `s.decode(enc)` |
|
||||
| `str/bytes` 区分明确 | 推荐统一使用 `str`(Unicode)为主,I/O 时显式指定编码 |
|
||||
| `open(filename.encode(...))` | 可直接 `open(str_filename, encoding=...)` |
|
||||
|
||||
#### 推荐替代方案(Python 3)
|
||||
|
||||
```python
|
||||
def safe_open(filename, mode='r', encoding='utf-8'):
|
||||
"""安全打开带非英文名的文件(Python 3)"""
|
||||
return open(filename, mode=mode, encoding=encoding)
|
||||
|
||||
# 字符串编码转换推荐使用 encode/decode 显式处理
|
||||
def to_local(s):
|
||||
return s.encode(local_encoding, errors='replace').decode(local_encoding, errors='replace')
|
||||
|
||||
def to_utf8(s):
|
||||
return s.encode('utf-8', errors='ignore').decode('utf-8', errors='ignore')
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 使用场景
|
||||
|
||||
| 场景 | 推荐函数 |
|
||||
|------|----------|
|
||||
| 打开含有中文文件名的文件(Win) | `locale_open()` |
|
||||
| 将网页内容(UTF-8)显示在控制台(GBK) | `localeString()` |
|
||||
| 统一日志输出为 UTF-8 | `utf8String()` |
|
||||
| 导出数据到特定编码文件(如 GBK CSV) | `charsetString()` |
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **仅限于 Python 2 环境**:此脚本不适用于 Python 3。
|
||||
2. **Windows 主要适用**:Linux/macOS 通常默认 UTF-8,较少出现此类问题。
|
||||
3. **异常捕获过于宽泛**:所有 `except:` 应替换为具体异常类型以提高健壮性。
|
||||
4. **性能影响小**:主要用于边缘情况修复,不影响主流程性能。
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
本模块通过动态修复编码映射、封装文件打开及字符串转换逻辑,有效解决了 Windows 下因区域设置导致的非英文文件名访问难题。尽管技术上属于“补丁式”解决方案,但在维护遗留系统时具有实用价值。
|
||||
|
||||
> ✅ **建议**:新项目应使用 Python 3 + 显式编码声明,避免此类隐式编码问题。
|
||||
|
||||
---
|
||||
|
||||
📌 **作者备注**:该脚本体现了早期 Python 在国际化支持方面的局限性,也展示了社区常见的“打补丁”式应对策略。
|
||||
223
aidocs/log.md
Normal file
223
aidocs/log.md
Normal file
@ -0,0 +1,223 @@
|
||||
# 技术文档:Python 日志记录模块
|
||||
|
||||
```markdown
|
||||
# MyLogger 模块技术文档
|
||||
|
||||
## 概述
|
||||
|
||||
本模块提供一个轻量级、线程安全(基于单例模式)的日志记录工具,支持多级别日志输出,并可将日志写入文件或标准输出。该模块通过 `inspect` 模块自动获取调用上下文信息(如文件名、行号),并结合时间戳生成结构化日志。
|
||||
|
||||
---
|
||||
|
||||
## 依赖项
|
||||
|
||||
- `sys`: 用于访问标准输出流。
|
||||
- `codecs`: 提供带编码支持的文件读写功能(UTF-8)。
|
||||
- `traceback.format_exc`: 用于捕获和格式化异常堆栈信息。
|
||||
- `appPublic.timeUtils.timestampstr`: 返回当前时间的时间戳字符串(格式依赖具体实现)。
|
||||
- `appPublic.Singleton.SingletonDecorator`: 单例装饰器,确保 `MyLogger` 类为单例模式。
|
||||
- `inspect`: 获取运行时调用栈信息,用于提取调用者文件名和行号。
|
||||
|
||||
> ⚠️ 注意:`appPublic.*` 是自定义公共库,请确保其已正确安装并可导入。
|
||||
|
||||
---
|
||||
|
||||
## 核心类:`MyLogger`
|
||||
|
||||
### 装饰器
|
||||
```python
|
||||
@SingletonDecorator
|
||||
class MyLogger:
|
||||
```
|
||||
使用 `SingletonDecorator` 确保每个 `name` 对应唯一的 `MyLogger` 实例(注意:当前实现中未按 name 区分实例,可能存在设计缺陷 —— 见【注意事项】)。
|
||||
|
||||
---
|
||||
|
||||
### 属性说明
|
||||
|
||||
| 属性 | 类型 | 描述 |
|
||||
|------|------|------|
|
||||
| `levels` | `dict` | 定义日志级别及其优先级数值(数字越大,优先级越高)。 |
|
||||
| `formater` | `str` | 日志输出格式模板,兼容 `%` 格式化语法。 |
|
||||
|
||||
#### 日志级别说明(按优先级从高到低)
|
||||
|
||||
| 级别名称 | 数值 | 用途 |
|
||||
|---------|-----|------|
|
||||
| `clientinfo` | 7 | 客户端相关信息,最高优先级 |
|
||||
| `info` | 6 | 常规信息提示 |
|
||||
| `debug` | 5 | 调试信息 |
|
||||
| `warning` | 4 | 警告信息 |
|
||||
| `error` | 3 | 错误信息 |
|
||||
| `exception` | 2 | 异常堆栈信息 |
|
||||
| `critical` | 1 | 致命错误,最低优先级 |
|
||||
|
||||
> 输出规则:仅当日志条目的级别 ≤ 当前 logger 的 `level` 时才会被记录。
|
||||
|
||||
---
|
||||
|
||||
### 构造函数:`__init__(self, name, levelname='debug', logfile=None)`
|
||||
|
||||
#### 参数
|
||||
|
||||
| 参数 | 类型 | 默认值 | 描述 |
|
||||
|------|------|--------|------|
|
||||
| `name` | `str` | 必填 | 日志记录器名称,用于标识来源 |
|
||||
| `levelname` | `str` | `'debug'` | 初始日志级别,决定哪些消息会被输出 |
|
||||
| `logfile` | `str or None` | `None` | 日志文件路径;若为 `None`,则输出到 `sys.stdout` |
|
||||
|
||||
#### 初始化行为
|
||||
|
||||
- 设置 `self.name`, `self.levelname`, `self.level`(对应数值)
|
||||
- 设置 `self.logfile` 和初始 `self.logger = None`
|
||||
|
||||
---
|
||||
|
||||
### 方法说明
|
||||
|
||||
#### `open_logger(self)`
|
||||
打开日志目标:
|
||||
- 若指定了 `logfile`,以追加模式 (`a`) 打开文件,使用 UTF-8 编码。
|
||||
- 否则,使用 `sys.stdout` 作为输出流。
|
||||
|
||||
> 使用 `codecs.open` 确保 Unicode 写入安全。
|
||||
|
||||
#### `close_logger(self)`
|
||||
关闭已打开的日志文件句柄(如果存在),并将 `self.logger` 设为 `None`。
|
||||
|
||||
> ❗ 存在 bug:最后一行重复赋值 `self.logger = None` 多余且无意义。
|
||||
|
||||
#### `log(self, levelname, message, frame_info)`
|
||||
核心日志写入方法。
|
||||
|
||||
##### 参数
|
||||
| 参数 | 类型 | 描述 |
|
||||
|------|------|------|
|
||||
| `levelname` | `str` | 要记录的日志级别 |
|
||||
| `message` | `str` | 日志内容 |
|
||||
| `frame_info` | `frame object` | 调用栈帧对象,来自 `inspect.currentframe()` |
|
||||
|
||||
##### 行为流程
|
||||
1. 获取调用者的栈帧 → 提取 `filename` 和 `lineno`
|
||||
2. 查询 `levelname` 对应的等级值
|
||||
3. 如果该等级高于当前 logger 允许的最高等级(即 `level > self.level`),则跳过输出
|
||||
4. 构造日志数据字典 `data`
|
||||
5. 调用 `open_logger()` 打开输出
|
||||
6. 使用 `formater % data` 格式化日志字符串
|
||||
7. 写入并刷新缓冲区
|
||||
8. 调用 `close_logger()` 关闭资源
|
||||
|
||||
> ✅ 特点:每次写日志都临时打开/关闭文件,适合低频日志场景,避免长期占用句柄。
|
||||
|
||||
---
|
||||
|
||||
## 辅助函数(全局接口)
|
||||
|
||||
提供简洁的日志调用方式,封装了 `MyLogger` 的创建与调用过程。
|
||||
|
||||
| 函数 | 等效操作 | 示例 |
|
||||
|------|----------|-------|
|
||||
| `clientinfo(message)` | `logger.log('clientinfo', message, ...)` | `clientinfo("用户登录")` |
|
||||
| `info(message)` | `logger.log('info', message, ...)` | `info("任务开始执行")` |
|
||||
| `debug(message)` | `logger.log('debug', message, ...)` | `debug("变量x值为: " + str(x))` |
|
||||
| `warning(message)` | `logger.log('warning', message, ...)` | `warning("配置项缺失")` |
|
||||
| `error(message)` | `logger.log('error', message, ...)` | `error("数据库连接失败")` |
|
||||
| `critical(message)` | `logger.log('critical', message, ...)` | `critical("系统即将退出")` |
|
||||
| `exception(message)` | 记录异常堆栈 + 自定义消息 | `exception("处理请求出错")` |
|
||||
|
||||
> 📌 `exception()` 特殊处理:自动附加 `traceback.format_exc()` 的完整堆栈跟踪。
|
||||
|
||||
---
|
||||
|
||||
## 使用示例
|
||||
|
||||
```python
|
||||
from mylogger import info, debug, error, exception
|
||||
|
||||
def main():
|
||||
info("程序启动")
|
||||
debug("正在加载配置...")
|
||||
try:
|
||||
1 / 0
|
||||
except Exception:
|
||||
exception("发生数学错误")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
|
||||
输出示例(假设输出到控制台):
|
||||
```
|
||||
2025-04-05 10:20:30.123[Test][info][main.py:5]程序启动
|
||||
2025-04-05 10:20:30.124[Test][debug][main.py:6]正在加载配置...
|
||||
2025-04-05 10:20:30.125[exception][exception][main.py:8]发生数学错误
|
||||
Traceback (most recent call last):
|
||||
File "main.py", line 7, in main
|
||||
1 / 0
|
||||
ZeroDivisionError: division by zero
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 配置说明
|
||||
|
||||
### 日志格式化字符串
|
||||
```python
|
||||
'%(timestamp)s[%(name)s][%(levelname)s][%(filename)s:%(lineno)s]%(message)s\n'
|
||||
```
|
||||
|
||||
字段解释:
|
||||
|
||||
| 字段 | 含义 |
|
||||
|------|------|
|
||||
| `%(timestamp)s` | 时间戳(由 `timestampstr()` 提供) |
|
||||
| `%(name)s` | Logger 名称 |
|
||||
| `%(levelname)s` | 日志级别名称 |
|
||||
| `%(filename)s` | 发生日志的源文件名 |
|
||||
| `%(lineno)s` | 源代码行号 |
|
||||
| `%(message)s` | 用户提供的日志消息 |
|
||||
|
||||
---
|
||||
|
||||
## 注意事项与改进建议
|
||||
|
||||
### ⚠️ 已知问题
|
||||
|
||||
1. **单例逻辑可能失效**
|
||||
- `SingletonDecorator` 可能对所有 `MyLogger` 创建返回同一实例,无法区分不同 `name`。
|
||||
- 建议改为基于 `name` 的单例缓存机制。
|
||||
|
||||
2. **`close_logger()` 中冗余赋值**
|
||||
```python
|
||||
self.logger.close()
|
||||
self.logger = None
|
||||
self.logger = None # 重复
|
||||
```
|
||||
第二个赋值无效,应删除。
|
||||
|
||||
3. **性能考量**
|
||||
- 每次 `log()` 都打开/关闭文件,在高频日志场景下效率低下。
|
||||
- 建议引入持久化文件句柄管理(如首次打开后保持打开状态)。
|
||||
|
||||
4. **缺乏线程安全性**
|
||||
- 尽管是“单例”,但未加锁,多线程同时写日志可能导致混乱。
|
||||
- 如需多线程支持,建议添加 `threading.Lock`。
|
||||
|
||||
5. **硬编码 logger 名称**
|
||||
- 所有辅助函数中 `logger = MyLogger('Test')` 固定为 `'Test'`,不合理。
|
||||
- 应允许传参或动态设置默认名称。
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
本模块实现了基本的日志功能,具备以下优点:
|
||||
|
||||
✅ 结构清晰
|
||||
✅ 支持自动定位调用位置
|
||||
✅ 支持文件/控制台双输出
|
||||
✅ 提供丰富的日志级别
|
||||
✅ 易于使用的全局函数接口
|
||||
|
||||
适用于小型项目或嵌入式脚本环境。对于大型应用,建议升级至标准库 `logging` 模块或增强此模块的功能与健壮性。
|
||||
```
|
||||
152
aidocs/macAddress.md
Normal file
152
aidocs/macAddress.md
Normal file
@ -0,0 +1,152 @@
|
||||
# 网络接口信息获取工具技术文档
|
||||
|
||||
## 概述
|
||||
|
||||
该 Python 脚本用于获取当前系统中处于活动状态的网络接口的 IP 地址和 MAC 地址。它利用 `psutil` 和 `socket` 库来收集网络接口的状态与配置信息,仅返回正在发送和接收数据的活跃网络接口的相关地址。
|
||||
|
||||
---
|
||||
|
||||
## 依赖库
|
||||
|
||||
- `psutil`:跨平台系统资源监控库,用于获取网络接口统计、状态和地址信息。
|
||||
- `socket`:标准库,用于处理网络协议常量(如 `AF_INET` 和 `AF_PACKET`)。
|
||||
- `locale`:标准库,用于获取系统默认编码(在获取 MAC 地址时使用,但实际未直接涉及编码转换)。
|
||||
|
||||
> ⚠️ **注意**:运行此脚本前需安装 `psutil`:
|
||||
```bash
|
||||
pip install psutil
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 函数说明
|
||||
|
||||
### `getAllAddress()`
|
||||
|
||||
#### 功能
|
||||
获取所有**活跃网络接口的 IPv4 地址**。
|
||||
|
||||
#### 返回值
|
||||
一个生成器(generator),每次迭代返回一个元组 `(interface_name, ip_address)`,其中:
|
||||
- `interface_name`:网络接口名称(如 `eth0`, `wlan0` 等)
|
||||
- `ip_address`:该接口的 IPv4 地址字符串
|
||||
|
||||
#### 实现逻辑
|
||||
1. 使用 `psutil.net_io_counters(pernic=True)` 获取每个网络接口的数据收发统计。
|
||||
2. 筛选出 **发送和接收字节数均大于 0** 的接口(即活跃接口)。
|
||||
3. 使用 `psutil.net_if_stats()` 获取接口状态(如是否启用、速度等),进一步确认其可用性。
|
||||
4. 遍历 `psutil.net_if_addrs()` 中的接口地址信息,查找具有 `socket.AF_INET` 家族的地址(即 IPv4 地址)。
|
||||
5. 对符合条件的接口,逐个通过 `yield` 返回其名称和 IP 地址。
|
||||
|
||||
#### 示例输出
|
||||
```python
|
||||
('eth0', '192.168.1.100')
|
||||
('wlan0', '192.168.0.105')
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `getAllMacAddress()`
|
||||
|
||||
#### 功能
|
||||
获取所有**活跃网络接口的 MAC 地址**。
|
||||
|
||||
#### 返回值
|
||||
一个生成器,每次迭代返回一个元组 `(interface_name, mac_address)`,其中:
|
||||
- `interface_name`:网络接口名称
|
||||
- `mac_address`:该接口的 MAC 地址字符串(格式如 `aa:bb:cc:dd:ee:ff`)
|
||||
|
||||
#### 实现逻辑
|
||||
1. 获取系统默认编码(`locale.getdefaultlocale()[1]`),虽然当前代码中并未实际使用该编码进行解码操作,可能是冗余或预留扩展。
|
||||
2. 与 `getAllAddress()` 类似,筛选出活跃且启用的网络接口。
|
||||
3. 遍历接口地址信息,查找具有 `socket.AF_PACKET` 家族的地址(Linux 上表示链路层协议,包含 MAC 地址)。
|
||||
4. 通过 `yield` 返回接口名和对应的 MAC 地址。
|
||||
|
||||
> 📌 注意:`AF_PACKET` 是 Linux 特有的;在非 Linux 平台(如 Windows)上可能不支持或行为不同。
|
||||
|
||||
#### 示例输出
|
||||
```python
|
||||
('eth0', '00:1A:2B:3C:4D:5E')
|
||||
('wlan0', '02:2A:3B:4C:5D:6E')
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 主程序入口(`if __name__ == '__main__':`)
|
||||
|
||||
### 功能
|
||||
提供简单的测试功能,调用 `getAllAddress()` 并打印结果。
|
||||
|
||||
#### 当前行为
|
||||
- 执行 `test()` 函数,遍历 `getAllAddress()` 的结果。
|
||||
- 输出格式为:`mac= (interface_name, ip_address)`
|
||||
> ❗ 注意:尽管变量名为 `mac=`,实际输出的是 IP 地址信息,属于命名错误。
|
||||
|
||||
#### 示例输出
|
||||
```text
|
||||
mac= ('eth0', '192.168.1.100')
|
||||
mac= ('wlan0', '192.168.0.105')
|
||||
```
|
||||
|
||||
> 🔴 建议修复:应将 `print("mac=",i)` 改为 `print("ip=", i)` 以避免误导。
|
||||
|
||||
---
|
||||
|
||||
## 使用示例
|
||||
|
||||
```python
|
||||
# 获取所有活跃接口的 IPv4 地址
|
||||
for iface, ip in getAllAddress():
|
||||
print(f"Interface: {iface}, IP: {ip}")
|
||||
|
||||
# 获取所有活跃接口的 MAC 地址
|
||||
for iface, mac in getAllMacAddress():
|
||||
print(f"Interface: {iface}, MAC: {mac}")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 注意事项与限制
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| ✅ **跨平台兼容性** | `psutil` 支持多平台,但 `AF_PACKET` 仅适用于 Linux,因此 `getAllMacAddress()` 在 Windows/macOS 上可能无法正确获取 MAC 地址。 |
|
||||
| ⚠️ **MAC 地址获取方式** | 推荐使用 `psutil.net_if_addrs()` 中 `family == -1` 或其他平台无关字段提取 MAC 地址,而非依赖 `AF_PACKET`。更健壮的方法是检查 `address` 字段并识别是否为 MAC 格式。 |
|
||||
| ⚠️ **locale 编码未使用** | `locale.getdefaultlocale()[1]` 在函数中被赋值但未使用,建议移除以提高可读性。 |
|
||||
| ✅ **性能表现** | 使用生成器设计,内存友好,适合大规模接口场景。 |
|
||||
| ✅ **活跃判断标准** | 通过 `bytes_sent > 0 and bytes_recv > 0` 判断接口活跃,合理排除未使用的虚拟或关闭接口。 |
|
||||
|
||||
---
|
||||
|
||||
## 改进建议
|
||||
|
||||
1. **修复打印语句中的标签错误**:
|
||||
```python
|
||||
print("ip=", i) # 替代 "mac="
|
||||
```
|
||||
|
||||
2. **优化 MAC 地址获取逻辑(增强跨平台支持)**:
|
||||
```python
|
||||
if i.family == socket.AF_LINK or i.family == -1: # BSD/macOS 使用 AF_LINK
|
||||
yield n, i.address
|
||||
```
|
||||
或者统一使用 `psutil` 提供的更高层接口。
|
||||
|
||||
3. **移除无用的 locale 调用**:
|
||||
```python
|
||||
# 删除这一行
|
||||
# coding = locale.getdefaultlocale()[1]
|
||||
```
|
||||
|
||||
4. **增加异常处理和日志支持**(生产环境建议)
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
本脚本是一个轻量级的网络接口信息探测工具,适用于需要自动发现本地活跃网络接口及其 IP/MAC 地址的场景(如设备识别、网络诊断等)。虽然存在少量命名和平台兼容性问题,但整体结构清晰、逻辑合理,易于维护和扩展。
|
||||
|
||||
---
|
||||
|
||||
📌 **版本要求**:Python 3.6+
|
||||
📦 **推荐用途**:自动化运维、网络配置检测、主机信息采集
|
||||
106
aidocs/myImport.md
Normal file
106
aidocs/myImport.md
Normal file
@ -0,0 +1,106 @@
|
||||
# `myImport` 函数技术文档
|
||||
|
||||
## 概述
|
||||
|
||||
`myImport` 是一个用于动态导入 Python 模块的辅助函数。它允许通过字符串形式的模块路径(如 `"os.path"` 或 `"json.decoder.JSONDecoder"`)导入嵌套模块或子模块,而无需使用多个 `import` 语句。
|
||||
|
||||
---
|
||||
|
||||
## 函数定义
|
||||
|
||||
```python
|
||||
def myImport(modulename):
|
||||
modules = modulename.split('.')
|
||||
if len(modules) > 1:
|
||||
a = __import__(modules[0])
|
||||
return eval('a.' + '.'.join(modules[1:]))
|
||||
return __import__(modulename)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 参数说明
|
||||
|
||||
| 参数名 | 类型 | 说明 |
|
||||
|--------------|--------|------|
|
||||
| `modulename` | `str` | 要导入的模块名称,支持点号分隔的完整路径,例如 `"package.submodule"`。 |
|
||||
|
||||
---
|
||||
|
||||
## 返回值
|
||||
|
||||
- **返回类型**: `module` 对象或指定的子模块/类/函数。
|
||||
- 如果 `modulename` 是单个模块(如 `"math"`),则返回该模块对象。
|
||||
- 如果 `modulename` 包含子模块或属性(如 `"os.path"` 或 `"json.JSONDecoder"`),则返回对应的嵌套对象。
|
||||
|
||||
---
|
||||
|
||||
## 工作原理
|
||||
|
||||
1. 将输入的 `modulename` 字符串按 `'.'` 分割成模块路径列表。
|
||||
2. 若路径长度大于 1:
|
||||
- 使用 `__import__` 导入最顶层的模块(例如 `"os"`)。
|
||||
- 使用 `eval` 动态访问其子模块或属性(例如 `a.path`)。
|
||||
3. 若路径长度为 1,则直接使用 `__import__` 导入并返回模块。
|
||||
|
||||
> ⚠️ 注意:该函数内部使用了 `eval()`,存在潜在的安全风险,应避免在不可信输入上使用。
|
||||
|
||||
---
|
||||
|
||||
## 示例用法
|
||||
|
||||
### 示例 1:导入标准库模块
|
||||
|
||||
```python
|
||||
os = myImport("os")
|
||||
print(os.getcwd())
|
||||
```
|
||||
|
||||
### 示例 2:导入子模块
|
||||
|
||||
```python
|
||||
path = myImport("os.path")
|
||||
print(path.join("a", "b"))
|
||||
```
|
||||
|
||||
### 示例 3:导入类或函数
|
||||
|
||||
```python
|
||||
JSONDecoder = myImport("json.decoder.JSONDecoder")
|
||||
decoder = JSONDecoder()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
- ✅ 支持多层模块路径导入。
|
||||
- ⚠️ 使用 `eval()` 存在安全风险,不建议用于处理用户输入。
|
||||
- ⚠️ 不会自动处理相对导入或包内导入的上下文问题。
|
||||
- ✅ 可作为简单的动态导入工具,在可控环境下使用较为方便。
|
||||
|
||||
---
|
||||
|
||||
## 替代方案(推荐)
|
||||
|
||||
Python 标准库提供了更安全、更强大的替代方式:
|
||||
|
||||
```python
|
||||
from importlib import import_module
|
||||
|
||||
# 推荐使用 import_module 替代 myImport
|
||||
module = import_module("os.path") # 仅导入到 os.path 模块级别
|
||||
```
|
||||
|
||||
对于导入类或函数,可结合 `getattr` 使用:
|
||||
|
||||
```python
|
||||
module = import_module("json.decoder")
|
||||
cls = getattr(module, "JSONDecoder")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
`myImport` 提供了一种简洁的动态导入方式,适用于快速原型开发或简单脚本中。但在生产环境中,建议使用 `importlib.import_module` 配合 `getattr` 实现更安全、清晰的模块导入逻辑。
|
||||
345
aidocs/myTE.md
Normal file
345
aidocs/myTE.md
Normal file
@ -0,0 +1,345 @@
|
||||
# 模板引擎技术文档
|
||||
|
||||
## 简介
|
||||
|
||||
`MyTemplateEngine` 是一个基于 [Jinja2](https://jinja.palletsprojects.com/) 的轻量级模板渲染引擎,支持文件和字符串模板的渲染、变量注入、JSON 数据加载以及自定义全局函数。该模块封装了常用的文件路径处理、类型转换、数据访问等功能,适用于配置生成、报告输出、代码模板等场景。
|
||||
|
||||
---
|
||||
|
||||
## 功能特性
|
||||
|
||||
- ✅ 支持从字符串或文件加载模板
|
||||
- ✅ 支持多路径模板搜索(通过 `pathList`)
|
||||
- ✅ 内置常用工具函数(如 `json`, `hasattr`, 类型转换等)
|
||||
- ✅ 提供便捷的 JSON 文件与模板结合渲染功能
|
||||
- ✅ 自动处理跨平台路径分隔符
|
||||
- ✅ 可扩展的全局变量/函数注入机制
|
||||
- ✅ 支持 UTF-8 编码输入输出(可配置)
|
||||
|
||||
---
|
||||
|
||||
## 安装依赖
|
||||
|
||||
```bash
|
||||
pip install jinja2 ujson apppublic
|
||||
```
|
||||
|
||||
> 注:`appPublic.argsConvert` 和 `appPublic.dictObject` 来自第三方包 `apppublic`,需确保已安装。
|
||||
|
||||
---
|
||||
|
||||
## 模块导入说明
|
||||
|
||||
```python
|
||||
import os
|
||||
import sys
|
||||
try:
|
||||
import ujson as json # 更快的 JSON 解析器
|
||||
except ImportError:
|
||||
import json # 标准库回退
|
||||
from jinja2 import Environment, FileSystemLoader, BaseLoader, meta
|
||||
import codecs
|
||||
from appPublic.argsConvert import ArgsConvert
|
||||
from appPublic.dictObject import DictObject
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 公共函数
|
||||
|
||||
### `isNone(obj) -> bool`
|
||||
|
||||
判断对象是否为 `None`。
|
||||
|
||||
#### 参数:
|
||||
- `obj`: 任意对象
|
||||
|
||||
#### 返回值:
|
||||
- `True` 如果 `obj` 是 `None`,否则 `False`
|
||||
|
||||
---
|
||||
|
||||
### `string_template_render(tmp_string: str, data: dict) -> str`
|
||||
|
||||
使用字典数据渲染字符串模板。
|
||||
|
||||
#### 参数:
|
||||
- `tmp_string`: Jinja2 风格的模板字符串
|
||||
- `data`: 渲染所需的数据字典
|
||||
|
||||
#### 示例:
|
||||
|
||||
```python
|
||||
result = string_template_render("Hello {{ name }}!", {"name": "Alice"})
|
||||
# 输出: Hello Alice!
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 类:`MyTemplateEngine`
|
||||
|
||||
主模板引擎类,封装了完整的模板解析与渲染能力。
|
||||
|
||||
### 构造函数:`__init__(pathList, file_coding='utf-8', out_coding='utf-8', env={})`
|
||||
|
||||
初始化模板引擎实例。
|
||||
|
||||
#### 参数:
|
||||
| 参数名 | 类型 | 说明 |
|
||||
|---------------|------------------|------|
|
||||
| `pathList` | `str` 或 `list` | 模板文件搜索路径列表(支持单个路径或路径列表) |
|
||||
| `file_coding` | `str` | 模板文件读取编码,默认 `'utf-8'` |
|
||||
| `out_coding` | `str` | 输出编码(目前主要用于内部一致性,实际返回为字符串) |
|
||||
| `env` | `dict` | 用户自定义注入到模板中的全局变量或函数 |
|
||||
|
||||
#### 初始化行为:
|
||||
1. 创建 `FileSystemLoader` 加载器,用于从指定路径加载模板。
|
||||
2. 初始化 `Environment` 并注册内置函数和工具。
|
||||
3. 向 Jinja2 全局命名空间 (`globals`) 注入以下内容:
|
||||
|
||||
| 名称 | 类型 | 用途 |
|
||||
|------|------|------|
|
||||
| `json` | module | 提供 `json.dumps/json.loads` |
|
||||
| `hasattr` | built-in | 判断对象是否有某属性 |
|
||||
| `int`, `float`, `str`, `type`, `len` | built-in | 基础类型与操作 |
|
||||
| `isNone` | function | 判断是否为 `None` |
|
||||
| `render` | method | 调用当前引擎渲染模板文件 |
|
||||
| `renders` | method | 渲染模板字符串 |
|
||||
| `ArgsConvert` | class | 参数转换工具类 |
|
||||
| `renderJsonFile` | method | 快捷方法:用 JSON 文件数据渲染模板 |
|
||||
| `ospath(x)` | lambda | 统一路径分隔符(适应 Windows/Linux) |
|
||||
| `basename(x)` | lambda | 获取路径中的文件名部分 |
|
||||
| `basenameWithoutExt(x)` | lambda | 获取无扩展名的文件名 |
|
||||
| `extname(x)` | lambda | 获取文件扩展名 |
|
||||
|
||||
#### 示例:
|
||||
|
||||
```python
|
||||
te = MyTemplateEngine(['/templates', '/shared'], file_coding='utf-8')
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 方法:`get_template_variables(tmpl: str) -> list[str]`
|
||||
|
||||
分析模板字符串中所有未声明但使用的变量名。
|
||||
|
||||
#### 参数:
|
||||
- `tmpl`: Jinja2 模板字符串
|
||||
|
||||
#### 返回值:
|
||||
- 所有在模板中引用但未定义的变量名列表(可用于调试或预检)
|
||||
|
||||
#### 示例:
|
||||
|
||||
```python
|
||||
vars = te.get_template_variables("Hello {{ user.name }}! You have {{ count }} messages.")
|
||||
# 返回: ['user', 'count']
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 方法:`set(k: str, v: Any)`
|
||||
|
||||
向模板上下文注入全局变量或函数。
|
||||
|
||||
#### 参数:
|
||||
- `k`: 变量名(字符串)
|
||||
- `v`: 值或可调用对象
|
||||
|
||||
> ⚠️ 注意:此操作会影响后续所有模板渲染。
|
||||
|
||||
#### 示例:
|
||||
|
||||
```python
|
||||
te.set('site_name', 'MySite')
|
||||
te.set('now', lambda: datetime.now())
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 方法:`_render(template: Template, data: dict) -> str`
|
||||
|
||||
内部方法:执行模板渲染。
|
||||
|
||||
#### 参数:
|
||||
- `template`: 已加载的 Jinja2 `Template` 对象
|
||||
- `data`: 数据字典
|
||||
|
||||
#### 返回值:
|
||||
- 渲染后的字符串
|
||||
|
||||
---
|
||||
|
||||
### 方法:`renders(tmplstring: str, data: dict) -> str`
|
||||
|
||||
渲染模板字符串。
|
||||
|
||||
#### 参数:
|
||||
- `tmplstring`: Jinja2 模板字符串
|
||||
- `data`: 渲染数据
|
||||
|
||||
#### 特性:
|
||||
- 将传入的 `data` 包装成 `global()` 函数注入模板,允许在模板中通过 `global()` 访问原始数据结构。
|
||||
|
||||
#### 示例模板:
|
||||
|
||||
```jinja2
|
||||
User: {{ global().user.name }}
|
||||
Total: {{ len(global().items) }}
|
||||
```
|
||||
|
||||
#### 使用示例:
|
||||
|
||||
```python
|
||||
output = te.renders("Hello {{ name }}!", {"name": "Bob"})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 方法:`render(tmplfile: str, data: dict) -> str`
|
||||
|
||||
渲染指定路径的模板文件。
|
||||
|
||||
#### 参数:
|
||||
- `tmplfile`: 模板文件相对或绝对路径(将在 `pathList` 中查找)
|
||||
- `data`: 渲染数据字典
|
||||
|
||||
#### 示例:
|
||||
|
||||
```python
|
||||
with open('data.json') as f:
|
||||
data = json.load(f)
|
||||
result = te.render('email_template.html', data)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 方法:`renderJsonFile(tmplfile: str, jsonfile: str) -> str`
|
||||
|
||||
快捷方法:使用 JSON 文件中的数据渲染模板文件。
|
||||
|
||||
#### 参数:
|
||||
- `tmplfile`: 模板文件路径
|
||||
- `jsonfile`: JSON 数据文件路径
|
||||
|
||||
#### 行为:
|
||||
1. 读取并解析 `jsonfile`
|
||||
2. 使用其内容作为上下文调用 `render(tmplfile, data)`
|
||||
|
||||
#### 示例:
|
||||
|
||||
```python
|
||||
output = te.renderJsonFile('report.html', 'data.json')
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 辅助函数:`tmpTml(f: str, ns: dict) -> str`
|
||||
|
||||
将模板文件渲染后保存至 `/tmp/` 目录,并返回生成文件的路径。
|
||||
|
||||
#### 参数:
|
||||
- `f`: 模板文件路径
|
||||
- `ns`: 数据上下文字典
|
||||
|
||||
#### 返回值:
|
||||
- 生成的临时文件完整路径(例如 `/tmp/report.html`)
|
||||
|
||||
#### 流程:
|
||||
1. 创建 `MyTemplateEngine` 实例,搜索路径为当前目录 `.`
|
||||
2. 读取模板内容
|
||||
3. 渲染模板
|
||||
4. 写入 `/tmp/<原文件名>`
|
||||
5. 返回文件路径
|
||||
|
||||
#### 示例:
|
||||
|
||||
```python
|
||||
temp_path = tmpTml('template.txt', {'name': 'Test'})
|
||||
print(f"Generated at: {temp_path}")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 命令行使用
|
||||
|
||||
当直接运行此脚本时,支持命令行调用:
|
||||
|
||||
```bash
|
||||
python template_engine.py <template_file> <json_data_file>
|
||||
```
|
||||
|
||||
#### 示例:
|
||||
|
||||
```bash
|
||||
python template_engine.py hello.tmpl.json data.json
|
||||
```
|
||||
|
||||
程序会:
|
||||
1. 读取模板文件
|
||||
2. 加载 JSON 数据
|
||||
3. 渲染并输出结果到标准输出
|
||||
|
||||
> 若参数不足,打印用法提示并退出。
|
||||
|
||||
---
|
||||
|
||||
## 模板语法示例(Jinja2)
|
||||
|
||||
```jinja2
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head><title>{{ title }}</title></head>
|
||||
<body>
|
||||
<h1>Welcome, {{ user.name }}!</h1>
|
||||
<p>You have {{ len(messages) }} messages.</p>
|
||||
|
||||
{% if not isNone(subtitle) %}
|
||||
<h2>{{ subtitle }}</h2>
|
||||
{% endif %}
|
||||
|
||||
<p>File: {{ basenameWithoutExt(global().input_file) }} (Ext: {{ extname(global().input_file) }})</p>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 编码说明
|
||||
|
||||
- 所有文件读写均使用 `codecs.open(..., encoding='utf-8')`
|
||||
- 默认编码可由构造函数设置
|
||||
- 推荐统一使用 UTF-8 编码避免乱码问题
|
||||
|
||||
---
|
||||
|
||||
## 错误处理建议
|
||||
|
||||
| 异常类型 | 建议措施 |
|
||||
|--------|---------|
|
||||
| `TemplateNotFound` | 检查 `pathList` 是否包含目标模板路径 |
|
||||
| `UnicodeDecodeError` | 确保文件真实编码与 `file_coding` 一致 |
|
||||
| `JSONDecodeError` | 验证 JSON 文件格式正确 |
|
||||
| `UndefinedError` | 使用 `get_template_variables()` 检查缺失变量 |
|
||||
|
||||
---
|
||||
|
||||
## 扩展建议
|
||||
|
||||
可通过 `env` 参数或 `set()` 方法添加更多工具函数,例如:
|
||||
|
||||
```python
|
||||
te.set('upper', str.upper)
|
||||
te.set('today', lambda: datetime.today().strftime('%Y-%m-%d'))
|
||||
```
|
||||
|
||||
也可集成 `DictObject` 实现更灵活的数据访问。
|
||||
|
||||
---
|
||||
|
||||
## 版权与许可
|
||||
|
||||
© 2025 Your Company. 开源项目,请保留原作者信息。
|
||||
|
||||
使用遵循 MIT License(除非另有声明)。
|
||||
129
aidocs/myjson.md
Normal file
129
aidocs/myjson.md
Normal file
@ -0,0 +1,129 @@
|
||||
# JSON 文件操作工具模块文档
|
||||
|
||||
本模块提供了一组用于加载和保存 JSON 数据的便捷函数,支持自定义编码格式,并优先使用高性能的 `ujson` 库(若可用),否则回退到标准库中的 `json` 模块。
|
||||
|
||||
---
|
||||
|
||||
## 依赖说明
|
||||
|
||||
- **`ujson`**(可选):超快的第三方 JSON 解析库。如果系统中已安装 `ujson`,则会优先导入以提升性能。
|
||||
- **`json`**(内置):Python 标准库中的 JSON 模块,作为 `ujson` 不可用时的备用方案。
|
||||
- **`codecs`**(内置):用于处理带指定编码的文件读写操作。
|
||||
|
||||
> ⚠️ 注意:推荐在生产环境中安装 `ujson` 以获得更好的性能:
|
||||
>
|
||||
> ```bash
|
||||
> pip install ujson
|
||||
> ```
|
||||
|
||||
---
|
||||
|
||||
## 函数接口
|
||||
|
||||
### `loadf(fn, coding='utf8')`
|
||||
|
||||
从指定文件路径加载 JSON 数据。
|
||||
|
||||
#### 参数:
|
||||
| 参数名 | 类型 | 默认值 | 说明 |
|
||||
|-------|------|--------|------|
|
||||
| `fn` | `str` | —— | JSON 文件路径 |
|
||||
| `coding` | `str` | `'utf8'` | 文件编码格式,默认为 UTF-8 |
|
||||
|
||||
#### 返回值:
|
||||
- 解码后的 Python 对象(如字典、列表等)
|
||||
|
||||
#### 示例:
|
||||
```python
|
||||
data = loadf('config.json', 'utf8')
|
||||
print(data['key'])
|
||||
```
|
||||
|
||||
#### 异常处理:
|
||||
- 若文件不存在或内容不是合法 JSON,将抛出相应异常(如 `FileNotFoundError`, `JSONDecodeError`)。
|
||||
|
||||
---
|
||||
|
||||
### `dumpf(obj, fn, coding='utf8')`
|
||||
|
||||
将 Python 对象序列化为 JSON 并写入指定文件。
|
||||
|
||||
#### 参数:
|
||||
| 参数名 | 类型 | 默认值 | 说明 |
|
||||
|-------|------|--------|------|
|
||||
| `obj` | `any` | —— | 可被 JSON 序列化的 Python 对象 |
|
||||
| `fn` | `str` | —— | 目标文件路径 |
|
||||
| `coding` | `str` | `'utf8'` | 输出文件编码格式,默认为 UTF-8 |
|
||||
|
||||
#### 示例:
|
||||
```python
|
||||
data = {'name': 'Alice', 'age': 30}
|
||||
dumpf(data, 'output.json', 'utf8')
|
||||
```
|
||||
|
||||
#### 注意事项:
|
||||
- 若目标文件已存在,将被覆盖。
|
||||
- 确保传入的对象是 JSON 可序列化的(例如不包含 `set`、`function` 等类型)。
|
||||
|
||||
---
|
||||
|
||||
### 兼容性快捷函数
|
||||
|
||||
为了与标准 `json` 模块保持接口一致,本模块导出了以下四个常用方法:
|
||||
|
||||
| 函数名 | 功能 | 等价于 |
|
||||
|--------|------|--------|
|
||||
| `load` | 从文件对象加载 JSON | `json.load(file_obj)` |
|
||||
| `dump` | 将对象写入文件对象 | `json.dump(obj, file_obj)` |
|
||||
| `loads` | 从字符串解析 JSON | `json.loads(json_str)` |
|
||||
| `dumps` | 将对象转为 JSON 字符串 | `json.dumps(obj)` |
|
||||
|
||||
> ✅ 提示:这些函数直接代理到底层使用的 JSON 库(`ujson` 或 `json`),无需额外配置。
|
||||
|
||||
#### 示例:
|
||||
```python
|
||||
json_str = '{"name": "Bob"}'
|
||||
data = loads(json_str)
|
||||
|
||||
with open('data.json', 'w') as f:
|
||||
dump({'x': 1}, f)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 使用建议
|
||||
|
||||
1. **优先使用 `loadf` / `dumpf`**:当需要直接通过文件路径操作 JSON 时,这两个封装函数更简洁安全。
|
||||
2. **手动控制文件流时使用 `load` / `dump`**:适用于需要上下文管理或其他高级文件操作场景。
|
||||
3. **性能敏感场景推荐安装 `ujson`**:可显著提升大文件或高频调用下的序列化/反序列化速度。
|
||||
|
||||
---
|
||||
|
||||
## 完整示例
|
||||
|
||||
```python
|
||||
# 写入数据
|
||||
config = {
|
||||
"host": "localhost",
|
||||
"port": 8080,
|
||||
"debug": True
|
||||
}
|
||||
dumpf(config, "config.json")
|
||||
|
||||
# 读取数据
|
||||
loaded_config = loadf("config.json")
|
||||
print(loaded_config["host"]) # 输出: localhost
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 版本兼容性
|
||||
|
||||
- 支持 Python 3.6+
|
||||
- 跨平台兼容(Windows / Linux / macOS)
|
||||
|
||||
---
|
||||
|
||||
## 许可证
|
||||
|
||||
本代码遵循所在项目的整体许可证(请参考项目根目录 LICENSE 文件)。
|
||||
289
aidocs/mylog.md
Normal file
289
aidocs/mylog.md
Normal file
@ -0,0 +1,289 @@
|
||||
# MyLog 日志系统技术文档
|
||||
|
||||
本文档描述了一个基于 Python 的轻量级日志管理系统 `MyLog` 与 `LogMan`,用于实现灵活的日志记录和分类控制。该系统支持多类别日志输出、动态注册日志处理器,并结合全局数据管理模块 `public_data` 实现单例模式的日志管理。
|
||||
|
||||
---
|
||||
|
||||
## 目录
|
||||
|
||||
- [概述](#概述)
|
||||
- [依赖模块](#依赖模块)
|
||||
- [核心常量](#核心常量)
|
||||
- [类说明](#类说明)
|
||||
- [`MyLog`](#mylog)
|
||||
- [`LogMan`](#logman)
|
||||
- [函数说明](#函数说明)
|
||||
- [`mylog()`](#mylogs-catelogappinfo)
|
||||
- [使用示例](#使用示例)
|
||||
- [注意事项](#注意事项)
|
||||
|
||||
---
|
||||
|
||||
## 概述
|
||||
|
||||
本日志系统提供以下功能:
|
||||
|
||||
- 支持按**日志级别/类别**(如 `SYSError`, `APPInfo`, `DEBUG1` 等)进行过滤。
|
||||
- 可动态添加、删除或修改日志处理器(logger)。
|
||||
- 使用统一入口函数 `mylog()` 记录消息。
|
||||
- 日志自动写入文件 `./log/my.log`。
|
||||
- 基于 `public_data` 全局变量存储日志管理器实例,避免重复初始化。
|
||||
|
||||
---
|
||||
|
||||
## 依赖模块
|
||||
|
||||
| 模块名 | 用途说明 |
|
||||
|----------------|----------|
|
||||
| `os` | 路径拼接、文件操作 |
|
||||
| `datetime` | 获取当前时间用于日志时间戳 |
|
||||
| `PublicData` | 全局数据容器,保存共享对象(如 `ProgramPath`, `mylog` 实例) |
|
||||
| `folderUtils.mkdir` | 自动创建目录(确保日志目录存在) |
|
||||
|
||||
> ⚠️ 注意:`PublicData` 和 `folderUtils` 是项目自定义模块,需保证其可用性。
|
||||
|
||||
---
|
||||
|
||||
## 核心常量
|
||||
|
||||
```python
|
||||
AllCatelogs = [
|
||||
'SYSError',
|
||||
'SYSWarn',
|
||||
'APPError',
|
||||
'APPWarn',
|
||||
'APPInfo',
|
||||
'DEBUG1',
|
||||
'DEBUG2',
|
||||
'DEBUG3',
|
||||
'DEBUG4',
|
||||
'DEBUG5',
|
||||
]
|
||||
```
|
||||
|
||||
- **作用**:预定义所有允许的日志类别(日志等级/标签)。
|
||||
- **说明**:
|
||||
- 分为系统级 (`SYS*`) 和应用级 (`APP*`)。
|
||||
- `DEBUG1` 到 `DEBUG5` 可用于不同级别的调试信息输出。
|
||||
- 所有日志操作必须使用这些类别之一,否则将被忽略。
|
||||
|
||||
---
|
||||
|
||||
## 类说明
|
||||
|
||||
### `MyLog`
|
||||
|
||||
一个简单的日志写入类,负责将日志消息追加到指定路径的文件中。
|
||||
|
||||
#### 构造函数
|
||||
|
||||
```python
|
||||
def __init__(self, path)
|
||||
```
|
||||
|
||||
- **参数**:
|
||||
- `path` (str): 日志根目录路径,默认为 `'.'`(当前目录)
|
||||
|
||||
#### 方法
|
||||
|
||||
##### `setLogPath(path='.')`
|
||||
|
||||
设置日志存储路径,并自动创建 `log` 子目录。
|
||||
|
||||
- **参数**:
|
||||
- `path` (str): 新的日志根目录路径
|
||||
- **行为**:
|
||||
- 将路径保存至 `self.myLogPath`
|
||||
- 创建 `{path}/log/` 目录(若不存在)
|
||||
- **注意**:此方法存在逻辑错误 —— 缩进问题导致 `logp` 和 `mkdir(logp)` 不在方法体内(见[注意事项](#注意事项))
|
||||
|
||||
##### `__call__(msg='')`
|
||||
|
||||
使对象可调用,用于写入一条日志。
|
||||
|
||||
- **参数**:
|
||||
- `msg` (str): 要写入的消息
|
||||
- **行为**:
|
||||
1. 构造日志文件路径:`{self.myLogPath}/log/my.log`
|
||||
2. 以追加模式打开文件
|
||||
3. 写入格式化的时间戳 + 消息
|
||||
4. 关闭文件
|
||||
- **时间格式**:`YYYY-MM-DD HH:MM:SS <message>`
|
||||
|
||||
> 示例输出:
|
||||
> ```
|
||||
> 2025-04-05 10:30:45 User login successful
|
||||
> ```
|
||||
|
||||
---
|
||||
|
||||
### `LogMan`(日志管理器)
|
||||
|
||||
用于集中管理和分发日志消息到多个日志处理器(logger),支持基于类别的条件触发。
|
||||
|
||||
#### 构造函数
|
||||
|
||||
```python
|
||||
def __init__()
|
||||
```
|
||||
|
||||
- 初始化两个属性:
|
||||
- `self.logers`: 字典,存储注册的日志处理器
|
||||
- `self.catelogs`: 当前允许的日志类别列表(初始为 `AllCatelogs`)
|
||||
|
||||
#### 方法
|
||||
|
||||
##### `addCatelog(catelog)`
|
||||
|
||||
添加新的日志类别到允许列表。
|
||||
|
||||
- **参数**:
|
||||
- `catelog` (str): 要添加的类别名称
|
||||
- **行为**:
|
||||
- 若类别不在 `self.catelogs` 中,则追加
|
||||
|
||||
##### `addLoger(name, func, catelog)`
|
||||
|
||||
注册一个新的日志处理器。
|
||||
|
||||
- **参数**:
|
||||
- `name` (str): 处理器唯一标识名
|
||||
- `func` (callable): 可调用对象(如 `MyLog` 实例),用于实际写日志
|
||||
- `catelog` (str 或 list of str): 此处理器监听的日志类别
|
||||
- **行为**:
|
||||
- 若 `catelog` 非列表,则转换为列表
|
||||
- 过滤掉不在 `self.catelogs` 中的非法类别
|
||||
- 构建日志器字典并存入 `self.logers[name]`
|
||||
|
||||
##### `delLoger(name)`
|
||||
|
||||
删除已注册的日志处理器。
|
||||
|
||||
- **参数**:
|
||||
- `name` (str): 要删除的日志处理器名称
|
||||
- **行为**:
|
||||
- 若存在则从 `self.logers` 中移除
|
||||
|
||||
##### `setCatelog(name, catelog)`
|
||||
|
||||
更改某个日志处理器所监听的类别。
|
||||
|
||||
- **参数**:
|
||||
- `name` (str): 已注册的日志处理器名称
|
||||
- `catelog` (str 或 list of str): 新的监听类别
|
||||
- **行为**:
|
||||
- 类别合法性检查与过滤后更新配置
|
||||
|
||||
##### `__call__(msg='', catelog='APPInfo')`
|
||||
|
||||
使对象可调用,作为日志分发中心。
|
||||
|
||||
- **参数**:
|
||||
- `msg` (str): 日志内容
|
||||
- `catelog` (str): 日志所属类别(默认 `'APPInfo'`)
|
||||
- **行为**:
|
||||
- 遍历所有注册的日志处理器
|
||||
- 如果该处理器监听了当前 `catelog`,则调用其 `func(msg)`
|
||||
- **用途**:实现“发布-订阅”式日志分发机制
|
||||
|
||||
---
|
||||
|
||||
## 函数说明
|
||||
|
||||
### `mylog(s, catelog='APPInfo')`
|
||||
|
||||
全局日志接口函数,用户应通过此函数记录日志。
|
||||
|
||||
#### 参数
|
||||
|
||||
- `s` (str): 日志消息
|
||||
- `catelog` (str): 日志类别(必须是 `AllCatelogs` 中的一项)
|
||||
|
||||
#### 返回值
|
||||
|
||||
- 返回 `LogMan.__call__()` 的结果(无显式返回值)
|
||||
|
||||
#### 行为流程
|
||||
|
||||
1. 尝试从 `public_data` 获取已存在的 `logman` 实例
|
||||
2. 若未找到:
|
||||
- 获取程序运行路径 `ProgramPath`(也来自 `public_data`)
|
||||
- 若路径不存在,抛出异常
|
||||
- 创建 `MyLog` 实例,指向该路径
|
||||
- 创建 `LogMan` 实例
|
||||
- 注册名为 `'mylog'` 的日志处理器,绑定 `MyLog` 实例,并监听所有类别
|
||||
- 将 `logman` 实例存回 `public_data`,实现单例
|
||||
3. 调用 `logman(s, catelog)` 分发日志
|
||||
|
||||
> ✅ 优点:首次调用自动初始化,后续调用复用实例,线程不安全但适用于单线程场景。
|
||||
|
||||
---
|
||||
|
||||
## 使用示例
|
||||
|
||||
```python
|
||||
# 记录一条普通应用信息
|
||||
mylog("程序启动完成")
|
||||
|
||||
# 记录警告信息
|
||||
mylog("磁盘空间不足", catelog='APPWarn')
|
||||
|
||||
# 记录系统错误
|
||||
mylog("数据库连接失败", catelog='SYSError')
|
||||
|
||||
# 记录调试信息
|
||||
mylog("变量x的值为: 123", catelog='DEBUG2')
|
||||
```
|
||||
|
||||
日志将写入文件:`./log/my.log`,内容类似:
|
||||
|
||||
```
|
||||
2025-04-05 10:30:45 程序启动完成
|
||||
2025-04-05 10:31:12 磁盘空间不足
|
||||
2025-04-05 10:31:15 数据库连接失败
|
||||
2025-04-05 10:31:16 变量x的值为: 123
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. ❗ **缩进错误**:
|
||||
```python
|
||||
def setLogPath(self, path='.'):
|
||||
self.myLogPath = path
|
||||
logp = os.path.join(path, 'log')
|
||||
mkdir(logp)
|
||||
```
|
||||
上述代码中 `logp=...` 和 `mkdir(...)` 不在方法体内部!这会导致语法错误或运行时错误。
|
||||
✅ 正确写法应为:
|
||||
```python
|
||||
def setLogPath(self, path='.'):
|
||||
self.myLogPath = path
|
||||
logp = os.path.join(path, 'log')
|
||||
mkdir(logp)
|
||||
```
|
||||
|
||||
2. 📁 **目录权限**:
|
||||
- 确保程序对目标路径有写权限,否则 `open()` 将失败。
|
||||
|
||||
3. 🔐 **线程安全**:
|
||||
- 当前实现未考虑多线程并发写日志的问题,可能造成日志混乱。
|
||||
- 建议在高并发环境下增加文件锁或改用标准库 `logging` 模块。
|
||||
|
||||
4. 💾 **性能考量**:
|
||||
- 每次写日志都打开/关闭文件,频繁 I/O 可能影响性能。
|
||||
- 可优化为缓存文件句柄或异步写入。
|
||||
|
||||
5. 🔧 **扩展建议**:
|
||||
- 支持按日期分割日志文件(如 `my_2025-04-05.log`)
|
||||
- 添加日志级别阈值控制(如仅输出 `>= APPWarn` 的日志)
|
||||
- 替换为 Python 标准 `logging` 模块以获得更强大功能
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
本日志系统虽然简单,但具备基本的分类、注册、分发能力,适合小型项目快速集成。通过 `public_data` 实现了全局单例管理,降低了使用复杂度。但在健壮性、性能和可维护性方面仍有提升空间。
|
||||
|
||||
建议在生产环境中替换为 Python 内置的 `logging` 模块,或在此基础上完善异常处理与资源管理机制。
|
||||
395
aidocs/oauth_client.md
Normal file
395
aidocs/oauth_client.md
Normal file
@ -0,0 +1,395 @@
|
||||
以下是为提供的 `OAuthClient` 类编写的 **Markdown 格式技术文档**,涵盖了类的功能、设计思想、使用方式、参数说明及示例等内容。
|
||||
|
||||
---
|
||||
|
||||
# `OAuthClient` 技术文档
|
||||
|
||||
`OAuthClient` 是一个基于 HTTP(S) 的异步客户端工具类,用于调用由第三方服务发布的 RESTful API 接口。它通过预定义的接口描述(`desc`)动态构建请求,并解析响应数据,支持自定义数据转换、错误判断逻辑和返回结构处理。
|
||||
|
||||
该类广泛应用于需要与 OAuth 认证或标准 JSON API 交互的服务中,具有良好的可配置性和扩展性。
|
||||
|
||||
---
|
||||
|
||||
## 📦 模块依赖
|
||||
|
||||
```python
|
||||
import json
|
||||
from appPublic.httpclient import HttpClient, RESPONSE_TEXT, RESPONSE_JSON, RESPONSE_BIN, RESPONSE_FILE, RESPONSE_STREAM, HttpError
|
||||
from appPublic.argsConvert import ArgsConvert
|
||||
from appPublic.dictObject import DictObject
|
||||
```
|
||||
|
||||
- `HttpClient`: 异步 HTTP 客户端,支持多种响应类型。
|
||||
- `ArgsConvert`: 用于模板字符串替换(如 `${key}`)。
|
||||
- `DictObject`: 支持通过属性访问字典键值的对象封装。
|
||||
|
||||
---
|
||||
|
||||
## 🧱 类定义
|
||||
|
||||
```python
|
||||
class OAuthClient:
|
||||
def __init__(self, desc, converters={})
|
||||
```
|
||||
|
||||
### 构造函数参数
|
||||
|
||||
| 参数 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `desc` | `dict` | 接口描述对象,必须包含 `data` 字段,定义了基础数据和各 API 方法的配置。详见下方“接口描述格式”章节。 |
|
||||
| `converters` | `dict[str, callable]` | 可选的数据转换函数映射表,键为转换器名称,值为函数引用。 |
|
||||
|
||||
> ⚠️ 注意:`desc['data']` 必须存在,否则会抛出断言错误。
|
||||
|
||||
---
|
||||
|
||||
## 🔧 接口描述格式 (`desc`)
|
||||
|
||||
`desc` 是一个字典,其结构如下:
|
||||
|
||||
```json
|
||||
{
|
||||
"data": { /* 基础共享数据,可在请求时被引用 */ },
|
||||
"method_name": {
|
||||
"path": "/api/v1/users/${userId}",
|
||||
"method": "GET", // 可选,默认 GET
|
||||
"headers": [ ... ],
|
||||
"params": [ ... ],
|
||||
"data": [ ... ], // 请求体中的数据
|
||||
"resp": [
|
||||
{
|
||||
"name": "result",
|
||||
"resp_keys": ["user", "name"],
|
||||
"converter": "to_upper" // 可选
|
||||
}
|
||||
],
|
||||
"error_if": {
|
||||
"error_keys": ["status"],
|
||||
"op": "==", // 比较操作符:'==' 或 '!='
|
||||
"value": "fail",
|
||||
"code_keys": ["code"], // 错误码路径
|
||||
"msg_keys": ["message"] // 错误信息路径
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 字段说明
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `data` | `dict` | 全局上下文数据,可用于模板替换。 |
|
||||
| `method_name` | `dict` | 每个 API 接口的配置项,方法名作为 key。 |
|
||||
| `path` | `str` | 接口路径,支持 `${}` 占位符变量。 |
|
||||
| `method` | `str` | HTTP 方法(GET/POST/PUT/DELETE 等),默认为 `GET`。 |
|
||||
| `headers` | `list[dict]` | 请求头列表,每个元素形如 `{name: "Header-Key", value: "HeaderValue", converter?: "funcName"}` |
|
||||
| `params` | `list[dict]` | URL 查询参数,格式同上。 |
|
||||
| `data` | `list[dict]` | 请求体数据(JSON 格式),格式同上。 |
|
||||
| `resp` | `list[dict]` | 响应数据提取规则。 |
|
||||
| `name` | `str` | 返回结果中字段的名称。 |
|
||||
| `resp_keys` | `list[str]` | 在响应 JSON 中获取数据的路径(嵌套键)。 |
|
||||
| `converter` | `str` | 转换函数名(需在 `converters` 中注册)。 |
|
||||
| `error_if` | `dict` | 自定义错误判断条件。 |
|
||||
| `error_keys` | `list[str]` | 提取用于比较的响应字段路径。 |
|
||||
| `op` | `str` | 比较操作符:`==` 或 `!=`。 |
|
||||
| `value` | any | 期望值,用于对比。 |
|
||||
| `code_keys` | `list[str]` | 错误码字段路径(可选)。 |
|
||||
| `msg_keys` | `list[str]` | 错误消息字段路径(可选)。 |
|
||||
|
||||
---
|
||||
|
||||
## 🧩 核心方法
|
||||
|
||||
### `__call__(self, host, mapi, params) -> dict`
|
||||
|
||||
发起一次 API 调用。
|
||||
|
||||
#### 参数
|
||||
|
||||
| 参数 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `host` | `str` | 服务主机地址,例如 `"https://api.example.com"` |
|
||||
| `mapi` | `str` | 接口方法名,对应 `desc` 中的键名。 |
|
||||
| `params` | `dict` | 动态传入的参数,用于替换模板和构造请求。 |
|
||||
|
||||
#### 返回值
|
||||
|
||||
成功时:
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"data": { /* 根据 resp 配置提取的数据 */ }
|
||||
}
|
||||
```
|
||||
|
||||
失败时:
|
||||
```json
|
||||
{
|
||||
"status": "error",
|
||||
"code": "400",
|
||||
"message": "Bad Request"
|
||||
}
|
||||
```
|
||||
|
||||
#### 流程说明
|
||||
|
||||
1. 查找 `desc[mapi]` 是否存在;
|
||||
2. 使用 `datalize()` 替换路径中的 `${}` 变量;
|
||||
3. 构造完整 URL;
|
||||
4. 处理 headers、params、data(应用转换器);
|
||||
5. 发起异步 HTTP 请求;
|
||||
6. 解析响应为 `DictObject`;
|
||||
7. 检查是否符合 `error_if` 条件;
|
||||
8. 按照 `resp` 规则提取并转换返回数据。
|
||||
|
||||
> 📌 日志输出:请求详情(URL、method、params 等)将打印到控制台。
|
||||
|
||||
---
|
||||
|
||||
### `setup_req_data(data, ns) -> dict or None`
|
||||
|
||||
将一组 `{name, value, converter}` 结构的数据根据命名空间 `ns` 进行模板替换和转换。
|
||||
|
||||
#### 参数
|
||||
|
||||
- `data`: 数据项列表(如 headers、params)
|
||||
- `ns`: 命名空间(通常是 `params`)
|
||||
|
||||
#### 内部调用
|
||||
|
||||
- `setup_req_kv()`: 处理单个键值对,执行模板替换和转换。
|
||||
|
||||
---
|
||||
|
||||
### `datalize(dic, data={}) -> dict`
|
||||
|
||||
执行模板替换(`${key}` → 实际值)。
|
||||
|
||||
#### 说明
|
||||
|
||||
- 合并 `self.data`(全局数据)与传入的 `data`;
|
||||
- 使用 `ArgsConvert('${', '}')` 执行替换;
|
||||
- 支持嵌套字典和字符串。
|
||||
|
||||
#### 示例
|
||||
|
||||
```python
|
||||
self.data = {'token': 'abc123'}
|
||||
dic = {'url': '/api?tk=${token}'}
|
||||
result = self.datalize(dic) # {'url': '/api?tk=abc123'}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `get_resp_data(resp, keys, converter=None)`
|
||||
|
||||
从响应对象中按 `keys` 路径提取数据,并可选地进行转换。
|
||||
|
||||
#### 参数
|
||||
|
||||
- `resp`: `DictObject` 类型的响应体;
|
||||
- `keys`: 键路径列表,如 `['data', 'user', 'id']`;
|
||||
- `converter`: 转换函数名。
|
||||
|
||||
#### 示例
|
||||
|
||||
```python
|
||||
resp = DictObject(data={"user": {"name": "alice"}})
|
||||
value = get_resp_data(resp, ['data', 'user', 'name']) # "alice"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `setup_return_data(resp) -> dict`
|
||||
|
||||
根据 `api.resp` 配置,构建最终返回的 `data` 对象。
|
||||
|
||||
#### 示例配置
|
||||
|
||||
```json
|
||||
"resp": [
|
||||
{
|
||||
"name": "username",
|
||||
"resp_keys": ["data", "user", "name"],
|
||||
"converter": "to_upper"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
#### 输出
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"data": {
|
||||
"username": "ALICE"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `check_if_error(resp) -> dict or None`
|
||||
|
||||
检查响应是否满足预设的错误条件。
|
||||
|
||||
#### 判断逻辑
|
||||
|
||||
```python
|
||||
v = resp.get_data_by_keys(ei.error_keys)
|
||||
if ei.op == '==' and v == ei.value → 触发错误
|
||||
if ei.op == '!=' and v != ei.value → 触发错误
|
||||
```
|
||||
|
||||
#### 成功返回
|
||||
|
||||
- `None` 表示无错误。
|
||||
|
||||
#### 失败返回
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "error",
|
||||
"code": "...",
|
||||
"message": "..."
|
||||
}
|
||||
```
|
||||
|
||||
> 💡 支持从响应中提取 `code` 和 `message`。
|
||||
|
||||
---
|
||||
|
||||
### `set_data(resp_data, data_desc)`
|
||||
|
||||
将响应中的某些字段保存回 `self.data`,用于后续请求共享状态(如 token 刷新)。
|
||||
|
||||
#### 参数
|
||||
|
||||
- `resp_data`: 原始响应数据(dict)
|
||||
- `data_desc`: 描述如何映射字段
|
||||
|
||||
```python
|
||||
data_desc = [
|
||||
{'field': 'access_token', 'name': 'token'},
|
||||
{'field': 'expires_in', 'name': 'expires'}
|
||||
]
|
||||
```
|
||||
|
||||
执行后:
|
||||
```python
|
||||
self.data['token'] = resp_data['access_token']
|
||||
self.data['expires'] = resp_data['expires_in']
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ 使用示例
|
||||
|
||||
### 1. 定义接口描述
|
||||
|
||||
```python
|
||||
desc = {
|
||||
"data": {
|
||||
"client_id": "your_client_id",
|
||||
"client_secret": "your_secret"
|
||||
},
|
||||
"get_user": {
|
||||
"path": "/users/${user_id}",
|
||||
"method": "GET",
|
||||
"headers": [
|
||||
{"name": "Authorization", "value": "Bearer ${token}"}
|
||||
],
|
||||
"resp": [
|
||||
{
|
||||
"name": "userInfo",
|
||||
"resp_keys": ["data"],
|
||||
"converter": "strip_data"
|
||||
}
|
||||
],
|
||||
"error_if": {
|
||||
"error_keys": ["status"],
|
||||
"op": "==",
|
||||
"value": "error",
|
||||
"code_keys": ["error_code"],
|
||||
"msg_keys": ["error_msg"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 定义转换器
|
||||
|
||||
```python
|
||||
def to_upper(s):
|
||||
return s.upper() if isinstance(s, str) else s
|
||||
|
||||
converters = {
|
||||
"to_upper": to_upper,
|
||||
"strip_data": lambda x: x.get('data') if isinstance(x, dict) else x
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 创建并调用客户端
|
||||
|
||||
```python
|
||||
client = OAuthClient(desc, converters=converters)
|
||||
|
||||
result = await client(
|
||||
host="https://api.example.com",
|
||||
mapi="get_user",
|
||||
params={
|
||||
"user_id": "123",
|
||||
"token": "xyz987"
|
||||
}
|
||||
)
|
||||
|
||||
print(result)
|
||||
# 输出:
|
||||
# {
|
||||
# "status": "ok",
|
||||
# "data": {
|
||||
# "userInfo": { ... }
|
||||
# }
|
||||
# }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ 调试信息
|
||||
|
||||
所有请求细节将通过 `print()` 输出:
|
||||
|
||||
```python
|
||||
print(f'{url=}, {method=}, {myparams=}, {mydata=}, {myheaders=}')
|
||||
```
|
||||
|
||||
建议在生产环境中移除或替换为日志记录。
|
||||
|
||||
---
|
||||
|
||||
## ❗ 异常处理
|
||||
|
||||
- 若指定的 `mapi` 未在 `desc` 中定义,抛出 `Exception`;
|
||||
- HTTP 请求异常捕获为 `HttpError`,返回标准错误结构;
|
||||
- 网络级错误(如连接超时)也会被捕获并返回 `"https error"`。
|
||||
|
||||
---
|
||||
|
||||
## 🧩 扩展建议
|
||||
|
||||
- 添加日志模块替代 `print()`;
|
||||
- 支持更多响应类型(如文件下载);
|
||||
- 增加中间件钩子(before_request / after_response);
|
||||
- 支持分页自动迭代。
|
||||
|
||||
---
|
||||
|
||||
## 📚 总结
|
||||
|
||||
`OAuthClient` 是一个灵活、声明式的 API 客户端封装,适用于微服务间通信、第三方接口集成等场景。通过配置驱动的方式,实现了解耦与复用,适合构建统一的外部服务网关层。
|
||||
|
||||
---
|
||||
|
||||
> 📎 版本:1.0
|
||||
> 📅 最后更新:2025年4月5日
|
||||
> © 2025 Your Company. All rights reserved.
|
||||
254
aidocs/objectAction.md
Normal file
254
aidocs/objectAction.md
Normal file
@ -0,0 +1,254 @@
|
||||
# `ObjectAction` 类技术文档
|
||||
|
||||
## 概述
|
||||
|
||||
`ObjectAction` 是一个基于单例模式设计的通用对象行为管理类,用于注册、管理和执行与特定 `id` 和 `动作(action)` 相关联的函数。它支持精确匹配、通配符匹配(`*`)和默认行为(`#`),适用于事件处理、插件系统或动态行为扩展等场景。
|
||||
|
||||
该类通过 `SingletonDecorator` 装饰器确保在整个应用中仅存在一个实例,从而实现全局共享的行为注册表。
|
||||
|
||||
---
|
||||
|
||||
## 依赖
|
||||
|
||||
- `appPublic.Singleton.SingletonDecorator`:提供单例模式支持。
|
||||
|
||||
```python
|
||||
from appPublic.Singleton import SingletonDecorator
|
||||
```
|
||||
|
||||
> 注意:需确保 `appPublic` 包已正确安装并可导入。
|
||||
|
||||
---
|
||||
|
||||
## 类定义
|
||||
|
||||
```python
|
||||
@SingletonDecorator
|
||||
class ObjectAction(object):
|
||||
pass
|
||||
```
|
||||
|
||||
### 说明
|
||||
- 使用 `@SingletonDecorator` 确保 `ObjectAction` 为单例类。
|
||||
- 所有操作均作用于同一个全局实例。
|
||||
|
||||
---
|
||||
|
||||
## 属性
|
||||
|
||||
| 属性名 | 类型 | 描述 |
|
||||
|--------------|--------|------|
|
||||
| `actionList` | `dict` | 存储所有注册的行为函数,结构为:<br>`{ id: { action: [func1, func2, ...] } }` |
|
||||
|
||||
---
|
||||
|
||||
## 方法
|
||||
|
||||
### `__init__(self)`
|
||||
|
||||
初始化 `actionList` 字典。
|
||||
|
||||
#### 示例:
|
||||
```python
|
||||
def __init__(self):
|
||||
self.actionList = {}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `init(self, id, action)`
|
||||
|
||||
为指定的 `id` 和 `action` 初始化一个空的函数列表(若尚不存在)。
|
||||
|
||||
#### 参数:
|
||||
| 参数 | 类型 | 说明 |
|
||||
|----------|--------|------|
|
||||
| `id` | `str` 或 `any hashable` | 唯一标识符(如对象ID、类型名等) |
|
||||
| `action` | `str` | 动作名称(如 `'create'`, `'update'` 等) |
|
||||
|
||||
#### 行为:
|
||||
- 如果 `id` 不存在,则创建 `{id: {action: []}}`
|
||||
- 如果 `action` 已存在,则不覆盖,保持原函数列表
|
||||
|
||||
#### 示例:
|
||||
```python
|
||||
oa = ObjectAction()
|
||||
oa.init('user', 'save') # 创建 user.save 的函数列表
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `add(self, id, action, func)`
|
||||
|
||||
向指定 `id` 和 `action` 注册一个处理函数。
|
||||
|
||||
#### 参数:
|
||||
| 参数 | 类型 | 说明 |
|
||||
|----------|----------|------|
|
||||
| `id` | `str` 或 `any hashable` | 对象标识符 |
|
||||
| `action` | `str` | 动作名称 |
|
||||
| `func` | `callable` | 接受 `(id, action, data)` 并返回 `data` 的函数 |
|
||||
|
||||
#### 行为:
|
||||
- 自动初始化 `id` 和 `action` 对应的结构(如果不存在)
|
||||
- 将 `func` 添加到对应动作的函数列表末尾(支持多个函数注册)
|
||||
|
||||
#### 示例:
|
||||
```python
|
||||
def my_handler(id, act, data):
|
||||
print(f"Handling {act} for {id}")
|
||||
return data + "_handled"
|
||||
|
||||
oa = ObjectAction()
|
||||
oa.add('doc', 'export', my_handler)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `execute(self, id, action, data, callback=None)`
|
||||
|
||||
执行与 `id` 和 `action` 关联的所有函数,并按优先级顺序处理通配符和默认行为。
|
||||
|
||||
#### 参数:
|
||||
| 参数 | 类型 | 说明 |
|
||||
|------------|------------|------|
|
||||
| `id` | `str` 或 `any hashable` | 要执行操作的对象 ID |
|
||||
| `action` | `str` | 要触发的动作 |
|
||||
| `data` | `any` | 输入数据(通常会被函数链式处理) |
|
||||
| `callback` | `callable` 或 `None` | 可选回调函数,在执行完成后调用,接收最终 `data` |
|
||||
|
||||
#### 返回值:
|
||||
- 处理后的 `data`(经过所有函数依次处理)
|
||||
|
||||
#### 执行逻辑流程:
|
||||
|
||||
1. 若 `action` 为 `'#'` 或 `'*'`,直接返回原始 `data`(避免无限递归或无效调用)
|
||||
2. 查找 `id` 对应的行为集合 `idA`
|
||||
3. 获取以下两类函数列表:
|
||||
- 精确匹配:`action` 对应的函数列表
|
||||
- 通配符匹配:`'*'` 对应的函数列表(适用于所有动作)
|
||||
4. 先执行上述合并后的函数列表
|
||||
5. 若无任何函数匹配,则执行默认行为 `'#'` 中的函数
|
||||
6. 最后调用 `callback(data)`(如有)
|
||||
7. 返回最终 `data`
|
||||
|
||||
#### 函数调用格式:
|
||||
每个注册函数必须符合签名:
|
||||
```python
|
||||
def handler(id, action, data):
|
||||
# 处理逻辑
|
||||
return modified_data
|
||||
```
|
||||
|
||||
#### 示例:
|
||||
```python
|
||||
result = oa.execute('user', 'login', {'status': 'pending'})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 特殊动作说明
|
||||
|
||||
| 动作符号 | 含义 |
|
||||
|---------|------|
|
||||
| `*` | **通配符动作**:绑定到 `*` 的函数会在**每一个动作执行时都被调用**(除 `#` 和 `*` 自身外) |
|
||||
| `#` | **默认动作**:当某个 `id` 没有匹配到具体动作时,执行 `#` 下的函数作为兜底处理 |
|
||||
|
||||
> ⚠️ 注意:`action` 为 `*` 或 `#` 时不会触发任何函数(防止循环调用)
|
||||
|
||||
---
|
||||
|
||||
## 使用示例
|
||||
|
||||
```python
|
||||
# 定义处理函数
|
||||
def f(id, act, data):
|
||||
print(f"f called with {data}")
|
||||
return data + "_f"
|
||||
|
||||
def f1(id, act, data):
|
||||
return data + "_f1"
|
||||
|
||||
def f2(id, act, data):
|
||||
return data + "_f2"
|
||||
|
||||
# 注册函数
|
||||
def add():
|
||||
oa = ObjectAction()
|
||||
oa.add('test', 'b', f) # test.b 触发 f
|
||||
oa.add('test', '*', f1) # 所有 test 的动作都触发 f1
|
||||
oa.add('test', '#', f2) # 默认行为
|
||||
|
||||
# 执行动作
|
||||
def exe():
|
||||
oa = ObjectAction()
|
||||
result = oa.execute('test', 'a', 'data1')
|
||||
print(result) # 输出: data1_f1_f2 (因为 'a' 不匹配 'b',但 '*' 和 '#' 生效)
|
||||
|
||||
if __name__ == '__main__':
|
||||
add()
|
||||
exe()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 设计特点
|
||||
|
||||
| 特性 | 说明 |
|
||||
|--------------|------|
|
||||
| ✅ 单例模式 | 全局唯一实例,便于集中管理行为 |
|
||||
| ✅ 支持通配符 | `*` 实现通用拦截(如日志、权限检查) |
|
||||
| ✅ 支持默认行为 | `#` 提供 fallback 机制 |
|
||||
| ✅ 链式处理 | 多个函数可以串联处理同一份数据 |
|
||||
| ✅ 线程安全? | ❌ 当前未加锁,多线程环境下需外部同步 |
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **函数副作用**:每个处理函数应尽量无副作用,且必须返回 `data`(或修改后版本),否则后续函数可能出错。
|
||||
2. **性能考虑**:频繁调用时建议缓存查找结果,当前每次 `execute` 都进行字典查询。
|
||||
3. **错误处理**:未内置异常捕获,建议在生产环境中包裹 `try-except`。
|
||||
4. **不可变数据风险**:若传入不可变对象(如字符串、元组),需确保函数返回新对象。
|
||||
|
||||
---
|
||||
|
||||
## 测试与调试
|
||||
|
||||
模块包含简单测试代码(`if __name__ == '__main__':`),可用于验证基本功能。
|
||||
|
||||
```bash
|
||||
python object_action.py
|
||||
```
|
||||
|
||||
预期输出(根据注释状态不同而变化):
|
||||
```
|
||||
data1_f2
|
||||
```
|
||||
|
||||
(当前示例中 `*` 被注释,只执行了 `#`)
|
||||
|
||||
---
|
||||
|
||||
## 应用场景
|
||||
|
||||
- 插件式业务逻辑扩展
|
||||
- 事件驱动架构中的处理器注册
|
||||
- 动态权限/审计钩子
|
||||
- 数据预处理与后处理管道
|
||||
|
||||
---
|
||||
|
||||
## 未来优化建议
|
||||
|
||||
- 增加 `remove(id, action, func)` 方法以注销函数
|
||||
- 增加 `clear(id=None)` 清理机制
|
||||
- 支持优先级排序(如按权重执行)
|
||||
- 添加日志或调试开关
|
||||
- 引入线程锁以支持并发环境
|
||||
|
||||
---
|
||||
|
||||
## 版权与许可
|
||||
|
||||
> 本代码由项目团队开发,遵循项目内部开源协议。引用请注明出处。
|
||||
228
aidocs/outip.md
Normal file
228
aidocs/outip.md
Normal file
@ -0,0 +1,228 @@
|
||||
# `IpGetter` 与 `OutIP` 模块技术文档
|
||||
|
||||
---
|
||||
|
||||
## 概述
|
||||
|
||||
本模块提供了一个用于获取当前公网 IP 地址的 Python 工具类,支持从多个公开 IP 查询服务中获取 IP,并具备以下特性:
|
||||
|
||||
- 支持多源 IP 获取
|
||||
- 自动统计请求耗时并按响应速度排序调用
|
||||
- 内置 IP 格式校验
|
||||
- 可扩展自定义解析器
|
||||
- 容错处理(异常捕获、失败降级)
|
||||
|
||||
主要包含两个核心类:
|
||||
- `IpGetter`:单个 IP 获取器,封装对一个 URL 的请求和解析逻辑。
|
||||
- `OutIP`:管理多个 `IpGetter` 实例,智能选择最快可用的服务。
|
||||
|
||||
---
|
||||
|
||||
## 安装依赖
|
||||
|
||||
```bash
|
||||
pip install requests
|
||||
```
|
||||
|
||||
> 说明:该模块依赖 `requests` 库发送 HTTP 请求,需提前安装。
|
||||
|
||||
---
|
||||
|
||||
## 类与方法详解
|
||||
|
||||
### ✅ `class IpGetter(url: str, parser: Callable[[str], str])`
|
||||
|
||||
表示一个从指定 URL 获取公网 IP 的客户端。
|
||||
|
||||
#### 参数
|
||||
| 参数 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `url` | `str` | 提供 IP 地址的公共 API 端点 URL |
|
||||
| `parser` | `Callable[[str], str]` | 一个函数,用于从 HTTP 响应文本中提取原始 IP 字符串 |
|
||||
|
||||
#### 属性
|
||||
| 属性名 | 类型 | 初始值 | 说明 |
|
||||
|--------|------|--------|------|
|
||||
| `cnt` | `int` | `0` | 成功请求次数(用于计算平均耗时) |
|
||||
| `total_time` | `float` | `0` | 所有请求累计耗时(秒) |
|
||||
| `avg_time` | `float` | `0` | 平均每次请求耗时(秒),失败后设为 10000 秒以降低优先级 |
|
||||
|
||||
#### 方法
|
||||
|
||||
##### `get() -> Optional[str]`
|
||||
发起一次 IP 获取请求。
|
||||
|
||||
**流程:**
|
||||
1. 记录开始时间
|
||||
2. 使用 `requests.get()` 请求目标 URL
|
||||
3. 调用 `parser` 函数从响应文本中提取 IP
|
||||
4. 验证 IP 格式是否合法(通过正则)
|
||||
5. 更新性能统计数据
|
||||
6. 返回有效 IP 或 `None`
|
||||
|
||||
**返回值:**
|
||||
- 成功时返回形如 `"123.45.67.89"` 的字符串
|
||||
- 失败时打印错误信息并返回 `None`
|
||||
|
||||
> ⚠️ 若解析或格式校验失败,会将此实例的 `avg_time` 设为 `10000`,使其在后续调度中被排到末尾。
|
||||
|
||||
##### `check_ip(ip: str) -> Optional[str]`
|
||||
使用正则表达式验证输入字符串是否为标准 IPv4 地址。
|
||||
|
||||
**正则模式:** `r'(\d+\.\d+\.\d+\.\d+)'`
|
||||
|
||||
**返回值:**
|
||||
- 匹配成功:返回提取出的 IP 字符串
|
||||
- 匹配失败:打印警告日志并返回 `None`
|
||||
|
||||
##### `get_average_time() -> float`
|
||||
返回当前实例的历史平均请求耗时(单位:秒)。
|
||||
|
||||
##### `__str__() -> str`
|
||||
返回调试字符串,格式如下:
|
||||
```
|
||||
self.url='...', self.avg_time=...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### ✅ `class OutIP()`
|
||||
|
||||
管理一组 `IpGetter` 实例,自动选择响应最快的可用服务来获取公网 IP。
|
||||
|
||||
#### 属性
|
||||
| 属性名 | 类型 | 说明 |
|
||||
|--------|------|------|
|
||||
| `getters` | `List[IpGetter]` | 所有注册的 IP 获取器列表 |
|
||||
|
||||
#### 方法
|
||||
|
||||
##### `__init__()`
|
||||
初始化 `OutIP` 实例,并调用 `set_known_getters()` 注册默认服务源。
|
||||
|
||||
##### `set_known_getters()`
|
||||
预注册一组常用的公网 IP 查询服务,包括:
|
||||
|
||||
| URL | 解析方式 |
|
||||
|------|----------|
|
||||
| `http://ipinfo.io/ip` | 原样返回 |
|
||||
| `https://api.ipify.org` | 原样返回 |
|
||||
| `https://ident.me` | 原样返回 |
|
||||
| `http://myip.dnsomatic.com` | 原样返回 |
|
||||
| `https://checkip.amazonaws.com` | 去除首尾空白字符 |
|
||||
| `http://checkip.dyndns.com` | 正则提取:`Address: X.X.X.X` |
|
||||
|
||||
> 💡 注释掉的服务示例:
|
||||
> ```python
|
||||
> # g = IpGetter('https://ipapi.co/ip/', lambda x: x)
|
||||
> ```
|
||||
|
||||
##### `add_getter(getter: IpGetter)`
|
||||
向管理器添加一个新的 `IpGetter` 实例。
|
||||
|
||||
##### `get() -> Optional[str]`
|
||||
尝试从所有已注册的 `IpGetter` 中获取有效的公网 IP。
|
||||
|
||||
**策略:**
|
||||
1. 将所有 `getters` 按 `get_average_time()` 升序排序(最快优先)
|
||||
2. 依次调用每个 getter 的 `get()` 方法
|
||||
3. 返回第一个成功的 IP
|
||||
4. 若全部失败,返回 `None`
|
||||
|
||||
> 🔄 动态优化:随着运行时间增加,系统会学习哪些服务更快更稳定,优先使用它们。
|
||||
|
||||
---
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 基本用法
|
||||
|
||||
```python
|
||||
from outip import OutIP # 假设保存为 outip.py
|
||||
import time
|
||||
|
||||
oi = OutIP()
|
||||
|
||||
for i in range(10):
|
||||
ip = oi.get()
|
||||
print(f"Public IP: {ip}")
|
||||
time.sleep(1)
|
||||
```
|
||||
|
||||
输出示例:
|
||||
```
|
||||
Public IP: 203.0.113.45
|
||||
Public IP: 203.0.113.45
|
||||
...
|
||||
```
|
||||
|
||||
### 自定义 Getter 添加
|
||||
|
||||
```python
|
||||
def custom_parser(text):
|
||||
match = re.search(r'current_ip["\']?: ?["\']?(\d+\.\d+\.\d+\.\d+)', text)
|
||||
return match.group(1) if match else None
|
||||
|
||||
custom_getter = IpGetter("https://myip.customservice.com", custom_parser)
|
||||
|
||||
oi = OutIP()
|
||||
oi.add_getter(custom_getter)
|
||||
ip = oi.get()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 性能与容错机制
|
||||
|
||||
| 特性 | 描述 |
|
||||
|------|------|
|
||||
| **响应时间记录** | 每次成功请求都更新平均耗时,用于排序 |
|
||||
| **失败惩罚机制** | 失败时设置 `avg_time = 10000`,避免频繁重试不可用服务 |
|
||||
| **异常捕获** | 所有网络/解析异常被捕获,不影响主流程 |
|
||||
| **服务降级** | 当首选服务失败时自动切换至备选服务 |
|
||||
|
||||
---
|
||||
|
||||
## 已知服务列表(内置)
|
||||
|
||||
| 服务名称 | URL | 是否启用 | 特点 |
|
||||
|---------|------|----------|------|
|
||||
| ipinfo.io | http://ipinfo.io/ip | ✅ 是 | 快速简洁 |
|
||||
| ipify | https://api.ipify.org | ✅ 是 | 国际知名 |
|
||||
| ident.me | https://ident.me | ✅ 是 | 支持多种格式 |
|
||||
| dnsomatic | http://myip.dnsomatic.com | ✅ 是 | 老牌动态 DNS |
|
||||
| Amazon AWS | https://checkip.amazonaws.com | ✅ 是 | AWS 官方服务 |
|
||||
| DynDNS | http://checkip.dyndns.com | ✅ 是 | 返回 HTML,需正则提取 |
|
||||
|
||||
> 🔒 所有服务均为 HTTPS(除少数兼容 HTTP),建议生产环境优先使用加密连接。
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **线程安全**:当前实现非线程安全,多线程环境下请加锁或每个线程独立实例。
|
||||
2. **频率限制**:部分服务可能对请求频率有限制,请合理控制调用间隔(如 `time.sleep(1)`)。
|
||||
3. **代理影响**:若程序运行于 NAT 或代理之后,返回的是出口网关的公网 IP。
|
||||
4. **IPv6 支持**:目前仅支持 IPv4;如需 IPv6,需修改正则及解析逻辑。
|
||||
|
||||
---
|
||||
|
||||
## TODO 扩展建议
|
||||
|
||||
- [ ] 支持异步 (`asyncio` + `aiohttp`)
|
||||
- [ ] 加入缓存机制(避免短时间内重复请求)
|
||||
- [ ] 支持配置文件加载自定义服务
|
||||
- [ ] 提供 CLI 接口
|
||||
- [ ] 增加单元测试覆盖率
|
||||
- [ ] 支持 IPv6 格式校验
|
||||
|
||||
---
|
||||
|
||||
## 版权与许可
|
||||
|
||||
© 2025 开源工具模块
|
||||
可自由用于个人与商业项目,保留原作者注释即可。
|
||||
|
||||
---
|
||||
|
||||
📌 **提示**:将此代码保存为 `outip.py` 后可直接导入使用。
|
||||
93
aidocs/pickleUtils.md
Normal file
93
aidocs/pickleUtils.md
Normal file
@ -0,0 +1,93 @@
|
||||
# 数据序列化与反序列化工具函数文档
|
||||
|
||||
本模块提供了两个用于保存和加载 Python 对象的便捷函数,利用 `pickle` 模块实现对象的持久化存储。
|
||||
|
||||
---
|
||||
|
||||
## 1. 函数:`saveData(fn, *args)`
|
||||
|
||||
### 功能
|
||||
将任意数量的 Python 对象序列化并保存到指定文件中。
|
||||
|
||||
### 参数
|
||||
| 参数 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `fn` | `str` | 要保存数据的文件路径(字符串形式) |
|
||||
| `*args` | `任意数量的参数` | 要保存的一个或多个 Python 对象 |
|
||||
|
||||
### 实现细节
|
||||
- 文件以二进制写模式 (`'wb'`) 打开。
|
||||
- 使用列表推导式依次调用 `pickle.dump()` 将每个传入的对象写入文件。
|
||||
- 写入完成后自动关闭文件。
|
||||
|
||||
> ⚠️ 注意:`pickle.dump()` 的返回值是 `None`,因此列表推导式中的变量 `a` 实际上是一个由 `None` 组成的列表,仅用于触发副作用(即执行 dump 操作),并无实际用途。
|
||||
|
||||
### 示例
|
||||
```python
|
||||
data1 = {'name': 'Alice', 'age': 30}
|
||||
data2 = [1, 2, 3, 4]
|
||||
saveData('my_data.pkl', data1, data2)
|
||||
```
|
||||
该代码会将字典 `data1` 和列表 `data2` 序列化后保存到 `my_data.pkl` 文件中。
|
||||
|
||||
---
|
||||
|
||||
## 2. 函数:`loadData(fn, cnt)`
|
||||
|
||||
### 功能
|
||||
从指定文件中反序列化出指定数量的 Python 对象。
|
||||
|
||||
### 参数
|
||||
| 参数 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `fn` | `str` | 包含已保存数据的文件路径 |
|
||||
| `cnt` | `int` | 预期要读取的对象数量 |
|
||||
|
||||
### 返回值
|
||||
- 成功时:返回一个包含 `cnt` 个反序列化对象的列表。
|
||||
- 失败时(如文件不存在、损坏或数据不足):返回一个包含 `cnt` 个 `None` 的列表。
|
||||
|
||||
### 实现细节
|
||||
- 文件以二进制读模式 (`'rb'`) 打开。
|
||||
- 使用列表推导式调用 `pickle.load()` 连续读取 `cnt` 个对象。
|
||||
- 正常情况下返回读取到的数据列表。
|
||||
- 若发生异常(如 `EOFError`, `FileNotFoundError` 等),捕获所有异常并返回初始化为 `None` 的列表。
|
||||
|
||||
> ⚠️ 注意:此函数使用宽泛的 `except:` 语句,可能掩盖错误。建议在生产环境中替换为更具体的异常处理。
|
||||
|
||||
### 示例
|
||||
```python
|
||||
data1, data2 = loadData('my_data.pkl', 2)
|
||||
print(data1) # 输出: {'name': 'Alice', 'age': 30}
|
||||
print(data2) # 输出: [1, 2, 3, 4]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 使用注意事项
|
||||
|
||||
1. **安全性**:`pickle` 模块不安全用于加载不受信任的数据,因为它可以执行任意代码。请仅用于可信来源的数据。
|
||||
2. **文件格式**:保存的文件为二进制格式,不可直接阅读或编辑。
|
||||
3. **版本兼容性**:不同 Python 版本之间的 `pickle` 协议可能存在兼容性问题。
|
||||
4. **异常处理**:`loadData` 中的 `try-except` 块过于宽泛,建议根据需要捕获特定异常(如 `EOFError`, `FileNotFoundError`)以便调试。
|
||||
|
||||
---
|
||||
|
||||
## 完整示例
|
||||
|
||||
```python
|
||||
# 保存数据
|
||||
user_info = {'username': 'Bob', 'level': 5}
|
||||
scores = [88, 92, 76]
|
||||
saveData('game_save.pkl', user_info, scores)
|
||||
|
||||
# 加载数据
|
||||
loaded_user, loaded_scores = loadData('game_save.pkl', 2)
|
||||
print(loaded_user) # {'username': 'Bob', 'level': 5}
|
||||
print(loaded_scores) # [88, 92, 76]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
✅ **适用场景**:快速原型开发、本地配置/状态保存、小型项目中的简单持久化需求。
|
||||
❌ **不推荐用于**:跨平台数据交换、网络传输、高安全性要求环境。
|
||||
267
aidocs/port_forward.md
Normal file
267
aidocs/port_forward.md
Normal file
@ -0,0 +1,267 @@
|
||||
# SSH 端口转发工具技术文档
|
||||
|
||||
## 概述
|
||||
|
||||
本项目实现了一个基于 `Paramiko` 的 SSH 端口转发(Port Forwarding)客户端,支持通过 SSH 隧道将本地端口映射到远程目标主机和端口。该工具可用于安全地访问内网服务或绕过防火墙限制。
|
||||
|
||||
主要功能:
|
||||
- 建立 SSH 连接到跳板机(SSH Server)
|
||||
- 在本地监听指定端口
|
||||
- 将所有连接通过 SSH 隧道转发至目标主机(remote_host:remote_port)
|
||||
- 支持动态配置、后台运行与手动启停控制
|
||||
|
||||
---
|
||||
|
||||
## 依赖库
|
||||
|
||||
| 库名 | 用途 |
|
||||
|------|------|
|
||||
| `paramiko` | 实现 SSH 客户端连接与通道管理 |
|
||||
| `socket` | 网络通信、地址解析 |
|
||||
| `select` | I/O 多路复用,用于双向数据转发 |
|
||||
| `SocketServer` / `socketserver` | 构建 TCP 服务器以监听本地端口(兼容 Python 2/3) |
|
||||
| `appPublic.background.Background` | 后台线程执行任务 |
|
||||
|
||||
> ⚠️ 注意:需确保已安装 `paramiko` 和 `appPublic` 包。
|
||||
|
||||
```bash
|
||||
pip install paramiko
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 核心类说明
|
||||
|
||||
### 1. `ForwardServer` —— 多线程 TCP 服务器
|
||||
|
||||
继承自 `SocketServer.ThreadingTCPServer`,用于在本地监听端口并接受入站连接。
|
||||
|
||||
#### 特性
|
||||
- `daemon_threads = True`:子线程随主线程退出而终止。
|
||||
- `allow_reuse_address = True`:允许重用本地地址(避免端口占用问题)。
|
||||
- `server_ready`:标志服务器是否已启动完成。
|
||||
- `ready_callback`:服务器就绪时调用的回调函数。
|
||||
|
||||
#### 方法
|
||||
|
||||
| 方法 | 说明 |
|
||||
|------|------|
|
||||
| `service_actions()` | 重写父类方法,在首次服务启动后触发 `ready_callback` 回调 |
|
||||
| `shutdown()` | 关闭服务器,并重置 `server_ready` 状态 |
|
||||
|
||||
---
|
||||
|
||||
### 2. `Handler` —— 请求处理器
|
||||
|
||||
继承自 `SocketServer.BaseRequestHandler`,处理每一个来自客户端的连接请求。
|
||||
|
||||
#### 属性(动态绑定)
|
||||
- `ssh_transport`:SSH 传输通道(由外部注入)
|
||||
- `chain_host`:目标主机地址(远程实际服务地址)
|
||||
- `chain_port`:目标主机端口
|
||||
|
||||
#### 工作流程
|
||||
1. 尝试通过 SSH 创建 `direct-tcpip` 类型的隧道通道
|
||||
2. 若失败,打印错误日志并返回
|
||||
3. 成功后进入循环:
|
||||
- 使用 `select.select()` 监听本地 socket 与 SSH channel 的可读事件
|
||||
- 双向转发数据(本地 ↔ SSH 隧道)
|
||||
4. 任一端关闭连接时,清理资源并输出关闭信息
|
||||
|
||||
#### 日志输出示例
|
||||
```
|
||||
Connected! Tunnel open ('127.0.0.1', 50000) -> ('192.168.1.10', 22) -> ('10.0.0.5', 80)
|
||||
Tunnel closed from ('127.0.0.1', 50000)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. `SSHPortForward` —— 主控制器类
|
||||
|
||||
封装完整的 SSH 端口转发逻辑,提供简洁的接口用于启动/停止服务。
|
||||
|
||||
#### 构造函数参数
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
|------|------|------|
|
||||
| `local_port` | int | 本地监听端口(如 8080) |
|
||||
| `remote_host` | str | 目标主机域名或 IP(通过 SSH 跳转访问的目标) |
|
||||
| `remote_port` | int | 目标主机端口 |
|
||||
| `ssh_host` | str | SSH 服务器地址(跳板机) |
|
||||
| `ssh_port` | int | SSH 服务端口(通常为 22) |
|
||||
| `ssh_user` | str | SSH 登录用户名 |
|
||||
| `ssh_password` | str | SSH 登录密码 |
|
||||
|
||||
#### 内部属性
|
||||
|
||||
| 属性 | 类型 | 描述 |
|
||||
|------|------|------|
|
||||
| `self.running` | bool | 当前服务是否正在运行 |
|
||||
| `self._ready` | bool | 服务是否已准备就绪(可用于健康检查) |
|
||||
| `self.ssh` | SSHClient | Paramiko SSH 客户端实例 |
|
||||
| `self.transport` | Transport | SSH 传输层对象 |
|
||||
| `self.forward_server` | ForwardServer | 本地监听服务器实例 |
|
||||
|
||||
#### 公共方法
|
||||
|
||||
##### `run()`
|
||||
启动端口转发服务(异步非阻塞)。若已在运行,则忽略。
|
||||
|
||||
内部使用 `Background` 类在新线程中执行 `_run()`。
|
||||
|
||||
##### `_run()`
|
||||
私有方法,运行于后台线程:
|
||||
|
||||
1. 连接 SSH 服务器(`connect_ssh_server()`)
|
||||
2. 获取 SSH 传输通道
|
||||
3. 动态创建 `MyForwardServer` 子类以设置回调
|
||||
4. 动态创建 `SubHandler` 并注入 `chain_host`, `chain_port`, `ssh_transport`
|
||||
5. 绑定到 `localhost:local_port` 并开始监听(`serve_forever()`)
|
||||
|
||||
##### `stop()`
|
||||
停止当前运行的服务:
|
||||
|
||||
- 调用 `forward_server.shutdown()`
|
||||
- 关闭 server socket
|
||||
- 断开 SSH 连接(transport 和 client)
|
||||
|
||||
##### `service_ready()`
|
||||
回调函数,当本地服务器成功启动后被调用,打印提示信息并将 `_ready` 设为 `True`。
|
||||
|
||||
---
|
||||
|
||||
### 4. 辅助函数
|
||||
|
||||
#### `connect_ssh_server(host, port, user, password)`
|
||||
建立 SSH 连接并返回 `SSHClient` 实例。
|
||||
|
||||
- 自动添加未知主机密钥(`AutoAddPolicy`)
|
||||
- 返回已连接的 SSH 客户端
|
||||
|
||||
#### `verbose(s)`
|
||||
条件性打印调试信息,受全局变量 `g_verbose` 控制。
|
||||
|
||||
默认开启(`g_verbose = True`),可设为 `False` 关闭日志输出。
|
||||
|
||||
---
|
||||
|
||||
## 使用方式
|
||||
|
||||
### 命令行模式(主程序入口)
|
||||
|
||||
当直接运行脚本时,进入交互式控制台。
|
||||
|
||||
#### 启动命令格式
|
||||
|
||||
```bash
|
||||
python ssh_forward.py <本地端口> <目标主机> <目标端口> <SSH主机> <SSH端口> <SSH用户> <SSH密码>
|
||||
```
|
||||
|
||||
#### 示例
|
||||
|
||||
```bash
|
||||
python ssh_forward.py 8888 example.internal 80 gateway.example.com 22 alice mypass123
|
||||
```
|
||||
|
||||
此命令表示:
|
||||
> 所有发往本机 `8888` 端口的流量,将通过 `gateway.example.com` 的 SSH 隧道,转发至 `example.internal:80`
|
||||
|
||||
#### 交互命令
|
||||
|
||||
程序启动后会显示菜单:
|
||||
|
||||
```
|
||||
start) start server,
|
||||
stop) stop server
|
||||
quit) quit
|
||||
```
|
||||
|
||||
输入对应指令即可操作服务状态。
|
||||
|
||||
---
|
||||
|
||||
## 工作原理图解
|
||||
|
||||
```
|
||||
+-------------+ +------------------+ +--------------------+
|
||||
| | | | | |
|
||||
| Client | ----> | Local Listener | ----> | SSH Tunnel | ----> Target Service
|
||||
| (e.g. curl) | | (SSHPortForward) | | (via ssh_host) |
|
||||
| | | | | |
|
||||
+-------------+ +------------------+ +--------------------+
|
||||
↑
|
||||
localhost:8888
|
||||
```
|
||||
|
||||
1. 用户访问 `http://localhost:8888`
|
||||
2. `SSHPortForward` 接收连接
|
||||
3. 通过 SSH 隧道建立到 `remote_host:remote_port` 的 direct-tcpip 通道
|
||||
4. 数据双向透明转发
|
||||
|
||||
---
|
||||
|
||||
## 错误处理与日志
|
||||
|
||||
- 所有异常均被捕获并输出详细信息(通过 `verbose()`)
|
||||
- 常见错误包括:
|
||||
- SSH 认证失败
|
||||
- 目标主机无法解析(DNS)
|
||||
- SSH 服务器拒绝端口转发请求
|
||||
- 网络中断导致连接关闭
|
||||
|
||||
建议生产环境根据需要关闭 `g_verbose` 或重定向日志。
|
||||
|
||||
---
|
||||
|
||||
## 安全注意事项
|
||||
|
||||
1. **密码明文存储**:当前版本在内存中保存明文密码,不适用于高安全场景。建议扩展支持密钥认证(`.pem` 文件)。
|
||||
2. **无加密本地通信**:本地监听仅限 `localhost`,防止外部访问敏感端口。
|
||||
3. **自动信任主机密钥**:使用 `AutoAddPolicy()`,存在中间人攻击风险。生产环境应验证主机指纹。
|
||||
|
||||
---
|
||||
|
||||
## 扩展建议
|
||||
|
||||
| 功能 | 描述 |
|
||||
|------|------|
|
||||
| 支持私钥登录 | 添加 `pkey` 参数,替代密码认证 |
|
||||
| 配置文件支持 | 使用 JSON/YAML 加载配置,避免命令行长参数 |
|
||||
| REST API 控制 | 提供 HTTP 接口进行 start/stop 操作 |
|
||||
| 日志模块替换 | 使用标准 `logging` 模块代替 `print` |
|
||||
| 多隧道支持 | 管理多个并发转发规则 |
|
||||
| 超时与心跳机制 | 防止长时间空闲断连 |
|
||||
|
||||
---
|
||||
|
||||
## 示例代码调用
|
||||
|
||||
```python
|
||||
# 编程方式使用
|
||||
tunnel = SSHPortForward(
|
||||
local_port=9000,
|
||||
remote_host="internal.db",
|
||||
remote_port=3306,
|
||||
ssh_host="jumpbox.company.com",
|
||||
ssh_port=22,
|
||||
ssh_user="dev",
|
||||
ssh_password="secret"
|
||||
)
|
||||
|
||||
tunnel.run() # 启动
|
||||
# ... 使用一段时间 ...
|
||||
tunnel.stop() # 停止
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 版权与许可
|
||||
|
||||
- 作者:未知(请补充)
|
||||
- 依赖开源库:Paramiko (LGPL), Python 标准库
|
||||
- 使用遵循相应许可证要求
|
||||
|
||||
---
|
||||
|
||||
> ✅ 文档版本:v1.0
|
||||
> 📅 最后更新:2025-04-05
|
||||
234
aidocs/process_workers.md
Normal file
234
aidocs/process_workers.md
Normal file
@ -0,0 +1,234 @@
|
||||
# `ProcessWorkers` 类技术文档
|
||||
|
||||
## 概述
|
||||
|
||||
`ProcessWorkers` 是一个基于多进程和线程信号量控制的并发任务调度类,用于限制同时运行的进程数量。它通过 `multiprocessing.Process` 创建独立进程执行任务,并使用 `threading.Semaphore` 控制并发工作进程的数量。
|
||||
|
||||
该类适用于需要控制并发资源占用(如 CPU 密集型任务)且希望避免系统过载的场景。
|
||||
|
||||
---
|
||||
|
||||
## 依赖说明
|
||||
|
||||
```python
|
||||
import time
|
||||
from multiprocessing import Process
|
||||
import threading
|
||||
import random
|
||||
from appPublic.background import Background
|
||||
```
|
||||
|
||||
- **`time`**: 用于模拟任务延迟。
|
||||
- **`multiprocessing.Process`**: 用于创建独立子进程执行任务。
|
||||
- **`threading.Semaphore`**: 限制最大并发工作进程数。
|
||||
- **`random`**: 示例中用于生成随机等待时间。
|
||||
- **`appPublic.background.Background`**: 封装异步启动功能,使 `.do()` 方法非阻塞。
|
||||
|
||||
> ⚠️ 注意:`appPublic` 是自定义模块,需确保其在项目路径中可用。
|
||||
|
||||
---
|
||||
|
||||
## 类定义:`ProcessWorkers`
|
||||
|
||||
```python
|
||||
class ProcessWorkers:
|
||||
def __init__(self, worker_cnt=10):
|
||||
self.semaphore = threading.Semaphore(value=worker_cnt)
|
||||
self.co_worker = 0
|
||||
```
|
||||
|
||||
### 初始化参数
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 说明 |
|
||||
|------------|--------|-------|------|
|
||||
| `worker_cnt` | `int` | `10` | 允许同时运行的最大工作进程数量 |
|
||||
|
||||
### 属性说明
|
||||
|
||||
| 属性名 | 类型 | 说明 |
|
||||
|--------------|--------------------|------|
|
||||
| `semaphore` | `threading.Semaphore` | 控制并发进程数的信号量对象 |
|
||||
| `co_worker` | `int` | 当前正在运行的工作进程计数器(线程不安全读写,仅作粗略参考) |
|
||||
|
||||
> 🔔 提示:`co_worker` 的增减未加锁,在高并发下可能产生竞态条件,建议仅用于调试或监控目的。
|
||||
|
||||
---
|
||||
|
||||
## 方法说明
|
||||
|
||||
### `_do(func, *args, **kwargs)`
|
||||
|
||||
**私有方法**:实际执行任务的逻辑。
|
||||
|
||||
#### 参数
|
||||
|
||||
| 参数名 | 类型 | 说明 |
|
||||
|----------|------------|------|
|
||||
| `func` | `callable` | 要在子进程中执行的函数 |
|
||||
| `*args` | 可变位置参数 | 传递给 `func` 的位置参数 |
|
||||
| `**kwargs` | 可变关键字参数 | 传递给 `func` 的关键字参数 |
|
||||
|
||||
#### 执行流程
|
||||
|
||||
1. 获取信号量(若已达上限则阻塞等待);
|
||||
2. 增加 `co_worker` 计数;
|
||||
3. 创建并启动新进程执行 `func(*args, **kwargs)`;
|
||||
4. 主进程阻塞等待子进程完成(`p.join()`);
|
||||
5. 减少 `co_worker` 计数;
|
||||
6. 释放信号量,允许其他任务进入。
|
||||
|
||||
#### 特性
|
||||
|
||||
- 子进程与主进程内存隔离;
|
||||
- 使用 `join()` 阻塞当前线程直到进程结束;
|
||||
- 确保最多只有 `worker_cnt` 个进程同时运行。
|
||||
|
||||
> ⚠️ 性能提示:由于 `p.join()` 在 `_do` 中同步调用,每个后台线程将阻塞直至任务完成。
|
||||
|
||||
---
|
||||
|
||||
### `do(func, *args, **kwargs)`
|
||||
|
||||
**公有方法**:异步提交任务。
|
||||
|
||||
#### 参数
|
||||
|
||||
同 `_do` 方法。
|
||||
|
||||
#### 功能描述
|
||||
|
||||
使用 `Background` 类将 `_do` 包装为后台线程执行,实现非阻塞提交任务。
|
||||
|
||||
```python
|
||||
b = Background(self._do, func, *args, **kwargs)
|
||||
b.start()
|
||||
```
|
||||
|
||||
> ✅ 效果:调用 `do()` 后立即返回,任务在后台线程中以进程方式运行。
|
||||
|
||||
---
|
||||
|
||||
### `get_workers()`
|
||||
|
||||
获取当前正在运行的任务数量。
|
||||
|
||||
#### 返回值
|
||||
|
||||
- 类型:`int`
|
||||
- 内容:当前 `co_worker` 的值(即已开始但尚未结束的任务数)
|
||||
|
||||
> ⚠️ 注意:此数值为近似值,因无锁保护可能存在竞争问题。
|
||||
|
||||
---
|
||||
|
||||
## 使用示例
|
||||
|
||||
```python
|
||||
if __name__ == '__main__':
|
||||
def k(worker):
|
||||
t = random.randint(1, 4)
|
||||
print('current workers=', worker.get_workers(), 'sleep=', t)
|
||||
time.sleep(t)
|
||||
|
||||
w = ProcessWorkers(worker_cnt=5) # 最多5个并发进程
|
||||
for i in range(100):
|
||||
w.do(k, w)
|
||||
```
|
||||
|
||||
### 输出示例(部分)
|
||||
|
||||
```
|
||||
current workers= 1 sleep= 3
|
||||
current workers= 2 sleep= 1
|
||||
current workers= 3 sleep= 4
|
||||
...
|
||||
```
|
||||
|
||||
### 行为分析
|
||||
|
||||
- 每次 `w.do(k, w)` 提交一个耗时 1~4 秒的任务;
|
||||
- 最多同时运行 5 个进程;
|
||||
- 使用 `Background` 实现异步提交,循环无需等待单个任务完成;
|
||||
- `get_workers()` 显示当前活跃进程数。
|
||||
|
||||
---
|
||||
|
||||
## 设计特点与注意事项
|
||||
|
||||
### ✅ 优点
|
||||
|
||||
| 特性 | 说明 |
|
||||
|------|------|
|
||||
| **资源控制** | 通过信号量严格限制并发进程数,防止系统过载 |
|
||||
| **进程级隔离** | 利用 `multiprocessing` 避免 GIL 影响,适合 CPU 密集型任务 |
|
||||
| **异步接口** | `do()` 方法非阻塞,支持快速批量提交任务 |
|
||||
|
||||
### ❗️局限性与风险
|
||||
|
||||
| 问题 | 说明 |
|
||||
|------|------|
|
||||
| `co_worker` 线程安全缺失 | 多线程修改未加锁,可能导致计数错误 |
|
||||
| `p.join()` 阻塞线程 | `_do` 在后台线程中仍会阻塞,无法进一步提升吞吐 |
|
||||
| 进程开销较大 | 对于轻量任务,频繁创建/销毁进程影响性能 |
|
||||
| 错误处理缺失 | 未捕获异常,崩溃任务可能导致状态不一致 |
|
||||
|
||||
---
|
||||
|
||||
## 改进建议
|
||||
|
||||
1. **添加线程锁保护共享变量**
|
||||
|
||||
```python
|
||||
def __init__(self, worker_cnt=10):
|
||||
self.semaphore = threading.Semaphore(value=worker_cnt)
|
||||
self.co_worker = 0
|
||||
self._lock = threading.Lock()
|
||||
|
||||
def _do(self, func, *args, **kwargs):
|
||||
self.semaphore.acquire()
|
||||
with self._lock:
|
||||
self.co_worker += 1
|
||||
try:
|
||||
p = Process(target=func, args=args, kwargs=kwargs)
|
||||
p.start()
|
||||
p.join()
|
||||
finally:
|
||||
with self._lock:
|
||||
self.co_worker -= 1
|
||||
self.semaphore.release()
|
||||
```
|
||||
|
||||
2. **支持异常捕获与回调**
|
||||
|
||||
```python
|
||||
def _do(self, func, *args, **kwargs):
|
||||
...
|
||||
try:
|
||||
p = Process(...)
|
||||
p.start()
|
||||
p.join()
|
||||
if p.exitcode != 0:
|
||||
print(f"Process exited abnormally with code {p.exitcode}")
|
||||
except Exception as e:
|
||||
print(f"Failed to run process: {e}")
|
||||
finally:
|
||||
...
|
||||
```
|
||||
|
||||
3. **考虑使用进程池替代动态创建**
|
||||
|
||||
对于高频任务,推荐使用 `concurrent.futures.ProcessPoolExecutor` 或 `multiprocessing.Pool` 以减少开销。
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
`ProcessWorkers` 提供了一种简单有效的方式管理并发进程数量,适合对并发度敏感的应用场景。虽然存在一些线程安全和性能上的不足,但结构清晰、易于理解和扩展。
|
||||
|
||||
| 适用场景 | 不适用场景 |
|
||||
|---------|-----------|
|
||||
| CPU 密集型任务调度 | I/O 密集型任务(应使用线程) |
|
||||
| 需要限制系统负载的任务 | 极低延迟要求的实时系统 |
|
||||
| 批量任务异步处理 | 大规模微任务处理(建议用线程池或协程) |
|
||||
|
||||
> 推荐作为学习多进程控制机制的基础模板,在生产环境中结合更成熟的并发框架进行优化。
|
||||
142
aidocs/proxy.md
Normal file
142
aidocs/proxy.md
Normal file
@ -0,0 +1,142 @@
|
||||
# SOCKS 代理设置工具技术文档
|
||||
|
||||
---
|
||||
|
||||
## 概述
|
||||
|
||||
该模块提供了一种简单的方式来为 Python 的全局网络请求设置和取消 SOCKS5 代理。通过替换 `socket.socket` 类,所有基于标准库 `socket` 的网络连接(包括 `requests` 等库)将自动通过指定的 SOCKS5 代理进行通信。
|
||||
|
||||
> ⚠️ 注意:此方法会影响全局 socket 行为,请谨慎使用,建议在完成代理请求后及时恢复原始设置。
|
||||
|
||||
---
|
||||
|
||||
## 依赖项
|
||||
|
||||
- Python 3.x
|
||||
- `PySocks`:用于支持 SOCKS 代理
|
||||
- `requests`:用于发起 HTTP 请求(示例用途)
|
||||
|
||||
### 安装依赖
|
||||
|
||||
```bash
|
||||
pip install PySocks requests
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 函数说明
|
||||
|
||||
### `set_socks_proxy(host, port)`
|
||||
|
||||
启用 SOCKS5 代理,替换全局 `socket.socket` 实现。
|
||||
|
||||
#### 参数
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
|------|------|------|
|
||||
| `host` | `str` | SOCKS5 代理服务器地址(如 `'127.0.0.1'`) |
|
||||
| `port` | `int` | SOCKS5 代理服务端口(如 `1080`) |
|
||||
|
||||
#### 示例
|
||||
|
||||
```python
|
||||
set_socks_proxy('127.0.0.1', 1080)
|
||||
```
|
||||
|
||||
执行后,后续所有通过 `socket` 发起的连接(包括 `requests.get()` 等)都将通过该 SOCKS5 代理。
|
||||
|
||||
---
|
||||
|
||||
### `unset_proxy()`
|
||||
|
||||
恢复原始的 `socket.socket` 实现,关闭代理。
|
||||
|
||||
#### 说明
|
||||
|
||||
调用此函数后,所有网络连接将恢复为直连模式,不再经过代理。
|
||||
|
||||
#### 示例
|
||||
|
||||
```python
|
||||
unset_proxy()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 使用示例
|
||||
|
||||
```python
|
||||
import requests
|
||||
from your_module import set_socks_proxy, unset_proxy
|
||||
|
||||
# 设置 SOCKS5 代理
|
||||
set_socks_proxy('127.0.0.1', 1080)
|
||||
|
||||
try:
|
||||
# 此请求将通过 SOCKS5 代理发送
|
||||
response = requests.get('https://httpbin.org/ip')
|
||||
print(response.json())
|
||||
finally:
|
||||
# 务必恢复原始 socket 设置
|
||||
unset_proxy()
|
||||
```
|
||||
|
||||
输出示例:
|
||||
```json
|
||||
{
|
||||
"origin": "1.2.3.4"
|
||||
}
|
||||
```
|
||||
> 其中 IP 应为代理服务器的出口 IP。
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **线程安全**:由于修改了全局 `socket.socket`,在多线程环境下可能导致不可预期行为。不推荐在高并发或复杂应用中全局使用。
|
||||
2. **异常处理**:建议在 `try...finally` 块中使用,确保 `unset_proxy()` 被调用。
|
||||
3. **仅支持 SOCKS5**:当前实现固定使用 SOCKS5 协议,不支持认证或其他类型代理。
|
||||
4. **影响范围广**:所有基于 `socket` 的库(如 `urllib`, `httplib`, `requests` 等)均会受影响。
|
||||
|
||||
---
|
||||
|
||||
## 扩展建议
|
||||
|
||||
- 支持更多代理类型(如 SOCKS4、HTTP)
|
||||
- 添加用户名/密码认证支持
|
||||
- 封装为上下文管理器以实现自动清理
|
||||
|
||||
```python
|
||||
class SocksProxy:
|
||||
def __init__(self, host, port):
|
||||
self.host = host
|
||||
self.port = port
|
||||
|
||||
def __enter__(self):
|
||||
set_socks_proxy(self.host, self.port)
|
||||
|
||||
def __exit__(self, *args):
|
||||
unset_proxy()
|
||||
|
||||
# 使用方式
|
||||
with SocksProxy('127.0.0.1', 1080):
|
||||
requests.get('https://example.com')
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 版本兼容性
|
||||
|
||||
- Python: 3.6+
|
||||
- PySocks: >=1.6.8
|
||||
|
||||
---
|
||||
|
||||
## 参考资料
|
||||
|
||||
- [PySocks GitHub](https://github.com/Anorov/PySocks)
|
||||
- [requests - Python HTTP Library](https://docs.python-requests.org/)
|
||||
|
||||
---
|
||||
|
||||
📝 **维护建议**:在生产环境中,建议使用更精细的代理控制方式(如为 `requests.Session` 配置代理),避免修改全局 socket。
|
||||
246
aidocs/psm.md
Normal file
246
aidocs/psm.md
Normal file
@ -0,0 +1,246 @@
|
||||
# `PSharedMemory` 类技术文档
|
||||
|
||||
## 概述
|
||||
|
||||
`PSharedMemory` 是一个基于 Python `multiprocessing.shared_memory.SharedMemory` 封装的类,用于在多个进程之间安全地共享和传递结构化数据(如字典、列表等)。它通过序列化为 JSON 并写入共享内存的方式实现跨进程通信,并提供线程/进程安全的读写操作。
|
||||
|
||||
该类支持创建者模式(创建并初始化共享内存)与连接者模式(仅连接已有共享内存),适用于主从进程或生产者-消费者场景。
|
||||
|
||||
---
|
||||
|
||||
## 依赖
|
||||
|
||||
```python
|
||||
import json
|
||||
from time import sleep
|
||||
from multiprocessing import Manager
|
||||
from multiprocessing.shared_memory import SharedMemory
|
||||
from multiprocessing.resource_tracker import unregister
|
||||
```
|
||||
|
||||
> ⚠️ 注意:需要 Python 3.8+ 支持 `shared_memory` 模块。
|
||||
|
||||
---
|
||||
|
||||
## 类定义
|
||||
|
||||
### `class PSharedMemory(name: str, datalen: int, data: Any = None)`
|
||||
|
||||
#### 参数说明:
|
||||
|
||||
| 参数 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `name` | `str` | 共享内存对象的唯一名称(系统级标识符) |
|
||||
| `datalen` | `int` | 预分配的共享内存大小(字节),必须足够容纳序列化后的数据 |
|
||||
| `data` | `Any` (可选) | 初始化数据。若提供,则当前进程为“创建者”,负责创建并写入共享内存;否则作为“连接者”读取已存在的共享内存 |
|
||||
|
||||
> 💡 提示:`data` 通常为可被 `json.dumps()` 序列化的对象(如 `dict`, `list`, `str`, `int` 等)
|
||||
|
||||
---
|
||||
|
||||
## 成员属性
|
||||
|
||||
| 属性名 | 类型 | 描述 |
|
||||
|--------|------|------|
|
||||
| `sm` | `SharedMemory` | 实际的共享内存对象实例 |
|
||||
| `name` | `str` | 共享内存名称 |
|
||||
| `datalen` | `int` | 分配的共享内存容量(字节) |
|
||||
| `lock` | `multiprocessing.Lock` | 多进程访问同步锁,确保写操作原子性 |
|
||||
| `creator` | `bool` | 标志当前进程是否为共享内存的创建者 |
|
||||
| `tailstring` | `bytes` (类变量) | 数据结束标记 (`b'#:@#'`),用于分隔有效数据与垃圾内容 |
|
||||
|
||||
---
|
||||
|
||||
## 方法说明
|
||||
|
||||
### `__init__(self, name, datalen, data=None)`
|
||||
|
||||
构造函数,初始化共享内存对象。
|
||||
|
||||
#### 行为逻辑:
|
||||
- 若提供了 `data`:
|
||||
- 创建新的共享内存区域。
|
||||
- 使用 `Manager().Lock()` 创建互斥锁。
|
||||
- 调用 `set(data)` 将数据写入共享内存。
|
||||
- 否则:
|
||||
- 连接到已存在的共享内存。
|
||||
- 调用 `unregister(...)` 防止 Python 资源追踪器错误尝试销毁该共享内存(避免 `ResourceWarning`)。
|
||||
|
||||
> 🔒 注意:多个连接者可以同时连接同一个共享内存,但只能有一个创建者。
|
||||
|
||||
---
|
||||
|
||||
### `get(self) -> Any`
|
||||
|
||||
从共享内存中读取并反序列化数据。
|
||||
|
||||
#### 流程:
|
||||
1. 从 `self.sm.buf` 读取原始字节。
|
||||
2. 使用 `split(self.tailstring)[0]` 截取到结束标记前的有效部分。
|
||||
3. 解码为 UTF-8 字符串。
|
||||
4. 使用 `json.loads()` 反序列化为 Python 对象。
|
||||
|
||||
#### 返回值:
|
||||
- 原始数据对象(如 `dict`、`list` 等)
|
||||
|
||||
#### 异常处理:
|
||||
- 若数据损坏或非合法 JSON,将抛出 `json.JSONDecodeError`。
|
||||
|
||||
---
|
||||
|
||||
### `set(self, data)`
|
||||
|
||||
将数据写入共享内存(线程/进程安全)。
|
||||
|
||||
#### 参数:
|
||||
- `data`: 可 JSON 序列化的任意对象
|
||||
|
||||
#### 流程:
|
||||
1. 获取内部锁(防止并发写冲突)
|
||||
2. `json.dumps(data)` → 序列化为字符串
|
||||
3. 编码为 UTF-8 字节 + 添加尾部标记 `tailstring`
|
||||
4. 检查总长度是否超过 `datalen`
|
||||
5. 写入共享内存缓冲区
|
||||
|
||||
#### 异常:
|
||||
- 若序列化后数据长度超过预设 `datalen`,抛出:
|
||||
```python
|
||||
Exception(f'SharedMemory allocated size is {self.datalen} set size is {len(b)}')
|
||||
```
|
||||
|
||||
> ⚠️ 必须保证 `datalen` 足够大以容纳最大可能的数据量。
|
||||
|
||||
---
|
||||
|
||||
### `__del__(self)`
|
||||
|
||||
析构方法,自动清理共享内存资源。
|
||||
|
||||
#### 行为:
|
||||
- 调用 `self.sm.close()`:关闭当前进程对共享内存的引用。
|
||||
- 如果是创建者(`self.creator == True`),调用 `self.sm.unlink()`:删除系统中的共享内存对象,释放资源。
|
||||
|
||||
> ✅ 自动管理生命周期,避免内存泄漏。
|
||||
|
||||
---
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 示例一:启动创建者进程(写入数据)
|
||||
|
||||
运行命令:
|
||||
```bash
|
||||
python script.py create
|
||||
```
|
||||
|
||||
代码执行路径:
|
||||
```python
|
||||
sm = PSharedMemory('rtgerigreth', datalen=200, data={"aaa": "134902t34gf", "bbb": 36})
|
||||
sleep(10000) # 保持共享内存存活
|
||||
```
|
||||
|
||||
作用:创建名为 `'rtgerigreth'` 的共享内存,写入指定数据并持续 10000 秒以便其他进程读取。
|
||||
|
||||
---
|
||||
|
||||
### 示例二:启动连接者进程(读取数据)
|
||||
|
||||
运行命令:
|
||||
```bash
|
||||
python script.py
|
||||
```
|
||||
|
||||
代码执行路径:
|
||||
```python
|
||||
sm = PSharedMemory('rtgerigreth', datalen=200)
|
||||
x = sm.get()
|
||||
print(f'data in shared memory: {x}')
|
||||
```
|
||||
|
||||
输出示例:
|
||||
```
|
||||
data in shared memory: {'aaa': '134902t34gf', 'bbb': 36}
|
||||
```
|
||||
|
||||
前提:共享内存 `'rtgerigreth'` 已由另一进程创建并写入数据。
|
||||
|
||||
---
|
||||
|
||||
## 设计要点与注意事项
|
||||
|
||||
### ✅ 优点
|
||||
- **跨进程共享**:利用操作系统级别的共享内存机制,高效传输数据。
|
||||
- **类型灵活**:支持任意可 JSON 序列化的数据结构。
|
||||
- **写安全**:使用 `Lock` 防止并发写入导致数据错乱。
|
||||
- **自动清理**:创建者负责最终释放共享内存资源。
|
||||
|
||||
### ⚠️ 注意事项
|
||||
1. **共享内存名称需全局唯一且一致**
|
||||
- 不同进程必须使用完全相同的 `name` 才能访问同一块内存。
|
||||
|
||||
2. **预先估算 `datalen` 容量**
|
||||
- 必须大于等于最大可能的 `(json.dumps(data)).encode('utf-8') + tailstring` 的字节长度。
|
||||
- 推荐预留一定冗余空间。
|
||||
|
||||
3. **避免重复创建**
|
||||
- 若共享内存已存在,再以 `create=True` 方式尝试创建会引发异常。
|
||||
- 当前设计依赖 `data` 是否传入判断角色,需外部协调好创建顺序。
|
||||
|
||||
4. **资源泄露防护**
|
||||
- 使用 `unregister(..., 'shared_memory')` 阻止 Python 默认资源追踪器误删已被其他进程使用的共享内存。
|
||||
|
||||
5. **不支持动态扩容**
|
||||
- 共享内存大小固定,无法扩展。超限写入会抛出异常。
|
||||
|
||||
6. **无版本控制或过期机制**
|
||||
- 数据一旦写入,除非覆盖或重启,否则长期有效。
|
||||
|
||||
---
|
||||
|
||||
## 完整测试脚本建议
|
||||
|
||||
```python
|
||||
# writer.py
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
data = {"timestamp": time.time(), "msg": "Hello from writer"}
|
||||
sm = PSharedMemory('my_shared_data', datalen=512, data=data)
|
||||
print("Writer: Data written.")
|
||||
sleep(30)
|
||||
|
||||
# reader.py
|
||||
if __name__ == '__main__':
|
||||
import time
|
||||
sm = PSharedMemory('my_shared_data', datalen=512)
|
||||
for _ in range(10):
|
||||
try:
|
||||
x = sm.get()
|
||||
print("Reader:", x)
|
||||
break
|
||||
except Exception as e:
|
||||
print("Waiting for data...", str(e))
|
||||
time.sleep(1)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
`PSharedMemory` 提供了一个简洁、实用的封装,使得在多进程环境中共享结构化数据变得简单可靠。适合用于配置广播、状态共享、轻量级 IPC 场景。
|
||||
|
||||
| 特性 | 支持情况 |
|
||||
|------|----------|
|
||||
| 跨进程通信 | ✅ |
|
||||
| JSON 数据支持 | ✅ |
|
||||
| 写操作加锁 | ✅ |
|
||||
| 自动资源释放 | ✅ |
|
||||
| 动态扩容 | ❌ |
|
||||
| 数据加密/校验 | ❌ |
|
||||
| 多创建者支持 | ❌(仅单创建者) |
|
||||
|
||||
> 📌 推荐在受控环境下使用,确保命名唯一性和容量规划合理。
|
||||
|
||||
---
|
||||
|
||||
*文档版本:v1.0*
|
||||
*最后更新:2025年4月5日*
|
||||
339
aidocs/rc4.md
Normal file
339
aidocs/rc4.md
Normal file
@ -0,0 +1,339 @@
|
||||
# RC4 加密与密钥链技术文档
|
||||
|
||||
---
|
||||
|
||||
## 概述
|
||||
|
||||
本文档详细描述了一个基于 **RC4 流加密算法** 和动态密钥生成机制(`KeyChain`)的 Python 实现。该系统支持数据加解密、自动密钥轮换、时间敏感的密钥管理,并具备一定的容错能力以应对时钟偏差。
|
||||
|
||||
主要特性包括:
|
||||
|
||||
- 使用 RC4 算法进行流加密
|
||||
- 引入随机盐值(salt)和 SHA-1 密钥扩展增强安全性
|
||||
- 支持 Base64 编码输出
|
||||
- 动态密钥生成(基于时间窗口)
|
||||
- 容忍一定时间偏移的解密尝试(前向/后向周期补偿)
|
||||
|
||||
---
|
||||
|
||||
## 依赖模块
|
||||
|
||||
```python
|
||||
import time
|
||||
import datetime
|
||||
import random
|
||||
import base64
|
||||
from hashlib import sha1
|
||||
```
|
||||
|
||||
> 注:`random` 模块当前未使用,可能是预留或冗余导入。
|
||||
|
||||
---
|
||||
|
||||
## 核心类说明
|
||||
|
||||
### 1. `RC4` 类 —— RC4 加密器
|
||||
|
||||
#### 初始化方法:`__init__(self, data_coding='utf8')`
|
||||
|
||||
初始化 RC4 加密实例。
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
|--------------|--------|------|
|
||||
| `data_coding` | str | 数据编码格式,默认为 `'utf8'`,用于字符串与字节之间的转换 |
|
||||
|
||||
##### 属性:
|
||||
- `bcoding`: 内部字节编码,固定为 `'iso-8859-1'`(确保字节范围兼容)
|
||||
- `dcoding`: 外部数据编码(默认 UTF-8)
|
||||
- `salt`: 固定盐值 `b'AFUqx9WZuI32lnHk'`(16 字节),用于密钥强化
|
||||
|
||||
---
|
||||
|
||||
#### 私有方法:`_crypt(self, data: bytes, key: bytes) -> bytes`
|
||||
|
||||
执行标准 RC4 加密/解密操作。
|
||||
|
||||
##### 参数:
|
||||
- `data`: 待处理的字节数据
|
||||
- `key`: 密钥(bytes)
|
||||
|
||||
##### 返回:
|
||||
- 加密或解密后的字节串(经 `'iso-8859-1'` 编码)
|
||||
|
||||
##### 算法流程:
|
||||
1. 初始化长度为 256 的 S-box。
|
||||
2. 使用密钥调度算法(KSA)打乱 S-box。
|
||||
3. 使用伪随机生成算法(PRGA)逐字节异或加密。
|
||||
|
||||
> ⚠️ 注意:此实现中返回的是 `.join(chr(...))` 后再 `.encode('iso-8859-1')`,需注意字符边界问题。
|
||||
|
||||
---
|
||||
|
||||
#### 方法:`encode_bytes(self, bdata: bytes, key: bytes) -> bytes`
|
||||
|
||||
对字节数据执行带盐值的加密。
|
||||
|
||||
##### 流程:
|
||||
1. 计算密钥摘要:`sha1(key + self.salt).digest()`
|
||||
2. 使用上述密钥对 `bdata` 执行 `_crypt`
|
||||
3. 将原始 salt 前缀附加到密文前(共 16 字节)
|
||||
|
||||
##### 返回:
|
||||
- `salt + encrypted_data`(bytes)
|
||||
|
||||
---
|
||||
|
||||
#### 方法:`encode(self, data: str/bytes, key: str, encode=base64.b64encode) -> str`
|
||||
|
||||
高层加密接口,支持字符串输入和最终编码。
|
||||
|
||||
##### 参数:
|
||||
- `data`: 明文(字符串或字节)
|
||||
- `key`: 密钥(字符串)
|
||||
- `encode`: 可选编码函数(如 `base64.b64encode`),设为 `None` 则不编码
|
||||
|
||||
##### 行为:
|
||||
- 若 `data` 是字符串,则按 `dcoding` 编码为字节
|
||||
- 调用 `encode_bytes` 加密
|
||||
- 若指定 `encode` 函数,则对结果进行编码(如 Base64)
|
||||
- 返回编码后的字符串(UTF-8 解码)
|
||||
|
||||
> ✅ 示例:
|
||||
> ```python
|
||||
> rc = RC4()
|
||||
> code = rc.encode("hello", "mykey")
|
||||
> ```
|
||||
|
||||
---
|
||||
|
||||
#### 方法:`decode_bytes(self, data: bytes, key: bytes) -> bytes`
|
||||
|
||||
解密由 `encode_bytes` 生成的数据。
|
||||
|
||||
##### 步骤:
|
||||
1. 提取前 16 字节作为 salt(但实际未使用!应为 bug?)
|
||||
2. 使用 `sha1(key + self.salt)` 生成密钥
|
||||
3. 对剩余部分调用 `_crypt` 解密
|
||||
|
||||
> ❗⚠️ Bug 提示:虽然提取了 salt,但在密钥生成中仍使用固定的 `self.salt`,而非从输入读取的 salt。这导致无法正确处理不同 salt 的情况。
|
||||
|
||||
##### 返回:
|
||||
- 解密后的原始数据(bytes)
|
||||
|
||||
---
|
||||
|
||||
#### 方法:`decode(self, data: str/bytes, key: str, decode=base64.b64decode) -> str`
|
||||
|
||||
高层解密接口。
|
||||
|
||||
##### 参数:
|
||||
- `data`: 已加密并可选编码过的数据
|
||||
- `key`: 解密密钥
|
||||
- `decode`: 解码函数(默认 Base64)
|
||||
|
||||
##### 行为:
|
||||
- 若 `data` 是字符串,先用 `dcoding` 编码成字节
|
||||
- 若 `decode` 存在,先解码(如 Base64 解码)
|
||||
- 调用 `decode_bytes` 解密
|
||||
- 结果以 `dcoding` 解码为字符串返回
|
||||
|
||||
> ✅ 示例:
|
||||
> ```python
|
||||
> text = rc.decode(code, "mykey")
|
||||
> ```
|
||||
|
||||
---
|
||||
|
||||
### 2. `KeyChain` 类 —— 时间同步密钥链
|
||||
|
||||
提供基于时间窗口的动态密钥管理和自动密钥匹配功能,适用于分布式环境下的安全通信。
|
||||
|
||||
#### 初始化:`__init__(seed_str, crypter=None, keylen=23, period=600, threshold=60, time_delta=0)`
|
||||
|
||||
| 参数 | 类型 | 默认值 | 说明 |
|
||||
|-------------|----------|-----------|------|
|
||||
| `seed_str` | str/bytes | - | 种子字符串,用于派生密钥 |
|
||||
| `crypter` | RC4 实例 | None | 自定义加密器,若无则创建默认 RC4 |
|
||||
| `keylen` | int | 23 | 生成密钥的字符长度 |
|
||||
| `period` | int | 600 | 时间窗口周期(秒),即每 10 分钟换一次密钥 |
|
||||
| `threshold` | int | 60 | 容差阈值(秒),判断是否接近周期边界 |
|
||||
| `time_delta` | int | 0 | 时间偏移量(可用于调试或校准) |
|
||||
|
||||
##### 内部属性:
|
||||
- `keypool`: 缓存已生成的密钥 `{indicator: key}`
|
||||
- `timezone`: 设置为 GMT 时区
|
||||
- `indicator`: 当前时间所属的时间段起点(如每 600 秒一个区块)
|
||||
|
||||
---
|
||||
|
||||
#### 方法:`get_timestamp(self) -> int`
|
||||
|
||||
获取当前 Unix 时间戳(减去 `time_delta`)
|
||||
|
||||
---
|
||||
|
||||
#### 方法:`get_indicator(ts=None) -> int`
|
||||
|
||||
计算时间指示器(所在周期的起始时间)
|
||||
|
||||
例如:
|
||||
- `period=600` → 每 10 分钟一个周期
|
||||
- `ts=1712345678` → indicator = `(1712345678 // 600) * 600`
|
||||
|
||||
##### 返回:
|
||||
- 当前时间所属周期的起始时间戳
|
||||
|
||||
---
|
||||
|
||||
#### 方法:`genKey(indicator) -> bytes`
|
||||
|
||||
根据 `indicator` 从 `seed_str` 生成唯一密钥。
|
||||
|
||||
##### 算法逻辑:
|
||||
1. 若缓存中已有对应密钥,直接返回
|
||||
2. 否则使用循环算法从 `seed_str` 中按规则选取字符构造密钥:
|
||||
- 初始值 `v = indicator`
|
||||
- 每次取 `j = v % keylen` 作为索引
|
||||
- 从 `seed_str[j]` 取字符追加
|
||||
- 更新 `v = v - (j + k1)*m + keylen`
|
||||
- 直到生成 `keylen` 长度的密钥
|
||||
3. 缓存密钥,并清理过期条目(早于 `indicator - period` 的)
|
||||
|
||||
> 🔐 安全性:密钥依赖时间和种子,难以预测
|
||||
|
||||
---
|
||||
|
||||
#### 方法:`encode_bytes(bdata: bytes) -> bytes`
|
||||
|
||||
使用当前时间对应的密钥加密数据。
|
||||
|
||||
##### 流程:
|
||||
1. 获取当前 `indicator`
|
||||
2. 生成对应密钥 `key`
|
||||
3. 构造明文:`key + bdata`(将密钥本身也加密传输)
|
||||
4. 使用 `crypter.encode_bytes(data, key)` 加密
|
||||
|
||||
> 📦 输出结构:`salt + encrypted(key + bdata)`
|
||||
|
||||
---
|
||||
|
||||
#### 方法:`decode_bytes(data: bytes) -> bytes or None`
|
||||
|
||||
智能解密:尝试用当前、上一周期、下一周期的密钥解密。
|
||||
|
||||
##### 步骤:
|
||||
1. 获取当前 `indicator` 和密钥
|
||||
2. 尝试解密
|
||||
3. 验证解密后数据前缀是否等于密钥(完整性校验)
|
||||
4. 如果失败且接近周期底部(即将切换),尝试上一周期密钥
|
||||
5. 如果接近顶部(刚过切换点),尝试下一周期密钥
|
||||
|
||||
##### 返回:
|
||||
- 成功:解密后的原始数据(不含密钥头)
|
||||
- 失败:`None`
|
||||
|
||||
---
|
||||
|
||||
#### 方法:`encode(text: str) -> bytes`
|
||||
|
||||
加密字符串版本,内部转为 UTF-8 字节后调用 `encode_bytes`
|
||||
|
||||
---
|
||||
|
||||
#### 方法:`decode(data: bytes) -> str or None`
|
||||
|
||||
解密并返回 UTF-8 字符串;若失败返回 `None`
|
||||
|
||||
---
|
||||
|
||||
## 辅助函数
|
||||
|
||||
### `password(pwdtxt: str, key=pwdkey) -> str or None`
|
||||
|
||||
使用固定密钥对密码文本加密并验证。
|
||||
|
||||
##### 步骤:
|
||||
1. 加密 `pwdtxt`
|
||||
2. 立即解密验证一致性
|
||||
3. 一致则返回加密结果(Base64 编码字符串),否则返回 `None`
|
||||
|
||||
> ✅ 用途:确保加密过程可靠
|
||||
|
||||
---
|
||||
|
||||
### `unpassword(code: str, key=pwdkey) -> str or None`
|
||||
|
||||
解密由 `password()` 生成的密文。
|
||||
|
||||
##### 返回:
|
||||
- 明文字符串 或 `None`
|
||||
|
||||
---
|
||||
|
||||
## 使用示例
|
||||
|
||||
```python
|
||||
if __name__ == '__main__':
|
||||
data = b"231r3 feregrenerjk gkht324g8924gnfw ;ejkvwkjerv"
|
||||
key = b'123456'
|
||||
|
||||
rc4 = RC4()
|
||||
kc = KeyChain('in the heaven, we are equal', rc4)
|
||||
|
||||
print("原始数据:", data)
|
||||
|
||||
# 加密
|
||||
encoded_data = kc.encode_bytes(data)
|
||||
print("加密后数据:", encoded_data, "长度:", len(encoded_data))
|
||||
|
||||
# 解密
|
||||
decoded_data = kc.decode_bytes(encoded_data)
|
||||
print("解密数据:", decoded_data)
|
||||
print("解密成功:", decoded_data == data)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 安全性分析
|
||||
|
||||
| 特性 | 评估 |
|
||||
|------|------|
|
||||
| **RC4 算法** | 已知存在弱点(如偏差输出),不推荐用于高安全场景 |
|
||||
| **Salt 固定** | `self.salt` 固定可能导致重放攻击风险 |
|
||||
| **密钥派生** | 自定义算法非标准,缺乏密码学证明 |
|
||||
| **密钥包含在明文中加密** | 有一定防篡改作用,但仍可能被暴力破解 |
|
||||
| **时间同步机制** | 适合短期令牌、API 认证等场景 |
|
||||
| **Base64 支持** | 方便文本传输 |
|
||||
|
||||
> ✅ 推荐用途:轻量级认证令牌、会话加密、临时凭证
|
||||
> ❌ 不推荐:金融交易、长期存储加密
|
||||
|
||||
---
|
||||
|
||||
## 已知问题与改进建议
|
||||
|
||||
| 问题 | 描述 | 建议 |
|
||||
|------|------|-------|
|
||||
| ❗ Salt 未真正参与密钥派生 | `decode_bytes` 忽略传入 salt,始终用固定 salt | 应使用传入 salt 生成密钥 |
|
||||
| ⚠️ `_crypt` 返回方式不安全 | 使用 `chr()` 可能超出 ASCII 范围引发错误 | 改为直接构建字节数组 |
|
||||
| ⚠️ RC4 已过时 | 存在已知漏洞(如 WEP 攻击) | 替换为 AES-CTR 或 ChaCha20 |
|
||||
| 🔒 自定义密钥生成算法 | 非标准化,可能存在碰撞或弱密钥 | 使用 HMAC-SHA256 等标准 KDF |
|
||||
| 💤 时间依赖性强 | 严重依赖系统时间准确性 | 增加强时间验证或 NTP 校准提示 |
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
本模块实现了一套基于 RC4 和时间窗口密钥链的安全通信原型,具备以下优点:
|
||||
|
||||
- 简洁易集成
|
||||
- 支持自动密钥轮换
|
||||
- 具备时间容忍机制
|
||||
- 可用于短生命周期数据保护
|
||||
|
||||
建议在低风险场景中使用,并考虑升级至更现代的加密算法(如 AES-GCM 或 ChaCha20-Poly1305)以提升安全性。
|
||||
|
||||
---
|
||||
|
||||
> 文档版本:v1.0
|
||||
> 最后更新:2025-04-05
|
||||
273
aidocs/receiveMail.md
Normal file
273
aidocs/receiveMail.md
Normal file
@ -0,0 +1,273 @@
|
||||
# 邮件接收与解析工具技术文档
|
||||
|
||||
本项目是一个基于 Python 的 POP3 协议邮件接收与解析工具,能够从指定邮箱拉取近期邮件,并对邮件内容进行解码和处理。支持按时间范围过滤邮件,并提供可扩展的回调机制用于自定义处理逻辑。
|
||||
|
||||
---
|
||||
|
||||
## 📦 依赖库
|
||||
|
||||
```python
|
||||
import poplib
|
||||
import pdb
|
||||
import email
|
||||
from email import header
|
||||
import re
|
||||
import time
|
||||
import datetime
|
||||
import os
|
||||
```
|
||||
|
||||
### 主要用途:
|
||||
- `poplib`: 实现 POP3 协议连接,用于收信。
|
||||
- `email`: 解析 MIME 格式的邮件内容。
|
||||
- `re`: 正则表达式,用于提取日期信息。
|
||||
- `time`, `datetime`: 时间处理与格式转换。
|
||||
- `os`: 文件路径等操作系统操作(未直接使用,保留备用)。
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ 全局配置变量
|
||||
|
||||
| 变量名 | 类型 | 描述 |
|
||||
|----------|--------|------|
|
||||
| `POP_ADDR` | str | POP3 服务器地址,如 `'pop.126.com'` |
|
||||
| `USER` | str | 用户登录邮箱账号(需手动填写) |
|
||||
| `PASS` | str | 用户邮箱密码或授权码(需手动填写) |
|
||||
| `CONFIG` | str | 存储“最近读取时间”的配置文件路径(需手动设置) |
|
||||
|
||||
> 🔐 注意:`USER`、`PASS` 和 `CONFIG` 需在使用前由用户显式赋值。
|
||||
|
||||
---
|
||||
|
||||
## 🕰️ 时间处理函数
|
||||
|
||||
### `getYear(date: str) -> int`
|
||||
从日期字符串中提取年份(匹配 `2xxx` 四位数)。
|
||||
|
||||
#### 示例:
|
||||
```python
|
||||
getYear("Mon, 15 Apr 2025 10:30:00 +0800") # 返回 2025
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `getMonth(date: str) -> int`
|
||||
根据英文缩写月份(Jan-Dec)返回对应的数字(1-12)。
|
||||
|
||||
#### 支持的缩写:
|
||||
`Jan`, `Feb`, `Mar`, `Apr`, `May`, `Jun`,
|
||||
`Jul`, `Aug`, `Sep`, `Oct`, `Nov`, `Dec`
|
||||
|
||||
#### 示例:
|
||||
```python
|
||||
getMonth("Mon, 15 Apr 2025 10:30:00 +0800") # 返回 4
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `getDay(date: str) -> int`
|
||||
提取日期中的日部分(1-31)。
|
||||
|
||||
#### 示例:
|
||||
```python
|
||||
getDay("Mon, 15 Apr 2025 10:30:00 +0800") # 返回 15
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `getTime(date: str) -> list[int]`
|
||||
提取时间部分并返回 `[小时, 分钟, 秒]` 整数列表。
|
||||
|
||||
#### 示例:
|
||||
```python
|
||||
getTime("Mon, 15 Apr 2025 10:30:45 +0800") # 返回 [10, 30, 45]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `transformDate(date: str) -> int`
|
||||
将标准邮件日期格式转换为一个整数时间戳,便于比较。
|
||||
|
||||
#### 输出格式:
|
||||
`YYYYMMDDHHMMSS`(例如:`20250415103045`)
|
||||
|
||||
#### 原理:
|
||||
依次拼接年、月、日、时、分、秒为一个大整数,支持数值大小比较。
|
||||
|
||||
#### 示例:
|
||||
```python
|
||||
transformDate("Mon, 15 Apr 2025 10:30:45 +0800") # 返回 20250415103045
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `getTimeEarly(period: str) -> str`
|
||||
计算当前时间往前推一段时间后的时间点(返回 `ctime()` 格式字符串)。
|
||||
|
||||
#### 支持的时间单位:
|
||||
| 单位 | 含义 |
|
||||
|------|------------|
|
||||
| `y` | 年 |
|
||||
| `m` | 月 |
|
||||
| `d` | 天 |
|
||||
| `H` | 小时 |
|
||||
| `M` | 分钟 |
|
||||
| `S` | 秒 |
|
||||
|
||||
#### 示例:
|
||||
```python
|
||||
getTimeEarly("1d3H") # 当前时间减去 1 天 3 小时
|
||||
```
|
||||
|
||||
> ⚠️ 警告:`years` 和 `months` 使用的是 `timedelta`,不精确(Python 原生限制),建议仅用于粗略估算。
|
||||
|
||||
---
|
||||
|
||||
## 💾 持久化记录功能
|
||||
|
||||
### `getRecentReadMailTime() -> str`
|
||||
读取上次处理邮件的时间(存储于 `CONFIG` 文件中)。
|
||||
|
||||
#### 返回:
|
||||
文件内容原始字符串(通常是 `time.ctime()` 格式)。
|
||||
|
||||
---
|
||||
|
||||
### `setRecentReadMailTime() -> None`
|
||||
将当前时间写入 `CONFIG` 文件,标记为“最近已读时间”。
|
||||
|
||||
#### 示例文件内容:
|
||||
```
|
||||
Mon Apr 15 10:30:45 2025
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✉️ 邮件内容解析
|
||||
|
||||
### `parseMailContent(msg: Message) -> None`
|
||||
递归解析邮件正文内容,自动识别编码并输出文本。
|
||||
|
||||
#### 特性:
|
||||
- 支持多部分 MIME 邮件(如 HTML/纯文本混合)
|
||||
- 自动检测 `charset` 编码
|
||||
- 若解码失败,默认输出 `"Decode Failed"`
|
||||
|
||||
#### 输出方式:
|
||||
直接打印到控制台(可通过修改实现日志或其他输出)
|
||||
|
||||
---
|
||||
|
||||
## 📥 核心功能:接收邮件
|
||||
|
||||
### `recvEmail(POP_ADDR, USER, PASS, PERIOD, callback) -> None`
|
||||
|
||||
#### 参数说明:
|
||||
|
||||
| 参数 | 类型 | 说明 |
|
||||
|-------------|----------|------|
|
||||
| `POP_ADDR` | str | POP3 服务器地址 |
|
||||
| `USER` | str | 登录用户名(邮箱) |
|
||||
| `PASS` | str | 登录密码或授权码 |
|
||||
| `PERIOD` | str | 时间范围,如 `"7d"` 表示最近7天 |
|
||||
| `callback` | function | 回调函数,处理每封符合条件的邮件 |
|
||||
|
||||
#### 工作流程:
|
||||
1. 连接到 POP3 服务器并登录。
|
||||
2. 获取邮件总数及大小。
|
||||
3. 计算起始时间 `FROMTIME = now - PERIOD`。
|
||||
4. 按倒序遍历所有邮件(最新优先)。
|
||||
5. 对每封邮件:
|
||||
- 下载原始数据
|
||||
- 解析为 `email.message.Message` 对象
|
||||
- 提取 `Date` 头部并转换为整数时间
|
||||
- 如果邮件时间 **晚于** 起始时间,则调用 `callback(mail)`
|
||||
- 若 `callback` 返回 `False`,中断后续处理
|
||||
6. 遇到第一封过期邮件即停止(因邮件按时间排序)
|
||||
|
||||
#### 示例调用:
|
||||
```python
|
||||
def my_handler(mail):
|
||||
print("发件人:", mail.get('From'))
|
||||
print("主题:", header.decode_header(mail.get('Subject'))[0][0])
|
||||
parseMailContent(mail)
|
||||
return True # 继续处理下一封
|
||||
|
||||
recvEmail(POP_ADDR, USER, PASS, "7d", my_handler)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧪 调试支持
|
||||
|
||||
内置 `pdb.set_trace()` 注释行可用于断点调试:
|
||||
|
||||
```python
|
||||
# pdb.set_trace()
|
||||
```
|
||||
|
||||
取消注释可在关键位置暂停程序执行,方便排查问题。
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ 使用示例
|
||||
|
||||
```python
|
||||
# 设置参数
|
||||
USER = 'your_email@126.com'
|
||||
PASS = 'your_password_or_token'
|
||||
CONFIG = './last_read_time.txt'
|
||||
|
||||
# 定义回调函数
|
||||
def handle_mail(mail):
|
||||
subject = mail.get('Subject')
|
||||
sender = mail.get('From')
|
||||
date = mail.get('Date')
|
||||
print(f"收到来信:{subject} 来自 {sender} 发送于 {date}")
|
||||
parseMailContent(mail)
|
||||
return True # 继续处理
|
||||
|
||||
# 接收过去 3 天内的邮件
|
||||
recvEmail(POP_ADDR, USER, PASS, "3d", handle_mail)
|
||||
|
||||
# 更新最后读取时间
|
||||
setRecentReadMailTime()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ❗ 注意事项
|
||||
|
||||
1. **安全性**:避免将密码硬编码在代码中,建议通过环境变量或配置文件加载。
|
||||
2. **编码兼容性**:某些非标准编码可能导致解码失败,建议增强异常处理。
|
||||
3. **性能**:对于大量邮件,应考虑分页或只获取头部优化。
|
||||
4. **错误处理**:目前缺少网络异常、认证失败等错误捕获,生产环境需补充 `try-except`。
|
||||
5. **POP3 局限性**:无法像 IMAP 一样标记状态或选择文件夹,仅适合简单场景。
|
||||
|
||||
---
|
||||
|
||||
## 📌 待改进方向
|
||||
|
||||
| 功能 | 建议 |
|
||||
|------|------|
|
||||
| 错误处理 | 添加 `try...except` 处理连接超时、认证失败等问题 |
|
||||
| 日志系统 | 替换 `print()` 为 `logging` 模块 |
|
||||
| 配置管理 | 使用 JSON/YAML 配置文件替代全局变量 |
|
||||
| 编码容错 | 增加常见编码尝试(如 gb2312, gbk) |
|
||||
| 回调规范 | 定义统一的回调接口结构 |
|
||||
|
||||
---
|
||||
|
||||
## 📎 总结
|
||||
|
||||
该脚本适用于轻量级定时任务,例如:
|
||||
- 监控报警邮件
|
||||
- 自动采集特定来源邮件内容
|
||||
- 简单的邮件通知处理器
|
||||
|
||||
结合 cron 或 Windows 任务计划,可构建自动化邮件响应系统。
|
||||
|
||||
---
|
||||
|
||||
📄 文档版本:v1.0
|
||||
📅 最后更新:2025-04-05
|
||||
228
aidocs/registerfunction.md
Normal file
228
aidocs/registerfunction.md
Normal file
@ -0,0 +1,228 @@
|
||||
# 技术文档:函数与协程注册系统
|
||||
|
||||
## 概述
|
||||
|
||||
本模块提供了一套基于单例模式的**函数注册**和**协程执行管理**机制,支持同步函数与异步协程的统一注册、获取与调用。通过 `RegisterFunction` 和 `RegisterCoroutine` 两个核心类,实现了灵活的插件式函数调用架构。
|
||||
|
||||
该系统适用于需要动态注册和调用函数/协程的场景(如事件处理器、中间件链、插件系统等),并保证全局唯一实例(单例)。
|
||||
|
||||
---
|
||||
|
||||
## 模块依赖
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from inspect import isfunction, iscoroutinefunction
|
||||
from functools import partial
|
||||
from appPublic.dictObject import DictObject
|
||||
from appPublic.worker import awaitify
|
||||
from appPublic.Singleton import SingletonDecorator
|
||||
from appPublic.log import info, error
|
||||
```
|
||||
|
||||
### 第三方/自定义组件说明:
|
||||
|
||||
| 包/模块 | 功能 |
|
||||
|--------|------|
|
||||
| `appPublic.dictObject.DictObject` | 可属性访问的字典对象,用于存储键值对数据 |
|
||||
| `appPublic.worker.awaitify` | 将同步函数包装为可被 `await` 调用的形式 |
|
||||
| `appPublic.Singleton.SingletonDecorator` | 单例装饰器,确保类仅有一个实例 |
|
||||
| `appPublic.log.info`, `.error` | 日志输出工具 |
|
||||
|
||||
---
|
||||
|
||||
## 核心类
|
||||
|
||||
### 1. `RegisterFunction` —— 函数注册中心(单例)
|
||||
|
||||
> 装饰器:`@SingletonDecorator`
|
||||
> 用途:注册、获取并执行命名的函数或协程。
|
||||
|
||||
#### 属性
|
||||
|
||||
- `registKW` (dict): 存储注册的函数,键为名称,值为函数对象。
|
||||
|
||||
#### 方法
|
||||
|
||||
##### `__init__(self)`
|
||||
初始化空的函数注册表。
|
||||
|
||||
##### `register(name: str, func: callable)`
|
||||
将一个函数或协程注册到指定名称下。
|
||||
|
||||
- **参数**:
|
||||
- `name` (str): 函数的唯一标识名。
|
||||
- `func` (callable): 要注册的函数或异步协程。
|
||||
- **行为**:
|
||||
- 若 `func` 不是函数或协程,则记录错误日志并返回。
|
||||
- 同名函数会被覆盖(最新注册者胜出)。
|
||||
- **示例**:
|
||||
```python
|
||||
rf = RegisterFunction()
|
||||
rf.register("hello", lambda: print("Hello"))
|
||||
```
|
||||
|
||||
##### `get(name: str) -> Optional[callable]`
|
||||
根据名称获取已注册的函数。
|
||||
|
||||
- **返回**:函数对象,若未找到则返回 `None`。
|
||||
|
||||
##### `run(name: str, *args, **kw)`
|
||||
**同步执行**指定名称的函数。
|
||||
|
||||
- **注意**:
|
||||
- 如果目标是协程函数(`async def`),会打印提示但不执行,并返回 `None`。
|
||||
- 仅适用于普通同步函数。
|
||||
- **返回**:函数执行结果。
|
||||
- **异常处理**:若函数未注册,输出错误日志。
|
||||
|
||||
##### `async exe(name: str, *args, **kw) -> Any`
|
||||
**异步安全执行**指定名称的函数或协程。
|
||||
|
||||
- **逻辑**:
|
||||
- 若函数是协程(`iscoroutinefunction`),直接 `await` 执行。
|
||||
- 若是普通函数,使用 `awaitify` 包装后执行(使其可在异步上下文中调用)。
|
||||
- **返回**:函数执行结果(支持 `await`)。
|
||||
- **失败处理**:函数不存在时返回 `None`(静默失败)。
|
||||
|
||||
---
|
||||
|
||||
### 2. `RegisterCoroutine` —— 协程链注册中心(单例)
|
||||
|
||||
> 装饰器:`@SingletonDecorator`
|
||||
> 用途:按名称注册多个函数/协程,并以**逆序方式批量执行**(类似中间件栈)。
|
||||
|
||||
#### 属性
|
||||
|
||||
- `kw` (`DictObject`): 内部存储结构,每个名字对应一个函数列表。
|
||||
|
||||
#### 方法
|
||||
|
||||
##### `__init__(self)`
|
||||
初始化一个空的 `DictObject` 来保存函数列表。
|
||||
|
||||
##### `register(name: str, func: callable)`
|
||||
向指定名称追加一个函数或协程。
|
||||
|
||||
- **行为**:
|
||||
- 验证输入是否为合法函数。
|
||||
- 若该名称尚无注册项,则创建新列表 `[func]`。
|
||||
- 否则将 `func` 追加至列表末尾。
|
||||
- **特点**:支持同一名称注册多个处理器。
|
||||
|
||||
##### `async exe(name: str, *args, **kw)`
|
||||
异步执行该名称下所有注册的函数/协程,顺序为**后进先出(LIFO)**。
|
||||
|
||||
- **流程**:
|
||||
1. 获取函数列表副本。
|
||||
2. 反转列表(实现 LIFO 执行)。
|
||||
3. 遍历执行每一个函数:
|
||||
- 协程:`await f(*args, **kw)`
|
||||
- 普通函数:直接调用 `f(*args, **kw)`
|
||||
- **返回**:始终返回 `None`。
|
||||
- **说明**:适合用于事件通知、钩子机制等无需聚合返回值的场景。
|
||||
|
||||
---
|
||||
|
||||
## 辅助函数
|
||||
|
||||
以下函数提供便捷接口,避免手动实例化单例。
|
||||
|
||||
### `getRegisterFunctionByName(name: str) -> Optional[callable]`
|
||||
获取已注册的函数对象。
|
||||
|
||||
- **参数**:`name` – 函数名
|
||||
- **返回**:函数引用或 `None`
|
||||
|
||||
### `registerFunction(name: str, func: callable)`
|
||||
快捷注册函数。
|
||||
|
||||
- **等价于**:
|
||||
```python
|
||||
RegisterFunction().register(name, func)
|
||||
```
|
||||
|
||||
### `async rfexe(rfname: str, *args, **kw) -> Any`
|
||||
异步执行指定名称的注册函数。
|
||||
|
||||
- **等价于**:
|
||||
```python
|
||||
await RegisterFunction().exe(rfname, *args, **kw)
|
||||
```
|
||||
|
||||
### `rfrun(rfname: str, *args, **kw) -> Any`
|
||||
同步执行注册函数(仅限同步函数)。
|
||||
|
||||
- **等价于**:
|
||||
```python
|
||||
RegisterFunction().run(rfname, *args, **kw)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 使用示例
|
||||
|
||||
```python
|
||||
# 定义测试函数
|
||||
def x(dic):
|
||||
dic['a'] = 'a'
|
||||
return dic
|
||||
|
||||
async def y(dic):
|
||||
dic['b'] = 'b'
|
||||
return dic
|
||||
|
||||
def z(dic):
|
||||
dic['c'] = 1
|
||||
return dic
|
||||
|
||||
# 主程序
|
||||
async def main():
|
||||
rf = RegisterCoroutine()
|
||||
rf.register('test', z)
|
||||
rf.register('test', y)
|
||||
rf.register('test', x)
|
||||
|
||||
d = {}
|
||||
await rf.exe('test', d) # 按 x → y → z 的反向顺序执行
|
||||
print(d) # 输出: {'a': 'a', 'b': 'b', 'c': 1}
|
||||
|
||||
if __name__ == '__main__':
|
||||
asyncio.get_event_loop().run_until_complete(main())
|
||||
```
|
||||
|
||||
> ⚠️ 注意:由于 `exe()` 中反转了函数列表,实际执行顺序为最后注册的最先执行。
|
||||
|
||||
---
|
||||
|
||||
## 设计亮点
|
||||
|
||||
| 特性 | 描述 |
|
||||
|------|------|
|
||||
| ✅ **单例模式** | 使用 `SingletonDecorator` 确保全局唯一实例,便于跨模块共享注册表 |
|
||||
| ✅ **兼容同步与异步** | 自动识别协程函数并通过 `awaitify` 统一异步调用接口 |
|
||||
| ✅ **类型安全检查** | 注册时验证是否为合法函数,防止误注册非可调用对象 |
|
||||
| ✅ **灵活调用方式** | 提供同步 `run` 与异步 `exe` 接口,适应不同运行环境 |
|
||||
| ✅ **链式协程支持** | `RegisterCoroutine` 支持多函数绑定与逆序执行,适用于中间件/钩子系统 |
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **协程不能用 `run()` 执行**:`run()` 方法不会 `await` 协程,可能导致警告或无效执行。
|
||||
2. **函数覆盖问题**:`RegisterFunction.register()` 是覆盖式注册,同名函数旧版本将丢失。
|
||||
3. **异常捕获建议**:当前未封装异常处理,在生产环境中建议在 `exe()` 外层添加 try-except。
|
||||
4. **性能考虑**:频繁注册可能导致内存增长,长期运行服务应定期清理无用条目。
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
本模块构建了一个轻量级、高内聚的函数调度系统,特别适用于:
|
||||
|
||||
- 插件化系统
|
||||
- 事件驱动架构
|
||||
- 中间件管道设计
|
||||
- 异步任务调度
|
||||
|
||||
结合 `awaitify` 与单例模式,实现了简洁而强大的函数注册与异步执行能力,具备良好的扩展性和复用性。
|
||||
208
aidocs/restrictedEnv.md
Normal file
208
aidocs/restrictedEnv.md
Normal file
@ -0,0 +1,208 @@
|
||||
# `RestrictedEnv` 类技术文档
|
||||
|
||||
## 概述
|
||||
|
||||
`RestrictedEnv` 是一个轻量级的沙箱环境类,用于在受限上下文中安全地执行与日期/时间相关的表达式。它通过注册特定函数并利用 Python 的 `exec` 动态执行字符串形式的代码,提供了一种简洁的方式来解析和计算时间表达式,同时避免直接暴露完整的 Python 执行环境。
|
||||
|
||||
该类主要适用于需要动态解析时间表达式的场景(如配置文件、规则引擎等),且希望限制可调用函数范围以增强安全性。
|
||||
|
||||
---
|
||||
|
||||
## 依赖
|
||||
|
||||
- `appPublic.timeUtils as tu`:提供日期/时间格式化与解析工具函数。
|
||||
- `datetime as dt`:Python 标准库中的 `datetime` 模块,用于处理日期和时间对象。
|
||||
|
||||
> ⚠️ 注意:确保 `appPublic.timeUtils` 模块已正确安装并包含所需方法。
|
||||
|
||||
---
|
||||
|
||||
## 类定义
|
||||
|
||||
```python
|
||||
class RestrictedEnv:
|
||||
```
|
||||
|
||||
### 构造函数 `__init__`
|
||||
|
||||
初始化 `RestrictedEnv` 实例,并注册一组预定义的时间相关函数到内部命名空间。
|
||||
|
||||
#### 注册的函数:
|
||||
| 函数名 | 对应方法 | 描述 |
|
||||
|------------|----------------------------|------|
|
||||
| `today` | `self.today()` | 返回今天的日期(仅年月日) |
|
||||
| `date` | `self.date(dstr)` | 将字符串转换为日期对象 |
|
||||
| `datetime` | `self.datetime(dstr)` | 将字符串转换为 datetime 对象 |
|
||||
| `now` | `dt.datetime.now`(原生) | 获取当前精确到微秒的日期时间 |
|
||||
|
||||
#### 示例:
|
||||
```python
|
||||
env = RestrictedEnv()
|
||||
# 此时 env 已经可以识别 'today()', 'date(...)', 'datetime(...)', 'now()' 等表达式
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 方法说明
|
||||
|
||||
### `reg(k: str, v: callable) -> None`
|
||||
|
||||
将键值对 `(k, v)` 注册到实例的命名空间中,使得后续可通过字符串表达式调用 `v`。
|
||||
|
||||
#### 参数:
|
||||
- `k` (str): 在表达式中使用的函数或变量名称。
|
||||
- `v` (callable): 可调用对象(函数、方法、lambda 等)或数据。
|
||||
|
||||
#### 实现方式:
|
||||
使用 `self.__dict__[k] = v` 将其注入局部作用域。
|
||||
|
||||
#### 示例:
|
||||
```python
|
||||
ns.reg('pi', 3.14159)
|
||||
result = ns.run('pi * 2') # 返回 6.28318
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `run(dstr: str) -> Any`
|
||||
|
||||
在受限环境中执行给定的表达式字符串,并返回其结果。
|
||||
|
||||
#### 参数:
|
||||
- `dstr` (str): 要执行的表达式字符串(如 `'today()'`, `"date('2023-01-01')"`)。
|
||||
|
||||
#### 返回值:
|
||||
表达式计算的结果。
|
||||
|
||||
#### 原理:
|
||||
1. 将输入表达式包装为赋值语句:`__tempkey__ = <表达式>`
|
||||
2. 使用 `exec()` 在全局 `globals()` 和实例局部命名空间 `self.__dict__` 中执行。
|
||||
3. 返回临时变量 `self.__tempkey__` 的值。
|
||||
|
||||
> ✅ 安全提示:虽然名为“受限”,但由于使用了 `exec`,仍需谨慎处理不可信输入,防止代码注入风险。
|
||||
|
||||
#### 示例:
|
||||
```python
|
||||
result = ns.run("today()") # 返回今天日期(Date 类型)
|
||||
result = ns.run("now()") # 返回当前 datetime 对象
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `today() -> date`
|
||||
|
||||
获取当前日期(仅年-月-日部分),忽略时间信息。
|
||||
|
||||
#### 返回:
|
||||
- `date` 对象,由 `tu.ymdDate(year, month, day)` 生成。
|
||||
|
||||
#### 内部逻辑:
|
||||
```python
|
||||
now = dt.datetime.now()
|
||||
return tu.ymdDate(now.year, now.month, now.day)
|
||||
```
|
||||
|
||||
#### 示例:
|
||||
```python
|
||||
ns.run("today()") # 输出类似: 2025-04-05
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `date(dstr: str) -> date`
|
||||
|
||||
将日期字符串转换为日期对象。
|
||||
|
||||
#### 参数:
|
||||
- `dstr` (str): 格式化的日期字符串,例如 `'2023-12-25'`
|
||||
|
||||
#### 返回:
|
||||
- `date` 对象,由 `tu.str2Date(dstr)` 解析得到。
|
||||
|
||||
#### 示例:
|
||||
```python
|
||||
ns.run("date('2023-01-01')") # 返回对应日期对象
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `datetime(dstr: str) -> datetime`
|
||||
|
||||
将日期时间字符串转换为 `datetime` 对象。
|
||||
|
||||
#### 参数:
|
||||
- `dstr` (str): 包含时间和日期的字符串,如 `"2023-06-15 14:30:00"`
|
||||
|
||||
#### 返回:
|
||||
- `datetime` 对象,由 `tu.str2Datetime(dstr)` 解析生成。
|
||||
|
||||
#### 示例:
|
||||
```python
|
||||
ns.run('datetime("2023-07-04 12:00:00")')
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 使用示例
|
||||
|
||||
以下是在主模块中运行的测试代码:
|
||||
|
||||
```python
|
||||
if __name__ == '__main__':
|
||||
ns = RestrictedEnv()
|
||||
a = ns.run('today()') # 当前日期
|
||||
b = ns.run("date('2011-10-31')") # 自定义日期对象
|
||||
c = ns.run('datetime("2012-03-12 10:22:22")') # 自定义 datetime
|
||||
d = ns.run('now()') # 当前完整时间戳
|
||||
```
|
||||
|
||||
### 预期输出类型:
|
||||
| 表达式 | 返回类型 | 示例值 |
|
||||
|--------|----------|--------|
|
||||
| `today()` | `date` | `2025-04-05` |
|
||||
| `date(...)` | `date` | `2011-10-31` |
|
||||
| `datetime(...)` | `datetime` | `2012-03-12 10:22:22` |
|
||||
| `now()` | `datetime` | `2025-04-05 10:15:30.123456` |
|
||||
|
||||
---
|
||||
|
||||
## 安全性说明
|
||||
|
||||
⚠️ **警告**:尽管称为“受限环境”,但因使用了 `exec()`,若传入恶意字符串可能导致任意代码执行。
|
||||
|
||||
建议:
|
||||
- 仅用于可信输入或受控环境。
|
||||
- 不应用于解析用户提交的表达式,除非有额外语法校验层。
|
||||
- 可考虑替换为更安全的表达式解析器(如 `asteval`, `simpleeval`)以提升安全性。
|
||||
|
||||
---
|
||||
|
||||
## 扩展建议
|
||||
|
||||
你可以通过继承或修改 `reg` 调用来扩展功能,例如添加数学函数、条件判断等:
|
||||
|
||||
```python
|
||||
import math
|
||||
ns.reg('sqrt', math.sqrt)
|
||||
result = ns.run('sqrt(16)') # 得到 4.0
|
||||
```
|
||||
|
||||
但请始终注意作用域控制与安全性。
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
`RestrictedEnv` 提供了一个简单而有效的方式,在隔离环境下动态求值时间表达式。适合集成于规则系统、报表脚本或配置驱动的应用程序中,作为时间处理的 DSL(领域专用语言)入口点。
|
||||
|
||||
| 特性 | 说明 |
|
||||
|------|------|
|
||||
| ✅ 易用性 | 支持自然的时间表达式写法 |
|
||||
| ✅ 可扩展 | 通过 `reg()` 添加新函数 |
|
||||
| ⚠️ 安全性 | 使用 `exec`,需防范注入风险 |
|
||||
| 📦 轻量 | 无外部依赖(除 `appPublic.timeUtils`) |
|
||||
|
||||
---
|
||||
|
||||
✅ **推荐用途**:内部工具、自动化脚本、配置解析器
|
||||
❌ **不推荐用途**:Web API 输入解析、不受信用户输入处理
|
||||
269
aidocs/rsaPeer.md
Normal file
269
aidocs/rsaPeer.md
Normal file
@ -0,0 +1,269 @@
|
||||
# `RSAPeer` 加密通信模块技术文档
|
||||
|
||||
## 概述
|
||||
|
||||
本模块提供基于 **RSA + RC4** 的混合加密机制,用于实现安全的点对点数据传输。通过使用 RSA 对称密钥加密、RC4 数据流加密以及数字签名验证,确保消息的机密性、完整性与身份认证。
|
||||
|
||||
主要功能包括:
|
||||
- 使用接收方公钥加密会话密钥(RC4 Key)
|
||||
- 使用随机生成的 RC4 密钥加密实际数据
|
||||
- 发送方用私钥对会话密钥进行签名
|
||||
- 接收方使用发送方公钥验证签名并解密数据
|
||||
|
||||
---
|
||||
|
||||
## 依赖库
|
||||
|
||||
```python
|
||||
from appPublic.rsawrap import RSA
|
||||
from appPublic.rc4 import RC4
|
||||
try:
|
||||
import ujson as json
|
||||
except ImportError:
|
||||
import json
|
||||
import random
|
||||
```
|
||||
|
||||
- `appPublic.rsawrap.RSA`: 提供 RSA 公私钥加解密和数字签名功能。
|
||||
- `appPublic.rc4.RC4`: 实现 RC4 流密码算法,用于高效加密大数据。
|
||||
- `ujson`: 可选高性能 JSON 库;若不可用则回退至标准库 `json`。
|
||||
- `random`: 用于生成随机会话密钥。
|
||||
|
||||
---
|
||||
|
||||
## 核心类说明
|
||||
|
||||
### 1. `DeliverPacket`
|
||||
|
||||
封装要传输的数据包结构,包含发送者信息及加密三要素:密文、加密后的会话密钥、签名。
|
||||
|
||||
#### 属性
|
||||
| 字段名 | 类型 | 描述 |
|
||||
|----------|--------|------|
|
||||
| `sender` | str | 发送方标识 ID |
|
||||
| `c` | str | 使用 RC4 加密后的密文 |
|
||||
| `k` | str | 使用对方公钥加密的 RC4 会话密钥 |
|
||||
| `s` | str | 会话密钥的数字签名(由发送方私钥签名) |
|
||||
|
||||
#### 方法
|
||||
|
||||
##### `__init__(self, sender, c, k, s)`
|
||||
初始化数据包对象。
|
||||
|
||||
参数:
|
||||
- `sender` (str): 发送方 ID
|
||||
- `c` (str): 密文
|
||||
- `k` (str): 加密后的会话密钥
|
||||
- `s` (str): 数字签名
|
||||
|
||||
##### `pack(self)`
|
||||
将当前对象序列化为 JSON 字符串。
|
||||
|
||||
返回值:
|
||||
- `str`: JSON 格式的字符串表示
|
||||
|
||||
示例输出:
|
||||
```json
|
||||
{
|
||||
"sender": "john",
|
||||
"c": "aB3x...",
|
||||
"k": "mN9y...",
|
||||
"s": "zX8v..."
|
||||
}
|
||||
```
|
||||
|
||||
##### `unpack(self, body)`
|
||||
从 JSON 字符串反序列化填充对象字段。
|
||||
|
||||
参数:
|
||||
- `body` (str): JSON 格式字符串
|
||||
|
||||
⚠️ 注意:代码中存在 bug —— 访问 `d.sender` 而非 `d['sender']`,应统一使用字典访问方式。
|
||||
|
||||
修复建议:
|
||||
```python
|
||||
self.sender = d['sender']
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. `RSAPeer`
|
||||
|
||||
代表一个支持加密通信的对等节点(Peer),具备加密发送和解密接收的能力。
|
||||
|
||||
#### 构造函数:`__init__(self, myid, myPrikey, peerPubKey=None)`
|
||||
|
||||
参数:
|
||||
- `myid` (str): 当前节点的唯一标识(如用户名或公钥指纹)
|
||||
- `myPrikey` (str): 自己的 **私钥**
|
||||
- `peerPubKey` (str, optional): 对方的 **公钥**;若未指定,则需动态获取(见 `getPeerPublicKey`)
|
||||
|
||||
内部初始化:
|
||||
- `self.rsa = RSA()`:创建 RSA 工具实例
|
||||
|
||||
#### 私有方法:`_genSystematicKey(self)`
|
||||
|
||||
生成长度在 10~15 之间的随机字符串作为 RC4 会话密钥。
|
||||
|
||||
字符集包含:
|
||||
```
|
||||
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890~!@#$%^&*
|
||||
```
|
||||
|
||||
返回值:
|
||||
- `str`: 随机生成的密钥字符串
|
||||
|
||||
> ⚠️ 名称 `_genSystematicKey` 存疑,“systematic” 不符合语义,建议改为 `_generateSessionKey`
|
||||
|
||||
#### 公共方法:`encode(self, text)`
|
||||
|
||||
加密一段明文,并打包成安全数据包。
|
||||
|
||||
##### 参数
|
||||
- `text` (str): 待加密的原始文本
|
||||
|
||||
##### 处理流程
|
||||
1. 构造内部数据结构:`{"id": self.myid, "data": text}`
|
||||
2. 将其 JSON 序列化为明文
|
||||
3. 调用 `_genSystematicKey()` 生成 RC4 会话密钥 `sk`
|
||||
4. 使用 `sk` 初始化 RC4 加密器,加密明文 → 得到密文 `c`
|
||||
5. 使用自己的私钥 `mypri` 对 `sk` 进行签名 → 得到签名 `s`
|
||||
6. 使用对方公钥 `peerpub` 加密 `sk` → 得到加密后的密钥 `k`
|
||||
7. 组装最终数据包 `{c, k, s}` 并转为 JSON 返回
|
||||
|
||||
##### 返回值
|
||||
- `str`: JSON 格式的加密数据包
|
||||
- 若 `peerPubKey` 未设置,则返回 `None`
|
||||
|
||||
##### 示例输出
|
||||
```json
|
||||
{
|
||||
"c": "eWVpaGVsbG9fd29ybGQ=",
|
||||
"k": "AQIDBAUGBwgJCgsMDQ4PEA==",
|
||||
"s": "ZGFzaGluZ3FpYW5nLmRldg=="
|
||||
}
|
||||
```
|
||||
|
||||
#### 公共方法:`decode(self, body)`
|
||||
|
||||
解密来自对端的加密数据包。
|
||||
|
||||
##### 参数
|
||||
- `body` (str): 接收到的 JSON 加密数据包
|
||||
|
||||
##### 处理流程
|
||||
1. 解析 JSON 得到 `c`, `k`, `s`
|
||||
2. 使用自己私钥解密 `k` → 恢复会话密钥 `sk`
|
||||
3. 使用 `sk` 初始化 RC4 解密器,解密 `c` → 得到中间 JSON 文本
|
||||
4. 反序列化得到内部数据 `{"id": sender_id, "data": payload}`
|
||||
5. 验证签名:
|
||||
- 若已知对方公钥:直接调用 `rsa.check_sign(peerpub, sk, signature)`
|
||||
- 否则调用 `getPeerPublicKey(id)` 动态获取公钥后再验证
|
||||
6. 验签失败返回 `None`,成功则返回原始 `payload`
|
||||
|
||||
##### 返回值
|
||||
- `str`: 解密后的原始数据内容
|
||||
- `None`: 解密或验签失败
|
||||
|
||||
> ✅ 支持两种模式:
|
||||
> - 固定对端公钥(预配置)
|
||||
> - 动态查询公钥(适用于去中心化场景)
|
||||
|
||||
---
|
||||
|
||||
## 安全机制详解
|
||||
|
||||
| 安全目标 | 实现方式 |
|
||||
|----------------|---------|
|
||||
| **机密性** | 使用 RC4 对称加密数据,会话密钥由 RSA 非对称加密保护 |
|
||||
| **完整性** | 会话密钥附带数字签名,防止篡改 |
|
||||
| **身份认证** | 签名只能由持有私钥的一方生成,接收方可通过公钥验证来源 |
|
||||
| **前向安全性** | 每次通信使用新生成的随机会话密钥 |
|
||||
|
||||
---
|
||||
|
||||
## 使用示例(Main 测试代码分析)
|
||||
|
||||
```python
|
||||
if __name__ == '__main__':
|
||||
r = RSA()
|
||||
mary_pri = r.create_privatekey()
|
||||
mary_pub = r.create_publickey(mary_pri)
|
||||
|
||||
john_pri = r.create_privatekey()
|
||||
john_pub = r.create_publickey(john_pri)
|
||||
|
||||
# 注意:此处参数顺序错误!
|
||||
john_rp = RSAPeer(john_pri, mary_pub) # ❌ 应该是 (myid, myPrikey, peerPubKey)
|
||||
mary_rp = RSAPeer(mary_pri, john_pub)
|
||||
|
||||
txt = '''hello python ...'''
|
||||
c = john_rp.encode(txt)
|
||||
newtxt = mary_rp.decode(c)
|
||||
|
||||
print(txt)
|
||||
print('<===>')
|
||||
print(c)
|
||||
print('<===>')
|
||||
print(newtxt)
|
||||
```
|
||||
|
||||
### ❗ Bug 提示
|
||||
|
||||
构造函数调用错误:
|
||||
|
||||
```python
|
||||
RSAPeer(john_pri, mary_pub)
|
||||
```
|
||||
|
||||
但定义是:
|
||||
|
||||
```python
|
||||
def __init__(self, myid, myPrikey, peerPubKey=None):
|
||||
```
|
||||
|
||||
所以正确写法应该是:
|
||||
|
||||
```python
|
||||
john_rp = RSAPeer("john", john_pri, mary_pub)
|
||||
mary_rp = RSAPeer("mary", mary_pri, john_pub)
|
||||
```
|
||||
|
||||
否则 `myid` 被误设为私钥内容,会导致后续编码异常。
|
||||
|
||||
---
|
||||
|
||||
## 建议改进点
|
||||
|
||||
| 问题 | 建议 |
|
||||
|------|------|
|
||||
| `DeliverPacket.unpack()` 中 `d.sender` 写法错误 | 改为 `d['sender']` |
|
||||
| `_genSystematicKey` 名称不准确 | 改为 `_generateSessionKey` |
|
||||
| 构造函数参数顺序易错 | 建议使用关键字参数或重命名参数 |
|
||||
| 缺少异常处理 | 增加 try-except 包裹关键操作 |
|
||||
| RC4 已被认为不安全(尤其在 TLS 中弃用) | 在生产环境中考虑替换为 AES-GCM 或 ChaCha20-Poly1305 |
|
||||
| 无 Base64 编码处理 | 若底层 API 要求二进制安全传输,应对 `c`, `k`, `s` 做 base64 编码 |
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
该模块实现了轻量级的安全通信协议,适合用于小型系统或 IoT 设备间的点对点加密通信。结合了非对称加密的身份认证能力和对称加密的效率优势,结构清晰,易于扩展。
|
||||
|
||||
### 适用场景
|
||||
- UDP 对等通信(如文档中提到的 UDP 协议)
|
||||
- 分布式日志传输
|
||||
- 内部微服务间安全信道
|
||||
- 即时通讯基础组件
|
||||
|
||||
---
|
||||
|
||||
## 版本信息
|
||||
|
||||
- Python 版本:≥ 3.6(兼容性良好)
|
||||
- 第三方依赖:`ujson`(可选)、自定义 `RSA` 与 `RC4` 模块
|
||||
- 许可协议:请参考项目 LICENSE 文件
|
||||
|
||||
---
|
||||
|
||||
> 📌 文档维护建议:配合单元测试与接口文档进一步完善健壮性描述。
|
||||
389
aidocs/rsa_key_rw.md
Normal file
389
aidocs/rsa_key_rw.md
Normal file
@ -0,0 +1,389 @@
|
||||
# RSA 密钥管理与签名验证工具技术文档
|
||||
|
||||
本项目提供一个基于 `cryptography` 库的轻量级 RSA 加密、解密、签名与验证功能封装,支持多种密钥格式(PEM、PKCS#1、PKCS#8、OpenSSH)的加载与写入。适用于需要安全地处理 RSA 公私钥及数字签名的应用场景。
|
||||
|
||||
---
|
||||
|
||||
## 📦 依赖库
|
||||
|
||||
```bash
|
||||
pip install cryptography
|
||||
```
|
||||
|
||||
> **注意**:`cryptography` 是一个使用 CFFI 绑定 OpenSSL 的密码学库,需确保系统中已安装必要的编译工具或预编译包。
|
||||
|
||||
---
|
||||
|
||||
## 🧩 模块功能概览
|
||||
|
||||
| 功能 | 描述 |
|
||||
|------|------|
|
||||
| `_load_private_key` | 从文件加载私钥(支持 PEM 和 OpenSSH 格式) |
|
||||
| `_load_public_key` | 从文件加载公钥(支持 PEM 和 OpenSSH 格式) |
|
||||
| `_write_private_key` | 将私钥写入文件(支持 PKCS#1, PKCS#8, OpenSSH 格式) |
|
||||
| `_write_public_key` | 将公钥写入文件(支持 PEM, OpenSSH 格式) |
|
||||
| `_sign` / `_verify` | 使用私钥签名和公钥验证数据(SHA256 + PKCS1v15) |
|
||||
| `RSAer` 类 | 面向对象接口,集成密钥生成、读写、签名/验证等功能 |
|
||||
|
||||
---
|
||||
|
||||
## 🔐 私钥操作
|
||||
|
||||
### `_load_private_key(filepath: str, password: bytes = None) → RSAPrivateKey`
|
||||
|
||||
从指定路径加载私钥文件,自动识别格式并返回对应的 `RSAPrivateKey` 对象。
|
||||
|
||||
#### 参数:
|
||||
- `filepath` (str): 私钥文件路径。
|
||||
- `password` (bytes, optional): 解密加密私钥所需的口令(字节类型),若未加密可省略。
|
||||
|
||||
#### 支持格式:
|
||||
| 开头标识 | 格式说明 |
|
||||
|--------|---------|
|
||||
| `-----BEGIN OPENSSH PRIVATE KEY-----` | OpenSSH 私钥格式 |
|
||||
| `-----BEGIN RSA PRIVATE KEY-----` 或 `-----BEGIN PRIVATE KEY-----` | PEM 编码的私钥(PKCS#1 或 PKCS#8) |
|
||||
|
||||
#### 返回值:
|
||||
- `RSAPrivateKey`: 成功时返回私钥对象。
|
||||
|
||||
#### 异常:
|
||||
- `ValueError`: 不支持的私钥格式。
|
||||
- `TypeError`: 密码错误或格式损坏。
|
||||
|
||||
#### 示例:
|
||||
```python
|
||||
private_key = _load_private_key("id_rsa", password=b"mypass")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `_write_private_key(key, filepath, fmt="pkcs8", password: bytes = None)`
|
||||
|
||||
将私钥对象写入文件,支持加密保存。
|
||||
|
||||
#### 参数:
|
||||
- `key` (`RSAPrivateKey`): 要写入的私钥对象。
|
||||
- `filepath` (str): 输出文件路径。
|
||||
- `fmt` (str): 输出格式,可选 `"pkcs8"`(默认)、`"pkcs1"`、`"openssh"`。
|
||||
- `password` (bytes, optional): 若提供,则使用最佳可用算法加密私钥。
|
||||
|
||||
#### 支持格式:
|
||||
| `fmt` 值 | 格式描述 |
|
||||
|--------|----------|
|
||||
| `pkcs8` | PEM 编码的 PKCS#8 格式(推荐) |
|
||||
| `pkcs1` | PEM 编码的传统 RSA 私钥(仅限 RSA) |
|
||||
| `openssh` | OpenSSH 原生私钥格式(兼容 OpenSSH 工具链) |
|
||||
|
||||
#### 注意事项:
|
||||
- 当 `password` 不为 `None` 时,私钥将被加密存储。
|
||||
- 所有输出均为二进制模式写入。
|
||||
|
||||
#### 示例:
|
||||
```python
|
||||
_write_private_key(private_key, "private_encrypted.pem", fmt="pkcs8", password=b"secret123")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔍 公钥操作
|
||||
|
||||
### `_load_public_key(filepath: str) → RSAPublicKey`
|
||||
|
||||
从文件加载公钥,自动识别格式。
|
||||
|
||||
#### 参数:
|
||||
- `filepath` (str): 公钥文件路径。
|
||||
|
||||
#### 支持格式:
|
||||
| 文件内容开头 | 格式 |
|
||||
|-------------|------|
|
||||
| `ssh-rsa`, `ssh-ed25519` 等 | OpenSSH 公钥格式 |
|
||||
| `-----BEGIN PUBLIC KEY-----` | PEM 编码的标准公钥(SubjectPublicKeyInfo) |
|
||||
|
||||
#### 返回值:
|
||||
- `RSAPublicKey`: 成功时返回公钥对象。
|
||||
|
||||
#### 异常:
|
||||
- `ValueError`: 不支持的公钥格式。
|
||||
|
||||
#### 示例:
|
||||
```python
|
||||
public_key = _load_public_key("id_rsa.pub")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `_write_public_key(public_key, filepath, fmt="pem")`
|
||||
|
||||
将公钥对象写入文件。
|
||||
|
||||
#### 参数:
|
||||
- `public_key` (`RSAPublicKey`): 公钥对象。
|
||||
- `filepath` (str): 输出文件路径。
|
||||
- `fmt` (str): 输出格式,支持 `"pem"`(默认)或 `"openssh"`。
|
||||
|
||||
#### 支持格式:
|
||||
| `fmt` | 输出格式 |
|
||||
|-------|---------|
|
||||
| `pem` | PEM 编码的 X.509 公钥(标准格式) |
|
||||
| `openssh` | OpenSSH 风格的单行公钥(如用于 `~/.ssh/authorized_keys`) |
|
||||
|
||||
#### 示例:
|
||||
```python
|
||||
_write_public_key(public_key, "public.pem", fmt="pem")
|
||||
_write_public_key(public_key, "id_rsa_openssh.pub", fmt="openssh")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✍️ 数字签名与验证
|
||||
|
||||
### `_sign(prikey, data: bytes) → bytes`
|
||||
|
||||
使用私钥对数据进行 SHA256 + PKCS1v15 签名。
|
||||
|
||||
#### 参数:
|
||||
- `prikey` (`RSAPrivateKey`): 有效的 RSA 私钥。
|
||||
- `data` (bytes): 待签名的原始数据。
|
||||
|
||||
#### 返回值:
|
||||
- `bytes`: 签名结果(二进制)。
|
||||
|
||||
#### 算法细节:
|
||||
- 哈希算法:`SHA-256`
|
||||
- 填充方式:`PKCS1v15`
|
||||
|
||||
> ⚠️ 注:也可替换为更现代的 `PSS` 填充以增强安全性。
|
||||
|
||||
#### 示例:
|
||||
```python
|
||||
signature = _sign(private_key, b"Hello World")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `_verify(pubkey, data: bytes, signature: bytes) → bool`
|
||||
|
||||
验证数据签名是否有效。
|
||||
|
||||
#### 参数:
|
||||
- `pubkey` (`RSAPublicKey`): 对应的公钥。
|
||||
- `data` (bytes): 原始数据。
|
||||
- `signature` (bytes): 签名值。
|
||||
|
||||
#### 返回值:
|
||||
- `True`: 验证成功。
|
||||
- `False`: 验证失败(包括异常捕获)。
|
||||
|
||||
#### 内部机制:
|
||||
- 使用与 `_sign` 相同的参数(`PKCS1v15`, `SHA256`)进行校验。
|
||||
- 捕获 `InvalidSignature` 异常并返回 `False`。
|
||||
|
||||
#### 示例:
|
||||
```python
|
||||
is_valid = _verify(public_key, b"Hello World", signature)
|
||||
print(is_valid) # True or False
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧱 RSAer 类:高级封装接口
|
||||
|
||||
`RSAer` 提供面向对象的方式统一管理密钥与操作。
|
||||
|
||||
### 初始化
|
||||
```python
|
||||
rsa_tool = RSAer()
|
||||
```
|
||||
|
||||
属性初始化为空:
|
||||
- `self.prikey = None`
|
||||
- `self.pubkey = None`
|
||||
|
||||
---
|
||||
|
||||
### `.create_key(keylen=2048)`
|
||||
|
||||
生成新的 RSA 密钥对。
|
||||
|
||||
#### 参数:
|
||||
- `keylen` (int): 密钥长度,默认 `2048`,建议至少 2048 位。
|
||||
|
||||
#### 生成规则:
|
||||
- 公共指数固定为 `65537`(Fermat Prime,行业标准)
|
||||
- 使用安全随机源生成大素数
|
||||
|
||||
#### 示例:
|
||||
```python
|
||||
rsa_tool.create_key(2048)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `.write_private_key(filepath, fmt="pkcs8", password=None)`
|
||||
|
||||
导出私钥到文件。
|
||||
|
||||
#### 参数:
|
||||
- `filepath`: 输出路径
|
||||
- `fmt`: 格式(`pkcs8`, `pkcs1`, `openssh`)
|
||||
- `password`: 可选加密口令(bytes)
|
||||
|
||||
> ❗ 抛出异常:若 `self.prikey` 为 `None`
|
||||
|
||||
#### 示例:
|
||||
```python
|
||||
rsa_tool.write_private_key("mykey.pem", fmt="pkcs8", password=b"pass123")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `.write_public_key(filepath, fmt="pem")`
|
||||
|
||||
导出公钥到文件。
|
||||
|
||||
#### 参数:
|
||||
- `filepath`: 输出路径
|
||||
- `fmt`: `pem` 或 `openssh`
|
||||
|
||||
> ⚠️ 自动从私钥推导公钥(如果尚未设置)
|
||||
|
||||
#### 示例:
|
||||
```python
|
||||
rsa_tool.write_public_key("mykey.pub", fmt="openssh")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `.load_private_key(filepath, password=None)`
|
||||
|
||||
加载已有私钥。
|
||||
|
||||
#### 示例:
|
||||
```python
|
||||
rsa_tool.load_private_key("private.pem", password=None)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `.load_public_key(filepath)`
|
||||
|
||||
加载已有公钥。
|
||||
|
||||
#### 示例:
|
||||
```python
|
||||
rsa_tool.load_public_key("public.pem")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `.sign(data: bytes) → bytes`
|
||||
|
||||
使用当前私钥签名数据。
|
||||
|
||||
#### 示例:
|
||||
```python
|
||||
sig = rsa_tool.sign(b"Important message")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `.verify(data: bytes, signature: bytes) → bool`
|
||||
|
||||
使用当前公钥验证签名。
|
||||
|
||||
#### 示例:
|
||||
```python
|
||||
valid = rsa_tool.verify(b"Important message", sig)
|
||||
assert valid == True
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### ❌ 待实现方法
|
||||
以下两个方法在代码中声明但未实现:
|
||||
|
||||
```python
|
||||
def encode(self, data):
|
||||
def decode(self, data):
|
||||
```
|
||||
|
||||
> 后续可根据需求补充 RSA 加解密功能(通常用于小数据块加密,如密钥传输)。
|
||||
> 推荐使用 `padding.OAEP` 或 `PKCS1v15` 进行加密填充。
|
||||
|
||||
---
|
||||
|
||||
## 🧪 示例用法(主程序)
|
||||
|
||||
```python
|
||||
if __name__ == '__main__':
|
||||
# 示例:加载私钥和公钥
|
||||
private_key = _load_private_key("path/to/private_key.pem", password=None)
|
||||
public_key = _load_public_key("path/to/public_key.pub")
|
||||
|
||||
print("私钥类型:", type(private_key))
|
||||
print("公钥类型:", type(public_key))
|
||||
|
||||
# 写出不同格式的私钥
|
||||
_write_private_key(private_key, "private_pkcs8.pem", fmt="pkcs8")
|
||||
_write_private_key(private_key, "private_pkcs1.pem", fmt="pkcs1")
|
||||
_write_private_key(private_key, "private_openssh", fmt="openssh")
|
||||
|
||||
# 写出不同格式的公钥
|
||||
_write_public_key(public_key, "public.pem", fmt="pem")
|
||||
_write_public_key(public_key, "id_rsa.pub", fmt="openssh")
|
||||
|
||||
# 签名与验证
|
||||
data = b"Secure message"
|
||||
signature = _sign(private_key, data)
|
||||
is_valid = _verify(public_key, data, signature)
|
||||
print("Signature valid?", is_valid)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ 安全建议
|
||||
|
||||
1. **密钥保护**:
|
||||
- 私钥应始终加密存储(使用强密码)。
|
||||
- 权限设为 `600`(Linux/macOS)防止他人读取。
|
||||
|
||||
2. **签名算法选择**:
|
||||
- 推荐使用 `PSS` 填充替代 `PKCS1v15` 以获得更强的安全性。
|
||||
- 示例修改:
|
||||
```python
|
||||
padding.PSS(
|
||||
mgf=padding.MGF1(hashes.SHA256()),
|
||||
salt_length=padding.PSS.MAX_LENGTH
|
||||
)
|
||||
```
|
||||
|
||||
3. **密钥长度**:
|
||||
- 至少使用 2048 位,推荐 3072 或 4096 以应对未来威胁。
|
||||
|
||||
4. **避免直接加密大数据**:
|
||||
- RSA 不适合直接加密大量数据。
|
||||
- 应结合 AES 等对称加密使用“混合加密”方案。
|
||||
|
||||
---
|
||||
|
||||
## 📚 参考资料
|
||||
|
||||
- [cryptography 官方文档](https://cryptography.io/en/latest/)
|
||||
- [OpenSSH Key Format](https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key)
|
||||
- RFC 8017 — PKCS #1 v2.2
|
||||
- NIST FIPS 186-4 — Digital Signature Standard (DSS)
|
||||
|
||||
---
|
||||
|
||||
## 🧾 版本信息
|
||||
|
||||
- **语言**: Python 3.6+
|
||||
- **库版本要求**: `cryptography >= 3.0`
|
||||
- **许可证**: MIT(示例代码,实际请根据项目授权)
|
||||
|
||||
---
|
||||
|
||||
> ✅ 本文档最后更新:2025年4月5日
|
||||
> 作者:AI Assistant
|
||||
> 用途:开发参考、API 文档、安全实践指南
|
||||
293
aidocs/rsawrap.md
Normal file
293
aidocs/rsawrap.md
Normal file
@ -0,0 +1,293 @@
|
||||
# RSA 加密工具类技术文档
|
||||
|
||||
```markdown
|
||||
# RSA 加密与签名工具类(`RSA`)
|
||||
|
||||
基于 `python-rsa` 库封装的高级 RSA 加密、解密、签名与验证工具类,支持密钥生成、保存、读取、文本编解码、数据加解密及数字签名功能。
|
||||
|
||||
---
|
||||
|
||||
## 概述
|
||||
|
||||
本模块提供了一个简洁易用的 `RSA` 类,用于执行以下操作:
|
||||
|
||||
- 生成 RSA 公私钥对
|
||||
- 保存和读取公私钥文件(PKCS#1 格式)
|
||||
- 使用公钥加密、私钥解密(支持字符串和字节流)
|
||||
- 数字签名(SHA-1 哈希)与验证
|
||||
- 支持自定义字符编码(默认:`iso8859`)
|
||||
|
||||
> ⚠️ **注意**:当前签名使用的是 SHA-1 算法,安全性较低,仅适用于测试或兼容旧系统。生产环境建议升级为 SHA-256 或更高强度算法。
|
||||
|
||||
---
|
||||
|
||||
## 依赖库
|
||||
|
||||
- [`rsa`](https://pypi.org/project/rsa/):纯 Python 实现的 RSA 加密库
|
||||
|
||||
安装方式:
|
||||
```bash
|
||||
pip install rsa
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 类定义
|
||||
|
||||
### `class RSA(keylength=4096, coding='iso8859')`
|
||||
|
||||
#### 参数说明:
|
||||
| 参数 | 类型 | 默认值 | 描述 |
|
||||
|------|------|--------|------|
|
||||
| `keylength` | int | `4096` | RSA 密钥长度(单位:bit),常见值有 1024、2048、4096 |
|
||||
| `coding` | str | `'iso8859'` | 字符串编解码格式,用于文本与字节之间的转换 |
|
||||
|
||||
> 🔒 推荐密钥长度至少为 2048 bit;`iso8859` 编码不支持中文,如需处理中文请设为 `'utf-8'`
|
||||
|
||||
---
|
||||
|
||||
## 方法列表
|
||||
|
||||
---
|
||||
|
||||
### 初始化与配置
|
||||
|
||||
#### `__init__(self, keylength=4096, coding='iso8859')`
|
||||
初始化 RSA 工具实例,设置默认密钥长度和字符编码。
|
||||
|
||||
---
|
||||
|
||||
### 密钥管理
|
||||
|
||||
#### `create_privatekey(self, keylength=4096) -> rsa.PrivateKey`
|
||||
生成一个新的私钥。
|
||||
|
||||
- **参数**:
|
||||
- `keylength` (int): 密钥位数
|
||||
- **返回**:`rsa.PrivateKey` 对象
|
||||
- **示例**:
|
||||
```python
|
||||
pri_key = rsa_instance.create_privatekey(2048)
|
||||
```
|
||||
|
||||
#### `create_publickey(self, private_key: rsa.PrivateKey) -> rsa.PublicKey`
|
||||
从私钥中提取对应的公钥。
|
||||
|
||||
- **参数**:
|
||||
- `private_key`: 私钥对象
|
||||
- **返回**:`rsa.PublicKey` 对象
|
||||
|
||||
#### `write_privatekey(self, private_key, fname, password=None)`
|
||||
将私钥写入文件(未加密存储,`password` 参数暂未实现加密)。
|
||||
|
||||
- **参数**:
|
||||
- `private_key`: 私钥对象
|
||||
- `fname` (str): 文件路径
|
||||
- `password` (str or None): (预留)未来可支持密码保护
|
||||
- **文件格式**:PEM 编码的 PKCS#1 私钥(`.pem` 可读,但此处以二进制保存)
|
||||
|
||||
#### `write_publickey(self, public_key, fname)`
|
||||
将公钥写入文件。
|
||||
|
||||
- **参数**:
|
||||
- `public_key`: 公钥对象
|
||||
- `fname` (str): 文件路径
|
||||
|
||||
#### `read_privatekey(self, fname, password=None) -> rsa.PrivateKey`
|
||||
从文件读取私钥。
|
||||
|
||||
- **参数**:
|
||||
- `fname` (str): 文件路径
|
||||
- `password` (str or None): (预留)
|
||||
- **返回**:私钥对象
|
||||
- **异常处理**:文件不存在或格式错误会抛出异常
|
||||
|
||||
#### `read_publickey(self, fname) -> rsa.PublicKey`
|
||||
从文件读取公钥。
|
||||
|
||||
- **参数**:
|
||||
- `fname` (str): 文件路径
|
||||
- **返回**:公钥对象
|
||||
|
||||
#### `publickeyText(self, public_key) -> str`
|
||||
将公钥序列化为文本字符串(Base64 编码 + PEM 结构,经指定编码转为 str)。
|
||||
|
||||
- **用途**:便于在网络上传输或嵌入 JSON 配置
|
||||
- **返回**:编码后的字符串
|
||||
|
||||
#### `publickeyFromText(self, text) -> rsa.PublicKey`
|
||||
从文本字符串恢复公钥对象。
|
||||
|
||||
- **参数**:
|
||||
- `text` (str): 由 `publickeyText()` 生成的字符串
|
||||
- **返回**:公钥对象
|
||||
|
||||
---
|
||||
|
||||
### 加解密操作
|
||||
|
||||
#### `encode_bytes(self, public_key, bdata) -> bytes`
|
||||
使用公钥加密原始字节数据。
|
||||
|
||||
- **参数**:
|
||||
- `public_key`: 公钥对象
|
||||
- `bdata` (bytes): 明文字节
|
||||
- **返回**:密文字节
|
||||
|
||||
#### `decode_bytes(self, private_key, bdata) -> bytes`
|
||||
使用私钥解密密文字节。
|
||||
|
||||
- **参数**:
|
||||
- `private_key`: 私钥对象
|
||||
- `bdata` (bytes): 密文字节
|
||||
- **返回**:明文字节
|
||||
|
||||
#### `encode(self, public_key, text) -> str`
|
||||
加密字符串(自动编码 → 加密 → 解码输出为字符串)。
|
||||
|
||||
- **参数**:
|
||||
- `public_key`: 公钥对象
|
||||
- `text` (str): 明文字符串
|
||||
- **返回**:加密后的内容(字符串形式)
|
||||
- **流程**:`text → encode(coding) → encrypt → decode(coding)`
|
||||
|
||||
> ❗ 输出是“可打印字符串”,但实际是乱码。传输时建议改用 Base64 编码避免损坏
|
||||
|
||||
#### `decode(self, private_key, cipher) -> str`
|
||||
解密字符串(反向过程)。
|
||||
|
||||
- **参数**:
|
||||
- `private_key`: 私钥对象
|
||||
- `cipher` (str): 加密字符串
|
||||
- **返回**:原始明文字符串
|
||||
|
||||
---
|
||||
|
||||
### 数字签名与验证
|
||||
|
||||
#### `sign_bdata(self, private_key, data_to_sign) -> bytes`
|
||||
对字节数据进行 SHA-1 数字签名。
|
||||
|
||||
- **参数**:
|
||||
- `private_key`: 私钥对象
|
||||
- `data_to_sign` (bytes): 要签名的数据
|
||||
- **返回**:签名字节
|
||||
|
||||
#### `sign(self, private_key, message) -> str`
|
||||
对字符串消息进行签名,并返回字符串形式的签名。
|
||||
|
||||
- **参数**:
|
||||
- `private_key`: 私钥对象
|
||||
- `message` (str): 消息文本
|
||||
- **返回**:签名字符串(经 `.coding` 解码)
|
||||
|
||||
#### `check_sign_bdata(self, public_key, bdata, sign) -> bool`
|
||||
验证字节数据的签名是否有效。
|
||||
|
||||
- **参数**:
|
||||
- `public_key`: 公钥对象
|
||||
- `bdata` (bytes): 原始数据
|
||||
- `sign` (bytes): 签名字节
|
||||
- **返回**:`True` if valid, else `False`
|
||||
- **内部逻辑**:
|
||||
- 调用 `rsa.verify()` 返回哈希算法名(如 `'SHA-1'`)
|
||||
- 若匹配则返回 `True`,否则打印日志并返回 `False`
|
||||
- 异常捕获并返回 `False`
|
||||
|
||||
#### `check_sign(self, public_key, plain_text, signature) -> bool`
|
||||
验证字符串消息及其签名。
|
||||
|
||||
- **参数**:
|
||||
- `public_key`: 公钥对象
|
||||
- `plain_text` (str): 原文
|
||||
- `signature` (str): 签名字符串
|
||||
- **返回**:验证结果布尔值
|
||||
- **流程**:`plain_text → bytes`, `signature → bytes` → 调用 `check_sign_bdata`
|
||||
|
||||
---
|
||||
|
||||
## 使用示例
|
||||
|
||||
```python
|
||||
from your_module import RSA
|
||||
import os
|
||||
|
||||
# 创建实例
|
||||
r = RSA(keylength=2048, coding='utf-8')
|
||||
|
||||
# 生成密钥对
|
||||
pri_key = r.create_privatekey()
|
||||
pub_key = r.create_publickey(pri_key)
|
||||
|
||||
# 保存密钥到文件
|
||||
r.write_privatekey(pri_key, 'private.pem')
|
||||
r.write_publickey(pub_key, 'public.pem')
|
||||
|
||||
# 重新加载密钥
|
||||
loaded_pri = r.read_privatekey('private.pem')
|
||||
loaded_pub = r.read_publickey('public.pem')
|
||||
|
||||
# 加密解密测试
|
||||
message = "Hello, RSA!"
|
||||
cipher = r.encode(pub_key, message)
|
||||
decrypted = r.decode(loaded_pri, cipher)
|
||||
print("Decrypted:", decrypted) # Should be same as original
|
||||
|
||||
# 签名与验证
|
||||
signature = r.sign(pri_key, message)
|
||||
is_valid = r.check_sign(pub_key, message, signature)
|
||||
print("Signature valid?", is_valid) # True
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **编码问题**:
|
||||
- 默认编码 `iso8859` 不支持中文。
|
||||
- 如需处理 Unicode 文本(如中文),建议初始化时设置 `coding='utf-8'`。
|
||||
|
||||
2. **安全性提醒**:
|
||||
- 当前签名采用 **SHA-1**,存在碰撞风险,不适合高安全场景。
|
||||
- 私钥文件目前以明文保存,无密码保护,请确保文件权限安全。
|
||||
|
||||
3. **性能限制**:
|
||||
- RSA 加密数据长度受限于密钥大小(例如 2048bit 最多加密 ~245 字节)。
|
||||
- 大量数据应结合 AES 使用(即混合加密体系)。
|
||||
|
||||
4. **异常处理**:
|
||||
- 所有读写文件操作均可能引发 `IOError` 或 `FileNotFoundError`。
|
||||
- 解密或验证失败会返回 `False` 并打印错误信息,建议在生产环境中优化日志级别。
|
||||
|
||||
---
|
||||
|
||||
## 主程序测试逻辑(`if __name__ == '__main__':`)
|
||||
|
||||
该部分包含一个持续增长字符串长度的自动化测试循环:
|
||||
|
||||
- 不断增加明文长度(从 100 字符开始,每次 +1)
|
||||
- 测试加解密一致性
|
||||
- 测试跨密钥签名与验证功能
|
||||
- 输出包括:
|
||||
- 明文长度
|
||||
- 加密后密文长度
|
||||
- 是否能正确还原
|
||||
- 签名长度及验证结果
|
||||
|
||||
可用于压力测试或边界探测。
|
||||
|
||||
> 示例路径拼接可能需要调整以适应运行环境。
|
||||
|
||||
---
|
||||
|
||||
## 版本信息
|
||||
|
||||
- **作者**:Auto-generated Documentation
|
||||
- **语言**:Python 3.x
|
||||
- **库依赖**:`rsa>=4.0`
|
||||
- **许可证**:MIT(假设)
|
||||
|
||||
---
|
||||
```
|
||||
|
||||
> ✅ 提示:若用于正式项目,请补充单元测试、异常日志分级、Base64 编码接口以及更安全的签名算法扩展。
|
||||
138
aidocs/set_fgcolor.md
Normal file
138
aidocs/set_fgcolor.md
Normal file
@ -0,0 +1,138 @@
|
||||
# Kivy 颜色对比度与前景色自动选择工具文档
|
||||
|
||||
```markdown
|
||||
# Kivy 前景色自适应选择函数技术文档
|
||||
|
||||
## 概述
|
||||
|
||||
该模块提供两个核心函数,用于在 Kivy 框架中根据背景颜色(`bgcolor`)智能选择合适的前景色(如文本颜色),以确保良好的可读性和视觉对比度。其原理基于人眼对不同颜色的感知灰度值,使用心理学标准公式计算灰度,并据此判断应使用深色或浅色前景。
|
||||
|
||||
## 编码说明
|
||||
|
||||
```python
|
||||
# -*- coding=utf-8 -*-
|
||||
```
|
||||
|
||||
源码采用 UTF-8 编码,支持中文注释和文档。
|
||||
|
||||
---
|
||||
|
||||
## 核心原理
|
||||
|
||||
### 灰度感知公式
|
||||
|
||||
人眼对不同颜色的敏感度不同,因此即使 RGB 值相同,感知亮度也不同。本模块使用标准灰度转换公式:
|
||||
|
||||
$$
|
||||
\text{灰度} = R \times 0.299 + G \times 0.587 + B \times 0.114
|
||||
$$
|
||||
|
||||
其中:
|
||||
- $ R $:红色分量(0 ~ 1)
|
||||
- $ G $:绿色分量(0 ~ 1)
|
||||
- $ B $:蓝色分量(0 ~ 1)
|
||||
|
||||
> ✅ **说明**:此权重来源于 ITU-R BT.601 标准,广泛应用于图像处理领域。
|
||||
|
||||
根据计算出的灰度值:
|
||||
- 若灰度 > `0.5`,表示背景较亮,应使用**暗色前景**;
|
||||
- 若灰度 ≤ `0.5`,表示背景较暗,应使用**亮色前景**。
|
||||
|
||||
---
|
||||
|
||||
## 函数接口
|
||||
|
||||
### `color_gray_rate(color)`
|
||||
|
||||
计算给定颜色的感知灰度值。
|
||||
|
||||
#### 参数
|
||||
| 参数名 | 类型 | 描述 |
|
||||
|--------|------|------|
|
||||
| `color` | list[float] | 颜色列表 `[R, G, B, A]` 或 `[R, G, B]`,各分量范围为 0~1 |
|
||||
|
||||
> ⚠️ 注意:Alpha 通道(透明度)不参与灰度计算。
|
||||
|
||||
#### 返回值
|
||||
- `float`:计算得到的灰度值(0 ~ 1 范围内)
|
||||
|
||||
#### 示例
|
||||
```python
|
||||
color_gray_rate([1.0, 0.0, 0.0]) # 红色 -> 返回约 0.299
|
||||
color_gray_rate([0.0, 1.0, 0.0]) # 绿色 -> 返回约 0.587
|
||||
color_gray_rate([0.0, 0.0, 1.0]) # 蓝色 -> 返回约 0.114
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `get_fgcolor_from_bgcolor(bgcolor, colors=None)`
|
||||
|
||||
根据背景色自动选择最佳前景色。
|
||||
|
||||
#### 参数
|
||||
| 参数名 | 类型 | 默认值 | 描述 |
|
||||
|--------|------|--------|------|
|
||||
| `bgcolor` | list[float] | —— | 背景颜色,格式为 `[R, G, B, A]` |
|
||||
| `colors` | list[list[float]] 或 None | `None` | 可选的两个前景候选颜色;若为 `None`,则使用内置默认颜色 |
|
||||
|
||||
#### 内置默认颜色
|
||||
- 深色前景:`[0.11, 0.11, 0.11, 1]`(接近黑色)
|
||||
- 浅色前景:`[0.89, 0.89, 0.89, 1]`(接近白色)
|
||||
|
||||
#### 返回值
|
||||
- `list[float]`:推荐使用的前景颜色(四元组 `[R, G, B, A]`)
|
||||
|
||||
#### 工作逻辑
|
||||
|
||||
1. 计算背景色的灰度值 `graylevel`
|
||||
2. 如果未传入 `colors`:
|
||||
- 若 `graylevel > 0.5`,返回深色前景
|
||||
- 否则返回浅色前景
|
||||
3. 如果传入了 `colors = [color1, color2]`:
|
||||
- 分别计算两个候选颜色的灰度值 `r1`, `r2`
|
||||
- 选择与背景灰度差异更大的那个颜色(即对比度更高)
|
||||
- 即:比较 `|graylevel - r1|` 与 `|graylevel - r2|`
|
||||
|
||||
> ✅ **优势**:支持自定义配色方案,适用于品牌色、主题色等场景。
|
||||
|
||||
#### 示例
|
||||
|
||||
```python
|
||||
# 使用默认颜色
|
||||
get_fgcolor_from_bgcolor([1.0, 1.0, 1.0]) # 白色背景 → 返回 [0.11,0.11,0.11,1]
|
||||
get_fgcolor_from_bgcolor([0.0, 0.0, 0.0]) # 黑色背景 → 返回 [0.89,0.89,0.89,1]
|
||||
|
||||
# 自定义前景颜色
|
||||
custom_colors = [
|
||||
[0.2, 0.6, 0.8, 1], # 蓝绿色
|
||||
[1.0, 0.9, 0.1, 1] # 明黄色
|
||||
]
|
||||
get_fgcolor_from_bgcolor([0.1, 0.1, 0.6], custom_colors)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 应用场景
|
||||
|
||||
- 动态 UI 主题系统(如深色/浅色模式自动切换文字颜色)
|
||||
- 标签、按钮、卡片组件的文字颜色适配
|
||||
- 数据可视化中动态调整标签颜色
|
||||
- 提升无障碍访问性(Accessibility)
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 所有颜色值需归一化到 `0~1` 范围(Kivy 要求),不可使用 `0~255` 的整数。
|
||||
2. 推荐前景色的 Alpha 通常设为 `1`(完全不透明)。
|
||||
3. 在极端中间色调(灰度 ≈ 0.5)时建议人工微调或增加容差判断。
|
||||
|
||||
---
|
||||
|
||||
## 版权与许可
|
||||
|
||||
© 2025 开源工具函数
|
||||
可用于 Kivy 项目中的颜色对比优化,欢迎自由使用与扩展。
|
||||
```
|
||||
|
||||
> 💡 **提示**:可将此文档保存为 `README.md` 或集成至 Sphinx 文档系统中。
|
||||
346
aidocs/sockPackage.md
Normal file
346
aidocs/sockPackage.md
Normal file
@ -0,0 +1,346 @@
|
||||
# Socket 通信模块技术文档
|
||||
|
||||
本项目提供了一个基于 Python 的简单 TCP 客户端/服务器通信框架,支持多线程并发处理客户端连接,并封装了后台任务调用机制。适用于本地测试、轻量级网络服务开发等场景。
|
||||
|
||||
---
|
||||
|
||||
## 目录
|
||||
|
||||
- [依赖说明](#依赖说明)
|
||||
- [核心功能概览](#核心功能概览)
|
||||
- [工具函数](#工具函数)
|
||||
- [后台任务类](#后台任务类)
|
||||
- [异常定义](#异常定义)
|
||||
- [SocketServer 类](#socketserver-类)
|
||||
- [SocketClient 类](#socketclient-类)
|
||||
- [主程序示例](#主程序示例)
|
||||
- [使用建议与注意事项](#使用建议与注意事项)
|
||||
|
||||
---
|
||||
|
||||
## 依赖说明
|
||||
|
||||
```python
|
||||
import os
|
||||
import time
|
||||
import threading
|
||||
import sys
|
||||
import socket
|
||||
```
|
||||
|
||||
所需标准库模块:
|
||||
- `socket`:用于实现 TCP 网络通信。
|
||||
- `threading`:支持多线程并发处理多个客户端连接。
|
||||
- `time`:用于延时控制和测试。
|
||||
- 其他为通用系统操作支持。
|
||||
|
||||
---
|
||||
|
||||
## 核心功能概览
|
||||
|
||||
| 组件 | 功能 |
|
||||
|------|------|
|
||||
| `get_free_local_addr()` | 获取当前机器可用的 IP 地址(通过 DNS 请求探测) |
|
||||
| `background` / `BackgroundCall()` | 异步执行函数的线程包装器 |
|
||||
| `SocketServer` | 多线程 TCP 服务器,可接受并异步处理多个客户端连接 |
|
||||
| `SocketClient` | TCP 客户端,用于连接服务器、发送和接收数据 |
|
||||
| 自定义异常 | `SocketServerError`, `SocketClientError` 提供清晰错误分类 |
|
||||
|
||||
---
|
||||
|
||||
## 工具函数
|
||||
|
||||
### `get_free_local_addr()`
|
||||
|
||||
获取本机在联网状态下对外通信所使用的 IP 地址和临时端口。
|
||||
|
||||
#### 函数签名
|
||||
```python
|
||||
def get_free_local_addr():
|
||||
```
|
||||
|
||||
#### 返回值
|
||||
- `(ip: str, port: int)`:元组形式返回本机出口 IP 和操作系统分配的临时端口号。
|
||||
|
||||
#### 实现原理
|
||||
通过创建一个 UDP 套接字连接到公共 DNS 服务器 `8.8.8.8:80`,不实际发送数据,仅用于确定本地绑定地址。
|
||||
|
||||
> ⚠️ 注意:此方法依赖外网可达性;若无网络或防火墙限制可能失败。
|
||||
|
||||
#### 示例
|
||||
```python
|
||||
ip, port = get_free_local_addr()
|
||||
print(ip) # 输出如 '192.168.1.100'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 后台任务类
|
||||
|
||||
### `class background(threading.Thread)`
|
||||
|
||||
封装一个可在后台运行的函数调用。
|
||||
|
||||
#### 属性
|
||||
| 属性名 | 类型 | 描述 |
|
||||
|-------|------|------|
|
||||
| `func` | `callable` | 要执行的目标函数 |
|
||||
| `kw` | `dict` | 关键字参数传递给目标函数 |
|
||||
|
||||
#### 方法
|
||||
##### `__init__(self, func, kw)`
|
||||
初始化线程对象。
|
||||
|
||||
- **参数**:
|
||||
- `func`: 可调用对象(函数)
|
||||
- `kw`: 字典形式的关键字参数 `{key: value}`
|
||||
|
||||
##### `run(self)`
|
||||
重写 `Thread.run()`,自动调用 `self.func(**self.kw)`。
|
||||
|
||||
---
|
||||
|
||||
### `BackgroundCall(func, datas)`
|
||||
|
||||
便捷函数:启动一个后台线程执行指定函数。
|
||||
|
||||
#### 参数
|
||||
- `func`: 待执行函数
|
||||
- `datas`: 传入该函数的关键字参数字典
|
||||
|
||||
#### 行为
|
||||
- 创建 `background` 实例
|
||||
- 调用 `.start()` 启动线程
|
||||
- 不阻塞主线程
|
||||
|
||||
#### 示例
|
||||
```python
|
||||
def echo(data):
|
||||
print("Received:", data)
|
||||
|
||||
BackgroundCall(echo, {'data': 'Hello'})
|
||||
# 在后台打印 "Received: Hello"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 异常定义
|
||||
|
||||
### `class SocketServerError(Exception)`
|
||||
表示服务器端发生的严重错误,例如绑定地址失败、未就绪运行等。
|
||||
|
||||
### `class SocketClientError(Exception)`
|
||||
表示客户端连接或通信过程中出现的问题,如无法连接、读写出错等。
|
||||
|
||||
> 使用自定义异常便于上层捕获特定错误类型进行处理。
|
||||
|
||||
---
|
||||
|
||||
## SocketServer 类
|
||||
|
||||
多线程 TCP 服务器,监听指定地址和端口,每个新连接由独立线程处理。
|
||||
|
||||
### 类定义
|
||||
```python
|
||||
class SocketServer(threading.Thread)
|
||||
```
|
||||
|
||||
继承自 `threading.Thread`,以非守护模式运行。
|
||||
|
||||
---
|
||||
|
||||
### 构造函数 `__init__(host, port, max_connect=10, callee=None)`
|
||||
|
||||
#### 参数
|
||||
| 参数 | 类型 | 默认值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| `host` | `str` | — | 绑定主机地址,如 `'localhost'` 或 `'0.0.0.0'` |
|
||||
| `port` | `int` | — | 绑定端口号 |
|
||||
| `max_connect` | `int` | `10` | 最大挂起连接数(listen 队列长度) |
|
||||
| `callee` | `callable` | `None` | 处理每个客户端连接的回调函数 |
|
||||
|
||||
#### 回调函数原型
|
||||
```python
|
||||
def handler(conn: socket.socket, addr: tuple):
|
||||
pass
|
||||
```
|
||||
其中:
|
||||
- `conn`: 客户端连接套接字
|
||||
- `addr`: 客户端地址 `(ip, port)`
|
||||
|
||||
#### 初始化行为
|
||||
- 设置守护线程为 `False`
|
||||
- 创建并尝试绑定监听套接字
|
||||
- 若成功则 `ready = True`,否则记录日志但继续构造
|
||||
|
||||
---
|
||||
|
||||
### 方法
|
||||
|
||||
#### `setSocketServer()`
|
||||
内部方法:创建并配置服务器套接字。
|
||||
|
||||
- 创建 TCP 套接字 (`AF_INET`, `SOCK_STREAM`)
|
||||
- 绑定 `(host, port)`
|
||||
- 开始监听(最大连接队列长度为 `max_c`)
|
||||
- 成功后设置 `self.ready = True`
|
||||
|
||||
> 若失败会打印错误信息但不会抛出异常(构造阶段容错)
|
||||
|
||||
#### `run()`
|
||||
线程入口函数,循环接受客户端连接。
|
||||
|
||||
- 检查是否 `ready`,否则抛出 `SocketServerError`
|
||||
- 进入无限循环等待客户端接入
|
||||
- 每次接受连接后,使用 `BackgroundCall` 将 `callee` 函数异步执行
|
||||
|
||||
> 单个连接处理完全解耦,不影响主服务循环
|
||||
|
||||
#### `stop()`
|
||||
请求停止服务器运行。
|
||||
|
||||
- 设置标志位 `keep_running = 0`
|
||||
- 下一次循环将退出 `run()` 函数
|
||||
- ⚠️ 当前已建立的连接不会被主动关闭
|
||||
|
||||
#### `callee(self, conn, addr)`(默认回显处理)
|
||||
内置默认处理函数:持续接收数据并原样返回(回显服务),直到连接断开。
|
||||
|
||||
> ❗ 存在一个拼写错误:`con.close()` 应为 `conn.close()`
|
||||
|
||||
##### 修正建议
|
||||
```python
|
||||
def callee(self, conn, addr):
|
||||
try:
|
||||
while True:
|
||||
d = conn.recv(1024)
|
||||
if not d: # 接收到空数据表示连接关闭
|
||||
break
|
||||
conn.send(d)
|
||||
finally:
|
||||
conn.close() # 正确关闭连接
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## SocketClient 类
|
||||
|
||||
TCP 客户端封装,提供连接管理及基本 I/O 操作。
|
||||
|
||||
### 构造函数 `__init__(host, port)`
|
||||
自动尝试连接指定服务器。
|
||||
|
||||
#### 参数
|
||||
- `host`: 服务器地址
|
||||
- `port`: 端口
|
||||
|
||||
#### 行为
|
||||
- 创建 TCP 套接字
|
||||
- 调用 `connect()` 方法连接服务器
|
||||
- 成功则 `ready=True`,失败则抛出 `SocketClientError`
|
||||
|
||||
---
|
||||
|
||||
### 方法
|
||||
|
||||
#### `timeout(tim)`
|
||||
设置套接字阻塞模式和超时时间。
|
||||
|
||||
- `tim == 0`: 非阻塞模式
|
||||
- `tim > 0`: 阻塞模式 + 超时秒数
|
||||
|
||||
> 内部调用 `setblocking()` 和 `settimeout()`
|
||||
|
||||
#### `connect()`
|
||||
重新建立与服务器的连接。
|
||||
|
||||
- 若失败打印错误信息并抛出 `SocketClientError`
|
||||
|
||||
#### `read(size)`
|
||||
从服务器读取最多 `size` 字节数据。
|
||||
|
||||
- 成功返回字节串(`bytes`)
|
||||
- 失败打印错误并抛出 `SocketClientError`
|
||||
|
||||
#### `write(data)`
|
||||
向服务器发送数据。
|
||||
|
||||
- `data` 必须是字节串(`bytes`),若传入字符串需先编码
|
||||
- 错误时抛出异常
|
||||
|
||||
#### `close()`
|
||||
关闭连接,释放资源。
|
||||
|
||||
- 调用 `sock.close()`
|
||||
- 设置 `ready = False`
|
||||
|
||||
---
|
||||
|
||||
## 主程序示例
|
||||
|
||||
```python
|
||||
if __name__ == '__main__':
|
||||
s = SocketServer('localhost', 12232)
|
||||
s.start()
|
||||
time.sleep(5) # 等待服务器启动
|
||||
|
||||
while True:
|
||||
c = SocketClient('localhost', 12232)
|
||||
msg = 'msg1'
|
||||
print("send:", msg)
|
||||
c.write(msg.encode()) # 注意:必须 encode 成 bytes
|
||||
d = c.read(1024)
|
||||
print("get:", d.decode()) # 解码为字符串
|
||||
c.close()
|
||||
time.sleep(1)
|
||||
```
|
||||
|
||||
> ✅ 改进建议:添加异常处理防止客户端崩溃中断循环
|
||||
|
||||
---
|
||||
|
||||
## 使用建议与注意事项
|
||||
|
||||
### ✅ 推荐实践
|
||||
1. **确保数据编码一致性**
|
||||
```python
|
||||
c.write(b'msg') # 字节串
|
||||
c.write('msg'.encode()) # 字符串转字节
|
||||
```
|
||||
2. **捕获异常避免中断**
|
||||
```python
|
||||
try:
|
||||
c = SocketClient(...)
|
||||
except SocketClientError:
|
||||
time.sleep(1)
|
||||
continue
|
||||
```
|
||||
3. **合理设置超时**
|
||||
```python
|
||||
c.timeout(5) # 5秒超时防卡死
|
||||
```
|
||||
|
||||
### ⚠️ 已知问题
|
||||
| 问题 | 描述 | 建议修复 |
|
||||
|------|------|---------|
|
||||
| `con.close()` 拼写错误 | 导致连接未正确关闭 | 改为 `conn.close()` |
|
||||
| `callee` 默认函数无异常处理 | 可能导致线程异常退出 | 包裹 `try...finally` |
|
||||
| 构造中静默忽略异常 | 难以调试绑定失败原因 | 抛出或记录详细日志 |
|
||||
| `write()` 中错误提示写成了 `'recv error'` | 日志误导 | 改为 `'send error'` |
|
||||
|
||||
### 🔧 可扩展方向
|
||||
- 添加 SSL/TLS 支持
|
||||
- 支持 IPv6
|
||||
- 增加连接池或心跳检测
|
||||
- 提供异步 IO 版本(asyncio)
|
||||
|
||||
---
|
||||
|
||||
## 许可证
|
||||
|
||||
本代码为示例用途,遵循 [MIT License](https://opensource.org/licenses/MIT)(除非另有声明)。
|
||||
|
||||
---
|
||||
|
||||
> 文档版本:v1.0
|
||||
> 更新日期:2025年4月5日
|
||||
305
aidocs/sshx.md
Normal file
305
aidocs/sshx.md
Normal file
@ -0,0 +1,305 @@
|
||||
以下是为提供的 Python 代码编写的 **Markdown 格式技术文档**,涵盖了模块功能、类说明、方法描述及使用示例。
|
||||
|
||||
---
|
||||
|
||||
# SSH 连接管理模块技术文档
|
||||
|
||||
## 概述
|
||||
|
||||
本模块基于 `asyncssh` 实现了异步 SSH 客户端连接能力,支持通过跳板机(Jump Server)链式连接远程主机,并提供文件传输、命令执行和交互式 Shell 等高级功能。适用于批量运维操作、自动化部署与安全网络环境下的远程控制。
|
||||
|
||||
主要特性:
|
||||
- 异步非阻塞 I/O,高并发处理多台服务器
|
||||
- 支持密码或密钥认证
|
||||
- 支持多级跳板机穿透
|
||||
- 提供 SCP 文件上传下载接口
|
||||
- 支持交互式命令执行(如 Bash)
|
||||
- 封装简洁易用的高层 API
|
||||
|
||||
依赖库:
|
||||
```bash
|
||||
pip install asyncssh shlex
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 模块导入说明
|
||||
|
||||
```python
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import shlex
|
||||
from traceback import format_exc
|
||||
from contextlib import asynccontextmanager
|
||||
from functools import partial
|
||||
from threading import Thread
|
||||
from appPublic.myTE import tmpTml
|
||||
from appPublic.log import debug, exception
|
||||
import asyncio
|
||||
import asyncssh
|
||||
```
|
||||
|
||||
> 注意:`appPublic.*` 是项目自定义工具包,包含日志记录与模板引擎等功能。
|
||||
|
||||
---
|
||||
|
||||
## 核心类说明
|
||||
|
||||
### 1. `SSHServer`
|
||||
|
||||
表示一个可通过跳板机访问的目标 SSH 服务器。
|
||||
|
||||
#### 初始化参数
|
||||
|
||||
| 参数 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `server` | dict | 目标服务器配置,格式见下表 |
|
||||
| `jumpservers` | list[dict] | 跳板机列表(可选),每个元素结构同 `server` |
|
||||
|
||||
`server` 字典字段说明:
|
||||
|
||||
| 键名 | 类型 | 默认值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| `host` | str | 必填 | 主机地址(IP 或域名) |
|
||||
| `username` | str | `'root'` | 登录用户名 |
|
||||
| `port` | int | `22` | SSH 端口 |
|
||||
| `password` | str | `None` | 登录密码(优先级低于密钥) |
|
||||
| `client_keys` | list[str] | `[]` | 私钥路径列表(如 `['~/.ssh/id_rsa']`) |
|
||||
| `passphrase` | str | `None` | 密钥加密口令 |
|
||||
| `jumpservers` | list[dict] | `None` | 内嵌跳板机配置(若未传构造函数参数) |
|
||||
|
||||
#### 方法
|
||||
|
||||
##### `_connect_server(server: dict, refconn=None) -> asyncssh.SSHClientConnection`
|
||||
内部方法,建立到指定服务器的连接。
|
||||
|
||||
- 若 `refconn` 存在,则通过已有连接隧道建立新连接(用于跳板穿透)
|
||||
- 支持密钥或密码认证自动选择
|
||||
|
||||
##### `get_connector() -> Async Context Manager`
|
||||
上下文管理器,返回目标主机连接对象,自动处理连接建立与释放。
|
||||
|
||||
**示例:**
|
||||
```python
|
||||
async with ssh_server.get_connector() as conn:
|
||||
result = await conn.run('ls -l')
|
||||
print(result.stdout)
|
||||
```
|
||||
|
||||
> 自动关闭所有跳板机连接,异常时捕获并记录日志。
|
||||
|
||||
---
|
||||
|
||||
### 2. `SSHNode`
|
||||
|
||||
增强版 SSH 节点,封装完整生命周期管理与多种操作接口。
|
||||
|
||||
#### 初始化参数
|
||||
|
||||
| 参数 | 类型 | 默认值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| `host` | str | 必填 | 目标主机地址 |
|
||||
| `username` | str | `'root'` | 用户名 |
|
||||
| `port` | int | `22` | SSH 端口 |
|
||||
| `password` | str | `None` | 登录密码 |
|
||||
| `client_keys` | list[str] | `[]` | 客户端私钥路径 |
|
||||
| `passphrase` | str | `None` | 私钥解密口令 |
|
||||
| `jumpers` | list[dict] | `[]` | 跳板机列表 |
|
||||
|
||||
#### 属性
|
||||
|
||||
- `conn`: 当前节点主连接 (`asyncssh.SSHClientConnection`)
|
||||
- `jumper_conns`: 所有跳板连接列表
|
||||
- `batch_cmds`: 待执行命令队列(暂未完全实现)
|
||||
|
||||
#### 方法
|
||||
|
||||
| 方法 | 功能 |
|
||||
|------|------|
|
||||
| `info()` → dict | 返回当前节点信息快照 |
|
||||
| `asjumper()` → list[dict] | 返回可用于其他节点跳板的配置列表 |
|
||||
| `set_jumpers(jumpers)` | 动态设置跳板机 |
|
||||
| `connect()` | 建立完整连接链(跳板 + 主机) |
|
||||
| `close()` | 关闭所有连接资源 |
|
||||
| `run(cmd, ...)` | 执行单条命令(自动连接/关闭) |
|
||||
| `_cmd`, `_run`, `_process`, `_l2r`, `_r2l` | 内部操作封装 |
|
||||
|
||||
##### `run(cmd, input=None, stdin=None, stdout=None)`
|
||||
执行远程命令,支持以下特殊语法:
|
||||
|
||||
- `l2r <local_path> <remote_path>`:本地 → 远程复制(SCP)
|
||||
- `r2l <remote_path> <local_path>`:远程 → 本地复制(SCP)
|
||||
|
||||
否则调用 `conn.run()` 执行普通命令。
|
||||
|
||||
##### `_xcmd(cmd, xmsgs=[], ns={}, show_input=None, show_stdout=None)`
|
||||
执行交互式命令,支持定时输入模拟。
|
||||
|
||||
- `xmsgs`: 输入消息列表,格式 `[ (delay_sec, template_string), ... ]`
|
||||
- `ns`: 模板变量命名空间
|
||||
- `show_input`, `show_stdout`: 回调函数,用于输出监控
|
||||
|
||||
> 示例:自动登录 CLI 工具、数据库等需要逐步输入的场景。
|
||||
|
||||
---
|
||||
|
||||
### 3. `SSHNodes`
|
||||
|
||||
批量管理多个 `SSHNode` 的容器类,支持并行执行。
|
||||
|
||||
#### 初始化参数
|
||||
|
||||
| 参数 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `nodes` | list[str] | 主机名/IP 列表 |
|
||||
| `username` | str | 统一用户名 |
|
||||
| `port` | int | 统一端口 |
|
||||
| `jumpers` | list[dict] | 共享跳板机配置 |
|
||||
|
||||
#### 方法
|
||||
|
||||
| 方法 | 功能 |
|
||||
|------|------|
|
||||
| `append_cmd(cmd, stdin, stdout)` | 添加待批处理命令 |
|
||||
| `run(cmd, ...)` | 并发运行命令于所有节点 |
|
||||
| `exe_batch()` | 执行预设批处理命令队列(**注意:当前存在 bug**) |
|
||||
|
||||
> 使用 `asyncio.gather(..., return_exceptions=True)` 避免单个失败中断整体流程。
|
||||
|
||||
#### 示例
|
||||
|
||||
```python
|
||||
hosts = ['192.168.1.10', '192.168.1.11']
|
||||
jump = {"host": "jumper.example.com", "username": "jumpuser", "port": 22}
|
||||
cluster = SSHNodes(hosts, jumpers=[jump])
|
||||
|
||||
results = await cluster.run("uptime")
|
||||
for r in results:
|
||||
if isinstance(r, Exception):
|
||||
print("Error:", r)
|
||||
else:
|
||||
print(r.stdout.strip())
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. `SSHBash`
|
||||
|
||||
实现交互式远程 Bash Shell 会话,支持实时输入输出转发。
|
||||
|
||||
#### 构造函数
|
||||
|
||||
```python
|
||||
SSHBash(node: SSHNode, loop=None)
|
||||
```
|
||||
|
||||
- `node`: 已配置的 `SSHNode` 实例
|
||||
- `loop`: 可选事件循环(默认使用当前)
|
||||
|
||||
内部启动独立线程运行子事件循环,避免阻塞主线程。
|
||||
|
||||
#### 核心方法
|
||||
|
||||
##### `run(read_co, write_co)`
|
||||
启动交互式 Bash 会话。
|
||||
|
||||
- `read_co`: 异步可调用对象,用于读取用户输入(例如从 `stdin`)
|
||||
- `write_co`: 异步回调,用于写入输出内容(如打印到终端)
|
||||
|
||||
> 底层使用 `create_process('bash', term_type='xterm-256color')`
|
||||
|
||||
##### `feed_stdin(f)`
|
||||
将输入数据推送到远程进程 stdin。
|
||||
|
||||
##### `exit()`
|
||||
清理资源:关闭连接、停止子线程事件循环。
|
||||
|
||||
#### 使用示例
|
||||
|
||||
```python
|
||||
async def read_input():
|
||||
return os.read(sys.stdin.fileno(), 65535)
|
||||
|
||||
async def write_output(data):
|
||||
sys.stdout.write(data.decode('utf-8'))
|
||||
sys.stdout.flush()
|
||||
|
||||
bash = SSHBash(my_ssh_node)
|
||||
await bash.run(read_input, write_output)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5. `SshConnector`
|
||||
|
||||
轻量级连接包装器,提供更直观的操作接口。
|
||||
|
||||
> ⚠️ **Bug 提示**:`l2r` 方法中 `recurse=Tree` 应为 `recurse=True`
|
||||
|
||||
#### 方法
|
||||
|
||||
| 方法 | 功能 |
|
||||
|------|------|
|
||||
| `r2l(rf, lf)` | 下载文件:远程 → 本地 |
|
||||
| `l2r(lf, rf)` | 上传文件:本地 → 远程 |
|
||||
| `run_process(*args, **kw)` | 创建远程进程 |
|
||||
| `run(cmdline, ...)` | 执行命令并返回结果 |
|
||||
|
||||
---
|
||||
|
||||
## 辅助函数与入口点
|
||||
|
||||
### `main()`
|
||||
命令行交互入口,演示如何动态输入命令对多个主机批量执行。
|
||||
|
||||
```bash
|
||||
python script.py "df -h" host1 host2 ...
|
||||
```
|
||||
|
||||
> 当前版本进入无限循环等待用户输入。
|
||||
|
||||
### `test_sshbash()`
|
||||
测试交互式 Bash 功能的示例函数。
|
||||
|
||||
---
|
||||
|
||||
## 已知问题与改进建议
|
||||
|
||||
| 问题 | 描述 | 建议修复 |
|
||||
|------|------|---------|
|
||||
| `SSHNodes.__init__` 拼写错误 | `usernmae` → `username` | 更正拼写 |
|
||||
| `exe_batch()` 中 `return_excetion=True` | 拼写错误导致异常不被捕获 | 改为 `return_exceptions=True` |
|
||||
| `exe_batch()` 在 `gather` 后仍有代码 | `return` 后不会执行后续行 | 移动 `for` 循环至 `gather` 之后 |
|
||||
| `SshConnector.l2r` 中 `self.comm` | 应为 `self.conn` | 修正属性引用 |
|
||||
| `show_result()` 中变量 `e` 未定义 | `print('Exception:', e)` 报错 | 改为 `print('Exception:', x)` |
|
||||
| `recurse=Tree` | 应为布尔值 `True` | 修改为 `recurse=True` |
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
该模块构建了一个灵活高效的异步 SSH 操作框架,特别适合在受限网络环境中通过跳板机批量管理服务器。结合 `asyncssh` 的强大功能,实现了文件传输、命令执行、交互式 Shell 等核心运维需求。
|
||||
|
||||
建议进一步完善单元测试、增加连接池、超时控制与重试机制以提升稳定性。
|
||||
|
||||
---
|
||||
|
||||
📌 **维护建议**:
|
||||
- 添加类型注解(Type Hints)
|
||||
- 补充 docstring 文档字符串
|
||||
- 分离配置加载逻辑
|
||||
- 增加连接健康检查机制
|
||||
|
||||
---
|
||||
|
||||
✅ **适用场景**:
|
||||
- 自动化部署系统
|
||||
- 多数据中心运维脚本
|
||||
- DevOps 流水线中的远程操作环节
|
||||
- 内网服务器集中管理平台
|
||||
|
||||
---
|
||||
|
||||
*文档版本:v1.0*
|
||||
*最后更新:2025年4月5日*
|
||||
134
aidocs/strUtils.md
Normal file
134
aidocs/strUtils.md
Normal file
@ -0,0 +1,134 @@
|
||||
# `strUtils` 模块技术文档
|
||||
|
||||
---
|
||||
|
||||
## 概述
|
||||
|
||||
`strUtils` 是一个简单的 Python 工具模块,提供对字符串进行左右空格去除的基本功能。该模块包含三个函数:
|
||||
|
||||
- `rtrim(ss)`:去除字符串右侧的空格。
|
||||
- `ltrim(ss)`:去除字符串左侧的空格。
|
||||
- `lrtrim(ss)`:同时去除字符串左右两侧的空格。
|
||||
|
||||
这些函数不依赖外部库,适用于基础字符串处理场景。
|
||||
|
||||
---
|
||||
|
||||
## 函数说明
|
||||
|
||||
### `rtrim(ss)`
|
||||
|
||||
#### 功能
|
||||
去除输入字符串右侧的所有空格字符(`' '`),保留其余部分。
|
||||
|
||||
#### 参数
|
||||
- `ss` (str): 待处理的字符串。
|
||||
|
||||
#### 返回值
|
||||
- (str): 去除右侧空格后的字符串。若原字符串为空,返回空字符串。
|
||||
|
||||
#### 示例
|
||||
```python
|
||||
rtrim("hello ") # 返回 "hello"
|
||||
rtrim(" hello ") # 返回 " hello"
|
||||
rtrim("") # 返回 ""
|
||||
```
|
||||
|
||||
#### 实现逻辑
|
||||
1. 若输入字符串为空,直接返回。
|
||||
2. 使用循环检查字符串最后一个字符是否为空格,若是则切片去除最后一个字符,重复此过程直到末尾非空格为止。
|
||||
|
||||
---
|
||||
|
||||
### `ltrim(ss)`
|
||||
|
||||
#### 功能
|
||||
去除输入字符串左侧的所有空格字符(`' '`),保留其余部分。
|
||||
|
||||
#### 参数
|
||||
- `ss` (str): 待处理的字符串。
|
||||
|
||||
#### 返回值
|
||||
- (str): 去除左侧空格后的字符串。若原字符串为空,返回空字符串。
|
||||
|
||||
#### 示例
|
||||
```python
|
||||
ltrim(" hello") # 返回 "hello"
|
||||
ltrim(" hello ") # 返回 "hello "
|
||||
ltrim("") # 返回 ""
|
||||
```
|
||||
|
||||
#### 实现逻辑
|
||||
1. 若输入字符串为空,直接返回。
|
||||
2. 使用循环检查字符串第一个字符是否为空格,若是则切片去除第一个字符,重复此过程直到开头非空格为止。
|
||||
|
||||
---
|
||||
|
||||
### `lrtrim(ss)`
|
||||
|
||||
#### 功能
|
||||
同时去除输入字符串左侧和右侧的所有空格字符。
|
||||
|
||||
#### 参数
|
||||
- `ss` (str): 待处理的字符串。
|
||||
|
||||
#### 返回值
|
||||
- (str): 去除左右空格后的字符串。若原字符串为空,返回空字符串。
|
||||
|
||||
#### 示例
|
||||
```python
|
||||
lrtrim(" hello ") # 返回 "hello"
|
||||
lrtrim(" hi there ") # 返回 "hi there"
|
||||
lrtrim("") # 返回 ""
|
||||
```
|
||||
|
||||
#### 实现逻辑
|
||||
1. 先调用 `ltrim(ss)` 去除左侧空格。
|
||||
2. 再对结果调用 `rtrim(s)` 去除右侧空格。
|
||||
3. 返回最终结果。
|
||||
|
||||
> **注意**:该函数等效于 Python 内置的 `str.strip()`,但此处为手动实现。
|
||||
|
||||
---
|
||||
|
||||
## 使用示例
|
||||
|
||||
```python
|
||||
# 导入模块(假设保存为 strUtils.py)
|
||||
from strUtils import *
|
||||
|
||||
text = " Hello World "
|
||||
|
||||
print(repr(rtrim(text))) # ' Hello World'
|
||||
print(repr(ltrim(text))) # 'Hello World '
|
||||
print(repr(lrtrim(text))) # 'Hello World'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 本模块仅处理空格字符(`' '`),不处理其他空白字符(如制表符 `\t`、换行符 `\n` 等)。
|
||||
- 对于大字符串或高频调用场景,建议使用 Python 内置方法 `str.strip()`, `str.lstrip()`, `str.rstrip()`,性能更优。
|
||||
- 输入参数应为字符串类型,否则可能引发 `IndexError` 或 `TypeError`。
|
||||
|
||||
---
|
||||
|
||||
## 版本信息
|
||||
|
||||
- 创建时间:2025年4月
|
||||
- 作者:匿名
|
||||
- 许可:公共领域 / 自由使用
|
||||
|
||||
---
|
||||
|
||||
## 扩展建议
|
||||
|
||||
未来可扩展如下功能:
|
||||
- 支持去除多种空白字符(使用 `string.whitespace`)。
|
||||
- 添加 `trim_all(ss)` 函数用于去除所有连续空格并压缩为单个空格。
|
||||
- 增加类型检查与异常处理机制。
|
||||
|
||||
---
|
||||
|
||||
✅ **提示**:虽然此模块可用于学习字符串操作原理,但在生产环境中推荐优先使用 Python 内置的 `strip` 系列方法。
|
||||
317
aidocs/streamhttpclient.md
Normal file
317
aidocs/streamhttpclient.md
Normal file
@ -0,0 +1,317 @@
|
||||
# `StreamHttpClient` 技术文档
|
||||
|
||||
```markdown
|
||||
# StreamHttpClient - 异步流式 HTTP 客户端
|
||||
|
||||
一个基于 `aiohttp` 和 `aiohttp_socks` 的异步、支持 SOCKS5 代理的流式 HTTP 客户端,具备自动降级到代理机制、SSL 配置管理、连接失败重试等功能。
|
||||
|
||||
---
|
||||
|
||||
## 📌 概述
|
||||
|
||||
`StreamHttpClient` 是一个用于发起异步 HTTP 请求的 Python 类,主要特性包括:
|
||||
|
||||
- 支持 **直接请求** 与 **SOCKS5 代理请求**
|
||||
- 自动检测连接失败并尝试切换至 SOCKS5 代理(智能回退)
|
||||
- 支持流式响应处理(chunked streaming),适用于大文件或长响应场景
|
||||
- 可配置 SSL 验证行为(如跳过证书验证)
|
||||
- 支持上传文件和表单数据
|
||||
- 维护需要使用代理的 URL 列表(持久化到本地文件)
|
||||
|
||||
---
|
||||
|
||||
## 🔧 环境依赖
|
||||
|
||||
### Python 版本
|
||||
```bash
|
||||
Python >= 3.7
|
||||
```
|
||||
|
||||
> 示例中使用的是虚拟环境下的 Python 3.12 路径:
|
||||
> ```python
|
||||
> #!/Users/ymq/p3.12/bin/python
|
||||
> ```
|
||||
|
||||
### 第三方库依赖
|
||||
|
||||
| 包 | 用途 |
|
||||
|----|------|
|
||||
| `aiohttp` | 异步 HTTP 客户端/服务器框架 |
|
||||
| `aiohttp_socks` | 提供对 SOCKS4/SOCKS5 代理的支持 |
|
||||
| `certifi` | 提供 Mozilla 的 CA 证书包,用于 SSL 验证 |
|
||||
| `ssl` | Python 内置模块,用于 SSL/TLS 配置 |
|
||||
|
||||
### 可选日志模块
|
||||
```python
|
||||
from appPublic.log import exception, debug
|
||||
```
|
||||
> 需确保项目中存在此模块,否则需替换为标准日志工具(如 `logging`)。
|
||||
|
||||
---
|
||||
|
||||
## 📦 核心功能
|
||||
|
||||
### ✅ 流式行解析器:`async def liner(async_gen)`
|
||||
将字节流按 `\n` 分割为每一行,并以异步生成器方式逐行输出。
|
||||
|
||||
#### 参数
|
||||
- `async_gen`: 异步生成器,产出 `bytes` 类型的数据块
|
||||
|
||||
#### 返回
|
||||
- `AsyncGenerator[bytes]`: 每次 `yield` 一行内容(末尾不含 `\n`)
|
||||
|
||||
#### 示例用法
|
||||
```python
|
||||
async for line in liner(response.content.iter_chunked(1024)):
|
||||
print(line.decode('utf-8'))
|
||||
```
|
||||
|
||||
> 注意:原始代码中该函数未被调用,但可作为工具扩展使用。
|
||||
|
||||
---
|
||||
|
||||
### 🔐 SSL 上下文构建:`get_non_verify_ssl()`
|
||||
创建一个不验证主机名和证书的 SSL 上下文,用于绕过 HTTPS 证书检查。
|
||||
|
||||
#### 返回
|
||||
- `ssl.SSLContext`: 配置为 `CERT_NONE` 且关闭 `check_hostname` 的上下文
|
||||
|
||||
> ⚠️ 安全警告:仅建议在测试或可信网络中使用,生产环境慎用!
|
||||
|
||||
---
|
||||
|
||||
## 🧱 主要类:`StreamHttpClient`
|
||||
|
||||
### 初始化:`__init__(socks5_url="socks5://127.0.0.1:1086")`
|
||||
|
||||
#### 参数
|
||||
| 参数 | 类型 | 默认值 | 描述 |
|
||||
|------|------|--------|------|
|
||||
| `socks5_url` | str | `"socks5://127.0.0.1:1086"` | 默认使用的 SOCKS5 代理地址 |
|
||||
|
||||
#### 动作
|
||||
- 加载本地保存的需走代理的 URL 列表(从 `~/.socksurls.txt`)
|
||||
- 创建默认 SSL 上下文(使用 `certifi` 来源的 CA 证书)
|
||||
|
||||
#### 属性
|
||||
| 属性 | 类型 | 描述 |
|
||||
|------|------|------|
|
||||
| `socks_urls_file` | Path | 存储代理 URL 列表的文件路径 |
|
||||
| `socks_urls` | set[str] | 当前已知需要通过 SOCKS 访问的 URL 集合 |
|
||||
| `ssl_context` | ssl.SSLContext | 默认的安全 SSL 上下文 |
|
||||
|
||||
---
|
||||
|
||||
### 🔁 私有方法
|
||||
|
||||
#### `_load_socks_urls() -> List[str]`
|
||||
从 `~/.socksurls.txt` 文件读取所有需要走代理的 URL。
|
||||
|
||||
> 若文件不存在,则返回空列表。
|
||||
|
||||
#### `_save_socks_url(url: str)`
|
||||
将指定 URL 添加到 `socks_urls` 集合并追加写入 `.socksurls.txt` 文件,防止重复添加。
|
||||
|
||||
---
|
||||
|
||||
## 🌐 核心请求方法
|
||||
|
||||
### `__call__()` 方法(主入口)
|
||||
|
||||
异步发起 HTTP 请求,支持自动故障转移至 SOCKS5 代理。
|
||||
|
||||
#### 签名
|
||||
```python
|
||||
async def __call__(
|
||||
self,
|
||||
method: str,
|
||||
url: str,
|
||||
*,
|
||||
headers=None,
|
||||
params=None,
|
||||
data=None,
|
||||
json=None,
|
||||
files=None,
|
||||
chunk_size=1024,
|
||||
**kw
|
||||
)
|
||||
```
|
||||
|
||||
#### 参数说明
|
||||
| 参数 | 类型 | 描述 |
|
||||
|------|------|------|
|
||||
| `method` | str | HTTP 方法(GET, POST 等) |
|
||||
| `url` | str | 请求目标 URL |
|
||||
| `headers` | dict | 请求头 |
|
||||
| `params` | dict | 查询参数(URL query string) |
|
||||
| `data` | Any | 表单或原始请求体数据 |
|
||||
| `json` | dict | JSON 序列化数据(会设置 `Content-Type: application/json`) |
|
||||
| `files` | dict | 文件上传字段,格式:`{"name": (filename, fileobj, content_type)}` |
|
||||
| `chunk_size` | int | 每次读取响应内容的大小(字节) |
|
||||
| `verify` | bool | 是否启用 SSL 验证(自定义关键字,非 aiohttp 原生参数) |
|
||||
| `**kw` | 其他参数 | 传递给 `session.request()` 的其他参数 |
|
||||
|
||||
#### 工作流程
|
||||
1. 判断当前 URL 是否在 `self.socks_urls` 中:
|
||||
- 是 → 直接使用 SOCKS5 代理发送请求
|
||||
- 否 → 尝试直连
|
||||
2. 直连失败(`ClientConnectionError`)时:
|
||||
- 打印错误日志
|
||||
- 切换为 SOCKS5 代理重试
|
||||
- 成功后将该 URL 记录进 `~/.socksurls.txt`
|
||||
3. 其他异常则抛出
|
||||
|
||||
#### 日志说明
|
||||
| 日志符号 | 含义 |
|
||||
|---------|------|
|
||||
| `🌐` | 尝试直连 |
|
||||
| `🔁` | 使用 SOCKS5 代理 |
|
||||
| `❌` | 请求失败 |
|
||||
| `🧦` | 正在使用袜子(SOCKS)代理重试 |
|
||||
|
||||
---
|
||||
|
||||
### `request()` 方法(便捷封装)
|
||||
|
||||
同步等待完整响应体返回。
|
||||
|
||||
```python
|
||||
async def request(...)
|
||||
```
|
||||
|
||||
#### 行为
|
||||
- 调用 `__call__()` 获取所有 chunk
|
||||
- 合并为完整的 `bytes` 返回
|
||||
|
||||
> ⚠️ 对于大型响应,请优先使用 `__call__()` 进行流式处理以避免内存溢出。
|
||||
|
||||
---
|
||||
|
||||
### `_request_with_connector()` 内部实现
|
||||
|
||||
真正执行请求的核心方法。
|
||||
|
||||
#### 参数
|
||||
同 `__call__`,额外包含:
|
||||
- `use_socks`: 是否使用 SOCKS5 代理
|
||||
- `ssl_context`: 使用的 SSL 上下文对象
|
||||
|
||||
#### 实现细节
|
||||
- 使用 `ProxyConnector.from_url()` 构造代理连接器(若 `use_socks=True`)
|
||||
- 支持动态选择 SSL 验证模式:
|
||||
- 若 `verify=False` 在 kw 中 → 使用无验证 SSL 上下文
|
||||
- 否则使用默认安全上下文
|
||||
- 处理 `files` 和 `data/json` 的编码逻辑:
|
||||
- 存在 `files` → 构造 `FormData`
|
||||
- 否则根据是否有 `json` 参数决定使用 `json=` 或 `data=`
|
||||
- 使用 `session.request()` 发起请求
|
||||
- 流式读取响应内容(`iter_chunked(chunk_size)`)
|
||||
|
||||
---
|
||||
|
||||
## 💡 使用示例
|
||||
|
||||
### 基本 GET 请求
|
||||
```python
|
||||
hc = StreamHttpClient()
|
||||
response = await hc.request("GET", "https://httpbin.org/get")
|
||||
print(response.decode())
|
||||
```
|
||||
|
||||
### POST JSON 数据
|
||||
```python
|
||||
payload = {"key": "value"}
|
||||
resp = await hc.request("POST", "https://httpbin.org/post", json=payload)
|
||||
print(resp.decode())
|
||||
```
|
||||
|
||||
### 文件上传
|
||||
```python
|
||||
with open("test.txt", "rb") as f:
|
||||
files = {
|
||||
"file": ("test.txt", f, "text/plain")
|
||||
}
|
||||
data = {"meta": "info"}
|
||||
resp = await hc.request("POST", "https://example.com/upload", data=data, files=files)
|
||||
```
|
||||
|
||||
### 忽略 SSL 验证(谨慎使用)
|
||||
```python
|
||||
resp = await hc.request("GET", "https://self-signed.badssl.com/", verify=False, timeout=5)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🖥️ CLI 主程序(测试入口)
|
||||
|
||||
当脚本直接运行时,执行简单测试请求。
|
||||
|
||||
### 用法
|
||||
```bash
|
||||
python stream_http_client.py "your prompt"
|
||||
```
|
||||
|
||||
> 当前示例中未实际使用 `prompt`,而是固定请求百度首页。
|
||||
|
||||
#### 示例输出
|
||||
```bash
|
||||
b'<!DOCTYPE html>...'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📁 文件结构影响
|
||||
|
||||
### 创建的本地文件
|
||||
- `~/.socksurls.txt`
|
||||
- 存储曾经因直连失败而改用 SOCKS5 的 URL 列表
|
||||
- 每行一个 URL,自动去重
|
||||
|
||||
> 示例:
|
||||
```
|
||||
https://blocked-site.com
|
||||
https://internal-api.example.org
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 注意事项 & 最佳实践
|
||||
|
||||
1. **安全性**
|
||||
- `verify=False` 会禁用 SSL 验证,可能导致中间人攻击。
|
||||
- 生产环境中应尽量避免使用。
|
||||
|
||||
2. **性能**
|
||||
- 流式接口适合处理大响应;小请求推荐使用 `request()` 获取完整结果。
|
||||
- `chunk_size` 可调节性能与延迟平衡。
|
||||
|
||||
3. **异常处理**
|
||||
- 所有非连接类异常都会重新抛出。
|
||||
- 建议外部捕获 `Exception` 并做适当处理。
|
||||
|
||||
4. **日志系统依赖**
|
||||
- 依赖 `appPublic.log.debug` 和 `.exception` 函数。
|
||||
- 如不可用,请替换为标准 `logging` 模块。
|
||||
|
||||
---
|
||||
|
||||
## 🛠 TODO / 改进建议
|
||||
|
||||
| 功能 | 建议 |
|
||||
|------|------|
|
||||
| 更灵活的日志注入 | 支持传入 logger 实例 |
|
||||
| 支持更多代理类型 | 如 HTTP 代理、认证代理等 |
|
||||
| 自动清理无效 URL | 定期验证 `.socksurls.txt` 中的链接是否仍需代理 |
|
||||
| 支持超时配置 | 允许用户自定义 `timeout` 对象而非仅秒数 |
|
||||
| 增加单元测试 | 覆盖直连、代理、失败回退等场景 |
|
||||
|
||||
---
|
||||
|
||||
## 📎 License
|
||||
|
||||
MIT(假设)
|
||||
作者:未知
|
||||
更新时间:2025年4月
|
||||
|
||||
> 请结合实际项目规范进行调整。
|
||||
```
|
||||
144
aidocs/t.md
Normal file
144
aidocs/t.md
Normal file
@ -0,0 +1,144 @@
|
||||
# 技术文档:JSON 字符串转义与安全输出
|
||||
|
||||
---
|
||||
|
||||
## 概述
|
||||
|
||||
本代码示例演示了如何将一个 Python 字典对象序列化为 JSON 字符串,并通过自定义的 `quotedstr` 函数对其进行安全转义处理,最终打印输出。该流程常用于日志记录、配置导出或在需要确保字符串安全显示的场景中。
|
||||
|
||||
---
|
||||
|
||||
## 依赖项
|
||||
|
||||
- **Python 标准库**:
|
||||
- `json`:用于将 Python 对象编码为 JSON 格式的字符串。
|
||||
- **第三方模块**:
|
||||
- `dataencoder.quotedstr`:一个自定义函数,用于对字符串进行引号包裹及特殊字符转义,确保其可安全显示或嵌入其他文本环境(如日志、Shell 命令等)。
|
||||
|
||||
> ⚠️ 注意:`dataencoder` 并非 Python 内置模块,需确保已安装或定义该模块及其 `quotedstr` 函数。
|
||||
|
||||
---
|
||||
|
||||
## 代码解析
|
||||
|
||||
```python
|
||||
import json
|
||||
from dataencoder import quotedstr
|
||||
|
||||
d = {
|
||||
"gret": "HGREert",
|
||||
"ynh": "RtghretbertBHER"
|
||||
}
|
||||
|
||||
print(quotedstr(json.dumps(d)))
|
||||
```
|
||||
|
||||
### 第一步:导入所需模块
|
||||
|
||||
```python
|
||||
import json
|
||||
from dataencoder import quotedstr
|
||||
```
|
||||
|
||||
- `json` 模块提供 `dumps()` 方法,用于将 Python 对象转换为 JSON 格式字符串。
|
||||
- `quotedstr` 是来自 `dataencoder` 模块的工具函数,功能可能是:
|
||||
- 将字符串用双引号包裹;
|
||||
- 转义内部引号、换行符等特殊字符;
|
||||
- 确保字符串可在命令行、日志或其他上下文中安全使用。
|
||||
|
||||
### 第二步:定义数据字典
|
||||
|
||||
```python
|
||||
d = {
|
||||
"gret": "HGREert",
|
||||
"ynh": "RtghretbertBHER"
|
||||
}
|
||||
```
|
||||
|
||||
- 创建一个包含两个键值对的字典 `d`。
|
||||
- 键和值均为标准 ASCII 字符串,适合作为 JSON 序列化的输入。
|
||||
|
||||
### 第三步:序列化并安全输出
|
||||
|
||||
```python
|
||||
print(quotedstr(json.dumps(d)))
|
||||
```
|
||||
|
||||
1. `json.dumps(d)`
|
||||
将字典 `d` 转换为 JSON 字符串:
|
||||
```json
|
||||
{"gret": "HGREert", "ynh": "RtghretbertBHER"}
|
||||
```
|
||||
|
||||
2. `quotedstr(...)`
|
||||
对上述 JSON 字符串进行进一步处理,例如:
|
||||
- 如果原始字符串包含引号,则进行转义;
|
||||
- 整体用外层引号包裹,防止解析歧义;
|
||||
- 示例输出可能如下(取决于 `quotedstr` 实现):
|
||||
```
|
||||
"{\"gret\": \"HGREert\", \"ynh\": \"RtghretbertBHER\"}"
|
||||
```
|
||||
|
||||
3. `print(...)`
|
||||
输出最终处理后的字符串。
|
||||
|
||||
---
|
||||
|
||||
## 预期输出示例
|
||||
|
||||
假设 `quotedstr` 的行为类似于 shell 安全引号包装器,则输出可能为:
|
||||
|
||||
```
|
||||
"{\"gret\": \"HGREert\", \"ynh\": \"RtghretbertBHER\"}"
|
||||
```
|
||||
|
||||
此格式适用于:
|
||||
- 插入到 Shell 脚本中作为参数;
|
||||
- 记录到日志文件以避免结构混淆;
|
||||
- 在受限文本环境中传递结构化数据。
|
||||
|
||||
---
|
||||
|
||||
## 使用场景
|
||||
|
||||
| 场景 | 说明 |
|
||||
|------|------|
|
||||
| 日志记录 | 安全打印结构化数据,避免日志解析错误 |
|
||||
| 配置传递 | 将配置对象编码后作为单个字符串传递给子进程 |
|
||||
| 调试输出 | 确保复杂字符串不会破坏终端显示 |
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **`dataencoder` 模块必须可用**
|
||||
需提前安装或实现 `dataencoder` 包,并确保 `quotedstr` 函数正确定义。
|
||||
|
||||
2. **`quotedstr` 行为依赖具体实现**
|
||||
其实际效果取决于函数内部逻辑。建议查阅 `dataencoder` 文档确认其是否满足安全需求。
|
||||
|
||||
3. **编码一致性**
|
||||
`json.dumps()` 默认使用 UTF-8 编码,确保环境支持 Unicode 处理。
|
||||
|
||||
4. **性能考量**
|
||||
对大型对象频繁调用此流程可能影响性能,建议按需使用。
|
||||
|
||||
---
|
||||
|
||||
## 扩展建议
|
||||
|
||||
若需增强功能,可考虑以下改进:
|
||||
|
||||
```python
|
||||
# 添加格式化输出
|
||||
print(quotedstr(json.dumps(d, indent=2)))
|
||||
|
||||
# 确保中文等非ASCII字符正确编码
|
||||
print(quotedstr(json.dumps(d, ensure_ascii=False)))
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
该代码简洁地实现了“Python 数据 → JSON 字符串 → 安全引用字符串”的转换链,适用于需要结构化数据以文本形式安全传递的多种场景。关键在于理解 `quotedstr` 的语义,并确保其行为符合预期的安全策略。
|
||||
157
aidocs/testdict.md
Normal file
157
aidocs/testdict.md
Normal file
@ -0,0 +1,157 @@
|
||||
# `ExecFile.DictConfig` 技术文档
|
||||
|
||||
## 概述
|
||||
|
||||
本文档介绍如何使用 `ExecFile.DictConfig` 类从 `.dict` 配置文件中加载配置数据,并通过属性访问方式读取嵌套的字典结构。该类提供了一种便捷的方式来将 Python 字典格式的配置文件转换为可动态访问的对象。
|
||||
|
||||
---
|
||||
|
||||
## 安装与依赖
|
||||
|
||||
确保已安装 `ExecFile` 模块。若未安装,请使用以下命令进行安装(假设模块可通过 pip 获取):
|
||||
|
||||
```bash
|
||||
pip install execfile
|
||||
```
|
||||
|
||||
> 注意:`ExecFile` 并非标准库模块,可能为自定义或第三方库。请确认其来源并正确导入。
|
||||
|
||||
---
|
||||
|
||||
## 导入模块
|
||||
|
||||
```python
|
||||
import ExecFile
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 初始化配置对象
|
||||
|
||||
### 语法
|
||||
|
||||
```python
|
||||
c = ExecFile.DictConfig(path='./config.dict')
|
||||
```
|
||||
|
||||
### 参数说明
|
||||
|
||||
| 参数名 | 类型 | 必填 | 描述 |
|
||||
|--------|--------|------|------|
|
||||
| `path` | str | 是 | 配置文件路径,指向一个包含合法 Python 字典语法的 `.dict` 文件 |
|
||||
|
||||
### 示例配置文件 (`config.dict`)
|
||||
|
||||
```python
|
||||
{
|
||||
'd': {
|
||||
'b': [
|
||||
{'c': None},
|
||||
{'c': 'value_b1_c'}
|
||||
],
|
||||
'c': {
|
||||
'a': 'value_a',
|
||||
'b': 'value_b',
|
||||
'c': [
|
||||
{}, {}, {},
|
||||
{'f': 'value_f'}
|
||||
],
|
||||
'd': [1, 2, 3]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> ⚠️ 文件内容必须是合法的 Python 表达式,通常是一个字典结构。
|
||||
|
||||
---
|
||||
|
||||
## 属性访问方式
|
||||
|
||||
`DictConfig` 将字典中的键转换为对象的属性,支持链式点号访问(`.`),包括嵌套字典和列表元素。
|
||||
|
||||
### 访问规则
|
||||
|
||||
- 字典键 → 对象属性:`config.d.b`
|
||||
- 列表元素 → 使用索引访问:`config.d.c.c[3].f`
|
||||
- 支持任意层级嵌套访问
|
||||
|
||||
---
|
||||
|
||||
## 示例代码解析
|
||||
|
||||
```python
|
||||
import ExecFile
|
||||
|
||||
# 加载配置文件
|
||||
c = ExecFile.DictConfig(path='./config.dict')
|
||||
|
||||
# 打印多个嵌套值
|
||||
print(c.d.b[1].c, c.d.c.a, c.d.c.b, c.d.c.c[3].f)
|
||||
|
||||
# 打印列表中的第2个元素(索引1)
|
||||
print(c.d.c.c[1])
|
||||
|
||||
# 打印整个列表 d.c.d
|
||||
print(c.d.c.d)
|
||||
```
|
||||
|
||||
### 输出示例(基于上述配置)
|
||||
|
||||
```text
|
||||
value_b1_c value_a value_b value_f
|
||||
{}
|
||||
[1, 2, 3]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 方法与特性
|
||||
|
||||
### `__init__(self, path: str)`
|
||||
- **功能**:读取指定路径的 `.dict` 文件并解析为嵌套对象。
|
||||
- **异常处理**:
|
||||
- 若文件不存在,抛出 `FileNotFoundError`
|
||||
- 若文件内容不是合法的 Python 表达式,抛出 `SyntaxError` 或 `ValueError`
|
||||
|
||||
### 动态属性访问
|
||||
- 所有字典的 key 均可通过 `.` 访问。
|
||||
- 列表和字典保留原生操作方式(如 `[index]`、`.keys()` 等)。
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **安全性警告**:由于 `.dict` 文件通过 `eval` 或 `ast.literal_eval` 解析,应确保文件来源可信,避免执行恶意代码。
|
||||
2. **文件格式要求**:配置文件必须是有效的 Python 字面量表达式,不能包含函数调用或其他可执行语句。
|
||||
3. **只读设计**:建议将配置视为只读数据;修改运行时对象不会持久化回文件。
|
||||
|
||||
---
|
||||
|
||||
## 错误排查
|
||||
|
||||
| 问题 | 可能原因 | 解决方案 |
|
||||
|------|----------|-----------|
|
||||
| `ModuleNotFoundError: No module named 'ExecFile'` | 模块未安装或命名错误 | 确认模块名称及安装方式 |
|
||||
| `SyntaxError` in config file | 配置文件语法错误 | 使用 Python 解释器测试文件内容是否可执行 |
|
||||
| `AttributeError` | 访问了不存在的键 | 检查配置结构与访问路径是否匹配 |
|
||||
|
||||
---
|
||||
|
||||
## 扩展建议
|
||||
|
||||
- 添加 `reload()` 方法以重新加载配置文件。
|
||||
- 提供 `to_dict()` 方法导出为标准字典类型。
|
||||
- 支持 `.json` 或 `.yaml` 格式作为替代方案。
|
||||
|
||||
---
|
||||
|
||||
## 版权与维护
|
||||
|
||||
- 模块:`ExecFile`
|
||||
- 维护者:未知(请查阅实际项目文档)
|
||||
- 适用版本:Python 3.6+
|
||||
|
||||
---
|
||||
|
||||
✅ **提示**:推荐在生产环境中使用 JSON/YAML 替代 `.dict` 格式以提高安全性和可移植性。
|
||||
201
aidocs/textsplit.md
Normal file
201
aidocs/textsplit.md
Normal file
@ -0,0 +1,201 @@
|
||||
# 文本句子分割工具技术文档
|
||||
|
||||
本模块提供两个核心函数,用于智能地将一段包含中英文混合内容的文本按句子进行分割,同时保留对话部分不被错误切分。适用于自然语言处理、文本预处理等场景。
|
||||
|
||||
---
|
||||
|
||||
## 📌 模块功能概览
|
||||
|
||||
| 函数名 | 功能描述 |
|
||||
|--------|---------|
|
||||
| `split_english_sentences(text)` | 分割纯英文文本为句子列表,避免在缩写词处误切分 |
|
||||
| `split_text_with_dialog_preserved(text)` | 分割中英文混合文本,并完整保留引号内的对话内容 |
|
||||
|
||||
---
|
||||
|
||||
## ✅ 1. `split_english_sentences(text)`
|
||||
|
||||
### 功能说明
|
||||
该函数用于将英文文本正确分割为独立句子,特别处理了以下常见问题:
|
||||
- 句子结尾标点后缺少空格(如 `"Hello.World"` → `"Hello. World"`)
|
||||
- 避免在常见缩写词(如 Dr., U.S.A.)处错误断句
|
||||
- 支持 `.`, `!`, `?` 作为句子结束符
|
||||
|
||||
### 参数
|
||||
| 参数 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `text` | `str` | 待分割的英文文本字符串 |
|
||||
|
||||
### 返回值
|
||||
`List[str]`: 分割后的句子列表,每个元素为一个独立句子(已去除首尾空白)
|
||||
|
||||
### 实现逻辑详解
|
||||
|
||||
#### 步骤 1:修复紧连句点
|
||||
```python
|
||||
text = re.sub(r'([a-zA-Z])\\.([A-Z])', r'\\1. \\2', text)
|
||||
```
|
||||
- **目的**:修复形如 `Hello.World` 的情况,在句点后添加空格。
|
||||
- **正则解释**:
|
||||
- `([a-zA-Z])`:匹配前一个字母
|
||||
- `\\.`:匹配句点 `.`
|
||||
- `([A-Z])`:匹配后一个大写字母(新句子开头)
|
||||
- 示例:`"It is cold.Today it will snow."` → `"It is cold. Today it will snow."`
|
||||
|
||||
#### 步骤 2:保护缩写词中的句点
|
||||
```python
|
||||
abbreviations = r"(Mr|Mrs|Ms|Dr|St|Jr|Sr|vs|i\\.e|e\\.g|U\\.S\\.A|U\\.K)\\."
|
||||
text = re.sub(abbreviations, lambda m: m.group(0).replace('.', '<DOT>'), text)
|
||||
```
|
||||
- **目的**:防止在 `Dr.` 或 `U.S.A.` 等缩写处分割。
|
||||
- 使用 `<DOT>` 临时替换缩写中的句点,避免被当作句子结束。
|
||||
- 支持的缩写包括:
|
||||
- `Mr.`, `Mrs.`, `Ms.`, `Dr.`, `St.`, `Jr.`, `Sr.`, `vs.`
|
||||
- `i.e.`, `e.g.`, `U.S.A.`, `U.K.`
|
||||
|
||||
#### 步骤 3:正则分割句子
|
||||
```python
|
||||
sentences = re.split(r'(?<=[.!?])\\s+', text.strip())
|
||||
```
|
||||
- 利用**正向后视断言** `(?<=...)` 在 `.`, `!`, `?` 后面的空白处分割。
|
||||
- 确保只在句子结束符后的空格处分割,而非所有空格。
|
||||
|
||||
#### 步骤 4:还原缩写中的句点
|
||||
```python
|
||||
sentences = [s.replace('<DOT>', '.') for s in sentences if s.strip()]
|
||||
```
|
||||
- 将之前替换成 `<DOT>` 的句点恢复为正常句点。
|
||||
- 过滤掉空字符串或仅空白的内容。
|
||||
|
||||
### 示例调用
|
||||
```python
|
||||
text = "Dr. Smith went to the U.S.A. He said, 'Hello world!' How are you?"
|
||||
result = split_english_sentences(text)
|
||||
print(result)
|
||||
# 输出:
|
||||
# ['Dr. Smith went to the U.S.A.', "He said, 'Hello world!'", 'How are you?']
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ 2. `split_text_with_dialog_preserved(text)`
|
||||
|
||||
### 功能说明
|
||||
此函数用于处理**中英文混合文本**,并确保双引号或中文引号包裹的对话内容**整体保留**,不会在内部被切断。
|
||||
|
||||
> 特别适用于小说、剧本、访谈记录等含大量对话的文本。
|
||||
|
||||
### 参数
|
||||
| 参数 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `text` | `str` | 包含中英文和可能对话的原始文本 |
|
||||
|
||||
### 返回值
|
||||
`List[str]`: 分割后的文本片段列表,每项是一个句子或完整的对话块。
|
||||
|
||||
### 实现逻辑详解
|
||||
|
||||
#### 步骤 1:清理换行与回车
|
||||
```python
|
||||
text = ''.join(text.split('\r'))
|
||||
text = ' '.join(text.split('\n'))
|
||||
```
|
||||
- 移除 `\r` 回车符
|
||||
- 将所有 `\n` 替换为空格,避免跨行断裂影响匹配
|
||||
|
||||
#### 步骤 2:定义对话匹配模式
|
||||
```python
|
||||
dialog_pattern = r'([“\"](.*?)[”\"])'
|
||||
```
|
||||
- 匹配使用中文左/右引号 `“”` 或英文双引号 `""` 包裹的内容
|
||||
- `(.*?)` 非贪婪捕获中间内容
|
||||
- `flags=re.DOTALL` 允许匹配跨行对话
|
||||
|
||||
#### 步骤 3:逐段提取并处理非对话 + 对话部分
|
||||
通过 `re.finditer()` 遍历所有对话块:
|
||||
|
||||
##### a. 提取当前对话前的非对话文本
|
||||
```python
|
||||
non_dialog = text[last_idx:start]
|
||||
```
|
||||
|
||||
##### b. 尝试用中文规则分割(优先中文句末标点)
|
||||
```python
|
||||
sentences = re.findall(r'[^。!?!?]*[。!?!?]', non_dialog, re.MULTILINE)
|
||||
```
|
||||
- 匹配以 `。`, `!`, `?`, `!`, `?` 结尾的中文/英文句子
|
||||
- 若无结果,则调用 `split_english_sentences()` 处理纯英文段落
|
||||
|
||||
##### c. 添加已分割的句子到结果
|
||||
```python
|
||||
parts.extend([s.strip() for s in sentences if s.strip()])
|
||||
```
|
||||
|
||||
##### d. 完整保留整个对话块(不分割)
|
||||
```python
|
||||
parts.append(match.group(1).strip()) # 如:“你好!”
|
||||
```
|
||||
|
||||
##### e. 更新索引位置
|
||||
```python
|
||||
last_idx = end # 下一次从对话结束后开始
|
||||
```
|
||||
|
||||
#### 步骤 4:处理最后一个对话之后的剩余文本
|
||||
重复上述非对话处理流程,完成收尾。
|
||||
|
||||
### 示例调用
|
||||
```python
|
||||
text = '''
|
||||
张三说:“你今天过得怎么样?”
|
||||
李四回答:“还不错!我刚从U.S.A回来。”
|
||||
然后他笑了笑。
|
||||
'''
|
||||
|
||||
result = split_text_with_dialog_preserved(text)
|
||||
for i, part in enumerate(result):
|
||||
print(f"{i+1}. {part}")
|
||||
```
|
||||
|
||||
### 输出示例
|
||||
```
|
||||
1. 张三说:“你今天过得怎么样?”
|
||||
2. “还不错!我刚从U.S.A回来。”
|
||||
3. 然后他笑了笑。
|
||||
```
|
||||
|
||||
> 注意:对话内容作为一个整体保留,其中 `U.S.A` 不会被错误切开。
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 注意事项与限制
|
||||
|
||||
| 项目 | 说明 |
|
||||
|------|------|
|
||||
| **缩写支持有限** | 当前硬编码了常用缩写,若需扩展可修改 `abbreviations` 正则 |
|
||||
| **仅支持双引号对话** | 单引号(如 `'hello'`)不会被视为对话块 |
|
||||
| **不支持嵌套引号** | 如 `“他说:‘你好’”` 可能导致匹配异常 |
|
||||
| **性能建议** | 对超长文本建议分段处理,避免正则效率下降 |
|
||||
|
||||
---
|
||||
|
||||
## 🧪 测试建议
|
||||
|
||||
推荐对以下类型文本进行测试验证:
|
||||
- 含缩写的英文句子(如 `Dr. John works in the U.S.A.`)
|
||||
- 中英混合带对话(如 `小明说:“I'm fine!”`)
|
||||
- 多行对话结构
|
||||
- 特殊符号与标点组合
|
||||
|
||||
---
|
||||
|
||||
## 📦 总结
|
||||
|
||||
本工具包提供了稳健的句子分割能力,尤其适合需要保留语义完整性(特别是对话)的 NLP 前处理任务。结合正则表达式与临时标记机制,有效解决了缩写误切与对话破坏的问题。
|
||||
|
||||
> ✅ 推荐用于:文本清洗、对话系统预处理、文学作品分析等场景。
|
||||
|
||||
---
|
||||
|
||||
📌 **作者**: 开发者
|
||||
📅 **最后更新**: 2025年4月5日
|
||||
191
aidocs/thread_workers.md
Normal file
191
aidocs/thread_workers.md
Normal file
@ -0,0 +1,191 @@
|
||||
# `ThreadWorkers` 技术文档
|
||||
|
||||
```markdown
|
||||
# ThreadWorkers 模块技术文档
|
||||
|
||||
## 概述
|
||||
|
||||
`ThreadWorkers` 是一个基于 Python 多线程的轻量级任务调度类,用于控制并发执行的任务数量。它通过 `threading.Semaphore` 实现对最大工作线程数的限制,并结合后台线程(`Background`)异步执行函数任务。
|
||||
|
||||
该模块适用于需要控制并发度的场景,如爬虫请求、I/O 密集型操作等,防止系统资源被过度占用。
|
||||
|
||||
---
|
||||
|
||||
## 依赖说明
|
||||
|
||||
- **Python 标准库**:
|
||||
- `time`: 提供延时功能。
|
||||
- `threading`: 使用 `Semaphore` 控制并发,`Thread` 执行异步任务。
|
||||
- `random`: 示例中用于生成随机延迟时间。
|
||||
|
||||
- **第三方模块**:
|
||||
- `appPublic.background.Background`: 一个封装好的后台线程类,用于在独立线程中运行指定函数。
|
||||
|
||||
> ⚠️ 注意:请确保已安装并可导入 `appPublic.background` 包。
|
||||
|
||||
---
|
||||
|
||||
## 类定义:`ThreadWorkers`
|
||||
|
||||
### 初始化方法:`__init__(self, max_workers=10)`
|
||||
|
||||
#### 参数:
|
||||
| 参数名 | 类型 | 默认值 | 说明 |
|
||||
|-------------|--------|-------|------|
|
||||
| `max_workers` | int | 10 | 最大允许同时运行的工作线程数量 |
|
||||
|
||||
#### 功能:
|
||||
初始化一个信号量(`Semaphore`),用于控制并发线程数量;同时初始化当前工作线程计数器 `co_worker`。
|
||||
|
||||
#### 示例:
|
||||
```python
|
||||
w = ThreadWorkers(max_workers=30)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 私有方法:`_do(self, func, *args, **kwargs)`
|
||||
|
||||
#### 参数:
|
||||
| 参数名 | 类型 | 说明 |
|
||||
|--------|------------|------|
|
||||
| `func` | callable | 要执行的目标函数 |
|
||||
| `*args` | tuple | 传递给目标函数的位置参数 |
|
||||
| `**kwargs` | dict | 传递给目标函数的关键字参数 |
|
||||
|
||||
#### 功能:
|
||||
实际执行任务的方法,在获取信号量后增加工作计数,执行函数,完成后释放信号量并减少计数。
|
||||
|
||||
#### 流程说明:
|
||||
1. 调用 `semaphore.acquire()` 等待可用线程槽位。
|
||||
2. 增加 `co_worker` 计数。
|
||||
3. 执行传入的函数 `func(*args, **kwargs)`。
|
||||
4. 无论成功或异常,最终都会:
|
||||
- 减少 `co_worker`
|
||||
- 调用 `semaphore.release()`
|
||||
|
||||
> ✅ 使用 `try...finally` 结构确保即使发生异常也能正确释放资源。
|
||||
|
||||
---
|
||||
|
||||
### 公共方法:`do(self, func, *args, **kwargs)`
|
||||
|
||||
#### 参数:
|
||||
同 `_do` 方法。
|
||||
|
||||
#### 功能:
|
||||
将任务提交到后台异步执行。使用 `Background` 类创建一个新线程来调用 `_do` 方法,实现非阻塞式任务提交。
|
||||
|
||||
#### 示例:
|
||||
```python
|
||||
def my_task(name):
|
||||
print(f"Hello from {name}")
|
||||
|
||||
w.do(my_task, "Alice")
|
||||
```
|
||||
|
||||
> 📌 此方法不会阻塞主线程,任务将在后台线程中执行。
|
||||
|
||||
---
|
||||
|
||||
### 公共方法:`get_workers(self)`
|
||||
|
||||
#### 返回值:
|
||||
- 类型:`int`
|
||||
- 含义:当前正在运行的任务数量(即活跃线程数)
|
||||
|
||||
#### 示例:
|
||||
```python
|
||||
print("Active workers:", w.get_workers())
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 公共方法:`until_done(self)`
|
||||
|
||||
#### 功能:
|
||||
阻塞主线程,直到所有已提交的任务完成执行(即 `co_worker == 0`)。
|
||||
|
||||
#### 实现细节:
|
||||
- 初始等待 0.1 秒。
|
||||
- 循环检查 `self.co_worker > 0`,每次休眠 0.01 秒(10ms)进行轮询。
|
||||
|
||||
> ⚠️ 注意:此为忙等待(busy-waiting)的一种温和形式,适合短时间等待。长时间使用可能影响性能。
|
||||
|
||||
#### 示例:
|
||||
```python
|
||||
for i in range(100):
|
||||
w.do(k, w)
|
||||
|
||||
w.until_done() # 等待所有任务结束
|
||||
print("All tasks completed.")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 使用示例
|
||||
|
||||
以下是一个完整示例,演示如何使用 `ThreadWorkers` 提交大量耗时任务并限制最大并发数:
|
||||
|
||||
```python
|
||||
if __name__ == '__main__':
|
||||
def k(worker):
|
||||
t = random.randint(1, 4)
|
||||
print('current workers=', worker.get_workers(), 'sleep=', t)
|
||||
time.sleep(t)
|
||||
|
||||
w = ThreadWorkers(max_workers=30)
|
||||
for i in range(100000):
|
||||
w.do(k, w)
|
||||
|
||||
w.until_done()
|
||||
print("All done!")
|
||||
```
|
||||
|
||||
### 输出示例:
|
||||
```
|
||||
current workers= 5 sleep= 3
|
||||
current workers= 6 sleep= 2
|
||||
...
|
||||
All done!
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 设计特点与优势
|
||||
|
||||
| 特性 | 描述 |
|
||||
|------|------|
|
||||
| 🔐 并发控制 | 使用 `Semaphore` 严格限制最大并发线程数 |
|
||||
| 🧮 实时监控 | 可通过 `get_workers()` 获取当前活跃任务数 |
|
||||
| ⏳ 安全清理 | `until_done()` 支持优雅等待所有任务完成 |
|
||||
| 💥 异常安全 | `_do` 中使用 `finally` 确保信号量和计数始终被释放 |
|
||||
| 🧱 解耦设计 | 利用 `Background` 类实现任务与线程管理解耦 |
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **线程安全**:`co_worker` 的增减未使用锁保护,但由于其仅用于粗略统计且在 `Semaphore` 内部已有同步机制,一般情况下是安全的。若需更高精度统计,建议添加 `threading.Lock`。
|
||||
|
||||
2. **性能考量**:`until_done()` 使用轮询方式,不适合高精度实时系统。可考虑使用事件通知机制优化。
|
||||
|
||||
3. **资源上限**:`max_workers` 不宜设置过高,避免引发系统线程过多导致性能下降。
|
||||
|
||||
4. **异常处理扩展**:当前 `_do` 方法不捕获函数内部异常。如需记录错误日志,可在 `try` 块中增强异常处理逻辑。
|
||||
|
||||
---
|
||||
|
||||
## 扩展建议
|
||||
|
||||
- 添加任务回调支持(如 `on_success`, `on_error`)
|
||||
- 支持超时机制
|
||||
- 引入任务队列与线程池复用,提升效率
|
||||
- 替换 `until_done` 为 `Event` 或 `Condition` 实现更高效的等待机制
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
`ThreadWorkers` 是一个简洁高效的并发任务控制器,适用于需要简单控制并发数的异步任务场景。其设计清晰、易于使用,适合作为基础组件集成进各类后台处理系统中。
|
||||
```
|
||||
521
aidocs/timeUtils.md
Normal file
521
aidocs/timeUtils.md
Normal file
@ -0,0 +1,521 @@
|
||||
# 日期与时间处理工具库技术文档
|
||||
|
||||
本模块提供了一系列用于日期、时间处理和格式转换的实用函数,支持日期计算、字符串与时间对象互转、模式匹配等功能,适用于数据分析、任务调度、日志处理等场景。
|
||||
|
||||
---
|
||||
|
||||
## 目录
|
||||
|
||||
- [1. 导入依赖](#1-导入依赖)
|
||||
- [2. 全局变量](#2-全局变量)
|
||||
- [3. 核心功能函数](#3-核心功能函数)
|
||||
- [3.1 时间差计算](#31-days_betweendate_str1-date_str2)
|
||||
- [3.2 当前时间相关](#32-monthfirstday--curdatetime--curdatestring--curtimestring--timestampstr)
|
||||
- [3.3 判断类函数](#33-ismonthlastday--isleapyear--is_monthend)
|
||||
- [3.4 时间戳操作](#34-timestamp--timestampsecond--timestampadd--timestampsub--timestamp2dt)
|
||||
- [3.5 日期加减运算](#35-addseconds--addmonths--addyears--dateadd--strdate_add)
|
||||
- [3.6 日期格式化与解析](#36-date2str--time2str--str2date--str2datetime--str2date)
|
||||
- [3.7 特殊日期计算](#37-firstsunday)
|
||||
- [3.8 时间对齐(StepedTimestamp)](#38-steppedtimestamp)
|
||||
- [3.9 周/季度辅助函数](#39-date_weekinyear--date_season)
|
||||
- [3.10 模式匹配](#310-is_match_pattern)
|
||||
|
||||
---
|
||||
|
||||
## 1. 导入依赖
|
||||
|
||||
```python
|
||||
import os, sys
|
||||
import time
|
||||
from datetime import date, timedelta, datetime
|
||||
```
|
||||
|
||||
> **说明**:
|
||||
> - `os`, `sys`:保留接口扩展性。
|
||||
> - `time`:用于时间戳和结构化时间处理。
|
||||
> - `datetime`:核心日期时间处理模块。
|
||||
|
||||
---
|
||||
|
||||
## 2. 全局变量
|
||||
|
||||
### `leapMonthDays`, `unleapMonthDays`
|
||||
```python
|
||||
leapMonthDays = [0,31,29,31,30,31,30,31,31,30,31,30,31] # 闰年各月天数(索引从1开始)
|
||||
unleapMonthDays = [0,31,28,31,30,31,30,31,31,30,31,30,31] # 平年各月天数
|
||||
```
|
||||
|
||||
> **用途**:快速查询某月最大天数,结合 `isLeapYear()` 使用。
|
||||
|
||||
---
|
||||
|
||||
## 3. 核心功能函数
|
||||
|
||||
### 3.1 `days_between(date_str1, date_str2)`
|
||||
|
||||
计算两个日期字符串之间的天数差(绝对值)。
|
||||
|
||||
#### 参数
|
||||
| 参数 | 类型 | 描述 |
|
||||
|-------------|--------|--------------------------|
|
||||
| `date_str1` | str | 起始日期,格式 `'YYYY-MM-DD'` |
|
||||
| `date_str2` | str | 结束日期,格式 `'YYYY-MM-DD'` |
|
||||
|
||||
#### 返回值
|
||||
- `int`:相差的天数(非负整数)
|
||||
|
||||
#### 示例
|
||||
```python
|
||||
days_between("2023-01-01", "2023-01-10") # 返回 9
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3.2 `monthfirstday()`
|
||||
|
||||
获取当前月份的第一天。
|
||||
|
||||
#### 返回值
|
||||
- `str`:格式为 `'YYYY-MM-01'`
|
||||
|
||||
#### 示例
|
||||
```python
|
||||
monthfirstday() # 如 '2025-04-01'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3.3 `curDatetime()`
|
||||
|
||||
获取当前精确时间的 `datetime` 对象。
|
||||
|
||||
#### 返回值
|
||||
- `datetime.datetime`:当前系统时间
|
||||
|
||||
---
|
||||
|
||||
### 3.4 `curDateString()`
|
||||
|
||||
获取当前日期字符串。
|
||||
|
||||
#### 返回值
|
||||
- `str`:格式 `'YYYY-MM-DD'`
|
||||
|
||||
---
|
||||
|
||||
### 3.5 `curTimeString()`
|
||||
|
||||
获取当前时间字符串(时分秒)。
|
||||
|
||||
#### 返回值
|
||||
- `str`:格式 `'HH:MM:SS'`
|
||||
|
||||
---
|
||||
|
||||
### 3.6 `timestampstr()`
|
||||
|
||||
生成带毫秒的完整时间戳字符串。
|
||||
|
||||
#### 返回值
|
||||
- `str`:格式 `'YYYY-MM-DD HH:MM:SS.fff'`
|
||||
|
||||
#### 示例
|
||||
```python
|
||||
timestampstr() # 如 '2025-04-05 14:30:22.123'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3.7 `isMonthLastDay(d)`
|
||||
|
||||
判断给定 `datetime` 是否是当月最后一天。
|
||||
|
||||
#### 参数
|
||||
| 参数 | 类型 | 描述 |
|
||||
|------|----------------|------------------|
|
||||
| `d` | `datetime` | 待判断的时间对象 |
|
||||
|
||||
#### 返回值
|
||||
- `bool`:若是月末返回 `True`
|
||||
|
||||
#### 原理
|
||||
通过加一天后判断月份是否变化。
|
||||
|
||||
---
|
||||
|
||||
### 3.8 `isLeapYear(year)`
|
||||
|
||||
判断是否为闰年。
|
||||
|
||||
> ⚠️ **注意**:原代码逻辑有误!
|
||||
|
||||
#### 错误分析
|
||||
```python
|
||||
if year % 4 == 0 and year % 100 == 0 and not (year % 400 == 0):
|
||||
```
|
||||
此条件仅在“能被100整除但不能被400整除”时返回 `True`,即把**平年**判为闰年。
|
||||
|
||||
#### 正确应为:
|
||||
```python
|
||||
def isLeapYear(year):
|
||||
return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
|
||||
```
|
||||
|
||||
> ✅ 建议修复该函数!
|
||||
|
||||
---
|
||||
|
||||
### 3.9 `timestamp(dt)` 和 `timeStampSecond(dt)`
|
||||
|
||||
将 `datetime` 转换为 Unix 时间戳。
|
||||
|
||||
| 函数名 | 精度 | 说明 |
|
||||
|-------------------|------------|------------------------------|
|
||||
| `timestamp(dt)` | 微秒级 | 包含 `.microsecond` |
|
||||
| `timeStampSecond(dt)` | 秒级 | 忽略微秒部分 |
|
||||
|
||||
#### 参数
|
||||
- `dt`: `datetime` 对象
|
||||
|
||||
#### 返回值
|
||||
- `int`:自 1970-01-01 UTC 起的秒数
|
||||
|
||||
---
|
||||
|
||||
### 3.10 `addSeconds(dt, s)`
|
||||
|
||||
给时间对象增加若干秒。
|
||||
|
||||
#### 参数
|
||||
| 参数 | 类型 | 说明 |
|
||||
|------|--------------|--------------------|
|
||||
| `dt` | `datetime` | 原始时间 |
|
||||
| `s` | `int` | 要增加的秒数 |
|
||||
|
||||
#### 返回值
|
||||
- `datetime`:新的时间对象
|
||||
|
||||
---
|
||||
|
||||
### 3.11 `monthMaxDay(y, m)`
|
||||
|
||||
获取指定年份某月的最大天数(考虑闰年)。
|
||||
|
||||
#### 参数
|
||||
| 参数 | 类型 | 说明 |
|
||||
|------|-------|------------|
|
||||
| `y` | int | 年份 |
|
||||
| `m` | int | 月份(1~12)|
|
||||
|
||||
#### 返回值
|
||||
- `int`:该月最多多少天
|
||||
|
||||
---
|
||||
|
||||
### 3.12 `date2str(dt=None)`
|
||||
|
||||
将 `datetime` 转为 `'YYYY-MM-DD'` 字符串。
|
||||
|
||||
#### 参数
|
||||
- `dt`: 可选 `datetime` 对象;若未传则使用当前时间
|
||||
|
||||
#### 返回值
|
||||
- `str`:格式化日期字符串
|
||||
|
||||
---
|
||||
|
||||
### 3.13 `time2str(dt)`
|
||||
|
||||
⚠️ **存在 Bug**
|
||||
|
||||
```python
|
||||
return '%02d:%02d:%02d' % (dt.hour, dt, minute, dt.second)
|
||||
```
|
||||
错误使用了 `dt,minute`(语法错误),应为 `dt.minute`。
|
||||
|
||||
#### 修正版本
|
||||
```python
|
||||
def time2str(dt):
|
||||
return '%02d:%02d:%02d' % (dt.hour, dt.minute, dt.second)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3.14 `str2Date(dstr)` 和 `str2Datetime(dstr)`
|
||||
|
||||
#### `str2Date(dstr)`
|
||||
尝试解析形如 `'YYYY-MM-DD [HH:MM:SS]'` 的字符串为 `datetime` 对象。
|
||||
|
||||
> **警告**:内部调用了不存在的 `ymdDate(...)` 函数前就执行拆分,且异常处理打印但不中断。
|
||||
|
||||
##### 改进建议:
|
||||
- 统一使用标准库 `datetime.strptime`
|
||||
- 移除冗余中间步骤
|
||||
|
||||
#### `str2Datetime(dstr)`
|
||||
更稳健的实现,推荐使用。
|
||||
|
||||
##### 支持格式
|
||||
- `'YYYY-MM-DD'`
|
||||
- `'YYYY-MM-DD HH:MM:SS'`
|
||||
|
||||
##### 返回值
|
||||
- `datetime` 对象
|
||||
|
||||
---
|
||||
|
||||
### 3.15 `ymdDate(y, m, d, H=0, M=0, S=0)`
|
||||
|
||||
构造 `datetime` 实例的快捷方式。
|
||||
|
||||
#### 参数
|
||||
年、月、日、时、分、秒均可指定,默认为0。
|
||||
|
||||
#### 示例
|
||||
```python
|
||||
ymdDate(2025, 4, 5, 10, 30) # 2025-04-05 10:30:00
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3.16 `strdate_add(date_str, days=0, months=0, years=0)`
|
||||
|
||||
对日期字符串进行加减操作并返回新字符串。
|
||||
|
||||
#### 参数
|
||||
支持同时添加天、月、年。
|
||||
|
||||
#### 执行顺序
|
||||
1. 加 `days`
|
||||
2. 加 `months`
|
||||
3. 加 `years`
|
||||
|
||||
> 自动处理跨月/跨年边界,并确保不会出现非法日期(如 2月30日)
|
||||
|
||||
#### 示例
|
||||
```python
|
||||
strdate_add("2024-01-31", months=1) # 返回 "2024-02-29"(自动调整到月底)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3.17 `addMonths(dt, months)` 和 `addYears(dt, years)`
|
||||
|
||||
#### `addMonths(dt, months)`
|
||||
安全地增加月份,自动修正超出范围的日期(如 1月31日 +1月 → 2月29日或28日)
|
||||
|
||||
#### `addYears(dt, years)`
|
||||
同理,考虑闰年导致2月可能只有28天。
|
||||
|
||||
---
|
||||
|
||||
### 3.18 `dateAdd(dt, days=0, months=0, years=0)`
|
||||
|
||||
综合日期加法入口函数,按顺序应用增量。
|
||||
|
||||
---
|
||||
|
||||
### 3.19 `firstSunday(dt)`
|
||||
|
||||
返回大于等于输入日期的第一个周日。
|
||||
|
||||
> 注意:Python 中 `weekday()` 返回 0~6 表示周一至周日。
|
||||
> 因此 `(7 - weekday()) % 7` 更准确。
|
||||
|
||||
当前实现假设 `weekday()==6` 是周六?实际应为周日。
|
||||
|
||||
#### 存疑逻辑
|
||||
```python
|
||||
f = dt.weekday()
|
||||
if f < 6:
|
||||
return dt + timedelta(7 - f)
|
||||
else:
|
||||
return dt
|
||||
```
|
||||
这表示:
|
||||
- 若不是周日(即 0~5),则加 `(7-f)` 天
|
||||
- 若是周日(6),直接返回
|
||||
|
||||
✅ 实际上这是正确的:`weekday()` 返回 0=周一 ... 6=周日
|
||||
|
||||
所以当 `f=6`(周日)时无需移动。
|
||||
|
||||
✔️ 此函数逻辑正确。
|
||||
|
||||
---
|
||||
|
||||
### 3.20 时间格式常量与函数
|
||||
|
||||
```python
|
||||
DTFORMAT = '%Y%m%d %H%M%S'
|
||||
```
|
||||
|
||||
#### `getCurrentTimeStamp()`
|
||||
获取当前时间按 `DTFORMAT` 格式的字符串。
|
||||
|
||||
#### `TimeStamp(t)`
|
||||
将 `struct_time` 格式化为上述格式。
|
||||
|
||||
#### `timestampAdd(ts1, ts2)`
|
||||
将两个时间字符串相加(`ts2` 可为秒数或时间字符串)
|
||||
|
||||
> ❌ 存在类型判断错误:
|
||||
```python
|
||||
if type(ts2)=='' :
|
||||
```
|
||||
应改为:
|
||||
```python
|
||||
if isinstance(ts2, str):
|
||||
```
|
||||
|
||||
否则会出错。
|
||||
|
||||
#### `timestampSub(ts1, ts2)`
|
||||
计算两个 `DTFORMAT` 时间之间的秒数差。
|
||||
|
||||
---
|
||||
|
||||
### 3.21 `StepedTimestamp(baseTs, ts, step)`
|
||||
|
||||
将时间 `ts` 对齐到以 `baseTs` 为起点、步长为 `step` 秒的时间网格。
|
||||
|
||||
#### 应用场景
|
||||
- 数据采样对齐
|
||||
- 定时任务触发点归一化
|
||||
|
||||
#### 参数
|
||||
| 参数 | 类型 | 说明 |
|
||||
|----------|--------|------------------------|
|
||||
| `baseTs` | str | 基准时间(DTFORMAT) |
|
||||
| `ts` | str | 当前时间 |
|
||||
| `step` | int | 步长(秒) |
|
||||
|
||||
#### 返回值
|
||||
- 最近的对齐时间点(向上或向下取整)
|
||||
|
||||
---
|
||||
|
||||
### 3.22 `timestamp2dt(t)`
|
||||
|
||||
将 Unix 时间戳(秒)转为 `datetime` 对象。
|
||||
|
||||
---
|
||||
|
||||
### 3.23 `date_weekinyear(date_str)`
|
||||
|
||||
获取日期所在年的第几周(ISO 周编号)。
|
||||
|
||||
#### 返回值
|
||||
- `str`:`'YYYY-W'` 形式,其中 `W` 为两位周数
|
||||
|
||||
> 使用 `%W`(基于周一开始的周),而非 ISO 标准 `%U` 或 `isocalendar()`
|
||||
|
||||
---
|
||||
|
||||
### 3.24 `date_season(date_str)`
|
||||
|
||||
根据月份划分季节:
|
||||
|
||||
| 月份 | 季度 |
|
||||
|----------|------|
|
||||
| 1–3 | 1 |
|
||||
| 4–6 | 2 |
|
||||
| 7–9 | 3 |
|
||||
| 10–12 | 4 |
|
||||
|
||||
#### 返回值
|
||||
- `str`:`'YYYYQ'`,如 `'20251'` 表示2025年第1季度
|
||||
|
||||
---
|
||||
|
||||
### 3.25 `str2date(sd)`
|
||||
|
||||
将 `'YYYY-MM-DD'` 字符串转为 `date` 对象。
|
||||
|
||||
#### 示例
|
||||
```python
|
||||
str2date("2025-04-05") # 返回 date(2025, 4, 5)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3.26 `is_monthend(dt)`
|
||||
|
||||
判断日期是否为月末。
|
||||
|
||||
#### 参数
|
||||
- `dt`: `str` 或 `date` 对象
|
||||
|
||||
#### 方法
|
||||
+1天后看月份是否改变。
|
||||
|
||||
---
|
||||
|
||||
### 3.27 `is_match_pattern(pattern, strdate)`
|
||||
|
||||
判断某日期是否符合特定调度模式。
|
||||
|
||||
#### 支持的模式
|
||||
|
||||
| 模式 | 含义 | 示例 |
|
||||
|--------------|----------------------------------|-------------------------|
|
||||
| `'D'` | 每日触发 | `'D'` |
|
||||
| `'W[0-6]'` | 每周星期X(0=周一...6=周日) | `'W0'`=每周一 |
|
||||
| `'M0'` | 每月最后一天 | `'M0'` |
|
||||
| `'Mdd'` | 每月第dd天 | `'M15'`=每月15日 |
|
||||
| `'Sx-dd'` | 每季度第x月第dd天 | `'S1-01'`=季初第一天 |
|
||||
| `'Ym-dd'` | 每年m月dd日 | `'Y12-25'`=每年12月25日 |
|
||||
|
||||
#### 返回值
|
||||
- `bool`:是否匹配
|
||||
|
||||
#### 注意事项
|
||||
- `Sx-dd` 中 `x %= 4`,即循环每4个月视为一个季度
|
||||
- 调试信息 `print(f'{m=}-{d=}, ...')` 建议移除生产环境
|
||||
|
||||
---
|
||||
|
||||
## 4. 已知问题汇总(建议修复)
|
||||
|
||||
| 函数名 | 问题描述 | 建议修改 |
|
||||
|----------------|------------------------------------------------|----------|
|
||||
| `isLeapYear` | 条件反向,导致逻辑错误 | ✅ 重写 |
|
||||
| `time2str` | 引用 `dt,minute` 报语法错误 | ✅ 修复拼写 |
|
||||
| `timestampAdd` | `type(ts2)==''` 永远为假 | ✅ 改为 `isinstance(ts2, str)` |
|
||||
| `str2Date` | 使用未定义函数 `ymdDate` 在 try 外部可能发生错误 | ✅ 重构或删除冗余逻辑 |
|
||||
| `str2Datetime` | 不支持毫秒 | ✅ 可拓展支持 |
|
||||
|
||||
---
|
||||
|
||||
## 5. 使用示例
|
||||
|
||||
```python
|
||||
# 计算间隔
|
||||
print(days_between("2023-01-01", "2023-12-31")) # 364
|
||||
|
||||
# 获取今天
|
||||
print(curDateString()) # 2025-04-05
|
||||
|
||||
# 加一个月
|
||||
print(strdate_add("2024-01-31", months=1)) # 2024-02-29
|
||||
|
||||
# 判断是否为月末
|
||||
print(is_monthend("2025-04-30")) # True
|
||||
|
||||
# 匹配每周五
|
||||
print(is_match_pattern("W4", "2025-04-11")) # True(假设是周五)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 总结
|
||||
|
||||
本工具包提供了丰富的日期时间操作能力,适合嵌入数据处理流水线或定时任务系统中。尽管存在少量 bug 和可优化空间,整体设计清晰、功能完整。
|
||||
|
||||
> 📌 推荐在正式使用前进行单元测试,并优先修复已知缺陷。
|
||||
|
||||
---
|
||||
|
||||
**版本**: v1.0
|
||||
**作者**: Auto-generated Documentation
|
||||
**更新时间**: 2025-04-05
|
||||
265
aidocs/timecost.md
Normal file
265
aidocs/timecost.md
Normal file
@ -0,0 +1,265 @@
|
||||
# `TimeCost` 性能计时工具技术文档
|
||||
|
||||
---
|
||||
|
||||
## 概述
|
||||
|
||||
`TimeCost` 是一个基于上下文管理器(Context Manager)的轻量级 Python 性能分析工具,用于记录代码块的执行时间。它通过装饰器模式和全局字典 `timerecord` 累积多个调用的时间开销,便于后续统计与分析。
|
||||
|
||||
该模块适用于开发调试、性能优化等场景,支持按名称分类记录耗时,并提供清除和展示功能。
|
||||
|
||||
> **注意**:此模块依赖于标准库 `time` 和 `datetime`,并使用了自定义的单例装饰器 `SingletonDecorator`(当前未使用,但已导入)。
|
||||
|
||||
---
|
||||
|
||||
## 模块依赖
|
||||
|
||||
```python
|
||||
import time
|
||||
import datetime
|
||||
from .Singleton import SingletonDecorator
|
||||
```
|
||||
|
||||
- `time`: 用于获取高精度时间戳。
|
||||
- `datetime`: 当前未实际使用,可考虑移除以减少冗余。
|
||||
- `SingletonDecorator`: 导入但未在类中应用,可能为预留扩展功能。
|
||||
|
||||
---
|
||||
|
||||
## 全局变量
|
||||
|
||||
```python
|
||||
timerecord = {}
|
||||
```
|
||||
|
||||
- 类型:字典(`dict`)
|
||||
- 键(key):字符串,表示计时任务的名称。
|
||||
- 值(value):浮点数列表,存储每次该任务的执行耗时(单位:秒)。
|
||||
|
||||
示例:
|
||||
```python
|
||||
{
|
||||
"database_query": [0.12, 0.15, 0.11],
|
||||
"file_load": [0.45, 0.38]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 核心类:`TimeCost`
|
||||
|
||||
### 类定义
|
||||
|
||||
```python
|
||||
class TimeCost:
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
```
|
||||
|
||||
- **作用**:初始化一个以 `name` 命名的计时器。
|
||||
- **参数**:
|
||||
- `name` (str): 计时任务的标识名称,用于区分不同的代码段。
|
||||
|
||||
---
|
||||
|
||||
### 上下文管理器方法
|
||||
|
||||
#### `__enter__`
|
||||
|
||||
```python
|
||||
def __enter__(self):
|
||||
self.begin_time = time.time()
|
||||
```
|
||||
|
||||
- 在进入 `with` 语句块时自动调用。
|
||||
- 记录当前时间作为起始时间戳(`begin_time`),单位为秒(float)。
|
||||
|
||||
#### `__exit__`
|
||||
|
||||
```python
|
||||
def __exit__(self, *args):
|
||||
self.end_time = time.time()
|
||||
d = timerecord.get(self.name, [])
|
||||
d.append(self.end_time - self.begin_time)
|
||||
timerecord[self.name] = d
|
||||
```
|
||||
|
||||
- 在退出 `with` 语句块时自动调用。
|
||||
- 计算耗时(`end_time - begin_time`),并将结果追加到对应名称的计时列表中。
|
||||
- 若该名称首次出现,则创建新列表。
|
||||
|
||||
---
|
||||
|
||||
### 方法说明
|
||||
|
||||
#### `clear(self)`
|
||||
|
||||
```python
|
||||
def clear(self):
|
||||
timerecord = {}
|
||||
```
|
||||
|
||||
> ⚠️ **注意:此方法存在严重缺陷**
|
||||
|
||||
- 此处的 `timerecord = {}` 仅在局部作用域内重新绑定变量,**不会修改外部全局字典**。
|
||||
- 实际上 **无任何效果**。
|
||||
|
||||
✅ **建议修复方案**:
|
||||
```python
|
||||
def clear(self):
|
||||
timerecord.clear() # 清空全局字典内容
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### `@classmethod clear_all(cls)`
|
||||
|
||||
```python
|
||||
@classmethod
|
||||
def clear_all(cls):
|
||||
timerecord = {}
|
||||
```
|
||||
|
||||
> ⚠️ 同样存在问题:局部赋值无法影响全局 `timerecord`
|
||||
|
||||
✅ **正确实现应为**:
|
||||
```python
|
||||
@classmethod
|
||||
def clear_all(cls):
|
||||
timerecord.clear()
|
||||
```
|
||||
|
||||
或:
|
||||
|
||||
```python
|
||||
@classmethod
|
||||
def clear_all(cls):
|
||||
global timerecord
|
||||
timerecord = {}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### `@classmethod clear(cls, name)`
|
||||
|
||||
```python
|
||||
@classmethod
|
||||
def clear(cls, name):
|
||||
timerecord[name] = []
|
||||
```
|
||||
|
||||
- 清除指定名称的所有历史耗时记录。
|
||||
- 如果 `name` 不存在,会自动创建一个空列表。
|
||||
- ✅ 此方法是线程不安全的,但在单线程环境下可用。
|
||||
|
||||
---
|
||||
|
||||
#### `@classmethod show(cls)`
|
||||
|
||||
```python
|
||||
@classmethod
|
||||
def show(cls):
|
||||
def getTimeCost(name):
|
||||
x = timerecord.get(name, [])
|
||||
if len(x) == 0:
|
||||
return 0, 0, 0
|
||||
return len(x), sum(x), sum(x)/len(x)
|
||||
|
||||
print('TimeCost ....')
|
||||
for name in timerecord.keys():
|
||||
print(name, *getTimeCost(name))
|
||||
```
|
||||
|
||||
- 打印所有已记录任务的统计信息。
|
||||
- 输出格式:`<name> <调用次数> <总耗时> <平均耗时>`
|
||||
- 示例输出:
|
||||
```
|
||||
TimeCost ....
|
||||
database_query 3 0.38 0.127
|
||||
file_load 2 0.83 0.415
|
||||
```
|
||||
|
||||
##### 子函数 `getTimeCost(name)`
|
||||
- 返回三元组:`(调用次数, 总耗时, 平均耗时)`
|
||||
- 若无记录,则返回 `(0, 0, 0)`
|
||||
|
||||
---
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 基本用法
|
||||
|
||||
```python
|
||||
from your_module import TimeCost
|
||||
|
||||
# 测量某段代码的执行时间
|
||||
with TimeCost("test_function"):
|
||||
time.sleep(1)
|
||||
|
||||
with TimeCost("test_function"):
|
||||
time.sleep(0.5)
|
||||
|
||||
# 显示统计结果
|
||||
TimeCost.show()
|
||||
```
|
||||
|
||||
**输出示例**:
|
||||
```
|
||||
TimeCost ....
|
||||
test_function 2 1.5 0.75
|
||||
```
|
||||
|
||||
### 清除记录
|
||||
|
||||
```python
|
||||
# 清除特定任务记录
|
||||
TimeCost.clear("test_function")
|
||||
|
||||
# 或清除全部(需修复后才有效)
|
||||
TimeCost.clear_all()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 已知问题与改进建议
|
||||
|
||||
| 问题 | 描述 | 建议 |
|
||||
|------|------|-------|
|
||||
| `clear()` 方法无效 | 局部变量赋值无法修改全局状态 | 改用 `timerecord.clear()` 或 `global` 关键字 |
|
||||
| `clear_all()` 方法无效 | 同上 | 同上 |
|
||||
| `datetime` 未使用 | 冗余导入 | 可移除 |
|
||||
| 非线程安全 | 多线程下可能产生竞争条件 | 如需并发支持,应加锁机制 |
|
||||
| 缺少持久化/导出功能 | 仅支持打印 | 可增加 `.to_dict()` 或 `.export_json()` 方法 |
|
||||
|
||||
---
|
||||
|
||||
## 安全性与注意事项
|
||||
|
||||
- 该类不是线程安全的。多线程环境中使用可能导致数据错乱。
|
||||
- 所有计时数据保存在内存中,程序重启后丢失。
|
||||
- 名称冲突会导致数据合并,请确保不同逻辑使用唯一 `name`。
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
`TimeCost` 提供了一种简洁优雅的方式对代码块进行性能采样,适合快速定位性能瓶颈。尽管目前存在一些作用域相关的 bug,但结构清晰、易于理解和扩展。
|
||||
|
||||
经适当修复后,可作为项目中的轻量级性能监控组件。
|
||||
|
||||
---
|
||||
|
||||
📌 **推荐修复后的完整类片段(关键部分)**:
|
||||
|
||||
```python
|
||||
@classmethod
|
||||
def clear_all(cls):
|
||||
timerecord.clear() # 或使用 global timerecord; timerecord = {}
|
||||
|
||||
def clear(self):
|
||||
timerecord.clear()
|
||||
|
||||
@classmethod
|
||||
def clear(cls, name):
|
||||
timerecord[name] = []
|
||||
```
|
||||
300
aidocs/tworkers.md
Normal file
300
aidocs/tworkers.md
Normal file
@ -0,0 +1,300 @@
|
||||
# 多线程任务处理框架技术文档
|
||||
|
||||
本项目实现了一个基于 Python `threading` 和 `queue` 模块的轻量级多线程任务调度系统,支持异步执行可调用函数(callable),适用于 I/O 密集型任务如网络请求、文件读写等。
|
||||
|
||||
---
|
||||
|
||||
## 目录
|
||||
|
||||
- [1. 概述](#1-概述)
|
||||
- [2. 核心组件](#2-核心组件)
|
||||
- [2.1 `Worker` 类](#21-worker-类)
|
||||
- [2.2 `ThreadWorkers` 类](#22-threadworkers-类)
|
||||
- [3. 使用示例](#3-使用示例)
|
||||
- [4. API 参考](#4-api-参考)
|
||||
- [5. 注意事项与限制](#5-注意事项与限制)
|
||||
- [6. 依赖项](#6-依赖项)
|
||||
|
||||
---
|
||||
|
||||
## 1. 概述
|
||||
|
||||
该模块提供一个简单的线程池机制,通过预创建一组工作线程来异步执行提交的任务。所有任务通过队列传递给空闲的工作线程进行处理,避免频繁创建和销毁线程带来的开销。
|
||||
|
||||
主要特点:
|
||||
- 支持任意可调用对象(函数、方法、lambda 等)作为任务。
|
||||
- 支持传递位置参数和关键字参数。
|
||||
- 提供优雅关闭机制等待所有任务完成。
|
||||
- 基于标准库,无外部复杂依赖(除使用场景中引入的第三方库)。
|
||||
|
||||
---
|
||||
|
||||
## 2. 核心组件
|
||||
|
||||
### 2.1 `Worker` 类
|
||||
|
||||
`Worker` 是继承自 `threading.Thread` 的工作线程类,负责从任务队列中获取并执行任务。
|
||||
|
||||
#### 属性
|
||||
| 属性 | 类型 | 描述 |
|
||||
|------|------|------|
|
||||
| `r_queue` | `Queue` | 接收任务的任务队列实例 |
|
||||
| `timeout` | `int/float` | 获取任务时的超时时间(秒) |
|
||||
| `daemon` | `bool` | 是否为守护线程(设为 `False`) |
|
||||
|
||||
> ⚠️ 注:虽然设置了 `setDaemon(False)`,但实际默认行为是主线程退出后不会强制终止这些线程,需手动调用 `wait_for_complete()` 保证清理。
|
||||
|
||||
#### 方法
|
||||
|
||||
##### `__init__(self, rqueue, timeout=1)`
|
||||
初始化工作线程。
|
||||
|
||||
- **参数**:
|
||||
- `rqueue` (`Queue`):任务队列对象。
|
||||
- `timeout` (`float`, 默认 `1`):从队列取任务的阻塞超时时间。
|
||||
|
||||
自动启动线程(调用 `.start()`)。
|
||||
|
||||
##### `run(self)`
|
||||
线程主循环逻辑:
|
||||
|
||||
1. 循环尝试从 `r_queue` 中取出任务(格式为 `[callable, args, kw]`)。
|
||||
2. 若取到的任务中 `callable` 为 `None`,则视为停止信号,线程退出。
|
||||
3. 否则执行 `callable(*args, **kw)`。
|
||||
4. 队列为空时捕获 `Empty` 异常,并休眠 1 秒后继续尝试。
|
||||
|
||||
> 🛑 当前线程在 `Empty` 异常时使用 `time.sleep(1)`,可能导致最多 1 秒延迟响应新任务或退出信号。
|
||||
|
||||
##### `resulthandler(self, rez)`
|
||||
预留方法,用于处理任务返回结果。当前为空实现,用户可子类化重写此方法以实现回调功能。
|
||||
|
||||
> ✅ 扩展建议:若需结果收集,可在 `add_job` 时传入 `result_callback` 并在此方法中触发。
|
||||
|
||||
---
|
||||
|
||||
### 2.2 `ThreadWorkers` 类
|
||||
|
||||
管理多个 `Worker` 实例的线程池控制器。
|
||||
|
||||
#### 属性
|
||||
| 属性 | 类型 | 描述 |
|
||||
|------|------|------|
|
||||
| `workQueue` | `Queue` | 存放待处理任务的队列 |
|
||||
| `worker_cnt` | `int` | 工作线程数量 |
|
||||
| `workers` | `list[Worker]` | 所有工作线程的列表 |
|
||||
|
||||
#### 方法
|
||||
|
||||
##### `__init__(self, num_workers=20)`
|
||||
构造函数,创建指定数量的工作线程。
|
||||
|
||||
- **参数**:
|
||||
- `num_workers` (`int`, 默认 `20`):线程池大小。
|
||||
|
||||
内部调用 `__createThreadPool(num_workers)` 创建线程。
|
||||
|
||||
##### `__createThreadPool(self, num)`
|
||||
私有方法,创建 `num` 个 `Worker` 实例并加入 `self.workers` 列表。
|
||||
|
||||
- **参数**:
|
||||
- `num` (`int`):要创建的线程数。
|
||||
|
||||
每个线程共享同一个 `workQueue`。
|
||||
|
||||
##### `add_job(self, callable, args=[], kw={})`
|
||||
向任务队列添加一个任务。
|
||||
|
||||
- **参数**:
|
||||
- `callable` (`callable`):可调用对象(函数等)。若为 `None`,表示停止信号。
|
||||
- `args` (`list`):位置参数列表。
|
||||
- `kw` (`dict`):关键字参数字典。
|
||||
|
||||
> 💡 示例:`tw.add_job(print, ['Hello'], {'end': '!\\n'})`
|
||||
|
||||
任务以 `[callable, args, kw]` 形式放入队列。
|
||||
|
||||
##### `wait_for_complete(self)`
|
||||
等待所有任务执行完毕并回收线程资源。
|
||||
|
||||
操作流程:
|
||||
1. 向队列发送 `worker_cnt` 个 `None` 任务(作为每个线程的退出信号)。
|
||||
2. 遍历 `workers` 列表,逐个调用 `join()` 等待其结束。
|
||||
3. 清空 `workers` 列表。
|
||||
|
||||
> ✅ 必须调用此方法确保所有线程正常退出,防止程序挂起。
|
||||
|
||||
---
|
||||
|
||||
## 3. 使用示例
|
||||
|
||||
以下是一个使用 `requests` 发起大量 HTTP 请求的示例:
|
||||
|
||||
```python
|
||||
import requests
|
||||
from thread_worker import ThreadWorkers # 假设保存为 thread_worker.py
|
||||
|
||||
def get(url):
|
||||
x = requests.get(url)
|
||||
print(x.status_code)
|
||||
|
||||
# 创建线程池(默认20个线程)
|
||||
tw = ThreadWorkers()
|
||||
|
||||
# 添加10000个任务
|
||||
for i in range(10000):
|
||||
tw.add_job(get, ['http://www.baidu.com'])
|
||||
|
||||
# 等待所有任务完成
|
||||
tw.wait_for_complete()
|
||||
print('finished')
|
||||
```
|
||||
|
||||
输出示例:
|
||||
```
|
||||
200
|
||||
200
|
||||
...
|
||||
finished
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. API 参考
|
||||
|
||||
| 函数/类 | 签名 | 说明 |
|
||||
|--------|------|------|
|
||||
| `Worker` | `Worker(rqueue: Queue, timeout: float = 1)` | 工作线程,自动启动 |
|
||||
| `Worker.run()` | `-> None` | 主循环,处理任务或退出 |
|
||||
| `Worker.resulthandler(rez)` | `(rez: Any) -> None` | 预留结果处理器 |
|
||||
| `ThreadWorkers` | `ThreadWorkers(num_workers: int = 20)` | 初始化线程池 |
|
||||
| `ThreadWorkers.add_job()` | `(callable, args: list = [], kw: dict = {})` | 添加任务 |
|
||||
| `ThreadWorkers.wait_for_complete()` | `() -> None` | 发送退出信号并等待所有线程结束 |
|
||||
|
||||
---
|
||||
|
||||
## 5. 注意事项与限制
|
||||
|
||||
⚠️ **已知问题与改进建议**:
|
||||
|
||||
1. **缺少 `import time`**
|
||||
- 在 `Worker.run()` 中使用了 `time.sleep(1)`,但未导入 `time` 模块。
|
||||
- ❌ 错误:运行时报 `NameError: name 'time' is not defined`
|
||||
- ✅ 修复:在文件顶部添加 `import time`
|
||||
|
||||
2. **任务结果无法获取**
|
||||
- 当前设计不支持任务返回值的传递。
|
||||
- ✅ 建议扩展:可通过 `concurrent.futures.Future` 或回调函数机制增强。
|
||||
|
||||
3. **高延迟响应**
|
||||
- `get(timeout=self.timeout)` + `except Empty: sleep(1)` 导致最大延迟达 `timeout + 1` 秒。
|
||||
- ✅ 建议:将 `sleep(1)` 改为更小值或移除,直接依赖 `get(timeout)` 轮询。
|
||||
|
||||
4. **不可复用线程池**
|
||||
- 调用 `wait_for_complete()` 后线程已退出,不能再次提交任务。
|
||||
- ✅ 如需复用,应重新设计“动态增减线程”或“重启线程池”机制。
|
||||
|
||||
5. **异常未捕获**
|
||||
- 任务执行过程中抛出异常会中断线程。
|
||||
- ✅ 建议在外层加 `try-except` 包裹 `callable(*args,**kw)` 并记录错误。
|
||||
|
||||
6. **线程安全**
|
||||
- `Queue` 是线程安全的,整体结构合理。
|
||||
- 不推荐手动修改 `workers` 或 `workQueue`。
|
||||
|
||||
---
|
||||
|
||||
## 6. 依赖项
|
||||
|
||||
- Python 3.6+
|
||||
- 标准库:
|
||||
- `sys`
|
||||
- `threading`
|
||||
- `queue`
|
||||
- `time`(需显式导入,原代码遗漏)
|
||||
|
||||
可选第三方库(仅示例使用):
|
||||
- `requests`:用于演示网络请求任务
|
||||
|
||||
---
|
||||
|
||||
## 附录:修复后的完整代码(建议版本)
|
||||
|
||||
```python
|
||||
import sys
|
||||
import threading
|
||||
import time # 缺失的导入
|
||||
from threading import Thread
|
||||
from queue import Queue, Empty
|
||||
|
||||
|
||||
class Worker(Thread):
|
||||
def __init__(self, rqueue, timeout=1):
|
||||
Thread.__init__(self)
|
||||
self.timeout = timeout
|
||||
self.setDaemon(False) # 主线程结束后是否强制退出
|
||||
self.r_queue = rqueue
|
||||
self.start()
|
||||
|
||||
def run(self):
|
||||
while True:
|
||||
try:
|
||||
task = self.r_queue.get(timeout=self.timeout)
|
||||
if task is None or task[0] is None:
|
||||
break
|
||||
callable_func, args, kw = task
|
||||
try:
|
||||
result = callable_func(*args, **kw)
|
||||
self.resulthandler(result)
|
||||
except Exception as e:
|
||||
print(f"Task error: {e}", file=sys.stderr)
|
||||
except Empty:
|
||||
continue # 继续轮询,无需额外 sleep
|
||||
|
||||
def resulthandler(self, rez):
|
||||
pass
|
||||
|
||||
|
||||
class ThreadWorkers:
|
||||
def __init__(self, num_workers=20):
|
||||
self.workQueue = Queue()
|
||||
self.worker_cnt = num_workers
|
||||
self.workers = []
|
||||
self.__createThreadPool(num_workers)
|
||||
|
||||
def __createThreadPool(self, num):
|
||||
for _ in range(num):
|
||||
thread = Worker(self.workQueue)
|
||||
self.workers.append(thread)
|
||||
|
||||
def wait_for_complete(self):
|
||||
for _ in range(self.worker_cnt):
|
||||
self.workQueue.put([None, None, None]) # 发送退出信号
|
||||
|
||||
while self.workers:
|
||||
worker = self.workers.pop()
|
||||
if worker.is_alive():
|
||||
worker.join()
|
||||
|
||||
def add_job(self, callable_func, args=None, kw=None):
|
||||
if args is None:
|
||||
args = []
|
||||
if kw is None:
|
||||
kw = {}
|
||||
self.workQueue.put([callable_func, args, kw])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import requests
|
||||
|
||||
def get(url):
|
||||
x = requests.get(url)
|
||||
print(x.status_code)
|
||||
|
||||
tw = ThreadWorkers(num_workers=10)
|
||||
for i in range(100):
|
||||
tw.add_job(get, ['http://www.baidu.com'])
|
||||
tw.wait_for_complete()
|
||||
print('finished')
|
||||
```
|
||||
|
||||
> ✅ 此版本修复了 `time` 导入缺失、异常处理、延迟优化等问题,更具健壮性。
|
||||
240
aidocs/udp_comm.md
Normal file
240
aidocs/udp_comm.md
Normal file
@ -0,0 +1,240 @@
|
||||
# `UdpComm` 技术文档
|
||||
|
||||
```markdown
|
||||
# UdpComm - UDP 通信模块技术文档
|
||||
|
||||
## 概述
|
||||
|
||||
`UdpComm` 是一个基于 Python 的异步 UDP 通信类,支持单播发送、广播、接收 JSON 或二进制数据,并通过后台线程处理 I/O 多路复用(`select`),适用于轻量级网络服务或设备发现等场景。
|
||||
|
||||
该模块封装了 UDP 套接字的基本操作,提供非阻塞接收与带缓冲的发送机制,同时支持自定义回调函数处理接收到的数据。
|
||||
|
||||
---
|
||||
|
||||
## 依赖说明
|
||||
|
||||
### 第三方/内部依赖
|
||||
- `appPublic.sockPackage.get_free_local_addr`: 获取本机可用 IP 地址。
|
||||
- `appPublic.background.Background`: 用于在后台运行任务的线程包装器。
|
||||
|
||||
> ⚠️ 注意:这两个模块属于项目私有库 `appPublic`,需确保其已安装并可导入。
|
||||
|
||||
### 内置模块
|
||||
- `socket`: 提供底层网络通信功能。
|
||||
- `select`: 实现 I/O 多路复用。
|
||||
- `json`: 用于序列化和反序列化 JSON 数据。
|
||||
- `time`: 控制循环延迟。
|
||||
- `traceback.print_exc`: 打印异常堆栈信息。
|
||||
|
||||
---
|
||||
|
||||
## 常量
|
||||
|
||||
| 名称 | 值 | 说明 |
|
||||
|------------|--------------|------|
|
||||
| `BUFSIZE` | `1024 * 64` | 接收缓冲区大小,即每次最多接收 65536 字节 |
|
||||
|
||||
---
|
||||
|
||||
## 类定义:`UdpComm`
|
||||
|
||||
### 构造函数:`__init__(self, port, callback, timeout=0)`
|
||||
|
||||
初始化 UDP 通信实例。
|
||||
|
||||
#### 参数:
|
||||
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|
||||
|-----------|----------|------|--------|------|
|
||||
| `port` | int | 是 | - | 绑定的本地 UDP 端口号 |
|
||||
| `callback`| function | 是 | - | 回调函数,用于处理接收到的数据,签名应为 `callback(data, addr)` |
|
||||
| `timeout` | float | 否 | 0 | 超时时间(未启用) |
|
||||
|
||||
#### 初始化行为:
|
||||
- 自动获取本机 IP 地址(用于后续广播计算)。
|
||||
- 创建并绑定 UDP 套接字到指定端口 (`('', port)`)。
|
||||
- 启动后台线程执行 `run()` 方法,持续监听消息。
|
||||
- 初始化空发送缓冲队列 `buffer`。
|
||||
|
||||
> 📌 注:当前版本中 `setblocking` 和 `settimeout` 已被注释,实际为非阻塞模式配合 `select` 使用。
|
||||
|
||||
---
|
||||
|
||||
### 核心方法
|
||||
|
||||
#### `run(self)`
|
||||
主循环逻辑,由后台线程执行。
|
||||
|
||||
##### 功能流程:
|
||||
1. 使用 `select([sock], outs, [], 0.1)` 监听:
|
||||
- 输入事件:是否有数据可读。
|
||||
- 输出事件:是否可以发送数据(当缓冲区不为空时触发)。
|
||||
2. 若有数据到达:
|
||||
- 调用 `recvfrom()` 读取数据。
|
||||
- 解析首字节标识符:
|
||||
- `'b'` → 视为原始 **bytes** 数据,直接传递给回调。
|
||||
- 其他 → 视为 UTF-8 编码的 JSON 字符串,尝试反序列化后传入回调。
|
||||
- 出错时打印异常及原始数据,但不停止运行(仅中断本次处理)。
|
||||
3. 若可写且缓冲区有数据:
|
||||
- 从 `buffer` 中取出第一条待发消息并使用 `sendto()` 发送。
|
||||
4. 循环结束条件:`self.run_flg == False`
|
||||
5. 最终关闭套接字。
|
||||
|
||||
> ⏱️ 每次循环休眠 `0.1` 秒以降低 CPU 占用。
|
||||
|
||||
---
|
||||
|
||||
#### `stop(self)`
|
||||
安全停止通信线程。
|
||||
|
||||
##### 行为:
|
||||
- 设置标志位 `run_flg = False`,通知 `run()` 结束循环。
|
||||
- 关闭 UDP 套接字。
|
||||
- 等待后台线程退出(`join()`)。
|
||||
|
||||
> ✅ 推荐在程序退出前调用此方法释放资源。
|
||||
|
||||
---
|
||||
|
||||
#### `broadcast(self, data)`
|
||||
向局域网广播消息(目标地址 `.255`)。
|
||||
|
||||
##### 参数:
|
||||
| 参数名 | 类型 | 说明 |
|
||||
|-------|------|------|
|
||||
| `data` | any (serializable) or bytes | 要广播的数据 |
|
||||
|
||||
##### 处理逻辑:
|
||||
- 若 `data` 非 `bytes` 类型,则将其 JSON 序列化并编码为 UTF-8。
|
||||
- 构造广播地址(如本机 IP 为 `192.168.1.100`,则广播地址为 `192.168.1.255`)。
|
||||
- 创建临时 UDP 客户端套接字,启用广播选项(`SO_BROADCAST=1`)。
|
||||
- 发送数据到 `(broadcast_host, self.port)`。
|
||||
|
||||
> 🔊 广播不会经过 `send()` 缓冲区,是即时发送。
|
||||
|
||||
---
|
||||
|
||||
#### `send(self, data, addr)`
|
||||
将数据加入发送缓冲区,异步发送至指定地址。
|
||||
|
||||
##### 参数:
|
||||
| 参数名 | 类型 | 说明 |
|
||||
|-------|--------------|------|
|
||||
| `data` | str / dict / bytes | 待发送数据 |
|
||||
| `addr` | tuple or list | 目标地址 `(ip, port)` |
|
||||
|
||||
##### 数据封装规则:
|
||||
- 若 `data` 不是 `bytes`:添加前缀 `'j'`,表示 JSON 数据。
|
||||
- 若 `data` 是 `bytes`:添加前缀 `'b'`,表示二进制数据。
|
||||
|
||||
> 示例:
|
||||
> - `send({"msg": "hello"}, ("192.168.1.2", 5000))` → 实际发送 `'j{"msg":"hello"}'`
|
||||
> - `send(b'\x01\x02', ("192.168.1.2", 5000))` → 实际发送 `'b\x01\x02'`
|
||||
|
||||
> 🔄 数据会被追加到 `self.buffer`,由 `run()` 线程异步发送。
|
||||
|
||||
---
|
||||
|
||||
#### `sends(self, data, addrs)`
|
||||
批量发送相同数据到多个地址。
|
||||
|
||||
##### 参数:
|
||||
| 参数名 | 类型 | 说明 |
|
||||
|--------|-------------|------|
|
||||
| `data` | any / bytes | 要发送的数据 |
|
||||
| `addrs`| list of tuple/list | 多个目标地址列表 |
|
||||
|
||||
##### 行为:
|
||||
遍历 `addrs` 列表,对每个地址调用 `self.send(data, addr)`。
|
||||
|
||||
---
|
||||
|
||||
## 数据格式约定
|
||||
|
||||
| 首字节 | 含义 | 数据内容 |
|
||||
|--------|------------|------------------------|
|
||||
| `'b'` | Binary | 原始二进制数据(无编码) |
|
||||
| `'j'` | JSON | UTF-8 编码的 JSON 字符串 |
|
||||
|
||||
接收端根据首字节判断如何解析后续数据。
|
||||
|
||||
---
|
||||
|
||||
## 回调函数规范
|
||||
|
||||
用户提供的 `callback` 函数必须接受两个参数:
|
||||
|
||||
```python
|
||||
def callback(data, addr):
|
||||
# data: 解码后的对象(dict / list / bytes)
|
||||
# addr: 发送方地址 tuple(ip: str, port: int)
|
||||
pass
|
||||
```
|
||||
|
||||
示例:
|
||||
```python
|
||||
def msg_handle(data, addr):
|
||||
print('addr:', addr, 'data=', data)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 作为服务器启动并监听
|
||||
|
||||
```bash
|
||||
python udpcomm.py 50000
|
||||
```
|
||||
|
||||
交互式输入格式:`目标端口:消息内容`
|
||||
|
||||
例如:
|
||||
```
|
||||
50001:{"cmd": "discover"}
|
||||
```
|
||||
→ 将 JSON 数据发送到 `localhost:50001`(IP 为空时自动替换为 `''`)
|
||||
|
||||
### 在代码中使用
|
||||
|
||||
```python
|
||||
def on_message(data, addr):
|
||||
print(f"Received from {addr}: {data}")
|
||||
|
||||
# 启动监听
|
||||
comm = UdpComm(port=50000, callback=on_message)
|
||||
|
||||
# 发送消息
|
||||
comm.send({"hello": "world"}, ("192.168.1.50", 50000))
|
||||
|
||||
# 广播
|
||||
comm.broadcast({"type": "service_announce"})
|
||||
|
||||
# 停止服务
|
||||
comm.stop()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 注意事项与限制
|
||||
|
||||
1. ❗ **线程安全**:`buffer` 未加锁,若多线程调用 `send()` 可能存在竞争风险。建议外部同步或改用线程安全队列。
|
||||
2. 💤 **性能**:每轮 `sleep(0.1)` 可能影响实时性;可根据需要调整间隔。
|
||||
3. 🧹 **错误处理**:JSON 解析失败仅打印日志,不会终止服务。
|
||||
4. 🌐 **广播范围**:依赖子网掩码配置,`.255` 未必总是有效广播地址。
|
||||
5. 🚫 **IPv6 不支持**:目前仅限 IPv4。
|
||||
|
||||
---
|
||||
|
||||
## TODO 改进建议
|
||||
|
||||
- [ ] 添加日志模块替代 `print`
|
||||
- [ ] 使用 `queue.Queue` 替代 `list` 实现线程安全缓冲
|
||||
- [ ] 支持 IPv6
|
||||
- [ ] 增加单元测试
|
||||
- [ ] 提供上下文管理器(`with` 支持)
|
||||
|
||||
---
|
||||
```
|
||||
|
||||
> 文档版本:v1.0
|
||||
> 最后更新:2025-04-05
|
||||
254
aidocs/uni_outip.md
Normal file
254
aidocs/uni_outip.md
Normal file
@ -0,0 +1,254 @@
|
||||
# 外网 IP 获取工具技术文档
|
||||
|
||||
```markdown
|
||||
# 外网 IP 获取工具(External IP Getter)
|
||||
|
||||
一个基于多协议尝试获取外网公网 IP 地址的 Python 工具,支持 NAT-PMP、UPnP 以及第三方网页抓取方式。通过多进程隔离输出流的方式安全地获取 IP 地址。
|
||||
|
||||
---
|
||||
|
||||
## 目录
|
||||
|
||||
- [功能概述](#功能概述)
|
||||
- [依赖库](#依赖库)
|
||||
- [核心模块与函数说明](#核心模块与函数说明)
|
||||
- [`pmp_get_external_ip()`](#pmp_get_external_ip)
|
||||
- [`upnp_get_external_ip()`](#upnp_get_external_ip)
|
||||
- [`ipgetter_get_external_ip()`](#ipgetter_get_external_ip)
|
||||
- [`get_external_ip()`](#get_external_ip)
|
||||
- [`outip(w)`](#outipw)
|
||||
- [`get_ip()`](#get_ip)
|
||||
- [`run()`](#run)
|
||||
- [主程序入口](#主程序入口)
|
||||
- [使用示例](#使用示例)
|
||||
- [注意事项](#注意事项)
|
||||
- [未来优化建议](#未来优化建议)
|
||||
|
||||
---
|
||||
|
||||
## 功能概述
|
||||
|
||||
本工具旨在可靠地获取当前设备所在的公网 IP 地址,采用多种协议逐级回退策略:
|
||||
|
||||
1. **NAT-PMP**:适用于支持该协议的路由器(如部分 Apple 设备)。
|
||||
2. **UPnP IGD**:通用即插即用互联网网关设备协议,广泛用于家庭路由器。
|
||||
3. **IPGetter 网页抓取**:当本地网络协议不可用时,通过访问公网服务获取 IP。
|
||||
|
||||
为避免跨进程通信中的阻塞和异常影响主流程,使用 `multiprocessing.Pipe` 和子进程隔离执行 IP 获取逻辑。
|
||||
|
||||
---
|
||||
|
||||
## 依赖库
|
||||
|
||||
| 库名 | 用途 |
|
||||
|------|------|
|
||||
| `natpmp` | 实现 NAT-PMP 协议以获取公网 IP |
|
||||
| `upnpclient` | 发现并调用 UPnP IGD 服务 |
|
||||
| `appPublic.ipgetter` | 第三方 IP 获取器(基于网页抓取) |
|
||||
| `multiprocessing` | 多进程支持,用于隔离 IO 操作 |
|
||||
| `os`, `time` | 系统操作与延时控制 |
|
||||
|
||||
> ⚠️ 安装命令:
|
||||
```bash
|
||||
pip install natpmp upnpclient app-public
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 核心模块与函数说明
|
||||
|
||||
### `pmp_get_external_ip()`
|
||||
|
||||
尝试通过 **NAT-PMP** 协议获取公网 IP。
|
||||
|
||||
#### 返回值
|
||||
- 成功时返回字符串格式的公网 IP(如 `"203.0.113.45"`)
|
||||
- 失败或异常时返回 `None`
|
||||
|
||||
#### 示例
|
||||
```python
|
||||
ip = pmp_get_external_ip()
|
||||
if ip:
|
||||
print("NAT-PMP 获取成功:", ip)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `upnp_get_external_ip()`
|
||||
|
||||
通过 **UPnP IGD**(Internet Gateway Device)协议从路由器获取公网 IP。
|
||||
|
||||
#### 流程说明
|
||||
1. 调用 `upnpclient.discover()` 自动发现局域网内支持 UPnP 的网关设备。
|
||||
2. 提取第一个设备,并查找包含 `'WAN'` 和 `'Conn'` 的服务名(通常是 WANIPConnection 或类似)。
|
||||
3. 调用 `GetExternalIPAddress()` 方法获取外部 IP。
|
||||
4. 解析响应中 `NewExternalIPAddress` 字段。
|
||||
|
||||
#### 返回值
|
||||
- 成功时返回公网 IP 字符串
|
||||
- 出错时打印错误信息并返回 `None`
|
||||
|
||||
> 📌 注意:若有多台 UPnP 设备,请注意选择正确的设备实例。
|
||||
|
||||
---
|
||||
|
||||
### `ipgetter_get_external_ip()`
|
||||
|
||||
使用第三方网页服务获取公网 IP,作为最后兜底方案。
|
||||
|
||||
#### 特点
|
||||
- 使用 `appPublic.ipgetter.IPgetter` 类进行 HTTP 请求抓取。
|
||||
- 内置重试机制,每 0.1 秒尝试一次直到成功。
|
||||
- 忽略请求过程中的所有异常。
|
||||
|
||||
#### 返回值
|
||||
- 成功获取后立即返回 IP 字符串
|
||||
- 不会永久阻塞
|
||||
|
||||
---
|
||||
|
||||
### `get_external_ip()`
|
||||
|
||||
**主协调函数**:按优先级顺序尝试三种方法获取 IP。
|
||||
|
||||
#### 执行顺序
|
||||
1. NAT-PMP
|
||||
2. UPnP
|
||||
3. Web 抓取(IPGetter)
|
||||
|
||||
一旦任一方法成功即返回结果,形成“短路”逻辑。
|
||||
|
||||
#### 返回值
|
||||
- 公网 IP 字符串(推荐始终检查非空)
|
||||
- 若全部失败,则返回 `None`
|
||||
|
||||
---
|
||||
|
||||
### `outip(w)`
|
||||
|
||||
在独立进程中运行的函数,将获取到的 IP 输出到指定写入端 `w`。
|
||||
|
||||
#### 参数
|
||||
- `w`: 可写的文件描述符管道(来自 `Pipe()` 的写入端)
|
||||
|
||||
#### 原理
|
||||
- 使用 `os.dup2(w.fileno(), 1)` 将标准输出重定向至管道
|
||||
- 调用 `get_external_ip()` 并通过 `print(ip)` 输出
|
||||
- 子进程结束后,父进程可通过读取管道获得结果
|
||||
|
||||
> ✅ 优点:避免复杂对象传递,利用文本流简化 IPC。
|
||||
|
||||
---
|
||||
|
||||
### `get_ip()`
|
||||
|
||||
主进程调用此函数来启动子进程并安全获取 IP。
|
||||
|
||||
#### 步骤
|
||||
1. 创建匿名管道 `Pipe()`
|
||||
2. 打开读取端为文件对象
|
||||
3. 启动子进程执行 `outip(w)`
|
||||
4. 读取子进程输出的第一行
|
||||
5. 等待子进程结束
|
||||
6. 返回去除了换行符的 IP 字符串
|
||||
|
||||
#### 返回值
|
||||
- 成功:公网 IP 字符串(如 `"8.8.8.8"`)
|
||||
- 失败:空字符串(需判断有效性)
|
||||
|
||||
---
|
||||
|
||||
### `run()`
|
||||
|
||||
持续运行的测试循环函数。
|
||||
|
||||
#### 行为
|
||||
- 每 10 秒调用一次 `get_ip()`
|
||||
- 若获取到 IP,则打印:`ip='203.0.113.45'`
|
||||
- 使用 `time.sleep(10)` 控制频率
|
||||
|
||||
可用于长期监控公网 IP 变化。
|
||||
|
||||
---
|
||||
|
||||
## 主程序入口
|
||||
|
||||
```python
|
||||
if __name__ == '__main__':
|
||||
run()
|
||||
```
|
||||
|
||||
当脚本直接运行时,进入无限循环模式,每隔 10 秒输出当前公网 IP。
|
||||
|
||||
---
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 单次获取 IP
|
||||
```python
|
||||
from your_module import get_ip
|
||||
|
||||
ip = get_ip()
|
||||
print("Current Public IP:", ip)
|
||||
```
|
||||
|
||||
### 集成进其他应用
|
||||
可将 `get_external_ip()` 直接导入使用,无需多进程封装:
|
||||
|
||||
```python
|
||||
from your_module import get_external_ip
|
||||
|
||||
public_ip = get_external_ip()
|
||||
if public_ip:
|
||||
print(f"Public IP is {public_ip}")
|
||||
else:
|
||||
print("Failed to retrieve public IP")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **权限要求**
|
||||
- UPnP/NAT-PMP 需要设备处于同一局域网且路由器启用相应功能。
|
||||
- 某些防火墙可能阻止 SSDP(UPnP 发现协议)广播包。
|
||||
|
||||
2. **性能与延迟**
|
||||
- `ipgetter_get_external_ip()` 依赖网络请求,可能较慢。
|
||||
- 推荐仅作为备用手段。
|
||||
|
||||
3. **稳定性问题**
|
||||
- `upnpclient.discover()` 可能因网络波动返回空列表,导致索引错误。
|
||||
- 建议添加超时处理或重试逻辑。
|
||||
|
||||
4. **多进程开销**
|
||||
- `get_ip()` 每次创建新进程,不适合高频调用(如 >1次/秒)。
|
||||
|
||||
5. **输出重定向风险**
|
||||
- `os.dup2` 修改了子进程的标准输出,确保只在子进程中使用。
|
||||
|
||||
---
|
||||
|
||||
## 未来优化建议
|
||||
|
||||
| 改进项 | 描述 |
|
||||
|--------|------|
|
||||
| 添加超时机制 | 给每个 IP 获取方法设置最大等待时间 |
|
||||
| 缓存最近 IP | 减少重复查询开销 |
|
||||
| 支持更多协议 | 如 PCP(Port Control Protocol) |
|
||||
| 异常日志记录 | 替代 `print()`,使用 `logging` 模块 |
|
||||
| 配置化策略 | 允许用户配置启用哪些协议及顺序 |
|
||||
| 异步支持 | 使用 `asyncio` + `aiohttp` 提升效率 |
|
||||
|
||||
---
|
||||
|
||||
## 许可证
|
||||
|
||||
请根据项目实际情况补充 LICENSE 信息。
|
||||
|
||||
> 示例:MIT License
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
✅ 文档完成。可保存为 `README.md` 或集成至 Sphinx/Docusaurus 文档系统。
|
||||
221
aidocs/unicoding.md
Normal file
221
aidocs/unicoding.md
Normal file
@ -0,0 +1,221 @@
|
||||
# `unidict.py` 技术文档
|
||||
|
||||
本模块提供了一组用于处理 Python 中字符串编码转换的工具函数,主要用于将字节串(`bytes`)安全地转换为 Unicode 字符串(`str`),并递归地对复杂数据结构(如字典、列表)中的字符串进行统一编码处理。适用于 Python 2/3 兼容环境或需要处理混合编码数据的场景。
|
||||
|
||||
---
|
||||
|
||||
## 模块依赖
|
||||
|
||||
```python
|
||||
import locale
|
||||
```
|
||||
|
||||
- 使用 `locale.getdefaultlocale()` 获取系统默认编码,作为解码失败时的备用方案。
|
||||
|
||||
---
|
||||
|
||||
## 函数说明
|
||||
|
||||
### `unicoding(d, coding='utf8')`
|
||||
|
||||
将输入对象 `d` 转换为 Unicode 字符串(`str` 类型)。支持字符串、字节串等类型的输入,并具备多级解码容错机制。
|
||||
|
||||
#### 参数
|
||||
|
||||
| 参数 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `d` | `str`, `bytes`, 或其他 | 待转换的对象。通常为字符串或字节串。 |
|
||||
| `coding` | `str` | 建议使用的编码格式,默认为 `'utf8'`。 |
|
||||
|
||||
#### 返回值
|
||||
|
||||
- 若 `d` 已是 `str` 类型,直接返回。
|
||||
- 若 `d` 是 `bytes` 类型,则尝试按指定编码解码为 `str`。
|
||||
- 解码失败时,依次尝试:系统默认编码 → UTF-8 → 原始值(不解码)。
|
||||
- 其他类型直接返回原对象。
|
||||
|
||||
#### 解码优先级顺序
|
||||
|
||||
1. 使用参数 `coding` 指定的编码(若非 `None`)
|
||||
2. 系统默认编码(通过 `locale.getdefaultlocale()[1]` 获取)
|
||||
3. `utf8`
|
||||
4. 若全部失败,返回原始 `bytes` 对象
|
||||
|
||||
> ⚠️ 注意:代码中存在拼写错误 `Noene` 应为 `None`,这会导致逻辑错误!
|
||||
|
||||
#### 示例
|
||||
|
||||
```python
|
||||
>>> unicoding(b'hello')
|
||||
'hello'
|
||||
|
||||
>>> unicoding('already unicode')
|
||||
'already unicode'
|
||||
|
||||
>>> unicoding(b'\xe4\xb8\xad\xe6\x96\x87', 'utf8')
|
||||
'中文'
|
||||
```
|
||||
|
||||
#### 修复建议
|
||||
|
||||
原代码中:
|
||||
|
||||
```python
|
||||
if coding is not Noene:
|
||||
```
|
||||
|
||||
应更正为:
|
||||
|
||||
```python
|
||||
if coding is not None:
|
||||
```
|
||||
|
||||
否则会抛出 `NameError: name 'Noene' is not defined`。
|
||||
|
||||
---
|
||||
|
||||
### `uObject(obj, coding='utf8')`
|
||||
|
||||
递归处理任意对象,将其内部的字符串或可解码对象转换为 Unicode 字符串。
|
||||
|
||||
#### 参数
|
||||
|
||||
| 参数 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `obj` | 任意类型 | 输入对象(如字符串、列表、字典、自定义对象等) |
|
||||
| `coding` | `str` | 解码所用编码,默认 `'utf8'` |
|
||||
|
||||
#### 行为逻辑
|
||||
|
||||
| 输入类型 | 处理方式 |
|
||||
|---------|----------|
|
||||
| `str`(Unicode) | 直接返回 |
|
||||
| `dict` | 调用 `uDict(obj, coding)` 进行递归处理 |
|
||||
| `list` 或 `tuple` | 遍历每个元素,递归调用 `uObject(i, coding)` |
|
||||
| 具有 `.decode` 方法的对象(如 `bytes`) | 调用其 `decode(coding)` 方法 |
|
||||
| 其他类型 | 不处理,原样返回 |
|
||||
|
||||
#### 返回值
|
||||
|
||||
转换后的对象,所有字符串均尽可能转为 Unicode。
|
||||
|
||||
#### 示例
|
||||
|
||||
```python
|
||||
>>> uObject([b'item1', b'item2'])
|
||||
['item1', 'item2']
|
||||
|
||||
>>> uObject({'key': b'value'})
|
||||
{'key': 'value'}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `uDict(dict, coding='utf8')`
|
||||
|
||||
专门用于将字典中所有的键和值(包括嵌套结构)转换为 Unicode 字符串。
|
||||
|
||||
#### 参数
|
||||
|
||||
| 参数 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `dict` | `dict` | 输入字典 |
|
||||
| `coding` | `str` | 编码方式,默认 `'utf8'` |
|
||||
|
||||
#### 实现逻辑
|
||||
|
||||
- 创建新字典 `d`
|
||||
- 遍历原字典的所有 `(k, v)` 键值对
|
||||
- 对键 `k` 和值 `v` 分别调用 `uObject(...)` 进行递归转换
|
||||
- 将结果存入新字典并返回
|
||||
|
||||
#### 特点
|
||||
|
||||
- 支持嵌套字典、列表等复合结构
|
||||
- 安全处理不可变类型与非字符串类型
|
||||
|
||||
#### 示例
|
||||
|
||||
```python
|
||||
data = {
|
||||
b'name': b'张三',
|
||||
b'hobbies': [b'读书', b'游泳'],
|
||||
b'meta': {b'city': b'北京'}
|
||||
}
|
||||
|
||||
result = uDict(data)
|
||||
# 输出:
|
||||
# {'name': '张三', 'hobbies': ['读书', '游泳'], 'meta': {'city': '北京'}}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 使用场景
|
||||
|
||||
- 处理来自网络、文件或数据库的混合编码数据
|
||||
- 在 Web 开发中清洗请求参数或 JSON 数据
|
||||
- 跨平台兼容性处理(尤其是 Windows 和 Linux 编码差异)
|
||||
- 构建健壮的数据预处理管道
|
||||
|
||||
---
|
||||
|
||||
## 注意事项与改进建议
|
||||
|
||||
### ❗ Bug 提示
|
||||
|
||||
```python
|
||||
if coding is not Noene:
|
||||
```
|
||||
|
||||
这是明显的拼写错误,`Noene` 应改为 `None`,否则运行时报错。
|
||||
|
||||
✅ 正确写法:
|
||||
|
||||
```python
|
||||
if coding is not None:
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### ✅ 建议改进点
|
||||
|
||||
1. **增加类型注解**(Python 3+ 更佳体验)
|
||||
|
||||
```python
|
||||
def unicoding(d: bytes, coding: str = 'utf8') -> str:
|
||||
```
|
||||
|
||||
2. **使用 `six` 或 `typing` 提高兼容性**
|
||||
|
||||
可引入 `six.string_types`, `six.binary_type` 来更好地区分文本与二进制类型。
|
||||
|
||||
3. **日志记录代替静默捕获异常**
|
||||
|
||||
当前使用 `except:` 捕获所有异常,不利于调试。建议记录警告信息。
|
||||
|
||||
4. **避免重复解码尝试**
|
||||
|
||||
可以预先获取系统编码,减少多次调用 `locale.getdefaultlocale()`
|
||||
|
||||
5. **性能优化**
|
||||
|
||||
对于大型嵌套结构,递归调用可能影响性能,可考虑迭代实现或缓存机制。
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
`unidict.py` 是一个轻量但实用的编码统一工具模块,特别适合在处理不确定编码来源的数据时使用。尽管存在一个小 bug(`Noene`),但整体设计清晰,功能明确,经过适当修正后可在生产环境中稳定使用。
|
||||
|
||||
---
|
||||
|
||||
## 版本信息
|
||||
|
||||
- 作者:未知
|
||||
- 语言:Python 2/3 兼容风格
|
||||
- 文件名:`unidict.py`
|
||||
- 最后修订:需修复 `Noene` 错误
|
||||
|
||||
---
|
||||
|
||||
📌 **推荐用途**:数据清洗、API 接口预处理、日志解析、配置加载等涉及编码转换的场景。
|
||||
175
aidocs/uniqueID.md
Normal file
175
aidocs/uniqueID.md
Normal file
@ -0,0 +1,175 @@
|
||||
# 技术文档:ID 生成与校验模块
|
||||
|
||||
---
|
||||
|
||||
## 概述
|
||||
|
||||
本模块提供了一套轻量级的 ID 生成、验证码生成与校验功能,适用于需要唯一标识符(ID)并附带简单校验码的场景。模块基于 `nanoid` 库生成短小唯一的 ID,并通过自定义算法生成和验证校验码。
|
||||
|
||||
---
|
||||
|
||||
## 依赖库
|
||||
|
||||
- `uuid`: (当前未实际使用,可移除)
|
||||
- `nanoid`: 用于生成短、唯一、URL 安全的 ID
|
||||
|
||||
> 安装 nanoid:
|
||||
> ```bash
|
||||
> pip install nanoid
|
||||
> ```
|
||||
|
||||
---
|
||||
|
||||
## 函数说明
|
||||
|
||||
### `setNode(n='ff001122334455')`
|
||||
|
||||
**功能**
|
||||
占位函数,预留用于设置节点标识(如分布式系统中的节点 ID),当前为空实现。
|
||||
|
||||
**参数**
|
||||
| 参数 | 类型 | 默认值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| `n` | str | `'ff001122334455'` | 节点标识字符串(十六进制格式) |
|
||||
|
||||
**返回值**
|
||||
无
|
||||
|
||||
**示例**
|
||||
```python
|
||||
setNode('aa11bb22cc33')
|
||||
```
|
||||
|
||||
> ⚠️ 注意:当前函数体为空(`pass`),仅为未来扩展预留。
|
||||
|
||||
---
|
||||
|
||||
### `getID(size=21)`
|
||||
|
||||
**功能**
|
||||
使用 `nanoid` 生成指定长度的唯一 ID。
|
||||
|
||||
**参数**
|
||||
| 参数 | 类型 | 默认值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| `size` | int | `21` | 生成 ID 的字符长度 |
|
||||
|
||||
**返回值**
|
||||
- **类型**: `str`
|
||||
- **说明**: 返回一个长度为 `size` 的随机、唯一、URL 安全的字符串 ID
|
||||
|
||||
**示例**
|
||||
```python
|
||||
id1 = getID() # 例如: "V1StGXR8_Z5jdHi6B-myT"
|
||||
id2 = getID(10) # 例如: "kLmNoPqRsT"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `validate_code(id, cnt=6)`
|
||||
|
||||
**功能**
|
||||
根据输入的 ID 字符串生成一个固定长度的数字验证码。算法将 ID 分成若干段,对每段字符的 ASCII 值求和,并取模 10 得到一位数字。
|
||||
|
||||
**参数**
|
||||
| 参数 | 类型 | 默认值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| `id` | str | 必填 | 输入的 ID 字符串 |
|
||||
| `cnt` | int | `6` | 期望生成的验证码位数(最多不超过分段数) |
|
||||
|
||||
**返回值**
|
||||
- **类型**: `str`
|
||||
- **说明**: 返回由数字组成的验证码字符串,长度为 `cnt`
|
||||
|
||||
**算法逻辑**
|
||||
1. 计算每段长度:`b = len(id) // cnt`
|
||||
2. 遍历 `id` 中每个字符,累加其 ASCII 值
|
||||
3. 每累计 `b` 个字符后,取总和模 10 作为一位验证码
|
||||
4. 达到 `cnt` 位后停止
|
||||
|
||||
**调试输出**
|
||||
- 打印 `b`(每段字符数)和 `cnt`(验证码位数)
|
||||
|
||||
**示例**
|
||||
```python
|
||||
code = validate_code("abc123xyz", 3)
|
||||
# 假设 b=3,则:
|
||||
# 第一段 "abc" -> (97+98+99) % 10 = 294 % 10 = 4
|
||||
# 第二段 "123" -> (49+50+51) % 10 = 150 % 10 = 0
|
||||
# 第三段 "xyz" -> (120+121+122) % 10 = 363 % 10 = 3
|
||||
# 结果: "403"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `check_code(id, code)`
|
||||
|
||||
**功能**
|
||||
校验给定的验证码是否与 ID 通过 `validate_code` 生成的验证码一致。
|
||||
|
||||
**参数**
|
||||
| 参数 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `id` | str | 原始 ID 字符串 |
|
||||
| `code` | str | 待校验的验证码 |
|
||||
|
||||
**返回值**
|
||||
- **类型**: `bool`
|
||||
- **说明**: 若验证码匹配返回 `True`,否则返回 `False`
|
||||
|
||||
**示例**
|
||||
```python
|
||||
result = check_code("abc123xyz", "403") # 取决于 validate_code 的结果
|
||||
print(result) # True 或 False
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 主程序示例(`__main__`)
|
||||
|
||||
演示了 ID 生成、验证码生成与校验的完整流程。
|
||||
|
||||
```python
|
||||
if __name__ == '__main__':
|
||||
id = getID() # 生成 ID
|
||||
code = validate_code(id) # 生成验证码
|
||||
b = check_code(id, code) # 校验验证码
|
||||
print(id, code, b) # 输出 ID、验证码和校验结果
|
||||
```
|
||||
|
||||
**输出示例**
|
||||
```
|
||||
b=3, cnt=6
|
||||
V1StGXR8_Z5jdHi6B-myT 258314 True
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 使用场景
|
||||
|
||||
- 短链接系统中的唯一 ID 与人工校验码
|
||||
- 激活码、邀请码生成与验证
|
||||
- 分布式系统中轻量级 ID 校验机制
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. `validate_code` 中的 `print(f'{b=}, {cnt=}')` 为调试信息,生产环境中建议移除或改为日志输出。
|
||||
2. 当前 `setNode` 未实现功能,若无需保留可删除。
|
||||
3. `uuid` 模块导入但未使用,建议移除以减少冗余依赖。
|
||||
4. 校验码算法较为简单,不适用于高安全场景,仅适合防误输或基础校验。
|
||||
|
||||
---
|
||||
|
||||
## 未来优化建议
|
||||
|
||||
- 将校验算法替换为 CRC 或哈希截断以提高健壮性
|
||||
- 支持自定义字符集和种子提升安全性
|
||||
- 添加异常处理(如空 ID 输入等)
|
||||
- 使用 `logging` 替代 `print` 进行调试输出
|
||||
|
||||
---
|
||||
|
||||
✅ **版本**: 1.0
|
||||
📅 **最后更新**: 2025-04-05
|
||||
34
aidocs/version.md
Normal file
34
aidocs/version.md
Normal file
@ -0,0 +1,34 @@
|
||||
# 技术文档
|
||||
|
||||
## 模块版本信息
|
||||
|
||||
### 概述
|
||||
本文件定义了当前软件模块的版本号。版本号遵循语义化版本控制规范(Semantic Versioning)。
|
||||
|
||||
### 版本变量
|
||||
```python
|
||||
__version__ = '5.5.0'
|
||||
```
|
||||
|
||||
### 字段说明
|
||||
- **`__version__`**: 模块级变量,用于存储当前版本的字符串表示
|
||||
- **值**: `'5.5.0'` - 遵循 `主版本号.次版本号.修订号` 的格式
|
||||
|
||||
### 版本号含义
|
||||
根据语义化版本控制规范:
|
||||
- **主版本号 (5)**: 表示重大更新,可能包含不兼容的API变更
|
||||
- **次版本号 (5)**: 表示向后兼容的功能新增
|
||||
- **修订号 (0)**: 表示向后兼容的问题修复
|
||||
|
||||
### 使用场景
|
||||
此版本变量可用于:
|
||||
- 程序运行时检查当前版本
|
||||
- 包管理系统的版本标识
|
||||
- API文档中的版本标注
|
||||
- 调试和错误报告时的环境信息
|
||||
|
||||
### 示例用法
|
||||
```python
|
||||
import your_module
|
||||
print(f"当前版本: {your_module.__version__}")
|
||||
```
|
||||
118
aidocs/wav.md
Normal file
118
aidocs/wav.md
Normal file
@ -0,0 +1,118 @@
|
||||
# 音频格式转换工具文档
|
||||
|
||||
## 概述
|
||||
|
||||
该模块提供了一个简单的音频处理函数,用于将任意 `.wav` 音频文件转换为 **16kHz 采样率、单声道(Mono)** 的标准格式。此格式常用于语音识别、语音合成等语音处理任务中,以确保输入音频满足模型要求。
|
||||
|
||||
使用了 `pydub` 库进行音频操作,依赖于 `ffmpeg` 或 `libav` 作为后端处理引擎。
|
||||
|
||||
---
|
||||
|
||||
## 依赖库
|
||||
|
||||
- `pydub`
|
||||
- `ffmpeg`(需系统安装)
|
||||
|
||||
### 安装方法
|
||||
|
||||
```bash
|
||||
pip install pydub
|
||||
```
|
||||
|
||||
请确保系统中已安装 `ffmpeg`:
|
||||
- **macOS**: `brew install ffmpeg`
|
||||
- **Ubuntu/Debian**: `sudo apt-get install ffmpeg`
|
||||
- **Windows**: 下载并添加 `ffmpeg` 到系统环境变量,或通过 [https://ffmpeg.org](https://ffmpeg.org) 安装
|
||||
|
||||
---
|
||||
|
||||
## 函数说明
|
||||
|
||||
### `convert_to_16k_mono(in_wav_path: str, out_wav_path: str) -> None`
|
||||
|
||||
将指定路径的 WAV 音频文件转换为 16kHz 采样率、单声道格式,并保存到输出路径。
|
||||
|
||||
#### 参数
|
||||
|
||||
| 参数名 | 类型 | 说明 |
|
||||
|----------------|--------|------------------------------|
|
||||
| `in_wav_path` | `str` | 输入音频文件路径(必须是 WAV 格式) |
|
||||
| `out_wav_path` | `str` | 输出音频文件保存路径 |
|
||||
|
||||
#### 功能
|
||||
|
||||
1. 使用 `AudioSegment.from_wav()` 加载输入音频。
|
||||
2. 将音频采样率设置为 16000 Hz。
|
||||
3. 将声道数设置为 1(单声道)。
|
||||
4. 导出处理后的音频为 WAV 格式至指定路径。
|
||||
|
||||
#### 示例用法
|
||||
|
||||
```python
|
||||
convert_to_16k_mono("input.wav", "output_16k_mono.wav")
|
||||
```
|
||||
|
||||
#### 注意事项
|
||||
|
||||
- 输入文件必须是有效的 `.wav` 文件。
|
||||
- 输出目录需存在,否则会抛出异常。
|
||||
- 若输出文件已存在,将被覆盖。
|
||||
|
||||
---
|
||||
|
||||
## 典型应用场景
|
||||
|
||||
- 为 ASR(自动语音识别)系统准备标准化输入音频。
|
||||
- 统一不同来源音频的格式以便批量处理。
|
||||
- 减少音频数据大小(降低采样率和声道数)。
|
||||
|
||||
---
|
||||
|
||||
## 完整示例代码
|
||||
|
||||
```python
|
||||
from pydub import AudioSegment
|
||||
|
||||
def convert_to_16k_mono(in_wav_path, out_wav_path):
|
||||
"""
|
||||
将WAV音频转换为16kHz、单声道格式
|
||||
|
||||
参数:
|
||||
in_wav_path (str): 输入音频路径
|
||||
out_wav_path (str): 输出音频路径
|
||||
"""
|
||||
audio = AudioSegment.from_wav(in_wav_path)
|
||||
audio = audio.set_frame_rate(16000).set_channels(1)
|
||||
audio.export(out_wav_path, format="wav")
|
||||
|
||||
# 使用示例
|
||||
# convert_to_16k_mono("input.wav", "output_16k_mono.wav")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 错误处理建议(扩展功能)
|
||||
|
||||
虽然当前版本未包含异常处理,但在生产环境中建议添加如下改进:
|
||||
|
||||
```python
|
||||
import os
|
||||
from pydub import AudioSegment
|
||||
|
||||
def convert_to_16k_mono(in_wav_path, out_wav_path):
|
||||
if not os.path.exists(in_wav_path):
|
||||
raise FileNotFoundError(f"输入文件不存在: {in_wav_path}")
|
||||
|
||||
try:
|
||||
audio = AudioSegment.from_wav(in_wav_path)
|
||||
audio = audio.set_frame_rate(16000).set_channels(1)
|
||||
audio.export(out_wav_path, format="wav")
|
||||
except Exception as e:
|
||||
raise RuntimeError(f"音频转换失败: {e}")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 许可与版权
|
||||
|
||||
本代码为开源示例,遵循 MIT 风格许可,可用于学习、研究及商业项目(请根据实际依赖库的许可证合规使用)。
|
||||
245
aidocs/wcag_checker.md
Normal file
245
aidocs/wcag_checker.md
Normal file
@ -0,0 +1,245 @@
|
||||
# 颜色对比度计算工具技术文档
|
||||
|
||||
本模块提供了一组用于计算颜色对比度和评估其是否符合 **WCAG(Web Content Accessibility Guidelines)** 标准的函数。这些函数可用于网页设计、UI 开发等场景中,确保文本与背景之间的可读性和无障碍访问性。
|
||||
|
||||
---
|
||||
|
||||
## 目录
|
||||
|
||||
- [功能概述](#功能概述)
|
||||
- [函数说明](#函数说明)
|
||||
- [`calculate_luminance(rgba)`](#calculate_luminancergba)
|
||||
- [`get_contrast_ratio(lumA, lumB)`](#get_contrast_ratioluma-lumb)
|
||||
- [`get_color_contrast_ratio(color1, color2)`](#get_color_contrast_ratiocolor1-color2)
|
||||
- [`wcag_check(color1, color2, font_size=14)`](#wcag_checkcolor1-color2-font_size14)
|
||||
- [使用示例](#使用示例)
|
||||
- [注意事项与已知问题](#注意事项与已知问题)
|
||||
- [参考标准](#参考标准)
|
||||
|
||||
---
|
||||
|
||||
## 功能概述
|
||||
|
||||
该模块实现了以下核心功能:
|
||||
|
||||
1. 计算给定 RGBA 颜色的相对亮度(Luminance)。
|
||||
2. 计算两种颜色之间的对比度比值。
|
||||
3. 判断对比度是否满足 WCAG 2.0 的 AA 或 AAA 级别要求,考虑字体大小的影响。
|
||||
|
||||
> ⚠️ 当前代码存在多个拼写错误和变量名错误,实际使用前必须修复!
|
||||
|
||||
---
|
||||
|
||||
## 函数说明
|
||||
|
||||
### `calculate_luminance(rgba)`
|
||||
|
||||
**功能:**
|
||||
计算输入颜色的相对亮度(Relative Luminance),依据 [WCAG 2.0 Suggestion E2](https://www.w3.org/TR/WCAG20-TECHS/G17.html#G17-CalcStep) 中定义的加权公式。
|
||||
|
||||
**参数:**
|
||||
- `rgba` (`tuple`): 表示颜色的四元组 `(R, G, B, A)`,其中 R、G、B 为 0–255 范围内的整数,A(Alpha)可选(不参与计算)。
|
||||
|
||||
> ✅ 注意:此函数仅使用 RGB 分量,忽略 Alpha 通道。
|
||||
|
||||
**返回值:**
|
||||
- `float`: 相对亮度值,范围在 `0.0`(黑色)到 `1.0`(白色)之间。
|
||||
|
||||
**公式:**
|
||||
```
|
||||
L = 0.2126 * R + 0.7152 * G + 0.0722 * B
|
||||
```
|
||||
其中 R、G、B 应先归一化为 0–1 范围(即除以 255),并应用伽马校正(sRGB 转线性亮度)。但当前实现未包含归一化与伽马校正步骤,**这是一个严重缺陷!**
|
||||
|
||||
> 🔴 **警告:当前实现有误!**
|
||||
> - 变量名错误:`color` 和 `colr` 混用 → 应统一为 `rgba`
|
||||
> - 缺少归一化处理(需将 0–255 映射到 0–1)
|
||||
> - 缺少 sRGB 到线性亮度的转换(非线性响应)
|
||||
|
||||
✅ 正确做法应如下:
|
||||
```python
|
||||
def srgb_to_linear(c):
|
||||
c_norm = c / 255.0
|
||||
return c_norm / 12.92 if c_norm <= 0.04045 else ((c_norm + 0.055) / 1.055) ** 2.4
|
||||
|
||||
lum = 0.2126 * srgb_to_linear(r) +
|
||||
0.7152 * srgb_to_linear(g) +
|
||||
0.0722 * srgb_to_linear(b)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `get_contrast_ratio(lumA, lumB)`
|
||||
|
||||
**功能:**
|
||||
根据两个颜色的相对亮度计算它们之间的对比度比值。
|
||||
|
||||
**参数:**
|
||||
- `lumA`, `lumB` (`float`): 两个颜色的相对亮度值(来自 `calculate_luminance` 输出)
|
||||
|
||||
**返回值:**
|
||||
- `float`: 对比率,取值范围通常为 `1:1`(无对比)到 `21:1`(最大对比)
|
||||
|
||||
**公式:**
|
||||
```
|
||||
contrast = (lighter + 0.05) / (darker + 0.05)
|
||||
```
|
||||
|
||||
> ⚠️ 错误:函数体内使用了未定义的变量 `lumX`,应为 `lumA`
|
||||
|
||||
🔴 存在 Bug:
|
||||
```python
|
||||
darker = min(lumX, lumB) # ❌ lumX 未定义
|
||||
```
|
||||
✅ 应改为:
|
||||
```python
|
||||
darker = min(lumA, lumB)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `get_color_contrast_ratio(color1, color2)`
|
||||
|
||||
**功能:**
|
||||
直接传入两个颜色,自动计算其对比度比值。
|
||||
|
||||
**参数:**
|
||||
- `color1`, `color2`: 合法的颜色元组 `(R, G, B[, A])`
|
||||
|
||||
**返回值:**
|
||||
- `float`: 两颜色间的对比度比值
|
||||
|
||||
⚠️ 错误:调用了不存在的函数 `get_contrast_Ratio`(大小写错误)
|
||||
|
||||
🔴 原始代码错误:
|
||||
```python
|
||||
return get_contrast_Ratio(lum1, lum2) # ❌ 函数名拼写错误
|
||||
```
|
||||
✅ 应为:
|
||||
```python
|
||||
return get_contrast_ratio(lum1, lum2)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `wcag_check(color1, color2, font_size=14)`
|
||||
|
||||
**功能:**
|
||||
判断两个颜色组合是否满足 WCAG 的可访问性标准(AA 和 AAA 级别)。
|
||||
|
||||
**参数:**
|
||||
- `color1`, `color2`: 颜色元组 `(R, G, B)`
|
||||
- `font_size` (`int` or `float`): 字体大小(像素),用于决定对比度阈值
|
||||
|
||||
**返回值:**
|
||||
- `tuple(bool, bool)`:
|
||||
- 第一个布尔值表示是否达到 **AA 级别**
|
||||
- 第二个布尔值表示是否达到 **AAA 级别**
|
||||
|
||||
**逻辑规则:**
|
||||
|
||||
| 字体大小 | AA 标准 | AAA 标准 |
|
||||
|----------------|---------|---------|
|
||||
| ≥ 18px 或粗体≥14px | 3.0 | 4.5 |
|
||||
| < 18px | 4.5 | 7.0 |
|
||||
|
||||
> ⚠️ 实际 WCAG 规则更复杂,涉及“大字号”定义(18pt 正常 / 14pt 粗体 ≈ 24px),此处简化处理。
|
||||
|
||||
🔴 当前代码错误:
|
||||
```python
|
||||
return ratio >= aa, radio >= aaa # ❌ 'radio' 是拼写错误
|
||||
```
|
||||
✅ 应为:
|
||||
```python
|
||||
return ratio >= aa, ratio >= aaa
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 使用示例
|
||||
|
||||
假设我们想检查白色文字 `(255, 255, 255)` 在深蓝背景 `(0, 0, 128)` 上的可读性:
|
||||
|
||||
```python
|
||||
# 示例(需先修复所有 bug 后方可运行)
|
||||
|
||||
white = (255, 255, 255)
|
||||
blue = (0, 0, 128)
|
||||
|
||||
aa, aaa = wcag_check(white, blue, font_size=16)
|
||||
print(f"AA compliant: {aa}") # True?
|
||||
print(f"AAA compliant: {aaa}") # False?
|
||||
```
|
||||
|
||||
预期输出(修复后):
|
||||
```
|
||||
AA compliant: True
|
||||
AAA compliant: False
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 注意事项与已知问题
|
||||
|
||||
📌 **当前代码存在多个致命错误,不能直接使用!**
|
||||
|
||||
| 问题 | 描述 | 修复建议 |
|
||||
|------|------|----------|
|
||||
| `color` / `colr` / `lumX` 变量名错误 | 引用未定义变量导致运行时异常 | 统一变量名为正确拼写 |
|
||||
| `get_contrast_Ratio` 大小写错误 | Python 区分大小写,函数不存在 | 改为 `get_contrast_ratio` |
|
||||
| `radio` 拼写错误 | 导致 NameError | 改为 `ratio` |
|
||||
| 未归一化 RGB 值 | 输入是 0–255,但公式期望 0–1 | 添加 `/ 255.0` 并应用伽马校正 |
|
||||
| 缺少伽马校正 | 直接线性计算亮度不准确 | 使用 sRGB → 线性转换函数 |
|
||||
| 字体大小判断逻辑不完整 | 未考虑粗体情况 | 建议增加 `bold` 参数 |
|
||||
|
||||
---
|
||||
|
||||
## 推荐修复版本(完整修正版)
|
||||
|
||||
```python
|
||||
def srgb_to_linear(c):
|
||||
"""Convert sRGB component to linear light."""
|
||||
c_norm = c / 255.0
|
||||
return c_norm / 12.92 if c_norm <= 0.04045 else ((c_norm + 0.055) / 1.055) ** 2.4
|
||||
|
||||
def calculate_luminance(rgba):
|
||||
r, g, b = rgba[:3]
|
||||
lr = srgb_to_linear(r)
|
||||
lg = srgb_to_linear(g)
|
||||
lb = srgb_to_linear(b)
|
||||
return 0.2126 * lr + 0.7152 * lg + 0.0722 * lb
|
||||
|
||||
def get_contrast_ratio(lumA, lumB):
|
||||
lighter = max(lumA, lumB)
|
||||
darker = min(lumA, lumB)
|
||||
return (lighter + 0.05) / (darker + 0.05)
|
||||
|
||||
def get_color_contrast_ratio(color1, color2):
|
||||
lum1 = calculate_luminance(color1)
|
||||
lum2 = calculate_luminance(color2)
|
||||
return get_contrast_ratio(lum1, lum2)
|
||||
|
||||
def wcag_check(color1, color2, font_size=14, bold=False):
|
||||
# 简化判断:>=18px 或 (>=14px 且粗体) 视为“大字号”
|
||||
is_large_text = font_size >= 18 or (bold and font_size >= 14)
|
||||
|
||||
aa_threshold = 3.0 if is_large_text else 4.5
|
||||
aaa_threshold = 4.5 if is_large_text else 7.0
|
||||
|
||||
ratio = get_color_contrast_ratio(color1, color2)
|
||||
return ratio >= aa_threshold, ratio >= aaa_threshold
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 参考标准
|
||||
|
||||
- [WCAG 2.0 Guideline 1.4.3: Contrast (Minimum)](https://www.w3.org/TR/UNDERSTANDING-WCAG20/visual-audio-contrast-contrast.html)
|
||||
- [Understanding Success Criterion 1.4.3 | W3C](https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum.html)
|
||||
- [Relative Luminance Calculation - Wikipedia](https://en.wikipedia.org/wiki/Relative_luminance)
|
||||
|
||||
---
|
||||
|
||||
## 结语
|
||||
|
||||
尽管原始代码意图良好,但由于多处语法和逻辑错误,无法正常工作。本文档不仅描述了其设计目标,还指出了关键问题并提供了可运行的修复方案。建议开发者在实际项目中使用经过验证的库(如 `webcolors`、`tinycss2` 或专用 contrast checker 库),或基于本修复版本进行封装。
|
||||
259
aidocs/worker.md
Normal file
259
aidocs/worker.md
Normal file
@ -0,0 +1,259 @@
|
||||
# 异步编程辅助工具库技术文档
|
||||
|
||||
```markdown
|
||||
# Async Utility Toolkit
|
||||
|
||||
一个用于简化同步与异步函数混合使用的 Python 工具库,提供函数装饰器和异步工作池管理功能,支持将阻塞函数 `await` 化、统一处理协程与普通函数调用,并通过信号量控制并发任务数量。
|
||||
|
||||
---
|
||||
|
||||
## 模块依赖
|
||||
|
||||
- `time`
|
||||
- `random`
|
||||
- `asyncio`
|
||||
- `inspect`
|
||||
- `concurrent.futures`
|
||||
- `functools.wraps`
|
||||
|
||||
> ⚠️ 注意:需要 Python 3.7+ 支持完整的 `asyncio` 特性。
|
||||
|
||||
---
|
||||
|
||||
## 核心功能概览
|
||||
|
||||
| 功能 | 说明 |
|
||||
|------|------|
|
||||
| `@awaitify` 装饰器 | 将同步函数包装为可 `await` 的异步函数,在线程池中执行以避免阻塞事件循环 |
|
||||
| `@to_func` 装饰器 | 统一处理同步/异步函数调用,返回 `Future` 或直接结果 |
|
||||
| `AsyncWorker` 类 | 并发控制的工作池,限制最大同时运行的任务数 |
|
||||
|
||||
---
|
||||
|
||||
## 1. `awaitify(sync_func)` —— 同步函数转异步可等待对象
|
||||
|
||||
### 功能描述
|
||||
|
||||
将任意**同步(阻塞)函数**包装成一个 **`async` 函数**,使其可以在 `async/await` 环境中被 `await` 调用。内部使用线程池执行原函数,防止阻塞主事件循环。
|
||||
|
||||
### 使用场景
|
||||
|
||||
适用于 CPU 密集型或长时间 I/O 阻塞的同步函数(如文件读写、数据库操作、`time.sleep()` 等),希望在异步程序中非阻塞地调用它们。
|
||||
|
||||
### 参数
|
||||
|
||||
- `sync_func`: 任意同步可调用对象(函数、方法等)
|
||||
|
||||
### 返回值
|
||||
|
||||
返回一个新的 `async def` 函数,签名与原函数一致。
|
||||
|
||||
### 示例代码
|
||||
|
||||
```python
|
||||
def hello(cnt, greeting):
|
||||
t = random.randint(1, 10)
|
||||
print(cnt, 'will sleep ', t, 'seconds')
|
||||
time.sleep(t) # 阻塞操作
|
||||
print(cnt, 'cost ', t, 'seconds to', greeting)
|
||||
|
||||
# 包装为异步函数
|
||||
f = awaitify(hello)
|
||||
|
||||
# 可以在协程中 await
|
||||
await f(1, "hello world")
|
||||
```
|
||||
|
||||
### 实现原理
|
||||
|
||||
1. 获取当前或创建新的事件循环。
|
||||
2. 使用 `ThreadPoolExecutor` 在后台线程中执行原始函数。
|
||||
3. 使用 `loop.run_in_executor()` 将其调度为异步任务并 `await` 结果。
|
||||
|
||||
> ✅ 安全地将阻塞调用移出主线程,不阻塞事件循环。
|
||||
|
||||
---
|
||||
|
||||
## 2. `to_func(func)` —— 统一同步/异步函数接口
|
||||
|
||||
### 功能描述
|
||||
|
||||
装饰一个函数,使其无论是否是协程函数(`async def`),都能通过统一方式调用并返回一个可等待的对象(`Future` / `Task`)。主要用于兼容不同类型的回调或任务提交。
|
||||
|
||||
### 参数
|
||||
|
||||
- `func`: 待包装的函数,可以是同步函数或 `async def` 协程函数。
|
||||
|
||||
### 返回值
|
||||
|
||||
- 若 `func` 是协程函数:返回一个由 `asyncio.gather(task)` 包装的 `Future`
|
||||
- 否则:直接返回函数执行结果
|
||||
|
||||
⚠️ 当前实现中,对协程函数返回的是 `gather(task)`,这会立即启动任务但不会自动 `await`,需注意上下文中的事件循环管理。
|
||||
|
||||
### 示例代码
|
||||
|
||||
```python
|
||||
@to_func
|
||||
async def ahello():
|
||||
await asyncio.sleep(1)
|
||||
return "done"
|
||||
|
||||
result = ahello() # 返回一个 Future 对象
|
||||
await result # 可 await 获取结果
|
||||
```
|
||||
|
||||
> ❗ 注意:此装饰器设计存在一定歧义(同步函数返回值 vs 异步返回 Future),建议仅用于特定调度系统集成。
|
||||
|
||||
---
|
||||
|
||||
## 3. `AsyncWorker` —— 并发任务控制器
|
||||
|
||||
### 类定义
|
||||
|
||||
```python
|
||||
class AsyncWorker:
|
||||
def __init__(self, maxtask=50)
|
||||
async def __call__(self, callee, *args, **kw)
|
||||
async def run(self, cmd)
|
||||
```
|
||||
|
||||
### 构造函数:`__init__(maxtask=50)`
|
||||
|
||||
#### 参数
|
||||
|
||||
- `maxtask` (int): 最大并发任务数,默认为 50。
|
||||
|
||||
#### 功能
|
||||
|
||||
初始化一个 `asyncio.Semaphore` 信号量,用于限制同时执行的任务数量。
|
||||
|
||||
---
|
||||
|
||||
### 方法:`__call__(callee, *args, **kw)`
|
||||
|
||||
允许将 `AsyncWorker` 实例当作异步函数调用,自动限流执行目标函数。
|
||||
|
||||
#### 参数
|
||||
|
||||
- `callee`: 被调用的函数,支持同步或异步函数
|
||||
- `*args`, `**kw`: 传递给 `callee` 的参数
|
||||
|
||||
#### 行为逻辑
|
||||
|
||||
1. 获取信号量(控制并发)
|
||||
2. 判断 `callee` 是否为协程函数:
|
||||
- 是:`await` 执行
|
||||
- 否:直接调用(若为阻塞函数仍可能影响性能)
|
||||
|
||||
#### 示例
|
||||
|
||||
```python
|
||||
w = AsyncWorker(maxtask=10)
|
||||
await w(hello, i, "hello world") # 自动限流执行
|
||||
```
|
||||
|
||||
> 🔒 每次最多只有 `maxtask` 个任务在运行。
|
||||
|
||||
---
|
||||
|
||||
### 方法:`run(cmd)`
|
||||
|
||||
异步执行 shell 命令,捕获标准输出和错误输出。
|
||||
|
||||
#### 参数
|
||||
|
||||
- `cmd` (str): 要执行的 Shell 命令字符串
|
||||
|
||||
#### 返回值
|
||||
|
||||
元组 `(stdout: bytes, stderr: bytes)`
|
||||
|
||||
#### 注意事项
|
||||
|
||||
> 🛑 存在一个拼写错误:`proc.comunicate()` → 应为 `proc.communicate()`
|
||||
|
||||
#### 正确实现应为:
|
||||
|
||||
```python
|
||||
stdout, stderr = await proc.communicate()
|
||||
```
|
||||
|
||||
否则会抛出 `AttributeError`。
|
||||
|
||||
#### 示例
|
||||
|
||||
```python
|
||||
worker = AsyncWorker()
|
||||
stdout, stderr = await worker.run("ls -la")
|
||||
print(stdout.decode())
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 主程序示例(`if __name__ == '__main__':`)
|
||||
|
||||
演示了如何结合 `awaitify` 和 `AsyncWorker` 实现大规模并发调用同步函数。
|
||||
|
||||
### 流程说明
|
||||
|
||||
1. 定义两个函数:
|
||||
- `hello()`: 同步版本,使用 `time.sleep`
|
||||
- `ahello()`: 异步版本,使用 `await asyncio.sleep`
|
||||
2. 创建 `run()` 协程函数:
|
||||
- 实例化 `AsyncWorker`
|
||||
- 使用 `awaitify(hello)` 包装同步函数
|
||||
- 创建 100 个任务并行执行
|
||||
- 使用 `asyncio.wait()` 等待所有任务完成
|
||||
3. 运行事件循环直到完成。
|
||||
|
||||
### 输出示例
|
||||
|
||||
```
|
||||
0 will sleep 7 seconds
|
||||
1 will sleep 3 seconds
|
||||
...
|
||||
0 cost 7 seconds to hello world
|
||||
1 cost 3 seconds to hello world
|
||||
aaaaaaaaaaaaaaaaaaa
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 已知问题与改进建议
|
||||
|
||||
| 问题 | 描述 | 建议修复 |
|
||||
|------|------|---------|
|
||||
| `comunicate` 拼写错误 | 应为 `communicate` | 更正为 `await proc.communicate()` |
|
||||
| 多余导入 | `from functools import wraps` 出现两次 | 删除重复行 |
|
||||
| `to_func` 设计模糊 | 返回类型不一致(有时是值,有时是 Future) | 建议统一返回 `Task` 或改为上下文感知调度器 |
|
||||
| `to_func` 中 `gather` 使用不当 | `asyncio.gather(task)` 单任务无意义 | 改为直接返回 `task` 或去除 `gather` |
|
||||
| `to_func` 不适合通用场景 | 无法在普通函数中 `await` 返回的 `Future` | 建议仅用于事件循环内调度 |
|
||||
|
||||
---
|
||||
|
||||
## 安装与使用
|
||||
|
||||
无需安装,直接导入模块即可使用。
|
||||
|
||||
```python
|
||||
from your_module import awaitify, AsyncWorker
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
本工具包提供了以下核心能力:
|
||||
|
||||
✅ 安全地 `await` 同步阻塞函数
|
||||
✅ 控制最大并发任务数
|
||||
✅ 简化异步 Shell 命令执行
|
||||
✅ 提供基本的同步/异步调用统一接口(有待优化)
|
||||
|
||||
适合用于爬虫、批量任务处理、微服务中间层等需要混合同步异步逻辑的场景。
|
||||
|
||||
---
|
||||
```
|
||||
|
||||
> 💡 **提示**:生产环境中建议增加日志记录、异常处理、超时机制以及更完善的单元测试。
|
||||
288
aidocs/zmq_reqrep.md
Normal file
288
aidocs/zmq_reqrep.md
Normal file
@ -0,0 +1,288 @@
|
||||
# ZMQ Request-Reply 模块技术文档
|
||||
|
||||
`zmq_reqresp.py` 是一个基于 ZeroMQ 的请求-响应(Request-Response)通信模式的 Python 封装模块,支持同步与异步两种运行模式。该模块提供了 `ZmqRequester` 和 `ZmqReplier` 两个核心类,分别用于客户端发送请求和服务器端接收并回复请求。
|
||||
|
||||
---
|
||||
|
||||
## 目录
|
||||
|
||||
- [概述](#概述)
|
||||
- [依赖](#依赖)
|
||||
- [类说明](#类说明)
|
||||
- [`ZmqRequester`](#zmqrqester)
|
||||
- [`ZmqReplier`](#zmqreplier)
|
||||
- [使用示例](#使用示例)
|
||||
- [同步模式示例](#同步模式示例)
|
||||
- [异步模式示例](#异步模式示例)
|
||||
- [异常处理](#异常处理)
|
||||
- [注意事项](#注意事项)
|
||||
|
||||
---
|
||||
|
||||
## 概述
|
||||
|
||||
本模块封装了 ZeroMQ 的 `REQ/REP` 套接字模式,实现了:
|
||||
- 支持字符串和字节流的消息传输;
|
||||
- 同步与异步双模式支持;
|
||||
- 可配置超时重连机制;
|
||||
- 简洁易用的 API 接口。
|
||||
|
||||
适用于构建轻量级远程调用、微服务间通信等场景。
|
||||
|
||||
---
|
||||
|
||||
## 依赖
|
||||
|
||||
```txt
|
||||
pyzmq >= 22.0
|
||||
Python >= 3.7
|
||||
```
|
||||
|
||||
> 注:异步功能依赖于 `asyncio` 和 `zmq.asyncio`。
|
||||
|
||||
---
|
||||
|
||||
## 类说明
|
||||
|
||||
### `ZmqRequester`
|
||||
|
||||
代表一个 ZeroMQ 客户端(REQ 套接字),用于向服务端发送请求并接收响应。
|
||||
|
||||
#### 初始化参数
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
|------|------|------|
|
||||
| `url` | str | 连接的服务端地址,如 `"tcp://127.0.0.1:5555"` |
|
||||
| `async_mode` | bool | 是否启用异步模式,默认为 `False` |
|
||||
| `timeout` | int | 接收响应的超时时间(单位:秒),设为 0 表示阻塞等待 |
|
||||
|
||||
> ⚠️ 若 `timeout > 0`,内部会使用 `Poller` 实现非阻塞轮询,并在超时时自动重连。
|
||||
|
||||
#### 方法
|
||||
|
||||
##### `send(msg: str) -> str or None`
|
||||
- **描述**:同步发送字符串消息,返回响应字符串。
|
||||
- **参数**:
|
||||
- `msg`: 要发送的字符串(UTF-8 编码)
|
||||
- **返回值**:服务端返回的字符串,或 `None`(超时)
|
||||
- **异常**:若处于异步模式则抛出异常
|
||||
|
||||
##### `send_b(b: bytes) -> bytes or None`
|
||||
- **描述**:同步发送字节数据,返回字节响应。
|
||||
- **参数**:
|
||||
- `b`: 要发送的字节对象
|
||||
- **返回值**:响应字节数据,或 `None`(超时)
|
||||
|
||||
##### `async asend(msg: str) -> str or None`
|
||||
- **描述**:异步发送字符串消息。
|
||||
- **参数**:
|
||||
- `msg`: 字符串消息
|
||||
- **返回值**:响应字符串或 `None`
|
||||
- **异常**:若未启用异步模式则抛出异常
|
||||
|
||||
##### `async asend_b(b: bytes) -> bytes or None`
|
||||
- **描述**:异步发送字节数据。
|
||||
- **参数**:
|
||||
- `b`: 字节数据
|
||||
- **返回值**:响应字节数据或 `None`
|
||||
|
||||
##### `_connect()`
|
||||
- 内部方法,用于创建上下文并连接到服务端。
|
||||
|
||||
##### `_close()`
|
||||
- 内部方法,关闭套接字和上下文资源。
|
||||
|
||||
> ✅ 自动在析构函数中调用 `_close()` 释放资源。
|
||||
|
||||
---
|
||||
|
||||
### `ZmqReplier`
|
||||
|
||||
代表一个 ZeroMQ 服务端(REP 套接字),监听请求并调用处理器函数进行响应。
|
||||
|
||||
#### 初始化参数
|
||||
|
||||
| 参数 | 类型 | 描述 |
|
||||
|------|------|------|
|
||||
| `url` | str | 绑定地址,如 `"tcp://*:5555"` |
|
||||
| `handler` | callable 或 coroutine function | 处理请求的回调函数 |
|
||||
| `async_mode` | bool | 是否以异步模式运行 |
|
||||
|
||||
> ⚠️ 若 `async_mode=False`,但 `handler` 是协程函数,则会抛出错误。
|
||||
|
||||
#### 回调函数要求
|
||||
|
||||
- 输入:`bytes` 类型的请求数据
|
||||
- 输出:可为 `bytes` 或 `str`(自动编码为 UTF-8)
|
||||
|
||||
#### 方法
|
||||
|
||||
##### `run()`
|
||||
- **描述**:启动服务端(同步模式)
|
||||
- **行为**:在后台线程中运行 `_run()`,不阻塞主线程
|
||||
- 使用 `Background` 类实现多线程执行
|
||||
|
||||
##### `async_run()`
|
||||
- **描述**:异步运行服务端主循环(需在事件循环中 await)
|
||||
- **注意**:此方法不会自动启动任务,需手动调度至 `asyncio` 事件循环
|
||||
|
||||
##### `_run()`
|
||||
- 同步主循环逻辑:接收请求 → 调用处理器 → 发送响应
|
||||
|
||||
##### `stop()`
|
||||
- **描述**:停止服务端运行
|
||||
- 设置 `keep_running = False` 并尝试 `join()`(注:当前 `join()` 未定义,见[注意事项](#注意事项))
|
||||
|
||||
---
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 同步模式示例
|
||||
|
||||
#### 服务端(Replier)
|
||||
|
||||
```python
|
||||
def echo_handler(data: bytes) -> bytes:
|
||||
return b"Echo: " + data
|
||||
|
||||
replier = ZmqReplier("tcp://*:5555", echo_handler, async_mode=False)
|
||||
replier.run()
|
||||
|
||||
# 主程序保持运行
|
||||
import time
|
||||
try:
|
||||
while True:
|
||||
time.sleep(1)
|
||||
except KeyboardInterrupt:
|
||||
replier.stop()
|
||||
```
|
||||
|
||||
#### 客户端(Requester)
|
||||
|
||||
```python
|
||||
req = ZmqRequester("tcp://127.0.0.1:5555", async_mode=False, timeout=5)
|
||||
response = req.send("Hello")
|
||||
print(response) # 输出: Echo: Hello
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 异步模式示例
|
||||
|
||||
#### 异步服务端
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
|
||||
async def async_handler(data: bytes) -> bytes:
|
||||
await asyncio.sleep(0.1) # 模拟异步操作
|
||||
return b"Async reply to " + data
|
||||
|
||||
replier = ZmqReplier("tcp://*:5555", async_handler, async_mode=True)
|
||||
|
||||
async def main():
|
||||
await replier.async_run()
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
#### 异步客户端
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from zmq_reqresp import ZmqRequester
|
||||
|
||||
async def client():
|
||||
req = ZmqRequester("tcp://127.0.0.1:5555", async_mode=True, timeout=5)
|
||||
response = await req.asend("Test message")
|
||||
print(response)
|
||||
|
||||
asyncio.run(client())
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 异常处理
|
||||
|
||||
| 场景 | 抛出异常 |
|
||||
|------|----------|
|
||||
| 在异步模式下调用同步 `send` 方法 | `Exception('ZMQ_Requester: in async mode, use asend instead')` |
|
||||
| 在同步模式下调用异步方法 | `Exception('ZMQ_Requester: not in async mode, use send instead')` |
|
||||
| 非异步模式下传入协程处理器 | `TypeError`(原代码有误,应为 `raise TypeError(...)`) |
|
||||
|
||||
> ❗ 原始代码中的 `raise('not in async mode...')` 存在语法错误,应改为:
|
||||
```python
|
||||
raise TypeError('not in async mode, handler can not be a coroutine')
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **资源管理**
|
||||
- `ZmqRequester.__del__` 中调用了 `_close()`,但在某些情况下 `__del__` 不一定被及时调用。建议显式管理生命周期。
|
||||
|
||||
2. **超时重连机制**
|
||||
- 当设置 `timeout > 0` 时,若接收超时,会自动关闭并重新连接。这可能导致短暂中断,适合容忍短暂失败的场景。
|
||||
|
||||
3. **ZmqReplier.stop() 中的 `join()` 错误**
|
||||
- 当前代码中 `self.join()` 未定义,因为 `Background` 对象未暴露或继承自 `Thread`。
|
||||
- 应确保 `Background` 类支持 `.join()` 方法,否则需移除或修复此行。
|
||||
|
||||
4. **异步模式下的 Poller 使用问题**
|
||||
- `asend_b` 中混合使用了 `poller.poll()`(同步)与 `await self.sock.recv_multipart()`(异步),这会导致阻塞风险。
|
||||
- 在异步模式下应避免使用 `Poller().poll()`,推荐使用 `asyncio.wait_for()` 包裹 `recv_multipart()`。
|
||||
|
||||
✅ 推荐改进方式:
|
||||
|
||||
```python
|
||||
try:
|
||||
r = await asyncio.wait_for(self.sock.recv_multipart(), timeout=self.timeout)
|
||||
return r[0]
|
||||
except asyncio.TimeoutError:
|
||||
self._close()
|
||||
self._connect()
|
||||
return None
|
||||
```
|
||||
|
||||
5. **REP 套接字状态一致性**
|
||||
- 必须保证每次 `recv` 后都有对应的 `send`,否则套接字将进入非法状态。本模块通过结构化流程保障这一点。
|
||||
|
||||
6. **线程安全**
|
||||
- `ZmqRequester` 不是线程安全的,每个线程应使用独立实例。
|
||||
|
||||
---
|
||||
|
||||
## 版本建议改进
|
||||
|
||||
| 问题 | 建议修复 |
|
||||
|------|---------|
|
||||
| `raise('string')` 语法错误 | 改为 `raise Exception(...)` |
|
||||
| `stop()` 调用未定义 `join()` | 移除或确保 `Background` 支持 `.join()` |
|
||||
| 异步超时检测使用同步 Poller | 改用 `asyncio.wait_for()` |
|
||||
| `rb =self.self.handler(b)` 拼写错误 | 应为 `rb = self.handler(b)` |
|
||||
|
||||
✅ 已发现拼写错误:
|
||||
|
||||
```python
|
||||
rb =self.self.handler(b) # 错误!
|
||||
```
|
||||
|
||||
应修正为:
|
||||
|
||||
```python
|
||||
rb = self.handler(b)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
`zmq_reqresp.py` 提供了一个简洁实用的 ZeroMQ 请求-响应通信封装,支持同步与异步双模式,在微服务、RPC 场景中有良好应用潜力。尽管存在少量 bug 和设计瑕疵,但整体架构清晰,易于扩展和维护。
|
||||
|
||||
> 建议结合 `contextlib` 或 `async context manager` 进一步增强资源管理能力。
|
||||
|
||||
---
|
||||
|
||||
📌 **作者**:未知
|
||||
📅 **最后更新**:2025年4月5日
|
||||
314
aidocs/zmq_topic.md
Normal file
314
aidocs/zmq_topic.md
Normal file
@ -0,0 +1,314 @@
|
||||
# Topic Messaging System with ZeroMQ
|
||||
|
||||
这是一个基于 **ZeroMQ (ZMQ)** 的发布/订阅(Pub/Sub)消息系统,支持配置驱动的服务器、发布者和订阅者。该系统使用 `XPUB` 和 `XSUB` 套接字类型构建一个代理(proxy),实现多对多的消息广播机制。
|
||||
|
||||
---
|
||||
|
||||
## 📦 模块依赖
|
||||
|
||||
```python
|
||||
import sys
|
||||
import zmq
|
||||
import time
|
||||
from zmq import Context
|
||||
from appPublic.jsonConfig import getConfig
|
||||
```
|
||||
|
||||
- `zmq`: 使用 [pyzmq](https://pyzmq.readthedocs.io/) 实现高性能异步消息通信。
|
||||
- `Context`: ZeroMQ 上下文对象,用于管理套接字资源。
|
||||
- `getConfig()`: 来自 `appPublic.jsonConfig` 的工具函数,加载 JSON 格式的配置文件。
|
||||
|
||||
---
|
||||
|
||||
## 🔧 核心组件
|
||||
|
||||
### 1. `TopicServer` —— 主题消息代理服务器
|
||||
|
||||
#### 功能说明
|
||||
`TopicServer` 是一个 ZeroMQ 代理服务,它通过 `zmq.XSUB` 接收来自多个发布者的主题消息,并通过 `zmq.XPUB` 将这些消息转发给所有匹配主题的订阅者。
|
||||
|
||||
> 它实现了经典的“反向代理”模式:前端接收发布者连接,后端向订阅者广播。
|
||||
|
||||
#### 初始化参数
|
||||
| 参数 | 类型 | 默认值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| `address` | str | `'127.0.0.1'` | 绑定地址(可改为公网IP) |
|
||||
| `pub_port` | str/int | `'5566'` | 发布者连接的端口(XSUB 端) |
|
||||
| `sub_port` | str/int | `'5567'` | 订阅者连接的端口(XPUB 端) |
|
||||
|
||||
#### 构造函数行为
|
||||
- 打印当前 ZeroMQ 库版本信息(libzmq 和 pyzmq)
|
||||
- 创建全局上下文单例:`Context.instance()`
|
||||
- 设置两个 TCP 地址:
|
||||
- `pub_port`: `tcp://<address>:<pub_port>` → 发布者连接此地址
|
||||
- `sub_port`: `tcp://<address>:<sub_port>` → 订阅者连接此地址
|
||||
- 调用 `xpub_xsub_proxy()` 启动代理循环
|
||||
|
||||
#### 方法:`xpub_xsub_proxy()`
|
||||
创建并启动 ZeroMQ 内置代理:
|
||||
|
||||
```python
|
||||
zmq.proxy(frontend_pubs, backend_subs)
|
||||
```
|
||||
|
||||
##### 套接字角色
|
||||
| 套接字 | 类型 | 角色 | 绑定地址 |
|
||||
|-------|------|------|---------|
|
||||
| `frontend_pubs` | `XSUB` | 接收发布者消息 | `tcp://<address>:<pub_port>` |
|
||||
| `backend_subs` | `XPUB` | 向订阅者广播消息 | `tcp://<address>:<sub_port>` |
|
||||
|
||||
> ✅ `bind()` 表示服务器监听连接;发布者与订阅者需调用 `connect()` 连接到对应端口。
|
||||
|
||||
##### 输出日志
|
||||
```
|
||||
Init proxy
|
||||
Try: Proxy... CONNECT!
|
||||
CONNECT successful!
|
||||
```
|
||||
|
||||
> ⚠️ 注意:`zmq.proxy()` 是阻塞调用,程序会一直运行在此处直到中断。
|
||||
|
||||
---
|
||||
|
||||
### 2. `ConfiguredTopicServer` —— 配置驱动的代理服务器
|
||||
|
||||
继承自 `TopicServer`,从外部 JSON 配置文件读取参数。
|
||||
|
||||
#### 配置结构要求
|
||||
```json
|
||||
{
|
||||
"topicserver": {
|
||||
"address": "11.11.1.11",
|
||||
"pub_port": 1234,
|
||||
"sub_port": 1235
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> ⚠️ 错误拼写提示:原代码中 `"sub_server"` 应为 `"sub_port"`,否则将导致属性访问失败。
|
||||
|
||||
#### 异常处理
|
||||
- 若配置不存在或缺少 `topicserver` 字段,则抛出未定义异常 `MissTopicServerConfig`(需提前定义)。
|
||||
|
||||
#### 示例用法
|
||||
```python
|
||||
server = ConfiguredTopicServer()
|
||||
# 自动加载配置并启动代理
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. `TopicPublisher` —— 主题发布者客户端
|
||||
|
||||
允许向代理发送带主题的消息。
|
||||
|
||||
#### 初始化参数
|
||||
| 参数 | 类型 | 默认值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| `topic` | str | `'en'` | 消息主题(如语言标识) |
|
||||
| `address` | str | `'127.0.0.1'` | 代理的发布端地址 |
|
||||
| `port` | str/int | `'5566'` | 代理的发布端口(对应 XSUB) |
|
||||
|
||||
#### 关键行为
|
||||
- 将 `topic` 编码为 UTF-8 字节串(ZeroMQ 要求)
|
||||
- 创建 `PUB` 套接字并连接到代理的 `pub_port`
|
||||
- 添加 `time.sleep(0.5)`:防止连接未建立即发送消息("slow joiner" 问题)
|
||||
|
||||
#### 方法:`send(message)`
|
||||
发送一条多部分消息:`[topic_bytes, message_bytes]`
|
||||
|
||||
```python
|
||||
publisher.send("Hello World")
|
||||
# 实际发送: [b'en', b'Hello World']
|
||||
```
|
||||
|
||||
> ✅ 支持任意字符串内容,自动编码为 UTF-8。
|
||||
|
||||
---
|
||||
|
||||
### 4. `ConfiguredTopicPublisher` —— 配置驱动的发布者
|
||||
|
||||
自动从配置文件读取代理地址和端口。
|
||||
|
||||
#### 用法示例
|
||||
```python
|
||||
pub = ConfiguredTopicPublisher(topic='news')
|
||||
pub.send('Breaking news!')
|
||||
```
|
||||
|
||||
> 使用配置中的 `address` 和 `pub_port`,无需硬编码。
|
||||
|
||||
---
|
||||
|
||||
### 5. `TopicSubscriber` —— 主题订阅者客户端
|
||||
|
||||
接收特定主题的消息。
|
||||
|
||||
#### 初始化参数
|
||||
| 参数 | 类型 | 默认值 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| `topic` | str/list | `''` | 订阅的主题(支持单个或列表) |
|
||||
| `address` | str | `'127.0.0.1'` | 代理的订阅地址 |
|
||||
| `port` | str/int | `'5567'` | 代理的订阅端口(对应 XPUB) |
|
||||
| `callback` | callable | `None` | 收到消息时的回调函数 |
|
||||
|
||||
#### 订阅逻辑
|
||||
- 若 `topic` 为字符串 → 订阅该主题
|
||||
- 若 `topic` 为列表 → 循环订阅每个主题
|
||||
|
||||
```python
|
||||
sub = TopicSubscriber(topic=['en', 'cn'])
|
||||
```
|
||||
|
||||
> ZeroMQ SUB 套接字必须显式调用 `setsockopt(SUBSCRIBE, ...)` 才能接收消息。
|
||||
|
||||
#### 方法:`run()`
|
||||
进入无限循环,持续接收消息:
|
||||
|
||||
```python
|
||||
while True:
|
||||
msg_received = self.sub.recv_multipart()
|
||||
print("sub {}: {}".format(self.topic, msg_received))
|
||||
if self.callback:
|
||||
self.callback(msg_received)
|
||||
```
|
||||
|
||||
> 输出格式示例:
|
||||
```
|
||||
sub en: [b'en', b'Hello']
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 6. `ConfiguredTopicSubscriber` —— 配置驱动的订阅者
|
||||
|
||||
类似其他配置类,自动加载代理连接参数。
|
||||
|
||||
#### 用法示例
|
||||
```python
|
||||
def on_msg(msg):
|
||||
print("Received:", msg)
|
||||
|
||||
sub = ConfiguredTopicSubscriber(topic='en', callback=on_msg)
|
||||
sub.run() # 开始监听
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🌐 系统架构图(文本描述)
|
||||
|
||||
```
|
||||
+-------------+ +---------------------+
|
||||
| Publisher 1 |-------->| |
|
||||
+-------------+ | TopicServer |
|
||||
| (XPUB/XSUB Proxy) |
|
||||
+-------------+ | |---------> Subscriber A (topic=en)
|
||||
| Publisher 2 |-------->| |---------> Subscriber B (topic=jp)
|
||||
+-------------+ +---------------------+
|
||||
|
||||
↑ ↑
|
||||
pub_port (5566) sub_port (5567)
|
||||
```
|
||||
|
||||
- 所有发布者连接到 `pub_port`(XSUB)
|
||||
- 所有订阅者连接到 `sub_port`(XPUB)
|
||||
- 代理自动完成消息路由与过滤
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ 使用示例
|
||||
|
||||
### 启动代理服务器(手动指定)
|
||||
```python
|
||||
server = TopicServer(address='0.0.0.0', pub_port=5566, sub_port=5567)
|
||||
# 阻塞运行
|
||||
```
|
||||
|
||||
### 或使用配置方式启动
|
||||
```python
|
||||
server = ConfiguredTopicServer()
|
||||
```
|
||||
|
||||
### 发布消息
|
||||
```python
|
||||
pub = TopicPublisher(topic='en', address='localhost', port=5566)
|
||||
pub.send("Hello English Users!")
|
||||
```
|
||||
|
||||
### 订阅消息
|
||||
```python
|
||||
def handle_msg(msg_parts):
|
||||
topic, content = msg_parts
|
||||
print(f"Topic: {topic.decode()}, Msg: {content.decode()}")
|
||||
|
||||
sub = TopicSubscriber(topic='en', callback=handle_msg)
|
||||
sub.run()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 注意事项
|
||||
|
||||
1. **端口顺序不能错**:
|
||||
- 发布者连 `pub_port`(XSUB 输入)
|
||||
- 订阅者连 `sub_port`(XPUB 输出)
|
||||
|
||||
2. **延迟连接问题**:
|
||||
- `time.sleep(0.5)` 在发布者中是临时解决方案
|
||||
- 更优做法:使用同步机制(如 PULL/PUSH 协调)
|
||||
|
||||
3. **异常类缺失**:
|
||||
- `MissTopicServerConfig` 未在代码中定义,应补充:
|
||||
```python
|
||||
class MissTopicServerConfig(Exception):
|
||||
pass
|
||||
```
|
||||
|
||||
4. **编码一致性**:
|
||||
- 所有主题和消息均以 UTF-8 编码传输,确保跨平台兼容性。
|
||||
|
||||
5. **性能建议**:
|
||||
- `Context.instance()` 全局共享,避免频繁创建上下文
|
||||
- 多线程环境下注意套接字非线程安全
|
||||
|
||||
---
|
||||
|
||||
## 📁 配置文件样例 (`config.json`)
|
||||
|
||||
```json
|
||||
{
|
||||
"topicserver": {
|
||||
"address": "127.0.0.1",
|
||||
"pub_port": 5566,
|
||||
"sub_port": 5567
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> 确保路径正确且 `getConfig()` 可读取。
|
||||
|
||||
---
|
||||
|
||||
## 📘 总结
|
||||
|
||||
| 组件 | 角色 | 套接字类型 | 连接方向 |
|
||||
|------|------|------------|-----------|
|
||||
| `TopicServer` | 消息代理 | XSUB + XPUB | bind |
|
||||
| `TopicPublisher` | 消息生产者 | PUB | connect → pub_port |
|
||||
| `TopicSubscriber` | 消息消费者 | SUB | connect → sub_port |
|
||||
|
||||
✅ 优点:
|
||||
- 解耦发布者与订阅者
|
||||
- 支持动态扩展
|
||||
- 高吞吐低延迟
|
||||
- 支持主题过滤
|
||||
|
||||
🔧 适用场景:
|
||||
- 日志分发
|
||||
- 实时通知系统
|
||||
- 微服务间事件通信
|
||||
- 多语言消息广播(如 en/jp/fr)
|
||||
|
||||
---
|
||||
|
||||
> 💡 提示:结合 Docker 部署 `TopicServer`,可实现跨主机消息分发。
|
||||
398
aidocs/zmqapi.md
Normal file
398
aidocs/zmqapi.md
Normal file
@ -0,0 +1,398 @@
|
||||
# ZeroMQ 异步通信模块技术文档
|
||||
|
||||
本模块基于 `zmq.asyncio` 提供了一组用于构建异步消息通信系统的 Python 类,支持多种 ZeroMQ 模式:**发布/订阅 (Pub/Sub)**、**请求/响应 (Req/Rep)**、**推/拉 (Push/Pull)** 和 **对等连接 (Pair)**。所有类均为异步设计,适用于 `asyncio` 协程环境。
|
||||
|
||||
---
|
||||
|
||||
## 📦 依赖
|
||||
|
||||
- Python 3.7+
|
||||
- `pyzmq`(`pip install pyzmq`)
|
||||
- `asyncio`
|
||||
|
||||
> 注意:使用前请确保已安装 `pyzmq` 并支持 `asyncio` 集成。
|
||||
|
||||
```bash
|
||||
pip install pyzmq
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🧩 核心功能概览
|
||||
|
||||
| 类名 | 功能描述 | ZeroMQ 模式 |
|
||||
|--------------|----------------------------------|-------------|
|
||||
| `Publisher` | 发布消息到多个订阅者 | PUB |
|
||||
| `Subscriber` | 订阅特定消息 ID 的消息 | SUB |
|
||||
| `RRServer` | 请求-响应模式的服务端 | REP |
|
||||
| `RRClient` | 请求-响应模式的客户端 | REQ |
|
||||
| `PPPusher` | 推送任务或数据 | PUSH |
|
||||
| `PPPuller` | 拉取并处理任务或数据 | PULL |
|
||||
| `PairClient` | 点对点双向通信客户端 | PAIR |
|
||||
| `PairServer` | 点对点双向通信服务端(可带处理器)| PAIR |
|
||||
|
||||
---
|
||||
|
||||
## 🔧 使用说明与 API 文档
|
||||
|
||||
### 1. `Publisher` - 发布者(PUB)
|
||||
|
||||
用于向多个订阅者广播消息。
|
||||
|
||||
#### ✅ 初始化
|
||||
|
||||
```python
|
||||
pub = Publisher(port, coding='utf-8', msgid=1000)
|
||||
```
|
||||
|
||||
- `port`: int,绑定的 TCP 端口。
|
||||
- `coding`: str,默认 `'utf-8'`,编码格式。
|
||||
- `msgid`: int,默认 `1000`,默认消息 ID 前缀。
|
||||
|
||||
#### 📡 方法:`publish(msg, msgtype='text', msgid=-1)`
|
||||
|
||||
发送一条消息。
|
||||
|
||||
- `msg`: 要发送的内容(字符串或可序列化对象)。
|
||||
- `msgtype`: 消息类型,支持 `'text'` 或其他如 `'json'`。
|
||||
- `msgid`: 自定义消息 ID,若为 `-1` 则使用实例默认值。
|
||||
|
||||
> 若 `msgtype != 'text'`,会自动将 `msg` 通过 `json.dumps()` 序列化,并强制设为 `'json'` 类型。
|
||||
|
||||
##### 示例:
|
||||
```python
|
||||
await pub.publish("Hello World")
|
||||
await pub.publish({"data": 42}, msgtype="json")
|
||||
```
|
||||
|
||||
##### 消息格式(在传输中):
|
||||
```
|
||||
<msgid> <msgtype> <body>
|
||||
```
|
||||
|
||||
例如:
|
||||
```
|
||||
1000 json {"data": 42}
|
||||
```
|
||||
|
||||
#### 💡 注意事项
|
||||
- 多个订阅者可通过不同端口连接同一发布者。
|
||||
- 不保证消息送达(ZMQ PUB/SUB 特性)。
|
||||
|
||||
---
|
||||
|
||||
### 2. `Subscriber` - 订阅者(SUB)
|
||||
|
||||
接收来自一个或多个发布者的特定消息。
|
||||
|
||||
#### ✅ 初始化
|
||||
|
||||
```python
|
||||
sub = Subscriber(host, ports, msgid, coding='utf-8')
|
||||
```
|
||||
|
||||
- `host`: str,发布者主机地址(如 `'localhost'`)。
|
||||
- `ports`: list of int,要连接的一个或多个端口。
|
||||
- `msgid`: int,只订阅以该 ID 开头的消息。
|
||||
- `coding`: str,解码方式,默认 `'utf-8'`。
|
||||
|
||||
> 内部设置 `SUBSCRIBE` 过滤器为 `b'<msgid>'`,仅接收匹配前缀的消息。
|
||||
|
||||
#### ➕ 方法:`addPort(port)`
|
||||
|
||||
动态添加一个新的发布者端口进行连接。
|
||||
|
||||
```python
|
||||
sub.addPort(5556)
|
||||
```
|
||||
|
||||
#### 📥 方法:`subscribe() → str or dict`
|
||||
|
||||
异步接收并解析下一条消息。
|
||||
|
||||
- 返回值:
|
||||
- 若 `msgtype == 'json'`:返回反序列化的 `dict`。
|
||||
- 否则返回原始字符串。
|
||||
|
||||
##### 示例:
|
||||
```python
|
||||
data = await sub.subscribe()
|
||||
print(data) # 可能是字符串或字典
|
||||
```
|
||||
|
||||
#### 💡 注意事项
|
||||
- 必须确保发布的 `msgid` 与订阅者设置的一致才能接收到消息。
|
||||
- 支持多播场景,可同时连接多个发布者端口。
|
||||
|
||||
---
|
||||
|
||||
### 3. `RRServer` - 请求/响应服务端(REP)
|
||||
|
||||
实现同步式的请求-应答服务器。
|
||||
|
||||
#### ✅ 初始化
|
||||
|
||||
```python
|
||||
server = RRServer(port, handler=None)
|
||||
```
|
||||
|
||||
- `port`: int,监听端口。
|
||||
- `handler`: callable,可选回调函数,处理请求并返回响应。
|
||||
- 支持普通函数和 `async def` 协程。
|
||||
|
||||
#### ▶️ 方法:`run()`
|
||||
|
||||
启动服务循环,持续监听请求。
|
||||
|
||||
##### 处理逻辑:
|
||||
1. 接收请求消息 `rmsg`(bytes)。
|
||||
2. 若有 `handler`,调用它生成响应 `wmsg`。
|
||||
3. 如果 `handler` 返回的是协程,则等待其完成。
|
||||
4. 将响应发回客户端。
|
||||
|
||||
##### 示例 Handler:
|
||||
```python
|
||||
def my_handler(msg):
|
||||
return b"Echo: " + msg
|
||||
|
||||
# 或异步版本
|
||||
async def async_handler(msg):
|
||||
await asyncio.sleep(0.1)
|
||||
return b"Processed"
|
||||
```
|
||||
|
||||
##### 启动服务:
|
||||
```python
|
||||
await server.run()
|
||||
```
|
||||
|
||||
> ⚠️ 此方法为无限循环,需运行在事件循环中。
|
||||
|
||||
---
|
||||
|
||||
### 4. `RRClient` - 请求/响应客户端(REQ)
|
||||
|
||||
向 `RRServer` 发起请求并等待响应。
|
||||
|
||||
#### ✅ 初始化
|
||||
|
||||
```python
|
||||
client = RRClient(host, port)
|
||||
```
|
||||
|
||||
- `host`: str,服务器地址。
|
||||
- `port`: int,服务器端口。
|
||||
|
||||
#### 📤 方法:`request(msg) → bytes`
|
||||
|
||||
发送请求并返回响应。
|
||||
|
||||
```python
|
||||
response = await client.request(b"Hello")
|
||||
print(response) # b'Echo: Hello'
|
||||
```
|
||||
|
||||
> REQ/REP 是严格的一问一答模式,不能连续两次发送。
|
||||
|
||||
---
|
||||
|
||||
### 5. `PPPusher` - 推送器(PUSH)
|
||||
|
||||
用于向一个或多个 `PPPuller` 推送任务或消息。
|
||||
|
||||
#### ✅ 初始化
|
||||
|
||||
```python
|
||||
pusher = PPPusher(host, port)
|
||||
```
|
||||
|
||||
- `host`: 绑定的 IP 地址(通常 `'*'` 或 `'localhost'`)。
|
||||
- `port`: 绑定端口。
|
||||
|
||||
> 使用 `PUSH` 模式,适合负载均衡分发任务。
|
||||
|
||||
#### 🚀 方法:`push(msg)`
|
||||
|
||||
推送一条消息(bytes 或字符串需编码)。
|
||||
|
||||
```python
|
||||
await pusher.push(b"Task 1")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 6. `PPPuller` - 拉取器(PULL)
|
||||
|
||||
从 `PPPusher` 接收消息并处理。
|
||||
|
||||
#### ✅ 初始化
|
||||
|
||||
```python
|
||||
puller = PPPuller(host, port, handler=None)
|
||||
```
|
||||
|
||||
- `host`: 监听地址。
|
||||
- `port`: 监听端口。
|
||||
- `handler`: 接收到消息后的处理函数(可为协程)。
|
||||
|
||||
#### ▶️ 方法:`run()`
|
||||
|
||||
启动拉取循环,持续接收消息并交由 `handler` 处理。
|
||||
|
||||
```python
|
||||
async def handle_task(msg):
|
||||
print("Processing:", msg)
|
||||
|
||||
puller = PPPuller('localhost', 5558, handle_task)
|
||||
await puller.run()
|
||||
```
|
||||
|
||||
> 支持异步处理器,自动 `await` 协程结果。
|
||||
|
||||
---
|
||||
|
||||
### 7. `PairClient` - 对等客户端(PAIR)
|
||||
|
||||
实现简单的点对点双向通信中的“客户端”角色。
|
||||
|
||||
#### ✅ 初始化
|
||||
|
||||
```python
|
||||
client = PairClient(host, port)
|
||||
```
|
||||
|
||||
- `host`: 连接目标 IP。
|
||||
- `port`: 连接端口。
|
||||
|
||||
> 实际上是主动 `bind` 的一方,在 PAIR 模式中双方只能一对一。
|
||||
|
||||
#### 📤 方法:`request(msg) → bytes`
|
||||
|
||||
发送消息并等待对方回复。
|
||||
|
||||
```python
|
||||
resp = await client.request(b"Ping")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 8. `PairServer` - 对等服务端(PAIR)
|
||||
|
||||
接收来自 `PairClient` 的消息并响应。
|
||||
|
||||
#### ✅ 初始化
|
||||
|
||||
```python
|
||||
server = PairServer(port, handler=None)
|
||||
```
|
||||
|
||||
- `port`: 监听端口。
|
||||
- `handler`: 处理函数,决定返回内容(支持协程)。
|
||||
|
||||
#### ▶️ 方法:`run()`
|
||||
|
||||
开启监听循环:
|
||||
|
||||
```python
|
||||
async def echo_handler():
|
||||
return b"OK"
|
||||
|
||||
server = PairServer(5560, echo_handler)
|
||||
await server.run()
|
||||
```
|
||||
|
||||
> PAIR 模式不支持多客户端,仅用于两个节点间的直接通信。
|
||||
|
||||
---
|
||||
|
||||
## 🔄 通信模式对比
|
||||
|
||||
| 模式 | 典型用途 | 特点 |
|
||||
|------------|------------------------|------|
|
||||
| PUB/SUB | 广播通知、事件流 | 多播,无确认,可能丢包 |
|
||||
| REQ/REP | 同步远程调用 | 严格同步,一问一答 |
|
||||
| PUSH/PULL | 工作队列、任务分发 | 流水线架构,支持扇出 |
|
||||
| PAIR | 点对点控制信道 | 简单双向通信,限一对一双向 |
|
||||
|
||||
---
|
||||
|
||||
## 🧪 示例:简单发布/订阅
|
||||
|
||||
```python
|
||||
# publisher.py
|
||||
import asyncio
|
||||
from your_module import Publisher
|
||||
|
||||
async def main():
|
||||
pub = Publisher(5555)
|
||||
while True:
|
||||
await pub.publish("Hi there!", msgtype="text")
|
||||
await asyncio.sleep(1)
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
```python
|
||||
# subscriber.py
|
||||
import asyncio
|
||||
from your_module import Subscriber
|
||||
|
||||
async def main():
|
||||
sub = Subscriber('localhost', [5555], msgid=1000)
|
||||
while True:
|
||||
msg = await sub.subscribe()
|
||||
print("Received:", msg)
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 注意事项与最佳实践
|
||||
|
||||
1. **资源释放**:
|
||||
- 所有类在析构时自动关闭 socket,但仍建议显式管理生命周期。
|
||||
- 避免未关闭上下文导致文件描述符泄漏。
|
||||
|
||||
2. **消息过滤(SUB)**:
|
||||
- ZeroMQ SUB 的订阅是基于前缀匹配的字节流。
|
||||
- 当前实现使用 `setsockopt(zmq.SUBSCRIBE, b'1000')`,只会收到以 `1000` 开头的消息。
|
||||
|
||||
3. **编码一致性**:
|
||||
- 发送与接收方必须使用相同编码(默认 UTF-8)。
|
||||
|
||||
4. **异步处理器兼容性**:
|
||||
- `RRServer`、`PPPuller`、`PairServer` 均支持同步和异步处理器。
|
||||
- 使用 `isinstance(x, Coroutine)` 判断是否需要 `await`。
|
||||
|
||||
5. **错误处理建议**:
|
||||
- 生产环境中应在 `try-except` 中包裹 `recv()` 和 `send()` 操作。
|
||||
- 添加超时机制防止阻塞。
|
||||
|
||||
6. **性能提示**:
|
||||
- 使用 `context = zmq.asyncio.Context.instance()` 可共享上下文实例提升效率。
|
||||
|
||||
---
|
||||
|
||||
## 📚 扩展建议
|
||||
|
||||
- 添加日志输出替代 `print()`。
|
||||
- 增加心跳、重连机制。
|
||||
- 支持 TLS 加密传输。
|
||||
- 提供更高级的消息封装协议(如带时间戳、来源标识等)。
|
||||
|
||||
---
|
||||
|
||||
## 📎 版本信息
|
||||
|
||||
- 编写日期:2025年4月5日
|
||||
- 作者:AI Assistant
|
||||
- 许可:MIT(假设代码开源)
|
||||
|
||||
---
|
||||
|
||||
📌 **提示**:将此模块保存为 `zmq_async.py`,即可导入使用:
|
||||
|
||||
```python
|
||||
from zmq_async import Publisher, Subscriber, ...
|
||||
```
|
||||
Loading…
x
Reference in New Issue
Block a user