先说明一下很多人看到“django-flask”这个写法第一反应是“这俩框架为什么要放在一起”。实际上在我的工作经历里这种组合并不少见而且往往是最务实的选型。我接过不少校内实训项目和中小型系统开发高校食堂餐饮管理系统这类题目看起来是典型的CRUD作业但真要做到能上线、能扛住饭点高峰、能应付老师验收时的“灵魂拷问”里面的门道比想象中多。这篇就围绕我实际搭建这类系统的经验把从框架选型到数据库设计、从Django主业务到Flask辅助服务的完整思路拆开讲提供可以直接复用的方案。1. 整体架构设计与技术选型思路1.1 为什么是Django和Flask混合架构标题里的“django-flask”很容易让人困惑但在我接触过的多个高校食堂项目中这种架构的真实形态通常是Django负责核心业务和后台管理Flask负责特定领域的轻量接口。不是二选一而是各干各擅长的事。Django的优势在于“全家桶”——自带ORM、Admin后台、认证体系和迁移工具。食堂管理系统的核心是菜品管理、订单处理、用户管理和数据统计这些恰好是Django的舒适区。一个python manage.py startapp就能生成标准目录Admin后台甚至不需要写一行前端代码就能实现基础的数据录入管理对快速交付和后期维护都极其友好。Flask恰恰相反它以“轻”著称。在项目里它通常承担两类工作一是做面向终端的轻量API比如食堂大屏展示系统、扫码点餐的接口透传、或者是给外部系统提供的简单查询接口二是跑一些Django不太方便做的独立任务比如定时统计脚本、WebSocket轻量推送等。在实际项目中我曾经用Flask写过一个独立的菜品推荐接口只依赖一个SQLite数据库和几百行代码部署在内网一台小机器上稳定跑了两个学期。这种混合架构能成立的另一个原因是两者可以通过HTTP通信统一协作。Django侧提供REST APIFlask侧通过请求转发或数据库共享完成数据交互。当然这种架构也有代价比如部署时要跑两个服务、日志管理要分开处理等但相比纯单体架构的臃肿和纯微服务的过度设计它正好卡在高校项目的复杂度需求上。1.2 项目功能边界与角色权限设计高校食堂餐饮管理系统核心角色至少需要四类超级管理员、食堂管理员、窗口员工、普通师生用户。角色不同看到的界面和可操作范围差异非常大。角色核心权限范围典型操作超级管理员全局配置、账号管理、审计日志创建食堂、分配管理员、查看全平台订单食堂管理员本食堂的菜品、窗口、统计上下架菜品、设置库存、查看本食堂流水窗口员工接单、出餐、菜品状态维护修改菜品售罄状态、打印小票、确认出餐普通用户浏览、点餐、评论、充值查询菜品、提交订单、评价、余额支付这四类角色对应着不同的认证策略。普通用户走Django自带的django.contrib.auth扩展字段通过OneToOneField关联Profile食堂管理员和窗口员工放进django.contrib.admin或自定义后台接口层通过JWT或Session认证区分权限。早期做这类系统时很多人习惯把全部角色都塞进一个Admin后台里操作这在验收演示时还说得过去但真实使用场景里体验很差——窗口员工根本不需要看到全局报表超级管理员也不应该去操作单个菜品的上下架。我实际搭建时将师生用户端设计为H5页面手机浏览器直接访问扫码进入食堂管理端使用Django Admin加定制化字段的混合方案超级管理员则通过Admin后台统一管理食堂列表和账号体系。三层各管一段互不干扰。1.3 这种方案能解决什么实际问题食堂系统最常见的痛点是“高峰期并发”、“菜品库存不准确”、“财务对账困难”。这三个问题直接决定了系统的口碑。高峰期并发靠的是后端缓存和数据库索引设计库存不准确靠的是订单和库存的原子性操作财务对账则依赖流水表设计和日终统计任务。混合架构之下Django侧的主业务是这些痛点的核心承载者Flask侧的辅助服务则解决一些旁支需求比如窗口端的极简点单页面Flask渲染一个只有两个接口的简单页面响应速度远优于加载全套Django静态资源比如每日营业数据快照推送Flask的定时任务把统计结果推送给食堂管理员的钉钉或企业微信机器人。这些功能塞进Django里不是不行只是会让主工程越来越臃肿而用Flask独立出来后既可以独立重启部署也不影响主食业务的稳定性。2. 数据库设计食堂系统的“地基工程”2.1 核心数据表结构详解数据库设计决定了系统的天花板。食堂管理系统的表可以划分为五个域用户域、食堂域、菜品域、交易域、评价域。每个域之间通过外键或逻辑关联。用户域我通常这样设计from django.contrib.auth.models import AbstractUser class User(AbstractUser): USER_TYPE ( (student, 在校师生), # 兼容教师身份 (canteen_admin, 食堂管理员), (window_staff, 窗口员工), (superadmin, 超级管理员), ) user_type models.CharField(max_length20, choicesUSER_TYPE, defaultstudent) balance models.DecimalField(max_digits8, decimal_places2, default0) student_id models.CharField(max_length20, blankTrue, nullTrue) phone models.CharField(max_length20, blankTrue, nullTrue) card_number models.CharField(max_length50, blankTrue, nullTrue, uniqueTrue)在这个设计里balance字段是学生账户余额用于对接线下饭卡或校园卡支付的预充值逻辑。card_number用于兼容实体卡刷卡场景——实体卡刷卡终端扫描卡号后通过内部API核销扣款。食堂域和菜品域是系统的核心业务数据class Canteen(models.Model): name models.CharField(max_length100) address models.CharField(max_length200) manager models.ForeignKey(User, on_deletemodels.SET_NULL, nullTrue, related_namemanaged_canteens) status models.BooleanField(defaultTrue) opening_time models.TimeField() closing_time models.TimeField() class Window(models.Model): canteen models.ForeignKey(Canteen, on_deletemodels.CASCADE, related_namewindows) name models.CharField(max_length100) category models.CharField(max_length50) # 例如川菜窗口、面食窗口 staff models.ManyToManyField(User, related_namework_windows, blankTrue) class Dish(models.Model): window models.ForeignKey(Window, on_deletemodels.CASCADE, related_namedishes) name models.CharField(max_length100) price models.DecimalField(max_digits6, decimal_places2) image models.ImageField(upload_todishes/, blankTrue, nullTrue) description models.TextField(blankTrue) stock models.IntegerField(default0) sales models.IntegerField(default0) status models.CharField(max_length20, choices((on_sale, 在售), (sold_out, 售罄), (off_shelf, 下架)), defaultoff_shelf) category models.CharField(max_length30, blankTrue) spice_level models.CharField(max_length10, default不辣, blankTrue) created_at models.DateTimeField(auto_now_addTrue)菜品表的stock字段是库存sales是销量统计。这里有个坑库存字段不应该直接用整数相减来记录剩余量因为订单创建和库存扣减不是同一时刻发生的。比较稳妥的做法是在订单域单独设计一个OrderItem表记录每次扣减明细而Dish.stock只作为展示用冗余字段通过ORM的F()表达式或数据库锁来更新。交易域是整个系统的关键我是这样设计的class Order(models.Model): ORDER_STATUS ( (pending, 待支付), (paid, 已支付), (preparing, 制作中), (completed, 已取餐), (cancelled, 已取消), (refunded, 已退款), ) order_no models.CharField(max_length64, uniqueTrue) user models.ForeignKey(User, on_deletemodels.CASCADE, related_nameorders) canteen models.ForeignKey(Canteen, on_deletemodels.SET_NULL, nullTrue, blankTrue) total_amount models.DecimalField(max_digits8, decimal_places2) status models.CharField(max_length20, choicesORDER_STATUS, defaultpending) pay_method models.CharField(max_length20, choices((balance, 余额), (alipay, 支付宝), (wechat, 微信)), defaultbalance) pickup_code models.CharField(max_length6, blankTrue, nullTrue) remark models.CharField(max_length255, blankTrue) created_at models.DateTimeField(auto_now_addTrue) paid_at models.DateTimeField(blankTrue, nullTrue) class OrderItem(models.Model): order models.ForeignKey(Order, on_deletemodels.CASCADE, related_nameitems) dish models.ForeignKey(Dish, on_deletemodels.SET_NULL, nullTrue) dish_name models.CharField(max_length100) price models.DecimalField(max_digits6, decimal_places2) quantity models.IntegerField(default1) subtotal models.DecimalField(max_digits8, decimal_places2) class Transaction(models.Model): order models.OneToOneField(Order, on_deletemodels.CASCADE, related_nametransaction) user models.ForeignKey(User, on_deletemodels.CASCADE) change models.DecimalField(max_digits8, decimal_places2) balance_after models.DecimalField(max_digits8, decimal_places2) created_at models.DateTimeField(auto_now_addTrue)这个设计的核心是订单流水与账户余额分离。Transaction记录每次余额变动的快照变动前余额、变动后余额这样即使后续需要人工审计或追溯也能完整还原历史时刻的账户状态。OrderItem中冗余存储dish_name和price是因为菜品价格和名称可能在下单后变动如果只存外键历史订单会呈现出“随菜品变动而变动”的错误数据。2.2 数据库索引与查询优化策略高校食堂系统的查询模式高度集中90%的流量都打在“用户查菜品”“用户下单”“食堂看订单”三个动作上。索引设计不当数据库会在并发撑不住时拖垮整个服务。我在实际项目中优先建立以下几个索引订单表按(user_id, created_at)建立联合索引。这个索引覆盖了用户订单列表页最常见的查询模式按用户查历史订单且按时间倒序。订单表按(canteen_id, status)建立联合索引。食堂管理员需要看“本食堂所有进行中的订单”这个索引能让查询走覆盖索引避免回表。菜品表按(window_id, status)建立联合索引。窗口员工查询自家在售菜品的频率极高这个索引能显著加速。交易流水表按(user_id, created_at)建立联合索引用于余额明细分页展示。除索引优化外还需要做分页。很多人写列表页时习惯用Order.objects.filter(userrequest.user)直接渲染全量数据这在数据量小的时候看不出问题但一学期下来订单量上万条之后页面加载会肉眼可见地变慢。稳妥的方案是Paginator分页配合page参数或者对高并发只读接口使用Django内置的cache_page装饰器缓存响应结果20秒。菜品查询接口的短时缓存对数据库压力优化非常明显实测在高峰期能将数据库查询量降低60%以上。2.3 库存扣减的原子性与并发安全食堂点餐系统的“秒杀”场景其实非常典型红烧肉每天只供应80份饭点一到几十个人同时下单。如果库存扣减不保证原子性很容易出现“订单成功但库存变负数”的问题。我踩过这个坑起初的代码是这样写的dish Dish.objects.get(pkdish_id) if dish.stock quantity: return error(库存不足) dish.stock - quantity dish.save()这个逻辑在并发请求下存在明显的竞态条件两个请求同时读到库存等于10都判断库存充足都执行减库存最后库存可能变成8而不是期望的6。解决这个问题的正确姿势是使用Django的F()表达式加条件更新from django.db.models import F updated Dish.objects.filter(pkdish_id, stock__gtequantity).update(stockF(stock) - quantity) if updated 0: return error(库存不足或菜品已下架)这段代码的关键在于判断库存是否充足和扣减库存是两条数据库原子操作不再存在“读到旧值再写回”的竞态窗口。filter(pkdish_id, stock__gtequantity)确保只有库存足够时才执行扣减update返回影响行数为0说明扣减失败。配合select_for_update()行锁方案也同样可行但F()表达式更简洁且不需要手动管理事务边界。对于订单超时未支付的库存回滚可以用Django的transaction.atomic()把订单创建和库存扣减包裹在同一个事务里支付超时后通过定时任务或Celery异步任务把库存加回去。实际项目里我更偏爱“延迟扣减”策略——用户下单后先锁定库存15分钟支付成功后才真正扣减库存超时未支付自动释放。这样可以有效避免用户下单后不支付导致的菜品虚耗。3. Django核心业务模块实战3.1 用户认证与多角色权限实现高校食堂系统有一个容易被忽略的点用户量不大但角色划分层级深。学生可能有一万多名但食堂管理员可能只有个位数。用Django默认的is_staff和is_superuser两个布尔字段无法覆盖多角色需求。我采用的做法是基于user_type字段的权限校验类。先定义一个通用的装饰器from functools import wraps from django.http import JsonResponse def role_required(*allowed_roles): def decorator(view_func): wraps(view_func) def _wrapped_view(request, *args, **kwargs): if not request.user.is_authenticated: return JsonResponse({code: 401, msg: 未登录}, status401) if request.user.user_type not in allowed_roles: return JsonResponse({code: 403, msg: 无权限访问}, status403) return view_func(request, *args, **kwargs) return _wrapped_view return decorator使用示例role_required(canteen_admin, superadmin) def canteen_stats(request, canteen_id): # 只有食堂管理员和超级管理员可以查看本食堂的统计数据 ...这里需要特别提及的是Django Admin后台不要开放给所有角色。我曾经在一个项目里把窗口员工加进了Admin后台虽然操作很方便但Admin后台的权限管理模型app_labelmodel粒度和业务角色并不完全匹配容易出现“员工能看所有订单”的权限失控风险。稳妥的做法是为食堂管理员单独写一个极简的canteen_dashboard视图函数只渲染本食堂的订单和菜品信息而不是暴露完整的Admin。3.2 菜品管理图片上传与缓存策略菜品图片是食堂系统里最容易导致性能问题的模块。老师验收时演示上传菜品图片手机拍摄的原始图片动辄3MB以上Django默认提供静态文件服务但这在真实部署时会直接把Web服务器拖垮。图片上传处理的成熟方案是原图压缩再存储。可使用Pillow库在save()时做压缩from PIL import Image import os def compress_dish_image(src_path, max_width800, quality75): img Image.open(src_path) if img.width max_width: ratio max_width / img.width new_height int(img.height * ratio) img img.resize((max_width, new_height), Image.LANCZOS) img.save(src_path, JPEG, qualityquality, optimizeTrue) return src_path这样处理后一张3MB的图片可以压缩到100-200KB左右。配合DjangoMEDIA_ROOT和Nginx的alias配置图片访问走静态文件服务不占用Python进程的资源。还需要注意文件的存储目录按日期分桶比如dishes/2025/06/01/避免单目录文件数过多影响文件系统性能。3.3 点餐下单与订单流转逻辑点餐下单是整个系统最核心的交互流程。用户的操作路径是浏览菜品 - 加入购物车 - 提交订单 - 支付 - 窗口取餐。这五个环节在实际设计时可以压缩为三个核心接口提交订单接口接收窗口ID、菜品ID列表及数量返回订单号。支付接口接收订单号和支付方式扣款成功后将订单状态更新为已支付。取餐码核销接口用户在窗口展示取餐码员工确认出餐。由于涉及多个数据表的变更提交订单的逻辑必须包裹在事务中from django.db import transaction transaction.atomic def create_order(user, order_data): canteen_id order_data[canteen_id] items_data order_data[items] canteen Canteen.objects.get(pkcanteen_id) total 0 order_items [] for item in items_data: dish Dish.objects.select_for_update().get(pkitem[dish_id]) if dish.status ! on_sale: raise ValueError(f{dish.name} 已下架) if dish.stock item[quantity]: raise ValueError(f{dish.name} 库存不足) subtotal dish.price * item[quantity] total subtotal # 扣减库存 dish.stock dish.stock - item[quantity] dish.sales dish.sales item[quantity] dish.save() order_items.append(OrderItem( dishdish, dish_namedish.name, pricedish.price, quantityitem[quantity], subtotalsubtotal )) # 生成订单号 order_no generate_order_no() order Order.objects.create( order_noorder_no, useruser, canteencanteen, total_amounttotal, statuspaid, # 默认余额支付 pay_methodbalance, pickup_codegenerate_pickup_code(order_no), ) OrderItem.objects.bulk_create(order_items) # 扣减用户余额 user.balance user.balance - total if user.balance 0: raise ValueError(账户余额不足) user.save() Transaction.objects.create( orderorder, useruser, change-total, balance_afteruser.balance, ) return order这里有个关键点余额扣减是在事务内的最后一步且扣减前重新读取了用户余额。这样即使同一用户的并发下单请求也会因为数据库行锁而串行化不会出现余额透支的问题。select_for_update()锁定了菜品记录保证同一时间只有一个事务能修改该菜品的库存字段。订单号的生成也有讲究。用UUID直接做订单号冗长且不直观用时间戳加随机数有碰撞风险。我常用这种方案import time import random import string def generate_order_no(): timestamp time.strftime(%Y%m%d%H%M%S) random_part .join(random.choices(string.digits, k6)) return f{timestamp}{random_part}这个方案的碰撞概率在单日十万级订单下可以忽略不计且订单号时间前缀方便调试时按时间排查。取餐码是食堂场景里提升体验的关键小功能。我设置为6位数字由订单号后6位派生窗口员工根据取餐码叫号。取餐码生成后窗口员工的“确认出餐”操作将订单状态更新为completed流程闭环。3.4 数据统计与可视化报表食堂系统的数据统计模块常用的指标是“食堂日营业额”“窗口菜品销量TOP10”“用户消费频率分布”。这些统计如果用Django ORM直接计算在高并发场景下会消耗大量数据库资源。比较成熟的做法是定时任务预聚合。建一张DailyReport表每天凌晨通过Celery或系统crontab执行聚合任务把前一天每个食堂、每个窗口的营业额、订单数、客单价写入表中。前台的展示接口直接查这张预聚合表响应速度是毫秒级且不压数据库。统计模块的实现还会涉及一些细节问题比如时区处理。Django默认USE_TZTrue数据库存的时间是UTC如果直接按本地日期分组统计跨天时容易统计不准。稳妥的方式是在查询时显式转换时区from django.db.models import Sum, Count from django.db.models.functions import TruncDate from django.utils import timezone from datetime import timedelta today_start timezone.localtime(timezone.now()).replace(hour0, minute0, second0, microsecond0) today_end today_start timedelta(days1) orders Order.objects.filter( created_at__gtetoday_start, created_at__lttoday_end, canteen_idcanteen_id, statuspaid ).aggregate(total_amountSum(total_amount), order_countCount(id))这种方式可以保证统计结果和食堂的实际营业日一致。我遇到过不止一次因为时区处理不当导致日结报表差一天的案例这是非常典型的隐性坑。4. Flask辅助服务的设计与实现4.1 为什么需要Flask独立服务在设计食堂管理系统架构时Django承担了大部分业务逻辑但有两个场景我倾向于单独用Flask解决。第一个场景是窗口员工极简操作台。食堂窗口员工的操作极其高频且机械确认出餐、切换菜品状态。他们不需要看到复杂的数据看板也不需要完整的后台管理界面。如果让窗口员工打开Django Admin操作加载时间、界面复杂度都是问题。用Flask写一个只有两个接口和一个静态页面的小服务部署在窗口的触屏一体机上页面秒开操作路径最短。第二个场景是对外提供轻量查询接口。比如食堂门口的大屏需要显示实时就餐人数和菜品余量导购终端需要轮询订单状态。这类接口不需要走Django完整的认证流程和中间件链用Flask实现一个极简API更轻量。两个服务通过共享同一个数据库或HTTP请求转发完成数据同步各自独立重启互不影响。4.2 用Flask快速构建菜品余量大屏接口大屏展示接口的逻辑很简单每5秒拉取一次在售菜品余量数据以JSON格式返回。用Flask实现只需要几十行代码from flask import Flask, jsonify from flask_sqlalchemy import SQLAlchemy from datetime import datetime app Flask(__name__) app.config[SQLALCHEMY_DATABASE_URI] mysqlpymysql://user:passlocalhost/canteen_db?charsetutf8mb4 db SQLAlchemy(app) class Dish(db.Model): __tablename__ dish id db.Column(db.Integer, primary_keyTrue) name db.Column(db.String(100)) stock db.Column(db.Integer) window_id db.Column(db.Integer) status db.Column(db.String(20)) app.route(/api/screen/dishes) def screen_dishes(): dishes Dish.query.filter(Dish.status on_sale).all() data [{ id: d.id, name: d.name, stock: d.stock, window_id: d.window_id, } for d in dishes] return jsonify({code: 0, data: data, time: datetime.now().strftime(%H:%M:%S)}) if __name__ __main__: app.run(host0.0.0.0, port5001)这里要注意的是Flask服务直接读取Django维护的MySQL数据库但不经过Django的ORM层和缓存层。这意味着Django对菜品数据的修改比如价格、库存要实时生效就必须保证数据库事务提交后Flask才能查询。Django默认的事务隔离级别是READ COMMITTED吗并不是MySQL默认是REPEATABLE READ。但Django每次请求结束自动提交事务Flask侧的查询在事务提交后自然能看到最新数据实际运行中没有出现脏读问题。如果对数据实时性要求更高可以在Flask里加一层HotCache每10秒从数据库拉取数据并缓存在内存中接口直接读内存返回数据库负载几乎为零。4.3 双服务通信模式与数据一致性Django和Flask两个服务共存最怕的是数据一致性出问题。我采用的核心策略是数据库为最终事实源HTTP为实时通信辅助。举一个实际场景用户在微信端提交订单后Django侧生成了订单记录此时窗口员工的Flask操作台需要立即看到新订单。最直接的方式是Flask操作台轮询查询订单表把状态为paid且completed为空的订单拉出来。但轮询频率太高会压力数据库太低则订单延迟明显。我的方案是增加一个order_notify标记字段。Django在订单支付成功后通过HTTP请求通知Flask服务的/api/notify/new_order接口Flask收到后立即更新自己的内部缓存队列。窗口员工的操作台页面再通过WebSocket或长轮询获取实时数据。这样既保证了实时性也避免了两边同时高频操作数据库。4.4 Flask服务部署与独立维护Flask服务通常部署在同一台服务器的另一个端口用Nginx做路径分流。比如/api/screen/开头的请求转发到Flask的5001端口其余请求转发到Django的8000端口。这样对前端来说API路径是统一的不需要关心后端服务是谁在处理。部署上Flask服务可以用Gunicorn启动gunicorn -w 2 -b 127.0.0.1:5001 app:appDjango则用uWSGI或Gunicorn多worker模式跑8000端口。两个服务各自独立重启互不影响。比如只更新了Flask的接口逻辑重启Flask进程即可不影响正在跑的Django服务。这个特性在项目维护阶段非常实用。5. 实际部署与常见问题排查5.1 从开发到上线的踩坑实录这里整理我在多个项目中实际遇到、且网上教程不太会提到的高频问题。问题一Django的ALLOWED_HOSTS配置缺失导致部署后无法访问。开发环境跑localhost没问题一旦部署到服务器用IP或域名访问直接报DisallowedHost错误。解决办法是把服务器IP或域名加入ALLOWED_HOSTS列表。问题二静态文件和媒体文件的路径配置。开发时Django能直接服务静态文件部署后如果Nginx配置不对页面能打开但所有CSS、图片全挂。检查项目里STATIC_ROOT和MEDIA_ROOT的配置是否和Nginx的location块匹配。我的建议是统一用django.conf的settings管理这三个变量按DEBUGTrue/False切换不同策略避免部署前手忙脚乱。问题三数据库迁移冲突。团队协作时migrations目录下容易出现多个开发者的迁移文件冲突。解决方法是约定每个开发者在makemigrations之前先pull最新代码migrate后再继续开发。如果冲突已经产生用python manage.py makemigrations --merge合并即可。5.2 性能调优的实战心得食堂系统的峰值流量集中在午餐和晚餐时段各约40分钟。服务器的性能压力不是持续性的而是脉冲式的。针对这个特征可以从三个层面做性能优化。第一层是缓存策略。菜品列表接口使用cache_page(60 * 5)缓存5分钟菜品详情和食堂列表同理。订单创建接口不能缓存但可以加throttling限流防止刷单。静态资源的缓存交给Nginx配置expires 7d即可。第二层是数据库连接池。Django默认每个请求新建数据库连接高峰期会创建大量连接。建议给数据库连接加CONN_MAX_AGE60配置让连接在60秒内复用。实测在阿里云RDS上这个配置能把数据库连接数从几百降到几十效果非常显著。第三层是异步任务拆分。像发送订单通知、生成日结报表、导出Excel这类非核心链路的耗时操作全部通过Celery丢给后台worker执行。Django主进程只负责响应请求和写入任务队列响应时间可以压缩到30ms以内。对接校园一卡通系统时还有一个值得一提的点。校园卡系统的接口往往是SOAP旧协议Django直接调用容易超时卡死。我的方案是写成Celery定时任务每5分钟梯度重试拉取交易记录成功则更新用户余额同步失败则记录日志由管理员人工处理。绝不让第三方接口的不稳定影响主业务的稳定性。5.3 安全加固与常见漏洞修复作为面向全校师生的系统安全问题不能只停留在“能跑就行”的层面。以下几个位置项目验收时经常被问到也是真实攻击者最常钻的空子。SQL注入Django ORM本身做了参数化查询基本免疫。但要注意不要使用raw()方法拼接字符串。Flask服务若使用原生SQL务必使用参数绑定。XSS攻击Django模板默认转义变量输出但mark_safe()或|safe过滤器会关闭转义。评论功能里的用户输入尤其是重灾区需要对富文本内容做白名单过滤。CSRF防护Django的csrf_token中间件默认开启但如果写了纯API接口给微信小程序等第三方调用反而需要用csrf_exempt关闭并改用Token认证。越权访问接口鉴权不能只看“是否登录”还要校验“是否有权限操作这个资源”。比如窗口员工只能操作自己所属窗口的菜品走在URL里传入其他窗口的ID也能操作就是典型的水平越权漏洞。敏感信息泄露开发时常用的settings.py调试配置如DEBUGTrue、数据库明文密码在部署前必须检查清理。我建议把数据库密码和SECRET_KEY放到环境变量或本地core_settings.py不进入版本控制中。5.4 常见问题速查表问题现象可能原因排查方法解决方案菜品图片上传后无法访问MEDIA_ROOT或Nginx映射配置错误检查图片URL是否404查看目录权限修正MEDIA_ROOT配置确认Nginxlocation /media块订单支付成功但余额未扣事务未包含扣款操作查看订单和交易流水表将扣款放入与订单创建相同的事务内大屏接口偶尔返回过期数据Flask缓存未及时更新检查缓存过期时间配置将缓存时间调整为5-10秒用户无法登录但后台账号正常Session配置或Cookie域错误检查浏览器Cookie和SESSION_COOKIE_DOMAIN确认域名和Cookie域一致Admin后台中文乱码数据库编码非utf8mb4查询表字符集修改表和连接的字符集为utf8mb45.5 项目复盘与扩展建议做完一个高校食堂餐饮管理系统我认为最重要的收获不是熟悉了Django或Flask的语法而是建立起了一套“从业务场景倒推技术方案”的思维方式。食堂系统表面上是点餐CRUD实际上是一个典型的高并发、强数据一致性、多角色权限的分布式系统微缩案例。这个项目后续可以扩展的方向有很多接入人脸识别支付替代实体校园卡的刷卡流程。Django侧只需增加一个face_id字段人脸特征数据可以存到独立的向量数据库支付时比对特征后走现有的余额扣款链路切换成本极低。对接外卖配送或“预约自取”功能。约餐自取比现场点餐增加了时间预约的逻辑需要给Order增加expected_pickup_time字段并增加一个定时任务处理超时未取的订单。增加食品安全溯源模块。给菜品增加供应商批次信息一旦有食安反馈可以快速定位到同一批次的菜品。数据库层面只需要增加SupplyBatch表并与Dish表建立关联。部署到云服务器并接入监控报警。Web服务最怕半夜挂掉没人知道用现成的云监控比如阿里云监控配置接口可用性探测一旦/healthz接口连续失败三次就触发电话告警比事后补救要靠谱得多。根据我的个人经验这个项目的关键不在于用了多少新技术而在于有没有真正从运营角度去思考食堂高峰期的业务逻辑。把极端场景想明白、把数据一致性处理透彻、把后台操作路径精简到极致这才是“管理系统”这四个字背后的真正含义。如果后续有人想在这个基础上深度迭代我建议优先做库存预测——根据历史销售数据预测各窗口次日的备货量这既能让食堂减少食材浪费也能让学生少吃到“售罄”闭门羹是投入产出比最高的优化方向。