ForgeAdmin实战:开源项目分布式幂等组件 v2.0 升级

我在开源项目重构了分布式幂等组件:支持三种策略、Token防重放、结果缓存

为什么要重构幂等组件?

在企业级开发中,幂等性是保障数据一致性必不可少的能力。之前我在 Forge Admin 开源项目中实现了一个基础版本的幂等组件,但随着使用场景越来越多,发现了一些问题:

问题 影响
无结果缓存 重复请求只能拒绝,用户体验差
缺少Token机制 无法防范CSRF和恶意重放攻击
没有监控统计 无法评估幂等效果和性能
锁实现简单 仅使用SETNX,长时间业务可能锁过期
策略单一 只支持拒绝,无法满足不同场景需求

因此我参考了业内主流开源项目的设计思想,对幂等组件进行了全面重构,推出了 v2.0 版本。

重构后的整体架构

重构后的架构更加清晰,分层职责明确:

sql 复制代码
┌─────────────────────────────────────────────────────────┐
│                Idempotent Framework v2.0                 │
├─────────────────────────────────────────────────────────┤
│                                                           │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐  │
│  │  @Idempotent │  │  Idempotent │  │   Context   │  │
│  │  Annotation │  │    Aspect    │  │   Manager   │  │
│  └──────────────┘  └──────────────┘  └──────────────┘  │
│                                                           │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐  │
│  │   Token      │  │   Result     │  │    Lock      │  │
│  │   Service    │  │    Cache     │  │   Manager    │  │
│  └──────────────┘  └──────────────┘  └──────────────┘  │
│                                                           │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐  │
│  │   Monitor    │  │   Storage   │  │   Strategy   │  │
│  │   Metrics    │  │   (Redis)    │  │   Provider   │  │
│  └──────────────┘  └──────────────┘  └──────────────┘  │
│                                                           │
└─────────────────────────────────────────────────────────┘

核心特性

1. 支持三种幂等策略

针对不同业务场景,提供三种不同的幂等处理策略:

策略 处理逻辑 适用场景
STRICT 严格拒绝重复请求 订单创建、支付处理等关键操作
RETURN_CACHE 返回上次缓存结果 查询类操作、可重复读取
TOKEN_REQUIRED Token验证优先 前端防重复提交、防重放攻击

2. 幂等结果缓存

当重复请求发生时,可以返回上次成功执行的缓存结果,无需重复执行业务逻辑:

  • 提升用户体验(用户无需重新提交)
  • 减少系统负载(避免重复计算)
  • 缓存有效期可配置

3. Token机制防重放

提供完整的Token生成、验证、消费机制:

  • Token单次消费,使用后即失效
  • Token绑定用户ID,防止跨用户盗用
  • 短TTL,减少泄露风险
  • 提供REST API供前端调用

4. Redisson分布式锁

基于Redisson实现增强型分布式锁:

  • 支持看门狗自动续期,长时间业务执行不会提前过期
  • 可配置锁等待时间和租约时间
  • 公平锁支持,避免饥饿

5. Prometheus监控集成

内置完整的监控指标:

指标 类型 说明
idempotent.requests.total Counter 请求总数
idempotent.requests.success Counter 成功次数
idempotent.requests.duplicate Counter 重复次数
idempotent.cache.returned Counter 缓存返回次数
idempotent.cache.hit.rate Gauge 缓存命中率
idempotent.execution.time Timer 执行耗时分布

快速开始

1. 添加依赖

xml 复制代码
<dependency>
    <groupId>com.mdframe.forge</groupId>
    <artifactId>forge-starter-idempotent</artifactId>
</dependency>

2. 配置文件

yaml 复制代码
forge:
  idempotent:
    enabled: true
    prefix: "idempotent:"
    expire: 600
    message: "请勿重复提交"
    
    cache:
      enabled: true
      expire: 3600
      
    token:
      enabled: true
      expire: 300
      header: "X-Idempotent-Token"
      
    lock:
      enabled: true
      wait-time: 3000
      lease-time: 5000

使用示例

示例1:严格模式(订单创建)

less 复制代码
@PostMapping("/order/create")
@Idempotent(
    strategy = IdempotentStrategy.STRICT,
    prefix = "order:",
    key = "#orderRequest.orderId",
    message = "订单正在处理中,请勿重复提交"
)
public RespInfo<Order> createOrder(@RequestBody OrderRequest orderRequest) {
    return RespInfo.success(orderService.create(orderRequest));
}

行为:第一次请求正常执行,重复请求直接抛出异常拒绝。


示例2:缓存模式(订单查询)

less 复制代码
@GetMapping("/order/{orderId}")
@Idempotent(
    strategy = IdempotentStrategy.RETURN_CACHE,
    prefix = "order:query:",
    key = "#orderId",
    cacheExpire = 300
)
public RespInfo<Order> queryOrder(@PathVariable String orderId) {
    return RespInfo.success(orderService.getById(orderId));
}

行为:第一次请求执行查询并缓存结果,重复请求直接返回缓存结果,不访问数据库。


示例3:Token模式(支付处理)

第一步:前端获取Token

ini 复制代码
const res = await axios.post('/api/idempotent/token/generate', {
  prefix: 'payment'
});
const token = res.data.data.token;

第二步:携带Token请求

csharp 复制代码
await axios.post('/api/payment/process', paymentData, {
  headers: {
    'X-Idempotent-Token': token
  }
});

第三步:后端验证

less 复制代码
@PostMapping("/payment/process")
@Idempotent(
    strategy = IdempotentStrategy.TOKEN_REQUIRED,
    prefix = "payment:",
    key = "#paymentRequest.paymentId"
)
public RespInfo<PaymentResult> processPayment(@RequestBody PaymentRequest request) {
    return RespInfo.success(paymentService.process(request));
}

Token API 一览

获取Token

json 复制代码
POST /api/idempotent/token/generate
{
  "prefix": "order"  // 可选
}
→
{
  "code": 200,
  "data": {
    "token": "abc123...",
    "expireSeconds": 300,
    "createTime": 1678923456789
  }
}

批量获取Token

bash 复制代码
POST /api/idempotent/token/batch-generate
{
  "count": 10,
  "prefix": "order"
}

验证Token

bash 复制代码
POST /api/idempotent/token/validate
{
  "token": "abc123..."
}
→
{
  "code": 200,
  "data": true
}

Redis 存储结构

Key类型 Key格式 TTL
幂等标记 idempotent:{prefix}:{businessKey} expire秒
结果缓存 idempotent:cache:{prefix}:{businessKey} cacheExpire秒
Token存储 idempotent:token:{prefix}:{tokenValue} tokenExpire秒
分布式锁 idempotent:lock:{prefix}:{businessKey} Redisson管理

设计思路总结

为什么选择这三种策略?

在实际业务中,不同场景对幂等的需求是不一样的:

  • 关键写操作 (如创建订单):必须严格防止重复,所以用 STRICT 模式
  • 读多写少的查询 :重复查询结果一样,用 RETURN_CACHE 可以提升性能
  • 前端表单提交 :用户可能重复点击,用 TOKEN_REQUIRED 配合Token可以有效防止重复提交

为什么选择Redisson而不是自己实现?

Redisson的分布式锁已经经过生产环境验证,特别是看门狗自动续期这个特性,自己实现很容易出问题,站在巨人肩膀上更好。

结果缓存会不会有一致性问题?

是的,如果业务数据更新了,缓存还没过期,会返回旧数据。所以建议:

  1. 缓存过期时间设置合理(一般几分钟)
  2. 更新数据时可以提供手动清理缓存的接口
  3. 不建议对一致性要求非常高的场景使用缓存模式

踩坑记录

坑1:SpEL表达式解析没有异常处理

一开始直接解析,不捕获异常,如果用户写错了表达式,整个接口直接500。现在改成:解析失败回退到参数哈希方案,保证接口可用。

坑2:参数名获取方式兼容性问题

一开始用 StandardReflectionParameterNameDiscoverer,如果编译没有开 -parameters 参数,就获取不到参数名,SpEL中的 #参数名 就失效了。现在改成 DefaultParameterNameDiscoverer,它会自动尝试多种方式,兼容性更好。

坑3:全局开关动态不生效

一开始只在自动配置上加了 @ConditionalOnProperty,如果运行时通过配置中心关闭,切面还是会执行。现在在切面切入点再次检查配置开关,保证即时生效。

项目地址

完整代码已经开源在 Forge Admin:

👉 gitee.com/ForgeLab/fo...

欢迎 Star 关注,如果你对幂等组件有更好的想法,欢迎提 Issue 交流。

总结

这次重构让幂等组件从"能用"变成"好用",主要提升:

功能更完整 :三种策略、Token防重放、结果缓存✅ 可靠性更高 :Redisson分布式锁 + 看门狗续期✅ 可观测性 :Prometheus监控,一切指标可查✅ 体验更好:重复请求返回缓存,用户无需等待

如果你也在找一个开箱即用的分布式幂等解决方案,不妨试试这个组件。


#Java #SpringBoot #分布式 #幂等 #开源 #ForgeAdmin #架构重构

相关推荐
小江的记录本2 小时前
【Swagger】Swagger系统性知识体系全方位结构化总结
java·前端·后端·python·mysql·spring·docker
原燊炜2 小时前
Struts2_拦截器_登录拦截
后端
Eagle_Clark2 小时前
从0到1 Vibe Coding 一个项目的真实感受
前端·人工智能·后端
CodeSheep2 小时前
兄弟被降职降薪,被猎头挖新公司时承诺45万年薪,结果签合同才发现这45万里有“部分”是股权激励,还得2年后才能兑现
前端·后端·程序员
默海笑2 小时前
JavaWeb Tomcat基础
后端
街霸星星2 小时前
Spring Boot 3 整合 Maven 多模块:分层架构设计与 ArchUnit 架构守护
后端
SimonKing2 小时前
白嫖党狂喜!魔塔社区每天2000次免费大模型调用,真香!
java·后端·程序员
杰克尼2 小时前
springCloud_day06
后端·spring·spring cloud
cch89182 小时前
ThinkPHP6.x全面升级:性能与功能双飞跃
开发语言·vue.js·后端·golang