Spring Boot新闻系统四层架构与安全实践
2026/9/13 12:03:12 网站建设 项目流程

简介:这是一套面向Java初学者与毕业设计学生的Spring Boot实战项目源码,聚焦新闻内容管理场景,适用于企事业单位、高校等组织的内部信息发布与维护需求。资源包共592个文件,涵盖80个Java后端业务类、106个Vue前端组件、39个HTML页面及31个JS交互脚本,辅以MySQL建表SQL、Spring Boot配置yml、Maven构建bat脚本等关键文件,完整呈现前后端分离架构下的开发闭环;压缩包大小为13.59MB,结构清晰,含环境配置、运行部署、数据库初始化等实用工具。已有117人学习下载,读者可直接获取可运行的全功能新闻系统:包含用户权限管理、多级新闻CRUD、评论审核、轮播图与公告配置、响应式前端适配等六大模块,并附带一键运行脚本(如运行.bat、安装maven环境.bat),大幅降低本地部署门槛,是理解Spring Boot企业级开发流程的优质入门范例。

1. 这不是又一个“Spring Boot + CRUD”模板,而是一套可落地的新闻内容生产闭环系统

你下载的(源码)基于Spring Boot框架的新闻管理系统.zip,表面看是教学级项目,实际藏着新闻类业务最典型的四层架构落地细节:从编辑端富文本提交、后台多级审核流控制、前端响应式列表渲染,到数据库层面的热点新闻缓存穿透防护。它不依赖任何云服务或第三方 CMS,所有功能都由 Spring Boot 原生组件驱动——JPA 处理稿件元数据与分类关系,Spring Security 实现角色分级(记者/编辑/主编/管理员),Thymeleaf 或 Vue CLI(热词中隐含前端构建线索)完成静态资源编译与路由隔离。尤其注意browserslistrcsass的存在,说明该项目已预置现代 CSS 工程化能力,能直接支持 IE11+ 到 Chrome 120+ 的渐进式兼容策略。适合两类人:刚学完 Spring Boot 基础想验证真实业务分层逻辑的开发者,以及需要快速搭建内部新闻简报平台的中小团队技术负责人。它不解决“如何做爆款”,但能让你看清一条新闻从草稿到上线的每一道权限闸门和数据流转路径。

2. 拆解项目结构:为什么用 Spring Boot 而不是纯 Spring MVC?四层架构如何对应源码目录

2.1 Spring Boot 的核心价值:用自动配置规避重复造轮子,而非简化 CRUD

传统 Spring MVC 项目需手动配置 DispatcherServlet、ViewResolver、DataSource、TransactionManager 等 10+ Bean;而本项目通过spring-boot-starter-webspring-boot-starter-data-jpaspring-boot-starter-security三个 starter,将这些配置压缩为application.yml中 5 行代码。例如:

# application.yml 片段 spring: datasource: url: jdbc:mysql://localhost:3306/news_db?useSSL=false&serverTimezone=Asia/Shanghai username: root password: 123456 jpa: hibernate: ddl-auto: update show-sql: true properties: hibernate: format_sql: true

提示:ddl-auto: update仅用于开发环境。生产部署必须改为validate并配合 Flyway 或 Liquibase 执行 SQL 脚本,否则表结构变更会丢失历史版本记录。

该配置背后是 Spring Boot 的DataSourceAutoConfigurationHibernateJpaAutoConfiguration自动装配机制——它扫描 classpath 下的DataSource类型 Bean,若存在且未被@ConditionalOnMissingBean排除,则注入默认 HikariCP 连接池;再根据spring.jpa.*属性生成LocalContainerEntityManagerFactoryBean。这种“约定优于配置”大幅降低新成员上手成本,但代价是调试时需理解@AutoConfigureAfter的加载顺序。比如SpringBootWebSecurityConfiguration必须在DataSourceAutoConfiguration之后执行,否则无法注入UserDetailsService所需的 JPA Repository。

2.2 四层架构在源码中的物理映射:controller → service → repository → entity

项目目录严格遵循 Spring Boot 四层分层规范,每一层职责边界清晰:

目录路径核心职责关键实现特征
com.example.news.controller接收 HTTP 请求,校验参数,调用 Service使用@Valid注解绑定NewsFormDTO,返回ResponseEntity<NewsDTO>统一格式
com.example.news.service封装业务逻辑,协调多个 Repository 操作NewsService包含publish()方法,内部调用newsRepository.save()auditLogRepository.save()两个事务性操作
com.example.news.repository定义数据访问接口,继承JpaRepositoryNewsRepository声明findByStatusAndPublishTimeBetween()方法,Spring Data JPA 自动生成 JPQL 查询
com.example.news.entity映射数据库表,定义 JPA 注解News类使用@Entity@Table(name="t_news")@Lob标注正文字段,避免TEXT类型被误映射为VARCHAR(255)

特别注意News实体中的@Lob注解:

@Entity @Table(name = "t_news") public class News { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(name = "title", length = 200) private String title; @Lob // 关键:声明 content 为大对象,适配 MySQL TEXT / PostgreSQL BYTEA @Column(name = "content") private String content; @Column(name = "status") @Enumerated(EnumType.STRING) private NewsStatus status; // 枚举类型,值为 DRAFT/PENDING/PUBLISHED/REJECTED }

若忽略@Lob,Spring Data JPA 默认将String映射为VARCHAR(255),当新闻正文超长时触发DataTruncation异常。这是新手在导入新闻数据时最常见的失败点。

2.3 Maven 作为构建中枢:依赖管理、插件编排与多环境打包

pom.xml是整个项目的构建契约。本项目依赖项明确区分三类:

  • 运行时依赖spring-boot-starter-webspring-boot-starter-data-jpamysql-connector-java(注意热词中com.mysql:mysql-connector-j:release报错提示,说明版本需匹配 MySQL 8.x)
  • 编译期依赖lombok(减少getter/setter模板代码)、validation-api(支持@NotBlank校验)
  • 构建插件maven-compiler-plugin(指定 Java 17)、spring-boot-maven-plugin(生成可执行 fat jar)

关键插件配置如下:

<plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <image> <builder>paketobuildpacks/builder:tiny</builder> </image> </configuration> </plugin>

注意:此配置启用 Spring Boot 3.x+ 的 Cloud Native Buildpacks 打包方式,生成 OCI 镜像而非传统 jar。若你的环境未安装 Docker,需注释掉<image>块,改用默认 jar 打包。

Maven 仓库配置也影响构建稳定性。国内用户应修改~/.m2/settings.xml,添加阿里云镜像:

<mirrors> <mirror> <id>aliyunmaven</id> <mirrorOf>*</mirrorOf> <name>阿里云公共仓库</name> <url>https://maven.aliyun.com/repository/public</url> </mirror> </mirrors>

否则mvn clean install可能因spring-boot-starter-parent下载超时而失败——这正是热词中高频出现的maven下载maven配置阿里云仓库的真实场景。

3. 前端工程化落地:Sass 编译链路与 browserslistrc 的兼容性控制

3.1 Sass 不是“写 CSS 的另一种写法”,而是构建时样式逻辑的封装层

项目中src/main/resources/static/css下的.scss文件并非直接被浏览器解析,而是通过 Maven 插件或 Node.js 工具链编译为标准 CSS。常见两种集成方式:

  • Maven 方式:使用sass-maven-plugin,在pom.xml中声明:

    <plugin> <groupId>nl.geodienstencentrum.maven</groupId> <artifactId>sass-maven-plugin</artifactId> <version>3.9.2</version> <executions> <execution> <goals> <goal>update-stylesheets</goal> </goals> </execution> </executions> <configuration> <sassSourceDirectory>${project.basedir}/src/main/resources/static/scss</sassSourceDirectory> <outputDirectory>${project.basedir}/src/main/resources/static/css</outputDirectory> </configuration> </plugin>
  • Node.js 方式:更主流,依赖package.json中的sass包。热词中dart sass安装node18对应的sass版本指向此路径:

    { "devDependencies": { "sass": "^1.70.0", "sass-loader": "^13.3.2" } }

    提示:Node 18 对应的 Dart Sass 最低版本为1.60.0,低于此版本在@use规则解析时会报错。执行npm install前务必确认node -v输出为v18.x

无论哪种方式,Sass 的核心价值在于变量复用与嵌套逻辑。例如variables.scss定义主题色:

// src/main/resources/static/scss/_variables.scss $primary-color: #2c3e50; $secondary-color: #3498db; $font-size-base: 16px; // src/main/resources/static/scss/main.scss @use 'variables'; .news-list { background-color: variables.$primary-color; font-size: variables.$font-size-base; &__item { color: variables.$secondary-color; } }

编译后生成标准 CSS,无运行时开销。

3.2 browserslistrc 是前端兼容性的宪法,不是可有可无的配置文件

.browserslistrc内容直接决定 Babel、Autoprefixer、CSS Nano 的处理范围。本项目典型配置如下:

> 1% last 2 versions not dead not ie <= 11

这意味着:

  • 覆盖全球使用率超 1% 的浏览器版本(如 Chrome 115+、Firefox 116+)
  • 包含最新两个主版本(Chrome 120/119、Edge 120/119)
  • 排除已停止安全更新的浏览器(IE 11 及更旧版本)

当执行npm run build时,Webpack 会读取此文件,触发 Autoprefixer 插件为flexgrid等特性自动添加-webkit--ms-前缀。若删除该文件,构建产物可能在 Safari 15.6 上因缺少-webkit-flex而布局错乱——这正是热词中sass编译后仍需验证的实际问题。

3.3 Thymeleaf 与 Vue 的混合渲染:服务端模板与客户端 SPA 的边界在哪里

项目前端采用“服务端渲染为主,局部增强为辅”策略:

  • 新闻列表页、详情页由 Thymeleaf 渲染,URL 为/news/list/news/detail/{id}
  • 富文本编辑器、图片上传组件使用 Vue 3 Composition API,通过<div id="editor-app"></div>挂载

关键整合点在news-edit.html

<!-- Thymeleaf 模板 --> <div th:fragment="content"> <div id="editor-app" >management: endpoints: web: exposure: include: health,info,metrics,prometheus # 仅暴露必要端点 endpoint: health: show-details: when_authorized # 详情仅对认证用户可见 endpoints: web: base-path: /manage # 修改根路径,增加探测难度

同时配合 Spring Security 配置:

@Configuration @Order(ManagementServerProperties.ACCESS_OVERRIDE_ORDER) public class ActuatorSecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.requestMatcher(EndpointRequest.toAnyEndpoint()) .authorizeHttpRequests(authz -> authz .requestMatchers(EndpointRequest.to("health", "info")).permitAll() .requestMatchers(EndpointRequest.toAnyEndpoint()).hasRole("ADMIN") ); return http.build(); } }

4.2 JPA 层防 SQL 注入:为什么@Query比字符串拼接更安全

新闻搜索功能常需动态条件,易引发注入漏洞。错误写法:

// 危险!字符串拼接导致注入 @Query("SELECT n FROM News n WHERE n.title LIKE '%" + keyword + "%'") List<News> searchByTitle(String keyword);

正确做法是使用参数化查询:

// 安全:JPA 自动转义参数 @Query("SELECT n FROM News n WHERE n.title LIKE %:keyword%") List<News> searchByTitle(@Param("keyword") String keyword); // 或使用方法名查询(推荐) List<News> findByTitleContaining(String keyword);

Spring Data JPA 将keyword作为 PreparedStatement 参数传入,数据库驱动自动处理引号转义。即使传入keyword = "'; DROP TABLE t_news; --",最终执行的 SQL 也是:

SELECT * FROM t_news WHERE title LIKE '%\'; DROP TABLE t_news; --%'

4.3 文件上传的双重校验:ContentType 检查 + 后缀白名单

新闻配图上传需防范 WebShell 注入。NewsController中的上传方法应包含:

@PostMapping("/upload") public ResponseEntity<String> uploadImage( @RequestParam("file") MultipartFile file, HttpServletRequest request) { // 1. Content-Type 白名单 String contentType = file.getContentType(); if (!Arrays.asList("image/jpeg", "image/png", "image/gif").contains(contentType)) { return ResponseEntity.badRequest().body("不支持的图片格式"); } // 2. 文件后缀二次校验(防止伪造 Content-Type) String originalFilename = file.getOriginalFilename(); String extension = FilenameUtils.getExtension(originalFilename).toLowerCase(); if (!Arrays.asList("jpg", "jpeg", "png", "gif").contains(extension)) { return ResponseEntity.badRequest().body("文件扩展名非法"); } // 3. 重命名存储,避免路径遍历 String newFilename = UUID.randomUUID() + "." + extension; Path uploadPath = Paths.get("uploads", newFilename); Files.createDirectories(uploadPath.getParent()); Files.write(uploadPath, file.getBytes()); return ResponseEntity.ok("/uploads/" + newFilename); }

提示:FilenameUtils来自 Apache Commons IO,需在pom.xml中添加依赖:

<dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.11.0</version> </dependency>

5. 生产部署调优:JVM 参数、连接池配置与热点新闻缓存穿透防护

5.1 JVM 参数设置:为什么 -Xmx 不等于可用堆内存?

Spring Boot 应用默认使用-Xmx指定最大堆,但新闻系统需额外预留元空间与直接内存。推荐启动参数:

java -Xms512m -Xmx1024m \ -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m \ -XX:+UseG1GC -XX:MaxGCPauseMillis=200 \ -Dfile.encoding=UTF-8 \ -jar news-system.jar
  • -XX:MetaspaceSize:避免频繁触发 Metaspace GC,新闻系统加载大量 Controller/Service 类
  • -XX:+UseG1GC:G1 垃圾收集器适合大堆(>4GB)且停顿可控,但本项目 1GB 堆已足够
  • -Dfile.encoding=UTF-8:强制文件读写编码,防止 Linux 环境下中文乱码

5.2 HikariCP 连接池关键参数:空闲连接与最大生命周期

application.yml中的数据库连接池配置需精细化:

spring: datasource: hikari: maximum-pool-size: 20 # 并发请求峰值预估(新闻首页 QPS × 2) minimum-idle: 5 # 保持 5 个空闲连接,避免冷启动延迟 idle-timeout: 600000 # 空闲 10 分钟后释放 max-lifetime: 1800000 # 连接最长存活 30 分钟,强制重连防 DB 连接超时 connection-timeout: 30000 # 获取连接超时 30 秒 validation-timeout: 3000 # 连接校验超时 3 秒 leak-detection-threshold: 60000 # 60 秒未归还连接触发泄漏告警

5.3 缓存穿透防护:热点新闻查询的布隆过滤器实践

当黑客构造大量不存在的newsId(如/news/detail/999999999)发起请求,数据库将承受无效查询压力。解决方案是在 Redis 层前置布隆过滤器:

@Component public class NewsBloomFilter { private static final int EXPECTED_INSERTIONS = 100000; private static final double FPP = 0.01; // 1% 误判率 private final BloomFilter<Long> bloomFilter; public NewsBloomFilter(RedisTemplate<String, Object> redisTemplate) { // 从 Redis 加载已有 ID 构建布隆过滤器(首次启动时) Set<String> ids = redisTemplate.opsForSet().members("news:all:ids"); this.bloomFilter = BloomFilter.create( Funnels.longFunnel(), EXPECTED_INSERTIONS, FPP ); ids.forEach(id -> bloomFilter.put(Long.parseLong(id))); } public boolean mightContain(Long newsId) { return bloomFilter.mightContain(newsId); } }

NewsService.findById()中调用:

public News findById(Long id) { // 1. 先查布隆过滤器 if (!newsBloomFilter.mightContain(id)) { throw new NewsNotFoundException("新闻不存在"); } // 2. 再查 Redis 缓存 String cacheKey = "news:" + id; News news = redisTemplate.opsForValue().get(cacheKey); if (news != null) return news; // 3. 最后查数据库 news = newsRepository.findById(id) .orElseThrow(() -> new NewsNotFoundException("新闻不存在")); // 4. 写入缓存与布隆过滤器 redisTemplate.opsForValue().set(cacheKey, news, 1, TimeUnit.HOURS); return news; }

布隆过滤器空间效率极高(10 万 ID 仅需 125KB 内存),且误判率可控。这是应对新闻系统突发流量的底层防御手段。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询