做企微二次开发时最头疼的一件事:员工在 CRM 里有个内部ID,客户在企微里有个external_userid,两边对不上。我上周刚遇到——客户A先被销售李添加成好友(external_userid=ext_001),后来又加了销售张(external_userid=ext_002),CRM 里两个不同的外部用户,其实是同一个微信用户。
先理清楚到底有哪些ID
ID | 归属 | 来源 | 特点 |
|---|---|---|---|
| 内部员工 |
| 企微内唯一,永不变 |
| 外部客户 | 客户列表、Webhook事件 | 同一微信用户被不同员工添加,会有多个external_userid |
| 微信用户 | 微信OAuth/JS-SDK | 单应用内唯一 |
| 微信用户 | 微信开放平台 | 跨应用可识别同一用户 |
映射方案一:靠归属关系(推荐)
企微客户有个归属员工ownerUserId,在拉客户列表时会一起返回:
resp = requests.get( f"{BASE}/wx-api/api/contact/getExternalContactList", headers=HEADERS, params={"cursor": cursor, "limit": 50} ) # 返回每条客户记录里有 externalUserId + ownerUserId(归属员工的uin)拿到之后在自己的数据库里建三张表(Eyun 返回的 id 全部是 int64 类型,存的时候用 bigint 不要用 varchar):
-- 内部员工 staff(userid, name, department) -- 外部客户(去重后,一个微信用户一条) customer(customer_id, unionid, nickname, avatar) -- 企微关系(一个微信用户被多少个员工加好友就有多少条) wecom_relation(external_userid, customer_id, owner_userid, added_at)核心思路:external_userid永远不能当客户的唯一标识,得用unionid或者微信昵称+头像组合来做去重,external_userid只是关系表的外键。
映射方案二:靠 add_external_contact 事件
新客户添加时,Webhook 会推送add_external_contact事件,payload 里同时带了userid(添加方=内部员工)和external_userid(被添加方=外部客户):
{ "event": "add_external_contact", "eventDetail": { "userid": "staff_uin_xxx", "externalUserId": "ext_abc123", "state": "from_live", "nickname": "张先生" } }收到事件时直接更新关系表,比轮询客户列表实时得多。我建议两个方案都用:事件做实时更新,客户列表做每日全量对账。
同一个人多个 external_userid 怎么去重?
这是最烦的场景。客户同时加了李、王、赵三个销售,就有三个external_userid。企微 API 不直接返回unionid(那是微信侧的),只有客户自己的微信里才能拿到。
实际做法:
先用
external_userid + ownerUserId建唯一键,避免关系表重复同一微信用户的多个
external_userid,靠昵称+头像URL的组合判断是否同一个人如果有条件,让客户通过 OAuth2 授权(之前写过的 OAuth2 网页授权接口),拿到
unionid后就能精准去重
# 拉客户详情时拿到微信头像URL detail = requests.get( f"{BASE}/wx-api/api/contact/getUserProfileDetail", headers=HEADERS, params={"externalUserId": ext_id} ) avatar_hash = md5(detail["data"]["avatarUrl"]) # 头像URL的hash,用来模糊匹配打通后能做什么
内部CRM里一个客户有多个企微好友的场景就能统一了:给客户发消息时,选任意一个有归属关系的员工的external_userid都能送到;做销售业绩统计时,同一个客户在不同销售名下不会重复计数。
总结下来就一句话:external_userid是关系ID(谁加的谁),不是用户ID(这个人是谁)。把这两个概念分开,映射就通了。