一、需求背景
每到放假,去火车站和机场拼车是刚需。与其在各个群里喊人,不如在论坛里做一个拼车渠道——发布需求、自动匹配、安全联系。
这篇文章记录我从需求分析到上线的完整开发过程。
二、功能设计
核心流程:
- 用户发布拼车:出发时间、目的地、微信号(自动从个人主页获取)
- 系统自动匹配:相同目的地 + 出发时间 ±20分钟
- 看到匹配的人后,点击“申请拼车”
- 发布者收到通知,同意或拒绝
- 双方同意后,可互看微信号
隐私保护设计:微信号存储在数据库,但列表接口绝不返回。只有双方都同意后,才通过专门接口获取。这个思路借鉴了论坛树洞功能的匿名设计——核心数据不公开,按需授权。
三、数据模型设计
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 > nowapplicants:用数组存储所有申请记录,支持同意/拒绝操作- 限制一个用户只能有一个活跃订单(发布时检查)
四、匹配算法实现
拼车匹配的核心是相同目的地 + 出发时间 ±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)分为三个区域:
- 发布表单:日期选择器(
datetime-local)、目的地下拉框、发布按钮 - 我的订单:显示自己的拼车信息,包含申请人数统计和取消按钮
- 匹配列表:显示其他人的匹配结果,包含申请按钮
解决日历弹窗 Bug:自定义 CSS 覆盖了浏览器原生日期控件的 Shadow DOM,导致日历无法弹出。排查思路是先用纯 HTML 测试确认浏览器支持,再定位到 CSS 冲突,最后用 :not([type="datetime-local"]) 排除自定义样式。
七、通知系统集成
拼车申请的操作(同意/拒绝/查看微信)统一放在消息通知页面处理,拼车页面只负责发布和展示。
扩展了 Notification 模型,新增三种通知类型:carpool_request(申请)、carpool_approve(同意)、carpool_reject(拒绝)。通知列表根据类型显示不同按钮:
- 申请通知 → 显示“同意”和“拒绝”
- 同意通知 → 显示“查看微信”
操作完成后通过 window.dispatchEvent 通知拼车页面刷新数据。
八、踩坑记录
-
通知类型枚举不一致:模型的
enum和接口里用的type字符串不匹配,导致ValidationError。修复后发现保持枚举值统一是后端开发的基本功。 -
订单被覆盖:
find()只返回第一个匹配项,导致用户发布第二条拼车时“我的订单”只显示一条。改用filter()取所有自己的订单。 -
匹配机制失效:最初
GET /接口只在收到查询参数时才筛选。后来改成无参数时自动使用用户自己的拼车信息筛选,匹配才真正生效。 -
中间件改造:auth 中间件最初直接信任 JWT payload,但拼车发布需要微信号。改为每次查库后注入完整用户信息,顺便给
password字段加了select: false防止泄露。
九、总结
拼车功能是论坛最后一个大模块,完整实现了一套申请-同意-拒绝的闭环流程。从数据模型设计到接口权限校验,从匹配算法到隐私保护,这次开发让我对全栈工程有了更深的理解。
这也是我对论坛项目的最后一次大功能迭代。从最开始的 CRUD,到后来的安全加固、通知系统重构,再到拼车匹配,每一步都踩过坑,但每一步都在进步。