☰
Java Web博客系统源码:课设导入部署与排坑指南
2026/9/28 9:06:03 网站建设 项目流程

简介:这是一套基于Java Web技术的完整博客系统源码,适合用于课程设计、毕业设计或Java Web进阶练习。项目覆盖Servlet、JSP、JSTL、EL等后端技术,并结合HTML、CSS、JavaScript构建前端界面,同时涉及MySQL等数据库的表设计与增删改查操作,便于学习者贯通前后端开发全流程。压缩包共629个文件,约18.43MB,其中包含55个Java源文件、20个JSP页面、32个XML配置、36个JAR依赖库,以及SQL脚本、属性配置和大量GIF演示与界面截图,结构清晰,便于按模块查阅。目前已有676人学习浏览。源码分层明确,从控制层、表现层到业务逻辑层和数据访问层均有完整实现,并配有设计文档和功能预览图,能够帮助读者快速理解从需求分析到代码落地的具体过程,是一份实践性很强的学习资料。

1. 基于Java Web的博客系统源码.zip:从交作业到真正看懂 Java Web 的一把钥匙

拿到一个"基于Java Web的博客系统源码.zip",最典型的场景是 Java 课程设计要交了,或者是头歌平台上的 Java Web 综合实训正好卡在博客系统这一节。你关心的问题其实就三个:它能不能在我的电脑上跑起来、答辩时能不能讲清每个类在干什么、我要把它改成自己的课程设计题目又从哪下手。这个 zip 里装的通常不是微服务架构,而是一个用 JSP/Servlet + JDBC 或 SSM 写出来的完整博客系统,代码量不大,但用户注册登录、文章增删改查、评论、分类这些 Java Web 基础功能全在里面,正好是拿来做课设或者入门框架的第一手素材。这篇笔记会按解压、看结构、建库、跑 Tomcat、排坑的顺序讲,最后再加一套让课设变成作品的进阶动作。跟着走一遍,你至少能把这份源码变成自己能讲清楚、敢交出去的东西。

2. 从 zip 到 IDEA:先摸清技术栈和项目结构,再决定要不要换框架

拿到压缩包,第一反应是双击解压直接拖进 IDEA。但我建议你先别急着打开,因为很多博客系统源码包是从学长电脑里打包的,里面带着.idea目录、不知道哪里拷来的 jar、甚至还有一个.git文件夹,直接打开后 IDEA 会加载别人的配置,给你报一堆莫名其妙的红。更稳的做法是先把压缩包解压到一个没有空格和中文的路径下,然后用命令行观察目录层级。等你能在文件列表里分辨出 Maven 工程和传统 Web 工程的区别,后面依赖导入、部署、调错都会顺畅很多。

2.1 解压后先看什么:pom.xml、web.xml 和三层包名暴露了全部家底

解压和查看结构,在cmd、PowerShell或终端里执行:

unzip blog-system.zip -d D:/blog # 解压到指定目录 cd D:/blog tree -L 3 -d # 只看目录层级,不刷文件 cat pom.xml # 有就打开看依赖

这段命令的意图很清楚:unzip的-d参数指定输出目录,防止压缩包内套着一层同名文件夹,后面 IDEA 选pom.xml时选错路径;tree -L 3 -d限制显示深度到三层,一眼就能看到src/main/java、src/main/resources、src/main/webapp这三个标准 Maven 目录。如果cat pom.xml提示文件不存在,说明这不是 Maven 工程,改而去找WebContent或WebRoot,里面的WEB-INF/web.xml是下一个突破口。

在没有pom.xml的传统工程里,web.xml是最重要的地图。打开后先看有没有<servlet-mapping>,有的话每一个servlet对应一个 Java 类,这是纯 JSP/Servlet 的结构;如果看到<context-param>里写的contextConfigLocation指向了 Spring XML 配置文件,说明底层是 SSM,数据库访问大概率走 MyBatis。纯 Servlet 的项目结构简单,一个 login 的流程从 JSP 到 Servlet 到 DAO 可能只有四五个类,断点好打,适合新手交课设时讲原理;SSM 项目把对象生命周期都交给了 Spring 容器,你拿到手需要再多看一个applicationContext.xml或spring-mvc.xml,但业务逻辑分层更清楚,也更能适应企业里的开发习惯。

判断完工程类型,再来看pom.xml里的关键依赖。下面是一个简化但非常常见的 Maven 博客工程依赖:

<groupId>com.example</groupId> <artifactId>blog-system</artifactId> <packaging>war</packaging> <properties> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.13</version> </dependency> </dependencies>

这里packaging是war,说明最终要部署到外部 Tomcat,不是 Spring Boot 那种内嵌 Tomcat 打成可执行 jar 的方式;javax.servlet-api的scope是provided,意思是编译期要,运行期由 Tomcat 提供,防止WEB-INF/lib里出现两份 Servlet API。看到 MyBatis 依赖,就知道接下来去找 SQL 要翻src/main/resources下的mapper目录,而不是在 Java 类里拼 SQL 字符串。maven.compiler.source和maven.compiler.target的值要和你本机 JDK 对应,JDK 8 写1.8,JDK 11 写11,JDK 17 写17。如果不对齐,编译时会遇到各种"非法字符"或"不兼容类型"的报错,而这些问题和业务代码毫无关系。

这里有个常见的选型问题:博客系统技术栈到底选什么?我的判断是,课设和毕设这种体量,JSP/Servlet + JDBC、SSM、Spring Boot 都能做,但源码既然给的是前两者,你就不要一上来重构成 Spring Boot。重构意味着要动 JSP 的请求路径、数据库访问方式、页面模板解析,工作量不比照着源码改一个新功能少。先按源码的固有路线跑通,等熟悉了数据流,再谈改进。

2.2 用 IDEA 打开源码:Maven 导入三步走,以及常见失败点

确认是 Maven 工程后,打开 IntelliJ IDEA,用Open选中有pom.xml的那一层目录,IDEA 会自己识别成 Maven 项目并启动导入。导入前建议先配好 Maven 的settings.xml。很多旧博客系统使用的依赖坐标版本不那么新,从中央仓库拉取时经常超时,这一步配置能省下大量等待时间。

<mirrors> <mirror> <id>aliyun</id> <name>aliyun maven</name> <url>https://maven.aliyun.com/repository/public</url> <mirrorOf>central</mirrorOf> </mirror> </mirrors>

这个镜像配置放在~/.m2/settings.xml的<mirrors>节点里。<mirrorOf>central</mirrorOf>表示只接管中央仓库请求,不会影响本地其他私服源。设置完成后,在 IDEA 的Settings -> Build, Execution, Deployment -> Build Tools -> Maven里把User settings file指向这个文件,然后点开 Maven 面板点一下刷新按钮。

依赖导入完成后,建议在File -> Project Structure -> Project里检查 SDK 版本。如果源码是 JDK 8 写的,而你默认用了 JDK 17,虽然大多数代码能编译,但像<dependencies>里老版本的javax.annotation或lombok会和新 JDK 的模块机制冲突。我一般直接把项目 SDK 和 Maven Runner 的 JRE 都选成同一个版本,避免出现 IDEA 里编译通过、命令行mvn package却失败的情况。Maven Runner 的 JRE 位置在Settings -> Maven -> Runner,那里有个JRE下拉框。

非 Maven 工程的处理再单独说一遍。它没有pom.xml,所有第三方 jar 都放在WEB-INF/lib目录。导入 IDEA 时选择普通项目打开,然后在Project Structure -> Modules -> Dependencies里用Add -> JARs or directories把lib目录加进来。这一步漏掉后面启动 Tomcat 会发现java.lang.NoClassDefFoundError,但 IDEA 里代码明明没有红。原因是编译期和运行期的 classpath 不一致,你在 IDEA 里编过了,但打包成 war 时没有把 lib 纳入。

无论是 Maven 还是非 Maven,都要检查一下Project Structure -> Artifacts。对于 Web 工程,IDEA 需要知道最终产物是一个 war 还是 war exploded。Maven 项目通常已经自动生成了,但 Artifact 的Output Layout里一定要有lib这一项,没有的话打包出的 war 会缺所有第三方依赖,部署时直接ClassNotFoundException: org.springframework.web.context.ContextLoaderListener。这个问题非常隐蔽,因为 IDEA 的编译和运行都可能正常,但一到外部 Tomcat 就崩。检查时如果发现Available Elements里有依赖 jar,右键放入WEB-INF/lib即可。

项目结构立住之后,你可以在Project Structure里看到模块下已经分好了main/java、main/resources和main/webapp三个源根,这样的划分意味着你可以直接在 IDEA 里断点调试,而不需要每次改完都mvn package到外部 Tomcat。做课设时,我一般先用 IDEA 的 Tomcat 模式把功能跑通,最后交付前再用外部 Tomcat 打一次 war,确保换一台电脑也能启动。两步都过了,这份源码才是真的属于你了。

3. 博客系统的数据库设计:四张表、外键和一条 SQL 引发的血案

博客系统的"源码"里,真正值钱的不只是 Java 代码,而是数据库设计。在头歌java web 实训或者课程设计评分表里,数据库合理性永远是硬指标。我见过不少课设翻车,不是 Java 代码跑不动,而是文章列表查询里有人在 for 循环里去查评论数,一次页面刷新发出几十条 SQL,数据库被压垮,答辩演示时当场卡死。所以先立好表结构,后面写代码才顺。

3.1 博客系统 - 数据库设计:用户、文章、评论、分类四张表

博客系统最少需要四张表:用户、分类、文章、评论。先用 MySQL 建库,字符集用utf8mb4,不要用utf8。原因是utf8在 MySQL 里最多只存 3 字节,用户评论一旦带上 emoji 表情,插入时直接报Incorrect string value,用utf8mb4才能存 4 字节的 BMP 外的字符。

CREATE DATABASE blog_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE blog_system; CREATE TABLE `user` ( `id` int NOT NULL AUTO_INCREMENT, `username` varchar(32) NOT NULL, `password` varchar(64) NOT NULL COMMENT '加盐后的密码,绝不明文', `nickname` varchar(32) DEFAULT NULL, `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_username` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `category` ( `id` int NOT NULL AUTO_INCREMENT, `name` varchar(32) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `article` ( `id` int NOT NULL AUTO_INCREMENT, `title` varchar(128) NOT NULL, `content` text NOT NULL, `category_id` int DEFAULT NULL, `user_id` int NOT NULL, `view_count` int NOT NULL DEFAULT 0, `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_category_id` (`category_id`), KEY `idx_user_id` (`user_id`), CONSTRAINT `fk_article_category` FOREIGN KEY (`category_id`) REFERENCES `category` (`id`) ON DELETE SET NULL, CONSTRAINT `fk_article_user` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `comment` ( `id` int NOT NULL AUTO_INCREMENT, `article_id` int NOT NULL, `user_id` int NOT NULL, `content` varchar(1000) NOT NULL, `parent_id` int DEFAULT NULL COMMENT 'NULL 表示顶级评论', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_article_id` (`article_id`), KEY `idx_user_id` (`user_id`), CONSTRAINT `fk_comment_article` FOREIGN KEY (`article_id`) REFERENCES `article` (`id`) ON DELETE CASCADE, CONSTRAINT `fk_comment_user` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

这些建表语句里有几个点值得展开说。用户表里username上加了唯一索引,注册时根据受影响行数就能判断重复用户名,不用先select再insert,能少写一次查询。文章表的category_id允许为 NULL,外键用ON DELETE SET NULL,这样删除分类时文章不会连带被删,只会变成"未分类",更符合博客的语义。评论表的parent_id指向自己的主键,用来做楼中楼回复;article_id用ON DELETE CASCADE,删除文章时评论自动清掉,省得手工delete from comment where article_id=...。

外键在课设阶段一定要建,因为 MyBatis 或 JDBC 删除文章的顺序如果写错,外键能替你兜底,不会出现一堆孤儿评论。等到了企业里高并发场景,会刻意去掉外键用应用层保证一致性,但博客系统这种读写都不大的项目,外键的好处远大于性能损耗。

说回表结构,文章表里没有冗余评论数字段,这是有意为之。如果你在列表页要显示评论数,可以在文章表加一个comment_count int default 0,每次插入评论时update article set comment_count=comment_count+1。但对于课设项目,直接select count(*) from comment where article_id=?完全够用,加冗余字段要处理加锁和一致性问题,反而容易成为答辩论据里的破绽。等数据量上万或你讲了性能优化,再加冗余也不迟。

3.2 从登录到发文章:Servlet/Controller 里的调用路径

表结构清楚后,看 Java 代码就很轻松了。博客系统源码里最重要的闭环是登录和发文章。以纯 Servlet 为例,登录的doPost通常长这样:

@WebServlet("/login") public class LoginServlet extends HttpServlet { private UserService userService = new UserServiceImpl(); 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.getSession().setAttribute("loginUser", user); resp.sendRedirect("index"); } else { req.setAttribute("error", "用户名或密码错误"); req.getRequestDispatcher("login.jsp").forward(req, resp); } } }

这段代码的信息量不少:setCharacterEncoding必须出现在读取任何getParameter之前,否则浏览器用 UTF-8 发来的中文在 Servlet 眼里就已经是乱码;登录成功把用户对象放进session,后续页面判断是否登录就看session.getAttribute("loginUser")是否为空;失败用forward转发回登录页,并带上error属性让 JSP 渲染错误提示。成功用sendRedirect而不是forward,是为了让浏览器地址栏变成/index,刷新页面时不会再次提交登录表单,这是区分重定向和转发的经典场景。

如果你手里的源码是 SSM 结构,同样的逻辑会拆成三层。UserController里用@RequestMapping("/login")接收请求,UserService处理业务,UserMapper接口定义方法,SQL 写在 XML 里。对应的登录查询会是这样:

<select id="selectForLogin" parameterType="map" resultType="com.example.entity.User"> SELECT id, username, nickname FROM user WHERE username = #{username} AND password = #{password} </select>

这里最关键的是#{}预编译占位符。它会把参数作为一个整体传给 PreparedStatement,单引号、双引号都只会被当成数据对待,而不是拼进 SQL 变成代码。如果你的源码里写的是WHERE username = '${username}',百分之一百存在 SQL 注入漏洞。答辩时老师问"你的系统怎么防注入",你答出#{}和${}的区别,比背十条 Spring 面试题都管用。另外注意resultType是com.example.entity.User,MyBatis 会用列名自动映射到实体的同名属性。如果实体里是createTime而表里是create_time,需要在 MyBatis 全局配置里打开mapUnderscoreToCamelCase才能自动转换,这也是很多源码悄悄在mybatis-config.xml里配好的隐藏参数。

如果你是在头歌平台上做过"博客系统之初识 MyBatis"那一节的作业,你应该记得 XML 里的namespace必须和 Mapper 接口全限定名一致,id必须和方法名一致。博客系统源码里如果出现 "Invalid bound statement" 报错,九成是这个对应关系没对上,或者 XML 没有被打包到 class path,这一点在 MyBatis 项目里几乎是固定考点,也值得在你的答辩PPT里点一句。

发文章的核心逻辑集中在insertArticle,大致有三步:先从 session 里拿到当前登录用户,再组装Article对象,最后调用 SQL 插入。这里有个容易踩的坑,就是用户对象存在 session 里的时间过长,昵称改了但 session 里还是旧值。简单做法是每次请求都从 DB 重新查一次当前用户,或者更新资料后主动刷新 session,源码里如果没有这个处理,你可以加两行代码作为自己的改进点。

数据库设计和核心调用看完,你已经知道登录时密码怎么比、文章怎么插、评论怎么挂了。下一章把整个系统部署到 Tomcat 上,让页面真的可以在浏览器里开出来。

4. 把 war 包跑在 Tomcat 上:db.properties、Artifact 和一条启动日志

源码和数据库都准备好了,接下来是新手放弃率最高的一步:本地启动。Java Web 博客系统不是前端项目那样npm install && npm run dev一把梭,它要过数据库连接、Tomcat 部署、依赖打包三道关卡,任何一环配置不对,页面就会给你一个大白板或 500。所以这一章我会把所有关键配置拆开讲,再给出三种部署方式,你任选一种顺着走即可。

4.1 连接 MySQL:db.properties 里的五个必填项

博客系统的数据库配置文件通常叫db.properties或jdbc.properties,放在项目的src/main/resources目录下。用文本编辑器打开后,你会看到类似下面的内容:

jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://127.0.0.1:3306/blog_system?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai jdbc.username=root jdbc.password=123456 jdbc.testWhileIdle=true

第一行是驱动类名,这可能是全项目里最骗人的配置。MySQL 5.x 时代驱动类是com.mysql.jdbc.Driver,MySQL 8.x 换成了com.mysql.cj.jdbc.Driver。源码是别人给的,他可能用的 5.7,而你本机装的是 8.0,这时候你不改这一行,Tomcat 启动时不一定立刻报错,直到第一条 SQL 执行才会抛ClassNotFoundException: com.mysql.jdbc.Driver。我的建议是看本机 MySQL 版本,统一用新版驱动,反正它向下兼容 5.x。

第二行url是重中之重。blog_system要和你刚刚建库的名字一致;characterEncoding=utf8保证 JSP 到数据库之间中文不迷路;useSSL=false是本地开发时关掉 SSL 握手,提高连接速度,同时避免 MySQL 8 多出来的一个 CA 证书提示;serverTimezone=Asia/Shanghai是 MySQL 8 的标准报错解法,不加它启动时多半会报一个 "The server time zone value '???±ê×??±??' is unrecognized"。127.0.0.1比localhost在有些机器上更稳,因为localhost有可能被解析成 IPv6 地址::1,而 MySQL 默认监听的是 IPv4 的 3306。

账号密码那一行就不用多说了,把密码改成你本机真实密码。如果你用的是root并且密码为空,某些连接池日志会直接输出 "Access denied for user 'root'@'localhost' (using password: NO)",这时候不是代码 bug,就是配置和实际不匹配。

有些源码里会把这些属性包在一个连接池工具类里,比如老项目的DBUtil.java里用Class.forName加载驱动,然后DriverManager.getConnection(url, username, password)。如果你看到的是这种写法,恭喜你,没有连接池,这种项目在课设里也常见。它的好处是非常透明,断点一打就能看到 SQL 执行前置;坏处是性能不行,每个请求都要经历getConnection和close。你可以在性能优化的加分项里写一个方案:换成 DBCP 或 Druid。但在跑通阶段,保持原样就好,不要一边调连接池一边排查基础问题,变量越多越难定位。

4.2 从 Maven 构建 war:三种部署玩法与访问路径统一

先讲最稳妥的外部 Tomcat 部署。在 IDEA 右侧的 Maven 面板里,双击Lifecycle下的clean,再双击package。等你看到控制台输出BUILD SUCCESS,去项目根目录的target里找 war 包。如果生成的名字是blog-system-1.0.war,你部署之后访问路径就是/blog-system-1.0,很长且不美观。一般会在pom.xml里固定一下:

<build> <finalName>blog</finalName> </build>

finalName的作用就是把所有构建产物的名字固定成blog,war 包会是blog.war,部署后上下文路径就是/blog。这一步看似不痛不痒,但很多 404 都栽在这:你明明把 war 放进 Tomcat 了,浏览器却凭记忆输了个/blog,而 war 重的是/blog-system-1.0。提前想好路径,后面不用猜。

接下来把 war 复制到 Tomcat 的webapps目录并启动,终端命令如下:

cp target/blog.war /opt/tomcat9/webapps/ cd /opt/tomcat9/bin ./startup.sh tail -f /opt/tomcat9/logs/catalina.out

Windows 上把startup.sh换成startup.bat,没有tail就用文本编辑器直接打开logs/catalina.out看末尾。启动脚本不会自动帮你打开浏览器,看到 "Server startup in [xxx] milliseconds" 就说明 Tomcat 起来了;再等一两秒,日志里会出现 "Deployment of web application archive [blog.war] has finished" 的提示,此时 war 解压完成,可以访问http://localhost:8080/blog/。

如果你不想切到命令行,IDEA 的 Tomcat Server 配置更直观。在Run Configuration里新增一个 Tomcat Server Local,Application server选本机 Tomcat 路径,Deployment页签添加 Artifact,选择blog-system:war exploded。注意 war exploded 和 war 是两种形式:war 是一次打好的包,war exploded 是展开的目录,调试时可以改代码就热更新,适合边改边看。默认访问路径会带_war_exploded,你可以在Application context里改成/blog,这样外部 Tomcat 和 IDEA 模式访问路径一致,后面写跳转不容易错。

启动时如果报端口被占,八成是 8080 被别的东西占了。两个办法:杀掉占用进程,或者改 Tomcat 的conf/server.xml里的<Connector port="8080" .../>。博客系统源码里的重定向地址如果写死了/index,而你换了端口,浏览器访问http://localhost:8888/blog/后重定向到http://localhost:8080/index会打不开。所以建议端口别乱换,除非你对全链路路径已经熟悉了。

最后看一眼日志。Tomcat 的Catalina.out是启动日志,里面如果出现Caused by,那就顺着这个异常往下看,绝大多数问题都能在两三行内定位。如果出现ClassNotFoundException: org.springframework.web.filter.CharacterEncodingFilter,说明项目用了 Spring 的字符过滤器,但依赖没打包进 war;如果是Invalid bound statement (not found): com.example.dao.UserMapper.selectForLogin,说明 MyBatis 的 XML 映射文件没放进target/classes,需要在pom.xml的build/resources里把src/main/java下的 XML 也作为资源一起打包,这个问题在 SSM 项目里很常见,下一章的排查清单里还会再提。

5. 常见问题排查清单:Java Web 博客系统从导入到运行的五次翻车

我把带课设和帮同事踩过的坑按出现频率排序,写成一个可以当 checklist 用的清单。每条都按"现象、原因、解决"三个环节展开。如果你已经跑到部署那一步,建议把这一章当成启动前的预防针,一次看完能省下好几个晚上的搜索时间。

5.1 Maven 依赖一片红:IDEA 里 import 报错,控制台全是 "Cannot resolve symbol"

现象:pom.xml里的坐标没有波浪线,但 Java 文件里import javax.servlet.*、import org.mybatis.*全部标红,Maven 面板 Dependencies 里显示一堆 "missing"。

原因:本地~/.m2/repository里没有这些依赖的缓存,Maven 从中央仓库拉取时网络不稳定,或者坐标版本号写错。还有一种情况是 IDEA 启动时 Maven 配置没有读到你的settings.xml,走了默认中央仓库,下载极慢然后白白超时。

解决:先检查settings.xml的镜像配置在不在、mirrorOf写没写对。然后在 IDEASettings -> Maven里确认User settings file指向的路径,点一下 Maven 面板的刷新按钮。如果还是红,把本地仓库~/.m2/repository下对应的目录删掉,再重新导入,避免下载到一半的损坏 jar 被 IDEA 一直缓存着。还有一招是检查项目 SDK 和 Maven Runner 的 JRE 是否一致,不一致时有些依赖的编译规则会触发奇怪错误,我在 IDEA 2023 版本里就遇到过 JDK 17 + Maven Runner 8 导致 lombok 注解不生效的问题,统一成 17 后立刻解决。Maven 依赖的问题 90% 是网络和缓存,剩下 10% 才是坐标写错,排查时不要先怀疑源码,先看自己的本地环境。

5.2 启动 Tomcat 就 ClassNotFoundException:Servlet API 和第三方 jar 全没进 war

现象:Tomcat 能启动,但访问页面后catalina.out里抛java.lang.ClassNotFoundException: org.apache.jsp.index_jsp,或者NoClassDefFoundError: javax/servlet/ServletException,页面 500。

原因:第一种是javax.servlet-api依赖作用域没写对,被打进了WEB-INF/lib,和 Tomcat 自带的servlet-api.jar冲突;第二种是 Artifact 的 Output Layout 里缺了lib,导致 jar 根本不在 war 包里。这两种问题在 IDEA 里运行时都可能被掩盖,因为 IDEA 用的 classpath 和打包目录是两套逻辑。

解决:打开pom.xml找到javax.servlet-api这一行,把<scope>改成provided,不要让它跟着 war 一起发布。然后在Project Structure -> Artifacts里选中当前 artifact,把Output Layout里的内容清掉,重新从Available Elements里把依赖加到WEB-INF/lib,再点Apply。最后执行mvn clean package,重新生成 war,用压缩软件打开 war 确认WEB-INF/lib里有内容,没有的话就是用 IDEA 的 Artifact 直接部署的,需要重新同步。这个问题一旦解决,后面再遇到 ClassNotFoundException 基本都是漏了某个 jar,先审查打包结构。

5.3 中文全变问号:JSP、Servlet、数据库三层编码不一致

现象:首页正常,但注册用户名带中文后,页面显示????,数据库里的值也是问号;评论区发 emoji 直接报Incorrect string value。

原因:三层编码全部要统一为 UTF-8,缺一层就乱。JSP 里没写pageEncoding,Servlet 没调request.setCharacterEncoding("UTF-8"),数据库表或db.properties用了latin1,只匹配一层就只保证那一环节不乱,最后渲染端一接,就变成问号。

解决:按链路统一。JSP 页面文件头加<%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>;继承HttpServlet的类在doGet和doPost的最开头写死两行,req.setCharacterEncoding("UTF-8")、resp.setCharacterEncoding("UTF-8");db.properties连接串保留useUnicode=true&characterEncoding=utf8;建表语句DEFAULT CHARSET=utf8mb4。如果用的是 Spring MVC,可以配置CharacterEncodingFilter并设置<init-param name="forceEncoding" value="true"/>,这样每个请求都会被强制按 UTF-8 解码。改完这些还要注意项目文件本身的编码,IDEA 右下角如果显示GBK,把File Encoding改成 UTF-8 再重新编译。排查顺序按"页面输出 -> 接收解码 -> 数据库连接 -> 文件编码"来,不要只盯着 JSP 里的一个charset。

5.4 数据库连不上:Access denied / Communications link failure 双连击

现象:Tomcat 启动正常,打开首页报 500,catalina.out里显示Communications link failure或Access denied for user 'root'@'localhost'。

原因:db.properties里的密码、库名、主机端口三个值有一个不对,或者 MySQL 服务本身没启动。如果从别处拷来的源码,配置多半是别人机器的,根本没改。还有 MySQL 8 对时区敏感,旧驱动 + 旧 URL 也会在建立连接后立刻断开。

解决:先做一个排除法。在命令行里执行mysql -u root -p,确认 MySQL 能登录;然后在 MySQL 里执行show databases;,确认blog_system库存在;再执行use blog_system; select count(*) from user;,确认表也有。三层都通以后,回来检查db.properties。如果数据库在另一台机器,127.0.0.1要改成那台机器地址;如果端口不是 3306,改3307等对应端口。最后处理 MySQL 8 的兼容:驱动换成com.mysql.cj.jdbc.Driver,URL 加serverTimezone=Asia/Shanghai&useSSL=false。这里有个细节,MySQL 5.7 的老驱动在 MySQL 8 上执行第一条 SQL 时会抛Access denied for user 'root'@'localhost' (using password: YES),看着像密码错,其实是驱动太旧不认识 8 的认证插件,别被误导,先换驱动。

5.5 部署后 404:上下文路径、Artifact 名和欢迎页三处不一致

现象:Tomcat 日志没有任何 ERROR,浏览器访问http://localhost:8080/blog/永远是 404,但访问http://localhost:8080/blog/login.jsp能打开。

原因:war 文件名和上下文路径不一致,或者web.xml里没有配 welcome-file,或者 JSP 页面的相对路径跳转写乱了。这个问题在 IDEA 部署时尤其常见,因为你可能改了 Artifact 名字,但没有同步改Application context。

解决:先打开 Tomcat 的webapps目录,看解压出来的文件夹叫blog还是blog-system-1.0,路径就是/加文件夹名。如果你在pom.xml里用了<finalName>blog</finalName>,war 名是blog.war,上下文路径就是/blog;如果没有finalName,默认是artifactId-version,那路径就是那个长名字。IDEA 里部署时,在Run Configuration -> Deployment页签里有一个Application context输入框,把它和上下文路径统一即可。再检查web.xml的<welcome-file-list>,确保里面写了index.jsp或一个能跳转到博客首页的 Servlet 路径。纯 Servlet 项目里常见的跳转是/index映射到IndexServlet,那 welcome-file 应该是index而不是index.jsp。如果你确认了这些还 404,打开浏览器的 F12 看请求路径,通常能看到页面里的 JS/CSS 用了相对路径,跳转后丢了一层目录,调整为${pageContext.request.contextPath}/...就能解决。

6. 让博客系统跳出课设味:加分项、验证清单和一个自查习惯

跑通只是及格,答辩和加分都靠你额外做的一两个功能。博客系统最值得加的三个点,是分页、富文本和安全过滤。下面给一套我常用的轻量升级路线。

6.1 给文章列表加分页:手写 LIMIT 比 PageHelper 更好讲

文章列表查询加分页,纯 Servlet 写法是在 Service 里接收pageNum和pageSize,Mapper 里写LIMIT #{offset}, #{size},再单独写一个count查询算总页数。用 MyBatis 的博客系统可以直接引 PageHelper,但答辩时老师大概率会追问插件原理,我建议你先手写 LIMIT,讲清楚后私底下再换插件。原理上都是把分页参数拼进 SQL,唯一要注意的是排序字段不要由前端直接传,否则容易被人注入成任意列。

6.2 把 textarea 换成 Markdown:注意 XSS 过滤

客户体验提升最快的是把后台发文章从textarea改成 Markdown 编辑器。前端找开源的轻量编辑器,在页面上引入 CSS/JS,初始化后拿到 Markdown 原文;后端保存时不要存渲染后的 HTML,永远保留 Markdown 源,展示页面再解析。不管是哪种方案,入库前都要过滤<script>标签,最稳的做法是只允许白名单标签,用过 jsoup 之类库的就知道,直接把content里的script字符串replace掉根本不够,因为<scr<script>ipt>这种绕过很容易写出来。

6.3 冒烟测试清单:交作业前十分钟的自查

用一张表当交付检查单,逐项勾选:

检查项操作预期结果
注册新用户名注册入库成功,密码不是明文
登录正确/错误密码正确跳首页,错误提示"用户名或密码错误"
发布文章新建文章,内容含中文列表出现标题,中文无乱码
评论登录后对文章评论评论出现在文章下方,无 XSS 弹窗
分类删除删除一个分类该分类下文章变为未分类
越权未登录直接访问写作页被重定向到登录页

这张表里的每一项都对应源码里一个功能闭环。跑完一遍再封包,比临时抱佛脚背代码强得多。

我的习惯是每次交付前把自己当成第一次用这个系统的人,从http://localhost:8080/blog/开始,不跳过任何一步,把上面六项重复走一遍。早年有个课设就是因为懒得测 XSS,老师用一个<script>alert(1)</script>让全班哄堂大笑,从那以后我写博客系统都会先给自己造攻击数据。希望这个流程能帮到你,也让你手里的这份源码不再只是"能打开的项目"。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询