邮件发送端测试完成
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,3 +1,3 @@
|
||||
bin/
|
||||
runtime/
|
||||
email_config.ini
|
||||
config.ini
|
||||
|
||||
14
config.ini-demo.txt
Normal file
14
config.ini-demo.txt
Normal file
@@ -0,0 +1,14 @@
|
||||
# 配置文件编码:UTF-8(关键,避免中文昵称乱码)
|
||||
# 邮件服务配置段(必须保留[EmailConfig]这个小节名,不能改)
|
||||
[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验证服务
|
||||
BIN
libs/activation.jar
Normal file
BIN
libs/activation.jar
Normal file
Binary file not shown.
BIN
libs/javax.mail.jar
Normal file
BIN
libs/javax.mail.jar
Normal file
Binary file not shown.
@@ -1,9 +1,9 @@
|
||||
import cc.winboll.LogUtils;
|
||||
import cc.winboll.auth.AuthCenterHttpService;
|
||||
import cc.winboll.util.EnvInfoUtils;
|
||||
import cc.winboll.util.HelpInfoUtils;
|
||||
import cc.winboll.util.ServerUtils;
|
||||
import cc.winboll.util.ConsoleInputUtils;
|
||||
import cc.winboll.util.EnvInfoUtils;
|
||||
import cc.winboll.util.IniConfigUtils;
|
||||
import cc.winboll.util.ServerUtils;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
@@ -27,6 +27,8 @@ public class Main {
|
||||
* @param args 启动参数,支持-detail(详细环境)、自定义服务器地址
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
// 新增这1行:启动就加载项目根目录的config.ini,必须放在最前面
|
||||
IniConfigUtils.loadRootConfig();
|
||||
LogUtils.d(TAG, "【函数调用】main(),传入参数:" + arrayToString(args));
|
||||
|
||||
boolean showDetailEnv = parseDetailArg(args);
|
||||
|
||||
@@ -6,18 +6,19 @@ import java.util.Scanner;
|
||||
/**
|
||||
* 控制台输入工具类,封装输入监听、指令处理、资源释放逻辑
|
||||
* 适配Java7语言规范,兼容Android Termux与标准JVM环境
|
||||
* 支持help查看帮助、exit退出服务,预留指令扩展入口
|
||||
* 支持help查看帮助、exit退出服务、selftestmail邮件自测,预留指令扩展入口
|
||||
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2026/01/14 00:00:00
|
||||
* @LastEditTime 2026/01/17 10:00:00
|
||||
* @LastEditTime 2026/01/17 11:20: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"; // 新增邮件自测指令
|
||||
private static Scanner scanner;
|
||||
private static volatile boolean isExit = false; // 线程安全,防止多线程并发问题
|
||||
private static boolean isScannerInit = false; // 扫描器初始化标识,避免重复创建/关闭
|
||||
private static volatile boolean isExit = false;
|
||||
private static boolean isScannerInit = false;
|
||||
|
||||
/**
|
||||
* 初始化控制台扫描器,支持重复调用(防重复初始化)
|
||||
@@ -31,7 +32,8 @@ public class ConsoleInputUtils {
|
||||
try {
|
||||
scanner = new Scanner(System.in);
|
||||
isScannerInit = true;
|
||||
System.out.println("\n💡 输入 " + CMD_HELP + " 查看帮助,输入 " + CMD_EXIT + " 退出服务");
|
||||
// 提示新增指令
|
||||
System.out.println("\n💡 输入 " + CMD_HELP + " 查看帮助,输入 " + CMD_EXIT + " 退出服务,输入 " + CMD_SELF_TEST_MAIL + " 邮件自测");
|
||||
LogUtils.d(TAG, "【函数结束】initConsoleScanner(),控制台输入扫描器初始化完成");
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "【函数异常】initConsoleScanner(),扫描器初始化失败", e);
|
||||
@@ -44,33 +46,31 @@ public class ConsoleInputUtils {
|
||||
* @return true=需退出服务,false=继续常驻
|
||||
*/
|
||||
public static boolean checkConsoleCommand() {
|
||||
// 退出标识优先,直接返回,避免无效执行
|
||||
if (isExit) {
|
||||
return true;
|
||||
}
|
||||
// 扫描器未初始化/已关闭,直接返回,防止空指针
|
||||
if (!isScannerInit || scanner == null) {
|
||||
return isExit;
|
||||
}
|
||||
|
||||
try {
|
||||
// 非阻塞检查,无输入直接返回,降低CPU占用+避免流阻塞
|
||||
if (!scanner.hasNextLine()) {
|
||||
return isExit;
|
||||
}
|
||||
String input = scanner.nextLine().trim();
|
||||
LogUtils.d(TAG, "检测到控制台输入指令:" + input);
|
||||
|
||||
// 指令分发逻辑
|
||||
// 指令分发-新增selftestmail分支
|
||||
if (CMD_HELP.equalsIgnoreCase(input)) {
|
||||
handleHelpCmd();
|
||||
} else if (CMD_EXIT.equalsIgnoreCase(input)) {
|
||||
handleExitCmd();
|
||||
} else if (CMD_SELF_TEST_MAIL.equalsIgnoreCase(input)) {
|
||||
handleSelfTestMailCmd();
|
||||
} else if (!input.isEmpty()) {
|
||||
handleCustomCmd(input);
|
||||
}
|
||||
} catch (IllegalStateException e) {
|
||||
// 捕获扫描器已关闭异常,避免Termux环境下报错
|
||||
LogUtils.w(TAG, "【指令检查】扫描器已关闭,停止输入监听", e);
|
||||
isExit = true;
|
||||
} catch (Exception e) {
|
||||
@@ -100,19 +100,34 @@ public class ConsoleInputUtils {
|
||||
LogUtils.d(TAG, "【指令处理】执行exit指令,标记服务退出状态");
|
||||
isExit = true;
|
||||
System.out.println("\n📢 已接收退出指令,正在执行优雅停机...");
|
||||
// 核心加固:提前关闭扫描器,释放终端输入流(解决Termux阻塞关键)
|
||||
closeConsoleScanner();
|
||||
// 主动刷新输出流,确保提示语正常打印
|
||||
System.out.flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增:处理邮件自测指令,自动生成测试码,发送到发送方邮箱
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理自定义指令:预留扩展入口,便于后续新增功能
|
||||
* @param cmd 自定义输入指令
|
||||
*/
|
||||
private static void handleCustomCmd(String cmd) {
|
||||
LogUtils.w(TAG, "【指令处理】未识别指令:" + cmd + ",输入help可查看支持的指令");
|
||||
System.out.println("❌ 未识别指令[" + cmd + "],输入help查看帮助,输入exit退出服务");
|
||||
System.out.println("❌ 未识别指令[" + cmd + "],输入help查看帮助,输入exit退出服务,输入selftestmail邮件自测");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -126,23 +141,16 @@ public class ConsoleInputUtils {
|
||||
} catch (Exception e) {
|
||||
LogUtils.w(TAG, "关闭扫描器时出现异常", e);
|
||||
} finally {
|
||||
scanner = null; // 置空,杜绝重复关闭
|
||||
isScannerInit = false; // 重置初始化标识
|
||||
scanner = null;
|
||||
isScannerInit = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取退出状态标识
|
||||
* @return 退出状态
|
||||
*/
|
||||
public static boolean isExit() {
|
||||
return isExit;
|
||||
}
|
||||
|
||||
/**
|
||||
* 强制退出(兜底方法,供外部紧急调用)
|
||||
*/
|
||||
public static void forceExit() {
|
||||
LogUtils.w(TAG, "【紧急处理】执行强制退出流程");
|
||||
isExit = true;
|
||||
|
||||
@@ -15,21 +15,22 @@ 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-15 23:50:00
|
||||
* @LastEditTime 2026-01-17 11:40:00
|
||||
*/
|
||||
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 fromEmail;
|
||||
private static String fromNickname;
|
||||
|
||||
/**
|
||||
* 初始化邮件配置(需先加载INI,再调用此方法)
|
||||
* 初始化邮件配置(增强校验,返回初始化结果,便于外部判断)
|
||||
* @return 配置完整且初始化成功返回true,否则false
|
||||
*/
|
||||
public static void initEmailConfig() {
|
||||
public static boolean initEmailConfig() {
|
||||
LogUtils.d(TAG, "initEmailConfig 函数调用,开始初始化邮件配置");
|
||||
// 从INI [EmailConfig] 小节读取配置
|
||||
// 读取INI配置,增强日志提示
|
||||
String smtpHost = IniConfigUtils.getConfigValue("EmailConfig", "smtp_host");
|
||||
String smtpPort = IniConfigUtils.getConfigValue("EmailConfig", "smtp_port");
|
||||
fromEmail = IniConfigUtils.getConfigValue("EmailConfig", "from_email");
|
||||
@@ -37,9 +38,22 @@ public class EmailSendUtils {
|
||||
fromNickname = IniConfigUtils.getConfigValue("EmailConfig", "from_nickname");
|
||||
|
||||
LogUtils.d(TAG, "读取INI邮件配置:smtpHost[" + smtpHost + "] smtpPort[" + smtpPort + "] fromEmail[" + fromEmail + "]");
|
||||
if (isEmpty(smtpHost, smtpPort, fromEmail, smtpAuthCode)) {
|
||||
LogUtils.e(TAG, "邮件核心配置缺失,初始化失败");
|
||||
return;
|
||||
// 增强空值提示,明确缺失项
|
||||
if (isEmpty(smtpHost)) {
|
||||
LogUtils.e(TAG, "邮件核心配置缺失:smtp_host 未配置");
|
||||
return false;
|
||||
}
|
||||
if (isEmpty(smtpPort)) {
|
||||
LogUtils.e(TAG, "邮件核心配置缺失:smtp_port 未配置");
|
||||
return false;
|
||||
}
|
||||
if (isEmpty(fromEmail)) {
|
||||
LogUtils.e(TAG, "邮件核心配置缺失:from_email 未配置");
|
||||
return false;
|
||||
}
|
||||
if (isEmpty(smtpAuthCode)) {
|
||||
LogUtils.e(TAG, "邮件核心配置缺失:smtp_auth_code 未配置");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 组装SMTP配置(适配Java7)
|
||||
@@ -50,15 +64,21 @@ public class EmailSendUtils {
|
||||
mailProps.put("mail.smtp.socketFactory.port", smtpPort);
|
||||
mailProps.put("mail.transport.protocol", "smtp");
|
||||
|
||||
// 创建邮件会话
|
||||
mailSession = Session.getInstance(mailProps, new Authenticator() {
|
||||
@Override
|
||||
protected PasswordAuthentication getPasswordAuthentication() {
|
||||
return new PasswordAuthentication(fromEmail, smtpAuthCode);
|
||||
}
|
||||
});
|
||||
mailSession.setDebug(false); // 调试时设为true,可看SMTP交互日志
|
||||
LogUtils.i(TAG, "邮件发送配置初始化完成,发送方邮箱:" + fromEmail);
|
||||
try {
|
||||
// 会话创建加异常捕获,防止配置错误导致崩溃
|
||||
mailSession = Session.getInstance(mailProps, new Authenticator() {
|
||||
@Override
|
||||
protected PasswordAuthentication getPasswordAuthentication() {
|
||||
return new PasswordAuthentication(fromEmail, smtpAuthCode);
|
||||
}
|
||||
});
|
||||
mailSession.setDebug(false);
|
||||
LogUtils.i(TAG, "邮件发送配置初始化完成,发送方邮箱:" + fromEmail);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
LogUtils.e(TAG, "邮件会话创建失败,检查配置是否正确", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -80,16 +100,11 @@ public class EmailSendUtils {
|
||||
|
||||
try {
|
||||
MimeMessage message = new MimeMessage(mailSession);
|
||||
// 发送方(指定昵称编码)
|
||||
message.setFrom(new InternetAddress(fromEmail, fromNickname, "UTF-8"));
|
||||
// 接收方
|
||||
message.setFrom(new InternetAddress(fromEmail, fromNickname == null ? "Winboll验证" : fromNickname, "UTF-8"));
|
||||
message.addRecipient(Message.RecipientType.TO, new InternetAddress(toEmail));
|
||||
// 邮件主题
|
||||
message.setSubject("【Winboll验证】您的验证码", "UTF-8");
|
||||
// 验证码邮件内容(简洁安全)
|
||||
String content = String.format("您好!您的验证代码为:%s\n该验证码5分钟内有效,请及时使用,请勿泄露给他人。", verifyCode);
|
||||
message.setText(content, "UTF-8");
|
||||
// 发送邮件
|
||||
Transport.send(message);
|
||||
LogUtils.i(TAG, "验证码邮件发送成功,收件人:" + toEmail);
|
||||
return true;
|
||||
@@ -99,6 +114,42 @@ public class EmailSendUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 邮件发送自测函数,发送测试邮件到发送方自身邮箱,验证配置有效性
|
||||
* @param testCode 自定义测试码
|
||||
* @return 自测发送成功返回true,失败返回false
|
||||
*/
|
||||
public static boolean sendSelfTestEmail(String testCode) {
|
||||
LogUtils.d(TAG, "sendSelfTestEmail 函数调用,开始邮件自测,测试码:" + testCode);
|
||||
if (isEmpty(fromEmail, testCode)) {
|
||||
LogUtils.w(TAG, "发送方邮箱或测试码为空,自测失败");
|
||||
return false;
|
||||
}
|
||||
// 先判断会话是否就绪,不就绪直接提示
|
||||
if (mailSession == null) {
|
||||
LogUtils.e(TAG, "邮件会话未初始化,自测终止,请先完成INI邮件配置");
|
||||
System.err.println("❌ 邮件会话未初始化!请先检查INI文件的[EmailConfig]配置项是否完整");
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
MimeMessage message = new MimeMessage(mailSession);
|
||||
message.setFrom(new InternetAddress(fromEmail, fromNickname == null ? "Winboll验证" : fromNickname, "UTF-8"));
|
||||
message.addRecipient(Message.RecipientType.TO, new InternetAddress(fromEmail));
|
||||
message.setSubject("【Winboll邮件自测】配置连通性测试", "UTF-8");
|
||||
String content = String.format("邮件发送配置自测成功!\n自测校验码:%s\n当前发送方邮箱:%s\n说明:邮件服务配置正常,可正常发送验证码。", testCode, fromEmail);
|
||||
message.setText(content, "UTF-8");
|
||||
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配置、邮箱授权码或网络连接");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 多参数空值校验工具(适配Java7)
|
||||
*/
|
||||
|
||||
@@ -43,11 +43,13 @@ public class HelpInfoUtils {
|
||||
/**
|
||||
* 打印控制台指令说明
|
||||
*/
|
||||
private static void printConsoleCmdInfo() {
|
||||
System.out.println("2. 控制台指令说明");
|
||||
System.out.println(" help :调用展示本帮助信息");
|
||||
System.out.println(" exit :执行优雅停机,关闭服务并释放资源(推荐手动停止方式)");
|
||||
}
|
||||
// 找到 printConsoleCmdInfo 方法,新增selftestmail说明
|
||||
private static void printConsoleCmdInfo() {
|
||||
System.out.println("2. 控制台指令说明");
|
||||
System.out.println(" help :调用展示本帮助信息");
|
||||
System.out.println(" exit :执行优雅停机,关闭服务并释放资源(推荐手动停止方式)");
|
||||
System.out.println(" selftestmail :邮件服务自测,发送测试邮件到发送方自身邮箱");
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印核心服务配置信息
|
||||
@@ -68,5 +70,6 @@ public class HelpInfoUtils {
|
||||
System.out.println(" 方式2:执行 kill 对应Java进程号(推荐,触发优雅停机释放资源)");
|
||||
System.out.println(" 方式3:强制中断(kill -9),可能造成资源未释放,不推荐");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package cc.winboll.util;
|
||||
|
||||
import cc.winboll.LogUtils;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
@@ -12,15 +13,42 @@ import java.util.Map;
|
||||
* INI配置文件读取工具类
|
||||
* 适配Android/assets目录与标准JVM文件路径,兼容Java7语法及Android API30
|
||||
* 提供INI配置加载、配置项获取核心能力,支持注释忽略与小节分组解析
|
||||
* 新增:Termux项目根目录config.ini自动加载,适配现有标准INI格式
|
||||
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
|
||||
* @Date 2026-01-15 00:00:00
|
||||
* @LastEditTime 2026-01-15 17:27:00
|
||||
* @LastEditTime 2026-01-17 13:00:00
|
||||
*/
|
||||
public class IniConfigUtils {
|
||||
// 日志标记
|
||||
private static final String TAG = "IniConfigUtils";
|
||||
// INI配置存储容器 <小节名, <配置键, 配置值>>
|
||||
private static final Map<String,Map<String,String>> iniConfigMap = new HashMap<String,Map<String,String>>();
|
||||
// 项目根目录config.ini路径(适配Termux环境,匹配你的目录结构)
|
||||
private static final String ROOT_CONFIG_PATH = System.getProperty("user.home") + "/AuthCenterConsoleApp/config.ini";
|
||||
|
||||
/**
|
||||
* 新增:自动加载项目根目录的config.ini(Termux专用,一键读取,无需传流)
|
||||
* 启动自动调用,开箱即用,兼容现有INI格式
|
||||
*/
|
||||
public static void loadRootConfig() {
|
||||
LogUtils.d(TAG, "loadRootConfig 函数调用,开始加载项目根目录config.ini,路径:" + ROOT_CONFIG_PATH);
|
||||
FileInputStream fis = null;
|
||||
try {
|
||||
fis = new FileInputStream(ROOT_CONFIG_PATH);
|
||||
loadIniConfig(fis); // 复用原有加载逻辑,无需重复写解析代码
|
||||
} catch (IOException e) {
|
||||
LogUtils.e(TAG, "加载根目录config.ini失败!请确认文件在 " + ROOT_CONFIG_PATH, e);
|
||||
System.err.println("❌ 配置文件未找到:" + ROOT_CONFIG_PATH + ",请检查文件存放路径");
|
||||
} finally {
|
||||
if (fis != null) {
|
||||
try {
|
||||
fis.close();
|
||||
} catch (IOException e) {
|
||||
LogUtils.w(TAG, "关闭根目录配置文件流失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载INI配置文件(统一入口,兼容多环境)
|
||||
@@ -40,20 +68,20 @@ public class IniConfigUtils {
|
||||
String line;
|
||||
while ((line = br.readLine()) != null) {
|
||||
line = line.trim();
|
||||
// 跳过空行、注释行(; 或 # 开头)
|
||||
// 跳过空行、注释行(; 或 # 开头),兼容你的注释格式
|
||||
if(line.isEmpty() || line.startsWith(";") || line.startsWith("#")) {
|
||||
continue;
|
||||
}
|
||||
// 解析小节 [XXX]
|
||||
// 解析小节 [XXX],完美适配你的[EmailConfig]小节
|
||||
if(line.startsWith("[") && line.endsWith("]")) {
|
||||
currentSection = line.substring(1, line.length() - 1).trim();
|
||||
iniConfigMap.put(currentSection, new HashMap<String,String>());
|
||||
LogUtils.d(TAG, "解析到INI小节,当前小节名:" + currentSection);
|
||||
continue;
|
||||
}
|
||||
// 解析键值对(非空小节+包含=)
|
||||
// 解析键值对(非空小节+包含=),适配你的key=value格式
|
||||
if(line.contains("=") && !currentSection.isEmpty()) {
|
||||
String[] keyValue = line.split("=", 2);
|
||||
String[] keyValue = line.split("=", 2); // 按第一个=分割,兼容值里带=的场景
|
||||
if(keyValue.length == 2) {
|
||||
String key = keyValue[0].trim();
|
||||
String value = keyValue[1].trim();
|
||||
|
||||
Reference in New Issue
Block a user