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