-- ============================================================================ -- 表单中心 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:应用身份由超管在管理端维护,脚本不碰主数据删除) -- ----------------------------------------------------------------------------