SSM+Vue做任务管理系统,可能是很多毕业设计选题清单里看起来最容易下手的一个:技术栈经典、业务好讲、网上资料也多。但真正动手之后你会发现,随便做几个页面很简单,要做出一个状态流转严谨、权限清晰、能写进论文、也能在答辩现场全程跑通的完整系统,要补的坑一点不少。
这篇文章把做这个系统时需要想清楚的事情完整过一遍:需求怎么圈定、表怎么设计、任务状态机怎么转、SSM后端和Vue前端怎么对接、论文和代码怎么对应、演示和答辩怎么准备。项目本身是通用的“组织级任务管理系统”,不管是面向机关单位的综合办公室,还是企业项目组内部协作,甚至高校实验室的横向任务分配,都可以套用,只需要把角色名称和部分字段语义改一下就行。
如果你正在准备毕业设计,尤其是选了SSM+Vue任务管理系统方向,这篇文章能帮你少走很多弯路。
1. 为什么这个题目值得做:选题逻辑与需求边界
先说选题逻辑。这一套系统几乎是毕设选题里的“常青树”,原因很实在:SSM框架加Vue前后端分离,是很多学校软件工程专业课程设计、实习项目里的主流组合,用这个组合做毕设,技术栈不用临时学,参考资料也足够多。
任务管理系统本身又是一个典型的管理信息系统,它具备毕业设计评审时最看重的几个要素:有用户角色区分、有权限控制、有业务状态流转、有数据统计展示、有前后端交互。业务逻辑既简单到能够在一篇论文里讲清楚,又复杂到可以充分展示数据库设计和系统设计能力,卡在刚刚好的位置上。
它还有两个隐性优势。第一,不依赖硬件环境,单机就能跑完整个流程,演示答辩不需要搭建复杂的实验环境。第二,业务场景足够通用,面向机关单位、企业项目组、高校二级学院都能说得通,需求分析章节好写,不会有“题目太大了讲不透”的问题。
1.1 功能边界先划清楚:做什么和不做什么
很多同学做毕设容易犯一个错误:功能列表写得特别长,每一个功能都只做了一半。评审老师翻论文的时候看到一堆功能,演示的时候这个报错那个没做完,整体印象反而不如一个功能完整、逻辑闭环、每个按钮都能点通的系统。
所以我建议在动手之前,先把功能边界划清楚。这套系统里“要做”的包括:
- 用户登录、退出,登录后展示个人信息
- 三种角色权限:系统管理员、任务负责人、任务执行人
- 部门管理、用户管理、角色分配,由系统管理员维护
- 任务的完整生命周期:创建、指派、接收、进度反馈、提交验收、验收通过、归档
- 任务驳回机制,验收不通过时退回给执行人继续处理
- 任务评论与附件上传,用于执行过程中的沟通留痕
- 通知消息,任务被指派、被驳回、验收通过时给相关人发送站内消息
- 任务列表的分页、关键字搜索、状态筛选、优先级筛选
- 数据统计看板:任务数量、完成率、状态分布、个人工作量排行
“不做的”也要写清楚。不做多级审批流,不做即时通讯,不做移动端App,不做复杂报表引擎。如果论文里非要提扩展性,就在“展望”章节说“未来可以引入消息队列、工作流引擎”,代码里不用真做。
1.2 角色划分与核心业务流程
这套系统里的角色,我建议就分三类,不要再多。角色多了权限设计会变得非常繁琐,单是权限管理本身就能耗掉大量的开发时间。
- 系统管理员:负责用户管理、部门管理、角色分配,可以查看全系统所有任务,不直接参与任务的创建和执行。
- 任务负责人:通常是部门主管或项目组组长,可以创建任务、指派给执行人、查看进度、验收任务、驳回任务。
- 任务执行人:普通成员,接收任务后更新进度、提交验收申请、查看自己的待办和已完成任务。
核心业务流程用一句话描述就是:负责人创建任务并指派给执行人,执行人接收后开始执行,执行过程中反馈进度,完成后提交验收,负责人验收通过则归档,验收不通过则驳回重新处理。
这个流程本质上是“一个任务的一生”。我建议你把这个流程当成整个系统的设计主线,后面所有数据库表、接口、页面都围绕这条主线铺开。主线稳了,系统就不会乱。
2. 数据库表设计与任务状态机:整个系统的地基
数据库设计是整个系统里最不能省时间的部分。表结构设计得合理,后面写代码会非常顺畅;表结构设计得随意,后面每写一个功能都要跟表结构较劲。
这套系统我用了9张表,一张不多一张不少。
| 表名 | 作用 |
|---|---|
| user | 用户表,存登录账号、密码、姓名、部门、联系电话 |
| role | 角色表,存角色ID和角色名称 |
| user_role | 用户角色关联表,一个用户可多角色 |
| department | 部门表,存部门名称和负责人 |
| task | 任务表,系统核心表,存任务所有字段 |
| task_log | 任务操作日志表,记录每一次状态变更和关键操作 |
| task_attachment | 任务附件表,存附件路径和上传人 |
| task_comment | 任务评论表,存评论内容和评论人 |
| notification | 通知消息表,存消息接收人、内容和已读状态 |
这9张表不是拍脑袋定的,每一张都对应前面的功能需求。比如“通知消息”如果没有单独的表,任务被指派时执行人根本不知道,只能靠刷新页面碰运气。
2.1 task表字段设计:每个字段都有存在的理由
任务表是整个系统的核心,字段怎么定直接决定功能能不能实现。我给出一个经过实际验证的字段设计方案:
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键,自增 |
| task_code | varchar(32) | 任务编号,生成规则见下文 |
| title | varchar(100) | 任务标题 |
| content | text | 任务详细描述 |
| priority | int | 优先级,1紧急 2高 3普通 4低 |
| deadline | datetime | 截止时间 |
| create_time | datetime | 创建时间 |
| creator_id | bigint | 创建人ID,即任务负责人 |
| assignee_id | bigint | 指派人ID,即任务执行人 |
| status | int | 任务状态:-1驳回 0待接收 1执行中 2待验收 3已归档 |
| verify_user_id | bigint | 验收人ID |
| actual_finish_time | datetime | 实际完成时间,任务归档时写入 |
| progress_report | text | 执行人提交的进度/完成说明 |
三个细节需要特别说明。
第一个是task_code任务编号。不要用纯自增ID作为任务编号对外展示,因为用户看不懂“任务53”和“任务54”有什么区别。我实际使用的生成规则是:T+ 日期 + 当日序号,比如T202612010001。生成逻辑放在Service层,先查当天已有任务数再加一,然后拼成编号。这个编号在列表页展示、搜索时都很有用,答辩时也可以说是“符合实际管理需求的唯一业务标识”。
第二个是priority字段用int而不是varchar。有些同学习惯用字符串存“高”“中”“低”,但字符串排序和筛选都很麻烦。用int存,页面显示时再映射成对应的文字标签,筛选时直接比较大小就行,“查询所有优先级为紧急的任务”就是一条简单的WHERE priority = 1。
第三个是status字段,这是整套系统的灵魂。我见过有人用多个boolean字段表示任务状态,比如is_received、is_completed、is_verified,这样做表面上看起来直观,实际上状态组合会越来越多,判断逻辑越写越乱。一个int字段配合状态字典,干净利落。
2.2 任务状态机:状态流转规则必须一次定死
任务状态的流转是整个系统最核心的业务逻辑,必须用状态机的方式严格定义。
状态定义:0待接收 → 1执行中 → 2待验收 → 3已归档,另有特殊状态-1已驳回。
| 当前状态 | 操作 | 操作人 | 目标状态 |
|---|---|---|---|
| 0待接收 | 接收任务 | 任务执行人 | 1执行中 |
| 1执行中 | 提交验收 | 任务执行人 | 2待验收 |
| 1执行中 | 驳回后重新提交 | 任务执行人 | 2待验收 |
| -1已驳回 | 重新反馈 | 任务执行人 | 2待验收 |
| 2待验收 | 验收通过 | 任务负责人/管理员 | 3已归档 |
| 2待验收 | 驳回 | 任务负责人/管理员 | -1已驳回 |
这里有一个很多项目都会忽略的关键点:并发控制。比如执行人A和执行人B同时打开同一个待接收的任务,A先点了接收,B又点接收,如果不做控制,两个人都成了执行人,任务状态也乱了。
解决办法是在更新SQL里带上状态条件:
UPDATE task SET status = 1, assignee_id = #{userId} WHERE id = #{taskId} AND status = 0如果这条SQL执行后影响行数为0,说明任务状态已经不是待接收了,接口直接返回“任务已被接收”。原理很简单,就是数据库层面的乐观锁思路,不需要引入额外的锁机制,但对毕设来说完全够用。
2.3 task_log操作日志表:最容易忽略但价值最高的表
我强烈建议在这个系统里加上task_log表,这是很多同类毕设没有做但我觉得价值最高的设计。
字段设计:id、task_id、operator_id、operation_type(create/assign/receive/progress/submit/accept/reject/comment)、from_status、to_status、operation_detail、create_time。
这张表有三个作用。第一,任务详情页需要一个“操作时间线”,让用户看到这个任务从创建到归档的完整历史,这些数据就存在这张表里。第二,数据统计模块要算“任务平均完成时长”“各负责人任务量”,没有日志表根本无从算起。第三,论文答辩时“数据库设计”和“系统详细设计”章节可以直接展示这张表的ER图和字段说明,内容扎实。
实现上,每次任务状态变更时,在一个事务里同时更新task表和插入task_log表,保证数据一致性。我用一个例子说明:
@Transactional public void receiveTask(Long taskId, Long userId) { int rows = taskMapper.receiveTask(taskId, userId); if (rows == 0) { throw new BusinessException("任务已被接收或状态已变更"); } TaskLog log = new TaskLog(); log.setTaskId(taskId); log.setOperatorId(userId); log.setOperationType("receive"); log.setFromStatus(0); log.setToStatus(1); log.setOperationDetail("执行人接收任务"); taskLogMapper.insert(log); }@Transactional保证任务更新和日志插入要么都成功,要么都失败,不会出现任务状态变了但日志没记上的情况。
3. 后端SSM核心实现:一条任务主链路的完整拆解
后端开发最忌讳的就是Controller里堆一大堆业务代码,看起来很热闹,实际上维护起来很痛苦。SSM项目还是老老实实分层:Controller接收参数和返回结果,Service处理业务逻辑,Mapper操作数据库。
3.1 后端接口设计:RESTful风格,路径清晰
我先把这次用到的核心接口列出来,后面写前端的时候可以直接对着调。
| 接口 | 方法 | 说明 |
|---|---|---|
| /api/auth/login | POST | 登录,返回token和用户信息 |
| /api/task/page | GET | 分页查询任务列表,支持状态/关键字/优先级筛选 |
| /api/task/create | POST | 创建任务并指派 |
| /api/task/receive | POST | 接收任务,参数taskId |
| /api/task/progress | POST | 提交进度反馈 |
| /api/task/submit | POST | 执行人提交验收申请 |
| /api/task/accept | POST | 负责人验收通过 |
| /api/task/reject | POST | 负责人驳回任务,需填驳回原因 |
| /api/task/logs | GET | 查询任务操作日志列表 |
| /api/task/comment | POST | 添加任务评论 |
| /api/statistics/overview | GET | 统计数据看板 |
| /api/user/list | GET | 用户列表,用于任务指派时的选择器 |
统一返回结构我建议定成Result<T>:code表示业务状态码,200为成功,401为未登录,500为业务异常;message是提示信息;data是实际数据。所有Controller都返回这个结构,前端axios拦截器统一处理,省去大量重复判断。
3.2 登录与权限控制:拦截器统一校验
登录接口的逻辑很直接:根据用户名查询用户,比对密码,查角色,生成token返回给前端。密码在数据库里存的是加盐的MD5值,千万不要明文存储,论文里写“密码经过加密存储”也是一个加分项。
token这块不需要引入复杂的框架,简单方式是用UUID生成一个随机字符串,以userId为key、token为value存入一个全局Map,拦截器根据前端传来的token查Map判断是否登录。重启后Map清空,用户重新登录即可,对毕设的场景完全够用。
登录校验我用SpringMVC的HandlerInterceptor实现:
public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getHeader("token"); if (token == null || !TokenStore.isValid(token)) { response.setContentType("application/json;charset=utf-8"); response.getWriter().write(JSON.toJSONString(Result.error(401, "未登录或登录已过期"))); return false; } User currentUser = TokenStore.getUser(token); request.setAttribute("currentUser", currentUser); return true; } }在SpringMVC配置文件里注册拦截器,并放行登录接口、静态资源和前端页面跨域预检请求。Controller里通过request.getAttribute("currentUser")拿当前登录用户,不需要在每个接口里重复解析token。
权限控制层面,我在自定义注解@RequireRole里定义需要的角色,用另一个拦截器校验当前用户的角色是否匹配。比如创建任务接口标注@RequireRole({"admin", "manager"}),执行人的接口标注@RequireRole({"member", "manager", "admin"})。这样权限逻辑集中管理,不用在每个Service方法里写一堆if判断。
3.3 任务创建与状态流转的实现细节
以“创建任务”为例,完整走一遍Service层逻辑。
@Transactional public TaskVO createTask(TaskCreateDTO dto, User currentUser) { if (!RoleUtil.hasRole(currentUser, "admin", "manager")) { throw new BusinessException("无权限创建任务"); } if (dto.getAssigneeId() == null) { throw new BusinessException("请选择任务执行人"); } Task task = new Task(); task.setTitle(dto.getTitle()); task.setContent(dto.getContent()); task.setPriority(dto.getPriority()); task.setDeadline(dto.getDeadline()); task.setCreatorId(currentUser.getId()); task.setAssigneeId(dto.getAssigneeId()); task.setStatus(0); task.setTaskCode(generateTaskCode()); task.setCreateTime(LocalDateTime.now()); taskMapper.insert(task); TaskLog log = new TaskLog(); log.setTaskId(task.getId()); log.setOperatorId(currentUser.getId()); log.setOperationType("create"); log.setFromStatus(null); log.setToStatus(0); log.setOperationDetail("创建任务并指派给执行人"); taskLogMapper.insert(log); // 给执行人插入一条通知消息 notificationMapper.insert(new Notification(null, dto.getAssigneeId(), "新任务提醒", "您有一个新任务:" + dto.getTitle(), 0, LocalDateTime.now())); return taskMapper.selectById(task.getId()); }几个容易忽略的细节。
第一,创建任务后一定要给执行人发通知。有些同学把通知模块做出来了但创建任务时没接入,等答辩演示时“通知功能”只能单独点开看,逻辑上说不通。把通知插入放在同一个事务里,这是业务闭环的一部分。
第二,截止时间的校验。创建任务时前端要校验截止时间不能早于当前时间,后端也要再校验一次。后端永远默认前端传的数据不可信,这是基本的安全意识。
第三,任务状态不是随便就能改的。每次状态变更都要先查当前状态,确认允许流转后才执行更新。
3.4 SSM环境里最容易翻车的地方
说几个我实际配置项目时踩过的坑。
MyBatis的Mapper接口和XML文件要放在同一个包路径下,并且接口名和XML文件名保持一致。XML文件里namespace要写接口的全限定名,每个方法id要对应接口方法名。很多同学第一次配SSM时全项目报“Invalid bound statement (not found)”,基本都是这三件事有一件没做对。
事务配置只放在Service层。Controller不做事务,Mapper也不做,这是约定。@Transactional注解加在Service方法上,Spring会自动完成事务的开启、提交和回滚。
分页用PageHelper插件时,一定要注意PageHelper和MyBatis版本兼容性。调用PageHelper.startPage(pageNum, pageSize)之后紧接着就是第一条SQL查询,中间不要夹杂其他查询语句,否则分页会作用到错误的SQL上。
Spring的配置文件里,组件扫描、Mapper扫描、数据源配置这三块的路径要对应,不然运行起来不是扫描不到Bean就是Mapper无法注入。
4. Vue前端实现与接口对接:从列表到详情的完整页面
前端这部分,我用Vue2加Element UI来做,因为SSM毕设的主流搭配还是Vue2,资料最多、遇到问题最好搜。如果你想用Vue3,思路完全一样,只是部分API写法不同。
4.1 前端工程结构与axios封装
前端目录结构我建议这样组织:
src/ ├── api/ # 接口请求封装 ├── assets/ # 静态资源 ├── components/ # 公共组件 ├── router/ # 路由配置 ├── utils/ # 工具函数,如request.js ├── views/ # 页面组件 │ ├── login/ │ ├── layout/ │ ├── task/ │ └── statistics/axios封装是整个前端的命脉,所有请求都要经过它。我在utils/request.js里的核心逻辑是:请求拦截器自动从localStorage取出token放到header里;响应拦截器统一处理返回数据,业务码200时直接返回data,401时清除token并跳转登录页,其他错误码弹出Message提示。
import axios from 'axios' import { Message } from 'element-ui' import router from '@/router' const service = axios.create({ baseURL: '/api', timeout: 10000 }) service.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers['token'] = token } return config }) service.interceptors.response.use( response => { const res = response.data if (res.code === 200) { return res.data } if (res.code === 401) { localStorage.removeItem('token') router.push('/login') return Promise.reject(new Error('未登录或登录已过期')) } Message.error(res.message || '请求失败') return Promise.reject(new Error(res.message)) }, error => { Message.error('网络异常,请检查后端服务是否启动') return Promise.reject(error) } ) export default service有一个很典型的坑:文件上传组件el-upload如果直接设置action地址,默认不会带上你在axios里配置的header,必须单独给el-upload设置:headers属性,否则后端拦截器一校验,上传请求直接401。
<el-upload :action="uploadUrl" :headers="uploadHeaders" name="file" :on-success="handleUploadSuccess"> </el-upload>上传后后端返回文件ID和访问路径,把这个路径存到任务附件表里。
4.2 路由守卫与多角色页面跳转
路由守卫的作用很简单:用户未登录不能访问任何业务页面,跳转到登录页;已登录用户访问登录页则跳回首页。
router.beforeEach((to, from, next) => { const token = localStorage.getItem('token') if (to.path === '/login') { next() } else if (!token) { next('/login') } else { next() } })页面跳转的节奏是按角色来设计的。系统管理员登录后默认进用户管理页,任务负责人登录后默认进任务看板页,普通执行人登录后默认进“我的任务”页。这个默认跳转逻辑在登录接口返回的role字段基础上做判断,代码只需要几行if语句,但对答辩演示的流畅度提升非常明显。
4.3 任务列表页和详情页的关键实现
任务列表页看起来只是一个表格加筛选栏,里面有几个细节值得注意。
列表页查询参数我设计了四个:pageNum、pageSize、status、keyword。status为空表示查询全部状态,keyword用于模糊搜索标题和任务编号。前端每次点击搜索、重置、切换页签时,重新调一次getTaskPage接口,表格数据即时刷新。
任务详情页是整个前端最复杂的页面,包含:基本信息展示区、任务状态时间线、进度反馈表单、评论列表、附件列表、操作按钮组。操作按钮组的显示逻辑必须严格根据角色和任务状态控制,比如任务状态是“待验收”时,只有负责人能看到“通过”和“驳回”按钮,执行人只能看到“任务详情”。
任务时间线用el-timeline组件渲染,数据源就是task_log表的接口返回数据。展示格式类似“2026-12-01 09:30 某负责人 创建任务并指派给执行人”,这条记录由后端封装好,前端只管渲染,逻辑边界清晰。
4.4 前端与后端时间格式不一致的问题
这个坑几乎所有人都会踩。Java后端返回的LocalDateTime默认序列化为2026-12-01T09:30:00,中间带个T,前端直接显示很丑。
解决方案是在后端统一配置全局时间序列化格式。在SpringMVC配置文件或Jackson配置里设置:
@Bean public Jackson2ObjectMapperBuilderCustomizer jacksonCustomizer() { return builder -> { builder.simpleDateFormat("yyyy-MM-dd HH:mm:ss"); builder.serializers(new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); }; }或者更简单的方式:在TaskVO的时间字段上直接加@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")注解。我倾向于后者,精细控制每个字段的格式,不容易误伤其他字段。
5. 论文结构设计与程序代码的对应关系
论文和程序是毕设的双胞胎,评审老师看论文时必然会对照程序来验证。论文里写的每一个功能模块,代码里必须能找到对应实现;代码里做的每一个功能,论文里必须有所描述。很多同学论文和程序脱节,一看就是先写论文后补代码或者纯抄模板,答辩时一问细节就露馅。
5.1 论文目录怎么排
我用的是最稳妥的结构,也是大多数学校比较认可的结构:
- 第1章 绪论:研究背景与意义、国内外研究现状、论文组织结构
- 第2章 相关技术介绍:SSM框架、Vue.js、Element UI、MySQL、开发工具
- 第3章 系统需求分析:可行性分析、功能需求分析、非功能需求分析、用例分析
- 第4章 系统总体设计:系统架构设计、功能模块设计、数据库设计
- 第5章 系统详细设计:核心模块的设计与实现、关键功能时序图
- 第6章 系统实现:运行效果展示、关键代码说明
- 第7章 系统测试:测试环境、功能测试用例表、测试结论
- 第8章 总结与展望
5.2 需求分析和详细设计章节的写作技巧
需求和设计章节是论文里字数的大头,也是最容易写得“假大空”的地方。
我建议在需求分析章节画用例图,配合用例描述表。一个标准的用例描述表包含:用例编号、参与者、前置条件、基本事件流、异常事件流、后置条件。这个表格特别能体现专业性,而且填起来并不难,就是把系统操作流程用文字描述一遍。比如“管理员登录”用例的基本事件流就是:输入用户名密码、系统校验、登录成功、跳转首页。
详细设计章节要画时序图和E-R图。评审老师看这个章节的重点是“图”和“代码对不对得上”。画时序图时我建议用PlantUML或Draw.io,干净规范。E-R图要跟数据库设计章节的表结构一一对应,不要出现图中有的表代码里没有的情况。
5.3 数据库设计章节直接放表结构
数据库设计章节不要用大段文字描述业务,直接用表格把每张表的字段列清楚,再加一段总结说明设计思路。
表结构设计说明的写法也有技巧。每张表旁写一两句话说明核心用途,比如“task_log表用于记录任务从创建到归档的完整操作历史,为统计模块提供数据支撑”。不要每一张表都长篇大论,重点写task表和task_log表的设计理由,其他表一句带过。
5.4 系统测试章节:表格比文字有用
系统测试章节几乎是论文评委翻得最快的一章,也是最容易拿印象分的一章。
我的做法是:先列测试环境,包括操作系统、数据库版本、浏览器、后端运行环境;再列功能测试用例表,格式为用例编号、测试模块、操作步骤、预期结果、实际结果、是否通过。
测试用例要覆盖正常流程和异常流程。正常流程就是任务创建到归档的完整链路;异常流程至少要有:未登录访问受保护页面、无权限用户尝试创建任务、重复接收任务、提交验收后驳回重新提交、截止日期早于当前时间。这五类异常测试用例写上去,论文评审至少会认为你系统思考过健壮性问题。
5.5 每个模块在论文和代码中都要有“实物”
我坚持一个原则:论文里提的每一个模块代码里都要找得到。比如通知消息模块,代码里有notification表、有创建任务时插入通知的逻辑、有前端“我的消息”红点提醒列表,论文里就要有对应的功能介绍和页面截图。哪怕只是一句话“系统通过站内消息机制向相关用户发送任务变更通知”,也得有代码支撑。
页面截图的选择也有讲究。尽量截关键页面:登录页、任务列表页、任务详情页(状态时间线展开)、创建任务弹窗、统计看板页。每张截图配一段说明文字,说清楚页面功能、操作方式和数据来源。
6. 部署演示与答辩准备:从跑通到拿高分的临门一脚
代码写完、论文写完,最后一个大环节是部署演示和答辩。这一关决定前面所有工作的最终呈现效果,但恰恰是很多同学最不重视的部分。
6.1 本地启动全流程与常见报错排查
完整启动顺序分三步。
第一步,数据库。先启动MySQL,用Navicat或命令行导入项目自带的SQL脚本。导入成功后检查一下表是否创建完整、初始数据(管理员账号、角色数据)是否正常。SQL脚本里的建表语句一定要用utf8mb4字符集,否则中文显示乱码。
第二步,后端。IDEA里导入后端项目,等Maven下载完依赖后修改数据库连接配置,改成自己本机的数据库账号密码,然后启动SpringBoot或部署到Tomcat。看到“Tomcat started on port(s): 8080”字样说明启动成功。
第三步,前端。命令行进入前端项目目录,npm install安装依赖,然后npm run serve启动开发服务器。浏览器访问前端地址就能看到登录页面。
我列一下最常见的几个启动报错和解决方法:
| 报错现象 | 根本原因 | 解决方法 |
|---|---|---|
| Access denied for user | 数据库账号密码错误 | 修改后端配置文件中的数据库连接 |
| Unknown database | 数据库不存在或名字写错 | 检查数据库名和连接串URL |
| Invalid bound statement | Mapper接口和XML不匹配 | 检查namespace、方法id、XML位置 |
| 中文乱码 | 字符集配置错误 | 连接串加characterEncoding=utf8,表用utf8mb4 |
| 时间显示差8小时 | 数据库连接时区问题 | 连接参数加serverTimezone=GMT%2B8 |
| 前端请求返回401 | token缺失或过期 | 检查是否已登录,检查上传组件headers |
启动成功后,先不要急着演示。按“管理员→负责人→执行人”的顺序完整走一遍流程:管理员登录创建用户、负责人登录创建任务指派、执行人登录接收并提交、负责人验收归档。走通后再清理测试数据,准备正式演示账号。
6.2 答辩现场演示脚本:两分钟讲清核心链路
答辩时最怕的不是功能少,而是演示没有重点,把每个页面都点到,每个功能都讲两句,评委反而抓不住核心。
我的建议是准备一个“演示脚本”,把演示内容压缩到2到3分钟,只讲核心链路:
- 用管理员账号登录,介绍用户管理和角色分配,一句话带过。
- 切换到负责人账号,创建一条测试任务,指派给执行人,展示任务列表中出现新记录。
- 切换到执行人账号,接收任务、提交一条进度反馈,再提交验收。
- 切换回负责人账号,查看任务详情里的时间线和进度反馈,点击验收通过。
- 打开统计看板,展示任务完成率等数据,说明数据来源于操作日志表和任务表。
这个演示路径是一个完整闭环:建任务→派任务→接任务→做任务→验收任务→统计任务。评委一眼就能看出系统逻辑是通的,比把所有功能都点一遍有效得多。
6.3 答辩高频提问与回答思路
答辩评委的问题一般集中在几个方向:为什么用这套技术、某个功能怎么实现、某个字段为什么这样设计、系统的不足是什么。我列几个高频问题和回答思路,你可以提前准备。
“为什么用SSM而不是SpringBoot?”回答思路:SSM是学校课程体系里的经典框架组合,能够更清晰地体现Spring、SpringMVC、MyBatis三者的职责划分,对理解框架底层原理更有帮助;而SpringBoot虽然简化了配置,但隐藏了太多细节。论文里会补充一句“SpringBoot是SSM的进一步封装,技术本质相同”。
“任务状态为什么用int不用字符串?”回答思路:int类型存储占用小、排序方便、筛选效率高,配合状态字典可以灵活扩展;字符串在业务扩展时容易产生新状态值而不好维护。这个回答直接体现你对状态机设计有思考。
“如果两个执行人同时接收同一个任务怎么办?”回答思路:接收逻辑使用带状态条件的UPDATE语句,从数据库层面保证只有一个执行人能成功接收,另一个更新影响行数为0后会被提示任务已被接收。
“这个系统有什么不足?还有什么可以改进?”这里是送分题,但要答得真诚。可以提:目前通知是站内消息模式,未来可以引入邮件或企业微信推送;附件存储用的是本地磁盘,未来可以改造成对象存储;统计分析目前是指标展示,未来可以做成可视化报表大屏。不要说自己系统“没有不足”,也不要自曝重大缺陷。
6.4 答辩前最后检查清单
答辩前一天,我建议你按清单过一遍。管理员和普通用户的账号密码确认可用、测试数据清理干净、浏览器打开过的无用标签页关掉、后端日志刷屏时可以随手清一下、投影时字体调大一点。关键的一步是:提前在答辩电脑上用无痕模式打开系统,完整走一遍演示脚本,确保不会因为浏览器缓存了旧token导致演示时退出登录状态。
我自己的经验是,演示失败最常见的原因不是系统本身有问题,而是临时网络波动、数据库没启动、后端进程被杀掉了这类环境问题。提前演练能发现绝大多数环境问题,这比临时抱佛脚有效得多。
再做一次完整流程的“冷启动测试”:关掉所有前后端进程、重启MySQL、启动后端、启动前端、打开浏览器走完整个流程,确认全部正常。这一遍跑通,答辩基本就稳了。