
简介这份资源是面向JavaWeb初学者与课程设计学习者的完整项目包围绕Servlet、Bootstrap、Ajax与JSP技术栈实现一个功能简洁的个人博客系统帮助读者理解前后端交互、MVC分层与数据库操作在实际项目中的落地方式。压缩包共2个文件包含1个zip工程包与1个sql数据库脚本整体约4.12MBzip内为可导入IDE的完整项目源码sql文件用于快速建库建表省去手动设计表结构的步骤。项目覆盖用户登录认证、博客发布、评论管理、分页展示等典型模块Servlet承担控制层请求分发JSP结合EL与JSTL完成视图渲染Ajax实现无刷新评论交互Bootstrap负责响应式页面布局JDBC完成MySQL的CRUD操作目录结构遵循WEB-INF、src、lib等标准规范。目前已有282人学习适合作为课程设计参考、JavaWeb入门练手或答辩演示素材帮助读者快速跑通一个可运行的小型博客系统并理解各层职责划分。1. 从零搭一个 Javaweb 个人博客为什么我劝你先跑通 Servlet 再碰框架很多人做 Javaweb 课程设计第一反应是上网找一个完整案例把源码拖进 IDEA 就点运行结果控制台一片红Tomcat 起不来数据库连不上最后连“个人博客”四个字都没在浏览器里看见。这个标题拆开看是四个技术点Servlet 做后端控制、JSP 做页面渲染、Bootstrap 做样式、Ajax 做局部刷新组合起来刚好是一个能写能读能评论的简单个人博客。它适合两类人一是正在做课程设计、需要一套能讲清楚原理又能跑起来的方案二是刚学完 Servlet 想找个完整项目练手的人。我自己的习惯是先把 Servlet 的请求转发跑通再往上叠 JSP 和 Bootstrap最后用 Ajax 把体验拉起来顺序反了就容易翻车。下面按这个顺序把每一步的命令、参数和踩坑点讲清楚。2. Servlet 与 JSP 的分工谁管请求、谁管渲染2.1 为什么不让 JSP 直接连数据库刚接触 Javaweb 的人容易把 Java 代码全写进 JSP 的% %里连数据库、查列表、拼 HTML 一条龙。这样做在课程设计里能跑但一旦要加评论、改密码、做分页页面会变成几百行的“意大利面”改一处崩三处。常见做法是让 Servlet 只做三件事接收参数、调用业务方法、把结果放进 request 或 session然后转发给 JSP。JSP 只负责把数据取出来渲染成 HTML不碰 JDBC。这样分工之后Ajax 请求也能直接打到 Servlet 上返回 JSON 而不是整页 HTML前端局部刷新才有意义。我一般会先建一个BlogServlet用WebServlet(/blog)注解映射路径在doGet里根据action参数决定是查列表还是查详情。这样不用为每个功能建一个 Servlet课程设计里够用也方便讲清楚“一个入口多个分支”的思路。2.2 用 IDEA 建项目并跑通第一个 Servlet在 IDEA 里新建项目时选 Java Enterprise勾选 Web Application注意 Application server 选本地 Tomcat不要选成 Jakarta EE 版本导致javax.servlet包找不到。建好之后在src下建包com.blog.servlet写一个最简单的HelloServletpackage com.blog.servlet; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; // 映射路径为 /hello浏览器访问 http://localhost:8080/项目名/hello 即可 WebServlet(/hello) public class HelloServlet extends HttpServlet { Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 设置响应类型和编码避免中文乱码 resp.setContentType(text/html;charsetUTF-8); resp.getWriter().write(Servlet 跑通了); } }这段代码的关键在WebServlet(/hello)和resp.setContentType。前者告诉 Tomcat 这个类处理哪个路径后者决定浏览器用什么编码解析。很多人只写resp.setCharacterEncoding(UTF-8)却忘了setContentType结果中文还是问号。参数上/hello前面必须带斜杠否则启动时报映射错误。跑通之后把web.xml里的metadata-complete保持默认 false让注解生效不要手动再配一遍 servlet 映射否则会冲突。2.3 JSP 里怎么安全地取 Servlet 传过来的数据Servlet 查完数据后用req.setAttribute(blogs, list)存进去再req.getRequestDispatcher(/index.jsp).forward(req, resp)转发。JSP 里用 JSTL 的c:forEach遍历不要用% %脚本段。如果项目里没有 JSTL 依赖需要在pom.xml或WEB-INF/lib下加jstl-1.2.jar和standard-1.1.2.jar否则c:forEach标签会被当成普通文本输出。取值的写法是${blog.title}注意 EL 表达式在 JSP 2.0 以上默认开启如果页面顶部写了isELIgnoredtrue就会原样输出这个坑我踩过不止一次。3. Bootstrap 与 Ajax 的配合把整页刷新改成局部更新3.1 Bootstrap 引入方式和栅格布局的取舍Bootstrap 用 CDN 引入最省事但课程设计答辩时如果断网就全乱。我一般把bootstrap.min.css和bootstrap.bundle.min.js下载到webapp/static下用相对路径引入。个人博客首页通常是“左侧文章列表 右侧个人信息卡”用container包一层里面row分col-md-8和col-md-4。注意 Bootstrap 5 去掉了 jQuery 依赖如果网上找的模板是 Bootstrap 3 的写法navbar-toggle和glyphicon在 5 里已经没了直接混用会出现图标不显示、导航栏展不开的问题。选一个版本就统一到底别一半 3 一半 5。3.2 用 Ajax 请求 Servlet 并渲染文章列表Ajax 的核心是XMLHttpRequest或fetch课程设计里用fetch更简洁。假设 Servlet 返回 JSON 数组前端这样写// 请求 /blog?actionlist拿到 JSON 后动态生成卡片 fetch(blog?actionlist) .then(response { // 必须检查响应状态404 或 500 时 response.json() 会抛异常 if (!response.ok) throw new Error(请求失败 response.status); return response.json(); }) .then(data { const container document.getElementById(blogList); container.innerHTML ; // 清空旧内容避免重复追加 data.forEach(blog { const card document.createElement(div); card.className card mb-3; card.innerHTML div classcard-body h5 classcard-title${blog.title}/h5 p classcard-text${blog.summary}/p /div; container.appendChild(card); }); }) .catch(err console.error(加载博客列表出错, err));这里的关键参数是actionlistServlet 里用req.getParameter(action)判断分支。返回 JSON 时后端要设置resp.setContentType(application/json;charsetUTF-8)然后用ObjectMapper或手动拼字符串写回。手动拼字符串容易在标题带引号时把 JSON 结构破坏掉建议用 Jackson 或 Gson。前端fetch默认不带 cookie如果登录态存在 session 里需要加credentials: same-origin否则后端拿不到 session会一直返回未登录。3.3 Ajax 请求编码格式和参数传递的细节热搜里有人问“ajax请求设置编码格式”这个问题在 POST 请求里特别常见。用fetch发 POST 时如果 body 是JSON.stringify(data)必须加headers: {Content-Type: application/json}后端用req.getReader()读流再解析。如果用的是传统application/x-www-form-urlencoded后端直接req.getParameter就能取到但中文参数需要在 Servlet 里先req.setCharacterEncoding(UTF-8)而且这行必须写在第一次getParameter之前写在后面无效。GET 请求的中文参数则由 Tomcat 的URIEncoding决定Tomcat 8 以上默认 UTF-8一般不用改。给 Ajax 请求参数赋值时注意URLSearchParams会自动做 URL 编码手动拼?title我的博客在部分浏览器里会乱码。4. 数据库与后端从建表到分页查询的完整链路4.1 博客表结构设计和 JDBC 工具类个人博客最少需要三张表用户表、文章表、评论表。文章表里title、content、create_time、user_id是必备字段summary可以冗余存一份摘要避免列表页每次截取 content 前 100 字。建表语句用utf8mb4字符集否则 emoji 存进去会报错。JDBC 工具类我一般写成静态方法getConnection()从db.properties读 url、username、password不要硬编码在 Java 里。连接池在课程设计里不是必须但用Druid或HikariCP能避免频繁创建连接导致的响应慢配置也就多一个dataSource初始化。4.2 分页查询的 SQL 和参数计算列表页不分页文章一多就卡。MySQL 用LIMIT offset, sizeoffset (pageNum - 1) * pageSize。Servlet 里接收page参数默认 1pageSize固定 5 或 10。查询总数用SELECT COUNT(*)总页数用Math.ceil(total * 1.0 / pageSize)。注意LIMIT后面不能直接跟计算表达式要在 Java 里算好再拼进 SQL或者用PreparedStatement的setInt传参。如果排序字段是create_time记得加索引否则数据量上千后翻页会明显变慢。4.3 用 Servlet 返回 JSON 的完整写法// 在 doGet 中根据 action 分支处理 String action req.getParameter(action); if (list.equals(action)) { int page Integer.parseInt(req.getParameter(page) null ? 1 : req.getParameter(page)); ListBlog blogs blogService.findByPage(page, 5); resp.setContentType(application/json;charsetUTF-8); // 用 Gson 把 list 转成 JSON 字符串 String json new Gson().toJson(blogs); resp.getWriter().write(json); }这段代码里Integer.parseInt如果参数不是数字会抛NumberFormatException前端传参前要校验后端也可以 try-catch 后返回默认值。Gson需要引入gson-2.8.9.jar如果实体类里有Date类型默认会转成复杂对象建议在 Gson 构造时设置setDateFormat(yyyy-MM-dd HH:mm)。返回 JSON 后前端fetch拿到的就是数组直接遍历渲染。5. 避坑与排查课程设计里最容易翻车的 5 个点5.1 现象Tomcat 启动报 ClassNotFoundException: javax.servlet.http.HttpServlet原因IDEA 项目里没有把 Tomcat 的servlet-api.jar加入依赖或者 Maven 里servlet-api的 scope 写成了compile导致打包冲突。解决在 Project Structure 的 Modules 里确认 Tomcat 库已添加Maven 项目把 scope 改成provided重新构建。5.2 现象Ajax 请求返回 404但浏览器直接访问 URL 能打开原因fetch里的路径写的是相对路径而当前页面 URL 带了额外的路径层级导致拼接后不对。解决用${pageContext.request.contextPath}/blog?actionlist在 JSP 里生成绝对路径或者在前端用new URL(blog, window.location.origin /项目名/)拼。注意不要硬编码项目名改项目名后全崩。5.3 现象JSP 页面中文乱码Servlet 里设了 UTF-8 也没用原因JSP 文件本身保存的编码不是 UTF-8或者页面指令% page contentTypetext/html;charsetUTF-8 %写成了ISO-8859-1。解决在 IDEA 设置里把 File Encodings 全部改成 UTF-8JSP 顶部加pageEncodingUTF-8同时确认 Tomcat 的server.xml里 Connector 没有写死URIEncodingISO-8859-1。5.4 现象Bootstrap 样式不生效控制台报 404 找不到 css 文件原因静态资源放在WEB-INF下浏览器无法直接访问。解决把static目录放在webapp根目录下不要放进WEB-INF。如果用了 SpringMVC 的 DispatcherServlet 拦截/还需要配default-servlet-handler纯 Servlet 项目没这个问题。5.5 现象Ajax 请求成功但页面不刷新数据没显示原因fetch是异步的渲染代码写在fetch外面执行顺序错了。解决把渲染逻辑放进.then()回调里或者用async/await写法。另外检查container的 id 是否和 HTML 里一致innerHTML赋值前是否清空否则旧数据会叠加。6. 进阶技巧用 Filter 统一编码和登录拦截课程设计答辩时老师常问“如果用户没登录直接访问发文页面怎么办”。这时候加一个 Filter 比在每个 Servlet 里写判断优雅得多。EncodingFilter统一设置req.setCharacterEncoding(UTF-8)和resp.setContentTypeLoginFilter检查 session 里有没有user没有就重定向到登录页。Filter 的WebFilter(/*)会拦截所有请求注意放行登录页、注册页和静态资源否则 CSS 和图片也会被拦页面直接裸奔。WebFilter(/*) public class LoginFilter implements Filter { Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; String uri request.getRequestURI(); // 放行登录、注册、静态资源和登录接口 if (uri.contains(/login) || uri.contains(/register) || uri.contains(/static/) || uri.contains(/blog?actionlist)) { chain.doFilter(req, resp); return; } Object user request.getSession().getAttribute(user); if (user null) { response.sendRedirect(request.getContextPath() /login.jsp); } else { chain.doFilter(req, resp); } } }这段代码里uri.contains的判断顺序要注意/blog?actionlist这种带参数的 URI 在getRequestURI()里不包含查询字符串所以实际判断要用getServletPath()加getParameter或者干脆把列表接口路径改成/blog/list。我自己的习惯是接口路径和页面路径分开/api/开头的走 JSON/page/开头的走 JSPFilter 里只拦/page/下的发文和编辑页面。这样逻辑清晰也不容易误伤。验证 Filter 是否生效可以在chain.doFilter前后各打一行日志看请求有没有进到 Servlet。如果登录后仍然被拦检查 session 的JSESSIONID有没有在 Ajax 请求里带上fetch默认同源会带跨域则需要credentials: include且后端设置Access-Control-Allow-Credentials。课程设计里一般同源不用折腾跨域。最后说个我自己的习惯每加一个新功能先关掉所有样式和脚本用最裸的 HTML 表单提交跑通后端再叠 Ajax 和 Bootstrap。这样出问题时能立刻判断是后端没返回还是前端没渲染省掉大量“玄学”排查时间。希望帮到你。本文还有配套的精品资源点击获取