Perf200kProbe(20 万答卷/78 万索引行,独立库 bsch_formcenter_perf): - V10 量化:列表首屏 p95=22ms、三条件筛选 p95=313ms、深分页 keyset p95=31ms、 统计 p95=1~334ms、导出 2 万级 4.1~8.1s(冷缓冲池首跑 22.1s 已如实记档) - 铁律取证:org.hibernate.SQL 日志抓真实语句——第一段只 select id、不含 values_json; 翻页谓词是 answer.id<游标;EXPLAIN 显示首屏/计数走 idx_answer_form_time 且 using_index=true - P26:8 线程×250 并发实测 answer_no 重号率=1392/2000(69.6%,"不限次+报名号"组合) - P37:新答卷后 cached=true、升版后 cached=false(铁律④语义与实现一致) - P35/P31:information_schema 复核索引基数与 ROW_FORMAT=Dynamic(本机 8.0.45) - 类名不带 Test/IT → 不进 mvn test 门禁(190 用例仍是快门禁),手点 -Dtest 才跑 PerfTreeCallsProbe(控制器形状,复用同一份数据): - P44 请求级取树次数:列表/详情/导出=2、统计=3、筛选候选=1(P44 的"1~2 次"上界在统计路径实测为 3) - V10 请求级延迟:筛选 p95=163ms、导出 2297ms/1.15MB - P36 升版补建实测:19,600 行表单的发布=373,272ms 且本次补出 0 行—— P36 记档的"2 万级秒级"不成立,N+1 逐行探测是主因(数字交验收轮裁决是否触发 publish 处改造) sql/h2_userbase_app_and_modules.sql(H2 + 第一个快应用登记交接件): 中心应用六条模块行(手册 §2.2,含 path 空 fc:scope:all 锚点)+ 快应用五条模块行 (方案 §6.2 checklist,导入/导出为 path 空锚点)+ 组/角色授权与开通 + 验收查询 + 回滚段。 AI 不代跑(沿 flow-center 先例;H2 既定路径是超管在管理端录入,脚本为批量/复核形态)。 步骤 3.5 显式记录"快应用数据视图的后端守卫仍扫中心树"这一过渡口径(T9 报告 P78 附注)。 docs/T6-report.md 已按 P69 从本仓 docs/ 挪至 form-center/docs/(本提交不含该文件)。
This commit is contained in:
@@ -0,0 +1,202 @@
|
||||
-- ============================================================================
|
||||
-- 表单中心 H2 + 第一个快应用:UserBase 侧应用行/模块行/授权登记脚本
|
||||
-- 编制:Qoder(T9 联调轮)|日期:2026-10-15
|
||||
-- 依据:《表单中心执行手册》§2.2(中心应用六条模块行表 + 验证口径)、
|
||||
-- 《表单中心建设方案 v1.4》§6.2(快应用登记 checklist R8)、§6.4(customId=fc:scope:all)、
|
||||
-- §6.5(中心=BUSINESS 应用,模块行与快应用模块行两棵树不混)
|
||||
-- 先例: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 表;path 必须逐字等于 front/src/router/routes.ts)
|
||||
-- ①表单设计 /admin/forms ②数据管理 /admin/data ③统计 /admin/stats
|
||||
-- ④字典管理 /admin/dicts ⑤我的填写 /my(全员) ⑥数据全量【path 留空 + customId=fc:scope:all】
|
||||
-- 第⑥行=纯权限锚点:导航不渲染、后端 DataScopeService 只扫 path 空节点的 customId(P47 收紧后口径),
|
||||
-- 授权给哪个组,该组的数据范围即升级为"全部"(方案 §6.4)。
|
||||
-- 子页面路由(/admin/forms/designer、/admin/data/detail、/app/:appCode/data/detail)
|
||||
-- **不建模块行**(手册 §2.2 注记 + P63/P77):不占 modId、不进导航,前端按 meta.parentPath 归位。
|
||||
-- custom_id 用 200~205 段(避开 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/dicts','zidingyi', 1, 203, @operator, NOW()
|
||||
UNION ALL SELECT @center_app_id, NULL, '我的填写', '204', '/my', '', 1, 204, @operator, NOW()
|
||||
-- 锚点行:path 空 + customId 协议值(非数字 customId 是刻意的,前端 modId 会回退主键 id,不影响消费)
|
||||
UNION ALL SELECT @center_app_id, NULL, '数据全量', 'fc:scope:all', '', '', 1, 205, @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 四步的前两步)
|
||||
-- 快应用=真实应用行 + 无自有后端 + 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 生效;否则手工填
|
||||
|
||||
-- 模块行五条(方案 §6.2 表的典型配法)。path 形态:
|
||||
-- 填报 = /f/{code}(该快应用名下的表单 code,经 resolve 换 uuid——快应用不授权定义管理行,§3.4)
|
||||
-- 数据查看/统计 = /app/{app_code}/data|stats(AppContext.vue 的分派段)
|
||||
-- 导入/导出 = path 留空的按钮级锚点(导出按钮显隐按它判,P76 修的就是"判哪棵树")
|
||||
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, '300' AS custom_id,
|
||||
CONCAT('/f/', @quick_code, '_form') AS path, 'edit' AS icon, 1 AS level, 300 AS sort_no,
|
||||
@operator AS create_user, NOW() AS create_time
|
||||
UNION ALL SELECT @quick_app_id, NULL, '数据查看', '301', CONCAT('/app/', @quick_code, '/data'), 'category', 1, 301, @operator, NOW()
|
||||
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; -- ← 快应用学生组(只给填报行)
|
||||
|
||||
-- 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 中心模块授权:管理员组=前五行(除锚点),全员组=只"我的填写",
|
||||
-- 要看全校数据的组=额外勾"数据全量"锚点行(customId=fc:scope:all)
|
||||
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 COALESCE(m.custom_id,'') <> 'fc:scope:all'
|
||||
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);
|
||||
|
||||
-- 数据全量锚点(V42 的授权动作;只给确实要看全校数据的组)
|
||||
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.custom_id = 'fc:scope:all'
|
||||
AND NOT EXISTS (SELECT 1 FROM sys_group_module_perm p WHERE p.group_id = @grp_admin AND p.module_id = m.id);
|
||||
|
||||
-- 3.4 快应用模块授权:教师组=数据查看+统计+导出锚点(不给填报也算得出数据);学生组=只给填报行
|
||||
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);
|
||||
|
||||
|
||||
INSERT INTO sys_group_module_perm (group_id, module_id, inherit_flag, create_user, create_time)
|
||||
SELECT @grp_student, m.id, 0, @operator, NOW()
|
||||
FROM app_module m
|
||||
WHERE m.app_id = @quick_app_id AND @grp_student > 0 AND m.level = 1 AND m.path LIKE '/f/%'
|
||||
AND NOT EXISTS (SELECT 1 FROM sys_group_module_perm p WHERE p.group_id = @grp_student 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 门户可见性由开通闸门决定;进应用只见填报
|
||||
-- ----------------------------------------------------------------------------
|
||||
SELECT '中心模块行数(期望 6)' AS chk, COUNT(*) AS n FROM app_module WHERE app_id = @center_app_id
|
||||
UNION ALL
|
||||
SELECT '快应用模块行数(期望 5)', COUNT(*) FROM app_module WHERE app_id = @quick_app_id
|
||||
UNION ALL
|
||||
SELECT '中心 path 空锚点数(期望 1)', COUNT(*) FROM app_module
|
||||
WHERE app_id = @center_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:应用身份由超管在管理端维护,脚本不碰主数据删除)
|
||||
-- ----------------------------------------------------------------------------
|
||||
Reference in New Issue
Block a user