简介:基于Python Django框架的伊人酒店管理系统设计与实现文档,面向计算机专业学生、Web开发入门者及中小型酒店管理人员。文档完整覆盖系统需求分析、可行性分析、UML建模、功能模块设计、数据库设计及网络接口设计等内容,重点阐述了前后端分离开发、MySQL数据库、Django ORM及Vue.js渲染等关键环节,能帮助读者快速理解Web酒店管理系统的完整实现路径。资源为1个docx文件,压缩包约2.37MB,便于阅读和打印。目前已有71人学习。对于课程设计或毕业设计人群,此文档提供了清晰的设计思路、模块化开发方法和详细用例规约,参考其体系结构可显著减少系统设计阶段的重复劳动,是一份实用性较强的参考资料。
1. 酒店管理系统自研的起点:从需求乱麻到技术选型
市面上的酒店管理系统不少,但绝大多数是给星级酒店设计的,功能堆得很满,中小型酒店买回去发现一半模块用不上,员工培训成本还高。这套基于 Python Django 的伊人酒店管理系统,走的是另一条路:只做真正高频的流程,把“官网展示 + 在线预订 + 后台管理”拆成两个客户端,让住客在前台订房、管理员在后台管房。拆解这套系统的意义不只是看代码,而是看它如何用 Django 的 ORM 把房间、价格、订单、消息这些实体串成一个完整的业务闭环。对于想上手 Django 全栈开发的人,或者需要给中小商户做管理系统的开发者,这套设计的模块划分和数据表结构都有直接可抄的价值。
2. Django 数据模型设计:房间、价格与订单的实体关系拆解
2.1 核心实体的建模思路
酒店管理系统本质上在管理三类核心数据:房间资源、价格策略、订单流转。伊人系统把这三大块拆成了清晰的 Django model,没有过度设计,但每一张表都有明确的业务含义。先看最基础的房型和房间号设计:
from django.db import models class RoomType(models.Model): name = models.CharField(max_length=50, verbose_name="房间类型名") price = models.DecimalField(max_digits=8, decimal_places=2, verbose_name="默认价格") description = models.TextField(verbose_name="房间描述") free_services = models.TextField(verbose_name="免费服务") cover_image = models.ImageField(upload_to="room_type/", verbose_name="封面图") is_active = models.BooleanField(default=True, verbose_name="是否启用") class Meta: db_table = "room_type" class Room(models.Model): room_type = models.ForeignKey(RoomType, on_delete=models.CASCADE, verbose_name="所属房型") room_no = models.CharField(max_length=10, unique=True, verbose_name="房间号") is_active = models.BooleanField(default=True, verbose_name="是否启用") class Meta: db_table = "room"这里把“房间类型”和“房间号”分成两张表,是酒店业务里很重要的一个设计决策。一个房型对应多个房间,比如“豪华大床房”这个类型下有 101、102、103 三个房间。后台管理时,管理员先维护房型,再往房型下挂房间号,添加房间号时强制选择所属房型。用ForeignKey建立一对多关系,db_table显式指定表名,方便后期 DBA 接手时直接看表结构。
值得注意的细节是is_active字段几乎出现在每张业务表里。它不是冗余,而是给管理员留了一个“软开关”——某个房型要停售,不需要删数据,直接关掉启用状态就行,历史订单仍然能正确关联到房型信息。
2.2 价格日期表:解决“某一天调价”的痛点
酒店价格的典型场景是:默认价格是 500 元一晚,但国庆期间某天要涨到 800 元。如果直接在房型表上改价格,改完还要改回来,既不灵活也容易出错。伊人系统单独设计了一张价格日期表来处理这个问题:
class RoomPriceDate(models.Model): room_type = models.ForeignKey(RoomType, on_delete=models.CASCADE, verbose_name="房型") date = models.DateField(verbose_name="日期") price = models.DecimalField(max_digits=8, decimal_places=2, verbose_name="当日价格") class Meta: db_table = "room_price_date" unique_together = ("room_type", "date")这张表的含义是:某房型在某天的特殊价格。查询某天价格时,优先查RoomPriceDate,查不到就回退到RoomType.price默认价。unique_together保证了同一个房型在同一天只有一条价格记录,从数据库层面防止脏数据。前端预订页面展示 30 天价格日历,就是循环查这张表的结果集,把有特殊价格的日期标出来。
2.3 订单表与状态流转
订单是整个系统的核心枢纽,串联起用户、房间、价格、入住天数、增值服务这些信息。伊人系统的订单模型设计覆盖了前台用户自助下单和管理员后台代下单两种场景:
class Order(models.Model): ORDER_STATUS_CHOICES = ( ("pending", "待入住"), ("checked_in", "已入住"), ("checked_out", "已退房"), ("cancelled", "已取消"), ) user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, blank=True, verbose_name="下单用户") room_type = models.ForeignKey(RoomType, on_delete=models.CASCADE, verbose_name="房间类型") room = models.ForeignKey(Room, on_delete=models.CASCADE, verbose_name="房间号") check_in_date = models.DateField(verbose_name="入住日期") check_out_date = models.DateField(verbose_name="离店日期") nights = models.IntegerField(verbose_name="入住天数") total_price = models.DecimalField(max_digits=10, decimal_places=2, verbose_name="总价") contact_name = models.CharField(max_length=20, verbose_name="联系人") contact_phone = models.CharField(max_length=11, verbose_name="联系电话") status = models.CharField(max_length=20, choices=ORDER_STATUS_CHOICES, default="pending") created_at = models.DateTimeField(auto_now_add=True)订单状态用字符串常量加choices限定,比用数字状态码直观得多。Django admin 后台会渲染成下拉框,不会出现填错状态值的情况。关键设计是user字段允许为空,因为后台管理员代客人下单时,客人可能没有注册官网账号,这时候订单仍然能正常创建,只是关联不到 user。
2.4 消息与增值服务的扩展设计
伊人系统的留言和增值服务模块值得单独拎出来说。评论和投诉共用一张消息表,用type字段区分是“普通评论”还是“投诉”,后台消息管理界面通过这个字段做筛选。增值服务模块拆成导游管理、合作酒店管理、景点协调管理三块,导游信息里带折扣字段,合作酒店带链接字段,景点带门票折扣字段。这三个实体本质上都是“给酒店引流或增收的外部资源”,做成三个独立 model 比合并成一张多态表更符合 Django 的惯例——查询简单,字段类型清晰,admin 注册后直接获得增删改查能力。
3. 双端功能实现:门户网站与管理后台的业务闭环
3.1 门户网站:预定流程的状态机与视图函数
门户网站面向住客,入口是酒店首页。首页展示酒店简介和周边景点,访客选择入住日期和离店日期后点击搜索,系统将日期参数传给预定页面,返回该时间段内所有可预订的房型:
def search_available_rooms(request): check_in = request.GET.get("check_in") check_out = request.GET.get("check_out") available_types = [] room_types = RoomType.objects.filter(is_active=True) for room_type in room_types: # 排除日期范围内已锁定的房间 conflicting_orders = Order.objects.filter( room_type=room_type, status__in=["pending", "checked_in"], check_in_date__lt=check_out, check_out_date__gt=check_in ) total_rooms = Room.objects.filter(room_type=room_type, is_active=True).count() locked_rooms = conflicting_orders.values("room").distinct().count() if locked_rooms < total_rooms: available_types.append(room_type) return render(request, "booking.html", {"available_types": available_types})参数check_in和check_out由前端日期选择器格式化后拼到 URL 上,后端用request.GET.get()接收。判断房间是否可订的核心逻辑是区间重叠检测:已存在订单的入住日期在当前查询的离店日期之前,且订单的离店日期在当前查询的入住日期之后,说明时间有交集,该房间在这个区间内不可用。用distinct().count()统计被锁定的房间数,再对比该房型下总房间数,就能算出还有没有余房。这种校验方式没有依赖复杂 SQL,纯 Django ORM 就能完成,对中小规模酒店完全够用。
用户点击“立即预定”后进入订单确认页。如果未登录,系统拦截请求并重定向到登录页,登录完成后带参数跳回预定页。提交订单时用 Django Form 做字段校验,重点验证手机号格式和入住人姓名字段非空。
3.2 管理后台:30 天房间状态看板的实现
管理后台是伊人系统的核心,其中最有看点的功能是“房间状态管理”——展示当日起 30 天内每一个房间每一天的预定状态,管理员点击某天的某个房间格,可以直接弹窗创建订单、办理入住或退房。
这个功能直观上是个日历热力图,实现上核心是对二维数据的组织:横轴是 30 个日期,纵轴是每个房间号。视图层组装数据的方式如下:
def room_status_board(request): today = timezone.now().date() date_list = [today + timedelta(days=i) for i in range(30)] rooms = Room.objects.filter(is_active=True).select_related("room_type") # 获取30天内的所有订单,按房间和日期建立索引 orders = Order.objects.filter( status__in=["pending", "checked_in"], check_out_date__gt=today, check_in_date__lt=today + timedelta(days=30) ) status_map = {} for order in orders: current = max(order.check_in_date, today) while current < order.check_out_date and current < today + timedelta(days=30): status_map[(order.room_id, current)] = order.status current += timedelta(days=1) board_data = [] for room in rooms: row = {"room": room, "statuses": []} for date in date_list: row["statuses"].append(status_map.get((room.id, date), "available")) board_data.append(row) return render(request, "admin/room_status.html", {"board_data": board_data, "date_list": date_list})这里用(room_id, date)作为字典键,把订单状态铺平到日期维度上,避免了在模板里做嵌套循环查库。每个房间每天只会命中一个状态:available、pending 或 checked_in。页面渲染时不同的状态映射到不同的 CSS 颜色,管理员一眼就能看出哪些房间空着、哪些已订出、哪些今天退房。点击事件用 JavaScript 绑定,把房间 ID 和日期传给弹窗,弹窗里再加载订单创建表单。
3.3 价格修改与实时刷新
房间价格管理页面与状态看板类似,也是 30 天日历视图,但数据源自RoomPriceDate表。管理员点击某天的价格,弹出输入框,提交后走下面的逻辑:
def update_price(request, room_type_id): if request.method == "POST": date = request.POST.get("date") price = request.POST.get("price") RoomPriceDate.objects.update_or_create( room_type_id=room_type_id, date=date, defaults={"price": price} ) return JsonResponse({"code": 0, "msg": "价格已更新"})update_or_create是 Django ORM 里很实用的一个方法——当天该房型已有特殊价格记录就更新,没有就创建,一步完成。前端页面无需刷新,Ajax 提交后返回 JSON,前端把页面上的价格文本直接替换成新值。这套交互做下来,整个价格调整流程顺畅得跟操作 Excel 一样,完全不需要去改数据库。
4. 关键业务逻辑落地的坑与对策:订单冲突、折扣计算与消息联动
4.1 订单创建时的并发冲突处理
管理后台代客人下单时,两个前台同时操作同一个房间同一天,很可能出现重复售卖。伊人系统在订单创建时加上了一层乐观锁保护:
def create_order(request): room_id = request.POST.get("room_id") check_in = request.POST.get("check_in") check_out = request.POST.get("check_out") nights = (datetime.strptime(check_out, "%Y-%m-%d") - datetime.strptime(check_in, "%Y-%m-%d")).days # 再次校验房间在目标区间是否可用 conflict = Order.objects.filter( room_id=room_id, status__in=["pending", "checked_in"], check_in_date__lt=check_out, check_out_date__gt=check_in ).exists() if conflict: return JsonResponse({"code": 1, "msg": "该房间在所选日期已被预订"}) # 通过校验后创建订单这段代码出现在用户提交订单和管理员代下单两个入口。前端已经过滤过可订房型,但后端必须再做一次校验,因为前端展示的数据可能已经过期,直接信任前端传参很容易产生超卖。订单创建后,房间状态看板下一次加载时会自动把新订单纳入锁定区间。
4.2 增值服务与导游折扣的订单联动
导游订房场景下,导游带团入住需要按协议折扣结算。伊人系统的做法是:订单创建弹窗里提供一个“选择导游”的下拉框,选中后自动带出该导游的折扣比例,前台工作人员手动确认总价:
def calc_discounted_price(room_type_id, date_list, discount, extra_services): total = 0 for date_str in date_list: date = datetime.strptime(date_str, "%Y-%m-%d").date() price_obj = RoomPriceDate.objects.filter(room_type_id=room_type_id, date=date).first() if price_obj: total += price_obj.price else: total += RoomType.objects.get(id=room_type_id).price total *= discount for service in extra_services: total += service.price return round(total, 2)总价计算分三段:先按日期逐天取价——当天有特殊价格用特殊价,没有用默认价;再乘以折扣——导游折扣和景点合作的入住的折扣都走这个逻辑;最后叠加增值服务费用。计算完返回给前端展示,管理员确认无误后提交订单。折扣比例没有单独存到订单表,因为导游和景点的合作协议可能会调整,存比例快照反而更合理——不过伊人系统是当下单时手动算好总价直接存进订单金额字段,后续协议变更不影响已生成订单。
4.3 评论与投诉的消息联动机制
门户站用户订单结束后可以发表评论或投诉,后台消息列表会展示这些内容。这里的联动逻辑是:用户在个人中心看到订单状态变为已退房,点击“评价”按钮,选择“好评/差评”或“投诉”,提交后写入消息表。后台管理员回复后,门户站对应评论下方会显示回复内容,同时用户的个人中心收到一条未读消息。
这个消息联动用 Django 的信号机制可以优雅实现,在Message模型的save方法里判断是否被管理员回复过:
class Message(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="发布者") content = models.TextField(verbose_name="内容") reply = models.TextField(blank=True, null=True, verbose_name="管理员回复") is_complaint = models.BooleanField(default=False, verbose_name="是否为投诉") created_at = models.DateTimeField(auto_now_add=True) def save(self, *args, **kwargs): is_new = self.pk is None super().save(*args, **kwargs) if not is_new and self.reply: # 向评论发布者发送未读消息通知 Notification.objects.create(user=self.user, content="您提交的反馈已收到回复")save方法重写时有个容易忽略的细节:必须在super().save()之后才能创建通知,因为在调用super()之前主键还没有生成。这里仅讨论了核心思路,实际开发中也可以把通知逻辑放到视图层处理。
5. 系统测试用例设计与 Django 项目的本地部署验证
5.1 功能测试用例的组织方式
伊人系统的测试覆盖分为两块:第一块是业务流程测试,第二块是权限控制测试。业务流程测试的核心用例如下:
| 测试模块 | 测试操作 | 预期结果 |
|---|---|---|
| 门户网站预定 | 用户登录后选择日期和房型提交订单 | 订单创建成功,管理后台状态看板对应房间日期变为已预订 |
| 管理后台代下单 | 管理员点击空闲房间格,填写入住信息提交 | 新订单生成,状态看板实时更新 |
| 价格动态调整 | 管理员修改某天某房型价格 | 门户网站预定界面对应日期价格同步更新 |
| 评论投诉联动 | 用户提交评论,管理员回复 | 门户网站显示回复内容,用户个人中心收到未读消息 |
| 增值服务折扣 | 管理员选择导游后录入订单 | 订单总价按导游折扣比例自动计算 |
权限控制这块的测试重点是:普通官网注册用户直接访问管理后台地址时,应该被拦截并提示无权限;管理员账号被禁用后无法登录;普通管理员尝试添加新管理员账号时操作被拒绝。这些逻辑在 Django 中可以用user_permissions或自定义装饰器实现,测试时验证的是装饰器正确拦截了未授权访问。
5.2 Django 项目的本地运行与调试
拿到系统源码后,本地跑起来只需要几步。项目开发环境是 PyCharm,虚拟环境管理是标准的venv方式:
# 1. 创建并激活虚拟环境 python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate # 2. 安装项目依赖 pip install -r requirements.txt # 3. 初始化数据库 python manage.py makemigrations python manage.py migrate # 4. 创建超级管理员账号 python manage.py createsuperuser # 5. 启动开发服务器 python manage.py runserver 0.0.0.0:8000makemigrations会扫描所有 app 下的models.py,把模型变更生成迁移文件;migrate将迁移文件应用到 MySQL 数据库。项目生产环境用的是 MySQL,本地调试如果没装 MySQL,可以把settings.py里的数据库配置临时改成 SQLite,改一行代码的事——这在前期快速验证页面效果时很省事。runserver启动后,浏览器访问http://127.0.0.1:8000进入酒店官网,访问http://127.0.0.1:8000/admin进入 Django 自带后台,再访问系统自定义的管理端 URL 需要先登录管理端账号。
5.3 生产部署的注意点:环境变量与静态文件
开发环境跑通后部署到 Linux 服务器,有几个常见坑。DEBUG必须设为False,否则会暴露完整报错堆栈和配置信息;ALLOWED_HOSTS必须填上实际域名或服务器 IP,不然 Django 会拒绝请求;静态文件需要执行python manage.py collectstatic统一收集,否则前端页面样式全部丢失。数据库配置建议通过环境变量读取,不要写死在settings.py里,方便不同环境切换。项目技术栈中提到了 Nginx,部署时可以用 Nginx 托管静态文件并反向代理到 Django 应用,这套方案比直接用runserver扛生产流量可靠得多。
6. 把伊人酒店系统抽成可复用的 Django 业务骨架
伊人酒店系统最有价值的不是某个页面写得多漂亮,而是它把“资源—时间—订单”三者的关系梳理清楚了。把它抽象成一套可复用的业务骨架:任何需要按时间维度管理可售资源的场景——民宿预订、会议室租赁、共享工位、甚至车辆调度——都可以直接套用这套数据模型。房型表对应资源类型表,房间号表对应具体资源实例表,价格日期表对应动态定价表,订单表对应占用记录表。30 天状态看板本质上就是一个“资源 × 日期”的二维网格,这套视图逻辑在管理后台可以直接复制到任何 Django 项目中。
具体复用的时候,注意三个边界条件。第一,时间粒度的选择——伊人系统以天为最小单位,但会议室租赁可能需要按小时计价,这时把RoomPriceDate.date字段改成DateTimeField即可,状态看板的横轴从 30 天变成一天内的 24 小时。第二,订单状态枚举需要根据业务扩展——租赁场景可能需要“已支付待使用”“使用中超时”“已完成待评价”等状态,直接在choices里扩充即可。第三,价格计算逻辑中折扣叠加的优先级要提前约定——伊人系统是固定折扣乘以总价,如果业务有满减、会员折扣、限时优惠叠加,建议把计价逻辑单独抽成PricingService类,不要把计算逻辑散落在视图函数里。这套骨架的取舍是放弃了通用性换取开发效率,拿到手改改模型字段就能跑业务,对中小型团队来说性价比很高。
本文还有配套的精品资源,点击获取