const express = require('express'); const fs = require('fs'); const authMiddleware = require('../middleware/auth'); const { getChannelList, exportChannelListCsv, createChannelExportJob, getChannelExportJob, getChannelExportJobFile, getChannelDashboard, getChannelIdAnalysisList, getChannelIdAnalysisDashboard, getChannelSpuList, getChannelStockMonitorDashboard, getChannelDetail, getChannelLargeCustomerList, getChannelLargeCustomerDashboard, getChannelLargeCustomerDetail, refreshChannelLargeCustomerReadStore, } = require('../services/channelAnalysis'); const router = express.Router(); function parseRangeHeader(rangeHeader, totalSize) { if (!rangeHeader) return null; const match = String(rangeHeader).match(/^bytes=(\d*)-(\d*)$/); if (!match) return { invalid: true }; let start = match[1] === '' ? null : Number.parseInt(match[1], 10); let end = match[2] === '' ? null : Number.parseInt(match[2], 10); if (start === null && end === null) return { invalid: true }; if (start === null) { const suffixLength = end; if (!Number.isFinite(suffixLength) || suffixLength <= 0) return { invalid: true }; start = Math.max(totalSize - suffixLength, 0); end = totalSize - 1; } else { if (!Number.isFinite(start) || start < 0) return { invalid: true }; end = end === null ? totalSize - 1 : end; } if (!Number.isFinite(end) || end < start || start >= totalSize) { return { invalid: true }; } return { start, end: Math.min(end, totalSize - 1), }; } function setCsvDownloadHeaders(res, { filename, totalSize, contentLength, range }) { const encodedFilename = encodeURIComponent(filename || `channel-data-${Date.now()}.csv`); res.setHeader('Content-Type', 'text/csv; charset=utf-8'); res.setHeader('Content-Disposition', `attachment; filename*=UTF-8''${encodedFilename}`); res.setHeader('Accept-Ranges', 'bytes'); res.setHeader('Content-Length', String(contentLength)); res.setHeader('Cache-Control', 'private, no-store'); if (range) { res.setHeader('Content-Range', `bytes ${range.start}-${range.end}/${totalSize}`); } } router.get('/list', authMiddleware, async (req, res) => { try { const data = await getChannelList({ startDate: req.query.startDate, endDate: req.query.endDate, page: req.query.page, pageSize: req.query.pageSize, keyword: req.query.keyword, sortField: req.query.sortField, sortOrder: req.query.sortOrder, channels: req.query.channels, statuses: req.query.statuses, purchasers: req.query.purchasers, minAfterSaleAmountRate: req.query.minAfterSaleAmountRate, maxAfterSaleAmountRate: req.query.maxAfterSaleAmountRate, }); res.json({ success: true, data }); } catch (error) { console.error('获取渠道数据失败:', error); res.status(error.statusCode || 500).json({ success: false, error: error.message || '获取渠道数据失败', }); } }); router.get('/export', authMiddleware, async (req, res) => { try { const data = await exportChannelListCsv({ startDate: req.query.startDate, endDate: req.query.endDate, keyword: req.query.keyword, sortField: req.query.sortField, sortOrder: req.query.sortOrder, channels: req.query.channels, statuses: req.query.statuses, purchasers: req.query.purchasers, minAfterSaleAmountRate: req.query.minAfterSaleAmountRate, maxAfterSaleAmountRate: req.query.maxAfterSaleAmountRate, }); const encodedFilename = encodeURIComponent(data.filename || `channel-data-${Date.now()}.csv`); res.setHeader('Content-Type', 'text/csv; charset=utf-8'); res.setHeader('Content-Disposition', `attachment; filename*=UTF-8''${encodedFilename}`); res.setHeader('X-Total-Count', String(data.total || 0)); res.setHeader('X-Export-Mode', data.exportMode || 'exact-enriched'); res.send(data.csv); } catch (error) { console.error('导出渠道数据失败:', error); res.status(error.statusCode || 500).json({ success: false, error: error.message || '导出渠道数据失败', }); } }); router.post('/export-jobs', authMiddleware, async (req, res) => { try { const data = createChannelExportJob({ startDate: req.body.startDate, endDate: req.body.endDate, keyword: req.body.keyword, sortField: req.body.sortField, sortOrder: req.body.sortOrder, channels: req.body.channels, statuses: req.body.statuses, purchasers: req.body.purchasers, minAfterSaleAmountRate: req.body.minAfterSaleAmountRate, maxAfterSaleAmountRate: req.body.maxAfterSaleAmountRate, }, req.user); res.json({ success: true, data }); } catch (error) { console.error('创建渠道数据导出任务失败:', error); res.status(error.statusCode || 500).json({ success: false, error: error.message || '创建渠道数据导出任务失败', }); } }); router.get('/export-jobs/:jobId', authMiddleware, async (req, res) => { try { const data = await getChannelExportJob(req.params.jobId); res.json({ success: true, data }); } catch (error) { res.status(error.statusCode || 500).json({ success: false, error: error.message || '获取渠道数据导出任务失败', }); } }); router.get('/export-jobs/:jobId/download', authMiddleware, async (req, res) => { try { const { filePath, filename } = await getChannelExportJobFile(req.params.jobId); const stat = await fs.promises.stat(filePath); const totalSize = stat.size; const range = parseRangeHeader(req.headers.range, totalSize); if (range?.invalid) { res.status(416).setHeader('Content-Range', `bytes */${totalSize}`); res.end(); return; } if (range) { const contentLength = range.end - range.start + 1; setCsvDownloadHeaders(res, { filename, totalSize, contentLength, range }); res.status(206); fs.createReadStream(filePath, { start: range.start, end: range.end }).pipe(res); return; } setCsvDownloadHeaders(res, { filename, totalSize, contentLength: totalSize }); fs.createReadStream(filePath).pipe(res); } catch (error) { console.error('下载渠道数据导出文件失败:', error); res.status(error.statusCode || 500).json({ success: false, error: error.message || '下载渠道数据导出文件失败', }); } }); router.get('/dashboard', authMiddleware, async (req, res) => { try { const data = await getChannelDashboard({ startDate: req.query.startDate, endDate: req.query.endDate, keyword: req.query.keyword, limit: req.query.limit, }); res.json({ success: true, data }); } catch (error) { console.error('获取渠道多维分析仪表盘失败:', error); res.status(error.statusCode || 500).json({ success: false, error: error.message || '获取渠道多维分析仪表盘失败', }); } }); router.get('/spu-list', authMiddleware, async (req, res) => { try { const data = await getChannelSpuList({ startDate: req.query.startDate, endDate: req.query.endDate, page: req.query.page, pageSize: req.query.pageSize, keyword: req.query.keyword, sortField: req.query.sortField, sortOrder: req.query.sortOrder, statuses: req.query.statuses, purchasers: req.query.purchasers, minAfterSaleAmountRate: req.query.minAfterSaleAmountRate, maxAfterSaleAmountRate: req.query.maxAfterSaleAmountRate, }); res.json({ success: true, data }); } catch (error) { console.error('获取渠道SPU图片数据失败:', error); res.status(error.statusCode || 500).json({ success: false, error: error.message || '获取渠道SPU图片数据失败', }); } }); router.get('/stock-monitor-dashboard', authMiddleware, async (req, res) => { try { const data = await getChannelStockMonitorDashboard({ startDate: req.query.startDate, endDate: req.query.endDate, keyword: req.query.keyword, limit: req.query.limit, }); res.json({ success: true, data }); } catch (error) { console.error('获取渠道库存影响仪表盘失败:', error); res.status(error.statusCode || 500).json({ success: false, error: error.message || '获取渠道库存影响仪表盘失败', }); } }); router.get('/id-analysis-dashboard', authMiddleware, async (req, res) => { try { const data = await getChannelIdAnalysisDashboard({ startDate: req.query.startDate, endDate: req.query.endDate, keyword: req.query.keyword, limit: req.query.limit, }); res.json({ success: true, data }); } catch (error) { console.error('获取渠道ID多维分析仪表盘失败:', error); res.status(error.statusCode || 500).json({ success: false, error: error.message || '获取渠道ID多维分析仪表盘失败', }); } }); router.get('/id-analysis', authMiddleware, async (req, res) => { try { const data = await getChannelIdAnalysisList({ startDate: req.query.startDate, endDate: req.query.endDate, page: req.query.page, pageSize: req.query.pageSize, keyword: req.query.keyword, sortField: req.query.sortField, sortOrder: req.query.sortOrder, }); res.json({ success: true, data }); } catch (error) { console.error('获取渠道ID分析失败:', error); res.status(error.statusCode || 500).json({ success: false, error: error.message || '获取渠道ID分析失败', }); } }); router.get('/detail', authMiddleware, async (req, res) => { try { const data = await getChannelDetail({ startDate: req.query.startDate, endDate: req.query.endDate, channelName: req.query.channelName, shopName: req.query.shopName, skuCode: req.query.skuCode, }); res.json({ success: true, data }); } catch (error) { console.error('获取渠道明细失败:', error); res.status(error.statusCode || 500).json({ success: false, error: error.message || '获取渠道明细失败', }); } }); router.get('/large-customers', authMiddleware, async (req, res) => { try { const data = await getChannelLargeCustomerList({ startMonth: req.query.startMonth, endMonth: req.query.endMonth, page: req.query.page, pageSize: req.query.pageSize, keyword: req.query.keyword, sortField: req.query.sortField, sortOrder: req.query.sortOrder, minSingleOrderQty: req.query.minSingleOrderQty, autoSync: req.query.autoSync, channels: req.query.channels, shops: req.query.shops, }); res.json({ success: true, data }); } catch (error) { console.error('获取渠道大客户分析失败:', error); res.status(error.statusCode || 500).json({ success: false, error: error.message || '获取渠道大客户分析失败', }); } }); router.get('/large-customers/dashboard', authMiddleware, async (req, res) => { try { const data = await getChannelLargeCustomerDashboard({ startMonth: req.query.startMonth, endMonth: req.query.endMonth, keyword: req.query.keyword, minSingleOrderQty: req.query.minSingleOrderQty, channels: req.query.channels, shops: req.query.shops, limit: req.query.limit, }); res.json({ success: true, data }); } catch (error) { console.error('获取渠道大客户多维分析仪表盘失败:', error); res.status(error.statusCode || 500).json({ success: false, error: error.message || '获取渠道大客户多维分析仪表盘失败', }); } }); router.get('/large-customers/detail', authMiddleware, async (req, res) => { try { const data = await getChannelLargeCustomerDetail({ startMonth: req.query.startMonth, endMonth: req.query.endMonth, buyerId: req.query.buyerId, channelName: req.query.channelName, shopName: req.query.shopName, channels: req.query.channels, shops: req.query.shops, minSingleOrderQty: req.query.minSingleOrderQty, limit: req.query.limit, }); res.json({ success: true, data }); } catch (error) { console.error('获取渠道大客户订单明细失败:', error); res.status(error.statusCode || 500).json({ success: false, error: error.message || '获取渠道大客户订单明细失败', }); } }); router.post('/large-customers/refresh', authMiddleware, async (req, res) => { try { const data = await refreshChannelLargeCustomerReadStore({ startMonth: req.body.startMonth, endMonth: req.body.endMonth, page: req.body.page, pageSize: req.body.pageSize, keyword: req.body.keyword, sortField: req.body.sortField, sortOrder: req.body.sortOrder, minSingleOrderQty: req.body.minSingleOrderQty, channels: req.body.channels, shops: req.body.shops, }); res.json({ success: true, data }); } catch (error) { console.error('刷新渠道大客户分析失败:', error); res.status(error.statusCode || 500).json({ success: false, error: error.message || '刷新渠道大客户分析失败', }); } }); module.exports = router;