☰
SSM+微信小程序实验室预约系统毕业设计源码实战与避坑指南
2026/10/8 16:22:58 网站建设 项目流程

简介:基于SSM框架和微信小程序开发的实验室管理毕业设计项目,后台页面采用Vue,数据库使用MySQL,JDK1.8运行环境,可在Eclipse、MyEclipse、STS、IDEA等常见开发工具中直接编译导入,适合计算机相关专业学生用于毕业设计选题、需求分析与功能扩展。项目完整实现管理员与用户双角色体系:管理员具备个人中心、用户信息管理、教学实践管理、学生签到管理、设备信息管理、设备预约管理、课程表管理、预约课程管理、预定实验室管理、实验室信息管理与系统管理;用户端可查看设备信息、课程表信息,完成签到和实验室预约。资源包共1219个文件,压缩后约62.68MB,文件构成以png图片、js脚本、svg图标、vue页面、java源码、json配置、wxss与wxml小程序编译文件为主,同时包含sql数据库脚本、doc论文与开题报告文档、mp4讲解视频、bat环境启动脚本及多个框架配置文件。除完整源码与数据库脚本外,还提供环境工具包、同框架项目安装教程,内容预览中可见后台管理页面组件、Vue页面备份和构建运行脚本,便于本地部署、局部修改与知识梳理。目前已有107人学习下载,适合需要一套可运行、可讲解、可扩展的毕设项目的初中级开发者。

1. 毕业设计选“实验室管理小程序+SSM”,到底在交付什么

临近开题,很多人拿到“毕业设计java实验室管理微信小程序+ssm源码含文档含教程”这类工程时,第一反应是解压、导入、跑起来截图交差。真这么做,大概率卡在环境上。这个题目交付的不只是源码,而是三样东西:一个SSM架构的Java后端、一个原生微信小程序前端、一份能支撑论文的数据库设计和接口说明。它解决的场景很典型——实验室信息展示、在线预约、管理员审核、用户管理,正好覆盖CRUD、状态流转、前后端联调这些毕业设计必考的点。适合Java方向、想快速拿出可运行项目并能在答辩时把原理讲清楚的本科生。反直觉的是,源码本身不难,难的是把JDK版本、Tomcat、小程序域名、真机预览这些工程问题理顺。

2. SSM和小程序的分工:先看三层架构,再决定先改哪端

2.1 为什么是SSM而不是Spring Boot:答辩时怎么解释

SSM是Spring、SpringMVC、MyBatis三件套的缩写,这个组合在2018年前后的Java Web课程里几乎是标准配置,毕业设计选它不是因为技术新,而是因为它每一层都“看得见”。Spring管的是对象创建和事务,SpringMVC管的是请求路由,MyBatis管的是SQL映射。三者的边界非常清楚,答辩时老师问“一个请求进来之后经历了什么”,你可以从Tomcat到DispatcherServlet到Controller到Service到Mapper到MySQL一条线讲下来。

框架核心职责对应工程里的位置
SpringIoC容器、事务管理applicationContext.xml、Service实现类
SpringMVCURL路由、参数绑定spring-mvc.xml、Controller
MyBatisSQL映射、结果集封装mybatis-config.xml、Mapper接口与XML

有人问为什么不用Spring Boot,我的习惯回答是:Spring Boot的自动配置确实省事,但它把Bean装配、DispatcherServlet初始化这些关键步骤都包成了黑匣子。答辩老师追问“starter是怎么生效的”,你得去讲条件注解和自动配置类,反而把简单问题复杂化。SSM的配置是显式写在XML里的,一行行能指给老师看。翻过springframework源码和mybatis源码的人,在讲MapperProxy和SqlSessionTemplate时明显比只背概念的人讲得实在。

这个选择还有一个现实原因:这套源码里的教程和文档大多按SSM分层结构写。你换成Spring Boot,文档对不上,代码结构也得推倒重来,毕业设计的时间耗不起。JDK版本也要留意,老SSM工程一般配JDK 8和Tomcat 8.5最稳,JDK 11以上偶尔会遇到cglib代理或者JAXB相关的兼容问题。

2.2 小程序端和后端的分工边界:谁管页面,谁管数据

很多人在改源码时犯的第一个错,是把业务逻辑写在小程序端。比如预约时间冲突判断,直接用JavaScript在前端比,后端接口只做插入。这样演示时没问题,但答辩老师一问“两个用户同时提交同一个实验室怎么办”,前端判断就站不住脚了。

小程序端该管的只有三件事:页面渲染、表单校验、通过wx.request发请求。后端负责的是接口鉴权、业务规则、数据持久化。一个完整请求的链路是:页面onLoad发起wx.request,后端Controller接收参数,Service做业务判断,Mapper操作MySQL,结果封装成JSON返回,小程序端拿到后setData刷新页面。这条链路必须能用自己的话讲明白,这是答辩的基本盘。

工程目录一般长这样,拿到源码先对照着看,不要急着改代码:

lab-wx/ ├── backend/ # SSM后端工程 │ ├── pom.xml │ └── src/main/ │ ├── java/ │ │ └── com/example/lab/ │ │ ├── controller/ # 接口层,接收小程序请求 │ │ ├── service/ # 业务层,预约规则、审核逻辑 │ │ ├── mapper/ # MyBatis Mapper接口 │ │ ├── entity/ # 数据表对应实体类 │ │ └── common/ # Result封装、工具类 │ └── resources/ │ ├── applicationContext.xml │ ├── spring-mvc.xml │ ├── mybatis-config.xml │ ├── jdbc.properties │ └── mapper/*.xml # SQL语句 ├── frontend/ # 微信小程序原生工程 │ ├── app.js │ ├── app.json │ ├── pages/ │ │ ├── login/ │ │ ├── index/ # 实验室列表 │ │ ├── reserve/ # 预约提交 │ │ └── admin/ # 管理员审核 │ └── utils/request.js # wx.request封装 └── doc/ # 论文、数据库脚本、操作教程

前端和后端之间只通过HTTP接口通信,所以接口返回格式要统一。常见做法是封装一个Result对象,包含code、message、data三个字段,小程序端判断code等于200才继续处理。这样Controller里无论查询成功还是失败,返回结构都是一致的。

2.3 最小可运行工程结构:拿到源码先看这几个文件

不要一上来就点启动按钮,先把四个关键文件找到:pom.xml、jdbc.properties、spring-mvc.xml、app.js。pom.xml决定依赖能不能拉下来,jdbc.properties决定数据库能不能连上,spring-mvc.xml决定接口能不能扫到,app.js决定小程序请求地址对不对。

<!-- pom.xml 关键依赖版本,老工程踩坑重灾区 --> <properties> <jdk.version>1.8</jdk.version> <spring.version>5.1.8.RELEASE</spring.version> <mybatis.version>3.4.6</mybatis.version> </properties> <dependencies> <!-- SpringMVC 依赖 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <!-- MyBatis 与 Spring 整合包 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.3.2</version> </dependency> <!-- MySQL 驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> </dependencies>

版本这里最容易翻车。Spring 5.x配合JDK 8没问题,但MyBatis 3.4.x和mysql-connector-java 5.1.x这套组合是老工程默认配置。如果你把MySQL驱动换成8.0.x,连接串里的驱动类名要改成com.mysql.cj.jdbc.Driver,jdbc.properties也得加serverTimezone参数,不然直接报“The server time zone value”错误。

看app.js时,重点看globalData里的baseUrl。开发工具里跑可以写localhost,真机预览必须改成电脑的局域网IP,否则手机请求打到手机自己身上。这个细节我会在第五章细说,但拿到源码第一件事就把它记住,能省半天排错时间。

3. 把源码跑起来:后端启动、小程序预览、数据库初始化三步

3.1 后端导入IDEA并启动SSM:Maven清理与Tomcat配置

SSM后端不是直接双击就能跑的主类,它需要部署到Tomcat里。用IDEA打开backend目录后,先确认两件事:IDEA里配置的JDK是1.8,Maven用的是本地仓库还是阿里云镜像。老工程依赖如果拉不下来,多半是Maven中央仓库访问太慢,在settings.xml里加aliyun镜像能解决。这一步做完再动代码。

# 在backend目录下执行,先规避本地仓库里的脏jar包 mvn clean install -DskipTests -Dfile.encoding=UTF-8

这条命令的作用是清理target目录并重新打包,skipTests跳过测试以减少时间,file.encoding固定编码防止Windows中文乱码。如果你的机器上还没配过java环境变量,打开cmd执行java -version确认一下,没有就先去装JDK 8并配置JAVA_HOME,这是最常被忽略的前置步骤。

打包成功后,在IDEA里配置Tomcat Server:选择Tomcat 8.5,Deployment里添加Artifact,选war exploded方式,Application context填/lab。这里有个细节:war和war exploded差别在于解压方式,毕业设计调试选war exploded,因为它支持热部署,改完Java代码不用重启整个Tomcat。启动后看到“Server startup in [xxx] milliseconds”就算成功了。

最容易在这里踩的坑有两个:一是端口被占用,Tomcat默认8080,你机器上可能跑着别的服务,在server.xml里改成8081或者8082就行;二是启动时提示“Unable to compile class for JSP”,这是JSP编译器依赖缺失,检查Tomcat的lib目录里有没有jasper.jar。

3.2 小程序端用微信开发者工具打开:AppID、域名与真机预览

后端启动之后,用微信开发者工具导入frontend目录。导入时有两个选择:用测试号或者用你自己的小程序AppID。测试号不需要注册,能覆盖登录、请求、本地存储这些常用能力,但拿不到手机号授权,因为获取手机号要求企业主体且已完成微信认证。源码里如果带了手机号登录功能,你就得注册一个小程序账号,个人主体无法开通这个接口,这是认证费用和主体类型决定的,不是代码能绕过的。

导入后第一件事,打开app.js,把baseUrl改成后端的实际地址:

// app.js 小程序入口 App({ globalData: { // 开发工具里可以用localhost,真机预览必须用电脑局域网IP baseUrl: 'http://192.168.1.100:8080/lab' }, onLaunch() { // 启动时检查本地是否有登录缓存的token const token = wx.getStorageSync('token') if (!token) { // 没有token就跳转登录页,避免直接进首页时报401 wx.reLaunch({ url: '/pages/login/login' }) } } })

登录逻辑缓存在本地是常见做法,但不能只依赖是否存在token来判断登录态,因为token可能过期。后端在返回Result时,如果code是401,小程序端要重新执行登录流程。这个我在第四章会展开。

接下来是开发工具里的关键配置:在“详情-本地设置”里勾选“不校验合法域名、web-view(业务域名)、TLS版本以及HTTPS证书”。这个选项只对开发工具生效,真机预览时如果不做任何配置,网络请求还是会因为域名不是HTTPS而失败。所以你需要把baseUrl从localhost改成电脑在局域网里的IP,手机和电脑连同一个Wi-Fi,才能真机调试。

3.3 数据库初始化与账号体系打通:jdbc连接串才是第一道坎

SSM工程一般都带SQL脚本,在doc目录下找.sql文件。用Navicat或者命令行执行前,先建好数据库。这里有个细节:数据库名、用户名、密码必须和jdbc.properties里的配置一致,否则启动Tomcat时Service初始化报错,但报错信息往往很晚才出现,容易被误解成“项目本身有问题”。

-- 实验室表:毕业设计最核心的数据表 CREATE TABLE `lab` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '实验室主键', `name` varchar(100) NOT NULL COMMENT '实验室名称', `location` varchar(200) DEFAULT NULL COMMENT '位置', `capacity` int(11) DEFAULT '30' COMMENT '容量', `status` tinyint(4) DEFAULT '1' COMMENT '1可用 0停用', `image` varchar(255) DEFAULT NULL COMMENT '展示图', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='实验室信息表';

字符集最好选utf8mb4而不是utf8,因为utf8在MySQL里最多存3字节,用户昵称里如果带了emoji表情,直接插入失败。engine用InnoDB,支持事务,预约提交时要保证扣减名额和插入记录在同一个事务里。

改jdbc.properties时,连接串一定要写成这样:

jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/lab?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false jdbc.username=root jdbc.password=123456

serverTimezone不写,MySQL 5.6以上版本会在获取连接时直接报时区异常;useSSL=false是为了避免本地开发时SSL握手警告。账号体系这里,源码通常会有一个初始化管理员账号的SQL,比如admin/123456,但密码多半是MD5加密后的字符串。你想在数据库里手动插数据时,别直接写明文密码,去entity里看密码字段的类型和对应用户登录时怎么校验的,照着已有数据的格式生成密文。

4. 核心预约流程代码走读:登录、列表、状态流转

4.1 预约流程的表设计与状态机:一张预约表撑起整个业务

实验室预约是整个题目的业务核心,大部分功能都围绕预约表的增删改查展开。预约表要包含谁约的、约哪个实验室、什么时间段、当前状态。状态用int字段比varchar更合适,排序快、占空间小,而且可以用状态机把整个业务流转讲清楚。

CREATE TABLE `reservation` ( `id` int(11) NOT NULL AUTO_INCREMENT, `lab_id` int(11) NOT NULL COMMENT '实验室ID', `user_id` int(11) NOT NULL COMMENT '预约用户ID', `reserve_date` date NOT NULL COMMENT '预约日期', `start_time` time NOT NULL COMMENT '开始时间', `end_time` time NOT NULL COMMENT '结束时间', `status` tinyint(4) NOT NULL DEFAULT '0' COMMENT '0待审核 1已通过 2已拒绝 3已取消', `reason` varchar(255) DEFAULT NULL COMMENT '使用用途/拒绝原因', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='预约记录表';

status字段建议统一用数字字典,前端用常量映射:0待审核、1已通过、2已拒绝、3已取消。为什么这么设计?因为答辩老师一旦问“预约冲突怎么控制”,你可以回答:在Service层做两件事,先查该实验室同一时间段是否有status=1的预约,有就拒绝;再把“查空档”和“插入预约”放到同一个事务里,配合预约表加唯一索引兜底。空档查询SQL通常是这样的:

-- 查询某实验室在指定时间段是否已被预约 SELECT COUNT(*) FROM reservation WHERE lab_id = #{labId} AND reserve_date = #{date} AND status = 1 AND start_time < #{endTime} AND end_time > #{startTime}

这段SQL的判断逻辑是时间段重叠判断,不是简单的等值查询。很多新手写成start_time = #{startTime},漏掉了别人预约从9点到11点、你预约10点到12点这种交叉情况。

4.2 微信小程序登录获取手机号:code换openid的完整链路

微信小程序登录获取手机号是这套系统的入口,同时也是最容易卡住的功能。登录链路分两步:第一步,wx.login拿到临时code,后端拿code去微信接口换openid和session_key;第二步,用户点“手机号快捷登录”按钮,前端拿到加密数据,后端解密获取手机号。很多源码把两步混在一起,导致个人主体小程序根本跑不通。

// pages/login/login.js handleLogin() { wx.login({ success: (res) => { const code = res.code wx.request({ url: `${getApp().globalData.baseUrl}/user/login`, method: 'POST', data: { code }, success: (resp) => { if (resp.data.code === 200) { wx.setStorageSync('token', resp.data.data.token) wx.reLaunch({ url: '/pages/index/index' }) } } }) } }) }

code这个参数只能用一次,有效期五分钟。后端拿到code后,调用https://api.weixin.qq.com/sns/jscode2session,带上appid和secret,返回openid和session_key。后端要做的不是把openid直接扔给前端,而是用它查用户表,新用户自动注册,老用户直接登录,然后生成一个自己的token返回给小程序。token可以简单用UUID,也可以把userId和过期时间拼起来做签名,毕业设计用UUID足够。

手机号按钮需要单独处理,一定要用button组件配合open-type="getPhoneNumber",不能用普通input让用户输手机号,微信审核会拒绝。拿到手机号后,按源码里的方式解密,如果源码用的是老接口getPhoneNumber的encryptedData方式,需要把session_key一起传过去。这一步在个人主体小程序里测试不了,因为接口权限没开放,所以演示时建议做一个“账号密码登录”兜底,避免答辩现场手机号登录失败。

4.3 实验室列表与预约提交:Controller到Mapper的调用链

实验室列表是首页默认页,接口设计要考虑分页和状态过滤,不然实验室一多,一次性返回全部数据会让小程序端渲染卡顿。Controller层要写得薄,只做参数接收和结果封装,业务逻辑下沉到Service。

// com/example/lab/controller/LabController.java @Controller @RequestMapping("/lab") public class LabController { @Autowired private LabService labService; // 实验室分页列表 @RequestMapping("/list") @ResponseBody public Result list(@RequestParam(defaultValue = "1") Integer page, @RequestParam(defaultValue = "10") Integer size, @RequestParam(required = false) String keyword) { return labService.pageList(page, size, keyword); } }

@RequestParam(defaultValue = "1")是防止小程序端没传page导致空指针,keyword用required = false允许不传。@ResponseBody把返回的Result对象自动序列化成JSON,注意Result实体类里要有getter/setter,否则Jackson序列化出来是空对象。

预约提交接口要做的校验比列表多:当前用户有没有登录、实验室是否存在、时间段是否合法、是否与已有预约冲突。这些放在Service层串行检查:

// com/example/lab/service/impl/ReserveServiceImpl.java @Transactional(rollbackFor = Exception.class) public Result submitReserve(ReserveDTO dto, Integer userId) { // 1. 参数合法性:结束时间必须晚于开始时间 if (dto.getEndTime().before(dto.getStartTime())) { return Result.error("结束时间不能早于开始时间"); } // 2. 冲突检测:同一实验室同一时间端已有审核通过的预约 int count = reserveMapper.checkConflict(dto); if (count > 0) { return Result.error("该时间段已被预约"); } // 3. 插入新预约,状态为待审核 Reserve reserve = new Reserve(); BeanUtils.copyProperties(dto, reserve); reserve.setUserId(userId); reserve.setStatus(0); reserveMapper.insert(reserve); return Result.success(); }

@Transactional注解放在Service实现类上,意思是checkConflict和insert要么都成功,要么都失败。这里有个坑:如果MySQL表不是InnoDB引擎,事务是不生效的,所以前面建表时强调engine=InnoDB。BeanUtils.copyProperties可以少写几行setter,但要求两个类的字段名完全一致,字段名对不上时它会静默跳过,你会看到插入的数据缺字段,排查时先检查这一步。

5. 避坑:SSM+小程序联调常见的6个问题

5.1 真机预览时request网络请求失败的3个原因

现象:微信开发者工具里接口调得通,数据渲染正常,一用真机预览就报request:fail,页面空白。

原因通常是三个中的一个:baseUrl写了localhost,真机上localhost指向手机自身;小程序真机环境默认要求域名备案且必须是HTTPS;企业主体小程序还校验域名白名单,不在白名单直接拦截。

解决:开发阶段先把baseUrl改成电脑的局域网IP,手机和电脑连同一个路由器,确保防火墙放行8080端口。用“不校验合法域名”选项只能让开发工具放行,真机预览依然受限,所以局域网IP是当下最快的解法。正式上线的话,需要买一台服务器、备案域名、配HTTPS证书,再把域名加到小程序后台的request合法域名列表里。日常调试时可以用Charles抓包看请求到底发到了哪个地址,确认是不是域名配置问题。

运行环境域名要求常见配置
微信开发者工具无勾选不校验合法域名
真机预览(开发)局域网IP可用baseUrl改192.168.x.x
正式上线HTTPS且已备案配置服务器、小程序后台白名单

5.2 顶部导航栏高度适配:自定义导航栏的测量陷阱

现象:小程序页面用了自定义导航栏后,在iPhone 14 Pro上标题明显偏上,甚至被状态栏盖住;在普通安卓机上却正常。

原因:导航栏设置为custom之后,系统不再帮你预留状态栏高度,而不同机型的状态栏高度和胶囊按钮位置都不一样。如果你在app.json里写了"navigationStyle": "custom",就必须自己计算导航栏高度。很多人写死一个44px,在非全面屏上看着没问题,一到刘海屏就露馅。

解决:用微信官方API动态获取胶囊按钮位置,再算出导航栏高度,放到全局变量里供每个页面使用。

// utils/navbar.js const getNavBarInfo = () => { const windowInfo = wx.getWindowInfo() const menuButton = wx.getMenuButtonBoundingClientRect() // 胶囊按钮顶部到状态栏底部的距离,乘以2就是上下留白总量 const navBarHeight = (menuButton.top - windowInfo.statusBarHeight) * 2 + menuButton.height return { statusBarHeight: windowInfo.statusBarHeight, navBarHeight, menuButton } } module.exports = { getNavBarInfo }

拿到navBarHeight后,在自定义导航栏组件的style里设置高度,而不是在页面里写死。有一个常被忽略的点:安卓和iOS的状态栏高度不同,微信开发者工具里模拟的是默认机型,所以必须以真机为准。改完这个工具函数后,把app.js的globalData里存一份,所有页面在onLoad时读取,不要每个页面重新计算。

5.3 微信小程序单选框与表单校验的类型坑

现象:预约表单里有一个radio-group选择“预约用途”,用户选“实验课”后提交,后端一直说缺少用途参数。

原因:radio-group的bindchange事件里,e.detail.value拿到的是字符串,比如"1"。而后端接口用Integer接收,前端又在data里定义了purpose: 0作为初始值。提交时直接传data对象,字符串"1"传到Java层,类型转换在某些SSM配置下会直接失败,或者被转成null。这是很典型的微信小程序单选框类型坑,因为radio的value天然是字符串。

解决:在bindchange回调里手动转换类型,再存到data中。

// 预约表单页 onPurposeChange(e) { // radio的value是字符串,后端要Integer,这里必须转换 this.setData({ purpose: parseInt(e.detail.value, 10) }) } onSubmit() { const { purpose, date, startTime, endTime } = this.data // 提交前做非空校验,避免空表单直接打到后端 if (!purpose || !date || !startTime || !endTime) { wx.showToast({ title: '请完整填写预约信息', icon: 'none' }) return } wx.request({ ... }) }

同理,表单里的日期和时间选择器返回的也是字符串,提交前要么统一parseInt,要么在Controller里用String接收再手动转换。用String接收是最省事的,但答辩时会显得不够严谨。更好的做法是前端把所有id类字段都转成数字,后端统一用Integer,双端都清爽。

5.4 数据库时间字段与Java Date的8小时时差

现象:预约记录提交成功后,数据库里存的时间比页面显示的时间少了8个小时。比如页面上选的10:00,数据库里变成02:00。

原因:MySQL连接串里没有指定serverTimezone,驱动用了默认时区,而Tomcat所在机器用的可能是UTC,导致JDBC在转换时间时偏移了8小时。还有一种情况是JSON序列化时,Jackson把Date序列化成带时区的字符串,小程序端解析时又按UTC解析一次。

解决:统一时区配置,分两层处理。第一层,jdbc.properties连接串加serverTimezone=Asia/Shanghai;第二层,SpringMVC的Jackson配置里设置日期格式和时区:

// spring-mvc.xml 中配置消息转换器 <bean id="jacksonObjectMapper" class="com.fasterxml.jackson.databind.ObjectMapper"> <property name="dateFormat"> <bean class="java.text.SimpleDateFormat"> <constructor-arg value="yyyy-MM-dd HH:mm:ss"/> </bean> </property> <property name="timeZone" value="GMT+8"/> </bean>

这里要说明的是,timeZone=GMT+8是Java层的序列化时区,serverTimezone=Asia/Shanghai是JDBC连接层的时区,两层保持一致。如果改了连接串还差8小时,多半是Tomcat启动参数里设了user.timezone,去catalina.sh或IDEA的VM options里检查,把-Duser.timezone=UTC删掉。

5.5 Tomcat部署路径与文件上传目录不一致

现象:管理员在后台给实验室上传图片,开发环境显示正常。重启Tomcat或者把项目打包部署到服务器后,图片全部404。

原因:源码里的上传代码可能写了绝对路径或者相对路径,比如FileUtil.uploadFile(file, "/upload")。IDEA里用war exploded方式部署时,应用解压在一个临时目录,图片写进了临时目录下的upload,重启后这个目录被清理。部署到服务器后路径更不一样,所以图片丢失。

解决:把上传目录从Web项目目录里拆出来,配置成外部绝对路径,再用SpringMVC的静态资源映射暴露出去。比如在服务器上建/data/lab/upload目录,然后在spring-mvc.xml里加:

<!-- 把外部目录 /data/lab/upload 映射到URL路径 /upload/** --> <mvc:resources mapping="/upload/**" location="file:/data/lab/upload/"/>

这样做的好处是,重启Tomcat不会清空图片,backup时直接备份整个目录就行。开发环境里如果不想建Linux路径,可以在项目根目录建一个本地upload文件夹,然后把location改成file:../upload/,注意相对路径是相对于Tomcat的工作目录,不是项目根目录,这点容易绕晕。

5.6 教程和源码对不上:先按代码反推文档

现象:这是一条大概率遇到的坑。doc目录里的教程文档写的是“打开LoginController”,但源码里根本没有这个文件;文档里截图显示菜单叫“实验室预约”,实际页面叫“预约管理”。

原因:同一份标题的资源包,经过多次转手后,源码可能被改动过,而文档没同步更新。教程是给人写论文时参考逻辑用的,不是给你逐行对照代码用的。

解决:先跑通项目,再以源码为准反推文档。排查时用IDEA的全局搜索快捷键,直接搜Controller或接口路径关键词,比如搜/user/login就能快速定位login相关代码。写论文时如果文档描述和代码不一致,以代码为准,因为答辩老师现场看的是项目演示,不会去读文档里的细节。另一个办法是看数据库表结构,表字段能反推出大部分业务逻辑,文档里没写的功能,从表里都能看出来。

6. 从“能跑”到“能答辩”:验收清单与两个加分进阶

源码跑通只是第一步,答辩现场才是真正的考场。我习惯在答辩前按一条固定路径完整走三遍:用户登录→查看实验室列表→提交预约→管理员登录→审核通过→刷新用户端看到预约状态变成已通过。这条路径要熟练到不用看页面也能操作。同时用Postman把所有接口的边界测一遍,重点测空参数、错误ID、时间段重叠这三种情况,别让老师随手点出一个500报错。

验收时要检查的细节很多,但优先级最高的是这几个:后端日志没有Exception堆栈、数据库表里没有乱码数据、小程序在开发工具和真机两种环境都能正常请求、登录token过期后能自动跳回登录页。图表相关的辅助页面可以放在最后演示,核心流程必须最先演示。

两个加分进阶很值得做,改动量不大但答辩效果明显。第一个是把预约冲突检测从Service里的手动SQL改成数据库唯一索引加乐观锁,这个能体现出你对并发控制的理解。第二个是给管理员后台加一个简单的预约统计,按周展示实验室使用率,小程序端用ec-canvas画一个柱状图,代码量不大,但答辩时“可视化”三个字能直接吸引老师注意力。

我自己的深刻教训是:拿到源码后不要急着改业务逻辑,先原样跑通、把每条接口和表字段的关系理清楚再动手。我第一次接到这类毕设工程时,觉得登录逻辑太简陋,直接加了密码加盐逻辑,结果改崩了整个注册流程,排错用了两天,后来老老实实回滚到源码做增量修改。毕设源码的价值是给你一个稳定的基线,在这之上做小步改动,比推翻重建稳妥得多。希望这篇实操记录能帮你少走这几步弯路,把精力花在能加分的地方,比如把状态机讲清楚、把并发冲突演示出来,这比折腾框架本身更能让答辩老师点头。

本文还有配套的精品资源,点击获取

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

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

立即咨询