资讯动态

JavaWeb文章管理系统源码解析:Servlet+JSP+逻辑删除实战

发布时间:2026/10/18 12:58:14 来源:尧图企业网站定制
简介基于JavaWeb的文章管理系统源码与数据库面向计算机相关专业学生及JavaWeb初学者可完整实现用户与管理员双角色登录、用户发布新文章、文章详情查看、文章修改、删除与恢复等功能适合作为课程设计、大作业或入门实战练习项目。资源包共396个文件约7.32MB以java源码、jsp页面、js脚本、css样式及class编译文件为主另含sql数据库脚本与jar依赖导入数据库即可运行目录结构清晰便于对照学习。已有154人浏览学习。借助源码中文章新增、查询、修改、删除、恢复及登录校验等核心类的实现可掌握JavaWeb项目从数据库设计、后端逻辑处理到前端页面渲染的完整流程。同时可借鉴登录鉴权、分页查询等模块写法为独立开发内容管理系统提供可复用模板。1. 这个zip里装的JavaWeb文章管理系统先看它解决了什么从zip解压出来的是“源码数据库”的完整JavaWeb项目用户和管理员登录、用户发布新文章、文章详情查看、文章删除与恢复。这套功能几乎是所有课设题目里出现频率最高的一套组合也是你现在面试或练手时常被问到的增删改查闭环。你真正想知道的是它能不能跑起来怎么改删了怎么找回。我一般是先打开WebContent/WEB-INF看目录结构再决定运行方式。这个项目的技术路线非常常规Servlet JSP JDBC MySQL登录用Session删除用逻辑删除is_deleted字段文章表加用户表就两三百行SQL数据表一导、Tomcat一放就能跑。和那些动辄引入Spring全家桶的工程相比它读起来更像你刚学完Servlet后想自己做出来的成品。这个东西适合两类人做课设要选型的学生以及想快速拿一套完整JavaWeb代码去改成个人博客或内部公告系统的开发者。它的边界也很清楚没有Spring、没有集群、没有富文本编辑器属于小而完整的一类。真正的价值在于读得懂、改得动、能讲清楚这也正是我下面要带你过一遍的东西。2. 表结构与逻辑删除文章管理系统的基础把zip里的SQL脚本打开最先看到的通常是两张表user和article。绝大多数功能跑不跑得通在这两张表的结构里就决定了一半。我以前做课设时急着看代码后来发现表设计才是应该先读的东西一个is_deleted字段直接决定了后面的删除与恢复要怎么写SQL。2.1 用户表和文章表字段怎么定才不返工常见做法是两张表对应两个核心实体我先把建表SQL贴出来CREATE TABLE user ( id INT NOT NULL AUTO_INCREMENT, username VARCHAR(50) NOT NULL, password VARCHAR(64) NOT NULL, role VARCHAR(20) DEFAULT user, status INT DEFAULT 1, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE article ( id INT NOT NULL AUTO_INCREMENT, title VARCHAR(200) NOT NULL, content TEXT, author_id INT NOT NULL, view_count INT DEFAULT 0, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, is_deleted INT DEFAULT 0, delete_time DATETIME DEFAULT NULL, PRIMARY KEY (id), KEY idx_author (author_id), KEY idx_deleted (is_deleted) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这里把密码列固定为64字符是为了预留MD5加盐或SHA一类哈希值的存储空间。文章表用author_id而不是直接存author_name因为用户改名后文章归属不会被带乱查询列表时只需要JOIN一次user表拿用户名即可。字段设计的逻辑是role区分用户和管理员status用于禁用账号而不是删行这是双角色登录的第一道关卡view_count做浏览计数详情页每次访问1这个字段在验收“文章详情查看”时会用到。is_deleted和delete_time是这套系统的核心没有这两个字段“删除与恢复”只能靠物理删行来做。而物理删除一旦执行数据就永久消失了恢复功能也就无从谈起。所以建表阶段一定要看到is_deleted默认值为0delete_time默认允许为NULL这是逻辑删除的基本形态。2.2 逻辑删除 vs 物理删除删除与恢复的开关文章删除与恢复统一用逻辑删除是最稳的做法删除不等于让记录消失而是给记录打标记。用户点“删除”实际执行的是UPDATE而不是DELETE-- 删除标记为已删除 UPDATE article SET is_deleted 1, delete_time NOW() WHERE id ?; -- 恢复取消标记清除删除时间 UPDATE article SET is_deleted 0, delete_time NULL WHERE id ?;列表默认查询时要带上is_deleted 0条件其余业务代码完全不用动。这个方案的好处有两点第一恢复功能不需要额外备份表一行UPDATE就能把数据拉回来第二误删有后悔药对做课设或内部小系统的人来说这一点在演示和自测时特别有用。代价是你在项目里所有查询文章的地方都要记得过滤漏一个就会出现“删除后刷新文章又出现”的翻车现场。有的项目会设计专门的trash表来存放已删除记录删除时先INSERT进trash再DELETE原记录。这种方案审计上更清晰但代码量和状态同步成本直接翻倍对文章管理系统来说完全不划算。一个is_deleted字段加delete_time就能覆盖“删除与恢复”的验收要求后面想加定时物理清理也只是一条DELETE语句的事。我的选择理由很直接按最小方案实现把复杂度留给分页和权限这些真正影响体验的部分。2.3 双角色权限与登录会话双角色的权限控制不复杂但容易漏。user表的role取值是user或admin控制层用一个Filter去判断请求路径。常见做法是/admin/*路径走管理员过滤器检查session里的role发布文章走登录过滤器检查userId文章详情和列表页是公开的不需要登录。我见过有同学把登录判断直接写在JSP页面里每个页面重复判断一次加第二个功能后维护成本就失控了。正确的做法是把登录信息放进会话登录成功后存三个值userId、username、role。过滤器只需要判断userId是否为null为null就重定向到login.jsp。这个设计也顺便解答了一个常见疑问用户和管理员不需要两张表一个user表加role字段就够登录统一走同一个LoginServlet密码校验通过后根据role决定跳转后台还是主页。注意过滤器的执行顺序按web.xml里filter-mapping的声明顺序排列。登录过滤器要排在最前编码过滤器其次管理员权限过滤器放最后。顺序写反会出现编码过滤器还没执行就被登录过滤器拦走导致会话里的中文参数读取异常。这个问题不太容易发现遇到了先按这个顺序调。3. 登录会话与文章发布从点击登录到文章落库的全链路下面把四个核心功能串成一条线登录会话、发布文章、详情查看、删除恢复。每个环节都会给出可以直接复制的代码以及我在实际部署时调过的参数。3.1 登录接口最小实现MD5加盐与Session写入先看LoginServlet的关键代码protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 编码必须放在第一次 getParameter 之前否则中文必然乱码 req.setCharacterEncoding(UTF-8); String username req.getParameter(username); String password req.getParameter(password); User user userDao.findByUsername(username); // 密码统一走 MD5 固定盐盐值放在常量类里 String md5Pwd MD5Util.md5(password SALT); if (user null || !user.getPassword().equals(md5Pwd)) { req.setAttribute(msg, 用户名或密码不对); req.getRequestDispatcher(/login.jsp).forward(req, resp); return; } if (user.getStatus() ! 1) { req.setAttribute(msg, 账号已被禁用); req.getRequestDispatcher(/login.jsp).forward(req, resp); return; } // 登录成功把用户标识写进 Session req.getSession().setAttribute(userId, user.getId()); req.getSession().setAttribute(username, user.getUsername()); req.getSession().setAttribute(role, user.getRole()); resp.sendRedirect(index); }逻辑说明先按username查用户用户不存在和密码错误统一提示“用户名或密码不对”这样不会暴露账号是否存在。密码校验用的是MD5加固定盐这是课设阶段最安全且易写的方案比明文存数据库高一个量级如果你做正式项目把这句话改成BCrypt即可接口思路不变。参数说明login.jsp的form表单提交方式必须为post因为只有post方式下setCharacterEncoding才能正确解码中文session默认30分钟无操作失效这个值课设一般不改。登录成功用sendRedirect而不是forward是为了防止用户按F5刷新时重复提交登录表单。3.2 发布新文章中文编码、XSS过滤与insert落库发布文章的核心逻辑在ArticleServlet的doPostprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); HttpSession session req.getSession(); // 未登录用户直接回登录页不执行后续任何查询 Object userId session.getAttribute(userId); if (userId null) { resp.sendRedirect(login.jsp); return; } String title req.getParameter(title).trim(); String content req.getParameter(content); Article article new Article(); // 把标题与内容里的 HTML 尖括号转义防止脚本注入 article.setTitle(HtmlUtil.clean(title)); article.setContent(HtmlUtil.clean(content)); article.setAuthorId(Integer.parseInt(userId.toString())); int rows articleDao.insert(article); resp.sendRedirect(index); }逻辑说明第一行setCharacterEncoding必须出现在第一次读取请求参数之前否则中文字符到这里已经损坏后面再设置也没有意义。先校验登录再取参数避免未登录的人直接往文章表里写数据。HtmlUtil.clean是一个工具方法内部用replace把、、替换成HTML实体能挡住大部分常见的脚本提交。发布成功后统一跳回列表页新文章就会出现在首页。参数说明content字段在表里是TEXT类型默认上限是64KB中文按字符数计算一篇几千字的文章完全够用如果以后要写长文把字段改成MEDIUMTEXT即可。这里有一个很多新手会踩的坑在setCharacterEncoding之前调用getParameter那这行代码就白写了一定要把它放在Servlet最顶部。3.3 文章详情查看主键查询与浏览计数详情查看用doGet就能实现protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String idParam req.getParameter(id); if (idParam null || idParam.isEmpty()) { resp.sendError(400); return; } int id Integer.parseInt(idParam); Article article articleDao.findById(id); // 已删除的文章直接返回 404防止通过路径直接访问 if (article null || article.getDeleted() 1) { resp.sendError(404); return; } // 浏览量 1刷新一次就加一次课设可接受 articleDao.incrementViewCount(id); req.setAttribute(article, article); req.getRequestDispatcher(/article_detail.jsp).forward(req, resp); }逻辑说明id参数先做非空校验避免Integer.parseInt收到null时抛空指针。findById内部SQL是SELECT * FROM article WHERE id ? AND is_deleted 0这样被删除的文章在详情页直接表现为404用户感知上就是“真删掉了”。incrementViewCount执行UPDATE article SET view_count view_count 1如果刷新页面每次都加一次这是可接受行为想要精确去重可以改成按Session判断。参数说明resp.sendError(400)会返回Tomcat默认的错误页比在页面上alert“参数错误”更省事。Integer.parseInt在实战中最好用try-catch包住传了非数字时走统一异常处理否则Tomcat默认报错页对用户不够友好。3.4 删除与恢复逻辑置位、回收站与彻底清除后台一共三个接口分别对应删除、恢复、彻底删除// 删除把 is_deleted 置为 1保留数据 protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { int id Integer.parseInt(req.getParameter(id)); articleDao.logicDelete(id); resp.sendRedirect(admin_article_list); } // 恢复把 is_deleted 置回 0清除 delete_time protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { int id Integer.parseInt(req.getParameter(id)); articleDao.restore(id); resp.sendRedirect(admin_trash); } // 彻底删除只允许角色为 admin 的人执行执行后不可恢复 protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { if (!admin.equals(req.getSession().getAttribute(role))) { throw new ServletException(无权限); } articleDao.physicalDelete(id); resp.sendRedirect(admin_trash); }逻辑说明删除和恢复都是逻辑置位操作成功后分别跳转到管理列表和回收站列表。彻底删除前做了一次角色校验因为一旦DELETE FROM执行完就没有后悔药。三种接口共用同一张article表区别只在条件不同核心SQL如下-- 逻辑删除 UPDATE article SET is_deleted 1, delete_time NOW() WHERE id ?; -- 恢复 UPDATE article SET is_deleted 0, delete_time NULL WHERE id ?; -- 彻底删除 DELETE FROM article WHERE id ?; -- 回收站列表 SELECT * FROM article WHERE is_deleted 1 ORDER BY delete_time DESC;参数说明管理列表只查is_deleted0的数据回收站只查is_deleted1的数据两套列表的数据是同一张表。前端页面上“移入回收站”和“彻底删除”要分成两个按钮彻底删除按钮加一次二次确认这能避免演示时手滑把数据清掉。4. 部署与运行从zip到浏览器能访问的完整步骤代码看懂了更重要的是把项目跑起来。很多读者卡在“源码数据库”这一步不是代码有问题而是导入和配置顺序不对。按照下面三步走基本能十分钟内跑通。4.1 数据库初始化用SQL脚本一次建库导数据打开zip里的SQL脚本命令行方式导入最稳妥mysql -u root -p CREATE DATABASE IF NOT EXISTS article_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE article_db; source /your/path/article_db.sql;逻辑说明先建库指定utf8mb4字符集再source导入表结构和初始数据。常见错误是忘记指定字符集导致Java端配置了UTF-8数据库表却还是latin1中文乱码问题就很难排查。source路径如果含中文或空格需要用引号包住整个路径否则mysql客户端会在解析路径时报错。参数说明MySQL 5.7和MySQL 8都能执行这套SQL但连接MySQL 8时驱动名要换。如果源码里给的是com.mysql.jdbc.Driver在8.x下会直接报ClassNotFoundException这个细节放在第5章展开。导入完成后执行SHOW TABLES确认出现user和article两张表再查一下user表里有没有预置的管理员账号有就说明初始数据导入成功。4.2 Tomcat与JDK版本课设最稳的组合我一般推荐JDK 8搭配Tomcat 8.5。这个组合对传统JavaWeb最友好Servlet用的是javax.servlet命名空间不需要改任何代码。java -version # 确认是 jdk1.8.0_ 开头的版本 cd $TOMCAT_HOME/webapps ls # 把解压后的项目文件夹整个放进来逻辑说明把解压后的项目文件夹放进webapps目录启动Tomcat后就能通过端口加项目名访问。如果文件夹叫article访问地址就是http://localhost:8080/article/index。zip解压后如果已经是标准的WebContent目录结构不需要重新打war包直接把整个文件夹丢进去即可。这一步要检查项目lib目录里有没有mysql驱动jar很多启动报错就出在驱动没被shipping进去。参数说明Tomcat 10的用户要特别注意Tomcat 10已经把API从javax迁移到jakarta命名空间旧课设代码直接部署会报NoClassDefFoundError除非源码里import的是jakarta.servlet否则别拿Tomcat 10跑课设。拿不准就看源码第一行import或web.xml里的命名空间声明。如果8080端口被占用启动时会报Address already in use。处理办法是先找到占用进程Linux下用lsof -i:8080Windows下用netstat -ano | findstr 8080。也可以直接改server.xml里的端口号。传统JavaWeb项目的端口和上下文路径都是可配置的这一点比Spring Boot那套更透明能让你更清楚理解每次访问背后走的链路。4.3 连接池配置db.properties的参数与典型值传统JavaWeb项目通常用db.properties管理数据库连接jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/article_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai jdbc.usernameroot jdbc.password123456 jdbc.initialSize5 jdbc.maxActive20 jdbc.maxIdle10 jdbc.minIdle5逻辑说明driver和url必须匹配。MySQL 5.x用com.mysql.jdbc.DriverMySQL 8.0以上用com.mysql.cj.jdbc.Driverurl一定要带serverTimezoneAsia/Shanghai否则连接时会报时区错误。useSSLfalse是本地开发时避免SSL握手警告。initialSize是启动时创建的连接数maxActive是并发峰值上限课设项目maxActive20就足够。参数说明如果项目用的是Druid连接池db.properties里的键名要改成druid.initialSize、druid.maxActive这类前缀不能直接写jdbc.initialSize。去源码里看连接池工厂类读取的key前缀再配置这是我见过很多次的部署翻车点。部署完成后打开浏览器访问http://localhost:8080/项目名/login.jsp能渲染出登录页说明Servlet映射没有问题。此时数据库连接还没被动用真正的连通性要点击登录按钮时才验证。我习惯按这个顺序确认先登录再发一篇文章最后检查后台列表。这样每走一步出问题时定位范围非常小。5. 避坑记录乱码、驱动、路径与删除恢复的整改记录这些坑我都走过每条按“现象、原因、解决”三连接着往下说。5.1 页面中文全是问号现象首页能访问但登录后发布的中文标题显示成“???”或乱码。原因三处编码不一致。请求编码、响应编码、数据库表字符集只要有一个不对中文链路就断。很多项目只设置了request.setCharacterEncoding(UTF-8)但JSP页面没有指定contentType或者数据库建表用了latin1。解决JSP页面顶部统一加% page contentTypetext/html;charsetUTF-8 languagejava %Tomcat的server.xml里给Connector加URIEncodingUTF-8建表统一用utf8mb4连接串带characterEncodingutf8。四步全做到中文乱码基本不会再出现。5.2 登录报ClassNotFoundException现象登录时提示ClassNotFoundException: com.mysql.jdbc.Driver或者卡很久之后报Access denied for user。原因项目lib里放的是旧的mysql-connector-java 5.x但你本机是MySQL 8另一种情况是db.properties里的账号密码和本机MySQL不一致。解决把驱动jar换成mysql-connector-java 8.0.xdb.properties里的driver改成com.mysql.cj.jdbc.Driverurl加上serverTimezoneAsia/Shanghai。如果报Access denied直接改成你本机真实的username和password不要照抄网上的123456。5.3 删除后刷新又出现现象点击删除提示成功刷新列表文章还在。原因删除接口执行了UPDATE把is_deleted置1但列表查询SQL是SELECT * FROM article没有拼is_deleted0条件逻辑删除根本没有产生效果。解决全项目所有查询文章的SQL统一加WHERE is_deleted 0详情查询也要加。这是逻辑删除方案唯一的隐藏成本漏一个就翻车。我习惯把is_deleted0条件直接写进DAO的公共查询片段里让所有列表和详情复用从源头上杜绝漏加。5.4 恢复按钮404现象在回收站点“恢复”URL变成/restore?id3结果404。原因JSP里写的链接是绝对路径/restore而项目部署路径不是根路径请求落到了Tomcat根上下文之外。如果项目部署在http://localhost:8080/article那不带上下文路径的链接必然访问不到。解决所有链接统一用${pageContext.request.contextPath}拼路径例如href${pageContext.request.contextPath}/article/restore?id${a.id}。同时检查Filter的放行规则如果过滤器拦截放行规则太糙拦截了login.jsp或静态CSS就会出现“登录成功又弹回登录页”的怪现象。5.5 Session过期后表单被拦截现象在编辑页面停留超过30分钟点发布直接被Filter拦回登录页写的文章内容全部丢失。原因Session默认30分钟无操作失效登录过滤器只认userId是否为空过期后页面还停留在编辑状态一提交就被拦。解决短平快做法是在web.xml里把session超时改到60分钟更细的做法是发布页用localStorage定时保存草稿提交成功后清除正式项目可以在登录拦截器里带redirectURL参数登录成功后自动回跳。课设阶段把超时调大加草稿保存体验就已经很好了。6. 进阶验证把课设改成一个能解释清楚的小作品功能跑通之后建议再做一遍“验收加小改”这个动作能让你在演示时明显比别人讲得清楚。6.1 最小验收清单按清单快速验证每个功能功能操作预期结果用户登录输入普通用户账号跳转到普通用户首页管理员登录输入管理员账号跳转到管理后台发布文章填写标题与内容提交列表出现新文章详情可访问查看详情点击文章标题正常渲染内容浏览量加1删除文章列表页点删除文章从列表消失进入回收站恢复文章回收站点恢复文章重新出现在列表6.2 三个值得顺手改的小功能分页、日志、防注入第一个是列表分页。现在的列表是全量查询文章一多就难找。改动很小加pageNo和pageSize两个参数SQL加LIMIT offset, pageSize前端渲染上一页下一页按钮。第二个是操作日志。每次删除、恢复、彻底删除都往log表写一条记录包含操作用户和文章标题这是演示时的加分项也能让你之后排查误删时有据可查。第三个是SQL全面改PreparedStatement杜绝字符串拼接传参数这是安全底线不是性能优化。我有一个习惯拿到这类zip源码先看SQL脚本和db.properties再先跑通登录最后才动业务代码。把乱码、驱动、路径这三个问题处理掉剩下的大多是逻辑问题反而好修。如果你准备把这个文章管理系统交作业或挂到个人网站上把上面提到的分页和日志一起补上完整度立刻上一个台阶。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑