Skip to content

数据权限使用说明

概述

数据权限是一种基于用户身份和权限范围来控制数据访问的机制。通过 @DataPermission 注解,可以在接口层面实现细粒度的数据权限控制,确保用户只能访问其权限范围内的数据。

核心概念

数据权限范围(DataScopeEnum)

  • ALL: 全部数据权限,可以访问所有数据
  • CUSTOM: 自定义数据权限,只能访问指定的数据ID集合
  • PERSONAL: 个人数据权限,只能访问自己创建的数据

验证模式(ValidationMode)

  • SINGLE: 单一验证模式,直接验证请求参数中的数据ID
  • QUERY_VALIDATE: 查询验证模式,通过查询数据库获取数据ID后再验证
  • MULTIPLE: 多重验证模式,支持批量数据验证

登录处理

1. 登录DTO定义

java
@Data
public class LoginDto {
    /**
     * 用户名
     */
    @NotBlank(message = "用户名不能为空!")
    private String accountNo;
    
    /**
     * 密码
     */
    @NotBlank(message = "密码不能为空!")
    private String password;
}

2. 登录Controller

java
@RestController
public class LoginController {

    @Autowired
    private LoginService loginService;

    /**
     * 登录接口
     * @param dto 登录参数
     * @return 登录结果,包含用户信息和权限信息
     */
    @PostMapping(value = "/login", produces = MediaType.APPLICATION_JSON_VALUE)
    @Lock(keys = "#p0.accountNo")  // 防止并发登录
    @OperationLog(name = "登录", content = "'欢迎登录系统,'+#p0?.accountNo+',登录ip:'+#user?.ip")
    public Mono<ResultVo<SessionVo>> login(@RequestBody @Valid LoginDto dto) {
        return Mono.just(ResultVo.ok(loginService.login(dto)));
    }
}

3. 登录Service实现

java
@Service
@Slf4j
public class LoginServiceImpl implements LoginService {

    @Override
    public SessionVo login(LoginDto dto) {
        // 1. 验证用户名密码
        if (!"123456".equals(dto.getPassword())) {
            throw new InvokeException(-1, "用户名或密码不正确");
        }
        
        // 2. 创建Session对象
        SessionVo session = new SessionVo();
        session.setIsSystem("admin".equals(dto.getAccountNo())); // 是否超级管理员
        session.setUserId("1"); // 用户ID
        session.setNickname("wueasy"); // 用户昵称
        
        // 3. 设置功能权限
        Set<String> authorizeCodeList = new HashSet<String>();
        authorizeCodeList.add("user");
        session.setAuthorizeCodeList(authorizeCodeList); // 权限代码集合
        
        // 4. 设置URL访问权限
        Set<String> linkUrlSetAll = new HashSet<String>();
        linkUrlSetAll.add("/demo/get");
        session.setAuthorizeUrlList(linkUrlSetAll);
        
        // 5. 设置数据权限
        setupDataPermission(session, dto.getAccountNo());
        
        return session;
    }
    
    /**
     * 设置数据权限
     * @param session 会话对象
     * @param accountNo 账号
     */
    private void setupDataPermission(SessionVo session, String accountNo) {
        DataScopeEnum dataScopeEnum = null;
        Set<String> dataAuthorizeIdSet = new HashSet<>();
        
        // 根据用户类型设置不同的数据权限
        if ("admin".equals(accountNo)) {
            // 管理员:全部数据权限
            dataScopeEnum = DataScopeEnum.ALL;
        } else if ("admin2".equals(accountNo)) {
            // 部门管理员:自定义数据权限
            dataScopeEnum = DataScopeEnum.CUSTOM;
            dataAuthorizeIdSet.add("1");
            dataAuthorizeIdSet.add("2");
        } else if ("admin3".equals(accountNo)) {
            // 普通用户:个人数据权限
            dataScopeEnum = DataScopeEnum.PERSONAL;
        }
        
        // 设置数据权限映射
        Map<String, DataScopeEnum> dataScopeMap = new HashMap<>();
        Map<String, Set<String>> dataAuthorizeMap = new HashMap<>();
        dataScopeMap.put("org", dataScopeEnum); // org为业务类型
        dataAuthorizeMap.put("org", dataAuthorizeIdSet);
        
        session.setDataScopeMap(dataScopeMap);
        session.setDataAuthorizeMap(dataAuthorizeMap);
    }
}

数据权限处理

1. @DataPermission注解使用

基本参数说明

  • businessType: 业务类型,如"org"表示组织权限
  • modes: 验证模式数组
  • idParams: 需要验证的参数名称
  • serviceClass: 查询服务类(QUERY_VALIDATE模式需要)
  • queryMethod: 查询方法名(QUERY_VALIDATE模式需要)
  • queryParam: 查询参数名(QUERY_VALIDATE模式需要)

2. 不同场景的使用示例

场景1:新增数据(SINGLE模式)

java
/**
 * 新增测试数据
 * 直接验证请求参数中的orgId是否在用户权限范围内
 */
@PostMapping(value = "/add", produces = MediaType.APPLICATION_JSON_VALUE)
@DataPermission(businessType = "org", modes = {ValidationMode.SINGLE}, idParams = "orgId")
public Mono<ResultVo<Void>> add(@RequestBody @Valid TestAddDto dto) {
    testService.add(dto);
    return Mono.just(ResultVo.ok());
}

场景2:修改数据(QUERY_VALIDATE + SINGLE模式)

java
/**
 * 修改测试数据
 * 1. 先通过id查询出orgId(QUERY_VALIDATE)
 * 2. 再验证orgId权限(SINGLE)
 */
@PostMapping(value = "/edit", produces = MediaType.APPLICATION_JSON_VALUE)
@DataPermission(
    businessType = "org",
    modes = {ValidationMode.QUERY_VALIDATE, ValidationMode.SINGLE},
    idParams = "orgId",
    serviceClass = TestService.class,
    queryMethod = "getOrgIdById",
    queryParam = "id"
)
public Mono<ResultVo<Void>> edit(@RequestBody @Valid TestEditDto dto) {
    testService.edit(dto);
    return Mono.just(ResultVo.ok());
}

场景3:查询/删除数据(QUERY_VALIDATE模式)

java
/**
 * 查询单个测试数据
 * 通过id查询出orgId,然后验证权限
 */
@PostMapping(value = "/get", produces = MediaType.APPLICATION_JSON_VALUE)
@DataPermission(
    businessType = "org",
    modes = {ValidationMode.QUERY_VALIDATE},
    idParams = "orgId",
    serviceClass = TestService.class,
    queryMethod = "getOrgIdById",
    queryParam = "id"
)
public Mono<ResultVo<TestVo>> get(@RequestBody @Valid TestIdDto dto) {
    return Mono.just(ResultVo.ok(testService.get(dto)));
}

/**
 * 删除测试数据
 */
@PostMapping(value = "/delete", produces = MediaType.APPLICATION_JSON_VALUE)
@DataPermission(
    businessType = "org",
    modes = {ValidationMode.QUERY_VALIDATE},
    idParams = "orgId",
    serviceClass = TestService.class,
    queryMethod = "getOrgIdById",
    queryParam = "id"
)
public Mono<ResultVo<Void>> delete(@RequestBody @Valid TestIdDto dto) {
    testService.delete(dto);
    return Mono.just(ResultVo.ok());
}

3. 查询方法实现

当使用 QUERY_VALIDATE 模式时,需要在Service中实现对应的查询方法:

java
@Service
public class TestServiceImpl implements TestService {

    /**
     * 根据ID查询组织ID
     * 用于数据权限验证
     * @param id 数据ID
     * @return 组织ID
     */
    @Override
    public Long getOrgIdById(Long id) {
        Test test = null;
        
        // 如果是个人数据权限,需要额外验证创建人
        if (DataScopeEnum.PERSONAL.equals(UserPermissionUtil.getDataScope("org"))) {
            LambdaQueryWrapper<Test> lambdaQueryWrapper = new LambdaQueryWrapper<>();
            lambdaQueryWrapper.eq(Test::getId, id);
            lambdaQueryWrapper.eq(Test::getCreatedBy, UserHelper.getUserIdLong());
            test = testMapper.selectOne(lambdaQueryWrapper);
        } else {
            test = testMapper.selectById(id);
        }
        
        if (null == test) {
            throw new InvokeException(-1, "记录不存在");
        }
        
        return test.getOrgId();
    }
}

4. 分页查询中的数据权限

对于分页查询,通常不使用 @DataPermission 注解,而是在Service层直接处理:

java
@Override
public PageVo<TestVo> queryPage(TestQueryPageDto dto) {
    // 获取用户数据权限
    DataScopeEnum dataScope = UserPermissionUtil.getDataScope("org");
    Set<String> dataAuthorizeIdSet = UserPermissionUtil.getDataAuthorizeIdSet("org");
    
    LambdaQueryWrapper<Test> lambdaQueryWrapper = new LambdaQueryWrapper<>();
    
    // 添加业务查询条件
    if (StringUtils.isNotBlank(dto.getName())) {
        lambdaQueryWrapper.like(Test::getName, dto.getName());
    }
    if (dto.getOrgId() != null) {
        lambdaQueryWrapper.eq(Test::getOrgId, dto.getOrgId());
    }
    
    // 添加数据权限过滤
    if (DataScopeEnum.CUSTOM.equals(dataScope)) {
        // 自定义权限:只能查看指定组织的数据
        lambdaQueryWrapper.in(Test::getOrgId, dataAuthorizeIdSet);
    } else if (DataScopeEnum.PERSONAL.equals(dataScope)) {
        // 个人权限:只能查看自己创建的数据
        lambdaQueryWrapper.eq(Test::getCreatedBy, UserHelper.getUserIdLong());
    }
    // ALL权限不需要额外过滤
    
    // 执行分页查询
    IPage<Test> page = new Page<>(dto.getPageNum(), dto.getPageSize());
    IPage<Test> resultPage = testMapper.selectPage(page, lambdaQueryWrapper);
    
    // 转换为VO对象
    PageVo<TestVo> newPage = new PageVo<>();
    newPage.setPageNum(resultPage.getCurrent());
    newPage.setPageSize(resultPage.getSize());
    newPage.setTotal(resultPage.getTotal());
    
    if (CollectionUtils.isNotEmpty(resultPage.getRecords())) {
        List<TestVo> newList = resultPage.getRecords().stream()
            .map(this::convertToVo)
            .collect(Collectors.toList());
        newPage.setList(newList);
    }
    
    return newPage;
}

工具类使用

1. UserHelper - 用户信息获取

java
// 获取当前用户ID
Long userId = UserHelper.getUserIdLong();
String userIdStr = UserHelper.getUserId();

// 获取用户昵称
String nickname = UserHelper.getNickname();

// 判断是否系统管理员
Boolean isSystem = UserHelper.getIsSystem();

2. UserPermissionUtil - 权限工具

java
// 获取指定业务类型的数据权限范围
DataScopeEnum dataScope = UserPermissionUtil.getDataScope("org");

// 获取指定业务类型的数据权限ID集合
Set<String> dataAuthorizeIdSet = UserPermissionUtil.getDataAuthorizeIdSet("org");

注意事项

  1. 业务类型一致性: 登录时设置的业务类型(如"org")必须与 @DataPermission 注解中的 businessType 保持一致。

  2. 查询方法要求: 使用 QUERY_VALIDATE 模式时,查询方法必须返回需要验证的数据ID,方法签名要与注解参数匹配。

  3. 个人数据权限: 当数据权限为 PERSONAL 时,除了验证业务权限外,还需要验证数据的创建人是否为当前用户。

  4. 异常处理: 权限验证失败时会抛出 InvokeException,需要在全局异常处理器中统一处理。

  5. 性能考虑: 对于大量数据的查询,建议在数据库层面添加相应的索引以提高查询性能。

  6. 事务处理: 涉及多表操作的Service方法需要添加 @Transactional 注解确保数据一致性。

完整示例

以下是一个完整的数据权限使用示例:

java
// 1. 实体类
@Data
@TableName("test")
public class Test {
    @TableId(type = IdType.ASSIGN_ID)
    private Long id;
    
    private String name;
    
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date createdDate;
    
    @TableField("org_id")
    private Long orgId;
    
    @TableField("created_by")
    private Long createdBy;
}

// 2. Controller
@RestController
@RequestMapping("/test")
public class TestController {
    
    @Autowired
    private TestService testService;
    
    @PostMapping("/add")
    @DataPermission(businessType = "org", modes = {ValidationMode.SINGLE}, idParams = "orgId")
    public Mono<ResultVo<Void>> add(@RequestBody @Valid TestAddDto dto) {
        testService.add(dto);
        return Mono.just(ResultVo.ok());
    }
    
    @PostMapping("/edit")
    @DataPermission(
        businessType = "org",
        modes = {ValidationMode.QUERY_VALIDATE, ValidationMode.SINGLE},
        idParams = "orgId",
        serviceClass = TestService.class,
        queryMethod = "getOrgIdById",
        queryParam = "id"
    )
    public Mono<ResultVo<Void>> edit(@RequestBody @Valid TestEditDto dto) {
        testService.edit(dto);
        return Mono.just(ResultVo.ok());
    }
}

// 3. Service实现
@Service
@Transactional
public class TestServiceImpl implements TestService {
    
    @Override
    public void add(TestAddDto dto) {
        Test test = new Test();
        test.setName(dto.getName());
        test.setOrgId(dto.getOrgId());
        test.setCreatedBy(UserHelper.getUserIdLong());
        test.setCreatedDate(new Date());
        testMapper.insert(test);
    }
    
    @Override
    public Long getOrgIdById(Long id) {
        Test test = testMapper.selectById(id);
        if (test == null) {
            throw new InvokeException(-1, "记录不存在");
        }
        return test.getOrgId();
    }
}

通过以上配置和实现,即可实现完整的数据权限控制功能。