基于若依实现区划权限控制案例
同一菜单、同一接口”按“县/镇/村”三级行政区划做数据隔离的完整落地方案。
思路:不改动部门(dept)体系,而是把“行政区划”当成一种新的维度,通过自定义数据权限**(DataScope)实现。
──────────────────
一、数据库设计(MySQL)
- 行政区划表(sys_region)
CREATE TABLE sys_region (
id bigint NOT NULL PRIMARY KEY AUTO_INCREMENT,
code varchar(20) NOT NULL COMMENT '区划代码:县=6位,镇=9位,村=12位',
name varchar(50) NOT NULL,
parent_id bigint NOT NULL DEFAULT 0,
level tinyint NOT NULL COMMENT '1省 2市 3县 4镇 5村',
ancestors varchar(200) COMMENT '如 0,1,2,3',
del_flag char(1) NOT NULL DEFAULT '0',
UNIQUE KEY uk_code (code)
) ENGINE=InnoDB COMMENT='行政区划';
- 业务数据表(以 agriculture_project 为例)
CREATE TABLE agriculture_project (
id bigint PRIMARY KEY AUTO_INCREMENT,
title varchar(100),
region_code varchar(20) NOT NULL COMMENT '所属行政区划代码',
create_by varchar(64),
create_time datetime,
update_time datetime,
del_flag char(1) DEFAULT '0'
) ENGINE=InnoDB;
- 用户与行政区划关联表(sys_user_region)
CREATE TABLE sys_user_region (
user_id bigint NOT NULL,
region_code varchar(20) NOT NULL,
level tinyint NOT NULL COMMENT '3县 4镇 5村',
) ENGINE=InnoDB COMMENT='用户管辖行政区划';
- 角色数据权限扩展表(sys_role_data_scope)
CREATE TABLE sys_role_data_scope (
role_id bigint NOT NULL,
scope_type char(1) NOT NULL COMMENT 'R=按行政区划过滤',
scope_value varchar(20) COMMENT 'role 对应的 region_code',
PRIMARY KEY (role_id)
);
──────────────────
二、角色配置
-
建 3 个角色
• 县级角色(county_admin)
• 镇级角色(town_admin)
• 村级角色(village_admin) -
在 系统管理 → 角色管理 → 数据权限
三种角色全部选 “自定数据权限”,再在sys_role_data_scope中插入:
| role_id | scope_type | scope_value |
|---|---|---|
| 101 | R | 320311 |
| 102 | R | 320311001 |
| 103 | R | 32031100101 |
──────────────────
三、用户绑定行政区划
在 系统管理 → 用户管理 → 编辑 → 更多 → 行政区划 里,往 sys_user_region 写一条记录:
| user_id | region_code | level |
|---|---|---|
| 1 | 320311 | 3 |
| 2 | 320311001 | 4 |
| 3 | 32031100101 | 5 |
(前端下拉框用级联组件,数据来源 sys_region)
──────────────────
四、后端代码
- 新增枚举
DataScopeTypeEnum
public enum DataScopeTypeEnum {
ALL("1"), CUSTOM("2"), DEPT("3"), DEPT_CHILD("4"), SELF("5"), REGION("R");
...
}
-
新建注解
@RegionScope(可选,用于标记 Mapper 方法) -
新建 AOP:RegionDataScopeAspect
作用:把用户能看到的 region_code 列表 注入到参数对象中的params.regionScope字段。
@Aspect
@Component
public class RegionDataScopeAspect {
@Before("@annotation(regionScope)")
public void doBefore(JoinPoint jp, RegionScope regionScope) {
SysUser user = SecurityUtils.getLoginUser().getUser();
String regionScopeSql = buildRegionScope(user);
Object param = Arrays.stream(jp.getArgs())
.filter(a -> a instanceof BaseEntity)
.findFirst()
.orElse(null);
if (param != null) {
((BaseEntity) param).getParams().put("regionScope", regionScopeSql);
}
}
private String buildRegionScope(SysUser user) {
Long userId = user.getUserId();
SysUserRegion ur = SpringUtils.getBean(SysUserRegionMapper.class)
.selectByUserId(userId);
if (ur == null) return "1=0"; // 无权
switch (ur.getLevel()) {
case 3: // 县
return "p.region_code like concat('" + ur.getRegionCode() + "', '%')";
case 4: // 镇
return "p.region_code like concat('" + ur.getRegionCode() + "', '%')";
case 5: // 村
return "p.region_code = '" + ur.getRegionCode() + "'";
default:
return "1=0";
}
}
}
- Mapper XML(agriculture_project.xml)
<select id="selectList" resultType="...">
SELECT * FROM agriculture_project p
<where>
p.del_flag = '0'
<if test="params.regionScope != null">
AND ${params.regionScope}
</if>
</where>
</select>
- Service 层
@Override
@RegionScope
public List<AgricultureProject> selectList(AgricultureProject bo) {
return mapper.selectList(bo);
}
──────────────────
五、前端(ruoyi-ui)
-
页面级菜单、按钮同传统做法,无需改动。
-
用户新增/编辑弹窗增加“行政区划”级联选择:
• src/views/system/user/index.vue
• src/api/system/region.js(调用后端 /system/region/treeselect) -
后端提供接口:
GET /system/region/treeselect
返回格式沿用 RuoYi 的 TreeSelect。
──────────────────
六、验证
| 登录用户 | 角色 | 实际 region_code | 只能看到 |
|---|---|---|---|
| A | county_admin | 320311 | 320311*** |
| B | town_admin | 320311001 | 320311001*** |
| C | village_admin | 32031100101 | 32031100101 |
──────────────────
七、小结
• 不侵入原有部门/岗位体系,行政区划完全平行。
• 数据权限仍使用 “自定义” + AOP 动态 SQL 这一官方思路。
• 角色、用户、业务表全部通过 region_code 关联,县/镇/村天然形成层级过滤。
至此,同一菜单、同一接口即可根据登录人的行政区划级别完成数据隔离。
更多推荐




所有评论(0)