ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

SpringBoot+Android民宿预订系统从零到答辩全指南

SpringBoot+Android民宿预订系统从零到答辩全指南 简介一份基于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 ) ENGINEInnoDB; 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) ) ENGINEInnoDB;order_no用时间戳加随机数拼接生成比如yyyyMMddHHmmss加四位随机数避免并发下单时主键冲突。KEY idx_user和KEY 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.xmlHutool 用来生成订单号和处理日期格式非常方便。dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3/version /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-api/artifactId version0.11.5/version /dependency dependency groupIdcn.hutool/groupId artifactIdhutool-all/artifactId version5.8.22/version /dependencyMyBatis-Plus 选 3.5.x 而不是 3.4.x因为 3.5 之后分页插件兼容性更好。jjwt 的 0.11.5 版本需要同时引入jjwt-impl和jjwt-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?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/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: 0map-underscore-to-camel-case必须为 true否则数据库字段create_time映射不到实体的createTime属性上。logic-delete是逻辑删除配置民宿和订单表都不建议物理删除用户误下单后取消订单是改状态而不是删行。serverTimezoneAsia/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实现类继承ServiceImplMyBatis-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 层不要写业务逻辑只做参数接收和结果返回。统一返回结果用ResultT包装code 为 200 表示成功401 表示未登录500 表示服务器异常。这样 Android 端拦截器只需要判断 code 字段就能决定是否跳转登录页。DTO 和 VO 的区分要提一下接收前端的参数用 DTO返回前端的展示对象用 VO不要把实体类直接返回到前端避免把数据库字段暴露出去。3.4 JWT 登录鉴权拦截器加注解的落地方式用户登录接口接收username和password密码用 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 QueryWrapperOrder().eq(status, 1)); // 营业额 BigDecimal totalAmount orderService.list(new QueryWrapperOrder().eq(status, 1)) .stream().map(Order::getAmount).reduce(BigDecimal.ZERO, BigDecimal::add); // 房源数 long houseCount houseService.count();Android 管理端页面直接把三个数值展示在首页卡片上调一次/api/admin/stats接口全部返回。字段名用orderCount、totalAmount、houseCount保持驼峰命名前端做 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:nameandroid.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.04.2 Retrofit 网络层封装与统一返回解析定义一个 Api 接口类用注解声明每个 HTTP 请求。以房源列表为例public interface ApiService { GET(api/house/list) CallResultListHouseVO getHouseList(Query(page) int page, Query(size) int size, Query(keyword) String keyword); }GET注解声明 GET 请求路径Query自动把参数拼接到 URL 后面CallResultListHouseVO表示返回体是一个 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.AdapterHouseAdapter.ViewHolder { private ListHouseVO list new ArrayList(); public void setData(ListHouseVO 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 存储 TokenAndroid 端登录成功后后端返回的 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:usesCleartextTraffictrue这是 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 对主线程执行网络请求会直接抛NetworkOnMainThreadExceptionRetrofit 的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处理同一房源的并发下单并在接口层做幂等校验。把这三个问题提前背熟答辩就能稳住因为民宿系统的技术深度本身不太会引出超出这三道题的方向。论文查重前把代码片段和图表部分做针对性改写代码块不要直接贴网上源码尽量用自己的变量名和注释风格重写一遍能显著降低重复率。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进