控制台配置文件调整,验证码发送功能修复,添加启动时自动检测功能。

This commit is contained in:
2026-01-16 11:46:18 +08:00
parent a334bf9993
commit 9863e77512
10 changed files with 77752 additions and 161 deletions

75752
bash/hs_err_pid12840.log Normal file

File diff suppressed because it is too large Load Diff

1223
bash/replay_pid12840.log Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,14 +1,43 @@
# 配置文件编码UTF-8关键避免中文昵称乱码
# 邮件服务配置段(必须保留[EmailConfig]这个小节名,不能改)
; AuthCenter 配置文件模板INI格式
; 适配 smtp_auth_code 双校验逻辑,所有【】内为占位模板,需替换为实际有效值,注释可按需删除
[GlobalConfig]
; 日志输出级别:可选 DEBUG/INFO/WARN/ERROR默认填INFO
log_level=INFO
; 服务心跳间隔(毫秒)默认500ms无需修改保持默认即可
service_heartbeat_interval=500
[EmailConfig]
#SMTP服务器(按需选,常用如下)
# QQ邮箱smtp.qq.com | 163邮箱smtp.163.com | 网易企业邮smtp.163.com
smtp_host=smtp.qq.com
# SMTP SSL端口固定465通用大部分邮箱
smtp_port=465
# 发送方完整邮箱(比如 123456@qq.com
from_email=你的邮箱地址@qq.com
# 邮箱SMTP授权码不是登录密码需去邮箱后台生成
smtp_auth_code=你的邮箱授权码
# 发送方显示昵称(可选,可自定义)
from_nickname=Winboll验证服务
;SMTP服务器【必填】示例QQ邮箱=smtp.qq.com | 163邮箱=smtp.163.com | 企业邮箱按实际填写
smtp_host=【填写SMTP服务器地址】
; SMTP端口【必填】推荐SSL端口465兼容性强普通端口25需服务器开放默认填465
smtp_port=【填写端口号推荐465】
; 发送方完整邮箱账号【必填】格式为xxx@xxx.com
send_email_account=【填写发送方完整邮箱地址】
; 邮箱SMTP授权码【必填】非登录密码QQ/163邮箱需在设置中开启SMTP服务后生成
smtp_auth_code=【填写邮箱SMTP授权码】
; 发件人显示昵称【可选】,收件箱会显示该名称,不填默认显示邮箱账号
from_nickname=【自定义发件人昵称,可留空】
; 邮件连接超时时间(毫秒)默认5000ms无需修改
smtp_connect_timeout=5000
; 验证码邮件主题前缀【可选】,可自定义文案
email_subject_prefix=【自定义邮件主题前缀如AuthCenter认证中心】
; 验证码邮件内容模板,{code}为固定占位符不可删,文本可微调
verify_code_email_content=您的认证验证码为:{code}有效期5分钟请及时使用请勿泄露给他人
[ServerConfig]
; 远程服务器基础地址【可选】本地测试填http://localhost:8080跨设备填服务器局域网IP如http://192.168.1.100:8080
server_base_url=【填写服务器地址本地测试默认http://localhost:8080】
[VerifyCodeConfig]
; 验证码长度默认6位纯数字无需修改
verify_code_length=6
; 验证码有效期(分钟)默认5分钟无需修改
verify_code_expire_minutes=5
; 过期验证码清理间隔(分钟)默认1分钟无需修改
expire_code_clean_interval=1
[HttpServiceConfig]
; HTTP服务监听端口默认8080若端口被占用可修改为其他未占用端口如8081
http_server_port=【填写服务端口默认8080】
; HTTP服务启动超时时间(毫秒)默认3000ms无需修改
http_start_timeout=3000

View File

@@ -1,52 +1,95 @@
import cc.winboll.LogUtils;
import cc.winboll.auth.AuthCenterHttpService;
import cc.winboll.auth.MailAuthUtils;
import cc.winboll.util.ConsoleInputUtils;
import cc.winboll.util.EnvInfoUtils;
import cc.winboll.util.IniConfigUtils;
import cc.winboll.util.ServerUtils;
import cc.winboll.util.InitCheckUtils;
import cc.winboll.util.EmailSendUtils;
import java.io.IOException;
/**
* 程序入口类负责初始化运行环境检测与AuthCenter HTTP服务启动
* 适配Java7语言规范兼容Android API30运行环境支持优雅停机
* 启动参数规则1. -detail(可选,显示详细环境信息) 2. 服务器地址(可选,覆盖默认)
* 启动参数规则1. -detail(可选,显示详细环境信息) 2. 服务器地址(可选,覆盖默认/INI配置)
* 支持控制台输入交互输入help查帮助、输入exit退出、其他指令提示未识别
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
* @Date 2026/01/14 00:00:00
* @LastEditTime 2026/01/17 10:10:00
* @LastEditTime 2026/01/18 10:00:00
*/
public class Main {
// 全局静态属性(常量→变量,顺序清晰)
private static final String TAG = "AuthCenterMain";
private static final String DEFAULT_SERVER_URL = "http://localhost:8080";
private static final String ARG_DETAIL = "-detail";
private static final String INI_SERVER_SECTION = "ServerConfig";
private static final String INI_SERVER_KEY = "server_base_url";
// 新增HTTP端口配置项读取INI适配灵活性
private static final String INI_HTTP_SECTION = "HttpServiceConfig";
private static final String INI_HTTP_PORT_KEY = "http_server_port";
private static final int DEFAULT_HTTP_PORT = 8080;
private static AuthCenterHttpService httpService;
private static int httpPort; // 全局HTTP端口变量
/**
* 程序主入口方法,统一调度初始化、服务启动、常驻阻塞全流程
* @param args 启动参数,支持-detail详细环境、自定义服务器地址
*/
public static void main(String[] args) {
// 新增这1行启动就加载项目根目录的config.ini必须放在最前面
IniConfigUtils.loadRootConfig();
IniConfigUtils.loadRootConfig();
LogUtils.d(TAG, "【函数调用】main(),传入参数:" + arrayToString(args));
boolean showDetailEnv = parseDetailArg(args);
String serverUrl = parseServerUrlArg(args);
String serverUrl = getFinalServerUrl(args);
httpPort = getHttpPortFromConfig(); // 新增读取HTTP端口配置
EnvInfoUtils.printEnvReport(showDetailEnv);
initServerUtils(serverUrl);
// 新增:先初始化邮件配置,保障邮件校验能读取到完整配置
EmailSendUtils.initEmailConfig();
// 启动初始化校验(邮件+服务器)
boolean initCheckPass = InitCheckUtils.checkAllInitConfig();
if (!initCheckPass) {
System.exit(1);
}
System.out.println("Hello World!");
startAuthCenterHttpService();
ConsoleInputUtils.initConsoleScanner();
consoleBlockForService();
// 适配加固:退出循环后,兜底释放所有资源,形成闭环
releaseAllResources();
LogUtils.i(TAG, "【函数结束】main(),程序正常退出");
}
/**
* 新增从INI读取HTTP端口无配置兜底默认8080
*/
private static int getHttpPortFromConfig() {
LogUtils.d(TAG, "【函数调用】getHttpPortFromConfig()读取HTTP服务端口配置");
String portStr = IniConfigUtils.getConfigValue(INI_HTTP_SECTION, INI_HTTP_PORT_KEY);
if (portStr == null || portStr.trim().isEmpty()) {
LogUtils.i(TAG, "未读取到HTTP端口配置使用默认端口" + DEFAULT_HTTP_PORT);
return DEFAULT_HTTP_PORT;
}
try {
int port = Integer.parseInt(portStr.trim());
if (port > 0 && port <= 65535) {
LogUtils.i(TAG, "从INI读取HTTP端口成功" + port);
return port;
} else {
LogUtils.w(TAG, "HTTP端口配置非法使用默认端口" + DEFAULT_HTTP_PORT);
return DEFAULT_HTTP_PORT;
}
} catch (Exception e) {
LogUtils.w(TAG, "HTTP端口配置解析失败使用默认端口" + DEFAULT_HTTP_PORT);
return DEFAULT_HTTP_PORT;
}
}
/**
* 解析启动参数,判断是否输出详细环境信息
* @param args 启动参数数组
@@ -73,28 +116,49 @@ public class Main {
}
/**
* 解析启动参数,提取自定义服务器地址,非-detail参数均视为服务器地址
* 整合多来源服务器地址,优先级 启动参数 > INI配置 > 默认地址适配config.ini
* @param args 启动参数数组
* @return 解析到的服务器地址,无则返回默认地址
* @return 最终生效的服务器地址
*/
private static String getFinalServerUrl(String[] args) {
LogUtils.d(TAG, "【函数调用】getFinalServerUrl(),整合服务器地址(启动参数>INI>默认)");
String argUrl = parseServerUrlArg(args);
if (argUrl != null && !argUrl.trim().isEmpty()) {
LogUtils.i(TAG, "服务器地址优先级1启动参数 → " + argUrl);
return argUrl;
}
String iniUrl = IniConfigUtils.getConfigValue(INI_SERVER_SECTION, INI_SERVER_KEY);
if (iniUrl != null && !iniUrl.trim().isEmpty()) {
LogUtils.i(TAG, "服务器地址优先级2INI配置 → " + iniUrl);
return iniUrl;
}
LogUtils.i(TAG, "服务器地址优先级3默认地址 → " + DEFAULT_SERVER_URL);
return DEFAULT_SERVER_URL;
}
/**
* 从启动参数提取自定义服务器地址,非-detail参数均视为服务器地址
* @param args 启动参数数组
* @return 解析到的地址无则返回null
*/
private static String parseServerUrlArg(String[] args) {
LogUtils.d(TAG, "【函数调用】parseServerUrlArg()入参args" + arrayToString(args));
if (args == null || args.length == 0) {
LogUtils.d(TAG, "【函数返回】parseServerUrlArg(),无启动参数,返回默认地址:" + DEFAULT_SERVER_URL);
return DEFAULT_SERVER_URL;
LogUtils.d(TAG, "【函数返回】无启动参数返回null");
return null;
}
for (String arg : args) {
String trimArg = arg.trim();
if (!ARG_DETAIL.equalsIgnoreCase(trimArg) && !trimArg.isEmpty()) {
LogUtils.i(TAG, "【参数检测】匹配自定义服务器地址,覆盖默认值:" + trimArg);
LogUtils.d(TAG, "【函数返回】parseServerUrlArg()解析到地址,返回:" + trimArg);
LogUtils.d(TAG, "【函数返回】解析到地址,返回:" + trimArg);
return trimArg;
}
}
LogUtils.d(TAG, "【函数返回】parseServerUrlArg(),未解析到自定义地址,返回默认地址:" + DEFAULT_SERVER_URL);
return DEFAULT_SERVER_URL;
LogUtils.d(TAG, "【函数返回】未解析到自定义地址返回null");
return null;
}
/**
@@ -137,11 +201,10 @@ public class Main {
}
/**
* 初始化并启动AuthCenter HTTP服务绑定8080端口监听,异常则退出程序
* 初始化并启动AuthCenter HTTP服务绑定配置端口监听,异常则退出程序
*/
private static void startAuthCenterHttpService() {
LogUtils.d(TAG, "【函数调用】startAuthCenterHttpService()开始启动HTTP服务");
int httpPort = 8080;
httpService = new AuthCenterHttpService(httpPort);
try {
@@ -153,7 +216,6 @@ public class Main {
String errMsg = "HTTP服务启动失败端口 " + httpPort + " 可能被占用";
System.err.println(errMsg);
LogUtils.e(TAG, "【函数异常】startAuthCenterHttpService()HTTP服务启动失败", e);
// 启动失败时,兜底释放已初始化资源,避免残留
ConsoleInputUtils.forceExit();
releaseAllResources();
System.exit(1);
@@ -166,26 +228,22 @@ public class Main {
*/
private static void consoleBlockForService() {
LogUtils.d(TAG, "【函数调用】consoleBlockForService(),开始注册关闭钩子与常驻阻塞");
// 注册JVM关闭钩子接收kill信号或exit指令执行优雅停机kill -9 不触发)
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
@Override
public void run() {
LogUtils.i(TAG, "收到服务停止信号,执行优雅停机流程");
// 适配加固:优先调用强制退出,确保状态同步
@Override
public void run() {
LogUtils.i(TAG, "收到服务停止信号,执行优雅停机流程");
ConsoleInputUtils.forceExit();
ConsoleInputUtils.closeConsoleScanner();
if (httpService != null && httpService.isRunning()) {
httpService.stop();
LogUtils.i(TAG, "HTTP服务已优雅停止");
}
}
}));
ConsoleInputUtils.closeConsoleScanner();
if (httpService != null && httpService.isRunning()) {
httpService.stop();
LogUtils.i(TAG, "HTTP服务已优雅停止");
}
}
}));
System.out.println("\n服务已常驻运行输入exit可退出服务或执行kill 对应Java进程号停机");
// 低功耗无限阻塞,根据指令状态判断是否退出循环
while (true) {
try {
// 适配加固:优先判断退出标识,减少无效调用,提速退出响应
if (ConsoleInputUtils.isExit() || ConsoleInputUtils.checkConsoleCommand()) {
break;
}
@@ -202,17 +260,16 @@ public class Main {
}
/**
* 兜底释放所有资源,适配加固后工具类,形成退出闭环(核心适配点)
* 兜底释放所有资源,适配加固后工具类,形成退出闭环
*/
private static void releaseAllResources() {
LogUtils.d(TAG, "【函数调用】releaseAllResources(),兜底释放所有服务资源");
// 1. 停止HTTP服务
if (httpService != null && httpService.isRunning()) {
httpService.stop();
LogUtils.i(TAG, "HTTP服务兜底停止完成");
}
// 2. 关闭控制台扫描器彻底释放终端输入流解决Termux阻塞关键
ConsoleInputUtils.closeConsoleScanner();
MailAuthUtils.getInstance().shutdownScheduler();
LogUtils.d(TAG, "【函数结束】releaseAllResources(),所有资源释放完成");
}

View File

@@ -0,0 +1,222 @@
package cc.winboll.auth;
import cc.winboll.LogUtils;
import cc.winboll.util.EmailSendUtils;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* 邮件验证码认证工具类,单例模式,统一管理验证码发送、存储、校验与过期清理
* 适配Java7语法兼容项目邮件工具类与Termux环境支持验证码时效自动管控
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
* @Date 2026-01-17
* @LastEditTime 2026-01-17
*/
public class MailAuthUtils {
private static final String TAG = "MailAuthUtils";
// 单例核心:静态私有实例 + 私有构造volatile保证多线程可见性
private static volatile MailAuthUtils instance;
// 存储映射表key=邮箱value=验证码_过期时间戳下划线分隔
private final Map<String, String> emailCodeExpireMap;
// 验证码配置常量(可按需调整)
private static final int CODE_LENGTH = 6;
private static final int CODE_EXPIRE_MINUTES = 5; // 验证码有效期5分钟
private static final Random random = new Random();
// 定时清理线程池,后台自动清理过期验证码
private final ScheduledExecutorService scheduler;
/**
* 私有构造:禁止外部实例化,初始化映射表+定时清理任务
*/
private MailAuthUtils() {
emailCodeExpireMap = new HashMap<String, String>();
// 初始化定时任务延迟1分钟启动每1分钟执行1次清理低消耗
scheduler = Executors.newSingleThreadScheduledExecutor();
scheduler.scheduleAtFixedRate(new ExpiredCodeCleanTask(), 1, 1, TimeUnit.MINUTES);
LogUtils.d(TAG, "MailAuthUtils 初始化完成,定时清理任务已启动,验证码有效期" + CODE_EXPIRE_MINUTES + "分钟");
}
/**
* 单例获取方法:双重校验锁,线程安全+高效初始化
* @return 全局唯一MailAuthUtils实例
*/
public static MailAuthUtils getInstance() {
if (instance == null) {
synchronized (MailAuthUtils.class) {
if (instance == null) {
instance = new MailAuthUtils();
}
}
}
return instance;
}
/**
* 核心方法:发送邮箱验证码,发送成功存入<验证码_过期时间>并返回验证码
* @param email 目标收件邮箱
* @return 发送成功返回验证码失败返回null
*/
public String sendVerifyCode(String email) {
LogUtils.d(TAG, "sendVerifyCode 函数调用,目标邮箱:" + email);
if (email == null || email.trim().isEmpty()) {
LogUtils.w(TAG, "发送验证码失败:邮箱地址为空");
return null;
}
String trimEmail = email.trim();
// 1. 生成6位数字验证码
String verifyCode = generateVerifyCode();
// 2. 计算过期时间戳(当前时间+有效期,单位毫秒)
long expireTime = System.currentTimeMillis() + CODE_EXPIRE_MINUTES * 60 * 1000L;
// 3. 发送验证码邮件
boolean sendResult = EmailSendUtils.sendVerifyCodeEmail(trimEmail, verifyCode);
if (sendResult) {
// 存储格式验证码_过期时间戳
emailCodeExpireMap.put(trimEmail, verifyCode + "_" + expireTime);
LogUtils.i(TAG, "验证码发送成功|邮箱[" + trimEmail + "] 验证码[" + verifyCode + "] 有效期至" + CODE_EXPIRE_MINUTES + "分钟后");
return verifyCode;
} else {
LogUtils.e(TAG, "验证码发送失败|邮箱:" + trimEmail);
return null;
}
}
/**
* 核心方法:校验邮箱与验证码,自动判断是否过期
* @param email 待校验邮箱
* @param code 待校验验证码
* @return 匹配且未过期返回true否则返回false
*/
public boolean checkVerifyCode(String email, String code) {
LogUtils.d(TAG, "checkVerifyCode 函数调用,校验邮箱[" + email + "] 验证码[" + code + "]");
// 1. 空值校验
if (email == null || code == null || email.trim().isEmpty() || code.trim().isEmpty()) {
LogUtils.w(TAG, "校验失败|邮箱或验证码为空");
return false;
}
String trimEmail = email.trim();
String trimCode = code.trim();
String storeValue = emailCodeExpireMap.get(trimEmail);
// 2. 无对应记录校验
if (storeValue == null || !storeValue.contains("_")) {
LogUtils.w(TAG, "校验失败|邮箱[" + trimEmail + "] 无验证码记录或记录异常");
return false;
}
// 3. 拆分验证码与过期时间
String[] codeExpire = storeValue.split("_", 2);
if (codeExpire.length != 2) {
emailCodeExpireMap.remove(trimEmail);
LogUtils.w(TAG, "校验失败|邮箱[" + trimEmail + "] 记录格式异常,已清理无效记录");
return false;
}
String storedCode = codeExpire[0];
long expireTime;
try {
expireTime = Long.parseLong(codeExpire[1]);
} catch (NumberFormatException e) {
emailCodeExpireMap.remove(trimEmail);
LogUtils.w(TAG, "校验失败|邮箱[" + trimEmail + "] 过期时间格式异常,已清理", e);
return false;
}
// 4. 过期判断
long currentTime = System.currentTimeMillis();
if (currentTime > expireTime) {
emailCodeExpireMap.remove(trimEmail);
LogUtils.w(TAG, "校验失败|邮箱[" + trimEmail + "] 验证码已过期,已清理过期记录");
return false;
}
// 5. 验证码匹配判断
boolean match = trimCode.equals(storedCode);
if (match) {
emailCodeExpireMap.remove(trimEmail);
LogUtils.i(TAG, "校验成功|邮箱[" + trimEmail + "] 验证码匹配且未过期,已清理记录");
} else {
LogUtils.w(TAG, "校验失败|邮箱[" + trimEmail + "] 验证码不匹配|存储码[" + storedCode + "] 输入码[" + trimCode + "]");
}
return match;
}
/**
* 辅助方法:生成指定长度纯数字验证码
* @return 6位数字验证码
*/
private String generateVerifyCode() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < CODE_LENGTH; i++) {
sb.append(random.nextInt(10));
}
return sb.toString();
}
/**
* 内部定时任务:清理过期验证码,避免内存堆积
*/
private class ExpiredCodeCleanTask implements Runnable {
@Override
public void run() {
long currentTime = System.currentTimeMillis();
int cleanCount = 0;
// 迭代器遍历,支持安全删除
for (Map.Entry<String, String> entry : emailCodeExpireMap.entrySet()) {
String value = entry.getValue();
if (value == null || !value.contains("_")) {
emailCodeExpireMap.remove(entry.getKey());
cleanCount++;
continue;
}
String[] codeExpire = value.split("_", 2);
if (codeExpire.length != 2) {
emailCodeExpireMap.remove(entry.getKey());
cleanCount++;
continue;
}
try {
long expireTime = Long.parseLong(codeExpire[1]);
if (currentTime > expireTime) {
emailCodeExpireMap.remove(entry.getKey());
cleanCount++;
}
} catch (NumberFormatException e) {
emailCodeExpireMap.remove(entry.getKey());
cleanCount++;
}
}
if (cleanCount > 0) {
LogUtils.d(TAG, "定时清理完成|本次清理过期/无效验证码记录共" + cleanCount + "");
}
}
}
/**
* 兜底方法:关闭定时线程池,程序退出时调用,避免线程泄露
* 可在Main类释放资源方法中调用
*/
public void shutdownScheduler() {
if (scheduler != null && !scheduler.isShutdown()) {
scheduler.shutdown();
LogUtils.d(TAG, "定时清理线程池已关闭");
}
}
/**
* 公开方法:清空所有验证码记录(适配控制台手动清理指令)
*/
public void clearAllVerifyCode() {
if (emailCodeExpireMap.isEmpty()) {
LogUtils.d(TAG, "清空验证码记录:当前无任何验证码记录,无需清理");
System.out.println("当前无验证码记录,无需清空");
return;
}
int recordCount = emailCodeExpireMap.size();
emailCodeExpireMap.clear();
LogUtils.i(TAG, "已手动清空所有验证码记录,本次清空数量:" + recordCount + "");
}
}

View File

@@ -1,21 +1,24 @@
package cc.winboll.util;
import cc.winboll.LogUtils;
import cc.winboll.auth.MailAuthUtils;
import java.util.Scanner;
/**
* 控制台输入工具类,封装输入监听、指令处理、资源释放逻辑
* 适配Java7语言规范兼容Android Termux与标准JVM环境
* 支持help查看帮助、exit退出服务、selftestmail邮件自测预留指令扩展入口
* 支持help/exit/selftestmail/clearverifycode/testserver 5大指令
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
* @Date 2026/01/14 00:00:00
* @LastEditTime 2026/01/17 11:20:00
* @LastEditTime 2026/01/17 17:00:00
*/
public class ConsoleInputUtils {
private static final String TAG = "ConsoleInputUtils";
public static final String CMD_HELP = "help";
public static final String CMD_EXIT = "exit";
public static final String CMD_SELF_TEST_MAIL = "selftestmail"; // 新增邮件自测指令
public static final String CMD_SELF_TEST_MAIL = "selftestmail";
public static final String CMD_CLEAR_VERIFY_CODE = "clearverifycode";
public static final String CMD_TEST_SERVER = "testserver"; // 新增:服务器连通性测试指令
private static Scanner scanner;
private static volatile boolean isExit = false;
private static boolean isScannerInit = false;
@@ -32,8 +35,8 @@ public class ConsoleInputUtils {
try {
scanner = new Scanner(System.in);
isScannerInit = true;
// 提示新增指令
System.out.println("\n💡 输入 " + CMD_HELP + " 查看帮助,输入 " + CMD_EXIT + " 退出服务,输入 " + CMD_SELF_TEST_MAIL + " 邮件自测");
// 提示语补充 testserver 指令
System.out.println("\n💡 输入 " + CMD_HELP + " 查看帮助,输入 " + CMD_EXIT + " 退出服务,输入 " + CMD_SELF_TEST_MAIL + " 邮件自测,输入 " + CMD_CLEAR_VERIFY_CODE + " 清空验证码记录,输入 " + CMD_TEST_SERVER + " 测试服务器连通性");
LogUtils.d(TAG, "【函数结束】initConsoleScanner(),控制台输入扫描器初始化完成");
} catch (Exception e) {
LogUtils.e(TAG, "【函数异常】initConsoleScanner(),扫描器初始化失败", e);
@@ -60,13 +63,17 @@ public class ConsoleInputUtils {
String input = scanner.nextLine().trim();
LogUtils.d(TAG, "检测到控制台输入指令:" + input);
// 指令分发-新增selftestmail分支
// 指令分发-新增 testserver 分支
if (CMD_HELP.equalsIgnoreCase(input)) {
handleHelpCmd();
} else if (CMD_EXIT.equalsIgnoreCase(input)) {
handleExitCmd();
} else if (CMD_SELF_TEST_MAIL.equalsIgnoreCase(input)) {
handleSelfTestMailCmd();
} else if (CMD_CLEAR_VERIFY_CODE.equalsIgnoreCase(input)) {
handleClearVerifyCodeCmd();
} else if (CMD_TEST_SERVER.equalsIgnoreCase(input)) {
handleTestServerCmd();
} else if (!input.isEmpty()) {
handleCustomCmd(input);
}
@@ -105,21 +112,54 @@ public class ConsoleInputUtils {
}
/**
* 新增:处理邮件自测指令,自动生成测试码,发送到发送方邮箱
* 处理邮件自测指令,自动生成测试码,发送到发送方邮箱
*/
private static void handleSelfTestMailCmd() {
LogUtils.d(TAG, "【指令处理】执行selftestmail指令启动邮件自测流程");
System.out.println("\n🔍 开始邮件配置自测,正在初始化邮件服务...");
// 接收初始化结果,失败直接提示,不执行后续自测
boolean initResult = EmailSendUtils.initEmailConfig();
if (!initResult) {
System.err.println("❌ 邮件配置初始化失败自测终止请先完善INI文件[EmailConfig]配置");
LogUtils.w(TAG, "邮件初始化失败,自测流程终止");
return;
}
String testCode = String.valueOf((int)((Math.random() * 900000) + 100000));
EmailSendUtils.sendSelfTestEmail(testCode);
}
private static void handleSelfTestMailCmd() {
LogUtils.d(TAG, "【指令处理】执行selftestmail指令启动邮件自测流程");
System.out.println("\n🔍 开始邮件配置自测,正在初始化邮件服务...");
boolean initResult = EmailSendUtils.initEmailConfig();
if (!initResult) {
System.err.println("❌ 邮件配置初始化失败自测终止请先完善INI文件[EmailConfig]配置");
LogUtils.w(TAG, "邮件初始化失败,自测流程终止");
return;
}
String testCode = String.valueOf((int)((Math.random() * 900000) + 100000));
EmailSendUtils.sendSelfTestEmail(testCode);
}
/**
* 处理清空验证码指令调用MailAuthUtils清空所有记录
*/
private static void handleClearVerifyCodeCmd() {
LogUtils.d(TAG, "【指令处理】执行clearverifycode指令清空所有验证码记录");
try {
MailAuthUtils.getInstance().clearAllVerifyCode();
System.out.println("✅ 所有验证码记录已清空完成");
LogUtils.i(TAG, "全部验证码记录清空成功");
} catch (Exception e) {
System.err.println("❌ 验证码记录清空失败");
LogUtils.e(TAG, "清空验证码记录异常", e);
}
}
/**
* 新增处理服务器连通性测试指令调用ServerUtils一键测试
*/
private static void handleTestServerCmd() {
LogUtils.d(TAG, "【指令处理】执行testserver指令启动服务器连通性测试");
System.out.println("\n🔍 开始服务器连通性测试...");
try {
boolean testResult = ServerUtils.testServerConnectivity();
if (testResult) {
System.out.println("✅ 服务器连通性测试全部通过!");
} else {
System.err.println("❌ 服务器连通性测试失败,请查看日志排查问题");
}
} catch (Exception e) {
System.err.println("❌ 服务器连通性测试异常");
LogUtils.e(TAG, "服务器连通性测试执行异常", e);
}
}
/**
* 处理自定义指令:预留扩展入口,便于后续新增功能
@@ -127,7 +167,8 @@ public class ConsoleInputUtils {
*/
private static void handleCustomCmd(String cmd) {
LogUtils.w(TAG, "【指令处理】未识别指令:" + cmd + "输入help可查看支持的指令");
System.out.println("❌ 未识别指令[" + cmd + "]输入help查看帮助输入exit退出服务输入selftestmail邮件自测");
// 提示语补充 testserver 指令
System.out.println("❌ 未识别指令[" + cmd + "]输入help查看帮助输入exit退出服务输入selftestmail邮件自测输入clearverifycode清空验证码记录输入testserver测试服务器连通性");
}
/**

View File

@@ -15,7 +15,7 @@ import javax.mail.internet.MimeMessage;
* 适配javax.mail依赖原生兼容Java7语法与Android API30环境
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
* @Date 2026-01-15 00:00:00
* @LastEditTime 2026-01-17 11:40:00
* @LastEditTime 2026-01-17 23:00:00
*/
public class EmailSendUtils {
private static final String TAG = "EmailSendUtils";
@@ -23,22 +23,31 @@ public class EmailSendUtils {
private static Session mailSession;
private static String fromEmail;
private static String fromNickname;
private static String emailSubjectPrefix;
private static String verifyCodeContentTemplate;
/**
* 初始化邮件配置(增强校验,返回初始化结果,便于外部判断
* 初始化邮件配置(增强校验+双键名兼容+修复Java7内部类变量final问题
* @return 配置完整且初始化成功返回true否则false
*/
public static boolean initEmailConfig() {
LogUtils.d(TAG, "initEmailConfig 函数调用,开始初始化邮件配置");
// 读取INI配置增强日志提示
String smtpHost = IniConfigUtils.getConfigValue("EmailConfig", "smtp_host");
String smtpPort = IniConfigUtils.getConfigValue("EmailConfig", "smtp_port");
fromEmail = IniConfigUtils.getConfigValue("EmailConfig", "from_email");
final String smtpAuthCode = IniConfigUtils.getConfigValue("EmailConfig", "smtp_auth_code");
fromEmail = IniConfigUtils.getConfigValue("EmailConfig", "send_email_account");
// 修复核心给smtpAuthCode加final适配Java7内部类访问规则
final String smtpAuthCode;
String tempAuthCode = IniConfigUtils.getConfigValue("EmailConfig", "send_email_auth_code");
if (isEmpty(tempAuthCode)) {
tempAuthCode = IniConfigUtils.getConfigValue("EmailConfig", "smtp_auth_code");
LogUtils.w(TAG, "未读取到send_email_auth_code兜底读取smtp_auth_code配置");
}
smtpAuthCode = tempAuthCode; // 赋值给final变量
fromNickname = IniConfigUtils.getConfigValue("EmailConfig", "from_nickname");
emailSubjectPrefix = IniConfigUtils.getConfigValue("EmailConfig", "email_subject_prefix");
verifyCodeContentTemplate = IniConfigUtils.getConfigValue("EmailConfig", "verify_code_email_content");
LogUtils.d(TAG, "读取INI邮件配置smtpHost[" + smtpHost + "] smtpPort[" + smtpPort + "] fromEmail[" + fromEmail + "]");
// 增强空值提示,明确缺失项
if (isEmpty(smtpHost)) {
LogUtils.e(TAG, "邮件核心配置缺失smtp_host 未配置");
return false;
@@ -48,15 +57,16 @@ public class EmailSendUtils {
return false;
}
if (isEmpty(fromEmail)) {
LogUtils.e(TAG, "邮件核心配置缺失:from_email 未配置");
LogUtils.e(TAG, "邮件核心配置缺失:send_email_account 未配置");
return false;
}
if (isEmpty(smtpAuthCode)) {
LogUtils.e(TAG, "邮件核心配置缺失smtp_auth_code 未配置");
LogUtils.e(TAG, "邮件核心配置缺失:send_email_auth_code / smtp_auth_code 未配置");
return false;
}
if (isEmpty(emailSubjectPrefix)) emailSubjectPrefix = "【AuthCenter 认证中心】";
if (isEmpty(verifyCodeContentTemplate)) verifyCodeContentTemplate = "您的认证验证码为:{code}有效期5分钟请及时使用请勿泄露给他人";
// 组装SMTP配置适配Java7
mailProps.put("mail.smtp.host", smtpHost);
mailProps.put("mail.smtp.port", smtpPort);
mailProps.put("mail.smtp.auth", "true");
@@ -65,7 +75,6 @@ public class EmailSendUtils {
mailProps.put("mail.transport.protocol", "smtp");
try {
// 会话创建加异常捕获,防止配置错误导致崩溃
mailSession = Session.getInstance(mailProps, new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
@@ -73,7 +82,7 @@ public class EmailSendUtils {
}
});
mailSession.setDebug(false);
LogUtils.i(TAG, "邮件发送配置初始化完成,发送方邮箱:" + fromEmail);
LogUtils.i(TAG, "邮件发送配置初始化完成,发送方邮箱:" + fromEmail + " 发送方昵称:" + (fromNickname == null ? "默认昵称" : fromNickname));
return true;
} catch (Exception e) {
LogUtils.e(TAG, "邮件会话创建失败,检查配置是否正确", e);
@@ -82,7 +91,7 @@ public class EmailSendUtils {
}
/**
* 发送验证码邮件(核心方法,供ServerUtils调用
* 发送验证码邮件(核心方法,适配INI主题/内容模板
* @param toEmail 接收方邮箱
* @param verifyCode 验证码
* @return 发送成功返回true失败返回false
@@ -100,10 +109,11 @@ public class EmailSendUtils {
try {
MimeMessage message = new MimeMessage(mailSession);
message.setFrom(new InternetAddress(fromEmail, fromNickname == null ? "Winboll验证" : fromNickname, "UTF-8"));
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("【Winboll验证】您的验证码", "UTF-8");
String content = String.format("您好!您的验证代码为:%s\n该验证码5分钟内有效请及时使用请勿泄露给他人。", verifyCode);
message.setSubject(emailSubjectPrefix + " 验证码通知", "UTF-8");
String content = verifyCodeContentTemplate.replace("{code}", verifyCode);
message.setText(content, "UTF-8");
Transport.send(message);
LogUtils.i(TAG, "验证码邮件发送成功,收件人:" + toEmail);
@@ -125,7 +135,6 @@ public class EmailSendUtils {
LogUtils.w(TAG, "发送方邮箱或测试码为空,自测失败");
return false;
}
// 先判断会话是否就绪,不就绪直接提示
if (mailSession == null) {
LogUtils.e(TAG, "邮件会话未初始化自测终止请先完成INI邮件配置");
System.err.println("❌ 邮件会话未初始化请先检查INI文件的[EmailConfig]配置项是否完整");
@@ -134,9 +143,10 @@ public class EmailSendUtils {
try {
MimeMessage message = new MimeMessage(mailSession);
message.setFrom(new InternetAddress(fromEmail, fromNickname == null ? "Winboll验证" : fromNickname, "UTF-8"));
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("【Winboll邮件自测】配置连通性测试", "UTF-8");
message.setSubject(emailSubjectPrefix + " 配置自测通知", "UTF-8");
String content = String.format("邮件发送配置自测成功!\n自测校验码%s\n当前发送方邮箱%s\n说明邮件服务配置正常可正常发送验证码。", testCode, fromEmail);
message.setText(content, "UTF-8");
Transport.send(message);

View File

@@ -1,75 +1,60 @@
package cc.winboll.util;
import cc.winboll.LogUtils;
import java.util.Scanner;
/**
* 服务帮助信息工具类,统一封装帮助信息打印逻辑
* 适配Java7语言规范兼容Android API30环境支持全局复用
* 帮助信息工具类,统一管理服务帮助内容,适配控制台展示
* 兼容Java7,清晰罗列功能与指令,便于快速查阅
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
* @Date 2026/01/14 00:00:00
* @LastEditTime 2026/01/16 23:10:00
* @Date 2026/01/14
* @LastEditTime 2026/01/17
*/
public class HelpInfoUtils {
private static final String TAG = "HelpInfoUtils";
// 服务核心常量(统一维护,便于全局修改)
private static final int HTTP_SERVICE_PORT = 8080;
private static final String DEFAULT_SERVER_URL = "http://localhost:8080";
private static final String HEALTH_CHECK_API = "GET http://localhost:" + HTTP_SERVICE_PORT + "/authcenter/ping";
/**
* 打印完整服务帮助信息,全局统一调用入口
* 打印完整帮助信息供help指令调用
*/
public static void printFullHelpInfo() {
LogUtils.d(TAG, "【函数调用】printFullHelpInfo(),开始打印服务帮助信息");
System.out.println("\n================================ 服务帮助信息 ================================");
printStartArgsInfo();
System.out.println("\n=============== Winboll AuthCenter 帮助中心 ===============");
printBasicInfo();
printConsoleCmdInfo();
printServiceCoreInfo();
printStopServiceInfo();
System.out.println("============================================================================\n");
LogUtils.d(TAG, "【函数结束】printFullHelpInfo(),服务帮助信息打印完成");
printNoticeInfo();
System.out.println("===========================================================\n");
}
/**
* 打印启动参数说明
* 基础服务信息
*/
private static void printStartArgsInfo() {
System.out.println("1. 启动参数说明");
System.out.println(" -detail :启动时输出详细运行环境检测报告(默认输出精简报告)");
System.out.println(" 自定义地址 启动时传入服务器地址覆盖默认值示例java Main http://192.168.1.100:8080");
System.out.println(" 组合使用 参数顺序无要求示例java Main -detail http://192.168.1.100:8080");
private static void printBasicInfo() {
System.out.println("1. 服务基础信息");
System.out.println(" 服务名称AuthCenter 认证中心");
System.out.println(" 核心能力:邮件验证码发送、校验、过期管理");
System.out.println(" 兼容环境Android Termux / 标准JVM");
System.out.println();
}
/**
* 打印控制台指令说明
* 控制台指令说明(核心:补全所有指令)
*/
// 找到 printConsoleCmdInfo 方法新增selftestmail说明
private static void printConsoleCmdInfo() {
System.out.println("2. 控制台指令说明");
System.out.println(" help :调用展示本帮助信息");
System.out.println(" exit :执行优雅停机,关闭服务并释放资源(推荐手动停止方式)");
System.out.println(" selftestmail :邮件服务自测,发送测试邮件到发送方自身邮箱");
}
/**
* 打印核心服务配置信息
*/
private static void printServiceCoreInfo() {
System.out.println("3. 核心服务信息");
System.out.println(" HTTP服务端口 " + HTTP_SERVICE_PORT);
System.out.println(" 健康检查接口 " + HEALTH_CHECK_API);
System.out.println(" 默认服务器地址:" + DEFAULT_SERVER_URL);
private static void printConsoleCmdInfo() {
System.out.println("2. 控制台指令说明(大小写不敏感)");
System.out.println(" help :调用展示本帮助信息");
System.out.println(" exit :优雅停机,释放资源后退出服务");
System.out.println(" selftestmail :邮件服务自测,发送测试邮件到发送方邮箱");
System.out.println(" clearverifycode :手动清空所有已存储的验证码记录");
System.out.println(" testserver :一键测试服务器基础地址+验证码接口连通性"); // 新增该行
System.out.println();
}
/**
* 打印服务停止方式说明
* 注意事项
*/
private static void printStopServiceInfo() {
System.out.println("4. 停止服务说明");
System.out.println(" 方式1控制台输入 exit 指令(最推荐,优雅停机释放所有资源)");
System.out.println(" 方式2执行 kill 对应Java进程号推荐触发优雅停机释放资源");
System.out.println(" 方式3强制中断kill -9可能造成资源未释放不推荐");
private static void printNoticeInfo() {
System.out.println("3. 注意事项");
System.out.println(" ① 执行selftestmail前需确保config.ini中[EmailConfig]配置完整");
System.out.println(" ② 验证码默认有效期5分钟超时自动清理也可手动clearverifycode");
System.out.println(" ③ 退出服务优先用exit指令避免资源泄露");
}
}

View File

@@ -0,0 +1,150 @@
package cc.winboll.util;
import cc.winboll.LogUtils;
/**
* 启动初始化校验工具类,一键校验邮件+服务器核心配置,保障服务启动基础条件
* 适配Java7语法兼容Android API30环境校验失败直接提示并终止服务
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
* @Date 2026/01/17 21:00:00
* @LastEditTime 2026/01/18 11:00:00
*/
public class InitCheckUtils {
private static final String TAG = "InitCheckUtils";
// 服务器连通性测试超时兜底和ServerUtils保持一致
private static final int CHECK_TIMEOUT_TIP = 5000;
// 优化:本地自服务地址常量,兼容带/不带斜杠场景,彻底解决匹配失败
private static final String LOCAL_SERVER_1 = "http://localhost:8080";
private static final String LOCAL_SERVER_1_SLASH = "http://localhost:8080/";
private static final String LOCAL_SERVER_2 = "http://127.0.0.1:8080";
private static final String LOCAL_SERVER_2_SLASH = "http://127.0.0.1:8080/";
// 新增邮件校验开关true=强制校验false=跳过调试时可改false
private static final boolean FORCE_MAIL_CHECK = true;
/**
* 启动核心入口:一键校验 邮件配置完整性 + 服务器配置+连通性,全通过才允许服务启动
* @return true=所有校验通过false=任意一项失败
*/
public static boolean checkAllInitConfig() {
LogUtils.i(TAG, "==================== 启动配置校验开始 ====================");
// 优化:支持跳过邮件校验,适配调试场景
boolean mailCheck = FORCE_MAIL_CHECK ? checkEmailConfig() : true;
boolean serverCheck = checkServerConfig();
if (mailCheck && serverCheck) {
LogUtils.i(TAG, "==================== 所有启动配置校验通过 ====================");
System.out.println("\n✅ 初始化校验全部通过,服务可正常启动");
return true;
} else {
LogUtils.e(TAG, "==================== 启动配置校验失败,服务终止 ====================");
System.err.println("\n❌ 初始化校验失败,请修复配置后重新启动");
return false;
}
}
/**
* 校验1邮件核心配置完整性必填项非空+配置格式基础校验)
* @return true=邮件配置合格false=不合格
*/
private static boolean checkEmailConfig() {
LogUtils.d(TAG, "开始校验【邮件服务配置】");
boolean result = true;
// 读取核心必填项和EmailConfig配置项一一对应
String smtpHost = IniConfigUtils.getConfigValue("EmailConfig", "smtp_host");
String smtpPort = IniConfigUtils.getConfigValue("EmailConfig", "smtp_port");
String sendAccount = IniConfigUtils.getConfigValue("EmailConfig", "send_email_account");
String authCode = IniConfigUtils.getConfigValue("EmailConfig", "smtp_auth_code");
// 1. 非空校验(核心必填项)
if (isEmpty(smtpHost)) {
LogUtils.e(TAG, "邮件配置缺失:[EmailConfig] smtp_host 未配置");
result = false;
}
if (isEmpty(smtpPort)) {
LogUtils.e(TAG, "邮件配置缺失:[EmailConfig] smtp_port 未配置");
result = false;
}
if (isEmpty(sendAccount)) {
LogUtils.e(TAG, "邮件配置缺失:[EmailConfig] send_email_account 未配置");
result = false;
}
if (isEmpty(authCode)) {
LogUtils.e(TAG, "邮件配置缺失:[EmailConfig] smtp_auth_code 未配置");
result = false;
}
// 2. 基础格式校验(提升配置正确性)
if (!isEmpty(sendAccount) && !sendAccount.contains("@")) {
LogUtils.e(TAG, "邮件配置错误send_email_account 格式非法,需为完整邮箱地址");
result = false;
}
if (!isEmpty(smtpPort)) {
try {
Integer.parseInt(smtpPort);
} catch (Exception e) {
LogUtils.e(TAG, "邮件配置错误smtp_port 需为数字端口号");
result = false;
}
}
if (result) {
LogUtils.i(TAG, "邮件服务配置校验通过");
} else {
System.err.println("⚠️ 邮件配置问题指引1. 检查config.ini[EmailConfig]小节 2. 授权码需开启SMTP后生成 3. 端口填465SSL");
}
return result;
}
/**
* 校验2服务器配置完整性+连通性(地址非空+一键连通测试)
* 优化:兼容本地地址带/不带斜杠,自身服务未启动时跳过连通校验,远程地址正常校验
* @return true=服务器配置合格false=不合格
*/
private static boolean checkServerConfig() {
LogUtils.d(TAG, "开始校验【服务器交互配置】");
boolean result = true;
String serverUrl = ServerUtils.getServerBaseUrl();
// 1. 服务器地址配置校验
if (isEmpty(serverUrl)) {
LogUtils.e(TAG, "服务器配置缺失:未初始化服务器地址,请检查启动参数/INI[ServerConfig]");
result = false;
return result;
}
if (!serverUrl.startsWith("http://") && !serverUrl.startsWith("https://")) {
LogUtils.e(TAG, "服务器配置错误地址需带http/https协议头");
result = false;
return result;
}
// 核心优化:兼容本地地址带/不带斜杠4种场景都能跳过连通校验
if (LOCAL_SERVER_1.equals(serverUrl) || LOCAL_SERVER_1_SLASH.equals(serverUrl)
|| LOCAL_SERVER_2.equals(serverUrl) || LOCAL_SERVER_2_SLASH.equals(serverUrl)) {
LogUtils.i(TAG, "检测为本地自服务地址,跳过连通性测试(服务启动后自动生效)");
result = true;
return result;
}
// 2. 非本地自服务,执行连通性测试
LogUtils.d(TAG, "开始服务器连通性测试,地址:" + serverUrl + "(超时时间" + CHECK_TIMEOUT_TIP + "ms");
boolean connectable = ServerUtils.testServerConnectivity();
if (!connectable) {
LogUtils.e(TAG, "服务器连通性测试失败");
System.err.println("⚠️ 服务器连通问题指引1. 检查IP+端口是否正确 2. 服务器是否启动 3. 设备是否在同一局域网");
result = false;
}
if (result) {
LogUtils.i(TAG, "服务器配置+连通性校验通过");
}
return result;
}
/**
* 空值校验工具,适配多参数判断
*/
private static boolean isEmpty(String str) {
return str == null || str.trim().isEmpty();
}
}

View File

@@ -2,19 +2,21 @@ package cc.winboll.util;
import cc.winboll.LogUtils;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
/**
* 纯服务器交互工具类仅负责封装HTTP请求、调用远程接口无监听能力
* 适配Java7语法兼容Android API30环境
* 适配Java7语法兼容Android API30环境,新增服务器连通性一键测试
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
* @Date 2026/01/16 00:00:00
* @LastEditTime 2026/01/16 00:45:00
* @LastEditTime 2026/01/17 16:00:00
*/
public class ServerUtils {
// 基础属性
@@ -22,25 +24,42 @@ public class ServerUtils {
private static final int CONNECT_TIMEOUT = 5000;
private static final int READ_TIMEOUT = 5000;
private static String serverBaseUrl;
/**
* 初始化服务器基础地址
*/
public static void initServerUrl(String url) {
LogUtils.d(TAG, "调用initServerUrl传入地址" + url);
serverBaseUrl = url;
if (url == null || url.trim().isEmpty()) {
LogUtils.w(TAG, "服务器地址为空,初始化失败");
return;
}
serverBaseUrl = url.trim().endsWith("/") ? url.trim() : url.trim() + "/";
LogUtils.d(TAG, "服务器地址初始化完成:" + serverBaseUrl);
}
/**
* 发送验证码请求POST
* 优化版:发送验证码请求POST,增强校验+兜底提示
*/
public static String sendVerifyCode(String email) {
LogUtils.d(TAG, "调用sendVerifyCode邮箱参数" + email);
String url = serverBaseUrl + "/api/sendVerifyCode";
// 1. 前置校验:服务器地址未初始化
if (serverBaseUrl == null || serverBaseUrl.trim().isEmpty()) {
LogUtils.e(TAG, "发送验证码失败服务器地址未初始化请先调用initServerUrl()");
return null;
}
// 2. 前置校验:邮箱空值/格式基础校验
if (email == null || email.trim().isEmpty() || !email.contains("@")) {
LogUtils.w(TAG, "发送验证码失败:邮箱地址为空或格式不合法");
return null;
}
String trimEmail = email.trim();
String url = serverBaseUrl + "api/sendVerifyCode";
Map<String, String> params = new HashMap<String, String>();
params.put("email", email);
return sendPostRequest(url, params);
params.put("email", trimEmail);
String result = sendPostRequest(url, params);
LogUtils.d(TAG, "验证码发送请求执行完成,邮箱[" + trimEmail + "] 接口响应:" + (result == null ? "null" : result));
return result;
}
/**
@@ -48,10 +67,18 @@ public class ServerUtils {
*/
public static String verifyCode(String email, String code) {
LogUtils.d(TAG, "调用verifyCode参数邮箱[" + email + "] 验证码[" + code + "]");
String url = serverBaseUrl + "/api/verifyCode";
if (serverBaseUrl == null || serverBaseUrl.trim().isEmpty()) {
LogUtils.e(TAG, "校验验证码失败:服务器地址未初始化");
return null;
}
if (email == null || email.trim().isEmpty() || code == null || code.trim().isEmpty()) {
LogUtils.w(TAG, "校验验证码失败:邮箱或验证码为空");
return null;
}
String url = serverBaseUrl + "api/verifyCode";
Map<String, String> params = new HashMap<String, String>();
params.put("email", email);
params.put("code", code);
params.put("email", email.trim());
params.put("code", code.trim());
return sendPostRequest(url, params);
}
@@ -60,10 +87,18 @@ public class ServerUtils {
*/
public static String submitAppPublicKey(String email, String publicKey) {
LogUtils.d(TAG, "调用submitAppPublicKey参数邮箱[" + email + "] 公钥[" + publicKey + "]");
String url = serverBaseUrl + "/api/submitPublicKey";
if (serverBaseUrl == null || serverBaseUrl.trim().isEmpty()) {
LogUtils.e(TAG, "提交公钥失败:服务器地址未初始化");
return null;
}
if (email == null || email.trim().isEmpty() || publicKey == null || publicKey.trim().isEmpty()) {
LogUtils.w(TAG, "提交公钥失败:邮箱或公钥为空");
return null;
}
String url = serverBaseUrl + "api/submitPublicKey";
Map<String, String> params = new HashMap<String, String>();
params.put("email", email);
params.put("appPublicKey", publicKey);
params.put("email", email.trim());
params.put("appPublicKey", publicKey.trim());
return sendPostRequest(url, params);
}
@@ -72,12 +107,89 @@ public class ServerUtils {
*/
public static String sendPingRequest(String encryptPingData) {
LogUtils.d(TAG, "调用sendPingRequest加密数据" + encryptPingData);
String url = serverBaseUrl + "/api/heartbeat/ping";
if (serverBaseUrl == null || serverBaseUrl.trim().isEmpty()) {
LogUtils.e(TAG, "发送心跳失败:服务器地址未初始化");
return null;
}
if (encryptPingData == null || encryptPingData.trim().isEmpty()) {
LogUtils.w(TAG, "发送心跳失败:加密数据为空");
return null;
}
String url = serverBaseUrl + "api/heartbeat/ping";
Map<String, String> params = new HashMap<String, String>();
params.put("encryptData", encryptPingData);
params.put("encryptData", encryptPingData.trim());
return sendPostRequest(url, params);
}
/**
* 新增核心:服务器连通性一键测试,快速检测地址是否可达、基础接口是否可用
* @return 测试成功返回true失败返回false日志输出详细原因
*/
public static boolean testServerConnectivity() {
LogUtils.i(TAG, "开始执行服务器连通性一键测试");
// 1. 先校验基础地址
if (serverBaseUrl == null || serverBaseUrl.trim().isEmpty()) {
LogUtils.e(TAG, "连通性测试失败服务器地址未初始化请先调用initServerUrl()");
System.err.println("❌ 连通性测试失败:未初始化服务器地址");
return false;
}
// 2. 测试基础地址可达性(优先测根地址,无接口也能判断连通)
String testUrl = serverBaseUrl;
HttpURLConnection conn = null;
try {
URL url = new URL(testUrl);
conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(CONNECT_TIMEOUT);
conn.setReadTimeout(READ_TIMEOUT);
conn.setRequestProperty("Charset", "UTF-8");
int responseCode = conn.getResponseCode();
// 响应码200-399都算连通成功3xx重定向也代表地址可达
boolean isReachable = responseCode >= 200 && responseCode < 400;
if (isReachable) {
LogUtils.i(TAG, "✅ 服务器基础地址连通成功,响应码:" + responseCode);
System.out.println("✅ 服务器基础地址连通成功:" + testUrl);
// 3. 额外测试验证码接口基础可用性(可选,补充提示)
String apiTestUrl = serverBaseUrl + "api/sendVerifyCode";
LogUtils.d(TAG, "额外测试验证码接口可达性:" + apiTestUrl);
HttpURLConnection apiConn = (HttpURLConnection) new URL(apiTestUrl).openConnection();
apiConn.setRequestMethod("OPTIONS"); // OPTIONS请求不触发业务仅测接口是否存在
apiConn.setConnectTimeout(CONNECT_TIMEOUT);
int apiCode = apiConn.getResponseCode();
if (apiCode >= 200 && apiCode < 400) {
LogUtils.i(TAG, "✅ 验证码接口可达性测试成功");
System.out.println("✅ 验证码接口可达性测试成功");
} else {
LogUtils.w(TAG, "⚠️ 验证码接口可达性测试提示,响应码:" + apiCode + "(接口可能未部署/未开放OPTIONS");
System.out.println("⚠️ 验证码接口响应码:" + apiCode + ",请确认接口是否部署");
}
apiConn.disconnect();
return true;
} else {
LogUtils.e(TAG, "❌ 服务器连通失败,响应码:" + responseCode + ",响应信息:" + conn.getResponseMessage());
System.err.println("❌ 服务器连通失败,响应码:" + responseCode);
return false;
}
} catch (java.net.ConnectException e) {
LogUtils.e(TAG, "❌ 服务器连通失败无法连接到地址可能IP/端口错误或网络不通", e);
System.err.println("❌ 连通失败:无法连接到 " + testUrl + "请检查IP、端口和网络");
return false;
} catch (java.net.SocketTimeoutException e) {
LogUtils.e(TAG, "❌ 服务器连通失败:请求超时,网络延迟过高或服务器未响应", e);
System.err.println("❌ 连通失败:请求超时(超时时间" + CONNECT_TIMEOUT + "ms");
return false;
} catch (Exception e) {
LogUtils.e(TAG, "❌ 服务器连通测试异常", e);
System.err.println("❌ 连通测试异常:" + e.getMessage());
return false;
} finally {
if (conn != null) {
conn.disconnect();
}
}
}
/**
* 获取当前初始化的服务器基础地址
*/
@@ -87,12 +199,12 @@ public class ServerUtils {
}
/**
* 通用POST请求封装
* 通用POST请求封装(优化参数拼接+URL编码兼容空参数场景
*/
private static String sendPostRequest(String urlStr, Map<String, String> params) {
LogUtils.d(TAG, "调用sendPostRequest请求地址" + urlStr);
if (urlStr == null || params == null) {
LogUtils.w(TAG, "POST请求参数为空");
if (urlStr == null || params == null || params.isEmpty()) {
LogUtils.w(TAG, "POST请求失败:地址为空或无有效参数");
return null;
}
@@ -105,11 +217,16 @@ public class ServerUtils {
conn.setReadTimeout(READ_TIMEOUT);
conn.setDoOutput(true);
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
conn.setRequestProperty("Charset", "UTF-8"); // 新增编码指定,避免乱码
// 拼接参数
// 拼接参数补充URL编码过滤空键空值
StringBuilder paramStr = new StringBuilder();
for (Map.Entry<String, String> entry : params.entrySet()) {
paramStr.append(entry.getKey()).append("=").append(entry.getValue()).append("&");
String key = entry.getKey();
String value = entry.getValue();
if (key != null && value != null) {
paramStr.append(key).append("=").append(URLEncoder.encode(value, "UTF-8")).append("&");
}
}
if (paramStr.length() > 0) {
paramStr.deleteCharAt(paramStr.length() - 1);
@@ -120,18 +237,23 @@ public class ServerUtils {
os.flush();
os.close();
// 优化捕获非200响应的内容便于排查问题
InputStream inputStream = conn.getResponseCode() == HttpURLConnection.HTTP_OK ? conn.getInputStream() : conn.getErrorStream();
BufferedReader br = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
StringBuilder response = new StringBuilder();
String line;
while ((line = br.readLine()) != null) {
response.append(line);
}
br.close();
inputStream.close();
if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"));
StringBuilder response = new StringBuilder();
String line;
while ((line = br.readLine()) != null) {
response.append(line);
}
br.close();
LogUtils.d(TAG, "POST请求成功响应内容" + response.toString());
return response.toString();
} else {
LogUtils.w(TAG, "POST请求失败响应码" + conn.getResponseCode());
LogUtils.w(TAG, "POST请求失败响应码" + conn.getResponseCode() + " 响应内容:" + response.toString());
return response.toString(); // 返回错误响应,便于上层排查
}
} catch (Exception e) {
LogUtils.e(TAG, "POST请求异常", e);