Files
form-center-service/sql/h2_userbase_app_and_modules.sql
T
chenxing a02f47ad5c
build / build-push (push) Successful in 58s
feat(p95-fill): 填报入口收回中心——三层角色模型 + 提交第 0 关 + 我可填清单
任务书 P95 W1/W2/W3/W7。发一张表单原本要 UserBase 超管加一条 /f/{code} 模块行并组授权,
实测成本不可接受(10 张表=10 次跨系统操作),人群判定收回表单中心自解析。

- FillAccessService:audience × 会话身份(roleIds ∪ currentRole)× 管理员两表 → 层集合
  ADMIN/MANAGE/FILL/空=NONE,谓词 canFill/canProxy/isAdmin + topTier;与 DataScopeService
  读矩阵、FormAdminGuard 管理端入口三条线正交,结论互不供给
- AnswerService.doSubmit:钉版解析之后插第 0 关(先鉴权后业务,幂等重落在闸门后);
  requireSubject 吸收行为表——教师×面向学生只能代填本校学生(P33 existsInSchool 保留),
  层③代填仍沿 canManage 现路径,层①普通成员代填照旧拒
- GET /api/form/my/fillable:会话校 ∪ 平台级取域、复用 FormDefService.pickByShadowing 做
  (app_code,code) 遮蔽去重、仅已发布未软删、NONE 不入集,行带 canFill + myTier 两件事,
  deadline 升序 null 在后;新增 MyModels.FillableRow
- h2 登记脚本快应用段 5→4 行:删 /f/ 填报行与学生组授权,保留应用行/管理页面行/
  path 空导入导出锚点/学校开通;H2 冻结延续,本轮未在任何环境执行
- 测试:FillAccessServiceTest 16(V48)+ AnswerFillGateIT 14(V49)+ FillableListIT 10(V50),
  mvn -B -o clean test 全量 259 绿(教师×面向学生的造数据通道按新口径改代填,
  读矩阵与限次等断言判据一字未改)

DataScopeService 与 FormAdminGuard 两个文件本轮未被改动(红线 1/2)。
2026-10-16 12:43:31 +08:00

215 lines
17 KiB
SQL
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.
-- ============================================================================
-- 表单中心 H2 + 第一个快应用:UserBase 侧应用行/模块行/授权登记脚本
-- 编制:Qoder(T9 联调轮)|日期:2026-10-15|改版:T10 中心段 6→5 行(手册 §2.2 v1.5,锚点行退役)
-- 改版:**P95(2026-10-16)快应用段 5→4 行——`/f/{code}` 填报行及其组授权整条撤销**
-- 终态口径(任务书 P95 §0.1 三层角色模型,用户 2026-10-16 裁决):填报人群由表单中心按
-- `audience × 会话身份 × 管理员两表` 自解析(后端 FillAccessService + GET /api/form/my/fillable),
-- UserBase 侧**不再逐表单配填报模块行**——发一张表单求一次超管的成本就此归零。
-- 快应用树只剩四种行:数据查看 / 统计 两个页面行 + 导入 / 导出 两个 path 空按钮锚点。
-- **中心应用五行(200~204)与 /admin/* 四行原样不动**(P95 红线 2:管理端入口仍只认模块行)。
-- ⚠ 存量环境里已录入的 `/f/` 行不在本脚本删除范围内(脚本只做"不存在才插",从不删行)——
-- 清理清单见 docs/P95-report.md 的「存量清理」段,由超管在管理端手点删。
-- 依据:《表单中心执行手册》§2.2(中心应用 v1.5 五条模块行表 + 验证口径)、
-- 《表单中心建设方案 v1.4》§6.2(快应用登记 checklist R8;P95 注记后收敛为两步)、§6.5(中心=BUSINESS 应用,
-- 模块行与快应用模块行两棵树不混)
-- 《表单中心管理员体系方案-20261015》§3/§7(T10 改版:中心 6→5 行,customId 数据范围锚点退役)
-- 先例:D:\SoftProject\yunhai\UserBase\flow-center接入UserBase菜单.sql(同一套形态与幂等写法)
--
-- ⚠ 纪律:本脚本**不由 AI 执行**(沿 flow-center 先例"AI 不代跑,交由你在 dev / prod 各自执行")。
-- H2 的既定路径是 UserBase 管理端(超管)逐项录入;本脚本是同一份数据的批量/复核形态,
-- 两者取其一即可,重复执行安全(全部按唯一键做"不存在才插",不覆盖人工改过的值)。
-- 目标库=**UserBase 的库**(本地 bsch_userbase / 160 容器网 userbase 库),不是 bsch_formcenter。
-- MySQL 5.7 兼容(无 CTE / 无窗口函数)。
-- ============================================================================
-- ----------------------------------------------------------------------------
-- 步骤 0:定位 form-center 的应用主键(关键,排第一大坑)
-- 开放接口按「调用方 app_key → application_info 主键 id」反查,而 app_module.app_id 存的是【主键 id】,
-- 不是 app_key(100xxx)。中心后端签名用的 app_key 由 projman 注入(P7:jar 内占位 100021 上线前替换)。
-- 期望下面这条命中一行;命中 0 行=应用行还没建(走 0b 建,或去管理端建)。
-- ----------------------------------------------------------------------------
SELECT id AS app_id_pk, app_key, name, app_code, app_nature, pc_url
FROM application_info
WHERE app_code = 'form-center' OR app_key = 100021;
SET @center_app_id = 0; -- ← 按上一步结果填:form-center 在 application_info 的主键 id
SET @center_app_key = 100021; -- ← 按 UserBase 注册所得 app_key 填(占位值仅本地)
SET @operator = 0; -- create_user 占位(0=系统)
-- 0b(可选):应用行缺失时才需要。app_secret 请改成 UserBase 注册实际下发的值,
-- 不要把这里的占位串带到任何环境(D7:一期无写 oapi,应用行由超管建=本行就是那个"手工建")。
-- INSERT INTO application_info (app_key, app_secret, name, app_code, app_nature, pc_url, pc_icon,
-- clients, create_time, create_user)
-- SELECT @center_app_key, 'REPLACE-WITH-REAL-SECRET', '表单中心', 'form-center', 'BUSINESS',
-- 'http://base.yunhaixingtong.com/form-center/front/', 'category', JSON_ARRAY('pc'),
-- NOW(), @operator
-- WHERE @center_app_id = 0;
-- ----------------------------------------------------------------------------
-- 步骤 1:中心应用五条模块行(执行手册 §2.2 v1.5 表;path 必须逐字等于 front/src/router/routes.ts)
-- ①表单设计 /admin/forms ②数据管理 /admin/data ③统计 /admin/stats
-- ④应用设置 /admin/app(页内 tab:角色设置/字典管理,字典 API 摊这一行把关) ⑤我的填写 /my(全员)
-- v1.5 改版(T10,2026-10-15 用户裁决):
-- · 原「字典管理 /admin/dicts」独立行撤销,迁入应用设置页内 tab(不占 modId、不进导航);
-- · 原「数据全量」path 空锚点行撤销——UserBase 侧 customId 数据范围协议退役,
-- 升级载体改为中心自建的管理员两表(school_admin / form_admin,见《管理员体系方案》§4/§5.1)。
-- **中心应用自此不再有 path 空行**;前端 menuNav 的"path 空跳过"规则保留给快应用面的按钮锚点。
-- 子页面路由(/admin/forms/designer、/admin/data/detail、/app/:appCode/data/detail、/admin/app 的页内 tab)
-- **不建模块行**(手册 §2.2 注记 + P63/P77):不占 modId、不进导航,前端按 meta.parentPath 归位。
-- custom_id 用 200~204 段(避开 flow-center 的 100~105;后端 resolveModId 只认数字 customId)
-- ----------------------------------------------------------------------------
INSERT INTO app_module
(app_id, parent_id, name, custom_id, path, icon, level, sort_no, create_user, create_time)
SELECT * FROM (
SELECT @center_app_id AS app_id, NULL AS parent_id, '表单设计' AS name, '200' AS custom_id,
'/admin/forms' AS path, 'word' AS icon, 1 AS level, 200 AS sort_no, @operator AS create_user, NOW() AS create_time
UNION ALL SELECT @center_app_id, NULL, '数据管理', '201', '/admin/data', 'category', 1, 201, @operator, NOW()
UNION ALL SELECT @center_app_id, NULL, '统计', '202', '/admin/stats','tongji', 1, 202, @operator, NOW()
UNION ALL SELECT @center_app_id, NULL, '应用设置', '203', '/admin/app', 'zidingyi', 1, 203, @operator, NOW()
UNION ALL SELECT @center_app_id, NULL, '我的填写', '204', '/my', '', 1, 204, @operator, NOW()
) t
WHERE @center_app_id > 0
AND NOT EXISTS (SELECT 1 FROM app_module m WHERE m.app_id = t.app_id AND m.custom_id = t.custom_id);
-- ----------------------------------------------------------------------------
-- 步骤 2:第一个快应用(演练对象,方案 §6.2 checklist R8;**P95 注记后 R8 只剩①建应用行 + ④学校开通**)
-- 快应用=真实应用行 + 无自有后端 + pc_url 指中心分发路由。app_code 一经确定不可改
-- (表单归属列 app_code 与模块树查询都按它走,P25/D8)。
-- ----------------------------------------------------------------------------
SET @quick_code = 'fc-drill'; -- ← 演练用快应用 app_code(前端 mock 桩也叫这个名字)
SET @quick_app_key = 0; -- ← UserBase 注册所得 app_key(无后端应用也要有身份行)
SELECT id AS quick_app_id_pk, app_key, name, app_code, app_nature
FROM application_info WHERE app_code = @quick_code;
SET @quick_app_id = 0; -- ← 按上一步结果填;0=还没建,走 2b
-- 2b(可选):快应用应用行(BUSINESS 之外按平台既有 nature 填;无自有后端=不建 projman 项目)
-- INSERT INTO application_info (app_key, app_secret, name, app_code, app_nature, pc_url, pc_icon,
-- clients, create_time, create_user)
-- SELECT @quick_app_key, 'REPLACE-WITH-REAL-SECRET', '表单演练应用', @quick_code, 'BUSINESS',
-- CONCAT('http://base.yunhaixingtong.com/form-center/app/', @quick_code, '/'), 'edit',
-- JSON_ARRAY('pc'), NOW(), @operator
-- WHERE @quick_app_id = 0;
-- SET @quick_app_id = LAST_INSERT_ID(); -- ← 紧接着上面那条 INSERT 生效;否则手工填
-- 模块行四条(P95 终态;方案 §6.2 表在 P95 注记后只剩这四行是 UserBase 侧的事)。path 形态:
-- 数据查看/统计 = /app/{app_code}/data|stats(AppContext.vue 的分派段)
-- 导入/导出 = path 留空的按钮级锚点(导出按钮显隐按它判,P76 修的就是"判哪棵树")
-- ⚠ 原「填报 /f/{code}」行**自 P95 起不再登记**:填报入口由中心按三层模型解析
-- (GET /api/form/my/fillable),模块行不参与;custom_id 300 段就此空置,不要再复用成别的行。
INSERT INTO app_module
(app_id, parent_id, name, custom_id, path, icon, level, sort_no, create_user, create_time)
SELECT * FROM (
SELECT @quick_app_id AS app_id, NULL AS parent_id, '数据查看' AS name, '301' AS custom_id,
CONCAT('/app/', @quick_code, '/data') AS path, 'category' AS icon, 1 AS level, 301 AS sort_no,
@operator AS create_user, NOW() AS create_time
UNION ALL SELECT @quick_app_id, NULL, '统计', '302', CONCAT('/app/', @quick_code, '/stats'), 'tongji', 1, 302, @operator, NOW()
UNION ALL SELECT @quick_app_id, NULL, '导入', '303', '', '', 1, 303, @operator, NOW()
UNION ALL SELECT @quick_app_id, NULL, '导出', '304', '', '', 1, 304, @operator, NOW()
) t
WHERE @quick_app_id > 0
AND NOT EXISTS (SELECT 1 FROM app_module m WHERE m.app_id = t.app_id AND m.custom_id = t.custom_id);
-- ----------------------------------------------------------------------------
-- 步骤 3:授权 + 开通(方案 §6.1 三层:①应用开通(校级)②模块授权(组/角色)③数据范围)
-- 判定口径(UserBase 侧现成实现):最终模块权限 =(组授权模块 ∩ 已开通应用)∪(启用角色授权模块)。
-- 组授权路径**必须先开通应用**(开通不继承,子组要自己开通或挂在已开通的祖先链上)。
-- ----------------------------------------------------------------------------
-- 3.1 中心管理员组(示例:把 @grp_admin 换成目标学校的管理员组 id;sys_group 里查)
SET @grp_admin = 0; -- ← 中心管理员组
SET @grp_all = 0; -- ← 全员组(学校根组/基础组,"我的填写"给它)
SET @grp_teacher = 0; -- ← 快应用教师组(数据查看 + 统计 + 导出锚点)
SET @grp_student = 0; -- ← 快应用学生组:**P95 起只需 3.2 的应用开通(门户可见性),不再勾任何模块行**
-- 填报入口由中心按三层模型给(面向学生的表单自动出现在学生账号的「可填写」里)
-- 3.2 应用开通(中心 + 快应用各一条;@school_id 是要开通的学校 id)
SET @school_id = 0; -- ← 目标学校 id
INSERT INTO sys_group_app_rel (group_id, app_id, create_user, create_time)
SELECT g.group_id, a.app_id, @operator, NOW()
FROM (SELECT @grp_admin AS group_id, @center_app_id AS app_id UNION ALL
SELECT @grp_all, @center_app_id UNION ALL
SELECT @grp_teacher, @quick_app_id UNION ALL
SELECT @grp_student, @quick_app_id) a
JOIN (SELECT 1 AS dummy) g ON a.group_id > 0
WHERE NOT EXISTS (SELECT 1 FROM sys_group_app_rel r
WHERE r.group_id = a.group_id AND r.app_id = a.app_id);
-- 3.3 中心模块授权:管理员组=四个管理行(表单设计/数据管理/统计/应用设置),全员组=只"我的填写"。
-- v1.5 起**没有"要看全校数据就多勾一行"这回事**:数据范围升级改由中心的管理员两表给
-- (/admin/app 的角色设置 tab 加校级表单管理员,或表单列表/设计器给单表单管理员)。
INSERT INTO sys_group_module_perm (group_id, module_id, inherit_flag, create_user, create_time)
SELECT @grp_admin, m.id, 0, @operator, NOW()
FROM app_module m
WHERE m.app_id = @center_app_id AND @grp_admin > 0 AND m.path <> '/my'
AND NOT EXISTS (SELECT 1 FROM sys_group_module_perm p WHERE p.group_id = @grp_admin AND p.module_id = m.id);
INSERT INTO sys_group_module_perm (group_id, module_id, inherit_flag, create_user, create_time)
SELECT @grp_all, m.id, 0, @operator, NOW()
FROM app_module m
WHERE m.app_id = @center_app_id AND @grp_all > 0 AND m.path = '/my'
AND NOT EXISTS (SELECT 1 FROM sys_group_module_perm p WHERE p.group_id = @grp_all AND p.module_id = m.id);
-- 3.4 快应用模块授权:教师组=数据查看+统计+导出锚点(导入 303 是 1.5 预留位不授)。
-- P95 起**学生组这一段整条撤销**:填报入口不再由模块行给(原"学生组=只给 /f/{code} 行"),
-- 学生所在组只需要 3.2 的**应用开通**(门户可见性与闸门照旧,V16 语义不变)——
-- 能不能填哪张表由中心按三层模型判(后端 FillAccessService)。
INSERT INTO sys_group_module_perm (group_id, module_id, inherit_flag, create_user, create_time)
SELECT @grp_teacher, m.id, 0, @operator, NOW()
FROM app_module m
WHERE m.app_id = @quick_app_id AND @grp_teacher > 0 AND COALESCE(m.custom_id,'') <> '303' -- 303=导入(1.5 预留位不授)
AND NOT EXISTS (SELECT 1 FROM sys_group_module_perm p WHERE p.group_id = @grp_teacher AND p.module_id = m.id);
-- 3.5 ⚠ 过渡口径(T9 报告 §5.4 末段 / 问题单 P78 附注,需裁决后才可能取消这一步):
-- 快应用的数据视图复用 /api/form/admin/data/**,而后端守卫扫的是【中心会话树】(签名调用方=表单中心自己),
-- 不消费快应用自己的"数据查看"行。只勾快应用行的教师组进应用数据视图会被后端拒。
-- 演练要端到端跑通,就必须给同一批组**再勾中心的 /admin/data 行**(下面这条就是干这个的)。
-- 这不是"权限放宽"的偷懒——它把两棵树的关系显式写在登记脚本里,避免演练时把它当 bug 报。
INSERT INTO sys_group_module_perm (group_id, module_id, inherit_flag, create_user, create_time)
SELECT @grp_teacher, m.id, 0, @operator, NOW()
FROM app_module m
WHERE m.app_id = @center_app_id AND @grp_teacher > 0 AND m.path = '/admin/data'
AND NOT EXISTS (SELECT 1 FROM sys_group_module_perm p WHERE p.group_id = @grp_teacher AND p.module_id = m.id);
-- ----------------------------------------------------------------------------
-- 步骤 4:验收(对应任务书 T9 演练清单与手册 §2.2"验证"行)
-- ①超管登录中心:/form-center/api/base/GetUserMod 返回步骤 1 的树(前端导航恰 5 项)
-- ②无授权用户:只见"我的填写"
-- ③快应用上下文:/form-center/api/base/GetUserMod?appCode=fc-drill 返回步骤 2 那棵树
-- ⚠ 这条要 **H4 上线**(UserBase /oapi/user/modules 的可选 appCode 参数)+ 中心消费侧接线才通;
-- 中心侧当前按 P3 返回空树+warn,见 T9 报告问题单 P78。
-- ④教师组账号进 /form-center/app/fc-drill/data:导出按钮出现(快应用按钮锚点判定生效,P76)
-- ⑤学生组账号:V16 门户可见性由开通闸门决定;进应用的「填报」档看到该应用下**面向学生**的表单
-- (P95:这张清单来自 GET /api/form/my/fillable,与快应用树里有没有 `/f/` 行无关——
-- 删掉存量 `/f/` 行后门户导航、「可填写」列表、分享直链三路都不受影响,见 P95 报告收尾清单)
-- ⑥中心应用管理端进 /form-center/#/admin/app:应用设置页出两个 tab(角色设置/字典管理),
-- 角色设置里加的人落 school_admin(数据范围升级,不是菜单行)——T10 V43/V46
-- ⑦P95 新判据(V52):快应用树里 `/f/` 行数=0(下面倒数第二条 chk),且中心段仍是 200~204 五行
-- ----------------------------------------------------------------------------
SELECT '中心模块行数(期望 5)' AS chk, COUNT(*) AS n FROM app_module WHERE app_id = @center_app_id
UNION ALL
SELECT '快应用模块行数(P95 期望 4)', COUNT(*) FROM app_module WHERE app_id = @quick_app_id
UNION ALL
SELECT '快应用 /f/ 填报行数(P95 期望 0)', COUNT(*) FROM app_module
WHERE app_id = @quick_app_id AND path LIKE '/f/%'
UNION ALL
SELECT '中心 path 空行数(期望 0,锚点已退役)', COUNT(*) FROM app_module
WHERE app_id = @center_app_id AND (path IS NULL OR path = '')
UNION ALL
SELECT '快应用 path 空锚点数(期望 2=导入/导出)', COUNT(*) FROM app_module
WHERE app_id = @quick_app_id AND (path IS NULL OR path = '')
UNION ALL
SELECT '已开通中心应用的组数', COUNT(*) FROM sys_group_app_rel WHERE app_id = @center_app_id
UNION ALL
SELECT '已授权中心模块的组-行对数', COUNT(*) FROM sys_group_module_perm p
JOIN app_module m ON m.id = p.module_id WHERE m.app_id = @center_app_id;
-- ----------------------------------------------------------------------------
-- 回滚(演练完要撤就跑这段;顺序与建立相反)
-- DELETE p FROM sys_group_module_perm p JOIN app_module m ON m.id=p.module_id
-- WHERE m.app_id IN (@center_app_id, @quick_app_id);
-- DELETE FROM sys_group_app_rel WHERE app_id IN (@center_app_id, @quick_app_id);
-- DELETE FROM app_module WHERE app_id IN (@center_app_id, @quick_app_id);
-- 应用行不在此删(D7:应用身份由超管在管理端维护,脚本不碰主数据删除)
-- ----------------------------------------------------------------------------