ARCHITECTURE_ANALYSIS.md 5.8 KB

SocketIO 模块架构设计分析

当前设计问题

❌ 问题 1:能力层依赖业务层逻辑

// capability-socketio 通过回调注入业务层的刷新逻辑
SocketIOManager.refreshTokenProvider = { AuthManager.refreshTokenIfNeeded() }

问题:

  • capability-socketio 是能力层,不应该依赖 app/auth 的业务逻辑
  • 回调注入的方式虽然避免了编译时依赖,但逻辑上仍然是依赖关系
  • 违反了"能力层只依赖基础设施,不依赖业务逻辑"的原则

❌ 问题 2:职责混乱

  • SocketIO 模块既要负责连接,又要负责 Token 刷新
  • Token 刷新是业务逻辑,应该在业务层处理

市面上常规设计

✅ 方案 1:能力层只读取 Token,不刷新(推荐)

原则:

  • 能力层(capability)只负责技术能力(SocketIO 连接、消息收发)
  • 业务层(app/auth)负责业务逻辑(Token 刷新、登录状态管理)

实现:

// 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:

// 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:简化,移除刷新逻辑

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 层:处理连接失败和刷新

// 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 刷新

// TokenRefreshInterceptor 已实现,统一处理 HTTP 请求的 401/402
// SocketIO 不需要单独处理

对比总结

维度 当前设计 推荐设计
能力层职责 连接 + Token 刷新 只负责连接
依赖关系 通过回调依赖业务层 只依赖基础设施(StorageImpl)
刷新逻辑 SocketIO 自己刷新 统一由 Network 拦截器处理
连接失败处理 SocketIO 内部处理 上层(app)处理
复杂度 高(能力层包含业务逻辑) 低(职责清晰)
可测试性 低(依赖业务层) 高(只依赖接口)

结论

当前设计的问题:

  • 能力层不应该包含业务逻辑(Token 刷新)
  • 通过回调注入业务逻辑,虽然避免了编译时依赖,但逻辑上仍然是依赖关系

推荐方案:

  • 能力层只读取 Token,不刷新
  • Token 刷新统一由 Network 拦截器处理(HTTP 请求)
  • SocketIO 连接失败时,由上层(app)处理刷新和重连
  • 这样职责清晰,符合分层架构原则