1f3c485ceb
角色与授权(交接摘要 §一·三十五): - 权限判定合并两个来源:内置角色按代码(BuiltinRole)、自定义角色按库 (sys_role_permission)。实现放 module 层的 DbRoleGrantSource,security 只依赖 接口 RoleGrantSource —— 否则角色服务要调 permissionChecker,两个包迟早成环 - 权限判定必须加缓存:此前是零 IO,而压测基线是"SQL 条数恒定"。缓存放 Redis, 空结果也缓存(否则已删角色的 token 会把库打穿)、Redis 挂了退化成直读库、TTL 只兜底 - V18 建 sys_role.user_type(账号的 user_type 一直从角色反推,自定义角色不在代码映射里) 与 sys_role_menu;8 个接口 + 错误码 40045;契约 119 -> 129 - 内置角色只读(权限码是前后端契约,做成可改 = 一次误操作就能把管理员锁在系统外); 平台级能力两道防线(界面禁用 + 服务端 40301);防自锁;改完立即生效(写授权清缓存) - RoleProvisioner 从"已存在就 continue"改成幂等回填 + 每日 03:20 定时任务 —— 修的是"代码改了权限、已开通租户永远停在发放那一刻",这种漂移不报错也没测试会红 - scripts/e2e-m10-role.py 77/77 需求收敛(交接摘要 §一·三十六,本节的数字为准): - 删副班主任 / 审核员两个内置角色(6 -> 4)、历史整理器(module/cleanup)、 加入申请(module/join + 学生端 pages/join)、collect_task_org.deputy_user_ids 整列; 审核范围三级回退 -> 两级,连带 6 个 mapper 的 LIKE '%,id,%' 分支 - V19 迁移处理存量:先解 sys_user_role 绑定再软删角色;账号只在"除这两个角色外没有 别的角色"时才软删。必须迁而不能只删代码 —— 角色码删掉后 mergePermissions 返回空集, 存量账号会"能登录、处处 40301",不抛异常不打日志 - 契约 129 -> 119、错误码 59 -> 54、权限码 30 -> 28、PC 菜单 12 -> 10、 小程序页 10 -> 9、原型 32 -> 29 屏、E2E 39 -> 37 个(断言 1695 条) - UserService.list 对不认识的角色码由"静默丢掉过滤条件"改成失败关闭 (之前传个不存在的 roleCode 会返回全校账号,且看起来完全正常) - 品牌元信息同步改名「深学在线」:pom 名称、/doc.html 标题、配置与启动类注释、 存储自检探针内容;学生端 app 名与全局导航栏标题
91 lines
3.5 KiB
Python
91 lines
3.5 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""手册与代码的一致性检查(客服话术的可信度靠它)。
|
||
|
||
## 为什么要查这个
|
||
|
||
手册(`docs/10-用户手册与客服话术.md`)的价值**完全建立在
|
||
"手册里那句话 == 用户屏幕上那句话"**上 —— 客服照着它回答,
|
||
对不上就会开始自己编,那比没有手册更糟。
|
||
|
||
而**手册是文档、文案在代码里**:改一次代码文案,手册就静默失准,
|
||
且没有任何东西会红。这与"首页在说一件不真的事"(F-82)是同一类问题。
|
||
|
||
所以这里把它变成机械检查,与 `check-api-parity.py`(契约↔路由)、
|
||
`check-entity-columns.py`(实体↔DDL)并列 —— 都是"文档与代码是否还对得上"。
|
||
|
||
## 查两件事
|
||
|
||
1. 手册里引用的每个错误码(反引号包着的 5 位数)在 `ErrorCode.java` 里**确实存在**;
|
||
2. 手册里"客服会照着念"的那些提示文案在代码里**逐字存在**。
|
||
|
||
⚠️ 它**不检查**手册有没有漏掉新错误码 —— 那要靠人。
|
||
它拦的是"手册写了代码里没有的东西"(这会让客服查到一半卡住)。
|
||
|
||
跑法:python scripts/check-manual.py
|
||
"""
|
||
|
||
import io
|
||
import re
|
||
import sys
|
||
|
||
sys.stdout.reconfigure(encoding="utf-8")
|
||
|
||
manual = io.open('docs/10-用户手册与客服话术.md', encoding='utf-8').read()
|
||
codes_src = io.open('backend/xt-common/src/main/java/cn/tianbaotong/fill/common/api/'
|
||
'ErrorCode.java', encoding='utf-8').read()
|
||
|
||
# ① 码:三个捕获组 → 自己建索引
|
||
rows = re.findall(r'([A-Z_]+)\((\d{4,5}),\s*"([^"]*)"\)', codes_src)
|
||
declared_by_code = {code: (name, msg) for name, code, msg in rows}
|
||
print("ErrorCode 里声明了 %d 个码" % len(declared_by_code))
|
||
|
||
used = sorted(set(re.findall(r'`(\d{5})`', manual)))
|
||
print("手册引用了 %d 个码" % len(used))
|
||
bad = []
|
||
for c in used:
|
||
if c in declared_by_code:
|
||
name, msg = declared_by_code[c]
|
||
print(" ✓ %s %-24s %s" % (c, name, msg[:44]))
|
||
else:
|
||
bad.append(c)
|
||
print(" ✗ %s **ErrorCode 里没有这个码**" % c)
|
||
|
||
# ② 关键文案
|
||
# ⚠️ 2026-10 需求收敛:原列表里的「你已在名单中,请返回重新核验」与
|
||
# 「你已提交过申请,请耐心等待」两句属于**已删除的加入申请模块**
|
||
# (`module/join`,连同错误码 40037/40038/40039/40040/40042),
|
||
# 代码里已不存在 —— 因此从本清单移除,不要再加回来。
|
||
key_phrases = [
|
||
"没有找到你的信息,请核对后重试",
|
||
"操作过于频繁,请稍后再试",
|
||
"该信息已被登记",
|
||
"这份材料可能不是你的,请重新上传",
|
||
"已进入审核,需先撤回才能删材料",
|
||
"已通过终审,不可撤回",
|
||
"任务已截止、已归档或已暂停",
|
||
"驳回次数已达上限",
|
||
"存在不合格文件,禁止导出",
|
||
"你的材料被退回了",
|
||
"导出包已过期",
|
||
"这个学生还没扫码核对身份",
|
||
]
|
||
src_all = ""
|
||
import glob
|
||
for p in glob.glob('backend/**/*.java', recursive=True):
|
||
try:
|
||
src_all += io.open(p, encoding='utf-8').read()
|
||
except Exception:
|
||
pass
|
||
|
||
print("\n核对手册里引用的提示文案(%d 句):" % len(key_phrases))
|
||
missing = []
|
||
for ph in key_phrases:
|
||
if ph in src_all:
|
||
print(" ✓ %s" % ph)
|
||
else:
|
||
missing.append(ph)
|
||
print(" ✗ %s —— **代码里找不到这句**" % ph)
|
||
|
||
print("\n结果:错误的码 %d 个,找不到的文案 %d 句" % (len(bad), len(missing)))
|
||
sys.exit(1 if (bad or missing) else 0)
|