做课程设计的时候很多人纠结“python的餐饮预订系统的设计与实现”到底该从哪下手。前后端分离、Django 写接口、Vue 做页面、PyCharm 做开发环境这个组合本身不复杂真正难的是把预订业务想清楚、把表设计好、把联调跑通。这篇文章我按自己做这类项目的习惯把这个系统从需求拆分、数据库设计、后端实现、前端联调到踩坑实录完整过一遍给正在写这类项目的同学一个可以直接照做、不用绕弯的思路。先说清楚这是个什么东西。餐饮预订系统说白了就是让顾客在小程序或网页上看到餐厅的菜品、选一个能坐下的桌台、挑一个还没被占的时间段完成预订到店后直接就餐。作为课程设计它天然包含了 Web 开发里最核心的几个环节用户认证、数据建模、业务规则冲突检测、接口设计和交互界面。用 Django Vue 做这个题目还有一个额外的好处前后端接口你都得自己定义能很自然地把 RESTful 风格、状态码设计、接口文档这些东西全部练到。这篇文章适合谁看适合拿这个题目做课程设计、毕业设计的同学也适合想快速了解 Django Vue 前后端分离项目怎么搭的人。我不打算写成一个功能清单式的需求和设计文档那玩意儿答辩一过就扔了。我尽量写成一套你打开电脑就能跟着动手的东西顺带告诉你哪些地方容易返工。1. 先从业务出发餐饮预订系统到底要做什么很多人在动手写代码之前就卡住了不是不会写是脑子里没业务。餐饮预订不是一个“增删改查”四个字能盖住的 CRUD订餐的核心是一个桌台在同一个时间段不能被预订两次这个业务规则决定了你所有表结构和接口设计的走向。所以我不建议一上来就建项目先花半小时把功能边界画清楚。1.1 核心需求与功能边界用户端是最先要明确的部分。顾客注册登录之后能看菜品列表、按分类筛选菜品、浏览可选桌台、选择日期和时间段提交预订、查看自己的预订记录、取消还没有开始的预订。就这么六件事不能再多了。我经常看到有人在这个阶段给自己加需求什么会员等级、积分商城、菜品评价系统全都塞进去。作为课程设计这是典型的给自己挖坑业务复杂度上去了你的表、接口、页面全都要跟着膨胀答辩老师还不一定买账。管理端再补上三件事菜品管理上架、下架、改价、桌台管理增删、设置桌台人数、订单/预订处理确认、取消、标记完成。如果时间充裕可以再加一个简单的营业统计比如今日预订量、热门菜品排名这就能把“设计”和“实现”的层次拉开但别一开始就奔着大而全去。做了这个裁剪之后系统的边界很清晰前端两个端顾客端 管理端后端一套接口一个数据库。功能边界画好了下面所有的表、接口、页面都是围绕这几件事展开。1.2 技术选型为什么是 Django Vue PyCharm先说 Django。它自带 Admin 后台自带 ORM自带迁移机制自带的用户认证体系可以让你少写一大半登录注册的逻辑。这不是偷懒是合理复用。尤其是你这个项目里面有管理端的场景Django Admin 可以直接当半个后台用你只需要把菜品、桌台、预订这几个模型注册进去管理端的增删改查功能就有了。剩下的精力可以全部放到预订接口和前端页面上。Vue 那一侧顾客体验和后台管理的交互都靠它。选 Vue 不是因为 Vue 本身比别的框架强而是它的中文资料多、上手快对做课程设计的人来说遇到问题你能在社区里快速找到答案。Vue 2 还是 Vue 3 的问题我的建议是能上 Vue 3 就上 Vue 3配合 Vite 用起来比旧方案轻快不少不过如果你手头的模板、教程是 Vue 2 的用 Vue 2 也完全不影响答辩关键是保证前后端能正常跑通。PyCharm 在这里承担的是开发环境管理者的角色。它内置虚拟环境管理一个项目一个 Python 解释器不会出现 A 项目的依赖把 B 项目冲掉的情况。它还能直接配置 Django 的运行方式按个调试按钮就能在断点处停下来看请求进到哪一行了这对排查接口问题来说价值非常大。很多人写 Python Web 项目喜欢用轻量编辑器但在这个项目里 PyCharm 对你的帮助会超过你为它消耗的内存。2. 数据库设计与预订业务规则系统能不能跑起来看接口接口做得好不好看表设计。餐饮预订系统的数据库设计本身不复杂核心就是你要能把“谁、在什么时间、订了哪个桌、什么状态”这件事完整地记录下来并且能回答“某个时间点还有哪些桌子能订”这个问题。2.1 核心表与字段设计按业务拆分数据库里至少要有这么几张表用户表、菜品分类表、菜品表、桌台表、预订表。用户表可以继承 Django 自带的用户模型然后扩展一个手机号和昵称就够了不用重新造轮子。菜品分类表和菜品表是典型的一对多关系。分类表只需要name菜品表需要name、price、image、category外键指向分类、description、is_available是否在售。这里有一个容易忽略的点价格字段不要用FloatField用DecimalField(max_digits8, decimal_places2)因为浮点数在 Python 和数据库里的精度问题会给你带来莫名其妙的金额误差。桌台表我建议这样设计name比如 A1、A2或者“靠窗 2 人桌”“包间 8 人桌”、seat_count可坐人数、is_active是否启用。这里不要给桌台加“空闲/繁忙”字段因为同一个桌子在不同时间段的忙闲状态是完全不一样的把状态画到桌台表上会造成严重的数据冗余和逻辑冲突。预订表是整个系统最核心的表字段大致如下字段名类型说明userFK 用户表谁预订的tableFK 桌台表订了哪张桌reservation_dateDateField预订日期reservation_timeTimeField到店时间段比如 18:00number_of_guestsIntegerField实际用餐人数statusCharField状态待确认 / 已确认 / 已取消 / 已完成remarkTextField顾客备注比如“靠窗”created_atDateTimeField下单时间status这个字段做选择辅助功能建议把它做成一个状态机来管理。无论你怎么设计都不要允许用户直接改状态所有状态变更必须通过接口或管理端操作。2.2 预订冲突检测与状态流转这是整个系统最核心的业务规则。顾客提交一个预订请求后端要判断“这张桌子在当天这个时间段是不是已经被订了”。判断逻辑用 ORM 表达就是一个条件查询from django.utils import timezone from .models import Reservation def table_is_available(table, date, time): conflict Reservation.objects.filter( tabletable, reservation_datedate, reservation_timetime, status__in(pending, confirmed), ).exists() return not conflict这里的status__in很关键。已取消的预订不能占位已完成的预订因为时间已经过去理论上也不会冲突但如果你把历史数据也放出来过滤会白白增加检索压力。过滤状态本身就是业务逻辑的一部分别图省事只查日期和桌台。状态流转建议这样定义创建预订之后默认是pending待确认管理端在后台确认后变为confirmed已确认顾客到店后由管理端标记为completed已完成顾客在未到店前可以取消状态变为cancelled已取消。不要搞什么自动取消超时未到的预订这种机制除非你很熟悉定时任务和消息队列否则这个设计会让系统复杂度陡增而且很容易在答辩时被追问到答不上来。2.3 可用桌台查询不是简单的“过滤已订”前端展示可选桌台的时候不能用“所有桌子减去已经订掉的桌子”这个思路。你还要考虑人数匹配一个 2 人桌没法承接 6 人的预订一个包间能坐 8 人但只有 2 个人来也属于资源浪费。所以可用桌台的查询逻辑应该是“启用的桌子、座位数满足要求、且在所选时间段没有被预订”# 前端传入了日期 date、时间 time、人数 guests taken_table_ids Reservation.objects.filter( reservation_datedate, reservation_timetime, status__in(pending, confirmed), ).values_list(table_id, flatTrue) available_tables Table.objects.filter( is_activeTrue, seat_count__gteguests, ).exclude( id__inlist(taken_table_ids) )这个查询还有一个隐藏细节seat_count__gte表示“至少能坐下”但你也可以反过来让顾客选“刚好坐下”的桌子那样就改成seat_count__exact。两种规则没有对错我建议用“至少能坐下”因为顾客的实际用餐人数往往比预订人数少桌台大一点反而容错高用户体验更好。前端拿到可用桌台之后推荐把桌台按座位数分组展示比如“2 人桌”“4 人桌”“8 人桌”这样顾客在下单页的挑选成本最低。这个体验细节在答辩演示的时候很加分。3. 后端接口实现与前端联调全过程业务和表定下来之后整个项目的骨架就出来了。下面这部分是实操流程从创建 Django 项目到 Vue 页面拿到真实数据我尽量按顺序写你照着做即可。3.1 Django 后端骨架搭建先在 PyCharm 里新建项目选 Python 虚拟环境。虚拟环境这一点我多唠叨两句不要用全局 Python 装包不然你装到一半依赖冲突后面排查的时间够你写一整个模块。然后安装后端需要的包pip install django djangorestframework django-cors-headers接着创建项目和 Appdjango-admin startproject restaurant python manage.py startapp order python manage.py startapp management我习惯把顾客相关的接口放到一个 App管理端接口放到另一个 App虽然麻烦一点但职责清晰答辩时被问“你项目怎么组织的”你能说出个一二三来。不过如果你嫌折腾只用一个orderApp 把所有模型放进去也能跑不影响最终结果。在settings.py里把rest_framework和corsheaders加进INSTALLED_APPS数据库我用的是 SQLite。是的课程设计用 SQLite 完全足够你不需要为了这个项目单独装一个 MySQL 服务。SQLite 是文件型数据库免安装、免配置、好迁移答辩演示的时候拿着项目文件夹换个电脑就能跑这在课程设计里的价值比“用了 MySQL”这几个字重要得多。3.2 预订接口与冲突检测逻辑实现预订接口是最重要的一个接口我用 DRF 的 APIView 写逻辑最直观。先声明序列化器from rest_framework import serializers from .models import Reservation class ReservationCreateSerializer(serializers.ModelSerializer): class Meta: model Reservation fields [table, reservation_date, reservation_time, number_of_guests, remark] def validate(self, attrs): table attrs.get(table) date attrs.get(reservation_date) time attrs.get(reservation_time) if Reservation.objects.filter( tabletable, reservation_datedate, reservation_timetime, status__in(pending, confirmed), ).exists(): raise serializers.ValidationError(该桌台在所选时间段已被预订) return attrs注意这里我把冲突检测写进了序列化器的validate方法。序列化器的validate会在创建/更新前被 DRF 自动调用有异常就直接返回400给前端前端拿到这个错误提示就能展示“这个桌台已被订了”不需要你再写一堆 if-else 的异常处理。这就是 DRF 这套框架给你的结构性便利。视图这边把客户端的用户从 request 里取出来放进预订记录from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status, permissions class ReservationCreateView(APIView): permission_classes [permissions.IsAuthenticated] def post(self, request): serializer ReservationCreateSerializer(datarequest.data) if serializer.is_valid(): serializer.save(userrequest.user, statuspending) return Response({code: 0, message: 预订提交成功}, statusstatus.HTTP_201_CREATED) return Response({code: 1, message: serializer.errors}, statusstatus.HTTP_400_BAD_REQUEST)permission_classes [permissions.IsAuthenticated]这一行解决了登录校验的问题。没有登录的请求会直接被拒前端拿到401跳转到登录页即可。这就是 Django 自带认证体系的优势你不用手写 session不用手写 tokenDRF 会帮你把事情办了。3.3 Vue 前端页面与联调Vue 前端我建议拆成几个页面菜品列表页、订单创建页选桌台、选时间、我的预订页、登录注册页。如果你做管理端再单独加一个管理布局里面放菜品管理表格和订单处理表格。菜品列表页要调后端接口取数据我习惯把接口封装到独立的api模块里统一用axios请求import axios from axios const request axios.create({ baseURL: /api, timeout: 10000 }) export function getDishes(params) { return request.get(/dishes/, { params }) }开发阶段最省事的做法是通过 Vite 的proxy配置把/api代理到 Django 的地址而不是在代码里写死http://127.0.0.1:8000。这样既能避免跨域问题又能让你的前端代码在最终部署时不用改。配置文件里加这一段server: { proxy: { /api: { target: http://127.0.0.1:8000, changeOrigin: true } } }联调阶段最常见的问题是后端返回的数据结构和前端预期对不上。我建议前后端先定一个轻量级的约定成功返回{code: 0, data: ...}失败返回{code: 1, message: 错误说明}。不要直接用 DRF 默认的{detail: ...}格式虽然那样也能用但前后端各处理一套格式会让代码非常乱。约定统一之后前端写一个响应拦截器非0的 code 直接弹出报错提示整个联调过程会顺畅很多。3.4 PyCharm 环境配置要点PyCharm 对这个项目的支持做得很到位但要用好它先解决两个问题。第一是解释器配置。打开 Settings 里的 Python Interpreter确保它指向你创建的那个虚拟环境目录而不是全局环境。判断方法很简单看它显示的路径是不是venv或者.venv结尾。如果路径显示的是系统自带的 Python那说明虚拟环境没生效。第二是运行配置。在 PyCharm 右上角添加一个 Django Server 配置把 settings 模块指向你项目的settings.py然后就能直接点绿色按钮启动服务。这样启动的好处是日志输出集成在 IDE 里而且能直接在代码边上打断点调试一个请求进来你可以看到它在哪个 view 方法里停留哪一行数据取出来的值是什么。这个技能你在课程设计和以后的工作里都用得上。4. 常见问题与排查实录我从这个项目里踩过的坑这部分是我最想写的因为这些问题你不动手是遇不到的遇到了能卡你一整天。4.1 跨域配置为什么前端能拿到数据却请求失败前后端分离之后第一个坑就是跨域。你在 Vue 的开发服务器上访问http://127.0.0.1:8080ajax 去请求http://127.0.0.1:8000浏览器会因为同源策略拦截响应。如果你不想配代理那就用django-cors-headers这个包安装后在settings.py里加两行INSTALLED_APPS [ # ...其他应用 corsheaders, ] MIDDLEWARE [ corsheaders.middleware.CorsMiddleware, # ...其他中间件 ] CORS_ALLOW_ALL_ORIGINS True但你要注意这个配置只适合开发环境因为CORS_ALLOW_ALL_ORIGINS True意味着任何网站都能调你的接口这在生产环境是安全漏洞。课程设计阶段问题不大但答辩老师如果问到“你这个接口安全吗”你得能答出来“生产环境会收紧白名单”。4.2 时间字段时区明明存了时间取出来少 8 小时SQLite 加 Django 的默认配置开启USE_TZ True之后存储和获取时间经常出现 8 小时偏差。原因很简单Django 的 ORM 存入数据库的时候会转 UTC 时间读取的时候再转本地时区如果你的前端页面没有做同样的转换展示出来的时间就跟实际对不上。解决这个问题别去改 Django 的全局时区配置那样会把数据搞乱。正确的做法是前端拿到时间字段之后用 JavaScript 的Date对象做一次本地时区转换或者在后端把 DateTimeField 序列化成字符串并明确标注时区。如果你只需要日期干脆用DateField不涉及时分秒这个坑自动绕过。4.3 并发冲突两个人同时抢同一张桌子怎么办这是这个项目里技术含量最高的问题也是答辩时老师最爱追问的问题。上面的冲突检测逻辑在单用户提交时没问题但如果两个用户同时在毫秒级内提交同一个桌台的时间段两个请求都通过了validate的过滤就会产生两条冲突的预订记录。这就是并发问题学名叫竞态条件。要彻底解决需要用到数据库事务和锁。在 Django 里最简单的做法是用select_for_update()from django.db import transaction with transaction.atomic(): table Table.objects.select_for_update().get(pktable_id) if table_is_available(table, date, time): Reservation.objects.create(...) else: raise ValidationError(已被预订)select_for_update()会锁定这一行数据直到事务提交才释放。后面来的请求会一直在锁上排队排到它的时候再检查一次“是否已被预订”就能确保不会出现两个人同时订到同一张桌子。这个实现也就十来行代码但在答辩时讲清楚“我考虑了并发安全”跟只会写串行逻辑的同学立刻拉开差距。4.4 其他问题速查表问题表现原因解决方案前端请求一直报 404/api代理没匹配到检查 Vite 的 proxy 路径是否带/api前缀和后端 url 路径是否一致图片上传后不显示Django 没配静态媒体路径settings.py配置MEDIA_URL和MEDIA_ROOT并urlpatterns static(...)登录接口返回 401token 没传或过期检查前端请求头是否带Authorization: Bearer token餐桌筛选没数据时间格式不一致前端传的reservation_time必须是HH:MM:SS格式删除菜品后订单报错外键关联导致数据不一致菜品设置is_availableFalse而不是直接删除保留历史订单的引用接口返回的日期是字符串DRF 默认序列化格式在settings.py里配置DATETIME_FORMAT或在序列化器定义format4.5 代码结构上的建议最后说一个很多课程设计会忽略但很影响评价的点你的代码分层。很多人喜欢在 view 里直接写数据库查询、写业务判断、写返回格式最后每个 view 函数都几百行看着像一锅粥。我建议至少做到这一步view 只负责接收参数、调用业务函数、返回结果业务判断比如冲突检测、桌台筛选放到独立的services.py或模型方法里序列化器只负责数据格式转换。这样的代码在答辩时翻起来老师一眼就能看出你理解“分层设计”是怎么回事比你嘴上说“我用了 MTV 模式”有说服力得多。还有一个小的加分项给每个 App 写一个简单的README或者在项目根目录放一个接口列表文档把接口路径、参数、返回值写清楚。这不是浪费时间是给答辩时的你自己省时间。老师当场让你演示一个功能你翻文档能比翻代码快得多而且显得你做事有条理。从我的经验来看这个题目想在答辩里拿高分“系统能跑”只是及格线真正拉开差距的是两个点一是你有没有把预订冲突这个业务规则想清楚并给出可靠实现二是你的代码节奏是不是清楚、接口是不是规范。这两点如果你在动手前就想明白了后面只是执行的问题不会再有大的反复。我自己做项目的时候最大的体会就是很多看似很技术性的坑本质都是开始的时候业务边界没画清表结构没想透。把前面两层功夫下足了后面的代码其实是很顺理成章的事。你照这个思路做下来至少能少熬两个通宵。
阅读完成 · 觉得有帮助?