2026-08-13-oa-wechat-service-notify-design.md 13 KB

OA 审批/抄送 · 服务号微信通知设计

日期:2026-08-13
工程:backend + mobile-h5(微信小程序);mobile-oa-h5 / web-admin 首期基本不动
相关:docs/superpowers/specs/2026-08-10-mobile-h5-wechat-login-design.md、OA 待办中心与 pages/oa/webview 深链


1. 背景与目标

1.1 现状

  • OA 审批引擎已可产生审批待办(approval + pending)与抄送任务(cc)。
  • mobile-h5 已具备待办/抄送列表与详情 web-view(instanceId + taskId)。
  • 用户表已有小程序 wx_openid;微信授权登录已支持微信侧多手机号选择(getPhoneNumber 系统面板)。
  • 后端存在 WechatMessageService 雏形,但配置与调用偏向小程序 appid,不能直接作为服务号推送实现。
  • 无服务号 openid 字段、无 OA 待办创建后的推送钩子、无服务号关注/绑定引导。

1.2 目标

  1. 流程到达审批人或产生抄送时,向对应用户发送已认证服务号模板消息。
  2. 用户点击消息后进入小程序对应审批/抄送详情,在已登录或可恢复会话前提下完成办理或查看。
  3. 登录页提供「关注服务号」入口;登录后完成服务号身份与系统账号绑定。
  4. 审批与抄送使用两套模板(文案区分);推送失败不阻断 OA 业务。

1.3 非目标(首期)

  • 非 OA 的其它工作流(如独立 workflow/请假引擎)推送。
  • 企业微信应用消息、短信、邮件作为主通道。
  • 以小程序订阅消息作为主推送通道(可作远期补充)。
  • web-admin 发送监控台、运营群发。
  • 改造 OA 审批业务规则本身。

2. 已确认决策

决策点 结论
推送通道 已认证服务号模板消息;小程序与服务号可关联
身份绑定 员工关注服务号 + 登录后显式绑定,写入服务号 openid / unionid
推送范围 OA 审批待办 + 抄送;各用一套模板文案区分
点击落地 直达 pages/oa/webview(instanceId + taskId);审批 mode=approve,抄送 mode=view
会话恢复 已绑小程序 openid → 深链可静默换票;否则走现有登录页 + returnUrl 回跳
多手机号 系统侧一人一账号一手机号;微信侧多号由现有登录页微信授权处理,深链不重复造选号 UI
登录页关注 次要链接「关注服务号,接收审批通知」→ 优先 wx.openOfficialAccountProfile;失败/低版本 → 二维码弹层
未绑定推送 跳过发送并记日志,不阻断 OA 提交/流转

3. 架构与数据流

OA 引擎 _create_tasks_for_node
  ├─ approval(pending) → 异步:审批模板 → 服务号 API
  └─ cc               → 异步:抄送模板 → 服务号 API
         │
         ▼
  查 sys_user.wx_mp_openid
         │ 无则 skip
         ▼
  template/send + miniprogram.pagepath
         │
         ▼
  用户点击 → 打开小程序 webview 详情
         │
         ├─ 本地 JWT 有效 → 直接办/看
         ├─ 无 JWT + 小程序 openid 已绑定 → 静默 wechat-login(仅 loginCode) → 详情
         └─ 否则 → 登录页(已有选手机号)→ returnUrl 回详情

3.1 微信账号关系

标识 用途
小程序 wx_openid 已有;静默恢复登录、业务鉴权侧微信身份
服务号 wx_mp_openid 模板消息 touser
wx_unionid 开放平台下打通两端;绑定与排查用

前提:服务号与小程序绑定同一微信开放平台;服务号与小程序关联;跳转主页需同主体或开放平台主体关联(openOfficialAccountProfile)。


4. 数据模型

在 sys_user 增加(SQL 落 docs/sql/,同步 ORM):

列名 类型 说明
wx_mp_openid VARCHAR(64) NULL 服务号下 openid
wx_unionid VARCHAR(64) NULL 开放平台 unionid
wx_mp_bind_time DATETIME NULL 服务号绑定时间(可选,便于审计)

约束建议:

  • wx_mp_openid 非空唯一(应用层保证一微信号服务号身份不绑两人)。
  • wx_unionid 可索引,非强制唯一(历史脏数据时以 openid 为准)。

可选(实现阶段二选一,设计允许延后):

  • 轻量发送日志表 biz_wx_notify_log(user_id、task_id、template_type、status、errcode、create_time),便于客诉排查;首期也可用应用日志,表可后补。

5. 后端设计

5.1 配置(.env,禁止写入前端源码)

变量 说明
WX_MP_APPID 服务号 AppID
WX_MP_SECRET 服务号 AppSecret
WX_MP_TEMPLATE_APPROVAL 审批待办模板 ID
WX_MP_TEMPLATE_CC 抄送知会模板 ID
WX_MP_GH_USERNAME 服务号原始 ID(gh_…),供小程序跳转主页;也可仅配前端
已有 WXPAY_MINI_APPID / WXPAY_SECRET 小程序;模板消息 miniprogram.appid 使用小程序 AppID

5.2 服务号消息发送

新建(或重构)WechatMpMessageService(与现有小程序向 WechatMessageService 分离,避免 appid/token 混用):

  • 使用服务号 appid/secret 取 access_token。
  • 调用 POST /cgi-bin/message/template/send。
  • touser = wx_mp_openid。
  • miniprogram: { appid: 小程序AppID, pagepath: "pages/oa/webview/index?instanceId=…&taskId=…&mode=…" }
    • pagepath 无前导 /。
    • 审批:mode=approve;抄送:mode=view。
  • 可选备用 url(H5 说明页);与 miniprogram 同传时优先小程序。

模板字段(以微信后台最终审核字段名为准,实现时做映射配置):建议包含审批类型/标题、申请人、时间、摘要或节点名;审批模板标题语义为「待您审批」,抄送为「抄送知会」。

5.3 绑定 API

  • POST /api/mini/auth/mp-bind(需登录):小程序 web-view 中间页走服务号网页授权 snsapi_base,将 code 交给后端,换取 wx_mp_openid(及 unionid),写入当前用户。
  • GET /api/mini/auth/mp-bind-status:是否已绑定(供个人中心展示)。

绑定冲突:该 wx_mp_openid 已绑其他用户 → 返回明确错误。

5.4 静默登录放宽

调整 POST /api/mini/auth/wechat-login:

  • 若 loginCode 换得的小程序 openid 已绑定唯一启用用户,且未传 phoneCode → 允许直接发 JWT(深链会话恢复)。
  • 若未绑定 → 仍要求手机号授权(或引导账号密码),行为与现网登录页一致。
  • 已绑定但调用方仍带 phoneCode:须校验手机号与账号 phone_no 一致(保持安全)。

5.5 OA 推送钩子

在 oa_engine._create_tasks_for_node(或统一的任务创建出口)于任务入库后:

  1. 对每个新建任务触发异步通知(线程池 / 后台 task,不阻塞请求事务提交;宜在 commit 后发送)。
  2. task_type == "approval" 且 status == "pending" → 审批模板。
  3. task_type == "cc" → 抄送模板。
  4. 查 assignee 的 wx_mp_openid;空则 debug/info 日志后返回。
  5. 发送失败:重试 1~2 次(短间隔);仍失败记 error 日志;不回滚 OA。

同一用户同一节点多人并签/或签:每人一条任务则每人一条通知(与任务一一对应)。


6. 小程序(mobile-h5)设计

6.1 登录页关注入口

入口已迁移(2026-08-14): 登录页关注与工作台顶栏绑定入口已废弃;统一见 docs/superpowers/specs/2026-08-14-wechat-notify-menu-entry-design.md 与菜单「开启微信通知」。

  • 位置:登录卡片下方次要链接文案:「关注服务号,接收审批通知」。
  • 点击:
    1. 调用 wx.openOfficialAccountProfile({ username: gh_… })(基础库 ≥ 3.7.10)。
    2. fail 或基础库不足 → 弹出服务号二维码(长按识别);二维码资源可静态配置或远端 URL。
  • 本入口只解决「关注」,不在此完成账号绑定。

6.2 登录后绑定入口

入口已迁移(2026-08-14): 登录页关注与工作台顶栏绑定入口已废弃;统一见 docs/superpowers/specs/2026-08-14-wechat-notify-menu-entry-design.md 与菜单「开启微信通知」。

  • 个人中心(或工作台设置)「开启微信审批通知」:检查绑定状态;未绑定则走绑定流程(web-view 授权或扫码说明)。
  • 首次进入 OA 待办且未绑定:可弱提示一次(非强制)。

6.3 深链与会话

  • 模板 pagepath 打开 pages/oa/webview/index。
  • 进入时:
    1. 有有效本地 token → 按现逻辑拼 H5 URL(含 token)打开详情。
    2. 无 token → 尝试静默 wx.login + wechat-login(仅 loginCode);成功则继续。
    3. 失败 → forceRelogin 并写入 returnUrl(完整 path + query);登录成功后 reLaunch/redirectTo 回该页。
  • 不在深链页单独实现选手机号 UI;复用现有「微信授权登录」(微信系统面板可选「上次提供」等多个号)。

6.4 与现有登录设计的关系

  • 冷启动全局策略仍可保持「有本地 JWT 则用」;仅深链入口增加静默恢复,避免点通知必进登录页。
  • 一微信多手机号:仅未绑定/需重新授权时出现;截图所示为微信原生能力,本需求不新增选号交互。

7. 错误处理与体验

情况 行为
未关注 / 取消关注 发送失败或未绑定;OA 正常;引导重新关注并绑定
未绑定 mp_openid 不发送;个人中心可提示
模板字段不符 发送失败日志;需运营修正模板
小程序未发布 / 未关联 点击无法跳小程序;发布前勿对生产用户推送
任务已办结仍点开 详情页只读或提示已处理(沿用现有详情权限逻辑)
登录选错手机号 现有登录错误提示;换号或账号密码

8. 测试要点

  1. 审批待办产生 → 审批人收到审批模板;点击进详情且可审批。
  2. 抄送产生 → 抄送人收到抄送模板;文案与审批模板可区分;点击为查看态。
  3. 未绑定用户:OA 成功,无推送或日志为 skip。
  4. 已绑定 + 清本地 token:点通知可静默进详情。
  5. 未绑定小程序微信:点通知 → 登录页选号 → 回跳详情。
  6. 登录页「关注服务号」:同主体下可打开公众号主页;失败出现二维码。
  7. 并签多人:每人一条通知。

9. 实现分期建议

阶段 内容
P0 字段与配置、服务号发送服务、OA 钩子、两套模板联调
P1 绑定 API + 个人中心绑定、登录页关注入口
P2 深链静默登录 + returnUrl、发送日志(可选表)

P0 在绑定未完成前可用测试号手工写入 wx_mp_openid 验证推送与跳转。


10. 实现约定(原开放项已拍板)

项 约定
绑定方式 服务号网页授权 snsapi_base:登录后经小程序 web-view 中间页拿 code → POST /mini/auth/mp-bind 换票写入。不做带参关注二维码主路径。
前端配置 mobile-h5 配置:MP_GH_USERNAME(原始 ID)、MP_QRCODE_URL(兜底二维码图)。密钥仅后端 .env。
模板字段 微信后台申请通过后,将字段名写入后端映射配置(如 env 或常量);联调前用占位字段名,上线前替换为正式值。
二维码资源 由运营提供服务号关注二维码图片,放入小程序 static/ 或 CDN,并配置 MP_QRCODE_URL。

运营依赖(非代码阻塞):服务号模板审核通过、小程序与服务号关联并已发布、开放平台同主体/主体关联。


11. 实现完成与运维检查表

代码(本变更已落地)

  • sys_user 服务号字段 + ORM
  • 服务号模板发送 + OA 任务钩子
  • 静默登录 / mp-bind API
  • mobile-oa-h5 绑定页 + mobile-h5 关注/绑定/深链回跳

运维待填(上线前)

  • backend .env: WX_MP_APPID / WX_MP_SECRET / WX_MP_TEMPLATE_APPROVAL / WX_MP_TEMPLATE_CC / WX_MP_TPL_FIELD_*
  • mobile-oa-h5 VITE_WX_MP_APPID
  • mobile-h5/utils/config.js MP_GH_USERNAME + 真实 static/mp-qrcode.png
  • 微信:服务号关联小程序、开放平台、网页授权回调域名、小程序业务域名
  • 生产库执行 docs/sql/alter-sys-user-wx-mp-openid.sql(若未执行)

真机验收(对照 §8)

  • 审批推送可批
  • 抄送推送可区分 + 查看态
  • 未绑定 skip
  • 静默登录
  • 选手机号登录回跳
  • 关注入口
  • 并签多人各一条