每年到这个时间点,就会有一批计算机专业的大四学生开始为毕业设计头疼。2026届的学弟学妹们,如果你正在纠结选题,或者已经选了“基于SSM的教工公寓管理系统”这类题目却不知道从何下手,这篇内容就是写给你看的。
这套题目可以说是经典中的经典:后端用SSM(Spring + SpringMVC + MyBatis),前端用Vue,再加一套完整的毕设论文。技术栈不新但也绝不落伍,难度适中,项目规模恰到好处,评委老师看着也眼熟——选它作为毕业设计,稳妥。但这并不意味着你能糊弄过去,正因为经典,老师对它的“标准完成度”心里有数,你做到什么水平,他一眼就能看出来。这篇文章我就把这个项目的完整设计思路、技术细节、实操步骤、论文结构和踩坑记录一次性聊透,保证你能拿来直接用。
1. 项目整体设计与技术选型思路
1.1 为什么是SSM+Vue而不是其他组合
很多同学一上来就会问:“老师,用SpringBoot不好吗?为什么还要用SSM?”
这个问题我在不知道多少个论文开题答辩现场听过。答案其实很实际:SSM(Spring + SpringMVC + MyBatis)是传统Java Web开发中分层最清晰、教学体系最成熟的一套组合。Spring管对象、SpringMVC管请求流转、MyBatis管数据库操作,三层架构天然分隔,每一层都能在论文里单独拎出来写一段,对毕设而言,这不是缺点,反而让论文更容易写出深度和条理。
另一个现实因素是:不少学校的Java课程还在用SSM体系教学,机房里的实验环境、老师的参考代码、往届学长留下的模板,全都基于SSM。你适当地“保守”,用大家熟悉的技术栈,意味着当你卡壳时,能捞你的人更多。
前端选Vue的原因就更加直白:Vue 2/3的生态成熟,上手曲线在三大框架里最平缓,中文资料和社区活跃度也是最高的。一个管理系统需要表单、表格、弹窗、路由跳转、状态展示,Vue的响应式数据绑定和组件化开发能把这些事情简化太多,用原生JS写,代码量翻三倍以上还不一定保证不出bug。
1.2 教工公寓管理系统的核心需求拆解
毕设题目叫“教工公寓管理”,和常见的“学生宿舍管理系统”不一样,重点服务对象是教职工,业务流程有明显的特殊性。普通学生宿舍是统一分配、按学期缴费、强制管理;教工公寓则更像是“人才周转房”,涉及申请、审核、分配、续住、退房、租金核算和维修上报。
我在指导学生的过程中,建议至少把系统拆成以下几个核心模块:
- 教职工信息管理:教职工的工号、姓名、部门、职称、联系方式、家属信息(因为教工公寓通常允许带家属入住)。
- 公寓房源管理:公寓楼信息、楼层与房间号、户型(一室一厅/两室一厅)、面积、朝向、当前状态(空闲/已入住/维修中)。
- 入住申请与审核:教职工线上提交入住申请,管理员审核资格,分配房源,生成入住记录。
- 退宿与搬迁管理:退宿申请、水电表结清、房间验收、押金退还流程。
- 租金与费用管理:按月记录房租、水电费、物业费,支持缴费状态追踪和账单导出。
- 报修管理:住户提交报修工单,维修员接单、填写维修结果。
- 公告通知管理:发布停水停电、社区活动、政策调整等公告。
- 角色权限管理:系统至少要有管理员、教职工(住户)、维修人员三类角色,不同角色看到的菜单和可执行操作必须隔离。
把这八个模块放在论文里,就构成了一个标准的“功能性需求分析”章节,主次分明,该有的业务闭环一个不少。
1.3 前后端分离架构的核心思路
传统SSM项目是后端渲染JSP页面,但既然用了Vue,就建议走前后端分离路线。后端SSM项目只负责提供JSON格式的RESTful接口(如/api/house/list、/api/staff/login),前端由Vue工程负责页面渲染和用户交互,通过axios发起HTTP请求,拿到JSON数据后动态展示。
前后端分离带来的直接好处是:前端开发可以和后端并行,只要接口文档约定好了,两边各自开发互不阻塞。对于你自己做毕设,等于把战线拉成两条,看起来工作量大了,实际上是分散了压力——后端写累了可以调前端界面,前端卡住了就回来改改Mapper接口,不容易陷入死磕一个点的僵局。
同时,前后端分离项目在论文里能专门写一个小节讲“前后端数据交互规范”,包括请求封装、状态码约定、异常处理结构。这些内容非常加分,能体现出你不仅是把功能跑通了,还具备工程化意识。
2. 数据库设计与后端核心实现详解
2.1 核心表结构设计——五张表起步,八张表稳妥
数据库设计能看出一个学生的基本功,也是答辩老师重点提问的区域。我的建议是最少设计以下八张核心表:
| 表名 | 核心字段 | 作用 |
|---|---|---|
| staff_info | id, staff_no, name, department, title, phone, family_info | 教职工基本信息 |
| building_info | id, building_no, layer_count, room_count, address | 公寓楼栋信息 |
| room_info | id, building_id, room_no, house_type, area, status | 房源房间信息 |
| check_in | id, staff_id, room_id, check_in_date, expected_out_date, status | 入住记录与当前状态 |
| payment_record | id, staff_id, room_id, fee_type, amount, month, status | 租金水电等缴费记录 |
| repair_order | id, room_id, reporter_id, content, assignee_id, status, result | 报修工单 |
| notice_info | id, title, content, publish_time, publisher_id | 公告信息 |
| sys_user | id, username, password, real_name, role | 系统登录账号与权限 |
其中sys_user和staff_info可以是独立的,也可以做一对一关联。我个人建议独立成表,因为登录账号涉及密码和角色,和教职工业务字段混在一起既不利于安全,也容易导致表结构变得臃肿。
在设置字段时,有几个细节必须注意。一是所有金额字段(amount、deposit等)建议用decimal(10,2),不要用float,否则会出现0.1加0.2不等于0.3这类让人血压升高的浮点精度问题。二是时间字段建议用datetime,并且默认值设为CURRENT_TIMESTAMP,这样插入数据时自动记录时间,少写一行代码。三是一定要设计逻辑删除字段is_deleted,虽然毕设项目规模小,但加上这个字段就能在论文“数据库设计”里写一句“考虑到数据的可追溯性,采用逻辑删除而非物理删除”,显得专业。
2.2 SSM整合时的依赖与配置文件要点
SSM整合的“三座大山”就是三个配置文件:spring-mvc.xml、spring-mybatis.xml、web.xml。很多新手一上来就在这栽跟头,不是包扫描漏了,就是路径映射配错。
Maven的pom.xml中核心依赖就这么几个:spring-webmvc、mybatis-spring、mybatis、druid连接池、mysql-connector-java、jackson-databind(JSON转换必备)。注意jackson-databind必须配,SpringMVC返回对象转JSON全靠它,漏了这个你前端拿到的一律是乱码或者报406错误。
spring-mvc.xml里关键配置是开启注解驱动和配置Json转换器:
<mvc:annotation-driven> <mvc:message-converters register-defaults="true"> <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"> <property name="objectMapper" ref="jacksonObjectMapper" /> </bean> </mvc:message-converters> </mvc:annotation-driven>这个配置加上以后,Controller里返回的对象就能自动序列化成JSON,前端拿到的就是标准的数据结构。
spring-mybatis.xml里的核心是配置SqlSessionFactoryBean和MapperScannerConfigurer:
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="typeAliasesPackage" value="com.example.entity" /> <property name="mapperLocations" value="classpath:mapper/*.xml" /> </bean> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.example.dao" /> </bean>有了MapperScannerConfigurer,你的DAO层接口就不需要写实现类,MyBatis自动创建代理对象注入到Service里去。这是SSM项目中节省大量模板代码的关键设计。
2.3 SSM常用注解——答辩被问烂的八张牌
说到SSM常用注解,这个是面试和答辩环节最容易被问到的知识点,网上搜“ssm常用注解”相关热度一直很高,说明大家都想知道哪些注解最核心。我在项目里实际用到频率最高的,给你总结成下面这几个:
@Controller:声明这是一个SpringMVC控制器类,负责接收HTTP请求。在Controller类上配合@RequestMapping("/api")可以设置类级别的URL前缀,避免每个方法都写全路径。
@RestController与@ResponseBody:@RestController是@Controller加@ResponseBody的合成注解,表示这个类所有方法的返回值都直接写入HTTP响应体,而不是走视图解析器。如果你的Controller每个方法都返回JSON,直接用@RestController最省事。
@RequestMapping:映射HTTP请求路径,可以定义请求方法类型(GET/POST/PUT/DELETE)。@GetMapping、@PostMapping等是它的变体,语义更清晰,推荐使用。
@RequestBody+@ResponseBody:前端传JSON过来时,用@RequestBody把请求体绑定到Java对象;返回数据时,@ResponseBody把Java对象转为JSON。这两个注解是前后端分离项目的数据通道。
@Service:声明业务层类,交给Spring容器管理。事务控制建议也放在Service层,用@Transactional(rollbackFor = Exception.class)标注,保证多表操作要么全成功要么全回滚。
@Autowired:依赖注入,常用在Service层注入DAO接口、在Controller层注入Service接口。毕设评审老师如果看到你还在用XML配置的方式组装bean,印象分会打折扣,但用注解就能说明你吃透了Spring的设计思想。
@Repository:标记DAO接口,但在实际项目中如果配了MapperScannerConfigurer,这个注解可视情况省略,我习惯还是写上,语义完整。
@Param:Mapper接口中的方法如果接受多个参数,必须用@Param("xxx")标注参数名,否则MyBatis不知道#{xxx}该取哪个值。比如:
List<RoomInfo> selectByCondition(@Param("buildingId") Integer buildingId, @Param("status") Integer status);每个注解都要能在答辩现场说出它的作用和底层原理,比如@Autowired默认按类型注入,找到多个候选时再按属性名,实在不行还要搭配@Qualifier指定名称。这些细节就是你答辩时回答问题不虚的底气。
2.4 MyBatis动态SQL——多条件查询的利器
教工公寓管理系统的房源列表、缴费记录、维修工单全部都有“多条件组合查询”的需求。比如房源管理页面可能需要根据楼栋号、楼层、房间朝向、状态等多个条件筛选房源,查询条件可能是全部为空,也可能只填其中一个,这时候MyBatis的动态SQL就派上用场了。
一个典型的房源查询Mapper XML映射如下:
<select id="selectByCondition" resultType="com.example.entity.RoomInfo"> SELECT r.*, b.building_no FROM room_info r LEFT JOIN building_info b ON r.building_id = b.id <where> <if test="buildingId != null and buildingId != ''"> AND r.building_id = #{buildingId} </if> <if test="houseType != null and houseType != ''"> AND r.house_type = #{houseType} </if> <if test="status != null"> AND r.status = #{status} </if> </where> ORDER BY r.id DESC </select>注意<where>标签的用法,它能让开头的AND自动被处理掉,避免了SQL拼接时的各种边界问题。如果你只写WHERE 1=1加AND拼接,也不是不行,但答辩时老师看到<where>标签会认为你懂MyBatis的精髓,这是本质区别。
2.5 文件上传与静态资源访问
教工公寓管理系统通常需要涉及教职工头像上传、维修验收照片上传等场景,SpringMVC的文件上传需要配一个MultipartResolver:
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="maxUploadSize" value="10485760" /> <property name="defaultEncoding" value="UTF-8" /> </bean>Controller接收文件保存到本地上传目录,同时返回访问URL给前端。注意上传目录不要放在项目部署的webapps下面,我个人的做法是保存到服务器的一个独立目录(如/data/upload/),然后在SpringMVC配置一个虚拟路径映射,这样项目重新部署时上传文件不会丢失。在spring-mvc.xml里配置:
<mvc:resources mapping="/upload/**" location="file:/data/upload/" />前端拿到的图片URL类似/upload/avatar/20260601.jpg,可以直接放在<img>标签的src里访问。
3. Vue前端项目的搭建与核心页面实现
3.1 Vue安装与环境配置——从零到项目跑起来
说到Vue的环境配置和安装,网上的教程很多,但真正照着做能一次成功的没几个,主要坑都藏在细节里。
第一步,安装Node.js。去官网下载LTS版本,按提示一路下一步就行。安装完成后打开命令行(Windows建议用PowerShell或Git Bash),输入node -v和npm -v,能输出版本号就是成功了。
第二步,更换npm镜像源。这一步不做的话,后面大概率会在装依赖时被漫长的等待折磨到怀疑人生。执行下面两条命令:
npm config get registry npm config set registry https://registry.npmmirror.com第二条命令执行后,再用第一条命令检查,发现输出的是国内镜像地址,就说明配置生效了。
第三步,创建Vue项目。目前官方推荐用create-vue:
npm create vue@latest运行后,它会问你项目名称、是否要TypeScript、路由、Pinia等选项。做毕设的话,我建议选择Router和Pinia,其他选项(如TSX、E2E测试)能选No就选No,减少不必要的学习成本。如果提示包不存在,先运行npm i -g @vue/cli装上脚手架再试。
第四步,启动开发服务器:
cd 你的项目名 npm install npm run dev看到终端输出Local: http://localhost:5173/,浏览器打开这个地址就是你的Vue应用跑起来了。
3.2 Vue路由——前端页面跳转的核心机制
很多同学在这个地方会犯迷糊,Vue路由这个内容是热门搜索词,说明大家确实觉得它有点绕。核心逻辑其实很简单:路由就是“URL路径”与“页面组件”之间的映射关系,用户访问哪个路径,前端就把对应的组件渲染出来。
我在公寓管理系统里,典型的路由配置是这样的:
const routes = [ { path: '/login', name: 'Login', component: () => import('../views/LoginView.vue') }, { path: '/dashboard', component: () => import('../layouts/MainLayout.vue'), children: [ { path: '', name: 'Dashboard', component: () => import('../views/DashboardView.vue') }, { path: 'room/list', name: 'RoomList', component: () => import('../views/room/RoomListView.vue') }, { path: 'checkin/list', name: 'CheckInList', component: () => import('../views/checkin/CheckInListView.vue') }, { path: 'payment/list', name: 'PaymentList', component: () => import('../views/payment/PaymentListView.vue') }, { path: 'repair/list', name: 'RepairList', component: () => import('../views/repair/RepairListView.vue') } ] } ]这里的() => import(...)是懒加载写法,意思是只有访问对应路径时才加载该组件,把业务代码切成小块,首屏加载速度会快很多。
关于动态路由,适用于“不同角色加载不同菜单”的场景。实现思路是前端登录后,根据用户角色动态生成可访问的路由表,通过router.addRoute()添加到路由器里。但说实话,毕设项目用Vue Router的导航守卫就足够了:在路由跳转前检查本地是否存储了token,没有就强制跳转到登录页。
router.beforeEach((to, from, next) => { const token = localStorage.getItem('token') if (to.path !== '/login' && !token) { next('/login') } else { next() } })这个写法在我带过的项目里出现频率极高,简单可靠,好在答辩时也能讲清楚——访问受保护页面时先验身份,没有登录凭证就不能放行,相当于给系统加了一道门禁。
3.3 Element UI布局——后台管理界面的骨架
Vue项目里做后台管理系统,90%的人会用Element Plus组件库。布局就是典型的“顶部导航 + 侧边栏 + 主内容区”三段式。当初网络热搜词里有一条问“利用element-ui布局组件布局时,如何将侧栏作为一个单独的vue文件引入到html中”,说明确实有不少人在布局这块卡壳。
我的做法是定义一个MainLayout.vue作为整体布局组件,把侧边栏、顶栏、主内容区组合在一起:
<template> <el-container> <el-aside width="220px"> <SideMenu :menu-list="menuList" /> </el-aside> <el-container> <el-header> <HeaderBar :user-info="userInfo" /> </el-header> <el-main> <router-view /> </el-main> </el-container> </el-container> </template>其中SideMenu和HeaderBar都单独拆成独立的Vue组件文件,放到components/目录。主内容区放一个<router-view />,它是Vue Router提供的占位组件,当前路由匹配到的页面组件就会渲染在这里。
这里要特别注意侧边栏菜单的数据化。我对菜单的处理是定义成一个数组,每个菜单项包括标题、图标、路由路径、和当前登录用户角色是否匹配,然后根据用户角色过滤后展示。这样做有两个好处:一是不同角色登录后看到的菜单天然不同,实现了按钮级别的权限控制雏形;二是代码没有写死,后续加菜单只改动数组,不用改模板。
3.4 前端核心业务页面实现
房源管理页面是整个系统最重要的界面,我的布局设计是:页面顶部放一个筛选表单(楼栋号下拉框、状态下拉框、户型下拉框、“查询”和“重置”按钮),中间是房源列表的el-table,右侧是“新增”“编辑”“删除”的操作列。核心实现是调后端接口获取数据:
const loadData = async () => { const loading = ElLoading.service({ target: '.main-content' }) try { const response = await axios.get('/api/room/list', { params: { ...queryForm } }) tableData.value = response.data.records total.value = response.data.total } finally { loading.close() } }这里我把axios实例封装在了utils/request.js里,统一配置了基础URL和请求拦截器。请求拦截器的作用是从localStorage里取出token,设置到请求头的Authorization字段。如果不做这一步,后端无法知道请求者是谁,权限校验全都会失效。
3.5 打包部署——Vue构建后如何放进后端项目
这是毕设最后的“临门一脚”,很多同学到这一步就开始慌了。SSM项目一般是打war包部署到Tomcat,前端Vue项目构建后是一堆静态文件(html、js、css)。
我推荐的部署方式是把Vue构建后的产物放到SSM项目的webapp目录下。
先执行构建命令:
npm run build构建完成后,项目根目录下会生成一个dist/文件夹。把这个文件夹里的所有内容复制到SSM项目的src/main/webapp目录下,然后重新打war包部署到Tomcat。Tomcat启动后,访问http://localhost:8080/项目名/,就能看到你的Vue前端页面了。
需要注意一个坑:如果前端项目使用了BrowserRouter模式的Vue Router(地址栏是/room/list这种不带#的路径),你在Tomcat里直接刷新页面会返回404,因为Tomcat找不到/room/list这个真实文件。解决办法有两个:要么给Tomcat配置一个RewriteValve把未匹配路径重写到index.html,要么更省事——在Vue Router里使用createWebHashHistory模式,路径会变成/#/room/list这种带#的格式,刷新就不会丢失路由了。毕设项目图省心,我直接用哈希模式,完全没毛病。
4. 后端接口设计与前后端联调
4.1 RESTful API 接口规范约定
接口是前后端之间的“合同”。不管是自己一个人做整个项目,还是有一个搭档,先把接口规范写清楚,后面开发效率会高非常非常多。
我用的统一返回结构是这样设计的:
{ "code": 200, "message": "操作成功", "data": { "total": 56, "records": [{ "id": 1, "roomNo": "1-201" }] } }后端封装了一个Result类,Controller所有方法都返回这个类型。这样前端在axios响应拦截器里统一判断code字段,等于给前后端的数据沟通建立了一个“协议”,无论是成功还是各种失败原因,都能通过message字段透出,前端只需要关心code是不是200,剩下的事情交给统一的弹窗提示去处理。
4.2 分页接口设计——必考知识点
所有列表页面都需要分页。MyBatis实现分页最稳定高效的方式是使用PageHelper插件,在pom.xml引入依赖后,只需要在Service实现类的查询方法前写一行:
PageHelper.startPage(pageNum, pageSize); List<RoomInfo> list = roomInfoDao.selectByCondition(query); PageInfo<RoomInfo> pageInfo = new PageInfo<>(list);然后把这个PageInfo对象封装到Result里返回前端。前端拿到total、pageNum、pageSize等字段后,再配合Element Plus的el-pagination组件渲染分页按钮。整个过程在答辩时非常好讲:查询前开启分页插件,插件自动拦截下一条SQL语句,执行COUNT(*)查询和LIMIT语句,把总记录数算出来,再把查询结果封装成分页对象。
4.3 跨域问题解决——最常见的拦路虎
前后端分离开发时,前端运行在http://localhost:5173,后端在http://localhost:8080,端口不同,浏览器默认会拦截跨域请求。你在Vue里调接口如果看到浏览器控制台报CORS policy相关错误,就是这个原因。
跨域不是安全问题,而是浏览器的一种同源限制策略。解决办法有很多,最省事的是在后端SpringMVC配置里加一个跨域过滤器:
@Component public class CorsFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletResponse res = (HttpServletResponse) response; res.setHeader("Access-Control-Allow-Origin", "http://localhost:5173"); res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization"); res.setHeader("Access-Control-Allow-Credentials", "true"); if ("OPTIONS".equalsIgnoreCase(((HttpServletRequest) request).getMethod())) { res.setStatus(HttpServletResponse.SC_OK); return; } chain.doFilter(request, response); } }注意有些预检请求(OPTIONS)不会携带实际数据,要提前放行,否则前端会一直收到跨域报错、但后端日志里又看不到日志,查半天也定位不到问题。加上Access-Control-Allow-Credentials: true是为了允许跨域请求携带Cookie,如果登录状态是存在Cookie里的就必须要它。
4.4 登录鉴权与会话管理
教工公寓管理系统的登录逻辑建议这样做:前端把用户名密码通过POST提交到/api/user/login,后端判断账号是否存在、密码是否匹配(建议密码用MD5加盐后存储,直接存明文在毕设里也太丢人了),验证通过后把用户信息存到Session里,同时返回一个简单的token字符串让前端放在请求头中携带。后续每一个受保护接口,都通过拦截器检查请求头里这个token是否有效。
用SpringMVC拦截器实现起来很简洁,在spring-mvc.xml里配置:
<mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/api/**" /> <mvc:exclude-mapping path="/api/user/login" /> <bean class="com.example.interceptor.LoginInterceptor" /> </mvc:interceptor> </mvc:interceptors>拦截器里取请求头的token,无效就统一返回401状态码并附带消息“登录已过期,请重新登录”。前端axios响应拦截器收到401后,自动跳转回登录页。
4.5 前端播放m3u8与PDF展示的扩展场景
热搜词里有很多关于“Vue播放m3u8”“Vue播放RTMP”的问题。虽然公寓管理系统的核心不需要做视频播放,但如果你打算在“公告通知”模块中发布会议视频链接,或者系统里要展示公寓户型图PDF、制度文件PDF,这些是经常会碰到的需求。开发时如果遇到,我就顺手整理了方案。
在Vue里展示PDF,最实用的方式不是用<img>(它只能显示PDF第一页,而且不同浏览器表现还不一致),而是用vue-pdf组件(Vue2时代比较常用)或者pdfjs-dist。Vue3的话推荐@vue-pdf-viewer/viewer,拉取组件后渲染:
<template> <vue-pdf :file="pdfUrl" /> </template>至于m3u8是视频流切片格式,常规<video>标签播放不了。如果你确实需要在Vue里播m3u8,建议直接用hls.js:
import Hls from 'hls.js' const video = document.getElementById('video') if (Hls.isSupported()) { const hls = new Hls() hls.loadSource('https://example.com/live/index.m3u8') hls.attachMedia(video) }但如果只是毕设里的一个附加功能,别把精力过多投入在此。答辩时核心竞争力仍然在主体业务功能,这些播放功能做得再花哨,也就是一个小亮点而已。
5. 论文结构设计与答辩准备
5.1 论文目录参考
“论文+程序”这个项目标题决定了你论文和程序都不能少,而且论文是占评分比重很大的部分。很多同学的毕业论文,最大的问题不是写不出来,而是结构太散,看起来像在写“技术心得”。一份标准的毕设论文目录,整理成下面的框架,几乎是通用格式:
- 摘要与Abstract
- 第一章 绪论:项目背景、国内外研究现状、研究内容与意义
- 第二章 需求分析:可行性分析、功能需求分析、非功能需求分析、用例图
- 第三章 系统设计:总体架构设计、功能模块设计、数据库设计(E-R图、表结构表)
- 第四章 系统实现:各模块实现界面截图加核心代码解析
- 第五章 系统测试:测试环境、测试用例表、测试结果与分析
- 第六章 总结与展望:工作内容总结、不足与改进方向
- 参考文献
- 致谢
每个章节的工作量相对均衡,进度好控制。这里特别提醒一下,需求分析章节不要只“假装”分析业务,可以把“教工公寓管理”不同于“学生宿舍管理”的地方写出来:如教职工入住申请的审核流程更严格、公寓租金补贴政策差异、家属入住登记、资源调配机制等。这些细节会让论文来源于实战,而非照搬模板。
5.2 测试用例怎么设计才专业
很多同学到测试章节就写“点击登录按钮,能登录成功”,这种测试用例基本等于没写。好的测试用例要能反映出测试逻辑的严谨性,比如登录模块的设计:
| 用例编号 | 测试项 | 输入数据 | 预期结果 | 实际结果 |
|---|---|---|---|---|
| TC-001 | 登录成功 | 正确用户名+正确密码 | 登录成功,跳转到dashboard页 | 通过 |
| TC-002 | 密码错误 | 正确用户名+错误密码 | 提示“用户名或密码错误” | 通过 |
| TC-003 | 用户名不存在 | 不存在的用户名+任意密码 | 提示“用户名或密码错误” | 通过 |
| TC-004 | 空字段校验 | 用户名为空 | 提示“请输入用户名” | 通过 |
| TC-005 | 重复提交 | 快速连续点击登录按钮 | 不产生重复请求 | 通过 |
除了功能测试,再加上并发登录测试、接口响应时间测试、数据一致性测试这几个维度,测试章节立刻显得充实了。如果你是拿JMeter做了并发测试,把截图放进论文,答辩时亮出来就是加分项。
5.3 答辩现场的常见追问与应对思路
答辩老师在你讲完PPT后通常会问几个固定套路的问题,提前准备好应对方案会从容很多:
第一问:你在这个项目中主要承担了哪些工作?——即使你一个人包办了所有工作,也不要傻乎乎地回答“什么都做了”,而要说“我主要负责后端SSM框架的搭建与核心业务的实现,包括数据库设计、房源管理、入住管理模块的接口开发,同时参与前端Vue页面的部分功能开发”。
第二问:你的系统安全性是如何保障的?——可以从三个层面回答:密码加密存储、拦截器统一登录校验、角色权限控制。哪怕具体实现没有做到位,回答得条理清晰依然能获得认可。
第三问:数据库的索引、事务你是怎么处理的?——答:入住登记涉及房间状态变更和住户信息新增,两者必须保证原子性,因此在Service层加了@Transactional注解;频繁查询的字段(如room_no、status)建立了普通索引,加快检索速度。
第四问:如果后续增加“线上支付租金”功能,你的设计思路是什么?——这个问题是开放题,核心是想考察你有没有扩展性思维,可以答:在payment_record表增加支付流水号和支付平台回调字段,用定时任务检查未支付账单,前端接入微信/支付宝支付SDK生成二维码。把支付标识、回调、对账逻辑梳理清楚,答案就是合格的。
6. 常见问题排查与踩坑记录
6.1 SpringMVC返回中文乱码
这个可以说是SSM项目的高频问题了。表现是前端拿到的JSON数据里的中文变成了一串问号。原因基本只有两个:一是spring-mvc.xml里没配置UTF-8编码过滤器,请求和响应的编码默认不是UTF-8;二是MySQL连接URL没加characterEncoding=utf-8。
解决方式是在web.xml里加上Spring自带的编码过滤器:
<filter> <filter-name>encodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> </filter>同时MySQL连接串统一写成这样:
jdbc:mysql://localhost:3306/apartment?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai6.2 MyBatis中查询出的字段为null
如果数据库里的字段是room_no(下划线风格),而实体类的属性是roomNo(驼峰风格),MyBatis默认是无法自动映射的。最简单的解决方式是在mybatis-config.xml里开启驼峰映射:
<settings> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings>这是一个不起眼但影响极大的配置项。不打开它,你会发现查询结果里大量字段都是null,排查起来费时间费精力。
6.3 页面样式丢失或刷新404
正如前面讲的,部署时静态资源问题天天见。如果你把Vue打包好的dist文件放进webapp后,发现页面样式正常但一刷新就404,八成就是前端路由使用了createWebHistory模式。我当时也傻傻踩过几次,后来老老实实换成了createWebHashHistory再也没出过问题。如果想保留干净美观的URL,也可以配置Tomcat的RewriteValve,但需要用额外的jar包,改动工程配置文件,不推荐在毕设里折腾。
6.4 端口被占用导致启动失败
开发过程中经常遇到Tomcat或Vue启动时报端口占用。Tomcat端口被占用时,用命令找到占用进程并结束它:
netstat -ano | findstr 8080 taskkill /pid 进程号 /F这是Windows下的做法。Vue开发服务器的5173端口被占用了,可以直接在vite.config.js里改端口配置:
server: { port: 5174 }6.5 Maven依赖下载慢或者项目报红
Maven下载依赖卡的痛,每个Java开发都知道。解决办法还是换源,找到你的settings.xml,把mirror配置改成阿里云镜像:
<mirror> <id>aliyunmaven</id> <mirrorOf>central</mirrorOf> <url>https://maven.aliyun.com/repository/public</url> </mirror>改完后记得重启IDEA,在Maven面板里点“刷新”重新拉依赖。
6.6 axios请求持续pending
这个坑多半出在拦截器或者请求配置上。比如你写了登录拦截器,但请求路径根本没有匹配到后端Controller,或者拦截器在OPTIONS预检请求时没有放行,所有正常请求都会卡在pending状态。排查思路:先看Network面板里请求是否真的发出去了,再逐渐排除是前端卡住还是后端耗时,最后再看是不是被拦截器拦截了但没返回响应。定位到这里,问题也就解决了一大半。
7. 各角色权限管理实现思路
7.1 三角色权限模型设计
教工公寓管理系统的角色,我建议至少分三类:系统管理员、教职工(公寓住户)、维修工作人员。不同角色的功能完全不一样,我用一个权限矩阵说明:
| 功能模块 | 管理员 | 教职工(住户) | 维修工 |
|---|---|---|---|
| 用户信息管理 | 全权限 | 查看本人信息、修改本人联系方式和密码 | 无 |
| 房源管理 | 全权限 | 查看空闲房源 | 无 |
| 入住/退宿申请 | 审核、分配、办理 | 提交申请、查询申请进度 | 无 |
| 租金缴费记录 | 全权限、生成账单 | 查看本人账单、登记缴费 | 无 |
| 报修工单 | 全权限 | 提交报修、查看进度 | 接单、填写处理结果 |
| 公告管理 | 发布、编辑、删除 | 查看、阅读 | 查看 |
后端在做HandlerInterceptor时,可以用一个Map维护每个接口的允许角色列表。这里有个技巧:把权限校验和登录校验放在同一个拦截器里,一次断言两层逻辑,代码量更小。前端菜单再根据角色过滤,实现了“后端兜底、前端优化体验”的双重策略,在答辩中性价比很高。
7.2 前端按钮级权限控制
热搜词里有一条是“vue按钮权限怎么控制”,这里就多聊一句。前端控制按钮权限常见的就是自定义指令的方式。在main.js中注册:
app.directive('permission', { mounted(el, binding) { const requiredPermission = binding.value const userPermissions = store.state.user.permissions if (!userPermissions.includes(requiredPermission)) { el.parentNode.removeChild(el) } } })然后在按钮上这样使用:
<el-button v-permission="'room:delete'" type="danger">删除</el-button>注意,前端按钮权限只是体验优化,真正的安全保障必须在后端接口层做权限校验。这一点在论文里可以写一句“前端权限控制主要提升用户操作体验,后端接口依然进行严格的角色验证”,既展示了你的专业度,也避免了被质疑“前端权限不够安全”。
8. 我把这套项目从头到尾做一遍的经验总结
从我自己做这个项目以及后来带着学生做的心得来看,毕设最大的障碍往往不是技术难度,而是把一个大任务拆解成每天可执行的小计划。SSM+Vue教工公寓管理系统如果每天投入四小时,满打满算三周左右可以完成一个相当完整、能拿优秀评级的成果。
我建议的时间分配是这样的:第一周集中做数据库设计、后端SSM框架搭建、实体与Mapper层开发;第二周把Service层、Controller层和核心接口跑通,同时开始Vue项目搭建和前端基础布局;第三周做前端业务页面、前后端联调、美化界面,然后集中写论文和做测试。最后留三到五天统一检查Bug和准备答辩PPT。
有几个地方我想单独再叮嘱一遍。一是数据库的表结构,不要为了省事把公寓楼、房间、住户信息全塞进一张表,八张表看起来多,实际上每一张都有存在的必要性,后期排查问题和写论文都会轻松很多。二是前端项目一定有版本管理,哪怕自己一个人做,也建议把代码托管到Git平台,因为不知道什么时候你写坏了一个功能,Git能让你从容地回到上一个可用版本。三是别拖延,最后一周再通宵是赶不出高质量项目加论文的,这一点说多了都是泪。
如果照着这篇文章走下来,你应该已经拥有一套能跑、能演示、能答辩的完整项目了。接下来最重要的就是把论文里的系统实现截图替换成自己项目的真实截图,把自己写的核心代码能讲清楚原理——这文章里的每一句话,建议都在自己脑子里重新过一遍。你答得从容,老师自然能给高分。