SpringBoot+Android民宿预订系统从零到答辩全指南
2026/9/19 14:47:54 网站建设 项目流程

简介:一份基于Spring Boot与Android平台的民宿预订系统毕业论文文档,面向计算机相关专业毕业生以及需要完成课程设计或毕业设计的开发者。内容系统阐述了民宿预订系统的设计目的、需求分析、总体架构与实现方案,重点涉及Spring Boot框架选型、MySQL数据库存储、Java语言数据传输,以及基于OOP和MVC模式的设计思想,能够帮助读者理清Android端民宿平台从后端服务到移动端交互的完整思路。资源包共1个文件,格式为docx,大小4.98MB,适合直接参考论文章节结构、摘要撰写和关键技术描述。已有218人学习下载,适合正处于选题、开题或论文写作阶段的读者借鉴。文档内含摘要、Abstract、目录、绪论、开发环境、系统设计、需求分析等内容预览,可作为论文写作框架与格式模板,同时补充了Spring Boot、MySQL、Android架构等关键知识点,有助于快速搭建同类系统并完成论文撰写。

1. 从选题到落地,这届毕设为什么都选SpringBoot加Android

民宿预订系统是毕业设计里出现频率最高的几类题目之一,它的业务边界清楚:房源展示、预订下单、订单管理、用户中心,后端不会膨胀到失控,前端又不至于只剩增删改查。而“SpringBoot + Android”这对组合之所以被反复选中,是因为它把技术栈压在了两条最主流的主线上——后端用 SpringBoot 做 RESTful 接口收敛业务,客户端用 Android 原生承载 C 端体验,数据流单向清晰,论文答辩时有得讲、有得演。这篇文章不带你重写论文,而是按“设计先行、接口为界、两端并进、论文收口”的路线,把一套可复现的民宿预订系统从表结构讲到 APK 联调,连带把论文里必须写清楚的关键章节拆给你看。适合正在做同类选题、需要把“设计与实现”落到代码和文档里的读者。

2. 先划清业务边界:系统模块划分与数据库设计

2.1 民宿预订系统的角色与用例拆分

民宿预订系统的角色一般收敛成三类:游客、注册用户、管理员。游客只能浏览房源列表和详情,不能下单;注册用户登录后可以下单、模拟支付、评价;管理员维护房源上下架、处理订单状态、管理用户。把角色切到这个粒度,后端接口的权限控制就很好做了——游客接口走公开路由,用户接口挂 JWT 校验,管理员接口再加一层角色判定。

用例图不必画得太复杂,核心用例就六个:用户注册登录、房源浏览、房源搜索筛选、下单预订、订单支付、订单管理。把六个用例落到接口层,你会发现后端只需要约二十个接口,正好是一篇本科毕设论文能讲透的量级。再往细走,每个用例都要能对应到一张前端页面和一个后端接口,论文第三章的“系统功能设计”才不会写空。

2.2 数据库表结构:六张表覆盖全部业务

民宿预订系统的数据量不大,表设计不需要过度范式化,但六张核心表是底线:用户表、房源表、房源图片表、订单表、评价表、收藏表。少了任何一张,业务闭环都会断。表结构直接决定接口能不能流畅对接,所以设计时要考虑“查询路径”:用户打开 App 先看房源列表,点进详情看大图和评价,下单生成订单,管理员在后端改订单状态。这个路径上的每一跳都需要表与表之间的外键关系清晰。

表名关键字段与业务的关系
userid, username, password, phone, avatar登录凭证与个人信息
houseid, owner_id, title, address, price, status房源主体,status 控制上下架
house_imageid, house_id, url, sort一个房源对应多张图片
ordersid, order_no, user_id, house_id, check_in, check_out, amount, status订单主体,状态机驱动
commentid, order_id, user_id, content, rating关联订单,防止无订单刷评
favoriteid, user_id, house_id收藏功能,唯一约束防重复

用户在民宿预订系统里每产生一次有效行为,最终都会落到某一张表的某一行上。这里最容易踩的坑是订单表的状态字段用字符串随意填,建议用 int 定义枚举值:0 待支付、1 已支付待入住、2 已入住、3 已退房、4 已取消、5 已退款。状态流转要放在后端 Service 层统一控制,前端只负责展示状态文案。

2.3 用 Navicat 建库与初始化数据的注意事项

我一般先用 Navicat 把数据库建好,再回头写实体类。建库时字符集选 utf8mb4,排序规则选 utf8mb4_general_ci,否则接口返回中文会出现乱码。初始化数据至少准备 8 到 10 个房源,每个房源 3 到 5 张图片,地址分布在同一个城市的多个区县,这样列表分页和搜索筛选才能演示得自然。

CREATE DATABASE homestay_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE homestay_db; CREATE TABLE house ( id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(100) NOT NULL COMMENT '房源标题', address VARCHAR(200) NOT NULL COMMENT '所在地址', price DECIMAL(10,2) NOT NULL COMMENT '每晚价格', status TINYINT DEFAULT 1 COMMENT '1在售 0下架', create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB; CREATE TABLE orders ( id INT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) NOT NULL COMMENT '订单号', user_id INT NOT NULL, house_id INT NOT NULL, check_in DATE NOT NULL, check_out DATE NOT NULL, amount DECIMAL(10,2) NOT NULL, status TINYINT DEFAULT 0 COMMENT '0待支付 1已支付', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_user (user_id), KEY idx_house (house_id) ) ENGINE=InnoDB;

order_no用时间戳加随机数拼接生成,比如yyyyMMddHHmmss加四位随机数,避免并发下单时主键冲突。KEY idx_userKEY idx_house是查询索引,用户点进“我的订单”页面时会按user_id过滤,没有索引数据量大以后会明显变慢。DECIMAL(10,2) 保证金额精确到分,千万不能用 float 存钱。建完表后先插入几行测试数据,确认自增主键和中文注释没有异常,再进 SpringBoot 工程写实体类。

3. SpringBoot 后端接口层:从工程骨架到 JWT 鉴权

3.1 用 Spring Initializr 生成工程与 Maven 依赖清单

用 IDEA 内置的 Spring Initializr 生成一个 SpringBoot 工程,Java 版本选 8 或 11 都可以,打包方式选 Jar。依赖只需要四个核心组件:Spring Web、MyBatis-Plus、MySQL Driver、Lombok。JWT 相关的 jjwt 依赖和 Hutool 工具包需要手动加进pom.xml,Hutool 用来生成订单号和处理日期格式非常方便。

<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.11.5</version> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.22</version> </dependency>

MyBatis-Plus 选 3.5.x 而不是 3.4.x,因为 3.5 之后分页插件兼容性更好。jjwt 的 0.11.5 版本需要同时引入jjwt-impljjwt-jackson,否则运行时报SignatureException。Hutool 工具包体积不大,但省掉自己写日期转换和随机数的工时,值得引入。

3.2 application.yml 配置与 MyBatis-Plus 分页插件

SpringBoot 的配置集中在application.yml里。端口选 8080,数据库连接串写上本机 MySQL 的库名;MyBatis-Plus 开启驼峰映射和日志输出,日志用stdout打印 SQL,方便前端联调时定位问题。

server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/homestay_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0

map-underscore-to-camel-case必须为 true,否则数据库字段create_time映射不到实体的createTime属性上。logic-delete是逻辑删除配置,民宿和订单表都不建议物理删除,用户误下单后取消订单是改状态而不是删行。serverTimezone=Asia/Shanghai解决 MySQL 8.x 时区报错,如果不加,启动时通常报The server time zone value异常,这是 springboot 配置中最常见的问题之一。

分页插件在配置类里单独声明一个 Bean,不用改任何 SQL。

@Configuration public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }

3.3 实体类、Mapper 与 Service 层的三层结构

每个表对应一个实体类,实体类字段用@TableName指定表名,主键用@TableId(type = IdType.AUTO)标注。Service 层继承IService,实现类继承ServiceImpl,MyBatis-Plus 会直接提供list()getById()save()updateById()这些基础方法,不需要手写 XML。这是 SpringBoot 框架在毕设里最值钱的部分——把大量重复的 CRUD 代码压缩到几乎为零。

@Data @TableName("house") public class House { @TableId(type = IdType.AUTO) private Integer id; private String title; private String address; private BigDecimal price; private Integer status; private Date createTime; }

Controller 层不要写业务逻辑,只做参数接收和结果返回。统一返回结果用Result<T>包装,code 为 200 表示成功,401 表示未登录,500 表示服务器异常。这样 Android 端拦截器只需要判断 code 字段就能决定是否跳转登录页。DTO 和 VO 的区分要提一下:接收前端的参数用 DTO,返回前端的展示对象用 VO,不要把实体类直接返回到前端,避免把数据库字段暴露出去。

3.4 JWT 登录鉴权:拦截器加注解的落地方式

用户登录接口接收usernamepassword,密码用 MD5 加盐后存库。登录成功后用 jjwt 生成 token,把 userId 和 role 放进 claims。

public String generateToken(Integer userId, String role) { return Jwts.builder() .setSubject(String.valueOf(userId)) .claim("role", role) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 24)) .signWith(secretKey, SignatureAlgorithm.HS256) .compact(); }

Token 有效期设为 24 小时,客户端退出登录时直接丢弃本地 token。拦截器在preHandle里从请求头取 Authorization,前缀是Bearer,去掉前缀后解析 token,解析失败直接返回 401。需要放行的接口在 WebMvcConfigurer 里用excludePathPatterns配置,注册接口、登录接口、房源列表和详情都公开,下单和评价必须登录。管理员专属接口在 Controller 上加@PreAuthorize注解,配合 Spring Security 使用,但不想引入 Security 的话也可以用自定义拦截器判断 role 字段。

@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new AuthInterceptor()) .addPathPatterns("/api/**") .excludePathPatterns("/api/user/login", "/api/user/register", "/api/house/list", "/api/house/detail/**"); } }

这样设计的好处是 Android 端每个请求都带 token,后端通过用户 ID 查询归属相关的订单,天然防止越权。我在答辩时被问到最多的就是“如何防止用户 A 访问用户 B 的订单”,答案就在拦截器加 userId 校验这里。

3.5 民宿预订与订单状态更新的时序控制

下单接口是后端最核心的业务逻辑,必须在事务中完成。先判断房源状态是否在售,再校验入住日期不能早于当天,退房日期必须晚于入住日期,最后计算金额等于单价乘以天数并生成订单。订单创建后状态为 0 待支付,模拟支付接口把状态改为 1 已支付,同时把入住日期之间冲突的订单检查一遍。

@Transactional(rollbackFor = Exception.class) public Order createOrder(OrderDTO dto, Integer userId) { House house = houseService.getById(dto.getHouseId()); if (house == null || house.getStatus() != 1) { throw new RuntimeException("房源不可预订"); } if (dto.getCheckOut().before(dto.getCheckIn())) { throw new RuntimeException("退房日期必须晚于入住日期"); } long days = (dto.getCheckOut().getTime() - dto.getCheckIn().getTime()) / 86400000; Order order = new Order(); order.setOrderNo(generateOrderNo()); order.setUserId(userId); order.setHouseId(house.getId()); order.setCheckIn(dto.getCheckIn()); order.setCheckOut(dto.getCheckOut()); order.setAmount(house.getPrice().multiply(BigDecimal.valueOf(days))); order.setStatus(0); orderService.save(order); return order; }

事务注解必须放在方法上而不是类上,rollbackFor = Exception.class表示任何异常都回滚,避免出现“订单生成了但库存没扣”这种数据不一致问题。防止同一个房源同一时间被重复预订,需要在 orders 表加唯一约束或者用 SQL 的SELECT ... FOR UPDATE锁行。本科毕设用FOR UPDATE就够了,不要在论文里写分布式锁,那个方向一展开就收不住。

3.6 接口返回结构与管理端统计接口

管理端需要的三个统计指标分别是:订单总数、营业额总和、房源数量。用 MyBatis-Plus 的 QueryWrapper 就能实现,不需要写自定义 SQL。

// 订单总数 long orderCount = orderService.count(new QueryWrapper<Order>().eq("status", 1)); // 营业额 BigDecimal totalAmount = orderService.list(new QueryWrapper<Order>().eq("status", 1)) .stream().map(Order::getAmount).reduce(BigDecimal.ZERO, BigDecimal::add); // 房源数 long houseCount = houseService.count();

Android 管理端页面直接把三个数值展示在首页卡片上,调一次/api/admin/stats接口全部返回。字段名用orderCounttotalAmounthouseCount,保持驼峰命名,前端做 JSON 解析时不需要做字段映射。

4. Android 客户端:从工程初始化到房源列表的完整链路

4.1 Android Studio 中的工程配置与依赖引入

Android 端用 Java 语言编写,Android Studio 版本选 Arctic Fox 之后的版本,Gradle 用 7.x,编译 SDK 用 33。新建工程时选 Empty Activity,包名建议与后端对应,例如com.homestay.app。网络请求需要 INTERNET 权限,在AndroidManifest.xml中声明访问后端地址的权限。

<uses-permission android:name="android.permission.INTERNET" />

build.gradle的 dependencies 中引入 Retrofit、Gson 转换器和 Glide。Retrofit 是基于 OkHttp 的 RESTful 请求框架,它能把后端接口定义成 Java 接口方法,Android 开发里这是最主流的网络层方案,比直接手写HttpURLConnection省去大量样板代码。

implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation 'com.squareup.retrofit2:converter-gson:2.9.0' implementation 'com.github.bumptech.glide:glide:4.15.1' implementation 'com.squareup.okhttp3:logging-interceptor:4.11.0'

4.2 Retrofit 网络层封装与统一返回解析

定义一个 Api 接口类,用注解声明每个 HTTP 请求。以房源列表为例:

public interface ApiService { @GET("api/house/list") Call<Result<List<HouseVO>>> getHouseList(@Query("page") int page, @Query("size") int size, @Query("keyword") String keyword); }

@GET注解声明 GET 请求路径,@Query自动把参数拼接到 URL 后面,Call<Result<List<HouseVO>>>表示返回体是一个 Result 对象,内部 data 字段是 HouseVO 列表。Retrofit 会把 JSON 自动转换成 Java 对象,前提是字段名和 JSON key 一致,不一致需要用@SerializedName注解修正。

在数据库或内存中,图片地址通常只存了相对路径,Android 端加载图片时要拼接完整地址。

String fullUrl = BASE_URL + "/" + house.getCoverImage(); Glide.with(context) .load(fullUrl) .placeholder(R.drawable.placeholder) .error(R.drawable.error_image) .into(holder.imageView);

4.3 房源列表页的 RecyclerView 与下拉刷新

列表页用 RecyclerView 承载房源信息流,配合 SwipeRefreshLayout 做下拉刷新和上拉加载更多。Adapter 的onBindViewHolder中绑定房源标题、价格、地址和首图。

public class HouseAdapter extends RecyclerView.Adapter<HouseAdapter.ViewHolder> { private List<HouseVO> list = new ArrayList<>(); public void setData(List<HouseVO> data) { list.clear(); list.addAll(data); notifyDataSetChanged(); } @Override public void onBindViewHolder(ViewHolder holder, int position) { HouseVO house = list.get(position); holder.title.setText(house.getTitle()); holder.price.setText("¥" + house.getPrice() + "/晚"); holder.address.setText(house.getAddress()); Glide.with(holder.itemView.getContext()) .load(BASE_URL + house.getCoverImage()) .into(holder.cover); } public class ViewHolder extends RecyclerView.ViewHolder { TextView title, price, address; ImageView cover; public ViewHolder(View itemView) { super(itemView); title = itemView.findViewById(R.id.house_title); price = itemView.findViewById(R.id.house_price); address = itemView.findViewById(R.id.house_address); cover = itemView.findViewById(R.id.house_cover); } } }

在 Fragment 或 Activity 里调用 Retrofit 的enqueue方法,回调中把结果交给 Adapter。如果首次加载返回空数据,需要有一个 EmptyView 提示“暂无房源”,避免用户以为页面卡死。

4.4 登录态的保持:SharedPreferences 存储 Token

Android 端登录成功后,后端返回的 token 要保存到SharedPreferences,后续所有请求都在拦截器中自动注入请求头。

public class AuthInterceptor implements Interceptor { @Override public Response intercept(Chain chain) throws IOException { Request original = chain.request(); String token = AppPreferences.getToken(); if (!TextUtils.isEmpty(token)) { Request request = original.newBuilder() .header("Authorization", "Bearer " + token) .method(original.method(), original.body()) .build(); return chain.proceed(request); } return chain.proceed(original); } }

AppPreferences是对 SharedPreferences 的一层封装,getToken从应用上下文读取 token 字符串。OkHttp 的拦截器在整个请求发出前统一加请求头,这样下单、评价等接口不需要每个请求单独传 token。登出时清除 token 并跳回登录页,新建订单、收藏、评价这些入口在未登录状态下点击时,先跳登录页而不是直接调接口,这个交互逻辑在演示时很加分。

4.5 与后端联调的几个必查项

Android 模拟器访问本机的后端服务时,不能用localhost,要写10.0.2.2。真机调试时要用电脑的局域网 IP,同时确保手机和电脑在同一 Wi-Fi 下。

public static final String BASE_URL = "http://10.0.2.2:8080/";

如果使用 Android 9.0 以上系统,默认禁止明文 HTTP 请求,需要在AndroidManifest.xml的 application 节点加android:usesCleartextTraffic="true",这是 springboot 配置完成后,Android 端首次联调必踩的坑。遇到“Failed to connect to /10.0.2.2:8080”时首先排查这个开关,其次是后端启动类是否绑定了 0.0.0.0 而不只是 localhost。

4.6 下单页面的日期选择与金额计算

下单页面用两个 DatePicker 分别选择入住和退房日期,Android 端在本地先把天数算好、金额预览出来,给用户一个下单前的确认弹窗,再调后端下单接口。这比直接提交让后端计算体验好,用户能立刻看到金额变化。日期格式用 yyyy-MM-dd 传字符串给后端,后端用@DateTimeFormat注解解析。

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd", Locale.CHINA); Date checkIn = sdf.parse(checkInStr); Date checkOut = sdf.parse(checkOutStr); long days = (checkOut.getTime() - checkIn.getTime()) / (1000 * 60 * 60 * 24);

4.0 版本以后的 Android 对主线程执行网络请求会直接抛NetworkOnMainThreadException,Retrofit 的enqueue自动在子线程执行,回调回到主线程,不需要手动切线程。但如果你在代码里用 OkHttp 的execute()同步方法,就必须放到子线程,否则 App 秒崩。

5. 毕业论文的落地技巧:把“设计与实现”写成人话

5.1 论文结构对应代码进度,别等写完再补

论文通常按六章推进:绪论、相关技术介绍、需求分析、系统设计、系统实现、系统测试。写论文时最容易出现的尴尬是系统实现写得像代码附在文档里,而系统设计部分又没有具体图表支撑。正确做法是每完成一个接口服务层,就同步更新系统设计的接口文档,每完成一个 Android 页面,就截图存档作为实现章节的配图。状态流转、用例表、ER 图这三样是外审老师最爱看的东西,建议在系统设计一章中用表格加文字描述的方式详写。

5.2 演示前必查的三条链路

答辩演示是整个毕设最关键的一环,演示前按这三条链路检查:

链路操作预期结果
注册登录注册新用户 → 登录界面跳转首页,token 已存储
下单支付选房源 → 选日期 → 提交订单 → 模拟支付订单状态变为已支付,可在“我的订单”查看
管理端管理员登录 → 房源上架 → 用户下单管理端订单列表同步出现新订单

演示时最怕的是后端突然连不上数据库。提前把 MySQL 服务设为开机自启,并确认电脑不会休眠断网。Android 模拟器如果启动太慢,可以提前半小时打开,让系统完全加载完成后再接测试。

5.3 答辩常见问题的一句话应答法

“为什么用 SpringBoot 而不是 SSM”是稳定出现的问题,答案是 SpringBoot 内嵌 Tomcat、自动装配依赖、简化配置,天然适合前后端分离架构。问“MyBatis-Plus 和 MyBatis 的区别”时,答 MP 提供了通用 CRUD 方法和条件构造器,开发效率更高但 SQL 定制能力不如手写 XML,两者可以并存。问“订单并发超卖怎么办”时,答数据库行锁FOR UPDATE处理同一房源的并发下单,并在接口层做幂等校验。把这三个问题提前背熟,答辩就能稳住,因为民宿系统的技术深度本身不太会引出超出这三道题的方向。论文查重前,把代码片段和图表部分做针对性改写,代码块不要直接贴网上源码,尽量用自己的变量名和注释风格重写一遍,能显著降低重复率。

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

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

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

立即咨询