
简介基于Python与Django、MySQL开发的停车场预约停车计费系统毕业设计源码包面向计算机相关专业学生可作为毕业设计或课程设计的完整参考。系统分为管理员和用户两种身份用户可注册登录查询区域与车位信息预约车位时自动进行时间冲突检测还能管理自己的车辆信息、查看预约与停车记录、发布留言管理员负责管理用户、区域和车位办理车辆停车和离开业务并自动结算费用同时审核预约、发布新闻公告、处理用户留言。资源包共2000个文件以JavaScript、HTML、CSS等前端文件为主包含Python后端源码、数据库脚本以及JSON、文本等辅助文件整个压缩包仅5.98MB目录结构清晰便于导入开发环境直接运行。目前已有227人学习下载这份源码对想快速搭建同类型管理系统、熟悉DjangoMySQL联动开发流程的读者很有帮助数据库脚本也可直接导入MySQL快速验证功能。1. 停车场预约计费系统在做什么Django MySQL 组合为什么够用很多毕业设计做到最后最怕的不是功能写不完而是演示完代码之后导师追问一句“两个用户同时提交同一个车位的预约你保证不会超卖吗”。这套基于 Python Django MySQL 的停车场预约停车计费系统说穿了就是把三件事讲清楚车位怎么预约、预约后状态怎么流转、车辆进出场后费用怎么算。它适合三类人想拿完整参考代码快速跑通的毕设学生想复现一个 Django MySQL 全栈项目的初级工程师以及需要导入数据库脚本就能启动系统、再继续二次开发的从业者。把这三块脉络理顺这套代码就能从“能跑”变成“敢演示”。2. 从空目录到首个可用页面Django 项目骨架与 MySQL 数据库落地这一章先解决“地基”问题。很多新手拿到源码后第一件事是急着跑页面结果卡在数据库连不上、迁移报错、中文字符集乱码上。先把 Django 项目骨架、MySQL 连接参数和核心数据表定下来后面预约和计费代码才有地方落脚。2.1 为什么选 Django MySQL预约计费场景的账怎么算预约计费系统本质上是一堆“表单 状态 金额 时间”的组合。Django 自带 ORM、Admin 后台和迁移机制意味着车场、车位、费率这些基础数据可以直接在后台增删改查不需要为毕设单独写一套管理页面开发成本一下子省掉大半。MySQL 在这个组合里负责的是事务和行锁。预约业务最怕并发冲突MySQL 的 InnoDB 引擎支持SELECT ... FOR UPDATE行锁能在一个事务里锁住车位记录直到预约落库。如果换成 SQLite并发写入会把整个库锁住演示时两个浏览器同时预约就直接卡死。方案ORM/后台事务能力适合场景Django MySQL自带 Admin 与迁移InnoDB 行锁表单密集型业务预约/计费典型场景Flask MySQL需自行集成 Flask-Admin同样依赖 MySQL想练手的小型服务Django SQLiteAdmin 可用并发写入锁库纯本地演示不适合真实预约所以这个题目选 Django MySQL 不是偶然是这类业务里风险最低、交付最快的组合。2.2 建虚拟环境与 Django 项目两个 app 把预约和计费分开拿到源码后第一件事是建虚拟环境避免依赖装到系统 Python 里越装越乱。Windows 和 Linux 只是激活命令不同其他都一样。python -m venv venv # Windows 用: venv\Scripts\activate source venv/bin/activate pip install django~4.2.0 mysqlclient django-admin startproject parking_system cd parking_system python manage.py startapp reservation python manage.py startapp billing这里的参数有几个值得说。django~4.2.0表示安装 4.2.x 系列不会直接装到 5.x避免新版 API 变化导致源码不兼容mysqlclient是 Django 连 MySQL 的官方推荐驱动如果 Windows 下编译失败第五章有替代方案。项目名叫parking_system内部拆成reservation和billing两个 appreservation管车位、预约单、状态流转billing管费率配置、计费计算和停车记录。业务边界分开后后面写测试、定位问题都会清爽很多。2.3 连接 MySQL 与配置 Settings字符集、引擎和认证参数先在 MySQL 里建库。字符集这里是最容易踩坑的地方很多默认建库语句只用utf8遇到 emoji 或生僻字直接报错。推荐用utf8mb4CREATE DATABASE parking_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;接着修改parking_system/settings.py里的数据库配置DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: parking_db, USER: parking_user, PASSWORD: 你的密码, HOST: 127.0.0.1, PORT: 3306, OPTIONS: { charset: utf8mb4, init_command: SET sql_modeSTRICT_TRANS_TABLES, }, } }几个参数的取舍要清楚。HOST建议写127.0.0.1而不是localhost某些 MySQL 8 环境下localhost会走 socket 连接导致 Django 连不上。OPTIONS里的charset必须和建库字符集一致utf8mb4能覆盖绝大多数中文场景。init_command里的STRICT_TRANS_TABLES是让 MySQL 对非法日期、超长字符串直接报错而不是静默截断否则排查问题时数据会变得非常诡异。注意MySQL 8 默认认证插件是caching_sha2_password老版本的mysqlclient可能报 authentication plugin 错误。遇到这个问题优先升级mysqlclient不要去改 MySQL 账号插件来迁就代码。2.4 用 Model 描述车位与预约单核心字段与逻辑主键取舍预约计费的模型不需要设计得很复杂核心就是三张表车场、车位、预约单。车位必须单独成表因为它是预约和计费的锚点不能只在预约单里存一个车位编号字符串。from django.conf import settings from django.db import models class ParkingLot(models.Model): name models.CharField(车场名称, max_length60) address models.CharField(地址, max_length200, blankTrue) total_spots models.IntegerField(总车位数, default0) class ParkingSpot(models.Model): lot models.ForeignKey(ParkingLot, on_deletemodels.CASCADE, related_namespots) spot_no models.CharField(车位编号, max_length20) is_active models.BooleanField(可用, defaultTrue) class Meta: unique_together [(lot, spot_no)] class Reservation(models.Model): user models.ForeignKey(settings.AUTH_USER_MODEL, on_deletemodels.CASCADE) spot models.ForeignKey(ParkingSpot, on_deletemodels.PROTECT) start_time models.DateTimeField(预约开始时间) end_time models.DateTimeField(预约结束时间) status models.CharField(状态, max_length20, defaultconfirmed) created_at models.DateTimeField(auto_now_addTrue) updated_at models.DateTimeField(auto_nowTrue)字段设计上有几个容易被忽略的点。ParkingSpot的unique_together保证同一个车场不会出现两个 A01is_active用于停用车位但不删除历史预约记录。Reservation.spot外键用on_deletemodels.PROTECT而不是 CASCADE预约单是业务凭证不能因为车位被删就连带消失。status用普通字符串而不是数字枚举代码里可读性更好状态流转规则第三章再讲。模型作用关键字段ParkingLot车场主体name, address, total_spotsParkingSpot具体车位lot, spot_no, is_activeReservation预约单user, spot, start_time, end_time, status这张表里故意没有金额字段。预约单只管“约没约到”费用应该由计费模块根据入场出场时间实时算这样以后改价签不用刷历史预约数据。2.5 迁移与数据库脚本从 makemigrations 到 sqlmigrate模型写好后用 Django 的迁移机制把表结构同步到 MySQL。迁移文件本质上是系统替我们维护的一套数据库脚本。python manage.py makemigrations reservation billing python manage.py migrate # 查看某个迁移文件实际生成的 SQL python manage.py sqlmigrate reservation 0001makemigrations负责生成迁移文件migrate把变更写入 MySQL。如果心里没底可以先跑sqlmigrate reservation 0001看看 Django 将要执行的CREATE TABLE语句确认字段、索引和约束符合预期。已有数据库需要反向生成模型的场景可以用python manage.py inspectdb生成初始模型再手工整理字段类型。3. 预约逻辑的实现数据库模型设计、事务与状态流转很多停车系统能演示“预约成功”页面实际上就是往表里插入一条数据。如果导师或同事追问一句“并发同一车位怎么办”这条裸 INSERT 就会露馅。预约逻辑必须写成独立服务函数用事务和行锁把数据可靠性兜住。3.1 预约链路拆解从登录到入场要过哪五步一个完整预约流程可以拆成五步登录后选择车场。查询目标车位在预约时段内是否空闲。创建预约单状态置为confirmed。到场后凭订单号入场预约单变更为completed。出场时按停车时长计费生成停车记录。页面上看核心是第 3 步技术难度最大的也是第 3 步。两个请求在同一秒执行“先查有没有人约、再插入预约单”就可能在查询阶段都看到空档然后插入两条重叠记录。这个问题不解决后面所有功能都是空中楼阁。3.2 关键实现用事务和 select_for_update() 避免车位超卖解决并发的标准做法是select_for_update()。它会把车位这一行锁住直到事务提交或回滚才释放。第二个请求执行到同一行时会等待第一个事务结束后才能继续判断。在reservation/services.py里写一个独立的创建预约函数from django.db import transaction from reservation.models import ParkingSpot, Reservation class ReservationConflict(Exception): pass transaction.atomic def create_reservation(user, spot_id, start_time, end_time): if start_time end_time: raise ValueError(预约结束时间必须晚于开始时间) spot ParkingSpot.objects.select_for_update().get(idspot_id, is_activeTrue) overlap_exists Reservation.objects.filter( spotspot, status__in[confirmed, pending], start_time__ltend_time, end_time__gtstart_time, ).exists() if overlap_exists: raise ReservationConflict(该车位在这个时段已被预约) return Reservation.objects.create( useruser, spotspot, start_timestart_time, end_timeend_time, statusconfirmed, )逻辑说明select_for_update()必须在transaction.atomic块内部行锁持续到事务结束。status__in[confirmed, pending]的意思是只有“待确认”和“已确认”的预约会参与冲突判断取消和已完成的单子不会占用车位时段。时间段重叠判断是这套逻辑的关键start_time__ltend_time且end_time__gtstart_time表示两条预约只要有任意交集就算冲突。注意边界情况如果 A 的结束时间恰好等于 B 的开始时间这种首尾相接的预约不应该冲突上面的判断条件天然放行了。注意事务里不要发送 HTTP 请求或调用外部接口。行锁在你手里握着外部接口响应慢 10 秒另一个预约请求就干等 50 秒。MySQL 默认innodb_lock_wait_timeout是 50 秒超过就报锁等待超时。3.3 预约状态机的流转约束与取消逻辑预约单状态不是随便 UPDATE 的否则会出现“已取消又变已完成”的脏数据。把流转规则收敛成一个状态机函数所有状态变更都走这个入口。当前状态允许流转到pendingconfirmed / cancelledconfirmedcompleted / cancelled / expiredcompleted / cancelled / expired终态不可再流转对应的函数可以放在reservation/services.pyALLOWED_TRANSITIONS { pending: {confirmed, cancelled}, confirmed: {completed, cancelled, expired}, completed: set(), cancelled: set(), expired: set(), } def transition_reservation(reservation, to_status): if to_status not in ALLOWED_TRANSITIONS[reservation.status]: raise ValueError(f不允许从 {reservation.status} 流转到 {to_status}) reservation.status to_status reservation.save(update_fields[status, updated_at])用户主动取消走cancelled到场入场走completed超时未入场由定时任务批量改成expired。把状态变更集中到一个函数里好处是测试时只需要对着这个函数写用例不用担心页面里到处散落save()调用。3.4 时间处理细节时区设置与超时释放预约是强时间依赖业务时区必须一开始就定对。settings.py里这样设置USE_TZ True TIME_ZONE Asia/ShanghaiUSE_TZ True时Django 存入 MySQL 的时间统一转成 UTC读取时再转回当前时区。所以在数据库里看到时间比本地少 8 小时是正常的不要慌更不要去改 MySQL 的全局时区来“纠正”。超时未入场的预约需要定期释放车位。常见做法是写一个 Django management command放在reservation/management/commands/expire_reservations.pyfrom datetime import timedelta from django.core.management.base import BaseCommand from django.utils import timezone from reservation.models import Reservation class Command(BaseCommand): help 释放超时未入场的预约 def handle(self, *args, **options): deadline timezone.now() - timedelta(minutes15) count Reservation.objects.filter( statusconfirmed, start_time__ltdeadline, ).update(statusexpired) self.stdout.write(f已过期 {count} 条预约)这里用update()批量更新不会触发模型的save()方法效率高但如果你在save()里挂了其他业务钩子这里就不会执行。15 分钟阈值是常见默认值实际项目里按运营规则调整即可。Windows 上用计划任务Linux 上用 cron定时执行python manage.py expire_reservations。4. 计费规则引擎的设计首小时、封顶价与跨天时长计算计费是停车场系统里用户最敏感的部分多收一分钱都会被投诉。这部分的核心不是写一个公式而是把规则参数化、计算纯函数化、历史账单快照化。规则能改、公式能测、账单能溯源才算一个能拿出手的计费模块。4.1 计费参数表先定规则再写代码写代码之前先把运营规则拆成参数。一套常见的停车场计费规则如下参数默认值含义first_hour_fee5.00停车首小时费用additional_per_half_hour2.00超过首小时后每 30 分钟费用daily_cap40.0024 小时滚动封顶价free_minutes15免费分钟数超过才开始计费这套规则翻译成人话是前 15 分钟免费首小时内收费 5 元超过首小时后每 30 分钟加 2 元不满 30 分钟按 30 分钟算24 小时内最高收 40 元。这些数字必须能改否则每次调价都要重新部署代码。4.2 把计费规则配置化为什么不用硬编码不要在代码里写死if minutes 60: fee 5这样的逻辑。价格一定会变变成硬编码就失去了灵活性。常见做法是建一张单例配置表只保存一行记录后台直接改。在billing/models.py里from decimal import Decimal from django.db import models class BillingConfig(models.Model): first_hour_fee models.DecimalField(首小时费用, max_digits7, decimal_places2, defaultDecimal(5.00)) additional_per_half_hour models.DecimalField(超时每半小时费用, max_digits7, decimal_places2, defaultDecimal(2.00)) daily_cap models.DecimalField(24小时封顶, max_digits7, decimal_places2, defaultDecimal(40.00)) free_minutes models.PositiveIntegerField(免费分钟数, default15) updated_at models.DateTimeField(auto_nowTrue) class Meta: verbose_name 计费规则 verbose_name_plural 计费规则金额字段必须用DecimalField不能用FloatField。浮点数在二进制里无法精确表示 0.1累加多次后会出现 0.30000000000000004 这种结果账单上多一分钱少一分钱都说不清。DecimalField配合 Python 的decimal.Decimal才能保证金额精度。4.3 计费核心计算函数精确到分钟的实现计费函数应该是纯函数输入入场时间、出场时间和配置输出费用金额不碰数据库。这样写单测非常舒服。在billing/services.py里import math from decimal import Decimal def calculate_fee(entry_time, exit_time, config): total_minutes (exit_time - entry_time).total_seconds() / 60 if total_minutes config.free_minutes: return Decimal(0.00) billable_minutes total_minutes - config.free_minutes fee config.first_hour_fee if billable_minutes 60: extra_half_hours math.ceil((billable_minutes - 60) / 30) fee extra_half_hours * config.additional_per_half_hour return min(fee, config.daily_cap)逻辑说明先算总分钟数免费时段直接返回 0。扣除免费分钟后的时长前 60 分钟按首小时费用算超出部分用math.ceil向上取整到 0.5 小时。最后用min()做 24 小时封顶。参数细节要留意free_minutes不为 0 时首小时费用覆盖的是“扣除免费时段后的第一个小时”。比如停了 2 小时免费 15 分钟计费时长 105 分钟前 60 分钟按首小时 5 元剩余 45 分钟按半小时向上取整为 2 个半小时段加 4 元总计 9 元。跨天是另一个坑。如果运营要求“24 小时滚动封顶”上面的函数直接用没问题。如果要求“自然日封顶”就不能把跨天订单当一段算需要把入场到出场按天切段第一段扣免费分钟其余段各自调用一次这个函数再累加。做需求时一定先和运营确认是哪种口径。4.4 停车记录的落库时机与账单快照预约和计费最终要汇合到一张停车记录表上。这张表保存每一次实际停车的入出场时间和最终费用。from decimal import Decimal from django.db import models class ParkingRecord(models.Model): reservation models.OneToOneField(reservation.Reservation, on_deletemodels.CASCADE) entry_time models.DateTimeField(入场时间) exit_time models.DateTimeField(出场时间, nullTrue, blankTrue) fee_amount models.DecimalField(应收金额, max_digits8, decimal_places2, defaultDecimal(0.00)) fee_snapshot models.JSONField(计费规则快照, defaultdict)入场时把预约单流转为completed同时创建一条ParkingRecord出场时写exit_time并调用calculate_fee回填fee_amount。使用OneToOneField保证一条预约单只会产生一条停车记录物理上避免重复计费。fee_snapshot存的是计费时的规则参数快照。以后运营改价历史账单仍然能还原当时的计算依据不需要回刷数据。5. 避坑停车场预约计费系统的 5 个高频翻车点这一章写给正在部署或调试这套代码的人。每一条都是实际跑项目时反复遇到过的问题按“现象 → 原因 → 解决”的顺序说清楚。5.1 mysqlclient 编译失败Windows 上的驱动安装方案现象执行pip install mysqlclient报Microsoft Visual C 14.0 is requiredLinux 上报mysql_config not found。原因mysqlclient是 C 扩展包安装时需要在本地编译Windows 缺编译工具链Linux 缺 MySQL 开发头文件。解决Windows 上最省事的方式是换用pymysql在项目同名目录的__init__.py里打补丁import pymysql pymysql.install_as_MySQLdb()这样 Django 的django.db.backends.mysql会自动使用 pymysql业务代码不用改动。Linux 上可以先安装系统依赖再装 mysqlclientsudo apt install default-libmysqlclient-dev build-essential pip install mysqlclient5.2 数据库里时间差 8 小时USE_TZ 与 TIME_ZONE 的坑现象MySQL 里看到的时间是2025-06-01 03:00:00用户本地时间却是2025-06-01 11:00:00。原因Django 开启USE_TZ True后写入数据库的 DateTimeField 统一转成 UTC 存储。MySQL 连接层没做时区转换直接看到的就是 UTC 时间。解决保持USE_TZ True同时设置TIME_ZONE Asia/Shanghai。代码里需要展示本地时间时用timezone.localtime()转换from django.utils import timezone local_now timezone.localtime(timezone.now())不要为了“让数据库看得顺眼”去改 MySQL 全局time_zone。UTC 存储时间语义清晰跨日结算不会错乱。5.3 并发预约超卖查锁要用 innodb_trx现象两个浏览器同时提交同一个车位的同一时段两条预约都显示成功。另一种表现是页面卡住约 50 秒后报锁等待超时。原因代码里先查询再插入中间没有行锁两个请求都判定“当前空闲”然后各自插入。卡顿则是因为某个事务持有了select_for_update()的行锁却没有及时提交或回滚。解决创建预约必须走 3.2 节的create_reservation函数用事务包住查询和插入。排查卡顿问题时进 MySQL 看事务状态SELECT * FROM information_schema.innodb_trx\G;重点看trx_state和trx_started如果事务长时间处于RUNNING说明代码某个分支忘记提交或回滚。配合SHOW ENGINE INNODB STATUS能看到具体等待锁的表和行。5.4 迁移文件与数据库对不上migrate 报错的处理现象新环境执行python manage.py migrate报relation already exists或InconsistentMigrationHistory。原因有人手工建过表或者迁移文件在并行分支里被改乱导致 Django 的迁移记录和实际数据库不一致。解决开发环境先确认表结构确实存在且与模型一致然后执行python manage.py migrate reservation --fake--fake的含义是“假装这个迁移已经执行过”只写入迁移记录不会真正建表。跑之前一定要确认表已经存在、结构正确。生产环境不要用--fake正确做法是用mysqldump --no-data导出两份库的表结构做 diff以数据库脚本为准对齐。5.5 金额精度与跨天收费Float 和简单乘法为什么不行现象账单偶尔多一分、少一分过夜订单费用高得离谱甚至超过日封顶价。原因用FloatField或 Python 的float累加金额产生二进制误差跨天订单直接按总时长套“首小时 半小时”公式没有按天分段封顶语义失效。解决金额一律用Decimalfrom decimal import Decimal fee Decimal(5.00) Decimal(2.00) * 2计费和跨天规则按第 4 章实现先确认运营要的是“24 小时滚动封顶”还是“自然日封顶”再决定是否按天切段。计费公式必须写成纯函数配单测不要在 view 函数里逐行写算术。6. 用自动化测试与 EXPLAIN 把预约计费系统钉死最后一章不讲新功能讲验证。预约和计费是规则密集型代码改一处很可能牵动另一处。把核心规则固化进测试再把数据库索引补齐这套系统才算真正稳定。6.1 三个必写的回归测试重叠预约、状态机、计费公式至少要为系统补三个测试并发重叠预约必须报错、非法状态流转必须被拒绝、计费公式输出必须符合手算结果。from datetime import timedelta from decimal import Decimal from django.contrib.auth.models import User from django.test import TestCase from django.utils import timezone from billing.models import BillingConfig from billing.services import calculate_fee from reservation.models import ParkingLot, ParkingSpot from reservation.services import ReservationConflict, create_reservation, transition_reservation class ParkingFlowTests(TestCase): def test_overlap_and_state(self): user User.objects.create_user(demo) lot ParkingLot.objects.create(name测试车场) spot ParkingSpot.objects.create(lotlot, spot_noA01) start timezone.now() timedelta(hours1) r1 create_reservation(user, spot.id, start, start timedelta(hours2)) with self.assertRaises(ReservationConflict): create_reservation(user, spot.id, start timedelta(minutes30), start timedelta(hours3)) with self.assertRaises(ValueError): transition_reservation(r1, expired) def test_fee_formula(self): config BillingConfig( first_hour_feeDecimal(5.00), additional_per_half_hourDecimal(2.00), daily_capDecimal(40.00), free_minutes15, ) fee calculate_fee( timezone.now(), timezone.now() timedelta(hours2), config, ) self.assertEqual(fee, Decimal(9.00))运行方式很简单python manage.py test reservation billingTestCase里的数据操作会被事务包住测试结束自动回滚不会污染 MySQL 里的真实数据。6.2 用 EXPLAIN 验证关键查询是否走索引预约表数据量一上来重叠判断的查询就会变成性能瓶颈。用EXPLAIN看执行计划EXPLAIN SELECT * FROM reservation WHERE spot_id 1 AND start_time 2025-06-01 12:00:00 AND end_time 2025-06-01 10:00:00;如果type列是ALL说明全表扫描。解决办法是在 Reservation 模型里加联合索引class Meta: indexes [ models.Index(fields[spot, start_time, end_time]), ]字段顺序有讲究等值条件spot放最前面范围条件start_time和end_time放后面这样索引能同时命中车位过滤和时间范围过滤。6.3 两项低成本加分项自动超时释放与日结报表把 3.4 节的超时释放命令挂上系统计划任务是让系统具备“自愈能力”的最低成本方案。日结报表也值得顺手做按天聚合停车记录里的fee_amount就能输出每日营收。这两项功能实现成本低但对完整度提升非常明显。我后来凡是接手预约类项目第一件事都是先把这三条断言跑绿再去翻数据库有没有建索引。别嫌测试脚本写着麻烦等演示现场或上线当天才暴露问题才是真的叫天天不应。希望帮到你。本文还有配套的精品资源点击获取