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"); + } +} +