Django+Flask双框架实现大学生社团场地预约系统实战详解

发布时间:2026/10/13 4:42:27
Django+Flask双框架实现大学生社团场地预约系统实战详解
大学社团活动场地预约这个需求几乎每个高校都躲不开。早点抢到排练厅、周末能用上多功能报告厅对社团来说跟过节差不多。我见过太多用微信群接龙、共享Excel表、甚至学生会办公室里贴一张值班表来解决预约的案例结果就是各种时间撞车、场地空着却没人登记、活动结束了管理员还不知道该谁打扫。这篇文章先从这个项目标题里那个不太寻常的“django-flask”说起。第一次看到标题里把Django和Flask并列的写法我也愣了一下这两个框架不是竞争关系吗后来实际梳理需求才明白这类校园管理系统往往同时存在两类完全不同的工作一边是给管理员用的重后台——场地维护、预约审核、统计报表、权限管理另一边是给学生用的轻接口——查场地、看空闲时段、提交预约。前者让Django做极其舒服后者用Flask写又格外顺手。所以我在落地这套系统时没有纠结二选一而是让它们各管一摊、共享同一个数据库。这篇博文就按我实际开发的顺序把大学生社团活动场地预约管理系统的架构设计、业务建模、冲突检测、审核链路、部署测试逐个拆开讲。如果你正准备做场地预约、实验室预约一类系统或者想在Django和Flask之间彻底搞明白怎么取舍这篇应该能帮你少走不少弯路。1. 双框架架构拆解Flask管接口、Django管后台这样分工到底合不合理1.1 标题里的双框架是怎么回事大多数课程的毕设题目写成“django-flask”时本意往往是二选一。但我在这个项目里发现真按“二选一”去实现反而别扭。因为系统的两端需求差异实在太大管理端需要用户管理、角色权限、场地信息 CRUD、预约审核、操作日志、统计报表。这种业务天然适合 Django因为它自带 Admin、ORM、迁移工具和成熟的 auth 系统一个人也能很快把后台框架搭起来。学生端需要快速查看场地、按日期查空闲时段、提交预约、查审核结果。这些接口逻辑相对简单追求轻巧直接用 Flask 写一个 REST API 再合适不过。于是最终方案变成了一个“双进程共享数据库”的协作架构Django 服务负责核心业务和管理后台Flask 服务负责对外提供场地查询与预约提交 API。两个服务完全独立启动谁也不调用谁的代码只是连接同一个 MySQL 数据库。一开始有人觉得这样“多此一举”但实际跑起来开发效率反而高不少。1.2 为什么主管理端选DjangoAPI端选Flask选型这件事关键从来不是哪个框架“更牛”而是哪一半业务更需要哪种开发范式。我直接用一张表说明当时的分工权衡维度DjangoFlask定位重后台、全家桶轻接口、微服务式用户认证自带 User 模型与权限体系需要自己写 Token 或引第三方扩展ORM 与迁移集成完善迁移命令直接可用通常需要搭配 SQLAlchemy 才能用得有舒适感后台管理Admin 站点基本开箱即用需要自己撸一套管理界面写 REST API冗余感明显要写一堆 serializer路由和视图极其自由几行代码就能返回 JSON新手友好度约定多学习曲线稍陡上手快但工程化能力弱当时选择这种组合还有一个非常现实的原因这套系统里管理后台的代码量远大于学生端 API 的代码量。如果所有代码都堆在 Django 里为了给小程序或第三方系统提供接口还要额外引入 Django REST Framework写一堆类视图如果全写在 Flask 里用户权限、迁移、Admin 后台又得全部手动实现。两边分开等于让框架做自己最擅长的事。1.3 双服务共存的连接方式与目录结构两个进程不可能直接在代码里“互相 import”所以连接方式必须围绕共享资源和接口契约来设计。我实际用的项目结构大概是这样的campus_site/ ├── manage_system/ # Django 主工程 │ ├── manage.py │ ├── settings.py │ └── apps/ │ ├── users/ │ ├── rooms/ │ └── reservations/ ├── api_service/ # Flask 轻接口工程 │ ├── app.py │ ├── api/ │ │ ├── availability.py │ │ └── reserve.py │ └── config.py ├── docs/ └── nginx.conf关键点在于config.py和settings.py里配置同一个数据库地址。比如两边都从环境变量SITE_DB_DSN读取连接串这样换环境时只需要改一个地方。另一个容易忽略的点是连接池Django 里设置CONN_MAX_AGE60Flask 的 SQLAlchemy 里设置pool_pre_pingTrue避免数据库服务重启导致连接失效。学生端的接口服务还接了 Redis把场地当天的空闲时间段缓存起来Django 后台审核通过或取消预约后主动清理对应 key避免两边读到脏缓存。2. 场地预约的业务模型角色权限、状态机和最容易漏掉的业务规则2.1 四类角色的权限边界很多同类系统只分了“用户”和“管理员”两级但放到大学生社团场景里完全不够用。一套真正能落地的预约管理系统至少需要四类角色普通学生查看场地信息、为自己提交预约、取消待审核的预约、查看自己的预约记录。社团负责人在普通学生权限的基础上可以代表社团提交预约能查看本社团的预约历史和审核结果还能对活动参与人数做预登记。场地管理员维护场地信息、开放时间和容纳人数审核预约单确认活动开始与结束场地现场有问题时可以强制取消预约。系统管理员管理用户和角色、查看全站统计、处理异常预约。这里最值得说的是“社团负责人”这个中间角色。如果没有它所有报名、排练、活动场地申请都要压到场地管理员一个人身上有了它学生端的初审分流了一部分工作管理员只需要处理已经通过初审的预约。Django 的角色体系可以直接放在User.role字段上用一个IntegerField存角色枚举配合自定义PermissionRequiredMixin做访问控制。2.2 预约单的完整状态流转场地预约不是提交完就结束了一张预约单最少应该有六个状态待审核 PENDING预约已提交等待审核。已通过 APPROVED管理员确认通过场地被占用。已拒绝 REJECTED管理员驳回拒绝原因必须填写。已取消 CANCELLED申请人自己在待审核阶段取消或管理员在特殊情况下强制取消。使用中 IN_USE活动开始后场地管理员确认场地已交付使用。已完成 COMPLETED活动结束管理员确认场地清理完毕。流转路径大致是提交后进入待审核管理员通过后变成已通过活动开始变成使用中活动结束变成已完成待审核阶段学生取消变已取消管理员拒绝变已拒绝已通过状态如果要取消则必须由管理员操作并且要在约定时间之前完成。我把这个状态机写成模型方法封装在Reservation里防止业务代码里到处都是status xxxx的裸赋值后面查 bug 会少很多痛苦。2.3 最容易漏掉的业务规则系统能不能真正上线往往取决于业务规则定得细不细。我在这个项目里整理出来的关键规则包括单次预约时长上限为 4 小时活动时间最早 8:00、最晚 22:00跨天预约默认禁止。最早提前 7 天预约最晚提前 2 小时提交预约。预计参与人数不能超过场地容量。同一场地同一时间段无论预约单是“待审核”还是“已通过”都视为占用时间片。一个月内累计 3 次无故取消或爽约的社团进入黑名单下月不能预约热门场地。这些规则不能只写在页面提示里必须在后端事务中统一校验。前端校验只是改善体验后端校验才是真正的防线否则任何人绕过页面直接调接口规则就全被架空了。3. 冲突检测是核心关卡四种重叠情况、数据库锁和并发超卖3.1 时间段重叠的四种情况场地预约系统的灵魂其实是“怎么判断两个时间段有没有冲突”。新手最容易在这个地方写错。假设新预约的时间区间是[S1, E1]库里已有预约的区间是[S2, E2]一共有四种重叠情况S1 落在已有区间内S2 S1 E2E1 落在已有区间内S2 E1 E2新预约完全包住已有预约S1 S2 且 E1 E2已有预约完全包住新预约S2 S1 且 E1 E2虽然四种情况看起来很复杂但判断条件可以统一成一个表达式S1 E2 AND E1 S2这个式子就是区间相交的标准条件。边界上如果允许“首尾相连”的时间连续使用比如 10:00-12:00 结束、12:00-14:00 开始那么判断条件里要严格用和如果前后时间必须留出缓冲则要改成和。我实际采用的是严格小于/大于因为场地管理员需要留出收拾时间前后预约贴太紧会导致上一场活动还没清场。3.2 冲突检测的ORM实现放在 Django 里检测冲突的代码并不复杂from django.db import models class Reservation(models.Model): class Status(models.TextChoices): PENDING PENDING, 待审核 APPROVED APPROVED, 已通过 REJECTED REJECTED, 已拒绝 CANCELLED CANCELLED, 已取消 IN_USE IN_USE, 使用中 COMPLETED COMPLETED, 已完成 room models.ForeignKey(rooms.Room, on_deletemodels.CASCADE, related_namereservations) applicant models.ForeignKey(users.User, on_deletemodels.CASCADE) org_name models.CharField(max_length50, blankTrue) start_time models.DateTimeField() end_time models.DateTimeField() purpose models.CharField(max_length200) participants models.IntegerField(default1) status models.CharField(max_length10, choicesStatus.choices, defaultStatus.PENDING) reject_reason models.TextField(blankTrue) created_at models.DateTimeField(auto_now_addTrue) class Meta: db_table reservation constraints [ models.CheckConstraint( checkmodels.Q(end_time__gtmodels.F(start_time)), nameend_after_start, ) ] indexes [ models.Index(fields[room, status, start_time, end_time], nameidx_room_status_time), ] def has_conflict(room_id, start_time, end_time, exclude_idNone): queryset Reservation.objects.filter( room_idroom_id, status__in[Reservation.Status.PENDING, Reservation.Status.APPROVED], start_time__ltend_time, end_time__gtstart_time, ) if exclude_id: queryset queryset.exclude(idexclude_id) return queryset.exists()exclude_id参数很重要。编辑预约单、管理员审核时要把自己这张预约单排除掉否则就会出现“查到自己导致无法通过审核”的尴尬情况。我在本地上线前就是因为漏了这个参数管理员每次刷新审核页面都提示冲突排查了半天才发现过滤掉了别人、没过滤掉自己。3.3 并发场景下的超卖问题has_conflict()加一层exists()查询在单用户场景下没问题。但如果两个学生同时提交同一场地同一时段的预约两个进程同时跑冲突检测可能都查不到对方于是两条记录都写进库系统就“超卖”了。要根治这个并发问题需要在数据库层面做锁与约束。我采用的方案是行锁 事务在同一个事务里先用select_for_update()锁住场地记录再执行冲突检测。场地行被锁住后同一场地的并发预约请求会串行化执行后一个请求执行冲突检测时就能看到前一个请求写入的数据。from django.db import transaction class ConflictError(Exception): pass transaction.atomic def create_reservation(room_id, start_time, end_time, applicant): room Room.objects.select_for_update().get(pkroom_id) if has_conflict(room_id, start_time, end_time): raise ConflictError(该时间段已被占用请更换场地或时间) return Reservation.objects.create( room_idroom_id, applicantapplicant, start_timestart_time, end_timeend_time, )锁住场地行比锁预约记录更直接因为所有冲突都围绕“同一块场地”发生。这个方案的缺点是写入吞吐量有限但高校社团预约本身就不是高并发场景一个场地一天最多几十次预约行锁完全够用。如果你非要追求极限性能可以在表上加UniqueConstraint(fields[room, start_time])做兜底但这样会把“同一个开始时间只能有一条预约”作为硬性限制业务上不一定合理所以我在项目里没有采用唯一约束兜底而是靠事务锁保证正确性。3.4 我踩过的冲突检测坑这个模块我重写了两遍踩过的坑基本都在边界情形里。第一审核操作时忘了二次冲突校验。待审核预约也占用时间片所以管理员审核 A 通过时B 可能也早就提交了同一时段。如果审核时不重新跑一次has_conflict()就会出现两张“已通过”的预约单。解决方式是把审核也放进事务里通过前重新查冲突。第二时区问题导致凌晨判断偏差。项目里USE_TZTrue前端传过来的是北京时间数据库存的是 UTC 时间如果不做换算晚上 22:00 的预约会被当成 14:00冲突判断自然出错。我在所有视图入口统一做了一件事接收前端本地时间字符串后先make_aware传给 ORM 前确保是 UTC 时间查询和存储完全一致。第三只用字符串比较时间段。有一次为了图省事把日期时间转成2025-05-01 10:00字符串直接比较结果 20:00 到次日 10:00 这种跨天预约直接判断错误。后来全部改成原生datetime对象比较一条代码都没多写问题立刻消失。第四开发时用 SQLite 调试、上线用 MySQL两者的并发表现完全不一样。SQLite 对并发写支持很弱本地能跑通不代表线上没问题尤其是事务锁这种机制必须在 MySQL 或 PostgreSQL 下验证。4. 数据建模与ORM落地从E-R关系到Django模型和索引实战4.1 核心数据表与关系这个系统最核心的几张表其实用一句话就能说清用户、场地、预约记录、通知、操作日志。其中用户和预约记录是多对多关系不在业务上是一对多。一个用户可以发出多条预约一个场地可以有多个预约记录用户与场地之间没有直接关系只有通过预约记录间接关联。预约记录里除了外键还要记录社团名、参与人数、用途这些字段是为了支持后续的统计报表。通知表用来存站内信日志表用来记录关键操作。很多人做管理系统会省略日志表但场地预约系统非常需要它因为一旦出现两个社团抢同一个时间段的纠纷管理员必须能回答“谁先提交的、谁审核的、改了什么”。4.2 Django模型实现我把Room和Reservation的实现放在一个models.py里核心关系如下class Room(models.Model): class Type(models.TextChoices): INDOOR INDOOR, 室内 OUTDOOR OUTDOOR, 室外 AUDITORIUM AUDITORIUM, 报告厅 name models.CharField(max_length50) room_type models.CharField(max_length20, choicesType.choices) capacity models.IntegerField() location models.CharField(max_length100) open_time models.TimeField(default08:00) close_time models.TimeField(default22:00) status models.BooleanField(defaultTrue) # 是否可预约 class Meta: db_table room indexes [models.Index(fields[room_type, status], nameidx_type_status)]用户表方面我直接用AbstractUser扩展而不是另建一张独立用户表。因为 Django 的 admin、permission 机制都依赖 auth 内置模型另建一张表等于把现成的轮子扔掉class User(AbstractUser): class Role(models.IntegerChoices): STUDENT 1, 普通学生 ORG_LEADER 2, 社团负责人 ROOM_ADMIN 3, 场地管理员 SUPER_ADMIN 4, 系统管理员 role models.IntegerField(choicesRole.choices, defaultRole.STUDENT) phone models.CharField(max_length20, blankTrue) college models.CharField(max_length50, blankTrue) student_id models.CharField(max_length20, blankTrue) class Meta: db_table user4.3 索引优化与慢查询排查第一次做压力测试时系统在不到 2000 条预约记录的情况下预约提交接口响应时间就飙到了 1.5 秒。一看日志问题出在冲突检测的exists()查询上——没有索引MySQL 只能全表扫描。后来给Reservation加了联合索引(room_id, status, start_time, end_time)响应时间直接掉到几十毫秒。加索引这件事要克制不是每个字段都加。我总结的原则是优先覆盖“高频查询的条件列”也就是where子句里出现频率最高的那几个字段。在这个项目里所有核心查询都逃不开room_id、status、start_time、end_time这四个字段所以联合索引把它们按顺序排进去。至于applicant查个人预约记录那是另一个相对低频的查询路径单加一个(applicant_id, status)索引就够了。5. 预约审核链路设计三层角色的授权边界与状态变更记录5.1 学生与社团负责人的权限差异实现角色权限的差异我通过在视图层做统一入口解决。所有需要登录的接口都先走一个get_current_role()工具函数然后不同的角色进入不同的处理分支。Django 自带的LoginRequiredMixin解决“有没有登录”的问题自定义的RoleRequiredMixin解决“角色够不够”的问题class RoleRequiredMixin: required_roles None def dispatch(self, request, *args, **kwargs): if not request.user.is_authenticated: return self.handle_no_permission() if self.required_roles and request.user.role not in self.required_roles: return JsonResponse({error: 权限不足}, status403) return super().dispatch(request, *args, **kwargs)社团负责人的特殊之处在于他们能访问一个org_reservations接口查看本社团所有成员提交的历史预约。这里我没有让每个学生填一遍社团名而是在用户表里增加了一个org_name字段社团负责人创建预约时会自动把当前用户所属的社团名带上去这样后面统计的时候不用再做模糊匹配。5.2 管理员的审核与二次冲突校验审核接口是整个后台最敏感的操作既要防止重复审核也要防止审核通过时产生新的时间冲突from django.views.decorators.http import require_POST require_POST def review_reservation(request, reservation_id): action request.POST.get(action) # approve / reject with transaction.atomic(): res Reservation.objects.select_for_update().get(pkreservation_id) if res.status ! Reservation.Status.PENDING: return JsonResponse({error: 该预约已被处理不能重复操作}, status400) if action approve: if has_conflict(res.room_id, res.start_time, res.end_time, exclude_idres.id): return JsonResponse({error: 当前时间段已冲突建议驳回或改期}, status409) res.status Reservation.Status.APPROVED elif action reject: reason request.POST.get(reason, ).strip() if not reason: return JsonResponse({error: 驳回必须填写原因}, status400) res.reject_reason reason res.status Reservation.Status.REJECTED else: return JsonResponse({error: 无效操作}, status400) res.save() OperationLog.objects.create( operatorrequest.user, actionfreview_{action}, object_typeReservation, object_idres.id, detailf{res.org_name or res.applicant} 的场地预约被{action}, ) return JsonResponse({ok: True})这段代码里有三个关键点一是select_for_update()锁住了预约记录本身防止两个管理员同时打开同一张预约单然后互相覆盖二是驳回必须带原因这是业务规则三是审核通过前重新跑冲突检测把“待审核转已通过”过程中可能出现的并发问题挡住。5.3 操作日志与状态变更记录日志表结构非常朴素关键在写入时机的统一。我在审核、取消、场地维护这些动作里都手动插一条OperationLog记录。有人建议用 Django signal 在post_save里自动写日志但我实际发现手动写更可控很多业务动作是在一个事务里完成多个字段更新的如果只监听post_save会漏掉“驳回原因”“操作结果”这些上下文。日志里必须记录四个东西谁操作的、针对哪条数据、做了什么动作、操作前后的关键状态。有了这张表管理员查纠纷时翻一下日志就能还原完整时间线省得双方各执一词时拿不出证据。6. 前端页面与接口联动日历视图、时间选择器与消息通知6.1 场地日历视图的实现思路学生端最核心的页面是“查空闲场地”。我让 Flask 提供数据结构化查询接口前端页面展示一个周视图日历。接口大概长这样GET /api/rooms/1/availability?date2025-05-01返回的 JSON 结构如下{ room_id: 1, date: 2025-05-01, open_time: 08:00, close_time: 22:00, slots: [ {start: 08:00, end: 12:00, status: free, reservation_id: null}, {start: 12:00, end: 14:00, status: occupied, reservation_id: 101}, {start: 14:00, end: 18:00, status: free, reservation_id: null} ] }前端拿到这个 JSON 后用不同颜色渲染每个时间段绿色表示空闲、红色表示已被预约、黄色表示待审核。注意这里的status是后端算好的不能由前端根据“有没有数据”来猜因为待审核预约也要占用时间片。6.2 表单校验前端体验与后端硬约束预约表单最容易让用户觉得坑的就是时间选择器。时间选择器上要加的硬性限制包括可预约日期范围最早当天最晚 7 天后。时间段范围场地开放时间闭区间通常 8:00-22:00。开始时间必须早于结束时间。单次时长不超过 4 小时。参与人数不超过场地容量。前端对时间控件的min、max、step属性只是辅助真正的硬性校验必须放在后端。我在 Flask 接口里写了专门的参数校验函数收到请求后先把时间字符串解析成datetime再逐条校验规则前端选错时间显示红框提示后端接口直接返回 400。前后端同时校验的系统运维时能省很多扯皮的事。6.3 站内信与结果通知机制系统最少要有两条通知链路预约提交成功后通知申请人审核完成后把结果通知给申请人。我实现了一个非常轻量的Notification模型支持站内信即可。审核通过或驳回时在同一个事务里插入一条通知记录用户登录后右上角有一个小红点数字展开就能看到历史通知。class Notification(models.Model): user models.ForeignKey(users.User, on_deletemodels.CASCADE, related_namenotifications) content models.CharField(max_length255) is_read models.BooleanField(defaultFalse) created_at models.DateTimeField(auto_now_addTrue) class Meta: db_table notification indexes [models.Index(fields[user, is_read, created_at], nameidx_notify_read_time)]邮件短信这些外部渠道我没在最初版本接因为没有稳定的邮件网关和短信通道站内信先跑通后续要接邮件只需要在写通知的地方顺手加一个异步任务调用就行。7. 本地联调与线上部署双服务端口协调、Nginx代理和测试用例7.1 双服务的本地联调与用户态共享本地开发时容易出的问题不是代码 bug而是服务协调。Django 跑 8000 端口Flask 跑 5000 端口前端页面如果既调 Django 的接口又调 Flask 的接口就会被浏览器的同源策略拦住。解决方案有三种我最终用的是开发环境加跨域处理、生产环境用 Nginx 同源反向代理。Flask 端在开发环境启用 CORS 很简单from flask import Flask from flask_cors import CORS app Flask(__name__) CORS(app, origins[http://127.0.0.1:8000])用户登录状态也是个大坑。Django 有 sessionFlask 不认识Flask 自己签发 tokenDjango 又校验不了。我采用的方案是Django 负责登录接口登录成功后返回一个 JWTFlask 的所有接口在before_request里读取Authorization: Bearer token解析 token 获取用户 ID再直接从数据库读用户信息。这样两边的用户态通过同一套 JWT 打通Django 和 Flask 各自只负责自己熟悉的那部分。7.2 线上Nginx配置与进程管理生产环境我用 Nginx 做入口按 URL 前缀分流到两个服务。Django 后台走/admin和/django-api/Flask 接口走/api/前端静态文件也由 Nginx 托管。一个简化版的配置片段server { listen 80; server_name example.com; location /static/ { alias /var/www/campus_site/static/; } location /api/ { proxy_pass http://127.0.0.1:5000; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }两个服务用 Gunicorn 启动Django 和 Flask 各起一个进程组用 Supervisor 守护。线上还有一个很容易踩的坑是静态文件权限Django 这边在settings.py里开了DEBUGFalse和STATIC_ROOT后务必执行一次collectstatic否则 Nginx 托管静态文件时会一直 404。7.3 核心逻辑的自动化测试用例冲突检测是系统的命脉我为此写了一套单元测试覆盖所有边界情况。用 Django 内置的TestCase就能跑from datetime import datetime from django.test import TestCase class ConflictLogicTest(TestCase): def setUp(self): self.room Room.objects.create(name排练厅, capacity30, room_typeINDOOR) def test_no_overlap_should_pass(self): Reservation.objects.create( roomself.room, start_timedatetime(2025, 5, 1, 9, 0), end_timedatetime(2025, 5, 1, 11, 0), statusReservation.Status.APPROVED, ) self.assertFalse(has_conflict(self.room.id, datetime(2025, 5, 1, 11, 0), datetime(2025, 5, 1, 12, 0))) def test_partial_overlap_should_fail(self): Reservation.objects.create( roomself.room, start_timedatetime(2025, 5, 1, 9, 0), end_timedatetime(2025, 5, 1, 11, 0), statusReservation.Status.APPROVED, ) self.assertTrue(has_conflict(self.room.id, datetime(2025, 5, 1, 10, 0), datetime(2025, 5, 1, 12, 0))) def test_pending_reservation_also_blocks_slot(self): Reservation.objects.create( roomself.room, start_timedatetime(2025, 5, 1, 9, 0), end_timedatetime(2025, 5, 1, 11, 0), statusReservation.Status.PENDING, ) self.assertTrue(has_conflict(self.room.id, datetime(2025, 5, 1, 9, 0), datetime(2025, 5, 1, 10, 0)))这套测试用例后来帮了大忙。有同学改代码时把冲突判断条件里的误改成结果所有边界测试直接红了一片问题在当天就被发现了。预约系统最值得反复验证的就是这个函数没有第二个地方比这里更容易产生隐蔽 bug。部署完一段时间后我把这套系统的下一步扩展方向列了一下基本都是围绕“效率”和“体验”做文章。第一是给预约审核通过后加自动提醒之前依赖站内信后来发现有同学根本不上后台看改成邮件提醒后爽约率明显下降。第二是做一个使用率统计报表功能Django 后台里直接按月、按周统计每个场地的占用率期末社团评优时非常有用。第三是预约即将开始时给管理员发一次提醒方便确认场地是否已经布置好。最后分享一个我做这类独立系统最深的一点体会不要把技术选型的夹生饭做成项目的锅。双框架协作看起来多了一套进程其实反而让每次改动都集中在更小、更清晰的代码范围里关键是两边共享数据库时一定要把连接配置、缓存清理和日志记录这些“协作协议”定义清楚否则后期联调会非常痛苦。如果你只是做一个课程设计级别的系统也不需要完全照搬这套架构Django 单框架也能完成所有功能但如果想借这个项目把两个框架的脾气都摸透那么“Django 管后台、Flask 管接口”这条路确实是一条可以复现的实战路径。