基于 Spring Cloud Alibaba 的会员套餐资源配额管理系统实现
·
这篇博客将手把手带你实现一个生产级的资源配额管理系统。
基于 Spring Cloud Alibaba 的会员套餐资源配额管理系统实现
1. 前言:为什么要实现配额管理?
在 SaaS 系统或多租户系统中,我们经常需要根据用户购买的会员套餐(Package)来限制其可以创建的资源数量。例如:
- 基础版:最多创建 100 个商品,5 个 H5 模板。
- 专业版:最多创建 1000 个商品,50 个 H5 模板。
如果每个接口都写 if-else 校验,代码会变得极其臃肿且难以维护。本文将利用 Spring Cloud Alibaba (Nacos) + AOP + Redis 实现一个优雅、可配置的配额控制方案。
2. 技术架构图
- Nacos: 动态存储各等级会员的配额限制。
- Redis: 缓存当前已使用的数量(高性能计数),可选 Lua 脚本保证原子性。
- AOP: 拦截业务请求,解耦业务逻辑与配额校验。
- Strategy Pattern (策略模式): 适配不同微服务、不同表的统计逻辑。
3. 环境准备
- Spring Cloud Alibaba (Nacos)
- Spring Boot Starter AOP
- Spring Data Redis
4. 手把手实现步骤
第一步:Nacos 配置动态配额
在 Nacos 配置中心创建 quota-config.yaml,方便运营随时调整限额而无需重启服务。
quota:
packages:
- code: "BASIC"
limits:
PRODUCT: 100
TAG: 50
H5_TEMPLATE: 5
- code: "PRO"
limits:
PRODUCT: 1000
TAG: 500
H5_TEMPLATE: 50
第二步:定义核心组件
1. 资源类型枚举
public enum ResourceType {
PRODUCT("商品"),
TAG("标签"),
H5_TEMPLATE("H5模板"),
TRACE_TEMPLATE("溯源模板");
private final String desc;
ResourceType(String desc) { this.desc = desc; }
}
2. 自定义业务注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface QuotaLimit {
ResourceType value(); // 指定检查哪种资源
}
第三步:读取 Nacos 配置
利用 @ConfigurationProperties 自动映射配置。
@Component
@ConfigurationProperties(prefix = "quota")
@Data
@RefreshScope // 支持 Nacos 动态刷新
public class MemberQuotaConfig {
private List<PackageLimit> packages;
@Data
public static class PackageLimit {
private String code;
private Map<String, Integer> limits; // 资源名 -> 数量
}
public Integer getLimit(String packageCode, ResourceType type) {
return packages.stream()
.filter(p -> p.getCode().equals(packageCode))
.map(p -> p.getLimits().get(type.name()))
.findFirst()
.orElse(0); // 找不到则默认为0,禁止创建
}
}
第四步:实现统计策略(适配多模块)
由于商品和模板可能在不同微服务中,我们定义一个接口,由各业务模块自行实现计数逻辑。
public interface QuotaCounter {
ResourceType getResourceType();
int count(Long tenantId);
}
// 商品模块的实现示例
@Component
public class ProductCounter implements QuotaCounter {
@Autowired private ProductMapper productMapper;
@Override
public ResourceType getResourceType() { return ResourceType.PRODUCT; }
@Override
public int count(Long tenantId) {
return productMapper.selectCountByTenant(tenantId);
}
}
第五步:核心 AOP 切面实现
这是校验发生的地方。
@Aspect
@Component
@Slf4j
public class QuotaAspect {
@Autowired private MemberQuotaConfig quotaConfig;
@Autowired private List<QuotaCounter> counters;
@Around("@annotation(quotaLimit)")
public Object doAround(ProceedingJoinPoint joinPoint, QuotaLimit quotaLimit) throws Throwable {
// 1. 获取当前租户信息(从SecurityContext或Header获取)
Long tenantId = SecurityUtils.getTenantId();
String packageCode = SecurityUtils.getPackageCode();
// 2. 获取配置限额
Integer maxLimit = quotaConfig.getLimit(packageCode, quotaLimit.value());
// 3. 寻找对应的计数器
QuotaCounter counter = counters.stream()
.filter(c -> c.getResourceType() == quotaLimit.value())
.findFirst()
.orElseThrow(() -> new RuntimeException("系统未配置该资源计数器"));
// 4. 统计当前数量(建议此处引入Redis优化,见下文)
int currentCount = counter.count(tenantId);
// 5. 校验
if (currentCount >= maxLimit) {
log.warn("租户 {} 创建 {} 失败,超出限额 {}", tenantId, quotaLimit.value(), maxLimit);
throw new BusinessException("您的套餐配额已用尽,请联系客服升级套餐");
}
return joinPoint.proceed();
}
}
5. 进阶:性能与并发优化
在真实高并发环境下,直接 COUNT(*) 数据库性能较差,且无法绝对防止并发超限。
优化方案:Redis Lua 脚本
在切面中使用 Redis 维护计数器,利用 Lua 脚本保证“校验+自增”的原子性。
-- Lua 脚本:check_and_increment.lua
local current = redis.call('get', KEYS[1])
if current and tonumber(current) >= tonumber(ARGV[1]) then
return -1 -- 返回-1表示超限
else
return redis.call('incr', KEYS[1])
end
Java 调用代码:
public boolean tryAcquire(String redisKey, int limit) {
DefaultRedisScript<Long> script = new DefaultRedisScript<>(luaScript, Long.class);
Long result = redisTemplate.execute(script, Collections.singletonList(redisKey), String.valueOf(limit));
return result != -1;
}
6. 实际业务使用
现在,在任何需要限制数量的 Service 方法上,只需要一行注解:
@Service
public class ProductServiceImpl implements ProductService {
@Override
@QuotaLimit(ResourceType.PRODUCT) // 自动校验商品配额
@Transactional
public void addProduct(ProductDTO dto) {
// 业务逻辑
productMapper.insert(product);
}
}
@Service
public class TemplateServiceImpl implements TemplateService {
@Override
@QuotaLimit(ResourceType.H5_TEMPLATE) // 自动校验H5模板配额
public void createH5(H5DTO dto) {
// 业务逻辑
}
}
7. 总结
通过这套方案,我们实现了:
- 高度解耦:业务代码完全不需要关心配额校验逻辑。
- 动态配置:通过 Nacos 修改配置后,全网实时生效。
- 易于扩展:增加一种受限资源只需要增加一个枚举项和一个
Counter实现类。 - 性能可靠:引入 Redis 后,配额校验不再是系统瓶颈。
希望这篇教程能帮到你!如果有分布式事务相关的疑问,欢迎在评论区留言交流。
注:本文代码为核心逻辑演示,实际项目中请根据具体的权限框架(如 Spring Security 或 Shiro)调整获取用户信息的逻辑。
更多推荐




所有评论(0)