From 0f1e2c759ed1f285739c4ff226e0eadd2f4bf0da Mon Sep 17 00:00:00 2001 From: notyclaw Date: Sun, 5 Apr 2026 20:15:41 +0800 Subject: [PATCH] Update: server/middleware/validate.js | 8 +- server/routes/storeAuth.js | 224 +++++++++++++++++++++++++++++++++++------- 2 files changed, 193 insertions(+), 39 deletions(-) --- server/middleware/validate.js | 8 +- server/routes/storeAuth.js | 224 ++++++++++++++++++++++++++++------ 2 files changed, 193 insertions(+), 39 deletions(-) diff --git a/server/middleware/validate.js b/server/middleware/validate.js index 8880d07..7fed337 100644 --- a/server/middleware/validate.js +++ b/server/middleware/validate.js @@ -21,7 +21,13 @@ module.exports = { validate }; // ─── 常用校验 Schema ─────────────────────────────────────────── const schemas = { - // 骑手登录 + // 门店登录(用 username) + storeLogin: Joi.object({ + username: Joi.string().min(1).max(50).required(), + password: Joi.string().min(6).max(20).required() + }), + + // 骑手登录(用 phone) login: Joi.object({ phone: Joi.string().pattern(/^1[3-9]\d{9}$/).required(), password: Joi.string().min(6).max(20).required() diff --git a/server/routes/storeAuth.js b/server/routes/storeAuth.js index 0504c34..ee5dc4a 100644 --- a/server/routes/storeAuth.js +++ b/server/routes/storeAuth.js @@ -2,67 +2,215 @@ const express = require('express'); const router = express.Router(); const jwt = require('jsonwebtoken'); const rateLimit = require('express-rate-limit'); -const User = require('../models/User'); -const UserRole = require('../models/UserRole'); -const Role = require('../models/Role'); +const Store = require('../models/Store'); +const StoreAuth = require('../models/StoreAuth'); +const Order = require('../models/Order'); +const Vehicle = require('../models/Vehicle'); const { comparePassword } = require('../utils/password'); +const { authMiddleware } = require('../middleware/auth'); +const { validate } = require('../middleware/validate'); +const { schemas } = require('../middleware/validate'); -// 登录限流 const loginLimiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 100, - message: { success: false, message: '登录尝试过于频繁' } + standardHeaders: true, + legacyHeaders: false, + message: { success: false, message: '登录尝试过于频繁,请稍后再试' } }); -router.post('/login', loginLimiter, async (req, res) => { +router.post('/login', loginLimiter, validate(schemas.storeLogin), async (req, res) => { try { const { username, password } = req.body; const trimmedUsername = username ? username.trim() : ''; const trimmedPassword = password ? password.trim() : ''; - if (!trimmedUsername || !trimmedPassword) { - return res.status(400).json({ success: false, message: '用户名和密码不能为空' }); - } - - // 从 User 表查 store 类型账号 - const user = await User.findOne({ username: trimmedUsername, type: 'store' }).select('+password'); - // 查关联的门店 - const Store = require('../models/Store'); - const store = await Store.findOne({ storeId: user.storeId }); - if (!user || user.status !== 'active') { + const auth = await StoreAuth.findOne({ username: trimmedUsername }).select('+password'); + if (!auth) { return res.status(401).json({ success: false, message: '用户名或密码错误' }); } - - const isMatch = await comparePassword(trimmedPassword, user.password); + const isMatch = await comparePassword(trimmedPassword, auth.password); if (!isMatch) { return res.status(401).json({ success: false, message: '用户名或密码错误' }); } - const token = jwt.sign( - { - id: user._id, - role: 'store', - type: 'store', - storeId: user.storeId || null, - permissions: ['store:read', 'store:write', 'orders:read', 'orders:write', 'vehicles:read', 'vehicles:write', 'vehicleTypes:read'], - jti: Math.random().toString(36) - }, + { id: auth._id, role: 'store', type: 'store', username: auth.username, storeId: auth.storeId, jti: Math.random().toString(36) }, process.env.JWT_SECRET, - { expiresIn: process.env.JWT_EXPIRES_IN || '24h' } + { expiresIn: process.env.JWT_EXPIRES_IN || '7d' } ); - res.json({ success: true, - data: { - id: store ? store._id : user._id, // 门店的 MongoDB _id - storeId: user.storeId, // 门店编号如 STORE001 - username: user.username, - name: user.name, - role: 'store', - token - } + data: { id: auth._id, storeId: auth.storeId, username: auth.username, name: auth.name, status: auth.status, role: auth.role, token } }); } catch (error) { - res.status(500).json({ success: false, message: '服务器内部错误' }); + res.status(500).json({ success: false, message: "服务器内部错误" }); + } +}); + +router.get('/store/info', authMiddleware, async (req, res) => { + try { + const store = await Store.findOne({ storeId: req.user.storeId }); + if (!store) { + return res.status(404).json({ success: false, message: '门店不存在' }); + } + res.json({ success: true, data: store }); + } catch (error) { + res.status(500).json({ success: false, message: "服务器内部错误" }); + } +}); + +router.put('/store/info', authMiddleware, async (req, res) => { + try { + const { name, address, phone, manager, status } = req.body; + const store = await Store.findOneAndUpdate( + { storeId: req.user.storeId }, + { name, address, phone, manager, status }, + { new: true, runValidators: true } + ); + if (!store) { + return res.status(404).json({ success: false, message: '门店不存在' }); + } + res.json({ success: true, data: store }); + } catch (error) { + res.status(500).json({ success: false, message: "服务器内部错误" }); + } +}); + +router.get('/orders', authMiddleware, async (req, res) => { + try { + const orders = await Order.find({ storeId: req.user.storeId }) + .populate('customer', 'name phone') + .populate('vehicle', 'model color vehicleId plateNumber') + .sort({ createdAt: -1 }); + res.json({ success: true, data: orders }); + } catch (error) { + res.status(500).json({ success: false, message: "服务器内部错误" }); + } +}); + +router.post('/orders', authMiddleware, async (req, res) => { + try { + const { customerName, customerPhone, vehicleId, startDate, endDate, deposit, totalAmount, notes } = req.body; + if (!customerName || !customerPhone || !vehicleId || !totalAmount) { + return res.status(400).json({ success: false, message: '请填写必填项' }); + } + const vehicle = await Vehicle.findOne({ _id: vehicleId, storeId: req.user.storeId }); + if (!vehicle) { + return res.status(404).json({ success: false, message: '车辆不存在或不属于本门店' }); + } + const order = await Order.create({ + storeId: req.user.storeId, + customer: { name: customerName, phone: customerPhone }, + vehicle: vehicleId, + startDate, + endDate, + deposit: deposit || 0, + totalAmount, + notes, + status: '待支付' + }); + res.json({ success: true, data: order }); + } catch (error) { + res.status(500).json({ success: false, message: "服务器内部错误" }); + } +}); + +router.put('/orders/:id', authMiddleware, async (req, res) => { + try { + const { status, notes } = req.body; + const order = await Order.findOneAndUpdate( + { _id: req.params.id, storeId: req.user.storeId }, + { status, notes }, + { new: true, runValidators: true } + ); + if (!order) { + return res.status(404).json({ success: false, message: '订单不存在' }); + } + if (status === '已完成') { + await Vehicle.findByIdAndUpdate(order.vehicle, { status: '空闲' }); + } + res.json({ success: true, data: order }); + } catch (error) { + res.status(500).json({ success: false, message: "服务器内部错误" }); + } +}); + +router.delete('/orders/:id', authMiddleware, async (req, res) => { + try { + const order = await Order.findOneAndDelete({ _id: req.params.id, storeId: req.user.storeId }); + if (!order) { + return res.status(404).json({ success: false, message: '订单不存在' }); + } + res.json({ success: true }); + } catch (error) { + res.status(500).json({ success: false, message: "服务器内部错误" }); + } +}); + +router.get('/vehicles', authMiddleware, async (req, res) => { + try { + const vehicles = await Vehicle.find({ storeId: req.user.storeId }).sort({ createdAt: -1 }); + res.json({ success: true, data: vehicles }); + } catch (error) { + res.status(500).json({ success: false, message: "服务器内部错误" }); + } +}); + +router.post('/vehicles', authMiddleware, async (req, res) => { + try { + const { vehicleId, plateNumber, model, color, batteryType, purchasePrice, notes } = req.body; + if (!vehicleId || !plateNumber || !model) { + return res.status(400).json({ success: false, message: '请填写必填项' }); + } + const existing = await Vehicle.findOne({ $or: [{ vehicleId }, { plateNumber }] }); + if (existing) { + return res.status(400).json({ success: false, message: '车架号或车牌号已存在' }); + } + const vehicle = await Vehicle.create({ + vehicleId, plateNumber, model, color: color || '', batteryType: batteryType || '', + purchasePrice: purchasePrice || 0, notes, status: '空闲', storeId: req.user.storeId + }); + res.json({ success: true, data: vehicle }); + } catch (error) { + res.status(500).json({ success: false, message: "服务器内部错误" }); + } +}); + +router.put('/vehicles/:id', authMiddleware, async (req, res) => { + try { + const { plateNumber, model, color, batteryType, purchasePrice, notes, status } = req.body; + const vehicle = await Vehicle.findOneAndUpdate( + { _id: req.params.id, storeId: req.user.storeId }, + { plateNumber, model, color, batteryType, purchasePrice, notes, status }, + { new: true, runValidators: true } + ); + if (!vehicle) { + return res.status(404).json({ success: false, message: '车辆不存在' }); + } + res.json({ success: true, data: vehicle }); + } catch (error) { + res.status(500).json({ success: false, message: "服务器内部错误" }); + } +}); + +router.delete('/vehicles/:id', authMiddleware, async (req, res) => { + try { + const vehicle = await Vehicle.findOneAndDelete({ _id: req.params.id, storeId: req.user.storeId }); + if (!vehicle) { + return res.status(404).json({ success: false, message: '车辆不存在' }); + } + res.json({ success: true }); + } catch (error) { + res.status(500).json({ success: false, message: "服务器内部错误" }); + } +}); + +router.get('/vehicle-types', authMiddleware, async (req, res) => { + try { + const VehicleType = require('../models/VehicleType'); + const types = await VehicleType.find().sort({ name: 1 }); + res.json({ success: true, data: types }); + } catch (error) { + res.status(500).json({ success: false, message: "服务器内部错误" }); } });