
简介MeyboMail Web(Java)开源简化项目是一份面向初级和中级Java开发者的Web邮件系统源码包旨在解决邮件收发、邮箱管理以及前端交互一体化实现的难题适合希望从实际工程中学习Java Web技术栈的程序员。压缩包共有237个文件大小仅2.4MB内部以126个GIF演示图片、23个Java源文件、23个class字节码、19个HTML页面、7个CSS样式和4个JavaScript脚本为主体另有少量JAR依赖库、XML配置与properties属性文件便于对照源码、页面和运行效果进行学习。项目不仅覆盖了JavaMail API对SMTP发送协议与IMAP接收协议的处理还包含了关系型数据库的数据持久化、安全认证机制、地址簿与用户管理等典型模块能够帮助阅读者梳理从邮件协议封装到Web界面呈现的完整流程。通过分析这些代码可以理解Mime消息解析、邮件地址分组、用户会话控制等关键功能的实现思路对日后自行开发邮件客户端或类似管理系统有直接的参考价值。目前已有108人学习适合需要深入掌握Java Web后端与前端协作开发的开发者借鉴。1. MeyboMail Web(Java)开源简化一个把邮件系统“拆开”给你看的 Java Web 项目MeyboMail Web(Java)开源简化项目是我最近拆过的一个很值得新手和中级开发者动手跑一遍的 Java Web 邮件客户端源码包。邮件系统在很多开发者眼里是个“黑匣子”——SMTP、IMAP、MIME 解析这些词听着就头大而市面上能下载到的完整邮件系统往往又太重框架套框架根本看不进去。这个简化版把 MeyboMail 的核心类抽取了出来保留了登录、收信、发信、通讯录、配置管理这几条主干流程去掉了一堆外围依赖。对想搞懂 Java Web 邮件系统原理的人、正在准备 Java 课程设计的人、或者面试前想补一补“邮件协议在 Java 里到底怎么写”的人它都是一个非常合适的阅读样本和改造底子。先看清它里面到底有什么再决定怎么跑起来。2. 先读包结构十个类把邮件系统拆成了三块2.1 从文件名反推职责哪些类管邮件哪些类管地址哪些类管配置拿到压缩包解压之后别急着往 Tomcat 里丢。先把 classes 目录下的 .class 文件按名字过一遍这个动作花不了两分钟但能让你对整个系统的边界有数。我拆这个包的时候第一件事就是把十个类按照“管什么”分了组。下表是当时的整理结果你应该也会得出差不多的结论类名所属模块职责判断EmailManage邮件核心邮件收发逻辑的封装处理 SMTP 发送与 IMAP 收件EmailActionWeb 控制层接收前端请求调用 EmailManage返回结果给页面ParseMimeMessage邮件核心MIME 消息解析处理邮件正文、附件、编码AddressAction通讯录控制层通讯录相关请求的入口增删改查联系人UserManage用户与安全用户登录校验、会话管理、用户数据维护XMLTool基础工具XML 配置和数据的读写封装整个系统的“文件数据库”EmailAddress实体类单个联系人实体的字段定义EmailAddressGroup实体类联系人分组实体的字段定义AddressGroupAction通讯录控制层联系人分组的管理逻辑Config基础工具系统全局配置读取如邮箱服务器地址、端口这个分组不是拍脑袋分的。任何一个邮件系统本质都绕不开三件事邮件协议的处理收发与解析、用户与地址本的管理业务数据、系统配置的读写运行参数。这十个类正好一一落在三块里——邮件协议由 EmailManage、EmailAction、ParseMimeMessage 负责业务数据由 UserManage、AddressAction、AddressGroupAction、EmailAddress、EmailAddressGroup 负责基础配置由 Config 和 XMLTool 负责。你把这个映射关系记在脑子里后面读代码就有索引了。2.2 简化版做了什么取舍对比完整版 MeyboMail 看“简化”二字MeyboMail 完整版是个典型的 SSH 组合项目Struts 做控制层Hibernate 做持久层MySQL 存数据前端还带着 JSP 标签库。如果你直接去读完整版源码光 Spring 的 applicationContext.xml 就能绕晕你。而拿到的这个简化版从类名就能看出它砍掉了哪些东西没有 Struts 的 ActionForm没有 Hibernate 的映射文件只剩 Action 结尾的控制类、Manage 结尾的业务类、实体类和工具类。这意味着简化版回到了 Java Web 最初的模样Servlet 接收请求JavaBean 处理业务XML 文件当数据库用。我自己在跑类似项目时见过不少这么干的——小项目用 XML 存数据其实很常见Apache Commons Config、DOM4J 都是干这个的。这个简化版里XMLTool 承担的正是这个角色。它把 config.xml、users.xml、address.xml 这样的文件读进内存或者把对象写回文件系统里所有“数据持久化”都走这条路径。实体类的写法也很有学习价值。比如 EmailAddress 这个类正常写法会是public class EmailAddress { private String displayName; private String address; private String groupId; public EmailAddress() {} public EmailAddress(String displayName, String address, String groupId) { this.displayName displayName; this.address address; this.groupId groupId; } public String getDisplayName() { return displayName; } public void setDisplayName(String displayName) { this.displayName displayName; } public String getAddress() { return address; } public void setAddress(String address) { this.address address; } public String getGroupId() { return groupId; } public void setGroupId(String groupId) { this.groupId groupId; } }逻辑说明这是一个标准的 POJOPlain Old Java Object四个字段分别对应联系人列表里要展示的名字、真正参与邮件发送的邮箱地址、以及所属分组的外部键。空构造方法和带参构造方法同时保留是为了兼容两种创建场景——XMLTool 反序列化时需要调用无参构造创建实例再逐个 set 字段而业务代码里可以直接用带参构造快速建对象。 参数说明displayName 用于页面显示address 用于 Transport 发送时填收件人groupId 用于按组查询时做过滤条件。如果你要扩展联系人字段比如加一个“备注”列就在这个类里加字段、加 getter/setter再去 XMLTool 对应的读写逻辑里补映射千万不能只改实体类不改存储层否则数据写出去就丢字段了。这就是简化版的底气所在类少但职责清晰它们的协作方式恰好构成了一条完整的邮件业务链。读懂了这十个类你就读懂了这个小系统的全部骨架。3. 部署与运行把项目从压缩包跑到浏览器里3.1 环境准备JDK、Tomcat、项目目录结构这个项目是标准的 Java Web 应用打包方式运行环境只需要 JDK 8 和 Tomcat 8.5 或更高版本。不建议用 JDK 11 以上去跑老项目有些 javax 包和 JDK 模块化之后会有兼容性问题用 JDK 8 最省心。Tomcat 版本同理8.5 系列兼容性最好。我一般会在 Linux 服务器上这样准备环境# 检查 JDK 版本必须是 1.8 系列 java -version # 如果没有安装用 yum 安装 OpenJDK 8 yum install -y java-1.8.0-openjdk-devel # 下载并解压 Tomcat 8.5.x wget https://archive.apache.org/dist/tomcat/tomcat-8/v8.5.100/bin/apache-tomcat-8.5.100.tar.gz tar -zxvf apache-tomcat-8.5.100.tar.gz -C /usr/local/ mv /usr/local/apache-tomcat-8.5.100 /usr/local/tomcat参数说明java -version 输出里如果显示 openjdk version 1.8.0_xxx说明环境没问题。Tomcat 解压目录后面用 CATALINA_HOME 这个环境变量统一指向后续部署 WAR 包或者拷贝 class 文件都要依赖这个位置建议把它写进 /etc/profile。环境准备好之后看一下解压出来的项目目录。正常的 Java Web 项目压缩包应该包含 src 源码目录、WebContent 或 web 目录以及部署描述文件 web.xml。如果你拿到的压缩包只有 classes 和 lib那就需要自己手动组装标准的 webapp 目录结构meybomailweb/ ├── WEB-INF/ │ ├── web.xml │ ├── classes/ │ │ └── com/....class 文件按包路径放 │ └── lib/ │ ├── javax.mail.jar │ ├── activation.jar │ └── ... ├── index.jsp ├── login.jsp ├── main.jsp └── ...提示拿到压缩包先看根目录有没有完整 webapp 结构。如果只有散落的 .class 文件就要自己按上面这个目录结构重建否则 Tomcat 无法识别这个应用。3.2 配置 Config 类邮件服务器参数决定你能不能收到信邮件系统最核心的配置都集中在 Config 类里。它通常对应项目根目录下的 config.xml 文件。在本地跑通之前你需要先有一个能用的邮箱账号作为收发信服务器常见做法是用 QQ 邮箱或网易邮箱的 IMAP/SMTP 服务来做测试。config.xml 的典型内容大致如下config smtp hostsmtp.qq.com/host port465/port authtrue/auth ssltrue/ssl usernameyour_accountqq.com/username passwordyour_authorization_code/password /smtp imap hostimap.qq.com/host port993/port ssltrue/ssl usernameyour_accountqq.com/username passwordyour_authorization_code/password /imap /config参数说明smtp.host 和 smtp.port 是发信服务器的地址和端口QQ 邮箱的 SMTP 是 smtp.qq.com:465网易是 smtp.163.com:465默认 25 端口通常被云厂商封禁建议一律走 SSL 加密端口。auth 表示是否需要登录认证几乎所有公共邮箱都要 true。ssl 字段控制是否启用 SSL 加密465 和 993 端口必须为 true。username 和 password 里的 password 不是你的邮箱登录密码而是邮箱服务商提供的“授权码”这是国内邮箱通用的安全机制。3.3 编译与部署三步走配置改完之后就可以编译部署了。如果压缩包里有完整源码编译过程如下# 设置编译输出目录 mkdir -p /opt/meybomail/WEB-INF/classes # 编译源码class 文件输出到 WEB-INF/classes javac -encoding UTF-8 -classpath /usr/local/tomcat/lib/servlet-api.jar:lib/javax.mail.jar:lib/activation.jar \ -d /opt/meybomail/WEB-INF/classes \ src/com/meybomail/web/*.java src/com/meybomail/util/*.java # 把 webapp 目录复制到 Tomcat 的 webapps 下 cp -r /opt/meybomail /usr/local/tomcat/webapps/meybomail # 启动 Tomcat /usr/local/tomcat/bin/startup.sh逻辑说明javac 的 -classpath 参数必须把 servlet-api.jar 加进去否则 HttpServlet 相关的类编译不通过-d 指定输出目录-encoding UTF-8 是为了避免中文注释和字符串在 Windows 环境下编译乱码。复制到 webapps 后Tomcat 启动时会自动扫描 meybomail 目录并部署这个应用。 参数说明如果你的源码里包路径不是 com.meybomail.web而是别的包名需要相应调整 src 下的路径。lib 目录里的 javax.mail.jar 是必须的activation.jar 在 JDK8 里可以直接从 JRE 拿到JDK9 以上需要额外引入。启动后访问http://localhost:8080/meybomail如果能看到登录页恭喜第一步通了。如果页面报 404 或者 500多半是 class 没编译进 WEB-INF/classes或者 lib 缺失去第五章看具体排查方法。4. 三个核心流程登录、收信、发信是怎么串起来的4.1 登录与 SessionUserManage 和 Config 背后的处理逻辑任何一个 Web 系统登录都是入口。MeyboMail Web 的登录逻辑不在配置文件里而在 UserManage 类中。它的工作方式很直接读取 XML 文件中的用户数据与会话状态绑定。常见的源码存储结构大致如下public class UserManage { private static final String USER_FILE WEB-INF/classes/users.xml; public boolean validateLogin(String username, String password) { ListUser users XMLTool.parseUsers(USER_FILE); for (User user : users) { if (user.getUsername().equals(username) user.getPassword().equals(password)) { return true; } } return false; } public void createSession(HttpServletRequest request, User user) { HttpSession session request.getSession(); session.setAttribute(currentUser, user); session.setMaxInactiveInterval(30 * 60); } }逻辑说明validateLogin 方法先从 XMLTool 读取用户列表遍历比对用户名和密码。这里有个重要边界——用户的邮箱服务器账号config.xml 里的和 Web 登录账号users.xml 里的是两套体系。也就是说你先得在 users.xml 里注册系统用户然后这个用户绑定的邮箱收发配置走的是 config.xml 里的全局账号。这对学习阶段来说够用但如果你想做多用户系统就得把邮箱账号和用户绑定存储而不是全局共用一个。 参数说明setMaxInactiveInterval(30 * 60) 设置会话有效期为 30 分钟单位是秒。超过这个时间用户需要重新登录。如果你在实际使用中发现“老掉线”就调大这个值如果担心安全问题就调小。4.2 收信与 MIME 解析ParseMimeMessage 的核心边界收信是整个项目里最有含金量的部分。它走的是 IMAP 协议通过 JavaMail 的 Store 和 Folder 类连接邮箱服务器把邮件拉下来之后交给 ParseMimeMessage 逐封解析。public class ParseMimeMessage { private String host imap.qq.com; private String username your_accountqq.com; private String password your_authorization_code; public ListSimpleEmail receive() throws Exception { Properties props new Properties(); props.setProperty(mail.store.protocol, imap); props.setProperty(mail.imap.host, host); props.setProperty(mail.imap.port, 993); props.setProperty(mail.imap.ssl.enable, true); Session session Session.getDefaultInstance(props); Store store session.getStore(imap); store.connect(username, password); Folder inbox store.getFolder(INBOX); inbox.open(Folder.READ_ONLY); Message[] messages inbox.getMessages(); ListSimpleEmail result new ArrayList(); for (Message msg : messages) { SimpleEmail email new SimpleEmail(); email.setSubject(msg.getSubject()); email.setFrom(((InternetAddress) msg.getFrom()[0]).getAddress()); email.setSentDate(msg.getSentDate()); email.setContent(parseContent(msg)); result.add(email); } inbox.close(false); store.close(); return result; } }逻辑说明这段代码连上 IMAP 服务器打开收件箱拉取全部邮件逐封提取主题、发件人、日期和正文。parseContent 方法里通常会判断 msg.isMimeType(text/plain) 还是 multipart/*——如果是 multipart就要用 Multipart 对象循环遍历 bodyPart分别处理纯文本、HTML 和附件。这是 ParseMimeMessage 与普通 JavaMail 代码最大的区别它不只是读邮件还要处理 MIME 的嵌套结构。 参数说明mail.imap.ssl.enable 必须为 true与 993 端口配套使用。inbox.open(Folder.READ_ONLY) 意味着只读收件箱不会把邮件标记为已读或删除。如果你希望拉取后标记已读改成 Folder.READ_WRITE 即可。inbox.close(false) 的 false 表示不执行 expunge 操作如果你调用 deleteMessages 后 close(true)才会真正删除服务器上的邮件这个参数极其容易踩坑建议新手一律写 false。4.3 发信与通讯录EmailAction、AddressAction 的协作发信相对收信简单走 SMTP 协议核心是 Transport.send。EmailAction 里会有类似下面的代码public class EmailAction { public void sendEmail(HttpServletRequest request, HttpServletResponse response) { String to request.getParameter(to); String subject request.getParameter(subject); String content request.getParameter(content); Properties props new Properties(); props.setProperty(mail.smtp.host, smtp.qq.com); props.setProperty(mail.smtp.port, 465); props.setProperty(mail.smtp.auth, true); props.setProperty(mail.smtp.socketFactory.class, javax.net.ssl.SSLSocketFactory); Session session Session.getInstance(props, new Authenticator() { Override protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication(your_accountqq.com, your_authorization_code); } }); MimeMessage message new MimeMessage(session); try { message.setFrom(new InternetAddress(your_accountqq.com)); message.setRecipient(Message.RecipientType.TO, new InternetAddress(to)); message.setSubject(subject); message.setText(content); Transport.send(message); } catch (MessagingException e) { // 这里要记录日志并返回错误给前端 } } }逻辑说明SMTP 发信的关键点有两个——mail.smtp.auth 开启认证以及 socketFactory.class 指定为 SSLSocketFactory。前者对应服务端要求登录后才能发信后者对应 QQ 邮箱这类“必须 SSL 加密”的服务器。Transport.send 是 JavaMail 的同步发送方法邮件发出后才会返回。如果你要支持附件需要在 message.setContent 时传入 Multipart 对象。 参数说明subject 和 content 都来自前端表单。当你把 AddressAction 接进来的时候原本需要手动输入的 to 地址可以由通讯录自动填充——AddressAction 从 XML 或数据库里读取 EmailAddress 列表前端页面在“收件人”输入框旁列出联系人供选择选中后把 EmailAddress.address 填入表单的 to 字段。这就是它与邮件发送模块的协作方式。5. 常见问题排查这五个坑我拆这个包时踩过5.1 现象收件箱列表为空但不报错我改完 config.xml 启动项目能正常登录但点“收件箱”就是没有邮件后台没有异常页面也没报错。原因JavaMail 的 getMessages() 返回的是 Message[] 数组但数组里的元素只有在访问时才真正从服务器拉取数据。邮件列表页面通常只取了前 N 封邮件而你的测试邮箱在 IMAP 服务器上默认是按照日期倒序返回新邮件在数组前面。但更常见的是——配置里的邮箱账号根本没收到任何邮件你用自己的主邮箱账号去连服务器把邮件返回了但你解析的字段和实际内容不匹配比如主题为空或发件人为空前端就过滤掉了。解决先用任意邮箱往测试账号发两三封带中文主题的邮件再去跑项目。如果列表仍然为空就把 ParseMimeMessage 里解析出的每一封邮件的 subject、from、date 打印到控制台逐项验证。我当时的排查结论是密码配错了——QQ 邮箱的授权码在配置时带了一个看不见的空格导致 IMAP 认证一直匿名失败但 JavaMail 对认证失败并不总是抛异常而是返回了一个空的 folder。5.2 现象邮件中文主题显示乱码发信功能通了但收到的邮件在 Web 页面里显示“?UTF-8?B?...?”一串乱码或者干脆是问号。原因JavaMail 在构造 MimeMessage 时如果 subject 没有显式指定编码会使用平台默认编码。Windows 下默认 GBKLinux 下默认 UTF-8这就导致同一封邮件的主题编码在不同环境下不一致。此外收到的邮件主题如果本身经过 MIME 编码UTF-8 的 Base64 形式就需要解码。ParseMimeMessage 如果没有调用 MimeUtility.decodeText 处理就会把这串编码原样输出。解决发信时显式指定编码收信时解码。两处都要改// 发信时指定主题编码 message.setSubject(subject, UTF-8); // 收信时解码主题 String decodedSubject MimeUtility.decodeText(msg.getSubject());参数说明setSubject 的第二个参数是字符集UTF-8 是标准选择。MimeUtility.decodeText 会把“?UTF-8?B?...?”格式自动解码回可读文本。正文的乱码则不同——需要看邮件内容的 Content-Type 里的 charset 字段用对应的字符集去 new String(content.getBytes(), charset)这个在 ParseMimeMessage 的 parseContent 里要单独处理。5.3 现象Tomcat 部署后一直 404页面访问 http://localhost:8080/meybomail 返回 404Tomcat 日志里没有应用加载记录。原因压缩包拿到的可能不是完整 webapp 目录。MeyboMail Web 的源码包和部署包经常分开打包你下载的可能只有源码和 class 文件缺了 WEB-INF/web.xml 或者 JSP 页面。Tomcat 找不到 web.xml 就不会把它当作 Web 应用部署自然 404。解决手动创建 WEB-INF 目录写一个最简单的 web.xml 把目录结构补完整再编译 class 进去web-app xmlnshttp://xmlns.jcp.org/xml/ns/javaee version3.1 display-nameMeyboMail Web/display-name welcome-file-list welcome-filelogin.jsp/welcome-file /welcome-file-list /web-app参数说明version3.1 对应 Servlet 3.1 规范Tomcat 8.5 完全支持。welcome-file-list 定义访问根路径时默认打开的页面这里配置成 login.jsp访问 /meybomail 就会自动跳转到登录页。如果你连 login.jsp 都没有那这个包就不是可直接运行的项目需要从 MeyboMail 完整版里把 JSP 页面抠出来对照着补。5.4 现象发信一直报 Connection refused 或 timeoutSMTP 发信超时后台报 Could not connect to SMTP host 或者 Connection refused。原因绝大部分是端口问题。云服务器默认封禁 25 端口如果你配置的是 smtp.qq.com:25必然连不上。另外QQ 邮箱和网易邮箱的 SMTP 服务器同时支持 SSL 和非 SSL 两种连接方式但端口不同SSL 用 465非 SSL 用 25 或 587。JavaMail 里如果只改了 port 没改 socketFactory 配置也会连接失败。解决统一改用 465 端口加 SSL并把 socketFactory 配置补全。注意把 mail.smtp.socketFactory.fallback 设为 false避免 JavaMail 在 SSL 连接失败后自动回退到明文端口props.setProperty(mail.smtp.socketFactory.class, javax.net.ssl.SSLSocketFactory); props.setProperty(mail.smtp.socketFactory.fallback, false); props.setProperty(mail.smtp.port, 465);参数说明fallback 设为 false 的目的是确保连接始终走 SSL不会因为 465 连接失败就静默降级到 25 端口这样报错信息会直接指向 SSL 问题而不是变成 timeout。如果你改用 587 端口则去掉 socketFactory.class改用 STARTTLS 的方式。5.5 现象修改 config.xml 后重启配置没生效改了 config.xml 里的邮箱账号重启 Tomcat还是用的旧配置。原因Config 类很可能用了静态代码块初始化而且把 XML 解析结果放在了 static 变量里。Tomcat 重启会重新加载类但如果 Config 类在启动时把它读进内存而你又没清空 Tomcat 的 work 目录旧 class 的静态变量可能被持久化到会话中。更常见的是——你改的文件路径和实际读取的路径不一致项目部署后在 webapps 下配置文件在 classes 目录里而你改的是 src 下的那一份。解决改配置前先确认文件路径。部署后的配置一定在 /usr/local/tomcat/webapps/meybomail/WEB-INF/classes/config.xml不是源码目录里的那份。改完之后不仅要重启 Tomcat还建议清掉 work 目录里的缓存rm -rf /usr/local/tomcat/work/Catalina/localhost/meybomail /usr/local/tomcat/bin/shutdown.sh /usr/local/tomcat/bin/startup.sh参数说明work 目录存的是 JSP 编译后的临时 class 文件如果你改动过 JSP不清这个目录可能导致页面还是旧的渲染逻辑。config.xml 的读取逻辑如果写在 static 块里类加载后就无法热更新必须整个 Tomcat 重启。6. 进阶把简化版改造成可用的轻量邮件客户端6.1 给通讯录加搜索功能现在通讯录是能用的但 AddressAction 只提供了按分组查询和全部列表两种查询方式。日常用起来联系人一多找一个人得翻半天。我改这个项目的时候第一件事就是给 AddressAction 加了个关键词搜索方法。核心就是在 XMLTool 读取联系人列表之后加一层过滤public ListEmailAddress search(String keyword) { ListEmailAddress all XMLTool.parseAddresses(); ListEmailAddress result new ArrayList(); for (EmailAddress addr : all) { if (addr.getDisplayName().contains(keyword) || addr.getAddress().contains(keyword)) { result.add(addr); } } return result; }逻辑说明contains 方法做的是子串匹配支持模糊搜索用户输入“张”就能把“张三”“张伟”都筛出来。这样 AddressAction 里多接收一个 keyword 参数转发到这个方法就行。如果你有几千个联系人这个线性遍历的性能也够用不必为这个规模上索引。6.2 把 XML 存储迁移到 MySQL如果这个项目要真正给一个团队用XML 存储肯定是瓶颈。我的建议是保留 XMLTool 的接口不变新增一个 JDBC 实现类把读写路径整体切到 MySQL。核心思路是把 UserManage 里的 XMLTool.parseUsers 换成从数据库读public ListUser getUsersFromDb() { ListUser users new ArrayList(); String sql SELECT username, password, email FROM sys_user WHERE status 1; try (Connection conn DriverManager.getConnection(DB_URL, DB_USER, DB_PASS); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { while (rs.next()) { User user new User(); user.setUsername(rs.getString(username)); user.setPassword(rs.getString(password)); user.setEmail(rs.getString(email)); users.add(user); } } catch (SQLException e) { e.printStackTrace(); } return users; }逻辑说明这段重点不在 SQL而在迁移策略——面向接口编程。UserManage 里提供 getUsers() 的公共接口内部现在走 XMLTool改造后走 getUsersFromDb()调用方完全无感。这样你可以分模块迁移先迁用户数据再迁通讯录最后迁邮件缓存每一步都能独立验证。 参数说明DB_URL 形如 jdbc:mysql://localhost:3306/meybomail?useSSLfalsecharacterEncodingutf8必须显式指定 characterEncoding否则中文用户名会乱码。useSSLfalse 是本地测试时的必须参数否则 MySQL 8 默认开启 SSL 握手你还要额外配证书。6.3 用脚本验证改造效果最后分享一个我常用的验证方式。改造完一个邮件系统最怕的就是“代码看起来对但邮件发不出去”。我用 telnet 直接连 SMTP 服务器验证网络层和认证层是否通# 连接 SMTP 服务器 SSL 端口需要先安装 openssl openssl s_client -connect smtp.qq.com:465 -crlf -quiet # 连接成功后手动输入 SMTP 命令 EHLO test AUTH LOGIN # 输入 Base64 编码后的用户名 # 输入 Base64 编码后的授权码 MAIL FROM: your_accountqq.com RCPT TO: targetqq.com DATA Subject: test From: your_accountqq.com To: targetqq.com This is a test mail. QUIT逻辑说明这一步能帮你区分“网络不通”和“代码问题”。如果 telnet 能完成 AUTH 和 DATA 并收到 250 响应说明服务器、端口、账号、授权码全部正常问题在 Java 代码里。如果连 EHLO 都失败那就别调代码了先去查防火墙和端口。 参数说明AUTH LOGIN 之后输入的是 Base64 编码不是明文。在 Linux 上用 echo -n your_accountqq.com | base64 可以生成编码串。整个会话中每个命令都要等服务器返回 250 或 235 再发下一条不要一次性粘贴所有命令否则服务器会因乱序而拒绝执行。从那以后我每次拿到一个陌生的开源项目都强制自己先跑通“看类名→分组→补配置→启动→抓异常→验证协议”这套流程。这套流程帮我避开了绝大多数“项目跑不起来”的尴尬时刻。MeyboMail Web 这个简化版虽然规模不大但它恰好覆盖了 Java Web 的核心链路配置管理、XML 持久化、邮件协议处理、会话控制。你可以拿它做课程设计也可以把它当作理解 JavaMail API 的活教材。希望帮到你。本文还有配套的精品资源点击获取