|
@@ -3,7 +3,7 @@
|
|
|
-- 【本脚本尚未执行】执行由用户做。执行前请先读下面「★ 授权给谁」一节。
|
|
-- 【本脚本尚未执行】执行由用户做。执行前请先读下面「★ 授权给谁」一节。
|
|
|
--
|
|
--
|
|
|
-- ── 本脚本干什么 ──────────────────────────────────────────────────────
|
|
-- ── 本脚本干什么 ──────────────────────────────────────────────────────
|
|
|
--- 在**已存在**的「收付款」目录下,新增:
|
|
|
|
|
|
|
+-- 在**已存在**的「银联收单」目录(id=268,`route_name='Collection'`;2026-09-23 时叫「收付款」)下,新增:
|
|
|
-- · 1 行页面(type=2)「扫码收款」 —— 收银台路由
|
|
-- · 1 行页面(type=2)「扫码收款」 —— 收银台路由
|
|
|
-- · 2 行按钮(type=3)「发起收款」「撤销收款」 —— 承载另两个写权限点
|
|
-- · 2 行按钮(type=3)「发起收款」「撤销收款」 —— 承载另两个写权限点
|
|
|
-- 并把这三行授权给 role_id = 1(管理员角色)**与 role_id = 2(租户角色)**。
|
|
-- 并把这三行授权给 role_id = 1(管理员角色)**与 role_id = 2(租户角色)**。
|
|
@@ -23,10 +23,14 @@
|
|
|
-- id=270「退款记录」 CollectionRefund
|
|
-- id=270「退款记录」 CollectionRefund
|
|
|
-- id=271「商户映射」 CollectionMerchant
|
|
-- id=271「商户映射」 CollectionMerchant
|
|
|
-- id=272「商户进件」 CollectionOnboarding
|
|
-- id=272「商户进件」 CollectionOnboarding
|
|
|
--- ⇒ 活库的命名空间是 **`Collection<名词>`**。
|
|
|
|
|
|
|
+-- ⇒ 活库的命名空间是 **`Collection<名词>`**(目录自己是 **`Collection`**)。
|
|
|
-- ★ 上面四个名字是**那一刻**的读数。其中 id=271 随后在本批次改名
|
|
-- ★ 上面四个名字是**那一刻**的读数。其中 id=271 随后在本批次改名
|
|
|
-- 「商户映射」→「手工登记商户号」(`lsy-merchant-menu-rename.sql`,同一批次交付);
|
|
-- 「商户映射」→「手工登记商户号」(`lsy-merchant-menu-rename.sql`,同一批次交付);
|
|
|
-- 其余三个名字未动。`route_name` 四行都没变。
|
|
-- 其余三个名字未动。`route_name` 四行都没变。
|
|
|
|
|
+-- ★★ **2026-09-28 复量(六天后)**:`name` 那一列**已经变了** ——
|
|
|
|
|
+-- 目录 268 今天叫「银联收单」,271 今天仍叫「商户映射」(改名脚本尚未执行)。
|
|
|
|
|
+-- **五个 `route_name` 一个都没变** ⇒ 这正是本脚本把 `dir` 的锚点从 `name` 改成 `route_name` 的实测依据。
|
|
|
|
|
+-- (那句「人在界面上手工改名」的旁证:「银联收单」四字在全仓 sql/java/md/ts/vue **零命中**。)
|
|
|
--
|
|
--
|
|
|
-- ★★ 但 `frontend/src/views/module_payment/collection/` 下**三个页面的源码**里写的是
|
|
-- ★★ 但 `frontend/src/views/module_payment/collection/` 下**三个页面的源码**里写的是
|
|
|
-- `LeshouyingOrder` / `LeshouyingRefund` / `LeshouyingMerchant` —— **与活库对不上**。
|
|
-- `LeshouyingOrder` / `LeshouyingRefund` / `LeshouyingMerchant` —— **与活库对不上**。
|
|
@@ -58,11 +62,24 @@
|
|
|
-- 前缀/后缀写错会拼成查不到的模块名 ⇒ **静默落到 error/404.vue**(不是报错)。
|
|
-- 前缀/后缀写错会拼成查不到的模块名 ⇒ **静默落到 error/404.vue**(不是报错)。
|
|
|
--
|
|
--
|
|
|
-- ── ★★ 授权给谁:role 1 **和** role 2 ─────────────────────────────────
|
|
-- ── ★★ 授权给谁:role 1 **和** role 2 ─────────────────────────────────
|
|
|
--- **活库只读实测(2026-09-23)**:收付款子行的现有授权是
|
|
|
|
|
|
|
+-- **活库只读实测(2026-09-23)**:收付款子行的现有授权曾是
|
|
|
-- 支付订单 → role 1,2 / 退款记录 → role 1,2 / 商户映射(今「手工登记商户号」) → role 1,2
|
|
-- 支付订单 → role 1,2 / 退款记录 → role 1,2 / 商户映射(今「手工登记商户号」) → role 1,2
|
|
|
-- / **商户进件 → 只有 role 1**。
|
|
-- / **商户进件 → 只有 role 1**。
|
|
|
-- ⇒ 本页按**多数形态**(两个角色都授)。
|
|
-- ⇒ 本页按**多数形态**(两个角色都授)。
|
|
|
--
|
|
--
|
|
|
|
|
+-- ★★ **2026-09-28 复量:上面那段读数今天不成立了。**
|
|
|
|
|
+-- `GET /system/role/detail/2` 实测:角色 2 持有 **54** 个菜单,`268/269/270/271/272` **一个都没有**
|
|
|
|
|
+-- (同法量角色 1:120 个,五个 id 全有 ⇒ 量具能分辨,不是「查不出来」)。
|
|
|
|
|
+-- 两个结论:
|
|
|
|
|
+-- 1) 「本页按多数形态」的**前提没了** —— 角色 2 今天连兄弟页都看不到。
|
|
|
|
|
+-- 2) 只授页面**等于没授**(父目录缺席 ⇒ 见文末授权段的 `buildMenuTree` 根因)
|
|
|
|
|
+-- ⇒ 本次把目录行一并授给角色 2。
|
|
|
|
|
+-- ★ 成因**未定**,不猜:可能是有人改过角色配置,也可能是
|
|
|
|
|
+-- 「角色详情不带授权 ⇒ 抽屉全不勾且保存清空授权」那个缺陷(`1bda59a`,**2026-09-28 才修**)
|
|
|
|
|
+-- 期间被触发过一次。两种解释我用现有读数分不开,留给出题的人。
|
|
|
|
|
+-- ★ 仍然成立的部分:role 2 是**唯一**能让收款台真正收到钱的角色 ——
|
|
|
|
|
+-- `uk_lsy_merchant_mch_no` 是全局唯一,而 `Y52211090004` 属租户 2。
|
|
|
|
|
+--
|
|
|
-- ★ `lsy-onboarding-menu.sql` 只授 role 1,它挡的是这条链(每一跳它都实测过):
|
|
-- ★ `lsy-onboarding-menu.sql` 只授 role 1,它挡的是这条链(每一跳它都实测过):
|
|
|
-- `onboarding:commit` 的租户归属闸证明的是「**本租户内存在一行 mch_no 等于该值**」,
|
|
-- `onboarding:commit` 的租户归属闸证明的是「**本租户内存在一行 mch_no 等于该值**」,
|
|
|
-- 而映射表**调用方自己就能写**(`merchant:list` 一个读权限守着写和删、mch_no 由调用方给)
|
|
-- 而映射表**调用方自己就能写**(`merchant:list` 一个读权限守着写和删、mch_no 由调用方给)
|
|
@@ -86,11 +103,14 @@
|
|
|
-- SELECT 2, ...`(即回到只授 role 1),不用重跑本脚本 —— 见「放开/收回」。
|
|
-- SELECT 2, ...`(即回到只授 role 1),不用重跑本脚本 —— 见「放开/收回」。
|
|
|
--
|
|
--
|
|
|
-- ── 幂等(与 lsy-onboarding-menu.sql 同形,两种相反情形都不报错)──────────
|
|
-- ── 幂等(与 lsy-onboarding-menu.sql 同形,两种相反情形都不报错)──────────
|
|
|
--- 1) 页面行已存在 ⇒ `page` 为空 ⇒ **两个按钮与全部授权行都不会插**,且不报错。
|
|
|
|
|
--- 2) 收付款目录不存在 ⇒ `dir` 为空 ⇒ 三行都不插,且不报错。执行前先跑第 1 条只读核对。
|
|
|
|
|
|
|
+-- 1) 页面行已存在 ⇒ `page` 为空 ⇒ 页面与两个按钮都不插(`buttons` 是 `FROM page`,
|
|
|
|
|
+-- 自然也为空),且不报错。
|
|
|
|
|
+-- ★ 唯一**不看** `page` 是否为空的是目录那一行(见末条 INSERT 的注释):
|
|
|
|
|
+-- 它由 `NOT EXISTS` 单独判。⇒ 重跑时若目录已授过,仍然什么都不做。
|
|
|
|
|
+-- 2) 目录不存在 ⇒ `dir` 为空 ⇒ 三行都不插,且不报错。执行前先跑第 1 条只读核对。
|
|
|
--
|
|
--
|
|
|
-- ── "order" ──────────────────────────────────────────────────────────
|
|
-- ── "order" ──────────────────────────────────────────────────────────
|
|
|
--- 用 `MAX("order") + 1` **现算**,不写死数字。实测(2026-09-23)现值为 1/2/3/4 ⇒ 本行会拿到 **5**。
|
|
|
|
|
|
|
+-- 用 `MAX("order") + 1` **现算**,不写死数字。实测(2026-09-28 复量)目录 268 下现值为 1/2/3/4 ⇒ 本行会拿到 **5**。
|
|
|
-- ★ 写成字面量 5 会在「有人手工调过序」或「本脚本重跑」时撞车,而 `sys_menu` 对
|
|
-- ★ 写成字面量 5 会在「有人手工调过序」或「本脚本重跑」时撞车,而 `sys_menu` 对
|
|
|
-- (parent_id, "order") **没有唯一约束** ⇒ 撞了也不报错,只是两行同序。
|
|
-- (parent_id, "order") **没有唯一约束** ⇒ 撞了也不报错,只是两行同序。
|
|
|
-- ★ 全表行数实测 240(`lsy-onboarding-menu.sql` 写作时是 237,其后 +3 正是它自己插的
|
|
-- ★ 全表行数实测 240(`lsy-onboarding-menu.sql` 写作时是 237,其后 +3 正是它自己插的
|
|
@@ -100,13 +120,23 @@
|
|
|
-- DELETE FROM sys_role_menus
|
|
-- DELETE FROM sys_role_menus
|
|
|
-- WHERE role_id = 2
|
|
-- WHERE role_id = 2
|
|
|
-- AND menu_id IN (SELECT id FROM sys_menu
|
|
-- AND menu_id IN (SELECT id FROM sys_menu
|
|
|
--- WHERE name IN ('扫码收款', '发起收款', '撤销收款') AND status = '0');
|
|
|
|
|
|
|
+-- WHERE (name IN ('扫码收款', '发起收款', '撤销收款')
|
|
|
|
|
+-- OR route_name = 'Collection') -- ← 目录那行(2026-09-28 新增的授权)
|
|
|
|
|
+-- AND status = '0');
|
|
|
|
|
+-- ★ 收回**目录那一行**(`route_name='Collection'`)才是让页面从角色 2 视野里消失的那一下;
|
|
|
|
|
+-- 只删页面行、留着目录行**效果一样**(子节点失去父节点即被丢弃),但会留下一个空目录。
|
|
|
-- ★ **不要**用「重跑本脚本」来收回:页面行已存在 ⇒ 授权行也不会插(见幂等 1),
|
|
-- ★ **不要**用「重跑本脚本」来收回:页面行已存在 ⇒ 授权行也不会插(见幂等 1),
|
|
|
-- 但同样**也不会删**,跑了等于什么都没做。
|
|
-- 但同样**也不会删**,跑了等于什么都没做。
|
|
|
|
|
+-- ★ 这一条**只动 role 2**:角色 1 对 268 的持有是**本脚本执行前就有**的,不在本次交付范围内。
|
|
|
|
|
|
|
|
WITH dir AS (
|
|
WITH dir AS (
|
|
|
|
|
+ -- ★★ 锚在 `route_name`(**身份**)上,**不锚 `name`**(标签)—— 与 `lsy-merchant-menu-rename.sql:38-39` 同一模式。
|
|
|
|
|
+ -- 理由是一条实测:本目录 2026-09-23 叫「收付款」,**2026-09-28 实测已叫「银联收单」**。
|
|
|
|
|
+ -- 那次改名是**人在界面上手工做的** —— 「银联收单」四个字在全仓(sql/java/md/ts/vue)**零命中**,
|
|
|
|
|
+ -- 不是任何脚本干的。⇒ 锚 `name` 的写法今天会 `dir` 为空 ⇒ **全脚本静默空跑**(见文件头「幂等」情形 2)。
|
|
|
|
|
+ -- 同目录的 route_name 实测 = `Collection`(活库只读,2026-09-28)。
|
|
|
SELECT id FROM sys_menu
|
|
SELECT id FROM sys_menu
|
|
|
- WHERE name = '收付款' AND type = 1 AND status = '0' AND parent_id IS NULL
|
|
|
|
|
|
|
+ WHERE route_name = 'Collection' AND type = 1 AND status = '0' AND parent_id IS NULL
|
|
|
),
|
|
),
|
|
|
page AS (
|
|
page AS (
|
|
|
INSERT INTO sys_menu (name, type, "order", permission, icon, route_name, route_path, component_path,
|
|
INSERT INTO sys_menu (name, type, "order", permission, icon, route_name, route_path, component_path,
|
|
@@ -140,18 +170,50 @@ buttons AS (
|
|
|
)
|
|
)
|
|
|
-- 页面行与两个按钮行**都要**授权:菜单树按 sys_role_menus 过滤,只授页面会让按钮权限缺席 ⇒ 403。
|
|
-- 页面行与两个按钮行**都要**授权:菜单树按 sys_role_menus 过滤,只授页面会让按钮权限缺席 ⇒ 403。
|
|
|
-- 两个角色都授(理由见上「★★ 授权给谁」—— 与进件那份的差异是**故意的**)。
|
|
-- 两个角色都授(理由见上「★★ 授权给谁」—— 与进件那份的差异是**故意的**)。
|
|
|
|
|
+--
|
|
|
|
|
+-- ★★ 2026-09-28 追加第三类:**目录行 `dir` 本身也要授给角色 2**。
|
|
|
|
|
+-- 根因(源码实测,不是推测):`UserService.buildMenuTree:672-677` 建树时,
|
|
|
|
|
+-- 父节点不在被授集合里的子节点**直接被丢掉**(`if (p != null) {…}` **没有 else 分支**,
|
|
|
|
|
+-- 节点既不进 tree 也不报错)。而 `role/detail/2` 实测:角色 2 持有的 54 个菜单里
|
|
|
|
|
+-- **268/269/270/271/272 一个都没有** ⇒ 只授页面的话,角色 2 登录后**什么都看不到**。
|
|
|
|
|
+-- ⇒ 这是本脚本原先的缺口:它按「兄弟页已授 role 1,2」的前提写了授权范围
|
|
|
|
|
+-- (见上「★★ 授权给谁」的 2026-09-23 读数),而那个前提**今天不成立**。
|
|
|
|
|
+-- ★ 只补 `dir` 一行、**不**补兄弟页:`lsy-onboarding-menu.sql:66-69` 记录了
|
|
|
|
|
+-- 「把 272 商户进件开给角色 2」会重新打开的那条攻击链,本条不碰它。
|
|
|
|
|
+-- ★ 代价如实写:角色 2 因此多出一个顶层目录「银联收单」,里面只有「扫码收款」一页。
|
|
|
|
|
+-- 而 `uk_lsy_merchant_mch_no` 是**全局**唯一(不带 tenant_id)⇒ 除租户 2 外,
|
|
|
|
|
+-- 其余租户进来只会读到「该租户未配置商户号」那句 404 文案(功能上无害,但确实是死角)。
|
|
|
|
|
+--
|
|
|
|
|
+-- `NOT EXISTS` 是为了 `dir`:角色 1 **本来就持有 268**,裸插会撞 (role_id, menu_id) 约束
|
|
|
|
|
+-- ⇒ 整条语句失败、连带回滚页面与按钮的授权。与 `har-order-refund-menu.sql:34` 同形。
|
|
|
|
|
+--
|
|
|
|
|
+-- ★★ 2026-09-30 订正:`dir` 那一支**故意不挂** `EXISTS (SELECT 1 FROM page)`(原先挂着)。
|
|
|
|
|
+-- 原先那条闸把「页面行已存在」与「目录已授出去」绑在一起,而这两个条件**恰好会分开**:
|
|
|
|
|
+-- 页面行是旧版脚本(那时还没有目录授权)插的 ⇒ 页面在、目录没授 ⇒ 本脚本一跑就跳过目录,
|
|
|
|
|
+-- 而角色 2 看不到「银联收单」的根因**正是目录没授**(`buildMenuTree` 静默丢弃父不在集合里的
|
|
|
|
|
+-- 子节点)。⇒ 那条闸让脚本在**最需要它的时候**什么都不做,与它自己下面写的目标相反。
|
|
|
|
|
+-- 现在目录那一行**无条件参与**,是否真的插入仍由 `NOT EXISTS` 逐行判 ——
|
|
|
|
|
+-- 幂等性不变(重跑不插重复行、不报错),只是不再依赖「`page` 为空」这个无关条件。
|
|
|
|
|
+-- ★ 与本仓 `lsy-tenant-page-grants.sql` 的关系:那份脚本也授目录 268 给角色 2,
|
|
|
|
|
+-- 且不受任何 CTE 非空性影响。两者都在时,先跑哪一份结果都一样(都是 `NOT EXISTS` 逐行判)。
|
|
|
INSERT INTO sys_role_menus (role_id, menu_id)
|
|
INSERT INTO sys_role_menus (role_id, menu_id)
|
|
|
SELECT r.role_id, x.id
|
|
SELECT r.role_id, x.id
|
|
|
- FROM (SELECT id FROM page UNION ALL SELECT id FROM buttons) AS x
|
|
|
|
|
- CROSS JOIN (VALUES (1), (2)) AS r(role_id);
|
|
|
|
|
|
|
+ FROM (SELECT id FROM page
|
|
|
|
|
+ UNION ALL SELECT id FROM buttons
|
|
|
|
|
+ UNION ALL SELECT d.id FROM dir d) AS x
|
|
|
|
|
+ CROSS JOIN (VALUES (1), (2)) AS r(role_id)
|
|
|
|
|
+ WHERE NOT EXISTS (SELECT 1 FROM sys_role_menus rm
|
|
|
|
|
+ WHERE rm.role_id = r.role_id AND rm.menu_id = x.id);
|
|
|
|
|
|
|
|
|
|
|
|
|
-- ── 只读核对(执行后手工跑一遍;第 1 条**执行前**也先跑)────────────────
|
|
-- ── 只读核对(执行后手工跑一遍;第 1 条**执行前**也先跑)────────────────
|
|
|
--
|
|
--
|
|
|
--- 1) 收付款目录在不在(**为空则本脚本什么都不会插,且不报错**)
|
|
|
|
|
--- SELECT id, name, type, route_path FROM sys_menu
|
|
|
|
|
--- WHERE name = '收付款' AND type = 1 AND status = '0' AND parent_id IS NULL;
|
|
|
|
|
--- 期望:**1 行**(2026-09-23 实测 id=268)。0 行 ⇒ 先建目录再回来。
|
|
|
|
|
|
|
+-- 1) 目录在不在(**为空则本脚本什么都不会插,且不报错**)
|
|
|
|
|
+-- SELECT id, name, title, type, route_name, route_path FROM sys_menu
|
|
|
|
|
+-- WHERE route_name = 'Collection' AND type = 1 AND status = '0' AND parent_id IS NULL;
|
|
|
|
|
+-- 期望:**1 行**。2026-09-28 实测 = id 268 / name「银联收单」/ route_path `/payment/collection`。
|
|
|
|
|
+-- ★ 查的是 `route_name` 而不是 `name`(同 CTE 的理由)。若这里用旧名「收付款」查,今天会得 0 行
|
|
|
|
|
+-- —— 那**不是**目录不存在,只是它被改过名了。
|
|
|
--
|
|
--
|
|
|
-- 2) 页面行
|
|
-- 2) 页面行
|
|
|
-- SELECT id, name, type, "order", permission, route_name, route_path, component_path,
|
|
-- SELECT id, name, type, "order", permission, route_name, route_path, component_path,
|
|
@@ -170,12 +232,18 @@ SELECT r.role_id, x.id
|
|
|
-- 期望:**2 行**,permission 逐字为
|
|
-- 期望:**2 行**,permission 逐字为
|
|
|
-- 'module_payment:leshouying:cashier:create' 与 'module_payment:leshouying:cashier:close'。
|
|
-- 'module_payment:leshouying:cashier:create' 与 'module_payment:leshouying:cashier:close'。
|
|
|
--
|
|
--
|
|
|
--- 4) 授权行(**6 行**:3 个菜单 × 2 个角色)
|
|
|
|
|
--- SELECT rm.role_id, m.name, m.permission
|
|
|
|
|
|
|
+-- 4) 授权行(**7 行**)
|
|
|
|
|
+-- SELECT rm.role_id, m.id, m.name, m.type, m.permission
|
|
|
-- FROM sys_role_menus rm JOIN sys_menu m ON m.id = rm.menu_id
|
|
-- FROM sys_role_menus rm JOIN sys_menu m ON m.id = rm.menu_id
|
|
|
--- WHERE m.name IN ('扫码收款', '发起收款', '撤销收款') AND m.status = '0'
|
|
|
|
|
--- ORDER BY rm.role_id, m.name;
|
|
|
|
|
--- 期望:**6 行**,role_id 是 1 和 2 各 3 行。若只有 3 行(全是 1)⇒ role 2 被收回了,见文件头。
|
|
|
|
|
|
|
+-- WHERE m.status = '0'
|
|
|
|
|
+-- AND (m.name IN ('扫码收款', '发起收款', '撤销收款') OR m.route_name = 'Collection')
|
|
|
|
|
+-- ORDER BY rm.role_id, m.type, m.id;
|
|
|
|
|
+-- 期望:**7 行** ——
|
|
|
|
|
+-- role 1:扫码收款 + 发起收款 + 撤销收款 = **3 行**(目录 268 它**本来就有**,`NOT EXISTS` 跳过 ⇒ 不重复计数)
|
|
|
|
|
+-- role 2:同上 3 行 **+ 目录 268 = 4 行**
|
|
|
|
|
+-- ★ 判据不是「行数对不对」,而是 **role 2 那 4 行在不在**:
|
|
|
|
|
+-- 少了目录那行 ⇒ 角色 2 登录后**整页不可见**(父节点缺席,见文件头授权段)。
|
|
|
|
|
+-- 少了按钮行 ⇒ 非超管点「发起收款 / 撤销收款」**必 403**,而**超管测不出来**。
|
|
|
--
|
|
--
|
|
|
-- 5) 端到端(**唯一能证明权限串没写错的一条**):用一个**非超管**的租户账号登录,
|
|
-- 5) 端到端(**唯一能证明权限串没写错的一条**):用一个**非超管**的租户账号登录,
|
|
|
-- 确认三个 `module_payment:leshouying:cashier:*` 都出现在
|
|
-- 确认三个 `module_payment:leshouying:cashier:*` 都出现在
|