Browse Source

网络封装 KT加协同

wangmeng 2 tuần trước cách đây
mục cha
commit
a4fe8882d0
65 tập tin đã thay đổi với 4729 bổ sung1545 xóa
  1. 36 1
      app/build.gradle
  2. 9 9
      app/src/main/AndroidManifest.xml
  3. 14 7
      app/src/main/java/com/narutohuo/xindazhou/MainActivity.kt
  4. 365 0
      app/src/main/java/com/narutohuo/xindazhou/auth/AuthManager.kt
  5. 40 0
      app/src/main/java/com/narutohuo/xindazhou/auth/README.md
  6. 2 2
      base-common/src/main/java/com/narutohuo/xindazhou/common/auth/datasource/local/AuthLocalDataSource.kt
  7. 3 3
      base-common/src/main/java/com/narutohuo/xindazhou/common/auth/datasource/local/AuthLocalDataSourceImpl.kt
  8. 4 4
      base-common/src/main/java/com/narutohuo/xindazhou/common/auth/datasource/remote/AuthApi.kt
  9. 15 17
      base-common/src/main/java/com/narutohuo/xindazhou/common/auth/datasource/remote/AuthRemoteDataSource.kt
  10. 1 1
      base-common/src/main/java/com/narutohuo/xindazhou/common/auth/model/LoginRequest.kt
  11. 1 1
      base-common/src/main/java/com/narutohuo/xindazhou/common/auth/model/LoginResponse.kt
  12. 1 1
      base-common/src/main/java/com/narutohuo/xindazhou/common/auth/model/RegisterRequest.kt
  13. 109 0
      app/src/main/java/com/narutohuo/xindazhou/auth/repository/AuthRepository.kt
  14. 111 3
      base-common/src/main/java/com/narutohuo/xindazhou/common/auth/storage/TokenStore.kt
  15. 64 45
      app/src/main/java/com/narutohuo/xindazhou/user/ui/login/LoginActivity.kt
  16. 65 50
      app/src/main/java/com/narutohuo/xindazhou/user/ui/register/RegisterActivity.kt
  17. 12 0
      app/src/main/java/com/narutohuo/xindazhou/auth/ui/viewmodel/LoginState.kt
  18. 68 0
      app/src/main/java/com/narutohuo/xindazhou/auth/ui/viewmodel/LoginViewModel.kt
  19. 8 6
      app/src/main/java/com/narutohuo/xindazhou/user/ui/viewmodel/LoginViewModelFactory.kt
  20. 12 0
      app/src/main/java/com/narutohuo/xindazhou/auth/ui/viewmodel/RegisterState.kt
  21. 68 0
      app/src/main/java/com/narutohuo/xindazhou/auth/ui/viewmodel/RegisterViewModel.kt
  22. 8 6
      app/src/main/java/com/narutohuo/xindazhou/user/ui/viewmodel/RegisterViewModelFactory.kt
  23. 13 19
      app/src/main/java/com/narutohuo/xindazhou/launch/AppInitializer.kt
  24. 75 6
      app/src/main/java/com/narutohuo/xindazhou/shop/ui/ShopActivity.kt
  25. 0 17
      app/src/main/java/com/narutohuo/xindazhou/user/ui/constant/UiConstants.kt
  26. 0 68
      app/src/main/java/com/narutohuo/xindazhou/user/ui/viewmodel/LoginViewModel.kt
  27. 0 68
      app/src/main/java/com/narutohuo/xindazhou/user/ui/viewmodel/RegisterViewModel.kt
  28. 54 31
      app/src/main/java/com/narutohuo/xindazhou/user/ui/viewmodel/UserViewModel.kt
  29. 1 1
      app/src/main/res/layout/activity_login.xml
  30. 1 1
      app/src/main/res/layout/activity_register.xml
  31. 0 367
      base-common/src/main/java/com/narutohuo/xindazhou/common/auth/AuthManager.kt
  32. 0 105
      base-common/src/main/java/com/narutohuo/xindazhou/common/auth/repository/AuthRepository.kt
  33. 0 120
      base-common/src/main/java/com/narutohuo/xindazhou/common/auth/utils/JWTUtil.kt
  34. 15 3
      base-common/src/main/java/com/narutohuo/xindazhou/common/dialog/CascadePickerDialog.kt
  35. 1 1
      base-common/src/main/java/com/narutohuo/xindazhou/common/launch/AppLaunchManager.kt
  36. 3 3
      base-common/src/main/java/com/narutohuo/xindazhou/common/auth/NavigationCallback.kt
  37. 1 1
      base-common/src/main/java/com/narutohuo/xindazhou/common/launch/ui/OnboardingFragment.kt
  38. 117 64
      base-common/src/main/java/com/narutohuo/xindazhou/common/network/ApiBaseRemoteDataSource.kt
  39. 2 2
      base-common/src/main/java/com/narutohuo/xindazhou/common/network/ApiBaseRepository.kt
  40. 93 82
      base-common/src/main/java/com/narutohuo/xindazhou/common/network/ApiManager.kt
  41. 85 53
      base-common/src/main/java/com/narutohuo/xindazhou/common/network/ApiResponseParser.kt
  42. 32 17
      base-core/src/main/java/com/narutohuo/xindazhou/core/network/NetworkManager.kt
  43. 794 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/network/README.md
  44. 111 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/network/adapter/ApiResponseCallAdapterFactory.kt
  45. 135 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/network/exception/ApiException.kt
  46. 131 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/network/exception/ExceptionHandle.kt
  47. 53 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/network/interceptor/HeaderInterceptor.kt
  48. 120 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/network/interceptor/TokenRefreshInterceptor.kt
  49. 35 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/network/operator/ApiResponseOperator.kt
  50. 209 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/network/operator/GlobalApiOperator.kt
  51. 204 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/network/operator/RetryOperator.kt
  52. 119 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/network/response/ApiResponse.kt
  53. 820 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/network/response/ApiResponseExtensions.kt
  54. 201 0
      base-common/src/main/java/com/narutohuo/xindazhou/common/ui/BaseViewModel.kt
  55. 1 6
      base-common/src/main/java/com/narutohuo/xindazhou/common/version/VersionUpdateManager.kt
  56. 3 4
      base-common/src/main/java/com/narutohuo/xindazhou/common/version/datasource/remote/VersionRemoteDataSource.kt
  57. 14 4
      base-core/build.gradle
  58. 2 7
      base-core/src/main/AndroidManifest.xml
  59. 0 139
      base-core/src/main/java/com/mooxygen/user/wxapi/WXEntryActivity.kt
  60. 7 129
      base-core/src/main/java/com/narutohuo/xindazhou/wxapi/WXEntryActivity.kt
  61. 9 2
      capability-share/src/main/java/com/narutohuo/xindazhou/share/impl/ShareServiceImpl.kt
  62. 12 5
      capability-share/src/main/java/com/narutohuo/xindazhou/share/model/ShareConfig.kt
  63. 185 0
      capability-socketio/ARCHITECTURE_ANALYSIS.md
  64. 54 64
      capability-socketio/src/main/java/com/narutohuo/xindazhou/socketio/SocketIOManager.kt
  65. 1 0
      capability-socketio/src/main/java/com/narutohuo/xindazhou/socketio/factory/SocketIOServiceFactory.kt

+ 36 - 1
app/build.gradle

@@ -12,7 +12,7 @@ android {
     }
 
     defaultConfig {
-        applicationId "com.narutohuo.xindazhou"
+        applicationId "com.mooxygen.user"
         minSdk 26
         targetSdk 36
         versionCode 1
@@ -27,10 +27,34 @@ android {
         // 所有配置都在各自模块的 AndroidManifest.xml 中使用 @string/ 引用,完全不需要 manifestPlaceholders
     }
 
+    // 原来的 buildTypes 配置(已注释,保留作为参考)
+    // buildTypes {
+    //     release {
+    //         minifyEnabled false
+    //         proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+    //     }
+    // }
+
+    // 签名配置
+    signingConfigs {
+        release {
+            storeFile file('../Keystore.jks')  // Keystore 文件路径(相对于 app 模块)
+            storePassword '123456'              // Keystore 密码
+            keyAlias 'keystore'                 // 密钥别名
+            keyPassword '123456'                 // 密钥密码(如果和 storePassword 不同,请修改)
+        }
+    }
+
+    // 新的 buildTypes 配置(带签名)
     buildTypes {
         release {
             minifyEnabled false
             proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+            signingConfig signingConfigs.release  // 使用 release 签名配置
+        }
+        debug {
+            // Debug 也使用 release 签名,确保分享功能正常(微信需要正确的签名)
+            signingConfig signingConfigs.release
         }
     }
     compileOptions {
@@ -65,6 +89,17 @@ android {
             excludes += ['META-INF/NOTICE.txt']
             excludes += ['META-INF/notice.txt']
             excludes += ['META-INF/ASL2.0']
+            
+            // 排除未对齐的 x86_64 库(解决 16 KB alignment 警告)
+            // 只影响 x86_64 模拟器,不影响 ARM 设备(荣耀300等所有真实手机)
+            excludes += ['assets/libwbsafeedit_x86_64']
+            excludes += ['assets/libwbsafeedit_x86']  // 也排除 x86 版本(未对齐)
+        }
+        
+        // 启用 16 KB 页大小对齐(解决 16 KB alignment 警告)
+        // 自动对齐原生库(.so 文件),确保兼容 16 KB 页大小的设备
+        jniLibs {
+            useLegacyPackaging = false
         }
     }
 }

+ 9 - 9
app/src/main/AndroidManifest.xml

@@ -47,7 +47,7 @@
         android:maxSdkVersion="32" />
 
     <application
-        android:name=".XinDaZhouApplication"
+        android:name="com.narutohuo.xindazhou.XinDaZhouApplication"
         android:allowBackup="true"
         android:icon="@mipmap/ic_launcher"
         android:label="@string/app_name"
@@ -65,7 +65,7 @@
         <!-- 主界面 Activity(启动 Activity,多 Activity 架构) -->
         <!-- 包含底部导航栏,点击时跳转到对应的业务模块 Activity -->
         <activity
-            android:name=".MainActivity"
+            android:name="com.narutohuo.xindazhou.MainActivity"
             android:exported="true"
             android:theme="@style/Theme.XinDaZhou">
             <intent-filter>
@@ -76,43 +76,43 @@
         
         <!-- 登录 Activity -->
         <activity
-            android:name=".user.ui.login.LoginActivity"
+            android:name="com.narutohuo.xindazhou.auth.ui.login.LoginActivity"
             android:exported="false"
             android:theme="@style/Theme.XinDaZhou" />
         
         <!-- 注册 Activity -->
         <activity
-            android:name=".user.ui.register.RegisterActivity"
+            android:name="com.narutohuo.xindazhou.auth.ui.register.RegisterActivity"
             android:exported="false"
             android:theme="@style/Theme.XinDaZhou" />
         
         <!-- 用户模块 Activity -->
         <activity
-            android:name=".user.ui.UserActivity"
+            android:name="com.narutohuo.xindazhou.user.ui.UserActivity"
             android:exported="false"
             android:theme="@style/Theme.XinDaZhou" />
         
         <!-- 车辆模块 Activity -->
         <activity
-            android:name=".vehicle.ui.VehicleActivity"
+            android:name="com.narutohuo.xindazhou.vehicle.ui.VehicleActivity"
             android:exported="false"
             android:theme="@style/Theme.XinDaZhou" />
         
         <!-- 商城模块 Activity -->
         <activity
-            android:name=".shop.ui.ShopActivity"
+            android:name="com.narutohuo.xindazhou.shop.ui.ShopActivity"
             android:exported="false"
             android:theme="@style/Theme.XinDaZhou" />
         
         <!-- 社区模块 Activity -->
         <activity
-            android:name=".community.ui.CommunityActivity"
+            android:name="com.narutohuo.xindazhou.community.ui.CommunityActivity"
             android:exported="false"
             android:theme="@style/Theme.XinDaZhou" />
         
         <!-- 服务模块 Activity -->
         <activity
-            android:name=".service.ui.ServiceActivity"
+            android:name="com.narutohuo.xindazhou.service.ui.ServiceActivity"
             android:exported="false"
             android:theme="@style/Theme.XinDaZhou" />
         

+ 14 - 7
app/src/main/java/com/narutohuo/xindazhou/MainActivity.kt

@@ -2,6 +2,8 @@ package com.narutohuo.xindazhou
 
 import android.content.Intent
 import android.os.Bundle
+import androidx.core.view.WindowCompat
+import androidx.core.view.WindowInsetsControllerCompat
 import com.narutohuo.xindazhou.core.log.ILog
 import com.narutohuo.xindazhou.common.ui.BaseActivity
 import com.narutohuo.xindazhou.common.version.VersionUpdateManager
@@ -39,15 +41,20 @@ class MainActivity : BaseActivity<ActivityMainBinding>() {
     }
     
     /**
-     * 设置全屏模式
+     * 设置全屏模式(使用新 API,兼容 Android 11+)
      */
     private fun setFullScreenMode() {
-        // 隐藏状态栏和导航栏
-        window.decorView.systemUiVisibility = (
-            android.view.View.SYSTEM_UI_FLAG_FULLSCREEN
-            or android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
-            or android.view.View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
-        )
+        // 使用新 API(WindowInsetsController)替代废弃的 systemUiVisibility
+        WindowCompat.setDecorFitsSystemWindows(window, false)
+        val insetsController = WindowInsetsControllerCompat(window, window.decorView)
+        insetsController.apply {
+            // 隐藏状态栏
+            hide(androidx.core.view.WindowInsetsCompat.Type.statusBars())
+            // 隐藏导航栏
+            hide(androidx.core.view.WindowInsetsCompat.Type.navigationBars())
+            // 设置沉浸式粘性模式(用户滑动时自动隐藏/显示)
+            systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
+        }
     }
     
     override fun initView() {

+ 365 - 0
app/src/main/java/com/narutohuo/xindazhou/auth/AuthManager.kt

@@ -0,0 +1,365 @@
+package com.narutohuo.xindazhou.auth
+
+import android.content.Context
+import androidx.fragment.app.FragmentActivity
+import androidx.lifecycle.lifecycleScope
+import com.narutohuo.xindazhou.auth.datasource.remote.AuthApi
+import com.narutohuo.xindazhou.auth.model.LoginRequest
+import com.narutohuo.xindazhou.auth.model.LoginResponse
+import com.narutohuo.xindazhou.auth.model.RegisterRequest
+import com.narutohuo.xindazhou.auth.storage.TokenStore
+import com.narutohuo.xindazhou.common.network.ApiManager
+import com.narutohuo.xindazhou.common.network.ApiBaseRemoteDataSource
+import com.narutohuo.xindazhou.common.network.NetworkManager
+import com.narutohuo.xindazhou.core.log.ILog
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+/**
+ * 认证管理器
+ * 
+ * ✅ 直接使用 Network 模块,展示其强大和易用性
+ * 
+ * 功能:
+ * - 用户登录
+ * - 用户注册
+ * - Token 刷新
+ * - 登录状态管理
+ * 
+ * 使用方式:
+ * ```kotlin
+ * // 在 Application 中初始化
+ * AuthManager.setContext(context)
+ * 
+ * // 用户登录(一行代码,自动处理所有网络逻辑)
+ * AuthManager.login(activity, mobile, password) { result ->
+ *     result.onSuccess { response ->
+ *         // 登录成功,Token 已自动保存
+ *     }
+ *     result.onFailure { error ->
+ *         // 登录失败,自动错误处理
+ *     }
+ * }
+ * ```
+ * 
+ * Network 模块提供的强大功能:
+ * - ✅ 自动错误处理(HTTP 错误、业务错误、网络异常)
+ * - ✅ 自动日志记录
+ * - ✅ 自动线程切换(IO 线程)
+ * - ✅ 全局处理器(GlobalApiOperator)
+ * - ✅ 自动重试(可选)
+ * - ✅ 统一响应格式(ApiResponse<T>)
+ */
+object AuthManager : ApiBaseRemoteDataSource() {
+    
+    private val TAG = "AuthManager"
+    
+    // 直接使用 Network 模块创建 API 接口
+    private val authApi: AuthApi by lazy {
+        ApiManager.create<AuthApi>()  // ← Network 模块的强大功能:一行代码创建接口
+    }
+    
+    private var applicationContext: Context? = null
+    private var isInitialized = false
+    
+    /**
+     * 设置应用上下文(懒加载模式)
+     */
+    fun setContext(context: Context) {
+        this.applicationContext = context.applicationContext
+        TokenStore.init(context.applicationContext)
+        
+        // 配置 Network 模块的 Token Provider(自动添加 Token 到请求头)
+        ApiManager.setTokenProvider {
+            TokenStore.getAccessToken()
+        }
+        
+        // 配置 Network 模块的 Token 刷新 Provider(自动刷新过期的 Token)
+        NetworkManager.refreshTokenProvider = {
+            refreshTokenIfNeeded()
+        }
+        
+        ILog.d(TAG, "AuthManager context 已设置,Network 模块已配置")
+    }
+    
+    /**
+     * 确保已初始化
+     */
+    private fun ensureInitialized() {
+        if (isInitialized) {
+            return
+        }
+        
+        val context = applicationContext ?: run {
+            throw IllegalStateException("AuthManager context 未设置,请先调用 AuthManager.setContext(context)")
+        }
+        
+        isInitialized = true
+        ILog.d(TAG, "AuthManager 初始化完成")
+    }
+    
+    /**
+     * 用户登录
+     * 
+     * ✅ 直接使用 Network 模块的强大功能:
+     * - executeRequestResponse() 自动处理所有网络逻辑
+     * - 自动错误处理、日志记录、线程切换
+     * - 支持自动重试(可选)
+     * 
+     * @param activity Activity 实例
+     * @param mobile 手机号
+     * @param password 密码
+     * @param onResult 登录结果回调
+     */
+    fun login(
+        activity: FragmentActivity,
+        mobile: String,
+        password: String,
+        onResult: (Result<LoginResponse>) -> Unit
+    ) {
+        activity.lifecycleScope.launch {
+            try {
+                ensureInitialized()
+                
+                // ✅ 使用 Network 模块的强大功能:executeRequestResponse()
+                // 自动处理:错误处理、日志记录、线程切换、全局处理器、自动重试
+                val result = executeRequestResponse(
+                    request = { authApi.login(LoginRequest(mobile, password)) },  // ← 一行代码发起请求
+                    enableRetry = true,  // ← 可选:自动重试(Network 模块的强大功能)
+                    retryTimes = 3
+                ).toResult()  // ← Network 模块的强大功能:自动转换为 Result<T>
+                
+                // 登录成功,自动保存 Token
+                result.onSuccess { response ->
+                    TokenStore.saveToken(
+                        response.accessToken,
+                        response.refreshToken,
+                        response.userId
+                    )
+                    ILog.d(TAG, "登录成功,Token 已自动保存")
+                }
+                
+                onResult(result)
+            } catch (e: Exception) {
+                ILog.e(TAG, "登录异常", e)
+                onResult(Result.failure(e))
+            }
+        }
+    }
+    
+    /**
+     * 用户注册
+     * 
+     * ✅ 直接使用 Network 模块的强大功能
+     */
+    fun register(
+        activity: FragmentActivity,
+        mobile: String,
+        password: String,
+        onResult: (Result<LoginResponse>) -> Unit
+    ) {
+        activity.lifecycleScope.launch {
+            try {
+                ensureInitialized()
+                
+                // ✅ 使用 Network 模块的强大功能:executeRequestResponse()
+                val result = executeRequestResponse(
+                    request = { authApi.register(RegisterRequest(mobile, password)) },
+                    enableRetry = true,
+                    retryTimes = 3
+                ).toResult()
+                
+                // 注册成功,自动保存 Token
+                result.onSuccess { response ->
+                    TokenStore.saveToken(
+                        response.accessToken,
+                        response.refreshToken,
+                        response.userId
+                    )
+                    ILog.d(TAG, "注册成功,Token 已自动保存")
+                }
+                
+                onResult(result)
+            } catch (e: Exception) {
+                ILog.e(TAG, "注册异常", e)
+                onResult(Result.failure(e))
+            }
+        }
+    }
+    
+    /**
+     * 刷新 Token
+     * 
+     * ✅ 直接使用 Network 模块的强大功能
+     */
+    suspend fun refreshToken(refreshToken: String): Result<LoginResponse> {
+        ensureInitialized()
+        
+        // ✅ 使用 Network 模块的强大功能:executeRequestResponse()
+        val result = executeRequestResponse(
+            request = { authApi.refreshToken(refreshToken) },
+            enableRetry = true,
+            retryTimes = 3
+        ).toResult()
+        
+        // 刷新成功,自动保存 Token
+        result.onSuccess { response ->
+            TokenStore.saveToken(
+                response.accessToken,
+                response.refreshToken,
+                response.userId
+            )
+            ILog.d(TAG, "Token 刷新成功,已自动保存")
+        }
+        
+        return result
+    }
+    
+    /**
+     * 刷新 Token(如果需要)
+     * 
+     * ✅ 先检查 Token 是否过期,如果未过期则返回当前 Token,过期才刷新
+     */
+    suspend fun refreshTokenIfNeeded(
+        scope: CoroutineScope? = null,
+        onResult: ((Result<String?>) -> Unit)? = null
+    ): String? {
+        // 先检查当前 Token 是否过期
+        val currentToken = TokenStore.getAccessToken()
+        if (!currentToken.isNullOrEmpty()) {
+            val expiresAt = TokenStore.getTokenExpiresAt(currentToken)
+            val currentTime = System.currentTimeMillis() / 1000 // 转换为秒
+            
+            // 如果 Token 未过期(留有 60 秒缓冲),直接返回当前 Token
+            if (expiresAt != null && expiresAt > currentTime + 60) {
+                ILog.d(TAG, "Token 未过期,无需刷新(过期时间: ${expiresAt - currentTime}秒后)")
+                onResult?.invoke(Result.success(currentToken))
+                return currentToken
+            }
+        }
+        
+        // Token 过期或不存在,需要刷新
+        val refreshToken = TokenStore.getRefreshToken()
+        if (refreshToken.isNullOrEmpty()) {
+            ILog.w(TAG, "RefreshToken 为空,无法刷新")
+            onResult?.invoke(Result.failure(Exception("RefreshToken 为空")))
+            return null
+        }
+        
+        return try {
+            ensureInitialized()
+            
+            ILog.d(TAG, "Token 已过期或不存在,开始刷新...")
+            // ✅ 使用 Network 模块的强大功能:executeRequestResponse()
+            val result = refreshToken(refreshToken)
+            
+            result.getOrNull()?.let { loginResponse ->
+                ILog.d(TAG, "Token 刷新成功")
+                onResult?.invoke(Result.success(loginResponse.accessToken))
+                loginResponse.accessToken
+            } ?: run {
+                val error = result.exceptionOrNull() ?: Exception("Token 刷新失败")
+                ILog.w(TAG, "Token 刷新失败: ${error.message}")
+                onResult?.invoke(Result.failure(error))
+                null
+            }
+        } catch (e: Exception) {
+            ILog.e(TAG, "Token 刷新异常", e)
+            onResult?.invoke(Result.failure(e))
+            null
+        }
+    }
+    
+    /**
+     * 在后台协程中刷新 Token(如果需要)
+     */
+    fun refreshTokenIfNeededAsync(
+        scope: CoroutineScope,
+        onResult: ((Result<String?>) -> Unit)? = null
+    ) {
+        scope.launch(Dispatchers.IO) {
+            refreshTokenIfNeeded(scope, onResult)
+        }
+    }
+    
+    // ========== 便捷方法 ==========
+    
+    /**
+     * 检查是否已登录(同步,仅检查 Access Token,不刷新)
+     */
+    fun isLoggedIn(): Boolean {
+        return TokenStore.isLoggedIn()
+    }
+    
+    /**
+     * 检查是否已登录(异步,支持自动刷新)
+     */
+    suspend fun isLoggedInAsync(): Boolean {
+        val accessToken = TokenStore.getAccessToken()
+        
+        if (accessToken.isNullOrEmpty()) {
+            val refreshToken = TokenStore.getRefreshToken()
+            if (!refreshToken.isNullOrEmpty()) {
+                ILog.d(TAG, "Access Token 不存在,尝试使用 Refresh Token 刷新...")
+                val newToken = refreshTokenIfNeeded()
+                return !newToken.isNullOrEmpty()
+            }
+            return false
+        }
+        
+        // 检查 Token 是否过期(使用 TokenStore 的内部方法)
+        val expiresAt = TokenStore.getTokenExpiresAt(accessToken)
+        if (expiresAt == null || expiresAt <= System.currentTimeMillis() / 1000) {
+            val refreshToken = TokenStore.getRefreshToken()
+            if (!refreshToken.isNullOrEmpty()) {
+                ILog.d(TAG, "Access Token 已过期,尝试使用 Refresh Token 刷新...")
+                val newToken = refreshTokenIfNeeded()
+                
+                // 检查新 Token 是否有效
+                val newExpiresAt = TokenStore.getTokenExpiresAt(newToken)
+                if (!newToken.isNullOrEmpty() && newExpiresAt != null && newExpiresAt > System.currentTimeMillis() / 1000) {
+                    ILog.d(TAG, "Access Token 刷新成功,用户仍然处于登录状态")
+                    return true
+                } else {
+                    ILog.w(TAG, "Access Token 刷新失败或新 Token 无效,用户需要重新登录")
+                    return false
+                }
+            } else {
+                ILog.w(TAG, "Access Token 已过期且没有 Refresh Token,用户需要重新登录")
+                return false
+            }
+        }
+        
+        return true
+    }
+    
+    /**
+     * 获取当前 Access Token
+     */
+    fun getAccessToken(): String? {
+        return TokenStore.getAccessToken()
+    }
+    
+    /**
+     * 获取当前 Refresh Token
+     */
+    fun getRefreshToken(): String? {
+        return TokenStore.getRefreshToken()
+    }
+    
+    /**
+     * 登出
+     */
+    fun logout() {
+        TokenStore.clearToken()
+        ILog.d(TAG, "用户已登出")
+    }
+    
+    /**
+     * 重写 tag 属性(供 Network 模块使用)
+     */
+    override val tag: String
+        get() = TAG
+}

+ 40 - 0
app/src/main/java/com/narutohuo/xindazhou/auth/README.md

@@ -0,0 +1,40 @@
+# Auth 模块(认证模块)
+
+## 📁 结构
+
+```
+app/auth/
+├── datasource/          # 数据源层
+│   ├── local/          # 本地数据源
+│   └── remote/         # 远程数据源(API接口定义)
+├── model/              # 数据模型
+├── repository/         # 数据仓库
+├── storage/            # 存储层(Token存储)
+├── utils/              # 工具类(JWT工具)
+├── AuthManager.kt      # 认证管理器(高级封装)
+├── NavigationCallback.kt  # 导航回调接口
+└── ui/                 # UI层
+    ├── login/          # 登录界面
+    ├── register/       # 注册界面
+    ├── viewmodel/      # ViewModel
+    └── constant/       # UI常量
+```
+
+## 📝 说明
+
+✅ **统一放在 app 模块**,和其他模块(vehicle、community)结构一致。
+
+✅ **完整的 MVVM 架构**,包含:
+- datasource(数据源)
+- model(数据模型)
+- repository(数据仓库)
+- ui(UI层)
+
+## 🔍 主要文件
+
+- **AuthManager.kt**:认证管理器,提供高级封装
+- **AuthApi.kt**:API接口定义
+- **AuthRepository.kt**:数据仓库
+- **LoginActivity.kt**:登录界面
+- **LoginViewModel.kt**:登录业务逻辑
+

+ 2 - 2
base-common/src/main/java/com/narutohuo/xindazhou/common/auth/datasource/local/AuthLocalDataSource.kt

@@ -1,6 +1,6 @@
-package com.narutohuo.xindazhou.common.auth.datasource.local
+package com.narutohuo.xindazhou.auth.datasource.local
 
-import com.narutohuo.xindazhou.common.auth.model.LoginResponse
+import com.narutohuo.xindazhou.auth.model.LoginResponse
 
 /**
  * 认证本地数据源接口

+ 3 - 3
base-common/src/main/java/com/narutohuo/xindazhou/common/auth/datasource/local/AuthLocalDataSourceImpl.kt

@@ -1,7 +1,7 @@
-package com.narutohuo.xindazhou.common.auth.datasource.local
+package com.narutohuo.xindazhou.auth.datasource.local
 
-import com.narutohuo.xindazhou.common.auth.model.LoginResponse
-import com.narutohuo.xindazhou.common.auth.storage.TokenStore
+import com.narutohuo.xindazhou.auth.model.LoginResponse
+import com.narutohuo.xindazhou.auth.storage.TokenStore
 
 /**
  * 认证本地数据源实现

+ 4 - 4
base-common/src/main/java/com/narutohuo/xindazhou/common/auth/datasource/remote/AuthApi.kt

@@ -1,8 +1,8 @@
-package com.narutohuo.xindazhou.common.auth.datasource.remote
+package com.narutohuo.xindazhou.auth.datasource.remote
 
-import com.narutohuo.xindazhou.common.auth.model.LoginRequest
-import com.narutohuo.xindazhou.common.auth.model.LoginResponse
-import com.narutohuo.xindazhou.common.auth.model.RegisterRequest
+import com.narutohuo.xindazhou.auth.model.LoginRequest
+import com.narutohuo.xindazhou.auth.model.LoginResponse
+import com.narutohuo.xindazhou.auth.model.RegisterRequest
 import com.narutohuo.xindazhou.common.network.ApiCommonResult
 import retrofit2.Response
 import retrofit2.http.Body

+ 15 - 17
base-common/src/main/java/com/narutohuo/xindazhou/common/auth/datasource/remote/AuthRemoteDataSource.kt

@@ -1,9 +1,10 @@
-package com.narutohuo.xindazhou.common.auth.datasource.remote
+package com.narutohuo.xindazhou.auth.datasource.remote
 
-import com.narutohuo.xindazhou.common.auth.model.LoginRequest
-import com.narutohuo.xindazhou.common.auth.model.LoginResponse
-import com.narutohuo.xindazhou.common.auth.model.RegisterRequest
+import com.narutohuo.xindazhou.auth.model.LoginRequest
+import com.narutohuo.xindazhou.auth.model.LoginResponse
+import com.narutohuo.xindazhou.auth.model.RegisterRequest
 import com.narutohuo.xindazhou.common.network.ApiBaseRemoteDataSource
+import com.narutohuo.xindazhou.common.network.ApiManager
 
 /**
  * 认证远程数据源接口
@@ -37,28 +38,25 @@ interface AuthRemoteDataSource {
 class AuthRemoteDataSourceImpl : ApiBaseRemoteDataSource(), AuthRemoteDataSource {
     
     private val authApi: AuthApi by lazy {
-        com.narutohuo.xindazhou.common.network.ApiManager.create<AuthApi>()
+        ApiManager.create<AuthApi>()
     }
     
     override suspend fun login(request: LoginRequest): Result<LoginResponse> {
-        return executeRequest(
-            request = { authApi.login(request) },
-            errorMessage = "登录失败"
-        )
+        return executeRequestResponse(
+            request = { authApi.login(request) }
+        ).toResult()
     }
     
     override suspend fun register(request: RegisterRequest): Result<LoginResponse> {
-        return executeRequest(
-            request = { authApi.register(request) },
-            errorMessage = "注册失败"
-        )
+        return executeRequestResponse(
+            request = { authApi.register(request) }
+        ).toResult()
     }
     
     override suspend fun refreshToken(refreshToken: String): Result<LoginResponse> {
-        return executeRequest(
-            request = { authApi.refreshToken(refreshToken) },
-            errorMessage = "Token 刷新失败"
-        )
+        return executeRequestResponse(
+            request = { authApi.refreshToken(refreshToken) }
+        ).toResult()
     }
 }
 

+ 1 - 1
base-common/src/main/java/com/narutohuo/xindazhou/common/auth/model/LoginRequest.kt

@@ -1,4 +1,4 @@
-package com.narutohuo.xindazhou.common.auth.model
+package com.narutohuo.xindazhou.auth.model
 
 /**
  * 登录请求参数

+ 1 - 1
base-common/src/main/java/com/narutohuo/xindazhou/common/auth/model/LoginResponse.kt

@@ -1,4 +1,4 @@
-package com.narutohuo.xindazhou.common.auth.model
+package com.narutohuo.xindazhou.auth.model
 
 /**
  * 登录响应数据

+ 1 - 1
base-common/src/main/java/com/narutohuo/xindazhou/common/auth/model/RegisterRequest.kt

@@ -1,4 +1,4 @@
-package com.narutohuo.xindazhou.common.auth.model
+package com.narutohuo.xindazhou.auth.model
 
 /**
  * 注册请求参数

+ 109 - 0
app/src/main/java/com/narutohuo/xindazhou/auth/repository/AuthRepository.kt

@@ -0,0 +1,109 @@
+package com.narutohuo.xindazhou.auth.repository
+
+import com.narutohuo.xindazhou.auth.datasource.local.AuthLocalDataSource
+import com.narutohuo.xindazhou.auth.datasource.remote.AuthRemoteDataSource
+import com.narutohuo.xindazhou.auth.model.LoginRequest
+import com.narutohuo.xindazhou.auth.model.LoginResponse
+import com.narutohuo.xindazhou.auth.model.RegisterRequest
+import com.narutohuo.xindazhou.core.log.ILog
+
+/**
+ * 认证数据仓库
+ * 
+ * ✅ 使用 AuthRemoteDataSource(它已经很好地封装了 Network 模块)
+ * 
+ * 职责:
+ * 1. 协调远程数据源(AuthRemoteDataSource)和本地数据源(AuthLocalDataSource)
+ * 2. 处理业务逻辑(保存 Token 等)
+ * 
+ * 调用流程:
+ * ViewModel → AuthRepository → AuthRemoteDataSource → Network 模块
+ * 
+ * 使用方式:
+ * ```kotlin
+ * val repository = AuthRepository(
+ *     remoteDataSource = AuthRemoteDataSourceImpl(),  // ← 使用 Network 模块
+ *     localDataSource = AuthLocalDataSourceImpl()
+ * )
+ * val result = repository.login(mobile, password)
+ * result.onSuccess { response -> /* 处理成功 */ }
+ *        .onFailure { error -> /* 处理失败 */ }
+ * ```
+ */
+class AuthRepository(
+    private val remoteDataSource: AuthRemoteDataSource,  // ← 远程数据源(使用 Network 模块)
+    private val localDataSource: AuthLocalDataSource     // ← 本地数据源(保存 Token)
+) {
+    
+    private val TAG = "AuthRepository"
+    
+    /**
+     * 用户登录
+     * 
+     * 流程:
+     * 1. 接收参数(mobile, password)
+     * 2. 转换为请求对象(LoginRequest)
+     * 3. 调用远程数据源(AuthRemoteDataSource)发起网络请求
+     * 4. 如果成功,保存 Token 到本地
+     * 5. 返回结果
+     * 
+     * @param mobile 手机号
+     * @param password 密码
+     * @return 登录结果(包含 Token、用户信息等)
+     */
+    suspend fun login(mobile: String, password: String): Result<LoginResponse> {
+        // 步骤1:转换为请求对象(Retrofit 需要对象,不能直接用多个参数)
+        val request = LoginRequest(mobile, password)
+        
+        // 步骤2:调用远程数据源(它使用 Network 模块发起请求)
+        val result = remoteDataSource.login(request)
+        
+        // 步骤3:如果成功,保存 Token 到本地
+        result.onSuccess { response ->
+            localDataSource.saveToken(response)
+            ILog.d(TAG, "登录成功,Token已保存")
+        }
+        
+        // 步骤4:返回结果
+        return result
+    }
+    
+    /**
+     * 用户注册
+     * 
+     * 流程:同 login()
+     */
+    suspend fun register(mobile: String, password: String): Result<LoginResponse> {
+        val request = RegisterRequest(mobile, password)
+        val result = remoteDataSource.register(request)
+        
+        result.onSuccess { response ->
+            localDataSource.saveToken(response)
+            ILog.d(TAG, "注册成功,Token已保存")
+        }
+        
+        return result
+    }
+    
+    /**
+     * 刷新 Token
+     * 
+     * 流程:
+     * 1. 调用远程数据源刷新 Token
+     * 2. 如果成功,保存新的 Token 到本地
+     * 3. 返回结果
+     * 
+     * @param refreshToken 刷新令牌
+     * @return 刷新结果(包含新的 accessToken 和 refreshToken)
+     */
+    suspend fun refreshToken(refreshToken: String): Result<LoginResponse> {
+        val result = remoteDataSource.refreshToken(refreshToken)
+        
+        result.onSuccess { response ->
+            localDataSource.saveToken(response)
+            ILog.d(TAG, "Token刷新成功,已保存")
+        }
+        
+        return result
+    }
+}

+ 111 - 3
base-common/src/main/java/com/narutohuo/xindazhou/common/auth/storage/TokenStore.kt

@@ -1,8 +1,9 @@
-package com.narutohuo.xindazhou.common.auth.storage
+package com.narutohuo.xindazhou.auth.storage
 
 import android.content.Context
-import com.narutohuo.xindazhou.common.auth.utils.JWTUtil
+import android.util.Base64
 import com.narutohuo.xindazhou.core.storage.StorageImpl
+import org.json.JSONObject
 
 /**
  * Token 存储管理器
@@ -105,7 +106,43 @@ object TokenStore {
             return false
         }
         // 检查 Token 是否过期(不刷新)
-        return JWTUtil.isTokenValid(token)
+        return isTokenValid(token)
+    }
+    
+    /**
+     * 获取 Token 过期时间
+     * 
+     * @param token JWT Token
+     * @return 过期时间(Unix 时间戳,单位:秒),如果无法解析返回 null
+     */
+    fun getTokenExpiresAt(token: String?): Long? {
+        if (token.isNullOrEmpty()) {
+            return null
+        }
+        
+        // JWT 格式:header.payload.signature
+        val parts = token.split(".")
+        if (parts.size != 3) {
+            return null
+        }
+        
+        // 解析 payload(第二部分)
+        val payload = parts[1]
+        
+        return try {
+            // Base64URL 解码
+            val payloadBytes = base64UrlDecode(payload) ?: return null
+            val payloadJson = JSONObject(String(payloadBytes))
+            
+            // 获取 exp 字段(过期时间,Unix 时间戳,单位:秒)
+            if (!payloadJson.has("exp")) {
+                null
+            } else {
+                payloadJson.getLong("exp")
+            }
+        } catch (e: Exception) {
+            null
+        }
     }
     
     /**
@@ -154,5 +191,76 @@ object TokenStore {
         val url = StorageImpl.getString(KEY_SOCKET_URL)
         return if (url.isEmpty()) null else url
     }
+    
+    // ==================== JWT 解析(私有方法)====================
+    
+    /**
+     * 解析 JWT Token 并检查是否过期
+     * 
+     * @param token JWT Token
+     * @return true 如果 Token 未过期,false 如果过期或无法解析
+     */
+    private fun isTokenValid(token: String?): Boolean {
+        if (token.isNullOrEmpty()) {
+            return false
+        }
+        
+        // JWT 格式:header.payload.signature
+        val parts = token.split(".")
+        if (parts.size != 3) {
+            return false
+        }
+        
+        // 解析 payload(第二部分)
+        val payload = parts[1]
+        
+        return try {
+            // Base64URL 解码
+            val payloadBytes = base64UrlDecode(payload) ?: return false
+            val payloadJson = JSONObject(String(payloadBytes))
+            
+            // 获取 exp 字段(过期时间,Unix 时间戳,单位:秒)
+            if (!payloadJson.has("exp")) {
+                return false
+            }
+            
+            val exp = payloadJson.getLong("exp")
+            
+            // 检查是否过期(exp 是 Unix 时间戳,单位:秒)
+            val currentTime = System.currentTimeMillis() / 1000 // 转换为秒
+            exp > currentTime
+        } catch (e: Exception) {
+            // 解析失败,认为 Token 无效
+            false
+        }
+    }
+    
+    /**
+     * Base64URL 解码
+     * 
+     * Base64URL 是 Base64 的变体,用于 URL 安全:
+     * - 将 '+' 替换为 '-'
+     * - 将 '/' 替换为 '_'
+     * - 移除填充字符 '='
+     */
+    private fun base64UrlDecode(base64Url: String): ByteArray? {
+        return try {
+            // 转换为标准 Base64
+            var base64 = base64Url
+                .replace("-", "+")
+                .replace("_", "/")
+            
+            // 添加填充字符
+            val remainder = base64.length % 4
+            if (remainder > 0) {
+                base64 += "=".repeat(4 - remainder)
+            }
+            
+            // Base64 解码
+            Base64.decode(base64, Base64.NO_WRAP)
+        } catch (e: Exception) {
+            null
+        }
+    }
 }
 

+ 64 - 45
app/src/main/java/com/narutohuo/xindazhou/user/ui/login/LoginActivity.kt

@@ -1,19 +1,21 @@
-package com.narutohuo.xindazhou.user.ui.login
+package com.narutohuo.xindazhou.auth.ui.login
 
 import android.content.Intent
-import android.text.TextUtils
+import android.os.Bundle
 import android.view.View
 import android.widget.Toast
+import androidx.lifecycle.ViewModelProvider
 import androidx.lifecycle.lifecycleScope
 import com.google.android.material.button.MaterialButton
 import com.google.android.material.textfield.TextInputEditText
 import com.narutohuo.xindazhou.R
-import com.narutohuo.xindazhou.common.auth.AuthManager
-import com.narutohuo.xindazhou.common.auth.NavigationCallback
 import com.narutohuo.xindazhou.common.dialog.ServerConfigDialog
 import com.narutohuo.xindazhou.common.ui.BaseActivity
 import com.narutohuo.xindazhou.databinding.ActivityLoginBinding
 import com.narutohuo.xindazhou.socketio.SocketIOManager
+import com.narutohuo.xindazhou.auth.ui.viewmodel.LoginViewModel
+import com.narutohuo.xindazhou.auth.ui.viewmodel.LoginViewModelFactory
+import com.narutohuo.xindazhou.auth.ui.viewmodel.LoginState
 import com.narutohuo.xindazhou.vehicle.ui.VehicleActivity
 import kotlinx.coroutines.launch
 
@@ -21,10 +23,10 @@ import kotlinx.coroutines.launch
  * 登录 Activity
  * 
  * 多 Activity 架构:登录模块独立 Activity
- * 实现 NavigationCallback 接口,支持导航请求
  */
-class LoginActivity : BaseActivity<ActivityLoginBinding>(), NavigationCallback {
+class LoginActivity : BaseActivity<ActivityLoginBinding>() {
     
+    private lateinit var viewModel: LoginViewModel
     private lateinit var etMobile: TextInputEditText
     private lateinit var etPassword: TextInputEditText
     private lateinit var btnLogin: MaterialButton
@@ -36,6 +38,19 @@ class LoginActivity : BaseActivity<ActivityLoginBinding>(), NavigationCallback {
         return ActivityLoginBinding.inflate(layoutInflater)
     }
     
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        
+        // 初始化 ViewModel
+        viewModel = ViewModelProvider(
+            this,
+            LoginViewModelFactory(application)
+        )[LoginViewModel::class.java]
+        
+        initView()
+        observeLoginState()
+    }
+    
     override fun initView() {
         etMobile = binding.etMobile
         etPassword = binding.etPassword
@@ -55,59 +70,63 @@ class LoginActivity : BaseActivity<ActivityLoginBinding>(), NavigationCallback {
             val mobile = etMobile.text?.toString()?.trim() ?: ""
             val password = etPassword.text?.toString() ?: ""
             
-            if (TextUtils.isEmpty(mobile)) {
-                Toast.makeText(this, "请输入手机号", Toast.LENGTH_SHORT).show()
-                return@setOnClickListener
-            }
-            
-            if (TextUtils.isEmpty(password)) {
-                Toast.makeText(this, "请输入密码", Toast.LENGTH_SHORT).show()
-                return@setOnClickListener
-            }
-            
-            // 显示加载状态
-            progressBar.visibility = View.VISIBLE
-            btnLogin.isEnabled = false
-            
-            // 调用 AuthManager 登录
-            lifecycleScope.launch {
-                AuthManager.login(this@LoginActivity, mobile, password) { result ->
-                    progressBar.visibility = View.GONE
-                    btnLogin.isEnabled = true
-                    
-                    result.onSuccess {
-                        Toast.makeText(this@LoginActivity, "登录成功", Toast.LENGTH_SHORT).show()
-                        // 登录成功后,确保 SocketIO 已连接(从存储中读取 Token)
+            // 调用 ViewModel 方法(简单!)
+            viewModel.login(mobile, password)
+        }
+        
+        // 注册按钮
+        btnRegister.setOnClickListener {
+            navigateToRegister()
+        }
+    }
+    
+    /**
+     * 观察登录状态(MVVM 模式:Activity 只负责观察状态,更新 UI)
+     */
+    private fun observeLoginState() {
+        lifecycleScope.launch {
+            viewModel.loginState.collect { state ->
+                when (state) {
+                    is LoginState.Idle -> {
+                        // 初始状态,什么都不做
+                    }
+                    is LoginState.Loading -> {
+                        // 显示加载状态
+                        progressBar.visibility = View.VISIBLE
+                        btnLogin.isEnabled = false
+                    }
+                    is LoginState.Success -> {
+                        // 登录成功
+                        progressBar.visibility = View.GONE
+                        btnLogin.isEnabled = true
+                        Toast.makeText(this@LoginActivity, state.message, Toast.LENGTH_SHORT).show()
+                        // 登录成功后,确保 SocketIO 已连接
                         SocketIOManager.ensureConnected()
                         // 跳转到主界面
                         navigateToMain()
-                    }.onFailure { error ->
-                        Toast.makeText(this@LoginActivity, error.message ?: "登录失败", Toast.LENGTH_SHORT).show()
+                        // 重置状态
+                        viewModel.resetState()
+                    }
+                    is LoginState.Error -> {
+                        // 登录失败
+                        progressBar.visibility = View.GONE
+                        btnLogin.isEnabled = true
+                        Toast.makeText(this@LoginActivity, state.message, Toast.LENGTH_SHORT).show()
                     }
                 }
             }
         }
-        
-        // 注册按钮
-        btnRegister.setOnClickListener {
-            // 跳转到注册页
-            navigateToRegister()
-        }
     }
     
-    // ==================== NavigationCallback 接口实现 ====================
+    // ==================== 导航方法 ====================
     
-    override fun navigateToMain() {
+    private fun navigateToMain() {
         startActivity(Intent(this, VehicleActivity::class.java))
         finish()
     }
     
-    override fun navigateToLogin() {
-        // 当前已在登录页,不需要跳转
-    }
-    
-    override fun navigateToRegister() {
-        startActivity(Intent(this, com.narutohuo.xindazhou.user.ui.register.RegisterActivity::class.java))
+    private fun navigateToRegister() {
+        startActivity(Intent(this, com.narutohuo.xindazhou.auth.ui.register.RegisterActivity::class.java))
     }
 }
 

+ 65 - 50
app/src/main/java/com/narutohuo/xindazhou/user/ui/register/RegisterActivity.kt

@@ -1,18 +1,20 @@
-package com.narutohuo.xindazhou.user.ui.register
+package com.narutohuo.xindazhou.auth.ui.register
 
 import android.content.Intent
-import android.text.TextUtils
+import android.os.Bundle
 import android.view.View
 import android.widget.Toast
+import androidx.lifecycle.ViewModelProvider
 import androidx.lifecycle.lifecycleScope
 import com.google.android.material.button.MaterialButton
 import com.google.android.material.textfield.TextInputEditText
 import com.narutohuo.xindazhou.R
-import com.narutohuo.xindazhou.common.auth.AuthManager
-import com.narutohuo.xindazhou.common.auth.NavigationCallback
 import com.narutohuo.xindazhou.common.ui.BaseActivity
 import com.narutohuo.xindazhou.databinding.ActivityRegisterBinding
 import com.narutohuo.xindazhou.socketio.SocketIOManager
+import com.narutohuo.xindazhou.auth.ui.viewmodel.RegisterViewModel
+import com.narutohuo.xindazhou.auth.ui.viewmodel.RegisterViewModelFactory
+import com.narutohuo.xindazhou.auth.ui.viewmodel.RegisterState
 import com.narutohuo.xindazhou.vehicle.ui.VehicleActivity
 import kotlinx.coroutines.launch
 
@@ -20,10 +22,10 @@ import kotlinx.coroutines.launch
  * 注册 Activity
  * 
  * 多 Activity 架构:注册模块独立 Activity
- * 实现 NavigationCallback 接口,支持导航请求
  */
-class RegisterActivity : BaseActivity<ActivityRegisterBinding>(), NavigationCallback {
+class RegisterActivity : BaseActivity<ActivityRegisterBinding>() {
     
+    private lateinit var viewModel: RegisterViewModel
     private lateinit var etMobile: TextInputEditText
     private lateinit var etPassword: TextInputEditText
     private lateinit var etConfirmPassword: TextInputEditText
@@ -35,6 +37,19 @@ class RegisterActivity : BaseActivity<ActivityRegisterBinding>(), NavigationCall
         return ActivityRegisterBinding.inflate(layoutInflater)
     }
     
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        
+        // 初始化 ViewModel
+        viewModel = ViewModelProvider(
+            this,
+            RegisterViewModelFactory(application)
+        )[RegisterViewModel::class.java]
+        
+        initView()
+        observeRegisterState()
+    }
+    
     override fun initView() {
         etMobile = binding.etMobile
         etPassword = binding.etPassword
@@ -49,70 +64,70 @@ class RegisterActivity : BaseActivity<ActivityRegisterBinding>(), NavigationCall
             val password = etPassword.text?.toString() ?: ""
             val confirmPassword = etConfirmPassword.text?.toString() ?: ""
             
-            if (TextUtils.isEmpty(mobile)) {
-                Toast.makeText(this, "请输入手机号", Toast.LENGTH_SHORT).show()
-                return@setOnClickListener
-            }
-            
-            if (TextUtils.isEmpty(password)) {
-                Toast.makeText(this, "请输入密码", Toast.LENGTH_SHORT).show()
-                return@setOnClickListener
-            }
-            
-            if (password.length < 6 || password.length > 16) {
-                Toast.makeText(this, "密码长度为6-16位", Toast.LENGTH_SHORT).show()
-                return@setOnClickListener
-            }
-            
+            // 简单的前端验证(ViewModel 中也会验证,这里双重保护)
             if (password != confirmPassword) {
                 Toast.makeText(this, "两次输入的密码不一致", Toast.LENGTH_SHORT).show()
                 return@setOnClickListener
             }
             
-            // 显示加载状态
-            progressBar.visibility = View.VISIBLE
-            btnRegister.isEnabled = false
-            
-            // 调用 AuthManager 注册
-            lifecycleScope.launch {
-                AuthManager.register(this@RegisterActivity, mobile, password) { result ->
-                    progressBar.visibility = View.GONE
-                    btnRegister.isEnabled = true
-                    
-                    result.onSuccess {
-                        Toast.makeText(this@RegisterActivity, "注册成功,请登录", Toast.LENGTH_SHORT).show()
-                        // 注册成功后,确保 SocketIO 已连接(从存储中读取 Token)
+            // 调用 ViewModel 方法(简单!)
+            viewModel.register(mobile, password)
+        }
+        
+        // 返回登录按钮
+        btnBackToLogin.setOnClickListener {
+            navigateToLogin()
+        }
+    }
+    
+    /**
+     * 观察注册状态(MVVM 模式:Activity 只负责观察状态,更新 UI)
+     */
+    private fun observeRegisterState() {
+        lifecycleScope.launch {
+            viewModel.registerState.collect { state ->
+                when (state) {
+                    is RegisterState.Idle -> {
+                        // 初始状态,什么都不做
+                    }
+                    is RegisterState.Loading -> {
+                        // 显示加载状态
+                        progressBar.visibility = View.VISIBLE
+                        btnRegister.isEnabled = false
+                    }
+                    is RegisterState.Success -> {
+                        // 注册成功
+                        progressBar.visibility = View.GONE
+                        btnRegister.isEnabled = true
+                        Toast.makeText(this@RegisterActivity, state.message, Toast.LENGTH_SHORT).show()
+                        // 注册成功后,确保 SocketIO 已连接
                         SocketIOManager.ensureConnected()
                         // 跳转到登录页
                         navigateToLogin()
-                    }.onFailure { error ->
-                        Toast.makeText(this@RegisterActivity, error.message ?: "注册失败", Toast.LENGTH_SHORT).show()
+                        // 重置状态
+                        viewModel.resetState()
+                    }
+                    is RegisterState.Error -> {
+                        // 注册失败
+                        progressBar.visibility = View.GONE
+                        btnRegister.isEnabled = true
+                        Toast.makeText(this@RegisterActivity, state.message, Toast.LENGTH_SHORT).show()
                     }
                 }
             }
         }
-        
-        // 返回登录按钮
-        btnBackToLogin.setOnClickListener {
-            // 跳转到登录页
-            navigateToLogin()
-        }
     }
     
-    // ==================== NavigationCallback 接口实现 ====================
+    // ==================== 导航方法 ====================
     
-    override fun navigateToMain() {
+    private fun navigateToMain() {
         startActivity(Intent(this, VehicleActivity::class.java))
         finish()
     }
     
-    override fun navigateToLogin() {
-        startActivity(Intent(this, com.narutohuo.xindazhou.user.ui.login.LoginActivity::class.java))
+    private fun navigateToLogin() {
+        startActivity(Intent(this, com.narutohuo.xindazhou.auth.ui.login.LoginActivity::class.java))
         finish()
     }
-    
-    override fun navigateToRegister() {
-        // 当前已在注册页,不需要跳转
-    }
 }
 

+ 12 - 0
app/src/main/java/com/narutohuo/xindazhou/auth/ui/viewmodel/LoginState.kt

@@ -0,0 +1,12 @@
+package com.narutohuo.xindazhou.auth.ui.viewmodel
+
+/**
+ * 登录状态
+ */
+sealed class LoginState {
+    object Idle : LoginState()
+    object Loading : LoginState()
+    data class Success(val message: String) : LoginState()
+    data class Error(val message: String) : LoginState()
+}
+

+ 68 - 0
app/src/main/java/com/narutohuo/xindazhou/auth/ui/viewmodel/LoginViewModel.kt

@@ -0,0 +1,68 @@
+package com.narutohuo.xindazhou.auth.ui.viewmodel
+
+import android.app.Application
+import androidx.lifecycle.viewModelScope
+import com.narutohuo.xindazhou.auth.repository.AuthRepository
+import com.narutohuo.xindazhou.common.ui.BaseViewModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.launch
+
+/**
+ * 登录ViewModel
+ */
+class LoginViewModel(
+    application: Application,
+    private val authRepository: AuthRepository
+) : BaseViewModel(application) {
+    
+    companion object {
+        private const val MIN_PASSWORD_LENGTH = 6
+        private const val MAX_PASSWORD_LENGTH = 16
+        
+        // UI 提示信息
+        private const val MSG_LOGIN_SUCCESS = "登录成功"
+        private const val MSG_LOGIN_FAILED = "登录失败"
+    }
+    
+    private val _loginState = MutableStateFlow<LoginState>(LoginState.Idle)
+    val loginState: StateFlow<LoginState> = _loginState
+    
+    /**
+     * 登录
+     * 
+     * @param mobile 手机号
+     * @param password 密码
+     */
+    fun login(mobile: String, password: String) {
+        // 输入验证
+        if (mobile.isBlank()) {
+            _loginState.value = LoginState.Error("手机号不能为空")
+            return
+        }
+        
+        if (password.length < MIN_PASSWORD_LENGTH || password.length > MAX_PASSWORD_LENGTH) {
+            _loginState.value = LoginState.Error("密码长度应为${MIN_PASSWORD_LENGTH}-${MAX_PASSWORD_LENGTH}位")
+            return
+        }
+        
+        // 使用 BaseViewModel 提供的 executeRequest 方法(自动处理 Loading/Success/Error)
+        executeRequest(
+            stateFlow = _loginState,
+            onLoading = { LoginState.Loading },
+            onSuccess = { LoginState.Success(MSG_LOGIN_SUCCESS) },
+            onError = { msg -> LoginState.Error(msg) },
+            errorMessage = MSG_LOGIN_FAILED
+        ) {
+            authRepository.login(mobile, password)
+        }
+    }
+    
+    /**
+     * 重置登录状态
+     */
+    fun resetState() {
+        _loginState.value = LoginState.Idle
+    }
+}
+

+ 8 - 6
app/src/main/java/com/narutohuo/xindazhou/user/ui/viewmodel/LoginViewModelFactory.kt

@@ -1,11 +1,11 @@
-package com.narutohuo.xindazhou.user.ui.viewmodel
+package com.narutohuo.xindazhou.auth.ui.viewmodel
 
 import android.app.Application
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.ViewModelProvider
-import com.narutohuo.xindazhou.common.auth.datasource.local.AuthLocalDataSourceImpl
-import com.narutohuo.xindazhou.common.auth.datasource.remote.AuthRemoteDataSourceImpl
-import com.narutohuo.xindazhou.common.auth.repository.AuthRepository
+import com.narutohuo.xindazhou.auth.datasource.local.AuthLocalDataSourceImpl
+import com.narutohuo.xindazhou.auth.datasource.remote.AuthRemoteDataSourceImpl
+import com.narutohuo.xindazhou.auth.repository.AuthRepository
 
 /**
  * LoginViewModel工厂类
@@ -17,12 +17,14 @@ class LoginViewModelFactory(
     @Suppress("UNCHECKED_CAST")
     override fun <T : ViewModel> create(modelClass: Class<T>): T {
         if (modelClass.isAssignableFrom(LoginViewModel::class.java)) {
+            // ✅ AuthRemoteDataSource 已经很好地封装了 Network 模块
             val authRepository = AuthRepository(
-                AuthRemoteDataSourceImpl(),
-                AuthLocalDataSourceImpl()
+                remoteDataSource = AuthRemoteDataSourceImpl(),  // ← 使用 Network 模块(已在 AuthRemoteDataSource 中展示)
+                localDataSource = AuthLocalDataSourceImpl()
             )
             return LoginViewModel(application, authRepository) as T
         }
         throw IllegalArgumentException("Unknown ViewModel class")
     }
 }
+

+ 12 - 0
app/src/main/java/com/narutohuo/xindazhou/auth/ui/viewmodel/RegisterState.kt

@@ -0,0 +1,12 @@
+package com.narutohuo.xindazhou.auth.ui.viewmodel
+
+/**
+ * 注册状态
+ */
+sealed class RegisterState {
+    object Idle : RegisterState()
+    object Loading : RegisterState()
+    data class Success(val message: String) : RegisterState()
+    data class Error(val message: String) : RegisterState()
+}
+

+ 68 - 0
app/src/main/java/com/narutohuo/xindazhou/auth/ui/viewmodel/RegisterViewModel.kt

@@ -0,0 +1,68 @@
+package com.narutohuo.xindazhou.auth.ui.viewmodel
+
+import android.app.Application
+import androidx.lifecycle.viewModelScope
+import com.narutohuo.xindazhou.auth.repository.AuthRepository
+import com.narutohuo.xindazhou.common.ui.BaseViewModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.launch
+
+/**
+ * 注册ViewModel
+ */
+class RegisterViewModel(
+    application: Application,
+    private val authRepository: AuthRepository
+) : BaseViewModel(application) {
+    
+    companion object {
+        private const val MIN_PASSWORD_LENGTH = 6
+        private const val MAX_PASSWORD_LENGTH = 16
+        
+        // UI 提示信息
+        private const val MSG_REGISTER_SUCCESS = "注册成功"
+        private const val MSG_REGISTER_FAILED = "注册失败"
+    }
+    
+    private val _registerState = MutableStateFlow<RegisterState>(RegisterState.Idle)
+    val registerState: StateFlow<RegisterState> = _registerState
+    
+    /**
+     * 注册
+     * 
+     * @param mobile 手机号
+     * @param password 密码
+     */
+    fun register(mobile: String, password: String) {
+        // 输入验证
+        if (mobile.isBlank()) {
+            _registerState.value = RegisterState.Error("手机号不能为空")
+            return
+        }
+        
+        if (password.length < MIN_PASSWORD_LENGTH || password.length > MAX_PASSWORD_LENGTH) {
+            _registerState.value = RegisterState.Error("密码长度应为${MIN_PASSWORD_LENGTH}-${MAX_PASSWORD_LENGTH}位")
+            return
+        }
+        
+        // 使用 BaseViewModel 提供的 executeRequest 方法(自动处理 Loading/Success/Error)
+        executeRequest(
+            stateFlow = _registerState,
+            onLoading = { RegisterState.Loading },
+            onSuccess = { RegisterState.Success(MSG_REGISTER_SUCCESS) },
+            onError = { msg -> RegisterState.Error(msg) },
+            errorMessage = MSG_REGISTER_FAILED
+        ) {
+            authRepository.register(mobile, password)
+        }
+    }
+    
+    /**
+     * 重置注册状态
+     */
+    fun resetState() {
+        _registerState.value = RegisterState.Idle
+    }
+}
+

+ 8 - 6
app/src/main/java/com/narutohuo/xindazhou/user/ui/viewmodel/RegisterViewModelFactory.kt

@@ -1,11 +1,11 @@
-package com.narutohuo.xindazhou.user.ui.viewmodel
+package com.narutohuo.xindazhou.auth.ui.viewmodel
 
 import android.app.Application
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.ViewModelProvider
-import com.narutohuo.xindazhou.common.auth.datasource.local.AuthLocalDataSourceImpl
-import com.narutohuo.xindazhou.common.auth.datasource.remote.AuthRemoteDataSourceImpl
-import com.narutohuo.xindazhou.common.auth.repository.AuthRepository
+import com.narutohuo.xindazhou.auth.datasource.local.AuthLocalDataSourceImpl
+import com.narutohuo.xindazhou.auth.datasource.remote.AuthRemoteDataSourceImpl
+import com.narutohuo.xindazhou.auth.repository.AuthRepository
 
 /**
  * RegisterViewModel工厂类
@@ -17,12 +17,14 @@ class RegisterViewModelFactory(
     @Suppress("UNCHECKED_CAST")
     override fun <T : ViewModel> create(modelClass: Class<T>): T {
         if (modelClass.isAssignableFrom(RegisterViewModel::class.java)) {
+            // ✅ AuthRemoteDataSource 已经很好地封装了 Network 模块
             val authRepository = AuthRepository(
-                AuthRemoteDataSourceImpl(),
-                AuthLocalDataSourceImpl()
+                remoteDataSource = AuthRemoteDataSourceImpl(),  // ← 使用 Network 模块(已在 AuthRemoteDataSource 中展示)
+                localDataSource = AuthLocalDataSourceImpl()
             )
             return RegisterViewModel(application, authRepository) as T
         }
         throw IllegalArgumentException("Unknown ViewModel class")
     }
 }
+

+ 13 - 19
app/src/main/java/com/narutohuo/xindazhou/launch/AppInitializer.kt

@@ -2,7 +2,7 @@ package com.narutohuo.xindazhou.launch
 
 import android.app.Application
 import com.alibaba.android.arouter.launcher.ARouter
-import com.narutohuo.xindazhou.common.auth.AuthManager
+import com.narutohuo.xindazhou.auth.AuthManager
 import com.narutohuo.xindazhou.common.config.ServerConfigManager
 import com.narutohuo.xindazhou.core.log.ILog
 import com.narutohuo.xindazhou.common.network.ApiManager
@@ -58,30 +58,24 @@ object AppInitializer {
             // 4. 初始化配置管理器(base-common)
             ServerConfigManager.init(application.applicationContext)
             
-            // 5. 初始化网络管理器(base-common)
-            // ApiManager.initialize() 会自动从 ServerConfigManager 读取服务器地址并初始化 NetworkHelper
-            // Token Provider 稍后由 AuthManager 设置
-            ApiManager.initialize(application)
-            ILog.d(TAG, "✅ 网络管理器初始化完成")
+            // 5. 网络管理器(base-common,完全懒加载)
+            // ApiManager 会在第一次 create() 时自动初始化,无需手动调用
+            ILog.d(TAG, "✅ 网络管理器(完全懒加载,首次使用时自动初始化)")
             
-            // 6. 初始化认证管理器(base-common,会自动初始化 TokenStore,TokenStore 使用 StorageImpl
-            // AuthManager.init() 会自动配置 NetworkHelper 的 Token Provider
-            AuthManager.init(application.applicationContext)
-            ILog.d(TAG, "✅ 认证管理器初始化完成")
+            // 6. 设置认证管理器上下文(懒加载模式,不立即初始化)
+            // AuthManager 会在第一次使用时自动初始化,并配置 NetworkHelper 的 Token Provider
+            AuthManager.setContext(application.applicationContext)
+            ILog.d(TAG, "✅ 认证管理器上下文已设置(懒加载模式)")
             
             // 7. 初始化版本更新管理器(base-common)
             VersionUpdateManager.init(application.applicationContext)
             ILog.d(TAG, "✅ 版本更新管理器初始化完成")
             
-            // 8. SocketIO 管理器(capability-socketio)
-            // SocketIOManager 会自动处理连接、Token 刷新、重连等
-            // 业务层可以直接订阅消息,无需关心连接细节
-            SocketIOManager.initialize(
-                application,
-                isLoggedInProvider = { AuthManager.isLoggedIn() },
-                refreshTokenProvider = { AuthManager.refreshTokenIfNeeded() }
-            )
-            ILog.d(TAG, "✅ SocketIO 管理器初始化完成(自动处理连接和Token刷新)")
+            // 8. SocketIO 管理器(capability-socketio,完全懒加载)
+            // 设置回调(app 层依赖 base-common,可以直接使用 AuthManager)
+            SocketIOManager.isLoggedInProvider = { AuthManager.isLoggedIn() }
+            SocketIOManager.refreshTokenProvider = { AuthManager.refreshTokenIfNeeded() }
+            ILog.d(TAG, "✅ SocketIO 管理器回调已设置(完全懒加载,首次使用时自动初始化)")
             
             // 9. 初始化分享服务(capability-share,可选)
             initShareService(application)

+ 75 - 6
app/src/main/java/com/narutohuo/xindazhou/shop/ui/ShopActivity.kt

@@ -2,12 +2,18 @@ package com.narutohuo.xindazhou.shop.ui
 
 import android.content.Intent
 import android.os.Bundle
+import android.widget.Button
+import android.widget.LinearLayout
 import android.widget.TextView
+import android.widget.Toast
 import com.narutohuo.xindazhou.R
 import com.narutohuo.xindazhou.common.ui.BaseActivity
+import com.narutohuo.xindazhou.core.log.ILog
 import com.narutohuo.xindazhou.databinding.ActivityShopBinding
 import com.narutohuo.xindazhou.community.ui.CommunityActivity
 import com.narutohuo.xindazhou.service.ui.ServiceActivity
+import com.narutohuo.xindazhou.share.factory.ShareServiceFactory
+import com.narutohuo.xindazhou.share.model.ShareContent
 import com.narutohuo.xindazhou.user.ui.UserActivity
 import com.narutohuo.xindazhou.vehicle.ui.VehicleActivity
 
@@ -19,25 +25,88 @@ import com.narutohuo.xindazhou.vehicle.ui.VehicleActivity
  */
 class ShopActivity : BaseActivity<ActivityShopBinding>() {
     
+    private val tag = "ShopActivity"
+    
     override fun getViewBinding(): ActivityShopBinding {
         return ActivityShopBinding.inflate(layoutInflater)
     }
     
     override fun initView() {
-        // 设置内容
-        val textView = TextView(this)
-        textView.text = "商城页面\n(待实现)"
-        textView.textSize = 18f
-        textView.gravity = android.view.Gravity.CENTER
+        // 创建布局
+        val layout = LinearLayout(this).apply {
+            orientation = LinearLayout.VERTICAL
+            gravity = android.view.Gravity.CENTER
+            setPadding(32, 32, 32, 32)
+        }
+
+        // 标题
+        val textView = TextView(this).apply {
+            text = "商城页面"
+            textSize = 18f
+            gravity = android.view.Gravity.CENTER
+            layoutParams = LinearLayout.LayoutParams(
+                LinearLayout.LayoutParams.MATCH_PARENT,
+                LinearLayout.LayoutParams.WRAP_CONTENT
+            ).apply {
+                setMargins(0, 0, 0, 32)
+            }
+        }
+        layout.addView(textView)
+
+        // 分享按钮
+        val shareButton = Button(this).apply {
+            text = "分享"
+            layoutParams = LinearLayout.LayoutParams(
+                LinearLayout.LayoutParams.WRAP_CONTENT,
+                LinearLayout.LayoutParams.WRAP_CONTENT
+            )
+            setOnClickListener {
+                shareContent()
+            }
+        }
+        layout.addView(shareButton)
         
         // 将内容添加到布局中
         binding.contentContainer.removeAllViews()
-        binding.contentContainer.addView(textView)
+        binding.contentContainer.addView(layout)
         
         // 设置底部导航栏
         setupBottomNavigation()
     }
     
+    /**
+     * 分享内容
+     */
+    private fun shareContent() {
+        try {
+            val shareService = ShareServiceFactory.getInstance()
+            
+            // 创建分享内容(不指定平台,会弹出分享弹窗让用户选择)
+            val shareContent = ShareContent.builder()
+                .setTitle("新大洲本田商城")
+                .setDescription("欢迎来到新大洲本田商城,这里有丰富的摩托车配件和用品!")
+                .setUrl("https://www.xindazhou.com/shop")  // 示例链接
+                // .setImageUrl("https://example.com/image.jpg")  // 可选:分享图片
+                .build()
+            
+            // 调用分享(不指定平台,会显示分享弹窗)
+            shareService.share(this, shareContent) { response ->
+                if (response.success) {
+                    val platformName = response.data?.name ?: "未知平台"
+                    Toast.makeText(this, "分享成功:$platformName", Toast.LENGTH_SHORT).show()
+                    ILog.d(tag, "分享成功:$platformName")
+                } else {
+                    val errorMsg = response.errorMessage ?: "分享失败"
+                    Toast.makeText(this, errorMsg, Toast.LENGTH_SHORT).show()
+                    ILog.e(tag, "分享失败:$errorMsg")
+                }
+            }
+        } catch (e: Exception) {
+            ILog.e(tag, "分享功能异常", e)
+            Toast.makeText(this, "分享功能异常:${e.message}", Toast.LENGTH_SHORT).show()
+        }
+    }
+    
     private fun setupBottomNavigation() {
         binding.bottomNavView.selectedItemId = R.id.shopFragment
         binding.bottomNavView.setOnItemSelectedListener { item ->

+ 0 - 17
app/src/main/java/com/narutohuo/xindazhou/user/ui/constant/UiConstants.kt

@@ -1,17 +0,0 @@
-package com.narutohuo.xindazhou.user.ui.constant
-
-/**
- * UI 相关常量
- */
-object UiConstants {
-    /** 提示信息 */
-    const val MSG_MOBILE_EMPTY = "请输入手机号"
-    const val MSG_PASSWORD_EMPTY = "请输入密码"
-    const val MSG_PASSWORD_INVALID = "密码长度为6-16位"
-    const val MSG_PASSWORD_NOT_MATCH = "两次输入的密码不一致"
-    const val MSG_LOGIN_SUCCESS = "登录成功"
-    const val MSG_LOGIN_FAILED = "登录失败"
-    const val MSG_REGISTER_SUCCESS = "注册成功"
-    const val MSG_REGISTER_FAILED = "注册失败"
-}
-

+ 0 - 68
app/src/main/java/com/narutohuo/xindazhou/user/ui/viewmodel/LoginViewModel.kt

@@ -1,68 +0,0 @@
-package com.narutohuo.xindazhou.user.ui.viewmodel
-
-import android.app.Application
-import androidx.lifecycle.AndroidViewModel
-import androidx.lifecycle.viewModelScope
-import com.narutohuo.xindazhou.common.auth.repository.AuthRepository
-import com.narutohuo.xindazhou.user.ui.constant.UiConstants
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.launch
-
-/**
- * 登录状态
- */
-sealed class LoginState {
-    object Idle : LoginState()
-    object Loading : LoginState()
-    data class Success(val message: String) : LoginState()
-    data class Error(val message: String) : LoginState()
-}
-
-/**
- * 登录ViewModel
- */
-class LoginViewModel(
-    application: Application,
-    private val authRepository: AuthRepository
-) : AndroidViewModel(application) {
-    
-    companion object {
-        private const val MIN_PASSWORD_LENGTH = 6
-        private const val MAX_PASSWORD_LENGTH = 16
-    }
-    
-    private val _loginState = MutableStateFlow<LoginState>(LoginState.Idle)
-    val loginState: StateFlow<LoginState> = _loginState
-    
-    /**
-     * 登录
-     * 
-     * @param mobile 手机号
-     * @param password 密码
-     */
-    fun login(mobile: String, password: String) {
-        viewModelScope.launch {
-            _loginState.value = LoginState.Loading
-            
-            // 输入验证
-            if (mobile.isBlank()) {
-                _loginState.value = LoginState.Error("手机号不能为空")
-                return@launch
-            }
-            
-            if (password.length < MIN_PASSWORD_LENGTH || password.length > MAX_PASSWORD_LENGTH) {
-                _loginState.value = LoginState.Error("密码长度应为${MIN_PASSWORD_LENGTH}-${MAX_PASSWORD_LENGTH}位")
-                return@launch
-            }
-            
-            authRepository.login(mobile, password)
-                .onSuccess {
-                    _loginState.value = LoginState.Success(UiConstants.MSG_LOGIN_SUCCESS)
-                }
-                .onFailure { e ->
-                    _loginState.value = LoginState.Error(e.message ?: UiConstants.MSG_LOGIN_FAILED)
-                }
-        }
-    }
-}

+ 0 - 68
app/src/main/java/com/narutohuo/xindazhou/user/ui/viewmodel/RegisterViewModel.kt

@@ -1,68 +0,0 @@
-package com.narutohuo.xindazhou.user.ui.viewmodel
-
-import android.app.Application
-import androidx.lifecycle.AndroidViewModel
-import androidx.lifecycle.viewModelScope
-import com.narutohuo.xindazhou.common.auth.repository.AuthRepository
-import com.narutohuo.xindazhou.user.ui.constant.UiConstants
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.launch
-
-/**
- * 注册状态
- */
-sealed class RegisterState {
-    object Idle : RegisterState()
-    object Loading : RegisterState()
-    data class Success(val message: String) : RegisterState()
-    data class Error(val message: String) : RegisterState()
-}
-
-/**
- * 注册ViewModel
- */
-class RegisterViewModel(
-    application: Application,
-    private val authRepository: AuthRepository
-) : AndroidViewModel(application) {
-    
-    companion object {
-        private const val MIN_PASSWORD_LENGTH = 6
-        private const val MAX_PASSWORD_LENGTH = 16
-    }
-    
-    private val _registerState = MutableStateFlow<RegisterState>(RegisterState.Idle)
-    val registerState: StateFlow<RegisterState> = _registerState
-    
-    /**
-     * 注册
-     * 
-     * @param mobile 手机号
-     * @param password 密码
-     */
-    fun register(mobile: String, password: String) {
-        viewModelScope.launch {
-            _registerState.value = RegisterState.Loading
-            
-            // 输入验证
-            if (mobile.isBlank()) {
-                _registerState.value = RegisterState.Error("手机号不能为空")
-                return@launch
-            }
-            
-            if (password.length < MIN_PASSWORD_LENGTH || password.length > MAX_PASSWORD_LENGTH) {
-                _registerState.value = RegisterState.Error("密码长度应为${MIN_PASSWORD_LENGTH}-${MAX_PASSWORD_LENGTH}位")
-                return@launch
-            }
-            
-            authRepository.register(mobile, password)
-                .onSuccess {
-                    _registerState.value = RegisterState.Success(UiConstants.MSG_REGISTER_SUCCESS)
-                }
-                .onFailure { e ->
-                    _registerState.value = RegisterState.Error(e.message ?: UiConstants.MSG_REGISTER_FAILED)
-                }
-        }
-    }
-}

+ 54 - 31
app/src/main/java/com/narutohuo/xindazhou/user/ui/viewmodel/UserViewModel.kt

@@ -1,8 +1,8 @@
 package com.narutohuo.xindazhou.user.ui.viewmodel
 
 import android.app.Application
-import androidx.lifecycle.AndroidViewModel
 import androidx.lifecycle.viewModelScope
+import com.narutohuo.xindazhou.common.ui.BaseViewModel
 import com.narutohuo.xindazhou.socketio.SocketIOManager
 import com.narutohuo.xindazhou.socketio.model.SocketIOResponse
 import com.narutohuo.xindazhou.socketio.model.SocketIOEvent
@@ -32,7 +32,7 @@ data class UserUiState(
  */
 class UserViewModel(
     application: Application
-) : AndroidViewModel(application) {
+) : BaseViewModel(application) {
     
     private val dateFormat = SimpleDateFormat("HH:mm:ss", Locale.getDefault())
     
@@ -58,49 +58,72 @@ class UserViewModel(
     /**
      * 开始订阅消息
      * 
-     * 注意:SocketIOManager 已经在 AppInitializer 中统一初始化
-     * 这里只需要订阅消息即可,如果未连接,subscribe() 会自动处理连接
+     * 注意:subscribe() 内部会自动处理连接(懒加载 + 自动连接),
+     * 无需手动调用 ensureConnected(),直接订阅即可
      */
     fun connect() {
         viewModelScope.launch {
             addLog("开始订阅 SocketIO 消息...")
             
-            // 订阅连接状态变化(已经在 observeConnectionState 中设置,这里确保状态同步)
-            updateConnectionStatus()
-            
-            // 订阅车辆报警事件
-            // subscribe() 会自动处理连接(如果未连接会调用 checkAndReconnect)
-            val alarmJob = launch {
-                SocketIOManager.shared.subscribe(SocketIOEvent.VEHICLE_ALARM).collect { response ->
-                    handleVehicleAlarm(response)
+            try {
+                // subscribe() 内部会自动处理连接,无需手动调用 ensureConnected()
+                // 订阅车辆报警事件
+                val alarmJob = launch {
+                    SocketIOManager.subscribe(SocketIOEvent.VEHICLE_ALARM).collect { response ->
+                        handleVehicleAlarm(response)
+                    }
                 }
-            }
-            subscriptionJobs.add(alarmJob)
-            
-            // 订阅车辆控制响应
-            val controlJob = launch {
-                SocketIOManager.shared.subscribe(SocketIOEvent.VEHICLE_CONTROL_RESPONSE).collect { response ->
-                    addLog("📥 收到响应: ${response.data}")
+                subscriptionJobs.add(alarmJob)
+                
+                // 订阅车辆控制响应
+                val controlJob = launch {
+                    SocketIOManager.subscribe(SocketIOEvent.VEHICLE_CONTROL_RESPONSE).collect { response ->
+                        addLog("📥 收到响应: ${response.data}")
+                    }
+                }
+                subscriptionJobs.add(controlJob)
+                
+                addLog("✅ SocketIO 消息订阅已启动(连接会自动处理)")
+                
+                // 观察连接状态变化,等待连接成功
+                var connected = false
+                var attempts = 0
+                while (!connected && attempts < 20) { // 最多等待 10 秒
+                    kotlinx.coroutines.delay(500)
+                    connected = SocketIOManager.isConnected()
+                    attempts++
+                    if (connected) {
+                        addLog("✅ SocketIO 连接成功")
+                        updateConnectionStatus()
+                    }
                 }
+                
+                if (!connected) {
+                    addLog("⚠️ SocketIO 连接中...(请检查登录状态和网络)")
+                    updateConnectionStatus()
+                }
+            } catch (e: Exception) {
+                addLog("❌ 订阅失败: ${e.message}")
+                _errorMessage.value = "订阅失败: ${e.message}"
+                updateConnectionStatus()
             }
-            subscriptionJobs.add(controlJob)
-            
-            addLog("✅ SocketIO 消息订阅已启动(SocketIOManager 已在 AppInitializer 中初始化)")
         }
     }
     
     /**
-     * 断开 SocketIO 连接
+     * 停止订阅消息(只取消当前页面的订阅,不影响全局 SocketIO 连接)
+     * 
+     * 注意:SocketIOManager 是全局单例,其他页面可能也在使用,
+     * 所以这里只取消当前页面的订阅,不断开全局连接
      */
     fun disconnect() {
         viewModelScope.launch {
-            // 取消所有订阅任务
+            // 只取消当前页面的订阅任务(不影响全局连接)
             subscriptionJobs.forEach { it.cancel() }
             subscriptionJobs.clear()
             
-            SocketIOManager.shared.disconnect()
-            addLog("已断开连接")
-            updateConnectionStatus()
+            addLog("已停止订阅消息(SocketIO 全局连接保持)")
+            // 注意:不调用 SocketIOManager.disconnect(),因为其他页面可能还在使用
         }
     }
     
@@ -109,7 +132,7 @@ class UserViewModel(
      */
     fun sendFindVehicleCommand() {
         viewModelScope.launch {
-            if (!SocketIOManager.shared.isConnected()) {
+            if (!SocketIOManager.isConnected()) {
                 _errorMessage.value = "未连接,请先连接SocketIO"
                 return@launch
             }
@@ -119,7 +142,7 @@ class UserViewModel(
                 "vehicleId" to 1
             )
             
-            SocketIOManager.shared.emit(SocketIOEvent.VEHICLE_CONTROL, data)
+            SocketIOManager.emit(SocketIOEvent.VEHICLE_CONTROL, data)
             addLog("📤 发送寻车指令:command=find_vehicle")
         }
     }
@@ -140,7 +163,7 @@ class UserViewModel(
      */
     private fun observeConnectionState() {
         viewModelScope.launch {
-            SocketIOManager.shared.connectionState.collect { isConnected ->
+            SocketIOManager.connectionState.collect { isConnected ->
                 _uiState.value = _uiState.value.copy(isConnected = isConnected)
             }
         }
@@ -151,7 +174,7 @@ class UserViewModel(
      */
     private fun updateConnectionStatus() {
         _uiState.value = _uiState.value.copy(
-            isConnected = SocketIOManager.shared.isConnected()
+            isConnected = SocketIOManager.isConnected()
         )
     }
     

+ 1 - 1
app/src/main/res/layout/activity_login.xml

@@ -6,7 +6,7 @@
     android:layout_height="match_parent"
     android:padding="24dp"
     android:background="@android:color/white"
-    tools:context=".user.ui.login.LoginActivity">
+    tools:context=".auth.ui.login.LoginActivity">
 
     <!-- Logo 或应用名称 -->
     <TextView

+ 1 - 1
app/src/main/res/layout/activity_register.xml

@@ -6,7 +6,7 @@
     android:layout_height="match_parent"
     android:padding="24dp"
     android:background="@android:color/white"
-    tools:context=".user.ui.register.RegisterActivity">
+    tools:context=".auth.ui.register.RegisterActivity">
 
     <!-- 标题 -->
     <TextView

+ 0 - 367
base-common/src/main/java/com/narutohuo/xindazhou/common/auth/AuthManager.kt

@@ -1,367 +0,0 @@
-package com.narutohuo.xindazhou.common.auth
-
-import android.content.Context
-import androidx.fragment.app.FragmentActivity
-import androidx.lifecycle.lifecycleScope
-import com.narutohuo.xindazhou.common.auth.datasource.local.AuthLocalDataSource
-import com.narutohuo.xindazhou.common.auth.datasource.local.AuthLocalDataSourceImpl
-import com.narutohuo.xindazhou.common.auth.datasource.remote.AuthRemoteDataSource
-import com.narutohuo.xindazhou.common.auth.datasource.remote.AuthRemoteDataSourceImpl
-import com.narutohuo.xindazhou.common.auth.repository.AuthRepository
-import com.narutohuo.xindazhou.common.auth.storage.TokenStore
-import com.narutohuo.xindazhou.common.auth.utils.JWTUtil
-import com.narutohuo.xindazhou.core.log.ILog
-import com.narutohuo.xindazhou.common.network.ApiManager
-// SocketIOManager 已移动到 capability-socketio,不再直接依赖
-// 业务层可以通过回调来处理 SocketIO 连接
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
-
-/**
- * 认证管理器
- * 
- * 封装认证相关的完整逻辑,包括:
- * - 用户登录
- * - 用户注册
- * - Token 刷新
- * - 登录状态管理
- * 
- * 使用方式:
- * ```kotlin
- * // 在 Application 中初始化(推荐方式,使用默认实现)
- * AuthManager.init(context)
- * 
- * // 或者使用自定义的 localDataSource
- * AuthManager.init(localDataSource)
- * 
- * // 在 Activity 中登录
- * AuthManager.login(activity, mobile, password) { result ->
- *     result.onSuccess { response ->
- *         // 登录成功,跳转到主界面
- *     }
- *     result.onFailure { error ->
- *         // 登录失败,显示错误
- *     }
- * }
- * ```
- */
-object AuthManager {
-    
-    private var localDataSource: AuthLocalDataSource? = null
-    private var remoteDataSource: AuthRemoteDataSource = AuthRemoteDataSourceImpl()
-    private var repository: AuthRepository? = null
-    
-    /**
-     * 初始化认证管理器(使用默认实现)
-     * 
-     * 需要在 Application.onCreate() 中调用
-     * 会自动初始化 TokenStore 并使用默认的 AuthLocalDataSourceImpl
-     * 会自动配置 NetworkHelper 的 Token Provider
-     * 
-     * @param context 应用上下文
-     */
-    fun init(context: Context) {
-        // 初始化 TokenStore
-        TokenStore.init(context)
-        // 使用默认实现
-        val defaultLocalDataSource = AuthLocalDataSourceImpl()
-        this.localDataSource = defaultLocalDataSource
-        this.repository = AuthRepository(remoteDataSource, defaultLocalDataSource)
-        
-        // 配置 ApiManager 的 Token Provider(使用 TokenStore 的同步方法)
-        ApiManager.setTokenProvider {
-            TokenStore.getAccessToken()
-        }
-        
-        ILog.d("AuthManager", "认证管理器初始化完成,Token Provider 已配置")
-    }
-    
-    /**
-     * 初始化认证管理器(使用自定义实现)
-     * 
-     * 如果需要自定义本地数据源,可以使用此方法
-     * 会自动配置 NetworkHelper 的 Token Provider
-     * 
-     * @param localDataSource 自定义的本地数据源
-     */
-    fun init(localDataSource: AuthLocalDataSource) {
-        this.localDataSource = localDataSource
-        this.repository = AuthRepository(remoteDataSource, localDataSource)
-        
-        // 配置 ApiManager 的 Token Provider(使用 TokenStore 的同步方法)
-        ApiManager.setTokenProvider {
-            TokenStore.getAccessToken()
-        }
-        
-        ILog.d("AuthManager", "认证管理器初始化完成(自定义数据源),Token Provider 已配置")
-    }
-    
-    /**
-     * 用户登录
-     * 
-     * @param activity Activity 实例
-     * @param mobile 手机号
-     * @param password 密码
-     * @param onResult 登录结果回调
-     */
-    fun login(
-        activity: FragmentActivity,
-        mobile: String,
-        password: String,
-        onResult: (Result<com.narutohuo.xindazhou.common.auth.model.LoginResponse>) -> Unit
-    ) {
-        activity.lifecycleScope.launch {
-            try {
-                val repo = repository ?: run {
-                    ILog.e("AuthManager", "AuthManager 未初始化,请先调用 AuthManager.init()")
-                    onResult(Result.failure(Exception("AuthManager 未初始化")))
-                    return@launch
-                }
-                
-                val result = repo.login(mobile, password)
-                
-                // 登录成功后,自动触发 Socket.IO 连接(由业务层通过回调处理)
-                result.onSuccess {
-                    ILog.d("AuthManager", "登录成功,业务层应调用 SocketIOManager.ensureConnected()")
-                }
-                
-                onResult(result as Result<com.narutohuo.xindazhou.common.auth.model.LoginResponse>)
-            } catch (e: Exception) {
-                ILog.e("AuthManager", "登录异常", e)
-                onResult(Result.failure(e))
-            }
-        }
-    }
-    
-    /**
-     * 用户注册
-     * 
-     * @param activity Activity 实例
-     * @param mobile 手机号
-     * @param password 密码
-     * @param onResult 注册结果回调
-     */
-    fun register(
-        activity: FragmentActivity,
-        mobile: String,
-        password: String,
-        onResult: (Result<com.narutohuo.xindazhou.common.auth.model.LoginResponse>) -> Unit
-    ) {
-        activity.lifecycleScope.launch {
-            try {
-                val repo = repository ?: run {
-                    ILog.e("AuthManager", "AuthManager 未初始化,请先调用 AuthManager.init()")
-                    onResult(Result.failure(Exception("AuthManager 未初始化")))
-                    return@launch
-                }
-                
-                val result = repo.register(mobile, password)
-                
-                // 注册成功后,自动触发 Socket.IO 连接(由业务层通过回调处理)
-                result.onSuccess {
-                    ILog.d("AuthManager", "注册成功,业务层应调用 SocketIOManager.ensureConnected()")
-                }
-                
-                onResult(result as Result<com.narutohuo.xindazhou.common.auth.model.LoginResponse>)
-            } catch (e: Exception) {
-                ILog.e("AuthManager", "注册异常", e)
-                onResult(Result.failure(e))
-            }
-        }
-    }
-    
-    /**
-     * 刷新 Token
-     * 
-     * @param activity Activity 实例
-     * @param refreshToken 刷新令牌
-     * @param onResult 刷新结果回调
-     */
-    fun refreshToken(
-        activity: FragmentActivity,
-        refreshToken: String,
-        onResult: (Result<com.narutohuo.xindazhou.common.auth.model.LoginResponse>) -> Unit
-    ) {
-        activity.lifecycleScope.launch {
-            try {
-                val repo = repository ?: run {
-                    ILog.e("AuthManager", "AuthManager 未初始化,请先调用 AuthManager.init()")
-                    onResult(Result.failure(Exception("AuthManager 未初始化")))
-                    return@launch
-                }
-                
-                val result = repo.refreshToken(refreshToken)
-                onResult(result as Result<com.narutohuo.xindazhou.common.auth.model.LoginResponse>)
-            } catch (e: Exception) {
-                ILog.e("AuthManager", "Token 刷新异常", e)
-                onResult(Result.failure(e))
-            }
-        }
-    }
-    
-    // ========== 便捷方法 ==========
-    
-    /**
-     * 检查是否已登录(同步,仅检查 Access Token,不刷新)
-     * 
-     * 注意:此方法只检查 Access Token 是否存在且未过期,不会尝试刷新
-     * 如果需要自动刷新功能,请使用 isLoggedInAsync()
-     * 
-     * @return true 表示已登录
-     */
-    fun isLoggedIn(): Boolean {
-        return TokenStore.isLoggedIn()
-    }
-    
-    /**
-     * 检查是否已登录(异步,支持自动刷新)
-     * 
-     * 检查 Access Token 是否存在且未过期
-     * 如果 Access Token 过期,会尝试使用 Refresh Token 刷新
-     * 只有在 Refresh Token 也过期或刷新失败时,才返回 false
-     * 
-     * @return true 表示已登录
-     */
-    suspend fun isLoggedInAsync(): Boolean {
-        // 1. 先检查 Access Token 是否存在
-        val accessToken = TokenStore.getAccessToken()
-        
-        // 2. 如果 Access Token 不存在,检查是否有 Refresh Token
-        if (accessToken.isNullOrEmpty()) {
-            val refreshToken = TokenStore.getRefreshToken()
-            if (!refreshToken.isNullOrEmpty()) {
-                // 有 Refresh Token,尝试刷新 Access Token
-                ILog.d("AuthManager", "Access Token 不存在,尝试使用 Refresh Token 刷新...")
-                val newToken = refreshTokenIfNeeded()
-                return !newToken.isNullOrEmpty()
-            }
-            // 没有 Refresh Token,返回 false
-            return false
-        }
-        
-        // 3. 检查 Access Token 是否过期
-        if (!JWTUtil.isTokenValid(accessToken)) {
-            // Access Token 已过期,检查是否有 Refresh Token
-            val refreshToken = TokenStore.getRefreshToken()
-            if (!refreshToken.isNullOrEmpty()) {
-                // 有 Refresh Token,尝试刷新 Access Token
-                ILog.d("AuthManager", "Access Token 已过期,尝试使用 Refresh Token 刷新...")
-                val newToken = refreshTokenIfNeeded()
-                
-                // 如果刷新成功,验证新 Token 是否有效
-                if (!newToken.isNullOrEmpty() && JWTUtil.isTokenValid(newToken)) {
-                    ILog.d("AuthManager", "Access Token 刷新成功,用户仍然处于登录状态")
-                    return true
-                } else {
-                    ILog.w("AuthManager", "Access Token 刷新失败或新 Token 无效,用户需要重新登录")
-                    return false
-                }
-            } else {
-                // 没有 Refresh Token,返回 false
-                ILog.w("AuthManager", "Access Token 已过期且没有 Refresh Token,用户需要重新登录")
-                return false
-            }
-        }
-        
-        // 4. Access Token 存在且未过期,返回 true
-        return true
-    }
-    
-    /**
-     * 获取当前 Access Token
-     * 
-     * @return Access Token,如果未登录返回 null
-     */
-    fun getAccessToken(): String? {
-        return TokenStore.getAccessToken()
-    }
-    
-    /**
-     * 获取当前 Refresh Token
-     * 
-     * @return Refresh Token,如果未登录返回 null
-     */
-    fun getRefreshToken(): String? {
-        return TokenStore.getRefreshToken()
-    }
-    
-    /**
-     * 刷新 Token(如果需要)
-     * 
-     * 在后台协程中自动刷新 Token,无需 Activity 生命周期
-     * 
-     * @param scope 协程作用域(例如:applicationScope)
-     * @param onResult 刷新结果回调(可选)
-     * @return 新的 accessToken,如果刷新失败返回 null
-     */
-    suspend fun refreshTokenIfNeeded(
-        scope: CoroutineScope? = null,
-        onResult: ((Result<String?>) -> Unit)? = null
-    ): String? {
-        val refreshToken = TokenStore.getRefreshToken()
-        if (refreshToken.isNullOrEmpty()) {
-            ILog.w("AuthManager", "RefreshToken 为空,无法刷新")
-            onResult?.invoke(Result.failure(Exception("RefreshToken 为空")))
-            return null
-        }
-        
-        return try {
-            val repo = repository ?: run {
-                val error = Exception("AuthManager 未初始化")
-                ILog.e("AuthManager", "AuthManager 未初始化,请先调用 AuthManager.init()")
-                onResult?.invoke(Result.failure(error))
-                return null
-            }
-            
-            val result = repo.refreshToken(refreshToken)
-            
-            result.getOrNull()?.let { loginResponse ->
-                // Token 已由 AuthRepository 自动保存
-                ILog.d("AuthManager", "Token 刷新成功")
-                
-                // Token 刷新成功后,业务层应调用 SocketIOManager.ensureConnected() 确保 Socket.IO 使用新 Token 重连
-                ILog.d("AuthManager", "Token 刷新成功,业务层应调用 SocketIOManager.ensureConnected()")
-                
-                onResult?.invoke(Result.success(loginResponse.accessToken))
-                loginResponse.accessToken
-            } ?: run {
-                val error = result.exceptionOrNull() ?: Exception("Token 刷新失败")
-                ILog.w("AuthManager", "Token 刷新失败: ${error.message}")
-                onResult?.invoke(Result.failure(error))
-                null
-            }
-        } catch (e: Exception) {
-            ILog.e("AuthManager", "Token 刷新异常", e)
-            onResult?.invoke(Result.failure(e))
-            null
-        }
-    }
-    
-    /**
-     * 在后台协程中刷新 Token(如果需要)
-     * 
-     * @param scope 协程作用域
-     * @param onResult 刷新结果回调(可选)
-     */
-    fun refreshTokenIfNeededAsync(
-        scope: CoroutineScope,
-        onResult: ((Result<String?>) -> Unit)? = null
-    ) {
-        scope.launch(Dispatchers.IO) {
-            refreshTokenIfNeeded(scope, onResult)
-        }
-    }
-    
-    /**
-     * 登出
-     * 
-     * 清除所有 Token 和用户信息
-     */
-    fun logout() {
-        TokenStore.clearToken()
-        ILog.d("AuthManager", "用户已登出")
-    }
-}
-

+ 0 - 105
base-common/src/main/java/com/narutohuo/xindazhou/common/auth/repository/AuthRepository.kt

@@ -1,105 +0,0 @@
-package com.narutohuo.xindazhou.common.auth.repository
-
-import com.narutohuo.xindazhou.common.auth.datasource.local.AuthLocalDataSource
-import com.narutohuo.xindazhou.common.auth.datasource.remote.AuthRemoteDataSource
-import com.narutohuo.xindazhou.common.auth.model.LoginRequest
-import com.narutohuo.xindazhou.common.auth.model.LoginResponse
-import com.narutohuo.xindazhou.common.auth.model.RegisterRequest
-import com.narutohuo.xindazhou.core.log.ILog
-import com.narutohuo.xindazhou.common.network.ApiBaseRepository
-
-/**
- * 认证数据仓库
- * 
- * 负责统一管理认证相关的数据获取和缓存
- * 
- * 继承 ApiBaseRepository,自动获得:
- * - 统一的错误处理
- * - 日志记录
- * - 数据转换扩展点
- */
-class AuthRepository(
-    private val remoteDataSource: AuthRemoteDataSource,
-    private val localDataSource: AuthLocalDataSource
-) : ApiBaseRepository() {
-    
-    /**
-     * 用户登录
-     * 
-     * @param mobile 手机号
-     * @param password 密码
-     * @return 登录结果
-     */
-    suspend fun login(mobile: String, password: String): Result<LoginResponse> {
-        val request = LoginRequest(mobile, password)
-        
-        // 从远程数据源获取数据
-        val result = remoteDataSource.login(request)
-        
-        // 如果成功,保存到本地
-        result.onSuccess { response ->
-            localDataSource.saveToken(response)
-            ILog.d("AuthRepository", "login: Token已保存")
-        }
-        
-        // 如果失败,可以在这里添加额外的错误处理逻辑
-        result.onFailure { throwable ->
-            // 例如:记录日志、发送错误上报等
-            handleError(throwable)
-        }
-        
-        return result
-    }
-    
-    /**
-     * 用户注册
-     * 
-     * @param mobile 手机号
-     * @param password 密码
-     * @return 注册结果
-     */
-    suspend fun register(mobile: String, password: String): Result<LoginResponse> {
-        val request = RegisterRequest(mobile, password)
-        
-        // 从远程数据源获取数据
-        val result = remoteDataSource.register(request)
-        
-        // 如果成功,保存到本地
-        result.onSuccess { response ->
-            localDataSource.saveToken(response)
-            ILog.d("AuthRepository", "register: Token已保存")
-        }
-        
-        // 如果失败,可以在这里添加额外的错误处理逻辑
-        result.onFailure { throwable ->
-            handleError(throwable)
-        }
-        
-        return result
-    }
-    
-    /**
-     * 刷新 Token
-     * 
-     * @param refreshToken 刷新令牌
-     * @return 刷新结果(包含新的 accessToken 和 refreshToken)
-     */
-    suspend fun refreshToken(refreshToken: String): Result<LoginResponse> {
-        // 从远程数据源获取数据
-        val result = remoteDataSource.refreshToken(refreshToken)
-        
-        // 如果成功,保存到本地
-        result.onSuccess { response ->
-            localDataSource.saveToken(response)
-            ILog.d("AuthRepository", "refreshToken: Token已刷新并保存")
-        }
-        
-        // 如果失败,可以在这里添加额外的错误处理逻辑
-        result.onFailure { throwable ->
-            handleError(throwable)
-        }
-        
-        return result
-    }
-}
-

+ 0 - 120
base-common/src/main/java/com/narutohuo/xindazhou/common/auth/utils/JWTUtil.kt

@@ -1,120 +0,0 @@
-package com.narutohuo.xindazhou.common.auth.utils
-
-import android.util.Base64
-import org.json.JSONObject
-
-/**
- * JWT 工具类(客户端解析,不验证签名)
- * 
- * 用于解析 JWT Token 并检查是否过期
- * 注意:客户端不需要验证签名(签名验证由服务端完成),只需要解析 payload 检查 exp 字段
- */
-object JWTUtil {
-    
-    /**
-     * 解析 JWT Token 并检查是否过期
-     * 
-     * @param token JWT Token
-     * @return true 如果 Token 未过期,false 如果过期或无法解析
-     */
-    fun isTokenValid(token: String?): Boolean {
-        if (token.isNullOrEmpty()) {
-            return false
-        }
-        
-        // JWT 格式:header.payload.signature
-        val parts = token.split(".")
-        if (parts.size != 3) {
-            return false
-        }
-        
-        // 解析 payload(第二部分)
-        val payload = parts[1]
-        
-        try {
-            // Base64URL 解码
-            val payloadBytes = base64UrlDecode(payload) ?: return false
-            val payloadJson = JSONObject(String(payloadBytes))
-            
-            // 获取 exp 字段(过期时间,Unix 时间戳,单位:秒)
-            if (!payloadJson.has("exp")) {
-                return false
-            }
-            
-            val exp = payloadJson.getLong("exp")
-            
-            // 检查是否过期(exp 是 Unix 时间戳,单位:秒)
-            val currentTime = System.currentTimeMillis() / 1000 // 转换为秒
-            return exp > currentTime
-        } catch (e: Exception) {
-            // 解析失败,认为 Token 无效
-            return false
-        }
-    }
-    
-    /**
-     * 解析 JWT Token 并获取过期时间
-     * 
-     * @param token JWT Token
-     * @return 过期时间(Unix 时间戳,单位:秒),如果无法解析返回 null
-     */
-    fun getExpiresAt(token: String?): Long? {
-        if (token.isNullOrEmpty()) {
-            return null
-        }
-        
-        // JWT 格式:header.payload.signature
-        val parts = token.split(".")
-        if (parts.size != 3) {
-            return null
-        }
-        
-        // 解析 payload(第二部分)
-        val payload = parts[1]
-        
-        try {
-            // Base64URL 解码
-            val payloadBytes = base64UrlDecode(payload) ?: return null
-            val payloadJson = JSONObject(String(payloadBytes))
-            
-            // 获取 exp 字段(过期时间,Unix 时间戳,单位:秒)
-            if (!payloadJson.has("exp")) {
-                return null
-            }
-            
-            return payloadJson.getLong("exp")
-        } catch (e: Exception) {
-            // 解析失败
-            return null
-        }
-    }
-    
-    /**
-     * Base64URL 解码
-     * 
-     * Base64URL 是 Base64 的变体,用于 URL 安全:
-     * - 将 '+' 替换为 '-'
-     * - 将 '/' 替换为 '_'
-     * - 移除填充字符 '='
-     */
-    private fun base64UrlDecode(base64Url: String): ByteArray? {
-        return try {
-            // 转换为标准 Base64
-            var base64 = base64Url
-                .replace("-", "+")
-                .replace("_", "/")
-            
-            // 添加填充字符
-            val remainder = base64.length % 4
-            if (remainder > 0) {
-                base64 += "=".repeat(4 - remainder)
-            }
-            
-            // Base64 解码
-            Base64.decode(base64, Base64.NO_WRAP)
-        } catch (e: Exception) {
-            null
-        }
-    }
-}
-

+ 15 - 3
base-common/src/main/java/com/narutohuo/xindazhou/common/dialog/CascadePickerDialog.kt

@@ -338,13 +338,25 @@ object CascadePickerDialog {
             }
             
             holder.textView.setOnClickListener {
+                // 使用 holder.bindingAdapterPosition 获取当前位置,避免 position 参数过时的问题
+                val currentPosition = holder.bindingAdapterPosition
+                if (currentPosition == RecyclerView.NO_POSITION) {
+                    return@setOnClickListener
+                }
+                
+                // 检查位置是否有效
+                if (currentPosition < 0 || currentPosition >= items.size) {
+                    return@setOnClickListener
+                }
+                
+                val currentItem = items[currentPosition]
                 val oldPosition = selectedPosition
-                selectedPosition = position
+                selectedPosition = currentPosition
                 if (oldPosition >= 0) {
                     notifyItemChanged(oldPosition)
                 }
-                notifyItemChanged(position)
-                onItemClick(item)
+                notifyItemChanged(currentPosition)
+                onItemClick(currentItem)
             }
         }
         

+ 1 - 1
base-common/src/main/java/com/narutohuo/xindazhou/common/launch/AppLaunchManager.kt

@@ -2,7 +2,7 @@ package com.narutohuo.xindazhou.common.launch
 
 import androidx.fragment.app.FragmentActivity
 import androidx.lifecycle.lifecycleScope
-import com.narutohuo.xindazhou.common.auth.NavigationCallback
+import com.narutohuo.xindazhou.common.launch.NavigationCallback
 import com.narutohuo.xindazhou.core.log.ILog
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.launch

+ 3 - 3
base-common/src/main/java/com/narutohuo/xindazhou/common/auth/NavigationCallback.kt

@@ -1,9 +1,9 @@
-package com.narutohuo.xindazhou.common.auth
+package com.narutohuo.xindazhou.common.launch
 
 /**
  * 导航回调接口
  * 
- * 用于单 Activity 架构中,Fragment 通过接口回调触发导航
+ * 用于 launch 模块中,Fragment 或 Manager 通过接口回调触发导航
  * Activity 实现此接口,处理具体的导航逻辑
  */
 interface NavigationCallback {
@@ -18,7 +18,7 @@ interface NavigationCallback {
     fun navigateToLogin()
     
     /**
-     * 导航到注册页
+     * 导航到注册页(可选)
      */
     fun navigateToRegister()
 }

+ 1 - 1
base-common/src/main/java/com/narutohuo/xindazhou/common/launch/ui/OnboardingFragment.kt

@@ -13,7 +13,7 @@ import com.google.android.material.button.MaterialButton
 import com.google.android.material.tabs.TabLayout
 import com.google.android.material.tabs.TabLayoutMediator
 import com.narutohuo.xindazhou.common.R
-import com.narutohuo.xindazhou.common.auth.NavigationCallback
+import com.narutohuo.xindazhou.common.launch.NavigationCallback
 import com.narutohuo.xindazhou.common.launch.OnboardingManager
 import com.narutohuo.xindazhou.common.launch.model.OnboardingItem
 

+ 117 - 64
base-common/src/main/java/com/narutohuo/xindazhou/common/network/ApiBaseRemoteDataSource.kt

@@ -1,11 +1,14 @@
 package com.narutohuo.xindazhou.common.network
 
-import android.util.Log
+import com.narutohuo.xindazhou.common.network.exception.ApiException
+import com.narutohuo.xindazhou.common.network.exception.ExceptionHandle
+import com.narutohuo.xindazhou.common.network.operator.GlobalApiOperator
+import com.narutohuo.xindazhou.common.network.operator.retryRequest
+import com.narutohuo.xindazhou.common.network.response.ApiResponse
+import com.narutohuo.xindazhou.core.log.ILog
 import retrofit2.Response
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.withContext
-import java.net.UnknownHostException
-import java.net.SocketTimeoutException
 
 /**
  * API 基础远程数据源
@@ -15,17 +18,33 @@ import java.net.SocketTimeoutException
  * - 日志记录
  * - 线程切换(自动切换到 IO 线程)
  * - 网络异常友好提示
+ * - 全局处理器(GlobalApiOperator)
+ * - 自动重试(可选)
  * 
  * 使用方式:
  * ```kotlin
  * class AuthRemoteDataSource : ApiBaseRemoteDataSource() {
  *     private val authApi: AuthApi = ApiManager.create()
  *     
- *     suspend fun login(request: LoginRequest): Result<LoginResponse> {
- *         return executeRequest(
+ *     suspend fun login(request: LoginRequest): ApiResponse<LoginResponse> {
+ *         return executeRequestResponse(
  *             request = { authApi.login(request) },
- *             errorMessage = "登录失败"
- *         )
+ *             enableRetry = true,
+ *             retryTimes = 3
+ *         ).onSuccess { data ->
+ *             ILog.d("Auth", "登录成功")
+ *         }.onError { code, message ->
+ *             ILog.w("Auth", "登录失败: $code - $message")
+ *         }.onException { exception ->
+ *             ILog.e("Auth", "登录异常: ${exception.message}")
+ *         }
+ *     }
+ *     
+ *     // 如果需要 Result<T>,使用 toResult() 转换
+ *     suspend fun loginAsResult(request: LoginRequest): Result<LoginResponse> {
+ *         return executeRequestResponse(
+ *             request = { authApi.login(request) }
+ *         ).toResult()
  *     }
  * }
  * ```
@@ -42,83 +61,117 @@ abstract class ApiBaseRemoteDataSource {
         get() = this::class.simpleName ?: "ApiBaseRemoteDataSource"
     
     /**
-     * 处理网络请求(通用方法)
+     * 执行请求(新 API,返回 ApiResponse<T>)
+     * 
+     * 参考 Sandwich 设计,返回统一的 ApiResponse 封装
+     * 支持全局处理、自动重试等高级功能
      * 
      * 自动处理:
-     * - HTTP 错误码
-     * - 业务错误码
-     * - 网络异常
-     * - 超时异常
-     * - 日志记录
+     * - HTTP 错误码 → Exception
+     * - 业务错误码 → Error
+     * - 网络异常 → Exception
+     * - 全局处理器(GlobalApiOperator)
+     * - 自动重试(如果启用)
      * 
      * @param request 网络请求的 suspend 函数
-     * @param errorMessage 错误消息前缀
-     * @return Result<T>
+     * @param enableRetry 是否启用自动重试(默认 false)
+     * @param retryTimes 重试次数(默认 3 次,仅在 enableRetry = true 时生效)
+     * @return ApiResponse<T> 成功返回 Success,业务错误返回 Error,网络异常返回 Exception
+     * 
+     * 示例:
+     * ```kotlin
+     * val response = executeRequestResponse(
+     *     request = { api.getData() },
+     *     enableRetry = true,
+     *     retryTimes = 3
+     * )
+     * 
+     * response.onSuccess { data -> ... }
+     *         .onError { code, message -> ... }
+     *         .onException { exception -> ... }
+     * ```
      */
-    protected suspend inline fun <reified T> executeRequest(
+    protected suspend inline fun <reified T> executeRequestResponse(
         crossinline request: suspend () -> Response<ApiCommonResult<T>>,
-        errorMessage: String = "请求失败"
-    ): Result<T> = withContext(Dispatchers.IO) {
-        try {
-            Log.d(tag, "开始请求: ${T::class.simpleName}")
+        enableRetry: Boolean = false,
+        retryTimes: Int = 3
+    ): ApiResponse<T> = withContext(Dispatchers.IO) {
+        val apiResponse = try {
+            ILog.d(tag, "开始请求: ${T::class.simpleName}")
             val response = request()
             
-            // 使用 reified 泛型,自动获取 Class
-            ApiResponseParser.handleResponse(
-                response = response,
-                dataClass = T::class.java,
-                tag = tag,
-                errorMessage = errorMessage
-            )
-        } catch (e: SocketTimeoutException) {
-            val errorMsg = "请求超时,请检查网络连接"
-            Log.e(tag, errorMsg, e)
-            Result.failure(Exception(errorMsg))
-        } catch (e: UnknownHostException) {
-            val errorMsg = "网络连接失败,请检查网络设置"
-            Log.e(tag, errorMsg, e)
-            Result.failure(Exception(errorMsg))
-        } catch (e: Exception) {
-            val errorMsg = e.message ?: errorMessage
-            Log.e(tag, "请求异常: $errorMsg", e)
-            Result.failure(Exception(errorMsg))
+            // 解析响应为 ApiResponse(使用新 API)
+            ApiResponseParser.parseResponse<T>(response, tag)
+        } catch (e: Throwable) {
+            // 使用 ExceptionHandle 统一处理异常
+            val apiException = ExceptionHandle.handle(e)
+            ILog.e(tag, "请求异常: ${apiException.getUserMessage()}", e)
+            ApiResponse.Exception(apiException)
+        }
+        
+        // 应用全局处理器
+        val processedResponse = GlobalApiOperator.apply(apiResponse)
+        
+        // 如果需要重试且响应为异常
+        if (enableRetry && processedResponse is ApiResponse.Exception) {
+            ILog.d(tag, "启用自动重试,重试次数: $retryTimes")
+            retryRequest(times = retryTimes) {
+                try {
+                    val retryResponse = request()
+                    ApiResponseParser.parseResponse<T>(retryResponse, tag)
+                } catch (e: Throwable) {
+                    val apiException = ExceptionHandle.handle(e)
+                    ApiResponse.Exception(apiException)
+                }
+            }
+        } else {
+            processedResponse
         }
     }
     
     /**
-     * 处理可空数据的网络请求
+     * 执行可空数据请求(返回 ApiResponse<T?>)
      * 
      * 用于处理服务器可能返回 null 的情况
      * 
      * @param request 网络请求的 suspend 函数
-     * @param errorMessage 错误消息前缀
-     * @return Result<T?>
+     * @param enableRetry 是否启用自动重试(默认 false)
+     * @param retryTimes 重试次数(默认 3 次,仅在 enableRetry = true 时生效)
+     * @return ApiResponse<T?> 成功返回数据(可能为 null),失败返回 Error 或 Exception
      */
-    protected suspend inline fun <reified T> executeNullableRequest(
+    protected suspend inline fun <reified T> executeNullableRequestResponse(
         crossinline request: suspend () -> Response<ApiCommonResult<T>>,
-        errorMessage: String = "请求失败"
-    ): Result<T?> = withContext(Dispatchers.IO) {
-        try {
-            Log.d(tag, "开始请求(可空): ${T::class.simpleName}")
+        enableRetry: Boolean = false,
+        retryTimes: Int = 3
+    ): ApiResponse<T?> = withContext(Dispatchers.IO) {
+        val apiResponse = try {
+            ILog.d(tag, "开始请求(可空): ${T::class.simpleName}")
             val response = request()
             
-            ApiResponseParser.handleNullableResponse(
-                response = response,
-                tag = tag,
-                errorMessage = errorMessage
-            )
-        } catch (e: SocketTimeoutException) {
-            val errorMsg = "请求超时,请检查网络连接"
-            Log.e(tag, errorMsg, e)
-            Result.failure(Exception(errorMsg))
-        } catch (e: UnknownHostException) {
-            val errorMsg = "网络连接失败,请检查网络设置"
-            Log.e(tag, errorMsg, e)
-            Result.failure(Exception(errorMsg))
-        } catch (e: Exception) {
-            val errorMsg = e.message ?: errorMessage
-            Log.e(tag, "请求异常: $errorMsg", e)
-            Result.failure(Exception(errorMsg))
+            // 解析响应为 ApiResponse(使用新 API,支持可空)
+            ApiResponseParser.parseNullableResponse<T>(response, tag)
+        } catch (e: Throwable) {
+            val apiException = ExceptionHandle.handle(e)
+            ILog.e(tag, "请求异常: ${apiException.getUserMessage()}", e)
+            ApiResponse.Exception(apiException)
+        }
+        
+        // 应用全局处理器
+        val processedResponse = GlobalApiOperator.apply(apiResponse)
+        
+        // 如果需要重试且响应为异常
+        if (enableRetry && processedResponse is ApiResponse.Exception) {
+            retryRequest(times = retryTimes) {
+                try {
+                    val retryResponse = request()
+                    ApiResponseParser.parseNullableResponse<T>(retryResponse, tag)
+                } catch (e: Throwable) {
+                    val apiException = ExceptionHandle.handle(e)
+                    ApiResponse.Exception(apiException)
+                }
+            }
+        } else {
+            processedResponse
         }
     }
 }

+ 2 - 2
base-common/src/main/java/com/narutohuo/xindazhou/common/network/ApiBaseRepository.kt

@@ -1,6 +1,6 @@
 package com.narutohuo.xindazhou.common.network
 
-import android.util.Log
+import com.narutohuo.xindazhou.core.log.ILog
 
 /**
  * API 基础仓库类
@@ -47,7 +47,7 @@ open class ApiBaseRepository {
      */
     protected open fun handleError(throwable: Throwable): Result<Nothing> {
         val errorMsg = throwable.message ?: "操作失败"
-        Log.e(tag, "操作失败: $errorMsg", throwable)
+        ILog.e(tag, "操作失败: $errorMsg", throwable)
         return Result.failure(throwable)
     }
 }

+ 93 - 82
base-common/src/main/java/com/narutohuo/xindazhou/common/network/ApiManager.kt

@@ -1,103 +1,81 @@
 package com.narutohuo.xindazhou.common.network
 
 import com.narutohuo.xindazhou.common.config.ServerConfigManager
+import com.narutohuo.xindazhou.common.network.NetworkManager
+import com.narutohuo.xindazhou.common.network.NetworkConfig
 import com.narutohuo.xindazhou.core.log.ILog
-import com.narutohuo.xindazhou.core.network.NetworkManager
 import retrofit2.Retrofit
 
 /**
- * API 管理器
+ * API 管理器(object 单例,统一使用类名直接调用)
  * 
  * 统一创建和管理 API 接口实例,避免重复代码
  * 整合了网络配置和 API 接口创建功能
  * 
- * 与 AuthManager、SocketIOManager 类似,提供统一的 API 封装
- * 自动处理配置初始化、Token 管理、URL 刷新等,外部只需要调用 create() 即可
- * 
- * 使用方式:
+ * 使用方式(完全懒加载,第一次使用时自动初始化):
  * ```kotlin
- * // 在 AppInitializer 中初始化(自动从 ServerConfigManager 读取服务器地址
- * ApiManager.initialize(application)
+ * // 创建 API 接口实例(第一次调用时自动初始化)
+ * val authApi = ApiManager.create<AuthApi>()
  * 
  * // 设置 Token 提供器(可选,通常由 AuthManager 自动设置)
  * ApiManager.setTokenProvider { TokenManager.getAccessToken() }
- * 
- * // 然后就可以直接使用,无需再设置 baseUrl
- * val authApi = ApiManager.create<AuthApi>()
- * 
- * // 或者使用自定义 baseUrl
- * val authApi = ApiManager.create<AuthApi>("https://api.example.com")
- * 
- * // 重置网络管理器(切换服务器地址时)
- * ApiManager.reset()
  * ```
  */
 object ApiManager {
     
-    private const val TAG = "ApiManager"
+    @PublishedApi
+    internal val TAG = "ApiManager"
     
-    private var cachedBaseURL: String? = null
-    private var isInitialized = false
+    @PublishedApi
+    internal var cachedBaseURL: String? = null
+    @PublishedApi
+    internal var isInitialized = false
     
     /**
-     * baseUrl 提供器(默认配置)
-     * 
-     * 在 initialize() 中自动设置,从 ServerConfigManager 读取服务器地址
-     * 也可以手动设置自定义提供器
-     * 
-     * 示例:
-     * ```kotlin
-     * ApiManager.baseUrlProvider = { 
-     *     ServerConfigManager.getHttpServerUrl() 
-     * }
-     * ```
+     * baseUrl 提供器(内部使用,自动从 ServerConfigManager 读取)
      */
-    var baseUrlProvider: (() -> String)? = null
+    private var baseUrlProvider: (() -> String)? = null
     
     /**
-     * 初始化 API 管理器(从存储读取服务器地址)
-     * 
-     * 自动从 ServerConfigManager 读取服务器地址并缓存,同时初始化底层网络管理器
-     * 自动检测调试模式,所有参数使用默认值,业务层只需一行代码即可完成初始化
-     * 
-     * @param application Application 实例(用于检测调试模式)
+     * 确保已初始化(懒加载,第一次使用时自动初始化)
      */
-    fun initialize(application: android.app.Application) {
+    @PublishedApi
+    internal fun ensureInitialized() {
         if (isInitialized) {
-            ILog.d(TAG, "已初始化,跳过")
             return
         }
         
-        // 从 ServerConfigManager 读取服务器地址
-        val serverURL = ServerConfigManager.getHttpServerUrl()
-        cachedBaseURL = serverURL
-        
-        // 设置 baseURLProvider 返回缓存的地址
-        this.baseUrlProvider = {
-            cachedBaseURL ?: serverURL
-        }
-        
-        // 自动检测调试模式
-        val isDebug = try {
-            val buildConfigClass = Class.forName("${application.packageName}.BuildConfig")
-            val debugField = buildConfigClass.getField("DEBUG")
-            debugField.getBoolean(null)
-        } catch (e: Exception) {
-            ILog.e(TAG, "无法获取 BuildConfig.DEBUG,默认返回 false", e)
-            false
+        synchronized(this) {
+            if (isInitialized) {
+                return
+            }
+            
+            ILog.d(TAG, "ApiManager 懒加载初始化...")
+            
+            // 从 ServerConfigManager 读取服务器地址
+            val serverURL = ServerConfigManager.getHttpServerUrl()
+            cachedBaseURL = serverURL
+            
+            // 自动设置 baseURLProvider 返回缓存的地址
+            this.baseUrlProvider = {
+                cachedBaseURL ?: serverURL
+            }
+            
+            // 检测调试模式(使用默认值 false,如果需要可以从全局获取)
+            val isDebug = false
+            
+            // 初始化底层网络管理器(使用统一的 NetworkManager)
+            NetworkManager.init(
+                NetworkConfig.Builder()
+                    .baseUrl(serverURL)
+                    .isDebug(isDebug)
+                    .enableLogging(isDebug)
+                    .build()
+            )
+            
+            isInitialized = true
+            ILog.d(TAG, "ApiManager 懒加载初始化完成,baseURL: $serverURL")
         }
-        
-        // 初始化底层网络管理器(使用 base-core 的 NetworkManager)
-        NetworkManager.init(
-            com.narutohuo.xindazhou.core.network.NetworkConfig.Builder()
-                .baseUrl(serverURL)
-                .isDebug(isDebug)
-                .enableLogging(isDebug)
-                .build()
-        )
-        
-        isInitialized = true
-        ILog.d(TAG, "初始化完成,baseURL: $serverURL, isDebug: $isDebug")
     }
     
     /**
@@ -105,6 +83,7 @@ object ApiManager {
      * 
      * 当服务器地址在存储中更新后,可以调用此方法刷新
      */
+    @JvmStatic
     fun refreshBaseURL() {
         val serverURL = ServerConfigManager.getHttpServerUrl()
         cachedBaseURL = serverURL
@@ -118,31 +97,61 @@ object ApiManager {
      * @return API 接口实例
      * @throws IllegalStateException 如果 baseUrl 和 baseUrlProvider 都未设置
      */
+    @JvmStatic
     inline fun <reified T> create(baseUrl: String? = null): T {
-        // 优先级:传入的 baseUrl > baseUrlProvider > 抛出异常
-        val url = baseUrl ?: baseUrlProvider?.invoke()
-            ?: throw IllegalStateException(
-                "baseUrl 未设置。请先调用 ApiManager.initialize() " +
-                "或 ApiManager.baseUrlProvider = { ... } " +
-                "或在 create() 中传入 baseUrl 参数"
-            )
+        ensureInitialized()  // 懒加载初始化
         
-        // 使用 NetworkManager 创建 Retrofit 实例
-        val retrofit = NetworkManager.getRetrofit(
-            com.narutohuo.xindazhou.core.network.NetworkConfig.Builder()
-                .baseUrl(url)
-                .build()
-        )
+        // 如果传入了 baseUrl,需要先重置 NetworkManager(切换服务器地址)
+        if (baseUrl != null) {
+            val currentBaseURL = getBaseUrlForCreate()
+            if (baseUrl != currentBaseURL) {
+                // baseUrl 改变了,需要重置 NetworkManager 并重新初始化
+                ILog.d(TAG, "检测到 baseUrl 变化: $currentBaseURL -> $baseUrl,重新初始化 NetworkManager")
+                handleBaseUrlChange(baseUrl)
+            }
+        }
+        
+        // 使用 NetworkManager 创建 Retrofit 实例(使用 defaultConfig)
+        val retrofit = NetworkManager.getRetrofit()  // 不传入 config,使用 defaultConfig
         
         return retrofit.create(T::class.java)
     }
     
     /**
+     * 处理 baseUrl 变化(内部方法,用于切换服务器地址)
+     */
+    @PublishedApi
+    internal fun handleBaseUrlChange(newBaseUrl: String) {
+        NetworkManager.reset()
+        NetworkManager.init(
+            NetworkConfig.Builder()
+                .baseUrl(newBaseUrl)
+                .isDebug(false)
+                .enableLogging(false)
+                .build()
+        )
+        cachedBaseURL = newBaseUrl
+        baseUrlProvider = { newBaseUrl }
+    }
+    
+    /**
+     * 获取 baseUrl(供 create() 使用)
+     */
+    @PublishedApi
+    internal fun getBaseUrlForCreate(): String {
+        return baseUrlProvider?.invoke() ?: cachedBaseURL
+            ?: throw IllegalStateException(
+                "baseUrl 未设置。请设置 ApiManager.baseUrlProvider 或在 create() 中传入 baseUrl 参数"
+            )
+    }
+    
+    /**
      * 使用指定的 Retrofit 实例创建 API 接口实例
      * 
      * @param retrofit Retrofit 实例
      * @return API 接口实例
      */
+    @JvmStatic
     inline fun <reified T> create(retrofit: Retrofit): T {
         return retrofit.create(T::class.java)
     }
@@ -155,6 +164,7 @@ object ApiManager {
      * 
      * @param tokenProvider Token 提供器,返回当前的 accessToken
      */
+    @JvmStatic
     fun setTokenProvider(tokenProvider: () -> String?) {
         NetworkManager.tokenProvider = tokenProvider
         ILog.d(TAG, "Token 提供器已设置")
@@ -165,6 +175,7 @@ object ApiManager {
      * 
      * 清空已缓存的 Retrofit 和 OkHttpClient 实例,下次使用时会重新创建
      */
+    @JvmStatic
     fun reset() {
         NetworkManager.reset()
         isInitialized = false

+ 85 - 53
base-common/src/main/java/com/narutohuo/xindazhou/common/network/ApiResponseParser.kt

@@ -1,6 +1,11 @@
 package com.narutohuo.xindazhou.common.network
 
-import android.util.Log
+import com.narutohuo.xindazhou.common.network.exception.BusinessException
+import com.narutohuo.xindazhou.common.network.exception.ExceptionHandle
+import com.narutohuo.xindazhou.common.network.exception.HttpException
+import com.narutohuo.xindazhou.common.network.exception.ParseException
+import com.narutohuo.xindazhou.common.network.response.ApiResponse
+import com.narutohuo.xindazhou.core.log.ILog
 import retrofit2.Response
 
 /**
@@ -18,109 +23,136 @@ data class ApiCommonResult<T>(
 /**
  * API响应解析器
  * 
- * 统一处理 Retrofit Response,将 Response<ApiCommonResult<T>> 转换为 Result<T>
+ * 统一处理 Retrofit Response,将 Response<ApiCommonResult<T>> 转换为 ApiResponse<T>
+ * 参考 Sandwich 设计,提供清晰的响应封装
  */
 object ApiResponseParser {
     
     /**
      * 业务成功码
      */
-    private const val SUCCESS_CODE = 0
+    @PublishedApi
+    internal const val SUCCESS_CODE = 0
     
     /**
-     * 处理网络响应
+     * 解析响应为 ApiResponse(新 API)
+     * 
+     * 使用 reified 泛型自动推断类型,无需手动传 Class
      * 
      * @param response Retrofit Response
-     * @param dataClass 数据类型的 Class(用于类型转换)
      * @param tag 日志标签(用于日志记录)
-     * @param errorMessage 错误消息前缀
-     * @return Result<T> 成功返回数据,失败返回异常
+     * @return ApiResponse<T> 成功返回 Success,业务错误返回 Error,网络异常返回 Exception
+     * 
+     * 使用方式:
+     * ```kotlin
+     * val response: ApiResponse<LoginResponse> = ApiResponseParser.parseResponse(
+     *     response = retrofitResponse,
+     *     tag = "Auth"
+     * )
+     * 
+     * response.onSuccess { data -> ... }
+     *         .onError { code, message -> ... }
+     *         .onException { exception -> ... }
+     * ```
      */
-    fun <T> handleResponse(
+    inline fun <reified T> parseResponse(
         response: Response<ApiCommonResult<T>>,
-        dataClass: Class<T>,
-        tag: String = "ApiResponseParser",
-        errorMessage: String = "请求失败"
-    ): Result<T> {
+        tag: String = "ApiResponseParser"
+    ): ApiResponse<T> {
         return try {
-            // 1. 检查HTTP状态码
+            // 1. 检查 HTTP 状态码
             if (!response.isSuccessful) {
-                val errorMsg = response.message() ?: errorMessage
-                Log.w(tag, "HTTP请求失败,code=${response.code()}")
-                return Result.failure(Exception(errorMsg))
+                val httpException = ExceptionHandle.handleHttpError(response.code(), response.message())
+                ILog.w(tag, "HTTP 请求失败: ${httpException.getUserMessage()}")
+                return ApiResponse.Exception(httpException)
             }
             
             // 2. 获取响应体
-            val commonResult = response.body()
-            if (commonResult == null) {
+            val commonResult = response.body() ?: run {
                 // 尝试读取原始响应体以便调试
                 val errorBody = response.errorBody()?.string() ?: "null"
-                Log.e(tag, "响应体为空,errorBody=$errorBody")
-                return Result.failure(Exception("响应体为空"))
+                ILog.e(tag, "响应体为空,errorBody=$errorBody")
+                val parseException = ExceptionHandle.handleParseError("服务器返回数据格式错误", null)
+                return ApiResponse.Exception(parseException)
             }
             
-            Log.d(tag, "响应体解析成功: code=${commonResult.code}, msg=${commonResult.msg}, data类型=${commonResult.data?.javaClass?.simpleName ?: "null"}")
+            ILog.d(tag, "响应体解析成功: code=${commonResult.code}, msg=${commonResult.msg}, data类型=${T::class.simpleName}")
             
             // 3. 检查业务状态码
             if (commonResult.code != SUCCESS_CODE) {
-                val errorMsg = commonResult.msg ?: "$errorMessage (code: ${commonResult.code})"
-                Log.w(tag, "业务失败,code=${commonResult.code}, msg=$errorMsg")
-                return Result.failure(Exception(errorMsg))
+                val message = commonResult.msg?.takeIf { it.isNotBlank() }
+                    ?: "操作失败(错误码:${commonResult.code})"
+                ILog.w(tag, "业务失败: $message")
+                return ApiResponse.Error(
+                    code = commonResult.code,
+                    message = message,
+                    body = commonResult
+                )
             }
             
-            // 4. 提取业务数据(Gson 已经处理好了类型转换)
+            // 4. 提取业务数据
             val data = commonResult.data
             if (data == null) {
-                Log.e(tag, "服务器返回数据为空: code=${commonResult.code}, msg=${commonResult.msg}")
-                return Result.failure(Exception("服务器返回数据为空"))
+                ILog.e(tag, "服务器返回数据为空: code=${commonResult.code}, msg=${commonResult.msg}")
+                val parseException = ExceptionHandle.handleParseError("服务器返回数据为空", null)
+                return ApiResponse.Exception(parseException)
             }
             
-            Log.d(tag, "请求成功: data类型=${data?.javaClass?.simpleName ?: "null"}")
-            Result.success(data)
+            ILog.d(tag, "请求成功: data类型=${data.javaClass.simpleName}")
+            ApiResponse.Success(data)
             
-        } catch (e: Exception) {
-            Log.e(tag, "处理响应异常", e)
-            Result.failure(e)
+        } catch (e: Throwable) {
+            // 使用 ExceptionHandle 统一处理异常
+            val apiException = ExceptionHandle.handle(e)
+            ILog.e(tag, "处理响应异常: ${apiException.getUserMessage()}", e)
+            ApiResponse.Exception(apiException)
         }
     }
     
     /**
-     * 处理网络响应(支持可空数据)
+     * 解析响应为 ApiResponse(支持可空数据)
      * 
      * @param response Retrofit Response
      * @param tag 日志标签
-     * @param errorMessage 错误消息前缀
-     * @return Result<T?> 成功返回数据(可能为null),失败返回异常
+     * @return ApiResponse<T?> 成功返回数据(可能为 null),失败返回 Error 或 Exception
      */
-    fun <T> handleNullableResponse(
+    inline fun <reified T> parseNullableResponse(
         response: Response<ApiCommonResult<T>>,
-        tag: String = "ApiResponseParser",
-        errorMessage: String = "请求失败"
-    ): Result<T?> {
+        tag: String = "ApiResponseParser"
+    ): ApiResponse<T?> {
         return try {
             if (!response.isSuccessful) {
-                val errorMsg = response.message() ?: errorMessage
-                Log.w(tag, "HTTP请求失败,code=${response.code()}")
-                return Result.failure(Exception(errorMsg))
+                val httpException = ExceptionHandle.handleHttpError(response.code(), response.message())
+                ILog.w(tag, "HTTP 请求失败: ${httpException.getUserMessage()}")
+                return ApiResponse.Exception(httpException)
             }
             
-            val commonResult = response.body() 
-                ?: return Result.failure(Exception("响应体为空"))
+            val commonResult = response.body() ?: run {
+                val parseException = ExceptionHandle.handleParseError("服务器返回数据格式错误", null)
+                return ApiResponse.Exception(parseException)
+            }
             
             if (commonResult.code != SUCCESS_CODE) {
-                val errorMsg = commonResult.msg ?: "$errorMessage (code: ${commonResult.code})"
-                Log.w(tag, "业务失败,code=${commonResult.code}")
-                return Result.failure(Exception(errorMsg))
+                val message = commonResult.msg?.takeIf { it.isNotBlank() }
+                    ?: "操作失败(错误码:${commonResult.code})"
+                ILog.w(tag, "业务失败: $message")
+                return ApiResponse.Error(
+                    code = commonResult.code,
+                    message = message,
+                    body = commonResult
+                )
             }
             
-            // 允许data为null
-            Log.d(tag, "请求成功")
-            Result.success(commonResult.data)
+            // 允许 data  null
+            ILog.d(tag, "请求成功(数据可能为 null)")
+            ApiResponse.Success(commonResult.data)
             
-        } catch (e: Exception) {
-            Log.e(tag, "处理响应异常", e)
-            Result.failure(e)
+        } catch (e: Throwable) {
+            val apiException = ExceptionHandle.handle(e)
+            ILog.e(tag, "处理响应异常: ${apiException.getUserMessage()}", e)
+            ApiResponse.Exception(apiException)
         }
     }
+    
 }
 

+ 32 - 17
base-core/src/main/java/com/narutohuo/xindazhou/core/network/NetworkManager.kt

@@ -1,5 +1,7 @@
-package com.narutohuo.xindazhou.core.network
+package com.narutohuo.xindazhou.common.network
 
+import com.narutohuo.xindazhou.common.network.interceptor.HeaderInterceptor
+import com.narutohuo.xindazhou.common.network.interceptor.TokenRefreshInterceptor
 import com.narutohuo.xindazhou.core.log.ILog
 import okhttp3.OkHttpClient
 import retrofit2.converter.gson.GsonConverterFactory
@@ -13,6 +15,10 @@ import java.util.concurrent.atomic.AtomicReference
  * 
  * 统一管理 Retrofit 和 OkHttpClient 实例,避免重复创建
  * 
+ * **架构说明**:
+ * 按照架构设计文档,网络服务统一放在 `base/common/network/` 模块中。
+ * 此管理器与 ApiManager、ApiResponseParser、HeaderInterceptor 等统一管理。
+ * 
  * 设计模式:
  * - 单例模式:全局唯一实例
  * - 建造者模式:通过 NetworkConfig 灵活配置
@@ -48,6 +54,14 @@ object NetworkManager {
     var tokenProvider: (() -> String?)? = null
     
     /**
+     * Token 刷新提供器(可选,用于自动刷新过期的 Token)
+     * 
+     * 当请求返回 401/402 时,会自动调用此方法刷新 Token
+     * 刷新成功后会自动重试原请求
+     */
+    var refreshTokenProvider: (suspend () -> String?)? = null
+    
+    /**
      * 初始化网络管理器(可选)
      * 
      * @param config 网络配置,如果为 null 则使用默认配置
@@ -66,6 +80,12 @@ object NetworkManager {
     fun getRetrofit(config: NetworkConfig? = null): Retrofit {
         val finalConfig = config ?: defaultConfig ?: NetworkConfig.default()
         
+        // 如果传入了 config 且与 defaultConfig 不同,直接构建新的实例(不缓存)
+        if (config != null && config != defaultConfig) {
+            return buildRetrofit(finalConfig)
+        }
+        
+        // 使用 defaultConfig 时,使用缓存的实例(单例模式)
         return retrofitRef.get() ?: synchronized(this) {
             retrofitRef.get() ?: buildRetrofit(finalConfig).also {
                 retrofitRef.set(it)
@@ -96,6 +116,7 @@ object NetworkManager {
         return Retrofit.Builder()
             .baseUrl(config.baseUrl)
             .client(getOkHttpClient(config))
+            .addCallAdapterFactory(com.narutohuo.xindazhou.common.network.adapter.ApiResponseCallAdapterFactory.create())
             .addConverterFactory(GsonConverterFactory.create())
             .build()
     }
@@ -122,22 +143,16 @@ object NetworkManager {
             builder.addInterceptor(loggingInterceptor)
         }
         
-        // 添加 Token 拦截器
-        tokenProvider?.let { provider ->
-            builder.addInterceptor { chain ->
-                val originalRequest = chain.request()
-                val token = provider()
-                
-                val requestBuilder = originalRequest.newBuilder()
-                    .header("Content-Type", "application/json")
-                    .header("Accept", "application/json")
-                
-                if (!token.isNullOrEmpty()) {
-                    requestBuilder.header("Authorization", "Bearer $token")
-                }
-                
-                chain.proceed(requestBuilder.build())
-            }
+        // 添加请求头拦截器(统一添加 Content-Type、Accept、Authorization 等)
+        // 注意:HeaderInterceptor 必须在前,先添加 Token
+        builder.addInterceptor(HeaderInterceptor(tokenProvider))
+        
+        // 添加 Token 刷新拦截器(拦截 401/402,自动刷新 Token 并重试)
+        // 注意:TokenRefreshInterceptor 必须在后,用于拦截响应并刷新
+        val currentTokenProvider = tokenProvider
+        val currentRefreshTokenProvider = refreshTokenProvider
+        if (currentRefreshTokenProvider != null && currentTokenProvider != null) {
+            builder.addInterceptor(TokenRefreshInterceptor(currentTokenProvider, currentRefreshTokenProvider))
         }
         
         return builder.build()

+ 794 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/network/README.md

@@ -0,0 +1,794 @@
+# 网络层组件说明
+
+本目录包含网络请求相关的所有组件,按照架构设计文档统一管理。
+
+## 📁 目录结构
+
+```
+common/network/
+├── NetworkManager.kt              # 网络管理器(基础设施)
+├── ApiManager.kt                  # API 管理器(业务封装)
+├── ApiCommonResult.kt             # 统一响应格式(定义在 ApiResponseParser.kt 中)
+├── ApiResponseParser.kt           # 响应解析器
+├── ApiBaseRemoteDataSource.kt     # 远程数据源基类
+├── ApiBaseRepository.kt           # Repository 基类
+├── adapter/                       # Retrofit 适配器模块
+│   └── ApiResponseCallAdapterFactory.kt  # CallAdapter 工厂(让接口直接返回 ApiResponse<T>)
+├── response/                      # 响应封装模块(参考 Sandwich 设计)
+│   ├── ApiResponse.kt             # 响应封装类(Success/Error/Exception)
+│   └── ApiResponseExtensions.kt   # 扩展函数(所有扩展函数,包含 zip/zip3/recoverWith/requireNotNull 等)
+├── operator/                      # 全局处理模块
+│   ├── ApiResponseOperator.kt     # Operator 接口
+│   ├── GlobalApiOperator.kt       # 全局响应处理器(支持多个 Operator 链式调用)
+│   └── RetryOperator.kt           # 重试机制(支持 Duration 和 Long 两种版本)
+├── interceptor/
+│   ├── HeaderInterceptor.kt       # 请求头拦截器
+│   └── TokenRefreshInterceptor.kt # Token 刷新拦截器(自动处理 401/402)
+└── exception/
+    ├── ApiException.kt            # 自定义异常类
+    └── ExceptionHandle.kt         # 统一异常处理
+```
+
+---
+
+## 1. NetworkManager(网络管理器)
+
+**文件**: `NetworkManager.kt`
+
+**作用**: 
+- 管理 Retrofit 和 OkHttpClient 实例(单例模式)
+- 构建网络客户端(添加拦截器、配置超时等)
+- 提供网络基础设施能力
+
+**职责**:
+- ✅ 管理 Retrofit 实例(单例,避免重复创建)
+- ✅ 管理 OkHttpClient 实例(单例)
+- ✅ 添加日志拦截器
+- ✅ 添加 HeaderInterceptor(请求头)
+- ✅ 配置超时时间
+
+**使用场景**:
+- **业务层不需要直接使用**,通过 `ApiManager` 间接使用
+- **自动初始化** - `NetworkManager` 由 `ApiManager` 自动初始化,无需手动调用
+- **能力层 SDK** 可以直接使用(如果需要独立的网络客户端)
+
+**注意**: 
+- ✅ **业务层不需要手动初始化** - `ApiManager.create<>()` 会自动初始化 `NetworkManager`
+- ✅ **完全自动化** - 所有配置都自动处理,无需手动操作
+
+---
+
+## 2. ApiManager(API 管理器)
+
+**文件**: `ApiManager.kt`
+
+**作用**: 
+- 业务层的统一入口,简化 API 接口的创建
+- 自动处理配置(从 ServerConfigManager 读取)
+- 自动初始化 NetworkManager
+
+**职责**:
+- ✅ 完全懒加载(第一次调用 `create()` 时自动初始化)
+- ✅ 自动从 ServerConfigManager 读取服务器地址
+- ✅ 自动检测调试模式
+- ✅ 自动初始化 NetworkManager
+- ✅ 创建 API 接口实例(工厂方法)
+- ✅ 管理 Token Provider(可选,通常由 AuthManager 自动设置)
+
+**使用场景**:
+- **业务层使用**(Repository、DataSource)
+- 业务层的首选方式(最简单)
+- **无需手动初始化** - 完全懒加载,第一次使用时自动初始化
+
+**使用示例**:
+```kotlin
+// 创建 API 接口(完全懒加载,第一次使用时自动初始化)
+// 无需手动调用 initialize(),create() 会自动初始化
+val authApi = ApiManager.create<AuthApi>()
+
+// 使用接口(如果使用 CallAdapter,直接返回 ApiResponse<T>)
+val response: ApiResponse<LoginResponse> = authApi.login(LoginRequest(...))
+response.onSuccess { data -> /* 处理成功 */ }
+        .onError { code, message -> /* 处理业务错误 */ }
+        .onException { exception -> /* 处理异常 */ }
+
+// 设置 Token(可选,通常由 AuthManager 自动设置)
+// 如果需要手动设置,可以调用:
+// ApiManager.setTokenProvider { TokenStore.getAccessToken() }
+```
+
+**与 NetworkManager 的区别**:
+| 对比项 | NetworkManager | ApiManager |
+|--------|---------------|------------|
+| 层级 | 基础设施层 | 业务封装层 |
+| 职责 | 构建网络客户端 | 简化业务使用 |
+| 配置 | 需要手动配置 | 自动读取配置(懒加载) |
+| 初始化 | 可选手动初始化 | 完全懒加载(第一次使用时自动初始化) |
+| 使用者 | 能力层 SDK | 业务层(Repository) |
+
+**核心特性**:
+- ✅ **完全懒加载** - 无需手动调用 `initialize()`,第一次使用 `create()` 时自动初始化
+- ✅ **自动配置** - 自动从 `ServerConfigManager` 读取服务器地址和配置
+- ✅ **简洁易用** - 业务层只需要调用 `ApiManager.create<Api>()` 即可
+
+---
+
+## 3. ApiCommonResult(统一响应格式)
+
+**文件**: `ApiResponseParser.kt`(定义在此文件中)
+
+**作用**: 
+- 定义统一的 API 响应格式(对应后端 `CommonResult<T>`)
+- 所有接口都使用此格式包装
+
+**结构**:
+```kotlin
+data class ApiCommonResult<T>(
+    val code: Int,        // 业务状态码(0 表示成功)
+    val msg: String?,     // 错误消息
+    val data: T?          // 业务数据
+)
+```
+
+**使用场景**:
+- 所有 API 接口都使用 CallAdapter,直接返回 `ApiResponse<T>`
+- 由 `ApiResponseParser` 自动解析和转换
+
+**示例**:
+```kotlin
+// API 接口定义(推荐方式,直接返回 ApiResponse<T>)
+interface AuthApi {
+    @POST("member/auth/login")
+    suspend fun login(@Body request: LoginRequest): ApiResponse<LoginResponse>
+}
+
+// 使用(无需手动解析)
+val response: ApiResponse<LoginResponse> = authApi.login(request)
+response.onSuccess { data -> /* 处理成功 */ }
+        .onError { code, message -> /* 处理业务错误 */ }
+        .onException { exception -> /* 处理异常 */ }
+```
+
+**注意**: 
+- ✅ `ApiResponseCallAdapterFactory` 已自动注册到 `NetworkManager`
+- ✅ 接口直接返回 `ApiResponse<T>`,无需手动解析
+- ✅ 类型安全,编译时检查
+
+---
+
+## 4. ApiResponseParser(响应解析器)
+
+**文件**: `ApiResponseParser.kt`
+
+**作用**: 
+- 解析 Retrofit 的 `Response<ApiCommonResult<T>>`
+- 提取业务数据,转换为 `ApiResponse<T>`
+- 统一处理 HTTP 错误码和业务错误码
+
+**职责**:
+- ✅ 检查 HTTP 状态码(200-299)
+- ✅ 检查业务状态码(code == 0)
+- ✅ 提取 `data` 字段
+- ✅ 转换为 `ApiResponse<T>`(Success/Error/Exception)
+
+**使用场景**:
+- **不直接使用**,由 `ApiBaseRemoteDataSource.executeRequestResponse()` 内部调用
+
+**处理流程**:
+```
+Response<ApiCommonResult<T>>
+    ↓
+检查 HTTP 状态码
+    ↓
+检查业务状态码 (code == 0)
+    ↓
+提取 data 字段
+    ↓
+ApiResponse<T>(Success/Error/Exception)
+```
+
+---
+
+## 5. ApiBaseRemoteDataSource(远程数据源基类)
+
+**文件**: `ApiBaseRemoteDataSource.kt`
+
+**作用**: 
+- 封装通用的网络请求逻辑
+- 提供 `executeRequestResponse()` 方法,简化网络请求代码
+- 统一处理异常、线程切换、全局处理和自动重试
+
+**职责**:
+- ✅ 自动切换到 IO 线程(`withContext(Dispatchers.IO)`)
+- ✅ 调用 `ApiResponseParser.parseResponse()` 解析响应
+- ✅ 使用 `GlobalApiOperator` 应用全局处理
+- ✅ 支持自动重试(可选)
+- ✅ 使用 `ExceptionHandle` 统一处理异常
+- ✅ 统一日志记录
+
+**使用场景**:
+- **RemoteDataSource 继承使用**(如 `AuthRemoteDataSource`)
+
+**使用示例**:
+```kotlin
+class AuthRemoteDataSource : ApiBaseRemoteDataSource() {
+    private val authApi = ApiManager.create<AuthApi>()
+    
+    suspend fun login(request: LoginRequest): ApiResponse<LoginResponse> {
+        // 使用 executeRequestResponse,自动处理异常、线程切换、响应解析、全局处理
+        return executeRequestResponse(
+            request = { authApi.login(request) },
+            enableRetry = true,
+            retryTimes = 3
+        ).onSuccess { data ->
+            ILog.d("Auth", "登录成功")
+        }.onError { code, message ->
+            ILog.w("Auth", "登录失败: $code - $message")
+        }.onException { exception ->
+            ILog.e("Auth", "登录异常: ${exception.message}")
+        }
+    }
+    
+    // 如果需要 Result<T>,使用 toResult() 转换
+    suspend fun loginAsResult(request: LoginRequest): Result<LoginResponse> {
+        return executeRequestResponse(
+            request = { authApi.login(request) }
+        ).toResult()
+    }
+}
+```
+
+---
+
+## 6. ApiBaseRepository(Repository 基类)
+
+**文件**: `ApiBaseRepository.kt`
+
+**作用**: 
+- Repository 模式的基类
+- 提供通用的错误处理和数据转换方法
+- 预留扩展点(子类可重写)
+
+**职责**:
+- ✅ 统一错误处理(`handleError()`)
+- ✅ 数据转换(`transform()`,Data Model → Domain Model)
+- ✅ 日志记录
+
+**使用场景**:
+- **Repository 继承使用**(如 `AuthRepository`)
+
+**使用示例**:
+```kotlin
+class AuthRepository(
+    private val remoteDataSource: AuthRemoteDataSource
+) : ApiBaseRepository() {
+    
+    suspend fun login(mobile: String, password: String): Result<LoginResponse> {
+        val request = LoginRequest(mobile, password)
+        return remoteDataSource.login(request)
+    }
+}
+```
+
+---
+
+## 7. HeaderInterceptor(请求头拦截器)
+
+**文件**: `interceptor/HeaderInterceptor.kt`
+
+**作用**: 
+- 统一为所有请求添加公共请求头
+- 自动添加 Token(如果提供)
+
+**职责**:
+- ✅ 添加 `Content-Type: application/json`
+- ✅ 添加 `Accept: application/json`
+- ✅ 添加 `Authorization: Bearer {token}`(可选)
+
+**使用场景**:
+- **不直接使用**,由 `NetworkManager` 自动添加
+- 所有通过 `NetworkManager` 创建的请求都会自动使用
+
+**添加的请求头**:
+```
+Content-Type: application/json
+Accept: application/json
+Authorization: Bearer {token}  // 如果提供了 tokenProvider
+```
+
+---
+
+## 8. ApiException(自定义异常类)
+
+**文件**: `exception/ApiException.kt`
+
+**作用**: 
+- 定义统一的异常类型
+- 区分不同类型的错误(网络、HTTP、业务、解析)
+
+**异常类型**:
+- `NetworkException`: 网络连接异常(无网络、超时、DNS 失败等)
+- `HttpException`: HTTP 状态码异常(404、500 等)
+- `BusinessException`: 业务异常(业务状态码错误,如登录失败)
+- `ParseException`: 数据解析异常(JSON 解析失败等)
+- `UnknownApiException`: 未知异常(兜底)
+
+**使用场景**:
+- 由 `ExceptionHandle` 自动转换
+- 业务层可以通过 `when` 判断异常类型
+
+**使用示例**:
+```kotlin
+result.onFailure { throwable ->
+    when (val apiException = ExceptionHandle.handle(throwable)) {
+        is NetworkException -> { /* 网络问题 */ }
+        is BusinessException -> { /* 业务错误 */ }
+        is HttpException -> { /* HTTP 错误 */ }
+    }
+}
+```
+
+---
+
+## 9. ExceptionHandle(统一异常处理)
+
+**文件**: `exception/ExceptionHandle.kt`
+
+**作用**: 
+- 统一异常处理类
+- 将各种异常转换为 `ApiException`
+- 提供用户友好的错误消息
+
+**职责**:
+- ✅ 转换异常类型(`Throwable` → `ApiException`)
+- ✅ 统一错误消息格式
+- ✅ 记录日志
+
+**处理流程**:
+```
+Throwable(各种异常)
+    ↓
+ExceptionHandle.handle()
+    ↓
+ApiException(统一异常类型)
+    ↓
+业务层处理
+```
+
+**使用场景**:
+- **不直接使用**,由 `ApiBaseRemoteDataSource` 和 `ApiResponseParser` 内部调用
+- 业务层需要时可以手动调用
+
+**异常转换规则**:
+- `SocketTimeoutException` → `NetworkException`
+- `UnknownHostException` → `NetworkException`
+- `RetrofitHttpException` → `HttpException`
+- 其他异常 → `UnknownApiException`
+
+---
+
+## 📊 调用关系图
+
+```
+业务层(ViewModel/Repository)
+    ↓
+Repository(如 AuthRepository)
+    ↓
+RemoteDataSource(如 AuthRemoteDataSource)
+    ├── ApiManager.create<AuthApi>()  ← 创建 API 接口
+    └── executeRequestResponse()
+        ├── ApiResponseParser.parseResponse()  ← 解析响应为 ApiResponse
+        ├── GlobalApiOperator.apply()  ← 应用全局处理
+        └── ExceptionHandle.handle()  ← 处理异常
+        
+NetworkManager
+    ├── HeaderInterceptor  ← 添加请求头
+    └── TokenRefreshInterceptor  ← Token 刷新拦截器
+```
+
+---
+
+## 🎯 使用建议
+
+### 业务层使用(推荐)
+
+```kotlin
+// 1. 创建 RemoteDataSource(继承 ApiBaseRemoteDataSource)
+class AuthRemoteDataSource : ApiBaseRemoteDataSource() {
+    private val authApi = ApiManager.create<AuthApi>()
+    
+    suspend fun login(request: LoginRequest): ApiResponse<LoginResponse> {
+        return executeRequestResponse(
+            request = { authApi.login(request) },
+            enableRetry = true,
+            retryTimes = 3
+        ).onSuccess { data ->
+            ILog.d("Auth", "登录成功")
+        }.onError { code, message ->
+            ILog.w("Auth", "登录失败: $code - $message")
+        }.onException { exception ->
+            ILog.e("Auth", "登录异常: ${exception.message}")
+        }
+    }
+}
+
+// 2. 创建 Repository(继承 ApiBaseRepository)
+class AuthRepository(
+    private val remoteDataSource: AuthRemoteDataSource
+) : ApiBaseRepository() {
+    suspend fun login(request: LoginRequest): ApiResponse<LoginResponse> {
+        return remoteDataSource.login(request)
+    }
+    
+    // 如果需要 Result<T>,使用 toResult() 转换
+    suspend fun loginAsResult(request: LoginRequest): Result<LoginResponse> {
+        return remoteDataSource.login(request).toResult()
+    }
+}
+```
+
+### 全局处理配置(可选)
+
+```kotlin
+// 在 AppInitializer 中配置
+GlobalApiOperator.globalErrorHandler = { error ->
+    // 全局错误处理(例如 Toast 提示)
+    Toast.makeText(context, error.message, Toast.LENGTH_SHORT).show()
+}
+
+GlobalApiOperator.globalExceptionHandler = { exception ->
+    // 全局异常处理(例如日志记录)
+    ILog.e("Global", "API 异常: ${exception.exception.message}")
+}
+```
+
+### 关键点
+
+1. **ApiManager**: 业务层统一入口,最简单
+2. **NetworkManager**: 基础设施,业务层不需要直接使用
+3. **ApiBaseRemoteDataSource**: RemoteDataSource 继承使用,减少重复代码
+4. **ApiResponse**: 统一的响应封装,清晰表达三种状态
+5. **GlobalApiOperator**: 全局处理,减少重复代码
+6. **ExceptionHandle**: 自动使用,统一异常处理
+7. **HeaderInterceptor**: 自动使用,统一请求头
+8. **TokenRefreshInterceptor**: 自动使用,Token 刷新
+
+---
+
+## 🔄 数据流
+
+```
+1. 业务层调用
+   ViewModel.login() 
+   
+2. Repository 处理
+   AuthRepository.login()
+   
+3. RemoteDataSource 执行网络请求
+   AuthRemoteDataSource.login()
+   └── executeRequestResponse()
+       └── ApiManager.create<AuthApi>().login()
+       
+4. NetworkManager 构建请求
+   NetworkManager.getRetrofit()
+   ├── HeaderInterceptor(添加请求头)
+   └── TokenRefreshInterceptor(Token 刷新)
+   
+5. 服务器返回
+   Response<ApiCommonResult<LoginResponse>>
+   
+6. ApiResponseParser 解析
+   ApiResponseParser.parseResponse()
+   └── 检查 HTTP 状态码
+   └── 检查业务状态码
+   └── 提取 data
+   └── 转换为 ApiResponse<T>
+   
+7. 全局处理
+   GlobalApiOperator.apply()
+   └── 应用全局错误处理
+   └── 应用全局异常处理
+   
+8. 异常处理
+   ExceptionHandle.handle()
+   └── 转换为 ApiException
+   
+9. 返回结果
+   ApiResponse<LoginResponse>
+   
+10. 业务层处理
+    response.onSuccess { ... }
+          .onError { code, message -> ... }
+          .onException { exception -> ... }
+```
+
+---
+
+## 📝 总结
+
+| 类名 | 职责 | 使用者 | 使用频率 |
+|------|------|--------|---------|
+| **ApiManager** | 业务层入口,简化使用 | 业务层 | ⭐⭐⭐⭐⭐ |
+| **NetworkManager** | 网络基础设施 | ApiManager、能力层 | ⭐⭐⭐ |
+| **ApiBaseRemoteDataSource** | 封装网络请求逻辑 | RemoteDataSource | ⭐⭐⭐⭐ |
+| **ApiResponseParser** | 解析响应为 ApiResponse | ApiBaseRemoteDataSource | ⭐⭐⭐⭐ |
+| **ApiResponse** | 响应封装(Success/Error/Exception) | 业务层 | ⭐⭐⭐⭐⭐ |
+| **GlobalApiOperator** | 全局响应处理器 | ApiBaseRemoteDataSource | ⭐⭐⭐⭐ |
+| **ExceptionHandle** | 统一异常处理 | 自动使用 | ⭐⭐⭐⭐ |
+| **HeaderInterceptor** | 添加请求头 | NetworkManager | ⭐⭐⭐⭐ |
+| **TokenRefreshInterceptor** | Token 刷新拦截器 | NetworkManager | ⭐⭐⭐⭐ |
+| **ApiException** | 自定义异常 | ExceptionHandle | ⭐⭐⭐⭐ |
+| **ApiBaseRepository** | Repository 基类 | Repository | ⭐⭐⭐ |
+
+**核心原则**: 
+- 业务层只需关心 `ApiManager.create<>()` 和 `executeRequestResponse()`
+- 其他组件自动工作,业务层无需关心细节
+- 响应封装清晰(Success/Error/Exception),代码更简洁
+
+---
+
+## 🆕 ApiResponse 使用指南(参考 Sandwich 设计)
+
+### 9. ApiResponseCallAdapterFactory(Retrofit 适配器)
+
+**文件**: `adapter/ApiResponseCallAdapterFactory.kt`
+
+**作用**: 
+- 让 Retrofit 接口直接返回 `ApiResponse<T>`,无需手动解析
+- 自动将 `Response<ApiCommonResult<T>>` 转换为 `ApiResponse<T>`
+
+**特点**:
+- ✅ 已自动注册到 `NetworkManager`,无需手动配置
+- ✅ 接口定义更简洁,直接返回 `ApiResponse<T>`
+- ✅ 类型更安全,编译时检查
+
+**使用示例**:
+```kotlin
+// 接口定义(推荐方式,直接返回 ApiResponse<T>)
+interface AuthApi {
+    @POST("member/auth/login")
+    suspend fun login(@Body request: LoginRequest): ApiResponse<LoginResponse>
+}
+
+// 使用(无需手动解析)
+val response: ApiResponse<LoginResponse> = authApi.login(request)
+response.onSuccess { data -> /* 处理成功 */ }
+        .onError { code, message -> /* 处理业务错误 */ }
+        .onException { exception -> /* 处理异常 */ }
+```
+
+---
+
+### 10. ApiResponse(响应封装)
+
+**文件**: `response/ApiResponse.kt`
+
+**作用**: 
+- 统一封装三种响应状态:Success(成功)、Error(业务错误)、Exception(网络异常)
+- 参考 Sandwich 设计,提供更清晰的响应处理方式
+
+**三种状态**:
+- `Success<T>`: HTTP 200 且业务状态码 = 0,成功返回数据
+- `Error<T>`: HTTP 200 但业务状态码 != 0,业务层面的错误
+- `Exception<T>`: HTTP 非 200 或网络异常,网络层面的错误
+
+**设计优势**:
+- ✅ **平级结构更直观** - Success/Error/Exception 平级设计,使用时不需要嵌套 `Failure`
+- ✅ **代码更简洁** - `when (response) { is Success -> ... is Error -> ... is Exception -> ... }` 直接匹配
+- ✅ **IDE 友好** - 类型检查和自动补全更清晰
+- ✅ **功能等价** - 与 Sandwich 的嵌套结构(Success/Failure.Error/Failure.Exception)功能完全等价,但使用更简单
+
+**使用示例**:
+```kotlin
+val response: ApiResponse<LoginResponse> = executeRequestResponse(
+    request = { authApi.login(request) },
+    enableRetry = true,
+    retryTimes = 3
+)
+
+response.onSuccess { data ->
+    // 处理成功
+    ILog.d("Auth", "登录成功: ${data.token}")
+}.onError { code, message ->
+    // 处理业务错误
+    ILog.w("Auth", "登录失败: $code - $message")
+}.onException { exception ->
+    // 处理异常
+    when (exception) {
+        is NetworkException -> { /* 网络错误 */ }
+        is BusinessException -> { /* 业务错误 */ }
+        else -> { /* 其他错误 */ }
+    }
+}
+
+// 如果需要 Result<T>,使用 toResult() 转换
+val result: Result<LoginResponse> = response.toResult()
+```
+
+### 11. ApiResponseExtensions(扩展函数)
+
+**文件**: `response/ApiResponseExtensions.kt`
+
+**作用**: 
+- 提供链式调用,简化响应处理
+- 支持数据转换、默认值恢复等功能
+
+**所有扩展函数**(与 Sandwich 完全对齐):
+- **核心处理**: `onSuccess()`, `onError()`, `onException()` - 响应处理
+- **数据转换**: `map()`, `mapSuccess()`, `mapFailure()` - 数据转换
+- **数据验证**: `validate()` - 数据验证(支持 lambda 构建错误消息)
+- **非空检查**: `requireNotNull()` - 非空检查(支持 suspend 版本和 lambda 版本)
+- **失败恢复**: `recover()`, `recoverValue()`, `recoverNull()`, `recoverWith()` - 失败时恢复(`recoverWith()` 支持 suspend 版本)
+- **组合响应**: `zip()`, `zip3()` - 组合多个 ApiResponse(支持 suspend 版本)
+- **Flow 转换**: `toFlow()`, `toFlowResponse()` - 转换为 Flow
+- **suspend 版本**: `suspendOnSuccess()`, `suspendOnError()`, `suspendOnException()`, `suspendRequireNotNull()`, `suspendRecoverWith()`, `suspendZip()`, `suspendZip3()`
+
+**✅ 所有扩展函数都已与 Sandwich 对齐,功能完整!**
+
+**使用示例**:
+```kotlin
+// 链式调用
+apiResponse
+    .onSuccess { data -> println("成功: $data") }
+    .onError { code, message -> println("错误: $code - $message") }
+    .onException { exception -> println("异常: ${exception.message}") }
+
+// 数据转换
+val userResponse: ApiResponse<User> = api.getUser()
+val nameResponse: ApiResponse<String> = userResponse.map { it.name }
+
+// 失败时返回默认值
+val userResponse: ApiResponse<User> = api.getUser()
+val userWithDefault: ApiResponse<User> = userResponse.recover { User.default() }
+
+// 组合多个响应
+val userResponse: ApiResponse<User> = api.getUser()
+val profileResponse: ApiResponse<Profile> = api.getProfile()
+val combined: ApiResponse<UserProfile> = userResponse.zip(profileResponse) { user, profile ->
+    UserProfile(user, profile)
+}
+
+// 非空检查
+val profileImageResponse: ApiResponse<String> = userResponse.requireNotNull(
+    selector = { it.profileImage },
+    errorMessage = "用户头像不能为空"
+)
+
+// 失败时恢复(备用响应)
+val recovered: ApiResponse<User> = userResponse.recoverWith { failure ->
+    when (failure) {
+        is ApiResponse.Error -> getCachedUser()
+        is ApiResponse.Exception -> getFallbackUser()
+    }
+}
+```
+
+### 12. GlobalApiOperator(全局处理器)
+
+**文件**: `operator/GlobalApiOperator.kt`
+
+**作用**: 
+- 统一处理所有 API 响应
+- 支持全局错误处理、异常处理等
+- 支持多个 Operator 链式调用(日志、Toast、埋点等)
+
+**使用方式**:
+
+**推荐方式:使用 Operator 接口(支持多个 Operator 链式调用)**
+```kotlin
+// 在 AppInitializer 中配置(可选)
+// 自定义 Operator
+class LogOperator : ApiResponseOperator<Any> {
+    override suspend fun invoke(response: ApiResponse<Any>): ApiResponse<Any> {
+        when (response) {
+            is ApiResponse.Success -> ILog.d("API", "成功: $response")
+            is ApiResponse.Error -> ILog.w("API", "错误: ${response.code} - ${response.message}")
+            is ApiResponse.Exception -> ILog.e("API", "异常: ${response.exception.message}")
+        }
+        return response
+    }
+}
+
+class ToastOperator : ApiResponseOperator<Any> {
+    override suspend fun invoke(response: ApiResponse<Any>): ApiResponse<Any> {
+        when (response) {
+            is ApiResponse.Error -> Toast.makeText(context, response.message, Toast.LENGTH_SHORT).show()
+            is ApiResponse.Exception -> Toast.makeText(context, "网络错误", Toast.LENGTH_SHORT).show()
+            else -> { /* 不处理 */ }
+        }
+        return response
+    }
+}
+
+// 注册多个 Operator(按注册顺序链式调用)
+GlobalApiOperator.addOperator(LogOperator())
+GlobalApiOperator.addOperator(ToastOperator())
+```
+
+**注意**: 
+- Token 刷新已在 `TokenRefreshInterceptor` 中统一处理,`GlobalApiOperator` 不处理 Token 刷新
+- 多个 Operator 会按注册顺序链式调用
+- 支持 suspend Operator,可以执行异步操作
+
+### 13. RetryOperator(重试机制)
+
+**文件**: `operator/RetryOperator.kt`
+
+**作用**: 
+- 自动重试失败的请求
+- 支持指数退避策略
+- 支持 Long 毫秒和 Duration 两种版本
+
+**使用方式**:
+
+**使用 Duration(推荐,更现代)**
+```kotlin
+import kotlin.time.Duration.Companion.seconds
+
+val response = retryRequest(
+    times = 3,                      // 最多重试 3 次
+    initialDelay = 1.seconds,       // 初始延迟 1 秒
+    maxDelay = 10.seconds,          // 最大延迟 10 秒
+    factor = 2.0,                   // 延迟翻倍(指数退避)
+    shouldRetry = { response ->     // 只有网络异常才重试
+        response is ApiResponse.Exception
+    }
+) {
+    api.getData()
+}
+```
+
+### 14. ApiResponseParser(响应解析器)
+
+**文件**: `ApiResponseParser.kt`
+
+**方法**:
+- `parseResponse()`: 返回 `ApiResponse<T>`(使用 reified 泛型自动推断类型)
+- `parseNullableResponse()`: 返回 `ApiResponse<T?>`(支持可空数据)
+
+**使用示例**:
+```kotlin
+val response: ApiResponse<LoginResponse> = ApiResponseParser.parseResponse(
+    response = retrofitResponse,
+    tag = "Auth"
+)
+
+response.onSuccess { data -> ... }
+        .onError { code, message -> ... }
+        .onException { exception -> ... }
+```
+
+### 15. ApiBaseRemoteDataSource(远程数据源基类)
+
+**文件**: `ApiBaseRemoteDataSource.kt`
+
+**方法**:
+- `executeRequestResponse()`: 返回 `ApiResponse<T>`(支持重试和全局处理)
+- `executeNullableRequestResponse()`: 返回 `ApiResponse<T?>`(支持可空数据)
+
+**使用示例**:
+```kotlin
+class AuthRemoteDataSource : ApiBaseRemoteDataSource() {
+    suspend fun login(request: LoginRequest): ApiResponse<LoginResponse> {
+        return executeRequestResponse(
+            request = { authApi.login(request) },
+            enableRetry = true,      // 启用自动重试
+            retryTimes = 3           // 重试 3 次
+        ).onSuccess { data ->
+            ILog.d("Auth", "登录成功")
+        }.onError { code, message ->
+            ILog.w("Auth", "登录失败: $code - $message")
+        }.onException { exception ->
+            ILog.e("Auth", "登录异常: ${exception.message}")
+        }
+    }
+    
+    // 如果需要 Result<T>,使用 toResult() 转换
+    suspend fun loginAsResult(request: LoginRequest): Result<LoginResponse> {
+        return executeRequestResponse(
+            request = { authApi.login(request) }
+        ).toResult()
+    }
+}
+```
+

+ 111 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/network/adapter/ApiResponseCallAdapterFactory.kt

@@ -0,0 +1,111 @@
+package com.narutohuo.xindazhou.common.network.adapter
+
+import com.narutohuo.xindazhou.common.network.ApiCommonResult
+import com.narutohuo.xindazhou.common.network.ApiResponseParser
+import com.narutohuo.xindazhou.common.network.response.ApiResponse
+import retrofit2.Call
+import retrofit2.CallAdapter
+import retrofit2.Response
+import retrofit2.Retrofit
+import java.lang.reflect.ParameterizedType
+import java.lang.reflect.Type
+
+/**
+ * Retrofit CallAdapter Factory for ApiResponse
+ * 
+ * 自动将 Retrofit 接口的返回类型从 `Response<ApiCommonResult<T>>` 转换为 `ApiResponse<T>`
+ * 
+ * 使用方式:
+ * ```kotlin
+ * // Retrofit 接口定义
+ * interface AuthApi {
+ *     @POST("auth/login")
+ *     suspend fun login(@Body request: LoginRequest): ApiResponse<LoginResponse>
+ * }
+ * 
+ * // Retrofit 配置(在 NetworkManager 中自动配置)
+ * Retrofit.Builder()
+ *     .baseUrl("https://api.example.com")
+ *     .addCallAdapterFactory(ApiResponseCallAdapterFactory.create())
+ *     .addConverterFactory(GsonConverterFactory.create())
+ *     .build()
+ * ```
+ * 
+ * 参考 Sandwich 的 ApiResponseCallAdapterFactory 设计
+ */
+object ApiResponseCallAdapterFactory : CallAdapter.Factory() {
+    
+    /**
+     * 创建 CallAdapter 实例
+     * 
+     * 在 Retrofit 配置时调用:
+     * ```kotlin
+     * Retrofit.Builder()
+     *     .addCallAdapterFactory(ApiResponseCallAdapterFactory.create())
+     *     .build()
+     * ```
+     */
+    fun create(): CallAdapter.Factory {
+        return ApiResponseCallAdapterFactory
+    }
+    
+    override fun get(
+        returnType: Type,
+        annotations: Array<Annotation>,
+        retrofit: Retrofit
+    ): CallAdapter<*, *>? {
+        // 检查返回类型是否是 ApiResponse
+        val rawType = getRawType(returnType)
+        if (rawType != ApiResponse::class.java) {
+            return null
+        }
+        
+        // 检查是否是 ParameterizedType(必须有泛型参数)
+        if (returnType !is ParameterizedType) {
+            throw IllegalStateException("ApiResponse 必须指定泛型参数,例如:ApiResponse<LoginResponse>")
+        }
+        
+        // 提取泛型参数类型(ApiResponse<T> 中的 T)
+        val responseType = getParameterUpperBound(0, returnType)
+        
+        return ApiResponseCallAdapter(responseType)
+    }
+    
+    /**
+     * ApiResponse CallAdapter 实现
+     * 
+     * 将 Retrofit 的 Call<Response<ApiCommonResult<T>>> 适配为 suspend 函数返回 ApiResponse<T>
+     * 
+     * 注意:对于 suspend 函数,Retrofit 会自动处理 Call 的执行,我们只需要在这里进行类型转换
+     */
+    private class ApiResponseCallAdapter(
+        private val responseType: Type
+    ) : CallAdapter<Response<ApiCommonResult<*>>, ApiResponse<*>> {
+        
+        override fun responseType(): Type {
+            // 创建 ParameterizedType: Response<ApiCommonResult<responseType>>
+            return object : ParameterizedType {
+                override fun getRawType(): Type = Response::class.java
+                override fun getOwnerType(): Type? = null
+                override fun getActualTypeArguments(): Array<Type> = arrayOf(
+                    object : ParameterizedType {
+                        override fun getRawType(): Type = ApiCommonResult::class.java
+                        override fun getOwnerType(): Type? = null
+                        override fun getActualTypeArguments(): Array<Type> = arrayOf(responseType)
+                    }
+                )
+            }
+        }
+        
+        override fun adapt(call: Call<Response<ApiCommonResult<*>>>): ApiResponse<*> {
+            // 执行网络请求(对于 suspend 函数,Retrofit 会自动在协程中执行)
+            val response = call.execute()
+            
+            // 使用 ApiResponseParser 解析 Response<ApiCommonResult<*>> 为 ApiResponse<*>
+            // 这里需要将 ApiResponse<*> 转换为正确的类型,但由于类型擦除,我们无法在运行时确定
+            // 所以使用 @Suppress("UNCHECKED_CAST")
+            @Suppress("UNCHECKED_CAST")
+            return ApiResponseParser.parseResponse(response as Response<ApiCommonResult<Any>>, "ApiResponseCallAdapter") as ApiResponse<*>
+        }
+    }
+}

+ 135 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/network/exception/ApiException.kt

@@ -0,0 +1,135 @@
+package com.narutohuo.xindazhou.common.network.exception
+
+/**
+ * API 异常基类
+ * 
+ * 所有网络相关异常的统一基类
+ * 用于区分业务异常和其他异常
+ */
+sealed class ApiException(
+    message: String,
+    cause: Throwable? = null
+) : Exception(message, cause) {
+    
+    /**
+     * 获取用户友好的错误消息
+     */
+    abstract fun getUserMessage(): String
+}
+
+/**
+ * 网络异常(网络连接问题)
+ * 
+ * 例如:无网络、超时、DNS 解析失败等
+ */
+class NetworkException(
+    message: String = "网络连接失败,请检查网络设置",
+    cause: Throwable? = null
+) : ApiException(message, cause) {
+    
+    override fun getUserMessage(): String = message ?: "网络连接失败,请检查网络设置"
+}
+
+/**
+ * HTTP 异常(HTTP 状态码错误)
+ * 
+ * 例如:404、500、502 等 HTTP 错误状态码
+ */
+class HttpException(
+    val code: Int,
+    message: String? = null,
+    cause: Throwable? = null
+) : ApiException(
+    message ?: "HTTP 请求失败 (状态码: $code)",
+    cause
+) {
+    
+    override fun getUserMessage(): String {
+        return when (code) {
+            400 -> "请求参数错误"
+            401 -> "未授权,请重新登录"
+            403 -> "无权限访问"
+            404 -> "请求的资源不存在"
+            500 -> "服务器内部错误"
+            502 -> "网关错误"
+            503 -> "服务不可用"
+            else -> message ?: "请求失败(错误码: $code)"
+        }
+    }
+}
+
+/**
+ * 业务异常(业务状态码错误)
+ * 
+ * 例如:登录失败、参数校验失败等业务层面的错误
+ */
+class BusinessException(
+    val code: Int,
+    message: String? = null,
+    cause: Throwable? = null
+) : ApiException(
+    message ?: "操作失败(错误码: $code)",
+    cause
+) {
+    
+    companion object {
+        // 常见的业务错误码(可根据实际后端定义调整)
+        const val CODE_TOKEN_EXPIRED = 401
+        const val CODE_TOKEN_INVALID = 402
+        const val CODE_PERMISSION_DENIED = 403
+        const val CODE_PARAM_ERROR = 400
+    }
+    
+    // 保存原始消息(用于 getUserMessage)
+    private val userMessage: String? = message
+    
+    override fun getUserMessage(): String {
+        // 如果后端返回了友好的错误消息,优先使用
+        if (!userMessage.isNullOrBlank()) {
+            return userMessage
+        }
+        
+        // 否则根据错误码返回默认消息
+        return when (code) {
+            CODE_TOKEN_EXPIRED -> "登录已过期,请重新登录"
+            CODE_TOKEN_INVALID -> "登录凭证无效,请重新登录"
+            CODE_PERMISSION_DENIED -> "无权限执行此操作"
+            CODE_PARAM_ERROR -> "请求参数错误"
+            else -> "操作失败(错误码: $code)"
+        }
+    }
+    
+    /**
+     * 判断是否是 Token 相关错误
+     */
+    fun isTokenError(): Boolean {
+        return code == CODE_TOKEN_EXPIRED || code == CODE_TOKEN_INVALID
+    }
+}
+
+/**
+ * 数据解析异常
+ * 
+ * 例如:JSON 解析失败、数据类型转换错误等
+ */
+class ParseException(
+    message: String = "数据解析失败",
+    cause: Throwable? = null
+) : ApiException(message, cause) {
+    
+    override fun getUserMessage(): String = "服务器返回数据格式错误,请稍后重试"
+}
+
+/**
+ * 未知异常(兜底异常)
+ * 
+ * 用于包装无法明确分类的异常
+ */
+class UnknownApiException(
+    message: String,
+    cause: Throwable? = null
+) : ApiException(message, cause) {
+    
+    override fun getUserMessage(): String = message ?: "操作失败"
+}
+

+ 131 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/network/exception/ExceptionHandle.kt

@@ -0,0 +1,131 @@
+package com.narutohuo.xindazhou.common.network.exception
+
+import com.narutohuo.xindazhou.core.log.ILog
+import retrofit2.HttpException as RetrofitHttpException
+import java.net.SocketTimeoutException
+import java.net.UnknownHostException
+import java.net.ConnectException
+import javax.net.ssl.SSLException
+
+/**
+ * 统一异常处理类
+ * 
+ * 负责将各种异常转换为统一的 ApiException
+ * 提供统一的错误处理逻辑,业务层只需要处理 ApiException
+ * 
+ * 使用方式:
+ * ```kotlin
+ * try {
+ *     val result = api.call()
+ * } catch (e: Throwable) {
+ *     val apiException = ExceptionHandle.handle(e)
+ *     // 统一处理 ApiException
+ *     when (apiException) {
+ *         is NetworkException -> { ... }
+ *         is BusinessException -> { ... }
+ *         is HttpException -> { ... }
+ *     }
+ * }
+ * ```
+ */
+object ExceptionHandle {
+    
+    private const val TAG = "ExceptionHandle"
+    
+    /**
+     * 处理异常,转换为统一的 ApiException
+     * 
+     * @param throwable 原始异常
+     * @param defaultMessage 默认错误消息(可选)
+     * @return ApiException 统一异常对象
+     */
+    fun handle(throwable: Throwable, defaultMessage: String = "操作失败"): ApiException {
+        return when (throwable) {
+            // 已经是 ApiException,直接返回
+            is ApiException -> {
+                ILog.d(TAG, "已经是 ApiException: ${throwable::class.simpleName}, message: ${throwable.message}")
+                throwable
+            }
+            
+            // Retrofit 的 HttpException(HTTP 状态码错误)
+            is RetrofitHttpException -> {
+                val code = throwable.code()
+                val message = throwable.message()
+                ILog.w(TAG, "HTTP 异常: code=$code, message=$message")
+                HttpException(code, message, throwable)
+            }
+            
+            // 网络超时
+            is SocketTimeoutException -> {
+                val message = "请求超时,请检查网络连接"
+                ILog.e(TAG, message, throwable)
+                NetworkException(message, throwable)
+            }
+            
+            // 无法连接到服务器(DNS 解析失败、无网络等)
+            is UnknownHostException -> {
+                val message = "网络连接失败,请检查网络设置"
+                ILog.e(TAG, message, throwable)
+                NetworkException(message, throwable)
+            }
+            
+            // 连接异常
+            is ConnectException -> {
+                val message = "无法连接到服务器,请检查网络"
+                ILog.e(TAG, message, throwable)
+                NetworkException(message, throwable)
+            }
+            
+            // SSL 异常
+            is SSLException -> {
+                val message = "安全连接失败,请检查网络设置"
+                ILog.e(TAG, message, throwable)
+                NetworkException(message, throwable)
+            }
+            
+            // 其他异常(兜底处理)
+            else -> {
+                val message = throwable.message ?: defaultMessage
+                ILog.e(TAG, "未知异常: $message", throwable)
+                UnknownApiException(message, throwable)
+            }
+        }
+    }
+    
+    /**
+     * 处理 HTTP 响应错误(非 2xx 状态码)
+     * 
+     * @param code HTTP 状态码
+     * @param message 错误消息
+     * @return HttpException
+     */
+    fun handleHttpError(code: Int, message: String? = null): HttpException {
+        ILog.w(TAG, "HTTP 错误: code=$code, message=$message")
+        return HttpException(code, message)
+    }
+    
+    /**
+     * 处理业务错误(业务状态码错误)
+     * 
+     * @param code 业务状态码
+     * @param message 错误消息
+     * @return BusinessException
+     */
+    fun handleBusinessError(code: Int, message: String? = null): BusinessException {
+        ILog.w(TAG, "业务错误: code=$code, message=$message")
+        return BusinessException(code, message)
+    }
+    
+    /**
+     * 处理数据解析错误
+     * 
+     * @param message 错误消息
+     * @param cause 原始异常
+     * @return ParseException
+     */
+    fun handleParseError(message: String = "数据解析失败", cause: Throwable? = null): ParseException {
+        ILog.e(TAG, message, cause)
+        return ParseException(message, cause)
+    }
+}
+

+ 53 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/network/interceptor/HeaderInterceptor.kt

@@ -0,0 +1,53 @@
+package com.narutohuo.xindazhou.common.network.interceptor
+
+import okhttp3.Interceptor
+import okhttp3.Response
+
+/**
+ * 请求头拦截器
+ * 
+ * 统一为所有请求添加公共请求头,包括:
+ * - Content-Type: application/json
+ * - Accept: application/json
+ * - Authorization: Bearer {token}(如果提供了 Token)
+ * 
+ * **架构说明**:
+ * 按照架构设计文档,网络服务统一放在 `base/common/network/` 模块中。
+ * 此拦截器与 NetworkManager、ApiManager、ApiResponseParser 等统一管理。
+ * 
+ * 使用方式:
+ * ```kotlin
+ * // 方式1:直接使用
+ * val interceptor = HeaderInterceptor { 
+ *     TokenStore.getAccessToken() 
+ * }
+ * builder.addInterceptor(interceptor)
+ * 
+ * // 方式2:通过 NetworkManager 使用(推荐)
+ * NetworkManager.tokenProvider = { TokenStore.getAccessToken() }
+ * // NetworkManager 会自动创建并使用 HeaderInterceptor
+ * ```
+ */
+class HeaderInterceptor(
+    private val tokenProvider: (() -> String?)? = null
+) : Interceptor {
+    
+    override fun intercept(chain: Interceptor.Chain): Response {
+        val originalRequest = chain.request()
+        
+        // 构建新的请求,添加公共请求头
+        val requestBuilder = originalRequest.newBuilder()
+            .header("Content-Type", "application/json")
+            .header("Accept", "application/json")
+        
+        // 如果提供了 Token Provider,则添加 Authorization 头
+        tokenProvider?.invoke()?.let { token ->
+            if (token.isNotBlank()) {
+                requestBuilder.header("Authorization", "Bearer $token")
+            }
+        }
+        
+        return chain.proceed(requestBuilder.build())
+    }
+}
+

+ 120 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/network/interceptor/TokenRefreshInterceptor.kt

@@ -0,0 +1,120 @@
+package com.narutohuo.xindazhou.common.network.interceptor
+
+import com.narutohuo.xindazhou.core.log.ILog
+import kotlinx.coroutines.runBlocking
+import okhttp3.Interceptor
+import okhttp3.Response
+
+/**
+ * Token 刷新拦截器
+ * 
+ * 拦截 401/402 响应,自动刷新 Token 并重试请求
+ * 
+ * **功能**:
+ * - 拦截 401/402 响应(Token 过期/无效)
+ * - 自动调用 refreshTokenProvider 刷新 Token
+ * - 使用新 Token 重试原请求
+ * - 并发控制:多个请求同时 401 时,只刷新一次,其他请求等待
+ * 
+ * **架构说明**:
+ * 按照架构设计文档,网络服务统一放在 `base/common/network/` 模块中。
+ * 此拦截器与 HeaderInterceptor、NetworkManager、ApiManager 等统一管理。
+ * 
+ * 使用方式:
+ * ```kotlin
+ * // 通过 NetworkManager 使用(推荐)
+ * NetworkManager.tokenProvider = { TokenStore.getAccessToken() }
+ * NetworkManager.refreshTokenProvider = { AuthManager.refreshTokenIfNeeded() }
+ * // NetworkManager 会自动创建并使用 TokenRefreshInterceptor
+ * ```
+ */
+class TokenRefreshInterceptor(
+    private val tokenProvider: () -> String?,
+    private val refreshTokenProvider: (suspend () -> String?)?
+) : Interceptor {
+    
+    private val TAG = "TokenRefreshInterceptor"
+    
+    @Volatile
+    private var isRefreshing = false
+    
+    private val refreshLock = Any()
+    
+    override fun intercept(chain: Interceptor.Chain): Response {
+        val request = chain.request()
+        var response = chain.proceed(request)
+        
+        // 拦截 401/402 响应(Token 过期/无效)
+        if (response.code == 401 || response.code == 402) {
+            // 如果没有提供刷新回调,直接返回 401 响应
+            if (refreshTokenProvider == null) {
+                ILog.w(TAG, "收到 401/402 响应,但未设置 refreshTokenProvider,无法自动刷新")
+                return response
+            }
+            
+            synchronized(refreshLock) {
+                // 并发控制:如果正在刷新,等待刷新完成
+                if (isRefreshing) {
+                    ILog.d(TAG, "Token 正在刷新中,等待刷新完成...")
+                    // 等待刷新完成,最多等待 3 秒
+                    var waitCount = 0
+                    while (isRefreshing && waitCount < 30) {
+                        Thread.sleep(100)
+                        waitCount++
+                    }
+                    // 刷新完成后,使用新 Token 重试
+                    val newToken = tokenProvider()
+                    if (!newToken.isNullOrEmpty()) {
+                        ILog.d(TAG, "使用刷新后的新 Token 重试请求")
+                        val newRequest = request.newBuilder()
+                            .header("Authorization", "Bearer $newToken")
+                            .removeHeader("Authorization") // 先移除,避免重复
+                            .header("Authorization", "Bearer $newToken")
+                            .build()
+                        // 关闭原响应
+                        response.close()
+                        response = chain.proceed(newRequest)
+                    } else {
+                        ILog.w(TAG, "等待刷新完成后,Token 仍为空,返回 401 响应")
+                    }
+                } else {
+                    // 开始刷新
+                    isRefreshing = true
+                    try {
+                        ILog.d(TAG, "检测到 401/402 响应,开始刷新 Token...")
+                        val newToken = runBlocking { 
+                            refreshTokenProvider.invoke() 
+                        }
+                        
+                        if (!newToken.isNullOrEmpty()) {
+                            ILog.d(TAG, "Token 刷新成功,使用新 Token 重试请求")
+                            // 重试原请求(使用新 Token)
+                            val newRequest = request.newBuilder()
+                                .header("Authorization", "Bearer $newToken")
+                                .removeHeader("Authorization") // 先移除,避免重复
+                                .header("Authorization", "Bearer $newToken")
+                                .build()
+                            // 关闭原响应
+                            response.close()
+                            response = chain.proceed(newRequest)
+                            
+                            // 如果重试后仍然是 401,说明刷新失败或 Token 无效
+                            if (response.code == 401 || response.code == 402) {
+                                ILog.w(TAG, "使用新 Token 重试后仍返回 401/402,Token 刷新可能失败")
+                            }
+                        } else {
+                            ILog.w(TAG, "Token 刷新失败(返回 null),返回 401 响应")
+                        }
+                    } catch (e: Exception) {
+                        ILog.e(TAG, "Token 刷新异常", e)
+                    } finally {
+                        isRefreshing = false
+                    }
+                }
+            }
+        }
+        
+        return response
+    }
+}
+

+ 35 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/network/operator/ApiResponseOperator.kt

@@ -0,0 +1,35 @@
+package com.narutohuo.xindazhou.common.network.operator
+
+import com.narutohuo.xindazhou.common.network.response.ApiResponse
+
+/**
+ * ApiResponse Operator 接口
+ * 
+ * 用于定义全局响应处理器,支持多个 Operator 链式调用
+ * 
+ * 使用方式:
+ * ```kotlin
+ * class LogOperator : ApiResponseOperator<Any> {
+ *     override suspend fun invoke(response: ApiResponse<Any>): ApiResponse<Any> {
+ *         ILog.d("LogOperator", "响应: $response")
+ *         return response
+ *     }
+ * }
+ * 
+ * // 注册
+ * GlobalApiOperator.addOperator(LogOperator())
+ * ```
+ * 
+ * 参考 Sandwich 的 ApiResponseOperator 设计
+ */
+interface ApiResponseOperator<T> {
+    
+    /**
+     * 处理响应
+     * 
+     * @param response API 响应
+     * @return 处理后的响应(可以修改或返回原响应)
+     */
+    suspend fun invoke(response: ApiResponse<T>): ApiResponse<T>
+}
+

+ 209 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/network/operator/GlobalApiOperator.kt

@@ -0,0 +1,209 @@
+package com.narutohuo.xindazhou.common.network.operator
+
+import com.narutohuo.xindazhou.common.network.response.ApiResponse
+import com.narutohuo.xindazhou.core.log.ILog
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
+
+/**
+ * 全局 API 响应处理器(参考 Sandwich Operator)
+ * 
+ * 统一处理所有 API 响应,例如:
+ * - 全局错误处理(Toast 提示)
+ * - 全局异常处理(日志记录)
+ * - 统一日志记录
+ * - 支持多个 Operator 链式调用
+ * 
+ * 注意:
+ * - Token 刷新已在 TokenRefreshInterceptor 中统一处理,这里不再处理
+ * - 这里主要用于 UI 提示、日志记录等全局统一的处理逻辑
+ * 
+ * 使用方式:
+ * ```kotlin
+ * // 方式1:使用简单的全局处理器(向后兼容)
+ * GlobalApiOperator.globalErrorHandler = { error ->
+ *     Toast.makeText(context, error.message, Toast.LENGTH_SHORT).show()
+ * }
+ * 
+ * // 方式2:使用 Operator 接口(推荐,支持多个 Operator 链式调用)
+ * GlobalApiOperator.addOperator(LogOperator())
+ * GlobalApiOperator.addOperator(ToastOperator())
+ * ```
+ */
+object GlobalApiOperator {
+    
+    private const val TAG = "GlobalApiOperator"
+    
+    // 多个 Operator 列表(支持链式调用)
+    private val operators = mutableListOf<ApiResponseOperator<*>>()
+    private val operatorsMutex = Mutex()
+    
+    /**
+     * 全局错误处理器(向后兼容,保留)
+     * 
+     * 当响应为 Error(业务错误)时自动调用
+     * 可以用于全局 Toast 提示、日志记录等
+     * 
+     * 示例:
+     * ```kotlin
+     * GlobalApiOperator.globalErrorHandler = { error ->
+     *     Toast.makeText(context, error.message, Toast.LENGTH_SHORT).show()
+     * }
+     * ```
+     */
+    @Deprecated("建议使用 addOperator() 代替,支持多个 Operator 链式调用", ReplaceWith("addOperator(operator)"))
+    var globalErrorHandler: ((ApiResponse.Error<*>) -> Unit)? = null
+    
+    /**
+     * 全局异常处理器(向后兼容,保留)
+     * 
+     * 当响应为 Exception(网络异常)时自动调用
+     * 可以用于全局错误提示、日志记录等
+     * 
+     * 示例:
+     * ```kotlin
+     * GlobalApiOperator.globalExceptionHandler = { exception ->
+     *     ILog.e("Global", "API 异常: ${exception.exception.message}")
+     * }
+     * ```
+     */
+    @Deprecated("建议使用 addOperator() 代替,支持多个 Operator 链式调用", ReplaceWith("addOperator(operator)"))
+    var globalExceptionHandler: ((ApiResponse.Exception<*>) -> Unit)? = null
+    
+    /**
+     * 添加 Operator(支持多个 Operator 链式调用)
+     * 
+     * @param operator Operator 实例
+     * 
+     * 示例:
+     * ```kotlin
+     * GlobalApiOperator.addOperator(LogOperator())
+     * GlobalApiOperator.addOperator(ToastOperator())
+     * ```
+     */
+    suspend fun <T> addOperator(operator: ApiResponseOperator<T>) {
+        operatorsMutex.withLock {
+            @Suppress("UNCHECKED_CAST")
+            operators.add(operator as ApiResponseOperator<*>)
+        }
+    }
+    
+    /**
+     * 添加 Operator(同步版本)
+     * 
+     * @param operator Operator 实例
+     */
+    fun <T> addOperatorSync(operator: ApiResponseOperator<T>) {
+        @Suppress("UNCHECKED_CAST")
+        operators.add(operator as ApiResponseOperator<*>)
+    }
+    
+    /**
+     * 移除 Operator
+     * 
+     * @param operator Operator 实例
+     */
+    suspend fun <T> removeOperator(operator: ApiResponseOperator<T>) {
+        operatorsMutex.withLock {
+            operators.remove(operator)
+        }
+    }
+    
+    /**
+     * 移除所有 Operator
+     */
+    suspend fun clearOperators() {
+        operatorsMutex.withLock {
+            operators.clear()
+        }
+    }
+    
+    /**
+     * 应用全局处理器
+     * 
+     * 对响应应用全局处理逻辑(按顺序执行所有 Operator,然后执行向后兼容的处理器)
+     * 
+     * @param response API 响应
+     * @return 处理后的响应(Operator 可以修改响应)
+     * 
+     * 注意:
+     * - 先执行所有 Operator(支持链式调用)
+     * - 然后执行向后兼容的全局处理器(只执行副作用)
+     */
+    suspend fun <T> apply(response: ApiResponse<T>): ApiResponse<T> {
+        var result: ApiResponse<T> = response
+        
+        // 1. 按顺序执行所有 Operator(支持链式调用)
+        val currentOperators = operatorsMutex.withLock {
+            operators.toList() // 复制列表,避免在迭代时修改
+        }
+        
+        for (operator in currentOperators) {
+            try {
+                @Suppress("UNCHECKED_CAST")
+                val typedOperator = operator as? ApiResponseOperator<T>
+                if (typedOperator != null) {
+                    result = typedOperator.invoke(result)
+                }
+            } catch (e: Exception) {
+                ILog.e(TAG, "Operator 执行失败: ${operator.javaClass.simpleName}, ${e.message}", e)
+            }
+        }
+        
+        // 2. 执行向后兼容的全局处理器(只执行副作用,不修改响应)
+        when (result) {
+            is ApiResponse.Success -> {
+                // 成功响应,不处理
+            }
+            
+            is ApiResponse.Error -> {
+                try {
+                    globalErrorHandler?.invoke(result)
+                } catch (e: Exception) {
+                    ILog.e(TAG, "全局错误处理器执行失败: ${e.message}", e)
+                }
+            }
+            
+            is ApiResponse.Exception -> {
+                try {
+                    globalExceptionHandler?.invoke(result)
+                } catch (e: Exception) {
+                    ILog.e(TAG, "全局异常处理器执行失败: ${e.message}", e)
+                }
+            }
+        }
+        
+        return result
+    }
+    
+    /**
+     * 同步应用全局处理器(非 suspend 版本)
+     * 
+     * 适用于不需要 suspend 的场景
+     * 
+     * 注意:只执行向后兼容的全局处理器,不执行 Operator(因为 Operator 可能是 suspend 的)
+     */
+    fun <T> applySync(response: ApiResponse<T>): ApiResponse<T> {
+        return when (response) {
+            is ApiResponse.Success -> response
+            
+            is ApiResponse.Error -> {
+                try {
+                    globalErrorHandler?.invoke(response)
+                } catch (e: Exception) {
+                    ILog.e(TAG, "全局错误处理器执行失败: ${e.message}", e)
+                }
+                response
+            }
+            
+            is ApiResponse.Exception -> {
+                try {
+                    globalExceptionHandler?.invoke(response)
+                } catch (e: Exception) {
+                    ILog.e(TAG, "全局异常处理器执行失败: ${e.message}", e)
+                }
+                response
+            }
+        }
+    }
+}

+ 204 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/network/operator/RetryOperator.kt

@@ -0,0 +1,204 @@
+package com.narutohuo.xindazhou.common.network.operator
+
+import com.narutohuo.xindazhou.common.network.response.ApiResponse
+import kotlinx.coroutines.delay
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.milliseconds
+import kotlin.time.Duration.Companion.seconds
+
+/**
+ * 重试机制(参考 Sandwich Retry)
+ * 
+ * 支持自动重试失败的请求,使用指数退避策略
+ * 
+ * 使用方式:
+ * ```kotlin
+ * val response = retryRequest(
+ *     times = 3,
+ *     initialDelayMillis = 1000,
+ *     shouldRetry = { it is ApiResponse.Exception }
+ * ) {
+ *     api.getData()
+ * }
+ * ```
+ */
+
+/**
+ * 重试请求
+ * 
+ * 如果请求失败且满足重试条件,会自动重试指定次数
+ * 
+ * @param times 最大重试次数(包含首次请求)
+ * @param initialDelayMillis 初始延迟时间(毫秒)
+ * @param maxDelayMillis 最大延迟时间(毫秒),防止延迟过长
+ * @param factor 延迟因子,每次重试延迟时间 = 上次延迟 * factor
+ * @param shouldRetry 判断是否应该重试的函数,返回 true 则重试
+ * @param block 需要重试的请求函数
+ * @return 最后一次请求的结果
+ * 
+ * 示例:
+ * ```kotlin
+ * val response = retryRequest(
+ *     times = 3,                      // 最多重试 3 次(包含首次)
+ *     initialDelayMillis = 1000,      // 初始延迟 1 秒
+ *     maxDelayMillis = 10000,         // 最大延迟 10 秒
+ *     factor = 2.0,                   // 延迟翻倍(指数退避)
+ *     shouldRetry = { response ->     // 只有网络异常才重试
+ *         response is ApiResponse.Exception
+ *     }
+ * ) {
+ *     api.login(request)
+ * }
+ * ```
+ */
+suspend inline fun <T> retryRequest(
+    times: Int = 3,
+    initialDelayMillis: Long = 1000,
+    maxDelayMillis: Long = 10000,
+    factor: Double = 2.0,
+    crossinline shouldRetry: (ApiResponse<T>) -> Boolean = { it is ApiResponse.Exception },
+    crossinline block: suspend () -> ApiResponse<T>
+): ApiResponse<T> {
+    require(times > 0) { "重试次数必须大于 0" }
+    require(initialDelayMillis > 0) { "初始延迟必须大于 0" }
+    require(factor > 0) { "延迟因子必须大于 0" }
+    
+    var currentDelay = initialDelayMillis
+    var lastResponse: ApiResponse<T>? = null
+    
+    // 执行首次请求
+    lastResponse = block()
+    
+    // 如果首次成功或不需要重试,直接返回
+    if (!shouldRetry(lastResponse)) {
+        return lastResponse
+    }
+    
+    // 重试剩余次数(times - 1 次,因为已经执行了首次)
+    repeat(times - 1) {
+        // 等待延迟时间(指数退避)
+        delay(currentDelay)
+        
+        // 执行重试请求
+        lastResponse = block()
+        
+        // 如果成功或不需要重试,提前返回
+        if (!shouldRetry(lastResponse)) {
+            return lastResponse
+        }
+        
+        // 计算下次延迟时间(指数退避)
+        currentDelay = (currentDelay * factor).toLong().coerceAtMost(maxDelayMillis)
+    }
+    
+    // 返回最后一次请求的结果
+    return lastResponse ?: block()
+}
+
+/**
+ * 重试请求(Duration 版本,更现代)
+ * 
+ * 如果请求失败且满足重试条件,会自动重试指定次数
+ * 
+ * @param times 最大重试次数(包含首次请求)
+ * @param initialDelay 初始延迟时间
+ * @param maxDelay 最大延迟时间,防止延迟过长
+ * @param factor 延迟因子,每次重试延迟时间 = 上次延迟 * factor
+ * @param shouldRetry 判断是否应该重试的函数,返回 true 则重试
+ * @param block 需要重试的请求函数
+ * @return 最后一次请求的结果
+ * 
+ * 示例:
+ * ```kotlin
+ * val response = retryRequest(
+ *     times = 3,                      // 最多重试 3 次(包含首次)
+ *     initialDelay = 1.seconds,      // 初始延迟 1 秒
+ *     maxDelay = 10.seconds,         // 最大延迟 10 秒
+ *     factor = 2.0,                   // 延迟翻倍(指数退避)
+ *     shouldRetry = { response ->     // 只有网络异常才重试
+ *         response is ApiResponse.Exception
+ *     }
+ * ) {
+ *     api.login(request)
+ * }
+ * ```
+ */
+suspend inline fun <T> retryRequest(
+    times: Int = 3,
+    initialDelay: Duration = 1.seconds,
+    maxDelay: Duration = 10.seconds,
+    factor: Double = 2.0,
+    crossinline shouldRetry: (ApiResponse<T>) -> Boolean = { it is ApiResponse.Exception },
+    crossinline block: suspend () -> ApiResponse<T>
+): ApiResponse<T> {
+    return retryRequest(
+        times = times,
+        initialDelayMillis = initialDelay.inWholeMilliseconds,
+        maxDelayMillis = maxDelay.inWholeMilliseconds,
+        factor = factor,
+        shouldRetry = shouldRetry,
+        block = block
+    )
+}
+
+/**
+ * 重试请求(简化版本)
+ * 
+ * 使用默认配置重试请求
+ * 
+ * @param times 最大重试次数(默认 3 次)
+ * @param shouldRetry 判断是否应该重试的函数(默认:只有网络异常才重试)
+ * @param block 需要重试的请求函数
+ * @return 最后一次请求的结果
+ * 
+ * 示例:
+ * ```kotlin
+ * val response = retryRequest(
+ *     times = 3,
+ *     shouldRetry = { it is ApiResponse.Exception }
+ * ) {
+ *     api.getData()
+ * }
+ * ```
+ */
+suspend inline fun <T> retryRequest(
+    times: Int = 3,
+    crossinline shouldRetry: (ApiResponse<T>) -> Boolean = { it is ApiResponse.Exception },
+    crossinline block: suspend () -> ApiResponse<T>
+): ApiResponse<T> {
+    return retryRequest(
+        times = times,
+        initialDelayMillis = 1000,
+        maxDelayMillis = 10000,
+        factor = 2.0,
+        shouldRetry = shouldRetry,
+        block = block
+    )
+}
+
+/**
+ * 重试请求(最简版本)
+ * 
+ * 只指定重试次数,使用默认的指数退避策略和重试条件
+ * 
+ * @param times 最大重试次数(默认 3 次)
+ * @param block 需要重试的请求函数
+ * @return 最后一次请求的结果
+ * 
+ * 示例:
+ * ```kotlin
+ * val response = retryRequest(times = 3) {
+ *     api.getData()
+ * }
+ * ```
+ */
+suspend inline fun <T> retryRequest(
+    times: Int = 3,
+    crossinline block: suspend () -> ApiResponse<T>
+): ApiResponse<T> {
+    return retryRequest(
+        times = times,
+        shouldRetry = { it is ApiResponse.Exception },
+        block = block
+    )
+}

+ 119 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/network/response/ApiResponse.kt

@@ -0,0 +1,119 @@
+package com.narutohuo.xindazhou.common.network.response
+
+import com.narutohuo.xindazhou.common.network.ApiCommonResult
+import com.narutohuo.xindazhou.common.network.exception.ApiException
+import com.narutohuo.xindazhou.common.network.exception.BusinessException
+
+/**
+ * API 响应封装(参考 Sandwich 设计)
+ * 
+ * 统一封装三种状态:Success(成功)、Error(业务错误)、Exception(网络异常)
+ * 
+ * 设计理念:
+ * - Success: HTTP 200 且业务状态码 = 0,成功返回数据
+ * - Error: HTTP 200 但业务状态码 != 0,业务层面的错误(如登录失败、参数错误等)
+ * - Exception: HTTP 非 200 或网络异常,网络层面的错误(如超时、连接失败等)
+ * 
+ * 使用方式:
+ * ```kotlin
+ * val response: ApiResponse<LoginResponse> = api.login(request)
+ * 
+ * response.onSuccess { data ->
+ *     // 处理成功
+ * }.onError { code, message ->
+ *     // 处理业务错误
+ * }.onException { exception ->
+ *     // 处理异常
+ * }
+ * 
+ * // 或转换为 Result<T>(向后兼容)
+ * val result: Result<LoginResponse> = response.toResult()
+ * ```
+ */
+sealed class ApiResponse<out T> {
+    
+    /**
+     * 成功响应
+     * 
+     * HTTP 200 且业务状态码 = 0,成功返回数据
+     */
+    data class Success<T>(val data: T) : ApiResponse<T>()
+    
+    /**
+     * 业务错误响应
+     * 
+     * HTTP 200 但业务状态码 != 0,业务层面的错误
+     * 
+     * @param code 业务错误码
+     * @param message 错误消息
+     * @param body 原始响应体(可选,用于调试)
+     */
+    data class Error<T>(
+        val code: Int,
+        val message: String,
+        val body: ApiCommonResult<*>? = null
+    ) : ApiResponse<T>()
+    
+    /**
+     * 异常响应
+     * 
+     * HTTP 非 200 或网络异常,网络层面的错误
+     * 
+     * @param exception API 异常对象
+     */
+    data class Exception<T>(val exception: ApiException) : ApiResponse<T>()
+    
+    /**
+     * 转换为 Result<T>(向后兼容)
+     * 
+     * 将 ApiResponse 转换为标准的 Result 类型,方便与现有代码集成
+     * 
+     * @return Result<T> 成功返回 Success,失败返回 Failure
+     */
+    fun toResult(): Result<T> = when (this) {
+        is Success -> Result.success(data)
+        is Error -> Result.failure(BusinessException(code, message))
+        is Exception -> Result.failure(exception)
+    }
+    
+    /**
+     * 判断是否为成功状态
+     */
+    val isSuccess: Boolean
+        get() = this is Success
+    
+    /**
+     * 判断是否为业务错误状态
+     */
+    val isError: Boolean
+        get() = this is Error
+    
+    /**
+     * 判断是否为异常状态
+     */
+    val isException: Boolean
+        get() = this is Exception
+    
+    /**
+     * 获取数据(如果成功)
+     * 
+     * @return 成功时返回数据,否则返回 null
+     */
+    fun getDataOrNull(): T? = when (this) {
+        is Success -> data
+        else -> null
+    }
+    
+    /**
+     * 获取数据(如果成功)或抛出异常
+     * 
+     * @return 成功时返回数据
+     * @throws BusinessException 如果是业务错误
+     * @throws ApiException 如果是异常
+     */
+    fun getDataOrThrow(): T = when (this) {
+        is Success -> data
+        is Error -> throw BusinessException(code, message)
+        is Exception -> throw exception
+    }
+}

+ 820 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/network/response/ApiResponseExtensions.kt

@@ -0,0 +1,820 @@
+package com.narutohuo.xindazhou.common.network.response
+
+import com.narutohuo.xindazhou.common.network.exception.ApiException
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flow
+
+/**
+ * ApiResponse 扩展函数(参考 Sandwich)
+ * 
+ * 提供链式调用,简化响应处理
+ * 
+ * 使用方式:
+ * ```kotlin
+ * apiResponse
+ *     .onSuccess { data -> /* 处理成功 */ }
+ *     .onError { code, message -> /* 处理业务错误 */ }
+ *     .onException { exception -> /* 处理异常 */ }
+ * ```
+ */
+
+/**
+ * 成功时执行
+ * 
+ * 当响应为 Success 时执行指定的动作
+ * 
+ * @param action 成功时执行的动作,参数为数据
+ * @return 返回自身,支持链式调用
+ * 
+ * 示例:
+ * ```kotlin
+ * response.onSuccess { data ->
+ *     println("成功: $data")
+ * }
+ * ```
+ */
+inline fun <T> ApiResponse<T>.onSuccess(action: (T) -> Unit): ApiResponse<T> {
+    if (this is ApiResponse.Success) {
+        action(data)
+    }
+    return this
+}
+
+/**
+ * 业务错误时执行
+ * 
+ * 当响应为 Error 时执行指定的动作
+ * 
+ * @param action 业务错误时执行的动作,参数为错误码和错误消息
+ * @return 返回自身,支持链式调用
+ * 
+ * 示例:
+ * ```kotlin
+ * response.onError { code, message ->
+ *     println("业务错误: $code - $message")
+ * }
+ * ```
+ */
+inline fun <T> ApiResponse<T>.onError(action: (code: Int, message: String) -> Unit): ApiResponse<T> {
+    if (this is ApiResponse.Error) {
+        action(code, message)
+    }
+    return this
+}
+
+/**
+ * 异常时执行
+ * 
+ * 当响应为 Exception 时执行指定的动作
+ * 
+ * @param action 异常时执行的动作,参数为异常对象
+ * @return 返回自身,支持链式调用
+ * 
+ * 示例:
+ * ```kotlin
+ * response.onException { exception ->
+ *     println("异常: ${exception.message}")
+ * }
+ * ```
+ */
+inline fun <T> ApiResponse<T>.onException(action: (ApiException) -> Unit): ApiResponse<T> {
+    if (this is ApiResponse.Exception) {
+        action(exception)
+    }
+    return this
+}
+
+/**
+ * 转换数据
+ * 
+ * 如果响应成功,将数据转换为新类型
+ * 如果响应失败,保持原样
+ * 
+ * @param transform 数据转换函数
+ * @return 转换后的 ApiResponse
+ * 
+ * 示例:
+ * ```kotlin
+ * val userResponse: ApiResponse<User> = api.getUser()
+ * val nameResponse: ApiResponse<String> = userResponse.map { it.name }
+ * ```
+ */
+inline fun <T, R> ApiResponse<T>.map(transform: (T) -> R): ApiResponse<R> {
+    return when (this) {
+        is ApiResponse.Success -> ApiResponse.Success(transform(data))
+        is ApiResponse.Error -> ApiResponse.Error(code, message, body)
+        is ApiResponse.Exception -> ApiResponse.Exception(exception)
+    }
+}
+
+/**
+ * 只转换成功数据
+ * 
+ * 如果响应成功,将数据转换为新类型
+ * 如果响应失败,保持原样
+ * 
+ * 与 map() 的区别:mapSuccess() 语义更清晰,明确表示只转换成功数据
+ * 
+ * @param transform 数据转换函数
+ * @return 转换后的 ApiResponse
+ * 
+ * 示例:
+ * ```kotlin
+ * val userResponse: ApiResponse<User> = api.getUser()
+ * val nameResponse: ApiResponse<String> = userResponse.mapSuccess { it.name }
+ * ```
+ */
+inline fun <T, R> ApiResponse<T>.mapSuccess(transform: (T) -> R): ApiResponse<R> {
+    return when (this) {
+        is ApiResponse.Success -> ApiResponse.Success(transform(data))
+        is ApiResponse.Error -> ApiResponse.Error(code, message, body)
+        is ApiResponse.Exception -> ApiResponse.Exception(exception)
+    }
+}
+
+/**
+ * 转换失败数据
+ * 
+ * 如果响应失败(Error 或 Exception),转换失败信息
+ * 如果响应成功,保持原样
+ * 
+ * @param transformError 业务错误转换函数
+ * @param transformException 异常转换函数
+ * @return 转换后的 ApiResponse
+ * 
+ * 示例:
+ * ```kotlin
+ * val response: ApiResponse<User> = api.getUser()
+ * val mappedResponse = response.mapFailure(
+ *     transformError = { code, message -> "业务错误: $code - $message" },
+ *     transformException = { exception -> "网络错误: ${exception.message}" }
+ * )
+ * ```
+ */
+inline fun <T> ApiResponse<T>.mapFailure(
+    crossinline transformError: (Int, String) -> ApiResponse.Error<T>,
+    crossinline transformException: (ApiException) -> ApiResponse.Exception<T>
+): ApiResponse<T> {
+    return when (this) {
+        is ApiResponse.Success -> this
+        is ApiResponse.Error -> transformError(code, message)
+        is ApiResponse.Exception -> transformException(exception)
+    }
+}
+
+/**
+ * 数据验证
+ * 
+ * 如果响应成功,验证数据是否符合条件
+ * 如果验证失败,转换为 Error
+ * 如果响应失败,保持原样
+ * 
+ * @param predicate 验证条件,返回 true 表示验证通过
+ * @param errorCode 验证失败时的错误码(默认 -1)
+ * @param errorMessage 验证失败时的错误消息
+ * @return 验证后的 ApiResponse
+ * 
+ * 示例:
+ * ```kotlin
+ * val response: ApiResponse<User> = api.getUser()
+ * val validatedResponse = response.validate(
+ *     predicate = { it.age >= 18 },
+ *     errorMessage = "用户年龄必须大于等于18岁"
+ * )
+ * ```
+ */
+inline fun <T> ApiResponse<T>.validate(
+    crossinline predicate: (T) -> Boolean,
+    errorCode: Int = -1,
+    errorMessage: String = "数据验证失败"
+): ApiResponse<T> {
+    return when (this) {
+        is ApiResponse.Success -> {
+            if (predicate(data)) {
+                this
+            } else {
+                ApiResponse.Error(errorCode, errorMessage, null)
+            }
+        }
+        is ApiResponse.Error -> this
+        is ApiResponse.Exception -> this
+    }
+}
+
+/**
+ * 数据验证(使用 lambda 构建错误消息)
+ * 
+ * 如果响应成功,验证数据是否符合条件
+ * 如果验证失败,转换为 Error(错误消息由 lambda 动态生成)
+ * 如果响应失败,保持原样
+ * 
+ * @param predicate 验证条件,返回 true 表示验证通过
+ * @param errorCode 验证失败时的错误码(默认 -1)
+ * @param errorMessage 验证失败时的错误消息构建函数
+ * @return 验证后的 ApiResponse
+ * 
+ * 示例:
+ * ```kotlin
+ * val response: ApiResponse<User> = api.getUser()
+ * val validatedResponse = response.validate(
+ *     predicate = { it.age >= 18 },
+ *     errorMessage = { "用户年龄 ${it.age} 必须大于等于18岁" }
+ * )
+ * ```
+ */
+inline fun <T> ApiResponse<T>.validate(
+    crossinline predicate: (T) -> Boolean,
+    errorCode: Int = -1,
+    crossinline errorMessage: (T) -> String
+): ApiResponse<T> {
+    return when (this) {
+        is ApiResponse.Success -> {
+            if (predicate(data)) {
+                this
+            } else {
+                ApiResponse.Error(errorCode, errorMessage(data), null)
+            }
+        }
+        is ApiResponse.Error -> this
+        is ApiResponse.Exception -> this
+    }
+}
+
+/**
+ * 非空检查
+ * 
+ * 从成功数据中提取可能为 null 的字段
+ * 如果提取的字段为 null,转换为 Error
+ * 如果响应失败,保持原样
+ * 
+ * @param selector 提取函数,从数据中提取可能为 null 的字段
+ * @param errorCode 字段为 null 时的错误码(默认 -1)
+ * @param errorMessage 字段为 null 时的错误消息
+ * @return 提取后的 ApiResponse(非空类型)
+ * 
+ * 示例:
+ * ```kotlin
+ * val response: ApiResponse<User> = api.getUser()
+ * val profileImageResponse: ApiResponse<String> = response.requireNotNull(
+ *     selector = { it.profileImage },
+ *     errorMessage = "用户头像不能为空"
+ * )
+ * ```
+ */
+inline fun <T, R> ApiResponse<T>.requireNotNull(
+    crossinline selector: (T) -> R?,
+    errorCode: Int = -1,
+    errorMessage: String = "数据不能为空"
+): ApiResponse<R> {
+    return when (this) {
+        is ApiResponse.Success -> {
+            val value = selector(data)
+            if (value != null) {
+                ApiResponse.Success(value)
+            } else {
+                ApiResponse.Error(errorCode, errorMessage, null)
+            }
+        }
+        is ApiResponse.Error -> ApiResponse.Error(code, message, body)
+        is ApiResponse.Exception -> ApiResponse.Exception(exception)
+    }
+}
+
+/**
+ * 非空检查(使用 lambda 构建错误消息)
+ * 
+ * 从成功数据中提取可能为 null 的字段
+ * 如果提取的字段为 null,转换为 Error(错误消息由 lambda 动态生成)
+ * 如果响应失败,保持原样
+ * 
+ * @param selector 提取函数,从数据中提取可能为 null 的字段
+ * @param errorCode 字段为 null 时的错误码(默认 -1)
+ * @param errorMessage 字段为 null 时的错误消息构建函数
+ * @return 提取后的 ApiResponse(非空类型)
+ * 
+ * 示例:
+ * ```kotlin
+ * val response: ApiResponse<User> = api.getUser()
+ * val profileImageResponse: ApiResponse<String> = response.requireNotNull(
+ *     selector = { it.profileImage },
+ *     errorMessage = { user -> "用户 ${user.name} 的头像不能为空" }
+ * )
+ * ```
+ */
+inline fun <T, R> ApiResponse<T>.requireNotNull(
+    crossinline selector: (T) -> R?,
+    errorCode: Int = -1,
+    crossinline errorMessage: (T) -> String
+): ApiResponse<R> {
+    return when (this) {
+        is ApiResponse.Success -> {
+            val value = selector(data)
+            if (value != null) {
+                ApiResponse.Success(value)
+            } else {
+                ApiResponse.Error(errorCode, errorMessage(data), null)
+            }
+        }
+        is ApiResponse.Error -> ApiResponse.Error(code, message, body)
+        is ApiResponse.Exception -> ApiResponse.Exception(exception)
+    }
+}
+
+/**
+ * 非空检查(suspend 版本)
+ * 
+ * 从成功数据中提取可能为 null 的字段
+ * 如果提取的字段为 null,转换为 Error
+ * 如果响应失败,保持原样
+ * 
+ * 注意:selector 可以是 suspend 函数,适用于需要异步提取的场景
+ * 
+ * @param selector 提取函数(suspend),从数据中提取可能为 null 的字段
+ * @param errorCode 字段为 null 时的错误码(默认 -1)
+ * @param errorMessage 字段为 null 时的错误消息
+ * @return 提取后的 ApiResponse(非空类型)
+ * 
+ * 示例:
+ * ```kotlin
+ * val response: ApiResponse<User> = api.getUser()
+ * val profileImageResponse: ApiResponse<String> = response.suspendRequireNotNull(
+ *     selector = { user -> fetchProfileImageFromCache(user.id) },
+ *     errorMessage = "用户头像不能为空"
+ * )
+ * ```
+ */
+suspend inline fun <T, R> ApiResponse<T>.suspendRequireNotNull(
+    crossinline selector: suspend (T) -> R?,
+    errorCode: Int = -1,
+    errorMessage: String = "数据不能为空"
+): ApiResponse<R> {
+    return when (this) {
+        is ApiResponse.Success -> {
+            val value = selector(data)
+            if (value != null) {
+                ApiResponse.Success(value)
+            } else {
+                ApiResponse.Error(errorCode, errorMessage, null)
+            }
+        }
+        is ApiResponse.Error -> ApiResponse.Error(code, message, body)
+        is ApiResponse.Exception -> ApiResponse.Exception(exception)
+    }
+}
+
+/**
+ * 非空检查(suspend 版本,使用 lambda 构建错误消息)
+ * 
+ * 从成功数据中提取可能为 null 的字段
+ * 如果提取的字段为 null,转换为 Error(错误消息由 lambda 动态生成)
+ * 如果响应失败,保持原样
+ * 
+ * 注意:selector 和 errorMessage 都可以是 suspend 函数
+ * 
+ * @param selector 提取函数(suspend),从数据中提取可能为 null 的字段
+ * @param errorCode 字段为 null 时的错误码(默认 -1)
+ * @param errorMessage 字段为 null 时的错误消息构建函数(suspend)
+ * @return 提取后的 ApiResponse(非空类型)
+ * 
+ * 示例:
+ * ```kotlin
+ * val response: ApiResponse<User> = api.getUser()
+ * val profileImageResponse: ApiResponse<String> = response.suspendRequireNotNull(
+ *     selector = { user -> fetchProfileImageFromCache(user.id) },
+ *     errorMessage = { user -> "用户 ${user.name} 的头像不能为空" }
+ * )
+ * ```
+ */
+suspend inline fun <T, R> ApiResponse<T>.suspendRequireNotNull(
+    crossinline selector: suspend (T) -> R?,
+    errorCode: Int = -1,
+    crossinline errorMessage: suspend (T) -> String
+): ApiResponse<R> {
+    return when (this) {
+        is ApiResponse.Success -> {
+            val value = selector(data)
+            if (value != null) {
+                ApiResponse.Success(value)
+            } else {
+                ApiResponse.Error(errorCode, errorMessage(data), null)
+            }
+        }
+        is ApiResponse.Error -> ApiResponse.Error(code, message, body)
+        is ApiResponse.Exception -> ApiResponse.Exception(exception)
+    }
+}
+
+/**
+ * 失败时返回默认值
+ * 
+ * 如果响应失败(Error 或 Exception),返回默认值
+ * 如果响应成功,保持原样
+ * 
+ * @param block 默认值生成函数
+ * @return 成功时返回原响应,失败时返回包含默认值的 Success
+ * 
+ * 示例:
+ * ```kotlin
+ * val response: ApiResponse<User> = api.getUser()
+ * val userWithDefault: ApiResponse<User> = response.recover { User.default() }
+ * ```
+ */
+inline fun <T> ApiResponse<T>.recover(block: () -> T): ApiResponse<T> {
+    return when (this) {
+        is ApiResponse.Success -> this
+        else -> ApiResponse.Success(block())
+    }
+}
+
+/**
+ * 失败时返回固定的默认值
+ * 
+ * @param defaultValue 默认值
+ * @return 成功时返回原响应,失败时返回包含默认值的 Success
+ * 
+ * 示例:
+ * ```kotlin
+ * val response: ApiResponse<User> = api.getUser()
+ * val userWithDefault: ApiResponse<User> = response.recoverValue(User.default())
+ * ```
+ */
+fun <T> ApiResponse<T>.recoverValue(defaultValue: T): ApiResponse<T> {
+    return when (this) {
+        is ApiResponse.Success -> this
+        else -> ApiResponse.Success(defaultValue)
+    }
+}
+
+/**
+ * 失败时返回 null
+ * 
+ * 如果响应失败,返回包含 null 的 Success
+ * 如果响应成功,保持原样
+ * 
+ * @return 成功时返回原响应,失败时返回包含 null 的 Success
+ * 
+ * 示例:
+ * ```kotlin
+ * val response: ApiResponse<User> = api.getUser()
+ * val userOrNull: ApiResponse<User?> = response.recoverNull()
+ * ```
+ */
+fun <T> ApiResponse<T>.recoverNull(): ApiResponse<T?> {
+    return when (this) {
+        is ApiResponse.Success -> ApiResponse.Success(data)
+        else -> ApiResponse.Success(null)
+    }
+}
+
+/**
+ * 失败时返回备用的 ApiResponse
+ * 
+ * 如果响应失败(Error 或 Exception),返回备用响应
+ * 如果响应成功,保持原样
+ * 
+ * 与 recover() 的区别:recover() 返回默认值(T),recoverWith() 返回备用响应(ApiResponse<T>)
+ * 
+ * @param block 备用响应生成函数,参数为失败响应(Error 或 Exception)
+ * @return 成功时返回原响应,失败时返回备用响应
+ * 
+ * 示例:
+ * ```kotlin
+ * val response: ApiResponse<User> = api.getUser()
+ * val recoveredResponse: ApiResponse<User> = response.recoverWith { failure ->
+ *     // 从缓存或备用服务器获取
+ *     when (failure) {
+ *         is ApiResponse.Error -> getCachedUser()
+ *         is ApiResponse.Exception -> getFallbackUser()
+ *     }
+ * }
+ * ```
+ */
+inline fun <T> ApiResponse<T>.recoverWith(
+    crossinline block: (ApiResponse<T>) -> ApiResponse<T>
+): ApiResponse<T> {
+    return when (this) {
+        is ApiResponse.Success -> this
+        else -> block(this)
+    }
+}
+
+/**
+ * 失败时返回备用的 ApiResponse(suspend 版本)
+ * 
+ * 如果响应失败(Error 或 Exception),返回备用响应
+ * 如果响应成功,保持原样
+ * 
+ * 注意:block 可以是 suspend 函数,适用于需要异步获取备用响应的场景
+ * 
+ * @param block 备用响应生成函数(suspend),参数为失败响应(Error 或 Exception)
+ * @return 成功时返回原响应,失败时返回备用响应
+ * 
+ * 示例:
+ * ```kotlin
+ * val response: ApiResponse<User> = api.getUser()
+ * val recoveredResponse: ApiResponse<User> = response.suspendRecoverWith { failure ->
+ *     // 从数据库或备用服务器异步获取
+ *     when (failure) {
+ *         is ApiResponse.Error -> getCachedUserFromDatabase()
+ *         is ApiResponse.Exception -> getFallbackUserFromServer()
+ *     }
+ * }
+ * ```
+ */
+suspend inline fun <T> ApiResponse<T>.suspendRecoverWith(
+    crossinline block: suspend (ApiResponse<T>) -> ApiResponse<T>
+): ApiResponse<T> {
+    return when (this) {
+        is ApiResponse.Success -> this
+        else -> block(this)
+    }
+}
+
+/**
+ * 转换为 Flow
+ * 
+ * 将 ApiResponse<T> 转换为 Flow<T>
+ * - 成功时发射数据
+ * - 失败时发射异常(业务错误或网络异常)
+ * 
+ * @return Flow<T> 成功时发射数据,失败时发射异常
+ * 
+ * 示例:
+ * ```kotlin
+ * val response: ApiResponse<User> = api.getUser()
+ * response.toFlow()
+ *     .catch { exception -> /* 处理异常 */ }
+ *     .collect { user -> /* 处理成功数据 */ }
+ * ```
+ */
+fun <T> ApiResponse<T>.toFlow(): Flow<T> {
+    return flow {
+        when (this@toFlow) {
+            is ApiResponse.Success -> emit(data)
+            is ApiResponse.Error -> throw com.narutohuo.xindazhou.common.network.exception.BusinessException(code, message)
+            is ApiResponse.Exception -> throw exception
+        }
+    }
+}
+
+/**
+ * 转换为 Flow(保持 ApiResponse 类型)
+ * 
+ * 将 ApiResponse<T> 转换为 Flow<ApiResponse<T>>
+ * 保持完整的响应信息
+ * 
+ * @return Flow<ApiResponse<T>> 发射完整的响应信息
+ * 
+ * 示例:
+ * ```kotlin
+ * val response: ApiResponse<User> = api.getUser()
+ * response.toFlowResponse()
+ *     .collect { apiResponse ->
+ *         when (apiResponse) {
+ *             is ApiResponse.Success -> { /* 处理成功 */ }
+ *             is ApiResponse.Error -> { /* 处理业务错误 */ }
+ *             is ApiResponse.Exception -> { /* 处理异常 */ }
+ *         }
+ *     }
+ * ```
+ */
+fun <T> ApiResponse<T>.toFlowResponse(): Flow<ApiResponse<T>> {
+    return flow {
+        emit(this@toFlowResponse)
+    }
+}
+
+// ==================== suspend 版本扩展函数 ====================
+
+/**
+ * 成功时执行(suspend 版本)
+ * 
+ * 当响应为 Success 时执行指定的 suspend 动作
+ * 
+ * @param action 成功时执行的动作,参数为数据
+ * @return 返回自身,支持链式调用
+ * 
+ * 示例:
+ * ```kotlin
+ * response.suspendOnSuccess { data ->
+ *     // 可以调用 suspend 函数
+ *     saveToDatabase(data)
+ * }
+ * ```
+ */
+suspend inline fun <T> ApiResponse<T>.suspendOnSuccess(action: suspend (T) -> Unit): ApiResponse<T> {
+    if (this is ApiResponse.Success) {
+        action(data)
+    }
+    return this
+}
+
+/**
+ * 业务错误时执行(suspend 版本)
+ * 
+ * 当响应为 Error 时执行指定的 suspend 动作
+ * 
+ * @param action 业务错误时执行的动作,参数为错误码和错误消息
+ * @return 返回自身,支持链式调用
+ * 
+ * 示例:
+ * ```kotlin
+ * response.suspendOnError { code, message ->
+ *     // 可以调用 suspend 函数
+ *     logError(code, message)
+ * }
+ * ```
+ */
+suspend inline fun <T> ApiResponse<T>.suspendOnError(action: suspend (code: Int, message: String) -> Unit): ApiResponse<T> {
+    if (this is ApiResponse.Error) {
+        action(code, message)
+    }
+    return this
+}
+
+/**
+ * 异常时执行(suspend 版本)
+ * 
+ * 当响应为 Exception 时执行指定的 suspend 动作
+ * 
+ * @param action 异常时执行的动作,参数为异常对象
+ * @return 返回自身,支持链式调用
+ * 
+ * 示例:
+ * ```kotlin
+ * response.suspendOnException { exception ->
+ *     // 可以调用 suspend 函数
+ *     reportException(exception)
+ * }
+ * ```
+ */
+suspend inline fun <T> ApiResponse<T>.suspendOnException(action: suspend (ApiException) -> Unit): ApiResponse<T> {
+    if (this is ApiResponse.Exception) {
+        action(exception)
+    }
+    return this
+}
+
+// ==================== zip 组合函数 ====================
+
+/**
+ * 组合两个 ApiResponse
+ * 
+ * 如果两个响应都是 Success,则使用 transform 函数组合数据
+ * 如果任一响应失败,则返回第一个失败响应(短路)
+ * 
+ * @param other 另一个 ApiResponse
+ * @param transform 组合函数,参数为两个成功数据
+ * @return 组合后的 ApiResponse
+ * 
+ * 示例:
+ * ```kotlin
+ * val userResponse: ApiResponse<User> = api.getUser()
+ * val profileResponse: ApiResponse<Profile> = api.getProfile()
+ * 
+ * val combinedResponse: ApiResponse<UserProfile> = userResponse.zip(profileResponse) { user, profile ->
+ *     UserProfile(user, profile)
+ * }
+ * ```
+ */
+inline fun <T1, T2, R> ApiResponse<T1>.zip(
+    other: ApiResponse<T2>,
+    crossinline transform: (T1, T2) -> R
+): ApiResponse<R> {
+    return when {
+        this is ApiResponse.Success && other is ApiResponse.Success -> {
+            ApiResponse.Success(transform(this.data, other.data))
+        }
+        this is ApiResponse.Error -> ApiResponse.Error(code, message, body)
+        this is ApiResponse.Exception -> ApiResponse.Exception(exception)
+        other is ApiResponse.Error -> ApiResponse.Error(other.code, other.message, other.body)
+        other is ApiResponse.Exception -> ApiResponse.Exception(other.exception)
+        else -> throw IllegalStateException("Unexpected state")
+    }
+}
+
+/**
+ * 组合三个 ApiResponse
+ * 
+ * 如果三个响应都是 Success,则使用 transform 函数组合数据
+ * 如果任一响应失败,则返回第一个失败响应(短路)
+ * 
+ * @param second 第二个 ApiResponse
+ * @param third 第三个 ApiResponse
+ * @param transform 组合函数,参数为三个成功数据
+ * @return 组合后的 ApiResponse
+ * 
+ * 示例:
+ * ```kotlin
+ * val userResponse: ApiResponse<User> = api.getUser()
+ * val profileResponse: ApiResponse<Profile> = api.getProfile()
+ * val settingsResponse: ApiResponse<Settings> = api.getSettings()
+ * 
+ * val combinedResponse: ApiResponse<UserProfile> = zip3(
+ *     userResponse, profileResponse, settingsResponse
+ * ) { user, profile, settings ->
+ *     UserProfile(user, profile, settings)
+ * }
+ * ```
+ */
+inline fun <T1, T2, T3, R> zip3(
+    first: ApiResponse<T1>,
+    second: ApiResponse<T2>,
+    third: ApiResponse<T3>,
+    crossinline transform: (T1, T2, T3) -> R
+): ApiResponse<R> {
+    return when {
+        first is ApiResponse.Success && second is ApiResponse.Success && third is ApiResponse.Success -> {
+            ApiResponse.Success(transform(first.data, second.data, third.data))
+        }
+        first is ApiResponse.Error -> ApiResponse.Error(first.code, first.message, first.body)
+        first is ApiResponse.Exception -> ApiResponse.Exception(first.exception)
+        second is ApiResponse.Error -> ApiResponse.Error(second.code, second.message, second.body)
+        second is ApiResponse.Exception -> ApiResponse.Exception(second.exception)
+        third is ApiResponse.Error -> ApiResponse.Error(third.code, third.message, third.body)
+        third is ApiResponse.Exception -> ApiResponse.Exception(third.exception)
+        else -> throw IllegalStateException("Unexpected state")
+    }
+}
+
+/**
+ * 组合两个 ApiResponse(suspend 版本)
+ * 
+ * 如果两个响应都是 Success,则使用 transform 函数组合数据(transform 可以是 suspend 函数)
+ * 如果任一响应失败,则返回第一个失败响应(短路)
+ * 
+ * @param other 另一个 ApiResponse
+ * @param transform 组合函数(suspend),参数为两个成功数据
+ * @return 组合后的 ApiResponse
+ * 
+ * 示例:
+ * ```kotlin
+ * val userResponse: ApiResponse<User> = api.getUser()
+ * val profileResponse: ApiResponse<Profile> = api.getProfile()
+ * 
+ * val combinedResponse: ApiResponse<UserProfile> = userResponse.suspendZip(profileResponse) { user, profile ->
+ *     // 可以调用 suspend 函数进行复杂组合
+ *     createUserProfile(user, profile)
+ * }
+ * ```
+ */
+suspend inline fun <T1, T2, R> ApiResponse<T1>.suspendZip(
+    other: ApiResponse<T2>,
+    crossinline transform: suspend (T1, T2) -> R
+): ApiResponse<R> {
+    return when {
+        this is ApiResponse.Success && other is ApiResponse.Success -> {
+            ApiResponse.Success(transform(this.data, other.data))
+        }
+        this is ApiResponse.Error -> ApiResponse.Error(code, message, body)
+        this is ApiResponse.Exception -> ApiResponse.Exception(exception)
+        other is ApiResponse.Error -> ApiResponse.Error(other.code, other.message, other.body)
+        other is ApiResponse.Exception -> ApiResponse.Exception(other.exception)
+        else -> throw IllegalStateException("Unexpected state")
+    }
+}
+
+/**
+ * 组合三个 ApiResponse(suspend 版本)
+ * 
+ * 如果三个响应都是 Success,则使用 transform 函数组合数据(transform 可以是 suspend 函数)
+ * 如果任一响应失败,则返回第一个失败响应(短路)
+ * 
+ * @param second 第二个 ApiResponse
+ * @param third 第三个 ApiResponse
+ * @param transform 组合函数(suspend),参数为三个成功数据
+ * @return 组合后的 ApiResponse
+ * 
+ * 示例:
+ * ```kotlin
+ * val userResponse: ApiResponse<User> = api.getUser()
+ * val profileResponse: ApiResponse<Profile> = api.getProfile()
+ * val settingsResponse: ApiResponse<Settings> = api.getSettings()
+ * 
+ * val combinedResponse: ApiResponse<UserProfile> = suspendZip3(
+ *     userResponse, profileResponse, settingsResponse
+ * ) { user, profile, settings ->
+ *     // 可以调用 suspend 函数进行复杂组合
+ *     createUserProfile(user, profile, settings)
+ * }
+ * ```
+ */
+suspend inline fun <T1, T2, T3, R> suspendZip3(
+    first: ApiResponse<T1>,
+    second: ApiResponse<T2>,
+    third: ApiResponse<T3>,
+    crossinline transform: suspend (T1, T2, T3) -> R
+): ApiResponse<R> {
+    return when {
+        first is ApiResponse.Success && second is ApiResponse.Success && third is ApiResponse.Success -> {
+            ApiResponse.Success(transform(first.data, second.data, third.data))
+        }
+        first is ApiResponse.Error -> ApiResponse.Error(first.code, first.message, first.body)
+        first is ApiResponse.Exception -> ApiResponse.Exception(first.exception)
+        second is ApiResponse.Error -> ApiResponse.Error(second.code, second.message, second.body)
+        second is ApiResponse.Exception -> ApiResponse.Exception(second.exception)
+        third is ApiResponse.Error -> ApiResponse.Error(third.code, third.message, third.body)
+        third is ApiResponse.Exception -> ApiResponse.Exception(third.exception)
+        else -> throw IllegalStateException("Unexpected state")
+    }
+}

+ 201 - 0
base-common/src/main/java/com/narutohuo/xindazhou/common/ui/BaseViewModel.kt

@@ -0,0 +1,201 @@
+package com.narutohuo.xindazhou.common.ui
+
+import android.app.Application
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.viewModelScope
+import com.narutohuo.xindazhou.core.log.ILog
+import kotlinx.coroutines.CoroutineExceptionHandler
+import kotlinx.coroutines.launch
+
+/**
+ * ViewModel 基类
+ * 
+ * 提供统一的 ViewModel 功能,与其他平台(HarmonyOS/iOS)保持一致
+ * 继承 AndroidViewModel,自动提供 Application 上下文
+ * 
+ * **核心功能**:
+ * 1. 自动日志标签(tag)
+ * 2. 安全的协程执行(自动捕获异常)
+ * 3. 统一的错误处理
+ * 4. 生命周期管理(onInit/onDestroy)
+ * 
+ * **使用方式**:
+ * ```kotlin
+ * class LoginViewModel(
+ *     application: Application,
+ *     private val authRepository: AuthRepository
+ * ) : BaseViewModel(application) {
+ *     
+ *     private val _loginState = MutableStateFlow<LoginState>(LoginState.Idle)
+ *     val loginState: StateFlow<LoginState> = _loginState
+ *     
+ *     fun login(mobile: String, password: String) {
+ *         // 方式1:使用 safeLaunch(推荐,自动处理异常)
+ *         safeLaunch {
+ *             _loginState.value = LoginState.Loading
+ *             val result = authRepository.login(mobile, password)
+ *             result.onSuccess {
+ *                 _loginState.value = LoginState.Success("登录成功")
+ *             }.onFailure { e ->
+ *                 _loginState.value = LoginState.Error(e.message ?: "登录失败")
+ *             }
+ *         }
+ *     }
+ * }
+ * ```
+ */
+abstract class BaseViewModel(
+    application: Application
+) : AndroidViewModel(application) {
+    
+    /**
+     * 日志标签(自动使用类名)
+     */
+    protected val tag: String
+        get() = this::class.simpleName ?: "BaseViewModel"
+    
+    /**
+     * ViewModel 初始化(可选)
+     * 
+     * 在 ViewModel 创建后调用,可用于初始化数据
+     * 子类可重写此方法
+     */
+    open fun onInit() {
+        // 子类可重写
+    }
+    
+    /**
+     * ViewModel 销毁(可选)
+     * 
+     * 在 ViewModel 被清除时调用,用于清理资源
+     * 子类可重写此方法
+     */
+    override fun onCleared() {
+        super.onCleared()
+        onDestroy()
+    }
+    
+    /**
+     * ViewModel 销毁回调(可选)
+     * 
+     * 在 onCleared() 中调用,子类可重写此方法进行清理
+     */
+    protected open fun onDestroy() {
+        // 子类可重写
+    }
+    
+    /**
+     * 处理错误(通用方法)
+     * 
+     * 提供统一的错误处理逻辑,子类可重写
+     * 
+     * @param throwable 异常对象
+     * @param errorMessage 错误消息前缀(可选)
+     * @return 处理后的错误消息
+     */
+    protected open fun handleError(throwable: Throwable, errorMessage: String = "操作失败"): String {
+        val errorMsg = throwable.message ?: errorMessage
+        ILog.e(tag, errorMsg, throwable)
+        return errorMsg
+    }
+    
+    /**
+     * 获取 Application 上下文(便捷方法)
+     */
+    protected val applicationContext: android.content.Context
+        get() = getApplication<Application>().applicationContext
+    
+    // ==================== 协程辅助方法 ====================
+    
+    /**
+     * 安全的协程启动(自动捕获异常)
+     * 
+     * 自动处理协程中的异常,避免崩溃
+     * 异常会通过 handleError() 方法统一处理
+     * 
+     * **使用示例**:
+     * ```kotlin
+     * safeLaunch {
+     *     val result = repository.loadData()
+     *     result.onSuccess { data ->
+     *         _uiState.value = UiState.Success(data)
+     *     }.onFailure { e ->
+     *         _uiState.value = UiState.Error(handleError(e))
+     *     }
+     * }
+     * ```
+     * 
+     * @param errorMessage 错误消息前缀(可选)
+     * @param block 协程代码块
+     */
+    protected fun safeLaunch(
+        errorMessage: String = "操作失败",
+        block: suspend () -> Unit
+    ) {
+        viewModelScope.launch(
+            CoroutineExceptionHandler { _, throwable ->
+                handleError(throwable, errorMessage)
+            }
+        ) {
+            try {
+                block()
+            } catch (e: Exception) {
+                handleError(e, errorMessage)
+            }
+        }
+    }
+    
+    /**
+     * 执行网络请求(简化版)
+     * 
+     * 自动处理 Loading/Success/Error 状态,减少重复代码
+     * 
+     * **使用示例**:
+     * ```kotlin
+     * private val _loginState = MutableStateFlow<LoginState>(LoginState.Idle)
+     * 
+     * fun login(mobile: String, password: String) {
+     *     executeRequest(
+     *         stateFlow = _loginState,
+     *         onLoading = { LoginState.Loading },
+     *         onSuccess = { LoginState.Success("登录成功") },
+     *         onError = { msg -> LoginState.Error(msg) },
+     *         errorMessage = "登录失败"
+     *     ) {
+     *         authRepository.login(mobile, password)
+     *     }
+     * }
+     * ```
+     * 
+     * @param stateFlow 状态流
+     * @param onLoading 加载状态
+     * @param onSuccess 成功状态
+     * @param onError 错误状态(接收错误消息)
+     * @param errorMessage 错误消息前缀
+     * @param request 网络请求(返回 Result<T>)
+     */
+    protected fun <T, S> executeRequest(
+        stateFlow: kotlinx.coroutines.flow.MutableStateFlow<S>,
+        onLoading: () -> S,
+        onSuccess: (T) -> S,
+        onError: (String) -> S,
+        errorMessage: String = "请求失败",
+        request: suspend () -> Result<T>
+    ) {
+        viewModelScope.launch {
+            try {
+                stateFlow.value = onLoading()
+                val result = request()
+                result.onSuccess { data ->
+                    stateFlow.value = onSuccess(data)
+                }.onFailure { throwable ->
+                    val errorMsg = handleError(throwable, errorMessage)
+                    stateFlow.value = onError(errorMsg)
+                }
+            } catch (e: Exception) {
+                val errorMsg = handleError(e, errorMessage)
+                stateFlow.value = onError(errorMsg)
+            }
+        }
+    }
+}

+ 1 - 6
base-common/src/main/java/com/narutohuo/xindazhou/common/version/VersionUpdateManager.kt

@@ -48,12 +48,7 @@ object VersionUpdateManager {
      * @param platform 平台类型(默认 1-安卓)
      */
     fun init(context: Context, platform: Int = PLATFORM_ANDROID) {
-        // 初始化 ApiManager 的 baseUrlProvider(如果还未设置)
-        if (ApiManager.baseUrlProvider == null) {
-            // 这里需要从 ServerConfigManager 获取,但为了避免循环依赖,
-            // 建议在 Application 中先设置 ApiManager.baseUrlProvider
-            ILog.w("VersionUpdateManager", "ApiManager.baseUrlProvider 未设置,版本检查可能失败")
-        }
+        // ApiManager 会自动从 ServerConfigManager 读取,无需手动设置
     }
     
     /**

+ 3 - 4
base-common/src/main/java/com/narutohuo/xindazhou/common/version/datasource/remote/VersionRemoteDataSource.kt

@@ -35,10 +35,9 @@ class VersionRemoteDataSourceImpl : ApiBaseRemoteDataSource(), VersionRemoteData
         platform: Int,
         currentVersionCode: Int?
     ): Result<VersionResponse> {
-        return executeRequest(
-            request = { versionApi.getLatestVersion(platform, currentVersionCode) },
-            errorMessage = "版本检查失败"
-        )
+        return executeRequestResponse(
+            request = { versionApi.getLatestVersion(platform, currentVersionCode) }
+        ).toResult()
     }
 }
 

+ 14 - 4
base-core/build.gradle

@@ -21,8 +21,11 @@ android {
 }
 
 repositories {
-    // base-core 不再需要访问 capability-share/libs 目录
-    // 友盟相关依赖由 capability-share 模块管理
+    flatDir {
+        dirs 'libs'
+    }
+    mavenCentral()
+    google()
 }
 
 dependencies {
@@ -54,10 +57,17 @@ dependencies {
     api("com.github.getActivity:XXPermissions:18.6")
     
     // 微信 SDK(仅用于 WXEntryActivity 回调 Activity)
-    // 注意:base-core 只依赖微信官方 SDK,不依赖友盟
-    // 友盟相关依赖应该在 capability-share 模块中
     api("com.tencent.mm.opensdk:wechat-sdk-android:6.8.24")
     
+    // 友盟分享核心库(仅用于 WXEntryActivity 继承 WXCallbackActivity)
+    // 注意:base-core 只依赖友盟分享核心库,不依赖完整的分享功能
+    // 完整的分享功能在 capability-share 模块中
+    api("com.umeng.umsdk:share-core:+")
+    
+    // 友盟微信分享完整版 JAR(包含 WXCallbackActivity)
+    // 注意:WXCallbackActivity 在 umeng-share-wechat-full JAR 中
+    api(files('libs/umeng-share-wechat-full-7.3.7.jar'))
+    
     // LogcatViewer - 浮动窗口日志输出(开发调试用)
     // 注意:只在开发环境使用,生产环境使用 NoOpLog
     implementation("com.github.weijiaxing:LogcatViewer:1.0.3")

+ 2 - 7
base-core/src/main/AndroidManifest.xml

@@ -3,14 +3,9 @@
 
     <application>
         <!-- 微信分享回调 Activity(必需) -->
-        <!-- 注意:虽然文件在 base-core 模块中,但包名使用应用包名(com.narutohuo.xindazhou.wxapi) -->
+        <!-- 注意:包名必须是微信开放平台配置的包名(com.mooxygen.user.wxapi) -->
         <!-- 微信 SDK 要求此 Activity 必须在应用包名的 wxapi 子包下 -->
-        <!-- 通过 ARouter 依赖注入获取 IShareCallback 实现,实现 base-core 与 share 模块的解耦 -->
-        <activity
-            android:name="com.narutohuo.xindazhou.wxapi.WXEntryActivity"
-            android:configChanges="keyboardHidden|orientation|screenSize"
-            android:exported="true"
-            android:theme="@android:style/Theme.Translucent.NoTitleBar" />
+        <!-- 按照友盟官方示例,直接继承 WXCallbackActivity,友盟会自动处理回调 -->
         <activity
             android:name="com.mooxygen.user.wxapi.WXEntryActivity"
             android:configChanges="keyboardHidden|orientation|screenSize"

+ 0 - 139
base-core/src/main/java/com/mooxygen/user/wxapi/WXEntryActivity.kt

@@ -1,139 +0,0 @@
-package com.mooxygen.user.wxapi
-
-import android.app.Activity
-import android.content.Intent
-import android.os.Bundle
-import com.alibaba.android.arouter.facade.annotation.Autowired
-import com.alibaba.android.arouter.launcher.ARouter
-import com.narutohuo.xindazhou.core.log.ILog
-import com.narutohuo.xindazhou.core.share.IShareCallback
-import com.tencent.mm.opensdk.constants.ConstantsAPI
-import com.tencent.mm.opensdk.modelbase.BaseReq
-import com.tencent.mm.opensdk.modelbase.BaseResp
-import com.tencent.mm.opensdk.openapi.IWXAPI
-import com.tencent.mm.opensdk.openapi.IWXAPIEventHandler
-import com.tencent.mm.opensdk.openapi.WXAPIFactory
-
-/**
- * 微信分享回调 Activity
- * 
- * 按照微信官方 SDK 文档,需要创建此 Activity 处理微信分享回调
- * 
- * 注意:
- * - 包名必须是应用包名(com.narutohuo.xindazhou.wxapi),不能是模块 namespace
- * - 微信 SDK 要求此 Activity 必须在应用包名的 wxapi 子包下
- * - 虽然文件在 base-core 模块中,但包名使用应用包名,这样 Android 清单合并后能正确识别
- * 
- * 架构说明:
- * - 使用 ARouter 依赖注入获取 IShareCallback 接口实现
- * - share 模块实现 IShareCallback 接口并通过 ARouter 注册
- * - base-core 不直接依赖 share 模块,实现解耦
- * - base-core 只依赖微信官方 SDK,不依赖友盟
- * 
- * 工作原理:
- * 1. 用户在微信中完成分享操作后,微信会通过 Intent 回调到此 Activity
- * 2. 通过微信 SDK 的 IWXAPIEventHandler 接口处理回调
- * 3. 通过 ARouter 获取 IShareCallback 实现,调用 share 模块的处理方法
- * 4. share 模块处理完成后,通过 ShareProxyActivity 的回调传递给业务层
- */
-class WXEntryActivity : Activity(), IWXAPIEventHandler {
-    
-    private var api: IWXAPI? = null
-    
-    /**
-     * 通过 ARouter 依赖注入获取分享回调服务
-     * 
-     * 注意:share 模块需要实现 IShareCallback 接口并通过 @Route 注册
-     */
-    @Autowired(name = "/share/callback")
-    @JvmField
-    var shareCallback: IShareCallback? = null
-    
-    override fun onCreate(savedInstanceState: Bundle?) {
-        super.onCreate(savedInstanceState)
-        
-        // ARouter 依赖注入
-        ARouter.getInstance().inject(this)
-        
-        // 创建微信 API 实例(需要从 share 模块获取 AppId,这里先尝试处理回调)
-        // 注意:实际的微信 API 初始化应该在 share 模块中完成
-        // 这里只是接收回调并转发给 share 模块处理
-        
-        ILog.d("WXEntryActivity", "onCreate: 收到微信回调")
-        
-        // 调用 share 模块处理回调
-        shareCallback?.let {
-            ILog.d("WXEntryActivity", "通过 ARouter 获取到 IShareCallback 实现,调用 onWeChatCallback")
-            it.onWeChatCallback(intent)
-        } ?: run {
-            ILog.e("WXEntryActivity", "未找到 IShareCallback 实现,请确保 share 模块已注册")
-        }
-        
-        // 处理微信回调(通过 IWXAPIEventHandler)
-        handleIntent(intent)
-        
-        // 关闭 Activity
-        finish()
-    }
-    
-    override fun onNewIntent(intent: Intent?) {
-        super.onNewIntent(intent)
-        setIntent(intent)
-        ILog.d("WXEntryActivity", "onNewIntent: 收到新的微信回调 Intent")
-        
-        // 调用 share 模块处理回调
-        intent?.let {
-            shareCallback?.onWeChatCallback(it)
-            handleIntent(it)
-        }
-    }
-    
-    /**
-     * 处理微信回调 Intent
-     */
-    private fun handleIntent(intent: Intent?) {
-        if (intent == null) return
-        
-        // 尝试从 share 模块获取微信 API 实例
-        // 如果 share 模块已经初始化了微信 API,这里可以直接使用
-        // 否则,这里只转发 Intent 给 share 模块处理
-        
-        // 注意:微信 SDK 要求通过 IWXAPI.handleIntent 处理回调
-        // 但这里为了解耦,将 Intent 转发给 share 模块处理
-        // share 模块会使用自己的 IWXAPI 实例处理回调
-    }
-    
-    /**
-     * 微信请求回调
-     */
-    override fun onReq(req: BaseReq?) {
-        ILog.d("WXEntryActivity", "onReq: ${req?.type}")
-        // 微信请求通常不需要处理,主要是响应回调
-    }
-    
-    /**
-     * 微信响应回调
-     */
-    override fun onResp(resp: BaseResp?) {
-        ILog.d("WXEntryActivity", "onResp: type=${resp?.type}, errCode=${resp?.errCode}, errStr=${resp?.errStr}")
-        
-        when (resp?.type) {
-            ConstantsAPI.COMMAND_SENDAUTH -> {
-                // 微信登录回调
-                ILog.d("WXEntryActivity", "收到微信登录回调")
-            }
-            ConstantsAPI.COMMAND_SENDMESSAGE_TO_WX -> {
-                // 微信分享回调
-                ILog.d("WXEntryActivity", "收到微信分享回调")
-            }
-            ConstantsAPI.COMMAND_PAY_BY_WX -> {
-                // 微信支付回调
-                ILog.d("WXEntryActivity", "收到微信支付回调")
-            }
-        }
-        
-        // 将回调转发给 share 模块处理
-        shareCallback?.onWeChatCallback(intent)
-    }
-}
-

+ 7 - 129
base-core/src/main/java/com/narutohuo/xindazhou/wxapi/WXEntryActivity.kt

@@ -1,139 +1,17 @@
-package com.narutohuo.xindazhou.wxapi
+package com.mooxygen.user.wxapi
 
-import android.app.Activity
-import android.content.Intent
-import android.os.Bundle
-import com.alibaba.android.arouter.facade.annotation.Autowired
-import com.alibaba.android.arouter.launcher.ARouter
-import com.narutohuo.xindazhou.core.log.ILog
-import com.narutohuo.xindazhou.core.share.IShareCallback
-import com.tencent.mm.opensdk.constants.ConstantsAPI
-import com.tencent.mm.opensdk.modelbase.BaseReq
-import com.tencent.mm.opensdk.modelbase.BaseResp
-import com.tencent.mm.opensdk.openapi.IWXAPI
-import com.tencent.mm.opensdk.openapi.IWXAPIEventHandler
-import com.tencent.mm.opensdk.openapi.WXAPIFactory
+import com.umeng.socialize.weixin.view.WXCallbackActivity
 
 /**
  * 微信分享回调 Activity
  * 
- * 按照微信官方 SDK 文档,需要创建此 Activity 处理微信分享回调
+ * 按照友盟官方示例,直接继承 WXCallbackActivity
  * 
  * 注意:
- * - 包名必须是应用包名(com.narutohuo.xindazhou.wxapi),不能是模块 namespace
+ * - 包名必须是微信开放平台配置的包名(com.mooxygen.user.wxapi)
  * - 微信 SDK 要求此 Activity 必须在应用包名的 wxapi 子包下
- * - 虽然文件在 base-core 模块中,但包名使用应用包名,这样 Android 清单合并后能正确识别
- * 
- * 架构说明:
- * - 使用 ARouter 依赖注入获取 IShareCallback 接口实现
- * - share 模块实现 IShareCallback 接口并通过 ARouter 注册
- * - base-core 不直接依赖 share 模块,实现解耦
- * - base-core 只依赖微信官方 SDK,不依赖友盟
- * 
- * 工作原理:
- * 1. 用户在微信中完成分享操作后,微信会通过 Intent 回调到此 Activity
- * 2. 通过微信 SDK 的 IWXAPIEventHandler 接口处理回调
- * 3. 通过 ARouter 获取 IShareCallback 实现,调用 share 模块的处理方法
- * 4. share 模块处理完成后,通过 ShareProxyActivity 的回调传递给业务层
+ * - 虽然文件在 base-core 模块中,但包名使用微信开放平台配置的包名
+ * - 友盟的 WXCallbackActivity 已经处理了所有回调逻辑,我们只需要继承即可
  */
-class WXEntryActivity : Activity(), IWXAPIEventHandler {
-    
-    private var api: IWXAPI? = null
-    
-    /**
-     * 通过 ARouter 依赖注入获取分享回调服务
-     * 
-     * 注意:share 模块需要实现 IShareCallback 接口并通过 @Route 注册
-     */
-    @Autowired(name = "/share/callback")
-    @JvmField
-    var shareCallback: IShareCallback? = null
-    
-    override fun onCreate(savedInstanceState: Bundle?) {
-        super.onCreate(savedInstanceState)
-        
-        // ARouter 依赖注入
-        ARouter.getInstance().inject(this)
-        
-        // 创建微信 API 实例(需要从 share 模块获取 AppId,这里先尝试处理回调)
-        // 注意:实际的微信 API 初始化应该在 share 模块中完成
-        // 这里只是接收回调并转发给 share 模块处理
-        
-        ILog.d("WXEntryActivity", "onCreate: 收到微信回调")
-        
-        // 调用 share 模块处理回调
-        shareCallback?.let {
-            ILog.d("WXEntryActivity", "通过 ARouter 获取到 IShareCallback 实现,调用 onWeChatCallback")
-            it.onWeChatCallback(intent)
-        } ?: run {
-            ILog.e("WXEntryActivity", "未找到 IShareCallback 实现,请确保 share 模块已注册")
-        }
-        
-        // 处理微信回调(通过 IWXAPIEventHandler)
-        handleIntent(intent)
-        
-        // 关闭 Activity
-        finish()
-    }
-    
-    override fun onNewIntent(intent: Intent?) {
-        super.onNewIntent(intent)
-        setIntent(intent)
-        ILog.d("WXEntryActivity", "onNewIntent: 收到新的微信回调 Intent")
-        
-        // 调用 share 模块处理回调
-        intent?.let {
-            shareCallback?.onWeChatCallback(it)
-            handleIntent(it)
-        }
-    }
-    
-    /**
-     * 处理微信回调 Intent
-     */
-    private fun handleIntent(intent: Intent?) {
-        if (intent == null) return
-        
-        // 尝试从 share 模块获取微信 API 实例
-        // 如果 share 模块已经初始化了微信 API,这里可以直接使用
-        // 否则,这里只转发 Intent 给 share 模块处理
-        
-        // 注意:微信 SDK 要求通过 IWXAPI.handleIntent 处理回调
-        // 但这里为了解耦,将 Intent 转发给 share 模块处理
-        // share 模块会使用自己的 IWXAPI 实例处理回调
-    }
-    
-    /**
-     * 微信请求回调
-     */
-    override fun onReq(req: BaseReq?) {
-        ILog.d("WXEntryActivity", "onReq: ${req?.type}")
-        // 微信请求通常不需要处理,主要是响应回调
-    }
-    
-    /**
-     * 微信响应回调
-     */
-    override fun onResp(resp: BaseResp?) {
-        ILog.d("WXEntryActivity", "onResp: type=${resp?.type}, errCode=${resp?.errCode}, errStr=${resp?.errStr}")
-        
-        when (resp?.type) {
-            ConstantsAPI.COMMAND_SENDAUTH -> {
-                // 微信登录回调
-                ILog.d("WXEntryActivity", "收到微信登录回调")
-            }
-            ConstantsAPI.COMMAND_SENDMESSAGE_TO_WX -> {
-                // 微信分享回调
-                ILog.d("WXEntryActivity", "收到微信分享回调")
-            }
-            ConstantsAPI.COMMAND_PAY_BY_WX -> {
-                // 微信支付回调
-                ILog.d("WXEntryActivity", "收到微信支付回调")
-            }
-        }
-        
-        // 将回调转发给 share 模块处理
-        shareCallback?.onWeChatCallback(intent)
-    }
-}
+class WXEntryActivity : WXCallbackActivity()
 

+ 9 - 2
capability-share/src/main/java/com/narutohuo/xindazhou/share/impl/ShareServiceImpl.kt

@@ -443,8 +443,15 @@ class ShareServiceImpl private constructor() : IProvider, IShareService, ShareSe
             
             // 配置微信平台
             config.weChatConfig?.let {
-                PlatformConfig.setWeixin(it.appId, it.appSecret)
-                ILog.d(tag, "微信平台配置完成")
+                // 如果 AppSecret 存在,使用 AppSecret;否则只传 AppId(仅分享功能可以不需要 AppSecret)
+                if (it.appSecret != null) {
+                    PlatformConfig.setWeixin(it.appId, it.appSecret)
+                    ILog.d(tag, "微信平台配置完成(AppId + AppSecret)")
+                } else {
+                    // 只配置 AppId,适用于仅分享功能(不涉及登录/授权)
+                    PlatformConfig.setWeixin(it.appId, "")
+                    ILog.d(tag, "微信平台配置完成(仅 AppId,适用于分享功能)")
+                }
             }
             
             // 配置QQ平台

+ 12 - 5
capability-share/src/main/java/com/narutohuo/xindazhou/share/model/ShareConfig.kt

@@ -69,8 +69,15 @@ data class ShareConfig(
             
             val weChatAppId = getStringResource("share_wechat_app_id")
             val weChatAppSecret = getStringResource("share_wechat_app_secret")
-            val weChatConfig = if (weChatAppId != null && weChatAppSecret != null) {
-                WeChatConfig(appId = weChatAppId, appSecret = weChatAppSecret)
+            // 如果 AppSecret 是占位符,视为未配置(可选)
+            val isAppSecretValid = weChatAppSecret != null && 
+                weChatAppSecret != "your_wechat_appsecret_here" && 
+                weChatAppSecret.isNotBlank()
+            val weChatConfig = if (weChatAppId != null) {
+                WeChatConfig(
+                    appId = weChatAppId, 
+                    appSecret = if (isAppSecretValid) weChatAppSecret else null
+                )
             } else null
             
             val qqAppId = getStringResource("share_qq_app_id")
@@ -102,14 +109,14 @@ data class ShareConfig(
  */
 data class WeChatConfig(
     /**
-     * 微信 AppID
+     * 微信 AppID(必需)
      */
     val appId: String,
     
     /**
-     * 微信 AppSecret
+     * 微信 AppSecret(可选,仅分享功能可以不需要,登录/授权功能需要)
      */
-    val appSecret: String
+    val appSecret: String? = null
 )
 
 /**

+ 185 - 0
capability-socketio/ARCHITECTURE_ANALYSIS.md

@@ -0,0 +1,185 @@
+# SocketIO 模块架构设计分析
+
+## 当前设计问题
+
+### ❌ 问题 1:能力层依赖业务层逻辑
+```kotlin
+// capability-socketio 通过回调注入业务层的刷新逻辑
+SocketIOManager.refreshTokenProvider = { AuthManager.refreshTokenIfNeeded() }
+```
+
+**问题:**
+- `capability-socketio` 是能力层,不应该依赖 `app/auth` 的业务逻辑
+- 回调注入的方式虽然避免了编译时依赖,但逻辑上仍然是依赖关系
+- 违反了"能力层只依赖基础设施,不依赖业务逻辑"的原则
+
+### ❌ 问题 2:职责混乱
+- SocketIO 模块既要负责连接,又要负责 Token 刷新
+- Token 刷新是业务逻辑,应该在业务层处理
+
+---
+
+## 市面上常规设计
+
+### ✅ 方案 1:能力层只读取 Token,不刷新(推荐)
+
+**原则:**
+- 能力层(capability)只负责技术能力(SocketIO 连接、消息收发)
+- 业务层(app/auth)负责业务逻辑(Token 刷新、登录状态管理)
+
+**实现:**
+```kotlin
+// capability-socketio:直接从存储读取 Token(基础设施)
+class SocketIORepository {
+    private fun getToken(): String? {
+        return StorageImpl.getString("access_token").takeIf { it.isNotEmpty() }
+    }
+    
+    suspend fun connect(): Result<Unit> {
+        val token = getToken()
+        if (token.isNullOrEmpty()) {
+            return Result.failure(Exception("未登录"))
+        }
+        // 直接使用 Token 连接,不负责刷新
+        return socketService.connect(token)
+    }
+}
+```
+
+**Token 刷新处理:**
+- HTTP 请求:由 `TokenRefreshInterceptor` 统一处理(已实现)
+- SocketIO 连接:如果连接失败(Token 过期),通过回调通知上层,由上层处理
+
+### ✅ 方案 2:接口抽象(如果必须刷新)
+
+**如果 SocketIO 必须在连接前刷新 Token:**
+
+```kotlin
+// base-core:定义接口(基础设施)
+interface ITokenProvider {
+    suspend fun getToken(): String?
+    suspend fun refreshTokenIfNeeded(): String?
+}
+
+// app/auth:实现接口
+object AuthManager : ITokenProvider {
+    override suspend fun getToken(): String? = TokenStore.getAccessToken()
+    override suspend fun refreshTokenIfNeeded(): String? { /* ... */ }
+}
+
+// capability-socketio:使用接口(不依赖具体实现)
+class SocketIORepository(
+    private val tokenProvider: ITokenProvider? = null  // 通过 DI 注入
+) {
+    suspend fun connect() {
+        val token = tokenProvider?.refreshTokenIfNeeded() 
+            ?: StorageImpl.getString("access_token")
+        // ...
+    }
+}
+```
+
+**优点:**
+- 能力层只依赖接口(base-core),不依赖业务层
+- 符合依赖倒置原则
+
+**缺点:**
+- 增加了接口抽象层,复杂度提升
+- SocketIO 通常不需要主动刷新 Token(连接失败时再处理即可)
+
+---
+
+## 推荐方案:简化设计
+
+### 核心原则
+1. **能力层只读取 Token,不刷新**
+2. **刷新统一由 Network 拦截器处理**
+3. **SocketIO 连接失败时,由上层处理重连**
+
+### 具体实现
+
+#### 1. capability-socketio:简化,移除刷新逻辑
+```kotlin
+class SocketIORepository {
+    // 直接从存储读取 Token(基础设施,可以依赖)
+    private fun getToken(): String? {
+        return StorageImpl.getString("access_token").takeIf { it.isNotEmpty() }
+    }
+    
+    suspend fun connect(): Result<Unit> {
+        val token = getToken()
+        if (token.isNullOrEmpty()) {
+            return Result.failure(Exception("未登录(Token 为空)"))
+        }
+        // 直接连接,不负责刷新
+        return socketService.connect(token)
+    }
+    
+    // 连接失败回调(通知上层处理)
+    var onConnectionFailed: ((Exception) -> Unit)? = null
+}
+```
+
+#### 2. app 层:处理连接失败和刷新
+```kotlin
+// UserViewModel 或专门的 SocketIO 管理类
+class SocketIOConnectionManager {
+    suspend fun connectWithRetry() {
+        var retryCount = 0
+        while (retryCount < 3) {
+            when (val result = SocketIOManager.connect()) {
+                is Result.Success -> return
+                is Result.Failure -> {
+                    val error = result.exception
+                    if (error.message?.contains("401") == true || 
+                        error.message?.contains("Token") == true) {
+                        // Token 可能过期,刷新后重试
+                        val newToken = AuthManager.refreshTokenIfNeeded()
+                        if (newToken != null) {
+                            retryCount++
+                            continue
+                        }
+                    }
+                    // 其他错误,直接失败
+                    break
+                }
+            }
+        }
+    }
+}
+```
+
+#### 3. Network 层:统一处理 HTTP 请求的 Token 刷新
+```kotlin
+// TokenRefreshInterceptor 已实现,统一处理 HTTP 请求的 401/402
+// SocketIO 不需要单独处理
+```
+
+---
+
+## 对比总结
+
+| 维度 | 当前设计 | 推荐设计 |
+|------|---------|---------|
+| **能力层职责** | 连接 + Token 刷新 | 只负责连接 |
+| **依赖关系** | 通过回调依赖业务层 | 只依赖基础设施(StorageImpl) |
+| **刷新逻辑** | SocketIO 自己刷新 | 统一由 Network 拦截器处理 |
+| **连接失败处理** | SocketIO 内部处理 | 上层(app)处理 |
+| **复杂度** | 高(能力层包含业务逻辑) | 低(职责清晰) |
+| **可测试性** | 低(依赖业务层) | 高(只依赖接口) |
+
+---
+
+## 结论
+
+**当前设计的问题:**
+- 能力层不应该包含业务逻辑(Token 刷新)
+- 通过回调注入业务逻辑,虽然避免了编译时依赖,但逻辑上仍然是依赖关系
+
+**推荐方案:**
+- 能力层只读取 Token,不刷新
+- Token 刷新统一由 Network 拦截器处理(HTTP 请求)
+- SocketIO 连接失败时,由上层(app)处理刷新和重连
+- 这样职责清晰,符合分层架构原则
+
+

+ 54 - 64
capability-socketio/src/main/java/com/narutohuo/xindazhou/socketio/SocketIOManager.kt

@@ -28,14 +28,15 @@ import kotlinx.coroutines.launch
  * ✅ 提供消息订阅接口(SharedFlow)
  * ✅ 外部只需要订阅即可
  * 
- * 使用方式:
+ * 使用方式(完全懒加载,自动处理连接)
  * ```kotlin
- * // 在 AppInitializer 中初始化(可选,会自动连接)
- * SocketIOManager.initialize(application) { isLoggedIn() } { refreshTokenIfNeeded() }
+ * // 在 AppInitializer 中设置回调
+ * SocketIOManager.isLoggedInProvider = { AuthManager.isLoggedIn() }
+ * SocketIOManager.refreshTokenProvider = { AuthManager.refreshTokenIfNeeded() }
  * 
- * // 在 ViewModel 中订阅消息
+ * // 在 ViewModel 中直接订阅消息(subscribe() 会自动处理连接,无需手动调用 ensureConnected())
  * viewModelScope.launch {
- *     SocketIOManager.shared.subscribe("community_message").collect { response ->
+ *     SocketIOManager.subscribe("community_message").collect { response ->
  *         // 处理社区消息
  *     }
  * }
@@ -45,20 +46,16 @@ object SocketIOManager : DefaultLifecycleObserver {
     
     private const val TAG = "SocketIOManager"
     
-    // 单例实例
-    val shared = SocketIOManager
-    
     private val repository = SocketIORepositoryFactory.getInstance()
     private val socketService = SocketIOServiceFactory.getInstance()
     private val appScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
     
-    private var application: Application? = null
     private var isInitialized = false
     
-    // 登录状态检查回调(由业务层设置)
+    // 登录状态检查回调(app 层设置)
     var isLoggedInProvider: (() -> Boolean)? = null
     
-    // Token 刷新回调(由业务层设置)
+    // Token 刷新回调(app 层设置)
     var refreshTokenProvider: (suspend () -> String?)? = null
     
     // 连接状态(使用 replay = 1 确保新订阅者能立即获取最新状态)
@@ -77,62 +74,49 @@ object SocketIOManager : DefaultLifecycleObserver {
     }
     
     /**
-     * 初始化并自动连接(如果用户已登录)
-     * 
-     * 在 AppInitializer 中调用一次即可
-     * 后续会自动处理连接、重连、Token 刷新等
+     * 确保已初始化(懒加载,第一次使用时自动初始化)
      * 
-     * @param application Application 实例(用于生命周期监听)
-     * @param isLoggedInProvider 登录状态检查回调
-     * @param refreshTokenProvider Token 刷新回调
+     * 在所有需要使用 SocketIO 功能的方法中调用
      */
-    fun initialize(
-        application: Application,
-        isLoggedInProvider: (() -> Boolean)? = null,
-        refreshTokenProvider: (suspend () -> String?)? = null
-    ) {
+    private fun ensureInitialized() {
         if (isInitialized) {
-            ILog.d(TAG, "已初始化,跳过")
             return
         }
         
-        this.application = application
-        this.isLoggedInProvider = isLoggedInProvider
-        this.refreshTokenProvider = refreshTokenProvider
-        isInitialized = true
-        
-        // 设置回调到底层 Manager
-        if (isLoggedInProvider != null || refreshTokenProvider != null) {
+        synchronized(this) {
+            if (isInitialized) {
+                return
+            }
+            
+            ILog.d(TAG, "SocketIOManager 懒加载初始化...")
+            
+            // 设置回调到底层 Manager
             com.narutohuo.xindazhou.socketio.manager.SocketIOManager.setCallbacks(
                 isLoggedInProvider = isLoggedInProvider,
                 refreshTokenProvider = refreshTokenProvider
             )
-        }
-        
-        // 注册 App 生命周期监听
-        try {
-            ProcessLifecycleOwner.get().lifecycle.addObserver(this)
-            ILog.d(TAG, "生命周期监听已注册")
-        } catch (e: Exception) {
-            ILog.e(TAG, "注册生命周期监听失败", e)
-        }
-        
-        // 初始化底层管理器
-        com.narutohuo.xindazhou.socketio.manager.SocketIOManager.init(application)
-        
-        // 如果用户已登录,自动连接
-        appScope.launch {
-            if (isLoggedInProvider?.invoke() == true) {
-                ILog.d(TAG, "用户已登录,自动连接 Socket.IO...")
-                ensureConnected()
-            } else {
-                ILog.d(TAG, "用户未登录,等待登录成功后连接")
-                // 更新连接状态为 false(用户未登录)
-                _connectionState.emit(false)
+            
+            // 注册 App 生命周期监听
+            try {
+                ProcessLifecycleOwner.get().lifecycle.addObserver(this)
+                ILog.d(TAG, "生命周期监听已注册")
+            } catch (e: Exception) {
+                ILog.w(TAG, "注册生命周期监听失败: ${e.message}")
+            }
+            
+            isInitialized = true
+            ILog.d(TAG, "SocketIOManager 懒加载初始化完成")
+            
+            // 如果设置了回调且用户已登录,自动连接
+            isLoggedInProvider?.let { provider ->
+                appScope.launch {
+                    if (provider.invoke()) {
+                        ILog.d(TAG, "用户已登录,自动连接 Socket.IO...")
+                        ensureConnected()
+                    }
+                }
             }
         }
-        
-        ILog.d(TAG, "SocketIOManager 初始化完成(自动处理连接和Token刷新)")
     }
     
     /**
@@ -143,6 +127,8 @@ object SocketIOManager : DefaultLifecycleObserver {
      * 如果未连接,会自动处理连接(包括 Token 刷新)
      */
     fun ensureConnected() {
+        ensureInitialized()  // 懒加载初始化
+        
         appScope.launch {
             // 检查是否已登录
             if (isLoggedInProvider?.invoke() != true) {
@@ -196,17 +182,19 @@ object SocketIOManager : DefaultLifecycleObserver {
      * - 如果未连接,会自动尝试连接
      * - 如果用户未登录,会等待登录成功后连接
      * 
-     * 示例:
-     * ```kotlin
-     * // CommunityViewModel.kt
-     * viewModelScope.launch {
-     *     SocketIOManager.shared.subscribe("community_message").collect { response ->
-     *         // 处理社区消息
-     *     }
-     * }
-     * ```
+ * 示例:
+ * ```kotlin
+ * // CommunityViewModel.kt
+ * viewModelScope.launch {
+ *     SocketIOManager.subscribe("community_message").collect { response ->
+ *         // 处理社区消息
+ *     }
+ * }
+ * ```
      */
     fun subscribe(eventName: String): SharedFlow<SocketIOResponse> {
+        ensureInitialized()  // 懒加载初始化
+        
         return eventFlows.getOrPut(eventName) {
             MutableSharedFlow<SocketIOResponse>(extraBufferCapacity = 1).also { flow ->
                 // 订阅 Socket.IO 事件
@@ -228,6 +216,8 @@ object SocketIOManager : DefaultLifecycleObserver {
      * 发送消息
      */
     fun emit(eventName: String, data: Any) {
+        ensureInitialized()  // 懒加载初始化
+        
         if (!isConnected()) {
             ILog.w(TAG, "Socket.IO 未连接,无法发送事件: $eventName,尝试重新连接...")
             appScope.launch {

+ 1 - 0
capability-socketio/src/main/java/com/narutohuo/xindazhou/socketio/factory/SocketIOServiceFactory.kt

@@ -33,6 +33,7 @@ object SocketIOServiceFactory {
      * 
      * @return SocketIOService实例(单例)
      */
+    @JvmStatic
     fun getInstance(): SocketIOService {
         return SocketIOServiceImpl.getInstance()
     }