From 4e852bd3cb6d9b64fd0074600f7d4ffab704a92f Mon Sep 17 00:00:00 2001 From: aiotagro Date: Thu, 18 Sep 2025 16:47:08 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=90=8E=E7=AB=AFAPI?= =?UTF-8?q?=E7=AB=AF=E5=8F=A3=E5=92=8C=E8=AE=A4=E8=AF=81=E7=B3=BB=E7=BB=9F?= =?UTF-8?q?=EF=BC=8C=E4=BC=98=E5=8C=96=E6=95=B0=E6=8D=AE=E5=BA=93=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E5=92=8C=E7=8E=AF=E5=A2=83=E5=8F=98=E9=87=8F=E7=AE=A1?= =?UTF-8?q?=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- admin-system/.env.development | 8 +- backend/.env | 2 +- backend/add_demo_users.js | 92 ++++++++ backend/docs/openapi.json | 262 ++++++++++++++++++++++ backend/src/config/config.js | 10 +- backend/src/controllers/AuthController.js | 98 +++++++- backend/src/main.js | 16 +- backend/src/routes/auth.js | 3 + test_login.js | 56 +++++ up_website.sh | 2 +- 10 files changed, 534 insertions(+), 15 deletions(-) create mode 100644 backend/add_demo_users.js create mode 100644 backend/docs/openapi.json create mode 100644 test_login.js diff --git a/admin-system/.env.development b/admin-system/.env.development index de41971..274c294 100644 --- a/admin-system/.env.development +++ b/admin-system/.env.development @@ -5,16 +5,16 @@ NODE_ENV=development VITE_APP_TITLE=活牛采购智能数字化系统 - 管理后台 # API接口地址 -VITE_API_BASE_URL=http://localhost:3002/api +VITE_API_BASE_URL=http://localhost:4330/api # WebSocket地址 -VITE_WS_BASE_URL=ws://localhost:3002 +VITE_WS_BASE_URL=ws://localhost:4330 # 上传文件地址 -VITE_UPLOAD_URL=http://localhost:3002/api/upload +VITE_UPLOAD_URL=http://localhost:4330/api/upload # 静态资源地址 -VITE_STATIC_URL=http://localhost:3002/static +VITE_STATIC_URL=http://localhost:4330/static # 是否启用Mock数据 VITE_USE_MOCK=false diff --git a/backend/.env b/backend/.env index c1469ea..91b9c9d 100644 --- a/backend/.env +++ b/backend/.env @@ -10,7 +10,7 @@ JWT_SECRET=niumall_jwt_secret_key_2024 JWT_EXPIRES_IN=24h # 应用配置 -NODE_ENV=production +NODE_ENV=development PORT=4330 API_PREFIX=/api diff --git a/backend/add_demo_users.js b/backend/add_demo_users.js new file mode 100644 index 0000000..06d4e8a --- /dev/null +++ b/backend/add_demo_users.js @@ -0,0 +1,92 @@ +// 添加演示账号到数据库的脚本 +const { sequelize, ApiUser } = require('./models'); +const bcrypt = require('bcryptjs'); + +// 演示账号数据 +const demoUsers = [ + { + username: 'admin', + password: 'admin123', + phone: '13800138001', + email: 'admin@niumall.com', + user_type: 'admin', + status: 'active' + }, + { + username: 'buyer', + password: 'buyer123', + phone: '13800138002', + email: 'buyer@niumall.com', + user_type: 'client', + status: 'active' + }, + { + username: 'trader', + password: 'trader123', + phone: '13800138003', + email: 'trader@niumall.com', + user_type: 'supplier', + status: 'active' + } +]; + +// 添加或更新演示账号 +const setupDemoUsers = async () => { + try { + // 测试数据库连接 + await sequelize.authenticate(); + console.log('✅ 数据库连接成功'); + + // 为每个演示账号创建或更新记录 + for (const userData of demoUsers) { + // 尝试通过用户名查找用户 + let user = await ApiUser.findOne({ + where: { username: userData.username } + }); + + // 加密密码 + const salt = await bcrypt.genSalt(10); + const passwordHash = await bcrypt.hash(userData.password, salt); + + try { + if (user) { + // 用户存在,更新信息,但保留现有手机号(避免唯一约束冲突) + await user.update({ + password_hash: passwordHash, + email: userData.email, + user_type: userData.user_type, + status: userData.status + // 不更新phone字段,避免唯一约束冲突 + }); + console.log(`✅ 成功更新用户: ${userData.username} (${userData.user_type})`); + } else { + // 用户不存在,创建新用户 + await ApiUser.create({ + ...userData, + password_hash: passwordHash + }); + console.log(`✅ 成功创建用户: ${userData.username} (${userData.user_type})`); + } + } catch (error) { + // 处理可能的唯一约束冲突或其他错误 + if (error.name === 'SequelizeUniqueConstraintError') { + console.log(`⚠️ 无法创建用户 ${userData.username}: 用户名或手机号已被使用`); + } else { + console.error(`❌ 处理用户 ${userData.username} 时出错:`, error.message); + } + } + } + + console.log('✅ 所有演示账号设置完成'); + } catch (error) { + console.error('❌ 设置演示账号失败:', error.message); + // 输出完整错误信息便于调试 + console.error(error); + } finally { + // 关闭数据库连接 + await sequelize.close(); + } +}; + +// 执行脚本 +setupDemoUsers(); \ No newline at end of file diff --git a/backend/docs/openapi.json b/backend/docs/openapi.json new file mode 100644 index 0000000..366d590 --- /dev/null +++ b/backend/docs/openapi.json @@ -0,0 +1,262 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "NiuMall 管理系统 API", + "description": "NiuMall 管理系统后端API文档", + "version": "1.0.0" + }, + "servers": [ + { + "url": "http://localhost:4330/api", + "description": "本地开发环境" + } + ], + "paths": { + "/auth/login": { + "post": { + "summary": "用户登录", + "description": "管理员用户登录认证接口,支持通过用户名或邮箱登录", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "required": ["username", "password"], + "properties": { + "username": { + "type": "string", + "description": "用户名或邮箱" + }, + "password": { + "type": "string", + "description": "用户密码" + } + }, + "example": { + "username": "admin", + "password": "123456" + } + } + } + } + }, + "responses": { + "200": { + "description": "登录成功", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": true + }, + "message": { + "type": "string", + "example": "登录成功" + }, + "access_token": { + "type": "string", + "description": "JWT访问令牌" + }, + "token_type": { + "type": "string", + "example": "Bearer" + }, + "expires_in": { + "type": "integer", + "description": "令牌过期时间(分钟)", + "example": 1440 + }, + "user": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "example": 1 + }, + "username": { + "type": "string", + "example": "admin" + }, + "email": { + "type": "string", + "example": "admin@niumall.com" + }, + "phone": { + "type": "string", + "example": "13800138000" + }, + "role": { + "type": "string", + "example": "admin" + }, + "status": { + "type": "string", + "example": "active" + } + } + } + } + } + } + } + }, + "400": { + "description": "参数错误", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": false + }, + "message": { + "type": "string", + "example": "用户名和密码不能为空" + } + } + } + } + } + }, + "401": { + "description": "认证失败", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": false + }, + "message": { + "type": "string", + "example": "用户名或密码错误" + } + } + } + } + } + }, + "500": { + "description": "服务器错误", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": false + }, + "message": { + "type": "string", + "example": "登录失败,请稍后再试" + } + } + } + } + } + } + } + } + }, + "/auth/current": { + "get": { + "summary": "获取当前用户信息", + "description": "获取当前登录用户的详细信息,需要JWT认证", + "security": [ + { + "bearerAuth": [] + } + ], + "responses": { + "200": { + "description": "获取成功", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": true + }, + "message": { + "type": "string", + "example": "获取用户信息成功" + }, + "data": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "example": 1 + }, + "username": { + "type": "string", + "example": "admin" + }, + "email": { + "type": "string", + "example": "admin@niumall.com" + }, + "phone": { + "type": "string", + "example": "13800138000" + }, + "user_type": { + "type": "string", + "example": "admin" + }, + "status": { + "type": "string", + "example": "active" + } + } + } + } + } + } + } + }, + "401": { + "description": "未认证", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { + "type": "boolean", + "example": false + }, + "message": { + "type": "string", + "example": "未授权,请登录" + } + } + } + } + } + } + } + } + } + }, + "components": { + "securitySchemes": { + "bearerAuth": { + "type": "http", + "scheme": "bearer", + "bearerFormat": "JWT" + } + } + } +} \ No newline at end of file diff --git a/backend/src/config/config.js b/backend/src/config/config.js index ca0190a..2e90bef 100644 --- a/backend/src/config/config.js +++ b/backend/src/config/config.js @@ -1,11 +1,11 @@ // 数据库配置 const dbConfig = { development: { - username: 'root', - password: 'password', - database: 'niumall_dev', - host: '127.0.0.1', - port: 3306, + username: process.env.DB_USERNAME || 'root', + password: process.env.DB_PASSWORD || 'password', + database: process.env.DB_NAME || 'niumall_dev', + host: process.env.DB_HOST || '127.0.0.1', + port: process.env.DB_PORT || 3306, dialect: 'mysql' }, production: { diff --git a/backend/src/controllers/AuthController.js b/backend/src/controllers/AuthController.js index 1f94bea..531642b 100644 --- a/backend/src/controllers/AuthController.js +++ b/backend/src/controllers/AuthController.js @@ -3,7 +3,98 @@ const bcrypt = require('bcryptjs'); const { v4: uuidv4 } = require('uuid'); const { successResponse, errorResponse } = require('../utils/response'); const { jwtConfig } = require('../config/config'); -const User = require('../models/User'); +const { ApiUser } = require('../../models'); +const jsonwebtoken = require('jsonwebtoken'); +const { Op } = require('sequelize'); + +// 传统用户登录 - 管理系统使用 +const login = async (req, res) => { + try { + const { username, password } = req.body; + + // 验证参数 + if (!username || !password) { + return res.status(400).json(errorResponse('用户名和密码不能为空', 400)); + } + + // 查找用户 - 支持通过用户名或邮箱登录 + let user = await ApiUser.findOne({ + where: { + [Op.or]: [ + { username }, + { email: username } + ] + } + }); + + // 检查用户是否存在 + if (!user) { + return res.status(401).json(errorResponse('用户名或密码错误', 401)); + } + + // 验证密码 + const isPasswordValid = await bcrypt.compare(password, user.password_hash); + if (!isPasswordValid) { + return res.status(401).json(errorResponse('用户名或密码错误', 401)); + } + + // 检查用户状态 + if (user.status !== 'active') { + return res.status(401).json(errorResponse('用户账号已被禁用', 401)); + } + + // 生成JWT token + const token = jsonwebtoken.sign( + { + id: user.id, + uuid: user.uuid, + username: user.username, + userType: user.user_type, + email: user.email + }, + jwtConfig.secret, + { expiresIn: jwtConfig.expiresIn } + ); + + // 准备返回的用户信息 + const userInfo = { + id: user.id, + username: user.username, + email: user.email, + user_type: user.user_type, + status: user.status, + phone: user.phone + }; + + // 返回响应 - 包含data字段以匹配前端期望的格式 + res.json({ + success: true, + message: '登录成功', + data: { + access_token: token, + token_type: 'Bearer', + expires_in: parseInt(jwtConfig.expiresIn) * 60, // 转换为秒 + user: { + id: user.id, + username: user.username, + email: user.email, + phone: user.phone, + avatar: user.avatar, + role: user.user_type, + status: user.status, + createdAt: user.created_at, + updatedAt: user.updated_at + } + } + }); + } catch (error) { + console.error('用户登录失败:', error); + res.status(500).json({ + success: false, + message: '登录失败,请稍后再试' + }); + } +}; // 小程序用户登录 const miniProgramLogin = async (req, res) => { @@ -30,7 +121,7 @@ const miniProgramLogin = async (req, res) => { } // 生成JWT token - const token = jwt.sign( + const token = jsonwebtoken.sign( { id: user.id, uuid: user.uuid, @@ -62,7 +153,7 @@ const miniProgramLogin = async (req, res) => { // 获取当前用户信息 const getCurrentUser = async (req, res) => { try { - const user = await User.findByPk(req.user.id); + const user = await ApiUser.findByPk(req.user.id); if (!user) { return res.status(404).json(errorResponse('用户不存在', 404)); @@ -86,6 +177,7 @@ const getCurrentUser = async (req, res) => { }; module.exports = { + login, miniProgramLogin, getCurrentUser }; \ No newline at end of file diff --git a/backend/src/main.js b/backend/src/main.js index 00993fd..fe69d13 100644 --- a/backend/src/main.js +++ b/backend/src/main.js @@ -1,3 +1,6 @@ +// 加载.env文件 +require('dotenv').config(); + const express = require('express'); const cors = require('cors'); const helmet = require('helmet'); @@ -5,6 +8,15 @@ const morgan = require('morgan'); const swaggerUi = require('swagger-ui-express'); const YAML = require('yamljs'); +// 打印环境变量用于调试 +console.log('Environment variables:'); +console.log('DB_HOST:', process.env.DB_HOST); +console.log('DB_PORT:', process.env.DB_PORT); +console.log('DB_USERNAME:', process.env.DB_USERNAME); +console.log('DB_NAME:', process.env.DB_NAME); +console.log('NODE_ENV:', process.env.NODE_ENV); +console.log('PORT:', process.env.PORT); + // 数据库连接 const sequelize = require('./config/database'); @@ -57,10 +69,12 @@ app.use(errorHandler); // 同步数据库模型 const syncDatabase = async () => { try { - await sequelize.sync({ alter: true }); + // 不修改现有表结构,只创建不存在的表 + await sequelize.sync({ force: false }); console.log('数据库模型同步成功'); } catch (error) { console.error('数据库模型同步失败:', error); + console.log('服务器仍然会继续运行,但某些功能可能受到影响'); } }; diff --git a/backend/src/routes/auth.js b/backend/src/routes/auth.js index 3f33bac..6f5bbd0 100644 --- a/backend/src/routes/auth.js +++ b/backend/src/routes/auth.js @@ -3,6 +3,9 @@ const router = express.Router(); const AuthController = require('../controllers/AuthController'); const { authenticate } = require('../middleware/auth'); +// 传统用户登录 - 管理系统使用 +router.post('/login', AuthController.login); + // 小程序用户登录 router.post('/mini-program/login', AuthController.miniProgramLogin); diff --git a/test_login.js b/test_login.js new file mode 100644 index 0000000..1f46725 --- /dev/null +++ b/test_login.js @@ -0,0 +1,56 @@ +const http = require('http'); + +// 测试登录接口 +function testLogin() { + const postData = JSON.stringify({ + username: 'admin', + password: 'admin123' + }); + + const options = { + hostname: 'localhost', + port: 4330, + path: '/api/auth/login', + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Content-Length': Buffer.byteLength(postData) + } + }; + + console.log('发送登录请求到:', options.hostname + ':' + options.port + options.path); + console.log('请求数据:', postData); + + const req = http.request(options, (res) => { + console.log(`状态码: ${res.statusCode}`); + console.log('响应头:', res.headers); + + let responseData = ''; + + res.on('data', (chunk) => { + responseData += chunk; + }); + + res.on('end', () => { + console.log('完整响应体:', responseData); + try { + const parsedData = JSON.parse(responseData); + console.log('解析后的响应数据:', JSON.stringify(parsedData)); + console.log('是否包含access_token:', 'access_token' in parsedData); + console.log('是否包含data字段:', 'data' in parsedData); + } catch (e) { + console.log('响应数据解析错误:', e.message); + } + }); + }); + + req.on('error', (error) => { + console.error('请求失败:', error); + }); + + // 发送请求体 + req.write(postData); + req.end(); +} + +testLogin(); \ No newline at end of file diff --git a/up_website.sh b/up_website.sh index 528ef51..02ffc1d 100755 --- a/up_website.sh +++ b/up_website.sh @@ -5,7 +5,7 @@ LOCAL_DIR="/Users/aiotagro/vue/niumall/website" REMOTE_USER="root" REMOTE_HOST="49.51.70.206" REMOTE_DIR="/data/website/niumall" -REMOTE_PASSWORD="Aiotjkl$7kl6756" +REMOTE_PASSWORD="Aiotjkl$758adfa@" # 检查本地目录是否存在 if [ ! -d "$LOCAL_DIR" ]; then