|
|
@@ -0,0 +1,166 @@
|
|
|
+-- 乐收赢进件 —— 菜单与权限(Task 8 / 三-B)
|
|
|
+--
|
|
|
+-- 【本脚本尚未执行】执行由用户做。执行前请先读下面「★ 与 lsy-menu.sql 的关键差异」一节。
|
|
|
+--
|
|
|
+-- ── 本脚本干什么 ──────────────────────────────────────────────────────
|
|
|
+-- 在**已存在**的「收付款」目录下,新增:
|
|
|
+-- · 1 行页面(type=2)「商户进件」 —— 进件页路由
|
|
|
+-- · 2 行按钮(type=3)「建号」「提交入网」 —— 承载两个写权限点
|
|
|
+-- 并把这三行授权给 role_id = 1(照 005 / lsy-menu.sql 的先例)。
|
|
|
+--
|
|
|
+-- ── ★ 与 lsy-menu.sql 的关键差异(照抄它的写法会出错的地方)──────────────
|
|
|
+-- lsy-menu.sql 的 `dir` CTE 是**插入**收付款目录;本脚本的 `dir` 是**查**它。
|
|
|
+-- 原因:`lsy-menu.sql` 已于 2026-09-22 执行,收付款目录**已在库里**。
|
|
|
+-- ⇒ 若照抄成 INSERT + `WHERE NOT EXISTS(...)`,`dir` 会命中空集、
|
|
|
+-- 于是**本脚本一行都不插、且不报错**(就是 lsy-menu.sql 第 61–62 行警告的那个形态)。
|
|
|
+-- ★ 反过来,本脚本的幂等比 lsy-menu.sql **更硬**:那里 `dir.id` 是本语句刚生成的新 id,
|
|
|
+-- 它的 `WHERE NOT EXISTS(... parent_id = dir.id ...)` 是**死条件**(lsy-menu.sql:95-97 自陈);
|
|
|
+-- 这里 `dir` 是 SELECT 出来的**既有** id ⇒ 那条 NOT EXISTS 是**活的**,重跑不会重复插页面。
|
|
|
+--
|
|
|
+-- ── ★★ 为什么必须有 type=3 的按钮行(**这是本脚本存在的理由**)───────────
|
|
|
+-- 一个 `sys_menu` 行的 `permission` 列只能装**一个**权限串。进件页有三个端点、三个权限串,
|
|
|
+-- 所以页面行只能装 `:list`,另两个必须落在**按钮行**上。
|
|
|
+-- 授权链(缺一不可):
|
|
|
+-- sys_menu.permission → sys_role_menus(授权行)→ MenuMapper.selectPermissionsByUserId
|
|
|
+-- → GrantedAuthority → PermissionCheck.hasAny(`authorities::contains`,**全等匹配**)
|
|
|
+-- `MenuMapper.selectPermissionsByUserId` 的 SQL **按 `m.type` 无过滤**(只要求 permission 非空)
|
|
|
+-- ⇒ type=3 的行**会**进入 GrantedAuthority(实测读源码:MenuMapper.java:22-27)。
|
|
|
+-- ★ **漏掉按钮行会怎样**:`module_payment:leshouying:onboarding:create` 永远不进入任何角色的
|
|
|
+-- 授权集合 ⇒ **普通租户用户点「建号」必 403**;而 **超管直通**(`PermissionCheck` 对
|
|
|
+-- `ROLE_SUPERUSER` 直接 `return true`)⇒ **超管测试一个都测不出来**。
|
|
|
+-- ★ 先例:`batch-menu.sql` 用 type=3 行承载 `module_payment:batch:authorize` / `:create` /
|
|
|
+-- `:detail`(它那 4 行全是 type=3)。本表自己的父子校验也认这个形态:
|
|
|
+-- `MenuService.validateParentChildType` —— `pt == 2` 时 `childType != 3` 即抛
|
|
|
+-- 「菜单下仅允许新增按钮」(MenuService.java:201-204)。
|
|
|
+-- ★ 但 **batch-menu.sql 是坏先例、不可照抄**:它通篇 **没有 `uuid`**(0 处命中),
|
|
|
+-- 按字面执行会违反 `uuid NOT NULL`(lsy-menu.sql:22-23 已为此留档)。
|
|
|
+-- 本脚本的列清单照 `lsy-menu.sql` 的 **20 列**写法,`uuid` 用 `gen_random_uuid()`。
|
|
|
+--
|
|
|
+-- ── 列名:本表的真实结构(逐字照 lsy-menu.sql 的警告)──────────────────
|
|
|
+-- `sys_menu` 的真实列(java/all_schema.sql 的 CREATE TABLE public.sys_menu)是:
|
|
|
+-- name, type, "order", permission, icon, route_name, route_path, component_path,
|
|
|
+-- redirect, hidden, keep_alive, always_show, affix, title, params, parent_id,
|
|
|
+-- id, uuid, status, description, created_time, updated_time
|
|
|
+-- 计划里那套 menu_id / menu_name / order_num / path / component / is_frame / is_cache /
|
|
|
+-- menu_type / visible / perms / create_by / create_time / remark 是 **RuoYi 的表结构**,
|
|
|
+-- 在本表上一个都不存在;'M' / 'C' 那种字符枚举也不存在(本表 type 是 integer)。
|
|
|
+-- type 的语义(MenuCreateDTO.java:22 逐字):**1=目录 2=菜单 3=按钮 4=外链**。
|
|
|
+--
|
|
|
+-- ── ★ uuid:NOT NULL 且无任何兜底 ──────────────────────────────────────
|
|
|
+-- `uuid character varying(64) NOT NULL`,**无 DEFAULT**(唯一索引 ix_sys_menu_uuid),
|
|
|
+-- 应用层也不填(AutoFillMetaObjectHandler 只填 createdTime/updatedTime/tenantId)⇒
|
|
|
+-- 每条 INSERT 都必须显式给值。本脚本用 gen_random_uuid()(36 字符,≤64;volatile ⇒ 逐行取值)。
|
|
|
+--
|
|
|
+-- ── ★ id:不要写死 ────────────────────────────────────────────────────
|
|
|
+-- `id` 有默认值 `nextval('public.sys_menu_id_seq'::regclass)` ⇒ 本脚本**省略 id 列**,
|
|
|
+-- 让序列生成(写死数字会与序列撞 PK)。
|
|
|
+--
|
|
|
+-- ── ★ permission 必须与 Task 7 的 @PreAuthorize 逐字相同 ────────────────
|
|
|
+-- 差一个字符就是 403,而且超管测不出来(同上)。
|
|
|
+-- 本脚本三个 permission 逐字取自(**2026-09-23 从源码现取,未抄任何文档**):
|
|
|
+-- LeshouyingOnboardingController.java:107 @PreAuthorize("@perm.hasAny('module_payment:leshouying:onboarding:list')")
|
|
|
+-- LeshouyingOnboardingController.java:133 @PreAuthorize("@perm.hasAny('module_payment:leshouying:onboarding:create')")
|
|
|
+-- LeshouyingOnboardingController.java:152 @PreAuthorize("@perm.hasAny('module_payment:leshouying:onboarding:commit')")
|
|
|
+-- 类级 `@RequestMapping("/payment/collection/onboarding")`(同文件 :78)。★ 若 Task 7 之后改动,
|
|
|
+-- 本文件必须重取 —— 取值口径是「现取」,不是「抄一次就完」。
|
|
|
+--
|
|
|
+-- ── ★ component_path 的形态 ───────────────────────────────────────────
|
|
|
+-- 必须是**相对 src/views/ 的路径**:不带 `views/` 前缀、不带 `.vue` 后缀。
|
|
|
+-- 依据:frontend/src/store/modules/permission.store.ts
|
|
|
+-- `modules[\`../../views/${component}.vue\`] || modules["../../views/error/404.vue"]`
|
|
|
+-- 带前缀会拼成 `…/views/views/…index.vue.vue` ⇒ 查不到 ⇒ **静默落到 error/404.vue**。
|
|
|
+-- 本脚本的值与 Task 9 的页面文件路径对齐:
|
|
|
+-- frontend/src/views/module_payment/collection/onboarding/index.vue
|
|
|
+-- ★ Task 9 若改了页面路径,这里必须同步改,否则**页面存在但点进去是 404**。
|
|
|
+--
|
|
|
+-- ── 需要人工确认的三处(取值来自先例,不是实测数据)──────────────────
|
|
|
+-- 1) route_name:本脚本用 PascalCase `CollectionOnboarding`,照**紧邻**的三个 lsy 页面
|
|
|
+-- (CollectionOrder / CollectionRefund / CollectionMerchant,见 lsy-menu.sql:88-93)。
|
|
|
+-- 但 batch-menu.sql 用的是 kebab('payment-batch')—— 两套先例不一致。
|
|
|
+-- route_name 会成为 vue-router 的 name(菜单高亮与 keep-alive 的 include 都按它匹配),必须唯一。
|
|
|
+-- 2) route_path:本脚本用 `/payment/collection/onboarding`(模块前缀式),照同目录三个 lsy 页面。
|
|
|
+-- 3) role_id = 1:照 005 与 lsy-menu.sql:65,**请按你库里的实际角色 id 调整**。
|
|
|
+-- 本脚本只授权给这一个角色;若进件页要给别的角色用,需另外补 sys_role_menus。
|
|
|
+--
|
|
|
+-- ── 其他 ─────────────────────────────────────────────────────────────
|
|
|
+-- · icon 一律留空:图标集是 src/assets/icons/*.svg,经 `i-svg:<name>` 渲染;
|
|
|
+-- 缺省时前端回退到 i-svg:menu。要图标请在「菜单管理」界面里选。
|
|
|
+-- · 幂等(两种**相反**的情形都要知道;本脚本不是通用 upsert):
|
|
|
+-- 1) 页面行已存在 ⇒ `page` 为空 ⇒ **两个按钮也不会插**(buttons 的 FROM 是 page),且**不报错**。
|
|
|
+-- 若出现「菜单在、按钮不在」,请手工补两条 type=3 的 INSERT。
|
|
|
+-- 2) 收付款目录不存在 ⇒ `dir` 为空 ⇒ 三行**都不插**,且**不报错**。
|
|
|
+-- 执行前请先跑下面第 1 条只读核对确认目录在。
|
|
|
+
|
|
|
+WITH dir AS (
|
|
|
+ SELECT id FROM sys_menu
|
|
|
+ WHERE name = '收付款' AND type = 1 AND status = '0' AND parent_id IS NULL
|
|
|
+),
|
|
|
+page AS (
|
|
|
+ INSERT INTO sys_menu (name, type, "order", permission, icon, route_name, route_path, component_path,
|
|
|
+ redirect, hidden, keep_alive, always_show, affix, title, params,
|
|
|
+ uuid, parent_id, status, created_time, updated_time)
|
|
|
+ SELECT '商户进件', 2,
|
|
|
+ (SELECT COALESCE(MAX("order"), 0) + 1 FROM sys_menu WHERE parent_id = dir.id),
|
|
|
+ 'module_payment:leshouying:onboarding:list', NULL,
|
|
|
+ 'CollectionOnboarding', '/payment/collection/onboarding',
|
|
|
+ 'module_payment/collection/onboarding/index',
|
|
|
+ NULL, false, true, false, false, '商户进件', NULL,
|
|
|
+ gen_random_uuid(), dir.id, '0', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP
|
|
|
+ FROM dir
|
|
|
+ WHERE NOT EXISTS (SELECT 1 FROM sys_menu
|
|
|
+ WHERE name = '商户进件' AND parent_id = dir.id AND status = '0')
|
|
|
+ RETURNING id
|
|
|
+),
|
|
|
+buttons AS (
|
|
|
+ INSERT INTO sys_menu (name, type, "order", permission, icon, route_name, route_path, component_path,
|
|
|
+ redirect, hidden, keep_alive, always_show, affix, title, params,
|
|
|
+ uuid, parent_id, status, created_time, updated_time)
|
|
|
+ SELECT v.name, 3, v.ord, v.perm, NULL, NULL, NULL, NULL,
|
|
|
+ NULL, false, false, false, false, v.name, NULL,
|
|
|
+ gen_random_uuid(), page.id, '0', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP
|
|
|
+ FROM page,
|
|
|
+ (VALUES
|
|
|
+ ('建号', 1, 'module_payment:leshouying:onboarding:create'),
|
|
|
+ ('提交入网', 2, 'module_payment:leshouying:onboarding:commit')
|
|
|
+ ) AS v(name, ord, perm)
|
|
|
+ RETURNING id
|
|
|
+)
|
|
|
+-- 页面行与两个按钮行都要授权:菜单树按 sys_role_menus 过滤,只授页面会让按钮权限缺席 ⇒ 403。
|
|
|
+INSERT INTO sys_role_menus (role_id, menu_id)
|
|
|
+SELECT 1, id FROM page
|
|
|
+UNION ALL
|
|
|
+SELECT 1, id FROM buttons;
|
|
|
+
|
|
|
+
|
|
|
+-- ── 只读核对(执行后手工跑一遍;前三条**执行前**也建议先跑)────────────────
|
|
|
+--
|
|
|
+-- 1) 收付款目录在不在(**为空则本脚本什么都不会插,且不报错**)
|
|
|
+-- SELECT id, name, type, route_path FROM sys_menu
|
|
|
+-- WHERE name = '收付款' AND type = 1 AND status = '0' AND parent_id IS NULL;
|
|
|
+-- 期望:**1 行**。若 0 行 ⇒ 先跑 lsy-menu.sql(或手工建目录)再回来。
|
|
|
+--
|
|
|
+-- 2) 本次插入的页面行
|
|
|
+-- SELECT id, name, type, "order", permission, route_name, route_path, component_path, uuid
|
|
|
+-- FROM sys_menu WHERE name = '商户进件' AND status = '0';
|
|
|
+-- 期望:**1 行**,type=2,permission = 'module_payment:leshouying:onboarding:list',
|
|
|
+-- route_path = '/payment/collection/onboarding',
|
|
|
+-- component_path = 'module_payment/collection/onboarding/index',uuid 非空(36 字符)。
|
|
|
+--
|
|
|
+-- 3) 两个按钮行(**漏了这两行 ⇒ 普通租户用户建号/提交必 403,而超管测不出来**)
|
|
|
+-- SELECT m.id, m.name, m.type, m."order", m.permission
|
|
|
+-- FROM sys_menu m JOIN sys_menu p ON m.parent_id = p.id
|
|
|
+-- WHERE p.name = '商户进件' AND m.type = 3 AND m.status = '0'
|
|
|
+-- ORDER BY m."order";
|
|
|
+-- 期望:**2 行**,permission 逐字为
|
|
|
+-- 'module_payment:leshouying:onboarding:create' 与 'module_payment:leshouying:onboarding:commit'。
|
|
|
+--
|
|
|
+-- 4) 授权行
|
|
|
+-- SELECT rm.role_id, m.name, m.permission
|
|
|
+-- FROM sys_role_menus rm JOIN sys_menu m ON m.id = rm.menu_id
|
|
|
+-- WHERE m.name IN ('商户进件', '建号', '提交入网') AND m.status = '0';
|
|
|
+-- 期望:**3 行**,role_id 都是 1。
|
|
|
+--
|
|
|
+-- 5) 端到端(**唯一能证明权限串没写错的一条**):用一个**非超管**的租户账号登录,
|
|
|
+-- 确认三个`module_payment:leshouying:onboarding:*`都出现在
|
|
|
+-- `MenuMapper.selectPermissionsByUserId` 的返回里。
|
|
|
+-- ★ 超管账号**证明不了任何事** —— 它对 ROLE_SUPERUSER 直接放行。
|