Compare commits
2 Commits
8eaaa501bc
...
9e0729f6f5
| Author | SHA1 | Date |
|---|---|---|
|
|
9e0729f6f5 | |
|
|
ba38fbf0cc |
|
|
@ -1,17 +1,31 @@
|
||||||
package com.lyxh.api.docking.exception;
|
package com.lyxh.api.docking.exception;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import java.time.Instant;
|
import org.apache.commons.codec.digest.DigestUtils;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public class ErrorResponse {
|
public class ErrorResponse {
|
||||||
private final String error;
|
|
||||||
private final String message;
|
|
||||||
private final String timestamp;
|
|
||||||
|
|
||||||
public ErrorResponse(String error, String message) {
|
private final String code;
|
||||||
this.error = error;
|
private final String message;
|
||||||
|
private final String responseId;
|
||||||
|
private final DataNode data;
|
||||||
|
|
||||||
|
public ErrorResponse(String code, String message) {
|
||||||
|
this.code = code;
|
||||||
this.message = message;
|
this.message = message;
|
||||||
this.timestamp = Instant.now().toString();
|
this.responseId = DigestUtils.md5Hex(UUID.randomUUID().toString());
|
||||||
|
this.data = new DataNode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class DataNode {
|
||||||
|
private final int status = 0;
|
||||||
|
|
||||||
|
@JsonProperty("result")
|
||||||
|
private final Object result = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ public class GlobalExceptionHandler {
|
||||||
.reduce((a, b) -> a + "; " + b)
|
.reduce((a, b) -> a + "; " + b)
|
||||||
.orElse("validation failed");
|
.orElse("validation failed");
|
||||||
return ResponseEntity.badRequest()
|
return ResponseEntity.badRequest()
|
||||||
.body(new ErrorResponse("VALIDATION_ERROR", msg));
|
.body(new ErrorResponse("400", msg));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ExceptionHandler(ApiCallException.class)
|
@ExceptionHandler(ApiCallException.class)
|
||||||
|
|
@ -34,13 +34,13 @@ public class GlobalExceptionHandler {
|
||||||
String detail = ex.getResponseBody() != null && !ex.getResponseBody().isEmpty()
|
String detail = ex.getResponseBody() != null && !ex.getResponseBody().isEmpty()
|
||||||
? ex.getResponseBody() : ex.getMessage();
|
? ex.getResponseBody() : ex.getMessage();
|
||||||
return ResponseEntity.status(status)
|
return ResponseEntity.status(status)
|
||||||
.body(new ErrorResponse(ex.getCode(), detail));
|
.body(new ErrorResponse(String.valueOf(status.value()), detail));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ExceptionHandler(Exception.class)
|
@ExceptionHandler(Exception.class)
|
||||||
public ResponseEntity<ErrorResponse> handleGeneral(Exception ex) {
|
public ResponseEntity<ErrorResponse> handleGeneral(Exception ex) {
|
||||||
log.error("Unexpected error", ex);
|
log.error("Unexpected error", ex);
|
||||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||||
.body(new ErrorResponse("INTERNAL_ERROR", "Internal server error"));
|
.body(new ErrorResponse("500", "Internal server error"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,13 @@
|
||||||
package com.lyxh.api.docking.service;
|
package com.lyxh.api.docking.service;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
import com.lyxh.api.docking.client.ApiCallResult;
|
import com.lyxh.api.docking.client.ApiCallResult;
|
||||||
import com.lyxh.api.docking.client.GenericApiClient;
|
import com.lyxh.api.docking.client.GenericApiClient;
|
||||||
import com.lyxh.api.docking.entity.ApiCacheData;
|
import com.lyxh.api.docking.entity.ApiCacheData;
|
||||||
import com.lyxh.api.docking.entity.ThirdPartyConfig;
|
import com.lyxh.api.docking.entity.ThirdPartyConfig;
|
||||||
|
import org.apache.commons.codec.digest.DigestUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
@ -11,6 +15,7 @@ import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.UUID;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -32,6 +37,9 @@ public class GenericApiService {
|
||||||
@Autowired
|
@Autowired
|
||||||
private ApiInvokeLogService invokeLogService;
|
private ApiInvokeLogService invokeLogService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ObjectMapper objectMapper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 执行第三方 API 调用(含缓存、日志),返回响应体原始 JSON
|
* 执行第三方 API 调用(含缓存、日志),返回响应体原始 JSON
|
||||||
*
|
*
|
||||||
|
|
@ -65,7 +73,19 @@ public class GenericApiService {
|
||||||
ApiCacheData cached = cacheDataService.getNonExpiredCache(apiCode, cacheKey, config.getId());
|
ApiCacheData cached = cacheDataService.getNonExpiredCache(apiCode, cacheKey, config.getId());
|
||||||
if (cached != null) {
|
if (cached != null) {
|
||||||
log.info("Cache hit: weOrderNo={} apiCode={} cacheKey={}", weOrderNo, apiCode, cacheKey);
|
log.info("Cache hit: weOrderNo={} apiCode={} cacheKey={}", weOrderNo, apiCode, cacheKey);
|
||||||
return cached.getCacheValue();
|
// 命中缓存,更新 responseId
|
||||||
|
String cacheValue = cached.getCacheValue();
|
||||||
|
try {
|
||||||
|
JsonNode root = objectMapper.readTree(cacheValue);
|
||||||
|
if (root.has("responseId")) {
|
||||||
|
String newResponseId = DigestUtils.md5Hex(UUID.randomUUID().toString());
|
||||||
|
((ObjectNode) root).put("responseId", newResponseId);
|
||||||
|
return objectMapper.writeValueAsString(root);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("Failed to replace responseId in cached response: weOrderNo={}", weOrderNo, e);
|
||||||
|
}
|
||||||
|
return cacheValue;
|
||||||
}
|
}
|
||||||
log.info("Cache miss: weOrderNo={} apiCode={} cacheKey={}", weOrderNo, apiCode, cacheKey);
|
log.info("Cache miss: weOrderNo={} apiCode={} cacheKey={}", weOrderNo, apiCode, cacheKey);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
24
对接接口文档.md
24
对接接口文档.md
|
|
@ -105,22 +105,26 @@
|
||||||
|
|
||||||
## 错误响应
|
## 错误响应
|
||||||
|
|
||||||
统一错误格式:
|
错误响应与三方接口响应保持相同结构,其中`data` 中 `status=0`、`result=null`:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"error": "ERROR_CODE",
|
"code": "503",
|
||||||
"message": "错误描述",
|
"message": "External API unreachable after 1 attempts",
|
||||||
"timestamp": "2026-07-08T10:30:00Z"
|
"responseId": "a1b2c3d4e5f6789012345678abcdef01",
|
||||||
|
"data": {
|
||||||
|
"status": 0,
|
||||||
|
"result": null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
| HTTP状态码 | error | 说明 |
|
| HTTP状态码 | code | 说明 |
|
||||||
|---|---|------------------|
|
|---|---|---|
|
||||||
| 400 | VALIDATION_ERROR | 请求参数校验失败 |
|
| 400 | 400 | 请求参数校验失败 |
|
||||||
| 502 | HTTP_ERROR | 三方 API HTTP 错误 |
|
| 502 | 502 | 三方 API HTTP 错误 |
|
||||||
| 503 | NETWORK_ERROR | 三方 API 网络不可达 |
|
| 503 | 503 | 三方 API 网络不可达 |
|
||||||
| 500 | INTERNAL_ERROR | 服务内部未知异常 api配置信息未找到 |
|
| 500 | 500 | 服务内部未知异常 |
|
||||||
|
|
||||||
## 完整调用示例
|
## 完整调用示例
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue