同一菜单、同一接口”按“县/镇/村”三级行政区划做数据隔离的完整落地方案。
思路:不改动部门(dept)体系,而是把“行政区划”当成一种新的维度,通过
自定义数据权限**(DataScope)实现。

──────────────────
一、数据库设计(MySQL)

  1. 行政区划表(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='行政区划';
  1. 业务数据表(以 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;
  1. 用户与行政区划关联表(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='用户管辖行政区划';
  1. 角色数据权限扩展表(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)
);

──────────────────
二、角色配置

  1. 建 3 个角色
    • 县级角色(county_admin)
    • 镇级角色(town_admin)
    • 村级角色(village_admin)

  2. 系统管理 → 角色管理 → 数据权限
    三种角色全部选 “自定数据权限”,再在 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

──────────────────
四、后端代码

  1. 新增枚举 DataScopeTypeEnum
public enum DataScopeTypeEnum {
    ALL("1"), CUSTOM("2"), DEPT("3"), DEPT_CHILD("4"), SELF("5"), REGION("R");
    ...
}
  1. 新建注解 @RegionScope(可选,用于标记 Mapper 方法)

  2. 新建 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";
        }
    }
}
  1. 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>
  1. Service 层
@Override
@RegionScope
public List<AgricultureProject> selectList(AgricultureProject bo) {
    return mapper.selectList(bo);
}

──────────────────
五、前端(ruoyi-ui)

  1. 页面级菜单、按钮同传统做法,无需改动。

  2. 用户新增/编辑弹窗增加“行政区划”级联选择:
    • src/views/system/user/index.vue
    • src/api/system/region.js(调用后端 /system/region/treeselect)

  3. 后端提供接口:

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 关联,县/镇/村天然形成层级过滤。

至此,同一菜单、同一接口即可根据登录人的行政区划级别完成数据隔离。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐