🧐 定时任务遇到数据权限限制问题?这样优雅解决!
最近写需求时遇到 若依框架 的数据权限限制场景,大致如下:
@DataScope(deptAlias = "company", userAlias = "company")
public List<CsCompany> selectCompanyList(Company company) {
// 代码逻辑肯定不会给你看
}
这个 @DataScope
注解的作用是 限制数据范围,通过获取当前用户信息,在 SQL 后拼接部门权限条件,做到数据隔离。
❗ 问题出现了
场景:在写一个定时任务时,需要批量获取公司信息,运行时突然报错:
@Scheduled
public void scheduleTest() {
List<CsCompany> csCompanies = selectCompanyList(new CsCompany());
// 处理逻辑
}
🚨 报错信息:
获取用户信息异常
奇怪:查询逻辑明明没写获取用户信息,怎么就异常了?
🔍 真相揭秘
跟踪源码后发现,@DataScope
背后有个切面逻辑,会默认获取当前用户信息:
@Before("@annotation(controllerDataScope)")
public void doBefore(JoinPoint point, DataScope controllerDataScope) {
clearDataScope(point);
handleDataScope(point, controllerDataScope);
}
protected void handleDataScope(final JoinPoint joinPoint, DataScope controllerDataScope) {
// 获取当前用户信息
LoginUser loginUser = SecurityUtils.getLoginUser();
...
}
进一步看 getLoginUser()
方法源码:
public static LoginUser getLoginUser() {
try {
return (LoginUser) getAuthentication().getPrincipal();
} catch (Exception e) {
throw new ServiceException("获取用户信息异常", HttpStatus.UNAUTHORIZED);
}
}
原因总结:
原因 | 说明 |
---|---|
❗ 没有登录上下文 | 定时任务场景下没有用户登录态 |
❗ SecurityContext 为空 | SecurityUtils.getLoginUser() 获取失败 |
🛠️ 常见解决方案
方案 | 优点 | 缺点 |
---|---|---|
1️⃣ 模拟登录用户 | 手动设置用户信息,快速解决 | 侵入性强,易遗忘,维护成本高 |
2️⃣ 重写方法 | 写一个不带 @DataScope 的版本 | 方法重复,逻辑分散,后续不好维护 |
3️⃣ 动态控制 ✅ | 通过参数动态控制权限逻辑 | 灵活 ✅,优雅 ✅,推荐使用 ✅ |
🚀 推荐方案 —— 动态控制数据权限
设计目标:
外部接口:需要权限过滤
内部调用(如定时任务):可以跳过权限过滤
🎨 改造 @DataScope
注解
新增一个 needDataScope
参数,支持 SpEL 动态绑定
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DataScope {
String deptAlias() default "";
String userAlias() default "";
/**
* 是否需要进行数据权限限制,支持 SpEL 表达式
*/
String needDataScope() default "true";
}
✅ 使用示例
@DataScope(deptAlias = "company", userAlias = "company", needDataScope = "#needDataScope")
public List<CsCompany> selectCompanyList(Company company, boolean needDataScope) {
// 查询逻辑
}
说明:
名称 | 说明 |
---|---|
needDataScope | 是否需要权限限制,通过参数 #needDataScope 动态传入 |
🗂️ 切面逻辑改造
protected void handleDataScope(final JoinPoint joinPoint, DataScope controllerDataScope) {
if (needDataScope(joinPoint, controllerDataScope)) {
LoginUser loginUser = SecurityUtils.getLoginUser();
if (StringUtils.isNotNull(loginUser)) {
SysUser currentUser = loginUser.getUser();
if (StringUtils.isNotNull(currentUser) && !currentUser.isAdmin()) {
dataScopeFilter(joinPoint, currentUser, controllerDataScope.deptAlias(),
controllerDataScope.userAlias(), loginUser);
}
}
}
}
✨ 动态参数解析
private boolean needDataScope(JoinPoint point, DataScope controllerDataScope) {
// 获取方法的参数名称
MethodSignature signature = (MethodSignature) point.getSignature();
String[] paramNames = signature.getParameterNames();
// 获取目标方法的参数值
Object[] args = point.getArgs();
// 创建 Spring 表达式上下文,将参数名和对应的值绑定进去
StandardEvaluationContext context = new StandardEvaluationContext();
for (int i = 0; i < paramNames.length; i++) {
context.setVariable(paramNames[i], args[i]);
}
// 解析 needDataScope 参数(SpEL 表达式),返回解析结果(Boolean 类型)
return parser.parseExpression(controllerDataScope.needDataScope())
.getValue(context, Boolean.class);
}
🔎 什么是 SpEL?
名称 | 说明 |
---|---|
SpEL | Spring Expression Language,Spring 表达式语言 |
✅ | 支持 #参数名 动态绑定参数 |
✅ | 支持逻辑判断、三目表达式、方法调用等 |
常见场景:
@Cacheable(key = "#id")
@PreAuthorize("hasRole('ADMIN')")
📌 优势总结
优势 | 说明 |
---|---|
✅ 灵活 | 根据实际需求动态决定是否校验权限逻辑 |
✅ 复用性强 | 只维护一个方法版本,无需写多个重载函数 |
✅ 低侵入性 | 不破坏原有业务逻辑,对已有接口完全兼容 |
🔚 结语
遇到问题,不慌不忙,先理解框架原理,再找优雅方案。
这次通过:
- ✅ 注解扩展
- ✅ 切面增强
- ✅ SpEL 参数绑定
实现了优雅又实用的权限动态控制。
📌 觉得有帮助?点个 👍 收藏一下,分享给你身边的程序员吧 👏