用户身份与消息推送产品需求说明
版本:v1.0 | 日期:2026-06-29 | 关联文档:《统一认证服务概要设计 v3》《登录体系统一整合方案》
文档说明
文档定位
本文档为产品需求说明文档(PRD),面向产品经理、开发团队、测试团队,围绕以下四个核心问题展开需求说明:
- 用户从不同微信服务号和小程序接触服务,如何获取 openID/unionID,如何管理应用与 ID 关系
- openID/unionID 登录后如何与手机号关联、生成通行证 ID、再调用会员库生成家长/学员 memberID,各实体之间什么关系
- 业务系统存了学员 ID 和学员 memberID,如果要发消息推送,逻辑是怎样的
- 用户可以关注多个服务号,需要注意什么
业务术语表
| 术语 | 说明 |
|---|---|
| openID | 用户在某个微信应用(公众号/小程序)下的唯一标识,同一用户在不同应用下 openID 不同 |
| unionID | 用户在同一微信开放平台账号下所有应用的统一标识,跨应用唯一 |
| pptid | 通行证账号 ID,用户在"外部通行证"系统中的唯一标识 |
| 家长 memberID | 会员库中家长角色的会员编号,与通行证 pptid 1:1 关联 |
| 学员 memberID | 会员库中学员角色的会员编号,与家长 memberID N:M 关联 |
| wechat_type | 微信应用类型标识,UCOUT 用于区分不同微信小程序/公众号 |
| UAS | 统一认证服务(Unified Authentication Service),基于 OAuth2/OIDC 的 Token 认证服务 |
| UCOUT | 用户中心对外服务,提供账号注册/绑定/认证/微信解密等接口 |
| VTS | 消息推送服务(Virtual Template Service),负责城市服务号模板消息发送 |
| wxprx | 微信代理服务,负责调用微信 API 解密 openID/unionID |
一、openID/unionID 获取与管理
1.1 业务背景
用户通过多个渠道接触我们的服务,包括多个微信小程序、卓越会员微服务(公众号)、以及各城市服务号。每个渠道会产生独立的 openID,需要统一管理这些 ID 与用户身份的关联关系。
1.2 微信 ID 基础概念
unionID: u_abc123] end subgraph 小程序A OA1[openID: o_miniprogram_A_xxx] end subgraph 小程序B OA2[openID: o_miniprogram_B_yyy] end subgraph 卓越会员微服务-公众号 OA3[openID: o_mp_main_zzz] end subgraph 城市服务号-广州 OA4[openID: o_mp_gz_www] end subgraph 城市服务号-深圳 OA5[openID: o_mp_sz_vvv] end U --> OA1 U --> OA2 U --> OA3 U --> OA4 U --> OA5
关键规则:
- openID:同一用户在每个微信应用(小程序/公众号)中有唯一的 openID,不同应用之间 openID 不互通
- unionID:当多个应用绑定到同一微信开放平台账号后,同一用户在这些应用下共享同一个 unionID
- 前提条件:所有小程序和公众号必须绑定到同一微信开放平台账号,否则无法获取 unionID,跨应用身份打通将无法实现
1.3 各渠道获取 openID/unionID 的流程
1.3.1 小程序渠道
{ grant_type=mp_code, wx_login_code, wechat_type } UAS->>UCOUT: retrieveOpenInfo(wx_login_code, wechat_type) UCOUT->>wxprx: 调用微信接口解密 wxprx-->>UCOUT: openid, unionid UCOUT-->>UAS: openid, unionid UAS->>UAS: 写入 openID 映射表 Note over UAS: 记录: openid + unionid +
app_id + wechat_type UAS-->>小程序: access_token, openid, unionid, pptid
触发时机:用户每次打开小程序时自动触发 wx.login,获取 wx_login_code 后换取 openid/unionid。
1.3.2 公众号 H5 渠道(含城市服务号)
(scope=snsapi_base) 微信服务器-->>公众号H5页面: 微信授权 code 公众号H5页面->>UAS: POST /oauth2/token
{ grant_type=mp_oauth, wx_code, app_id } UAS->>UCOUT: retrieveOpenInfo(wx_code, wechat_type) UCOUT->>wxprx: 调用微信接口解密 wxprx-->>UCOUT: openid, unionid UCOUT-->>UAS: openid, unionid UAS->>UAS: 写入 openID 映射表 Note over UAS: 记录: openid + unionid +
app_id + wechat_type UAS-->>公众号H5页面: access_token, openid, unionid
触发时机:用户通过公众号菜单、文章链接、扫码等方式进入公众号 H5 页面时触发 OAuth2 授权。
重要说明:使用
scope=snsapi_base静默授权,用户无需关注公众号即可获取 openID。只要用户点击进入公众号内的 H5 页面,系统即可获取并记录该用户在此服务号下的 openID。
1.4 openID 与应用的关联关系模型
关系说明:
- 一个微信应用(app_id)下有多个用户的 openID 记录(1:N)
- 一个用户(unionID)在不同应用下有不同的 openID(1:N)
- 同一用户在同一应用下 openID 唯一(UK: openid + wechat_type)
1.5 openID 映射管理规则
1.5.1 写入时机
| 场景 | 触发条件 | 写入内容 | 备注 |
|---|---|---|---|
| 小程序登录 | 用户打开小程序,wx.login 成功 | openid, unionid, app_id, wechat_type | 每次登录检查并更新 |
| 公众号 H5 访问 | 用户进入公众号 H5 页面,OAuth2 授权成功 | openid, unionid, app_id, wechat_type | 静默授权即可触发 |
| 通行证绑定 | 用户手机号授权绑定通行证 | 更新 pptid 字段,标记 bindsource=passport | 一次性写入 |
| 会员库关联 | 会员注册或关联操作 | 更新 member_id 字段,标记 bindsource=member | 一次性写入 |
| 历史数据迁移 | 批量导入历史 openID 数据 | 全字段 | 一次性操作 |
1.5.2 更新规则
- openID 记录不重复:以
(openid, wechat_type)作为唯一键,已存在则更新 unionID 等字段,不重复插入 - unionID 补充:首次获取 openID 时如 unionID 为空(极端情况),后续获取到 unionID 时补充更新
- pptid/member_id 更新:绑定通行证或关联会员库后,更新对应字段
1.5.3 查询方式
| 查询场景 | 查询条件 | 返回结果 |
|---|---|---|
| VTS 推送查 openID | member_id + target_app_id | 目标服务号下的 openID |
| 按 unionID 查 openID | unionid + target_app_id | 目标服务号下的 openID |
| 查用户所有 openID | unionid 或 pptid | 该用户在所有应用下的 openID 列表 |
二、身份关联链路
2.1 业务背景
用户首次接触服务时,从微信授权开始,经历手机号绑定、通行证创建、会员库关联,最终形成完整的身份链。各环节的实体关系是本节的核心内容。
2.2 身份关联全链路 E-R 图
2.3 各实体基数关系
| 关系 | 基数 | 说明 |
|---|---|---|
| unionID : 通行证 pptid | 1:1 | 一个微信用户(unionID)绑定一个通行证账号 |
| 通行证 pptid : 家长 memberID | 1:1 | 通行证账号对应一个家长会员 |
| 家长 memberID : 学员 memberID | N:M | 一个家长可关联多个学员,一个学员可被多个家长关联 |
| 学员 memberID : 业务系统学员编号 | 1:1/系统 | 同一学员在 TMS 有一个编号,在 evip 有另一个编号,每个系统内 1:1 |
| 用户(unionID) : openID | 1:N | 同一用户在每个微信应用下有一个 openID |
2.4 首次用户完整身份建立流程
关联手机号 + unionID UCOUT-->>UAS: pptid UAS->>UAS: 更新 openID 映射表 pptid end rect rgb(230, 255, 230) Note over 用户, 业务系统: 阶段三:会员库关联,生成家长/学员 memberID UAS-->>小程序: access_token, pptid 小程序->>业务系统: 携带 access_token 请求业务登录 业务系统->>UAS: 获取用户信息 (pptid, phone) UAS-->>业务系统: user_info 业务系统->>会员库: 调用会员库接口
传入 pptid + phone 会员库->>会员库: 创建家长会员 (parent_member_id) 会员库->>会员库: 创建学员会员 (student_member_id) 会员库->>会员库: 建立家长-学员关联 会员库-->>业务系统: parent_member_id, student_member_id 业务系统->>业务系统: 存储学员 memberID + 业务学员编号 end 业务系统-->>小程序: 业务登录成功
2.5 身份链路各阶段说明
阶段一:微信授权获取 openID/unionID
- 输入:wx_login_code(小程序场景)或 OAuth2 code(公众号 H5 场景)
- 输出:openid + unionid
- 存储:写入 openID 映射表(pp_wechat_openid_map)
- 判断:查询该 unionid/openid 是否已绑定通行证
阶段二:手机号授权,创建通行证
- 输入:手机号(微信授权获取)+ unionID
- 输出:通行证 pptid
- 存储:通行证账号表(pp_acct)新增记录,openID 映射表更新 pptid
- 规则:同一手机号不重复创建通行证,已有通行证则直接关联
阶段三:会员库关联
- 输入:通行证 pptid + 手机号
- 输出:家长 memberID + 学员 memberID
- 存储:会员库新增家长和学员记录,建立关联关系
- 调用方:由业务系统在用户首次登录时调用会员库接口
2.6 非通行证用户的兼容路径
全日制等部分业务不使用通行证体系,其用户身份链路如下:
处理方式:
- openID 映射表中 pptid 为空,通过 member_id 字段关联会员库
- 后续如用户使用小程序等功能,引导绑定通行证后自动补充 pptid
- VTS 消息推送时,支持通过 member_id 查询 openID(双通道查询)
三、消息推送逻辑
3.1 业务背景
业务系统(如网报、TMS/evip)在完成业务操作后(如课程购买、绑定学员),需要向用户发送微信模板消息通知。消息发送需通过 VTS 消息服务,按用户报读课程所在城市,选择对应城市服务号发送。
3.2 消息推送完整流程
网报/TMS/evip participant VTS
消息推送服务 participant UCOUT
用户中心 participant openID映射表 participant 微信服务器 rect rgb(230, 245, 255) Note over 业务系统, VTS: 步骤一:业务触发 业务系统->>业务系统: 业务事件触发
(如: 课程购买成功) 业务系统->>VTS: 发送消息请求
{ student_member_id,
city_code, msg_type } end rect rgb(255, 245, 230) Note over VTS, openID映射表: 步骤二:确定目标服务号与 openID VTS->>VTS: 根据 city_code 查找
目标服务号 app_id VTS->>VTS: 根据 msg_type + app_id
查找模板 template_id VTS->>UCOUT: 查询 openID
{ member_id, target_app_id } UCOUT->>openID映射表: SELECT openid
WHERE member_id=? AND app_id=? alt 通过 member_id 直接找到 openID映射表-->>UCOUT: openid else member_id 未命中,通过 unionID 兜底 UCOUT->>openID映射表: 先查 unionID,
再按 unionID + app_id 查 openid openID映射表-->>UCOUT: openid end UCOUT-->>VTS: openid end rect rgb(230, 255, 230) Note over VTS, 微信服务器: 步骤三:发送模板消息 VTS->>微信服务器: 发送模板消息
{ app_id, openid,
template_id, data } 微信服务器-->>VTS: 发送结果 VTS-->>业务系统: 推送结果 end
3.3 关键步骤详细说明
步骤一:业务触发
业务系统在特定事件发生时(如课程购买成功、绑定学员成功等),向 VTS 发起消息推送请求。
请求参数:
| 参数 | 说明 | 示例 |
|---|---|---|
| student_member_id | 学员会员 ID(会员库中的编号) | 12345 |
| city_code | 报读课程所在城市编码 | GZ(广州) |
| msg_type | 消息类型标识 | course_purchase_success |
| data | 模板消息填充数据 | { "课程名称": "高中数学" } |
说明:业务系统传递
student_member_id而非 openID,业务系统不需要关心 openID 的获取逻辑。
步骤二:确定目标服务号与 openID
VTS 收到请求后,执行两步查找:
- 查找目标服务号:根据
city_code在vts_city_service表中查找对应城市的服务号 app_id - 查找 openID:通过 UCOUT 查询该用户在目标服务号下的 openID
openID 查找优先级:
直接命中?} B -->|命中| C[返回 openID] B -->|未命中| D[根据 member_id
查找 unionID] D --> E{unionID + app_id
命中?} E -->|命中| C E -->|未命中| F[根据 member_id
查找 pptid] F --> G{pptid 对应的
unionID + app_id 命中?} G -->|命中| C G -->|未命中| H[返回: 未找到 openID
消息推送失败]
步骤三:发送模板消息
VTS 获取到 openID 后,结合 vts_template_config 中的模板配置,调用微信 API 发送模板消息。
3.4 跨城市场景
场景描述:用户在广州报读了课程 A,后来到深圳报读了课程 B。
app_id: wx_guangzhou
openid: o_gz_xxx] R2[member_id: 12345
app_id: wx_shenzhen
openid: o_sz_yyy] end subgraph 消息推送 E1[广州课程购买成功] -->|city_code: GZ| VTS1[VTS] VTS1 -->|查 app_id: wx_guangzhou| R1 R1 -->|o_gz_xxx| WX1[广州服务号发送] E2[深圳课程购买成功] -->|city_code: SZ| VTS2[VTS] VTS2 -->|查 app_id: wx_shenzhen| R2 R2 -->|o_sz_yyy| WX2[深圳服务号发送] end
关键点:用户在深圳服务号下的 openID,需要在用户首次访问深圳服务号 H5 页面时自动采集并写入映射表。如果用户尚未访问过深圳服务号,则映射表中无深圳服务号的 openID 记录,消息推送将失败。
3.5 openID 不存在时的兜底策略
| 情况 | 处理 |
|---|---|
| 用户在目标服务号下无 openID | 消息推送失败,记录日志,不重试(用户未接触过该服务号) |
| 用户已关注服务号但未触发 OAuth2 | 关注事件回调中获取 openID 并写入映射表(需服务号配置关注事件推送) |
| openID 存在但用户已取消关注 | 微信 API 返回错误,VTS 记录推送失败,标记该 openID 状态 |
四、用户关注多个服务号的注意事项
4.1 业务背景
用户可以同时关注多个城市服务号(如同时关注广州服务号和深圳服务号),也可以同时使用多个小程序。这意味着同一用户在我们的系统中会有多条 openID 记录。
4.2 数据模型
unionID: u_abc123
手机号: 13800138000
通行证 pptid: 1001
家长 memberID: 5001] U --> ID1[小程序 A
openid: o_mp_a_xxx] U --> ID2[小程序 B
openid: o_mp_b_yyy] U --> ID3[卓越会员微服务
openid: o_main_zzz] U --> ID4[广州服务号
openid: o_gz_www] U --> ID5[深圳服务号
openid: o_sz_vvv] style U fill:#e8f4fd,stroke:#3182ce style ID1 fill:#fff,stroke:#90cdf4 style ID2 fill:#fff,stroke:#90cdf4 style ID3 fill:#fff,stroke:#90cdf4 style ID4 fill:#fff,stroke:#90cdf4 style ID5 fill:#fff,stroke:#90cdf4
映射表中该用户的数据示例:
| openid | unionid | app_id | app_category | pptid | member_id |
|---|---|---|---|---|---|
| o_mp_a_xxx | u_abc123 | wx_miniprogram_a | miniprogram | 1001 | 5001 |
| o_mp_b_yyy | u_abc123 | wx_miniprogram_b | miniprogram | 1001 | 5001 |
| o_main_zzz | u_abc123 | wx_main_mp | mp_service | 1001 | 5001 |
| o_gz_www | u_abc123 | wx_gz_service | mp_service | 1001 | 5001 |
| o_sz_vvv | u_abc123 | wx_sz_service | mp_service | 1001 | 5001 |
4.3 消息推送时的服务号选择规则
核心原则:消息推送的目标服务号由业务决定(报读课程所在城市),不由用户关注了哪些服务号决定。
学员 memberID + 城市] --> B{用户在目标城市
服务号下有 openID?} B -->|有| C[向该服务号发送消息] B -->|无| D[推送失败
记录日志] A2[同一学员在广州有课程] --> C1[推送到广州服务号] A3[同一学员在深圳有课程] --> C2[推送到深圳服务号] A4[同一学员在北京有课程] --> D2{北京服务号下有 openID?} D2 -->|无| E[推送失败]
场景示例:张三同时关注了广州和深圳服务号:
- 在广州报读课程 -> 推送消息到广州服务号(使用 openid: o_gz_www)
- 在深圳报读课程 -> 推送消息到深圳服务号(使用 openid: o_sz_vvv)
- 在北京报读课程 -> 如果张三从未访问过北京服务号,则无 openID 记录,推送失败
4.4 关键注意事项
注意事项一:openID 采集的前置性
| 问题 | 说明 | 建议 |
|---|---|---|
| 用户未到过的城市服务号无 openID | 消息无法推送 | 用户报读新课程时,引导用户访问对应城市服务号 H5 页面完成 openID 采集 |
| 新城市服务号上线后老用户无 openID | 老用户从未访问过新服务号 | 通过已有服务号推送引导消息,引导用户访问新服务号 |
注意事项二:unionID 是打通多服务号的关键
- 所有服务号必须绑定同一微信开放平台,否则各服务号的 openID 无法通过 unionID 关联
- 如果某个服务号未绑定开放平台,该服务号下的 openID 将成为"孤岛",无法与其他服务号的数据打通
- 建议:新申请的城市服务号在投入使用前,必须完成开放平台绑定
注意事项三:用户取消关注不影响 openID 记录
- 用户取消关注某服务号后,映射表中的 openID 记录保留不删除
- 但向已取消关注的用户发送模板消息,微信 API 会返回错误
- 建议:监听服务号的取消关注事件回调,更新 openID 记录的 follow_status 字段
- 用户重新关注时,openID 不变(同一用户在同一服务号的 openID 始终不变),更新 follow_status
注意事项四:openID 不会变化,unionID 不会变化
- 同一用户在同一微信应用下的 openID 是永久不变的,即使用户取消关注后重新关注
- 只要应用仍绑定在同一开放平台账号下,unionID 也不会变化
- 但如果应用从开放平台解绑后重新绑定到另一个开放平台,unionID 会变化(此情况应避免)
注意事项五:模板消息需在各服务号分别申请
- 同一类型的模板消息(如"课程购买成功通知"),需要在每个城市服务号后台分别申请模板 ID
- 不同服务号下同一类型消息的 template_id 可能不同
- VTS 的
vts_template_config表需维护每个服务号对应的 template_id
4.5 边界情况处理
| 边界情况 | 处理方式 |
|---|---|
| 用户关注了多个同城市服务号(如广州有 2 个服务号) | 根据 VTS 配置的 city_code 与 app_id 映射关系,精确匹配目标服务号 |
| 用户从 A 城市转学到 B 城市 | 历史消息仍通过 A 城市服务号发送(如有新课程触发),新课程消息通过 B 城市服务号发送 |
| 全日制学员(无通行证)的消息推送 | 通过 member_id 查询 openID,映射表中 pptid 为空但 member_id 有值 |
| 用户同时在小程序和公众号登录 | 两个渠道都会写入 openID 映射表,互不影响,unionID 关联为同一人 |
| 服务号 AppID 变更(如重新申请) | 需更新映射表的 app_id 和 VTS 配置,旧 openID 数据失效 |
五、附录
5.1 系统关系总览图
5.2 与已有文档关系
| 文档 | 定位 | 与本文档关系 |
|---|---|---|
| 统一认证服务(UAS)概要设计 v3 | UAS 技术架构设计 | 本文档引用的认证流程以 UAS 概要设计为准 |
| 登录体系统一整合方案 | 技术整合方案(含 SQL 表结构、接口定义) | 本文档的需求实现依赖整合方案中的技术设计 |
| 用户体系图.html | 用户关系可视化 | 本文档 E-R 图是对用户体系图的补充和细化 |