# 平台数据详情页数据计算逻辑详解 ## 概述 本文档详细说明平台数据详情页(`/api/work-day-statistics/platform-detail`)中各项数据的计算逻辑,帮助理解为什么某些数据可能显示为 0 或 null。 ## 数据计算流程 ### 1. 接口入口 **文件**: `server/src/routes/workDayStatistics.ts` **路由**: `GET /api/work-day-statistics/platform-detail` **参数**: - `platform`: 平台类型(必填) - `startDate`: 开始日期(必填,格式:YYYY-MM-DD) - `endDate`: 结束日期(必填,格式:YYYY-MM-DD) ### 2. 服务方法 **文件**: `server/src/services/WorkDayStatisticsService.ts` **方法**: `getPlatformDetail(userId, platform, { startDate, endDate })` ## 详细计算逻辑 ### 2.1 获取账号列表 ```typescript // 获取该平台的所有账号 const accounts = await this.accountRepository.find({ where: { userId, platform: platform as any, }, relations: ['group'], }); ``` ### 2.2 遍历每个账号计算数据 对每个账号执行以下步骤: #### 步骤 1: 获取账号的所有作品 ```typescript const works = await this.workRepository.find({ where: { accountId: account.id }, select: ['id'], }); const workIds = works.map(w => w.id); ``` **注意**: 如果账号没有作品,`workIds` 为空数组,后续所有统计数据都会是 0。 #### 步骤 2: 计算账号在时间范围内的增量数据 **核心方法**: `getWorkSumsAtDate(workIds, targetDate)` **作用**: 获取指定日期(<= targetDate)时,该账号所有作品的累计数据总和 **SQL 逻辑**: ```sql SELECT COALESCE(SUM(wds.play_count), 0) AS views, COALESCE(SUM(wds.like_count), 0) AS likes, COALESCE(SUM(wds.comment_count), 0) AS comments, COALESCE(SUM(wds.collect_count), 0) AS collects FROM work_day_statistics wds INNER JOIN ( SELECT wds2.work_id, MAX(wds2.record_date) AS record_date FROM work_day_statistics wds2 WHERE wds2.work_id IN (...) AND wds2.record_date <= ? GROUP BY wds2.work_id ) latest ON latest.work_id = wds.work_id AND latest.record_date = wds.record_date ``` **计算增量**: ```typescript // 获取结束日期和开始日期的累计数据 const [endSums, startSums] = await Promise.all([ this.getWorkSumsAtDate(workIds, endDateStr), this.getWorkSumsAtDate(workIds, startDateStr), ]); // 计算增量(结束日期累计值 - 开始日期累计值) const accountViews = endSums.views - startSums.views; const accountComments = endSums.comments - startSums.comments; const accountLikes = endSums.likes - startSums.likes; ``` **关键点**: - 如果开始日期和结束日期相同,增量 = 当日累计值 - 前一日累计值 - 如果账号没有作品,`workIds` 为空,`getWorkSumsAtDate` 返回 `{ views: 0, likes: 0, comments: 0, collects: 0 }` - 如果账号没有统计数据,`getWorkSumsAtDate` 也会返回 0 #### 步骤 3: 计算粉丝增量 ```typescript // 获取结束日期的粉丝数(<= endDate 的最近一条记录) const endUserStat = await this.userDayStatisticsRepository .createQueryBuilder('uds') .where('uds.account_id = :accountId', { accountId: account.id }) .andWhere('DATE(uds.record_date) <= :d', { d: endDateStr }) .orderBy('uds.record_date', 'DESC') .getOne(); // 获取开始日期的粉丝数(<= startDate 的最近一条记录) const startUserStat = await this.userDayStatisticsRepository .createQueryBuilder('uds') .where('uds.account_id = :accountId', { accountId: account.id }) .andWhere('DATE(uds.record_date) <= :d', { d: startDateStr }) .orderBy('uds.record_date', 'DESC') .getOne(); // 计算粉丝增量 const accountFansIncrease = (endUserStat?.fansCount || 0) - (startUserStat?.fansCount || 0); ``` **关键点**: - 如果开始日期和结束日期相同,增量 = 当日粉丝数 - 前一日粉丝数 - 如果没有统计数据,`endUserStat` 或 `startUserStat` 为 `null`,使用 `|| 0` 默认值 - 如果开始日期和结束日期都没有数据,增量 = 0 - 0 = 0 #### 步骤 4: 获取更新时间 ```typescript // 获取时间范围内的最新更新时间 const latestUserStat = await this.userDayStatisticsRepository .createQueryBuilder('uds') .where('uds.account_id = :accountId', { accountId: account.id }) .andWhere('DATE(uds.record_date) >= :startDate', { startDate: startDateStr }) .andWhere('DATE(uds.record_date) <= :endDate', { endDate: endDateStr }) .orderBy('uds.updated_at', 'DESC') .getOne(); const updateTime = latestUserStat?.updatedAt ? this.formatUpdateTime(latestUserStat.updatedAt) : ''; ``` **关键点**: - 只有在时间范围内有 `user_day_statistics` 记录时才有值 - 如果没有记录,返回空字符串 `""` #### 步骤 5: 组装账号数据 ```typescript accountList.push({ id: account.id, nickname: account.accountName || '', username: account.accountId || '', avatarUrl: account.avatarUrl, platform: account.platform, income: null, // 收益数据需要从其他表获取 recommendationCount: null, // 推荐量(部分平台支持) viewsCount: accountViews > 0 ? accountViews : null, // ⚠️ 关键:只有 > 0 才返回,否则为 null commentsCount: Math.max(0, accountComments), // ⚠️ 关键:负数会被截断为 0 likesCount: Math.max(0, accountLikes), // ⚠️ 关键:负数会被截断为 0 fansIncrease: Math.max(0, accountFansIncrease), // ⚠️ 关键:负数会被截断为 0 updateTime, }); ``` **关键逻辑**: 1. **viewsCount**: - 如果 `accountViews > 0`,返回实际值 - 如果 `accountViews <= 0`,返回 `null` - **这意味着如果增量为 0 或负数,前端会显示为 null** 2. **commentsCount / likesCount / fansIncrease**: - 使用 `Math.max(0, value)` 确保不为负数 - 如果计算结果为负数,会被截断为 0 - **这意味着如果数据减少(比如掉粉),会显示为 0 而不是负数** ### 2.3 计算每日汇总数据 ```typescript // 遍历日期范围内的每一天 while (currentDate <= dateEnd) { const dateStr = this.formatDate(currentDate); // 获取该日期的累计数据 const [daySums, prevDaySums] = await Promise.all([ this.getWorkSumsAtDate(workIds, dateStr), this.getWorkSumsAtDate(workIds, this.formatDate(new Date(currentDate.getTime() - 24 * 60 * 60 * 1000))), ]); // 计算当日增量 const dayViews = daySums.views - prevDaySums.views; const dayComments = daySums.comments - prevDaySums.comments; const dayLikes = daySums.likes - prevDaySums.likes; // 获取粉丝增量 const dayUserStat = await this.userDayStatisticsRepository .createQueryBuilder('uds') .where('uds.account_id = :accountId', { accountId: account.id }) .andWhere('DATE(uds.record_date) = :d', { d: dateStr }) .getOne(); const prevDayUserStat = await this.userDayStatisticsRepository .createQueryBuilder('uds') .where('uds.account_id = :accountId', { accountId: account.id }) .andWhere('DATE(uds.record_date) = :d', { d: this.formatDate(new Date(currentDate.getTime() - 24 * 60 * 60 * 1000)) }) .getOne(); const dayFans = (dayUserStat?.fansCount || 0) - (prevDayUserStat?.fansCount || 0); // 累加到每日汇总 if (!dailyMap.has(dateStr)) { dailyMap.set(dateStr, { views: 0, comments: 0, likes: 0, fansIncrease: 0, }); } const daily = dailyMap.get(dateStr)!; daily.views += Math.max(0, dayViews); daily.comments += Math.max(0, dayComments); daily.likes += Math.max(0, dayLikes); daily.fansIncrease += Math.max(0, dayFans); currentDate.setDate(currentDate.getDate() + 1); } ``` **关键点**: - 每日增量 = 当日累计值 - 前一日累计值 - 如果前一日没有数据,前一日累计值 = 0 - 负数会被 `Math.max(0, ...)` 截断为 0 ### 2.4 计算汇总统计 ```typescript // 累加所有账号的数据 totalAccounts++; totalViews += Math.max(0, accountViews); totalComments += Math.max(0, accountComments); totalLikes += Math.max(0, accountLikes); totalFansIncrease += Math.max(0, accountFansIncrease); ``` ## 数据为 0 或 null 的原因分析 ### 情况 1: viewsCount 为 null **原因**: - `accountViews <= 0` 时返回 `null` - 可能的情况: 1. 账号没有作品(`workIds` 为空) 2. 账号有作品但没有统计数据 3. 开始日期和结束日期的累计播放量相同(增量为 0) 4. 播放量减少(增量为负数) **解决方案**: - 检查账号是否有作品 - 检查 `work_day_statistics` 表中是否有该账号作品的数据 - 检查时间范围是否合理 ### 情况 2: commentsCount / likesCount / fansIncrease 为 0 **原因**: - 计算结果为 0 或负数时,使用 `Math.max(0, value)` 截断为 0 - 可能的情况: 1. 账号没有作品或统计数据 2. 开始日期和结束日期的累计值相同(增量为 0) 3. 数据减少(增量为负数,被截断为 0) **解决方案**: - 检查账号是否有作品 - 检查统计数据是否存在 - 检查时间范围是否合理 - 如果需要显示负数(如掉粉),需要修改逻辑 ### 情况 3: updateTime 为空字符串 **原因**: - 时间范围内没有 `user_day_statistics` 记录 **解决方案**: - 检查账号是否有统计数据 - 检查时间范围是否合理 - 可能需要触发数据同步 ### 情况 4: income / recommendationCount 为 null **原因**: - 这些字段目前未实现,固定返回 `null` **解决方案**: - 需要实现收益和推荐量的数据获取逻辑 ## 数据依赖关系 ### 数据来源表 1. **作品统计数据**: `work_day_statistics` 表 - 字段: `work_id`, `record_date`, `play_count`, `like_count`, `comment_count`, `collect_count` - 用途: 计算播放量、点赞量、评论量增量 2. **账号统计数据**: `user_day_statistics` 表 - 字段: `account_id`, `record_date`, `fans_count`, `updated_at` - 用途: 计算粉丝增量、获取更新时间 3. **账号表**: `platform_accounts` 表 - 字段: `id`, `account_name`, `account_id`, `avatar_url`, `platform` - 用途: 获取账号基本信息 4. **作品表**: `works` 表 - 字段: `id`, `account_id` - 用途: 获取账号下的所有作品ID ### 数据计算依赖链 ``` 账号 (platform_accounts) ↓ 作品 (works) → 作品ID列表 (workIds) ↓ 作品统计数据 (work_day_statistics) → 累计数据 (getWorkSumsAtDate) ↓ 增量计算 (endSums - startSums) → 账号数据 (viewsCount, commentsCount, likesCount) ↓ 账号统计数据 (user_day_statistics) → 粉丝增量 (fansIncrease) ``` ## 调试建议 ### 1. 检查账号是否有作品 ```sql SELECT COUNT(*) FROM works WHERE account_id = ?; ``` ### 2. 检查作品统计数据 ```sql SELECT COUNT(*) FROM work_day_statistics wds INNER JOIN works w ON wds.work_id = w.id WHERE w.account_id = ?; ``` ### 3. 检查账号统计数据 ```sql SELECT COUNT(*) FROM user_day_statistics WHERE account_id = ?; ``` ### 4. 检查特定日期的数据 ```sql -- 检查作品统计数据 SELECT * FROM work_day_statistics wds INNER JOIN works w ON wds.work_id = w.id WHERE w.account_id = ? AND DATE(wds.record_date) = '2026-01-27'; -- 检查账号统计数据 SELECT * FROM user_day_statistics WHERE account_id = ? AND DATE(record_date) = '2026-01-27'; ``` ## 修复建议 ### 1. 改进 viewsCount 的逻辑 **当前逻辑**: ```typescript viewsCount: accountViews > 0 ? accountViews : null, ``` **建议**: ```typescript // 如果账号有作品,即使增量为 0 也显示 0,而不是 null viewsCount: workIds.length > 0 ? (accountViews > 0 ? accountViews : 0) : null, ``` ### 2. 允许显示负数(如果需要) **当前逻辑**: ```typescript fansIncrease: Math.max(0, accountFansIncrease), ``` **建议**: ```typescript // 如果需要显示掉粉(负数),可以改为: fansIncrease: accountFansIncrease, // 允许负数 ``` ### 3. 改进 updateTime 的逻辑 **当前逻辑**: ```typescript const updateTime = latestUserStat?.updatedAt ? this.formatUpdateTime(latestUserStat.updatedAt) : ''; ``` **建议**: ```typescript // 如果没有统计数据,可以使用账号表的更新时间 const updateTime = latestUserStat?.updatedAt ? this.formatUpdateTime(latestUserStat.updatedAt) : (account.updatedAt ? this.formatUpdateTime(account.updatedAt) : ''); ``` ## 相关文件 - `server/src/services/WorkDayStatisticsService.ts` - 数据计算逻辑 - `server/src/routes/workDayStatistics.ts` - API 路由 - `client/src/views/Analytics/PlatformDetail/index.vue` - 前端展示 ## 更新日期 2026-01-28