+
+
+ 管理后台
diff --git a/admin-system/vite.config.ts b/admin-system/vite.config.ts index 171f3d6..fb1561b 100644 --- a/admin-system/vite.config.ts +++ b/admin-system/vite.config.ts @@ -95,7 +95,7 @@ export default defineConfig(({ mode }) => { css: { preprocessorOptions: { scss: { - additionalData: `@import "@/assets/styles/variables.scss"; @import "@/assets/styles/mixins.scss";` + additionalData: `@import "@/style/variables.scss"; @import "@/style/mixins.scss";` } } } diff --git a/backend/.env b/backend/.env new file mode 100644 index 0000000..93b4f89 --- /dev/null +++ b/backend/.env @@ -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 \ No newline at end of file diff --git a/backend/.env.development b/backend/.env.development deleted file mode 100644 index 61a2c04..0000000 --- a/backend/.env.development +++ /dev/null @@ -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 \ No newline at end of file diff --git a/backend/app.js b/backend/app.js index c4c257a..d749312 100644 --- a/backend/app.js +++ b/backend/app.js @@ -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(); \ No newline at end of file +startServer() diff --git a/backend/config/database.js b/backend/config/database.js index 322686f..c24a056 100644 --- a/backend/config/database.js +++ b/backend/config/database.js @@ -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 + } + } }; \ No newline at end of file diff --git a/backend/middleware/auth.js b/backend/middleware/auth.js deleted file mode 100644 index 72f05e2..0000000 --- a/backend/middleware/auth.js +++ /dev/null @@ -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 -}; \ No newline at end of file diff --git a/backend/models/User.js b/backend/models/User.js deleted file mode 100644 index 5fed7d1..0000000 --- a/backend/models/User.js +++ /dev/null @@ -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; \ No newline at end of file diff --git a/backend/models/index.js b/backend/models/index.js new file mode 100644 index 0000000..3066260 --- /dev/null +++ b/backend/models/index.js @@ -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 +}; \ No newline at end of file diff --git a/backend/package.json b/backend/package.json index c504e63..794c5b5 100644 --- a/backend/package.json +++ b/backend/package.json @@ -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", diff --git a/backend/routes/auth.js b/backend/routes/auth.js index 3838164..56f44a2 100644 --- a/backend/routes/auth.js +++ b/backend/routes/auth.js @@ -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; \ No newline at end of file +// 用户登出 +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 \ No newline at end of file diff --git a/backend/routes/users.js b/backend/routes/users.js index 35aaab6..e94dc33 100644 --- a/backend/routes/users.js +++ b/backend/routes/users.js @@ -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: '更新用户状态失败' diff --git a/backend/scripts/initData.js b/backend/scripts/initData.js deleted file mode 100644 index 1908337..0000000 --- a/backend/scripts/initData.js +++ /dev/null @@ -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 -}; \ No newline at end of file