简介:基于Python与Django、MySQL开发的停车场预约停车计费系统毕业设计源码包,面向计算机相关专业学生,可作为毕业设计或课程设计的完整参考。系统分为管理员和用户两种身份:用户可注册登录,查询区域与车位信息,预约车位时自动进行时间冲突检测,还能管理自己的车辆信息、查看预约与停车记录、发布留言;管理员负责管理用户、区域和车位,办理车辆停车和离开业务并自动结算费用,同时审核预约、发布新闻公告、处理用户留言。资源包共2000个文件,以JavaScript、HTML、CSS等前端文件为主,包含Python后端源码、数据库脚本以及JSON、文本等辅助文件,整个压缩包仅5.98MB,目录结构清晰,便于导入开发环境直接运行。目前已有227人学习下载,这份源码对想快速搭建同类型管理系统、熟悉Django+MySQL联动开发流程的读者很有帮助,数据库脚本也可直接导入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 + SQLite | Admin 可用 | 并发写入锁库 | 纯本地演示,不适合真实预约 |
所以这个题目选 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两个 app,reservation管车位、预约单、状态流转,billing管费率配置、计费计算和停车记录。业务边界分开后,后面写测试、定位问题都会清爽很多。
2.3 连接 MySQL 与配置 Settings:字符集、引擎和认证参数
先在 MySQL 里建库。字符集这里是最容易踩坑的地方,很多默认建库语句只用utf8,遇到 emoji 或生僻字直接报错。推荐用utf8mb4:
CREATE 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_mode='STRICT_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_length=60) address = models.CharField('地址', max_length=200, blank=True) total_spots = models.IntegerField('总车位数', default=0) class ParkingSpot(models.Model): lot = models.ForeignKey(ParkingLot, on_delete=models.CASCADE, related_name='spots') spot_no = models.CharField('车位编号', max_length=20) is_active = models.BooleanField('可用', default=True) class Meta: unique_together = [('lot', 'spot_no')] class Reservation(models.Model): user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE) spot = models.ForeignKey(ParkingSpot, on_delete=models.PROTECT) start_time = models.DateTimeField('预约开始时间') end_time = models.DateTimeField('预约结束时间') status = models.CharField('状态', max_length=20, default='confirmed') created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True)字段设计上有几个容易被忽略的点。ParkingSpot的unique_together保证同一个车场不会出现两个 A01;is_active用于停用车位但不删除历史预约记录。Reservation.spot外键用on_delete=models.PROTECT而不是 CASCADE,预约单是业务凭证,不能因为车位被删就连带消失。status用普通字符串而不是数字枚举,代码里可读性更好,状态流转规则第三章再讲。
| 模型 | 作用 | 关键字段 |
|---|---|---|
| ParkingLot | 车场主体 | name, address, total_spots |
| ParkingSpot | 具体车位 | lot, spot_no, is_active |
| Reservation | 预约单 | 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(id=spot_id, is_active=True) overlap_exists = Reservation.objects.filter( spot=spot, status__in=['confirmed', 'pending'], start_time__lt=end_time, end_time__gt=start_time, ).exists() if overlap_exists: raise ReservationConflict("该车位在这个时段已被预约") return Reservation.objects.create( user=user, spot=spot, start_time=start_time, end_time=end_time, status='confirmed', )逻辑说明:select_for_update()必须在transaction.atomic块内部,行锁持续到事务结束。status__in=['confirmed', 'pending']的意思是只有“待确认”和“已确认”的预约会参与冲突判断,取消和已完成的单子不会占用车位时段。
时间段重叠判断是这套逻辑的关键:start_time__lt=end_time且end_time__gt=start_time,表示两条预约只要有任意交集就算冲突。注意边界情况,如果 A 的结束时间恰好等于 B 的开始时间,这种首尾相接的预约不应该冲突,上面的判断条件天然放行了。
注意:事务里不要发送 HTTP 请求或调用外部接口。行锁在你手里握着,外部接口响应慢 10 秒,另一个预约请求就干等 50 秒。MySQL 默认
innodb_lock_wait_timeout是 50 秒,超过就报锁等待超时。
3.3 预约状态机的流转约束与取消逻辑
预约单状态不是随便 UPDATE 的,否则会出现“已取消又变已完成”的脏数据。把流转规则收敛成一个状态机函数,所有状态变更都走这个入口。
| 当前状态 | 允许流转到 |
|---|---|
| pending | confirmed / cancelled |
| confirmed | completed / cancelled / expired |
| completed / cancelled / expired | 终态,不可再流转 |
对应的函数可以放在reservation/services.py:
ALLOWED_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/Shanghai'USE_TZ = True时,Django 存入 MySQL 的时间统一转成 UTC,读取时再转回当前时区。所以在数据库里看到时间比本地少 8 小时是正常的,不要慌,更不要去改 MySQL 的全局时区来“纠正”。
超时未入场的预约需要定期释放车位。常见做法是写一个 Django management command,放在reservation/management/commands/expire_reservations.py:
from 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(minutes=15) count = Reservation.objects.filter( status='confirmed', start_time__lt=deadline, ).update(status='expired') self.stdout.write(f"已过期 {count} 条预约")这里用update()批量更新,不会触发模型的save()方法,效率高,但如果你在save()里挂了其他业务钩子,这里就不会执行。15 分钟阈值是常见默认值,实际项目里按运营规则调整即可。Windows 上用计划任务,Linux 上用 cron,定时执行python manage.py expire_reservations。
4. 计费规则引擎的设计:首小时、封顶价与跨天时长计算
计费是停车场系统里用户最敏感的部分,多收一分钱都会被投诉。这部分的核心不是写一个公式,而是把规则参数化、计算纯函数化、历史账单快照化。规则能改、公式能测、账单能溯源,才算一个能拿出手的计费模块。
4.1 计费参数表:先定规则再写代码
写代码之前,先把运营规则拆成参数。一套常见的停车场计费规则如下:
| 参数 | 默认值 | 含义 |
|---|---|---|
| first_hour_fee | 5.00 | 停车首小时费用 |
| additional_per_half_hour | 2.00 | 超过首小时后,每 30 分钟费用 |
| daily_cap | 40.00 | 24 小时滚动封顶价 |
| free_minutes | 15 | 免费分钟数,超过才开始计费 |
这套规则翻译成人话是:前 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_digits=7, decimal_places=2, default=Decimal('5.00')) additional_per_half_hour = models.DecimalField('超时每半小时费用', max_digits=7, decimal_places=2, default=Decimal('2.00')) daily_cap = models.DecimalField('24小时封顶', max_digits=7, decimal_places=2, default=Decimal('40.00')) free_minutes = models.PositiveIntegerField('免费分钟数', default=15) updated_at = models.DateTimeField(auto_now=True) 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_delete=models.CASCADE) entry_time = models.DateTimeField('入场时间') exit_time = models.DateTimeField('出场时间', null=True, blank=True) fee_amount = models.DecimalField('应收金额', max_digits=8, decimal_places=2, default=Decimal('0.00')) fee_snapshot = models.JSONField('计费规则快照', default=dict)入场时把预约单流转为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 required,Linux 上报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 上可以先安装系统依赖再装 mysqlclient:
sudo 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累加金额产生二进制误差;跨天订单直接按总时长套“首小时 + 半小时”公式,没有按天分段,封顶语义失效。
解决:金额一律用Decimal:
from 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(lot=lot, spot_no='A01') start = timezone.now() + timedelta(hours=1) r1 = create_reservation(user, spot.id, start, start + timedelta(hours=2)) with self.assertRaises(ReservationConflict): create_reservation(user, spot.id, start + timedelta(minutes=30), start + timedelta(hours=3)) with self.assertRaises(ValueError): transition_reservation(r1, 'expired') def test_fee_formula(self): config = BillingConfig( first_hour_fee=Decimal('5.00'), additional_per_half_hour=Decimal('2.00'), daily_cap=Decimal('40.00'), free_minutes=15, ) fee = calculate_fee( timezone.now(), timezone.now() + timedelta(hours=2), 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,就能输出每日营收。这两项功能实现成本低,但对完整度提升非常明显。
我后来凡是接手预约类项目,第一件事都是先把这三条断言跑绿,再去翻数据库有没有建索引。别嫌测试脚本写着麻烦,等演示现场或上线当天才暴露问题,才是真的叫天天不应。希望帮到你。
本文还有配套的精品资源,点击获取