简介:这份资源是面向JavaWeb初学者与课程设计学习者的完整项目包,以Servlet、Bootstrap、Ajax与JSP技术栈实现一个简单的个人博客系统,适合正在准备课程设计或希望打通前后端协作流程的开发者参考实践。压缩包共2个文件,包含1个zip项目源码包与1个sql数据库脚本,整体约4.12MB,源码包内按标准JavaWeb目录组织,涵盖Servlet控制器、JSP视图、Bootstrap页面模板及依赖库,sql文件用于快速还原博客文章、用户与评论等数据表结构。项目覆盖用户登录认证、博客发布、Ajax无刷新评论、JDBC增删改查等核心功能,并体现MVC分层思路,可帮助读者理解请求处理、会话管理与动态页面渲染的完整链路。目前已有282人学习,适合作为课程设计参考或JavaWeb入门练手项目。
1. 从一份 blog.zip 说起:Servlet+Bootstrap+Ajax+JSP 的个人博客能跑成什么样
很多人做 JavaWeb 课程设计,卡的不是写不出代码,而是拿到一份 blog.zip 之后不知道从哪下手:导入 IDE 一堆红叉、Tomcat 一启动就 404、数据库连不上、Ajax 提交评论页面直接白屏。这份资源就是一套典型的 Servlet + Bootstrap + Ajax + JSP 个人博客,压缩包里是blog.zip、Blog.zip和blog.sql三样东西,前者是工程源码,后者是建库建表脚本。它解决的是「从零搭一个能登录、能发博文、能评论、能分页展示」的完整闭环,而不是只给你一个登录页就结束。适合正在做课程设计的学生、想复习原生 Servlet 流程的初级开发者,以及需要一份能改造成自己作业底座的从业者。下面我按「先跑起来、再拆结构、最后避坑」的顺序,把这份资源拆开讲。
2. 环境与工程结构:把 blog.zip 导入 IDEA 并跑通 Tomcat
2.1 先确认技术栈版本,别急着点运行
这份工程是原生 Servlet 路线,没有 Spring 那一套,所以对版本反而更敏感。常见做法是 JDK 8 或 11、Tomcat 8.5/9、MySQL 5.7/8.0,IDE 用 IntelliJ IDEA 社区版或旗舰版都行。为什么强调版本?因为 Servlet 4.0 之后web.xml的写法、JSTL 的 jar 包坐标、MySQL 驱动类名(com.mysql.jdbc.Driver还是com.mysql.cj.jdbc.Driver)都会变,版本对不上就是一堆ClassNotFoundException。
导入前先解压,看清楚目录层级。标准 JavaWeb 工程长这样:
blog/ ├── src/ # Java 源码,Servlet、DAO、实体类 ├── web/ 或 webapp/ # 页面根目录 │ ├── WEB-INF/ │ │ ├── web.xml # 部署描述符 │ │ ├── lib/ # 依赖 jar │ │ └── classes/ # 编译输出(IDEA 里通常不手动放) │ ├── css/ js/ img/ # Bootstrap 静态资源 │ └── *.jsp # 视图页面 └── blog.sql # 数据库脚本如果解压出来是Blog.zip套blog.zip的双层结构,先全部解开再导入,别在压缩包里直接开工程,否则 IDEA 识别不到web.xml。
2.2 导入 IDEA 并配置 Artifact 与 Tomcat
IDEA 里走File → Open选中工程根目录,然后按下面几步配:
# 1. 配置 Project Structure → Modules,确认 src 被标记为 Sources # 2. Project Structure → Libraries,把 WEB-INF/lib 下的 jar 全部加入 # 3. Project Structure → Facets,添加 Web Facet,指向 web 目录和 web.xml # 4. Project Structure → Artifacts,新建 Web Application: Exploded # 输出目录默认 out/artifacts/xxx_war_exploded 即可 # 5. Run → Edit Configurations → 新增 Tomcat Server → Local # Deployment 里加入刚才的 Artifact,Application context 设为 /blog这几步的逻辑是:Modules 告诉 IDEA 哪些是源码,Libraries 解决编译期依赖,Facets 让 IDEA 认识这是个 Web 工程,Artifacts 决定打包成什么形态部署,Tomcat 配置决定访问路径。任何一步漏了,表现都是「编译过但访问 404」或者「jar 找不到」。
提示:Application context 设成
/blog后,访问地址就是http://localhost:8080/blog/,别直接访问localhost:8080,那是 Tomcat 默认页。
2.3 建库导数据,改 JDBC 连接参数
blog.sql里一般包含建库、建表、插初始数据三部分。常见做法是先建库再导:
CREATE DATABASE blog DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE blog; -- 然后执行 blog.sql 里的建表与 insert 语句 -- 典型表:user(用户)、article(博文)、comment(评论)导完数据,去源码里找 JDBC 工具类,通常是DBUtil.java或BaseDao.java,把连接串改成自己的:
// 常见写法,注意 MySQL 8 的驱动类名和时区参数 private static final String URL = "jdbc:mysql://localhost:3306/blog?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false"; private static final String USER = "root"; private static final String PASSWORD = "你的密码"; private static final String DRIVER = "com.mysql.cj.jdbc.Driver";参数说明:characterEncoding=utf8防止中文乱码,serverTimezone不加在 MySQL 8 上会报时区异常,useSSL=false避免本地连接时的证书警告。驱动类名如果是 MySQL 5.x,用com.mysql.jdbc.Driver;8.x 用com.mysql.cj.jdbc.Driver,写错就是ClassNotFoundException。
2.4 启动验证:先看首页,再走一遍登录发帖评论
Tomcat 启动后按这个顺序验证,能最快定位问题:
- 访问
http://localhost:8080/blog/,看首页博文列表是否渲染出来,这一步验证 JSP + 数据库查询链路。 - 点登录,用
blog.sql里的初始账号(常见是 admin/123456 之类)登录,验证 Servlet 的doPost和 session 写入。 - 登录后发一篇博文,验证表单提交和 insert。
- 在博文详情页提交一条评论,观察页面是否局部刷新,这一步验证 Ajax 链路。
四步都过,说明工程是完整可跑的;哪一步断,就回到对应章节排查。
3. Servlet 与 JSP 的分工:请求怎么进来,页面怎么出去
3.1 一个请求的完整链路
这份工程走的是经典 MVC:Servlet 当 Controller,JSP 当 View,实体类和 DAO 当 Model。以「查看博文详情」为例,链路是这样的:
浏览器 GET /blog/article?action=detail&id=5 → ArticleServlet.doGet() 接收 → 调 ArticleDao.findById(5) 查数据库 → request.setAttribute("article", article) → request.getRequestDispatcher("/article_detail.jsp").forward(...) → JSP 用 EL 表达式渲染理解这条链路,后面所有功能都是它的变体。登录是UserServlet+user_login.jsp,发帖是ArticleServlet的add分支 + 表单页,评论是CommentServlet+ Ajax。
3.2 Servlet 里 doGet 与 doPost 的取舍
常见做法是让doGet和doPost都转到一个统一的process方法,用参数区分动作:
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { process(req, resp); } protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { process(req, resp); } private void process(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding("UTF-8"); // 必须在取参数前设置 resp.setContentType("text/html;charset=UTF-8"); String action = req.getParameter("action"); if ("detail".equals(action)) { detail(req, resp); } else if ("add".equals(action)) { add(req, resp); } // ... }逻辑说明:setCharacterEncoding一定要在第一次getParameter之前调用,否则 POST 中文参数会乱码,这是血泪经验。action参数是这套工程的路由开关,前端每个链接或表单都带action=xxx,Servlet 里用 if-else 分发。参数说明:req是请求对象,resp是响应对象,forward是服务器内部跳转,URL 不变;sendRedirect是让浏览器重新请求,URL 会变,登录成功后一般用重定向防止表单重复提交。
3.3 JSP 用 EL 和 JSTL 渲染,别写脚本片段
这份工程的 JSP 页面应该尽量用 EL 和 JSTL,而不是<% %>脚本。列表渲染的典型写法:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <c:forEach var="article" items="${articleList}"> <div class="card mb-3"> <div class="card-body"> <h5 class="card-title">${article.title}</h5> <p class="card-text">${article.summary}</p> <a href="article?action=detail&id=${article.id}" class="btn btn-primary">阅读全文</a> </div> </div> </c:forEach>逻辑说明:${articleList}是 Servlet 通过request.setAttribute放进去的集合,c:forEach遍历渲染。参数说明:var是循环变量名,items是集合表达式。用 EL 的好处是页面干净、不用 import Java 类,也避免脚本片段带来的维护问题。如果页面报javax.servlet.jsp.jstl找不到,就是jstl.jar和standard.jar没放进WEB-INF/lib。
3.4 分页查询的 SQL 与参数
博文列表一般要分页,常见做法是用LIMIT:
SELECT id, title, summary, create_time FROM article ORDER BY create_time DESC LIMIT ?, ?;第一个?是偏移量(pageNo - 1) * pageSize,第二个?是每页条数。Servlet 里从req.getParameter("pageNo")取页码,默认 1,算出偏移量传给 DAO。参数说明:pageSize一般设 5 或 10,pageNo要做边界校验,小于 1 归 1,超过总页数归总页数,否则会查出空列表或越界。总页数用SELECT COUNT(*) FROM article单独查一次。
4. Ajax 无刷新评论与 Bootstrap 页面:前端交互怎么接后端
4.1 Ajax 提交评论的完整代码
评论功能是这份工程里 Ajax 的主战场。前端不刷新页面,只把评论插到列表里:
// 提交评论,绑定在按钮点击上 document.getElementById('submitComment').addEventListener('click', function () { var content = document.getElementById('commentContent').value.trim(); var articleId = document.getElementById('articleId').value; if (!content) { alert('评论内容不能为空'); return; } var xhr = new XMLHttpRequest(); xhr.open('POST', 'comment?action=add', true); xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded;charset=UTF-8'); xhr.onreadystatechange = function () { if (xhr.readyState === 4 && xhr.status === 200) { var resp = JSON.parse(xhr.responseText); if (resp.code === 200) { // 局部插入新评论,不刷新整页 var list = document.getElementById('commentList'); var div = document.createElement('div'); div.className = 'border-bottom py-2'; div.innerText = resp.data.content; list.appendChild(div); document.getElementById('commentContent').value = ''; } else { alert(resp.msg); } } }; xhr.send('articleId=' + encodeURIComponent(articleId) + '&content=' + encodeURIComponent(content)); });逻辑说明:open第三个参数true表示异步;setRequestHeader设置表单编码,中文才不会乱;onreadystatechange里readyState === 4 && status === 200才算成功;后端返回 JSON,前端JSON.parse后按code判断。参数说明:encodeURIComponent对参数做 URL 编码,评论里有&、=、中文时必须加,否则后端取到的参数会被截断,这是最常见的翻车点。
4.2 后端 Servlet 返回 JSON 而不是跳转
Ajax 请求的后端不能forward到 JSP,必须写 JSON:
private void addComment(HttpServletRequest req, HttpServletResponse resp) throws IOException { req.setCharacterEncoding("UTF-8"); resp.setContentType("application/json;charset=UTF-8"); int articleId = Integer.parseInt(req.getParameter("articleId")); String content = req.getParameter("content"); Comment c = new Comment(); c.setArticleId(articleId); c.setContent(content); c.setCreateTime(new Date()); boolean ok = commentDao.insert(c); PrintWriter out = resp.getWriter(); if (ok) { out.print("{\"code\":200,\"msg\":\"ok\",\"data\":{\"content\":\"" + content.replace("\"", "\\\"") + "\"}}"); } else { out.print("{\"code\":500,\"msg\":\"评论失败\"}"); } out.flush(); }逻辑说明:setContentType必须是application/json,否则前端JSON.parse会失败;返回的 JSON 字符串里如果 content 含双引号,要转义,否则 JSON 结构被破坏。参数说明:code是业务状态码,200 成功、500 失败,前端据此分支。生产项目一般用 Gson 或 Jackson 序列化,课程设计里手拼字符串也能跑,但要注意转义。
4.3 Bootstrap 布局与响应式栅格
页面用 Bootstrap 的栅格系统,列表页常见结构:
<div class="container mt-4"> <div class="row"> <div class="col-md-8"> <!-- 博文列表 --> </div> <div class="col-md-4"> <!-- 侧边栏:个人信息、分类 --> </div> </div> </div>逻辑说明:container是居中容器,row是行,col-md-8表示中等屏幕以上占 8/12 宽度,小屏幕自动堆叠。参数说明:md是断点,还有sm、lg、xl,数字之和不超过 12。Bootstrap 的 CSS 和 JS 要放在web/css、web/js下并在 JSP 里正确引用,路径写错页面就没样式,看着像「工程坏了」,其实只是静态资源 404。
4.4 个人信息展示页的数据绑定
热词里常出现「jsp 个人信息展示页面」,这份工程里一般对应user_info.jsp。数据由UserServlet查出来后setAttribute("user", user),页面用 EL 绑定:
<div class="card"> <div class="card-body"> <h5 class="card-title">${user.username}</h5> <p class="card-text">邮箱:${user.email}</p> <p class="card-text">注册时间:${user.createTime}</p> </div> </div>逻辑说明:${user.username}等价于user.getUsername(),EL 会自动调用 getter。参数说明:如果页面显示空白,先确认 Servlet 里setAttribute的 key 和 EL 里的名字一致,再确认 user 对象不为 null,最常见的是没登录就访问,session 里没用户。
5. 避坑与排查:这份工程最容易翻车的五个地方
5.1 启动报 404,首页都打不开
现象:Tomcat 启动无报错,访问http://localhost:8080/blog/返回 404。 原因:Artifact 没配置或没部署,或者 Application context 写错,或者web.xml里 welcome-file 没指向首页。 解决:Run Configuration 的 Deployment 里确认 Artifact 已添加;检查web.xml的<welcome-file-list>是否包含index.jsp;确认访问路径带了正确的 context。
5.2 中文乱码,评论和博文全是问号
现象:提交中文后数据库里是???或页面显示乱码。 原因:三个环节任一没设 UTF-8——请求编码、响应编码、数据库连接编码。 解决:Servlet 里req.setCharacterEncoding("UTF-8")放在取参数前;resp.setContentType("text/html;charset=UTF-8");JDBC URL 加characterEncoding=utf8;数据库和表用utf8mb4。四处都对齐才彻底解决。
5.3 Ajax 请求成功但前端报 JSON 解析错误
现象:Network 里看到 200,但控制台Unexpected token或JSON.parse报错。 原因:后端返回的不是纯 JSON,可能混了 HTML 或多余空格,或者 content 里的引号没转义。 解决:后端setContentType("application/json;charset=UTF-8"),返回前确认字符串是合法 JSON;用JSON.parse前先console.log(xhr.responseText)看原始内容;含引号的字段做转义。
5.4 数据库连不上,报 Access denied 或时区异常
现象:启动后第一次查询就抛异常,Access denied for user或The server time zone value ... is unrecognized。 原因:账号密码错、权限不足,或 MySQL 8 没配时区。 解决:确认DBUtil里的账号密码;MySQL 8 的 URL 加serverTimezone=Asia/Shanghai;驱动类名用com.mysql.cj.jdbc.Driver;确认mysql-connector-java的 jar 版本和数据库版本匹配。
5.5 JSTL 标签不生效,页面直接输出${...}
现象:JSP 里${article.title}原样显示,c:forEach不循环。 原因:缺jstl.jar和standard.jar,或者 taglib 的 uri 写错,或者 web.xml 的 Servlet 版本太低不支持 EL。 解决:把两个 jar 放进WEB-INF/lib并加入 IDEA Libraries;确认<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>;web.xml用 Servlet 3.0 以上的 schema。
6. 进阶改造:把课程设计变成能写进简历的项目
跑通只是起点,这份工程真正值钱的地方是它能被改造成一个有亮点的作品。我一般会从三个方向动手,成本低但效果明显。
第一个方向是加密码加密。原始工程多半是明文存密码,改成 MD5 加盐或 BCrypt,登录时比对哈希值。改动集中在UserDao的注册和登录方法,代码量不大,但面试时能讲清楚「为什么不能明文存密码」,这是加分项。
// 用 MessageDigest 做 MD5 加盐,课程设计够用 public static String md5(String password, String salt) throws Exception { MessageDigest md = MessageDigest.getInstance("MD5"); byte[] bytes = md.digest((password + salt).getBytes("UTF-8")); StringBuilder sb = new StringBuilder(); for (byte b : bytes) { sb.append(String.format("%02x", b)); } return sb.toString(); }逻辑说明:把密码和盐拼起来做摘要,盐每个用户随机生成存库,这样即使库泄露也难反推。参数说明:salt建议用 UUID 随机串,长度 16 以上;MD5 本身不安全,生产环境用 BCrypt,课程设计里讲清楚这个演进就够了。
第二个方向是把评论的 Ajax 改成fetch或封装一层,顺便加上「评论后实时更新评论数」。改动点在comment.js和CommentServlet的返回体,返回体里多带一个commentCount,前端更新对应 DOM。这样页面交互更完整,演示时观感好很多。
第三个方向是加一层简单的权限过滤。用Filter拦截/article?action=add这类需要登录的路径,session 里没有 user 就重定向到登录页。这是 Servlet 体系里很典型的知识点,写进简历比「增删改查」有分量。
@WebFilter(urlPatterns = {"/article", "/comment"}) public class AuthFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) resp; String action = request.getParameter("action"); // 只有写操作才校验登录,读操作放行 if (("add".equals(action) || "delete".equals(action)) && request.getSession().getAttribute("user") == null) { response.sendRedirect(request.getContextPath() + "/user?action=loginPage"); return; } chain.doFilter(req, resp); } }逻辑说明:@WebFilter声明拦截路径,doFilter里判断动作和 session,未登录就重定向,否则放行。参数说明:urlPatterns可以写多个路径;request.getContextPath()拿到应用上下文,避免硬编码/blog。注意 Filter 里不要对静态资源也拦截,否则 CSS、JS 全 404。
验证改造是否成功,我习惯用一套固定动作:清空浏览器缓存、退出登录、直接访问发帖 URL,看是否被拦回登录页;登录后再发帖,看是否正常;评论后看评论数是否实时变。这套动作走一遍,基本能覆盖权限和 Ajax 两条链路。
从那以后我每次拿到一份 JavaWeb 课程设计,都强制先跑通「首页 → 登录 → 发帖 → 评论」四步再动代码,因为跳过验证直接改,最后分不清是原工程的坑还是自己改出来的坑。希望帮到你。
本文还有配套的精品资源,点击获取