功能说明文档.md 13 KB

功能说明文档

更新时间: 2026-06-16


目录

  1. 项目概述
  2. 数据获取体系
  3. 群数据
  4. 群成员数据
  5. 消息数据
  6. 联系人数据
  7. 定时任务
  8. 数据完整性边界

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 手动触发联系人同步