尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Django+Vue景点酒店预订系统实战:前后端分离与事务一致性

Django+Vue景点酒店预订系统实战:前后端分离与事务一致性 简介这是一套面向计算机专业本科生的毕业设计级旅游系统实战项目聚焦景点酒店预订与游记分享场景适用于DjangoVue全栈开发学习与课程设计参考。资源包含完整前后端源码、MySQL数据库脚本、配套毕业论文及实操视频教程覆盖需求分析、系统设计、编码实现到部署演示全流程。压缩包共817个文件71.59MB其中Python后端逻辑57个.py、Vue前端组件49个.vue、静态资源162个.svg/79个.gif/51个.jpg和样式脚本55个.css/166个.js构成主体辅以.bat运行脚本、.sql建库文件及.bak备份模板结构清晰便于分层学习与调试。已有68人下载学习读者可直接部署运行深入理解角色权限控制管理员/用户双端、景点购票与酒店预定核心业务流、游记UGC模块实现以及前后端分离架构下的接口联调与数据安全处理方案。1. 用 PythonDjangoVueMySQL 搭建景点酒店预订系统不是拼凑技术栈而是理清前后端分离的职责边界一个毕业设计项目叫“景点酒店预订旅游系统”光看标题容易误以为是套模板——Python 写后端、Vue 做页面、MySQL 存数据三件套一装就完事。但实际落地时90% 的学生卡在「为什么登录成功却拿不到用户信息」「Vue 调接口总返回 403」「Django Admin 里改了酒店库存前端页面不刷新」这类问题上。根本原因不是不会写代码而是没厘清前后端分离中每个环节的契约关系Django 不只是吐 JSON它要定义清晰的 API 协议、状态码语义、CSRF 策略和跨域规则Vue 不只是发 axios 请求它得管理 token 生命周期、路由守卫拦截、错误提示粒度MySQL 更不只是建几张表它的索引设计直接影响搜索景点响应速度外键约束决定订单与房间状态的一致性。这个系统真正考验的是对 Web 开发链路的闭环理解——从用户点击“预订”那一刻起请求如何穿过 Nginx或开发服务器、Django 中间件、ORM 查询、JSON 序列化再经 Vue 的 Promise 链、Vuex/Pinia 状态更新、组件 re-render 最终呈现。适合正在做毕设、想把项目跑通且能讲清楚每层逻辑的开发者尤其需要避开“照着教程复制粘贴却不知为何而设”的陷阱。2. Django 后端从零初始化 API 服务重点解决认证、跨域与数据一致性2.1 初始化 Django 项目并配置 MySQL 连接新建虚拟环境后安装核心依赖python -m venv venv source venv/bin/activate # Linux/macOS # venv\Scripts\activate.bat # Windows pip install django djangorestframework django-cors-headers mysqlclient创建项目与应用django-admin startproject travel_backend . python manage.py startapp hotels python manage.py startapp bookings在settings.py中配置 MySQL注意mysqlclient需提前安装 C 编译工具链Windows 用户推荐用pymysql替代并在__init__.py中注册# travel_backend/settings.py DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: travel_db, USER: root, PASSWORD: your_password, HOST: 127.0.0.1, PORT: 3306, OPTIONS: { charset: utf8mb4, init_command: SET sql_modeSTRICT_TRANS_TABLES, }, } }提示utf8mb4是必须项否则景点名称含 emoji如 ⛰️会报错STRICT_TRANS_TABLES防止 MySQL 静默截断字段保障数据完整性。2.2 定义核心模型与外键约束逻辑hotels/models.py中建模需体现业务强约束from django.db import models from django.contrib.auth.models import User class Hotel(models.Model): name models.CharField(max_length100) address models.TextField() star_rating models.PositiveSmallIntegerField(choices[(i, f{i}星) for i in range(1, 6)]) # 每个酒店可关联多个景点多对多用中间表显式管理距离与描述 nearby_scenic_spots models.ManyToManyField(ScenicSpot, throughHotelScenicRelation) class RoomType(models.Model): hotel models.ForeignKey(Hotel, on_deletemodels.CASCADE, related_nameroom_types) name models.CharField(max_length50) # 如“豪华大床房” price_per_night models.DecimalField(max_digits8, decimal_places2) total_count models.PositiveIntegerField() # 总房间数 available_count models.PositiveIntegerField() # 可预订数关键避免超卖 class Booking(models.Model): user models.ForeignKey(User, on_deletemodels.CASCADE) room_type models.ForeignKey(RoomType, on_deletemodels.PROTECT) # PROTECT 防止删除在订房型 check_in models.DateField() check_out models.DateField() status models.CharField(max_length20, choices[ (pending, 待确认), (confirmed, 已确认), (cancelled, 已取消), ], defaultpending)RoomType.available_count是库存核心字段不能靠应用层计算必须由数据库事务保证原子性。后续下单逻辑将用SELECT ... FOR UPDATE锁行实现。2.3 构建 RESTful API 并处理跨域与认证安装django-cors-headers解决开发阶段跨域# settings.py INSTALLED_APPS [corsheaders] MIDDLEWARE.insert(0, corsheaders.middleware.CorsMiddleware) CORS_ALLOWED_ORIGINS [ http://localhost:8080, # Vue 开发服务器地址 ] # 生产环境必须禁用 CORS_ALLOW_ALL_ORIGINSTrue用 DRF 实现景点搜索 API带分页与模糊匹配# hotels/views.py from rest_framework import generics, status from rest_framework.response import Response from .models import ScenicSpot from .serializers import ScenicSpotSerializer class ScenicSpotSearchView(generics.ListAPIView): serializer_class ScenicSpotSerializer pagination_class PageNumberPagination def get_queryset(self): query self.request.query_params.get(q, ).strip() if not query: return ScenicSpot.objects.none() # 使用 MySQL 全文索引需先建索引ALTER TABLE hotels_scenicspot ADD FULLTEXT(name, description); return ScenicSpot.objects.filter( models.Q(name__icontainsquery) | models.Q(description__icontainsquery) ).order_by(-popularity_score)注意icontains在大数据量下性能差上线前必须为name和description字段添加复合索引CREATE INDEX idx_spot_search ON hotels_scenicspot (name, description);。全文索引仅当查询词长度 4 且需相关性排序时启用。2.4 实现安全的预订流程事务锁与状态机bookings/views.py中的关键逻辑from django.db import transaction from django.db.models import F from rest_framework.views import APIView class BookingCreateView(APIView): def post(self, request): room_type_id request.data.get(room_type_id) check_in request.data.get(check_in) check_out request.data.get(check_out) try: with transaction.atomic(): # 锁定该房型记录防止并发超卖 room RoomType.objects.select_for_update().get(idroom_type_id) if room.available_count 0: return Response({error: 房间已售罄}, statusstatus.HTTP_400_BAD_REQUEST) # 创建订单此时库存未减 booking Booking.objects.create( userrequest.user, room_typeroom, check_incheck_in, check_outcheck_out, statuspending ) # 原子性扣减库存 RoomType.objects.filter(idroom_type_id).update( available_countF(available_count) - 1 ) return Response({booking_id: booking.id}, statusstatus.HTTP_201_CREATED) except RoomType.DoesNotExist: return Response({error: 房型不存在}, statusstatus.HTTP_404_NOT_FOUND) except Exception as e: return Response({error: str(e)}, statusstatus.HTTP_400_BAD_REQUEST)此实现确保即使 100 个用户同时点击“预订同一房型”数据库层面只允许一人成功其余返回 400。select_for_update()在 MySQL 中生成SELECT ... FOR UPDATE语句锁住对应行直至事务结束。3. Vue 前端构建可维护的预订交互流重点处理 token 管理与状态同步3.1 初始化 Vue 3 项目并配置 Axios 拦截器使用 Vite 创建项目npm create vitelatest travel-frontend -- --template vue cd travel-frontend npm install npm install axios pinia vue/router在src/utils/request.js中封装带认证的请求import axios from axios const api axios.create({ baseURL: http://127.0.0.1:8000/api/, // Django 开发服务器地址 timeout: 10000, }) // 请求拦截器自动携带 token api.interceptors.request.use( config { const token localStorage.getItem(auth_token) if (token) { config.headers.Authorization Token ${token} } return config }, error Promise.reject(error) ) // 响应拦截器统一处理 401 未授权 api.interceptors.response.use( response response, error { if (error.response?.status 401) { localStorage.removeItem(auth_token) // 跳转登录页保留原路径用于登录后重定向 window.location.href /login?redirect${encodeURIComponent(window.location.pathname)} } return Promise.reject(error) } ) export default api提示Django REST Framework 默认使用 TokenAuthentication需在settings.py中启用REST_FRAMEWORK {DEFAULT_AUTHENTICATION_CLASSES: [rest_framework.authentication.TokenAuthentication]}。Vue 端登录成功后务必将response.data.token存入localStorage而非sessionStorage后者关闭标签页即丢失导致用户反复登录。3.2 实现景点搜索与酒店详情页的响应式交互src/views/ScenicSearch.vue中使用 Composition APItemplate div classsearch-container input v-modelsearchQuery keyup.enterperformSearch placeholder搜索景点名称或描述... classsearch-input / button clickperformSearch classsearch-btn搜索/button div v-ifloading classloading加载中.../div div v-else-iferror classerror{{ error }}/div div v-else classresults div v-forspot in spots :keyspot.id classspot-card h3{{ spot.name }}/h3 p{{ spot.description.substring(0, 100) }}.../p router-link :to/hotels?scenic_id${spot.id} classview-hotels 查看附近酒店 /router-link /div /div /div /template script setup import { ref, onMounted } from vue import api from /utils/request const searchQuery ref() const spots ref([]) const loading ref(false) const error ref() const performSearch async () { if (!searchQuery.value.trim()) return loading.value true error.value try { const res await api.get(scenic-spots/search/, { params: { q: searchQuery.value } }) spots.value res.data.results } catch (e) { error.value e.response?.data?.error || 搜索失败请重试 } finally { loading.value false } } onMounted(() { // 页面加载时默认搜索热门景点 searchQuery.value 西湖 performSearch() }) /script此组件关键点keyup.enter触发搜索符合用户预期router-link传递scenic_id参数供酒店列表页过滤onMounted中预加载热门景点提升首屏体验错误提示明确到具体原因如后端返回的error字段而非笼统“网络错误”。3.3 订单提交与库存实时反馈src/views/BookingForm.vue中处理预订template form submit.preventsubmitBooking classbooking-form div classform-group label入住日期/label input typedate v-modelformData.check_in required / /div div classform-group label离店日期/label input typedate v-modelformData.check_out required / /div div classform-group label房型/label select v-modelformData.room_type_id required option value请选择/option option v-forroom in availableRooms :keyroom.id :valueroom.id {{ room.name }} - ¥{{ room.price_per_night }}/晚 (剩余{{ room.available_count }}间) /option /select /div button typesubmit :disabledisSubmitting {{ isSubmitting ? 提交中... : 立即预订 }} /button /form /template script setup import { ref, computed } from vue import api from /utils/request const props defineProps({ hotelId: { type: Number, required: true } }) const formData ref({ room_type_id: , check_in: , check_out: }) const isSubmitting ref(false) // 动态获取当前酒店可用房型含实时库存 const availableRooms computed(async () { try { const res await api.get(hotels/${props.hotelId}/rooms/) return res.data } catch (e) { console.error(获取房型失败, e) return [] } }) const submitBooking async () { isSubmitting.value true try { const res await api.post(bookings/, formData.value) alert(预订成功订单号${res.data.booking_id}) // 清空表单并跳转订单页 formData.value { room_type_id: , check_in: , check_out: } } catch (e) { alert(e.response?.data?.error || 预订失败请检查输入) } finally { isSubmitting.value false } } /script注意availableRooms使用computed而非onMounted一次性获取确保用户切换日期时库存数字实时更新。但需警惕频繁请求生产环境应在后端加缓存如 Redis 缓存 30 秒。4. 数据库优化与部署验证让系统在真实场景下稳定运行4.1 MySQL 关键索引与查询性能压测针对高频查询场景建立索引-- 景点搜索加速 name 和 description 的 LIKE 查询 CREATE INDEX idx_scenic_name_desc ON hotels_scenicspot (name, description); -- 预订查询按用户查历史订单常用 CREATE INDEX idx_booking_user_status ON bookings_booking (user_id, status); -- 房型库存按酒店查可用房型列表页核心 CREATE INDEX idx_room_hotel_avail ON hotels_roomtype (hotel_id, available_count); -- 时间范围查询查某日期区间内可订房型搜索页关键 CREATE INDEX idx_room_date ON bookings_booking (room_type_id, check_in, check_out);使用EXPLAIN验证查询计划EXPLAIN SELECT * FROM hotels_roomtype WHERE hotel_id 123 AND available_count 0; -- 确保 typeref, keyidx_room_hotel_avail, rows 较小提示避免在WHERE子句中对字段使用函数如DATE(check_in)会导致索引失效。Django ORM 中应使用__date查询Booking.objects.filter(check_in__datetoday)。4.2 Django Vue 本地联调排错清单现象检查点快速验证命令Vue 页面空白控制台报Failed to load resource: net::ERR_CONNECTION_REFUSEDDjango 开发服务器是否启动端口是否被占用lsof -i :8000macOS/Linux或netstat -ano | findstr :8000Windows登录成功但后续请求 401Token 是否正确存入 localStorageDjango 的TokenAuthentication是否启用浏览器开发者工具 → Application → Local Storage 查看auth_token检查settings.py中REST_FRAMEWORK配置搜索景点返回空数组MySQL 数据库是否有测试数据ScenicSpot表是否为空mysql -u root -p -e SELECT COUNT(*) FROM hotels_scenicspot; travel_db预订时提示“房间已售罄”但后台available_count明显大于 0是否漏掉select_for_update()并发测试是否复现在 Django shell 中手动执行RoomType.objects.select_for_update().get(id1)观察是否阻塞4.3 生产环境最小化部署方案Django 生产部署不建议直接用runserver推荐gunicornnginx# 安装 gunicorn pip install gunicorn # 启动 gunicorn监听 8000 端口4 个工作进程 gunicorn travel_backend.wsgi:application --bind 127.0.0.1:8000 --workers 4 --timeout 120 # nginx 配置片段/etc/nginx/sites-enabled/travel upstream django_app { server 127.0.0.1:8000; } server { listen 80; server_name your-domain.com; location /static/ { alias /path/to/your/staticfiles/; } location / { proxy_pass http://django_app; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }Vue 打包后部署# 在 Vue 项目根目录执行 npm run build # 输出文件在 dist/ 目录将其内容复制到 nginx 的 root 目录如 /var/www/travel-frontend注意Vue 路由若用history模式需在 nginx 中配置try_files $uri $uri/ /index.html;否则刷新页面会 404。5. 毕设答辩高价值技巧用三个可视化证据证明系统可靠性5.1 展示数据库事务一致性证据在答辩 PPT 中插入一张 MySQL 命令行截图显示并发预订测试结果-- 模拟两个用户同时预订同一房型ID5 -- 用户A执行 START TRANSACTION; SELECT available_count FROM hotels_roomtype WHERE id5 FOR UPDATE; UPDATE hotels_roomtype SET available_count available_count - 1 WHERE id5; COMMIT; -- 用户B在同一时刻执行相同语句观察其等待时间及最终结果 -- 截图显示用户A成功用户B的 UPDATE 等待 3 秒后执行available_count 准确减为 0 SELECT id, available_count FROM hotels_roomtype WHERE id5; -- 输出5 | 0此证据直接回应“如何防止超卖”这一高频问题比口头解释“用了事务”更有说服力。5.2 展示前端请求链路追踪在 Chrome 开发者工具 Network 标签页中筛选XHR请求截取一个完整预订流程POST /api/bookings/请求头含Authorization: Token xxx响应状态码201 Created响应体含{booking_id: 123}紧接着GET /api/user/profile/自动触发因路由守卫重定向验证 token 有效性。标注箭头说明Token 由登录接口返回 → 存入 localStorage → Axios 拦截器自动注入 → Django 后端验证通过 → 返回用户数据。这串证据链证明“前后端分离的认证机制已闭环”。5.3 展示性能优化对比数据用 Apache Bench 工具压测搜索接口# 优化前无索引 ab -n 100 -c 10 http://127.0.0.1:8000/api/scenic-spots/search/?q西湖 # 优化后加索引 ab -n 100 -c 10 http://127.0.0.1:8000/api/scenic-spots/search/?q西湖制作对比表格指标优化前优化后提升Requests per second12.389.7629%Time per request (ms)813111-86%90% percentile (ms)1200180-85%表格下方注明“索引使高并发搜索响应进入亚秒级满足旅游平台用户对实时性的要求”。数据比“做了优化”更有力。本文还有配套的精品资源点击获取
返回列表