Java后端系统对接第三方外卖API时的幂等性设计与重试策略实践
在构建高可用的外卖业务系统时,Java后端常需调用美团、饿了么等第三方外卖平台的API,如创建订单、取消订单、核销霸王餐等。由于网络抖动、服务超时或第三方限流等原因,请求可能失败或响应不确定。若盲目重试,极易导致重复下单、多次退款等严重业务问题。因此,幂等性设计与合理的重试策略成为保障系统稳定性的关键。

幂等性机制设计
幂等性指同一操作多次执行所产生的影响与一次执行相同。在外卖API场景中,可通过**唯一业务标识(BizId)**实现幂等控制。例如,在创建订单时,由本地系统生成全局唯一的 outTradeNo 作为外部订单号传给第三方平台。美团等平台会基于该字段去重,避免重复创建。
在本地系统中,也需建立幂等记录表,防止重复处理回调或重试请求:
package juwatech.cn.idempotent;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface IdempotentRecordRepository extends JpaRepository<IdempotentRecord, String> {
Optional<IdempotentRecord> findByBizId(String bizId);
}
package juwatech.cn.idempotent;
import javax.persistence.Entity;
import javax.persistence.Id;
import java.time.LocalDateTime;
@Entity
public class IdempotentRecord {
@Id
private String bizId;
private String actionType; // 如 "CREATE_ORDER", "CANCEL_ORDER"
private LocalDateTime createTime;
private String resultData; // 可存第三方返回的完整响应
// getters and setters
}
业务逻辑中先校验幂等记录:
package juwatech.cn.service;
import juwatech.cn.idempotent.IdempotentRecord;
import juwatech.cn.idempotent.IdempotentRecordRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class OrderService {
@Autowired
private IdempotentRecordRepository idempotentRepo;
@Autowired
private ThirdPartyApiClient apiClient;
@Transactional
public String createOrderWithIdempotency(String bizId, OrderRequest request) {
var existing = idempotentRepo.findByBizId(bizId);
if (existing.isPresent()) {
return existing.get().getResultData(); // 直接返回历史结果
}
String response = apiClient.createOrder(request.withOutTradeNo(bizId));
var record = new IdempotentRecord();
record.setBizId(bizId);
record.setActionType("CREATE_ORDER");
record.setResultData(response);
record.setCreateTime(LocalDateTime.now());
idempotentRepo.save(record);
return response;
}
}
重试策略实现
对于网络异常或5xx错误,应采用指数退避+最大重试次数策略。使用 Spring Retry 或自定义重试逻辑均可。以下为自定义实现:
package juwatech.cn.retry;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
public class RetryUtils {
public static <T> T retry(Supplier<T> operation, int maxRetries, long baseDelayMs) {
Exception lastException = null;
for (int i = 0; i <= maxRetries; i++) {
try {
return operation.get();
} catch (Exception e) {
lastException = e;
if (i < maxRetries) {
long delay = (long) (baseDelayMs * Math.pow(2, i));
try {
TimeUnit.MILLISECONDS.sleep(delay);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new RuntimeException("Retry interrupted", ie);
}
}
}
}
throw new RuntimeException("Operation failed after " + maxRetries + " retries", lastException);
}
}
结合幂等ID调用第三方API:
package juwatech.cn.client;
import juwatech.cn.retry.RetryUtils;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import java.nio.charset.StandardCharsets;
public class ThirdPartyApiClient {
private final CloseableHttpClient httpClient = HttpClients.createDefault();
private static final String CREATE_ORDER_URL = "https://openapi.meituan.com/v1/order/create";
public String createOrder(OrderRequest request) {
return RetryUtils.retry(() -> {
HttpPost post = new HttpPost(CREATE_ORDER_URL);
post.setHeader("Content-Type", "application/json");
post.setEntity(new StringEntity(request.toJson(), StandardCharsets.UTF_8));
HttpResponse response = httpClient.execute(post);
int status = response.getStatusLine().getStatusCode();
String body = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
if (status >= 500) {
throw new RuntimeException("Third-party server error: " + status);
}
if (status == 429) {
throw new RuntimeException("Rate limited");
}
// 注意:400/401/403 等客户端错误不应重试
return body;
}, 3, 500); // 最多重试3次,初始延迟500ms
}
}
回调处理中的幂等性
第三方平台异步通知(如订单状态变更回调)同样需幂等处理:
package juwatech.cn.controller;
import juwatech.cn.idempotent.IdempotentRecordRepository;
import juwatech.cn.service.OrderCallbackService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class MeituanCallbackController {
@Autowired
private IdempotentRecordRepository idempotentRepo;
@Autowired
private OrderCallbackService callbackService;
@PostMapping("/callback/meituan/order")
public String handleOrderCallback(@RequestBody String payload,
@RequestHeader("X-MT-Signature") String signature,
@RequestHeader("X-MT-Nonce") String nonce) {
// 验签逻辑略
String bizId = extractBizIdFromPayload(payload); // 从回调体提取 out_trade_no
if (idempotentRepo.findByBizId(bizId).isPresent()) {
return "success"; // 已处理过,直接返回成功
}
callbackService.processOrderUpdate(payload);
return "success";
}
private String extractBizIdFromPayload(String payload) {
// 解析 JSON 提取 bizId
return "mock_biz_id";
}
}
本文著作权归吃喝不愁app开发者团队,转载请注明出处!
163

被折叠的 条评论
为什么被折叠?



