# 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 { 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 { 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)处理刷新和重连 - 这样职责清晰,符合分层架构原则