简介:本资源是一套完整的JavaWeb课程设计级个人博客系统实战项目,面向计算机类专业在校学生、初学者及教学人员,解决课程设计、期末大作业与项目实践选题难、环境搭建复杂、功能实现不完整等痛点。压缩包共128个文件,含17个核心Java源码(如Login.class、addArticle.class、getArticle.class等)、13个CSS与12个JS前端资源、12个映射配置文件(.map)、4个运行依赖JAR包、1个SQL建表脚本及1份答辩报告PDF,辅以大量截图(40张JPG)与文档说明(README.md、MD格式),整体约12.38MB,结构清晰便于分层学习。已有561人下载学习,项目经实际部署测试,所有功能模块均运行稳定,答辩平均分达96分,可直接用于课程展示、课设提交或毕设基础框架;小白可依文档快速启动,进阶者亦可基于现有MVC结构拓展评论、搜索、权限管理等功能。
1. 为什么一个“JavaWeb个人博客系统”能成为期末大作业的硬通货?——它不是Demo,而是你工程能力的实体快照
你手头这份《JavaWeb课程设计/期末大作业-个人博客系统+源代码+文档说明+sql+报告pdf》,表面看是学生交差用的压缩包,实则是高校JavaWeb教学链条里最真实、最密集的工程切片:它必须同时承载Servlet/JSP基础、MySQL建模、JDBC或轻量ORM(如DBUtils)、前端三件套(HTML/CSS/JS+少量jQuery)、MVC分层意识、文件上传(头像/文章配图)、用户登录态管理(Session)、SQL脚本可重放性、以及一份能经得起答辩追问的技术报告。这不是写个HelloWorld就能糊弄过去的场景——当老师问“你这个分页怎么防SQL注入?”“登录失败三次后怎么锁账户?”“文章草稿箱的数据怎么和正式发布表隔离?”,答案全藏在你跑起来的那几行<c:forEach>和PreparedStatement里。它适合两类人:一是刚学完Servlet生命周期但还没串起完整请求链路的大二学生,需要一个有血有肉、能调试、能改、能讲清楚每层职责的参照系;二是想快速搭建教学案例的助教,需要一套开箱即用、结构清晰、注释到位、SQL可一键导入、报告框架已预留填空位的交付物。别被“个人博客”四个字骗了——它本质是Web工程最小可行闭环:用户输入 → 后端处理 → 数据落库 → 页面渲染 → 状态反馈。下面,我们就从零开始,把它真正跑通、调透、讲明白。
2. 从解压到运行:用IDEA跑通JavaWeb博客系统的最小必要路径
2.1 环境准备:JDK、Tomcat、MySQL版本对齐是第一道生死线
很多同学卡在第一步:解压后双击index.jsp看到404,或者IDEA里右键Run As→Server报错“No Tomcat installation found”。这不是你的问题,是环境没对齐。这个项目大概率基于Java 8 + Tomcat 8.5 + MySQL 5.7(这是高校实验室最普遍的组合),强行升级到JDK 17或Tomcat 10会直接翻车。确认方式很简单:打开项目根目录下的pom.xml(如果有)或WEB-INF/web.xml,看<web-app>标签的version属性——如果是3.0或3.1,对应Tomcat 7+/8+;如果是4.0,才需要Tomcat 9+。MySQL同理,看SQL脚本里有没有ENGINE=InnoDB DEFAULT CHARSET=utf8mb4这种写法,有就是5.7+,没有utf8mb4而只有utf8,大概率是5.6。我的做法是:
- JDK:装JDK 8u202(官网存档版),环境变量
JAVA_HOME指向它; - Tomcat:下载apache-tomcat-8.5.90.zip,解压后不要放在中文路径或带空格路径(如
C:\Program Files\); - MySQL:用MySQL 5.7.32(免安装版),初始化时明确指定字符集为
utf8mb4,命令如下:
# 初始化MySQL(管理员权限运行cmd) mysqld --initialize-insecure --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci # 启动服务 net start mysql提示:如果用Navicat连接不上,先检查MySQL服务是否启动(
services.msc里找MySQL80或MySQL57),再确认my.ini里[client]和[mysqld]段都写了default-character-set=utf8mb4。
2.2 IDEA配置:不是“Add Framework Support”,而是手动搭骨架
很多教程说“右键项目→Add Framework Support→Web Application”,这会导致IDEA自动生成web.xml和lib目录,反而和你项目原有的结构冲突。正确做法是:
- File → New → Project from Existing Sources,选中你解压后的文件夹;
- 在向导里取消勾选“Create project from template”,直接进入模块配置;
- 到“Project SDK”页,选择你装好的JDK 8;
- 关键一步:在“Additional Libraries and Frameworks”页,只勾选“Web Application”并点“Configure…”,弹出窗口里,“Web application directory”手动指向你项目里的
WebContent或src/main/webapp(看项目实际结构),不要勾选“Create web.xml”(因为项目里已有现成的); - 完成后,右键项目→Open Module Settings → Artifacts,点击“+”→“Web Application: Archive”,Name填
blog.war,Output Directory选out/artifacts/,然后在左侧“Available Elements”里把WebContent(或webapp)拖到右侧“WEB-INF”下,再把WEB-INF/lib下的jar包全拖进去。
这样配置后,IDEA才真正理解:“这是一个传统JavaWeb项目,不是Spring Boot,它的入口是web.xml里定义的Servlet,静态资源在WebContent下,class文件要打到WEB-INF/classes”。
2.3 运行前必做三件事:SQL导入、数据库连接、编码统一
项目给的xxx.sql脚本不能直接双击运行!必须用MySQL命令行或Navicat执行,且注意顺序:
- 先创建数据库:
CREATE DATABASE blog_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; - 再执行SQL脚本:在Navicat里右键数据库→“运行SQL文件”,选中
xxx.sql,务必勾选“使用UTF8MB4编码”; - 检查
WEB-INF/web.xml里是否有数据库配置?没有的话,去src/com/util/DBUtil.java(或类似路径)里改url、username、password。典型错误是URL写成jdbc:mysql://localhost:3306/blog?useSSL=false,但MySQL 5.7默认requireSSL,得改成jdbc:mysql://localhost:3306/blog?useSSL=false&serverTimezone=Asia/Shanghai。
最后,统一编码:
- IDEA右下角点击“UTF-8”,选“Convert to UTF-8”(对所有
.java和.jsp); web.xml顶部声明<?xml version="1.0" encoding="UTF-8"?>;- JSP页面顶部加
<%@ page contentType="text/html;charset=UTF-8" language="java" %>; - Tomcat的
conf/server.xml里,在<Connector>标签加URIEncoding="UTF-8"。
做完这三步,右键index.jsp→“Run ‘index.jsp’ on Server”,看到首页加载成功,才算真正跨过第一道门槛。
3. 代码结构拆解:看懂这5个包,你就掌握了JavaWeb MVC的骨架
3.1com.entity:POJO不是摆设,字段命名暴露设计功底
这个包里通常是User.java、Article.java、Comment.java等类。重点看三点:
- 字段类型是否匹配数据库?比如MySQL里
create_time是datetime,Java里必须是java.util.Date或LocalDateTime(若用JDK8+),不能是String; - getter/setter是否完整?尤其
User类里password字段,有些项目为了安全会故意不生成setter,靠构造器注入,这是好习惯; - 是否加了
@Override toString()?答辩时老师常问“你怎么调试对象值?”,有toString()就能直接System.out.println(user)看全字段。
常见坑:Article.java里有个status字段,数据库是tinyint(1)(0草稿/1发布),但Java里定义成int,导致MyBatis查询时自动转成Integer,而JSP里用<c:if test="${article.status == 1}">判断失败——因为Integer对象比较要用equals(),==比的是引用。解决方案:要么Java里用byte status,要么JSP里用${article.status eq 1}(EL表达式支持eq)。
3.2com.dao与com.service:为什么不用Hibernate/MyBatis,而坚持JDBC+DBUtils?
项目给的源码大概率用DBUtils(Apache Commons DbUtils),而不是主流ORM。这不是技术落后,而是教学考量:
DBUtils封装了Connection获取、PreparedStatement预编译、ResultSet遍历,但不隐藏SQL,学生必须亲手写SELECT * FROM user WHERE username = ?,理解参数占位符意义;UserDao.java里login(String username, String password)方法,核心是QueryRunner.query(conn, sql, new BeanHandler<>(User.class), username, password),这里new BeanHandler<>(User.class)会自动把结果集映射到User对象,但前提是数据库字段名和Java属性名严格一致(如user_name→userName,需靠BasicRowProcessor自定义转换)。
com.service层是业务逻辑中枢。比如ArticleService.java里publishArticle(Article article)方法,通常包含:
- 校验标题非空、内容长度>10字;
- 调用
articleDao.add(article)插入数据库; - 如果有图片上传,调用
FileUtil.saveImage(article.getImage())保存到WebContent/upload/; - 返回
Result对象(含success=true和message="发布成功")。
注意:
service层绝不直接操作request或response,那是Servlet的事。如果看到service里有request.setAttribute(),说明架构混乱,要重构。
3.3com.servlet:Servlet不是万能胶,每个类只干一件事
LoginServlet.java、PublishServlet.java、ListArticleServlet.java——这些类名已经说明一切。关键看doPost()里三板斧:
- 取参:
String username = request.getParameter("username");,注意getParameter()只能取表单数据,JSON要用getInputStream(); - 调service:
UserService userService = new UserService(); User user = userService.login(username, password);; - 跳转/响应:登录成功
request.getSession().setAttribute("user", user); response.sendRedirect("main.jsp");,失败request.setAttribute("error", "密码错误"); request.getRequestDispatcher("login.jsp").forward(request, response);。
最易错的是转发(forward)和重定向(redirect)混用:
forward是服务器内部跳转,URL不变,request域对象可传递;redirect是发302给浏览器,URL变,request域对象丢失,必须用session或URL传参。
比如登录后forward到main.jsp,main.jsp里能用${sessionScope.user.username}取值;但如果redirect过去,就得session.setAttribute("user", user)再取。
3.4com.filter:登录拦截器不是炫技,是安全底线
LoginFilter.java实现Filter接口,doFilter()里核心逻辑:
HttpSession session = request.getSession(false); if (session == null || session.getAttribute("user") == null) { // 放行登录相关URL if (request.getRequestURI().endsWith("login.jsp") || request.getRequestURI().endsWith("LoginServlet")) { chain.doFilter(request, response); } else { response.sendRedirect("login.jsp"); } } else { chain.doFilter(request, response); }这里有两个细节决定成败:
getSession(false)不创建新session,避免未登录用户也生成session ID;- 白名单URL必须包含
.jsp和Servlet,否则login.jsp自己会被拦截,死循环重定向。
提示:
web.xml里<filter-mapping>的<url-pattern>写/*会拦截所有请求,包括/css/style.css,导致样式丢失。更稳妥的是<url-pattern>/user/*</url-pattern>,把受保护资源统一放到/user/路径下。
4. 避坑指南:那些让答辩老师皱眉、让你熬夜到凌晨的5个经典翻车现场
4.1 现象:首页显示乱码,中文变成“???”;原因:Tomcat、MySQL、IDEA、JSP四端编码不一致;解决:
- Tomcat:
conf/server.xml中<Connector>加URIEncoding="UTF-8"; - MySQL:
my.ini中[client]和[mysqld]段加default-character-set=utf8mb4; - IDEA:File→Settings→Editor→File Encodings,全局编码、项目编码、属性文件编码全设为
UTF-8; - JSP:页面顶部加
<%@ page contentType="text/html;charset=UTF-8" %>,且<head>里加<meta charset="UTF-8">。
血泪经验:只改其中三处,第四处不改,依然乱码。必须四端同步。
4.2 现象:登录后session.getAttribute("user")返回null;原因:web.xml里<session-config>的<cookie-http-only>设为true,但前端AJAX登录未处理Cookie;解决:
- 方案一(推荐):在
web.xml里注释掉<http-only>配置,或设为false; - 方案二:前端登录成功后,用
document.cookie手动设置JSESSIONID(不推荐,破坏同源策略)。
根本原因是:HttpOnlyCookie无法被JS读取,AJAX请求不会自动携带,导致后续请求无Session ID。
4.3 现象:SQL脚本执行报错“Unknown character set: 'utf8mb4'”;原因:MySQL版本低于5.5.3,不支持utf8mb4;解决:
- 降级SQL脚本:用文本编辑器全局替换
utf8mb4为utf8,COLLATE utf8mb4_unicode_ci改为COLLATE utf8_general_ci; - 或升级MySQL到5.7+(实验室环境通常不允许)。
注意:utf8在MySQL里实际是utf8mb3,不支持emoji,但教学项目够用。
4.4 现象:图片上传后路径正确,但网页显示404;原因:<img src="upload/xxx.jpg">中的upload文件夹不在WebContent下,而在项目根目录;解决:
- 将
upload文件夹复制到WebContent目录内(不是src下); - 或修改
FileUtil.java里的保存路径:String path = request.getServletContext().getRealPath("/upload/") + fileName;,确保getRealPath返回的是Web应用根目录下的upload。
玄学提示:Tomcat热部署时,
WebContent/upload可能被清空,建议在web.xml里加<init-param>配置上传路径为绝对路径,如D:/blog_upload/。
4.5 现象:分页功能点击第2页,URL变成list.jsp?page=2,但数据还是第1页;原因:ListArticleServlet里没从URL参数取page,而是固定查第1页;解决:
- 在Servlet中加
String pageStr = request.getParameter("page"); int page = StringUtils.isEmpty(pageStr) ? 1 : Integer.parseInt(pageStr);; - 调用
articleService.listByPage(page, pageSize)时传入正确page; - JSP里生成页码链接时,用
<a href="list.jsp?page=${p}">${p}</a>,而非<a href="list.jsp">2</a>。
常见误用:把分页逻辑写在DAO层,导致SQL里LIMIT #{offset}, #{size}的offset计算错误,应由Service层根据page和pageSize算出offset = (page-1)*pageSize。
5. 报告PDF与文档说明:如何把代码写成让老师眼前一亮的技术叙事
5.1 报告结构不是八股文,而是你思考过程的镜像
拿到的报告.pdf模板,别只填空。我一般按这四块重写:
- 需求分析:不写“用户需要登录”,写“为防止未授权访问文章后台,系统采用Session+Filter双重校验,登录态有效期设为30分钟(见
web.xml中<session-timeout>)”; - 数据库设计:贴ER图(用draw.io画),重点标出外键关系——比如
comment表的article_id指向article.id,user_id指向user.id,并在文字里说明“一对多关系通过外键约束保证数据一致性”; - 核心功能实现:挑1个难点深挖。比如“文章富文本编辑”,不写“用了KindEditor”,写“为解决XSS风险,后端在
PublishServlet中对content字段执行Jsoup.clean(content, Whitelist.relaxed()),白名单允许<p><br><img>标签及src属性,过滤onerror等危险事件”; - 测试用例:列3个真实测试场景,如“输入用户名admin、密码123456,预期跳转至main.jsp,实际截图见附录图3”。
提示:报告里所有截图,必须是你本地运行的真实界面,URL栏要显示
localhost:8080/blog/,不能是网上盗图。老师一眼能识破。
5.2 文档说明.md:让助教/接棒者3分钟看懂项目脉络
文档说明.md不是README.md的翻译。我把它拆成:
- 快速启动:3行命令搞定,如
1. 导入SQL到blog_db库 2. IDEA配置Tomcat 8.5 3. Run index.jsp; - 目录树:用
tree /f命令生成,标注关键文件,如:src/ ├── com/entity/User.java # 用户实体,含password加密逻辑(见line 45) ├── com/dao/UserDao.java # 使用DBUtils,SQL语句见line 22 ├── com/servlet/LoginServlet.java # 登录拦截在Filter,此处只处理业务 └── util/DBUtil.java # 单例模式,连接池大小=5(可调) WebContent/ ├── upload/ # 图片上传目录,需手动创建 ├── js/jquery.min.js # 版本1.12.4,因IE8兼容性要求 - 配置项清单:表格形式,避免遗漏:
| 配置文件 | 关键配置项 | 默认值 | 修改建议 |
|---|---|---|---|
DBUtil.java | url | jdbc:mysql://localhost:3306/blog | 若MySQL端口非3306,需修改 |
web.xml | <session-timeout> | 30 | 测试时可设为1快速验证超时逻辑 |
LoginFilter.java | 白名单URL | login.jsp,LoginServlet | 如新增注册页,需在此添加register.jsp |
5.3 SQL脚本优化:从“能跑”到“可维护”的3个动作
原始SQL脚本往往是一次性产物。我必做三件事:
- 补全建表语句的ENGINE和CHARSET:
CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL, `password` varchar(100) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; - 为关键字段加注释:
ALTER TABLE `article` MODIFY COLUMN `status` tinyint(1) COMMENT '0-草稿,1-已发布,2-已删除'; - 分离初始化数据:单独建
init_data.sql,只插admin用户和1条测试文章,避免每次重装库都刷一堆假数据。
最后,把所有SQL文件按功能归类:schema.sql(建库建表)、init_data.sql(初始数据)、alter_v1.1.sql(后续字段变更),这样下次迭代时,别人一眼知道该跑哪个脚本。
6. 进阶技巧:用这个博客系统练出企业级开发手感的3个实战动作
6.1 给登录加图形验证码:不是为了炫技,而是理解前后端状态协同
很多项目缺验证码,不是不会,是怕麻烦。其实就3步:
- 后端生成:在
VerifyCodeServlet.java里,用BufferedImage画图,随机字符存入session.setAttribute("verifyCode", code); - 前端展示:
<img src="VerifyCodeServlet" onclick="this.src='VerifyCodeServlet?'+Math.random()">; - 登录校验:
LoginServlet里取request.getParameter("verifyCode")和session.getAttribute("verifyCode")比对,比对后立即session.removeAttribute("verifyCode"),防止重放。
关键细节:VerifyCodeServlet的doGet()里必须加response.setContentType("image/png"),否则浏览器当文本解析。这个小功能,逼你搞懂Session生命周期、HTTP响应头、前端事件绑定——全是面试高频题。
6.2 用Log4j2替代System.out.println:日志不是装饰,是线上排障的后悔药
把所有System.out.println("用户登录成功")替换成:
private static final Logger logger = LogManager.getLogger(LoginServlet.class); // ... logger.info("用户{}登录成功,IP:{}", username, request.getRemoteAddr());然后在src/main/resources/log4j2.xml里配置:
<Appenders> <File name="FileAppender" fileName="logs/blog.log"> <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/> </File> </Appenders> <Loggers> <Root level="info"> <AppenderRef ref="FileAppender"/> </Root> </Loggers>这样,所有日志自动写入logs/blog.log,比控制台输出可靠一万倍。某次线上NullPointerException,我就是靠日志里23:45:21 [http-nio-8080-exec-3] ERROR com.servlet.PublishServlet - 文章标题为空这行,5分钟定位到前端没校验标题。
6.3 用Git管理源代码:不是交作业,而是建立工程师的版本直觉
别再交zip包了。初始化Git仓库后,立刻做三件事:
- 写
.gitignore:排除target/、logs/、WebContent/upload/、.idea/、*.iml,只留源码、SQL、文档; - 分阶段提交:
git commit -m "feat: 初始化项目结构,含entity/dao/service/servlet"git commit -m "feat: 实现用户登录注册,含Filter拦截"git commit -m "fix: 修复分页参数未传递bug"
- 打Tag:
git tag -a v1.0 -m "课程设计终版,含完整报告PDF",这样回溯时git checkout v1.0就能回到交作业那一刻。
我的习惯:每次改完一个功能,先
git status看哪些文件变了,再git diff确认没误删,最后git add . && git commit。这30秒,省下未来3小时debug时间。希望帮到你。
本文还有配套的精品资源,点击获取