功能说明文档
更新时间: 2026-06-16
目录
- 项目概述
- 数据获取体系
- 群数据
- 群成员数据
- 消息数据
- 联系人数据
- 定时任务
- 数据完整性边界
1. 项目概述
lami-qiwe 是一个 QiWe 开放平台数据同步服务,通过 Webhook 实时接收 + API 主动拉取 双通道,将企微平台的群、成员、消息、联系人数据全量同步到 PostgreSQL 数据库(Parse Server)。
核心设计原则
- 实时优先:Webhook 事件 3 秒内响应,数据处理异步进行
- 补漏机制:定时 API 拉取弥补 Webhook 可能丢失的数据
- 逻辑删除:成员离开、群解散、联系人删除均保留历史记录
- 去重保障:消息通过
msgUniqueIdentifier 全局去重
- 渐进补全:即时获取不到的字段(昵称、群主名)通过定时回填逐步补齐
2. 数据获取体系
┌─────────────────────────┐
│ QiWe 开放平台 │
└───────────┬─────────────┘
│
┌───────────────────┼───────────────────┐
│ │ │
实时推送 主动拉取 主动拉取
(Webhook) (定时任务) (手动触发)
│ │ │
▼ ▼ ▼
┌────────────────┐ ┌────────────────┐ ┌────────────────┐
│ webhook │ │ scheduler │ │ POST /api/qiwe │
│ .controller.ts │ │ .service.ts │ │ /sync-groups │
│ │ │ │ │ /sync-messages │
│ 群创建/改名 │ │ 每3min 消息同步 │ │ /sync-contacts │
│ 成员变动/消息 │ │ 每1h 群同步 │ │ │
│ 系统事件 │ │ 每6h 联系人 │ │ │
└────────┬───────┘ └───────┬────────┘ └───────┬────────┘
│ │ │
└───────────────────┼───────────────────┘
│
┌───────────▼───────────┐
│ PostgreSQL │
│ (Parse Server) │
│ │
│ GroupChat / Member │
│ Message / Contact │
│ SyncCursor / Log │
└───────────────────────┘
两种通道的职责分工
| 通道 |
职责 |
优势 |
劣势 |
| Webhook |
实时事件:群创建/改名、成员加入/离开/退群、群主转让、群解散、聊天消息 |
实时性高,事件语义明确 |
可能因网络丢包、平台限流丢失 |
| API 定时拉取 |
全量同步:群目录、群详情、增量消息、联系人 |
可补漏、获取历史数据 |
有频率限制,非实时 |
3. 群数据
3.1 群基本信息获取
| 来源 |
途径 |
获取内容 |
覆盖范围 |
| Webhook 群创建 (1006) |
实时推送 |
roomId, roomName, ownerId, ownerName, memberCount, 初始成员列表 |
机器人所在的所有群 |
API getRoomList |
定时 + 手动 |
roomId, roomName, roomOwnerId, roomMemberCount |
仅机器人创建的群 |
API getSessionList |
定时 + 手动 |
sessionId(即 roomId) |
全部群聊会话(含非机器人创建的) |
API batchGetRoomDetail |
定时 + 手动 |
roomName, ownerId, memberCount, 成员 ID 列表 |
指定 roomId 的群 |
3.2 群同步流程(三阶段)
阶段1: getRoomList (全量分页)
└─ 机器人创建的群 → upsert GroupChat
└─ 耗时: 约 N/50 次请求
阶段2: getSessionList (会话列表)
└─ 发现非机器人创建的群 → batchGetRoomDetails 取名称 → 创建 GroupChat
└─ 耗时: 1 + 1 次请求
阶段3: enrichGroupsAndMembers (详情补全 + 成员同步)
└─ 所有群 → batchGetRoomDetails(全部roomId, 每批30)
└─ 所有群 → containedIn 批量查 GroupMember (1次查询)
└─ 内存匹配 → 补全占位群名、更新ownerId、ownerPhone回填(via _User)、memberCount更新
└─ 成员批量 upsert:新成员创建(无昵称等消息补全)/ 已有成员更新昵称和状态
└─ 耗时: N/30 + 1 次请求
3.3 群主信息 (ownerName / ownerPhone) 的获取
ownerName 在 三个时机 写入:
| 时机 |
来源 |
触发条件 |
| Webhook 群创建 |
event.senderName |
机器人被拉入新群时 QiWe 推送 |
| Webhook 群主转让 |
查 GroupMember.nickname |
群主转让事件推送 |
定时 backfillOwnerNames |
GroupMember 表中群主的 nickname |
群主曾发言过(有 nickname 记录) |
ownerPhone 在 两个时机 写入:
| 时机 |
来源 |
触发条件 |
| Webhook 群创建 |
通过 wecomUserId 查 _User.phone |
群主 userId 在 _User 表中有 wecomUserId 匹配 |
定时 enrichGroupsAndMembers |
通过 wecomUserId 查 _User.phone |
同上,补全 API 同步发现的群 |
限制:如果群主从未在消息中出现过,ownerName 将无法获取。如果群主未在系统中注册(_User 表无对应记录),ownerPhone 将无法获取。
4. 群成员数据
4.1 为什么必须依赖 Webhook 回调
群成员数据的完整性和实时性高度依赖 Webhook 回调,原因如下:
API 能做的(有限)
| API |
返回内容 |
局限 |
batchGetRoomDetail |
成员 userId 列表 |
不返回昵称(官方文档写明);仅是查询时刻的快照;每次调用消耗 API 配额 |
getRoomMemberChanges |
新增/移除的成员 userId + 时间 |
需精确知道查询时间范围;已封装但未接入定时任务 |
API 做不到的(必须 Webhook)
| 需求 |
为何 API 不行 |
Webhook 事件 |
| 实时感知成员加入 |
无"成员加入"查询 API |
msgType=1002 |
| 实时感知成员被移出 |
无"成员移出"查询 API |
msgType=1003 |
| 实时感知成员主动退群 |
无"退群"查询 API |
msgType=1005 |
| 获取成员昵称 |
所有 API 均不返回成员昵称 |
聊天消息中的 senderName |
| 群创建时的初始成员 |
API 只能事后查询快照 |
msgType=1006 携带完整初始成员列表 |
结论
成员 userId 列表 = Webhook(实时) + API(补漏快照)
成员 昵称 = 仅消息 senderName (Webhook + API消息同步)
成员 状态 = Webhook(实时加入/离开) + API(逻辑删除恢复)
如果没有 Webhook 回调,将面临:
- 成员加入/离开延迟最多 1 小时(等下次定时群同步)
- 成员昵称大面积缺失(API 不返回昵称,只能等消息同步提取 senderName,但消息同步也只能拿到有过发言的人)
- 群创建时的批量成员无法一次性入库
4.2 成员昵称的获取链路
消息到达
│
├─ Webhook 实时推送 ──→ senderName 写入 Message.senderName
│ └─ 同时更新 GroupMember.nickname
│
└─ API syncMsgPage ──→ senderName 写入 Message.senderName
└─ applySenderNames() 批量更新 GroupMember.nickname
定时回填:
backfillMissingSenderNames()
└─ Message.senderName 为空的 → 查同群同 userId 的其他消息 → 回填
永久限制:从未发言的成员(潜水用户、微信用户)的 nick name 永远无法获取,这不是系统缺陷,是 QiWe 平台的数据边界。
5. 消息数据
5.1 消息获取双通道
| 通道 |
覆盖 |
时效 |
| Webhook (cmd=15000, msgType 非群事件) |
文本、图片、语音等所有消息类型 |
实时(秒级) |
API syncMsgPage 增量同步 |
同上 |
每 3 分钟补漏 |
5.2 消息处理流程
消息到达(Webhook/API)
│
├─ 去重: msgUniqueIdentifier 查 Message 表
│ └─ 已存在 → 跳过
│
├─ senderName 补充:
│ ├─ 消息自带 senderName → 直接使用
│ ├─ 消息无 senderName → 查 GroupMember 表
│ └─ GroupMember 也无 → 留空等定时回填
│
├─ 入库 Message
│
├─ 更新群计数器: messageCountTotal+1, messageCountToday+1, lastActivityAt 更新
│
├─ 更新 GroupMember.nickname (如果有 senderName)
│
└─ 发现未知群 → 创建 GroupChat
5.3 增量同步游标
MessageSyncCursor 表记录最后一次同步到的消息 seq,每轮增量同步从游标位置开始拉取新消息,同步完成后更新游标。这确保:
- 不重复拉取已处理的消息
- 每次只拉取增量数据
- Webhook 丢失的消息会被自动补回
6. 联系人数据
6.1 同步方式
联系人仅通过 API 全量同步,不支持 Webhook 实时推送。
| 类型 |
API |
周期 |
| 外部联系人 |
getExternalContactList 分页遍历 |
每 6 小时 |
| 内部联系人 |
getInternalContactList 分页遍历 |
每 6 小时 |
6.2 逻辑删除与恢复
- 联系人被删除 → status =
'inactive'(保留历史记录)
- 同一联系人再次出现 → status 自动恢复为
'active'
- QiWe 文档建议"保存后不用反复调用,根据回调保存"——但目前未使用联系人回调
7. 定时任务
| 周期 |
任务 |
内容 |
| 启动 30 秒后 |
首次全量同步 |
消息 + 群 + 联系人一次性全量同步 |
| 每 3 分钟 |
scheduledMessageSync |
增量消息同步 (maxPages=10, limit=100),含群计数器更新 |
| 每 1 小时 |
scheduledGroupSync |
群目录 → 会话发现 → 群详情补全 + 成员同步 → ownerName 回填 → senderName 回填 |
| 每 6 小时 |
scheduledContactSync |
外部联系人全量 + 内部联系人全量 |
| 每日 0 点 |
scheduledResetTodayCounter |
将所有 GroupChat 的 messageCountToday 重置为 0 |
8. 数据完整性边界
可以 100% 获取的数据
- 群 roomId、roomName(Webhook + API 双通道)
- 群 ownerId(Webhook 群创建 + API 详情)
- 群 memberCount(Webhook 实时维护 + API 补全)
- 群 messageCountTotal/messageCountToday/lastActivityAt(Webhook + 消息同步自动维护,每日重置 Today)
- 消息内容、发送时间(Webhook 实时 + API 补漏)
- 联系人基础信息(API 全量同步,除名外所有字段)
- 群成员 userId 列表(Webhook 实时 + API 快照)
无法保证 100% 的数据(平台限制)
| 数据 |
无法获取的场景 |
原因 |
| 成员昵称 (nickname) |
从未发言的成员 |
昵称唯一来源是消息 senderName,API 不返回 |
| 群主名称 (ownerName) |
群主从未发言 |
回填依赖 GroupMember 中群主的 nickname |
| 微信用户名称 |
微信用户在企微群中 |
企微 API 无法获取微信用户信息 |
| 成员实时变动 |
Webhook 丢包期间 |
无 API 可查询成员变动历史 |
系统的应对策略
首次获取 持续补充 天花板
──────── ──────── ──────
成员userId Webhook/API快照 Webhook实时变动 ≈100%
成员昵称 消息senderName 每条消息更新 仅发言者
群主名称 Webhook群创建 backfillOwnerNames 需群主发言
群主手机 Webhook群创建 enrichGroupsAndMembers 需_User注册
群计数器 backfill脚本 Webhook+消息同步实时 ≈100%
联系人 全量API同步 每6h刷新 ≈100%
附录:API 接口速查
| 方法 |
路径 |
说明 |
| GET |
/api/health |
健康检查 |
| GET |
/api/qiwe/status |
QiWe 配置状态 |
| POST |
/api/qiwe/webhook |
QiWe Webhook 回调接收 |
| POST |
/api/qiwe/sync-groups |
手动触发群全量同步 |
| POST |
/api/qiwe/sync-messages?maxPages=10&limit=100 |
手动触发消息同步 |
| POST |
/api/qiwe/sync-contacts |
手动触发联系人同步 |