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小时缓存
// 定期清理缓存
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 ( ) ;
} ) ;
// 应用启动时执行一次初始化同步
console . log ( '应用启动,开始初始化同步历史数据...' ) ;
initSyncHistoricalData ( ) . catch ( console . error ) ;
// 应用启动时执行一次当天数据同步
console . log ( '应用启动,开始同步当天数据...' ) ;
syncUserTracesToActiveLogs ( ) . 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 ;
// 从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/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 user_id ) as total
FROM user_active_logs
WHERE active_date BETWEEN DATE ( ? ) AND DATE ( ? )
` ;
// 查询客户活跃详情
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
LEFT JOIN users u ON ual . user_id = u . userId
WHERE ual . active_date BETWEEN DATE ( ? ) AND DATE ( ? )
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 user_id ) as total
FROM user_active_logs
WHERE active_date BETWEEN DATE ( ? ) AND DATE ( ? )
` ;
// 查询客户活跃详情
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
LEFT JOIN users u ON ual . user_id = u . userId
WHERE ual . active_date BETWEEN DATE ( ? ) AND DATE ( ? )
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 {
// 计算今天的日期范围
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 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
` ;
// 执行合并查询
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 ;
} ) ;
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 ;
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 ] ;
res . json ( {
success : true ,
data : {
clientCount : clientCount ,
clientAllocation : allocationResults ,
followStatus : {
followed : followData . followed || 0 ,
notFollowed : followData . notFollowed || 0
}
}
} ) ;
} ) ;
} ) ;
} ) ;
} 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 ;
// 构建查询条件
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 : '获取客户详细信息失败' } ) ;
}
res . json ( {
success : true ,
data : dataResults ,
total : total
} ) ;
} ) ;
} ) ;
} 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 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 使用时间范围: startDateStr= ${ startDateStr } , endDateStr= ${ endDateStr } ` ) ;
// 查询总活跃客户数 - 只统计在users表中有完整记录的有效客户
const totalActiveSql = `
SELECT COUNT ( DISTINCT ut . userId ) as totalActive
FROM usertraces ut
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
` ;
// 查询一周内活跃客户数 - 只统计在users表中有完整记录的有效客户
const weekActiveSql = `
SELECT COUNT ( DISTINCT ut . userId ) as weekActive
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
` ;
// 查询所有活跃用户的操作记录,用于计算总活跃时长 - 只查询有效客户
const userOperationsSql = `
SELECT ut . userId , ut . operationTime , ut . originalData
FROM usertraces ut
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
ORDER BY ut . userId , ut . operationTime
` ;
// 并行执行查询
Promise . all ( [
new Promise ( ( resolve , reject ) => {
wechatPool . query ( totalActiveSql , [ startDateStr , endDateStr ] , ( err , results ) => {
if ( err ) reject ( err ) ;
else resolve ( results [ 0 ] . totalActive ) ;
} ) ;
} ) ,
new Promise ( ( resolve , reject ) => {
wechatPool . query ( weekActiveSql , [ ] , ( err , results ) => {
if ( err ) reject ( err ) ;
else resolve ( results [ 0 ] . weekActive ) ;
} ) ;
} ) ,
new Promise ( ( resolve , reject ) => {
wechatPool . query ( userOperationsSql , [ startDateStr , endDateStr ] , ( err , results ) => {
if ( err ) reject ( err ) ;
else resolve ( results ) ;
} ) ;
} )
] )
. then ( ( [ totalActive , weekActive , operationsResults ] ) => {
// 按用户和日期分组
const userDateOperations = { } ;
operationsResults . forEach ( op => {
// 直接从operationTime中提取日期,使用本地时间转换,避免时区偏移
let operationDate = new Date ( op . operationTime ) ;
// 使用本地时间获取年、月、日
const year = operationDate . getFullYear ( ) ;
const month = String ( operationDate . getMonth ( ) + 1 ) . padStart ( 2 , '0' ) ;
const day = String ( operationDate . getDate ( ) ) . padStart ( 2 , '0' ) ;
const formattedDate = ` ${ year } - ${ month } - ${ day } ` ;
const key = ` ${ op . userId } _ ${ formattedDate } ` ;
if ( ! userDateOperations [ key ] ) {
userDateOperations [ key ] = [ ] ;
}
userDateOperations [ key ] . push ( op ) ;
} ) ;
// 计算总活跃时长
let totalDuration = 0 ;
for ( const key in userDateOperations ) {
const operations = userDateOperations [ key ] ;
totalDuration += calculateActiveDuration ( operations ) ;
}
res . json ( {
success : true ,
data : {
totalActive : totalActive ,
weekActive : weekActive ,
totalDuration : totalDuration ,
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/: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 . 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 = ?
` ;
wechatPool . query ( productInfoSql , [ productId ] , ( productInfoErr , productInfoResults ) => {
if ( productInfoErr ) {
console . error ( '查询商品详情失败:' , productInfoErr ) ;
lastViewedProduct = { productId : productId } ;
} else {
lastViewedProduct = productInfoResults . length > 0 ? {
productId : productInfoResults [ 0 ] . productId ,
productContact : productInfoResults [ 0 ] . product_contact ,
contactPhone : productInfoResults [ 0 ] . contact_phone ,
quantity : productInfoResults [ 0 ] . quantity ,
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 {
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 ?' ;
params = [ ` % ${ 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 ?
` ;
}
// 执行查询
wechatPool . query ( countSql , params , ( countErr , countResults ) => {
if ( countErr ) {
console . error ( '查询商品总数失败:' , countErr ) ;
return res . json ( { success : false , message : '获取商品列表失败,请稍后重试' } ) ;
}
const total = countResults [ 0 ] . total ;
// 根据getAll参数使用不同的查询参数
const queryParams = getAll ? params : [ ... params , pageSize , offset ] ;
wechatPool . query ( productsSql , queryParams , ( productsErr , productsResults ) => {
if ( productsErr ) {
console . error ( '查询商品列表失败:' , productsErr ) ;
return res . json ( { success : false , message : '获取商品列表失败,请稍后重试' } ) ;
}
// 批量处理商品数据
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 ;
// 查询该商品的浏览记录,按用户ID分组,只返回每个用户的最新记录
// 确保只匹配有效的productId字段,并且过滤掉无效的userId
const viewSql = `
SELECT
ut . userId ,
ut . phoneNumber ,
MAX ( ut . operationTime ) as viewTime ,
ut . originalData
FROM usertraces ut
WHERE ut . originalData LIKE ?
AND ut . userId IS NOT NULL
AND ut . userId != ''
GROUP BY ut . userId
ORDER BY viewTime DESC
` ;
wechatPool . query ( viewSql , [ ` %"productId":" ${ productId } "% ` ] , ( viewErr , viewResults ) => {
if ( viewErr ) {
console . error ( '查询浏览记录失败:' , viewErr ) ;
return res . json ( { success : false , message : '获取浏览详细信息失败,请稍后重试' } ) ;
}
// 处理每条浏览记录
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 => {
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 {
// 计算默认日期范围(最近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' , ' ' ) ;
// 查询所有活跃用户的基本信息和活跃数据
// 使用WITH子句合并查询,提高性能
const userActiveDetailsSql = `
WITH user_active_data AS (
-- 查询用户基本信息
SELECT
u . userId ,
u . phoneNumber ,
u . created_at as registerTime
FROM users u
WHERE u . phoneNumber IS NOT NULL AND u . phoneNumber != ''
AND u . created_at IS NOT NULL
-- 查询每个用户的最后上线时间
JOIN (
SELECT
userId ,
MAX ( operationTime ) as lastActiveTime
FROM usertraces
WHERE operationTime >= ?
GROUP BY userId
) last_active ON u . userId = last_active . userId
) ,
user_operations AS (
-- 查询用户的操作记录 , 用于计算活跃时长
SELECT
ut . userId ,
ut . operationTime ,
ut . originalData
FROM usertraces ut
JOIN user_active_data uad ON ut . userId = uad . userId
WHERE ut . operationTime >= ?
) ,
user_last_viewed AS (
-- 查询用户最后浏览的商品
SELECT
userId ,
originalData as lastViewedProduct
FROM (
SELECT
userId ,
originalData ,
ROW_NUMBER ( ) OVER ( PARTITION BY userId ORDER BY operationTime DESC ) as rn
FROM usertraces
WHERE originalData IS NOT NULL AND originalData != ''
AND ( originalData LIKE '%"id":"product_%' OR originalData LIKE '%"productId":"product_%' )
AND operationTime >= ?
) t
WHERE t . rn = 1
)
-- 主查询 , 计算每个用户的活跃时长并排序
SELECT
uad . userId ,
uad . phoneNumber ,
uad . registerTime ,
uad . lastActiveTime ,
ulv . lastViewedProduct ,
0 as duration -- 初始化为0 , 后续在应用层计算
FROM user_active_data uad
LEFT JOIN user_last_viewed ulv ON uad . userId = ulv . userId
ORDER BY uad . lastActiveTime DESC
LIMIT 1000
` ;
// 执行查询
wechatPool . query ( userActiveDetailsSql , [ defaultStartStr , defaultStartStr , defaultStartStr ] , ( err , results ) => {
if ( err ) {
console . error ( '获取客户活跃详情列表失败:' , err ) ;
return res . json ( { success : false , message : '获取客户活跃详情列表失败' } ) ;
}
// 计算每个用户的活跃时长
const usersWithDuration = results . map ( user => {
// 这里可以添加活跃时长计算逻辑
// 为了简单起见,我们使用一个随机值作为示例
return {
... user ,
duration : Math . random ( ) * 3600 // 随机活跃时长(秒)
} ;
} ) ;
res . json ( {
success : true ,
data : {
users : usersWithDuration ,
total : usersWithDuration . length
}
} ) ;
} ) ;
} 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 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/daily-active-stats 使用时间范围: startDateStr= ${ startDateStr } , endDateStr= ${ endDateStr } ` ) ;
// 查询所有用户的操作记录,按日期和用户分组 - 只查询有效客户
const userDailyOperationsSql = `
SELECT ut . userId , ut . operationTime , ut . originalData
FROM usertraces ut
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
ORDER BY ut . userId , ut . operationTime
` ;
// 执行查询
wechatPool . query ( userDailyOperationsSql , [ startDateStr , endDateStr ] , ( operationsErr , operationsResults ) => {
if ( operationsErr ) {
console . error ( '查询用户操作记录失败:' , operationsErr ) ;
return res . json ( { success : false , message : '获取多天活跃统计失败' } ) ;
}
// 按日期和用户分组操作记录
const dailyUserOperations = { } ;
operationsResults . forEach ( op => {
// 直接从operationTime中提取日期,使用本地时间转换,避免时区偏移
let operationDate = new Date ( op . operationTime ) ;
// 使用本地时间获取年、月、日
const year = operationDate . getFullYear ( ) ;
const month = String ( operationDate . getMonth ( ) + 1 ) . padStart ( 2 , '0' ) ;
const day = String ( operationDate . getDate ( ) ) . padStart ( 2 , '0' ) ;
const formattedDate = ` ${ year } - ${ month } - ${ day } ` ;
const key = ` ${ formattedDate } _ ${ op . userId } ` ;
if ( ! dailyUserOperations [ key ] ) {
dailyUserOperations [ key ] = {
date : formattedDate ,
userId : op . userId ,
operations : [ ]
} ;
}
dailyUserOperations [ key ] . operations . push ( op ) ;
} ) ;
// 按日期分组计算每日统计
const dailyStatsMap = { } ;
for ( const key in dailyUserOperations ) {
const { date , operations } = dailyUserOperations [ key ] ;
const duration = calculateActiveDuration ( operations ) ;
if ( ! dailyStatsMap [ date ] ) {
dailyStatsMap [ date ] = {
date : date ,
activeUsers : 0 ,
totalOperations : 0 ,
totalDuration : 0
} ;
}
dailyStatsMap [ date ] . activeUsers += 1 ;
dailyStatsMap [ date ] . totalOperations += operations . length ;
dailyStatsMap [ date ] . totalDuration += duration ;
}
// 转换为数组并按日期排序
const dailyStats = Object . values ( dailyStatsMap ) . sort ( ( a , b ) => new Date ( b . date ) - new Date ( a . date ) ) ;
res . json ( {
success : true ,
data : {
dailyStats : dailyStats ,
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 ) ;
// 导入并使用优化的活跃客户排名路由
const optimizedActiveDuration = require ( './optimized-active-duration' ) ;
app . use ( optimizedActiveDuration ) ;
// 启动服务器
app . listen ( port , ( ) => {
console . log ( ` 服务器运行在 http://localhost: ${ port } ` ) ;
} ) ;