2026/10/20 15:51:56

Flask与Django混合架构实践:构建景区酒店预订平台

Flask与Django混合架构实践:构建景区酒店预订平台 做了小半年的旅游景区酒店服务平台总算从能跑变成了能用。这个项目从需求梳理到上线踩了不少坑其中一个比较有代表性的决策就是技术栈同时用到了 Python 生态里的 Flask 和 Django。很多人一听到Flask Django就本能地问这俩不是一个重量级的框架吗为什么放在一起这正是我想在这篇文章里聊透的事。这个平台的核心业务很简单游客登录后可以浏览景区介绍、查看周边酒店列表、按日期和房型搜索空房、提交订单并在线支付运营方在后台管理景区内容、酒店房源、价格日历和订单状态。项目跑起来之后后台管理人员、酒店方的操作体验和游客端的响应速度都达到了预期。如果你也在做类似的信息管理类平台或者正纠结 Flask 和 Django 到底该怎么选、能不能混用这篇文章应该能给你一些参考。2. 项目整体设计与技术选型思路1.1 为什么一把梭用了两个框架先说结论这个项目的主体业务用的是 DjangoFlask 承担了部分独立服务接口。这不是为了炫技而是被实际需求逼出来的。项目里有一个比较棘手的功能模块酒店房价和余量的实时同步。合作酒店的系统会定期推送房价库存数据格式五花八门有 JSON 的、有 XML 的甚至还有 CSV。同步服务需要做数据清洗、格式转换、异常重试而且它和主站的业务逻辑耦合度很低几乎就是一条独立的数据管道。用 Django 直接做也不是不行但会牵动主项目的 models、settings、迁移脚本稍微改点东西就要跑一遍测试风险太大。Flask 在这里的优势就很明显了——它是一个即插即用的微型框架起一个独立进程当数据接收端和清洗服务和主站的 Django 通过 HTTP 接口通信。数据管道坏了不影响主站主站发版也不用带着同步服务一起重启。所以这个项目的实际架构是Django 负责核心业务Flask 负责辅助服务两者各司其职。1.2 系统架构与模块划分整个平台按职责拆成了三块Django 主站用户认证、景区内容、酒店房型管理、订单流转、支付回调、后台管理。Flask 辅助服务酒店房价库存同步、节假日调价通知、数据清洗任务。前端页面服务端渲染为主配合少量 Ajax 局部刷新。Django 这一侧用到了它的全套能力自带 User 模型做扩展、ORM 做数据库操作、Admin 后台给运营人员用、模板引擎渲染页面。Flask 那一侧不连主数据库只做数据处理处理完的结果通过内部接口回传给 Django。模块划分的思路是主站封闭、边界清晰。同步服务的所有数据都先落在独立表里清洗完成后再通过服务层接口写入主库不允许 Flask 服务直接操作 Django 的 model。这样两个项目虽然共用一个 MySQL 实例但逻辑上完全隔离出了问题也好排查。1.3 业务场景梳理需求方给的原始诉求其实很朴素游客能查到景区附近的酒店能下单酒店方能收到订单通知。但实际梳理下来业务场景远比这复杂。景区有淡旺季房价要跟着日期浮动酒店的房间分为大床房、双床房、套房等类型每种房型每天的可售数量不一样订单可能取消、可能修改日期钱要原路退回平台方还得在后台手动调整某个日期的底价应对临时活动。这些场景对数据模型的设计要求很高。价格日历、库存日历、订单状态流转、取消策略、支付对账每一个都是单独的复杂度。选 Django 当主框架的一个关键原因也在这里——它对这种多模型关联、事务操作、后台管理的场景支持非常成熟开发效率明显比 Flask 高。2. 核心细节解析与实操要点2.1 数据模型设计与关键字段考量数据模型是整个项目的基石这里踩过的坑最有代表性。最初的设计只做了酒店表、房型表、订单表没有考虑价格日历和库存日历的概念结果上线第一天就发现问题不同日期预订同样的房型价格完全一样库存也没有按天区分根本没法用。后来重构成了日历模型核心表结构如下景区表名称、介绍、地址、图片、开放时间、评分。酒店表所属景区、名称、地址、星级、设施列表、封面图。房型表所属酒店、名称、面积、可住人数、床型、基础价格。房价日历表房型、日期、价格、状态可售/关房/满房。库存日历表房型、日期、剩余房间数。订单表用户、酒店、房型、入住日期、离店日期、订单金额、状态。支付记录表订单、支付流水号、支付金额、渠道、回调状态。这些表里最值得注意的坑是价格和金额的字段类型。价格一律用 DecimalField 而不是 FloatField浮点数做金额计算会出现 0.1 0.2 ! 0.3 的问题对账的时候会炸。库存字段虽然看起来是整数但更新时要用乐观锁后面会细说。2.2 预订流程与状态机设计预订流程是整个平台最核心的业务链路涉及事务一致性。游客选定房型和日期后系统要做几件事锁定库存、生成订单、发送支付请求、等待回调。这一串操作里任何一步失败都不能让用户数据处于中间状态。订单状态机设计为待支付 - 已支付 - 入住完成以及待支付 - 已取消已支付 - 已退款。这里有个值得分享的细节待支付订单并不是永久有效的超过了 15 分钟未支付要自动释放库存。这个功能用 Django 的定时任务实现每隔 5 分钟扫描一次超时订单批量修改状态并回补库存。事务处理用 Django 的 transaction.atomic 块包裹库存扣减使用乐观锁UPDATE ... SET stock stock - 1 WHERE id ... AND stock 0确保并发下单时不会超卖。这个在后面的常见问题里我会展开讲。2.3 Django Admin 的高效运用Django Admin 是选择 Django 的另一个决定性因素。运营方需要的酒店管理、房型管理、价格调整、订单查询功能几乎不用额外开发配置好 ModelAdmin 就能用。价格调整这块印象最深。酒店方经常要批量修改某一段日期的房价比如国庆节前几天突然要求涨价。我们通过自定义 Admin 动作实现了选择房型后在指定日期范围内批量上调/下调价格运营人员几分钟就能完成过去需要写 SQL 才能搞定的操作。Admin 还有一个隐藏价值它天然提供了数据录入和修改的完整审计日志运营方排查数据问题时有据可查。这一点很多团队容易忽略真到出问题要对账的时候才发现有个操作日志有多重要。2.4 Flask 子服务的定位与接口约定Flask 在项目里的定位是旁路服务核心职责是接收酒店方推送的房价库存数据。酒店方用的对接协议不一样有的是 POST JSON有的是上传 CSV 文件我们的 Flask 服务就要把这些不同格式统一清洗成标准数据再通过内部接口写入主库。接口约定这里有一个比较重要的经验Flask 和 Django 之间的内部接口虽然只在服务器内部调用但依然要做签名校验。原因是这类服务很有可能因为部署配置问题意外暴露到公网如果没有签名任何人都可以往你的库存表里灌脏数据。签名方案用的是最简单的密钥 时间戳 请求体哈希拼接后做 HMAC 校验Flask 侧校验通过后再调用 Django 的服务层接口写入数据。这套方案不复杂但能挡住绝大多数误调用和恶意请求。3. 实操过程与核心环节实现3.1 环境准备与项目骨架搭建开发环境用的是 Python 3.10Django 4.2 LTS 版本Flask 2.3。数据库选 MySQL 8.0用 Docker 起实例本地开发和线上环境一致避免在我机器上是好的这种尴尬。项目结构按两个独立应用 共享代码目录的方式组织project/ ├── django_main/ # Django 主站 │ ├── apps/ │ │ ├── accounts/ # 用户模块 │ │ ├── scenes/ # 景区模块 │ │ ├── hotels/ # 酒店房型模块 │ │ └── orders/ # 订单模块 │ └── config/ # Django 配置 ├── flask_service/ # Flask 同步服务 │ ├── app.py # Flask 入口 │ └── handlers/ # 数据清洗处理器 └── common/ # 两边共享的工具函数虚拟环境到了这个阶段建议直接用 pipenv 或 poetry 管理单纯用 requirements.txt 在多项目协作时经常出现依赖版本打架的情况。Python 虚拟环境是基本功不再多说但把 Django 和 Flask 的依赖分开放在两个虚拟环境里是必须的因为它们对某些第三方库的版本要求并不完全一样。3.2 核心业务代码实现Django 侧的模型代码是整个平台的核心。以房价日历表为例模型定义大致是这样from django.db import models class PriceCalendar(models.Model): room_type models.ForeignKey(hotels.RoomType, on_deletemodels.CASCADE, related_nameprices) date models.DateField() price models.DecimalField(max_digits10, decimal_places2) status models.SmallIntegerField(choices((1, 可售), (2, 关房), (3, 满房)), default1) class Meta: db_table price_calendar unique_together (room_type, date) indexes [models.Index(fields[room_type, date])]联合唯一约束在这里特别重要它保证了同一个房型同一天只能有一条价格记录从数据库层面杜绝了重复数据。库存扣减的乐观锁实现from django.db import transaction from django.db.models import F transaction.atomic def deduct_stock(room_type_id, date, count): result InventoryCalendar.objects.filter( room_type_idroom_type_id, datedate, stock__gtecount ).update(stockF(stock) - count) if result 0: raise ValueError(库存不足) return True使用F(stock) - count而不是先查后改是因为查询和更新之间的时间窗口里很可能有其他请求也在改这行记录。F 表达式让更新操作在数据库层面原子完成配合stock__gtecount的过滤条件能够可靠避免超卖。视图层用 Django 的类视图和数据库查询的 select_related/prefetch_related 优化关联查询。酒店详情页需要展示房型、价格日历、酒店图片等多组关联数据直接用 ORM 默认取值会产生大量 SQL 查询页面响应时间会从几十毫秒变成几秒。用预取优化后快很多这个技巧在后面也会详细说。3.3 Flask 同步服务实现Flask 服务最核心的代码是数据清洗和统一入库。酒店方推送的数据格式不确定所以用了 Flask 的蓝图注册版本路由每种格式对应一个处理函数from flask import Flask, request, jsonify import hmac, hashlib app Flask(__name__) app.post(/api/sync/hotel_price) def sync_hotel_price(): # 1. 验签 sign request.headers.get(X-Signature) if not verify_sign(request.get_data(), sign): return jsonify({code: 401, msg: invalid signature}), 401 # 2. 解析数据这里假设收到的是 JSON payload request.get_json() # 3. 清洗校验字段、转换日期、剔除非法数据 cleaned clean_price_data(payload) # 4. 调用 Django 内部接口写入主库 result call_django_api(/internal/sync/prices, cleaned) return jsonify({code: 0, data: result})这里面有一个隐藏得很深的坑Flask 的request.get_json()在请求体是空字符串时会抛异常而且乱数据还可能出现编码问题。我们在清洗函数里加了全面的数据校验任何一条数据不合法就跳过并记录日志不能让一条脏数据摧毁整个批次。3.4 前后端联调与数据流转前后端联调阶段最耗时的是预订流程的数据流转。游客在前端选择日期后前端要请求房价日历接口获取可选房型和价格提交订单时要把房型、日期、游客信息打包发给 Django 接口 Django 生成订单后返回订单号和支付参数前端再调用支付接口最后通过支付回调确认支付结果。这个链路数据方向多、状态变化频繁联调时经常出现订单状态停留在待支付的问题。后来加了一个联调用的调试面板把整个请求链路的关键日志和状态变化输出到页面上问题定位快了不少。把支付回调的日志全部落库也是一个排障利器回调失败还是参数错误一眼就能看出来。4. 常见问题与排查技巧实录4.1 并发预订导致库存超卖这个问题是预订系统里最经典的坑我们也是在压测阶段发现的。压测脚本模拟 50 个用户同时预订同一房型同一日期结果库存从 10 变成了负数。原因就是最初的库存扣减代码是先查询 stock再判断 if stock 0然后执行 update查询和更新之间存在时间差并发请求全都读到了同一个剩余库存。修复方案就是刚才讲的乐观锁。用update语句的stock__gtecount条件确保数据库层面只有足够库存时才扣减返回值等于 0 表示扣减失败直接提示用户库存不足。修复后连续压测多次库存数据始终正确。4.2 ORM 查询导致的 N1 问题N1 问题是 Django 新手最容易踩的坑。酒店列表页要展示每家酒店及其关联的景区名称、最新价格如果直接用 ORM 查询酒店列表然后在模板里通过关系字段取景区名称每取一条酒店就会额外发一条 SQL 查询列表有 50 家酒店就会产生 1 50 次查询。解决方案是查询时主动用select_related拉取外键关联对象。对于多对多关系或反向关联用prefetch_related。最关键的是在开发环境安装一个查 SQL 计数的工具把页面请求产生的 SQL 数量和耗时直接打印到终端任何 N1 问题一眼就能发现。4.3 图片上传与静态资源处理景区图片和酒店图片是运营内容的刚需但这个环节特别容易在部署后出问题。开发环境下用 Django 的 MEDIA_ROOT 存文件没问题但线上如果还用同样的方案应用服务器重启或者多实例部署时用户上传的图片就可能丢失。我们最后的处理是本地图片目录只做临时存储图片上传后立即转存到对象存储服务数据库里存的是对象的文件路径 URL。模板里展示图片时直接拼完整 URL不经过 Django这样静态资源请求不占用应用服务器带宽页面加载速度也快很多。4.4 接口鉴权与数据安全游客端的酒店查询接口、房价日历接口理论上不需要登录也能访问但订单相关接口必须校验登录状态。最开始用的是 Django 自带 session 认证前端通过 Cookie 携带 sessionid联调一直没问题。后来发现手机端浏览器对第三方 Cookie 的限制越来越严格session 方案不稳定就重构了 Token 认证方案。自定义了一个简单的 Token 模型登录成功后生成随机 Token 存到 Redis设置过期时间前端请求时放在 Authorization 请求头里后端写一个自定义认证类解析 Token 并获取用户。这个方案对前后端分离的场景友好得多也方便后续接入小程序。4.5 定时任务与订单超时处理订单超时自动取消的功能看起来简单但因为涉及库存回补必须保证幂等。定时任务扫描到超时订单后先判断订单当前状态是否还是待支付是的话才执行取消和库存回补。如果任务执行到一半挂了重启后再次扫描不会重复回补库存因为这个订单的状态已经变成已取消了。这个幂等设计是整个定时任务里最重要的一环。没有这个保障一个订单超时后如果触发两次回补酒店方的可售库存就会虚高实际入住时才发现安排不下那就成事故了。5. 踩坑后的几点真实体会项目收尾之后回头去看最初Flask 和 Django 一起用的决定我觉得是值得的。Django 把人从重复造轮子的工作中解放出来Admin 后台、认证系统、ORM 这些能力确实稳但代价是框架本身比较重不适合做高吞吐的旁路数据处理Flask 刚好补上了这个短板。两个框架放在一起如果有清晰的模块边界完全是一种务实的组合。个人印象最深的一个教训是上线前一定要做并发测试尤其是涉及金额、库存这类核心资源的操作。很多问题看起来在单用户测试时完美运行并发一上来就原形毕露。乐观锁、事务、幂等性这些概念读文档时总觉得懂了只有真正被线上事故教育过才知道这些设计不是理论而是保命的东西。再分享一个小技巧给所有外部调用和内部模块之间的接口都加上请求 ID日志里带着请求 ID 打排查问题的时候顺着一个 ID 能把整条调用链串起来。项目刚上线那阵子一天要查好几次生产问题全靠这个设计节省了大量时间。这个平台后续要扩展的方向还挺多比如接入民宿房源、增加景区门票预订、做酒店价格的智能推荐技术架构上现有的 Django 主站 Flask 旁路服务的骨架还能继续支撑。如果你也在做类似的项目欢迎根据你的场景去调整这个方案核心思路是一样的主业务选成熟的框架旁路服务保持轻量独立数据安全和幂等性从一开始就要想清楚。