ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

JavaMail实战:SMTP发信与POP3收信模拟邮件系统

JavaMail实战:SMTP发信与POP3收信模拟邮件系统 简介这份资源是面向高校计算机网络课程设计场景的模拟邮件系统完整项目适合正在完成SMTP与POP3协议相关大作业的计算机专业学生参考。项目采用MVC架构基于Maven构建整合Servlet、JSP、MyBatis与JavaMail实现用户注册登录、资料编辑、发信、收件箱查看及来信回复等核心功能用户登录并与公网邮箱绑定后即可通过SMTP协议向其他邮箱发送邮件配置好数据库便能直接运行。压缩包共58个文件约119KB其中12个Java源文件承载业务逻辑6个JSP页面负责视图展示10个XML与3个properties文件用于框架和数据库配置另有class编译产物、css样式及Eclipse工程配置等辅助文件目录结构清晰便于快速导入IDE调试。目前已有3493人学习下载可作为课程设计参考方案帮助理解邮件收发流程、协议交互细节与SSM整合思路节省从零搭建的时间。1. 从一封发不出去的邮件说起这套 SMTPPOP3 模拟系统到底能跑通什么课程设计最怕的不是代码写不出来而是答辩时老师问一句「你这邮件到底走没走 SMTP 协议」你只能指着界面说「点了发送就发出去了」。这套基于 JavaMail 的模拟邮件系统核心价值就在于把「邮件发送」这个黑匣子拆开——它用真实的 SMTP 协议把信投递到公网邮箱再用 POP3 协议把回信拉回本地收件箱中间的用户注册、登录、信息编辑、回复全部走 ServletJSPMyBatis 的 MVC 链路。换句话说它不是自己给自己发着玩的假邮件而是真的和 163、QQ 邮箱这类公网服务完成了一次协议握手。适合谁用如果你正在做计算机网络课程设计需要一份能演示「应用层协议实际交互过程」的完整工程或者你在学 J2EE想找一个把 Servlet、JSP、MyBatis、Maven 串起来的真实案例这套源码的参考价值比那些纯增删改查的图书管理系统高出一截。它把「用户系统」和「邮件协议」两条线拧在一起注册登录是业务外壳SMTP/POP3 才是技术内核。下面我从工程结构、协议配置、数据库落地到踩坑排查把这份资源拆成能照着复现的步骤。2. 工程结构与技术栈拆解Maven 项目里每个目录在干什么2.1 从 pom.xml 看依赖关系与版本约束拿到一个 Java Web 项目我第一件事不是打开 IDE 点运行而是先看 pom.xml。这份工程的依赖决定了它能不能在你机器上跑起来也决定了你答辩时能不能说清楚「为什么选 JavaMail 而不是自己写 Socket」。常见做法是打开 pom.xml 确认三组依赖Servlet API、MyBatis、JavaMail。!-- pom.xml 关键依赖片段版本号以工程实际为准 -- dependencies !-- Servlet 容器接口Tomcat 运行时提供scope 必须是 provided -- dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version4.0.1/version scopeprovided/scope /dependency !-- JSP 支持同样由容器提供 -- dependency groupIdjavax.servlet.jsp/groupId artifactIdjavax.servlet.jsp-api/artifactId version2.3.3/version scopeprovided/scope /dependency !-- MyBatis 持久层负责用户表和邮件记录的读写 -- dependency groupIdorg.mybatis/groupId artifactIdmybatis/artifactId version3.5.6/version /dependency !-- JavaMailSMTP 发送与 POP3 接收的核心库 -- dependency groupIdcom.sun.mail/groupId artifactIdjavax.mail/artifactId version1.6.2/version /dependency !-- MySQL 驱动版本要和你的数据库服务匹配 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.26/version /dependency /dependencies逻辑说明Servlet 和 JSP 的 scope 写成 provided 是关键因为 Tomcat 自带这两个库如果你打成 compile 塞进 WEB-INF/lib启动时大概率报类冲突。JavaMail 的 1.6.2 是 javax.mail 命名空间下的稳定版本注意它和 jakarta.mail 不兼容别混用。MySQL 驱动 8.x 需要配合com.mysql.cj.jdbc.Driver驱动类名和带时区参数的连接串这是新手最容易翻车的地方。参数说明javax.mail的 groupId 是com.sun.mail不是javax.mail写错了 Maven 直接拉不到包。如果你的 JDK 是 17 以上Servlet 4.0 配 Tomcat 9 是稳妥组合别上 Tomcat 10因为 Tomcat 10 把命名空间换成了 jakarta这套代码会全线报错。2.2 src 目录分层与 MVC 职责边界工程正文里列出的src/main、src/test、target、classes、test-classes是标准 Maven 目录结构。真正要关注的是src/main/java下的包划分常见做法是按 controller、service、dao、entity、util 五层拆开。src/main/java/com/xxx/mail/ ├── controller/ # Servlet接收请求、调用 service、转发 JSP │ ├── LoginServlet.java │ ├── RegisterServlet.java │ └── MailServlet.java ├── service/ # 业务逻辑事务边界 │ └── MailService.java ├── dao/ # MyBatis Mapper 接口 │ ├── UserMapper.java │ └── MailMapper.java ├── entity/ # 与数据库表对应的实体类 │ ├── User.java │ └── Mail.java └── util/ # 工具类SMTP/POP3 会话封装在这里 └── MailUtil.java逻辑说明controller 层只做参数校验和页面跳转不写业务service 层负责「发信」这个动作的完整编排——先存数据库记录再调 MailUtil 走 SMTPdao 层用 MyBatis 的 Mapper 接口配合 XML 映射文件操作数据库。这样分层的好处是答辩时老师问「你的 SMTP 调用在哪」你能直接指到 util 包而不是在一堆 JSP 里翻。参数说明src/main/resources下通常放mybatis-config.xml、jdbc.properties、Mapper XML 文件。target和classes是编译输出目录不用手动改但如果你发现改了代码没生效八成是 IDE 没重新编译清一下 target 再跑。2.3 .settings 与 .classpath 里的 Eclipse 工程痕迹正文里那一串.settings、.project、.classpath、.jsdtscope说明这个工程最初是在 Eclipse 里建的还带了 m2e-wtp 插件配置。如果你用 IDEA 打开这些文件基本会被忽略但有两个地方要注意。# 如果 IDEA 导入后不识别为 Web 工程检查 facet 配置 # 常见做法是删掉 Eclipse 专属文件让 IDEA 重新识别 rm -rf .settings .project .classpath .jsdtscope # 然后在 IDEA 里 File - Project Structure - Facets 添加 Web facet # Web Resource Directory 指向 src/main/webapp逻辑说明.settings/org.eclipse.wst.common.project.facet.core.xml里定义了 jst.web 和 jst.java 的版本比如 3.1 对应 Servlet 3.1。IDEA 不读这个文件所以导入后如果发现没有 Web 支持需要手动加 facet。org.eclipse.wst.common.component定义了源码目录到部署目录的映射删掉不影响 Maven 构建。参数说明pom.xml里的packaging必须是war否则打出来的是 jarTomcat 部署不了。如果你在 IDEA 里跑配置 Tomcat 时 Deployment 选 war explodedApplication context 设成/mail之类的短路径别用默认的_war_exploded。3. SMTP 发信与 POP3 收信JavaMail 会话参数怎么配才不报 5353.1 SMTP 发送授权码、SSL 端口与 Session 属性这是整个系统最核心也最容易翻车的一环。很多同学卡在javax.mail.AuthenticationFailedException: 535 Error然后开始怀疑代码其实问题出在「用登录密码当授权码」或者「端口和加密方式不匹配」。常见做法是把 SMTP 配置抽成一个 properties 文件别硬编码在 Java 里。// MailUtil.java 中构建 SMTP Session 的核心代码 public static Session buildSmtpSession(String host, String port, String username, String authCode) { Properties props new Properties(); // 指定 SMTP 服务器地址如 smtp.163.com props.put(mail.smtp.host, host); // 端口465 走 SSL587 走 STARTTLS25 基本被公网封了 props.put(mail.smtp.port, port); // 必须开启认证否则服务器拒绝投递 props.put(mail.smtp.auth, true); // 465 端口必须开 SSL props.put(mail.smtp.ssl.enable, true); // 调试开关排错时打开能看到完整协议交互 props.put(mail.debug, true); return Session.getInstance(props, new Authenticator() { Override protected PasswordAuthentication getPasswordAuthentication() { // 注意这里的 password 是邮箱授权码不是登录密码 return new PasswordAuthentication(username, authCode); } }); }逻辑说明Session.getInstance拿到会话后用Transport.send(message)投递。mail.debugtrue会打印出EHLO、AUTH LOGIN、MAIL FROM、RCPT TO、DATA的完整交互答辩时这就是你「真的走了 SMTP 协议」的铁证。授权码需要在邮箱网页端的设置里开启 SMTP 服务后生成163 和 QQ 邮箱的入口都在「账户」或「设置」里。参数说明465 端口配mail.smtp.ssl.enabletrue587 端口则要配mail.smtp.starttls.enabletrue且不开 ssl。两者混配会报Could not connect to SMTP host。mail.smtp.timeout建议设成 10000 毫秒避免网络不通时界面卡死。发件人地址必须和认证用户名一致否则 163 会报553 Mail from must equal authorized user。3.2 POP3 收信把回信拉回本地收件箱发信只是上半场收信才是这套系统区别于普通「邮件发送 demo」的地方。POP3 的配置逻辑和 SMTP 类似但端口和属性名不同。// 构建 POP3 会话并读取收件箱 public static ListMail fetchInbox(String host, String username, String authCode) throws MessagingException, IOException { Properties props new Properties(); // POP3 服务器如 pop.163.com props.put(mail.pop3.host, host); // 995 是 POP3S 加密端口 props.put(mail.pop3.port, 995); props.put(mail.pop3.ssl.enable, true); Session session Session.getInstance(props, new Authenticator() { Override protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication(username, authCode); } }); Store store session.getStore(pop3s); // 注意是 pop3s不是 pop3 store.connect(host, username, authCode); Folder inbox store.getFolder(INBOX); inbox.open(Folder.READ_ONLY); Message[] messages inbox.getMessages(); ListMail result new ArrayList(); for (Message msg : messages) { Mail mail new Mail(); mail.setSubject(msg.getSubject()); mail.setFrom(InternetAddress.toString(msg.getFrom())); mail.setContent(msg.getContent().toString()); result.add(mail); } inbox.close(false); store.close(); return result; }逻辑说明getStore(pop3s)对应 SSL 加密的 POP3如果写成pop3再连 995 端口会握手失败。Folder.READ_ONLY表示只读避免误删服务器上的邮件。inbox.close(false)的 false 表示不 expunge即不删除已标记删除的邮件这个参数在只读模式下其实无所谓但养成习惯没坏处。参数说明POP3 默认把邮件下载到本地后服务器上的邮件是否保留取决于邮箱设置代码层面控制不了。msg.getContent()返回的类型可能是 String 也可能是 Multipart如果邮件带附件直接 toString 会得到一堆对象地址需要判断msg.isMimeType(multipart/*)后递归解析。课程设计阶段可以先只处理纯文本邮件把附件解析作为进阶点。3.3 用户注册登录与邮件记录的数据库落地邮件系统不只是收发还要有用户体系和邮件存档。MyBatis 的 Mapper XML 里通常有两张核心表user 和 mail_record。-- 建表语句字符集用 utf8mb4 避免中文乱码 CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, email VARCHAR(100) NOT NULL, auth_code VARCHAR(100) DEFAULT NULL, -- 邮箱授权码绑定公网邮箱时填写 create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE mail_record ( id INT PRIMARY KEY AUTO_INCREMENT, sender_id INT NOT NULL, receiver_email VARCHAR(100) NOT NULL, subject VARCHAR(200) DEFAULT NULL, content TEXT, send_time DATETIME DEFAULT CURRENT_TIMESTAMP, type TINYINT DEFAULT 0 -- 0 发件1 收件 ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;逻辑说明auth_code字段存的是邮箱授权码登录系统用的密码和邮箱授权码要分开存别混为一谈。mail_record表同时记录发件和收件用 type 区分这样收件箱和发件箱查询就是同一个 Mapper 加不同 where 条件。utf8mb4是为了支持 emoji 和生僻字用 utf8 在某些 MySQL 版本下会出问题。参数说明MyBatis 的jdbc.properties里连接串要带useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8否则 MySQL 8 会报时区错误或乱码。password字段建议存 MD5 或 BCrypt 哈希课程设计里至少做个 MD5答辩时能多说一句「密码没有明文存储」。4. 避坑与排查从 535 认证失败到中文乱码的五个血泪经验4.1 535 AuthenticationFailed授权码和密码搞混了现象调用Transport.send时抛AuthenticationFailedException: 535 Error: authentication failed。原因代码里PasswordAuthentication传的是邮箱登录密码而不是在邮箱设置里生成的 SMTP 授权码。163、QQ、Gmail 现在都要求用授权码登录密码在第三方客户端里一律拒绝。解决登录邮箱网页端找到「POP3/SMTP/IMAP」服务设置开启后生成授权码把授权码填到auth_code字段或配置文件里。注意授权码通常只显示一次没存下来就重新生成。4.2 Could not connect to SMTP host端口和加密方式不匹配现象连接超时或MessagingException: Could not connect to SMTP host。原因用了 465 端口但没开mail.smtp.ssl.enable或者用了 587 端口却开了 ssl 而没开 starttls。也有可能是本地网络或防火墙拦了 25 端口。解决465 配 ssl587 配 starttls二选一。如果公司或学校网络封了外发端口换 465 通常能通。排错时把mail.debug打开看卡在哪一步。4.3 中文乱码从 JSP 到数据库到邮件主题全链路现象页面显示问号数据库里存的是乱码收件人看到的主题是?UTF-8?B?...?这种编码串。原因三个环节都可能出问题——JSP 页面没设pageEncodingUTF-8数据库连接串没带characterEncodingutf8邮件主题没做 MIME 编码。解决JSP 头部加% page contentTypetext/html;charsetUTF-8 pageEncodingUTF-8 %连接串加characterEncodingutf8邮件主题用MimeUtility.encodeText(subject, UTF-8, B)编码后再 setSubject。4.4 Tomcat 启动报 ClassNotFoundException依赖没打进 WEB-INF/lib现象Tomcat 启动或访问时报java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver或 JavaMail 相关类找不到。原因Maven 依赖的 scope 写成了 provided或者 IDEA 的 Artifact 里没把依赖加进去。解决检查 pom.xml 里 MySQL 驱动和 JavaMail 的 scope 是不是默认的 compile在 IDEA 的 Project Structure - Artifacts 里确认 WEB-INF/lib 下有没有这些 jar。如果用的是 Tomcat 9 以下Servlet API 的 scope 才写 provided。4.5 POP3 收信重复或丢失服务器保留策略没搞清现象每次点「收件箱」都拉到重复邮件或者换台机器登录后邮件不见了。原因POP3 协议默认行为是「下载并删除」但很多邮箱服务端有「保留副本」选项代码层面控制不了。如果本地数据库没做去重就会重复插入。解决在mail_record表里对sender_id subject send_time建唯一索引或者插入前先查重。更稳妥的做法是记录每封邮件的 Message-ID用msg.getHeader(Message-ID)做幂等判断。5. 进阶技巧把协议交互日志变成答辩材料课程设计答辩最怕老师追问「你怎么证明邮件真的走了 SMTP」。我的习惯是在MailUtil里加一个日志拦截器把 JavaMail 的 debug 输出重定向到文件同时把关键协议命令截取出来存到数据库。这样你不仅能演示界面还能翻出EHLO、AUTH LOGIN、MAIL FROM、RCPT TO、DATA的完整记录。// 把 mail.debug 输出重定向到文件答辩时直接展示 public static Session buildDebugSession(Properties props, Authenticator auth) { Session session Session.getInstance(props, auth); // 默认 debug 输出到 System.out这里改成自定义 PrintStream session.setDebug(true); session.setDebugOut(new PrintStream(new FileOutputStream(smtp_debug.log, true), true)); return session; }逻辑说明setDebugOut接收一个 PrintStream你可以指向文件也可以包装一个 ByteArrayOutputStream 再定期落库。smtp_debug.log里会记录每次会话的完整命令和服务器响应码比如250 OK、354 Start mail input、221 Bye。答辩时打开这个文件比任何 PPT 都有说服力。参数说明FileOutputStream的第二个参数 true 表示追加模式避免每次发信覆盖历史日志。如果并发发信多个线程写同一个文件会交错课程设计阶段单用户场景够用真要严谨就按 session id 分文件。另一个进阶点是「回复」功能的实现。回复不是简单地把原邮件内容拼进去而是要正确设置In-Reply-To和References头这样在公网邮箱里才会显示成会话线程。// 回复邮件时设置会话头让公网邮箱识别为同一线程 MimeMessage reply new MimeMessage(session); reply.setFrom(new InternetAddress(fromAddr)); reply.setRecipient(Message.RecipientType.TO, new InternetAddress(originalFrom)); reply.setSubject(Re: originalSubject); // 关键引用原邮件的 Message-ID reply.setHeader(In-Reply-To, originalMessageId); reply.setHeader(References, originalMessageId); reply.setText(replyContent); Transport.send(reply);逻辑说明originalMessageId从原邮件的Message-ID头取格式类似xxx163.com。设置这两个头之后收件人在 163 或 QQ 邮箱里看到的就是一个折叠的会话而不是两封孤立邮件。这个细节在课程设计里很少有人做答辩时是加分项。参数说明setSubject加Re:前缀是惯例但别重复加判断一下原主题是不是已经以 Re 开头。setText默认是text/plain如果要发 HTML 邮件用setContent(content, text/html;charsetUTF-8)。从那以后我每次做邮件相关的东西都会先把mail.debug打开跑一遍确认协议交互没问题再关掉。这套源码的价值不在于界面多漂亮而在于它把 SMTP 和 POP3 的调用链路完整暴露出来了你顺着MailUtil一路跟下去应用层协议就不再是试卷上的名词。希望帮到你。本文还有配套的精品资源点击获取
返回列表