资讯详情

Django车位共享系统源码解析:从数据库设计到并发控制

📅 2026/10/15 6:00:49 | 华诺云谱 👁 阅读
Django车位共享系统源码解析:从数据库设计到并发控制
我最近翻出一套编号43334的Django车位共享系统源码正好之前有朋友问过这类项目该从哪下手我就顺着这个项目从头到尾梳理了一遍。它解决的场景很常见小区业主白天上班私家车位空着十几个小时另一边写字楼附近的车主却找不到地方停。车位共享的思路就是把闲置时段挂出来按小时租给需要的人。无论你是拿它做毕业设计还是想给小区或小团队搭一套错时停车平台这套源码都能提供一个很完整的业务雏形。下面我会沿着这个项目的壳把需求拆解、数据建模、并发控制、部署上线的关键步骤全部摊开讲不带源码也能自己照着写。1. 车位共享这件事到底要解决什么问题1.1 停车难不是车位少是“错时”机会没被利用城区停车难表面上是总量不够实际上很多固定车位一天里真正被使用的时间只有早晚高峰那一两个小时。我见过不少写字楼附近的车主月租车位买不到临时停车场一小时十几块一个月下来停车费比油费还高。反过来一些私家车位主早上八点出门晚上六点才回来白天车位就这么空着。车位共享系统的核心价值就是把这两类人的需求通过时间维度匹配起来让同一个车位一天内可以被多个时段复用。这套源码里最基础的角色就是两方车位主发布车位车主下单租用。管理员居中审核防止有人把道路红线内的位置也当成车位挂上来。理解了这个逻辑再回头看系统里的功能模块就会很清楚车位管理解决“供给端怎么发布”搜索和下单解决“需求端怎么找到”订单和支付解决“交易怎么闭环”评价和管理员后台解决“事后怎么约束”。1.2 核心角色与业务闭环把业务闭环画出来才能知道代码里哪些表是必须的。一次完整的共享流程大概是这样的车位主注册提交车位信息位置、照片、时段、价格管理员审核通过车位进入“可预订”状态车主按位置、时间搜索看到符合条件的时段车主下单系统生成待支付订单支付成功后该时段被锁定其他人不能再预订车主按时到场停车订单进入“进行中”停车结束订单完成车位主收到结算款双方可以评价。这个闭环里最容易被新手忽略的是“时段”这个概念。很多人会直接在车位上存一个“开始时间-结束时间”认为车位一直都按这个时间段开放。可实际业务里同一个车位可能今天上午被订走、下午开放、晚上又开放一个日期范围根本表达不了这种碎片化的占用情况。后面我在第四节会专门讲怎么用独立的时段表来解决。1.3 拿到“附源码43334”这类项目包后先做需求拆解像“django车位共享系统--附源码43334”这样的资源包网上一抓一大把但绝大多数人拿到手就直接python manage.py runserver看到首页能开就以为完事了。这样做的后果是答辩或面试时一旦被问到“订单超时为什么能自动关闭”“两个人同时抢同一个时段怎么处理”基本就卡住了。我自己的习惯是拿到源码先不看代码先列三张清单。第一张是“已有功能清单”把登录、发车位、搜索、下单、支付、订单管理这些点一个个在页面上点过去第二张是“缺失功能清单”比如有没有退款、有没有超时释放、有没有消息通知第三张是“风险点清单”比如测试数据是不是一堆假订单、管理后台是不是裸奔、密钥是不是写死在配置文件里。先把这三张清单列完再动手改代码思路会清楚很多。2. 技术选型同样是做后台Django强在哪2.1 开发效率优先的时代Django为什么还是这么能打车位共享这类系统本质上是一个“管理业务状态”的系统车位状态要流转订单状态要流转钱要记流水账。选技术栈时最该看的不是谁更炫而是谁能在最短时间内把状态流转安全地落地。Django在这类事务型业务里优势非常明显ORM自带迁移工具改模型后一条命令就能同步数据库Admin后台不用写一行代码就能管理所有数据内置的认证、会话、CSRF防护也省掉了安全设计的一大半工作量。拿这套源码和同样热门的Flask、Express做个对比就能看明白对比点DjangoFlaskExpress数据库建表模型类 migrate 自动迁移Flask-SQLAlchemy 需手动配置较多需要自行选型 ORM后台管理自带 Admin开箱即用需要自己写或接第三方基本没有认证授权内置 User、权限、登录状态Flask-Login 等扩展组合Passport 等扩展组合适合规模中小型业务系统一站看完轻量接口、微服务高并发前端需求较多的场景学习门槛中等模型和迁移概念需要时间低灵活但约定少低但工程化依赖大量中间件不是说Flask不好而是对一个“要交作业、要演示、要快速被看懂”的项目来说Django的“约定优于配置”能少走很多弯路。就算你原来没怎么用过Django照着源码把模型层和视图层翻一遍也能很快建立整体认知。2.2 数据库、缓存和异步任务怎么搭配源码基础环境下用 SQLite 也能跑但只要你开始模拟多人并发下单SQLite 的弱类型和锁机制就会成为瓶颈。我建议在本地开发阶段就切换到 MySQL原因后面讲并发时会提到Django 的select_for_update行级锁在 MySQL 的 InnoDB 引擎下才真正可靠SQLite 虽然不报错但锁的语义是完全不同的。组件层面一套完整方案大致是这样MySQL主数据库存用户、车位、时段、订单、交易流水Redis高频缓存也可以用来做分布式锁和短时订单倒计时缓存Celery Beat处理超时未支付订单关闭、定时生成统计报表Django REST Framework这是扩展项如果你后续打算做小程序端或App端可以直接在现有业务上套一层API。这套源码里大概率只有MySQL加Django自带的简单能力Redis和Celery需要自己补上。不用觉得这是缺点很多真实的创业项目也是先把核心闭环跑通再加这些重型组件。我做二次开发时习惯把“必须现在做”和“可以以后加”分开超时关闭这种直接影响用户资金体验的功能属于前者实时消息推送属于后者。2.3 一套标准Django项目的目录长什么样拿到源码先把目录结构看清后面改代码才不迷路。这类项目通常是按App拆分的manages.py config/ # 项目配置目录 settings.py urls.py wsgi.py apps/ accounts/ # 用户注册、登录、个人中心 parking/ # 车位发布、审核、搜索 order/ # 下单、支付、订单管理 common/ # 公共工具、验证码、文件上传 templates/ # 页面模板 static/ # 静态文件 media/ # 用户上传的图片 requirements.txt这里想提醒一句如果原始源码把业务代码全堆在views.py里一个文件几千行别急着重构。先看懂核心路由和模型关系等要扩展功能时再按模块拆出 service 层。重构是必要的但不是第一优先级。3. 数据库模型怎么设计才能扛住后续扩展3.1 用户模型默认User不够用手机号才是主键Django 自带的 User 带着用户名和邮箱字段但车位共享系统的实际用户基本上都用手机号登录车位主需要做实名认证车主可能还需要绑定车牌。这类扩展字段不建议直接改 Django 的 User 表而是建一个 Profile 用户资料表用 OneToOne 关联过去class Profile(models.Model): user models.OneToOneField(User, on_deletemodels.CASCADE) mobile models.CharField(max_length11, uniqueTrue) real_name models.CharField(max_length50, blankTrue) plate_number models.CharField(max_length10, blankTrue) avatar models.ImageField(upload_toavatar/, blankTrue)手机号设成 unique 后登录逻辑也要跟着改。Django 默认用用户名做认证你需要自定义一个AuthenticationBackend让用户输入手机号和密码也能登录或者干脆把手机号作为用户名存入 User 表。很多新手在这里踩坑改完 Profile 发现数据库迁不过去多半是没处理旧数据或者 field 冲突。3.2 车位与可用时段别把排期塞进JSON字段车位模型是核心字段至少要覆盖这些归属用户、车位标题、详细地址、经纬度、单小时价格、覆盖范围照片、状态、审核备注。价格字段一定要用 DecimalField 而不是 FloatField金额计算出现浮点误差后对账时很难解释。真正需要重点设计的是可用时段。我看到不少车位共享源码会在车位上放一个available_time的 JSON 字段直接存“周一到周五 08:00-20:00”这种字符串。听起来很方便但到了要查“明天上午10点有哪些车位能订”时SQL 根本没法高效过滤只能把数据全部捞到内存里循环判断。正确做法是拆出一张独立的时段表class TimeSlot(models.Model): space models.ForeignKey(ParkingSpace, on_deletemodels.CASCADE, related_nameslots) date models.DateField() start_time models.TimeField() end_time models.TimeField() status models.CharField(max_length20, choices( (open, 可预订), (locked, 锁定), (closed, 关闭)), defaultopen) class Meta: constraints [ models.UniqueConstraint( fields[space, date, start_time], nameunique_space_slot ) ]只要一个时段被下单就把该时段的status改成locked并记录是对应哪个订单。这样车位是否可订就变成了一条简单的数据库查询而不是一段复杂的字符串解析逻辑。这个设计是整张表里性价比最高的改动。3.3 订单状态机一个字段管好所有流转订单表的字段包括用户、车位、时段、开始时间、结束时间、订单金额、支付流水号、下单时间、支付时间、状态。比字段更重要的是状态流转我习惯把状态定义成一个常量类class OrderStatus: PENDING_PAY pending_pay # 待支付 PAID paid # 已支付 IN_PROGRESS in_progress # 停车中 FINISHED finished # 已完成 CANCELED canceled # 已取消 TIME_OUT timeout # 超时关闭 REFUNDED refunded # 已退款状态流转要有明确方向待支付可以变成已支付、已取消、超时关闭已支付可以变成进行中、已退款进行中只能变成已完成。禁止出现“已完成-重新支付”这种往回跳的情况。可以在模型里写一个transition方法统一控制任何地方需要改状态都走同一个入口避免在视图里散落一堆order.status xxx。3.4 索引、外键和金额字段的“基建”规范新手建表最容易忽略三个细节。第一所有外键都要显式指定on_delete常用的是CASCADE或PROTECT。用户注销时订单里的用户外键如果设置成级联删除那整个交易流水就没了这在财务上是事故正确做法是用PROTECT禁止删除有订单关联的用户或者用SET_NULL保留流水。第二高频查询字段要加索引。比如订单表的用户外键、车位表的状态加经纬度、时段表的日期加状态都是典型查询条件。数据量小的时候感觉不到一旦数据到几十万条没有索引的查询能把接口拖到三五秒。第三金额相关的字段老老实实用 DecimalField 并设置max_digits10, decimal_places2。别看不起这个细节我见过因为用 FloatField 存金额导致总额差一毛钱然后通宵对账的真实案例。4. 核心业务逻辑逐个说清实现思路4.1 从发布车位到管理员审核状态切换别藏在页面按钮里车位主提交车位的表单很简单难点在于审核通过和上下架的状态管理。我建议在车位模型上维护一个总的status字段取值包括draft、pending、approved、rejected、off_shelf。车位主提交后状态是 pending管理员在 Admin 后台点通过后变成 approved车位才能在搜索列表里出现。这里有个容易被忽略的通知环节审核结果怎么告诉车位主简单的方案是直接写一个站内信模型审核动作发生时额外插入一条消息紧一点的做法是通过邮箱或短信平台发通知。源码里若只有页面上的状态展示体验会比较生硬二次开发时优先补上这个闭环。4.2 按距离找车位经纬度计算的常见做法搜索功能核心是按距离排序。如果项目数据量不大最直接的办法是在 Python 层用 Haversine 公式计算距离from math import radians, sin, cos, sqrt, asin def haversine(lng1, lat1, lng2, lat2): R 6371.0 d_lng radians(lng2 - lng1) d_lat radians(lat2 - lat1) a sin(d_lat / 2) ** 2 cos(radians(lat1)) * cos(radians(lat2)) * sin(d_lng / 2) ** 2 return R * 2 * asin(sqrt(a))把附近所有 approved 状态的车位全部拉出来逐一算距离按距离从近到远排序再对小于 200 米这种阈值做最近距离说明。这个方案在几千条数据时完全没问题几十万条就会慢。此时可以选两个方向一是迁移到 MySQL 的空间索引或者 PostgreSQL PostGIS用原生地理函数算距离二是用经纬度先粗筛一个矩形范围再在内存里精算。对演示项目来说先跑通 Haversine 就足够了。4.3 预订和支付并发抢单怎么保证不超卖这是整个系统最重要的技术点。先想清楚问题场景两个车主同时看中了同一个车位同一个时段如果不用任何锁两个人都能下单成功支付后时段就冲突了。最简单可靠的方案是“先到先得 数据库行锁”。流程分两步。第一步在事务里对时段记录加锁from django.db import transaction with transaction.atomic(): slot TimeSlot.objects.select_for_update().get( idslot_id, statusopen, datetarget_date ) # 在这里判断当前用户是否已经持有该时段订单 slot.status locked slot.save(update_fields[status]) order LeaseOrder.objects.create(...)select_for_update()会锁住这一行直到事务提交。第二个用户执行到同一行时会阻塞等第一个用户提交后第二个用户拿到的是已经变成locked的记录自然就进不了下单分支。这套方案的关键是必须在事务内执行而且该时段被锁的状态要持久化到数据库不能只放 Redis因为 Redis 一旦重启就全丢了。但这里还要处理另一个问题用户恶意下单占着时段不付钱怎么办单纯locked就卡死了供给。所以需要配合超时释放机制。我用的方案是下单时给订单设置付款截止时间通常是10分钟或15分钟然后通过定时任务把超时订单关闭同时把对应时段状态恢复成open。如果不想引入 Celery也可以写一个 Django 管理命令每五分钟跑一次扫描超时订单效果一样只是不能做到秒级释放。4.4 模拟支付与真实支付接入的边界毕设项目拿不到支付商户号这是正常现象所以源码里给的往往是模拟支付页面跳到一个假支付页点一下“确认支付”系统直接把订单状态改成已支付。这个交互能演示业务闭环但代码里要留好接口边界让以后接真实支付时不用大改。我的做法是封装一个PaymentService对外只暴露create_payment(order)和handle_payment_callback(order, result)两个方法。模拟环境下前者直接生成支付成功结果后者把订单改成已支付真实环境下前者返回支付跳转链接后者处理异步回调。回调处理还有一个铁律必须幂等同一个支付结果通知重复到达也不能把订单状态改来改去否则对账时全是脏数据。4.5 订单状态流转的代码示例用一个简单例子说明如何把状态流转收口。订单支付成功回调里不要直接save()而是通过统一方法处理def pay_order(self, order, payment_no): if order.status ! OrderStatus.PENDING_PAY: return order.status OrderStatus.PAID order.payment_no payment_no order.paid_at timezone.now() order.save(update_fields[status, payment_no, paid_at])进入已支付状态后对应时段保持locked。订单完成后再把时段状态改成closed或释放为下一次可预约。这样每个状态变化都自带前提校验从根源上减少脏数据。5. 我复现这套源码时踩过的坑5.1 时区配置引发的订单时间错乱第一次跑通系统后我测试下单发现订单结束时间比预期差了8个小时。查了半天问题出在settings.py里的TIME_ZONE和USE_TZ。如果设置USE_TZ True但TIME_ZONE Asia/Shanghai模型中所有 DateTimeField 在数据库里存的是 UTC 时间读取时会自动按本地时区展示。问题通常出在代码里手动构造时间from datetime import datetime datetime.now() # 错误这是naive datetime from django.utils import timezone timezone.now() # 正确带时区信息把所有地方改成timezone.now()并且在页面显示前统一用模板过滤或者自定义格式化这个问题就消失了。检查项目里所有datetime.now()的位置凡是和数据库时间做比较的都要改。5.2 行锁不是写了就生效有一段时间我发现并发测试时偶尔会重复下单明明代码里已经写了select_for_update()。后来定位到两个原因。第一该查询没有放在transaction.atomic()块里Django 的部分版本在自动提交模式下select_for_update是不立即生效的第二如果连接的是 SQLite 数据库行锁操作会被静默忽略因为 SQLite 的锁粒度不在行级别。换到 MySQL InnoDB 并补齐事务块之后并发场景才算真正稳定。测试并发时建议开两个浏览器窗口同时点下单或者写一个并发脚本打接口不要只自己手动点一遍。5.3 DEBUG关闭后的静态文件梦魇源码在本地开发时一切正常一部署到服务器就页面惨不忍睹图片全裂。原因是 Django 在DEBUG False时不再自动托管 static 和 media 文件。正确的做法是让 Nginx 直接服务这两个目录或者在 Django 层加urlpatterns static(...)仅限临时环境。如果你发现部署后后台样式全没了先检查 Nginx 配置里的 location 是否指向了正确的静态文件收集目录。图片上传的路径问题同样麻烦。MEDIA_ROOT应该设置成绝对路径不要用相对路径否则在不同目录启动服务时图片可能写到了别的位置。我在源码里看到不少写死/home/user/project/media/的例子换环境后必须逐项改掉。5.4 清理种子数据演示账号、假订单、假图片下载来的源码包里通常带着一堆测试数据包括手机号138xxxx的演示用户、几十条已经完成的假订单、一些从别处复制来的车位照片。不管是做答辩还是自己练手这堆数据都要清理干净。清理不是简单地把数据库删了重建而是要注意外键关联。我会先把订单、钱包流水、评价这类子表数据清空再清空时段和车位最后把用户表里的演示账号删掉之后重新createsuperuser。图片文件也要从 media 目录里删掉否则页面里会出现一堆晒车位的死链。生产环境千万别图省事直接python manage.py flush连管理员账号一起没了更麻烦。5.5 环境变量和密钥最容易被人忽略的安全雷区很多共享源码里把SECRET_KEY、数据库密码直接写在settings.py里这个习惯很危险。Django 的SECRET_KEY一旦泄露攻击者可以伪造会话甚至执行部分攻击。正确做法是用环境变量或者本地.env文件settings.py里用os.getenv(DJANGO_SECRET_KEY)读取.env文件加入.gitignore。如果这套源码要继续往上加功能或者要演示给别人看这一步务必提前做好。6. 把源码跑起来并部署上线6.1 本地五分钟跑通假设你已经把源码包解压到本地快速跑通的步骤是这样的cd django-parking-share python -m venv venv source venv/bin/activate # Windows 下是 venv\Scripts\activate pip install -r requirements.txt python manage.py migrate python manage.py createsuperuser python manage.py runserver访问http://127.0.0.1:8000就能看到首页。如果页面提示静态文件异常先检查requirements.txt是否包含每个 App 依赖以及INSTALLED_APPS里是否少了某个自定义 App。如果你拿到的是前后端混编项目可能还需要在浏览器里允许混合内容或者修改 API 地址配置。6.2 生产部署清单上服务器前把下面这些配置逐项核对配置项建议值说明DEBUGFalse生产环境必须关闭ALLOWED_HOSTS域名或服务器IP列表不配置会直接403SECRET_KEY从环境变量读取不要与开发环境共用DATABASEMySQL不要用SQLite并发和数据安全更可靠STATIC_ROOT独立目录Nginx托管需要先collectstaticMEDIA_ROOT独立目录Nginx托管上传图片的存储位置启动方式建议 Gunicorn 加 Nginx命令大致是gunicorn config.wsgi:application --bind 127.0.0.1:8000 --workers 3Nginx 负责转发请求到 Gunicorn同时托管静态文件和上传图片。如果项目里还有 Celery 任务需要单独启动一个 worker 进程并把 beat 定时器跑起来否则超时关闭、统计报表这类功能不会自动执行。6.3 面向答辩或面试的改造顺序如果这套系统是用来交作业或面试展示我建议在交付前按这个顺序改造先保证核心流程能完整演示从注册、发布车位、审核、下单、支付到订单完成一条链路走通然后把项目里明显不合理的演示数据清掉把页面标题和站点文案改成自己的最后挑一个技术亮点深入讲透比如并发下单的行锁处理或超时释放机制。不懂的部分宁可照实说“目前是模拟支付”也不要含糊带过面试官更看重你对边界问题的理解。写在最后源码之外的收获把整套车位共享系统从源码包跑通到能部署我最大的体会是这种项目真正的难点从来不是 Djangos 语法而是业务状态的设计。车位的闲置时段怎么表达、订单状态怎么流转、并发条件下怎么保证唯一性每一处都需要先想清楚“如果真实世界里有人恶意操作会发生什么”再回到数据库层面找答案。我建议你拿到任何一套源码都先按这个思路把模型关系画出来再去看代码会比直接跑通更有收获。最后分享一个小技巧给订单相关的时间字段统一加注释写上“这是本地时间还是UTC时间”半年后再维护代码时你会感谢当时的自己。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑