All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.neko233.config233.Config233.kt Maven / Gradle / Ivy

The newest version!
package com.neko233.config233

import com.alibaba.fastjson2.JSON
import com.neko233.config233.annotation.*
import com.neko233.config233.listener.ConfigDataChangeListenerByHotUpdateField
import com.neko233.config233.listener.ConfigDataChangeListenerByHotUpdateMethod
import com.neko233.config233.reader.ConfigHandler
import com.neko233.config233.reader.api.Config233LifecycleApi
import com.neko233.config233.reader.dto.FrontEndConfigDto
import com.neko233.config233.reader.fileWatcher.FileWatchListener
import com.neko233.config233.reader.fileWatcher.FileWatcherForConfig233
import com.neko233.config233.repository.ConfigDataChangeListener
import com.neko233.config233.repository.ConfigDataRepository
import com.neko233.config233.utils.*
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.io.File
import java.lang.reflect.Field
import java.lang.reflect.ParameterizedType
import java.nio.file.Path
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicBoolean
import java.util.function.Function
import java.util.stream.Collectors

/**
 * 统一配置入口
 */
class Config233 {

    // 开始是否调用?
    private var startMethodCallFlag = AtomicBoolean(false)
    // 首次读取配置
    private var firstInitConfigFlag = AtomicBoolean(false)

    // 一个配置目录
    private var configDirPath: String = ""

    // 类扫描路径 jvm
    private var classPackageName: String = ""

    // 排除的文件名
    private var excludeFileNameSet: MutableSet = hashSetOf()

    // 热更新记录 class
    private val classToHotUpdateMap = ConcurrentHashMap, Boolean>()

    /**
     * Core] configData 仓库
     * 加载并 ORM 后的 data 都在这里
     */
    var configRepository: ConfigDataRepository = ConfigDataRepository()
        private set

    // 后缀名到处理类的映射 <"xlsx", XlsxHandler>
    private val fileExtensionToHandlerMap: MutableMap = mutableMapOf()

    companion object {
        @JvmStatic
        val LOGGER: Logger = LoggerFactory.getLogger(Config233::class.java)!!
    }

    /**
     * 添加配置文件处理器
     *
     * @param fileExtension 文件后缀名
     * @param handler 配置处理器
     */
    fun addConfigSuffixHandler(
        fileExtension: String,
        handler: ConfigHandler,
    ): Config233 {
        fileExtensionToHandlerMap[fileExtension] = handler
        return this
    }

    /**
     * 添加要排除掉的文件名
     * 如: logback.xml
     */
    fun addExcludeFileName(
        vararg fileNameArray: String,
    ): Config233 {
        for (fileName in fileNameArray) {
            excludeFileNameSet.add(fileName.trim())
        }
        return this
    }

    /**
     * 开始加载 + 监听
     */
    fun start(): Config233 {
        startMethodCallFlag.compareAndSet(false, true)

        if (StringUtilsForConfig233.isBlank(classPackageName)) {
            throw RuntimeException("没有配置要扫描的包名")
        }

        val configDir = File(configDirPath)
        // 不存在目录
        if (!configDir.exists()) {
            throw RuntimeException("配置目录不存在! config dir path = ${configDirPath}")
        }


        val packageName = classPackageName


        val configFileNameToPathMap: MutableMap = this.getFileNameToFullPathMapByRecursive(configDir)


        // 获取我的配置 class
        val configClassList = PackageScannerForConfig233.scanClass(packageName, true) { clazz ->
            if (clazz == Config233LifecycleApi::class.java) {
                return@scanClass false
            }
            return@scanClass clazz.getAnnotation(ConfigFileClass233::class.java) != null
        }

        // 配置名: 配置Class
        val configNameToClassMap = configClassList.stream()
            .collect(
                Collectors.toMap(
                    { clazz ->
                        val anno = clazz.getAnnotation(ConfigFileClass233::class.java)
                        if (anno != null) {
                            val configName = anno.value
                            if (StringUtilsForConfig233.isNotBlank(configName)) {
                                return@toMap configName
                            }
                        }
                        return@toMap clazz.simpleName
                    }, Function.identity()
                )
            )

        this.updateConfigToObject0(configNameToClassMap, configFileNameToPathMap)

        this.runConfigFileWatcherThread(configDirPath, configNameToClassMap, configFileNameToPathMap)

        firstInitConfigFlag.compareAndSet(false, true)

        return this
    }

    /**
     * 递归扫描目录, 含有名字的都去除掉
     */
    private fun getFileNameToFullPathMapByRecursive(configDir: File): MutableMap {
        val fileList: List = FileUtilsForConfig233.scanFiles(configDir, true)
            .stream()
            .collect(Collectors.toList())

        // 
        val configFileNameToPathMap: MutableMap = fileList
            .stream()
            .filter {
                val fileName = it.name
                val suffix = StringUtilsForConfig233.getFileExtensionName(fileName)
                // 必须要有后缀
                val configHandler: ConfigHandler = fileExtensionToHandlerMap.get(suffix)
                    ?: return@filter false
                for (excludeHandler in configHandler.excludeSimpleFileNameRegex()) {
                    val isNotNeed = excludeHandler.isNotNeed(fileName)
                    if (isNotNeed) {
                        return@filter false
                    }
                }

                // 不能含有过滤掉的文件名 (全名 )
                val isExclude = excludeFileNameSet.contains(fileName)
                if (isExclude) {
                    // 不能含有过滤掉的文件名 (全名 )
                    return@filter false
                }
                return@filter true
            }
            .collect(
                Collectors.toMap(
                    { file -> removeFileExtension(file.name) },
                    File::getPath,
                    { v1, v2 ->
                        error(
                            "duplicate config fileName! \nv1 = ${v1}, \nv2 = ${v2}"
                        )
                    }
                )
            )
        return configFileNameToPathMap
    }

    private fun runConfigFileWatcherThread(
        configDirPath: String,
        configNameToClassMap: MutableMap>,
        configFileNameToPathMap: MutableMap,
    ) {
        // 只关心以下文件路径
        val carefulFilePathSet: Set = HashSet(configFileNameToPathMap.values)

        ConsoleUtils.printYellow("------------------- [Config233] hot update /start -------------------")
        for (filePath in carefulFilePathSet) {
            ConsoleUtils.printYellow("[Config233] 对 file 进行热监听. filePath = ${filePath}")
        }
        ConsoleUtils.printYellow("------------------- [Config233] hot update /end -------------------")

        FileWatcherForConfig233.watchDirectory(configDirPath, carefulFilePathSet, object : FileWatchListener {
            override fun onFileCreate(filePath: Path) {
                hotUpdateFile0(filePath, configNameToClassMap, configFileNameToPathMap)
            }

            override fun onFileUpdate(filePath: Path) {
                hotUpdateFile0(filePath, configNameToClassMap, configFileNameToPathMap)
            }

            override fun onFileDelete(filePath: Path) {
                // ignored delete

            }
        })
    }

    private fun Config233.hotUpdateFile0(
        filePath: Path,
        configNameToClassMap: MutableMap>,
        configFileNameToPathMap: MutableMap
    ) {
        val file: File = filePath.toFile()

        val fileExtension = StringUtilsForConfig233.getFileExtensionName(file.path)
        // 配置处理器
        val configHandler: ConfigHandler = fileExtensionToHandlerMap[fileExtension]
            ?: return

        val tempSimpleFileName = removeFileExtension(file.name)
        val simpleFileName = configHandler.handleHotUpdateTempFileName(tempSimpleFileName)
        val clazz = configNameToClassMap[simpleFileName]
            ?: return


        // 目标文件全路径, 不允许变更位置
        val targetFilePath = configFileNameToPathMap[simpleFileName]
            ?: return

        try {
            this.ormConfigFileToObjList(clazz, simpleFileName, targetFilePath)
        } catch (e: Exception) {
            System.err.println("读取配置文件时候报错. class=${clazz.name}, fileName=${simpleFileName}, filePath = ${targetFilePath} \n" + e.printStackTrace())
        }

    }

    private fun updateConfigToObject0(
        configNameToClassMap: MutableMap>,
        configFileNameToPathMap: MutableMap,
    ) {
        for ((configName, clazz) in configNameToClassMap) {
            val filePath = configFileNameToPathMap[configName] ?: continue

            this.ormConfigFileToObjList(clazz, configName, filePath)

        }
    }


    /**
     * 翻译配置表格 -> objectList
     */
    private fun  ormConfigFileToObjList(
        clazz: Class,
        configName: String,
        filePath: String,
    ) {
        // 根据后缀, 获取处理器
        val fileExtension = StringUtilsForConfig233.getFileExtensionName(filePath)
        val configHandler = fileExtensionToHandlerMap[fileExtension]
        if (configHandler == null) {
            ConsoleUtils.printRed("没找到配置类")
            return
        }

        // core, 读取配置 -> dataList
        val dataList = configHandler.readConfigAndOrm(clazz, configName, filePath)

        this.postHandleDataAndSaveToConfigRepository0(clazz, dataList, configName, filePath)
    }

    private fun  postHandleDataAndSaveToConfigRepository0(
        clazz: Class,
        dataList: List,
        configName: String,
        filePath: String,
    ) {
        // lifecycle
        for (configFileClass in dataList) {
            // 如果有 lifecycle 接口
            if (configFileClass is Config233LifecycleApi) {
                configFileClass.afterLoad()

                configFileClass.check()
            }
        }

        // log
        val curDateTimeStr = DateTimeUtilsForConfig233.curDateTimeStr()
        ConsoleUtils.printGreen("$curDateTimeStr - [Config233] 监听文件变化! clazz=${clazz.name} fileName = ${configName}, absolutePath = ${filePath}")

        // 处理 dataList,根据需要进行其他操作
        configRepository.put(clazz, dataList)
    }

    private fun removeFileExtension(fileName: String): String {
        val lastDotIndex = fileName.lastIndexOf(".")
        return if (lastDotIndex > 0) {
            fileName.substring(0, lastDotIndex)
        } else {
            fileName
        }
    }

    fun directory(dirPath: String): Config233 {
        this.configDirPath = dirPath
        return this
    }

    /**
     * 配置目录
     */
    fun directory(file: File): Config233 {
        if (!file.exists()) {
            throw RuntimeException("dir/file not found = ${file.path}")
        }
        this.configDirPath = file.path
        return this
    }

    fun scanClassPackage(appClass: Class<*>): Config233 {
        this.classPackageName = appClass.`package`.name
        return this
    }

    fun scanClassPackage(packageName: String): Config233 {
        this.classPackageName = packageName
        return this
    }

    /**
     * 读取配置, 导出给前端数据
     *
     * @return 
     */
    fun readConfigToJsonForFrontEnd(): Map {
        val configMap = readFrontEndConfigDtoMap()
        val configNameToJsonMap = HashMap(configMap.size)
        for ((configName, configDto) in configMap) {
            val json = JSON.toJSONString(configDto.dataList)
            configNameToJsonMap.put(configName, json)
        }
        return configNameToJsonMap
    }

    /**
     * 读取成前端的配置数据 FrontEndConfigDto
     * 核心数据 =  List>
     *
     * @return 
     */
    fun readFrontEndConfigDtoMap(): HashMap {
        val configDir = File(configDirPath)
        // 不存在目录
        if (!configDir.exists()) {
            throw RuntimeException("配置目录不存在! config dir path = ${configDirPath}")
        }

        val configFileNameToPathMap: MutableMap = this.getFileNameToFullPathMapByRecursive(configDir)


        val configNameToDtoMap = HashMap()
        for ((configName, filePath) in configFileNameToPathMap) {
            // 根据后缀, 获取处理器
            val fileExtension = StringUtilsForConfig233.getFileExtensionName(filePath)
            val configHandler = fileExtensionToHandlerMap[fileExtension] ?: continue

            // core, 读取配置 -> dataList
            val configDto: FrontEndConfigDto = configHandler.readToFrontEndDataList(configName, filePath)

            configNameToDtoMap.put(configDto.configNameSimple, configDto)
        }

        return configNameToDtoMap
    }


    /**
     * get 配置列表
     */
    @Suppress("UNCHECKED_CAST")
    fun  getConfigList(clazz: Class): List {
        return configRepository.get(clazz)
    }


    /**
     * add 配置变更监听器
     */
    fun  addConfigChangeListener(
        clazz: Class,
        listener: ConfigDataChangeListener
    ): Config233 {
        this.configRepository.addConfigChangeListener(clazz, listener)
        return this
    }

    /**
     * remove 配置更新 callback
     */
    fun  removeConfigChangeListener(
        clazz: Class,
        listener: ConfigDataChangeListener
    ): Config233 {
        this.configRepository.removeConfigChangeListener(clazz, listener)
        return this
    }

    /**
     * remove all 配置更新 callback
     */
    fun  removeAllConfigChangeListener(
        clazz: Class,
    ): Config233 {
        this.configRepository.removeAllConfigChangeListener(clazz)
        return this
    }

    /**
     * 注册所有对象, 热更新内容
     */
    @Synchronized
    fun registerAllObjectForHotUpdate(
        objList: List,
        maxWaitMs: Long = TimeUnit.SECONDS.toMillis(5)
    ) {
        val configUidFieldAnnoName = ConfigUidMapInjectField233::class.java.simpleName
        val uidAnnoName = ConfigUid233::class.java.simpleName

        ConsoleUtils.printYellow("$configUidFieldAnnoName 需要使用在字段 Map<@$uidAnnoName Class, Class>")

        if (!startMethodCallFlag.get()) {
            throw RuntimeException("need first call config233.start() ! 需要先调用 start() 方法, 才能注册对象热更")
        }

        val startTime = System.currentTimeMillis()

        // 循环直到达到最大尝试时间或条件满足
        while (true) {
            // 经过的时间,单位为秒
            val elapsedTime = (System.currentTimeMillis() - startTime)
            if (elapsedTime >= maxWaitMs) {
                break
            }

            if (firstInitConfigFlag.get()) {
                ConsoleUtils.printBlue("config233 first init done. start to register obj for hot-update. 注册对象为了热更新 field, method")
                break
            }

            Thread.sleep(20) // 等待20毫秒
        }

        if (!firstInitConfigFlag.get()) {
            throw RuntimeException("注册热更新 obj, 但是到了时间仍然未初始化 config233, 请检查!")
        }


        for (obj in objList) {
            val objClass = obj::class.java
            if (objClass.getAnnotation(ConfigHotUpdateWatcherClass233::class.java) == null) {
                return
            }
            if (this.classToHotUpdateMap.contains(objClass)) {
                continue
            }
            this.classToHotUpdateMap.put(objClass, true)

            val fields = FieldUtilsForConfig233.getAllFields(obj)
            for (field: Field in fields) {
                // 检查字段是否标记了特定的注解
                if (!field.isAnnotationPresent(ConfigUidMapInjectField233::class.java)) {
                    continue
                }
                // 检查字段类型是否为 Map
                val fieldType = field.genericType
                if (fieldType is ParameterizedType) {
                    val keyType = fieldType.actualTypeArguments[0]
                    val configClass = fieldType.actualTypeArguments[1]

                    if (keyType is Class<*> && configClass is Class<*>) {
                        if (Map::class.java.isAssignableFrom(field.type)) {
                            // 获取唯一 id Map
                            val uidMap = configRepository.getUidMapByAnnotation(configClass)

                            // 设置字段可访问性
                            field.isAccessible = true

                            try {
                                field.set(obj, uidMap)
                            } catch (e: Throwable) {
                                throw RuntimeException("field 类型不是 Map", e)
                            }

                            // callback
                            val listener = ConfigDataChangeListenerByHotUpdateField(this, obj, field, configClass)
                            addConfigChangeListener(configClass, listener)
                        }
                    }
                } else {

                    throw RuntimeException("$configUidFieldAnnoName 需要使用在字段 Map<@$uidAnnoName Class, Class>")
                }
            }

            val allMethods = MethodUtilsForConfig233.getAllMethods(obj)
            for (method in allMethods) {
                val annotation = method.getAnnotation(ConfigHotUpdateInvokeMethod233::class.java)
                    ?: continue

                val value = annotation.value
                for (kClass in value) {
                    val clazz = kClass.java

                    // callback
                    val listener = ConfigDataChangeListenerByHotUpdateMethod(this, obj, method)
                    addConfigChangeListener(clazz, listener)
                }
            }


        }
    }

    fun getConfigUidMap(configClass: Class<*>): Map {
        return configRepository.getUidMapByAnnotation(configClass)
    }

}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy