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

5972 lines
207 KiB

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}`);
});