邮件发送端测试完成

This commit is contained in:
2026-01-16 10:18:45 +08:00
parent 098b9887e2
commit a334bf9993
9 changed files with 166 additions and 60 deletions

2
.gitignore vendored
View File

@@ -1,3 +1,3 @@
bin/
runtime/
email_config.ini
config.ini

14
config.ini-demo.txt Normal file
View 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

Binary file not shown.

BIN
libs/javax.mail.jar Normal file

Binary file not shown.

View File

@@ -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);

View File

@@ -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;

View File

@@ -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
*/

View File

@@ -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可能造成资源未释放不推荐");
}
}

View File

@@ -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.iniTermux专用一键读取无需传流
* 启动自动调用开箱即用兼容现有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();