安全增强:JWT密钥重置、RBAC五表权限体系、标准用户角色权限分离、门店数据隔离修复

This commit is contained in:
notyclaw 2026-03-28 18:36:11 +08:00
parent c48e2c2961
commit e61281ec76
35 changed files with 801 additions and 165 deletions

18
package-lock.json generated
View File

@ -209,9 +209,9 @@
} }
}, },
"node_modules/brace-expansion": { "node_modules/brace-expansion": {
"version": "5.0.4", "version": "5.0.5",
"resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-5.0.4.tgz", "resolved": "http://mirrors.tencentyun.com/npm/brace-expansion/-/brace-expansion-5.0.5.tgz",
"integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -1365,15 +1365,15 @@
} }
}, },
"node_modules/path-to-regexp": { "node_modules/path-to-regexp": {
"version": "0.1.12", "version": "0.1.13",
"resolved": "https://registry.npmmirror.com/path-to-regexp/-/path-to-regexp-0.1.12.tgz", "resolved": "http://mirrors.tencentyun.com/npm/path-to-regexp/-/path-to-regexp-0.1.13.tgz",
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/picomatch": { "node_modules/picomatch": {
"version": "2.3.1", "version": "2.3.2",
"resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz", "resolved": "http://mirrors.tencentyun.com/npm/picomatch/-/picomatch-2.3.2.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {

View File

@ -8,6 +8,18 @@ const rateLimit = require('express-rate-limit');
const app = express(); const app = express();
const PORT = process.env.PORT || 3000; const PORT = process.env.PORT || 3000;
// ─── 自定义中间件 ────────────────────────────────────────────
const { authMiddleware } = require('./middleware/auth');
const roleAuth = require('./middleware/roleAuth');
const rbacAuth = require('./middleware/rbacAuth');
// ─── 加载模型(注册到 mongoose───────────────────────────────
require('./models/User');
require('./models/Role');
require('./models/UserRole');
require('./models/Permission');
require('./models/RolePerm');
// ─── 安全中间件 ────────────────────────────────────────────── // ─── 安全中间件 ──────────────────────────────────────────────
// helmet: 安全响应头 // helmet: 安全响应头
@ -50,20 +62,33 @@ mongoose.connect(process.env.MONGODB_URI || 'mongodb://localhost:27017/e-scooter
.then(() => console.log('✅ MongoDB 连接成功')) .then(() => console.log('✅ MongoDB 连接成功'))
.catch(err => console.error('❌ MongoDB 连接失败:', err.message)); .catch(err => console.error('❌ MongoDB 连接失败:', err.message));
// ─── 路由 ─────────────────────────────────────────────────── // ─── 路由(统一加 authMiddleware按角色分级授权 ────────────
app.use('/api/vehicles', require('./routes/vehicles')); //
app.use('/api/orders', require('./routes/orders')); /// 管理后台 APIadmin only
app.use('/api/customers', require('./routes/customers')); app.use('/api/vehicles', rbacAuth('vehicles:read', 'vehicles:write'), require('./routes/vehicles'));
app.use('/api/finance', require('./routes/finance')); app.use('/api/customers', rbacAuth('customers:read', 'customers:write'), require('./routes/customers'));
app.use('/api/stores', require('./routes/stores')); app.use('/api/finance', rbacAuth('finance:read'), require('./routes/finance'));
app.use('/api/complaints', require('./routes/complaints')); app.use('/api/orders', rbacAuth('orders:read', 'orders:write'), require('./routes/orders'));
app.use('/api/approvals', require('./routes/approvals')); app.use('/api/approvals', rbacAuth('approvals:read', 'approvals:write'), require('./routes/approvals'));
app.use('/api/payments', require('./routes/payments')); app.use('/api/disputes', rbacAuth('disputes:read', 'disputes:write'), require('./routes/disputes'));
app.use('/api/conflicts', require('./routes/conflicts')); app.use('/api/conflicts', rbacAuth('disputes:read', 'disputes:write'), require('./routes/conflicts'));
app.use('/api/applications', require('./routes/applications'));
app.use('/api/disputes', require('./routes/disputes')); /// 门店端 APIstore + admin
app.use('/api/stores', rbacAuth('store:read', 'store:write'), require('./routes/stores'));
app.use('/api/complaints', rbacAuth('complaints:read', 'complaints:write'), require('./routes/complaints'));
app.use('/api/payments', rbacAuth('payments:read', 'payments:write'), require('./routes/payments'));
app.use('/api/applications', rbacAuth('applications:read', 'applications:write'), require('./routes/applications'));
// ─── 独立账号体系登录路由(无需鉴权) ─────────────────────────
app.use('/api/admin', require('./routes/adminAuth'));
app.use('/api/store-auth', require('./routes/storeAuth'));
// ─── 新的 RBAC 统一认证登录路由 ─────────────────────────────
app.use('/api/auth', require('./routes/auth'));
/// 骑手端 API
/// 注riders.js 内部对非登录路由独立加 authMiddleware故此处不挂载 roleAuth
app.use('/api/riders', require('./routes/riders')); app.use('/api/riders', require('./routes/riders'));
app.use('/api/vehicle-types', require('./routes/vehicleTypes')); app.use('/api/vehicle-types', rbacAuth('vehicleTypes:read', 'vehicleTypes:write'), require('./routes/vehicleTypes'));
// ─── 健康检查(不限流) ───────────────────────────────────── // ─── 健康检查(不限流) ─────────────────────────────────────
app.get('/health', (req, res) => { app.get('/health', (req, res) => {

30
server/initAdmin.js Normal file
View File

@ -0,0 +1,30 @@
/**
* 初始化默认 admin 账号
* 用法: node server/initAdmin.js
*/
require('dotenv').config();
const mongoose = require('mongoose');
const Admin = require('./models/Admin');
const { hashPassword } = require('./utils/password');
async function init() {
await mongoose.connect(process.env.MONGODB_URI || 'mongodb://localhost:27017/e-scooter-rental');
const existing = await Admin.findOne({ username: 'admin' });
if (existing) {
console.log('⚠️ Admin already exists:', existing.username);
} else {
const hashed = await hashPassword('admin123');
await Admin.create({
username: 'admin',
password: hashed,
name: '系统管理员',
role: 'superadmin'
});
console.log('✅ Admin created: admin / admin123');
}
await mongoose.disconnect();
}
init().catch(err => { console.error(err); process.exit(1); });

114
server/initRBAC.js Normal file
View File

@ -0,0 +1,114 @@
const mongoose = require('mongoose');
const User = require('./models/User');
const Role = require('./models/Role');
const UserRole = require('./models/UserRole');
const Permission = require('./models/Permission');
const RolePerm = require('./models/RolePerm');
const { hashPassword } = require('./utils/password');
async function init() {
await mongoose.connect(process.env.MONGODB_URI || 'mongodb://localhost:27017/e-scooter-rental');
console.log('📦 MongoDB 连接成功,开始初始化 RBAC...');
// 清理旧数据(可选,生产环境请注释掉)
await RolePerm.deleteMany({});
await Permission.deleteMany({});
await UserRole.deleteMany({});
await Role.deleteMany({});
await User.deleteMany({});
// 1. 创建角色
const roles = {
admin: await Role.create({ roleName: 'admin', roleLabel: '管理员', description: '系统管理员' }),
store: await Role.create({ roleName: 'store', roleLabel: '商家', description: '门店管理员' }),
rider: await Role.create({ roleName: 'rider', roleLabel: '骑手', description: '骑手用户' })
};
console.log('✅ 角色创建完成');
// 2. 创建权限
const perms = {
vehiclesRead: await Permission.create({ permName: 'vehicles:read', permLabel: '查看车辆', module: 'vehicles', action: 'read' }),
vehiclesWrite: await Permission.create({ permName: 'vehicles:write', permLabel: '管理车辆', module: 'vehicles', action: 'write' }),
ordersRead: await Permission.create({ permName: 'orders:read', permLabel: '查看订单', module: 'orders', action: 'read' }),
ordersWrite: await Permission.create({ permName: 'orders:write', permLabel: '管理订单', module: 'orders', action: 'write' }),
financeRead: await Permission.create({ permName: 'finance:read', permLabel: '查看财务', module: 'finance', action: 'read' }),
usersRead: await Permission.create({ permName: 'users:read', permLabel: '查看用户', module: 'users', action: 'read' }),
usersWrite: await Permission.create({ permName: 'users:write', permLabel: '管理用户', module: 'users', action: 'write' }),
storeRead: await Permission.create({ permName: 'store:read', permLabel: '查看门店', module: 'store', action: 'read' }),
storeWrite: await Permission.create({ permName: 'store:write', permLabel: '管理门店', module: 'store', action: 'write' }),
customersRead: await Permission.create({ permName: 'customers:read', permLabel: '查看客户', module: 'customers', action: 'read' }),
customersWrite: await Permission.create({ permName: 'customers:write', permLabel: '管理客户', module: 'customers', action: 'write' }),
applicationsRead: await Permission.create({ permName: 'applications:read', permLabel: '查看申请', module: 'applications', action: 'read' }),
applicationsWrite: await Permission.create({ permName: 'applications:write', permLabel: '管理申请', module: 'applications', action: 'write' }),
complaintsRead: await Permission.create({ permName: 'complaints:read', permLabel: '查看投诉', module: 'complaints', action: 'read' }),
complaintsWrite: await Permission.create({ permName: 'complaints:write', permLabel: '管理投诉', module: 'complaints', action: 'write' }),
disputesRead: await Permission.create({ permName: 'disputes:read', permLabel: '查看纠纷', module: 'disputes', action: 'read' }),
disputesWrite: await Permission.create({ permName: 'disputes:write', permLabel: '管理纠纷', module: 'disputes', action: 'write' }),
approvalsRead: await Permission.create({ permName: 'approvals:read', permLabel: '查看审批', module: 'approvals', action: 'read' }),
approvalsWrite: await Permission.create({ permName: 'approvals:write', permLabel: '管理审批', module: 'approvals', action: 'write' }),
paymentsRead: await Permission.create({ permName: 'payments:read', permLabel: '查看支付', module: 'payments', action: 'read' }),
paymentsWrite: await Permission.create({ permName: 'payments:write', permLabel: '管理支付', module: 'payments', action: 'write' }),
vehicleTypesRead: await Permission.create({ permName: 'vehicleTypes:read', permLabel: '查看车型', module: 'vehicleTypes', action: 'read' }),
vehicleTypesWrite: await Permission.create({ permName: 'vehicleTypes:write', permLabel: '管理车型', module: 'vehicleTypes', action: 'write' }),
};
console.log('✅ 权限创建完成');
// 3. 角色-权限关联
// admin: 所有权限
for (const key of Object.keys(perms)) {
await RolePerm.create({ role: roles.admin._id, permission: perms[key]._id });
}
// store: 门店 + 订单 + 车辆 + 客户 + 投诉 + 申请 + 支付 + 车型(部分写权限)
const storePerms = [
'storeRead', 'storeWrite',
'ordersRead', 'ordersWrite',
'vehiclesRead', 'vehiclesWrite',
'customersRead', 'customersWrite',
'complaintsRead', 'complaintsWrite',
'applicationsRead', 'applicationsWrite',
'paymentsRead', 'paymentsWrite',
'disputesRead',
'vehicleTypesRead'
];
for (const key of storePerms) {
await RolePerm.create({ role: roles.store._id, permission: perms[key]._id });
}
// rider: 只读部分
const riderPerms = [
'ordersRead',
'vehiclesRead',
'customersRead',
'vehicleTypesRead'
];
for (const key of riderPerms) {
await RolePerm.create({ role: roles.rider._id, permission: perms[key]._id });
}
console.log('✅ 角色-权限关联完成');
// 4. 创建默认 admin 账号
const hashed = await hashPassword('admin123');
const adminUser = await User.create({
username: 'admin',
password: hashed,
name: '系统管理员',
type: 'admin',
status: 'active'
});
await UserRole.create({ user: adminUser._id, role: roles.admin._id });
console.log('');
console.log('═══════════════════════════════════════');
console.log('✅ RBAC 初始化完成!');
console.log('默认账号: admin / admin123');
console.log('═══════════════════════════════════════');
await mongoose.disconnect();
}
init().catch(err => {
console.error('❌ RBAC 初始化失败:', err);
process.exit(1);
});

42
server/initStore.js Normal file
View File

@ -0,0 +1,42 @@
/**
* 初始化默认 store门店账号
* 用法: node server/initStore.js
* 注意需要先有 Store 记录才能创建门店账号
*/
require('dotenv').config();
const mongoose = require('mongoose');
const Store = require('./models/Store');
const StoreAuth = require('./models/StoreAuth');
const { hashPassword } = require('./utils/password');
async function init() {
await mongoose.connect(process.env.MONGODB_URI || 'mongodb://localhost:27017/e-scooter-rental');
// 查找第一个已审批的门店
const store = await Store.findOne({ approvalStatus: '已通过' });
if (!store) {
console.log('⚠️ 没有找到已审批的门店,请先创建门店记录');
await mongoose.disconnect();
return;
}
const existing = await StoreAuth.findOne({ storeId: store.storeId });
if (existing) {
console.log('⚠️ StoreAuth already exists for store:', store.name, '/ storeId:', store.storeId);
} else {
const hashed = await hashPassword('store123');
await StoreAuth.create({
storeId: store.storeId,
username: store.storeId, // 默认用户名 = 门店编号
password: hashed,
name: store.manager || '门店管理员',
status: 'active'
});
console.log('✅ StoreAuth created for:', store.name);
console.log(' username:', store.storeId, '/ password: store123');
}
await mongoose.disconnect();
}
init().catch(err => { console.error(err); process.exit(1); });

View File

@ -1,5 +1,16 @@
const jwt = require('jsonwebtoken'); const jwt = require('jsonwebtoken');
/**
* JWT Token 黑名单已撤销的 jti 集合
*/
const revokedTokens = new Set();
/**
* 撤销指定 jti token
*/
const revokeToken = (jti) => revokedTokens.add(jti);
module.exports.revokeToken = revokeToken;
/** /**
* JWT 鉴权中间件 * JWT 鉴权中间件
* 验证请求头中的 Bearer token写入 req.user * 验证请求头中的 Bearer token写入 req.user
@ -11,6 +22,10 @@ const authMiddleware = (req, res, next) => {
} }
try { try {
const decoded = jwt.verify(token, process.env.JWT_SECRET); const decoded = jwt.verify(token, process.env.JWT_SECRET);
// 检查 token 是否已撤销
if (decoded.jti && revokedTokens.has(decoded.jti)) {
return res.status(401).json({ success: false, message: 'token已失效' });
}
req.user = decoded; req.user = decoded;
next(); next();
} catch (err) { } catch (err) {

View File

@ -6,8 +6,7 @@ const errorHandler = (err, req, res, next) => {
if (err.name === 'MongoError' || err.name === 'MongooseError') { if (err.name === 'MongoError' || err.name === 'MongooseError') {
return res.status(500).json({ return res.status(500).json({
success: false, success: false,
message: '数据库错误', message: '数据库错误'
error: err.message
}); });
} }
@ -15,8 +14,7 @@ const errorHandler = (err, req, res, next) => {
if (err.name === 'ValidationError') { if (err.name === 'ValidationError') {
return res.status(400).json({ return res.status(400).json({
success: false, success: false,
message: '数据验证失败', message: '数据验证失败'
error: err.message
}); });
} }
@ -24,16 +22,14 @@ const errorHandler = (err, req, res, next) => {
if (err.name === 'CastError') { if (err.name === 'CastError') {
return res.status(404).json({ return res.status(404).json({
success: false, success: false,
message: '数据不存在', message: '数据不存在'
error: err.message
}); });
} }
// 默认错误 // 默认错误
res.status(err.status || 500).json({ res.status(err.status || 500).json({
success: false, success: false,
message: err.message || '服务器内部错误', message: '服务器内部错误'
error: process.env.NODE_ENV === 'development' ? err.stack : undefined
}); });
}; };

View File

@ -0,0 +1,39 @@
const jwt = require('jsonwebtoken');
const revokedTokens = new Set();
const revokeToken = (jti) => revokedTokens.add(jti);
module.exports.revokeToken = revokeToken;
const rbacAuth = (...requiredPerms) => {
return (req, res, next) => {
const token = req.headers.authorization?.replace('Bearer ', '');
if (!token) {
return res.status(401).json({ success: false, message: '未登录' });
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
if (decoded.jti && revokedTokens.has(decoded.jti)) {
return res.status(401).json({ success: false, message: 'token已失效' });
}
req.user = decoded;
if (requiredPerms.length > 0) {
const userPerms = decoded.permissions || [];
const hasPerm = requiredPerms.some(perm => userPerms.includes(perm));
if (!hasPerm) {
return res.status(403).json({ success: false, message: '权限不足' });
}
}
next();
} catch (err) {
return res.status(401).json({ success: false, message: 'token无效或已过期' });
}
};
};
module.exports = rbacAuth;

View File

@ -0,0 +1,23 @@
/**
* 角色权限分级中间件RBAC
* 用法: roleAuth('admin'), roleAuth('admin', 'store', 'rider')
*
* 角色说明:
* admin - 管理后台最高权限可访问所有 API
* store - 门店管理员可访问门店相关数据和业务操作
* rider - 骑手只能访问自己的数据和公开浏览接口
*/
const roleAuth = (...allowedRoles) => {
return (req, res, next) => {
if (!req.user) {
return res.status(401).json({ success: false, message: '未登录' });
}
const { role } = req.user;
if (!allowedRoles.includes(role)) {
return res.status(403).json({ success: false, message: '无权访问' });
}
next();
};
};
module.exports = roleAuth;

11
server/models/Admin.js Normal file
View File

@ -0,0 +1,11 @@
const mongoose = require('mongoose');
const AdminSchema = new mongoose.Schema({
username: { type: String, required: true, unique: true }, // 登录名,如 "admin"
password: { type: String, required: true, select: false }, // bcrypt 加密
name: { type: String, required: true },
role: { type: String, enum: ['admin', 'superadmin'], default: 'admin' },
status: { type: String, enum: ['active', 'disabled'], default: 'active' }
}, { timestamps: true });
module.exports = mongoose.model('Admin', AdminSchema);

View File

@ -32,6 +32,9 @@ const orderSchema = new mongoose.Schema({
paymentMethod: { type: String, enum: ['微信', '支付宝', '现金', '银行卡'] }, paymentMethod: { type: String, enum: ['微信', '支付宝', '现金', '银行卡'] },
paymentDate: { type: Date }, paymentDate: { type: Date },
// 门店关联
storeId: { type: String, index: true }, // 所属门店
// 合同信息 // 合同信息
contractUrl: { type: String }, // 合同文件路径 contractUrl: { type: String }, // 合同文件路径
contractSigned: { type: Boolean, default: false }, // 合同是否签署 contractSigned: { type: Boolean, default: false }, // 合同是否签署

View File

@ -0,0 +1,15 @@
const mongoose = require('mongoose');
const PermissionSchema = new mongoose.Schema({
permName: { type: String, required: true, unique: true },
permLabel: { type: String, required: true },
module: { type: String, required: true },
action: { type: String, required: true }
}, { timestamps: true });
// 索引
PermissionSchema.index({ permName: 1 }, { unique: true });
PermissionSchema.index({ module: 1 });
PermissionSchema.index({ action: 1 });
module.exports = mongoose.model('Permission', PermissionSchema);

View File

@ -9,8 +9,8 @@ const riderSchema = new mongoose.Schema({
// 密码bcrypt 哈希存储,查询时默认不返回) // 密码bcrypt 哈希存储,查询时默认不返回)
password: { type: String, required: true, select: false }, password: { type: String, required: true, select: false },
// 角色customer=普通用户, admin=管理员, store=门店管理员 // 角色customer=普通用户, admin=管理员, store=门店管理员, rider=骑手
role: { type: String, enum: ['customer', 'admin', 'store'], default: 'customer' }, role: { type: String, enum: ['customer', 'admin', 'store', 'rider'], default: 'customer' },
// 接单状态 // 接单状态
status: { status: {

9
server/models/Role.js Normal file
View File

@ -0,0 +1,9 @@
const mongoose = require('mongoose');
const RoleSchema = new mongoose.Schema({
roleName: { type: String, required: true, unique: true },
roleLabel: { type: String, required: true },
description: { type: String }
}, { timestamps: true });
module.exports = mongoose.model('Role', RoleSchema);

13
server/models/RolePerm.js Normal file
View File

@ -0,0 +1,13 @@
const mongoose = require('mongoose');
const RolePermSchema = new mongoose.Schema({
role: { type: mongoose.Schema.Types.ObjectId, ref: 'Role', required: true },
permission: { type: mongoose.Schema.Types.ObjectId, ref: 'Permission', required: true }
}, { timestamps: true });
// 索引
RolePermSchema.index({ role: 1, permission: 1 }, { unique: true });
RolePermSchema.index({ role: 1 });
RolePermSchema.index({ permission: 1 });
module.exports = mongoose.model('RolePerm', RolePermSchema);

View File

@ -0,0 +1,11 @@
const mongoose = require('mongoose');
const StoreAuthSchema = new mongoose.Schema({
storeId: { type: String, required: true, unique: true }, // 关联的门店编号
username: { type: String, required: true, unique: true }, // 门店登录用户名
password: { type: String, required: true, select: false }, // bcrypt 加密
name: { type: String, required: true }, // 门店负责人姓名
status: { type: String, enum: ['active', 'disabled'], default: 'active' }
}, { timestamps: true });
module.exports = mongoose.model('StoreAuth', StoreAuthSchema);

19
server/models/User.js Normal file
View File

@ -0,0 +1,19 @@
const mongoose = require('mongoose');
const UserSchema = new mongoose.Schema({
username: { type: String, required: true, unique: true },
password: { type: String, required: true, select: false },
phone: { type: String },
name: { type: String, required: true },
status: { type: String, enum: ['active', 'disabled'], default: 'active' },
type: { type: String, enum: ['admin', 'store', 'rider'], required: true },
storeId: { type: String } // store 类型账号关联的门店ID
}, { timestamps: true });
// 索引
UserSchema.index({ username: 1 }, { unique: true });
UserSchema.index({ phone: 1 });
UserSchema.index({ type: 1 });
UserSchema.index({ status: 1 });
module.exports = mongoose.model('User', UserSchema);

10
server/models/UserRole.js Normal file
View File

@ -0,0 +1,10 @@
const mongoose = require('mongoose');
const UserRoleSchema = new mongoose.Schema({
user: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
role: { type: mongoose.Schema.Types.ObjectId, ref: 'Role', required: true }
}, { timestamps: true });
UserRoleSchema.index({ user: 1, role: 1 }, { unique: true });
module.exports = mongoose.model('UserRole', UserRoleSchema);

View File

@ -0,0 +1,47 @@
const express = require('express');
const router = express.Router();
const jwt = require('jsonwebtoken');
const rateLimit = require('express-rate-limit');
const Admin = require('../models/Admin');
const { comparePassword } = require('../utils/password');
// 登录限流
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 10,
message: { success: false, message: '登录尝试过于频繁' }
});
router.post('/login', loginLimiter, async (req, res) => {
try {
const { username, password } = req.body;
if (!username || !password) {
return res.status(400).json({ success: false, message: '用户名和密码不能为空' });
}
const admin = await Admin.findOne({ username }).select('+password');
if (!admin || admin.status !== 'active') {
return res.status(401).json({ success: false, message: '用户名或密码错误' });
}
const isMatch = await comparePassword(password, admin.password);
if (!isMatch) {
return res.status(401).json({ success: false, message: '用户名或密码错误' });
}
const token = jwt.sign(
{ id: admin._id, role: admin.role, type: 'admin', jti: Math.random().toString(36) },
process.env.JWT_SECRET,
{ expiresIn: process.env.JWT_EXPIRES_IN || '24h' }
);
res.json({
success: true,
data: { id: admin._id, username: admin.username, name: admin.name, role: admin.role, token }
});
} catch (error) {
res.status(500).json({ success: false, message: '服务器内部错误' });
}
});
module.exports = router;

View File

@ -1,41 +1,46 @@
const express = require('express'); const express = require('express');
const router = express.Router(); const router = express.Router();
const Application = require('../models/Application'); const Application = require('../models/Application');
const { authMiddleware, requireRole } = require('../middleware/auth');
router.get('/', async (req, res) => { // 获取所有申请admin 或 store
router.get('/', authMiddleware, requireRole('admin', 'store'), async (req, res) => {
try { try {
const apps = await Application.find().populate('store'); const apps = await Application.find().populate('store');
res.json({ success: true, data: apps }); res.json({ success: true, data: apps });
} catch (error) { } catch (error) {
res.status(500).json({ success: false, message: error.message }); res.status(500).json({ success: false, message: '服务器内部错误' });
} }
}); });
router.post('/', async (req, res) => { // 创建申请admin 或 store
router.post('/', authMiddleware, requireRole('admin', 'store'), async (req, res) => {
try { try {
const app = new Application(req.body); const app = new Application(req.body);
await app.save(); await app.save();
res.json({ success: true, data: app }); res.json({ success: true, data: app });
} catch (error) { } catch (error) {
res.status(400).json({ success: false, message: error.message }); res.status(400).json({ success: false, message: '服务器内部错误' });
} }
}); });
router.put('/:id', async (req, res) => { // 更新申请admin 或 store
router.put('/:id', authMiddleware, requireRole('admin', 'store'), async (req, res) => {
try { try {
const app = await Application.findByIdAndUpdate(req.params.id, req.body, { new: true }); const app = await Application.findByIdAndUpdate(req.params.id, req.body, { new: true });
res.json({ success: true, data: app }); res.json({ success: true, data: app });
} catch (error) { } catch (error) {
res.status(400).json({ success: false, message: error.message }); res.status(400).json({ success: false, message: '服务器内部错误' });
} }
}); });
router.delete('/:id', async (req, res) => { // 删除申请admin 或 store
router.delete('/:id', authMiddleware, requireRole('admin', 'store'), async (req, res) => {
try { try {
await Application.findByIdAndDelete(req.params.id); await Application.findByIdAndDelete(req.params.id);
res.json({ success: true }); res.json({ success: true });
} catch (error) { } catch (error) {
res.status(400).json({ success: false, message: error.message }); res.status(400).json({ success: false, message: '服务器内部错误' });
} }
}); });

View File

@ -1,45 +1,46 @@
const express = require('express'); const express = require('express');
const router = express.Router(); const router = express.Router();
const Approval = require('../models/Approval'); const Approval = require('../models/Approval');
const { authMiddleware, requireRole } = require('../middleware/auth');
// 获取所有审批 // 获取所有审批(仅 admin
router.get('/', async (req, res) => { router.get('/', authMiddleware, requireRole('admin'), async (req, res) => {
try { try {
const approvals = await Approval.find(); const approvals = await Approval.find();
res.json({ success: true, data: approvals }); res.json({ success: true, data: approvals });
} catch (error) { } catch (error) {
res.status(500).json({ success: false, message: error.message }); res.status(500).json({ success: false, message: '服务器内部错误' });
} }
}); });
// 创建审批 // 创建审批(仅 admin
router.post('/', async (req, res) => { router.post('/', authMiddleware, requireRole('admin'), async (req, res) => {
try { try {
const approval = new Approval(req.body); const approval = new Approval(req.body);
await approval.save(); await approval.save();
res.json({ success: true, data: approval }); res.json({ success: true, data: approval });
} catch (error) { } catch (error) {
res.status(400).json({ success: false, message: error.message }); res.status(400).json({ success: false, message: '服务器内部错误' });
} }
}); });
// 更新审批 // 更新审批(仅 admin
router.put('/:id', async (req, res) => { router.put('/:id', authMiddleware, requireRole('admin'), async (req, res) => {
try { try {
const approval = await Approval.findByIdAndUpdate(req.params.id, req.body, { new: true }); const approval = await Approval.findByIdAndUpdate(req.params.id, req.body, { new: true });
res.json({ success: true, data: approval }); res.json({ success: true, data: approval });
} catch (error) { } catch (error) {
res.status(400).json({ success: false, message: error.message }); res.status(400).json({ success: false, message: '服务器内部错误' });
} }
}); });
// 删除审批 // 删除审批(仅 admin
router.delete('/:id', async (req, res) => { router.delete('/:id', authMiddleware, requireRole('admin'), async (req, res) => {
try { try {
await Approval.findByIdAndDelete(req.params.id); await Approval.findByIdAndDelete(req.params.id);
res.json({ success: true }); res.json({ success: true });
} catch (error) { } catch (error) {
res.status(400).json({ success: false, message: error.message }); res.status(400).json({ success: false, message: '服务器内部错误' });
} }
}); });

74
server/routes/auth.js Normal file
View File

@ -0,0 +1,74 @@
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 RolePerm = require('../models/RolePerm');
const { comparePassword } = require('../utils/password');
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 10,
message: { success: false, message: '登录尝试过于频繁' }
});
router.post('/login', loginLimiter, async (req, res) => {
try {
const { username, password } = req.body;
if (!username || !password) {
return res.status(400).json({ success: false, message: '用户名和密码不能为空' });
}
const user = await User.findOne({ username }).select('+password');
if (!user || user.status !== 'active') {
return res.status(401).json({ success: false, message: '用户名或密码错误' });
}
const isMatch = await comparePassword(password, user.password);
if (!isMatch) {
return res.status(401).json({ success: false, message: '用户名或密码错误' });
}
const userRole = await UserRole.findOne({ user: user._id }).populate('role');
if (!userRole) {
return res.status(403).json({ success: false, message: '该用户未分配角色' });
}
const rolePerms = await RolePerm.find({ role: userRole.role._id }).populate('permission');
const permissions = rolePerms.map(rp => rp.permission.permName);
const token = jwt.sign(
{
id: user._id,
username: user.username,
type: user.type,
role: userRole.role.roleName,
permissions,
jti: Math.random().toString(36)
},
process.env.JWT_SECRET,
{ expiresIn: process.env.JWT_EXPIRES_IN || '24h' }
);
res.json({
success: true,
data: {
id: user._id,
username: user.username,
name: user.name,
type: user.type,
role: userRole.role.roleName,
roleLabel: userRole.role.roleLabel,
permissions,
token
}
});
} catch (error) {
console.error('Login error:', error);
res.status(500).json({ success: false, message: '服务器内部错误' });
}
});
module.exports = router;

View File

@ -1,45 +1,46 @@
const express = require('express'); const express = require('express');
const router = express.Router(); const router = express.Router();
const Complaint = require('../models/Complaint'); const Complaint = require('../models/Complaint');
const { authMiddleware, requireRole } = require('../middleware/auth');
// 获取所有投诉 // 获取所有投诉admin 或 store
router.get('/', async (req, res) => { router.get('/', authMiddleware, requireRole('admin', 'store'), async (req, res) => {
try { try {
const complaints = await Complaint.find().populate('customer order'); const complaints = await Complaint.find().populate('customer order');
res.json({ success: true, data: complaints }); res.json({ success: true, data: complaints });
} catch (error) { } catch (error) {
res.status(500).json({ success: false, message: error.message }); res.status(500).json({ success: false, message: '服务器内部错误' });
} }
}); });
// 创建投诉 // 创建投诉admin 或 store
router.post('/', async (req, res) => { router.post('/', authMiddleware, requireRole('admin', 'store'), async (req, res) => {
try { try {
const complaint = new Complaint(req.body); const complaint = new Complaint(req.body);
await complaint.save(); await complaint.save();
res.json({ success: true, data: complaint }); res.json({ success: true, data: complaint });
} catch (error) { } catch (error) {
res.status(400).json({ success: false, message: error.message }); res.status(400).json({ success: false, message: '服务器内部错误' });
} }
}); });
// 更新投诉 // 更新投诉admin 或 store
router.put('/:id', async (req, res) => { router.put('/:id', authMiddleware, requireRole('admin', 'store'), async (req, res) => {
try { try {
const complaint = await Complaint.findByIdAndUpdate(req.params.id, req.body, { new: true }); const complaint = await Complaint.findByIdAndUpdate(req.params.id, req.body, { new: true });
res.json({ success: true, data: complaint }); res.json({ success: true, data: complaint });
} catch (error) { } catch (error) {
res.status(400).json({ success: false, message: error.message }); res.status(400).json({ success: false, message: '服务器内部错误' });
} }
}); });
// 删除投诉 // 删除投诉admin 或 store
router.delete('/:id', async (req, res) => { router.delete('/:id', authMiddleware, requireRole('admin', 'store'), async (req, res) => {
try { try {
await Complaint.findByIdAndDelete(req.params.id); await Complaint.findByIdAndDelete(req.params.id);
res.json({ success: true }); res.json({ success: true });
} catch (error) { } catch (error) {
res.status(400).json({ success: false, message: error.message }); res.status(400).json({ success: false, message: '服务器内部错误' });
} }
}); });

View File

@ -1,45 +1,46 @@
const express = require('express'); const express = require('express');
const router = express.Router(); const router = express.Router();
const Conflict = require('../models/Conflict'); const Conflict = require('../models/Conflict');
const { authMiddleware, requireRole } = require('../middleware/auth');
// 获取所有矛盾记录 // 获取所有矛盾记录admin 或 store
router.get('/', async (req, res) => { router.get('/', authMiddleware, requireRole('admin', 'store'), async (req, res) => {
try { try {
const conflicts = await Conflict.find(); const conflicts = await Conflict.find();
res.json({ success: true, data: conflicts }); res.json({ success: true, data: conflicts });
} catch (error) { } catch (error) {
res.status(500).json({ success: false, message: error.message }); res.status(500).json({ success: false, message: '服务器内部错误' });
} }
}); });
// 创建矛盾记录 // 创建矛盾记录admin 或 store
router.post('/', async (req, res) => { router.post('/', authMiddleware, requireRole('admin', 'store'), async (req, res) => {
try { try {
const conflict = new Conflict(req.body); const conflict = new Conflict(req.body);
await conflict.save(); await conflict.save();
res.json({ success: true, data: conflict }); res.json({ success: true, data: conflict });
} catch (error) { } catch (error) {
res.status(400).json({ success: false, message: error.message }); res.status(400).json({ success: false, message: '服务器内部错误' });
} }
}); });
// 更新矛盾记录 // 更新矛盾记录admin 或 store
router.put('/:id', async (req, res) => { router.put('/:id', authMiddleware, requireRole('admin', 'store'), async (req, res) => {
try { try {
const conflict = await Conflict.findByIdAndUpdate(req.params.id, req.body, { new: true }); const conflict = await Conflict.findByIdAndUpdate(req.params.id, req.body, { new: true });
res.json({ success: true, data: conflict }); res.json({ success: true, data: conflict });
} catch (error) { } catch (error) {
res.status(400).json({ success: false, message: error.message }); res.status(400).json({ success: false, message: '服务器内部错误' });
} }
}); });
// 删除矛盾记录 // 删除矛盾记录admin 或 store
router.delete('/:id', async (req, res) => { router.delete('/:id', authMiddleware, requireRole('admin', 'store'), async (req, res) => {
try { try {
await Conflict.findByIdAndDelete(req.params.id); await Conflict.findByIdAndDelete(req.params.id);
res.json({ success: true }); res.json({ success: true });
} catch (error) { } catch (error) {
res.status(400).json({ success: false, message: error.message }); res.status(400).json({ success: false, message: '服务器内部错误' });
} }
}); });

View File

@ -1,6 +1,7 @@
const express = require('express'); const express = require('express');
const router = express.Router(); const router = express.Router();
const Customer = require('../models/Customer'); const Customer = require('../models/Customer');
const Store = require('../models/Store');
const { authMiddleware, requireRole } = require('../middleware/auth'); const { authMiddleware, requireRole } = require('../middleware/auth');
const { validate } = require('../middleware/validate'); const { validate } = require('../middleware/validate');
const { schemas } = require('../middleware/validate'); const { schemas } = require('../middleware/validate');
@ -11,7 +12,7 @@ router.get('/', authMiddleware, requireRole('admin', 'store'), async (req, res)
const customers = await Customer.find(); const customers = await Customer.find();
res.json({ success: true, data: customers }); res.json({ success: true, data: customers });
} catch (error) { } catch (error) {
res.status(500).json({ success: false, message: error.message }); res.status(500).json({ success: false, message: "服务器内部错误" });
} }
}); });
@ -22,7 +23,7 @@ router.get('/:id', authMiddleware, requireRole('admin', 'store'), async (req, re
if (!customer) return res.status(404).json({ success: false, message: '客户不存在' }); if (!customer) return res.status(404).json({ success: false, message: '客户不存在' });
res.json({ success: true, data: customer }); res.json({ success: true, data: customer });
} catch (error) { } catch (error) {
res.status(500).json({ success: false, message: error.message }); res.status(500).json({ success: false, message: "服务器内部错误" });
} }
}); });
@ -33,7 +34,7 @@ router.post('/', authMiddleware, requireRole('admin', 'store'), validate(schemas
await customer.save(); await customer.save();
res.status(201).json({ success: true, data: customer }); res.status(201).json({ success: true, data: customer });
} catch (error) { } catch (error) {
res.status(400).json({ success: false, message: error.message }); res.status(400).json({ success: false, message: "服务器内部错误" });
} }
}); });
@ -44,7 +45,7 @@ router.put('/:id', authMiddleware, requireRole('admin', 'store'), async (req, re
if (!customer) return res.status(404).json({ success: false, message: '客户不存在' }); if (!customer) return res.status(404).json({ success: false, message: '客户不存在' });
res.json({ success: true, data: customer }); res.json({ success: true, data: customer });
} catch (error) { } catch (error) {
res.status(400).json({ success: false, message: error.message }); res.status(400).json({ success: false, message: "服务器内部错误" });
} }
}); });
@ -55,7 +56,7 @@ router.delete('/:id', authMiddleware, requireRole('admin'), async (req, res) =>
if (!customer) return res.status(404).json({ success: false, message: '客户不存在' }); if (!customer) return res.status(404).json({ success: false, message: '客户不存在' });
res.json({ success: true, message: '客户已删除' }); res.json({ success: true, message: '客户已删除' });
} catch (error) { } catch (error) {
res.status(500).json({ success: false, message: error.message }); res.status(500).json({ success: false, message: "服务器内部错误" });
} }
}); });
@ -72,7 +73,7 @@ router.get('/search/:keyword', authMiddleware, requireRole('admin', 'store'), as
}); });
res.json({ success: true, data: customers }); res.json({ success: true, data: customers });
} catch (error) { } catch (error) {
res.status(500).json({ success: false, message: error.message }); res.status(500).json({ success: false, message: "服务器内部错误" });
} }
}); });
@ -93,7 +94,7 @@ router.patch('/:id/credit', authMiddleware, requireRole('admin', 'store'), async
if (!customer) return res.status(404).json({ success: false, message: '客户不存在' }); if (!customer) return res.status(404).json({ success: false, message: '客户不存在' });
res.json({ success: true, data: customer }); res.json({ success: true, data: customer });
} catch (error) { } catch (error) {
res.status(400).json({ success: false, message: error.message }); res.status(400).json({ success: false, message: "服务器内部错误" });
} }
}); });

View File

@ -1,41 +1,46 @@
const express = require('express'); const express = require('express');
const router = express.Router(); const router = express.Router();
const Dispute = require('../models/Dispute'); const Dispute = require('../models/Dispute');
const { authMiddleware, requireRole } = require('../middleware/auth');
router.get('/', async (req, res) => { // 获取所有纠纷admin 或 store
router.get('/', authMiddleware, requireRole('admin', 'store'), async (req, res) => {
try { try {
const disputes = await Dispute.find(); const disputes = await Dispute.find();
res.json({ success: true, data: disputes }); res.json({ success: true, data: disputes });
} catch (error) { } catch (error) {
res.status(500).json({ success: false, message: error.message }); res.status(500).json({ success: false, message: '服务器内部错误' });
} }
}); });
router.post('/', async (req, res) => { // 创建纠纷admin 或 store
router.post('/', authMiddleware, requireRole('admin', 'store'), async (req, res) => {
try { try {
const dispute = new Dispute(req.body); const dispute = new Dispute(req.body);
await dispute.save(); await dispute.save();
res.json({ success: true, data: dispute }); res.json({ success: true, data: dispute });
} catch (error) { } catch (error) {
res.status(400).json({ success: false, message: error.message }); res.status(400).json({ success: false, message: '服务器内部错误' });
} }
}); });
router.put('/:id', async (req, res) => { // 更新纠纷admin 或 store
router.put('/:id', authMiddleware, requireRole('admin', 'store'), async (req, res) => {
try { try {
const dispute = await Dispute.findByIdAndUpdate(req.params.id, req.body, { new: true }); const dispute = await Dispute.findByIdAndUpdate(req.params.id, req.body, { new: true });
res.json({ success: true, data: dispute }); res.json({ success: true, data: dispute });
} catch (error) { } catch (error) {
res.status(400).json({ success: false, message: error.message }); res.status(400).json({ success: false, message: '服务器内部错误' });
} }
}); });
router.delete('/:id', async (req, res) => { // 删除纠纷admin 或 store
router.delete('/:id', authMiddleware, requireRole('admin', 'store'), async (req, res) => {
try { try {
await Dispute.findByIdAndDelete(req.params.id); await Dispute.findByIdAndDelete(req.params.id);
res.json({ success: true }); res.json({ success: true });
} catch (error) { } catch (error) {
res.status(400).json({ success: false, message: error.message }); res.status(400).json({ success: false, message: '服务器内部错误' });
} }
}); });

View File

@ -48,7 +48,7 @@ router.get('/', authMiddleware, requireRole('admin'), async (req, res) => {
} }
}); });
} catch (error) { } catch (error) {
res.status(500).json({ success: false, message: error.message }); res.status(500).json({ success: false, message: "服务器内部错误" });
} }
}); });
@ -59,7 +59,7 @@ router.post('/', authMiddleware, requireRole('admin'), async (req, res) => {
await payment.save(); await payment.save();
res.json({ success: true, data: payment }); res.json({ success: true, data: payment });
} catch (error) { } catch (error) {
res.status(400).json({ success: false, message: error.message }); res.status(400).json({ success: false, message: "服务器内部错误" });
} }
}); });
@ -69,7 +69,7 @@ router.put('/:id', authMiddleware, requireRole('admin'), async (req, res) => {
const payment = await Payment.findByIdAndUpdate(req.params.id, req.body, { new: true }); const payment = await Payment.findByIdAndUpdate(req.params.id, req.body, { new: true });
res.json({ success: true, data: payment }); res.json({ success: true, data: payment });
} catch (error) { } catch (error) {
res.status(400).json({ success: false, message: error.message }); res.status(400).json({ success: false, message: "服务器内部错误" });
} }
}); });
@ -79,7 +79,7 @@ router.delete('/:id', authMiddleware, requireRole('admin'), async (req, res) =>
await Payment.findByIdAndDelete(req.params.id); await Payment.findByIdAndDelete(req.params.id);
res.json({ success: true }); res.json({ success: true });
} catch (error) { } catch (error) {
res.status(400).json({ success: false, message: error.message }); res.status(400).json({ success: false, message: "服务器内部错误" });
} }
}); });
@ -89,7 +89,7 @@ router.post('/delete-all', authMiddleware, requireRole('admin'), async (req, res
await Payment.deleteMany({}); await Payment.deleteMany({});
res.json({ success: true, message: 'All payments deleted' }); res.json({ success: true, message: 'All payments deleted' });
} catch (error) { } catch (error) {
res.status(400).json({ success: false, message: error.message }); res.status(400).json({ success: false, message: "服务器内部错误" });
} }
}); });
@ -142,7 +142,7 @@ router.get('/stats', authMiddleware, requireRole('admin'), async (req, res) => {
} }
}); });
} catch (error) { } catch (error) {
res.status(500).json({ success: false, message: error.message }); res.status(500).json({ success: false, message: "服务器内部错误" });
} }
}); });

View File

@ -3,6 +3,7 @@ const router = express.Router();
const Order = require('../models/Order'); const Order = require('../models/Order');
const Vehicle = require('../models/Vehicle'); const Vehicle = require('../models/Vehicle');
const Customer = require('../models/Customer'); const Customer = require('../models/Customer');
const Store = require('../models/Store');
const { authMiddleware, requireRole } = require('../middleware/auth'); const { authMiddleware, requireRole } = require('../middleware/auth');
const { validate } = require('../middleware/validate'); const { validate } = require('../middleware/validate');
const { schemas } = require('../middleware/validate'); const { schemas } = require('../middleware/validate');
@ -18,15 +19,17 @@ router.get('/', authMiddleware, async (req, res) => {
// 此处通过 rider id 查找(假设订单中 rider 字段关联骑手) // 此处通过 rider id 查找(假设订单中 rider 字段关联骑手)
filter.rider = req.user.id; filter.rider = req.user.id;
} else if (req.user.role === 'store') { } else if (req.user.role === 'store') {
// store 角色可按门店过滤(前端传 storeId // store 角色永远只能看自己门店的数据,忽略前端传的 storeId 参数
if (req.query.storeId) filter.storeId = req.query.storeId; if (req.user.storeId) {
filter.storeId = req.user.storeId;
}
} }
const orders = await Order.find(filter) const orders = await Order.find(filter)
.populate('customer', 'name phone') .populate('customer', 'name phone')
.populate('vehicle', 'model vehicleId'); .populate('vehicle', 'model vehicleId');
res.json({ success: true, data: orders }); res.json({ success: true, data: orders });
} catch (error) { } catch (error) {
res.status(500).json({ success: false, message: error.message }); res.status(500).json({ success: false, message: '服务器内部错误' });
} }
}); });
@ -45,7 +48,7 @@ router.get('/:id', authMiddleware, async (req, res) => {
} }
res.json({ success: true, data: order }); res.json({ success: true, data: order });
} catch (error) { } catch (error) {
res.status(500).json({ success: false, message: error.message }); res.status(500).json({ success: false, message: "服务器内部错误" });
} }
}); });
@ -122,7 +125,7 @@ router.post('/', authMiddleware, requireRole('admin', 'store'), validate(schemas
res.status(201).json({ success: true, data: order }); res.status(201).json({ success: true, data: order });
} catch (error) { } catch (error) {
res.status(400).json({ success: false, message: error.message }); res.status(400).json({ success: false, message: "服务器内部错误" });
} }
}); });
@ -133,7 +136,7 @@ router.put('/:id', authMiddleware, requireRole('admin', 'store'), async (req, re
if (!order) return res.status(404).json({ success: false, message: '订单不存在' }); if (!order) return res.status(404).json({ success: false, message: '订单不存在' });
res.json({ success: true, data: order }); res.json({ success: true, data: order });
} catch (error) { } catch (error) {
res.status(400).json({ success: false, message: error.message }); res.status(400).json({ success: false, message: "服务器内部错误" });
} }
}); });
@ -164,7 +167,7 @@ router.patch('/:id/complete', authMiddleware, requireRole('admin', 'store'), asy
res.json({ success: true, data: order }); res.json({ success: true, data: order });
} catch (error) { } catch (error) {
res.status(500).json({ success: false, message: error.message }); res.status(500).json({ success: false, message: "服务器内部错误" });
} }
}); });
@ -197,7 +200,7 @@ router.patch('/:id/cancel', authMiddleware, requireRole('admin', 'store'), async
res.json({ success: true, data: order }); res.json({ success: true, data: order });
} catch (error) { } catch (error) {
res.status(500).json({ success: false, message: error.message }); res.status(500).json({ success: false, message: "服务器内部错误" });
} }
}); });
@ -221,7 +224,7 @@ router.get('/status/overdue', authMiddleware, requireRole('admin', 'store'), asy
} }
res.json({ success: true, data: orders }); res.json({ success: true, data: orders });
} catch (error) { } catch (error) {
res.status(500).json({ success: false, message: error.message }); res.status(500).json({ success: false, message: "服务器内部错误" });
} }
}); });
@ -233,7 +236,7 @@ router.get('/status/:status', authMiddleware, requireRole('admin', 'store'), asy
.populate('vehicle', 'model vehicleId'); .populate('vehicle', 'model vehicleId');
res.json({ success: true, data: orders }); res.json({ success: true, data: orders });
} catch (error) { } catch (error) {
res.status(500).json({ success: false, message: error.message }); res.status(500).json({ success: false, message: "服务器内部错误" });
} }
}); });

View File

@ -1,45 +1,46 @@
const express = require('express'); const express = require('express');
const router = express.Router(); const router = express.Router();
const Payment = require('../models/Payment'); const Payment = require('../models/Payment');
const { authMiddleware, requireRole } = require('../middleware/auth');
// 获取所有打款记录 // 获取所有打款记录(需 admin 或 store
router.get('/', async (req, res) => { router.get('/', authMiddleware, requireRole('admin', 'store'), async (req, res) => {
try { try {
const payments = await Payment.find(); const payments = await Payment.find();
res.json({ success: true, data: payments }); res.json({ success: true, data: payments });
} catch (error) { } catch (error) {
res.status(500).json({ success: false, message: error.message }); res.status(500).json({ success: false, message: '服务器内部错误' });
} }
}); });
// 创建打款记录 // 创建打款记录(需 admin 或 store
router.post('/', async (req, res) => { router.post('/', authMiddleware, requireRole('admin', 'store'), async (req, res) => {
try { try {
const payment = new Payment(req.body); const payment = new Payment(req.body);
await payment.save(); await payment.save();
res.json({ success: true, data: payment }); res.json({ success: true, data: payment });
} catch (error) { } catch (error) {
res.status(400).json({ success: false, message: error.message }); res.status(400).json({ success: false, message: '服务器内部错误' });
} }
}); });
// 更新打款记录 // 更新打款记录(需 admin 或 store
router.put('/:id', async (req, res) => { router.put('/:id', authMiddleware, requireRole('admin', 'store'), async (req, res) => {
try { try {
const payment = await Payment.findByIdAndUpdate(req.params.id, req.body, { new: true }); const payment = await Payment.findByIdAndUpdate(req.params.id, req.body, { new: true });
res.json({ success: true, data: payment }); res.json({ success: true, data: payment });
} catch (error) { } catch (error) {
res.status(400).json({ success: false, message: error.message }); res.status(400).json({ success: false, message: '服务器内部错误' });
} }
}); });
// 删除打款记录 // 删除打款记录(需 admin 或 store
router.delete('/:id', async (req, res) => { router.delete('/:id', authMiddleware, requireRole('admin', 'store'), async (req, res) => {
try { try {
await Payment.findByIdAndDelete(req.params.id); await Payment.findByIdAndDelete(req.params.id);
res.json({ success: true }); res.json({ success: true });
} catch (error) { } catch (error) {
res.status(400).json({ success: false, message: error.message }); res.status(400).json({ success: false, message: '服务器内部错误' });
} }
}); });

View File

@ -5,6 +5,7 @@ const rateLimit = require('express-rate-limit');
const Rider = require('../models/Rider'); const Rider = require('../models/Rider');
const Order = require('../models/Order'); const Order = require('../models/Order');
const { comparePassword } = require('../utils/password'); const { comparePassword } = require('../utils/password');
const { authMiddleware, requireRole } = require('../middleware/auth');
const { validate } = require('../middleware/validate'); const { validate } = require('../middleware/validate');
const { schemas } = require('../middleware/validate'); const { schemas } = require('../middleware/validate');
@ -17,7 +18,7 @@ const loginLimiter = rateLimit({
message: { success: false, message: '登录尝试过于频繁请15分钟后再试' } message: { success: false, message: '登录尝试过于频繁请15分钟后再试' }
}); });
// 骑手登录 // 骑手登录(无需鉴权)
router.post('/login', loginLimiter, validate(schemas.login), async (req, res) => { router.post('/login', loginLimiter, validate(schemas.login), async (req, res) => {
try { try {
const { phone, password } = req.body; const { phone, password } = req.body;
@ -34,9 +35,9 @@ router.post('/login', loginLimiter, validate(schemas.login), async (req, res) =>
return res.status(401).json({ success: false, message: '手机号或密码错误' }); return res.status(401).json({ success: false, message: '手机号或密码错误' });
} }
// 签发 JWT包含 id、role // 签发 JWT包含 id、role、jti
const token = jwt.sign( const token = jwt.sign(
{ id: rider._id, role: rider.role, phone: rider.phone }, { id: rider._id, role: rider.role, type: 'rider', phone: rider.phone, jti: Math.random().toString(36) },
process.env.JWT_SECRET, process.env.JWT_SECRET,
{ expiresIn: process.env.JWT_EXPIRES_IN || '7d' } { expiresIn: process.env.JWT_EXPIRES_IN || '7d' }
); );
@ -57,31 +58,45 @@ router.post('/login', loginLimiter, validate(schemas.login), async (req, res) =>
} }
}); });
} catch (error) { } catch (error) {
res.status(500).json({ success: false, message: error.message }); res.status(500).json({ success: false, message: "服务器内部错误" });
} }
}); });
// 获取骑手信息(需登录 // 获取骑手信息(需登录且只能查看自己admin/store 可查看任意骑手
router.get('/:id', async (req, res) => { router.get('/:id', authMiddleware, async (req, res) => {
try { try {
const isSelf = req.params.id === req.user.id;
const isPrivileged = ['admin', 'store'].includes(req.user.role);
if (!isSelf && !isPrivileged) {
return res.status(403).json({ success: false, message: '无权查看该骑手信息' });
}
const rider = await Rider.findById(req.params.id); const rider = await Rider.findById(req.params.id);
if (!rider) { if (!rider) {
return res.status(404).json({ success: false, message: '骑手不存在' }); return res.status(404).json({ success: false, message: '骑手不存在' });
} }
res.json({ success: true, data: rider }); res.json({ success: true, data: rider });
} catch (error) { } catch (error) {
res.status(500).json({ success: false, message: error.message }); res.status(500).json({ success: false, message: "服务器内部错误" });
} }
}); });
// 更新骑手信息(如状态切换,需登录本人或管理员 // 更新骑手信息(需登录本人,或 admin/store
router.put('/:id', async (req, res) => { router.put('/:id', authMiddleware, async (req, res) => {
try { try {
// 不允许通过 PUT 修改密码和 role const isSelf = req.params.id === req.user.id;
const { password, role, ...safeBody } = req.body; const isPrivileged = ['admin', 'store'].includes(req.user.role);
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( const rider = await Rider.findByIdAndUpdate(
req.params.id, req.params.id,
safeBody, updateData,
{ new: true, runValidators: true } { new: true, runValidators: true }
); );
if (!rider) { if (!rider) {
@ -89,20 +104,25 @@ router.put('/:id', async (req, res) => {
} }
res.json({ success: true, data: rider }); res.json({ success: true, data: rider });
} catch (error) { } catch (error) {
res.status(400).json({ success: false, message: error.message }); res.status(400).json({ success: false, message: "服务器内部错误" });
} }
}); });
// 获取骑手的订单 // 获取骑手的订单(需登录本人,或 admin/store
router.get('/:id/orders', async (req, res) => { router.get('/:id/orders', authMiddleware, async (req, res) => {
try { try {
const isSelf = req.params.id === req.user.id;
const isPrivileged = ['admin', 'store'].includes(req.user.role);
if (!isSelf && !isPrivileged) {
return res.status(403).json({ success: false, message: '无权查看该骑手的订单' });
}
const orders = await Order.find({ rider: req.params.id }) const orders = await Order.find({ rider: req.params.id })
.populate('customer', 'name phone') .populate('customer', 'name phone')
.populate('vehicle', 'vehicleId model color') .populate('vehicle', 'vehicleId model color')
.sort({ createdAt: -1 }); .sort({ createdAt: -1 });
res.json({ success: true, data: orders }); res.json({ success: true, data: orders });
} catch (error) { } catch (error) {
res.status(500).json({ success: false, message: error.message }); res.status(500).json({ success: false, message: "服务器内部错误" });
} }
}); });

View File

@ -0,0 +1,67 @@
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 { comparePassword } = require('../utils/password');
// 登录限流
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 10,
message: { success: false, message: '登录尝试过于频繁' }
});
router.post('/login', loginLimiter, async (req, res) => {
try {
const { username, password } = req.body;
if (!username || !password) {
return res.status(400).json({ success: false, message: '用户名和密码不能为空' });
}
// 从 User 表查 store 类型账号
const user = await User.findOne({ username, 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: '用户名或密码错误' });
}
const isMatch = await comparePassword(password, user.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)
},
process.env.JWT_SECRET,
{ expiresIn: process.env.JWT_EXPIRES_IN || '24h' }
);
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
}
});
} catch (error) {
res.status(500).json({ success: false, message: '服务器内部错误' });
}
});
module.exports = router;

View File

@ -8,10 +8,37 @@ const { schemas } = require('../middleware/validate');
// 获取所有门店(登录即可) // 获取所有门店(登录即可)
router.get('/', authMiddleware, async (req, res) => { router.get('/', authMiddleware, async (req, res) => {
try { try {
// store 角色只能看自己关联的门店
if (req.user.role === 'store' && req.user.storeId) {
const stores = await Store.find({ storeId: req.user.storeId });
return res.json({ success: true, data: stores });
}
const stores = await Store.find(); const stores = await Store.find();
res.json({ success: true, data: stores }); res.json({ success: true, data: stores });
} catch (error) { } catch (error) {
res.status(500).json({ success: false, message: error.message }); res.status(500).json({ success: false, message: "服务器内部错误" });
}
});
// 获取单个门店
router.get('/:id', authMiddleware, async (req, res) => {
try {
let store;
// 如果是 MongoDB ObjectId 格式则用 findById否则用 storeId 字段查
if (req.params.id.match(/^[0-9a-fA-F]{24}$/)) {
store = await Store.findById(req.params.id);
} else {
store = await Store.findOne({ storeId: req.params.id });
}
if (!store) return res.status(404).json({ success: false, message: '门店不存在' });
// store 角色只能看自己关联的门店
if (req.user.role === 'store' && req.user.storeId && store.storeId !== req.user.storeId) {
return res.status(403).json({ success: false, message: '无权操作该门店数据' });
}
res.json({ success: true, data: store });
} catch (error) {
res.status(500).json({ success: false, message: "服务器内部错误" });
} }
}); });
@ -22,7 +49,7 @@ router.post('/', authMiddleware, requireRole('admin'), validate(schemas.store),
await store.save(); await store.save();
res.json({ success: true, data: store }); res.json({ success: true, data: store });
} catch (error) { } catch (error) {
res.status(400).json({ success: false, message: error.message }); res.status(400).json({ success: false, message: "服务器内部错误" });
} }
}); });
@ -33,7 +60,7 @@ router.put('/:id', authMiddleware, requireRole('admin'), async (req, res) => {
if (!store) return res.status(404).json({ success: false, message: '门店不存在' }); if (!store) return res.status(404).json({ success: false, message: '门店不存在' });
res.json({ success: true, data: store }); res.json({ success: true, data: store });
} catch (error) { } catch (error) {
res.status(400).json({ success: false, message: error.message }); res.status(400).json({ success: false, message: "服务器内部错误" });
} }
}); });
@ -43,7 +70,7 @@ router.delete('/:id', authMiddleware, requireRole('admin'), async (req, res) =>
await Store.findByIdAndDelete(req.params.id); await Store.findByIdAndDelete(req.params.id);
res.json({ success: true }); res.json({ success: true });
} catch (error) { } catch (error) {
res.status(400).json({ success: false, message: error.message }); res.status(400).json({ success: false, message: "服务器内部错误" });
} }
}); });

View File

@ -1,32 +1,33 @@
const express = require('express'); const express = require('express');
const router = express.Router(); const router = express.Router();
const VehicleType = require('../models/VehicleType'); const VehicleType = require('../models/VehicleType');
const { authMiddleware, requireRole } = require('../middleware/auth');
// 获取所有车型(支持按 storeId 筛选 // 获取所有车型(所有登录用户均可浏览
router.get('/', async (req, res) => { router.get('/', authMiddleware, async (req, res) => {
try { try {
const filter = {}; const filter = {};
if (req.query.storeId) filter.storeId = req.query.storeId; if (req.query.storeId) filter.storeId = req.query.storeId;
const vehicleTypes = await VehicleType.find(filter).sort({ createdAt: -1 }); const vehicleTypes = await VehicleType.find(filter).sort({ createdAt: -1 });
res.json({ success: true, data: vehicleTypes }); res.json({ success: true, data: vehicleTypes });
} catch (error) { } catch (error) {
res.status(500).json({ success: false, message: error.message }); res.status(500).json({ success: false, message: '服务器内部错误' });
} }
}); });
// 创建车型 // 创建车型(需 admin 或 store
router.post('/', async (req, res) => { router.post('/', authMiddleware, requireRole('admin', 'store'), async (req, res) => {
try { try {
const vehicleType = new VehicleType(req.body); const vehicleType = new VehicleType(req.body);
await vehicleType.save(); await vehicleType.save();
res.status(201).json({ success: true, data: vehicleType }); res.status(201).json({ success: true, data: vehicleType });
} catch (error) { } catch (error) {
res.status(400).json({ success: false, message: error.message }); res.status(400).json({ success: false, message: '服务器内部错误' });
} }
}); });
// 更新车型 // 更新车型(需 admin 或 store
router.put('/:id', async (req, res) => { router.put('/:id', authMiddleware, requireRole('admin', 'store'), async (req, res) => {
try { try {
const vehicleType = await VehicleType.findByIdAndUpdate( const vehicleType = await VehicleType.findByIdAndUpdate(
req.params.id, req.params.id,
@ -38,12 +39,12 @@ router.put('/:id', async (req, res) => {
} }
res.json({ success: true, data: vehicleType }); res.json({ success: true, data: vehicleType });
} catch (error) { } catch (error) {
res.status(400).json({ success: false, message: error.message }); res.status(400).json({ success: false, message: '服务器内部错误' });
} }
}); });
// 删除车型 // 删除车型(需 admin 或 store
router.delete('/:id', async (req, res) => { router.delete('/:id', authMiddleware, requireRole('admin', 'store'), async (req, res) => {
try { try {
const vehicleType = await VehicleType.findByIdAndDelete(req.params.id); const vehicleType = await VehicleType.findByIdAndDelete(req.params.id);
if (!vehicleType) { if (!vehicleType) {
@ -51,7 +52,7 @@ router.delete('/:id', async (req, res) => {
} }
res.json({ success: true, message: '车型已删除' }); res.json({ success: true, message: '车型已删除' });
} catch (error) { } catch (error) {
res.status(500).json({ success: false, message: error.message }); res.status(500).json({ success: false, message: '服务器内部错误' });
} }
}); });

View File

@ -1,6 +1,7 @@
const express = require('express'); const express = require('express');
const router = express.Router(); const router = express.Router();
const Vehicle = require('../models/Vehicle'); const Vehicle = require('../models/Vehicle');
const Store = require('../models/Store');
const { authMiddleware, requireRole } = require('../middleware/auth'); const { authMiddleware, requireRole } = require('../middleware/auth');
const { validate } = require('../middleware/validate'); const { validate } = require('../middleware/validate');
const { schemas } = require('../middleware/validate'); const { schemas } = require('../middleware/validate');
@ -9,12 +10,18 @@ const { schemas } = require('../middleware/validate');
router.get('/', authMiddleware, async (req, res) => { router.get('/', authMiddleware, async (req, res) => {
try { try {
const filter = {}; const filter = {};
if (req.query.storeId) filter.storeId = req.query.storeId; // store 角色查询时校验 storeId 归属(只能看自己关联的门店)
if (req.user.role === 'store' && req.user.storeId) {
// store 用户永远只能看自己门店的数据,忽略前端传的 storeId 参数
filter.storeId = req.user.storeId;
} else if (req.query.storeId) {
filter.storeId = req.query.storeId;
}
if (req.query.status) filter.status = req.query.status; if (req.query.status) filter.status = req.query.status;
const vehicles = await Vehicle.find(filter).populate('currentOrderId'); const vehicles = await Vehicle.find(filter).populate('currentOrderId');
res.json({ success: true, data: vehicles }); res.json({ success: true, data: vehicles });
} catch (error) { } catch (error) {
res.status(500).json({ success: false, message: error.message }); res.status(500).json({ success: false, message: '服务器内部错误' });
} }
}); });
@ -25,7 +32,7 @@ router.get('/:id', authMiddleware, async (req, res) => {
if (!vehicle) return res.status(404).json({ success: false, message: '车辆不存在' }); if (!vehicle) return res.status(404).json({ success: false, message: '车辆不存在' });
res.json({ success: true, data: vehicle }); res.json({ success: true, data: vehicle });
} catch (error) { } catch (error) {
res.status(500).json({ success: false, message: error.message }); res.status(500).json({ success: false, message: "服务器内部错误" });
} }
}); });
@ -38,7 +45,7 @@ router.post('/', authMiddleware, requireRole('admin', 'store'), validate(schemas
await vehicle.save(); await vehicle.save();
res.status(201).json({ success: true, data: vehicle }); res.status(201).json({ success: true, data: vehicle });
} catch (error) { } catch (error) {
res.status(400).json({ success: false, message: error.message }); res.status(400).json({ success: false, message: "服务器内部错误" });
} }
}); });
@ -51,7 +58,7 @@ router.put('/:id', authMiddleware, requireRole('admin', 'store'), validate(schem
if (!vehicle) return res.status(404).json({ success: false, message: '车辆不存在' }); if (!vehicle) return res.status(404).json({ success: false, message: '车辆不存在' });
res.json({ success: true, data: vehicle }); res.json({ success: true, data: vehicle });
} catch (error) { } catch (error) {
res.status(400).json({ success: false, message: error.message }); res.status(400).json({ success: false, message: "服务器内部错误" });
} }
}); });
@ -62,7 +69,7 @@ router.delete('/:id', authMiddleware, requireRole('admin'), async (req, res) =>
if (!vehicle) return res.status(404).json({ success: false, message: '车辆不存在' }); if (!vehicle) return res.status(404).json({ success: false, message: '车辆不存在' });
res.json({ success: true, message: '车辆已删除' }); res.json({ success: true, message: '车辆已删除' });
} catch (error) { } catch (error) {
res.status(500).json({ success: false, message: error.message }); res.status(500).json({ success: false, message: "服务器内部错误" });
} }
}); });
@ -72,7 +79,7 @@ router.get('/status/:status', authMiddleware, async (req, res) => {
const vehicles = await Vehicle.find({ status: req.params.status }); const vehicles = await Vehicle.find({ status: req.params.status });
res.json({ success: true, data: vehicles }); res.json({ success: true, data: vehicles });
} catch (error) { } catch (error) {
res.status(500).json({ success: false, message: error.message }); res.status(500).json({ success: false, message: "服务器内部错误" });
} }
}); });
@ -91,7 +98,7 @@ router.patch('/:id/location', authMiddleware, requireRole('admin', 'store'), asy
if (!vehicle) return res.status(404).json({ success: false, message: '车辆不存在' }); if (!vehicle) return res.status(404).json({ success: false, message: '车辆不存在' });
res.json({ success: true, data: vehicle }); res.json({ success: true, data: vehicle });
} catch (error) { } catch (error) {
res.status(400).json({ success: false, message: error.message }); res.status(400).json({ success: false, message: "服务器内部错误" });
} }
}); });

View File

@ -22,11 +22,11 @@ async function clearData() {
async function createVehicles() { async function createVehicles() {
const vehicles = [ const vehicles = [
{ vehicleId: 'SCOOTER001', model: '黑骑士', color: '黑色', batteryType: '锂电池', batteryCapacity: 20, batteryStatus: '正常', status: '空闲', purchaseDate: new Date('2024-01-15'), purchasePrice: 3500, purchaseSupplier: '小牛电动车' }, { vehicleId: 'SCOOTER001', storeId: 'STORE001', frameNumber: 'FN20240001', model: '黑骑士', color: '黑色', batteryType: '锂电池', batteryCapacity: 20, batteryStatus: '正常', status: '空闲', purchaseDate: new Date('2024-01-15'), purchasePrice: 3500, purchaseSupplier: '小牛电动车' },
{ vehicleId: 'SCOOTER002', model: '黑骑士', color: '白色', batteryType: '锂电池', batteryCapacity: 20, batteryStatus: '正常', status: '空闲', purchaseDate: new Date('2024-02-20'), purchasePrice: 3500, purchaseSupplier: '小牛电动车' }, { vehicleId: 'SCOOTER002', storeId: 'STORE001', frameNumber: 'FN20240002', model: '黑骑士', color: '白色', batteryType: '锂电池', batteryCapacity: 20, batteryStatus: '正常', status: '空闲', purchaseDate: new Date('2024-02-20'), purchasePrice: 3500, purchaseSupplier: '小牛电动车' },
{ vehicleId: 'SCOOTER003', model: '电动车', color: '蓝色', batteryType: '铅酸电池', batteryCapacity: 24, batteryStatus: '正常', status: '空闲', purchaseDate: new Date('2024-03-10'), purchasePrice: 2800, purchaseSupplier: '雅迪电动车' }, { vehicleId: 'SCOOTER003', storeId: 'STORE002', frameNumber: 'FN20240003', model: '电动车', color: '蓝色', batteryType: '铅酸电池', batteryCapacity: 24, batteryStatus: '正常', status: '空闲', purchaseDate: new Date('2024-03-10'), purchasePrice: 2800, purchaseSupplier: '雅迪电动车' },
{ vehicleId: 'SCOOTER004', model: '高端豪车', color: '红色', batteryType: '锂电池', batteryCapacity: 30, batteryStatus: '正常', status: '空闲', purchaseDate: new Date('2024-04-05'), purchasePrice: 8000, purchaseSupplier: '特斯拉' }, { vehicleId: 'SCOOTER004', storeId: 'STORE002', frameNumber: 'FN20240004', model: '高端豪车', color: '红色', batteryType: '锂电池', batteryCapacity: 30, batteryStatus: '正常', status: '空闲', purchaseDate: new Date('2024-04-05'), purchasePrice: 8000, purchaseSupplier: '特斯拉' },
{ vehicleId: 'SCOOTER005', model: '普通标准套餐', color: '绿色', batteryType: '铅酸电池', batteryCapacity: 20, batteryStatus: '正常', status: '空闲', purchaseDate: new Date('2024-05-01'), purchasePrice: 2500, purchaseSupplier: '爱玛电动车' } { vehicleId: 'SCOOTER005', storeId: 'STORE003', frameNumber: 'FN20240005', model: '普通标准套餐', color: '绿色', batteryType: '铅酸电池', batteryCapacity: 20, batteryStatus: '正常', status: '空闲', purchaseDate: new Date('2024-05-01'), purchasePrice: 2500, purchaseSupplier: '爱玛电动车' }
]; ];
console.log('🚗 创建示例车辆...'); console.log('🚗 创建示例车辆...');
await Vehicle.insertMany(vehicles); await Vehicle.insertMany(vehicles);
@ -116,10 +116,10 @@ async function createRiders() {
async function main() { async function main() {
try { try {
await clearData(); await clearData();
await createStores();
await createVehicles(); await createVehicles();
await createCustomers(); await createCustomers();
await createOrders(); await createOrders();
await createStores();
await createRiders(); await createRiders();
console.log('\n🎉 示例数据创建完成!'); console.log('\n🎉 示例数据创建完成!');
console.log('车辆: 5 辆 | 客户: 5 个 | 订单: 3 个 | 门店: 3 个 | 骑手: 3 个'); console.log('车辆: 5 辆 | 客户: 5 个 | 订单: 3 个 | 门店: 3 个 | 骑手: 3 个');