做了好几套“Spring Boot + Android”的毕业设计项目,这种酒店预订系统算是我被问得最多的类型之一。一方面因为题目关键词覆盖广,有后端有客户端还有小程序;另一方面网上虽然能找到大量源码片段,但真正能跑通闭环、配套文档齐全、能应付答辩完整的并不多。这篇就把我实际落地一套酒店预订 App 的全过程拆开讲,从技术选型、数据库设计、核心接口、Android 端联调到常见坑位和答辩准备,全部覆盖。无论你是刚拿到这个题目、想要完整源码作参考,还是已经开始写代码卡在某个环节,这篇文章都应该能帮你少走不少弯路。
1. 项目整体设计与功能拆解
1.1 毕设为什么要选“酒店预订”方向
酒店预订系统是典型的“信息展示 + 状态管理 + 交易闭环”业务,麻雀虽小但五脏俱全。比起图书管理系统、班级管理系统这类纯 CRUD 项目,酒店预订有更真实、更清晰的业务约束:库存、日期冲突、订单状态流转、支付流程。这些约束放在毕业设计里,恰恰是你能展示技术深度的地方。老师问到“你的订单状态怎么设计”“并发下单会不会超卖”,你都有具体场景可以展开回答。
从技术选型来看,这个题目自带了完整链路:Android 原生客户端负责用户操作,Spring Boot 提供后端接口,MySQL 存数据,可以再加一个微信小程序端复用同一套接口。对毕设来说,它天然体现“移动端 + 后端服务 + 数据库”三种能力的结合,展示效果也比网页后台直观得多。你在台上用模拟器点两下订房,比放几张系统截图有说服力。
还有一个很实际的优势:酒店预订贴近日常,不需要额外学习行业知识。需求分析章节也不难写,用户角色、预订流程、房型信息、价格计算都是普通人都理解的场景,不会在开题阶段就卡住。后面想扩展也容易,比如加评论、地图定位、会员积分、价格日历,随便挑一个都能作为论文里的“系统特色”。
1.2 核心功能模块怎么拆
系统按角色分为用户端和管理端两部分。用户端就是 Android App 和微信小程序,管理端一般用后台 Web 页面或 Swagger 接口文档来体现。我这里主要围绕用户端梳理几个必备功能:
- 用户模块:注册、登录、个人资料修改。密码不能明文存,登录成功后返回 Token,客户端在后续请求里带上。
- 首页与房源模块:酒店和房型列表,支持按城市、价格区间筛选,展示房型图片、床型、面积、设施、价格。
- 预订模块:选择入住和离店日期,自动计算晚数和总价,生成订单。核心是校验日期不能冲突、不能选过去日期,并且下单时要锁定对应日期段的库存。
- 订单模块:订单列表按状态区分为待支付、已支付、已入住、已完成、已取消。用户可取消未支付的订单,管理端可确认入住和退房。
- 支付模块:毕业设计一般用模拟支付,生成一个支付二维码或直接模拟支付成功回调,避免真实资金操作。
- 管理后台:维护房型信息、管理订单状态、管理用户,可以通过 Spring Boot 提供 REST API,再配一个简单的 Vue 页面,或者直接用 Swagger 展示接口能力。
我的建议是实现优先级先做登录、房源列表、房型详情、下单、订单列表这五个闭环。不需要一开始就把所有页面都做出来。很多同学一上手只顾着写漂亮的 Android 页面,后端接口还没跑通,最后演示时只能点死界面,这是最坑的。先打通最小端到端链路,再逐步补后台和支付,才是稳的做法。
1.3 服务端与客户端职责怎么划分
一个常见错误是让客户端做太多业务判断。比如判断入住日期是否在今天之前,前端做了校验固然体验好,但真正的判断必须放在 Spring Boot 后端,因为接口可以被任何人直接调用,不只是你的 App。正确做法是:客户端只做展示和交互,业务规则全部由后端兜底。
后端接口统一返回固定格式的 JSON,大概长这样:
{ "result": "success", "code": 200, "msg": "操作成功", "data": {} }失败时 result 为 failure,code 换成业务状态码,msg 是展示给用户的信息,data 为业务数据。这个统一响应结构非常重要,Android 端在 Retrofit 的解析层做统一处理,登录状态失效就自动跳回登录页;小程序端在 request 封装里做同样逻辑。我后来做其他项目也一直沿用这套约定,能省掉大量重复的错误判断代码。
2. 技术选型与核心实现细节
2.1 Spring Boot 后端分层与依赖选择
我的后端选的是 Spring Boot 2.7,搭配 MyBatis-Plus、MySQL、Redis 和 JWT。选 2.x 而不是 3.x,主要是兼容性考虑:3.x 要求 JDK 17 及以上,很多同学本机装的还是 JDK 8 或 11,强上用 Spring Boot 3 会遇到大量环境问题。另外 2.x 的教程和踩坑资料最丰富,MyBatis-Plus 等中间件适配也稳定,对毕设来说完全够用。如果你的电脑已经是 JDK 17 以上并且想尝新,才考虑升级。切记,不是版本越新越好,先求稳。
后端采用标准三层结构:
- Controller 层:接收参数,调用 Service,不写业务逻辑。
- Service 层:处理核心业务,事务在这里控制。
- Mapper 层:MyBatis-Plus 的 BaseMapper 提供单表 CRUD,复杂查询再用 XML 写动态 SQL。
这样分层最大的好处是定位问题快。参数不对找 Controller,业务逻辑错找 Service,SQL 慢看 Mapper。写论文架构图时也很清晰,三层加客户端再加数据库,一张图讲完所有。
依赖方面,核心的是 spring-boot-starter-web、mybatis-plus-boot-starter、mysql-connector-java、spring-boot-starter-data-redis、jjwt,以及 hutool 工具库。Hutool 主要是用来做随机数、日期处理、雪花 ID 生成这些琐碎操作,不引也行,但引用后效率高很多。
2.2 Android 端网络请求与页面架构
Android 端用原生 Java + Retrofit + OkHttp + Glide + RecyclerView 这一套组合。页面架构没有上重框架,就用简化版 MVVM:Activity/Fragment 负责交互,ViewModel 持有数据,Repository 封装网络请求。对于毕设项目来说,不需要把 LiveData、Room 全引进来,过度设计反而是负担,重点是把网络层写好。
网络请求的写法以登录为例,定义 ApiService 接口:
public interface ApiService { @POST("user/login") Call<ApiResponse<UserVo>> login(@Body LoginRequest request); @GET("room/list") Call<ApiResponse<List<RoomVo>>> roomList(@Query("city") String city, @Query("page") int page, @Query("size") int size); }Retrofit 初始化时设置 baseUrl,这里有一个经典大坑:Android 模拟器访问本机 Spring Boot,不能写 localhost 或 127.0.0.1,要用 10.0.2.2。真机调试则通过 adb reverse tcp:8081 tcp:8081 把电脑端口映射到手机,或者确保手机和电脑在同一局域网,使用电脑的局域网 IP。很多毕设项目第一次联调,百分之八九十的时间都耗在这个地址问题上,后面我会在常见问题里详细展开。
2.3 小程序端怎么快速复用后端接口
题目里带“小程序”,这里单独解释一下。小程序的定位是复用同一个后端服务,展示多端能力。因为后端接口全部是 RESTful JSON,小程序端不需要重新写业务逻辑,只需要封装一层 request,把 Token 拼接、错误提示、401 处理这些公共逻辑抽出来。
小程序页面结构大致是:首页搜索城市和推荐酒店、房型列表页、房型详情页、下单页、订单列表页、个人中心。如果使用原生微信小程序开发,代码量不小;但如果你已经写了 Android 原生 App,不建议再花大量时间做一个小程序版本。除非你的课题明确要求多端覆盖,否则更推荐用 uni-app 写一套基础版小程序页面,与 Android 核心功能对齐即可。论文里把这个做法总结为“多端复用同一套接口,降低业务实现成本”,本身就是一个亮点。
3. 实操过程与核心环节实现
3.1 数据库设计与建表 SQL
数据库命名 hotel_db,核心表为用户表、房型表、库存表、订单表。用户表负责账号登录,房型表存酒店和房间信息,库存表按日期维度管理可订数量,订单表记录用户每一笔预订。四张表就能构成完整闭环。
用户表:
CREATE TABLE user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL, nickname VARCHAR(50), phone VARCHAR(20), avatar VARCHAR(255), create_time DATETIME DEFAULT CURRENT_TIMESTAMP );房型表:
CREATE TABLE room_type ( id BIGINT PRIMARY KEY AUTO_INCREMENT, hotel_name VARCHAR(100) NOT NULL, city VARCHAR(50) NOT NULL, name VARCHAR(100) NOT NULL COMMENT '房型名:大床房/双床房', price DECIMAL(10,2) NOT NULL, area INT, bed_type VARCHAR(50), max_people INT, image_url VARCHAR(500), description TEXT, status INT DEFAULT 1 COMMENT '1上架 0下架' );订单表:
CREATE TABLE orders ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL UNIQUE COMMENT '订单号', user_id BIGINT NOT NULL, room_type_id BIGINT NOT NULL, room_id BIGINT DEFAULT NULL, check_in_date DATE NOT NULL, check_out_date DATE NOT NULL, nights INT NOT NULL, total_price DECIMAL(10,2) NOT NULL, contact_name VARCHAR(50), contact_phone VARCHAR(20), status INT DEFAULT 0 COMMENT '0待支付 1已支付 2已入住 3已完成 4已取消 5已退款', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, pay_time DATETIME );这里有几个设计点值得专门说。第一,订单号字段必须唯一,不要在代码里直接拿自增 id 当订单号展示,并发场景下很容易出问题,用时间戳加随机数或雪花 ID 更合适。第二,库存控制不要简单用“订单总数减总库存”,因为已取消的订单会污染数量,建议单独建库存表,按房间类型和日期维度管理。第三,订单状态用 int 不用字符串,枚举直观且查询高效。第四,金额字段用 DECIMAL,不要用 float 或 double,否则价格计算会出现浮点误差。
库存表是预订模块的核心,脚本如下:
CREATE TABLE hotel_stock ( id BIGINT PRIMARY KEY AUTO_INCREMENT, room_type_id BIGINT NOT NULL, stock_date DATE NOT NULL, total_stock INT NOT NULL DEFAULT 10, booked_stock INT NOT NULL DEFAULT 0, UNIQUE KEY uk_room_date (room_type_id, stock_date) );每次下单时,把入住到离店每一天的库存都检查一遍,全部有房才允许创建订单,并在事务里对 booked_stock 加一。
3.2 后端核心接口实现
登录接口是第一个闭环。用户注册时用 BCrypt 加密密码,登录成功后生成 JWT 返回给客户端。JWT 是无状态方案,服务端不保存会话,客户端在后续请求的 Header 里带 Authorization,后端拦截器解析即可。
登录 Controller 的代码很简单:
@RestController @RequestMapping("/user") public class UserController { @Resource private UserService userService; @PostMapping("/login") public ApiResponse<LoginResult> login(@RequestBody @Valid LoginRequest request) { return ApiResponse.success(userService.login(request.getUsername(), request.getPassword())); } }Service 里核心是查用户、比对密码、生成 Token。比对密码不要用 MD5,引入 spring-security-crypto 的 BCryptPasswordEncoder 单独处理,比写一堆加盐逻辑规范得多。
预订接口是整个系统的重头戏。下单时要做三件事:校验用户、校验所选日期段库存、创建订单并扣库存。这里扣库存的做法决定系统是否容易出脏数据。我在事务里执行更新库存的 SQL:
UPDATE hotel_stock SET booked_stock = booked_stock + 1 WHERE room_type_id = ? AND stock_date = ? AND booked_stock < total_stock;如果影响行数为 0,说明当天没房,直接抛业务异常并回滚事务,提示用户“所选日期段部分日期已满房”。这种写法本质上是数据库行锁加乐观条件更新,能避免两个用户同时下单导致超卖。答辩时把这个逻辑讲清楚,老师基本不会再纠结你的并发控制问题。
3.3 Android 端登录与房源列表实现
登录页逻辑很直接,EditText 拿账号密码,点击按钮调用 Retrofit,loading 对话框等待,返回成功就把 Token 存到 SharedPreferences,然后跳转主页。需要注意在 Activity 销毁时取消请求,避免页面关闭后回调更新 UI 导致崩溃。
房源列表页用 RecyclerView 展示卡片,每张卡片包含酒店图、房型名、地址、价格和剩余库存。列表数据用分页接口,Android 端监听 RecyclerView 滑动到底部时自动加载下一页。这个功能基础但很实用,体现移动端性能优化意识。
几个 Android 开发细节值得单独记一下:
- 图片加载用 Glide,占位图和错误图一定要设置,否则弱网环境下页面很难看。
- Item 使用 ViewBinding 或 ViewHolder 管理,不要在列表里频繁 findViewById。
- 日期选择建议用 MaterialDatePicker,比两个 EditText 手输日期体验好太多。
- 金额展示统一用 BigDecimal 转字符串,避免精度问题。
- 每次请求都要处理网络错误和业务错误,不要只在成功回调里写 UI。
4. 常见问题与排查技巧实录
4.1 Spring Boot 版本过高引发的兼容问题
最近毕设季收到最多的提问就是“为什么我的项目启动就报错”。翻来覆去查,大多都是新建项目时顺手选了 Spring Boot 3.x,结果 MyBatis-Plus 版本不兼容,或者 javax.* 包名改成 jakarta.* 后一堆代码全挂。GitHub 上大量教程还是 2.x 的写法,抄作业抄到一半发现类不存在,这种体验非常折磨人。
解决办法是选稳的组合:Spring Boot 2.7 + MyBatis-Plus 3.5.x + JDK 8。如果你的环境已经是 JDK 17 且想用 3.x,至少要把所有 javax.* 换成 jakarta.*,并引入 mybatis-plus-spring-boot3-starter。但对毕业设计来说,2.7 在功能上完全不落后,以稳为主就好。还有一个建议:把项目所有第三方依赖的版本整理成一张对照表放进文档,答辩时问到“版本怎么定”,你能答出依据,印象分会明显提升。
4.2 模拟器与真机联调的网络地址问题
这个话题在毕设联调里几乎必问。先记住三条规则:
- Android 模拟器访问宿主机后端,地址用 10.0.2.2,不是 localhost。
- 后端端口不要用 8080,建议换 8081 或 9000,减少被本机其他程序占用的概率。
- Spring Boot 配置文件的 server.address 不要写 127.0.0.1,直接留空或写 0.0.0.0,否则手机连不上。
真机调试时,用 adb reverse 最省心:
adb reverse tcp:8081 tcp:8081执行之后,手机访问 localhost:8081 就会转发到电脑的 8081 端口,不需要手机和电脑在同一 WiFi。这个方法对 USB 调试场景非常稳定。如果非要用局域网 IP,要确保手机和电脑在同一网段,并且 Windows 防火墙放行对应端口。很多同学后端启动成功但手机连不上,八成都是防火墙没放行。
4.3 订单状态与并发预订的几个坑
订单状态流转看着简单,实际做起来容易漏细节。几个反复被问到的坑:
- 重复提交订单。用户快速点两次“提交预订”,可能产生两条订单。前端要加防抖,后端在短时间窗口内限制同一用户对同一房型重复下单,两者都做最保险。
- 取消订单后库存没恢复。取消接口的 Service 方法必须有事务,并且在修改订单状态的同时,减去库存表对应日期的已订数量。这个逻辑漏了之后,库存会越卖越少,而且很难定位。
- 价格与日期挂钩。节假日和平日价格不一样,计算总价不能只乘一个固定单价。我的做法是增加价格日历表,按日期保存价格,下单时聚合求和。
- 超时未支付。如果要做“下单 15 分钟未支付自动取消”,用 @Scheduled 定时任务扫表最稳妥,不要依赖 Redis 过期事件。Redis 过期事件在流量高的场景下并不可靠,漏触发很常见。
这些细节我在文档里整理成“异常场景清单”,每个截图配一段说明。答辩老师看到一个学生主动聊异常处理,印象分往往比聊功能要高。
5. 毕设文档与答辩准备
5.1 论文结构怎么组织
任何一个带 Spring Boot 和 Android 的毕设,论文大纲都是“绪论、需求分析、系统设计、系统实现、系统测试、总结展望”这个框架,但内容要落到实处:
- 绪论写背景和国内外现状时,不用抄千篇一律的话,可以引用一两个具体产品和技术演进趋势,比如移动端酒店预订 App 的常见架构,以及服务端从单体走向微服务的过程。
- 需求分析放用户角色、用例图、功能需求、非功能性需求。
- 系统设计放总体架构图、功能模块图、数据库 E-R 图、界面原型图。
- 系统实现按模块写,每个模块给出关键类图、接口说明、核心代码片段、运行截图。
- 系统测试设计测试用例,覆盖正常流程和异常流程,列出测试结果表。
论文里放代码要克制,只截取核心逻辑的关键片段,不要整个类贴进去。截图要清晰、尺寸统一,界面状态与文字描述对应。数据库设计章节,我给每张表都做了字段说明表,一个字段一行,整理成 Markdown 再粘进 Word,比手敲高效很多,也更整齐。
最重要的一条原则:代码和论文必须一致。我见过不少学生最终演示的接口和论文里写的完全对不上,老师一追问就露馅。正确做法是代码跑通一个模块,就截图、写描述;代码改动后,同步更新论文里的接口说明。千万不要代码写完了再补论文,那样既累又容易遗漏。
5.2 答辩演示和常见提问
演示顺序建议:先启动后端,用 Swagger 或 Postman 展示核心接口;再打开 Android 模拟器演示注册、登录、浏览房源、下单、取消订单;最后打开管理端修改一条数据,展示前后端联动。整个过程控制在 10 分钟以内,不需要把每个按钮都点一遍,重点演示三个闭环即可。
答辩老师常问的问题,我列了几个:
- JWT 为什么无状态?服务端怎么判断登录失效?答:JWT 自带过期时间,后端解析签名并检查过期时间;要主动踢人则引入 Redis 记录登出态或黑名单。
- MyBatis-Plus 与 MyBatis 的区别?答:前者内置常用 CRUD,后者需要手写 SQL;本项目单表操作用 MyBatis-Plus,复杂查询用 XML 写自定义 SQL。
- 库存扣减如何避免超卖?答:核心是对库存表的可用数量做条件更新,影响行数为 0 则回滚,同时在事务里完成。
- 为什么不用微服务架构?答:当前业务规模单体架构足够,引入服务注册、网关、配置中心会显著增加复杂度;将来用户量和订单量增长后,可以按用户服务和订单服务拆分。
- 客户端与服务端数据传输怎么保证安全?答:生产环境使用 HTTPS,接口层可加签名和参数加密;毕设演示使用本地环境,重点把业务闭环和异常处理讲清楚。
回答问题的核心不是背概念,而是讲清“我遇到了什么问题、为什么选这个方案、有没有验证过”。老师最怕只会背理论不知道怎么落地的学生,你把自己的设计和实践讲清楚,分数通常不会低。
踩过几次坑之后,我的体会是:做这类全栈毕设项目,最大的成本不是写代码,而是联调和兜底。提前把统一响应结构、Token 机制、库存更新策略定下来,后面所有功能都会顺很多。数据库表也不用刚开始就急着建完,先跑通登录,再慢慢加订单和库存,边写边补反而更自然。毕设资料一定要边做边整理,代码注释、数据库导出脚本、接口文档、测试用例截图,每完成一个模块就同步更新一次。真到了写论文阶段,基本只是把素材组装进去,不会再有那种熬夜赶工的绝望感。如果时间还宽裕,我建议把支付模块换成真实的小程序支付沙箱,或者把后台管理做成一个独立的小型 Vue 页面,这两个改动会让项目的完整度和答辩亮点直接上一个台阶。