diff --git a/server/models/Order.js b/server/models/Order.js index 665b162..23d8886 100644 --- a/server/models/Order.js +++ b/server/models/Order.js @@ -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 }, // 合同是否签署 diff --git a/server/routes/riders.js b/server/routes/riders.js index 93a6e08..f3b0330 100644 --- a/server/routes/riders.js +++ b/server/routes/riders.js @@ -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; diff --git a/server/routes/storeAuth.js b/server/routes/storeAuth.js index 88a5bb6..0504c34 100644 --- a/server/routes/storeAuth.js +++ b/server/routes/storeAuth.js @@ -10,7 +10,7 @@ const { comparePassword } = require('../utils/password'); // 登录限流 const loginLimiter = rateLimit({ windowMs: 15 * 60 * 1000, - max: 10, + max: 100, message: { success: false, message: '登录尝试过于频繁' } });