拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

原生Servlet+Vue+MySQL留言板:课程设计完整实战解析

原生Servlet+Vue+MySQL留言板:课程设计完整实战解析 简介这套基于原生Servlet、Vue与MySQL实现的留言板系统面向Java初学者及正在进行课程设计或毕业设计的同学可帮助理解经典前后端交互流程与Web项目分层思路。压缩包共122个文件大小约13.49MB包含13个Java源码、28个class编译产物、43个Jar依赖库以及9个Vue前端组件、8个XML配置文件和数据库相关脚本源码与编译结果并存便于对照学习运行机制。项目已经过本地编译验证按随包文档配置JDK、Tomcat与MySQL环境即可直接运行难度适中适合作为Java Web入门练习或课设参照。现有95人下载学习内容覆盖留言发布、列表查询、回复处理等典型模块读者可借此掌握Servlet请求响应处理、Vue组件数据绑定以及MyBatis或JDBC的读写要点。若在运行中遇到问题可通过作者页面留言咨询。1. 原生ServletVueMySQL留言板课程设计里最经得起追问的一套组合刚拆完这套“原生ServletVueMySQL留言板”的时候我第一反应是这题目太经典了。经典在它不依赖Spring Boot那套自动装配登录、列留言、回留言全靠原生Servlet一个个写好映射你反而能看清一次HTTP请求从浏览器到Tomcat再到JDBC的全过程。压缩包里是编译过的class文件和对应源码DB、LoginServlet、MessageListServlet、ReplyServlet、Utils五个类把后端拆得明明白白前端用Vue接接口数据落在MySQL。要交毕业设计、课程设计或者想补Servlet基本功的人这套都适合。2. 源码包结构拆解五个核心类与一次请求的完整路径2.1 压缩包里的编译产物class文件为什么成对出现解压之后你会看到 DB.class、LoginServlet.class 这类字节码文件和对应的 .java 源文件同时存在。class 成对出现不是资源包做的冗余而是IDEEclipse或IDEA在编译输出目录和源码目录各保留了一份Tomcat 实际运行加载的是 WEB-INF/classes 下的 class源码是给你改的。这个细节别忽略。你改了 .java 不重新编译Tomcat 跑的还是旧 class后面所有“我改了但没生效”的诡异问题八成都是从这儿来的。拿到资源包的第一件事应该是打开源码目录确认五个类的代码是不是和 class 文件同版本然后再决定是直接部署 class 还是从源码重新编译。提示我一般会把 WEB-INF/classes 下的 class 先删掉用源码重新编译一遍再放回去确保运行的是自己看得懂的代码。2.2 五个核心类的职责边界谁调谁、谁依赖谁拆完源码后端逻辑其实就五条线DB.class提供 getConnection() 静态方法所有 Servlet 拿数据库连接都找它内部封装了驱动加载、URL、账号密码Utils.classJSON 字符串拼接、空值判断、日期格式化这类公共方法避免每个 Servlet 里重复写LoginServlet.class处理登录请求从 request 拿用户名密码查 user 表做比对MessageListServlet.class查留言列表联表带出用户名和回复内容输出 JSON 数组ReplyServlet.class处理回复请求向 message 表插入回复文本更新回复时间。调用方向是 Servlet → Utils / DB → MySQL。Utils 不依赖 ServletDB 也不依赖业务类所以五个类彼此边界清楚。这里有个答辩时老师爱问的点为什么 LoginServlet 和 ReplyServlet 用 doPost而 MessageListServlet 用 doGet因为登录、回复是写操作参数放在请求体里更安全查列表是读操作用 doGet 配合 query string 传参就够了。HTTP 方法语义区分开起码说明作者不是把所有逻辑都塞进 service() 的初学者。2.3 用 javap 验证 class 文件内容不反编译也能看清结构如果你不想装反编译工具JDK 自带的 javap 就能看 class 的结构。在 WEB-INF/classes 对应的目录下执行javap -p -c com.example.servlet.LoginServlet-p参数显示私有成员-c参数输出字节码指令。输出里能看到 doPost 方法里调用了哪些类比如invokestatic DB.getConnection就说明登录 Servlet 确实走的是 DB 类拿连接。这个命令的价值在于当你怀疑 class 和源码对不上时不用启动 Tomcat 就能快速确认类结构是否一致。2.4 一个请求的完整路径从前端点击到数据落库拿“提交一条留言”举例整条链路是Vue 组件里 axios.post 到/messageListServlet→ Tomcat 根据注解或 web.xml 找到 MessageListServlet → doPost 里 request.getParameter(“content”) 拿到留言内容 → 调 DB.getConnection() 获取连接 → PreparedStatement 执行 INSERT → 查询最新列表拼成 JSON → response 返回给前端 → VUE 重新渲染列表。这条链路里最容易断的一环是中间那段“查最新列表”。常见做法是插入之后立刻再查一次全表简单粗暴但数据量大了会变慢也可以插入后直接返回成功状态前端收到成功再主动刷新列表。课设场景用前者足够代码逻辑也更直观。这套项目用的就是前者留言提交后列表自动带上新数据从用户视角看是“即发即显”。3. 数据库设计与后端实现从建库 SQL 到 Servlet 路由3.1 建库建表user 表和 message 表的最小设计数据库层不需要花哨两张表就够用户表和留言表。下面是按这套资源的设计还原的建表语句CREATE DATABASE IF NOT EXISTS message_board DEFAULT CHARACTER SET utf8mb4; USE message_board; CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE message ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, content VARCHAR(500) NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, reply_content VARCHAR(500) DEFAULT NULL, reply_time DATETIME DEFAULT NULL, CONSTRAINT fk_user_id FOREIGN KEY (user_id) REFERENCES user(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;字段设计上有几个细节值得说。user 表的 password 字段用了 VARCHAR(100)按 MD5 或 SHA-256 加密后的长度来预留的message 表的 create_time 用 DEFAULT CURRENT_TIMESTAMP插入时不需要手动传时间MySQL 自动补reply_content 和 reply_time 允许 NULL表示“还没回复”这样不需要额外加状态字段。外键约束在课设里最好保留老师问“两张表怎么关联”的时候直接指着外键说就行。3.2 DB 类的标准写法JDBC 连接 MySQL 的四个参数DB 类是这个项目的地基写法很典型package com.example.util; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DB { private static final String DRIVER com.mysql.cj.jdbc.Driver; private static final String URL jdbc:mysql://localhost:3306/message_board ?useUnicodetruecharacterEncodingutf8 serverTimezoneAsia/ShanghaiuseSSLfalse; private static final String USER root; private static final String PASSWORD 123456; public static Connection getConnection() { Connection conn null; try { Class.forName(DRIVER); conn DriverManager.getConnection(URL, USER, PASSWORD); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } return conn; } }逻辑说明Class.forName 加载 MySQL 驱动类MySQL 8 必须用 com.mysql.cj.jdbc.Driver用旧的 com.mysql.jdbc.Driver 会直接报 ClassNotFoundExceptionDriverManager.getConnection 负责建立连接URL 里的三个参数是关键。参数说明useUnicodetrue 和 characterEncodingutf8 保证乱码问题不会出现在 JDBC 层serverTimezoneAsia/Shanghai 是 MySQL 8 的硬性要求不写会报“The server time zone value”的异常useSSLfalse 是本地开发关掉 SSL 握手否则控制台会刷一片警告。我一般还会把 USER 和 PASSWORD 也抽成常量换环境只需要改一个位置不用在代码里满世界搜“root”。3.3 LoginServlet 的 doPost 处理链参数、比对、响应登录接口是理解这套代码最好的入口完整逻辑如下package com.example.servlet; import java.io.IOException; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.example.util.DB; WebServlet(/loginServlet) public class LoginServlet extends HttpServlet { Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); response.setContentType(application/json;charsetUTF-8); String username request.getParameter(username); String password request.getParameter(password); String result {\code\:500,\msg\:\用户名或密码为空\}; if (username ! null password ! null) { Connection conn null; PreparedStatement ps null; ResultSet rs null; try { conn DB.getConnection(); String sql SELECT id FROM user WHERE username? AND password?; ps conn.prepareStatement(sql); ps.setString(1, username); ps.setString(2, password); rs ps.executeQuery(); if (rs.next()) { int userId rs.getInt(id); request.getSession().setAttribute(userId, userId); result {\code\:200,\msg\:\登录成功\}; } else { result {\code\:500,\msg\:\用户名或密码错误\}; } } catch (SQLException e) { e.printStackTrace(); result {\code\:500,\msg\:\系统异常\}; } finally { try { if (rs ! null) rs.close(); } catch (SQLException e) { } try { if (ps ! null) ps.close(); } catch (SQLException e) { } try { if (conn ! null) conn.close(); } catch (SQLException e) { } } } response.getWriter().write(result); } }逻辑说明doPost 里先设置请求和响应的编码然后从参数里拿 username 和 password。查询用的是 PreparedStatement 而不是 Statement好处是参数通过 setString 传入SQL 注入的坑自动避开这也是课设答辩老师一定会问“为什么不用字符串拼接 SQL”的原因。登录成功后把 userId 放进 session后续留言接口取登录人就用 session 里的值。参数说明WebServlet(“/loginServlet”) 是 Servlet 3.0 的注解式映射如果运行环境是 Tomcat 7 以下需要在 web.xml 里配servlet和servlet-mapping。注意代码里的 JSON 是手动拼的字符串字段值写死为 200/500前端就是按这两个值判断成功失败的改的时候要前后端一起改。3.4 MessageListServlet联表查询与手动拼 JSON留言列表要比登录稍微复杂一点因为它要联表查出每个留言对应的用户名还要带上回复内容。核心 SQL 是SELECT m.id, m.content, m.create_time, u.username, m.reply_content, m.reply_time FROM message m JOIN user u ON m.user_id u.id ORDER BY m.create_time DESC排序用 DESC 让最新留言排在最上面这是留言板的默认预期。Servlet 里拿到 ResultSet 之后循环遍历拼成一个 JSON 数组字符串。这里有个课设里常见的偷懒写法用 StringBuilder 手动拼[{},{}]字段值要做转义处理否则内容里出现双引号会破坏 JSON 结构。严谨一点的做法是引入 fastjson 或 Jackson但这套资源用原生方式是为了不额外引依赖理解它就好。connections 和 statement 的关闭顺序是先 rs、再 ps、最后 conn。顺序反了会报“Connection is already closed”之类的错这是 JDBC 最基础也最容易翻车的细节之一。4. Vue 前端接入开发代理、axios 封装与列表刷新4.1 Vue 项目的两种跑法devServer 代理与打包放 Tomcat前端 Vue 代码要接上后端的 Servlet有两条路。开发阶段我用 devServer 代理生产阶段我打把包丢进 Tomcat 的 webapps。两者的配置方式完全不同。开发模式在 vue.config.js 里配代理// vue.config.js module.exports { devServer: { port: 8080, proxy: { /: { target: http://localhost:8081, changeOrigin: true } } } };关键点在于这里不加pathRewrite因为 Servlet 的映射路径就是/loginServlet这种根路径不需要重写前缀。前端页面跑在 8080代理把/开头的请求转发到 8081 的 Tomcat解决跨域问题的同时前端请求地址和后端映射保持一致。生产模式的做法是npm run build copy dist\* D:\apache-tomcat-9.0.80\webapps\ROOT\把构建产物直接覆盖到 Tomcat 的 ROOT 目录这样访问http://localhost:8081/就是前端页面http://localhost:8081/loginServlet就是后端接口同源不存在跨域。注意如果 Tomcat 里已经有其他项目ROOT 目录里的 index.html 会被覆盖先把原目录备份再 copy。4.2 axios 请求封装把 Servlet 的 JSON 响应接进组件前端和后端约定的是 JSON 格式所以 axios 可以集中封装。我习惯建一个 src/api 目录// src/api/message.js import axios from axios; const http axios.create({ baseURL: /, timeout: 10000 }); export function fetchMessages() { return http.get(messageListServlet); } export function submitMessage(content) { return http.post(messageListServlet, content encodeURIComponent(content), { headers: { Content-Type: application/x-www-form-urlencoded } }); } export function login(username, password) { const params new URLSearchParams(); params.append(username, username); params.append(password, password); return http.post(loginServlet, params); }逻辑说明baseURL 配成/开发环境走代理生产环境直接同源请求切换部署方式时前端代码不用动。submitMessage 里用 encodeURIComponent 对 content 编码防止留言内容里的特殊字符破坏表单格式login 用了 URLSearchParams它能把对象转成表单编码比手动拼字符串更不容易出错。参数说明timeout: 10000 是 10 秒超时如果后端 Servlet 因为 SQL 语句问题一直没响应前端不会一直白屏转圈而是走 catch 分支弹错误提示。headers 里指定的 Content-Type 是 application/x-www-form-urlencoded和后端request.getParameter()是对应的如果你改成 application/json后端就必须改用request.getReader()读取这一对关系要记住。4.3 页面间跳转与数据刷新登录后如何进入列表页登录页拿到成功响应之后跳转到留言列表页是一个典型的前端路由场景。以 Vue Router 的 hash 模式为例this.$router.push({ path: /messageList });跳转本身不复杂真正的坑在于列表页首次加载时的数据拉取。常见做法是把 fetchMessages 放在 created 钩子而不是 mounted 里created 时组件的数据对象还没渲染到 DOM但已经可以发起请求响应回来后数据绑定到列表上页面渲染时直接就有内容视觉上少一次闪烁。留言提交之后的刷新我建议这样写async function handleSubmit() { await submitMessage(this.newContent); this.newContent ; const res await fetchMessages(); this.messages res.data; }提交成功后重新请求一次列表而不是用this.messages.push()手动追加。原因很简单后端在插入后有默认排序手动 push 需要自己维护排序逻辑还拿不到后端自动生成的时间字段。重新拉一次全表最省事数据量小的时候性能没有影响。4.4 Vue 环境搭不起的常见卡点npm install 缓慢与版本差异下载资源后要跑前端第一步是npm install。如果安装慢或者报错先确认 Node 版本。Vue 2 项目要求 Node 10 以上Vue 3 项目更挑剔一些node-sass 这个包是经典的重灾区如果你的项目里有用到Node 16 以上直接换成sass包并改掉引用方式。Windows 上如果 npm install 一直卡在 node-sass 的安装上试试在环境变量里加SASS_BINARY_SITEhttps://npmmirror.com/mirrors/node-sass安装速度会快很多。5. 部署避坑指南Tomcat 版本、MySQL 时区与中文乱码5.1 现象Tomcat 10 下启动就报 ClassNotFoundException: javax.servlet原因Tomcat 10 起把 Java EE 的包名从 javax 迁到了 jakarta这套资源里的 Servlet 代码 import 的是 javax.servlet.*在 Tomcat 10 底下加载不到类。解决换成 Tomcat 9 或更早的 8.5 版本两个版本在 Windows 上都是解压即用。如果是毕业设计我建议直接用 Tomcat 9兼容性最稳。需要确认当前 Tomcat 版本时启动控制台的第一行就会打印版本号或者看安装目录下 RELEASE-NOTES 文件。5.2 现象数据库连接失败报 Access denied for user rootlocalhost原因MySQL 8 默认的认证插件是 caching_sha2_password老版本驱动或者连接配置不匹配时会出现拒绝访问也有可能是 DB 类里的密码和本机 MySQL 密码不一致。解决优先检查 DB.java 里的 USER 和 PASSWORD 常量是否对得上。确认密码没错仍报错的话在 MySQL 命令行执行ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 你的密码; FLUSH PRIVILEGES;这条命令把认证插件切回 mysql_native_password兼容性最好。MySQL 8 装好后如果不记得 root 密码用mysqld --initialize-insecure初始化可以拿到空密码登录登录后再改。5.3 现象留言内容中文变问号数据库里也是乱码原因乱码是三处叠加的后果。数据库表不是 utf8mb4 编码、JDBC URL 没带 characterEncodingutf8、请求进来时没执行 setCharacterEncoding。三处只要有一处漏了中文就会在某一环变成问号。解决建库用前面给的 utf8mb4 语句JDBC URL 确保带了characterEncodingutf8Servlet 的 doPost 开头必须加request.setCharacterEncoding(UTF-8);如果你用 GET 请求传中文参数光设 request 编码还不够Tomcat 8 以上默认用 UTF-8 解码 GET要是 Tomcat 7 需要在 server.xml 的 Connector 上加URIEncodingUTF-8。排查的时候按数据库、JDBC、Servlet、Tomcat 四层逐个确认。5.4 现象Vue 页面能打开但接口 404原因最常见的不是后端没启动而是前端页面和 Servlet 不在同一个 context path 下。比如前端 devServer 跑 8080后端 Tomcat 跑 8081代理没配或者 target 端口写错请求根本没到 Tomcat。解决开发模式下先直接浏览器访问http://localhost:8081/loginServlet用 POST 工具发一个请求能通说明后端没问题问题在代理配置。把 vue.config.js 里 target 改成实际 Tomcat 端口改完重启 devServer。生产模式下确认前端静态文件是不是放到了 Tomcat webapps 的 ROOT 目录访问http://localhost:8081/能出页面再测接口。5.5 现象Class.forName 报驱动类找不到原因lib 目录下没有 MySQL 驱动 jar 包或者 jar 包版本太老。mysql-connector-java 8.x 对应新驱动名5.1.x 对应旧驱动名代码里写哪个就要配哪个。解决检查 WEB-INF/lib 下是否有 mysql-connector-java 的 jar 包。没有的话去 Maven 中央仓库下载 8.0.33 版本文件名类似 mysql-connector-j-8.0.33.jar复制到 lib 目录后重启 Tomcat。确认 jar 是否被加载可以看 Tomcat 启动日志里有没有“Deployment of web application directory”相关的信息类路径问题多半是 jar 没放对位置。6. 进阶改造加一个登录拦截 Filter比在 Servlet 里反复判断 session 省事留言板能跑通只是第一步课设答辩时老师大概率会追问一个问题“你怎么保证没登录的人不能访问留言列表”原资源里如果只在登录时才写 session而 MessageListServlet 没有做校验那这就是个明显的安全漏洞。补这个漏洞有两种做法在每个 Servlet 里判断 session或者写一个 Filter 统一拦截。我建议用后者代码只写一遍改起来也好维护。package com.example.filter; import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; WebFilter(/*) public class LoginFilter implements Filter { Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req (HttpServletRequest) request; HttpServletResponse res (HttpServletResponse) response; HttpSession session req.getSession(false); String uri req.getRequestURI(); String contextPath req.getContextPath(); String path uri.substring(contextPath.length()); // 放行登录接口和静态资源 if (/loginServlet.equals(path) || path.startsWith(/static/) || path.equals(/) || path.endsWith(.js) || path.endsWith(.css)) { chain.doFilter(request, response); return; } // 会话为空则跳回登录页 if (session null || session.getAttribute(userId) null) { res.sendRedirect(contextPath /); return; } chain.doFilter(request, response); } Override public void init(FilterConfig filterConfig) throws ServletException { } Override public void destroy() { } }逻辑说明WebFilter(“/*”) 表示拦截所有请求。getSession(false) 的关键点是参数改成了 false意思是“没有 session 就返回 null而不是重新创建一个”。这样未登录用户访问列表页时session 为 null直接重定向回首页。代码里先放行了登录接口、静态资源等路径再把真正需要保护的 Servlet 拦下来。参数说明path 的计算方式是去掉 contextPath 前缀这样项目部署在 ROOT 还是其他目录下都不会出错。放行规则里我保留了 .js 和 .css 后缀的放行因为生产模式下 Vue 打包后的静态资源是靠这些后缀访问的如果你用 devServer 代理这行也不会误伤接口请求。放行的路径列表要根据实际项目调整留言、回复这两个接口绝对不能在放行名单里。改完 Filter 之后验证一下完整流程未登录时直接访问http://localhost:8081/messageListServlet应该被重定向到首页登录成功后访问列表接口能拿到数据。这套资源验证时我通常还会顺手检查登录接口返回的 JSON 格式里有没有把用户信息返回给前端——凡是能从前端代码里看到明文密码的写法都建议改掉 password 字段的返回。从那以后我每次拿到课设资源包第一件事不再是急着启动 Tomcat而是先看 lib 目录和 class 文件对应关系再确认 Servlet 的路由和数据库表设计最后才谈部署。这套流程帮我过滤掉了至少一半的“运行不起来”问题希望你拿到这套资源时能少走我走过的那些弯路。希望帮到你。本文还有配套的精品资源点击获取
返回列表