Java Web博客系统:标准目录结构与安全实现
简介本资源是一份完整的Java Web课程实践报告文档面向高校计算机专业学生及Java初学者聚焦MVC架构下的个人博客系统开发全流程。文档详细阐述了基于SSHStrutsSpringHibernate框架的博客管理系统设计与实现覆盖用户管理、博客增删改查、照片上传、留言交互及Web日志记录等核心功能并深入解析了各层技术选型依据与协同机制辅以MyEclipse开发环境配置、数据库设计说明及实习心得总结。资源为单文件Word文档.doc大小205KB结构清晰、图文结合含需求分析、系统设计、编码实现、测试调试与维护建议等完整环节是理解Java Web工程化开发与框架整合的优质学习范例。目前已有109人学习下载适合用于课程设计参考、毕业设计借鉴或SSH框架入门实战复盘。1. 这不是“写个博客页面”——它是一套可部署、可维护、带权限控制的 Java Web 博客管理闭环很多人看到“个人博客管理系统”第一反应是不就是几个 JSP 页面 Servlet MySQL但真正落地时才发现光是登录态保持、文章草稿自动保存、富文本内容安全过滤、分类与标签的树形关联、后台操作日志审计这五项就足以让一个未经结构化设计的 Java Web 项目在第三天开始出现 session 失效、XSS 漏洞、SQL 注入风险和事务不一致问题。本项目标题中的“基于 Java Web”不是指用 JSP 写个静态页而是以 Servlet 规范为基底、遵循 Java EE或 Jakarta EE分层契约、适配 Tomcat 9 的标准 Web 工程“文档”二字也不是指 Word 报告而是指系统设计说明书、接口契约定义、部署配置清单、数据库 ER 图及字段约束说明——这些才是支撑后续迭代、交接、安全审计和 CI/CD 自动化的核心资产。适合正在准备 Java 面试八股文、需要交付课程设计、或想从零构建一个真实可用 Web 工程的开发者。它不追求炫酷前端但必须能跑通用户注册 → 登录 → 发布带图片的文章 → 后台审核 → 分类归档 → 导出 PDF 文档的全链路。2. 用标准 Java Web 目录结构搭建可调试、可热部署的工程骨架Java Web 项目不是把所有 .java 和 .jsp 塞进一个文件夹就能运行。Tomcat 对 WAR 包结构有严格约定而现代开发中IDE如 IntelliJ IDEA 或 Eclipse对目录识别也依赖标准布局。若目录错位会出现web.xml not found、JSP compilation failed: no servlet context、ClassNotFoundException for com.mysql.cj.jdbc.Driver等典型错误——这些都不是代码逻辑问题而是工程结构失准。2.1 标准 Web 工程目录结构解析对应java web 项目 标准目录结构一个合规的 Java Web 工程必须包含以下层级以 Maven 项目为例blog-system/ ├── pom.xml # Maven 依赖声明含 servlet-api、mysql-connector-java、jstl 等 ├── src/ │ ├── main/ │ │ ├── java/ # Java 源码根目录非 webapp 下 │ │ │ └── com/example/blog/ # 包名需体现业务域避免 default package │ │ │ ├── controller/ # Servlet 入口处理 HTTP 请求如 ArticleServlet.java │ │ │ ├── service/ # 业务逻辑层含事务控制Transactional 注解需 Spring 支持 │ │ │ ├── dao/ # 数据访问层封装 JDBC 或 MyBatis 操作 │ │ │ └── model/ # POJO 实体类Article.java, User.java含 Lombok Data 可简化 │ │ ├── resources/ # 配置文件存放处log4j2.xml, db.properties │ │ └── webapp/ # Web 资源根目录Tomcat 部署后映射为 /context-path/ │ │ ├── WEB-INF/ │ │ │ ├── web.xml # 部署描述符Servlet 3.0 可用注解替代但课程报告建议保留 │ │ │ └── lib/ # 手动添加的 jarMaven 会自动复制到此无需手动放 │ │ ├── static/ # 静态资源CSS/JS/IMG不经过 Servlet 处理推荐 │ │ └── jsp/ # JSP 页面login.jsp, admin/article-list.jsp │ └── test/ # 单元测试JUnit 5 Mockito └── target/ # Maven 编译输出目录生成 blog-system.war提示webapp必须是src/main/的同级目录不能放在src/main/java下WEB-INF是受保护目录外部无法直接访问其中的web.xml或lib这是安全设计前提。2.2web.xml中的关键配置项Servlet 3.0 兼容写法即使使用注解web.xml仍需声明核心组件以满足课程报告规范性要求?xml version1.0 encodingUTF-8? web-app xmlnshttp://xmlns.jcp.org/xml/ns/javaee xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd version4.0 !-- 应用名称影响上下文路径 -- display-namePersonal Blog Management System/display-name !-- 字符编码过滤器解决 POST 中文乱码 -- filter filter-nameCharacterEncodingFilter/filter-name filter-classorg.apache.catalina.filters.SetCharacterEncodingFilter/filter-class init-param param-nameencoding/param-name param-valueUTF-8/param-value /init-param init-param param-nameforceEncoding/param-name param-valuetrue/param-value /init-param /filter filter-mapping filter-nameCharacterEncodingFilter/filter-name url-pattern/*/url-pattern /filter-mapping !-- 默认欢迎页 -- welcome-file-list welcome-fileindex.jsp/welcome-file /welcome-file-list !-- 错误页配置提升用户体验 -- error-page error-code404/error-code location/error/404.jsp/location /error-page error-page error-code500/error-code location/error/500.jsp/location /error-page /web-app该配置确保① 所有请求强制 UTF-8 编码② 访问根路径自动跳转index.jsp③ 404/500 错误定向到友好页面。若省略filter-mapping表单提交中文将显示为????若未设welcome-file-list访问http://localhost:8080/blog-system/将返回 404。2.3 Maven 依赖关键项pom.xml 片段课程项目应控制依赖数量聚焦核心能力dependencies !-- Servlet APIprovided不打包进 WAR -- dependency groupIdjakarta.servlet/groupId artifactIdjakarta.servlet-api/artifactId version5.0.0/version scopeprovided/scope /dependency !-- JSTLJSP 标签库用于循环、判断 -- dependency groupIdjakarta.servlet.jsp.jstl/groupId artifactIdjakarta.servlet.jsp.jstl-api/artifactId version2.0.0/version /dependency dependency groupIdorg.glassfish.web/groupId artifactIdjakarta.servlet.jsp.jstl/artifactId version2.0.0/version /dependency !-- MySQL 驱动8.0 版本 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency !-- 日志Log4j2比 logback 更易配置 -- dependency groupIdorg.apache.logging.log4j/groupId artifactIdlog4j-core/artifactId version2.20.0/version /dependency dependency groupIdorg.apache.logging.log4j/groupId artifactIdlog4j-slf4j2-impl/artifactId version2.20.0/version /dependency /dependencies注意jakarta.servlet-api的scope必须为provided否则部署时会与 Tomcat 自带的 servlet-api 冲突报LinkageErrormysql-connector-java若用 5.x 版本连接 MySQL 8.0会因默认认证插件不兼容而报Access denied for user。3. 实现用户登录与文章管理的最小可行 Servlet 流程“个人博客管理系统”的核心交互是用户身份验证与内容 CRUD。用纯 Servlet JSP 实现不引入 Spring 框架既符合课程要求又能暴露底层机制。关键在于① Session 状态管理② 表单数据安全绑定③ SQL 参数化防注入④ JSP 与 JavaBean 的标准协作。3.1 用户登录 Servlet从 request 获取参数到 session 存储用户信息LoginServlet.java示例位于src/main/java/com/example/blog/controller/package com.example.blog.controller; import com.example.blog.dao.UserDao; import com.example.blog.model.User; 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 javax.servlet.http.HttpSession; import java.io.IOException; WebServlet(/login) public class LoginServlet extends HttpServlet { private final UserDao userDao new UserDao(); // DAO 层实例实际应通过工厂或 DI 管理 Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 1. 获取表单参数nameusername, namepassword String username req.getParameter(username); String password req.getParameter(password); // 2. 参数校验空值检查 if (username null || username.trim().isEmpty() || password null || password.trim().isEmpty()) { req.setAttribute(error, 用户名或密码不能为空); req.getRequestDispatcher(/login.jsp).forward(req, resp); return; } // 3. 查询数据库DAO 层已做 PreparedStatement 防注入 User user userDao.findByUsernameAndPassword(username, password); // 4. 登录成功存入 session并重定向到后台首页 if (user ! null) { HttpSession session req.getSession(true); // true 表示创建新 session session.setAttribute(currentUser, user); // 关键将 User 对象存入 session session.setMaxInactiveInterval(1800); // 设置 session 过期时间30分钟 resp.sendRedirect(req.getContextPath() /admin/index.jsp); } else { req.setAttribute(error, 用户名或密码错误); req.getRequestDispatcher(/login.jsp).forward(req, resp); } } Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // GET 请求显示登录页 req.getRequestDispatcher(/login.jsp).forward(req, resp); } }逻辑说明req.getParameter()获取 HTML 表单字段不进行任何自动类型转换或 trim需手动判空userDao.findByUsernameAndPassword()内部必须使用PreparedStatement例如SELECT * FROM users WHERE username ? AND password ?杜绝字符串拼接 SQLreq.getSession(true)创建 sessionsetAttribute(currentUser, user)是后续所有页面获取登录用户的基础sendRedirect()使用绝对路径req.getContextPath()获取应用上下文如/blog-system避免相对路径跳转失败。3.2 文章列表 JSP用 JSTL 标签安全展示数据并支持分页admin/article-list.jsp示例位于src/main/webapp/admin/% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % % taglib prefixfmt urihttp://java.sun.com/jsp/jstl/fmt % html head title文章管理/title link relstylesheet href${pageContext.request.contextPath}/static/css/admin.css /head body jsp:useBean idarticleList classjava.util.ArrayList scoperequest/ c:if test${empty articleList} p暂无文章/p /c:if c:forEach items${articleList} vararticle div classarticle-item h3${article.title}/h3 psmall作者c:out value${article.author}/ | 发布时间fmt:formatDate value${article.publishTime} patternyyyy-MM-dd HH:mm//small/p p${fn:substring(article.content, 0, 100)}.../p a href${pageContext.request.contextPath}/admin/edit-article.jsp?id${article.id}编辑/a | a href${pageContext.request.contextPath}/delete-article?id${article.id} onclickreturn confirm(确定删除)删除/a /div /c:forEach !-- 分页导航假设 page1, totalPage5 -- div classpagination c:forEach begin1 end${totalPage} vari a href${pageContext.request.contextPath}/admin/article-list.jsp?page${i} class${param.page i ? active : }${i}/a /c:forEach /div /body /html参数说明c:out value${...}/自动 HTML 转义防止 XSS如用户输入scriptalert(1)/script会被渲染为纯文本${fn:substring(..., 0, 100)}截取摘要避免长内容撑爆页面fn是 JSTL 函数库需在web.xml中声明或使用taglibonclickreturn confirm(...)是客户端确认不能替代服务端权限校验删除操作必须在 Servlet 中验证当前 session 用户是否有权操作该文章分页链接中的?page${i}是 GET 参数需在ArticleListServlet中解析并传入 DAO 查询LIMIT offset, size。3.3 数据库建表语句MySQL 8.0blog_system.sql需在src/main/resources/下提供-- 用户表 CREATE TABLE users ( id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL COMMENT BCrypt 加密后的密码, email VARCHAR(100), role ENUM(USER, ADMIN) DEFAULT USER, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_0900_ai_ci; -- 文章表 CREATE TABLE articles ( id BIGINT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(200) NOT NULL, content LONGTEXT NOT NULL COMMENT HTML 内容需过滤 script 标签, author_id BIGINT NOT NULL, category_id BIGINT, status ENUM(DRAFT, PUBLISHED, REJECTED) DEFAULT DRAFT, publish_time DATETIME, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (author_id) REFERENCES users(id) ON DELETE CASCADE, INDEX idx_author_status (author_id, status), INDEX idx_publish_time (publish_time) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_0900_ai_ci; -- 分类表支持多级分类parent_id 为 0 表示一级分类 CREATE TABLE categories ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL, parent_id BIGINT DEFAULT 0, sort_order INT DEFAULT 0, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_0900_ai_ci;关键设计点users.password字段长度设为 100为 BCrypt 加密后的哈希值预留空间$2a$10$...长度约 60articles.content用LONGTEXT存储富文本 HTML但必须在 Service 层调用 Jsoup 清洗移除script、onerror等危险属性articles.status枚举值明确区分草稿、已发布、被拒避免用is_published布尔值导致状态歧义外键ON DELETE CASCADE保证用户删除时其文章自动清理避免孤儿数据。4. 文档结构化解析与导出从 JSP 生成 PDF 报告的技术实现课程报告要求的.doc文档本质是结构化内容的呈现载体。与其手动维护 Word不如在系统中内置“导出为 PDF”功能——这既是技术亮点也是文档自动化实践。Java 生态中iText 7是最成熟、社区最活跃的 PDF 生成库支持 HTML 转 PDF、表格嵌套、中文宋体/黑体渲染且无商业授权限制AGPLv3课程项目完全适用。4.1 使用 iText 7 将文章内容转为 PDF含中文支持ExportPdfServlet.java核心逻辑package com.example.blog.controller; import com.itextpdf.kernel.font.PdfFont; import com.itextpdf.kernel.font.PdfFontFactory; import com.itextpdf.kernel.pdf.PdfDocument; import com.itextpdf.kernel.pdf.PdfWriter; import com.itextpdf.layout.Document; import com.itextpdf.layout.element.*; import com.itextpdf.layout.properties.HorizontalAlignment; import com.itextpdf.layout.properties.TextAlignment; import com.itextpdf.layout.properties.VerticalAlignment; 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.ByteArrayOutputStream; import java.io.IOException; import java.net.URLEncoder; WebServlet(/export-pdf) public class ExportPdfServlet extends HttpServlet { Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Long articleId Long.parseLong(req.getParameter(id)); // 1. 查询文章此处省略 DAO 调用 // Article article articleService.findById(articleId); // 2. 构建 PDF 字节流 ByteArrayOutputStream baos new ByteArrayOutputStream(); PdfWriter writer new PdfWriter(baos); PdfDocument pdfDoc new PdfDocument(writer); Document document new Document(pdfDoc); // 3. 加载中文字体需提前准备 simsun.ttc 或 NotoSansCJKsc-Regular.otf String fontPath getServletContext().getRealPath(/static/fonts/NotoSansCJKsc-Regular.otf); PdfFont chineseFont PdfFontFactory.createFont(fontPath, Identity-H); // 4. 添加标题居中、加粗、16号 Paragraph title new Paragraph(个人博客系统 - 文章导出报告) .setFont(chineseFont).setFontSize(16f) .setTextAlignment(TextAlignment.CENTER) .setHorizontalAlignment(HorizontalAlignment.CENTER); document.add(title); // 5. 添加文章信息表格 Table table new Table(2).useAllAvailableWidth(); table.addCell(new Cell().add(new Paragraph(标题).setFont(chineseFont)).setVerticalAlignment(VerticalAlignment.MIDDLE)); table.addCell(new Cell().add(new Paragraph(《我的 Java Web 学习笔记》).setFont(chineseFont))); table.addCell(new Cell().add(new Paragraph(作者).setFont(chineseFont))); table.addCell(new Cell().add(new Paragraph(张三).setFont(chineseFont))); document.add(table); // 6. 添加正文需对 HTML 内容做基础清洗移除 script 标签 String cleanContent p这是文章正文的第一段。/pp第二段包含strong加粗文本/strong和a href#超链接/a。/p; ListHTMLWorker workers new ArrayList(); workers.add(new HTMLWorker(document, chineseFont)); // 自定义 HTML 解析器 // 此处省略 HTML 解析细节实际需用 Jsoup 预处理 // 7. 设置响应头触发浏览器下载 resp.setContentType(application/pdf); String fileName URLEncoder.encode(博客文章_ System.currentTimeMillis() .pdf, UTF-8); resp.setHeader(Content-Disposition, attachment; filename fileName); resp.getOutputStream().write(baos.toByteArray()); document.close(); } }关键步骤说明PdfFontFactory.createFont(..., Identity-H)是中文显示核心Identity-H表示水平书写、支持 UnicodeURLEncoder.encode(..., UTF-8)处理文件名中文避免 Chrome 下下载文件名乱码Content-Disposition: attachment强制下载而非内嵌预览符合“导出”语义实际项目中cleanContent应来自Jsoup.parse(html).select(script, style).remove().body().html()确保无 XSS 风险。4.2 文档结构化解析为课程报告生成可检索的 XML/JSON 元数据课程报告不仅要有功能还要有“文档意识”。系统应能自动生成结构化元数据供教师快速验收。例如导出system-metadata.json{ projectName: 基于Java Web的个人博客管理系统, version: 1.0.0, buildTime: 2024-06-15T14:22:3308:00, techStack: { backend: [Servlet 4.0, JSP 2.3, JSTL 2.0], database: MySQL 8.0, buildTool: Maven 3.8.6, deployment: Apache Tomcat 10.1 }, coreFeatures: [ {name: 用户登录认证, status: completed, testCases: 3}, {name: 文章CRUD, status: completed, testCases: 5}, {name: PDF导出, status: completed, testCases: 2} ], deploymentSteps: [ 1. 将 target/blog-system.war 复制到 Tomcat webapps 目录, 2. 启动 Tomcatbin/startup.sh 或 startup.bat, 3. 访问 http://localhost:8080/blog-system/ ] }生成方式在ServletContextListener中监听应用启动读取pom.xml版本、System.currentTimeMillis()、硬编码功能列表序列化为 JSON 写入WEB-INF/classes/metadata.json。教师只需打开该文件即可确认项目完整性。5. Tomcat 部署排错与性能调优的 3 个必查项本地开发环境能跑通不等于部署后稳定。学生项目常见问题集中在 Tomcat 配置、JVM 参数、静态资源缓存三方面。以下为高频故障的定位路径与修复命令。5.1web.xml not found或Failed to start component的根因排查现象Tomcat 启动日志出现SEVERE: Error starting static Resources或Caused by: java.lang.IllegalArgumentException: The main resource set specified [...] is not valid。检查顺序与命令确认 WAR 包结构是否合规Linux/macOS 终端unzip -l target/blog-system.war | head -20输出应包含WEB-INF/web.xml、WEB-INF/lib/、jsp/等路径。若缺失WEB-INF说明 Maven 插件未正确打包。验证web.xml是否符合 DTD/XSD 打开target/blog-system/WEB-INF/web.xml检查首行是否为?xml version1.0 encodingUTF-8? web-app xmlnshttp://xmlns.jcp.org/xml/ns/javaee ...若仍用旧版http://java.sun.com/xml/ns/j2eeTomcat 10 将拒绝加载。检查context.xml中的 docBase 路径conf/Catalina/localhost/blog-system.xmlContext docBase/path/to/blog-system.war reloadabletrue/docBase必须是绝对路径且文件存在。若用相对路径Tomcat 会尝试在webapps/下查找导致404。5.2 JSP 编译失败查看work/Catalina/localhost/下的 Java 源码Tomcat 将 JSP 编译为 Java 类存于work/Catalina/localhost/blog-system/org/apache/jsp/。当页面报An error occurred at line: [15] in the generated java file直接打开对应.java文件如login_jsp.java定位第 15 行——通常是 EL 表达式语法错误如${user.name}但user未在 request 中 setAttribute或 JSTL 标签未声明。快速验证命令# 查看编译日志 tail -f logs/catalina.out | grep -i jsp compile # 清理 JSP 缓存重启前执行 rm -rf work/Catalina/localhost/blog-system/5.3 数据库连接池配置避免Connection refused与连接泄漏课程项目常直接new Connection()但高并发下必然崩溃。应在META-INF/context.xml中配置 JNDI 数据源?xml version1.0 encodingUTF-8? Context Resource namejdbc/BlogDB authContainer typejavax.sql.DataSource factoryorg.apache.tomcat.jdbc.pool.DataSourceFactory driverClassNamecom.mysql.cj.jdbc.Driver urljdbc:mysql://localhost:3306/blog_system?serverTimezoneAsia/Shanghaiamp;useSSLfalse usernameroot password123456 maxActive20 minIdle5 maxWait10000 validationQuerySELECT 1 testOnBorrowtrue/ /Context然后在web.xml中声明资源引用resource-ref descriptionBlog Database Connection/description res-ref-namejdbc/BlogDB/res-ref-name res-typejavax.sql.DataSource/res-type res-authContainer/res-auth /resource-refJava 代码中获取连接Context initCtx new InitialContext(); Context envCtx (Context) initCtx.lookup(java:comp/env); DataSource ds (DataSource) envCtx.lookup(jdbc/BlogDB); Connection conn ds.getConnection(); // 自动从连接池获取注意maxActive20是关键阈值若并发请求超过 20后续请求将阻塞等待validationQuerySELECT 1确保连接有效性避免 MySQL 8 小时超时断连。本文还有配套的精品资源点击获取