一、核心设计原则
高可用:内置重试、熔断、限流机制,处理网络抖动、API 限流、服务降级;
线程安全:工具类无状态设计,适配多线程并发调用;
可扩展:支持自定义请求参数、响应解析规则、异常处理策略;
性能优化:使用 HTTP 连接池、异步调用,提升高并发下的响应效率;
易维护:分层设计(签名层、请求层、解析层、异常层),便于扩展和调试。
二、环境准备与依赖配置
1. Maven 核心依赖
<dependencies> <!-- HTTP客户端(高性能、连接池) --> <dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> <version>4.12.0</version> </dependency> <!-- JSON解析(Jackson) --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.16.1</version> </dependency> <!-- 熔断/限流(Resilience4j) --> <dependency> <groupId>io.github.resilience4j</groupId> <artifactId>resilience4j-circuitbreaker</artifactId> <version>2.1.0</version> </dependency> <dependency> <groupId>io.github.resilience4j</groupId> <artifactId>resilience4j-ratelimiter</artifactId> <version>2.1.0</version> </dependency> <dependency> <groupId>io.github.resilience4j</groupId> <artifactId>resilience4j-retry</artifactId> <version>2.1.0</version> </dependency> <!-- 日志(SLF4J) --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>2.0.9</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.4.11</version> </dependency> <!-- 配置读取(可选) --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.30</version> <optional>true</optional> </dependency></dependencies>
三、核心代码实现
1. 配置类(参数统一管理)
import lombok.Data;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.stereotype.Component;/**
* 苏宁API配置类(支持Spring Boot配置文件注入)
*/@Data@Component@ConfigurationProperties(prefix = "suning.api")public class SuningApiConfig {
/**
* 应用Key
*/
private String appKey;
/**
* 应用秘钥
*/
private String appSecret;
/**
* API请求地址
*/
private String apiUrl = "https://open.suning.com/api/http/sopRequest";
/**
* 商品详情API方法名
*/
private String productDetailMethod = "suning.custom.product.get";
/**
* 请求超时时间(毫秒)
*/
private int timeout = 15000;
/**
* 最大重试次数
*/
private int retryTimes = 3;
/**
* 限流次数(每分钟)
*/
private int rateLimitPerMinute = 100;
/**
* 熔断阈值(失败率>50%触发熔断)
*/
private float circuitBreakerFailureRate = 0.5f;
/**
* 熔断恢复时间(秒)
*/
private int circuitBreakerWaitDuration = 60;}2. 核心模型类(响应解析)
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;import lombok.Data;import java.util.List;/**
* 苏宁API公共响应模型
*/@Data@JsonIgnoreProperties(ignoreUnknown = true)public class SuningApiResponse<T> {
/**
* 状态码(0000=成功)
*/
private String code;
/**
* 提示信息
*/
private String msg;
/**
* 业务数据体
*/
private T body;}/**
* 商品详情响应体
*/@Data@JsonIgnoreProperties(ignoreUnknown = true)public class ProductDetailBody {
/**
* 商品基础信息
*/
private ProductInfo productInfo;
/**
* 价格信息
*/
private PriceInfo priceInfo;
/**
* 库存信息
*/
private StockInfo stockInfo;
/**
* SKU信息
*/
private SkuInfo skuInfo;}/**
* 商品基础信息
*/@Data@JsonIgnoreProperties(ignoreUnknown = true)public class ProductInfo {
private String productCode; // 商品ID
private String productName; // 商品名称
private String brandName; // 品牌名称
private String categoryName;// 类目名称
private String mainImageUrl;// 主图URL}/**
* 价格信息
*/@Data@JsonIgnoreProperties(ignoreUnknown = true)public class PriceInfo {
private Double salePrice; // 售价
private Double originalPrice;// 原价
private Double memberPrice; // 会员价}/**
* 库存信息
*/@Data@JsonIgnoreProperties(ignoreUnknown = true)public class StockInfo {
private String stockCount; // 库存数(可能为"200+"格式)
private String stockStatus; // 库存状态(1=有货,0=无货)}/**
* SKU信息
*/@Data@JsonIgnoreProperties(ignoreUnknown = true)public class SkuInfo {
private List<SkuItem> skuList; // SKU列表}/**
* SKU项
*/@Data@JsonIgnoreProperties(ignoreUnknown = true)public class SkuItem {
private String skuCode; // SKU编码
private String skuAttr; // SKU属性
private Double salePrice; // SKU售价}3. 工具类核心实现(高可用 + 并发适配)
import com.fasterxml.jackson.core.JsonProcessingException;import com.fasterxml.jackson.databind.ObjectMapper;import io.github.resilience4j.circuitbreaker.CircuitBreaker;import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;import io.github.resilience4j.ratelimiter.RateLimiter;import io.github.resilience4j.ratelimiter.RateLimiterConfig;import io.github.resilience4j.retry.Retry;import io.github.resilience4j.retry.RetryConfig;import okhttp3.*;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Component;import javax.annotation.PostConstruct;import java.nio.charset.StandardCharsets;import java.security.MessageDigest;import java.security.NoSuchAlgorithmException;import java.time.Duration;import java.util.*;import java.util.concurrent.TimeUnit;/**
* 苏宁商品详情API工具类(高可用、线程安全)
*/@Componentpublic class SuningProductDetailApiUtil {
private static final Logger log = LoggerFactory.getLogger(SuningProductDetailApiUtil.class);
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private static final MediaType FORM_DATA = MediaType.parse("application/x-www-form-urlencoded;charset=utf-8");
@Autowired
private SuningApiConfig suningApiConfig;
// OKHTTP客户端(连接池,线程安全)
private OkHttpClient okHttpClient;
// 重试组件
private Retry retry;
// 限流组件
private RateLimiter rateLimiter;
// 熔断组件
private CircuitBreaker circuitBreaker;
/**
* 初始化:创建HTTP客户端、重试、限流、熔断组件
*/
@PostConstruct
public void init() {
// 1. 初始化OKHTTP连接池(高并发优化)
okHttpClient = new OkHttpClient.Builder()
.connectTimeout(suningApiConfig.getTimeout(), TimeUnit.MILLISECONDS)
.readTimeout(suningApiConfig.getTimeout(), TimeUnit.MILLISECONDS)
.writeTimeout(suningApiConfig.getTimeout(), TimeUnit.MILLISECONDS)
.connectionPool(new ConnectionPool(10, 5, TimeUnit.MINUTES)) // 连接池大小10,空闲5分钟
.retryOnConnectionFailure(false) // 关闭默认重试,使用Resilience4j重试
.build();
// 2. 初始化重试配置
RetryConfig retryConfig = RetryConfig.custom()
.maxAttempts(suningApiConfig.getRetryTimes())
.waitDuration(Duration.ofMillis(1000)) // 初始重试间隔1秒
.exponentialBackoff(2, 5000) // 指数退避(2倍递增,最大5秒)
.retryExceptions(
java.net.SocketTimeoutException.class,
java.net.ConnectException.class,
okhttp3.internal.http2.StreamResetException.class
) // 仅重试网络异常
.ignoreExceptions(
SuningApiBusinessException.class, // 业务异常(如签名错误)不重试
SuningApiInvalidParamException.class // 参数错误不重试
)
.build();
retry = Retry.of("suningProductDetailApi", retryConfig);
// 3. 初始化限流配置(100次/分钟)
RateLimiterConfig rateLimiterConfig = RateLimiterConfig.custom()
.limitRefreshPeriod(Duration.ofMinutes(1))
.limitForPeriod(suningApiConfig.getRateLimitPerMinute())
.timeoutDuration(Duration.ofSeconds(1)) // 获取令牌超时1秒
.build();
rateLimiter = RateLimiter.of("suningProductDetailApi", rateLimiterConfig);
// 4. 初始化熔断配置
CircuitBreakerConfig circuitBreakerConfig = CircuitBreakerConfig.custom()
.failureRateThreshold(suningApiConfig.getCircuitBreakerFailureRate() * 100) // 失败率阈值(百分比)
.waitDurationInOpenState(Duration.ofSeconds(suningApiConfig.getCircuitBreakerWaitDuration())) // 熔断恢复时间
.slidingWindowSize(20) // 滑动窗口大小20
.permittedNumberOfCallsInHalfOpenState(5) // 半开状态允许5次调用
.recordExceptions(
java.net.SocketTimeoutException.class,
java.net.ConnectException.class,
SuningApiSystemException.class // 系统异常计入失败率
)
.ignoreExceptions(
SuningApiBusinessException.class,
SuningApiInvalidParamException.class
)
.build();
circuitBreaker = CircuitBreaker.of("suningProductDetailApi", circuitBreakerConfig);
}
/**
* 生成苏宁API签名(MD5)
* @param params 请求参数(不含sign)
* @return 签名字符串
*/
private String generateSign(Map<String, String> params) {
try {
// 1. 按参数名ASCII升序排序
List<Map.Entry<String, String>> sortedParams = new ArrayList<>(params.entrySet());
sortedParams.sort(Comparator.comparing(Map.Entry::getKey));
// 2. 拼接参数字符串
StringBuilder paramStr = new StringBuilder();
for (Map.Entry<String, String> entry : sortedParams) {
paramStr.append(entry.getKey()).append(entry.getValue());
}
// 3. 首尾拼接appSecret并MD5加密
String signStr = suningApiConfig.getAppSecret() + paramStr + suningApiConfig.getAppSecret();
MessageDigest md5 = MessageDigest.getInstance("MD5");
byte[] digest = md5.digest(signStr.getBytes(StandardCharsets.UTF_8));
// 4. 转大写十六进制
StringBuilder sign = new StringBuilder();
for (byte b : digest) {
sign.append(String.format("%02X", b));
}
return sign.toString();
} catch (NoSuchAlgorithmException e) {
throw new SuningApiSystemException("生成签名失败:不支持MD5算法", e);
}
}
/**
* 构造请求参数
* @param productCode 商品ID
* @return 完整请求参数(含签名)
*/
private Map<String, String> buildRequestParams(String productCode) {
if (productCode == null || productCode.trim().isEmpty()) {
throw new SuningApiInvalidParamException("商品ID不能为空");
}
Map<String, String> params = new HashMap<>();
// 基础参数
params.put("appKey", suningApiConfig.getAppKey());
params.put("timestamp", String.valueOf(System.currentTimeMillis())); // 毫秒级时间戳
params.put("format", "json");
params.put("version", "v1.0");
params.put("method", suningApiConfig.getProductDetailMethod());
params.put("productCode", productCode);
params.put("signMethod", "md5");
// 生成签名
params.put("sign", generateSign(params));
return params;
}
/**
* 转换参数为FormBody
* @param params 参数Map
* @return FormBody
*/
private FormBody buildFormBody(Map<String, String> params) {
FormBody.Builder builder = new FormBody.Builder(StandardCharsets.UTF_8);
for (Map.Entry<String, String> entry : params.entrySet()) {
builder.add(entry.getKey(), entry.getValue());
}
return builder.build();
}
/**
* 调用苏宁商品详情API(同步)
* @param productCode 商品ID
* @return 商品详情数据
*/
public ProductDetailBody getProductDetail(String productCode) {
// 组合限流+熔断+重试
return RateLimiter.decorateSupplier(rateLimiter,
CircuitBreaker.decorateSupplier(circuitBreaker,
Retry.decorateSupplier(retry,
() -> doGetProductDetail(productCode)
)
)
).get();
}
/**
* 异步调用苏宁商品详情API(高并发场景)
* @param productCode 商品ID
* @return Callable<ProductDetailBody>
*/
public java.util.concurrent.Callable<ProductDetailBody> getProductDetailAsync(String productCode) {
return () -> getProductDetail(productCode);
}
/**
* 核心调用逻辑
*/
private ProductDetailBody doGetProductDetail(String productCode) {
long startTime = System.currentTimeMillis();
Map<String, String> params = buildRequestParams(productCode);
FormBody formBody = buildFormBody(params);
// 构建请求
Request request = new Request.Builder()
.url(suningApiConfig.getApiUrl())
.post(formBody)
.build();
try (Response response = okHttpClient.newCall(request).execute()) {
// 1. 校验响应状态
if (!response.isSuccessful()) {
log.error("苏宁API调用失败,HTTP状态码:{},商品ID:{}", response.code(), productCode);
if (response.code() == 429) {
throw new SuningApiRateLimitException("API调用触发限流(429),商品ID:" + productCode);
} else if (response.code() == 401) {
throw new SuningApiAuthException("签名错误/权限不足(401),商品ID:" + productCode);
}
throw new SuningApiSystemException(String.format("HTTP请求失败,状态码:%d,商品ID:%s", response.code(), productCode));
}
// 2. 解析响应体
String responseBody = response.body().string();
log.debug("苏宁API响应:{},商品ID:{},耗时:{}ms",
responseBody.length() > 500 ? responseBody.substring(0, 500) + "..." : responseBody,
productCode,
System.currentTimeMillis() - startTime);
SuningApiResponse<ProductDetailBody> apiResponse = OBJECT_MAPPER.readValue(responseBody,
OBJECT_MAPPER.getTypeFactory().constructParametricType(SuningApiResponse.class, ProductDetailBody.class));
// 3. 校验业务状态码
if (!"0000".equals(apiResponse.getCode())) {
log.error("苏宁API业务错误,错误码:{},错误信息:{},商品ID:{}",
apiResponse.getCode(), apiResponse.getMsg(), productCode);
throw new SuningApiBusinessException(String.format("业务调用失败:%s(%s),商品ID:%s",
apiResponse.getMsg(), apiResponse.getCode(), productCode));
}
// 4. 返回业务数据
ProductDetailBody productDetail = apiResponse.getBody();
if (productDetail == null) {
throw new SuningApiBusinessException("商品详情数据为空,商品ID:" + productCode);
}
return productDetail;
} catch (JsonProcessingException e) {
throw new SuningApiSystemException("JSON解析失败,商品ID:" + productCode, e);
} catch (Exception e) {
// 包装未捕获的异常
if (e instanceof SuningApiException) {
throw (SuningApiException) e;
} else {
throw new SuningApiSystemException("API调用未知异常,商品ID:" + productCode, e);
}
}
}
/**
* 批量调用商品详情API(并发处理)
* @param productCodes 商品ID列表
* @param threadPool 线程池
* @return 商品详情映射(商品ID→详情)
*/
public Map<String, ProductDetailBody> batchGetProductDetail(List<String> productCodes,
java.util.concurrent.ExecutorService threadPool) {
Map<String, ProductDetailBody> resultMap = new HashMap<>();
List<java.util.concurrent.Future<?>> futures = new ArrayList<>();
for (String productCode : productCodes) {
futures.add(threadPool.submit(() -> {
try {
ProductDetailBody detail = getProductDetail(productCode);
resultMap.put(productCode, detail);
log.info("批量调用完成,商品ID:{}", productCode);
} catch (Exception e) {
log.error("批量调用失败,商品ID:{},异常:{}", productCode, e.getMessage());
resultMap.put(productCode, null);
}
}));
}
// 等待所有任务完成
for (java.util.concurrent.Future<?> future : futures) {
try {
future.get();
} catch (Exception e) {
log.error("批量调用任务异常", e);
}
}
return resultMap;
}
// -------------------------- 自定义异常 --------------------------
public static abstract class SuningApiException extends RuntimeException {
public SuningApiException(String message) {
super(message);
}
public SuningApiException(String message, Throwable cause) {
super(message, cause);
}
}
/**
* 参数无效异常
*/
public static class SuningApiInvalidParamException extends SuningApiException {
public SuningApiInvalidParamException(String message) {
super(message);
}
}
/**
* 认证异常(签名错误、权限不足)
*/
public static class SuningApiAuthException extends SuningApiException {
public SuningApiAuthException(String message) {
super(message);
}
}
/**
* 限流异常
*/
public static class SuningApiRateLimitException extends SuningApiException {
public SuningApiRateLimitException(String message) {
super(message);
}
}
/**
* 业务异常(状态码非0000)
*/
public static class SuningApiBusinessException extends SuningApiException {
public SuningApiBusinessException(String message) {
super(message);
}
}
/**
* 系统异常(网络、解析等)
*/
public static class SuningApiSystemException extends SuningApiException {
public SuningApiSystemException(String message, Throwable cause) {
super(message, cause);
}
public SuningApiSystemException(String message) {
super(message);
}
}}4. 使用示例(同步 + 异步)
import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import java.util.Arrays;import java.util.List;import java.util.Map;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;/**
* 商品服务示例
*/@Servicepublic class ProductService {
@Autowired
private SuningProductDetailApiUtil suningProductDetailApiUtil;
/**
* 同步调用示例
*/
public void syncGetProductDetail() {
String productCode = "100032189765"; // 商品ID
try {
ProductDetailBody detail = suningProductDetailApiUtil.getProductDetail(productCode);
System.out.println("商品名称:" + detail.getProductInfo().getProductName());
System.out.println("商品售价:" + detail.getPriceInfo().getSalePrice());
} catch (SuningApiException e) {
System.err.println("调用失败:" + e.getMessage());
}
}
/**
* 异步批量调用示例(高并发)
*/
public void asyncBatchGetProductDetail() {
// 1. 创建线程池(核心数=CPU核心数*2)
int corePoolSize = Runtime.getRuntime().availableProcessors() * 2;
ExecutorService threadPool = Executors.newFixedThreadPool(corePoolSize);
// 2. 待调用的商品ID列表
List<String> productCodes = Arrays.asList("100032189765", "100032189888", "100032189999");
// 3. 批量调用
Map<String, ProductDetailBody> resultMap = suningProductDetailApiUtil.batchGetProductDetail(productCodes, threadPool);
// 4. 处理结果
for (Map.Entry<String, ProductDetailBody> entry : resultMap.entrySet()) {
if (entry.getValue() != null) {
System.out.println("商品ID:" + entry.getKey() + ",名称:" + entry.getValue().getProductInfo().getProductName());
} else {
System.out.println("商品ID:" + entry.getKey() + ",调用失败");
}
}
// 5. 关闭线程池
threadPool.shutdown();
}
public static void main(String[] args) {
// Spring环境外使用示例(需手动初始化配置)
SuningApiConfig config = new SuningApiConfig();
config.setAppKey("你的appKey");
config.setAppSecret("你的appSecret");
SuningProductDetailApiUtil apiUtil = new SuningProductDetailApiUtil();
apiUtil.setSuningApiConfig(config);
apiUtil.init(); // 手动初始化
// 同步调用
try {
ProductDetailBody detail = apiUtil.getProductDetail("100032189765");
System.out.println(detail.getProductInfo().getProductName());
} catch (SuningApiException e) {
e.printStackTrace();
}
}}四、核心设计亮点
1. 高可用设计
| 设计点 | 实现方式 | 解决问题 |
|---|---|---|
| 重试机制 | Resilience4j 重试,指数退避策略,仅重试网络异常 | 网络抖动、临时连接失败导致的调用失败 |
| 限流机制 | 基于分钟级限流(100 次 / 分钟),适配苏宁 API 限流规则 | 触发苏宁平台限流,避免账号 / 应用被封禁 |
| 熔断机制 | 失败率 > 50% 触发熔断,60 秒后恢复,避免雪崩效应 | API 服务降级 / 不可用时,减少无效调用 |
| 连接池 | OKHTTP 连接池(10 个连接,空闲 5 分钟),复用连接 | 高并发下频繁创建 / 关闭连接导致的性能损耗 |
| 异常分层 | 自定义异常(参数 / 认证 / 限流 / 业务 / 系统),精准定位问题 | 异常类型不清晰,难以快速排查 |
| 超时控制 | 连接 / 读 / 写超时均为 15 秒,避免长时间阻塞 | 网络超时导致线程阻塞 |
2. 并发适配设计
| 设计点 | 实现方式 | 解决问题 |
|---|---|---|
| 无状态设计 | 工具类无成员变量存储请求上下文,所有参数通过方法传递 | 多线程并发调用时的线程安全问题 |
| 异步调用 | 提供batchGetProductDetail方法,支持线程池批量调用 | 同步调用在高并发下响应慢 |
| 连接池复用 | OKHTTP 连接池,避免每个请求创建新连接 | 高并发下 Socket 资源耗尽 |
| 线程池优化 | 线程池核心数 = CPU 核心数 * 2,适配 IO 密集型场景(API 调用为 IO 密集型) | 线程数过多导致上下文切换频繁 |
3. 性能优化
JSON 解析优化:使用 Jackson 而非 Gson,性能更高,支持忽略未知字段;
参数拼接优化:使用
StringBuilder拼接参数,避免字符串常量池开销;响应体处理:使用
try-with-resources自动关闭 Response,避免资源泄漏;日志优化:大响应体只打印前 500 字符,减少日志 IO 开销。
五、生产环境适配建议
1. 配置优化
连接池大小:根据并发量调整(如并发 500 则连接池大小调整为 50);
重试策略:核心商品可增加重试次数,非核心商品减少重试;
限流阈值:根据苏宁平台分配的限流额度调整(部分应用可申请更高额度);
熔断阈值:核心业务可降低熔断失败率(如 30%),非核心业务可提高(如 60%)。
2. 监控与告警
接入监控系统:监控熔断状态、重试次数、限流次数、调用成功率;
关键指标告警:调用成功率 <90%、熔断触发、限流次数> 50 次 / 分钟时触发告警;
日志接入 ELK:收集 API 调用日志,便于问题排查和趋势分析。
3. 容灾设计
降级策略:API 调用失败时,使用本地缓存的商品数据兜底;
多环境适配:区分测试 / 生产环境的 API 地址和密钥;
数据缓存:缓存商品详情数据(如 10 分钟),减少重复调用。
4. 合规性
签名安全:
appSecret加密存储(如配置中心 / 密钥管理系统),避免硬编码;调用频率:严格遵守苏宁平台的调用规则,避免恶意调用;
数据脱敏:日志中隐藏敏感信息(如
appKey仅显示后 4 位)。
六、总结
高可用底座:通过重试、限流、熔断、连接池等机制,保障 API 调用的稳定性;
并发适配:无状态设计 + 异步调用 + 线程池优化,适配高并发场景;
可扩展设计:分层架构、自定义异常、配置可插拔,便于后续扩展功能(如新增 API、调整策略)。