const express = require('express'); const mysql = require('mysql2'); const bodyParser = require('body-parser'); const path = require('path'); const compression = require('compression'); // 缓存热点数据 const personnelCache = new Map(); const cacheExpiry = 3600000; // 1小时缓存 // 永久缓存:存储所有可能的时间范围数据 const permanentCache = new Map(); // 普通缓存:存储最近访问的数据 const normalCache = new Map(); const normalCacheExpiry = 300000; // 5分钟缓存 // 缓存清理定时器 let cacheCleanupTimer = null; // 初始化缓存清理 function initCacheCleanup() { // 清理普通缓存 cacheCleanupTimer = setInterval(() => { console.log('清理普通缓存'); normalCache.clear(); }, normalCacheExpiry); } // 预加载数据 async function preloadData() { console.log('开始预加载数据...'); try { // 预加载客户统计数据 await preloadClientStats(); // 预加载客户活跃统计数据 await preloadActiveStats(); console.log('数据预加载完成'); } catch (error) { console.error('数据预加载失败:', error); } } // 预加载客户统计数据 async function preloadClientStats() { console.log('预加载客户统计数据...'); // 生成最近30天的日期范围 const now = new Date(); // 生成关键时间范围(对应前端时间范围选择器) const dateRanges = [ { start: now.toISOString().split('T')[0], end: now.toISOString().split('T')[0] }, // 今天 { start: new Date(now.getTime() - 1 * 24 * 60 * 60 * 1000).toISOString().split('T')[0], end: new Date(now.getTime() - 1 * 24 * 60 * 60 * 1000).toISOString().split('T')[0] }, // 昨天 { start: new Date(now.getTime() - 3 * 24 * 60 * 60 * 1000).toISOString().split('T')[0], end: now.toISOString().split('T')[0] }, // 最近3天 { start: new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000).toISOString().split('T')[0], end: now.toISOString().split('T')[0] }, // 最近7天 { start: new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000).toISOString().split('T')[0], end: now.toISOString().split('T')[0] }, // 最近30天 { start: new Date(now.getTime() - 90 * 24 * 60 * 60 * 1000).toISOString().split('T')[0], end: now.toISOString().split('T')[0] }, // 最近90天 ]; // 预加载每个时间范围的数据 for (const range of dateRanges) { try { await loadClientStatsForRange(range.start, range.end); } catch (error) { console.error(`预加载客户统计数据失败 [${range.start} - ${range.end}]:`, error); } } } // 加载指定时间范围的客户统计数据 async function loadClientStatsForRange(startDate, endDate) { const cacheKey = `client-stats-${startDate}-${endDate}`; try { // 计算时间范围 const startDateStr = `${startDate} 00:00:00`; const endDateStr = `${endDate} 23:59:59`; // 查询指定时间范围内的新增客户数 const newClientsSql = 'SELECT COUNT(*) as clientCount FROM users WHERE created_at BETWEEN ? AND ? AND type != ?'; const [newClientsResult] = await wechatPool.promise().query(newClientsSql, [startDateStr, endDateStr, 'Colleague']); const clientCount = newClientsResult[0].clientCount; // 查询客户分配情况 const clientAllocationSql = ` SELECT CASE WHEN um.userName IS NULL OR um.userName = '' THEN '公海池' ELSE um.userName END as managerName, COUNT(*) as clientCount FROM users u LEFT JOIN usermanagements um ON um.userId = u.userId WHERE u.created_at BETWEEN ? AND ? AND u.type != ? GROUP BY managerName ORDER BY clientCount DESC `; const [allocationResults] = await wechatPool.promise().query(clientAllocationSql, [startDateStr, endDateStr, 'Colleague']); // 查询客户跟进情况 const followStatusSql = ` SELECT SUM(CASE WHEN u.followup IS NOT NULL AND u.followup != '' THEN 1 ELSE 0 END) as followed, SUM(CASE WHEN u.followup IS NULL OR u.followup = '' THEN 1 ELSE 0 END) as notFollowed FROM users u WHERE u.created_at BETWEEN ? AND ? AND u.type != ? `; const [followResults] = await wechatPool.promise().query(followStatusSql, [startDateStr, endDateStr, 'Colleague']); const followData = followResults[0]; // 构建结果 const result = { success: true, data: { clientCount: clientCount, clientAllocation: allocationResults, followStatus: { followed: followData.followed || 0, notFollowed: followData.notFollowed || 0 } }, timestamp: Date.now() }; // 存储到永久缓存 permanentCache.set(cacheKey, result); console.log(`客户统计数据已缓存: ${cacheKey}`); return result; } catch (error) { console.error(`加载客户统计数据失败 [${startDate} - ${endDate}]:`, error); throw error; } } // 预加载客户活跃统计数据 async function preloadActiveStats() { console.log('预加载客户活跃统计数据...'); // 生成最近30天的日期范围 const now = new Date(); // 生成关键时间范围(对应前端时间范围选择器) const dateRanges = [ { start: now.toISOString().split('T')[0], end: now.toISOString().split('T')[0] }, // 今天 { start: new Date(now.getTime() - 1 * 24 * 60 * 60 * 1000).toISOString().split('T')[0], end: new Date(now.getTime() - 1 * 24 * 60 * 60 * 1000).toISOString().split('T')[0] }, // 昨天 { start: new Date(now.getTime() - 3 * 24 * 60 * 60 * 1000).toISOString().split('T')[0], end: now.toISOString().split('T')[0] }, // 最近3天 { start: new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000).toISOString().split('T')[0], end: now.toISOString().split('T')[0] }, // 最近7天 { start: new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000).toISOString().split('T')[0], end: now.toISOString().split('T')[0] }, // 最近30天 { start: new Date(now.getTime() - 90 * 24 * 60 * 60 * 1000).toISOString().split('T')[0], end: now.toISOString().split('T')[0] }, // 最近90天 ]; // 预加载每个时间范围的数据 for (const range of dateRanges) { try { await loadActiveStatsForRange(range.start, range.end); } catch (error) { console.error(`预加载客户活跃统计数据失败 [${range.start} - ${range.end}]:`, error); } } } // 加载指定时间范围的客户活跃统计数据 async function loadActiveStatsForRange(startDate, endDate) { const cacheKey = `active-stats-${startDate}-${endDate}`; try { // 计算时间范围 const startDateStr = `${startDate} 00:00:00`; const endDateStr = `${endDate} 23:59:59`; // 从user_active_logs表中查询活跃统计数据 const activeStatsSql = ` SELECT COUNT(DISTINCT user_id) as totalActive, SUM(active_duration) as totalDuration FROM user_active_logs WHERE active_date BETWEEN DATE(?) AND DATE(?) `; const [statsResult] = await wechatPool.promise().query(activeStatsSql, [startDateStr, endDateStr]); const stats = statsResult[0]; // 查询一周内活跃客户数 const weekActiveSql = ` SELECT COUNT(DISTINCT user_id) as weekActive FROM user_active_logs WHERE active_date >= DATE(DATE_SUB(NOW(), INTERVAL 7 DAY)) `; const [weekActiveResult] = await wechatPool.promise().query(weekActiveSql); const weekActive = weekActiveResult[0].weekActive; // 构建结果 const result = { success: true, data: { totalActive: stats.totalActive || 0, weekActive: weekActive || 0, totalDuration: parseFloat(stats.totalDuration) / 3600 || 0, // 转换为小时 dateRange: { start: startDateStr, end: endDateStr } }, timestamp: Date.now() }; // 存储到永久缓存 permanentCache.set(cacheKey, result); console.log(`客户活跃统计数据已缓存: ${cacheKey}`); return result; } catch (error) { console.error(`加载客户活跃统计数据失败 [${startDate} - ${endDate}]:`, error); throw error; } } // 定期清理缓存 setInterval(() => { personnelCache.clear(); console.log('缓存已清理'); }, cacheExpiry); const app = express(); const port = 3005; // 响应压缩中间件 app.use(compression({ threshold: 1024, // 1KB以上压缩 level: 6, // 中等压缩级别 filter: (req, res) => { // 排除已压缩的资源类型 const contentType = res.getHeader('Content-Type'); if (contentType) { return !/(jpg|jpeg|png|gif|svg|mp3|mp4|zip|rar|7z)/.test(contentType); } return true; } })); // 解析JSON请求体 app.use(bodyParser.json()); // 静态文件服务 app.use(express.static(__dirname)); // 缓存策略 app.use((req, res, next) => { // 对于API响应,设置为不缓存,确保每次都获取最新数据 if (req.path.startsWith('/api/')) { res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate'); res.setHeader('Pragma', 'no-cache'); res.setHeader('Expires', '0'); } next(); }); // 数据库连接配置 const dbConfig = { host: '1.95.162.61', port: 3306, user: 'root', password: 'schl@2025', timezone: '+08:00' // 设置时区为东八区 }; // 创建主库连接池(写操作) const masterPool = mysql.createPool({ ...dbConfig, database: 'userlogin', waitForConnections: true, connectionLimit: 10, // 写操作连接池较小 queueLimit: 1000, enableKeepAlive: true, keepAliveInitialDelay: 30000,// 保持连接初始延迟30秒 connectTimeout: 10000 }); // 创建从库连接池(读操作) const slavePool = mysql.createPool({ ...dbConfig, database: 'userlogin', waitForConnections: true, connectionLimit: 20, // 读操作连接池较大 queueLimit: 2000,// 读操作队列较大 enableKeepAlive: true,// 启用保持连接 keepAliveInitialDelay: 30000,// 保持连接初始延迟30秒 connectTimeout: 10000// 连接超时10秒 }); // 创建wechat_app数据库连接池(用于操作日志) const wechatPool = mysql.createPool({ ...dbConfig, database: 'wechat_app', waitForConnections: true, connectionLimit: 15, queueLimit: 0, enableKeepAlive: true, keepAliveInitialDelay: 30000, connectTimeout: 10000 }); // 处理连接池错误 wechatPool.on('error', (err) => { console.error('数据库连接池错误:', err); // 连接池会自动尝试重新连接 }); // 创建eggbar数据库连接池(用于论坛动态) const eggbarPool = mysql.createPool({ ...dbConfig, database: 'eggbar', waitForConnections: true, connectionLimit: 15, queueLimit: 0, enableKeepAlive: true, keepAliveInitialDelay: 30000, connectTimeout: 10000 }); // 读写分离路由中间件 function getDbPool(operationType = 'read') { // 在实际生产环境中,这里会根据操作类型选择主库或从库 // 由于是示例环境,我们暂时都使用同一个库,但保留配置结构 if (operationType === 'write') { return masterPool; } else { return slavePool; } } // 处理连接池错误 masterPool.on('error', (err) => { console.error('主库连接池错误:', err); }); slavePool.on('error', (err) => { console.error('从库连接池错误:', err); }); // 处理eggbar连接池错误 eggbarPool.on('error', (err) => { console.error('eggbar数据库连接池错误:', err); // 连接池会自动尝试重新连接 }); // 导入node-schedule库用于定时任务 const schedule = require('node-schedule'); // 数据同步任务:将usertraces表中的数据同步到user_active_logs和user_product_views表中 async function syncUserTracesToActiveLogs() { try { console.log('开始同步用户活跃数据...'); // 获取今天的日期范围 const today = new Date(); today.setHours(0, 0, 0, 0); const todayStart = today; const todayEnd = new Date(); todayEnd.setHours(23, 59, 59, 999); const todayStr = today.toISOString().split('T')[0]; console.log(`同步日期: ${todayStr}`); // 查询今天的用户操作记录 const operationsSql = ` SELECT userId, operationTime, originalData FROM usertraces WHERE operationTime BETWEEN ? AND ? `; // 执行查询 const [operations] = await wechatPool.promise().query(operationsSql, [todayStart, todayEnd]); console.log(`查询到 ${operations.length} 条操作记录`); // 按用户和日期分组 const userOperations = {}; const productViews = []; operations.forEach(op => { const key = op.userId; if (!userOperations[key]) { userOperations[key] = []; } userOperations[key].push(op); // 检查是否为商品浏览记录 if (isProductView(op.originalData)) { try { const data = typeof op.originalData === 'string' ? JSON.parse(op.originalData) : op.originalData || {}; const productId = data.productId || data.id; if (productId) { productViews.push({ userId: op.userId, productId: productId, viewTime: op.operationTime, activeDate: todayStr }); } } catch (e) { // 忽略无效的商品数据 } } }); console.log(`提取到 ${Object.keys(userOperations).length} 个活跃用户`); console.log(`提取到 ${productViews.length} 条商品浏览记录`); // 计算每个用户的活跃信息并插入到user_active_logs表中 for (const userId in userOperations) { const ops = userOperations[userId]; const duration = calculateActiveDuration(ops); const operationCount = ops.length; const productViewCount = ops.filter(op => isProductView(op.originalData)).length; const favoriteCount = ops.filter(op => isFavoriteAction(op.originalData)).length; const lastActiveTime = ops[ops.length - 1].operationTime; // 插入或更新活跃记录 const upsertSql = ` INSERT INTO user_active_logs (user_id, active_date, active_duration, operation_count, product_view_count, favorite_count, last_active_time) VALUES (?, ?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE active_duration = VALUES(active_duration), operation_count = VALUES(operation_count), product_view_count = VALUES(product_view_count), favorite_count = VALUES(favorite_count), last_active_time = VALUES(last_active_time) `; await wechatPool.promise().query(upsertSql, [ userId, todayStr, duration, operationCount, productViewCount, favoriteCount, lastActiveTime ]); } console.log('用户活跃信息同步完成'); // 插入浏览商品记录 for (const view of productViews) { const insertSql = ` INSERT INTO user_product_views (user_id, active_date, product_id, view_count, first_view_time, last_view_time) VALUES (?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE view_count = view_count + 1, last_view_time = VALUES(last_view_time) `; await wechatPool.promise().query(insertSql, [ view.userId, view.activeDate, view.productId, 1, // 初始浏览次数 view.viewTime, // 首次浏览时间 view.viewTime // 最后浏览时间 ]); } console.log('商品浏览记录同步完成'); console.log('用户活跃数据同步完成'); } catch (error) { console.error('同步用户活跃数据失败:', error); } } // 初始化数据同步任务:同步历史数据 async function initSyncHistoricalData() { try { console.log('开始初始化同步历史数据...'); // 计算30天前的日期 const thirtyDaysAgo = new Date(); thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30); thirtyDaysAgo.setHours(0, 0, 0, 0); // 查询30天内的用户操作记录 const operationsSql = ` SELECT userId, operationTime, originalData FROM usertraces WHERE operationTime >= ? `; // 执行查询 const [operations] = await wechatPool.promise().query(operationsSql, [thirtyDaysAgo]); console.log(`查询到 ${operations.length} 条历史操作记录`); // 按用户和日期分组 const userDateOperations = {}; const productViews = []; operations.forEach(op => { const date = new Date(op.operationTime).toISOString().split('T')[0]; const key = `${op.userId}_${date}`; if (!userDateOperations[key]) { userDateOperations[key] = { userId: op.userId, date: date, operations: [] }; } userDateOperations[key].operations.push(op); // 检查是否为商品浏览记录 if (isProductView(op.originalData)) { try { const data = typeof op.originalData === 'string' ? JSON.parse(op.originalData) : op.originalData || {}; const productId = data.productId || data.id; if (productId) { productViews.push({ userId: op.userId, productId: productId, viewTime: op.operationTime, activeDate: date }); } } catch (e) { // 忽略无效的商品数据 } } }); console.log(`提取到 ${Object.keys(userDateOperations).length} 条用户日期分组记录`); console.log(`提取到 ${productViews.length} 条历史商品浏览记录`); // 计算每个用户每天的活跃信息并插入到user_active_logs表中 let userLogCount = 0; for (const key in userDateOperations) { const { userId, date, operations } = userDateOperations[key]; const duration = calculateActiveDuration(operations); const operationCount = operations.length; const productViewCount = operations.filter(op => isProductView(op.originalData)).length; const favoriteCount = operations.filter(op => isFavoriteAction(op.originalData)).length; const lastActiveTime = operations[operations.length - 1].operationTime; // 插入或更新活跃记录 const upsertSql = ` INSERT INTO user_active_logs (user_id, active_date, active_duration, operation_count, product_view_count, favorite_count, last_active_time) VALUES (?, ?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE active_duration = VALUES(active_duration), operation_count = VALUES(operation_count), product_view_count = VALUES(product_view_count), favorite_count = VALUES(favorite_count), last_active_time = VALUES(last_active_time) `; await wechatPool.promise().query(upsertSql, [ userId, date, duration, operationCount, productViewCount, favoriteCount, lastActiveTime ]); userLogCount++; } console.log(`同步了 ${userLogCount} 条用户活跃记录`); // 插入历史浏览商品记录 let viewCount = 0; for (const view of productViews) { const insertSql = ` INSERT INTO user_product_views (user_id, active_date, product_id, view_count, first_view_time, last_view_time) VALUES (?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE view_count = view_count + 1, last_view_time = VALUES(last_view_time) `; await wechatPool.promise().query(insertSql, [ view.userId, view.activeDate, view.productId, 1, // 初始浏览次数 view.viewTime, // 首次浏览时间 view.viewTime // 最后浏览时间 ]); viewCount++; } console.log(`同步了 ${viewCount} 条商品浏览记录`); console.log('历史数据初始化同步完成'); } catch (error) { console.error('初始化同步历史数据失败:', error); } } // 设置定时任务,每天同步今天的客户数据 // 每天每小时同步一次 const syncJob = schedule.scheduleJob('0 * * * *', async function() { console.log('定时任务执行:同步用户活跃数据'); await syncUserTracesToActiveLogs(); }); // 初始化缓存清理 initCacheCleanup(); // 应用启动时执行一次初始化同步 console.log('应用启动,开始初始化同步历史数据...'); initSyncHistoricalData().catch(console.error); // 应用启动时执行一次当天数据同步 console.log('应用启动,开始同步当天数据...'); syncUserTracesToActiveLogs().catch(console.error); // 应用启动时预加载数据 console.log('应用启动,开始预加载数据...'); preloadData().catch(console.error); // 客户活跃统计概览(兼容前端调用) app.get('/api/active-stats', (req, res) => { try { // 获取时间范围参数 const { startDate, endDate } = req.query; // 计算默认日期范围(最近7天) const now = new Date(); const defaultEndStr = now.toISOString().slice(0, 19).replace('T', ' '); const sevenDaysAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); const defaultStartStr = sevenDaysAgo.toISOString().slice(0, 19).replace('T', ' '); // 使用提供的时间范围或默认值 const startDateStr = startDate ? `${startDate} 00:00:00` : defaultStartStr; const endDateStr = endDate ? `${endDate} 23:59:59` : defaultEndStr; // 生成缓存键 const cacheKey = `active-stats-${startDateStr.split(' ')[0]}-${endDateStr.split(' ')[0]}`; // 检查永久缓存 if (permanentCache.has(cacheKey)) { const cachedData = permanentCache.get(cacheKey); console.log(`从永久缓存返回客户活跃统计数据: ${cacheKey}`); // 立即返回缓存数据 res.json(cachedData); // 后台异步更新缓存 console.log(`后台更新客户活跃统计数据缓存: ${cacheKey}`); loadActiveStatsForRange(startDateStr.split(' ')[0], endDateStr.split(' ')[0]).catch(console.error); return; } // 检查普通缓存 if (normalCache.has(cacheKey)) { const cachedData = normalCache.get(cacheKey); console.log(`从普通缓存返回客户活跃统计数据: ${cacheKey}`); // 立即返回缓存数据 res.json(cachedData); // 后台异步更新缓存 console.log(`后台更新客户活跃统计数据缓存: ${cacheKey}`); loadActiveStatsForRange(startDateStr.split(' ')[0], endDateStr.split(' ')[0]).catch(console.error); return; } // 从user_active_logs表中查询活跃统计数据 const activeStatsSql = ` SELECT COUNT(DISTINCT ual.user_id) as totalActive, SUM(ual.active_duration) as totalDuration FROM user_active_logs ual JOIN users u ON ual.user_id = u.userId WHERE ual.active_date BETWEEN DATE(?) AND DATE(?) AND u.type != 'Colleague' `; // 查询一周内活跃客户数 const weekActiveSql = ` SELECT COUNT(DISTINCT ual.user_id) as weekActive FROM user_active_logs ual JOIN users u ON ual.user_id = u.userId WHERE ual.active_date >= DATE(DATE_SUB(NOW(), INTERVAL 7 DAY)) AND u.type != 'Colleague' `; // 并行执行查询 Promise.all([ new Promise((resolve, reject) => { wechatPool.query(activeStatsSql, [startDateStr, endDateStr], (err, results) => { if (err) reject(err); else resolve(results[0]); }); }), new Promise((resolve, reject) => { wechatPool.query(weekActiveSql, [], (err, results) => { if (err) reject(err); else resolve(results[0].weekActive); }); }) ]) .then(([stats, weekActive]) => { const result = { success: true, data: { totalActive: stats.totalActive || 0, weekActive: weekActive || 0, totalDuration: stats.totalDuration || 0, dateRange: { start: startDateStr, end: endDateStr } }, timestamp: Date.now() }; // 存储到普通缓存 normalCache.set(cacheKey, result); console.log(`客户活跃统计数据已存储到普通缓存: ${cacheKey}`); res.json(result); }) .catch(error => { console.error('获取活跃统计数据失败:', error); res.json({ success: false, message: '获取活跃统计数据失败' }); }); } catch (error) { console.error('获取活跃统计数据失败:', error); res.json({ success: false, message: '获取活跃统计数据失败' }); } }); // 基于新表的客户活跃统计概览 app.get('/api/active-stats/new', (req, res) => { try { // 获取时间范围参数 const { startDate, endDate } = req.query; console.log(`[${new Date().toISOString()}] API /api/active-stats/new 被调用,接收参数: startDate=${startDate}, endDate=${endDate}`); // 计算默认日期范围(最近7天) const now = new Date(); const defaultEndStr = now.toISOString().slice(0, 19).replace('T', ' '); const sevenDaysAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); const defaultStartStr = sevenDaysAgo.toISOString().slice(0, 19).replace('T', ' '); // 使用提供的时间范围或默认值 const startDateStr = startDate ? `${startDate} 00:00:00` : defaultStartStr; const endDateStr = endDate ? `${endDate} 23:59:59` : defaultEndStr; console.log(`[${new Date().toISOString()}] API /api/active-stats/new 使用时间范围: startDateStr=${startDateStr}, endDateStr=${endDateStr}`); // 从user_active_logs表中查询活跃统计数据 const activeStatsSql = ` SELECT COUNT(DISTINCT user_id) as totalActive, SUM(active_duration) as totalDuration FROM user_active_logs WHERE active_date BETWEEN DATE(?) AND DATE(?) `; // 查询一周内活跃客户数 const weekActiveSql = ` SELECT COUNT(DISTINCT user_id) as weekActive FROM user_active_logs WHERE active_date >= DATE(DATE_SUB(NOW(), INTERVAL 7 DAY)) `; // 并行执行查询 Promise.all([ new Promise((resolve, reject) => { wechatPool.query(activeStatsSql, [startDateStr, endDateStr], (err, results) => { if (err) reject(err); else resolve(results[0]); }); }), new Promise((resolve, reject) => { wechatPool.query(weekActiveSql, [], (err, results) => { if (err) reject(err); else resolve(results[0].weekActive); }); }) ]) .then(([stats, weekActive]) => { res.json({ success: true, data: { totalActive: stats.totalActive || 0, weekActive: weekActive || 0, totalDuration: stats.totalDuration || 0, dateRange: { start: startDateStr, end: endDateStr } } }); }) .catch(error => { console.error('获取活跃统计数据失败:', error); res.json({ success: false, message: '获取活跃统计数据失败' }); }); } catch (error) { console.error('获取活跃统计数据失败:', error); res.json({ success: false, message: '获取活跃统计数据失败' }); } }); // 客户活跃详情列表(兼容前端调用) app.get('/api/active-stats/details', (req, res) => { try { // 获取分页参数 const page = parseInt(req.query.page) || 1; const pageSize = parseInt(req.query.pageSize) || 10; const offset = (page - 1) * pageSize; // 获取时间范围参数 const { startDate, endDate } = req.query; // 计算默认日期范围(最近7天) const now = new Date(); const defaultEndStr = now.toISOString().slice(0, 19).replace('T', ' '); const sevenDaysAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); const defaultStartStr = sevenDaysAgo.toISOString().slice(0, 19).replace('T', ' '); // 使用提供的时间范围或默认值 const startDateStr = startDate ? `${startDate} 00:00:00` : defaultStartStr; const endDateStr = endDate ? `${endDate} 23:59:59` : defaultEndStr; // 查询总数 const countSql = ` SELECT COUNT(DISTINCT ual.user_id) as total FROM user_active_logs ual JOIN users u ON ual.user_id = u.userId WHERE ual.active_date BETWEEN DATE(?) AND DATE(?) AND u.type != 'Colleague' `; // 查询客户活跃详情 const detailsSql = ` SELECT ual.user_id, u.phoneNumber, u.created_at as registerTime, SUM(ual.active_duration) as totalDuration, MAX(ual.last_active_time) as lastActiveTime FROM user_active_logs ual JOIN users u ON ual.user_id = u.userId WHERE ual.active_date BETWEEN DATE(?) AND DATE(?) AND u.type != 'Colleague' GROUP BY ual.user_id, u.phoneNumber, u.created_at ORDER BY lastActiveTime DESC LIMIT ? OFFSET ? `; // 并行执行查询 Promise.all([ new Promise((resolve, reject) => { wechatPool.query(countSql, [startDateStr, endDateStr], (err, results) => { if (err) reject(err); else resolve(results[0].total); }); }), new Promise((resolve, reject) => { wechatPool.query(detailsSql, [startDateStr, endDateStr, pageSize, offset], (err, results) => { if (err) reject(err); else resolve(results); }); }) ]) .then(([total, users]) => { res.json({ success: true, data: { users: users, total: total, dateRange: { start: startDateStr, end: endDateStr } } }); }) .catch(error => { console.error('获取客户活跃详情列表失败:', error); res.json({ success: false, message: '获取客户活跃详情列表失败' }); }); } catch (error) { console.error('获取客户活跃详情列表失败:', error); res.json({ success: false, message: '获取客户活跃详情列表失败' }); } }); // 基于新表的客户活跃详情列表 app.get('/api/active-stats/details/new', (req, res) => { try { // 获取分页参数 const page = parseInt(req.query.page) || 1; const pageSize = parseInt(req.query.pageSize) || 10; const offset = (page - 1) * pageSize; // 获取时间范围参数 const { startDate, endDate } = req.query; // 计算默认日期范围(最近7天) const now = new Date(); const defaultEndStr = now.toISOString().slice(0, 19).replace('T', ' '); const sevenDaysAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); const defaultStartStr = sevenDaysAgo.toISOString().slice(0, 19).replace('T', ' '); // 使用提供的时间范围或默认值 const startDateStr = startDate ? `${startDate} 00:00:00` : defaultStartStr; const endDateStr = endDate ? `${endDate} 23:59:59` : defaultEndStr; // 查询总数 const countSql = ` SELECT COUNT(DISTINCT ual.user_id) as total FROM user_active_logs ual JOIN users u ON ual.user_id = u.userId WHERE ual.active_date BETWEEN DATE(?) AND DATE(?) AND u.type != 'Colleague' `; // 查询客户活跃详情 const detailsSql = ` SELECT ual.user_id, u.phoneNumber, u.created_at as registerTime, SUM(ual.active_duration) as totalDuration, MAX(ual.last_active_time) as lastActiveTime FROM user_active_logs ual JOIN users u ON ual.user_id = u.userId WHERE ual.active_date BETWEEN DATE(?) AND DATE(?) AND u.type != 'Colleague' GROUP BY ual.user_id, u.phoneNumber, u.created_at ORDER BY lastActiveTime DESC LIMIT ? OFFSET ? `; // 并行执行查询 Promise.all([ new Promise((resolve, reject) => { wechatPool.query(countSql, [startDateStr, endDateStr], (err, results) => { if (err) reject(err); else resolve(results[0].total); }); }), new Promise((resolve, reject) => { wechatPool.query(detailsSql, [startDateStr, endDateStr, pageSize, offset], (err, results) => { if (err) reject(err); else resolve(results); }); }) ]) .then(([total, users]) => { res.json({ success: true, data: { users: users, total: total, dateRange: { start: startDateStr, end: endDateStr } } }); }) .catch(error => { console.error('获取客户活跃详情列表失败:', error); res.json({ success: false, message: '获取客户活跃详情列表失败' }); }); } catch (error) { console.error('获取客户活跃详情列表失败:', error); res.json({ success: false, message: '获取客户活跃详情列表失败' }); } }); // 获取用户浏览商品记录 app.get('/api/user-product-views', (req, res) => { try { const { userId, startDate, endDate, limit = 50 } = req.query; if (!userId) { return res.json({ success: false, message: '缺少userId参数' }); } let whereClause = 'WHERE user_id = ?'; let params = [userId]; if (startDate) { whereClause += ' AND active_date >= ?'; params.push(startDate); } if (endDate) { whereClause += ' AND active_date <= ?'; params.push(endDate); } const sql = ` SELECT * FROM user_product_views ${whereClause} ORDER BY last_view_time DESC LIMIT ? `; params.push(parseInt(limit)); wechatPool.query(sql, params, (err, results) => { if (err) { console.error('查询浏览商品记录失败:', err); return res.json({ success: false, message: '获取浏览商品记录失败' }); } res.json({ success: true, data: results }); }); } catch (error) { console.error('获取浏览商品记录失败:', error); res.json({ success: false, message: '获取浏览商品记录失败' }); } }); // 获取商品被浏览记录 app.get('/api/product-views', (req, res) => { try { const { productId, startDate, endDate, limit = 50 } = req.query; if (!productId) { return res.json({ success: false, message: '缺少productId参数' }); } let whereClause = 'WHERE product_id = ?'; let params = [productId]; if (startDate) { whereClause += ' AND active_date >= ?'; params.push(startDate); } if (endDate) { whereClause += ' AND active_date <= ?'; params.push(endDate); } const sql = ` SELECT * FROM user_product_views ${whereClause} ORDER BY last_view_time DESC LIMIT ? `; params.push(parseInt(limit)); wechatPool.query(sql, params, (err, results) => { if (err) { console.error('查询商品被浏览记录失败:', err); return res.json({ success: false, message: '获取商品被浏览记录失败' }); } res.json({ success: true, data: results }); }); } catch (error) { console.error('获取商品被浏览记录失败:', error); res.json({ success: false, message: '获取商品被浏览记录失败' }); } }); // 获取用户浏览商品统计 app.get('/api/user-product-views/stats', (req, res) => { try { const { userId, startDate, endDate } = req.query; if (!userId) { return res.json({ success: false, message: '缺少userId参数' }); } let whereClause = 'WHERE user_id = ?'; let params = [userId]; if (startDate) { whereClause += ' AND active_date >= ?'; params.push(startDate); } if (endDate) { whereClause += ' AND active_date <= ?'; params.push(endDate); } const sql = ` SELECT active_date, COUNT(*) as view_count, COUNT(DISTINCT product_id) as product_count, SUM(view_count) as total_views FROM user_product_views ${whereClause} GROUP BY active_date ORDER BY active_date DESC `; wechatPool.query(sql, params, (err, results) => { if (err) { console.error('查询用户浏览商品统计失败:', err); return res.json({ success: false, message: '获取浏览商品统计失败' }); } res.json({ success: true, data: results }); }); } catch (error) { console.error('获取浏览商品统计失败:', error); res.json({ success: false, message: '获取浏览商品统计失败' }); } }); // 登录接口 app.post('/login', (req, res) => { const { projectName, userName, password } = req.body; // 验证参数 if (!projectName || !userName || !password) { return res.json({ success: false, message: '请填写完整的登录信息' }); } // 查询数据库 const sql = 'SELECT * FROM login WHERE userName = ?'; getDbPool('read').execute(sql, [userName], (err, results) => { if (err) { console.error('数据库查询失败:', err); return res.json({ success: false, message: '登录失败,请稍后重试' }); } // 检查用户是否存在 if (results.length === 0) { return res.json({ success: false, message: '用户名不存在' }); } const user = results[0]; // 检查密码是否正确 if (user.password !== password) { return res.json({ success: false, message: '密码错误' }); } // 检查职位名称是否匹配 if (user.projectName !== projectName) { return res.json({ success: false, message: '职位名称不匹配' }); } // 登录成功 res.json({ success: true, message: '登录成功' }); }); }); // 获取操作日志列表 app.get('/api/logs', (req, res) => { const page = parseInt(req.query.page) || 1; const pageSize = parseInt(req.query.pageSize) || 10; const userName = req.query.userName || ''; const userId = req.query.userId || ''; const phoneNumber = req.query.phoneNumber || ''; const system = req.query.system || ''; const startDate = req.query.startDate || ''; const endDate = req.query.endDate || ''; // 计算偏移量 const offset = (page - 1) * pageSize; // 构建查询条件 let whereClause = ''; let countParams = []; let dataParams = []; if (userName) { whereClause += whereClause ? ' AND ' : ' WHERE '; whereClause += 'trauserName LIKE ?'; countParams.push(`%${userName}%`); dataParams.push(`%${userName}%`); } if (userId) { whereClause += whereClause ? ' AND ' : ' WHERE '; whereClause += 'i.userId LIKE ?'; countParams.push(`%${userId}%`); dataParams.push(`%${userId}%`); } if (startDate) { whereClause += whereClause ? ' AND ' : ' WHERE '; whereClause += 'i.operationTime >= ?'; countParams.push(startDate); dataParams.push(startDate); } if (endDate) { whereClause += whereClause ? ' AND ' : ' WHERE '; whereClause += 'i.operationTime <= ?'; countParams.push(`${endDate} 23:59:59`); dataParams.push(`${endDate} 23:59:59`); } // 电话号码筛选 if (phoneNumber) { whereClause += whereClause ? ' AND ' : ' WHERE '; whereClause += 'u.phoneNumber LIKE ?'; countParams.push(`%${phoneNumber}%`); dataParams.push(`%${phoneNumber}%`); } // 系统筛选 if (system) { whereClause += whereClause ? ' AND ' : ' WHERE '; whereClause += 'i.operationEvent LIKE ?'; countParams.push(`${system}-%`); dataParams.push(`${system}-%`); } // 添加分页参数 dataParams.push(pageSize); dataParams.push(offset); // 查询总数 const countSql = `SELECT COUNT(*) as total FROM informationtra i LEFT JOIN users u ON i.userId = u.userId${whereClause}`; // 查询数据 const dataSql = `SELECT i.*, u.phoneNumber FROM informationtra i LEFT JOIN users u ON i.userId = u.userId${whereClause} ORDER BY i.operationTime DESC LIMIT ? OFFSET ?`; // 先查询总数 wechatPool.query(countSql, countParams, (countErr, countResults) => { if (countErr) { console.error('查询日志总数失败:', countErr); return res.json({ success: false, message: '查询日志失败,请稍后重试' }); } const total = countResults[0].total; // 再查询数据 wechatPool.query(dataSql, dataParams, (dataErr, dataResults) => { if (dataErr) { console.error('查询日志数据失败:', dataErr); return res.json({ success: false, message: '查询日志失败,请稍后重试' }); } res.json({ success: true, logs: dataResults, total }); }); }); }); // 获取单个操作日志详情 app.get('/api/logs/:id', (req, res) => { const logId = req.params.id; const sql = 'SELECT i.*, u.phoneNumber FROM informationtra i LEFT JOIN users u ON i.userId = u.userId WHERE i.id = ?'; wechatPool.query(sql, [logId], (err, results) => { if (err) { console.error('查询日志详情失败:', err); return res.json({ success: false, message: '查询日志详情失败,请稍后重试' }); } if (results.length === 0) { return res.json({ success: false, message: '日志不存在' }); } res.json({ success: true, log: results[0] }); }); }); // 获取统计数据 - 合并查询优化 app.get('/api/stats', (req, res) => { try { console.log(`[${new Date().toISOString()}] API /api/stats 被调用`); // 计算今天的日期范围 const today = new Date(); today.setHours(0, 0, 0, 0); const todayStr = today.toISOString().slice(0, 19).replace('T', ' '); // 合并查询:产品统计 + 活跃用户统计 const consolidatedSql = ` WITH dashboard_stats AS ( -- 产品统计 SELECT 'products_total' as metric_type, COUNT(*) as metric_value FROM products UNION ALL SELECT 'products_pending', COUNT(*) FROM products WHERE status = ? UNION ALL SELECT 'products_today', COUNT(*) FROM products WHERE created_at >= ? -- 活跃用户统计 UNION ALL SELECT 'total_active_1d', COUNT(DISTINCT ual.user_id) FROM user_active_logs ual JOIN users u ON ual.user_id = u.userId WHERE ual.active_date >= DATE(DATE_SUB(NOW(), INTERVAL 1 DAY)) AND u.phoneNumber IS NOT NULL AND u.phoneNumber != '' AND u.created_at IS NOT NULL AND u.type != 'Colleague' UNION ALL SELECT 'total_active_7d', COUNT(DISTINCT ual.user_id) FROM user_active_logs ual JOIN users u ON ual.user_id = u.userId WHERE ual.active_date >= DATE(DATE_SUB(NOW(), INTERVAL 7 DAY)) AND u.phoneNumber IS NOT NULL AND u.phoneNumber != '' AND u.created_at IS NOT NULL AND u.type != 'Colleague' ) SELECT * FROM dashboard_stats `; // 执行合并查询 wechatPool.query(consolidatedSql, ['pending_review', todayStr], (err, results) => { if (err) { console.error('获取统计数据失败:', err); res.json({ success: false, message: '获取统计数据失败' }); return; } // 处理查询结果 const statsMap = {}; results.forEach(row => { statsMap[row.metric_type] = row.metric_value; }); console.log(`[${new Date().toISOString()}] API /api/stats 查询结果:`, statsMap); res.json({ success: true, data: { total: statsMap.products_total || 0, pending: statsMap.products_pending || 0, today: statsMap.products_today || 0, active1d: statsMap.total_active_1d || 0, active7d: statsMap.total_active_7d || 0 } }); }); } catch (error) { console.error('获取统计数据失败:', error); res.json({ success: false, message: '获取统计数据失败' }); } }); // 获取客户统计数据 - 合并查询优化 app.get('/api/client-stats', (req, res) => { try { // 获取时间范围参数 const { startDate, endDate, newClientBy, followedClientBy } = req.query; console.log('API接收到的时间范围参数:', startDate, endDate); console.log('API接收到的时间标准参数:', newClientBy, followedClientBy); // 计算默认日期范围(今天) const today = new Date(); today.setHours(0, 0, 0, 0); const defaultStartStr = today.toISOString().slice(0, 19).replace('T', ' '); const now = new Date(); // 当前时间 const defaultEndStrFormatted = now.toISOString().slice(0, 19).replace('T', ' '); // 使用提供的时间范围或默认值 const startDateStr = startDate ? `${startDate} 00:00:00` : defaultStartStr; const endDateStr = endDate ? `${endDate} 23:59:59` : defaultEndStrFormatted; // 生成缓存键 const cacheKey = `client-stats-${startDateStr.split(' ')[0]}-${endDateStr.split(' ')[0]}`; // 检查永久缓存 if (permanentCache.has(cacheKey)) { const cachedData = permanentCache.get(cacheKey); console.log(`从永久缓存返回客户统计数据: ${cacheKey}`); // 立即返回缓存数据 res.json(cachedData); // 后台异步更新缓存 console.log(`后台更新客户统计数据缓存: ${cacheKey}`); loadClientStatsForRange(startDateStr.split(' ')[0], endDateStr.split(' ')[0]).catch(console.error); return; } // 检查普通缓存 if (normalCache.has(cacheKey)) { const cachedData = normalCache.get(cacheKey); console.log(`从普通缓存返回客户统计数据: ${cacheKey}`); // 立即返回缓存数据 res.json(cachedData); // 后台异步更新缓存 console.log(`后台更新客户统计数据缓存: ${cacheKey}`); loadClientStatsForRange(startDateStr.split(' ')[0], endDateStr.split(' ')[0]).catch(console.error); return; } console.log('使用的时间范围:', { startDateStr, endDateStr }); console.log('API使用的时间范围:', startDateStr, endDateStr); // 查询指定时间范围内的新增客户数(排除type值为Colleague的客户) const newClientsSql = 'SELECT COUNT(*) as clientCount FROM users WHERE created_at BETWEEN ? AND ? AND type != ?'; // 查询客户分配情况(排除type值为Colleague的客户) const clientAllocationSql = ` SELECT CASE WHEN um.userName IS NULL OR um.userName = '' THEN '公海池' ELSE um.userName END as managerName, COUNT(*) as clientCount FROM users u LEFT JOIN usermanagements um ON um.userId = u.userId WHERE u.created_at BETWEEN ? AND ? AND u.type != ? GROUP BY managerName ORDER BY clientCount DESC `; // 构建客户跟进情况查询 let followStatusSql = ''; let followParams = []; if (followedClientBy === 'updated_at') { // 已跟进客户:使用updated_at作为时间标准(包括今日新增和之前新增的) // 未跟进客户:使用created_at作为时间标准(仅今日新增客户中没有跟进的) followStatusSql = ` SELECT SUM(CASE WHEN (u.followup IS NOT NULL AND u.followup != '') AND u.updated_at BETWEEN ? AND ? THEN 1 ELSE 0 END) as followed, SUM(CASE WHEN (u.followup IS NULL OR u.followup = '') AND u.created_at BETWEEN ? AND ? THEN 1 ELSE 0 END) as notFollowed FROM users u WHERE u.type != ? `; followParams = [startDateStr, endDateStr, startDateStr, endDateStr, 'Colleague']; } else { // 默认:使用created_at作为时间标准 followStatusSql = ` SELECT SUM(CASE WHEN u.followup IS NOT NULL AND u.followup != '' THEN 1 ELSE 0 END) as followed, SUM(CASE WHEN u.followup IS NULL OR u.followup = '' THEN 1 ELSE 0 END) as notFollowed FROM users u WHERE u.created_at BETWEEN ? AND ? AND u.type != ? `; followParams = [startDateStr, endDateStr, 'Colleague']; } // 执行查询 wechatPool.query(newClientsSql, [startDateStr, endDateStr, 'Colleague'], (clientsErr, clientsResults) => { if (clientsErr) { console.error('查询新增客户数失败:', clientsErr); return res.json({ success: false, message: '获取客户统计数据失败' }); } const clientCount = clientsResults[0].clientCount; wechatPool.query(clientAllocationSql, [startDateStr, endDateStr, 'Colleague'], (allocationErr, allocationResults) => { if (allocationErr) { console.error('查询客户分配情况失败:', allocationErr); return res.json({ success: false, message: '获取客户统计数据失败' }); } wechatPool.query(followStatusSql, followParams, (followErr, followResults) => { if (followErr) { console.error('查询客户跟进情况失败:', followErr); return res.json({ success: false, message: '获取客户统计数据失败' }); } const followData = followResults[0]; const result = { success: true, data: { clientCount: clientCount, clientAllocation: allocationResults, followStatus: { followed: followData.followed || 0, notFollowed: followData.notFollowed || 0 } }, timestamp: Date.now() }; // 存储到普通缓存 normalCache.set(cacheKey, result); console.log(`客户统计数据已存储到普通缓存: ${cacheKey}`); // 返回结果 res.json(result); }); }); }); } catch (error) { console.error('获取客户统计数据失败:', error); res.json({ success: false, message: '获取客户统计数据失败' }); } }); // 获取客户详细信息 app.get('/api/client-details', (req, res) => { try { // 获取分页参数 const page = parseInt(req.query.page) || 1; const pageSize = parseInt(req.query.pageSize) || 10; // 获取时间范围参数 const { startDate, endDate, followStatus, isUnassigned, newClientBy, followedClientBy } = req.query; console.log('API接收到的参数:', { startDate, endDate, followStatus, isUnassigned, newClientBy, followedClientBy, page, pageSize }); // 计算默认日期范围(今天) const today = new Date(); today.setHours(0, 0, 0, 0); const defaultStartStr = today.toISOString().slice(0, 19).replace('T', ' '); const now = new Date(); // 当前时间 const defaultEndStrFormatted = now.toISOString().slice(0, 19).replace('T', ' '); // 使用提供的时间范围或默认值 const startDateStr = startDate ? `${startDate} 00:00:00` : defaultStartStr; const endDateStr = endDate ? `${endDate} 23:59:59` : defaultEndStrFormatted; // 生成缓存键 const cacheKey = `client-details-${startDateStr.split(' ')[0]}-${endDateStr.split(' ')[0]}-${followStatus || 'all'}-${isUnassigned || 'false'}-${page}-${pageSize}`; // 检查普通缓存 if (normalCache.has(cacheKey)) { const cachedData = normalCache.get(cacheKey); console.log(`从普通缓存返回客户详情数据: ${cacheKey}`); // 立即返回缓存数据 res.json(cachedData); // 后台异步更新缓存 console.log(`后台更新客户详情数据缓存: ${cacheKey}`); // 这里可以添加后台更新缓存的逻辑 return; } // 构建查询条件 let whereClause = 'WHERE u.type != ?'; let params = ['Colleague']; // 添加跟进状态筛选 if (followStatus === 'followed') { whereClause += " AND (u.followup IS NOT NULL AND u.followup != '')"; // 已跟进客户:使用updated_at作为时间标准,兼容时间筛选 whereClause += ' AND u.updated_at BETWEEN ? AND ?'; params.push(startDateStr, endDateStr); } else if (followStatus === 'notFollowed') { whereClause += " AND (u.followup IS NULL OR u.followup = '')"; // 未跟进客户:使用created_at作为时间标准 whereClause += ' AND u.created_at BETWEEN ? AND ?'; params.push(startDateStr, endDateStr); } else { // 其他情况:使用created_at作为时间标准 whereClause += ' AND u.created_at BETWEEN ? AND ?'; params.push(startDateStr, endDateStr); } // 添加未分配筛选 if (isUnassigned === 'true') { whereClause = 'WHERE u.type != ?'; // 重置params到基础参数 params = ['Colleague']; // 根据跟进状态添加时间筛选条件 if (followStatus === 'followed') { whereClause += " AND (u.followup IS NOT NULL AND u.followup != '')"; // 已跟进客户:使用updated_at作为时间标准 whereClause += ' AND u.updated_at BETWEEN ? AND ?'; params.push(startDateStr, endDateStr); } else if (followStatus === 'notFollowed') { whereClause += " AND (u.followup IS NULL OR u.followup = '')"; // 未跟进客户:使用created_at作为时间标准 whereClause += ' AND u.created_at BETWEEN ? AND ?'; params.push(startDateStr, endDateStr); } else { // 其他情况:使用created_at作为时间标准 whereClause += ' AND u.created_at BETWEEN ? AND ?'; params.push(startDateStr, endDateStr); } // 查询未分配客户,即负责人为空的客户 whereClause += ` AND NOT EXISTS (SELECT 1 FROM usermanagements um WHERE um.userId = u.userId AND um.userName IS NOT NULL AND um.userName != '')`; } // 计算偏移量 const offset = (page - 1) * pageSize; // 查询总数 const countSql = `SELECT COUNT(*) as total FROM users u ${whereClause}`; console.log('执行的countSql:', countSql); console.log('countSql参数:', params); // 查询客户详细信息 const clientDetailsSql = ` SELECT u.nickName, u.phoneNumber, u.followup, u.followup_at, u.updated_at, CASE WHEN u.followup IS NOT NULL AND u.followup != '' THEN '已跟进' ELSE '未跟进' END as status, CASE WHEN um.userName IS NULL OR um.userName = '' THEN '公海池' ELSE um.userName END as userName FROM users u LEFT JOIN usermanagements um ON um.userId = u.userId ${whereClause} ORDER BY u.created_at DESC LIMIT ? OFFSET ? `; console.log('执行的clientDetailsSql:', clientDetailsSql); // 执行查询 wechatPool.query(countSql, params, (countErr, countResults) => { if (countErr) { console.error('查询客户总数失败:', countErr); return res.json({ success: false, message: '获取客户详细信息失败' }); } const total = countResults[0].total; // 添加分页参数 const dataParams = [...params, pageSize, offset]; // 查询分页数据 wechatPool.query(clientDetailsSql, dataParams, (dataErr, dataResults) => { if (dataErr) { console.error('查询客户详细信息失败:', dataErr); return res.json({ success: false, message: '获取客户详细信息失败' }); } const result = { success: true, data: dataResults, total: total, timestamp: Date.now() }; // 存储到普通缓存 normalCache.set(cacheKey, result); console.log(`客户详情数据已存储到普通缓存: ${cacheKey}`); res.json(result); }); }); } catch (error) { console.error('获取客户详细信息失败:', error); res.json({ success: false, message: '获取客户详细信息失败' }); } }); // 获取指定业务员的客户信息 app.get('/api/manager-clients', (req, res) => { try { // 获取分页参数 const page = parseInt(req.query.page) || 1; const pageSize = parseInt(req.query.pageSize) || 10; // 获取参数 const { managerName, startDate, endDate } = req.query; // 计算默认日期范围(今天) const today = new Date(); today.setHours(0, 0, 0, 0); const defaultStartStr = today.toISOString().slice(0, 19).replace('T', ' '); const now = new Date(); // 当前时间 const defaultEndStrFormatted = now.toISOString().slice(0, 19).replace('T', ' '); // 使用提供的时间范围或默认值 const startDateStr = startDate ? `${startDate} 00:00:00` : defaultStartStr; const endDateStr = endDate ? `${endDate} 23:59:59` : defaultEndStrFormatted; // 构建查询条件和参数 const params = [managerName, startDateStr, endDateStr, 'Colleague']; // 计算偏移量 const offset = (page - 1) * pageSize; // 查询总数 const countSql = ` SELECT COUNT(*) as total FROM usermanagements um JOIN users u ON um.userId = u.userId WHERE um.userName = ? AND u.created_at BETWEEN ? AND ? AND u.type != ? `; // 查询指定业务员的客户信息 const managerClientsSql = ` SELECT u.nickName, u.phoneNumber, u.followup, u.followup_at, u.updated_at, CASE WHEN u.followup IS NOT NULL AND u.followup != '' THEN '已跟进' ELSE '未跟进' END as status, um.userName as userName FROM usermanagements um JOIN users u ON um.userId = u.userId WHERE um.userName = ? AND u.created_at BETWEEN ? AND ? AND u.type != ? ORDER BY u.created_at DESC LIMIT ? OFFSET ? `; // 执行查询 wechatPool.query(countSql, params, (countErr, countResults) => { if (countErr) { console.error('查询指定业务员客户总数失败:', countErr); return res.json({ success: false, message: '获取客户详细信息失败' }); } const total = countResults[0].total; // 添加分页参数 const dataParams = [...params, pageSize, offset]; // 查询分页数据 wechatPool.query(managerClientsSql, dataParams, (dataErr, dataResults) => { if (dataErr) { console.error('查询指定业务员的客户信息失败:', dataErr); return res.json({ success: false, message: '获取客户详细信息失败' }); } res.json({ success: true, data: dataResults, total: total }); }); }); } catch (error) { console.error('获取指定业务员的客户信息失败:', error); res.json({ success: false, message: '获取客户详细信息失败' }); } }); // 获取商品日志列表 app.get('/api/product-logs', (req, res) => { const page = parseInt(req.query.page) || 1; const pageSize = parseInt(req.query.pageSize) || 10; const productId = req.query.productId || ''; const sellerId = req.query.sellerId || ''; const startDate = req.query.startDate || ''; const endDate = req.query.endDate || ''; // 计算偏移量 const offset = (page - 1) * pageSize; // 构建查询条件 let whereClause = ''; let countParams = []; let dataParams = []; if (productId) { whereClause += whereClause ? ' AND ' : ' WHERE '; whereClause += 'productId LIKE ?'; countParams.push(`%${productId}%`); dataParams.push(`%${productId}%`); } if (sellerId) { whereClause += whereClause ? ' AND ' : ' WHERE '; whereClause += 'sellerId LIKE ?'; countParams.push(`%${sellerId}%`); dataParams.push(`%${sellerId}%`); } if (startDate) { whereClause += whereClause ? ' AND ' : ' WHERE '; whereClause += 'updated_at >= ?'; countParams.push(startDate); dataParams.push(startDate); } if (endDate) { whereClause += whereClause ? ' AND ' : ' WHERE '; whereClause += 'updated_at <= ?'; countParams.push(`${endDate} 23:59:59`); dataParams.push(`${endDate} 23:59:59`); } // 只查询有日志的商品 whereClause += whereClause ? ' AND ' : ' WHERE '; whereClause += 'product_log IS NOT NULL AND product_log != ""'; // 添加分页参数 dataParams.push(pageSize); dataParams.push(offset); // 查询总数 const countSql = `SELECT COUNT(*) as total FROM products${whereClause}`; // 查询数据 const dataSql = `SELECT id, productId, sellerId, productName, product_log as logContent, updated_at as logTime FROM products${whereClause} ORDER BY updated_at DESC LIMIT ? OFFSET ?`; // 先查询总数 wechatPool.query(countSql, countParams, (countErr, countResults) => { if (countErr) { console.error('查询商品日志总数失败:', countErr); return res.json({ success: false, message: '查询商品日志失败,请稍后重试' }); } const total = countResults[0].total; // 再查询数据 wechatPool.query(dataSql, dataParams, (dataErr, dataResults) => { if (dataErr) { console.error('查询商品日志数据失败:', dataErr); return res.json({ success: false, message: '查询商品日志失败,请稍后重试' }); } res.json({ success: true, logs: dataResults, total }); }); }); }); // 获取单个商品日志详情 app.get('/api/product-logs/:id', (req, res) => { const logId = req.params.id; const sql = 'SELECT id, productId, sellerId, productName, product_log as logContent, updated_at as logTime FROM products WHERE id = ?'; wechatPool.query(sql, [logId], (err, results) => { if (err) { console.error('查询商品日志详情失败:', err); return res.json({ success: false, message: '查询商品日志详情失败,请稍后重试' }); } if (results.length === 0) { return res.json({ success: false, message: '商品日志不存在' }); } res.json({ success: true, log: results[0] }); }); }); // 获取产品生命周期历史(被哪些业务员修改过) app.get('/api/product-lifecycle/:productId', (req, res) => { const productId = req.params.productId; // 查询产品基本信息 const productSql = 'SELECT * FROM products WHERE productId = ?'; // 查询产品相关的操作日志,关联业务员信息,只返回审核系统的日志 // 注:informationtra表存储的是操作记录,通过operationEvent区分操作类型 // 使用informationtra表中的trauserName作为操作人名称,这是最准确的 // 只返回与当前产品相关的操作日志 const lifecycleSql = ` SELECT i.*, u.phoneNumber, i.trauserName as operationUser FROM informationtra i LEFT JOIN users u ON i.userId = u.userId WHERE i.operationEvent LIKE '审核系统-%' AND (i.operationEvent LIKE CONCAT('%', ? ,'%') OR i.originalData LIKE CONCAT('%', ? ,'%') OR i.modifiedData LIKE CONCAT('%', ? ,'%')) ORDER BY i.operationTime ASC `; // 先查询产品基本信息 wechatPool.query(productSql, [productId], (productErr, productResults) => { if (productErr) { console.error('查询产品信息失败:', productErr); return res.json({ success: false, message: '查询产品生命周期失败,请稍后重试' }); } if (productResults.length === 0) { return res.json({ success: false, message: '产品不存在' }); } // 再查询操作日志,只返回与当前产品相关的日志 wechatPool.query(lifecycleSql, [productId, productId, productId], (lifecycleErr, lifecycleResults) => { if (lifecycleErr) { console.error('查询产品操作日志失败:', lifecycleErr); return res.json({ success: false, message: '查询产品生命周期失败,请稍后重试' }); } // 查询产品的卖家信息,优先使用informationtra表中的trauserName作为卖家名称 const sellerSql = ` SELECT u.*, COALESCE(it.trauserName, um.userName, u.nickName) as sellerName FROM users u LEFT JOIN usermanagements um ON u.userId = um.userId LEFT JOIN (SELECT DISTINCT userId, trauserName FROM informationtra) it ON u.userId = it.userId WHERE u.userId = ? `; wechatPool.query(sellerSql, [productResults[0].sellerId], (sellerErr, sellerResults) => { if (sellerErr) { console.error('查询产品卖家信息失败:', sellerErr); return res.json({ success: false, message: '查询产品生命周期失败,请稍后重试' }); } res.json({ success: true, product: productResults[0], seller: sellerResults[0] || {}, lifecycle: lifecycleResults }); }); }); }); }); // 获取客户操作历史(被哪些业务员操作过) app.get('/api/customer-history/:userIdOrPhone', (req, res) => { const userIdOrPhone = req.params.userIdOrPhone; // 判断输入是客户ID还是电话号码 // 查询客户基本信息 - 先尝试通过userId查询,失败则尝试通过phoneNumber查询 const customerSql = ` SELECT * FROM users WHERE userId = ? OR phoneNumber = ? `; // 查询客户相关的操作日志,关联业务员信息 // 先通过userId或phoneNumber查询客户ID,再查询操作日志 // 只返回跟进系统的操作日志 const historySql = ` SELECT i.*, um.userName as operationUser FROM informationtra i LEFT JOIN usermanagements um ON i.userId = um.userId WHERE i.userId = ( SELECT userId FROM users WHERE userId = ? OR phoneNumber = ? ) AND i.operationEvent LIKE '跟进系统-%' ORDER BY i.operationTime ASC `; // 查询客户的分配历史 const allocationSql = ` SELECT um.userName as managerName, u.created_at as allocationTime FROM users u LEFT JOIN usermanagements um ON u.userId = um.userId WHERE u.userId = ( SELECT userId FROM users WHERE userId = ? OR phoneNumber = ? ) ORDER BY u.created_at ASC `; // 先查询客户基本信息 wechatPool.query(customerSql, [userIdOrPhone, userIdOrPhone], (customerErr, customerResults) => { if (customerErr) { console.error('查询客户信息失败:', customerErr); return res.json({ success: false, message: '查询客户操作历史失败,请稍后重试' }); } if (customerResults.length === 0) { return res.json({ success: false, message: '客户不存在' }); } const customerId = customerResults[0].userId; // 再查询操作日志 wechatPool.query(historySql, [userIdOrPhone, userIdOrPhone], (historyErr, historyResults) => { if (historyErr) { console.error('查询客户操作日志失败:', historyErr); return res.json({ success: false, message: '查询客户操作历史失败,请稍后重试' }); } // 查询客户分配历史 wechatPool.query(allocationSql, [userIdOrPhone, userIdOrPhone], (allocationErr, allocationResults) => { if (allocationErr) { console.error('查询客户分配历史失败:', allocationErr); return res.json({ success: false, message: '查询客户操作历史失败,请稍后重试' }); } res.json({ success: true, customer: customerResults[0], operationHistory: historyResults, allocationHistory: allocationResults }); }); }); }); }); // 获取业务员操作记录(业务员操作过哪些客户和货源) app.get('/api/agent-operations/:userName', (req, res) => { const userName = req.params.userName; // 查询业务员基本信息 const agentSql = 'SELECT * FROM usermanagements WHERE userName = ?'; // 查询业务员操作过的客户列表(跟进系统相关操作) const customerOperationsSql = ` SELECT DISTINCT i.userId, u.nickName, u.phoneNumber, MIN(i.operationTime) as firstOperationTime, MAX(i.operationTime) as lastOperationTime, COUNT(i.id) as operationCount FROM informationtra i LEFT JOIN users u ON i.userId = u.userId WHERE i.trauserName = ? AND i.operationEvent LIKE '跟进系统-%' GROUP BY i.userId, u.nickName, u.phoneNumber ORDER BY lastOperationTime DESC `; // 查询业务员操作过的货源列表(审核系统或货源相关操作) const productOperationsSql = ` SELECT DISTINCT JSON_UNQUOTE(JSON_EXTRACT(i.modifiedData, '$.productId')) as productId, MIN(i.operationTime) as firstOperationTime, MAX(i.operationTime) as lastOperationTime, COUNT(i.id) as operationCount FROM informationtra i WHERE i.trauserName = ? AND (i.operationEvent LIKE '审核系统-%' OR i.operationEvent LIKE '%货源%') AND JSON_EXTRACT(i.modifiedData, '$.productId') IS NOT NULL GROUP BY JSON_UNQUOTE(JSON_EXTRACT(i.modifiedData, '$.productId')) ORDER BY lastOperationTime DESC `; // 查询业务员操作的详细客户日志 const customerLogsSql = ` SELECT i.*, u.nickName as customerName, u.phoneNumber as customerPhone FROM informationtra i LEFT JOIN users u ON i.userId = u.userId WHERE i.trauserName = ? AND i.operationEvent LIKE '跟进系统-%' ORDER BY i.operationTime DESC `; // 查询业务员操作的详细货源日志 const productLogsSql = ` SELECT i.*, u.nickName as customerName, u.phoneNumber as customerPhone FROM informationtra i LEFT JOIN users u ON i.userId = u.userId WHERE i.trauserName = ? AND (i.operationEvent LIKE '审核系统-%' OR i.operationEvent LIKE '%货源%') ORDER BY i.operationTime DESC `; // 先查询业务员基本信息 wechatPool.query(agentSql, [userName], (agentErr, agentResults) => { if (agentErr) { console.error('查询业务员信息失败:', agentErr); return res.json({ success: false, message: '查询业务员操作记录失败,请稍后重试' }); } if (agentResults.length === 0) { return res.json({ success: false, message: '业务员不存在' }); } // 查询业务员操作过的客户列表 wechatPool.query(customerOperationsSql, [userName], (customerOpsErr, customerOpsResults) => { if (customerOpsErr) { console.error('查询业务员操作客户列表失败:', customerOpsErr); return res.json({ success: false, message: '查询业务员操作记录失败,请稍后重试' }); } // 查询业务员操作过的货源列表 wechatPool.query(productOperationsSql, [userName], (productOpsErr, productOpsResults) => { if (productOpsErr) { console.error('查询业务员操作货源列表失败:', productOpsErr); return res.json({ success: false, message: '查询业务员操作记录失败,请稍后重试' }); } // 查询业务员操作的详细客户日志 wechatPool.query(customerLogsSql, [userName], (customerLogsErr, customerLogsResults) => { if (customerLogsErr) { console.error('查询业务员客户操作日志失败:', customerLogsErr); return res.json({ success: false, message: '查询业务员操作记录失败,请稍后重试' }); } // 查询业务员操作的详细货源日志 wechatPool.query(productLogsSql, [userName], (productLogsErr, productLogsResults) => { if (productLogsErr) { console.error('查询业务员货源操作日志失败:', productLogsErr); return res.json({ success: false, message: '查询业务员操作记录失败,请稍后重试' }); } res.json({ success: true, agent: agentResults[0], customerOperations: customerOpsResults, productOperations: productOpsResults, customerLogs: customerLogsResults, productLogs: productLogsResults }); }); }); }); }); }); }); // 批量请求API端点 app.post('/api/batch', (req, res) => { try { const { requests } = req.body; if (!Array.isArray(requests)) { return res.json({ success: false, message: '请求格式错误,requests必须是数组' }); } if (requests.length === 0) { return res.json({ success: false, message: '请求数组不能为空' }); } if (requests.length > 20) { return res.json({ success: false, message: '请求数量不能超过20个' }); } // 并行处理所有请求 const requestPromises = requests.map((request, index) => { return new Promise((resolve) => { const { id, endpoint, params = {} } = request; try { // 解析endpoint,提取HTTP方法和路径 let method = 'GET'; let path = endpoint; if (endpoint.includes(' ')) { const parts = endpoint.split(' '); method = parts[0].toUpperCase(); path = parts[1]; } // 构建完整的URL(如果需要) let url = path; // 处理GET请求的查询参数 if (method === 'GET' && Object.keys(params).length > 0) { const queryString = new URLSearchParams(params).toString(); if (queryString) { url += url.includes('?') ? '&' : '?'; url += queryString; } } // 模拟请求处理 // 这里需要根据实际的API路径调用相应的处理函数 // 为了简单起见,我们使用fetch来模拟内部请求 const options = { method, headers: { 'Content-Type': 'application/json' } }; if (method !== 'GET') { options.body = JSON.stringify(params); } // 发送请求到内部API fetch(`http://localhost:3005${url}`, options) .then(response => response.json()) .then(data => { resolve({ id, success: true, data }); }) .catch(error => { console.error(`处理请求 ${id} 失败:`, error); resolve({ id, success: false, error: error.message }); }); } catch (error) { console.error(`处理请求 ${id} 失败:`, error); resolve({ id, success: false, error: error.message }); } }); }); // 等待所有请求完成 Promise.all(requestPromises) .then(results => { res.json({ success: true, results }); }) .catch(error => { console.error('批量请求处理失败:', error); res.json({ success: false, message: '批量请求处理失败' }); }); } catch (error) { console.error('批量请求处理失败:', error); res.json({ success: false, message: '批量请求处理失败' }); } }); // 缓存解析结果,避免重复解析相同的JSON字符串 const jsonParseCache = new Map(); // 批量解析JSON数据,使用缓存提升性能 function batchParseJSON(operations) { return operations .filter(op => { // 过滤掉没有operationTime或operationTime为undefined的记录 return op && op.operationTime; }) .map(op => { try { // 创建有效的Date对象 const operationTime = new Date(op.operationTime); // 过滤掉无效的时间值 if (isNaN(operationTime.getTime())) { return null; } if (typeof op.originalData === 'string') { // 检查缓存中是否已有解析结果 if (jsonParseCache.has(op.originalData)) { return { operationTime: operationTime, originalData: jsonParseCache.get(op.originalData) }; } // 解析新的JSON字符串并缓存结果 const parsedData = JSON.parse(op.originalData); jsonParseCache.set(op.originalData, parsedData); return { operationTime: operationTime, originalData: parsedData }; } else { return { operationTime: operationTime, originalData: op.originalData || {} }; } } catch (e) { // 解析失败时返回null,后续会被过滤 return null; } }) .filter(item => item !== null); // 过滤掉所有null值 } // 批量计算活跃时长 function batchCalculateActiveDuration(operationsList) { return operationsList.map(operations => calculateActiveDuration(operations)); } // 计算活跃时长的函数(基于app_show/app_hide事件,无事件时基于操作记录时间范围) function calculateActiveDuration(operations) { if (!operations || operations.length === 0) { return 0; } let totalDuration = 0; let currentSessionStart = null; let lastAction = null; let firstOperationTime = null; let lastOperationTime = null; // 使用批量解析JSON函数,提升性能并过滤无效时间值 const parsedOperations = batchParseJSON(operations) .sort((a, b) => a.operationTime - b.operationTime); // 检查解析后是否还有有效操作记录 if (parsedOperations.length === 0) { // 兜底:如果没有有效操作记录,返回一个基础活跃时长 return 30; // 默认30秒 } // 单次遍历处理所有逻辑 for (const op of parsedOperations) { const operationTime = op.operationTime; const action = op.originalData.action; // 更新第一条和最后一条操作时间 if (!firstOperationTime || operationTime < firstOperationTime) { firstOperationTime = operationTime; } if (!lastOperationTime || operationTime > lastOperationTime) { lastOperationTime = operationTime; } // 处理带有sessionDuration的app_hide事件 if (action === 'app_hide' && op.originalData.sessionDuration) { const durationInSeconds = op.originalData.sessionDuration / 1000; totalDuration += durationInSeconds; } // 处理app_show/app_hide事件状态 if (action === 'app_show') { currentSessionStart = operationTime; lastAction = 'app_show'; } else if (action === 'app_hide') { currentSessionStart = null; lastAction = 'app_hide'; } else if (action) { lastAction = action; } } // 处理未结束的app_show事件 if (currentSessionStart && lastAction === 'app_show') { const now = new Date(); const maxDurationInSeconds = 5 * 60; // 限制为5分钟 const durationInSeconds = Math.min(maxDurationInSeconds, (now - currentSessionStart) / 1000); totalDuration += durationInSeconds; } // 兜底逻辑 - 改进:根据操作次数动态调整基础活跃时长 if (totalDuration === 0 && firstOperationTime && lastOperationTime) { const timeDiffSeconds = (lastOperationTime - firstOperationTime) / 1000; const operationCount = parsedOperations.length; // 根据操作次数计算基础活跃时长 let baseDuration = 30; // 默认30秒 if (operationCount > 50) { baseDuration = 300; // 5分钟 } else if (operationCount > 20) { baseDuration = 180; // 3分钟 } else if (operationCount > 10) { baseDuration = 120; // 2分钟 } const maxDurationInSeconds = 5 * 60; const durationInSeconds = Math.min(maxDurationInSeconds, Math.max(baseDuration, timeDiffSeconds)); totalDuration = durationInSeconds; } // 兜底:如果所有逻辑都没计算出时长,返回基础值 if (totalDuration === 0) { totalDuration = 60; // 默认1分钟 } // 限制最大活跃时长 const maxUserDurationInSeconds = 24 * 60 * 60; return Math.min(maxUserDurationInSeconds, totalDuration); } // 判断是否为商品浏览记录 function isProductView(originalData) { try { const data = typeof originalData === 'string' ? JSON.parse(originalData) : originalData || {}; // 商品记录包含productId字段,且id以product_开头(支持视频商品) return data.productId && (data.id ? data.id.startsWith('product_') : true); } catch (e) { return false; } } // 判断是否为收藏操作记录 function isFavoriteAction(originalData) { try { const data = typeof originalData === 'string' ? JSON.parse(originalData) : originalData || {}; // 收藏操作包含action字段,且值为add_favorite return data.action === 'add_favorite'; } catch (e) { return false; } } // 获取客户活跃统计概览 app.get('/api/active-stats', (req, res) => { try { // 获取时间范围参数 const { startDate, endDate } = req.query; console.log(`[${new Date().toISOString()}] API /api/active-stats 被调用,接收参数: startDate=${startDate}, endDate=${endDate}`); // 计算默认日期范围(最近7天) const now = new Date(); const defaultEnd = now.toISOString().split('T')[0]; const sevenDaysAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); const defaultStart = sevenDaysAgo.toISOString().split('T')[0]; // 使用提供的时间范围或默认值 const start = startDate || defaultStart; const end = endDate || defaultEnd; console.log(`[${new Date().toISOString()}] API /api/active-stats 使用时间范围: start=${start}, end=${end}`); // 使用user_active_logs表查询活跃统计 const totalActiveSql = ` SELECT COUNT(DISTINCT ual.user_id) as totalActive FROM user_active_logs ual JOIN users u ON ual.user_id = u.userId WHERE ual.active_date BETWEEN ? AND ? AND u.phoneNumber IS NOT NULL AND u.phoneNumber != '' AND u.created_at IS NOT NULL AND u.type != 'Colleague' `; // 查询时间范围内的活跃客户数 const weekActiveSql = ` SELECT COUNT(DISTINCT ual.user_id) as weekActive FROM user_active_logs ual JOIN users u ON ual.user_id = u.userId WHERE ual.active_date BETWEEN ? AND ? AND u.phoneNumber IS NOT NULL AND u.phoneNumber != '' AND u.created_at IS NOT NULL AND u.type != 'Colleague' `; // 查询总活跃时长 const totalDurationSql = ` SELECT SUM(ual.active_duration) as totalDuration FROM user_active_logs ual JOIN users u ON ual.user_id = u.userId WHERE ual.active_date BETWEEN ? AND ? AND u.phoneNumber IS NOT NULL AND u.phoneNumber != '' AND u.created_at IS NOT NULL AND u.type != 'Colleague' `; // 并行执行查询 Promise.all([ new Promise((resolve, reject) => { wechatPool.query(totalActiveSql, [start, end], (err, results) => { if (err) reject(err); else resolve(results[0].totalActive || 0); }); }), new Promise((resolve, reject) => { wechatPool.query(weekActiveSql, [start, end], (err, results) => { if (err) reject(err); else resolve(results[0].weekActive || 0); }); }), new Promise((resolve, reject) => { wechatPool.query(totalDurationSql, [start, end], (err, results) => { if (err) reject(err); else resolve(results[0].totalDuration || 0); }); }) ]) .then(([totalActive, weekActive, totalDuration]) => { // 将总活跃时长从秒转换为小时 const totalDurationHours = parseFloat(totalDuration) / 3600; console.log(`[${new Date().toISOString()}] API /api/active-stats 计算结果: totalActive=${totalActive}, weekActive=${weekActive}, totalDurationSeconds=${totalDuration}, totalDurationHours=${totalDurationHours}`); res.json({ success: true, data: { totalActive: totalActive, weekActive: weekActive, totalDuration: totalDurationHours, dateRange: { start: start, end: end } } }); }) .catch(error => { console.error('获取活跃统计数据失败:', error); res.json({ success: false, message: '获取活跃统计数据失败' }); }); } catch (error) { console.error('获取活跃统计数据失败:', error); res.json({ success: false, message: '获取活跃统计数据失败' }); } }); // 获取单个客户的活跃详情 app.get('/api/active-stats/:userId', (req, res) => { try { const { userId } = req.params; const { startDate, endDate } = req.query; console.log(`[${new Date().toISOString()}] API /api/active-stats/${userId} 被调用,接收参数: userId=${userId}, startDate=${startDate}, endDate=${endDate}`); // 计算默认日期范围(最近7天) const now = new Date(); const defaultEndStr = now.toISOString().slice(0, 19).replace('T', ' '); const sevenDaysAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); const defaultStartStr = sevenDaysAgo.toISOString().slice(0, 19).replace('T', ' '); // 使用提供的时间范围或默认值 const startDateStr = startDate ? `${startDate} 00:00:00` : defaultStartStr; const endDateStr = endDate ? `${endDate} 23:59:59` : defaultEndStr; console.log(`[${new Date().toISOString()}] API /api/active-stats/${userId} 使用时间范围: startDateStr=${startDateStr}, endDateStr=${endDateStr}`); // 查询客户基本信息 const clientInfoSql = ` SELECT phoneNumber, created_at as registerTime FROM users WHERE userId = ? `; // 查询客户最后上线时间(使用新表) const lastOnlineSql = 'SELECT MAX(last_active_time) as lastOnline FROM user_active_logs WHERE user_id = ?'; // 查询客户最后浏览的商品(使用新表) const lastViewedProductSql = ` SELECT product_id FROM user_product_views WHERE user_id = ? ORDER BY last_view_time DESC LIMIT 1 `; // 查询客户的多天活跃详情(使用新表) const dailyStatsSql = ` SELECT active_date, active_duration, operation_count, product_view_count, favorite_count, last_active_time FROM user_active_logs WHERE user_id = ? AND active_date BETWEEN DATE(?) AND DATE(?) ORDER BY active_date DESC `; // 执行查询 wechatPool.query(clientInfoSql, [userId], (clientInfoErr, clientInfoResults) => { if (clientInfoErr) { console.error('查询客户基本信息失败:', clientInfoErr); return res.json({ success: false, message: '获取客户活跃详情失败' }); } wechatPool.query(lastOnlineSql, [userId], (lastOnlineErr, lastOnlineResults) => { if (lastOnlineErr) { console.error('查询客户最后上线时间失败:', lastOnlineErr); return res.json({ success: false, message: '获取客户活跃详情失败' }); } wechatPool.query(lastViewedProductSql, [userId], (lastViewedErr, lastViewedResults) => { if (lastViewedErr) { console.error('查询客户最后浏览商品失败:', lastViewedErr); return res.json({ success: false, message: '获取客户活跃详情失败' }); } wechatPool.query(dailyStatsSql, [userId, startDateStr, endDateStr], (dailyStatsErr, dailyStatsResults) => { if (dailyStatsErr) { console.error('查询客户活跃详情失败:', dailyStatsErr); return res.json({ success: false, message: '获取客户活跃详情失败' }); } // 计算总活跃时长 let totalDuration = 0; const dailyStats = []; dailyStatsResults.forEach(stat => { totalDuration += stat.active_duration || 0; dailyStats.push({ date: stat.active_date, operationCount: stat.operation_count || 0, duration: stat.active_duration || 0, productCount: stat.product_view_count || 0, favoriteCount: stat.favorite_count || 0 }); }); // 处理最后浏览的商品 let lastViewedProduct = null; if (lastViewedResults.length > 0 && lastViewedResults[0].product_id) { const productId = lastViewedResults[0].product_id; // 查询完整商品信息 const productInfoSql = ` SELECT p.productId, p.productName, p.product_contact, p.contact_phone, p.quantity, p.price, p.specification, p.category, p.sellerId, u.nickName as creatorNickName, u.phoneNumber as creatorPhone, p.created_at as createdAt FROM products p LEFT JOIN users u ON p.sellerId = u.userId WHERE p.productId = ? `; wechatPool.query(productInfoSql, [productId], (productInfoErr, productInfoResults) => { if (productInfoErr) { console.error('查询商品详情失败:', productInfoErr); lastViewedProduct = { productId: productId }; } else { lastViewedProduct = productInfoResults.length > 0 ? { productId: productInfoResults[0].productId, productName: productInfoResults[0].productName, productContact: productInfoResults[0].product_contact, contactPhone: productInfoResults[0].contact_phone, quantity: productInfoResults[0].quantity, price: productInfoResults[0].price, specification: productInfoResults[0].specification, category: productInfoResults[0].category, creatorNickName: productInfoResults[0].creatorNickName, creatorPhone: productInfoResults[0].creatorPhone, createdAt: productInfoResults[0].createdAt } : { productId: productId }; } res.json({ success: true, data: { userId: userId, phoneNumber: clientInfoResults.length > 0 ? clientInfoResults[0].phoneNumber : null, registerTime: clientInfoResults.length > 0 ? clientInfoResults[0].registerTime : null, totalDuration: totalDuration, lastOnline: lastOnlineResults[0].lastOnline, lastViewedProduct: lastViewedProduct, dailyStats: dailyStats, dateRange: { start: startDateStr, end: endDateStr } } }); }); return; } res.json({ success: true, data: { userId: userId, phoneNumber: clientInfoResults.length > 0 ? clientInfoResults[0].phoneNumber : null, registerTime: clientInfoResults.length > 0 ? clientInfoResults[0].registerTime : null, totalDuration: totalDuration, lastOnline: lastOnlineResults[0].lastOnline, lastViewedProduct: lastViewedProduct, dailyStats: dailyStats, dateRange: { start: startDateStr, end: endDateStr } } }); }); }); }); }); } catch (error) { console.error('获取客户活跃详情失败:', error); res.json({ success: false, message: '获取客户活跃详情失败' }); } }); // 获取货源管理商品列表 app.get('/api/supply-management', (req, res) => { try { console.log(`[${new Date().toISOString()}] API /api/supply-management 被调用`); console.log(`[${new Date().toISOString()}] 请求参数:`, req.query); const page = parseInt(req.query.page) || 1; const pageSize = parseInt(req.query.pageSize) || 10; const search = req.query.search || ''; const getAll = req.query.getAll === 'true'; const offset = (page - 1) * pageSize; // 构建搜索条件 let whereClause = ''; let params = []; if (search) { whereClause = 'WHERE (p.productName LIKE ? OR p.specification LIKE ? OR p.region LIKE ? OR p.sellerId IN (SELECT userId FROM users u WHERE u.nickName LIKE ? OR u.phoneNumber LIKE ?))'; params = [`%${search}%`, `%${search}%`, `%${search}%`, `%${search}%`, `%${search}%`]; } // 查询商品总数 const countSql = `SELECT COUNT(*) as total FROM products p ${whereClause}`; // 查询商品列表 let productsSql; if (getAll) { // 获取所有数据 productsSql = ` SELECT p.* FROM products p ${whereClause} ORDER BY p.created_at DESC `; } else { // 分页查询 productsSql = ` SELECT p.* FROM products p ${whereClause} ORDER BY p.created_at DESC LIMIT ? OFFSET ? `; } // 执行查询 console.log(`[${new Date().toISOString()}] 执行总数查询:`, countSql); console.log(`[${new Date().toISOString()}] 查询参数:`, params); wechatPool.query(countSql, params, (countErr, countResults) => { if (countErr) { console.error('查询商品总数失败:', countErr); return res.json({ success: false, message: '获取商品列表失败,请稍后重试' }); } const total = countResults[0].total; console.log(`[${new Date().toISOString()}] 商品总数:`, total); // 根据getAll参数使用不同的查询参数 const queryParams = getAll ? params : [...params, pageSize, offset]; console.log(`[${new Date().toISOString()}] 执行商品列表查询:`, productsSql); console.log(`[${new Date().toISOString()}] 查询参数:`, queryParams); wechatPool.query(productsSql, queryParams, (productsErr, productsResults) => { if (productsErr) { console.error('查询商品列表失败:', productsErr); return res.json({ success: false, message: '获取商品列表失败,请稍后重试' }); } console.log(`[${new Date().toISOString()}] 查询到商品数量:`, productsResults.length); // 批量处理商品数据 const processProductsBatch = async (products) => { // 提取所有唯一的联系电话 const phoneNumbers = new Set(); products.forEach(product => { if (product.contact_phone) { phoneNumbers.add(product.contact_phone); } }); // 提取所有唯一的sellerId const sellerIds = new Set(); products.forEach(product => { if (product.sellerId) { sellerIds.add(product.sellerId); } }); // 批量查询联系人信息 const contactsMap = new Map(); if (phoneNumbers.size > 0) { try { // 先从缓存中获取 let uncachedPhones = []; phoneNumbers.forEach(phone => { if (personnelCache.has(phone)) { contactsMap.set(phone, personnelCache.get(phone)); } else { uncachedPhones.push(phone); } }); // 批量查询未缓存的电话号码 if (uncachedPhones.length > 0) { const contactsSql = 'SELECT name, phoneNumber FROM personnel WHERE phoneNumber IN (?)'; const contactResults = await new Promise((resolve, reject) => { getDbPool('read').query(contactsSql, [uncachedPhones], (err, results) => { if (err) reject(err); else resolve(results); }); }); contactResults.forEach(item => { contactsMap.set(item.phoneNumber, item.name); // 更新缓存 personnelCache.set(item.phoneNumber, item.name); }); } } catch (err) { console.error('批量查询联系人失败:', err); } } // 批量查询创建人信息(从users表中查询) const creatorsMap = new Map(); if (sellerIds.size > 0) { try { const creatorsSql = 'SELECT userId, nickName, phoneNumber FROM users WHERE userId IN (?)'; const creatorResults = await new Promise((resolve, reject) => { wechatPool.query(creatorsSql, [Array.from(sellerIds)], (err, results) => { if (err) reject(err); else resolve(results); }); }); creatorResults.forEach(item => { creatorsMap.set(item.userId, item.nickName || item.phoneNumber || '暂无'); }); } catch (err) { console.error('批量查询创建人失败:', err); } } // 批量查询商品浏览次数 const viewCountsMap = new Map(); const productIds = products.map(product => product.productId).filter(Boolean); if (productIds.length > 0) { try { const viewsSql = 'SELECT product_id, COUNT(DISTINCT user_id) as unique_users FROM user_product_views WHERE product_id IN (?) GROUP BY product_id'; const viewResults = await new Promise((resolve, reject) => { // 使用wechatPool连接池,因为user_product_views表在wechat_app数据库中 wechatPool.query(viewsSql, [productIds], (err, results) => { if (err) reject(err); else resolve(results); }); }); viewResults.forEach(item => { viewCountsMap.set(item.product_id, item.unique_users || 0); }); } catch (err) { console.error('批量查询浏览次数失败:', err); } } // 处理商品数据 const processedProducts = products.map(product => { // 从缓存或批量查询结果中获取联系人 let contactPerson = '暂无'; if (product.contact_phone) { if (personnelCache.has(product.contact_phone)) { contactPerson = personnelCache.get(product.contact_phone); } else if (contactsMap.has(product.contact_phone)) { contactPerson = contactsMap.get(product.contact_phone); } } // 创建人信息 (使用sellerId从users表中查询) let creator = '暂无'; if (product.sellerId) { if (creatorsMap.has(product.sellerId)) { creator = creatorsMap.get(product.sellerId); } } // 浏览次数 (从批量查询结果中获取) let viewCount = viewCountsMap.get(product.productId) || 0; return { ...product, contactPerson, creator, viewCount }; }); return processedProducts; }; processProductsBatch(productsResults) .then(processedProducts => { res.json({ success: true, products: processedProducts, total }); }) .catch(error => { console.error('处理商品数据失败:', error); res.json({ success: false, message: '处理商品数据失败,请稍后重试' }); }); }); }); } catch (error) { console.error('获取商品列表失败:', error); res.json({ success: false, message: '获取商品列表失败,请稍后重试' }); } }); // 获取货源浏览详细信息 app.get('/api/supply-management/view-details/:productId', (req, res) => { try { const productId = req.params.productId; console.log(`[${new Date().toISOString()}] API /api/supply-management/view-details/${productId} 被调用`); // 使用user_product_views表查询商品浏览记录,按用户ID分组,只返回每个用户的最新记录 const viewSql = ` SELECT upv.user_id as userId, u.phoneNumber, upv.last_view_time as viewTime FROM user_product_views upv LEFT JOIN users u ON upv.user_id = u.userId WHERE upv.product_id = ? AND upv.user_id IS NOT NULL AND upv.user_id != '' GROUP BY upv.user_id, u.phoneNumber ORDER BY upv.last_view_time DESC `; wechatPool.query(viewSql, [productId], (viewErr, viewResults) => { if (viewErr) { console.error('查询浏览记录失败:', viewErr); return res.json({ success: false, message: '获取浏览详细信息失败,请稍后重试' }); } console.log(`查询到 ${viewResults.length} 条浏览记录`); // 处理每条浏览记录 const processViews = async (views) => { const processedViews = []; const validUserIds = new Set(); // 用于进一步去重,确保只处理有效的用户ID for (const view of views) { // 跳过无效记录 if (!view.userId || view.userId === '') { console.log('跳过无效的浏览记录(缺少userId)'); continue; } // 跳过重复的用户ID if (validUserIds.has(view.userId)) { console.log(`跳过重复的浏览记录(userId: ${view.userId})`); continue; } // 标记该用户ID为已处理 validUserIds.add(view.userId); // 获取首次分配人和当前处理人 let firstAssignee = '暂无'; let currentHandler = '暂无'; try { // 先查询customer_apply表 const applySql = 'SELECT sales_name, original_manager_name FROM customer_apply WHERE user_id = ?'; const applyResults = await new Promise((resolve, reject) => { wechatPool.query(applySql, [view.userId], (err, results) => { if (err) reject(err); else resolve(results); }); }); if (applyResults.length > 0) { firstAssignee = applyResults[0].original_manager_name || applyResults[0].sales_name || '暂无'; currentHandler = applyResults[0].sales_name || '暂无'; } else { // 如果customer_apply表没有记录,查询usermanagements表 const userMgmtSql = 'SELECT userName FROM usermanagements WHERE userId = ?'; const userMgmtResults = await new Promise((resolve, reject) => { wechatPool.query(userMgmtSql, [view.userId], (err, results) => { if (err) reject(err); else resolve(results); }); }); if (userMgmtResults.length > 0) { firstAssignee = userMgmtResults[0].userName; currentHandler = userMgmtResults[0].userName; } } } catch (err) { console.error('查询分配信息失败:', err); } // 获取跟进内容 let followContent = '暂无'; try { const userSql = 'SELECT followup FROM users WHERE userId = ?'; const userResults = await new Promise((resolve, reject) => { wechatPool.query(userSql, [view.userId], (err, results) => { if (err) reject(err); else resolve(results); }); }); if (userResults.length > 0 && userResults[0].followup) { followContent = userResults[0].followup; } } catch (err) { console.error('查询跟进内容失败:', err); } processedViews.push({ userId: view.userId, phoneNumber: view.phoneNumber || (view.userId ? '未知' : '暂无'), viewTime: view.viewTime, firstAssignee, currentHandler, followContent }); } // 最后再按时间倒序排序一次,确保顺序正确 processedViews.sort((a, b) => new Date(b.viewTime) - new Date(a.viewTime)); return processedViews; }; processViews(viewResults) .then(processedViews => { console.log(`处理完成 ${processedViews.length} 条浏览记录`); res.json({ success: true, views: processedViews }); }) .catch(error => { console.error('处理浏览记录失败:', error); res.json({ success: false, message: '处理浏览记录失败,请稍后重试' }); }); }); } catch (error) { console.error('获取浏览详细信息失败:', error); res.json({ success: false, message: '获取浏览详细信息失败,请稍后重试' }); } }); // 获取供应商列表 app.get('/api/suppliers', (req, res) => { try { const { search, page = 1, pageSize = 10 } = req.query; const pageNum = parseInt(page, 10); const pageSizeNum = parseInt(pageSize, 10); const offset = (pageNum - 1) * pageSizeNum; let sql = `SELECT userId, phoneNumber, collaborationid, company, province, city, district, detailedaddress, cooperation, businesslicenseurl, proofurl, brandurl, partnerstatus, created_at, newtime, liaison, seller_followup FROM users WHERE collaborationid != '' `; let countSql = `SELECT COUNT(*) as total FROM users WHERE collaborationid != '' `; let params = []; let countParams = []; if (search) { sql += `AND (phoneNumber LIKE ? OR liaison LIKE ? OR company LIKE ?) `; countSql += `AND (phoneNumber LIKE ? OR liaison LIKE ? OR company LIKE ?) `; params = [`%${search}%`, `%${search}%`, `%${search}%`]; countParams = [`%${search}%`, `%${search}%`, `%${search}%`]; } sql += `ORDER BY COALESCE(newtime, created_at) DESC LIMIT ? OFFSET ?`; params.push(pageSizeNum, offset); // 先查询总数 wechatPool.query(countSql, countParams, (countErr, countResults) => { if (countErr) { console.error('查询供应商总数失败:', countErr); return res.json({ success: false, message: '获取供应商列表失败' }); } const totalCount = countResults[0].total; // 再查询分页数据 wechatPool.query(sql, params, (err, results) => { if (err) { console.error('查询供应商列表失败:', err); return res.json({ success: false, message: '获取供应商列表失败' }); } res.json({ success: true, suppliers: results, totalCount }); }); }); } catch (error) { console.error('获取供应商列表失败:', error); res.json({ success: false, message: '获取供应商列表失败' }); } }); // 获取对接人列表(从userlogin数据库的personnel表) app.get('/api/liaisons', (req, res) => { try { // 只查询职位名称为采购员的数据,并且添加去重 const sql = 'SELECT DISTINCT name, phoneNumber FROM personnel WHERE projectName = ?'; getDbPool('read').query(sql, ['采购员'], (err, results) => { if (err) { console.error('查询对接人列表失败:', err); return res.json({ success: false, message: '获取对接人列表失败' }); } // 进一步在代码层面去重,确保数据唯一 const uniqueLiaisons = []; const seen = new Set(); results.forEach(person => { // 跳过空数据 if (!person.name || !person.phoneNumber) return; // 使用姓名+电话作为唯一标识 const key = `${person.name}-${person.phoneNumber}`; if (!seen.has(key)) { seen.add(key); uniqueLiaisons.push({ value: key, name: person.name, phoneNumber: person.phoneNumber }); } }); res.json({ success: true, liaisons: uniqueLiaisons }); }); } catch (error) { console.error('获取对接人列表失败:', error); res.json({ success: false, message: '获取对接人列表失败' }); } }); // 获取客户活跃详情列表(用于虚拟滚动) app.get('/api/active-stats/details', (req, res) => { try { // 获取分页参数 const page = parseInt(req.query.page) || 1; const pageSize = parseInt(req.query.pageSize) || 10; const offset = (page - 1) * pageSize; // 获取时间范围参数 const { startDate, endDate } = req.query; console.log(`[${new Date().toISOString()}] API /api/active-stats/details 被调用,接收参数: startDate=${startDate}, endDate=${endDate}, page=${page}, pageSize=${pageSize}`); // 计算默认日期范围(最近7天) const now = new Date(); const defaultEnd = now.toISOString().split('T')[0]; const sevenDaysAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); const defaultStart = sevenDaysAgo.toISOString().split('T')[0]; // 使用提供的时间范围或默认值 const start = startDate || defaultStart; const end = endDate || defaultEnd; console.log(`[${new Date().toISOString()}] API /api/active-stats/details 使用时间范围: start=${start}, end=${end}`); // 使用user_active_logs表查询活跃详情 const userActiveDetailsSql = ` SELECT ual.user_id as userId, u.phoneNumber, u.created_at as registerTime, MAX(ual.last_active_time) as lastActiveTime, SUM(ual.active_duration) as totalDuration FROM user_active_logs ual LEFT JOIN users u ON ual.user_id = u.userId WHERE ual.active_date BETWEEN ? AND ? AND u.phoneNumber IS NOT NULL AND u.phoneNumber != '' AND u.created_at IS NOT NULL AND u.type != 'Colleague' GROUP BY ual.user_id, u.phoneNumber, u.created_at ORDER BY lastActiveTime DESC LIMIT ? OFFSET ? `; // 查询总数 const countSql = ` SELECT COUNT(DISTINCT ual.user_id) as total FROM user_active_logs ual LEFT JOIN users u ON ual.user_id = u.userId WHERE ual.active_date BETWEEN ? AND ? AND u.phoneNumber IS NOT NULL AND u.phoneNumber != '' AND u.created_at IS NOT NULL AND u.type != 'Colleague' `; // 并行执行查询 Promise.all([ new Promise((resolve, reject) => { wechatPool.query(countSql, [start, end], (err, results) => { if (err) reject(err); else resolve(results[0].total || 0); }); }), new Promise((resolve, reject) => { wechatPool.query(userActiveDetailsSql, [start, end, pageSize, offset], (err, results) => { if (err) reject(err); else resolve(results); }); }) ]) .then(([total, users]) => { // 处理结果 const usersWithDuration = users.map(user => ({ userId: user.userId, phoneNumber: user.phoneNumber, registerTime: user.registerTime, lastActiveTime: user.lastActiveTime, duration: parseFloat(user.totalDuration) / 3600 || 0, // 转换为小时 lastViewedProduct: null, originalProductData: null })); // 为每个用户查询最后浏览的商品 const userIds = usersWithDuration.map(user => user.userId); if (userIds.length > 0) { const lastViewedProductsSql = ` SELECT upv.user_id as userId, upv.product_id as productId, p.productName, p.product_contact, p.contact_phone, p.quantity, p.price, p.specification, p.category, p.created_at as createdAt, u.nickName as creatorNickName, u.phoneNumber as creatorPhone FROM user_product_views upv LEFT JOIN products p ON upv.product_id = p.productId LEFT JOIN users u ON p.sellerId = u.userId WHERE upv.user_id IN (${userIds.map(() => '?').join(',')}) AND upv.active_date BETWEEN ? AND ? AND upv.last_view_time = ( SELECT MAX(last_view_time) FROM user_product_views WHERE user_id = upv.user_id AND active_date BETWEEN ? AND ? ) `; wechatPool.query(lastViewedProductsSql, [...userIds, start, end, start, end], (err, productResults) => { if (!err && productResults.length > 0) { // 将查询到的商品信息添加到用户数据中 const productMap = {}; productResults.forEach(product => { productMap[product.userId] = product; }); usersWithDuration.forEach(user => { if (productMap[user.userId]) { const product = productMap[user.userId]; user.lastViewedProduct = { productId: product.productId, productName: product.productName || '未知商品', productContact: product.product_contact, contactPhone: product.contact_phone, quantity: product.quantity, price: product.price, specification: product.specification, category: product.category, createdAt: product.createdAt, creatorNickName: product.creatorNickName, creatorPhone: product.creatorPhone }; user.originalProductData = product; } }); } res.json({ success: true, data: { users: usersWithDuration, total: total } }); }); } else { res.json({ success: true, data: { users: usersWithDuration, total: total } }); } }) .catch(error => { console.error('获取客户活跃详情列表失败:', error); res.json({ success: false, message: '获取客户活跃详情列表失败' }); }); } catch (error) { console.error('获取客户活跃详情列表失败:', error); res.json({ success: false, message: '获取客户活跃详情列表失败' }); } }); // 分配供应商对接人 app.post('/api/assign-liaison', (req, res) => { try { console.log(`[${new Date().toISOString()}] API /api/assign-liaison 被调用,请求体:`, req.body); const { userId, liaison } = req.body; if (!userId || !liaison) { console.log(`[${new Date().toISOString()}] 分配对接人失败:参数不全,userId: ${userId}, liaison: ${liaison}`); return res.json({ success: false, message: '参数不全' }); } const sql = 'UPDATE users SET liaison = ? WHERE userId = ?'; wechatPool.query(sql, [liaison, userId], (err, results) => { if (err) { console.error(`[${new Date().toISOString()}] 分配对接人失败,SQL错误:`, err); console.error(`[${new Date().toISOString()}] SQL语句: ${sql}`); console.error(`[${new Date().toISOString()}] SQL参数:`, [liaison, userId]); return res.json({ success: false, message: '分配对接人失败' }); } console.log(`[${new Date().toISOString()}] 分配对接人结果:`, results); if (results.affectedRows === 0) { console.log(`[${new Date().toISOString()}] 分配对接人失败:未找到匹配的用户,userId: ${userId}`); return res.json({ success: false, message: '未找到匹配的用户' }); } res.json({ success: true, message: '分配对接人成功' }); }); } catch (error) { console.error(`[${new Date().toISOString()}] 分配对接人失败,捕获到异常:`, error); res.json({ success: false, message: '分配对接人失败' }); } }); // 获取多天的客户活跃统计 app.get('/api/daily-active-stats', (req, res) => { try { // 获取时间范围参数 const { startDate, endDate } = req.query; console.log(`[${new Date().toISOString()}] API /api/daily-active-stats 被调用,接收参数: startDate=${startDate}, endDate=${endDate}`); // 计算默认日期范围(最近7天,减少数据量) const now = new Date(); const defaultEnd = now.toISOString().split('T')[0]; const sevenDaysAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); const defaultStart = sevenDaysAgo.toISOString().split('T')[0]; // 使用提供的时间范围或默认值 const start = startDate || defaultStart; const end = endDate || defaultEnd; // 生成缓存键 const cacheKey = `daily-active-stats-${start}-${end}`; // 检查普通缓存 if (normalCache.has(cacheKey)) { const cachedData = normalCache.get(cacheKey); console.log(`从普通缓存返回多天活跃统计数据: ${cacheKey}`); // 立即返回缓存数据 res.json(cachedData); // 后台异步更新缓存 console.log(`后台更新多天活跃统计数据缓存: ${cacheKey}`); // 这里可以添加后台更新缓存的逻辑 return; } console.log(`[${new Date().toISOString()}] API /api/daily-active-stats 使用时间范围: start=${start}, end=${end}`); // 使用user_active_logs表查询每日活跃统计 const dailyStatsSql = ` SELECT ual.active_date as date, COUNT(DISTINCT ual.user_id) as activeUsers, SUM(ual.operation_count) as totalOperations, SUM(ual.active_duration) as totalDuration FROM user_active_logs ual JOIN users u ON ual.user_id = u.userId WHERE ual.active_date BETWEEN ? AND ? AND u.phoneNumber IS NOT NULL AND u.phoneNumber != '' AND u.created_at IS NOT NULL AND u.type != 'Colleague' GROUP BY ual.active_date ORDER BY ual.active_date DESC `; // 执行查询 wechatPool.query(dailyStatsSql, [start, end], (err, results) => { if (err) { console.error('查询每日活跃统计失败:', err); return res.json({ success: false, message: '获取多天活跃统计失败' }); } // 处理结果 const dailyStats = results.map(row => ({ date: row.date, activeUsers: row.activeUsers, totalOperations: row.totalOperations, totalDuration: parseFloat(row.totalDuration) / 3600 // 转换为小时 })); console.log(`[${new Date().toISOString()}] API /api/daily-active-stats 处理结果: 共 ${dailyStats.length} 天数据`); const result = { success: true, data: { dailyStats: dailyStats, dateRange: { start: start, end: end } }, timestamp: Date.now() }; // 存储到普通缓存 normalCache.set(cacheKey, result); console.log(`多天活跃统计数据已存储到普通缓存: ${cacheKey}`); res.json(result); }); } catch (error) { console.error('获取多天活跃统计失败:', error); res.json({ success: false, message: '获取多天活跃统计失败' }); } }); // 获取总客户的活跃时长统计 app.get('/api/total-active-duration', (req, res) => { try { // 获取时间范围参数和分页参数 const { startDate, endDate, page, pageSize } = req.query; // 计算默认日期范围(最近7天,减少数据量) const now = new Date(); const defaultEnd = now.toISOString().split('T')[0]; const sevenDaysAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); const defaultStart = sevenDaysAgo.toISOString().split('T')[0]; // 使用提供的时间范围或默认值 const start = startDate || defaultStart; const end = endDate || defaultEnd; // 设置分页参数,默认为第1页,每页10条 const currentPage = parseInt(page) || 1; const sizePerPage = parseInt(pageSize) || 10; const offset = (currentPage - 1) * sizePerPage; console.log(`[${new Date().toISOString()}] API /api/total-active-duration 被调用,接收参数: startDate=${startDate}, endDate=${endDate}, page=${page}, pageSize=${pageSize}`); console.log(`[${new Date().toISOString()}] API /api/total-active-duration 使用时间范围: start=${start}, end=${end}`); // 使用user_active_logs表查询活跃时长统计 const userDurationSql = ` SELECT ual.user_id as userId, SUM(ual.active_duration) as totalDuration, COUNT(DISTINCT ual.active_date) as activeDays, MAX(ual.last_active_time) as lastActiveTime, u.phoneNumber, u.created_at as registerTime, u.updated_at as updateTime FROM user_active_logs ual JOIN users u ON ual.user_id = u.userId WHERE ual.active_date BETWEEN ? AND ? AND u.phoneNumber IS NOT NULL AND u.phoneNumber != '' AND u.created_at IS NOT NULL AND u.type != 'Colleague' GROUP BY ual.user_id, u.phoneNumber, u.created_at, u.updated_at ORDER BY totalDuration DESC, activeDays DESC, lastActiveTime DESC LIMIT ? OFFSET ? `; // 查询总数 const countSql = ` SELECT COUNT(DISTINCT ual.user_id) as total FROM user_active_logs ual JOIN users u ON ual.user_id = u.userId WHERE ual.active_date BETWEEN ? AND ? AND u.phoneNumber IS NOT NULL AND u.phoneNumber != '' AND u.created_at IS NOT NULL AND u.type != 'Colleague' `; // 并行执行查询 Promise.all([ new Promise((resolve, reject) => { wechatPool.query(countSql, [start, end], (err, results) => { if (err) reject(err); else resolve(results[0].total || 0); }); }), new Promise((resolve, reject) => { wechatPool.query(userDurationSql, [start, end, sizePerPage, offset], (err, results) => { if (err) reject(err); else resolve(results); }); }) ]) .then(([total, users]) => { // 计算总活跃时长(秒) const totalDurationSeconds = users.reduce((sum, user) => { // 确保user.totalDuration是有效的数字 const duration = parseFloat(user.totalDuration) || 0; return sum + duration; }, 0); // 转换为小时 const totalDurationHours = totalDurationSeconds / 3600; // 处理结果 const topActiveUsers = users.map(user => ({ userId: user.userId, phoneNumber: user.phoneNumber, registerTime: user.registerTime, updateTime: user.updateTime, duration: parseFloat(user.totalDuration) || 0, // 确保是有效的数字 lastActiveTime: user.lastActiveTime, lastViewedProduct: null, originalProductData: null })); console.log(`[${new Date().toISOString()}] API /api/total-active-duration 计算结果: totalDurationSeconds=${totalDurationSeconds}, totalDurationHours=${totalDurationHours}, activeUsers=${total}`); // 为每个用户查询最后浏览的商品 const userIds = topActiveUsers.map(user => user.userId); if (userIds.length > 0) { const lastViewedProductsSql = ` SELECT upv.user_id as userId, upv.product_id as productId, p.productName, p.product_contact, p.contact_phone, p.quantity, p.price, p.specification, p.category, p.created_at as createdAt, u.nickName as creatorNickName, u.phoneNumber as creatorPhone FROM user_product_views upv LEFT JOIN products p ON upv.product_id = p.productId LEFT JOIN users u ON p.sellerId = u.userId WHERE upv.user_id IN (${userIds.map(() => '?').join(',')}) AND upv.active_date BETWEEN ? AND ? AND upv.last_view_time = ( SELECT MAX(last_view_time) FROM user_product_views WHERE user_id = upv.user_id AND active_date BETWEEN ? AND ? ) `; wechatPool.query(lastViewedProductsSql, [...userIds, start, end, start, end], (err, productResults) => { if (!err && productResults.length > 0) { // 将查询到的商品信息添加到用户数据中 const productMap = {}; productResults.forEach(product => { productMap[product.userId] = product; }); topActiveUsers.forEach(user => { if (productMap[user.userId]) { const product = productMap[user.userId]; user.lastViewedProduct = { productId: product.productId, productName: product.productName || '未知商品', productContact: product.product_contact, contactPhone: product.contact_phone, quantity: product.quantity, price: product.price, specification: product.specification, category: product.category, createdAt: product.createdAt, creatorNickName: product.creatorNickName, creatorPhone: product.creatorPhone }; user.originalProductData = product; } }); } res.json({ success: true, data: { totalDuration: totalDurationHours, activeUsers: total, topActiveUsers: topActiveUsers, dateRange: { start: start, end: end } } }); }); } else { res.json({ success: true, data: { totalDuration: totalDurationHours, activeUsers: total, topActiveUsers: topActiveUsers, dateRange: { start: start, end: end } } }); } }) .catch(error => { console.error('获取总活跃时长统计失败:', error); res.json({ success: false, message: '获取总活跃时长统计失败' }); }); } catch (error) { console.error('获取总活跃时长统计失败:', error); res.json({ success: false, message: '获取总活跃时长统计失败' }); } }); // 旧的实现已被替换 - 使用user_active_logs表提高性能 // 以下是旧的实现,已被注释掉 /* app.get('/api/total-active-duration', (req, res) => { try { // 获取时间范围参数和分页参数 const { startDate, endDate, page, pageSize } = req.query; // 计算默认日期范围(最近7天,减少数据量) const now = new Date(); const defaultEndStr = now.toISOString().slice(0, 19).replace('T', ' '); const sevenDaysAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); const defaultStartStr = sevenDaysAgo.toISOString().slice(0, 19).replace('T', ' '); // 使用提供的时间范围或默认值 const startDateStr = startDate ? `${startDate} 00:00:00` : defaultStartStr; const endDateStr = endDate ? `${endDate} 23:59:59` : defaultEndStr; // 设置分页参数,默认为第1页,每页10条 const currentPage = parseInt(page) || 1; const sizePerPage = parseInt(pageSize) || 10; // 优化:先获取活跃用户列表(限制1000个),减少数据量 const activeUsersSql = ` SELECT ut.userId, MAX(ut.operationTime) as lastActiveTime, u.phoneNumber, u.created_at as registerTime, u.updated_at as updateTime FROM usertraces ut INNER JOIN users u ON ut.userId = u.userId WHERE ut.operationTime BETWEEN ? AND ? AND u.phoneNumber IS NOT NULL AND u.phoneNumber != '' AND u.created_at IS NOT NULL GROUP BY ut.userId, u.phoneNumber, u.created_at, u.updated_at ORDER BY lastActiveTime DESC LIMIT 1000 `; // 执行查询 wechatPool.query(activeUsersSql, [startDateStr, endDateStr], (operationsErr, operationsResults) => { if (operationsErr) { console.error('查询用户操作记录失败:', operationsErr); return res.json({ success: false, message: '获取总活跃时长统计失败' }); } // 按用户和日期分组操作记录 const userDailyOperations = {}; const lastViewedProducts = {}; const userLastActiveTime = {}; operationsResults.forEach(op => { const { userId, lastActiveTime, phoneNumber, registerTime, updateTime } = op; // 处理无效的时间值 let date = 'unknown'; try { date = new Date(lastActiveTime).toISOString().split('T')[0]; } catch (e) { console.error('无效的时间值:', lastActiveTime); } const key = `${userId}_${date}`; // 保存用户基本信息 - 确保保存最新的有效信息 if (!userLastActiveTime[userId]) { userLastActiveTime[userId] = { phoneNumber, registerTime, updateTime, lastActiveTime: lastActiveTime, lastViewedProduct: null }; } else { // 更新用户信息,确保保存最新的有效信息 if (phoneNumber && phoneNumber !== null && phoneNumber !== '') { userLastActiveTime[userId].phoneNumber = phoneNumber; } if (registerTime && registerTime !== null) { userLastActiveTime[userId].registerTime = registerTime; } if (updateTime && updateTime !== null) { userLastActiveTime[userId].updateTime = updateTime; } if (lastActiveTime && lastActiveTime > userLastActiveTime[userId].lastActiveTime) { userLastActiveTime[userId].lastActiveTime = lastActiveTime; } } // 分组操作记录 if (!userDailyOperations[key]) { userDailyOperations[key] = { userId, date, operations: [] }; } // 添加用户记录到operations数组 userDailyOperations[key].operations.push(op); }); // 提取活跃用户ID列表 const activeUserIds = operationsResults.map(user => user.userId); // 查询这些活跃用户的详细操作记录,用于计算活跃时长 const userOperationsSql = ` SELECT ut.userId, ut.operationTime, ut.originalData FROM usertraces ut WHERE ut.userId IN (${activeUserIds.map(() => '?').join(',')}) AND ut.operationTime BETWEEN ? AND ? ORDER BY ut.userId, ut.operationTime `; wechatPool.query(userOperationsSql, [...activeUserIds, startDateStr, endDateStr], (opErr, detailedOperationsResults) => { if (opErr) { console.error('查询用户操作记录失败:', opErr); return res.json({ success: false, message: '获取总活跃时长统计失败' }); } // 按用户和日期分组详细操作记录 const detailedUserDailyOperations = {}; detailedOperationsResults.forEach(op => { const { userId, operationTime, originalData } = op; // 处理无效的时间值 if (!operationTime) { return; // 跳过没有时间值的操作记录 } let date = 'unknown'; try { date = new Date(operationTime).toISOString().split('T')[0]; } catch (e) { console.error('无效的时间值:', operationTime); return; // 跳过无效的时间值 } const key = `${userId}_${date}`; // 分组操作记录 if (!detailedUserDailyOperations[key]) { detailedUserDailyOperations[key] = { userId, date, operations: [] }; } // 添加用户记录到operations数组 detailedUserDailyOperations[key].operations.push(op); // 更新用户最后浏览商品(只保存真正的商品记录) try { if (originalData && originalData !== '' && isProductView(originalData)) { if (userLastActiveTime[userId]) { userLastActiveTime[userId].lastViewedProduct = originalData; } } } catch (e) { // 忽略无效的商品数据 } }); // 计算每个用户的总活跃时长 const userTotalDuration = {}; for (const key in detailedUserDailyOperations) { const { userId, operations } = detailedUserDailyOperations[key]; const duration = calculateActiveDuration(operations); if (!userTotalDuration[userId]) { userTotalDuration[userId] = 0; } userTotalDuration[userId] += duration; } // 计算总统计 let totalDuration = 0; for (const userId in userTotalDuration) { totalDuration += userTotalDuration[userId]; } // 生成所有活跃客户的活跃时长排名,先不处理商品信息 let allActiveUsers = Object.keys(userTotalDuration) .sort((a, b) => userTotalDuration[b] - userTotalDuration[a]) .map(userId => { const userInfo = userLastActiveTime[userId]; return { userId, phoneNumber: userInfo.phoneNumber, registerTime: userInfo.registerTime, updateTime: userInfo.updateTime, duration: userTotalDuration[userId], lastActiveTime: userInfo.lastActiveTime, lastViewedProduct: userInfo.lastViewedProduct, originalProductData: userInfo.lastViewedProduct // 保存原始数据用于后续处理 }; }); // 过滤掉无效客户,只保留有电话号码和注册时间的客户 allActiveUsers = allActiveUsers.filter(user => { return user.phoneNumber && user.phoneNumber !== null && user.phoneNumber !== '' && user.registerTime && user.registerTime !== null; }); // 使用过滤后的客户数量作为活跃客户数 const activeUsers = allActiveUsers.length; // 分页处理 const startIndex = (currentPage - 1) * sizePerPage; const endIndex = startIndex + sizePerPage; let topActiveUsers = allActiveUsers.slice(startIndex, endIndex); // 提取所有商品ID,用于批量查询 const productIds = []; topActiveUsers.forEach(user => { if (user.originalProductData) { try { const originalData = JSON.parse(user.originalProductData); const productId = originalData.productId || originalData.id; if (productId) { productIds.push(productId); } } catch (e) { console.error('解析原始商品数据失败:', e); } } }); // 如果没有商品ID,直接返回 if (productIds.length === 0) { return res.json({ success: true, data: { totalDuration, activeUsers, topActiveUsers: topActiveUsers.map(user => ({ ...user, lastViewedProduct: user.originalProductData })), dateRange: { start: startDateStr, end: endDateStr } } }); } // 批量查询商品信息 const productInfoSql = ` SELECT p.productId, p.product_contact, p.contact_phone, p.quantity, p.sellerId, u.nickName as creatorNickName, u.phoneNumber as creatorPhone, p.created_at as createdAt FROM products p LEFT JOIN users u ON p.sellerId = u.userId WHERE p.productId IN (${productIds.map(() => '?').join(',')}) `; wechatPool.query(productInfoSql, productIds, (productInfoErr, productInfoResults) => { if (productInfoErr) { console.error('查询商品详情失败:', productInfoErr); // 如果查询失败,返回原始数据 return res.json({ success: true, data: { totalDuration, activeUsers, topActiveUsers: topActiveUsers.map(user => ({ ...user, lastViewedProduct: user.originalProductData })), dateRange: { start: startDateStr, end: endDateStr } } }); } else { // 构建商品信息映射 const productInfoMap = {}; productInfoResults.forEach(product => { productInfoMap[product.productId] = { productContact: product.product_contact || '', contactPhone: product.contact_phone || '', quantity: product.quantity || '', creatorNickName: product.creatorNickName || '', creatorPhone: product.creatorPhone || '', createdAt: product.createdAt || null }; }); // 更新topActiveUsers,添加完整商品信息 const updatedTopActiveUsers = topActiveUsers.map(user => { try { if (user.originalProductData) { const originalData = JSON.parse(user.originalProductData); const productId = originalData.productId || originalData.id; if (productId && productInfoMap[productId]) { // 合并商品信息 const updatedProductData = { ...originalData, ...productInfoMap[productId] }; return { ...user, lastViewedProduct: JSON.stringify(updatedProductData) }; } } } catch (e) { console.error('处理商品数据失败:', e); } // 如果处理失败,返回原始数据 return { ...user, lastViewedProduct: user.originalProductData }; }); res.json({ success: true, data: { totalDuration, activeUsers, topActiveUsers: updatedTopActiveUsers, dateRange: { start: startDateStr, end: endDateStr } } }); } }); }); }); } catch (error) { console.error('获取总活跃时长统计失败:', error); res.json({ success: false, message: '获取总活跃时长统计失败' }); } }); */ // 获取总客户的活跃时长统计 app.get('/api/total-active-duration', (req, res) => { try { // 获取时间范围参数和分页参数 const { startDate, endDate, page, pageSize } = req.query; // 计算默认日期范围(最近7天,减少数据量) const now = new Date(); const defaultEndStr = now.toISOString().slice(0, 19).replace('T', ' '); const sevenDaysAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); const defaultStartStr = sevenDaysAgo.toISOString().slice(0, 19).replace('T', ' '); // 使用提供的时间范围或默认值 const startDateStr = startDate ? `${startDate} 00:00:00` : defaultStartStr; const endDateStr = endDate ? `${endDate} 23:59:59` : defaultEndStr; // 设置分页参数,默认为第1页,每页10条 const currentPage = parseInt(page) || 1; const sizePerPage = parseInt(pageSize) || 10; // 优化:先获取活跃用户列表(限制1000个),减少数据量 const activeUsersSql = ` SELECT ut.userId, MAX(ut.operationTime) as lastActiveTime, u.phoneNumber, u.created_at as registerTime, u.updated_at as updateTime FROM usertraces ut INNER JOIN users u ON ut.userId = u.userId WHERE ut.operationTime BETWEEN ? AND ? AND u.phoneNumber IS NOT NULL AND u.phoneNumber != '' AND u.created_at IS NOT NULL GROUP BY ut.userId, u.phoneNumber, u.created_at, u.updated_at ORDER BY lastActiveTime DESC LIMIT 1000 `; // 执行查询 wechatPool.query(activeUsersSql, [startDateStr, endDateStr], (operationsErr, operationsResults) => { if (operationsErr) { console.error('查询用户操作记录失败:', operationsErr); return res.json({ success: false, message: '获取总活跃时长统计失败' }); } // 按用户和日期分组操作记录 const userDailyOperations = {}; const lastViewedProducts = {}; const userLastActiveTime = {}; operationsResults.forEach(op => { const { userId, lastActiveTime, phoneNumber, registerTime, updateTime } = op; // 处理无效的时间值 let date = 'unknown'; try { date = new Date(lastActiveTime).toISOString().split('T')[0]; } catch (e) { console.error('无效的时间值:', lastActiveTime); } const key = `${userId}_${date}`; // 保存用户基本信息 - 确保保存最新的有效信息 if (!userLastActiveTime[userId]) { userLastActiveTime[userId] = { phoneNumber, registerTime, updateTime, lastActiveTime: lastActiveTime, lastViewedProduct: null }; } else { // 更新用户信息,确保保存最新的有效信息 if (phoneNumber && phoneNumber !== null && phoneNumber !== '') { userLastActiveTime[userId].phoneNumber = phoneNumber; } if (registerTime && registerTime !== null) { userLastActiveTime[userId].registerTime = registerTime; } if (updateTime && updateTime !== null) { userLastActiveTime[userId].updateTime = updateTime; } if (lastActiveTime && lastActiveTime > userLastActiveTime[userId].lastActiveTime) { userLastActiveTime[userId].lastActiveTime = lastActiveTime; } } // 分组操作记录 if (!userDailyOperations[key]) { userDailyOperations[key] = { userId, date, operations: [] }; } // 添加用户记录到operations数组 userDailyOperations[key].operations.push(op); }); // 提取活跃用户ID列表 const activeUserIds = operationsResults.map(user => user.userId); // 查询这些活跃用户的详细操作记录,用于计算活跃时长 const userOperationsSql = ` SELECT ut.userId, ut.operationTime, ut.originalData FROM usertraces ut WHERE ut.userId IN (${activeUserIds.map(() => '?').join(',')}) AND ut.operationTime BETWEEN ? AND ? ORDER BY ut.userId, ut.operationTime `; wechatPool.query(userOperationsSql, [...activeUserIds, startDateStr, endDateStr], (opErr, detailedOperationsResults) => { if (opErr) { console.error('查询用户操作记录失败:', opErr); return res.json({ success: false, message: '获取总活跃时长统计失败' }); } // 按用户和日期分组详细操作记录 const detailedUserDailyOperations = {}; detailedOperationsResults.forEach(op => { const { userId, operationTime, originalData } = op; // 处理无效的时间值 if (!operationTime) { return; // 跳过没有时间值的操作记录 } let date = 'unknown'; try { date = new Date(operationTime).toISOString().split('T')[0]; } catch (e) { console.error('无效的时间值:', operationTime); return; // 跳过无效的时间值 } const key = `${userId}_${date}`; // 分组操作记录 if (!detailedUserDailyOperations[key]) { detailedUserDailyOperations[key] = { userId, date, operations: [] }; } detailedUserDailyOperations[key].operations.push(op); // 更新用户最后浏览商品(只保存真正的商品记录) try { if (originalData && originalData !== '' && isProductView(originalData)) { if (userLastActiveTime[userId]) { userLastActiveTime[userId].lastViewedProduct = originalData; } } } catch (e) { // 忽略无效的商品数据 } }); // 计算每个用户的总活跃时长 const userTotalDuration = {}; for (const key in detailedUserDailyOperations) { const { userId, operations } = detailedUserDailyOperations[key]; const duration = calculateActiveDuration(operations); if (!userTotalDuration[userId]) { userTotalDuration[userId] = 0; } userTotalDuration[userId] += duration; } // 计算总统计 let totalDuration = 0; for (const userId in userTotalDuration) { totalDuration += userTotalDuration[userId]; } // 生成所有活跃客户的活跃时长排名,先不处理商品信息 let allActiveUsers = Object.keys(userTotalDuration) .sort((a, b) => userTotalDuration[b] - userTotalDuration[a]) .map(userId => { const userInfo = userLastActiveTime[userId]; return { userId, phoneNumber: userInfo.phoneNumber, registerTime: userInfo.registerTime, updateTime: userInfo.updateTime, duration: userTotalDuration[userId], lastActiveTime: userInfo.lastActiveTime, lastViewedProduct: userInfo.lastViewedProduct, originalProductData: userInfo.lastViewedProduct // 保存原始数据用于后续处理 }; }); // 过滤掉无效客户,只保留有电话号码和注册时间的客户 allActiveUsers = allActiveUsers.filter(user => { return user.phoneNumber && user.phoneNumber !== null && user.phoneNumber !== '' && user.registerTime && user.registerTime !== null; }); // 使用过滤后的客户数量作为活跃客户数 const activeUsers = allActiveUsers.length; // 分页处理 const startIndex = (currentPage - 1) * sizePerPage; const endIndex = startIndex + sizePerPage; let topActiveUsers = allActiveUsers.slice(startIndex, endIndex); // 提取所有商品ID,用于批量查询 const productIds = []; topActiveUsers.forEach(user => { if (user.originalProductData) { try { const originalData = JSON.parse(user.originalProductData); const productId = originalData.productId || originalData.id; if (productId) { productIds.push(productId); } } catch (e) { console.error('解析原始商品数据失败:', e); } } }); // 如果没有商品ID,直接返回 if (productIds.length === 0) { return res.json({ success: true, data: { totalDuration, activeUsers, topActiveUsers: topActiveUsers.map(user => ({ ...user, lastViewedProduct: user.originalProductData })), dateRange: { start: startDateStr, end: endDateStr } } }); } // 批量查询商品信息 const productInfoSql = ` SELECT p.productId, p.product_contact, p.contact_phone, p.quantity, p.sellerId, u.nickName as creatorNickName, u.phoneNumber as creatorPhone, p.created_at as createdAt FROM products p LEFT JOIN users u ON p.sellerId = u.userId WHERE p.productId IN (${productIds.map(() => '?').join(',')}) `; wechatPool.query(productInfoSql, productIds, (productInfoErr, productInfoResults) => { if (productInfoErr) { console.error('查询商品详情失败:', productInfoErr); // 如果查询失败,返回原始数据 return res.json({ success: true, data: { totalDuration, activeUsers, topActiveUsers: topActiveUsers.map(user => ({ ...user, lastViewedProduct: user.originalProductData })), dateRange: { start: startDateStr, end: endDateStr } } }); } else { // 构建商品信息映射 const productInfoMap = {}; productInfoResults.forEach(product => { productInfoMap[product.productId] = { productContact: product.product_contact || '', contactPhone: product.contact_phone || '', quantity: product.quantity || '', creatorNickName: product.creatorNickName || '', creatorPhone: product.creatorPhone || '', createdAt: product.createdAt || null }; }); // 更新topActiveUsers,添加完整商品信息 const updatedTopActiveUsers = topActiveUsers.map(user => { try { if (user.originalProductData) { const originalData = JSON.parse(user.originalProductData); const productId = originalData.productId || originalData.id; if (productId && productInfoMap[productId]) { // 合并商品信息 const updatedProductData = { ...originalData, ...productInfoMap[productId] }; return { ...user, lastViewedProduct: JSON.stringify(updatedProductData) }; } } } catch (e) { console.error('处理商品数据失败:', e); } // 如果处理失败,返回原始数据 return { ...user, lastViewedProduct: user.originalProductData }; }); res.json({ success: true, data: { totalDuration, activeUsers, topActiveUsers: updatedTopActiveUsers, dateRange: { start: startDateStr, end: endDateStr } } }); } }); }); }); } catch (error) { console.error('获取总活跃时长统计失败:', error); res.json({ success: false, message: '获取总活跃时长统计失败' }); } }); // 获取用户某一天的浏览商品记录 app.get('/api/user-day-products/:userId', (req, res) => { try { const { userId } = req.params; let { date } = req.query; console.log(`[${new Date().toISOString()}] API /api/user-day-products/${userId} 被调用,接收参数: userId=${userId}, date=${date}`); // 验证参数 if (!date) { console.error(`[${new Date().toISOString()}] API /api/user-day-products/${userId} 缺少日期参数`); return res.json({ success: false, message: '缺少日期参数' }); } // 确保日期格式为YYYY-MM-DD let formattedDate = date; try { // 检查是否已经是YYYY-MM-DD格式 const dateRegex = /^\d{4}-\d{2}-\d{2}$/; if (dateRegex.test(date)) { // 已经是YYYY-MM-DD格式,直接使用 formattedDate = date; console.log(`[${new Date().toISOString()}] API /api/user-day-products/${userId} 日期已是YYYY-MM-DD格式,直接使用: ${formattedDate}`); } else { // 尝试解析日期并格式化为YYYY-MM-DD console.log(`[${new Date().toISOString()}] API /api/user-day-products/${userId} 日期需要转换: 原始日期=${date}`); const dateObj = new Date(date); if (!isNaN(dateObj.getTime())) { formattedDate = dateObj.toISOString().split('T')[0]; console.log(`[${new Date().toISOString()}] API /api/user-day-products/${userId} 日期转换成功: 转换后=${formattedDate}`); } else { console.error(`[${new Date().toISOString()}] API /api/user-day-products/${userId} 日期格式错误: ${date}`); return res.json({ success: false, message: '日期格式错误,请使用YYYY-MM-DD格式' }); } } } catch (e) { console.error(`[${new Date().toISOString()}] API /api/user-day-products/${userId} 日期处理异常:`, e); return res.json({ success: false, message: '日期格式错误,请使用YYYY-MM-DD格式' }); } // 构建查询SQL - 使用BETWEEN替代DATE()函数,避免时区问题 const startDateStr = `${formattedDate} 00:00:00`; const endDateStr = `${formattedDate} 23:59:59`; // 查询所有记录,然后在应用层过滤出真正的商品记录 const sql = ` SELECT operationTime, originalData FROM usertraces WHERE userId = ? AND operationTime BETWEEN ? AND ? ORDER BY operationTime ASC `; console.log(`[${new Date().toISOString()}] API /api/user-day-products/${userId} 使用时间范围查询: startDateStr=${startDateStr}, endDateStr=${endDateStr}`); // 执行查询 wechatPool.query(sql, [userId, startDateStr, endDateStr], (err, results) => { if (err) { console.error('获取用户浏览商品记录失败:', err); return res.json({ success: false, message: '获取浏览商品记录失败' }); } // 解析并过滤出真正的商品记录和收藏操作记录 const productRecords = []; const productIds = []; results.forEach(item => { if (item.originalData && item.originalData !== '') { try { const originalData = JSON.parse(item.originalData); // 同时包含商品浏览记录和收藏操作记录 if (isProductView(item.originalData) || isFavoriteAction(item.originalData)) { const productId = originalData.productId || originalData.id; if (productId) { productIds.push(productId); productRecords.push({ ...item, parsedData: originalData, productId: productId }); } } } catch (e) { console.error('解析商品数据失败:', e); } } }); // 如果没有有效的商品记录,直接返回 if (productRecords.length === 0) { return res.json({ success: true, data: { products: [] } }); } // 去重productIds const uniqueProductIds = [...new Set(productIds)]; // 如果没有有效的productId,直接返回 if (uniqueProductIds.length === 0) { return res.json({ success: true, data: { products: [] } }); } // 查询产品信息和创建人信息 const productSql = ` SELECT p.productId, p.productName, p.price, p.category, p.specification, p.quantity, p.imageUrls, p.product_contact, p.contact_phone, p.sellerId, p.variety, p.created_at as productCreatedAt, u.nickName as creatorNickName, u.phoneNumber as creatorPhone FROM products p LEFT JOIN users u ON p.sellerId = u.userId WHERE p.productId IN (${uniqueProductIds.map(() => '?').join(',')}) `; wechatPool.query(productSql, uniqueProductIds, (productErr, productResults) => { if (productErr) { console.error('查询产品信息失败:', productErr); // 如果查询失败,返回基础商品数据 const finalResults = productRecords.map(item => ({ operationTime: item.operationTime, // 作为createdAt originalData: item.originalData, parsedData: item.parsedData, productId: item.productId, createdAt: item.operationTime, quantity: '', productContact: '', contactPhone: '', creatorNickName: '', creatorPhone: '' })); return res.json({ success: true, data: { products: finalResults } }); } // 构建产品信息映射 const productMap = {}; productResults.forEach(product => { productMap[product.productId] = { productName: product.productName || '', price: product.price || '', category: product.category || '', specification: product.specification || '', quantity: product.quantity || '', imageUrls: product.imageUrls || '', productContact: product.product_contact || '', contactPhone: product.contact_phone || '', creatorNickName: product.creatorNickName || '', creatorPhone: product.creatorPhone || '', variety: product.variety || '', productCreatedAt: product.productCreatedAt || '' }; }); // 合并信息 const finalResults = productRecords.map(item => { const productInfo = productMap[item.productId] || { productName: '', price: '', category: '', specification: '', quantity: '', imageUrls: '', productContact: '', contactPhone: '', creatorNickName: '', creatorPhone: '', variety: '', productCreatedAt: '' }; return { operationTime: item.operationTime, originalData: item.originalData, parsedData: item.parsedData, productId: item.productId, createdAt: item.operationTime, productName: productInfo.productName, price: productInfo.price, category: productInfo.category, specification: productInfo.specification, quantity: productInfo.quantity, imageUrls: productInfo.imageUrls, productContact: productInfo.productContact, contactPhone: productInfo.contactPhone, creatorNickName: productInfo.creatorNickName, creatorPhone: productInfo.creatorPhone, variety: productInfo.variety, productCreatedAt: productInfo.productCreatedAt }; }); res.json({ success: true, data: { products: finalResults } }); }); }); } catch (error) { console.error('获取用户浏览商品记录失败:', error); res.json({ success: false, message: '获取浏览商品记录失败' }); } }); // 根据电话号码获取客户ID app.get('/api/user-id-by-phone', (req, res) => { try { const { phone } = req.query; // 验证参数 if (!phone) { return res.json({ success: false, message: '缺少电话号码参数' }); } // 构建查询SQL const sql = ` SELECT userId FROM users WHERE phoneNumber = ? LIMIT 1 `; // 执行查询 wechatPool.query(sql, [phone], (err, results) => { if (err) { console.error('根据电话号码获取客户ID失败:', err); return res.json({ success: false, message: '获取客户信息失败' }); } if (results.length > 0) { res.json({ success: true, data: { userId: results[0].userId } }); } else { res.json({ success: false, message: '未找到该电话号码对应的客户' }); } }); } catch (error) { console.error('根据电话号码获取客户ID失败:', error); res.json({ success: false, message: '获取客户信息失败' }); } }); // 获取业务统计数据 app.get('/api/business-stats', (req, res) => { try { console.log('API接收到的业务统计请求,获取所有数据'); // 查询业务员客户统计数据(排除type值为Colleague的客户) const agentStatsSql = ` SELECT CASE WHEN um.userName IS NULL OR um.userName = '' THEN '公海池客户' ELSE um.userName END as agentName, COUNT(*) as totalClients, SUM(CASE WHEN u.followup IS NOT NULL AND u.followup != '' THEN 1 ELSE 0 END) as followedClients, SUM(CASE WHEN u.followup IS NULL OR u.followup = '' THEN 1 ELSE 0 END) as notFollowedClients, COUNT(DISTINCT u.company) as companyCount, COUNT(DISTINCT u.level) as levelCount, COUNT(DISTINCT u.demand) as demandCount, COUNT(DISTINCT u.region) as regionCount FROM users u LEFT JOIN usermanagements um ON um.userId = u.userId WHERE u.type != ? GROUP BY agentName ORDER BY totalClients DESC `; // 查询客户公司分布 const companyDistributionSql = ` SELECT company, COUNT(*) as clientCount FROM users WHERE type != ? AND company IS NOT NULL AND company != '' GROUP BY company ORDER BY clientCount DESC `; // 查询客户级别分布 const levelDistributionSql = ` SELECT level, COUNT(*) as clientCount FROM users WHERE type != ? AND level IS NOT NULL AND level != '' GROUP BY level ORDER BY clientCount DESC `; // 查询客户需求分布 const demandDistributionSql = ` SELECT demand, COUNT(*) as clientCount FROM users WHERE type != ? AND demand IS NOT NULL AND demand != '' GROUP BY demand ORDER BY clientCount DESC `; // 查询客户地区分布 const regionDistributionSql = ` SELECT region, COUNT(*) as clientCount FROM users WHERE type != ? AND region IS NOT NULL AND region != '' GROUP BY region ORDER BY clientCount DESC `; // 查询客户跟进状态分布 const followStatusDistributionSql = ` SELECT CASE WHEN followup_at IS NOT NULL THEN '已跟进' ELSE '未跟进' END as followStatus, COUNT(*) as clientCount FROM users WHERE type != ? GROUP BY followStatus `; // 查询客户类型分布 const typeDistributionSql = ` SELECT type, COUNT(*) as clientCount FROM users WHERE type != ? GROUP BY type ORDER BY clientCount DESC `; // 执行查询 wechatPool.query(agentStatsSql, ['Colleague'], (agentStatsErr, agentStatsResults) => { if (agentStatsErr) { console.error('查询业务员客户统计数据失败:', agentStatsErr); return res.json({ success: false, message: '获取业务统计数据失败' }); } wechatPool.query(companyDistributionSql, ['Colleague'], (companyErr, companyResults) => { if (companyErr) { console.error('查询客户公司分布失败:', companyErr); return res.json({ success: false, message: '获取业务统计数据失败' }); } wechatPool.query(levelDistributionSql, ['Colleague'], (levelErr, levelResults) => { if (levelErr) { console.error('查询客户级别分布失败:', levelErr); return res.json({ success: false, message: '获取业务统计数据失败' }); } wechatPool.query(demandDistributionSql, ['Colleague'], (demandErr, demandResults) => { if (demandErr) { console.error('查询客户需求分布失败:', demandErr); return res.json({ success: false, message: '获取业务统计数据失败' }); } wechatPool.query(regionDistributionSql, ['Colleague'], (regionErr, regionResults) => { if (regionErr) { console.error('查询客户地区分布失败:', regionErr); return res.json({ success: false, message: '获取业务统计数据失败' }); } wechatPool.query(followStatusDistributionSql, ['Colleague'], (followStatusErr, followStatusResults) => { if (followStatusErr) { console.error('查询客户跟进状态分布失败:', followStatusErr); return res.json({ success: false, message: '获取业务统计数据失败' }); } wechatPool.query(typeDistributionSql, ['Colleague'], (typeErr, typeResults) => { if (typeErr) { console.error('查询客户类型分布失败:', typeErr); return res.json({ success: false, message: '获取业务统计数据失败' }); } // 计算总客户数 const totalClients = followStatusResults.reduce((sum, item) => sum + item.clientCount, 0); res.json({ success: true, data: { totalClients: totalClients, agentStats: agentStatsResults, distributions: { company: companyResults, level: levelResults, type: typeResults, demand: demandResults, region: regionResults, followStatus: followStatusResults } } }); }); }); }); }); }); }); }); } catch (error) { console.error('获取业务统计数据失败:', error); res.json({ success: false, message: '获取业务统计数据失败' }); } }); // 获取指定业务员的详细业务统计数据 app.get('/api/business-stats/agent/:agentName', (req, res) => { try { const { agentName } = req.params; console.log('API接收到的业务员详细统计参数:', agentName); // 查询指定业务员的客户列表及跟进情况 const agentClientsSql = ` SELECT u.nickName as clientName, u.phoneNumber as clientPhone, u.company, u.level, u.type, u.demand, u.region, u.followup, u.followup_at, u.created_at as clientCreatedAt, u.updated_at as clientUpdatedAt FROM users u LEFT JOIN usermanagements um ON um.userId = u.userId WHERE ( (um.userName = ? AND u.type != ?) ${agentName === '公海池客户' ? "OR (um.userName IS NULL OR um.userName = '' AND u.type != ?)" : ''} ) ORDER BY u.followup_at DESC, u.updated_at DESC `; // 查询参数 let queryParams = []; if (agentName === '公海池客户') { queryParams = [agentName, 'Colleague', 'Colleague']; } else { queryParams = [agentName, 'Colleague']; } // 执行查询 wechatPool.query(agentClientsSql, queryParams, (clientsErr, clientsResults) => { if (clientsErr) { console.error('查询业务员客户列表失败:', clientsErr); return res.json({ success: false, message: '获取业务员详细统计数据失败' }); } // 查询指定业务员的统计汇总 const agentSummarySql = ` SELECT COUNT(*) as totalClients, SUM(CASE WHEN u.followup IS NOT NULL AND u.followup != '' THEN 1 ELSE 0 END) as followedClients, SUM(CASE WHEN u.followup IS NULL OR u.followup = '' THEN 1 ELSE 0 END) as notFollowedClients, COUNT(DISTINCT u.company) as companyCount, COUNT(DISTINCT u.level) as levelCount, COUNT(DISTINCT u.demand) as demandCount, COUNT(DISTINCT u.region) as regionCount FROM users u LEFT JOIN usermanagements um ON um.userId = u.userId WHERE ( (um.userName = ? AND u.type != ?) ${agentName === '公海池客户' ? "OR (um.userName IS NULL OR um.userName = '' AND u.type != ?)" : ''} ) `; wechatPool.query(agentSummarySql, queryParams, (summaryErr, summaryResults) => { if (summaryErr) { console.error('查询业务员统计汇总失败:', summaryErr); return res.json({ success: false, message: '获取业务员详细统计数据失败' }); } res.json({ success: true, data: { agentName: agentName, summary: summaryResults[0], clients: clientsResults } }); }); }); } catch (error) { console.error('获取业务员详细统计数据失败:', error); res.json({ success: false, message: '获取业务员详细统计数据失败' }); } }); // 获取评论列表 app.get('/api/comments', (req, res) => { try { const page = parseInt(req.query.page) || 1; const pageSize = parseInt(req.query.pageSize) || 10; const status = req.query.status || ''; const search = req.query.search || ''; // 计算偏移量 const offset = (page - 1) * pageSize; // 构建查询条件 let whereClause = ''; let countParams = []; let dataParams = []; if (status) { whereClause += whereClause ? ' AND ' : ' WHERE '; if (status === '0') { // 待审核状态包括review=0和review=NULL whereClause += '(review = 0 OR review IS NULL)'; } else { whereClause += 'review = ?'; countParams.push(status); dataParams.push(status); } } if (search) { whereClause += whereClause ? ' AND ' : ' WHERE '; whereClause += '(comments LIKE ? OR phoneNumber LIKE ?)'; countParams.push(`%${search}%`); countParams.push(`%${search}%`); dataParams.push(`%${search}%`); dataParams.push(`%${search}%`); } const startDate = req.query.startDate; const endDate = req.query.endDate; if (startDate) { whereClause += whereClause ? ' AND ' : ' WHERE '; whereClause += 'time >= ?'; countParams.push(startDate); dataParams.push(startDate); } if (endDate) { whereClause += whereClause ? ' AND ' : ' WHERE '; whereClause += 'time <= ?'; countParams.push(endDate); dataParams.push(endDate); } // 添加分页参数 dataParams.push(pageSize); dataParams.push(offset); // 查询总数 const countSql = `SELECT COUNT(*) as total FROM comments${whereClause}`; // 查询待审核总数 const pendingCountSql = "SELECT COUNT(*) as pendingTotal FROM comments WHERE review = 0 OR review IS NULL"; // 查询数据 const dataSql = `SELECT * FROM comments${whereClause} ORDER BY time DESC LIMIT ? OFFSET ?`; // 先查询总数 wechatPool.query(countSql, countParams, (countErr, countResults) => { if (countErr) { console.error('查询评论总数失败:', countErr); return res.json({ success: false, message: '查询评论失败,请稍后重试' }); } const total = countResults[0].total; // 查询待审核总数 wechatPool.query(pendingCountSql, [], (pendingErr, pendingResults) => { if (pendingErr) { console.error('查询待审核总数失败:', pendingErr); return res.json({ success: false, message: '查询评论失败,请稍后重试' }); } const pendingTotal = pendingResults[0].pendingTotal; // 再查询数据 wechatPool.query(dataSql, dataParams, (dataErr, dataResults) => { if (dataErr) { console.error('查询评论数据失败:', dataErr); return res.json({ success: false, message: '查询评论失败,请稍后重试' }); } // 数据库字段名已经与前端期望的格式匹配,直接返回 const formattedComments = dataResults.map(comment => ({ id: comment.id, phoneNumber: comment.phoneNumber, productId: comment.productId, time: comment.time, comments: comment.comments, review: comment.review, like: comment.like || 0, hate: comment.hate || 0 })); res.json({ success: true, comments: formattedComments, total, pendingTotal }); }); }); }); } catch (error) { console.error('获取评论列表失败:', error); res.json({ success: false, message: '获取评论列表失败' }); } }); // 更新评论审核状态 app.post('/api/comments/update', (req, res) => { try { const { id, review } = req.body; if (!id || review === undefined) { return res.json({ success: false, message: '参数不全' }); } const sql = 'UPDATE comments SET review = ? WHERE id = ?'; wechatPool.query(sql, [review, id], (err, results) => { if (err) { console.error('更新评论审核状态失败:', err); return res.json({ success: false, message: '更新评论审核状态失败,请稍后重试' }); } if (results.affectedRows === 0) { return res.json({ success: false, message: '未找到匹配的评论' }); } res.json({ success: true, message: '更新评论审核状态成功' }); }); } catch (error) { console.error('更新评论审核状态失败:', error); res.json({ success: false, message: '更新评论审核状态失败' }); } }); // 批量更新评论审核状态 app.post('/api/comments/batch-update', (req, res) => { try { const { ids, review } = req.body; if (!ids || !Array.isArray(ids) || ids.length === 0 || review === undefined) { return res.json({ success: false, message: '参数不全' }); } const sql = 'UPDATE comments SET review = ? WHERE id IN (?)'; wechatPool.query(sql, [review, ids], (err, results) => { if (err) { console.error('批量更新评论审核状态失败:', err); return res.json({ success: false, message: '批量更新评论审核状态失败,请稍后重试' }); } if (results.affectedRows === 0) { return res.json({ success: false, message: '未找到匹配的评论' }); } res.json({ success: true, message: '批量更新评论审核状态成功', affectedRows: results.affectedRows }); }); } catch (error) { console.error('批量更新评论审核状态失败:', error); res.json({ success: false, message: '批量更新评论审核状态失败' }); } }); // 获取论坛动态列表 app.get('/api/forum-posts', (req, res) => { try { const page = parseInt(req.query.page) || 1; const pageSize = parseInt(req.query.pageSize) || 10; const status = req.query.status; const search = req.query.search; const startDate = req.query.startDate; const endDate = req.query.endDate; // 计算偏移量 const offset = (page - 1) * pageSize; // 构建查询条件 let whereClause = ''; let countParams = []; let dataParams = []; if (status) { whereClause += whereClause ? ' AND ' : ' WHERE '; if (status === '0') { // 待审核状态包括status=0和status=NULL whereClause += '(status = 0 OR status IS NULL)'; } else { whereClause += 'status = ?'; countParams.push(status); dataParams.push(status); } } if (search) { whereClause += whereClause ? ' AND ' : ' WHERE '; whereClause += '(content LIKE ? OR phone LIKE ?)'; countParams.push(`%${search}%`); countParams.push(`%${search}%`); dataParams.push(`%${search}%`); dataParams.push(`%${search}%`); } if (startDate) { whereClause += whereClause ? ' AND ' : ' WHERE '; whereClause += 'created_at >= ?'; countParams.push(startDate); dataParams.push(startDate); } if (endDate) { whereClause += whereClause ? ' AND ' : ' WHERE '; whereClause += 'created_at <= ?'; countParams.push(endDate); dataParams.push(endDate); } // 添加分页参数 dataParams.push(pageSize); dataParams.push(offset); // 查询总数 const countSql = `SELECT COUNT(*) as total FROM eggbar_posts${whereClause}`; // 查询待审核总数 const pendingCountSql = "SELECT COUNT(*) as pendingTotal FROM eggbar_posts WHERE status = 0 OR status IS NULL"; // 查询数据 const dataSql = `SELECT * FROM eggbar_posts${whereClause} ORDER BY created_at DESC LIMIT ? OFFSET ?`; // 先查询总数 eggbarPool.query(countSql, countParams, (countErr, countResults) => { if (countErr) { console.error('查询论坛动态总数失败:', countErr); return res.json({ success: false, message: '查询论坛动态失败,请稍后重试' }); } const total = countResults[0].total; // 查询待审核总数 eggbarPool.query(pendingCountSql, [], (pendingErr, pendingResults) => { if (pendingErr) { console.error('查询待审核总数失败:', pendingErr); return res.json({ success: false, message: '查询论坛动态失败,请稍后重试' }); } const pendingTotal = pendingResults[0].pendingTotal; // 再查询数据 eggbarPool.query(dataSql, dataParams, (dataErr, dataResults) => { if (dataErr) { console.error('查询论坛动态数据失败:', dataErr); return res.json({ success: false, message: '查询论坛动态失败,请稍后重试' }); } res.json({ success: true, posts: dataResults, total, pendingTotal }); }); }); }); } catch (error) { console.error('获取论坛动态列表失败:', error); res.json({ success: false, message: '获取论坛动态列表失败' }); } }); // 更新论坛动态审核状态 app.post('/api/forum-posts/update', (req, res) => { try { const { id, status } = req.body; if (!id || status === undefined) { return res.json({ success: false, message: '参数不全' }); } const sql = 'UPDATE eggbar_posts SET status = ? WHERE id = ?'; eggbarPool.query(sql, [status, id], (err, results) => { if (err) { console.error('更新论坛动态审核状态失败:', err); return res.json({ success: false, message: '更新论坛动态审核状态失败,请稍后重试' }); } if (results.affectedRows === 0) { return res.json({ success: false, message: '未找到匹配的论坛动态' }); } res.json({ success: true, message: '更新论坛动态审核状态成功' }); }); } catch (error) { console.error('更新论坛动态审核状态失败:', error); res.json({ success: false, message: '更新论坛动态审核状态失败' }); } }); // 批量更新论坛动态审核状态 app.post('/api/forum-posts/batch-update', (req, res) => { try { const { ids, status } = req.body; if (!ids || !Array.isArray(ids) || ids.length === 0 || status === undefined) { return res.json({ success: false, message: '参数不全' }); } const sql = 'UPDATE eggbar_posts SET status = ? WHERE id IN (?)'; eggbarPool.query(sql, [status, ids], (err, results) => { if (err) { console.error('批量更新论坛动态审核状态失败:', err); return res.json({ success: false, message: '批量更新论坛动态审核状态失败,请稍后重试' }); } if (results.affectedRows === 0) { return res.json({ success: false, message: '未找到匹配的论坛动态' }); } res.json({ success: true, message: '批量更新论坛动态审核状态成功', affectedRows: results.affectedRows }); }); } catch (error) { console.error('批量更新论坛动态审核状态失败:', error); res.json({ success: false, message: '批量更新论坛动态审核状态失败' }); } }); // 获取身份信息审核列表 app.get('/api/identity-verification', (req, res) => { try { const { status = 'pending', page = 1, pageSize = 10 } = req.query; // 计算偏移量 const offset = (page - 1) * pageSize; // 构建查询条件 let whereClause = ''; let countParams = []; let dataParams = []; if (status === 'pending') { whereClause = ' WHERE (idcardstatus = 0 OR idcardstatus IS NULL)'; } else if (status === 'approved') { whereClause = ' WHERE idcardstatus = 1'; } else if (status === 'rejected') { whereClause = ' WHERE idcardstatus = 2'; } // 添加分页参数(确保是数字类型) dataParams.push(parseInt(pageSize)); dataParams.push(parseInt(offset)); // 查询总数 const countSql = `SELECT COUNT(*) as total FROM users${whereClause}`; // 查询数据 const dataSql = `SELECT id, userId, phoneNumber as phone, idcard1, idcard2, businesslicenseurl, idcardstatus, reason, created_at FROM users${whereClause} ORDER BY created_at DESC LIMIT ? OFFSET ?`; // 先查询总数 wechatPool.query(countSql, countParams, (countErr, countResults) => { if (countErr) { console.error('查询身份信息审核总数失败:', countErr); return res.json({ success: false, message: '获取身份信息审核列表失败,请稍后重试' }); } const total = countResults[0].total; // 再查询数据 wechatPool.query(dataSql, dataParams, (dataErr, dataResults) => { if (dataErr) { console.error('查询身份信息审核数据失败:', dataErr); return res.json({ success: false, message: '获取身份信息审核列表失败,请稍后重试' }); } res.json({ success: true, data: dataResults, total, page, pageSize }); }); }); } catch (error) { console.error('获取身份信息审核列表失败:', error); res.json({ success: false, message: '获取身份信息审核列表失败,请稍后重试' }); } }); // 获取身份信息列表 app.get('/api/identities', (req, res) => { try { const page = parseInt(req.query.page) || 1; const pageSize = parseInt(req.query.pageSize) || 10; const status = req.query.status; const search = req.query.search || ''; const startDate = req.query.startDate || ''; const endDate = req.query.endDate || ''; // 计算偏移量 const offset = (page - 1) * pageSize; // 构建查询条件 let whereClause = ''; let countParams = []; let dataParams = []; if (status) { whereClause += whereClause ? ' AND ' : ' WHERE '; if (status === '0') { // 待审核状态包括idcardstatus=0和idcardstatus=NULL whereClause += '(idcardstatus = 0 OR idcardstatus IS NULL)'; } else { whereClause += 'idcardstatus = ?'; countParams.push(status); dataParams.push(status); } } if (search) { whereClause += whereClause ? ' AND ' : ' WHERE '; whereClause += '(phoneNumber LIKE ? OR userId LIKE ?)'; countParams.push(`%${search}%`); countParams.push(`%${search}%`); dataParams.push(`%${search}%`); dataParams.push(`%${search}%`); } if (startDate) { whereClause += whereClause ? ' AND ' : ' WHERE '; whereClause += 'created_at >= ?'; countParams.push(startDate); dataParams.push(startDate); } if (endDate) { whereClause += whereClause ? ' AND ' : ' WHERE '; whereClause += 'created_at <= ?'; countParams.push(`${endDate} 23:59:59`); dataParams.push(`${endDate} 23:59:59`); } // 添加分页参数 dataParams.push(pageSize); dataParams.push(offset); // 查询总数 const countSql = `SELECT COUNT(*) as total FROM users${whereClause}`; // 查询待审核总数 const pendingCountSql = "SELECT COUNT(*) as pendingTotal FROM users WHERE idcardstatus = 0 OR idcardstatus IS NULL"; // 查询数据 const dataSql = `SELECT id, userId, phoneNumber as phone, idcard1, idcard2, businesslicenseurl, idcardstatus, reason, created_at, newtime FROM users${whereClause} ORDER BY created_at DESC LIMIT ? OFFSET ?`; // 先查询总数 wechatPool.query(countSql, countParams, (countErr, countResults) => { if (countErr) { console.error('查询身份信息总数失败:', countErr); return res.json({ success: false, message: '查询身份信息失败,请稍后重试' }); } const total = countResults[0].total; // 查询待审核总数 wechatPool.query(pendingCountSql, [], (pendingErr, pendingResults) => { if (pendingErr) { console.error('查询待审核身份信息总数失败:', pendingErr); return res.json({ success: false, message: '查询身份信息失败,请稍后重试' }); } const pendingTotal = pendingResults[0].pendingTotal; // 再查询数据 wechatPool.query(dataSql, dataParams, (dataErr, dataResults) => { if (dataErr) { console.error('查询身份信息数据失败:', dataErr); return res.json({ success: false, message: '查询身份信息失败,请稍后重试' }); } res.json({ success: true, identities: dataResults, total, pendingTotal }); }); }); }); } catch (error) { console.error('获取身份信息列表失败:', error); res.json({ success: false, message: '获取身份信息列表失败' }); } }); // 更新身份信息审核状态 app.post('/api/identities/update', (req, res) => { try { const { id, idcardstatus, reason } = req.body; if (!id || idcardstatus === undefined) { return res.json({ success: false, message: '参数不全' }); } let sql, params; if (reason !== undefined) { sql = 'UPDATE users SET idcardstatus = ?, reason = ? WHERE id = ?'; params = [idcardstatus, reason, id]; } else { sql = 'UPDATE users SET idcardstatus = ? WHERE id = ?'; params = [idcardstatus, id]; } wechatPool.query(sql, params, (err, results) => { if (err) { console.error('更新身份信息审核状态失败:', err); return res.json({ success: false, message: '更新身份信息审核状态失败,请稍后重试' }); } if (results.affectedRows === 0) { return res.json({ success: false, message: '未找到匹配的身份信息' }); } res.json({ success: true, message: '更新身份信息审核状态成功' }); }); } catch (error) { console.error('更新身份信息审核状态失败:', error); res.json({ success: false, message: '更新身份信息审核状态失败' }); } }); // 批量更新身份信息审核状态 app.post('/api/identities/batch-update', (req, res) => { try { const { ids, idcardstatus, reason } = req.body; if (!ids || !Array.isArray(ids) || ids.length === 0 || idcardstatus === undefined) { return res.json({ success: false, message: '参数不全' }); } let sql, params; if (reason !== undefined) { sql = 'UPDATE users SET idcardstatus = ?, reason = ? WHERE id IN (?)'; params = [idcardstatus, reason, ids]; } else { sql = 'UPDATE users SET idcardstatus = ? WHERE id IN (?)'; params = [idcardstatus, ids]; } wechatPool.query(sql, params, (err, results) => { if (err) { console.error('批量更新身份信息审核状态失败:', err); return res.json({ success: false, message: '批量更新身份信息审核状态失败,请稍后重试' }); } if (results.affectedRows === 0) { return res.json({ success: false, message: '未找到匹配的身份信息' }); } res.json({ success: true, message: '批量更新身份信息审核状态成功', affectedRows: results.affectedRows }); }); } catch (error) { console.error('批量更新身份信息审核状态失败:', error); res.json({ success: false, message: '批量更新身份信息审核状态失败' }); } }); // 批量请求端点 app.post('/api/batch', async (req, res) => { try { const { requests } = req.body; if (!Array.isArray(requests)) { return res.json({ success: false, message: '请求格式错误' }); } // 并行处理所有请求 const responses = await Promise.all( requests.map(async (request) => { try { const { id, endpoint, params } = request; // 构建请求URL let url = endpoint; if (params && Object.keys(params).length > 0) { const queryString = new URLSearchParams(params).toString(); url += '?' + queryString; } // 模拟请求处理(实际项目中可能需要更复杂的路由处理) // 这里使用简单的映射来处理常见的API请求 let responseData; if (endpoint === '/api/consolidated/dashboard') { // 处理控制台数据请求 const today = new Date(); today.setHours(0, 0, 0, 0); const todayStr = today.toISOString().slice(0, 19).replace('T', ' '); const consolidatedSql = ` WITH dashboard_stats AS ( -- 产品统计 SELECT 'products_total' as metric_type, COUNT(*) as metric_value FROM products UNION ALL SELECT 'products_pending', COUNT(*) FROM products WHERE status = 'pending_review' UNION ALL SELECT 'products_today', COUNT(*) FROM products WHERE created_at >= ? -- 活跃用户统计 UNION ALL SELECT 'total_active_1d', COUNT(DISTINCT ut.userId) FROM usertraces ut JOIN users u ON ut.userId = u.userId WHERE ut.operationTime >= DATE_SUB(NOW(), INTERVAL 1 DAY) AND u.phoneNumber IS NOT NULL AND u.phoneNumber != '' AND u.created_at IS NOT NULL UNION ALL SELECT 'total_active_7d', COUNT(DISTINCT ut.userId) FROM usertraces ut JOIN users u ON ut.userId = u.userId WHERE ut.operationTime >= DATE_SUB(NOW(), INTERVAL 7 DAY) AND u.phoneNumber IS NOT NULL AND u.phoneNumber != '' AND u.created_at IS NOT NULL ) SELECT * FROM dashboard_stats `; responseData = await new Promise((resolve, reject) => { wechatPool.query(consolidatedSql, [todayStr], (err, results) => { if (err) reject(err); else { const statsMap = {}; results.forEach(row => { statsMap[row.metric_type] = row.metric_value; }); resolve({ success: true, data: { total: statsMap.products_total || 0, pending: statsMap.products_pending || 0, today: statsMap.products_today || 0, active1d: statsMap.total_active_1d || 0, active7d: statsMap.total_active_7d || 0 } }); } }); }); } else if (endpoint === '/api/consolidated/statistics') { // 处理统计数据请求 const { startDate, endDate } = params || {}; const now = new Date(); const defaultEndStr = now.toISOString().slice(0, 19).replace('T', ' '); const defaultStartStr = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000).toISOString().slice(0, 19).replace('T', ' '); const startDateStr = startDate ? `${startDate} 00:00:00` : defaultStartStr; const endDateStr = endDate ? `${endDate} 23:59:59` : defaultEndStr; const consolidatedSql = ` WITH client_business_stats AS ( -- 客户统计 SELECT 'client_total' as stat_type, COUNT(*) as stat_value, NULL as sub_type, NULL as sub_value FROM users WHERE created_at BETWEEN ? AND ? AND type != 'Colleague' UNION ALL -- 客户分配统计 SELECT 'client_allocation' as stat_type, COUNT(*) as stat_value, CASE WHEN um.userName IS NULL OR um.userName = '' THEN '公海池' ELSE um.userName END as sub_type, NULL as sub_value FROM users u LEFT JOIN usermanagements um ON um.userId = u.userId WHERE u.created_at BETWEEN ? AND ? AND u.type != 'Colleague' GROUP BY sub_type ) SELECT * FROM client_business_stats `; responseData = await new Promise((resolve, reject) => { wechatPool.query(consolidatedSql, [startDateStr, endDateStr, startDateStr, endDateStr], (err, results) => { if (err) reject(err); else { let clientCount = 0; const clientAllocation = []; results.forEach(row => { if (row.stat_type === 'client_total') { clientCount = row.stat_value; } else if (row.stat_type === 'client_allocation') { clientAllocation.push({ managerName: row.sub_type, clientCount: row.stat_value }); } }); clientAllocation.sort((a, b) => b.clientCount - a.clientCount); resolve({ success: true, data: { clientCount: clientCount, clientAllocation: clientAllocation } }); } }); }); } else { // 其他API请求处理 responseData = { success: false, message: 'Unsupported endpoint' }; } return { id, response: responseData }; } catch (error) { console.error(`处理批量请求 ${request.id} 失败:`, error); return { id, response: { success: false, message: '请求失败' } }; } }) ); res.json({ success: true, responses }); } catch (error) { console.error('处理批量请求失败:', error); res.json({ success: false, message: '处理批量请求失败' }); } }); // 获取身份信息审核列表 app.get('/api/identity-verification', (req, res) => { try { const { status = 'pending', page = 1, pageSize = 10 } = req.query; const offset = (page - 1) * pageSize; // 构建查询条件 let whereClause = ''; if (status === 'pending') { whereClause = 'WHERE status = 0'; } else if (status === 'approved') { whereClause = 'WHERE status = 1'; } else if (status === 'rejected') { whereClause = 'WHERE status = 2'; } // 查询总数 const countSql = `SELECT COUNT(*) as total FROM identity_verification ${whereClause}`; // 查询数据 const dataSql = ` SELECT * FROM identity_verification ${whereClause} ORDER BY created_at DESC LIMIT ? OFFSET ? `; // 执行查询 wechatPool.query(countSql, (countErr, countResults) => { if (countErr) { console.error('查询身份信息审核总数失败:', countErr); return res.json({ success: false, message: '获取身份信息审核列表失败,请稍后重试' }); } const total = countResults[0].total; wechatPool.query(dataSql, [pageSize, offset], (dataErr, dataResults) => { if (dataErr) { console.error('查询身份信息审核数据失败:', dataErr); return res.json({ success: false, message: '获取身份信息审核列表失败,请稍后重试' }); } res.json({ success: true, verifications: dataResults, total }); }); }); } catch (error) { console.error('获取身份信息审核列表失败:', error); res.json({ success: false, message: '获取身份信息审核列表失败,请稍后重试' }); } }); // 设置wechatPool到app对象,以便路由使用 app.set('wechatPool', wechatPool); // 获取功能使用统计 API app.get('/api/function-usage', (req, res) => { try { const { startDate, endDate } = req.query; // 构建时间范围查询条件 let timeCondition = ''; let params = []; if (startDate && endDate) { timeCondition = 'WHERE time BETWEEN ? AND ?'; params = [`${startDate} 00:00:00`, `${endDate} 23:59:59`]; } // 查询总功能数(去重后的操作类型数量) const totalFunctionsSql = ` SELECT COUNT(DISTINCT operation) as totalFunctions FROM use_history ${timeCondition} `; // 查询总调用次数 const totalCallsSql = ` SELECT COUNT(*) as totalCalls FROM use_history ${timeCondition} `; // 查询各操作的统计信息 const operationStatsSql = ` SELECT operation as operationName, COUNT(*) as callCount, COUNT(DISTINCT phone) as userCount, MAX(time) as lastUsed FROM use_history ${timeCondition} GROUP BY operation ORDER BY callCount DESC `; // 执行查询 wechatPool.query(totalFunctionsSql, params, (err1, totalResults) => { if (err1) { console.error('查询功能总数失败:', err1); return res.json({ success: false, message: '获取功能使用数据失败' }); } wechatPool.query(totalCallsSql, params, (err2, callResults) => { if (err2) { console.error('查询总调用次数失败:', err2); return res.json({ success: false, message: '获取功能使用数据失败' }); } wechatPool.query(operationStatsSql, params, (err3, statsResults) => { if (err3) { console.error('查询操作统计失败:', err3); return res.json({ success: false, message: '获取功能使用数据失败' }); } const totalFunctions = totalResults[0].totalFunctions || 0; const totalCalls = callResults[0].totalCalls || 0; const mostUsedFunction = statsResults.length > 0 ? statsResults[0].operationName : '无'; // 计算平均使用频率(按天计算) const daysDiff = startDate && endDate ? Math.ceil((new Date(endDate) - new Date(startDate)) / (1000 * 60 * 60 * 24)) + 1 : 1; const avgUsage = totalCalls > 0 ? Math.round(totalCalls / daysDiff) : 0; res.json({ success: true, data: { totalFunctions: totalFunctions, mostUsedFunction: mostUsedFunction, totalCalls: totalCalls, avgUsage: avgUsage, operations: statsResults } }); }); }); }); } catch (error) { console.error('获取功能使用统计失败:', error); res.json({ success: false, message: '获取功能使用数据失败' }); } }); // 获取功能使用详情 API app.get('/api/function-usage/:operation', (req, res) => { try { const { operation } = req.params; const { startDate, endDate, page = 1, pageSize = 20 } = req.query; // 构建时间范围查询条件 let timeCondition = 'WHERE operation = ?'; let params = [operation]; if (startDate && endDate) { timeCondition += ' AND time BETWEEN ? AND ?'; params.push(`${startDate} 00:00:00`, `${endDate} 23:59:59`); } // 计算分页偏移量 const offset = (parseInt(page) - 1) * parseInt(pageSize); // 查询总记录数 const countSql = ` SELECT COUNT(*) as total FROM use_history ${timeCondition} `; // 查询详细记录 const dataSql = ` SELECT id, phone, operation, time FROM use_history ${timeCondition} ORDER BY time DESC LIMIT ? OFFSET ? `; // 执行查询 wechatPool.query(countSql, params, (countErr, countResults) => { if (countErr) { console.error('查询功能使用详情总数失败:', countErr); return res.json({ success: false, message: '获取功能使用详情失败' }); } const total = countResults[0].total; // 添加分页参数 const dataParams = [...params, parseInt(pageSize), offset]; wechatPool.query(dataSql, dataParams, (dataErr, dataResults) => { if (dataErr) { console.error('查询功能使用详情数据失败:', dataErr); return res.json({ success: false, message: '获取功能使用详情失败' }); } res.json({ success: true, data: { records: dataResults, total: total, page: parseInt(page), pageSize: parseInt(pageSize) } }); }); }); } catch (error) { console.error('获取功能使用详情失败:', error); res.json({ success: false, message: '获取功能使用详情失败' }); } }); // 获取用户历史记录 API app.get('/api/user-history/:phone', (req, res) => { try { const { phone } = req.params; const { startDate, endDate, page = 1, pageSize = 20 } = req.query; // 构建时间范围查询条件 let timeCondition = 'WHERE phone = ?'; let params = [phone]; if (startDate && endDate) { timeCondition += ' AND time BETWEEN ? AND ?'; params.push(`${startDate} 00:00:00`, `${endDate} 23:59:59`); } // 计算分页偏移量 const offset = (parseInt(page) - 1) * parseInt(pageSize); // 查询总记录数 const countSql = ` SELECT COUNT(*) as total FROM use_history ${timeCondition} `; // 查询详细记录 const dataSql = ` SELECT id, operation, time FROM use_history ${timeCondition} ORDER BY time DESC LIMIT ? OFFSET ? `; // 执行查询 wechatPool.query(countSql, params, (countErr, countResults) => { if (countErr) { console.error('查询用户历史记录总数失败:', countErr); return res.json({ success: false, message: '获取用户历史记录失败' }); } const total = countResults[0].total; // 添加分页参数 const dataParams = [...params, parseInt(pageSize), offset]; wechatPool.query(dataSql, dataParams, (dataErr, dataResults) => { if (dataErr) { console.error('查询用户历史记录数据失败:', dataErr); return res.json({ success: false, message: '获取用户历史记录失败' }); } res.json({ success: true, data: { records: dataResults, total: total, page: parseInt(page), pageSize: parseInt(pageSize) } }); }); }); } catch (error) { console.error('获取用户历史记录失败:', error); res.json({ success: false, message: '获取用户历史记录失败' }); } }); // 获取所有使用记录 API app.get('/api/usage-records', (req, res) => { try { const { operation, phone, page = 1, pageSize = 20 } = req.query; // 构建查询条件 let conditions = []; let params = []; if (operation) { conditions.push('operation = ?'); params.push(operation); } if (phone) { conditions.push('phone LIKE ?'); params.push(phone + '%'); } let whereClause = conditions.length > 0 ? 'WHERE ' + conditions.join(' AND ') : ''; // 计算分页偏移量 const offset = (parseInt(page) - 1) * parseInt(pageSize); // 查询总记录数 const countSql = ` SELECT COUNT(*) as total FROM use_history ${whereClause} `; // 查询详细记录 const dataSql = ` SELECT id, phone, operation, time FROM use_history ${whereClause} ORDER BY time DESC LIMIT ? OFFSET ? `; // 执行查询 wechatPool.query(countSql, params, (countErr, countResults) => { if (countErr) { console.error('查询使用记录总数失败:', countErr); return res.json({ success: false, message: '获取使用记录失败' }); } const total = countResults[0].total; // 添加分页参数 const dataParams = [...params, parseInt(pageSize), offset]; wechatPool.query(dataSql, dataParams, (dataErr, dataResults) => { if (dataErr) { console.error('查询使用记录数据失败:', dataErr); return res.json({ success: false, message: '获取使用记录失败' }); } res.json({ success: true, data: { records: dataResults, total: total, page: parseInt(page), pageSize: parseInt(pageSize) } }); }); }); } catch (error) { console.error('获取使用记录失败:', error); res.json({ success: false, message: '获取使用记录失败' }); } }); // 导入并使用优化的活跃客户排名路由 const optimizedActiveDuration = require('./optimized-active-duration'); app.use(optimizedActiveDuration); // 启动服务器 app.listen(port, () => { console.log(`服务器运行在 http://localhost:${port}`); });