银行接口调用复杂场景处理技术分析
1. 概述
在支付系统中,银行接口调用是最核心也是最复杂的环节。由于网络不稳定、银行系统特性差异、业务规则复杂等因素,银行接口调用经常面临各种异常场景。本文深入分析四种最典型的复杂场景及其处理方案。
1.1 核心挑战
- 网络不确定性:超时不等于失败,成功不等于到账
- 银行系统差异:不同银行的响应格式、错误码、处理时间差异巨大
- 状态不一致:本地状态与银行状态可能不同步
- 资金安全:任何处理不当都可能导致资金损失
1.2 处理原则
- 安全第一:宁可多查询,不可漏处理
- 幂等保证:相同请求多次执行结果一致
- 主动确认:不依赖银行主动通知
- 分类处理:技术问题和业务问题区别对待
2. 场景一:超时后返回"订单已存在"
2.1 问题描述
这是支付系统中最危险的场景之一:
时间线:
T1: 发起银行支付请求
T2: 网络超时,但银行可能已处理
T3: 系统重试支付
T4: 银行返回"订单已存在"
核心风险:不知道第一次调用的实际结果,可能导致重复扣款或漏扣款。
2.2 完整处理方案
@Service
public class BankTimeoutRetryHandler {
/**
* 超时重试处理流程
*/
public BankResponse handleTimeoutRetry(PaymentOrder order, BankTimeoutException timeoutException) {
// 1. 记录超时事件
recordTimeoutEvent(order, timeoutException);
// 2. 智能等待策略
waitForBankProcessing(order);
// 3. 主动查询银行状态
BankQueryResponse queryResponse = queryBankOrderStatus(order);
if (queryResponse.isSuccess()) {
return handleQueryResult(order, queryResponse);
} else {
return handleRetryPayment(order);
}
}
/**
* 智能等待策略
*/
private void waitForBankProcessing(PaymentOrder order) {
// 根据银行类型和金额确定等待时间
long waitTime = calculateWaitTime(order.getBankCode(), order.getAmount());
// 大额订单:10秒,小额订单:5秒
if (order.getAmount().compareTo(new BigDecimal("10000")) > 0) {
waitTime = 10000;
} else {
waitTime = 5000;
}
try {
Thread.sleep(waitTime);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
/**
* 处理查询结果
*/
private BankResponse handleQueryResult(PaymentOrder order, BankQueryResponse queryResponse) {
switch (queryResponse.getOrderStatus()) {
case SUCCESS:
// 银行已成功,更新本地状态
updateOrderToSuccess(order, queryResponse);
return BankResponse.success(queryResponse);
case FAILED:
// 银行已失败,可以重新发起支付
return retryWithNewOrderId(order);
case PROCESSING:
// 仍在处理中,继续等待
return scheduleDelayedQuery(order);
default:
throw new UnknownBankStatusException("未知银行状态: " + queryResponse.getOrderStatus());
}
}
/**
* 使用新订单号重试
*/
private BankResponse retryWithNewOrderId(PaymentOrder order) {
// 生成新的银行订单号
String newBankOrderId = generateNewBankOrderId(order);
order.setBankOrderId(newBankOrderId);
try {
return bankClient.payment(buildBankRequest(order));
} catch (BankOrderExistsException e) {
// 新订单号仍然冲突,说明银行系统异常
throw new BankSystemException("银行系统异常,订单号生成冲突");
}
}
}
2.3 关键技术点
等待时间策略
- 小额支付:5秒等待,银行处理较快
- 大额支付:10秒等待,可能需要额外审核
- 特殊银行:根据历史数据调整等待时间
查询重试机制
- 指数退避:1s, 2s, 4s, 8s...
- 最大重试:3次查询失败后转人工处理
- 超时控制:总查询时间不超过5分钟
3. 场景二:响应解析失败的重试判断
3.1 问题分析
银行响应解析失败的原因多样:
- 格式错误:JSON/XML格式不正确
- 编码问题:字符编码不匹配
- 内容截断:网络传输中数据丢失
- 格式变更:银行接口升级导致格式变化
3.2 响应解析失败的深度分析
3.2.1 解析失败的根本原因
/**
* 响应解析失败的常见原因分析:
* 1. 格式问题:JSON/XML格式不正确、字段缺失
* 2. 编码问题:UTF-8/GBK编码不匹配
* 3. 内容截断:网络传输中数据丢失或截断
* 4. 接口升级:银行接口版本升级导致格式变化
* 5. 异常响应:银行返回HTML错误页面而非JSON/XML
* 6. 压缩问题:GZIP压缩/解压缩失败
* 7. 特殊字符:包含不可见字符或控制字符
*/
3.2.2 多层次解析策略
@Component
public class EnhancedBankResponseParser {
private static final Logger log = LoggerFactory.getLogger(EnhancedBankResponseParser.class);
/**
* 增强的响应解析器
*/
public BankResponse parseBankResponse(String rawResponse, PaymentOrder order) {
// 1. 预处理检查
ParsePreCheckResult preCheckResult = preCheckResponse(rawResponse, order);
if (!preCheckResult.isValid()) {
return handlePreCheckFailure(preCheckResult, order);
}
// 2. 多格式尝试解析
for (ResponseFormat format : ResponseFormat.values()) {
try {
BankResponse response = tryParseWithFormat(rawResponse, format, order);
if (response != null) {
log.info("解析成功: orderId={}, format={}", order.getOrderId(), format);
return response;
}
} catch (Exception e) {
log.debug("格式{}解析失败: orderId={}, error={}", format, order.getOrderId(), e.getMessage());
}
}
// 3. 智能内容分析
return performIntelligentAnalysis(rawResponse, order);
}
/**
* 响应预检查
*/
private ParsePreCheckResult preCheckResponse(String rawResponse, PaymentOrder order) {
ParsePreCheckResult result = new ParsePreCheckResult();
// 检查响应是否为空
if (StringUtils.isBlank(rawResponse)) {
result.setValid(false);
result.setFailureReason("响应内容为空");
result.setRecommendedAction(RecommendedAction.RETRY_AFTER_DELAY);
return result;
}
// 检查响应长度
if (rawResponse.length() < 10) {
result.setValid(false);
result.setFailureReason("响应内容过短,疑似网络问题");
result.setRecommendedAction(RecommendedAction.RETRY_IMMEDIATELY);
return result;
}
if (rawResponse.length() > 1024 * 1024) { // 1MB
result.setValid(false);
result.setFailureReason("响应内容过大,疑似异常");
result.setRecommendedAction(RecommendedAction.QUERY_BANK_STATUS);
return result;
}
// 检查是否为HTML错误页面
if (isHtmlErrorPage(rawResponse)) {
result.setValid(false);
result.setFailureReason("银行返回HTML错误页面");
result.setRecommendedAction(RecommendedAction.QUERY_BANK_STATUS);
return result;
}
// 检查编码问题
if (hasEncodingIssues(rawResponse)) {
result.setValid(false);
result.setFailureReason("响应存在编码问题");
result.setRecommendedAction(RecommendedAction.RETRY_WITH_ENCODING_FIX);
return result;
}
result.setValid(true);
return result;
}
/**
* 多格式解析尝试
*/
private BankResponse tryParseWithFormat(String rawResponse, ResponseFormat format, PaymentOrder order) {
switch (format) {
case JSON:
return tryJsonParse(rawResponse, order);
case XML:
return tryXmlParse(rawResponse, order);
case FORM_ENCODED:
return tryFormEncodedParse(rawResponse, order);
case PLAIN_TEXT:
return tryPlainTextParse(rawResponse, order);
case CUSTOM_DELIMITED:
return tryCustomDelimitedParse(rawResponse, order);
default:
return null;
}
}
/**
* JSON解析增强版
*/
private BankResponse tryJsonParse(String rawResponse, PaymentOrder order) {
try {
// 1. 标准JSON解析
return parseStandardJson(rawResponse);
} catch (JsonParseException e) {
// 2. 尝试修复常见JSON问题
String fixedJson = fixCommonJsonIssues(rawResponse);
if (!fixedJson.equals(rawResponse)) {
try {
log.info("JSON修复后重新解析: orderId={}", order.getOrderId());
return parseStandardJson(fixedJson);
} catch (Exception ex) {
log.debug("修复后仍解析失败: orderId={}", order.getOrderId());
}
}
// 3. 提取关键信息
return extractKeyInfoFromBrokenJson(rawResponse, order);
}
}
/**
* 修复常见JSON问题
*/
private String fixCommonJsonIssues(String jsonString) {
String fixed = jsonString;
// 移除BOM标记
if (fixed.startsWith("\uFEFF")) {
fixed = fixed.substring(1);
}
// 移除前后空白字符
fixed = fixed.trim();
// 修复单引号问题
fixed = fixed.replaceAll("'([^']*)':", "\"$1\":");
fixed = fixed.replaceAll(":[ ]*'([^']*)'", ": \"$1\"");
// 修复尾部逗号问题
fixed = fixed.replaceAll(",[ ]*}", "}");
fixed = fixed.replaceAll(",[ ]*]", "]");
// 修复缺失引号的字段名
fixed = fixed.replaceAll("([{,][ ]*)([a-zA-Z_][a-zA-Z0-9_]*)[ ]*:", "$1\"$2\":");
// 移除注释
fixed = fixed.replaceAll("//.*", "");
fixed = fixed.replaceAll("/\\*.*?\\*/", "");
return fixed;
}
/**
* 从损坏的JSON中提取关键信息
*/
private BankResponse extractKeyInfoFromBrokenJson(String brokenJson, PaymentOrder order) {
log.warn("JSON解析失败,尝试提取关键信息: orderId={}", order.getOrderId());
BankResponse response = new BankResponse();
// 提取状态码
String statusCode = extractFieldValue(brokenJson, "status", "code", "result_code");
if (statusCode != null) {
response.setErrorCode(statusCode);
}
// 提取消息
String message = extractFieldValue(brokenJson, "message", "msg", "error_msg", "desc");
if (message != null) {
response.setErrorMessage(message);
}
// 提取银行订单号
String bankOrderId = extractFieldValue(brokenJson, "bank_order_id", "transaction_id", "ref_no");
if (bankOrderId != null) {
response.setBankOrderId(bankOrderId);
}
// 根据提取的信息判断结果
if (isSuccessIndicator(statusCode, message)) {
response.setSuccess(true);
log.info("从损坏JSON中识别为成功: orderId={}, status={}", order.getOrderId(), statusCode);
} else if (isFailureIndicator(statusCode, message)) {
response.setSuccess(false);
log.info("从损坏JSON中识别为失败: orderId={}, status={}", order.getOrderId(), statusCode);
} else {
// 无法确定,需要查询
response.setNeedQuery(true);
log.warn("从损坏JSON中无法确定结果: orderId={}", order.getOrderId());
}
return response;
}
/**
* 提取字段值(支持多个可能的字段名)
*/
private String extractFieldValue(String content, String... fieldNames) {
for (String fieldName : fieldNames) {
// 尝试不同的模式
String[] patterns = {
"\"" + fieldName + "\"[ ]*:[ ]*\"([^\"]*)", // "field":"value"
"'" + fieldName + "'[ ]*:[ ]*'([^']*)", // 'field':'value'
fieldName + "[ ]*=[ ]*\"([^\"]*)", // field="value"
fieldName + "[ ]*:[ ]*([^,}\\]\\s]*)" // field:value
};
for (String pattern : patterns) {
Pattern regex = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE);
Matcher matcher = regex.matcher(content);
if (matcher.find()) {
return matcher.group(1).trim();
}
}
}
return null;
}
/**
* 智能内容分析
*/
private BankResponse performIntelligentAnalysis(String rawResponse, PaymentOrder order) {
log.warn("所有格式解析失败,执行智能分析: orderId={}", order.getOrderId());
// 1. 内容特征分析
ContentAnalysisResult analysisResult = analyzeResponseContent(rawResponse);
// 2. 根据分析结果决策
switch (analysisResult.getContentType()) {
case SUCCESS_INDICATOR:
return createSuccessResponse(analysisResult, order);
case FAILURE_INDICATOR:
return createFailureResponse(analysisResult, order);
case PROCESSING_INDICATOR:
return createProcessingResponse(analysisResult, order);
case NETWORK_ERROR:
return createNetworkErrorResponse(analysisResult, order);
case UNKNOWN_CONTENT:
default:
return createUnknownResponse(analysisResult, order);
}
}
/**
* 响应内容分析
*/
private ContentAnalysisResult analyzeResponseContent(String content) {
ContentAnalysisResult result = new ContentAnalysisResult();
// 转换为小写便于匹配
String lowerContent = content.toLowerCase();
// 成功指示器检查
String[] successIndicators = {
"success", "成功", "ok", "accepted", "approved", "completed",
"\"status\":\"00\"", "\"code\":\"0000\"", "\"result\":\"success\"",
"交易成功", "支付成功", "处理成功"
};
for (String indicator : successIndicators) {
if (lowerContent.contains(indicator.toLowerCase())) {
result.setContentType(ContentType.SUCCESS_INDICATOR);
result.addEvidence("发现成功指示器: " + indicator);
result.setConfidence(calculateConfidence(content, indicator));
return result;
}
}
// 失败指示器检查
String[] failureIndicators = {
"fail", "error", "失败", "错误", "rejected", "declined", "denied",
"\"status\":\"01\"", "\"code\":\"9999\"", "\"result\":\"fail\"",
"余额不足", "账户异常", "密码错误", "交易失败"
};
for (String indicator : failureIndicators) {
if (lowerContent.contains(indicator.toLowerCase())) {
result.setContentType(ContentType.FAILURE_INDICATOR);
result.addEvidence("发现失败指示器: " + indicator);
result.setConfidence(calculateConfidence(content, indicator));
return result;
}
}
// 处理中指示器检查
String[] processingIndicators = {
"processing", "pending", "处理中", "等待", "审核中",
"\"status\":\"02\"", "\"result\":\"processing\""
};
for (String indicator : processingIndicators) {
if (lowerContent.contains(indicator.toLowerCase())) {
result.setContentType(ContentType.PROCESSING_INDICATOR);
result.addEvidence("发现处理中指示器: " + indicator);
result.setConfidence(calculateConfidence(content, indicator));
return result;
}
}
// 网络错误检查
if (isNetworkErrorContent(content)) {
result.setContentType(ContentType.NETWORK_ERROR);
result.addEvidence("识别为网络错误内容");
result.setConfidence(0.8);
return result;
}
// 未知内容
result.setContentType(ContentType.UNKNOWN_CONTENT);
result.addEvidence("无法识别内容类型");
result.setConfidence(0.0);
return result;
}
/**
* 计算置信度
*/
private double calculateConfidence(String content, String indicator) {
double baseConfidence = 0.6;
// 如果指示器出现在JSON/XML结构中,置信度更高
if (content.contains("\"" + indicator + "\"") || content.contains("<" + indicator + ">")) {
baseConfidence += 0.3;
}
// 如果有多个相关指示器,置信度更高
String lowerContent = content.toLowerCase();
String lowerIndicator = indicator.toLowerCase();
if (lowerIndicator.contains("success") || lowerIndicator.contains("成功")) {
if (lowerContent.contains("00") || lowerContent.contains("0000")) {
baseConfidence += 0.2;
}
}
if (lowerIndicator.contains("fail") || lowerIndicator.contains("失败")) {
if (lowerContent.contains("01") || lowerContent.contains("9999")) {
baseConfidence += 0.2;
}
}
return Math.min(baseConfidence, 1.0);
}
/**
* 检查是否为网络错误内容
*/
private boolean isNetworkErrorContent(String content) {
String[] networkErrorIndicators = {
"connection timeout", "read timeout", "connection refused",
"network error", "socket timeout", "connection reset",
"502 bad gateway", "503 service unavailable", "504 gateway timeout",
"网络超时", "连接超时", "网络错误"
};
String lowerContent = content.toLowerCase();
for (String indicator : networkErrorIndicators) {
if (lowerContent.contains(indicator)) {
return true;
}
}
return false;
}
}
enum ResponseFormat {
JSON, // JSON格式
XML, // XML格式
FORM_ENCODED, // 表单编码格式
PLAIN_TEXT, // 纯文本格式
CUSTOM_DELIMITED // 自定义分隔符格式
}
enum ContentType {
SUCCESS_INDICATOR, // 成功指示
FAILURE_INDICATOR, // 失败指示
PROCESSING_INDICATOR, // 处理中指示
NETWORK_ERROR, // 网络错误
UNKNOWN_CONTENT // 未知内容
}
enum RecommendedAction {
RETRY_IMMEDIATELY, // 立即重试
RETRY_AFTER_DELAY, // 延迟重试
QUERY_BANK_STATUS, // 查询银行状态
RETRY_WITH_ENCODING_FIX, // 修复编码后重试
NO_RETRY // 不重试
}
@Data
class ParsePreCheckResult {
private boolean valid;
private String failureReason;
private RecommendedAction recommendedAction;
}
@Data
class ContentAnalysisResult {
private ContentType contentType;
private List<String> evidences = new ArrayList<>();
private double confidence;
public void addEvidence(String evidence) {
this.evidences.add(evidence);
}
}
3.2.3 高级重试决策引擎
@Component
public class AdvancedRetryDecisionEngine {
/**
* 高级重试决策
*/
public RetryDecision makeRetryDecision(ParseFailureContext context) {
// 1. 基础检查
if (context.getRetryCount() >= getMaxRetryCount(context.getOrder())) {
return RetryDecision.NO_RETRY("超过最大重试次数");
}
// 2. 响应内容分析
ResponseAnalysis analysis = analyzeResponseContent(context);
// 3. 历史成功率分析
double historicalSuccessRate = getHistoricalSuccessRate(context.getOrder().getBankCode());
// 4. 网络状况分析
NetworkCondition networkCondition = analyzeNetworkCondition(context);
// 5. 综合决策
return makeComprehensiveDecision(analysis, historicalSuccessRate, networkCondition, context);
}
/**
* 综合决策逻辑
*/
private RetryDecision makeComprehensiveDecision(ResponseAnalysis analysis,
double historicalSuccessRate,
NetworkCondition networkCondition,
ParseFailureContext context) {
// 决策权重计算
DecisionWeights weights = calculateDecisionWeights(analysis, historicalSuccessRate, networkCondition);
// 如果响应包含明确的成功/失败指示,优先查询确认
if (analysis.hasDefinitiveIndicator()) {
if (analysis.getConfidence() > 0.8) {
return RetryDecision.QUERY_INSTEAD("高置信度指示器,查询确认");
}
}
// 网络条件很差,延长重试间隔
if (networkCondition == NetworkCondition.POOR) {
long delay = calculateAdaptiveDelay(context.getRetryCount(), networkCondition);
return RetryDecision.RETRY_AFTER_DELAY(delay);
}
// 银行历史成功率很低,优先查询
if (historicalSuccessRate < 0.7) {
return RetryDecision.QUERY_INSTEAD("银行成功率较低,查询确认");
}
// 根据解析失败类型决策
switch (analysis.getFailureType()) {
case FORMAT_ERROR:
return handleFormatError(analysis, context);
case ENCODING_ERROR:
return handleEncodingError(analysis, context);
case TRUNCATION_ERROR:
return handleTruncationError(analysis, context);
case NETWORK_ERROR:
return handleNetworkError(analysis, context);
default:
return RetryDecision.QUERY_INSTEAD("未知错误类型,查询确认");
}
}
/**
* 处理格式错误
*/
private RetryDecision handleFormatError(ResponseAnalysis analysis, ParseFailureContext context) {
// 如果是JSON格式错误,尝试修复后重试
if (analysis.getOriginalFormat() == ResponseFormat.JSON) {
if (context.getRetryCount() == 0) {
return RetryDecision.RETRY_WITH_FIX("尝试修复JSON格式");
}
}
// 如果是XML格式错误,可能是编码问题
if (analysis.getOriginalFormat() == ResponseFormat.XML) {
return RetryDecision.RETRY_WITH_ENCODING_CHANGE("尝试不同编码解析XML");
}
// 其他格式错误,查询确认
return RetryDecision.QUERY_INSTEAD("格式错误无法修复");
}
/**
* 处理编码错误
*/
private RetryDecision handleEncodingError(ResponseAnalysis analysis, ParseFailureContext context) {
// 尝试不同的编码方式
String[] encodings = {"UTF-8", "GBK", "GB2312", "ISO-8859-1"};
if (context.getTriedEncodings().size() < encodings.length) {
String nextEncoding = getNextUntried Encoding(encodings, context.getTriedEncodings());
return RetryDecision.RETRY_WITH_ENCODING(nextEncoding);
}
// 所有编码都尝试过,查询确认
return RetryDecision.QUERY_INSTEAD("所有编码方式都已尝试");
}
/**
* 处理截断错误
*/
private RetryDecision handleTruncationError(ResponseAnalysis analysis, ParseFailureContext context) {
// 截断错误通常是网络问题,可以重试
if (context.getRetryCount() < 2) {
long delay = 2000 * (context.getRetryCount() + 1); // 2s, 4s
return RetryDecision.RETRY_AFTER_DELAY(delay);
}
// 多次重试仍截断,可能是银行系统问题,查询确认
return RetryDecision.QUERY_INSTEAD("多次重试仍出现截断");
}
/**
* 自适应延迟计算
*/
private long calculateAdaptiveDelay(int retryCount, NetworkCondition networkCondition) {
long baseDelay = 1000; // 1秒基础延迟
// 根据网络状况调整
switch (networkCondition) {
case EXCELLENT:
baseDelay = 500;
break;
case GOOD:
baseDelay = 1000;
break;
case FAIR:
baseDelay = 2000;
break;
case POOR:
baseDelay = 5000;
break;
}
// 指数退避
long delay = baseDelay * (1L << retryCount);
// 最大延迟限制
return Math.min(delay, 30000); // 最大30秒
}
}
@Data
class DecisionWeights {
private double responseAnalysisWeight = 0.4;
private double historicalDataWeight = 0.3;
private double networkConditionWeight = 0.2;
private double businessContextWeight = 0.1;
}
enum NetworkCondition {
EXCELLENT, // 优秀
GOOD, // 良好
FAIR, // 一般
POOR // 较差
}
enum ParseFailureType {
FORMAT_ERROR, // 格式错误
ENCODING_ERROR, // 编码错误
TRUNCATION_ERROR, // 截断错误
NETWORK_ERROR, // 网络错误
UNKNOWN_ERROR // 未知错误
}
3.2.4 响应修复和重构机制
@Component
public class ResponseRepairService {
/**
* 尝试修复损坏的响应
*/
public String repairBrokenResponse(String brokenResponse, ResponseFormat expectedFormat) {
switch (expectedFormat) {
case JSON:
return repairBrokenJson(brokenResponse);
case XML:
return repairBrokenXml(brokenResponse);
default:
return brokenResponse;
}
}
/**
* 修复损坏的JSON
*/
private String repairBrokenJson(String brokenJson) {
String repaired = brokenJson;
// 1. 基础清理
repaired = basicJsonCleanup(repaired);
// 2. 结构修复
repaired = repairJsonStructure(repaired);
// 3. 字段修复
repaired = repairJsonFields(repaired);
// 4. 验证修复结果
if (isValidJson(repaired)) {
log.info("JSON修复成功");
return repaired;
}
// 5. 如果修复失败,尝试提取关键部分
return extractJsonCore(brokenJson);
}
/**
* JSON结构修复
*/
private String repairJsonStructure(String json) {
String repaired = json.trim();
// 确保以{开始,}结束
if (!repaired.startsWith("{") && !repaired.startsWith("[")) {
// 查找第一个{或[
int startIndex = Math.max(repaired.indexOf('{'), repaired.indexOf('['));
if (startIndex > 0) {
repaired = repaired.substring(startIndex);
}
}
// 确保正确结束
if (repaired.startsWith("{") && !repaired.endsWith("}")) {
// 查找最后一个}
int lastBrace = repaired.lastIndexOf('}');
if (lastBrace > 0) {
repaired = repaired.substring(0, lastBrace + 1);
} else {
repaired += "}";
}
}
if (repaired.startsWith("[") && !repaired.endsWith("]")) {
// 查找最后一个]
int lastBracket = repaired.lastIndexOf(']');
if (lastBracket > 0) {
repaired = repaired.substring(0, lastBracket + 1);
} else {
repaired += "]";
}
}
return repaired;
}
/**
* 提取JSON核心内容
*/
private String extractJsonCore(String brokenJson) {
// 尝试提取关键字段构建新的JSON
Map<String, String> extractedFields = new HashMap<>();
// 提取常见字段
String[] commonFields = {"status", "code", "message", "result", "data", "error"};
for (String field : commonFields) {
String value = extractFieldValue(brokenJson, field);
if (value != null) {
extractedFields.put(field, value);
}
}
// 构建新的JSON
if (!extractedFields.isEmpty()) {
StringBuilder jsonBuilder = new StringBuilder("{");
boolean first = true;
for (Map.Entry<String, String> entry : extractedFields.entrySet()) {
if (!first) {
jsonBuilder.append(",");
}
jsonBuilder.append("\"").append(entry.getKey()).append("\":\"")
.append(entry.getValue()).append("\"");
first = false;
}
jsonBuilder.append("}");
return jsonBuilder.toString();
}
return brokenJson;
}
}
6. 基于Spring Retry的重试实现
6.1 Spring Retry配置
@Configuration
@EnableRetry
public class BankRetryConfiguration {
/**
* 银行接口重试模板配置
*/
@Bean
public RetryTemplate bankRetryTemplate() {
RetryTemplate retryTemplate = new RetryTemplate();
// 重试策略配置
FixedBackOffPolicy backOffPolicy = new FixedBackOffPolicy();
backOffPolicy.setBackOffPeriod(2000); // 2秒间隔
retryTemplate.setBackOffPolicy(backOffPolicy);
// 重试条件配置
CompositeRetryPolicy retryPolicy = new CompositeRetryPolicy();
retryPolicy.setPolicies(new RetryPolicy[]{
createTimeoutRetryPolicy(),
createParseErrorRetryPolicy(),
createNetworkErrorRetryPolicy()
});
retryTemplate.setRetryPolicy(retryPolicy);
return retryTemplate;
}
/**
* 超时重试策略
*/
private RetryPolicy createTimeoutRetryPolicy() {
SimpleRetryPolicy timeoutPolicy = new SimpleRetryPolicy();
timeoutPolicy.setMaxAttempts(3);
Map<Class<? extends Throwable>, Boolean> retryableExceptions = new HashMap<>();
retryableExceptions.put(SocketTimeoutException.class, true);
retryableExceptions.put(ConnectTimeoutException.class, true);
retryableExceptions.put(ReadTimeoutException.class, true);
timeoutPolicy.setRetryableExceptions(retryableExceptions);
return timeoutPolicy;
}
/**
* 解析错误重试策略
*/
private RetryPolicy createParseErrorRetryPolicy() {
SimpleRetryPolicy parsePolicy = new SimpleRetryPolicy();
parsePolicy.setMaxAttempts(2); // 解析错误最多重试2次
Map<Class<? extends Throwable>, Boolean> retryableExceptions = new HashMap<>();
retryableExceptions.put(JsonParseException.class, true);
retryableExceptions.put(XmlParseException.class, true);
retryableExceptions.put(ResponseParseException.class, true);
parsePolicy.setRetryableExceptions(retryableExceptions);
return parsePolicy;
}
/**
* 网络错误重试策略
*/
private RetryPolicy createNetworkErrorRetryPolicy() {
ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
backOffPolicy.setInitialInterval(1000); // 1秒
backOffPolicy.setMultiplier(2.0); // 指数倍数
backOffPolicy.setMaxInterval(30000); // 最大30秒
SimpleRetryPolicy networkPolicy = new SimpleRetryPolicy();
networkPolicy.setMaxAttempts(4);
Map<Class<? extends Throwable>, Boolean> retryableExceptions = new HashMap<>();
retryableExceptions.put(ConnectException.class, true);
retryableExceptions.put(NoRouteToHostException.class, true);
retryableExceptions.put(SocketException.class, true);
networkPolicy.setRetryableExceptions(retryableExceptions);
return networkPolicy;
}
}
6.2 银行接口重试服务
@Service
public class BankInterfaceRetryService {
@Autowired
private RetryTemplate bankRetryTemplate;
@Autowired
private BankClient bankClient;
@Autowired
private BankQueryService bankQueryService;
/**
* 带重试的银行支付调用
*/
@Retryable(
value = {BankTimeoutException.class, BankNetworkException.class},
maxAttempts = 3,
backoff = @Backoff(delay = 2000, multiplier = 2, maxDelay = 10000)
)
public BankResponse callBankPaymentWithRetry(PaymentOrder order) {
try {
log.info("调用银行支付接口: orderId={}, attempt={}",
order.getOrderId(), getCurrentAttempt());
BankResponse response = bankClient.payment(buildBankRequest(order));
// 验证响应有效性
validateBankResponse(response, order);
return response;
} catch (BankTimeoutException e) {
log.warn("银行接口超时: orderId={}, timeout={}ms",
order.getOrderId(), e.getTimeoutDuration());
throw e; // 重新抛出,触发重试
} catch (BankResponseParseException e) {
log.warn("银行响应解析失败: orderId={}, error={}",
order.getOrderId(), e.getMessage());
// 尝试智能解析
BankResponse recoveredResponse = attemptIntelligentParse(e.getRawResponse(), order);
if (recoveredResponse != null) {
return recoveredResponse;
}
throw e; // 重新抛出,触发重试
} catch (BankBusinessException e) {
log.info("银行业务失败: orderId={}, errorCode={}",
order.getOrderId(), e.getErrorCode());
// 业务异常不重试,直接返回
return BankResponse.failed(e.getErrorCode(), e.getMessage());
}
}
/**
* 重试恢复方法
*/
@Recover
public BankResponse recoverFromBankFailure(BankTimeoutException ex, PaymentOrder order) {
log.error("银行接口重试失败,执行恢复逻辑: orderId={}", order.getOrderId());
// 1. 主动查询银行状态
try {
BankQueryResponse queryResponse = queryBankStatusWithRetry(order);
if (queryResponse.isSuccess()) {
return convertQueryToPaymentResponse(queryResponse);
}
} catch (Exception e) {
log.error("查询银行状态也失败: orderId={}", order.getOrderId(), e);
}
// 2. 如果查询也失败,标记为需要人工处理
return BankResponse.needManualProcess("重试和查询都失败,需要人工处理");
}
@Recover
public BankResponse recoverFromParseFailure(BankResponseParseException ex, PaymentOrder order) {
log.error("响应解析重试失败: orderId={}, rawResponse={}",
order.getOrderId(), ex.getRawResponse());
// 1. 尝试从原始响应中提取关键信息
BankResponse extractedResponse = extractCriticalInfo(ex.getRawResponse(), order);
if (extractedResponse != null && extractedResponse.hasDefinitiveResult()) {
return extractedResponse;
}
// 2. 主动查询确认
try {
BankQueryResponse queryResponse = queryBankStatusWithRetry(order);
return convertQueryToPaymentResponse(queryResponse);
} catch (Exception e) {
log.error("解析失败后查询也失败: orderId={}", order.getOrderId(), e);
return BankResponse.needManualProcess("解析失败且查询失败");
}
}
/**
* 带重试的银行查询
*/
@Retryable(
value = {BankTimeoutException.class, BankNetworkException.class},
maxAttempts = 5,
backoff = @Backoff(delay = 3000, multiplier = 1.5, maxDelay = 15000)
)
public BankQueryResponse queryBankStatusWithRetry(PaymentOrder order) {
log.info("查询银行订单状态: orderId={}, bankOrderId={}",
order.getOrderId(), order.getBankOrderId());
return bankQueryService.queryOrderStatus(order);
}
@Recover
public BankQueryResponse recoverFromQueryFailure(Exception ex, PaymentOrder order) {
log.error("银行查询重试失败: orderId={}", order.getOrderId(), ex);
// 查询失败,返回未知状态
BankQueryResponse response = new BankQueryResponse();
response.setSuccess(false);
response.setErrorMessage("查询重试失败: " + ex.getMessage());
return response;
}
}
6.3 自定义重试策略
@Component
public class BankSpecificRetryPolicy implements RetryPolicy {
private final Map<String, BankRetryConfig> bankRetryConfigs;
public BankSpecificRetryPolicy() {
this.bankRetryConfigs = initializeBankConfigs();
}
/**
* 初始化银行特定配置
*/
private Map<String, BankRetryConfig> initializeBankConfigs() {
Map<String, BankRetryConfig> configs = new HashMap<>();
// 工商银行配置
configs.put("ICBC", BankRetryConfig.builder()
.maxAttempts(3)
.initialDelay(2000)
.maxDelay(10000)
.multiplier(2.0)
.timeoutThreshold(30000)
.build());
// 建设银行配置
configs.put("CCB", BankRetryConfig.builder()
.maxAttempts(4)
.initialDelay(3000)
.maxDelay(15000)
.multiplier(1.5)
.timeoutThreshold(45000)
.build());
// 农业银行配置
configs.put("ABC", BankRetryConfig.builder()
.maxAttempts(2)
.initialDelay(5000)
.maxDelay(20000)
.multiplier(2.5)
.timeoutThreshold(60000)
.build());
return configs;
}
@Override
public boolean canRetry(RetryContext context) {
// 获取当前订单信息
PaymentOrder order = (PaymentOrder) context.getAttribute("paymentOrder");
if (order == null) {
return false;
}
// 获取银行特定配置
BankRetryConfig config = bankRetryConfigs.get(order.getBankCode());
if (config == null) {
config = getDefaultConfig();
}
// 检查重试次数
if (context.getRetryCount() >= config.getMaxAttempts()) {
return false;
}
// 检查异常类型
Throwable lastThrowable = context.getLastThrowable();
if (lastThrowable == null) {
return true;
}
// 业务异常不重试
if (lastThrowable instanceof BankBusinessException) {
return false;
}
// 技术异常可以重试
return isTechnicalException(lastThrowable);
}
@Override
public RetryContext open(RetryPolicy parent) {
return new SimpleRetryContext(parent);
}
@Override
public void close(RetryContext context) {
// 清理资源
}
@Override
public void registerThrowable(RetryContext context, Throwable throwable) {
context.registerThrowable(throwable);
// 记录重试统计
recordRetryMetrics(context, throwable);
}
/**
* 判断是否为技术异常
*/
private boolean isTechnicalException(Throwable throwable) {
return throwable instanceof SocketTimeoutException ||
throwable instanceof ConnectTimeoutException ||
throwable instanceof ConnectException ||
throwable instanceof BankTimeoutException ||
throwable instanceof BankNetworkException ||
throwable instanceof BankResponseParseException;
}
/**
* 记录重试指标
*/
private void recordRetryMetrics(RetryContext context, Throwable throwable) {
PaymentOrder order = (PaymentOrder) context.getAttribute("paymentOrder");
if (order != null) {
meterRegistry.counter("bank.retry.attempt",
"bank", order.getBankCode(),
"exception", throwable.getClass().getSimpleName(),
"attempt", String.valueOf(context.getRetryCount())
).increment();
}
}
}
@Data
@Builder
class BankRetryConfig {
private int maxAttempts; // 最大重试次数
private long initialDelay; // 初始延迟
private long maxDelay; // 最大延迟
private double multiplier; // 延迟倍数
private long timeoutThreshold; // 超时阈值
}
6.4 重试监听器
@Component
public class BankRetryListener implements RetryListener {
private static final Logger log = LoggerFactory.getLogger(BankRetryListener.class);
@Autowired
private MeterRegistry meterRegistry;
@Override
public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) {
PaymentOrder order = extractPaymentOrder(context);
if (order != null) {
log.info("开始重试: orderId={}, method={}",
order.getOrderId(), callback.getClass().getSimpleName());
// 记录重试开始
context.setAttribute("retryStartTime", System.currentTimeMillis());
}
return true;
}
@Override
public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
PaymentOrder order = extractPaymentOrder(context);
if (order != null) {
long startTime = (Long) context.getAttribute("retryStartTime");
long duration = System.currentTimeMillis() - startTime;
if (throwable == null) {
// 重试成功
log.info("重试成功: orderId={}, totalAttempts={}, duration={}ms",
order.getOrderId(), context.getRetryCount() + 1, duration);
meterRegistry.counter("bank.retry.success",
"bank", order.getBankCode(),
"attempts", String.valueOf(context.getRetryCount() + 1)
).increment();
} else {
// 重试失败
log.error("重试失败: orderId={}, totalAttempts={}, duration={}ms, finalError={}",
order.getOrderId(), context.getRetryCount() + 1, duration, throwable.getMessage());
meterRegistry.counter("bank.retry.failure",
"bank", order.getBankCode(),
"attempts", String.valueOf(context.getRetryCount() + 1),
"finalError", throwable.getClass().getSimpleName()
).increment();
}
// 记录重试耗时
meterRegistry.timer("bank.retry.duration",
"bank", order.getBankCode()
).record(duration, TimeUnit.MILLISECONDS);
}
}
@Override
public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
PaymentOrder order = extractPaymentOrder(context);
if (order != null) {
log.warn("重试失败: orderId={}, attempt={}, error={}",
order.getOrderId(), context.getRetryCount(), throwable.getMessage());
// 记录每次重试失败
meterRegistry.counter("bank.retry.attempt.failure",
"bank", order.getBankCode(),
"attempt", String.valueOf(context.getRetryCount()),
"error", throwable.getClass().getSimpleName()
).increment();
// 检查是否需要告警
checkRetryAlert(order, context, throwable);
}
}
/**
* 检查重试告警
*/
private void checkRetryAlert(PaymentOrder order, RetryContext context, Throwable throwable) {
// 如果重试次数过多,发送告警
if (context.getRetryCount() >= 2) {
alertService.sendAlert("银行接口重试次数过多",
String.format("订单: %s, 银行: %s, 重试次数: %d, 错误: %s",
order.getOrderId(), order.getBankCode(),
context.getRetryCount(), throwable.getMessage()));
}
// 如果是特定银行频繁失败,发送告警
double recentFailureRate = calculateRecentFailureRate(order.getBankCode());
if (recentFailureRate > 0.3) { // 30%
alertService.sendAlert("银行接口失败率过高",
String.format("银行: %s, 最近失败率: %.2f%%",
order.getBankCode(), recentFailureRate * 100));
}
}
private PaymentOrder extractPaymentOrder(RetryContext context) {
return (PaymentOrder) context.getAttribute("paymentOrder");
}
private double calculateRecentFailureRate(String bankCode) {
// 计算最近1小时的失败率
// 这里简化实现,实际应该从监控系统获取
return 0.0;
}
}
6.5 重试使用示例
@Service
public class PaymentService {
@Autowired
private BankInterfaceRetryService bankRetryService;
/**
* 处理支付请求
*/
public PaymentResult processPayment(PaymentRequest request) {
PaymentOrder order = createPaymentOrder(request);
try {
// 使用Spring Retry调用银行接口
BankResponse bankResponse = bankRetryService.callBankPaymentWithRetry(order);
// 处理银行响应
return handleBankResponse(order, bankResponse);
} catch (Exception e) {
log.error("支付处理失败: orderId={}", order.getOrderId(), e);
return PaymentResult.failed("支付处理失败: " + e.getMessage());
}
}
/**
* 使用RetryTemplate的编程式重试
*/
public BankResponse callBankWithTemplate(PaymentOrder order) {
return bankRetryTemplate.execute(context -> {
// 设置上下文信息
context.setAttribute("paymentOrder", order);
// 调用银行接口
return bankClient.payment(buildBankRequest(order));
}, context -> {
// 重试失败后的恢复逻辑
log.error("银行调用重试失败: orderId={}", order.getOrderId());
// 尝试查询确认
try {
BankQueryResponse queryResponse = bankQueryService.queryOrderStatus(order);
return convertQueryToPaymentResponse(queryResponse);
} catch (Exception e) {
return BankResponse.needManualProcess("调用和查询都失败");
}
});
}
}
6.6 重试配置优化
# application.yml
spring:
retry:
# 全局重试配置
max-attempts: 3
backoff:
delay: 1000
max-delay: 10000
multiplier: 2.0
# 银行特定配置
bank:
retry:
configs:
ICBC:
max-attempts: 3
initial-delay: 2000
max-delay: 10000
multiplier: 2.0
timeout-threshold: 30000
CCB:
max-attempts: 4
initial-delay: 3000
max-delay: 15000
multiplier: 1.5
timeout-threshold: 45000
ABC:
max-attempts: 2
initial-delay: 5000
max-delay: 20000
multiplier: 2.5
timeout-threshold: 60000
7. Spring Retry的优势
7.1 声明式重试
- 简洁性:通过注解即可实现重试逻辑
- 可读性:重试逻辑与业务逻辑分离
- 可维护性:统一的重试配置管理
7.2 灵活的重试策略
- 多种退避策略:固定间隔、指数退避、随机间隔
- 条件重试:基于异常类型的精确控制
- 恢复机制:重试失败后的优雅降级
7.3 完善的监控
- 重试监听器:全面的重试过程监控
- 指标收集:重试次数、成功率、耗时统计
- 告警机制:异常情况的及时通知
这种基于Spring Retry的实现方案既保持了代码的简洁性,又提供了强大的重试能力和完善的监控机制,是生产环境中银行接口调用的最佳实践。
/**
* JSON解析错误处理
*/
private BankResponse handleJsonParseError(String rawResponse, PaymentOrder order, JsonParseException e) {
// 1. 关键字检测
if (containsSuccessIndicator(rawResponse)) {
// 包含成功标识,主动查询确认
return triggerActiveQuery(order, "JSON_PARSE_ERROR_BUT_SUCCESS_INDICATOR");
}
if (containsFailureIndicator(rawResponse)) {
// 包含失败标识,判定为失败
return BankResponse.failed("响应格式错误但包含失败标识");
}
// 2. 响应长度检查
if (rawResponse.length() < 50) {
// 响应过短,可能是网络问题,可以重试
return BankResponse.needRetry("响应内容过短,疑似网络问题");
}
// 3. 无法判断,主动查询
return triggerActiveQuery(order, "JSON_PARSE_ERROR_UNKNOWN");
}
/**
* 关键字检测
*/
private boolean containsSuccessIndicator(String response) {
String[] successKeywords = {
"success", "成功", "00", "SUCCESS", "ACCEPTED",
"\"status\":\"00\"", "\"code\":\"0000\"", "\"result\":\"success\""
};
String lowerResponse = response.toLowerCase();
return Arrays.stream(successKeywords)
.anyMatch(keyword -> lowerResponse.contains(keyword.toLowerCase()));
}
private boolean containsFailureIndicator(String response) {
String[] failureKeywords = {
"fail", "error", "失败", "错误", "FAILED", "REJECTED",
"\"status\":\"01\"", "\"code\":\"9999\"", "\"result\":\"fail\""
};
String lowerResponse = response.toLowerCase();
return Arrays.stream(failureKeywords)
.anyMatch(keyword -> lowerResponse.contains(keyword.toLowerCase()));
}
}
### 3.3 重试决策矩阵
```java
@Component
public class RetryDecisionMatrix {
/**
* 重试决策表
*/
public RetryDecision shouldRetry(ParseFailureContext context) {
// 检查重试次数
if (context.getRetryCount() >= 3) {
return RetryDecision.NO_RETRY("超过最大重试次数");
}
// 分析响应内容
ResponseAnalysis analysis = analyzeResponse(context.getRawResponse());
switch (analysis.getType()) {
case PARTIAL_SUCCESS:
// 包含成功信息,查询而不重试
return RetryDecision.QUERY_INSTEAD("响应包含成功信息");
case PARTIAL_FAILURE:
// 包含失败信息,不重试
return RetryDecision.NO_RETRY("响应包含失败信息");
case NETWORK_ERROR:
// 网络错误,延迟重试
return RetryDecision.RETRY_AFTER_DELAY(5000);
case FORMAT_ERROR:
// 格式错误,查询确认
return RetryDecision.QUERY_INSTEAD("响应格式错误");
case EMPTY_RESPONSE:
// 空响应,可能是超时,重试
return RetryDecision.RETRY_AFTER_DELAY(3000);
default:
return RetryDecision.NO_RETRY("无法分析响应类型");
}
}
/**
* 响应内容分析
*/
private ResponseAnalysis analyzeResponse(String response) {
if (StringUtils.isBlank(response)) {
return new ResponseAnalysis(ResponseType.EMPTY_RESPONSE);
}
if (response.length() < 20) {
return new ResponseAnalysis(ResponseType.NETWORK_ERROR);
}
if (containsSuccessKeywords(response)) {
return new ResponseAnalysis(ResponseType.PARTIAL_SUCCESS);
}
if (containsFailureKeywords(response)) {
return new ResponseAnalysis(ResponseType.PARTIAL_FAILURE);
}
if (!isValidFormat(response)) {
return new ResponseAnalysis(ResponseType.FORMAT_ERROR);
}
return new ResponseAnalysis(ResponseType.UNKNOWN);
}
}
4. 场景三:"处理中"状态的轮询策略
4.1 处理中状态管理
银行返回"处理中"状态时,需要建立完善的轮询机制:
@Component
public class BankProcessingStatusHandler {
// 不同银行的处理配置
private final Map<String, ProcessingConfig> bankConfigs = Map.of(
"ICBC", new ProcessingConfig(30000, 300000, 5), // 工行:30s首查,5分钟超时,最多5次
"CCB", new ProcessingConfig(60000, 600000, 3), // 建行:60s首查,10分钟超时,最多3次
"ABC", new ProcessingConfig(45000, 450000, 4) // 农行:45s首查,7.5分钟超时,最多4次
);
/**
* 处理"处理中"状态
*/
public BankResponse handleProcessingStatus(PaymentOrder order, BankResponse processingResponse) {
// 1. 获取银行配置
ProcessingConfig config = getProcessingConfig(order.getBankCode());
// 2. 记录处理中状态
recordProcessingStatus(order, processingResponse, config);
// 3. 启动异步轮询
scheduleAsyncPolling(order, config);
// 4. 返回处理中响应
return BankResponse.processing(
processingResponse.getBankOrderId(),
"银行处理中,预计" + (config.getFirstQueryDelay() / 1000) + "秒后可查询结果"
);
}
/**
* 异步轮询任务
*/
@Async("bankQueryExecutor")
public void scheduleAsyncPolling(PaymentOrder order, ProcessingConfig config) {
int queryCount = 0;
long startTime = System.currentTimeMillis();
while (queryCount < config.getMaxQueryTimes()) {
try {
// 计算等待时间(指数退避)
long delay = calculateQueryDelay(queryCount, config);
Thread.sleep(delay);
// 执行查询
BankQueryResponse queryResponse = bankQueryService.queryOrderStatus(order);
queryCount++;
if (queryResponse.isSuccess()) {
switch (queryResponse.getOrderStatus()) {
case SUCCESS:
handleAsyncQuerySuccess(order, queryResponse);
return;
case FAILED:
handleAsyncQueryFailure(order, queryResponse);
return;
case PROCESSING:
// 继续等待
log.debug("银行订单仍在处理: orderId={}, queryCount={}",
order.getOrderId(), queryCount);
break;
}
}
// 检查总超时
if (System.currentTimeMillis() - startTime > config.getMaxWaitTime()) {
handleQueryTimeout(order, queryCount);
return;
}
} catch (Exception e) {
log.error("轮询查询异常: orderId={}, queryCount={}", order.getOrderId(), queryCount, e);
if (queryCount >= config.getMaxQueryTimes() - 1) {
handleQueryException(order, e);
return;
}
}
}
// 达到最大查询次数
handleMaxQueryReached(order, queryCount);
}
/**
* 计算查询延迟(指数退避 + 银行特性)
*/
private long calculateQueryDelay(int queryCount, ProcessingConfig config) {
if (queryCount == 0) {
return config.getFirstQueryDelay();
}
// 指数退避:30s, 60s, 120s, 240s...
long exponentialDelay = config.getFirstQueryDelay() * (1L << queryCount);
// 最大不超过5分钟
return Math.min(exponentialDelay, 300000);
}
}
@Data
@AllArgsConstructor
class ProcessingConfig {
private final long firstQueryDelay; // 首次查询延迟
private final long maxWaitTime; // 最大等待时间
private final int maxQueryTimes; // 最大查询次数
}
4.2 轮询策略优化
智能间隔调整
- 首次查询:根据银行特性设定(30-60秒)
- 后续查询:指数退避,但不超过5分钟
- 最大次数:根据银行SLA设定(3-5次)
超时处理
- 总超时:10分钟后强制结束轮询
- 单次超时:30秒查询超时
- 异常处理:连续3次查询失败转人工处理
5. 场景四:业务失败与技术失败的区分
5.1 失败类型分类
准确区分业务失败和技术失败是支付系统的关键能力:
@Component
public class BankFailureClassifier {
/**
* 失败类型分类
*/
public FailureType classifyFailure(BankResponse response, Exception exception) {
// 1. 技术失败检查
if (isTechnicalFailure(response, exception)) {
return FailureType.TECHNICAL_FAILURE;
}
// 2. 业务失败检查
if (isBusinessFailure(response)) {
return FailureType.BUSINESS_FAILURE;
}
// 3. 未知失败
return FailureType.UNKNOWN_FAILURE;
}
/**
* 技术失败判断
*/
private boolean isTechnicalFailure(BankResponse response, Exception exception) {
// 网络层异常
if (exception instanceof ConnectTimeoutException ||
exception instanceof SocketTimeoutException ||
exception instanceof ConnectException ||
exception instanceof UnknownHostException) {
return true;
}
// HTTP状态码异常
if (response != null && isTechnicalHttpStatus(response.getHttpStatus())) {
return true;
}
// 银行系统错误码
if (response != null && isTechnicalErrorCode(response.getErrorCode())) {
return true;
}
return false;
}
/**
* 业务失败判断
*/
private boolean isBusinessFailure(BankResponse response) {
if (response == null) {
return false;
}
return isBusinessErrorCode(response.getErrorCode());
}
/**
* 技术错误码映射表
*/
private boolean isTechnicalErrorCode(String errorCode) {
Map<String, String> technicalErrorCodes = Map.of(
"9999", "系统异常",
"8888", "网络异常",
"7777", "数据库异常",
"6666", "服务不可用",
"5555", "系统维护中",
"TIMEOUT", "请求超时",
"NETWORK_ERROR", "网络错误",
"SYSTEM_ERROR", "系统错误",
"SERVICE_UNAVAILABLE", "服务不可用"
);
return technicalErrorCodes.containsKey(errorCode);
}
/**
* 业务错误码映射表
*/
private boolean isBusinessErrorCode(String errorCode) {
Map<String, String> businessErrorCodes = Map.of(
"1001", "余额不足",
"1002", "账户状态异常",
"1003", "密码错误",
"1004", "卡片状态异常",
"1005", "超过限额",
"1006", "账户冻结",
"1007", "交易时间限制",
"INSUFFICIENT_BALANCE", "余额不足",
"INVALID_ACCOUNT", "无效账户",
"EXCEED_LIMIT", "超过限额",
"ACCOUNT_FROZEN", "账户冻结"
);
return businessErrorCodes.containsKey(errorCode);
}
/**
* HTTP状态码判断
*/
private boolean isTechnicalHttpStatus(int httpStatus) {
// 5xx服务器错误 - 技术问题
if (httpStatus >= 500 && httpStatus < 600) {
return true;
}
// 特定的4xx错误 - 技术问题
Set<Integer> technicalClientErrors = Set.of(
408, // Request Timeout
429, // Too Many Requests
502, // Bad Gateway
503, // Service Unavailable
504 // Gateway Timeout
);
return technicalClientErrors.contains(httpStatus);
}
}
enum FailureType {
TECHNICAL_FAILURE, // 技术失败
BUSINESS_FAILURE, // 业务失败
UNKNOWN_FAILURE // 未知失败
}
5.2 不同失败类型的处理策略
@Service
public class BankFailureHandler {
/**
* 根据失败类型处理
*/
public PaymentResult handleBankFailure(PaymentOrder order, BankResponse response, Exception exception) {
FailureType failureType = failureClassifier.classifyFailure(response, exception);
switch (failureType) {
case TECHNICAL_FAILURE:
return handleTechnicalFailure(order, response, exception);
case BUSINESS_FAILURE:
return handleBusinessFailure(order, response);
case UNKNOWN_FAILURE:
return handleUnknownFailure(order, response, exception);
default:
throw new IllegalStateException("未知失败类型: " + failureType);
}
}
/**
* 技术失败处理策略
*/
private PaymentResult handleTechnicalFailure(PaymentOrder order, BankResponse response, Exception exception) {
log.warn("银行技术失败: orderId={}, error={}", order.getOrderId(), exception.getMessage());
// 1. 记录技术失败事件
recordTechnicalFailure(order, response, exception);
// 2. 判断重试策略
RetryStrategy retryStrategy = determineRetryStrategy(order, exception);
switch (retryStrategy) {
case IMMEDIATE_RETRY:
return retryImmediately(order);
case DELAYED_RETRY:
return scheduleDelayedRetry(order, calculateRetryDelay(order.getRetryCount()));
case QUERY_FIRST:
return queryBeforeRetry(order);
case NO_RETRY:
return markAsNeedManualProcess(order, "技术失败无法重试");
default:
throw new IllegalStateException("未知重试策略: " + retryStrategy);
}
}
/**
* 业务失败处理策略
*/
private PaymentResult handleBusinessFailure(PaymentOrder order, BankResponse response) {
log.info("银行业务失败: orderId={}, errorCode={}, errorMsg={}",
order.getOrderId(), response.getErrorCode(), response.getErrorMessage());
// 1. 记录业务失败
recordBusinessFailure(order, response);
// 2. 分析业务失败类型
BusinessFailureType businessFailureType = analyzeBusinessFailure(response);
switch (businessFailureType) {
case INSUFFICIENT_BALANCE:
// 余额不足 - 直接失败,不重试
return PaymentResult.failed("余额不足", response.getErrorCode());
case ACCOUNT_FROZEN:
// 账户冻结 - 直接失败,不重试
return PaymentResult.failed("账户冻结", response.getErrorCode());
case EXCEED_LIMIT:
// 超过限额 - 可能是临时限制,可以稍后重试
return scheduleDelayedRetry(order, 300000); // 5分钟后重试
case INVALID_PASSWORD:
// 密码错误 - 直接失败,不重试
return PaymentResult.failed("密码错误", response.getErrorCode());
case TIME_RESTRICTION:
// 时间限制 - 等待到允许时间后重试
return scheduleTimeBasedRetry(order);
default:
// 其他业务失败 - 直接失败
return PaymentResult.failed(response.getErrorMessage(), response.getErrorCode());
}
}
/**
* 未知失败处理策略
*/
private PaymentResult handleUnknownFailure(PaymentOrder order, BankResponse response, Exception exception) {
log.error("银行未知失败: orderId={}, response={}, exception={}",
order.getOrderId(), response, exception.getMessage());
// 1. 记录未知失败
recordUnknownFailure(order, response, exception);
// 2. 主动查询确认状态
try {
BankQueryResponse queryResponse = bankQueryService.queryOrderStatus(order);
if (queryResponse.isSuccess()) {
return convertQueryToPaymentResult(queryResponse);
} else {
// 查询也失败,转人工处理
return markAsNeedManualProcess(order, "未知失败且查询失败");
}
} catch (Exception e) {
log.error("查询银行状态失败: orderId={}", order.getOrderId(), e);
return markAsNeedManualProcess(order, "未知失败且查询异常");
}
}
/**
* 重试策略决策
*/
private RetryStrategy determineRetryStrategy(PaymentOrder order, Exception exception) {
// 检查重试次数
if (order.getRetryCount() >= 3) {
return RetryStrategy.NO_RETRY;
}
// 根据异常类型决策
if (exception instanceof ConnectTimeoutException) {
return RetryStrategy.QUERY_FIRST; // 连接超时,先查询
}
if (exception instanceof SocketTimeoutException) {
return RetryStrategy.QUERY_FIRST; // 读取超时,先查询
}
if (exception instanceof ConnectException) {
return RetryStrategy.DELAYED_RETRY; // 连接失败,延迟重试
}
if (exception instanceof UnknownHostException) {
return RetryStrategy.NO_RETRY; // DNS解析失败,不重试
}
return RetryStrategy.DELAYED_RETRY; // 默认延迟重试
}
}
enum RetryStrategy {
IMMEDIATE_RETRY, // 立即重试
DELAYED_RETRY, // 延迟重试
QUERY_FIRST, // 先查询再决定
NO_RETRY // 不重试
}
enum BusinessFailureType {
INSUFFICIENT_BALANCE, // 余额不足
ACCOUNT_FROZEN, // 账户冻结
EXCEED_LIMIT, // 超过限额
INVALID_PASSWORD, // 密码错误
TIME_RESTRICTION, // 时间限制
OTHER // 其他
}
6. 综合处理框架
6.1 统一异常处理器
@Component
public class BankInterfaceExceptionHandler {
/**
* 统一异常处理入口
*/
public PaymentResult handleBankException(PaymentOrder order, Exception exception, BankResponse response) {
try {
// 1. 记录异常事件
recordExceptionEvent(order, exception, response);
// 2. 分类处理
if (exception instanceof BankTimeoutException) {
return timeoutRetryHandler.handleTimeoutRetry(order, (BankTimeoutException) exception);
}
if (exception instanceof BankResponseParseException) {
return responseParseHandler.handleParseFailure(order, (BankResponseParseException) exception);
}
if (response != null && "PROCESSING".equals(response.getStatus())) {
return processingStatusHandler.handleProcessingStatus(order, response);
}
// 3. 通用失败处理
return bankFailureHandler.handleBankFailure(order, response, exception);
} catch (Exception e) {
log.error("异常处理器本身异常: orderId={}", order.getOrderId(), e);
return PaymentResult.needManualProcess("异常处理失败,需要人工介入");
}
}
}
6.2 监控和告警
@Component
public class BankInterfaceMonitor {
/**
* 记录银行接口指标
*/
public void recordBankInterfaceMetrics(PaymentOrder order, BankResponse response, Exception exception, long duration) {
String bankCode = order.getBankCode();
String result = determineResult(response, exception);
// 1. 记录调用次数和耗时
meterRegistry.timer("bank.interface.call",
"bank", bankCode,
"result", result
).record(duration, TimeUnit.MILLISECONDS);
// 2. 记录成功率
meterRegistry.counter("bank.interface.result",
"bank", bankCode,
"result", result
).increment();
// 3. 记录异常类型
if (exception != null) {
meterRegistry.counter("bank.interface.exception",
"bank", bankCode,
"exception_type", exception.getClass().getSimpleName()
).increment();
}
// 4. 检查告警条件
checkAlertConditions(bankCode, result, duration);
}
/**
* 告警检查
*/
private void checkAlertConditions(String bankCode, String result, long duration) {
// 单次调用耗时过长
if (duration > 30000) { // 30秒
alertService.sendAlert("银行接口调用耗时过长",
String.format("银行: %s, 耗时: %d ms", bankCode, duration));
}
// 失败率过高检查(需要结合历史数据)
double failureRate = calculateRecentFailureRate(bankCode);
if (failureRate > 0.1) { // 10%
alertService.sendAlert("银行接口失败率过高",
String.format("银行: %s, 失败率: %.2f%%", bankCode, failureRate * 100));
}
}
}
7. 最佳实践总结
7.1 设计原则
- 安全优先:宁可多查询确认,不可遗漏处理
- 幂等保证:所有操作都要保证幂等性
- 主动确认:不依赖银行的主动通知
- 分类处理:技术问题和业务问题区别对待
- 监控完善:全面的监控和告警机制
7.2 关键技术点
- 超时处理:智能等待 + 主动查询 + 新订单号重试
- 解析失败:关键字检测 + 重试决策 + 主动查询
- 处理中状态:异步轮询 + 指数退避 + 超时控制
- 失败分类:错误码映射 + 异常类型判断 + 差异化处理
7.3 运维要点
- 监控指标:成功率、耗时、异常类型、重试次数
- 告警规则:失败率阈值、耗时阈值、异常频率
- 日志记录:完整的调用链路和状态变更
- 人工介入:复杂场景的人工处理机制
8. 总结
银行接口调用的复杂场景处理是支付系统的核心技术挑战。通过建立完善的分类处理机制、智能重试策略、主动查询确认和全面监控告警,可以有效应对各种异常情况,确保支付系统的稳定性和资金安全。
在实际应用中,需要根据具体的银行特性、业务需求和系统架构进行适当调整,持续优化处理策略,提升系统的健壮性和用户体验。