是不是后端开发的程序员都觉的后端程序很简单,特别是做java的。的确,现在java后端已经是相当成熟了,各种开源产品数不胜数。我接触过很多同事都有此感受。不过在实际开发工作中还是会难免遇到些不容易解决的问题。本专栏主要以问答的形式梳理了作者实际工作中遇到的一些问题方法供各位感兴趣的读者参考学习。
本文章列举了1个知识点的问答梳理。
1,如何通过AOP优雅简单的设计一个系统数据权限功能
基于Java AOP的优雅简单数据权限设计方案
设计原则
非侵入式:尽量减少对业务代码的影响
灵活可配置:支持多种数据权限规则
易于扩展:方便添加新的权限控制方式
性能高效:减少对系统性能的影响
核心实现方案
1. 数据权限注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DataPermission {
/**
* 数据权限类型
*/
DataPermissionType type() default DataPermissionType.USER;
/**
* 权限字段名
*/
String field() default "create_user_id";
/**
* 表别名(用于JOIN查询)
*/
String tableAlias() default "";
}
2. 数据权限类型枚举
public enum DataPermissionType {
/**
* 只能查看自己的数据
*/
USER,
/**
* 查看部门数据
*/
DEPARTMENT,
/**
* 查看部门及子部门数据
*/
DEPARTMENT_AND_CHILD,
/**
* 查看全部数据
*/
ALL,
/**
* 自定义数据权限
*/
CUSTOM
}
3. 数据权限上下文
public class DataPermissionContext {
private static final ThreadLocal<UserPermission> context = new ThreadLocal<>();
public static void setPermission(UserPermission permission) {
context.set(permission);
}
public static UserPermission getPermission() {
return context.get();
}
public static void clear() {
context.remove();
}
}
public class UserPermission {
private Long userId;
private Long deptId;
private List<Long> roleIds;
private Set<String> permissions;
// 构造方法、getter和setter
}
4. AOP拦截器实现数据权限控制
@Aspect
@Component
public class DataPermissionAspect {
@Autowired
private DeptService deptService;
@Around("@annotation(dataPermission)")
public Object around(ProceedingJoinPoint point, DataPermission dataPermission) throws Throwable {
try {
// 获取当前用户权限
UserPermission userPermission = DataPermissionContext.getPermission();
if (userPermission == null) {
return point.proceed();
}
// 构建数据权限SQL
String dataFilterSql = buildDataFilterSql(dataPermission, userPermission);
// 设置数据权限SQL到当前线程(供MyBatis拦截器使用)
DataFilterContext.setFilter(dataFilterSql);
return point.proceed();
} finally {
DataFilterContext.clear();
}
}
private String buildDataFilterSql(DataPermission dataPermission, UserPermission userPermission) {
switch (dataPermission.type()) {
case USER:
return buildUserFilter(dataPermission, userPermission.getUserId());
case DEPARTMENT:
return buildDeptFilter(dataPermission, userPermission.getDeptId(), false);
case DEPARTMENT_AND_CHILD:
return buildDeptFilter(dataPermission, userPermission.getDeptId(), true);
case ALL:
return "";
case CUSTOM:
return buildCustomFilter(dataPermission, userPermission);
default:
return "";
}
}
private String buildUserFilter(DataPermission dataPermission, Long userId) {
String field = getFieldWithAlias(dataPermission);
return field + " = " + userId;
}
private String buildDeptFilter(DataPermission dataPermission, Long deptId, boolean includeChildren) {
String field = getFieldWithAlias(dataPermission);
if (includeChildren) {
List<Long> deptIds = deptService.getChildDeptIds(deptId);
return field + " IN (" + StringUtils.join(deptIds, ",") + ")";
} else {
return field + " = " + deptId;
}
}
private String buildCustomFilter(DataPermission dataPermission, UserPermission userPermission) {
// 自定义权限逻辑,可根据实际需求扩展
return "";
}
private String getFieldWithAlias(DataPermission dataPermission) {
String alias = StringUtils.isNotBlank(dataPermission.tableAlias()) ?
dataPermission.tableAlias() + "." : "";
return alias + dataPermission.field();
}
}
5. MyBatis拦截器实现SQL改写,这是本方法的重点难点之一,也是技巧所在之处
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
@Component
public class DataPermissionInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
// 获取数据权限过滤条件
String dataFilter = DataFilterContext.getFilter();
if (StringUtils.isBlank(dataFilter)) {
return invocation.proceed();
}
// 获取原始SQL
BoundSql boundSql = (BoundSql) metaObject.getValue("delegate.boundSql");
String originalSql = boundSql.getSql();
// 判断是否需要添加数据权限
if (shouldAddDataPermission(originalSql, mappedStatement.getId())) {
String newSql = addDataPermissionToSql(originalSql, dataFilter);
metaObject.setValue("delegate.boundSql.sql", newSql);
}
return invocation.proceed();
}
private boolean shouldAddDataPermission(String sql, String mappedStatementId) {
// 排除不需要数据权限的方法
if (mappedStatementId.contains("ByDataPermission")) {
return true;
}
// 根据实际需求判断是否需要添加数据权限
return true;
}
private String addDataPermissionToSql(String originalSql, String dataFilter) {
String upperCaseSql = originalSql.toUpperCase();
if (upperCaseSql.contains(" WHERE ")) {
int whereIndex = upperCaseSql.indexOf(" WHERE ");
String beforeWhere = originalSql.substring(0, whereIndex + 7);
String afterWhere = originalSql.substring(whereIndex + 7);
return beforeWhere + " (" + dataFilter + ") AND " + afterWhere;
} else {
return originalSql + " WHERE " + dataFilter;
}
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}
6. 数据权限上下文管理
public class DataFilterContext {
private static final ThreadLocal<String> context = new ThreadLocal<>();
public static void setFilter(String filter) {
context.set(filter);
}
public static String getFilter() {
return context.get();
}
public static void clear() {
context.remove();
}
}
7.使用示例。整合了 action ,service及配置文件代码.
@RestController
@RequestMapping("/api/users")
public class UserController {
@GetMapping
@DataPermission(type = DataPermissionType.DEPARTMENT_AND_CHILD, field = "dept_id")
public List<User> listUsers() {
return userService.listUsers();
}
}
@Service
public class UserServiceImpl implements UserService {
@Override
@DataPermission(type = DataPermissionType.USER, field = "create_user_id")
public List<User> listMyUsers() {
return userMapper.selectList(null);
}
}
public class CustomDataPermissionProvider {
public String getCustomFilter() {
// 实现自定义数据权限逻辑
UserPermission permission = DataPermissionContext.getPermission();
if (permission.getPermissions().contains("admin")) {
return ""; // 管理员可以看到所有数据
} else {
return "dept_id IN (SELECT dept_id FROM user_dept WHERE user_id = " + permission.getUserId() + ")";
}
}
}
# application.yml
data-permission:
enabled: true
ignore-tables: sys_log, sys_config # 不需要数据权限的表
ignore-methods: com.example.mapper.UserMapper.selectById # 不需要数据权限的方法
该方案的优点
非侵入式:通过注解和AOP实现,对业务代码影响小
灵活配置:支持多种数据权限类型,可自定义扩展
易于维护:权限逻辑集中管理,便于维护和修改
性能优化:通过SQL改写实现,减少内存中的数据过滤
可扩展建议
缓存优化:对部门层级等不常变的数据进行缓存
动态数据源:支持多租户场景下的数据隔离
权限继承:支持角色继承和权限组合
可视化配置:提供界面化配置数据权限规则
欢迎各位读者及同行举出该方案的不足之处,谢谢!!!喜欢该文章的请点个关注,感谢!!!!
959

被折叠的 条评论
为什么被折叠?



