简介:这是一份面向高校计算机类专业学生(如计科、人工智能、通信工程等)的JavaWeb课程设计实战资源,聚焦留言板系统开发,适用于期末大作业、课程设计、毕设初期演示或小白进阶学习。项目基于JavaEE技术栈,使用Eclipse IDE开发,完整实现用户注册登录、留言发布与管理、评论增删、后台管理等核心功能,代码经实机测试全部运行成功,答辩平均分达96分,具备良好可读性与可扩展性。压缩包共236个文件,含35个Java源码、19个JSP页面、26个JS脚本、21个CSS样式及40张PNG界面截图等,涵盖前后端逻辑、UI展示与部署说明,整体大小15.73MB。已有495人下载学习,配套提供详细文档说明与README指引,支持在理解基础上二次开发,适合作为教学参考、作业提交范本或自主学习的结构化入门项目。
1. 这不是“写个JSP页面交差”的作业:一个能跑通、能调试、能讲清HTTP生命周期的JavaWeb留言板,才是期末大作业的及格线
你手里的Eclipse刚装好,Tomcat配到一半弹出“找不到或无法加载主类 org.apache.catalina.startup.Bootstrap”,JSP里写了个<%=request.getParameter("msg")%>却总显示null,数据库连上了但插入数据后查不到——这不是玄学,是JavaWeb初学者在真实开发链路上必然撞上的第一堵墙。这个标题里的“留言板”,本质是一个最小可行的Servlet+JSP+MySQL+Tomcat四层联动闭环系统:它不追求炫酷UI,但必须清晰暴露HTTP请求如何从浏览器发起、经Tomcat解析、由Servlet处理、用JDBC存取、再通过JSP渲染返回;它不堆砌SpringBoot自动装配,但要求你能手动配置web.xml、理解<url-pattern>匹配逻辑、亲手写doPost()里request.setCharacterEncoding("UTF-8")这行救命代码。适合计算机专业大二大三学生——你不需要会写框架,但必须亲手把HttpServletRequest对象从头到尾拆开看过;文档说明不是应付老师,而是你未来排查ClassNotFoundException时唯一能翻的救命纸。别被“源代码+文档”误导,真正值钱的是每行代码背后对应的容器行为、编码陷阱和调试路径。
2. 从零搭起可运行环境:Eclipse + Tomcat + MySQL 的三件套校准
2.1 Eclipse不是IDEA:必须用“Eclipse IDE for Enterprise Java and Web Developers”
很多同学用错版本——下载了“Eclipse IDE for Java Developers”(纯Java版),结果新建Dynamic Web Project时根本找不到Servlet模板。正确做法是:
- 访问 https://www.eclipse.org/downloads/
- 找到Eclipse IDE for Enterprise Java and Web Developers(注意后缀不是“Java Developers”)
- 下载最新稳定版(如2023-09),解压即用,无需安装
提示:不要用Eclipse Marketplace里搜“Tomcat”插件来配服务器——那是给老版本留的兼容方案。新版本直接用内置Server视图管理更稳。
2.2 Tomcat 9.x 是当前JavaWeb教学的黄金搭档
为什么不用Tomcat 10+?因为Servlet规范从4.0升到5.0,javax.servlet.*包名全改成jakarta.servlet.*,而绝大多数教材、实验手册、甚至老师给的参考代码仍基于javax。强行用Tomcat 10会导致编译报错The import javax.servlet cannot be resolved。
- 下载地址:https://tomcat.apache.org/download.cgi → Binary Distributions → Core →
apache-tomcat-9.0.xx.zip - 解压后,在Eclipse中:Window → Preferences → Server → Runtime Environments → Add → Apache → Tomcat v9.0 → Next → Browse选解压目录 → Finish
2.3 MySQL 5.7/8.0 驱动必须手动丢进WEB-INF/lib
别信Eclipse自动生成的“Add Library”——它只会把驱动加到Build Path,但Tomcat运行时根本看不到。真实路径是:
YourProject/WebContent/WEB-INF/lib/mysql-connector-java-5.1.47.jar # 或 MySQL 8.0 对应的 mysql-connector-java-8.0.28.jar- 下载jar包:https://dev.mysql.com/downloads/connector/j/ → 选择Platform Independent → ZIP Archive
- 解压后找到
.jar文件,直接复制粘贴进项目目录,不是通过Eclipse界面导入
注意:MySQL 8.0默认开启caching_sha2_password认证,而老驱动不支持。若连接失败,进MySQL命令行执行:
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'your_password';
再刷新权限:FLUSH PRIVILEGES;
3. 留言板核心逻辑落地:Servlet处理流程与JSP渲染边界
3.1 三层结构必须物理隔离:src放Java,WebContent放JSP,WEB-INF放配置
这是新手最容易糊弄的地方——把所有.java文件拖进WebContent/WEB-INF/classes,结果Tomcat启动时报Class not found。正确结构如下:
MyMessageBoard/ ├── src/ # Java源码(Servlet、DAO、Bean) │ ├── servlet/MessageServlet.java │ ├── dao/MessageDAO.java │ └── bean/Message.java ├── WebContent/ # 静态资源和JSP │ ├── index.jsp # 入口页(表单提交到MessageServlet) │ ├── list.jsp # 显示留言列表 │ └── WEB-INF/ │ ├── web.xml # 核心配置(Servlet映射、编码过滤器) │ └── lib/ # mysql-connector-java.jar在此 └── build/ # Eclipse自动生成,勿手动改3.2 web.xml里这三件事缺一不可:Servlet声明、URL映射、字符编码过滤器
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <!-- 1. 声明Servlet --> <servlet> <servlet-name>MessageServlet</servlet-name> <servlet-class>servlet.MessageServlet</servlet-class> </servlet> <!-- 2. URL映射(关键!/msg对应index.jsp里的action="/msg")--> <servlet-mapping> <servlet-name>MessageServlet</servlet-name> <url-pattern>/msg</url-pattern> </servlet-mapping> <!-- 3. 全局编码过滤器(解决中文乱码的后悔药) --> <filter> <filter-name>CharacterEncodingFilter</filter-name> <filter-class>filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>CharacterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>逻辑说明:
<url-pattern>/msg</url-pattern>必须和JSP中<form action="/msg">完全一致,少个斜杠就404;- 过滤器
<url-pattern>/*</url-pattern>表示拦截所有请求,确保request.getParameter()拿到的是UTF-8解码后的中文;version="4.0"对应Servlet 4.0规范,与Tomcat 9.x严格匹配,写成3.1会丢失部分特性。
3.3 MessageServlet.java:HTTP方法分发与业务逻辑分离
package servlet; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import bean.Message; import dao.MessageDAO; public class MessageServlet extends HttpServlet { private static final long serialVersionUID = 1L; protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // GET请求:跳转到list.jsp显示所有留言 MessageDAO dao = new MessageDAO(); request.setAttribute("messageList", dao.getAllMessages()); request.getRequestDispatcher("/list.jsp").forward(request, response); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // POST请求:处理表单提交 request.setCharacterEncoding("UTF-8"); // 双保险:过滤器已设,此处再确认 String content = request.getParameter("content"); String author = request.getParameter("author"); if (content == null || content.trim().isEmpty()) { request.setAttribute("error", "留言内容不能为空!"); request.getRequestDispatcher("/index.jsp").forward(request, response); return; } Message msg = new Message(); msg.setContent(content); msg.setAuthor(author != null ? author.trim() : "匿名用户"); msg.setCreateTime(new java.util.Date()); MessageDAO dao = new MessageDAO(); boolean success = dao.saveMessage(msg); if (success) { response.sendRedirect(request.getContextPath() + "/msg"); // 重定向避免重复提交 } else { request.setAttribute("error", "保存失败,请重试"); request.getRequestDispatcher("/index.jsp").forward(request, response); } } }参数说明:
request.setCharacterEncoding("UTF-8"):虽有过滤器,但此处显式设置是血泪经验——某些Tomcat版本对POST参数编码不敏感;response.sendRedirect(...):用重定向而非forward,防止用户刷新页面时重复提交表单;request.getContextPath():动态获取项目上下文路径(如/MyMessageBoard),避免硬编码导致部署失败。
4. 数据库操作与JSP渲染:DAO模式与EL表达式避坑指南
4.1 MessageDAO.java:JDBC连接池不是必须,但Connection必须手动关闭
package dao; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.text.SimpleDateFormat; import bean.Message; public class MessageDAO { private static final String URL = "jdbc:mysql://localhost:3306/message_db?useSSL=false&serverTimezone=GMT%2B8"; private static final String USER = "root"; private static final String PASSWORD = "123456"; public boolean saveMessage(Message msg) { String sql = "INSERT INTO messages(content, author, create_time) VALUES(?, ?, ?)"; try (Connection conn = DriverManager.getConnection(URL, USER, PASSWORD); PreparedStatement pstmt = conn.prepareStatement(sql)) { pstmt.setString(1, msg.getContent()); pstmt.setString(2, msg.getAuthor()); pstmt.setTimestamp(3, new java.sql.Timestamp(msg.getCreateTime().getTime())); return pstmt.executeUpdate() > 0; } catch (SQLException e) { e.printStackTrace(); // 开发期必须打印,上线要改日志框架 return false; } } public java.util.List<Message> getAllMessages() { String sql = "SELECT id, content, author, create_time FROM messages ORDER BY create_time DESC"; java.util.List<Message> list = new java.util.ArrayList<>(); try (Connection conn = DriverManager.getConnection(URL, USER, PASSWORD); PreparedStatement pstmt = conn.prepareStatement(sql); ResultSet rs = pstmt.executeQuery()) { while (rs.next()) { Message msg = new Message(); msg.setId(rs.getInt("id")); msg.setContent(rs.getString("content")); msg.setAuthor(rs.getString("author")); msg.setCreateTime(rs.getTimestamp("create_time")); list.add(msg); } } catch (SQLException e) { e.printStackTrace(); } return list; } }关键点:
try-with-resources自动关闭Connection/PreparedStatement/ResultSet,避免连接泄漏;serverTimezone=GMT%2B8:MySQL 5.7+必须显式指定时区,否则create_time存入为0000-00-00;java.sql.Timestamp转换:java.util.Date不能直接set,必须转成Timestamp。
4.2 list.jsp:用JSTL替代脚本片段,EL表达式必须配合<c:forEach>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>留言列表</title> <style>body{font-family:Arial,sans-serif;margin:40px}</style> </head> <body> <h2>所有留言</h2> <a href="${pageContext.request.contextPath}/index.jsp">我要留言</a> <br><br> <c:if test="${empty messageList}"> <p>暂无留言</p> </c:if> <c:forEach items="${messageList}" var="msg" varStatus="status"> <div style="border:1px solid #ccc;padding:10px;margin:10px 0;"> <strong>${msg.author}</strong> <span style="color:#999;font-size:0.9em;"> ${fn:substring(msg.createTime, 0, 19)} <!-- JSTL fn函数截取时间 --> </span> <p>${msg.content}</p> </div> </c:forEach> </body> </html>注意事项:
<%@ taglib ... %>必须存在,否则<c:forEach>会报错;${pageContext.request.contextPath}获取项目路径,比硬写/MyMessageBoard安全;fn:substring需要引入JSTL函数库:在WEB-INF/lib下放jstl-1.2.jar(Tomcat 9自带,但需确认);- EL表达式
${msg.content}自动调用getMessage(),无需写getMsg().getContent()。
5. 避坑:Eclipse+Tomcat环境下5个高频翻车现场与根治方案
5.1 现象:启动Tomcat后访问http://localhost:8080/MyMessageBoard显示404
原因:项目未发布到Tomcat,或Context Path配置错误。
解决:右键项目 → Properties → Targeted Runtimes → 勾选已配置的Tomcat v9.0 → Apply;再右键Tomcat Server → Add and Remove → 把项目加进去;最后检查Servers视图里项目状态是否为“Started”。
5.2 现象:JSP中<%=request.getParameter("content")%>输出乱码(如“????”)
原因:GET请求参数编码未设,request.setCharacterEncoding()对GET无效。
解决:在web.xml中添加<connector port="8080" protocol="HTTP/1.1" URIEncoding="UTF-8" />(需修改Tomcat/conf/server.xml),或统一用POST提交表单。
5.3 现象:ClassNotFoundException: com.mysql.jdbc.Driver
原因:MySQL驱动jar包没放对位置,或驱动类名过时(MySQL 8.0用com.mysql.cj.jdbc.Driver)。
解决:确认jar包在WEB-INF/lib下;MySQL 5.7用com.mysql.jdbc.Driver,MySQL 8.0用com.mysql.cj.jdbc.Driver,并在MessageDAO.java中Class.forName()里写对。
5.4 现象:java.lang.NullPointerException在request.getAttribute("messageList")处
原因:Servlet中request.setAttribute()后没forward(),或JSP里${messageList}写错变量名。
解决:检查Servlet中是否执行了forward();JSP中EL表达式变量名必须与setAttribute()第一个参数完全一致(区分大小写)。
5.5 现象:Eclipse控制台报SEVERE: Error listenerStart,项目启动失败
原因:web.xml语法错误(如标签未闭合)、WEB-INF/lib下有冲突jar包(如两个不同版本log4j)。
解决:打开Servers视图 → 双击Tomcat → 打开Overview→ 点击Open launch configuration→ 在Arguments页签末尾加-Dorg.apache.catalina.STRICT_SERVLET_COMPLIANCE=true,重启后错误详情会打印在Console。
6. 文档说明怎么写才不是摆设:从“老师看了点头”到“自己半年后还能复现”
6.1 README.md必须包含这四个区块:环境清单、一键启动步骤、数据库初始化SQL、常见问题速查表
我一般会把文档写成可执行的“运行说明书”,而不是作文。比如:
# JavaWeb留言板课程设计文档 ## ✅ 环境要求(实测通过) - Eclipse IDE for Enterprise Java and Web Developers 2023-09 - Apache Tomcat 9.0.83 - MySQL 5.7.32(或8.0.28,需调整驱动类名) - JDK 1.8.0_361 ## ▶️ 三步启动(复制粘贴即可) 1. 启动MySQL服务:`sudo service mysql start`(Linux)或双击`mysqld.exe`(Windows) 2. 创建数据库:执行`sql/init.sql`中的建表语句 3. 在Eclipse中右键项目 → Run As → Run on Server → 选择Tomcat v9 ## 💾 数据库初始化(sql/init.sql) ```sql CREATE DATABASE IF NOT EXISTS message_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE message_db; CREATE TABLE messages ( id INT PRIMARY KEY AUTO_INCREMENT, content TEXT NOT NULL, author VARCHAR(50) DEFAULT '匿名用户', create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP );❓ 常见问题速查
| 现象 | 定位命令 | 解决方案 |
|---|---|---|
访问/msg报404 | tail -f logs/catalina.out | 检查web.xml中<servlet-mapping>的<url-pattern>是否匹配表单action |
| 中文存入MySQL变问号 | SELECT @@character_set_database; | 执行ALTER DATABASE message_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; |
| Eclipse提示“Dynamic Web Module 4.0 requires Java 1.8 or newer” | Project Properties → Project Facets → Java → 1.8 | 右键项目 → Properties → Project Facets → Java → 选1.8,Apply |
### 6.2 源代码注释不是写“这里创建连接”,而是写“为什么用try-with-resources” 我在每个关键方法开头加一行`// [决策依据]`注释,比如: ```java // [决策依据] 不用Connection Pool:课程设计要求展示JDBC原生操作,且并发量为0,连接池反而增加复杂度 public boolean saveMessage(Message msg) { ... } // [决策依据] 用PreparedStatement而非Statement:防止SQL注入,且课程设计明确要求体现安全编码意识 String sql = "INSERT INTO messages(content, author, create_time) VALUES(?, ?, ?)";这种注释让老师一眼看到你的设计思考,也让你半年后重看代码时,不用翻教材就能懂当初为什么这么写。
6.3 最后交付前必做的三件事:删掉调试日志、验证URL路径、导出WAR包
- 删日志:把所有
e.printStackTrace()换成System.err.println("DEBUG: " + e.getMessage()),上线环境严禁打印堆栈; - 验路径:在
index.jsp里写<%=request.getContextPath()%>,访问页面确认输出是/MyMessageBoard而非/; - 导WAR:右键项目 → Export → WAR file → 选
WebContent为Web root → 生成MyMessageBoard.war,扔到Tomcat/webapps下能独立运行,证明项目自包含。
我带过三届学生做这个作业,最常被退回的不是功能缺陷,而是文档里写着“数据库密码:123456”,结果老师用这个密码真连上了——安全意识比代码能力更重要。现在我自己的项目,哪怕只是本地测试,PASSWORD也一定从config.properties读,绝不硬编码。希望帮到你。
本文还有配套的精品资源,点击获取