
简介这是一套基于JavaWeb的文章管理系统完整源码与数据库面向计算机相关专业学生及企业开发者可用于课程设计、毕业设计、大作业或初期项目立项演示。系统区分用户与管理员两种登录角色支持用户发布新文章、查看文章详情、修改文章以及文章的删除与恢复功能链路完整适合作为JavaWeb入门到进阶的实战练习素材。压缩包共396个文件约7.32MB包含15个java源文件、12个jsp页面、27个html页面以及大量js、css、png、gif等前端资源另有26个jar依赖、1个sql数据库脚本和若干xml配置前后端与数据库结构一目了然。目前已有153人学习下载代码经过测试运行成功、功能正常。读者可借此掌握Servlet、JSP、DAO分层与数据库交互的完整实现思路并参考其目录组织与增删改查逻辑快速搭建自己的管理系统项目。1. 从一份 JavaWeb 文章管理系统源码说起登录、发文、删除与恢复到底怎么串起来很多人拿到「基于JavaWeb的文章管理系统源码数据库」这类压缩包第一反应是解压、找 SQL、丢进 IDE 跑起来结果卡在数据库连不上、登录 500、文章删了不知道怎么恢复。这个标题背后其实是一套非常典型的 JavaWeb 项目完整案例用户和管理员双角色登录、用户发布新文章、文章详情查看、文章删除与恢复配上 MySQL 数据库增删改查。它适合两类人——正在做 Java 课程设计案例源码的学生以及想用一个小系统把 JSP/Servlet 或 Spring Boot MyBatis 链路跑通的初学者。核心难点不在写页面而在角色权限怎么隔离、删除为什么必须做成软删除、恢复时怎么保证数据不串。下面按「先跑通、再拆解、后避坑」的顺序把这份源码类项目从环境到参数讲透。2. 环境与数据库先落地把源码跑起来的最小闭环2.1 技术栈选型JSPServlet 还是 Spring Boot拿到源码先别急着改代码先确认它属于哪一套技术栈因为这决定了你后面所有配置的写法。常见的 JavaWeb 文章管理系统源码有两类一类是黑马程序员 JavaWeb 笔记里那种原生 JSP Servlet JDBC MySQL另一类是 Spring Boot MyBatis Thymeleaf 或前后端分离。两者在目录结构上差别很大原生项目通常有webapp/WEB-INF/web.xml、src下按dao/service/servlet分包Spring Boot 项目则是src/main/resources/application.yml加controller/service/mapper。选型理由很直接如果你只是交课程设计、想看清请求从浏览器到数据库的完整链路原生 JSPServlet 更透明每个HttpServlet的doGet/doPost都能打断点如果你要顺带学 MyBatis 和依赖注入Spring Boot 版本更贴近企业写法。我一般建议先按源码原本的栈跑通不要一上来就重构否则登录都进不去就开始改架构纯属给自己找麻烦。判断方法看pom.xml里有没有spring-boot-starter-web有就是 Spring Boot只有javax.servlet-api、jsp-api、mysql-connector-java就是原生。确认后再决定用 Tomcat 还是内置容器。2.2 建库建表用户表、文章表与软删除字段数据库是这套系统的地基。标题里明确有「用户和管理员登录」「文章删除与恢复」所以至少两张核心表用户表和文章表。删除与恢复能不能做取决于文章表有没有状态字段这是很多人忽略的点。-- 用户表用 role 区分普通用户和管理员 CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, -- 存 MD5 或 BCrypt不要明文 role TINYINT NOT NULL DEFAULT 0, -- 0普通用户, 1管理员 create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 文章表is_deleted 是删除与恢复的关键 CREATE TABLE article ( id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(200) NOT NULL, content TEXT, author_id INT NOT NULL, is_deleted TINYINT NOT NULL DEFAULT 0, -- 0正常, 1已删除 create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX idx_author (author_id), INDEX idx_deleted (is_deleted) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;逻辑说明user.role承担权限隔离登录后把 role 放进 Session后续管理员才能看到「恢复」入口。article.is_deleted是软删除标记删除时只把 0 改成 1恢复时改回 0这样数据永远不丢也就有了「后悔药」。参数上utf8mb4保证中文和 emoji 不乱码idx_deleted让列表查询过滤已删除文章时走索引数据量上千后差别明显。注意如果源码里的 SQL 用的是utf8导入后中文可能变问号统一改成utf8mb4再建库。2.3 连接配置与 IDEA 运行 JavaWeb 项目数据库建好后改连接配置。原生项目一般在src下有个db.properties或c3p0-config.xmlSpring Boot 项目在application.yml。MySQL 8 的驱动类名和 URL 跟 5.x 不同这是登录失败的高频原因。# db.properties原生 JSPServlet 项目常见写法 jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/article_db?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/ShanghaiuseSSLfalse jdbc.usernameroot jdbc.password你的密码参数说明com.mysql.cj.jdbc.Driver是 MySQL 8 的驱动写老的com.mysql.jdbc.Driver会报驱动加载失败serverTimezoneAsia/Shanghai不加会抛时区异常useSSLfalse避免本地自签证书告警。IDEA 里运行原生项目需要在 Project Structure 里配好 Artifacts再挂到 TomcatSpring Boot 直接跑main方法即可。跑通后先访问登录页能出页面说明容器和静态资源没问题再测登录接口。3. 登录与权限用户和管理员两条线怎么隔离3.1 登录流程从表单到 Session 的完整链路登录是这套系统的入口也是「登录失败」这类热搜词最集中的地方。完整链路是登录页表单 POST 到LoginServlet或 Controller后端按用户名查用户比对密码成功则把用户对象写进HttpSession再按 role 跳不同首页。// LoginServlet 核心逻辑原生 Servlet 写法 protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String username req.getParameter(username); String password req.getParameter(password); // 密码统一加密后再比对避免明文 String encrypted MD5Util.md5(password); User user userService.login(username, encrypted); if (user null) { req.setAttribute(msg, 用户名或密码错误); req.getRequestDispatcher(/login.jsp).forward(req, resp); return; } HttpSession session req.getSession(); session.setAttribute(loginUser, user); // 后续鉴权全靠它 if (user.getRole() 1) { resp.sendRedirect(req.getContextPath() /admin/index); } else { resp.sendRedirect(req.getContextPath() /user/index); } }逻辑说明session.setAttribute(loginUser, user)是整条权限链的锚点后面所有需要登录的页面都从 Session 取它。参数上密码必须先加密再和库里比对库里存什么、代码就比什么两边算法要一致否则永远登录失败。跳转用sendRedirect而不是forward避免刷新时重复提交表单。3.2 权限隔离过滤器拦住未登录和越权访问只做登录不做拦截用户直接敲管理员 URL 就能进后台这是课程设计里最常见的漏洞。正确做法是加一个Filter统一校验 Session 和角色。WebFilter(urlPatterns {/admin/*, /user/*}) 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; HttpSession session request.getSession(false); User user session null ? null : (User) session.getAttribute(loginUser); if (user null) { response.sendRedirect(request.getContextPath() /login.jsp); return; } // 管理员路径只允许 role1 String uri request.getRequestURI(); if (uri.contains(/admin/) user.getRole() ! 1) { response.sendRedirect(request.getContextPath() /user/index); return; } chain.doFilter(req, resp); } }逻辑说明getSession(false)表示没有 Session 就不新建避免给未登录用户凭空造一个。urlPatterns覆盖/admin/*和/user/*把鉴权收口到一处比在每个 Servlet 里写 if 靠谱得多。参数上管理员路径判断用uri.contains(/admin/)如果你的上下文路径里也含 admin要改成startsWith(contextPath /admin/)更严谨。提示Session 默认 30 分钟过期测试删除恢复时如果中途掉登录先看是不是 Session 超时别急着怀疑代码。4. 文章发布、详情与软删除恢复增删改查的落地细节4.1 发布新文章表单到入库的字段校验发布文章看着简单坑在字段校验和作者绑定。标题、内容不能为空作者 ID 必须从 Session 取绝不能信前端传的 author_id否则用户能冒充别人发文。// ArticleServlet 发布逻辑 protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String title req.getParameter(title); String content req.getParameter(content); if (title null || title.trim().isEmpty()) { req.setAttribute(msg, 标题不能为空); req.getRequestDispatcher(/user/publish.jsp).forward(req, resp); return; } User loginUser (User) req.getSession().getAttribute(loginUser); Article article new Article(); article.setTitle(title.trim()); article.setContent(content); article.setAuthorId(loginUser.getId()); // 作者来自 Session安全 articleService.save(article); resp.sendRedirect(req.getContextPath() /user/myArticles); }逻辑说明作者 ID 从 Session 取是安全底线前端传的一律不信任。参数上title.trim()去掉首尾空格避免「空标题」绕过校验。入库时is_deleted默认 0create_time交给数据库默认值代码不用手动塞时间减少时区不一致问题。4.2 文章详情查看按 ID 查询与已删除拦截详情页按文章 ID 查询但必须加一个判断已删除的文章不能通过直接改 URL 访问到否则软删除形同虚设。protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String idStr req.getParameter(id); if (idStr null || !idStr.matches(\\d)) { resp.sendError(404); return; } Article article articleService.getById(Integer.parseInt(idStr)); // 已删除的文章普通用户不可见 if (article null || article.getIsDeleted() 1) { resp.sendError(404); return; } req.setAttribute(article, article); req.getRequestDispatcher(/articleDetail.jsp).forward(req, resp); }逻辑说明idStr.matches(\\d)防止非数字参数导致NumberFormatException。已删除文章返回 404 而不是提示「已删除」避免泄露数据存在性。参数上管理员如果需要查看已删除文章用于恢复要单独开一个/admin/deleted列表接口不要放宽这个详情接口。4.3 删除与恢复软删除的 SQL 与状态流转删除与恢复是标题里最有辨识度的功能本质是同一条记录is_deleted字段在 0 和 1 之间切换。删除不是DELETE FROM恢复也不是从备份里捞。-- 删除标记为已删除 UPDATE article SET is_deleted 1 WHERE id #{id} AND author_id #{authorId}; -- 恢复改回正常状态 UPDATE article SET is_deleted 0 WHERE id #{id} AND author_id #{authorId}; -- 用户文章列表只查未删除 SELECT * FROM article WHERE author_id #{authorId} AND is_deleted 0 ORDER BY create_time DESC; -- 回收站列表只查已删除 SELECT * FROM article WHERE author_id #{authorId} AND is_deleted 1 ORDER BY update_time DESC;逻辑说明删除和恢复的 WHERE 都带上author_id保证用户只能操作自己的文章这是越权防护的关键。参数上列表查询必须带is_deleted条件否则回收站和正常列表会互相污染。恢复用update_time排序能看出最近删的是哪篇。管理员如果要有权恢复任意文章另写一条不带 author_id 的 SQL并在 Service 层校验 role。注意软删除会让表越来越大长期运行要定期归档is_deleted1且超过半年的数据别让回收站变成垃圾场。5. 避坑与排查登录失败、乱码、恢复失效的 5 个真实记录5.1 登录一直失败日志报驱动或时区异常现象点登录直接 500控制台抛ClassNotFoundException或The server time zone value is unrecognized。原因MySQL 8 驱动类名写成了老的com.mysql.jdbc.Driver或者 JDBC URL 没带serverTimezone。解决驱动改成com.mysql.cj.jdbc.DriverURL 补上serverTimezoneAsia/Shanghai并把mysql-connector-java依赖升到 8.x。5.2 中文标题入库变问号现象发布文章后详情页标题显示成???。原因数据库、表、连接三处字符集不一致常见是建表用了utf8而连接串没指定。解决库表统一utf8mb4JDBC URL 加characterEncodingutf8mb4JSP 页面顶部pageEncodingUTF-8三处对齐才彻底。5.3 删除后文章还在列表里现象点了删除提示成功但列表还能看到。原因列表 SQL 没加is_deleted 0或者删除 SQL 实际执行的是DELETE但页面缓存了。解决检查列表查询条件确认删除走的是UPDATE ... SET is_deleted 1浏览器强刷一次排除缓存干扰。5.4 恢复按钮点了没反应现象回收站里点恢复页面刷新后文章还在回收站。原因恢复 SQL 的 WHERE 条件带了is_deleted 1之外还误带了别的限制或者author_id对不上导致更新 0 行。解决先打印受影响行数executeUpdate返回 0 就说明条件没匹配上核对当前登录用户 ID 和文章 author_id。5.5 普通用户能删别人的文章现象改 URL 里的文章 id普通用户把别人的文章删了。原因删除接口只按 id 更新没校验归属。解决所有写操作 SQL 都带上author_id 当前登录用户管理员操作单独走带 role 校验的接口别图省事共用一条 SQL。6. 进阶把删除恢复做成可验证的回收站并守住数据边界跑通基础功能后真正拉开差距的是把「删除与恢复」做成一个可验证、可追溯的回收站而不是两个孤立的按钮。我一般会加三样东西删除时间、操作人、恢复次数。给文章表补一个delete_time DATETIME删除时写入当前时间恢复时置空这样回收站能按删除时间倒序用户一眼看到最近删了什么。ALTER TABLE article ADD COLUMN delete_time DATETIME NULL; -- 删除时 UPDATE article SET is_deleted 1, delete_time NOW() WHERE id #{id} AND author_id #{authorId}; -- 恢复时 UPDATE article SET is_deleted 0, delete_time NULL WHERE id #{id} AND author_id #{authorId};验证方法很土但有效准备两个账号 A 和 BA 发三篇文章删两篇回收站应显示两篇B 登录后回收站应为空且直接访问 A 的文章详情 URL 应返回 404。再让 A 恢复一篇正常列表应重新出现回收站剩一篇。这套用例跑一遍权限和状态流转基本就稳了。参数边界上回收站列表一定要分页LIMIT配合is_deleted索引别一次查全表。管理员恢复他人文章时Service 层先查 role再执行不带 author_id 的更新两条路径分开写别用同一个方法靠参数区分否则迟早出越权。我自己踩过最深的坑是早期图省事把删除写成物理DELETE结果用户误删后找不回来只能翻数据库备份那次之后所有涉及用户数据的删除我一律先问一句「要不要留后悔药」。软删除不是万能但它给了你一次纠错机会。希望帮到你。本文还有配套的精品资源点击获取