本文目录导读:

- 基于MyBatis-Plus的插件实现(最常见)
- 基于ShardingSphere的联邦查询过滤
- 使用Spring Data JPA的Specification
- 基于Redis缓存的范围过滤
- Elasticsearch范围过滤
- MongoDB范围过滤
- 最佳实践建议
在Java分布式系统中,实现数据范围API的过滤通常涉及以下几种主流方案和技术选型:
基于MyBatis-Plus的插件实现(最常见)
自定义拦截器
@Component
@Intercepts({@Signature(
type = StatementHandler.class,
method = "prepare",
args = {Connection.class, Integer.class}
)})
public class RangeFilterInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
MetaObject metaObject = MetaObject.forObject(statementHandler,
SystemMetaObject.DEFAULT_OBJECT_FACTORY,
SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY,
new DefaultReflectorFactory());
// 获取原始SQL
String originalSql = statementHandler.getBoundSql().getSql();
// 动态拼接范围过滤条件
String filteredSql = addRangeFilter(originalSql);
// 设置修改后的SQL
metaObject.setValue("delegate.boundSql.sql", filteredSql);
return invocation.proceed();
}
private String addRangeFilter(String sql) {
// 从上下文获取当前用户的权限范围
UserContext userContext = UserContextHolder.get();
String rangeCondition = buildRangeCondition(userContext);
// 在WHERE条件后追加范围过滤
if (sql.toUpperCase().contains("WHERE")) {
return sql + " AND " + rangeCondition;
} else {
return sql + " WHERE " + rangeCondition;
}
}
}
注解驱动模式
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DataRange {
String fieldName() default "org_id";
Class<? extends RangeHandler> handler() default DefaultRangeHandler.class;
}
// 使用时
@Service
public class UserService {
@DataRange(fieldName = "dept_id", handler = DeptRangeHandler.class)
public List<User> queryUsers(QueryParam param) {
// 正常查询逻辑,拦截器自动追加范围过滤
}
}
基于ShardingSphere的联邦查询过滤
// 配置数据分片策略,自动实现范围路由
@Configuration
public class ShardingConfig {
@Bean
public ShardingRuleConfiguration shardingRuleConfig() {
ShardingRuleConfiguration config = new ShardingRuleConfiguration();
// 按组织ID范围分片
TableRuleConfiguration orderRule = new TableRuleConfiguration("t_order");
orderRule.setTableShardingStrategyConfig(
new StandardShardingStrategyConfiguration("org_id",
new OrgRangeShardingAlgorithm()));
config.getTableRuleConfigs().add(orderRule);
return config;
}
}
public class OrgRangeShardingAlgorithm
implements RangeShardingAlgorithm<Long> {
@Override
public Collection<String> doSharding(
Collection<String> availableTargetNames,
RangeShardingValue<Long> shardingValue) {
Range<Long> range = shardingValue.getValueRange();
// 根据范围计算实际需要查询的分片
return calculateTargetTables(range);
}
}
使用Spring Data JPA的Specification
@Service
public class UserDataService {
public Page<User> queryUsersWithRange(Pageable pageable) {
Specification<User> spec = (root, query, cb) -> {
List<Predicate> predicates = new ArrayList<>();
// 获取当前用户的数据权限范围
DataRange range = SecurityUtils.getCurrentUserDataRange();
// 如果是部门范围
if (range.getType().equals(DataRangeType.DEPT)) {
predicates.add(cb.equal(
root.get("departmentId"), range.getDeptId()));
}
// 如果是自定义范围
if (range.getType().equals(DataRangeType.CUSTOM)) {
predicates.add(root.get("orgId").in(range.getOrgIds()));
}
// 如果是所有数据,不添加任何条件
return cb.and(predicates.toArray(new Predicate[0]));
};
return userRepository.findAll(spec, pageable);
}
}
基于Redis缓存的范围过滤
@Component
public class RangeCacheFilter {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 构建数据权限缓存
public void buildUserDataRangeCache(Long userId) {
List<Long> accessibleOrgIds = getAccessibleOrgs(userId);
// 存储到Redis,设置过期时间
String key = "data:range:" + userId;
redisTemplate.opsForSet().add(key, accessibleOrgIds.toArray());
redisTemplate.expire(key, 1, TimeUnit.HOURS);
}
// 使用缓存进行过滤
public <T> List<T> filterByRange(List<T> dataList,
Function<T, Long> idExtractor, Long userId) {
Set<Object> accessibleIds = redisTemplate.opsForSet()
.members("data:range:" + userId);
return dataList.stream()
.filter(item -> accessibleIds.contains(idExtractor.apply(item)))
.collect(Collectors.toList());
}
}
Elasticsearch范围过滤
@Service
public class ESDocumentService {
@Autowired
private ElasticsearchRestTemplate esTemplate;
public SearchHits<Document> searchWithRangeFilter(
String keyword, Long orgId, Pageable pageable) {
NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
// 添加关键词搜索
if (StringUtils.hasText(keyword)) {
queryBuilder.withQuery(QueryBuilders.matchQuery("content", keyword));
}
// 添加组织范围过滤
queryBuilder.withFilter(QueryBuilders.termQuery("orgId", orgId));
// 或者范围查询
queryBuilder.withFilter(QueryBuilders.rangeQuery("createTime")
.gte("2024-01-01")
.lte("2024-12-31"));
queryBuilder.withPageable(pageable);
return esTemplate.search(queryBuilder.build(), Document.class);
}
}
MongoDB范围过滤
@Service
public class MongoDataService {
@Autowired
private MongoTemplate mongoTemplate;
public List<Product> getProductsWithRange(ProductQuery query) {
Criteria criteria = new Criteria();
// 价格范围过滤
if (query.getMinPrice() != null && query.getMaxPrice() != null) {
criteria.and("price").gte(query.getMinPrice())
.lte(query.getMaxPrice());
}
// 数据权限范围过滤
UserContext user = UserContextHolder.get();
if (user.getDataScope() == DataScope.DEPT) {
criteria.and("deptId").is(user.getDeptId());
} else if (user.getDataScope() == DataScope.ORG) {
criteria.and("orgId").in(user.getAccessibleOrgIds());
}
Query mongoQuery = new Query(criteria);
return mongoTemplate.find(mongoQuery, Product.class);
}
}
最佳实践建议
- 分层设计:范围过滤应在Service层统一处理,DAO层保持纯净
- 缓存策略:频繁使用的范围规则应缓存,避免重复计算
- 性能监控:对大规模数据的范围过滤要进行性能监控和优化
- 兼容性处理:考虑MySQL、Elasticsearch、MongoDB等异构数据源的统一过滤逻辑
- 熔断机制:当范围过滤导致查询性能下降时,应有降级策略
选择具体的实现方案时,需要根据:
- 数据量规模
- 实时性要求
- 系统复杂度
- 团队技术栈
来权衡选择最适合的方案。