简介:本资源为基于Android的酒店预约预定管理系统完整源码包,采用Java语言开发,面向计算机相关专业的在校学生、教师及企业开发者,可用于毕业设计、课程设计、期末大作业或项目初期立项演示,也适合初学者进阶学习移动端开发。压缩包共150个文件,约2.34MB,其中53个java文件承载核心业务逻辑,52个xml文件负责界面布局与配置,另有31个png图片资源及gradle构建脚本、properties配置等,工程结构完整规范。资源内代码均经过测试运行成功,功能正常,涵盖酒店信息管理、房间预约、订单处理等典型模块,读者可据此快速理解Android客户端与后台交互的实现思路,并在此基础上修改扩展功能,直接用于毕设或课设提交。目前已有239人学习下载,欢迎交流共进。
1. 从一份酒店预约系统源码说起:Android 端 + Java 后端到底怎么落地
很多做 Android 的朋友拿到「Java 开发基于 Android 的酒店预约预定管理系统源码」这类资源时,第一反应是打开压缩包看目录结构,然后发现后端是 Java、前端是 Android、数据库是 MySQL,三块技术栈各说各话,跑不起来。这个标题背后其实是一个典型的移动端 + 服务端分离的预约类业务系统,核心链路是:用户在 Android 端选房型、选日期、提交订单,Java 后端处理库存扣减和订单状态流转,数据库保证并发下不超卖。它适合两类人:一是想拿一个完整业务闭环练手的 Android 或 Java 初学者,二是需要快速搭一个预约类 Demo 做课程设计或内部验证的开发者。真正要跑通它,难点不在代码量,而在环境对齐、接口联调和库存并发这三件事上。下面按「先理解结构、再动手跑通、最后避坑」的顺序拆开讲。
2. 先看清这套系统的骨架:三层结构与数据流怎么走
2.1 Android 端、Java 后端、MySQL 各自负责什么
这类酒店预约系统源码,不管具体实现怎么变,基本都逃不出三层结构。Android 端负责界面展示和用户交互,常见做法是用 Activity 或 Fragment 承载页面,用 RecyclerView 展示房型列表,用 DatePicker 选入住和离店日期。Java 后端负责业务逻辑,通常是一个 Servlet 或 Spring Boot 应用,对外暴露 HTTP 接口,处理登录、查房、下单、取消订单这些动作。MySQL 负责持久化,核心表一般包括用户表、房型表、房间表、订单表。
数据流是这样的:Android 端发一个 HTTP 请求到后端某个接口,后端查数据库或写数据库,再把 JSON 结果返回给 Android 端解析展示。听起来简单,但很多人翻车在「Android 端以为后端返回的是对象,后端实际返回的是字符串」这种序列化问题上。所以拿到源码后,第一件事不是急着跑,而是先把接口清单和数据库表结构对一遍。
我一般会先看三个东西:后端有没有统一的返回格式封装(比如 code、msg、data 三段式),数据库订单表有没有记录入住日期和离店日期,Android 端有没有把 baseUrl 抽成常量。这三处能看出这套源码的成熟度。
2.2 数据库表设计里最容易被忽略的字段
酒店预约系统的表设计,新手容易只建「订单表 + 房型表」就开跑,结果做到退房和续住时发现字段不够。下面这张表是我从多个类似源码里归纳出的订单表关键字段,缺一个后面都要改代码。
| 字段名 | 类型 | 作用 | 缺失后果 |
|---|---|---|---|
| order_id | varchar(32) | 订单号,建议用时间戳+随机数 | 用自增 ID 容易被遍历 |
| user_id | int | 下单用户 | 无法查我的订单 |
| room_type_id | int | 房型 ID | 无法关联房型信息 |
| check_in_date | date | 入住日期 | 无法判断库存 |
| check_out_date | date | 离店日期 | 无法计算天数 |
| order_status | tinyint | 0待支付 1已支付 2已入住 3已取消 | 状态流转混乱 |
| create_time | datetime | 下单时间 | 无法排序和超时取消 |
| total_price | decimal(10,2) | 总价 | 金额用 float 会丢精度 |
注意total_price一定用 decimal,不要用 float 或 double。我见过一个 Demo 用 float 存金额,结果 199.9 加 299.9 算出来是 499.79999999999995,前端展示直接裂开。这是血泪经验,不是理论。
2.3 接口清单:Android 端和后端必须先对齐的五个接口
在动手改代码之前,把下面这五个接口的请求参数和返回结构写清楚,能省掉后面大量联调时间。常见做法是用 Postman 先把后端接口跑通,再让 Android 端去调。
# 1. 用户登录 POST /api/user/login Body: {"username":"test","password":"123456"} 返回: {"code":200,"msg":"success","data":{"userId":1,"token":"xxx"}} # 2. 查询房型列表(按日期查可用库存) GET /api/room/list?checkIn=2025-06-01&checkOut=2025-06-03 返回: {"code":200,"data":[{"roomTypeId":1,"name":"大床房","price":299,"stock":5}]} # 3. 提交订单 POST /api/order/create Body: {"userId":1,"roomTypeId":1,"checkIn":"2025-06-01","checkOut":"2025-06-03"} 返回: {"code":200,"data":{"orderId":"202506011200001"}} # 4. 查询我的订单 GET /api/order/list?userId=1 返回: {"code":200,"data":[{"orderId":"xxx","status":1,"totalPrice":598}]} # 5. 取消订单 POST /api/order/cancel Body: {"orderId":"202506011200001"} 返回: {"code":200,"msg":"cancelled"}这五个接口覆盖了预约系统的最小闭环。参数说明上,checkIn和checkOut用字符串传日期,格式统一成yyyy-MM-dd,不要一边用时间戳一边用字符串,否则后端解析会出玄学问题。token在真实项目里要校验,Demo 里很多源码直接跳过,你如果要拿去改造成能用的东西,这一步必须补上。
3. 把源码跑起来:环境搭建与最小可运行路径
3.1 后端环境:JDK、Tomcat、MySQL 的版本对齐
这类源码的后端,如果是老一点的写法,多半是 Servlet + JDBC + Tomcat;新一点的会用 Spring Boot 打成一个 jar。两种跑法不一样,先看源码里有没有pom.xml或build.gradle,有就是 Maven/Gradle 项目,没有就是传统 Web 项目。
传统 Web 项目的跑法:
# 1. 确认 JDK 版本,老项目一般是 JDK 8 java -version # 2. 导入数据库,假设源码里有 hotel.sql mysql -u root -p -e "CREATE DATABASE hotel_db DEFAULT CHARSET utf8mb4;" mysql -u root -p hotel_db < hotel.sql # 3. 改数据库连接配置,通常在 src/db.properties 或类似文件 # jdbc.url=jdbc:mysql://localhost:3306/hotel_db?useSSL=false&serverTimezone=Asia/Shanghai # jdbc.username=root # jdbc.password=你的密码 # 4. 用 Tomcat 部署,把编译后的 web 目录丢进 webapps # 或者用 IDE 直接配置 Tomcat 运行Spring Boot 项目的跑法更简单,改完application.yml里的数据库配置,直接mvn spring-boot:run或java -jar就行。这里的关键参数是serverTimezone,不写这个,MySQL 8 以上版本连上去会报时区错误,订单时间全部差 8 小时,这是最常见的翻车点之一。
3.2 Android 端:改 baseUrl 和网络权限这两步不能漏
Android 端拿到源码后,跑不起来的原因九成集中在两个地方:baseUrl 没改,网络权限没加。
<!-- AndroidManifest.xml 里必须加网络权限 --> <uses-permission android:name="android.permission.INTERNET" /> <!-- Android 9 以上默认禁止明文 HTTP,需要在 application 标签加 --> <application android:usesCleartextTraffic="true" ...> </application>// 找到网络请求工具类,通常是 HttpUtil 或 Retrofit 的 ApiService // 把 baseUrl 改成你后端实际运行的地址 public static final String BASE_URL = "http://10.0.2.2:8080/hotel/"; // 说明:10.0.2.2 是 Android 模拟器访问宿主机 localhost 的特殊地址 // 如果你用真机调试,要改成电脑在局域网里的 IP,比如 http://192.168.1.100:8080/hotel/参数说明:10.0.2.2只对 Android 官方模拟器有效,Genymotion 是10.0.3.2,真机必须用局域网 IP。很多人改完 baseUrl 还是连不上,就是因为用了localhost,而localhost在模拟器里指向模拟器自己,不是你的电脑。这个坑几乎每个新手都会踩一次。
3.3 最小验证:用 Postman 先跑通登录和查房
在 Android 端跑之前,先用 Postman 把后端接口验证一遍,能排除掉一半问题。
# 登录接口测试 curl -X POST http://localhost:8080/hotel/api/user/login \ -H "Content-Type: application/json" \ -d '{"username":"test","password":"123456"}' # 如果返回 {"code":200,...} 说明后端和数据库通了 # 如果返回 500,看后端控制台报错,多半是数据库连接或 SQL 写错 # 如果返回 404,检查 Tomcat 的 context path 和接口路径是否匹配这一步过了,再去跑 Android 端。如果 Postman 通、Android 不通,问题一定在 Android 端的 baseUrl、权限或 JSON 解析上,不用再怀疑后端。这种分段排查的思路,比一上来就同时调两端效率高得多。
4. 库存扣减与订单状态:预约系统最容易翻车的地方
4.1 超卖是怎么发生的:两个用户同时下单同一房型
预约系统最核心的业务风险是超卖。假设某房型 6 月 1 日只剩 1 间,用户 A 和用户 B 同时点下单,如果后端逻辑是先查库存再扣库存,两步之间没有锁,就会出现两个人都查到库存为 1,然后都扣成功,最终卖出 2 间。
// 错误写法:先查后扣,并发下必然超卖 int stock = queryStock(roomTypeId, checkIn, checkOut); if (stock > 0) { createOrder(userId, roomTypeId, checkIn, checkOut); updateStock(roomTypeId, checkIn, checkOut, stock - 1); }正确做法有两种。一种是数据库层面用条件更新,把判断和扣减合成一条 SQL:
-- 正确写法:条件更新,影响行数为 0 说明库存不足 UPDATE room_stock SET stock = stock - 1 WHERE room_type_id = #{roomTypeId} AND stock_date = #{checkIn} AND stock > 0; -- 后端判断这条 SQL 的返回值,如果是 0,直接返回库存不足,不创建订单另一种是在 Java 层用synchronized或分布式锁,但 Demo 里用数据库条件更新就够了,简单可靠。参数说明:stock_date是按天存库存的设计,入住日期到离店日期之间的每一天都要扣减,跨天订单要循环处理,不能只扣入住当天。
4.2 订单状态流转:哪些状态可以互相跳转
订单状态不能随便改,要有明确的流转规则。常见做法是用一个状态机来约束。
| 当前状态 | 允许操作 | 目标状态 |
|---|---|---|
| 0 待支付 | 支付 | 1 已支付 |
| 0 待支付 | 取消 | 3 已取消 |
| 1 已支付 | 入住 | 2 已入住 |
| 1 已支付 | 取消(需退款逻辑) | 3 已取消 |
| 2 已入住 | 退房 | 4 已完成 |
| 3 已取消 | 无 | 终态 |
| 4 已完成 | 无 | 终态 |
// 状态流转校验,防止非法跳转 public boolean canTransfer(int from, int to) { if (from == 0 && (to == 1 || to == 3)) return true; if (from == 1 && (to == 2 || to == 3)) return true; if (from == 2 && to == 4) return true; return false; }这段逻辑看着简单,但很多源码里直接是update order set status = ?,没有校验,结果出现「已取消的订单又被改成已入住」这种脏数据。你如果要拿这套源码做二次开发,状态校验必须补上。
4.3 日期区间查询:一条 SQL 查出某房型在指定日期段是否可用
查可用库存是预约系统查询最频繁的操作。按天存库存的设计下,查一段日期是否可订,要保证区间内每一天库存都大于 0。
-- 查询某房型在 6月1日到6月3日之间是否有足够库存 -- 注意:离店日期当天不占库存,所以条件是 stock_date >= checkIn AND stock_date < checkOut SELECT COUNT(*) AS available_days FROM room_stock WHERE room_type_id = #{roomTypeId} AND stock_date >= #{checkIn} AND stock_date < #{checkOut} AND stock > 0; -- 如果 available_days 等于入住天数,说明可订 -- 入住天数 = DATEDIFF(checkOut, checkIn)参数说明:stock_date < checkOut这个边界很关键。6 月 1 日入住、6 月 3 日离店,实际占用 6 月 1 日和 6 月 2 日两晚,6 月 3 日当天不占库存。如果写成<= checkOut,会多扣一天,导致明明有空房却显示订不了。这个边界问题我在不止一个源码里见过写错的。
5. 避坑与排查:跑这套源码时最常见的五个问题
5.1 后端启动报 404,接口路径对不上
现象:Postman 请求返回 404,但后端日志没有报错。原因通常是 Tomcat 的 context path 和代码里的@WebServlet或@RequestMapping路径拼接后和你请求的不一致。解决:先看 Tomcat 启动日志里应用部署的路径,比如/hotel,再确认接口注解上的路径,两者拼起来才是完整 URL。如果源码里用的是web.xml配置 Servlet,还要检查url-pattern有没有写错。
5.2 Android 端报 NetworkOnMainThreadException
现象:点击登录按钮,应用直接崩溃,日志提示NetworkOnMainThreadException。原因是 Android 不允许在主线程做网络请求。解决:把网络请求放到子线程,用Thread+Handler,或者用OkHttp的异步回调,再或者用AsyncTask(老写法)。现在主流做法是 Retrofit + RxJava 或协程,但老源码里很多是裸HttpURLConnection,需要自己包一层线程。
5.3 中文乱码:请求参数和返回结果都变问号
现象:提交的房型名称或用户名在数据库里变成???。原因有三个可能:数据库字符集不是 utf8mb4、JDBC 连接没加字符集参数、Tomcat 的server.xml没配 URIEncoding。解决:数据库建库时指定DEFAULT CHARSET utf8mb4,JDBC URL 加characterEncoding=utf8,Tomcat 的 Connector 加URIEncoding="UTF-8"。三处都对齐,乱码基本消失。
5.4 订单金额算错:float 精度丢失
现象:两晚 299 元的房,总价显示 597.9999999999999。原因是金额字段用了 float 或 double。解决:数据库用decimal(10,2),Java 里用BigDecimal,计算时用BigDecimal.valueOf(299).multiply(BigDecimal.valueOf(2)),不要用new BigDecimal(299.0),后者还是会引入精度问题。这个坑不解决,后面做支付对接会出大问题。
5.5 模拟器连不上后端:baseUrl 用了 localhost
现象:Postman 能通,Android 模拟器请求超时。原因就是前面说的,模拟器里的localhost指向模拟器自身。解决:模拟器用10.0.2.2,真机用电脑局域网 IP,并确保手机和电脑在同一网段,电脑防火墙放行对应端口。如果还不行,用adb reverse tcp:8080 tcp:8080把端口反向映射一下,这是比较稳的兜底方案。
6. 把这套源码改造成能用的东西:三个进阶动作
跑通只是起点,这套源码真正有价值的地方在于它是一个完整的业务骨架,你可以往上加东西。第一个动作是补登录鉴权。Demo 里的登录往往只查一下用户名密码就返回用户信息,没有 token 机制。你可以加一个 JWT,登录成功后签发 token,后续接口从请求头里取 token 校验,这样才算一个能对外用的接口。第二个动作是把库存扣减改成 Redis 预扣。数据库条件更新在并发量不大时够用,但一旦 QPS 上去,行锁会成为瓶颈。常见做法是先把库存加载到 Redis,下单时用 Lua 脚本原子扣减,异步落库,这样能扛住更高的并发。第三个动作是加订单超时取消。用户下单后 15 分钟未支付,订单自动取消并回滚库存。实现方式可以用定时任务扫表,也可以用延迟队列。定时任务简单,适合 Demo;延迟队列更优雅,但引入的组件多。
// 订单超时取消的定时任务骨架 @Scheduled(cron = "0 */1 * * * ?") // 每分钟执行一次 public void cancelTimeoutOrders() { // 查出创建时间超过15分钟且状态为待支付的订单 List<Order> orders = orderMapper.selectTimeoutOrders(15); for (Order order : orders) { // 1. 改状态为已取消 orderMapper.updateStatus(order.getOrderId(), 3); // 2. 回滚库存,按天加回去 stockMapper.rollbackStock(order.getRoomTypeId(), order.getCheckIn(), order.getCheckOut()); } }参数说明:cron表达式0 */1 * * * ?表示每分钟的第 0 秒触发。selectTimeoutOrders(15)里的 15 是分钟数,按业务调整。回滚库存时要注意,只回滚那些确实扣过的天数,逻辑要和下单时扣减的天数完全对称,否则库存会越滚越多。我自己做这类系统时,习惯在下单和回滚两处都打日志,记录订单号和库存变化前后的值,出问题时能直接对账。这套源码值不值得投入,取决于你是只想交个作业,还是想拿它当真实项目练手。如果是后者,上面三个动作做完,它就从一份「能跑的 Demo」变成了「能讲清楚的项目」。希望帮到你。
本文还有配套的精品资源,点击获取