“多租户(tenant)+ 部门数据权限(dept)双层隔离” 的升鲜宝最终设计(单体 Spring Boot 直接可落地,未来拆微服务也不改核心规则)。
“多租户(tenant)+ 部门数据权限(dept)双层隔离” 的升鲜宝最终设计(单体 Spring Boot 直接可落地,未来拆微服务也不改核心规则)。
1)双层隔离的核心目标
第一层:租户隔离(硬隔离)
-
所有业务表必须有
tenant_id -
所有查询/写入都必须自动带
tenant_id -
任何绕过都视为严重漏洞
第二层:组织数据权限(软隔离)
同一租户内,不同角色只能看到自己范围:
-
部门(dept_id)
-
门店(shop_id)
-
仓库(warehouse_id)
-
以及“本人数据(created_by)”等
最终效果:
tenant_id 把公司隔开;dept/shop/warehouse 把内部组织隔开。
2)统一字段规范(全库强制)
所有业务表(必须)
-
tenant_id BIGINT NOT NULL -
created_by BIGINT -
created_at BIGINT -
deleted TINYINT DEFAULT 0 -
enabled TINYINT DEFAULT 1
需要组织隔离的业务表(按域选择其一或多)
-
dept_id BIGINT(组织/部门维度) -
shop_id BIGINT(门店域/hwms/pos/销售) -
warehouse_id BIGINT(公司仓库/wms/成本) -
company_id BIGINT(主组织维度,可选)
升鲜宝建议:
WMS & cost:warehouse_id 必须有
HWMS & hcost:shop_id 必须有
订单:sales 走 shop_id,purchase 走 company/warehouse(看你履约)
3)权限模型:RBAC + DataScope(数据范围)
3.1 角色数据范围枚举(DataScopeType)
-
ALL:本租户全量(仅租户管理员/老板) -
DEPT:本部门 -
DEPT_AND_SUB:本部门及下级 -
SHOPS:指定门店集合 -
WAREHOUSES:指定仓库集合 -
SELF:本人创建的数据 -
CUSTOM:自定义(部门+门店+仓库混合)
注意:所有 scope 都是在 tenant_id 已过滤后 才生效。
4)数据库表结构(权限域)
4.1 组织结构(树)
-
org_dept(id, tenant_id, parent_id, dept_name, path, level, ...) -
org_shop(id, tenant_id, company_id, ...) -
org_warehouse(id, tenant_id, company_id, ...)
4.2 用户/角色/权限
-
sys_user(id, tenant_id, username, password_hash, dept_id, is_tenant_admin, ...) -
sys_role(id, tenant_id, role_code, role_name, ...) -
sys_user_role(id, tenant_id, user_id, role_id) -
sys_permission(id, tenant_id, perm_code, perm_name, ...) -
sys_role_permission(id, tenant_id, role_id, perm_id)
4.3 数据范围(关键)
角色的数据范围策略表:
-
sys_role_data_scope(id, tenant_id, role_id, scope_type, ...)
当 scope_type = SHOPS / WAREHOUSES / CUSTOM 时,需要映射表:
-
sys_role_scope_shop(id, tenant_id, role_id, shop_id) -
sys_role_scope_warehouse(id, tenant_id, role_id, warehouse_id) -
sys_role_scope_dept(id, tenant_id, role_id, dept_id)(用于 CUSTOM 或 DEPT 子集)
为什么用“角色”而不是“用户”存数据范围?
因为同角色一批人可复用规则;用户特殊情况可加sys_user_data_scope_override做覆盖(可选)。
5)执行链路:必须“双拦截”
5.1 Tenant 拦截(强制)
实现方式(推荐 MyBatis-Plus):
-
TenantLineInnerInterceptor 自动拼:
tenant_id = ? -
对于不含 tenant_id 的表(平台表 tenant/tenant_module),加入忽略名单
5.2 DataScope 拦截(组织范围)
实现方式(推荐两种之一):
方案 A(推荐):MyBatis-Plus DataPermissionInterceptor
-
对指定 Mapper 方法开启数据权限
-
自动拼接:
dept_id/shop_id/warehouse_id/created_by条件
方案 B:AOP + SQL 注入(不推荐长期用)
-
用 AOP 包一层拼条件,侵入性更高
升鲜宝建议:
Tenant 用 TenantLineInnerInterceptor
数据权限用 DataPermissionInterceptor(可维护、可统一管理)。
6)数据权限规则生成器(核心算法)
对每次请求,先得到一个 DataScopeContext:
-
tenantId(必有)
-
userId
-
deptId
-
roleScopes(可能多个角色,取并集 or 取最宽策略)
-
allowedShopIds / allowedWarehouseIds / allowedDeptIds(集合)
然后按业务表的“组织维度”生成 SQL 条件:
示例 1:WMS 库存表(warehouse_id)
示例 2:HWMS 门店库存表(shop_id)
示例 3:通用单据(dept_id + created_by)
若 scope=DEPT_AND_SUB:
若 scope=SELF:
多角色合并规则(建议)
-
取并集(更符合“多个角色赋权叠加”)
-
但租户管理员直接
ALL
7)“表级策略”配置(避免每张表手写)
做一张元数据表:sys_data_scope_rule
-
table_name -
scope_dimension:DEPT/SHOP/WAREHOUSE/SELF/MIXED -
dept_field/shop_field/warehouse_field/owner_field -
enabled
这样 DataPermissionInterceptor 能根据 “表名 → 维度字段” 自动拼接,不需要每个 Mapper 单独写。
8)典型升鲜宝域的推荐隔离维度
| 域 | 推荐维度字段 | 说明 |
|---|---|---|
| product 商品域 | dept_id 或 company_id(可选) | 多数情况下租户内共享即可 |
| supplier 供应商域 | company_id 或 dept_id | 采购组织隔离 |
| wms 公司仓库域 | warehouse_id(必须) | 仓库是天然数据边界 |
| cost 公司成本域 | warehouse_id(必须) | 成本必须跟仓库隔离 |
| hwms 门店域 | shop_id(必须) | 门店是天然边界 |
| hcost 门店成本域 | shop_id(必须) | 同上 |
| sales 销售域 | shop_id + created_by | 门店业务 |
| finance 财务域 | company_id/ dept_id | 视财务组织而定 |
9)安全底线(必须做)
-
所有写入必须自动填充 tenant_id
-
所有查询必须自动带 tenant_id(不允许手写遗漏)
-
数据权限条件必须在 SQL 层生效(只靠前端/业务逻辑不算)
-
对管理员账号(tenant_admin)走白名单放行,但仍必须 tenant 隔离
-
审计表(sys_audit_log)也要带 tenant_id + operator_id

浙公网安备 33010602011771号