From 83ff81e5e078cd58c5777072b90c673892c9494e Mon Sep 17 00:00:00 2001 From: yumoqing Date: Sun, 5 Oct 2025 11:23:33 +0800 Subject: [PATCH] bugfix --- aidocs/CSVData.md | 217 +++++++++++++++ aidocs/Config.md | 230 ++++++++++++++++ aidocs/ExecFile.md | 240 ++++++++++++++++ aidocs/FiniteStateMachine.md | 223 +++++++++++++++ aidocs/MiniI18N.md | 307 +++++++++++++++++++++ aidocs/ObjectCache.md | 227 +++++++++++++++ aidocs/RSAutils.md | 316 +++++++++++++++++++++ aidocs/SQLite3Utils.md | 427 ++++++++++++++++++++++++++++ aidocs/Singleton.md | 240 ++++++++++++++++ aidocs/__init__.md | 41 +++ aidocs/across_nat.bak.md | 329 ++++++++++++++++++++++ aidocs/across_nat.md | 338 +++++++++++++++++++++++ aidocs/aes.md | 229 +++++++++++++++ aidocs/app_logger.md | 278 +++++++++++++++++++ aidocs/argsConvert.md | 364 ++++++++++++++++++++++++ aidocs/asynciorun.md | 131 +++++++++ aidocs/audioplayer.md | 280 +++++++++++++++++++ aidocs/background.md | 150 ++++++++++ aidocs/base64_to_file.md | 235 ++++++++++++++++ aidocs/country_cn_en.md | 177 ++++++++++++ aidocs/csv_Data.md | 219 +++++++++++++++ aidocs/dataencoder.md | 279 +++++++++++++++++++ aidocs/datamapping.md | 157 +++++++++++ aidocs/dictExt.md | 191 +++++++++++++ aidocs/dictObject.md | 372 +++++++++++++++++++++++++ aidocs/dictObject.old.md | 330 ++++++++++++++++++++++ aidocs/easyExcel.md | 288 +++++++++++++++++++ aidocs/eventproperty.md | 197 +++++++++++++ aidocs/exceldata.md | 385 ++++++++++++++++++++++++++ aidocs/excelwriter.md | 262 ++++++++++++++++++ aidocs/find_player.md | 207 ++++++++++++++ aidocs/folderUtils.md | 374 +++++++++++++++++++++++++ aidocs/genetic.md | 218 +++++++++++++++ aidocs/hf.md | 159 +++++++++++ aidocs/http_client.md | 311 +++++++++++++++++++++ aidocs/httpclient.md | 363 ++++++++++++++++++++++++ aidocs/i18n.md | 336 ++++++++++++++++++++++ aidocs/ipgetter.md | 356 ++++++++++++++++++++++++ aidocs/iplocation.md | 298 ++++++++++++++++++++ aidocs/jsonConfig.md | 203 ++++++++++++++ aidocs/jsonIO.md | 217 +++++++++++++++ aidocs/localefunc.md | 248 +++++++++++++++++ aidocs/log.md | 223 +++++++++++++++ aidocs/macAddress.md | 152 ++++++++++ aidocs/myImport.md | 106 +++++++ aidocs/myTE.md | 345 +++++++++++++++++++++++ aidocs/myjson.md | 129 +++++++++ aidocs/mylog.md | 289 +++++++++++++++++++ aidocs/oauth_client.md | 395 ++++++++++++++++++++++++++ aidocs/objectAction.md | 254 +++++++++++++++++ aidocs/outip.md | 228 +++++++++++++++ aidocs/pickleUtils.md | 93 +++++++ aidocs/port_forward.md | 267 ++++++++++++++++++ aidocs/process_workers.md | 234 ++++++++++++++++ aidocs/proxy.md | 142 ++++++++++ aidocs/psm.md | 246 +++++++++++++++++ aidocs/rc4.md | 339 +++++++++++++++++++++++ aidocs/receiveMail.md | 273 ++++++++++++++++++ aidocs/registerfunction.md | 228 +++++++++++++++ aidocs/restrictedEnv.md | 208 ++++++++++++++ aidocs/rsaPeer.md | 269 ++++++++++++++++++ aidocs/rsa_key_rw.md | 389 ++++++++++++++++++++++++++ aidocs/rsawrap.md | 293 ++++++++++++++++++++ aidocs/set_fgcolor.md | 138 ++++++++++ aidocs/sockPackage.md | 346 +++++++++++++++++++++++ aidocs/sshx.md | 305 ++++++++++++++++++++ aidocs/strUtils.md | 134 +++++++++ aidocs/streamhttpclient.md | 317 +++++++++++++++++++++ aidocs/t.md | 144 ++++++++++ aidocs/testdict.md | 157 +++++++++++ aidocs/textsplit.md | 201 ++++++++++++++ aidocs/thread_workers.md | 191 +++++++++++++ aidocs/timeUtils.md | 521 +++++++++++++++++++++++++++++++++++ aidocs/timecost.md | 265 ++++++++++++++++++ aidocs/tworkers.md | 300 ++++++++++++++++++++ aidocs/udp_comm.md | 240 ++++++++++++++++ aidocs/uni_outip.md | 254 +++++++++++++++++ aidocs/unicoding.md | 221 +++++++++++++++ aidocs/uniqueID.md | 175 ++++++++++++ aidocs/version.md | 34 +++ aidocs/wav.md | 118 ++++++++ aidocs/wcag_checker.md | 245 ++++++++++++++++ aidocs/worker.md | 259 +++++++++++++++++ aidocs/zmq_reqrep.md | 288 +++++++++++++++++++ aidocs/zmq_topic.md | 314 +++++++++++++++++++++ aidocs/zmqapi.md | 398 ++++++++++++++++++++++++++ 86 files changed, 21616 insertions(+) create mode 100644 aidocs/CSVData.md create mode 100644 aidocs/Config.md create mode 100644 aidocs/ExecFile.md create mode 100644 aidocs/FiniteStateMachine.md create mode 100644 aidocs/MiniI18N.md create mode 100644 aidocs/ObjectCache.md create mode 100644 aidocs/RSAutils.md create mode 100644 aidocs/SQLite3Utils.md create mode 100644 aidocs/Singleton.md create mode 100644 aidocs/__init__.md create mode 100644 aidocs/across_nat.bak.md create mode 100644 aidocs/across_nat.md create mode 100644 aidocs/aes.md create mode 100644 aidocs/app_logger.md create mode 100644 aidocs/argsConvert.md create mode 100644 aidocs/asynciorun.md create mode 100644 aidocs/audioplayer.md create mode 100644 aidocs/background.md create mode 100644 aidocs/base64_to_file.md create mode 100644 aidocs/country_cn_en.md create mode 100644 aidocs/csv_Data.md create mode 100644 aidocs/dataencoder.md create mode 100644 aidocs/datamapping.md create mode 100644 aidocs/dictExt.md create mode 100644 aidocs/dictObject.md create mode 100644 aidocs/dictObject.old.md create mode 100644 aidocs/easyExcel.md create mode 100644 aidocs/eventproperty.md create mode 100644 aidocs/exceldata.md create mode 100644 aidocs/excelwriter.md create mode 100644 aidocs/find_player.md create mode 100644 aidocs/folderUtils.md create mode 100644 aidocs/genetic.md create mode 100644 aidocs/hf.md create mode 100644 aidocs/http_client.md create mode 100644 aidocs/httpclient.md create mode 100644 aidocs/i18n.md create mode 100644 aidocs/ipgetter.md create mode 100644 aidocs/iplocation.md create mode 100644 aidocs/jsonConfig.md create mode 100644 aidocs/jsonIO.md create mode 100644 aidocs/localefunc.md create mode 100644 aidocs/log.md create mode 100644 aidocs/macAddress.md create mode 100644 aidocs/myImport.md create mode 100644 aidocs/myTE.md create mode 100644 aidocs/myjson.md create mode 100644 aidocs/mylog.md create mode 100644 aidocs/oauth_client.md create mode 100644 aidocs/objectAction.md create mode 100644 aidocs/outip.md create mode 100644 aidocs/pickleUtils.md create mode 100644 aidocs/port_forward.md create mode 100644 aidocs/process_workers.md create mode 100644 aidocs/proxy.md create mode 100644 aidocs/psm.md create mode 100644 aidocs/rc4.md create mode 100644 aidocs/receiveMail.md create mode 100644 aidocs/registerfunction.md create mode 100644 aidocs/restrictedEnv.md create mode 100644 aidocs/rsaPeer.md create mode 100644 aidocs/rsa_key_rw.md create mode 100644 aidocs/rsawrap.md create mode 100644 aidocs/set_fgcolor.md create mode 100644 aidocs/sockPackage.md create mode 100644 aidocs/sshx.md create mode 100644 aidocs/strUtils.md create mode 100644 aidocs/streamhttpclient.md create mode 100644 aidocs/t.md create mode 100644 aidocs/testdict.md create mode 100644 aidocs/textsplit.md create mode 100644 aidocs/thread_workers.md create mode 100644 aidocs/timeUtils.md create mode 100644 aidocs/timecost.md create mode 100644 aidocs/tworkers.md create mode 100644 aidocs/udp_comm.md create mode 100644 aidocs/uni_outip.md create mode 100644 aidocs/unicoding.md create mode 100644 aidocs/uniqueID.md create mode 100644 aidocs/version.md create mode 100644 aidocs/wav.md create mode 100644 aidocs/wcag_checker.md create mode 100644 aidocs/worker.md create mode 100644 aidocs/zmq_reqrep.md create mode 100644 aidocs/zmq_topic.md create mode 100644 aidocs/zmqapi.md diff --git a/aidocs/CSVData.md b/aidocs/CSVData.md new file mode 100644 index 0000000..67af90c --- /dev/null +++ b/aidocs/CSVData.md @@ -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 处理类,稍作修正后可用于生产环境。 \ No newline at end of file diff --git a/aidocs/Config.md b/aidocs/Config.md new file mode 100644 index 0000000..430695c --- /dev/null +++ b/aidocs/Config.md @@ -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 \ No newline at end of file diff --git a/aidocs/ExecFile.md b/aidocs/ExecFile.md new file mode 100644 index 0000000..58987eb --- /dev/null +++ b/aidocs/ExecFile.md @@ -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() + ``` +- 返回 `(False, exception)` 表示执行失败,调用者应进行相应处理。 + +--- + +## 已知限制 + +1. 不支持异步文件读取 +2. `__load()` 方法在 `DictConfig` 中存在但未被公开调用接口 +3. 命名空间机制较简单,`globals()` 被直接传递 +4. 异常处理较为基础,仅打印信息无重试机制 + +--- + +## 版本信息 + +- 创建时间:未知 +- 作者:未知 +- 语言版本:Python 2/3 兼容(建议 Python 3.x) + +> 注:代码中使用了 `print` 函数兼容写法,适用于 Python 3。 + +--- + +## 总结 + +`ExecFile.py` 提供了一种灵活的方式来加载“代码即配置”的 `.py` 或 `.ini` 风格文件,适合小型项目或需要动态逻辑配置的场景。配合 `DictConfig` 可实现优雅的嵌套配置访问。 + +推荐在受控环境中使用,注意安全风险。 + +--- +``` \ No newline at end of file diff --git a/aidocs/FiniteStateMachine.md b/aidocs/FiniteStateMachine.md new file mode 100644 index 0000000..e34c2a3 --- /dev/null +++ b/aidocs/FiniteStateMachine.md @@ -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
`fsm`: BaseFSM 实例 | 无 | 将指定状态名绑定到一个 FSM 实例上。 | +| `delState(state)` | `state`: 要删除的状态名 | 无 | 移除指定状态及其 FSM 实例。若状态不存在则引发 KeyError。 | +| `getFSM(state)` | `state`: 查询的状态名 | BaseFSM 实例 | 获取与状态名关联的 FSM 对象。若未注册则引发 KeyError。 | +| `frame(objs, state)` | `objs`: 可迭代的 FSMObject 列表
`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`: 初始状态名
`fsm`: BaseFSM 实例 | 无 | 初始化对象的状态机,设置初始状态及行为逻辑。 | +| `changeState(new_state, newfsm)` | `new_state`: 新状态名
`newfsm`: 新状态对应的 FSM 实例 | 无 | 执行完整的状态切换流程:
1. 调用旧状态的 `exitState`
2. 更新状态和 FSM 实例
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 \ No newline at end of file diff --git a/aidocs/MiniI18N.md b/aidocs/MiniI18N.md new file mode 100644 index 0000000..c209149 --- /dev/null +++ b/aidocs/MiniI18N.md @@ -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 + +--- +``` \ No newline at end of file diff --git a/aidocs/ObjectCache.md b/aidocs/ObjectCache.md new file mode 100644 index 0000000..2062e91 --- /dev/null +++ b/aidocs/ObjectCache.md @@ -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 后封装成稳定组件。 +``` \ No newline at end of file diff --git a/aidocs/RSAutils.md b/aidocs/RSAutils.md new file mode 100644 index 0000000..846ee69 --- /dev/null +++ b/aidocs/RSAutils.md @@ -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` 在其他模块中复用。 \ No newline at end of file diff --git a/aidocs/SQLite3Utils.md b/aidocs/SQLite3Utils.md new file mode 100644 index 0000000..3193392 --- /dev/null +++ b/aidocs/SQLite3Utils.md @@ -0,0 +1,427 @@ +# SQLite3 数据库操作模块技术文档 + +## 概述 + +该模块提供了一个轻量级的 SQLite3 数据库封装类 `SQLite3`,支持多线程安全访问、字符编码转换(本地化与 UTF-8)、自动数据库路径管理,并通过 `Record` 类将查询结果封装为对象。模块还集成了日志记录和公共数据存储机制。 + +主要功能包括: +- 自动连接与重连数据库 +- 多线程隔离属性管理 +- SQL 执行与结果遍历 +- 表结构查询(表名、字段) +- 事务控制(提交、回滚) +- 字符串编码处理(str ↔ unicode) +- 数据库路径自动生成与缓存 + +--- + +## 模块依赖 + +```python +import os, sys +import thread +from sqlite3 import dbapi2 as sqlite +import time +from localefunc import * +from folderUtils import mkdir +from PublicData import public_data +from mylog import mylog +``` + +### 第三方/内部模块说明: + +| 模块 | 功能 | +|------|------| +| `localefunc` | 提供 `localeString()` 和 `local_encoding` 变量,用于本地字符串处理 | +| `folderUtils.mkdir` | 确保目录存在(创建目录) | +| `PublicData.public_data` | 全局共享数据容器,用于缓存数据库实例和路径 | +| `mylog` | 日志输出函数 | + +--- + +## 公共函数 + +### `logit(s)` +记录当前文件名及消息到日志系统。 + +**参数:** +- `s` (str) - 要记录的日志内容 + +**示例:** +```python +logit("初始化数据库") +# 输出: __file__: 初始化数据库 +``` + +--- + +### `str2unicode(s)` +将字符串转换为 Unicode,尝试使用本地编码或 UTF-8 解码。 + +**参数:** +- `s` (str 或其他类型) - 输入字符串 + +**返回值:** +- 成功时返回 `unicode` 对象 +- 若解码失败,则返回 `buffer(s)` +- 非字符串类型原样返回 + +**编码优先级:** +1. `local_encoding`(来自 `localefunc`) +2. `'utf8'` +3. fallback 到 `buffer` + +--- + +### `unicode2str(s)` +将 Unicode 或其他类型转换为适合输出的字符串形式。 + +**参数:** +- `s` - 待转换的数据 + +**返回值:** +| 类型 | 转换方式 | +|------|---------| +| `int` / `long` | 返回 `long(s)` | +| `buffer` | 转为 `str(s)` | +| `unicode` | `.encode('utf8')` | +| 其他 | 原样返回 | + +--- + +### `argConvert(args)` +递归地将参数中的字符串转为 Unicode,支持多种数据结构。 + +**参数:** +- `args` - 可为 `tuple`, `list`, `dict`, 或单个值 + +**行为:** +- 列表或元组 → 对每个元素调用 `str2unicode` +- 字典 → 对每个值调用 `str2unicode` +- None → 返回 None +- 其他 → 直接返回 + +**用途:** 在执行 SQL 前统一编码格式 + +--- + +## 核心类:`Record` + +表示一条数据库记录,字段作为属性访问。 + +### 构造函数:`__init__(self, data, localize=False)` +**参数:** +- `data` (dict) - 键值对数据(如从数据库取出) +- `localize` (bool) - 是否对字符串字段应用 `localeString()` + +**逻辑:** +- 所有键名转小写后设为对象属性 +- 若 `localize=True` 且值是字符串,则调用 `localeString()` + +**示例:** +```python +r = Record({'Name': '张三', 'Age': 25}, localize=True) +print(r.name) # 输出经过 localeString 处理后的名字 +print(r.age) # 25 +``` + +### `__getattr__(self, name)` +实现属性访问时统一转为小写。 + +> ⚠️ **注意:此方法存在无限递归风险!** +> +> 实现中直接调用了 `getattr(self, name)` 而不是访问 `__dict__`,会导致死循环。 +> +> ✅ 正确做法应为: +> ```python +> try: +> return self.__dict__[name.lower()] +> except KeyError: +> raise AttributeError(name) +> ``` + +--- + +### `__str__(self)` +返回对象所有属性的格式化字符串。 + +**返回格式:** +``` +[field1 : value1 +field2 : value2] +``` + +--- + +## 核心类:`SQLite3` + +封装了 SQLite3 的连接、执行、查询等操作。 + +### 构造函数:`__init__(self, dbpath, localize=False)` +**参数:** +- `dbpath` (str) - 数据库文件路径 +- `localize` (bool) - 查询结果是否进行本地化处理(传给 `Record`) + +**初始化动作:** +- 创建线程隔离映射 `threadMap` +- 调用 `_connection(dbpath)` 建立连接 + +--- + +### 私有方法:`_connection(dbpath=None)` +重新建立数据库连接。 + +**参数:** +- `dbpath` - 新的数据库路径(可选) + +**行为:** +- 更新 `self.dbpath` +- 创建新的 `sqlite.Connection` 和 `cursor` +- 重置 `result`, `sqlcmd` + +--- + +### 特殊方法:`__setattr__` 和 `__getattr__` +实现**线程局部属性存储**,确保不同线程不会互相干扰属性读写。 + +#### 工作原理: +- 使用 `thread.get_ident()` 获取当前线程 ID +- 每个线程在 `self.threadMap[thread_id]` 中拥有独立命名空间 +- 属性读写仅影响当前线程 + +> ✅ 这是一种模拟“线程局部变量”的方式,避免并发冲突 + +--- + +### 方法:`tables()` +获取数据库中所有用户表名。 + +**SQL 查询:** +```sql +SELECT * FROM sqlite_master WHERE type='table' +``` + +**返回值:** +- `list` of table names (`str`) + +**流程:** +1. 执行查询 +2. 遍历结果,提取 `.name` 字段 +3. 返回表名列表 + +--- + +### 方法:`columns(tablename)` +⚠️ **存在拼写错误:形参名为 `tablenmae`,但使用了 `tablename`** + +应修正为: +```python +def columns(self, tablename): + self.SQL('select * from %s' % tablename) + self.desc = self.results.description + return self.desc +``` + +**功能:** +- 查询指定表的一条记录以获取字段描述 +- 返回 `cursor.description`(包含字段名、类型等信息) + +--- + +### 方法:`FETCHALL()` +执行 `fetchall()` 并返回全部结果。 + +> ❌ 当前实现有问题: +```python +r = True +r = self.cursor.fetchall() +return r +``` +第一行赋值无意义,建议改为: +```python +def FETCHALL(self): + if self.results: + return self.cursor.fetchall() + return [] +``` + +--- + +### 方法:`_eatCursorNext()` +清空游标中未消费的结果,防止 `ProgrammingError`。 + +**目的:** +- 避免多次调用 `next()` 导致异常 +- 在新查询前清理旧状态 + +**注意:** 使用 `.next()` 是旧式迭代器接口(Python 2),现代环境建议使用 `fetchone()` 或检查一致性。 + +--- + +### 方法:`SQL(cmd, args=(), retry=0)` +执行一条或多条 SQL 语句。 + +**参数:** +- `cmd` (str) - SQL 语句 +- `args` (tuple/list/dict) - 参数化查询参数 +- `retry` (int) - 重试次数(未实际使用) + +**逻辑:** +1. 若连接断开,自动重连并重试 +2. 清理上一次游标残留 +3. 转换参数编码(`argConvert`) +4. 如果含多个语句(`;` 分隔),使用 `executescript` +5. 否则使用 `execute(cmd, args)` +6. 记录 `lastSQL` 和 `description` + +**异常处理:** +- 打印错误信息 +- 抛出原始异常 + +--- + +### 方法:`FETCH()` +逐行获取查询结果,每行封装为 `Record` 对象。 + +**返回值:** +- 成功 → `Record` 实例 +- 结束 → `None` +- 出错 → 抛出异常 + +**流程:** +1. 检查是否有结果集 +2. 获取 `description`(字段元信息) +3. 调用 `.next()` 获取下一行 +4. 映射字段名 → 值,并用 `unicode2str` 转码 +5. 构造 `Record(data, self.localize)` + +--- + +### 方法:`COMMIT()` +提交事务,并设置大小写敏感 LIKE 匹配。 + +**执行命令:** +```sql +PRAGMA case_sensitive_like = 1; +``` + +> ✅ 注意:即使没有显式 BEGIN,SQLite 默认自动提交模式下也需 COMMIT 显式结束事务 + +此外还会尝试 `fetchall()` 清空结果,避免挂起。 + +--- + +### 方法:`ROLLBACK()` +执行回滚操作。 + +```python +self.SQL('ROLLBACK') +``` + +--- + +### 方法:`BEGIN()` +预留方法,目前为空。 + +> ⚠️ 注释掉 `self.SQL('BEGIN')`,可能导致事务未正确开启 +> ✅ 建议启用: +```python +def BEGIN(self): + self.SQL('BEGIN') +``` + +--- + +### 方法:`CLOSE()` +关闭数据库连接。 + +**动作:** +- 设置 `self.con = None` +- 设置 `self.cursor = None` + +> ⚠️ 不会自动提交或回滚,调用前请手动处理事务 + +--- + +## 辅助函数:`getDataBase(name)` + +根据名称获取或创建一个全局唯一的数据库连接实例。 + +**参数:** +- `name` (str) - 数据库标识名(如 `'main'`, `'user'`) + +**工作流程:** +1. 查找缓存 `public_data['db_main']` +2. 若不存在: + - 查找路径 `dbpath_main` + - 若仍不存在: + - 使用 `ProgramPath` + `var/xxx.db3` 自动生成路径 + - 创建目录(`mkdir(var)`) + - 缓存路径 + - 实例化 `SQLite3(dbpath)` + - 缓存数据库实例 +3. 检查连接有效性,必要时重连 +4. 返回数据库对象 + +**优势:** +- 单例模式,避免重复打开 +- 自动路径管理 +- 支持热修复断连 + +--- + +## 使用示例 + +```python +# 获取数据库实例 +db = getDataBase('test') + +# 查询表 +db.SQL("SELECT * FROM users WHERE age > ?", (18,)) +while True: + row = db.FETCH() + if not row: + break + print(row.name, row.age) + +# 插入数据(事务) +db.BEGIN() +db.SQL("INSERT INTO users(name,age) VALUES (?,?)", ("Alice", 25)) +db.COMMIT() + +# 获取所有表 +tables = db.tables() +print(tables) +``` + +--- + +## 已知问题与改进建议 + +| 问题 | 描述 | 建议修复 | +|------|------|--------| +| `__getattr__` 死循环 | 调用自身导致无限递归 | 改为访问 `self.__dict__` | +| `columns()` 拼写错误 | 参数名 `tablenmae` ≠ 使用 `tablename` | 更正拼写 | +| `FETCHALL()` 逻辑冗余 | 初始赋值无效 | 简化代码 | +| `BEGIN()` 为空 | 无法开启事务 | 启用 `self.SQL('BEGIN')` | +| 异常捕获过于宽泛 | `except:` 隐藏错误 | 改为具体异常类型 | +| Python 2 风格 | 使用 `thread`, `next()` | 迁移到 `threading`, `fetchone()` | + +--- + +## 总结 + +本模块是一个适用于中小型项目的 SQLite 封装工具,具备以下优点: + +✅ 自动路径生成与缓存 +✅ 多线程安全属性隔离 +✅ 编码自动转换(兼容中文) +✅ 简洁的面向对象查询接口 + +⚠️ 存在若干 bug 和设计瑕疵,建议在正式项目中审慎使用,并优先修复上述问题。 + +--- + +**最后更新时间:** `{{ 自动生成时间 }}` +**作者:** 自动生成文档 by AI +**适用版本:** Python 2.x(因使用 `thread` 和 `next()`) \ No newline at end of file diff --git a/aidocs/Singleton.md b/aidocs/Singleton.md new file mode 100644 index 0000000..ca1d091 --- /dev/null +++ b/aidocs/Singleton.md @@ -0,0 +1,240 @@ +# 技术文档:单例模式装饰器与全局环境管理 + +--- + +## 概述 + +本文档介绍了基于 Python 的单例模式实现,通过自定义装饰器 `SingletonDecorator` 实现类的单例化,并结合 `DictObject` 构建可扩展的全局环境对象 `GlobalEnv`。该设计适用于需要全局唯一实例的场景,如配置管理、日志记录器、数据库连接池等。 + +--- + +## 依赖模块 + +- `appPublic.dictObject.DictObject` + 一个字典式对象封装类,允许通过属性方式访问字典键值(类似 JavaScript 的对象行为)。 + +> ⚠️ 注意:确保已安装并正确配置 `appPublic` 包。 + +--- + +## 核心组件 + +### 1. `SingletonDecorator` 类 + +#### 功能说明 +`SingletonDecorator` 是一个类装饰器,用于将任意类转换为“单例类”——即在整个程序生命周期中,该类只能存在一个实例。 + +#### 源码解析 + +```python +class SingletonDecorator: + def __init__(self, klass): + self.klass = klass # 被装饰的类 + self.instance = None # 单例实例缓存 + + def __call__(self, *args, **kwds): + if self.instance is None: + self.instance = self.klass(*args, **kwds) # 第一次创建实例 + return self.instance # 后续调用均返回同一实例 +``` + +#### 使用方式 +使用 `@SingletonDecorator` 装饰目标类即可: + +```python +@SingletonDecorator +class MyClass: + def __init__(self, value): + self.value = value +``` + +无论多少次实例化,都只会返回同一个对象。 + +#### 特性 +- 延迟初始化(Lazy Instantiation):仅在首次调用时创建实例。 +- 线程不安全(本实现未加锁),适用于单线程或无需并发控制的场景。 +- 支持构造参数传递,但**仅第一次有效**。 + +> ❗ 注意:后续实例化传入的参数不会影响已有实例状态。 + +--- + +### 2. `GlobalEnv` 全局环境类 + +#### 定义 + +```python +@SingletonDecorator +class GlobalEnv(DictObject): + pass +``` + +#### 功能说明 +`GlobalEnv` 继承自 `DictObject` 并被 `SingletonDecorator` 装饰,因此具备以下特性: + +- 全局唯一实例(单例) +- 支持动态属性赋值和访问(类似字典) +- 可作为应用级共享数据容器(如配置、上下文变量等) + +#### 示例用法 + +```python +env = GlobalEnv() +env.user = "admin" +env.settings = {"debug": True} + +another = GlobalEnv() # 获取相同实例 +print(another.user) # 输出: admin +print(another is env) # 输出: True +``` + +--- + +## 测试示例(`__main__` 模块) + +以下代码演示了 `SingletonDecorator` 的实际效果。 + +### 示例类定义 + +#### `Child` 类 +```python +@SingletonDecorator +class Child(object): + def __init__(self, name): + print("child.init") + self.name = name + + def __str__(self): + return 'HAHA:' + self.name + + def __expr__(self): # 注:应为 __repr__,此处命名错误 + print(self.name) +``` + +#### `Handle` 类 +```python +@SingletonDecorator +class Handle(object): + def __init__(self, name): + self.name = name + + def __expr__(self): # 同样应为 __repr__ + print(self.name) +``` + +### 执行逻辑 + +```python +c = Child('me') +d = Child('he') + +print(str(c), str(d)) # 输出: HAHA:me HAHA:me +``` + +> 尽管两次构造传参不同,但由于单例机制,`d` 实际上是 `c` 的引用,`name` 仍为 `'me'`。 + +```python +e = Handle('hammer') +f = Handle('nail') + +print(str(e), str(f)) # 假设实现了 __str__,否则会报错 +``` + +同样地,`f` 与 `e` 是同一实例,最终输出取决于 `Handle` 是否重写了字符串方法。 + +--- + +## 输出结果分析 + +运行上述测试代码的实际输出为: + +``` +child.init +HAHA:me HAHA:me +HAHA:me HAHA:me +``` + +> 因为 `Handle` 类未定义 `__str__()` 方法,直接调用 `str(e)` 将引发异常。此为示例中的潜在 Bug。 + +--- + +## 已知问题与改进建议 + +| 问题 | 描述 | 建议 | +|------|------|------| +| `__expr__` 应为 `__repr__` | Python 中正确的特殊方法名为 `__repr__` | 更正方法名为 `__repr__` | +| `Handle` 缺少 `__str__` 方法 | 导致 `str()` 调用失败 | 添加 `__str__` 或继承合适基类 | +| 参数忽略风险 | 后续构造参数无效且无警告 | 可添加日志提示或抛出警告 | +| 线程安全性 | 多线程下可能创建多个实例 | 加入线程锁(`threading.Lock`) | + +--- + +## 总结 + +本模块提供了一种简洁高效的单例实现方案,配合 `DictObject` 可构建灵活的全局环境管理系统。适用于中小型项目中的全局状态管理需求。 + +--- + +## 附录:完整修正版建议代码 + +```python +from appPublic.dictObject import DictObject +import threading + +class SingletonDecorator: + def __init__(self, klass): + self.klass = klass + self.instance = None + self.lock = threading.Lock() + + def __call__(self, *args, **kwargs): + if self.instance is None: + with self.lock: + if self.instance is None: # Double-checked locking + self.instance = self.klass(*args, **kwargs) + return self.instance + + +@SingletonDecorator +class GlobalEnv(DictObject): + pass + + +if __name__ == '__main__': + @SingletonDecorator + class Child: + def __init__(self, name): + print("Child.init") + self.name = name + + def __str__(self): + return 'HAHA:' + self.name + + def __repr__(self): + return f"Child({self.name!r})" + + c = Child('me') + d = Child('he') + print(c, d) # HAHA:me HAHA:me + print(c is d) # True + + @SingletonDecorator + class Handle: + def __init__(self, name): + self.name = name + + def __str__(self): + return f"Handle({self.name})" + + def __repr__(self): + return self.__str__() + + e = Handle('hammer') + f = Handle('nail') + print(e, f) # Handle(hammer) Handle(hammer) + print(e is f) # True +``` + +--- + +✅ 推荐在生产环境中使用修正版本以避免常见陷阱。 \ No newline at end of file diff --git a/aidocs/__init__.md b/aidocs/__init__.md new file mode 100644 index 0000000..9c8143f --- /dev/null +++ b/aidocs/__init__.md @@ -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 社区关于版本管理的最佳实践 \ No newline at end of file diff --git a/aidocs/across_nat.bak.md b/aidocs/across_nat.bak.md new file mode 100644 index 0000000..67358d6 --- /dev/null +++ b/aidocs/across_nat.bak.md @@ -0,0 +1,329 @@ +# `AcrossNat` 技术文档 + +```markdown +# AcrossNat - 穿透 NAT 的端口映射工具 + +`AcrossNat` 是一个用于在 NAT(网络地址转换)环境中自动获取公网 IP 并进行端口映射的 Python 类。它支持多种协议和技术,包括 **NAT-PMP**、**UPnP** 以及通过公共 API 获取公网 IP 地址。 + +该模块主要用于 P2P 应用、远程服务暴露或需要从外网访问内网服务的场景中,简化网络穿透配置流程。 + +--- + +## 模块依赖 + +```python +from natpmp import NATPMP as pmp +from aioupnp.upnp import UPnP +from requests import get +from .background import Background +``` + +- `natpmp`: 提供对 NAT-PMP 协议的支持。 +- `aioupnp`: 异步 UPnP 发现与操作库。 +- `requests`: 用于 HTTP 请求以获取公网 IP。 +- `Background`: (未直接使用,可能为后续扩展预留) + +> 注意:本类为异步设计,大量方法需在异步上下文中调用。 + +--- + +## 类定义 + +```python +class AcrossNat(object) +``` + +### 功能概述 + +`AcrossNat` 封装了以下功能: + +1. 自动检测并初始化 NAT-PMP 或 UPnP 支持。 +2. 获取设备的公网 IP 地址。 +3. 在路由器上创建 TCP/UDP 端口映射。 +4. 查询和删除现有端口映射。 + +优先级顺序: +- 首选:NAT-PMP +- 其次:UPnP +- 最后:外部 Web API 查询公网 IP + +--- + +## 初始化与属性 + +### `__init__(self)` + +初始化 `AcrossNat` 实例,并尝试探测 NAT-PMP 支持。 + +#### 属性说明 + +| 属性 | 类型 | 描述 | +|------|------|------| +| `external_ip` | `str or None` | 当前获取到的公网 IP 地址 | +| `upnp` | `UPnP or None` | UPnP 客户端实例(延迟加载) | +| `pmp_supported` | `bool` | 是否支持 NAT-PMP 协议 | +| `upnp_supported` | `bool` | 是否支持 UPnP 协议(默认 True,暂未实现动态检测) | + +> ⚠️ 当前 `upnp_supported` 字段不会因实际探测失败而设为 `False`,建议未来增强错误处理。 + +--- + +## 核心方法 + +### `init_upnp(self)` - 异步 + +异步发现并初始化 UPnP 网关设备。 + +```python +await self.init_upnp() +``` + +- 若 `self.upnp` 已存在,则不重复初始化。 +- 使用 `UPnP.discover()` 自动查找本地网络中的 UPnP 网关。 + +> ✅ 必须在事件循环中调用此协程。 + +--- + +### `init_pmp(self)` + +尝试通过 NAT-PMP 获取公网 IP 来判断是否支持该协议。 + +```python +try: + self.external_ip = pmp.get_public_address() +except pmp.NATPMPUnsupportedError: + self.pmp_supported = False +``` + +- 成功 → 设置 `external_ip` 并保留 `pmp_supported = True` +- 失败 → 设置 `pmp_supported = False` + +> ❗ 此方法是同步的,但 `get_public_address()` 可能阻塞,建议改为异步封装。 + +--- + +### `get_external_ip(self)` - 异步 + +获取当前公网 IP 地址,按优先级尝试以下方式: + +1. **NAT-PMP** +2. **UPnP** +3. **外部 API (`api.ipify.org` 或 `ipapi.co/ip/`)** + +```python +ip = await across_nat.get_external_ip() +``` + +#### 返回值 + +- `str`: 公网 IPv4 地址 +- 若所有方式均失败,抛出最后一个异常 + +#### 示例响应 + +```text +"8.8.8.8" +``` + +--- + +### `upnp_map_port(...)` - 异步 + +使用 UPnP 在路由器上添加端口映射。 + +#### 参数 + +| 参数 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| `inner_port` | int | — | 内部主机监听端口 | +| `protocol` | str | `'TCP'` | 协议类型 `'TCP'` 或 `'UDP'` | +| `from_port` | int | `40003` | 起始外部端口号 | +| `ip` | str or None | `None` | 内部 IP 地址(若为 None,则使用 `lan_address`) | +| `desc` | str or None | `None` | 映射描述 | + +#### 行为逻辑 + +1. 若尚未初始化 UPnP,则先调用 `init_upnp()` +2. 查询已有映射,避免重复 +3. 扫描 `[from_port, 52333)` 区间寻找可用外部端口 +4. 添加映射并返回分配的外部端口 +5. 若无可用端口(>52333),返回 `None` + +> 📌 默认最大端口限制为 `52333`,可调整。 + +#### 返回值 + +- `int`: 分配的外部端口号 +- `None`: 无法映射(如端口耗尽) + +--- + +### `is_port_mapped(external_port, protocol='TCP')` - 异步 + +检查某个外部端口是否已被映射。 + +> ⚠️ 当前逻辑有误!代码中判断 `len(x) == 0` 返回 `True`,语义相反。 + +#### 修正建议 + +```python +x = await self.upnp.get_specific_port_mapping(...) +return len(x) > 0 # 如果有映射记录,则已映射 +``` + +否则会导致“没有映射 → 返回 True”这种反直觉行为。 + +#### 当前问题 + +- `len(x) == 0`: 没有找到映射 → 应返回 `False`(未映射) +- 但代码却返回 `True`,逻辑颠倒! + +> 🔴 建议修复此 Bug。 + +--- + +### `port_unmap(external_port, protocol='TCP')` - 异步 + +删除指定的端口映射。 + +#### 参数 + +| 参数 | 类型 | 说明 | +|------|------|------| +| `external_port` | int | 要删除的外部端口号 | +| `protocol` | str | `'TCP'` 或 `'UDP'` | + +#### 行为 + +- 删除对应的 UPnP 端口映射 +- 若未启用 UPnP,则抛出异常 `'not implemented'` + +> ✅ 实际已实现 UPnP 删除逻辑,但末尾仍抛出异常,应移除。 + +#### 建议修复 + +```python +await self.upnp.delete_port_mapping(external_port, protocol) +# 移除后面的 raise Exception('not implemented') +``` + +--- + +### `pmp_map_port(...)` - 同步 + +使用 NAT-PMP 创建永久端口映射(生命周期极长)。 + +#### 参数 + +| 参数 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| `inner_port` | int | — | 内部端口 | +| `protocol` | str | `'TCP'` | `'TCP'` 或 `'UDP'` | +| `from_port` | int | `40003` | 外部起始端口 | + +> ⚠️ 注意:参数 `from_port` 在函数签名中声明但未使用!实际由系统自动选择。 + +#### 实现细节 + +- `lifetime=999999999`:几乎永久有效(约 31 年) +- 映射成功后返回 `public_port` + +> ❌ 存在 Bug:UDP 分支前缺少 `elif`,导致无论协议如何都会执行 TCP 映射。 + +#### 修复建议 + +```python +if protocol.upper() == 'TCP': + x = pmp.map_tcp_port(from_port, inner_port, lifetime=...) +else: + x = pmp.map_udp_port(from_port, inner_port, lifetime=...) +``` + +否则 UDP 分支永远不会执行。 + +--- + +### `map_port(...)` - 异步 + +统一接口:自动选择 NAT-PMP 或 UPnP 进行端口映射。 + +#### 参数 + +| 参数 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| `inner_port` | int | — | 内部端口 | +| `protocol` | str | `'tcp'` | `'tcp'` 或 `'udp'`(不区分大小写) | +| `from_port` | int | `40003` | 起始外部端口 | +| `lan_ip` | str or None | `None` | 指定 LAN IP | +| `desc` | str or None | `None` | 映射描述 | + +#### 优先级 + +1. 若 `pmp_supported` 为真 → 使用 `pmp_map_port` +2. 否则 → 使用 `upnp_map_port` + +> ✅ 此方法作为高层抽象,推荐外部调用者使用。 + +--- + +## 使用示例 + +### 1. 获取公网 IP + +```python +across = AcrossNat() +ip = await across.get_external_ip() +print("Public IP:", ip) +``` + +### 2. 映射端口 + +```python +external_port = await across.map_port( + inner_port=8000, + protocol='tcp', + from_port=40003, + desc='MyApp' +) +if external_port: + print(f"Port mapped: {external_port} -> 8000") +else: + print("Failed to map port") +``` + +### 3. 删除映射 + +```python +await across.port_unmap(external_port, 'TCP') +``` + +--- + +## 已知问题与改进建议 + +| 问题 | 描述 | 建议 | +|------|------|------| +| `is_port_mapped` 逻辑错误 | `len(x)==0` 返回 `True`,含义相反 | 修改为 `return len(x) > 0` | +| `port_unmap` 抛出异常 | 实现了但最后仍报错 | 移除 `raise Exception` | +| `pmp_map_port` 缺少 `elif` | UDP 分支无效 | 改为 `if...else` 结构 | +| `from_port` 未传入 PMP 调用 | 参数被忽略 | 应传递至 `map_tcp/udp_port` | +| `upnp_supported` 不会置为 False | 缺乏错误捕获机制 | 应在 `init_upnp` 中捕获异常设置标志 | +| `init_pmp` 为同步方法 | 可能阻塞事件循环 | 建议包装成异步或在线程中运行 | + +--- + +## 总结 + +`AcrossNat` 提供了一个简洁、多协议兼容的 NAT 穿透解决方案,适用于需要动态开放端口的应用场景。尽管存在少量逻辑 Bug 和设计瑕疵,但整体结构清晰,易于集成。 + +✅ 推荐用于: +- P2P 网络通信 +- 内网穿透工具 +- DHT 节点部署 +- 自托管服务暴露 + +🔧 建议修复上述 Bug 并增加日志输出以提升稳定性。 + +--- +``` \ No newline at end of file diff --git a/aidocs/across_nat.md b/aidocs/across_nat.md new file mode 100644 index 0000000..aae17fe --- /dev/null +++ b/aidocs/across_nat.md @@ -0,0 +1,338 @@ +# `AcrossNat` 技术文档 + +```markdown +# AcrossNat - 穿越 NAT 的端口映射与公网 IP 获取工具 + +`AcrossNat` 是一个用于在 NAT(网络地址转换)环境中获取公网 IP 地址、自动探测并配置端口映射的 Python 类。它支持多种协议和技术,包括 **NAT-PMP** 和 **UPnP IGD**,并在无法使用这些协议时回退到公共 API 查询公网 IP。 + +--- + +## 概述 + +该类主要用于 P2P 应用、远程访问服务等需要穿透家庭路由器进行端口暴露的场景。通过优先使用本地 NAT 协议(如 UPnP 或 NAT-PMP),避免手动配置端口转发,提升部署自动化能力。 + +--- + +## 依赖库 + +- `natpmp`: 实现 NAT-PMP 协议通信 +- `upnpclient`: 发现和操作 UPnP 兼容的网关设备 +- `requests`: 用于 HTTP 请求(获取公网 IP) +- `.background` (内部模块): 可能用于后台任务处理(未在代码中直接使用) + +> 注意:需确保上述第三方库已安装: +> +> ```bash +> pip install natpmp upnpclient requests +> ``` + +--- + +## 类定义 + +```python +class AcrossNat(object) +``` + +封装了 NAT 穿透相关的功能,包括: + +- 自动探测 NAT-PMP / UPnP 支持状态 +- 获取外部公网 IP +- 动态映射内网端口到外网端口 +- 查询/删除端口映射 + +--- + +## 初始化方法 + +### `__init__(self)` + +初始化 `AcrossNat` 实例,并尝试自动检测 NAT-PMP 和 UPnP 是否可用。 + +#### 属性说明: + +| 属性 | 类型 | 描述 | +|------|------|------| +| `external_ip` | str or None | 缓存的公网 IP 地址 | +| `upnp` | UPnP Service Object or None | UPnP WANConnectionDevice 服务对象 | +| `pmp_supported` | bool | 是否支持 NAT-PMP 协议 | +| `upnp_supported` | bool | 是否支持 UPnP 协议 | + +#### 行为: + +1. 调用 `init_pmp()` 尝试获取公网 IP(测试 NAT-PMP 支持) +2. 调用 `init_upnp()` 探测 UPnP 网关并绑定服务 + +--- + +## 核心方法 + +### `init_upnp(self)` + +尝试发现局域网中的 UPnP 网关设备,并绑定第一个匹配的 WAN 连接服务。 + +#### 异常处理: + +- 若发现失败或无可用设备,则设置 `self.upnp_supported = False` +- 打印异常信息(含 traceback) + +#### 实现细节: + +- 使用 `upnpclient.discover()` 发现设备 +- 匹配服务名称包含 `'WAN'` 和 `'Conn'` 的服务(如 `WANIPConnection1`) + +--- + +### `init_pmp(self)` + +尝试通过 NAT-PMP 协议获取公网 IP 地址以判断是否支持。 + +#### 异常处理: + +- 若设备不支持 NAT-PMP(返回 `NATPMPUnsupportedError`),则设 `self.pmp_supported = False` + +--- + +### `get_external_ip(self) -> str or None` + +获取当前设备的公网 IPv4 地址,按优先级尝试以下方式: + +1. **NAT-PMP**(最快,本地协议) +2. **UPnP IGD GetExternalIPAddress** +3. 备用方案:HTTP 请求公共 IP 服务 + - `https://api.ipify.org` + - `https://ipapi.co/ip/` + +#### 返回值: + +- 成功时返回字符串格式的 IP 地址(如 `"8.8.8.8"`) +- 所有方式均失败时返回 `None` + +#### 示例: + +```python +nat = AcrossNat() +ip = nat.get_external_ip() +print(ip) # 输出: 123.45.67.89 +``` + +--- + +### `upnp_check_external_port(eport, protocol='TCP') -> bool` + +检查指定的外网端口是否已被映射。 + +#### 参数: + +| 参数 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| `eport` | int | —— | 外部端口号 | +| `protocol` | str | `'TCP'` | 协议类型,可选 `'TCP'` 或 `'UDP'` | + +#### 返回值: + +- `True`:端口已被映射 +- `False`:未被映射或调用失败 + +#### 注意: + +此方法仅适用于 UPnP 支持环境。 + +--- + +### `upnp_map_port(inner_port, protocol='TCP', from_port=40003, ip=None, desc='test') -> int or None` + +使用 UPnP 映射一个内网端口到外网端口。 + +#### 参数: + +| 参数 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| `inner_port` | int | —— | 内部主机监听端口 | +| `protocol` | str | `'TCP'` | 协议类型(大小写不敏感) | +| `from_port` | int | `40003` | 起始外网端口 | +| `ip` | str or None | 当前主机 IP | 内部客户端 IP 地址(若为空由网关自动识别) | +| `desc` | str | `'test'` | 端口映射描述 | + +#### 行为逻辑: + +从 `from_port` 开始递增查找空闲外网端口(最大至 52332),直到成功添加映射。 + +#### 返回值: + +- 成功:返回分配的外网端口号(int) +- 失败:返回 `None` + +#### 示例: + +```python +ext_port = nat.upnp_map_port(8080, protocol='tcp', desc='Web Server') +if ext_port: + print(f"Port mapped: {ext_port} -> 8080") +``` + +--- + +### `is_port_mapped(external_port, protocol='TCP') -> bool` + +查询某个外网端口是否已被映射。 + +#### 参数: + +| 参数 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| `external_port` | int | —— | 外部端口号 | +| `protocol` | str | `'TCP'` | 协议类型 | + +#### 返回值: + +- `True`:已映射 +- `False`:未映射 +- 若 UPnP 不支持,抛出异常:`Exception('not implemented')` + +--- + +### `port_unmap(external_port, protocol='TCP')` + +删除指定的端口映射条目。 + +#### 参数: + +| 参数 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| `external_port` | int | —— | 要解绑的外网端口 | +| `protocol` | str | `'TCP'` | 协议类型 | + +#### 行为: + +仅当 UPnP 支持时调用 `delete_port_mapping` 删除映射。 + +> ⚠️ 当前 `upnpclient` 库可能不提供 `delete_port_mapping` 方法,请确认版本兼容性。 + +否则抛出异常:`Exception('not implemented')` + +--- + +### `pmp_map_port(inner_port, protocol='TCP', from_port=40003) -> int or None` + +使用 NAT-PMP 协议映射端口。 + +#### 参数: + +| 参数 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| `inner_port` | int | —— | 内部端口 | +| `protocol` | str | `'TCP'` | 协议类型 | +| `from_port` | int | `40003` | 起始外网端口 | + +#### 实现: + +- TCP:调用 `pmp.map_tcp_port(...)` +- UDP:调用 `pmp.map_udp_port(...)` +- 永久映射(`lifetime=999999999`) + +#### 返回值: + +- 成功:返回公网端口号 +- 失败:抛出异常(由 `natpmp` 抛出) + +--- + +### `map_port(inner_port, protocol='tcp', from_port=40003, lan_ip=None, desc=None) -> int or None` + +统一入口:自动选择最佳方式(NAT-PMP > UPnP)来映射端口。 + +#### 参数: + +| 参数 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| `inner_port` | int | —— | 内部端口 | +| `protocol` | str | `'tcp'` | 协议(不区分大小写) | +| `from_port` | int | `40003` | 起始外网端口 | +| `lan_ip` | str | None | 内部客户端 IP | +| `desc` | str | None | 映射描述 | + +#### 优先级策略: + +1. 若 `pmp_supported == True` → 使用 `pmp_map_port` +2. 否则使用 `upnp_map_port` + +#### 返回值: + +- 成功:返回外网端口号 +- 失败:返回 `None` + +--- + +## 使用示例 + +```python +from acrosnat import AcrossNat + +# 创建实例 +nat = AcrossNat() + +# 获取公网 IP +ip = nat.get_external_ip() +print("Public IP:", ip) + +# 映射本地 8000 端口 +mapped_port = nat.map_port(8000, protocol='tcp', desc='MyApp') +if mapped_port: + print(f"Successfully mapped external port {mapped_port} to 8000") +else: + print("Failed to map port") + +# 查询某端口是否已映射 +if nat.is_port_mapped(mapped_port, 'tcp'): + print("Port still mapped.") +``` + +--- + +## 错误处理与健壮性设计 + +- 所有关键操作都包裹在 `try-except` 中,防止因单个功能失效导致程序崩溃 +- 对不可用的功能自动降级(如关闭 `pmp_supported` 标志) +- 提供多级 fallback: + - NAT-PMP → UPnP → Public API(IP 获取) + - UPnP 端口扫描避免冲突 + +--- + +## 已知限制与注意事项 + +1. **依赖网关支持**: + - 需要路由器开启 UPnP 或 NAT-PMP 功能 + - 部分运营商级 NAT(CGNAT)环境下仍无法映射 + +2. **安全性提示**: + - 自动端口映射存在安全风险,建议配合防火墙使用 + - `NewLeaseDuration=0` 表示永久有效,重启后可能保留 + +3. **upnpclient.delete_port_mapping**: + - 原生 `upnpclient` 可能未实现此方法,需自行扩展或打补丁 + +4. **并发问题**: + - 多线程同时调用 `map_port` 可能造成端口竞争,建议加锁控制 + +--- + +## 未来改进方向 + +- 添加日志系统替代 `print()` +- 支持异步非阻塞操作(结合 `Background` 模块) +- 增加对 IPv6 的支持 +- 实现定期刷新和自动清理机制 +- 提供更详细的错误码反馈 + +--- + +## 许可证 + +请根据项目实际情况填写许可证信息(如 MIT、Apache-2.0 等)。 +``` + +--- + +> ✅ 提示:将此文档保存为 `README.md` 或集成进 Sphinx 文档系统以便团队协作维护。 \ No newline at end of file diff --git a/aidocs/aes.md b/aidocs/aes.md new file mode 100644 index 0000000..d54e4b2 --- /dev/null +++ b/aidocs/aes.md @@ -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,可用于学习、测试及有限范围集成。 +请根据实际安全需求选择是否投入生产环境。 + +--- + +📌 **作者提示:密码学应严谨对待,切勿轻率使用弱算法于敏感数据!** \ No newline at end of file diff --git a/aidocs/app_logger.md b/aidocs/app_logger.md new file mode 100644 index 0000000..910fae4 --- /dev/null +++ b/aidocs/app_logger.md @@ -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日 \ No newline at end of file diff --git a/aidocs/argsConvert.md b/aidocs/argsConvert.md new file mode 100644 index 0000000..e03a5c2 --- /dev/null +++ b/aidocs/argsConvert.md @@ -0,0 +1,364 @@ +# `ArgsConvert` 与 `ConditionConvert` 技术文档 + +> **语言**: Python +> **编码**: UTF-8 +> **模块路径**: `appPublic.argsconvert`(假设) +> **功能描述**: 提供字符串模板变量替换和条件性文本块解析的工具类。 + +--- + +## 概述 + +本模块包含两个核心类: + +1. **`ArgsConvert`**:用于在字符串、列表或字典中查找并替换特定格式的占位符(如 `%{...}%`),支持表达式求值。 +2. **`ConditionConvert`**:用于处理带有开始/结束标记的条件性文本块(如 `$$...$$`),根据命名空间中的值决定是否保留内容。 + +这两个类广泛适用于模板渲染、动态配置生成、条件输出等场景。 + +--- + +## 安装依赖 + +无需外部依赖,仅需标准库及以下内部模块: + +- `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 +$$ ... $$ +``` + +#### 方法 + +##### 1. `convert(obj, namespace)` + +递归处理对象中的条件块。 + +支持:字符串、列表、字典 + +返回转换后的结果。 + +##### 2. `getVarName(vs)` + +同 `ArgsConvert`,去除前后缀得到变量名。 + +特别地,若以 `/` 开头表示是闭合标签。 + +##### 3. `getVarValue(var, namespace)` + +尝试用 `eval(var, namespace)` 求值,否则 `.get(var, None)` + +##### 4. `convertList(parts, namespace)` + +核心方法:处理由正则分割的字符串片段列表,实现条件判断与嵌套控制。 + +使用 `self.buffer1` 作为标签栈记录开启的标签名。 + +**逻辑流程**: +- 遍历每个片段: + - 若不是标签 → 添加到结果 + - 若是开启标签(如 `$$`)→ 压栈,进入子块 + - 若是关闭标签(如 `$$`)→ 出栈校验,求值决定是否保留子块内容 +- 若最终栈非空 → 抛出 `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 $$this is $$ba = 100 $$condition out$$ 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 +$$and ratingtype=${rtype}$$$ +$$and bond_id = ${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` 安全问题 | 直接执行任意表达式,需谨慎使用 | +| 标签必须严格匹配 | `` 会报错 | +| 不支持 else 分支 | 仅支持 if-like 结构 | +| 性能 | 每次 split + findall,大文本效率较低 | + +--- + +## 设计建议(改进方向) + +| 改进建议 | 说明 | +|--------|------| +| 引入沙箱机制 | 限制 `eval` 可访问的内置函数 | +| 支持更多语法 | 如 `$$...$$` | +| 使用 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 + ')' +``` + +匹配: +- `$$` +- `$$` + +标签名支持中文、字母、数字、下划线及常见符号。 + +--- + +> 📌 **作者**: Unknown +> 📅 **最后更新**: 2025年4月5日 +> 📚 **适用版本**: Python 3.x \ No newline at end of file diff --git a/aidocs/asynciorun.md b/aidocs/asynciorun.md new file mode 100644 index 0000000..78b1004 --- /dev/null +++ b/aidocs/asynciorun.md @@ -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()` 函数是一个轻量级的异步应用启动器,集成了配置加载与数据库连接池初始化功能,适用于微服务、脚本工具或后台任务等异步应用场景。通过简单的封装,降低了异步程序的启动复杂度。 \ No newline at end of file diff --git a/aidocs/audioplayer.md b/aidocs/audioplayer.md new file mode 100644 index 0000000..05b7da8 --- /dev/null +++ b/aidocs/audioplayer.md @@ -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日 \ No newline at end of file diff --git a/aidocs/background.md b/aidocs/background.md new file mode 100644 index 0000000..58ed3af --- /dev/null +++ b/aidocs/background.md @@ -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+ + +--- + +## 许可证 + +此代码为公共领域示例代码,可用于学习与商业用途(请根据实际项目要求添加许可证)。 \ No newline at end of file diff --git a/aidocs/base64_to_file.md b/aidocs/base64_to_file.md new file mode 100644 index 0000000..361eb1f --- /dev/null +++ b/aidocs/base64_to_file.md @@ -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:;base64,` 的 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`: 形如 `.` 的文件名字符串。 + - `` 来自 `getID()` 生成的唯一标识; + - `` 由 MIME 类型决定,若无法解析则尝试从类型中提取子类型。 + +#### 示例 + +```python +url = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUg..." +fname = getFilenameFromBase64(url) +# 输出: abc123def.png + +invalid = "invalid_base64_string" +fname = getFilenameFromBase64(invalid) +# 输出: xyz789abc (无扩展名) +``` + +#### 实现逻辑 + +1. 使用正则表达式匹配 `data:;base64,` 格式; +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`) \ No newline at end of file diff --git a/aidocs/country_cn_en.md b/aidocs/country_cn_en.md new file mode 100644 index 0000000..1f663f2 --- /dev/null +++ b/aidocs/country_cn_en.md @@ -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日 \ No newline at end of file diff --git a/aidocs/csv_Data.md b/aidocs/csv_Data.md new file mode 100644 index 0000000..dfcb694 --- /dev/null +++ b/aidocs/csv_Data.md @@ -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 开发中的反模式,但通过简单重构即可提升其稳定性与兼容性。 +``` \ No newline at end of file diff --git a/aidocs/dataencoder.md b/aidocs/dataencoder.md new file mode 100644 index 0000000..65aa3a7 --- /dev/null +++ b/aidocs/dataencoder.md @@ -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 +``` \ No newline at end of file diff --git a/aidocs/datamapping.md b/aidocs/datamapping.md new file mode 100644 index 0000000..63ad755 --- /dev/null +++ b/aidocs/datamapping.md @@ -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` 支持动态属性访问,提升代码可读性和便利性。 + +--- + +## 版本信息 + +- 创建时间:未知 +- 最后更新:根据代码逻辑整理 +- 维护者:开发者团队 + +--- + +📌 **提示**:在实际项目中建议添加类型注解和异常处理以增强稳定性。 \ No newline at end of file diff --git a/aidocs/dictExt.md b/aidocs/dictExt.md new file mode 100644 index 0000000..988c89b --- /dev/null +++ b/aidocs/dictExt.md @@ -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` 的逻辑错误。 \ No newline at end of file diff --git a/aidocs/dictObject.md b/aidocs/dictObject.md new file mode 100644 index 0000000..6a672c8 --- /dev/null +++ b/aidocs/dictObject.md @@ -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'])) # +``` + +--- + +#### `update(*args, **kwargs)` +批量更新字典内容,所有新增值都会经过 `_wrap()` 处理。 + +##### 示例 +```python +obj.update({'list': [1, {'deep': 2}]}) +print(type(obj.list[1])) # DictObject +``` + +--- + +#### `to_dict()` +将整个 `DictObject` 结构递归转换为原始的 Python 原生字典(去除 `DictObject` 类型)。 + +##### 返回 +`dict`: 完全由 `dict`, `list`, 基本类型组成的结构,适合 JSON 序列化。 + +##### 示例 +```python +obj = DictObject(a=DictObject(b=2)) +plain = obj.to_dict() +print(plain) # {'a': {'b': 2}} +``` + +--- + +#### `copy()` +创建一个深拷贝副本,包括所有嵌套结构都重新包装。 + +> ⚠️ 并非完全等同于 `copy.deepcopy()`,但对常见结构足够安全。 + +##### 返回 +新的 `DictObject` 实例。 + +##### 示例 +```python +new_obj = obj.copy() +new_obj.a.b = 999 +# 不影响原始 obj +``` + +--- + +#### `@staticmethod _wrap(value)` +递归包装输入值中的 `dict` 和 `list`。 + +##### 规则 +- `dict` → `DictObject(dict)` +- `list` → `[ _wrap(item) for item in list ]` +- 其他类型保持不变 + +##### 示例 +```python +wrapped = DictObject._wrap({'x': {'y': 1}}) +print(type(wrapped)) # DictObject +print(type(wrapped['x'])) # DictObject +``` + +--- + +#### `@staticmethod _dict(value)` +与 `_wrap` 相反,将 `DictObject` 或其嵌套结构还原为原生 `dict` / `list`。 + +##### 规则 +- `DictObject` → `.to_dict()` +- `list` → 递归处理每个元素 +- 其他类型原样返回 + +##### 示例 +```python +data = DictObject._dict(obj) +# data 是纯 dict/list 结构 +``` + +--- + +## 已注释掉的工厂函数 `dictObjectFactory`(建议扩展用) + +该函数原本设计用于根据类名动态创建特定子类实例。 + +```python +# 示例原型(当前被注释): +# obj = dictObjectFactory("User", name="Tom", age=30) +``` + +### 功能逻辑(潜在用途) +1. 查找 `DictObject` 的子类中哪个满足 `isMe(_klassName__) == True` +2. 若找到,则实例化该子类;否则回退到 `DictObject` + +> ✅ 可作为插件式对象注册机制的基础,适合 ORM 或配置系统。 + +### 当前状态 +- 被注释,暂未启用。 +- 使用时需确保各子类实现了 `isMe(class_name)` 类方法。 + +--- + +## 使用示例 + +### 示例 1:基础用法 +```python +user = DictObject( + name="Alice", + profile={"age": 28, "city": "Shanghai"}, + hobbies=["reading", "hiking"] +) + +print(user.name) # Alice +print(user.profile.city) # Shanghai +user.email = "alice@example.com" +print(user.email) # alice@example.com +``` + +### 示例 2:JSON 序列化 +```python +class Person(DictObject): + def _addon(self): + return self.to_dict() # 或添加额外元信息 + +p = Person(name="Bob", info={"job": "Engineer"}) +json_str = json.dumps(p, cls=DictObjectEncoder, indent=2) +print(json_str) +# { +# "name": "Bob", +# "info": { +# "job": "Engineer" +# } +# } +``` + +### 示例 3:多值字典处理 +```python +form_data = {'tag': 'python', 'tag': 'web', 'author': 'admin'} +# 实际可能是 MultiDict({'tag': ['python', 'web'], 'author': ['admin']}) + +flat = multiDict2Dict(form_data) +print(flat['tag']) # ['python', 'web'] (如果是真实多值) +``` + +--- + +## 注意事项与限制 + +| 项目 | 说明 | +|------|------| +| ❌ `__getattr__` 返回 `None` | 导致无法区分“无此属性”和“属性值为 None” | +| ⚠️ `_addon()` 必须实现 | 否则 `DictObjectEncoder` 会失败 | +| 🔁 `_wrap` 是递归的 | 大型嵌套结构可能影响性能 | +| 🧩 子类工厂未启用 | 如需动态构造,请解注并完善 `isMe()` 逻辑 | +| 📦 不支持 tuple/set 自动包装 | 仅处理 `dict` 和 `list` | + +--- + +## 总结 + +`DictObject` 提供了一种简洁优雅的方式来处理嵌套数据结构,特别适用于: + +- 配置文件解析(YAML/JSON) +- API 响应数据建模 +- Web 请求参数处理 +- 构建轻量级数据传输对象(DTO) + +结合 `multiDict2Dict` 和 `DictObjectEncoder`,可在前后端交互、日志记录、序列化等场景中大幅提升开发效率。 + +--- + +📌 **建议改进方向**: +- 添加类型提示(Type Hints) +- 引入 `__dir__()` 支持 IDE 自动补全 +- 实现更健壮的工厂模式或注册中心 +- 增加单元测试覆盖率 + +--- + +📄 *文档版本:v1.0* +📅 *最后更新:2025-04-05* \ No newline at end of file diff --git a/aidocs/dictObject.old.md b/aidocs/dictObject.old.md new file mode 100644 index 0000000..b878090 --- /dev/null +++ b/aidocs/dictObject.old.md @@ -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)) # +``` + +--- + +## 注意事项 + +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 +``` \ No newline at end of file diff --git a/aidocs/easyExcel.md b/aidocs/easyExcel.md new file mode 100644 index 0000000..d5b1263 --- /dev/null +++ b/aidocs/easyExcel.md @@ -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 相关许可协议。 +``` \ No newline at end of file diff --git a/aidocs/eventproperty.md b/aidocs/eventproperty.md new file mode 100644 index 0000000..a67e5a7 --- /dev/null +++ b/aidocs/eventproperty.md @@ -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: +# Observer 2 received: + +si.state = 20 +# 输出: +# Observer 1 received: ... +# Observer 2 received: ... + +si.age = 30 +# 输出: +# Observer 3 received: +``` + +每次修改 `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` 以支持实例级状态存储,确保线程安全和多实例兼容性。 \ No newline at end of file diff --git a/aidocs/exceldata.md b/aidocs/exceldata.md new file mode 100644 index 0000000..ea4a12d --- /dev/null +++ b/aidocs/exceldata.md @@ -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日 \ No newline at end of file diff --git a/aidocs/excelwriter.md b/aidocs/excelwriter.md new file mode 100644 index 0000000..792e589 --- /dev/null +++ b/aidocs/excelwriter.md @@ -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()` 以获得更好的功能和维护支持。 \ No newline at end of file diff --git a/aidocs/find_player.md b/aidocs/find_player.md new file mode 100644 index 0000000..939e792 --- /dev/null +++ b/aidocs/find_player.md @@ -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 作者保留所有权利。 +仅供内部学习与开发使用,遵循项目整体开源协议(如有)。 + +--- + +📌 **提示**:部署前请测试网络环境是否允许广播通信。 \ No newline at end of file diff --git a/aidocs/folderUtils.md b/aidocs/folderUtils.md new file mode 100644 index 0000000..64cc8e9 --- /dev/null +++ b/aidocs/folderUtils.md @@ -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` 扩展功能 | + +--- + +> ✅ 本模块适合作为基础组件集成至各类文件管理系统、备份工具、资源上传服务中。 \ No newline at end of file diff --git a/aidocs/genetic.md b/aidocs/genetic.md new file mode 100644 index 0000000..5aee1aa --- /dev/null +++ b/aidocs/genetic.md @@ -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__` 魔法方法和父子引用机制,实现了灵活的运行时属性共享。适合作为基础组件集成进需要层次化数据管理的系统中。 + +> 💡 “不是所有对象都需要基因,但一旦拥有,传承便有了意义。” \ No newline at end of file diff --git a/aidocs/hf.md b/aidocs/hf.md new file mode 100644 index 0000000..ea78e1c --- /dev/null +++ b/aidocs/hf.md @@ -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`),便于项目复用。 \ No newline at end of file diff --git a/aidocs/http_client.md b/aidocs/http_client.md new file mode 100644 index 0000000..fba5c4f --- /dev/null +++ b/aidocs/http_client.md @@ -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 调用,具备良好的扩展性和清晰的异常体系。 \ No newline at end of file diff --git a/aidocs/httpclient.md b/aidocs/httpclient.md new file mode 100644 index 0000000..c44a83f --- /dev/null +++ b/aidocs/httpclient.md @@ -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+ + +> 建议增加版本号字段和单元测试覆盖。 + +--- + +✅ **文档完成** \ No newline at end of file diff --git a/aidocs/i18n.md b/aidocs/i18n.md new file mode 100644 index 0000000..3cf8bab --- /dev/null +++ b/aidocs/i18n.md @@ -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` 文件并构建翻译字典。 + +**结构要求:** +``` +/ +└── 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(基于所提供代码分析) \ No newline at end of file diff --git a/aidocs/ipgetter.md b/aidocs/ipgetter.md new file mode 100644 index 0000000..77c18e0 --- /dev/null +++ b/aidocs/ipgetter.md @@ -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日 \ No newline at end of file diff --git a/aidocs/iplocation.md b/aidocs/iplocation.md new file mode 100644 index 0000000..18c486a --- /dev/null +++ b/aidocs/iplocation.md @@ -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日 \ No newline at end of file diff --git a/aidocs/jsonConfig.md b/aidocs/jsonConfig.md new file mode 100644 index 0000000..94ebc6b --- /dev/null +++ b/aidocs/jsonConfig.md @@ -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. 加载 `/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 文档。 \ No newline at end of file diff --git a/aidocs/jsonIO.md b/aidocs/jsonIO.md new file mode 100644 index 0000000..9712f6a --- /dev/null +++ b/aidocs/jsonIO.md @@ -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)。 \ No newline at end of file diff --git a/aidocs/localefunc.md b/aidocs/localefunc.md new file mode 100644 index 0000000..c03e790 --- /dev/null +++ b/aidocs/localefunc.md @@ -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 在国际化支持方面的局限性,也展示了社区常见的“打补丁”式应对策略。 \ No newline at end of file diff --git a/aidocs/log.md b/aidocs/log.md new file mode 100644 index 0000000..ca3e3f2 --- /dev/null +++ b/aidocs/log.md @@ -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` 模块或增强此模块的功能与健壮性。 +``` \ No newline at end of file diff --git a/aidocs/macAddress.md b/aidocs/macAddress.md new file mode 100644 index 0000000..00d202b --- /dev/null +++ b/aidocs/macAddress.md @@ -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+ +📦 **推荐用途**:自动化运维、网络配置检测、主机信息采集 \ No newline at end of file diff --git a/aidocs/myImport.md b/aidocs/myImport.md new file mode 100644 index 0000000..fe3239a --- /dev/null +++ b/aidocs/myImport.md @@ -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` 实现更安全、清晰的模块导入逻辑。 \ No newline at end of file diff --git a/aidocs/myTE.md b/aidocs/myTE.md new file mode 100644 index 0000000..b5ce329 --- /dev/null +++ b/aidocs/myTE.md @@ -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 +``` + +#### 示例: + +```bash +python template_engine.py hello.tmpl.json data.json +``` + +程序会: +1. 读取模板文件 +2. 加载 JSON 数据 +3. 渲染并输出结果到标准输出 + +> 若参数不足,打印用法提示并退出。 + +--- + +## 模板语法示例(Jinja2) + +```jinja2 + + +{{ title }} + +

Welcome, {{ user.name }}!

+

You have {{ len(messages) }} messages.

+ + {% if not isNone(subtitle) %} +

{{ subtitle }}

+ {% endif %} + +

File: {{ basenameWithoutExt(global().input_file) }} (Ext: {{ extname(global().input_file) }})

+ + +``` + +--- + +## 编码说明 + +- 所有文件读写均使用 `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(除非另有声明)。 \ No newline at end of file diff --git a/aidocs/myjson.md b/aidocs/myjson.md new file mode 100644 index 0000000..0490265 --- /dev/null +++ b/aidocs/myjson.md @@ -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 文件)。 \ No newline at end of file diff --git a/aidocs/mylog.md b/aidocs/mylog.md new file mode 100644 index 0000000..6dfce7d --- /dev/null +++ b/aidocs/mylog.md @@ -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 ` + +> 示例输出: +> ``` +> 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` 模块,或在此基础上完善异常处理与资源管理机制。 \ No newline at end of file diff --git a/aidocs/oauth_client.md b/aidocs/oauth_client.md new file mode 100644 index 0000000..c34f010 --- /dev/null +++ b/aidocs/oauth_client.md @@ -0,0 +1,395 @@ +以下是为提供的 `OAuthClient` 类编写的 **Markdown 格式技术文档**,涵盖了类的功能、设计思想、使用方式、参数说明及示例等内容。 + +--- + +# `OAuthClient` 技术文档 + +`OAuthClient` 是一个基于 HTTP(S) 的异步客户端工具类,用于调用由第三方服务发布的 RESTful API 接口。它通过预定义的接口描述(`desc`)动态构建请求,并解析响应数据,支持自定义数据转换、错误判断逻辑和返回结构处理。 + +该类广泛应用于需要与 OAuth 认证或标准 JSON API 交互的服务中,具有良好的可配置性和扩展性。 + +--- + +## 📦 模块依赖 + +```python +import json +from appPublic.httpclient import HttpClient, RESPONSE_TEXT, RESPONSE_JSON, RESPONSE_BIN, RESPONSE_FILE, RESPONSE_STREAM, HttpError +from appPublic.argsConvert import ArgsConvert +from appPublic.dictObject import DictObject +``` + +- `HttpClient`: 异步 HTTP 客户端,支持多种响应类型。 +- `ArgsConvert`: 用于模板字符串替换(如 `${key}`)。 +- `DictObject`: 支持通过属性访问字典键值的对象封装。 + +--- + +## 🧱 类定义 + +```python +class OAuthClient: + def __init__(self, desc, converters={}) +``` + +### 构造函数参数 + +| 参数 | 类型 | 说明 | +|------|------|------| +| `desc` | `dict` | 接口描述对象,必须包含 `data` 字段,定义了基础数据和各 API 方法的配置。详见下方“接口描述格式”章节。 | +| `converters` | `dict[str, callable]` | 可选的数据转换函数映射表,键为转换器名称,值为函数引用。 | + +> ⚠️ 注意:`desc['data']` 必须存在,否则会抛出断言错误。 + +--- + +## 🔧 接口描述格式 (`desc`) + +`desc` 是一个字典,其结构如下: + +```json +{ + "data": { /* 基础共享数据,可在请求时被引用 */ }, + "method_name": { + "path": "/api/v1/users/${userId}", + "method": "GET", // 可选,默认 GET + "headers": [ ... ], + "params": [ ... ], + "data": [ ... ], // 请求体中的数据 + "resp": [ + { + "name": "result", + "resp_keys": ["user", "name"], + "converter": "to_upper" // 可选 + } + ], + "error_if": { + "error_keys": ["status"], + "op": "==", // 比较操作符:'==' 或 '!=' + "value": "fail", + "code_keys": ["code"], // 错误码路径 + "msg_keys": ["message"] // 错误信息路径 + } + } +} +``` + +### 字段说明 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `data` | `dict` | 全局上下文数据,可用于模板替换。 | +| `method_name` | `dict` | 每个 API 接口的配置项,方法名作为 key。 | +|   `path` | `str` | 接口路径,支持 `${}` 占位符变量。 | +|   `method` | `str` | HTTP 方法(GET/POST/PUT/DELETE 等),默认为 `GET`。 | +|   `headers` | `list[dict]` | 请求头列表,每个元素形如 `{name: "Header-Key", value: "HeaderValue", converter?: "funcName"}` | +|   `params` | `list[dict]` | URL 查询参数,格式同上。 | +|   `data` | `list[dict]` | 请求体数据(JSON 格式),格式同上。 | +|   `resp` | `list[dict]` | 响应数据提取规则。 | +|     `name` | `str` | 返回结果中字段的名称。 | +|     `resp_keys` | `list[str]` | 在响应 JSON 中获取数据的路径(嵌套键)。 | +|     `converter` | `str` | 转换函数名(需在 `converters` 中注册)。 | +|   `error_if` | `dict` | 自定义错误判断条件。 | +|     `error_keys` | `list[str]` | 提取用于比较的响应字段路径。 | +|     `op` | `str` | 比较操作符:`==` 或 `!=`。 | +|     `value` | any | 期望值,用于对比。 | +|     `code_keys` | `list[str]` | 错误码字段路径(可选)。 | +|     `msg_keys` | `list[str]` | 错误消息字段路径(可选)。 | + +--- + +## 🧩 核心方法 + +### `__call__(self, host, mapi, params) -> dict` + +发起一次 API 调用。 + +#### 参数 + +| 参数 | 类型 | 说明 | +|------|------|------| +| `host` | `str` | 服务主机地址,例如 `"https://api.example.com"` | +| `mapi` | `str` | 接口方法名,对应 `desc` 中的键名。 | +| `params` | `dict` | 动态传入的参数,用于替换模板和构造请求。 | + +#### 返回值 + +成功时: +```json +{ + "status": "ok", + "data": { /* 根据 resp 配置提取的数据 */ } +} +``` + +失败时: +```json +{ + "status": "error", + "code": "400", + "message": "Bad Request" +} +``` + +#### 流程说明 + +1. 查找 `desc[mapi]` 是否存在; +2. 使用 `datalize()` 替换路径中的 `${}` 变量; +3. 构造完整 URL; +4. 处理 headers、params、data(应用转换器); +5. 发起异步 HTTP 请求; +6. 解析响应为 `DictObject`; +7. 检查是否符合 `error_if` 条件; +8. 按照 `resp` 规则提取并转换返回数据。 + +> 📌 日志输出:请求详情(URL、method、params 等)将打印到控制台。 + +--- + +### `setup_req_data(data, ns) -> dict or None` + +将一组 `{name, value, converter}` 结构的数据根据命名空间 `ns` 进行模板替换和转换。 + +#### 参数 + +- `data`: 数据项列表(如 headers、params) +- `ns`: 命名空间(通常是 `params`) + +#### 内部调用 + +- `setup_req_kv()`: 处理单个键值对,执行模板替换和转换。 + +--- + +### `datalize(dic, data={}) -> dict` + +执行模板替换(`${key}` → 实际值)。 + +#### 说明 + +- 合并 `self.data`(全局数据)与传入的 `data`; +- 使用 `ArgsConvert('${', '}')` 执行替换; +- 支持嵌套字典和字符串。 + +#### 示例 + +```python +self.data = {'token': 'abc123'} +dic = {'url': '/api?tk=${token}'} +result = self.datalize(dic) # {'url': '/api?tk=abc123'} +``` + +--- + +### `get_resp_data(resp, keys, converter=None)` + +从响应对象中按 `keys` 路径提取数据,并可选地进行转换。 + +#### 参数 + +- `resp`: `DictObject` 类型的响应体; +- `keys`: 键路径列表,如 `['data', 'user', 'id']`; +- `converter`: 转换函数名。 + +#### 示例 + +```python +resp = DictObject(data={"user": {"name": "alice"}}) +value = get_resp_data(resp, ['data', 'user', 'name']) # "alice" +``` + +--- + +### `setup_return_data(resp) -> dict` + +根据 `api.resp` 配置,构建最终返回的 `data` 对象。 + +#### 示例配置 + +```json +"resp": [ + { + "name": "username", + "resp_keys": ["data", "user", "name"], + "converter": "to_upper" + } +] +``` + +#### 输出 + +```json +{ + "status": "ok", + "data": { + "username": "ALICE" + } +} +``` + +--- + +### `check_if_error(resp) -> dict or None` + +检查响应是否满足预设的错误条件。 + +#### 判断逻辑 + +```python +v = resp.get_data_by_keys(ei.error_keys) +if ei.op == '==' and v == ei.value → 触发错误 +if ei.op == '!=' and v != ei.value → 触发错误 +``` + +#### 成功返回 + +- `None` 表示无错误。 + +#### 失败返回 + +```json +{ + "status": "error", + "code": "...", + "message": "..." +} +``` + +> 💡 支持从响应中提取 `code` 和 `message`。 + +--- + +### `set_data(resp_data, data_desc)` + +将响应中的某些字段保存回 `self.data`,用于后续请求共享状态(如 token 刷新)。 + +#### 参数 + +- `resp_data`: 原始响应数据(dict) +- `data_desc`: 描述如何映射字段 + +```python +data_desc = [ + {'field': 'access_token', 'name': 'token'}, + {'field': 'expires_in', 'name': 'expires'} +] +``` + +执行后: +```python +self.data['token'] = resp_data['access_token'] +self.data['expires'] = resp_data['expires_in'] +``` + +--- + +## ✅ 使用示例 + +### 1. 定义接口描述 + +```python +desc = { + "data": { + "client_id": "your_client_id", + "client_secret": "your_secret" + }, + "get_user": { + "path": "/users/${user_id}", + "method": "GET", + "headers": [ + {"name": "Authorization", "value": "Bearer ${token}"} + ], + "resp": [ + { + "name": "userInfo", + "resp_keys": ["data"], + "converter": "strip_data" + } + ], + "error_if": { + "error_keys": ["status"], + "op": "==", + "value": "error", + "code_keys": ["error_code"], + "msg_keys": ["error_msg"] + } + } +} +``` + +### 2. 定义转换器 + +```python +def to_upper(s): + return s.upper() if isinstance(s, str) else s + +converters = { + "to_upper": to_upper, + "strip_data": lambda x: x.get('data') if isinstance(x, dict) else x +} +``` + +### 3. 创建并调用客户端 + +```python +client = OAuthClient(desc, converters=converters) + +result = await client( + host="https://api.example.com", + mapi="get_user", + params={ + "user_id": "123", + "token": "xyz987" + } +) + +print(result) +# 输出: +# { +# "status": "ok", +# "data": { +# "userInfo": { ... } +# } +# } +``` + +--- + +## 🛠️ 调试信息 + +所有请求细节将通过 `print()` 输出: + +```python +print(f'{url=}, {method=}, {myparams=}, {mydata=}, {myheaders=}') +``` + +建议在生产环境中移除或替换为日志记录。 + +--- + +## ❗ 异常处理 + +- 若指定的 `mapi` 未在 `desc` 中定义,抛出 `Exception`; +- HTTP 请求异常捕获为 `HttpError`,返回标准错误结构; +- 网络级错误(如连接超时)也会被捕获并返回 `"https error"`。 + +--- + +## 🧩 扩展建议 + +- 添加日志模块替代 `print()`; +- 支持更多响应类型(如文件下载); +- 增加中间件钩子(before_request / after_response); +- 支持分页自动迭代。 + +--- + +## 📚 总结 + +`OAuthClient` 是一个灵活、声明式的 API 客户端封装,适用于微服务间通信、第三方接口集成等场景。通过配置驱动的方式,实现了解耦与复用,适合构建统一的外部服务网关层。 + +--- + +> 📎 版本:1.0 +> 📅 最后更新:2025年4月5日 +> © 2025 Your Company. All rights reserved. \ No newline at end of file diff --git a/aidocs/objectAction.md b/aidocs/objectAction.md new file mode 100644 index 0000000..4e95e37 --- /dev/null +++ b/aidocs/objectAction.md @@ -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` | 存储所有注册的行为函数,结构为:
`{ 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)` 清理机制 +- 支持优先级排序(如按权重执行) +- 添加日志或调试开关 +- 引入线程锁以支持并发环境 + +--- + +## 版权与许可 + +> 本代码由项目团队开发,遵循项目内部开源协议。引用请注明出处。 \ No newline at end of file diff --git a/aidocs/outip.md b/aidocs/outip.md new file mode 100644 index 0000000..a2511cf --- /dev/null +++ b/aidocs/outip.md @@ -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` 后可直接导入使用。 \ No newline at end of file diff --git a/aidocs/pickleUtils.md b/aidocs/pickleUtils.md new file mode 100644 index 0000000..71f4ca5 --- /dev/null +++ b/aidocs/pickleUtils.md @@ -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] +``` + +--- + +✅ **适用场景**:快速原型开发、本地配置/状态保存、小型项目中的简单持久化需求。 +❌ **不推荐用于**:跨平台数据交换、网络传输、高安全性要求环境。 \ No newline at end of file diff --git a/aidocs/port_forward.md b/aidocs/port_forward.md new file mode 100644 index 0000000..e553754 --- /dev/null +++ b/aidocs/port_forward.md @@ -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 <本地端口> <目标主机> <目标端口> +``` + +#### 示例 + +```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 \ No newline at end of file diff --git a/aidocs/process_workers.md b/aidocs/process_workers.md new file mode 100644 index 0000000..c328e87 --- /dev/null +++ b/aidocs/process_workers.md @@ -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 密集型任务(应使用线程) | +| 需要限制系统负载的任务 | 极低延迟要求的实时系统 | +| 批量任务异步处理 | 大规模微任务处理(建议用线程池或协程) | + +> 推荐作为学习多进程控制机制的基础模板,在生产环境中结合更成熟的并发框架进行优化。 \ No newline at end of file diff --git a/aidocs/proxy.md b/aidocs/proxy.md new file mode 100644 index 0000000..d7ac717 --- /dev/null +++ b/aidocs/proxy.md @@ -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。 \ No newline at end of file diff --git a/aidocs/psm.md b/aidocs/psm.md new file mode 100644 index 0000000..42299a8 --- /dev/null +++ b/aidocs/psm.md @@ -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日* \ No newline at end of file diff --git a/aidocs/rc4.md b/aidocs/rc4.md new file mode 100644 index 0000000..7ba46fc --- /dev/null +++ b/aidocs/rc4.md @@ -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 \ No newline at end of file diff --git a/aidocs/receiveMail.md b/aidocs/receiveMail.md new file mode 100644 index 0000000..fbeebb6 --- /dev/null +++ b/aidocs/receiveMail.md @@ -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 \ No newline at end of file diff --git a/aidocs/registerfunction.md b/aidocs/registerfunction.md new file mode 100644 index 0000000..2bba53e --- /dev/null +++ b/aidocs/registerfunction.md @@ -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` 与单例模式,实现了简洁而强大的函数注册与异步执行能力,具备良好的扩展性和复用性。 \ No newline at end of file diff --git a/aidocs/restrictedEnv.md b/aidocs/restrictedEnv.md new file mode 100644 index 0000000..ce2d1f2 --- /dev/null +++ b/aidocs/restrictedEnv.md @@ -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 输入解析、不受信用户输入处理 \ No newline at end of file diff --git a/aidocs/rsaPeer.md b/aidocs/rsaPeer.md new file mode 100644 index 0000000..9815985 --- /dev/null +++ b/aidocs/rsaPeer.md @@ -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 文件 + +--- + +> 📌 文档维护建议:配合单元测试与接口文档进一步完善健壮性描述。 \ No newline at end of file diff --git a/aidocs/rsa_key_rw.md b/aidocs/rsa_key_rw.md new file mode 100644 index 0000000..2ddca4b --- /dev/null +++ b/aidocs/rsa_key_rw.md @@ -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 文档、安全实践指南 \ No newline at end of file diff --git a/aidocs/rsawrap.md b/aidocs/rsawrap.md new file mode 100644 index 0000000..dacc8ac --- /dev/null +++ b/aidocs/rsawrap.md @@ -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 编码接口以及更安全的签名算法扩展。 \ No newline at end of file diff --git a/aidocs/set_fgcolor.md b/aidocs/set_fgcolor.md new file mode 100644 index 0000000..f6dbecd --- /dev/null +++ b/aidocs/set_fgcolor.md @@ -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 文档系统中。 \ No newline at end of file diff --git a/aidocs/sockPackage.md b/aidocs/sockPackage.md new file mode 100644 index 0000000..20805a9 --- /dev/null +++ b/aidocs/sockPackage.md @@ -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日 \ No newline at end of file diff --git a/aidocs/sshx.md b/aidocs/sshx.md new file mode 100644 index 0000000..f110828 --- /dev/null +++ b/aidocs/sshx.md @@ -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 `:本地 → 远程复制(SCP) +- `r2l `:远程 → 本地复制(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日* \ No newline at end of file diff --git a/aidocs/strUtils.md b/aidocs/strUtils.md new file mode 100644 index 0000000..626b42c --- /dev/null +++ b/aidocs/strUtils.md @@ -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` 系列方法。 \ No newline at end of file diff --git a/aidocs/streamhttpclient.md b/aidocs/streamhttpclient.md new file mode 100644 index 0000000..41ace62 --- /dev/null +++ b/aidocs/streamhttpclient.md @@ -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'...' +``` + +--- + +## 📁 文件结构影响 + +### 创建的本地文件 +- `~/.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月 + +> 请结合实际项目规范进行调整。 +``` \ No newline at end of file diff --git a/aidocs/t.md b/aidocs/t.md new file mode 100644 index 0000000..90d1d71 --- /dev/null +++ b/aidocs/t.md @@ -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` 的语义,并确保其行为符合预期的安全策略。 \ No newline at end of file diff --git a/aidocs/testdict.md b/aidocs/testdict.md new file mode 100644 index 0000000..11fb14d --- /dev/null +++ b/aidocs/testdict.md @@ -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` 格式以提高安全性和可移植性。 \ No newline at end of file diff --git a/aidocs/textsplit.md b/aidocs/textsplit.md new file mode 100644 index 0000000..50bd455 --- /dev/null +++ b/aidocs/textsplit.md @@ -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('.', ''), text) +``` +- **目的**:防止在 `Dr.` 或 `U.S.A.` 等缩写处分割。 +- 使用 `` 临时替换缩写中的句点,避免被当作句子结束。 +- 支持的缩写包括: + - `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('', '.') for s in sentences if s.strip()] +``` +- 将之前替换成 `` 的句点恢复为正常句点。 +- 过滤掉空字符串或仅空白的内容。 + +### 示例调用 +```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日 \ No newline at end of file diff --git a/aidocs/thread_workers.md b/aidocs/thread_workers.md new file mode 100644 index 0000000..54b1129 --- /dev/null +++ b/aidocs/thread_workers.md @@ -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` 是一个简洁高效的并发任务控制器,适用于需要简单控制并发数的异步任务场景。其设计清晰、易于使用,适合作为基础组件集成进各类后台处理系统中。 +``` \ No newline at end of file diff --git a/aidocs/timeUtils.md b/aidocs/timeUtils.md new file mode 100644 index 0000000..cc3b25d --- /dev/null +++ b/aidocs/timeUtils.md @@ -0,0 +1,521 @@ +# 日期与时间处理工具库技术文档 + +本模块提供了一系列用于日期、时间处理和格式转换的实用函数,支持日期计算、字符串与时间对象互转、模式匹配等功能,适用于数据分析、任务调度、日志处理等场景。 + +--- + +## 目录 + +- [1. 导入依赖](#1-导入依赖) +- [2. 全局变量](#2-全局变量) +- [3. 核心功能函数](#3-核心功能函数) + - [3.1 时间差计算](#31-days_betweendate_str1-date_str2) + - [3.2 当前时间相关](#32-monthfirstday--curdatetime--curdatestring--curtimestring--timestampstr) + - [3.3 判断类函数](#33-ismonthlastday--isleapyear--is_monthend) + - [3.4 时间戳操作](#34-timestamp--timestampsecond--timestampadd--timestampsub--timestamp2dt) + - [3.5 日期加减运算](#35-addseconds--addmonths--addyears--dateadd--strdate_add) + - [3.6 日期格式化与解析](#36-date2str--time2str--str2date--str2datetime--str2date) + - [3.7 特殊日期计算](#37-firstsunday) + - [3.8 时间对齐(StepedTimestamp)](#38-steppedtimestamp) + - [3.9 周/季度辅助函数](#39-date_weekinyear--date_season) + - [3.10 模式匹配](#310-is_match_pattern) + +--- + +## 1. 导入依赖 + +```python +import os, sys +import time +from datetime import date, timedelta, datetime +``` + +> **说明**: +> - `os`, `sys`:保留接口扩展性。 +> - `time`:用于时间戳和结构化时间处理。 +> - `datetime`:核心日期时间处理模块。 + +--- + +## 2. 全局变量 + +### `leapMonthDays`, `unleapMonthDays` +```python +leapMonthDays = [0,31,29,31,30,31,30,31,31,30,31,30,31] # 闰年各月天数(索引从1开始) +unleapMonthDays = [0,31,28,31,30,31,30,31,31,30,31,30,31] # 平年各月天数 +``` + +> **用途**:快速查询某月最大天数,结合 `isLeapYear()` 使用。 + +--- + +## 3. 核心功能函数 + +### 3.1 `days_between(date_str1, date_str2)` + +计算两个日期字符串之间的天数差(绝对值)。 + +#### 参数 +| 参数 | 类型 | 描述 | +|-------------|--------|--------------------------| +| `date_str1` | str | 起始日期,格式 `'YYYY-MM-DD'` | +| `date_str2` | str | 结束日期,格式 `'YYYY-MM-DD'` | + +#### 返回值 +- `int`:相差的天数(非负整数) + +#### 示例 +```python +days_between("2023-01-01", "2023-01-10") # 返回 9 +``` + +--- + +### 3.2 `monthfirstday()` + +获取当前月份的第一天。 + +#### 返回值 +- `str`:格式为 `'YYYY-MM-01'` + +#### 示例 +```python +monthfirstday() # 如 '2025-04-01' +``` + +--- + +### 3.3 `curDatetime()` + +获取当前精确时间的 `datetime` 对象。 + +#### 返回值 +- `datetime.datetime`:当前系统时间 + +--- + +### 3.4 `curDateString()` + +获取当前日期字符串。 + +#### 返回值 +- `str`:格式 `'YYYY-MM-DD'` + +--- + +### 3.5 `curTimeString()` + +获取当前时间字符串(时分秒)。 + +#### 返回值 +- `str`:格式 `'HH:MM:SS'` + +--- + +### 3.6 `timestampstr()` + +生成带毫秒的完整时间戳字符串。 + +#### 返回值 +- `str`:格式 `'YYYY-MM-DD HH:MM:SS.fff'` + +#### 示例 +```python +timestampstr() # 如 '2025-04-05 14:30:22.123' +``` + +--- + +### 3.7 `isMonthLastDay(d)` + +判断给定 `datetime` 是否是当月最后一天。 + +#### 参数 +| 参数 | 类型 | 描述 | +|------|----------------|------------------| +| `d` | `datetime` | 待判断的时间对象 | + +#### 返回值 +- `bool`:若是月末返回 `True` + +#### 原理 +通过加一天后判断月份是否变化。 + +--- + +### 3.8 `isLeapYear(year)` + +判断是否为闰年。 + +> ⚠️ **注意**:原代码逻辑有误! + +#### 错误分析 +```python +if year % 4 == 0 and year % 100 == 0 and not (year % 400 == 0): +``` +此条件仅在“能被100整除但不能被400整除”时返回 `True`,即把**平年**判为闰年。 + +#### 正确应为: +```python +def isLeapYear(year): + return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0) +``` + +> ✅ 建议修复该函数! + +--- + +### 3.9 `timestamp(dt)` 和 `timeStampSecond(dt)` + +将 `datetime` 转换为 Unix 时间戳。 + +| 函数名 | 精度 | 说明 | +|-------------------|------------|------------------------------| +| `timestamp(dt)` | 微秒级 | 包含 `.microsecond` | +| `timeStampSecond(dt)` | 秒级 | 忽略微秒部分 | + +#### 参数 +- `dt`: `datetime` 对象 + +#### 返回值 +- `int`:自 1970-01-01 UTC 起的秒数 + +--- + +### 3.10 `addSeconds(dt, s)` + +给时间对象增加若干秒。 + +#### 参数 +| 参数 | 类型 | 说明 | +|------|--------------|--------------------| +| `dt` | `datetime` | 原始时间 | +| `s` | `int` | 要增加的秒数 | + +#### 返回值 +- `datetime`:新的时间对象 + +--- + +### 3.11 `monthMaxDay(y, m)` + +获取指定年份某月的最大天数(考虑闰年)。 + +#### 参数 +| 参数 | 类型 | 说明 | +|------|-------|------------| +| `y` | int | 年份 | +| `m` | int | 月份(1~12)| + +#### 返回值 +- `int`:该月最多多少天 + +--- + +### 3.12 `date2str(dt=None)` + +将 `datetime` 转为 `'YYYY-MM-DD'` 字符串。 + +#### 参数 +- `dt`: 可选 `datetime` 对象;若未传则使用当前时间 + +#### 返回值 +- `str`:格式化日期字符串 + +--- + +### 3.13 `time2str(dt)` + +⚠️ **存在 Bug** + +```python +return '%02d:%02d:%02d' % (dt.hour, dt, minute, dt.second) +``` +错误使用了 `dt,minute`(语法错误),应为 `dt.minute`。 + +#### 修正版本 +```python +def time2str(dt): + return '%02d:%02d:%02d' % (dt.hour, dt.minute, dt.second) +``` + +--- + +### 3.14 `str2Date(dstr)` 和 `str2Datetime(dstr)` + +#### `str2Date(dstr)` +尝试解析形如 `'YYYY-MM-DD [HH:MM:SS]'` 的字符串为 `datetime` 对象。 + +> **警告**:内部调用了不存在的 `ymdDate(...)` 函数前就执行拆分,且异常处理打印但不中断。 + +##### 改进建议: +- 统一使用标准库 `datetime.strptime` +- 移除冗余中间步骤 + +#### `str2Datetime(dstr)` +更稳健的实现,推荐使用。 + +##### 支持格式 +- `'YYYY-MM-DD'` +- `'YYYY-MM-DD HH:MM:SS'` + +##### 返回值 +- `datetime` 对象 + +--- + +### 3.15 `ymdDate(y, m, d, H=0, M=0, S=0)` + +构造 `datetime` 实例的快捷方式。 + +#### 参数 +年、月、日、时、分、秒均可指定,默认为0。 + +#### 示例 +```python +ymdDate(2025, 4, 5, 10, 30) # 2025-04-05 10:30:00 +``` + +--- + +### 3.16 `strdate_add(date_str, days=0, months=0, years=0)` + +对日期字符串进行加减操作并返回新字符串。 + +#### 参数 +支持同时添加天、月、年。 + +#### 执行顺序 +1. 加 `days` +2. 加 `months` +3. 加 `years` + +> 自动处理跨月/跨年边界,并确保不会出现非法日期(如 2月30日) + +#### 示例 +```python +strdate_add("2024-01-31", months=1) # 返回 "2024-02-29"(自动调整到月底) +``` + +--- + +### 3.17 `addMonths(dt, months)` 和 `addYears(dt, years)` + +#### `addMonths(dt, months)` +安全地增加月份,自动修正超出范围的日期(如 1月31日 +1月 → 2月29日或28日) + +#### `addYears(dt, years)` +同理,考虑闰年导致2月可能只有28天。 + +--- + +### 3.18 `dateAdd(dt, days=0, months=0, years=0)` + +综合日期加法入口函数,按顺序应用增量。 + +--- + +### 3.19 `firstSunday(dt)` + +返回大于等于输入日期的第一个周日。 + +> 注意:Python 中 `weekday()` 返回 0~6 表示周一至周日。 +> 因此 `(7 - weekday()) % 7` 更准确。 + +当前实现假设 `weekday()==6` 是周六?实际应为周日。 + +#### 存疑逻辑 +```python +f = dt.weekday() +if f < 6: + return dt + timedelta(7 - f) +else: + return dt +``` +这表示: +- 若不是周日(即 0~5),则加 `(7-f)` 天 +- 若是周日(6),直接返回 + +✅ 实际上这是正确的:`weekday()` 返回 0=周一 ... 6=周日 + +所以当 `f=6`(周日)时无需移动。 + +✔️ 此函数逻辑正确。 + +--- + +### 3.20 时间格式常量与函数 + +```python +DTFORMAT = '%Y%m%d %H%M%S' +``` + +#### `getCurrentTimeStamp()` +获取当前时间按 `DTFORMAT` 格式的字符串。 + +#### `TimeStamp(t)` +将 `struct_time` 格式化为上述格式。 + +#### `timestampAdd(ts1, ts2)` +将两个时间字符串相加(`ts2` 可为秒数或时间字符串) + +> ❌ 存在类型判断错误: +```python +if type(ts2)=='' : +``` +应改为: +```python +if isinstance(ts2, str): +``` + +否则会出错。 + +#### `timestampSub(ts1, ts2)` +计算两个 `DTFORMAT` 时间之间的秒数差。 + +--- + +### 3.21 `StepedTimestamp(baseTs, ts, step)` + +将时间 `ts` 对齐到以 `baseTs` 为起点、步长为 `step` 秒的时间网格。 + +#### 应用场景 +- 数据采样对齐 +- 定时任务触发点归一化 + +#### 参数 +| 参数 | 类型 | 说明 | +|----------|--------|------------------------| +| `baseTs` | str | 基准时间(DTFORMAT) | +| `ts` | str | 当前时间 | +| `step` | int | 步长(秒) | + +#### 返回值 +- 最近的对齐时间点(向上或向下取整) + +--- + +### 3.22 `timestamp2dt(t)` + +将 Unix 时间戳(秒)转为 `datetime` 对象。 + +--- + +### 3.23 `date_weekinyear(date_str)` + +获取日期所在年的第几周(ISO 周编号)。 + +#### 返回值 +- `str`:`'YYYY-W'` 形式,其中 `W` 为两位周数 + +> 使用 `%W`(基于周一开始的周),而非 ISO 标准 `%U` 或 `isocalendar()` + +--- + +### 3.24 `date_season(date_str)` + +根据月份划分季节: + +| 月份 | 季度 | +|----------|------| +| 1–3 | 1 | +| 4–6 | 2 | +| 7–9 | 3 | +| 10–12 | 4 | + +#### 返回值 +- `str`:`'YYYYQ'`,如 `'20251'` 表示2025年第1季度 + +--- + +### 3.25 `str2date(sd)` + +将 `'YYYY-MM-DD'` 字符串转为 `date` 对象。 + +#### 示例 +```python +str2date("2025-04-05") # 返回 date(2025, 4, 5) +``` + +--- + +### 3.26 `is_monthend(dt)` + +判断日期是否为月末。 + +#### 参数 +- `dt`: `str` 或 `date` 对象 + +#### 方法 ++1天后看月份是否改变。 + +--- + +### 3.27 `is_match_pattern(pattern, strdate)` + +判断某日期是否符合特定调度模式。 + +#### 支持的模式 + +| 模式 | 含义 | 示例 | +|--------------|----------------------------------|-------------------------| +| `'D'` | 每日触发 | `'D'` | +| `'W[0-6]'` | 每周星期X(0=周一...6=周日) | `'W0'`=每周一 | +| `'M0'` | 每月最后一天 | `'M0'` | +| `'Mdd'` | 每月第dd天 | `'M15'`=每月15日 | +| `'Sx-dd'` | 每季度第x月第dd天 | `'S1-01'`=季初第一天 | +| `'Ym-dd'` | 每年m月dd日 | `'Y12-25'`=每年12月25日 | + +#### 返回值 +- `bool`:是否匹配 + +#### 注意事项 +- `Sx-dd` 中 `x %= 4`,即循环每4个月视为一个季度 +- 调试信息 `print(f'{m=}-{d=}, ...')` 建议移除生产环境 + +--- + +## 4. 已知问题汇总(建议修复) + +| 函数名 | 问题描述 | 建议修改 | +|----------------|------------------------------------------------|----------| +| `isLeapYear` | 条件反向,导致逻辑错误 | ✅ 重写 | +| `time2str` | 引用 `dt,minute` 报语法错误 | ✅ 修复拼写 | +| `timestampAdd` | `type(ts2)==''` 永远为假 | ✅ 改为 `isinstance(ts2, str)` | +| `str2Date` | 使用未定义函数 `ymdDate` 在 try 外部可能发生错误 | ✅ 重构或删除冗余逻辑 | +| `str2Datetime` | 不支持毫秒 | ✅ 可拓展支持 | + +--- + +## 5. 使用示例 + +```python +# 计算间隔 +print(days_between("2023-01-01", "2023-12-31")) # 364 + +# 获取今天 +print(curDateString()) # 2025-04-05 + +# 加一个月 +print(strdate_add("2024-01-31", months=1)) # 2024-02-29 + +# 判断是否为月末 +print(is_monthend("2025-04-30")) # True + +# 匹配每周五 +print(is_match_pattern("W4", "2025-04-11")) # True(假设是周五) +``` + +--- + +## 6. 总结 + +本工具包提供了丰富的日期时间操作能力,适合嵌入数据处理流水线或定时任务系统中。尽管存在少量 bug 和可优化空间,整体设计清晰、功能完整。 + +> 📌 推荐在正式使用前进行单元测试,并优先修复已知缺陷。 + +--- + +**版本**: v1.0 +**作者**: Auto-generated Documentation +**更新时间**: 2025-04-05 \ No newline at end of file diff --git a/aidocs/timecost.md b/aidocs/timecost.md new file mode 100644 index 0000000..c809262 --- /dev/null +++ b/aidocs/timecost.md @@ -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)) +``` + +- 打印所有已记录任务的统计信息。 +- 输出格式:` <调用次数> <总耗时> <平均耗时>` +- 示例输出: + ``` + 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] = [] +``` \ No newline at end of file diff --git a/aidocs/tworkers.md b/aidocs/tworkers.md new file mode 100644 index 0000000..d360f3c --- /dev/null +++ b/aidocs/tworkers.md @@ -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` 导入缺失、异常处理、延迟优化等问题,更具健壮性。 \ No newline at end of file diff --git a/aidocs/udp_comm.md b/aidocs/udp_comm.md new file mode 100644 index 0000000..3a66392 --- /dev/null +++ b/aidocs/udp_comm.md @@ -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 \ No newline at end of file diff --git a/aidocs/uni_outip.md b/aidocs/uni_outip.md new file mode 100644 index 0000000..eb13262 --- /dev/null +++ b/aidocs/uni_outip.md @@ -0,0 +1,254 @@ +# 外网 IP 获取工具技术文档 + +```markdown +# 外网 IP 获取工具(External IP Getter) + +一个基于多协议尝试获取外网公网 IP 地址的 Python 工具,支持 NAT-PMP、UPnP 以及第三方网页抓取方式。通过多进程隔离输出流的方式安全地获取 IP 地址。 + +--- + +## 目录 + +- [功能概述](#功能概述) +- [依赖库](#依赖库) +- [核心模块与函数说明](#核心模块与函数说明) + - [`pmp_get_external_ip()`](#pmp_get_external_ip) + - [`upnp_get_external_ip()`](#upnp_get_external_ip) + - [`ipgetter_get_external_ip()`](#ipgetter_get_external_ip) + - [`get_external_ip()`](#get_external_ip) + - [`outip(w)`](#outipw) + - [`get_ip()`](#get_ip) + - [`run()`](#run) +- [主程序入口](#主程序入口) +- [使用示例](#使用示例) +- [注意事项](#注意事项) +- [未来优化建议](#未来优化建议) + +--- + +## 功能概述 + +本工具旨在可靠地获取当前设备所在的公网 IP 地址,采用多种协议逐级回退策略: + +1. **NAT-PMP**:适用于支持该协议的路由器(如部分 Apple 设备)。 +2. **UPnP IGD**:通用即插即用互联网网关设备协议,广泛用于家庭路由器。 +3. **IPGetter 网页抓取**:当本地网络协议不可用时,通过访问公网服务获取 IP。 + +为避免跨进程通信中的阻塞和异常影响主流程,使用 `multiprocessing.Pipe` 和子进程隔离执行 IP 获取逻辑。 + +--- + +## 依赖库 + +| 库名 | 用途 | +|------|------| +| `natpmp` | 实现 NAT-PMP 协议以获取公网 IP | +| `upnpclient` | 发现并调用 UPnP IGD 服务 | +| `appPublic.ipgetter` | 第三方 IP 获取器(基于网页抓取) | +| `multiprocessing` | 多进程支持,用于隔离 IO 操作 | +| `os`, `time` | 系统操作与延时控制 | + +> ⚠️ 安装命令: +```bash +pip install natpmp upnpclient app-public +``` + +--- + +## 核心模块与函数说明 + +### `pmp_get_external_ip()` + +尝试通过 **NAT-PMP** 协议获取公网 IP。 + +#### 返回值 +- 成功时返回字符串格式的公网 IP(如 `"203.0.113.45"`) +- 失败或异常时返回 `None` + +#### 示例 +```python +ip = pmp_get_external_ip() +if ip: + print("NAT-PMP 获取成功:", ip) +``` + +--- + +### `upnp_get_external_ip()` + +通过 **UPnP IGD**(Internet Gateway Device)协议从路由器获取公网 IP。 + +#### 流程说明 +1. 调用 `upnpclient.discover()` 自动发现局域网内支持 UPnP 的网关设备。 +2. 提取第一个设备,并查找包含 `'WAN'` 和 `'Conn'` 的服务名(通常是 WANIPConnection 或类似)。 +3. 调用 `GetExternalIPAddress()` 方法获取外部 IP。 +4. 解析响应中 `NewExternalIPAddress` 字段。 + +#### 返回值 +- 成功时返回公网 IP 字符串 +- 出错时打印错误信息并返回 `None` + +> 📌 注意:若有多台 UPnP 设备,请注意选择正确的设备实例。 + +--- + +### `ipgetter_get_external_ip()` + +使用第三方网页服务获取公网 IP,作为最后兜底方案。 + +#### 特点 +- 使用 `appPublic.ipgetter.IPgetter` 类进行 HTTP 请求抓取。 +- 内置重试机制,每 0.1 秒尝试一次直到成功。 +- 忽略请求过程中的所有异常。 + +#### 返回值 +- 成功获取后立即返回 IP 字符串 +- 不会永久阻塞 + +--- + +### `get_external_ip()` + +**主协调函数**:按优先级顺序尝试三种方法获取 IP。 + +#### 执行顺序 +1. NAT-PMP +2. UPnP +3. Web 抓取(IPGetter) + +一旦任一方法成功即返回结果,形成“短路”逻辑。 + +#### 返回值 +- 公网 IP 字符串(推荐始终检查非空) +- 若全部失败,则返回 `None` + +--- + +### `outip(w)` + +在独立进程中运行的函数,将获取到的 IP 输出到指定写入端 `w`。 + +#### 参数 +- `w`: 可写的文件描述符管道(来自 `Pipe()` 的写入端) + +#### 原理 +- 使用 `os.dup2(w.fileno(), 1)` 将标准输出重定向至管道 +- 调用 `get_external_ip()` 并通过 `print(ip)` 输出 +- 子进程结束后,父进程可通过读取管道获得结果 + +> ✅ 优点:避免复杂对象传递,利用文本流简化 IPC。 + +--- + +### `get_ip()` + +主进程调用此函数来启动子进程并安全获取 IP。 + +#### 步骤 +1. 创建匿名管道 `Pipe()` +2. 打开读取端为文件对象 +3. 启动子进程执行 `outip(w)` +4. 读取子进程输出的第一行 +5. 等待子进程结束 +6. 返回去除了换行符的 IP 字符串 + +#### 返回值 +- 成功:公网 IP 字符串(如 `"8.8.8.8"`) +- 失败:空字符串(需判断有效性) + +--- + +### `run()` + +持续运行的测试循环函数。 + +#### 行为 +- 每 10 秒调用一次 `get_ip()` +- 若获取到 IP,则打印:`ip='203.0.113.45'` +- 使用 `time.sleep(10)` 控制频率 + +可用于长期监控公网 IP 变化。 + +--- + +## 主程序入口 + +```python +if __name__ == '__main__': + run() +``` + +当脚本直接运行时,进入无限循环模式,每隔 10 秒输出当前公网 IP。 + +--- + +## 使用示例 + +### 单次获取 IP +```python +from your_module import get_ip + +ip = get_ip() +print("Current Public IP:", ip) +``` + +### 集成进其他应用 +可将 `get_external_ip()` 直接导入使用,无需多进程封装: + +```python +from your_module import get_external_ip + +public_ip = get_external_ip() +if public_ip: + print(f"Public IP is {public_ip}") +else: + print("Failed to retrieve public IP") +``` + +--- + +## 注意事项 + +1. **权限要求** + - UPnP/NAT-PMP 需要设备处于同一局域网且路由器启用相应功能。 + - 某些防火墙可能阻止 SSDP(UPnP 发现协议)广播包。 + +2. **性能与延迟** + - `ipgetter_get_external_ip()` 依赖网络请求,可能较慢。 + - 推荐仅作为备用手段。 + +3. **稳定性问题** + - `upnpclient.discover()` 可能因网络波动返回空列表,导致索引错误。 + - 建议添加超时处理或重试逻辑。 + +4. **多进程开销** + - `get_ip()` 每次创建新进程,不适合高频调用(如 >1次/秒)。 + +5. **输出重定向风险** + - `os.dup2` 修改了子进程的标准输出,确保只在子进程中使用。 + +--- + +## 未来优化建议 + +| 改进项 | 描述 | +|--------|------| +| 添加超时机制 | 给每个 IP 获取方法设置最大等待时间 | +| 缓存最近 IP | 减少重复查询开销 | +| 支持更多协议 | 如 PCP(Port Control Protocol) | +| 异常日志记录 | 替代 `print()`,使用 `logging` 模块 | +| 配置化策略 | 允许用户配置启用哪些协议及顺序 | +| 异步支持 | 使用 `asyncio` + `aiohttp` 提升效率 | + +--- + +## 许可证 + +请根据项目实际情况补充 LICENSE 信息。 + +> 示例:MIT License +``` + +--- + +✅ 文档完成。可保存为 `README.md` 或集成至 Sphinx/Docusaurus 文档系统。 \ No newline at end of file diff --git a/aidocs/unicoding.md b/aidocs/unicoding.md new file mode 100644 index 0000000..d703f97 --- /dev/null +++ b/aidocs/unicoding.md @@ -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 接口预处理、日志解析、配置加载等涉及编码转换的场景。 \ No newline at end of file diff --git a/aidocs/uniqueID.md b/aidocs/uniqueID.md new file mode 100644 index 0000000..0d63f68 --- /dev/null +++ b/aidocs/uniqueID.md @@ -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 \ No newline at end of file diff --git a/aidocs/version.md b/aidocs/version.md new file mode 100644 index 0000000..93c20be --- /dev/null +++ b/aidocs/version.md @@ -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__}") +``` \ No newline at end of file diff --git a/aidocs/wav.md b/aidocs/wav.md new file mode 100644 index 0000000..6e0095f --- /dev/null +++ b/aidocs/wav.md @@ -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 风格许可,可用于学习、研究及商业项目(请根据实际依赖库的许可证合规使用)。 \ No newline at end of file diff --git a/aidocs/wcag_checker.md b/aidocs/wcag_checker.md new file mode 100644 index 0000000..16ea1e2 --- /dev/null +++ b/aidocs/wcag_checker.md @@ -0,0 +1,245 @@ +# 颜色对比度计算工具技术文档 + +本模块提供了一组用于计算颜色对比度和评估其是否符合 **WCAG(Web Content Accessibility Guidelines)** 标准的函数。这些函数可用于网页设计、UI 开发等场景中,确保文本与背景之间的可读性和无障碍访问性。 + +--- + +## 目录 + +- [功能概述](#功能概述) +- [函数说明](#函数说明) + - [`calculate_luminance(rgba)`](#calculate_luminancergba) + - [`get_contrast_ratio(lumA, lumB)`](#get_contrast_ratioluma-lumb) + - [`get_color_contrast_ratio(color1, color2)`](#get_color_contrast_ratiocolor1-color2) + - [`wcag_check(color1, color2, font_size=14)`](#wcag_checkcolor1-color2-font_size14) +- [使用示例](#使用示例) +- [注意事项与已知问题](#注意事项与已知问题) +- [参考标准](#参考标准) + +--- + +## 功能概述 + +该模块实现了以下核心功能: + +1. 计算给定 RGBA 颜色的相对亮度(Luminance)。 +2. 计算两种颜色之间的对比度比值。 +3. 判断对比度是否满足 WCAG 2.0 的 AA 或 AAA 级别要求,考虑字体大小的影响。 + +> ⚠️ 当前代码存在多个拼写错误和变量名错误,实际使用前必须修复! + +--- + +## 函数说明 + +### `calculate_luminance(rgba)` + +**功能:** +计算输入颜色的相对亮度(Relative Luminance),依据 [WCAG 2.0 Suggestion E2](https://www.w3.org/TR/WCAG20-TECHS/G17.html#G17-CalcStep) 中定义的加权公式。 + +**参数:** +- `rgba` (`tuple`): 表示颜色的四元组 `(R, G, B, A)`,其中 R、G、B 为 0–255 范围内的整数,A(Alpha)可选(不参与计算)。 + +> ✅ 注意:此函数仅使用 RGB 分量,忽略 Alpha 通道。 + +**返回值:** +- `float`: 相对亮度值,范围在 `0.0`(黑色)到 `1.0`(白色)之间。 + +**公式:** +``` +L = 0.2126 * R + 0.7152 * G + 0.0722 * B +``` +其中 R、G、B 应先归一化为 0–1 范围(即除以 255),并应用伽马校正(sRGB 转线性亮度)。但当前实现未包含归一化与伽马校正步骤,**这是一个严重缺陷!** + +> 🔴 **警告:当前实现有误!** +> - 变量名错误:`color` 和 `colr` 混用 → 应统一为 `rgba` +> - 缺少归一化处理(需将 0–255 映射到 0–1) +> - 缺少 sRGB 到线性亮度的转换(非线性响应) + +✅ 正确做法应如下: +```python +def srgb_to_linear(c): + c_norm = c / 255.0 + return c_norm / 12.92 if c_norm <= 0.04045 else ((c_norm + 0.055) / 1.055) ** 2.4 + +lum = 0.2126 * srgb_to_linear(r) + + 0.7152 * srgb_to_linear(g) + + 0.0722 * srgb_to_linear(b) +``` + +--- + +### `get_contrast_ratio(lumA, lumB)` + +**功能:** +根据两个颜色的相对亮度计算它们之间的对比度比值。 + +**参数:** +- `lumA`, `lumB` (`float`): 两个颜色的相对亮度值(来自 `calculate_luminance` 输出) + +**返回值:** +- `float`: 对比率,取值范围通常为 `1:1`(无对比)到 `21:1`(最大对比) + +**公式:** +``` +contrast = (lighter + 0.05) / (darker + 0.05) +``` + +> ⚠️ 错误:函数体内使用了未定义的变量 `lumX`,应为 `lumA` + +🔴 存在 Bug: +```python +darker = min(lumX, lumB) # ❌ lumX 未定义 +``` +✅ 应改为: +```python +darker = min(lumA, lumB) +``` + +--- + +### `get_color_contrast_ratio(color1, color2)` + +**功能:** +直接传入两个颜色,自动计算其对比度比值。 + +**参数:** +- `color1`, `color2`: 合法的颜色元组 `(R, G, B[, A])` + +**返回值:** +- `float`: 两颜色间的对比度比值 + +⚠️ 错误:调用了不存在的函数 `get_contrast_Ratio`(大小写错误) + +🔴 原始代码错误: +```python +return get_contrast_Ratio(lum1, lum2) # ❌ 函数名拼写错误 +``` +✅ 应为: +```python +return get_contrast_ratio(lum1, lum2) +``` + +--- + +### `wcag_check(color1, color2, font_size=14)` + +**功能:** +判断两个颜色组合是否满足 WCAG 的可访问性标准(AA 和 AAA 级别)。 + +**参数:** +- `color1`, `color2`: 颜色元组 `(R, G, B)` +- `font_size` (`int` or `float`): 字体大小(像素),用于决定对比度阈值 + +**返回值:** +- `tuple(bool, bool)`: + - 第一个布尔值表示是否达到 **AA 级别** + - 第二个布尔值表示是否达到 **AAA 级别** + +**逻辑规则:** + +| 字体大小 | AA 标准 | AAA 标准 | +|----------------|---------|---------| +| ≥ 18px 或粗体≥14px | 3.0 | 4.5 | +| < 18px | 4.5 | 7.0 | + +> ⚠️ 实际 WCAG 规则更复杂,涉及“大字号”定义(18pt 正常 / 14pt 粗体 ≈ 24px),此处简化处理。 + +🔴 当前代码错误: +```python +return ratio >= aa, radio >= aaa # ❌ 'radio' 是拼写错误 +``` +✅ 应为: +```python +return ratio >= aa, ratio >= aaa +``` + +--- + +## 使用示例 + +假设我们想检查白色文字 `(255, 255, 255)` 在深蓝背景 `(0, 0, 128)` 上的可读性: + +```python +# 示例(需先修复所有 bug 后方可运行) + +white = (255, 255, 255) +blue = (0, 0, 128) + +aa, aaa = wcag_check(white, blue, font_size=16) +print(f"AA compliant: {aa}") # True? +print(f"AAA compliant: {aaa}") # False? +``` + +预期输出(修复后): +``` +AA compliant: True +AAA compliant: False +``` + +--- + +## 注意事项与已知问题 + +📌 **当前代码存在多个致命错误,不能直接使用!** + +| 问题 | 描述 | 修复建议 | +|------|------|----------| +| `color` / `colr` / `lumX` 变量名错误 | 引用未定义变量导致运行时异常 | 统一变量名为正确拼写 | +| `get_contrast_Ratio` 大小写错误 | Python 区分大小写,函数不存在 | 改为 `get_contrast_ratio` | +| `radio` 拼写错误 | 导致 NameError | 改为 `ratio` | +| 未归一化 RGB 值 | 输入是 0–255,但公式期望 0–1 | 添加 `/ 255.0` 并应用伽马校正 | +| 缺少伽马校正 | 直接线性计算亮度不准确 | 使用 sRGB → 线性转换函数 | +| 字体大小判断逻辑不完整 | 未考虑粗体情况 | 建议增加 `bold` 参数 | + +--- + +## 推荐修复版本(完整修正版) + +```python +def srgb_to_linear(c): + """Convert sRGB component to linear light.""" + c_norm = c / 255.0 + return c_norm / 12.92 if c_norm <= 0.04045 else ((c_norm + 0.055) / 1.055) ** 2.4 + +def calculate_luminance(rgba): + r, g, b = rgba[:3] + lr = srgb_to_linear(r) + lg = srgb_to_linear(g) + lb = srgb_to_linear(b) + return 0.2126 * lr + 0.7152 * lg + 0.0722 * lb + +def get_contrast_ratio(lumA, lumB): + lighter = max(lumA, lumB) + darker = min(lumA, lumB) + return (lighter + 0.05) / (darker + 0.05) + +def get_color_contrast_ratio(color1, color2): + lum1 = calculate_luminance(color1) + lum2 = calculate_luminance(color2) + return get_contrast_ratio(lum1, lum2) + +def wcag_check(color1, color2, font_size=14, bold=False): + # 简化判断:>=18px 或 (>=14px 且粗体) 视为“大字号” + is_large_text = font_size >= 18 or (bold and font_size >= 14) + + aa_threshold = 3.0 if is_large_text else 4.5 + aaa_threshold = 4.5 if is_large_text else 7.0 + + ratio = get_color_contrast_ratio(color1, color2) + return ratio >= aa_threshold, ratio >= aaa_threshold +``` + +--- + +## 参考标准 + +- [WCAG 2.0 Guideline 1.4.3: Contrast (Minimum)](https://www.w3.org/TR/UNDERSTANDING-WCAG20/visual-audio-contrast-contrast.html) +- [Understanding Success Criterion 1.4.3 | W3C](https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum.html) +- [Relative Luminance Calculation - Wikipedia](https://en.wikipedia.org/wiki/Relative_luminance) + +--- + +## 结语 + +尽管原始代码意图良好,但由于多处语法和逻辑错误,无法正常工作。本文档不仅描述了其设计目标,还指出了关键问题并提供了可运行的修复方案。建议开发者在实际项目中使用经过验证的库(如 `webcolors`、`tinycss2` 或专用 contrast checker 库),或基于本修复版本进行封装。 \ No newline at end of file diff --git a/aidocs/worker.md b/aidocs/worker.md new file mode 100644 index 0000000..943df2e --- /dev/null +++ b/aidocs/worker.md @@ -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 命令执行 +✅ 提供基本的同步/异步调用统一接口(有待优化) + +适合用于爬虫、批量任务处理、微服务中间层等需要混合同步异步逻辑的场景。 + +--- +``` + +> 💡 **提示**:生产环境中建议增加日志记录、异常处理、超时机制以及更完善的单元测试。 \ No newline at end of file diff --git a/aidocs/zmq_reqrep.md b/aidocs/zmq_reqrep.md new file mode 100644 index 0000000..be3b481 --- /dev/null +++ b/aidocs/zmq_reqrep.md @@ -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日 \ No newline at end of file diff --git a/aidocs/zmq_topic.md b/aidocs/zmq_topic.md new file mode 100644 index 0000000..290c3ef --- /dev/null +++ b/aidocs/zmq_topic.md @@ -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://
:` → 发布者连接此地址 + - `sub_port`: `tcp://
:` → 订阅者连接此地址 +- 调用 `xpub_xsub_proxy()` 启动代理循环 + +#### 方法:`xpub_xsub_proxy()` +创建并启动 ZeroMQ 内置代理: + +```python +zmq.proxy(frontend_pubs, backend_subs) +``` + +##### 套接字角色 +| 套接字 | 类型 | 角色 | 绑定地址 | +|-------|------|------|---------| +| `frontend_pubs` | `XSUB` | 接收发布者消息 | `tcp://
:` | +| `backend_subs` | `XPUB` | 向订阅者广播消息 | `tcp://
:` | + +> ✅ `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`,可实现跨主机消息分发。 \ No newline at end of file diff --git a/aidocs/zmqapi.md b/aidocs/zmqapi.md new file mode 100644 index 0000000..2b175cc --- /dev/null +++ b/aidocs/zmqapi.md @@ -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") +``` + +##### 消息格式(在传输中): +``` + +``` + +例如: +``` +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''`,仅接收匹配前缀的消息。 + +#### ➕ 方法:`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, ... +``` \ No newline at end of file