简介:本资源是一套基于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 用户与车辆模型:不要直接用默认User
Django自带的User模型只有用户名、邮箱、密码这些基础字段,停车场预约系统里,用户一般要绑定手机号、车牌号,甚至要支持一个用户管理多辆车。所以我通常用AbstractUser把默认User扩展一下,加一个phone字段,同时单独建一张车辆表。为什么车辆要单独建表?因为一个用户可能有多辆车,车牌号要参与预约记录的查询和计费,如果和用户信息混在一起,后期加车辆或者做车辆换绑会非常痛苦。
核心字段设计大致是这样:
from django.contrib.auth.models import AbstractUser from django.db import models class User(AbstractUser): phone = models.CharField("手机号", max_length=20, blank=True) class Meta: db_table = "user" class Vehicle(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name="所属用户") plate_number = models.CharField("车牌号", max_length=20, unique=True) car_type = models.CharField("车型", max_length=20, choices=[ ("small", "小型车"), ("suv", "SUV"), ("truck", "大型车"), ], default="small") def __str__(self): return self.plate_number class Meta: db_table = "vehicle"要点是on_delete=models.CASCADE,用户删除时车辆记录一起删掉,避免产生孤儿数据。plate_number加unique=True是必要的,现实中不可能有同一块车牌出现在两个用户下面。
2.2 车位资源模型:停车场、区域、车位三层结构
车位本身是一个资源,需要建模成一张表。但如果不加停车场和区域的概念,系统就没法扩展成多个停车场。比较合理的方案是拆三层:停车场ParkingLot→区域Zone→车位ParkingSpot。有了这三层,以后接多个停车场,或者在一个停车场上区分地面、地下、VIP区,都不需要改表结构。
车位表的核心字段包括车位编号、所属区域、车位类型(普通车位/充电车位/无障碍车位)、以及一个状态字段。状态字段是重点,它决定了车位能不能被预约,一般用Django的choices枚举:空闲、已预约、已占用、维护中。这里容易踩的坑是:有些项目把“已预约”和“已占用”搞混,导致逻辑判断出错。实际上“已预约”是未来一个时间段被锁定了,但现在物理上仍然空着;“已占用”是车辆已经停进来了。这两个状态在业务上语义完全不同,处理方式也不同。
class ParkingLot(models.Model): name = models.CharField("停车场名称", max_length=100) address = models.CharField("地址", max_length=200) class Zone(models.Model): lot = models.ForeignKey(ParkingLot, on_delete=models.CASCADE, verbose_name="所属停车场") name = models.CharField("区域名称", max_length=50) class ParkingSpot(models.Model): spot_number = models.CharField("车位编号", max_length=20) zone = models.ForeignKey(Zone, on_delete=models.CASCADE, verbose_name="所属区域") spot_type = models.CharField("车位类型", max_length=20, choices=[ ("normal", "普通车位"), ("charging", "充电车位"), ("disabled", "无障碍车位"), ], default="normal") status = models.CharField("当前状态", max_length=20, choices=[ ("free", "空闲"), ("booked", "已预约"), ("occupied", "已占用"), ("maintenance", "维护中"), ], default="free") 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(id=spot_id) if spot.status != "free": raise ValueError("车位当前不可用") conflict = Appointment.objects.filter( spot=spot, status__in=["pending", "confirmed", "checked_in"], start_time__lt=end_time, end_time__gt=start_time, ).exists() if conflict: raise ValueError("该时段已被其他用户预约") spot.status = "booked" spot.save(update_fields=["status"]) appointment = Appointment.objects.create( user=request.user, spot=spot, start_time=start_time, end_time=end_time, status="pending", ) 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(hours=i) 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_TZ=True,数据库里存的是UTC时间,页面展示时会转换成当地时间。如果用户在预约时传入的是本地时间字符串,而你直接用这个字符串和数据库里的UTC时间比较,会出现8小时偏差。最稳妥的做法是全程用datetime对象,存储时用timezone.now(),取出来展示时用Django模板的本地化过滤器。
计算停车时长时不要用字符串直接相减,要确保两个时间都是Python的datetime对象,最好是同一时区。时区混用是计费错误的头号原因,尤其是跨日期的时段,差8小时可能把白天算成夜间,价格差一半以上,用户投诉率极高。
6.3 部署运行时的配置坑
DEBUG=True的状态下部署到公网,会暴露详细报错信息和项目路径,这是安全隐患。正式环境必须把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,而是怎么把业务逻辑翻译成清晰的数据模型和状态流转。这套系统的核心价值也就在这里——停车场的壳子不重要,壳子里“预约+计费”的骨架,才是能让你举一反三的真正资产。
本文还有配套的精品资源,点击获取