ARTICLE DETAIL

资讯详情

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

原生Servlet+Vue+MySQL留言板:课程设计实战与避坑指南

原生Servlet+Vue+MySQL留言板:课程设计实战与避坑指南 简介这是一套面向Java Web初学者与课程设计、毕业设计需求者的留言板系统完整源码采用原生Servlet搭配Vue与MySQL实现适合用来练习前后端分离思路、理解请求处理流程与数据库交互。压缩包共122个文件约13.49MB其中jar包与class文件构成运行依赖与编译产物java源码与jsp页面体现后端逻辑vue组件、js脚本与json配置负责前端交互另有xml、md等配置与说明文档整体结构清晰、便于按模块阅读。目前已有96人学习下载。源码均经本地编译验证按文档配置环境即可运行难度适中内容经助教老师审定可满足学习与使用需求。读者可借此掌握Servlet请求分发、登录与留言列表、回复等核心功能的实现方式并对照前端组件理解数据渲染与接口调用遇到问题也可私信作者获取解答。1. 原生 Servlet Vue MySQL 留言板一个能跑通的课程设计骨架如果你正在找一份不依赖 Spring 全家桶、能看清请求从浏览器到数据库完整链路的 Java Web 课程设计这个留言板系统值得拆开看。它用原生 Servlet 做后端控制器Vue 做前端交互MySQL 存留言数据整个项目编译产物里能看到LoginServlet、MessageListServlet、ReplyServlet以及DB、Utils这些类文件说明登录、留言列表、回复三条主线是分开落地的不是把逻辑全塞进一个类里。对刚学完 JSP 和 Servlet、想找一个结构清晰的项目练手的人来说这种分层方式比直接上 Spring Boot 更容易理解「一个请求到底经过了什么」。它适合课程设计、毕业设计前期打底也适合想补一补原生 Servlet 请求转发和 JDBC 连接池基础的人。下面按环境搭建、后端实现、前端对接、避坑排查、进阶验证的顺序把这份资源拆成能照着复现的步骤。2. 环境搭建JDK、Tomcat、MySQL 与 Vue 的版本咬合2.1 为什么这套组合对版本敏感原生 Servlet 项目不像 Spring Boot 那样把 Tomcat 内嵌进来它依赖外部 Servlet 容器所以 JDK 版本、Tomcat 版本、Servlet API 版本三者必须对齐。常见做法是 JDK 8 配 Tomcat 8.5 或 9对应 Servlet 3.1/4.0如果你用 JDK 17 配 Tomcat 10包名会从javax.servlet变成jakarta.servlet项目里原有的import javax.servlet.*会全部报错。这不是代码写错了是规范换了命名空间。Vue 这边课程设计里多数用的是 Vue 2 axios因为 Vue 3 的createApp和选项式 API 写法差异会让新手在对照源码时对不上号。MySQL 建议 5.7 或 8.0两者在 JDBC 驱动类名和连接 URL 参数上有区别后面会具体说。先确认本机环境把版本记下来再动手java -version # 期望输出 java version 1.8.0_xxx 或 11/17记下主版本 mysql --version # 期望 MySQL 5.7.x 或 8.0.x node -v npm -v # Vue 2 项目 node 建议 14/16Vue 3 可用 16这几条命令的作用是先把「地基」摸清楚。java -version决定你后面 Tomcat 选哪个大版本mysql --version决定 JDBC URL 里要不要加serverTimezonenode -v决定 Vue 脚手架能不能正常装依赖。很多人一上来就导入项目结果编译报错回头才发现是 JDK 17 配了 Tomcat 9白折腾半天。2.2 数据库建库建表与连接配置留言板的核心表通常就三张用户表、留言表、回复表。字段不用多但要保证外键关系清楚。下面是一份可以直接执行的建表脚本字段名按常见命名习惯来你对照源码里的实体类改一下即可CREATE DATABASE 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 ); 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, FOREIGN KEY (user_id) REFERENCES user(id) ); CREATE TABLE reply ( id INT PRIMARY KEY AUTO_INCREMENT, message_id INT NOT NULL, user_id INT NOT NULL, content VARCHAR(500) NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (message_id) REFERENCES message(id), FOREIGN KEY (user_id) REFERENCES user(id) );utf8mb4是为了存中文和特殊字符不出乱码create_time用DEFAULT CURRENT_TIMESTAMP省去手动赋值。外键约束保证回复不会挂到不存在的留言上。执行完用SHOW TABLES;确认三张表都在。接着看DB类里的连接配置。原生 Servlet 项目一般把 JDBC 参数写在DB.class对应的源码里或者抽到db.properties。MySQL 8.0 的驱动类是com.mysql.cj.jdbc.DriverURL 要带时区// MySQL 8.0 写法 String url jdbc:mysql://localhost:3306/message_board?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8; String user root; String password 你的密码; Class.forName(com.mysql.cj.jdbc.Driver);如果你用的是 MySQL 5.7驱动类可以写com.mysql.jdbc.DriverURL 里serverTimezone可省略。useSSLfalse是关掉 SSL 警告本地开发够用characterEncodingutf8保证中文不乱码。这几个参数是血泪经验堆出来的少一个就可能出现连接被拒或者问号乱码。2.3 Tomcat 部署与 Vue 前端启动后端编译成classes后按标准 Web 项目结构放到 Tomcat 的webapps下或者直接在 IDE 里配 Tomcat Server 运行。访问http://localhost:8080/项目名/LoginServlet能出响应说明后端通了。Vue 前端单独用npm run serve起在 8080 之外的端口比如 8081然后在vue.config.js里配代理把/api转发到 Tomcat// vue.config.js module.exports { devServer: { port: 8081, proxy: { /api: { target: http://localhost:8080/项目名, changeOrigin: true, pathRewrite: { ^/api: } } } } }changeOrigin: true是为了让后端收到的 Host 头正确pathRewrite把/api前缀去掉这样前端写/api/LoginServlet实际请求的是后端/LoginServlet。这一步不做前端请求会 404 或者跨域报错是新手最容易卡住的地方。3. 后端 Servlet 三件套登录、列表、回复的请求链路3.1 LoginServlet 的会话管理LoginServlet负责校验用户名密码并写 Session。原生 Servlet 里用HttpSession存登录态后续请求从 Session 里取用户信息判断是否登录。核心逻辑大致是这样protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); String username req.getParameter(username); String password req.getParameter(password); User user userDao.findByUsername(username); resp.setContentType(application/json;charsetUTF-8); PrintWriter out resp.getWriter(); if (user ! null user.getPassword().equals(password)) { req.getSession().setAttribute(user, user); out.print({\code\:200,\msg\:\登录成功\}); } else { out.print({\code\:401,\msg\:\用户名或密码错误\}); } }req.setCharacterEncoding(UTF-8)必须在取参数之前调用否则 POST 中文会乱码。resp.setContentType设成 JSON 并带 charset前端 axios 才能正确解析。Session 里存的是整个User对象后续MessageListServlet和ReplyServlet都从 Session 取user_id避免前端传用户 ID 被篡改。这里有个边界如果用户直接访问列表接口但没登录Session 里取不到 user应该返回 401 而不是空列表否则前端会显示异常。3.2 MessageListServlet 的分页与查询留言列表接口要处理两个参数页码和每页条数。原生 Servlet 里用LIMIT做分页配合COUNT算总数。常见实现protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { int page Integer.parseInt(req.getParameter(page) null ? 1 : req.getParameter(page)); int size 10; int offset (page - 1) * size; ListMessage list messageDao.findByPage(offset, size); int total messageDao.count(); MapString, Object result new HashMap(); result.put(list, list); result.put(total, total); result.put(page, page); resp.setContentType(application/json;charsetUTF-8); resp.getWriter().print(new Gson().toJson(result)); }offset是跳过的记录数page1时 offset 为 0。Gson把 Map 转成 JSON前端拿到list渲染列表拿到total算总页数。参数说明page要做空值和非法值判断如果传了page0或负数offset 会变负SQL 直接报错。稳妥做法是加一层Math.max(1, page)。另外size不建议从外部传固定值能防止有人传size100000把库拖垮。3.3 ReplyServlet 的写入与事务边界回复接口接收message_id和content从 Session 取user_id写入 reply 表。单条插入本身不需要事务但如果业务要求「回复成功后更新留言的回复数」就要把两个操作放一个事务里protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); User user (User) req.getSession().getAttribute(user); if (user null) { resp.getWriter().print({\code\:401}); return; } int messageId Integer.parseInt(req.getParameter(message_id)); String content req.getParameter(content); Connection conn null; try { conn DB.getConnection(); conn.setAutoCommit(false); replyDao.insert(conn, messageId, user.getId(), content); messageDao.incrementReplyCount(conn, messageId); conn.commit(); resp.getWriter().print({\code\:200}); } catch (Exception e) { if (conn ! null) conn.rollback(); resp.getWriter().print({\code\:500}); } finally { DB.close(conn); } }setAutoCommit(false)开启事务两个 DAO 操作共用同一个Connection成功一起提交失败一起回滚。这里的关键是 DAO 方法要接收外部传入的Connection不能各自去DB.getConnection()否则事务管不到两个连接。这是原生 JDBC 事务最容易翻车的地方很多人写完发现回滚没生效就是因为连接不是同一个。4. Vue 前端对接axios 请求、路由与状态保持4.1 axios 封装与请求拦截Vue 这边用 axios 发请求建议统一封装一个request.js把 baseURL 和拦截器配好import axios from axios const request axios.create({ baseURL: /api, timeout: 5000 }) request.interceptors.response.use( response response.data, error { if (error.response error.response.status 401) { window.location.href /login } return Promise.reject(error) } ) export default requestbaseURL: /api配合前面的代理配置开发环境请求会转发到 Tomcat。响应拦截器里判断 401 跳登录页避免未登录状态下一直请求失败。timeout: 5000防止请求挂死。这里要注意后端返回的是 JSON 字符串axios 默认会尝试解析如果后端Content-Type没设对前端拿到的可能是字符串而不是对象渲染时就会报list is not a function之类的错。4.2 留言列表渲染与分页组件列表页拿到数据后用v-for渲染分页用简单的前后端配合export default { data() { return { messages: [], page: 1, total: 0 } }, methods: { loadMessages() { this.$request.get(/MessageListServlet, { params: { page: this.page } }).then(res { this.messages res.list this.total res.total }) } }, mounted() { this.loadMessages() } }params会把page拼到 URL 上对应后端req.getParameter(page)。mounted里首次加载。分页按钮点击时改this.page再调loadMessages。这里有个细节如果后端返回的list里字段名和前端模板里写的不一致页面会显示空白但不报错排查时先console.log(res)看真实结构。4.3 登录态在前端的保持原生 Servlet 用 Session 保持登录态前端不需要存 token但需要知道当前是否登录。常见做法是登录成功后把用户信息存localStorage路由守卫里判断router.beforeEach((to, from, next) { const user localStorage.getItem(user) if (to.path ! /login !user) { next(/login) } else { next() } })localStorage里存的是用户信息副本真正的权限判断仍在后端 Session。前端守卫只是提升体验不能当安全边界。如果用户手动改了localStorage后端该拦还是拦。这一点在课程设计答辩时经常被问到要能说清楚前后端各自的责任。5. 避坑与排查五个让项目跑不起来的典型问题5.1 启动报 ClassNotFoundException: com.mysql.jdbc.Driver现象是 Tomcat 启动或第一次请求数据库时抛这个异常。原因是 MySQL 8.0 的驱动类名变了或者mysql-connector-java的 jar 没放进WEB-INF/lib。解决确认 jar 在WEB-INF/lib下MySQL 8.0 用com.mysql.cj.jdbc.Driver5.7 用com.mysql.jdbc.Driver。如果用的是 Maven检查pom.xml里依赖的 scope 不是provided。5.2 前端请求 404 或跨域现象是浏览器控制台报404 Not Found或CORS policy。原因是 Vue 起的端口和 Tomcat 不同直接请求后端地址会跨域或者代理配了但pathRewrite写错。解决确认vue.config.js里target指向 Tomcat 的完整项目路径pathRewrite把/api去掉后剩下的路径要和 Servlet 的url-pattern对上。改完代理要重启npm run serve热更新不会重载代理配置。5.3 中文乱码现象是留言内容存进库变成问号或者页面显示乱码。原因可能在三处数据库字符集不是utf8mb4、JDBC URL 没带characterEncodingutf8、Servlet 没设req.setCharacterEncoding(UTF-8)。解决三处逐一确认。数据库用SHOW VARIABLES LIKE character%;看JDBC URL 补参数Servlet 里setCharacterEncoding放在取参数之前。5.4 Session 取不到用户一直跳登录现象是登录成功但后续请求都返回 401。原因是前端 axios 默认不带 cookie跨端口时 Session ID 没传过去。解决axios 里设withCredentials: true后端响应头加Access-Control-Allow-Credentials: true且Access-Control-Allow-Origin不能是*要写具体来源。如果用了代理同源请求一般不会有这个问题优先走代理方案。5.5 分页参数为负导致 SQL 报错现象是点上一页到第一页再点上一页后端抛 SQL 异常。原因是page减到 0 或负数offset变负。解决前端分页按钮加边界判断page 1才允许减后端加Math.max(1, page)兜底。两层都做防止有人直接改 URL 参数。6. 进阶验证用 Postman 和日志把请求链路走一遍项目能跑起来只是第一步要确认它真的按预期工作我一般会做两件事用 Postman 单独打后端接口看返回结构在关键 Servlet 里加日志看请求参数和 Session 状态。先测登录curl -X POST http://localhost:8080/项目名/LoginServlet \ -d usernametestpassword123456 \ -c cookies.txt-c cookies.txt把 Session ID 存下来后续请求带上-b cookies.txt就能保持登录态。然后测列表和回复curl -b cookies.txt http://localhost:8080/项目名/MessageListServlet?page1 curl -b cookies.txt -X POST http://localhost:8080/项目名/ReplyServlet \ -d message_id1content测试回复这样能在不依赖前端的情况下确认后端逻辑正确。如果 curl 通了但前端不通问题就在代理或前端代码如果 curl 也不通问题在后端或数据库。这个二分法能省很多排查时间。再进一步可以在DB类里加一个简单的连接检测方法启动时打印连接是否成功public static void checkConnection() { try (Connection conn getConnection()) { System.out.println(DB connected: conn.getMetaData().getURL()); } catch (Exception e) { System.err.println(DB connect failed: e.getMessage()); } }在contextInitialized或某个 Servlet 的init里调一次Tomcat 启动日志里就能看到数据库通没通。这比等到第一次请求报错再回头查要主动得多。参数对照表放在这里方便你改配置时核对配置项MySQL 5.7MySQL 8.0驱动类com.mysql.jdbc.Drivercom.mysql.cj.jdbc.DriverURL 时区可省略serverTimezoneAsia/ShanghaiSSL 参数可省略useSSLfalse字符集characterEncodingutf8characterEncodingutf8最后说一个我自己的习惯每次拿到这种原生 Servlet 项目先不急着改代码而是把web.xml或注解里的url-pattern全部列出来对照前端请求路径画一遍确认没有对不上的。这个动作花五分钟能省掉后面半小时的 404 排查。从那以后我每次部署这类项目都强制走一遍「路径对照 curl 冒烟」希望帮到你。本文还有配套的精品资源点击获取
返回列表