全栈实战:拼车匹配系统的设计与实现

4 阅读4分钟

一、需求背景

每到放假,去火车站和机场拼车是刚需。与其在各个群里喊人,不如在论坛里做一个拼车渠道——发布需求、自动匹配、安全联系。

这篇文章记录我从需求分析到上线的完整开发过程。

二、功能设计

核心流程

  1. 用户发布拼车:出发时间、目的地、微信号(自动从个人主页获取)
  2. 系统自动匹配:相同目的地 + 出发时间 ±20分钟
  3. 看到匹配的人后,点击“申请拼车”
  4. 发布者收到通知,同意或拒绝
  5. 双方同意后,可互看微信号

隐私保护设计:微信号存储在数据库,但列表接口绝不返回。只有双方都同意后,才通过专门接口获取。这个思路借鉴了论坛树洞功能的匿名设计——核心数据不公开,按需授权。

三、数据模型设计

const carpoolSchema = new mongoose.Schema({
  user: { type: ObjectId, ref: 'User' },      // 发布者
  wechat: { type: String, required: true },     // 微信(不公开返回)
  departureTime: { type: Date, required: true },// 出发时间
  destination: { type: String, enum: [...] },   // 目的地枚举
  status: { type: String, enum: ['active', 'expired', 'cancelled'] },
  expireAt: { type: Date, required: true },     // 过期时间(出发+1h)
  applicants: [{                                 // 申请记录
    user: { type: ObjectId, ref: 'User' },
    status: { type: String, enum: ['pending', 'accepted', 'rejected'] }
  }]
}, { timestamps: true });

设计要点

  • expireAt:出发时间 + 1小时自动过期,查询时过滤 expireAt > now
  • applicants:用数组存储所有申请记录,支持同意/拒绝操作
  • 限制一个用户只能有一个活跃订单(发布时检查)

四、匹配算法实现

拼车匹配的核心是相同目的地 + 出发时间 ±20分钟。用 MongoDB 的范围查询操作符实现:

const twentyMin = 20 * 60 * 1000;
filter.departureTime = {
  $gte: new Date(depDate.getTime() - twentyMin),
  $lte: new Date(depDate.getTime() + twentyMin)
};

$gte 是“大于等于”,$lte 是“小于等于”。一开始我不熟悉这些操作符,理解后发现它们和 JavaScript 的 >=<= 逻辑完全一样,只是写法不同。

自动匹配策略:如果前端没传筛选参数,后端自动使用当前用户自己的目的地和出发时间进行匹配。这样用户发布拼车后,不需要手动筛选,列表自动显示匹配结果。

五、接口设计

方法路径功能
GET/api/carpool获取匹配列表
POST/api/carpool发布拼车
DELETE/api/carpool/:id取消拼车
POST/api/carpool/:id/apply申请拼车
PUT/api/carpool/:id/approve同意申请
PUT/api/carpool/:id/reject拒绝申请
GET/api/carpool/:id/contact查看微信号

关键权限校验

  • 申请:不能申请自己的拼车,不能重复申请
  • 同意/拒绝:只有发布者本人才能操作
  • 查看微信:只有发布者或已获得同意的申请者才能查看

六、前端实现

拼车页面(Carpool.vue)分为三个区域:

  1. 发布表单:日期选择器(datetime-local)、目的地下拉框、发布按钮
  2. 我的订单:显示自己的拼车信息,包含申请人数统计和取消按钮
  3. 匹配列表:显示其他人的匹配结果,包含申请按钮

解决日历弹窗 Bug:自定义 CSS 覆盖了浏览器原生日期控件的 Shadow DOM,导致日历无法弹出。排查思路是先用纯 HTML 测试确认浏览器支持,再定位到 CSS 冲突,最后用 :not([type="datetime-local"]) 排除自定义样式。

七、通知系统集成

拼车申请的操作(同意/拒绝/查看微信)统一放在消息通知页面处理,拼车页面只负责发布和展示。

扩展了 Notification 模型,新增三种通知类型:carpool_request(申请)、carpool_approve(同意)、carpool_reject(拒绝)。通知列表根据类型显示不同按钮:

  • 申请通知 → 显示“同意”和“拒绝”
  • 同意通知 → 显示“查看微信”

操作完成后通过 window.dispatchEvent 通知拼车页面刷新数据。

八、踩坑记录

  1. 通知类型枚举不一致:模型的 enum 和接口里用的 type 字符串不匹配,导致 ValidationError。修复后发现保持枚举值统一是后端开发的基本功。

  2. 订单被覆盖find() 只返回第一个匹配项,导致用户发布第二条拼车时“我的订单”只显示一条。改用 filter() 取所有自己的订单。

  3. 匹配机制失效:最初 GET / 接口只在收到查询参数时才筛选。后来改成无参数时自动使用用户自己的拼车信息筛选,匹配才真正生效。

  4. 中间件改造:auth 中间件最初直接信任 JWT payload,但拼车发布需要微信号。改为每次查库后注入完整用户信息,顺便给 password 字段加了 select: false 防止泄露。

九、总结

拼车功能是论坛最后一个大模块,完整实现了一套申请-同意-拒绝的闭环流程。从数据模型设计到接口权限校验,从匹配算法到隐私保护,这次开发让我对全栈工程有了更深的理解。

这也是我对论坛项目的最后一次大功能迭代。从最开始的 CRUD,到后来的安全加固、通知系统重构,再到拼车匹配,每一步都踩过坑,但每一步都在进步。