diff --git a/.classpath b/.classpath
index c9b1baa..6fde173 100644
--- a/.classpath
+++ b/.classpath
@@ -3,6 +3,7 @@
-
+
+
diff --git a/README.md b/README.md
index 7a371ba..6f915be 100644
--- a/README.md
+++ b/README.md
@@ -4,6 +4,8 @@ bash bash/start_winboll_in_termux.sh
测试命令
http://localhost:8080/authcenter/ping
+服务介绍
+http://localhost:8080/authcenter/login?action=
# 服务器端配置
服务器编译命令
@@ -37,3 +39,13 @@ AdminUtils.getInstance().sendTemporaryNotify(
"系统于2026-01-22 15:00 更新了邮件服务器地址,请留意后续通知是否正常。"
);
+
+邮件类库使用
+// Source: https://mvnrepository.com/artifact/javax.activation/activation
+implementation 'javax.activation:activation:1.1.1'
+// Source: https://mvnrepository.com/artifact/com.sun.mail/javax.mail
+implementation 'com.sun.mail:javax.mail:1.4.7'
+// Source: https://mvnrepository.com/artifact/javax.mail/javax.mail-api
+implementation 'javax.mail:javax.mail-api:1.4.7'
+// Source: https://mvnrepository.com/artifact/com.sun.mail/android-mail
+runtimeOnly 'com.sun.mail:android-mail:1.6.2'
diff --git a/libs/activation.jar b/libs/activation-1.1.1.jar
similarity index 100%
rename from libs/activation.jar
rename to libs/activation-1.1.1.jar
diff --git a/libs/android-mail-1.6.2.jar b/libs/android-mail-1.6.2.jar
new file mode 100644
index 0000000..ee6a63b
Binary files /dev/null and b/libs/android-mail-1.6.2.jar differ
diff --git a/libs/jakarta.mail-1.6.7.jar b/libs/jakarta.mail-1.6.7.jar
deleted file mode 100644
index 2fbf851..0000000
Binary files a/libs/jakarta.mail-1.6.7.jar and /dev/null differ
diff --git a/libs/jakarta.mail-api-1.6.7.jar b/libs/jakarta.mail-api-1.6.7.jar
deleted file mode 100644
index ad07d03..0000000
Binary files a/libs/jakarta.mail-api-1.6.7.jar and /dev/null differ
diff --git a/libs/javax.mail.jar b/libs/javax.mail-1.4.7.jar
similarity index 95%
rename from libs/javax.mail.jar
rename to libs/javax.mail-1.4.7.jar
index 236fcdb..87f5d9f 100644
Binary files a/libs/javax.mail.jar and b/libs/javax.mail-1.4.7.jar differ
diff --git a/libs/javax.mail-api-1.4.7.jar b/libs/javax.mail-api-1.4.7.jar
new file mode 100644
index 0000000..187cbe3
Binary files /dev/null and b/libs/javax.mail-api-1.4.7.jar differ
diff --git a/src/Main.java b/src/Main.java
index 7bb7c8c..889c5b5 100644
--- a/src/Main.java
+++ b/src/Main.java
@@ -1,5 +1,5 @@
import cc.winboll.LogUtils;
-import cc.winboll.auth.RSAUtils;
+import cc.winboll.test.ConsoleCmdAutoTest;
import cc.winboll.util.IniConfigUtils;
import java.util.Date;
import java.util.logging.Level;
@@ -45,9 +45,7 @@ public class Main {
System.out.println("\n【配置日志测试】...");
// 单元测试
- LogUtils.test(); // 执行测试
- RSAUtils.test();
- //ConsoleCmdAutoTest.main(args);
+ ConsoleCmdAutoTest.main(args);
LogUtils.i("Main", "正在处理事务...");
diff --git a/src/cc/winboll/WinBoLL.java b/src/cc/winboll/WinBoLL.java
index b1b4ef6..ab0738a 100644
--- a/src/cc/winboll/WinBoLL.java
+++ b/src/cc/winboll/WinBoLL.java
@@ -24,7 +24,7 @@ import java.util.logging.Level;
* 支持控制台输入交互,输入help查帮助、输入exit退出、其他指令提示未识别
* @Author 豆包&ZhanGSKen
* @Date 2026-01-14 00:00:00
- * @LastEditTime 2026-01-26 17:18:36
+ * @LastEditTime 2026-01-26 适配邮件工具单例调用
*/
public class WinBoLL {
// ========== 静态属性(常量在前 变量在后,顺序规整) ==========
@@ -40,7 +40,7 @@ public class WinBoLL {
// 解析启动参数 + 加载INI根配置
MainUtils.parseArgs(args);
LogUtils.d(TAG, "【参数信息】main(),传入启动参数:" + MainUtils.arrayToString(args));
-
+
// 读取项目根目录 初始化日志目录
String projectRootDir = "./";
@@ -61,7 +61,8 @@ public class WinBoLL {
// 环境信息打印 + 工具类初始化
EnvInfoUtils.printEnvReport(showDetailEnv);
initServerUtils(serverUrl);
- EmailSendUtils.initEmailConfig();
+ // 调整:邮件工具单例初始化,增加结果判断+日志输出
+ initEmailSendUtils();
// 启动配置校验
boolean initCheckPass = InitCheckUtils.checkAllInitConfig();
@@ -103,6 +104,22 @@ public class WinBoLL {
LogUtils.d(TAG, "【函数结束】initServerUtils(),服务器工具类初始化就绪");
}
+ /**
+ * 调整:独立封装邮件工具初始化,适配单例,增加状态日志
+ */
+ private static void initEmailSendUtils() {
+ LogUtils.d(TAG, "【函数调用】initEmailSendUtils(),开始初始化邮件发送工具");
+ EmailSendUtils emailUtils = EmailSendUtils.getInstance();
+ boolean initResult = emailUtils.initEmailConfig();
+ if (initResult) {
+ System.out.println("✅ EmailSendUtils 初始化完成,可正常发送邮件");
+ LogUtils.i(TAG, "邮件发送工具初始化就绪");
+ } else {
+ System.out.println("⚠️ EmailSendUtils 初始化失败,邮件相关功能不可用,请检查INI邮箱配置");
+ LogUtils.w(TAG, "邮件发送工具初始化失败,不影响核心服务启动");
+ }
+ }
+
/**
* 启动AuthCenter HTTP服务,绑定控制台重启实例
*/
diff --git a/src/cc/winboll/auth/ActionUtils.java b/src/cc/winboll/auth/ActionUtils.java
new file mode 100644
index 0000000..bfaf07a
--- /dev/null
+++ b/src/cc/winboll/auth/ActionUtils.java
@@ -0,0 +1,5 @@
+package cc.winboll.auth;
+
+public class ActionUtils
+{
+}
diff --git a/src/cc/winboll/auth/MailAuthUtils.java b/src/cc/winboll/auth/MailAuthUtils.java
index 11b2a1c..69c0d50 100644
--- a/src/cc/winboll/auth/MailAuthUtils.java
+++ b/src/cc/winboll/auth/MailAuthUtils.java
@@ -14,7 +14,7 @@ import java.util.concurrent.TimeUnit;
* 适配Java7语法,兼容项目邮件工具类与Termux环境,支持验证码时效自动管控
* @Author 豆包&ZhanGSKen
* @Date 2026/01/17 10:00:00
- * @LastEditTime 2026/01/20 20:00:00
+ * @LastEditTime 2026/01/26 适配邮件工具单例调用
*/
public class MailAuthUtils {
private static final String TAG = "MailAuthUtils";
@@ -71,8 +71,21 @@ public class MailAuthUtils {
String verifyCode = generateVerifyCode();
// 2. 计算过期时间戳(当前时间+有效期,单位毫秒)
long expireTime = System.currentTimeMillis() + CODE_EXPIRE_MINUTES * 60 * 1000L;
- // 3. 发送验证码邮件
- boolean sendResult = EmailSendUtils.sendVerifyCodeEmail(trimEmail, verifyCode);
+
+ // ========== 核心调整:适配邮件单例 + 补充初始化判断 ==========
+ EmailSendUtils emailUtils = EmailSendUtils.getInstance();
+ // 未初始化则执行初始化,失败直接返回
+ if (!emailUtils.isConfigInited()) {
+ LogUtils.d(TAG, "邮件工具未就绪,执行初始化流程");
+ boolean initOk = emailUtils.initEmailConfig();
+ if (!initOk) {
+ LogUtils.e(TAG, "邮件工具初始化失败,无法发送验证码");
+ return null;
+ }
+ }
+ // 3. 单例调用发送验证码邮件
+ boolean sendResult = emailUtils.sendVerifyCodeEmail(trimEmail, verifyCode);
+
if (sendResult) {
// 存储格式:验证码_过期时间戳
emailCodeExpireMap.put(trimEmail, verifyCode + "_" + expireTime);
@@ -173,7 +186,6 @@ public class MailAuthUtils {
/**
* 辅助方法:生成指定长度纯数字验证码
- * @return 6位数字验证码
*/
private String generateVerifyCode() {
StringBuilder sb = new StringBuilder();
diff --git a/src/cc/winboll/models/ActionModel.java b/src/cc/winboll/models/ActionModel.java
new file mode 100644
index 0000000..28d1b95
--- /dev/null
+++ b/src/cc/winboll/models/ActionModel.java
@@ -0,0 +1,5 @@
+package cc.winboll.models;
+
+public class ActionModel
+{
+}
\ No newline at end of file
diff --git a/src/cc/winboll/models/UserModel.java b/src/cc/winboll/models/UserModel.java
new file mode 100644
index 0000000..441d415
--- /dev/null
+++ b/src/cc/winboll/models/UserModel.java
@@ -0,0 +1,194 @@
+package cc.winboll.models;
+
+import cc.winboll.LogUtils;
+import java.util.Date;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * 用户模型类(密码MD5加密存储+密码校验+LogUtils日志输出)
+ * @Author 豆包&ZhanGSKen
+ * @Date 2026-01-20 11:45:00
+ * @LastEditTime 2026-01-20 12:05:00
+ */
+public class UserModel {
+ // 成员变量(按业务逻辑排序)
+ private Long userId; // 用户ID
+ private String userEmail; // 用户邮箱
+ private String userPassword; // 用户密码(MD5加密后存储)
+ private String token; // 连接token
+ private Date loginTime; // 登录时间
+ private Date lastTokenUpdateTime; // 最后Token更新时间
+ private Integer loginCount; // 登录次数计数(默认0)
+
+ // 空参构造(Java7规范,必备)
+ public UserModel() {
+ this.loginCount = 0; // 登录次数默认初始化0
+ }
+
+ // Getter & Setter 方法(按字段顺序)
+ public Long getUserId() {
+ return userId;
+ }
+
+ public void setUserId(Long userId) {
+ this.userId = userId;
+ }
+
+ public String getUserEmail() {
+ return userEmail;
+ }
+
+ public void setUserEmail(String userEmail) {
+ this.userEmail = userEmail;
+ }
+
+ // 密码Setter:传入明文自动MD5加密后存储
+ public void setUserPassword(String userPassword) {
+ this.userPassword = encryptMD5(userPassword);
+ }
+
+ // 密码Getter:返回加密后密码(不对外暴露明文)
+ public String getUserPassword() {
+ return userPassword;
+ }
+
+ public String getToken() {
+ return token;
+ }
+
+ public void setToken(String token) {
+ this.token = token;
+ }
+
+ public Date getLoginTime() {
+ return loginTime;
+ }
+
+ public void setLoginTime(Date loginTime) {
+ this.loginTime = loginTime;
+ }
+
+ public Date getLastTokenUpdateTime() {
+ return lastTokenUpdateTime;
+ }
+
+ public void setLastTokenUpdateTime(Date lastTokenUpdateTime) {
+ this.lastTokenUpdateTime = lastTokenUpdateTime;
+ }
+
+ public Integer getLoginCount() {
+ return loginCount;
+ }
+
+ public void setLoginCount(Integer loginCount) {
+ this.loginCount = loginCount;
+ }
+
+ // 登录次数自增方法
+ public void incrementLoginCount() {
+ this.loginCount += 1;
+ }
+
+ /**
+ * 校验密码是否正确
+ * @param checkPwd 待校验的明文密码
+ * @return 密码匹配返回true,不匹配返回false
+ */
+ public boolean checkPassword(String checkPwd) {
+ // 空值防护,避免空指针
+ if (this.userPassword == null || checkPwd == null) {
+ LogUtils.w("UserModel", "密码校验:待校验密码或存储密码为空");
+ return false;
+ }
+ // 明文加密后和存储的密文对比
+ boolean match = this.userPassword.equals(encryptMD5(checkPwd));
+ LogUtils.d("UserModel", "密码校验结果:" + (match ? "匹配成功" : "匹配失败"));
+ return match;
+ }
+
+ /**
+ * Java7兼容 MD5加密工具方法(32位小写)
+ * @param plainText 明文
+ * @return 加密后32位小写字符串,异常返回null
+ */
+ private String encryptMD5(String plainText) {
+ if (plainText == null) {
+ LogUtils.w("UserModel", "MD5加密:明文密码为null");
+ return null;
+ }
+ try {
+ MessageDigest md = MessageDigest.getInstance("MD5");
+ byte[] bytes = md.digest(plainText.getBytes());
+ StringBuilder sb = new StringBuilder();
+ for (byte b : bytes) {
+ int val = b & 0xFF;
+ if (val < 16) {
+ sb.append("0");
+ }
+ sb.append(Integer.toHexString(val));
+ }
+ return sb.toString();
+ } catch (NoSuchAlgorithmException e) {
+ LogUtils.e("UserModel", "MD5加密算法获取失败", e);
+ return null;
+ }
+ }
+
+ // 重写toString,隐藏密码,方便调试
+ @Override
+ public String toString() {
+ return "UserModel{" +
+ "userId=" + userId +
+ ", userEmail='" + userEmail + '\'' +
+ ", token='" + token + '\'' +
+ ", loginTime=" + loginTime +
+ ", lastTokenUpdateTime=" + lastTokenUpdateTime +
+ ", loginCount=" + loginCount +
+ '}';
+ }
+
+ /**
+ * 静态测试方法,用LogUtils输出日志,验证核心功能
+ */
+ public static void test() {
+ String tag = "UserModelTest";
+ LogUtils.i(tag, "==================================== UserModel 测试开始 ====================================");
+
+ // 1. 实例化+基础字段赋值
+ UserModel user = new UserModel();
+ user.setUserId(1001L);
+ user.setUserEmail("test@winboll.cc");
+ user.setToken("TOKEN_2026_ABC123");
+ user.setLoginTime(new Date());
+ user.setLastTokenUpdateTime(new Date());
+ LogUtils.i(tag, "1. 用户基础字段赋值完成");
+
+ // 2. 密码加密&校验测试
+ String plainPwd = "123456";
+ user.setUserPassword(plainPwd);
+ LogUtils.d(tag, "2. 明文密码:" + plainPwd);
+ LogUtils.d(tag, "3. MD5加密后密码:" + user.getUserPassword());
+
+ boolean pwdRight = user.checkPassword(plainPwd);
+ boolean pwdWrong = user.checkPassword("654321");
+ LogUtils.i(tag, "4. 正确密码校验结果:" + pwdRight + "(预期true)");
+ LogUtils.i(tag, "5. 错误密码校验结果:" + pwdWrong + "(预期false)");
+
+ // 3. 登录次数测试
+ LogUtils.i(tag, "6. 初始登录次数:" + user.getLoginCount() + "(预期0)");
+ user.incrementLoginCount();
+ user.incrementLoginCount();
+ LogUtils.i(tag, "7. 2次自增后登录次数:" + user.getLoginCount() + "(预期2)");
+
+ // 4. 空密码校验测试
+ UserModel emptyPwdUser = new UserModel();
+ boolean emptyPwdCheck = emptyPwdUser.checkPassword(null);
+ LogUtils.i(tag, "8. 空密码校验结果:" + emptyPwdCheck + "(预期false)");
+
+ // 5. 完整对象打印
+ LogUtils.i(tag, "9. 用户对象完整信息:" + user.toString());
+ LogUtils.i(tag, "==================================== UserModel 测试结束 ====================================\n");
+ }
+}
+
diff --git a/src/cc/winboll/test/ConsoleCmdAutoTest.java b/src/cc/winboll/test/ConsoleCmdAutoTest.java
index 0278774..5d7060c 100644
--- a/src/cc/winboll/test/ConsoleCmdAutoTest.java
+++ b/src/cc/winboll/test/ConsoleCmdAutoTest.java
@@ -2,47 +2,46 @@ package cc.winboll.test;
import cc.winboll.LogUtils;
import cc.winboll.auth.MailAuthUtils;
+import cc.winboll.auth.RSAUtils;
+import cc.winboll.models.UserModel;
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 cc.winboll.util.IniConfigUtils;
+import cc.winboll.util.EnvInfoUtils;
+import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
+import java.io.FileReader;
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 运行环境
- * 自动执行控制台指令测试用例,生成结构化测试报告
+ * 自动执行控制台指令测试用例,生成结构化测试报告 + 发送报告到配置邮箱
* 核心优化:从config.ini读取配置,动态初始化测试路径与参数,根目录配置缺失直接日志退出
- * 调整:邮件测试用例改为调用 EmailSendUtils.test() 函数,利用自动初始化特性
- * 新增:loadExternalConfig配置加载成功后输出提示信息
- * 修复:移除重复配置加载,消除矛盾日志输出;移除Main.DEFAULT_ROOT依赖
- * 新增:LogUtils专项测试用例,验证日志工具基础功能有效性
+ * 本次更新:1.新增EnvInfoUtils测试用例 2.邮件报告追加环境信息 3.用EmailSendUtils无参test()+修复用例索引错误+精简冗余日志
* @Author 豆包&ZhanGSKen
* @Date 2026/01/20 10:00:00
- * @LastEditTime 2026/01/24 补充LogUtils测试用例
+ * @LastEditTime 2026/01/26 新增EnvInfoUtils用例+邮件追加环境信息+补充用户ID展示
*/
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";
- private static final String CONFIG_KEY_TEST_REPORT_DIR = "test_report_dir";
+ private static final String CONFIG_KEY_REPORTS_PATH = "reports_path";// 报告目录配置键
+ private static final String CONFIG_KEY_LOG_PATH = "log_path";// 日志目录配置键
private static final String CONFIG_SECTION_GLOBAL = "GlobalConfig";
- private static final String CONFIG_SECTION_TEST = "TestConfig";
+ private static final String CONFIG_SECTION_EMAIL = "EmailConfig";
+ private static final String CONFIG_KEY_SEND_EMAIL = "send_email_account";
// ========== 动态配置属性(从INI读取,可变) ==========
private static String PROJECT_ROOT_DIR;
- private static String CONFIG_FILE_PATH;
private static String LOG_DIR_PATH;
private static String REPORT_DIR_PATH;
private static String REPORT_FILE;
@@ -73,15 +72,17 @@ public class ConsoleCmdAutoTest {
public static void main(String[] args) {
LogUtils.d(TAG, "【函数调用】main(),自动化测试程序启动");
- // 1. 加载外部config.ini,配置缺失直接退出
- loadExternalConfig(args);
+ // 初始化报告路径(读取GlobalConfig的reports_path,优先级最高)
+ initReportPathFromIni();
- // 2. 执行测试流程
- initTestEnv();
+ // 直接执行测试流程(依赖外部已初始化配置)
registerTestCases();
runAllTestCases();
generateTestReport();
+ // 新增:生成报告后发送到配置邮箱
+ sendReportToEmail();
+
// 输出测试总结
String summary = String.format("测试完成!总计:%d 用例 | 通过:%d | 失败:%d",
testCaseList.size(), passCount, failCount);
@@ -91,169 +92,167 @@ public class ConsoleCmdAutoTest {
LogUtils.i(TAG, "【函数结束】main()," + summary);
}
- // ========== 新增核心方法:从启动参数/默认路径加载外部config.ini ==========
- /**
- * 从启动参数读取config.ini路径,无参数则使用当前目录下的config.ini
- * 启动参数格式:-config:xxx/config.ini
- * 核心调整:PROJECT_ROOT_DIR缺失直接日志退出,移除兜底路径
- */
- private static void loadExternalConfig(String[] args) {
- LogUtils.d(TAG, "【函数调用】loadExternalConfig(),加载外部配置文件");
- // 默认配置文件路径:当前目录下的config.ini
- String defaultConfigPath = new File("config.ini").getAbsolutePath();
- CONFIG_FILE_PATH = defaultConfigPath;
+ // ========== 核心修改:读取GlobalConfig的reports_path初始化报告路径 ==========
+ private static void initReportPathFromIni() {
+ try {
+ // 读取核心配置
+ PROJECT_ROOT_DIR = IniConfigUtils.getConfigValue(CONFIG_SECTION_GLOBAL, CONFIG_KEY_ROOT_DIR, "");
+ LOG_DIR_PATH = IniConfigUtils.getConfigValue(CONFIG_SECTION_GLOBAL, CONFIG_KEY_LOG_PATH, "logs");
+ // 核心修改:读取GlobalConfig下的reports_path作为报告根目录
+ REPORT_DIR_PATH = IniConfigUtils.getConfigValue(CONFIG_SECTION_GLOBAL, CONFIG_KEY_REPORTS_PATH, "test/reports");
- // 解析启动参数中的配置文件路径
- if (args != null && args.length > 0) {
- for (String arg : args) {
- String trimArg = arg.trim();
- if (trimArg.startsWith("-config:")) {
- CONFIG_FILE_PATH = trimArg.substring(8).trim();
- LogUtils.i(TAG, "【配置路径】从启动参数读取配置文件:" + CONFIG_FILE_PATH);
- break;
- }
- }
+ // 拼接完整报告文件路径,文件名带时间戳防重复
+ REPORT_FILE = new File(REPORT_DIR_PATH, "AuthCenter_TestReport_" + MainUtils.getCurrentTime().replaceAll("[: ]", "_") + ".txt").getAbsolutePath();
+
+ // 自动创建目录(多级目录也能创建)
+ new File(REPORT_DIR_PATH).mkdirs();
+ LogUtils.d(TAG, "报告路径初始化完成:" + REPORT_FILE);
+ LogUtils.d(TAG, "日志目录路径:" + LOG_DIR_PATH);
+ } catch (Exception e) {
+ LogUtils.e(TAG, "报告路径初始化失败,使用默认路径兜底", e);
+ // 兜底路径
+ REPORT_DIR_PATH = "./test/reports";
+ REPORT_FILE = "./test/reports/AuthCenter_TestReport_" + MainUtils.getCurrentTime().replaceAll("[: ]", "_") + ".txt";
+ new File(REPORT_DIR_PATH).mkdirs();
+ }
+ }
+
+ // ========== 新增:发送测试报告到配置邮箱(追加环境信息) ==========
+ private static void sendReportToEmail() {
+ LogUtils.d(TAG, "【函数调用】sendReportToEmail(),开始发送测试报告邮件");
+ EmailSendUtils emailUtils = EmailSendUtils.getInstance();
+ // 1. 获取配置的发送邮箱(收件人=发送人)
+ String receiveEmail = IniConfigUtils.getConfigValue(CONFIG_SECTION_EMAIL, CONFIG_KEY_SEND_EMAIL, "");
+ if (EmailSendUtils.isStrEmpty(receiveEmail)) {
+ LogUtils.w(TAG, "未获取到配置的邮箱账号,跳过邮件发送");
+ return;
+ }
+ // 2. 读取报告文件内容
+ String reportContent = readReportFile();
+ if (reportContent == null) {
+ LogUtils.e(TAG, "读取测试报告失败,无法发送邮件");
+ return;
}
- // 加载配置文件,读取核心路径参数
+ // 核心更新:拼接EnvInfoUtils完整环境报告,格式和终端一致
+ String envInfo = getEnvReportForEmail();
+ String finalContent = reportContent + "\n\n" + envInfo;
+
+ // 3. 发送邮件 适配单例
+ String emailSubject = "【AuthCenter 自动化测试报告】" + MainUtils.getCurrentTime();
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()) {
- LogUtils.e(TAG, "【配置缺失】GlobalConfig小节必填项PROJECT_ROOT_DIR未配置,程序退出");
- System.err.println("❌ 配置缺失:GlobalConfig.PROJECT_ROOT_DIR 不能为空,请检查config.ini");
- System.exit(1);
+ // 初始化邮件配置(确保配置生效)
+ if (!emailUtils.isConfigInited()) {
+ emailUtils.initEmailConfig();
+ }
+ // 发送拼接环境信息后的完整内容
+ boolean sendSuccess = emailUtils.sendTextEmail(receiveEmail, emailSubject, finalContent);
+ if (sendSuccess) {
+ LogUtils.i(TAG, "测试报告已成功发送到邮箱:" + receiveEmail);
+ System.out.println("\n✅ 测试报告邮件发送成功!收件邮箱:" + receiveEmail);
+ } else {
+ LogUtils.w(TAG, "测试报告邮件发送失败");
+ System.out.println("\n⚠️ 测试报告邮件发送失败,请检查邮箱配置");
}
- PROJECT_ROOT_DIR = PROJECT_ROOT_DIR.trim();
-
- // 读取测试报告目录(可选,兜底为根目录下的reports)
- String reportDir = IniConfigUtils.getConfigValue(CONFIG_SECTION_TEST, CONFIG_KEY_TEST_REPORT_DIR);
- REPORT_DIR_PATH = (reportDir == null || reportDir.trim().isEmpty())
- ? PROJECT_ROOT_DIR + File.separator + "reports"
- : PROJECT_ROOT_DIR + File.separator + reportDir.trim();
-
- // 日志目录:根目录下的logs
- LOG_DIR_PATH = PROJECT_ROOT_DIR + File.separator + "logs";
- // 报告文件路径
- REPORT_FILE = REPORT_DIR_PATH + File.separator + "test-report.txt";
-
- LogUtils.i(TAG, "【路径初始化】项目根目录:" + PROJECT_ROOT_DIR);
- 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);
- System.exit(1);
+ LogUtils.e(TAG, "发送测试报告邮件异常", e);
+ System.out.println("\n⚠️ 测试报告邮件发送异常:" + e.getMessage());
+ }
+ }
+
+ // ========== 更新:组装邮件用的环境信息(格式统一+无冗余) ==========
+ private static String getEnvReportForEmail() {
+ StringBuilder envSb = new StringBuilder();
+ envSb.append("=== 运行环境核心信息(EnvInfoUtils获取)===\n");
+ envSb.append("核心环境标识:").append(EnvInfoUtils.getEnvFlag()).append("\n");
+ envSb.append("是否Android环境:").append(EnvInfoUtils.isAndroidEnv() ? "是" : "否").append("\n\n");
+
+ // 核心:直接拼接完整环境报告,和终端输出完全一致
+ envSb.append(EnvInfoUtils.getFullEnvReportText()).append("\n");
+ envSb.append("=====================================");
+ return envSb.toString();
+ }
+
+ // ========== 新增:读取报告文件内容 ==========
+ private static String readReportFile() {
+ BufferedReader reader = null;
+ StringBuilder content = new StringBuilder();
+ try {
+ reader = new BufferedReader(new FileReader(REPORT_FILE));
+ String line;
+ while ((line = reader.readLine()) != null) {
+ content.append(line).append("\n");
+ }
+ return content.toString();
+ } catch (Exception e) {
+ LogUtils.e(TAG, "读取报告文件异常", e);
+ return null;
+ } finally {
+ if (reader != null) {
+ try {
+ reader.close();
+ } catch (IOException e) {
+ LogUtils.e(TAG, "关闭报告文件流异常", e);
+ }
+ }
}
}
// ========== 工具函数(按执行顺序排列) ==========
-
- /**
- * 初始化测试环境:基于INI配置动态创建目录、初始化依赖工具类
- * 修复:移除重复的配置文件加载逻辑;移除Main类依赖
- */
- 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));
-
- // 5. 初始化核心工具类
- EnvInfoUtils.printEnvReport(true);
- ServerUtils.initServerUrl(MainUtils.getFinalServerUrl());
- System.out.println("✅ 测试环境初始化完成");
- LogUtils.d(TAG, "【函数结束】initTestEnv(),初始化成功");
- } catch (Exception e) {
- LogUtils.e(TAG, "【函数异常】initTestEnv(),初始化失败", e);
- throw new RuntimeException("Test environment initialization failed", e);
- }
- }
-
- /**
- * 注册所有控制台指令测试用例
- * 核心调整:exit指令测试用例移至最后注册,保证最后执行;移除Main类依赖
- * 关键修改:邮件测试用例改为调用 EmailSendUtils.test() 函数
- * 新增:LogUtils专项测试用例,放在最前面执行
- * 【Java7 兼容改造】移除 Lambda 表达式,替换为匿名内部类
- */
private static void registerTestCases() {
LogUtils.d(TAG, "【函数调用】registerTestCases(),开始注册测试用例");
+ final EmailSendUtils emailUtils = EmailSendUtils.getInstance();
- // ========== 新增:LogUtils专项测试用例(最前面执行) ==========
- // 用例01:LogUtils基础分级日志输出测试
+ // ========== 3个核心工具类独立测试(优先执行) ==========
testCaseList.add(new TestCase(
- "LogUtils基础日志输出测试",
- "验证LogUtils debug/info/warn/error分级日志输出有效性",
+ "核心工具类集成测试",
+ "执行LogUtils/RSAUtils/UserModel独立测试函数,验证工具类完整性",
new Runnable() {
@Override
public void run() {
try {
- LogUtils.d(TAG, "LogUtils DEBUG级别日志测试");
- LogUtils.i(TAG, "LogUtils INFO级别日志测试");
- LogUtils.w(TAG, "LogUtils WARN级别日志测试");
- LogUtils.e(TAG, "LogUtils ERROR级别日志测试");
+ LogUtils.test(); // 执行LogUtils测试
+ RSAUtils.test(); // 执行RSAUtils测试
+ UserModel.test(); // 执行UserModel测试
testCaseList.get(0).isPass = true;
- LogUtils.d(TAG, "【用例结果】LogUtils基础日志输出测试通过");
+ LogUtils.d(TAG, "【用例结果】核心工具类集成测试通过");
} catch (Exception e) {
- testCaseList.get(0).failReason = "基础日志输出异常:" + e.getMessage();
- LogUtils.w(TAG, "【用例结果】LogUtils基础日志测试失败:" + testCaseList.get(0).failReason);
+ testCaseList.get(0).failReason = "工具类测试异常:" + e.getMessage();
+ LogUtils.w(TAG, "【用例结果】核心工具类集成测试失败:" + testCaseList.get(0).failReason);
}
}
}
));
- // 用例02:LogUtils异常堆栈打印测试
+ // 新增用例02:EnvInfoUtils环境检测工具测试(紧跟核心工具类)
testCaseList.add(new TestCase(
- "LogUtils异常日志打印测试",
- "验证LogUtils带异常堆栈的日志输出功能",
+ "EnvInfoUtils环境检测测试",
+ "验证环境信息工具类核心功能,输出环境标识+完整环境报告",
new Runnable() {
@Override
public void run() {
try {
- // 构造测试异常
- Exception testException = new RuntimeException("LogUtils异常打印测试专用异常");
- LogUtils.e(TAG, "LogUtils带异常堆栈的日志测试", testException);
- testCaseList.get(1).isPass = true;
- LogUtils.d(TAG, "【用例结果】LogUtils异常日志打印测试通过");
+ // 执行工具类自测
+ EnvInfoUtils.test();
+ // 验证核心函数有效性
+ String envFlag = EnvInfoUtils.getEnvFlag();
+ String javaVer = EnvInfoUtils.getEnvInfo(EnvInfoUtils.PROP_JAVA_VERSION);
+ if (!EmailSendUtils.isStrEmpty(envFlag) && !EmailSendUtils.isStrEmpty(javaVer)) {
+ testCaseList.get(1).isPass = true;
+ LogUtils.d(TAG, "【用例结果】EnvInfoUtils环境检测测试通过,环境标识:" + envFlag);
+ } else {
+ testCaseList.get(1).failReason = "环境信息获取为空,工具类异常";
+ LogUtils.w(TAG, "【用例结果】EnvInfoUtils环境检测测试失败:" + testCaseList.get(1).failReason);
+ }
} catch (Exception e) {
- testCaseList.get(1).failReason = "异常日志打印异常:" + e.getMessage();
- LogUtils.w(TAG, "【用例结果】LogUtils异常日志测试失败:" + testCaseList.get(1).failReason);
+ testCaseList.get(1).failReason = "EnvInfoUtils执行异常:" + e.getMessage();
+ LogUtils.w(TAG, "【用例结果】EnvInfoUtils环境检测测试异常:" + testCaseList.get(1).failReason, e);
}
}
}
));
- // 原有用例顺延(序号从2开始)
- // 用例03:help指令响应测试
+ // 用例03:help指令响应测试(索引同步后移)
testCaseList.add(new TestCase(
"help指令响应测试",
"验证help指令能否正常打印帮助信息",
@@ -273,33 +272,45 @@ public class ConsoleCmdAutoTest {
}
));
- // 用例04:selftestmail指令测试 - 核心修改:调用 EmailSendUtils.test() 函数
+ // 用例04:selftestmail指令测试 - 用无参test()+精简日志+精准排错(索引同步后移)
testCaseList.add(new TestCase(
"selftestmail指令测试",
- "验证邮件自测指令能否正常执行(需正确配置INI)",
+ "验证邮件自测功能(需配置INI邮箱账号、授权码,且依赖齐全)",
new Runnable() {
@Override
public void run() {
try {
- // 生成随机测试码
- String testCode = String.valueOf((int) (Math.random() * 900000 + 100000));
- boolean testResult = EmailSendUtils.test(testCode);
+ LogUtils.d(TAG, "邮件自测开始,使用无参test自动生成测试码");
+
+ // 1. 先检查配置是否就绪,不就绪先初始化
+ if (!emailUtils.isConfigInited()) {
+ 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);
+ return;
+ }
+ }
+
+ // 2. 核心更新:调用无参test(),自动生成测试码,简洁高效
+ boolean testResult = emailUtils.test();
if (testResult) {
testCaseList.get(3).isPass = true;
- LogUtils.d(TAG, "【用例结果】selftestmail指令测试通过,测试码:" + testCode);
+ LogUtils.d(TAG, "【用例结果】selftestmail指令测试通过");
} else {
- testCaseList.get(3).failReason = "邮件自测失败,请检查INI配置或邮箱授权码";
+ testCaseList.get(3).failReason = "邮件发送失败,排查方向:1.授权码正确 2.邮箱开启SMTP 3.网络通畅 4.依赖齐全";
LogUtils.w(TAG, "【用例结果】selftestmail指令测试失败:" + testCaseList.get(3).failReason);
}
} catch (Exception e) {
- testCaseList.get(3).failReason = "邮件自测异常:" + e.getMessage();
- LogUtils.w(TAG, "【用例结果】selftestmail指令测试异常:" + testCaseList.get(3).failReason);
+ testCaseList.get(3).failReason = "邮件自测异常:" + e.getMessage() + "(已修复AWT依赖,优先检查邮件配置)";
+ LogUtils.w(TAG, "【用例结果】selftestmail指令测试异常:" + testCaseList.get(3).failReason, e);
}
}
}
));
- // 用例05:clearverifycode指令测试
+ // 用例05:clearverifycode指令测试(索引同步后移)
testCaseList.add(new TestCase(
"clearverifycode指令测试",
"验证清空验证码指令能否清空MailAuthUtils缓存",
@@ -324,7 +335,7 @@ public class ConsoleCmdAutoTest {
}
));
- // 用例06:testserver指令测试
+ // 用例06:testserver指令测试(索引同步后移)
testCaseList.add(new TestCase(
"testserver指令测试",
"验证服务器连通性测试指令能否正常执行",
@@ -344,7 +355,7 @@ public class ConsoleCmdAutoTest {
}
));
- // 用例07:未识别指令测试
+ // 用例07:未识别指令测试(索引同步后移)
testCaseList.add(new TestCase(
"未识别指令测试",
"验证输入未知指令能否正常提示",
@@ -363,7 +374,7 @@ public class ConsoleCmdAutoTest {
}
));
- // 用例08(最后执行):exit指令逻辑测试(移除Main类依赖,测试指令核心逻辑)
+ // 用例08(最后执行):exit指令逻辑测试(索引同步后移)
testCaseList.add(new TestCase(
"exit指令逻辑测试",
"验证exit指令核心处理逻辑有效性",
@@ -371,7 +382,6 @@ public class ConsoleCmdAutoTest {
@Override
public void run() {
try {
- // 移除Main类依赖,直接验证指令处理逻辑
ConsoleInputUtils.forceExit();
testCaseList.get(7).isPass = true;
LogUtils.d(TAG, "【用例结果】exit指令测试通过,核心停机逻辑执行成功");
@@ -419,7 +429,6 @@ public class ConsoleCmdAutoTest {
/**
* 生成结构化测试报告文件
- * 调整:报告文件路径完全从INI配置动态生成
*/
private static void generateTestReport() {
LogUtils.d(TAG, "【函数调用】generateTestReport(),开始生成测试报告");
@@ -431,8 +440,8 @@ public class ConsoleCmdAutoTest {
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("测试报告路径:" + REPORT_FILE + "\n");
writer.write("=====================================\n");
writer.write("总计用例数:" + testCaseList.size() + "\n");
writer.write("通过用例数:" + passCount + "\n");
diff --git a/src/cc/winboll/util/AdminUtils.java b/src/cc/winboll/util/AdminUtils.java
index a3db401..db6b2cf 100644
--- a/src/cc/winboll/util/AdminUtils.java
+++ b/src/cc/winboll/util/AdminUtils.java
@@ -6,19 +6,15 @@ import java.net.UnknownHostException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
-import javax.mail.Session;
-import javax.mail.internet.InternetAddress;
-import javax.mail.internet.MimeMessage;
-import javax.mail.Transport;
/**
* 管理员工具类(单例模式)
* 功能:发送服务启动/关闭/临时通知邮件,携带完整运行环境信息
* 适配Java7语法 & Android API30环境
- * 重构:抽离通用邮件发送逻辑,拆分业务函数
+ * 重构:抽离通用邮件发送逻辑,拆分业务函数,移除反射,适配邮件单例
* @Author 豆包&ZhanGSKen
* @Date 2026-01-21 23:00:00
- * @LastEditTime 2026/01/23 16:00:00
+ * @LastEditTime 2026/01/26 适配邮件单例+移除反射依赖
*/
public class AdminUtils {
// ========== 单例模式核心 ==========
@@ -102,47 +98,38 @@ public class AdminUtils {
);
}
- // ========== 通用工具函数:发送自定义邮件 ==========
+ // ========== 通用工具函数:发送自定义邮件 核心调整 ==========
/**
* 通用邮件发送函数,所有通知函数复用此逻辑
+ * 适配单例、移除反射、优化初始化判断
* @param subject 邮件主题
* @param content 邮件正文内容
* @return 发送成功返回true,失败返回false
*/
private boolean sendCustomEmail(String subject, String content) {
LogUtils.d(TAG, "sendCustomEmail() 函数调用,主题:" + subject);
+ EmailSendUtils emailUtils = EmailSendUtils.getInstance();
- // 1. 初始化邮件配置
- if (!EmailSendUtils.initEmailConfig()) {
- LogUtils.e(TAG, "邮件配置初始化失败,无法发送邮件");
- return false;
+ // 1. 校验邮件工具是否就绪,未初始化则执行初始化
+ if (!emailUtils.isConfigInited()) {
+ LogUtils.d(TAG, "邮件工具未初始化,执行初始化流程");
+ boolean initOk = emailUtils.initEmailConfig();
+ if (!initOk) {
+ LogUtils.e(TAG, "邮件配置初始化失败,无法发送邮件");
+ return false;
+ }
}
- // 2. 获取收件人邮箱
+ // 2. 获取收件人邮箱,统一用工具类校验空值
String sendToEmail = IniConfigUtils.getConfigValue("EmailConfig", "send_email_account", "");
if (EmailSendUtils.isStrEmpty(sendToEmail)) {
LogUtils.e(TAG, "收件人邮箱 send_email_account 未配置");
return false;
}
- // 3. 获取邮件会话
- Session mailSession = getMailSessionFromEmailSendUtils();
- if (mailSession == null) {
- LogUtils.e(TAG, "获取邮件会话失败");
- return false;
- }
-
- // 4. 构建邮件
+ // 3. 调用邮件工具发送,无需反射获取会话,解耦依赖
try {
- MimeMessage message = new MimeMessage(mailSession);
- String fromNickname = IniConfigUtils.getConfigValue("EmailConfig", "from_nickname", "AuthCenter 系统");
- message.setFrom(new InternetAddress(sendToEmail, fromNickname, "UTF-8"));
- message.addRecipient(javax.mail.Message.RecipientType.TO, new InternetAddress(sendToEmail));
- message.setSubject(subject, "UTF-8");
- message.setText(content, "UTF-8");
- Transport.send(message);
- LogUtils.i(TAG, "自定义邮件发送成功,收件人:" + sendToEmail);
- return true;
+ return emailUtils.sendTextEmail(sendToEmail, subject, content);
} catch (Exception e) {
LogUtils.e(TAG, "自定义邮件发送失败", e);
return false;
@@ -183,7 +170,7 @@ public class AdminUtils {
return sendCustomEmail(tempSubject, fullContent);
}
- // ========== 新增:对外暴露用户ID获取方法 ==========
+ // ========== 对外暴露用户ID获取方法 ==========
/**
* 获取运行用户ID,供外部工具类调用
* @return 格式化的用户ID字符串
@@ -192,18 +179,7 @@ public class AdminUtils {
return getUserIdInternal();
}
- // ========== 原有工具函数 ==========
- private Session getMailSessionFromEmailSendUtils() {
- try {
- java.lang.reflect.Field field = EmailSendUtils.class.getDeclaredField("mailSession");
- field.setAccessible(true);
- return (Session) field.get(null);
- } catch (Exception e) {
- LogUtils.e(TAG, "反射获取邮件会话失败", e);
- return null;
- }
- }
-
+ // ========== 工具函数 移除反射相关无用方法 ==========
private String getLocalHostIp() {
try {
InetAddress localHost = InetAddress.getLocalHost();
diff --git a/src/cc/winboll/util/ConsoleInputUtils.java b/src/cc/winboll/util/ConsoleInputUtils.java
index 8565aba..169784d 100644
--- a/src/cc/winboll/util/ConsoleInputUtils.java
+++ b/src/cc/winboll/util/ConsoleInputUtils.java
@@ -13,7 +13,7 @@ import java.util.logging.Level;
* 新增:每处理一个指令,通过AdminUtils发送临时邮件通知
* @Author 豆包&ZhanGSKen
* @Date 2026/01/14 00:00:00
- * @LastEditTime 2026/01/23 17:00:00
+ * @LastEditTime 2026/01/26 适配邮件工具单例调用
*/
public class ConsoleInputUtils {
private static final String TAG = "ConsoleInputUtils";
@@ -185,19 +185,36 @@ public class ConsoleInputUtils {
/**
+ * 调整核心:适配邮件单例,优化流程+精准反馈
* 处理邮件自测指令,自动生成测试码,发送到发送方邮箱
*/
private static void handleSelfTestMailCmd() {
LogUtils.d(TAG, "【指令处理】执行selftestmail指令,启动邮件自测流程");
System.out.println("\n🔍 开始邮件配置自测,正在初始化邮件服务...");
- boolean initResult = EmailSendUtils.initEmailConfig();
+
+ EmailSendUtils emailUtils = EmailSendUtils.getInstance();
+ // 先判断是否已初始化,未初始化再执行,避免重复初始化
+ boolean initResult = emailUtils.isConfigInited() ? true : emailUtils.initEmailConfig();
+
if (!initResult) {
- System.err.println("❌ 邮件配置初始化失败,自测终止!请先完善INI文件[EmailConfig]配置");
+ System.err.println("❌ 邮件配置初始化失败,自测终止!");
+ System.err.println("🔧 排查:1.检查INI[EmailConfig]配置 2.确认send_email_account和smtp_auth_code必填项 3.核对邮箱SMTP权限");
LogUtils.w(TAG, "邮件初始化失败,自测流程终止");
return;
}
+
String testCode = String.valueOf((int)((Math.random() * 900000) + 100000));
- EmailSendUtils.sendSelfTestEmail(testCode);
+ System.out.println("📝 生成自测校验码:" + testCode + ",正在发送自测邮件...");
+ boolean sendResult = emailUtils.sendSelfTestEmail(testCode);
+
+ if (sendResult) {
+ System.out.println("✅ 邮件自测成功!请前往发送方邮箱查收");
+ LogUtils.i(TAG, "邮件自测完成,测试码:"+testCode);
+ } else {
+ System.err.println("❌ 邮件自测失败!");
+ System.err.println("🔧 排查:1.授权码有效性 2.网络连通性 3.javax.mail依赖完整性 4.端口465是否开放");
+ LogUtils.w(TAG, "邮件自测发送失败");
+ }
}
/**
@@ -245,6 +262,8 @@ public class ConsoleInputUtils {
System.out.println(" 日志级别:" + LogUtils.getGlobalLogLevel().getName());
System.out.println(" 服务器地址:" + ServerUtils.getServerUrl());
System.out.println(" 项目根目录:" + IniConfigUtils.getConfigValue("GlobalConfig", "PROJECT_ROOT_DIR", "【未设置】"));
+ // 新增邮件工具状态
+ System.out.println(" 邮件服务:" + (EmailSendUtils.getInstance().isConfigInited() ? "✅ 已初始化" : "❌ 未就绪"));
}
/**
diff --git a/src/cc/winboll/util/EmailSendUtils.java b/src/cc/winboll/util/EmailSendUtils.java
index 3dc896a..41777b3 100644
--- a/src/cc/winboll/util/EmailSendUtils.java
+++ b/src/cc/winboll/util/EmailSendUtils.java
@@ -11,32 +11,57 @@ import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
/**
- * 邮件发送工具类,读取INI配置,专注验证码发送场景,兼容Android/JVM
+ * 邮件发送工具类(单例模式),读取INI配置,专注验证码发送场景,兼容Android/JVM
* 适配javax.mail依赖,原生兼容Java7语法与Android API30环境
- * 调整:仅保留smtp_auth_code配置读取,移除send_email_auth_code相关逻辑
- * 修复:默认端口与SSL协议匹配
+ * 核心调整:改为线程安全懒汉单例+重整初始化逻辑+适配新测试用例
* 新增:公开静态空值校验方法 isStrEmpty,供外部类调用
+ * 本次修复:终极规避AWT依赖,配合补丁类彻底解决java.awt.datatransfer.Transferable缺失异常
+ * 新增无参test():自动生成6位测试码,快速自测邮件配置
* @Author 豆包&ZhanGSKen
* @Date 2026-01-15 00:00:00
- * @LastEditTime 2026-01-22 00:00:00
+ * @LastEditTime 2026-01-26 补全防AWT配置,兼容android-30.jar不冲突
*/
public class EmailSendUtils {
private static final String TAG = "EmailSendUtils";
- private static Properties mailProps = new Properties();
- private static Session mailSession;
- private static String fromEmail;
- private static String fromNickname;
- private static String emailSubjectPrefix;
- private static String verifyCodeContentTemplate;
+ // 终极防AWT:统一文本类型常量,杜绝隐性触发AWT类
+ private static final String MAIL_CONTENT_TYPE_PLAIN = "text/plain;charset=UTF-8";
+
+ // ========== 单例核心(懒汉模式-线程安全) ==========
+ private static volatile EmailSendUtils instance = null;
+
+ // ========== 成员变量(单例私有,替代原静态变量) ==========
+ private Properties mailProps = new Properties();
+ private Session mailSession;
+ private String fromEmail;
+ private String fromNickname;
+ private String emailSubjectPrefix;
+ private String verifyCodeContentTemplate;
+ private boolean isConfigInited = false; // 配置初始化标识
// ========== 默认配置常量 ==========
private static final String DEFAULT_SMTP_HOST = "smtp.qq.com";
private static final String DEFAULT_SMTP_PORT = "465";
private static final String DEFAULT_SUBJECT_PREFIX = "【AuthCenter 认证中心】";
private static final String DEFAULT_CONTENT_TEMPLATE = "您的认证验证码为:{code},有效期5分钟,请及时使用,请勿泄露给他人!";
+ private static final String CONFIG_SECTION_EMAIL = "EmailConfig";
+
+ // ========== 单例私有构造 ==========
+ private EmailSendUtils(){}
+
+ // ========== 获取单例实例(对外唯一入口) ==========
+ public static EmailSendUtils getInstance() {
+ if (instance == null) {
+ synchronized (EmailSendUtils.class) {
+ if (instance == null) {
+ instance = new EmailSendUtils();
+ }
+ }
+ }
+ return instance;
+ }
/**
- * 【新增公开方法】单字符串空值校验,供外部类调用
+ * 【公开方法】单字符串空值校验,供外部类调用
* @param str 待校验字符串
* @return true=空/空白,false=非空
*/
@@ -45,32 +70,48 @@ public class EmailSendUtils {
}
/**
- * 初始化邮件配置
- * 仅读取smtp_auth_code,无其他兜底逻辑,配置匹配更精准
+ * 【重整核心】初始化邮件配置(适配新测试用例,支持重复调用)
+ * 已初始化直接返回true,未初始化则读取配置构建会话,校验核心参数
* @return 配置完整且初始化成功返回true,否则false
*/
- public static boolean initEmailConfig() {
+ public boolean initEmailConfig() {
+ // 已初始化,直接返回成功
+ if (isConfigInited && mailSession != null) {
+ LogUtils.d(TAG, "邮件配置已初始化,无需重复执行");
+ return true;
+ }
LogUtils.d(TAG, "initEmailConfig 函数调用,开始初始化邮件配置");
- // 仅读取smtp_auth_code,删除send_email_auth_code相关代码
- String smtpHost = IniConfigUtils.getConfigValue("EmailConfig", "smtp_host", DEFAULT_SMTP_HOST);
- String smtpPort = IniConfigUtils.getConfigValue("EmailConfig", "smtp_port", DEFAULT_SMTP_PORT);
- fromEmail = IniConfigUtils.getConfigValue("EmailConfig", "send_email_account", "");
- final String smtpAuthCode = IniConfigUtils.getConfigValue("EmailConfig", "smtp_auth_code", "");
- fromNickname = IniConfigUtils.getConfigValue("EmailConfig", "from_nickname", "");
- emailSubjectPrefix = IniConfigUtils.getConfigValue("EmailConfig", "email_subject_prefix", DEFAULT_SUBJECT_PREFIX);
- verifyCodeContentTemplate = IniConfigUtils.getConfigValue("EmailConfig", "verify_code_email_content", DEFAULT_CONTENT_TEMPLATE);
+
+ // 读取INI配置(精简key名,直接关联配置段)
+ String smtpHost = IniConfigUtils.getConfigValue(CONFIG_SECTION_EMAIL, "smtp_host", DEFAULT_SMTP_HOST);
+ String smtpPort = IniConfigUtils.getConfigValue(CONFIG_SECTION_EMAIL, "smtp_port", DEFAULT_SMTP_PORT);
+ fromEmail = IniConfigUtils.getConfigValue(CONFIG_SECTION_EMAIL, "send_email_account", "");
+ final String smtpAuthCode = IniConfigUtils.getConfigValue(CONFIG_SECTION_EMAIL, "smtp_auth_code", "");
+ fromNickname = IniConfigUtils.getConfigValue(CONFIG_SECTION_EMAIL, "from_nickname", "");
+ emailSubjectPrefix = IniConfigUtils.getConfigValue(CONFIG_SECTION_EMAIL, "email_subject_prefix", DEFAULT_SUBJECT_PREFIX);
+ verifyCodeContentTemplate = IniConfigUtils.getConfigValue(CONFIG_SECTION_EMAIL, "verify_code_email_content", DEFAULT_CONTENT_TEMPLATE);
LogUtils.d(TAG, "读取INI邮件配置:smtpHost[" + smtpHost + "] smtpPort[" + smtpPort + "] fromEmail[" + fromEmail + "]");
- // 核心校验:仅校验send_email_account和smtp_auth_code
+
+ // 核心校验:发送邮箱+授权码 必填
if (isEmpty(fromEmail)) {
LogUtils.e(TAG, "邮件核心配置缺失:send_email_account 未配置");
+ resetConfig();
return false;
}
if (isEmpty(smtpAuthCode)) {
LogUtils.e(TAG, "邮件核心配置缺失:smtp_auth_code 未配置");
+ resetConfig();
return false;
}
+ // 配置SMTP参数(SSL适配,固定协议,兼容Java7+android-30)
+ mailProps.clear();
+ // 核心新增3行防AWT配置,配合补丁类彻底杜绝冲突
+ mailProps.put("mail.mime.multipart.ignoreexistingboundaryparameter", "true");
+ mailProps.put("mail.mime.base64.ignoreerrors", "true");
+ mailProps.put("mail.mime.encodefilename", "false");
+ // 原有核心配置
mailProps.put("mail.smtp.host", smtpHost);
mailProps.put("mail.smtp.port", smtpPort);
mailProps.put("mail.smtp.auth", "true");
@@ -79,29 +120,45 @@ public class EmailSendUtils {
mailProps.put("mail.transport.protocol", "smtp");
try {
+ // 创建邮件会话(匿名内部类,适配Java7)
mailSession = Session.getInstance(mailProps, new Authenticator() {
- @Override
- protected PasswordAuthentication getPasswordAuthentication() {
- return new PasswordAuthentication(fromEmail, smtpAuthCode);
- }
- });
+ @Override
+ protected PasswordAuthentication getPasswordAuthentication() {
+ return new PasswordAuthentication(fromEmail, smtpAuthCode);
+ }
+ });
mailSession.setDebug(false);
- LogUtils.i(TAG, "邮件发送配置初始化完成,发送方邮箱:" + fromEmail + " 发送方昵称:" + (fromNickname == null ? "默认昵称" : fromNickname));
+ isConfigInited = true; // 标记初始化完成
+ LogUtils.i(TAG, "邮件发送配置初始化完成,发送方邮箱:" + fromEmail + " 发送方昵称:" + (isEmpty(fromNickname) ? "默认昵称" : fromNickname));
return true;
} catch (Exception e) {
LogUtils.e(TAG, "邮件会话创建失败,检查配置是否正确", e);
+ resetConfig();
return false;
}
}
/**
- * 单元测试专用函数
- * @param testCode 测试验证码
+ * 【新增无参自测】自动生成6位测试码,快速校验邮件配置(最常用)
+ * 无需传参,一键自测,适配快速验证场景
+ * @return 自测成功返回true,失败返回false
+ */
+ public boolean test() {
+ // 自动生成6位纯数字测试码,和验证码规则一致
+ String testCode = String.valueOf((int)(Math.random() * 900000 + 100000));
+ LogUtils.d(TAG, "无参test()调用,自动生成测试码:" + testCode);
+ return test(testCode);
+ }
+
+ /**
+ * 单元测试专用函数(适配新测试用例,单例调用)
+ * @param testCode 自定义测试码
* @return 测试邮件发送成功返回true,否则false
*/
- public static boolean test(String testCode) {
+ public boolean test(String testCode) {
LogUtils.d(TAG, "test() 函数调用,开始邮件工具类单元测试");
- if (mailSession == null) {
+ // 未初始化则自动执行,失败直接终止
+ if (!isConfigInited) {
LogUtils.w(TAG, "邮件会话未初始化,自动执行配置初始化");
boolean initResult = initEmailConfig();
if (!initResult) {
@@ -113,15 +170,15 @@ public class EmailSendUtils {
}
/**
- * 发送验证码邮件
+ * 发送验证码邮件(核心业务方法)- 终极防AWT写法
* @param toEmail 接收方邮箱
* @param verifyCode 验证码
* @return 发送成功返回true,失败返回false
*/
- public static boolean sendVerifyCodeEmail(String toEmail, String verifyCode) {
+ public boolean sendVerifyCodeEmail(String toEmail, String verifyCode) {
LogUtils.d(TAG, "sendVerifyCodeEmail 函数调用,参数:toEmail[" + toEmail + "] verifyCode[" + verifyCode + "]");
- if (mailSession == null) {
- LogUtils.e(TAG, "邮件会话未初始化,请先调用initEmailConfig()");
+ // 前置校验:配置+参数
+ if (!checkInitStatus()) {
return false;
}
if (isEmpty(toEmail, verifyCode)) {
@@ -132,11 +189,15 @@ public class EmailSendUtils {
try {
MimeMessage message = new MimeMessage(mailSession);
String finalNickname = isEmpty(fromNickname) ? fromEmail.split("@")[0] : fromNickname;
+ // 防乱码+防AWT
message.setFrom(new InternetAddress(fromEmail, finalNickname, "UTF-8"));
message.addRecipient(Message.RecipientType.TO, new InternetAddress(toEmail));
message.setSubject(emailSubjectPrefix + " 验证码通知", "UTF-8");
String content = verifyCodeContentTemplate.replace("{code}", verifyCode);
- message.setText(content, "UTF-8");
+
+ // 终极写法1:杜绝触发AWT类,固定文本类型
+ message.setContent(content, MAIL_CONTENT_TYPE_PLAIN);
+
Transport.send(message);
LogUtils.i(TAG, "验证码邮件发送成功,收件人:" + toEmail);
return true;
@@ -147,19 +208,18 @@ public class EmailSendUtils {
}
/**
- * 发送自测邮件到发送方自身邮箱
+ * 发送自测邮件到发送方自身邮箱 - 终极防AWT写法(彻底解决报错)
* @param testCode 自定义测试码
* @return 自测发送成功返回true,失败返回false
*/
- public static boolean sendSelfTestEmail(String testCode) {
+ public boolean sendSelfTestEmail(String testCode) {
LogUtils.d(TAG, "sendSelfTestEmail 函数调用,开始邮件自测,测试码:" + testCode);
- if (isEmpty(fromEmail, testCode)) {
- LogUtils.w(TAG, "发送方邮箱或测试码为空,自测失败");
+ // 前置校验:配置+测试码
+ if (!checkInitStatus()) {
return false;
}
- if (mailSession == null) {
- LogUtils.e(TAG, "邮件会话未初始化,自测终止,请先完成INI邮件配置");
- System.err.println("❌ 邮件会话未初始化!请先检查INI文件的[EmailConfig]配置项是否完整");
+ if (isEmpty(testCode)) {
+ LogUtils.w(TAG, "测试码为空,自测失败");
return false;
}
@@ -167,25 +227,59 @@ public class EmailSendUtils {
MimeMessage message = new MimeMessage(mailSession);
String finalNickname = isEmpty(fromNickname) ? fromEmail.split("@")[0] : fromNickname;
message.setFrom(new InternetAddress(fromEmail, finalNickname, "UTF-8"));
+ // 自测发往自身邮箱
message.addRecipient(Message.RecipientType.TO, new InternetAddress(fromEmail));
message.setSubject(emailSubjectPrefix + " 配置自测通知", "UTF-8");
String content = String.format("邮件发送配置自测成功!\n自测校验码:%s\n当前发送方邮箱:%s\n说明:邮件服务配置正常,可正常发送验证码。", testCode, fromEmail);
- message.setText(content, "UTF-8");
+
+ // 终极写法2:彻底规避AWT依赖,无任何隐性调用
+ message.setContent(content, MAIL_CONTENT_TYPE_PLAIN);
+
Transport.send(message);
LogUtils.i(TAG, "自测邮件发送成功,已发送至发送方邮箱:" + fromEmail);
System.out.println("✅ 邮件自测成功,请到 " + fromEmail + " 查收测试邮件");
return true;
} catch (Exception e) {
LogUtils.e(TAG, "自测邮件发送失败,发送目标:" + fromEmail, e);
- System.err.println("❌ 邮件自测失败!请检查INI配置、邮箱授权码或网络连接");
+ System.err.println("❌ 邮件自测失败!请检查INI配置、邮箱授权码、网络连接或依赖版本");
return false;
}
}
/**
- * 【私有方法】多参数空值校验,内部使用
+ * 发送文本邮件(适配测试报告发送)- 终极防AWT写法
+ * @param toEmail 收件邮箱
+ * @param subject 邮件主题
+ * @param content 邮件内容
+ * @return 发送成功返回true
*/
- private static boolean isEmpty(String... strs) {
+ public boolean sendTextEmail(String toEmail, String subject, String content) {
+ LogUtils.d(TAG, "sendTextEmail 函数调用,收件人:" + toEmail);
+ if (!checkInitStatus() || isEmpty(toEmail, subject, content)) {
+ return false;
+ }
+ try {
+ MimeMessage message = new MimeMessage(mailSession);
+ String finalNickname = isEmpty(fromNickname) ? fromEmail.split("@")[0] : fromNickname;
+ message.setFrom(new InternetAddress(fromEmail, finalNickname, "UTF-8"));
+ message.addRecipient(Message.RecipientType.TO, new InternetAddress(toEmail));
+ message.setSubject(subject, "UTF-8");
+
+ // 终极写法3:统一标准,杜绝AWT相关报错
+ message.setContent(content, MAIL_CONTENT_TYPE_PLAIN);
+
+ Transport.send(message);
+ LogUtils.i(TAG, "文本邮件发送成功,收件人:" + toEmail);
+ return true;
+ } catch (Exception e) {
+ LogUtils.e(TAG, "文本邮件发送失败,收件人:" + toEmail, e);
+ return false;
+ }
+ }
+
+ // ========== 私有辅助方法 ==========
+ /** 多参数空值校验,内部使用 */
+ private boolean isEmpty(String... strs) {
for (String str : strs) {
if (str == null || str.trim().isEmpty()) {
return true;
@@ -193,5 +287,27 @@ public class EmailSendUtils {
}
return false;
}
+
+ /** 检查初始化状态,未初始化则提示 */
+ private boolean checkInitStatus() {
+ if (!isConfigInited || mailSession == null) {
+ LogUtils.e(TAG, "邮件会话未初始化,请先调用initEmailConfig()");
+ return false;
+ }
+ return true;
+ }
+
+ /** 初始化失败,重置配置状态 */
+ private void resetConfig() {
+ isConfigInited = false;
+ mailSession = null;
+ fromEmail = null;
+ fromNickname = null;
+ }
+
+ // ========== 对外提供初始化状态查询(适配测试用例调用) ==========
+ public boolean isConfigInited() {
+ return this.isConfigInited;
+ }
}
diff --git a/src/cc/winboll/util/EnvInfoUtils.java b/src/cc/winboll/util/EnvInfoUtils.java
index b126cd2..a01709d 100644
--- a/src/cc/winboll/util/EnvInfoUtils.java
+++ b/src/cc/winboll/util/EnvInfoUtils.java
@@ -3,107 +3,344 @@ package cc.winboll.util;
import cc.winboll.LogUtils;
/**
- * 运行环境信息工具类,独立封装环境检测、信息获取逻辑
- * 适配Java7,支持服务器/Android双环境,参数化控制输出
+ * 运行环境信息工具类
+ * 适配Java7 + Android/Dalvik/ART/Termux OpenJDK多环境,封装环境检测、信息获取、报告生成
+ * 修复点:1.Android UID/品牌/型号/Java版本获取异常 2.Termux OpenJDK用户ID显示未知 3.系统指纹获取失败
* @Author 豆包&ZhanGSKen
* @Date 2026/01/15
+ * @LastEdit 修复Termux用户ID未知问题+兼容多环境UID获取+统一异常处理逻辑
*/
public class EnvInfoUtils {
private static final String TAG = "EnvInfoUtils";
- /**
- * 核心方法:打印环境检测报告,支持参数控制是否输出详细信息
- * @param showDetail true=显示完整报告,false=仅显示核心信息
- */
- public static void printEnvReport(boolean showDetail) {
- if (!showDetail) {
- printSimpleEnvInfo();
- return;
- }
- printDetailedEnvInfo();
+ // ========== 系统属性常量(按类型归类) ==========
+ // Java核心属性
+ public static final String PROP_JAVA_VERSION = "java.version";
+ public static final String PROP_JAVA_VM_NAME = "java.vm.name";
+ public static final String PROP_JAVA_VM_VENDOR = "java.vm.vendor";
+ public static final String PROP_JAVA_HOME = "java.home";
+ public static final String PROP_JAVA_SPEC_VER = "java.specification.version";
+
+ // 操作系统属性
+ public static final String PROP_OS_NAME = "os.name";
+ public static final String PROP_OS_ARCH = "os.arch";
+ public static final String PROP_OS_VERSION = "os.version";
+ public static final String PROP_FILE_SEP = "file.separator";
+ public static final String PROP_PATH_SEP = "path.separator";
+ public static final String PROP_USER_ID = "user.id";// JVM专属,Android/Termux不支持
+
+ // 运行目录&用户属性
+ public static final String PROP_USER_DIR = "user.dir";
+ public static final String PROP_USER_NAME = "user.name";
+ public static final String PROP_USER_HOME = "user.home";
+ public static final String PROP_FILE_ENCODING = "file.encoding";
+
+ // Android专属属性(仅标记,需反射获取)
+ public static final String PROP_ANDROID_SDK = "android.os.Build.VERSION.SDK_INT";
+ public static final String PROP_ANDROID_MODEL = "android.os.Build.MODEL";
+ public static final String PROP_ANDROID_BRAND = "android.os.Build.BRAND";
+ public static final String PROP_ANDROID_FINGER = "android.os.Build.FINGERPRINT";
+
+
+ // ========== 对外核心API(简洁统一) ==========
+ /** 获取单个环境属性值 */
+ public static String getEnvInfo(String key) {
+ return getSysProp(key, "未知");
}
- /**
- * 打印精简环境信息(快速启动场景)
- */
- private static void printSimpleEnvInfo() {
- System.out.println("===== 运行环境核心信息 =====");
- System.out.println("Java版本: " + getSysProp("java.version", "未知"));
- System.out.println("运行系统: " + getSysProp("os.name", "未知") + "(" + getSysProp("os.arch", "未知") + ")");
- System.out.println("运行目录: " + getSysProp("user.dir", "未知"));
+ /** 获取核心环境标识(快速区分Android/Termux/JVM) */
+ public static String getEnvFlag() {
+ String javaVer = getAvailableJavaVersion();
if (isAndroidEnv()) {
- System.out.println("运行环境: Android(" + getSysProp("os.version", "未知") + ")");
+ return "Android-" + getEnvInfo(PROP_OS_VERSION);
+ } else if (isTermuxEnv()) {
+ return "Termux-OpenJDK-" + javaVer;
+ } else {
+ return "JVM-" + javaVer;
+ }
+ }
+
+ /** 打印环境报告(支持精简/完整模式) */
+ public static void printEnvReport(boolean showDetail) {
+ preloadSysProps();
+ if (showDetail) {
+ printDetailedEnvReport();
+ } else {
+ printSimpleEnvReport();
+ }
+ }
+
+ /** 工具类自测入口(一键验证所有功能) */
+ public static void test() {
+ LogUtils.d(TAG, "EnvInfoUtils 自测启动");
+ System.out.println(">>>>>>>>>>>>> EnvInfoUtils 自测启动 <<<<<<<<<<<<<");
+ preloadSysProps();
+
+ // 核心标识
+ System.out.println("【核心环境标识】: " + getEnvFlag());
+ System.out.println("【是否Android环境】: " + (isAndroidEnv() ? "是" : "否"));
+ System.out.println("【是否Termux环境】: " + (isTermuxEnv() ? "是" : "否"));
+
+ // 打印双版本报告
+ System.out.println("\n--- 精简环境报告 ---");
+ printSimpleEnvReport();
+ System.out.println("--- 完整环境报告 ---");
+ printDetailedEnvReport();
+
+ // 单独属性演示
+ System.out.println(">>>>>>>>>>>>> 常用属性单独获取演示 <<<<<<<<<<<<<");
+ System.out.println("Java版本: " + getAvailableJavaVersion());
+ System.out.println("运行系统: " + getEnvInfo(PROP_OS_NAME));
+ System.out.println("运行目录: " + getEnvInfo(PROP_USER_DIR));
+ System.out.println("运行用户ID: " + getOsUserId());
+ System.out.println("设备品牌: " + getAndroidBrand());
+ System.out.println("设备型号: " + getAndroidModel());
+ System.out.println("系统指纹: " + getAndroidFingerprint());
+ System.out.println(">>>>>>>>>>>>> EnvInfoUtils 自测结束 <<<<<<<<<<<<<");
+ LogUtils.d(TAG, "EnvInfoUtils 自测完成");
+ }
+
+ /** 获取完整环境报告文本(供邮件/日志输出) */
+ public static String getFullEnvReportText() {
+ preloadSysProps();
+ StringBuilder sb = new StringBuilder();
+ sb.append("========================================").append("\n");
+ sb.append(" 运行环境详细检测报告").append("\n");
+ sb.append("========================================").append("\n\n");
+
+ // Java信息
+ sb.append("1. Java 核心信息").append("\n");
+ sb.append(" - Java 版本号: ").append(getAvailableJavaVersion()).append("\n");
+ sb.append(" - Java 虚拟机: ").append(getEnvInfo(PROP_JAVA_VM_NAME)).append("\n");
+ sb.append(" - VM 厂商信息: ").append(getEnvInfo(PROP_JAVA_VM_VENDOR)).append("\n");
+ sb.append(" - Java 环境路径: ").append(getEnvInfo(PROP_JAVA_HOME)).append("\n\n");
+
+ // 系统信息
+ sb.append("2. 操作系统信息").append("\n");
+ sb.append(" - 系统名称: ").append(getEnvInfo(PROP_OS_NAME)).append("\n");
+ sb.append(" - 系统架构: ").append(getEnvInfo(PROP_OS_ARCH)).append("\n");
+ sb.append(" - 系统版本: ").append(getEnvInfo(PROP_OS_VERSION)).append("\n");
+ sb.append(" - 文件分隔符: ").append(getEnvInfo(PROP_FILE_SEP)).append("\n");
+ sb.append(" - 路径分隔符: ").append(getEnvInfo(PROP_PATH_SEP)).append("\n");
+ sb.append(" - 运行用户ID: ").append(getOsUserId()).append("\n\n");
+
+ // 运行目录&用户
+ sb.append("3. 运行目录&用户信息").append("\n");
+ sb.append(" - 当前运行目录: ").append(getEnvInfo(PROP_USER_DIR)).append("\n");
+ sb.append(" - 当前用户名称: ").append(getEnvInfo(PROP_USER_NAME)).append("\n");
+ sb.append(" - 用户主目录: ").append(getEnvInfo(PROP_USER_HOME)).append("\n");
+ sb.append(" - 文件编码格式: ").append(getEnvInfo(PROP_FILE_ENCODING)).append("\n\n");
+
+ // Android专属
+ if (isAndroidEnv()) {
+ sb.append("4. Android 专属信息").append("\n");
+ sb.append(" - 设备品牌: ").append(getAndroidBrand()).append("\n");
+ sb.append(" - 设备型号: ").append(getAndroidModel()).append("\n");
+ sb.append(" - 系统指纹: ").append(getAndroidFingerprint()).append("\n");
+ }
+ sb.append("========================================");
+ return sb.toString();
+ }
+
+
+ // ========== 环境判断核心方法 ==========
+ /** 判断是否为Android环境(Dalvik/ART虚拟机) */
+ public static boolean isAndroidEnv() {
+ String vmName = getEnvInfo(PROP_JAVA_VM_NAME);
+ return vmName.contains("Dalvik") || vmName.contains("ART");
+ }
+
+ /** 判断是否为Termux环境(OpenJDK+Termux厂商标识) */
+ public static boolean isTermuxEnv() {
+ String vendor = getEnvInfo(PROP_JAVA_VM_VENDOR);
+ String javaHome = getEnvInfo(PROP_JAVA_HOME);
+ return vendor.contains("Termux") || javaHome.contains("com.termux");
+ }
+
+
+ // ========== 报告打印(精简+完整,消除重复) ==========
+ /** 打印精简报告 */
+ private static void printSimpleEnvReport() {
+ System.out.println("===== 运行环境核心信息 =====");
+ System.out.println("Java版本: " + getAvailableJavaVersion());
+ System.out.println("运行系统: " + getEnvInfo(PROP_OS_NAME) + "(" + getEnvInfo(PROP_OS_ARCH) + ")");
+ System.out.println("运行目录: " + getEnvInfo(PROP_USER_DIR));
+ System.out.println("运行用户ID: " + getOsUserId());
+ if (isAndroidEnv()) {
+ System.out.println("运行环境: Android(" + getEnvInfo(PROP_OS_VERSION) + ")");
+ System.out.println("设备品牌: " + getAndroidBrand());
+ System.out.println("设备型号: " + getAndroidModel());
+ } else if (isTermuxEnv()) {
+ System.out.println("运行环境: Termux OpenJDK");
} else {
System.out.println("运行环境: 标准JVM");
}
System.out.println("===========================\n");
}
- /**
- * 打印完整环境详细报告(调试/运维场景)
- */
- private static void printDetailedEnvInfo() {
- LogUtils.d(TAG, "开始执行完整运行环境检测");
+ /** 打印完整报告 */
+ private static void printDetailedEnvReport() {
+ LogUtils.d(TAG, "执行完整环境检测");
System.out.println("========================================");
System.out.println(" 运行环境详细检测报告");
System.out.println("========================================");
- // Java 核心信息
+ // Java核心信息
System.out.println("1. Java 核心信息");
- System.out.println(" - Java 版本号: " + getSysProp("java.version", "未知版本"));
- System.out.println(" - Java 虚拟机: " + getSysProp("java.vm.name", "未知虚拟机"));
- System.out.println(" - VM 厂商信息: " + getSysProp("java.vm.vendor", "未知厂商"));
- System.out.println(" - VM 版本号: " + getSysProp("java.vm.version", "未知版本"));
- System.out.println(" - Java 环境路径: " + getSysProp("java.home", "未知路径"));
- System.out.println(" - Java 规范版本: " + getSysProp("java.specification.version", "未知"));
+ System.out.println(" - Java 版本号: " + getAvailableJavaVersion());
+ System.out.println(" - Java 虚拟机: " + getEnvInfo(PROP_JAVA_VM_NAME));
+ System.out.println(" - VM 厂商信息: " + getEnvInfo(PROP_JAVA_VM_VENDOR));
+ System.out.println(" - Java 环境路径: " + getEnvInfo(PROP_JAVA_HOME));
// 操作系统信息
System.out.println("\n2. 操作系统信息");
- System.out.println(" - 系统名称: " + getSysProp("os.name", "未知系统"));
- System.out.println(" - 系统架构: " + getSysProp("os.arch", "未知架构"));
- System.out.println(" - 系统版本: " + getSysProp("os.version", "未知版本"));
- System.out.println(" - 文件分隔符: " + getSysProp("file.separator", "未知"));
- System.out.println(" - 路径分隔符: " + getSysProp("path.separator", "未知"));
+ System.out.println(" - 系统名称: " + getEnvInfo(PROP_OS_NAME));
+ System.out.println(" - 系统架构: " + getEnvInfo(PROP_OS_ARCH));
+ System.out.println(" - 系统版本: " + getEnvInfo(PROP_OS_VERSION));
+ System.out.println(" - 文件分隔符: " + getEnvInfo(PROP_FILE_SEP));
+ System.out.println(" - 路径分隔符: " + getEnvInfo(PROP_PATH_SEP));
+ System.out.println(" - 运行用户ID: " + getOsUserId());
// 运行目录&用户信息
System.out.println("\n3. 运行目录&用户信息");
- System.out.println(" - 当前运行目录: " + getSysProp("user.dir", "未知目录"));
- System.out.println(" - 当前用户名称: " + getSysProp("user.name", "未知用户"));
- System.out.println(" - 用户主目录: " + getSysProp("user.home", "未知目录"));
- System.out.println(" - 文件编码格式: " + getSysProp("file.encoding", "UTF-8"));
+ System.out.println(" - 当前运行目录: " + getEnvInfo(PROP_USER_DIR));
+ System.out.println(" - 当前用户名称: " + getEnvInfo(PROP_USER_NAME));
+ System.out.println(" - 用户主目录: " + getEnvInfo(PROP_USER_HOME));
+ System.out.println(" - 文件编码格式: " + getEnvInfo(PROP_FILE_ENCODING));
- // Android 专属信息
+ // Android专属信息
if (isAndroidEnv()) {
- LogUtils.d(TAG, "检测到Android环境,补充专属信息");
+ LogUtils.d(TAG, "补充Android专属信息");
System.out.println("\n4. Android 专属信息");
- System.out.println(" - Android SDK 等级: " + getSysProp("android.os.Build.VERSION.SDK_INT", "未知"));
- System.out.println(" - 设备型号: " + getSysProp("android.os.Build.MODEL", "未知"));
- System.out.println(" - 设备品牌: " + getSysProp("android.os.Build.BRAND", "未知"));
- System.out.println(" - 系统指纹: " + getSysProp("android.os.Build.FINGERPRINT", "未知"));
+ System.out.println(" - 设备品牌: " + getAndroidBrand());
+ System.out.println(" - 设备型号: " + getAndroidModel());
+ System.out.println(" - 系统指纹: " + getAndroidFingerprint());
}
System.out.println("========================================\n");
- LogUtils.d(TAG, "完整运行环境检测完成");
+ LogUtils.d(TAG, "完整环境检测完成");
}
- /**
- * 判定是否为Android环境
- */
- public static boolean isAndroidEnv() {
- String vmName = getSysProp("java.vm.name", "");
- return vmName.contains("Dalvik") || vmName.contains("ART");
- }
- /**
- * 安全获取系统属性,封装异常处理
- */
- public static String getSysProp(String key, String defaultValue) {
+ // ========== 内部工具方法(统一收口,消除冗余) ==========
+ /** 安全获取系统属性(统一异常处理) */
+ private static String getSysProp(String key, String defaultValue) {
try {
String value = System.getProperty(key);
return (value == null || value.trim().isEmpty()) ? defaultValue : value.trim();
} catch (Exception e) {
- LogUtils.w(TAG, "获取系统属性[" + key + "]失败,使用默认值", e);
+ LogUtils.w(TAG, "获取属性[" + key + "]失败,使用默认值", e);
return defaultValue;
}
}
+
+ /** 预热核心属性,解决首次获取失败问题 */
+ private static void preloadSysProps() {
+ getSysProp(PROP_USER_ID, "未知");
+ getSysProp(PROP_USER_NAME, "未知");
+ getSysProp(PROP_JAVA_VM_NAME, "未知");
+ }
+
+ /** 获取可用的Java版本(兼容多环境) */
+ private static String getAvailableJavaVersion() {
+ String javaVer = getEnvInfo(PROP_JAVA_VERSION);
+ if ("0".equals(javaVer) || "未知".equals(javaVer)) {
+ javaVer = getSysProp("java.vm.version", "未知Java版本");
+ }
+ return javaVer;
+ }
+
+ /** 跨环境获取用户ID(核心修复:支持Android/Termux/JVM) */
+ private static String getOsUserId() {
+ // 1. Android环境 取应用UID
+ if (isAndroidEnv()) {
+ try {
+ return String.valueOf(android.os.Process.myUid());
+ } catch (Exception e) {
+ LogUtils.w(TAG, "获取Android UID失败", e);
+ }
+ }
+
+ // 2. Termux环境 优先取环境变量,备选读uid_map文件
+ if (isTermuxEnv()) {
+ try {
+ // 优先读Termux USER_ID环境变量
+ String userId = System.getenv("USER_ID");
+ if (userId != null && !userId.trim().isEmpty()) {
+ return userId.trim();
+ }
+ // 备选方案:读取proc文件获取真实UID
+ String uidPath = "/proc/self/uid_map";
+ java.io.File uidFile = new java.io.File(uidPath);
+ if (uidFile.exists() && uidFile.canRead()) {
+ java.io.BufferedReader br = new java.io.BufferedReader(new java.io.FileReader(uidFile));
+ String line = br.readLine();
+ br.close();
+ if (line != null && line.trim().length() > 0) {
+ String[] parts = line.split("\\s+");
+ if (parts.length >= 1 && !parts[0].trim().isEmpty()) {
+ return parts[0].trim();
+ }
+ }
+ }
+ } catch (Exception e) {
+ LogUtils.w(TAG, "获取Termux用户ID失败", e);
+ }
+ }
+
+ // 3. 兜底:JVM标准属性 + 最终默认值(兼容Termux显示)
+ String jvmUid = getSysProp(PROP_USER_ID, "");
+ return jvmUid.isEmpty() ? getEnvInfo(PROP_USER_NAME) : jvmUid;
+ }
+
+
+ // ========== Android专属方法(对外公开,兼容Java7) ==========
+ /** 获取Android设备品牌 */
+ public static String getAndroidBrand() {
+ if (!isAndroidEnv()) {
+ return isTermuxEnv() ? "Termux 环境" : "非Android环境";
+ }
+ try {
+ Class> buildClz = Class.forName("android.os.Build");
+ return (String) buildClz.getField("BRAND").get(null);
+ } catch (Exception e) {
+ LogUtils.w(TAG, "获取Android品牌失败", e);
+ return "未知品牌";
+ }
+ }
+
+ /** 获取Android设备型号 */
+ public static String getAndroidModel() {
+ if (!isAndroidEnv()) {
+ return isTermuxEnv() ? "Termux 环境" : "非Android环境";
+ }
+ try {
+ Class> buildClz = Class.forName("android.os.Build");
+ String model = (String) buildClz.getField("MODEL").get(null);
+ return model == null || model.trim().isEmpty() ? "未知型号" : model.trim();
+ } catch (Exception e) {
+ LogUtils.w(TAG, "获取Android型号失败", e);
+ return "未知型号";
+ }
+ }
+
+ /** 获取Android系统指纹 */
+ public static String getAndroidFingerprint() {
+ if (!isAndroidEnv()) {
+ return isTermuxEnv() ? "Termux 环境" : "非Android环境";
+ }
+ try {
+ Class> buildClz = Class.forName("android.os.Build");
+ String fingerprint = (String) buildClz.getField("FINGERPRINT").get(null);
+ return fingerprint == null || fingerprint.trim().isEmpty() ? "未知指纹" : fingerprint.trim();
+ } catch (Exception e) {
+ LogUtils.w(TAG, "获取Android系统指纹失败", e);
+ return "未知指纹";
+ }
+ }
+
+ /** 获取Android应用UID(对外公开) */
+ public static String getAndroidUserId() {
+ return getOsUserId();
+ }
}
diff --git a/src/cc/winboll/util/ServerUtils.java b/src/cc/winboll/util/ServerUtils.java
index fef566c..381a632 100644
--- a/src/cc/winboll/util/ServerUtils.java
+++ b/src/cc/winboll/util/ServerUtils.java
@@ -16,7 +16,7 @@ import java.util.Map;
* 适配Java7语法,兼容Android API30环境,新增服务器连通性一键测试 + 本地验证码发送能力
* @Author 豆包&ZhanGSKen
* @Date 2026/01/16 00:00:00
- * @LastEditTime 2026/01/22 18:00:00
+ * @LastEditTime 2026/01/26 适配邮件工具单例调用
*/
public class ServerUtils {
// 基础属性
@@ -53,17 +53,29 @@ public class ServerUtils {
*/
public static boolean sendVerifyCodeLocal(String email) {
LogUtils.d(TAG, "调用sendVerifyCodeLocal,本地发送验证码,邮箱:" + email);
- // 1. 前置校验:邮箱格式
+ // 1. 前置校验:邮箱格式
if (email == null || email.trim().isEmpty() || !email.contains("@")) {
LogUtils.w(TAG, "本地发送验证码失败:邮箱为空或格式不合法");
return false;
}
String trimEmail = email.trim();
- // 2. 生成6位验证码(依赖VerifyCodeUtils,需确保该类存在)
+ // 2. 生成6位验证码(依赖VerifyCodeUtils,需确保该类存在)
String verifyCode = VerifyCodeUtils.generateCode();
- // 3. 直接调用邮件工具类发送
- boolean sendResult = EmailSendUtils.sendVerifyCodeEmail(trimEmail, verifyCode);
- // 4. 缓存验证码(供后续校验,依赖VerifyCodeCacheUtils)
+
+ // 核心调整:适配EmailSendUtils单例,先判就绪再发送
+ EmailSendUtils emailUtils = EmailSendUtils.getInstance();
+ if (!emailUtils.isConfigInited()) {
+ LogUtils.d(TAG, "邮件工具未初始化,先执行初始化流程");
+ boolean initSuccess = emailUtils.initEmailConfig();
+ if (!initSuccess) {
+ LogUtils.e(TAG, "邮件工具初始化失败,无法发送验证码");
+ return false;
+ }
+ }
+
+ // 3. 单例调用发送验证码邮件
+ boolean sendResult = emailUtils.sendVerifyCodeEmail(trimEmail, verifyCode);
+ // 4. 缓存验证码(供后续校验,依赖VerifyCodeCacheUtils)
if (sendResult) {
VerifyCodeCacheUtils.putCode(trimEmail, verifyCode);
LogUtils.i(TAG, "本地验证码发送成功,邮箱[" + trimEmail + "] 验证码[" + verifyCode + "]");
diff --git a/src/java/awt/datatransfer/DataFlavor.java b/src/java/awt/datatransfer/DataFlavor.java
new file mode 100644
index 0000000..d4eaba5
--- /dev/null
+++ b/src/java/awt/datatransfer/DataFlavor.java
@@ -0,0 +1,53 @@
+package java.awt.datatransfer;
+
+import java.io.Serializable;
+
+// 补丁类:补全所有缺失构造方法和属性,彻底解决ActivationDataFlavor初始化问题
+public class DataFlavor implements Serializable {
+ private static final long serialVersionUID = 1L;
+ public static final DataFlavor stringFlavor = new DataFlavor(String.class, "Plain String");
+
+ private final Class> representationClass;
+ private final String mimeType;
+ private final String humanPresentableName;
+
+ // 构造方法1:Class + humanName
+ public DataFlavor(Class> representationClass, String humanPresentableName) {
+ this.representationClass = representationClass;
+ this.mimeType = "text/plain";
+ this.humanPresentableName = humanPresentableName;
+ }
+
+ // 构造方法2:mimeType + humanName(核心缺失的构造方法!)
+ public DataFlavor(String mimeType, String humanPresentableName) {
+ this.representationClass = String.class;
+ this.mimeType = mimeType;
+ this.humanPresentableName = humanPresentableName;
+ }
+
+ // 构造方法3:完整参数版本(兼容更多场景)
+ public DataFlavor(String mimeType, String humanPresentableName, ClassLoader classLoader) {
+ this(mimeType, humanPresentableName);
+ }
+
+ public Class> getRepresentationClass() {
+ return representationClass;
+ }
+
+ public String getMimeType() {
+ return mimeType;
+ }
+
+ public String getHumanPresentableName() {
+ return humanPresentableName;
+ }
+
+ public boolean isMimeTypeEqual(String mimeType) {
+ return this.mimeType != null && this.mimeType.equalsIgnoreCase(mimeType);
+ }
+
+ public boolean isMimeTypeEqual(DataFlavor dataFlavor) {
+ return dataFlavor != null && isMimeTypeEqual(dataFlavor.getMimeType());
+ }
+}
+
diff --git a/src/java/awt/datatransfer/Transferable.java b/src/java/awt/datatransfer/Transferable.java
new file mode 100644
index 0000000..1686936
--- /dev/null
+++ b/src/java/awt/datatransfer/Transferable.java
@@ -0,0 +1,11 @@
+package java.awt.datatransfer;
+
+import java.io.IOException;
+
+// 补丁类:完整实现Transferable接口,适配javax.activation的所有调用
+public interface Transferable {
+ Object getTransferData(DataFlavor flavor) throws IOException, UnsupportedFlavorException;
+ DataFlavor[] getTransferDataFlavors();
+ boolean isDataFlavorSupported(DataFlavor flavor);
+}
+
diff --git a/src/java/awt/datatransfer/UnsupportedFlavorException.java b/src/java/awt/datatransfer/UnsupportedFlavorException.java
new file mode 100644
index 0000000..33d7c64
--- /dev/null
+++ b/src/java/awt/datatransfer/UnsupportedFlavorException.java
@@ -0,0 +1,9 @@
+// 配套异常类:新建UnsupportedFlavorException.java
+package java.awt.datatransfer;
+
+public class UnsupportedFlavorException extends Exception {
+ public UnsupportedFlavorException(DataFlavor flavor) {
+ super(flavor != null ? flavor.getMimeType() : "Unsupported data flavor");
+ }
+}
+