变更名称: idempotent-refactor
变更类型: 功能重构
优先级: 高
影响范围: forge-starter-idempotent 模块(破坏性升级)
预计工作量: 3-5人日
在企业级应用中,分布式幂等性是保障数据一致性和系统可靠性的核心能力。Forge Admin框架作为企业级后台管理系统,需要处理大量关键业务操作(如订单创建、支付处理、库存扣减等),这些操作必须具备严格的幂等性保障,防止重复提交导致的业务异常。
现有幂等模块(forge-starter-idempotent)存在以下关键缺陷:
| 问题类别 | 具体问题 | 业务影响 |
|---|---|---|
| 功能缺失 | 无结果缓存能力,重复请求仅拒绝无法返回原结果 | 用户无法得知上次操作结果,体验差 |
| 安全不足 | 缺少Token机制,无法防范CSRF和恶意重放攻击 | 存在安全隐患,可被恶意攻击 |
| 监控缺失 | 无监控统计,无法评估幂等效果和性能 | 无法发现问题和优化 |
| 锁实现简单 | 仅使用SETNX,无看门狗续期,极端情况可能失效 | 并发场景可靠性不足 |
| 策略单一 | 仅支持拒绝重复,无多种幂等策略 | 灵活性不足,不同场景需求无法满足 |
| 存储单一 | 仅支持Redis,无降级方案 | Redis故障时系统不可用 |
参考主流开源项目(如美团Leaf、goku-framework、yue-library)的设计理念,构建企业级幂等解决方案:
现有实现:
├── IdempotentAutoConfiguration # 自动配置
├── IdempotentAspect # AOP切面拦截
├── @Idempotent # 注解(功能简单)
├── RedisIdempotentStorageService # Redis存储(仅SETNX)
├── DefaultIdempotentKeyGenerator # Key生成(SpEL支持)
└── IdempotentException # 异常定义
缺失组件:
❌ 结果缓存服务
❌ Token服务
❌ 分布式锁管理(Redisson)
❌ 幂等策略抽象
❌ 监控指标
❌ Token API
@Idempotent(
prefix = "order:", // 前缀
expire = 600, // 过期时间
key = "#orderId", // SpEL表达式
message = "请勿重复提交", // 提示消息
deleteKeyAfterSuccess = false
)
缺陷分析:
请求 → Aspect拦截 → 生成Key → SETNX尝试获取
↓
成功 → 执行业务 → 返回结果
↓
失败 → 抛异常(拒绝)
问题:
需求描述:当重复请求发生时,系统应能返回上次成功执行的业务结果,而非直接拒绝。
业务价值:
功能规格:
需求描述:提供Token生成、验证、消费机制,防范前端重复提交和恶意重放攻击。
业务价值:
功能规格:
需求描述:支持不同业务场景的幂等处理策略,而非单一的拒绝模式。
策略定义:
| 策略名称 | 处理逻辑 | 适用场景 |
|---|---|---|
| STRICT | 严格拒绝重复请求 | 关键操作,必须唯一执行 |
| RETURN_CACHE | 返回上次缓存结果 | 查询类、可重复操作 |
| TOKEN_REQUIRED | Token验证优先 | 防恶意重放、前端防重复 |
需求描述:使用Redisson实现增强型分布式锁,支持看门狗自动续期,提升并发可靠性。
业务价值:
功能规格:
需求描述:集成Prometheus/Micrometer,提供幂等效果的监控指标。
监控指标:
| 指标 | 类型 | 说明 |
|---|---|---|
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 | 执行耗时分布 |
idempotent.lock.acquire.time |
Timer | 锁获取耗时 |
┌─────────────────────────────────────────────────────────┐
│ Idempotent Framework v2.0 │
├─────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Annotation │ │ Aspect │ │ Context │ │
│ │ @Idempotent │ │ Intercept │ │ Manager │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Token │ │ Result │ │ Lock │ │
│ │ Service │ │ Cache │ │ Manager │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Monitor │ │ Storage │ │ Strategy │ │
│ │ Metrics │ │ (Redis) │ │ Provider │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
└─────────────────────────────────────────────────────────┘
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
/**
* 幂等策略
*/
IdempotentStrategy strategy() default IdempotentStrategy.RETURN_CACHE;
/**
* 幂等Key前缀
*/
String prefix() default "idempotent:";
/**
* 自定义幂等Key(支持SpEL)
*/
String key() default "";
/**
* 幂等有效期(秒)
*/
int expire() default 600;
/**
* 结果缓存有效期(秒)
*/
int cacheExpire() default 3600;
/**
* 重复请求提示消息
*/
String message() default "请勿重复提交";
/**
* 成功后删除幂等Key
*/
boolean deleteKeyAfterSuccess() default false;
/**
* 是否缓存执行结果
*/
boolean cacheResult() default true;
/**
* 是否开启监控
*/
boolean enableMetrics() default true;
}
public enum IdempotentStrategy {
/**
* 严格模式:拒绝重复请求
*/
STRICT,
/**
* 缓存模式:返回上次执行结果
*/
RETURN_CACHE,
/**
* Token模式:必须携带有效Token
*/
TOKEN_REQUIRED
}
# 幂等Key(标记请求是否已处理)
Key: idempotent:{prefix}:{businessKey}
Value: {
"requestId": "uuid",
"createTime": timestamp,
"status": "PROCESSING|SUCCESS|FAILED"
}
TTL: expire秒
# 结果缓存
Key: idempotent:cache:{prefix}:{businessKey}
Value: {
"requestId": "uuid",
"result": "序列化结果",
"executeTime": timestamp,
"status": "SUCCESS|FAILED"
}
TTL: cacheExpire秒
# Token存储
Key: idempotent:token:{prefix}:{tokenValue}
Value: {
"createTime": timestamp,
"userId": "用户ID",
"status": "UNUSED|CONSUMED"
}
TTL: tokenExpire秒
# 分布式锁(Redisson)
Key: idempotent:lock:{prefix}:{businessKey}
Value: Redisson内部管理(看门狗续期)
请求进入
↓
[1] 解析注解配置
↓
[2] 生成幂等Key(SpEL + Hash)
↓
[3] Token验证(如果策略=TOKEN_REQUIRED)
├─ 提取Token Header
├─ 验证Token存在性
├─ 验证Token用户归属
├─ 验证Token未消费
└─ 消费Token
↓
[4] 根据策略执行
├─ STRICT: tryAcquire → 执行 → 返回
├─ RETURN_CACHE: 检查缓存 → 有则返回 → 无则获取锁 → 执行 → 缓存结果
└─ TOKEN_REQUIRED: Token验证 → 执行上述逻辑
↓
[5] 执行业务逻辑
├─ 成功 → 缓存结果 → deleteKey判断 → 返回
└─ 失败 → 释放锁 → 抛异常
↓
[6] 记录监控指标
↓
请求结束
@RestController
@RequestMapping("/api/idempotent/token")
public class IdempotentTokenController {
/**
* 获取Token
*/
@PostMapping("/generate")
public RespInfo<TokenInfo> generateToken(@RequestParam(required = false) String prefix);
/**
* 批量获取Token
*/
@PostMapping("/batch-generate")
public RespInfo<List<TokenInfo>> batchGenerateToken(@RequestParam int count);
/**
* 验证Token(前端预检查)
*/
@PostMapping("/validate")
public RespInfo<Boolean> validateToken(@RequestParam String token);
}
| Key类型 | 变更类型 | 说明 |
|---|---|---|
idempotent:{prefix}:{key} |
修改 | Value结构增强,增加requestId、status |
idempotent:cache:{prefix}:{key} |
新增 | 存储幂等结果缓存 |
idempotent:token:{prefix}:{token} |
新增 | 存储Token元数据 |
idempotent:lock:{prefix}:{key} |
新增 | Redisson分布式锁 |
新增配置属性(forge.idempotent.*):
forge:
idempotent:
# 基础配置
enabled: true
prefix: "idempotent:"
expire: 600
message: "请勿重复提交"
# 结果缓存配置
cache:
enabled: true
expire: 3600
max-size: 10000
# Token配置
token:
enabled: true
expire: 300
header: "X-Idempotent-Token"
# 分布式锁配置
lock:
enabled: true
wait-time: 3000 # 等待时间(ms)
lease-time: 5000 # 租约时间(ms)
# 监控配置
metrics:
enabled: true
sample-rate: 100 # 采样率(%)
原注解:
@Idempotent(prefix="order:", expire=600, key="#orderId", message="...")
新注解:
@Idempotent(
strategy = IdempotentStrategy.RETURN_CACHE, // 新增
prefix = "order:",
expire = 600,
cacheExpire = 3600, // 新增
key = "#orderId",
message = "...",
cacheResult = true, // 新增
enableMetrics = true // 新增
)
兼容性影响:
原异常:
throw new IdempotentException("请勿重复提交");
新异常:
IdempotentException exception = new IdempotentException("请勿重复提交");
exception.setCachedResult(result); // 可携带缓存结果
exception.setStrategy(strategy); // 携带策略信息
兼容性影响:
POST /api/idempotent/token/generate
Request: { "prefix": "order" }
Response: {
"token": "abc123...",
"expireSeconds": 300,
"createTime": 1234567890
}
POST /api/idempotent/token/batch-generate
Request: { "count": 10, "prefix": "order" }
Response: [
{ "token": "abc1...", "expireSeconds": 300 },
{ "token": "abc2...", "expireSeconds": 300 },
...
]
POST /api/idempotent/token/validate
Request: { "token": "abc123..." }
Response: { "valid": true }
| 风险项 | 风险等级 | 影响分析 | 缓解措施 |
|---|---|---|---|
| Redis故障 | 高 | 幂等功能失效,系统可用性下降 | 增加降级策略,异常时跳过幂等 |
| Redisson依赖冲突 | 中 | 可能与现有Redis配置冲突 | 提供Redisson配置兼容方案 |
| 序列化兼容性 | 中 | 结果缓存序列化可能失败 | 提供Jackson配置扩展点 |
| 性能影响 | 中 | 监控和缓存可能增加延迟 | 提供采样率和缓存开关 |
| 看门狗续期失败 | 低 | 长时间业务锁可能失效 | 增加锁超时告警 |
| 风险项 | 风险等级 | 影响分析 | 缓解措施 |
|---|---|---|---|
| 缓存结果过期 | 中 | 过期后重复请求无法返回结果 | 合理配置cacheExpire |
| Token泄露 | 高 | Token被盗用可能绕过幂等 | Token绑定用户ID,短TTL |
| 并发冲突 | 中 | RETURN_CACHE模式可能并发冲突 | 增加短暂等待和二次检查 |
| 缓存一致性问题 | 中 | 业务结果更新后缓存未同步 | 提供手动清除缓存接口 |
| 风险项 | 风险等级 | 影响分析 | 缓解措施 |
|---|---|---|---|
| 注解API变更 | 低 | 新增属性有默认值,现有代码无需修改 | 提供迁移文档 |
| Redis Key结构变更 | 中 | 新旧Key格式不同,需清理旧数据 | 提供清理脚本 |
| 异常类扩展 | 低 | 异常新增属性,不影响现有catch | 提供异常处理文档 |
| 配置路径变更 | 中 | 配置从idempotent.改为forge.idempotent. | 提供配置迁移指南 |
测试范围:
测试用例清单:
| 测试类 | 核心测试方法 | 验证点 |
|---|---|---|
DefaultIdempotentKeyGeneratorTest |
testSpelKeyGeneration |
SpEL解析正确性 |
RedisTokenServiceTest |
testTokenGeneration |
Token生成唯一性 |
RedisTokenServiceTest |
testTokenValidation |
Token验证逻辑 |
RedisTokenServiceTest |
testTokenConsumption |
Token消费状态 |
RedisResultCacheServiceTest |
testCacheResult |
结果缓存读写 |
RedisResultCacheServiceTest |
testCacheExpiry |
缓存过期处理 |
RedissonLockManagerTest |
testLockAcquire |
锁获取成功 |
RedissonLockManagerTest |
testLockWatchdog |
看门狗续期 |
StrictStrategyHandlerTest |
testStrictRejectDuplicate |
严格拒绝重复 |
ReturnCacheStrategyHandlerTest |
testReturnCachedResult |
返回缓存结果 |
TokenRequiredStrategyHandlerTest |
testTokenValidation |
Token验证流程 |
测试场景:
端到端幂等流程测试
@Idempotent的Controller方法Token流程测试
并发场景测试
异常场景测试
测试工具:
测试指标:
| 指标 | 目标值 | 测试方法 |
|---|---|---|
| 单次幂等检查耗时 | < 5ms | JMH Benchmark |
| 缓存命中率 | > 70% | 模拟重复请求 |
| 锁获取耗时 | < 10ms | 并发锁竞争测试 |
| Token生成耗时 | < 2ms | 批量生成测试 |
| 内存占用 | < 50MB | 结果缓存压测 |
测试范围:
| 任务ID | 任务描述 | 预估工时 | 优先级 |
|---|---|---|---|
| T1 | 新增注解属性和枚举定义 | 0.5人日 | P0 |
| T2 | 实现Token服务和API | 1人日 | P0 |
| T3 | 实现结果缓存服务 | 1人日 | P0 |
| T4 | 集成Redisson分布式锁 | 0.5人日 | P0 |
| T5 | 实现幂等策略处理器 | 1人日 | P0 |
| T6 | 重构IdempotentAspect切面 | 1人日 | P0 |
| T7 | 集成Prometheus监控 | 0.5人日 | P1 |
| T8 | 编写单元测试 | 1人日 | P1 |
| T9 | 编写集成测试 | 1人日 | P1 |
| T10 | 性能测试和优化 | 0.5人日 | P2 |
| T11 | 文档编写(使用指南) | 0.5人日 | P2 |
总工时: 7人日
建议团队: 1-2人
Phase 1:核心功能实现(3人日)
Phase 2:测试与监控(2人日)
Phase 3:优化与文档(2人日)
| 问题ID | 问题描述 | 影响范围 | 建议方案 |
|---|---|---|---|
| Q1 | Redisson版本选择:3.24.3还是最新版? | Maven依赖 | 使用3.24.3(稳定版) |
| Q2 | 结果序列化:Jackson还是FastJson? | 性能和兼容性 | 使用Jackson(Spring Boot默认) |
| Q3 | 监控采样率:100%还是可配置? | 性能影响 | 默认100%,可配置降低 |
| Q4 | Token Header名称是否固定? | 前端集成 | 固定为X-Idempotent-Token,可配置 |
| 问题ID | 问题描述 | 影响范围 | 建议方案 |
|---|---|---|---|
| Q5 | 缓存结果是否包含异常信息? | 用户体验 | 仅缓存成功结果,失败不缓存 |
| Q6 | Token是否需要绑定具体业务方法? | 精准控制 | 仅绑定前缀,不绑定方法 |
| Q7 | 并发冲突时是否自动重试? | 用户体验 | 不重试,直接抛异常 |
| Q8 | 是否提供手动清除缓存接口? | 灵活性 | 提供管理接口清除缓存 |
| 验收项 | 验收标准 | 验证方法 |
|---|---|---|
| 幂等结果缓存 | 重复请求返回上次结果 | 单元测试 + 集成测试 |
| Token机制 | Token验证和消费正常 | 单元测试 + API测试 |
| 多策略支持 | STRICT/RETURN_CACHE/TOKEN_REQUIRED均正常 | 策略测试 |
| Redisson锁 | 看门狗续期正常,锁释放可靠 | 锁测试 + 并发测试 |
| 监控指标 | Prometheus指标正常采集 | 监控端点验证 |
| 验收项 | 验收标准 | 验证方法 |
|---|---|---|
| 幂等检查耗时 | < 5ms(P95) | JMH Benchmark |
| 锁获取耗时 | < 10ms(P95) | 并发测试 |
| 缓存命中率 | > 70%(重复场景) | 监控指标 |
| 内存占用 | < 50MB(10000缓存) | 堆内存监控 |
| 集收项 | 验收标准 | 验证方法 |
|---|---|---|
| 现有注解兼容 | 现有@Idempotent无需修改即可运行 |
兼容性测试 |
| 配置兼容 | 新旧配置路径均支持 | 配置测试 |
| 异常兼容 | 现有异常处理逻辑正常 | 异常测试 |
| 项目名称 | 参考内容 | GitHub |
|---|---|---|
| goku-framework | 幂等框架设计理念 | https://github.com/goku-framework |
| 美团Leaf | Token机制设计 | https://github.com/Meituan-Dianping/Leaf |
| yue-library | 模块化架构设计 | https://github.com/yl-yue/yue-library |
| Redisson | 分布式锁实现 | https://github.com/redisson/redisson |
forge:
idempotent:
enabled: true
prefix: "idempotent:"
expire: 600
message: "请勿重复提交"
cache:
enabled: true
expire: 3600
max-size: 10000
token:
enabled: true
expire: 300
header: "X-Idempotent-Token"
lock:
enabled: true
wait-time: 3000
lease-time: 5000
metrics:
enabled: true
sample-rate: 100
示例1:订单创建(严格幂等)
@PostMapping("/order/create")
@Idempotent(
strategy = IdempotentStrategy.STRICT,
prefix = "order:",
key = "#orderRequest.orderId",
expire = 600,
message = "订单正在处理中,请勿重复提交"
)
public RespInfo<Order> createOrder(@RequestBody OrderRequest orderRequest) {
return RespInfo.success(orderService.create(orderRequest));
}
示例2:订单查询(缓存幂等)
@GetMapping("/order/{orderId}")
@Idempotent(
strategy = IdempotentStrategy.RETURN_CACHE,
prefix = "order:query:",
key = "#orderId",
expire = 60,
cacheExpire = 300,
cacheResult = true
)
public RespInfo<Order> queryOrder(@PathVariable String orderId) {
return RespInfo.success(orderService.getById(orderId));
}
示例3:支付处理(Token幂等)
@PostMapping("/payment/process")
@Idempotent(
strategy = IdempotentStrategy.TOKEN_REQUIRED,
prefix = "payment:",
key = "#paymentRequest.paymentId",
expire = 1200
)
public RespInfo<PaymentResult> processPayment(@RequestBody PaymentRequest paymentRequest) {
return RespInfo.success(paymentService.process(paymentRequest));
}
// 前端调用流程:
// 1. POST /api/idempotent/token/generate → 获取Token
// 2. POST /payment/process,Header携带 X-Idempotent-Token: xxx
文档版本: v1.0
编写日期: 2026-04-06
编写人: AI Assistant
审核状态: 待用户审核