Java新闻管理系统:基于Spring Boot的内容生命周期与权限状态机实现
2026/9/16 0:24:26 网站建设 项目流程

简介:本资源是一套完整的Java Web毕业设计实战项目,面向计算机专业本科生及Java初学者,聚焦新闻发布与管理业务场景,提供从需求分析到系统部署的全流程学习材料。压缩包共45个文件,含40张系统功能界面截图(PNG)、1份数据库脚本(SQL)、1份README说明(TXT)、1份项目报告(DOC)、1份答辩PPT(PPT)及1个源代码压缩包(ZIP),总大小35.49MB;截图覆盖登录、新闻列表、编辑、权限配置等核心操作界面,SQL文件支持快速建库建表,文档与PPT清晰呈现设计思路与技术选型依据。已有91人学习下载,读者可直接复现基于Servlet+JSP+JDBC或Spring+MyBatis的双技术栈实现方案,掌握MVC分层开发、用户角色权限控制、前后端交互及数据库ER建模等关键能力,是Java Web全栈开发入门与课程设计落地的高实用性参考范例。

1. 这不是又一个“学生管理系统”模板:基于 Java 的新闻发布及管理系统,解决的是真实内容生产流中的权限断层与发布闭环问题

很多计算机专业同学拿到“新闻发布及管理系统”毕业设计选题时,第一反应是套用 SSM 或 Spring Boot 模板改个表名、换个登录页——结果答辩被问“新闻审核流程怎么落地?”“编辑和管理员权限边界在哪?”“数据库里如何体现草稿→待审→已发布状态迁移?”当场卡壳。这个项目标题里的“新闻发布及管理系统”,核心不在“有页面能增删改查”,而在于构建一条可追溯、可管控、可回滚的内容生命周期链路:从记者提交初稿、编辑加标签/配图、主编终审、定时发布,到下架归档、历史版本比对。它用 Java 做后端主干,不是因为“Java 适合写毕业设计”,而是因为其成熟的事务控制(@Transactional)、细粒度权限框架(Spring Security + RBAC)、以及与 MySQL 事务日志、Redis 缓存穿透防护的天然协同能力。适合正在做毕业设计、需要展示工程思维而非仅 CRUD 能力的同学;也适合想快速复用一套内容型后台骨架的校内信息化项目组——它不追求 Vue3 的炫酷动效,但每张表字段设计都对应真实业务动作,每个 Controller 方法签名都暴露了状态机流转意图。


2. 用 Spring Boot + MyBatis-Plus 搭建新闻领域模型:从 ER 图到实体类的精准映射

2.1 为什么不用纯 JPA?MyBatis-Plus 在毕业设计场景下的三重优势

在毕业设计中选择 MyBatis-Plus 而非 JPA,不是技术倒退,而是对可控性、调试透明度和 SQL 可观测性的主动选择。JPA 的二级缓存抽象层在小型项目中反而增加理解成本;而 MyBatis-Plus 提供的LambdaQueryWrapper既能类型安全地构造条件,又允许在.last("LIMIT 10")中直接嵌入分页逻辑,避免 Hibernate 的 N+1 查询陷阱。更重要的是,其@TableField(fill = FieldFill.INSERT)注解可自动填充create_timeupdate_time,无需在 Service 层手动 set;配合MetaObjectHandler,连create_by(操作人ID)也能在插入时自动注入——这对答辩时演示“谁在什么时候发布了什么新闻”至关重要。此外,IService接口自带的saveBatch()updateBatchById()方法,让批量导入新闻、批量下架操作一行代码即可完成,远比手写 foreach + update 更贴近真实运维场景。

提示:不要在application.yml中配置mybatis-plus.configuration.map-underscore-to-camel-case: true后就以为万事大吉。MySQL 表字段若为is_top(置顶标志),Java 实体类必须声明为private Boolean isTop;,而非private Boolean is_top;,否则 MP 无法自动映射。这是答辩高频扣分点。

2.2 新闻核心实体类:用枚举约束状态流转,拒绝 magic number

新闻状态不能靠int status = 1这种硬编码维护。本系统定义NewsStatusEnum枚举,明确限定合法值:

public enum NewsStatusEnum { DRAFT(0, "草稿"), PENDING_REVIEW(1, "待审核"), REJECTED(2, "已驳回"), PUBLISHED(3, "已发布"), ARCHIVED(4, "已归档"); private final int code; private final String desc; NewsStatusEnum(int code, String desc) { this.code = code; this.desc = desc; } // 提供根据 code 查找枚举实例的静态方法,用于 Controller 参数解析 public static NewsStatusEnum fromCode(int code) { for (NewsStatusEnum e : values()) { if (e.code == code) return e; } throw new IllegalArgumentException("Invalid news status code: " + code); } }

对应News实体类中,status字段类型为NewsStatusEnum,而非Integer

@TableField("status") private NewsStatusEnum status; // MyBatis-Plus 自动调用 valueOf() 或 fromCode()

这样做的好处是:

  • 数据库status字段仍为TINYINT(节省空间),但 Java 层获得编译期类型安全;
  • switch(status)语句可直接 case 枚举常量,避免case 1:这种易错写法;
  • 返回给前端的 JSON 自动序列化为"status": "PUBLISHED",语义清晰,无需额外字典表查询。

2.3 关键表结构与外键约束:ER 图落地时必须检查的三个细节

毕业设计答辩常被追问“你的数据库设计怎么保证数据一致性?”。答案就藏在三处外键与索引设计中:

表名字段约束类型作用说明
newscategory_idnews_category.idON DELETE RESTRICT分类删除前必须清空该分类下所有新闻,防止孤儿数据
newsauthor_idsys_user.idON DELETE SET NULL记者离职后,其发布的新闻仍保留,作者字段置为 NULL,符合 GDPR 隐私要求
news_audit_log(news_id, audit_step)UNIQUE INDEX同一新闻在“初审”“终审”环节只能有一条记录,避免重复审批

特别注意news_audit_log表的设计:它不存储审核人姓名,只存auditor_id(关联sys_user.id),并在@Select("SELECT u.real_name FROM sys_user u WHERE u.id = #{auditorId}")中动态联查——这样当用户姓名修改时,历史审核记录仍显示当时的真实姓名,而非当前姓名,满足审计溯源要求。


3. 实现新闻发布全流程:从草稿提交到定时上线的四步状态机

3.1 Controller 层:用@Valid@RequestBody封装业务意图,而非裸露字段

新闻提交接口不接受Map<String, Object>或泛型JSONObject,而是定义强类型 DTO:

@PostMapping("/draft") public Result<?> saveAsDraft(@Valid @RequestBody NewsDraftDTO dto) { // dto 包含 title, content, categoryId, coverImage, tags(字符串数组) // @Valid 触发 @NotBlank、@Size(max=50) 等校验,失败时自动返回 400 newsService.saveAsDraft(dto); return Result.success(); }

NewsDraftDTOtags字段声明为String[] tags,而非String tags。这样前端传["Java", "毕业设计"],后端直接接收为数组,省去split(",")的字符串解析错误风险。同时,在@TableField(exist = false)标记该字段不映射数据库,由 Service 层拆解后存入news_tag_rel关联表——这体现了“DTO 负责传输,Entity 负责持久化”的分层原则,也是答辩时展示架构意识的关键点。

3.2 Service 层:用@Transactional划定状态变更原子边界

新闻从“待审核”变为“已发布”,绝非简单update status=3。它必须包含:更新新闻状态、记录审核日志、推送至 Redis 缓存、触发 Elasticsearch 同步(若集成)。这些操作要么全成功,要么全回滚:

@Transactional(rollbackFor = Exception.class) public void publish(Long newsId, Long auditorId) { // 1. 校验当前状态是否为 PENDING_REVIEW News news = newsMapper.selectById(newsId); if (!NewsStatusEnum.PENDING_REVIEW.equals(news.getStatus())) { throw new BusinessException("仅待审核状态可发布"); } // 2. 更新状态并设置发布时间 news.setStatus(NewsStatusEnum.PUBLISHED); news.setPublishTime(LocalDateTime.now()); news.setAuditorId(auditorId); newsMapper.updateById(news); // 3. 记录审核日志(独立 insert,非级联) AuditLog log = new AuditLog(newsId, auditorId, "发布", "稿件已上线"); auditLogMapper.insert(log); // 4. 清除旧缓存(key: news:detail:{id}) redisTemplate.delete("news:detail:" + newsId); // 5. 异步通知 ES(此处用线程池,非 @Async,避免事务未提交时 ES 读到脏数据) esUpdateExecutor.submit(() -> esService.syncNews(newsId)); }

注意:@Async方法不能在同一个 Service 类内调用,否则事务上下文丢失。此处用ThreadPoolTaskExecutor显式提交,确保 ES 同步发生在事务 commit 之后。

3.3 定时发布功能:用@Scheduled+cron表达式实现零侵入调度

毕业设计常忽略“定时发布”需求,但企业级系统必备。本方案不引入 Quartz,而是用 Spring Boot 原生@Scheduled

@Component public class ScheduledNewsPublisher { @Autowired private NewsService newsService; // 每分钟扫描一次,检查 publish_time <= now 且 status == DRAFT 的新闻 @Scheduled(cron = "0 */1 * * * ?") public void checkAndPublishScheduledNews() { LocalDateTime now = LocalDateTime.now(); List<News> scheduledNews = newsMapper.selectList( new QueryWrapper<News>() .eq("status", NewsStatusEnum.DRAFT.getCode()) .le("publish_time", now) ); for (News news : scheduledNews) { try { // 复用已验证的 publish 逻辑,保证事务一致性 newsService.publish(news.getId(), -1L); // -1L 表示系统自动发布 } catch (Exception e) { // 记录错误日志,但不抛出,避免阻塞后续扫描 log.error("Auto-publish failed for news {}", news.getId(), e); } } } }

关键参数说明:

  • cron = "0 */1 * * * ?"表示“秒=0,分=每1分钟,时=任意,日=任意,月=任意,周=任意,年=任意”,即每分钟执行一次;
  • le("publish_time", now)使用 MyBatis-Plus 的le(less than or equal)方法,生成publish_time <= ?SQL,避免手写<=字符串拼接;
  • publish_time字段类型为LocalDateTime,MySQL 对应datetime,确保时区无关(不使用timestamp,避免夏令时偏移)。

4. 权限控制与角色分离:用 Spring Security 实现编辑、审核、发布的三级操作隔离

4.1 RBAC 模型落地:三张表搞定权限体系,不依赖 Shiro 复杂配置

毕业设计不必堆砌权限框架,用最简 RBAC 即可体现设计深度:

表名主要字段说明
sys_roleid,role_name,code(如EDITOR,CHIEF_EDITOR,ADMIN角色编码用于@PreAuthorize("hasRole('EDITOR')")
sys_user_roleuser_id,role_id用户-角色多对多关联
sys_role_permissionrole_id,permission_code(如news:draft:add,news:review:pass角色-权限细粒度绑定

permission_code设计规则:资源:操作:范围,例如:

  • news:draft:add—— 编辑可新建草稿;
  • news:review:pass—— 主编可批准发布;
  • news:archive:batch—— 管理员可批量归档。

这样设计的好处是:答辩时可清晰说明“为什么编辑不能看到‘终审’按钮?因为其角色未分配news:review:pass权限”,而非模糊回答“权限没开”。

4.2 方法级鉴权:@PreAuthorize与 SpEL 表达式的实战写法

Controller 方法上直接标注权限,比拦截器更直观:

@RestController @RequestMapping("/api/news") public class NewsController { @PostMapping("/submit") @PreAuthorize("hasAuthority('news:draft:add')") public Result<?> submitDraft(@RequestBody NewsDraftDTO dto) { ... } @PostMapping("/{id}/approve") @PreAuthorize("@newsSecurityService.canApprove(authentication, #id)") public Result<?> approve(@PathVariable Long id) { ... } }

第二行@PreAuthorize调用自定义服务newsSecurityService.canApprove(),实现动态鉴权:

  • 检查当前用户角色是否为CHIEF_EDITOR
  • 查询该新闻的category_id,判断用户是否被授权管理该栏目(通过sys_user_category关联表);
  • 防止主编越权审批其他栏目的稿件。
@Service public class NewsSecurityService { public boolean canApprove(Authentication auth, Long newsId) { // 1. 获取当前用户 ID Long userId = ((UserDetailsImpl) auth.getPrincipal()).getId(); // 2. 查询新闻所属栏目 Long categoryId = newsMapper.selectCategoryIdById(newsId); // 3. 检查用户是否对该栏目有审核权限 return userCategoryMapper.exists(userId, categoryId); } }

提示:Authentication对象需在SecurityConfig中配置@EnableGlobalMethodSecurity(prePostEnabled = true)才能注入到 SpEL 表达式中。此配置常被遗漏,导致@PreAuthorize不生效。

4.3 登录态管理:JWT Token 有效期与刷新机制的轻量实现

毕业设计不需复杂 OAuth2,用 JWT 即可。Token 有效期设为 2 小时,过期后前端调用/api/auth/refresh刷新:

@PostMapping("/refresh") public Result<JwtResponse> refresh(@RequestHeader("Authorization") String token) { String jwt = token.replace("Bearer ", ""); if (!jwtUtil.validateToken(jwt)) { return Result.fail("Token 已失效"); } Long userId = jwtUtil.getUserIdFromToken(jwt); // 生成新 Token,有效期延长 2 小时 String newToken = jwtUtil.generateToken(userId); return Result.success(new JwtResponse(newToken)); }

JwtUtil.generateToken()中,setExpiration(new Date(System.currentTimeMillis() + 2 * 60 * 60 * 1000))显式设置过期时间,而非依赖spring.security.jwt.expiration配置——因为答辩时可能被问“如果配置文件写错,Token 会不会永不过期?”,显式编码可证明你理解原理。


5. 毕业设计交付物实操指南:从源码编译到答辩 PPT 的关键检查点

5.1 源代码包结构自查清单(答辩前必做)

交付的.zip包内,目录结构必须严格遵循 Maven 标准,且包含以下不可缺失项:

路径必须存在检查要点
/src/main/java/com/example/news/包名需体现项目特征(如com.example.news),禁用com.testcom.demo
/src/main/resources/application-dev.yml必须有dev环境配置,包含spring.datasource.urlredis.host等,且密码字段用******占位(非明文)
/src/main/resources/mapper/XML 文件名必须与@MapperScan扫描路径一致,如NewsMapper.xml对应NewsMapper.java
/docs/存放数据库ER图.png系统架构图.png核心流程图.png,图片需高清可放大,箭头标注清晰
/pom.xml<parent>必须指向spring-boot-starter-parent,且<version>与所用 Spring Boot 版本一致(如2.7.18

特别注意:pom.xmlmysql-connector-java依赖版本必须与 MySQL 服务器版本匹配。例如 MySQL 8.0.x 需用8.0.33,若误用5.1.47,运行时会报Public Key Retrieval is not allowed错误——这是答辩现场最高频环境问题。

5.2 答辩 PPT 的三页黄金结构:技术深度 > 界面美观

PPT 不要堆砌截图,聚焦三个技术决策页:

第一页:ER 图与关键约束说明

  • 展示newsnews_categorynews_tag_rel三张表关系;
  • 用红色箭头标出ON DELETE RESTRICTON DELETE SET NULL外键行为;
  • 文字说明:“news_tag_rel采用联合主键(news_id, tag_id),避免重复打标”。

第二页:状态机流转图与异常处理

  • 绘制DRAFT → PENDING_REVIEW → [REJECTED \| PUBLISHED] → ARCHIVED状态图;
  • PUBLISHED → ARCHIVED箭头上标注“管理员操作,不可逆”;
  • 文字说明:“驳回时自动将reject_reason写入news表,前端展示给编辑员”。

第三页:权限控制代码片段

  • 截取NewsSecurityService.canApprove()方法核心代码;
  • userCategoryMapper.exists()调用旁加注释:“查询sys_user_category表,实现栏目级权限隔离”;
  • 文字说明:“相比角色级权限,栏目级权限更贴近媒体组织实际管理结构”。

注意:PPT 中所有代码截图必须来自你实际运行的源码,禁止从网上复制。答辩老师会随机打开你 U 盘里的代码文件验证。

5.3 数据库初始化脚本:用schema.sql+data.sql分离结构与数据

交付包中必须提供src/main/resources/schema.sql(建表语句)和data.sql(初始数据):

-- schema.sql CREATE TABLE news_category ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL COMMENT '分类名称', sort_order TINYINT DEFAULT 0 COMMENT '排序序号' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- data.sql INSERT INTO news_category (name, sort_order) VALUES ('校园新闻', 1), ('学术动态', 2), ('通知公告', 3);

data.sql中的INSERT语句必须用VALUES (...)格式,而非INSERT ... SELECT——因为后者在不同 MySQL 版本下兼容性差,答辩现场演示时可能失败。同时,schema.sql中所有COMMENT字段必须存在,体现你对数据库文档规范的理解。

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

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

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

立即咨询