资讯动态

体育场预约管理系统Android课程设计:基于Room的时段冲突与事务实现

发布时间:2026/9/14 12:28:22 来源:尧图企业网站定制
简介这是一款基于Android Studio开发的体育场预约管理系统面向高校软件技术、移动开发相关专业学生尤其适合需要完成安卓课程大作业或毕业设计的读者。系统重点解决线下排队预约体育场馆的痛点提供用户注册登录、普通会员与管理员双角色权限控制、体育馆信息浏览、个人中心管理、在线预约下单、历史订单查询修改及状态跟踪等功能覆盖从界面搭建到业务处理的完整开发链路。开发过程中综合运用Activity与Fragment组件、XML布局文件、SQLite数据持久化、适配器列表展示、角色权限管理等核心Android技术有助于读者理解真实预约类应用的业务流程与代码组织方式。资源以zip压缩包形式发布包体大小约10.8MB便于下载后直接导入Android Studio查看或调试。目前已有6022人学习下载可作为课程设计参考模板并可在基础上扩展地图定位、在线支付等高级功能。1. 体育场预约管理系统课程设计里最容易被低估的是“时段冲突”体育场预约管理系统在课程设计清单里常被归进“简单 CRUD”那一类可实际动手以后你会意识到真正的复杂度集中在“时间”上场地可以被不同人预约、用户会在临近截止时反复提交、时段粒度一旦从半天改成小时数据量立刻翻倍。所有这些问题最终都会落到同一个不变量上——同一块场地在同一个时间段内不能同时存在两个有效预约订单。这个项目也很适合用 Android Studio 单独交付。大作业场景下单人开发最常见的取舍是做一个本地单机版本用 Room 完成持久化预约状态全部放到本地事务里管理不引入后端服务器。这样既能在答辩时完成从选场地、选时段到提交预约的完整演示也能把有限的代码量集中到界面逻辑和数据处理上。与其盲目堆功能不如把时间片设计、状态流转和重复点击防护做扎实这是这门课题更容易拿分的地方。2. 预约系统的数据模型Room 表结构、时间片与状态字段2.1 为什么在课程设计里优先选 RoomAndroid 本地持久化的惯例是 SQLite但课程设计里直接写 SQLiteOpenHelper 的维护成本偏高表结构变更要手工改版本号、写迁移脚本查询结果还要手动映射成对象。Room 是 Android 官方提供的 ORM 框架它在编译期生成 SQLite 访问代码把 DAO、Entity 和 Database 类串起来。用 Room 有三个很现实的好处写法上接近“定义数据类 加注解”SQL 集中在 DAO 接口里查询错误在编译期就能暴露一部分Room 对 LiveData 的支持也让列表页可以自动刷新预约成功后不用手动调用 Adapter 的 notifyDataSetChanged。针对体育场预约系统这种课设Room 的迁移机制也值得设计进方案。比如你一开始设计了“开始时间 结束时间”两个字段答辩前想改成“日期 起始时段 时段数”的模型Room 的 Migration 可以在不删表的前提下完成升级。课程设计阶段不强制要求数据不丢失但能把 Migration 写对本身就是一个加分点。Room 的三件套通常这样组织Entity 定义表结构DAO 定义数据访问接口Database 类持有数据库实例并在应用启动时初始化。下面先从 Entity 设计开始。2.2 三张核心表场地、预约、用户体育场预约系统的表结构不需要设计得过于复杂评分点集中在“预约”核心流程上三张表足够表名关键字段用途venuevenueId, name, sportType, pricePerHour, capacity记录场地基础信息和单价bookingbookingId, venueId, userId, date, startSlot, durationSlots, status存储预约记录包含时段与状态useruserId, username, role, phone区分用户与管理员管理员可作废任意预约booking 表是整个系统的核心。设计时建议把时段拆成“date startSlot durationSlots”而不是直接存开始时间和结束时间。原因很直接场地预约的时间粒度通常是固定的比如半小时或一小时。存时间戳虽然更通用但在冲突判断时要处理大量的边界比较改用整数 slot 表达时段后冲突判断只需要比较两个整数区间代码量少一半也更容易用单元测试覆盖。为什么需要 status 字段预约不是一条 INSERT 就结束了它会经历用户取消、超时未生效、管理员作废等状态变化。用整型字段管理状态比物理删除记录更适合课程设计的展示和答辩提问。实际订单表里建议保留的取值是0 表示已提交待确认1 表示预约成功2 表示已取消3 表示已过期。如果指导老师要求流程更简单0 和 1 可以合并但 status 字段本身仍然值得保留因为它让后续的状态机设计变得可行。2.3 用 DAO 和 Repository 封装查询逻辑定义好 Entity 之后DAO 是 Room 里最重要的层。这个系统至少需要三个数据访问方法场地列表查询、预约插入、冲突检测。冲突检测可以在 DAO 里通过 SQL 直接完成检查同一日期同一个场地是否存在时间重叠的记录Dao interface BookingDao { Query( SELECT * FROM booking WHERE venue_id :venueId AND date :date AND status IN (0, 1) AND start_slot :endSlot AND (start_slot duration_slots) :startSlot ) suspend fun findConflictingBooking( venueId: Long, date: String, startSlot: Int, durationSlots: Int ): ListBookingEntity }这段 SQL 判断冲突的思路是新预约的区间是 [startSlot, startSlot durationSlots)已存在预约的区间是 [start_slot, start_slot duration_slots)两个区间重叠的条件是“新起点小于旧终点”且“新终点大于旧起点”。用这种区间方式是因为要覆盖三种情况新预约完全包含旧预约、部分重叠、旧预约完全包含新预约。如果只是比较两个时间戳谁大谁小必然漏掉其中一两种。Room 的 DAO 支持 suspend 函数这要求项目引入协程依赖。后面会给完整的 build.gradle 配置。把 DAO 包在 Repository 层里的好处是业务逻辑可复用预约按钮点击后的冲突检测、列表页的剩余时段计算、管理员的强制作废都调用同一组 Repository 方法而不是在 ViewModel 里重复写 SQL。Repository 层的职责要划分清楚只负责数据获取和事务组合不负责界面状态。界面的 loading、错误提示、预约按钮的可用状态应该由 ViewModel 处理。用一个 ViewModel 可以同时管理这几件事拉取场地列表、根据当前选中的日期计算可预约时段、提交预约并处理冲突结果。3. 用 Android Studio 实现“选场地 → 选时段 → 提交预约”主流程3.1 Gradle 依赖配置开始写界面前先把 build.gradle 里的依赖确认好。一个基于现代 Android Studio 课设项目的依赖组合如下plugins { id com.android.application id org.jetbrains.kotlin.android id kotlin-kapt } android { compileSdk 34 defaultConfig { applicationId com.example.stadiumbooking minSdk 24 targetSdk 34 versionCode 1 versionName 1.0 } } dependencies { implementation androidx.core:core-ktx:1.12.0 implementation androidx.appcompat:appcompat:1.6.1 implementation androidx.recyclerview:recyclerview:1.3.2 implementation androidx.room:room-runtime:2.6.1 implementation androidx.room:room-ktx:2.6.1 kapt androidx.room:room-compiler:2.6.1 implementation androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0 }room-ktx 提供协程支持lifecycle-viewmodel-ktx 提供 viewModelScope。kapt 是 Room 在编译期生成代码的注解处理器也可以换成 ksp但 kapt 在 Studio 里开箱即用对课设来说更稳妥。如果遇到 Gradle 依赖下载过慢的问题可以在 settings.gradle 里配置阿里云镜像仓库方法是在 dependencyResolutionManagement 的 repositories 中添加 maven { url https://maven.aliyun.com/repository/public }。3.2 场地列表页RecyclerView ViewModel 的组合系统主界面建议采用“场地列表 预约入口”的结构。RecyclerView 作为列表容器每个 item 显示场地名称、运动类型、单价和容量。这里涉及两个 layout 文件item_venue.xml 只做展示item_venue_booking.xml 多一个“立即预约”按钮点击后跳转到时段选择页。场地列表的 ViewModel 需要暴露两个 LiveDatavenueList 和 isRefreshing。获取列表可以这样写class VenueListViewModel(private val repository: VenueRepository) : ViewModel() { private val _venueList MutableLiveDataListVenueEntity() val venueList: LiveDataListVenueEntity _venueList fun loadVenues() { viewModelScope.launch { _venueList.value repository.getAllVenues() } } }viewModelScope.launch 会在 ViewModel 销毁时自动取消协程不需要在 Activity 的 onDestroy 里手动处理异步回调。如果你用的是 Java 写法没有协程就需要用 Executor 或 Thread这两个方案在屏幕旋转时会有内存泄漏风险不推荐用于新写的课设代码。3.3 时段选择页用 Grid 渲染可预约的 slot时段选择页是交互最复杂的界面。常见做法是把一天划分为若干 slot例如从 8 点到 22 点共 14 个 slot每个 slot 代表一小时。用 RecyclerView 配合 GridLayoutManager 展示每个 slot 有三种状态可选、已被预约、当前选中。可约时段的计算逻辑放在 ViewModel 里核心函数如下fun computeAvailableSlots( allSlots: ListInt, existingBookings: ListBookingEntity ): SetInt { val occupied existingBookings.flatMap { booking - (booking.startSlot until booking.startSlot booking.durationSlots).toList() }.toSet() return allSlots.filter { it !in occupied }.toSet() }这里把已有预约展开成具体的 slot 集合再与全部 slot 做差集。occupied 集合里的每个元素是一个小时单位这种展开写法在 slot 数量少时非常直观。如果用户选中了多个连续 slot提交时要再把选中集合折叠回 startSlot durationSlots 的形式这样才能复用上一章的冲突检测 SQL。3.4 提交预约事务、二次确认与防重复点击点击“提交预约”按钮后先弹出 AlertDialog 做二次确认列出场地名称、日期、时间段和费用确认后才写数据库。这里最容易踩的坑是快速双击按钮产生两条记录。防护手段分两层UI 层在 Dialog 弹出后把按钮置为不可点击数据层做冲突检测和唯一索引。两层缺一不可UI 层解决用户体验数据层兜底逻辑漏洞。插入预约的 Repository 方法这样写suspend fun createBooking(booking: BookingEntity): BookingResult { return try { db.withTransaction { val conflict bookingDao.findConflictingBooking( booking.venueId, booking.date, booking.startSlot, booking.durationSlots ) if (conflict.isEmpty()) { bookingDao.insert(booking) BookingResult.Success(booking.bookingId) } else { BookingResult.Failed(该时段已被预约请重新选择) } } } catch (e: Exception) { BookingResult.Error(e.message ?: 保存失败) } }withTransaction 是 Room 提供的协程事务扩展能确保冲突检测和插入在同一个数据库事务内执行。为什么必须放在事务里因为检测冲突和插入之间如果被其他线程插入了一条记录两条预约就可能同时通过检测。Room 的 withTransaction 在同一数据库实例上是串行执行的事务内的代码不会和其他事务并发因此这个场景下可以放心使用。4. 预约冲突的并发控制事务、唯一索引与控制逻辑4.1 重复点击与完全重复预约课程设计虽然不像生产环境那样有高并发压力但我在排查实际课设代码时发现“重复点击”仍是最常见的失败场景。双击按钮通常产生两条完全一样的预约原因在于插入前没做防抖或者数据库层没有唯一性约束。两个手段可以同时加UI 防抖解决操作体验数据库约束兜底异常情况。SQLite 里可以建复合唯一索引CREATE UNIQUE INDEX idx_booking_venue_time ON booking(venue_id, date, start_slot, duration_slots);这个索引只能挡住完全相同的预约也就是 start_slot 和 duration_slots 都一样的记录两个部分重叠的预约它拦不住。因此 DAO 层的时间段重叠检测仍然必要。这个唯一索引的真正价值在于挡住用户双击造成的完全一致请求以及在程序逻辑因 bug 漏检时提供最后一道防线。4.2 部分重叠的单元测试与边界情况部分重叠的检测逻辑已经在前面的 SQL 中给出了这里补充一个配套的单元测试。答辩时被问“你怎么验证冲突检测是对的”直接跑测试比口头解释有说服力得多Test fun testOverlapDetection() { // 已有预约10 点到 12 点 val existing BookingEntity(venueId 1, date 2024-06-01, startSlot 10, durationSlots 2) // 新预约11 点到 13 点部分重叠 val newStart 11 val newDuration 2 val isOverlap newStart existing.startSlot existing.durationSlots newStart newDuration existing.startSlot assertTrue(isOverlap) }这个测试覆盖的是“新预约开始在旧预约结束前且新预约结束在旧预约开始后”的典型部分重叠场景。建议把相邻时段也测一下10-12 和 12-14 的预约在边界相切正确结果应该是 false因为 12 点整开始的预约和已结束的预约不构成重叠。边界相切是否允许由业务决定但课程设计里建议允许否则用户的结束时间选择会很别扭。4.3 状态机设计用条件更新替代状态机框架预约状态在实体中用整型字段管理状态之间允许的迁移用代码约束。常见的状态机定义如下状态值含义可迁移到的状态0已提交待确认1确认、2取消1预约成功2取消、3过期2已取消无3已过期无实现状态迁移时不推荐引入状态机框架课程设计用不上。直接在 Repository 里写条件更新即可suspend fun cancelBooking(bookingId: Long): Boolean { val current bookingDao.getById(bookingId) ?: return false if (current.status in listOf(2, 3)) return false bookingDao.updateStatus(bookingId, 2) return true }更新前先读当前状态更新时按 bookingId 和旧 status 做条件更新可以避免两个线程同时取消同一订单导致的状态漂移。如果观察得更仔细会发现这里用的是乐观锁的简化版先比较后更新。真正的乐观锁会要求 UPDATE 语句里带上旧的 status 作为 WHERE 条件这种做法更严谨但课程设计里用先读取再判断已经足够。5. 答辩演示前的调试技巧造数据、查库和验证过期状态5.1 用模拟器快速造数据演示阶段最大的痛点是“没有数据可展示”。与其手动点击十几次不如在应用启动时判断数据库是否为空为空则自动写入几条模拟场地记录class SeedDataWorker(private val dao: VenueDao) { fun seedIfEmpty() { if (dao.count() 0) return dao.insertAll( VenueEntity(name 室内篮球馆, sportType 篮球, pricePerHour 80), VenueEntity(name 足球场A, sportType 足球, pricePerHour 200), VenueEntity(name 羽毛球馆1号, sportType 羽毛球, pricePerHour 40) ) } }这段代码必须加一个 BuildConfig.DEBUG 开关只在调试包中执行避免答辩演示完打包时把演示数据带进最终产物。5.2 用 Database Inspector 查看预约记录Android Studio 自带 App Inspection 工具连接模拟器运行应用时可以在工具窗口里直接查看应用沙盒内的数据库文件浏览 booking 表的每一行记录甚至可以手动修改字段值来测试界面响应。这个工具对调试状态机非常有用你可以把一条记录的 status 从 1 改成 2然后切回应用界面观察列表是否立刻刷新。如果更习惯命令行也可以直接拉取数据库文件到本地用 SQLite 工具检查adb exec-out run-as com.example.stadiumbooking cat databases/stadium.db stadium.db拿到文件后在终端里用 sqlite3 直接查询冲突检测 SQL 的结果这样可以绕开整个 Android 环境单独验证数据逻辑。5.3 模拟“预约过期”的场景如果系统里有“已提交但 30 分钟未确认则过期”的逻辑不用真的等待三十分钟。用 adb 修改模拟器的系统时间就能制造过期场景adb shell date 062115002024.00执行后杀掉应用进程重新打开观察过期状态是否被正确刷新。注意只能改模拟器不要改真机时间否则系统日历、闹钟等应用会受到影响而且真机上的时间修改通常需要手动同步校正操作成本高。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价