恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

基于Python的校园体育器材租赁系统:Django+Flask混合架构实战

  • 首页
  • 资讯中心
  • /
  • 基于Python的校园体育器材租赁系统:Django+Flask混合架构实战

相关资讯

SAP-PP替代物料详解:从BOM替代组到MRP与生产订单换料 2026/10/13 22:32:31
Vue3大文件上传实战:切片、哈希、断点续传与秒传 2026/10/13 22:32:31
SAP Excel模板上传下载的隐性规则与工程化实践 2026/10/13 22:32:31

最新资讯

Quartz 定时任务到点不执行?misfire 策略与并发锁复盘
uni-app 地图找岗:定位权限、map 组件与半径搜索,从授权到出结果 5 个坑
联邦学习+NSL-KDD实现隐私保护的网络入侵检测
LSTM+CNN双流模型实现台风路径预测的工程实践
NeRF三维重建实战:从手机拍摄到mesh导出的完整指南
如何撰写有效的技术博客内容

今日推荐

IBM Verify Identity Access 修复 22 个漏洞:两个 9.8 严重 RCE 无需认证
小区物业管理系统
小型超市仓库管理系统

本周热门

UE动画修改实战:从资产编辑到重定向与蒙太奇驱动
统计随机数生成器攻击下的KLJN安全密钥交换协议Matlab仿真
政务API安全治理:资产测绘、低代码编排与行标对标实践

本月精选

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)

基于Python的校园体育器材租赁系统:Django+Flask混合架构实战

发布时间:2026/10/13 22:37:31
基于Python的校园体育器材租赁系统:Django+Flask混合架构实战 1. 为什么校园体育器材租赁系统需要单独开发一套很多人觉得器材管理不就是个Excel表的事吗真正去体育器材室借过几次东西你就不会这么想了。高校里的体育器材租赁业务表面上是登记—借用—归还三个动作实际上牵扯到的细节远比想象中复杂。以某高校的实际场景为例体育课高峰期两个班同时上课一节课要借出篮球、排球、羽毛球拍等上百件器材课外活动时段学生个人借用和社团批量借用混在一起遇到运动会器材出库入库量直接翻几倍。传统的人工登记方式器材室老师在忙的时候根本来不及逐一核对经常出现账上显示有球架子上找不到学生明明还了记录里却是未归还这类扯皮事。更重要的是器材损耗和赔偿管理几乎全靠老师记忆期末盘点时账实不符是常态。做这个基于Python的校园体育器材租赁管理系统核心目标就是把这些乱象梳理清楚学生通过系统预约和借用器材管理员在后台统一处理借用审批、归还登记、损坏赔偿所有的库存变动、借还记录、损耗状态都能实时追踪。整个项目涉及的角色有三类——学生用户、器材室管理员、系统超级管理员对应的权限和操作完全不同。这套系统跑起来之后器材室老师的日常工作从人工对账变成了系统审批高峰期处理效率能提升一大截。对于想学习Python Web开发的读者来说这个项目的价值在于它同时用到了Django和Flask两种主流框架。Django负责后台管理端和核心业务逻辑Flask负责对外API接口和轻量级查询服务。一套系统里能体验到两种框架各自的定位和分工比单学一个框架更能理解框架选型这件事的实际意义。2. 技术选型Django和Flask各自负责什么2.1 为什么不让一个框架从头管到尾先说我踩过的一次选型弯路。最早我打算全站用Django实现理由很简单Django自带Admin后台、ORM、认证系统开发效率确实高。但做到移动端对接的时候就难受了——虽然Django也能写API但为了接口文档和请求校验我得额外装DRF为了处理跨域又得配中间件为了轻量权限还得自己写装饰器。一套折腾下来接口层变得又重又绕。后来我在另一个项目里尝到了用Flask写独立API服务的甜头路由简单、请求上下文清晰、配合蓝图模块化后代码结构一目了然。于是这个校园体育器材租赁系统我做了个混合架构Django端承载后台管理系统、数据库模型、用户认证、器材库存管理、借还审批流。这部分是数据密集型操作Django的ORM和管理后台能省掉大量重复劳动。Flask端承载前端小程序/网页端需要调用的API接口比如器材列表查询、个人借用记录、预约提交、库存余量实时查询。这些接口的特点是读多写少、响应要快Flask轻量得刚刚好。为什么这么分因为后台管理行为和管理员操作绑得很紧需要完善的事务处理和权限控制这是Django的强项而前端接口要暴露给多种客户端调用需要简洁的请求处理、灵活的响应格式、低耦合的路由分发这是Flask的舒适区。说白了一个项目里不同模块对框架特性的诉求不一样没必要用一把锤子砸所有钉子。2.2 两端的模块划分与数据交互方式两个服务之间靠什么通信直接操作同一个数据库还是通过HTTP调用我最终选用的是MySQL 8.0作为共享数据库Django负责建表、迁移和数据写操作Flask端做查询时也走同一个库但只读不写。这么设计的原因有两个一是避免双写造成的数据一致性问题二是Django的ORM迁移机制是最省心的建表方式Flask端不需要维护自己的模型层直接执行SQL查询即可。核心模块划分如下模块归属框架核心职责用户认证与权限Django登录校验、角色区分、Token签发器材档案管理Django器材类别、编号、库存量、存放位置借还业务流Django借用审批、归还登记、超期标记损坏与赔偿记录Django报损登记、赔偿金额核算、状态跟踪器材列表/余量查询Flask前端实时查询接口预约提交与取消Flask前端预约操作接口个人借用记录Flask前端查询当前用户借还历史统计报表Flask近7日借出量、器材使用频率排行为了拿到可用的TokenDjango在登录成功后生成一个签名凭证Flask端在收到请求时通过一个共享的密钥来校验。这个校验逻辑写在Flask的装饰器里每个需要登录的接口都加上代码量很少却很实用。3. 数据模型设计器材、用户、订单三张核心表怎么建3.1 用户模型三种角色的权限区分校园场景下的用户模型和普通电商系统不太一样。学生、教师、管理员对器材的使用场景不同权限边界也不同。我建了一张UserProfile表和Django内置的User表通过一对一关联扩展字段from django.db import models from django.contrib.auth.models import User class UserProfile(models.Model): USER_TYPE_CHOICES ( (student, 学生), (teacher, 教师), (admin, 管理员), ) user models.OneToOneField(User, on_deletemodels.CASCADE, related_nameprofile) user_type models.CharField(max_length20, choicesUSER_TYPE_CHOICES, defaultstudent) student_id models.CharField(max_length20, blankTrue, nullTrue, verbose_name学号) department models.CharField(max_length50, blankTrue, nullTrue, verbose_name学院/部门) phone models.CharField(max_length11, blankTrue, nullTrue, verbose_name联系电话) credit_score models.IntegerField(default100, verbose_name信用分) def __str__(self): return f{self.user.username} - {self.get_user_type_display()}信用分这个字段是实际运营中很关键的设计。学生逾期不还或者器材损坏信用分会扣减低于某个阈值比如60分时禁止借用。这比单纯靠罚款更柔性也更容易被学生接受。管理员端在审批借用时能看到申请人的信用分作为是否批准的依据。3.2 器材模型唯一编号和状态流转器材管理的难点在于同一类器材有很多件每一件又必须能追踪。所以器材表我设计了两个层次class EquipmentCategory(models.Model): 器材类别表篮球、排球、羽毛球拍等 name models.CharField(max_length50, uniqueTrue, verbose_name器材类别) total_stock models.IntegerField(default0, verbose_name总库存) available_stock models.IntegerField(default0, verbose_name可借库存) daily_rental_fee models.DecimalField(max_digits6, decimal_places2, default0, verbose_name日租金/元) deposit models.DecimalField(max_digits6, decimal_places2, default0, verbose_name押金/元) class EquipmentItem(models.Model): 器材实例表每件具体器材 STATUS_CHOICES ( (available, 可借用), (borrowed, 已借出), (maintenance, 维修中), (damaged, 已损坏), (discarded, 已报废), ) category models.ForeignKey(EquipmentCategory, on_deletemodels.CASCADE, related_nameitems) equipment_code models.CharField(max_length30, uniqueTrue, verbose_name器材编号) location models.CharField(max_length50, blankTrue, nullTrue, verbose_name存放位置) status models.CharField(max_length20, choicesSTATUS_CHOICES, defaultavailable) purchase_date models.DateField(nullTrue, blankTrue, verbose_name购买日期)为什么要拆成两张表因为类别表管的是这一类器材有多少可借实例表管的是具体哪一件在谁手里。很多管理系统只做到类别层面结果一查篮球还了5个但不知道还的是哪几个真出了问题根本追溯不了。拆开之后每一件器材都有自己从入库到报废的完整生命周期。3.3 订单模型预约、借用、归还一体的状态机订单是整个系统里最核心的表它承载了从预约到最终归还的全流程状态流转class RentalOrder(models.Model): ORDER_STATUS ( (pending, 待审批), (approved, 已批准), (borrowed, 已借出), (returned, 已归还), (overdue, 已超期), (cancelled, 已取消), ) order_no models.CharField(max_length30, uniqueTrue, verbose_name订单号) user models.ForeignKey(User, on_deletemodels.CASCADE, related_namerental_orders) category models.ForeignKey(EquipmentCategory, on_deletemodels.CASCADE, verbose_name器材类别) items models.ManyToManyField(EquipmentItem, blankTrue, verbose_name实际借出的器材) quantity models.IntegerField(default1, verbose_name借用数量) start_time models.DateTimeField(verbose_name借用开始时间) end_time models.DateTimeField(verbose_name预计归还时间) actual_return_time models.DateTimeField(nullTrue, blankTrue, verbose_name实际归还时间) status models.CharField(max_length20, choicesORDER_STATUS, defaultpending) purpose models.CharField(max_length100, blankTrue, nullTrue, verbose_name借用用途) create_time models.DateTimeField(auto_now_addTrue)状态机的流转逻辑是这样的学生提交预约 →pending管理员审批通过 →approved学生到器材室扫码/报号领取 →borrowed归还时管理员确认器材状态并收回 →returned如果超过end_time未归还系统定时任务自动把状态标为overdue同时扣信用分。整个过程每一步都有时间戳记录账目可追溯。实际做的时候很多同学容易漏掉items和category的双重关联。光记录借了3个篮球却不记录具体拿了哪几件后面库存核对和损坏赔偿就会对不上。我建议宁可订单表复杂一点也要把预约的是数量和实际领走的是哪些具体器材分开记录。4. 核心业务流程的实现从提交预约到归还入库4.1 预约与审批Django后台的事务处理预约流程直接写在前端页面里学生选择器材类别、填写借用时间段和用途提交后生成一条pending状态的订单。审批是管理员在Django Admin后台完成的。这里要注意一个关键点审批通过的同时必须锁定库存。from django.db import transaction from django.utils import timezone transaction.atomic def approve_order(order_id): order RentalOrder.objects.select_for_update().get(pkorder_id) if order.status ! pending: raise ValueError(该订单已不在待审批状态) category order.category if category.available_stock order.quantity: raise ValueError(库存不足无法批准) # 锁定库存 category.available_stock - order.quantity category.save() order.status approved order.save() return ordertransaction.atomicselect_for_update是这套逻辑里最核心的两个点。select_for_update会对这条订单记录加行级锁防止两个管理员同时审批同一单导致库存扣重复transaction.atomic保证扣库存和改状态要么都成功要么都失败。没有这两层保护高并发下一定会出问题。同样地学生取消审批通过的订单也要在事务里把库存加回来。这个补偿逻辑别忘了不然会出现订单取消了库存却永久少了的bug。4.2 借用与归还管理员扫码核验我实际做的时候给每件器材的编号做成了条形码贴纸学生领器材时管理员扫一下条形码系统自动把对应的EquipmentItem.status从available改成borrowed同时关联到当前订单。归还时再扫一次状态改回available订单状态改为returned写入实际归还时间。为什么要用扫码而不是手动输入编号手动输入在高峰期根本来不及而且容易输错。扫码器本质上是键盘输入设备扫一下等于输入一串编号加回车代码里只需要写一个输入框监听回车事件就行成本极低、效率极高。归还时的破损检查必须做成强制流程。归还页面里我加了一个器材状态确认的选项组正常 / 轻微磨损 / 明显损坏 / 丢失。选了后两项会自动弹出报损单要求上传照片并填写说明。这一步卡住了后面赔偿核算才有依据。4.3 超期订单的自动标记与信用分扣减超期不能全靠管理员肉眼盯我写了一个Django management command配合系统计划任务每小时跑一次from django.core.management.base import BaseCommand from django.utils import timezone from rentals.models import RentalOrder class Command(BaseCommand): def handle(self, *args, **options): overdue_orders RentalOrder.objects.filter( statusborrowed, end_time__lttimezone.now() ) for order in overdue_orders: order.status overdue order.save() # 扣信用分每超期一天扣5分 profile order.user.profile overdue_days (timezone.now() - order.end_time).days profile.credit_score max(0, profile.credit_score - overdue_days * 5) profile.save() self.stdout.write(f处理 {overdue_orders.count()} 条超期订单)计划的执行频率我设的是每小时一次主要考虑到超期扣分是提醒性质的不需要精确到分钟。如果哪天学校觉得扣分太狠或者太轻调整overdue_days * 5这个乘数就行。5. Flask端API开发给前端页面提供轻量接口5.1 蓝图结构设计与Token校验Flask端的项目结构我按功能拆成了蓝图每个蓝图管一块接口flask_api/ ├── app.py # 主入口注册蓝图 ├── config.py # 数据库连接、密钥配置 ├── decorators.py # 登录校验装饰器 ├── blueprints/ │ ├── auth.py # 登录/刷新Token │ ├── equipment.py # 器材列表、余量查询 │ ├── order.py # 提交预约、取消预约、我的订单 │ └── stats.py # 借用统计报表登录校验装饰器是整个API层的看门人。Django端在用户登录成功后用itsdangerous库签发一个带有过期时间的TokenFlask端用同一个密钥解析Token解析成功就放行失败就返回401。from functools import wraps from flask import request, jsonify, current_app import itsdangerous def login_required(f): wraps(f) def decorated_function(*args, **kwargs): token request.headers.get(Authorization, ).replace(Bearer , ) if not token: return jsonify({code: 401, message: 未登录}), 401 try: s itsdangerous.TimestampSigner(current_app.config[SECRET_KEY]) user_id s.unsign(token).decode(utf-8) request.user_id int(user_id) except itsdangerous.BadSignature: return jsonify({code: 401, message: Token无效或已过期}), 401 return f(*args, **kwargs) return decorated_function这个方案比直接用Django Session或JWT都更轻。Session需要存储会话状态Flask端还要连Session后端JWT要引入额外的库处理签名算法。itsdangerous本身就是Django和Flask共用的签名工具两边都能用同一个SECRET_KEY验签省掉不少对接成本。5.2 器材余量查询接口警惕N1查询器材列表接口是最常用的接口前端首页要展示所有类别的名称、库存余量、租金、押金。第一版我写得很天真categories EquipmentCategory.query.all() result [] for c in categories: result.append({ name: c.name, available: c.available_stock, rental_fee: str(c.daily_rental_fee), deposit: str(c.deposit), })数据量小的时候没问题但一旦器材类别上百每个类别还要关联查询实例表里的可用数量就会产生大量SQL查询。我在实际压测时这个接口的响应时间直接到了800毫秒以上。优化方案是只在页面初始化时查一次然后前端做缓存接口响应压到50毫秒以内categories EquipmentCategory.query.all() # 一次性查出所有可借器材数量避免N1 available_counts dict( db.session.query( EquipmentItem.category_id, func.count(EquipmentItem.id) ).filter(EquipmentItem.status available) .group_by(EquipmentItem.category_id) .all() )5.3 预约提交接口与库存校验的并发问题预约提交这个接口必须考虑并发场景两个学生同时提交同一个器材类别的预约剩余库存只有最后1件但两个人都显示预约成功。根本原因在于先查库存再插入订单这两步之间有间隙。Flask端的校验不能只做一次。我的做法是提交预约时先检查该时间段内该器材类别的预约占用数量 当前剩余库存是否足够同时Django端在审批时再做一次终极校验。两道校验虽然冗余但能保证前端响应快后台数据准。前端校验失败直接返回库存不足后台审批失败则事务回滚。预约信息到达Flask后Flask会调用Django的接口写入订单。这里Flask和Django之间我用的是HTTP调用地址走内网IP不走公网省去了权限认证的麻烦。6. 前端页面后台管理端怎么做到好用6.1 Django Admin的改造不要直接用默认界面很多人的后台就是Django Admin原样交付功能是有了但管理员用起来很别扭。器材室的老师不是程序员他们需要的是打开就能看到今天要处理哪些审批、哪些器材要归还这种直观界面。我的做法是给Admin自定义了首页看板。通过重写admin/index.html模板把待办事项放到首页最上面待审批订单数、今日应归还数量、库存不足预警、损坏待处理列表。管理员登录后第一眼就看到这些数字点进去直接处理不用东翻西找。Admin列表页也别直接展示所有字段通过list_display只展示关键信息加上list_filter按状态筛选。不然几百条订单堆在一个页面上找一条要滚半天。6.2 面向普通用户的借用页面逻辑学生端的前端页面如果直接做成Web版逻辑要足够简单进入器材列表页看到所有可借类别和实时余量点击借用选择时间段默认不超过7天填写用途提交后显示等待管理员审批审批结果通过站内消息或邮件通知审批通过后页面上显示请在XX时间前到器材室领取使用完毕到器材室归还系统更新订单状态前端和后端的交互全部走Flask的API。这里有个经验预约时前端最好把归还时间限制为当天不借、明确节假日不可预约。学校里的实际情况是节假日器材室基本没人值班闭馆期间器材是不能借出的。我第一版没做这个限制有学生预约了国庆期间的器材结果到了时间器材室大门紧锁订单卡在已批准状态等了一天最后还得人工取消。7. 我在实际部署和测试中遇到的几个棘手问题7.1 器材状态和数据一致性问题最容易出现的bug是器材在系统中显示可借用但物理位置上根本找不到。原因是某些器材被临时调去比赛或维修没有走系统流程。后来我加了批量状态调整功能管理员可以一键把一批器材标记为维修中或报废并在备注里填写原因。同时每个月至少做一次账实盘点发现差异直接在系统里调整。另一个坑是订单关联了很多具体器材后如果其中一件损坏不能把整笔订单都标记为已损坏。我在订单和器材关联表里增加了return_status字段用来单独记录每件器材的归还状态这样一单里可以同时存在正常归还2件、损坏1件的情况。7.2 Django Admin登录安全性Django Admin是管理员的入口默认情况下登录接口容易被暴力尝试。我加了两个简单的防护一是登录失败5次后锁定该账号15分钟写一个简单的失败计数存到缓存里二是启用Django自带的AdminSite.site_header自定义样式的同时把Admin后台的URL从默认的/admin/改成了一个不容易猜到的路径。这个改动成本几乎为零但能有效挡住绝大多数批量扫描流量。7.3 整点并发压测库存为什么会变成负数上线前我做过一次并发测试50个线程同时提交预约目标是一个库存只有30件的篮球类别。测试结果惊到了我——订单只成功8笔但库存变成了-7。原因是Flask端的高并发请求都涌到了Django审批接口而Django端的库存校验和扣减没有放在同一个事务里。排查过程是这样的先看错误日志发现大量IntegrityErrorSQL层面出现了唯一键冲突再看数据库记录发现有两笔订单是同一时刻写入的最后定位到代码审批流程里查询库存和更新库存确实是两条独立的SQL语句中间没有加锁。修复方案就是把审批逻辑完整包进transaction.atomic并加上select_for_update强制锁行。改完之后重新压测30件的库存最多成功30单不会再多。7.4 归还流程中的权限漏洞我最初设计的归还接口只校验了当前用户是否登录结果是任何登录用户都可以调用归还接口。如果有学生知道某笔订单的订单号他可以手动标记别人的订单为已归还造成账实不符。修复时我在归还接口增加了双重校验不仅校验Token还要校验order.user_id是否等于当前用户管理员归还不受此限制。这类水平越权问题在开发中很容易被忽略但做管理系统时必须从第一天就绷紧这根弦。8. 代码组织与部署直接照抄的可运行方案8.1 后端整体目录结构我最终交付的项目结构是这样的分前后端两个服务独立部署django_backend/ ├── manage.py ├── config/ │ ├── settings.py # Django配置包含数据库、Admin、日志 │ └── urls.py ├── apps/ │ ├── users/ # 用户认证与UserProfile │ ├── equipment/ # 器材类别与实例 │ ├── rental/ # 订单、审批、归还逻辑 │ └── reports/ # 统计报表接口 └── requirements.txt flask_api/ ├── app.py ├── config.py ├── decorators.py ├── blueprints/ │ ├── auth.py │ ├── equipment.py │ ├── order.py │ └── stats.py └── requirements.txtDjango端通过python manage.py runserver跑在8000端口Flask端通过gunicorn跑在5000端口。内外网访问策略是Django Admin只允许校园网内访问Flask API所在的服务器同时提供对前端页面的托管不做额外负载均衡——这个体量的系统一台4核8G的服务器绰绰有余。8.2 Nginx配置要点Nginx上我们需要处理两件事一是静态文件指向Django的static目录二是将/api/路径的请求转发到Flask服务。配置片段的写法不再赘述但有一个关键点Flask接口返回的JSON里必须带Cache-Control: no-store头否则学生端查到的器材余量可能是缓存的脏数据。8.3 定时任务还得考虑节假日上面的超期自动标记脚本平时跑没问题但寒暑假会误伤。假期里器材室闭馆所有订单都处于应该归还但没人还的状态如果自动脚本照常扣信用分学生开学后会集体来投诉。我加了假期时间表配置在系统设置里维护一个节假日起止日期的列表定时任务检测到当前日期在假期内就直接跳过处理。这个细节看着小但直接决定了系统能不能真正用起来。任何管理系统脱离实际作息规则的功能都是负担。若从这个系统继续往后做比较自然的扩展方向是给Flask接口增加按时间段的器材热度统计帮助学生避开借还高峰把器材实例表里的条形码换成二维码学生可以通过小程序扫码查看器材状态和来源记录对接学校的统一身份认证平台省去单独的注册流程学号即账号做这类校园系统最深的体会是技术选型和代码结构固然重要但真正决定系统能不能落地的是对业务场景的理解——什么时候闭馆、假期谁值班、损坏怎么界定、信用分怎么扣才不会引发矛盾。这些规则想清楚了写代码只是时间问题想不清楚再好的架构也只是个精美的空壳。

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号