#colleen

加载环境变量

package site.daydream.colleen.config

import io.github.cdimascio.dotenv.Dotenv
import site.daydream.colleen.logger
import java.io.File

/**
 * Environment configuration loader
 * 
 * Priority: System env > .env.local > .env.{profile} > .env
 */
class EnvLoader(
    private val profileKey: String = "COLLEEN_ENV",
    private val defaultProfile: String = "prod"
) {
    
    private val cache = mutableMapOf<String, String>()
    private var loaded = false
    
    fun load(profile: String? = null) {
        if (loaded) return
        
        val activeProfile = profile ?: getActiveProfile()
        
        // 这个日志很重要:防止生产环境配置错误
        logger.info("Active profile: $activeProfile")
        
        val files = listOf(".env", ".env.$activeProfile", ".env.local")
        
        for (fileName in files) {
            val file = File(System.getProperty("user.dir"), fileName)
            if (file.exists() && file.isFile) {
                loadFile(file, fileName)
            }
        }
        
        applyToSystem()
        loaded = true
    }
    
    operator fun get(key: String): String? {
        return System.getenv(key) ?: System.getProperty(key) ?: cache[key]
    }
    
    fun require(key: String): String {
        return checkNotNull(get(key)) { "Missing required variable: $key" }
    }
    
    fun getOrDefault(key: String, default: String): String = get(key) ?: default
    
    fun getActiveProfile(): String = get(profileKey) ?: defaultProfile
    
    fun isProfile(vararg profiles: String): Boolean {
        return profiles.any { it.equals(getActiveProfile(), ignoreCase = true) }
    }
    
    fun requireAll(vararg keys: String) {
        val missing = keys.filter { get(it).isNullOrBlank() }
        check(missing.isEmpty()) { "Missing variables: ${missing.joinToString()}" }
    }
    
    operator fun contains(key: String): Boolean = !get(key).isNullOrBlank()
    
    val isDev: Boolean get() = isProfile("dev", "development")
    val isProd: Boolean get() = isProfile("prod", "production")
    val isTest: Boolean get() = isProfile("test", "testing")
    
    private fun loadFile(file: File, fileName: String) {
        try {
            Dotenv.configure()
                .directory(file.parent)
                .filename(file.name)
                .ignoreIfMalformed()
                .ignoreIfMissing()
                .load()
                .entries()
                .forEach { (key, value) -> cache[key] = value }
        } catch (e: Exception) {
            // 这个日志很重要:文件存在但加载失败,可能是格式问题
            logger.warn("Failed to load $fileName: ${e.message}")
        }
    }
    
    private fun applyToSystem() {
        cache.forEach { (key, value) ->
            if (System.getenv(key) == null && System.getProperty(key) == null) {
                System.setProperty(key, value)
            }
        }
    }
}

object Env : EnvLoader()
site/daydream/colleen/
  ├── config/
  │   └── EnvLoader.kt
  ├── core/
  ├── http/
  └── ...