「企业微信会话存档」这个词被搜的时候,底下往往压着三种不同的诉求,而它们的数据范围、保留期和访问权限完全不一样。把三者混成一句「把聊天记录都存下来」,结果通常是存了远超需要的数据、付了远超必要的合规成本,真要查的时候还查不到。这篇按归集、检索、留存三段讲工程做法,并把合规边界写清楚 —— 消息链路部分按 wecomapi 的事件接入方式描述。
先把三种诉求拆开
把需求方叫过来问一句「你要用这些数据干什么」,答案基本落在三类里,而这三类几乎没有共同的技术方案。
- 合规留档:为满足外部监管或行业规则而留存。范围由规则定,保留期通常最长,访问要严格受限并全程审计。
- 业务可追溯:客诉复盘、服务质检、纠纷举证。真正要的是「某个客户某段时间的往来」,范围窄、时效近、查询频率低。
- 数据分析:转化归因、话术效果、响应时长。它要的是聚合指标和结构化元数据,绝大部分场景根本不需要正文。
这一步的判断很值钱:大多数团队嘴上说要第一类,实际在用第二类,而第三类用元数据就能满足。把范围从「全量正文长期留存」收窄到「特定范围可追溯 + 全量元数据」,存储成本和合规暴露面会同时降一个量级 —— 这是整篇里性价比最高的一个决定,而且只需要开一次会。
有个前置绕不过去:企业微信会话数据涉及员工与客户的通信内容,这类能力普遍要求企业侧显式开通、并让相关成员知情。开通条件、适用范围与告知义务以官方规则和你们自己的合规评审为准。它是设计输入,不是上线前补的一张表 —— 顺序反了,方案可能整体推倒。
归集:不丢、不重、不乱序,按这个优先级
归集这一段的目标只有一个:落下来的东西能还原一条完整的会话时间轴。三种失效方式按严重程度排序是丢了(不可恢复)、乱序(可修但要重排)、重了(最好处理),工程精力也该按这个顺序分配。
写入路径要和业务处理路径分开。用 wecomapi 的事件回调拿到消息后,先快速 ACK 再异步处理是固定动作;归集要在这条异步链路上单独分一支,只做一件事 —— 把原始事件原样落进追加写的存储,不解析、不做业务判断。理由很实际:业务逻辑会改、会出缺陷,改坏了可以重放;原始数据丢了就再也回不来。
// 示意逻辑:事件字段与端点以 wecomapi 文档为准,n.* 是归一化后的自有结构
onEvent(async (raw) => {
await ack(); // 先快速 ACK,重活一律异步
await bus.publish("archive.raw", raw); // 支线一:原样落档,不解析
await bus.publish("biz.message", raw); // 支线二:业务处理,可失败重放
});
async function onArchive(raw) {
const n = normalize(raw);
if (await seen(n.eventId)) return; // 幂等:重复投递直接丢弃
const refs = await blob.putAll(n.files); // 附件进对象存储,主表只留引用
await store.append({
conv: n.convId,
at: n.sentAt, // 时间轴按发生时间排,不按到达顺序
text: n.text,
refs,
});
}两条纪律值得单独强调。时间轴要按事件自带的发生时间排,不按到达顺序 —— 网络抖动和重试会让到达顺序失真,同时要允许一个迟到窗口,窗口内的插入是正常现象而不是异常。正文和附件必须分开存:附件走对象存储、主表只留引用与元数据,否则主库撑不过几个月,备份和恢复也会变得不可操作。
检索:别在主存储上直接做全文检索
归集存储和检索索引是两种东西,混成一个是这一段最常见的错误。主存储要的是写入吞吐、追加不改、成本低、能冷热分层;检索要的是低延迟随机查询和可重建。用同一套引擎同时满足这两组要求,代价是两边都做不好,而且成本还比分开做高。
先看真实的查询形状再建索引。实践中两个维度覆盖绝大多数请求:按会话加时间窗,用于复盘一次服务过程;按人加时间窗,用于处理投诉或离职交接。关键词全文检索是第三位需求,成本却是前两者的数倍 —— 第一版不做,等有人真的提出来、并且说得出频率再说。
- 主存储:按会话与时间分区,追加写,超过热区的分区转冷存。
- 检索索引:只放用于定位的字段(会话、参与人、时间、消息类型),不放正文全文。
- 索引必须能从主存储完整重建 —— 它是缓存,不是账本。这一条决定了索引出问题时你是重建一次,还是丢一批数据。
还有一个容易被忽略的维度:账号。多账号场景下同一个客户可能出现在不同员工的会话里,检索必须支持跨账号聚合。用 wecomapi 时账号本身就是订阅与隔离的单位,归集时把账号标识作为一等字段落进去,比事后从消息内容里反推可靠得多,也省掉一次全量回填。
留存与删除:保留期是合规决定,不是存储决定
「存储便宜,多存两年」是这一段最贵的错误。多存一天就是多一天的暴露面,一次数据事故的成本和存储费根本不在一个量级。保留期应该由合规要求和业务举证需要倒推,而不是由磁盘价格决定。
更实用的做法是分级保留:元数据(谁在什么时候和谁说过话、多少条、响应多久)体积小、敏感度低,可以长留,分析需求基本靠它满足;正文体积大、敏感度高,按举证需要设一个短得多的期限;附件最大,期限可以再短一档。三档在归集时就按不同存储分开写并各自带过期策略,而不是先合着存、到期再拆 —— 后者需要全量扫描重整,数据量上来根本跑不动。
- 1到期删除要能证明。删除本身也是要留痕的操作:删了哪个范围、依据哪条策略、谁批准、什么时候执行。只删数据不留记录,等于无法回答审计问题。
- 2导出是一次数据出域,必须走审批并留痕。范围限定到具体会话和时间窗,不要提供「导出全部」这种入口。
- 3访问控制要到人到会话,不是到系统。「运营团队都能查」这种粒度,出事时无法定位到具体的人。
- 4加密与密钥分离。数据和密钥落在同一处等于没加密,这条在自建存储时最常被跳过。
合规边界:把不做的事也写清楚
这一段没有取舍空间,写清楚反而省事。可以做的:在企业按官方要求开通、相关成员已被告知的前提下,按最小必要范围归集;对访问全程审计;按既定保留期到点删除;只把数据用于告知过的用途。落到实现上,在 wecomapi 侧把订阅的事件类型收窄到确实要用的那几类,是范围控制里最省事的一道闸 —— 没订阅的数据不会进来,也就不需要为它做任何合规设计。
不做的同样明确:不在未开通、未告知的情况下采集通信内容;不超出告知范围留存或使用;不把检索与导出权限开放给与该用途无关的角色;不尝试任何绕过平台既定机制获取数据的做法 —— 这类做法既不合规,工程上也不可持续,平台侧任何一次变更都会让它整体失效,而你还得为已经落库的数据负责。
会话数据是这套系统里最敏感的一类资产,范围收窄比架构做漂亮更有价值。开通前置与可用范围以官方规则和你们的合规评审为准,事件类型与字段以 wecomapi 线上接口文档为准,本文示意代码只表达结构,不要照抄上生产。
常见问题
- 企业微信会话存档必须走官方开通流程吗?
- 涉及员工与客户通信内容的留存,通常都有明确的开通前置与告知义务。这不是技术上能不能拿到数据的问题,而是能不能合法留存和使用的问题。具体条件以官方规则和你们自己的合规评审为准,建议在方案设计阶段就确认掉这一条,而不是等到上线前 —— 结论如果是否定的,整套存储设计都要重来。
- 只做业务追溯,需要全量存正文吗?
- 多数情况下不需要。业务追溯的实际查询形状是「某个客户某段时间的往来」,把全量元数据长留、正文按较短期限保留,就能覆盖绝大部分客诉复盘与质检场景,同时把存储成本和暴露面一起降下来。真正需要全量长期留存的是合规留档那一类,范围由外部规则定,不由团队自己决定。
- 会话归集怎么和消息处理链路共用一套接入?
- 共用事件入口,分开消费链路。用 wecomapi 的事件回调收到消息后先快速 ACK,再把同一份原始事件分发给两条独立的消费链:归集只管原样落档、做幂等和按发生时间排序,业务链路允许失败重试与重放。两条链互不影响,业务逻辑改坏了可以从原始档重放,归集出问题也不会拖垮实时业务。
准备好动手了?
精确字段、鉴权与端点以线上文档为准;可在控制台创建密钥后联调。
