You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2340 lines
94 KiB

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 } = 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) 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 // 添加联系人信息和自动下架时间、小时数
];
} 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) 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 // 添加联系人信息和自动下架时间、小时数
];
}
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
}, '货源创建成功');
} 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/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 } = 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 = ?,
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,
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 = '' } = 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}%`);
}
// 获取总数
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
FROM users${whereClause}
ORDER BY audit_time 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, '供应商列表查询失败');
}
});
// 管理员统计 - 获取货源创建统计数据
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表
const 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
]
);
connection.release();
sendResponse(res, true, { logId: result.insertId }, '日志记录成功');
} 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;