主题切换
数据权限使用说明
概述
数据权限是一种基于用户身份和权限范围来控制数据访问的机制。通过 @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");
注意事项
业务类型一致性: 登录时设置的业务类型(如"org")必须与
@DataPermission
注解中的businessType
保持一致。查询方法要求: 使用
QUERY_VALIDATE
模式时,查询方法必须返回需要验证的数据ID,方法签名要与注解参数匹配。个人数据权限: 当数据权限为
PERSONAL
时,除了验证业务权限外,还需要验证数据的创建人是否为当前用户。异常处理: 权限验证失败时会抛出
InvokeException
,需要在全局异常处理器中统一处理。性能考虑: 对于大量数据的查询,建议在数据库层面添加相应的索引以提高查询性能。
事务处理: 涉及多表操作的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();
}
}
通过以上配置和实现,即可实现完整的数据权限控制功能。