news 2026/10/7 13:42:57

JavaWeb新闻发布系统源码拆解:Servlet+JSP三层架构与分页上传实战解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JavaWeb新闻发布系统源码拆解:Servlet+JSP三层架构与分页上传实战解析

简介:这是一套基于JavaWeb的新闻发布系统毕业设计完整资料包,面向计算机专业毕业生、Java Web学习者及需要实战项目的开发者。资源围绕可运行的源代码、数据库SQL脚本与毕业论文展开,覆盖新闻管理、栏目分类、评论互动、用户登录等核心功能;代码采用Servlet+JSP+DAO分层,帮助理解请求处理、业务逻辑与数据访问的完整链路。压缩包内共556个文件,大小11.65MB,包含26个Java核心类、21个JSP视图、12个Jar依赖库,以及224个JS、80个PNG、30个CSS等前端页面资源,后端逻辑与前端界面一一对应;同时附有1个SQL数据库脚本和doc格式论文,可直接导入数据库并参考写作。开发环境说明与部署常见问题解答也已包含,可帮助规避Tomcat、数据库连接等典型问题。目前已有123人学习下载,适合作为毕业设计蓝本、框架实战练习或求职项目经验积累。

1. 把 JavaWeb 新闻发布系统拆开看:这套 Servlet + JSP 源码到底能练出什么

聊到 JavaWeb 新闻发布系统,很多人的第一反应是“又是毕设老三样”。但真把这份源码的类文件摊开——NewsServlet、NewsDao、CommentDao、IndexServlet、UserServlet、ImageUploadServlet——你会发现它是一套非常典型的 Servlet + JSP + DAO 三层结构。没有 Spring 全家桶的包装,反而把请求分发、JDBC 操作、分页查询、文件上传这些 JavaWeb 核心考点全暴露在明面上。对于做毕业设计、准备校招面试基础轮、或者需要一个干净模板做二次开发的人来说,这种“裸奔”结构的项目比框架整合项目更有拆解价值。它能让你在一两天内把 JavaWeb 的完整请求链路跑通,也知道哪些地方最容易翻车。

2. 请求分发链路:从 NewsServlet 到 DAO 的三层结构与分页参数

2.1 项目结构与请求流转的底层逻辑

这套项目里没有 Spring MVC 那套注解路由,而是用最原始的 Servlet 映射 + 业务分发来驱动整个系统。拿到源码包后,第一件事不是急着在 IDEA 里跑起来,而是先把类名和职责对上:

  • InitServlet:应用启动时初始化,通常用来加载分类列表到内存或配置文件;
  • IndexServlet:首页入口,负责组装新闻列表和分类数据;
  • NewsServlet:新闻操作的统一入口,列表、详情、新增、删除、修改都走它;
  • CommentServlet:评论提交与列表查询;
  • UserServlet:登录、注册、登出;
  • NewsTypeServlet:新闻分类维护;
  • LinkServlet:友情链接维护;
  • ImageUploadServlet:图片上传处理。

这套命名非常直白,对应的就是后台管理菜单和前台页面模块。请求流转是一条单向链路:浏览器发起 URL 请求 → 容器根据 web.xml 映射找到对应的 Servlet → Servlet 调用业务方法 → 业务方法调 DAO → DAO 拼 JDBC 执行 SQL → 结果逐层返回 → Servlet 转发或重定向到 JSP 页面。

看源码时建议先从 NewsServlet 下手,它是整个系统里业务分支最多的类。常见的做法是 doGet 和 doPost 共用同一个 dispatcher,然后通过 action 参数区分具体操作:

protected void service(HttpServletRequest request, HttpServletResponse response) { String action = request.getParameter("action"); if ("list".equals(action)) { // 分页查询新闻列表 } else if ("detail".equals(action)) { // 查看新闻详情 } else if ("add".equals(action)) { // 跳转到新增页面 } else if ("save".equals(action)) { // 保存新闻,含插入正文 } else if ("delete".equals(action)) { // 删除新闻 } }

action 参数的值必须和 JSP 页面表单里提交的 action 完全一致,否则会直接掉进 else 分支返回空页面。这个参数是整套请求分发机制的唯一约定,改错一个字符就是请求 404 或者页面空白。

源码里各个 Servlet 之间没有继承统一的 BaseServlet,每个类单独实现 service 或 doGet/doPost 方法。好处是逻辑直观,坏处是代码重复率高。读代码时重点看两个地方:RequestDispatcher 的 forward 跳转和 sendRedirect 的重定向,前者用于带着 request 域中的数据到 JSP 渲染,后者用于表单提交后防止重复提交。

2.2 分页参数设计:page 与 pageSize 的计算规则

新闻列表是用户访问最频繁的页面,分页查询的实现质量直接决定你能不能在答辩时讲清楚。这套项目里分页走的是传统 limit 方式,传 page 和 pageSize 两个参数给 DAO,SQL 动态拼 limit 偏移量。

看 NewsDao 的分页方法时注意这个核心逻辑:

public List<News> findPage(int page, int pageSize) { int offset = (page - 1) * pageSize; String sql = "SELECT id, title, category_id, author, publish_time " + "FROM news ORDER BY publish_time DESC LIMIT ?, ?"; PreparedStatement ps = conn.prepareStatement(sql); ps.setInt(1, offset); ps.setInt(2, pageSize); // 执行查询并封装结果 }

offset = (page - 1) * pageSize 这条公式是整个分页的命根子。page 为 1 时偏移量是 0,查第一页;page 为 2 时偏移量是 pageSize,跳过第一页的数据。前端页面上显示“上一页 / 下一页”时,也要在 URL 里带上 page 参数,否则每次点击都会回到第一页。

总页数的计算在 Service 层,通常用 ceil 处理:

int totalCount = newsDao.countAll(); int totalPage = (int) Math.ceil((double) totalCount / pageSize);

countAll 对应的是 SELECT COUNT(*) FROM news,这条统计 SQL 和列表 SQL 必须保持同样的过滤条件,否则列表页数和实际数据对不上。如果源码里加了分类筛选,那么 count 和 findPage 都要同时带 categoryId 条件,这就是一个容易踩的隐藏坑。

3. 数据库设计与首页渲染:news_category 与 news 表结构的取舍

3.1 表结构设计与外键约束的关键点

这套系统 SQL 文件里最核心的表是 news(新闻表)、news_category(分类表)、t_comment(评论表,也有项目叫 comment)和 t_user(用户表)。SQL 文件导入后,先用 Navicat 或 DBeaver 把表关系理清再动代码。

新闻表是一张信息密度很高的表,设计时大部分字段是必填的:

字段名类型约束说明
idint主键自增新闻唯一标识
titlevarchar(100)非空新闻标题
contenttext非空正文内容
category_idint外键关联 news_category.id
authorvarchar(50)可空作者
imagevarchar(255)可空封面图路径
publish_timedatetime默认当前时间发布时间
click_countint默认 0点击量

category 表就简单得多:id 自增主键 + name 分类名。两个表通过 category_id 建立一对多关系,一张分类对应多条新闻。创建表时建议加上外键约束并在 category_id 上建普通索引,查询按分类筛选时会快不少:

CREATE TABLE news_category ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE news ( id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(100) NOT NULL, content TEXT NOT NULL, category_id INT NOT NULL, author VARCHAR(50), image VARCHAR(255), publish_time DATETIME DEFAULT CURRENT_TIMESTAMP, click_count INT DEFAULT 0, INDEX idx_category (category_id), CONSTRAINT fk_news_category FOREIGN KEY (category_id) REFERENCES news_category(id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

字符集用 utf8mb4 而不是 utf8,这点很多毕设项目都忽略。utf8 在 MySQL 里最多存 3 字节,遇到表情符号会直接报 Incorrect string value 错误,utf8mb4 是完整的 4 字节支持。另外注意到外键策略了吗?这里没有指定 ON DELETE 规则,默认是 RESTRICT。也就是说后台分类管理中想删除一个下面还有新闻的分类,数据库会直接拒绝。

3.2 IndexServlet 首页数据组装与 JSP 渲染的传值方式

首页 IS 上展示的内容通常是:顶部导航栏分类列表、中间最新新闻列表、侧边栏热门新闻。这里涉及一个知识点:一次请求如何组装多个数据集。IndexServlet 的做法是循环向分类列表里填充每类新闻的数据,属于经典的手动组装:

public class IndexServlet extends HttpServlet { protected void doGet(HttpServletRequest req, HttpServletResponse resp) { // 1. 查询所有分类 List<NewsType> typeList = newsTypeDao.findAll(); // 2. 查询最新新闻列表 List<News> newestList = newsDao.findTopN(10); // 3. 查询点击量最高的新闻 List<News> hotList = newsDao.findByClickCount(8); req.setAttribute("typeList", typeList); req.setAttribute("newestList", newestList); req.setAttribute("hotList", hotList); req.getRequestDispatcher("/index.jsp").forward(req, resp); } }

findTopN 在 DAO 层对应的 SQL 是 ORDER BY publish_time DESC LIMIT 10,findByClickCount 对应 ORDER BY click_count DESC LIMIT 8。两个方法都需要在 SQL 层面控制返回条数,而不是查全表后在 Java 里截取。

JSP 页面里用 JSTL 标签循环输出,注意页面顶部要引入标签库:

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <ul> <c:forEach items="${newestList}" var="news"> <li> <a href="news?action=detail&id=${news.id}">${news.title}</a> <span>${news.publishTime}</span> </li> </c:forEach> </ul>

itemList 的 key 必须和 request.setAttribute 时的 key 完全一致。新闻标题如果是用户输入的富文本,在列表页输出前建议做 HTML 转义,否则标题里插一段 script 就是存储型 XSS。这是新闻类系统面试时高频被问到的安全问题,源码里如果没做防御,你可以自己加一个简单的 HtmlUtils 处理。

4. 后台管理闭环:NewsTypeServlet 与 ImageUploadServlet 的增删改查和文件上传

4.1 NewsTypeServlet 分类维护的完整 CRUD 流程

后台管理里新闻分类维护是最典型的 CRUD 场景。NewsTypeServlet 走的还是 action 分发模式,没有用独立的方法路由。新增分类和编辑分类都走 save action,通过是否携带 id 参数判断是插入还是更新:

protected void doPost(HttpServletRequest req, HttpServletResponse resp) { request.setCharacterEncoding("UTF-8"); String action = req.getParameter("action"); if ("add".equals(action)) { // 把表单数据封装成 NewsType 对象,调用 DAO 插入 NewsType type = new NewsType(); type.setName(req.getParameter("name")); newsTypeDao.save(type); resp.sendRedirect("type?action=list"); } else if ("update".equals(action)) { // 必须有 id,否则无法定位记录 int id = Integer.parseInt(req.getParameter("id")); NewsType type = newsTypeDao.findById(id); type.setName(req.getParameter("name")); newsTypeDao.update(type); resp.sendRedirect("type?action=list"); } }

这里有一个非常重要的细节:doPost 里第一行必须设置 request 编码,否则表单提交的中文分类名到后端全是问号。还有 update 操作必须先按 id 查出原对象,再把新名字 set 上去,避免把其他字段误置为 null。源码里如果直接 new 对象只填 id 和 name,那么旧记录的备注、排序字段就会丢。

删除分类时要注意,外键约束默认 RESTRICT 会导致删除失败。源码里如果没有先删除该分类下所有新闻再删分类的逻辑,那后台点击删除按钮就会报 SQLIntegrityConstraintViolationException。建议改成逻辑删除——给 category 表加一个 status 字段,删除时 UPDATE status = 0,新闻列表查询时只展示 status = 1 的分类。这样既不破坏外键关系,也不丢历史新闻。

4.2 ImageUploadServlet 文件上传与存储路径的决定性细节

图片上传是新闻发布系统里的高频功能,稿件封面、正文插图都依赖它。这套项目里的 ImageUploadServlet 用的是 Servlet 3.0 的 Part 接口,没有引入 commons-fileupload 外部库,所以 web.xml 里对应 Servlet 声明需要配置 multipart 支持:

@MultipartConfig( maxFileSize = 5 * 1024 * 1024, // 单个文件最大 5MB maxRequestSize = 10 * 1024 * 1024, // 整个请求最大 10MB fileSizeThreshold = 1024 * 1024 // 超过 1MB 写入临时文件 ) public class ImageUploadServlet extends HttpServlet { protected void doPost(HttpServletRequest req, HttpServletResponse resp) { Part part = req.getPart("file"); // 表单里文件域的 name String fileName = extractFileName(part); // 保存到指定目录 String uploadPath = getServletContext().getRealPath("/upload"); part.write(uploadPath + File.separator + fileName); // 返回可访问的 URL 给前端 resp.getWriter().write("upload/" + fileName); } }

extractFileName 是封装的工具方法,核心逻辑是截取 Part 头里 filename= 的值。这里最大的坑是 getRealPath("/upload") 拿到的其实是项目部署目录,在 IDEA 里跑的时候是 target 目录下的临时路径,重启 Tomcat 后上传的图片就没了。我一般会改成配置文件里指定绝对路径,比如 D:/news_upload/,然后把这个路径映射成虚拟目录给前端访问。另一种做法是在 Tomcat 的 server.xml 里配 Context 的虚拟路径映射,但那样会让项目换个环境就起不来,不利于答辩演示。

上传文件名的处理上,源码里如果直接把用户原始文件名写到磁盘,两个用户传同名图片就会互相覆盖。要在服务端做唯一化处理:

String ext = fileName.substring(fileName.lastIndexOf(".")); String uuidName = UUID.randomUUID().toString().replace("-", "") + ext;

用 UUID 重新命名后存盘,数据库里只存 uuidName,前端回显时拼上访问前缀就能显示。注意 ext 要控制后缀类型,只允许 jpg、png、gif,防止有人传 jsp 文件伪装成图片,配合 Tomcat 目录执行权限问题就是拿站姿势。

5. 避坑记录:Tomcat 版本、字符集、SQL 与上传路径的四个坑

5.1 Servlet 类编译报错或页面 404:Tomcat 10 的 jakarta 命名空间

现象:项目导入 IDEA 后一堆 Servlet 相关的 import 全部飘红,编译都过不去;或者能编译但启动后访问页面直接 404,Tomcat 日志里提示 ClassNotFoundException: javax.servlet.ServletException。

原因:Tomcat 10 开始把 Servlet API 的包名从 javax.servlet 迁移到了 jakarta.servlet,传统 JavaWeb 项目用的是 javax.servlet.http.HttpServlet,在 Tomcat 10 里根本找不到类。现在很多教程默认配 Tomcat 9 以下,新手如果自己下载了最新版 Tomcat 10,就会撞上这个坑。

解决:下载 Tomcat 9 重新配置 IDEA 的 Run Configuration,把 Server 指向 Tomcat 9 的目录。如果想在 Tomcat 10 上跑,就得把所有 import 批量替换成 jakarta.servlet,工作量大且容易漏。

5.2 中文全部变成问号:请求编码与连接串编码双重缺失

现象:列表页标题和内容正常,但后台新增新闻填了中文,保存后数据库里存的是 ??,前台显示也全是问号。

原因:两层问题叠加。第一层是请求没设置编码,Tomcat 默认用 ISO-8859-1 解码 POST 表单,中文天然乱码;第二层是 JDBC 连接串没加 characterEncoding=UTF-8,即使请求编码对了,数据插入 MySQL 时还是按错误字符集转换。

解决:写一个全局 Filter 设置 request 和 response 的编码:

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) { request.setCharacterEncoding("UTF-8"); response.setCharacterEncoding("UTF-8"); response.setContentType("text/html;charset=UTF-8"); chain.doFilter(request, response); }

JDBC 连接串完整写法是:

jdbc:mysql://localhost:3306/news?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false

数据库连接串里没带 serverTimezone 的还会在 MySQL 8.0 下报时区错误,这也是常见附加坑。

5.3 分页第二页开始空白:limit 偏移量写死

现象:首页第一页新闻正常显示,点击“下一页”后 URL 变成了 ?action=list&page=2,但页面还是第一页的数据,或者直接空白。

原因:DAO 层的分页 SQL 把偏移量写死了,比如 LIMIT 0, 5,page 参数传进来完全没用;另一种是页面上的上一页/下一页链接没有拼接 page 参数,每次点击都从默认的 page=1 开始。

解决:把页码当前值从 request 参数读取,偏移量用 (page-1) * pageSize 计算,且每一条分页链接上都要带当前页参数。调试时在控制台打印最终执行的 SQL,一眼能看出 LIMIT 后面的值对不对。

5.4 上传图片重启后丢失或裂图:保存到了部署临时目录

现象:后台提交新闻带封面图,当时显示正常,但重启 Tomcat 后图片全部 404;或者图片上传后立即回显,刷新页面就裂图。

原因:getServletContext().getRealPath() 在 IDEA 开发模式下解析到的是 target/项目名/ 目录,每次重新构建会清空,图片自然被连带删除。

解决:把保存路径拎到项目外部,比如 Windows 下固定为 D:/newsUpload/,数据库里存相对路径,前端使用独立的文件访问 URL。部署到生产环境时再根据服务器磁盘重新规划路径,但代码里不要出现相对路径拼接。

6. 部署与验证:在 IDEA 里把整套 JavaWeb 新闻系统跑起来的关键三步

拿到源码后最快的验证路径是:先导数据库,再配 Tomcat,最后过一遍三个核心页面。先说数据库部分。新建一个 news 库,导入项目里的 SQL 文件,然后确认连接配置——看 db.properties 或 JDBC 工具类里的用户名密码是否和本地 MySQL 一致。这里要确认 MySQL 服务真的在运行,用 Navicat 能连上不代表 IDEA 里能连上,要看连接串的端口和编码参数。

然后配置 IDEA 的 Run Configuration。Project Structure 里把 Artifact 设置为 Web Application Exploded,点击右侧的坑位图标——Deployment 里选这个 Artifact,Application context 填 /news。Server 选项卡里配好 Tomcat 9 的路径。系统里已经装 Tomcat 10 的记得先换掉,这是复现成本最低的版本选择。

启动后在浏览器里依次验证三个页面:

验证点访问地址通过标准
前台首页http://localhost:8080/news/分类导航显示、新闻列表有数据、图片正常
新闻详情首页点击任意新闻标题正文完整、评论列表加载
后台管理/admin/login.jsp 或对应登录路径用户名密码登录后能进后台,能新增新闻

登录后第一件事是新增一条带中文标题和图片的新闻。如果这条新闻存进去是乱码、图片裂图、或者保存后列表看不到——恭喜你,把第 5 章的坑全部踩了一遍。

从那以后我接手任何 JavaWeb 项目,第一件事永远是看它的 web.xml 版本和 Tomcat 版本,第二件事是确认数据库连接串里的字符集参数,这两个字段没问题,项目基本就能跑通了。这个小习惯帮我避免过至少十次“代码明明没问题但就是跑不起来”的排查,希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/7 13:42:26

Bonding IC气泡诊断与优化指南:从ACF原理到COG/COF产线实战

开头那段话&#xff0c;我经常在产线遇到。显示模组厂里最磨人的品质问题&#xff0c;往往不是电路设计的大毛病&#xff0c;而是这种藏在IC底下、切开才能看到的"气泡"。Bonding IC出现气泡&#xff0c;轻则影响导通电阻&#xff0c;重则直接导致接触不良、显示异常…

作者头像 李华
网站建设 2026/10/7 13:41:44

Logisim实现MIPS五级流水线CPU实战指南

1. 这不是“玩具CPU”&#xff0c;而是计组实验里最硬的那块骨头 Logisim搭5级流水MIPS CPU&#xff0c;听起来像教科书里的标准练习&#xff0c;但对华中科技大学计算机学院的学生来说&#xff0c;这几乎是《计算机组成原理》实验课上最让人头皮发紧的一关。我带过三届计组实验…

作者头像 李华
网站建设 2026/10/7 13:41:24

基于FPGA CARRY4进位链实现高精度TDC时间数字转换器

在实验室里跟时间打交道多了&#xff0c;你会发现一个尴尬的现实&#xff1a;示波器动不动就是几个G的采样率&#xff0c;商用TDC芯片标称皮秒级分辨率&#xff0c;可一看到价格和供货周期就头大。尤其是做激光测距&#xff08;TOF&#xff09;、PET成像、物理实验时间戳这类项…

作者头像 李华
网站建设 2026/10/7 13:41:18

Windows R0进程保护驱动开发实战:ObRegisterCallbacks与内存页保护

简介&#xff1a;本资源是一份面向Windows内核开发者与安全研究人员的R0级进程保护驱动源码包&#xff0c;聚焦Ring 0层内存读写与进程防护机制实现&#xff0c;适用于游戏反作弊开发、内核调试学习及底层安全技术实践。压缩包为ZIP格式&#xff0c;大小36.67MB&#xff0c;包含…

作者头像 李华
网站建设 2026/10/7 13:41:02

多Agent实战:WorkBuddy角色拆解与缓存记忆调优

WorkBuddy 这个工具我从第一版就在用&#xff0c;这个系列写到第六篇&#xff0c;后台几乎每天都有读者来问多 Agent 到底应该怎么配。很多人把多 Agent 想得特别玄乎&#xff0c;觉得把界面里的 Agent 数量从 1 加到 3 就能解决所有问题&#xff0c;实际根本不是这么回事。这篇…

作者头像 李华
网站建设 2026/10/7 13:40:35

GaN HEMT TCAD仿真实战:从崩溃到流片的物理建模指南

1. 这不是软件教程&#xff0c;是GaN HEMT仿真现场的“血泪笔记”我第一次在Sentaurus TCAD里画出GaN HEMT结构图时&#xff0c;信心满满——毕竟文献里那些I-V曲线、电场分布图看着挺规整。结果跑完第一个直流扫描&#xff0c;漏极电流直接崩到10⁹ A/cm&#xff0c;仿真器报错…

作者头像 李华