Skip to content

SDK 错误处理

本文档说明一单位一平台 SDK 的异常处理机制和常见错误的处理方法。

异常体系

SDK 使用了三层异常体系:

SdkException (SDK 基础异常)
├── CryptoException (加密异常)
└── ApiException (API 调用异常)

异常类型

SdkException

SDK 基础异常,所有 SDK 异常的父类。

java
package cn.hztech.sdk.exception;

public class SdkException extends RuntimeException {
    private String errorCode;

    public SdkException(String message) {
        super(message);
    }

    public SdkException(String errorCode, String message) {
        super(message);
        this.errorCode = errorCode;
    }

    public String getErrorCode() {
        return errorCode;
    }
}

CryptoException

加密相关异常,在加密、解密、签名、验签过程中抛出。

java
package cn.hztech.sdk.exception;

public class CryptoException extends SdkException {

    public CryptoException(String message) {
        super(message);
    }

    public CryptoException(String message, Throwable cause) {
        super(message, cause);
    }
}

常见场景

  • 密钥格式错误
  • 密钥加载失败
  • 加密算法初始化失败
  • 加密数据格式错误

ApiException

API 调用异常,在调用平台接口时抛出。

java
package cn.hztech.sdk.exception;

public class ApiException extends SdkException {

    public ApiException(String message) {
        super(message);
    }

    public ApiException(String errorCode, String message) {
        super(errorCode, message);
    }
}

常见场景

  • 网络连接失败
  • 请求超时
  • 服务器返回错误
  • 业务逻辑错误

错误码

通用错误码

错误码说明HTTP 状态码
1000成功200
1001请求参数错误400
1002缺少必填参数400
1003签名验证失败401
1004解密失败401
1005应用 ID 不存在403
1006时间戳验证失败403
1007Nonce 重复403
1008请求已过期403
1999系统错误500

业务错误码

人员管理

错误码说明HTTP 状态码
2501用户不存在404
2502用户已存在409
2503用户状态异常403

部门管理

错误码说明HTTP 状态码
2601部门不存在404
2602部门已存在409
2603部门存在子部门409

角色管理

错误码说明HTTP 状态码
2701角色不存在404
2702角色已存在409

全局异常处理

使用 @RestControllerAdvice

推荐在 Spring Boot 项目中使用全局异常处理器来统一处理 SDK 异常:

java
@RestControllerAdvice
public class GlobalExceptionHandler {

    private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    /**
     * 处理 API 异常
     */
    @ExceptionHandler(ApiException.class)
    public Result<Void> handleApiException(ApiException e) {
        log.error("API 调用异常: {}", e.getMessage(), e);

        String errorCode = e.getErrorCode();

        // 根据错误码返回不同的提示信息
        if (StringUtils.hasText(errorCode)) {
            switch (errorCode) {
                case "2501":
                    return Result.error(404, "用户不存在");
                case "2502":
                    return Result.error(409, "用户已存在");
                case "2503":
                    return Result.error(403, "用户状态异常");
                case "2601":
                    return Result.error(404, "部门不存在");
                case "2602":
                    return Result.error(409, "部门已存在");
                case "2701":
                    return Result.error(404, "角色不存在");
                case "2702":
                    return Result.error(409, "角色已存在");
                case "1003":
                    return Result.error(401, "签名验证失败");
                case "1004":
                    return Result.error(401, "解密失败");
                case "1005":
                    return Result.error(403, "应用 ID 不存在");
                case "1006":
                    return Result.error(403, "时间戳验证失败");
                case "1007":
                    return Result.error(403, "Nonce 重复");
                case "1008":
                    return Result.error(403, "请求已过期");
                default:
                    return Result.error(500, "系统错误: " + e.getMessage());
            }
        }

        return Result.error(500, "API 调用失败: " + e.getMessage());
    }

    /**
     * 处理加密异常
     */
    @ExceptionHandler(CryptoException.class)
    public Result<Void> handleCryptoException(CryptoException e) {
        log.error("加密异常: {}", e.getMessage(), e);
        return Result.error(500, "数据处理异常");
    }

    /**
     * 处理 SDK 基础异常
     */
    @ExceptionHandler(SdkException.class)
    public Result<Void> handleSdkException(SdkException e) {
        log.error("SDK 异常: {}", e.getMessage(), e);
        return Result.error(500, "SDK 异常: " + e.getMessage());
    }
}

错误响应格式

java
public class Result<T> {
    private Integer code;
    private String message;
    private T data;

    public static <T> Result<T> error(Integer code, String message) {
        Result<T> result = new Result<>();
        result.setCode(code);
        result.setMessage(message);
        return result;
    }
}

常见错误处理

1. 用户不存在

错误码:2501

处理方法

java
try {
    User user = userApiClient.getDetail(userId);
    return Result.success(user);
} catch (ApiException e) {
    if (e.getMessage().contains("2501")) {
        return Result.error(404, "用户不存在");
    }
    throw e;
}

2. 签名验证失败

错误码:1003

可能原因

  • 密钥配置错误
  • 密钥被修改
  • 密钥格式不正确

处理方法

java
try {
    // 调用 API
} catch (ApiException e) {
    if (e.getMessage().contains("1003")) {
        log.error("签名验证失败,请检查密钥配置");
        return Result.error(401, "认证失败");
    }
    throw e;
}

3. 解密失败

错误码:1004

可能原因

  • 私钥配置错误
  • 加密数据被篡改

处理方法

java
try {
    // 调用 API
} catch (ApiException e) {
    if (e.getMessage().contains("1004")) {
        log.error("解密失败,请检查私钥配置");
        return Result.error(401, "解密失败");
    }
    throw e;
}

4. 应用 ID 不存在

错误码:1005

可能原因

  • 应用 ID 配置错误
  • 应用未在平台注册

处理方法

java
try {
    // 调用 API
} catch (ApiException e) {
    if (e.getMessage().contains("1005")) {
        log.error("应用 ID 不存在,请检查配置");
        return Result.error(403, "应用未授权");
    }
    throw e;
}

5. 时间戳验证失败

错误码:1006

可能原因

  • 系统时间不同步
  • 时间误差超过容差范围

处理方法

java
try {
    // 调用 API
} catch (ApiException e) {
    if (e.getMessage().contains("1006")) {
        log.error("时间戳验证失败,请同步系统时间");
        return Result.error(403, "时间验证失败");
    }
    throw e;
}

解决方案

  1. 使用 NTP 服务同步时间
  2. 增加 timestamp-tolerance 配置值
  3. 检查系统时区设置

6. Nonce 重复

错误码:1007

可能原因

  • 短时间内重复请求
  • Nonce 生成器配置错误

处理方法

SDK 自动处理 Nonce,正常情况不会出现此错误。如果频繁出现,请检查:

  1. 是否存在代码逻辑错误导致的重复调用
  2. 是否使用了自定义的 Nonce 生成器

7. 连接超时

错误信息Connect timeout

可能原因

  • 网络不通
  • 防火墙阻止
  • 服务器地址错误

处理方法

java
try {
    // 调用 API
} catch (ApiException e) {
    if (e.getMessage().contains("timeout")) {
        log.error("连接超时: {}", e.getMessage());
        return Result.error(504, "连接超时");
    }
    throw e;
}

解决方案

  1. 检查网络连接
  2. 增加 connect-timeout 配置值
  3. 检查防火墙设置
  4. 确认网关地址正确

8. 加密异常

异常类型CryptoException

可能原因

  • 密钥格式错误
  • 密钥加载失败
  • 加密算法初始化失败

处理方法

java
try {
    // 调用 API
} catch (CryptoException e) {
    log.error("加密异常: {}", e.getMessage(), e);
    return Result.error(500, "数据处理异常");
}

解决方案

  1. 检查密钥格式是否正确(PEM 格式)
  2. 确认密钥包含完整的头尾标记
  3. 检查密钥是否已损坏

日志记录

记录异常日志

java
@Slf4j
@Service
public class UserService {

    @Autowired
    private UserApiClient userApiClient;

    public User getUser(String userId) {
        try {
            return userApiClient.getDetail(userId);
        } catch (ApiException e) {
            log.error("查询用户失败, userId: {}, error: {}", userId, e.getMessage(), e);
            throw new BusinessException("查询用户失败");
        }
    }
}

启用 SDK 调试日志

在配置文件中设置:

yaml
logging:
  level:
    cn.hztech.sdk: DEBUG

调试日志会记录:

  • 请求详情(URL、参数、加密后的报文)
  • 响应详情(状态码、解密后的数据)
  • 加解密过程
  • 重试记录

重试机制

SDK 内置了自动重试机制,配置方法详见 配置说明文档

重试策略

SDK 会在以下情况自动重试:

  • 网络连接失败
  • 读取超时
  • 5xx 服务器错误

注意:4xx 客户端错误(如参数错误、用户不存在)不会重试。

自定义重试逻辑

如果需要自定义重试逻辑,可以捕获异常并手动重试:

java
@Service
public class UserService {

    @Autowired
    private UserApiClient userApiClient;

    @Retryable(value = {ApiException.class},
               maxAttempts = 3,
               backoff = @Backoff(delay = 1000))
    public User getUserWithRetry(String userId) {
        return userApiClient.getDetail(userId);
    }
}

使用 Spring Retry 注解需要添加依赖:

xml
<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
</dependency>

最佳实践

1. 使用全局异常处理器

统一处理所有异常,避免在业务代码中大量 try-catch。

2. 记录详细的错误日志

记录错误信息时,应包括:

  • 错误码
  • 错误消息
  • 请求参数
  • 堆栈跟踪
java
log.error("API 调用失败, code: {}, message: {}, params: {}",
    e.getErrorCode(), e.getMessage(), params, e);

3. 友好的错误提示

向客户端返回友好的错误提示,不要直接暴露技术细节。

java
// 不好的做法
return Result.error(500, "CryptoException: Invalid key format");

// 好的做法
log.error("密钥格式错误: {}", e.getMessage());
return Result.error(500, "系统异常,请联系管理员");

4. 区分可重试和不可重试的错误

  • 可重试:网络超时、5xx 错误
  • 不可重试:参数错误、资源不存在

5. 监控和告警

对异常进行监控和告警:

java
@ExceptionHandler(ApiException.class)
public Result<Void> handleApiException(ApiException e) {
    // 记录监控指标
    meterRegistry.counter("sdk.api.errors",
        "error_code", e.getErrorCode()).increment();

    // 严重错误发送告警
    if (isCriticalError(e.getErrorCode())) {
        alertService.sendAlert("SDK 调用失败", e.getMessage());
    }

    return Result.error(500, "系统错误");
}

参考文档

最近更新