添加测试用例
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,5 +1,5 @@
|
||||
bin/
|
||||
logs/
|
||||
runtime/
|
||||
reports/
|
||||
config.ini
|
||||
*.log
|
||||
|
||||
@@ -1,40 +1,70 @@
|
||||
#!/bin/bash
|
||||
# Termux专属启动脚本 编译+启动 解决exit后卡空行/需手动^C问题
|
||||
# 优化:增加JVM内存限制、进程异常捕获、日志输出
|
||||
# Termux专属启动脚本 编译+启动 解决exit后卡空行/需手动^C/参数误判问题
|
||||
# 优化:分离日志输出、保留终端交互、增强异常处理、适配新版参数解析
|
||||
cd "$(dirname "${BASH_SOURCE[0]}")" || { echo "目录切换失败"; exit 1; }
|
||||
BASE_DIR=$(cd .. && pwd)
|
||||
LOG_DIR="$BASE_DIR/logs"
|
||||
MAIN_LOG="$LOG_DIR/main.log"
|
||||
|
||||
# 1. 编译校验
|
||||
# 1. 前置准备:创建日志目录
|
||||
mkdir -p "$LOG_DIR" || { echo "日志目录创建失败"; exit 1; }
|
||||
|
||||
# 2. 编译校验
|
||||
BUILD_SH="$BASE_DIR/bash/build_class.sh"
|
||||
if [ ! -f "$BUILD_SH" ]; then
|
||||
echo "编译脚本不存在:$BUILD_SH"
|
||||
exit 1
|
||||
fi
|
||||
bash "$BUILD_SH" || { echo "编译失败退出"; exit 1; }
|
||||
|
||||
# 2. 类路径配置
|
||||
# 3. 类路径配置(修复:指定完整包名主类)
|
||||
CLASSPATH="$BASE_DIR/runtime"
|
||||
[ -d "$BASE_DIR/libs" ] && CLASSPATH="$CLASSPATH:$BASE_DIR/libs/*"
|
||||
MAIN_CLASS="Main"
|
||||
# 关键:Main类带完整包名,避免类找不到或参数解析异常
|
||||
MAIN_CLASS="cc.winboll.Main"
|
||||
|
||||
# 3. 关键优化:JVM内存限制(避免内存耗尽)+ 异常日志输出
|
||||
export _JAVA_OPTIONS="-Xmx128m -Xms64m" # 限制最大堆内存128M,初始堆64M,适配Termux
|
||||
# 4. JVM优化配置(适配Termux低内存环境)
|
||||
export _JAVA_OPTIONS="-Xmx128m -Xms64m -Dfile.encoding=UTF-8"
|
||||
STOP_SIGNAL=0
|
||||
JAVA_PID=0
|
||||
|
||||
# 捕获进程异常终止信号
|
||||
# 5. 信号捕获:优雅停机+终端重置
|
||||
on_exit() {
|
||||
STOP_SIGNAL=1
|
||||
echo -e "\n⚠️ 服务异常终止,可查看日志排查问题"
|
||||
echo -e "\n⚠️ 收到停止信号,正在优雅停机..."
|
||||
# 向Java进程发送中断信号
|
||||
if [ $JAVA_PID -ne 0 ]; then
|
||||
kill -INT $JAVA_PID 2>/dev/null
|
||||
sleep 1
|
||||
kill -9 $JAVA_PID 2>/dev/null
|
||||
fi
|
||||
stty sane
|
||||
echo -e "✅ 服务已停止,日志文件:$MAIN_LOG"
|
||||
exit 1
|
||||
}
|
||||
trap 'on_exit' SIGABRT SIGSEGV SIGILL SIGTERM
|
||||
# 捕获更多退出信号
|
||||
trap 'on_exit' SIGINT SIGTERM SIGABRT SIGSEGV SIGILL
|
||||
|
||||
# 4. 启动服务+日志输出
|
||||
# 6. 启动服务(核心修改:分离日志,保留终端stdin)
|
||||
stty -echoctl # 屏蔽^C视觉印记
|
||||
echo -e "\n🚀 服务启动中,输入help查指令,输入exit优雅停机"
|
||||
java -cp "$CLASSPATH" "$MAIN_CLASS" -v -log:ALL &> "$BASE_DIR/logs/main.log"
|
||||
echo -e "🚀 服务启动中,输入help查指令,输入exit优雅停机"
|
||||
echo -e "📝 日志输出路径:$MAIN_LOG\n"
|
||||
|
||||
# 5. Java退出后 强制重置终端+脚本退出,彻底无残留
|
||||
if [ $STOP_SIGNAL -eq 0 ]; then
|
||||
echo -e "\n✅ 服务优雅停机"
|
||||
fi
|
||||
# 关键:stdout/stderr 追加到日志,但保留stdin给程序交互
|
||||
java -cp "$CLASSPATH" "$MAIN_CLASS" -v -log:ALL >> "$MAIN_LOG" 2>&1 &
|
||||
JAVA_PID=$!
|
||||
|
||||
# 7. 等待Java进程结束(解决卡空行问题)
|
||||
wait $JAVA_PID
|
||||
EXIT_CODE=$?
|
||||
|
||||
# 8. 后置处理
|
||||
stty sane
|
||||
exit $?
|
||||
|
||||
if [ $STOP_SIGNAL -eq 0 ]; then
|
||||
if [ $EXIT_CODE -eq 0 ]; then
|
||||
echo -e "\n✅ 服务优雅停机完成"
|
||||
else
|
||||
echo -e "\n❌ 服务异常退出,退出码:$EXIT_CODE,查看日志:$MAIN_LOG"
|
||||
fi
|
||||
fi
|
||||
exit $EXIT_CODE
|
||||
|
||||
64
bash/unit_test.sh
Normal file
64
bash/unit_test.sh
Normal file
@@ -0,0 +1,64 @@
|
||||
#!/bin/bash
|
||||
# Termux专属 测试用例启动脚本
|
||||
# 功能:编译代码 + 运行ConsoleCmdAutoTest测试类 + 输出测试日志 + 终端环境重置
|
||||
cd "$(dirname "${BASH_SOURCE[0]}")" || { echo "❌ 目录切换失败"; exit 1; }
|
||||
BASE_DIR=$(cd .. && pwd)
|
||||
|
||||
# ========== 配置项 ==========
|
||||
# 测试类全限定名
|
||||
TEST_MAIN_CLASS="cc.winboll.test.ConsoleCmdAutoTest"
|
||||
# 日志输出路径(与测试类中配置的日志目录一致)
|
||||
TEST_LOG_DIR="$BASE_DIR/logs"
|
||||
TEST_LOG_FILE="$TEST_LOG_DIR/test_run.log"
|
||||
# JVM 内存配置(适配Termux低内存环境)
|
||||
JAVA_OPTS="-Xmx128m -Xms64m"
|
||||
# 编译脚本路径
|
||||
BUILD_SH="$BASE_DIR/bash/build_class.sh"
|
||||
|
||||
# ========== 前置检查 ==========
|
||||
# 创建日志目录
|
||||
if [ ! -d "$TEST_LOG_DIR" ]; then
|
||||
mkdir -p "$TEST_LOG_DIR" || { echo "❌ 日志目录创建失败: $TEST_LOG_DIR"; exit 1; }
|
||||
fi
|
||||
|
||||
# ========== 编译代码 ==========
|
||||
echo "📦 开始编译项目代码..."
|
||||
bash "$BUILD_SH" || { echo "❌ 项目编译失败,退出测试"; exit 1; }
|
||||
echo "✅ 项目编译成功"
|
||||
|
||||
# ========== 配置类路径 ==========
|
||||
CLASSPATH="$BASE_DIR/runtime"
|
||||
if [ -d "$BASE_DIR/libs" ]; then
|
||||
CLASSPATH="$CLASSPATH:$BASE_DIR/libs/*"
|
||||
fi
|
||||
|
||||
# ========== 信号捕获(异常终止处理) ==========
|
||||
STOP_SIGNAL=0
|
||||
on_exit() {
|
||||
STOP_SIGNAL=1
|
||||
echo -e "\n⚠️ 测试进程异常终止!查看日志: $TEST_LOG_FILE"
|
||||
stty sane
|
||||
exit 1
|
||||
}
|
||||
trap 'on_exit' SIGABRT SIGSEGV SIGILL SIGTERM
|
||||
|
||||
# ========== 启动测试用例 ==========
|
||||
echo -e "\n🚀 启动测试用例: $TEST_MAIN_CLASS"
|
||||
echo -e "📜 测试日志将输出到: $TEST_LOG_FILE"
|
||||
echo -e "=====================================\n"
|
||||
|
||||
# 屏蔽^C视觉印记 + 执行测试类 + 日志重定向
|
||||
stty -echoctl
|
||||
java $JAVA_OPTS -cp "$CLASSPATH" "$TEST_MAIN_CLASS" -v -log:ALL &> "$TEST_LOG_FILE"
|
||||
|
||||
# ========== 测试结束处理 ==========
|
||||
if [ $STOP_SIGNAL -eq 0 ]; then
|
||||
echo -e "\n====================================="
|
||||
echo "✅ 测试用例执行完成!"
|
||||
echo "📊 测试报告路径: $BASE_DIR/test-report.txt"
|
||||
echo "📜 详细日志路径: $TEST_LOG_FILE"
|
||||
fi
|
||||
|
||||
# 重置终端属性,避免残留问题
|
||||
stty sane
|
||||
exit $?
|
||||
@@ -1,8 +1,11 @@
|
||||
package cc.winboll;
|
||||
|
||||
import cc.winboll.util.MainUtils;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
import java.util.logging.ConsoleHandler;
|
||||
import java.util.logging.FileHandler;
|
||||
import java.util.logging.Formatter;
|
||||
import java.util.logging.Handler;
|
||||
import java.util.logging.Level;
|
||||
@@ -13,15 +16,22 @@ import java.util.logging.Logger;
|
||||
* 日志工具类,对接Java原生java.util.logging,简化分级调用+统一格式化输出
|
||||
* 适配Java7语法,兼容Android API30,支持异常堆栈打印
|
||||
* 支持通过启动参数动态控制全局日志级别、-v参数控制终端同步输出
|
||||
* 新增:文件日志输出功能,支持指定日志目录存储日志文件
|
||||
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2026/01/14 00:00:00
|
||||
* @LastEditTime 2026/01/18 19:30:00
|
||||
* @LastEditTime 2026/01/20 23:00:00
|
||||
*/
|
||||
public class LogUtils {
|
||||
// 全局日志实例,绑定当前工具类名
|
||||
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";
|
||||
|
||||
// 静态代码块初始化日志配置(仅执行一次,全局生效)
|
||||
static {
|
||||
@@ -55,18 +65,65 @@ public class LogUtils {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 【新增缺失方法】设置日志输出目录,并初始化文件日志处理器
|
||||
* 支持Android/sdcard路径,自动创建不存在的目录
|
||||
* @param logDir 日志文件存储的目录路径
|
||||
*/
|
||||
public static void setLogDir(String logDir) {
|
||||
LogUtils.d("LogUtils", "setLogDir 函数调用,指定日志目录:" + logDir);
|
||||
// 1. 校验目录路径
|
||||
if (logDir == null || logDir.trim().isEmpty()) {
|
||||
LogUtils.w("LogUtils", "日志目录路径为空,跳过文件处理器初始化");
|
||||
return;
|
||||
}
|
||||
File dir = new File(logDir);
|
||||
// 2. 自动创建目录(包括多级目录)
|
||||
if (!dir.exists()) {
|
||||
boolean mkdirSuccess = dir.mkdirs();
|
||||
if (mkdirSuccess) {
|
||||
LogUtils.i("LogUtils", "日志目录创建成功:" + logDir);
|
||||
} else {
|
||||
LogUtils.e("LogUtils", "日志目录创建失败:" + logDir);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// 3. 初始化文件日志处理器
|
||||
try {
|
||||
// 配置:日志文件追加模式 + 自定义格式化
|
||||
String logFilePath = new File(dir, LOG_FILE_PREFIX).getAbsolutePath();
|
||||
fileHandler = new FileHandler(logFilePath + "%u" + LOG_FILE_SUFFIX, true);
|
||||
fileHandler.setFormatter(new CustomLogFormatter());
|
||||
fileHandler.setLevel(Level.ALL); // 文件日志默认输出全级别
|
||||
// 4. 添加文件处理器到Logger(避免重复添加)
|
||||
if (!containsHandler(fileHandler)) {
|
||||
LOGGER.addHandler(fileHandler);
|
||||
LogUtils.i("LogUtils", "文件日志处理器初始化成功,日志将输出到:" + logDir);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LogUtils.e("LogUtils", "初始化文件日志处理器失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 动态设置全局日志级别 + 控制终端输出开关(-v参数驱动)
|
||||
* @param level 目标日志级别(ALL/FINE/INFO/WARNING/SEVERE)
|
||||
*/
|
||||
public static void setGlobalLogLevel(Level level) {
|
||||
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);
|
||||
}
|
||||
consoleHandler.setLevel(level);
|
||||
} else {
|
||||
if (containsHandler(consoleHandler)) {
|
||||
LOGGER.removeHandler(consoleHandler);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package cc.winboll;
|
||||
|
||||
import cc.winboll.LogUtils;
|
||||
import cc.winboll.auth.MailAuthUtils;
|
||||
import cc.winboll.service.AuthCenterHttpService;
|
||||
import cc.winboll.util.ConsoleInputUtils;
|
||||
import cc.winboll.util.EmailSendUtils;
|
||||
@@ -18,57 +17,70 @@ import java.util.logging.Level;
|
||||
* 适配Java7语言规范,兼容Android API30运行环境,支持优雅停机
|
||||
* 启动参数规则:
|
||||
* 1. -detail(可选,显示详细环境信息)
|
||||
* 2. 服务器地址(可选,无-前缀,覆盖默认/INI配置)
|
||||
* 3. -log:LEVEL(可选,指定日志级别,支持ALL/FINE/INFO/WARNING/SEVERE)
|
||||
* 2. -v(可选,开启终端日志同步输出)
|
||||
* 3. 服务器地址(可选,无-前缀,覆盖默认/INI配置)
|
||||
* 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/18 16:30:00
|
||||
* @LastEditTime 2026/01/20 09:00:00
|
||||
*/
|
||||
public class Main {
|
||||
private static final String TAG = "AuthCenterMain";
|
||||
private static AuthCenterHttpService httpService;
|
||||
private static int httpPort;
|
||||
// 核心状态:volatile保证多线程可见性,false=运行,true=退出
|
||||
private static volatile boolean isExit = false;
|
||||
|
||||
public static void main(String[] args) {
|
||||
// 1. 优先解析所有启动参数到MainUtils的argMap,全局唯一入口
|
||||
// 1. 初始化退出状态为false,表示程序正常运行
|
||||
isExit = false;
|
||||
|
||||
// 2. 优先解析所有启动参数到MainUtils的argMap,全局唯一入口
|
||||
MainUtils.parseArgs(args);
|
||||
// 2. 加载INI配置
|
||||
// 3. 加载INI配置
|
||||
IniConfigUtils.loadRootConfig();
|
||||
|
||||
// 3. 从argMap获取日志级别并设置,确保后续日志输出符合预期
|
||||
// 4. 从argMap获取日志级别并设置,确保后续日志输出符合预期
|
||||
Level logLevel = MainUtils.parseLogLevelArg();
|
||||
LogUtils.setGlobalLogLevel(logLevel);
|
||||
|
||||
// 4. 输出启动参数与日志级别信息,此时日志级别已生效
|
||||
// 5. 输出启动参数与日志级别信息,此时日志级别已生效
|
||||
LogUtils.d(TAG, "【函数调用】main(),传入参数:" + MainUtils.arrayToString(args));
|
||||
LogUtils.i(TAG, "当前全局日志级别已设置为:" + logLevel.getName());
|
||||
// 新增:打印-v参数解析状态,验证是否生效
|
||||
boolean isVerbose = MainUtils.isVerbose();
|
||||
LogUtils.i(TAG, "终端日志同步输出状态:" + (isVerbose ? "✅ 开启" : "❌ 关闭"));
|
||||
|
||||
// 5. 从argMap读取参数,不再传递args
|
||||
// 6. 从argMap读取参数,不再传递args
|
||||
boolean showDetailEnv = MainUtils.parseDetailArg();
|
||||
String serverUrl = MainUtils.getFinalServerUrl();
|
||||
httpPort = MainUtils.getHttpPortFromConfig();
|
||||
|
||||
// 6. 环境信息打印与工具类初始化
|
||||
// 7. 环境信息打印与工具类初始化
|
||||
EnvInfoUtils.printEnvReport(showDetailEnv);
|
||||
// 移除冗余校验:MainUtils.getFinalServerUrl 已实现非法地址过滤
|
||||
initServerUtils(serverUrl);
|
||||
EmailSendUtils.initEmailConfig();
|
||||
|
||||
// 7. 初始化校验,不通过则退出
|
||||
// 8. 初始化校验,不通过则退出
|
||||
boolean initCheckPass = InitCheckUtils.checkAllInitConfig();
|
||||
if (!initCheckPass) {
|
||||
LogUtils.e(TAG, "初始化校验未通过,程序退出");
|
||||
isExit = true;
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
System.out.println("Hello World!");
|
||||
|
||||
// 8. 启动HTTP服务
|
||||
// 9. 启动HTTP服务 + 初始化控制台输入
|
||||
startAuthCenterHttpService();
|
||||
ConsoleInputUtils.initConsoleScanner();
|
||||
// 阻塞等待指令:修复循环逻辑,持续监听控制台输入
|
||||
consoleBlockForService();
|
||||
|
||||
// 9. 资源释放与程序退出
|
||||
// 10. 资源释放与程序退出:新增扫描器关闭,保证资源完整回收
|
||||
ConsoleInputUtils.closeConsoleScanner();
|
||||
MainUtils.releaseAllResources(httpService);
|
||||
LogUtils.i(TAG, "【函数结束】main(),程序正常退出");
|
||||
}
|
||||
@@ -101,46 +113,55 @@ public class Main {
|
||||
String errMsg = "HTTP服务启动失败!端口 " + httpPort + " 可能被占用";
|
||||
System.err.println(errMsg);
|
||||
LogUtils.e(TAG, "【函数异常】startAuthCenterHttpService(),HTTP服务启动失败", e);
|
||||
// 启动失败时标记为需要退出
|
||||
isExit = true;
|
||||
ConsoleInputUtils.forceExit();
|
||||
ConsoleInputUtils.closeConsoleScanner();
|
||||
MainUtils.releaseAllResources(httpService);
|
||||
System.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 服务常驻阻塞逻辑
|
||||
* 服务常驻阻塞逻辑(核心修复:持续调用checkConsoleCommand,实现控制台输入的永久监听)
|
||||
*/
|
||||
private static void consoleBlockForService() {
|
||||
LogUtils.d(TAG, "【函数调用】consoleBlockForService(),开始注册关闭钩子与常驻阻塞");
|
||||
// 注册JVM关闭钩子:仅处理外部kill/Ctrl+C信号的兜底停机
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
LogUtils.i(TAG, "收到服务停止信号,执行优雅停机流程");
|
||||
LogUtils.i(TAG, "收到JVM停止信号,执行兜底停机流程");
|
||||
isExit = true;
|
||||
ConsoleInputUtils.forceExit();
|
||||
ConsoleInputUtils.closeConsoleScanner();
|
||||
if (httpService != null && httpService.isRunning()) {
|
||||
httpService.stop();
|
||||
LogUtils.i(TAG, "HTTP服务已优雅停止");
|
||||
LogUtils.i(TAG, "HTTP服务已通过关闭钩子停止");
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
System.out.println("\n服务已常驻运行,输入exit可退出服务,或执行kill 对应Java进程号停机");
|
||||
while (true) {
|
||||
try {
|
||||
if (ConsoleInputUtils.isExit() || ConsoleInputUtils.checkConsoleCommand()) {
|
||||
break;
|
||||
}
|
||||
Thread.sleep(500);
|
||||
} catch (InterruptedException e) {
|
||||
LogUtils.w(TAG, "【阻塞中断】服务阻塞线程被中断,准备退出循环", e);
|
||||
ConsoleInputUtils.forceExit();
|
||||
ConsoleInputUtils.closeConsoleScanner();
|
||||
Thread.currentThread().interrupt();
|
||||
break;
|
||||
}
|
||||
// 核心修复:循环持续调用checkConsoleCommand
|
||||
// 利用checkConsoleCommand内部的scanner.hasNextLine()实现阻塞,无输入时不占用CPU
|
||||
while (!isExit) {
|
||||
ConsoleInputUtils.checkConsoleCommand();
|
||||
}
|
||||
LogUtils.d(TAG, "【函数结束】consoleBlockForService(),常驻阻塞流程退出");
|
||||
}
|
||||
|
||||
/**
|
||||
* 对外暴露退出状态查询方法(供其他工具类调用)
|
||||
*/
|
||||
public static boolean isExit() {
|
||||
return isExit;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对外暴露退出状态设置方法(供ConsoleInputUtils等工具类调用)
|
||||
*/
|
||||
public static void setExit(boolean exit) {
|
||||
isExit = exit;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,8 +13,8 @@ import java.util.concurrent.TimeUnit;
|
||||
* 邮件验证码认证工具类,单例模式,统一管理验证码发送、存储、校验与过期清理
|
||||
* 适配Java7语法,兼容项目邮件工具类与Termux环境,支持验证码时效自动管控
|
||||
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2026-01-17
|
||||
* @LastEditTime 2026-01-17
|
||||
* @Date 2026/01/17 10:00:00
|
||||
* @LastEditTime 2026/01/20 20:00:00
|
||||
*/
|
||||
public class MailAuthUtils {
|
||||
private static final String TAG = "MailAuthUtils";
|
||||
@@ -84,6 +84,34 @@ public class MailAuthUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 【新增缺失方法】手动保存验证码记录(用于测试/特殊场景)
|
||||
* @param email 目标邮箱
|
||||
* @param code 验证码内容
|
||||
*/
|
||||
public void saveVerifyCode(String email, String code) {
|
||||
LogUtils.d(TAG, "saveVerifyCode 函数调用,邮箱:" + email + " 验证码:" + code);
|
||||
if (email == null || code == null || email.trim().isEmpty() || code.trim().isEmpty()) {
|
||||
LogUtils.w(TAG, "手动保存验证码失败:邮箱或验证码为空");
|
||||
return;
|
||||
}
|
||||
String trimEmail = email.trim();
|
||||
// 测试场景:设置有效期为默认时长
|
||||
long expireTime = System.currentTimeMillis() + CODE_EXPIRE_MINUTES * 60 * 1000L;
|
||||
emailCodeExpireMap.put(trimEmail, code + "_" + expireTime);
|
||||
LogUtils.d(TAG, "手动保存验证码成功|邮箱[" + trimEmail + "] 验证码[" + code + "]");
|
||||
}
|
||||
|
||||
/**
|
||||
* 【新增缺失方法】获取所有验证码记录的映射表(用于测试验证)
|
||||
* @return 完整的验证码-过期时间映射表
|
||||
*/
|
||||
public Map<String, String> getAllVerifyCode() {
|
||||
LogUtils.d(TAG, "getAllVerifyCode 函数调用,当前记录数:" + emailCodeExpireMap.size());
|
||||
// 返回新的HashMap对象,避免外部修改内部存储
|
||||
return new HashMap<String, String>(emailCodeExpireMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* 核心方法:校验邮箱与验证码,自动判断是否过期
|
||||
* @param email 待校验邮箱
|
||||
@@ -155,6 +183,31 @@ public class MailAuthUtils {
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 公开方法:清空所有验证码记录(适配控制台手动清理指令)
|
||||
*/
|
||||
public void clearAllVerifyCode() {
|
||||
if (emailCodeExpireMap.isEmpty()) {
|
||||
LogUtils.d(TAG, "清空验证码记录:当前无任何验证码记录,无需清理");
|
||||
System.out.println("当前无验证码记录,无需清空");
|
||||
return;
|
||||
}
|
||||
int recordCount = emailCodeExpireMap.size();
|
||||
emailCodeExpireMap.clear();
|
||||
LogUtils.i(TAG, "已手动清空所有验证码记录,本次清空数量:" + recordCount + "条");
|
||||
}
|
||||
|
||||
/**
|
||||
* 兜底方法:关闭定时线程池,程序退出时调用,避免线程泄露
|
||||
* 可在Main类释放资源方法中调用
|
||||
*/
|
||||
public void shutdownScheduler() {
|
||||
if (scheduler != null && !scheduler.isShutdown()) {
|
||||
scheduler.shutdown();
|
||||
LogUtils.d(TAG, "定时清理线程池已关闭");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 内部定时任务:清理过期验证码,避免内存堆积
|
||||
*/
|
||||
@@ -193,30 +246,5 @@ public class MailAuthUtils {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 兜底方法:关闭定时线程池,程序退出时调用,避免线程泄露
|
||||
* 可在Main类释放资源方法中调用
|
||||
*/
|
||||
public void shutdownScheduler() {
|
||||
if (scheduler != null && !scheduler.isShutdown()) {
|
||||
scheduler.shutdown();
|
||||
LogUtils.d(TAG, "定时清理线程池已关闭");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 公开方法:清空所有验证码记录(适配控制台手动清理指令)
|
||||
*/
|
||||
public void clearAllVerifyCode() {
|
||||
if (emailCodeExpireMap.isEmpty()) {
|
||||
LogUtils.d(TAG, "清空验证码记录:当前无任何验证码记录,无需清理");
|
||||
System.out.println("当前无验证码记录,无需清空");
|
||||
return;
|
||||
}
|
||||
int recordCount = emailCodeExpireMap.size();
|
||||
emailCodeExpireMap.clear();
|
||||
LogUtils.i(TAG, "已手动清空所有验证码记录,本次清空数量:" + recordCount + "条");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
371
src/cc/winboll/test/ConsoleCmdAutoTest.java
Normal file
371
src/cc/winboll/test/ConsoleCmdAutoTest.java
Normal file
@@ -0,0 +1,371 @@
|
||||
package cc.winboll.test;
|
||||
|
||||
import cc.winboll.Main;
|
||||
import cc.winboll.LogUtils;
|
||||
import cc.winboll.auth.MailAuthUtils;
|
||||
import cc.winboll.util.ConsoleInputUtils;
|
||||
import cc.winboll.util.EmailSendUtils;
|
||||
import cc.winboll.util.EnvInfoUtils;
|
||||
import cc.winboll.util.HelpInfoUtils;
|
||||
import cc.winboll.util.IniConfigUtils;
|
||||
import cc.winboll.util.MainUtils;
|
||||
import cc.winboll.util.ServerUtils;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
* AuthCenter 控制台指令自动化测试类
|
||||
* 适配 Java7 语言规范与 Android API30 运行环境
|
||||
* 自动执行控制台指令测试用例,生成结构化测试报告
|
||||
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2026/01/20 10:00:00
|
||||
* @LastEditTime 2026/01/21 09:00:00
|
||||
*/
|
||||
public class ConsoleCmdAutoTest {
|
||||
// ========== 静态常量(不可变) ==========
|
||||
private static final String TAG = "ConsoleCmdAutoTest";
|
||||
// 项目根目录(配置为指定路径)
|
||||
private static final String PROJECT_ROOT_DIR = "/sdcard/ZhanGSKen/Sources/AuthCenterConsoleApp/";
|
||||
// 配置文件路径(基于根目录拼接)
|
||||
private static final String CONFIG_FILE_PATH = PROJECT_ROOT_DIR + "config.ini";
|
||||
// 日志文件夹路径(基于根目录拼接)
|
||||
private static final String LOG_DIR_PATH = PROJECT_ROOT_DIR + "logs/";
|
||||
// 报告文件夹路径 + 报告文件名(基于根目录拼接,存储到 reports 文件夹)
|
||||
private static final String REPORT_DIR_PATH = PROJECT_ROOT_DIR + "reports/";
|
||||
private static final String REPORT_FILE = REPORT_DIR_PATH + "test-report.txt";
|
||||
|
||||
// ========== 静态属性(可变) ==========
|
||||
private static List<TestCase> testCaseList = new ArrayList<TestCase>();
|
||||
private static int passCount = 0;
|
||||
private static int failCount = 0;
|
||||
|
||||
// ========== 内部测试用例数据结构 ==========
|
||||
static class TestCase {
|
||||
String caseName;
|
||||
String caseDesc;
|
||||
Runnable testAction;
|
||||
boolean isPass;
|
||||
String failReason;
|
||||
|
||||
public TestCase(String caseName, String caseDesc, Runnable testAction) {
|
||||
this.caseName = caseName;
|
||||
this.caseDesc = caseDesc;
|
||||
this.testAction = testAction;
|
||||
this.isPass = false;
|
||||
this.failReason = "";
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 程序入口主函数 ==========
|
||||
public static void main(String[] args) {
|
||||
LogUtils.d(TAG, "【函数调用】main(),自动化测试程序启动");
|
||||
|
||||
// 执行测试流程
|
||||
initTestEnv();
|
||||
registerTestCases();
|
||||
runAllTestCases();
|
||||
generateTestReport();
|
||||
|
||||
// 输出测试总结
|
||||
String summary = String.format("测试完成!总计:%d 用例 | 通过:%d | 失败:%d",
|
||||
testCaseList.size(), passCount, failCount);
|
||||
System.out.println("\n=====================================");
|
||||
System.out.println(summary);
|
||||
System.out.println("报告文件路径:" + new File(REPORT_FILE).getAbsolutePath());
|
||||
LogUtils.i(TAG, "【函数结束】main()," + summary);
|
||||
}
|
||||
|
||||
// ========== 工具函数(按执行顺序排列) ==========
|
||||
|
||||
/**
|
||||
* 初始化测试环境:模拟启动参数、加载指定配置、初始化依赖工具类
|
||||
* 新增:创建报告目录 + 日志目录
|
||||
*/
|
||||
private static void initTestEnv() {
|
||||
LogUtils.d(TAG, "【函数调用】initTestEnv()");
|
||||
try {
|
||||
// 1. 创建日志目录(确保日志正常输出)
|
||||
File logDir = new File(LOG_DIR_PATH);
|
||||
if (!logDir.exists()) {
|
||||
boolean mkdirSuccess = logDir.mkdirs();
|
||||
if (mkdirSuccess) {
|
||||
LogUtils.d(TAG, "【路径初始化】日志目录创建成功:" + LOG_DIR_PATH);
|
||||
} else {
|
||||
LogUtils.w(TAG, "【路径初始化】日志目录创建失败:" + LOG_DIR_PATH);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 创建报告目录(确保测试报告能正常生成)
|
||||
File reportDir = new File(REPORT_DIR_PATH);
|
||||
if (!reportDir.exists()) {
|
||||
boolean mkdirSuccess = reportDir.mkdirs();
|
||||
if (mkdirSuccess) {
|
||||
LogUtils.d(TAG, "【路径初始化】报告目录创建成功:" + REPORT_DIR_PATH);
|
||||
} else {
|
||||
LogUtils.w(TAG, "【路径初始化】报告目录创建失败:" + REPORT_DIR_PATH);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 模拟启动参数并解析(添加配置文件路径参数)
|
||||
String[] mockArgs = {"-v", "-detail", "-log:INFO", "-config:" + CONFIG_FILE_PATH};
|
||||
MainUtils.parseArgs(mockArgs);
|
||||
LogUtils.d(TAG, "【参数信息】模拟启动参数:" + MainUtils.arrayToString(mockArgs));
|
||||
|
||||
// 4. 加载指定路径的配置文件
|
||||
IniConfigUtils.loadConfig(CONFIG_FILE_PATH);
|
||||
LogUtils.d(TAG, "【配置加载】已加载指定配置文件:" + CONFIG_FILE_PATH);
|
||||
|
||||
// 5. 设置日志级别与日志输出目录
|
||||
LogUtils.setGlobalLogLevel(Level.INFO);
|
||||
LogUtils.setLogDir(LOG_DIR_PATH);
|
||||
|
||||
// 6. 初始化核心工具类
|
||||
EnvInfoUtils.printEnvReport(true);
|
||||
ServerUtils.initServerUrl(MainUtils.getFinalServerUrl());
|
||||
EmailSendUtils.initEmailConfig();
|
||||
|
||||
System.out.println("✅ 测试环境初始化完成");
|
||||
LogUtils.d(TAG, "【函数结束】initTestEnv(),初始化成功");
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "【函数异常】initTestEnv(),初始化失败", e);
|
||||
throw new RuntimeException("Test environment initialization failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册所有控制台指令测试用例
|
||||
* 【Java7 兼容改造】移除 Lambda 表达式,替换为匿名内部类
|
||||
*/
|
||||
private static void registerTestCases() {
|
||||
LogUtils.d(TAG, "【函数调用】registerTestCases(),开始注册测试用例");
|
||||
|
||||
// 用例1:help指令响应测试
|
||||
testCaseList.add(new TestCase(
|
||||
"help指令响应测试",
|
||||
"验证help指令能否正常打印帮助信息",
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
ConsoleInputUtils.initConsoleScanner();
|
||||
HelpInfoUtils.printFullHelpInfo();
|
||||
testCaseList.get(0).isPass = true;
|
||||
LogUtils.d(TAG, "【用例结果】help指令测试通过");
|
||||
} catch (Exception e) {
|
||||
testCaseList.get(0).failReason = "帮助信息打印异常:" + e.getMessage();
|
||||
LogUtils.w(TAG, "【用例结果】help指令测试失败:" + testCaseList.get(0).failReason);
|
||||
}
|
||||
}
|
||||
}
|
||||
));
|
||||
|
||||
// 用例2:exit指令优雅停机测试
|
||||
testCaseList.add(new TestCase(
|
||||
"exit指令停机测试",
|
||||
"验证exit指令能否设置isExit=true并触发优雅停机",
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Main.setExit(false);
|
||||
ConsoleInputUtils.handleExitCmd();
|
||||
if (Main.isExit()) {
|
||||
testCaseList.get(1).isPass = true;
|
||||
LogUtils.d(TAG, "【用例结果】exit指令测试通过,isExit状态已更新为true");
|
||||
} else {
|
||||
testCaseList.get(1).failReason = "isExit状态未更新为true";
|
||||
LogUtils.w(TAG, "【用例结果】exit指令测试失败:" + testCaseList.get(1).failReason);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
testCaseList.get(1).failReason = "exit指令执行异常:" + e.getMessage();
|
||||
LogUtils.w(TAG, "【用例结果】exit指令测试异常:" + testCaseList.get(1).failReason);
|
||||
}
|
||||
}
|
||||
}
|
||||
));
|
||||
|
||||
// 用例3:selftestmail指令测试
|
||||
testCaseList.add(new TestCase(
|
||||
"selftestmail指令测试",
|
||||
"验证邮件自测指令能否正常执行(需正确配置INI)",
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
boolean initResult = EmailSendUtils.initEmailConfig();
|
||||
if (!initResult) {
|
||||
testCaseList.get(2).failReason = "邮件配置初始化失败,请检查INI文件[EmailConfig]";
|
||||
LogUtils.w(TAG, "【用例结果】selftestmail指令测试失败:" + testCaseList.get(2).failReason);
|
||||
return;
|
||||
}
|
||||
String testCode = String.valueOf((int) (Math.random() * 900000 + 100000));
|
||||
EmailSendUtils.sendSelfTestEmail(testCode);
|
||||
testCaseList.get(2).isPass = true;
|
||||
LogUtils.d(TAG, "【用例结果】selftestmail指令测试通过,测试码:" + testCode);
|
||||
} catch (Exception e) {
|
||||
testCaseList.get(2).failReason = "邮件自测异常:" + e.getMessage();
|
||||
LogUtils.w(TAG, "【用例结果】selftestmail指令测试异常:" + testCaseList.get(2).failReason);
|
||||
}
|
||||
}
|
||||
}
|
||||
));
|
||||
|
||||
// 用例4:clearverifycode指令测试
|
||||
testCaseList.add(new TestCase(
|
||||
"clearverifycode指令测试",
|
||||
"验证清空验证码指令能否清空MailAuthUtils缓存",
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
// 预置测试数据
|
||||
MailAuthUtils.getInstance().saveVerifyCode("test@qq.com", "123456");
|
||||
// 执行清空操作
|
||||
MailAuthUtils.getInstance().clearAllVerifyCode();
|
||||
// 验证结果
|
||||
if (MailAuthUtils.getInstance().getAllVerifyCode().isEmpty()) {
|
||||
testCaseList.get(3).isPass = true;
|
||||
LogUtils.d(TAG, "【用例结果】clearverifycode指令测试通过");
|
||||
} else {
|
||||
testCaseList.get(3).failReason = "验证码缓存未清空";
|
||||
LogUtils.w(TAG, "【用例结果】clearverifycode指令测试失败:" + testCaseList.get(3).failReason);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
testCaseList.get(3).failReason = "清空验证码异常:" + e.getMessage();
|
||||
LogUtils.w(TAG, "【用例结果】clearverifycode指令测试异常:" + testCaseList.get(3).failReason);
|
||||
}
|
||||
}
|
||||
}
|
||||
));
|
||||
|
||||
// 用例5:testserver指令测试
|
||||
testCaseList.add(new TestCase(
|
||||
"testserver指令测试",
|
||||
"验证服务器连通性测试指令能否正常执行",
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
boolean testResult = ServerUtils.testServerConnectivity();
|
||||
testCaseList.get(4).isPass = true;
|
||||
testCaseList.get(4).failReason = "服务器连通性结果:" + (testResult ? "可达" : "不可达");
|
||||
LogUtils.d(TAG, "【用例结果】testserver指令测试通过," + testCaseList.get(4).failReason);
|
||||
} catch (Exception e) {
|
||||
testCaseList.get(4).failReason = "服务器测试异常:" + e.getMessage();
|
||||
LogUtils.w(TAG, "【用例结果】testserver指令测试异常:" + testCaseList.get(4).failReason);
|
||||
}
|
||||
}
|
||||
}
|
||||
));
|
||||
|
||||
// 用例6:未识别指令测试
|
||||
testCaseList.add(new TestCase(
|
||||
"未识别指令测试",
|
||||
"验证输入未知指令能否正常提示",
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
ConsoleInputUtils.handleCustomCmd("abc123");
|
||||
testCaseList.get(5).isPass = true;
|
||||
LogUtils.d(TAG, "【用例结果】未识别指令测试通过");
|
||||
} catch (Exception e) {
|
||||
testCaseList.get(5).failReason = "未识别指令处理异常:" + e.getMessage();
|
||||
LogUtils.w(TAG, "【用例结果】未识别指令测试异常:" + testCaseList.get(5).failReason);
|
||||
}
|
||||
}
|
||||
}
|
||||
));
|
||||
|
||||
LogUtils.d(TAG, String.format("【函数结束】registerTestCases(),成功注册 %d 个测试用例", testCaseList.size()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行所有已注册的测试用例
|
||||
*/
|
||||
private static void runAllTestCases() {
|
||||
LogUtils.d(TAG, "【函数调用】runAllTestCases(),开始执行测试用例");
|
||||
System.out.println("\n=====================================");
|
||||
System.out.println("开始执行 " + testCaseList.size() + " 个测试用例...");
|
||||
|
||||
for (int i = 0; i < testCaseList.size(); i++) {
|
||||
TestCase tc = testCaseList.get(i);
|
||||
System.out.println("\n[" + (i + 1) + "] 执行用例:" + tc.caseName);
|
||||
try {
|
||||
tc.testAction.run();
|
||||
if (tc.isPass) {
|
||||
passCount++;
|
||||
System.out.println("✅ 用例通过");
|
||||
} else {
|
||||
failCount++;
|
||||
System.out.println("❌ 用例失败:" + tc.failReason);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
failCount++;
|
||||
tc.isPass = false;
|
||||
tc.failReason = "执行异常:" + e.getMessage();
|
||||
System.out.println("❌ 用例执行异常:" + tc.failReason);
|
||||
LogUtils.e(TAG, String.format("【用例异常】第 %d 个用例执行失败", i + 1), e);
|
||||
}
|
||||
}
|
||||
LogUtils.d(TAG, "【函数结束】runAllTestCases(),测试用例执行完毕");
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成结构化测试报告文件
|
||||
* 调整:报告文件存储到根目录的 reports 文件夹
|
||||
*/
|
||||
private static void generateTestReport() {
|
||||
LogUtils.d(TAG, "【函数调用】generateTestReport(),开始生成测试报告");
|
||||
BufferedWriter writer = null;
|
||||
try {
|
||||
writer = new BufferedWriter(new FileWriter(REPORT_FILE));
|
||||
// 写入报告头部
|
||||
writer.write("=== AuthCenter 控制台指令自动化测试报告 ===\n");
|
||||
writer.write("测试时间:" + MainUtils.getCurrentTime() + "\n");
|
||||
writer.write("测试环境:Java7 + Android API30\n");
|
||||
writer.write("项目根目录:" + PROJECT_ROOT_DIR + "\n");
|
||||
writer.write("配置文件路径:" + CONFIG_FILE_PATH + "\n");
|
||||
writer.write("日志文件夹路径:" + LOG_DIR_PATH + "\n");
|
||||
writer.write("=====================================\n");
|
||||
writer.write("总计用例数:" + testCaseList.size() + "\n");
|
||||
writer.write("通过用例数:" + passCount + "\n");
|
||||
writer.write("失败用例数:" + failCount + "\n");
|
||||
writer.write(String.format("测试通过率:%.2f%%\n", (passCount * 100.0) / testCaseList.size()));
|
||||
writer.write("=====================================\n\n");
|
||||
|
||||
// 写入每个用例详情
|
||||
for (int i = 0; i < testCaseList.size(); i++) {
|
||||
TestCase tc = testCaseList.get(i);
|
||||
writer.write("【用例 " + (i + 1) + "】 " + tc.caseName + "\n");
|
||||
writer.write("描述:" + tc.caseDesc + "\n");
|
||||
writer.write("结果:" + (tc.isPass ? "✅ 通过" : "❌ 失败") + "\n");
|
||||
if (!tc.isPass) {
|
||||
writer.write("失败原因:" + tc.failReason + "\n");
|
||||
}
|
||||
writer.write("\n");
|
||||
}
|
||||
|
||||
System.out.println("\n✅ 测试报告生成完成");
|
||||
LogUtils.d(TAG, "【函数结束】generateTestReport(),报告路径:" + new File(REPORT_FILE).getAbsolutePath());
|
||||
} catch (IOException e) {
|
||||
LogUtils.e(TAG, "【函数异常】generateTestReport(),报告生成失败", e);
|
||||
System.err.println("❌ 报告生成失败:" + e.getMessage());
|
||||
} finally {
|
||||
if (writer != null) {
|
||||
try {
|
||||
writer.close();
|
||||
} catch (IOException e) {
|
||||
LogUtils.e(TAG, "【流关闭异常】BufferedWriter 关闭失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package cc.winboll.util;
|
||||
|
||||
import cc.winboll.LogUtils;
|
||||
import cc.winboll.Main;
|
||||
import cc.winboll.auth.MailAuthUtils;
|
||||
import java.util.Scanner;
|
||||
|
||||
@@ -10,7 +11,7 @@ import java.util.Scanner;
|
||||
* 支持help/exit/selftestmail/clearverifycode/testserver 5大指令
|
||||
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2026/01/14 00:00:00
|
||||
* @LastEditTime 2026/01/17 17:00:00
|
||||
* @LastEditTime 2026/01/19 23:00:00
|
||||
*/
|
||||
public class ConsoleInputUtils {
|
||||
private static final String TAG = "ConsoleInputUtils";
|
||||
@@ -20,7 +21,6 @@ public class ConsoleInputUtils {
|
||||
public static final String CMD_CLEAR_VERIFY_CODE = "clearverifycode";
|
||||
public static final String CMD_TEST_SERVER = "testserver"; // 新增:服务器连通性测试指令
|
||||
private static Scanner scanner;
|
||||
private static volatile boolean isExit = false;
|
||||
private static boolean isScannerInit = false;
|
||||
|
||||
/**
|
||||
@@ -33,7 +33,8 @@ public class ConsoleInputUtils {
|
||||
}
|
||||
LogUtils.d(TAG, "【函数调用】initConsoleScanner(),初始化控制台输入监听");
|
||||
try {
|
||||
scanner = new Scanner(System.in);
|
||||
// 核心:设置Scanner为阻塞模式,兼容标准输入流
|
||||
scanner = new Scanner(System.in, "UTF-8");
|
||||
isScannerInit = true;
|
||||
// 提示语补充 testserver 指令
|
||||
System.out.println("\n💡 输入 " + CMD_HELP + " 查看帮助,输入 " + CMD_EXIT + " 退出服务,输入 " + CMD_SELF_TEST_MAIL + " 邮件自测,输入 " + CMD_CLEAR_VERIFY_CODE + " 清空验证码记录,输入 " + CMD_TEST_SERVER + " 测试服务器连通性");
|
||||
@@ -45,46 +46,50 @@ public class ConsoleInputUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查控制台输入指令,分发到对应功能逻辑(非阻塞优化+异常兜底)
|
||||
* 检查控制台输入指令,分发到对应功能逻辑
|
||||
* 完全依赖Main.isExit()判断是否退出,语义:isExit=true 表示需要退出
|
||||
* @return true=需退出服务,false=继续常驻
|
||||
*/
|
||||
public static boolean checkConsoleCommand() {
|
||||
if (isExit) {
|
||||
// 优先判断全局退出状态:isExit=true 直接返回退出
|
||||
if (Main.isExit()) {
|
||||
return true;
|
||||
}
|
||||
if (!isScannerInit || scanner == null) {
|
||||
return isExit;
|
||||
LogUtils.w(TAG, "【指令检查】扫描器未初始化,无法监听输入");
|
||||
return Main.isExit();
|
||||
}
|
||||
|
||||
try {
|
||||
if (!scanner.hasNextLine()) {
|
||||
return isExit;
|
||||
}
|
||||
String input = scanner.nextLine().trim();
|
||||
LogUtils.d(TAG, "检测到控制台输入指令:" + input);
|
||||
// 单次阻塞读取输入
|
||||
if (scanner.hasNextLine()) {
|
||||
String input = scanner.nextLine().trim();
|
||||
LogUtils.d(TAG, "检测到控制台输入指令:" + input);
|
||||
|
||||
// 指令分发-新增 testserver 分支
|
||||
if (CMD_HELP.equalsIgnoreCase(input)) {
|
||||
handleHelpCmd();
|
||||
} else if (CMD_EXIT.equalsIgnoreCase(input)) {
|
||||
handleExitCmd();
|
||||
} else if (CMD_SELF_TEST_MAIL.equalsIgnoreCase(input)) {
|
||||
handleSelfTestMailCmd();
|
||||
} else if (CMD_CLEAR_VERIFY_CODE.equalsIgnoreCase(input)) {
|
||||
handleClearVerifyCodeCmd();
|
||||
} else if (CMD_TEST_SERVER.equalsIgnoreCase(input)) {
|
||||
handleTestServerCmd();
|
||||
} else if (!input.isEmpty()) {
|
||||
handleCustomCmd(input);
|
||||
// 指令分发-新增 testserver 分支
|
||||
if (CMD_HELP.equalsIgnoreCase(input)) {
|
||||
handleHelpCmd();
|
||||
} else if (CMD_EXIT.equalsIgnoreCase(input)) {
|
||||
handleExitCmd();
|
||||
} else if (CMD_SELF_TEST_MAIL.equalsIgnoreCase(input)) {
|
||||
handleSelfTestMailCmd();
|
||||
} else if (CMD_CLEAR_VERIFY_CODE.equalsIgnoreCase(input)) {
|
||||
handleClearVerifyCodeCmd();
|
||||
} else if (CMD_TEST_SERVER.equalsIgnoreCase(input)) {
|
||||
handleTestServerCmd();
|
||||
} else if (!input.isEmpty()) {
|
||||
handleCustomCmd(input);
|
||||
}
|
||||
}
|
||||
} catch (IllegalStateException e) {
|
||||
LogUtils.w(TAG, "【指令检查】扫描器已关闭,停止输入监听", e);
|
||||
isExit = true;
|
||||
Main.setExit(true); // 标记需要退出
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "【指令检查】输入监听异常,终止服务", e);
|
||||
isExit = true;
|
||||
Main.setExit(true); // 标记需要退出
|
||||
}
|
||||
return isExit;
|
||||
// 返回全局退出状态,与方法语义完全匹配
|
||||
return Main.isExit();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -101,13 +106,12 @@ public class ConsoleInputUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理exit指令:强制退出闭环,提前释放资源,杜绝终端阻塞
|
||||
* 处理exit指令:直接设置全局退出状态为true
|
||||
*/
|
||||
private static void handleExitCmd() {
|
||||
public static void handleExitCmd() {
|
||||
LogUtils.d(TAG, "【指令处理】执行exit指令,标记服务退出状态");
|
||||
isExit = true;
|
||||
Main.setExit(true);
|
||||
System.out.println("\n📢 已接收退出指令,正在执行优雅停机...");
|
||||
closeConsoleScanner();
|
||||
System.out.flush();
|
||||
}
|
||||
|
||||
@@ -163,9 +167,8 @@ public class ConsoleInputUtils {
|
||||
|
||||
/**
|
||||
* 处理自定义指令:预留扩展入口,便于后续新增功能
|
||||
* @param cmd 自定义输入指令
|
||||
*/
|
||||
private static void handleCustomCmd(String cmd) {
|
||||
public static void handleCustomCmd(String cmd) {
|
||||
LogUtils.w(TAG, "【指令处理】未识别指令:" + cmd + ",输入help可查看支持的指令");
|
||||
// 提示语补充 testserver 指令
|
||||
System.out.println("❌ 未识别指令[" + cmd + "],输入help查看帮助,输入exit退出服务,输入selftestmail邮件自测,输入clearverifycode清空验证码记录,输入testserver测试服务器连通性");
|
||||
@@ -188,13 +191,12 @@ public class ConsoleInputUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isExit() {
|
||||
return isExit;
|
||||
}
|
||||
|
||||
/**
|
||||
* 强制退出:设置全局退出状态+关闭扫描器
|
||||
*/
|
||||
public static void forceExit() {
|
||||
LogUtils.w(TAG, "【紧急处理】执行强制退出流程");
|
||||
isExit = true;
|
||||
Main.setExit(true);
|
||||
closeConsoleScanner();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,10 +13,10 @@ import java.util.Map;
|
||||
* INI配置文件读取工具类
|
||||
* 适配Android/assets目录与标准JVM文件路径,兼容Java7语法及Android API30
|
||||
* 提供INI配置加载、配置项获取核心能力,支持注释忽略与小节分组解析
|
||||
* 新增:Termux项目根目录config.ini自动加载,适配现有标准INI格式
|
||||
* 新增:Termux项目根目录config.ini自动加载 + 指定路径配置加载,适配现有标准INI格式
|
||||
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2026-01-15 00:00:00
|
||||
* @LastEditTime 2026-01-17 13:00:00
|
||||
* @LastEditTime 2026-01-20 22:00:00
|
||||
*/
|
||||
public class IniConfigUtils {
|
||||
// 日志标记
|
||||
@@ -27,7 +27,7 @@ public class IniConfigUtils {
|
||||
private static final String ROOT_CONFIG_PATH = System.getProperty("user.home") + "/AuthCenterConsoleApp/config.ini";
|
||||
|
||||
/**
|
||||
* 新增:自动加载项目根目录的config.ini(Termux专用,一键读取,无需传流)
|
||||
* 自动加载项目根目录的config.ini(Termux专用,一键读取,无需传流)
|
||||
* 启动自动调用,开箱即用,兼容现有INI格式
|
||||
*/
|
||||
public static void loadRootConfig() {
|
||||
@@ -50,6 +50,34 @@ public class IniConfigUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 【新增缺失方法】加载指定路径的INI配置文件
|
||||
* 适配测试类传入的自定义路径,如/sdcard下的配置文件
|
||||
* @param configFilePath 配置文件的绝对路径
|
||||
*/
|
||||
public static void loadConfig(String configFilePath) {
|
||||
LogUtils.d(TAG, "loadConfig 函数调用,开始加载指定路径配置文件:" + configFilePath);
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
// 清空旧配置,避免多路径加载导致数据冲突
|
||||
iniConfigMap.clear();
|
||||
fis = new FileInputStream(configFilePath);
|
||||
loadIniConfig(fis);
|
||||
LogUtils.i(TAG, "指定路径配置文件加载成功:" + configFilePath);
|
||||
} catch (IOException e) {
|
||||
LogUtils.e(TAG, "加载指定路径config.ini失败!请确认文件在 " + configFilePath, e);
|
||||
System.err.println("❌ 配置文件未找到:" + configFilePath + ",请检查文件存放路径");
|
||||
} finally {
|
||||
if (fis != null) {
|
||||
try {
|
||||
fis.close();
|
||||
} catch (IOException e) {
|
||||
LogUtils.w(TAG, "关闭指定路径配置文件流失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载INI配置文件(统一入口,兼容多环境)
|
||||
* @param inputStream INI文件输入流(Android传assets流,JVM传文件流)
|
||||
@@ -125,5 +153,13 @@ public class IniConfigUtils {
|
||||
LogUtils.d(TAG, "获取配置结果:小节[" + section + "] 键[" + key + "] 值[" + value + "]");
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 【可选新增】获取所有配置小节,便于调试和配置完整性校验
|
||||
* @return 所有小节名称的集合
|
||||
*/
|
||||
public static Map<String, Map<String, String>> getAllConfig() {
|
||||
return new HashMap<String, Map<String, String>>(iniConfigMap);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,8 @@ package cc.winboll.util;
|
||||
import cc.winboll.LogUtils;
|
||||
import cc.winboll.auth.MailAuthUtils;
|
||||
import cc.winboll.service.AuthCenterHttpService;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
@@ -13,7 +15,7 @@ import java.util.logging.Level;
|
||||
* 支持 -key:value 格式参数解析,基于Map存储参数,供外部模块查询
|
||||
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2026/01/18 11:00:00
|
||||
* @LastEditTime 2026/01/18 17:00:00
|
||||
* @LastEditTime 2026/01/19 11:00:00
|
||||
*/
|
||||
public class MainUtils {
|
||||
// 常量迁移:与业务无关的通用常量
|
||||
@@ -136,15 +138,22 @@ public class MainUtils {
|
||||
|
||||
/**
|
||||
* 整合多来源服务器地址,优先级 启动参数 > INI配置 > 默认地址
|
||||
* 新增:校验地址合法性,禁止以'-'开头
|
||||
*/
|
||||
public static String getFinalServerUrl() {
|
||||
LogUtils.d(TAG, "【函数调用】getFinalServerUrl(),整合服务器地址(启动参数>INI>默认)");
|
||||
// 从argMap获取启动参数中的服务器地址
|
||||
String argUrl = argMap.get(ARG_SERVER_URL);
|
||||
if (argUrl != null && !argUrl.trim().isEmpty()) {
|
||||
LogUtils.i(TAG, "服务器地址优先级1:启动参数 → " + argUrl);
|
||||
LogUtils.d(TAG, "【函数返回】getFinalServerUrl(),返回启动参数地址:" + argUrl);
|
||||
return argUrl;
|
||||
// 新增:校验启动参数中的地址,禁止以'-'开头
|
||||
if (argUrl.trim().startsWith("-")) {
|
||||
LogUtils.e(TAG, "【地址校验失败】启动参数中的服务器地址非法:" + argUrl);
|
||||
argUrl = null; // 置空后走后续优先级逻辑
|
||||
} else {
|
||||
LogUtils.i(TAG, "服务器地址优先级1:启动参数 → " + argUrl);
|
||||
LogUtils.d(TAG, "【函数返回】getFinalServerUrl(),返回启动参数地址:" + argUrl);
|
||||
return argUrl;
|
||||
}
|
||||
}
|
||||
|
||||
String iniUrl = IniConfigUtils.getConfigValue(INI_SERVER_SECTION, INI_SERVER_KEY);
|
||||
@@ -220,7 +229,8 @@ public class MainUtils {
|
||||
httpService.stop();
|
||||
LogUtils.i(TAG, "HTTP服务兜底停止完成");
|
||||
}
|
||||
ConsoleInputUtils.closeConsoleScanner();
|
||||
// 修复:添加类全限定名或导入,避免编译错误
|
||||
cc.winboll.util.ConsoleInputUtils.closeConsoleScanner();
|
||||
MailAuthUtils.getInstance().shutdownScheduler();
|
||||
LogUtils.d(TAG, "【函数结束】releaseAllResources(),所有资源释放完成");
|
||||
}
|
||||
@@ -241,5 +251,14 @@ public class MainUtils {
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前时间字符串,格式:yyyy-MM-dd HH:mm:ss
|
||||
* 适配Java7
|
||||
*/
|
||||
public static String getCurrentTime() {
|
||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
return sdf.format(new Date());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user