新闻分类系统,光听名字就知道是毕设里的常青树。我前后经手过好几个类似选题的源码,这套编号 31188 的 Spring Boot 新闻分类系统,是我认为结构最规整、扩展起来最舒服的一套。它不是什么花哨的大项目,但 Spring Boot + MyBatis 这一条主线上该有的东西全都齐了:分类管理、新闻发布、列表分页、关键字搜索、登录拦截、文件上传,甚至后台管理和前台展示都分得清清楚楚。对准备做 Java 毕设、想搞懂 Spring Boot 项目到底是怎么串起来的人来说,这套源码比看十篇零散的教程都管用。
它解决的实际问题很明确:把杂乱的新闻内容按照分类组织起来,让用户能按类别浏览、按关键词检索,让管理员能统一维护新闻和分类信息。这个场景不管在校招项目、课程设计还是毕业设计里出现频率都很高,因为它的业务逻辑足够完整,却又不至于复杂到一个人做不完。网上问得最多的“springboot 项目怎么做”“springboot 框架介绍”“springboot vue 前后端分离”这些问题,拿这套源码对应的技术栈去回答,基本能覆盖大半。
我写这篇东西的目的很实在:把这套 Spring Boot 新闻分类系统的设计思路、表结构、关键代码、实际踩过的坑,以及答辩时容易被抓着问的点全部摊开讲清楚。不管你是打算直接参考这套源码,还是想自己从零写一个类似的系统,都能从里面找到可以直接抄作业的部分。
1. 项目定位与整体设计思路
1.1 新闻分类系统到底在做什么
很多同学拿到选题第一反应是“新闻分类系统不就是 CRUD 吗”,这个说法对了一半。它确实绕不开增删改查,但真正的难点不在 CRUD 本身,在于把业务规则理顺。新闻分类系统里有几个核心角色:普通访客、登录用户、管理员。访客能看到前台的新闻列表、分类导航、新闻详情;登录用户在前台基础上还能做评论或者收藏(这套源码里主要做了浏览和检索,扩展空间很大);管理员进后台维护分类和新闻。
整个系统的数据流转也不复杂:管理员先创建新闻分类,比如“科技”“体育”“财经”“娱乐”,然后在某个分类下发布新闻,新闻包含标题、作者、封面图、正文、发布时间、状态这些字段。前台页面按分类展示新闻列表,用户点击进入详情,也能通过搜索框按标题关键字查新闻。后台的管理页面则提供分类的新增修改删除、新闻的发布编辑上下架,以及分页列表展示。
从这个流程可以看出,它本质上是一个典型的内容管理类系统。你把它换成“博客系统”“公告系统”“资讯管理系统”,表结构和业务代码至少能复用七成。所以做完这一套,后面遇到同类型选题基本就是改字段的事。
1.2 技术栈选型的权衡:为什么是 Spring Boot + MyBatis
这套源码的技术栈是 Spring Boot 2.7.x + MyBatis + MySQL,前端使用的是 Thymeleaf 模板引擎加 Bootstrap,如果是前后端分离的版本则换成 Vue。这个选型放在毕业设计的场景里非常合理,原因有三个。
第一,Spring Boot 是目前 Java 后端开发的事实标准。网上搜“springboot 配置”“springboot 框架介绍”“springboot 项目”这些关键词,资料饱和度极高,遇到问题很容易找到解决方案。对基础一般的同学来说,生态完善意味着容错率高,卡住了不至于寸步难行。
第二,MyBatis 上手门槛比 JPA 低,SQL 是自己写的,能清楚地看到每条查询怎么执行。这一点在答辩时非常加分,因为老师问“你的查询怎么实现的”,你直接把 Mapper XML 里的 SQL 拿出来讲就行。用 JPA 反而容易陷入“框架帮我做了什么”的解释困境。MyBatis 的半自动特性,让你对 SQL 有绝对掌控力,这在查分类统计、分页、模糊搜索这类需求时特别直观。
第三,Spring Boot 的自动配置特性让项目搭建成本极低。不用像 SSM 时代那样写一大堆 XML 配置,依赖一引、配置一写、启动类一跑,项目就起来了。配合 Lombok 省掉 getter/setter,开发效率能提升一大截。
选型时还有个细节值得注意:Spring Boot 版本不要追新。热搜词里总有“springboot 版本太高”这类问题,确实我在实际项目里也遇到过,Spring Boot 3.x 强制要求 JDK 17,很多老教材和老依赖在新版本下会报错。这套源码选的 2.7.x 搭配 JDK 8 是兼容性最好的组合,任何教程、任何中间件基本都能匹配上,不折腾。
1.3 项目模块划分与代码结构
我拿到一套源码习惯先看包结构,包结构清晰的项目,代码质量通常不会差。这套源码的标准包结构如下:
com.news.system ├── controller # 控制层:接收请求、返回视图或JSON ├── service # 业务层:处理业务逻辑 │ └── impl # 业务实现类 ├── mapper # MyBatis Mapper接口 ├── entity # 实体类:对应数据库表 ├── common # 通用工具、常量、统一返回结果 ├── config # 配置类:拦截器、静态资源映射等 └── NewsSystemApplication.java # 启动类这个分层结构对应的是经典的“三层架构”:Controller 负责接收参数和返回结果,Service 负责业务规则,Mapper 负责数据库操作。它最大的好处是职责单一,出了问题能快速定位。比如分页查新闻列表出错了,先看 Controller 传入的参数对不对,再看 Service 里有没有处理分页逻辑,最后看 Mapper XML 里的 SQL 是不是写错了。
项目启动类一定要放在最外层包下,这样 Spring Boot 的组件扫描才能覆盖到所有子包。很多新手把启动类放错位置,结果全是一堆@Autowired注入失败的报错。另外 common 包里建议放一个统一返回结果类,比如Result<T>,封装 code、msg、data 三个字段,前台和后台的接口都返回这个结构,前后端联调时就不容易出现“数据结构对不上”的问题。
2. 数据库设计与核心业务逻辑
2.1 数据表设计:从分类到文章的字段规划
数据库设计是一套毕设源码的灵魂,表结构合理,后面的代码写起来就顺风顺水。这套新闻分类系统的核心表主要有三张:新闻分类表、新闻信息表、用户表,另外视功能扩展情况可能还有评论表。
新闻分类表是整套系统的基石,字段不多但作用关键:
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键,自增 |
| category_name | varchar(50) | 分类名称 |
| category_code | varchar(50) | 分类编码,唯一 |
| sort_order | int | 排序号,越小越靠前 |
| status | tinyint | 状态:0停用 1启用 |
| create_time | datetime | 创建时间 |
| update_time | datetime | 更新时间 |
新闻信息表是内容主体,字段要覆盖标题、来源、作者、封面图、摘要、正文、所属分类、浏览量、状态、发布时间等。
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| category_id | bigint | 所属分类ID,关联分类表 |
| title | varchar(200) | 新闻标题 |
| summary | varchar(500) | 摘要 |
| author | varchar(50) | 作者 |
| cover_image | varchar(255) | 封面图URL |
| content | text | 正文内容 |
| view_count | int | 浏览量 |
| status | tinyint | 0草稿 1发布 2下架 |
| create_time | datetime | 创建时间 |
| publish_time | datetime | 发布时间 |
用户表根据需求可繁可简,最基本的就是 id、username、password、nickname、role、status、create_time。密码字段要注意,存的是加密后的密文,绝不能存明文。
这里有一个表设计上的关键决策:分类到底做单级还是树形?这套源码做的是单级分类,也就是所有分类平级。如果要做多级分类,比如“科技”下面有“互联网”“AI”“硬件”,那么需要在分类表里加一个 parent_id 字段,改成树形结构。毕设场景下单级分类足够,而且前后端展示都简单,不建议一上来就做无限级分类,容易把自己绕进去。扩展成两级分类是性价比最高的方案,加一个 parent_id 就行,三级以上就要考虑递归查询效率了。
2.2 分类管理的实现思路
分类管理是这套系统里第一个完整的业务闭环,实现逻辑非常适合作为理解整个项目的切入口。管理员的分类管理页面会展示一张分类列表,每条分类都有“编辑”“删除”按钮,顶部有“新增分类”按钮。这个功能的流程是:Controller 接收请求,调用 Service 层的分类服务,Mapper 执行 SQL,数据通过 Thymeleaf 或 JSON 返回到页面。
分类管理的核心难点不在 CRUD,而在两个细节上。第一个是查分类下有多少篇新闻,这个信息能帮管理员判断分类能不能删。实现这句统计 SQL 很直接:
SELECT c.id, c.category_name, COUNT(a.id) AS news_count FROM news_category c LEFT JOIN news_article a ON c.id = a.category_id GROUP BY c.id, c.category_name ORDER BY c.sort_order ASC;这里用 LEFT JOIN 而不是 INNER JOIN,是为了把那些“还没有新闻的分类”也查出来,新闻数量显示为 0 而不是直接不出现。删除分类时也要先判断下面的新闻数量,分类下有新闻时应该提示管理员先转移或删除新闻,否则数据就会变成悬空状态,前端拿到一个找不到分类名的 category_id,页面就容易显示异常。
第二个细节是唯一性校验。分类名称和分类编码都不能重复,Controller 层在新增和修改时都要做查重,否则数据库里出现两个“体育”分类,前台导航栏就会很奇怪。查重的 SQL 也简单,WHERE category_name = #{name}或者WHERE category_code = #{code},查到记录就返回“分类已存在”。
2.3 新闻列表与分页查询的常见坑
新闻列表是前台用户看到最多的页面,也是最容易出问题的地方。列表页一般有几个筛选条件:分类 ID、标题关键字、状态(后台用),然后按发布时间倒序排列。组合条件一多,SQL 动态拼接就成了不可避免的事。
MyBatis 的<if>标签在动态 SQL 里用得极多,一个标准的组合查询是这样写的:
<select id="selectNewsList" resultType="com.news.system.entity.NewsArticle"> SELECT * FROM news_article <where> <if test="categoryId != null and categoryId != ''"> AND category_id = #{categoryId} </if> <if test="title != null and title != ''"> AND title LIKE CONCAT('%', #{title}, '%') </if> <if test="status != null"> AND status = #{status} </if> </where> ORDER BY publish_time DESC LIMIT #{offset}, #{pageSize} </select>这里的<where>标签很聪明,它会自动处理掉第一个条件的 AND 前缀,不用手动加WHERE 1=1。模糊查询要用CONCAT('%', #{title}, '%')而不是直接在参数里拼%,这样做能避免 SQL 注入风险。
分页这块我强烈建议初学者先手工分页,LIMIT #{offset}, #{pageSize}配合计算offset = (current - 1) * size,逻辑清清楚楚。PageHelper 这类分页插件虽然方便,但它基于拦截器改 SQL 的原理对新手来说太黑了,翻车时很难排查。手工分页的代码写完,你心里对分页机制的把握是扎实的。后面真要用 PageHelper,再看官方文档也很快。
分页接口返回的数据结构要包含四个核心信息:当前页数据集合、总数、当前页码、每页条数。前端渲染时要用到总数来计算总页数,用当前页码来高亮当前页和判断“上一页”“下一页”是否可点击。
3. 关键功能实现与代码解析
3.1 项目初始化与核心配置
这套源码的初始化方式很标准,通过 Spring Initializr 生成基础项目,Maven 管理依赖。pom.xml 里最核心的依赖就是 Web 场景启动器和 MyBatis 启动器,如果用 Thymeleaf 还要引入对应的 starter。
一段典型的 pom 核心依赖如下:
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.18</version> <relativePath/> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.3.2</version> </dependency> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies>这里有个版本对应关系值得注意:Spring Boot 2.7.x 使用的是 MySQL Connector/J 8.0.x,driver-class-name 要写com.mysql.cj.jdbc.Driver。网上老教程里的com.mysql.jdbc.Driver是 5.x 的老写法,在新版本下会直接报错。驱动名称这个坑,几乎每个用 MySQL 8 的人都会踩一次。
application.yml 是 Spring Boot 项目里最重要的配置文件。数据源配置、MyBatis 配置、文件上传大小限制都在这里调整。核心配置如下:
server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/news_system?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 123456 mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.news.system.entity configuration: map-underscore-to-camel-case: true spring: servlet: multipart: max-file-size: 10MB max-request-size: 20MB数据库连接 URL 里的serverTimezone=Asia/Shanghai在 MySQL 8 下必须加,否则会因为时区问题报错。useUnicode=true&characterEncoding=utf8负责告诉驱动用 UTF-8 编码传输中文数据。MyBatis 的map-underscore-to-camel-case配置是神器,开启后数据库的 create_time 字段能自动映射到实体类的 createTime 属性,省掉一大堆手动映射。
3.2 基于MyBatis的持久层实现
前面的配置把环境搭好了,接下来就是 Mapper 层的实现。Mapper 接口只是定义方法签名,真正的 SQL 写在 resources/mapper 目录下的 XML 文件里。这个分离的设计让 SQL 修改不用动 Java 代码,尤其是毕设后期改需求时,改 XML 比改 Java 再重新编译要省事得多。
以新闻详情查询为例,前台点击一条新闻后,要展示新闻的标题、内容、作者、发布时间,同时要显示所属分类的名称。新闻表里只有 category_id,分类名在分类表里,这就需要联表查询:
<select id="selectNewsDetail" resultType="map"> SELECT a.*, c.category_name FROM news_article a LEFT JOIN news_category c ON a.category_id = c.id WHERE a.id = #{id} </select>一次联表查询就把两个表的数据查回来了,比先在新闻表查出 category_id,再查一次分类表高效得多。这也是 MyBatis 和 JPA 在思维方式上的差异,JPA 倾向于对象导航,MyBatis 倾向于直接写 SQL 拿结果。
浏览量自增这个细节容易被忽略。正确写法是发一条独立的 UPDATE 语句:
UPDATE news_article SET view_count = view_count + 1 WHERE id = #{id}这条 SQL 是原子操作,高并发下也不会丢更新。如果你先 SELECT 出来在 Java 里加 1 再 UPDATE,并发请求下数据就会覆盖丢失。虽然毕设场景没什么高并发,但这个写法上的好习惯最好一开始就养成。
新增新闻时,有一个需求是自动设置发布时间。这里推荐在 Java 代码里用LocalDateTime.now()直接赋值,而不是依赖数据库的 DEFAULT CURRENT_TIMESTAMP。原因很简单:如果你把 insert 语句里的 publish_time 字段省略,Java 里没赋值,那这条数据可能存了 NULL 进去,前台排序就会出现没有时间的脏数据。显式赋值是最可控的方式,也方便后期做定时发布之类的扩展。
3.3 后台管理功能与登录拦截
后台管理是系统的另一大块。管理员登录之后,进入后台能看到仪表盘、分类管理、新闻管理、系统管理等菜单。这套源码的后台权限控制方式非常简单且实用:登录成功后把用户信息放到 Session 里,然后定义一个拦截器,拦截/admin/**路径下的请求,判断 Session 里有没有用户,没有就重定向到登录页。
拦截器的核心逻辑如下:
public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Object user = request.getSession().getAttribute("loginUser"); if (user == null) { response.sendRedirect("/admin/login"); return false; } return true; } }然后在配置类里注册这个拦截器,并指定拦截路径和放行路径:
@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginInterceptor()) .addPathPatterns("/admin/**") .excludePathPatterns("/admin/login", "/admin/doLogin"); } }这个方案简单到没有什么学习成本,但足以应对毕设的权限需求。密码加密必须用 BCryptPasswordEncoder,这是 Spring Security 自带的加密器,比 MD5 可靠得多。MD5 加不加盐都是过了时的方案,彩虹表一查一个准。教师问起来,直接回答“用 BCrypt 加盐哈希存储密码,即使数据库泄露也无法反推出明文”,这句话说出来老师就知道你关注过安全问题。
后台新闻管理页面还有一个高频操作用途是上传封面图。图片上传后要保存到服务器本地磁盘,同时在数据库中存文件的访问 URL。这里面有一个 Spring Boot 新手必踩的坑:上传的图片存在本地磁盘路径后,默认是无法通过浏览器 URL 直接访问的。必须额外配置静态资源映射:
@Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/upload/**") .addResourceLocations("file:" + uploadPath); }addResourceHandler("/upload/**")表示浏览器访问 /upload/xxx.jpg 时,Spring 会去file:指定的本地磁盘路径找文件。这个配置不做,前端 img 标签的 src 就永远显示裂图。至于把图片传到云存储 OSS 之类的,那是后话,毕设阶段本地存储完全够用。
3.4 前端页面与前后端交互
前端部分,这套源码有两种形态,一种是服务端渲染的 Thymeleaf 版本,一种是前后端分离的 Vue 版本。对毕设而言,Thymeleaf + Bootstrap 是最省事的组合,页面直接用模板语法从后端拿数据,不用考虑跨域,不用 Mock 数据,开发效率极高。前后端分离版本则要处理跨域配置、接口联调、Token 传递这些额外议题,学习成本明显更高。
热搜词里“springboot vue 前后端分离”出现频率很高,如果你选择这个方案,需要额外做好三件事。第一是配置跨域,在 Spring Boot 里实现 CorsFilter 或实现 WebMvcConfigurer 的 addCorsMappings 方法,允许前端开发服务器的 Origin 进行跨域访问。第二是统一返回 JSON 结构,前端拿到{code: 200, data: {...}}后,根据 code 判断成功还是失败,这一步在 common 包里封装 Result 类就是为了这个。第三是接口鉴权,前后端分离后登录态不适合用 Session 存,需要改成 JWT 之类的 Token 方案,前端每次请求在 Header 里带 Token,后端用拦截器解析。
我个人的建议是:如果你是 Java 后端方向,优先做 Thymeleaf 版本,把精力集中在后端逻辑上,答辩时也容易讲透彻。如果指导老师特别强调前后端分离,那再上 Vue,但要有额外的时间预期,毕竟前端调试的坑不比后端少。
前台的新闻列表页推荐加一个简单的搜索框。这个功能表面上是查标题关键字,实际上一做出来,整个系统的完整度立刻上来了。导航栏按分类展示是“分类浏览”,搜索框是“站内检索”,两个入口一组合,用户找新闻的路径就闭环了。搜索接口就是前面那段<where>标签组合查询,参数传 title 就行,实现成本几乎为零,但答辩演示时效果很好。
4. 常见问题与排查技巧实录
4.1 启动失败的典型原因
我每次帮别人看毕设源码,花时间最多的就是排查启动失败。Spring Boot 项目启动失败的报错一般分为三类:端口被占用、数据库连接失败、依赖冲突。
端口被占用是最常见的,启动日志里出现Port 8080 was already in use就是它。解决办法要么杀掉占用进程,要么在 application.yml 里改端口,比如改成 8081。这里有个实用命令记录一下:Windows 下netstat -ano | findstr 8080查 PID,然后taskkill /F /PID 进程号杀掉;Linux 或 macOS 下用lsof -i:8080和kill -9 PID。
数据库连接失败的报错特征是Access denied for user 'root'@'localhost'或者Unknown database 'news_system'。前者是用户名或密码错了,后者是数据库没创建或者名字没对上。排查思路很简单:先用 Navicat 或命令行试试能不能用配置里的账号密码连上库。本地能连上,项目肯定也能连上,连不上基本就是配置里的用户名、密码、库名、端口这四个里有一个写错了。
依赖冲突这个问题隐蔽一些,典型表现是启动时 NoSuchMethodError 或 ClassNotFoundException。用 Maven 的话,最常用的一条命令是mvn dependency:tree,查看依赖树里有没有同一个 jar 的不同版本。Spring Boot 的 parent 已经管理了大部分依赖版本,自己手动加依赖时不要再写 version 标签,让 parent 统一管理。我在项目里见过有人手动指定了一个很老的 fastjson 版本,结果和 Spring Boot 内置的 Jackson 在序列化时互相干扰,数据输出直接乱套。
4.2 Mapper 注入失败的排查
@Autowired报错可能是 Spring Boot + MyBatis 项目里最让新手崩溃的问题。报错信息类似于Field newsMapper in com.news.system.service.impl.NewsServiceImpl required a bean of type 'com.news.system.mapper.NewsMapper' that could not be found。
这个报错绝大多数原因是 Mapper 接口没有注册到 Spring 容器里。解决办法有两种。第一种是在 Mapper 接口上直接加@Mapper注解,第二种是在启动类或者配置类上统一加@MapperScan("com.news.system.mapper")。我推荐用@MapperScan,好处是新增 Mapper 时不用每个接口都手动加注解,扫描范围一目了然。
还有一种不太容易注意到的情况是 Mapper XML 里的 namespace 写错了。MyBatis 要求 namespace 必须是对应的 Mapper 接口的全限定名,比如com.news.system.mapper.NewsMapper。如果 namespace 写成了别的,即使接口注入成功,真正执行 SQL 时也会报Invalid bound statement (not found)。这个报错一出现,优先检查两件事:检查 XML 文件是否在 mapper-locations 配置的目录下,检查 namespace 和接口全限定名是否一字不差。
4.3 中文乱码与时间格式问题
中文乱码在后台新闻管理里特别常见,尤其是通过表单提交新闻标题和正文时出现???或一堆乱码。产生原因通常是字符集在某个环节没有统一为 UTF-8。排查链路要从下往上捋:数据库表字符集(建议 utf8mb4)、数据库连接 URL 里的 characterEncoding=utf8、Spring Boot 本身的过滤器配置(大多数情况下 spring-boot-starter-web 已经自动配置了 CharacterEncodingFilter)。
数据库表没有指定字符集的话,在 MySQL 里执行建表语句时可以加上:
CREATE TABLE news_article ( ... ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;如果已经建表了,可以用 ALTER 语句修改。utf8mb4 是 utf8 的超集,能存 emoji 和更多特殊字符,做内容管理类系统建议直接用 utf8mb4。
时间格式问题则集中在 JSON 返回给前端时,LocalDateTime 序列化后变成一串数字或者显示格式难看。处理方法是在 application.yml 里配置:
spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8列表页的时间就会显示成2025-01-15 10:30:00这个可读性很高的格式。假如前端还要求展示“几分钟前”这类相对时间,那是前端模板的活,后端只管把标准时间传过去就行。
4.4 安全与性能细节
最后一个板块我想把安全性和性能这些容易被浮于表面的东西讲透一点。虽然毕设不会上生产环境,但代码里体现出的意识,是答辩评分的隐形分。
SQL 注入的防范就是坚定使用#{}占位符,不要用${}拼接参数。${}在大多数场景下都不该出现,它唯一的合理用途是动态表名或动态排序字段,而这些在毕设里几乎用不到。写 MyBatis 语句时但凡犹豫该用哪个,选#{}永远没错。
前端页面上的 XSS 问题也不能完全无视,新闻正文如果允许直接输入 HTML,用户提交的<script>标签可能会被浏览器执行。Thymeleaf 的th:text默认会做 HTML 转义,比用th:utext安全得多。除非你明确知道自己在干什么,否则展示用户输入内容时永远用th:text。
性能方面最值得做的一件事是给查询频率高的表加索引。新闻列表的查询条件通常是分类 ID 和发布时间,所以建立联合索引:
ALTER TABLE news_article ADD INDEX idx_category_time (category_id, publish_time);这个索引能让按分类浏览新闻的查询速度明显提升,在新闻数据量稍大时效果更明显。索引的原理很简单,相当于给数据建了目录,查找时不用全表扫描。答辩时如果老师问“系统性能怎么优化”,你能说出这个联合索引的设计思路,比背一堆缓存理论实在得多。
5. 从毕设源码到高分项目的进阶思路
5.1 如何在答辩时把系统讲清楚
很多同学功能做完了,一到答辩就讲得稀里糊涂。我建议按照“项目背景、功能分解、表结构设计、核心流程、技术亮点、演示”这个顺序来讲。第一页展示系统截图时,直接说“这是一个基于 Spring Boot 的新闻分类系统,前台面向访客提供分类浏览和新闻检索,后台面向管理员提供新闻和分类的维护”,这句话就把定位定死了。
讲技术时要主动抛出细节。比如讲到分页查询,重点说清楚为什么要用 LIMIT offset 公式,页码和条数是怎么算出来的。讲到登录拦截,演示一下不登录直接访问后台地址会被重定向到登录页,这个动作用截图展示出来,说服力是很强的。讲到密码加密,对比一下 MD5 和 BCrypt,一句话就能体现深度。
如果老师问 Spring Boot 的好处在哪,不要背“自动配置、约定大于配置”这种概念。讲一个具体例子:引入 spring-boot-starter-web 之后,内嵌的 Tomcat 帮你把服务器问题解决了,你直接写 Controller 就能跑起来,而不用像 SSM 时代那样手动配置 Tomcat。概念加案例,说服力完全不同。
5.2 值得扩展的高价值功能
这套系统的底子非常干净,扩展空间很大,而扩展功能正是把毕设从“及格”拉到“优秀”的关键。如果时间充裕,我建议按优先级考虑下面几个方向。
第一个是达到“集成中间件”的层次。引入 Redis 缓存分类列表,减少每次访问前台都查数据库的压力,这是一个很小但很典型的“缓存”技术体现。引入 Spring Task 做定时任务,可以实现新闻定时发布,比如设置凌晨 0 点自动发布某篇新闻,这里是@Scheduled注解加上一个定时扫描 SQL 就能实现。这两个扩展点都不算难,但它们能在答辩时向老师证明你掌握的不只是 CRUD。
第二个方向是做内容维度的增强。给新闻表加一个recommend字段,实现“推荐新闻”功能;加评论表,实现用户评论功能;用 ECharts 做一个后台分类统计图表,展示每类新闻的数量占比。这些功能在业务上很自然,加进去页面立刻显得丰满,代码复杂度也完全可控。
第三个方向是引入流程引擎,比如热搜词里反复出现的 springboot 使用 flowable。如果你的毕设想再上一个台阶,把新闻发布流程变成“编辑提交 -> 主编审核 -> 发布”这种带审批流转的状态机,那可以引入 Flowable 或者 Activiti。但这里我给一个实在的建议:流程引擎本身学习成本不低,如果不是选题本身就和审批流程强相关,不要硬上。把审核状态字段做成 status 的几档流转,一样能讲清楚业务规则。
最后再聊一个很多人忽视的事情,一份合格的源码交付物不仅要能跑,文档也要跟上。至少要有三份资料:数据库初始化 SQL 脚本、项目部署说明、核心流程的文字说明。数据库脚本要用带CREATE DATABASE和USE的完整版,别人拿到之后导入就能跑。部署说明要把 JDK、Maven、MySQL 的版本写清楚,避免对方环境对不上。这三份东西不仅是为了让别人能跑通源码,更是你梳理自己项目逻辑的过程。我见过不少同学源码写得还行但交上去缺这少那,指导老师印象分直接打了折扣。
这套新闻分类系统做下来,我最大的体会是:技术栈不在多,而在真正理解每个选型为什么这么定。Spring Boot 负责把项目快速跑起来,MyBatis 负责让 SQL 可控,MySQL 负责把关系型数据存明白,前端负责把内容呈现出来,这一条链路本身就是一个完整的 Web 全栈认知闭环。把这套东西吃透,往后看其他 Spring Boot 项目都会轻松很多。