Files
shanxia2.0/docs/wechat-approval-notices-setup.md
T

145 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 服务号审批、发放完成与证件提醒:部署与验收
## 本次范围
报价、合同、备用金、报销、运费结算、车辆充值六类审批。待办只通知当前可审批人;最终通过/驳回通知申请人。CRM和运费申请人按本轮提交人,BX/充值按单据 applicant_id(兼容代申请)。运费业务提交人的审批结果及发放完成通知已恢复,已取消的历史通知不自动补发。
审批通过不等于财务支付。充值进入财务发放节点时通知“通过”,不再用审批模板催办财务;从一开始直接发放、没有实际审批记录的单据不虚构“通过”消息。
现已新增必选3“发放/支付完成”和必选6“证件到期定期汇总”,均复用上述绑定和通知队列,不新增队列表。必选3在真实发放登记成功后通知申请人(运费为业务提交人),表示门户已登记完成,不代表核验了银行到账。报销纯冲抵不发送支付完成;支付并冲抵中实际支付金额大于零才发送。充值审批发放、手工记录关联申请都覆盖,无关联申请的手工记录不推测收件人。
## 新增模板与角色配置
必选3模板 `rnPOIVknPqfgwVzKbchj6pCMCWpoDe3h3dkrFbUEsD4`:thing3 为“备用金发放/报销支付/运费发放/充值发放”,thing4 为完整业务单号,thing5 为“发放完成/支付完成”,time12 为真实处理时间,无金额字段。单号为空或超过20字符记为 failed,不截断发送。BX重复完成操作保留首次处理时间;撤销充值发放或更换关联申请时取消尚未发出的旧通知。
必选6模板 `aYzLZ_EqI8hq7RXrlf73BTAfNvzgJHeg_xWKar01Md0`:thing5 示例“证件到期3项,已到期2项”(前项统计即将到期证件),仅即将到期时为“证件到期3项”,仅已到期时为“证件已到期2项”;time6 为实际发送的北京时间,thing3 **固定为“系统默认发出”**。当天到期计入“已到期”,沿用提醒列表统计口径。
1. 执行 `docs/sql/20261027_wechat_cert_notice_role.sql`。脚本兼容 MySQL 5.7,使用临时存储过程,需要 CREATE ROUTINE 权限;为已有租户幂等创建“证件到期通知接收人”角色,并关联证件提醒 Web 菜单及祖先目录。
2. 在系统角色/用户管理中给指定人员分配该角色;脚本不自动分配人员。角色名称需要与 `WECHAT_OA_CERT_NOTICE_ROLE_NAME` 一致。
3. 新角色默认空自定义数据范围,**不会扩大用户原有数据权限**。接收人需要通过已有角色或管理员配置的数据范围拥有相应证件提醒的可见权限;没有可见提醒就不发。菜单、数据范围、角色启用状态均沿用现有管理界面。
4. 接收人关注服务号并完成头像菜单“微信通知”绑定。
5. 默认每周一、周三、周五 **10:00(Asia/Shanghai)** 在证件扫描完成后入队,每人每个通知日最多一条;周二、周四及周末不发,10点前也不发,数量按各人现有权限计算。无预警不发;发送前重新检查角色、证件有效期及数量。跨日滞留通知取消,不补发旧日摘要。当天漏过调度时间的启动不会自动补跑,可在联调中手动执行扫描任务验证。
新租户创建后需再次执行角色初始化SQL或在角色管理中手动建立同名角色、菜单和数据范围。新增模板不是公众号新建模板接口自动申请,须确保这两个模板已添加到当前服务号。
## 发布顺序
1. 保持 `WECHAT_OA_ENABLED=false`,部署后端及前端。
2. 执行 `docs/sql/20261027_wechat_approval_notices.sql` 建立三张表。SQL兼容MySQL 5.7;既有审批数据不回填。
3. 在服务号后台核对模板标题、字段、适用行业及消息接口权限;配置服务端出口 IP 白名单。
4. 在微信网页授权域名中配置门户公网域名。回调为 `https://门户域名/api/public/wechat-notices/oauth/callback`。这是网页授权回调,不是公众号消息接收服务器 URL。
5. 在后端私有环境配置新 AppSecret、公网HTTPS基址、已关联小程序 AppID,确保 Redis 可用。不要把 AppSecret 放入前端、SQL或版本库。此次对话中曾披露的密钥建议先重置。
6. 如有反向代理访问日志,回调 location 不记录查询串(code/state),仅保留路径/状态/耗时。应用审计已脱敏。
7. 启用 `WECHAT_OA_ENABLED=true`,重启后端。既有自定义加密排除列表可追加回调精确路径;回调位于 public 路径,无需取消其他业务加密或鉴权。
8. 员工先关注服务号,在门户头像菜单“微信通知”生成二维码,微信扫码授权,核对手机与门户窗口六位验证码,点击“确认绑定”。二维码五分钟有效,原登录端必须确认。
## 环境配置(仅服务端)
```dotenv
WECHAT_OA_ENABLED=false
WECHAT_OA_APPID=wxf63dfb83af901d43
WECHAT_OA_SECRET=
WECHAT_OA_PUBLIC_BASE_URL=https://门户域名
WECHAT_OA_MINI_APPID=
WECHAT_OA_TEMPLATE_PENDING=ukQRYoczEXH1fZe9TdL8uX6LmuJJTlPidboxqtq_7N0
WECHAT_OA_TEMPLATE_RESULT=vA_z15BdP-8LltRmS__1MmUcNKQBy6b6rvAXKEYzoLY
WECHAT_OA_TEMPLATE_COMPLETION=rnPOIVknPqfgwVzKbchj6pCMCWpoDe3h3dkrFbUEsD4
WECHAT_OA_TEMPLATE_CERT_REMINDER=aYzLZ_EqI8hq7RXrlf73BTAfNvzgJHeg_xWKar01Md0
WECHAT_OA_CERT_REMINDER_ENABLED=true
WECHAT_OA_CERT_NOTICE_ROLE_NAME=证件到期通知接收人
WECHAT_OA_CERT_REMINDER_HOUR=10
WECHAT_OA_CERT_REMINDER_MINUTE=0
```
`WECHAT_OA_MINI_APPID` 必须是已关联的门户小程序,不能填服务号AppID。未配置时消息仅提醒,不提供跳转;不再回退到门户Web。小程序与服务号OpenID分别保存,不能互换。
证件提醒开关同时受 `WECHAT_OA_ENABLED` 总开关控制;修改小时/分钟需要重启调度器。证件扫描与微信摘要使用同一调度任务,扫描仍每日执行,微信入队和发送复核均限制在周一、周三、周五。部署时如私有环境仍显式配置旧值8,须改为10并重启后端;本次未修改私有环境。通知关闭时仍执行原证件扫描;已安装队列表时,充值撤销操作仍会取消旧完成通知,避免重新启用后复活。
## 跳转边界
### 小程序关注快捷入口
小程序工作台顶部栏右下角提供金色强调的“关注服务号”按钮,位于欢迎语和退出区域下方,点击直接进入小程序内的关注与绑定页面 `pages/common/wechat-official-account`,顶部入口不调用微信原生公众号跳转,不弹“即将打开公众号”。按用户要求按钮与“退出登录”相同视觉高度,使用白色文字、无边框;顶部栏预留底部空间,不覆盖标题和退出操作。第1步只保留“打开服务号并关注”按钮,已移除关注卡片、搜索说明和复制名称入口;打开失败提示更新微信后重试或联系管理员。打开主页不表示关注成功。
登录进入工作台后,会通过现有本人绑定接口查询真实关注状态:已绑定且已关注不弹窗;已绑定未关注弹出“关注服务号”;未绑定无法确认关注情况,使用“关注并绑定,如已关注请完成绑定”的中性说明。通知未启用或查询失败时不误判、不阻断登录。弹窗可选“稍后再说”,当前登录在进程内最多提示一次,返回工作台或切换Tab不重复;重新登录或冷启动恢复登录可再次检查。离开工作台、开始退出或切换账号后,旧请求与旧确认回调不会再触发弹窗/跳转。
页面内“打开服务号并关注”按钮使用 [微信官方 wx.openOfficialAccountProfile](https://developers.weixin.qq.com/miniprogram/dev/api/navigate/wx.openOfficialAccountProfile.html),username为服务号原始ID `gh_53e43e6f5c92`(不是AppID)。基础库3.7.10起支持,目标公众号须与小程序同主体或关联主体;只能在点击行为同步调用,不在onShow或网络回调中自动调用。登录弹窗确认进入引导页,由用户点击打开。微信可能先显示“即将打开公众号”的原生确认,用户点击允许后进入公众号;本项目未额外添加该确认,接口不提供关闭它的参数。上线须真机核验主页主体及实际关注效果。
**关注不等于绑定。** 小程序关注页提供“一键绑定当前账号”:用户主动点击后创建五分钟有效的移动绑定会话,在专用web-view完成微信网页授权,回调页尝试自动返回,原登录小程序查询本次会话并自动提交绑定,无需记验证码或再次确认。回调仍仅保存候选身份,不直接绑定;关注、重复绑定冲突、租户与会话归属仍由原接口校验。显示“已开通服务号通知”以后才能接收通知。电脑门户“头像菜单 → 微信通知”的扫码绑定仍保留;仅登录小程序不会自动关联服务号OpenID。发送器还会检查对应账号、业务状态、接收角色及数据权限。后端通知开关、迁移与服务号配置步骤仍需完成。
关注入口及登录提醒本地验证:48项交互、模板、会话保护、原生主页跳转、移动自动绑定及网页返回桥接测试通过;后端微信相关215项测试通过;使用本机HBuilderX内置uni-app编译器完成微信小程序构建,产物位于临时目录,已核对原生主页接口、移动绑定入口与新页面路由。尚未进行真机关注验证、上传或发布。编译依赖通过临时Vite配置指向HBuilderX自带pinia,未修改项目依赖。
### 小程序内绑定上线与验收
- 保持现有服务号网页授权域名与回调配置,另将回调所在HTTPS域名配置为小程序的业务域名;小程序须具备web-view使用资格。参考[腾讯官方域名配置说明](https://cloud.tencent.com.cn/document/product/1301/100434)及[uni-app web-view说明](https://uniapp.dcloud.io/component/web-view.html)。回调域名若不能在小程序中打开,授权页会失败,需管理员配置;不能仅凭本地编译认定真机授权成功。
- 新页面 `pages/common/wechat-binding-authorize` 仅从当前关注页通过EventChannel取得服务端签发的门户中转地址(`/api/public/wechat-notices/oauth/launch`),等待onReady再挂载。web-view 不能直接打开 `open.weixin.qq.com`,中转页位于已配置的业务域名,再跳到微信授权。登录JWT、服务号OpenID和AppSecret不放进路由参数或本地存储;不接受任意URL query作为web-view来源。
- 小程序发起会话传 `{mode: "mobile"}`,服务端记录模式;电脑无请求体时默认 desktop,仍保留验证码人工确认。移动回调页 `backend/app/templates/wechat-binding-mobile.html` 使用固定官方 JS SDK 地址 `https://res.wx.qq.com/open/js/jweixin-1.6.0.js` 和 nonce CSP,尝试 `wx.miniProgram.navigateBack` 返回上一页,不跳转首页以免丢失发起会话。部署时须包含该模板文件。SDK 或桥接失败时显示返回按钮、左上角返回提示。
- 返回后仅对当前登录用户在本页主动发起且授权、关注均完成的会话自动提交确认;普通进入页面不会自动发起绑定。失败可刷新重试,过期或未关注可重新授权;切换账号或离开页面后忽略旧异步响应。关闭/重新进入页面可重新发起会话。
- 真机验收:关注→一键绑定当前账号→网页授权→自动返回→自动绑定;桥接异常时左上角返回仍应完成绑定,再次进入显示已绑定;验证未关注、授权取消、会话过期、账号切换、重复点击和重复微信冲突。未作真机授权或发布。
### 消息跳转
- 报价、合同:统一小程序 WebView 详情。
- 备用金、报销:待办仅提醒,不提供跳转;结果跳小程序详情。当前小程序详情没有审批按钮。
- 充值:待办仅提醒,不提供跳转;结果跳现有小程序申请详情。当前小程序表单没有审批按钮。
- 运费:审批待办、审批结果、发放完成均仅提醒,不提供跳转。
- 发放完成:备用金、报销、充值复用已有小程序详情。证件汇总仅提醒,不提供跳转。
- 所有通知停止附带 Web URL,包括小程序无法打开时的门户回退;待发送队列也按新规则发送。公网基址仍用于服务号绑定授权,不应删除。已送达的历史消息链接不受此次代码变更影响。
- 点击消息不授予额外业务权限,依赖现有登录、菜单和单据权限。若未登录小程序,现有登录流程可能回到工作台,需要重新点击原消息。
## 发送记录与失败处理
每15秒处理最多20条。审批与outbox在同一数据库事务,失败回滚不留下通知。发送前复核当前审批、接收人、轮次;撤回/作废/转交取消尚未发送的旧任务,已送达消息不可撤回。
`sys_wechat_notice.status`:pending待发送、sending已认领、sent微信接口已接受、cancelled业务已失效、skipped未绑定/未关注、failed明确失败、unknown发送结果不确定。sent不表示用户已读。
系统繁忙/频率限制最多尝试5次;超时/网络中断/进程中断可能已经发送,记unknown,不自动重发。先核对微信侧记录,不能直接批量重置unknown造成重复推送。未绑定的历史通知不在绑定后自动补发。
排查时按租户筛选,避免导出payload和openid到公开日志:
```sql
SELECT id, business_type, business_id, kind, recipient_id,
status, attempts, last_error, create_time, sent_at
FROM sys_wechat_notice
WHERE tenant_no = '待检查租户' AND del_flag = 0
ORDER BY create_time DESC
LIMIT 100;
```
## 真实联调清单
使用已关注并绑定的申请人、两名审批人分别验证:提交仅首节点收到;中间通过只通知下一审批人;最终结果为通过或打回,运费提交人收到审批结果及汇款凭证登记完成通知;代申请给实际申请人;撤回/重提不发送旧轮次;或签一人通过取消其他未发任务;未关注/解绑不向小程序OpenID发送。
新增验收:备用金、报销、运费、充值四业务真实发放/支付后收到完成模板,纯冲抵不发;重复操作不重复发送;充值撤销/换绑后未发旧通知失效;长单号只记失败。证件接收人角色开启/移除、停用、不同租户与不同数据范围分别验证;周一/周三/周五10点扫描发送,其他日期及10点前不发送,同日重复扫描不重复发送;续期/删除后更新数量,无预警不发送;跨日滞留取消;核对申请人字面值“系统默认发出”。
本地自动化使用SQLite及假的微信传输,不代替真实账号权限、模板内容、域名、小程序关联、MySQL多实例并发测试。生产发布及真实发送需单独执行上述验收。
## 本地验证记录(2026-10-27)
- 本次新增后端测试:119 项通过;前端绑定交互测试:9 项通过。
- 扩展后端回归:412 项通过,2 项既有充值列表测试因模拟对象缺少当前油卡字段而失败。
- 修改的前端文件通过定向 ESLint、Prettier;新弹窗通过 Stylelint。类型检查仍有 193 项既有依赖及其他模块错误,本次三个前端文件无对应报错。
- 规格与代码质量复核通过,后端、前端及根目录知识图谱已更新。字段一致性脚本在本仓库不存在,未能执行。
- 当前交付为代码、迁移 SQL 与配置说明,通知开关默认关闭;尚未部署、迁移真实数据库或发送真实微信消息。
复验命令(在对应项目目录执行):
```bash
# backend/
venv/bin/python -m pytest tests/test_wechat_approval_notices.py tests/test_wechat_notice_binding.py tests/test_wechat_oa_client.py tests/test_wechat_notice_crm_sources.py tests/test_wechat_notice_bx_sources.py -q
# web-admin/
node --test scripts/wechat-notice-binding.test.cjs
```
## 必选3与必选6追加验证记录(2026-10-27)
微信通知全部测试198项通过,合并证件扫描及数据权限测试为211项通过;扩展业务回归493项通过、2项既有充值列表模拟数据失败(缺少油卡逻辑所需`recharge_type`)。规格与代码质量复核通过。Python编译检查通过,本次没有前端代码变更。字段一致性脚本仍不存在;MySQL5.7迁移、真实并发和微信模板接受情况仍需部署验收。
```bash
# backend/;隔离配置,不加载业务环境,不调用真实微信
ENV_FILE=/dev/null venv/bin/python -m pytest tests/test_wechat*.py tests/test_pl_scan_cert_expiry.py tests/test_data_scope.py -q
```
登录关注提醒追加验证(2026-10-27):关注入口与提醒共22项交互/模板/工作台生命周期测试通过,微信小程序构建成功。产物已核对右下金色定位、原生弹窗及show/hide处理。未进行真实关注查询联调、真机交互验收或发布。