# 分布式幂等模块使用指南
## 概述
Forge幂等模块提供了企业级的分布式幂等性解决方案,支持多种幂等策略、Token机制、结果缓存和分布式锁。
## 快速开始
### 1. 添加依赖
```xml
com.mdframe.forge
forge-starter-idempotent
```
### 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:严格模式(STRICT)
**适用场景**:订单创建、支付处理等关键操作
```java
@PostMapping("/order/create")
@Idempotent(
strategy = IdempotentStrategy.STRICT,
prefix = "order:",
key = "#orderRequest.orderId",
message = "订单正在处理中,请勿重复提交"
)
public RespInfo createOrder(@RequestBody OrderRequest orderRequest) {
return RespInfo.success(orderService.create(orderRequest));
}
```
**行为**:
- 第一次请求:执行业务逻辑
- 重复请求:抛出 `IdempotentException`
---
### 方式2:缓存模式(RETURN_CACHE)
**适用场景**:查询类操作、可重复操作
```java
@GetMapping("/order/{orderId}")
@Idempotent(
strategy = IdempotentStrategy.RETURN_CACHE,
prefix = "order:query:",
key = "#orderId",
cacheExpire = 300
)
public RespInfo queryOrder(@PathVariable String orderId) {
return RespInfo.success(orderService.getById(orderId));
}
```
**行为**:
- 第一次请求:执行业务逻辑并缓存结果
- 重复请求:直接返回缓存结果(不执行业务)
---
### 方式3:Token模式(TOKEN_REQUIRED)
**适用场景**:防止前端重复提交、防重放攻击
#### 步骤1:前端获取Token
```javascript
// 前端调用API获取Token
const response = await axios.post('/api/idempotent/token/generate', {
prefix: 'payment'
});
const token = response.data.data.token; // 例如: "abc123def456..."
```
#### 步骤2:提交请求携带Token
```javascript
// 提交支付请求时携带Token
await axios.post('/api/payment/process', paymentData, {
headers: {
'X-Idempotent-Token': token
}
});
```
#### 步骤3:后端验证
```java
@PostMapping("/payment/process")
@Idempotent(
strategy = IdempotentStrategy.TOKEN_REQUIRED,
prefix = "payment:",
key = "#paymentRequest.paymentId"
)
public RespInfo processPayment(@RequestBody PaymentRequest paymentRequest) {
return RespInfo.success(paymentService.process(paymentRequest));
}
```
**行为**:
- Token验证成功:执行业务逻辑
- Token验证失败:抛出 `TokenInvalidException`
---
## Token API
### 1. 获取Token
**请求**:
```http
POST /api/idempotent/token/generate
Content-Type: application/json
{
"prefix": "order" // 可选,默认为 global
}
```
**响应**:
```json
{
"code": 200,
"message": "success",
"data": {
"token": "abc123def456ghi789...",
"expireSeconds": 300,
"createTime": 1678923456789
}
}
```
### 2. 批量获取Token
**请求**:
```http
POST /api/idempotent/token/batch-generate
Content-Type: application/json
{
"count": 10,
"prefix": "order"
}
```
**响应**:
```json
{
"code": 200,
"message": "success",
"data": [
{ "token": "abc1...", "expireSeconds": 300 },
{ "token": "abc2...", "expireSeconds": 300 }
]
}
```
### 3. 验证Token
**请求**:
```http
POST /api/idempotent/token/validate
Content-Type: application/json
{
"token": "abc123def456..."
}
```
**响应**:
```json
{
"code": 200,
"message": "success",
"data": true
}
```
---
## 注解属性说明
### @Idempotent 注解
| 属性 | 类型 | 默认值 | 说明 |
|-----|------|-------|------|
| `strategy` | IdempotentStrategy | RETURN_CACHE | 幂等策略 |
| `prefix` | String | "idempotent:" | 幂等Key前缀 |
| `key` | String | "" | 自定义Key(支持SpEL) |
| `expire` | int | 600 | 幂等有效期(秒) |
| `cacheExpire` | int | 3600 | 缓存有效期(秒) |
| `message` | String | "请勿重复提交" | 提示消息 |
| `deleteKeyAfterSuccess` | boolean | false | 成功后删除Key |
| `cacheResult` | boolean | true | 是否缓存结果 |
| `enableMetrics` | boolean | true | 是否开启监控 |
### IdempotentStrategy 枚举
| 策略 | Code | 说明 |
|-----|------|------|
| STRICT | strict | 严格拒绝重复请求 |
| RETURN_CACHE | return_cache | 返回缓存结果 |
| TOKEN_REQUIRED | token_required | 必须携带有效Token |
---
## 监控指标
模块集成了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 | 执行耗时 |
---
## 异常处理
### IdempotentException
```java
@RestControllerAdvice
public class IdempotentExceptionHandler {
@ExceptionHandler(IdempotentException.class)
public RespInfo handleIdempotentException(IdempotentException e) {
return RespInfo.error(e.getMessage());
}
}
```
### TokenInvalidException
```java
@ExceptionHandler(TokenInvalidException.class)
public RespInfo handleTokenInvalidException(TokenInvalidException e) {
return RespInfo.error("Token无效或已过期");
}
```
---
## 最佳实践
### 1. Key设计原则
- 使用业务唯一标识(如订单ID、支付ID)
- 添加业务前缀区分不同场景
- 避免使用可变参数
```java
// ✅ 好的设计
@Idempotent(key = "#order.orderId")
// ❌ 不好的设计
@Idempotent(key = "#order.createTime") // 时间会变化
```
### 2. 过期时间设置
- `expire` 应该大于业务处理时间
- `cacheExpire` 建议 ≤ `expire`,避免缓存残留
- Token有效期建议 300秒(5分钟)
### 3. 策略选择
- **关键操作**:STRICT(订单创建、支付)
- **查询操作**:RETURN_CACHE(订单查询)
- **防重复提交**:TOKEN_REQUIRED(前端防抖)
### 4. Redis配置
确保Redis配置正确:
```yaml
spring:
redis:
host: localhost
port: 6379
database: 0
timeout: 3000
```
---
## 故障排查
### 问题1:Token验证失败
**原因**:
- Token过期
- Token已被消费
- Token不存在
**解决**:重新获取Token
### 问题2:缓存未命中
**原因**:
- 缓存过期时间过短
- Redis连接异常
**解决**:调整 `cacheExpire` 配置
### 问题3:锁获取失败
**原因**:
- 并发请求过多
- 锁等待时间过短
**解决**:调整 `lock.wait-time` 配置
---
## 技术栈
- Java 17
- Spring Boot 3
- Redisson 3.24.3
- Micrometer (Prometheus)
- Jackson
- Hutool
---
## 相关文档
- 需求规格:`code-copilot/changes/idempotent-refactor/spec.md`
- 实施总结:`code-copilot/changes/idempotent-refactor/implementation-summary.md`
- 项目规范:`AGENTS.md`