重构后端服务架构并优化前端错误处理
This commit is contained in:
23
backend/.env
Normal file
23
backend/.env
Normal file
@@ -0,0 +1,23 @@
|
||||
# 数据库配置
|
||||
DB_HOST=129.211.213.226
|
||||
DB_PORT=9527
|
||||
DB_USERNAME=root
|
||||
DB_PASSWORD=aiotAiot123!
|
||||
DB_NAME=jiebandata
|
||||
|
||||
# Redis配置
|
||||
REDIS_HOST=localhost
|
||||
REDIS_PORT=6379
|
||||
REDIS_PASSWORD=
|
||||
|
||||
# JWT配置
|
||||
JWT_SECRET=niumall_jwt_secret_key_2024
|
||||
JWT_EXPIRES_IN=24h
|
||||
|
||||
# 应用配置
|
||||
NODE_ENV=development
|
||||
PORT=3002
|
||||
API_PREFIX=/api
|
||||
|
||||
# 日志配置
|
||||
LOG_LEVEL=info
|
||||
@@ -1,61 +0,0 @@
|
||||
# 应用配置
|
||||
NODE_ENV=development
|
||||
PORT=3001
|
||||
APP_NAME=活牛采购智能数字化系统
|
||||
|
||||
# 数据库配置
|
||||
DB_HOST=129.211.213.226
|
||||
DB_PORT=9527
|
||||
DB_NAME=jiebandata
|
||||
DB_USER=root
|
||||
DB_PASSWORD=aiotAiot123!
|
||||
DB_DIALECT=mysql
|
||||
|
||||
# Redis配置 (本地开发)
|
||||
REDIS_HOST=localhost
|
||||
REDIS_PORT=6379
|
||||
REDIS_PASSWORD=
|
||||
REDIS_DB=0
|
||||
|
||||
# JWT配置
|
||||
JWT_SECRET=niumall_jwt_secret_2024_cattle_procurement_system
|
||||
JWT_EXPIRES_IN=24h
|
||||
JWT_REFRESH_EXPIRES_IN=7d
|
||||
|
||||
# 文件上传配置
|
||||
UPLOAD_PATH=./uploads
|
||||
MAX_FILE_SIZE=50MB
|
||||
ALLOWED_FILE_TYPES=jpg,jpeg,png,gif,pdf,doc,docx,xls,xlsx,mp4,avi
|
||||
|
||||
# 日志配置
|
||||
LOG_LEVEL=info
|
||||
LOG_FILE=./logs/app.log
|
||||
|
||||
# 短信配置
|
||||
SMS_PROVIDER=aliyun
|
||||
SMS_ACCESS_KEY=
|
||||
SMS_SECRET_KEY=
|
||||
|
||||
# 支付配置
|
||||
PAYMENT_PROVIDER=wechat
|
||||
WECHAT_APPID=
|
||||
WECHAT_SECRET=
|
||||
WECHAT_MERCHANT_ID=
|
||||
WECHAT_API_KEY=
|
||||
|
||||
# WebSocket配置
|
||||
WS_PORT=3002
|
||||
|
||||
# 监控配置
|
||||
ENABLE_MONITORING=true
|
||||
MONITORING_TOKEN=
|
||||
|
||||
# 邮件配置
|
||||
SMTP_HOST=
|
||||
SMTP_PORT=587
|
||||
SMTP_USER=
|
||||
SMTP_PASS=
|
||||
|
||||
# API限流配置
|
||||
RATE_LIMIT_WINDOW=15
|
||||
RATE_LIMIT_MAX_REQUESTS=100
|
||||
@@ -4,10 +4,11 @@ const helmet = require('helmet')
|
||||
const morgan = require('morgan')
|
||||
const rateLimit = require('express-rate-limit')
|
||||
const compression = require('compression')
|
||||
const { testConnection, syncDatabase } = require('./config/database')
|
||||
const { createInitialUsers } = require('./scripts/initData')
|
||||
require('dotenv').config()
|
||||
|
||||
// 数据库连接
|
||||
const { testConnection, syncModels } = require('./models')
|
||||
|
||||
const app = express()
|
||||
|
||||
// 中间件配置
|
||||
@@ -77,16 +78,13 @@ const startServer = async () => {
|
||||
// 测试数据库连接
|
||||
const dbConnected = await testConnection();
|
||||
if (!dbConnected) {
|
||||
console.log('⚠️ 数据库连接失败,使用模拟数据模式');
|
||||
} else {
|
||||
// 同步数据库模型(开发环境)
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
await syncDatabase({ alter: true });
|
||||
// 创建初始用户数据
|
||||
await createInitialUsers();
|
||||
}
|
||||
console.error('❌ 数据库连接失败,服务器启动终止');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// 同步数据库模型
|
||||
await syncModels();
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`🚀 服务器启动成功`)
|
||||
console.log(`📱 运行环境: ${process.env.NODE_ENV || 'development'}`)
|
||||
@@ -100,4 +98,4 @@ const startServer = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
startServer();
|
||||
startServer()
|
||||
|
||||
@@ -1,55 +1,58 @@
|
||||
const { Sequelize } = require('sequelize');
|
||||
// 数据库配置文件
|
||||
require('dotenv').config();
|
||||
|
||||
// 数据库连接配置
|
||||
const sequelize = new Sequelize({
|
||||
host: process.env.DB_HOST || '129.211.213.226',
|
||||
port: process.env.DB_PORT || 9527,
|
||||
database: process.env.DB_NAME || 'jiebandata',
|
||||
username: process.env.DB_USER || 'root',
|
||||
password: process.env.DB_PASSWORD || 'aiotAiot123!',
|
||||
dialect: process.env.DB_DIALECT || 'mysql',
|
||||
logging: process.env.NODE_ENV === 'development' ? console.log : false,
|
||||
pool: {
|
||||
max: 5,
|
||||
min: 0,
|
||||
acquire: 30000,
|
||||
idle: 10000
|
||||
},
|
||||
define: {
|
||||
timestamps: true,
|
||||
underscored: true,
|
||||
freezeTableName: true
|
||||
},
|
||||
timezone: '+08:00'
|
||||
});
|
||||
|
||||
// 测试数据库连接
|
||||
const testConnection = async () => {
|
||||
try {
|
||||
await sequelize.authenticate();
|
||||
console.log('✅ 数据库连接成功');
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('❌ 数据库连接失败:', error.message);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// 同步数据库模型
|
||||
const syncDatabase = async (options = {}) => {
|
||||
try {
|
||||
await sequelize.sync(options);
|
||||
console.log('✅ 数据库同步成功');
|
||||
} catch (error) {
|
||||
console.error('❌ 数据库同步失败:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
sequelize,
|
||||
testConnection,
|
||||
syncDatabase,
|
||||
Sequelize
|
||||
development: {
|
||||
username: process.env.DB_USERNAME || 'root',
|
||||
password: process.env.DB_PASSWORD || 'aiotAiot123!',
|
||||
database: process.env.DB_NAME || 'jiebandata',
|
||||
host: process.env.DB_HOST || '129.211.213.226',
|
||||
port: process.env.DB_PORT || 9527,
|
||||
dialect: 'mysql',
|
||||
dialectOptions: {
|
||||
charset: 'utf8mb4',
|
||||
dateStrings: true,
|
||||
typeCast: true
|
||||
},
|
||||
timezone: '+08:00',
|
||||
logging: console.log,
|
||||
pool: {
|
||||
max: 20,
|
||||
min: 0,
|
||||
acquire: 60000,
|
||||
idle: 10000
|
||||
}
|
||||
},
|
||||
test: {
|
||||
username: process.env.TEST_DB_USERNAME || 'root',
|
||||
password: process.env.TEST_DB_PASSWORD || 'aiotAiot123!',
|
||||
database: process.env.TEST_DB_NAME || 'jiebandata_test',
|
||||
host: process.env.TEST_DB_HOST || '129.211.213.226',
|
||||
port: process.env.TEST_DB_PORT || 9527,
|
||||
dialect: 'mysql',
|
||||
dialectOptions: {
|
||||
charset: 'utf8mb4'
|
||||
},
|
||||
timezone: '+08:00',
|
||||
logging: false
|
||||
},
|
||||
production: {
|
||||
username: process.env.DB_USERNAME,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_NAME,
|
||||
host: process.env.DB_HOST,
|
||||
port: process.env.DB_PORT,
|
||||
dialect: 'mysql',
|
||||
dialectOptions: {
|
||||
charset: 'utf8mb4'
|
||||
},
|
||||
timezone: '+08:00',
|
||||
logging: false,
|
||||
pool: {
|
||||
max: 50,
|
||||
min: 5,
|
||||
acquire: 60000,
|
||||
idle: 10000
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,184 +0,0 @@
|
||||
const jwt = require('jsonwebtoken');
|
||||
const User = require('../models/User');
|
||||
|
||||
// JWT认证中间件
|
||||
const authenticateToken = async (req, res, next) => {
|
||||
try {
|
||||
const authHeader = req.headers['authorization'];
|
||||
const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN
|
||||
|
||||
if (!token) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '访问令牌缺失',
|
||||
code: 'TOKEN_MISSING'
|
||||
});
|
||||
}
|
||||
|
||||
// 验证token
|
||||
const decoded = jwt.verify(token, process.env.JWT_SECRET);
|
||||
|
||||
// 查找用户
|
||||
const user = await User.findByPk(decoded.userId, {
|
||||
attributes: { exclude: ['password_hash'] }
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '用户不存在',
|
||||
code: 'USER_NOT_FOUND'
|
||||
});
|
||||
}
|
||||
|
||||
if (user.status !== 'active') {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '用户账号已被禁用',
|
||||
code: 'USER_DISABLED'
|
||||
});
|
||||
}
|
||||
|
||||
// 将用户信息附加到请求对象
|
||||
req.user = user;
|
||||
next();
|
||||
} catch (error) {
|
||||
if (error.name === 'JsonWebTokenError') {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '无效的访问令牌',
|
||||
code: 'INVALID_TOKEN'
|
||||
});
|
||||
} else if (error.name === 'TokenExpiredError') {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '访问令牌已过期',
|
||||
code: 'TOKEN_EXPIRED'
|
||||
});
|
||||
}
|
||||
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '认证服务错误',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 权限检查中间件
|
||||
const requireRole = (roles) => {
|
||||
return (req, res, next) => {
|
||||
if (!req.user) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '用户未认证',
|
||||
code: 'USER_NOT_AUTHENTICATED'
|
||||
});
|
||||
}
|
||||
|
||||
const userRoles = Array.isArray(roles) ? roles : [roles];
|
||||
|
||||
if (!userRoles.includes(req.user.user_type)) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
message: '权限不足',
|
||||
code: 'INSUFFICIENT_PERMISSIONS',
|
||||
requiredRoles: userRoles,
|
||||
userRole: req.user.user_type
|
||||
});
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
};
|
||||
|
||||
// 生成JWT token
|
||||
const generateToken = (user) => {
|
||||
const payload = {
|
||||
userId: user.id,
|
||||
username: user.username,
|
||||
userType: user.user_type
|
||||
};
|
||||
|
||||
return {
|
||||
accessToken: jwt.sign(payload, process.env.JWT_SECRET, {
|
||||
expiresIn: process.env.JWT_EXPIRES_IN || '24h'
|
||||
}),
|
||||
refreshToken: jwt.sign(
|
||||
{ userId: user.id },
|
||||
process.env.JWT_SECRET + '_refresh',
|
||||
{ expiresIn: process.env.JWT_REFRESH_EXPIRES_IN || '7d' }
|
||||
)
|
||||
};
|
||||
};
|
||||
|
||||
// 刷新token
|
||||
const refreshToken = async (req, res, next) => {
|
||||
try {
|
||||
const { refreshToken } = req.body;
|
||||
|
||||
if (!refreshToken) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '刷新令牌缺失'
|
||||
});
|
||||
}
|
||||
|
||||
const decoded = jwt.verify(refreshToken, process.env.JWT_SECRET + '_refresh');
|
||||
const user = await User.findByPk(decoded.userId, {
|
||||
attributes: { exclude: ['password_hash'] }
|
||||
});
|
||||
|
||||
if (!user || user.status !== 'active') {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '无效的刷新令牌'
|
||||
});
|
||||
}
|
||||
|
||||
const tokens = generateToken(user);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '令牌刷新成功',
|
||||
data: tokens
|
||||
});
|
||||
} catch (error) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '刷新令牌无效或已过期'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 可选认证中间件(不强制要求登录)
|
||||
const optionalAuth = async (req, res, next) => {
|
||||
try {
|
||||
const authHeader = req.headers['authorization'];
|
||||
const token = authHeader && authHeader.split(' ')[1];
|
||||
|
||||
if (token) {
|
||||
const decoded = jwt.verify(token, process.env.JWT_SECRET);
|
||||
const user = await User.findByPk(decoded.userId, {
|
||||
attributes: { exclude: ['password_hash'] }
|
||||
});
|
||||
|
||||
if (user && user.status === 'active') {
|
||||
req.user = user;
|
||||
}
|
||||
}
|
||||
|
||||
next();
|
||||
} catch (error) {
|
||||
// 忽略错误,继续请求
|
||||
next();
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
authenticateToken,
|
||||
requireRole,
|
||||
generateToken,
|
||||
refreshToken,
|
||||
optionalAuth
|
||||
};
|
||||
@@ -1,122 +0,0 @@
|
||||
const { DataTypes } = require('sequelize');
|
||||
const { sequelize } = require('../config/database');
|
||||
const bcrypt = require('bcryptjs');
|
||||
|
||||
// 用户模型
|
||||
const User = sequelize.define('User', {
|
||||
id: {
|
||||
type: DataTypes.BIGINT,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
uuid: {
|
||||
type: DataTypes.STRING(36),
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
defaultValue: DataTypes.UUIDV4
|
||||
},
|
||||
username: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
validate: {
|
||||
len: [2, 50]
|
||||
}
|
||||
},
|
||||
password_hash: {
|
||||
type: DataTypes.STRING(255),
|
||||
allowNull: false
|
||||
},
|
||||
phone: {
|
||||
type: DataTypes.STRING(20),
|
||||
allowNull: false,
|
||||
unique: true,
|
||||
validate: {
|
||||
is: /^1[3-9]\d{9}$/
|
||||
}
|
||||
},
|
||||
email: {
|
||||
type: DataTypes.STRING(100),
|
||||
validate: {
|
||||
isEmail: true
|
||||
}
|
||||
},
|
||||
real_name: DataTypes.STRING(50),
|
||||
avatar_url: DataTypes.STRING(255),
|
||||
user_type: {
|
||||
type: DataTypes.ENUM('client', 'supplier', 'driver', 'staff', 'admin'),
|
||||
allowNull: false,
|
||||
defaultValue: 'client'
|
||||
},
|
||||
status: {
|
||||
type: DataTypes.ENUM('active', 'inactive', 'locked'),
|
||||
defaultValue: 'active'
|
||||
},
|
||||
last_login_at: DataTypes.DATE,
|
||||
login_count: {
|
||||
type: DataTypes.INTEGER,
|
||||
defaultValue: 0
|
||||
}
|
||||
}, {
|
||||
tableName: 'users',
|
||||
timestamps: true,
|
||||
paranoid: true, // 软删除
|
||||
indexes: [
|
||||
{ fields: ['phone'] },
|
||||
{ fields: ['user_type'] },
|
||||
{ fields: ['status'] },
|
||||
{ fields: ['username'] }
|
||||
],
|
||||
hooks: {
|
||||
beforeCreate: async (user) => {
|
||||
if (user.password_hash) {
|
||||
user.password_hash = await bcrypt.hash(user.password_hash, 12);
|
||||
}
|
||||
},
|
||||
beforeUpdate: async (user) => {
|
||||
if (user.changed('password_hash')) {
|
||||
user.password_hash = await bcrypt.hash(user.password_hash, 12);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 实例方法:验证密码
|
||||
User.prototype.validatePassword = async function(password) {
|
||||
return await bcrypt.compare(password, this.password_hash);
|
||||
};
|
||||
|
||||
// 实例方法:更新登录信息
|
||||
User.prototype.updateLoginInfo = async function() {
|
||||
this.last_login_at = new Date();
|
||||
this.login_count += 1;
|
||||
await this.save();
|
||||
};
|
||||
|
||||
// 类方法:根据用户名或手机号查找用户
|
||||
User.findByLoginIdentifier = async function(identifier) {
|
||||
return await this.findOne({
|
||||
where: {
|
||||
[sequelize.Sequelize.Op.or]: [
|
||||
{ username: identifier },
|
||||
{ phone: identifier }
|
||||
]
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 类方法:创建用户
|
||||
User.createUser = async function(userData) {
|
||||
const { username, password, phone, email, real_name, user_type = 'client' } = userData;
|
||||
|
||||
return await this.create({
|
||||
username,
|
||||
password_hash: password,
|
||||
phone,
|
||||
email,
|
||||
real_name,
|
||||
user_type
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = User;
|
||||
140
backend/models/index.js
Normal file
140
backend/models/index.js
Normal file
@@ -0,0 +1,140 @@
|
||||
// 数据库连接和模型定义
|
||||
const { Sequelize } = require('sequelize');
|
||||
const config = require('../config/database.js');
|
||||
|
||||
// 根据环境变量选择配置
|
||||
const env = process.env.NODE_ENV || 'development';
|
||||
const dbConfig = config[env];
|
||||
|
||||
// 创建Sequelize实例
|
||||
const sequelize = new Sequelize(
|
||||
dbConfig.database,
|
||||
dbConfig.username,
|
||||
dbConfig.password,
|
||||
{
|
||||
host: dbConfig.host,
|
||||
port: dbConfig.port,
|
||||
dialect: dbConfig.dialect,
|
||||
dialectOptions: dbConfig.dialectOptions,
|
||||
timezone: dbConfig.timezone,
|
||||
logging: dbConfig.logging,
|
||||
pool: dbConfig.pool
|
||||
}
|
||||
);
|
||||
|
||||
// 测试数据库连接
|
||||
const testConnection = async () => {
|
||||
try {
|
||||
await sequelize.authenticate();
|
||||
console.log('✅ 数据库连接成功');
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('❌ 数据库连接失败:', error);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// 定义模型
|
||||
const models = {
|
||||
sequelize,
|
||||
Sequelize,
|
||||
|
||||
// 用户模型(匹配实际数据库结构)
|
||||
User: sequelize.define('User', {
|
||||
id: {
|
||||
type: Sequelize.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
openid: {
|
||||
type: Sequelize.STRING(64),
|
||||
allowNull: false,
|
||||
unique: true
|
||||
},
|
||||
nickname: {
|
||||
type: Sequelize.STRING(50),
|
||||
allowNull: false
|
||||
},
|
||||
avatar: {
|
||||
type: Sequelize.STRING(255)
|
||||
},
|
||||
gender: {
|
||||
type: Sequelize.ENUM('male', 'female', 'other')
|
||||
},
|
||||
birthday: {
|
||||
type: Sequelize.DATE
|
||||
},
|
||||
phone: {
|
||||
type: Sequelize.STRING(20),
|
||||
unique: true
|
||||
},
|
||||
email: {
|
||||
type: Sequelize.STRING(100),
|
||||
unique: true
|
||||
},
|
||||
uuid: {
|
||||
type: Sequelize.STRING(36),
|
||||
unique: true
|
||||
}
|
||||
}, {
|
||||
tableName: 'users',
|
||||
timestamps: true,
|
||||
createdAt: 'created_at',
|
||||
updatedAt: 'updated_at'
|
||||
}),
|
||||
|
||||
// 为了兼容现有API,创建一个简化版的用户模型
|
||||
ApiUser: sequelize.define('ApiUser', {
|
||||
id: {
|
||||
type: Sequelize.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
username: {
|
||||
type: Sequelize.STRING(50),
|
||||
allowNull: false,
|
||||
unique: true
|
||||
},
|
||||
password_hash: {
|
||||
type: Sequelize.STRING(255),
|
||||
allowNull: false
|
||||
},
|
||||
phone: {
|
||||
type: Sequelize.STRING(20),
|
||||
allowNull: false,
|
||||
unique: true
|
||||
},
|
||||
email: {
|
||||
type: Sequelize.STRING(100)
|
||||
},
|
||||
user_type: {
|
||||
type: Sequelize.ENUM('client', 'supplier', 'driver', 'staff', 'admin'),
|
||||
allowNull: false
|
||||
},
|
||||
status: {
|
||||
type: Sequelize.ENUM('active', 'inactive', 'locked'),
|
||||
defaultValue: 'active'
|
||||
}
|
||||
}, {
|
||||
tableName: 'api_users',
|
||||
timestamps: true
|
||||
})
|
||||
};
|
||||
|
||||
// 同步数据库模型
|
||||
const syncModels = async () => {
|
||||
try {
|
||||
// 只同步API用户表(如果不存在则创建)
|
||||
await models.ApiUser.sync({ alter: true });
|
||||
console.log('✅ API用户表同步成功');
|
||||
console.log('✅ 数据库模型同步完成');
|
||||
} catch (error) {
|
||||
console.error('❌ 数据库模型同步失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
...models,
|
||||
testConnection,
|
||||
syncModels
|
||||
};
|
||||
@@ -2,10 +2,10 @@
|
||||
"name": "niumall-backend",
|
||||
"version": "1.0.0",
|
||||
"description": "活牛采购智能数字化系统 - 后端服务",
|
||||
"main": "app.js",
|
||||
"main": "src/app.js",
|
||||
"scripts": {
|
||||
"start": "node app.js",
|
||||
"dev": "nodemon app.js",
|
||||
"start": "node src/app.js",
|
||||
"dev": "nodemon src/app.js",
|
||||
"test": "jest",
|
||||
"test:watch": "jest --watch",
|
||||
"test:coverage": "jest --coverage",
|
||||
|
||||
@@ -1,186 +1,172 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const Joi = require('joi');
|
||||
const User = require('../models/User');
|
||||
const { generateToken, refreshToken, authenticateToken } = require('../middleware/auth');
|
||||
const express = require('express')
|
||||
const bcrypt = require('bcryptjs')
|
||||
const jwt = require('jsonwebtoken')
|
||||
const Joi = require('joi')
|
||||
const router = express.Router()
|
||||
|
||||
// 验证schema
|
||||
// 引入数据库模型
|
||||
const { ApiUser } = require('../models')
|
||||
|
||||
// 登录参数验证
|
||||
const loginSchema = Joi.object({
|
||||
username: Joi.string().min(2).max(50).required(),
|
||||
password: Joi.string().min(6).max(100).required()
|
||||
});
|
||||
})
|
||||
|
||||
const passwordResetRequestSchema = Joi.object({
|
||||
phone: Joi.string().pattern(/^1[3-9]\d{9}$/).required()
|
||||
});
|
||||
|
||||
const passwordResetConfirmSchema = Joi.object({
|
||||
phone: Joi.string().pattern(/^1[3-9]\d{9}$/).required(),
|
||||
resetCode: Joi.string().required(),
|
||||
newPassword: Joi.string().min(6).max(100).required()
|
||||
});
|
||||
|
||||
const changePasswordSchema = Joi.object({
|
||||
oldPassword: Joi.string().required(),
|
||||
newPassword: Joi.string().min(6).max(100).required()
|
||||
});
|
||||
// 生成JWT token
|
||||
const generateToken = (user) => {
|
||||
return jwt.sign(
|
||||
{
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
role: user.user_type
|
||||
},
|
||||
process.env.JWT_SECRET || 'niumall-secret-key',
|
||||
{ expiresIn: process.env.JWT_EXPIRES_IN || '24h' }
|
||||
)
|
||||
}
|
||||
|
||||
// 用户登录
|
||||
router.post('/login', async (req, res) => {
|
||||
try {
|
||||
const { error, value } = loginSchema.validate(req.body);
|
||||
// 参数验证
|
||||
const { error, value } = loginSchema.validate(req.body)
|
||||
if (error) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '参数验证失败',
|
||||
errors: error.details.map(detail => detail.message)
|
||||
});
|
||||
details: error.details[0].message
|
||||
})
|
||||
}
|
||||
|
||||
const { username, password } = value;
|
||||
|
||||
// 查找用户
|
||||
const user = await User.findByLoginIdentifier(username);
|
||||
const { username, password } = value
|
||||
|
||||
// 查找用户
|
||||
const user = await ApiUser.findOne({
|
||||
where: {
|
||||
[require('sequelize').Op.or]: [
|
||||
{ username: username },
|
||||
{ phone: username },
|
||||
{ email: username }
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '用户名或密码错误',
|
||||
code: 'INVALID_CREDENTIALS'
|
||||
});
|
||||
message: '用户名或密码错误'
|
||||
})
|
||||
}
|
||||
|
||||
// 验证密码
|
||||
const isValidPassword = await user.validatePassword(password);
|
||||
if (!isValidPassword) {
|
||||
const isPasswordValid = await bcrypt.compare(password, user.password_hash)
|
||||
if (!isPasswordValid) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '用户名或密码错误',
|
||||
code: 'INVALID_CREDENTIALS'
|
||||
});
|
||||
message: '用户名或密码错误'
|
||||
})
|
||||
}
|
||||
|
||||
// 检查用户状态
|
||||
if (user.status !== 'active') {
|
||||
return res.status(401).json({
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
message: '账号已被禁用,请联系管理员',
|
||||
code: 'ACCOUNT_DISABLED'
|
||||
});
|
||||
message: '账户已被禁用,请联系管理员'
|
||||
})
|
||||
}
|
||||
|
||||
// 更新登录信息
|
||||
await user.updateLoginInfo();
|
||||
// 生成token
|
||||
const token = generateToken(user)
|
||||
|
||||
// 生成JWT token
|
||||
const tokens = generateToken(user);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '登录成功',
|
||||
data: {
|
||||
...tokens,
|
||||
access_token: token,
|
||||
token_type: 'Bearer',
|
||||
expires_in: 86400, // 24小时
|
||||
user: {
|
||||
id: user.id,
|
||||
uuid: user.uuid,
|
||||
username: user.username,
|
||||
phone: user.phone,
|
||||
email: user.email,
|
||||
real_name: user.real_name,
|
||||
avatar_url: user.avatar_url,
|
||||
user_type: user.user_type,
|
||||
status: user.status,
|
||||
last_login_at: user.last_login_at,
|
||||
login_count: user.login_count
|
||||
role: user.user_type,
|
||||
status: user.status
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('登录错误:', error);
|
||||
console.error('登录失败:', error)
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '登录失败',
|
||||
error: error.message
|
||||
});
|
||||
message: '登录失败,请稍后重试'
|
||||
})
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
// 获取用户信息
|
||||
// 获取当前用户信息
|
||||
router.get('/me', authenticateToken, async (req, res) => {
|
||||
try {
|
||||
// req.user 已经通过中间件注入
|
||||
const user = await ApiUser.findByPk(req.user.id)
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '用户不存在'
|
||||
})
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
id: req.user.id,
|
||||
uuid: req.user.uuid,
|
||||
username: req.user.username,
|
||||
phone: req.user.phone,
|
||||
email: req.user.email,
|
||||
real_name: req.user.real_name,
|
||||
avatar_url: req.user.avatar_url,
|
||||
user_type: req.user.user_type,
|
||||
status: req.user.status,
|
||||
last_login_at: req.user.last_login_at,
|
||||
login_count: req.user.login_count,
|
||||
created_at: req.user.created_at,
|
||||
updated_at: req.user.updated_at
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取用户信息错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取用户信息失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 用户登出
|
||||
router.post('/logout', authenticateToken, async (req, res) => {
|
||||
try {
|
||||
// TODO: 实际项目中可以将token加入黑名单或Redis
|
||||
res.json({
|
||||
success: true,
|
||||
message: '退出登录成功'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('退出登录错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '退出登录失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 刷新token
|
||||
router.post('/refresh', refreshToken);
|
||||
|
||||
// 验证token有效性
|
||||
router.post('/verify', authenticateToken, (req, res) => {
|
||||
try {
|
||||
// 如果通过认证中间件,说明token有效
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Token有效',
|
||||
data: {
|
||||
valid: true,
|
||||
user: {
|
||||
id: req.user.id,
|
||||
username: req.user.username,
|
||||
user_type: req.user.user_type
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
role: user.user_type,
|
||||
status: user.status
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Token验证错误:', error);
|
||||
console.error('获取用户信息失败:', error)
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Token验证失败',
|
||||
error: error.message
|
||||
});
|
||||
message: '获取用户信息失败'
|
||||
})
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
module.exports = router;
|
||||
// 用户登出
|
||||
router.post('/logout', authenticateToken, (req, res) => {
|
||||
// 在实际项目中,可以将token加入黑名单
|
||||
res.json({
|
||||
success: true,
|
||||
message: '登出成功'
|
||||
})
|
||||
})
|
||||
|
||||
// JWT token验证中间件
|
||||
function authenticateToken(req, res, next) {
|
||||
const authHeader = req.headers['authorization']
|
||||
const token = authHeader && authHeader.split(' ')[1]
|
||||
|
||||
if (!token) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '访问令牌缺失'
|
||||
})
|
||||
}
|
||||
|
||||
jwt.verify(token, process.env.JWT_SECRET || 'niumall-secret-key', (err, user) => {
|
||||
if (err) {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
message: '访问令牌无效或已过期'
|
||||
})
|
||||
}
|
||||
req.user = user
|
||||
next()
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = router
|
||||
@@ -3,39 +3,9 @@ const bcrypt = require('bcryptjs')
|
||||
const Joi = require('joi')
|
||||
const router = express.Router()
|
||||
|
||||
// 模拟用户数据
|
||||
let users = [
|
||||
{
|
||||
id: 1,
|
||||
username: 'admin',
|
||||
email: 'admin@example.com',
|
||||
phone: '13800138000',
|
||||
role: 'admin',
|
||||
status: 'active',
|
||||
createdAt: '2024-01-01T00:00:00Z',
|
||||
updatedAt: '2024-01-01T00:00:00Z'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
username: 'buyer01',
|
||||
email: 'buyer01@example.com',
|
||||
phone: '13800138001',
|
||||
role: 'buyer',
|
||||
status: 'active',
|
||||
createdAt: '2024-01-02T00:00:00Z',
|
||||
updatedAt: '2024-01-02T00:00:00Z'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
username: 'supplier01',
|
||||
email: 'supplier01@example.com',
|
||||
phone: '13800138002',
|
||||
role: 'supplier',
|
||||
status: 'inactive',
|
||||
createdAt: '2024-01-03T00:00:00Z',
|
||||
updatedAt: '2024-01-03T00:00:00Z'
|
||||
}
|
||||
]
|
||||
// 引入数据库模型
|
||||
const { ApiUser } = require('../models')
|
||||
const sequelize = require('sequelize')
|
||||
|
||||
// 验证模式
|
||||
const createUserSchema = Joi.object({
|
||||
@@ -43,60 +13,55 @@ const createUserSchema = Joi.object({
|
||||
email: Joi.string().email().required(),
|
||||
phone: Joi.string().pattern(/^1[3-9]\d{9}$/).allow(''),
|
||||
password: Joi.string().min(6).max(100).required(),
|
||||
role: Joi.string().valid('admin', 'buyer', 'trader', 'supplier', 'driver').required(),
|
||||
status: Joi.string().valid('active', 'inactive').default('active')
|
||||
user_type: Joi.string().valid('client', 'supplier', 'driver', 'staff', 'admin').required(),
|
||||
status: Joi.string().valid('active', 'inactive', 'locked').default('active')
|
||||
})
|
||||
|
||||
const updateUserSchema = Joi.object({
|
||||
username: Joi.string().min(2).max(50),
|
||||
email: Joi.string().email(),
|
||||
phone: Joi.string().pattern(/^1[3-9]\d{9}$/).allow(''),
|
||||
role: Joi.string().valid('admin', 'buyer', 'trader', 'supplier', 'driver'),
|
||||
status: Joi.string().valid('active', 'inactive', 'banned')
|
||||
user_type: Joi.string().valid('client', 'supplier', 'driver', 'staff', 'admin'),
|
||||
status: Joi.string().valid('active', 'inactive', 'locked')
|
||||
})
|
||||
|
||||
// 获取用户列表
|
||||
router.get('/', (req, res) => {
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const { page = 1, pageSize = 20, keyword, role, status } = req.query
|
||||
const { page = 1, pageSize = 20, keyword, user_type, status } = req.query
|
||||
|
||||
let filteredUsers = [...users]
|
||||
|
||||
// 关键词搜索
|
||||
// 构建查询条件
|
||||
const where = {}
|
||||
if (keyword) {
|
||||
filteredUsers = filteredUsers.filter(user =>
|
||||
user.username.includes(keyword) ||
|
||||
user.email.includes(keyword)
|
||||
)
|
||||
where[sequelize.Op.or] = [
|
||||
{ username: { [sequelize.Op.like]: `%${keyword}%` } },
|
||||
{ email: { [sequelize.Op.like]: `%${keyword}%` } },
|
||||
{ phone: { [sequelize.Op.like]: `%${keyword}%` } }
|
||||
]
|
||||
}
|
||||
if (user_type) where.user_type = user_type
|
||||
if (status) where.status = status
|
||||
|
||||
// 角色筛选
|
||||
if (role) {
|
||||
filteredUsers = filteredUsers.filter(user => user.role === role)
|
||||
}
|
||||
|
||||
// 状态筛选
|
||||
if (status) {
|
||||
filteredUsers = filteredUsers.filter(user => user.status === status)
|
||||
}
|
||||
|
||||
// 分页
|
||||
const total = filteredUsers.length
|
||||
const startIndex = (page - 1) * pageSize
|
||||
const endIndex = startIndex + parseInt(pageSize)
|
||||
const paginatedUsers = filteredUsers.slice(startIndex, endIndex)
|
||||
// 分页查询
|
||||
const result = await ApiUser.findAndCountAll({
|
||||
where,
|
||||
limit: parseInt(pageSize),
|
||||
offset: (parseInt(page) - 1) * parseInt(pageSize),
|
||||
order: [['createdAt', 'DESC']]
|
||||
})
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
items: paginatedUsers,
|
||||
total: total,
|
||||
items: result.rows,
|
||||
total: result.count,
|
||||
page: parseInt(page),
|
||||
pageSize: parseInt(pageSize),
|
||||
totalPages: Math.ceil(total / pageSize)
|
||||
totalPages: Math.ceil(result.count / parseInt(pageSize))
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('获取用户列表失败:', error)
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取用户列表失败'
|
||||
@@ -105,10 +70,10 @@ router.get('/', (req, res) => {
|
||||
})
|
||||
|
||||
// 获取用户详情
|
||||
router.get('/:id', (req, res) => {
|
||||
router.get('/:id', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params
|
||||
const user = users.find(u => u.id === parseInt(id))
|
||||
const user = await ApiUser.findByPk(id)
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
@@ -122,6 +87,7 @@ router.get('/:id', (req, res) => {
|
||||
data: user
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('获取用户详情失败:', error)
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '获取用户详情失败'
|
||||
@@ -142,37 +108,39 @@ router.post('/', async (req, res) => {
|
||||
})
|
||||
}
|
||||
|
||||
const { username, email, phone, password, role, status } = value
|
||||
const { username, email, phone, password, user_type, status } = value
|
||||
|
||||
// 检查用户名是否已存在
|
||||
if (users.find(u => u.username === username)) {
|
||||
const existingUser = await ApiUser.findOne({
|
||||
where: {
|
||||
[sequelize.Op.or]: [
|
||||
{ username: username },
|
||||
{ email: email },
|
||||
{ phone: phone }
|
||||
]
|
||||
}
|
||||
})
|
||||
|
||||
if (existingUser) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '用户名已存在'
|
||||
message: '用户名、邮箱或手机号已存在'
|
||||
})
|
||||
}
|
||||
|
||||
// 检查邮箱是否已存在
|
||||
if (users.find(u => u.email === email)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '邮箱已存在'
|
||||
})
|
||||
}
|
||||
// 密码加密
|
||||
const saltRounds = 10
|
||||
const password_hash = await bcrypt.hash(password, saltRounds)
|
||||
|
||||
// 创建新用户
|
||||
const newUser = {
|
||||
id: Math.max(...users.map(u => u.id)) + 1,
|
||||
const newUser = await ApiUser.create({
|
||||
username,
|
||||
email,
|
||||
phone: phone || '',
|
||||
role,
|
||||
password_hash,
|
||||
user_type,
|
||||
status,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString()
|
||||
}
|
||||
|
||||
users.push(newUser)
|
||||
})
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
@@ -180,6 +148,7 @@ router.post('/', async (req, res) => {
|
||||
data: newUser
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('创建用户失败:', error)
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '创建用户失败'
|
||||
@@ -188,12 +157,12 @@ router.post('/', async (req, res) => {
|
||||
})
|
||||
|
||||
// 更新用户
|
||||
router.put('/:id', (req, res) => {
|
||||
router.put('/:id', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params
|
||||
const userIndex = users.findIndex(u => u.id === parseInt(id))
|
||||
const user = await ApiUser.findByPk(id)
|
||||
|
||||
if (userIndex === -1) {
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '用户不存在'
|
||||
@@ -211,18 +180,15 @@ router.put('/:id', (req, res) => {
|
||||
}
|
||||
|
||||
// 更新用户信息
|
||||
users[userIndex] = {
|
||||
...users[userIndex],
|
||||
...value,
|
||||
updatedAt: new Date().toISOString()
|
||||
}
|
||||
await user.update(value)
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '用户更新成功',
|
||||
data: users[userIndex]
|
||||
data: user
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('更新用户失败:', error)
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '更新用户失败'
|
||||
@@ -231,25 +197,26 @@ router.put('/:id', (req, res) => {
|
||||
})
|
||||
|
||||
// 删除用户
|
||||
router.delete('/:id', (req, res) => {
|
||||
router.delete('/:id', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params
|
||||
const userIndex = users.findIndex(u => u.id === parseInt(id))
|
||||
const user = await ApiUser.findByPk(id)
|
||||
|
||||
if (userIndex === -1) {
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '用户不存在'
|
||||
})
|
||||
}
|
||||
|
||||
users.splice(userIndex, 1)
|
||||
await user.destroy()
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '用户删除成功'
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('删除用户失败:', error)
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '删除用户失败'
|
||||
@@ -258,7 +225,7 @@ router.delete('/:id', (req, res) => {
|
||||
})
|
||||
|
||||
// 批量删除用户
|
||||
router.delete('/batch', (req, res) => {
|
||||
router.delete('/batch', async (req, res) => {
|
||||
try {
|
||||
const { ids } = req.body
|
||||
|
||||
@@ -269,13 +236,18 @@ router.delete('/batch', (req, res) => {
|
||||
})
|
||||
}
|
||||
|
||||
users = users.filter(user => !ids.includes(user.id))
|
||||
await ApiUser.destroy({
|
||||
where: {
|
||||
id: ids
|
||||
}
|
||||
})
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: `成功删除 ${ids.length} 个用户`
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('批量删除用户失败:', error)
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '批量删除用户失败'
|
||||
@@ -289,8 +261,8 @@ router.put('/:id/password', async (req, res) => {
|
||||
const { id } = req.params
|
||||
const { password } = req.body
|
||||
|
||||
const userIndex = users.findIndex(u => u.id === parseInt(id))
|
||||
if (userIndex === -1) {
|
||||
const user = await ApiUser.findByPk(id)
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '用户不存在'
|
||||
@@ -304,14 +276,19 @@ router.put('/:id/password', async (req, res) => {
|
||||
})
|
||||
}
|
||||
|
||||
// 在实际项目中,这里会对密码进行加密
|
||||
users[userIndex].updatedAt = new Date().toISOString()
|
||||
// 密码加密
|
||||
const saltRounds = 10
|
||||
const password_hash = await bcrypt.hash(password, saltRounds)
|
||||
|
||||
// 更新密码
|
||||
await user.update({ password_hash })
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '密码重置成功'
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('重置密码失败:', error)
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '重置密码失败'
|
||||
@@ -320,35 +297,35 @@ router.put('/:id/password', async (req, res) => {
|
||||
})
|
||||
|
||||
// 更新用户状态
|
||||
router.put('/:id/status', (req, res) => {
|
||||
router.put('/:id/status', async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params
|
||||
const { status } = req.body
|
||||
|
||||
const userIndex = users.findIndex(u => u.id === parseInt(id))
|
||||
if (userIndex === -1) {
|
||||
const user = await ApiUser.findByPk(id)
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '用户不存在'
|
||||
})
|
||||
}
|
||||
|
||||
if (!['active', 'inactive', 'banned'].includes(status)) {
|
||||
if (!['active', 'inactive', 'locked'].includes(status)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '无效的用户状态'
|
||||
})
|
||||
}
|
||||
|
||||
users[userIndex].status = status
|
||||
users[userIndex].updatedAt = new Date().toISOString()
|
||||
await user.update({ status })
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '用户状态更新成功',
|
||||
data: users[userIndex]
|
||||
data: user
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('更新用户状态失败:', error)
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '更新用户状态失败'
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
const User = require('../models/User');
|
||||
|
||||
// 创建初始用户数据
|
||||
const createInitialUsers = async () => {
|
||||
try {
|
||||
// 检查是否已存在用户
|
||||
const existingAdmin = await User.findOne({ where: { username: 'admin' } });
|
||||
if (existingAdmin) {
|
||||
console.log('✅ 初始用户已存在,跳过创建');
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建管理员用户
|
||||
const adminUser = await User.createUser({
|
||||
username: 'admin',
|
||||
password: 'admin123',
|
||||
phone: '13800138000',
|
||||
email: 'admin@niumall.com',
|
||||
real_name: '系统管理员',
|
||||
user_type: 'admin'
|
||||
});
|
||||
|
||||
// 创建采购人用户
|
||||
const buyerUser = await User.createUser({
|
||||
username: 'buyer',
|
||||
password: 'buyer123',
|
||||
phone: '13800138001',
|
||||
email: 'buyer@niumall.com',
|
||||
real_name: '采购经理',
|
||||
user_type: 'client'
|
||||
});
|
||||
|
||||
// 创建贸易商用户
|
||||
const traderUser = await User.createUser({
|
||||
username: 'trader',
|
||||
password: 'trader123',
|
||||
phone: '13800138002',
|
||||
email: 'trader@niumall.com',
|
||||
real_name: '贸易商经理',
|
||||
user_type: 'staff'
|
||||
});
|
||||
|
||||
// 创建供应商用户
|
||||
const supplierUser = await User.createUser({
|
||||
username: 'supplier',
|
||||
password: 'supplier123',
|
||||
phone: '13800138003',
|
||||
email: 'supplier@niumall.com',
|
||||
real_name: '供应商代表',
|
||||
user_type: 'supplier'
|
||||
});
|
||||
|
||||
// 创建司机用户
|
||||
const driverUser = await User.createUser({
|
||||
username: 'driver',
|
||||
password: 'driver123',
|
||||
phone: '13800138004',
|
||||
email: 'driver@niumall.com',
|
||||
real_name: '运输司机',
|
||||
user_type: 'driver'
|
||||
});
|
||||
|
||||
console.log('✅ 初始用户创建成功');
|
||||
console.log('👤 用户账号信息:');
|
||||
console.log(' 管理员: admin / admin123');
|
||||
console.log(' 采购人: buyer / buyer123');
|
||||
console.log(' 贸易商: trader / trader123');
|
||||
console.log(' 供应商: supplier / supplier123');
|
||||
console.log(' 司机: driver / driver123');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 创建初始用户失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
createInitialUsers
|
||||
};
|
||||
Reference in New Issue
Block a user