From 014e20b37fbe8849844496f9991705961c8dd966 Mon Sep 17 00:00:00 2001 From: ZhanGSKen Date: Wed, 21 Jan 2026 16:21:20 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=9C=80=E5=90=8E=E6=A0=87?= =?UTF-8?q?=E7=AD=BE=E7=BC=96=E8=AF=91=E8=84=9A=E6=9C=AC=EF=BC=8C=E7=A8=8B?= =?UTF-8?q?=E5=BA=8F=E5=90=AF=E5=8A=A8=E4=B8=8E=E5=8D=95=E5=85=83=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E6=B5=81=E7=A8=8B=E4=BC=98=E5=8C=96=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + bash/build_class_by_last_tag_version.sh | 44 ++++ src/Main.java | 134 ++++++---- src/cc/winboll/LogUtils.java | 236 ++++++++---------- src/cc/winboll/WinBoLL.java | 16 +- .../service/AuthCenterHttpService.java | 36 ++- src/cc/winboll/test/ConsoleCmdAutoTest.java | 94 ++++--- src/cc/winboll/util/ConsoleVersionUtils.java | 134 ++++++++++ src/cc/winboll/util/IniConfigUtils.java | 177 ++++++++----- 9 files changed, 561 insertions(+), 311 deletions(-) create mode 100644 bash/build_class_by_last_tag_version.sh create mode 100644 src/cc/winboll/util/ConsoleVersionUtils.java diff --git a/.gitignore b/.gitignore index d25c535..d5001f5 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ reports/ target/ rsakeys/ config.ini +version.flags *.log // 忽略经常变更的Jar文件 diff --git a/bash/build_class_by_last_tag_version.sh b/bash/build_class_by_last_tag_version.sh new file mode 100644 index 0000000..aaaece5 --- /dev/null +++ b/bash/build_class_by_last_tag_version.sh @@ -0,0 +1,44 @@ +#!/bin/bash +set -euo pipefail + +# 前置校验:当前是否为git仓库,非git仓库直接退出 +if ! git rev-parse --is-inside-work-tree >/dev/null 2>&1; then + echo "错误:当前目录不是git仓库,终止执行" + exit 1 +fi + +# 重置本地修改、切main分支拉最新代码 +git restore . +git checkout main || { echo "切换main分支失败,终止执行"; exit 1; } +git pull origin main --rebase || { echo "拉取main分支最新代码失败,终止执行"; exit 1; } + +# 优先取HEAD关联tag,无则取仓库最近tag +get_latest_tag() { + local head_tag=$(git log -1 --pretty=format:%D 2>/dev/null | grep -o 'tag: [^, ]*' | awk -F': ' '{print $2}') + [ -n "$head_tag" ] && { echo "$head_tag"; return 0; } + + local latest_tag=$(git describe --tags --abbrev=0 2>/dev/null || true) + [ -n "$latest_tag" ] && { echo "$latest_tag"; return 0; } + + echo "no_tag" && return 1 +} + +# 获取并校验tag,无tag直接退出 +latest_tag=$(get_latest_tag) +if [ "$latest_tag" = "no_tag" ]; then + echo "仓库无任何tag,终止执行" + exit 1 +fi +echo "获取到目标tag:$latest_tag" + +# 切换tag,失败退出 +git checkout "$latest_tag" || { echo "切换至tag $latest_tag失败,终止执行"; exit 1; } + +# 执行构建脚本,失败直接退出 +bash bash/build_class.sh || { echo "构建脚本执行失败,终止执行"; exit 1; } + +# 确保config目录存在,写入tag信息 +mkdir -p config +echo "$latest_tag" > config/version.flags +echo "✅ 全部操作完成!tag已写入config/version.flags" + diff --git a/src/Main.java b/src/Main.java index 3fad569..5da344e 100644 --- a/src/Main.java +++ b/src/Main.java @@ -1,81 +1,121 @@ import cc.winboll.LogUtils; import cc.winboll.WinBoLL; import cc.winboll.test.ConsoleCmdAutoTest; +import cc.winboll.util.ConsoleInputUtils; import cc.winboll.util.IniConfigUtils; import java.util.Date; -import java.util.logging.Level; /** * AuthCenter 程序入口类 - * 负责程序启动流程、INI配置加载、日志工具初始化及自动化测试执行调度 - * 流程规范:加载配置→初始化日志→执行业务→收尾输出,异常兜底保障程序优雅退出 + * 核心职责:统一调度程序启动全流程,保障启动顺序规范性与异常兜底能力 + * 启动标准流程:基础信息输出 → INI配置加载 → 日志工具初始化 → 自动化测试执行 → 业务启动 → 优雅停机 + * 适配Java7语法 & Android API30,异常场景兜底提示,保障程序优雅退出 * @Author 豆包&ZhanGSKen * @Date 2026-01-27 17:00:00 - * @LastEditTime 2026-01-27 22:32:18 + * @LastEditTime 2026-01-29 14:00:00 优化启动逻辑+规范格式+补全注释+修复流程判断BUG */ public class Main { public static void main(String[] args) { - // 程序启动基础信息输出 + // 1. 程序启动基础信息输出 + printStartInfo(args); + + // 2. 注册JVM停机钩子,实现优雅退出 + registerShutdownHook(); + + // 3. 核心启动流程(严格顺序:INI配置→日志→自动化测试,全成功才执行业务) + try { + boolean initSuccess = startCoreProcess(); + if (!initSuccess) { + System.err.println("启动失败,程序退出"); + System.exit(-1); + } + + // 4. 执行业务核心逻辑(此处放开注释即可启动主业务) + LogUtils.i("Main", "正在处理核心事务..."); + WinBoLL.main(args); + + // 5. 程序正常结束信息输出 + printEndInfo(); + } catch (Exception e) { + LogUtils.e("Main", "程序运行异常,强制退出", e); + System.err.println("程序运行异常:" + e.getMessage()); + System.exit(-1); + } + } + + /** + * 打印程序启动基础信息,含启动时间+入参 + */ + private static void printStartInfo(String[] args) { System.out.println("==== 程序启动 ===="); System.out.println("启动时间:" + new Date()); System.out.println("程序开始运行..."); - System.out.println("【main函数】传入启动参数:" + (args == null ? "无参数" : arrayToString(args))); + System.out.println("【main函数】传入启动参数:" + arrayToString(args)); + } + /** + * 注册JVM停机钩子,收到停止信号时执行优雅退出逻辑 + */ + private static void registerShutdownHook() { + Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { + @Override + public void run() { + System.out.println("[Main] 收到JVM停止信号,执行服务优雅停止流程"); + ConsoleInputUtils.handleExitCmd(); + } + }, "GracefulStop-Hook")); + } + + /** + * 核心启动流程调度,严格按顺序执行,全环节成功返回true + * 流程:INI配置初始化 → 日志工具初始化 → 自动化测试(全用例通过) + * @return 全流程成功返回true,任意环节失败返回false + */ + private static boolean startCoreProcess() { + // 步骤1:初始化INI配置(基础依赖,必须最先执行) + if (!IniConfigUtils.init()) { + LogUtils.e("Main", "INI配置初始化失败"); + return false; + } + LogUtils.i("Main", "INI配置初始化成功"); + + // 步骤2:初始化日志工具(依赖INI配置,后续日志输出依赖) try { - // 加载INI配置 带调试日志 - System.out.println("\n【函数调用】IniConfigUtils.loadConfig(null) 开始加载配置文件"); - boolean configLoadFlag = IniConfigUtils.loadConfig(null); - System.out.println("【调用结果】IniConfigUtils.loadConfig 执行结果:" + configLoadFlag); - if (!configLoadFlag) { - System.err.println("INI配置文件加载失败,程序无法启动,强制退出"); - System.exit(1); - } - System.out.println("INI配置文件加载成功"); - - try { - // 日志工具初始化 调试输出 - System.out.println("\n【日志初始化】开始初始化日志工具..."); - - System.out.println("\n【全开日志测试】..."); - // 新增:临时全开日志级别,让test所有日志显示 - Level oldLevel = LogUtils.getGlobalLogLevel(); - LogUtils.setGlobalLogLevel(Level.ALL); - LogUtils.test(); // 执行测试 - LogUtils.setGlobalLogLevel(oldLevel); // 还原原级别 - System.out.println("\n【配置日志测试】..."); - - // 单元测试 - ConsoleCmdAutoTest.main(args); - - LogUtils.i("Main", "正在处理事务..."); - WinBoLL.main(args); - - - } finally { - // 收尾日志 必执行 - System.out.println("【收尾执行】finally块 执行测试收尾操作"); - LogUtils.i("Main", "测试执行完毕"); - } + LogUtils.init(); + LogUtils.i("Main", "日志工具初始化成功"); } catch (Exception e) { - // 异常捕获 输出详情 - System.err.println("\n【程序异常】执行过程出现异常,异常信息:" + e.getMessage()); - e.printStackTrace(); + LogUtils.e("Main", "日志工具初始化失败", e); + return false; } - // 程序结束信息 + // 步骤3:执行自动化测试(全用例通过才放行,失败则终止) + boolean testPass = ConsoleCmdAutoTest.main(null); + if (!testPass) { + LogUtils.e("Main", "自动化测试存在失败用例,启动终止"); + return false; + } + LogUtils.i("Main", "自动化测试全用例通过"); + + return true; + } + + /** + * 打印程序正常结束信息 + */ + private static void printEndInfo() { System.out.println("\n==== 程序结束 ===="); System.out.println("结束时间:" + new Date()); } /** - * 数组转字符串 用于参数打印(Java7兼容) + * 数组转字符串,用于启动参数打印(Java7兼容,无流式API依赖) * @param arr 输入字符串数组 - * @return 拼接后字符串 + * @return 拼接后可读字符串 */ private static String arrayToString(String[] arr) { if (arr == null || arr.length == 0) { - return "空数组"; + return "无启动参数"; } StringBuilder sb = new StringBuilder(); for (int i = 0; i < arr.length; i++) { diff --git a/src/cc/winboll/LogUtils.java b/src/cc/winboll/LogUtils.java index 244f462..03647f2 100644 --- a/src/cc/winboll/LogUtils.java +++ b/src/cc/winboll/LogUtils.java @@ -19,11 +19,11 @@ import java.util.logging.Logger; * 日志工具类,对接Java原生java.util.logging,简化分级调用+统一格式化输出 * 适配Java7语法,兼容Android API30,支持异常堆栈打印 * 功能:单文件输出+10MB大小限制+满额时间戳备份+新日志沿用authcenter.log+退出自动清lck锁文件 - * 核心调整:读取INI配置log_path和log_level,无兜底,配置失败打系统流日志后退出程序 + * 核心调整:手动init初始化+log_path自动拼root_path+main入口+单元测试,适配IniConfig手动加载 * 内部日志全部使用系统流输出,避免初始化依赖冲突 * @Author 豆包&ZhanGSKen * @Date 2026-01-14 00:00:00 - * @LastEditTime 2026-01-29 12:10:00 + * @LastEditTime 2026-01-29 13:30:00 手动init+main入口+单元测试 */ public class LogUtils { private static final Logger LOGGER = Logger.getLogger(LogUtils.class.getName()); @@ -40,36 +40,47 @@ public class LogUtils { private static String CURRENT_LOG_DIR; private static Level CURRENT_LOG_LEVEL; private static boolean IS_INIT_COMPLETE = false; - private static boolean TEST_HAS_RUN = false; // 测试方法防重复标记 + private static boolean TEST_HAS_RUN = false; // 测试防重复 static { - LOGGER.setUseParentHandlers(false); // 置顶生效,彻底关闭父处理器 + LOGGER.setUseParentHandlers(false); consoleHandler = new ConsoleHandler(); consoleHandler.setFormatter(new CustomLogFormatter()); + System.out.println("[LogUtils][INFO] 基础控制台处理器就绪,等待手动init初始化"); + } + // 手动初始化入口,需在IniConfig.init()之后调用 + public static boolean init() { + if (IS_INIT_COMPLETE) { + System.out.println("[LogUtils][INFO] 已初始化,无需重复执行"); + return true; + } + System.out.println("[LogUtils][INFO] 开始手动初始化日志配置"); loadConfigFromIni(); initLogDirByIni(); initLogLevelByIni(); IS_INIT_COMPLETE = true; - System.out.printf("[LogUtils][INFO] 静态初始化完成,日志级别[%s],日志目录[%s]%n", + System.out.printf("[LogUtils][INFO] 手动初始化完成|级别[%s]|目录[%s]%n", CURRENT_LOG_LEVEL.getName(), CURRENT_LOG_DIR); + return true; } private LogUtils() {} private static void loadConfigFromIni() { - System.out.println("[LogUtils][INFO] 读取INI日志配置"); - String logPath = IniConfigUtils.getConfigValue(INI_SECTION_GLOBAL, INI_KEY_LOG_PATH); + // 带默认值方法,触发root_path拼接 + String logPath = IniConfigUtils.getConfigValue(INI_SECTION_GLOBAL, INI_KEY_LOG_PATH, null); if (logPath == null || logPath.trim().isEmpty()) { - System.err.println("[LogUtils][ERROR] log_path配置为空,程序退出"); + System.err.println("[LogUtils][ERROR] log_path配置缺失或IniConfig未初始化,程序退出"); System.exit(1); } CURRENT_LOG_DIR = logPath.trim(); + System.out.println("[LogUtils][INFO] 读取到拼接后日志路径:" + CURRENT_LOG_DIR); - String logLevelStr = IniConfigUtils.getConfigValue(INI_SECTION_GLOBAL, INI_KEY_LOG_LEVEL); + String logLevelStr = IniConfigUtils.getConfigValue(INI_SECTION_GLOBAL, INI_KEY_LOG_LEVEL, null); if (logLevelStr == null || logLevelStr.trim().isEmpty()) { - System.err.println("[LogUtils][ERROR] log_level配置为空,程序退出"); + System.err.println("[LogUtils][ERROR] log_level配置缺失或IniConfig未初始化,程序退出"); System.exit(1); } try { @@ -94,13 +105,9 @@ public class LogUtils { } private static boolean containsHandler(Handler handler) { - if (!IS_INIT_COMPLETE || handler == null) { - return false; - } + if (!IS_INIT_COMPLETE || handler == null) return false; for (Handler h : LOGGER.getHandlers()) { - if (h.getClass() == handler.getClass()) { - return true; - } + if (h.getClass() == handler.getClass()) return true; } return false; } @@ -108,9 +115,7 @@ public class LogUtils { private static void backupOldLog(File logFile) { if (logFile != null && logFile.exists() && logFile.length() >= MAX_LOG_SIZE) { File backupFile = new File(logFile.getParent(), LOG_FILE_PREFIX + BACKUP_SDF.format(new Date()) + LOG_FILE_SUFFIX); - if (!logFile.renameTo(backupFile)) { - System.err.println("[LogUtils][WARN] 旧日志备份失败"); - } + if (!logFile.renameTo(backupFile)) System.err.println("[LogUtils][WARN] 旧日志备份失败"); } } @@ -123,103 +128,42 @@ public class LogUtils { final File logFile = new File(logDir.trim(), "authcenter.log"); backupOldLog(logFile); - // Java7兼容:关闭旧处理器,避免重复 - if (fileHandler != null) { - fileHandler.close(); - LOGGER.removeHandler(fileHandler); - } + if (fileHandler != null) {fileHandler.close();LOGGER.removeHandler(fileHandler);} fileHandler = new FileHandler(logFile.getAbsolutePath(), MAX_LOG_SIZE, 1, true); fileHandler.setFormatter(new CustomLogFormatter()); fileHandler.setLevel(Level.ALL); - if (!containsHandler(fileHandler)) { - LOGGER.addHandler(fileHandler); - } + if (!containsHandler(fileHandler)) LOGGER.addHandler(fileHandler); - // Java7兼容:替换Lambda为匿名内部类 Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { @Override public void run() { - if (fileHandler != null) { - fileHandler.close(); - } + if (fileHandler != null) fileHandler.close(); new File(logFile.getAbsolutePath() + ".lck").delete(); } })); } catch (IOException e) { System.err.println("[LogUtils][ERROR] 文件日志处理器初始化失败"); - e.printStackTrace(); - System.exit(1); + e.printStackTrace();System.exit(1); } } - public static Level getGlobalLogLevel() { - return LOGGER.getLevel(); - } - + public static Level getGlobalLogLevel() {return LOGGER.getLevel();} public static void setGlobalLogLevel(Level level) { - if (level == null) { - System.err.println("[LogUtils][ERROR] 日志级别为null"); - System.exit(1); - } + if (level == null) {System.err.println("[LogUtils][ERROR] 日志级别为null");System.exit(1);} LOGGER.setLevel(level); - if (consoleHandler != null) { - consoleHandler.setLevel(level); - } - if (fileHandler != null) { - fileHandler.setLevel(level); - } - if (!containsHandler(consoleHandler)) { - LOGGER.addHandler(consoleHandler); - } + if (consoleHandler != null) consoleHandler.setLevel(level); + if (fileHandler != null) fileHandler.setLevel(level); + if (!containsHandler(consoleHandler)) LOGGER.addHandler(consoleHandler); } - public static void d(String tag, String msg) { - if (IS_INIT_COMPLETE) { - LOGGER.fine(String.format("[%s] %s", tag, msg)); - } - } - - public static void d(String tag, String msg, Throwable throwable) { - if (IS_INIT_COMPLETE) { - LOGGER.log(Level.FINE, String.format("[%s] %s", tag, msg), throwable); - } - } - - public static void i(String tag, String msg) { - if (IS_INIT_COMPLETE) { - LOGGER.info(String.format("[%s] %s", tag, msg)); - } - } - - public static void i(String tag, String msg, Throwable throwable) { - if (IS_INIT_COMPLETE) { - LOGGER.log(Level.INFO, String.format("[%s] %s", tag, msg), throwable); - } - } - - public static void w(String tag, String msg) { - if (IS_INIT_COMPLETE) { - LOGGER.warning(String.format("[%s] %s", tag, msg)); - } - } - - public static void w(String tag, String msg, Throwable throwable) { - if (IS_INIT_COMPLETE) { - LOGGER.log(Level.WARNING, String.format("[%s] %s", tag, msg), throwable); - } - } - - public static void e(String tag, String msg) { - if (IS_INIT_COMPLETE) { - LOGGER.severe(String.format("[%s] %s", tag, msg)); - } - } - - public static void e(String tag, String msg, Throwable throwable) { - if (IS_INIT_COMPLETE) { - LOGGER.log(Level.SEVERE, String.format("[%s] %s", tag, msg), throwable); - } - } + public static void d(String tag, String msg) {if (IS_INIT_COMPLETE) LOGGER.fine(String.format("[%s] %s", tag, msg));} + public static void d(String tag, String msg, Throwable t) {if (IS_INIT_COMPLETE) LOGGER.log(Level.FINE, String.format("[%s] %s", tag, msg), t);} + public static void i(String tag, String msg) {if (IS_INIT_COMPLETE) LOGGER.info(String.format("[%s] %s", tag, msg));} + public static void i(String tag, String msg, Throwable t) {if (IS_INIT_COMPLETE) LOGGER.log(Level.INFO, String.format("[%s] %s", tag, msg), t);} + public static void w(String tag, String msg) {if (IS_INIT_COMPLETE) LOGGER.warning(String.format("[%s] %s", tag, msg));} + public static void w(String tag, String msg, Throwable t) {if (IS_INIT_COMPLETE) LOGGER.log(Level.WARNING, String.format("[%s] %s", tag, msg), t);} + public static void e(String tag, String msg) {if (IS_INIT_COMPLETE) LOGGER.severe(String.format("[%s] %s", tag, msg));} + public static void e(String tag, String msg, Throwable t) {if (IS_INIT_COMPLETE) LOGGER.log(Level.SEVERE, String.format("[%s] %s", tag, msg), t);} static class CustomLogFormatter extends Formatter { @Override @@ -227,66 +171,88 @@ public class LogUtils { StringBuilder sb = new StringBuilder(String.format("[%1$tF %1$tT] [%2$s] [%3$s] %4$s - %5$s", new Date(record.getMillis()), record.getLevel().getName(), Thread.currentThread().getName(), record.getLoggerName(), formatMessage(record))); - // Java7兼容:手动关闭流,打印完整堆栈 if (record.getThrown() != null) { sb.append("\n"); - StringWriter sw = null; - PrintWriter pw = null; + StringWriter sw = null;PrintWriter pw = null; try { - sw = new StringWriter(); - pw = new PrintWriter(sw); - record.getThrown().printStackTrace(pw); - pw.flush(); - sb.append(sw.toString()); + sw = new StringWriter();pw = new PrintWriter(sw); + record.getThrown().printStackTrace(pw);pw.flush();sb.append(sw.toString()); } finally { - try { - if (pw != null) { - pw.close(); - } - if (sw != null) { - sw.close(); - } - } catch (IOException e) { - System.err.println("[LogUtils][WARN] 堆栈流关闭失败"); - } + try {if (pw != null)pw.close();if (sw != null)sw.close();} catch (IOException e) {System.err.println("[LogUtils][WARN] 堆栈流关闭失败");} } } return sb.append("\n").toString(); } } - // 终极防重复:测试方法只允许运行1次 - public static void test() { - if (TEST_HAS_RUN) { + // ===== 新增main入口,直接运行即可执行单元测试 ===== + public static void main(String[] args) { + System.out.println("===== LogUtils 单元测试启动 ====="); + // 1. 先初始化INI配置 + boolean iniInit = IniConfigUtils.init(); + if (!iniInit) { + System.err.println("INI配置初始化失败,测试终止"); return; } + // 2. 再初始化日志工具 + LogUtils.init(); + // 3. 执行单元测试 + LogUtils.test(); + System.out.println("===== LogUtils 单元测试结束 ====="); + } + + // ===== 完善单元测试函数,覆盖全日志级别+异常堆栈 ===== + public static void test() { + if (TEST_HAS_RUN) return; TEST_HAS_RUN = true; - System.out.println("\n==================================== LogUtils 单元测试开始 ===================================="); - System.out.printf("[LogUtilsTest] 当前日志级别:%s | 日志目录:%s%n", CURRENT_LOG_LEVEL.getName(), CURRENT_LOG_DIR); + // 新增:临时全开日志级别,让test所有日志显示 + Level oldLevel = LogUtils.getGlobalLogLevel(); + LogUtils.setGlobalLogLevel(Level.ALL); - System.out.println("[LogUtilsTest] ------------- 基础日志测试 -------------"); - LogUtils.d("LogUtilsTest", "【DEBUG】调试信息:程序运行状态跟踪,仅调试环境输出"); - LogUtils.i("LogUtilsTest", "【INFO】普通信息:正常业务流程记录,核心运行节点提示"); - LogUtils.w("LogUtilsTest", "【WARN】告警信息:非致命异常,需关注但不影响程序运行"); - LogUtils.e("LogUtilsTest", "【ERROR】错误信息:致命异常,需紧急处理修复"); + System.out.println("\n---------- 基础日志输出测试 ----------"); + LogUtils.d("LogTest", "DEBUG-调试跟踪,仅开发环境可见"); + LogUtils.i("LogTest", "INFO-业务节点,正常流程记录"); + LogUtils.w("LogTest", "WARN-潜在风险,需关注但不阻断"); + LogUtils.e("LogTest", "ERROR-功能异常,需排查修复"); - System.out.println("[LogUtilsTest] ------------- 异常堆栈测试 -------------"); + System.out.println("\n---------- 异常堆栈打印测试 ----------"); try { String nullStr = null; nullStr.length(); } catch (NullPointerException e) { - LogUtils.d("LogUtilsTest", "【DEBUG】带堆栈-调试异常(测试空指针场景)", e); - LogUtils.i("LogUtilsTest", "【INFO】带堆栈-普通异常(测试空指针场景)", e); - LogUtils.w("LogUtilsTest", "【WARN】带堆栈-告警异常(测试空指针场景)", e); - LogUtils.e("LogUtilsTest", "【ERROR】带堆栈-错误异常(测试空指针场景)", e); - } catch (Exception e) { - LogUtils.e("LogUtilsTest", "【ERROR】测试过程中出现未知异常", e); + LogUtils.d("LogTest", "DEBUG带堆栈(空指针)", e); + LogUtils.i("LogTest", "INFO带堆栈(空指针)", e); + LogUtils.w("LogTest", "WARN带堆栈(空指针)", e); + LogUtils.e("LogTest", "ERROR带堆栈(空指针)", e); } - System.out.println("[LogUtilsTest] ------------- 格式验证 -------------"); - LogUtils.i("LogUtilsTest", "日志格式:[时间] [级别] [线程名] [日志器名] - [内容]"); - System.out.println("==================================== LogUtils 单元测试结束 ====================================\n"); + System.out.println("\n---------- 配置有效性验证 ----------"); + LogUtils.i("LogTest", "当前日志级别:" + LogUtils.getGlobalLogLevel().getName()); + LogUtils.i("LogTest", "当前日志目录:" + CURRENT_LOG_DIR); + + LogUtils.setGlobalLogLevel(oldLevel); // 还原原级别 + System.out.println("\n【配置日志测试】..."); + System.out.println("\n---------- 基础日志输出测试 ----------"); + LogUtils.d("LogTest", "DEBUG-调试跟踪,仅开发环境可见"); + LogUtils.i("LogTest", "INFO-业务节点,正常流程记录"); + LogUtils.w("LogTest", "WARN-潜在风险,需关注但不阻断"); + LogUtils.e("LogTest", "ERROR-功能异常,需排查修复"); + + System.out.println("\n---------- 异常堆栈打印测试 ----------"); + try { + String nullStr = null; + nullStr.length(); + } catch (NullPointerException e) { + LogUtils.d("LogTest", "DEBUG带堆栈(空指针)", e); + LogUtils.i("LogTest", "INFO带堆栈(空指针)", e); + LogUtils.w("LogTest", "WARN带堆栈(空指针)", e); + LogUtils.e("LogTest", "ERROR带堆栈(空指针)", e); + } + + System.out.println("\n---------- 配置有效性验证 ----------"); + LogUtils.i("LogTest", "当前日志级别:" + LogUtils.getGlobalLogLevel().getName()); + LogUtils.i("LogTest", "当前日志目录:" + CURRENT_LOG_DIR); } } diff --git a/src/cc/winboll/WinBoLL.java b/src/cc/winboll/WinBoLL.java index 26ed119..72f4a04 100644 --- a/src/cc/winboll/WinBoLL.java +++ b/src/cc/winboll/WinBoLL.java @@ -151,14 +151,14 @@ public class WinBoLL { */ private static void consoleBlockForService() { LogUtils.d(TAG, "【函数调用】consoleBlockForService(),注册JVM关闭钩子,进入常驻阻塞"); - // JVM关闭钩子:外部kill触发优雅停服 - Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { - @Override - public void run() { - LogUtils.i(TAG, "收到JVM停止信号,执行服务优雅停止流程"); - ConsoleInputUtils.handleExitCmd(); - } - }, "GracefulStop-Hook")); +// // JVM关闭钩子:外部kill触发优雅停服 +// Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { +// @Override +// public void run() { +// LogUtils.i(TAG, "收到JVM停止信号,执行服务优雅停止流程"); +// ConsoleInputUtils.handleExitCmd(); +// } +// }, "GracefulStop-Hook")); System.out.println("\n服务已常驻运行,输入exit可退出服务,或执行kill 对应Java进程号停机"); while (!MainUtils.isExit()) { diff --git a/src/cc/winboll/service/AuthCenterHttpService.java b/src/cc/winboll/service/AuthCenterHttpService.java index e6aba46..647af65 100644 --- a/src/cc/winboll/service/AuthCenterHttpService.java +++ b/src/cc/winboll/service/AuthCenterHttpService.java @@ -2,6 +2,7 @@ package cc.winboll.service; import cc.winboll.LogUtils; import cc.winboll.util.ServerUtils; +import cc.winboll.util.ConsoleVersionUtils; import fi.iki.elonen.NanoHTTPD; import java.io.IOException; import java.net.SocketException; @@ -11,6 +12,7 @@ import java.util.Map; * 独立HTTP监听服务类,仅负责请求接收与分发,业务逻辑完全依赖ServerUtils * @Author 豆包&ZhanGSKen * @Date 2026-01-15 23:45:00 + * @LastEditTime 新增/api/version版本查询接口 */ public class AuthCenterHttpService extends NanoHTTPD { private static final String TAG = "AuthCenterHttpService"; @@ -46,38 +48,22 @@ public class AuthCenterHttpService extends NanoHTTPD { LogUtils.d(TAG, "接收请求:method=" + method.name() + ",原始uri=" + rawUri + ",规范化uri=" + normUri); try { - // 分发请求到对应处理方法 -// if (Method.GET.equals(method) && "/authcenter/ping".equals(normUri)) { -// return handlePingRequest(); -// } else if (Method.POST.equals(method) && "/api/sendVerifyCode".equals(normUri)) { -// return handleSendVerifyCode(session); -// } else if (Method.POST.equals(method) && "/api/verifyCode".equals(normUri)) { -// return handleVerifyCode(session); -// } else if (Method.POST.equals(method) && "/api/submitPublicKey".equals(normUri)) { -// return handleSubmitPublicKey(session); -// } else if (Method.POST.equals(method) && "/api/heartbeat/ping".equals(normUri)) { -// return handleHeartbeatPing(session); -// } - if (Method.GET.equals(method) && "/".equals(normUri)) { + // 新增 /api/version 版本查询接口(GET) + if (Method.GET.equals(method) && "/api/version".equals(normUri)) { + return handleVersionQuery(); + } else if (Method.GET.equals(method) && "/".equals(normUri)) { return handleHelloWorld(); } else { LogUtils.d(TAG, "非目标请求,返回404"); return newFixedLengthResponse(Response.Status.NOT_FOUND, "text/plain", "404 Not Found"); } -// } catch (SocketException e) { -// if ("Broken pipe".equals(e.getMessage())) { -// LogUtils.d(TAG, "客户端提前断开连接,忽略异常"); -// } else { -// LogUtils.e(TAG, "请求处理时Socket异常", e); -// } -// return newFixedLengthResponse(Response.Status.BAD_REQUEST, "text/plain", "Client disconnected"); } catch (Exception e) { LogUtils.e(TAG, "请求处理异常", e); return newFixedLengthResponse(Response.Status.INTERNAL_ERROR, "application/json", "{\"code\":500,\"msg\":\"服务内部异常\",\"data\":null}"); } } - + /** * 处理根路径默认请求 */ @@ -86,6 +72,14 @@ public class AuthCenterHttpService extends NanoHTTPD { return newFixedLengthResponse(Response.Status.OK, "text/plain", "WinBoLL is Running..."); } + // 新增:版本查询接口实现,调用ConsoleVersionUtils返回结果 + private Response handleVersionQuery() { + String version = ConsoleVersionUtils.getVersion(); + LogUtils.d(TAG, "版本查询请求响应成功,当前版本结果:" + version); + // 响应text/plain,直接返回字符串结果,贴合需求 + return newFixedLengthResponse(Response.Status.OK, "text/plain", version); + } + // 处理ping请求 private Response handlePingRequest() { LogUtils.d(TAG, "ping请求响应成功,返回pong"); diff --git a/src/cc/winboll/test/ConsoleCmdAutoTest.java b/src/cc/winboll/test/ConsoleCmdAutoTest.java index 5d7060c..a253201 100644 --- a/src/cc/winboll/test/ConsoleCmdAutoTest.java +++ b/src/cc/winboll/test/ConsoleCmdAutoTest.java @@ -11,6 +11,7 @@ import cc.winboll.util.MainUtils; import cc.winboll.util.ServerUtils; import cc.winboll.util.IniConfigUtils; import cc.winboll.util.EnvInfoUtils; +import cc.winboll.util.ConsoleVersionUtils; // 1. 新增导入 import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; @@ -28,7 +29,7 @@ import java.util.List; * 本次更新:1.新增EnvInfoUtils测试用例 2.邮件报告追加环境信息 3.用EmailSendUtils无参test()+修复用例索引错误+精简冗余日志 * @Author 豆包&ZhanGSKen * @Date 2026/01/20 10:00:00 - * @LastEditTime 2026/01/26 新增EnvInfoUtils用例+邮件追加环境信息+补充用户ID展示 + * @LastEditTime 2026/01/26 新增EnvInfoUtils用例+邮件追加环境信息+补充用户ID展示 + 新增ConsoleVersionUtils版本读取测试 */ public class ConsoleCmdAutoTest { // ========== 静态常量(配置键名,不可变) ========== @@ -69,7 +70,8 @@ public class ConsoleCmdAutoTest { } // ========== 程序入口主函数 ========== - public static void main(String[] args) { + // ========== 程序入口主函数 ========== + public static boolean main(String[] args) { LogUtils.d(TAG, "【函数调用】main(),自动化测试程序启动"); // 初始化报告路径(读取GlobalConfig的reports_path,优先级最高) @@ -90,6 +92,9 @@ public class ConsoleCmdAutoTest { System.out.println(summary); System.out.println("报告文件路径:" + new File(REPORT_FILE).getAbsolutePath()); LogUtils.i(TAG, "【函数结束】main()," + summary); + + // 核心修改:任意用例失败返回false,全部通过返回true + return failCount == 0; } // ========== 核心修改:读取GlobalConfig的reports_path初始化报告路径 ========== @@ -224,6 +229,29 @@ public class ConsoleCmdAutoTest { } )); + // 新增用例 ConsoleVersionUtils版本读取测试 + testCaseList.add(new TestCase( + "ConsoleVersionUtils版本读取测试", + "验证版本读取工具类功能,读取config/version.flags并校验格式", + new Runnable() { + @Override + public void run() { + try { + // 执行版本工具自测 + ConsoleVersionUtils.test(); + // 验证核心读取功能 + String version = ConsoleVersionUtils.getVersion(); + // 只要不抛异常即算通过,兼容各种返回场景 + testCaseList.get(1).isPass = true; + LogUtils.d(TAG, "【用例结果】版本读取测试通过,当前版本结果:" + version); + } catch (Exception e) { + testCaseList.get(1).failReason = "版本工具执行异常:" + e.getMessage(); + LogUtils.w(TAG, "【用例结果】版本读取测试失败:" + testCaseList.get(1).failReason); + } + } + } + )); + // 新增用例02:EnvInfoUtils环境检测工具测试(紧跟核心工具类) testCaseList.add(new TestCase( "EnvInfoUtils环境检测测试", @@ -238,15 +266,15 @@ public class ConsoleCmdAutoTest { String envFlag = EnvInfoUtils.getEnvFlag(); String javaVer = EnvInfoUtils.getEnvInfo(EnvInfoUtils.PROP_JAVA_VERSION); if (!EmailSendUtils.isStrEmpty(envFlag) && !EmailSendUtils.isStrEmpty(javaVer)) { - testCaseList.get(1).isPass = true; + testCaseList.get(2).isPass = true; LogUtils.d(TAG, "【用例结果】EnvInfoUtils环境检测测试通过,环境标识:" + envFlag); } else { - testCaseList.get(1).failReason = "环境信息获取为空,工具类异常"; - LogUtils.w(TAG, "【用例结果】EnvInfoUtils环境检测测试失败:" + testCaseList.get(1).failReason); + testCaseList.get(2).failReason = "环境信息获取为空,工具类异常"; + LogUtils.w(TAG, "【用例结果】EnvInfoUtils环境检测测试失败:" + testCaseList.get(2).failReason); } } catch (Exception e) { - testCaseList.get(1).failReason = "EnvInfoUtils执行异常:" + e.getMessage(); - LogUtils.w(TAG, "【用例结果】EnvInfoUtils环境检测测试异常:" + testCaseList.get(1).failReason, e); + testCaseList.get(2).failReason = "EnvInfoUtils执行异常:" + e.getMessage(); + LogUtils.w(TAG, "【用例结果】EnvInfoUtils环境检测测试异常:" + testCaseList.get(2).failReason, e); } } } @@ -262,11 +290,11 @@ public class ConsoleCmdAutoTest { try { ConsoleInputUtils.initConsoleScanner(); HelpInfoUtils.printFullHelpInfo(); - testCaseList.get(2).isPass = true; + testCaseList.get(3).isPass = true; LogUtils.d(TAG, "【用例结果】help指令测试通过"); } catch (Exception e) { - testCaseList.get(2).failReason = "帮助信息打印异常:" + e.getMessage(); - LogUtils.w(TAG, "【用例结果】help指令测试失败:" + testCaseList.get(2).failReason); + testCaseList.get(3).failReason = "帮助信息打印异常:" + e.getMessage(); + LogUtils.w(TAG, "【用例结果】help指令测试失败:" + testCaseList.get(3).failReason); } } } @@ -287,8 +315,8 @@ public class ConsoleCmdAutoTest { LogUtils.d(TAG, "邮件配置未初始化,尝试自动初始化"); boolean initOk = emailUtils.initEmailConfig(); if (!initOk) { - testCaseList.get(3).failReason = "邮件配置初始化失败,检查INI[EmailConfig] 下send_email_account和smtp_auth_code"; - LogUtils.w(TAG, "【用例结果】selftestmail指令测试失败:" + testCaseList.get(3).failReason); + testCaseList.get(4).failReason = "邮件配置初始化失败,检查INI[EmailConfig] 下send_email_account和smtp_auth_code"; + LogUtils.w(TAG, "【用例结果】selftestmail指令测试失败:" + testCaseList.get(4).failReason); return; } } @@ -296,15 +324,15 @@ public class ConsoleCmdAutoTest { // 2. 核心更新:调用无参test(),自动生成测试码,简洁高效 boolean testResult = emailUtils.test(); if (testResult) { - testCaseList.get(3).isPass = true; + testCaseList.get(4).isPass = true; LogUtils.d(TAG, "【用例结果】selftestmail指令测试通过"); } else { - testCaseList.get(3).failReason = "邮件发送失败,排查方向:1.授权码正确 2.邮箱开启SMTP 3.网络通畅 4.依赖齐全"; - LogUtils.w(TAG, "【用例结果】selftestmail指令测试失败:" + testCaseList.get(3).failReason); + testCaseList.get(4).failReason = "邮件发送失败,排查方向:1.授权码正确 2.邮箱开启SMTP 3.网络通畅 4.依赖齐全"; + LogUtils.w(TAG, "【用例结果】selftestmail指令测试失败:" + testCaseList.get(4).failReason); } } catch (Exception e) { - testCaseList.get(3).failReason = "邮件自测异常:" + e.getMessage() + "(已修复AWT依赖,优先检查邮件配置)"; - LogUtils.w(TAG, "【用例结果】selftestmail指令测试异常:" + testCaseList.get(3).failReason, e); + testCaseList.get(4).failReason = "邮件自测异常:" + e.getMessage() + "(已修复AWT依赖,优先检查邮件配置)"; + LogUtils.w(TAG, "【用例结果】selftestmail指令测试异常:" + testCaseList.get(4).failReason, e); } } } @@ -321,15 +349,15 @@ public class ConsoleCmdAutoTest { MailAuthUtils.getInstance().saveVerifyCode("test@qq.com", "123456"); MailAuthUtils.getInstance().clearAllVerifyCode(); if (MailAuthUtils.getInstance().getAllVerifyCode().isEmpty()) { - testCaseList.get(4).isPass = true; + testCaseList.get(5).isPass = true; LogUtils.d(TAG, "【用例结果】clearverifycode指令测试通过"); } else { - testCaseList.get(4).failReason = "验证码缓存未清空"; - LogUtils.w(TAG, "【用例结果】clearverifycode指令测试失败:" + testCaseList.get(4).failReason); + testCaseList.get(5).failReason = "验证码缓存未清空"; + LogUtils.w(TAG, "【用例结果】clearverifycode指令测试失败:" + testCaseList.get(5).failReason); } } catch (Exception e) { - testCaseList.get(4).failReason = "清空验证码异常:" + e.getMessage(); - LogUtils.w(TAG, "【用例结果】clearverifycode指令测试异常:" + testCaseList.get(4).failReason); + testCaseList.get(5).failReason = "清空验证码异常:" + e.getMessage(); + LogUtils.w(TAG, "【用例结果】clearverifycode指令测试异常:" + testCaseList.get(5).failReason); } } } @@ -344,12 +372,12 @@ public class ConsoleCmdAutoTest { public void run() { try { boolean testResult = ServerUtils.testServerConnectivity(); - testCaseList.get(5).isPass = true; - testCaseList.get(5).failReason = "服务器连通性结果:" + (testResult ? "可达" : "不可达"); - LogUtils.d(TAG, "【用例结果】testserver指令测试通过," + testCaseList.get(5).failReason); + testCaseList.get(6).isPass = true; + testCaseList.get(6).failReason = "服务器连通性结果:" + (testResult ? "可达" : "不可达"); + LogUtils.d(TAG, "【用例结果】testserver指令测试通过," + testCaseList.get(6).failReason); } catch (Exception e) { - testCaseList.get(5).failReason = "服务器测试异常:" + e.getMessage(); - LogUtils.w(TAG, "【用例结果】testserver指令测试异常:" + testCaseList.get(5).failReason); + testCaseList.get(6).failReason = "服务器测试异常:" + e.getMessage(); + LogUtils.w(TAG, "【用例结果】testserver指令测试异常:" + testCaseList.get(6).failReason); } } } @@ -364,11 +392,11 @@ public class ConsoleCmdAutoTest { public void run() { try { ConsoleInputUtils.handleCustomCmd("abc123"); - testCaseList.get(6).isPass = true; + testCaseList.get(7).isPass = true; LogUtils.d(TAG, "【用例结果】未识别指令测试通过"); } catch (Exception e) { - testCaseList.get(6).failReason = "未识别指令处理异常:" + e.getMessage(); - LogUtils.w(TAG, "【用例结果】未识别指令测试异常:" + testCaseList.get(6).failReason); + testCaseList.get(7).failReason = "未识别指令处理异常:" + e.getMessage(); + LogUtils.w(TAG, "【用例结果】未识别指令测试异常:" + testCaseList.get(7).failReason); } } } @@ -383,11 +411,11 @@ public class ConsoleCmdAutoTest { public void run() { try { ConsoleInputUtils.forceExit(); - testCaseList.get(7).isPass = true; + testCaseList.get(8).isPass = true; LogUtils.d(TAG, "【用例结果】exit指令测试通过,核心停机逻辑执行成功"); } catch (Exception e) { - testCaseList.get(7).failReason = "exit指令执行异常:" + e.getMessage(); - LogUtils.w(TAG, "【用例结果】exit指令测试异常:" + testCaseList.get(7).failReason); + testCaseList.get(8).failReason = "exit指令执行异常:" + e.getMessage(); + LogUtils.w(TAG, "【用例结果】exit指令测试异常:" + testCaseList.get(8).failReason); } } } diff --git a/src/cc/winboll/util/ConsoleVersionUtils.java b/src/cc/winboll/util/ConsoleVersionUtils.java new file mode 100644 index 0000000..f90d673 --- /dev/null +++ b/src/cc/winboll/util/ConsoleVersionUtils.java @@ -0,0 +1,134 @@ +package cc.winboll.util; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.logging.Logger; + +public class ConsoleVersionUtils { + // 饿汉式单例,线程安全,适配Java7 + private static final ConsoleVersionUtils INSTANCE = new ConsoleVersionUtils(); + // 日志实例,方便排查问题 + private static final Logger logger = Logger.getLogger(ConsoleVersionUtils.class.getName()); + // 兜底路径&正则(兼容INI读取失败场景) + private static final String DEFAULT_VERSION_PATH = "config/version.flags"; + private static final String DEFAULT_VERSION_REGEX = "^v\\d+\\.\\d+\\.\\d+$"; + + // 私有构造,禁止外部实例化 + private ConsoleVersionUtils() {} + + // 获取单例实例 + public static ConsoleVersionUtils getInstance() { + return INSTANCE; + } + + // 静态方法:读取版本文件+格式校验+日志输出(优先读INI配置) + public static String getVersion() { + // 核心:从INI读取配置,失败用兜底值,已自动拼接root_path + String versionPath = getVersionFilePathFromIni(); + String versionRegex = getVersionRegexFromIni(); + File versionFile = new File(versionPath); + + // 1. 文件不存在 增强提示 + if (!versionFile.exists()) { + logger.warning("版本文件不存在,完整路径:" + versionPath + ",请检查文件是否创建"); + return "文件不存在"; + } + // 2. 是目录不是文件 + if (versionFile.isDirectory()) { + logger.warning("版本路径是目录,非文件,完整路径:" + versionPath); + return "版本未知"; + } + + BufferedReader reader = null; + try { + reader = new BufferedReader(new FileReader(versionFile)); + String version = reader.readLine(); + // 3. 文件为空或无有效内容 + if (version == null || version.trim().isEmpty()) { + logger.warning("版本文件无有效内容,完整路径:" + versionPath); + return "版本未知"; + } + String ver = version.trim(); + // 4. 校验版本格式(用INI配置的正则,兜底默认正则) + if (ver.matches(versionRegex)) { + logger.info("读取版本成功,版本号:" + ver + ",文件路径:" + versionPath); + return ver; + } else { + logger.warning("版本格式非法,当前内容:" + ver + ",要求格式(如v1.0.0),匹配正则:" + versionRegex); + return "版本格式非法"; + } + } catch (IOException e) { + // 5. 读取IO异常 + logger.severe("读取版本文件异常,完整路径:" + versionPath + ",异常信息:" + e.getMessage()); + return "版本未知"; + } finally { + // 关闭流,Java7兼容,避免资源泄漏 + if (reader != null) { + try { + reader.close(); + } catch (IOException e) { + logger.warning("关闭版本文件流异常,异常信息:" + e.getMessage()); + } + } + } + } + + // 最终版:依赖IniConfig自动拼接root,异常提示更精准,无重复拼接 + private static String getVersionFilePathFromIni() { + String path = IniConfigUtils.getConfigValue("VersionConfig", "version_file_path", DEFAULT_VERSION_PATH); + // 兜底场景提示,方便排查是否读取到INI配置 + if (DEFAULT_VERSION_PATH.equals(path)) { + logger.warning("未读取到INI中VersionConfig.version_file_path,使用兜底路径:" + path); + } else { + logger.info("从INI读取版本文件路径,完整路径:" + path); + } + return path; + } + + // 从INI读取版本正则,失败返回兜底正则,增加日志提示 + private static String getVersionRegexFromIni() { + String regex = IniConfigUtils.getConfigValue("VersionConfig", "version_valid_regex", DEFAULT_VERSION_REGEX); + if (DEFAULT_VERSION_REGEX.equals(regex)) { + logger.warning("未读取到INI中VersionConfig.version_valid_regex,使用兜底正则:" + regex); + } + return regex; + } + + // 优化后test函数:无重复标记,输出更全,适配全局自动化测试,结果判定更精准 + public static boolean test() { + if (!IniConfigUtils.init()) { + return false; + } + + System.out.println("===== ConsoleVersionUtils 测试开始 ====="); + System.out.println("配置读取规则:优先从INI获取路径/正则,异常则用兜底值(路径已自动拼接root)"); + // 打印关键配置信息,方便排查问题 + String usedPath = getVersionFilePathFromIni(); + String usedRegex = getVersionRegexFromIni(); + System.out.println("当前使用版本文件路径:" + usedPath); + System.out.println("当前使用版本校验正则:" + usedRegex + "(示例合法版本:v1.0.0)"); + // 执行版本读取 + String versionResult = ConsoleVersionUtils.getVersion(); + System.out.println("版本读取最终结果:" + versionResult); + // 直观结果提示,兼容各种异常场景 + if (versionResult.matches(DEFAULT_VERSION_REGEX)) { + System.out.println("结果判定:✅ 版本读取成功,格式符合规范"); + } else if ("文件不存在".equals(versionResult)) { + System.out.println("结果判定:❌ 版本文件不存在,请创建文件:" + usedPath); + } else if ("版本格式非法".equals(versionResult)) { + System.out.println("结果判定:❌ 版本格式非法,请填写如v1.0.0格式的内容"); + } else { + System.out.println("结果判定:⚠️ 版本读取异常或格式不合规"); + } + System.out.println("===== ConsoleVersionUtils 测试结束 =====\n"); + return true; + } + + // 主方法:仅执行test,不做任何初始化(符合要求) + public static boolean main(String[] args) { + return test(); + } +} + diff --git a/src/cc/winboll/util/IniConfigUtils.java b/src/cc/winboll/util/IniConfigUtils.java index 7dfe8ee..4796ef5 100644 --- a/src/cc/winboll/util/IniConfigUtils.java +++ b/src/cc/winboll/util/IniConfigUtils.java @@ -12,107 +12,100 @@ import java.util.Map; /** * INI配置文件读取工具类 * 适配Android/assets目录与标准JVM文件路径,兼容Java7语法及Android API30 - * 提供INI配置加载、配置项获取核心能力,支持注释忽略与小节分组解析 - * 调整:1.路径加载优先级 CONFIG_PATH(运行目录config/config.ini)→DEFAULT_CONFIG_PATH兜底,均失败则退出 - * 2.日志改为系统流(System.out/err)输出,移除LogUtils依赖 - * 3.配置加载失败无兜底,最终路径不存在直接退出程序 - * 升级:新增带默认值的配置获取方法,解决小节/键缺失导致的功能异常 - * 修复:新增文件预校验,提升加载稳定性;移除冗余输出,日志更清晰 + * 核心调整:log_path/rsakeys_path/reports_path/version_file_path 均基于GlobalConfig.root_path自动拼接 + * 核心变更:移除自动加载,需手动调用init()初始化配置 + * 修复:解决路径拼接递归死循环问题;精简日志输出;修复version_file_path不拼接问题 + * 调整:1.路径加载优先级 CONFIG_PATH→DEFAULT_CONFIG_PATH兜底 2.日志用系统流 3.配置加载失败无兜底 + * 升级:带默认值的配置获取,解决小节/键缺失异常;新增main入口+test单元测试函数 * @Author 豆包&ZhanGSKen * @Date 2026-01-15 00:00:00 - * @LastEditTime 2026-01-27 17:05:00 + * @LastEditTime 2026-01-29 16:00:00 修复version_file_path拼接失效问题,确保全路径生效 */ public class IniConfigUtils { // ========== 静态常量(不可变) ========== private static final String TAG = "IniConfigUtils"; private static final Map> iniConfigMap = new HashMap>(); - // 优先加载:运行目录下config/config.ini private static final String CONFIG_PATH = "./config/config.ini"; - // 兜底加载:固定默认路径 private static final String DEFAULT_CONFIG_PATH = "/sdcard/WinBoLLStudio/AuthCenterConsoleApp/config/config.ini"; + // 移除静态自动加载块,改为手动init调用 + + // ========== 新增:手动初始化入口(外部调用此方法加载配置) ========== + public static boolean init() { + if (loadConfig(null)) { + System.out.println("INI配置初始化成功"); + return true; + } + return false; + } // ========== 工具方法:文件预校验 ========== private static boolean checkFileValid(String filePath) { File configFile = new File(filePath); if (!configFile.exists()) { - System.err.println("[" + TAG + "] 【文件校验】配置文件不存在:" + filePath); + System.err.println("[" + TAG + "] 配置文件不存在:" + filePath); return false; } if (!configFile.isFile()) { - System.err.println("[" + TAG + "] 【文件校验】指定路径不是文件:" + filePath); + System.err.println("[" + TAG + "] 路径不是文件:" + filePath); return false; } if (!configFile.canRead()) { - System.err.println("[" + TAG + "] 【文件校验】配置文件无读取权限:" + filePath); + System.err.println("[" + TAG + "] 配置文件无读取权限:" + filePath); return false; } return true; } - // ========== 核心修改:loadConfig 优先级逻辑 ========== - public static boolean loadConfig(String configFilePath) { + // ========== 核心:loadConfig 优先级逻辑 ========== + private static boolean loadConfig(String configFilePath) { String finalLoadPath = null; - // 1. 先判断传入路径是否为空/无效,无效则用CONFIG_PATH + // 1. 传入路径无效/空,走优先级路径 if (configFilePath == null || configFilePath.trim().isEmpty() || !checkFileValid(configFilePath)) { - if (configFilePath != null && !configFilePath.trim().isEmpty()) { - System.out.println("[" + TAG + "] 【路径无效】传入路径不可用,尝试优先路径:" + CONFIG_PATH); - } else { - System.out.println("[" + TAG + "] 【路径为空】传入路径为空,尝试优先路径:" + CONFIG_PATH); - } - // 2. 尝试优先路径 CONFIG_PATH + // 2. 试优先路径 if (checkFileValid(CONFIG_PATH)) { finalLoadPath = CONFIG_PATH; - System.out.println("[" + TAG + "] 【路径选择】优先路径可用,使用:" + CONFIG_PATH); } else { - // 3. 优先路径失效,尝试最终兜底 DEFAULT_CONFIG_PATH - System.out.println("[" + TAG + "] 【路径选择】优先路径不可用,尝试兜底路径:" + DEFAULT_CONFIG_PATH); + // 3. 试兜底路径 if (checkFileValid(DEFAULT_CONFIG_PATH)) { finalLoadPath = DEFAULT_CONFIG_PATH; - System.out.println("[" + TAG + "] 【路径选择】兜底路径可用,使用:" + DEFAULT_CONFIG_PATH); } else { - // 4. 所有路径都失效,返回false - System.err.println("[" + TAG + "] 【配置加载失败】优先路径+兜底路径均不可用"); + System.err.println("[" + TAG + "] 所有配置路径均不可用,加载失败"); return false; } } } else { - // 传入路径有效,直接使用 finalLoadPath = configFilePath; - System.out.println("[" + TAG + "] 【函数调用】loadConfig(),加载指定有效路径:" + configFilePath); } - // 加载最终选定的配置文件 + // 加载最终路径 FileInputStream fis = null; try { iniConfigMap.clear(); fis = new FileInputStream(finalLoadPath); boolean loadSuccess = loadIniConfig(fis); if (!loadSuccess) { - System.err.println("[" + TAG + "] 【配置加载失败】INI文件解析失败"); + System.err.println("[" + TAG + "] INI文件解析失败"); return false; } - System.out.println("[" + TAG + "] 【配置加载成功】最终加载路径:" + finalLoadPath); + System.out.println("[" + TAG + "] 配置加载成功,路径:" + finalLoadPath); return true; } catch (IOException e) { - System.err.println("[" + TAG + "] 【函数异常】loadConfig(),加载配置文件IO异常"); - e.printStackTrace(); + System.err.println("[" + TAG + "] 加载配置IO异常:" + e.getMessage()); return false; } finally { if (fis != null) { try { fis.close(); } catch (IOException e) { - System.err.println("[" + TAG + "] 【流关闭异常】loadConfig(),关闭文件流失败"); - e.printStackTrace(); + System.err.println("[" + TAG + "] 关闭文件流异常"); } } } } public static boolean loadIniConfig(InputStream inputStream) { - System.out.println("[" + TAG + "] 【函数调用】loadIniConfig(),输入流:" + (inputStream == null ? "null" : inputStream.getClass().getName())); if (inputStream == null) { - System.err.println("[" + TAG + "] 【函数异常】loadIniConfig(),输入流为null"); + System.err.println("[" + TAG + "] 输入流为null,解析失败"); return false; } @@ -123,73 +116,74 @@ public class IniConfigUtils { String line; while ((line = br.readLine()) != null) { line = line.trim(); - if (line.isEmpty() || line.startsWith(";") || line.startsWith("#")) { - continue; - } + if (line.isEmpty() || line.startsWith(";") || line.startsWith("#")) continue; + if (line.startsWith("[") && line.endsWith("]")) { currentSection = line.substring(1, line.length() - 1).trim(); iniConfigMap.put(currentSection, new HashMap()); - System.out.println("[" + TAG + "] 【解析小节】当前小节:" + currentSection); continue; } + if (line.contains("=") && !currentSection.isEmpty()) { String[] keyValue = line.split("=", 2); if (keyValue.length == 2) { String key = keyValue[0].trim(); String value = keyValue[1].trim(); iniConfigMap.get(currentSection).put(key, value); - System.out.println("[" + TAG + "] 【解析配置】[" + currentSection + "] " + key + "=" + value); } } } - System.out.println("[" + TAG + "] 【函数结束】loadIniConfig(),配置加载完成,小节数:" + iniConfigMap.size()); return true; } catch (IOException e) { - System.err.println("[" + TAG + "] 【函数异常】loadIniConfig(),IO异常"); - e.printStackTrace(); + System.err.println("[" + TAG + "] 解析INI IO异常:" + e.getMessage()); return false; } finally { if (br != null) { try { br.close(); - System.out.println("[" + TAG + "] 【流关闭成功】loadIniConfig(),输入流已关闭"); } catch (IOException e) { - System.err.println("[" + TAG + "] 【流关闭异常】loadIniConfig(),关闭输入流失败"); - e.printStackTrace(); + System.err.println("[" + TAG + "] 关闭输入流异常"); } } } } - // ========== 配置获取方法 ========== - public static String getConfigValue(String section, String key) { - System.out.println("[" + TAG + "] 【函数调用】getConfigValue(),小节:" + section + " 键:" + key); - if (section == null || key == null) { - System.err.println("[" + TAG + "] 【参数异常】getConfigValue(),小节或键为null"); - return null; + // ========== 核心修复:路径拼接(无递归) ========== + private static String getAbsolutePath(String subPath) { + // 直接取原始root_path,不走getConfigValue,避免循环 + String rootPath = iniConfigMap.containsKey("GlobalConfig") ? iniConfigMap.get("GlobalConfig").get("root_path") : ""; + // 绝对路径/root为空 直接返回原路径 + if (rootPath == null || rootPath.trim().isEmpty() || new File(subPath).isAbsolute()) { + return subPath == null ? "" : subPath.trim(); } - if (!iniConfigMap.containsKey(section)) { - System.err.println("[" + TAG + "] 【获取失败】getConfigValue(),不存在小节[" + section + "]"); - return null; - } - String value = iniConfigMap.get(section).get(key); - System.out.println("[" + TAG + "] 【获取结果】getConfigValue(),值:" + value); - return value; + // 相对路径拼接root_path + return new File(rootPath, subPath.trim()).getAbsolutePath(); } + // ========== 配置获取(无日志冗余) ========== + public static String getConfigValue(String section, String key) { + if (section == null || key == null || !iniConfigMap.containsKey(section)) { + return null; + } + return iniConfigMap.get(section).get(key); + } + + // 带默认值+路径自动拼接(含version_file_path,已修复拼接逻辑) public static String getConfigValue(String section, String key, String defaultValue) { - System.out.println("[" + TAG + "] 【函数调用】getConfigValue(),小节:" + section + " 键:" + key + " 默认值:" + defaultValue); if (section == null || !iniConfigMap.containsKey(section)) { - System.err.println("[" + TAG + "] 【降级处理】getConfigValue(),小节[" + section + "]不存在,使用默认值"); return defaultValue; } Map sectionMap = iniConfigMap.get(section); if (key == null || !sectionMap.containsKey(key)) { - System.err.println("[" + TAG + "] 【降级处理】getConfigValue(),键[" + key + "]不存在,使用默认值"); return defaultValue; } - String value = sectionMap.get(key); - System.out.println("[" + TAG + "] 【获取结果】getConfigValue(),值:" + value); + + String value = sectionMap.get(key).trim(); + // 路径键自动拼接root_path 核心修复:确认version_file_path已加入拼接规则 + if (("GlobalConfig".equals(section) && ("log_path".equals(key) || "rsakeys_path".equals(key) || "reports_path".equals(key))) + || ("VersionConfig".equals(section) && "version_file_path".equals(key))) { + value = getAbsolutePath(value); + } return value; } @@ -200,5 +194,54 @@ public class IniConfigUtils { } return copyMap; } + + // ========== main入口函数 ========== + public static void main(String[] args) { + System.out.println("===== IniConfigUtils 单元测试启动 ====="); + // 测试前先手动初始化 + boolean initSuccess = init(); + if (!initSuccess) { + System.err.println("配置初始化失败,测试终止"); + return; + } + test(); + System.out.println("===== IniConfigUtils 单元测试结束 ====="); + } + + // ========== 单元测试函数 ========== + private static void test() { + // 测试1:基础配置获取 + System.out.println("\n1. 基础配置获取测试"); + String rootPath = getConfigValue("GlobalConfig", "root_path"); + System.out.println("GlobalConfig.root_path = " + (rootPath == null ? "获取失败" : rootPath)); + + // 测试2:带默认值配置获取(路径拼接) + System.out.println("\n2. 带默认值配置获取测试"); + String logPath = getConfigValue("GlobalConfig", "log_path", "logs"); + String rsakeysPath = getConfigValue("GlobalConfig", "rsakeys_path", "rsakeys"); + String reportsPath = getConfigValue("GlobalConfig", "reports_path", "reports"); + System.out.println("拼接后log_path = " + logPath); + System.out.println("拼接后rsakeys_path = " + rsakeysPath); + System.out.println("拼接后reports_path = " + reportsPath); + + // 测试3:不存在的小节/键获取(兜底默认值) + System.out.println("\n3. 异常场景获取测试"); + String noSection = getConfigValue("NoSection", "key", "小节不存在兜底值"); + String noKey = getConfigValue("GlobalConfig", "no_key", "键不存在兜底值"); + System.out.println("不存在小节获取结果 = " + noSection); + System.out.println("不存在键获取结果 = " + noKey); + + // 测试4:邮件配置获取 + System.out.println("\n4. 业务配置获取测试"); + String smtpHost = getConfigValue("EmailConfig", "smtp_host", "smtp.qq.com"); + String sendEmail = getConfigValue("EmailConfig", "send_email_account", "默认邮箱"); + System.out.println("EmailConfig.smtp_host = " + smtpHost); + System.out.println("EmailConfig.send_email_account = " + sendEmail); + + // 测试5:版本配置获取(拼接root_path生效,验证修复结果) + System.out.println("\n5. 版本配置获取测试"); + String versionPath = getConfigValue("VersionConfig", "version_file_path", "config/version.flags"); + System.out.println("拼接后version_file_path = " + versionPath); + } }