本文说明 DGJ2 PC、APP、OPS 管理端中的菜单展示、页面元素、接口权限、服务站能力、应用开通、白名单和财务锁站机制。核心原则是:页面可见、按钮可见、接口可调用、站点具备业务能力是四件不同的事。
适用于新增菜单/按钮/接口、给子账号授权、OPS 控制服务站能力、应用开通后菜单未出现、权限修改不生效、采购/销售被锁、财务逾期仅能查看资金菜单等场景。
1. 业务目标
- 明确旧权限体系与新 ACL 体系并存时的读取和写入位置。
- 解释服务站、用户、应用、白名单和页面资源之间的交集关系。
- 从“没有菜单”“没有按钮”“接口无权”“业务被禁止”反查到准确控制点。
- 新增功能时同时补齐资源、OPS、用户授权、接口校验和回归验证。
- 避免仅修改前端显示或单张表,造成权限绕过或配置表面生效。
- 为财务逾期锁站、采购/销售黑名单提供可执行排查流程。
2. 权限模型总览
flowchart LR
RES["资源默认配置\nt_acl_sys_resource"] --> MENU["站点菜单过滤"]
OPS["OPS菜单规则\nold/new"] --> MENU
APP["应用开通关系"] --> MENU
WL["白名单/地区规则"] --> MENU
USER["用户资源规则"] --> MENU
MENU --> PAGE["PC菜单与页面元素"]
USER --> API["运行时接口鉴权"]
CACHE["USER_RIGHTS缓存"] --> API
ABILITY["采购/销售能力黑名单"] --> BIZ["业务提交校验"]
LOCK["财务逾期锁站"] --> OPS
LOCK --> ACCOUNT["PC/APP/PDA账户状态"]
最终可用能力可近似理解为:
站点可见菜单
= 资源默认开启
∩ OPS允许
∩ 应用已开通
∩ 白名单允许
∩ 用户已授权
∩ 父级仍有可见子项
接口可调用
= 登录态有效
∩ 用户为主账号或拥有对应resource_id
∩ 业务控制器实际调用鉴权
∩ 站点业务能力未被额外禁用
3. 先区分四类问题
| 问题层 | 用户现象 | 主要数据源 | 首查代码 |
|---|---|---|---|
| 菜单 | 左侧没有入口 | Resource、OPS、应用、用户规则 | ResourceService::getMenuList |
| 页面元素 | 页面存在但按钮/字段不显示 | Resource type=2/4、前端 rights | getPageElementList、getWebRightList |
| 接口 | 手工请求返回无权限 | ResourceRule、USER_RIGHTS | Common_model::checkpurview |
| 业务能力 | 页面和接口都在,但提交采购/销售失败 | Redis 能力黑名单、锁站状态 | InvPoFactory、InvSaService |
不要把“菜单隐藏”等同于“接口禁止”。如果控制器没有调用权限校验,仅隐藏前端入口不能构成安全边界。
4. 新旧权限体系并存
DGJ2 处于从旧 SYS_MENU + SYS_ADMIN.lever 向新 ACL_SYS_RESOURCE + ACL_SYS_RESOURCE_RULE 迁移后的兼容阶段。
| 维度 | 旧体系 | 新体系 |
|---|---|---|
| 资源表 | t_sys_menu | t_acl_sys_resource |
| 用户授权 | t_sys_admin.lever 逗号分隔 ID | t_acl_sys_resource_rule 一行一个资源 |
| OPS 站点规则 | t_sys_ops_menu_rule | t_sys_ops_menu_rule_new |
| PC 菜单 | SYS_MENU.platform=pc | Resource platform=pc,type=1 |
| APP 菜单 | SYS_MENU.platform=app、HomeRight | 仍主要兼容旧数据 |
| 接口检查 | module -> SYS_MENU.id -> lever | code -> Resource.id -> ResourceRule |
| 缓存 | 主要直接读账号字段 | USER_RIGHTS:{uid},TTL 3600 秒 |
canAccessV2() 决定 Common_model 运行时采用哪套权限检查。排查时必须先确认当前用户进入的是旧分支还是新分支。
flowchart TD
REQ["checkpurview(module/code)"] --> EMPTY{"权限码为空?"}
EMPTY -- 是 --> PASS0["直接通过"]
EMPTY -- 否 --> ADMIN["查询SYS_ADMIN"]
ADMIN --> MAIN{"roleid=0?"}
MAIN -- 是 --> PASS1["主账号通过"]
MAIN -- 否 --> V2{"canAccessV2()?"}
V2 -- 否 --> OLD["SYS_MENU.module -> id\n检查SYS_ADMIN.lever"]
V2 -- 是 --> NEW["Resource.code -> id\n检查ResourceRule/Redis"]
OLD --> RESULT{"命中?"}
NEW --> RESULT
RESULT -- 是 --> PASS2["通过"]
RESULT -- 否 --> DENY["无页面管理权"]
5. 代码入口地图
| 职责 | 文件 | 核心方法 |
|---|---|---|
| PC 首页组装 | application/controllers/v2/Desktop.php | index |
| 新资源服务 | application/service/v2/ResourceService.php | getMenuList、getWebRightList |
| 通用接口鉴权 | application/models/Common_model.php | checkpurview、checkPermissionV2 |
| 子账号授权接口 | application/controllers/v2/Right.php | queryalluserright、addrights2Outuser |
| 资源创建接口 | application/controllers/v2/Resource.php | create |
| OPS 站点菜单入口 | application/controllers/inner/StationManage.php | getMenuConfig、setMenuConfig |
| OPS 菜单规则 | application/Services/MenuRule/MenuRuleSer.php | getOpsMenuRuleList、setOpsMenuRule |
| 采购/销售能力 | 同上 | getAbilityRuleList、setAbilityRule |
| 财务锁站 | 同上 | lockMenu、unLockMenu |
| 通用白名单 API | application/controllers/inner/sys/OpsWhitelist.php | index、batchSet |
| 通用白名单服务 | application/Services/System/OpsWhitelistSer.php | getWhitelistPage、batchSetWhitelist |
| 菜单同步任务 | application/controllers/tasks/MenuRule.php | syncOpsMenuRule、refreshSysMenu、refreshUserLever |
| ACL 迁移脚本 | application/controllers/tasks/AclMigrate.php | 多阶段迁移方法 |
6. 核心表字典
6.1 新 ACL 资源表
| 常量 | 物理表 | 关键字段 | 用途 |
|---|---|---|---|
ACL_SYS_RESOURCE | t_acl_sys_resource | id,parent_id,type,title,module,code,platform,status,is_delete,display,path | 菜单、按钮、接口、前端权限定义 |
ACL_SYS_RESOURCE_RULE | t_acl_sys_resource_rule | sid,uid,resource_id,platform,code,is_delete | 子账号拥有的资源 |
资源类型来自 ResourceEnums:
| type | 名称 | 典型用途 |
|---|---|---|
| 1 | 菜单 | 左侧导航和菜单树 |
| 2 | 页面元素 | 按钮、字段、操作入口 |
| 3 | 接口 | 后端调用权限定义 |
| 4 | Web Right | 前端按 rights[code] 控制的能力 |
通用状态:status=1 启用、0 关闭;is_delete=0 有效、1 逻辑删除;display=1 显示、0 隐藏。
6.2 旧菜单与账号表
| 常量 | 物理表 | 关键字段 | 用途 |
|---|---|---|---|
SYS_ADMIN | t_sys_admin | uid,sid,roleid,lever,storeLever,storeDefault,status,isDelete | 登录账号、旧权限、门店数据范围 |
SYS_MENU | t_sys_menu | id,parentId,module,platform,path,level,isDelete | 旧 PC/APP 菜单 |
SYS_MENU_DATA | t_sys_menu_data | id,surname 等 | 旧数据权限辅助配置 |
SYS_MENU_CONFIG | t_sys_menu_config | sid,content 等 | 历史菜单配置源 |
roleid=0 在当前通用权限逻辑中视为主账号,跳过用户资源过滤;子账号必须命中授权。
6.3 OPS 站点控制表
| 常量 | 物理表 | 关键字段 | 用途 |
|---|---|---|---|
SYS_OPS_MENU_RULE | t_sys_ops_menu_rule | sid,pc_rule_json,app_rule_json,update_user_* | 旧 PC 与 APP OPS 菜单规则 |
SYS_OPS_MENU_RULE_NEW | t_sys_ops_menu_rule_new | sid,pc_rule_json,before_lock_menu,lock_status,update_user_* | 新 PC 规则与财务锁站快照 |
SYS_OPS_WHITELIST | t_sys_ops_whitelist | sid,finance_version,create_user,update_user,*_time | 可扩展的站点开通配置 |
旧、新 OPS 表必须一起理解:setOpsMenuRule 会把 pcMenuList/appMenuList 写旧表,把 newPcMenuList 写新表。
6.4 应用和特殊控制
菜单组装还会读取应用定义、应用资源关系、站点应用开通关系及特殊权限控制。相关模型包括:
ApplicationModelApplicationResourceRelModelApplicationOpenModelSpecialAuthorityControlModelHomeRightModel
应用相关菜单必须同时满足“资源存在、站点已开应用、用户有权”。
7. 新 PC 菜单过滤顺序
ResourceService::getMenuList 的真实顺序非常重要:
- 从 Resource 读取 PC、菜单类型、启用、未删除、默认显示的资源。
- 执行地区/运营/报价/对账等白名单过滤。
- 执行
t_sys_ops_menu_rule_new站点菜单过滤。 - 执行应用开通关系过滤。
- 执行用户资源权限过滤。
- 子账号额外隐藏权限设置和系统新增入口。
- 转为树结构。
- 如果一个父节点所有子项都隐藏,父节点也隐藏。
flowchart TD
A["Resource有效PC菜单"] --> B["filterWhiteListRule"]
B --> C["filterOpsMenuRuleNew"]
C --> D["filterApplicationResourceRule"]
D --> E["filterUserResourceRule"]
E --> F["特殊账号规则"]
F --> G["list_to_tree"]
G --> H["父级无可见子项则隐藏"]
H --> OUT["Desktop.menus"]
这意味着只查最后一层用户授权并不够。前面任一层把 display 置为 0,后续用户授权不会自动恢复。
8. PC 首页返回的三类权限数据
v2/Desktop::index 一次加载三组数据:
| 返回字段 | 服务方法 | 前端用途 |
|---|---|---|
menus | getMenuList | 左侧菜单和路由 |
pageElement | getPageElementList | 应用相关页面元素显示 |
rights | getWebRightList | 按权限码控制按钮/操作 |
rights 最终是类似 {CODE: true} 的映射,并会通过 CODE_CHANGE_REVERSE 兼容旧权限码。因此排查前端按钮时要同时搜索新旧 code。
9. 用户授权写入流程
子账号授权入口:v2/Right::addrights2Outuser。
sequenceDiagram
participant UI as 权限设置页
participant C as v2/Right
participant S as ResourceService
participant RR as ResourceRule
participant A as SYS_ADMIN
participant R as Redis
UI->>C: ruleIds/noRuleIds + APP权限 + 门店范围
C->>C: checkpurview(STAFF_ADD)
C->>S: createPcResourceRule
S->>A: 更新storeLever/storeDefault
S->>RR: 恢复、插入或逻辑删除资源规则
S->>R: DEL USER_RIGHTS:{uid}
C->>S: createAppResourceRule
S->>A: 更新旧lever字段
C-->>UI: 操作成功
9.1 PC 资源规则的细节
ruleIds会自动补齐所有父资源 ID,避免只有子项没有父菜单。- 已逻辑删除的相同规则会恢复
is_delete=0。 - 新规则批量插入。
noRuleIds指定的规则逻辑删除为is_delete=1。- 最后删除
USER_RIGHTS:{uid}缓存。 - 门店范围同步到
SYS_ADMIN.storeLever/storeDefault,并可能触发 IM 群成员变更。
9.2 APP 资源规则的细节
APP 仍通过 SYS_ADMIN.lever 维护:新增 ID 合并,取消 ID 从集合剔除,再写回逗号分隔字符串。
9.3 授权请求示例
以下仅表达字段语义,URL 以前端实际路由配置为准:
POST /v2/right/addrights2Outuser
Content-Type: application/json
{
"JXCSID": 10001,
"userId": 20001,
"ruleIds": "101,102,103",
"noRuleIds": "201,202",
"appRightid": "419,422",
"appNoRightid": "430",
"storeLever": "10,11",
"storeDefault": "10",
"status": 1
}
约束:设置门店范围时必须有一个默认门店,storeDefault 不能包含多个 ID。
代码审计提示:createPcResourceRule当前仍引用历史变量$userName进行参数检查和日志记录,但方法签名没有该参数。现网如未暴露告警,可能受 PHP 错误级别影响;后续修改该公共方法时应先补测试,不要在无回归的情况下顺手调整。
10. 运行时接口鉴权
典型控制器会显式调用:
$this->common_model->checkpurview('STAFF_ADD');
新体系检查过程:
- 用
ResourceEnums::CODE_CHANGE把旧 code 映射成新 code。 - 从 Redis 读取
USER_RIGHTS:{uid}。 - 缓存不存在时查
t_acl_sys_resource_rule的有效resource_id。 - 把结果缓存 3600 秒。
- 用 Resource
code找资源 ID。 - 判断资源 ID 是否在用户权限列表中。
module 参数也可以是数组,数组语义是“任一权限命中即可通过”。
10.1 三种通用检查方法
| 方法 | 无权行为 | 适用场景 |
|---|---|---|
checkpurview | 页面跳转/JSON 错误 | 传统控制器入口 |
checkpurdata | 返回 1/0 | 是否展示敏感数据 |
checkPurviewWithException | 抛异常 | Service/API 统一异常处理 |
10.2 重要风险
如果控制器只依赖前端 rights 隐藏按钮,没有调用任何后端鉴权方法,用户仍可能直接请求接口。新增写接口时必须检查同模块既有入口的后端校验模式。
11. USER_RIGHTS 缓存
| 项 | 值 |
|---|---|
| Redis 枚举 | RedisKeys::USER_RIGHTS |
| 逻辑格式 | user_rights:{uid},实际带项目统一前缀 |
| 内容 | JSON 数组,元素为 resource_id |
| TTL | 3600 秒 |
| 正常失效点 | createPcResourceRule 写权限后主动删除 |
flowchart LR
CHECK["接口鉴权"] --> EXISTS{"缓存存在?"}
EXISTS -- 是 --> JSON["解析resource_id数组"]
EXISTS -- 否 --> DB["查询有效ResourceRule"]
DB --> SET["缓存3600秒"]
SET --> JSON
JSON --> MATCH{"目标资源ID存在?"}
MATCH -- 是 --> YES["允许"]
MATCH -- 否 --> NO["拒绝"]
如果通过脚本或 SQL 直接改 ResourceRule,没有走服务方法,旧缓存可能继续生效一小时。生产排查应先证明数据库与缓存是否一致,不能直接清空整个 Redis。
12. OPS 菜单配置
12.1 查询站点配置
逻辑入口:inner/StationManage::getMenuConfig。
POST /inner/stationManage/getMenuConfig
Content-Type: application/json
{
"sid": 10001
}
返回分三组:
{
"menuConfig": {
"pcMenuList": [],
"newPcMenuList": [],
"appMenuList": []
},
"accountConfig": {},
"abilityConfig": {
"poAbilityNo": false,
"saleAbilityNo": false
}
}
12.2 设置站点配置
POST /inner/stationManage/setMenuConfig
Content-Type: application/json
{
"sid": 10001,
"config": {
"menuConfig": {
"pcMenuList": [],
"newPcMenuList": [],
"appMenuList": []
},
"abilityConfig": {
"poAbilityNo": 0,
"saleAbilityNo": 0
}
}
}
操作者信息由可信内部请求上下文提供,文档不记录真实请求凭证。接口在数据库事务中写账号、菜单和能力配置,并增加安全操作日志。
13. 新旧 OPS 规则的生成方式
13.1 旧 PC 规则
数据基础来自 MenusService::getMenus,再叠加旧表 pc_rule_json 的 display 配置。
13.2 新 PC 规则
数据基础来自 Resource:
type=菜单
status=启用
is_delete=否
platform=pc
display=显示
规则按 module_id 建立临时映射,再把 display 回填到资源树。应用中心会动态加入应用数据;部分应用默认隐藏,需明确开通。
13.3 APP 规则
数据基础来自 HomeRightModel,排除 PDA/HOMEPAGE 后,按 module 构造首页、查找、数据统计、配送等分组,再叠加旧表 app_rule_json。
14. 应用开通与菜单
应用菜单不是普通菜单授权:
flowchart LR
DEF["应用定义"] --> REL["应用-资源关系"]
OPEN["站点应用开通"] --> REL
REL --> FILTER["ResourceService应用过滤"]
USER["用户资源授权"] --> FILTER
FILTER --> SHOW["菜单/页面元素显示"]
排查应用菜单时依次确认:
- 应用定义有效。
- Resource 已绑定到应用。
- 站点存在有效应用开通记录。
- OPS 没有隐藏该资源。
- 子账号拥有对应资源。
- 应用页面元素规则也处于开启状态。
15. 通用 OPS 白名单
当前 SysOpsWhitelistModel::COLUMN_DEFAULT 明确定义的可配置字段为:
| 字段 | 值 | 默认行为 |
|---|---|---|
finance_version | 1 老版、2 新版 | 无记录或非 1 时按新版 2 |
通用白名单表按“可扩展字段”设计,但服务和 Validator 只接受 COLUMN_DEFAULT 中登记的字段。新增列时必须同步模型默认值、DDL、校验、列表和业务读取方。
15.1 白名单查询
支持站点、城市、财务版本、开通时间范围和分页。列表以正常主账号站点为底表,左连接白名单,因此未配置站点也会按默认值显示。
15.2 批量设置
POST /inner/sys/opsWhitelist/batchSet
Content-Type: application/json
{
"sids": [10001, 10002],
"config_column": "finance_version",
"config_value": 2
}
模型会:
- 已存在
sid:按记录 ID 批量更新,并写更新人。 - 不存在
sid:合并所有默认值后批量插入,并写创建人。 - 无效
config_column:Validator 拒绝。
16. 地区和运营白名单
ResourceService::filterWhiteListRule 还读取多个 Redis/业务白名单:
| 控制 | 结果 | 排查方向 |
|---|---|---|
| 地区简化菜单 | 仅保留销售、采购或两者 | 会话 areaCode 与地区 Redis Hash |
| 精细化运营白名单 | 服务站/修理厂画像、库存分析菜单 | 站点是否在运营白名单 |
| 报价规则版本 | 切换菜单标题、table_code、href | QuoteManager 的 V2 判断 |
| 对账单白名单 | 可能切换对账菜单配置 | Statement 白名单服务 |
| 少量特殊功能 | 代码级站点例外 | 搜索目标权限 code 的硬编码判断 |
代码中存在少量历史硬编码站点例外。公共文档不列真实站点标识;维护时应优先迁移到有审计记录的配置或白名单表。
17. 采购与销售能力黑名单
OPS 的 abilityConfig 不是菜单控制,而是业务提交控制:
| 字段 | Redis Hash | 1 的业务含义 |
|---|---|---|
poAbilityNo | HASH_PURCHASE_STATION_BLACKLIST | 禁止采购能力 |
saleAbilityNo | HASH_SALE_STATION_BLACKLIST | 禁止销售能力 |
setAbilityRule 的写法:值为 1 时 HSET sid 1,值为 0 时 HDEL sid。
读取点包括:
- 采购:
application/Services/InvPoFactory.php - 销售:
application/service/scm/InvSaService.php - OPS 展示:
MenuRuleSer::getAbilityRuleList - 缓存管理:
application/controllers/inner/CacheManage.php
sequenceDiagram
participant OPS as OPS配置
participant MS as MenuRuleSer
participant R as Redis Hash
participant B as 采购/销售Service
OPS->>MS: abilityConfig
alt 禁止能力
MS->>R: HSET hash sid 1
else 恢复能力
MS->>R: HDEL hash sid
end
B->>R: HGET hash sid
R-->>B: 1或空
B-->>B: 拒绝或继续业务
因此“菜单能看到但采购提交失败”应优先查能力 Hash,而不是 ResourceRule。
18. 财务逾期锁站
支付中心逾期状态变化会调用 MenuRuleSer::lockMenu/unLockMenu。
18.1 锁定
- 从 Resource 获取所有有效 PC 菜单。
- 仅保留
module=money为显示,其余隐藏。 - 把锁定前
pc_rule_json保存到before_lock_menu。 - 写
lock_status=1。 - 锁定 PDA、APP、商城相关账号。
- 记录财务锁定安全日志。
- 通过 WebSocket 通知站点强制下线。
18.2 解锁
- 要求站点存在新 OPS 配置。
- 要求当前
lock_status=1。 - 用
before_lock_menu恢复pc_rule_json。 - 清空快照并写
lock_status=0。 - 解锁 PDA、APP、商城账号。
- 记录财务解锁日志。
stateDiagram-v2
[*] --> Unlocked
Unlocked: lock_status=0
Unlocked --> Locked: 逾期锁定
Locked: lock_status=1\n仅显示资金菜单
Locked --> Unlocked: 逾期解除
Locked --> Locked: 重复锁定抛异常
Unlocked --> Unlocked: 重复解锁抛异常
18.3 主要风险
- 首次锁定时快照来自动态菜单结果,必须确认是完整规则。
- 锁定过程中数据库、账号服务、WebSocket 不是天然分布式事务。
before_lock_menu被覆盖或为空时,解锁可能无法恢复原菜单。- 直接修改
pc_rule_json可能破坏锁定快照。 - 重复锁/解锁当前会抛状态异常,上游需理解重试行为。
19. 菜单同步和迁移任务
| 任务 | 作用 | 风险 |
|---|---|---|
syncOpsMenuRule | 菜单源与 SYS_MENU_CONFIG 合并写旧 OPS 规则 | 可能批量覆盖站点 JSON |
refreshSysMenu | 根据 APP HomeRight 刷新旧 SYS_MENU | 固定 ID/重复执行风险需验 DDL |
refreshUserLever | 把 PC 对应 APP 菜单合并进用户 lever | 可能扩大旧 APP 权限 |
AclMigrate | 旧菜单、用户规则迁移到新 ACL | 迁移脚本步骤多,不应在线随意重跑 |
syncLoginStatus | 按站点状态同步 PC/APP/PDA 关闭标记 | 影响整站登录 |
执行任何批量任务前必须备份受影响记录、统计目标数量、限定环境,并准备回滚数据。
20. 新增菜单的标准流程
flowchart TD
A["确定菜单层级/路由/权限码"] --> B["新增Resource资源"]
B --> C["确认parent_id/path/level/sort"]
C --> D["如属应用则绑定应用资源关系"]
D --> E["更新OPS规则展示"]
E --> F["给测试子账号授权"]
F --> G["控制器增加后端鉴权"]
G --> H["清权限缓存/重新登录"]
H --> I["主账号、子账号、白名单内外回归"]
资源最少需要确认:
| 字段 | 要求 |
|---|---|
parent_id | 指向正确父资源 |
platform | PC 当前为 pc |
module | 前端菜单模块标识,保持同层约定 |
title | 展示名称 |
type | 菜单/页面元素/接口/Web Right |
code | 全局稳定权限码,接口校验使用 |
table_code | 前端页面/表格标识,按项目约定 |
href | 实际页面路由 |
level/path/sort | 树层级与顺序 |
status/is_delete/display | 启用、有效、默认显示 |
21. 新增按钮或接口权限
新增写操作建议至少有两类资源:
- 前端按钮/Web Right:控制正常 UI 是否显示。
- 后端接口权限:控制直接请求是否允许。
检查清单:
- 新 code 是否唯一。
- 旧前端是否仍使用历史 code,是否需要兼容映射。
- 权限设置页能否看到并勾选资源。
- 子账号授权是否自动带父资源。
- 控制器是否在业务执行前校验。
- 主账号和系统内部回调是否需要不同授权策略。
- 查询、导出、编辑、删除是否应拆分权限。
22. SQL 排查模板
所有 SQL 默认为只读;表字段以目标环境 DDL 为准。
22.1 查账号和新旧权限分支
SELECT uid, sid, username, roleid, status, isDelete,
lever, storeLever, storeDefault
FROM t_sys_admin
WHERE uid = :uid;
判断:roleid=0 通常为主账号;子账号继续查 ResourceRule 或 lever。
22.2 按权限码查资源
SELECT id, parent_id, type, title, module, code,
platform, status, is_delete, display, path, href
FROM t_acl_sys_resource
WHERE code = :resource_code;
重点检查是否重复 code、是否逻辑删除、是否默认隐藏、父资源是否有效。
22.3 查完整父链
SELECT id, parent_id, title, module, code, level, path,
status, is_delete, display
FROM t_acl_sys_resource
WHERE FIND_IN_SET(id, :resource_path)
OR id = :resource_id
ORDER BY level, sort;
22.4 查用户资源
SELECT rr.sid, rr.uid, rr.resource_id, rr.code, rr.platform,
rr.is_delete, r.title, r.module, r.type, r.status, r.display
FROM t_acl_sys_resource_rule rr
LEFT JOIN t_acl_sys_resource r ON r.id = rr.resource_id
WHERE rr.sid = :sid
AND rr.uid = :uid
ORDER BY rr.is_delete, rr.resource_id;
22.5 查站点 OPS 规则
SELECT sid, pc_rule_json, app_rule_json,
update_user_name, update_user_code
FROM t_sys_ops_menu_rule
WHERE sid = :sid;
SELECT sid, pc_rule_json, before_lock_menu, lock_status,
update_user_name, update_user_code
FROM t_sys_ops_menu_rule_new
WHERE sid = :sid;
不要在生产查询结果中传播完整 JSON、人员信息或真实站点标识。
22.6 查通用白名单
SELECT sid, finance_version, create_user, update_user,
create_time, update_time, modify_time
FROM t_sys_ops_whitelist
WHERE sid = :sid;
无记录不等于老版;当前 Service 会默认返回财务新版。
22.7 查旧菜单
SELECT id, parentId, name, module, platform, path,
level, status, isDelete
FROM t_sys_menu
WHERE module = :module
ORDER BY platform, level;
23. Redis 排查原则
不要在文档或工单记录 Redis 密码、地址和完整 key 前缀。使用项目已有安全连接方式。
需要核对的逻辑键:
USER_RIGHTS:{uid}
HASH_PURCHASE_STATION_BLACKLIST[sid]
HASH_SALE_STATION_BLACKLIST[sid]
HASH_SIDS_PROVINCE_SIMPLE_MENU[areaCode]
HASH_REFINED_OPERATIONS_WHITE_LIST[sid]
只读检查顺序:
- 确认连接环境。
- 获取逻辑 key 的实际项目前缀。
- 查看类型和 TTL。
- 仅读取目标 uid/sid field。
- 与数据库记录比对。
- 如需删除单用户缓存,记录操作前后值和原因。
24. 常用代码搜索
# 查权限码在哪些页面、按钮和接口使用
rg -n "TARGET_RESOURCE_CODE" application public assets
# 查后端是否真正鉴权
rg -n "checkpurview|checkPermissionV2|checkPurviewWithException" application/controllers application/service application/Services
# 查菜单组装与过滤顺序
rg -n "getMenuList|filterWhiteListRule|filterOpsMenuRuleNew|filterUserResourceRule" application/service/v2/ResourceService.php
# 查新旧资源映射
rg -n "CODE_CHANGE|CODE_CHANGE_REVERSE" application/KzData/Enums application
# 查OPS菜单和财务锁站
rg -n "setOpsMenuRule|lockMenu|unLockMenu|before_lock_menu|lock_status" application
# 查采购/销售能力黑名单
rg -n "HASH_PURCHASE_STATION_BLACKLIST|HASH_SALE_STATION_BLACKLIST" application
# 查通用白名单读取方
rg -n "OpsWhitelistSer|getFinanceVersion|SYS_OPS_WHITELIST" application
25. 故障一:主账号有菜单,子账号没有
flowchart TD
A["主账号有/子账号无"] --> B{"Resource默认有效?"}
B -- 否 --> R1["修资源配置"]
B -- 是 --> C{"站点OPS/应用允许?"}
C -- 否 --> R2["修站点级配置"]
C -- 是 --> D{"ResourceRule有效?"}
D -- 否 --> R3["走授权接口补规则"]
D -- 是 --> E{"USER_RIGHTS含resource_id?"}
E -- 否 --> R4["删除单用户缓存再验证"]
E -- 是 --> F{"父资源均可见?"}
F -- 否 --> R5["补父链/修父级display"]
F -- 是 --> R6["检查前端缓存和路由"]
补权限时不要只插叶子资源,标准服务会自动补父资源。
26. 故障二:菜单可见但接口无权限
按以下顺序:
- 记录接口实际传入
checkpurview的 code/module。 - 查
CODE_CHANGE是否发生映射。 - 查 Resource 中目标 code 是否唯一且有效。
- 查 ResourceRule 是否属于同一
sid + uid。 - 查 USER_RIGHTS 是否为旧值。
- 确认用户会话 uid 与页面展示账号一致。
- 确认接口没有校验另一个更细粒度 code。
常见原因是菜单资源和接口资源不是同一个 ID,或者前端按一个 Web Right 显示按钮,而后端检查另一个接口 code。
27. 故障三:有按钮但直接请求可越权
这是安全问题,不是显示问题。
- 确认按钮资源是否只是 type=2/4。
- 搜索控制器是否调用权限检查。
- 使用无权限子账号验证直接请求。
- 在业务执行前增加后端权限校验。
- 回归正常账号、主账号、内部系统回调。
- 检查批量、导入、导出等旁路接口。
28. 故障四:OPS 设置后页面不变
| 检查项 | 预期 |
|---|---|
| 请求是否成功 | 事务提交且返回成功 |
| 写入哪张表 | 旧 PC/APP 写旧表,新 PC 写新表 |
| JSON 是否有效 | 可解析、节点 id/module/display 完整 |
| 当前页面体系 | Desktop 是否走 v2 ResourceService |
| 应用/用户过滤 | 后续过滤可能继续隐藏 |
| 财务锁状态 | 锁定时新 PC JSON 被锁站规则覆盖 |
| 前端状态 | 重新拉 Desktop 数据或重新登录 |
OPS 菜单当前主要数据库直读,没有统一菜单缓存;“不生效”更多来自写错新旧表、后续过滤或浏览器未刷新。
29. 故障五:采购或销售突然不可用
- 查对应能力 Redis Hash 的目标 sid field。
- 查 OPS 最近是否设置
abilityConfig。 - 查财务是否触发锁站和账户锁定。
- 查站点账号状态、登录关闭字段。
- 查具体业务 Service 的拒绝日志。
- 菜单仍存在时不要误判为权限资源故障。
恢复能力应走 OPS 服务写入,使安全日志和操作来源完整;不要直接删除整个 Hash。
30. 故障六:财务解锁后菜单不完整
- 查
lock_status是否从 1 变为 0。 - 查解锁前
before_lock_menu是否非空且 JSON 有效。 - 比较当前
pc_rule_json与快照。 - 检查锁定期间是否有 OPS 菜单变更覆盖快照。
- 查 APP/PDA/商城账号是否都已解锁。
- 查用户 ResourceRule 和应用开通是否另有变化。
- 重新登录获取完整 Desktop 数据。
仅恢复菜单 JSON 不代表账号和业务能力全部恢复。
31. 故障七:白名单内外表现相反
优先确认语义:系统中同时存在“白名单”“黑名单”“版本默认值”“地区简化菜单”。
| 配置 | 空值语义 |
|---|---|
finance_version | 默认新版 2 |
| 采购/销售能力 Hash | 无 field 表示不禁止 |
| 运营白名单 | 无 field 表示不展示目标菜单 |
| 地区简化菜单 | 无匹配类型时不做该项筛选 |
| OPS pc_rule_json | 无对应节点时使用资源默认 display |
排查时必须写清“存在时是允许还是禁止”,避免把黑名单当白名单。
32. 数据修复门槛
权限数据修复前至少满足:
- 已确认环境、sid、uid、资源 code 和 resource_id。
- 已确认旧/新权限分支。
- 已导出目标账号、资源规则、OPS JSON 的修复前快照。
- 已评估父资源、应用关系、门店范围和 IM 副作用。
- 已确认用户缓存的单点失效方式。
- 已准备主账号和最小权限子账号验收。
禁止做法:
- 清空整库 Redis。
- 批量把所有资源
display改为 1。 - 给子账号复制主账号全量权限以临时绕过。
- 覆盖 OPS JSON 而不保存原值。
- 在财务锁定状态下直接改
pc_rule_json。 - 只修页面显示,不验证接口权限。
33. 完整回归矩阵
| 维度 | 用例 |
|---|---|
| 账号 | 主账号、已授权子账号、未授权子账号、禁用账号 |
| 平台 | 新 PC、旧 PC 兼容、APP、PDA |
| 资源 | 一级菜单、三级菜单、按钮、查询、编辑、导出、接口 |
| 站点 | 默认站点、OPS 隐藏站点、应用未开站点、白名单内外 |
| 能力 | 采购允许/禁止、销售允许/禁止 |
| 财务 | 未锁、锁定、重复锁、解锁、重复解锁 |
| 缓存 | 无缓存、旧缓存、写权限后缓存删除、TTL 后重建 |
| 门店 | 无分店、多个分店、默认门店变更 |
| 兼容 | 旧 code、新 code、CODE_CHANGE 双向映射 |
| 安全 | 隐藏按钮直调接口、越权 sid/uid、批量/导入/导出旁路 |
34. 新功能上线验收
34.1 配置层
- Resource 字段、父链、path、sort、code 正确。
- OPS 新旧规则展示符合目标平台。
- 应用资源关系和站点开通正确。
- 白名单默认值和内外表现正确。
- 没有把真实账号或站点写死在新增逻辑中。
34.2 用户层
- 主账号可见并可操作。
- 已授权子账号可见并可操作。
- 未授权子账号不可见且直调接口被拒绝。
- 权限取消后缓存立即失效。
- 父菜单不会残留空壳,也不会误隐藏其他子项。
34.3 业务层
- 菜单、按钮和接口使用同一业务语义的权限码。
- 采购/销售能力禁用时提交被拒绝。
- 恢复能力后无需人工改业务单即可继续正常操作。
- 财务锁定仅保留资金菜单,相关账号被锁并强制下线。
- 解锁恢复原菜单、账号和业务能力。
34.4 审计层
- 权限变更有系统日志。
- OPS 变更记录操作者。
- 锁站/解锁有账户安全日志。
- 批量任务有目标数量、开始结束和异常日志。
35. 改动风险分级
| 级别 | 改动 | 风险 |
|---|---|---|
| 高 | Common_model 鉴权、ResourceService 过滤顺序 | 影响全站菜单和接口 |
| 高 | Resource/ResourceRule 批量迁移 | 可能全量扩大或收缩权限 |
| 高 | lockMenu/unLockMenu | 菜单、账号、WebSocket 多系统副作用 |
| 高 | AclMigrate、菜单同步任务 | 批量覆盖历史配置 |
| 中 | 修复 createPcResourceRule 历史变量 | 可能改变现有告警、日志或异常分支 |
| 中 | 新增资源或 code 映射 | 影响目标模块及旧前端兼容 |
| 中 | OPS 菜单 JSON | 影响单站或一组站点菜单 |
| 中 | 能力黑名单 | 直接阻断采购/销售 |
| 低 | 单用户授权调整 | 影响单账号,但仍需清缓存和审计 |
36. 证据来源
| 结论 | 代码证据 |
|---|---|
| 新 PC 菜单六层过滤 | application/service/v2/ResourceService.php::getMenuList |
| Resource 四种类型与状态 | application/KzData/Enums/ResourceEnums.php |
| 主账号跳过、子账号检查 | application/models/Common_model.php::checkpurview |
| USER_RIGHTS TTL 3600 | Common_model::checkPermissionV2 |
| 写权限后删除缓存 | ResourceService::createPcResourceRule |
| 新旧 code 兼容 | ResourceEnums::CODE_CHANGE/CODE_CHANGE_REVERSE |
| OPS 同写新旧规则 | MenuRuleSer::setOpsMenuRule |
| 采购/销售黑名单 | MenuRuleSer::setAbilityRule |
| 财务锁定仅留资金 | MenuRuleSer::getLockMenu/lockMenu/unLockMenu |
| 通用白名单默认新版 | SysOpsWhitelistModel::COLUMN_DEFAULT、OpsWhitelistSer::getFinanceVersion |
| 白名单字段受校验限制 | SysOpsWhitelistValidate::checkConfigColumn |
| 旧菜单刷新和迁移任务 | application/controllers/tasks/MenuRule.php、AclMigrate.php |
37. 待环境确认项
- 线上
canAccessV2()对不同站点和版本的实际分流条件。 - Resource 与 ResourceRule 的唯一索引、外键及历史重复数据。
- 前端对
menus/pageElement/rights的本地缓存和刷新时机。 - OPS 内部接口真实网关路由、认证和审计保留周期。
- 应用开通关系的线上状态枚举和失效机制。
- 财务锁站的消息重试、WebSocket 失败补偿和跨系统一致性。
- 历史硬编码站点例外是否仍有效,能否配置化下线。
- 菜单同步任务当前调度状态、最近运行时间和可重入性。
环境未确认项不能写成确定事实;上线前应通过 DDL、真实配置、只读查询和最小权限账号验证补齐。
请求-日志-数据变更追踪卡
多入口请求链路
| 场景 | 调用方与入口 | 请求载荷/上下文 | Controller/Consumer | Service/Provider | 汇合点 | 最终业务事实 |
|---|---|---|---|---|---|---|
| PC 菜单加载 | 登录后桌面请求 | 用户、sid、角色、组织 | v2/Desktop.php | v2/ResourceService.php | 用户-角色-资源关系 | 返回当前用户可见菜单与按钮 |
| 权限配置 | OPS/管理页 | 资源、规则、角色、菜单配置 | v2/Right.php、v2/Resource.php | Resource/MenuRule Service | resource/menu ID | 权限关系和菜单规则落库 |
| 白名单维护 | 内部系统 | OPS 用户/接口/站点范围 | inner/sys/OpsWhitelist.php | OpsWhitelistSer.php | 白名单业务键 | 命中白名单的例外放行事实 |
| 规则迁移 | CLI task | 旧规则批次、用户/菜单范围 | tasks/MenuRule.php、AclMigrate.php | 迁移 Service | 旧新规则 ID | 新旧权限数据按批迁移 |
日志证据矩阵
| 链路段 | 日志来源 | 可检索锚点 | 成功信号 | 失败信号 | 与下一段关联方式 |
|---|---|---|---|---|---|
| 登录与菜单 | Web 日志/Desktop | request_id、用户 ID、sid、URI | 菜单树含目标资源 | 未登录、菜单为空、站点上下文错 | 用户 ID 查角色和规则 |
| 鉴权判定 | BaseController/Resource Service | controller/method、resource ID、用户/角色 | allow 且进入业务方法 | deny/资源未登记 | resource ID 关联菜单和规则表 |
| 配置写入 | Right/Resource/MenuRule Service | 操作人、资源/角色 ID | 规则 commit,重新查询生效 | duplicate、父子菜单错误、缓存旧 | 规则 ID 与菜单树回查 |
| 白名单/迁移 | OpsWhitelistSer/Task | 白名单键、task 批次、用户 ID | 例外命中或迁移数一致 | 范围过宽、重复迁移、部分失败 | 业务键/批次查新旧表 |
环节数据变更台账
| 步骤 | 代码位置 | 事务 | 读取事实 | 写入表/缓存/MQ | 字段或数量变化 | 回查证据 |
|---|---|---|---|---|---|---|
| 资源登记 | v2/Resource.php | 权限事务 | Controller/method、父资源 | ACL_SYS_RESOURCE、ACL_SYS_RESOURCE_RULE | resource/rule insert 或启停 old -> new | 路由、资源 ID、规则行 |
| 菜单授权 | Right/MenuRule Service | 权限事务 | 用户角色、菜单配置 | SYS_MENU*、SYS_OPS_MENU_RULE(_NEW) | 角色-菜单关系增删;排序/状态更新 | 用户菜单树与表关系 |
| 白名单 | OpsWhitelistSer | 独立事务 | 对象是否已存在、范围 | SYS_OPS_WHITELIST | insert/delete/enable;仅窄范围例外 | 对象键、操作人、更新时间 |
| 读取生效 | Desktop/ResourceService | 只读;缓存边界需确认 | 资源、规则、菜单、白名单 | 可能的权限缓存 | DB 不变;计算 allow/deny 与菜单树 | 最小权限账号实测 + request ID |
| 迁移 | AclMigrate/MenuRule task | 每批事务 | 旧规则、目标是否存在 | 新规则表 | 旧关系 -> 新关系;重复批次不重复 insert | 批次计数、新旧集合差 |
子模块追踪:menu-load PC 菜单加载与过滤
| 环节 | 入口/触发 | 请求/业务键 | 代码链路 | 读取事实 | 写入与字段变化 | 日志证据 | 异常与补偿 |
|---|---|---|---|---|---|---|---|
| 加载 | PC 首页,userId/sid | request ID、用户与站点 | application/controllers/v2/Desktop.php -> application/service/v2/ResourceService.php | 主子账号、ACL、旧菜单、OPS/应用/锁站 | 查询只读 不写;逐层过滤菜单树 | request ID + userId/sid + resource code | 从第一处过滤原因修复,不硬加最终菜单 |
| 生效 | 授权后重新加载 | userId、缓存键 | application/controllers/inner/CacheManage.php | DB 授权与 USER_RIGHTS 缓存 | 事务外 cache old -> deleted/latest,权限 DB 不变 | userId + key + hit/miss | DB 正确只清受影响缓存;TTL 需环境确认 |
子模块追踪:user-grant 用户授权写入
| 环节 | 入口/触发 | 请求/业务键 | 代码链路 | 读取事实 | 写入与字段变化 | 日志证据 | 异常与补偿 |
|---|---|---|---|---|---|---|---|
| 授权 | 管理员勾选 PC/App 资源 | operator、userId、resource IDs | application/controllers/v2/Right.php -> application/service/v2/ResourceService.php | 操作者权限、资源和父子关系、原集合 | 权限本地事务 old grants -> new grants | request ID + operator/userId + count | 越权/非法资源零写入;批量失败整批回滚 |
| 回查 | 保存后按钮仍旧 | userId、resource code | application/controllers/v2/Resource.php | 授权表、资源规则、缓存 | 查询只读 不写;必要时失效缓存 | userId + resource code + cache state | DB 缺失重走授权接口,不手插孤儿规则 |
子模块追踪:runtime-auth 运行时接口鉴权
| 环节 | 入口/触发 | 请求/业务键 | 代码链路 | 读取事实 | 写入与字段变化 | 日志证据 | 异常与补偿 |
|---|---|---|---|---|---|---|---|
| 鉴权 | 直接请求受控 action | userId、sid、URI、permission code | application/core/BaseController.php -> application/KzData/Enums/ResourceEnums.php | session、资源码、用户授权、站点限制 | 鉴权查询只读 不写;拒绝时业务零写入 | request ID + URI + userId/sid + code | 菜单可见不等于接口有权;缺鉴权是代码风险 |
| 执行 | 鉴权通过后业务动作 | billNo、当前状态 | application/service/scm/InvSaService.php | 对象归属和业务前置条件 | 领域本地事务才执行 status old -> new | request ID + permission + billNo | 越权尝试不得有副作用;回归按钮和直接 URL |
子模块追踪:rights-cache USER_RIGHTS 缓存
| 环节 | 入口/触发 | 请求/业务键 | 代码链路 | 读取事实 | 写入与字段变化 | 日志证据 | 异常与补偿 |
|---|---|---|---|---|---|---|---|
| 读写 | 菜单/鉴权读取权限 | userId、cache key | application/service/v2/ResourceService.php | Redis 命中、DB 授权、资源状态 | miss 后事务外 none -> rights snapshot,DB 不变 | userId + key + hit/miss + count | 区分空缓存和无权限;Redis 环境事实待验证 |
| 失效 | 授权/迁移/资源变更 | userId/批次 | application/controllers/inner/CacheManage.php | 受影响用户和旧缓存 | 事务外 delete cache,授权 DB 不写 | batch/userId + deleted count | 只清受影响键;失败重试不重复改授权 |
子模块追踪:ops-menu OPS 菜单规则配置
| 环节 | 入口/触发 | 请求/业务键 | 代码链路 | 读取事实 | 写入与字段变化 | 日志证据 | 异常与补偿 |
|---|---|---|---|---|---|---|---|
| 配置 | OPS 按站点设置菜单 | operator、sid、menu/resource codes | application/Services/MenuRule/MenuRuleSer.php | 新旧规则、站点与菜单映射 | 配置本地事务 old rules -> new rules,同步规则表 | request ID + sid + rule count | 生成失败整批回滚;新旧表双读验收 |
| 过滤 | PC/App 加载菜单 | sid、app/type | application/controllers/inner/StationManage.php | 站点规则、应用开通、用户授权 | 查询只读 不写;取多层交集 | request ID + sid + matched rules | 页面旧时查缓存/应用开通,不重复保存 |
子模块追踪:app-menu 应用开通与菜单
| 环节 | 入口/触发 | 请求/业务键 | 代码链路 | 读取事实 | 写入与字段变化 | 日志证据 | 异常与补偿 |
|---|---|---|---|---|---|---|---|
| 开停应用 | 站点应用配置 | sid、app code、menu set | application/Services/MenuRule/MenuRuleSer.php | 应用状态、资源映射、已有规则 | 本地事务 app disabled/enabled old -> new 并更新菜单关系 | request ID + sid + app code | 停用后敏感接口也须拒绝;失败整批回滚 |
| 回查 | 已开通但无入口 | userId、sid、resource code | application/controllers/v2/Desktop.php | 应用、OPS 规则、授权、缓存 | 查询只读 不写 | request ID + sid/userId + app code | 找第一处过滤原因,禁止绕过权限硬显示 |
子模块追踪:ops-whitelist OPS、地区与运营白名单
| 环节 | 入口/触发 | 请求/业务键 | 代码链路 | 读取事实 | 写入与字段变化 | 日志证据 | 异常与补偿 |
|---|---|---|---|---|---|---|---|
| 设置 | 批量增删白名单 | operator、rule code、sid/area 集合 | application/controllers/inner/sys/OpsWhitelist.php -> application/Services/System/OpsWhitelistSer.php | 规则定义、原集合、站点地区 | 白名单本地事务 old set -> new set | request ID + rule code + item count | 空集合和反选语义须确认;失败整批回滚 |
| 命中 | 页面/业务判断例外 | sid、area、rule code | application/Services/System/OpsWhitelistSer.php | 当前规则和站点属性 | 查询只读 不写;返回 allow/deny | request ID + sid + rule + matched row | 表现相反先查 include/exclude 与缓存 |
子模块追踪:business-blacklist 采购销售黑名单与财务锁站
| 环节 | 入口/触发 | 请求/业务键 | 代码链路 | 读取事实 | 写入与字段变化 | 日志证据 | 异常与补偿 |
|---|---|---|---|---|---|---|---|
| 拦截锁定 | 采购/销售创建或逾期任务 | sid、能力类型、逾期单 | application/Services/InvPoFactory.php、application/service/scm/InvSaService.php | 黑名单、逾期未还和锁因 | 业务校验只读;锁站本地事务 unlocked -> locked | request/task ID + sid + rule/repaymentNo | 拦截业务零写入;多个锁因分别保留 |
| 解锁 | 移出名单或还清 | sid、有效锁因集合 | application/service/scm/InvSaService.php | 全部黑名单和逾期事实 | 条件满足才本地事务 locked -> unlocked | sid + action + remaining reasons | 不因单笔还款清其他锁;菜单缓存另失效 |
子模块追踪:acl-migration 菜单同步与 ACL 迁移
| 环节 | 入口/触发 | 请求/业务键 | 代码链路 | 读取事实 | 写入与字段变化 | 日志证据 | 异常与补偿 |
|---|---|---|---|---|---|---|---|
| 迁移 | CLI ACL/MenuRule 任务 | batch、resource/menu code | application/controllers/tasks/AclMigrate.php、application/controllers/tasks/MenuRule.php | 旧菜单、新资源、映射、已迁移标记 | 每批本地事务 upsert old ACL -> new ACL,重跑零新增 | task + batch + insert/update counts | 先小批;中断保留失败码,成功项不重复 |
| 验收 | 新旧菜单和鉴权对比 | sample users、resource codes | application/service/v2/ResourceService.php | 新旧授权、父链、鉴权和缓存 | 查询只读 不写;回滚按批次恢复映射 | batch + sample + diff count | 历史脏数据单列,不自动吞掉未知资源 |