我去年帮一个城中村片区做过一套民宿租赁管理系统,需求方对技术栈就一句话:用Python写,网上说可以“django-flask”。这句话背后藏着很多开发者都遇到过的问题——到底是用Django还是Flask,还是两个一起上?实际把系统跑起来之后,我对这个问题的答案比之前清晰了很多。这篇文章就是完整的复盘:从业务调研、架构取舍、数据模型,到Django实现核心业务、Flask做轻量查询API,再到上线后踩过的几个真坑。如果你也在规划类似的租赁管理系统,不管是毕业设计还是外包项目,这篇应该能帮你少走不小弯路。
1. 城中村民宿租赁,到底在租什么、管什么
很多人一看“民宿租赁管理系统”,下意识就套用酒店PMS或者长租公寓SaaS的那套理解。但城中村的租赁业务和这两者都不一样,差别非常具体,直接决定了系统该怎么做。
1.1 城中村民宿的业务形态与系统目标
我服务的这个片区,房东大多是本地村民,一栋楼往往五六层,房东自己住一层,剩下全部拿来出租。租客成分很杂:附近服务业的年轻人、小商贩、短期的工地工人。租期也非常混乱,日租、周租、月租、季租混着来,而且经常是“先住后补合同”。
这就导致原来的管理方式几乎全是纸质的:一个本子记房态,一个本子记账。到了月底要算水电费,得挨个房间看电表读数,再用计算器按一遍。催缴租金靠微信语音。房东不是不想用系统,而是市面上的酒店系统太重、太贵,长租公寓系统又假设租约都是半年一年起步,搬到城中村场景里完全水土不服。
所以这系统要解决的核心问题就三条:第一,房态可视化,哪间房空着、哪间房今天到期,打开手机一眼能看明白;第二,账目清晰,押金、定金、租金、水电费、退款,每一笔钱都有来路和去处,月底自动出账单;第三,提醒到位,谁该交租了、谁的租约快到期了,提前提醒,不用人肉翻本子。
另外还有一个隐形刚需:操作门槛必须极低。实际用这套系统的人不是IT从业者,而是五十多岁的房东和前台阿姨。如果界面绕来绕去,他们宁可重新拿回本子。
1.2 用户角色:低保真后台才是刚需
系统我把角色分成四类,这个划分后来证明非常关键:
- 系统管理员:管账号、权限、系统参数,通常是我自己或者技术维护方。
- 房东/管家:管自己的楼栋和房间,录房源、看房态、发起退房退款。
- 收银/前台:负责实际办入住、收押金、收租金,他们最在意“收钱路径短”。
- 租客:最轻量,只查自己的合同、账单和租期剩余天数,再用一个报修入口。
这四类角色对界面的要求完全不同。房东和管理员喜欢“表单化”的后台,而前台和租客则更依赖手机页面。这也是我后来坚持“Django做后台、Flask做手机端查询API”的一个原始动机,如果只做一个纯Web后台,前台阿姨是不可能抱着一台电脑到处跑着办入住的。
1.3 一条完整租赁链:从空房到退房
把业务抽象成一条链路,系统其实就干一件事:让房间在“空置、预定、已住、保洁、维修”这几个状态里稳定流转,并在流转过程中把该收的钱收清楚。一条典型链路是:
空房被看到 -> 租客交定金预定 -> 到期办理入住(签合同、收押金、收首期租金) -> 租期中按约定周期生成租金账单 -> 水电费按表录入 -> 退房检查 -> 扣除水电和损坏费用 -> 退还押金余款 -> 房间转保洁 -> 重新上架为空房。
每个环节都伴随至少一笔账,系统设计的时候必须把“房态流”和“资金流”同步考虑,而不能分开设计——这是我踩过坑后最深刻的体会。分开设计的结果就是状态对不上账,后期非常痛苦。
2. Django为主、Flask为辅:这套组合架构怎么落地
标题里写“django-flask”,不同人理解不一样。有人以为是要同时用两个框架,有人只是想表达“用Python系的框架都行”。从实际项目出发,我把这两种理解都照顾到了。
2.1 为什么选型上来就会是“django-flask”这种说法
我判断这种说法大概率来自课程设计和外包报价场景。Django和Flask是国内Python Web项目里两个最常被提到的框架,写标书的人并不确切知道它们的区别,于是干脆并列写上。但作为真正动手实现的人,必须做一次明确的取舍,而不是稀里糊涂地“两个都是”。
我的取舍标准是看模块成熟度。这个系统涉及到用户登录、后台管理、数据建模、权限控制,这几块Django的Admin、ORM、auth体系几乎是白送的,开发效率极高。而手机端需要一个轻量的查询接口,如果用Django直接写也不是不行,但Flask写只读API更快,上下文也干净——不需要加载Admin那一大套中间件和模板系统。
所以最终架构落定为:主工程用Django 4.x,承载全部业务写入和后台管理;Flask 2.x只作为旁路服务,对外提供只读查询API,服务手机端和未来的小程序。两个服务共享同一个MySQL库,各司其职。
2.2 我采用的进程与库划分
部署结构是这样的:
- Nginx监听80/443,负责反向代理和静态文件。
- 路径
/admin/、/manage/开头的所有请求转给Django的Gunicorn进程。 - 路径
/api/开头的请求转给Flask进程。 - Django进程负责所有写操作,包括房源、租约、账单、退款。
- Flask进程只做SELECT查询,并且只查几张核心表。
这个划分的关键逻辑是:写入路径只有一个,写入逻辑不会出现“两套代码写同一个表”的冲突。Flask这边哪怕代码写得再随意,它不写库,就不会把数据搞坏。手机端要发起的那些“预约看房”之类的写操作,我的做法是单独开一个Django下的API端点,Flask只负责展示类的查询。这样就将复杂度控制在一个可控范围。
2.3 共享数据库时的迁移与权限约束
两个框架连同一个MySQL,听起来简单,实际要处理两个问题:迁移归属和连接健康。
迁移归属上,我明确规定:数据库结构变更只走Django的Migration。凡是涉及表结构修改的,一律在Django工程里写model、迁移、上线。Flask这边不建任何表,它如果需要查询一张新表,直接依赖Django已经迁移好的现成表。从源头上避免两边各自建表、互相覆盖的混乱。
连接健康上,Django默认的数据库连接和Flask长连接策略不一样。Django方面我设置了CONN_MAX_AGE=60,让短请求也能复用连接。Flask这边用了带连接池的SQLAlchemy,并开启pool_pre_ping,避免MySQL断连后Flask还拿着失效连接继续查询。这件事在后面踩坑部分还会细讲。
2.4 什么时候该砍掉Flask只留Django
如果你正在评估自己的项目,我觉得可以更果断一点。如果你的系统只有Web后台,没有独立手机端,那么只留Django完全够用,连Flask都不用写。网上很多模板把Flask加进来,纯粹是技术展示,不是业务刚需。
反过来,如果手机端有实时查询房价、查空房、查账单这类高频只读场景,而且未来打算拆独立小程序或者App,那Flask旁路查询服务就值了。理由很简单:一次查询不需要经过Django的一整套认证中间件,响应能快几十毫秒;更重要的是,就算Flask服务被并发打崩,Django核心业务和后台管理还能正常跑,不会因为一个查询接口爆炸导致整站不可用。
3. 核心表结构:房源、租约、账单、流水四层模型
数据模型是整个系统里最不能省时间的部分。城中村民宿系统通常有几十个房间,单表查询没问题,但表与表之间的关联设计一旦有漏洞,后期改起来伤筋动骨。
3.1 用户与角色:不新建用户表,用Django的Group撑住
一开始我差点进入自己写用户表的误区,后来想通了:Django自带的User模型加Group和Permission已经足够。系统管理员是一个Group,房东是一个Group,前台是一个Group,租客我单独用一张TenantProfile去关联User。这样既享受Django auth的成熟登录体系,又不用自己维护密码逻辑。
给租客建TenantProfile是因为租客不一定要有后台登录权限,他们只是通过手机端用token访问,这个Profile上记录手机号、身份证号、常驻地址等额外的身份信息。
3.2 楼栋、房间与价格策略
房源我拆成了楼栋和房间两层,而不是直接一张房间表。原因是城中村房东往往有不止一栋楼,每栋楼地址不同,水电表独立,统计收入也要按楼栋拆开看。核心模型大致长这样:
class Building(models.Model): name = models.CharField(max_length=64, verbose_name="楼栋名称") address = models.CharField(max_length=255, verbose_name="详细地址") owner = models.ForeignKey(User, on_delete=models.PROTECT, related_name="buildings", verbose_name="房东账号") remark = models.TextField(blank=True, verbose_name="备注") class Room(models.Model): STATUS_CHOICES = [ (0, "空置"), (1, "已预定"), (2, "已入住"), (3, "保洁中"), (4, "维修中"), ] building = models.ForeignKey(Building, on_delete=models.CASCADE, related_name="rooms") room_no = models.CharField(max_length=32, verbose_name="房号") area = models.DecimalField(max_digits=6, decimal_places=2, verbose_name="面积") daily_price = models.DecimalField(max_digits=8, decimal_places=2, default=0, verbose_name="日租价") monthly_price = models.DecimalField(max_digits=8, decimal_places=2, default=0, verbose_name="月租价") status = models.SmallIntegerField(choices=STATUS_CHOICES, default=0, db_index=True, verbose_name="当前状态") is_active = models.BooleanField(default=True, verbose_name="是否在租")这里有个细节:我把日租价和月租价分开存,因为城中村民宿的日租和月租差价非常大,月租通常按日租的六折甚至五折算。而且后续做空房查询时,前端会根据“今天住一晚”还是“长住一个月”展示不同价格,不能用一个价格字段硬套。
3.3 租约的三态:进行中、已结束、已取消
租约是系统的核心,我设计的表叫RentalContract,它关联房间和租客,记录起止时间、押金金额、租金方案。关键字段如下:
class RentalContract(models.Model): STATUS_RUNNING = 0 STATUS_FINISHED = 1 STATUS_CANCELLED = 2 room = models.ForeignKey(Room, on_delete=models.PROTECT, related_name="contracts") tenant = models.ForeignKey(TenantProfile, on_delete=models.PROTECT, related_name="contracts") start_date = models.DateField(verbose_name="入住日期") end_date = models.DateField(verbose_name="退房日期") deposit = models.DecimalField(max_digits=8, decimal_places=2, default=0, verbose_name="押金") monthly_rent = models.DecimalField(max_digits=8, decimal_places=2, default=0, verbose_name="月租金") status = models.SmallIntegerField(choices=STATUS_CHOICES, default=STATUS_RUNNING, db_index=True)一个容易忽略的问题:同一个房间不允许同时有两个STATUS_RUNNING的合同。这个约束我不仅在Django层面做了校验,还在数据库层面建了部分唯一索引,双保险。不然并发请求下,前台手快点了两次入住,同一天可能冒出两份有效合同,房态彻底乱掉。
这里也建议你加一个MonthlyRenewal或直接在合同上维护last_renew_month字段。城中村很多租客是“一个月一个月住”的,合同到期前如果续租,我在原合同上改end_date,而不是新建合同,这样账单和流水能保持连续。
3.4 财务:押金、租金、水电代收怎么落账
财务我用了“账单+流水”两层的设计。账单是应收概念,流水是实收概念,两者分开,月底对账才有依据。
class Bill(models.Model): BILL_TYPE = [ ("deposit", "押金"), ("rent", "租金"), ("utility", "水电费"), ("refund", "退款"), ("penalty", "违约金"), ] contract = models.ForeignKey(RentalContract, on_delete=models.PROTECT, related_name="bills") bill_type = models.CharField(max_length=16, choices=BILL_TYPE) amount = models.DecimalField(max_digits=8, decimal_places=2, verbose_name="账单金额") status = models.SmallIntegerField(default=0, verbose_name="0未付 1已付 2部分付 3已取消") due_date = models.DateField(verbose_name="截止日期") class Transaction(models.Model): bill = models.ForeignKey(Bill, on_delete=models.PROTECT, related_name="transactions", null=True, blank=True) direction = models.SmallIntegerField(choices=((0, "收入"), (1, "支出")), verbose_name="方向") amount = models.DecimalField(max_digits=8, decimal_places=2, verbose_name="金额") pay_time = models.DateTimeField(auto_now_add=True, verbose_name="交易时间") pay_method = models.CharField(max_length=16, verbose_name="支付方式:cash/wechat/alipay/bank") operator = models.ForeignKey(User, on_delete=models.PROTECT, verbose_name="操作人")水电费比较特殊。我单独建了UtilityReading表,记录每个房间每次抄表的电表读数和用水吨数,然后按“本次读数 - 上次读数”得出用量,再乘以单价生成utility类型的账单。这个逻辑可以确保不管抄表周期多不规则,都能正确算出本期费用。水电单价允许房东在楼栋配置里设置,因为城中村不同楼栋的水电价格经常不一样。
4. 业务闭环的Django实现:房态机、账单与后台
数据模型只是骨架,真正让系统跑起来的是业务闭环。这一节我把核心流程的代码级实现讲一遍。
4.1 用一个字典把房态转换焊死
房态如果不限制跳转,系统很快就会产生脏数据。比如“已预定”的房间不能直接变成“已入住”,必须先经过“签约”;“维修中”的房间也不能被下单。我用一个字典定义合法转换:
ROOM_STATUS_FLOW = { 0: [1, 4], # 空置 -> 已预定、维修中 1: [0, 2, 4], # 已预定 -> 空置(取消)、已入住、维修中 2: [0, 3], # 已入住 -> 空置(退房)、保洁中(待保洁) 3: [0], # 保洁中 -> 空置 4: [0], # 维修中 -> 空置 } def change_room_status(room, target_status, operator=None): allowed = ROOM_STATUS_FLOW.get(room.status, []) if target_status not in allowed: raise ValidationError(f"非法房态流转:{room.get_status_display()} -> {target_status}") room.status = target_status room.save(update_fields=["status"])这样做的好处是:所有业务入口统一调用这个函数,哪怕未来新增一个“今日待入住”的快捷键,也绕不开状态限制。逻辑集中,排查问题时只需要盯这一个函数。
4.2 入住、退房、续租的代码级流程
入住的完整流程我放在一个Django事务里:
from django.db import transaction @transaction.atomic def check_in(room, tenant, start, end, deposit, first_month_rent, operator): if room.status != Room.STATUS_AVAILABLE: raise ValidationError("房间当前不可入住,请刷新房态") contract = RentalContract.objects.create( room=room, tenant=tenant, start_date=start, end_date=end, deposit=deposit, monthly_rent=first_month_rent, status=RentalContract.STATUS_RUNNING, ) Bill.objects.create(contract=contract, bill_type="deposit", amount=deposit, due_date=start) Bill.objects.create(contract=contract, bill_type="rent", amount=first_month_rent, due_date=start) change_room_status(room, Room.STATUS_OCCUPIED, operator) return contract这里押金和首期租金同时生成账单,前台收钱时在Transaction里登记一笔或者在账单上标记已付,再关联对应的支付方式。
退房则要额外处理水电费和押金退款。我写了一个finalize_contract方法,逻辑分为三步:
- 录入截止日期的水电读数,自动生成水电账单;
- 计算押金应退 = 原押金减去待扣的水电欠费、损坏赔偿,若为负数则生成“补收”账单;
- 合同状态置为
FINISHED,房间转入保洁中。
续租最建议在原有合同上延end_date并生成下一期租金账单,避免租客和房源重新绑定,也方便查看同一个租客的连续居住历史。
4.3 账单生成与水电费计算的防错设计
账单生成的频率不是统一的。月租租客按自然月生成,日租租客按“每晚”或者“入住时一次性收完”,城中村多以“入住时就收完整段房费”为主。我在系统里提供了一个“按期间生成账单”的任务:支持选择开始和结束日期,系统按日租价乘以含首含尾天数的逻辑一次性生成租金账单,并把入住时已收到的金额自动标记为已付。
水电费计算的关键就是防止积分错误。我用Django的DecimalField,在代码里也坚持用Decimal而不是float:
from decimal import Decimal, ROUND_HALF_UP def calc_utility_cost(current_reading, last_reading, unit_price): usage = current_reading - last_reading cost = (usage * unit_price).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP) return cost另外,抄表时如果当前读数小于上次读数,直接拦截报警,多半是上次抄错或者换表了。这类小校验能省掉大量月底对账的精力。
4.4 把Admin调成房东看得懂的后台
Django Admin默认界面非常“后台”,房东第一次打开会发懵。我做了几个关键配置让它变得可用:
- 在
RoomAdmin里配置list_filter为楼栋和状态,让房东按自己最关心的维度筛选; list_display里直接展示楼栋、房号、状态、月租价、当前租客;- 合同后台默认按“进行中优先”排序,结束的合同折叠到筛选里;
- 在首页额外挂一个简单的统计面板,显示每个楼栋“入住中房间数”“今日到期合同数”“本月实收金额”。
没必要过度定制,这些调整工作量不大,却能让房东真的愿意用。后面维护期收到的反馈,大多来自这些朴实的功能,而不是花哨的图表。
5. Flask侧只读API与移动端对接
手机端是前台和租客频繁使用的入口。我坚持用Flask做只读API有一个原因:它可以跟Django的繁重请求上下文完全隔离,接口写起来非常轻。
5.1 API的边界设计:只读能解决90%的查询
我对外提供的核心接口非常少:
- 按日期和区域查询可用房间,返回房间号、面积、日租价、月租价;
- 查询某房间未来N天的价格日历;
- 按租客手机号查询自己的合同、账单、缴费记录;
- 公共房源列表带分页。
这四个接口覆盖了手机端的绝大多数高并发查询。所有会产生写入的动作,比如提交预约看房、报修,我仍然通过Django侧专门的API入口处理,因为预约要创建记录,报修要创建工单,这些必须走完整的Django逻辑,享受事务和权限控制。
5.2 token鉴权:不碰Django的session
Flask侧不能依赖Django的session cookie,因为跨框架解密和会话共享很麻烦。我的方案是租客端发放token,存Redis,有效期7天。
Flask封了一个简单的装饰器做鉴权:
import functools import redis r = redis.Redis(host="127.0.0.1", port=6379, db=0) def login_required(f): @functools.wraps(f) def wrapper(*args, **kwargs): token = request.headers.get("X-Token") tenant_id = r.get(f"tenant_token:{token}") if token else None if not tenant_id: return {"code": 401, "msg": "登录已过期"}, 401 kwargs["tenant_id"] = int(tenant_id) return f(*args, **kwargs) return wrapperDjango负责在租客登录成功后,把token = uuid4().hex和租客ID写入Redis,这样两边只用Redis做缓存同步,不需要共享session数据库。这个设计后来证明非常省心。
5.3 空房查询与缓存降级策略
空房查询是最高频接口,也是并发压力最大的接口。我做了两级缓存:
- 对“按日期查询某楼栋可用房”的请求,结果以
available:{building_id}:{date}为key缓存30秒; - 如果Redis服务异常,接口直接降级为查询MySQL,并返回一个“数据可能非最新”的标记,让前端提示用户刷新。
缓存时间特意设为30秒而不是更长,因为空房信息变化快,宁可多查几次库也不要展示陈旧的信息导致“抢房失败”的客诉。
5.4 弱网环境下的移动端体验
城中村手机网络并不稳定,尤其是楼栋地下室和电梯间。移动端对接时我做了两件事:
- 所有列表接口强制分页,每页20条,不允许客户端一次拖走全部数据;
- 前端在下拉刷新失败时,保留上一次成功返回的数据并展示“上次更新时间”,而不是转圈卡死。
图片资源这块单独强调一下:房源照片若要显示在手机端,上传后必须压缩,否则弱网下面一张2MB的图能卡掉用户所有耐心。我在Django侧保存原图的同时生成一个thumb缩略图,Flask接口只返回缩略图地址。
6. 上线之后踩到的四个坑(完整排查链路)
这套系统真正跑起来是在上线后的第二周开始,陆陆续续出来的问题全都是我在设计阶段没预料到的。挑了几个最有代表性的,完整还原一下当时的排查过程。
6.1 双框架同库,MySQL连接数一夜飙高
现象:上线第三天,运维收到告警,MySQL连接数超过260。正常这台服务器最多几十个连接。
排查过程:先看SHOW PROCESSLIST,发现大量来自Flask进程的Sleep连接,每条都挂着不释放。再看Flask侧日志,确认是SQLAlchemy每次请求都新建连接,请求结束也没归还池子。原因是Flask侧连接池参数没有配置,SQLAlchemy默认行为在低并发下也能把连接堆起来。
解决办法:Flask侧SQLAlchemy加上:
engine = create_engine( "mysql+pymysql://user:pass@127.0.0.1/dbname", pool_size=10, max_overflow=20, pool_pre_ping=True, pool_recycle=600, )Django侧则把CONN_MAX_AGE设为60秒。改完后连接数稳定在20个以内。这个坑提醒我:多进程连接同一个库时,连接池参数必须一开始就配好,不要等到生产环境炸了才补。
6.2 float算水电费,月底对账差了几毛钱
现象:月底对账的时候,某栋楼的水电费总额跟手工算的差了几毛钱,房东很不高兴。
排查过程:我核对数据库后,发现个别账单金额出现类似23.550000000000004这样的值。顺藤摸瓜找到原因,是录入电价时用了float乘法。代码里usage * unit_price两个float一乘,精度就丢了。
解决办法:全项目金额字段全部改用Decimal,计算也全部用Decimal,再没有出现过金额尾差。当时觉得很基础的问题,恰恰是最容易忽略的——只要有一次用float乘,账就对不上。
6.3 跨天房费:28号住到3号到底是几天
现象:租客入住时,前台在系统里选了“12月28日入住,1月3日退房”,系统算出房费4天,租客说住了5晚,双方当场争执。
排查过程:代码里计算天数用的是(end_date - start_date).days,得到4天。但租房行业的口径是“含首含尾”:28号晚上住到3号早上,实际占用的晚数是5晚(28、29、30、31、1、2,2号晚上还住着,3号白天退房)。
解决办法:我统一口径为“含首含尾”,计算逻辑改为(end_date - start_date).days + 1,并且在入住确认弹窗上直接展示“本次共住X晚”字样。系统内所有日期差价、水电估算都按这个规则来。从此再没因为这个吵过架。
6.4 房东手机上传20MB原图,接口直接超时
现象:房东在手机端上传房源照片,选的是手机相册原图,20MB的图通过Nginx传到Django,请求等待超时失败。
排查过程:先看Nginx日志,发现上传请求耗时长,Nginx默认的client_max_body_size只允许1MB,直接返回413。调整后能传了,但传上去的图太大,手机端展示和加载都很慢,弱网下根本打不开。
解决办法:前端在上传前用canvas把图片最长边压缩到1600px以内,质量压缩到80%,再传给后端。后端保存文件并生成400px缩略图,供列表页使用。这样单个请求体积从MB级降到300KB以内,弱网也能顺利上传。
7. 把系统装进一台低配服务器,顺带聊聊还能怎么演进
整套系统最终部署在一台2核4G的云服务器上,跑了半年没出过硬件问题。部署方案属于典型的小型Python Web应用组合,给同样预算紧张的项目做个参考。
7.1 服务器与进程布局
我用的是Ubuntu 22.04,装好以下组件:
- Nginx:负责反向代理、静态文件、图片访问;
- Gunicorn:跑Django主服务,3个worker;
- Gunicorn:单独一个worker跑Flask服务,因为接口只读,压力不大;
- MySQL 8.0:业务数据库;
- Redis:token缓存、空房查询缓存;
- Supervisor:守护两个Python进程,崩溃自动拉起。
Nginx配置里两个服务的location互不干扰:
location /api/ { proxy_pass http://127.0.0.1:8001; proxy_set_header X-Real-IP $remote_addr; } location / { proxy_pass http://127.0.0.1:8000; proxy_set_header X-Real-IP $remote_addr; }这里要特别提醒:Django的静态文件和上传图片一定要交给Nginx直接处理,不要让Python进程读文件。我在系统里把MEDIA_ROOT配置指向一个本地目录,Nginx用alias映射到/media/路径,图片加载性能至少翻倍。
7.2 备份与日常维护
我写了一个简单的shell脚本,每天凌晨3点执行mysqldump备份数据库,保留最近7天,再通过rsync推送到另一台内网机器。服务器上还加了日志按天切割,避免access.log无限膨胀把磁盘挤爆。这种规模的项目不需要上什么自动化编排平台,一条crontab加两个脚本就够用了。
#!/bin/bash backup_dir="/backup/mysql" date_str=$(date +\%Y\%m\%d) mysqldump -u backup_user -p'password' rental_db > "$backup_dir/rental_$date_str.sql" find "$backup_dir" -name "*.sql" -type f -mtime +7 -exec rm {} \;7.3 后续演进建议
如果租客端需求继续扩大,我的建议是把Flask侧那些只读API逐步换成更标准化的接口文档,甚至引入接口网关。而如果业务变得非常复杂,需要处理多店、多片区,Django这边可以考虑引入DRF(Django REST Framework),把Django本身变成一套完整的API服务,然后让Flask慢慢退役。
以我个人的体会来说,这套“Django管业务、Flask旁路查询”的组合最大的价值不是技术有多新,而是让不同需求的模块都能选最合适的最小实现。真正踩过坑之后才明白,技术选型争论不重要,重要的是每个模块的职责边界清晰,团队改得动、上线不容易出问题。最后再分享一个小经验:像这种带财务和状态流转的业务系统,开发前先花一周把房态流转图和账务规则写在纸上,比多写一万行代码都管用。