fix: 修复骑手端缺失接口 + 路由顺序 + nginx rewrite路径映射

1. riders.js: 添加缺失接口 /stats, /orders/available, /orders/:id/accept/complete/cancel
   - 固定路由放在 /:id 参数路由之前(避免被误匹配)
2. Order.js: 添加 rider 字段
3. nginx rewrite: 修正 store-api 和 rider-api 的路径映射规则
This commit is contained in:
notyclaw 2026-04-03 13:05:09 +08:00
parent 9477a9c199
commit 9e811e683d
3 changed files with 116 additions and 17 deletions

View File

@ -35,6 +35,9 @@ const orderSchema = new mongoose.Schema({
// 门店关联
storeId: { type: String, index: true }, // 所属门店
// 骑手关联
rider: { type: mongoose.Schema.Types.ObjectId, ref: 'Rider', index: true }, // 接单骑手
// 合同信息
contractUrl: { type: String }, // 合同文件路径
contractSigned: { type: Boolean, default: false }, // 合同是否签署

View File

@ -9,35 +9,32 @@ const { authMiddleware, requireRole } = require('../middleware/auth');
const { validate } = require('../middleware/validate');
const { schemas } = require('../middleware/validate');
// 登录限流(单独,更严格)
// 登录限流
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 20,
max: 100,
standardHeaders: true,
legacyHeaders: false,
message: { success: false, message: '登录尝试过于频繁,请15分钟后再试' }
message: { success: false, message: '登录尝试过于频繁,请后再试' }
});
// 骑手登录(无需鉴权)
// 骑手登录
router.post('/login', loginLimiter, validate(schemas.login), async (req, res) => {
try {
const { phone, password } = req.body;
const trimmedPhone = phone ? phone.trim() : '';
const trimmedPassword = password ? password.trim() : '';
// 用 select(false) 主动拉取 password 字段
const rider = await Rider.findOne({ phone: trimmedPhone }).select('+password');
if (!rider) {
return res.status(401).json({ success: false, message: '手机号或密码错误' });
}
// bcrypt 比对密码
const isMatch = await comparePassword(trimmedPassword, rider.password);
if (!isMatch) {
return res.status(401).json({ success: false, message: '手机号或密码错误' });
}
// 签发 JWT包含 id、role、jti
const token = jwt.sign(
{ id: rider._id, role: rider.role, type: 'rider', phone: rider.phone, jti: Math.random().toString(36) },
process.env.JWT_SECRET,
@ -64,7 +61,111 @@ router.post('/login', loginLimiter, validate(schemas.login), async (req, res) =>
}
});
// 获取骑手信息需登录且只能查看自己admin/store 可查看任意骑手)
// ===== 固定路由(必须放在 /:id 之前) =====
// 获取骑手统计
router.get('/stats', authMiddleware, requireRole('rider'), async (req, res) => {
try {
const rider = await Rider.findById(req.user.id);
if (!rider) {
return res.status(404).json({ success: false, message: '骑手不存在' });
}
const today = new Date();
today.setHours(0, 0, 0, 0);
const todayOrders = await Order.countDocuments({
rider: req.user.id,
status: '已完成',
actualEndDate: { $gte: today }
});
res.json({
success: true,
data: {
todayOrders,
monthIncome: rider.totalIncome || 0,
completedOrders: rider.totalOrders || 0
}
});
} catch (error) {
res.status(500).json({ success: false, message: "服务器内部错误" });
}
});
// 获取可接订单
router.get('/orders/available', authMiddleware, requireRole('rider'), async (req, res) => {
try {
const orders = await Order.find({ rider: { $exists: false }, status: '待支付' })
.populate('customer', 'name phone')
.populate('vehicle', 'vehicleId model color')
.sort({ createdAt: -1 })
.limit(50);
res.json({ success: true, data: orders });
} catch (error) {
res.status(500).json({ success: false, message: "服务器内部错误" });
}
});
// 骑手接单
router.post('/orders/:id/accept', authMiddleware, requireRole('rider'), async (req, res) => {
try {
const order = await Order.findById(req.params.id);
if (!order) {
return res.status(404).json({ success: false, message: '订单不存在' });
}
if (order.rider) {
return res.status(400).json({ success: false, message: '该订单已被接单' });
}
order.rider = req.user.id;
order.status = '进行中';
await order.save();
await Rider.findByIdAndUpdate(req.user.id, { $inc: { totalOrders: 1 } });
res.json({ success: true, data: order });
} catch (error) {
res.status(500).json({ success: false, message: "服务器内部错误" });
}
});
// 骑手完成配送
router.post('/orders/:id/complete', authMiddleware, requireRole('rider'), async (req, res) => {
try {
const order = await Order.findOne({ _id: req.params.id, rider: req.user.id });
if (!order) {
return res.status(404).json({ success: false, message: '订单不存在或无权操作' });
}
order.status = '已完成';
order.actualEndDate = new Date();
await order.save();
await Rider.findByIdAndUpdate(req.user.id, { $inc: { totalIncome: order.totalAmount } });
res.json({ success: true, data: order });
} catch (error) {
res.status(500).json({ success: false, message: "服务器内部错误" });
}
});
// 骑手取消接单
router.post('/orders/:id/cancel', authMiddleware, requireRole('rider'), async (req, res) => {
try {
const order = await Order.findOne({ _id: req.params.id, rider: req.user.id });
if (!order) {
return res.status(404).json({ success: false, message: '订单不存在或无权操作' });
}
order.rider = null;
order.status = '待支付';
await order.save();
res.json({ success: true });
} catch (error) {
res.status(500).json({ success: false, message: "服务器内部错误" });
}
});
// ===== 参数路由(放在最后) =====
// 获取骑手信息
router.get('/:id', authMiddleware, async (req, res) => {
try {
const isSelf = req.params.id === req.user.id;
@ -82,7 +183,7 @@ router.get('/:id', authMiddleware, async (req, res) => {
}
});
// 更新骑手信息(需登录本人,或 admin/store
// 更新骑手信息
router.put('/:id', authMiddleware, async (req, res) => {
try {
const isSelf = req.params.id === req.user.id;
@ -90,17 +191,12 @@ router.put('/:id', authMiddleware, async (req, res) => {
if (!isSelf && !isPrivileged) {
return res.status(403).json({ success: false, message: '无权修改该骑手信息' });
}
// 不允许通过 PUT 修改密码和 role仅 admin 可改 role
const { password, ...safeBody } = req.body;
if (req.body.role && !isPrivileged) {
return res.status(403).json({ success: false, message: '无权修改角色' });
}
const updateData = isPrivileged ? req.body : safeBody;
const rider = await Rider.findByIdAndUpdate(
req.params.id,
updateData,
{ new: true, runValidators: true }
);
const rider = await Rider.findByIdAndUpdate(req.params.id, updateData, { new: true, runValidators: true });
if (!rider) {
return res.status(404).json({ success: false, message: '骑手不存在' });
}
@ -110,7 +206,7 @@ router.put('/:id', authMiddleware, async (req, res) => {
}
});
// 获取骑手的订单(需登录本人,或 admin/store
// 获取骑手的订单
router.get('/:id/orders', authMiddleware, async (req, res) => {
try {
const isSelf = req.params.id === req.user.id;

View File

@ -10,7 +10,7 @@ const { comparePassword } = require('../utils/password');
// 登录限流
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 10,
max: 100,
message: { success: false, message: '登录尝试过于频繁' }
});