用Django实现车位共享系统:从数据模型、并发锁到支付回调的实战指南
初版用的是单表存经纬度然后按距离排序筛选。数据量小的时候没问题但车位一旦过千这个计算量就很尴尬。后来改成了方框粗略筛选加距离精排的两段式方案。先根据当前坐标和搜索半径算出经纬度上下限用ORM的filter把范围缩到几百条内再对这批数据精确算距离排序。这个方案在日常场景下足够用而且实现简单。3.2 预订时段冲突检测与锁定只要车位的资源匹配逻辑靠谱并发冲突就少。这里我设计了不能重叠和可以重叠两类场景。出租车位是按时间段租给一个人的两个订单的时段不能重叠临时租用则以小时为单位同一时段同样不能重复预订。检测冲突的SQL其实很简单在过滤条件里同时判断时间交叉的四种情况即可。真实场景里更重要的是把冲突检测做成原子操作。我用了一个比较朴素的方案在创建订单时先对车位记录执行select_for_update锁定车位行再查询该车位上的有效订单有没有时间段重叠没有才写入订单。注意select_for_update必须在事务内使用。这一行锁的作用就是串行化同一车位的预订请求避免两个人同时看到有空位、同时下单的竞态问题。上线初期这个方案一直很稳订单量大了之后再用Redis分布式锁也别怕逻辑本质是一样的。订单状态流转方面我按照“待支付 - 已支付 - 使用中 - 已完成 - 已取消”这条链路设计了状态机并且用一张状态日志表记录每次变更。做状态机的最大好处是避免乱跳比如一个已完成的订单不能直接取消。这个业务的订单状态比较常规不搞重型工作流一个状态字段加几个条件判断就够了。3.3 支付对接与回调处理支付我接的是线上支付平台的模拟接口。真实对接时支付回调的验签和幂等处理是必须注意的。回调通知到达后第一件事是验签第二件事是根据订单号和交易号查库确认这笔订单确实存在且状态是待支付。如果订单已经完成说明回调重复了直接忽略返回成功。这步幂等要是没做好很容易出现支付成功但订单状态被打回待支付的诡异Bug。支付成功后的业务动作不止是改订单状态还要做两件事一是给车位租用方发消息通知二是往车主的账户里记账。为了不因为某个环节报错导致主流程失败我把这些后续动作放到了事务提交后的信号里去执行。Django的transaction.on_commit在这里很好用提交成功后才触发短信和记账任务哪怕这两步失败了也不会影响支付结果本身。4. 前端交互与后台管理落地4.1 用户端页面与API交互要点页面层面分了两种入口访客浏览和登录操作。首页可以直接搜索周边车位但预约和下单必须登录后操作。这个规则在视图层和模板里都做了校验避免有人绕过前端直接构造请求下单。前端技术栈用的还是Django模板加少量JavaScript。搜索页用一个区域块展示车位卡片每张卡片显示车位名称、地址、距离、价格和可预约时段。这里的距离是后端计算好的传到模板直接展示。用模板引擎渲染的好处是服务端渲染首屏体验不错也不用额外维护前端路由。预订流程分三步选择车位进入详情页 - 选择时段确认金额 - 提交订单去支付。每一步都有对应的URL和视图函数。最开始是三步合一、一次性提交但用户体验不够好无法确认车位详情。拆开后其实逻辑更清晰每步视图的代码量也少坏账率显著下降。前端这里有个容易被忽略的坑列表页和详情页显示的价格必须由后端从车位关联的价格表读取不能由前端传值。提交订单的时候金额要以服务端重新计算的为准前端传过来的金额仅仅是展示参考。否则改一下请求参数就能用1块钱订一天的车位。4.2 管理后台定制发布审核与数据统计Django自带的admin后台在这个项目的价值非常高甚至可以说是整个项目能快速交付的关键。我把车位管理、订单管理、用户管理直接接入admin然后针对运营场景做了一些定制。车位发布后需要管理员审核我在车位模型上加了audit_status字段默认待审核。admin后台的列表页增加了审核状态筛选框和管理员操作按钮。操作按钮通过自定义Admin Action实现选中多个车位可以一键批量审核通过运营同学直呼这功能实在。数据统计这块我没有额外引入图表库直接在admin后台加了几个只读的统计页面展示每日订单量、成交金额、车位新增数。用Django的extra_tags和自定义视图就能解决数据来源于订单表的create_time做TruncDate分组统计。报表虽然朴素但胜在可靠运营看数据完全够用。提醒一下admin后台虽然开发效率高但暴露在公网上之前一定要做访问限制。我用了两层防护第一层是路由上不把admin放在默认的/admin/路径改成一个不易猜测的路径第二层是对admin的视图做IP白名单和登录权限校验。不要小看这个默认路径网上扫描器遍地都是。5. 常见问题与排查技巧实录5.1 高频问题速查表开发过程中有几个问题反复被踩我整理成了速查表供后来者直接对照。问题现象可能原因解决方案用户注册提示邮箱已存在用的缺省User模型用户名脱不开邮箱约束更换扩展的User模型或注册时用手机号做唯一标识车位列表详情和列表页价格不一致详情页读取的是最新价格列表页是缓存数据未更新在保存车位的信号里主动清理对应缓存地图搜索总是搜不到车位经纬度字段格式不对或存反了经度纬度打印每条记录的坐标用地图工具手动验证订单支付后状态未更新回调处理里验签或幂等校验不过在回调函数开头加日志观察返回码和订单号两个订单时间重叠冲突检测条件少了边界判断确保查询条件包含开始时间相等、结束时间相等这类边界情况车位列表加载慢关联查询没有用select_related检查ORM查询语句对车位关联的业主、地址信息做联表预取5.2 性能优化与开发效率心得说到性能这个系统的瓶颈不在数据库查询而在图片上传和列表页渲染。车位照片是用户上传的最初直接把原图存下来列表页一次加载几十张几兆大小的图片页面耗时直接拉满。后来我做了图片压缩上传时用Pillow把长边缩到800像素并转成JPEG格式性能提升了不止一个量级。列表页渲染方面除了select_related预取关联表之外还有一个技巧把车位描述这类大文本字段在列表查询时排除掉用ORM的only方法指定需要的字段减少数据传输量。别小看这个操作车位数上来以后列表接口的响应时间能降一半。开发效率方面我想说一件事先用admin把后台跑通再补前台。这个顺序能少走很多弯路。admin后台天然帮你把增删改查、权限、分页都做了业务逻辑在里面校验通过后前台页面只是换一层皮。如果一开始就埋头写前台视图和表单整个项目节奏会慢很多还容易遗漏数据维护入口。6. 几点真心话这套车位共享系统我从数据模型设计到上线部署前后用了不到两周的时间。Django的确是很适合这类项目的框架它的ORM、Admin和信号机制让业务开发从繁琐的重复劳动里解放了不少。我在实际开发中最大的体会是先把数据模型和状态流转理清楚再去写页面基本上不会有大返工。数据结构稳了业务逻辑就稳了页面只是数据的呈现方式。另外想给正在做类似系统或者准备仿照这个项目练手的朋友一个建议别一开始就想着把所有功能都堆上去。车位共享的核心就是发车位、搜车位、下订单、做支付、管状态。把这几条链路打磨顺了比增加评论点赞分享这些辅助功能有价值得多。代码写到最后真正决定工程质量的是那些看不见的地方——权限控制、异常处理、日志记录、并发锁这些才是系统能长期稳定运行的关键。最后再分享一个小技巧开发时一定要早早在关键业务操作里埋上日志。比如订单状态变更、支付回调、用户注册这些地方打点线上出问题时你能省下大半排查时间。我就是被线上问题查了两天之后才把所有关键流程的日志老老实实补齐的。日志是你和线上系统的对话窗口别省这几行代码。