# 幂等性检查与修复报告

**任务编号：** 2.4
**执行时间：** 2026-04-05
**涉及服务：** order-service, inventory-service, logistics-service

---

## 一、检查结果

### 1.1 订单创建 `POST /api/orders/pull`
- **检查结果：** ❌ 无幂等性保护
- **问题描述：** 重复调用 `pullOrders` / `createOrders` 会重复创建订单
- **现有字段：** `platformOrderSn`（平台订单号）存在，但无唯一约束和去重逻辑

### 1.2 库存扣减 `POST /api/stock/deduct`
- **检查结果：** ❌ 无幂等性保护
- **问题描述：** 网络重试导致重复扣减，可能出现库存为负数
- **现有保护：** 乐观锁 `@Version` 仅防止并发覆盖，不防止重复请求

### 1.3 发货回传 `POST /api/logistics/callback/{carrier}`
- **检查结果：** ⚠️ 部分幂等（轨迹去重，无运单状态幂等）
- **问题描述：** 同一运单状态更新回调重复处理，可能导致状态回退
- **现有保护：** `logisticsTraceMapper.countExist()` 对轨迹有去重，但运单状态更新无幂等

### 1.4 消息队列消费者
- **检查结果：** ℹ️ 无 MQ 消费者
- **说明：** 系统使用 Spring Event（`StockChangedEventListener`）实现异步，非传统 MQ

---

## 二、修复方案

### 2.1 订单服务（order-service）

#### 新增文件
| 文件 | 说明 |
|------|------|
| `service/impl/OrderIdempotencyService.java` | Redis 幂等性工具类 |

#### 修改文件
| 文件 | 修改内容 |
|------|---------|
| `service/impl/OrderServiceImpl.java` | 注入 `OrderIdempotencyService`，修改 `createOrders()` 和 `shipOrder()` 添加幂等检查 |

#### 幂等键设计
```
订单创建：order:idempotent:{platform}:{shopId}:{platformOrderSn}
订单发货：order:idempotent:ship:{orderId}
```

#### 修复逻辑
1. **Redis 检查**：请求到达时先查 Redis 是否有处理记录
2. **DB 兜底**：数据库唯一约束 `uk_orders_platform_order_sn(platform_order_sn, platform, shop_id)` 防止并发插入
3. **幂等标记**：处理成功后写入 Redis，TTL=24h

---

### 2.2 库存服务（inventory-service）

#### 新增文件
| 文件 | 说明 |
|------|------|
| `service/impl/StockIdempotencyService.java` | Redis 幂等性工具类 |

#### 修改文件
| 文件 | 修改内容 |
|------|---------|
| `controller/StockFeignController.java` | 注入 `StockIdempotencyService`，修改 `deductStock()` / `lockStock()` / `unlockStock()` 添加幂等检查 |

#### 幂等键设计
```
扣减库存：stock:idempotent:{skuCode}:{warehouseId}:{relatedNo}
锁定库存：stock:idempotent:lock:{skuCode}:{warehouseId}:{relatedNo}
解锁库存：stock:idempotent:unlock:{skuCode}:{warehouseId}:{relatedNo}
```

#### 修复逻辑
1. **Redis 检查**：到达即查重
2. **幂等响应**：重复请求返回缓存结果而非错误，保证接口幂等
3. **DB 唯一索引**：`uk_stocks_sku_warehouse(sku_code, warehouse_id)` 防止库存记录重复

---

### 2.3 物流服务（logistics-service）

#### 新增文件
| 文件 | 说明 |
|------|------|
| `service/impl/LogisticsIdempotencyService.java` | Redis 幂等性工具类 |

#### 修改文件
| 文件 | 修改内容 |
|------|---------|
| `service/TraceSyncService.java` | 注入 `LogisticsIdempotencyService`，修改 `processCallback()` 添加幂等检查 |

#### 幂等键设计
```
回调处理：logistics:idempotent:{carrierCode}:{waybillNo}:{status}:{latestTraceTime}
```

#### 修复逻辑
1. **Redis 检查**：同一运单同一状态回调在24h内不重复处理
2. **轨迹去重**：数据库已有 `uk_logistics_trace_waybill_time_location` 保障轨迹不重复

---

## 三、数据库变更（SQL Migration）

**文件：** `docs/database/V2.4__idempotency_indexes.sql`

```sql
-- orders 表：平台订单号+平台+店铺 唯一索引
ALTER TABLE orders
ADD CONSTRAINT uk_orders_platform_order_sn
UNIQUE (platform_order_sn, platform, shop_id);

-- stocks 表：SKU+仓库 唯一索引
ALTER TABLE stocks
ADD CONSTRAINT uk_stocks_sku_warehouse
UNIQUE (sku_code, warehouse_id);

-- logistics_trace 表：运单号+轨迹时间+地点 唯一索引
ALTER TABLE logistics_trace
ADD CONSTRAINT uk_logistics_trace_waybill_time_location
UNIQUE (waybill_no, trace_time, location);
```

---

## 四、幂等性保证层次

```
┌─────────────────────────────────────────────────────┐
│                    接入层                            │
│  幂等Token / 请求ID（可选，未来扩展）                   │
├─────────────────────────────────────────────────────┤
│                   Redis 层                           │
│  SETNX 原子操作防并发重复请求（TTL=24h）               │
├─────────────────────────────────────────────────────┤
│                   数据库层                           │
│  唯一索引兜底：UK(platform_order_sn,platform,shop_id) │
│              UK(sku_code, warehouse_id)              │
│              UK(waybill_no, trace_time, location)   │
├─────────────────────────────────────────────────────┤
│                   业务层                             │
│  状态机 + 乐观锁防止状态异常                          │
└─────────────────────────────────────────────────────┘
```

---

## 五、影响范围

| 接口 | 服务 | 幂等键 | 影响 |
|------|------|--------|------|
| `POST /api/orders/pull` | order-service | `platform:shopId:platformOrderSn` | ✅ 已修复 |
| `POST /api/orders/{id}/ship` | order-service | `ship:orderId` | ✅ 已修复 |
| `POST /api/stock/deduct` | inventory-service | `skuCode:warehouseId:relatedNo` | ✅ 已修复 |
| `POST /api/stock/lock` | inventory-service | `lock:skuCode:warehouseId:relatedNo` | ✅ 已修复 |
| `POST /api/stock/unlock` | inventory-service | `unlock:skuCode:warehouseId:relatedNo` | ✅ 已修复 |
| `POST /api/logistics/callback/{carrier}` | logistics-service | `carrier:waybillNo:status:traceTime` | ✅ 已修复 |

---

## 六、注意事项

1. **Redis 连接**：所有服务 pom.xml 均已包含 `spring-boot-starter-data-redis`，无需额外依赖
2. **幂等TTL**：统一设为 24 小时，可根据业务调整
3. **数据库索引**：执行 SQL migration 时注意在线表操作影响，建议在低峰期执行
4. **消息队列**：当前系统无 MQ 消费者（使用 Spring Event），无需额外处理
5. **测试建议**：使用 Postman/Curl 重复调用上述接口，验证幂等返回而非重复创建

---

## 七、修改文件清单

```
services/order-service/src/main/java/com/erp/order/service/impl/
  + OrderIdempotencyService.java          [NEW]

services/order-service/src/main/java/com/erp/order/service/impl/OrderServiceImpl.java
  [MOD] 添加 OrderIdempotencyService 依赖
  [MOD] createOrders() 添加幂等检查
  [MOD] shipOrder() 添加幂等检查

services/inventory-service/src/main/java/com/erp/inventory/service/impl/
  + StockIdempotencyService.java         [NEW]

services/inventory-service/src/main/java/com/erp/inventory/controller/StockFeignController.java
  [MOD] 添加 StockIdempotencyService 依赖
  [MOD] deductStock() 添加幂等检查
  [MOD] lockStock() 添加幂等检查
  [MOD] unlockStock() 添加幂等检查

services/logistics-service/src/main/java/com/erp/logistics/service/impl/
  + LogisticsIdempotencyService.java      [NEW]

services/logistics-service/src/main/java/com/erp/logistics/service/TraceSyncService.java
  [MOD] 添加 LogisticsIdempotencyService 依赖
  [MOD] processCallback() 添加幂等检查

docs/database/V2.4__idempotency_indexes.sql   [NEW]
docs/幂等性修复报告.md                          [NEW]
```
