Update: server/middleware/validate.js | 8 +-
server/routes/storeAuth.js | 224 +++++++++++++++++++++++++++++++++++------- 2 files changed, 193 insertions(+), 39 deletions(-)
This commit is contained in:
parent
8d7c63c5cf
commit
0f1e2c759e
|
|
@ -21,7 +21,13 @@ module.exports = { validate };
|
||||||
// ─── 常用校验 Schema ───────────────────────────────────────────
|
// ─── 常用校验 Schema ───────────────────────────────────────────
|
||||||
|
|
||||||
const schemas = {
|
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({
|
login: Joi.object({
|
||||||
phone: Joi.string().pattern(/^1[3-9]\d{9}$/).required(),
|
phone: Joi.string().pattern(/^1[3-9]\d{9}$/).required(),
|
||||||
password: Joi.string().min(6).max(20).required()
|
password: Joi.string().min(6).max(20).required()
|
||||||
|
|
|
||||||
|
|
@ -2,67 +2,215 @@ const express = require('express');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const jwt = require('jsonwebtoken');
|
const jwt = require('jsonwebtoken');
|
||||||
const rateLimit = require('express-rate-limit');
|
const rateLimit = require('express-rate-limit');
|
||||||
const User = require('../models/User');
|
const Store = require('../models/Store');
|
||||||
const UserRole = require('../models/UserRole');
|
const StoreAuth = require('../models/StoreAuth');
|
||||||
const Role = require('../models/Role');
|
const Order = require('../models/Order');
|
||||||
|
const Vehicle = require('../models/Vehicle');
|
||||||
const { comparePassword } = require('../utils/password');
|
const { comparePassword } = require('../utils/password');
|
||||||
|
const { authMiddleware } = require('../middleware/auth');
|
||||||
|
const { validate } = require('../middleware/validate');
|
||||||
|
const { schemas } = require('../middleware/validate');
|
||||||
|
|
||||||
// 登录限流
|
|
||||||
const loginLimiter = rateLimit({
|
const loginLimiter = rateLimit({
|
||||||
windowMs: 15 * 60 * 1000,
|
windowMs: 15 * 60 * 1000,
|
||||||
max: 100,
|
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 {
|
try {
|
||||||
const { username, password } = req.body;
|
const { username, password } = req.body;
|
||||||
const trimmedUsername = username ? username.trim() : '';
|
const trimmedUsername = username ? username.trim() : '';
|
||||||
const trimmedPassword = password ? password.trim() : '';
|
const trimmedPassword = password ? password.trim() : '';
|
||||||
if (!trimmedUsername || !trimmedPassword) {
|
const auth = await StoreAuth.findOne({ username: trimmedUsername }).select('+password');
|
||||||
return res.status(400).json({ success: false, message: '用户名和密码不能为空' });
|
if (!auth) {
|
||||||
}
|
|
||||||
|
|
||||||
// 从 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') {
|
|
||||||
return res.status(401).json({ success: false, message: '用户名或密码错误' });
|
return res.status(401).json({ success: false, message: '用户名或密码错误' });
|
||||||
}
|
}
|
||||||
|
const isMatch = await comparePassword(trimmedPassword, auth.password);
|
||||||
const isMatch = await comparePassword(trimmedPassword, user.password);
|
|
||||||
if (!isMatch) {
|
if (!isMatch) {
|
||||||
return res.status(401).json({ success: false, message: '用户名或密码错误' });
|
return res.status(401).json({ success: false, message: '用户名或密码错误' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = jwt.sign(
|
const token = jwt.sign(
|
||||||
{
|
{ id: auth._id, role: 'store', type: 'store', username: auth.username, storeId: auth.storeId, jti: Math.random().toString(36) },
|
||||||
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)
|
|
||||||
},
|
|
||||||
process.env.JWT_SECRET,
|
process.env.JWT_SECRET,
|
||||||
{ expiresIn: process.env.JWT_EXPIRES_IN || '24h' }
|
{ expiresIn: process.env.JWT_EXPIRES_IN || '7d' }
|
||||||
);
|
);
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
data: {
|
data: { id: auth._id, storeId: auth.storeId, username: auth.username, name: auth.name, status: auth.status, role: auth.role, token }
|
||||||
id: store ? store._id : user._id, // 门店的 MongoDB _id
|
|
||||||
storeId: user.storeId, // 门店编号如 STORE001
|
|
||||||
username: user.username,
|
|
||||||
name: user.name,
|
|
||||||
role: 'store',
|
|
||||||
token
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} 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: "服务器内部错误" });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue