每年到了毕业设计的季节,总有人私信问我:Java Web方向的题目怎么选,才能既不太难,又有东西能讲。我的看法一直很明确——SpringBoot+Vue的公寓报修管理系统,属于那种"看着朴素,实际上很完整"的稳妥型选题。它没有商城、博客那么泛滥,但业务闭环清楚:住户能报修、维修工能接单、管理员能派单和统计,天然就是一个前后端分离的多人协作系统,几乎覆盖了SpringBoot和Vue在大作业级别能遇到的所有核心套路。这篇就把这个项目的完整拆解写出来,从需求定位、数据库设计、后端权限控制到Vue页面联调,再到SQL脚本和接口文档怎么用、上线跑通要注意什么,一条线说清楚。你如果是正在纠结选题,或者拿到源码之后不知道怎么消化,这篇文章可以直接当参考手册看。
1. 从选题到落地:公寓报修管理系统的需求拆解与定位
1.1 为什么报修系统是Java Web毕设的"稳妥型"赛道
很多人选题的时候,第一反应是"做电商"或者"做社交",结果越做越复杂,最后答辩的时候被一个问题卡住就下不来台。报修系统不一样,它的业务逻辑是所有人都能快速理解的:我住在某栋楼某房间,灯泡坏了,我提交报修;物业管理员看到之后安排维修工;维修工上门处理完,我做个评价,流程结束。这个场景非常朴素,但恰恰是它最大的优点——你在答辩介绍的时候,不需要铺垫一堆背景,一句话讲完,老师立刻知道你做的是什么。
从技术覆盖角度来说,这个题目也很划算。系统里至少有三种角色(住户、维修工、管理员),这就逼着你处理用户认证和权限区分;报修单从提交到完成有多个状态,这就要设计状态流转和工单逻辑;不同角色看到的是不同页面和不同数据范围,这又牵扯到前后端联调时的数据隔离。再加上分页、搜索、图片上传、数据统计这些常见需求,一个项目做下来,SpringBoot的Controller/Service/Mapper分层、JWT或Session的鉴权方式、Vue的路由守卫和组件通信,全都练到了。相比之下,只做个博客或者学生管理系统,要做到这个完整度反而要额外硬加功能。
还有一点是实际体验上的优势:因为流程是线性的,最后做出来演示效果特别好。从住户注册登录、提交报修,到管理员派单完成,整个过程不到一分钟就能走完一遍,演示不会翻车。你要是做个复杂的电商项目,演示时万一支付流程出问题,或者并发数据对不上,场面就很尴尬。
1.2 三种角色三条主线:维修工单状态机的设计思路
报修系统的核心,不是那些增删改查页面,而是那张维修工单,以及它在整个生命周期里的状态变化。我接触过的不少学生项目,报修单就两个字段:状态(0已报修、1已完成),非常简单,但也暴露了问题——管理员到底有没有派单?维修工是不是已经在路上了?住户能不能知道目前的进展?全都没有体现。
一个比较完整的状态设计建议是五到六个状态:
- 待派单:住户提交后,还没有维修工认领;
- 已派单:管理员或系统把工单分给了某位维修工;
- 处理中:维修工确认接单,已经开始处理;
- 已完成:维修工填写处理结果,工单关闭;
- 已评价:住户对这次维修进行了评价,流程完全结束。
有些项目还会加一个"已取消",用来处理住户报错了或者不打算修的情况,这个按需求取舍。状态机的价值在于,它能帮你把"谁在哪个状态下能做什么事"这件事说清楚。比如住户只能在待派单时取消工单,维修工只能把已派单的工单改成处理中、把处理中的改成已完成,管理员可以派单但是不能直接替维修工填写结果。这个约束如果不做,就会出现用户随便改状态、严重破坏业务逻辑的情况,而那正是答辩时老师最容易深挖的攻击点。
实现上不需要引入特别复杂的框架,在Service层写清楚状态迁移就行。我把这种写法称为"显示状态校验",虽然代码多一点,但每个状态能到达哪些后续状态,读代码的人一眼就能看明白,后续加需求也不会改乱。
1.3 MVP范围:哪些功能必须做,哪些宁可不做
毕设项目的功能范围,我的建议永远是"做深做通,而不是做多做杂"。一个能拿高分的公寓报修系统,最少要有这五个模块:
- 用户管理:注册、登录、个人信息维护,默认区分住户、维修工、管理员三种角色;
- 报修管理:住户提交报修、查看自己的报修列表、撤销未接单的报修;
- 派单管理:管理员查看所有待派单,分配给维修工,也能重新指派;
- 维修管理:维修工查看被派给自己的工单,填写处理进度和完成情况;
- 评价管理:住户对已完成的工单打分和留言,管理员可以看到评价列表。
加分项可以选统计报表(比如按楼层统计报修次数、维修工工作量排名)、公告管理、报修进度提醒这类功能。但像在线聊天、短信通知、维修工定位打卡这种,我只建议把它写进开题报告的"后期展望"里,不建议真做——这些功能要么对接第三方服务,要么涉及实时通信,工作量翻倍,但拿到的分数增量很有限。做项目要讲投产比,尤其是毕设这种有硬时限的活儿。
2. SpringBoot后端:表结构设计与权限控制的几个关键决策
2.1 六张核心表的字段设计与关系梳理
数据库设计是整个项目的地基。很多学生项目后期改来改去,根因就是表结构设计偷懒了。这套报修系统,我建议核心表控制在六张左右,既够用又不臃肿。
第一张是用户表,字段大概包括用户ID、用户名、密码、手机号、真实姓名、角色、所属房间ID、创建时间。需要说明的是,"维修工"我也是放在用户表里,用角色字段去区分,而不是单独建一张维修工信息表。毕设级别的业务里,维修工也就需要姓名、电话、工号这些信息,用户表完全承载得下。当然,如果你后续想加维修工技能标签(比如擅长水电还是家电),那再单独拆表也不迟。
第二张是房间表,字段有房间ID、楼栋号、房间号、当前住户ID。这里有个常见问题:报修单上报修地址是去关联房间表实时查,还是在报修单里存一份"冗余地址"?我的建议是报修单里直接冗余存放楼栋号和房间号。理由很简单:报修查询的频率比房间变更的频率高得多,而且住户搬走之后,历史工单上的地址信息不应该跟着变。这个细节体现了你对数据建模的理解,答辩时如果被问到,能讲出逻辑来就是加分项。
剩下四张表分别是报修单表、派工记录表、评价表和系统公告表。报修单表是核心,字段包括报修单号、报修人ID、房间冗余信息、报修类型、文字描述、图片路径、状态、期望上门时间、提交时间、完成时间。派工记录表记录工单ID、维修工ID、派单时间、备注,这样就能追溯"这个单是谁派的、派给了谁"。评价表关联工单ID、住户ID、评分、评价内容、评价时间。公告表就很简单,标题、内容、发布时间、发布人。
这里我特别提醒一条:所有业务表都要保留创建时间和更新时间两个字段,不要偷懒删掉。哪怕系统里现在没用到,后期做统计、排查问题的时候这两个字段都是救命稻草,而且SQL脚本里加上这两个字段成本几乎为零。
2.2 报修单状态流转的后端Java实现
前面讲了状态机设计,现在说代码怎么写才能干净。先看一段核心的Service层代码思路,以维修工确认接单为例:
public void acceptOrder(Long orderId, Long workerId) { RepairOrder order = repairOrderMapper.selectById(orderId); if (order == null) { throw new BusinessException("工单不存在"); } if (!order.getStatus().equals(OrderStatus.ASSIGNED.getCode())) { throw new BusinessException("当前状态不可接单"); } if (!order.getWorkerId().equals(workerId)) { throw new BusinessException("该工单未分配给你"); } order.setStatus(OrderStatus.PROCESSING.getCode()); order.setAcceptTime(new Date()); repairOrderMapper.updateById(order); }这段代码看着没什么花哨,但注意几个细节。第一是每一步都做前置状态校验,不符合预期就直接抛异常,绝不偷偷改状态。第二是"是否分配给我"这种数据范围校验放在Service里做,而不是只在前端隐藏按钮。第三是接单的同时记录acceptTime,所有关键时间点都要留在数据里,后面做统计报表就靠它了。
状态流转如果分支很多,我建议用一个工具类,把每种状态的可达状态做成Map,然后用统一的changeStatus方法去校验和转移。这样后续新增一个状态(比如"待评价"),只要改一张表和一个Map,比满屏if-eLse好维护得多。
public void changeStatus(RepairOrder order, String targetStatus) { Map<String, List<String>> stateMachine = StateMachine.getTransitions(); List<String> allowed = stateMachine.get(order.getStatus()); if (allowed == null || !allowed.contains(targetStatus)) { throw new BusinessException("不允许从[" + order.getStatus() + "]变更为[" + targetStatus + "]"); } order.setStatus(targetStatus); }这套思路本质上是简化版的状态模式。面试和答辩都爱问"如果状态规则很复杂你会怎么设计",你把这套Map驱动说明白,比死记硬背状态模式的定义图有用得多。
2.3 登录鉴权与角色权限:拦截器配合JWT的做法
既然有三类角色,接口必须做权限控制。部分项目用Session,也完全能跑通,但既然用了前后端分离,我更推荐用JWT,理由就一条:前端拿到Token之后存起来,以后每个请求头里带上,后端无状态校验,部署到服务器上不用考虑Session共享的问题。
SpringBoot这边的基本结构是:登录接口校验用户名密码,成功后签发一个Token,把用户ID和角色塞进去;然后定义一个拦截器,拦截所有需要登录的路径,先从请求头里取Token,解析成功就把用户信息放进ThreadLocal,方便后续Controller里取当前用户;最后在注解或者拦截器里判断角色是否满足接口要求,比如派单接口必须管理员才能调。
一个比避坑的点是Token解析失败和被拒绝的区别。Token没传、传了假Token、Token过期,这三种情况前端要能区分。我会统一返回业务码401,提示"请重新登录"。前端收到401就清掉本地Token,跳回登录页。这个联动机制前后端必须商量好,否则就会出现页面还停在列表、但后端一直报未登录的诡异状态。
角色控制我建议用注解实现,自定义一个@RequireRole("ADMIN"),在拦截器解析完Token之后,利用Spring的HandlerMethod拿到方法上的注解做判断。比路径前缀匹配灵活得多,想要对某个接口做多角色放行也方便。
2.4 为什么接口要包一层统一返回体
做前后端分离项目,统一返回体不是可选项,是必选项。我见过最朴素的写法是Controller直接返回Map,成功就返回一个带数据的Map,失败就返回一个带消息的Map,前端拿到之后自己猜结构。这种写法前期很爽,后期加字段、改错误类型的时候,前端每个回调都要跟着改,非常痛苦。
我建议所有接口统一返回下面这种结构:
{ "code": 200, "message": "操作成功", "data": {} }对应的Java类一般叫ResultVO或者R,提供几个静态方法:R.ok()、R.ok(data)、R.fail(message)。然后配合全局异常处理器,Controller里只管正常返回值,业务异常和系统异常统一在@RestControllerAdvice里捕获,转成标准的失败返回。这套架构一旦搭好,整个项目写接口就像填空一样快。
前端也不用每次在回调里做兼容。封装的请求方法里先判断code是否为200,不是就直接弹错误提示,是就返回data部分。全局只有一条判断逻辑,维护成本极低。答辩时如果老师问"你项目里异常是怎么处理的",你能把统一异常和全局异常处理器这套讲清楚,属于很亮眼的工程习惯。
3. Vue前端:页面拆解与接口对接的实战细节
3.1 路由与侧边栏:按角色动态生成菜单的两种写法
前端和后端最大的区别在于,前端是给人看的,不同角色看到的界面必须不一样。住户登录之后应该看到"我要报修、我的工单、我的评价",维修工看到的是"待办工单、我的维修记录",管理员才是完整的侧边栏菜单。
动态生成菜单有两种常见写法。第一种是注册所有路由,但在组件渲染时根据角色用v-if控制菜单显隐,好处是切换角色时不用加载路由,缺点就是路由表对所有人可见,你能直接猜测到系统里存在哪些隐藏页面。第二种是登录后根据后端返回的菜单列表,用router.addRoute动态注册路由,侧边栏也由这份菜单数据生成。我推荐第二种,虽然实现起来多几步,但"后端返回菜单"这种模式在企业项目里才是主流,而且答辩时能讲出这套设计,说明你不是只会写页面的。
菜单数据我在后端用一张菜单表管理,或者干脆在前端写死一个角色菜单映射。考虑到毕设体量,前端的roleMenuMap就够用了,别为了设计而设计。核心代码长这样:
const roleMenuMap = { HOUSEHOLD: ['home', 'report', 'myOrders'], WORKER: ['home', 'todo', 'myComplete'], ADMIN: ['home', 'orderManage', 'userManage', 'typeManage', 'stats', 'notice'] }在路由守卫beforeEach里先判断是否登录,再判断目标路由是否在当前角色允许的名单里,不允许就直接重定向到首页。这套逻辑虽然简单,但能挡住用户手动输入无效地址的情况,结算安全体验会好很多。
3.2 报修单列表页:分页、搜索与状态标签的落地细节
报修单列表是整个系统最核心的页面,住户、维修工、管理员看到的是同一张表的三种切片。前端用Element Plus的el-table做列表,配合el-pagination分页和上方的筛选条件区。筛选条件建议包含状态、报修类型、时间范围,再加一个关键词搜索(匹配报修人姓名或房间号)。
这里有一个特别容易出bug的地方:前端传参风格和后端接收风格必须一致。后端如果用PageHelper或者MyBatis-Plus的分页插件,通常接收current、pageSize这种字段;筛选条件可能是status、type;日期范围可能是startTime、endTime。前端直接照搬这些字段名,不要自己另起一套名字,否则联调时你会在浏览器网络面板里调半天。
状态标签这一块,我习惯在前端做一个枚举映射对象,把后端传的状态码翻译成中文标签和对应的标签颜色:
const statusMap = { 0: { text: '待派单', type: 'warning' }, 1: { text: '已派单', type: 'primary' }, 2: { text: '处理中', type: 'info' }, 3: { text: '已完成', type: 'success' }, 4: { text: '已评价', type: '' } }不要在后端返回"待派单"这种中文,而是返回状态码,前端负责展示。原因很简单:后端接口是给所有调用方用的,这种状态码的中文文案以后可能会变(比如"待派单"改成"待受理"),但只要码值不变,前端改一下映射就好,不需要动后端代码。这就是前后端分离里"枚举语义共享"的基本功。
3.3 axios封装:请求加token、401统一跳转
前端和后端对接,第一件事是把axios封装成统一入口。我发现很多学生项目是每个页面import axios直接用,看起来没啥问题,但一旦遇到"Token过期要统一弹登录提示""错误弹窗样式要统一"这种需求,就得满项目改代码。我在项目里会建一个request.js,先把所有逻辑收口。
const service = axios.create({ baseURL: '/api', timeout: 10000 }) service.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers['Authorization'] = 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('登录状态已过期')) } ElMessage.error(res.message || '请求失败') return Promise.reject(new Error(res.message)) }, error => { ElMessage.error('网络异常,请稍后重试') return Promise.reject(error) } )这段代码里有两个容易被忽略的点。第一是请求拦截里,从localStorage拿Token,等登录成功之后一定要把Token存进去,我习惯叫token,统一就好。第二是响应拦截里如果拿到了code: 401,除了清Token跳登录页,最好把用户原先想访问的地址一起带到登录页,登录成功后再帮忙跳回去,这样体验才算完整。get传参时用params,post用data,这个不复杂,但错了很难排查,先在这里说清楚。
3.4 用时间线组件展示维修进度,小成本提升完成度
很多报修系统做出来功能没问题,但视觉上"单薄",就是因为没有考虑到用户需要知道"事情进展到哪一步"。我的建议是给工单详情加一个时间线组件,展示这张工单的关键节点:提交报修→管理员派单→维修工接单→维修完成→住户评价。前端用el-timeline,后端保证报修单和派工记录里的时间字段完整,前端按顺序渲染。
实现上不需要额外的复杂接口,数据从工单详情接口就可以拿到,把几个字段重新组装成时间线需要的结构就行:
const timeline = [ { title: '提交报修', time: order.createTime, icon: 'document' }, { title: '管理员派单', time: order.assignTime, icon: 'user' }, { title: '维修工接单', time: order.acceptTime, icon: 'tools' }, { title: '维修完成', time: order.finishTime, icon: 'circle-check' } ]这个功能代码量不大,但演示效果提升明显。平时老师看学生项目,都是"列表-详情-表单"这种老三样,突然看到一个有时间线的进度视图,会认为你站在用户角度做了思考。另外,如果后端把状态更新时对应的时间都记录为null(没赋值),这里渲染就会空白,所以项目里要养成习惯:凡是状态变化,顺手把时间字段也update进去。
4. SQL脚本与接口文档:这两份"附赠文件"的关键使用方法
4.1 SQL脚本导入顺序与初始数据说明
拿到源码后第一件事不是读代码,而是先把数据库跑起来。看清楚SQL脚本提供的结构,一般会有建库语句、建表语句、初始数据插入三部分。我建议的导入顺序是:先建库,再切换use,然后按表依赖顺序建表——用户表→房间表→报修单表→派工表→评价表→公告表,最后插入初始数据。
初始数据通常包含管理员账号、几个维修工账号、几个住户账号、一些房间信息和几条演示报修记录。这是最容易被忽略的宝贝:如果没有初始数据,你登录进去空荡荡一片,连列表分页都看不出效果。拿到SQL脚本后,强烈建议先看清楚初始账号和各账号的角色,在接口文档里一般会写明默认密码。如果文档没写,SQL脚本里通常能看到插入语句的字段值,是123456还是admin,一目了然。
另外一个提醒:插入初始数据时,报修单和派工表之间是有外键关联的,所以插入顺序一定是先工单后派工,断掉关联去插数据会报外键约束错误。虽然用MyBatis-Plus可以不管外键,但建表脚本里往往是带外键的,导入时顺序错了报错,很多人就会以为源码有问题,其实只是顺序问题。
4.2 接口文档要重点看哪几块
接口文档是前后端联调的说明书,拿到之后不要从头到尾看,重点看四块。第一是统一返回体数据结构,前面讲了code、message、data的约定,这是所有接口的前置知识。第二是鉴权方式,文档里会说明哪些接口需要登录,Token放在哪里,一般是请求头里的Authorization字段。第三是核心接口的路径和参数说明,比如登录接口、提交报修接口、派单接口、状态流转接口。第四是数据库初始化相关的操作说明,比如如何创建账号。
看接口路径的时候也要总结规律。这个项目的RESTful风格大概率是/api/order/list、/api/order/report、/api/order/assign、/api/order/complete这种,动词和后缀直接表达了操作。按这个规律,你甚至不用看文档都能猜到新接口该叫什么。这就是RESTful设计的好处,相对路径一目了然。
用接口调试工具(随便哪款都好用)测接口时,建议直接导入项目里提供的接口集合文件,没有的话就手工照着文档建一遍。把登录接口调通拿到Token,然后把Token填到全局变量里,后面调查单、派单这些接口就能一路测下去。接口文档和实际代码如果出现对不上的情况,比如参数名大小写不一致,以代码为准,但要把问题记录下来,这也是之后写答辩PPT素材的来源。
4.3 联调最常见的坑:下划线字段名与驼峰命名不一致
前后端联调最头大的问题,我在前面提过,现在专门展开。MySQL表字段习惯用下划线风格,比如create_time、worker_id;Java实体类习惯用驼峰,比如createTime、workerId;Vue如果直接把后端返回的数据拿来用,字段名必须和Java侧保持一致。
如果项目里配了MyBatis的驼峰映射:
mybatis: configuration: map-underscore-to-camel-case: true那么从数据库查出来的下划线字段会自动映射成Java实体的驼峰属性,这部分没有坑。坑在Vue侧:后端返回的JSON如果你直接用实体序列化,默认还是Java属性的驼峰名字,所以前端拿到的就是createTime、workerId这样的字段。如果你的VO里故意改了字段名,或者后端加了下划线风格的Map作为返回,前端就会拿到create_time,然后你写row.createTime永远取不到值。
碰到这种问题,用浏览器F12看网络面板里的JSON响应,把实际返回字段名和前端代码里的字段名对比一眼就明白。这个问题的排查思路也是答辩时能体现工程经验的,老师问"你怎么排查Bug",你能说出"先看网络面板确定数据格式,再定位是后端映射问题还是前端字段问题"这种话,得分会高很多。
5. 从运行到答辩:环境踩坑、二次开发与高频追问
5.1 本地运行环境清单与几个高频启动报错
把项目跑起来之前,先检查环境:后端需要JDK8或11、Maven、MySQL;前端需要Node(版本不要太新也不要太老,16到20之间基本没问题)。前后端分离项目还要注意:后端启动后监听8080还是其他端口,前端Vue项目的package.json以及相关配置里有没有配代理转发,把/api开头的请求转发到后端端口。代理配置不对,前端页面能打开但所有请求都会404或者报跨域,这是出现频率最高的启动问题。
我整理几个高频报错和解决思路:
- 端口被占用:启动SpringBoot时报Web server failed to start,查一下8080被谁占了,改掉后端的
server.port,或者关掉占用程序。 - MySQL时区报错:连接串后面加上
serverTimezone=Asia/Shanghai,这个问题在MySQL 8下面特别常见,报错信息像乱码一样,其实就差这个参数。 - Maven依赖下载慢:第一次加载SpringBoot依赖可能需要很长时间,检查Maven配置里有没有用国内的镜像源。
- 前端npm install卡住:和Maven一样,把npm源切到国内镜像,然后重新安装,基本能解决。
这些坑单独任何一个都不难,但叠在一起会非常消耗耐心。我的建议是搭建环境时一步一步来,后端先跑通再启前端,这样才能定位问题是出在哪个环节。如果后端启动后直接访问Swagger接口或者用接口工具调一下,能返回数据,再开始联调前端。
5.2 如果这学期还有时间:三个值得二次开发的方向
基础功能做完了,代码也跑通了,如果你还想让项目在结题时更亮眼,我建议按优先级试试下面三个方向。
第一个是图片上传。现在的报修单大概率只是文字描述,如果能支持上传照片,录入住户报修热水器坏了,配上一张漏水照片,整个系统的真实感瞬间上来。实现也简单,后端接收MultipartFile保存到本地目录,返回可访问的URL存到数据库,前端在表单里用el-upload组件调用接口。注意给上传目录配置静态资源映射,别存到磁盘路径后URL访问不到。
第二个是数据统计图表。管理员页面加一个ECharts报表页,统计这学期各楼栋的报修次数、报修类型分布、每个维修工的处理量排名。后端只需要写几个带GROUP BY的聚合查询接口,前端用ECharts折线图和饼图展示。这个功能视觉冲击力很强,答辩PPT里插几张截图非常加分。
第三个是WebSocket通知。当管理员派单给维修工时,前端的维修工页面能实时收到一条提醒,而不是刷新才能看到。这个功能做起来也不难,SpringBoot集成WebSocket,前端监听消息,代码量在可控范围内。不过需要提醒一下,WebSocket的鉴权比普通HTTP麻烦,容易引入复杂度,如果时间不够,这个可以只做"启动时全局推送一条欢迎消息"这种程度,当作技术亮点提一下即可。
5.3 答辩时容易被追问的问题与拆解思路
答辩环节,老师问的问题往往绕不开几个方向。提前准备,回答的时候就不会慌。
第一个高频问题必然是"这个项目里你遇到的最大的问题是什么,怎么解决的"。不要答"没有",也不要泛泛说"学会了前后端分离"。比较稳的回答是:报修单状态流转时曾经出现过用户绕过前端直接调接口修改状态的情况,后来在后端Service层加了前置状态校验和角色判断,确保只有符合状态规则的请求才会被处理。这个回答既体现了你做了安全性思考,也展示了完整的排查链路。
第二个常见追问是"为什么用JWT不用Session"。别答"用它比较流行",要有对比:前后端分离下Session不够灵活,移动端也要接入的话Token更通用;JWT是无状态的,天然适合后端的水平扩展。顺手补一句JWT也有缺点,比如过期时间不好主动废除,但毕设场景下完全够用——这种既要又要的回答最有说服力。
第三个追问方向是"这些接口怎么保证数据安全"。可以从三个层次说:登录需要密码加密存储(哈希算法很多,取一种常见的就可以),修改敏感信息需要校验当前登录用户身份,后端接口会做角色权限控制,前端按钮隐藏只是第一层。再补一句"文件上传时验证了文件类型和大小",印象分会很扎实。
第四个问题是"如果用户量很大,这个方法跑得动吗"。这题其实是在考察架构思维。你可以先承认MySQL单表在量级上来后肯定不行,然后说可以通过分页已经是必须的,未来可以做读写分离、报修单冷热数据归档,以及静态资源走独立存储。关键是你能不能讲清楚优化的思路,而不是真的把项目改成分布式,那是毕业设计不需要承担的事。
第五个是"这个设计里你觉得哪里还可以做得更好"。这是开放题,不要紧张。你可以指出现有报修单的紧急程度目前只是普通和紧急两档,未来可以引入自动派单算法;或者评价维度可以拆成速度、态度、质量三个维度,再把平均分展示在维修工详情页里。把短板说在前面,并且接一个改进想法,这就是最漂亮的收尾。
我个人的经验是,答辩前花一个小时把项目的核心流程从数据库到前端页面完整走一遍,每个操作对应哪些表和接口,心里有数。老师问任何一个页面,你都能顺着业务流程讲出背后逻辑,这种"即问即答"的状态,比任何套路话术都管用。最后再说一句,报修系统这个题虽然不花哨,但正因为它的结构和真实业务高度匹配,只要你把细节讲透、把过程讲实,它就能成为一份很扎实的毕业设计。