【发布时间】:2020-08-31 19:09:36
【问题描述】:
好久没在这里写东西了,现在真的需要建议)
我使用 Retrofit2 作为 api 客户端。服务器 API 有一个端点,例如 /api/stats,它接收 JSON 正文 request 并返回 JSON response 为:
data class StatsResult<T>(
@SerializedName("code") val code: Int,
@SerializedName("message") val msg: String?,
@SerializedName("request_id") val requestId: String?,
@SerializedName("data") val data: T?
)
如果出现错误,data 为空。
否则,data 是一个数组,可以包含不同类型的数据,具体取决于 request 的类型。
例如:
请求1:
{
"type": "type1",
"params": {
}
}
Response:
{
"code": 0,
"request_id": "...",
"data": [
{
"key1": "value1",
"key2": "value2"
},
{
"key1": "value3",
"key2": "value4"
}
]
}
请求2:
{
"type": "type2",
"params": {
}
}
Response:
{
"code": 0,
"request_id": "...",
"data": [
{
"key3": "value1",
"key4": "value2"
},
{
"key3": "value3",
"key4": "value4"
}
]
}
简而言之,这是我的实现:
interface StatsApi {
@POST("/api/stats")
suspend fun getStats(@Body request: StatsRequest): ApiResponse<StatsData>
}
sealed class ApiResponse<out T: Any> {
data class Success<T: Any>(val body: T): ApiResponse<T>()
object Unauthorized : ApiResponse<Nothing>()
object Forbidden: ApiResponse<Nothing>()
object NetworkError: ApiResponse<Nothing>()
data class Error(val msg: String? = null): ApiResponse<Nothing>()
data class Exception(val t: Throwable): ApiResponse<Nothing>()
}
typealias StatsData = StatsResult<List<BaseStatsDto>>
open class BaseStatsDto()
class Type1StatsDto: BaseStatsDto() {
@SerializedName("key1") var key1: String? = null
@SerializedName("key2") var key2: String? = null
}
class Type2StatsDto: BaseStatsDto() {
@SerializedName("key3") var key3: String? = null
@SerializedName("key4") var key4: String? = null
}
所以我尝试使用开放/抽象类BaseStatsDto 解决这个问题,然后将其转换为最终类。但是这个解决方案不起作用。
对于响应处理,我使用 CallAdapter.Factory() 和自定义 Call<>:
open class ApiResponseCall<T : Any>(
private val delegate: Call<T>
) : Call<ApiResponse<T>> {
override fun enqueue(callback: Callback<ApiResponse<T>>) {
return delegate.enqueue(object : Callback<T> {
override fun onFailure(call: Call<T>, t: Throwable) {
val rsp = when (t) {
is IOException -> ApiResponse.NetworkError
else -> ApiResponse.Exception(t)
}
callback.onResponse(this@ApiResponseCall, Response.success(rsp))
}
override fun onResponse(call: Call<T>, response: Response<T>) {
val rsp: ApiResponse<T>
rsp = if (response.isSuccessful) {
val body = response.body()
ApiResponse.Success(body as T)
} else {
val code = response.code()
val error = response.errorBody()
when (code) {
401 -> ApiResponse.Unauthorized
403 -> ApiResponse.Forbidden
in 400..499 -> ApiResponse.Error("Client error")
in 500..599 -> ApiResponse.Error("Server error")
else -> ApiResponse.Exception(Exception("Unknown error"))
}
}
callback.onResponse(this@ApiResponseCall, Response.success(rsp))
}
})
}
...
}
我看到了另一种解决方案 - 具有单独的接口函数和单独的响应类型。它工作正常:
@POST("/api/stats")
suspend fun getType1Stats(@Body request: StatsRequest): ApiResponse<StatsResult<List<Type1StatsDto>>>
@POST("/api/stats")
suspend fun getType2Stats(@Body request: StatsRequest): ApiResponse<StatsResult<List<Type2StatsDto>>>
但如果统计数据类型数量增加,维护起来会非常不舒服。
我想要一个统计 API 端点。
【问题讨论】: