claude

我来分析一下这个 Colleen 框架的代码,指出潜在问题并提供插件系统的设计方案。

现有代码的问题和改进点

1. 线程安全问题

// 当前代码
private val shutdownHooks = mutableListOf<Runnable>()

// 问题:多线程环境下 mutableList 不安全
// 建议改为:
private val shutdownHooks = CopyOnWriteArrayList<Runnable>()

2. 路由注册的线程安全

Router 类明确标注了"Not thread-safe",但在实际使用中可能需要动态添加路由。建议要么:

3. 异常处理的类型安全问题

@Suppress("UNCHECKED_CAST")
(handler as ErrorHandler<Exception>)(e, ctx)

这个强制转换存在潜在的类型不安全。可以改进泛型设计。

4. 配置修改时机

fun listen(port: Int = 8080, host: String = "127.0.0.1", callback: Runnable = Runnable {}) {
    if (host != config.server.host) config.server.host = host
    if (port != config.server.port) config.server.port = port
    // ...
}

listen 中修改配置可能导致混淆,建议要么在构造时固定配置,要么提供明确的配置方法。

5. 资源清理不完整

shutdown() 方法中没有清理 serviceContainer 和其他资源,可能导致内存泄漏。

6. Mount 路径处理

// TODO: 直接相加?
this.addRoute(it.method, basePath + it.path, handler)

路径拼接应该使用 PathUtils.normalize() 或专门的路径合并方法。

插件系统设计方案

方案一:事件驱动的插件系统(推荐 ⭐)

这是我最推荐的方案,平衡了灵活性、可扩展性和简洁性。

// ========================================================================
// 插件核心接口
// ========================================================================

/**
 * 插件生命周期钩子
 */
interface Plugin {
    /**
     * 插件名称(唯一标识)
     */
    val name: String
    
    /**
     * 插件初始化,在应用启动前调用
     */
    fun install(app: Colleen) {}
    
    /**
     * 应用启动后调用
     */
    fun onStarted(app: Colleen) {}
    
    /**
     * 应用关闭前调用
     */
    fun onShutdown(app: Colleen) {}
}

/**
 * 应用事件类型
 */
sealed class ApplicationEvent {
    data class BeforeRequest(val ctx: Context) : ApplicationEvent()
    data class AfterRequest(val ctx: Context) : ApplicationEvent()
    data class RouteRegistered(val method: String, val path: String) : ApplicationEvent()
    data class ErrorOccurred(val error: Throwable, val ctx: Context) : ApplicationEvent()
    data class ServerStarting(val host: String, val port: Int) : ApplicationEvent()
    data class ServerStarted(val host: String, val port: Int) : ApplicationEvent()
    data class ServerStopping() : ApplicationEvent()
}

/**
 * 事件监听器
 */
fun interface EventListener {
    fun onEvent(event: ApplicationEvent)
}

// ========================================================================
// Colleen 类扩展
// ========================================================================

class Colleen {
    // ... 现有代码 ...
    
    /**
     * 已安装的插件
     */
    private val plugins = mutableMapOf<String, Plugin>()
    
    /**
     * 事件监听器
     */
    private val eventListeners = ConcurrentHashMap<KClass<out ApplicationEvent>, 
                                                     MutableList<EventListener>>()
    
    /**
     * 安装插件
     */
    fun install(plugin: Plugin) {
        if (plugins.containsKey(plugin.name)) {
            throw IllegalStateException("Plugin '${plugin.name}' already installed")
        }
        plugins[plugin.name] = plugin
        plugin.install(this)
    }
    
    /**
     * 批量安装插件
     */
    fun install(vararg plugins: Plugin) {
        plugins.forEach { install(it) }
    }
    
    /**
     * 订阅事件
     */
    inline fun <reified T : ApplicationEvent> on(noinline listener: (T) -> Unit) {
        val listeners = eventListeners.getOrPut(T::class) { 
            Collections.synchronizedList(mutableListOf()) 
        }
        listeners.add { event -> 
            if (event is T) listener(event) 
        }
    }
    
    /**
     * 发布事件(内部使用)
     */
    internal fun emit(event: ApplicationEvent) {
        eventListeners[event::class]?.forEach { listener ->
            try {
                listener.onEvent(event)
            } catch (e: Exception) {
                logger.error("Event listener failed for ${event::class.simpleName}", e)
            }
        }
    }
    
    // 修改 listen 方法
    fun listen(port: Int = 8080, host: String = "127.0.0.1", callback: Runnable = Runnable {}) {
        // ... 现有代码 ...
        
        emit(ApplicationEvent.ServerStarting(host, port))
        
        try {
            server.start(createHttpHandler())
            
            // 通知插件应用已启动
            plugins.values.forEach { it.onStarted(this) }
            emit(ApplicationEvent.ServerStarted(host, port))
            
            callback.run()
        } catch (e: Exception) {
            // ... 错误处理 ...
        }
    }
    
    // 修改 handleRequest
    internal fun handleRequest(ctx: Context) {
        emit(ApplicationEvent.BeforeRequest(ctx))
        try {
            router.handleRequest(ctx)
            ctx.response.materialize(ctx)
            emit(ApplicationEvent.AfterRequest(ctx))
        } catch (e: Exception) {
            emit(ApplicationEvent.ErrorOccurred(e, ctx))
            // ... 现有错误处理 ...
        }
    }
    
    // 修改 shutdown
    private fun shutdown() {
        if (!shuttingDown.compareAndSet(false, true)) return
        
        emit(ApplicationEvent.ServerStopping())
        
        // 通知插件关闭
        plugins.values.reversed().forEach { 
            try {
                it.onShutdown(this)
            } catch (e: Exception) {
                logger.error("Plugin shutdown failed: ${it.name}", e)
            }
        }
        
        // ... 现有关闭逻辑 ...
    }
}

// ========================================================================
// 示例插件实现
// ========================================================================

/**
 * CORS 插件
 */
class CorsPlugin(
    private val allowedOrigins: List<String> = listOf("*"),
    private val allowedMethods: List<String> = listOf("GET", "POST", "PUT", "DELETE"),
    private val allowedHeaders: List<String> = listOf("*")
) : Plugin {
    override val name = "cors"
    
    override fun install(app: Colleen) {
        app.use { ctx, next ->
            ctx.header("Access-Control-Allow-Origin", allowedOrigins.joinToString(","))
            ctx.header("Access-Control-Allow-Methods", allowedMethods.joinToString(","))
            ctx.header("Access-Control-Allow-Headers", allowedHeaders.joinToString(","))
            
            if (ctx.method == "OPTIONS") {
                ctx.status(204)
            } else {
                next()
            }
        }
    }
}

/**
 * 日志插件
 */
class LoggingPlugin(
    private val logLevel: String = "INFO"
) : Plugin {
    override val name = "logging"
    
    override fun install(app: Colleen) {
        app.on<ApplicationEvent.BeforeRequest> { event ->
            val ctx = event.ctx
            logger.info("→ ${ctx.method} ${ctx.path}")
        }
        
        app.on<ApplicationEvent.AfterRequest> { event ->
            val ctx = event.ctx
            logger.info("← ${ctx.method} ${ctx.path} - ${ctx.response.status}")
        }
    }
}

/**
 * 健康检查插件
 */
class HealthCheckPlugin(
    private val endpoint: String = "/health"
) : Plugin {
    override val name = "health-check"
    
    override fun install(app: Colleen) {
        app.get(endpoint) { 
            mapOf("status" to "healthy", "timestamp" to System.currentTimeMillis())
        }
    }
}

/**
 * 请求限流插件
 */
class RateLimitPlugin(
    private val maxRequests: Int = 100,
    private val windowMs: Long = 60_000
) : Plugin {
    override val name = "rate-limit"
    
    private val requestCounts = ConcurrentHashMap<String, AtomicInteger>()
    private val windowStarts = ConcurrentHashMap<String, Long>()
    
    override fun install(app: Colleen) {
        app.use { ctx, next ->
            val clientId = ctx.ip()
            val now = System.currentTimeMillis()
            val windowStart = windowStarts.getOrPut(clientId) { now }
            
            if (now - windowStart > windowMs) {
                windowStarts[clientId] = now
                requestCounts[clientId] = AtomicInteger(0)
            }
            
            val count = requestCounts.getOrPut(clientId) { AtomicInteger(0) }
            if (count.incrementAndGet() > maxRequests) {
                throw HttpException(429, "Too Many Requests")
            }
            
            next()
        }
    }
}

// ========================================================================
// 使用示例
// ========================================================================

fun main() {
    val app = Colleen()
    
    // 安装插件
    app.install(
        CorsPlugin(),
        LoggingPlugin(),
        HealthCheckPlugin(),
        RateLimitPlugin(maxRequests = 100, windowMs = 60_000)
    )
    
    // 监听自定义事件
    app.on<ApplicationEvent.RouteRegistered> { event ->
        println("Route registered: ${event.method} ${event.path}")
    }
    
    app.get("/") { "Hello, World!" }
    
    app.listen(8080)
}

方案二:扩展点模式

适合需要更细粒度控制的场景。

/**
 * 扩展点接口
 */
interface Extension<T> {
    fun extend(target: T)
}

/**
 * 路由扩展点
 */
interface RouteExtension : Extension<Router> {
    override fun extend(target: Router)
}

/**
 * 配置扩展点
 */
interface ConfigExtension : Extension<Config> {
    override fun extend(target: Config)
}

class Colleen {
    private val routeExtensions = mutableListOf<RouteExtension>()
    private val configExtensions = mutableListOf<ConfigExtension>()
    
    fun extend(extension: RouteExtension) {
        routeExtensions.add(extension)
        extension.extend(router)
    }
    
    fun extend(extension: ConfigExtension) {
        configExtensions.add(extension)
        extension.extend(config)
    }
}

// 使用示例
class SwaggerExtension : RouteExtension {
    override fun extend(target: Router) {
        target.addRoute("GET", "/swagger-ui") { ctx ->
            // Swagger UI 实现
        }
    }
}

方案三:构建器模式插件

适合复杂配置场景。

abstract class PluginBuilder<T : Plugin> {
    abstract fun build(): T
}

interface Plugin {
    fun configure(app: Colleen)
}

class AuthPluginBuilder : PluginBuilder<AuthPlugin>() {
    var jwtSecret: String = ""
    var tokenExpiration: Long = 3600
    var excludePaths: List<String> = emptyList()
    
    override fun build() = AuthPlugin(jwtSecret, tokenExpiration, excludePaths)
}

fun Colleen.auth(block: AuthPluginBuilder.() -> Unit) {
    val plugin = AuthPluginBuilder().apply(block).build()
    install(plugin)
}

// 使用
app.auth {
    jwtSecret = "my-secret"
    tokenExpiration = 7200
    excludePaths = listOf("/login", "/register")
}

推荐方案总结

我最推荐方案一(事件驱动),原因:

  1. 灵活性高:插件可以监听多种事件,实现复杂功能

  2. 解耦性好:插件之间、插件与核心之间通过事件通信

  3. 易于扩展:新增功能只需添加新事件类型

  4. 符合直觉:开发者容易理解和使用

  5. 类似成熟框架:Koa.js、Express.js 都采用类似设计

实施建议

需要我详细实现某个部分吗?