This commit is contained in:
yumoqing 2025-10-05 11:23:33 +08:00
parent 6d74d084a9
commit 83ff81e5e0
86 changed files with 21616 additions and 0 deletions

217
aidocs/CSVData.md Normal file
View 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
View 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
View 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` 可实现优雅的嵌套配置访问。
推荐在受控环境中使用,注意安全风险。
---
```

View 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
View 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
View 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
View 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
View 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;
```
> ✅ 注意:即使没有显式 BEGINSQLite 默认自动提交模式下也需 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
View 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)) # 输出: HAHAme HAHAme
```
> 尽管两次构造传参不同,但由于单例机制,`d` 实际上是 `c` 的引用,`name` 仍为 `'me'`
```python
e = Handle('hammer')
f = Handle('nail')
print(str(e), str(f)) # 假设实现了 __str__,否则会报错
```
同样地,`f``e` 是同一实例,最终输出取决于 `Handle` 是否重写了字符串方法。
---
## 输出结果分析
运行上述测试代码的实际输出为:
```
child.init
HAHAme HAHAme
HAHAme HAHAme
```
> 因为 `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) # HAHAme HAHAme
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
View 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
View 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`
> ❌ 存在 BugUDP 分支前缺少 `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
View 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 APIIP 获取)
- UPnP 端口扫描避免冲突
---
## 已知限制与注意事项
1. **依赖网关支持**
- 需要路由器开启 UPnP 或 NAT-PMP 功能
- 部分运营商级 NATCGNAT环境下仍无法映射
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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
```
### 示例 2JSON 序列化
```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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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。 |
| &nbsp;&nbsp;`path` | `str` | 接口路径,支持 `${}` 占位符变量。 |
| &nbsp;&nbsp;`method` | `str` | HTTP 方法GET/POST/PUT/DELETE 等),默认为 `GET`。 |
| &nbsp;&nbsp;`headers` | `list[dict]` | 请求头列表,每个元素形如 `{name: "Header-Key", value: "HeaderValue", converter?: "funcName"}` |
| &nbsp;&nbsp;`params` | `list[dict]` | URL 查询参数,格式同上。 |
| &nbsp;&nbsp;`data` | `list[dict]` | 请求体数据JSON 格式),格式同上。 |
| &nbsp;&nbsp;`resp` | `list[dict]` | 响应数据提取规则。 |
| &nbsp;&nbsp;&nbsp;&nbsp;`name` | `str` | 返回结果中字段的名称。 |
| &nbsp;&nbsp;&nbsp;&nbsp;`resp_keys` | `list[str]` | 在响应 JSON 中获取数据的路径(嵌套键)。 |
| &nbsp;&nbsp;&nbsp;&nbsp;`converter` | `str` | 转换函数名(需在 `converters` 中注册)。 |
| &nbsp;&nbsp;`error_if` | `dict` | 自定义错误判断条件。 |
| &nbsp;&nbsp;&nbsp;&nbsp;`error_keys` | `list[str]` | 提取用于比较的响应字段路径。 |
| &nbsp;&nbsp;&nbsp;&nbsp;`op` | `str` | 比较操作符:`==``!=`。 |
| &nbsp;&nbsp;&nbsp;&nbsp;`value` | any | 期望值,用于对比。 |
| &nbsp;&nbsp;&nbsp;&nbsp;`code_keys` | `list[str]` | 错误码字段路径(可选)。 |
| &nbsp;&nbsp;&nbsp;&nbsp;`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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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)`
根据月份划分季节:
| 月份 | 季度 |
|----------|------|
| 13 | 1 |
| 46 | 2 |
| 79 | 3 |
| 1012 | 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]'` | 每周星期X0=周一...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
View 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
View 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
View 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
View 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 需要设备处于同一局域网且路由器启用相应功能。
- 某些防火墙可能阻止 SSDPUPnP 发现协议)广播包。
2. **性能与延迟**
- `ipgetter_get_external_ip()` 依赖网络请求,可能较慢。
- 推荐仅作为备用手段。
3. **稳定性问题**
- `upnpclient.discover()` 可能因网络波动返回空列表,导致索引错误。
- 建议添加超时处理或重试逻辑。
4. **多进程开销**
- `get_ip()` 每次创建新进程,不适合高频调用(如 >1次/秒)。
5. **输出重定向风险**
- `os.dup2` 修改了子进程的标准输出,确保只在子进程中使用。
---
## 未来优化建议
| 改进项 | 描述 |
|--------|------|
| 添加超时机制 | 给每个 IP 获取方法设置最大等待时间 |
| 缓存最近 IP | 减少重复查询开销 |
| 支持更多协议 | 如 PCPPort Control Protocol |
| 异常日志记录 | 替代 `print()`,使用 `logging` 模块 |
| 配置化策略 | 允许用户配置启用哪些协议及顺序 |
| 异步支持 | 使用 `asyncio` + `aiohttp` 提升效率 |
---
## 许可证
请根据项目实际情况补充 LICENSE 信息。
> 示例MIT License
```
---
✅ 文档完成。可保存为 `README.md` 或集成至 Sphinx/Docusaurus 文档系统。

221
aidocs/unicoding.md Normal file
View 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
View 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
View 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
View 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
View File

@ -0,0 +1,245 @@
# 颜色对比度计算工具技术文档
本模块提供了一组用于计算颜色对比度和评估其是否符合 **WCAGWeb 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 为 0255 范围内的整数AAlpha可选不参与计算
> ✅ 注意:此函数仅使用 RGB 分量,忽略 Alpha 通道。
**返回值:**
- `float`: 相对亮度值,范围在 `0.0`(黑色)到 `1.0`(白色)之间。
**公式:**
```
L = 0.2126 * R + 0.7152 * G + 0.0722 * B
```
其中 R、G、B 应先归一化为 01 范围(即除以 255并应用伽马校正sRGB 转线性亮度)。但当前实现未包含归一化与伽马校正步骤,**这是一个严重缺陷!**
> 🔴 **警告:当前实现有误!**
> - 变量名错误:`color``colr` 混用 → 应统一为 `rgba`
> - 缺少归一化处理(需将 0255 映射到 01
> - 缺少 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 值 | 输入是 0255但公式期望 01 | 添加 `/ 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
View 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
View 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
View 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
View 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, ...
```