简介:本资源是一套完整的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_time和update_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 图落地时必须检查的三个细节
毕业设计答辩常被追问“你的数据库设计怎么保证数据一致性?”。答案就藏在三处外键与索引设计中:
| 表名 | 字段 | 约束类型 | 作用说明 |
|---|---|---|---|
news | category_id→news_category.id | ON DELETE RESTRICT | 分类删除前必须清空该分类下所有新闻,防止孤儿数据 |
news | author_id→sys_user.id | ON 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(); }NewsDraftDTO中tags字段声明为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_role | id,role_name,code(如EDITOR,CHIEF_EDITOR,ADMIN) | 角色编码用于@PreAuthorize("hasRole('EDITOR')") |
sys_user_role | user_id,role_id | 用户-角色多对多关联 |
sys_role_permission | role_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.test或com.demo |
/src/main/resources/application-dev.yml | 是 | 必须有dev环境配置,包含spring.datasource.url、redis.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.xml中mysql-connector-java依赖版本必须与 MySQL 服务器版本匹配。例如 MySQL 8.0.x 需用8.0.33,若误用5.1.47,运行时会报Public Key Retrieval is not allowed错误——这是答辩现场最高频环境问题。
5.2 答辩 PPT 的三页黄金结构:技术深度 > 界面美观
PPT 不要堆砌截图,聚焦三个技术决策页:
第一页:ER 图与关键约束说明
- 展示
news、news_category、news_tag_rel三张表关系; - 用红色箭头标出
ON DELETE RESTRICT和ON 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字段必须存在,体现你对数据库文档规范的理解。
本文还有配套的精品资源,点击获取