tanbushi commited on
Commit
82f9be0
·
1 Parent(s): c0a2a3f
Dockerfile CHANGED
@@ -6,6 +6,7 @@ FROM python:3.12.11
6
  RUN useradd -m -u 1000 user
7
  USER user
8
  ENV PATH="/home/user/.local/bin:$PATH"
 
9
 
10
  WORKDIR /app
11
 
@@ -13,4 +14,9 @@ COPY --chown=user ./requirements.txt requirements.txt
13
  RUN pip install --no-cache-dir --upgrade -r requirements.txt
14
 
15
  COPY --chown=user . /app
16
- CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
 
 
 
 
 
 
6
  RUN useradd -m -u 1000 user
7
  USER user
8
  ENV PATH="/home/user/.local/bin:$PATH"
9
+ ENV PYTHONPATH="/app:$PYTHONPATH"
10
 
11
  WORKDIR /app
12
 
 
14
  RUN pip install --no-cache-dir --upgrade -r requirements.txt
15
 
16
  COPY --chown=user . /app
17
+
18
+ # 设置环境变量
19
+ ENV API_HOST="0.0.0.0"
20
+ ENV API_PORT="7860"
21
+
22
+ CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "7860"]
README_APP.md ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Human-Clone System
2
+
3
+ Human-Clone 系统是一个多主体通讯协作平台,支持 human 和 clone 之间的实时异步通讯。
4
+
5
+ ## 快速开始
6
+
7
+ ### 环境要求
8
+ - Python 3.9+
9
+ - Redis 服务器
10
+
11
+ ### 安装依赖
12
+ ```bash
13
+ pip install -r requirements.txt
14
+ ```
15
+
16
+ ### 启动方式
17
+
18
+ #### 1. 直接启动(推荐)
19
+ ```bash
20
+ uvicorn src.main:app --reload --host 0.0.0.0 --port 7860
21
+ ```
22
+
23
+ #### 2. 使用脚本启动
24
+ ```bash
25
+ python scripts/start_dev.py
26
+ ```
27
+
28
+ #### 3. 开发模式
29
+ ```bash
30
+ uvicorn src.main:app --reload --debug --port 7860
31
+ ```
32
+
33
+ ## API 文档
34
+ 启动后访问:
35
+ - Swagger UI: http://localhost:7860/docs
36
+ - ReDoc: http://localhost:7860/redoc
37
+
38
+ ## 项目结构
39
+ ```
40
+ ├── src/ # 源代码目录
41
+ │ ├── core/ # 核心功能模块
42
+ │ ├── config/ # 配置管理
43
+ │ ├── api/ # API路由
44
+ │ └── utils/ # 工具函数
45
+ ├── tests/ # 测试代码
46
+ ├── scripts/ # 启动脚本
47
+ └── docs/ # 项目文档
48
+ ```
49
+
50
+ ## 核心功能
51
+ - 多主体(human/clone)实时通讯
52
+ - 基于 Redis 的异步消息传递
53
+ - 支持点对点消息发送
54
+ - 完全解耦的发送/接收队列机制
55
+
56
+ ## 开发指南
57
+ 详见 `docs/development-workflow.md`
app.py DELETED
@@ -1,7 +0,0 @@
1
- from fastapi import FastAPI
2
-
3
- app = FastAPI()
4
-
5
- @app.get("/")
6
- def greet_json():
7
- return {"Hello": "World!"}
 
 
 
 
 
 
 
 
docs/development-workflow.md ADDED
@@ -0,0 +1,130 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Human-Clone 系统开发流程
2
+
3
+ ## 项目概述
4
+ 基于需求调研结果的Human-Clone系统开发实施计划
5
+
6
+ ## 开发阶段规划
7
+
8
+ ### 第一阶段:基础设计
9
+
10
+ #### 1.1 数据结构设计
11
+ - **主体信息结构**
12
+ - UUID(唯一标识)
13
+ - name(显示名称)
14
+ - redis配置(服务器地址、端口、DB、channel)
15
+
16
+ - **消息结构**
17
+ - sender_id(发送者UUID)
18
+ - receiver_id(接收者UUID)
19
+ - timestamp(时间戳)
20
+ - content(消息内容)
21
+
22
+ #### 1.2 Redis-Adapter组件设计
23
+ - **发送模块**
24
+ - 发送队列(thread-safe queue)
25
+ - 发送线程(独立线程循环处理)
26
+ - Redis连接管理
27
+
28
+ - **接收模块**
29
+ - 接收队列(thread-safe queue)
30
+ - 接收线程(独立线程循环处理)
31
+ - 回调注册机制
32
+
33
+ - **配置管理**
34
+ - Redis连接参数
35
+ - 主体身份信息
36
+
37
+ #### 1.3 接口设计
38
+ - **主体注册接口**
39
+ - register_entity(entity_info)
40
+ - unregister_entity(entity_id)
41
+
42
+ - **消息发送接口**
43
+ - send_message(sender_id, receiver_id, content)
44
+
45
+ - **消息接收接口**
46
+ - register_callback(callback_function)
47
+ - unregister_callback()
48
+
49
+ ### 第二阶段:核心实现
50
+
51
+ #### 2.1 消息发送流程实现
52
+ - 构造消息对象
53
+ - 放入发送队列
54
+ - 发送线程异步处理
55
+ - Redis连接和发布
56
+
57
+ #### 2.2 消息接收流程实现
58
+ - 监听Redis channel
59
+ - 推送到接收队列
60
+ - 接收线程异步处理
61
+ - 回调函数调用
62
+
63
+ #### 2.3 队列管理实现
64
+ - 线程安全队列设计
65
+ - 生产者-消费者模式
66
+ - 异常处理机制
67
+
68
+ ### 第三阶段:测试验证
69
+
70
+ #### 3.1 单元测试
71
+ - 数据结构测试
72
+ - Redis-Adapter组件测试
73
+ - 队列操作测试
74
+ - 线程安全测试
75
+
76
+ #### 3.2 集成测试
77
+ - 发送流程集成测试
78
+ - 接收流程集成测试
79
+ - 端到端通讯测试
80
+
81
+ #### 3.3 性能测试
82
+ - 并发发送测试
83
+ - 大量消息处理测试
84
+ - 内存和性能监控
85
+
86
+ ### 第四阶段:优化完善
87
+
88
+ #### 4.1 错误处理
89
+ - 连接失败处理
90
+ - 消息发送失败处理
91
+ - 线程异常处理
92
+
93
+ #### 4.2 监控日志
94
+ - 消息发送接收日志
95
+ - 性能指标监控
96
+ - 异常情况记录
97
+
98
+ ## 技术栈选择
99
+
100
+ ### 编程语言
101
+ - **推荐**: Python(丰富的Redis库支持,线程处理便利)
102
+ - **备选**: Java/Go(高性能要求场景)
103
+
104
+ ### 核心依赖
105
+ - **Redis客户端**: redis-py (Python)
106
+ - **线程库**: threading/queue (Python原生)
107
+ - **JSON处理**: json (Python原生)
108
+
109
+ ## 开发顺序
110
+
111
+ 1. **数据结构定义** → 2. **Redis连接工具类** → 3. **发送队列模块** → 4. **接收队列模块** → 5. **主控制器** → 6. **测试用例**
112
+
113
+ ## 里程碑
114
+
115
+ - **M1**: 完成数据结构和Redis连接(第1-2天)
116
+ - **M2**: 完成发送模块(第3-4天)
117
+ - **M3**: 完成接收模块(第5-6天)
118
+ - **M4**: 完成集成测试(第7-8天)
119
+
120
+ ## 风险评估
121
+
122
+ ### 技术风险
123
+ - Redis连接稳定性
124
+ - 线程安全问题
125
+ - 队列性能瓶颈
126
+
127
+ ### 缓解措施
128
+ - 连接池设计
129
+ - 线程安全机制
130
+ - 队列大小监控
docs/research-log.md ADDED
@@ -0,0 +1,214 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Human-Clone 系统需求调研记录
2
+
3
+ ## 调研日期
4
+ 2025-01-04
5
+
6
+ ## 项目概述
7
+ Human-Clone 系统需求分析
8
+
9
+ ## 第一轮调研
10
+
11
+ ### 用户回答记录
12
+ **问题**: 这个human-clone系统的主要用途是什么?
13
+ **回答**: human 和 clone 之间相互协作,且可以和其他 clone、human 之间相互通讯的系统。现阶段首先解决的是通讯问题。
14
+
15
+ ### 初步需求分析
16
+ - **核心功能**: 多主体间通讯协作系统
17
+ - **主体类型**: human, clone
18
+ - **通讯模式**:
19
+ - human ↔ clone
20
+ - clone ↔ clone
21
+ - human ↔ human (通过系统)
22
+ - **当前优先级**: 通讯功能
23
+
24
+ ### 下一步调研方向
25
+ 需要深入了解通讯的具体需求和技术细节。
26
+
27
+ ### 第二轮调研记录
28
+
29
+ **问题**: 这些主体之间的通讯主要是通过什么形式?文字、语音、还是视频?
30
+ **回答**: 第一步先解决主体之间文字形式的通讯
31
+
32
+ ### 需求更新
33
+ - **通讯方式**: 首先实现文字通讯
34
+ - **开发阶段**: 第一阶段专注文字通讯功能
35
+ - **通讯模式**: 实时异步通讯
36
+
37
+ ### 第三轮调研记录
38
+
39
+ **问题**: 系统需要支持实时通讯(如聊天室形式)还是异步通讯(如邮件/留言形式)?
40
+ **回答**: 基本属于实施通讯,但是是异步的,计划采用 redis 订阅的方式来实现信息传递,每一个主体都会有一个 redis-adapter,负责发送和接收消息
41
+
42
+ ### 技术架构初步确定
43
+ - **消息传递**: Redis 订阅/发布模式
44
+ - **组件设计**: 每个主体配备 redis-adapter
45
+ - **消息处理**: 发送/接收消息统一处理
46
+
47
+ ### 第四轮调研记录
48
+
49
+ **问题**: 每个主体(human/clone)需要有自己的身份标识吗?比如用户名、ID之类的?
50
+ **回答**: 需要有 id —— uuid 格式、实体名称(name)、redis-adapter 里和 redis 相关订阅 channel 相关的配置信息
51
+
52
+ ### 主体身份设计
53
+ - **唯一标识**: UUID格式ID
54
+ - **显示名称**: 实体名称(name)
55
+ - **配置信息**: redis-adapter配置(订阅channel等)
56
+
57
+ ### 第五轮调研记录
58
+
59
+ **问题**: 消息需要有哪些基本字段?比如发送者、接收者、时间戳、消息内容?
60
+ **回答**: 需要,其中发送者、接收者都使用 id
61
+
62
+ ### 消息结构设计
63
+ - **发送者**: UUID格式ID
64
+ - **接收者**: UUID格式ID
65
+ - **时间戳**: 消息发送时间
66
+ - **消息内容**: 文字内容
67
+ - **消息类型**: 点对点通讯
68
+
69
+ ### 第六轮调研记录
70
+
71
+ **问题**: 是否需要支持群发消息(一个发送者同时发给多个接收者)?
72
+ **回答**: 此阶段不需要
73
+
74
+ ### 功能范围明确
75
+ - **当前阶段**: 仅支持点对点消息
76
+ - **群发功能**: 后续版本考虑
77
+
78
+ ### 第七轮调研记录
79
+
80
+ **问题**: 系统需要存储历史消息记录吗?
81
+ **回答**: 需要
82
+
83
+ ### 数据持久化需求
84
+ - **消息存储**: 需要保存历史消息记录
85
+ - **存储方案**: 待确定(数据库选择)
86
+
87
+ ### 第八轮调研记录
88
+
89
+ **问题**: 历史消息需要保存多长时间?
90
+ **回答**: 先不讨论历史记录的问题,先解决通信问题
91
+
92
+ ### 开发优先级调整
93
+ - **当前重点**: 通讯功能实现
94
+ - **历史记录**: 后续阶段处理
95
+
96
+ ### 第九轮调研记录
97
+
98
+ **问题**: redis-adapter 需要哪些具体的配置信息?比如 Redis 服务器地址、端口、认证信息等?
99
+ **回答**: redis 服务器地址、端口、db、channel
100
+
101
+ ### redis-adapter 配置设计
102
+ - **服务器地址**: Redis host
103
+ - **端口**: Redis port
104
+ - **数据库**: Redis DB number
105
+ - **频道**: 订阅/发布的 channel
106
+
107
+ ### 第十轮调研记录
108
+
109
+ **问题**: 每个主体是使用相同的 channel 还是各自有独立的 channel?如果是独立的,channel 的命名规则是什么?
110
+ **回答**: 每个主体都是独立的 channel,channel 的命名就是主体的 id
111
+
112
+ ### Channel 设计方案
113
+ - **Channel 分配**: 每个主体独立 channel
114
+ - **命名规则**: 使用主体的 UUID 作为 channel 名称
115
+ - **通讯机制**: 目标主体监听自己的 channel 接收消息
116
+
117
+ ### 第十一轮调研记录
118
+
119
+ **问题**: 当一个主体要发送消息给另一个主体时,是如何知道接收者的 channel 名称的?是否需要一个主体注册发现机制?
120
+ **回答**: 主体注册发现机制可以放到下一个迭代,此次只假设可以通过目标主体的 id 就可以获得目标主体的订阅 redis 参数,包括服务器、端口、db、channel
121
+
122
+ ### 主体发现机制设计
123
+ - **当前版本**: 假设可通过 ID 获取目标的 Redis 连接参数
124
+ - **后续迭代**: 实现主体注册发现机制
125
+ - **参数获取**: 服务器地址、端口、DB、channel 通过 ID 直接获得
126
+
127
+ ### 第十二轮调研记录
128
+
129
+ **问题**: redis-adapter 是否需要处理连接断开重连、消息发送失败重试等异常情况?
130
+ **回答**: 目前不需要
131
+
132
+ ### 异常处理设计
133
+ - **当前版本**: 不处理连接重连和重试机制
134
+ - **设计原则**: 专注核心通讯功能,异常处理后续考虑
135
+
136
+ ### 第十三轮调研记录
137
+
138
+ **问题**: 消息的发送和接收是否需要确认机制?比如接收者收到消息后需要回复确认?
139
+ **回答**: 不需要
140
+
141
+ ### 消息确认机制
142
+ - **确认机制**: 不需要消息确认
143
+ - **通讯模式**: 单向发送,无回复确认
144
+ - **简化设计**: 降低复杂度,专注基础通讯
145
+
146
+ ### 第十四轮调研记录
147
+
148
+ **问题**: 系统是否需要区分不同类型的消息?比如普通消息、系统消息、命令消息等?
149
+ **回答**: 不需要
150
+
151
+ ### 消息类型设计
152
+ - **消息分类**: 不区分消息类型
153
+ - **统一处理**: 所有消息均为普通文字消息
154
+ - **简化架构**: 无需消息类型判断和处理逻辑
155
+
156
+ ### 第十五轮调研记录
157
+
158
+ **问题**: 每个主体的 redis-adapter 是需要同时监听多个 channel(包括自己的和其他主体的)还是只监听自己的 channel?
159
+ **回答**: 只监听自己的
160
+
161
+ ### 监听机制设计
162
+ - **监听范围**: 每个主体只监听自己的 channel
163
+ - **消息接收**: 通过订阅自己的 ID channel 接收消息
164
+ - **消息发送**: 向目标主体的 ID channel 发布消息
165
+
166
+ ### 需求调研总结
167
+
168
+ 基于15轮调研,Human-Clone 系统第一版本的核心需求已明确:
169
+
170
+ #### 系统架构
171
+ - **主体类型**: human、clone
172
+ - **通讯模式**: 点对点文字通讯
173
+ - **技术方案**: Redis 订阅/发布模式
174
+ - **身份标识**: UUID + name
175
+
176
+ #### 核心组件
177
+ - **redis-adapter**: 每个主体一个,负责发送/接收消息
178
+ - **配置参数**: 服务器地址、端口、DB、channel(=主体ID)
179
+ - **监听机制**: 只监听自己的 channel
180
+
181
+ #### 消息格式
182
+ - **发送者ID**: UUID格式
183
+ - **接收者ID**: UUID格式
184
+ - **时间戳**: 发送时间
185
+ - **内容**: 文字消息
186
+
187
+ #### 功能边界
188
+ - ✅ 点对点文字通讯
189
+ - ❌ 群发消息
190
+ - ❌ 历史消息存储
191
+ - ❌ 连接重连机制
192
+ - ❌ 消息确认机制
193
+ - ❌ 消息类型区分
194
+ - ❌ 主体注册发现
195
+
196
+ ### 第十六轮调研记录
197
+
198
+ **问题**: 发送消息时是否需要引入发送缓存队列来解耦主体发送和Redis发送操作?
199
+ **回答**: 在发送主体不直接调用发送消息到 redis,而是在 redis-adapter 里设置一个发送缓存 queue,通过线程循环逐个 pop 后发送到 redis,这样主体发送和 redis 发送分开,且有缓存,解耦
200
+
201
+ ### 第十七轮调研记录
202
+
203
+ **问题**: 消息接收部分是否也需要类似的队列解耦设计?
204
+ **回答**: 消息接收部分,目标主体 B 的redis-adapter 收到 channel 里的消息后,推送到接收队列 receiver-queue,在线程里循环读取 queue,并调用 callback,给到 B 的函数里进行处理
205
+
206
+ ### 完整架构设计优化
207
+ - **发送缓存**: redis-adapter内部设置发送队列
208
+ - **接收缓存**: redis-adapter内部设置接收队列
209
+ - **双向异步**: 发送和接收均采用队列+线程机制
210
+ - **回调机制**: 接收线程通过callback将消息传递给主体
211
+ - **完全解耦**: Redis操作与主体处理完全分离
212
+
213
+ #### 下一步行动
214
+ 基于明确的需求和完整的双向异步架构设计,可以开始进行技术设计和开发实现。
requirements.txt CHANGED
@@ -1,2 +1,4 @@
1
- fastapi
2
- uvicorn[standard]
 
 
 
1
+ redis>=5.0.0
2
+ fastapi>=0.104.0
3
+ uvicorn[standard]>=0.24.0
4
+ pydantic-settings>=2.0.0
roles/requirements-analyst-role.md ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 需求分析师角色设定
2
+
3
+ ## 角色定义
4
+ 软件系统需求分析师
5
+
6
+ ## 职责
7
+ - 分析和梳理软件系统需求
8
+ - 进行需求调研和评估
9
+ - 编写需求规格文档
10
+ - 协助项目规划和设计
11
+
12
+ ## 工作要求
13
+ 1. 深入挖掘系统需求
14
+ 2. 每次与用户提问时,一次只提出一个问题
15
+ 3. 避免对用户造成不必要的干扰
16
+ 4. 将调研过程及报告写入根目录下的 docs 文件夹
17
+
18
+ ## 当前项目
19
+ Human-Clone 系统需求分析
roles/software-engineer-role.md ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 软件开发工程师角色设定
2
+
3
+ ## 角色定义
4
+ 软件开发工程师 - Human-Clone 系统实现
5
+
6
+ ## 职责
7
+ - 根据需求分析和设计文档进行代码实现
8
+ - 编写高质量、可维护的代码
9
+ - 进行单元测试和集成测试
10
+ - 代码重构和性能优化
11
+ - 技术文档编写
12
+
13
+ ## 技术要求
14
+ - 熟练掌握 Python 编程
15
+ - Redis 操作和 pub/sub 机制
16
+ - 多线程编程和并发处理
17
+ - 队列数据结构和线程安全
18
+ - 软件架构设计和模式应用
19
+
20
+ ## 工作原则
21
+ 1. 遵循 SOLID 设计原则
22
+ 2. 编写可测试的代码
23
+ 3. 关注代码可读性和可维护性
24
+ 4. 进行充分的测试验证
25
+ 5. 及时更新技术文档
26
+
27
+ ## 当前项目
28
+ Human-Clone 系统核心通讯功能开发
29
+
30
+ ## 开发任务
31
+ - 实现数据结构定义
32
+ - 开发 Redis-Adapter 组件
33
+ - 实现发送/接收队列机制
34
+ - 编写测试用例
35
+ - 性能优化和错误处理
scripts/start_dev.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ 开发启动脚本
3
+ """
4
+
5
+ import uvicorn
6
+ from src.config import settings
7
+
8
+
9
+ def main():
10
+ """开发环境启动"""
11
+ uvicorn.run(
12
+ "src.main:app",
13
+ host=settings.api_host,
14
+ port=settings.api_port,
15
+ reload=settings.debug,
16
+ log_level=settings.log_level.lower()
17
+ )
18
+
19
+
20
+ if __name__ == "__main__":
21
+ main()
scripts/start_prod.py ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ 生产启动脚本
3
+ """
4
+
5
+ import uvicorn
6
+ from src.config import settings
7
+
8
+
9
+ def main():
10
+ """生产环境启动"""
11
+ uvicorn.run(
12
+ "src.main:app",
13
+ host=settings.api_host,
14
+ port=settings.api_port,
15
+ reload=False,
16
+ log_level=settings.log_level.lower(),
17
+ workers=4
18
+ )
19
+
20
+
21
+ if __name__ == "__main__":
22
+ main()
src/__init__.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ """
2
+ Human-Clone System
3
+ 多主体通讯协作系统
4
+ """
5
+
6
+ __version__ = "1.0.0"
7
+ __author__ = "Human-Clone Team"
src/api/__init__.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ """
2
+ API模块初始化
3
+ """
4
+
5
+ from .routes import communication
6
+
7
+ __all__ = ["communication"]
src/api/routes/__init__.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ """
2
+ 路由模块初始化
3
+ """
4
+
5
+ from .communication import router
6
+
7
+ __all__ = ["router"]
src/api/routes/communication.py ADDED
@@ -0,0 +1,130 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ 通讯API路由
3
+ """
4
+
5
+ from fastapi import APIRouter, HTTPException
6
+ from pydantic import BaseModel
7
+ from typing import List
8
+ import uuid
9
+ from datetime import datetime
10
+
11
+ from ...core import EntityInfo, Message, RedisAdapter
12
+
13
+ router = APIRouter()
14
+
15
+ # 全局存储(生产环境应使用数据库)
16
+ entities: dict[str, EntityInfo] = {}
17
+ adapters: dict[str, RedisAdapter] = {}
18
+
19
+
20
+ class EntityCreate(BaseModel):
21
+ name: str
22
+ redis_host: str = "localhost"
23
+ redis_port: int = 6379
24
+ redis_db: int = 0
25
+
26
+
27
+ class MessageSend(BaseModel):
28
+ sender_id: str
29
+ receiver_id: str
30
+ content: str
31
+
32
+
33
+ class MessageResponse(BaseModel):
34
+ sender_id: str
35
+ receiver_id: str
36
+ timestamp: str
37
+ content: str
38
+
39
+
40
+ @router.post("/entities", response_model=dict)
41
+ async def create_entity(entity: EntityCreate):
42
+ """创建新实体"""
43
+ entity_id = str(uuid.uuid4())
44
+ channel = entity_id
45
+
46
+ entity_info = EntityInfo(
47
+ id=entity_id,
48
+ name=entity.name,
49
+ redis_host=entity.redis_host,
50
+ redis_port=entity.redis_port,
51
+ redis_db=entity.redis_db,
52
+ channel=channel
53
+ )
54
+
55
+ entities[entity_id] = entity_info
56
+
57
+ return {
58
+ "id": entity_id,
59
+ "name": entity.name,
60
+ "status": "created"
61
+ }
62
+
63
+
64
+ @router.get("/entities", response_model=List[dict])
65
+ async def list_entities():
66
+ """获取所有实体列表"""
67
+ return [
68
+ {
69
+ "id": entity_id,
70
+ "name": entity.name,
71
+ "redis_host": entity.redis_host,
72
+ "redis_port": entity.redis_port,
73
+ "redis_db": entity.redis_db
74
+ }
75
+ for entity_id, entity in entities.items()
76
+ ]
77
+
78
+
79
+ @router.post("/entities/{entity_id}/start")
80
+ async def start_entity(entity_id: str):
81
+ """启动实体通讯"""
82
+ if entity_id not in entities:
83
+ raise HTTPException(status_code=404, detail="Entity not found")
84
+
85
+ if entity_id in adapters:
86
+ raise HTTPException(status_code=400, detail="Entity already started")
87
+
88
+ adapter = RedisAdapter(entities[entity_id])
89
+ if adapter.start():
90
+ adapters[entity_id] = adapter
91
+ return {"status": "started"}
92
+ else:
93
+ raise HTTPException(status_code=500, detail="Failed to start entity")
94
+
95
+
96
+ @router.post("/entities/{entity_id}/stop")
97
+ async def stop_entity(entity_id: str):
98
+ """停止实体通讯"""
99
+ if entity_id not in adapters:
100
+ raise HTTPException(status_code=404, detail="Entity not started")
101
+
102
+ adapters[entity_id].stop()
103
+ del adapters[entity_id]
104
+
105
+ return {"status": "stopped"}
106
+
107
+
108
+ @router.post("/messages/send")
109
+ async def send_message(message: MessageSend):
110
+ """发送消息"""
111
+ if message.sender_id not in adapters:
112
+ raise HTTPException(status_code=404, detail="Sender not started")
113
+
114
+ if message.receiver_id not in entities:
115
+ raise HTTPException(status_code=404, detail="Receiver not found")
116
+
117
+ adapter = adapters[message.sender_id]
118
+ success = adapter.send_message(message.receiver_id, message.content)
119
+
120
+ if success:
121
+ return {"status": "sent"}
122
+ else:
123
+ raise HTTPException(status_code=500, detail="Failed to send message")
124
+
125
+
126
+ @router.get("/entities/{entity_id}/messages", response_model=List[MessageResponse])
127
+ async def get_entity_messages(entity_id: str):
128
+ """获取实体的消息历史(临时存储,演示用)"""
129
+ # 这里只是示例,实际应该从持久化存储获取
130
+ return []
src/config/__init__.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ """
2
+ 配置模块初始化
3
+ """
4
+
5
+ from .settings import settings
6
+
7
+ __all__ = ["settings"]
src/config/settings.py ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ 配置管理模块
3
+ """
4
+
5
+ from pydantic_settings import BaseSettings
6
+ from typing import Optional
7
+
8
+
9
+ class Settings(BaseSettings):
10
+ """系统配置"""
11
+
12
+ # Redis配置
13
+ redis_host: str = "localhost"
14
+ redis_port: int = 6379
15
+ redis_db: int = 0
16
+ redis_password: Optional[str] = None
17
+
18
+ # API配置
19
+ api_host: str = "0.0.0.0"
20
+ api_port: int = 7860
21
+ debug: bool = True
22
+
23
+ # 日志配置
24
+ log_level: str = "INFO"
25
+
26
+ class Config:
27
+ env_file = ".env"
28
+
29
+
30
+ # 全局配置实例
31
+ settings = Settings()
src/core/__init__.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ 核心模块初始化
3
+ """
4
+
5
+ from .entities import EntityInfo
6
+ from .messages import Message
7
+ from .adapter import RedisAdapter
8
+
9
+ __all__ = ["EntityInfo", "Message", "RedisAdapter"]
src/core/adapter.py ADDED
@@ -0,0 +1,237 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Redis适配器 - 处理消息发送和接收
3
+ """
4
+
5
+ import redis
6
+ import json
7
+ import threading
8
+ import queue
9
+ import time
10
+ import logging
11
+ from dataclasses import dataclass
12
+ from datetime import datetime
13
+ from typing import Optional, Callable, Dict, Any
14
+
15
+ from .entities import EntityInfo
16
+ from .messages import Message
17
+
18
+
19
+ # 配置日志
20
+ logging.basicConfig(level=logging.INFO)
21
+ logger = logging.getLogger(__name__)
22
+
23
+
24
+ # 类型定义
25
+ MessageCallback = Callable[[Message], None]
26
+
27
+
28
+ @dataclass
29
+ class RedisConfig:
30
+ """Redis连接配置"""
31
+ host: str
32
+ port: int
33
+ db: int
34
+ password: Optional[str] = None
35
+
36
+ def to_connection_params(self) -> Dict[str, Any]:
37
+ """获取连接参数"""
38
+ params = {
39
+ 'host': self.host,
40
+ 'port': self.port,
41
+ 'db': self.db
42
+ }
43
+ if self.password:
44
+ params['password'] = self.password
45
+ return params
46
+
47
+
48
+ class RedisAdapter:
49
+ """Redis适配器 - 处理消息发送和接收"""
50
+
51
+ def __init__(self, entity_info: EntityInfo):
52
+ self.entity_info = entity_info
53
+ self.redis_config = RedisConfig(
54
+ host=entity_info.redis_host,
55
+ port=entity_info.redis_port,
56
+ db=entity_info.redis_db
57
+ )
58
+
59
+ # Redis连接
60
+ self.redis_client: Optional[redis.Redis] = None
61
+
62
+ # 发送相关
63
+ self.send_queue = queue.Queue()
64
+ self.send_thread = None
65
+ self.send_running = False
66
+
67
+ # 接收相关
68
+ self.receive_queue = queue.Queue()
69
+ self.receive_thread = None
70
+ self.receive_running = False
71
+ self.message_callback: Optional[MessageCallback] = None
72
+
73
+ # 连接相关
74
+ self.pubsub: Optional[redis.client.PubSub] = None
75
+ self.connected = False
76
+
77
+ def connect(self) -> bool:
78
+ """连接到Redis服务器"""
79
+ try:
80
+ self.redis_client = redis.Redis(**self.redis_config.to_connection_params())
81
+ self.redis_client.ping() # 测试连接
82
+ self.connected = True
83
+ logger.info(f"Entity {self.entity_info.id} connected to Redis")
84
+ return True
85
+ except Exception as e:
86
+ logger.error(f"Failed to connect to Redis: {e}")
87
+ return False
88
+
89
+ def disconnect(self):
90
+ """断开Redis连接"""
91
+ self.stop()
92
+ if self.redis_client:
93
+ self.redis_client.close()
94
+ self.connected = False
95
+ logger.info(f"Entity {self.entity_info.id} disconnected from Redis")
96
+
97
+ def start(self) -> bool:
98
+ """启动适配器(发送和接收线程)"""
99
+ if not self.connect():
100
+ return False
101
+
102
+ self.send_running = True
103
+ self.receive_running = True
104
+
105
+ # 启动发送线程
106
+ self.send_thread = threading.Thread(target=self._send_worker, daemon=True)
107
+ self.send_thread.start()
108
+
109
+ # 启动接收线程
110
+ self.receive_thread = threading.Thread(target=self._receive_worker, daemon=True)
111
+ self.receive_thread.start()
112
+
113
+ logger.info(f"RedisAdapter started for entity {self.entity_info.id}")
114
+ return True
115
+
116
+ def stop(self):
117
+ """停止适配器"""
118
+ self.send_running = False
119
+ self.receive_running = False
120
+
121
+ # 等待线程结束
122
+ if self.send_thread and self.send_thread.is_alive():
123
+ self.send_thread.join(timeout=5)
124
+ if self.receive_thread and self.receive_thread.is_alive():
125
+ self.receive_thread.join(timeout=5)
126
+
127
+ logger.info(f"RedisAdapter stopped for entity {self.entity_info.id}")
128
+
129
+ def send_message(self, receiver_id: str, content: str) -> bool:
130
+ """发送消息(异步)"""
131
+ try:
132
+ message = Message(
133
+ sender_id=self.entity_info.id,
134
+ receiver_id=receiver_id,
135
+ timestamp=datetime.now(),
136
+ content=content
137
+ )
138
+ self.send_queue.put(message)
139
+ logger.debug(f"Message queued for {receiver_id}")
140
+ return True
141
+ except Exception as e:
142
+ logger.error(f"Failed to queue message: {e}")
143
+ return False
144
+
145
+ def register_callback(self, callback: MessageCallback):
146
+ """注册消息接收回调函数"""
147
+ self.message_callback = callback
148
+
149
+ def _send_worker(self):
150
+ """发送工作线程"""
151
+ logger.info("Send worker thread started")
152
+
153
+ while self.send_running:
154
+ try:
155
+ # 从队列获取消息(超时1秒)
156
+ message = self.send_queue.get(timeout=1)
157
+
158
+ # 这里需要获取接收者的Redis连接信息
159
+ # 当前简化处理,使用同一个Redis实例
160
+ target_channel = message.receiver_id
161
+
162
+ # 发送到Redis
163
+ if self.redis_client:
164
+ self.redis_client.publish(
165
+ target_channel,
166
+ json.dumps(message.to_dict())
167
+ )
168
+ logger.debug(f"Message sent to channel {target_channel}")
169
+
170
+ self.send_queue.task_done()
171
+
172
+ except queue.Empty:
173
+ continue
174
+ except Exception as e:
175
+ logger.error(f"Error in send worker: {e}")
176
+
177
+ logger.info("Send worker thread stopped")
178
+
179
+ def _receive_worker(self):
180
+ """接收工作线程"""
181
+ logger.info("Receive worker thread started")
182
+
183
+ if not self.redis_client:
184
+ logger.error("Redis client not available for receiving")
185
+ return
186
+
187
+ try:
188
+ # 创建pubsub对象
189
+ self.pubsub = self.redis_client.pubsub()
190
+ self.pubsub.subscribe(self.entity_info.channel)
191
+
192
+ while self.receive_running:
193
+ try:
194
+ # 获取消息(超时1秒)
195
+ message = self.pubsub.get_message(timeout=1)
196
+
197
+ if message and message['type'] == 'message':
198
+ # 解析消息数据
199
+ message_data = json.loads(message['data'].decode('utf-8'))
200
+ received_message = Message.from_dict(message_data)
201
+
202
+ # 放入接收队列
203
+ self.receive_queue.put(received_message)
204
+
205
+ # 处理接收队列中的消息
206
+ self._process_receive_queue()
207
+
208
+ except Exception as e:
209
+ logger.error(f"Error processing received message: {e}")
210
+
211
+ except Exception as e:
212
+ logger.error(f"Error in receive worker: {e}")
213
+ finally:
214
+ if self.pubsub:
215
+ self.pubsub.close()
216
+
217
+ logger.info("Receive worker thread stopped")
218
+
219
+ def _process_receive_queue(self):
220
+ """处理接收队列中的消息"""
221
+ try:
222
+ while not self.receive_queue.empty():
223
+ message = self.receive_queue.get_nowait()
224
+
225
+ if self.message_callback:
226
+ try:
227
+ self.message_callback(message)
228
+ logger.debug(f"Message delivered to callback")
229
+ except Exception as e:
230
+ logger.error(f"Error in message callback: {e}")
231
+
232
+ self.receive_queue.task_done()
233
+
234
+ except queue.Empty:
235
+ pass
236
+ except Exception as e:
237
+ logger.error(f"Error processing receive queue: {e}")
src/core/entities.py ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ 实体信息结构
3
+ """
4
+
5
+ from dataclasses import dataclass
6
+ from typing import Optional
7
+ import uuid
8
+
9
+
10
+ @dataclass
11
+ class EntityInfo:
12
+ """主体信息结构"""
13
+ id: str # UUID格式唯一标识
14
+ name: str # 显示名称
15
+ redis_host: str # Redis服务器地址
16
+ redis_port: int # Redis端口
17
+ redis_db: int # Redis数据库编号
18
+ channel: str # 订阅channel名称
19
+
20
+ def __post_init__(self):
21
+ """数据验证"""
22
+ if not self.id:
23
+ self.id = str(uuid.uuid4())
24
+ if not self.channel:
25
+ self.channel = self.id
src/core/messages.py ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ 消息结构定义
3
+ """
4
+
5
+ from dataclasses import dataclass
6
+ from typing import Dict, Any
7
+ from datetime import datetime
8
+
9
+
10
+ @dataclass
11
+ class Message:
12
+ """消息结构"""
13
+ sender_id: str # 发送者UUID
14
+ receiver_id: str # 接收者UUID
15
+ timestamp: datetime # 时间戳
16
+ content: str # 消息内容
17
+
18
+ def to_dict(self) -> Dict[str, Any]:
19
+ """转换为字典格式"""
20
+ return {
21
+ 'sender_id': self.sender_id,
22
+ 'receiver_id': self.receiver_id,
23
+ 'timestamp': self.timestamp.isoformat(),
24
+ 'content': self.content
25
+ }
26
+
27
+ @classmethod
28
+ def from_dict(cls, data: Dict[str, Any]) -> 'Message':
29
+ """从字典创建消息对象"""
30
+ return cls(
31
+ sender_id=data['sender_id'],
32
+ receiver_id=data['receiver_id'],
33
+ timestamp=datetime.fromisoformat(data['timestamp']),
34
+ content=data['content']
35
+ )
src/main.py ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ FastAPI应用主入口
3
+ """
4
+
5
+ from fastapi import FastAPI
6
+ from fastapi.middleware.cors import CORSMiddleware
7
+ from src.api.routes.communication import router as comm_router
8
+
9
+ app = FastAPI(
10
+ title="Human-Clone API",
11
+ description="Human-Clone系统通讯接口",
12
+ version="1.0.0"
13
+ )
14
+
15
+ # CORS中间件
16
+ app.add_middleware(
17
+ CORSMiddleware,
18
+ allow_origins=["*"],
19
+ allow_credentials=True,
20
+ allow_methods=["*"],
21
+ allow_headers=["*"],
22
+ )
23
+
24
+ # 注册路由
25
+ app.include_router(comm_router, prefix="/api/v1")
26
+
27
+ @app.get("/")
28
+ async def root():
29
+ return {"message": "Human-Clone API"}
30
+
31
+ @app.get("/health")
32
+ async def health_check():
33
+ return {"status": "healthy"}
src/utils/__init__.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ """
2
+ 工具模块初始化
3
+ """
4
+
5
+ from .logger import setup_logger
6
+
7
+ __all__ = ["setup_logger"]
src/utils/logger.py ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ 日志工具
3
+ """
4
+
5
+ import logging
6
+ import sys
7
+ from typing import Optional
8
+
9
+
10
+ def setup_logger(
11
+ name: str,
12
+ level: str = "INFO",
13
+ format_string: Optional[str] = None
14
+ ) -> logging.Logger:
15
+ """设置日志器"""
16
+
17
+ if format_string is None:
18
+ format_string = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
19
+
20
+ logger = logging.getLogger(name)
21
+ logger.setLevel(getattr(logging, level.upper()))
22
+
23
+ # 避免重复添加handler
24
+ if not logger.handlers:
25
+ handler = logging.StreamHandler(sys.stdout)
26
+ formatter = logging.Formatter(format_string)
27
+ handler.setFormatter(formatter)
28
+ logger.addHandler(handler)
29
+
30
+ return logger
tests/__init__.py ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ """
2
+ 测试模块初始化
3
+ """
tests/fixtures/__init__.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ """
2
+ 测试数据模块初始化
3
+ """
4
+
5
+ from .test_data import create_test_entity_a, create_test_entity_b, get_test_messages
6
+
7
+ __all__ = ["create_test_entity_a", "create_test_entity_b", "get_test_messages"]
tests/fixtures/test_data.py ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ 测试数据fixture
3
+ """
4
+
5
+ from src.core import EntityInfo
6
+
7
+
8
+ def create_test_entity_a():
9
+ """创建测试实体A"""
10
+ return EntityInfo(
11
+ id="test-entity-a-001",
12
+ name="Test Entity A",
13
+ redis_host="localhost",
14
+ redis_port=6379,
15
+ redis_db=0,
16
+ channel="test-entity-a-001"
17
+ )
18
+
19
+
20
+ def create_test_entity_b():
21
+ """创建测试实体B"""
22
+ return EntityInfo(
23
+ id="test-entity-b-002",
24
+ name="Test Entity B",
25
+ redis_host="localhost",
26
+ redis_port=6379,
27
+ redis_db=0,
28
+ channel="test-entity-b-002"
29
+ )
30
+
31
+
32
+ def get_test_messages():
33
+ """获取测试消息列表"""
34
+ return [
35
+ "Hello from Test Entity A!",
36
+ "This is a test message",
37
+ "Human-Clone system working"
38
+ ]
tests/integration/__init__.py ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ """
2
+ 集成测试模块初始化
3
+ """
tests/integration/test_communication.py ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Human-Clone 系统集成测试
3
+ """
4
+
5
+ import sys
6
+ import os
7
+ sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
8
+
9
+ import unittest
10
+ import time
11
+ import threading
12
+ from datetime import datetime
13
+ from src.core import EntityInfo, RedisAdapter
14
+ from tests.fixtures import create_test_entity_a, create_test_entity_b, get_test_messages
15
+
16
+
17
+ class TestCommunication(unittest.TestCase):
18
+ """通讯集成测试类"""
19
+
20
+ def test_basic_communication(self):
21
+ """测试基础通讯功能"""
22
+ print("=== Human-Clone 系统通讯测试 ===")
23
+
24
+ # 创建测试主体
25
+ entity_a = create_test_entity_a()
26
+ entity_b = create_test_entity_b()
27
+
28
+ # 创建适配器
29
+ adapter_a = RedisAdapter(entity_a)
30
+ adapter_b = RedisAdapter(entity_b)
31
+
32
+ # 消息接收回调
33
+ received_messages = []
34
+
35
+ def message_callback(message):
36
+ received_messages.append(message)
37
+ print(f"[{message.receiver_id}] 收到来自 {message.sender_id} 的消息: {message.content}")
38
+
39
+ # 启动适配器
40
+ print("\n1. 启动适配器...")
41
+ if adapter_a.start() and adapter_b.start():
42
+ print("✓ 适配器启动成功")
43
+
44
+ # 注册回调
45
+ adapter_b.register_callback(message_callback)
46
+
47
+ # 等待连接建立
48
+ time.sleep(1)
49
+
50
+ # 发送测试消息
51
+ print("\n2. 发送测试消息...")
52
+ test_messages = get_test_messages()
53
+
54
+ for msg in test_messages:
55
+ if adapter_a.send_message(entity_b.id, msg):
56
+ print(f"✓ 消息已发送: {msg}")
57
+ time.sleep(0.5)
58
+
59
+ # 等待消息接收
60
+ print("\n3. 等待消息接收...")
61
+ time.sleep(2)
62
+
63
+ # 检查结果
64
+ print(f"\n4. 测试结果:")
65
+ print(f" 发送消息数: {len(test_messages)}")
66
+ print(f" 接收消息数: {len(received_messages)}")
67
+
68
+ if len(received_messages) == len(test_messages):
69
+ print("✓ 通讯测试通过!")
70
+ else:
71
+ print("✗ 通讯测试失败!")
72
+
73
+ else:
74
+ print("✗ 适配器启动失败")
75
+
76
+ # 清理资源
77
+ print("\n5. 清理资源...")
78
+ adapter_a.stop()
79
+ adapter_b.stop()
80
+
81
+ print("\n=== 测试完成 ===")
82
+
83
+
84
+ if __name__ == "__main__":
85
+ unittest.main()
tests/run_tests.py ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ 批量运行所有测试
3
+ """
4
+
5
+ import unittest
6
+ import sys
7
+ import os
8
+ sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
9
+
10
+
11
+ def run_unit_tests():
12
+ """运行单元测试"""
13
+ print("=== 运行单元测试 ===")
14
+ loader = unittest.TestLoader()
15
+ suite = loader.discover('tests/unit', pattern='test_*.py')
16
+ runner = unittest.TextTestRunner(verbosity=2)
17
+ result = runner.run(suite)
18
+ return result.wasSuccessful()
19
+
20
+
21
+ def run_integration_tests():
22
+ """运行集成测试"""
23
+ print("\n=== 运行集成测试 ===")
24
+ loader = unittest.TestLoader()
25
+ suite = loader.discover('tests/integration', pattern='test_*.py')
26
+ runner = unittest.TextTestRunner(verbosity=2)
27
+ result = runner.run(suite)
28
+ return result.wasSuccessful()
29
+
30
+
31
+ def run_all_tests():
32
+ """运行所有测试"""
33
+ print("Human-Clone 系统测试套件")
34
+ print("=" * 50)
35
+
36
+ unit_success = run_unit_tests()
37
+ integration_success = run_integration_tests()
38
+
39
+ print("\n=== 测试结果汇总 ===")
40
+ print(f"单元测试: {'✓ 通过' if unit_success else '✗ 失败'}")
41
+ print(f"集成测试: {'✓ 通过' if integration_success else '✗ 失败'}")
42
+
43
+ if unit_success and integration_success:
44
+ print("🎉 所有测试通过!")
45
+ return True
46
+ else:
47
+ print("❌ 存在测试失败")
48
+ return False
49
+
50
+
51
+ if __name__ == "__main__":
52
+ success = run_all_tests()
53
+ sys.exit(0 if success else 1)
tests/unit/__init__.py ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ """
2
+ 单元测试模块初始化
3
+ """
tests/unit/test_entities.py ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ 实体信息测试
3
+ """
4
+
5
+ import sys
6
+ import os
7
+ sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
8
+
9
+ import unittest
10
+ from src.core import EntityInfo
11
+
12
+
13
+ class TestEntityInfo(unittest.TestCase):
14
+ """测试EntityInfo类"""
15
+
16
+ def test_entity_creation(self):
17
+ """测试实体创建"""
18
+ entity = EntityInfo(
19
+ id="test-001",
20
+ name="Test Entity",
21
+ redis_host="localhost",
22
+ redis_port=6379,
23
+ redis_db=0,
24
+ channel="test-channel"
25
+ )
26
+
27
+ self.assertEqual(entity.id, "test-001")
28
+ self.assertEqual(entity.name, "Test Entity")
29
+ self.assertEqual(entity.redis_host, "localhost")
30
+ self.assertEqual(entity.redis_port, 6379)
31
+ self.assertEqual(entity.redis_db, 0)
32
+ self.assertEqual(entity.channel, "test-channel")
33
+
34
+ def test_auto_uuid_generation(self):
35
+ """测试UUID自动生成"""
36
+ entity = EntityInfo(
37
+ id="", # 空ID应该自动生成UUID
38
+ name="Test Entity",
39
+ redis_host="localhost",
40
+ redis_port=6379,
41
+ redis_db=0,
42
+ channel="test-channel"
43
+ )
44
+
45
+ self.assertIsNotNone(entity.id)
46
+ self.assertNotEqual(entity.id, "")
47
+
48
+ def test_auto_channel_assignment(self):
49
+ """测试channel自动赋值"""
50
+ entity = EntityInfo(
51
+ id="test-001",
52
+ name="Test Entity",
53
+ redis_host="localhost",
54
+ redis_port=6379,
55
+ redis_db=0,
56
+ channel="" # 空channel应该自动使用ID
57
+ )
58
+
59
+ self.assertEqual(entity.channel, "test-001")
60
+
61
+
62
+ if __name__ == "__main__":
63
+ unittest.main()
tests/unit/test_messages.py ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ 消息结构测试
3
+ """
4
+
5
+ import sys
6
+ import os
7
+ sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
8
+
9
+ import unittest
10
+ from datetime import datetime
11
+ from src.core import Message
12
+
13
+
14
+ class TestMessage(unittest.TestCase):
15
+ """测试Message类"""
16
+
17
+ def test_message_creation(self):
18
+ """测试消息创建"""
19
+ timestamp = datetime.now()
20
+ message = Message(
21
+ sender_id="sender-001",
22
+ receiver_id="receiver-001",
23
+ timestamp=timestamp,
24
+ content="Test message content"
25
+ )
26
+
27
+ self.assertEqual(message.sender_id, "sender-001")
28
+ self.assertEqual(message.receiver_id, "receiver-001")
29
+ self.assertEqual(message.timestamp, timestamp)
30
+ self.assertEqual(message.content, "Test message content")
31
+
32
+ def test_message_to_dict(self):
33
+ """测试消息转换为字典"""
34
+ timestamp = datetime.now()
35
+ message = Message(
36
+ sender_id="sender-001",
37
+ receiver_id="receiver-001",
38
+ timestamp=timestamp,
39
+ content="Test message"
40
+ )
41
+
42
+ message_dict = message.to_dict()
43
+
44
+ self.assertEqual(message_dict['sender_id'], "sender-001")
45
+ self.assertEqual(message_dict['receiver_id'], "receiver-001")
46
+ self.assertEqual(message_dict['timestamp'], timestamp.isoformat())
47
+ self.assertEqual(message_dict['content'], "Test message")
48
+
49
+ def test_message_from_dict(self):
50
+ """测试从字典创建消息"""
51
+ timestamp = datetime.now()
52
+ message_dict = {
53
+ 'sender_id': 'sender-001',
54
+ 'receiver_id': 'receiver-001',
55
+ 'timestamp': timestamp.isoformat(),
56
+ 'content': 'Test message'
57
+ }
58
+
59
+ message = Message.from_dict(message_dict)
60
+
61
+ self.assertEqual(message.sender_id, "sender-001")
62
+ self.assertEqual(message.receiver_id, "receiver-001")
63
+ self.assertEqual(message.content, "Test message")
64
+ # 比较datetime对象可能会有毫秒级差异,所以转换为字符串比较
65
+ self.assertEqual(message.timestamp.isoformat(), timestamp.isoformat())
66
+
67
+
68
+ if __name__ == "__main__":
69
+ unittest.main()