ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Django+Vue酒店预订系统实战:防超卖、状态机与部署避坑

Django+Vue酒店预订系统实战:防超卖、状态机与部署避坑 简介这是一套面向计算机专业本科生的毕业设计级酒店预订系统实战项目基于Python全栈技术栈构建适用于课程设计、毕设选题与Web开发能力进阶学习。项目采用B/S架构后端以Django框架实现业务逻辑与数据管理前端使用Vue.js构建响应式界面完整覆盖用户预订、订单处理、客房运营及后台管理全流程。资源包共391个文件含32个核心Python后端模块、28个Vue组件、约100张图片jpeg/jpg/png/webp用于界面展示以及svg图标、less样式、ts类型定义等前端工程化资源整体压缩包33.19MB结构清晰分为serverDjango与webVue两大目录。已有249人学习下载提供开箱即用的源码、完整功能模块含前台首页/订单中心/用户中心后台总览/客房/订单/评论/日志等10管理模块、标准化依赖配置requirements.txt及详细部署说明助开发者快速理解全栈协同开发模式与企业级系统架构设计。1. 为什么用 Django Vue 做酒店预订系统不是“炫技”而是真能扛住毕业答辩真实压测你手头这份毕业设计不是写个“Hello World”页面交差——它得在答辩现场连上数据库跑通订房、退房、房态实时更新得让老师用手机扫二维码打开 Vue 前端点选日期、房型、提交订单后端 Django 真实扣减库存、生成订单号、发邮件通知还得经得住导出 Excel 报表、按入住率画折线图、管理员后台批量导入房价这些“突然加戏”。很多人卡在第一步Python 写 Web该选 Flask 还是 DjangoFlask 轻量但权限、用户、管理后台全得自己造轮子Django 自带 Admin、ORM、认证、中间件、CSRF 防护毕业设计最怕的“功能堆到一半发现登录态崩了”“导出 Excel 没权限报 403”“并发订房超卖”——Django 的成熟生态就是你的后悔药。而 Vue 不是为“高大上”选的它比 React 学习曲线平缓组件化让首页轮播图、房型卡片、日历选择器、订单列表能拆开开发、独立调试打包后丢进 Django 的 static 目录就能跑不依赖 Node 服务端渲染部署极简。这项目本质是一个有真实业务闭环的中小型 Web 系统用户侧Vue负责交互体验与数据展示服务侧Django负责业务逻辑、数据一致性与安全边界。它不追求百万并发但必须做到订房不超卖、订单状态可追溯、管理员操作留痕、前端页面不裸奔暴露 API 密钥。下面我们就从零开始把这套组合真正跑通、调稳、防翻车。2. 搭建最小可行环境Django 后端 Vue 前端分离开发的三步落地法2.1 创建 Django 项目骨架并初始化核心 App毕业设计最怕环境配半天跑不起来。我们跳过虚拟环境创建细节python -m venv env source env/bin/activate直接聚焦关键命令和参数含义# 创建 Django 项目项目名 hotel_booking注意不是 app 名 django-admin startproject hotel_booking . # 创建核心业务 Apphotel房型/房态、booking订单、user扩展用户 python manage.py startapp hotel python manage.py startapp booking python manage.py startapp user提示hotel_booking是项目容器hotel/booking/user才是功能模块。Django 的INSTALLED_APPS必须显式注册每个 App否则模型、视图、URL 都不生效。别漏掉userApp——Django 默认 User 模型缺手机号、头像字段必须继承AbstractUser自定义。接着在settings.py中注册 App 并配置数据库毕业设计用 SQLite 足够但务必理解切换 PostgreSQL 的路径# settings.py INSTALLED_APPS [ django.contrib.admin, django.contrib.auth, django.contrib.contenttypes, django.contrib.sessions, django.contrib.messages, django.contrib.staticfiles, # 你的 App顺序重要靠前的 App 可被靠后的覆盖 user, hotel, booking, ] # 数据库配置SQLite 开箱即用无需额外安装 DATABASES { default: { ENGINE: django.db.backends.sqlite3, NAME: BASE_DIR / db.sqlite3, # 文件路径Django 自动创建 } }2.2 Vue 前端工程初始化与代理配置解决跨域痛点很多同学卡在“Vue 页面调 Django API 返回 500”——根本原因是开发时 Vue 运行在http://localhost:8080Django 在http://localhost:8000浏览器同源策略拦截请求。不推荐用 CORS 库硬配易留安全隐患而是用 Vue CLI 的 devServer 代理# 在 Vue 项目根目录非 Django 目录执行 npm create vuelatest # 一路回车选默认TypeScript? No, Router? Yes, Pinia? No, ESLint? No cd frontend # 假设 Vue 项目文件夹叫 frontend npm install关键一步在vue.config.js若不存在则新建中配置代理将/api/开头的请求转发到 Django// vue.config.js module.exports { devServer: { proxy: { /api: { target: http://localhost:8000, // Django 开发服务器地址 changeOrigin: true, // 修改 Origin 头避免后端拒绝 pathRewrite: { ^/api: // 去掉 /api 前缀Django URL 无需带 /api } } } } }参数说明changeOrigin: true是必须项否则 Django 的ALLOWED_HOSTS会拒绝请求pathRewrite让前端 axios 请求axios.get(/api/hotels/)实际发到http://localhost:8000/hotels/避免 Django URLconf 冗余。这是毕业设计最稳妥的跨域解法比在 Django 中装django-cors-headers更轻量、更可控。2.3 Django REST Framework 接口快速输出让 Vue 真正拿到数据Vue 需要 JSONDjango 默认返回 HTML。用djangorestframeworkDRF封装数据是最主流做法pip install djangorestframework在settings.py中注册 DRFINSTALLED_APPS [rest_framework] REST_FRAMEWORK { DEFAULT_AUTHENTICATION_CLASSES: [ rest_framework.authentication.SessionAuthentication, # 兼容 Django 登录态 rest_framework.authentication.TokenAuthentication, # 后续可扩展 Token ], DEFAULT_PERMISSION_CLASSES: [ rest_framework.permissions.IsAuthenticatedOrReadOnly, # 列表页公开操作需登录 ], }以hotelApp 为例定义模型并暴露 API# hotel/models.py from django.db import models class RoomType(models.Model): name models.CharField(max_length50) # 标准间、豪华套房 price_per_night models.DecimalField(max_digits8, decimal_places2) capacity models.PositiveSmallIntegerField() # 可住几人 description models.TextField(blankTrue) class Room(models.Model): room_number models.CharField(max_length10, uniqueTrue) room_type models.ForeignKey(RoomType, on_deletemodels.CASCADE) status models.CharField( max_length10, choices[(available, 可预订), (occupied, 已入住), (maintenance, 维修中)], defaultavailable )# hotel/views.py from rest_framework import viewsets from .models import RoomType, Room from .serializers import RoomTypeSerializer, RoomSerializer class RoomTypeViewSet(viewsets.ReadOnlyModelViewSet): queryset RoomType.objects.all() serializer_class RoomTypeSerializer class RoomViewSet(viewsets.ReadOnlyModelViewSet): queryset Room.objects.filter(statusavailable) # 只返回可预订房间 serializer_class RoomSerializer# hotel/urls.py from django.urls import path, include from rest_framework.routers import DefaultRouter from . import views router DefaultRouter() router.register(rroom-types, views.RoomTypeViewSet) router.register(rrooms, views.RoomViewSet) urlpatterns [ path(, include(router.urls)), ]# hotel_booking/urls.py from django.contrib import admin from django.urls import path, include urlpatterns [ path(admin/, admin.site.urls), path(api/, include(hotel.urls)), # 所有 API 统一加 /api/ 前缀 ]逻辑说明ReadOnlyModelViewSet表示只允许 GET 请求查避免学生误删数据filter(statusavailable)是业务强约束——前端看到的“可预订房间”必须由后端过滤不能靠前端 JS 判断否则绕过页面也能订维修中的房间。这是毕业设计里最容易被忽略的安全细节。3. 核心业务闭环实现订房流程的原子性、幂等性与状态机设计3.1 订单创建用 Django Transaction 和 select_for_update 防超卖酒店系统最核心的“订房”动作本质是查房态 → 扣库存 → 生订单 → 发通知。四步必须原子执行否则并发时两个用户同时查到“可用”都去创建订单导致超卖。Django 的transaction.atomicselect_for_update是标准解法# booking/views.py from django.db import transaction from django.http import JsonResponse from django.views.decorators.csrf import csrf_exempt from django.utils import timezone from .models import Booking, Room from hotel.models import RoomType csrf_exempt def create_booking(request): if request.method ! POST: return JsonResponse({error: Method not allowed}, status405) try: data json.loads(request.body) room_id data.get(room_id) check_in timezone.datetime.fromisoformat(data[check_in]) check_out timezone.datetime.fromisoformat(data[check_out]) with transaction.atomic(): # 关键锁定指定房间其他事务必须等待 room Room.objects.select_for_update().get(idroom_id, statusavailable) # 创建订单此时房间仍为 available但已被锁 booking Booking.objects.create( roomroom, userrequest.user, # 前提用户已登录Django Session 或 Token check_incheck_in, check_outcheck_out, total_priceroom.room_type.price_per_night * (check_out - check_in).days, statusconfirmed ) # 锁定成功后更新房间状态此时其他事务才能读到 occupied room.status occupied room.save() return JsonResponse({success: True, booking_id: booking.id}) except Room.DoesNotExist: return JsonResponse({error: Room not available}, status400) except Exception as e: return JsonResponse({error: str(e)}, status400)参数说明select_for_update()在数据库层面加行锁阻塞其他事务对同一行的写操作transaction.atomic()确保锁在事务结束时释放无论成功或异常。切记不要在锁外查房态再锁——查和锁之间存在时间窗口必须查即锁。这是毕业答辩时老师最爱问的并发问题代码必须体现这个意识。3.2 Vue 前端调用订房接口与错误处理前端不能只写axios.post(/api/bookings/)就完事。真实场景下用户可能网络中断、房间秒没、服务器超时必须反馈明确!-- BookingForm.vue -- template form submit.preventsubmitBooking select v-modelselectedRoomId option v-forroom in availableRooms :keyroom.id {{ room.room_number }} - {{ room.room_type.name }} /option /select input typedate v-modelcheckIn / input typedate v-modelcheckOut / button typesubmit :disabledisSubmitting {{ isSubmitting ? 正在预订... : 确认预订 }} /button div v-iferror classerror{{ error }}/div /form /template script import axios from axios export default { data() { return { availableRooms: [], selectedRoomId: , checkIn: , checkOut: , isSubmitting: false, error: } }, async mounted() { try { const res await axios.get(/api/rooms/) this.availableRooms res.data // 设置默认入住日期为明天 const tomorrow new Date() tomorrow.setDate(tomorrow.getDate() 1) this.checkIn tomorrow.toISOString().split(T)[0] this.checkOut new Date(tomorrow.getTime() 24*60*60*1000).toISOString().split(T)[0] } catch (e) { this.error 加载房间失败请刷新页面 } }, methods: { async submitBooking() { this.isSubmitting true this.error try { const res await axios.post(/api/bookings/, { room_id: this.selectedRoomId, check_in: this.checkIn, check_out: this.checkOut }) alert(预订成功订单号${res.data.booking_id}) this.$router.push(/my-bookings) } catch (e) { if (e.response?.status 400) { this.error e.response.data.error || 预订失败请重试 } else { this.error 网络错误请检查连接 } } finally { this.isSubmitting false } } } } /script关键点mounted中预加载房间列表避免用户点击时才请求submitBooking中try/catch捕获 400业务错误如房间不可用和网络错误分别提示按钮禁用防止重复提交。这些细节决定答辩演示是否丝滑。3.3 订单状态机用 Django Model Field Choices 管理生命周期订单不是只有“已创建”和“已完成”它有完整生命周期confirmed已确认→checked_in已入住→checked_out已离店→cancelled已取消。用choices字段强制约束状态流转比字符串自由赋值更可靠# booking/models.py class Booking(models.Model): STATUS_CHOICES [ (confirmed, 已确认), (checked_in, 已入住), (checked_out, 已离店), (cancelled, 已取消), ] room models.ForeignKey(Room, on_deletemodels.CASCADE) user models.ForeignKey(settings.AUTH_USER_MODEL, on_deletemodels.CASCADE) check_in models.DateTimeField() check_out models.DateTimeField() total_price models.DecimalField(max_digits10, decimal_places2) status models.CharField(max_length20, choicesSTATUS_CHOICES, defaultconfirmed) created_at models.DateTimeField(auto_now_addTrue) updated_at models.DateTimeField(auto_nowTrue) def can_cancel(self): 入住前 24 小时可取消 return self.status confirmed and \ (timezone.now() - self.created_at) timezone.timedelta(hours24) def cancel(self): if not self.can_cancel(): raise PermissionError(超过取消时限) self.status cancelled self.room.status available # 释放房间 self.room.save() self.save()逻辑说明can_cancel()方法封装业务规则避免前端传参绕过校验cancel()方法内完成状态变更与关联数据更新释放房间保证一致性。毕业设计中这种“状态驱动”的设计比“if-else 判断状态”更易维护、更易测试。4. 避坑指南毕业设计高频翻车点与血泪解决方案4.1 现象Django Admin 后台无法登录报错 “Please enter the correct username and password”原因新建项目后未运行python manage.py createsuperuser创建管理员账号直接访问/admin或密码含特殊字符如,$在终端输入时被 shell 解析实际创建的密码与预期不符或settings.py中AUTH_PASSWORD_VALIDATORS启用强密码策略但创建 superuser 时未满足如要求含数字字母符号。解决确保在项目根目录执行python manage.py createsuperuser全程用键盘输入不要复制粘贴密码若已创建但密码遗忘用python manage.py changepassword username重置临时关闭密码验证仅限开发环境注释settings.py中AUTH_PASSWORD_VALIDATORS配置或改为[]。4.2 现象Vue 页面空白控制台报错 “Failed to resolve component: router-view”原因Vue Router 版本 mismatchVue 3 默认用vue-router4但create-vue脚手架可能生成vue-router3配置或main.js中未正确app.use(router)或路由文件router/index.js导出的createRouter实例未被正确引入。解决检查package.json中vue-router版本是否为^4.x确认main.js包含import { createApp } from vue import { createRouter } from vue-router // 注意是 createRouter非 VueRouter import App from ./App.vue import router from ./router const app createApp(App) app.use(router) // 必须调用 app.mount(#app)router/index.js必须导出createRouter实例import { createRouter, createWebHistory } from vue-router export default createRouter({ history: createWebHistory(), routes: [/* ... */] })4.3 现象Django 迁移时报错 “no module named hotel.apps.HotelConfig”原因apps.py文件中name hotel与INSTALLED_APPS中注册的hotel不一致或apps.py未被__init__.py导入Django 找不到 AppConfig 类或INSTALLED_APPS中写了hotel.apps.HotelConfig但apps.py里类名不是HotelConfig。解决确保hotel/apps.py内容为from django.apps import AppConfig class HotelConfig(AppConfig): default_auto_field django.db.models.BigAutoField name hotel # 必须与 App 文件夹名完全一致小写hotel/__init__.py中添加default_app_config hotel.apps.HotelConfigsettings.py中INSTALLED_APPS写hotel即可不要写hotel.apps.HotelConfig除非你明确需要自定义 AppConfig。4.4 现象Vue 打包后静态资源 404Django 返回 “Page not found at /static/js/app.xxx.js”原因vue.config.js中publicPath未设为/导致打包后 JS/CSS 路径指向根目录但 Django 未配置 static 服务或settings.py中STATIC_URL /static/但STATIC_ROOT未指向 Vue 打包输出目录如dist/static或未运行python manage.py collectstatic收集静态文件。解决vue.config.js中设置module.exports { outputDir: ../hotel_booking/staticfiles, // 打包输出到 Django staticfiles 目录 publicPath: /static/ // 确保资源 URL 以 /static/ 开头 }settings.py中STATIC_URL /static/ STATICFILES_DIRS [BASE_DIR / static] # 开发时静态文件位置 STATIC_ROOT BASE_DIR / staticfiles # collectstatic 输出目录必须与 vue.config.js 一致打包并收集cd frontend npm run build cd .. python manage.py collectstatic --noinput注意collectstatic会把frontend/dist/static下所有文件拷贝到hotel_booking/staticfilesDjango 的runserver才能 serve。4.5 现象Django 模板中{% url hotel:room-list %}报错 “NoReverseMatch”原因urls.py中未给 URL pattern 命名nameroom-list或命名空间app_name hotel未在主urls.py中声明namespacehotel或模板中{% load static %}未加{% load url %}虽通常默认加载但显式声明更稳妥。解决hotel/urls.py顶部加app_name hotel # 必须与 namespace 一致 urlpatterns [ path(rooms/, views.RoomListView.as_view(), nameroom-list), ]主urls.py中path(hotel/, include(hotel.urls, namespacehotel)),模板中{% load static %} a href{% url hotel:room-list %}查看房间/a5. 毕业答辩加分技巧用 Django Admin 定制报表、用 Vue 动态图表呈现业务价值5.1 Django Admin 高级定制让老师一眼看懂你的系统价值答辩时老师不会逐行看代码但会点开 Admin 后台看数据。把 Admin 做成“业务仪表盘”比写 100 行文档更有说服力# hotel/admin.py from django.contrib import admin from django.db.models import Count, Sum, Avg from .models import RoomType, Room, Booking admin.register(RoomType) class RoomTypeAdmin(admin.ModelAdmin): list_display [name, price_per_night, capacity, total_rooms, booked_count] list_filter [capacity] def total_rooms(self, obj): return obj.room_set.count() total_rooms.short_description 房间总数 def booked_count(self, obj): return obj.room_set.filter(statusoccupied).count() booked_count.short_description 已入住数 admin.register(Booking) class BookingAdmin(admin.ModelAdmin): list_display [id, room, user, check_in, check_out, total_price, status] list_filter [status, check_in] search_fields [user__username, room__room_number] date_hierarchy check_in # 添加自定义统计 Action actions [export_to_excel] def export_to_excel(self, request, queryset): # 此处集成 openpyxl 或 pandas 导出略重点在思路 self.message_user(request, f已导出 {queryset.count()} 条订单) export_to_excel.short_description 导出选中订单为 Excel技巧说明list_display展示业务关键字段list_filter和date_hierarchy让老师能快速筛选“某天的订单”actions提供一键导出体现系统实用性。答辩时主动说“老师您可以在后台直接导出本月订单 Excel我做了自动计算入住率的功能”——这就是加分项。5.2 Vue 集成 ECharts用折线图展示“近 30 天入住率趋势”纯表格数据枯燥图表直观。ECharts 是 Vue 生态最成熟的图表库且支持 SSR服务端渲染答辩演示更流畅cd frontend npm install echarts!-- Dashboard.vue -- template div refchartRef stylewidth: 100%; height: 400px;/div /template script import * as echarts from echarts export default { name: Dashboard, data() { return { chart: null, chartData: [] } }, async mounted() { await this.loadChartData() this.initChart() }, beforeUnmount() { if (this.chart) this.chart.dispose() }, methods: { async loadChartData() { try { const res await axios.get(/api/reports/occupancy-rate/) this.chartData res.data // 格式[{date: 2024-01-01, rate: 75.2}, ...] } catch (e) { console.error(加载图表数据失败, e) } }, initChart() { this.chart echarts.init(this.$refs.chartRef) this.chart.setOption({ title: { text: 近30天客房入住率趋势 }, tooltip: { trigger: axis }, xAxis: { type: category, data: this.chartData.map(d d.date) }, yAxis: { type: value, name: 入住率 (%), max: 100 }, series: [{ name: 入住率, type: line, data: this.chartData.map(d d.rate), smooth: true, areaStyle: {} // 填充区域 }] }) } } } /script关键点beforeUnmount中dispose()防止内存泄漏smooth: true让折线更美观areaStyle填充区域增强视觉效果。答辩时指着图表说“系统自动统计每日入住率老师您看周末明显高于工作日——这说明我们的定价策略和营销活动是有效的”——瞬间把技术实现升维到业务分析。5.3 最后一个习惯用 Git 分支管理答辩版本与代码洁癖毕业设计代码不是写完就扔答辩后可能还要修改、补材料。我坚持三个分支习惯main稳定可运行版本每次答辩前git tag v1.0打标签dev日常开发新功能、修复都在此docs存放README.md、数据库 ER 图draw.io 导出 PNG、API 文档Swagger UI 截图、答辩 PPTPDF。每次提交前必做git status确认无意外修改git add -p交互式添加避免误提交.env或db.sqlite3提交信息写清楚“feat(booking): 添加订单取消逻辑修复超卖漏洞”。血泪经验答辩前夜发现settings.py里DEBUGTrue和SECRET_KEY明文提交到 GitHub紧急git reset --hard回退 git push --force—— 从此养成pre-commit钩子检查敏感词。希望帮到你。本文还有配套的精品资源点击获取
返回列表