简介:民宿短租小程序是一套面向计算机相关专业毕业设计的完整项目资料,基于微信小程序、SSM框架和MySQL实现,覆盖民宿展示、在线预订、订单处理等典型业务场景,主要解决传统民宿中介费高、房源信息分散、预订效率低等问题。压缩包共996个文件,涵盖Java后端、Vue后台管理、微信小程序前端源码,以及SQL数据库脚本、毕业论文文档和mp4视频演示等内容,同时包含png、svg等界面资源与项目配置文件,整体约38.58MB,结构清晰便于按模块检索。已有335人学习/下载。借助项目自带的安装、运行、构建脚本,可快速搭建运行环境;配套论文与演示视频能帮助读者梳理民宿信息发布、在线预订和订单管理等功能实现,也可参考其中的接口设计与目录划分方式,无论是用于毕业设计、课程答辩还是二次开发,都有较高的参考价值,适合正在开展相关课题设计或需要快速上手SSM+小程序开发的毕业生参考。
1. 民宿短租小程序背后的完整工程:不是写 demo,是能交付的毕设
很多同学拿到这套「民宿短租小程序 + SSM + MySQL」毕设工程包,第一反应是解压、导入、点运行,然后卡在第一关。这套东西本质上是一条完整的三层架构链路:微信小程序做前端界面与交互,SSM(Spring + SpringMVC + MyBatis)处理后端接口,MySQL 存用户、房源、订单、评论,项目包里再配上毕业论文和视频演示,构成一个能直接拿去答辩的闭环。它适合两类人:Java 方向想把毕设做成「能演示的系统」的本科生,以及想快速理解小程序和 Java Web 怎么联调的开发者。别急着配环境,先把选型逻辑和数据链路理清楚,后面每一步才不会靠猜。
2. 为什么是微信小程序 + SSM + MySQL:选型理由与一条请求的数据链路
2.1 SSM 做毕设「老」但不「过时」
2025 年提 SSM,很多人第一反应是「怎么还在用 XML 配置」。但毕设场景里,「老」恰恰是优势。SSM 是 Spring、SpringMVC、MyBatis 三个框架的叠加,每一处配置都是显式的:数据源怎么连、Mapper 在哪里、Controller 怎么被扫描到,全部写在配置文件里。SpringBoot 把这些封装成自动配置,项目确实跑得更快,但答辩老师问一句「SpringMVC 的前端控制器是什么」「MyBatis 的一级缓存存在哪」,多数照抄 SpringBoot 代码的同学会卡壳。SSM 的工程结构足够简单,一个 Controller 对应一批接口,一个 Mapper XML 对应一套 SQL,代码量适中,够写进论文做技术描述,又不会大到两周写不完。
对比一下常见选型:
| 方案 | 优点 | 在毕设里的代价 |
|---|---|---|
| SSM + 微信小程序原生 | 三层结构清晰,答辩好讲,XML 配置都是考点 | 配置繁琐,需要手写 JSON 处理和跨域配置 |
| SpringBoot + 小程序 | 启动快,依赖少,开发效率高 | 自动配置是黑匣子,答辩容易问穿 |
| SSM + Vue 管理后台 | 前端体验更好 | 工程体积大,论文篇幅难控制 |
| uniapp 打包小程序 | 一套代码多端复用 | 打包配置和原生差异容易翻车,答辩重点偏向前端 |
如果你手上这套工程包只带小程序端,我一般会建议再补一个简单的后台管理入口(哪怕是 JSP 页面),因为答辩现场「房源上下架」「订单状态修改」这类管理动作是需要演示的,纯小程序端会显得系统不完整。
2.2 一条民宿列表请求的数据链路
理解这套系统最快的方式,是跟一条请求走完整条链路。用户在小程序首页看到民宿列表,实际发生的事是:
小程序端wx.request发起 GET 请求 → HTTP 到达 Tomcat → DispatcherServlet 根据 URL 找到 HouseController 的方法 → Controller 调 HouseService → Service 调 HouseMapper 接口 → MyBatis 执行 Mapper XML 里的 SQL → MySQL 返回结果集 → MyBatis 映射成 House 对象 → Jackson 把对象序列化成 JSON → 响应回小程序 →setData更新页面数据。
那条 JSON 响应长这样:
{ "code": 200, "message": "success", "data": [ { "id": 1, "title": "西湖边北欧风一居室", "coverImg": "http://192.168.1.100:8080/upload/house1.jpg", "price": 328.00, "address": "杭州市西湖区", "status": 1 } ] }注意两点:整个链路里小程序端只认 JSON,后端返回结构里code和message是给前端判断业务用的,data才是真正要渲染的数据;coverImg是图片 URL,开发阶段直接用局域网 IP 访问 Tomcat 下的静态资源,后面联调时这是最常见的坑。这条链路能从头到尾讲清楚,答辩的「系统设计」环节就稳了一半。
2.3 MySQL 在毕设规模下的角色
民宿短租系统的数据量在毕设场景下很小,几十张表反而增加负担,四到五张核心表最合适。MySQL 在这里承担的是标准的关系型数据库职责:用户表存账号与 openid,房源表存民宿信息,订单表存下单记录,评论表存用户评价,四张表通过外键逻辑关联。这个规模刚好覆盖 MySQL 的常用知识点——ORDER BY排序、JOIN联表、GROUP BY分组统计,后端接口里都能用上,论文的「数据库设计」章节也有东西可写。
MySQL 的版本选择也影响后面的配置。毕设环境我推荐 5.7,兼容性最稳,网上教程多,SSM 老工程普遍按 5.7 写的;装 8.0 也行,但驱动类名和时区参数都得跟着改,改了之后 pom 依赖也要对应升级。具体差异在第 5 章的避坑清单里展开。
3. 环境准备与数据库初始化:先把地基打牢
3.1 版本搭配清单与 Windows 安装注意点
拿到工程包先对照版本,版本不匹配是启动失败的第一大原因。我的固定搭配是:
| 组件 | 推荐版本 | 说明 |
|---|---|---|
| JDK | 1.8(8u202 或更高) | SSM 老工程最稳的版本,高版本 JDK 编译的 class 文件 Tomcat 8.5 读不了 |
| Maven | 3.6.x | 3.8 以上默认中央仓库地址变了,老工程 dependency 可能拉不下来 |
| Tomcat | 8.5 或 9.0 | 不要用 Tomcat 10+,javax.servlet 和 jakarta.servlet 的包名不兼容 |
| MySQL | 5.7 或 8.0 | 5.7 最省事,8.0 需要换驱动和时区参数 |
| 微信开发者工具 | 稳定版 | 开发阶段勾选「不校验合法域名」 |
| Navicat / DataGrip | 任一 | 导入 SQL、查数据用 |
Windows 10 上装 MySQL,几个细节容易忽略:安装包右键管理员身份运行;字符集选 utf8mb4;root 密码记牢,密码忘了重置的成本比重装还高;MySQL 5.7 的配置文件在安装目录下的my.ini,改完要重启服务。命令行连接测试用mysql -uroot -p,能进到mysql>提示符就说明服务正常。
3.2 建库建表:四张核心表怎么设计
工程包里通常带一份house.sql,但建议自己动手建一遍,既能熟悉表结构,答辩问起来也能说清每个字段为什么这么设计。下面这份建表语句是民宿短租的常见结构,四张表加几条测试数据:
CREATE DATABASE IF NOT EXISTS house_rental DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_general_ci; USE house_rental; -- 用户表:openid 是微信登录的唯一凭证 CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, openid VARCHAR(64) NOT NULL UNIQUE COMMENT '微信openid', nickname VARCHAR(50) DEFAULT '' COMMENT '昵称', avatar VARCHAR(255) DEFAULT '' COMMENT '头像URL', phone VARCHAR(20) DEFAULT '' COMMENT '手机号', create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB COMMENT '用户表'; -- 民宿表:price 用 DECIMAL 避免浮点误差 CREATE TABLE t_house ( id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(100) NOT NULL COMMENT '民宿标题', cover_img VARCHAR(255) DEFAULT '' COMMENT '封面图', intro TEXT COMMENT '民宿介绍', address VARCHAR(255) DEFAULT '' COMMENT '地址', price DECIMAL(10,2) NOT NULL COMMENT '每晚价格', status TINYINT DEFAULT 1 COMMENT '1上架 0下架', create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB COMMENT '民宿表'; -- 订单表:order_no 唯一,用于演示和答辩时快速找到订单 CREATE TABLE t_orders ( id INT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL UNIQUE COMMENT '订单编号', user_id INT NOT NULL COMMENT '下单用户', house_id INT NOT NULL COMMENT '民宿', check_in DATE NOT NULL COMMENT '入住日期', check_out DATE NOT NULL COMMENT '离店日期', total_price DECIMAL(10,2) NOT NULL COMMENT '总价', status TINYINT DEFAULT 0 COMMENT '0待支付 1已支付 2已完成 3已取消', create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB COMMENT '订单表'; -- 评论表:rate 用 TINYINT 存 1-5 星 CREATE TABLE t_comment ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, house_id INT NOT NULL, content VARCHAR(500) DEFAULT '', rate TINYINT DEFAULT 5 COMMENT '评分1-5', create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB COMMENT '评论表'; INSERT INTO t_house (title, cover_img, intro, address, price) VALUES ('西湖边北欧风一居室', '/upload/house1.jpg', '步行到西湖10分钟,独立卫浴', '杭州市西湖区', 328.00), ('老门东民国风小院', '/upload/house2.jpg', '历史街区里的安静小院', '南京市秦淮区', 268.00), ('迪士尼附近亲子套房', '/upload/house3.jpg', '两室一厅,接送服务', '上海市浦东新区', 458.00);字段设计上有几个点值得讲:openid加唯一索引,同一用户在小程序里不会被重复注册;金额一律用DECIMAL(10,2),场景里价格、总价都涉及钱,用 FLOAT 算总价会出现 0.1+0.2 不等于 0.3 的问题;订单状态用TINYINT存数字,不用字符串,状态流转在 Java 枚举里管。
3.3 SSM 工程结构与 Maven 依赖
典型的 SSM 后端工程按 Maven 的标准目录组织,拿到工程包后先看src/main/java下的包名分层和src/main/resources下的配置文件:
src/main/java └── com.house.controller # Controller 层 └── com.house.service # Service 层(接口 + 实现) └── com.house.mapper # MyBatis Mapper 接口 └── com.house.pojo # 实体类 src/main/resources ├── spring-dao.xml # 数据源 + SqlSessionFactory + Mapper 扫描 ├── spring-mvc.xml # 注解驱动 + JSON 转换器 └── mapper/HouseMapper.xml # SQL 映射文件 src/main/webapp/WEB-INF/web.xmlpom.xml里的核心依赖长这样,版本号按你工程包里的实际为准,别自己乱升:
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>4.3.30.RELEASE</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.6</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.3.2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.49</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.10</version> </dependency> </dependencies>这段依赖的版本关系是配套的:Spring 4.3 支持@RestController注解,也不用 java 8 以上的语法;mybatis-spring 1.3.x 对应 MyBatis 3.4.x。如果你升级 Spring 到 5.x,mybatis-spring 也要跟着升级,不然运行期会报版本不兼容。spring-dao.xml里数据源和 Mapper 扫描是重头:
<context:property-placeholder location="classpath:db.properties"/> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="mapperLocations" value="classpath:mapper/*.xml"/> </bean> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.house.mapper"/> </bean>mapperLocations指定 Mapper XML 的路径,basePackage指定 Mapper 接口的包路径,两者必须指向同一个接口和 XML 的组合,扫描不到就会在启动时直接报Invalid bound statement (not found)。spring-mvc.xml里开<mvc:annotation-driven/>,再配一个 Jackson 的消息转换器,Controller 返回对象时才能自动序列化成 JSON 而不是把对象 toString 成乱码。
3.4 一个接口从 Controller 到 Mapper XML 的完整写法
以「民宿列表」接口为例,前端请求GET /api/house/list?sort=price,后端从入口到 SQL 的完整写法是:
@RestController @RequestMapping("/api/house") public class HouseController { @Autowired private HouseMapper houseMapper; @GetMapping("/list") public Result list(@RequestParam(defaultValue = "id") String sort) { return Result.success(houseMapper.selectList(sort)); } }Controller 里只干两件事:接收参数、返回统一结构的 Result。Result是一个封装类,包含code、message、data三个字段,这是前后端约定好的返回格式,所有接口共用。真正的 SQL 在HouseMapper.xml里:
<select id="selectList" resultType="com.house.pojo.House"> SELECT id, title, cover_img AS coverImg, intro, address, price, status FROM t_house WHERE status = 1 ORDER BY ${sort} </select>注意两个细节:cover_img通过别名AS coverImg映射到 Java 属性的驼峰命名,也可以在spring-dao.xml的 SqlSessionFactory 里开mapUnderscoreToCamelCase全局转换,二选一;ORDER BY ${sort}用的是字符串拼接而不是 MyBatis 的#{}占位符,因为排序字段是列名,不能用占位符方式传值,这意味着sort参数只能在白名单里选(比如id、price、rate),不能直接让前端传任意字符串,否则有注入风险。毕设里很多人忽略这一点,答辩时可能被追问。
4. 小程序端对接 SSM:登录、列表与真机联调
4.1 封装 wx.request:统一 baseUrl 与错误处理
小程序端最容易乱的地方是每个页面各自写一遍wx.request,URL 散落各处,后端 IP 一换就要全局搜。我的习惯是单独封装一个utils/request.js,把所有请求统一走 Promise:
// utils/request.js const BASE_URL = 'http://192.168.1.100:8080/house'; // 后端 IP,部署时改这里 function request(url, method = 'GET', data = {}) { return new Promise((resolve, reject) => { wx.request({ url: BASE_URL + url, method: method, data: data, header: { 'Content-Type': 'application/json' }, success(res) { if (res.statusCode === 200 && res.data.code === 200) { resolve(res.data.data); } else { wx.showToast({ title: res.data.message || '请求失败', icon: 'none' }); reject(res.data); } }, fail(err) { wx.showToast({ title: '网络异常', icon: 'none' }); reject(err); } }); }); } module.exports = { request, BASE_URL };封装之后页面调用变成request('/api/house/list?sort=price'),返回的res.data.data已经剥离了包装层,页面拿到的是纯列表数据。BASE_URL里的 IP 是开发阶段的电脑局域网 IP,后面真机联调时只需要改这一个地方。
header里的Content-Type: application/json要和后端 SpringMVC 的@RequestBody配合使用——后端接口如果期望 JSON 入参,而请求头没带这个类型,参数会解析为null,这是接口联调阶段很常见的翻车点。GET 请求不需要这个头,POST 传对象时才需要。
4.2 登录授权:openid 才是毕设里的身份凭证
民宿短租要下单,必须解决「用户是谁」的问题。微信小程序里,身份凭证是openid,不是手机号。wx.login拿到一个临时code,后端拿code去微信接口换openid:
// pages/login/login.js const app = getApp(); wx.login({ success: async (res) => { const code = res.code; const data = await request('/api/user/login', 'POST', { code: code }); app.globalData.userId = data.userId; wx.setStorageSync('userId', data.userId); wx.switchTab({ url: '/pages/index/index' }); } });后端对应的LoginController里,核心逻辑是拿着code调微信的code2session接口,换回openid,然后查表:
@PostMapping("/login") public Result login(@RequestBody Map<String, String> params) { String code = params.get("code"); // 用 code 换取 openid 和 session_key String url = "https://api.weixin.qq.com/sns/jscode2session?" + "appid=" + APPID + "&secret=" + SECRET + "&js_code=" + code + "&grant_type=authorization_code"; String result = HttpUtil.get(url); JSONObject obj = JSON.parseObject(result); String openid = obj.getString("openid"); // 查表,不存在则自动注册 User user = userMapper.selectByOpenid(openid); if (user == null) { user = new User(); user.setOpenid(openid); userMapper.insert(user); } return Result.success(user); }这里的APPID和SECRET在微信公众平台的「开发管理-开发设置」里,各自对应你自己的小程序。code2session拿到的不止openid,还有session_key,微信侧不会持久化,后端有需要就存 Redis,毕设里存数据库或内存即可。注意答辩时别把SECRET的截图放进论文或演示视频,这是很多同学会踩的坑。
关于手机号,微信目前的规定是:open-type="getPhoneNumber"获取手机号要求小程序已完成微信认证,个人主体的小程序没有这个权限。毕设个人主体居多,别在产品设计里把「手机号」设成下单的必填项,否则演示时当场翻车。常规做法是把手机号做成选填,或者用「手动输入 + 模拟验证码」的方式在表单里演示。
4.3 民宿列表页:从 HTTP 到 WXML 渲染
列表页是整套系统最核心的展示场景。index.js里拉数据,index.wxml里渲染:
// pages/index/index.js const { request } = require('../../utils/request'); Page({ data: { houseList: [], sort: 'price', loading: false }, onLoad() { this.fetchList(); }, async fetchList() { this.setData({ loading: true }); const list = await request('/api/house/list?sort=' + this.data.sort); this.setData({ houseList: list, loading: false }); }, onPullDownRefresh() { this.fetchList().then(() => wx.stopPullDownRefresh()); } });<!-- pages/index/index.wxml --> <view class="house-card" wx:for="{{houseList}}" wx:key="id"> <image src="{{item.coverImg}}" mode="aspectFill"></image> <view class="info"> <text class="title">{{item.title}}</text> <text class="address">{{item.address}}</text> <view class="price">¥{{item.price}}<text>/晚</text></view> </view> </view>wx:key="id"是列表渲染的必备项,没有它列表更新时会整批重渲染,数据多了明显卡顿。coverImg的 URL 如果返回的是/upload/house1.jpg开头的相对路径,图片会加载不出来,需要前端拼上BASE_URL,或者后端在返回数据时直接拼好完整 URL——一个项目里只做一处,两处都做会显示成双份前缀。
排序这块,前端传sort参数,后端在ORDER BY里切换。民宿列表页常见的排序按钮是「默认 / 价格 / 评分」,评分需要联表查t_comment求平均值,SQL 大概是ORDER BY (SELECT AVG(rate) FROM t_comment WHERE house_id = t_house.id) DESC。毕设里写出这条子查询,论文和答辩就都有东西讲了。
另外,如果你把顶部标题栏改成了自定义导航(很多模板会这么做),要注意不同机型的顶部导航栏高度不一致,wx.getMenuButtonBoundingClientRect()拿到胶囊按钮位置,再结合wx.getSystemInfoSync()的状态栏高度去动态计算导航栏高度,别写死 64px,真机上 iPhone 和 Android 会差出一大截。列表页的「推荐、价格」这类 tab 定位可以往下压,但胶囊按钮的适配错位会直接暴露在答辩投影上。
5. SSM 民宿短租项目的避坑清单:从环境到联调的 5 个高频翻车点
5.1 后端环境与启动类坑
坑 1:MySQL 8 把驱动类名换了,启动直接 ClassNotFoundException
现象:Tomcat 启动时控制台报java.lang.ClassNotFoundException: com.mysql.jdbc.Driver,或数据源初始化时抛Communications link failure。
原因:mysql-connector-java 8.x 把驱动类从com.mysql.jdbc.Driver改成了com.mysql.cj.jdbc.Driver,同时新驱动强制校验时区参数。老 SSM 工程里db.properties写的是老驱动类名,在 MySQL 8 环境里必然加载失败。
解决:改db.properties,驱动类和 URL 一起换:
jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/house_rental?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai如果用的是 MySQL 5.7,保持原样即可,serverTimezone参数加上也不会有副作用。MySQL 8 如果只换驱动不写serverTimezone,会报The server time zone value相关错误,属于同一根因。
坑 2:Tomcat 10 和 JDK 版本不匹配,接口全 500
现象:工程能启动,但访问接口时要么直接抛NoClassDefFoundError: javax/servlet/...,要么UnsupportedClassVersionError,页面一片 500。
原因:Tomcat 10+ 把javax.servlet替换成了jakarta.servlet,老 SSM 工程的 web.xml 和HttpServletRequest相关代码全部对不上;另外 pom 里没有显式指定编译版本,Maven 默认按当前 JDK 版本编译 class 文件,高版本 JDK 编译的 class 在 Tomcat 8.5 上读不了。
解决:Tomcat 固定用 8.5 或 9.0;pom.xml 里显式加上编译属性:
<properties> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties>判断是不是这个问题,看 Tomcat 的catalina.out日志,搜索UnsupportedClassVersionError,前面的数字就是 class 文件版本号,61 对应 JDK 17,52 才是 JDK 8。
5.2 小程序联调类坑
坑 3:开发者工具里一切正常,真机预览全部 request:fail
现象:开发者工具里列表、登录都正常,点「预览」用手机扫码打开,所有请求都失败,页面白屏。
原因:两个叠加因素。第一,localhost在手机上指向手机自己,不指向电脑;第二,小程序对请求域名有合法性校验,只允许 HTTPS 的备案域名,局域网 IP 和http://协议都在拦截范围内。
解决:BASE_URL改成电脑的局域网 IP(Win 下ipconfig查 IPv4 地址),Tomcat 监听默认就是0.0.0.0所有网卡;Windows 防火墙需要放行 8080 端口,不然手机访问不到;开发阶段在微信开发者工具的「详情-本地设置」里勾选「不校验合法域名、web-view(业务域名)、TLS 版本以及 HTTPS 证书」,并且真机预览时手机端也要在项目的「设置-项目设置」里打开「不校验合法域名」开关——这两个开关是分开的,只在开发者工具里勾了,真机照样拦。
有个细节最容易忽略:电脑连着公司或校园网的 WiFi,局域网 IP 通常是192.168.x.x;如果电脑开了热点给手机,IP 段会变成10.x.x.x或192.168.137.x,BASE_URL 要跟着变。这个「IP 换了整个项目连不上」的问题,排查时先ping再换 IP,别去改代码。
5.3 数据与交付类坑
坑 4:微信获取手机号一直失败,errMsg 里没有具体原因
现象:点击「获取手机号」按钮后,回调里errMsg提示失败,代码看起来没问题,但手机号就是拿不到。
原因:获取手机号是微信侧的能力限制,需要小程序完成微信认证(企业/组织主体),个人主体小程序没有这个接口权限。这是产品层面的硬门槛,不是代码能绕过的。
解决:毕设里不要把所有业务绑定在手机号上,把手机号改成选填项,登录直接用wx.login换 openid 建立用户身份。订单通知用「微信订阅消息」或者干脆在订单列表里轮询状态,都能绕开手机号权限的依赖。
坑 5:导入数据库后中文全是「???」,列表页显示乱码
现象:house.sql导入 MySQL 后,SELECT查到的是???,小程序页面显示的地址和标题也是乱码。
原因:SQL 文件本身是 GBK/ANSI 编码保存的,导入到utf8mb4的库就是乱码;或者建库语句没指定字符集,MySQL 用了默认的latin1。
解决:第一步,建库语句里显式写DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_general_ci,第 3.2 节给的 SQL 里已经写了;第二步,用 Navicat 导入前,把.sql文件用编辑器(VSCode 或 Notepad++)另存为「UTF-8 无 BOM」格式;第三步,导入完成后先SELECT验证一遍中文数据,确认没问题再往后配。乱码类问题一旦发生后端接口层是查不出来的,因为它显示的就是数据库里存坏的值。
6. 从「能跑」到「能答辩」:论文组织与演示视频的实操技巧
工程包里的毕业论文和视频演示,是加分项也是容易露怯的地方。先说视频演示的录制脚本:开录之前先往数据库里造好数据,至少 6 到 8 条房源、1 条「已支付」的订单、2 条带评分的评论,保证演示时每个页面都有内容可看,而不是现场临时下单等待响应。录制顺序按使用路径走:启动小程序 → 首页 → 民宿列表 → 详情页 → 选择入住日期提交订单 → 订单列表看到新订单 → 取消订单或模拟支付 → 个人中心。全程别切后台,别录到桌面和多余窗口。
论文组织上,我见过太多人直接把源码包里的文档改个名字就交,查重率直接爆表。正确做法是把技术选型章节自己重写一遍——为什么用 SSM、为什么用 MySQL、为什么小程序选原生而不是 uniapp,这些在第 2 章里都有素材,用自己的话组织。数据库设计章节的 ER 图用 draw.io 重画,别用源码包里的截图;核心代码只贴关键片段,比如第 3.4 节那个selectList和 4.1 节的request.js封装,每个片段配 100 到 200 字的实现说明,这是论文里「关键技术」部分的标准写法。
答辩高频问题先准备话术:为什么不用 SpringBoot;openid 和用户表怎么关联的;订单状态从待支付到已完成是怎么流转的;价格排序是怎么实现的。把这些问题的答案按「先说思路、再指代码位置」的方式组织,回答时能点到具体的类名和 XML 文件,比背定义有效得多。
最后给一个我自己的习惯:强烈建议用一个独立目录把「数据库脚本 + 完整源码 + 已配置好的环境」固定下来,视频演示录完就不再动任何代码。我做这类毕设项目最深的教训是,答辩前一天想「顺手加个功能」,结果把后端配置弄坏,第二天演示时数据库起不来,只能现场修。先把环境固化,演示脚本在答辩前完整走两遍,确认下单流程和订单状态流转顺畅,再考虑锦上添花。希望帮到你。
本文还有配套的精品资源,点击获取