
简介本资源是一套基于Python Django框架开发的停车场预约与智能计费系统完整源码面向Web开发初学者及Django进阶学习者解决城市停车场景中车位查询、时段预约、动态计费、在线支付等核心业务问题。压缩包共2000个文件含1632个JavaScript前端交互脚本支撑预约日历、实时状态刷新等功能、249个HTML模板页面覆盖用户端与管理后台全界面、54个CSS样式文件含bootstrap、font-awesome、ui等主流UI库以及5个核心Python后端模块models/views/urls等整体13.19MB结构清晰、模块解耦度高。已有129人下载学习可直接运行调试快速掌握Django MVC架构实践、数据库关系建模停车场/车位/预约/计费规则、第三方支付集成逻辑及响应式前端协同开发要点。 看到这个标题我第一反应是这又是一个打包好的毕设源码。但真正把需求捋了一遍之后会发现停车场预约计费系统这个题目选得相当聪明——它不是一个纯CRUD的增删改查练习而是把“资源预约”和“按时计费”这两个在真实商业场景里最常见的硬骨头都装进去了。预约要处理并发抢位计费要处理费率规则这两块做好了Django这门技术基本就算玩明白了。这套系统解决的是很具体的问题用户在网页上选停车场、选车位、选时间段提交预约到点进场出场时自动按停车时长算钱。看起来简单但背后涉及用户认证、车位状态管理、预约单状态机、并发锁、计费规则引擎、支付回调处理这些模块任何一个环节设计不好系统就跑不通。它适合三类人正在做课程设计或毕业设计的计算机专业学生想拿真实项目练手的Python/Django学习者以及需要快速搭一套小型停车管理系统的创业团队。1. 项目整体思路拆解先想清楚再动手1.1 三个关键词拆解Python、Django与源码案例的定位标题里三个关键词要分开看。“Python”说明整个系统是用Python写的而Python在Web开发领域最成熟的框架之一就是Django所以技术栈选型非常稳。“Django”意味着项目自带Admin后台、ORM、认证系统、模板引擎这些标配能力能省掉大量重复造轮子的工作。“源码案例设计”这几个字点出了项目的定位——它不是一个线上商用的庞然大物而是一个结构清晰、可以逐行阅读、能跑通完整业务的示例项目。很多初学者拿到源码第一件事是双击运行跑起来就觉得自己会了。我的建议是反着来先把项目的目录结构摸一遍看它按什么逻辑分成了几个app一般会分为accounts用户模块、parking车位模块、reservation预约模块、billing计费模块每个app里有哪些models、views、urls理清楚了再动手改代码。Django项目本身就是模块化的看懂app边界的划分比看懂某一行代码更重要。1.2 核心业务流程预约和计费是怎么串起来的这套系统的核心其实是一个通用业务模型资源预约 按时计费。资源就是车位预约就是锁定资源计费就是按使用时长收费。这个模型套到会议室预订、自习室占座、健身房私教课约课、设备租赁上逻辑完全一致。所以做这套系统收获的不只是停车场的知识而是理解了一类系统的架构思路。把主流程画出来大概是用户注册登录→选择停车场和车位→选择预约起止时间→提交预约并锁定车位→按时到场入场→出场结算计费→完成订单。预约时一定要做状态管理车位也不是随时都能被预约的它有时间冲突判断。计费要根据不同时段、不同车型、不同车位类型计算出不同价格。这两条主线串下来功能需求就清楚了。2. 数据库模型设计把业务翻译成数据表2.1 用户与车辆模型不要直接用默认UserDjango自带的User模型只有用户名、邮箱、密码这些基础字段停车场预约系统里用户一般要绑定手机号、车牌号甚至要支持一个用户管理多辆车。所以我通常用AbstractUser把默认User扩展一下加一个phone字段同时单独建一张车辆表。为什么车辆要单独建表因为一个用户可能有多辆车车牌号要参与预约记录的查询和计费如果和用户信息混在一起后期加车辆或者做车辆换绑会非常痛苦。核心字段设计大致是这样from django.contrib.auth.models import AbstractUser from django.db import models class User(AbstractUser): phone models.CharField(手机号, max_length20, blankTrue) class Meta: db_table user class Vehicle(models.Model): user models.ForeignKey(User, on_deletemodels.CASCADE, verbose_name所属用户) plate_number models.CharField(车牌号, max_length20, uniqueTrue) car_type models.CharField(车型, max_length20, choices[ (small, 小型车), (suv, SUV), (truck, 大型车), ], defaultsmall) def __str__(self): return self.plate_number class Meta: db_table vehicle要点是on_deletemodels.CASCADE用户删除时车辆记录一起删掉避免产生孤儿数据。plate_number加uniqueTrue是必要的现实中不可能有同一块车牌出现在两个用户下面。2.2 车位资源模型停车场、区域、车位三层结构车位本身是一个资源需要建模成一张表。但如果不加停车场和区域的概念系统就没法扩展成多个停车场。比较合理的方案是拆三层停车场ParkingLot→区域Zone→车位ParkingSpot。有了这三层以后接多个停车场或者在一个停车场上区分地面、地下、VIP区都不需要改表结构。车位表的核心字段包括车位编号、所属区域、车位类型普通车位/充电车位/无障碍车位、以及一个状态字段。状态字段是重点它决定了车位能不能被预约一般用Django的choices枚举空闲、已预约、已占用、维护中。这里容易踩的坑是有些项目把“已预约”和“已占用”搞混导致逻辑判断出错。实际上“已预约”是未来一个时间段被锁定了但现在物理上仍然空着“已占用”是车辆已经停进来了。这两个状态在业务上语义完全不同处理方式也不同。class ParkingLot(models.Model): name models.CharField(停车场名称, max_length100) address models.CharField(地址, max_length200) class Zone(models.Model): lot models.ForeignKey(ParkingLot, on_deletemodels.CASCADE, verbose_name所属停车场) name models.CharField(区域名称, max_length50) class ParkingSpot(models.Model): spot_number models.CharField(车位编号, max_length20) zone models.ForeignKey(Zone, on_deletemodels.CASCADE, verbose_name所属区域) spot_type models.CharField(车位类型, max_length20, choices[ (normal, 普通车位), (charging, 充电车位), (disabled, 无障碍车位), ], defaultnormal) status models.CharField(当前状态, max_length20, choices[ (free, 空闲), (booked, 已预约), (occupied, 已占用), (maintenance, 维护中), ], defaultfree) class Meta: unique_together (zone, spot_number)unique_together值得专门说一下它保证同一个区域里“A01”这个编号只能出现一次防止管理员录入重复的车位号。2.3 预约单与订单模型状态机是灵魂预约单是整个系统最核心的表。关键字段包括预约单号、用户、车辆、车位、预约开始时间、预约结束时间、实际入场时间、实际出场时间、总费用、支付状态、预约状态。预约单号我建议用UUID或“日期随机数”生成不要用自增ID否则容易被猜到系统一天的预约量也会暴露商业数据。预约状态建议用choices枚举通常包含待支付锁定中、已确认、已入场、已出场待结算、已完成、已取消、已超时。为什么需要这么多状态因为一条预约记录不像购物车它是有生命的——用户提交后占住了车位到时间没来要超时释放来了要入场走了要结算。每一步业务动作都对应一个状态流转。状态机设计清楚后面的业务逻辑就清晰了状态机设计混乱views里会堆满if else改一次崩一次。计费订单可以单独一张表也可以直接在预约单上加计费字段。如果系统是预约时需要预付押金、出场时再结算差额的模式建议单独拆分订单表方便记录支付流水和回调日志。一个预约单可以对应多次支付记录这是正常的因为预付和补缴是两笔钱。3. 预约模块实现并发抢位与超时释放3.1 预约创建完整链路从选择车位到生成预约单预约功能的核心是创建预约单但这个创建动作不是简单insert一条记录。完整的链路应该是用户选择车位和时段→后端校验车位是否存在、是否空闲→校验预约时间段是否和其他预约冲突→锁定车位状态为“已预约”→创建预约单→跳转到支付或等待确认。用Django实现时我建议把所有校验和写操作放到一个事务里。为什么一定要事务因为如果校验完车位可用、但创建预约单失败车位状态已经被改成“已预约”了这就产生了脏数据。事务可以保证要么全部成功要么全部回滚不会出现“有单没锁位”或“锁了位没单”的情况。3.2 并发控制为什么必须用select_for_update这是整个系统里最容易被忽略、也最容易出问题的点。两个人同时点击同一个车位预约如果不做并发控制两个请求都读到“车位空闲”然后都创建了预约单这个车位就被重复预约了。普通查询根本拦不住这种情况必须在数据库层面加行锁。from django.db import transaction from django.utils import timezone def create_appointment(request, spot_id, start_time, end_time): with transaction.atomic(): spot ParkingSpot.objects.select_for_update().get(idspot_id) if spot.status ! free: raise ValueError(车位当前不可用) conflict Appointment.objects.filter( spotspot, status__in[pending, confirmed, checked_in], start_time__ltend_time, end_time__gtstart_time, ).exists() if conflict: raise ValueError(该时段已被其他用户预约) spot.status booked spot.save(update_fields[status]) appointment Appointment.objects.create( userrequest.user, spotspot, start_timestart_time, end_timeend_time, statuspending, ) return appointmentselect_for_update()是重点它会在事务期间锁住这一行记录第二个请求必须等第一个事务提交后才能读到这条数据此时状态已经变成“booked”就会进入“车位不可用”的分支。没有这一行前面的所有校验都是纸上谈兵。另外注意时间冲突判断用的是SQL区间重叠判断start_time end_time AND end_time start_time这是判断两个时间段是否重叠的标准写法。3.3 超时未到场两种释放策略对比预约了车位但人没来如果不处理这个车位就永远处于锁定状态。常规做法有两种。第一种是Celery Redis做延时任务预约创建后延迟30分钟检查一次如果状态仍是“待支付”或“已确认”且未入场就把车位释放、预约单标记为“已超时”。这种方式实时性好但引入Celery需要额外维护Redis和worker进程项目复杂度会明显上升。第二种是“懒检查定时清理”方案在创建新预约或查询车位时先顺带把超时的预约单批量标记掉再用Django的manage.py自定义命令定期跑一次清理任务。这种方式不需要额外组件对毕设和小型项目完全够用。我个人推荐先做第二种跑通后再决定要不要上Celery。不要一上来就引入分布式组件那会掩盖掉你对业务本身的理解。4. 计费模块实现从停车时长到精确费用4.1 计费规则设计免费时长、时段费率与封顶价格停车计费最怕的就是规则写死。不同停车场、不同时段、不同车型费率完全不一样。设计时建议把计费规则和业务代码解耦至少把费率做成配置简单一点可以直接放在Django的settings.py里专业一点就单独建一张费率表。一个比较完整的计费规则通常包含三部分免费时长比如前15分钟免费、按时段费率比如白天每小时5元、夜间每小时3元、封顶价格比如单次停车24小时封顶30元。计算时先算总停车分钟数扣掉免费时长再按时间段拆分计算。这里有一个很容易踩的坑跨时段的停车一定要分段计算不能只看入场时间选一个费率。比如晚上10点入场、第二天早上8点出场这10个小时里夜间费率和小部分白天费率必须分开算。import math from datetime import datetime, time, timedelta def calc_fee(entry_time, exit_time): total_minutes (exit_time - entry_time).total_seconds() / 60 if total_minutes 15: return 0 billable_minutes total_minutes - 15 billable_hours math.ceil(billable_minutes / 60) night_start 22 night_end 8 fee 0 for i in range(billable_hours): base entry_time timedelta(hoursi) hour_fee 3 if (base.hour night_start or base.hour night_end) else 5 fee hour_fee return min(fee, 30)这个示例简化了很多但思路是对的按每个计费小时判断它落在白天还是夜间累加费用最后和封顶值取最小值。实际项目里还要考虑节假日费率、会员折扣等设计思路是一样的。4.2 入场与出场状态变更和费用结算入场操作很简单用户到达停车场管理员在后台点击“入场”或者对接车牌识别摄像头自动入场。系统要做的是把预约单状态从“已确认”改成“已入场”记录实际入场时间。入场时间要以系统记录为准不是预约时间。这一点的业务意义是计费不能按用户预约的时间算必须按真实占用时间算否则很多人会故意约长实际停短或者约短结果停超了。出场结算的流程是用户离场时系统根据实际入场时间和当前时间计算费用如果用户之前预付过押金就用应付金额减去预付金额多退少补如果没有预付就生成待支付账单。结算完成后车位的状态从“已占用”改为“空闲”方便下一个用户预约。这里要注意顺序——先结算改订单再释放车位避免出现“车位释放了但结算失败”的中间态。4.3 支付流程设计预约预付与出场补缴支付是计费闭环的最后一环。小型项目对接支付宝或微信支付时要特别注意回调处理支付成功后平台会异步通知你的服务器这个回调必须做幂等处理——同一个支付通知可能收到多次如果每次都把钱加到账上就出大问题了。一般方案是记录支付单号每次回调先查这个支付单号是否已经处理过处理过就直接返回成功。预约系统的支付通常分两种场景。一是预约时预付全款或押金锁定车位入场后实际费用多退少补二是预约不收费出场时统一结算。第一种对运营方更友好能过滤掉大量占位不来的用户第二种用户体验好但需要承担爽约风险。毕设项目我建议做第一种因为可以多展示一个“支付回调处理”的完整知识点答辩时能讲的东西更多。5. 源码部署与运行指南从零跑到通5.1 环境准备与项目结构说明拿到源码压缩包第一步是看README或者requirements.txt确认项目依赖。Python版本建议3.10以上Django版本建议4.x如果源码使用的是Django 2.x或3.x有些语法需要微调。我建的开发环境一般是这样操作的# 创建虚拟环境 python -m venv venv # Windows激活 venv\Scripts\activate # Linux/Mac激活 source venv/bin/activate # 安装依赖 pip install -r requirements.txt虚拟环境的价值在于隔离依赖版本不同项目需要不同版本的Django时不会互相干扰这个习惯越早养成越好。项目目录结构一般会看到这样几块manage.py是Django的命令入口项目配置目录通常是config或项目名同名目录放settings.py、urls.py业务app目录放着各自独立的models、views、urls、admin。如果源码里有static和templates目录分别放静态文件和前端模板。5.2 数据库配置与初始化源码默认用的是SQLite好处是零配置本地开发和毕设演示足够用。如果你的项目需要切换MySQL需要在settings.py里改数据库配置DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: parking_db, USER: root, PASSWORD: yourpassword, HOST: 127.0.0.1, PORT: 3306, } }改完数据库配置后按顺序执行数据库迁移命令。很多新手在这里卡住报错说table不存在其实就是顺序错了——必须先makemigrations生成迁移文件再migrate把迁移文件同步到数据库。python manage.py makemigrations python manage.py migrate python manage.py createsuperuser python manage.py runservercreatesuperuser创建的管理员账号可以用来登录Django自带的Admin后台在后台里录入停车场、车位、区域这些基础数据。这一点对刚拿到源码的人特别重要不录车位数据预约功能根本没数据可测。5.3 后台管理和基础测试Django Admin是这个框架最强的生产力工具之一。只要在admin.py里注册了模型你就能在后台直接增删改查车位、预约单、用户调试数据非常方便。注册方式很简单from django.contrib import admin from .models import ParkingLot, Zone, ParkingSpot, Appointment admin.register(ParkingSpot) class ParkingSpotAdmin(admin.ModelAdmin): list_display (spot_number, zone, status, spot_type) list_filter (status, spot_type)list_display控制列表页显示的字段list_filter是右侧筛选栏。把状态筛选加上之后维护人员能一眼看到当前有多少车位空闲、多少被预约、多少在维护这也是一个实用性很强的加分项。6. 开发中的常见问题与避坑清单6.1 并发与事务相关的坑没有加select_for_update导致同一车位被重复预约。这个问题在低并发测试环境下根本测不出来但一上线就出事。排查方法是看预约单表里是否出现同一车位同一时间段的多条记录或者在生产环境开Django的日志观察是否有两个请求在毫秒级间隔内更新了同一条数据。事务内执行耗时操作比如在事务里调用外部支付接口会导致数据库连接长时间占用并发一高系统就卡死。支付请求应该放在事务外面事务只负责更新本地状态。这是很多初学者会犯的错把HTTP请求写进事务块里结果锁表锁到怀疑人生。6.2 时区与时间计算的坑Django默认开启USE_TZTrue数据库里存的是UTC时间页面展示时会转换成当地时间。如果用户在预约时传入的是本地时间字符串而你直接用这个字符串和数据库里的UTC时间比较会出现8小时偏差。最稳妥的做法是全程用datetime对象存储时用timezone.now()取出来展示时用Django模板的本地化过滤器。计算停车时长时不要用字符串直接相减要确保两个时间都是Python的datetime对象最好是同一时区。时区混用是计费错误的头号原因尤其是跨日期的时段差8小时可能把白天算成夜间价格差一半以上用户投诉率极高。6.3 部署运行时的配置坑DEBUGTrue的状态下部署到公网会暴露详细报错信息和项目路径这是安全隐患。正式环境必须把DEBUG设为False并配置ALLOWED_HOSTS和静态文件收集路径。静态文件方面CSS和JS加载不出来是常见问题需要执行collectstatic命令并在nginx里配置静态文件目录。数据库迁移时修改了模型字段但migrate报错说不兼容。大部分原因是你删了某个字段或改了字段类型而表里已经有数据。解决方法是备份数据后生成空的迁移文件再执行或者直接删除迁移记录和数据库重建——后者只适合还没有重要数据的开发阶段。常见问题可能原因解决方法预约同一车位成功两次没有用select_for_update参考3.2加事务和行锁计费金额总差几块钱时区混用或跨时段没分段统一用datetime和timezone支付回调重复入账回调未做幂等记录支付单号并查重CSRF验证失败模板表单缺少csrf_token在模板中加{% csrf_token %}图片或CSS加载不出来静态文件未收集或路径配置错执行collectstatic并检查nginx配置管理后台看不到自定义模型未在admin.py注册在admin.py中注册模型7. 项目扩展方向从毕设作品到商用系统这套系统跑通之后往上叠功能的空间非常大。短期可以加一个停车记录管理页面展示每笔订单的流水明细配合ECharts画一个每日收入的柱状图这对毕设答辩来说是很直观的亮点。中期可以对接微信小程序用户在小程序里完成全部预约和支付管理员在Web后台管理这就已经具备一个半商用产品的雏形了。再往深了做可以考虑接入车牌识别摄像头入场自动识别车牌、自动开闸、出场自动扣费无人值守停车场的基本体验就出来了。充电车位可以按电量计费而不是按时长计费这需要对接充电桩的接口涉及物联网层面的数据交互技术上更有挑战性面试时也是很好的谈资。把预约计费的逻辑抽出来做成一个可复用的Django app用GenericForeignKey接不同的资源表就能同时支持车位、会议室、工位、设备租赁的预约。到了这个阶段你已经不是在做一个停车场的系统而是在做一个通用的资源预约中台职业天花板完全不一样了。我个人在实际操作中的体会是项目源码拿到手之后不要只满足于“跑起来”一定要自己做一遍重构。把预约状态机重画一遍把计费函数删掉重写一遍比读十遍源码都管用。Django的ORM、Admin、迁移体系都很成熟真正的难点从来不是框架API而是怎么把业务逻辑翻译成清晰的数据模型和状态流转。这套系统的核心价值也就在这里——停车场的壳子不重要壳子里“预约计费”的骨架才是能让你举一反三的真正资产。本文还有配套的精品资源点击获取