const express = require('express'); const bodyParser = require('body-parser'); const cors = require('cors'); const mysql = require('mysql2/promise'); const path = require('path'); const http = require('http'); const WebSocket = require('ws'); const OssUploader = require('./oss-uploader'); const app = express(); const PORT = 3000; // 创建HTTP服务器 const server = http.createServer(app); // 创建WebSocket服务器 const wss = new WebSocket.Server({ server }); // 客户端连接集合 const clients = new Set(); // WebSocket连接处理 wss.on('connection', (ws) => { console.log('WebSocket客户端已连接'); // 将新连接的客户端添加到集合中 clients.add(ws); // 接收消息处理 ws.on('message', (message) => { try { const data = JSON.parse(message); console.log('收到WebSocket消息:', data); // 处理不同类型的消息 switch (data.type) { case 'login': // 登录消息,存储用户信息 ws.userId = data.userId; console.log(`用户 ${data.userId} 已登录`); break; case 'pong': // 心跳响应,无需处理 break; default: console.log('未知消息类型:', data.type); } } catch (error) { console.error('解析WebSocket消息失败:', error); } }); // 连接关闭处理 ws.on('close', () => { console.log('WebSocket客户端已断开连接'); clients.delete(ws); }); // 连接错误处理 ws.on('error', (error) => { console.error('WebSocket连接错误:', error); }); }); // 广播消息给所有客户端 function broadcastMessage(message) { const messageStr = JSON.stringify(message); clients.forEach((client) => { if (client.readyState === WebSocket.OPEN) { client.send(messageStr); } }); } // 广播消息给特定用户 function broadcastToUser(userId, message) { const messageStr = JSON.stringify(message); clients.forEach((client) => { if (client.readyState === WebSocket.OPEN && client.userId === userId) { client.send(messageStr); } }); } // 启动心跳机制,每30秒发送一次ping消息 setInterval(() => { broadcastMessage({ type: 'ping' }); // console.log('发送心跳消息: ping'); }, 30000); // 自动下架任务 - 每分钟检查一次 setInterval(async () => { try { console.log('检查需要自动下架的商品...'); const connection = await pool.getConnection(); const currentTime = new Date(); // 查找所有状态为published的商品 const [publishedProducts] = await connection.query( 'SELECT id, autoOfflineTime, autoOfflineHours, created_at, updated_at FROM products WHERE status = ?', ['published'] ); const productsToOffline = []; // 检查每个商品是否需要自动下架 for (const product of publishedProducts) { let shouldOffline = false; if (product.autoOfflineTime) { // 情况1:有明确的autoOfflineTime const autoOfflineTime = new Date(product.autoOfflineTime); if (currentTime >= autoOfflineTime) { shouldOffline = true; } } else if (product.autoOfflineHours && !isNaN(product.autoOfflineHours) && product.autoOfflineHours > 0) { // 情况2:只有autoOfflineHours,计算自动下架时间 const baseTime = product.updated_at ? new Date(product.updated_at) : new Date(product.created_at); const offlineTime = new Date(baseTime.getTime() + product.autoOfflineHours * 60 * 60 * 1000); if (currentTime >= offlineTime) { shouldOffline = true; } } if (shouldOffline) { productsToOffline.push(product.id); } } if (productsToOffline.length > 0) { console.log(`发现${productsToOffline.length}个商品需要自动下架`); // 批量更新商品状态为sold_out for (const productId of productsToOffline) { await connection.query( 'UPDATE products SET status = ?, label = 1, updated_at = NOW() WHERE id = ?', ['sold_out', productId] ); // 发送WebSocket消息通知所有客户端 broadcastMessage({ type: 'supply_status_change', supplyId: productId, action: 'unpublish', status: 'sold_out' }); } console.log('自动下架任务完成'); } connection.release(); } catch (error) { console.error('自动下架任务失败:', error.message); } }, 60000); // 每分钟检查一次 // 配置CORS app.use(cors()); app.use((req, res, next) => { res.header('Access-Control-Allow-Origin', '*'); res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization'); if (req.method === 'OPTIONS') { res.status(200).end(); return; } next(); }); app.use(bodyParser.json({ limit: '100mb' })); app.use(express.static(path.join(__dirname))); // 数据库配置 - 从环境变量获取,与docker-compose.yml保持一致 const dbConfig = { host: process.env.DB_HOST || '1.95.162.61', user: process.env.DB_USER || 'root', password: process.env.DB_PASSWORD || 'schl@2025', database: process.env.DB_NAME || 'wechat_app', waitForConnections: true, connectionLimit: 20, // 增加连接池大小,提高并发处理能力 queueLimit: 0, connectTimeout: 10000, // 增加连接超时时间(mysql2支持的选项) timezone: '+08:00' // 设置为北京时间时区 }; // userlogin数据库配置 - 从环境变量获取 const userLoginDbConfig = { host: process.env.DB_HOST || '1.95.162.61', user: process.env.DB_USER || 'root', password: process.env.DB_PASSWORD || 'schl@2025', database: process.env.USER_LOGIN_DB_NAME || 'userlogin', waitForConnections: true, connectionLimit: 20, // 增加连接池大小 queueLimit: 0, connectTimeout: 10000, // 增加连接超时时间(mysql2支持的选项) timezone: '+08:00' // 设置为北京时间时区 }; // 创建数据库连接池 let pool; let userLoginPool; // 初始化数据库连接 async function initDatabase() { try { pool = mysql.createPool(dbConfig); console.log('wechat_app数据库连接池创建成功'); // 初始化userlogin数据库连接池 userLoginPool = mysql.createPool(userLoginDbConfig); console.log('userlogin数据库连接池创建成功'); // 测试wechat_app连接 const connection = await pool.getConnection(); console.log('wechat_app数据库连接测试成功'); connection.release(); // 测试userlogin连接 const userLoginConnection = await userLoginPool.getConnection(); console.log('userlogin数据库连接测试成功'); userLoginConnection.release(); // 确保数据库结构 await ensureDatabaseSchema(); } catch (error) { console.error('数据库初始化失败:', error.message); console.error('错误详情:', error); // 如果初始化失败,尝试重新初始化 setTimeout(() => { console.log('尝试重新初始化数据库连接...'); initDatabase(); }, 5000); } } // 通用响应函数 function sendResponse(res, success, data = null, message = '') { res.json({ success, data, message }); } // 导出函数供测试使用 module.exports.sendResponse = sendResponse; // 获取货源列表API app.get('/api/supplies', async (req, res) => { console.log('收到获取货源列表请求:', req.query); try { const connection = await pool.getConnection(); // 支持search和keyword两种参数名,确保兼容性 const { page = 1, pageSize = 10, search = '', keyword = '', status = '', phoneNumber = '' } = req.query; // 如果提供了keyword参数,优先使用keyword const actualSearch = keyword || search; const offset = (page - 1) * pageSize; // 构建基础查询,添加LEFT JOIN获取用户信息 let query = 'SELECT p.*, u.phoneNumber, u.nickName FROM products p LEFT JOIN users u ON p.sellerId = u.userId'; let countQuery = 'SELECT COUNT(*) as total FROM products p LEFT JOIN users u ON p.sellerId = u.userId'; let whereClause = ''; let params = []; // 默认过滤掉status为hidden的货源,实现软删除效果 whereClause += ` WHERE status != 'hidden'`; // 添加搜索条件 if (actualSearch) { whereClause += ` AND (p.id LIKE ? OR p.productId LIKE ? OR p.productName LIKE ?)`; params.push(`%${actualSearch}%`, `%${actualSearch}%`, `%${actualSearch}%`); } // 添加手机号搜索 if (phoneNumber) { whereClause += ` AND u.phoneNumber LIKE ?`; params.push(`%${phoneNumber}%`); } // 添加状态筛选 if (status) { whereClause += ` AND status = ?`; params.push(status); } // 添加sellerId筛选,只返回指定用户的货源 const { sellerId } = req.query; if (sellerId) { whereClause += ` AND p.sellerId = ?`; params.push(sellerId); } // 执行查询 const [results] = await connection.query( `${query}${whereClause} ORDER BY p.id DESC LIMIT ? OFFSET ?`, [...params, parseInt(pageSize), offset] ); // 获取总数 const [countResults] = await connection.query( `${countQuery}${whereClause}`, params ); connection.release(); // 处理返回结果中的imageUrls字段 const processedResults = results.map(product => { // 处理imageUrls字段 let imageUrls = []; if (product.imageUrls) { if (typeof product.imageUrls === 'string') { // 尝试解析为JSON数组 try { let parsedImages = JSON.parse(product.imageUrls); // 检查是否是JSON字符串的字符串表示(转义的JSON) if (typeof parsedImages === 'string' && (parsedImages.startsWith('[') || parsedImages.startsWith('{'))) { // 进行第二次解析 parsedImages = JSON.parse(parsedImages); } if (Array.isArray(parsedImages)) { imageUrls = parsedImages; } else if (typeof parsedImages === 'string') { // 如果解析结果是字符串,可能是单个URL imageUrls = [parsedImages]; } } catch (e) { // 解析失败,尝试按逗号分隔 if (product.imageUrls.includes(',')) { imageUrls = product.imageUrls.split(',').map(url => url.trim()); } else { // 作为单个URL处理 imageUrls = [product.imageUrls.trim()]; } } } else if (Array.isArray(product.imageUrls)) { // 已经是数组,直接使用 imageUrls = product.imageUrls; } else { // 其他类型,转换为字符串数组 imageUrls = [String(product.imageUrls)]; } } // 过滤并处理无效的URL:移除反引号并验证 imageUrls = imageUrls .filter(url => { if (!url) return false; const processedUrl = url.replace(/`/g, '').trim(); return processedUrl.startsWith('http://') || processedUrl.startsWith('https://'); }) // 对每个有效URL进行处理,移除反引号 .map(url => url.replace(/`/g, '').trim()); return { ...product, imageUrls }; }); // 返回结果 sendResponse(res, true, { list: processedResults, total: countResults[0].total, page: parseInt(page), pageSize: parseInt(pageSize) }, '获取货源列表成功'); } catch (error) { console.error('获取货源列表失败:', error.message); console.error('错误详情:', error); sendResponse(res, false, null, '获取货源列表失败'); } }); // 审核通过API app.post('/api/supplies/:id/approve', async (req, res) => { console.log('收到审核通过请求:', req.params.id, req.body); try { const connection = await pool.getConnection(); const { remark = '' } = req.body; const productId = req.params.id; // 开始事务 await connection.beginTransaction(); // 检查当前状态 const [currentProduct] = await connection.query( 'SELECT status FROM products WHERE id = ?', [productId] ); if (currentProduct.length === 0) { await connection.rollback(); connection.release(); return sendResponse(res, false, null, '货源不存在'); } // 检查状态是否可审核,只允许审核中的状态进行审核操作 if (!['pending_review'].includes(currentProduct[0].status)) { await connection.rollback(); connection.release(); return sendResponse(res, false, null, '该货源已审核,无需重复操作'); } // 更新状态为已审核 await connection.query( 'UPDATE products SET status = ?, audit_time = ? WHERE id = ?', ['published', new Date(), productId] ); // 记录日志 await connection.query( 'INSERT INTO audit_logs (supply_id, action, user_id, remark, created_at) VALUES (?, ?, ?, ?, ?)', [productId, 'approve', 'system', remark, new Date()] ); // 提交事务 await connection.commit(); connection.release(); sendResponse(res, true, null, '审核通过成功'); } catch (error) { console.error('审核通过失败:', error.message); console.error('错误详情:', error); sendResponse(res, false, null, '审核通过失败'); } }); console.log('正在注册拒绝审核API路由: /api/supplies/:id/reject'); // 审核拒绝API app.post('/api/supplies/:id/reject', async (req, res) => { console.log('收到审核拒绝请求:', req.params.id, req.body); try { const connection = await pool.getConnection(); // 同时支持reason和rejectReason参数,保持向后兼容 const { reason, rejectReason = '', remark = '' } = req.body; // 如果有reason参数,则使用reason,否则使用rejectReason const actualRejectReason = reason || rejectReason; const productId = req.params.id; // 开始事务 await connection.beginTransaction(); // 检查当前状态 const [currentProduct] = await connection.query( 'SELECT status FROM products WHERE id = ?', [productId] ); if (currentProduct.length === 0) { await connection.rollback(); connection.release(); return sendResponse(res, false, null, '货源不存在'); } // 检查状态是否可审核,只允许审核中的状态进行审核操作 if (!['pending_review'].includes(currentProduct[0].status)) { await connection.rollback(); connection.release(); return sendResponse(res, false, null, '当前状态不允许审核拒绝'); } // 更新状态和拒绝理由 await connection.query( 'UPDATE products SET status = ?, rejectReason = ?, audit_time = ? WHERE id = ?', ['rejected', actualRejectReason, new Date(), productId] ); // 记录日志 await connection.query( 'INSERT INTO audit_logs (supply_id, action, user_id, remark, created_at) VALUES (?, ?, ?, ?, ?)', [productId, 'reject', 'system', remark, new Date()] ); // 提交事务 await connection.commit(); connection.release(); sendResponse(res, true, null, '审核拒绝成功'); } catch (error) { console.error('审核拒绝失败:', error.message); console.error('错误详情:', error); sendResponse(res, false, null, '审核拒绝失败'); } }); // 获取供应商列表API已删除 // 供应商审核通过API已删除 // 供应商审核拒绝API已删除 // 供应商开始合作API已删除 // 供应商终止合作API已删除 console.log('正在注册测试API路由: /api/test-db'); // 测试数据库连接API app.get('/api/test-db', async (req, res) => { console.log('收到数据库连接测试请求'); try { const connection = await pool.getConnection(); const [results] = await connection.query('SELECT 1 + 1 as solution'); connection.release(); sendResponse(res, true, results[0], '数据库连接成功'); } catch (error) { console.error('数据库连接测试失败:', error.message); console.error('错误详情:', error); sendResponse(res, false, null, '数据库连接测试失败'); } }); // 登录API app.post('/api/login', async (req, res) => { try { const { projectName, userName, password } = req.body; // 验证参数 if (!projectName || !userName || !password) { return sendResponse(res, false, null, '职位名称、用户名和密码不能为空'); } // 1. 验证职位名称是否为允许的类型 const allowedProjectNames = ['采购员', '管理员']; if (!allowedProjectNames.includes(projectName)) { return sendResponse(res, false, null, '仅允许采购员和管理员登录'); } // 2. 在login表中验证登录信息 const userLoginConnection = await userLoginPool.getConnection(); const [loginResult] = await userLoginConnection.query( 'SELECT id, projectName, userName, managerId FROM login WHERE projectName = ? AND userName = ? AND password = ?', [projectName, userName, password] ); if (loginResult.length === 0) { userLoginConnection.release(); return sendResponse(res, false, null, '职位名称、用户名或密码错误'); } const loginInfo = loginResult[0]; const { managerId } = loginInfo; // 2. 在personnel表中查询手机号码 const [personnelResult] = await userLoginConnection.query( 'SELECT phoneNumber, name FROM personnel WHERE managerId = ?', [managerId] ); userLoginConnection.release(); if (personnelResult.length === 0) { return sendResponse(res, false, null, '未找到对应的员工信息'); } const { phoneNumber, name } = personnelResult[0]; // 3. 在users表中查询userId const connection = await pool.getConnection(); const [userResult] = await connection.query( 'SELECT userId FROM users WHERE phoneNumber = ?', [phoneNumber] ); connection.release(); let userId = null; if (userResult.length > 0) { userId = userResult[0].userId; } // 4. 生成token(简单实现,实际项目中应使用JWT等安全机制) const token = `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; // 5. 返回登录结果 const userInfo = { id: loginInfo.id, projectName: loginInfo.projectName, userName: loginInfo.userName, managerId: loginInfo.managerId, name: name, phoneNumber: phoneNumber, userId: userId }; sendResponse(res, true, { userInfo, token }, '登录成功'); } catch (error) { console.error('登录失败:', error.message); console.error('错误详情:', error); sendResponse(res, false, null, '登录失败,请稍后重试'); } }); // 获取联系人数据API app.get('/api/contacts', async (req, res) => { try { // 直接从userlogin数据库的Personnel表查询联系人信息,只查询工位名为销售员的联系人 const userLoginConnection = await userLoginPool.getConnection(); const [personnelData] = await userLoginConnection.query( 'SELECT id, projectName, alias, phoneNumber FROM Personnel WHERE projectName = "销售员" AND phoneNumber IS NOT NULL AND phoneNumber != ""' ); userLoginConnection.release(); if (personnelData.length === 0) { sendResponse(res, true, [], '没有找到联系人信息'); return; } // 创建联系人数据数组,保持与原API相同的返回格式,使用实际的数据库ID const contacts = personnelData.map((person) => ({ id: person.id, // 使用数据库中的实际ID salesPerson: person.projectName, // 销售员 name: person.alias, // 联系人别名 phoneNumber: person.phoneNumber // 电话号码 })); sendResponse(res, true, contacts, '联系人数据获取成功'); } catch (error) { console.error('获取联系人数据失败:', error.message); console.error('错误详情:', error); sendResponse(res, false, null, '获取联系人数据失败'); } }); // 供应商审核通过API - /api/suppliers/:id/approve console.log('正在注册供应商审核通过API路由: /api/suppliers/:id/approve'); app.post('/api/suppliers/:id/approve', async (req, res) => { console.log('收到供应商审核通过请求:', req.params); try { const connection = await pool.getConnection(); const userId = req.params.id; if (!userId) { connection.release(); return sendResponse(res, false, null, '用户ID不能为空'); } // 开始事务 await connection.beginTransaction(); // 检查当前状态 const [currentUser] = await connection.query( 'SELECT partnerstatus FROM users WHERE userId = ?', [userId] ); if (currentUser.length === 0) { await connection.rollback(); connection.release(); return sendResponse(res, false, null, '供应商不存在'); } if (currentUser[0].partnerstatus !== 'underreview') { await connection.rollback(); connection.release(); return sendResponse(res, false, null, '当前状态不允许审核通过'); } // 更新状态和审核时间 await connection.query( 'UPDATE users SET partnerstatus = ?, audit_time = ? WHERE userId = ?', ['approved', new Date(), userId] ); // 提交事务 await connection.commit(); connection.release(); sendResponse(res, true, null, '供应商审核通过成功'); } catch (error) { console.error('供应商审核通过失败:', error.message); console.error('错误详情:', error); sendResponse(res, false, null, '供应商审核通过失败'); } }); // 供应商审核拒绝API - /api/suppliers/:id/reject console.log('正在注册供应商审核拒绝API路由: /api/suppliers/:id/reject'); app.post('/api/suppliers/:id/reject', async (req, res) => { console.log('收到供应商审核拒绝请求:', req.params, req.body); try { const connection = await pool.getConnection(); const userId = req.params.id; const { rejectReason } = req.body; if (!userId) { connection.release(); return sendResponse(res, false, null, '用户ID不能为空'); } if (!rejectReason) { connection.release(); return sendResponse(res, false, null, '审核失败原因不能为空'); } // 开始事务 await connection.beginTransaction(); // 检查当前状态 const [currentUser] = await connection.query( 'SELECT partnerstatus FROM users WHERE userId = ?', [userId] ); if (currentUser.length === 0) { await connection.rollback(); connection.release(); return sendResponse(res, false, null, '供应商不存在'); } if (currentUser[0].partnerstatus !== 'underreview') { await connection.rollback(); connection.release(); return sendResponse(res, false, null, '当前状态不允许审核拒绝'); } // 更新状态、审核失败原因和审核时间 await connection.query( 'UPDATE users SET partnerstatus = ?, reasonforfailure = ?, audit_time = ? WHERE userId = ?', ['reviewfailed', rejectReason, new Date(), userId] ); // 提交事务 await connection.commit(); connection.release(); sendResponse(res, true, null, '供应商审核拒绝成功'); } catch (error) { console.error('供应商审核拒绝失败:', error.message); console.error('错误详情:', error); sendResponse(res, false, null, '供应商审核拒绝失败'); } }); // 供应商开始合作API - /api/suppliers/:id/cooperate console.log('正在注册供应商开始合作API路由: /api/suppliers/:id/cooperate'); app.post('/api/suppliers/:id/cooperate', async (req, res) => { console.log('收到供应商开始合作请求:', req.params); try { const connection = await pool.getConnection(); const userId = req.params.id; if (!userId) { connection.release(); return sendResponse(res, false, null, '用户ID不能为空'); } // 开始事务 await connection.beginTransaction(); // 检查当前状态 const [currentUser] = await connection.query( 'SELECT partnerstatus FROM users WHERE userId = ?', [userId] ); if (currentUser.length === 0) { await connection.rollback(); connection.release(); return sendResponse(res, false, null, '供应商不存在'); } if (currentUser[0].partnerstatus !== 'approved') { await connection.rollback(); connection.release(); return sendResponse(res, false, null, '只有审核通过的供应商才能开始合作'); } // 更新状态 await connection.query( 'UPDATE users SET partnerstatus = ? WHERE userId = ?', ['incooperation', userId] ); // 提交事务 await connection.commit(); connection.release(); sendResponse(res, true, null, '供应商开始合作成功'); } catch (error) { console.error('供应商开始合作失败:', error.message); console.error('错误详情:', error); sendResponse(res, false, null, '供应商开始合作失败'); } }); // 供应商终止合作API - /api/suppliers/:id/terminate console.log('正在注册供应商终止合作API路由: /api/suppliers/:id/terminate'); app.post('/api/suppliers/:id/terminate', async (req, res) => { console.log('收到供应商终止合作请求:', req.params, req.body); try { const connection = await pool.getConnection(); const userId = req.params.id; const { reason } = req.body; if (!userId) { connection.release(); return sendResponse(res, false, null, '用户ID不能为空'); } // 开始事务 await connection.beginTransaction(); // 检查当前状态 const [currentUser] = await connection.query( 'SELECT partnerstatus FROM users WHERE userId = ?', [userId] ); if (currentUser.length === 0) { await connection.rollback(); connection.release(); return sendResponse(res, false, null, '供应商不存在'); } if (currentUser[0].partnerstatus !== 'approved' && currentUser[0].partnerstatus !== 'incooperation') { await connection.rollback(); connection.release(); return sendResponse(res, false, null, '只有审核通过或合作中的供应商才能终止合作'); } // 更新状态和终止原因 await connection.query( 'UPDATE users SET partnerstatus = ?, terminate_reason = ? WHERE userId = ?', ['notcooperative', reason, userId] ); // 提交事务 await connection.commit(); connection.release(); sendResponse(res, true, null, '供应商终止合作成功'); } catch (error) { console.error('供应商终止合作失败:', error.message); console.error('错误详情:', error); sendResponse(res, false, null, '供应商终止合作失败'); } }); // 导入图片处理工具 const ImageProcessor = require('./image-processor'); // 创建货源API - /api/supplies/create console.log('正在注册创建货源API路由: /api/supplies/create'); app.post('/api/supplies/create', async (req, res) => { console.log('收到创建货源请求:', req.body); let connection; try { connection = await pool.getConnection(); const { productName, costprice, quantity, grossWeight, yolk, specification, quality, region, imageUrls, sellerId, supplyStatus, description, sourceType, contactId, category, producting, spec_status } = req.body; // 开始事务 await connection.beginTransaction(); // 验证必填字段 if (!productName || !costprice || !quantity || !supplyStatus || !sourceType) { connection.release(); return sendResponse(res, false, null, '商品名称、采购价、最小起订量、货源状态和货源类型不能为空'); } // 如果sellerId为空,设置一个默认值 if (!sellerId) { sellerId = 'default_seller'; } // 移除重复货源检测限制,允许创建相同货源 console.log('移除重复货源检测限制,允许创建相同货源...'); // 处理联系人信息 let productContact = ''; let contactPhone = ''; if (contactId) { console.log('开始处理联系人信息,contactId:', contactId); // 从userlogin数据库获取联系人信息 const userLoginConnection = await userLoginPool.getConnection(); const [personnelData] = await userLoginConnection.query( 'SELECT alias, phoneNumber FROM Personnel WHERE projectName = "销售员" AND phoneNumber IS NOT NULL AND phoneNumber != "" AND id = ?', [parseInt(contactId)] // 使用contactId直接查询对应的联系人 ); userLoginConnection.release(); console.log('查询到的联系人数据:', personnelData); if (personnelData && personnelData.length > 0) { productContact = personnelData[0].alias || ''; contactPhone = personnelData[0].phoneNumber || ''; console.log('获取到的联系人信息:', productContact, contactPhone); } } console.log('准备插入的联系人信息:', productContact, contactPhone); // 生成唯一的productId const productId = `product_${Date.now()}_${Math.floor(Math.random() * 1000)}`; // 处理媒体文件上传(图片和视频) let uploadedImageUrls = []; if (Array.isArray(imageUrls) && imageUrls.length > 0) { console.log('开始处理媒体文件上传,共', imageUrls.length, '个文件'); for (const mediaUrl of imageUrls) { if (mediaUrl.startsWith('data:image/') || mediaUrl.startsWith('data:video/')) { // 处理DataURL let base64Data, ext, fileType; if (mediaUrl.startsWith('data:image/')) { // 图片类型 base64Data = mediaUrl.replace(/^data:image\/(png|jpeg|jpg|gif);base64,/, ''); ext = mediaUrl.match(/^data:image\/(png|jpeg|jpg|gif);base64,/)?.[1] || 'png'; fileType = 'image'; } else { // 视频类型 base64Data = mediaUrl.replace(/^data:video\/(mp4|mov|avi|wmv|flv);base64,/, ''); ext = mediaUrl.match(/^data:video\/(mp4|mov|avi|wmv|flv);base64,/)?.[1] || 'mp4'; fileType = 'video'; } let buffer = Buffer.from(base64Data, 'base64'); const filename = `${Date.now()}-${Math.random().toString(36).substr(2, 9)}.${ext}`; try { // 不再添加水印,前端已处理 console.log('【水印处理】前端已添加水印,跳过后端水印处理'); // 使用OSS上传媒体文件 const ossUrl = await OssUploader.uploadBuffer(buffer, filename, `products/${productName || 'general'}`, fileType); uploadedImageUrls.push(ossUrl); console.log(`${fileType}上传成功:`, ossUrl); } catch (uploadError) { console.error(`${fileType}上传失败:`, uploadError.message); // 继续上传其他文件,不中断流程 } } else { // 已经是URL,直接使用 uploadedImageUrls.push(mediaUrl); } } console.log('媒体文件处理完成,成功上传', uploadedImageUrls.length, '个文件'); } // 创建商品数据 const productData = { productId, sellerId: sellerId, // 使用前端传入的sellerId productName, category: category || '', // 添加种类 // 不再使用price字段,移除price字段 costprice: costprice || '', // 添加采购价 quantity: quantity, // 保持为逗号分隔的字符串,不转换为整数 grossWeight, yolk, specification, quality, producting: producting || '', // 添加产品包装 region, status: 'published', // 直接上架,而不是审核中 supplyStatus: supplyStatus || '', // 预售/现货 sourceType: sourceType || '', // 平台货源/三方认证/三方未认证 description: description || '', rejectReason: '', imageUrls: uploadedImageUrls.length > 0 ? JSON.stringify(uploadedImageUrls) : '[]', created_at: new Date(), product_contact: productContact, // 添加联系人名称 contact_phone: contactPhone, // 添加联系人电话 autoOfflineTime: req.body.autoOfflineTime, // 自动下架时间 autoOfflineDays: req.body.autoOfflineDays, // 自动下架天数 autoOfflineHours: req.body.autoOfflineHours // 自动下架小时数 }; // 插入商品数据 let result; try { // 检查products表是否有quality字段 const [columns] = await connection.query('SHOW COLUMNS FROM products LIKE ?', ['quality']); let insertQuery; let insertParams; if (columns.length === 0) { // 没有quality字段,不包含quality字段的插入 insertQuery = 'INSERT INTO products (productId, sellerId, productName, category, freshness, costprice, quantity, grossWeight, yolk, specification, producting, region, status, supplyStatus, sourceType, description, rejectReason, imageUrls, created_at, audit_time, product_contact, contact_phone, autoOfflineTime, autoOfflineDays, autoOfflineHours, spec_status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'; insertParams = [ productId, productData.sellerId, productName, category || '', req.body.freshness || '', costprice || '', quantity, grossWeight, yolk, specification, producting, region, productData.status, productData.supplyStatus, productData.sourceType, productData.description, productData.rejectReason, productData.imageUrls, new Date(), new Date(), productContact, contactPhone, req.body.autoOfflineTime, req.body.autoOfflineDays, req.body.autoOfflineHours, spec_status || '' // 添加联系人信息和自动下架时间、小时数、规格状态 ]; } else { // 有quality字段,包含quality字段的插入 insertQuery = 'INSERT INTO products (productId, sellerId, productName, category, freshness, costprice, quantity, grossWeight, yolk, specification, producting, quality, region, status, supplyStatus, sourceType, description, rejectReason, imageUrls, created_at, audit_time, product_contact, contact_phone, autoOfflineTime, autoOfflineDays, autoOfflineHours, spec_status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'; insertParams = [ productId, productData.sellerId, productName, category || '', req.body.freshness || '', costprice || '', quantity, grossWeight, yolk, specification, producting, quality, region, productData.status, productData.supplyStatus, productData.sourceType, productData.description, productData.rejectReason, productData.imageUrls, new Date(), new Date(), productContact, contactPhone, req.body.autoOfflineTime, req.body.autoOfflineDays, req.body.autoOfflineHours, spec_status || '' // 添加联系人信息和自动下架时间、小时数、规格状态 ]; } const queryResult = await connection.query(insertQuery, insertParams); result = queryResult[0]; } catch (insertError) { await connection.rollback(); connection.release(); console.error('插入商品数据失败:', insertError.message); console.error('SQL错误:', insertError.sqlMessage); return sendResponse(res, false, null, `创建货源失败: ${insertError.sqlMessage}`); } // 提交事务 await connection.commit(); connection.release(); // 发送WebSocket消息通知所有客户端 broadcastMessage({ type: 'supply_create', supplyId: result.insertId, action: 'create', data: { id: result.insertId, productId: productId, productName, category: category || '', // 不再使用price字段,移除price字段 costprice: costprice || '', quantity, grossWeight, yolk, specification, producting: producting || '', region, status: 'published', supplyStatus: supplyStatus || '', sourceType: sourceType || '', description: description || '', sellerId, imageUrls: uploadedImageUrls } }); // 返回完整的货源数据,包括ID和所有字段 sendResponse(res, true, { id: result.insertId, productId: productId, productName, category: category || '', freshness: req.body.freshness || '', costprice: costprice || '', quantity, grossWeight, yolk, specification, producting: producting || '', region, status: 'published', supplyStatus: supplyStatus || '', sourceType: sourceType || '', description: description || '', sellerId, imageUrls: uploadedImageUrls, created_at: new Date(), product_contact: productContact, contact_phone: contactPhone, autoOfflineTime: req.body.autoOfflineTime, autoOfflineDays: req.body.autoOfflineDays, autoOfflineHours: req.body.autoOfflineHours, spec_status: spec_status || '' }, '货源创建成功'); } catch (error) { if (connection) { try { await connection.rollback(); connection.release(); } catch (rollbackError) { console.error('回滚失败:', rollbackError.message); } } console.error('创建货源失败:', error.message); console.error('错误详情:', error); sendResponse(res, false, null, `创建货源失败: ${error.message}`); } }); // 媒体文件上传API - /api/upload-media console.log('正在注册媒体文件上传API路由: /api/upload-media'); app.post('/api/upload-media', async (req, res) => { console.log('收到媒体文件上传请求:', req.body); try { const { fileData, fileName, folder = 'general' } = req.body; // 验证参数 if (!fileData) { return sendResponse(res, false, null, '文件数据不能为空'); } let base64Data, ext, fileType; if (fileData.startsWith('data:image/')) { // 图片类型 base64Data = fileData.replace(/^data:image\/(png|jpeg|jpg|gif);base64,/, ''); ext = fileData.match(/^data:image\/(png|jpeg|jpg|gif);base64,/)?.[1] || 'png'; fileType = 'image'; } else if (fileData.startsWith('data:video/')) { // 视频类型 base64Data = fileData.replace(/^data:video\/(mp4|mov|avi|wmv|flv);base64,/, ''); ext = fileData.match(/^data:video\/(mp4|mov|avi|wmv|flv);base64,/)?.[1] || 'mp4'; fileType = 'video'; } else { return sendResponse(res, false, null, '不支持的文件类型'); } let buffer = Buffer.from(base64Data, 'base64'); const filename = fileName || `${Date.now()}-${Math.random().toString(36).substr(2, 9)}.${ext}`; try { // 使用OSS上传媒体文件 const ossUrl = await OssUploader.uploadBuffer(buffer, filename, `uploads/${folder}`, fileType); console.log(`${fileType}上传成功:`, ossUrl); sendResponse(res, true, { url: ossUrl, fileType: fileType, message: `${fileType}上传成功` }, `${fileType}上传成功`); } catch (uploadError) { console.error(`${fileType}上传失败:`, uploadError.message); sendResponse(res, false, null, `${fileType}上传失败: ${uploadError.message}`); } } catch (error) { console.error('媒体文件上传API错误:', error.message); sendResponse(res, false, null, `媒体文件上传失败: ${error.message}`); } }); // 图片上传API - /api/upload-image(兼容旧接口) console.log('正在注册图片上传API路由: /api/upload-image'); app.post('/api/upload-image', async (req, res) => { console.log('收到图片上传请求,转发到媒体文件上传API'); // 将请求转发到媒体文件上传API const uploadMediaHandler = app._router.stack.find(layer => layer.route && layer.route.path === '/api/upload-media' && layer.route.methods['post'] ); if (uploadMediaHandler && uploadMediaHandler.route && uploadMediaHandler.route.stack[0]) { return uploadMediaHandler.route.stack[0].handle(req, res); } else { sendResponse(res, false, null, '图片上传API内部错误'); } }); // 视频上传API - /api/upload-video(专门的视频上传接口) console.log('正在注册视频上传API路由: /api/upload-video'); app.post('/api/upload-video', async (req, res) => { console.log('收到视频上传请求,转发到媒体文件上传API'); // 将请求转发到媒体文件上传API const uploadMediaHandler = app._router.stack.find(layer => layer.route && layer.route.path === '/api/upload-media' && layer.route.methods['post'] ); if (uploadMediaHandler && uploadMediaHandler.route && uploadMediaHandler.route.stack[0]) { return uploadMediaHandler.route.stack[0].handle(req, res); } else { sendResponse(res, false, null, '视频上传API内部错误'); } }); // 获取审核失败原因API - /api/supplies/:id/reject-reason console.log('正在注册获取审核失败原因API路由: /api/supplies/:id/reject-reason'); app.get('/api/supplies/:id/reject-reason', async (req, res) => { console.log('收到获取审核失败原因请求:', req.params.id); try { const connection = await pool.getConnection(); const supplyId = req.params.id; // 查询该货源的拒绝原因 const [supply] = await connection.query( 'SELECT rejectReason FROM products WHERE id = ?', [supplyId] ); connection.release(); if (supply.length === 0) { return sendResponse(res, false, null, '货源不存在'); } sendResponse(res, true, { rejectReason: supply[0].rejectReason }, '获取审核失败原因成功'); } catch (error) { console.error('获取审核失败原因失败:', error.message); sendResponse(res, false, null, '获取审核失败原因失败'); } }); // 下架货源API - /api/supplies/:id/unpublish console.log('正在注册下架货源API路由: /api/supplies/:id/unpublish'); app.post('/api/supplies/:id/unpublish', async (req, res) => { console.log('收到下架货源请求:', req.params.id); try { const connection = await pool.getConnection(); const productId = req.params.id; // 开始事务 await connection.beginTransaction(); // 检查当前状态 const [currentProduct] = await connection.query( 'SELECT status FROM products WHERE id = ?', [productId] ); if (currentProduct.length === 0) { await connection.rollback(); connection.release(); return sendResponse(res, false, null, '货源不存在'); } // 更新状态为已下架,并将标签设置为已锁定 await connection.query( 'UPDATE products SET status = ?, label = 1, updated_at = NOW() WHERE id = ?', ['sold_out', productId] ); // 查询更新后的完整货源数据 const [updatedProduct] = await connection.query('SELECT * FROM products WHERE id = ?', [productId]); // 提交事务 await connection.commit(); connection.release(); // 发送WebSocket消息通知所有客户端 broadcastMessage({ type: 'supply_status_change', supplyId: productId, action: 'unpublish', status: 'sold_out' }); // 返回更新后的完整货源数据 sendResponse(res, true, updatedProduct[0], '货源下架成功'); } catch (error) { console.error('下架货源失败:', error.message); console.error('错误详情:', error); sendResponse(res, false, null, '货源下架失败'); } }); // 按规格下架API - /api/supplies/:id/unpublish-spec console.log('正在注册按规格下架API路由: /api/supplies/:id/unpublish-spec'); app.post('/api/supplies/:id/unpublish-spec', async (req, res) => { console.log('收到按规格下架请求:', req.params.id, req.body); try { const connection = await pool.getConnection(); const productId = req.params.id; const { specIndex } = req.body; // 开始事务 await connection.beginTransaction(); // 检查当前状态 const [currentProduct] = await connection.query( 'SELECT spec_status, status FROM products WHERE id = ?', [productId] ); if (currentProduct.length === 0) { await connection.rollback(); connection.release(); return sendResponse(res, false, null, '货源不存在'); } // 解析spec_status为数组 let specStatuses = []; if (currentProduct[0].spec_status) { if (typeof currentProduct[0].spec_status === 'string') { specStatuses = currentProduct[0].spec_status.split(',').map(s => s.trim()); } else if (Array.isArray(currentProduct[0].spec_status)) { specStatuses = currentProduct[0].spec_status; } else { specStatuses = [currentProduct[0].spec_status]; } } // 确保specIndex是数组 let specIndices = specIndex; if (!Array.isArray(specIndices)) { specIndices = [specIndices]; } // 将所有指定索引的规格状态改为1 specIndices.forEach(index => { if (index >= 0 && index < specStatuses.length) { specStatuses[index] = '1'; } }); // 检查是否所有规格都已下架 const allUnpublished = specStatuses.every(status => status === '1'); // 更新规格状态 let updateQuery = 'UPDATE products SET spec_status = ?, updated_at = NOW() WHERE id = ?'; let updateParams = [specStatuses.join(','), productId]; // 如果所有规格都已下架,更新货源状态和label if (allUnpublished) { updateQuery = 'UPDATE products SET spec_status = ?, status = ?, label = 1, updated_at = NOW() WHERE id = ?'; updateParams = [specStatuses.join(','), 'sold_out', productId]; } await connection.query(updateQuery, updateParams); // 查询更新后的完整货源数据 const [updatedProduct] = await connection.query('SELECT * FROM products WHERE id = ?', [productId]); // 提交事务 await connection.commit(); connection.release(); // 发送WebSocket消息通知所有客户端 broadcastMessage({ type: 'supply_status_change', supplyId: productId, action: 'unpublish_spec', specIndex: specIndex, status: allUnpublished ? 'sold_out' : currentProduct[0].status }); // 返回更新后的完整货源数据 sendResponse(res, true, updatedProduct[0], '规格下架成功'); } catch (error) { console.error('按规格下架失败:', error.message); console.error('错误详情:', error); sendResponse(res, false, null, '按规格下架失败'); } }); // 上架货源API - /api/supplies/:id/publish console.log('正在注册上架货源API路由: /api/supplies/:id/publish'); app.post('/api/supplies/:id/publish', async (req, res) => { console.log('收到上架货源请求:', req.params.id); try { const connection = await pool.getConnection(); const productId = req.params.id; // 开始事务 await connection.beginTransaction(); // 检查当前状态和label const [currentProduct] = await connection.query( 'SELECT status, label FROM products WHERE id = ?', [productId] ); if (currentProduct.length === 0) { await connection.rollback(); connection.release(); return sendResponse(res, false, null, '货源不存在'); } // 检查是否被锁定 if (currentProduct[0].label === 1) { await connection.rollback(); connection.release(); return sendResponse(res, false, null, '货源已被锁定,无法上架'); } // 更新状态为已发布(直接上架,不需要审核),同时更新updated_at和autoOfflineHours await connection.query( 'UPDATE products SET status = ?, updated_at = NOW() WHERE id = ?', ['published', productId] ); // 提交事务 await connection.commit(); connection.release(); // 发送WebSocket消息通知所有客户端 broadcastMessage({ type: 'supply_status_change', supplyId: productId, action: 'publish', status: 'published' }); sendResponse(res, true, null, '货源上架成功'); } catch (error) { console.error('上架货源失败:', error.message); console.error('错误详情:', error); sendResponse(res, false, null, '货源上架失败'); } }); // 删除货源API - /api/supplies/:id/delete console.log('正在注册删除货源API路由: /api/supplies/:id/delete'); app.post('/api/supplies/:id/delete', async (req, res) => { console.log('收到删除货源请求:', req.params.id); try { const connection = await pool.getConnection(); const productId = req.params.id; // 开始事务 await connection.beginTransaction(); // 检查当前状态 const [currentProduct] = await connection.query( 'SELECT status FROM products WHERE id = ?', [productId] ); if (currentProduct.length === 0) { await connection.rollback(); connection.release(); return sendResponse(res, false, null, '货源不存在'); } // 将货源状态改为hidden,而不是删除 await connection.query( 'UPDATE products SET status = ? WHERE id = ?', ['hidden', productId] ); // 查询更新后的完整货源数据 const [updatedProduct] = await connection.query('SELECT * FROM products WHERE id = ?', [productId]); // 提交事务 await connection.commit(); connection.release(); // 发送WebSocket消息通知所有客户端 broadcastMessage({ type: 'supply_update', supplyId: productId, action: 'update', status: 'hidden' }); // 返回更新后的完整货源数据 sendResponse(res, true, updatedProduct[0], '货源已删除'); } catch (error) { console.error('删除货源失败:', error.message); console.error('错误详情:', error); sendResponse(res, false, null, '货源删除失败'); } }); // 更新货源状态API - /api/supplies/:id console.log('正在注册更新货源状态API路由: /api/supplies/:id'); app.put('/api/supplies/:id', async (req, res) => { console.log('收到更新货源状态请求:', req.params.id, req.body); try { const connection = await pool.getConnection(); const productId = req.params.id; const { status } = req.body; // 验证状态参数 if (!status) { connection.release(); return sendResponse(res, false, null, '状态不能为空'); } // 开始事务 await connection.beginTransaction(); // 检查当前状态 const [currentProduct] = await connection.query( 'SELECT status FROM products WHERE id = ?', [productId] ); if (currentProduct.length === 0) { await connection.rollback(); connection.release(); return sendResponse(res, false, null, '货源不存在'); } // 更新状态,当状态为sold_out时同时锁定label if (status === 'sold_out') { await connection.query( 'UPDATE products SET status = ?, label = 1, updated_at = NOW() WHERE id = ?', [status, productId] ); } else { await connection.query( 'UPDATE products SET status = ?, updated_at = NOW() WHERE id = ?', [status, productId] ); } // 提交事务 await connection.commit(); connection.release(); // 发送WebSocket消息通知所有客户端 broadcastMessage({ type: 'supply_status_change', supplyId: productId, action: 'update_status', status: status }); sendResponse(res, true, null, '状态更新成功'); } catch (error) { console.error('更新货源状态失败:', error.message); console.error('错误详情:', error); sendResponse(res, false, null, '更新货源状态失败'); } }); // 编辑货源API - /api/supplies/:id/edit console.log('正在注册编辑货源API路由: /api/supplies/:id/edit'); app.put('/api/supplies/:id/edit', async (req, res) => { console.log('收到编辑货源请求:', req.params.id); console.log('请求体中的category:', req.body.category); console.log('请求体中的sourceType:', req.body.sourceType); console.log('请求体中的freshness:', req.body.freshness); try { const connection = await pool.getConnection(); const productId = req.params.id; const { productName, costprice, quantity, grossWeight, yolk, specification, supplyStatus, description, region, contactId, producting, imageUrls, autoOfflineTime, category, sourceType, freshness, autoOfflineHours, spec_status } = req.body; // 开始事务 await connection.beginTransaction(); // 检查当前状态和label const [currentProduct] = await connection.query( 'SELECT status, label FROM products WHERE id = ?', [productId] ); if (currentProduct.length === 0) { await connection.rollback(); connection.release(); return sendResponse(res, false, null, '货源不存在'); } // 检查是否被锁定 if (currentProduct[0].label === 1) { await connection.rollback(); connection.release(); return sendResponse(res, false, null, '货源已被锁定,无法编辑'); } // 处理联系人信息:只在有新联系人ID且能查询到时才更新,否则保持原有值 let productContact = null; let contactPhone = null; // 先查询原有联系人信息 const [existingProduct] = await connection.query( 'SELECT product_contact, contact_phone FROM products WHERE id = ?', [productId] ); // 如果没有提供contactId或contactId为空,使用原有联系人信息 if (!contactId || contactId === '') { productContact = existingProduct[0].product_contact; contactPhone = existingProduct[0].contact_phone; } else { // 从userlogin数据库获取联系人信息 const userLoginConnection = await userLoginPool.getConnection(); const [personnelData] = await userLoginConnection.query( 'SELECT alias, phoneNumber FROM Personnel WHERE projectName = "销售员" AND phoneNumber IS NOT NULL AND phoneNumber != "" AND id = ?', [parseInt(contactId)] // 使用contactId直接查询对应的联系人 ); userLoginConnection.release(); if (personnelData && personnelData.length > 0) { // 成功查询到联系人,使用新联系人信息 productContact = personnelData[0].alias || ''; contactPhone = personnelData[0].phoneNumber || ''; } else { // 未查询到联系人,使用原有联系人信息 productContact = existingProduct[0].product_contact; contactPhone = existingProduct[0].contact_phone; } } // 处理媒体文件上传(图片和视频) let uploadedImageUrls = []; if (imageUrls && Array.isArray(imageUrls) && imageUrls.length > 0) { console.log('开始处理编辑媒体文件上传,共', imageUrls.length, '个文件'); for (const mediaUrl of imageUrls) { if (mediaUrl.startsWith('data:image/') || mediaUrl.startsWith('data:video/')) { // 处理DataURL let base64Data, ext, fileType; if (mediaUrl.startsWith('data:image/')) { // 图片类型 base64Data = mediaUrl.replace(/^data:image\/(png|jpeg|jpg|gif);base64,/, ''); ext = mediaUrl.match(/^data:image\/(png|jpeg|jpg|gif);base64,/)?.[1] || 'png'; fileType = 'image'; } else { // 视频类型 base64Data = mediaUrl.replace(/^data:video\/(mp4|mov|avi|wmv|flv);base64,/, ''); ext = mediaUrl.match(/^data:video\/(mp4|mov|avi|wmv|flv);base64,/)?.[1] || 'mp4'; fileType = 'video'; } let buffer = Buffer.from(base64Data, 'base64'); const filename = `${Date.now()}-${Math.random().toString(36).substr(2, 9)}.${ext}`; try { // 不再添加水印,前端已处理 console.log('【水印处理】前端已添加水印,跳过后端水印处理'); // 使用OSS上传媒体文件 const ossUrl = await OssUploader.uploadBuffer(buffer, filename, `products/${productName || 'general'}`, fileType); uploadedImageUrls.push(ossUrl); console.log(`${fileType}上传成功:`, ossUrl); } catch (uploadError) { console.error(`${fileType}上传失败:`, uploadError.message); // 继续上传其他文件,不中断流程 } } else { // 已经是URL,直接使用 uploadedImageUrls.push(mediaUrl); } } console.log('媒体文件处理完成,成功上传', uploadedImageUrls.length, '个文件'); } // 处理数量:保存所有数量值,与规格保持一致 let quantityValue = quantity; // 直接保存前端提交的数量字符串 // 如果是数字,转换为字符串 if (typeof quantity === 'number') { quantityValue = quantity.toString(); } else if (typeof quantity === 'string' && !isNaN(parseFloat(quantity)) && isFinite(quantity)) { // 如果是单个数字字符串,直接使用 quantityValue = quantity; } // 只有当有新上传的图片时,才更新imageUrls,否则保留原有值 let imageUrlsToUpdate = null; if (uploadedImageUrls.length > 0) { imageUrlsToUpdate = JSON.stringify(uploadedImageUrls); } else { // 如果没有新上传的图片,查询原有图片URL const [existingProduct] = await connection.query( 'SELECT imageUrls FROM products WHERE id = ?', [productId] ); imageUrlsToUpdate = existingProduct[0].imageUrls; } // 打印用于调试的更新参数 console.log('更新参数调试:'); console.log('productName:', productName); // 不再使用price字段,移除price调试日志 console.log('costprice:', costprice || ''); console.log('quantityValue:', quantityValue); console.log('grossWeight:', grossWeight); console.log('yolk:', yolk); console.log('specification:', specification); console.log('producting:', producting); console.log('supplyStatus:', supplyStatus); console.log('description:', description); console.log('region:', region); console.log('category:', category); console.log('sourceType:', sourceType); console.log('freshness:', freshness); console.log('productContact:', productContact); console.log('contactPhone:', contactPhone); console.log('imageUrlsToUpdate:', imageUrlsToUpdate); console.log('autoOfflineTime:', autoOfflineTime || null); console.log('autoOfflineHours:', autoOfflineHours || 24); console.log('productId:', productId); // 更新货源信息 const updateQuery = ` UPDATE products SET productName = ?, costprice = ?, quantity = ?, grossWeight = ?, yolk = ?, specification = ?, producting = ?, supplyStatus = ?, description = ?, region = ?, category = ?, sourceType = ?, freshness = ?, spec_status = ?, product_contact = ?, contact_phone = ?, imageUrls = ?, autoOfflineTime = ?, autoOfflineHours = ?, updated_at = ? WHERE id = ? `; const [result] = await connection.query(updateQuery, [ productName, costprice || '', quantityValue, grossWeight, yolk, specification, producting, supplyStatus, description, region, category, sourceType, freshness, spec_status || '', productContact, contactPhone, imageUrlsToUpdate, autoOfflineTime || null, // 自动下架时间 autoOfflineHours || 24, // 默认24小时 new Date(), // 更新updated_at字段 productId ]); console.log('更新结果:', result); // 提交事务 await connection.commit(); connection.release(); // 发送WebSocket消息通知所有客户端 broadcastMessage({ type: 'supply_update', supplyId: productId, action: 'update', data: { id: productId, productName, // 不再使用price字段,移除price字段 costprice: costprice || '', quantity, grossWeight, yolk, specification, category, sourceType, freshness, producting, supplyStatus, description, region, imageUrls: uploadedImageUrls.length > 0 ? uploadedImageUrls : imageUrls } }); // 查询修改后的完整货源数据(在事务提交前查询,使用同一个连接) const [updatedProduct] = await connection.query('SELECT * FROM products WHERE id = ?', [productId]); // 提交事务 await connection.commit(); connection.release(); // 发送WebSocket消息通知所有客户端 broadcastMessage({ type: 'supply_update', supplyId: productId, action: 'update', data: { id: productId, productName, // 不再使用price字段,移除price字段 costprice: costprice || '', quantity, grossWeight, yolk, specification, category, sourceType, freshness, producting, supplyStatus, description, region, imageUrls: uploadedImageUrls.length > 0 ? uploadedImageUrls : imageUrls } }); // 返回完整的修改后货源数据 sendResponse(res, true, updatedProduct[0], '货源编辑成功'); } catch (error) { console.error('编辑货源失败:', error.message); console.error('错误详情:', error); sendResponse(res, false, null, '货源编辑失败'); } }); // 供应商列表查询API - /api/suppliers console.log('正在注册供应商列表查询API路由: /api/suppliers'); app.get('/api/suppliers', async (req, res) => { console.log('收到供应商列表查询请求:', req.query); try { const connection = await pool.getConnection(); const { page = 1, pageSize = 10, status = '', keyword = '', phoneNumber = '', currentUserName = '', currentUserPhone = '' } = req.query; // 构建查询条件 let whereClause = ''; let params = []; // 添加状态筛选 if (status) { whereClause += ` WHERE partnerstatus = ?`; params.push(status); } // 添加关键词搜索 if (keyword) { whereClause += status ? ' AND' : ' WHERE'; whereClause += ` (username LIKE ? OR company LIKE ? OR phoneNumber LIKE ?)`; params.push(`%${keyword}%`, `%${keyword}%`, `%${keyword}%`); } // 添加手机号搜索(优先级高于keyword中的手机号搜索) if (phoneNumber) { whereClause += (status || keyword) ? ' AND' : ' WHERE'; whereClause += ` phoneNumber LIKE ?`; params.push(`%${phoneNumber}%`); } // 添加对接人匹配逻辑,只显示当前登录者对接的供应商(非管理员) if (currentUserName || currentUserPhone) { whereClause += (status || keyword || phoneNumber) ? ' AND' : ' WHERE'; whereClause += ` (liaison LIKE ? OR liaison LIKE ?)`; params.push(`%${currentUserName}%`, `%${currentUserPhone}%`); } // 管理员不需要对接人匹配,会显示所有供应商 // 获取总数 const [totalResult] = await connection.query( `SELECT COUNT(*) as total FROM users${whereClause}`, params ); const total = totalResult[0].total; // 计算分页 const offset = (page - 1) * pageSize; params.push(parseInt(pageSize), offset); // 查询供应商列表,按创建时间倒序排序,确保最新创建的在前面 const [suppliers] = await connection.query( `SELECT userId, phoneNumber, province, city, district, detailedaddress, company, collaborationid, cooperation, businesslicenseurl, proofurl, brandurl, partnerstatus, reasonforfailure, reject_reason, terminate_reason, audit_time, created_at, liaison, seller_followup FROM users${whereClause} ORDER BY created_at DESC LIMIT ? OFFSET ?`, params ); connection.release(); sendResponse(res, true, { list: suppliers, total, page: parseInt(page), pageSize: parseInt(pageSize) }, '查询成功'); } catch (error) { console.error('供应商列表查询失败:', error.message); console.error('错误详情:', error); sendResponse(res, false, null, '供应商列表查询失败'); } }); // 供应商跟进信息API - /api/suppliers/:id/followup console.log('正在注册供应商跟进信息API路由: /api/suppliers/:id/followup'); app.post('/api/suppliers/:id/followup', async (req, res) => { console.log('收到供应商跟进信息更新请求:', req.params.id); try { const connection = await pool.getConnection(); const supplierId = req.params.id; const { content } = req.body; if (!content) { connection.release(); return sendResponse(res, false, null, '跟进信息不能为空'); } // 更新供应商跟进信息 await connection.query( 'UPDATE users SET seller_followup = ? WHERE userId = ?', [content, supplierId] ); connection.release(); sendResponse(res, true, null, '跟进信息保存成功'); } catch (error) { console.error('更新供应商跟进信息失败:', error.message); console.error('错误详情:', error); sendResponse(res, false, null, '更新供应商跟进信息失败'); } }); // 管理员统计 - 获取货源创建统计数据 app.get('/api/admin/stats/supplies', async (req, res) => { try { const { filter } = req.query; const connection = await pool.getConnection(); // 计算时间范围 let timeCondition = ''; const now = new Date(); if (filter === 'today') { // 今天 const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()); timeCondition = `AND created_at >= '${today.toISOString().slice(0, 19).replace('T', ' ')}'`; } else if (filter === 'yesterday') { // 昨天 const yesterday = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1); const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()); timeCondition = `AND created_at >= '${yesterday.toISOString().slice(0, 19).replace('T', ' ')}' AND created_at < '${today.toISOString().slice(0, 19).replace('T', ' ')}'`; } else if (filter === 'beforeYesterday') { // 前天 const beforeYesterday = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 2); const yesterday = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1); timeCondition = `AND created_at >= '${beforeYesterday.toISOString().slice(0, 19).replace('T', ' ')}' AND created_at < '${yesterday.toISOString().slice(0, 19).replace('T', ' ')}'`; } else if (filter === 'week') { // 本周 const weekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); timeCondition = `AND created_at >= '${weekAgo.toISOString().slice(0, 19).replace('T', ' ')}'`; } else if (filter === 'month') { // 本月 const monthAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000); timeCondition = `AND created_at >= '${monthAgo.toISOString().slice(0, 19).replace('T', ' ')}'`; } else if (filter === 'custom') { // 自定义时间范围 const { startDate, endDate } = req.query; if (startDate) { // 开始日期,格式化为YYYY-MM-DD 00:00:00 const start = new Date(startDate); start.setHours(0, 0, 0, 0); timeCondition += `AND created_at >= '${start.toISOString().slice(0, 19).replace('T', ' ')}'`; } if (endDate) { // 结束日期,格式化为YYYY-MM-DD 23:59:59 const end = new Date(endDate); end.setHours(23, 59, 59, 999); timeCondition += `AND created_at <= '${end.toISOString().slice(0, 19).replace('T', ' ')}'`; } } // 获取每个卖家创建的货源数量,关联users表获取nickName,过滤掉hidden状态的货源 // 替换created_at为p.created_at,避免歧义 const chartTimeCondition = timeCondition.replace(/created_at/g, 'p.created_at'); const [chartData] = await connection.query(` SELECT p.sellerId, u.nickName, COUNT(*) as count FROM products p LEFT JOIN users u ON p.sellerId = u.userId WHERE 1=1 AND p.status != 'hidden' ${chartTimeCondition} GROUP BY p.sellerId, u.nickName ORDER BY count DESC `); // 获取总体统计信息,过滤掉hidden状态的货源 const [totalSuppliesResult] = await connection.query(` SELECT COUNT(*) as total FROM products WHERE 1=1 AND status != 'hidden' ${timeCondition} `); const totalSupplies = totalSuppliesResult[0].total; const [totalUsersResult] = await connection.query(` SELECT COUNT(DISTINCT sellerId) as total FROM products WHERE 1=1 AND status != 'hidden' ${timeCondition} `); const totalUsers = totalUsersResult[0].total; const avgPerUser = totalUsers > 0 ? totalSupplies / totalUsers : 0; connection.release(); // 调试日志:检查API返回数据结构 console.log('stats API返回的chartData:', chartData); console.log('stats API返回的totalSupplies:', totalSupplies); console.log('stats API返回的totalUsers:', totalUsers); console.log('stats API返回的avgPerUser:', avgPerUser); sendResponse(res, true, { chartData, stats: { totalSupplies, totalUsers, avgPerUser }, suppliesData: [], // 保持与前端期望的数据结构一致 usersData: [] // 保持与前端期望的数据结构一致 }, '获取统计数据成功'); } catch (error) { console.error('获取统计数据失败:', error.message); console.error('错误详情:', error); sendResponse(res, false, null, '获取统计数据失败'); } }); // 管理员统计 - 获取指定卖家的货源列表 app.get('/api/admin/supplies', async (req, res) => { try { const { sellerId, filter } = req.query; const connection = await pool.getConnection(); // 计算时间范围 let timeCondition = ''; const now = new Date(); if (filter === 'today') { // 今天 const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()); timeCondition = `AND created_at >= '${today.toISOString().slice(0, 19).replace('T', ' ')}'`; } else if (filter === 'yesterday') { // 昨天 const yesterday = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1); const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()); timeCondition = `AND created_at >= '${yesterday.toISOString().slice(0, 19).replace('T', ' ')}' AND created_at < '${today.toISOString().slice(0, 19).replace('T', ' ')}'`; } else if (filter === 'beforeYesterday') { // 前天 const beforeYesterday = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 2); const yesterday = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1); timeCondition = `AND created_at >= '${beforeYesterday.toISOString().slice(0, 19).replace('T', ' ')}' AND created_at < '${yesterday.toISOString().slice(0, 19).replace('T', ' ')}'`; } else if (filter === 'week') { // 本周 const weekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); timeCondition = `AND created_at >= '${weekAgo.toISOString().slice(0, 19).replace('T', ' ')}'`; } else if (filter === 'month') { // 本月 const monthAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000); timeCondition = `AND created_at >= '${monthAgo.toISOString().slice(0, 19).replace('T', ' ')}'`; } else if (filter === 'custom') { // 自定义时间范围 const { startDate, endDate } = req.query; if (startDate) { // 开始日期,格式化为YYYY-MM-DD 00:00:00 const start = new Date(startDate); start.setHours(0, 0, 0, 0); timeCondition += `AND created_at >= '${start.toISOString().slice(0, 19).replace('T', ' ')}'`; } if (endDate) { // 结束日期,格式化为YYYY-MM-DD 23:59:59 const end = new Date(endDate); end.setHours(23, 59, 59, 999); timeCondition += `AND created_at <= '${end.toISOString().slice(0, 19).replace('T', ' ')}'`; } } // 获取货源列表,关联users表获取创建人姓名,过滤掉hidden状态的货源 // 替换created_at为p.created_at,避免歧义 const suppliesTimeCondition = timeCondition.replace(/created_at/g, 'p.created_at'); // 构建查询条件,支持带sellerId或不带sellerId的情况 let whereCondition = 'p.status != "hidden"'; let queryParams = []; if (sellerId) { whereCondition += ' AND p.sellerId = ?'; queryParams.push(sellerId); } const [supplies] = await connection.query(` SELECT p.*, u.nickName FROM products p LEFT JOIN users u ON p.sellerId = u.userId WHERE ${whereCondition} ${suppliesTimeCondition} ORDER BY p.created_at DESC `, queryParams); connection.release(); // 调试日志:检查查询结果中是否包含product_log字段 console.log('查询到的货源数量:', supplies.length); if (supplies.length > 0) { console.log('第一个货源的所有字段和值:', JSON.stringify(supplies[0], null, 2)); console.log('第一个货源的字段:', Object.keys(supplies[0])); console.log('第一个货源是否包含product_log字段:', 'product_log' in supplies[0]); if ('product_log' in supplies[0]) { console.log('第一个货源的product_log值:', supplies[0].product_log); console.log('第一个货源的product_log类型:', typeof supplies[0].product_log); } else { console.log('第一个货源不包含product_log字段,可能的原因:'); console.log('1. 数据库中products表可能没有product_log字段'); console.log('2. 字段名称可能拼写错误'); console.log('3. 查询语句可能有问题'); } } sendResponse(res, true, { supplies }, '获取货源列表成功'); } catch (error) { console.error('获取货源列表失败:', error.message); console.error('错误详情:', error); sendResponse(res, false, null, '获取货源列表失败'); } }); // 首页路由 - 根据角色重定向 app.get('/', (req, res) => { // 注意:这里无法直接获取localStorage中的用户信息,因为localStorage是客户端的 // 实际生产环境中,应该通过cookie或token来获取用户角色信息 // 这里我们默认跳转到登录页面,让客户端处理角色跳转 res.sendFile(path.join(__dirname, 'login.html')); }); // 记录货源操作日志API app.post('/api/supplies/log', async (req, res) => { try { const { phoneNumber, projectName, operationEvent, productId, originalData, modifiedData, changedFields, userId } = req.body; // 验证必填字段 if (!phoneNumber || !projectName || !operationEvent || !productId || !userId) { return sendResponse(res, false, null, '缺少必填字段'); } // 1. 查询personnel表获取完整信息 const userLoginConnection = await userLoginPool.getConnection(); // 使用SELECT * 确保获取所有字段,包括company和organization const [personnelResult] = await userLoginConnection.query( 'SELECT * FROM Personnel WHERE phoneNumber = ?', [phoneNumber] ); userLoginConnection.release(); // 打印查询结果,查看是否包含company和organization字段 console.log('查询到的personnel信息:', personnelResult[0]); // 自动计算变更字段 let calculatedChangedFields = changedFields; if (operationEvent.includes('编辑') && originalData && modifiedData && !changedFields) { // 比较两个对象的差异,返回变更的字段列表 const getChangedFields = (oldObj, newObj) => { if (!oldObj || !newObj) return []; const changed = []; const allKeys = new Set([...Object.keys(oldObj), ...Object.keys(newObj)]); allKeys.forEach(key => { if (JSON.stringify(oldObj[key]) !== JSON.stringify(newObj[key])) { changed.push(key); } }); return changed; }; calculatedChangedFields = getChangedFields(originalData, modifiedData); } // 2. 准备日志数据 const logData = { tracompany: '', tradepartment: '', traorganization: '', trarole: projectName || '', trauserName: '', traassistant: '', // 暂无协助人信息 userId: userId, // 使用前端传递的正确userId operationEvent: operationEvent, operationTime: new Date(), originalData: originalData ? JSON.stringify(originalData) : null, modifiedData: modifiedData ? JSON.stringify(modifiedData) : null, changedFields: calculatedChangedFields ? JSON.stringify(calculatedChangedFields) : null }; // 如果找到了员工信息,使用真实的员工数据 if (personnelResult.length > 0) { const personnelInfo = personnelResult[0]; // 检查personnelInfo中的字段名,确保使用正确的字段名 console.log('personnelInfo的所有字段:', Object.keys(personnelInfo)); // 使用正确的字段名:managercompany(公司)、managerdepartment(部门)、organization(组织) logData.tracompany = personnelInfo.managercompany || personnelInfo.managerCompany || personnelInfo.MANAGERCOMPANY || ''; logData.tradepartment = personnelInfo.managerdepartment || personnelInfo.managerDepartment || personnelInfo.MANAGERDEPARTMENT || ''; logData.traorganization = personnelInfo.organization || personnelInfo.Organization || personnelInfo.ORGANIZATION || personnelInfo.org || ''; logData.trauserName = personnelInfo.name || personnelInfo.Name || personnelInfo.NAME || personnelInfo.alias || personnelInfo.Alias || personnelInfo.ALIAS || ''; } // 3. 插入日志到informationtra表 let connection; try { connection = await pool.getConnection(); const [result] = await connection.query( `INSERT INTO informationtra ( tracompany, tradepartment, traorganization, trarole, trauserName, traassistant, userId, operationEvent, operationTime, originalData, modifiedData, changedFields ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [ logData.tracompany, logData.tradepartment, logData.traorganization, logData.trarole, logData.trauserName, logData.traassistant, logData.userId, logData.operationEvent, logData.operationTime, logData.originalData, logData.modifiedData, logData.changedFields ] ); sendResponse(res, true, { logId: result.insertId }, '日志记录成功'); } catch (error) { console.error('记录日志失败:', error); sendResponse(res, false, null, '记录日志失败'); } finally { if (connection) { connection.release(); } } } catch (error) { console.error('记录日志失败:', error); sendResponse(res, false, null, '记录日志失败'); } }); // 货源管理页面路由 app.get('/management', (req, res) => { res.sendFile(path.join(__dirname, 'Management.html')); }); // 错误处理中间件 app.use((err, req, res, next) => { console.error('服务器错误:', err.message); console.error('错误详情:', err); res.status(500).json({ success: false, message: '服务器内部错误' }); }); // 启动服务器 async function startServer() { try { await initDatabase(); server.listen(PORT, () => { console.log(`服务器已启动,监听端口 ${PORT}`); console.log(`访问地址: http://localhost:${PORT}`); console.log(`WebSocket服务已启动,端口: ${PORT}`); }); } catch (error) { console.error('服务器启动失败:', error.message); console.error('错误详情:', error); // 如果启动失败,尝试重新启动 setTimeout(() => { console.log('尝试重新启动服务器...'); startServer(); }, 5000); } } // 确保数据库结构 async function ensureDatabaseSchema() { console.log('开始执行数据库结构检查...'); try { const connection = await pool.getConnection(); console.log('获取数据库连接成功'); // 检查users表是否有必要的字段 console.log('检查users表是否有partnerstatus字段...'); const [partnerStatusColumns] = await connection.query( 'SHOW COLUMNS FROM `users` LIKE ?', ['partnerstatus'] ); console.log('检查表字段结果:', partnerStatusColumns.length > 0 ? '已存在' : '不存在'); if (partnerStatusColumns.length === 0) { console.log('添加partnerstatus字段到users表...'); await connection.query( 'ALTER TABLE `users` ADD COLUMN partnerstatus VARCHAR(50) DEFAULT "underreview" COMMENT "合作商状态"' ); console.log('partnerstatus字段添加成功'); } console.log('检查users表是否有reject_reason字段...'); const [rejectReasonColumns] = await connection.query( 'SHOW COLUMNS FROM `users` LIKE ?', ['reject_reason'] ); console.log('检查表字段结果:', rejectReasonColumns.length > 0 ? '已存在' : '不存在'); if (rejectReasonColumns.length === 0) { console.log('添加reject_reason字段到users表...'); await connection.query( 'ALTER TABLE `users` ADD COLUMN reject_reason TEXT COMMENT "拒绝理由"' ); console.log('reject_reason字段添加成功'); } console.log('检查users表是否有terminate_reason字段...'); const [terminateReasonColumns] = await connection.query( 'SHOW COLUMNS FROM `users` LIKE ?', ['terminate_reason'] ); console.log('检查表字段结果:', terminateReasonColumns.length > 0 ? '已存在' : '不存在'); if (terminateReasonColumns.length === 0) { console.log('添加terminate_reason字段到users表...'); await connection.query( 'ALTER TABLE `users` ADD COLUMN terminate_reason TEXT COMMENT "终止合作理由"' ); console.log('terminate_reason字段添加成功'); } console.log('检查users表是否有audit_time字段...'); const [userAuditTimeColumns] = await connection.query( 'SHOW COLUMNS FROM `users` LIKE ?', ['audit_time'] ); console.log('检查表字段结果:', userAuditTimeColumns.length > 0 ? '已存在' : '不存在'); if (userAuditTimeColumns.length === 0) { console.log('添加audit_time字段到users表...'); await connection.query( 'ALTER TABLE `users` ADD COLUMN audit_time DATETIME COMMENT "审核时间"' ); console.log('audit_time字段添加成功'); } // 检查表是否有rejectReason字段 console.log('检查表products是否有rejectReason字段...'); const [columns] = await connection.query( 'SHOW COLUMNS FROM `products` LIKE ?', ['rejectReason'] ); console.log('检查表字段结果:', columns.length > 0 ? '已存在' : '不存在'); if (columns.length === 0) { console.log('添加rejectReason字段到products表...'); await connection.query( 'ALTER TABLE `products` ADD COLUMN rejectReason TEXT COMMENT "拒绝理由"' ); console.log('rejectReason字段添加成功'); } // 检查表是否有autoOfflineTime字段 console.log('检查表products是否有autoOfflineTime字段...'); const [autoOfflineTimeColumns] = await connection.query( 'SHOW COLUMNS FROM `products` LIKE ?', ['autoOfflineTime'] ); console.log('检查表字段结果:', autoOfflineTimeColumns.length > 0 ? '已存在' : '不存在'); if (autoOfflineTimeColumns.length === 0) { console.log('添加autoOfflineTime字段到products表...'); await connection.query( 'ALTER TABLE `products` ADD COLUMN autoOfflineTime DATETIME COMMENT "自动下架时间"' ); console.log('autoOfflineTime字段添加成功'); } // 检查表是否有autoOfflineDays字段 console.log('检查表products是否有autoOfflineDays字段...'); const [autoOfflineDaysColumns] = await connection.query( 'SHOW COLUMNS FROM `products` LIKE ?', ['autoOfflineDays'] ); console.log('检查表字段结果:', autoOfflineDaysColumns.length > 0 ? '已存在' : '不存在'); if (autoOfflineDaysColumns.length === 0) { console.log('添加autoOfflineDays字段到products表...'); await connection.query( 'ALTER TABLE `products` ADD COLUMN autoOfflineDays INT COMMENT "自动下架天数"' ); console.log('autoOfflineDays字段添加成功'); } // 检查表是否有autoOfflineHours字段 console.log('检查表products是否有autoOfflineHours字段...'); const [autoOfflineHoursColumns] = await connection.query( 'SHOW COLUMNS FROM `products` LIKE ?', ['autoOfflineHours'] ); console.log('检查表字段结果:', autoOfflineHoursColumns.length > 0 ? '已存在' : '不存在'); if (autoOfflineHoursColumns.length === 0) { console.log('添加autoOfflineHours字段到products表...'); await connection.query( 'ALTER TABLE `products` ADD COLUMN autoOfflineHours FLOAT COMMENT "自动下架小时数"' ); console.log('autoOfflineHours字段添加成功'); } // 检查表是否有supplyStatus字段 console.log('检查表products是否有supplyStatus字段...'); const [supplyStatusColumns] = await connection.query( 'SHOW COLUMNS FROM `products` LIKE ?', ['supplyStatus'] ); console.log('检查表字段结果:', supplyStatusColumns.length > 0 ? '已存在' : '不存在'); if (supplyStatusColumns.length === 0) { console.log('添加supplyStatus字段到products表...'); await connection.query( 'ALTER TABLE `products` ADD COLUMN supplyStatus VARCHAR(50) COMMENT "货源状态"' ); console.log('supplyStatus字段添加成功'); } // 检查表是否有sourceType字段 console.log('检查表products是否有sourceType字段...'); const [sourceTypeColumns] = await connection.query( 'SHOW COLUMNS FROM `products` LIKE ?', ['sourceType'] ); console.log('检查表字段结果:', sourceTypeColumns.length > 0 ? '已存在' : '不存在'); if (sourceTypeColumns.length === 0) { console.log('添加sourceType字段到products表...'); await connection.query( 'ALTER TABLE `products` ADD COLUMN sourceType VARCHAR(50) COMMENT "货源类型"' ); console.log('sourceType字段添加成功'); } // 检查表是否有category字段 console.log('检查表products是否有category字段...'); const [categoryColumns] = await connection.query( 'SHOW COLUMNS FROM `products` LIKE ?', ['category'] ); console.log('检查表字段结果:', categoryColumns.length > 0 ? '已存在' : '不存在'); if (categoryColumns.length === 0) { console.log('添加category字段到products表...'); await connection.query( 'ALTER TABLE `products` ADD COLUMN category VARCHAR(50) COMMENT "种类"' ); console.log('category字段添加成功'); } // 检查表是否有audit_time字段 console.log('检查表products是否有audit_time字段...'); const [auditTimeColumns] = await connection.query( 'SHOW COLUMNS FROM `products` LIKE ?', ['audit_time'] ); console.log('检查表字段结果:', auditTimeColumns.length > 0 ? '已存在' : '不存在'); if (auditTimeColumns.length === 0) { console.log('添加audit_time字段到products表...'); await connection.query( 'ALTER TABLE `products` ADD COLUMN audit_time DATETIME COMMENT "审核时间"' ); console.log('audit_time字段添加成功'); } // 检查表是否有producting字段 console.log('检查表products是否有producting字段...'); const [productingColumns] = await connection.query( 'SHOW COLUMNS FROM `products` LIKE ?', ['producting'] ); console.log('检查表字段结果:', productingColumns.length > 0 ? '已存在' : '不存在'); if (productingColumns.length === 0) { console.log('添加producting字段到products表...'); await connection.query( 'ALTER TABLE `products` ADD COLUMN producting VARCHAR(255) COMMENT "产品包装"' ); console.log('producting字段添加成功'); } // 检查audit_logs表是否存在,如果不存在则创建 console.log('检查audit_logs表是否存在...'); const [tables] = await connection.query( "SHOW TABLES LIKE 'audit_logs'" ); console.log('检查表存在性结果:', tables.length > 0 ? '已存在' : '不存在'); if (tables.length === 0) { console.log('创建audit_logs表...'); await connection.query(` CREATE TABLE audit_logs ( id INT AUTO_INCREMENT PRIMARY KEY, supply_id VARCHAR(50) NOT NULL, action VARCHAR(20) NOT NULL COMMENT 'approve或reject', user_id VARCHAR(50) NOT NULL COMMENT '操作人ID', remark TEXT COMMENT '备注信息', created_at DATETIME NOT NULL, INDEX idx_supply_id (supply_id), INDEX idx_created_at (created_at) ) COMMENT '审核操作日志表' `); console.log('audit_logs表创建成功'); } connection.release(); console.log('数据库结构检查完成'); } catch (error) { console.error('数据库结构检查失败:', error.message); console.error('错误详情:', error); } } // 启动服务器 startServer(); // 优雅关闭 process.on('SIGINT', async () => { console.log('正在关闭服务器...'); if (pool) { try { await pool.end(); console.log('数据库连接池已关闭'); } catch (error) { console.error('关闭数据库连接池失败:', error.message); } } console.log('服务器已关闭'); process.exit(0); }); module.exports = app;