用户身份与消息推送产品需求说明

版本:v1.0 | 日期:2026-06-29 | 关联文档:《统一认证服务概要设计 v3》《登录体系统一整合方案》


文档说明

文档定位

本文档为产品需求说明文档(PRD),面向产品经理、开发团队、测试团队,围绕以下四个核心问题展开需求说明:

  1. 用户从不同微信服务号和小程序接触服务,如何获取 openID/unionID,如何管理应用与 ID 关系
  2. openID/unionID 登录后如何与手机号关联、生成通行证 ID、再调用会员库生成家长/学员 memberID,各实体之间什么关系
  3. 业务系统存了学员 ID 和学员 memberID,如果要发消息推送,逻辑是怎样的
  4. 用户可以关注多个服务号,需要注意什么

业务术语表

术语说明
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 基础概念

graph TB subgraph 微信开放平台-同一账号 U[用户-微信账号
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

关键规则

1.3 各渠道获取 openID/unionID 的流程

1.3.1 小程序渠道

sequenceDiagram participant 用户 participant 小程序 participant 微信服务器 participant UAS participant UCOUT participant wxprx 用户->>小程序: 打开小程序 小程序->>微信服务器: wx.login() 微信服务器-->>小程序: wx_login_code 小程序->>UCOUT: getWechatTypeByAppId(app_id) UCOUT-->>小程序: wechat_type 小程序->>UAS: POST /oauth2/token
{ 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 渠道(含城市服务号)

sequenceDiagram participant 用户 participant 公众号H5页面 participant 微信服务器 participant UAS participant UCOUT participant wxprx 用户->>公众号H5页面: 点击公众号菜单/链接进入 H5 公众号H5页面->>微信服务器: 重定向 OAuth2 授权
(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 与应用的关联关系模型

erDiagram WECHAT_APP { string app_id PK "微信应用 AppID" string app_name "应用名称" string app_category "应用类别: miniprogram/mp_service" string wechat_type "UCOUT 应用类型标识" string open_platform_id "绑定的开放平台账号" } USER_WX_IDENTITY { bigint id PK "记录ID" string openid "微信 openID" string unionid "微信 unionID" string app_id FK "所属应用 AppID" string wechat_type "应用类型标识" bigint pptid "通行证 ID(可空)" bigint member_id "会员 memberID(可空)" string bindsource "绑定来源" int status "状态" } USER_UNION { string unionid PK "微信 unionID" bigint pptid "通行证 ID" string phone "手机号" } WECHAT_APP ||--o{ USER_WX_IDENTITY : "一个应用下有多条 openID 记录" USER_UNION ||--o{ USER_WX_IDENTITY : "一个 unionID 关联多个应用的 openID"

关系说明

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 更新规则

1.5.3 查询方式

查询场景查询条件返回结果
VTS 推送查 openIDmember_id + target_app_id目标服务号下的 openID
按 unionID 查 openIDunionid + target_app_id目标服务号下的 openID
查用户所有 openIDunionid 或 pptid该用户在所有应用下的 openID 列表

二、身份关联链路

2.1 业务背景

用户首次接触服务时,从微信授权开始,经历手机号绑定、通行证创建、会员库关联,最终形成完整的身份链。各环节的实体关系是本节的核心内容。

2.2 身份关联全链路 E-R 图

erDiagram USER_WX_IDENTITY { string openid "微信 openID" string unionid "微信 unionID" string app_id "所属应用 AppID" } PP_ACCT { bigint pptid PK "通行证 ID" string phone "手机号" string unionid "微信 unionID" int token_version "令牌版本号" } MEMBER_PARENT { bigint parent_member_id PK "家长 memberID" bigint pptid FK "通行证 ID" string phone "手机号" } MEMBER_STUDENT { bigint student_member_id PK "学员 memberID" string name "姓名" string grade "年级" string phone "手机号" string gender "性别" string source_school "生源校" } PARENT_STUDENT_REL { bigint parent_member_id FK "家长 memberID" bigint student_member_id FK "学员 memberID" string relation "关系" } BIZ_STUDENT { bigint id PK "业务记录 ID" string biz_system "业务系统: TMS/evip/网报" string biz_student_id "业务系统学员编号" bigint student_member_id FK "学员 memberID" } USER_WX_IDENTITY }o--|| PP_ACCT : "多个 openID 通过 unionID 关联到一个通行证" PP_ACCT ||--|| MEMBER_PARENT : "通行证 1:1 家长 memberID" MEMBER_PARENT }o--o{ PARENT_STUDENT_REL : "家长关联多个学员" MEMBER_STUDENT }o--o{ PARENT_STUDENT_REL : "学员被多个家长关联" MEMBER_STUDENT ||--o{ BIZ_STUDENT : "学员 memberID 对应各业务系统学员编号"

2.3 各实体基数关系

关系基数说明
unionID : 通行证 pptid1:1一个微信用户(unionID)绑定一个通行证账号
通行证 pptid : 家长 memberID1:1通行证账号对应一个家长会员
家长 memberID : 学员 memberIDN:M一个家长可关联多个学员,一个学员可被多个家长关联
学员 memberID : 业务系统学员编号1:1/系统同一学员在 TMS 有一个编号,在 evip 有另一个编号,每个系统内 1:1
用户(unionID) : openID1:N同一用户在每个微信应用下有一个 openID

2.4 首次用户完整身份建立流程

sequenceDiagram participant 用户 participant 小程序 participant 微信 participant UAS participant UCOUT participant 会员库 participant 业务系统 rect rgb(230, 245, 255) Note over 用户, UCOUT: 阶段一:微信授权获取 openID/unionID 用户->>小程序: 打开小程序 小程序->>微信: wx.login() 微信-->>小程序: wx_login_code 小程序->>UAS: 请求认证 (wx_login_code) UAS->>UCOUT: retrieveOpenInfo UCOUT-->>UAS: openid, unionid UAS->>UAS: 写入 openID 映射表 UAS-->>小程序: 返回: 未绑定通行证 end rect rgb(255, 245, 230) Note over 用户, UCOUT: 阶段二:手机号授权,创建通行证 小程序->>用户: 显示手机号授权按钮 用户->>微信: 点击授权手机号 微信-->>小程序: phone_code 小程序->>UAS: 再次请求认证 (wx_login_code + phone_code) UAS->>UCOUT: getPhone 解密手机号 UCOUT->>UCOUT: 创建通行证账号
关联手机号 + 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

阶段二:手机号授权,创建通行证

阶段三:会员库关联

2.6 非通行证用户的兼容路径

全日制等部分业务不使用通行证体系,其用户身份链路如下:

graph LR A[openID/unionID] -->|直接关联| B[会员 memberID] B --> C[业务系统学员编号] style A fill:#fff,stroke:#90cdf4 style B fill:#fff,stroke:#3182ce style C fill:#fff,stroke:#3182ce

处理方式


三、消息推送逻辑

3.1 业务背景

业务系统(如网报、TMS/evip)在完成业务操作后(如课程购买、绑定学员),需要向用户发送微信模板消息通知。消息发送需通过 VTS 消息服务,按用户报读课程所在城市,选择对应城市服务号发送。

3.2 消息推送完整流程

sequenceDiagram participant 业务系统
网报/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 收到请求后,执行两步查找:

  1. 查找目标服务号:根据 city_codevts_city_service 表中查找对应城市的服务号 app_id
  2. 查找 openID:通过 UCOUT 查询该用户在目标服务号下的 openID

openID 查找优先级

graph TB A[查询请求: member_id + target_app_id] --> B{member_id + app_id
直接命中?} 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。

graph TB subgraph openID映射表 R1[member_id: 12345
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 数据模型

graph TB U[用户张三
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

映射表中该用户的数据示例

openidunionidapp_idapp_categorypptidmember_id
o_mp_a_xxxu_abc123wx_miniprogram_aminiprogram10015001
o_mp_b_yyyu_abc123wx_miniprogram_bminiprogram10015001
o_main_zzzu_abc123wx_main_mpmp_service10015001
o_gz_wwwu_abc123wx_gz_servicemp_service10015001
o_sz_vvvu_abc123wx_sz_servicemp_service10015001

4.3 消息推送时的服务号选择规则

核心原则:消息推送的目标服务号由业务决定(报读课程所在城市),不由用户关注了哪些服务号决定。

graph TB A[业务系统触发推送
学员 memberID + 城市] --> B{用户在目标城市
服务号下有 openID?} B -->|有| C[向该服务号发送消息] B -->|无| D[推送失败
记录日志] A2[同一学员在广州有课程] --> C1[推送到广州服务号] A3[同一学员在深圳有课程] --> C2[推送到深圳服务号] A4[同一学员在北京有课程] --> D2{北京服务号下有 openID?} D2 -->|无| E[推送失败]

场景示例:张三同时关注了广州和深圳服务号:

4.4 关键注意事项

注意事项一:openID 采集的前置性

问题说明建议
用户未到过的城市服务号无 openID消息无法推送用户报读新课程时,引导用户访问对应城市服务号 H5 页面完成 openID 采集
新城市服务号上线后老用户无 openID老用户从未访问过新服务号通过已有服务号推送引导消息,引导用户访问新服务号

注意事项二:unionID 是打通多服务号的关键

注意事项三:用户取消关注不影响 openID 记录

注意事项四:openID 不会变化,unionID 不会变化

注意事项五:模板消息需在各服务号分别申请

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 系统关系总览图

graph TB subgraph 微信生态 MP[小程序] GZH1[卓越会员微服务] GZH2[城市服务号 x N] OPEN[微信开放平台] end subgraph 认证与用户数据 UAS[UAS 统一认证] SSO[SSO 认证] UCOUT[UCOUT 用户中心] MAP[openID 映射表] MEMBER[会员库] end subgraph 业务系统 WB[网报] TMS[TMS/evip] OTHER[其他系统] end subgraph 消息推送 VTS[VTS] end MP --> UAS GZH1 --> UAS GZH2 --> UAS UAS --> UCOUT UAS --> MAP UCOUT --> MEMBER SSO <--> UAS WB --> UAS TMS --> UAS OTHER --> UAS WB --> VTS TMS --> VTS VTS --> UCOUT VTS --> GZH2

5.2 与已有文档关系

文档定位与本文档关系
统一认证服务(UAS)概要设计 v3UAS 技术架构设计本文档引用的认证流程以 UAS 概要设计为准
登录体系统一整合方案技术整合方案(含 SQL 表结构、接口定义)本文档的需求实现依赖整合方案中的技术设计
用户体系图.html用户关系可视化本文档 E-R 图是对用户体系图的补充和细化