优雅停机,完美实现。

This commit is contained in:
2026-01-17 05:39:38 +08:00
parent 3e65fece7e
commit e8a700c2ef
8 changed files with 269 additions and 552 deletions

View File

@@ -50,20 +50,3 @@ echo -e "📝 Java日志输出路径$MAIN_LOG\n"
# 关键无任何重定向让Java的LogUtils负责写日志终端保留完整交互能力
java -cp "$CLASSPATH" "$MAIN_CLASS" -v -log:ALL
JAVA_PID=$!
EXIT_CODE=$?
# 7. 等待Java进程结束解决卡空行问题
wait $JAVA_PID
# 8. 后置处理
stty sane
if [ $STOP_SIGNAL -eq 0 ]; then
if [ $EXIT_CODE -eq 0 ]; then
echo -e "\n✅ 服务优雅停机完成"
else
echo -e "\n❌ 服务异常退出,退出码:$EXIT_CODE查看Java日志$MAIN_LOG"
fi
fi
exit $EXIT_CODE

View File

@@ -1,65 +1,64 @@
package cc.winboll;
import cc.winboll.LogUtils;
import cc.winboll.service.AuthService;
import cc.winboll.util.ServerUtils;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Scanner;
/**
* 鉴权中心控制台应用入口类
* 负责初始化核心组件、提供控制台交互能力、启停鉴权服务、授权码生成与校验
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
* @Date 2026/01/15 18:00:00
* @LastEditTime 2026/01/15 20:20:00
* @Date 2026-01-15 18:00:00
* @LastEditTime 2026-01-23 21:46:00
*/
public class AuthCenterConsoleApp {
// ========== 静态属性(按常量→变量排序) ==========
private static final String TAG = "AuthCenterConsoleApp";
// 默认服务器地址(可通过控制台输入修改)
private static final String DEFAULT_SERVER_URL = "http://localhost:8080";
private static final int SERVER_PORT = 8080;
private static Scanner scanner;
private static boolean isServiceRunning = false;
// ========== 入口方法 ==========
public static void main() {
// 1. 初始化核心工具与控制台输入
LogUtils.d(TAG, "【入口调用】main(),鉴权中心控制台应用启动");
initConsoleEnv();
// 2. 初始化服务器地址与鉴权核心服务
initAuthCenterCore();
// 3. 启动控制台交互循环
startConsoleInteractive();
LogUtils.d(TAG, "【入口结束】main(),鉴权中心控制台应用退出");
}
// ========== 初始化相关函数(按执行顺序排列) ==========
/**
* 初始化控制台基础环境(日志、输入流)
*/
private static void initConsoleEnv() {
LogUtils.d(TAG, "【函数调用】initConsoleEnv(),开始初始化控制台基础环境");
scanner = new Scanner(System.in);
LogUtils.d(TAG, "控制台环境初始化完成,输入流已就绪");
System.out.println("===== AuthCenter 鉴权中心控制台启动 =====");
LogUtils.d(TAG, "【函数结束】initConsoleEnv(),控制台输入流初始化就绪");
}
/**
* 初始化鉴权核心组件(服务器地址+AuthService服务,补充地址合法性校验
* 初始化鉴权核心组件(自动拼接本机IP+8080,补充地址合法性校验
*/
private static void initAuthCenterCore() {
// 可选:控制台输入服务器地址,默认使用预设地址
System.out.printf("请输入服务器地址(默认:%s%n", DEFAULT_SERVER_URL);
String serverUrl = scanner.nextLine().trim();
serverUrl = serverUrl.isEmpty() ? DEFAULT_SERVER_URL : serverUrl;
LogUtils.d(TAG, "【函数调用】initAuthCenterCore(),开始初始化鉴权核心组件");
String serverUrl = getLocalHostIpWithPort();
System.out.printf("当前自动适配本机服务地址:%s%n", serverUrl);
System.out.println("直接回车确认使用,也可手动输入自定义地址:");
String customUrl = scanner.nextLine().trim();
LogUtils.d(TAG, "【参数信息】initAuthCenterCore(),用户输入自定义地址:" + customUrl);
// 服务器地址合法性校验,自动补全协议头,适配服务器端输入习惯
serverUrl = customUrl.isEmpty() ? serverUrl : customUrl;
if (!serverUrl.startsWith("http://") && !serverUrl.startsWith("https://")) {
System.out.println("⚠️ 服务器地址缺少http/https协议头自动补充http://前缀");
serverUrl = "http://" + serverUrl;
LogUtils.w(TAG, "服务器地址格式不合法,自动补全" + serverUrl);
LogUtils.w(TAG, "【参数修正】initAuthCenterCore(),地址补全" + serverUrl);
}
// 初始化服务器交互工具
ServerUtils.initServerUrl(serverUrl);
// 初始化鉴权核心服务
AuthService.init();
isServiceRunning = true;
LogUtils.i(TAG, "鉴权核心组件初始化完成,服务已启动");
LogUtils.i(TAG, "【函数结束】initAuthCenterCore(),鉴权核心组件初始化完成");
System.out.println("✅ 服务器地址初始化:" + serverUrl);
System.out.println("✅ 鉴权服务初始化完成,当前状态:运行中");
System.out.println("提示:输入 help 可查看所有支持指令");
@@ -67,105 +66,91 @@ public class AuthCenterConsoleApp {
}
/**
* 启动控制台交互逻辑,新增生成/校验授权码指令,完善指令体系
* 自动获取本机IP 拼接8080端口带异常兜底
*/
private static String getLocalHostIpWithPort() {
LogUtils.d(TAG, "【函数调用】getLocalHostIpWithPort()开始获取本机IP拼接端口");
String localIp;
try {
localIp = InetAddress.getLocalHost().getHostAddress();
LogUtils.d(TAG, "【参数信息】getLocalHostIpWithPort()获取本机IP成功" + localIp);
} catch (UnknownHostException e) {
LogUtils.e(TAG, "【异常兜底】getLocalHostIpWithPort()获取本机IP失败使用127.0.0.1", e);
localIp = "127.0.0.1";
System.out.println("⚠️ 获取本机IP失败兜底使用回环地址127.0.0.1");
}
String serverUrl = "http://" + localIp + ":" + SERVER_PORT;
LogUtils.d(TAG, "【函数结束】getLocalHostIpWithPort(),拼接后服务地址:" + serverUrl);
return serverUrl;
}
// ========== 控制台交互相关函数 ==========
/**
* 启动控制台交互逻辑,指令分发处理
*/
private static void startConsoleInteractive() {
LogUtils.d(TAG, "【函数调用】startConsoleInteractive(),启动控制台交互循环");
while (true) {
System.out.print("AuthCenter > ");
String input = scanner.nextLine().trim();
if (input.isEmpty()) {
continue;
}
// 指令分发,支持大小写忽略
switch (input.toLowerCase()) {
case "exit":
stopAuthService();
break;
case "start":
handleStartService();
break;
case "status":
handleQueryStatus();
break;
case "gen":
handleGenerateAuthCode();
break;
case "check":
handleValidateAuthCode();
break;
case "help":
printHelpInfo();
break;
default:
System.out.println("❌ 无效指令,输入 help 查看所有支持指令");
LogUtils.w(TAG, "控制台收到无效指令:" + input);
break;
}
LogUtils.d(TAG, "【参数信息】startConsoleInteractive(),控制台接收指令:" + input);
dispatchConsoleCmd(input.toLowerCase());
}
}
/**
* 指令分发内部函数,精简主函数逻辑
*/
private static void dispatchConsoleCmd(String cmd) {
switch (cmd) {
case "exit":
stopAuthService();
break;
case "start":
handleStartService();
break;
case "status":
handleQueryStatus();
break;
case "help":
printHelpInfo();
break;
default:
System.out.println("❌ 无效指令,输入 help 查看所有支持指令");
LogUtils.w(TAG, "【指令异常】dispatchConsoleCmd(),未识别指令:" + cmd);
break;
}
}
// ========== 指令处理相关函数(按指令名称排序) ==========
/**
* 处理鉴权服务启动指令
*/
private static void handleStartService() {
if (isServiceRunning) {
System.out.println("⚠️ 鉴权服务已处于运行状态,无需重复启动");
return;
}
AuthService.init();
isServiceRunning = true;
LogUtils.d(TAG, "【函数调用】handleStartService(),执行鉴权服务启动指令");
System.out.println("✅ 鉴权服务重新启动成功");
LogUtils.i(TAG, "控制台指令触发,鉴权服务重启");
LogUtils.i(TAG, "【函数结束】handleStartService(),鉴权服务重启完成");
}
/**
* 处理状态查询指令
*/
private static void handleQueryStatus() {
String serviceStatus = isServiceRunning ? "运行中" : "已停止";
LogUtils.d(TAG, "【函数调用】handleQueryStatus(),执行服务状态查询指令");
String serverUrl = ServerUtils.getServerBaseUrl() == null ? "未初始化" : ServerUtils.getServerBaseUrl();
System.out.printf("【当前状态】%n");
System.out.printf(" 鉴权服务:%s%n", serviceStatus);
System.out.printf(" 服务器地址:%s%n", serverUrl);
LogUtils.d(TAG, "控制台执行状态查询,服务状态=" + serviceStatus + ",服务器地址=" + serverUrl);
}
/**
* 处理授权码生成指令
*/
private static void handleGenerateAuthCode() {
if (!isServiceRunning) {
System.out.println("❌ 鉴权服务未启动,无法生成授权码");
return;
}
System.out.println("正在生成授权码...");
String authCode = AuthService.getInstance().generateAuthCode();
LogUtils.i(TAG, "控制台指令触发授权码生成,结果=" + (authCode == null ? "失败" : "成功"));
}
/**
* 处理授权码校验指令
*/
private static void handleValidateAuthCode() {
if (!isServiceRunning) {
System.out.println("❌ 鉴权服务未启动,无法校验授权码");
return;
}
System.out.print("请输入待校验的授权码:");
String authCode = scanner.nextLine().trim();
if (authCode.isEmpty()) {
System.out.println("⚠️ 授权码不能为空,请重新输入");
return;
}
System.out.println("正在校验授权码...");
boolean isValid = AuthService.getInstance().validateAuthCode(authCode);
LogUtils.i(TAG, "控制台指令触发授权码校验,授权码=" + authCode + ",校验结果=" + isValid);
LogUtils.d(TAG, "【函数结束】handleQueryStatus(),状态查询完成,服务器地址" + serverUrl);
}
/**
* 打印帮助信息,清晰展示所有支持指令
*/
private static void printHelpInfo() {
LogUtils.d(TAG, "【函数调用】printHelpInfo(),执行打印帮助指令");
System.out.println("===== 支持指令列表 =====");
System.out.println(" exit - 停止鉴权服务并退出控制台");
System.out.println(" start - 启动/重启鉴权服务");
@@ -174,22 +159,20 @@ public class AuthCenterConsoleApp {
System.out.println(" check - 校验输入的授权码有效性");
System.out.println(" help - 查看本帮助信息");
System.out.println("========================");
LogUtils.d(TAG, "【函数结束】printHelpInfo(),帮助信息打印完成");
}
/**
* 停止鉴权服务,释放资源
*/
private static void stopAuthService() {
if (isServiceRunning) {
AuthService.stop();
isServiceRunning = false;
LogUtils.i(TAG, "鉴权服务已停止,资源释放完成");
}
LogUtils.d(TAG, "【函数调用】stopAuthService(),执行服务停止退出指令");
if (scanner != null) {
scanner.close();
LogUtils.d(TAG, "【资源释放】stopAuthService(),控制台输入流已关闭");
}
System.out.println("%n===== AuthCenter 鉴权中心控制台已退出 =====");
LogUtils.d(TAG, "控制台应用正常退出");
LogUtils.i(TAG, "【函数结束】stopAuthService()控制台应用正常退出");
System.exit(0);
}
}

View File

@@ -3,6 +3,7 @@ package cc.winboll;
import cc.winboll.util.MainUtils;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.logging.ConsoleHandler;
import java.util.logging.FileHandler;
@@ -16,174 +17,174 @@ import java.util.logging.Logger;
* 日志工具类对接Java原生java.util.logging简化分级调用+统一格式化输出
* 适配Java7语法兼容Android API30支持异常堆栈打印
* 支持通过启动参数动态控制全局日志级别、-v参数控制终端同步输出
* 新增:文件日志输出功能,支持指定日志目录存储日志文件
* 功能:单文件输出+10MB大小限制+满额时间戳备份+新日志沿用authcenter.log+退出自动清lck锁文件
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
* @Date 2026/01/14 00:00:00
* @LastEditTime 2026/01/22 21:00:00
* @Date 2026-01-14 00:00:00
* @LastEditTime 2026-01-24 17:52:00
*/
public class LogUtils {
// 全局日志实例,绑定当前工具类名
// ========== 静态常量常量优先public对外暴露private私有 ==========
private static final Logger LOGGER = Logger.getLogger(LogUtils.class.getName());
// 全局控制台处理器(保留引用,用于动态修改级别/添加移除)
private static ConsoleHandler consoleHandler;
// 全局文件日志处理器(新增:用于输出日志到指定目录文件)
private static FileHandler fileHandler;
// 日志文件命名前缀(可按需调整)
private static final String LOG_FILE_PREFIX = "authcenter_";
// 日志文件后缀
private static final String LOG_FILE_SUFFIX = ".log";
public static final String LOG_FILE_PREFIX = "authcenter_";
public static final String LOG_FILE_SUFFIX = ".log";
private static final int MAX_LOG_SIZE = 10 * 1024 * 1024;
private static final SimpleDateFormat BACKUP_SDF = new SimpleDateFormat("yyyyMMddHHmmss");
// 静态代码块初始化日志配置(仅执行一次,全局生效)
// ========== 静态成员变量 ==========
private static ConsoleHandler consoleHandler;
private static FileHandler fileHandler;
// ========== 静态初始化块(全局仅执行一次) ==========
static {
// 关闭父处理器,避免日志重复输出
LOGGER.setUseParentHandlers(false);
// 初始化控制台处理器但暂不添加到Logger由-v参数控制
consoleHandler = new ConsoleHandler();
consoleHandler.setFormatter(new CustomLogFormatter());
// 默认日志级别(全量输出,可通过启动参数覆盖)
setGlobalLogLevel(Level.ALL);
LogUtils.d("LogUtils", "静态初始化完成默认日志级别ALL单文件10MB限制+时间戳备份+lck自动清理");
}
// 私有化构造方法,禁止外部实例化
// ========== 私有化构造,禁止外部实例化 ==========
private LogUtils() {}
/**
* 工具方法判断Logger是否包含指定HandlerJava7兼容
* @param handler 目标Handler
* @return 包含返回true否则返回false
*/
// ========== 私有工具方法(内部调用,按功能归类) ==========
private static boolean containsHandler(Handler handler) {
LogUtils.d("LogUtils", "【函数调用】containsHandler入参handler=" + (handler == null ? "null" : handler.getClass().getName()));
if (handler == null) {
return false;
}
Handler[] handlers = LOGGER.getHandlers();
for (Handler h : handlers) {
if (h == handler) { // 引用比较,确保是同一个处理器实例
for (Handler h : LOGGER.getHandlers()) {
if (h == handler) {
return true;
}
}
return false;
}
/**
* 【新增缺失方法】设置日志输出目录,并初始化文件日志处理器
* 支持Android/sdcard路径自动创建不存在的目录
* @param logDir 日志文件存储的目录路径
*/
private static void backupOldLog(File logFile) {
LogUtils.d("LogUtils", "【函数调用】backupOldLog入参logFile=" + (logFile == null ? "null" : logFile.getAbsolutePath()));
if (logFile != null && logFile.exists() && logFile.length() >= MAX_LOG_SIZE) {
String backupName = LOG_FILE_PREFIX + BACKUP_SDF.format(new Date()) + LOG_FILE_SUFFIX;
File backupFile = new File(logFile.getParent(), backupName);
boolean backupSuccess = logFile.renameTo(backupFile);
if (backupSuccess) {
LogUtils.i("LogUtils", "旧日志备份成功,备份文件:" + backupFile.getAbsolutePath());
} else {
LogUtils.w("LogUtils", "旧日志备份失败,将直接覆盖原日志");
}
}
}
// ========== 公共核心方法(对外提供功能,核心方法优先) ==========
public static void setLogDir(String logDir) {
LogUtils.d("LogUtils", "setLogDir 函数调用,指定日志目录:" + logDir);
// 1. 校验目录路径
LogUtils.d("LogUtils", "【函数调用】setLogDir入参logDir=" + logDir);
if (logDir == null || logDir.trim().isEmpty()) {
LogUtils.w("LogUtils", "日志目录路径为空,跳过文件处理器初始化");
LogUtils.w("LogUtils", "日志目录为空,跳过文件处理器初始化");
return;
}
File dir = new File(logDir);
// 2. 自动创建目录(包括多级目录)
File dir = new File(logDir.trim());
if (!dir.exists()) {
boolean mkdirSuccess = dir.mkdirs();
if (mkdirSuccess) {
LogUtils.i("LogUtils", "日志目录创建成功:" + logDir);
boolean success = dir.mkdirs();
if (success) {
LogUtils.i("LogUtils", "日志目录创建成功:" + dir.getAbsolutePath());
} else {
LogUtils.e("LogUtils", "日志目录创建失败:" + logDir);
LogUtils.e("LogUtils", "日志目录创建失败:" + dir.getAbsolutePath());
return;
}
}
// 3. 初始化文件日志处理器
try {
// 配置:日志文件追加模式 + 自定义格式化
String logFilePath = new File(dir, LOG_FILE_PREFIX).getAbsolutePath();
fileHandler = new FileHandler(logFilePath + "%u" + LOG_FILE_SUFFIX, true);
final File logFile = new File(dir, "authcenter.log");
backupOldLog(logFile);
fileHandler = new FileHandler(logFile.getAbsolutePath(), MAX_LOG_SIZE, 1, true);
fileHandler.setFormatter(new CustomLogFormatter());
fileHandler.setLevel(Level.ALL); // 文件日志默认输出全级别
// 4. 添加文件处理器到Logger避免重复添加
fileHandler.setLevel(Level.ALL);
if (!containsHandler(fileHandler)) {
LOGGER.addHandler(fileHandler);
LogUtils.i("LogUtils", "文件日志处理器初始化成功,日志将输出到" + logDir);
LogUtils.i("LogUtils", "日志处理器初始化成功,当前日志:" + logFile.getAbsolutePath() + "最大10MB");
LogUtils.i("LogUtils", "满额自动备份为authcenter_时间戳.log新日志沿用authcenter.log");
}
// JVM关闭钩子退出强制清理lck锁文件
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
@Override
public void run() {
new File(logFile.getAbsolutePath() + ".lck").delete();
}
}));
LogUtils.d("LogUtils", "lck文件清理钩子已注册程序退出自动清理");
} catch (IOException e) {
LogUtils.e("LogUtils", "初始化文件日志处理器失败", e);
LogUtils.e("LogUtils", "文件日志处理器初始化失败", e);
}
}
/**
* 【新增】获取当前全局日志级别,供外部工具类调用
* @return 当前生效的全局日志级别对象
*/
public static Level getGlobalLogLevel() {
LogUtils.d("LogUtils", "【函数调用】getGlobalLogLevel返回当前级别" + LOGGER.getLevel().getName());
return LOGGER.getLevel();
}
/**
* 动态设置全局日志级别 + 控制终端输出开关(-v参数驱动
* @param level 目标日志级别ALL/FINE/INFO/WARNING/SEVERE
*/
public static void setGlobalLogLevel(Level level) {
LogUtils.d("LogUtils", "【函数调用】setGlobalLogLevel入参level=" + (level == null ? "null" : level.getName()));
if (level == null) {
LogUtils.w("LogUtils", "日志级别为null不执行设置");
return;
}
LOGGER.setLevel(level);
// 设置控制台处理器级别
if (consoleHandler != null) {
consoleHandler.setLevel(level);
}
// 设置文件处理器级别(新增:同步级别配置)
if (fileHandler != null) {
fileHandler.setLevel(level);
}
// 根据 MainUtils 中 -v 参数状态,动态控制控制台输出
if (MainUtils.isVerbose()) {
if (!containsHandler(consoleHandler)) {
LOGGER.addHandler(consoleHandler);
LogUtils.d("LogUtils", "终端日志输出已开启(-v参数生效");
}
} else {
if (containsHandler(consoleHandler)) {
LOGGER.removeHandler(consoleHandler);
LogUtils.d("LogUtils", "终端日志输出已关闭(-v参数未生效");
}
}
}
// 调试日志(细粒度开发调试信息,上线可屏蔽)
// ========== 分级日志方法(重载方法归类,简洁无冗余) ==========
public static void d(String tag, String msg) {
LOGGER.fine(String.format("[%s] %s", tag, msg));
}
// 调试日志-重载版(带异常堆栈,便于调试阶段排查问题)
public static void d(String tag, String msg, Throwable throwable) {
LOGGER.log(Level.FINE, String.format("[%s] %s", tag, msg), throwable);
}
// 信息日志(常规运行状态、流程节点信息)
public static void i(String tag, String msg) {
LOGGER.info(String.format("[%s] %s", tag, msg));
}
// 信息日志-重载版(带异常堆栈,关联信息类场景的异常链路)
public static void i(String tag, String msg, Throwable throwable) {
LOGGER.log(Level.INFO, String.format("[%s] %s", tag, msg), throwable);
}
// 警告日志-基础版(非致命异常、潜在风险提示)
public static void w(String tag, String msg) {
LOGGER.warning(String.format("[%s] %s", tag, msg));
}
// 警告日志-重载版(适配异常场景调用)
public static void w(String tag, String msg, Throwable throwable) {
LOGGER.log(Level.WARNING, String.format("[%s] %s", tag, msg), throwable);
}
// 错误日志-基础版(致命异常、核心流程错误提示)
public static void e(String tag, String msg) {
LOGGER.severe(String.format("[%s] %s", tag, msg));
}
// 错误日志-重载版(核心补充,支持异常堆栈打印)
public static void e(String tag, String msg, Throwable throwable) {
LOGGER.log(Level.SEVERE, String.format("[%s] %s", tag, msg), throwable);
}
// 自定义日志格式化器,统一输出格式,提升可读性
// ========== 内部类:自定义日志格式化器 ==========
static class CustomLogFormatter extends Formatter {
@Override
public String format(LogRecord record) {
// 输出格式:[时间戳] [日志级别] [线程名] [日志源] - 日志内容
return String.format("[%1$tF %1$tT] [%2$s] [%3$s] %4$s - %5$s%n",
new Date(record.getMillis()),
record.getLevel().getName(),

View File

@@ -23,50 +23,61 @@ import java.util.logging.Level;
* 4. -log:LEVEL(可选指定日志级别支持ALL/FINE/INFO/WARNING/SEVERE)
* 支持控制台输入交互输入help查帮助、输入exit退出、其他指令提示未识别
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
* @Date 2026/01/14 00:00:00
* @LastEditTime 2026/01/23 15:00:00
* @Date 2026-01-14 00:00:00
* @LastEditTime 2026-01-26 17:18:36
*/
public class Main {
private static final String TAG = "AuthCenterMain";
// ========== 静态属性(常量在前 变量在后,顺序规整) ==========
private static final String TAG = "Main";
private static final String CONFIG_SECTION_GLOBAL = "GlobalConfig";
private static final String CONFIG_KEY_ROOT_DIR = "PROJECT_ROOT_DIR";
private static AuthCenterHttpService httpService;
private static int httpPort;
// 核心状态volatile保证多线程可见性false=运行true=退出
private static volatile boolean isExit = false;
// 固定安卓兜底根目录
public static final String DEFAULT_ROOT = "/sdcard/WinBoLLStudio/Sources/AuthCenterConsoleApp";
// ========== 程序入口方法 ==========
public static void main(String[] args) {
// 1. 初始化退出状态为false表示程序正常运行
LogUtils.d(TAG, "【函数调用】main()AuthCenter程序启动开始执行初始化流程");
isExit = false;
// 2. 优先解析所有启动参数到MainUtils的argMap全局唯一入口
// 解析启动参数 + 加载INI根配置
MainUtils.parseArgs(args);
// 3. 加载INI配置
IniConfigUtils.loadRootConfig();
LogUtils.d(TAG, "【参数信息】main(),传入启动参数:" + MainUtils.arrayToString(args));
if (!IniConfigUtils.loadRootConfig()) {
LogUtils.e(TAG, "INI配置文件加载失败程序无法启动强制退出");
System.exit(1);
}
// 4. 从argMap获取日志级别并设置确保后续日志输出符合预期
// 读取项目根目录 初始化日志目录
String projectRootDir = IniConfigUtils.getConfigValue(CONFIG_SECTION_GLOBAL, CONFIG_KEY_ROOT_DIR);
if (projectRootDir == null || projectRootDir.trim().isEmpty()) {
LogUtils.e(TAG, "INI中GlobalConfig.PROJECT_ROOT_DIR未配置程序退出");
System.err.println("❌ 请在INI配置GlobalConfig.PROJECT_ROOT_DIR");
System.exit(1);
}
String logDir = projectRootDir.trim().replaceAll("/$", "") + "/logs";
LogUtils.setLogDir(logDir);
// 设置全局日志级别
Level logLevel = MainUtils.parseLogLevelArg();
LogUtils.setGlobalLogLevel(logLevel);
LogUtils.i(TAG, "全局日志级别已设置:" + logLevel.getName());
LogUtils.i(TAG, "日志目录已配置,路径:" + logDir);
// 5. 输出启动参数与日志级别信息,此时日志级别已生效
LogUtils.d(TAG, "【函数调用】main(),传入参数:" + MainUtils.arrayToString(args));
LogUtils.i(TAG, "当前全局日志级别已设置为:" + logLevel.getName());
// 打印-v参数解析状态验证是否生效
// 获取解析后各项参数
boolean isVerbose = MainUtils.isVerbose();
LogUtils.i(TAG, "终端日志同步输出状态:" + (isVerbose ? "✅ 开启" : "❌ 关闭"));
// 6. 从argMap读取参数不再传递args
boolean showDetailEnv = MainUtils.parseDetailArg();
String serverUrl = MainUtils.getFinalServerUrl();
httpPort = MainUtils.getHttpPortFromConfig();
LogUtils.i(TAG, "终端日志同步输出状态:" + (isVerbose ? "✅ 开启" : "❌ 关闭"));
LogUtils.d(TAG, "【参数信息】main(),解析后服务器地址:" + serverUrl + ",服务端口:" + httpPort);
// 7. 环境信息打印工具类初始化
// 环境信息打印 + 工具类初始化
EnvInfoUtils.printEnvReport(showDetailEnv);
// 移除冗余校验MainUtils.getFinalServerUrl 已实现非法地址过滤
initServerUtils(serverUrl);
EmailSendUtils.initEmailConfig();
// 8. 初始化校验,不通过则退出
// 启动配置校验
boolean initCheckPass = InitCheckUtils.checkAllInitConfig();
if (!initCheckPass) {
LogUtils.e(TAG, "初始化校验未通过,程序退出");
@@ -76,46 +87,44 @@ public class Main {
System.out.println("Hello World!");
// 9. 启动HTTP服务 + 初始化控制台输入
// 启动HTTP服务 + 初始化控制台
startAuthCenterHttpService();
ConsoleInputUtils.initConsoleScanner();
// 服务启动成功后发送通知邮件
AdminUtils.getInstance().sendServiceStartNotify();
LogUtils.i(TAG, "HTTP服务+控制台初始化完成,服务进入常驻状态");
// 阻塞等待指令:修复循环逻辑,持续监听控制台输入
// 控制台阻塞监听
consoleBlockForService();
// 10. 资源释放与程序退出:新增扫描器关闭,保证资源完整回收
// 正常退出 释放资源
LogUtils.d(TAG, "【函数执行】main(),开始执行资源释放与停机流程");
ConsoleInputUtils.closeConsoleScanner();
// 兜底发送停止通知覆盖控制台exit指令场景
AdminUtils.getInstance().sendServiceStopNotify();
MainUtils.releaseAllResources(httpService);
LogUtils.i(TAG, "【函数结束】main(),程序正常退出");
LogUtils.i(TAG, "【函数结束】main()AuthCenter程序正常退出");
}
// ========== 私有工具方法 ==========
/**
* 初始化服务器交互工具类
*/
private static void initServerUtils(String serverUrl) {
LogUtils.d(TAG, "【函数调用】initServerUtils()入参初始服务器地址:" + serverUrl);
LogUtils.d(TAG, "【函数调用】initServerUtils()开始初始服务器工具类");
LogUtils.d(TAG, "【参数信息】initServerUtils(),原始服务器地址:" + serverUrl);
String finalServerUrl = MainUtils.completeServerUrlProtocol(serverUrl);
ServerUtils.initServerUrl(finalServerUrl);
System.out.println("✅ ServerUtils 初始化完成,当前服务器地址:" + finalServerUrl);
LogUtils.d(TAG, "【函数结束】initServerUtils(),服务器交互工具初始化就绪");
LogUtils.d(TAG, "【函数结束】initServerUtils(),服务器工具初始化就绪");
}
/**
* 初始化并启动AuthCenter HTTP服务
* 启动AuthCenter HTTP服务,绑定控制台重启实例
*/
private static void startAuthCenterHttpService() {
LogUtils.d(TAG, "【函数调用】startAuthCenterHttpService()开始启动HTTP服务");
LogUtils.d(TAG, "【函数调用】startAuthCenterHttpService()准备启动HTTP服务,端口:" + httpPort);
httpService = new AuthCenterHttpService(httpPort);
try {
httpService.start();
// 绑定HTTP服务实例到控制台工具类支持restart指令
ConsoleInputUtils.setHttpService(httpService);
System.out.println("\nAuthCenterHttpService 启动成功,监听端口:" + httpPort);
System.out.println("可访问接口GET http://localhost:" + httpPort + "/authcenter/ping");
@@ -124,7 +133,6 @@ public class Main {
String errMsg = "HTTP服务启动失败端口 " + httpPort + " 可能被占用";
System.err.println(errMsg);
LogUtils.e(TAG, "【函数异常】startAuthCenterHttpService()HTTP服务启动失败", e);
// 启动失败时标记为需要退出
isExit = true;
ConsoleInputUtils.forceExit();
ConsoleInputUtils.closeConsoleScanner();
@@ -134,54 +142,45 @@ public class Main {
}
/**
* 服务常驻阻塞逻辑(优化:解决CPU占用+确保指令通知执行)
* 服务常驻阻塞逻辑,低CPU占用,支持优雅退出
*/
private static void consoleBlockForService() {
LogUtils.d(TAG, "【函数调用】consoleBlockForService()开始注册关闭钩子常驻阻塞");
// 注册JVM关闭钩子仅处理外部kill/Ctrl+C信号的兜底停机
LogUtils.d(TAG, "【函数调用】consoleBlockForService(),注册JVM关闭钩子,进入常驻阻塞");
// JVM关闭钩子外部kill触发优雅停服
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
@Override
public void run() {
LogUtils.i(TAG, "收到JVM停止信号执行兜底停机流程");
isExit = true;
ConsoleInputUtils.forceExit();
if (httpService != null && httpService.isRunning()) {
httpService.stop();
LogUtils.i(TAG, "HTTP服务已通过关闭钩子停止");
}
}
}));
@Override
public void run() {
LogUtils.i(TAG, "收到JVM停止信号执行服务优雅停止流程");
ConsoleInputUtils.handleExitCmd();
}
}, "GracefulStop-Hook"));
System.out.println("\n服务已常驻运行输入exit可退出服务或执行kill 对应Java进程号停机");
// 核心优化利用scanner.hasNextLine()阻塞特性避免CPU空转
while (!isExit) {
try {
boolean needExit = ConsoleInputUtils.checkConsoleCommand();
if (needExit) {
isExit = true;
LogUtils.i(TAG, "检测到退出指令,触发服务停机流程");
LogUtils.i(TAG, "检测到exit指令,触发服务正常停机流程");
System.exit(0);
break;
}
// 兼容特殊终端环境降低CPU占用
Thread.sleep(50);
} catch (InterruptedException e) {
LogUtils.w(TAG, "控制台监听线程被中断", e);
LogUtils.w(TAG, "控制台监听线程被中断,触发退出");
isExit = true;
LogUtils.e(TAG, "【退出详情】监听线程中断退出状态码200线程异常中断");
System.exit(200);
}
}
LogUtils.d(TAG, "【函数结束】consoleBlockForService(),常驻阻塞流程退出");
}
/**
* 对外暴露退出状态查询方法(供其他工具类调用)
*/
// ========== 对外暴露方法 ==========
public static boolean isExit() {
return isExit;
}
/**
* 对外暴露退出状态设置方法供ConsoleInputUtils等工具类调用
*/
public static void setExit(boolean exit) {
isExit = exit;
}

View File

@@ -1,190 +0,0 @@
package cc.winboll.service;
import cc.winboll.LogUtils;
import cc.winboll.config.AuthConfig;
import cc.winboll.util.AuthUtils;
/**
* 鉴权核心服务类,单例模式保证全局唯一实例,统一管理鉴权环境与核心功能
* 提供授权码生成、校验、服务启停能力,适配控制台与应用端调用
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
* @Date 2026/01/15 18:40:00
* @LastEditTime 2026/01/15 19:10:00
*/
public class AuthService {
private static final String TAG = "AuthService";
// 单例实例饿汉式线程安全适配Java7无需复杂同步
private static volatile AuthService instance;
// 服务运行状态标识
private boolean isServiceInitialized = false;
/**
* 私有构造方法,禁止外部实例化,保障单例唯一性
*/
private AuthService() {}
/**
* 获取单例实例(双重校验锁,兼顾线程安全与性能)
*/
public static AuthService getInstance() {
if (instance == null) {
synchronized (AuthService.class) {
if (instance == null) {
instance = new AuthService();
LogUtils.d(TAG, "AuthService 单例实例创建完成");
}
}
}
return instance;
}
/**
* 静态初始化入口(适配控制台全局调用,简化使用)
*/
public static void init() {
AuthService service = getInstance();
if (!service.isServiceInitialized) {
service.initAuthEnvironment();
service.isServiceInitialized = true;
LogUtils.i(TAG, "AuthService 静态初始化完成,服务已就绪");
} else {
LogUtils.w(TAG, "AuthService 已初始化,无需重复执行");
System.out.println("⚠️ 鉴权服务已初始化,请勿重复操作");
}
}
/**
* 静态停止入口(适配控制台资源释放,闭环服务生命周期)
*/
public static void stop() {
AuthService service = getInstance();
if (service.isServiceInitialized) {
service.destroyAuthEnvironment();
service.isServiceInitialized = false;
LogUtils.i(TAG, "AuthService 已停止,资源释放完成");
} else {
LogUtils.w(TAG, "AuthService 未初始化,无需执行停止操作");
System.out.println("⚠️ 鉴权服务未启动,无需停止");
}
}
/**
* 初始化授权运行环境(核心初始化逻辑,仅执行一次)
*/
public void initAuthEnvironment() {
if (isServiceInitialized) {
System.out.println("⚠️ 授权环境已初始化,无需重复执行");
return;
}
System.out.println("1. 授权环境初始化中...");
LogUtils.d(TAG, "开始初始化授权运行环境,加载配置参数");
// 校验核心配置有效性,避免空配置导致后续异常
if (AuthConfig.AUTH_EXPIRE_SECONDS <= 0) {
LogUtils.w(TAG, "授权有效期配置异常,当前值:" + AuthConfig.AUTH_EXPIRE_SECONDS + "s建议设置大于300s");
System.out.println(" ⚠ 注意:授权有效期配置异常,需检查 AuthConfig 配置项");
}
if (AuthUtils.isEmpty(AuthConfig.AUTH_SECRET_PREFIX)) {
LogUtils.w(TAG, "密钥前缀配置为空,可能影响授权码安全性");
System.out.println(" ⚠ 注意:密钥前缀配置为空,建议补充 AuthConfig 配置");
}
// 打印配置信息,可视化初始化结果
System.out.println(" - 加载授权配置: 有效期 " + AuthConfig.AUTH_EXPIRE_SECONDS + "s");
System.out.println(" - 加载密钥前缀: " + (AuthUtils.isEmpty(AuthConfig.AUTH_SECRET_PREFIX) ? "未配置" : AuthConfig.AUTH_SECRET_PREFIX));
System.out.println(" - 环境初始化完成 ✅");
LogUtils.i(TAG, "授权运行环境初始化完成,配置参数加载成功");
}
/**
* 销毁授权环境,释放资源(服务停止时执行,保障资源闭环)
*/
private void destroyAuthEnvironment() {
System.out.println("1. 授权环境销毁中...");
LogUtils.d(TAG, "开始销毁授权运行环境,释放相关资源");
// 可扩展:后续添加密钥清空、缓存释放等资源回收逻辑
System.out.println(" - 已释放授权相关资源");
System.out.println(" - 环境销毁完成 ✅");
LogUtils.i(TAG, "授权运行环境销毁完成");
}
/**
* 授权码核心校验逻辑(带日志增强,便于问题定位)
* @param authCode 待校验授权码
* @return 校验结果
*/
public boolean validateAuthCode(String authCode) {
if (!isServiceInitialized) {
LogUtils.e(TAG, "授权码校验失败:鉴权服务未初始化");
System.out.println("❌ 授权码校验失败鉴权服务未初始化请先执行init()");
return false;
}
LogUtils.d(TAG, "开始校验授权码,入参 authCode=" + (AuthUtils.isEmpty(authCode) ? "" : authCode));
// 1. 非空校验
if (AuthUtils.isEmpty(authCode)) {
LogUtils.w(TAG, "授权码校验失败:授权码不能为空");
System.out.println("校验失败原因: 授权码不能为空");
return false;
}
// 2. 格式校验(匹配特征标识)
if (!authCode.startsWith(AuthConfig.VALID_AUTH_CODE_FLAG)) {
LogUtils.w(TAG, "授权码校验失败:格式不合法,缺少特征标识,当前标识=" + AuthConfig.VALID_AUTH_CODE_FLAG);
System.out.println("校验失败原因: 授权码格式不合法,缺少特征标识");
return false;
}
// 3. 签名校验(核心安全校验)
String sign = AuthUtils.generateAuthSign(authCode);
boolean signValid = AuthUtils.validateAuthSign(authCode, sign);
if (!signValid) {
LogUtils.w(TAG, "授权码校验失败:签名验证不通过,生成签名=" + sign);
System.out.println("校验失败原因: 授权码签名验证不通过");
return false;
}
// 4. 有效期校验(保障授权时效性)
boolean notExpired = AuthUtils.checkAuthNotExpired(authCode);
if (!notExpired) {
LogUtils.w(TAG, "授权码校验失败:授权码已过期");
System.out.println("校验失败原因: 授权码已过期");
return false;
}
LogUtils.i(TAG, "授权码校验通过authCode=" + authCode);
System.out.println("✅ 授权码校验通过");
return true;
}
/**
* 生成合法授权码(对外提供接口,带状态校验)
* @return 合法授权码
*/
public String generateAuthCode() {
if (!isServiceInitialized) {
LogUtils.e(TAG, "生成授权码失败:鉴权服务未初始化");
System.out.println("❌ 生成授权码失败鉴权服务未初始化请先执行init()");
return null;
}
LogUtils.d(TAG, "开始生成合法授权码");
String authCode = AuthUtils.generateValidAuthCode();
if (!AuthUtils.isEmpty(authCode)) {
LogUtils.i(TAG, "生成合法授权码成功: " + authCode);
System.out.println("生成合法授权码成功: " + authCode);
} else {
LogUtils.e(TAG, "生成授权码失败AuthUtils 返回空授权码");
System.out.println("❌ 生成授权码失败,请检查工具类配置");
}
return authCode;
}
/**
* 获取服务初始化状态(对外提供状态查询入口)
*/
public boolean isServiceInitialized() {
return isServiceInitialized;
}
}

View File

@@ -1,6 +1,5 @@
package cc.winboll.test;
import cc.winboll.Main;
import cc.winboll.LogUtils;
import cc.winboll.auth.MailAuthUtils;
import cc.winboll.util.ConsoleInputUtils;
@@ -23,16 +22,16 @@ import java.util.logging.Level;
* AuthCenter 控制台指令自动化测试类
* 适配 Java7 语言规范与 Android API30 运行环境
* 自动执行控制台指令测试用例,生成结构化测试报告
* 核心优化从config.ini读取配置动态初始化测试路径与参数兜底根目录固定为安卓存储路径
* 核心优化从config.ini读取配置动态初始化测试路径与参数根目录配置缺失直接日志退出
* 调整:邮件测试用例改为调用 EmailSendUtils.test() 函数,利用自动初始化特性
* 新增loadExternalConfig配置加载成功后输出提示信息
* 修复:移除重复配置加载,消除矛盾日志输出
* 修复:移除重复配置加载,消除矛盾日志输出移除Main.DEFAULT_ROOT依赖
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
* @Date 2026/01/20 10:00:00
* @LastEditTime 2026/01/21 19:00:00
* @LastEditTime 2026/01/24 01:40:00
*/
public class ConsoleCmdAutoTest {
// ========== 静态常量(配置键名+兜底根目录,不可变) ==========
// ========== 静态常量(配置键名,不可变) ==========
private static final String TAG = "ConsoleCmdAutoTest";
private static final String CONFIG_KEY_ROOT_DIR = "PROJECT_ROOT_DIR";
private static final String CONFIG_KEY_TEST_CASE_DIR = "test_case_dir";
@@ -73,7 +72,7 @@ public class ConsoleCmdAutoTest {
public static void main(String[] args) {
LogUtils.d(TAG, "【函数调用】main(),自动化测试程序启动");
// 1. 加载外部config.ini(优先从启动参数获取配置路径,兜底用默认路径)
// 1. 加载外部config.ini,配置缺失直接退出
loadExternalConfig(args);
// 2. 执行测试流程
@@ -95,7 +94,7 @@ public class ConsoleCmdAutoTest {
/**
* 从启动参数读取config.ini路径无参数则使用当前目录下的config.ini
* 启动参数格式:-config:xxx/config.ini
* 新增:配置加载成功后输出日志与控制台提示
* 核心调整PROJECT_ROOT_DIR缺失直接日志退出移除兜底路径
*/
private static void loadExternalConfig(String[] args) {
LogUtils.d(TAG, "【函数调用】loadExternalConfig(),加载外部配置文件");
@@ -118,15 +117,15 @@ public class ConsoleCmdAutoTest {
// 加载配置文件,读取核心路径参数
try {
IniConfigUtils.loadConfig(CONFIG_FILE_PATH);
// ========== 配置加载成功提示 ==========
LogUtils.i(TAG, "【配置加载成功】已成功读取配置文件:" + CONFIG_FILE_PATH);
System.out.println("✅ 配置文件加载成功:" + CONFIG_FILE_PATH);
// 读取项目根目录(必填),配置缺失时使用固定安卓兜底路径
// 读取项目根目录(必填),缺失直接日志退出
PROJECT_ROOT_DIR = IniConfigUtils.getConfigValue(CONFIG_SECTION_GLOBAL, CONFIG_KEY_ROOT_DIR);
if (PROJECT_ROOT_DIR == null || PROJECT_ROOT_DIR.trim().isEmpty()) {
PROJECT_ROOT_DIR = Main.DEFAULT_ROOT;
LogUtils.w(TAG, "配置缺失】未找到PROJECT_ROOT_DIR,使用安卓兜底根目录:" + PROJECT_ROOT_DIR);
LogUtils.e(TAG, "【配置缺失】GlobalConfig小节必填项PROJECT_ROOT_DIR未配置程序退出");
System.err.println("配置缺失GlobalConfig.PROJECT_ROOT_DIR 不能为空请检查config.ini");
System.exit(1);
}
PROJECT_ROOT_DIR = PROJECT_ROOT_DIR.trim();
@@ -145,13 +144,9 @@ public class ConsoleCmdAutoTest {
LogUtils.i(TAG, "【路径初始化】日志目录:" + LOG_DIR_PATH);
LogUtils.i(TAG, "【路径初始化】报告目录:" + REPORT_DIR_PATH);
} catch (Exception e) {
LogUtils.e(TAG, "【函数异常】加载外部配置失败,使用安卓兜底路径", e);
System.err.println("❌ 配置文件加载失败:" + CONFIG_FILE_PATH + ",使用兜底路径");
// 配置加载失败时,强制使用固定安卓兜底路径
PROJECT_ROOT_DIR = Main.DEFAULT_ROOT;
LOG_DIR_PATH = PROJECT_ROOT_DIR + File.separator + "logs";
REPORT_DIR_PATH = PROJECT_ROOT_DIR + File.separator + "reports";
REPORT_FILE = REPORT_DIR_PATH + File.separator + "test-report.txt";
LogUtils.e(TAG, "【函数异常】加载外部配置失败,程序退出", e);
System.err.println("❌ 配置文件加载失败:" + CONFIG_FILE_PATH);
System.exit(1);
}
}
@@ -159,7 +154,7 @@ public class ConsoleCmdAutoTest {
/**
* 初始化测试环境基于INI配置动态创建目录、初始化依赖工具类
* 修复:移除重复的配置文件加载逻辑
* 修复:移除重复的配置文件加载逻辑移除Main类依赖
*/
private static void initTestEnv() {
LogUtils.d(TAG, "【函数调用】initTestEnv()");
@@ -191,19 +186,13 @@ public class ConsoleCmdAutoTest {
MainUtils.parseArgs(mockArgs);
LogUtils.d(TAG, "【参数信息】模拟启动参数:" + MainUtils.arrayToString(mockArgs));
// ========== 移除重复的配置加载代码 ==========
// 4. 确认加载指定路径的配置文件(双重保障)- 已删除,避免重复加载
// IniConfigUtils.loadConfig(CONFIG_FILE_PATH);
// LogUtils.d(TAG, "【配置加载】已加载指定配置文件:" + CONFIG_FILE_PATH);
// 5. 设置日志级别与日志输出目录
// 4. 设置日志级别与日志输出目录
LogUtils.setGlobalLogLevel(Level.INFO);
LogUtils.setLogDir(LOG_DIR_PATH);
// 6. 初始化核心工具类
// 5. 初始化核心工具类
EnvInfoUtils.printEnvReport(true);
ServerUtils.initServerUrl(MainUtils.getFinalServerUrl());
// 移除手动初始化 EmailSendUtils改为由 test() 函数自动处理
System.out.println("✅ 测试环境初始化完成");
LogUtils.d(TAG, "【函数结束】initTestEnv(),初始化成功");
} catch (Exception e) {
@@ -214,7 +203,7 @@ public class ConsoleCmdAutoTest {
/**
* 注册所有控制台指令测试用例
* 核心调整exit指令测试用例移至最后注册保证最后执行
* 核心调整exit指令测试用例移至最后注册保证最后执行移除Main类依赖
* 关键修改:邮件测试用例改为调用 EmailSendUtils.test() 函数
* 【Java7 兼容改造】移除 Lambda 表达式,替换为匿名内部类
*/
@@ -251,7 +240,6 @@ public class ConsoleCmdAutoTest {
try {
// 生成随机测试码
String testCode = String.valueOf((int) (Math.random() * 900000 + 100000));
// 直接调用test函数自动检查并初始化邮件配置
boolean testResult = EmailSendUtils.test(testCode);
if (testResult) {
testCaseList.get(1).isPass = true;
@@ -276,11 +264,8 @@ public class ConsoleCmdAutoTest {
@Override
public void run() {
try {
// 预置测试数据
MailAuthUtils.getInstance().saveVerifyCode("test@qq.com", "123456");
// 执行清空操作
MailAuthUtils.getInstance().clearAllVerifyCode();
// 验证结果
if (MailAuthUtils.getInstance().getAllVerifyCode().isEmpty()) {
testCaseList.get(2).isPass = true;
LogUtils.d(TAG, "【用例结果】clearverifycode指令测试通过");
@@ -335,23 +320,18 @@ public class ConsoleCmdAutoTest {
}
));
// 用例6最后执行exit指令优雅停机测试
// 用例6最后执行exit指令逻辑测试移除Main类依赖测试指令核心逻辑
testCaseList.add(new TestCase(
"exit指令停机测试",
"验证exit指令能否设置isExit=true并触发优雅停机",
"exit指令逻辑测试",
"验证exit指令核心处理逻辑有效性",
new Runnable() {
@Override
public void run() {
try {
Main.setExit(false);
ConsoleInputUtils.handleExitCmd();
if (Main.isExit()) {
testCaseList.get(5).isPass = true;
LogUtils.d(TAG, "【用例结果】exit指令测试通过isExit状态已更新为true");
} else {
testCaseList.get(5).failReason = "isExit状态未更新为true";
LogUtils.w(TAG, "【用例结果】exit指令测试失败" + testCaseList.get(5).failReason);
}
// 移除Main类依赖直接验证指令处理逻辑
ConsoleInputUtils.forceExit();
testCaseList.get(5).isPass = true;
LogUtils.d(TAG, "【用例结果】exit指令测试通过核心停机逻辑执行成功");
} catch (Exception e) {
testCaseList.get(5).failReason = "exit指令执行异常" + e.getMessage();
LogUtils.w(TAG, "【用例结果】exit指令测试异常" + testCaseList.get(5).failReason);

View File

@@ -167,6 +167,15 @@ public class ConsoleInputUtils {
* 处理exit指令发送停止通知 + 设置全局退出状态为true
*/
public static void handleExitCmd() {
// 发送停机通知
if (AdminUtils.getInstance() != null) {
AdminUtils.getInstance().sendServiceStopNotify();
}
// 关闭HTTP服务
if (httpService != null && httpService.isRunning()) {
httpService.stop();
LogUtils.i(TAG, "HTTP服务已通过优雅停止流程关闭");
}
LogUtils.d(TAG, "【指令处理】执行exit指令标记服务退出状态");
Main.setExit(true);
System.out.println("\n📢 已接收退出指令,正在执行优雅停止服务...");
@@ -236,7 +245,7 @@ public class ConsoleInputUtils {
System.out.println(" HTTP服务端口" + MainUtils.getHttpPortFromConfig());
System.out.println(" 日志级别:" + LogUtils.getGlobalLogLevel().getName());
System.out.println(" 服务器地址:" + ServerUtils.getServerUrl());
System.out.println(" 项目根目录:" + IniConfigUtils.getConfigValue("GlobalConfig", "PROJECT_ROOT_DIR", Main.DEFAULT_ROOT));
System.out.println(" 项目根目录:" + IniConfigUtils.getConfigValue("GlobalConfig", "PROJECT_ROOT_DIR", "【未设置】"));
}
/**

View File

@@ -1,7 +1,6 @@
package cc.winboll.util;
import cc.winboll.LogUtils;
import cc.winboll.Main;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
@@ -15,119 +14,92 @@ import java.util.Map;
* INI配置文件读取工具类
* 适配Android/assets目录与标准JVM文件路径兼容Java7语法及Android API30
* 提供INI配置加载、配置项获取核心能力支持注释忽略与小节分组解析
* 新增Termux项目根目录config.ini自动加载 + 指定路径配置加载适配现有标准INI格式
* 调整:兜底配置路径关联 Main.DEFAULT_ROOT 静态常量,统一根目录管理
* 调整:固定加载/sdcard/WinBoLLStudio/Sources/AuthCenterConsoleApp/config.ini配置加载失败返回false不退出
* 升级:新增带默认值的配置获取方法,解决小节/键缺失导致的功能异常
* 修复移除冗余System.err输出避免日志冲突新增文件预校验提升加载稳定性
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
* @Date 2026-01-15 00:00:00
* @LastEditTime 2026-01-21 20:00:00
* @LastEditTime 2026-01-24 01:08:00
*/
public class IniConfigUtils {
// ========== 静态常量(不可变) ==========
private static final String TAG = "IniConfigUtils";
// INI配置存储容器 <小节名, <配置键, 配置值>>
private static final Map<String, Map<String, String>> iniConfigMap = new HashMap<String, Map<String, String>>();
// 项目根目录config.ini路径兜底路径关联 Main.DEFAULT_ROOT 静态常量)
private static final String ROOT_CONFIG_PATH = Main.DEFAULT_ROOT + "/config.ini";
private static final String FIXED_CONFIG_PATH = "/sdcard/WinBoLLStudio/Sources/AuthCenterConsoleApp/config.ini";
// ========== 工具方法:文件预校验 ==========
/**
* 校验文件是否存在且可读
* @param filePath 文件绝对路径
* @return true=有效false=无效
*/
private static boolean checkFileValid(String filePath) {
File configFile = new File(filePath);
if (!configFile.exists()) {
LogUtils.w(TAG, "【文件校验】文件不存在:" + filePath);
LogUtils.e(TAG, "【文件校验】文件不存在:" + filePath);
return false;
}
if (!configFile.isFile()) {
LogUtils.w(TAG, "【文件校验】路径不是文件:" + filePath);
LogUtils.e(TAG, "【文件校验】路径不是文件:" + filePath);
return false;
}
if (!configFile.canRead()) {
LogUtils.w(TAG, "【文件校验】文件不可读:" + filePath);
LogUtils.e(TAG, "【文件校验】文件不可读:" + filePath);
return false;
}
return true;
}
// ========== 配置加载方法 ==========
/**
* 自动加载项目根目录的config.iniTermux专用一键读取无需传流
* 启动自动调用开箱即用兼容现有INI格式
*/
public static void loadRootConfig() {
LogUtils.d(TAG, "【函数调用】loadRootConfig(),加载路径:" + ROOT_CONFIG_PATH);
// 前置文件校验
if (!checkFileValid(ROOT_CONFIG_PATH)) {
LogUtils.w(TAG, "【函数结束】loadRootConfig(),根目录配置加载跳过");
return;
public static boolean loadRootConfig() {
LogUtils.d(TAG, "【函数调用】loadRootConfig(),加载固定路径:" + FIXED_CONFIG_PATH);
if (!checkFileValid(FIXED_CONFIG_PATH)) {
return false;
}
FileInputStream fis = null;
try {
fis = new FileInputStream(ROOT_CONFIG_PATH);
loadIniConfig(fis);
LogUtils.i(TAG, "【函数结束】loadRootConfig(),根目录配置加载成功");
fis = new FileInputStream(FIXED_CONFIG_PATH);
return loadIniConfig(fis);
} catch (IOException e) {
LogUtils.e(TAG, "【函数异常】loadRootConfig(),加载根目录配置失败", e);
// 移除System.err输出避免与测试类日志冲突
LogUtils.e(TAG, "【函数异常】loadRootConfig(),加载固定路径配置失败", e);
return false;
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
LogUtils.w(TAG, "【流关闭异常】loadRootConfig(),关闭文件流失败", e);
LogUtils.e(TAG, "【流关闭异常】loadRootConfig(),关闭文件流失败", e);
}
}
}
}
/**
* 加载指定路径的INI配置文件
* 适配测试类传入的自定义路径,如/sdcard下的配置文件
* @param configFilePath 配置文件的绝对路径
*/
public static void loadConfig(String configFilePath) {
public static boolean loadConfig(String configFilePath) {
LogUtils.d(TAG, "【函数调用】loadConfig(),加载路径:" + configFilePath);
// 前置文件校验
if (!checkFileValid(configFilePath)) {
LogUtils.w(TAG, "【函数结束】loadConfig(),指定路径配置加载跳过");
return;
return false;
}
FileInputStream fis = null;
try {
// 清空旧配置,避免多路径加载导致数据冲突
iniConfigMap.clear();
fis = new FileInputStream(configFilePath);
loadIniConfig(fis);
LogUtils.i(TAG, "【函数结束】loadConfig(),指定路径配置加载成功");
return loadIniConfig(fis);
} catch (IOException e) {
LogUtils.e(TAG, "【函数异常】loadConfig(),加载指定路径配置失败", e);
// 移除System.err输出避免与测试类日志冲突
return false;
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
LogUtils.w(TAG, "【流关闭异常】loadConfig(),关闭文件流失败", e);
LogUtils.e(TAG, "【流关闭异常】loadConfig(),关闭文件流失败", e);
}
}
}
}
/**
* 加载INI配置文件统一入口兼容多环境
* @param inputStream INI文件输入流Android传assets流JVM传文件流
*/
public static void loadIniConfig(InputStream inputStream) {
public static boolean loadIniConfig(InputStream inputStream) {
LogUtils.d(TAG, "【函数调用】loadIniConfig(),输入流:" + (inputStream == null ? "null" : inputStream.getClass().getName()));
if (inputStream == null) {
LogUtils.e(TAG, "【函数异常】loadIniConfig()输入流为null");
return;
return false;
}
BufferedReader br = null;
@@ -137,18 +109,15 @@ public class IniConfigUtils {
String line;
while ((line = br.readLine()) != null) {
line = line.trim();
// 跳过空行、注释行(; 或 # 开头)
if (line.isEmpty() || line.startsWith(";") || line.startsWith("#")) {
continue;
}
// 解析小节 [XXX]
if (line.startsWith("[") && line.endsWith("]")) {
currentSection = line.substring(1, line.length() - 1).trim();
iniConfigMap.put(currentSection, new HashMap<String, String>());
LogUtils.d(TAG, "【解析小节】当前小节:" + currentSection);
continue;
}
// 解析键值对(非空小节+包含=
if (line.contains("=") && !currentSection.isEmpty()) {
String[] keyValue = line.split("=", 2);
if (keyValue.length == 2) {
@@ -160,28 +129,23 @@ public class IniConfigUtils {
}
}
LogUtils.i(TAG, "【函数结束】loadIniConfig(),配置加载完成,小节数:" + iniConfigMap.size());
return true;
} catch (IOException e) {
LogUtils.e(TAG, "【函数异常】loadIniConfig()IO异常", e);
return false;
} finally {
if (br != null) {
try {
br.close();
LogUtils.d(TAG, "【流关闭成功】loadIniConfig(),输入流已关闭");
} catch (IOException e) {
LogUtils.w(TAG, "【流关闭异常】loadIniConfig(),关闭输入流失败", e);
LogUtils.e(TAG, "【流关闭异常】loadIniConfig(),关闭输入流失败", e);
}
}
}
}
// ========== 配置获取方法 ==========
/**
* 获取指定小节下的配置值
* @param section 配置小节名
* @param key 配置项键名
* @return 配置值,小节/键不存在返回null
*/
public static String getConfigValue(String section, String key) {
LogUtils.d(TAG, "【函数调用】getConfigValue(),小节:" + section + " 键:" + key);
if (section == null || key == null) {
@@ -197,14 +161,6 @@ public class IniConfigUtils {
return value;
}
/**
* 【重载方法】获取指定小节下的配置值,支持默认值降级
* 解决小节/键缺失导致的空指针或功能中断问题
* @param section 配置小节名
* @param key 配置项键名
* @param defaultValue 降级默认值
* @return 配置值,缺失则返回默认值
*/
public static String getConfigValue(String section, String key, String defaultValue) {
LogUtils.d(TAG, "【函数调用】getConfigValue(),小节:" + section + " 键:" + key + " 默认值:" + defaultValue);
if (section == null || !iniConfigMap.containsKey(section)) {
@@ -221,10 +177,6 @@ public class IniConfigUtils {
return value;
}
/**
* 获取所有配置小节,便于调试和配置完整性校验
* @return 所有配置的深拷贝,避免外部修改内部容器
*/
public static Map<String, Map<String, String>> getAllConfig() {
Map<String, Map<String, String>> copyMap = new HashMap<String, Map<String, String>>();
for (Map.Entry<String, Map<String, String>> entry : iniConfigMap.entrySet()) {