You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Kotlin中API响应的错误与异常处理优化方案咨询

优化API错误与异常处理方案

针对你的需求,现有实现的核心问题是APIResponse的OnFailure只能接受ErrorResponseDto,无法直接传递异常,导致你需要手动将异常转成ErrorResponseDto,显得不够优雅。以下是两种更优的处理方案:

方案一:扩展APIResponse密封类,区分API错误与异常

将APIResponse改为密封类,新增专门处理异常的子类,让调用方可以明确区分「API返回的错误响应」和「客户端抛出的异常」(如网络错误、序列化失败等):

修改APIResponse定义

sealed class APIResponse<out T> {
    // 接口返回成功数据
    data class OnSuccess<T>(val data: T) : APIResponse<T>()
    // 接口返回错误状态码及错误响应体
    data class OnApiFailure(val error: ErrorResponseDto) : APIResponse<Nothing>()
    // 客户端执行过程中抛出异常
    data class OnException(val exception: Exception) : APIResponse<Nothing>()
}

修改loginUser实现

override suspend fun loginUser(loginRequestModel: LoginRequestModel): APIResponse<LoginResponseDto> {
    return try {
        val response = httpClient
            .post("https://endpoint") {
                contentType(ContentType.Application.Json)
                setBody(LoginRequestDto(/* body data */))
            }

        when (response.status.value) {
            200 -> APIResponse.OnSuccess(response.body())
            else -> APIResponse.OnApiFailure(response.body())
        }
    } catch (exception: Exception) {
        if (exception is CancellationException) {
            Timber.e(exception)
            throw exception // 协程取消异常需重新抛出,不纳入业务错误处理
        }
        Timber.e(exception)
        APIResponse.OnException(exception)
    }
}

调用方处理示例

when (val response = loginUser(request)) {
    is APIResponse.OnSuccess -> // 处理成功数据
    is APIResponse.OnApiFailure -> // 处理API返回的错误响应
    is APIResponse.OnException -> // 处理客户端异常(如网络错误)
}

这个方案的优势是边界清晰,调用方可以针对不同错误类型做精细化处理。

方案二:统一错误模型,优雅转换异常为ErrorResponseDto

如果希望保持现有APIResponse的结构不变,只需优化异常转ErrorResponseDto的代码,可通过扩展函数封装转换逻辑,避免重复代码:

添加异常转ErrorResponseDto的扩展函数

fun Exception.toErrorResponseDto(): ErrorResponseDto {
    return ErrorResponseDto(
        errors = listOf(
            ErrorDto(
                code = localizedMessage ?: "CLIENT_EXCEPTION",
                detail = message ?: "未知客户端错误"
            )
        )
    )
}

修改loginUser中的catch块

catch (exception: Exception) {
    if (exception is CancellationException) {
        Timber.e(exception)
        throw exception
    }
    Timber.e(exception)
    APIResponse.OnFailure(exception.toErrorResponseDto())
}

这个方案的优势是保持原有接口结构不变,同时让异常转换逻辑更整洁、可复用。

关键注意点

  • 保留CancellationException的重新抛出是正确的,因为协程取消属于流程控制,不应被包装为业务错误。
  • 若选择方案一,建议在OnException中额外记录异常日志,便于排查问题。

内容的提问来源于stack exchange,提问作者ant2009

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 15:34:57