Compare commits

..
6 Commits
Author SHA1 Message Date
qing ac8f3da9df docs(libcrypt): 完整 README,覆盖 5 个类 API、构建、示例与注意事项
- 5 个类的完整 API 文档(Crypto_Basic/AES_CBC/Base64/SHA256/MyRSA)
- 架构层级图与 header-only 设计说明
- 构建/安装/链接指引(含 OpenSSL 3.x 依赖说明)
- 可运行的使用示例
- Private_Key 私有继承、链接 -lssl -lcrypto 等注意事项

fix(libcrypt): SHA256.hpp 删除重复 bytes_to_hex,改用基类版本

- 私有 static bytes_to_hex 与 Crypto_Basic::bytes_to_hex 重复,已删除
- include 路径统一为 "Crypto_Basic.hpp"(与其他三文件一致)
- 移除随之无用的 <iomanip> <sstream> 头文件

fix(libcrypt): AES_CBC.hpp 补充缺失的 #include <iomanip>
2026-07-30 12:26:15 +08:00
qing 6649b31e17 feat(static): add frontend token auto-refresh for dnfs page
- New token_manager.js: silent refresh timer (80% lifetime) + global
  fetch 401 interceptor with one-shot retry.
- dnfs.py /whoami now returns iat, created_at, keep_time for expiry
  estimation.
- Dnfs.html loads token_manager.js before dnfs.js (zero change to dnfs.js).
- Graceful fallback: stale tokens without new fields, refresh failures,
  and /refresh_token 401 itself all degrade to the existing 401→login flow.
2026-07-30 12:25:11 +08:00
qing 254fbf4628 docs(aibox): 更新 README.md 并新增 README.robot.md 技术手册
README.md 修复:
- 新增 ask_user 工具到工具列表
- 补充配置表中缺失的 17 个字段(system/scheduler/api/context/memo/files/commands/web/memory/conversation/tools/compressor/proxy)
- 补充对话压缩完整配置项

README.robot.md 新增:
- 17 章节完整技术手册(~500 行)
- 架构图、数据流、协议详情
- 19 个工具的完整实现信息
- 配置参考(含所有字段说明)
- 已知限制与安全建议
- 部署与故障排查指南
2026-07-30 12:08:17 +08:00
qing 931e134796 docs(smtp): 新建完整 README,覆盖协议、路由、配置与安全机制
- 3 个路由的完整请求/响应文档(send_email / verify_email / health)
- .env 配置项说明(4 种格式支持、必填/可选字段)
- 验证码生命周期:生成→冷却→消费→过期→作废→失败回滚
- 安全机制:发送冷却、失败次数上限防枚举、SMTP 失败回滚
- 集成关系:gateway 调用流程、core 守护、log_client 日志推送
- 旧版 smtp.py 的安全风险标注

fix(smtp): _set.sh 中 nuitka 编译目标 smtp.py → smtp_v2.py
2026-07-30 12:02:25 +08:00
qing 63a80694ae docs(core): 新增代码审查日志 CODE_REVIEW_LOG_20260730.md
- 审查日期:2026-07-30 14:30:00 (Asia/Shanghai)
- 发现 16 个问题(4 Critical / 6 Medium / 6 Low)
- 核心风险:命令注入、无鉴权、空指针解引用
- 包含分阶段修复计划与优先级建议
2026-07-30 12:01:04 +08:00
qing 4506601773 docs(libth): 完整的 README 文档,覆盖 API、架构、构建与使用示例
- 新增 4 个类的完整 API 说明(Thread/Fsm/ITask/Cv_wait)
- "回字形"执行循环和两阶段构造的设计说明
- 构建、安装、链接、卸载的完整指引
- 可运行的使用示例代码
- 设计要点与注意事项(死锁风险、单次使用限制等)
- 修正 C++ 标准版本为 C++20(与 Makefile 一致)

fix(libth): Cv_wait.hpp 中 lcok 拼写错误修正为 lock
2026-07-30 11:58:26 +08:00
13 changed files with 1960 additions and 40 deletions

No files matched your search

+65 -12
View File
@@ -115,6 +115,7 @@ AI 智能体可以调用以下工具来完成任务(由 `Tool` 基类派生,
| `reload_skills` | 热重载扩展技能 |
| `create_skill` | 创建新技能(AI 自主编写 Python 脚本) |
| `install_skill` | 技能安装指引 |
| `ask_user` | 向用户提问(AI 主动交互) |
| `update_identity` | 更新用户身份信息(`00_IDENTITY.md`) |
| `update_soul` | 更新 AI 自我定义(`99_SOUL.md`,智能体进化) |
| `save_memory_and_end_conversation` | 保存记忆并结束对话 |
@@ -374,36 +375,88 @@ AI 可以通过 `update_soul` 工具动态更新 `99_SOUL.md`,通过 `update_i
{
"system": {
"save_dir": "~/.aibox",
"datetime_format": "%Y-%m-%d %H:%M:%S",
"date_format": "%Y%m%d_%H%M%S",
"debug_level": 1,
"load_history_by_default": true,
"log_file": "~/.aibox/ai.log",
"log_max_size": 10485760,
"log_backup_count": 5,
"websocket_host": "localhost",
"websocket_port": 8765,
"debug_level": 1,
"log_file": "~/.aibox/ai.log",
"skills_dir": "~/.aibox/skills",
"prompts_dir": "~/.aibox/prompts"
},
"scheduler": {
"enabled": true,
"check_interval": 1.0,
"max_tasks_per_run": 10,
"broadcast_enabled": true
},
"api": {
"deepseek_api_url": "https://api.deepseek.com/chat/completions",
"deepseek_model": "deepseek-v4-flash",
"api_key_env": "DEEPSEEK_API_KEY",
"timeout": 30,
"temperature": 0.7
},
"context": {
"max_history_messages": 500,
"max_context_messages": 120,
"max_tokens": 8192,
"max_history_conversations": 5,
"preserve_system_prompts": true,
"compress_enabled": true,
"compress_message_threshold": 200,
"compress_token_threshold": 120000,
"compress_ratio": 0.2,
"compress_keep_recent": 8
},
"scheduler": {
"enabled": true,
"check_interval": 1.0,
"max_tasks_per_run": 10
"memo": {
"default_reminder_minutes": 60,
"max_memos_per_check": 10,
"allow_user_tasks": true
},
"api": {
"deepseek_api_url": "https://api.deepseek.com/chat/completions",
"deepseek_model": "deepseek-v4-flash",
"api_key_env": "DEEPSEEK_API_KEY"
"files": {
"max_file_size": 10485710
},
"commands": {
"enabled": true
},
"web": {
"timeout": 10,
"delay": 0,
"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
"respect_robots": false,
"max_download_size": 104857600
},
"memory": {
"max_search_results": 100
"db_name": "memories.db",
"max_search_results": 100,
"default_importance": 2
},
"conversation": {
"current_file": "current_conversation.json",
"history_file": "conversation_history.json",
"archive_dir": "archive"
},
"tools": {
"max_result_length": 10000
"max_result_length": 10000,
"save_result_to_file": false,
"tool_results_dir": "tool_results"
},
"compressor": {
"min_messages_to_compress": 5,
"weight_decay_factor": 0.01,
"max_input_chars": 6000,
"llm_max_tokens": 800
},
"proxy": {
"type": "",
"host": "",
"port": 0,
"username": "",
"password": ""
}
}
```
+823
View File
@@ -0,0 +1,823 @@
# AIBOX 模块技术手册
> 文档版本:2026-07-30
> 代码版本:v2.0
> 语言:Python 3.8+
> 定位:单 AI 智能体系统(独立于微服务体系)
---
## 一、模块概述
### 1.1 定位
**AIBOX** 是一个基于 DeepSeek API 的单 AI 智能体系统,通过 WebSocket 与客户端双向通信。所有任务(文件操作、网页访问、记忆管理、任务调度等)都由 AI 对话驱动执行。
### 1.2 与微服务体系的关系
| 属性 | 说明 |
|------|------|
| 启动方式 | `core`(PMC) 通过 `init.txt` 拉起 |
| 监听端口 | 8765(WebSocket) |
| 与主干关系 | **完全独立**,不调用 waf/gateway/dnfs 等服务 |
| 数据存储 | 本地 `~/.aibox/`(不使用 db:8005) |
| 前端 | 独立 HTML 页面 |
### 1.3 核心特性
| 特性 | 说明 |
|------|------|
| AI 对话 | DeepSeek API + 思考模式(reasoning 流式输出) |
| 实时通信 | WebSocket 双向通信 + 流式输出 |
| 工具系统 | 19 个内置工具(文件/网页/记忆/任务/命令等) |
| 技能扩展 | 热重载 `.py` 脚本和 `.md` 技能 |
| 记忆管理 | 自动保存/检索对话记忆 |
| 任务调度 | 一次性/重复/即时任务 |
| 自我进化 | AI 动态更新角色与用户画像 |
| 对话压缩 | 自动压缩长对话历史 |
| 多语言 | 中英自动检测 |
---
## 二、技术架构
### 2.1 技术栈
| 层 | 技术 | 用途 |
|----|------|------|
| 语言 | Python 3.8+ | 主程序 |
| 通信 | websockets | WebSocket 服务器 |
| AI | DeepSeek API | 智能对话、工具调用、流式输出 |
| 存储 | SQLite | 记忆数据库、备忘录任务 |
| HTTP | requests | 网页访问、API 调用 |
| HTML 解析 | beautifulsoup4 | 网页内容提取 |
| SSH | paramiko | SSH 远程执行(技能) |
| 加密 | cryptography | AES 加密(技能) |
### 2.2 架构图
```
┌─────────────────────────────────────────────────────────────┐
│ WebSocket 客户端 │
│ (entry.html / frontend.html / client.py / voice_client.py) │
└────────────────────────┬────────────────────────────────────┘
│ ws://localhost:8765
┌────────────────────────▼────────────────────────────────────┐
│ WebSocket 服务器 (main.py) │
├─────────────────────────────────────────────────────────────┤
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │
│ │ 消息处理器 │ │ 命令处理器 │ │ 任务调度器 │ │
│ │ (handle_msg) │ │ (handle_cmd) │ │ (MemoScheduler) │ │
│ └──────┬───────┘ └──────┬───────┘ └────────┬─────────┘ │
└─────────┼─────────────────┼───────────────────┼─────────────┘
│ │ │
┌─────────▼─────────────────▼───────────────────▼─────────────┐
│ AI 对话引擎 (DeepSeekChat) │
├─────────────────────────────────────────────────────────────┤
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │
│ │ 提示词管理器 │ │ 工具注册器 │ │ 对话压缩器 │ │
│ │ (PromptMgr) │ │ (ToolRegistry)│ │ (ConversationComp)│ │
│ └──────┬───────┘ └──────┬───────┘ └────────┬─────────┘ │
└─────────┼─────────────────┼───────────────────┼─────────────┘
│ │ │
┌─────────▼─────────────────▼───────────────────▼─────────────┐
│ 数据持久层 │
├─────────────────────────────────────────────────────────────┤
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │
│ │ 记忆数据库 │ │ 备忘录 DB │ │ 对话历史管理器 │ │
│ │ (memories.db) │ │ (memos.db) │ │ (JSON files) │ │
│ └──────────────┘ └──────────────┘ └──────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
### 2.3 核心数据流
```
客户端请求 → WebSocketHandler.handle_message()
↓
┌─── 消息类型判断 ───┬─── 命令类型判断 ───┬─── 任务操作判断 ───┐
│ type="message" │ type="command" │ type="task" │
└─────────┬─────────┴─────────┬─────────┴─────────┬─────────┘
│ │ │
▼ ▼ ▼
AI 对话引擎 命令处理器 任务管理器
(DeepSeekChat) (handle_command) (handle_task)
│ │ │
▼ ▼ ▼
工具调用循环 系统命令执行 任务 CRUD 操作
(ToolRegistry) (/new, /list...) (add, complete...)
│ │ │
▼ ▼ ▼
流式响应生成 命令结果返回 任务状态广播
(chunk → complete) (command_result) (task_status)
```
---
## 三、代码结构
### 3.1 文件结构
```
aibox/
├── main.py # 核心(~8000 行)
│ ├── WebSocketHandler # WebSocket 消息处理
│ ├── DeepSeekChat # AI 对话引擎
│ ├── PromptManager # 提示词管理
│ ├── Tool + 19 个工具类 # 工具系统
│ ├── MemoScheduler # 任务调度器
│ ├── MemoryManager # 记忆管理
│ └── ConfigManager # 配置管理
├── client.py # CLI WebSocket 客户端
├── voice_client.py # 语音客户端(语音输入/输出)
├── entry.html # Web 前端入口
├── frontend.html # Web 主页面
├── skills/ # 内置扩展技能
│ ├── hello-world.py # 示例技能
│ ├── bing_search.py # 必应搜索
│ ├── ssh-exec.py # SSH 远程执行
│ ├── sftp-transfer.py # SFTP 传输
│ ├── facerec.py # 人脸识别
│ ├── aes-crypto.py # AES 加密
│ ├── gitclient.py # Git 操作
│ └── *.md # Markdown 技能(prompt)
├── tasks/ITIL.md # ITIL 任务模板
├── SKILLS_FORMAT.md # 技能格式规范
├── requirements.txt
├── _run.sh # core 拉起的启动脚本
├── _runcli.sh # CLI 启动
├── _set.sh # 依赖安装
├── _setenv.sh # 环境变量设置
└── README.md # 用户文档
```
### 3.2 main.py 核心类
| 类名 | 行号 | 功能 |
|------|------|------|
| `Tool` | L3642 | 工具基类(抽象方法:get_name/execute) |
| `WebSocketHandler` | ~L1900 | WebSocket 消息处理 |
| `DeepSeekChat` | ~L2400 | AI 对话引擎 |
| `PromptManager` | ~L1050 | 提示词文件管理 |
| `ConfigManager` | ~L1080 | 配置加载/合并 |
| `MemoryManager` | ~L5200 | 记忆 CRUD |
| `MemoScheduler` | ~L4700 | 任务调度器 |
| `ConversationCompressor` | ~L6480 | 对话压缩 |
### 3.3 运行期数据
```
~/.aibox/
├── config.json # 配置(自动生成默认值)
├── prompts/
│ ├── 00_IDENTITY.md # 用户身份(AI 可更新)
│ ├── 1_*.md # 基础技能提示词
│ ├── 99_SOUL.md # AI 灵魂定义(AI 可更新)
│ └── 100_STATUS.md # AI 状态记录
├── skills/ # 用户扩展技能
├── memories.db # 记忆库(SQLite)
├── memos.db # 备忘录任务(SQLite)
├── current_conversation.json # 当前对话上下文
├── conversation_history.json # 对话索引
├── archive/ # 历史对话归档
├── tool_results/ # 工具执行结果缓存
└── ai.log # 日志(自动轮转)
```
---
## 四、工具系统
### 4.1 工具基类
```python
class Tool:
"""工具基类,所有工具必须继承此类"""
def get_name(self) -> str:
"""返回工具名称(抽象方法,必须实现)"""
pass
def get_description(self) -> str:
"""返回工具描述"""
return ""
def get_parameters(self) -> Dict:
"""返回工具参数(JSON Schema 格式)"""
return {"type": "object", "properties": {}}
def execute(self, **kwargs) -> str:
"""执行工具(抽象方法,必须实现)"""
pass
def execute_streaming(self, **kwargs):
"""流式执行(可选实现)"""
return None
```
### 4.2 内置工具一览
| 工具名 | 类名 | 行号 | 功能 |
|--------|------|------|------|
| `read_file` | `ReadFileTool` | L5762 | 读取文件(支持 head/tail/paging) |
| `write_file` | `WriteFileTool` | L5884 | 写入/追加文件 |
| `list_files` | `ListFilesTool` | L5961 | 目录列表 |
| `web` | `SimpleWebCrawlerTool` | L5637 | 网页抓取(标题/正文/链接) |
| `memory` | `MemoryTool` | L5280 | 记忆 CRUD(search/save/delete) |
| `memo` | `MemoTool` | L4753 | 任务 CRUD(add/complete/list/search) |
| `run_command` | `CommandLineTool` | L6150 | 系统命令执行(支持超时) |
| `search_skills` | `SearchSkillsTool` | L4076 | 搜索扩展技能 |
| `reload_skills` | `ReloadSkillsTool` | L4199 | 热重载技能 |
| `create_skill` | `CreateSkillTool` | L4409 | AI 自主创建技能 |
| `install_skill` | `InstallSkillTool` | L4306 | 技能安装指引 |
| `ask_user` | `AskUserTool` | L4275 | 向用户提问 |
| `update_identity` | `UpdateIdentityTool` | L4518 | 更新用户身份 |
| `update_soul` | `UpdateSoulTool` | L4582 | 更新 AI 灵魂 |
| `save_memory_and_end_conversation` | `SaveMemoryAndEndConversationTool` | L4646 | 保存记忆并结束对话 |
| `get_current_time` | `GetCurrentTimeTool` | L6055 | 获取当前时间 |
| `calculator` | `CalculatorTool` | L6095 | 数学计算 |
| `set_proxy` | `SetProxyTool` | L6415 | 设置 SOCKS5 代理 |
| `get_proxy` | `GetProxyTool` | L6453 | 查看代理配置 |
### 4.3 工具执行流程
```
AI 决定调用工具 → 生成 JSON 格式工具调用
↓
DeepSeekChat.process_tool_calls()
↓
┌─── 解析工具名 ───┬─── 解析参数 ───┬─── 注册工具 ───┐
└─────────────────┴───────────────┴─────────────────┘
↓
ToolRegistry.get_tool(name)
↓
tool.execute(**params)
↓
┌─── 结果处理 ───┬─── 错误处理 ───┬─── 长结果截断 ───┐
└─────────────────┴───────────────┴─────────────────┘
↓
返回结果给 AI → AI 继续生成响应
```
---
## 五、WebSocket 协议
### 5.1 客户端 → 服务器
| 类型 | 字段 | 说明 |
|------|------|------|
| `message` | `content` | 普通对话文本 |
| `command` | `command` | 斜杠命令(如 `/new`, `/help`) |
| `task` | `action`, `title`, ... | 任务操作(add/complete/list) |
| `ping` | — | 心跳检测 |
### 5.2 服务器 → 客户端
| 类型 | 字段 | 说明 |
|------|------|------|
| `chunk` | `content` | AI 响应片段(流式) |
| `complete` | `content` | AI 响应完成 |
| `reasoning` | `content` | AI 思考过程 |
| `error` | `content` | 错误信息 |
| `command_result` | `content` | 命令执行结果 |
| `task_status` | `task_id`, `status` | 任务状态更新 |
| `task_process` | `task_id`, `content` | 任务执行过程 |
| `dialogue_ended` | — | 对话结束 |
| `dialogue_reset` | — | 对话重置完成 |
| `info` | `content` | 系统信息 |
| `pong` | — | Ping 响应 |
### 5.3 消息流示例
```
// 普通对话
Client: {type: "message", content: "你好"}
Server: {type: "chunk", content: "你好!"}
Server: {type: "complete", content: "你好!有什么可以帮你的吗?"}
// 工具调用
Server: {type: "tool_call", tool: "web", args: {url: "..."}}
Server: {type: "chunk", content: "网页内容:..."}
Server: {type: "complete", content: "网页已获取"}
// 任务执行
Client: {type: "task", action: "add", title: "提醒", delay: 60}
Server: {type: "command_result", content: "任务已添加"}
// 60 秒后
Server: {type: "task_status", task_id: 1, status: "executing"}
Server: {type: "task_process", task_id: "1", content: "处理中..."}
Server: {type: "task_status", task_id: 1, status: "complete"}
```
---
## 六、任务系统
### 6.1 任务类型
| 类型 | 字段 | 说明 |
|------|------|------|
| 一次性 | `reminder_time` | 指定执行时间 |
| 重复(daily) | `repeat_type: "daily"` | 每天执行 |
| 重复(weekly) | `repeat_type: "weekly"`, `weekday` | 每周指定日 |
| 重复(monthly) | `repeat_type: "monthly"`, `day` | 每月指定日 |
| 重复(custom) | `repeat_type: "custom"`, `interval`, `unit` | 自定义间隔 |
| 即时 | `is_immediate: true` | 立即执行 |
### 6.2 任务调度流程
```
MemoScheduler._run() # 后台线程
↓
检查所有未完成任务
↓
┌─── 到达时间? ───┬─── 未完成? ───┬─── 允许用户任务? ───┐
└─────────────────┴───────────────┴─────────────────┘
↓
创建新的 AI 对话(将任务内容作为用户输入)
↓
AI 处理任务(可能调用多个工具)
↓
标记任务已触发
↓
广播结果到所有客户端
↓
计算下一次执行时间(重复任务)
```
### 6.3 数据库表结构
```sql
-- memories.db
CREATE TABLE memories (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id TEXT,
content TEXT,
importance INTEGER DEFAULT 2,
created_at TEXT,
updated_at TEXT,
embedding BLOB -- 预留向量嵌入
);
-- memos.db
CREATE TABLE memos (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT,
content TEXT,
reminder_time TEXT,
repeat_type TEXT,
repeat_interval INTEGER,
repeat_interval_unit TEXT,
is_immediate INTEGER DEFAULT 0,
status TEXT DEFAULT 'pending',
is_triggered INTEGER DEFAULT 0,
created_at TEXT,
user_id TEXT
);
```
---
## 七、记忆系统
### 7.1 记忆操作
| 操作 | AI 调用 | 说明 |
|------|---------|------|
| 保存 | `memory(action="save", content=...)` | AI 主动保存重要信息 |
| 搜索 | `memory(action="search", query=...)` | 按关键词搜索记忆 |
| 列表 | `memory(action="list", page=...)` | 分页浏览所有记忆 |
| 删除 | `memory(action="delete", id=...)` | 删除指定记忆 |
### 7.2 自动记忆
- AI 在对话结束时(`/exit`)自动保存关键信息
- 可配置 `system.auto_save_memory` 开关
- 重要度(importance)1-5 级
---
## 八、对话压缩
### 8.1 压缩触发条件
| 条件 | 阈值(默认) | 配置项 |
|------|-------------|--------|
| 消息数超过 | 200 条 | `compress_message_threshold` |
| Token 数超过 | 120000 | `compress_token_threshold` |
### 8.2 压缩流程
```
检测到阈值触发
↓
保留最近 8 条消息(compress_keep_recent)
↓
对早期消息按时间衰减加权(weight_decay_factor=0.01)
↓
调用 AI 生成摘要(max_input_chars=6000)
↓
将摘要作为系统消息插入
↓
压缩后继续对话
```
### 8.3 压缩配置
```json
{
"compressor": {
"min_messages_to_compress": 5,
"weight_decay_factor": 0.01,
"max_input_chars": 6000,
"llm_max_tokens": 800
}
}
```
---
## 九、扩展技能系统
### 9.1 技能类型
| 类型 | 格式 | 存储位置 |
|------|------|---------|
| 可执行脚本 | Python/Shell,支持 `--help` | `~/.aibox/skills/` |
| Markdown 技能 | `.md` 文件,首行为简介 | `~/.aibox/skills/` |
### 9.2 内置技能
| 技能名 | 文件 | 功能 |
|--------|------|------|
| hello-world | `skills/hello-world.py` | 示例技能 |
| bing_search | `skills/bing_search.py` | 必应搜索 |
| ssh-exec | `skills/ssh-exec.py` | SSH 远程执行 |
| sftp-transfer | `skills/sftp-transfer.py` | SFTP 文件传输 |
| facerec | `skills/facerec.py` | 人脸识别 |
| aes-crypto | `skills/aes-crypto.py` | AES 加密/解密 |
| gitclient | `skills/gitclient.py` | Git 操作封装 |
### 9.3 技能热重载
```python
# 新技能放置到 ~/.aibox/skills/
# 调用 reload_skills 工具
# AI 下次对话时即可使用
```
### 9.4 技能格式规范
详见 [SKILLS_FORMAT.md](file:///Users/qingsong/micro_servers/aibox/SKILLS_FORMAT.md)
---
## 十、配置参考
### 10.1 完整配置项
#### system 段
| 字段 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `save_dir` | str | `~/.aibox` | 数据存储目录 |
| `datetime_format` | str | `%Y-%m-%d %H:%M:%S` | 日期时间格式 |
| `date_format` | str | `%Y%m%d_%H%M%S` | 文件名日期格式 |
| `debug_level` | int | `1` | 调试级别 0-3 |
| `load_history_by_default` | bool | `true` | 启动时加载历史对话 |
| `log_file` | str | `~/.aibox/ai.log` | 日志文件路径 |
| `log_max_size` | int | `10485760` | 日志最大字节数(10MB) |
| `log_backup_count` | int | `5` | 日志备份数量 |
| `websocket_host` | str | `localhost` | WebSocket 监听地址 |
| `websocket_port` | int | `8765` | WebSocket 监听端口 |
| `skills_dir` | str | `~/.aibox/skills` | 技能目录 |
| `prompts_dir` | str | `~/.aibox/prompts` | 提示词目录 |
#### scheduler 段
| 字段 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `enabled` | bool | `true` | 启用任务调度器 |
| `check_interval` | float | `1.0` | 检查间隔(秒) |
| `max_tasks_per_run` | int | `10` | 单次最大触发任务数 |
| `broadcast_enabled` | bool | `true` | 广播任务执行结果 |
#### api 段
| 字段 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `deepseek_api_url` | str | `https://api.deepseek.com/chat/completions` | API URL |
| `deepseek_model` | str | `deepseek-v4-flash` | 模型名 |
| `api_key_env` | str | `DEEPSEEK_API_KEY` | API Key 环境变量名 |
| `timeout` | int | `30` | 请求超时(秒) |
| `temperature` | float | `0.7` | 生成温度 0-1 |
#### context 段
| 字段 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `max_history_messages` | int | `500` | 历史消息上限 |
| `max_context_messages` | int | `120` | 上下文窗口 |
| `max_tokens` | int | `8192` | 最大生成 Token |
| `max_history_conversations` | int | `5` | 保留历史对话数 |
| `preserve_system_prompts` | bool | `true` | 保留系统提示词 |
| `compress_enabled` | bool | `true` | 启用对话压缩 |
| `compress_message_threshold` | int | `200` | 压缩消息阈值 |
| `compress_token_threshold` | int | `120000` | 压缩 Token 阈值 |
| `compress_ratio` | float | `0.2` | 压缩保留比例 |
| `compress_keep_recent` | int | `8` | 保留最近消息数 |
#### memo 段
| 字段 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `default_reminder_minutes` | int | `60` | 默认提醒分钟数 |
| `max_memos_per_check` | int | `10` | 单次最大触发任务数 |
| `allow_user_tasks` | bool | `true` | 允许用户创建任务 |
#### proxy 段
| 字段 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `type` | str | `""` | 代理类型(socks5/http) |
| `host` | str | `""` | 代理主机 |
| `port` | int | `0` | 代理端口 |
| `username` | str | `""` | 代理用户名 |
| `password` | str | `""` | 代理密码 |
### 10.2 环境变量
| 变量 | 说明 |
|------|------|
| `DEEPSEEK_API_KEY` | DeepSeek API Key(必填) |
| `AI_CONFIG_PATH` | 自定义配置文件路径 |
---
## 十一、命令行参数
```bash
python main.py [选项]
```
| 参数 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| `--verbose`, `-v` | flag | `false` | 显示详细日志 |
| `--debug` | int | `None` | 调试级别 0-3 |
| `--lang` | str | `None` | 强制语言 zh/en |
| `--host` | str | `None` | 覆盖 WebSocket 主机 |
| `--port` | int | `None` | 覆盖 WebSocket 端口 |
| `--no-scheduler` | flag | `false` | 禁用任务调度器 |
| `--scheduler-interval` | float | `1.0` | 调度器检查间隔 |
| `--config`, `-c` | str | `None` | 指定配置文件 |
| `--check-memo` | flag | `false` | 检查到期任务并退出 |
| `--memo-add` | str | `None` | 添加任务(CLI) |
| `--memo-list` | flag | `false` | 列出任务 |
| `--memo-complete` | int | `None` | 完成指定任务 |
### 使用示例
```bash
# 启动服务器
python main.py
# 详细日志 + 指定端口
python main.py --verbose --port 9000
# 检查并执行到期任务(cron 场景)
python main.py --check-memo
# 添加任务
python main.py --memo-add "每日备份|执行数据库备份|+1h"
# 列出所有任务
python main.py --memo-list
```
---
## 十二、前端集成
### 12.1 前端文件
| 文件 | 说明 |
|------|------|
| `entry.html` | 简单入口页(重定向到 frontend.html) |
| `frontend.html` | 完整前端界面(对话/任务/设置) |
### 12.2 WebSocket 连接
```javascript
const ws = new WebSocket('ws://localhost:8765');
// 发送消息
ws.send(JSON.stringify({
type: 'message',
content: '你好'
}));
// 接收消息
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
switch (data.type) {
case 'chunk':
appendToChat(data.content);
break;
case 'complete':
finishResponse();
break;
case 'error':
showError(data.content);
break;
}
};
```
---
## 十三、部署
### 13.1 目录结构
```
/opt/micro_servers/aibox/
├── main.py
├── client.py
├── skills/
├── _run.sh # core 拉起入口
├── _set.sh # 首次安装
└── requirements.txt
```
### 13.2 core init.txt 配置
```bash
# aibox 由 core 拉起
bash /opt/micro_servers/aibox/_run.sh
```
### 13.3 systemd 单元(可选)
```ini
[Unit]
Description=AIBOX AI Agent
After=network.target
[Service]
Type=simple
User=deploy
WorkingDirectory=/opt/micro_servers/aibox
ExecStart=/usr/bin/python3 main.py
Restart=always
RestartSec=5
Environment=DEEPSEEK_API_KEY=xxx
[Install]
WantedBy=multi-user.target
```
---
## 十四、已知限制
| ID | 描述 | 严重等级 |
|----|------|---------|
| L1 | 单实例运行,不支持多进程 | 🟡 Medium |
| L2 | 记忆库无向量搜索,仅关键词匹配 | 🟡 Medium |
| L3 | 对话压缩可能丢失早期重要信息 | 🟢 Low |
| L4 | 工具执行无超时控制(部分工具) | 🟡 Medium |
| L5 | 日志无远程推送,仅本地文件 | 🟢 Low |
| L6 | 无用户认证,WebSocket 连接可被滥用 | 🔴 High |
| L7 | API Key 以明文存储在环境变量或文件中 | 🟡 Medium |
### L6 详细说明
WebSocket 服务器**无任何认证**,任何能连接到 `ws://localhost:8765` 的客户端都可以:
- 与 AI 对话(消耗 API 额度)
- 执行系统命令(`run_command`)
- 操作文件系统(`read_file`/`write_file`)
- 创建/删除任务
**建议**:
1. 仅绑定 `localhost`(默认配置已这样做)
2. 添加 API Token 认证
3. 限制 `run_command` 可执行的命令范围
---
## 十五、开发指南
### 15.1 添加新工具
1. 继承 `Tool` 基类
2. 实现 `get_name()`、`get_description()`、`get_parameters()`、`execute()`
3. 在 `DeepSeekChat._register_tools()` 中注册
4. 更新 README 文档
```python
class MyTool(Tool):
def get_name(self) -> str:
return "my_tool"
def get_description(self) -> str:
return "我的自定义工具"
def get_parameters(self) -> Dict:
return {
"type": "object",
"properties": {
"param": {"type": "string", "description": "参数说明"}
},
"required": ["param"]
}
def execute(self, param: str, **kwargs) -> str:
return f"执行结果: {param}"
```
### 15.2 添加新命令
在 `WebSocketHandler.handle_command()` 中添加:
```python
elif command == "/mycommand":
result = "命令执行结果"
await self.send_message(websocket, {
"type": "command_result",
"content": result
})
```
### 15.3 添加配置项
1. 在 `ConfigManager._get_default_config()` 中添加默认值
2. 在 README 文档中添加说明
3. 确保 `_merge_configs()` 能正确处理新增字段
---
## 十六、故障排查
### 16.1 常见问题
| 问题 | 排查步骤 |
|------|---------|
| API Key 无效 | 检查 `echo $DEEPSEEK_API_KEY` 或 `~/.cert/deepseek.key` |
| 技能不生效 | 确认技能文件在 `~/.aibox/skills/` 且有执行权限 |
| 任务不执行 | 检查调度器状态:`/tasks`;检查时间设置 |
| 对话压缩丢失信息 | 调整 `compress_message_threshold` 和 `compress_keep_recent` |
| WebSocket 断开 | 检查服务器进程:`ps aux | grep main.py`;查看日志 |
### 16.2 日志位置
```bash
# 查看实时日志
tail -f ~/.aibox/ai.log
# 查看最近错误
grep "ERROR" ~/.aibox/ai.log | tail -20
```
### 16.3 数据库检查
```bash
# 检查记忆库
sqlite3 ~/.aibox/memories.db "SELECT * FROM memories LIMIT 10;"
# 检查任务库
sqlite3 ~/.aibox/memos.db "SELECT * FROM memos WHERE status='pending';"
```
---
## 十七、版本历史
| 版本 | 日期 | 主要变更 |
|------|------|---------|
| v2.0 | 当前 | 新增 `ask_user`、`update_soul`、`create_skill`、`install_skill`、`set_proxy`/`get_proxy` 等工具;支持 Markdown 技能;对话压缩支持 Token 阈值 |
| v1.0 | — | 初始版本:WebSocket 服务器、文件/网页/记忆操作、任务调度、扩展技能、多语言 |
---
## 附录
### A. 配置文件示例
完整默认配置见 [main.py:1100-1180](file:///Users/qingsong/micro_servers/aibox/main.py#L1100-L1180)
### B. 数据库 Schema
完整建表语句见:
- 记忆库:[main.py](file:///Users/qingsong/micro_servers/aibox/main.py) 中 `MemoryManager._init_db()`
- 任务库:[main.py](file:///Users/qingsong/micro_servers/aibox/main.py) 中 `MemoScheduler._init_db()`
### C. 参考文档
- [DeepSeek API 文档](https://platform.deepseek.com/docs)
- [websockets 库文档](https://websockets.readthedocs.io/)
- [SKILLS_FORMAT.md](file:///Users/qingsong/micro_servers/aibox/SKILLS_FORMAT.md)
---
*本文档由代码审查工具生成,供开发团队参考。*
+381
View File
@@ -0,0 +1,381 @@
# Core 模块代码审查日志
> 审查日期:2026-07-30 14:30:00 (Asia/Shanghai)
> 审查人:Automated Review
> 审查范围:`core/` 全模块(C++20,PMC/Nidus 进程托管服务)
---
## 一、模块概述
| 属性 | 值 |
|------|-----|
| 定位 | 微服务进程托管控制器(Process Machine Controller) |
| 语言 | C++20 |
| 构建 | Makefile(GCC)+ systemd 部署 |
| 入口 | `nidus.out` |
| 端口 | HTTP API: 5200(仅本机 `127.0.0.1`) |
| 启动列表 | `init.txt`(9 个子服务) |
### 依赖项
| 库 | 用途 | 版本要求 |
|----|------|---------|
| Boost.Process | 子进程创建/管理 | — |
| Boost.JSON | JSON 解析与序列化 | — |
| Boost.Interprocess | POSIX 消息队列 | — |
| Boost.Program.Options | CLI 参数解析 | — |
| UniServe_libth | 自定义线程/HTTP 服务器封装 | 自研 |
| OpenSSL | TLS(HTTP 服务器证书) | — |
### 目录结构
```
core/
├── include/
│ ├── cn/ # 中文代码库(自研)
│ │ ├── 中文化.hpp # 保留字映射 + 中文字符串/IO 类
│ │ ├── 链串.hpp # 分块链表
│ │ ├── 顺序表.hpp # 数组封装
│ │ ├── 向量.hpp # 动态数组
│ │ └── 异常.hpp # 异常层次
│ ├── logs/
│ │ └── Logger.hpp # 单例日志(控制台输出)
│ ├── main/
│ │ ├── HttpTask.hh # HTTP 服务器任务
│ │ ├── PPool.hh # 进程池
│ │ ├── ProcessTask.hh # 子进程任务
│ │ ├── Tttask.hh # 通信任务(消息队列监听)
│ │ ├── pmc_mtd.hh # 进程管理方法集
│ │ └── subsys_call.hh # 子系统调用封装
│ └── tt/
│ ├── Mq.h # POSIX 消息队列封装
│ └── Pipe.h # FIFO 管道封装
├── src/
│ ├── main.cpp # 主入口
│ └── main/ # 实现文件
├── init.txt # 自启动列表
├── Makefile # 构建脚本
├── Nidus.service # systemd 单元
├── README.md
├── _set.sh # 编译+安装
└── _run.sh # 运行脚本
```
---
## 二、审查发现
### 🔴 严重问题 (P0)
#### [CORE-S01] 命令注入漏洞
- **文件**: `src/main/HttpTask.cpp:230-247`
- **时间**: 2026-07-30 14:32:15
- **描述**: `exec` 路由直接将 URL 查询参数 `command` 传递给 `boost::process::child`,无任何过滤或校验。攻击者可构造恶意请求执行任意系统命令。
```cpp
// 危险代码
std::string result = execCommand(command, target);
// command 直接来自 HTTP 请求参数
```
- **风险**: 远程代码执行(RCE)
- **严重等级**: 🔴 Critical
- **修复建议**:
1. 实现命令白名单机制
2. 使用 `execvp` 而非 `system`/shell 模式
3. 限制可执行命令的路径范围
4. 添加参数长度和字符集校验
#### [CORE-S02] HTTP API 无任何鉴权
- **文件**: `src/main/HttpTask.cpp:130-340`
- **时间**: 2026-07-30 14:33:00
- **描述**: HTTP API (`/list`, `/exec`, `/kill`) 完全开放,无认证、无授权、无访问控制。任何人(本地)都可以:
- 列出所有子进程
- 启动任意进程
- 终止任意进程
- **风险**: 权限绕过、未授权操作
- **严重等级**: 🔴 Critical
- **修复建议**:
1. 添加 API Token 认证(请求头 `Authorization: Bearer <token>`)
2. 实现 IP 白名单(仅允许 `127.0.0.1`)
3. 添加请求速率限制
4. 记录所有管理操作审计日志
#### [CORE-S03] ProcessTask 空指针解引用
- **文件**: `include/main/ProcessTask.hh:19,36`
- **时间**: 2026-07-30 14:35:22
- **描述**: `isRunning()` 和 `pid()` 方法未检查 `p` 是否为空。如果在 `start()` 之前调用这些方法,会导致空指针解引用(段错误)。
```cpp
bool isRunning() override { return p && p->running(); } // 应加 p 非空检查
pid_t pid() { return p->id(); } // 危险!p 可能为 nullptr
```
- **风险**: 程序崩溃
- **严重等级**: 🔴 Critical
- **修复建议**:
```cpp
bool isRunning() override { return p != nullptr && p->running(); }
pid_t pid() { return p ? p->id() : -1; }
```
#### [CORE-S04] exec 路由路径穿越
- **文件**: `src/main/HttpTask.cpp:232-236`
- **时间**: 2026-07-30 14:37:10
- **描述**: `getExecCommand` 函数从 URL 参数提取命令,允许包含 `..` 路径穿越符,可能执行预期外的脚本。
- **风险**: 路径遍历
- **严重等级**: 🔴 High
- **修复建议**:
- 白名单校验,仅允许执行 `init.txt` 中列出的命令
- 规范化路径后检查是否在允许目录内
### 🟡 中等问题 (P1)
#### [CORE-S05] 进程池无线程安全保护
- **文件**: `src/main/PPool.cpp`
- **时间**: 2026-07-30 14:40:05
- **描述**: `PPool` 类使用 `std::list<std::shared_ptr<ProcessTask>>` 作为内部存储,但无互斥锁保护。`HttpTask` 和 `Tttask` 可能并发调用 `crtp()`、`kill()`、`clr()` 等方法,导致数据竞争。
- **风险**: 数据竞争、未定义行为
- **严重等级**: 🟡 Medium
- **修复建议**: 添加 `std::mutex` 保护所有公共方法
#### [CORE-S06] 消息队列命名冲突
- **文件**: `src/main/Tttask.cpp:41-42`
- **时间**: 2026-07-30 14:42:30
- **描述**: 使用 `getpid()` 作为消息队列名。同一台机器上启动多个 Nidus 实例时,可能发生队列名冲突。
```cpp
name = std::to_string(getpid()); // 仅用 PID,无唯一化
```
- **风险**: 多实例冲突
- **严重等级**: 🟡 Medium
- **修复建议**: 使用 `PID + 时间戳` 或 `PID + 随机后缀` 组合命名
#### [CORE-S07] 管道文件残留
- **文件**: `src/main/subsys_call.cpp`
- **时间**: 2026-07-30 14:44:18
- **描述**: 在异常路径中,`Mq` 和 `Pipe` 对象的析构器可能未正确执行(依赖栈展开),导致 `/tmp/pst-*` 文件残留,长期运行会占满 `/tmp` 空间。
- **风险**: 磁盘空间耗尽
- **严重等级**: 🟡 Medium
- **修复建议**:
1. 在 `catch` 块中显式清理管道文件
2. 添加定期清理脚本
3. 使用 `std::filesystem::remove` 在构造器前清理旧文件
#### [CORE-S08] kill/exec IPC 路径已废弃
- **文件**: `src/main.cpp:282-296`
- **时间**: 2026-07-30 14:46:00
- **描述**: `parse_data_from_mq()` 中 `kill` 和 `exec` 的 IPC 处理路径被注释,仅 `list` 可用。HTTP 路由 `/exec` 和 `/kill` 虽然存在,但远端调用时会失败。
```cpp
// 已注释的代码
//else if (typ == "kill") {
// int trg = jv.at("kill").as_int64();
// auto flag = pmc_mtd::kill(trg);
// ...
//}
```
- **风险**: 功能缺失
- **严重等级**: 🟡 Medium
- **修复建议**: 恢复被注释的代码,或在文档中明确说明限制
#### [CORE-S09] PPool::clr() 边界条件错误
- **文件**: `src/main/PPool.cpp:20-32`
- **时间**: 2026-07-30 14:48:22
- **描述**: `clr()` 方法从尾部反向遍历删除已终止的进程,但边界条件处理复杂:
- 当 `pool.size() == 1` 时,循环条件 `it != pool.begin()` 直接跳过
- 最后的 `if (pool.size() > 0 && !(*it)->isRunning())` 可能导致双删或漏删
```cpp
auto it = --pool.end();
for (; it != pool.begin(); --it)
if (!(*it)->isRunning()) { /* erase */ }
if (pool.size() > 0 && !(*it)->isRunning()) // 可能越界
pool.erase(it);
```
- **风险**: 迭代器失效、崩溃
- **严重等级**: 🟡 Medium
- **修复建议**: 简化为正向前遍历,或使用 `std::list::remove_if`
#### [CORE-S10] 日志仅输出到控制台
- **文件**: `include/logs/Logger.hpp`
- **时间**: 2026-07-30 14:50:00
- **描述**: `Logger` 类仅支持控制台输出,日志不持久化。服务重启后日志丢失,无法进行事后审计或问题追溯。
- **风险**: 可观测性缺失
- **严重等级**: 🟡 Medium
- **修复建议**:
1. 实现日志文件轮转
2. 添加远程日志推送(syslog、Elasticsearch)
3. 集成到微服务体系的集中日志(db:8005 Logs 表)
### 🟢 轻微问题 (P2)
#### [CORE-Q01] 版本号不一致
- **文件**: `src/main.cpp:42` vs `Makefile:1`
- **时间**: 2026-07-30 14:52:10
- **描述**: `PMC_VERSION = "0.0.10"` 与 `Makefile` 中 `VERSION := 0.0.4` 不一致。
- **严重等级**: 🟢 Low
- **修复建议**: 统一版本管理,或在 Makefile 中定义并编译时注入
#### [CORE-Q02] parse_data_from_mq 语法存疑
- **文件**: `src/main.cpp:267-304`
- **时间**: 2026-07-30 14:53:45
- **描述**: 函数声明缺少 `try` 关键字(`void parse_data_from_mq(std::string& json_str) try {`),但代码能编译通过,可能使用了 C++11 的 `function-try-block` 语法。代码可读性较差,建议显式标注。
- **严重等级**: 🟢 Low
- **修复建议**: 添加注释说明使用的是函数 try-catch 语法
#### [CORE-Q03] PPool::kill 参数类型不匹配
- **文件**: `include/main/PPool.hh:35` vs `src/main/pmc_mtd.cpp:53`
- **时间**: 2026-07-30 14:55:00
- **描述**: `PPool::kill(int idx)` 接受下标参数,但 `pmc_mtd::kill(pid)` 传入的是 PID。需要遍历查找,O(n) 复杂度。
- **严重等级**: 🟢 Low
- **修复建议**:
1. 改为 `killByPid(pid_t pid)`
2. 或维护 PID → 迭代器 的映射表
#### [CORE-Q04] 缺少配置加载能力
- **文件**: 全局
- **时间**: 2026-07-30 14:56:30
- **描述**: 当前无 `config.yaml` 或类似配置文件支持,所有参数硬编码:
- HTTP 端口 5200
- 消息队列大小 2048/100
- 证书路径 `server.crt/server.key`
- **严重等级**: 🟢 Low
- **修复建议**: 引入 `config.yaml` 支持,至少分离端口和证书路径配置
#### [CORE-Q05] 中文代码库未被实际使用
- **文件**: `include/cn/`
- **时间**: 2026-07-30 14:58:00
- **描述**: `中文化.hpp`、`链串.hpp`、`顺序表.hpp`、`向量.hpp`、`异常.hpp` 构成了一套完整的中文 API,但在 `src/` 代码中未被引用。这些文件更像是演示项目,而非生产代码。
- **严重等级**: 🟢 Low
- **修复建议**:
1. 删除未使用的中文代码库
2. 或在 README 中明确标注为"实验性功能"
3. 迁移到独立仓库
#### [CORE-Q06] 缺少单元测试
- **文件**: 全局
- **时间**: 2026-07-30 14:59:30
- **描述**: 项目无任何测试代码。核心功能(进程管理、消息队列、管道通信)完全依赖手工测试。
- **严重等级**: 🟢 Low
- **修复建议**: 引入 Google Test,覆盖:
- PPool 的增删查改
- Pipe 的读写超时
- Mq 的发送接收
- 中文代码库的基础功能
---
## 三、统计汇总
| 等级 | 数量 | 问题 ID |
|------|------|---------|
| 🔴 Critical | 4 | S01, S02, S03, S04 |
| 🟡 Medium | 6 | S05, S06, S07, S08, S09, S10 |
| 🟢 Low | 6 | Q01, Q02, Q03, Q04, Q05, Q06 |
| **合计** | **16** | |
### 按类别统计
| 类别 | 问题数 | 占比 |
|------|--------|------|
| 安全(注入、鉴权) | 3 | 19% |
| 稳定性(崩溃、竞态) | 4 | 25% |
| 功能缺失 | 2 | 13% |
| 代码质量 | 5 | 31% |
| 可观测性 | 2 | 12% |
---
## 四、修复优先级建议
### 第一阶段(紧急,预计 1 天)
1. **CORE-S03**: 修复 `ProcessTask` 空指针检查(最简单,影响启动)
2. **CORE-S01**: 实现命令白名单 + 路径校验(核心安全)
3. **CORE-S02**: 添加 API Token 鉴权(核心安全)
### 第二阶段(重要,预计 3 天)
4. **CORE-S05**: 添加 PPool 互斥锁(并发安全)
5. **CORE-S08**: 恢复 kill/exec IPC 路径(功能补全)
6. **CORE-S09**: 简化 PPool::clr() 实现(稳定性)
7. **CORE-S10**: 添加日志文件持久化(可观测性)
### 第三阶段(优化,预计 1 周)
8. **CORE-S04**: 完善路径穿越防护
9. **CORE-S06**: 改进消息队列命名
10. **CORE-S07**: 添加管道清理机制
11. **CORE-Q01 ~ Q06**: 代码质量优化
---
## 五、亮点
尽管问题较多,项目仍有值得肯定之处:
| 亮点 | 说明 |
|------|------|
| 清晰的模块划分 | 进程池、线程、通信、HTTP 各司其职 |
| 双轨 IPC 设计 | 消息队列 + 管道的请求-响应模式简洁高效 |
| 中文代码实验 | 对中文编程的探索有教育意义 |
| systemd 集成 | 标准部署方式,便于运维 |
| 最小依赖 | 仅依赖 Boost,无过度设计 |
---
## 六、附录
### 审查工具
- 人工静态分析
- grep 模式匹配
- 代码路径追踪
### 参考文档
- [Boost.Process 文档](https://www.boost.org/doc/libs/release/libs/process/)
- [Boost.Interprocess 文档](https://www.boost.org/doc/libs/release/libs/interprocess/)
- [POSIX 消息队列](https://man7.org/linux/man-pages/man7/mq_overview.7.html)
- [FIFO 管道](https://man7.org/linux/man-pages/man7/fifo.7.html)
### 审查人
- 自动化审查助手
- 审查时间:2026-07-30
---
*本日志由自动化代码审查工具生成,供开发团队参考。修复建议需结合实际场景评估。*
+10 -2
View File
@@ -288,11 +288,19 @@ def whoami():
if info is None:
return jsonify({'login': False}), 200
return jsonify({
resp = {
'login': True,
'user': info.get('user'),
'is_admin': bool(info.get('is_admin'))
}), 200
}
# 附加 Token 生命周期信息,供前端 Token 管理器估算过期时间
if info.get('iat'):
resp['iat'] = info['iat']
if info.get('created_at'):
resp['created_at'] = info['created_at']
if info.get('keep_time'):
resp['keep_time'] = info['keep_time']
return jsonify(resp), 200
# eg: {"id": 1}
+199 -2
View File
@@ -1,2 +1,199 @@
# UniServe_libcrypt
UniServe Framework Cryption Library
# libcrypt — UniServe 加密库
C++20 轻量级加密库,基于 OpenSSL 3.x EVP API 封装。提供 AES-256-CBC 加解密、Base64 编解码、SHA-256 哈希、RSA 密钥管理与加解密/签名验证。
命名空间 `qing`,库文件 `libus_crypt.so`,头文件安装至 `/usr/include/us/`。被 `gateway` 的 Token 签发/验证和密码哈希直接依赖。
## 架构
```
qing::Crypto_Basic (基类: bytes_to_hex / hex_to_bytes / CRYPTO_CHECK)
├── AES_CBC AES-256-CBC 对称加密(随机密钥+IV / 外部密钥)
├── Base64 Base64 编解码(OpenSSL BIO 链)
├── SHA256 SHA-256 哈希与验证
└── MyRSA RSA 非对称密钥
├── Generator 密钥对生成(2048-bit,PKCS#8 PEM 输出)
├── Private_Key 私钥加载、OAEP 解密、PKCS1_v1_5/PSS 签名
└── Public_Key 公钥加载、OAEP 加密、PKCS1_v1_5/PSS 验签
```
所有加密类均使用 OpenSSL 3.x EVP 高层 API。`bytes_to_hex` / `hex_to_bytes` 在 `Crypto_Basic` 中实现(查表法),编译进 `libus_crypt.so`。加解密类为纯头文件(header-only),需链接时额外加 `-lssl -lcrypto`。
## 类与 API
### Crypto_Basic — 基类
提供两个静态工具方法和异常类型,不直接实例化。
| 成员 | 说明 |
|------|------|
| `bytes_to_hex(data)` | 字节向量 → 十六进制字符串(查表法) |
| `hex_to_bytes(hex)` | 十六进制字符串 → 字节向量(大小写兼容) |
| `CryptoExp` | 异常类,继承 `std::runtime_error` |
| `CRYPTO_CHECK(cond, msg)` | 宏,条件为 false 时抛出 `std::runtime_error` |
### AES_CBC — AES-256-CBC 对称加密
```cpp
AES_CBC aes; // 自动生成随机 32B 密钥 + 16B IV
AES_CBC aes(key_vec, iv_vec); // 使用外部密钥和 IV
std::string hex = aes.encrypt(text); // 加密,返回 hex 密文
std::string raw = aes.decrypt(hex); // 解密 hex 密文,返回原文
std::string k = aes.get_key(); // 获取密钥(hex)
std::string v = aes.get_iv(); // 获取 IV(hex)
```
加密输出为 hex 编码的密文,非原始字节。解密接受 hex 输入,返回原始字符串。
### Base64 — Base64 编解码
提供实例方法和静态工具方法两种调用方式。
| 方法 | 说明 |
|------|------|
| `encode(str)` / `encode(vec)` | 编码为 Base64 字符串 |
| `decode_to_string(b64)` | 解码为原始字符串 |
| `decode_to_bytes(b64)` | 解码为字节向量 |
| `base64_encode(str)` (static) | 静态编码工具 |
| `base64_decode_to_string(b64)` (static) | 静态解码工具 |
| `is_valid_base64(str)` (static) | 验证字符集合法性(不依赖 OpenSSL) |
编码不添加换行符(`BIO_FLAGS_BASE64_NO_NL`)。
### SHA256 — SHA-256 哈希
```cpp
SHA256 h;
std::string hex = h.hash("hello"); // 字符串 → hex 哈希
auto bytes = h.hash_bytes("hello"); // 字符串 → 字节哈希
bool ok = h.verify("hello", expected_hex); // 验证
// 静态快捷方式
std::string h = SHA256::sha256("hello");
```
### MyRSA — RSA 非对称密钥
**Generator** — 生成 2048-bit RSA 密钥对(OpenSSL 3.x `OSSL_PARAM` API)。
```cpp
MyRSA::Generator gen;
gen.save_pri_key("private.pem", "password"); // PKCS#8 + AES-256-CBC 加密
gen.save_pub_key("public.pem"); // SubjectPublicKeyInfo
std::string pem = gen.get_public_key_pem(); // 导出 PEM 字符串
```
**Private_Key** — 加载私钥(支持 PKCS#1/PKCS#8,加密或明文)。
```cpp
auto pri = MyRSA::Private_Key("private.pem", true, "password");
std::string raw = pri.Decrypt(cipher_hex); // OAEP 解密
std::string sig = pri.Sign(msg); // PSS 签名(默认)
std::string sig = pri.Sign(msg, RSASignaturePadding::PKCS1_v1_5);
```
> 私有继承自 `Crypto_Basic`,内部使用其工具方法,外部不可将 `Private_Key*` 转为 `Crypto_Basic*`。
**Public_Key** — 加载公钥。
```cpp
auto pub = MyRSA::Public_Key("public.pem");
std::string hex = pub.Encrypt(text); // OAEP 加密
bool ok = pub.Verify(msg, sig_hex); // 验签(默认 PSS)
bool ok = pub.Verify(msg, sig_hex, RSASignaturePadding::PKCS1_v1_5);
```
**签名填充模式**(`RSASignaturePadding` 枚举):
| 值 | 说明 |
|----|------|
| `PSS` | 概率签名方案(默认,更安全) |
| `PKCS1_v1_5` | 传统填充(兼容旧系统) |
## 构建与安装
**前置依赖**:OpenSSL 3.x(开发包 `libssl-dev`),g++ C++20。
```bash
# 一键构建 + 安装(需 sudo)
bash _set.sh
# 或手动
make # 编译 libus_crypt.so
make install # 安装头文件到 /usr/include/us/ + .so 到 /lib/
make clear # 清理构建产物
make uninstall # 卸载
```
使用:
```cpp
#include <us/Crypto_Basic.hpp>
#include <us/AES_CBC.hpp>
#include <us/Base64.hpp>
#include <us/SHA256.hpp>
#include <us/MyRSA.hpp>
```
链接(OpenSSL 符号在 header-only 类中,需显式链接):
```bash
g++ -std=c++20 ... -lus_crypt -lssl -lcrypto
```
## 使用示例
```cpp
#include <us/SHA256.hpp>
#include <us/Base64.hpp>
#include <us/AES_CBC.hpp>
#include <iostream>
int main() {
// SHA-256
std::string hash = qing::SHA256::sha256("hello world");
std::cout << "SHA256: " << hash << std::endl;
// Base64
std::string b64 = qing::Base64::base64_encode("hello");
std::cout << "Base64: " << b64 << std::endl;
// AES-256-CBC
qing::AES_CBC aes;
std::string cipher = aes.encrypt("secret message");
std::string plain = aes.decrypt(cipher);
std::cout << "AES decrypt: " << plain << std::endl;
return 0;
}
```
## 项目结构
```
libcrypt/
├── inc/us/
│ ├── Crypto_Basic.hpp # 基类:hex 转换 + 异常
│ ├── AES_CBC.hpp # AES-256-CBC 加解密(header-only)
│ ├── Base64.hpp # Base64 编解码(header-only)
│ ├── SHA256.hpp # SHA-256 哈希(header-only)
│ └── MyRSA.hpp # RSA 密钥/加解密/签名(header-only)
├── src/
│ └── Crypto_Basic.cpp # bytes_to_hex / hex_to_bytes 实现
├── Makefile
├── _set.sh
└── README.md
```
## 注意事项
- **OpenSSL 版本**:MyRSA 的 `Generator` 使用了 `OSSL_PARAM`、`EVP_PKEY_CTX_new_from_name` 等 OpenSSL 3.x 独占 API,无法在 OpenSSL 1.1.x 下编译。AES/Base64/SHA256 兼容 1.1.x。
- **链接要求**:`libus_crypt.so` 仅含 `bytes_to_hex` 和 `hex_to_bytes`(无 OpenSSL 调用)。所有加密类为 header-only,链接时必须添加 `-lssl -lcrypto`。
- **RSA 密钥长度**:默认 2048-bit,可在 `generate_rsa_key(size_t key_bits)` 中调整。
- **AES 密钥和 IV**:默认构造函数随机生成,每次创建 `AES_CBC` 对象产生不同的密钥对。需要长期存储密钥时使用参数化构造函数传入外部密钥。
- **hex 编码**:`bytes_to_hex` 使用查表法实现,位于 `Crypto_Basic.cpp` 中编译进 `.so`。`SHA256` 不再有私有副本。
- **`Private_Key` 私有继承**:该设计防止 `Private_Key` 被隐式转换为 `Crypto_Basic*`,是刻意为之的安全措施。
## 许可证
MIT License
+1
View File
@@ -2,6 +2,7 @@
#include <openssl/evp.h>
#include <openssl/err.h>
#include <openssl/rand.h>
#include <iomanip>
#include <iostream>
#include <vector>
#include <stdexcept>
+1 -13
View File
@@ -5,9 +5,7 @@
#include <vector>
#include <string>
#include <stdexcept>
#include <iomanip>
#include <sstream>
#include "us/Crypto_Basic.hpp"
#include "Crypto_Basic.hpp"
namespace qing {
@@ -99,15 +97,5 @@ namespace qing {
return digest;
}
/* 辅助函数:字节向量转十六进制字符串 */
static std::string bytes_to_hex(const std::vector<unsigned char>& bytes) {
std::stringstream ss;
ss << std::hex << std::setfill('0');
for (unsigned char byte : bytes) {
ss << std::setw(2) << static_cast<int>(byte);
}
return ss.str();
}
};
}
+194 -9
View File
@@ -1,14 +1,199 @@
# UniServe_libth
UniServe Framework Thread Library.
# libth — UniServe 线程库
# Dependancy Chain
It should be C++17.
C++20 轻量级多线程控制库,为 UniServe Framework 提供线程抽象与状态机驱动的执行模型。由 `core`(PMC 进程管理器)的 `HttpTask` 和 `ProcessTask` 直接依赖。
# Install Method
Use UniServe Generate Installation Script.
命名空间 `qing`,库文件 `libus_th.so`,头文件安装至 `/usr/include/us/`。
``` bash
bash _set.sh
## 架构
```
┌──────────┐ ┌───────────────────────────────┐
│ ITask │ │ Thread │
│ (接口) │ │ 持有 Fsm + std::thread │
│ start() │ │ │
│ stop() │ │ set_stop / set_wake / │
│ isRunning│ │ set_loop / set_clear │
└────┬─────┘ │ Activate / WaitStart / │
│ 实现 │ WaitClose │
▼ │ │
HttpTask │ main(): "回字形" 执行循环 │
ProcessTask └──────────────┬────────────────┘
(位于 core) │ 持有
┌─────────────────┼─────────────────┐
▼ ▼ ▼
┌──────────┐ ┌────────────┐ ┌──────────┐
│ Fsm │ │ Cv_wait │ │ Thread │
│ 状态机 │ │ 条件等待 │ │ .cpp │
│ SHUT/STOP│ │ Wait() │ │ 实现文件 │
│ START/ │ │ WakeCv() │ │ │
│ RUNNING │ │ │ │ │
└──────────┘ └────────────┘ └──────────┘
```
# ENJOY!
## 类与 API
### ITask — 任务接口
纯虚基类,定义可运行任务的契约。下游模块(core 中的 HttpTask、ProcessTask)继承实现。
```cpp
class ITask {
virtual void start() = 0; // 启动任务
virtual void stop() = 0; // 停止任务
virtual bool isRunning() = 0; // 查询运行状态
};
```
### Fsm — 线程安全有限状态机
4 个状态:`SHUT(-1)` → `STOP(0)` → `START(1)` → `RUNNING(2)`。Thread 通过 Fsm 驱动其执行流程。
| 方法 | 说明 |
|------|------|
| `check()` | 线程安全地读取当前状态 |
| `set(Stat)` | 加锁设置状态 + 唤醒阻塞线程 |
| `set_without_lock(Stat)` | 不加锁设置(用于临界区内) |
| `set_without_notify(Stat)` | 加锁但不唤醒条件变量 |
| `suspend()` | 阻塞当前线程直至状态变更 |
初始状态为 `STOP`。
### Thread — 线程抽象
基于回调组合(composition over inheritance),持有 `Fsm` 和 `std::thread`。执行模型为"回字形"循环:
```
while (状态 != SHUT) {
① STOP: 循环调用 StopEvent 回调(空闲轮询)
② START: 单次调用 WakeEvent 回调(启动)
③ RUNNING:循环调用 LoopEvent 回调(工作循环)
④ 退出后: 调用 ClearEvent 回调(清理)
}
异常捕获:loop 内抛出 std::exception 自动回退到 STOP,进程不崩溃。
```
**生命周期:两阶段构造。** 构造函数只初始化成员,`Activate()` 才真正创建 `std::thread`——避免线程在对象构造完成前启动的竞态条件。
| 方法 | 说明 |
|------|------|
| `set_stop(f_t)` | 设置 STOP 状态回调 |
| `set_wake(f_t)` | 设置 START 状态回调 |
| `set_loop(f_t)` | 设置 RUNNING 状态回调 |
| `set_clear(f_t)` | 设置清理回调 |
| `Activate()` | 创建 std::thread 并启动 main() |
| `WaitStart()` | 唤醒线程并阻塞至其进入 RUNNING |
| `WaitClose()` | 关闭线程并 join 等待退出 |
| `wake()` | 状态 → START(不可持有锁,否则死锁) |
| `stop()` | 状态 → STOP |
| `shut()` | 状态 → SHUT |
| `run()` | 状态 → RUNNING |
| `check()` | 查询当前状态 |
| `suspend()` | 阻塞等待状态变更 |
回调类型:`using f_t = std::function<void(Thread&)>;`
### Cv_wait — 条件变量等待工具
简单的单次等待-唤醒工具,用于线程间同步。
| 方法 | 说明 |
|------|------|
| `Wait()` | 阻塞当前线程至 WakeCv() 被调用 |
| `WakeCv()` | 唤醒所有等待线程 |
> **已知限制**:仅支持单次等待-唤醒周期,不可复用。缺少超时机制,可能永久阻塞。代码中标注建议改用 `std::future`/`std::promise`。core 的 `main.cpp` 使用此类。
## 构建与安装
```bash
# 一键构建 + 安装(需 sudo)
bash _set.sh
# 或手动执行
make # 编译 libus_th.so
make install # 安装头文件到 /usr/include/us/ + .so 到 /lib/
make uninstall # 卸载
make clear # 清理构建产物
```
安装后使用:
```cpp
#include <us/Thread.hpp>
#include <us/Fsm.hpp>
#include <us/ITask.hpp>
#include <us/Cv_wait.hpp>
```
链接:`g++ -std=c++20 ... -lus_th`
## 使用示例
```cpp
#include <us/Thread.hpp>
#include <iostream>
int main() {
qing::Thread worker;
bool done = false;
// STOP 状态回调:空闲等待
worker.set_stop([&](qing::Thread& t) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
});
// START 状态回调:一次性初始化
worker.set_wake([&](qing::Thread& t) {
std::cout << "worker started" << std::endl;
t.run(); // 进入 RUNNING
});
// RUNNING 状态回调:工作循环
worker.set_loop([&](qing::Thread& t) {
std::cout << "working..." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
done = true;
t.stop(); // 回到 STOP
});
// 清理回调
worker.set_clear([](qing::Thread& t) {
std::cout << "cycle cleaned up" << std::endl;
});
worker.Activate();
worker.WaitStart(); // 阻塞至 RUNNING
while (!done) {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
worker.WaitClose();
return 0;
}
```
## 依赖
- C++20(`g++ --std=c++20`)
- 纯标准库,无第三方依赖
- 线程库需链接 `-pthread`
## 设计要点
- **回调组合优于继承**。2025 年 6 月从派生改为持有 `std::function` 回调,Thread 不再需要子类化。
- **两阶段构造**。构造与 `Activate()` 分离,防止 `std::thread` 在对象构造完成前访问未初始化的成员。
- **异常安全**。main() 循环捕获 `std::exception` 后回退到 STOP 而非崩溃。注意:catch 后继续运行可能造成内存泄漏。
- **wake() 不可加锁**。`WaitStart()` 先持有 `mtx_th` 再调用 `wake()`,若 `wake()` 内部也尝试获取同一锁会造成死锁。
- **Fsm 提供两套条件变量**。Fsm 内部有一套 `cv` 用于 `suspend()`,Thread 自身另有一套 `cv` 用于 `WaitStart()`/`WaitClose()`,互不干扰。
## 注意事项
- Cv_wait 仅支持单次使用,复用时 `flag` 残留为 `true` 会导致 `Wait()` 立即返回而不阻塞。
- Fsm 的 `suspend()` 等待的是 Fsm 内部的 `ready` 标志,由 `set()` 触发;Thread 的 `WaitStart()` 等待的是 Thread 内部的 `ready`,由 `run()`/`stop()`/`shut()` 触发。两者独立。
- `Activate()` 不可重复调用(内部有空指针检查),但不会报错,静默返回。
- 析构函数为虚函数,允许安全的多态删除。
## 许可证
MIT License
+1 -1
View File
@@ -28,7 +28,7 @@ public:
inline void WakeCv() /* 唤醒线程 */
{
{
std::lock_guard<std::mutex> lcok(mtx);
std::lock_guard<std::mutex> lock(mtx);
flag = true;
}
cv.notify_all();
+165
View File
@@ -0,0 +1,165 @@
# smtp — 邮箱验证码服务
基于自研 [pymsg](../pymsg/) 协议(TCP 长度前缀帧 + JSON)的邮箱验证码服务,通过 SMTP_SSL 发送 6 位数字验证码,支持发送冷却和防暴力枚举。
服务由 `core`(PMC)的 `init.txt` 拉起,监听 `127.0.0.1:8008`(TCP),仅允许本机调用。`gateway` 在 `/register` 流程中经 pymsg 调用本服务完成邮箱验证。
## 架构
```
gateway :8443 (注册流程)
│ pymsg TCP
▼
smtp :8008 (MsgServer)
│ SMTP_SSL
▼
外部 SMTP 服务器 (163/QQ 等)
│
▼
用户邮箱(收验证码)
```
## 路由
服务基于 pymsg 协议,每条消息为完整 JSON,`_route` 字段指定路由。
| _route | 说明 |
|--------|------|
| `send_email` | 生成 6 位数字验证码,通过 SMTP_SSL 发送至指定邮箱 |
| `verify_email` | 校验用户输入的验证码,一次性消费 |
| `health` | 健康检查 |
响应格式:
```json
{"success": true, "data": {...}}
{"success": false, "error": "..."}
```
### 1. send_email — 发送验证码
**请求**:
```json
{"_route": "send_email", "email": "user@example.com"}
```
可选字段 `subject` / `content` 覆盖默认标题与正文,`content` 中 `{code}` 会被替换为验证码。
**响应**:
| 响应 data | 触发条件 |
|-----------|----------|
| `{"sent": true, "ttl": 600}` | 发送成功,TTL 为有效期(秒) |
| `{"sent": false, "error": "invalid email"}` | 邮箱格式无效 |
| `{"sent": false, "error": "SMTP 服务未配置"}` | .env 缺少 SMTP 配置 |
| `{"sent": false, "retry_after": N, "error": "发送过于频繁,请 N 秒后重试"}` | 同一邮箱在冷却期内 |
**安全机制**:
- 发送冷却:同一邮箱两次成功发送的最小间隔 `SEND_INTERVAL`(60 秒)。冷却期内请求直接拒绝,不覆盖已有验证码。
- 发送失败回滚:SMTP 发送失败时立即删除刚写入的验证码,避免"码已存但邮件没到"的不可用状态。
### 2. verify_email — 校验验证码
**请求**:
```json
{"_route": "verify_email", "email": "user@example.com", "code": "123456"}
```
兼容旧字段名 `input_captcha`。
**响应**(均为 `success: true`,业务结果在 `data` 中区分):
| 响应 data | 触发条件 |
|-----------|----------|
| `{"valid": true}` | 验证码正确(已消费) |
| `{"valid": false, "error": "email and code required"}` | 缺少字段 |
| `{"valid": false, "error": "invalid or expired code"}` | 验证码错误、过期或不存在 |
| `{"valid": false, "invalidated": true, "error": "验证码连续错误 5 次,已失效,请重新获取"}` | 连续校验失败达上限 |
> 验证码一经正确校验即从内存中删除(一次性消费),防重放。同一验证码连续校验失败达 `MAX_VERIFY_ATTEMPTS`(5 次)即作废,防止对 6 位数字码的在线枚举。
### 3. health — 健康检查
**请求**:`{"_route": "health"}`
**响应**:`{"success": true, "data": {"status": "ok", "service": "smtp"}}`
## 配置
通过 `.env` 文件配置 SMTP 连接信息,支持 4 种格式:
```bash
# KEY=VALUE(推荐)
SMTP_SERVER=smtp.163.com
SMTP_PORT=465
SMTP_USER=your@163.com
__PASSWORD=your_smtp_password
# 也支持 KEY: VALUE / KEY VALUE / export KEY=...
MAIL_FROM=dnfs@qsont.me # 可选,发件人身份;默认跟随 SMTP_USER
```
| 变量 | 必填 | 说明 |
|------|------|------|
| `SMTP_SERVER` | 是 | SMTP 服务器地址 |
| `SMTP_PORT` | 否 | SMTP 端口,默认 465 |
| `SMTP_USER` | 是 | 邮箱登录账号 |
| `__PASSWORD` | 是 | 邮箱密码/授权码(兼容别名 `SMTP_PASSWORD`) |
| `MAIL_FROM` | 否 | 发件人身份,默认同 SMTP_USER。多数服务商强制 From 头与登录账号一致 |
服务启动时检查必填配置项,缺失时打印警告日志但不阻止启动(`send_email` 路由会返回配置错误)。
## 数据模型与验证码生命周期
- **存储**:进程内存字典 `code_store`,键为 email,值为 `{code, expires_at, attempts}`。
- **冷却记录**:`send_history` 字典,键为 email,值为上次成功发送的时间戳。
- **生成**:`send_email` 成功时写入 `code_store`,记录 `expires_at = now + CODE_TTL`(600 秒)。
- **消费**:`verify_email` 校验通过时 `del code_store[email]`,不可再次使用。
- **过期**:超时后 `verify_email` 自动删除并返回 `invalid or expired code`。`send_email` 时惰性清理过期项。
- **作废**:连续校验失败达 5 次后删除验证码,返回 `invalidated: true`。
- **发送失败回滚**:SMTP 异常时立即 `pop` 刚写入的 `code_store` 条目。
## 运行
```bash
# 初始化(创建 venv + 安装依赖)
bash _set.sh
# 启动
bash _run.sh
# 或直接运行
python3 smtp_v2.py --addr 127.0.0.1 --port 8008
```
## 集成关系
- 由 `core` 通过 `init.txt` 拉起的 `bash _run.sh` 守护运行。
- `gateway` 在 `/register` 时经 pymsg 调 `send_email` 发送验证码,调 `verify_email` 校验。需在 gateway 的 `example.ini` 中启用邮箱验证功能。
- 日志通过 `log_client.py` 异步批量推送至 db:8005 的 Logs 表(缓冲 50 条 / 2 秒)。
## 项目结构
```
smtp/
├── smtp_v2.py # 服务主程序(pymsg 协议,当前有效版本)
├── smtp.py # 旧版 Flask HTTP 实现(已废弃,仅作参考)
├── log_client.py # 集中日志推送 SDK
├── _set.sh # 初始化脚本(创建 venv + 安装依赖)
├── _run.sh # 启动脚本(exec python3 smtp_v2.py)
├── requirements.txt # Python 依赖(pymsg、nuitka)
└── .gitignore # 忽略 __pycache__ / .env / venv
```
## 注意事项
- 验证码存储在进程内存中,服务重启后全部失效;不支持多实例水平扩展。
- 默认仅监听 `127.0.0.1`,仅供本机调用;浏览器侧不直接访问本服务。
- 旧版 `smtp.py`(Flask HTTP 实现)已废弃,`_run.sh` 当前启动的是 `smtp_v2.py`。旧版存在无冷却、无失败回滚、无防枚举等安全问题,且 `verify_email` 在 email 不存在时有 KeyError 风险,不应在生产环境使用。
- 多数邮箱服务商(163/QQ 等)强制 From 头与登录账号一致,`MAIL_FROM` 仅当服务商允许自定义发件身份时才起作用。
- 验证码为纯 6 位数字,有效期 600 秒。
## 许可证
MIT License
+1 -1
View File
@@ -4,4 +4,4 @@ python3 -m venv venv
#sudo chmod +x .env/bin/activate
source venv/bin/activate
pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple
#nuitka --standalone smtp.py
#nuitka --standalone smtp_v2.py
+1
View File
@@ -26,6 +26,7 @@
<!--<script src="https://cdnjs.cloudflare.com/ajax/libs/markdown-it/12.0.0/markdown-it.min.js"></script>-->
<script src="js/markdown-it-12_0_0.js"></script>
<script src="js/validation.js"></script>
<script src="js/token_manager.js"></script>
<script src="js/dnfs.js"></script>
</div>
<footer class="footer">
+118
View File
@@ -0,0 +1,118 @@
/**
* Token 生命周期管理器
* - 定时静默刷新:在 Token 80% 生命周期时提前调 /refresh_token
* - 全局 fetch 拦截器:收到 401 先尝试刷新一次,成功则重试原请求,失败则原样返回 401
* - 依赖:gateway POST /refresh_token(Cookie q4_token 自动携带)
* - 无侵入:与 dnfs.js 完全解耦
*/
let _tokenExpireAt = null;
let _refreshTimer = null;
const REFRESH_ADVANCE = 0.8; // 在 80% 生命周期时提前刷新
const REFRESH_MIN_GAP = 60; // 两次刷新最少间隔(秒)
const RETRY_COOLDOWN = 15; // 刷新失败后冷却期(秒)
/* ---------- 过期时间估算:通过 /whoami 获取 iat + keep_time ---------- */
async function estimateExpiry() {
try {
const res = await _rawFetch('/whoami', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: '{}'
});
if (!res.ok) return false;
const data = await res.json();
if (!data.login) return false;
// 优先 iat(首次签发时间,刷新不变),回退 created_at(兼容旧 Token)
const issueStr = data.iat || data.created_at;
const keepTime = data.keep_time;
if (!issueStr || !keepTime) return false;
const issueDate = new Date(issueStr.replace(' ', 'T') + 'Z');
if (isNaN(issueDate.getTime())) return false;
_tokenExpireAt = issueDate.getTime() + keepTime * 1000;
scheduleRefresh();
return true;
} catch (e) {
console.warn('TokenManager: 过期时间估算失败', e.message);
return false;
}
}
/* ---------- 定时刷新 ---------- */
function scheduleRefresh() {
clearTimeout(_refreshTimer);
if (!_tokenExpireAt) return;
const remainingSec = (_tokenExpireAt - Date.now()) / 1000;
if (remainingSec <= 0) {
// Token 已过期,立即尝试刷新
tryRefresh();
return;
}
// 80% 位置触发,但至少等 REFRESH_MIN_GAP 秒
const delaySec = Math.max(remainingSec * (1 - REFRESH_ADVANCE), REFRESH_MIN_GAP);
_refreshTimer = setTimeout(tryRefresh, delaySec * 1000);
}
async function tryRefresh() {
try {
const res = await _rawFetch('/refresh_token', { method: 'POST' });
if (!res.ok) {
const data = await res.json().catch(() => ({}));
if (data.message) console.warn('TokenManager: 刷新被拒 -', data.message);
// 冷却后重新估算
_refreshTimer = setTimeout(() => estimateExpiry(), RETRY_COOLDOWN * 1000);
return;
}
const data = await res.json();
if (data.success && data.data) {
console.log(`TokenManager: 已刷新 (第${data.data.refresh_count}次, 剩余${data.data.refreshes_remaining}次)`);
}
await estimateExpiry();
} catch (e) {
console.warn('TokenManager: 刷新网络异常', e.message);
_refreshTimer = setTimeout(() => estimateExpiry(), RETRY_COOLDOWN * 1000);
}
}
/* ---------- 全局 fetch 401 拦截器 ---------- */
const _rawFetch = window.fetch;
window.fetch = async function (url, options) {
let resp = await _rawFetch(url, options);
// 仅处理 401;/refresh_token 自身不重试防循环
if (resp.status !== 401 || (typeof url === 'string' && url.includes('/refresh_token'))) {
return resp;
}
try {
const refreshRes = await _rawFetch('/refresh_token', { method: 'POST' });
if (!refreshRes.ok) return resp;
const data = await refreshRes.json();
if (!data.success) return resp;
console.log('TokenManager: 401 拦截 → 刷新成功,重试原请求');
await estimateExpiry();
// Cookie 已由 gateway Set-Cookie 自动更新,直接重试
return await _rawFetch(url, options);
} catch (e) {
console.warn('TokenManager: 401 刷新异常', e.message);
}
return resp;
};
/* ---------- 启动 ---------- */
estimateExpiry();