重构: 将 AuthService、ApiService、WebSocketClient 改为适配器

各服务现在通过主构造函数接收各自的客户端提供器;
为在迁移期间保持现有调用点可编译,保留了向后兼容的次构造函数。
This commit is contained in:
Kevin
2026-03-12 10:35:52 +08:00
parent 0f53d7ce34
commit e3f370a22f
5 changed files with 197 additions and 262 deletions

View File

@@ -2,6 +2,8 @@ package com.huaga.life_echo.network
import com.huaga.life_echo.data.auth.TokenManager
import com.huaga.life_echo.network.models.*
import com.huaga.life_echo.network.runtime.RestClientProfile
import com.huaga.life_echo.network.runtime.RestClientProvider
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.engine.android.*
@@ -13,33 +15,53 @@ import io.ktor.client.plugins.HttpTimeout
import io.ktor.client.request.*
import io.ktor.http.*
import io.ktor.serialization.kotlinx.json.*
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
class ApiService(
private val provider: RestClientProvider,
private val tokenManager: TokenManager? = null,
private val authService: AuthService? = null
) {
private val client by lazy {
HttpClient(Android) {
install(ContentNegotiation) {
json(Json {
ignoreUnknownKeys = true
})
}
install(Logging) {
level = LogLevel.INFO
}
install(HttpTimeout) {
requestTimeoutMillis = 45_000 // 单次请求总超时(如创建订单)
connectTimeoutMillis = 15_000 // 连接超时
socketTimeoutMillis = 45_000 // 读写超时
}
@Suppress("DEPRECATION")
@Deprecated("Use the provider-based constructor via AppContainer")
constructor(
tokenManager: TokenManager? = null,
authService: AuthService? = null,
) : this(
provider = createLegacyProvider(tokenManager, authService),
tokenManager = tokenManager,
authService = authService,
)
// 使用Ktor内置Auth插件自动添加Bearer token401时自动刷新并重试
private val client get() = provider.getClient(RestClientProfile.API)
companion object {
private val BASE_URL = com.huaga.life_echo.config.AppConfig.BASE_URL
private fun createLegacyProvider(
tokenManager: TokenManager?,
authService: AuthService?,
): RestClientProvider = RestClientProvider { profile ->
when (profile) {
RestClientProfile.AUTH -> HttpClient(Android) {
install(ContentNegotiation) {
json(Json { ignoreUnknownKeys = true })
}
install(Logging) { level = LogLevel.INFO }
}
RestClientProfile.API -> HttpClient(Android) {
install(ContentNegotiation) {
json(Json { ignoreUnknownKeys = true })
}
install(Logging) { level = LogLevel.INFO }
install(HttpTimeout) {
requestTimeoutMillis = 45_000
connectTimeoutMillis = 15_000
socketTimeoutMillis = 45_000
}
if (tokenManager != null && authService != null) {
install(Auth) {
bearer {
@@ -85,9 +107,7 @@ class ApiService(
}
}
}
companion object {
private val BASE_URL = com.huaga.life_echo.config.AppConfig.BASE_URL
}
}
// ==================== 对话相关API ====================
@@ -138,7 +158,7 @@ class ApiService(
suspend fun deleteConversation(conversationId: String): Result<Unit> {
return try {
val response = client.delete("$BASE_URL/api/conversations/$conversationId") {
client.delete("$BASE_URL/api/conversations/$conversationId") {
contentType(ContentType.Application.Json)
}
Result.success(Unit)
@@ -205,12 +225,9 @@ class ApiService(
}
}
/**
* 清除章节(将章节标记为 disabled
*/
suspend fun disableChapter(chapterId: String): Result<Unit> {
return try {
val response = client.delete("$BASE_URL/api/chapters/$chapterId") {
client.delete("$BASE_URL/api/chapters/$chapterId") {
contentType(ContentType.Application.Json)
}
Result.success(Unit)
@@ -300,13 +317,8 @@ class ApiService(
}
}
// ==================== 支付订单API(微信/支付宝) ====================
// ==================== 支付订单API ====================
/**
* 创建支付订单
* 返回微信支付参数或支付宝订单字符串。
* 失败时尽量返回后端返回的 detail 信息,便于 debug。
*/
suspend fun createPaymentOrder(
planId: String,
paymentMethod: String
@@ -324,14 +336,10 @@ class ApiService(
}
Result.success(response.body())
} catch (e: Exception) {
// 超时、网络异常等:保留原始异常信息
Result.failure(e)
}
}
/**
* 解析 FastAPI 错误响应中的 detail 字段(字符串或数组),用于展示后端返回的详尽错误信息
*/
private fun parseApiErrorDetail(errorBody: String?): String {
if (errorBody.isNullOrBlank()) return ""
return try {
@@ -350,9 +358,6 @@ class ApiService(
}
}
/**
* 查询支付订单状态
*/
suspend fun getPaymentOrderStatus(orderId: String): Result<PaymentOrderStatusResponse> {
return try {
val response = client.get("$BASE_URL/api/payment/order/$orderId/status") {
@@ -364,9 +369,6 @@ class ApiService(
}
}
/**
* 获取支付订单列表(新接口)
*/
suspend fun getPaymentOrders(): Result<List<PaymentOrderStatusResponse>> {
return try {
val response = client.get("$BASE_URL/api/payment/orders") {
@@ -380,10 +382,6 @@ class ApiService(
// ==================== 用户相关API ====================
/**
* 测试订阅开关(仅当服务端 ENABLE_TEST_SUBSCRIPTION=1 时可用)
* 用于微信支付审核通过前模拟付费通过,体验订阅额度。
*/
suspend fun setTestSubscription(
action: String,
planId: String = "pro"
@@ -433,14 +431,13 @@ class ApiService(
Result.failure(Exception("提交失败: ${response.status}"))
}
} catch (e: Exception) {
// 提交失败时也返回成功,避免阻塞用户
Result.success(Unit)
}
}
// ==================== 回忆录状态相关API ====================
suspend fun getMemoirState(): Result<com.huaga.life_echo.network.models.MemoirStateDto> {
suspend fun getMemoirState(): Result<MemoirStateDto> {
return try {
val response = client.get("$BASE_URL/api/memoir-state") {
contentType(ContentType.Application.Json)
@@ -453,7 +450,7 @@ class ApiService(
// ==================== 任务状态相关API ====================
suspend fun getTasksStatus(): Result<com.huaga.life_echo.network.models.TasksStatusDto> {
suspend fun getTasksStatus(): Result<TasksStatusDto> {
return try {
val response = client.get("$BASE_URL/api/tasks/status") {
contentType(ContentType.Application.Json)

View File

@@ -2,6 +2,8 @@ package com.huaga.life_echo.network
import com.huaga.life_echo.config.AppConfig
import com.huaga.life_echo.network.models.*
import com.huaga.life_echo.network.runtime.RestClientProfile
import com.huaga.life_echo.network.runtime.RestClientProvider
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.engine.android.Android
@@ -24,31 +26,30 @@ import io.ktor.serialization.kotlinx.json.json
import kotlinx.serialization.json.Json
import java.io.File
/**
* 认证服务
* 处理用户注册、登录、刷新令牌、登出等操作
*/
class AuthService {
private val client = HttpClient(Android) {
class AuthService(
private val provider: RestClientProvider,
) {
constructor() : this(
RestClientProvider { _ ->
HttpClient(Android) {
install(ContentNegotiation) {
json(Json {
ignoreUnknownKeys = true
encodeDefaults = false
})
}
install(Logging) {
level = LogLevel.INFO
install(Logging) { level = LogLevel.INFO }
}
}
)
private val client get() = provider.getClient(RestClientProfile.AUTH)
companion object {
private val BASE_URL = AppConfig.BASE_URL
private val AUTH_BASE = "$BASE_URL/api/auth"
}
/**
* 用户注册
*/
suspend fun register(
phone: String,
password: String,
@@ -68,7 +69,6 @@ class AuthService {
Result.success(tokenResponse)
}
HttpStatusCode.BadRequest -> {
// 尝试解析简单错误响应
try {
val error = response.body<ErrorResponse>()
Result.failure(Exception(error.detail))
@@ -77,13 +77,11 @@ class AuthService {
}
}
HttpStatusCode.UnprocessableEntity -> {
// FastAPI验证错误422- 尝试解析验证错误详情
try {
val validationError = response.body<ValidationErrorResponse>()
val errorMessages = validationError.detail.joinToString(", ") { it.msg }
Result.failure(Exception("数据验证失败: $errorMessages"))
} catch (e: Exception) {
// 如果无法解析为验证错误,尝试解析为简单错误
try {
val error = response.body<ErrorResponse>()
Result.failure(Exception(error.detail))
@@ -101,9 +99,6 @@ class AuthService {
}
}
/**
* 用户登录
*/
suspend fun login(phone: String, password: String, agreedToTerms: Boolean): Result<TokenResponse> {
return try {
val response = client.post("$AUTH_BASE/login") {
@@ -120,7 +115,6 @@ class AuthService {
Result.failure(Exception("手机号或密码错误"))
}
HttpStatusCode.BadRequest -> {
// 尝试解析简单错误响应
try {
val error = response.body<ErrorResponse>()
Result.failure(Exception(error.detail))
@@ -129,13 +123,11 @@ class AuthService {
}
}
HttpStatusCode.UnprocessableEntity -> {
// FastAPI验证错误422- 尝试解析验证错误详情
try {
val validationError = response.body<ValidationErrorResponse>()
val errorMessages = validationError.detail.joinToString(", ") { it.msg }
Result.failure(Exception("数据验证失败: $errorMessages"))
} catch (e: Exception) {
// 如果无法解析为验证错误,尝试解析为简单错误
try {
val error = response.body<ErrorResponse>()
Result.failure(Exception(error.detail))
@@ -153,9 +145,6 @@ class AuthService {
}
}
/**
* 刷新访问令牌
*/
suspend fun refreshToken(refreshToken: String): Result<TokenResponse> {
return try {
val response = client.post("$AUTH_BASE/refresh") {
@@ -180,9 +169,6 @@ class AuthService {
}
}
/**
* 用户登出
*/
suspend fun logout(accessToken: String, refreshToken: String): Result<Unit> {
return try {
val response = client.post("$AUTH_BASE/logout") {
@@ -192,24 +178,15 @@ class AuthService {
}
when (response.status) {
HttpStatusCode.OK -> {
Result.success(Unit)
}
HttpStatusCode.Unauthorized -> {
Result.failure(Exception("未授权"))
}
else -> {
Result.failure(Exception("登出失败: ${response.status}"))
}
HttpStatusCode.OK -> Result.success(Unit)
HttpStatusCode.Unauthorized -> Result.failure(Exception("未授权"))
else -> Result.failure(Exception("登出失败: ${response.status}"))
}
} catch (e: Exception) {
Result.failure(Exception("网络错误: ${e.message}", e))
}
}
/**
* 获取当前用户信息
*/
suspend fun getCurrentUser(accessToken: String): Result<UserResponse> {
return try {
val response = client.get("$AUTH_BASE/me") {
@@ -221,21 +198,14 @@ class AuthService {
val userResponse = response.body<UserResponse>()
Result.success(userResponse)
}
HttpStatusCode.Unauthorized -> {
Result.failure(Exception("未授权"))
}
else -> {
Result.failure(Exception("获取用户信息失败: ${response.status}"))
}
HttpStatusCode.Unauthorized -> Result.failure(Exception("未授权"))
else -> Result.failure(Exception("获取用户信息失败: ${response.status}"))
}
} catch (e: Exception) {
Result.failure(Exception("网络错误: ${e.message}", e))
}
}
/**
* 上传用户头像
*/
suspend fun uploadAvatar(accessToken: String, imageFile: File): Result<UserResponse> {
return try {
val response = client.post("$AUTH_BASE/me/avatar") {
@@ -270,25 +240,14 @@ class AuthService {
Result.failure(Exception("上传失败: ${response.status}"))
}
}
HttpStatusCode.Unauthorized -> {
Result.failure(Exception("未授权"))
}
else -> {
Result.failure(Exception("上传头像失败: ${response.status}"))
}
HttpStatusCode.Unauthorized -> Result.failure(Exception("未授权"))
else -> Result.failure(Exception("上传头像失败: ${response.status}"))
}
} catch (e: Exception) {
Result.failure(Exception("网络错误: ${e.message}", e))
}
}
// ============================================================================
// 短信验证码相关方法
// ============================================================================
/**
* 发送短信验证码
*/
suspend fun sendVerificationCode(phone: String, purpose: String): Result<SmsResponse> {
return try {
val response = client.post("$AUTH_BASE/sms/send") {
@@ -317,19 +276,13 @@ class AuthService {
Result.failure(Exception("发送过于频繁,请稍后再试"))
}
}
else -> {
Result.failure(Exception("发送验证码失败: ${response.status}"))
}
else -> Result.failure(Exception("发送验证码失败: ${response.status}"))
}
} catch (e: Exception) {
Result.failure(Exception("网络错误: ${e.message}", e))
}
}
/**
* 验证码登录/注册(统一接口)
* 如果用户不存在会自动注册需要提供nickname
*/
suspend fun loginWithSms(phone: String, code: String, agreedToTerms: Boolean, nickname: String? = null): Result<TokenResponse> {
return try {
val response = client.post("$AUTH_BASE/login/sms") {
@@ -350,18 +303,13 @@ class AuthService {
Result.failure(Exception("登录失败: ${response.status}"))
}
}
else -> {
Result.failure(Exception("登录失败: ${response.status}"))
}
else -> Result.failure(Exception("登录失败: ${response.status}"))
}
} catch (e: Exception) {
Result.failure(Exception("网络错误: ${e.message}", e))
}
}
/**
* 验证码注册
*/
suspend fun registerWithSms(
phone: String,
code: String,
@@ -389,18 +337,13 @@ class AuthService {
Result.failure(Exception("注册失败: ${response.status}"))
}
}
else -> {
Result.failure(Exception("注册失败: ${response.status}"))
}
else -> Result.failure(Exception("注册失败: ${response.status}"))
}
} catch (e: Exception) {
Result.failure(Exception("网络错误: ${e.message}", e))
}
}
/**
* 重置密码
*/
suspend fun resetPassword(phone: String, code: String, newPassword: String): Result<MessageResponse> {
return try {
val response = client.post("$AUTH_BASE/password/reset") {
@@ -421,18 +364,13 @@ class AuthService {
Result.failure(Exception("重置密码失败: ${response.status}"))
}
}
else -> {
Result.failure(Exception("重置密码失败: ${response.status}"))
}
else -> Result.failure(Exception("重置密码失败: ${response.status}"))
}
} catch (e: Exception) {
Result.failure(Exception("网络错误: ${e.message}", e))
}
}
/**
* 修改密码(已登录)
*/
suspend fun changePassword(accessToken: String, oldPassword: String, newPassword: String): Result<MessageResponse> {
return try {
val response = client.post("$AUTH_BASE/password/change") {
@@ -454,21 +392,14 @@ class AuthService {
Result.failure(Exception("修改密码失败: ${response.status}"))
}
}
HttpStatusCode.Unauthorized -> {
Result.failure(Exception("未授权"))
}
else -> {
Result.failure(Exception("修改密码失败: ${response.status}"))
}
HttpStatusCode.Unauthorized -> Result.failure(Exception("未授权"))
else -> Result.failure(Exception("修改密码失败: ${response.status}"))
}
} catch (e: Exception) {
Result.failure(Exception("网络错误: ${e.message}", e))
}
}
/**
* 修改手机号
*/
suspend fun changePhone(accessToken: String, newPhone: String, code: String): Result<UserResponse> {
return try {
val response = client.post("$AUTH_BASE/phone/change") {
@@ -490,21 +421,14 @@ class AuthService {
Result.failure(Exception("修改手机号失败: ${response.status}"))
}
}
HttpStatusCode.Unauthorized -> {
Result.failure(Exception("未授权"))
}
else -> {
Result.failure(Exception("修改手机号失败: ${response.status}"))
}
HttpStatusCode.Unauthorized -> Result.failure(Exception("未授权"))
else -> Result.failure(Exception("修改手机号失败: ${response.status}"))
}
} catch (e: Exception) {
Result.failure(Exception("网络错误: ${e.message}", e))
}
}
/**
* 登出所有设备
*/
suspend fun logoutAll(accessToken: String): Result<MessageResponse> {
return try {
val response = client.post("$AUTH_BASE/logout/all") {
@@ -516,21 +440,14 @@ class AuthService {
val messageResponse = response.body<MessageResponse>()
Result.success(messageResponse)
}
HttpStatusCode.Unauthorized -> {
Result.failure(Exception("未授权"))
}
else -> {
Result.failure(Exception("登出失败: ${response.status}"))
}
HttpStatusCode.Unauthorized -> Result.failure(Exception("未授权"))
else -> Result.failure(Exception("登出失败: ${response.status}"))
}
} catch (e: Exception) {
Result.failure(Exception("网络错误: ${e.message}", e))
}
}
/**
* 更新用户昵称
*/
suspend fun updateNickname(accessToken: String, nickname: String): Result<UserResponse> {
return try {
val response = client.put("$AUTH_BASE/me/nickname") {
@@ -552,12 +469,8 @@ class AuthService {
Result.failure(Exception("更新昵称失败: ${response.status}"))
}
}
HttpStatusCode.Unauthorized -> {
Result.failure(Exception("未授权"))
}
else -> {
Result.failure(Exception("更新昵称失败: ${response.status}"))
}
HttpStatusCode.Unauthorized -> Result.failure(Exception("未授权"))
else -> Result.failure(Exception("更新昵称失败: ${response.status}"))
}
} catch (e: Exception) {
Result.failure(Exception("网络错误: ${e.message}", e))

View File

@@ -1,5 +1,6 @@
package com.huaga.life_echo.network
import com.huaga.life_echo.network.runtime.RealtimeTransportProvider
import io.ktor.client.*
import io.ktor.client.engine.okhttp.*
import io.ktor.client.plugins.websocket.*
@@ -12,20 +13,21 @@ import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put
import kotlinx.serialization.json.putJsonObject
import android.util.Log
class WebSocketClient {
private val client by lazy {
open class WebSocketClient(
private val transportProvider: RealtimeTransportProvider,
) {
constructor() : this(
RealtimeTransportProvider {
HttpClient(OkHttp) {
install(WebSockets)
// 注意WebSocket不需要ContentNegotiation插件因为消息是手动序列化的
// ContentNegotiation插件会导致尝试序列化WebSocket会话对象引发序列化错误
install(Logging) {
level = LogLevel.INFO
}
install(Logging) { level = LogLevel.INFO }
}
}
)
private val client get() = transportProvider.getClient()
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
private var session: DefaultWebSocketSession? = null
@@ -34,9 +36,9 @@ class WebSocketClient {
private var isConnected = false
private var currentConversationId: String? = null
private var currentToken: String? = null
private var isGenerating = false // 是否正在生成回复
private var currentGenerationJob: Job? = null // 当前生成任务
private var onErrorCallback: ((String) -> Unit)? = null // 错误回调
private var isGenerating = false
private var currentGenerationJob: Job? = null
private var onErrorCallback: ((String) -> Unit)? = null
companion object {
private const val TAG = "WebSocketClient"
@@ -45,13 +47,12 @@ class WebSocketClient {
private const val MAX_RECONNECT_ATTEMPTS = 5
}
suspend fun connect(
open suspend fun connect(
conversationId: String,
token: String? = null,
onMessage: (WebSocketMessage) -> Unit,
onError: ((String) -> Unit)? = null
) {
// 如果已连接,先断开
if (isConnected) {
disconnect()
}
@@ -68,7 +69,6 @@ class WebSocketClient {
Log.d(TAG, "开始连接WebSocket: $url")
try {
// 建立WebSocket连接
session = client.webSocketSession {
url {
takeFrom(url)
@@ -80,15 +80,12 @@ class WebSocketClient {
currentConversationId = conversationId
currentToken = token
// 启动消息接收协程在设置isConnected之前
val receiveJob = scope.launch {
receiveMessages(onMessage)
}
// 等待一小段时间确保连接建立
delay(100)
// 检查session是否有效
if (session == null || session?.isActive != true) {
Log.e(TAG, "WebSocket连接失败session无效")
throw Exception("WebSocket连接失败session无效")
@@ -96,7 +93,6 @@ class WebSocketClient {
Log.d(TAG, "WebSocket session有效准备发送连接消息")
// 发送连接消息不等待确认由服务器返回connect消息时再设置isConnected
try {
sendMessage(WebSocketMessage(
type = MessageType.connect,
@@ -105,15 +101,12 @@ class WebSocketClient {
))
Log.d(TAG, "连接消息已发送,等待服务器确认")
} catch (e: Exception) {
// 发送失败,连接可能有问题
Log.e(TAG, "发送连接消息失败: ${e.message}", e)
isConnected = false
receiveJob.cancel()
throw Exception("发送连接消息失败: ${e.message}")
}
// 注意isConnected将在收到服务器的connect确认消息时设置为true
} catch (e: Exception) {
Log.e(TAG, "WebSocket连接异常: ${e.message}", e)
isConnected = false
@@ -126,7 +119,6 @@ class WebSocketClient {
private suspend fun receiveMessages(onMessage: (WebSocketMessage) -> Unit) {
try {
// 持续接收消息,直到连接关闭
while (true) {
val frame = session?.incoming?.receive() as? Frame.Text
?: break
@@ -137,13 +129,11 @@ class WebSocketClient {
val message = Json.decodeFromString<WebSocketMessage>(text)
// 如果收到connect消息设置连接状态为已连接
if (message.type == MessageType.connect) {
isConnected = true
Log.d(TAG, "WebSocket连接已确认状态设置为已连接")
}
// 如果收到error消息设置连接状态为未连接
if (message.type == MessageType.error) {
isConnected = false
val errorMsg = message.getString("message") ?: "未知错误"
@@ -155,19 +145,16 @@ class WebSocketClient {
onMessage(message)
messageFlow.emit(message)
} catch (e: Exception) {
// JSON解析失败记录错误但继续接收
Log.e(TAG, "消息解析失败: ${e.message}", e)
onErrorCallback?.invoke("消息解析失败: ${e.message}")
}
}
} catch (e: Exception) {
// 连接异常
Log.e(TAG, "接收消息异常: ${e.message}", e)
isConnected = false
val errorMsg = "连接异常: ${e.message}"
onErrorCallback?.invoke(errorMsg)
// 触发重连
reconnectJob?.cancel()
reconnectJob = scope.launch {
Log.d(TAG, "开始重连...")
@@ -194,7 +181,6 @@ class WebSocketClient {
session?.send(Frame.Text(json))
Log.d(TAG, "消息发送成功: ${message.type}")
} catch (e: Exception) {
// 发送失败,可能连接已断开
Log.e(TAG, "发送消息失败: ${e.message}", e)
isConnected = false
onErrorCallback?.invoke("发送消息失败: ${e.message}")
@@ -211,9 +197,6 @@ class WebSocketClient {
))
}
/**
* 发送分段语音(长语音边录边传)
*/
suspend fun sendAudioSegment(
audioBytes: ByteArray,
conversationId: String,
@@ -250,12 +233,6 @@ class WebSocketClient {
))
}
/**
* 发送完整的音频消息(类似微信语音消息)
* @param audioBytes 音频文件字节数组
* @param conversationId 对话 ID
* @param duration 音频时长(秒)
*/
suspend fun sendAudioMessage(audioBytes: ByteArray, conversationId: String, duration: Int) {
Log.d(TAG, "准备发送音频消息,大小: ${audioBytes.size} 字节, 时长: $duration")
if (!isConnected) {
@@ -275,9 +252,6 @@ class WebSocketClient {
))
}
/**
* 仅转写:发送音频获取文字,不落库、不触发 Agent用于「转文字」后客户端再发文本
*/
suspend fun sendTranscribeOnly(audioBytes: ByteArray, conversationId: String) {
Log.d(TAG, "发送仅转写请求,大小: ${audioBytes.size} 字节")
if (!isConnected) throw Exception("WebSocket未连接请先建立连接")
@@ -311,9 +285,6 @@ class WebSocketClient {
))
}
/**
* 取消当前正在生成的回复
*/
suspend fun cancelGeneration(conversationId: String) {
isGenerating = false
currentGenerationJob?.cancel()
@@ -324,19 +295,13 @@ class WebSocketClient {
))
}
/**
* 检查是否正在生成回复
*/
fun isGenerating(): Boolean = isGenerating
/**
* 设置生成状态
*/
fun setGenerating(generating: Boolean) {
isGenerating = generating
}
suspend fun disconnect() {
open suspend fun disconnect() {
Log.d(TAG, "断开WebSocket连接")
isConnected = false
isGenerating = false
@@ -370,5 +335,5 @@ class WebSocketClient {
}
}
fun isConnected(): Boolean = isConnected
open fun isConnected(): Boolean = isConnected
}

View File

@@ -0,0 +1,32 @@
package com.huaga.life_echo.network
import com.huaga.life_echo.network.runtime.RestClientProfile
import com.huaga.life_echo.network.runtime.RestClientProvider
import io.ktor.client.HttpClient
import org.junit.Assert.assertEquals
import org.junit.Test
import org.mockito.Mockito
class ApiServiceProviderTest {
@Test
fun api_service_uses_api_profile_from_provider() {
val requestedProfiles = mutableListOf<RestClientProfile>()
val provider = RestClientProvider { profile ->
requestedProfiles += profile
Mockito.mock(HttpClient::class.java)
}
ApiService(
provider = provider,
tokenManager = null,
authService = null
)
assertEquals(
"ApiService should not eagerly request a client at construction time",
emptyList<RestClientProfile>(),
requestedProfiles
)
}
}

View File

@@ -0,0 +1,28 @@
package com.huaga.life_echo.network
import com.huaga.life_echo.network.runtime.RestClientProfile
import com.huaga.life_echo.network.runtime.RestClientProvider
import io.ktor.client.HttpClient
import org.junit.Assert.assertEquals
import org.junit.Test
import org.mockito.Mockito
class AuthServiceProviderTest {
@Test
fun auth_service_uses_auth_profile_from_provider_when_constructed() {
val requestedProfiles = mutableListOf<RestClientProfile>()
val provider = RestClientProvider { profile ->
requestedProfiles += profile
Mockito.mock(HttpClient::class.java)
}
AuthService(provider = provider)
assertEquals(
"AuthService should not eagerly request a client at construction time",
emptyList<RestClientProfile>(),
requestedProfiles
)
}
}