Files
sino-mci/docs/superpowers/specs/2026-07-10-channel-service-mci-server-integration-design.md

17 KiB
Raw Blame History

channel-service 与 mci-server 双向集成设计

需求来源:继续完成 pywechat 客户端功能,打通 Python 渠道服务与 Java 消息中台的双向通信。
日期2026-07-10
状态:待实现
关联文档docs/architecture/2026-07-06-message-customer-interaction-design.md


1. 目标与范围

1.1 目标

channel-servicePython FastAPImci-serverJava Spring Boot完成双向 HTTP 集成:

  • 下行mci-server → channel-servicemci-server 继续通过现有 REST API 调用 channel-service 的发送、登录控制等指令。
  • 上行channel-service → mci-serverchannel-service 把心跳、登录/在线状态变更、发送结果、异常事件持久化到本地 SQLite并由后台 worker 可靠地推送到 mci-server。
  • 可靠性:服务重启不丢事件;网络异常按指数退避重试;超次进死信;本地事件保留 10 天后自动清理。

1.2 范围

  • channel-service新增事件模型、SQLite 持久化、后台事件发布 worker、mci-server HTTP 客户端、配置项扩展、API 行为调整。
  • mci-server:新增 /api/v1/channel-account/event 接收端点,处理心跳/状态/发送结果事件并更新 mci_channel_accountmci_send_record
  • 不在本期范围
    • 真实 pywechat/pywinauto 接入(仍为 mock
    • RabbitMQ/MQ 等外部消息总线。
    • 入站消息从 channel-service 回传(本期只传运行态事件和发送结果)。

2. 整体架构

┌─────────────────────────────────────────────────────────────┐
│                        mci-server                            │
│  Java 21 + Spring Boot 4.1.x                                │
│  POST /api/v1/channel-account/event                         │
└───────────────────────┬─────────────────────────────────────┘
                        │  HTTP 上行事件
                        ▼
┌─────────────────────────────────────────────────────────────┐
│                     channel-service                          │
│  Python 3.11 + FastAPI                                       │
│                                                              │
│  ┌─────────────┐    ┌──────────────────┐    ┌────────────┐ │
│  │  HTTP API   │───►│  业务操作 + 事件   │───►│ outbound_  │ │
│  │             │    │  写入 SQLite      │    │ events     │ │
│  └─────────────┘    └──────────────────┘    └─────┬──────┘ │
│                                                   │         │
│                              ┌────────────────────┘         │
│                              ▼                              │
│                     ┌─────────────────┐                     │
│                     │ EventPublisher  │                     │
│                     │ 单线程 worker   │                     │
│                     └────────┬────────┘                     │
│                              │                              │
│                              ▼                              │
│                     ┌─────────────────┐                     │
│                     │ mci_server_client│                    │
│                     │ HTTP POST        │                    │
│                     └─────────────────┘                     │
└─────────────────────────────────────────────────────────────┘

核心原则:状态变更/发送操作与事件写入在同一个 SQLite 事务内完成,保证原子性。


3. channel-service 设计

3.1 新增模块

模块 路径 职责
事件模型 app/models/event.py OutboundEvent 数据类、事件类型枚举、payload schema
事件存储 app/repositories/event_store.py SQLite 建表、CRUD、事务、清理
事件发布 app/services/event_publisher.py 后台单线程 worker、重试、死信
mci-server 客户端 app/clients/mci_server_client.py HTTP POST 到 mci-server
配置扩展 app/config.py 新增事件存储、重试、清理相关配置

3.2 SQLite 事件表 outbound_events

CREATE TABLE IF NOT EXISTS outbound_events (
    id TEXT PRIMARY KEY,
    event_type TEXT NOT NULL,
    payload TEXT NOT NULL,
    status TEXT NOT NULL DEFAULT 'pending',
    attempt_count INTEGER NOT NULL DEFAULT 0,
    next_retry_at TIMESTAMP NOT NULL,
    created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    last_error TEXT
);

CREATE INDEX IF NOT EXISTS idx_status_next_retry
    ON outbound_events(status, next_retry_at);

CREATE INDEX IF NOT EXISTS idx_created_at
    ON outbound_events(created_at);

字段说明:

字段 类型 说明
id TEXT PK UUID v4event_id
event_type TEXT heartbeat / login_status_changed / online_status_changed / send_result / error
payload TEXT JSON 字符串,见第 3.3 节
status TEXT pending / retrying / delivered / dead_letter
attempt_count INTEGER 已尝试次数,上限 max_retry
next_retry_at TIMESTAMP 下次重试时间;pending 事件为创建时间
created_at TIMESTAMP 创建时间,用于 10 天清理
updated_at TIMESTAMP 更新时间
last_error TEXT 最近一次失败摘要

3.3 事件 payload 统一结构

所有事件统一外层:

{
  "event_id": "evt_20260710_xxx",
  "event_type": "heartbeat",
  "account_id": "wx-bot-01",
  "tenant_code": "rescue",
  "timestamp": "2026-07-10T10:00:00Z",
  "data": { }
}

data 按事件类型定义:

heartbeat

{
  "login_status": "success",
  "online_status": "online",
  "risk_level": 10,
  "sent_count_today": 5,
  "server_host": "win-svr-01"
}

login_status_changed

{
  "previous": "pending",
  "current": "success",
  "qr_code_url": "https://mock.pywechat.qrcode/...",
  "error_message": null
}

online_status_changed

{
  "previous": "offline",
  "current": "online"
}

send_result

{
  "task_id": "task_001",
  "record_id": 1001,
  "conversation_id": "room-001",
  "success": true,
  "message_id": "msg-123",
  "channel_result": {},
  "error": null
}

说明:record_id 由 mci-server 在调用 send API 时传入channel-service 原样回传,用于 mci-server 更新 mci_send_record

error

{
  "operation": "send_text",
  "error_code": "ACCOUNT_OFFLINE",
  "error_message": "account wx-bot-01 not online"
}

3.4 API 行为变更

API 变更
POST /v1/wechat-personal/login/start 创建/更新账号、生成二维码,并在同一事务写入 login_status_changed 事件
POST /v1/wechat-personal/login/{id}/confirm 确认登录后,在同一事务写入 login_status_changed + online_status_changed
POST /v1/wechat-personal/login/{id}/expire 二维码过期后,写入 login_status_changed 事件
POST /v1/wechat-personal/send/text 改为异步受理:立即返回 {"accepted": true, "message_id": "..."};真实发送完成后写入 send_result 事件
POST /v1/wechat-personal/send/image 同上
POST /v1/heartbeat 更新本地状态,并在同一事务写入 heartbeat 事件
POST /v1/admin/retry-dead-letter(新增) 手动把 dead_letter 状态重置为 retrying,用于人工恢复

3.5 发送异步化与风控

当前仍为 mock 客户端,但接口行为需与真实 pywechat 一致:

  1. send_text / send_image 收到请求后,先校验账号在线、风控等级。
  2. 校验通过后返回 accepted=true,并立即写入 send_result 事件。
  3. mock 场景下该事件直接表示成功;真实 pywechat 场景下,发送完成后用同一 event_id 更新 send_result 事件内容,或删除旧事件后写入最终结果事件。

风控处理

  • 风控等级 risk_level >= 80 的账号,发送请求直接返回失败,不进入重试。
  • 因微信风控导致的发送失败(如 error_codeRISK_CONTROL_BLOCKEDACCOUNT_BANNED),事件直接标记为 dead_letter,不再重试。
  • 因网络超时、mci-server 不可达等临时错误,才进入指数退避重试。
  • sent_count_today 每日重置channel-service 在心跳中上报mci-server 可据此做策略判断。

3.6 EventPublisher 后台 worker

  • 单线程执行:使用一个 asyncio 任务循环消费,避免并发导致 SQLite 锁竞争或事件乱序。
  • 扫描频率:每 5 秒一次。
  • 每次拉取:最多 100 条 status IN ('pending','retrying') AND next_retry_at <= now(),按 next_retry_at 升序。
  • 推送目标{mci_server_url}/api/v1/channel-account/event
  • 成功status='delivered'attempt_count 不变。
  • 失败
    • attempt_count += 1
    • next_retry_at = now + base_delay * 2^(attempt_count - 1)(首次重试为 base_delay,第二次为 2*base_delay,依此类推)
    • status='retrying'
    • last_error 记录 HTTP 状态码或异常摘要
    • attempt_count >= max_retry,则 status='dead_letter'
  • 最大重试次数:由配置 max_retry 控制,默认 5,上限 10(代码中硬上限,防止配置错误)。
  • 服务启动:从 main.py 启动时创建 worker启动后自动恢复 pending / retrying 事件。
  • 服务关闭:优雅关闭时等待当前批次处理完成(超时 5 秒)。

3.7 数据清理

  • 每天凌晨 3:00 启动清理任务(或每次 worker 循环顺带清理)。
  • 删除 status IN ('delivered','dead_letter')created_at < CURRENT_TIMESTAMP - INTERVAL '10 days' 的事件。
  • 保留 pending / retrying 事件,无论创建时间。

3.8 配置项

# mci-server 地址
MCI_SERVER_URL=http://localhost:8080/msg-platform

# SQLite 事件库路径
EVENT_STORE_PATH=./data/events.db

# 心跳间隔(秒)
HEARTBEAT_INTERVAL_SECONDS=30

# 最大重试次数(代码硬上限 10
MAX_RETRY=5

# 基础重试延迟(秒)
BASE_RETRY_DELAY_SECONDS=5

# 事件保留天数
EVENT_RETENTION_DAYS=10

# 后台 worker 扫描间隔(秒)
EVENT_PUBLISH_INTERVAL_SECONDS=5

4. mci-server 设计

4.1 新增端点

POST /api/v1/channel-account/event

接收 channel-service 推送的统一事件。

请求头

Content-Type: application/json

请求体:见第 3.3 节统一 payload。

处理逻辑

event_type 处理
heartbeat 根据 account_id 查询 mci_channel_account;更新 login_statusonline_statuslast_heartbeatrisk_levelsent_count_today
login_status_changed 更新 mci_channel_account.login_status;如果 current=success 则同时更新 online_status=1
online_status_changed 更新 mci_channel_account.online_status
send_result 根据 record_id 查询 mci_send_record;更新 send_statussuccess=true 则为 2否则 3finish_timechannel_result;成功后调用 SendCallbackService.sendCallback() 向业务系统回调
error 记录告警日志,可扩展发送告警通知

状态字段映射channel-service 字符串 → mci-server 整数):

channel-service 字段 mci-server 字段 映射关系
login_status mci_channel_account.login_status pending=0, scanning=0, success=1, expired=2, failed=3
online_status mci_channel_account.online_status offline=0, online=1, error=2

账号定位:使用 tenant_code + account_id 联合查询 mci_channel_account,确保租户隔离。

幂等

  • 使用 event_id 作为幂等键。
  • 第一次收到时写入 mci_channel_event 去重表;重复事件直接返回 200。

响应

{
  "code": 0,
  "message": "ok",
  "data": null,
  "request_id": "..."
}

4.2 保持现有端点

  • POST /api/v1/channel-account/heartbeat:保持兼容,但建议新接入走 /event
  • POST /api/v1/channel-account/init:继续调用 WechatPersonalChannelAdapter,由 adapter 调用 channel-service /v1/wechat-personal/login/start

4.3 发送流程补齐

为了让 send_result 能回写到正确的 mci_send_record,需要在 mci-server 调用 channel-service 发送时传入 record_id

  1. SendService.dispatchRecord(record) 调用 ChannelAdapter.sendMessage(request)
  2. SendMessageRequest.extra 中带上 record_id
  3. WechatPersonalChannelAdapter.sendMessage()record_id 放入 body调用 channel-service /v1/wechat-personal/send/text/send/image
  4. channel-service 异步完成后,通过 /event 回传 send_result 并携带 record_id

4.4 数据库表扩展

4.4.1 新增幂等表 mci_channel_event

用于防止 channel-service 重试导致重复处理。

字段 类型 说明
event_id VARCHAR PK channel-service 事件 ID
account_id VARCHAR 账号 ID
event_type VARCHAR 事件类型
create_time DATETIME 接收时间

索引:CREATE INDEX idx_account_time ON mci_channel_event(account_id, create_time);

清理策略:保留 30 天后删除(可配置)。


5. 错误处理与边界

场景 处理
mci-server 不可达 事件留在 SQLiteworker 按指数退避重试
服务重启 启动时恢复 worker继续推送 pending / retrying 事件
payload 序列化失败 直接标记 dead_letter,不阻塞其他事件
mci-server 返回业务错误(如账号不存在) 视为失败重试;若连续失败达到上限,进 dead_letter
微信风控/账号被封 对应 error_code 直接标记 dead_letter,不重试
SQLite 磁盘满/损坏 worker 记录错误并停止推送;监控告警
单条事件推送超时 使用 httpx timeout默认 10s超时即失败进入重试
重试次数达到上限 status='dead_letter',记录 last_error,等待人工处理或手动重试
事件堆积 worker 每批最多 100 条;如果持续堆积,考虑后续改为批量 POST 或引入 MQ

6. 测试策略

6.1 channel-service 单元测试

  • test_event_store.py:验证 SQLite CRUD、事务、状态流转、清理。
  • test_event_publisher.pymock mci-server HTTP验证重试、死信、幂等。
  • test_send_async.py:验证 send API 改为异步受理后,事件正确写入 outbound_events
  • test_heartbeat_event.py:验证心跳接口产生事件。

6.2 mci-server 单元测试

  • ChannelAccountEventControllerTest:使用 MockWebServer 或 MockMvc 接收 channel-service 事件,验证:
    • heartbeat 更新 mci_channel_account
    • send_result 更新 mci_send_record
    • event_id 幂等

6.3 联调测试

  • docker-compose 启动 mci-server + channel-service。
  • 调用 mci-server 发送 API。
  • 验证 channel-service 收到指令、产生 send_result 事件、推回 mci-server。
  • 验证 mci-server 更新 SendRecord 并向业务系统 callback_url 回调。

7. 部署与配置

7.1 channel-service 新增环境变量

environment:
  MCI_SERVER_URL: http://mci-server:8080/msg-platform
  EVENT_STORE_PATH: /app/data/events.db
  HEARTBEAT_INTERVAL_SECONDS: "30"
  MAX_RETRY: "5"
  BASE_RETRY_DELAY_SECONDS: "5"
  EVENT_RETENTION_DAYS: "10"
  EVENT_PUBLISH_INTERVAL_SECONDS: "5"

7.2 数据卷

volumes:
  - channel-service-data:/app/data

8. 后续扩展

  • 入站消息从 channel-service 回传 mci-server复用 /event 新增 message_received 类型)。
  • 批量事件上报:把多条事件打包为一个 HTTP 请求,降低 mci-server 压力。
  • 当事件量增大时,可考虑用 RabbitMQ 替代 SQLite + HTTP worker。
  • 真实 pywechat 接入时,把 mock 客户端替换为 pywechat/pywinauto 驱动,事件模型保持不变。

设计完成,待评审。