拓冰建站拓冰建站
首页 / 资讯中心 / 正文

JSP+Servlet+JDBC+MySQL女性交流网站毕设源码部署与二次开发

前几天有个学弟发消息过来说他抽到的毕设题目是“女性交流网站”技术栈限定 JSP还附了一份 w97199 编号的源码问我这套东西现在还有没有必要认真做能不能直接跑起来交差。我的回答是JSP 这套东西确实不新但作为计算机毕设它反而是最不容易翻车的一条路——环境成熟、资料多、导师看得懂、答辩时能讲清楚。这份源码本身是个典型的 JavaWeb 项目用的是 JSP Servlet JDBC MySQL 的老三样组合功能上覆盖了用户注册登录、帖子发布、板块分类、评论互动和后台管理属于“麻雀虽小五脏俱全”的类型。我把这份源码从头到尾捋了一遍也顺手在自己的环境里重新部署验证了一次把踩过的坑和几个关键实现点整理出来。这篇内容适合三类人看一是抽到同类毕设题目、手上拿到一份源码但不知道怎么下手的同学二是想拿它当模板做二次开发的人三是单纯想重温一下 JSP 项目完整开发流程的从业者。下面我会从需求拆解、架构分层、数据库设计、核心功能实现到部署排查一条线讲透能直接照着复现。1. 从标题拆解这个毕设到底要做什么1.1 需求侧拆解一个女性交流社区的最小可用集合拿到“女性交流网站”这个题目很多人第一反应是“不就是个论坛吗”然后照着 Discuz 的样子堆功能最后把自己堆死了。我的经验是毕设的需求范围必须收窄到一个能在两三周内做完、并且每一个功能都能在答辩时讲出设计理由的集合。这份源码的思路就很清晰它把整个站点收敛成了“内容社区 用户体系 后台管理”三块。具体到内容社区它划了几个固定板块比如时尚穿搭、美妆护肤、育儿经验、职场成长、健康生活这几类。这里有个设计决策值得说一下板块是固定的而不是让用户自己创建。原因很实际用户自建板块会带来权限、审核、排序、空板块清理一堆问题工作量直接翻倍而固定板块只需要在数据库里放一张分类表前端循环渲染即可。答辩时老师问起来你可以说这是“基于运营可控性的折中设计”听起来还挺专业。用户体系部分核心就是注册、登录、个人信息维护、头像上传这几件事。注意不要一上来就做第三方登录或者手机验证码前者要对接外部平台后者要接短信服务都是花钱又费时的坑。源码里用的是最朴素的用户名 密码 邮箱的注册方式密码做 MD5 加盐存储这个程度对于毕设来说完全够用也能体现安全意识。后台管理是很多人容易忽略的地方但它在答辩加分上特别管用。一个能登录、能删帖、能封用户、能看统计数据的后台会让你的项目从“学生作业”变成“一个系统”。源码里的后台做得很轻就是一个单独的 admin 目录加一套守卫逻辑成本很低但效果很好。1.2 技术侧拆解为什么是 JSP 而不是前后端分离现在网上到处在讲前后端分离、讲 Vue SpringBoot很多同学拿到 JSP 题目会觉得“老师怎么给我这么老的技术”。但从毕设的实际约束来看JSP 有三个不可替代的优势我在带人做毕设的过程中反复验证过。第一个优势是部署链路短。前后端分离的项目你得同时起一个后端服务和前端 dev server还要处理跨域、代理、打包后静态资源路径这些问题。JSP 项目把页面、逻辑、数据访问全塞进一个 war 包扔进 Tomcat 就完事出问题的环节少了一大半。对于只有两三周时间的毕设来说少一个环节就少一批 bug。第二个优势是代码可读性对答辩友好。你打开一个 .jsp 文件上面是 Java 逻辑下面是 HTML 结构老师一眼就能看出数据是怎么来的、怎么渲染的。而前后端分离的项目老师在答辩现场看到的往往是一个打包压缩过的 js 文件你解释起来反而更费劲。第三个优势是资料密度高。JSP 相关的报错、配置、写法网上能搜到的中文资料量级非常大遇到问题基本都能找到答案。这一点在实际开发中太重要了尤其是当你半夜两点卡在一个诡异报错上时能不能搜到答案直接决定你今晚睡不睡。但我必须说清楚它的短板JSP 页面里写 Java 代码容易写成“面条式代码”业务逻辑和展示逻辑糊在一起。所以这份源码做了一件对的事——把数据访问抽到 DAO 层把业务规则放到 Service 层JSP 只负责展示。这个分层不是为了炫技而是为了让你在答辩时能回答“你的代码结构是怎样的”这个问题。1.3 工作量评估与时间分配建议很多人做毕设失败不是因为技术不会而是因为时间分配失衡。前面三周在纠结用哪个框架最后一周开始疯狂写代码结果功能没做完、论文没写、答辩 PPT 是通宵做的。基于这份源码的规模我给出一个比较实际的时间分配参考。阶段内容建议天数产出物第一阶段环境搭建、源码跑通、数据库导入2 到 3 天能在本地访问首页并登录第二阶段读懂核心代码、梳理表关系3 到 4 天手绘 ER 图、功能清单第三阶段功能增删改、界面调整5 到 7 天可演示的完整系统第四阶段论文撰写、图表整理4 到 5 天论文初稿第五阶段答辩准备、录制演示视频2 天PPT 与演示材料这个表里最关键的是第一阶段和第二阶段。源码能不能跑起来决定了你后面所有工作的起点。如果三天过去了首页还是 404那就不是“再调调”的问题而是环境版本不匹配需要果断换版本重来而不是死磕。2. 技术栈组合与架构分层2.1 版本匹配一套能跑通且不折腾的组合JSP 项目最恶心的地方就是版本兼容。JDK、Tomcat、Servlet 规范、JSTL 标签库、MySQL 驱动这五个东西只要有一个版本错位报错信息就会变得非常抽象。我实测下来最稳的组合是这个组件推荐版本说明JDK81.8兼容性最好Tomcat 9 以下都吃Tomcat9.0.xServlet 4.0 规范不推荐 10 以上Servlet API4.0javax 包注意是 javax 不是 jakartaJSTL1.2配合 c 标签使用别用 1.0MySQL5.7 或 8.08.0 要换新的驱动和连接串mysql-connector-java8.0.28 或 5.1.49要和 MySQL 版本对应构建工具Maven 3.6用 Maven 管依赖别手动导 jar这里有一个坑我必须重点提醒Tomcat 10 开始把 javax.servlet 改成了 jakarta.servlet。如果你把一份基于 javax 的老项目扔进 Tomcat 10会看到满屏的 ClassNotFoundException 和 NoClassDefFoundError而且报错信息里根本不会提“包名变了”这件事。很多同学在这里卡一整天最后才发现是 Tomcat 版本问题。所以我的建议很直接老老实实用 Tomcat 9别去追新。MySQL 8.0 的驱动类名和连接串也变了。老写法是com.mysql.jdbc.Driver新写法是com.mysql.cj.jdbc.Driver连接串后面还要加serverTimezoneAsia/Shanghai和useSSLfalse这些参数否则会报时区错误或者 SSL 警告。这些细节看着琐碎但每一个都能让你卡半天。2.2 目录结构一眼看出这个项目靠不靠谱一个规范的 JavaWeb 项目目录结构是有固定套路的。这份源码的结构属于标准写法我把它列出来你可以对照自己的项目检查。female-community/ ├── src/ │ └── main/ │ ├── java/ │ │ └── com/community/ │ │ ├── dao/ # 数据访问层 │ │ │ ├── UserDao.java │ │ │ └── PostDao.java │ │ ├── service/ # 业务逻辑层 │ │ │ └── UserService.java │ │ ├── servlet/ # 控制器 │ │ │ ├── LoginServlet.java │ │ │ └── PostServlet.java │ │ ├── entity/ # 实体类 │ │ │ └── User.java │ │ └── util/ # 工具类 │ │ ├── DBUtil.java │ │ └── MD5Util.java │ ├── resources/ │ │ └── db.properties │ └── webapp/ │ ├── WEB-INF/ │ │ ├── web.xml │ │ └── lib/ │ ├── css/ │ ├── js/ │ ├── images/ │ └── index.jsp └── pom.xml看到这个结构你应该有判断力了有 dao、service、servlet 分层的说明作者至少受过基本训练。如果打开一看所有逻辑全写在 JSP 文件的% %里那这份源码的维护成本会非常高你需要花大力气重构或者干脆照着自己的思路重写一遍。WEB-INF这个目录是个要点它下面的文件是不能被浏览器直接访问的只能通过服务器内部转发访问。把web.xml和 jar 包放这里是规范做法。有些同学喜欢把 JSP 页面放在WEB-INF下这样用户就不能通过 URL 直接访问页面必须走 Servlet 转发安全性更好但调试时相对麻烦一点看你的取舍。2.3 分层职责每一层到底该干什么分层的意义在于“改一处不影响其他处”。我见过太多毕设项目用户表加一个字段要从 JSP 页面改到数据库一路改下来十几个文件。这就是分层没做好。DAO 层的唯一职责是跟数据库打交道。它的方法命名通常是findById、findAll、insert、update、delete这种参数是实体对象或者 ID返回值是实体对象或者 List。这一层绝对不能出现任何业务判断比如“这个用户是不是 VIP”“这篇帖子能不能被删”这些都不是 DAO 的事。Service 层的职责是业务规则。比如发帖时要判断内容是否包含敏感词、用户是否被禁言、当天发帖数是否超限这些都是 Service 层的活儿。Service 层调用 DAO 层拿数据做完判断再把结果往上抛。Servlet 层控制器的职责是接收请求、调度、返回响应。它从 request 里取参数调 Service把结果塞进 request 或 session然后转发到 JSP。这一层不应该有复杂的 if-else 业务逻辑否则就是分层没做好。JSP 层的职责是展示。理想状态下JSP 里只有 JSTL 标签和 EL 表达式看不到一行% %脚本。虽然实际毕设里很难做到 100% 干净但方向应该是这个方向。3. 数据库设计几张表撑起整个站点3.1 核心表结构设计一个交流社区的核心表其实不多但每张表的设计细节都会影响后面的开发难度。我按这份源码的思路把最关键的四张表拆开讲顺便说说每张表为什么这么设计。用户表是地基字段设计要考虑到登录、展示、权限三个用途CREATE TABLE t_user ( id INT NOT NULL AUTO_INCREMENT COMMENT 用户ID, username VARCHAR(50) NOT NULL COMMENT 登录名, password VARCHAR(64) NOT NULL COMMENT MD5加盐后的密码, salt VARCHAR(16) NOT NULL COMMENT 盐值, nickname VARCHAR(50) DEFAULT NULL COMMENT 昵称, avatar VARCHAR(255) DEFAULT NULL COMMENT 头像路径, email VARCHAR(100) DEFAULT NULL COMMENT 邮箱, role TINYINT DEFAULT 0 COMMENT 0普通用户 1管理员, status TINYINT DEFAULT 1 COMMENT 1正常 0禁用, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这里有几个细节值得说。密码字段长度给到 64因为 MD5 输出是 32 位十六进制加上盐值拼接和可能的二次哈希留足空间更稳妥。加了 salt 字段每个用户一个独立盐值这样即使数据库泄露攻击者也没法用彩虹表批量破解。role 和 status 分开一个管权限一个管状态逻辑上不重叠不要合成一个字段否则后面加一种状态就要改所有判断逻辑。帖子表的设计要特别注意索引。社区类应用最频繁的操作就是“按板块分页查帖子”和“按用户查帖子”这两个查询路径必须在设计阶段就考虑进去CREATE TABLE t_post ( id BIGINT NOT NULL AUTO_INCREMENT, user_id INT NOT NULL COMMENT 作者ID, category_id INT NOT NULL COMMENT 板块ID, title VARCHAR(200) NOT NULL, content TEXT NOT NULL, view_count INT DEFAULT 0, like_count INT DEFAULT 0, status TINYINT DEFAULT 1 COMMENT 1正常 0已删除, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_category_time (category_id, create_time), KEY idx_user (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;注意那个联合索引idx_category_time。为什么是category_id在前、create_time在后因为查询语句是WHERE category_id ? ORDER BY create_time DESC联合索引遵循最左前缀原则category_id做等值匹配create_time做排序这个顺序能让 MySQL 直接利用索引完成排序省掉一次 filesort。这个点如果能在答辩时讲出来是很加分的。评论表用 parent_id 支持两级嵌套就够了不要做无限层级因为前端渲染一棵无限深的树会非常麻烦CREATE TABLE t_comment ( id BIGINT NOT NULL AUTO_INCREMENT, post_id BIGINT NOT NULL, user_id INT NOT NULL, parent_id BIGINT DEFAULT 0 COMMENT 0为一级评论, content VARCHAR(1000) NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_post_time (post_id, create_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;3.2 字符集、外键与级联操作的取舍字符集统一用utf8mb4这是必须的。用utf8的话遇到 emoji 或者某些生僻字会直接插入失败报一个让人摸不着头脑的编码错误。改起来还特别麻烦要从数据库、表、字段、连接串一路改下来。一开始就上 utf8mb4一劳永逸。外键这个东西在毕设里争议比较大。加了外键数据库层面能保证一致性删除用户时可以用ON DELETE CASCADE自动清理他的帖子但加了外键导入导出数据时顺序错了就会报错调试起来也麻烦。这份源码的做法是逻辑外键不加物理约束也就是说user_id只是个 INT 字段不建 FOREIGN KEY 约束一致性靠代码保证。我的看法是毕设项目两种做法都行但如果你选择不加外键在论文里一定要写明“采用逻辑外键以保证导入导出的灵活性”否则老师看到 ER 图上画了关系线但数据库里没有约束会觉得你概念不清楚。设计决策本身没有绝对对错能不能说清楚理由才是关键。4. 核心功能实现要点4.1 注册登录Session 与密码安全登录功能看着简单但里面藏着不少细节。先说密码存储。明文存密码是绝对不行的连毕设都不能这么干。简单做法就是用 MD5但单纯 MD5 有三个问题一是相同密码哈希值相同容易被批量比对二是 MD5 计算速度快暴力破解成本低三是没有盐值的话彩虹表直接查表。一个够用的做法是“随机盐 MD5 多轮”public class MD5Util { public static String generateSalt() { byte[] bytes new byte[8]; new SecureRandom().nextBytes(bytes); return Hex.encodeHexString(bytes); } public static String encrypt(String password, String salt) { String source password salt; String result source; for (int i 0; i 3; i) { result DigestUtils.md5Hex(result); } return result; } }注意这里用的是SecureRandom而不是Random因为Random的种子是可预测的生成的盐值理论上可以被推测出来。这个细节说出来答辩时是很能体现安全意识的。登录成功之后把用户对象塞进 SessionWebServlet(/login) public class LoginServlet extends HttpServlet { private final UserService userService new UserService(); Override 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 userService.login(username, password); if (user null) { req.setAttribute(msg, 用户名或密码错误); req.getRequestDispatcher(/login.jsp).forward(req, resp); return; } if (user.getStatus() 0) { req.setAttribute(msg, 账号已被禁用); req.getRequestDispatcher(/login.jsp).forward(req, resp); return; } HttpSession session req.getSession(); session.setAttribute(loginUser, user); session.setMaxInactiveInterval(30 * 60); resp.sendRedirect(req.getContextPath() /index.jsp); } }几个要点setCharacterEncoding必须在取参数之前调用否则中文参数会乱码这个顺序错了排查很久。登录失败时用forward而不是sendRedirect因为 forward 能保住 request 里的错误提示信息重定向会丢掉。登录成功时用sendRedirect这是 PRG 模式Post-Redirect-Get防止用户按 F5 重复提交表单。4.2 帖子发布与富文本内容的处理发帖功能的核心难点不是存数据而是处理用户输入的内容。如果直接做富文本编辑器用户粘贴的 HTML 里可能带着 script 标签这就构成了 XSS 风险。毕设里不想处理这么复杂的话最稳妥的方案是只接受纯文本换行符在前端渲染时转成 br这样从根本上没有注入 HTML 的可能。如果一定要支持富文本那必须做过滤。可以用 Jsoup 这个库做白名单过滤只允许 p、br、strong、em、img 这些安全标签public class HtmlFilter { private static final Whitelist WHITELIST Whitelist.basic() .addTags(img, p, br, strong, em) .addAttributes(img, src, alt, width, height) .addProtocols(img, src, http, https); public static String clean(String html) { return Jsoup.clean(html, WHITELIST); } }这样即使有人塞进scriptalert(1)/script经过过滤之后也只剩下纯文本不会被执行。白名单比黑名单安全因为黑名单永远会漏白名单只放行已知安全的东西。另外图片上传功能要限制文件类型和大小。只接受 jpg、png、gif、webp大小限制在 2MB 以内并且文件名要重新生成用 UUID 加扩展名的方式绝对不要用用户上传的原始文件名。原来那种做法有两个坑一是用户上传的文件名可能带路径穿越字符二是重名会互相覆盖。重新生成文件名虽然让文件变得不好辨认但可以用用户 ID 做目录分层来弥补。4.3 分页查询别用全表查再截取很多初学者写分页是这样写的把整张表的帖子查出来然后从 List 里subList取一段。数据量小的时候没问题数据量一大就凉了。正确做法是在 SQL 层面用 LIMITpublic ListPost findByCategory(int categoryId, int page, int pageSize) { String sql SELECT p.*, u.nickname, u.avatar, c.name AS categoryName FROM t_post p LEFT JOIN t_user u ON p.user_id u.id LEFT JOIN t_category c ON p.category_id c.id WHERE p.category_id ? AND p.status 1 ORDER BY p.create_time DESC LIMIT ?, ?; ListPost list new ArrayList(); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, categoryId); ps.setInt(2, (page - 1) * pageSize); ps.setInt(3, pageSize); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { list.add(mapRow(rs)); } } } catch (SQLException e) { throw new RuntimeException(查询帖子失败, e); } return list; }这里的LIMIT ?, ?参数化写法要注意MySQL 允许在 LIMIT 位置使用占位符但要求参数是整型。这个写法比字符串拼接 SQL 安全得多也是防 SQL 注入的基本功。分页控件本身也需要处理一个细节总页数要基于总记录数计算而这个总数需要单独一条SELECT COUNT(*)查询。有的同学为了省一次查询用list.size()当总数那就永远只有一页了这是很常见的低级错误。分页组件通常在 JSP 里用 JSTL 渲染div classpagination c:if test${page 1} a href${ctx}/post?categoryId${categoryId}page${page - 1}上一页/a /c:if c:forEach begin1 end${totalPage} vari c:choose c:when test${i page} span classcurrent${i}/span /c:when c:otherwise a href${ctx}/post?categoryId${categoryId}page${i}${i}/a /c:otherwise /c:choose /c:forEach c:if test${page totalPage} a href${ctx}/post?categoryId${categoryId}page${page 1}下一页/a /c:if /div如果总页数很多这个循环会渲染出几百个页码页面会很难看。改进做法是只显示当前页前后各两个页码加省略号。这个优化不复杂但对最终演示效果提升很明显。4.4 后台管理轻量但能加分后台管理模块是毕设里性价比最高的部分。功能不用多但一定要有而且要有权限校验。核心是三个功能用户管理禁用/启用/删除、帖子管理删除/置顶、数据统计用户数、帖子数、今日新增。权限校验的实现最简做法是写一个 Filter拦下所有/admin/*的请求检查 Session 里有没有管理员用户WebFilter(/admin/*) public class AdminFilter implements Filter { Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req (HttpServletRequest) request; HttpServletResponse resp (HttpServletResponse) response; HttpSession session req.getSession(false); Object user session null ? null : session.getAttribute(loginUser); if (user null || ((User) user).getRole() ! 1) { resp.sendRedirect(req.getContextPath() /login.jsp); return; } chain.doFilter(request, response); } }这里用getSession(false)而不是getSession()区别在于前者在 session 不存在时返回 null 而不是创建新的。这个细节避免了为每个匿名请求都创建一个空 session在访问量上来之后能省不少内存。当然毕设里访问量上不来但答辩时说出来是加分项。5. 部署与常见问题排查实录5.1 源码导入 IDEA 后的完整配置流程拿到源码第一步不是急着运行而是按顺序把环境配好顺序错了会浪费大量时间。我的建议流程是这样的。第一步确认 JDK 版本。打开 IDEA 的 Project Structure把 Project SDK 和 Language Level 都设成 8。如果设成 11 或 17老项目里某些依赖会因为模块系统变化而报错。第二步配置 Tomcat。在 Run/Debug Configurations 里新增一个 Tomcat Server LocalApplication server 指向 Tomcat 9 的安装目录然后在 Deployment 标签页里添加 Artifact选择 war exploded 形式。用 war exploded 而不是 war因为 exploded 形式支持热更新改了 JSP 不用重启服务器就能生效开发效率高很多。第三步配置 Artifact。在 Project Structure 的 Artifacts 里确认输出目录结构正确WEB-INF/classes下应该有编译后的 class 文件WEB-INF/lib下应该有所有依赖 jar。如果 lib 是空的说明 Maven 依赖没打进去需要在 Artifact 配置里手动把 Maven 依赖加进来。第四步配置 Application context。默认是/项目名_war_exploded建议改成/community短一点这样访问路径清爽也不会出现路径太长导致某些 cookie 写入失败的问题。很多同学在第三步和第四步之间踩坑访问http://localhost:8080/看到 Tomcat 首页但访问自己的项目就是 404。这通常是 Application context 记错了去 Run 配置里看一眼实际路径就知道了。5.2 编译后的 JSP 和 Java 类到底去了哪这个问题问的人特别多。JSP 本质上会被编译成 Servlet那么编译后的文件在哪答案是Tomcat 的工作目录。对于 IDEA 部署的项目通常在项目目录下的.tomcat或者 Tomcat 安装目录的work/Catalina/localhost/下面。你要找的是类似这样的路径work/Catalina/localhost/community/org/apache/jsp/index_jsp.java work/Catalina/localhost/community/org/apache/jsp/index_jsp.class打开这个index_jsp.java你会看到 Tomcat 是怎么把你的 JSP 翻译成 Servlet 的——HTML 部分变成了out.write(...)% %里的代码原样搬进了_jspService方法EL 表达式变成了PageContext.findAttribute之类的调用。看懂这个翻译结果你就真正理解 JSP 的运行机制了答辩时如果被问到“JSP 是怎么执行的”你能答出“先翻译成 Servlet 再编译执行”再补一句“可以在 work 目录看到翻译后的 Java 文件”基本就满分了。如果你想临时查看某个 JSP 的翻译结果但找不到文件可能是 Tomcat 缓存没刷新。删掉 work 目录下对应项目的文件夹重新访问页面就会重新生成。不过要注意不要在生产环境随便删 work 目录会触发全站重新编译短时间性能下降。5.3 常见报错速查表下面这张表是我在帮人排查 JSP 项目问题时整理出来的覆盖了八成以上的报错场景。报错信息关键词大概率原因排查动作ClassNotFoundException: com.mysql.jdbc.Driver驱动类名旧或 jar 没进 lib改成 com.mysql.cj.jdbc.Driver检查 WEB-INF/libNoClassDefFoundError: javax/servlet/...Tomcat 10 与 javax 包不兼容换回 Tomcat 9The server time zone value is unrecognizedMySQL 8 连接串缺时区参数加 serverTimezoneAsia/Shanghai中文乱码请求、响应、数据库三处编码不一致三处统一 UTF-8404 但文件存在访问路径与 Servlet 映射不匹配检查 WebServlet 值和 contextPath500 空指针参数没取到或对象没初始化看堆栈定位具体行号JSTL 标签不解析直接输出缺 JSTL jar 或标签库声明加 jstl-1.2.jar 和 taglib 声明上传文件失败缺 multipart 配置或大小超限检查 MultipartConfig 和 maxFileSize连接池耗尽连接没关闭检查是否用了 try-with-resources页面样式全丢静态资源路径用了相对路径改用${pageContext.request.contextPath}关于中文乱码展开说一下。JSP 项目的乱码有三个环节请求参数编码、响应输出编码、数据库连接编码。三个都必须设成 UTF-8缺一个就会乱。请求参数用req.setCharacterEncoding(UTF-8)响应输出用resp.setContentType(text/html;charsetUTF-8)数据库连接串加characterEncodingutf8。还有个偷懒但有效的办法在 web.xml 里注册一个 CharacterEncodingFilter统一处理比在每个 Servlet 里写一遍可靠。如果这些都设对了还是乱码那就检查 JSP 页面顶部的 page 指令% page contentTypetext/html;charsetUTF-8 languagejava pageEncodingUTF-8 %contentType和pageEncoding两个都要有前者管响应头后者管文件本身的编码。有些同学只写了一个结果页面里的中文正常但从数据库读出来的中文乱码就是这两个没配对。5.4 数据库连接工具类的写法与连接泄漏问题连接泄漏是 JavaWeb 新手最容易犯的错误症状是系统跑一会儿就卡死报“Too many connections”。原因通常是拿了连接忘了关。看这段代码public class DBUtil { private static final String URL; private static final String USER; private static final String PASSWORD; static { Properties props new Properties(); try (InputStream in DBUtil.class.getClassLoader() .getResourceAsStream(db.properties)) { props.load(in); } catch (IOException e) { throw new ExceptionInInitializerError(加载数据库配置失败); } URL props.getProperty(jdbc.url); USER props.getProperty(jdbc.username); PASSWORD props.getProperty(jdbc.password); try { Class.forName(props.getProperty(jdbc.driver)); } catch (ClassNotFoundException e) { throw new ExceptionInInitializerError(数据库驱动加载失败); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } }这个写法把配置抽到 properties 文件里改数据库地址不用动代码是基本工程素养。但它的短板是每次请求都新建连接开销大。改进方向是用连接池Druid 或者 HikariCP 都行配置也就几行public class DBUtil { private static final DruidDataSource DATA_SOURCE new DruidDataSource(); static { Properties props new Properties(); try (InputStream in DBUtil.class.getClassLoader() .getResourceAsStream(db.properties)) { props.load(in); } catch (IOException e) { throw new ExceptionInInitializerError(e); } DATA_SOURCE.setUrl(props.getProperty(jdbc.url)); DATA_SOURCE.setUsername(props.getProperty(jdbc.username)); DATA_SOURCE.setPassword(props.getProperty(jdbc.password)); DATA_SOURCE.setDriverClassName(props.getProperty(jdbc.driver)); DATA_SOURCE.setInitialSize(5); DATA_SOURCE.setMaxActive(20); DATA_SOURCE.setMaxWait(3000); } public static Connection getConnection() throws SQLException { return DATA_SOURCE.getConnection(); } }但不管是直连还是连接池关闭连接这一步绝对不能省。用 try-with-resources 是最省心的写法编译器会自动生成 finally 块try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql); ResultSet rs ps.executeQuery()) { // 处理结果集 } catch (SQLException e) { // 异常处理 }注意这里关闭顺序是反的ResultSet 先关Statement 再关Connection 最后关。用 try-with-resources 的话JVM 会自动按声明的逆序关闭你不用操心。但如果你手动写 finally顺序写反了在某些驱动下会抛异常。6. 二次开发方向与答辩准备6.1 几个能明显拉开差距的改造点如果你手上这份源码已经跑通了想让它更出彩我推荐几个投入产出比高的改造方向。第一个是加一个简易的推荐逻辑。不用搞什么协同过滤就用“热门帖子 浏览量 × 0.3 点赞数 × 0.5 评论数 × 0.2”算个分数按分数排序展示在首页侧边栏。公式简单但你在论文里可以画个权重表讲讲每个系数为什么这么定看起来非常有方法论。第二个是加敏感词过滤。用一个 txt 文件存敏感词启动时加载进内存可以用简单的字符串匹配也可以用 DFA 算法做高效匹配。DFA 版本大概五十行代码但性能比逐个contains好得多答辩时讲出来是个亮点。第三个是加登录失败次数限制。同一个用户名连续失败五次锁定十分钟。实现方式可以记在 session 里也可以记在数据库的字段上。这个小功能能体现你对安全场景的思考比单纯说“我做了登录”要有说服力。第四个是把图片存储从本地磁盘改成相对路径管理。毕设里图片存本地是正常的但路径如果写死了绝对路径换台电脑就跑不起来这是个很常见的翻车点。改成相对于项目部署目录的路径配合配置文件里的 base path 参数项目就有了可移植性。6.2 答辩时最容易被追问的几个点根据我了解到的情况JSP 类毕设答辩时老师最爱问这几个问题提前准备一下能省很多事。问为什么用 JSP 不用前后端分离回答的思路不要是“因为老师要求”而应该是“项目规模小、部署环境简单、开发周期短JSP 的全栈一体化方案能减少环境依赖和联调成本”。把技术选型和项目约束挂钩逻辑就立住了。问你的系统安全性怎么保证的分四点答密码加盐哈希存储、所有 SQL 用 PreparedStatement 防注入、富文本白名单过滤防 XSS、后台管理加 Filter 做权限校验。四点说完基本没有追问空间。问并发量大了怎么办这个问题的标准答案是分阶段先加连接池、再给高频查询加索引、然后上缓存Redis、再往后做读写分离。你不需要真的实现这些但要能说出演进路径体现你懂这个方向。问你做了什么自己的东西这个问题最忌讳答“都是照着教程做的”。哪怕你只是改了界面配色、加了两个板块、优化了分页样式也要具体说出来并且说清楚为什么这么改。原创性不一定是核心算法界面上的一处体验改进也算。6.3 我在实际部署中踩过的两个坑最后分享两个我实际遇到的坑都是那种文档里不会写、但真能卡住人的问题。第一个是IDEA 热部署失效。改完 JSP 刷新页面还是旧内容重启才好。后来发现是 Tomcat 配置里 “On frame deactivation” 设成了 Do nothing。改成 Update classes and resources 之后改 JSP 保存就生效改 Java 类需要重新部署但对小改动也够快。这个设置藏在 Run 配置的 Server 标签页里不仔细看真找不到。第二个是Maven 依赖冲突导致的 NoSuchMethodError。项目里同时引了两个不同版本的 JSTL编译能过运行时报方法找不到。排查办法是在终端跑mvn dependency:tree看依赖树里有没有同一个 artifact 出现多次。有的话用exclusions排除掉旧版本。这个问题的隐蔽性在于编译期不报错只有运行到那一行才炸如果是某个不常用的功能可能上线很久才发现。还有个更简单的经验每次改动之前先提交一次 git。毕设项目代码经常改着改着就崩了想回退又没备份只能重写。建个本地 git 仓库改之前 commit 一次出问题一键回退这个习惯能救你至少一次通宵。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门