通用工具包设计 / 公共 SDK 开发规范
SDK 整体设计
SDK 设计原则
公共 SDK 的设计遵循以下核心原则,确保其高质量、可维护且易于使用。
简单易用
API 设计遵循直观原则,降低学习成本。方法签名简洁,参数命名自解释,提供合理的默认值。使用 Builder 模式构造复杂对象,避免构造函数参数过多的问题。
// 推荐的简单易用 API 风格
SdkClient client = SdkClient.builder()
.endpoint("https://api.example.com")
.accessKey("your-access-key")
.secretKey("your-secret-key")
.region("cn-beijing")
.build();
CreateOrderRequest request = CreateOrderRequest.builder()
.productId("prod-123456")
.quantity(2)
.build();
CreateOrderResponse response = client.createOrder(request);无状态
SDK 实例本身不持有任何业务状态,每次请求所需信息均通过方法参数传入。这使得 SDK 实例可以被安全地共享和复用,也便于水平扩展。
线程安全
所有 SDK 组件必须保证线程安全。Client 实例及其内部组件(连接池、序列化器、认证器)在并发场景下需正确同步。推荐使用不可变对象和 Copy-on-Write 模式。
// 线程安全的配置设计 —— 使用不可变对象
public final class SdkConfig {
private final String endpoint;
private final Duration connectTimeout;
private final Duration readTimeout;
private final int maxRetries;
private final Map<String, String> customHeaders;
// 通过 Builder 构造,所有字段 final
private SdkConfig(Builder builder) { /* ... */ }
// 修改配置返回新实例,原实例不变
public SdkConfig withEndpoint(String endpoint) {
return new Builder(this).endpoint(endpoint).build();
}
public static Builder builder() { return new Builder(); }
public static final class Builder { /* ... */ }
}最小依赖
严格控制第三方依赖的引入。每个依赖必须有明确的引入理由,优先使用标准库和成熟稳定的小型库。避免依赖传递导致冲突。核心 SDK 模块应保持轻量。
# 推荐的最小依赖原则示例
# 核心模块 (sdk-core) 仅依赖:
# - slf4j-api (日志门面)
# - Jackson (JSON 序列化)
# - OkHttp (HTTP 传输)可测试
SDK 的每个组件都应易于单元测试。通过接口抽象和依赖注入,让测试可以方便地 mock 外部依赖。提供 Mock Server 工具辅助集成测试。
版本兼容
遵循语义化版本规范。主版本号变更表示不兼容的 API 修改,次版本号表示向下兼容的功能新增,修订号表示向下兼容的问题修正。
统一异常
SDK 层所有异常统一封装,调用方只需捕获顶层异常即可处理所有 SDK 层面的错误。
可观测
融入 OpenTelemetry 生态,支持链路追踪和指标采集。每条请求自动携带 TraceId,便于分布式环境下的问题排查。
模块划分
推荐模块结构
sdk-project/
├── sdk-core # 核心模块:基础抽象、通用工具、异常体系
├── sdk-api # API 定义:请求/响应模型、Client 接口
├── sdk-transport # 传输层:HTTP 客户端封装、连接池管理
├── sdk-serialization # 序列化层:JSON/Protobuf 等序列化实现
├── sdk-security # 安全模块:签名、认证、加密
├── sdk-spring-boot-starter # Spring Boot Starter 集成
└── sdk-example # 使用示例sdk-core
核心基础模块,包含所有模块共享的基础设施:
- 统一异常体系 (SdkException 及其子类)
- 基础接口和抽象类
- 通用工具类 (字符串、集合、IO 等)
- SPI 加载机制
- 配置管理基础类
sdk-api
业务 API 定义模块:
- 请求基类和响应基类
- 具体业务的请求/响应模型
- Client 接口定义
- 枚举和常量定义
sdk-transport
HTTP 传输模块:
- HTTP 客户端抽象接口
- OkHttp/Netty/Apache HttpClient 等实现
- 连接池管理
- 重试与超时机制
- HTTP/2 支持
sdk-serialization
序列化模块:
- 序列化器接口抽象
- JSON/Protobuf/MessagePack 等实现
- SPI 可切换
sdk-security
安全模块:
- 签名算法实现 (HMAC-SHA256 等)
- 认证信息管理
- 临时 Token 管理
- 加解密工具
sdk-spring-boot-starter
Spring Boot 集成模块:
- 自动配置
- 属性绑定
- 健康检查
- 指标导出
sdk-example
示例模块:
- 快速入门示例
- 高级用法示例
- 配置示例
与其他 SDK 模块结构对比
| 模块 | 本 SDK | AWS SDK v2 | Alibaba Cloud SDK | Azure SDK |
|---|---|---|---|---|
| 核心 | sdk-core | core | core | core |
| API | sdk-api | 按服务分模块 | 按产品分模块 | 按服务分模块 |
| 传输 | sdk-transport | http-client-spi | http | http |
| 序列化 | sdk-serialization | 内置在 core | protocol | serialization |
| 安全 | sdk-security | auth | auth | identity |
| Spring集成 | sdk-spring-boot-starter | spring-boot-starter | 无官方支持 | spring-integration |
统一异常体系
异常层次设计
SDK 异常采用分级继承结构,顶层为 SdkException,按错误来源和性质划分子类。
/**
* SDK 异常基类 —— 所有 SDK 引发的异常均继承自此
*/
public class SdkException extends RuntimeException {
private final String errorCode;
private final String errorMessage;
private final ErrorContext errorContext;
public SdkException(String errorCode, String errorMessage) {
super(errorMessage);
this.errorCode = errorCode;
this.errorMessage = errorMessage;
this.errorContext = ErrorContext.empty();
}
public SdkException(String errorCode, String errorMessage, Throwable cause) {
super(errorMessage, cause);
this.errorCode = errorCode;
this.errorMessage = errorMessage;
this.errorContext = ErrorContext.empty();
}
public SdkException(String errorCode, String errorMessage, ErrorContext errorContext) {
super(errorMessage);
this.errorCode = errorCode;
this.errorMessage = errorMessage;
this.errorContext = errorContext;
}
// getters
public String getErrorCode() { return errorCode; }
public String getErrorMessage() { return errorMessage; }
public ErrorContext getErrorContext() { return errorContext; }
}异常子类体系
SdkException (RuntimeException)
├── ClientException // 客户端侧错误(参数校验、连接失败等)
├── ServerException // 服务端侧错误(业务异常、服务错误等)
├── AuthenticationException // 认证授权失败
└── ValidationException // 请求参数校验失败ClientException
由客户端自身原因导致的异常,如网络连接失败、请求超时、请求被取消等。调用方通常可以通过重试或调整配置解决。
public class ClientException extends SdkException {
public ClientException(String errorCode, String errorMessage) {
super(errorCode, errorMessage);
}
public ClientException(String errorCode, String errorMessage, Throwable cause) {
super(errorCode, errorMessage, cause);
}
public ClientException(String errorCode, String errorMessage, ErrorContext errorContext) {
super(errorCode, errorMessage, errorContext);
}
}ServerException
服务端返回错误响应时抛出的异常,包含服务端返回的错误码和错误信息。调用方应根据不同的错误码采取不同处理策略。
public class ServerException extends SdkException {
private final int httpStatusCode;
private final String requestId;
public ServerException(String errorCode, String errorMessage, int httpStatusCode, String requestId) {
super(errorCode, errorMessage);
this.httpStatusCode = httpStatusCode;
this.requestId = requestId;
}
// getters ...
}AuthenticationException
认证或授权失败时抛出,包括 AK/SK 不匹配、签名无效、Token 过期、权限不足等场景。此类异常通常不需要重试。
public class AuthenticationException extends SdkException {
public AuthenticationException(String errorCode, String errorMessage) {
super(errorCode, errorMessage);
}
}ValidationException
请求参数校验失败时抛出,用于在发送请求前快速失败(Fail-Fast),避免无效请求到达服务端。
public class ValidationException extends SdkException {
private final List<FieldViolation> violations;
public ValidationException(List<FieldViolation> violations) {
super("VALIDATION_ERROR", "Request validation failed");
this.violations = violations;
}
public List<FieldViolation> getViolations() { return violations; }
public static class FieldViolation {
private final String field;
private final String message;
// constructor & getters
}
}ErrorCode 枚举设计
错误码采用类似 HTTP 状态码的分段设计,使用 5 位数字编码:第 1 位标识错误大类,第 2-3 位标识模块,第 4-5 位标识具体错误。
错误码格式: XYYZZ
- X : 错误大类 (1=系统, 2=参数, 3=认证, 4=业务, 5=限流, 9=未知)
- YY : 模块标识 (00=通用, 01=用户, 02=订单, 03=支付, ...)
- ZZ : 具体错误 (00-99)public enum ErrorCode {
// ===== 系统错误 (1xxxx) =====
INTERNAL_ERROR("10000", "Internal server error"),
SERVICE_UNAVAILABLE("10001", "Service temporarily unavailable"),
TIMEOUT("10002", "Request timeout"),
// ===== 参数错误 (2xxxx) =====
INVALID_PARAMETER("20000", "Invalid parameter"),
MISSING_REQUIRED_PARAM("20001", "Missing required parameter"),
INVALID_FORMAT("20002", "Invalid parameter format"),
INVALID_PARAMETER_VALUE("20003", "Invalid parameter value, out of range or invalid enum"),
// ===== 认证错误 (3xxxx) =====
AUTHENTICATION_FAILED("30000", "Authentication failed"),
INVALID_ACCESS_KEY("30001", "Invalid access key"),
INVALID_SIGNATURE("30002", "Invalid signature"),
SIGNATURE_EXPIRED("30003", "Signature has expired"),
INVALID_TOKEN("30004", "Invalid or expired token"),
INSUFFICIENT_PERMISSIONS("30005", "Insufficient permissions"),
// ===== 业务错误 (4xxxx) =====
RESOURCE_NOT_FOUND("40000", "Resource not found"),
RESOURCE_EXISTS("40001", "Resource already exists"),
RESOURCE_LOCKED("40002", "Resource is locked or in use"),
RATE_LIMIT_EXCEEDED("40003", "Rate limit exceeded"),
QUOTA_EXCEEDED("40004", "Quota exceeded"),
// ===== 限流/熔断 (5xxxx) =====
FLOW_CONTROL("50000", "Request denied by flow control"),
CIRCUIT_BREAKER_OPEN("50001", "Circuit breaker is open"),
;
private final String code;
private final String message;
ErrorCode(String code, String message) {
this.code = code;
this.message = message;
}
public String getCode() { return code; }
public String getMessage() { return message; }
/**
* 根据 code 查找枚举,未找到返回 null
*/
public static ErrorCode fromCode(String code) {
for (ErrorCode ec : values()) {
if (ec.code.equals(code)) {
return ec;
}
}
return null;
}
}错误码分类原则
- 全局统一:所有服务共享同一套错误码体系,避免同一含义在不同服务中使用不同错误码
- 预留扩展:每个分类预留足够的编号空间,方便后续新增
- 可读性强:错误码本身携带分类信息,便于快速定位问题
国际化支持
错误消息支持多语言,通过 MessageSource 机制加载不同语言的错误描述。
public class I18nErrorMessageResolver {
private final MessageSource messageSource;
public I18nErrorMessageResolver(MessageSource messageSource) {
this.messageSource = messageSource;
}
public String resolve(ErrorCode errorCode, Locale locale, Object... args) {
String code = "sdk.error." + errorCode.getCode();
return messageSource.getMessage(code, args, errorCode.getMessage(), locale);
}
}兼容性保证
- 错误码一旦发布,不得变更其含义
- 新增错误码只能新增,不能复用已废弃的错误码
- 废弃的错误码在文档中标注
@deprecated,至少保留两个大版本
异常上下文
每个异常携带详细的上下文信息,便于问题排查。
/**
* 异常上下文 —— 包含错误的上下文信息
*/
public final class ErrorContext {
private final String requestId; // 请求唯一标识
private final String serviceName; // 服务名
private final String method; // 调用的 API 方法名
private final long elapsedMs; // 请求耗时(毫秒)
private final String traceId; // 分布式链路追踪 ID
private final String connectionInfo; // 连接信息(可选)
private final Map<String, String> extraParams; // 额外参数
private ErrorContext(Builder builder) {
this.requestId = builder.requestId;
this.serviceName = builder.serviceName;
this.method = builder.method;
this.elapsedMs = builder.elapsedMs;
this.traceId = builder.traceId;
this.connectionInfo = builder.connectionInfo;
this.extraParams = Collections.unmodifiableMap(
builder.extraParams != null ? new HashMap<>(builder.extraParams) : Collections.emptyMap()
);
}
// getters ...
public static ErrorContext empty() {
return new Builder().build();
}
public static Builder builder() { return new Builder(); }
public static final class Builder {
private String requestId;
private String serviceName;
private String method;
private long elapsedMs;
private String traceId;
private String connectionInfo;
private Map<String, String> extraParams;
public Builder requestId(String requestId) { this.requestId = requestId; return this; }
public Builder serviceName(String serviceName) { this.serviceName = serviceName; return this; }
public Builder method(String method) { this.method = method; return this; }
public Builder elapsedMs(long elapsedMs) { this.elapsedMs = elapsedMs; return this; }
public Builder traceId(String traceId) { this.traceId = traceId; return this; }
public Builder connectionInfo(String connectionInfo) { this.connectionInfo = connectionInfo; return this; }
public Builder extraParam(String key, String value) {
if (this.extraParams == null) {
this.extraParams = new HashMap<>();
}
this.extraParams.put(key, value);
return this;
}
public Builder extraParams(Map<String, String> extraParams) {
this.extraParams = extraParams;
return this;
}
public ErrorContext build() {
return new ErrorContext(this);
}
}
}异常使用示例
try {
CreateOrderResponse response = client.createOrder(request);
} catch (ValidationException e) {
// 参数校验失败,打印具体的字段错误
e.getViolations().forEach(v ->
logger.error("Field [{}] validation failed: {}", v.getField(), v.getMessage())
);
} catch (AuthenticationException e) {
// 认证失败,检查 AK/SK 配置
logger.error("Auth failed: code={}, message={}", e.getErrorCode(), e.getMessage());
} catch (ServerException e) {
// 服务端错误,记录 requestId 用于排查
logger.error("Server error: requestId={}, code={}, httpStatus={}",
e.getRequestId(), e.getErrorCode(), e.getHttpStatusCode());
} catch (ClientException e) {
// 客户端错误,可考虑重试
ErrorContext ctx = e.getErrorContext();
logger.error("Client error: method={}, elapsed={}ms, traceId={}",
ctx.getMethod(), ctx.getElapsedMs(), ctx.getTraceId());
} catch (SdkException e) {
// 兜底处理所有 SDK 异常
logger.error("SDK error: code={}, message={}", e.getErrorCode(), e.getMessage());
}客户端设计
客户端接口设计
接口单一职责
每个 Client 接口聚焦一个业务领域,方法命名遵循业务语义,避免过度泛化。
/**
* 订单服务的 SDK 客户端接口 —— 单一职责
*/
public interface OrderClient {
CreateOrderResponse createOrder(CreateOrderRequest request);
GetOrderResponse getOrder(GetOrderRequest request);
ListOrdersResponse listOrders(ListOrdersRequest request);
CancelOrderResponse cancelOrder(CancelOrderRequest request);
}
/**
* 支付服务的 SDK 客户端接口 —— 单一职责
*/
public interface PaymentClient {
CreatePaymentResponse createPayment(CreatePaymentRequest request);
GetPaymentResponse getPayment(GetPaymentRequest request);
RefundResponse refund(RefundRequest request);
}Builder 模式构造 Client
Client 的构造过程复杂、配置项多,使用 Builder 模式提供流畅的构造体验。
OrderClient client = OrderClient.builder()
.endpoint("https://api.example.com")
.credentialsProvider(DefaultCredentialsProvider.create())
.connectTimeout(Duration.ofSeconds(10))
.readTimeout(Duration.ofSeconds(30))
.maxRetries(3)
.build();Builder 内部实现要点:
public final class OrderClientBuilder {
// 必要参数
private String endpoint;
// 带默认值的参数
private CredentialsProvider credentialsProvider = DefaultCredentialsProvider.create();
private Duration connectTimeout = Duration.ofSeconds(5);
private Duration readTimeout = Duration.ofSeconds(15);
private Duration writeTimeout = Duration.ofSeconds(15);
private int maxRetries = 3;
private int maxConnections = 50;
private RetryPolicy retryPolicy = RetryPolicy.DEFAULT;
private List<Interceptor> interceptors = new ArrayList<>();
// 对用户隐藏的内部参数
private SerializationFacade serializationFacade = SerializationFacade.defaultInstance();
private HttpClient httpClient;
OrderClientBuilder() {}
// 链式 setter
public OrderClientBuilder endpoint(String endpoint) {
this.endpoint = Objects.requireNonNull(endpoint, "endpoint must not be null");
return this;
}
public OrderClientBuilder credentialsProvider(CredentialsProvider credentialsProvider) {
this.credentialsProvider = Objects.requireNonNull(credentialsProvider, "credentialsProvider must not be null");
return this;
}
public OrderClientBuilder connectTimeout(Duration connectTimeout) {
this.connectTimeout = connectTimeout;
return this;
}
public OrderClientBuilder readTimeout(Duration readTimeout) {
this.readTimeout = readTimeout;
return this;
}
public OrderClientBuilder maxRetries(int maxRetries) {
this.maxRetries = maxRetries;
return this;
}
public OrderClientBuilder addInterceptor(Interceptor interceptor) {
this.interceptors.add(interceptor);
return this;
}
public OrderClient build() {
// 校验必要参数
Objects.requireNonNull(endpoint, "endpoint is required");
// 构建不可变配置
SdkConfig config = SdkConfig.builder()
.endpoint(endpoint)
.connectTimeout(connectTimeout)
.readTimeout(readTimeout)
.writeTimeout(writeTimeout)
.maxRetries(maxRetries)
.maxConnections(maxConnections)
.retryPolicy(retryPolicy)
.build();
// 构建传输层
HttpClient httpClient = this.httpClient != null
? this.httpClient
: HttpClientFactory.create(config);
// 构建序列化器
SerializationFacade serialization = this.serializationFacade;
// 构建认证器
Authenticator authenticator = new HmacSha256Authenticator(credentialsProvider);
// 创建 Client 实例
return new DefaultOrderClient(config, httpClient, serialization, authenticator, interceptors);
}
}配置不可变与 Copy-on-Write
Client 配置一经构造即为不可变。若需要修改配置,通过 toBuilder() 方法基于原配置创建新实例。
// 基于已有 client 修改配置创建新 client
OrderClient newClient = client.toBuilder()
.endpoint("https://new-api.example.com")
.readTimeout(Duration.ofSeconds(60))
.build();
// Client 基类中的实现
public abstract class AbstractSdkClient {
protected final SdkConfig config;
protected AbstractSdkClient(SdkConfig config) {
this.config = config;
}
public SdkConfig getConfig() {
return config; // SdkConfig 本身是不可变的,直接返回安全
}
}连接池管理
/**
* HTTP 连接池配置
*/
public final class ConnectionPoolConfig {
private final int maxIdleConnections; // 最大空闲连接数
private final Duration keepAliveDuration; // 空闲连接的存活时间
private final int maxConnections; // 总最大连接数
private final int maxConnectionsPerRoute; // 每路由最大连接数
private final Duration connectionAcquireTimeout; // 获取连接超时
// Builder 构造 ...
public ConnectionPool buildPool() {
return new ConnectionPool(maxIdleConnections, keepAliveDuration.toMillis(), TimeUnit.MILLISECONDS);
}
}连接配置
public final class ConnectionConfig {
private final Duration connectTimeout; // 连接超时
private final Duration readTimeout; // 读取超时
private final Duration writeTimeout; // 写入超时
private final Duration connectionAcquireTimeout; // 从池获取连接超时
private final boolean tcpNoDelay; // TCP_NODELAY
private final boolean keepAlive; // TCP keep-alive
private final Proxy proxy; // 代理配置
private final SslConfig sslConfig; // SSL/TLS 配置
// Builder + 默认值 ...
}重试与超时
指数退避 + 抖动
重试策略使用指数退避算法,并在每次退避中引入随机抖动(Jitter),防止重试风暴。
public final class RetryPolicy {
private final int maxRetries; // 最大重试次数
private final Duration baseDelay; // 基础延迟
private final Duration maxDelay; // 最大延迟
private final double jitterFactor; // 抖动因子 (0.0 ~ 1.0)
private final Predicate<SdkException> retryablePredicate; // 可重试异常判定
public static final RetryPolicy DEFAULT = RetryPolicy.builder()
.maxRetries(3)
.baseDelay(Duration.ofMillis(100))
.maxDelay(Duration.ofSeconds(5))
.jitterFactor(0.2)
.retryablePredicate(ex ->
ex instanceof ClientException || // 客户端错误可重试
(ex instanceof ServerException se && se.getHttpStatusCode() >= 500) // 服务端 5xx 可重试
)
.build();
// Builder ...
private RetryPolicy(Builder builder) { /* ... */ }
public static Builder builder() { return new Builder(); }
}
/**
* 指数退避 + 抖动计算器
*/
public class BackoffCalculator {
private final RetryPolicy retryPolicy;
private final Random random = new SecureRandom();
public BackoffCalculator(RetryPolicy retryPolicy) {
this.retryPolicy = retryPolicy;
}
/**
* 计算第 retryAttempt 次重试的延迟时间(从 1 开始计数)
*/
public Duration calculateDelay(int retryAttempt) {
long baseMs = retryPolicy.getBaseDelay().toMillis();
long maxMs = retryPolicy.getMaxDelay().toMillis();
// 指数退避: baseDelay * 2^(attempt-1)
long exponentialDelay = (long) (baseMs * Math.pow(2, retryAttempt - 1));
// 加上抖动: delay * (1 + jitterFactor * (random - 0.5) * 2)
double jitterFactor = retryPolicy.getJitterFactor();
double jitter = 1.0 + jitterFactor * (random.nextDouble() - 0.5) * 2;
long delay = (long) (Math.min(exponentialDelay, maxMs) * jitter);
return Duration.ofMillis(Math.max(delay, 1));
}
}重试执行器
/**
* 重试执行器 —— 封装重试逻辑
*/
public class RetryExecutor {
private final RetryPolicy retryPolicy;
private final BackoffCalculator backoffCalculator;
public RetryExecutor(RetryPolicy retryPolicy) {
this.retryPolicy = retryPolicy;
this.backoffCalculator = new BackoffCalculator(retryPolicy);
}
/**
* 执行一个可能重试的操作
*/
public <T> T execute(RetryableSupplier<T> supplier) {
SdkException lastException = null;
for (int attempt = 1; attempt <= retryPolicy.getMaxRetries(); attempt++) {
try {
return supplier.get();
} catch (SdkException e) {
lastException = e;
if (!retryPolicy.getRetryablePredicate().test(e)) {
throw e; // 不可重试异常,直接抛出
}
if (attempt == retryPolicy.getMaxRetries()) {
throw e; // 已达最大重试次数,抛出
}
// 等待后退延迟
Duration delay = backoffCalculator.calculateDelay(attempt);
try {
Thread.sleep(delay.toMillis());
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
throw new ClientException("REQUEST_INTERRUPTED", "Request interrupted during retry backoff", ie);
}
}
}
// 不应到达这里
throw lastException;
}
@FunctionalInterface
public interface RetryableSupplier<T> {
T get() throws SdkException;
}
}重试使用场景
// SDK 内部自动进行重试
public class DefaultOrderClient implements OrderClient {
private final RetryExecutor retryExecutor;
// ... 其他字段
@Override
public CreateOrderResponse createOrder(CreateOrderRequest request) {
return retryExecutor.execute(() -> {
// 1. 请求参数校验
validate(request);
// 2. 序列化请求
byte[] body = serialization.serialize(request);
// 3. 签名
HttpRequest httpRequest = buildHttpRequest(request, body);
authenticator.sign(httpRequest);
// 4. 发送请求
HttpResponse httpResponse = httpClient.send(httpRequest);
// 5. 处理响应
return handleResponse(httpResponse, CreateOrderResponse.class);
});
}
}重试注意事项
- 幂等要求:只有幂等的 API 可以安全重试。非幂等 API(如创建订单)需要业务层配合去重
- 可重试异常判定:只有网络超时、服务端 5xx 等异常才触发重试,参数错误、认证失败等不重试
- SDK 默认值与用户自定义:SDK 提供合理的默认重试策略,用户可通过 Builder 完全自定义
请求与响应模型
请求基类
/**
* 基础请求类
*/
public abstract class SdkRequest {
private final Map<String, String> headers = new HashMap<>();
private final Map<String, String> queryParams = new HashMap<>();
/**
* 请求路径(相对于 endpoint)
*/
public abstract String path();
/**
* HTTP 方法
*/
public abstract HttpMethod method();
// 添加自定义 header
public void addHeader(String name, String value) {
headers.put(name, value);
}
// 添加 query 参数
public void addQueryParam(String name, String value) {
queryParams.put(name, value);
}
public Map<String, String> getHeaders() {
return Collections.unmodifiableMap(headers);
}
public Map<String, String> getQueryParams() {
return Collections.unmodifiableMap(queryParams);
}
@Override
public String toString() {
return method() + " " + path();
}
}响应基类
/**
* 基础响应类 —— 所有响应继承此类
*/
public abstract class SdkResponse {
private String requestId; // 服务端返回的请求 ID
private int httpStatusCode; // HTTP 状态码
private Map<String, String> headers; // 响应头
public String getRequestId() { return requestId; }
public void setRequestId(String requestId) { this.requestId = requestId; }
public int getHttpStatusCode() { return httpStatusCode; }
public void setHttpStatusCode(int httpStatusCode) { this.httpStatusCode = httpStatusCode; }
public Map<String, String> getHeaders() { return headers; }
public void setHeaders(Map<String, String> headers) { this.headers = headers; }
}具体请求/响应示例
/**
* 创建订单请求
*/
public class CreateOrderRequest extends SdkRequest {
private String productId;
private int quantity;
private String clientToken; // 幂等 Token
@Override
public String path() {
return "/v1/orders";
}
@Override
public HttpMethod method() {
return HttpMethod.POST;
}
// Builder
public static Builder builder() { return new Builder(); }
public String getProductId() { return productId; }
public int getQuantity() { return quantity; }
public String getClientToken() { return clientToken; }
public static final class Builder {
private String productId;
private int quantity;
private String clientToken;
public Builder productId(String productId) { this.productId = productId; return this; }
public Builder quantity(int quantity) { this.quantity = quantity; return this; }
public Builder clientToken(String clientToken) { this.clientToken = clientToken; return this; }
public CreateOrderRequest build() {
CreateOrderRequest request = new CreateOrderRequest();
request.productId = this.productId;
request.quantity = this.quantity;
request.clientToken = this.clientToken != null ? this.clientToken : UUID.randomUUID().toString();
return request;
}
}
}
/**
* 创建订单响应
*/
public class CreateOrderResponse extends SdkResponse {
private String orderId;
private String status;
private BigDecimal totalAmount;
private Instant createdAt;
public String getOrderId() { return orderId; }
public void setOrderId(String orderId) { this.orderId = orderId; }
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
public BigDecimal getTotalAmount() { return totalAmount; }
public void setTotalAmount(BigDecimal totalAmount) { this.totalAmount = totalAmount; }
public Instant getCreatedAt() { return createdAt; }
public void setCreatedAt(Instant createdAt) { this.createdAt = createdAt; }
}分页请求/响应
/**
* 分页请求基类
*/
public abstract class PaginatedRequest extends SdkRequest {
private int pageNum = 1;
private int pageSize = 20;
public int getPageNum() { return pageNum; }
public void setPageNum(int pageNum) { this.pageNum = pageNum; }
public int getPageSize() { return pageSize; }
public void setPageSize(int pageSize) { this.pageSize = pageSize; }
}
/**
* 分页响应基类
*/
public class PaginatedResponse<T> extends SdkResponse {
private List<T> items;
private int pageNum;
private int pageSize;
private long totalCount;
private int totalPages;
public boolean hasMore() {
return pageNum < totalPages;
}
// getters & setters ...
}
// 使用示例
public class ListOrdersRequest extends PaginatedRequest {
private String status; // 过滤条件
@Override
public String path() { return "/v1/orders"; }
@Override
public HttpMethod method() { return HttpMethod.GET; }
// getters, setters, builder ...
}
public class ListOrdersResponse extends PaginatedResponse<OrderSummary> {
// 业务字段都在基类中
}异步回调与 CompletableFuture
public interface AsyncOrderClient {
/**
* 异步创建订单,返回 CompletableFuture
*/
CompletableFuture<CreateOrderResponse> createOrderAsync(CreateOrderRequest request);
/**
* 异步创建订单,通过回调通知结果
*/
void createOrderAsync(CreateOrderRequest request, Callback<CreateOrderResponse> callback);
}
/**
* 异步回调接口
*/
@FunctionalInterface
public interface Callback<T> {
void onComplete(T result, SdkException error);
}
// 异步客户端实现
public class DefaultAsyncOrderClient implements AsyncOrderClient {
private final ExecutorService executor;
private final DefaultOrderClient delegate;
public DefaultAsyncOrderClient(DefaultOrderClient delegate, ExecutorService executor) {
this.delegate = delegate;
this.executor = executor;
}
@Override
public CompletableFuture<CreateOrderResponse> createOrderAsync(CreateOrderRequest request) {
return CompletableFuture.supplyAsync(() -> delegate.createOrder(request), executor);
}
@Override
public void createOrderAsync(CreateOrderRequest request, Callback<CreateOrderResponse> callback) {
executor.submit(() -> {
try {
CreateOrderResponse response = delegate.createOrder(request);
callback.onComplete(response, null);
} catch (SdkException e) {
callback.onComplete(null, e);
}
});
}
}响应式支持(Reactive)
对于需要背压支持的高吞吐场景,提供响应式(Reactive)客户端接口,基于 Project Reactor 或 RxJava。
// 依赖: reactor-core (可选模块)
public interface ReactiveOrderClient {
Mono<CreateOrderResponse> createOrderReactive(CreateOrderRequest request);
Flux<OrderSummary> listOrdersReactive(ListOrdersRequest request);
}序列化与传输
序列化选型
选择合适的序列化协议是 SDK 设计的关键决策之一。以下从多维度对比主流序列化方案。
序列化协议对比
| 特性 | JSON | Protobuf | MessagePack | Kryo |
|---|---|---|---|---|
| JS 友好度 | 原生支持 | 需工具链 | 需工具链 | 不友好 |
| 跨语言 | 极好 | 好 | 好 | 一般(Java为主) |
| 序列化性能 | 中等 | 高 | 高 | 极高 |
| 反序列化性能 | 中等 | 高 | 高 | 极高 |
| 压缩比 | 低(文本) | 高(二进制) | 高(二进制) | 极高 |
| Schema 要求 | 无 | 必须(.proto) | 无 | 无 |
| 可读性 | 可读 | 不可读 | 不可读 | 不可读 |
| 生态成熟度 | 极好 | 极好 | 好 | 好 |
| 安全风险 | 低 | 低 | 中 | 中(需配置) |
默认序列化器:JSON
JSON 作为默认序列化方案,平衡了跨语言兼容性、可读性和生态成熟度。使用 Jackson 库实现。
/**
* 序列化器接口 —— SPI 可扩展
*/
public interface Serializer {
/**
* 序列化对象为字节数组
*/
<T> byte[] serialize(T obj) throws SerializationException;
/**
* 反序列化字节数组为对象
*/
<T> T deserialize(byte[] data, Class<T> clazz) throws SerializationException;
/**
* 反序列化字节数组为参数化类型
*/
<T> T deserialize(byte[] data, TypeReference<T> typeRef) throws SerializationException;
/**
* 内容类型标识 (如 application/json, application/x-protobuf)
*/
String contentType();
}
/**
* JSON 序列化实现 —— 基于 Jackson
*/
public class JsonSerializer implements Serializer {
private final ObjectMapper objectMapper;
public JsonSerializer() {
this.objectMapper = new ObjectMapper();
this.objectMapper.registerModule(new JavaTimeModule());
this.objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
this.objectMapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
this.objectMapper.setPropertyNamingStrategy(PropertyNamingStrategies.LOWER_CAMEL_CASE);
}
public JsonSerializer(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
@Override
public <T> byte[] serialize(T obj) throws SerializationException {
try {
return objectMapper.writeValueAsBytes(obj);
} catch (JsonProcessingException e) {
throw new SerializationException("SERIALIZE_FAILED", "Failed to serialize object", e);
}
}
@Override
public <T> T deserialize(byte[] data, Class<T> clazz) throws SerializationException {
try {
return objectMapper.readValue(data, clazz);
} catch (IOException e) {
throw new SerializationException("DESERIALIZE_FAILED", "Failed to deserialize object", e);
}
}
@Override
public <T> T deserialize(byte[] data, TypeReference<T> typeRef) throws SerializationException {
try {
return objectMapper.readValue(data, typeRef);
} catch (IOException e) {
throw new SerializationException("DESERIALIZE_FAILED", "Failed to deserialize object", e);
}
}
@Override
public String contentType() {
return "application/json";
}
}序列化 SPI 切换机制
通过 Java SPI 机制实现序列化器的可切换,也支持用户自定义序列化器。
// META-INF/services/com.example.sdk.serialization.Serializer
// 内容: com.example.sdk.serialization.JsonSerializer
/**
* 序列化门面 —— 统一序列化入口,支持 SPI 加载
*/
public class SerializationFacade {
private final Serializer serializer;
public SerializationFacade() {
this(loadDefaultSerializer());
}
public SerializationFacade(Serializer serializer) {
this.serializer = Objects.requireNonNull(serializer, "serializer must not be null");
}
public <T> byte[] serialize(T obj) {
return serializer.serialize(obj);
}
public <T> T deserialize(byte[] data, Class<T> clazz) {
return serializer.deserialize(data, clazz);
}
public <T> T deserialize(byte[] data, TypeReference<T> typeRef) {
return serializer.deserialize(data, typeRef);
}
public String getContentType() {
return serializer.contentType();
}
/**
* 通过 SPI 加载默认序列化器,第一个有效的序列化器优先
*/
private static Serializer loadDefaultSerializer() {
ServiceLoader<Serializer> loader = ServiceLoader.load(Serializer.class);
for (Serializer s : loader) {
return s; // 返回第一个 SPI 实现
}
return new JsonSerializer(); // 兜底使用 JSON
}
}HTTP 传输
传输层抽象
/**
* HTTP 客户端抽象接口
*/
public interface HttpClient {
/**
* 发送 HTTP 请求,返回响应
*/
HttpResponse send(HttpRequest request) throws ClientException;
/**
* 异步发送 HTTP 请求
*/
CompletableFuture<HttpResponse> sendAsync(HttpRequest request);
}
/**
* HTTP 请求封装
*/
public class HttpRequest {
private final HttpMethod method;
private final String url;
private final Map<String, String> headers;
private final byte[] body;
private final Duration readTimeout;
private final Duration connectTimeout;
// Builder ...
}
/**
* HTTP 响应封装
*/
public class HttpResponse {
private final int statusCode;
private final Map<String, List<String>> headers;
private final byte[] body;
// constructor & getters ...
}OkHttp 实现(默认传输层)
/**
* 基于 OkHttp 的 HTTP 传输实现
*/
public class OkHttpClient implements HttpClient {
private final okhttp3.OkHttpClient httpClient;
private final SerializationFacade serialization;
public OkHttpClient(SdkConfig config, SerializationFacade serialization) {
this.serialization = serialization;
ConnectionPool pool = new ConnectionPool(
config.getMaxIdleConnections(),
config.getKeepAliveDuration().toMillis(),
TimeUnit.MILLISECONDS
);
this.httpClient = new okhttp3.OkHttpClient.Builder()
.connectTimeout(config.getConnectTimeout())
.readTimeout(config.getReadTimeout())
.writeTimeout(config.getWriteTimeout())
.connectionPool(pool)
.protocols(Arrays.asList(Protocol.HTTP_2, Protocol.HTTP_1_1))
.retryOnConnectionFailure(true)
.addInterceptor(new RetryInterceptor(config.getMaxRetries()))
.addInterceptor(new TracingInterceptor())
.build();
}
@Override
public HttpResponse send(HttpRequest request) throws ClientException {
try {
okhttp3.Request okRequest = toOkHttpRequest(request);
try (Response okResponse = httpClient.newCall(okRequest).execute()) {
return toSdkResponse(okResponse);
}
} catch (SocketTimeoutException e) {
throw new ClientException("TIMEOUT", "Request timed out", e);
} catch (IOException e) {
throw new ClientException("CONNECTION_ERROR", "Failed to execute HTTP request", e);
}
}
@Override
public CompletableFuture<HttpResponse> sendAsync(HttpRequest request) {
okhttp3.Request okRequest = toOkHttpRequest(request);
CompletableFuture<HttpResponse> future = new CompletableFuture<>();
httpClient.newCall(okRequest).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
future.completeExceptionally(
new ClientException("CONNECTION_ERROR", "Async request failed", e));
}
@Override
public void onResponse(Call call, Response response) {
try {
future.complete(toSdkResponse(response));
} catch (Exception e) {
future.completeExceptionally(e);
}
}
});
return future;
}
private okhttp3.Request toOkHttpRequest(HttpRequest request) {
RequestBody body = request.getBody() != null
? RequestBody.create(request.getBody(), MediaType.parse(serialization.getContentType()))
: null;
return new okhttp3.Request.Builder()
.url(request.getUrl())
.method(request.getMethod().name(), body)
.headers(Headers.of(request.getHeaders()))
.build();
}
private HttpResponse toSdkResponse(Response okResponse) throws IOException {
byte[] body = okResponse.body() != null ? okResponse.body().bytes() : new byte[0];
return new HttpResponse(okResponse.code(), okResponse.headers().toMultimap(), body);
}
}传输层配置建议
| 配置项 | 推荐默认值 | 说明 |
|---|---|---|
| 最大连接数 | 50 | 连接池中最大连接数 |
| 每路由最大连接数 | 10 | 单个目标主机的最大连接数 |
| 最大空闲连接数 | 5 | 保持空闲的连接数 |
| 空闲连接存活时间 | 5 分钟 | 空闲连接超时回收 |
| 连接超时 | 5 秒 | 建立 TCP 连接的超时时间 |
| 读取超时 | 15 秒 | 等待响应数据的超时时间 |
| 写入超时 | 15 秒 | 发送请求数据的超时时间 |
| 获取连接超时 | 3 秒 | 从连接池获取连接的超时时间 |
| HTTP/2 | 启用 | 优先使用 HTTP/2,回退到 HTTP/1.1 |
| Keep-Alive | 启用 | TCP 长连接复用 |
认证与签名
AK/SK HMAC-SHA256 签名流程
签名机制确保请求在传输过程中不被篡改,并验证调用方的身份。
/**
* 认证信息
*/
public class Credentials {
private final String accessKey;
private final String secretKey;
private final String securityToken; // 临时 Token,可选
public Credentials(String accessKey, String secretKey) {
this(accessKey, secretKey, null);
}
public Credentials(String accessKey, String secretKey, String securityToken) {
this.accessKey = accessKey;
this.secretKey = secretKey;
this.securityToken = securityToken;
}
// getters ...
}
/**
* 认证信息提供者 —— 支持动态获取
*/
public interface CredentialsProvider {
Credentials resolveCredentials();
}
/**
* 默认认证信息提供者
*/
public class DefaultCredentialsProvider implements CredentialsProvider {
private final Credentials credentials;
public DefaultCredentialsProvider(Credentials credentials) {
this.credentials = credentials;
}
public static DefaultCredentialsProvider create() {
// 从环境变量/系统属性/配置文件加载
String ak = System.getenv("SDK_ACCESS_KEY");
String sk = System.getenv("SDK_SECRET_KEY");
if (ak == null || sk == null) {
throw new AuthenticationException("MISSING_CREDENTIALS",
"Credentials not found. Set SDK_ACCESS_KEY and SDK_SECRET_KEY environment variables.");
}
return new DefaultCredentialsProvider(new Credentials(ak, sk));
}
@Override
public Credentials resolveCredentials() {
return credentials;
}
}签名计算
HMAC-SHA256 签名流程包含以下步骤:
- 规范请求(Canonical Request):将请求参数按字典序排序并拼接
- 计算签名摘要:使用 SK 对规范请求进行 HMAC-SHA256 计算
- 添加 Authorization 头
/**
* HMAC-SHA256 签名认证器
*/
public class HmacSha256Authenticator implements Authenticator {
private static final String HMAC_SHA256 = "HmacSHA256";
private static final String HEADER_AUTHORIZATION = "Authorization";
private static final String HEADER_X_DATE = "X-Sdk-Date";
private static final String HEADER_X_CONTENT_SHA256 = "X-Sdk-Content-Sha256";
private static final String CREDENTIAL_SCOPE = "sdk_request";
private final CredentialsProvider credentialsProvider;
public HmacSha256Authenticator(CredentialsProvider credentialsProvider) {
this.credentialsProvider = credentialsProvider;
}
@Override
public void sign(HttpRequest request) {
Credentials credentials = credentialsProvider.resolveCredentials();
String timestamp = getTimestamp();
// 1. 设置时间头
request.addHeader(HEADER_X_DATE, timestamp);
// 2. 计算 Body 的 SHA256 作为头
byte[] body = request.getBody() != null ? request.getBody() : new byte[0];
String bodySha256 = sha256Hex(body);
request.addHeader(HEADER_X_CONTENT_SHA256, bodySha256);
// 3. 构建规范请求字符串
String canonicalRequest = buildCanonicalRequest(request, bodySha256);
// 4. 构建待签名字符串
String stringToSign = buildStringToSign(timestamp, canonicalRequest);
// 5. 计算签名
String signature = computeSignature(credentials.getSecretKey(), timestamp, stringToSign);
// 6. 添加 Authorization 头
String authorization = buildAuthorizationHeader(credentials.getAccessKey(), timestamp, signature);
request.addHeader(HEADER_AUTHORIZATION, authorization);
// 7. 添加临时 Token(如果存在)
if (credentials.getSecurityToken() != null) {
request.addHeader("X-Sdk-Security-Token", credentials.getSecurityToken());
}
}
/**
* 构建规范请求字符串
* 包含: HTTP方法 + 换行 + 规范URI + 换行 + 规范查询参数 + 换行 + 签名头 + 换行 + Body SHA256
*/
private String buildCanonicalRequest(HttpRequest request, String bodySha256) {
// 规范 URI(对路径进行 URL 编码)
String canonicalUri = UriEncoder.encodePath(request.getUrl());
// 规范查询参数(按字典序排序)
String canonicalQuery = sortAndEncodeQueryParams(request.getQueryParams());
// 签名头(参与签名的 header 名,字典序小写)
String signedHeaders = buildSignedHeaders(request.getHeaders());
return request.getMethod().name() + "\n"
+ canonicalUri + "\n"
+ canonicalQuery + "\n"
+ signedHeaders + "\n"
+ bodySha256;
}
/**
* 构建待签名字符串
* 格式: SDK-HMAC-SHA256 + 换行 + 时间戳 + 换行 + 凭据范围 + 换行 + 规范请求的 SHA256
*/
private String buildStringToSign(String timestamp, String canonicalRequest) {
return "SDK-HMAC-SHA256\n"
+ timestamp + "\n"
+ CREDENTIAL_SCOPE + "\n"
+ sha256Hex(canonicalRequest.getBytes(StandardCharsets.UTF_8));
}
/**
* 计算签名
* 使用: HMAC-SHA256(HMAC-SHA256(SK, 时间戳), 待签名字符串)
*/
private String computeSignature(String secretKey, String timestamp, String stringToSign) {
try {
Mac hmac = Mac.getInstance(HMAC_SHA256);
// 第一层: 使用 SK 对时间戳进行 HMAC-SHA256
SecretKeySpec keySpec1 = new SecretKeySpec(
secretKey.getBytes(StandardCharsets.UTF_8), HMAC_SHA256);
hmac.init(keySpec1);
byte[] dateKey = hmac.doFinal(timestamp.getBytes(StandardCharsets.UTF_8));
// 第二层: 使用日期密钥对待签名字符串进行 HMAC-SHA256
SecretKeySpec keySpec2 = new SecretKeySpec(dateKey, HMAC_SHA256);
hmac.init(keySpec2);
byte[] signature = hmac.doFinal(stringToSign.getBytes(StandardCharsets.UTF_8));
return bytesToHex(signature);
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
throw new AuthenticationException("SIGNATURE_ERROR", "Failed to compute HMAC-SHA256 signature", e);
}
}
private String buildAuthorizationHeader(String accessKey, String timestamp, String signature) {
return "SDK-HMAC-SHA256 Credential=" + accessKey + ", SignedHeaders=" + getSignedHeaders() + ", Signature=" + signature;
}
private String getSignedHeaders() {
return "x-sdk-date;x-sdk-content-sha256;host";
}
private String buildSignedHeaders(Map<String, String> headers) {
// 筛选 x-sdk- 和 host 头,按字典序排序,小写
return "host;x-sdk-content-sha256;x-sdk-date";
}
private String getTimestamp() {
return Instant.now().toString(); // ISO-8601 格式
}
private String sha256Hex(byte[] data) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
return bytesToHex(md.digest(data));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("SHA-256 not available", e);
}
}
private String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
}服务端验签流程
服务端验签是客户端签名的对称过程:
- 从请求头中提取 Authorization、X-Sdk-Date、X-Sdk-Content-Sha256
- 从 Authorization 中解析 Credential 和 Signature
- 根据 AK 查找对应的 SK
- 使用与客户端相同的算法重建签名
- 比对签名是否一致
- 验证时间戳是否在有效窗口内(通常为 15 分钟)
临时 Token 机制
临时 Token 通过 STS(Security Token Service)颁发,具有时效性限制。SDK 端自动管理 Token 的获取和刷新。
/**
* 临时 Token 认证信息提供者 —— 自动刷新
*/
public class StsCredentialsProvider implements CredentialsProvider {
private volatile Credentials currentCredentials;
private final StsClient stsClient;
private final ScheduledExecutorService scheduler;
public StsCredentialsProvider(StsClient stsClient) {
this.stsClient = stsClient;
this.scheduler = Executors.newSingleThreadScheduledExecutor();
refreshCredentials();
// 在 Token 过期前 5 分钟自动刷新
scheduler.scheduleAtFixedRate(this::refreshCredentials, 55, 55, TimeUnit.MINUTES);
}
@Override
public Credentials resolveCredentials() {
return currentCredentials;
}
private void refreshCredentials() {
try {
AssumeRoleResponse response = stsClient.assumeRole(/* ... */);
currentCredentials = new Credentials(
response.getAccessKey(),
response.getSecretKey(),
response.getSecurityToken()
);
} catch (Exception e) {
// 刷新失败使用旧 Token,下次重试
logger.warn("Failed to refresh STS credentials", e);
}
}
}开发者体验
文档
README 快速开始
SDK 的 README 应包含以下内容,让用户在 5 分钟内完成第一次调用:
# Example SDK - [产品名称]
## 简介
[产品一句话介绍]
## 环境要求
- Java 8 或以上版本
- Maven 3.6+ / Gradle 7+
## 快速开始
### 1. 添加依赖
```xml
<dependency>
<groupId>com.example</groupId>
<artifactId>sdk-core</artifactId>
<version>${latest.version}</version>
</dependency>2. 配置凭证
export SDK_ACCESS_KEY=your-access-key
export SDK_SECRET_KEY=your-secret-key3. 调用 API
// 创建客户端
OrderClient client = OrderClient.builder()
.endpoint("https://api.example.com")
.build();
// 构造请求
CreateOrderRequest request = CreateOrderRequest.builder()
.productId("prod-123")
.quantity(1)
.build();
// 执行调用
CreateOrderResponse response = client.createOrder(request);
System.out.println("Order created: " + response.getOrderId());文档
**API 文档规范**
- 每个公开类、接口、方法必须有 JavaDoc
- 标注参数范围约束和 null 安全注解 (`@Nullable`, `@NonNull`)
- 标注废弃方法 (`@deprecated`) 及替代方案
- 生成 javadoc 站点并对外发布
### 测试
**单元测试覆盖率**
- SDK 核心逻辑的单元测试覆盖率达到 90% 以上
- 使用 JUnit 5 + Mockito 进行测试
- 关键测试场景:正常流程、参数校验异常、网络超时、服务端错误、重试逻辑、并发安全
```java
class RetryExecutorTest {
@Test
void shouldSucceedOnFirstAttempt() {
RetryPolicy policy = RetryPolicy.builder()
.maxRetries(3)
.baseDelay(Duration.ofMillis(10))
.build();
RetryExecutor executor = new RetryExecutor(policy);
String result = executor.execute(() -> "success");
assertEquals("success", result);
}
@Test
void shouldRetryOnClientException() {
RetryPolicy policy = RetryPolicy.builder()
.maxRetries(3)
.baseDelay(Duration.ofMillis(10))
.build();
RetryExecutor executor = new RetryExecutor(policy);
AtomicInteger counter = new AtomicInteger(0);
String result = executor.execute(() -> {
if (counter.incrementAndGet() < 3) {
throw new ClientException("TIMEOUT", "timeout");
}
return "success-after-retry";
});
assertEquals("success-after-retry", result);
assertEquals(3, counter.get());
}
@Test
void shouldThrowAfterMaxRetries() {
RetryPolicy policy = RetryPolicy.builder()
.maxRetries(2)
.baseDelay(Duration.ofMillis(5))
.build();
RetryExecutor executor = new RetryExecutor(policy);
assertThrows(ClientException.class, () ->
executor.execute(() -> {
throw new ClientException("TIMEOUT", "persistent timeout");
})
);
}
@Test
void shouldNotRetryOnAuthenticationException() {
RetryPolicy policy = RetryPolicy.builder()
.maxRetries(3)
.baseDelay(Duration.ofMillis(10))
.build();
RetryExecutor executor = new RetryExecutor(policy);
assertThrows(AuthenticationException.class, () ->
executor.execute(() -> {
throw new AuthenticationException("INVALID_SIGNATURE", "bad signature");
})
);
}
}SDK Mock Server
提供 Mock Server 工具,让开发者在不依赖真实服务的情况下进行集成测试。
/**
* SDK Mock Server —— 用于集成测试
*/
public class SdkMockServer implements AutoCloseable {
private final MockWebServer server = new MockWebServer();
public void start() throws IOException {
server.start();
}
public void shutdown() {
try {
server.shutdown();
} catch (IOException e) {
// ignore
}
}
public String getEndpoint() {
return server.url("/").toString();
}
/**
* 设置 Mock 响应
*/
public void enqueue(MockResponse response) {
server.enqueue(response);
}
public void enqueue(int statusCode, String body) {
server.enqueue(new MockResponse()
.setResponseCode(statusCode)
.setBody(body)
.setHeader("Content-Type", "application/json"));
}
/**
* 获取收到的请求记录
*/
public List<RecordedRequest> getRecordedRequests() throws InterruptedException {
List<RecordedRequest> requests = new ArrayList<>();
RecordedRequest request;
while ((request = server.takeRequest(0, TimeUnit.MILLISECONDS)) != null) {
requests.add(request);
}
return requests;
}
@Override
public void close() {
shutdown();
}
}// Mock Server 使用示例(集成测试)
class OrderClientIntegrationTest {
private SdkMockServer mockServer;
private OrderClient client;
@BeforeEach
void setUp() {
mockServer = new SdkMockServer();
mockServer.start();
client = OrderClient.builder()
.endpoint(mockServer.getEndpoint())
.credentialsProvider(() -> new Credentials("test-ak", "test-sk"))
.maxRetries(0) // 集成测试中关闭重试
.build();
}
@AfterEach
void tearDown() {
mockServer.close();
}
@Test
void shouldCreateOrderSuccessfully() {
// 准备 mock 响应
String responseJson = "{\"orderId\":\"ord-001\",\"status\":\"CREATED\",\"totalAmount\":99.99}";
mockServer.enqueue(200, responseJson);
// 执行请求
CreateOrderRequest request = CreateOrderRequest.builder()
.productId("prod-001")
.quantity(1)
.build();
CreateOrderResponse response = client.createOrder(request);
// 验证结果
assertEquals("ord-001", response.getOrderId());
assertEquals("CREATED", response.getStatus());
// 验证请求内容
RecordedRequest recorded = mockServer.getRecordedRequests().get(0);
assertThat(recorded.getMethod()).isEqualTo("POST");
assertThat(recorded.getPath()).isEqualTo("/v1/orders");
assertThat(recorded.getHeader("Authorization")).isNotNull();
assertThat(recorded.getHeader("X-Sdk-Date")).isNotNull();
}
}集成测试范围
| 测试类型 | 覆盖内容 | 工具 |
|---|---|---|
| 单元测试 | 工具类、异常、签名、重试逻辑 | JUnit 5 + Mockito |
| Mock Server 测试 | 请求构造、响应解析、错误处理 | MockWebServer |
| 真实环境集成测试 | 完整请求链路、真实服务交互 | Testcontainers/Sandbox |
| 协议兼容性测试 | 不同序列化格式、HTTP 版本兼容 | 自定义测试套件 |
发布与维护
版本号规范
严格遵循语义化版本号 2.0.0(SemVer):
主版本号.次版本号.修订号 (例如 2.5.1)
主版本号:不兼容的 API 修改
次版本号:向下兼容的功能新增
修订号 :向下兼容的问题修正向下兼容保证
- 二进制兼容:已编译的代码不需要重新编译即可工作
- 源码兼容:用户代码不需要修改即可编译通过
- 行为兼容:相同输入产生相同输出,不改变语义
@Deprecated 废弃周期
public class OrderClient {
/**
* 创建订单(旧版,仅支持单品)
*
* @deprecated 从 v2.1.0 开始废弃,推荐使用 {@link #createOrder(CreateOrderRequest)}
* 代替。此方法将在 v3.0.0 移除。
*/
@Deprecated
public CreateOrderResponse createOrderSingle(String productId, int quantity) {
CreateOrderRequest request = CreateOrderRequest.builder()
.productId(productId)
.quantity(quantity)
.build();
return createOrder(request);
}
}废弃周期策略
| 阶段 | 操作 | 时间点 |
|---|---|---|
| 标记废弃 | 添加 @Deprecated 注解和 JavaDoc 说明 | 引入替代方案时 |
| 继续支持 | 旧方法仍可正常使用,仅编译告警 | 至少三个次版本 |
| 移除 | 删除废弃代码 | 下一个主版本 |
不兼容变更通知流程
- 提前邮件通知:至少提前一个次版本周期(通常 1-2 个月)
- 发布 Migration Guide:详细说明变更内容和迁移步骤
- 提供迁移工具:自动化的代码迁移辅助工具
- 兼容期:提供 --compat-mode 作为临时降级手段
版本升级 Migration Guide
# v2.x 迁移指南
## 概述
本指南帮助从 v1.x 迁移到 v2.x,主要变更包括:
- 包名从 com.example.old 变更为 com.example.sdk
- 异常体系重构,统一使用 SdkException
- 客户端构造方式改为 Builder 模式
## 步骤 1:更新依赖
将依赖版本从 1.x 更新到 2.0.0。
## 步骤 2:替换客户端构造
### 旧版 (v1.x)
```java
OrderClient client = new OrderClient("https://api.example.com", "ak", "sk");新版 (v2.x)
OrderClient client = OrderClient.builder()
.endpoint("https://api.example.com")
.credentialsProvider(DefaultCredentialsProvider.create())
.build();步骤 3:异常处理更新
旧版
try {
client.createOrder(request);
} catch (ApiException e) {
// 处理异常
}新版
try {
client.createOrder(request);
} catch (SdkException e) {
String errorCode = e.getErrorCode();
String requestId = e.getErrorContext().getRequestId();
// 处理异常
}步骤 4:完整变更对照表
[详细变更对照表链接]
**CHANGELOG 规范**
CHANGELOG 按版本号逆序排列,格式遵循 Keep a Changelog 规范。
```markdown
# Changelog
## [2.1.0] - 2026-06-15
### 新增
- 新增响应式客户端 ReactiveOrderClient,支持 Mono/Flux 响应式编程
- 新增 STS 临时 Token 自动刷新支持
- 新增 ConnectionConfig#sslConfig 自定义 SSL 配置
### 变更
- 将默认连接超时从 10 秒调整为 5 秒
- 优化重试退避算法的抖动计算,减少重试风暴概率
### 废弃
- OrderClient#createOrderSingle 废弃,推荐使用 createOrder(CreateOrderRequest)
### 修复
- 修复在特定情况下连接池泄漏的问题
- 修复 Protobuf 序列化时枚举值为空导致的 NPE
### 依赖升级
- OkHttp 从 4.11.0 升级到 4.12.0
- Jackson 从 2.15.2 升级到 2.16.0
## [2.0.0] - 2026-03-20
### 重大变更
- 包名从 com.example.old 变更为 com.example.sdk
- 统一异常体系,移除旧版 ApiException,使用 SdkException 替代
- 客户端构造改为 Builder 模式
### 迁移
- 请参考 [v2.x 迁移指南](./migration-guide-v2.md)发布清单
每次正式发布前需完成以下检查项:
- [ ] 所有测试通过(单元测试 + 集成测试)
- [ ] 测试覆盖率达标
- [ ] API 兼容性检查通过(使用 japicmp 或类似工具)
- [ ] CHANGELOG 已更新
- [ ] 版本号已更新
- [ ] JavaDoc 生成无报错
- [ ] 示例代码已更新
- [ ] 附件说明文档已更新
- [ ] 制品已发布到 Maven 中央仓库或企业内部仓库