Java进度管理系统开发实战:从CRUD到状态流与甘特图
2026/9/9 18:47:01 网站建设 项目流程

如果毕设题目是“基于Java的软件项目进度管理系统”,很多人第一反应是:这不就是一个带日期的增删改查吗?真把它全做完你会发现,CRUD只是外壳,进度管理的内核是状态流、偏差计算和甘特图数据组织。本文以我实际开发这类系统的完整经验为线索,从选题分析、技术选型、数据库设计到核心功能实现、权限审批和答辩准备,把“怎么把一个能拿得出手的Java毕设项目做下去”这件事讲透。

1. 选这个题目之前,先把“进度管理”这四个字拆开看

1.1 进度管理系统的毕设定位

毕设题目里带“管理系统”三个字的多了去了,图书管理、学生管理、宿舍管理、设备管理……这些题目的通病是:业务太单薄,写着写着就变成了对一张表的增删改查。而进度管理系统天然比纯信息管理多一层东西——它管理的是“状态随时间的变化”

这一层差异决定了项目的展示效果。答辩时你讲图书管理系统,只能演示“加一本书、改一本书、删一本书”;讲进度管理系统,可以演示任务分解、进度填报、自动计算偏差、甘特图联动、项目风险预警。同样是Spring Boot+Vue,背后的业务逻辑复杂度完全不是一个量级。

从工作量评估角度看,这个题目也属于“性价比”很高的一类。它不会像算法类题目那样死磕某一个核心算法,也不会像电商系统那样涉及支付、库存、秒杀等一堆难控边界。它有一个清晰的主线——项目、任务、里程碑、进度日志,围绕这条线每增加一个维度,系统深度就明显上一层。

1.2 业务本质:任务、依赖、里程碑和偏差

做任何系统之前,先把现实世界里的业务抽象出来。软件项目进度管理,管的是什么?

  • 项目:一次软件开发活动的整体容器,有起止日期、有负责人、有阶段划分。
  • 任务:项目拆解出来的最小工作单元,有预估工时、实际工时、负责人、开始和截止时间。
  • 依赖关系:很多任务不是并行推进的,比如“数据库设计”没完成,“后端开发”就无法开始。任务之间的先后关系必须建模。
  • 里程碑:项目中的关键节点,比如“需求评审通过”“第一轮迭代上线”。里程碑不是任务,它是“确认某阶段目标达成”的标记。
  • 进度偏差:计划完成的百分比和实际完成的百分比之间的差距,这是管理者最关注的东西。

如果把系统只做成“给任务填个完成百分比”,那本质上就是“带日期的待办清单”,这种深度撑不起一篇合格的毕业设计论文。真正的进度管理,核心在于偏差是怎么计算出来的,以及哪些因素会导致进度偏移

1.3 为什么很多人做出来像“带日期的待办清单”

这是我见过最常见的翻车情况。学生把任务表设计成:id、任务名、负责人、开始时间、结束时间、状态、完成百分比。然后前端一个表格,后端五个接口,项目就“做完”了。

问题不出在技术上,而出在没有把“进度”当做一个可计算、可推演的过程,进度被当成了一个手工维护的文本字段。你问它这个任务为什么是60%而不是70%,系统答不上来。

要摆脱这个局面,必须在设计阶段就引入几个额外概念:

  • 任务状态机:待执行、进行中、待验收、已关闭
  • 进度确认机制:成员填报进度,项目经理审核后才生效
  • 偏差计算规则:计划进度和实际进度的数学对比
  • 项目风险颜色:绿、黄、红三档,由偏差自动推导

把这些东西加进去之后,系统才叫“管理进度”,而不是“记录进度”。

2. 技术组合怎么定:Spring Boot 为主的那套成熟方案

2.1 后端为什么选 Spring Boot 而不是 SSM 或 Spring Cloud

Java 方向毕设,后端框架的纠结通常集中在三个词上:SSM、Spring Boot、Spring Cloud。

SSM 就是 Spring + SpringMVC + MyBatis,前些年很流行,但现在的问题是配置繁琐,而且大量 XML 配置对毕设来说完全是无意义工作量。Spring Cloud 是微服务方案,听着高大上,但对于一个单机部署、几千行代码的项目来说,引入注册中心、网关、配置中心纯属给自己挖坑——服务拆分之后的事务处理、接口调用链复杂度会直接淹没你的主业。

Spring Boot 是这两者之间的最佳平衡点。它自动装配,内置 Tomcat,一个 main 方法就能启动;配合 MyBatis-Plus,连单表的 CRUD 都能省掉大半手写代码。为什么不推荐 JPA?对于联表查询比较多、SQL 需要精确控制的场景,MyBatis 体系的掌控感更强。如果你对 SQL 那套更熟,就别为了“流行”去硬啃一条不顺手的技术线。

版本方面,我给一个保守稳妥的组合:

组件推荐版本说明
JDK1.8兼容性最好,Linux 服务器上也方便部署
Spring Boot2.7.x稳定,资料多,问题一搜就有答案
MyBatis-Plus3.5.x分页插件好用,代码生成器能省大量重复劳动
MySQL8.0性能足够,Navicat 连接查看数据方便
Redis可选如果只想做缓存登录态,不引入也完全可以

2.2 前端选 Vue 的技术考量

前端现在最常见的搭配是 Vue 3 + Element Plus + Vite,也有不少学校老师习惯 Vue 2 + Element UI。如果你对前端不算特别熟,我的建议是选资料最多、踩坑成本最低的那套,而不是最新那套。Vue 2 已经停止维护,不建议新项目再用;Vue 3 配 Element Plus 的示例代码现在也够多了,生态已经成熟。

特别提一下Vite 和 Webpack 的选择。Vite 启动速度确实快,但在某些低配开发机上 npm install 依赖时的兼容性不如 Webpack 稳。毕设项目规模不大,热更新快几秒慢几秒没什么实际影响,关键是跑起来之后不要出稀奇古怪的包版本报错。如果前端报错你完全看不懂,就老老实实用 Vue 官方脚手架创建项目,然后手动把 Element Plus 装进去,保持依赖版本在一个已知可用的区间。

另外,甘特图是这个系统的重头戏。前端展示甘特图有两类方案:

  • 组件库方案:dhtmlxGantt、Vue Ganttastic,功能强但样式定制和 License 限制要留意
  • 手写方案:用 div + CSS 按时间轴定位任务条,数量不夸张时完全可行

我做过几次对比,毕设场景下 dhtmlxGantt 的免费版够用,但它的数据格式要求比较固定,需要后端配合输出特定结构。手写方案看起来工作量多一点,但实际上可控性最强,答辩时还能说清楚“甘特图渲染原理”。后面我会给出具体的数据结构设计。

2.3 数据库选型与部署形态的取舍

数据库不用多想,MySQL 8.0。有老师会问“为什么不用 PostgreSQL”或“为什么不用 Oracle”,你只要能回答“开发资料多、团队熟悉、支持事务和复杂查询,对单机毕设项目量级完全够用”即可。

部署形态上,最稳妥的是前后端分离部署在同一台服务器:后端打成 jar 包跑在 8080 端口,前端 npm run build 之后生成静态文件,用 Nginx 托管并转发 /api 到后端。这样演示时只开一个 IP 就能访问完整系统,不用在答辩现场忙活前后端两个服务的启动顺序。

如果你不想碰 Linux 服务器,本地 Windows 上直接用 IDEA 启动后端、用 VSCode 的 Live Server 或 npm run dev 起前端,也可以完成演示。但这里有个隐藏风险:答辩现场的电脑环境不一定是你开发那台,没装 Java 或者没装 MySQL 都很尴尬。提前把环境变量、端口占用、数据库导入脚本都验证一遍,甚至准备一份 Docker Compose 启动方式,能救命的概率不低。

3. 核心数据库设计:进度不是字段,是状态和关系

3.1 数据模型整体拆解

直接给一套我在类似系统中验证过的表结构,这套设计能满足“任务分解、依赖、进度填报、里程碑、审批”这几大核心需求:

表名作用关键字段
sys_user用户表id, username, password, real_name, role_type
project_base项目表id, name, code, description, start_date, end_date, manager_id, status
project_member项目成员表id, project_id, user_id, role_in_project
project_task任务表id, project_id, parent_id, name, assignee_id, estimated_hours, actual_hours, plan_start, plan_end, actual_start, actual_end, status, progress_percent
task_dependency任务依赖表id, task_id, depends_on_task_id
project_milestone里程碑表id, project_id, name, due_date, achieved_date, status
task_log任务日志表id, task_id, user_id, log_date, progress_change, description, confirm_status

这里面有几处不是拍脑袋拍的,得解释一下:

project_task 里的 parent_id 很关键。软件开发项目天然是 WBS(工作分解结构)树。比如“后端开发”是父任务,下面有“用户模块”“项目模块”“进度模块”。父任务的进度不能直接手填,而是由子任务加权汇总而来。这个逻辑如果不做,系统的“进度管理”就名存实亡。

task_dependency 表解决的是“前置任务”问题。甘特图里那些箭头连线,就是靠这张表画出来的。有了依赖,系统才能校验“前置任务没完成,当前任务不能开始”这类业务规则。

task_log 表是进度可追溯的基础。每次成员填报进度,都应该留下一条日志,包含进步了多少、什么时候填的、谁填的、项目经理是否确认。以后导师问“这个项目的进度怎么证明是真的”,你祭出这张表,比任何口头解释都有说服力。

3.2 任务状态机设计

任务状态如果只用一个字段填字符串,后面会乱成一锅粥。我建议用状态机来约束流转。定义的四个状态:

  • TO_DO(待执行):任务已创建,未开始
  • IN_PROGRESS(进行中):开发人员认领并开始工作
  • PENDING_CONFIRM(待确认):开发完成,提交项目经理验收
  • CLOSED(已关闭):验收通过,任务关闭

外加两个衍生状态:BLOCKED(阻塞中,遇到风险暂时无法推进)和 OVERDUE(已逾期,它不是独立状态,而是由时间字段自动推导出的展示状态)。

状态机流转规则用代码控制:

public enum TaskStatus implements IEnum<String> { TO_DO("TO_DO", "待执行"), IN_PROGRESS("IN_PROGRESS", "进行中"), PENDING_CONFIRM("PENDING_CONFIRM", "待验收"), CLOSED("CLOSED", "已关闭"), BLOCKED("BLOCKED", "阻塞"); private final String value; private final String desc; public boolean canTransferTo(TaskStatus target) { switch (this) { case TO_DO: return target == IN_PROGRESS || target == BLOCKED; case IN_PROGRESS: return target == PENDING_CONFIRM || target == BLOCKED || target == CLOSED; case PENDING_CONFIRM: return target == CLOSED || target == IN_PROGRESS; case BLOCKED: return target == IN_PROGRESS || target == TO_DO; case CLOSED: return false; default: return false; } } }

这个枚举放在公共模块里,Service 层做状态流转时统一调用canTransferTo校验。好处是校验逻辑集中,不会在十个接口里各写一套 if-else,答辩时也能理直气壮地说“我做了状态流转控制”。

3.3 进度百分比怎么算:自底向上汇总

进度百分比是系统里最核心的一个计算字段。我的做法是叶子任务由开发人员手动填报,父任务按子任务加权自动汇总。权重的选择有两种:

  • 按预估工时加权:parentProgress = sum(child.estimatedHours * child.progress) / sum(child.estimatedHours)
  • 按子任务数量平均:实现更简单,但不太准确,一个填 100% 的大任务会被一个填 10% 的小任务拖累

推荐第一种,原因很实际:预估工时本身就是评估任务量的主要依据,用它做权重最公平。

举个例子,父任务下面有三个子任务:

子任务预估工时实际进度
用户模块开发40h100%
项目模块开发30h50%
进度模块开发30h0%

父任务进度 = (40 × 1 + 30 × 0.5 + 30 × 0) / (40 + 30 + 30) = 55%。

这个公式一旦确定下来,所有层级的任务进度都可以自动计算。设计数据库时,还要注意一点:叶子任务和父任务要加一个 is_leaf 标识或者通过有没有子任务来判断,否则计算时会出现歧义。

4. 两个核心功能怎么落地:甘特图与偏差预警

4.1 甘特图的后端数据结构设计

甘特图展示需要的数据,后端不应该把一个 list 原样丢给前端,而是应该组装成前端组件最容易消费的结构。这里我以 dhtmlxGantt 为例,它需要的数据格式大致是:

{ "data": [ { "id": 1, "text": "需求分析", "start_date": "2024-03-01", "end_date": "2024-03-10", "progress": 1, "parent": 0, "type": "project" }, { "id": 2, "text": "数据库设计", "start_date": "2024-03-11", "end_date": "2024-03-15", "progress": 0.5, "parent": 1, "type": "task" } ], "links": [ { "id": 1, "source": 1, "target": 2, "type": "0" } ] }

后端的核心工作是树形化类型映射。树形化就是根据 parent_id 组装父子关系;类型映射是把我们自己定义的 TO_DO、IN_PROGRESS 等状态映射成甘特图能识别的颜色规则,比如红黄绿。为了算偏差,还需要在返回数据时额外带上计划进度和实际进度的字段。

组装接口的伪代码:

public GanttDataVO getGanttData(Long projectId) { List<Task> taskList = taskMapper.selectByProjectId(projectId); // 1. 构造 DTO,计算每层任务的进度 List<GanttTaskVO> tree = buildTaskTree(taskList); // 2. 生成链接数据(依赖关系) List<GanttLinkVO> links = buildLinks(taskList); // 3. 批量更新时间偏差和进度偏差 enrichWithDeviation(tree); return new GanttDataVO(tree, links); }

里面有一个细节:甘特图的任务结束日期在组件里往往是排他性的,也就是说 3 月 10 日结束的任务,下个任务从 3 月 11 日开始,如果直接传 end_date 会出现任务条重叠一天的视觉问题。解决方法是后端统一处理,把 end_date 减一天再传给前端,保证展示和业务语义一致。这个坑我在第一次开发的时候反复调了好久才发现根源。

4.2 进度偏差的计算与项目健康状态

偏差的本质是比较“计划进度”和“实际进度”。计划进度按时间线性估算,公式是:

计划进度 = (当前日期 - plan_start) / (plan_end - plan_start)

注意这个算法只保证了 0 到 1 之间,实际中要加边界处理,比如当前日期早于 plan_start 时计划进度为 0,晚于 plan_end 时直接视为 100%。

实际进度就是我们上一节说的叶子任务填报值、父任务汇总值。

偏差的计算:

进度偏差率 = 实际进度 - 计划进度

偏差率为正说明超前,为负说明滞后。更进一步,可以用这个偏差率推导项目健康状态:

  • 偏差 >= -0.05:绿灯,进度正常
  • 偏差在 -0.20 到 -0.05 之间:黄灯,有延期风险
  • 偏差 < -0.20:红灯,严重滞后

我在任务表里并不会存这个颜色状态,因为它是实时计算出来的。数据库里只存起始时间、实际进度、状态这些“元数据”,颜色是查询时算出来的派生字段。这样做的好处是:当系统时间变化时,项目状态会自动更新,不需要一个定时任务跑批去修改“状态字段”

延迟天数也很有用:

public int calcDelayDays(Task task) { long now = LocalDate.now().toEpochDay(); long planEnd = task.getPlanEndDate().toEpochDay(); if (now > planEnd && !TaskStatus.CLOSED.equals(task.getStatus())) { return (int) (now - planEnd); } return 0; }

这个“逾期天数”会出现在任务列表的专门一列以及项目详情页的统计卡片上。实际使用中,这比单纯的百分比更能直观提醒管理者:这个任务已经拖了多久了。

4.3 风险项目的首页看板

首页是答辩时第一个亮相的页面,也是整个系统业务深度的集中体现。我给这个系统设计的首页看板包含下面几个模块:

  • 项目进度总览:每个项目的名称、开始和截止时间、整体进度条、健康状态灯
  • 逾期任务列表:按逾期天数倒序排列的 TopN 任务
  • 各成员任务负载:统计每个成员名下未完成任务数、总预估剩余工时
  • 里程碑完成情况:哪些里程碑已经按时达成,哪些已经超期未确认

这些数据都不是简单的select count(*)能搞定的,需要联合多张表做聚合。比如“成员任务负载”的 SQL 大致是:

SELECT u.real_name, COUNT(t.id) AS todo_count, SUM(CASE WHEN t.status = 'IN_PROGRESS' THEN 1 ELSE 0 END) AS doing_count, IFNULL(SUM(CASE WHEN t.status != 'CLOSED' THEN t.estimated_hours ELSE 0 END), 0) AS remain_hours FROM sys_user u LEFT JOIN project_task t ON t.assignee_id = u.id AND t.project_id = #{projectId} WHERE u.id IN (SELECT user_id FROM project_member WHERE project_id = #{projectId}) GROUP BY u.id, u.real_name

首页的另一个隐藏作用是引导答辩。当老师问“你的系统怎么体现进度管理”时,你可以从首页的看板讲起,然后一层层钻进任务列表、甘特图、日志审批,整个过程自然流畅。如果首页只是一张空空的表格,你连讲故事的机会都没有。

5. 权限与进度审批:让系统从“能跑”到“有亮点”

5.1 三种角色的权限边界

权限设计是毕设评分的一个重点方向。“管理系统”里如果任何用户都能改任何数据,评委印象分直接减半。

常见角色划分:

角色权限范围典型操作
系统管理员用户管理、项目配置创建用户、分配角色、查看所有项目
项目经理项目内的全部数据创建任务、分解 WBS、审批进度、调整里程碑
开发人员分配给自己的任务填报进度、更新任务日志、标记阻塞

实现层面,我不推荐毕设里硬上 Spring Security 的完整过滤器链加 @PreAuthorize 注解,也不推荐完全不用。折中方案是自定义一个拦截器 + 简单的权限注解

@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface RequireRole { String[] value(); }

拦截器里拿到当前登录用户角色,判断是否在注解允许的列表里。校验逻辑集中在一个地方,Service 层也能通过切面控制。这样写的好处是代码量可控,而且答辩时你能把“权限控制实现原理”讲清楚——这比背一个 Spring Security 配置然后说不清原理要强得多。

数据权限是另一个容易忽略的维度。项目经理应该只能看到自己管理的项目,开发人员只能看到自己参与的任务。这里的核心是所有查询都要带上当前用户维度的过滤条件,而不是只在前端做菜单隐藏。菜单隐藏只是用户体验层面,后端接口必须做强制过滤。

5.2 进度填报与审批确认

进度填报如果不加审批,系统就是个“各说各话”的记事本。我设计的流程是:

  1. 开发人员在任务页面点击“填报进度”,输入本次完成百分比,比如从 40% 改为 60%
  2. 系统生成一条 task_log 记录,confirm_status 为 PENDING
  3. 项目经理在“待确认列表”里看到这条记录,核对该任务的实际完成情况
  4. 确认后,任务的实际 progress_percent 才更新到 60%;不确认则保持原值

这里有个稍微反直觉的设计:任务表的 progress_percent 并不随着填报立即变化,而是要等审批确认后才会变化。这意味着“填报值”和“确认值”可能存在差异。我在数据库里其实是这么处理的:

  • task_log.progress_change 记录的是本次填报的目标值
  • project_task.progress_percent 记录的是最终确认值
  • progress_confirm_status 标记当前是否存在待确认的填报

这个机制可能会引来一个疑问:如果任务表进度不改,那销售看板上的展示又按哪个算呢?我的回答是:按确认值,因为一个不可信的计算结果没有展示意义。这也是为什么首页看板和任务列表的进度刷新,都只在审批通过后才触发。

5.3 里程碑管理的独特价值

任务进度管理解决的是“日常执行”层面,里程碑解决的是“阶段目标”层面。两者缺一个,项目管理系统就不完整。

里程碑表的核心字段是 due_date、achieved_date 和 status。它的计算公式很简单:

  • 当前日期 > due_date 且 achieved_date 为空 → 已逾期
  • achieved_date <= due_date → 按时达成
  • achieved_date > due_date → 延迟达成

里程碑的达成应该和任务状态联动。比如“第一轮迭代上线”这个里程碑,需要对应的“打包部署”“线上验证”任务都处于 CLOSED 状态才能勾选达成。技术上可以在 Service 层做校验:

public boolean isMilestoneAchievable(Long projectId, List<Long> relatedTaskIds) { long closedCount = taskMapper.selectBatchIds(relatedTaskIds).stream() .filter(t -> t.getStatus() == TaskStatus.CLOSED) .count(); return closedCount == relatedTaskIds.size(); }

这一步可以让老师觉得你对“项目阶段管理”有真实的业务理解,而不是只会做数据表。

6. 开发过程中最值得记录的坑

6.1 定时任务还是实时计算?时间字段埋的雷

我在项目里做了一个“逾期任务自动标记”的功能。有两种实现思路:

  1. 定时任务每天凌晨跑一次,把所有 end_date 早于今天的未完成任务标记成 OVERDUE
  2. 不存逾期状态,查询时实时计算

我一开始选了方案 1,结果掉进了经典的“状态一致性”坑:如果当天凌晨任务还没跑,白天用户查到的数据里就没有逾期标记;如果定时任务失败了,逾期状态就永远漏掉了。而且 OVERDUE 一旦写成字段,后面要改状态、要写清楚“为什么逾期”都无从追溯。

最后我改成方案 2:不在表里存逾期状态,所有需要展示逾期信息的地方都通过calcDelayDays实时计算。这样既不会出现状态不一致,还能节省掉一个定时任务模块。如果老师特别想看你用定时任务,可以做“每日项目进度快照”或者“超时自动通知”试试。

6.2 日期和时区:LocalDateTime 与字符串互转

Java 8 之后的 LocalDateTime 用着很爽,但也容易踩坑。比如前端传来的日期是“2024-03-15”,后端用@JsonFormat(pattern = "yyyy-MM-dd")解析时,如果前端实际传的是 “2024-03-15 00:00:00”,就会因为格式不匹配直接 400 报错。

我建议后端统一使用LocalDate表示日期,LocalDateTime表示带时间的日志记录。前端组件 date-picker 默认返回的值格式要固定,后端接口层再把 String 统一转成 LocalDate,不要靠 JSON 框架的默认行为去猜。

还有个容易忽略的点:Jackson 序列化 LocalDateTime 时,默认输出的是数组格式[2024, 3, 15, 10, 30, 0],前端拿到的不是字符串,不处理的话必挂。解决方法是加一个全局配置:

@Bean public Jackson2ObjectMapperBuilderCustomizer jacksonCustomizer() { return builder -> { builder.serializers(new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); builder.deserializers(new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); }; }

这条全局规则比在每个字段上反复加注解要省心得多。

6.3 递归查询 WBS 树导致的性能问题

如果任务的层级很深,而且前端展开某个节点就要调一次后端接口,那递归查询的效率就很重要。实测中,任务 1000 条以内时,一次性查出来、在内存里组装树完全没有问题,顶多几十毫秒;但如果你在循环里for (Task t : taskList) { taskMapper.selectChildren(t.getId()); },那 N+1 次查询就会让接口响应时间涨到秒级。

解决思路:

  1. 一次性查出项目下所有任务
  2. 在内存里按 parentId 分组,构建 Map
  3. 从根节点开始递归组装 DTO
Map<Long, List<Task>> groupByParent = taskList.stream() .collect(Collectors.groupingBy(Task::getParentId)); List<TaskVO> buildTree(Long parentId) { List<Task> children = groupByParent.getOrDefault(parentId, Collections.emptyList()); return children.stream() .map(t -> { TaskVO vo = convert(t); vo.setChildren(buildTree(t.getId())); return vo; }).toList(); }

如果任务量确实很大,再考虑从数据库层用 MySQL 递归 CTE(8.0 支持WITH RECURSIVE)来一次性查出子树。但对毕设来说,内存组装完全够用且逻辑更直观,答辩时也更容易解释。

6.4 前端树形表格的坑

Element Plus 的 el-table 树形数据要求每条记录有一个唯一的children字段。如果后端返回来的是childList或者subTasks,树形展开就会静默失败——页面不报错,但点开没反应。

解决办法是后端 DTO 里直接给字段命名成children,或者前端用:tree-props="{ children: 'subTasks' }"做映射。这个坑看起来很小,但排查起来特别浪费时间,因为不报错,你甚至会怀疑是自己权限不够导致数据没加载出来。

6.5 Lombok 和编译版本相关的那些幺蛾子

热词里出现了一个很典型的报错:java: you aren't using a compiler supported by lombok, so lombok will not work。这个问题的根因通常是 IDE 自带的编译器版本和 Lombok 版本不兼容,或者 Maven 编译时用了太旧的 JDK。

稳妥的解决方案:

  • 把 Lombok 升级到较新且稳定的版本(比如 1.18.30 左右)
  • 确保 IDEA 里 Project Structure 配置的 SDK 版本和 pom.xml 里的<java.version>一致
  • 在 IDEA 的 Settings -> Build Tools -> Maven -> Runner 里勾选 Delegate IDE build/run actions to Maven

如果你用 JDK 17 以上,还可能遇到“源发行版 17 需要目标发行版 17”这类报错,根源也是编译版本不匹配。最简单的方法:项目统一用 JDK 1.8 和 Spring Boot 2.x,避开大多数 Java 17 模块化相关的坑。

7. 演示数据准备、测试与答辩话术

7.1 造一套可信的演示数据

很多毕设项目在功能上没有任何问题,但演示时一打开页面全是测试数据“task1”“task2”,观感很差。我强烈建议你提前造一套接近真实软件项目的演示数据。

以一个“高校实验室管理系统开发”项目为例,可以这样拆:

  • 项目周期:2024-03-01 到 2024-05-30
  • 任务层级:需求分析 -> 系统设计 -> 后端开发 -> 前端开发 -> 测试 -> 部署上线
  • 后端开发再拆:用户模块、权限模块、实验项目管理、仪器预约模块、数据统计模块
  • 每个叶子任务都设置合理工时:用户模块 40h,权限模块 20h,仪器预约 60h
  • 进度数据有梯度:已完成的任务填 100%,进行中的填 60%、30%,未开始的填 0%
  • 特意留两个逾期任务:比如“数据统计模块”已经超过 plan_end 3 天且进度只有 40%,这样可以演示红色预警效果

数据造完之后,花一点时间做端到端走查:从登录开始,逐个页面点击一遍,确保甘特图、看板、任务列表的数据联动都正常。有很多功能在单测里是好的,一联动就出问题。

7.2 测试重点:除了常规 CRUD,还要测这几个场景

单元测试和接口测试是毕设的加分项,但很多学生的测试只覆盖了“新增用户成功”“查询列表成功”这种 happy path。实测下来,进度管理系统有几个业务场景很容易出 bug,必须单独测:

  • 任务状态非法流转:一个 CLOSED 的任务不应该能被改回 IN_PROGRESS
  • 审批驳回后的处理:项目经理驳回进度填报时,任务日志应该留下记录
  • 父子任务进度汇总:子任务进度变化后,父任务进度能否正确同步
  • 甘特图依赖校验:被依赖的任务延期,依赖它的任务能否给出阻塞提示
  • 并发填报:两个成员同时填报进度时,task_log 记录是否会相互覆盖

并发这块比较容易被忽略,但又是答辩老师最喜欢问的。即使你没引入乐观锁或者 Redis 分布式锁,也应该在“填报进度”接口里用UPDATE ... WHERE progress_percent = 旧值这样的乐观锁方式做一次校验,避免后提交的人把前一个人的进度覆盖掉。

7.3 答辩时可能被追问的问题

结合我接触过的答辩场景,围绕这个题目老师大概率会问这几个问题,提前准备比临场现想强太多:

  • “进度百分比是手填的还是自动计算的?” —— 答:叶子任务手填,父任务按预估工时加权汇总。
  • “你的偏差预警是怎么实现的?” —— 答:当前时间对比计划时间,算出计划进度,与实际进度做差,设置阈值映射红黄绿。
  • “如果有人乱填进度,系统怎么办?” —— 答:进度填报后进入待确认状态,项目经理确认后生效,全程留日志。
  • “你的系统最多能支撑多少人同时在线?” —— 答:单机部署通过连接池和索引优化可以支撑百级并发,毕设演示场景足够,如果要更大规模可以拆分微服务。
  • “为什么不用原生 SQL 存树形结构?” —— 答:任务层级不会太深,内存递归组装性能足够,而且逻辑清晰;如果层级和数据量暴增,可以迁移到 MySQL 递归查询。

7.4 从毕设到项目的扩展方向

如果时间充裕,想再往上拔一档,可以考虑这几个扩展方向,它们会显著提升项目的“完整度”:

  • 消息通知:任务逾期、进度待确认、里程碑即将到期时,给相关用户发送站内信或邮件通知
  • WBS 自动生成模板:选择“标准迭代开发”模板,系统自动创建需求分析、设计、开发、测试、上线等阶段任务
  • 燃尽图:基于 task_log 的时间序列数据,按天统计剩余工时,画出团队在迭代周期内的燃尽曲线
  • 工时统计报表:按成员、按任务类型聚合实际工时,用 ECharts 展示
  • 导入导出:用 EasyExcel 导入任务列表、导出进度报表,导师看到你考虑到“和外部工具对接”这一点会很加分

我实际做下来发现,最容易让答辩老师眼前一亮的并不是哪个页面多华丽,而是“系统里每一个数字都能讲出计算逻辑”,尤其是一个项目的进度百分比、健康状态、预警规则能被完整自洽地解释清楚。你在演示时能主动讲出“看,这个模块延期了,因为前置的数据模型评审还没关闭”,比任何华丽图表都更有说服力。这个系统的核心价值也正在于此——它不是数据的搬运工,而是把项目进度从“靠感觉”变成“靠算”,而这恰好也是真实软件项目管理里最刚需的能力。

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

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

立即咨询