|
|
@@ -1,10 +1,9 @@
|
|
|
-import { AppDataSource, WorkDayStatistics, Work, PlatformAccount } from '../models/index.js';
|
|
|
+import { AppDataSource, WorkDayStatistics, Work, PlatformAccount, UserDayStatistics } from '../models/index.js';
|
|
|
import { Between, In } from 'typeorm';
|
|
|
import { logger } from '../utils/logger.js';
|
|
|
|
|
|
interface StatisticsItem {
|
|
|
workId: number;
|
|
|
- fansCount?: number;
|
|
|
playCount?: number;
|
|
|
likeCount?: number;
|
|
|
commentCount?: number;
|
|
|
@@ -39,7 +38,6 @@ interface PlatformStatItem {
|
|
|
|
|
|
interface WorkStatisticsItem {
|
|
|
recordDate: string;
|
|
|
- fansCount: number;
|
|
|
playCount: number;
|
|
|
likeCount: number;
|
|
|
commentCount: number;
|
|
|
@@ -51,6 +49,7 @@ export class WorkDayStatisticsService {
|
|
|
private statisticsRepository = AppDataSource.getRepository(WorkDayStatistics);
|
|
|
private workRepository = AppDataSource.getRepository(Work);
|
|
|
private accountRepository = AppDataSource.getRepository(PlatformAccount);
|
|
|
+ private userDayStatisticsRepository = AppDataSource.getRepository(UserDayStatistics);
|
|
|
|
|
|
/**
|
|
|
* 保存作品日统计数据
|
|
|
@@ -75,9 +74,8 @@ export class WorkDayStatisticsService {
|
|
|
});
|
|
|
|
|
|
if (existing) {
|
|
|
- // 更新已有记录
|
|
|
+ // 更新已有记录(不再包含粉丝数,粉丝数从 user_day_statistics 表获取)
|
|
|
await this.statisticsRepository.update(existing.id, {
|
|
|
- fansCount: stat.fansCount ?? existing.fansCount,
|
|
|
playCount: stat.playCount ?? existing.playCount,
|
|
|
likeCount: stat.likeCount ?? existing.likeCount,
|
|
|
commentCount: stat.commentCount ?? existing.commentCount,
|
|
|
@@ -86,11 +84,10 @@ export class WorkDayStatisticsService {
|
|
|
});
|
|
|
updatedCount++;
|
|
|
} else {
|
|
|
- // 插入新记录
|
|
|
+ // 插入新记录(不再包含粉丝数,粉丝数从 user_day_statistics 表获取)
|
|
|
const newStat = this.statisticsRepository.create({
|
|
|
workId: stat.workId,
|
|
|
recordDate: today,
|
|
|
- fansCount: stat.fansCount ?? 0,
|
|
|
playCount: stat.playCount ?? 0,
|
|
|
likeCount: stat.likeCount ?? 0,
|
|
|
commentCount: stat.commentCount ?? 0,
|
|
|
@@ -132,13 +129,12 @@ export class WorkDayStatisticsService {
|
|
|
dateStart.setDate(dateStart.getDate() - Math.min(days, 30) + 1);
|
|
|
}
|
|
|
|
|
|
- // 构建查询
|
|
|
+ // 构建查询(不再从 work_day_statistics 读取粉丝数,粉丝数从 user_day_statistics 表获取)
|
|
|
const queryBuilder = this.statisticsRepository
|
|
|
.createQueryBuilder('wds')
|
|
|
.innerJoin(Work, 'w', 'wds.work_id = w.id')
|
|
|
.select('wds.record_date', 'recordDate')
|
|
|
.addSelect('w.accountId', 'accountId')
|
|
|
- .addSelect('MAX(wds.fans_count)', 'accountFans')
|
|
|
.addSelect('SUM(wds.play_count)', 'accountViews')
|
|
|
.addSelect('SUM(wds.like_count)', 'accountLikes')
|
|
|
.addSelect('SUM(wds.comment_count)', 'accountComments')
|
|
|
@@ -270,8 +266,7 @@ export class WorkDayStatisticsService {
|
|
|
const firstDayQuery = this.statisticsRepository
|
|
|
.createQueryBuilder('wds')
|
|
|
.innerJoin(Work, 'w', 'wds.work_id = w.id')
|
|
|
- .select('MAX(wds.fans_count)', 'fans')
|
|
|
- .addSelect('SUM(wds.play_count)', 'views')
|
|
|
+ .select('SUM(wds.play_count)', 'views')
|
|
|
.addSelect('SUM(wds.like_count)', 'likes')
|
|
|
.addSelect('SUM(wds.comment_count)', 'comments')
|
|
|
.addSelect('SUM(wds.collect_count)', 'collects')
|
|
|
@@ -285,8 +280,7 @@ export class WorkDayStatisticsService {
|
|
|
const lastDayQuery = this.statisticsRepository
|
|
|
.createQueryBuilder('wds')
|
|
|
.innerJoin(Work, 'w', 'wds.work_id = w.id')
|
|
|
- .select('MAX(wds.fans_count)', 'fans')
|
|
|
- .addSelect('SUM(wds.play_count)', 'views')
|
|
|
+ .select('SUM(wds.play_count)', 'views')
|
|
|
.addSelect('SUM(wds.like_count)', 'likes')
|
|
|
.addSelect('SUM(wds.comment_count)', 'comments')
|
|
|
.addSelect('SUM(wds.collect_count)', 'collects')
|
|
|
@@ -302,8 +296,26 @@ export class WorkDayStatisticsService {
|
|
|
lastDayQuery.getRawOne(),
|
|
|
]);
|
|
|
|
|
|
- const currentFans = account.fansCount ?? 0;
|
|
|
- const earliestFans = parseInt(firstDay?.fans) || currentFans;
|
|
|
+ // 从 user_day_statistics 表获取粉丝数
|
|
|
+ const todayDate = new Date();
|
|
|
+ todayDate.setHours(0, 0, 0, 0);
|
|
|
+ const todayUserStat = await this.userDayStatisticsRepository.findOne({
|
|
|
+ where: {
|
|
|
+ accountId: account.id,
|
|
|
+ recordDate: todayDate,
|
|
|
+ },
|
|
|
+ });
|
|
|
+ const currentFans = todayUserStat?.fansCount ?? account.fansCount ?? 0;
|
|
|
+
|
|
|
+ // 获取最早日期的粉丝数
|
|
|
+ const earliestUserStat = await this.userDayStatisticsRepository
|
|
|
+ .createQueryBuilder('uds')
|
|
|
+ .where('uds.account_id = :accountId', { accountId: account.id })
|
|
|
+ .andWhere('uds.record_date >= :dateStart', { dateStart })
|
|
|
+ .andWhere('uds.record_date <= :dateEnd', { dateEnd })
|
|
|
+ .orderBy('uds.record_date', 'ASC')
|
|
|
+ .getOne();
|
|
|
+ const earliestFans = earliestUserStat?.fansCount ?? currentFans;
|
|
|
const fansIncrease = currentFans - earliestFans;
|
|
|
|
|
|
const viewsIncrease = (parseInt(lastDay?.views) || 0) - (parseInt(firstDay?.views) || 0);
|
|
|
@@ -344,7 +356,6 @@ export class WorkDayStatisticsService {
|
|
|
.createQueryBuilder('wds')
|
|
|
.select('wds.work_id', 'workId')
|
|
|
.addSelect('wds.record_date', 'recordDate')
|
|
|
- .addSelect('wds.fans_count', 'fansCount')
|
|
|
.addSelect('wds.play_count', 'playCount')
|
|
|
.addSelect('wds.like_count', 'likeCount')
|
|
|
.addSelect('wds.comment_count', 'commentCount')
|
|
|
@@ -378,7 +389,6 @@ export class WorkDayStatisticsService {
|
|
|
|
|
|
groupedData[workId].push({
|
|
|
recordDate,
|
|
|
- fansCount: parseInt(row.fansCount) || 0,
|
|
|
playCount: parseInt(row.playCount) || 0,
|
|
|
likeCount: parseInt(row.likeCount) || 0,
|
|
|
commentCount: parseInt(row.commentCount) || 0,
|
|
|
@@ -487,7 +497,55 @@ export class WorkDayStatisticsService {
|
|
|
|
|
|
if (works.length === 0) {
|
|
|
// 如果没有作品,只返回账号基本信息
|
|
|
- const accountFansCount = account.fansCount || 0;
|
|
|
+ // 从 user_day_statistics 表获取账号的最新粉丝数
|
|
|
+ // 使用日期字符串直接查询(更可靠,避免时区问题)
|
|
|
+ const todayUserStat = await this.userDayStatisticsRepository
|
|
|
+ .createQueryBuilder('uds')
|
|
|
+ .where('uds.account_id = :accountId', { accountId: account.id })
|
|
|
+ .andWhere('DATE(uds.record_date) = :today', { today: todayStr })
|
|
|
+ .getOne();
|
|
|
+
|
|
|
+ const yesterdayUserStat = await this.userDayStatisticsRepository
|
|
|
+ .createQueryBuilder('uds')
|
|
|
+ .where('uds.account_id = :accountId', { accountId: account.id })
|
|
|
+ .andWhere('DATE(uds.record_date) = :yesterday', { yesterday: yesterdayStr })
|
|
|
+ .getOne();
|
|
|
+
|
|
|
+ // 获取今天的粉丝数:优先使用统计数据,如果没有则使用账号表的当前值
|
|
|
+ let accountFansCount: number;
|
|
|
+ if (todayUserStat) {
|
|
|
+ accountFansCount = todayUserStat.fansCount || 0;
|
|
|
+ } else {
|
|
|
+ accountFansCount = account.fansCount || 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 计算昨日涨粉(今天粉丝数 - 昨天粉丝数)
|
|
|
+ let accountYesterdayFansIncrease: number;
|
|
|
+ if (yesterdayUserStat) {
|
|
|
+ // 昨天有数据,直接使用
|
|
|
+ const yesterdayFans = yesterdayUserStat.fansCount || 0;
|
|
|
+ accountYesterdayFansIncrease = Math.max(0, accountFansCount - yesterdayFans);
|
|
|
+ logger.debug(`[WorkDayStatistics] Account ${account.id} (no works) - yesterday: ${yesterdayFans}, today: ${accountFansCount}, increase: ${accountYesterdayFansIncrease}`);
|
|
|
+ } else {
|
|
|
+ // 昨天没有数据,查找最近一天(早于今天)的数据作为基准
|
|
|
+ const recentUserStat = await this.userDayStatisticsRepository
|
|
|
+ .createQueryBuilder('uds')
|
|
|
+ .where('uds.account_id = :accountId', { accountId: account.id })
|
|
|
+ .andWhere('DATE(uds.record_date) < :today', { today: todayStr })
|
|
|
+ .orderBy('uds.record_date', 'DESC')
|
|
|
+ .getOne();
|
|
|
+
|
|
|
+ if (recentUserStat) {
|
|
|
+ const recentFans = recentUserStat.fansCount || 0;
|
|
|
+ accountYesterdayFansIncrease = Math.max(0, accountFansCount - recentFans);
|
|
|
+ logger.debug(`[WorkDayStatistics] Account ${account.id} (no works) - using recent: ${recentFans}, today: ${accountFansCount}, increase: ${accountYesterdayFansIncrease}`);
|
|
|
+ } else {
|
|
|
+ // 完全没有历史数据,涨粉为 0
|
|
|
+ accountYesterdayFansIncrease = 0;
|
|
|
+ logger.debug(`[WorkDayStatistics] Account ${account.id} (no works) - no history data, increase: 0`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
accountList.push({
|
|
|
id: account.id,
|
|
|
nickname: account.accountName || '',
|
|
|
@@ -502,7 +560,7 @@ export class WorkDayStatisticsService {
|
|
|
yesterdayViews: null,
|
|
|
yesterdayComments: 0,
|
|
|
yesterdayLikes: 0,
|
|
|
- yesterdayFansIncrease: 0,
|
|
|
+ yesterdayFansIncrease: accountYesterdayFansIncrease,
|
|
|
updateTime: account.updatedAt.toISOString(),
|
|
|
status: account.status,
|
|
|
});
|
|
|
@@ -510,12 +568,13 @@ export class WorkDayStatisticsService {
|
|
|
// 即使没有作品,也要累加账号的粉丝数到总粉丝数
|
|
|
totalAccounts++;
|
|
|
totalFans += accountFansCount;
|
|
|
+ yesterdayFansIncrease += accountYesterdayFansIncrease;
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
const workIds = works.map(w => w.id);
|
|
|
|
|
|
- // 获取每个作品的最新日期统计数据(总播放量等)
|
|
|
+ // 获取每个作品的最新日期统计数据(总播放量等,不再包含粉丝数)
|
|
|
const latestStatsQuery = this.statisticsRepository
|
|
|
.createQueryBuilder('wds')
|
|
|
.select('wds.work_id', 'workId')
|
|
|
@@ -523,7 +582,6 @@ export class WorkDayStatisticsService {
|
|
|
.addSelect('MAX(wds.play_count)', 'playCount')
|
|
|
.addSelect('MAX(wds.like_count)', 'likeCount')
|
|
|
.addSelect('MAX(wds.comment_count)', 'commentCount')
|
|
|
- .addSelect('MAX(wds.fans_count)', 'fansCount')
|
|
|
.where('wds.work_id IN (:...workIds)', { workIds })
|
|
|
.groupBy('wds.work_id');
|
|
|
|
|
|
@@ -537,15 +595,13 @@ export class WorkDayStatisticsService {
|
|
|
latestDateMap.set(stat.workId, stat.latestDate);
|
|
|
}
|
|
|
|
|
|
- // 获取昨天和今天的数据来计算增量
|
|
|
- // 使用日期字符串直接比较(DATE 类型会自动转换)
|
|
|
+ // 获取昨天和今天的数据来计算增量(不再包含粉丝数,粉丝数从 user_day_statistics 表获取)
|
|
|
const yesterdayStatsQuery = this.statisticsRepository
|
|
|
.createQueryBuilder('wds')
|
|
|
.select('wds.work_id', 'workId')
|
|
|
.addSelect('SUM(wds.play_count)', 'playCount')
|
|
|
.addSelect('SUM(wds.like_count)', 'likeCount')
|
|
|
.addSelect('SUM(wds.comment_count)', 'commentCount')
|
|
|
- .addSelect('MAX(wds.fans_count)', 'fansCount')
|
|
|
.where('wds.work_id IN (:...workIds)', { workIds })
|
|
|
.andWhere('wds.record_date = :yesterday', { yesterday: yesterdayStr })
|
|
|
.groupBy('wds.work_id');
|
|
|
@@ -556,7 +612,6 @@ export class WorkDayStatisticsService {
|
|
|
.addSelect('SUM(wds.play_count)', 'playCount')
|
|
|
.addSelect('SUM(wds.like_count)', 'likeCount')
|
|
|
.addSelect('SUM(wds.comment_count)', 'commentCount')
|
|
|
- .addSelect('MAX(wds.fans_count)', 'fansCount')
|
|
|
.where('wds.work_id IN (:...workIds)', { workIds })
|
|
|
.andWhere('wds.record_date = :today', { today: todayStr })
|
|
|
.groupBy('wds.work_id');
|
|
|
@@ -579,9 +634,9 @@ export class WorkDayStatisticsService {
|
|
|
let accountYesterdayLikes = 0;
|
|
|
let accountYesterdayFansIncrease = 0;
|
|
|
|
|
|
- // 按作品ID汇总
|
|
|
- const yesterdayMap = new Map<number, { play: number; like: number; comment: number; fans: number }>();
|
|
|
- const todayMap = new Map<number, { play: number; like: number; comment: number; fans: number }>();
|
|
|
+ // 按作品ID汇总(不再包含粉丝数)
|
|
|
+ const yesterdayMap = new Map<number, { play: number; like: number; comment: number }>();
|
|
|
+ const todayMap = new Map<number, { play: number; like: number; comment: number }>();
|
|
|
|
|
|
for (const stat of yesterdayStats) {
|
|
|
const workId = parseInt(String(stat.workId)) || 0;
|
|
|
@@ -589,7 +644,6 @@ export class WorkDayStatisticsService {
|
|
|
play: Number(stat.playCount) || 0,
|
|
|
like: Number(stat.likeCount) || 0,
|
|
|
comment: Number(stat.commentCount) || 0,
|
|
|
- fans: Number(stat.fansCount) || 0,
|
|
|
});
|
|
|
}
|
|
|
|
|
|
@@ -599,16 +653,15 @@ export class WorkDayStatisticsService {
|
|
|
play: Number(stat.playCount) || 0,
|
|
|
like: Number(stat.likeCount) || 0,
|
|
|
comment: Number(stat.commentCount) || 0,
|
|
|
- fans: Number(stat.fansCount) || 0,
|
|
|
});
|
|
|
}
|
|
|
|
|
|
logger.debug(`[WorkDayStatistics] Account ${account.id} - yesterdayMap size: ${yesterdayMap.size}, todayMap size: ${todayMap.size}`);
|
|
|
|
|
|
- // 计算增量(今天 - 昨天)
|
|
|
+ // 计算增量(今天 - 昨天,不再包含粉丝数)
|
|
|
for (const workId of workIds) {
|
|
|
- const todayData = todayMap.get(workId) || { play: 0, like: 0, comment: 0, fans: 0 };
|
|
|
- const yesterdayData = yesterdayMap.get(workId) || { play: 0, like: 0, comment: 0, fans: 0 };
|
|
|
+ const todayData = todayMap.get(workId) || { play: 0, like: 0, comment: 0 };
|
|
|
+ const yesterdayData = yesterdayMap.get(workId) || { play: 0, like: 0, comment: 0 };
|
|
|
|
|
|
const viewsDiff = todayData.play - yesterdayData.play;
|
|
|
const commentsDiff = todayData.comment - yesterdayData.comment;
|
|
|
@@ -621,46 +674,64 @@ export class WorkDayStatisticsService {
|
|
|
|
|
|
logger.info(`[WorkDayStatistics] Account ${account.id} - Calculated: views=${accountYesterdayViews}, comments=${accountYesterdayComments}, likes=${accountYesterdayLikes}`);
|
|
|
|
|
|
- // 获取账号的最新粉丝数(从最新日期的统计数据中取最大值)
|
|
|
- let accountFansCount = account.fansCount || 0;
|
|
|
- if (latestStats.length > 0) {
|
|
|
- const maxFans = Math.max(...latestStats.map(s => parseInt(s.fansCount) || 0));
|
|
|
- if (maxFans > 0) {
|
|
|
- accountFansCount = maxFans;
|
|
|
- }
|
|
|
+ // 从 user_day_statistics 表获取账号的最新粉丝数和作品数
|
|
|
+ // 优先使用统计数据,如果没有则使用账号表的当前值
|
|
|
+
|
|
|
+ // 使用日期字符串直接查询(更可靠,避免时区问题)
|
|
|
+ // todayStr 和 yesterdayStr 是中国时区的日期字符串(如 "2026-01-26")
|
|
|
+ // 直接使用字符串查询,TypeORM 会自动转换为 DATE 类型进行比较
|
|
|
+ const todayUserStat = await this.userDayStatisticsRepository
|
|
|
+ .createQueryBuilder('uds')
|
|
|
+ .where('uds.account_id = :accountId', { accountId: account.id })
|
|
|
+ .andWhere('DATE(uds.record_date) = :today', { today: todayStr })
|
|
|
+ .getOne();
|
|
|
+
|
|
|
+ const yesterdayUserStat = await this.userDayStatisticsRepository
|
|
|
+ .createQueryBuilder('uds')
|
|
|
+ .where('uds.account_id = :accountId', { accountId: account.id })
|
|
|
+ .andWhere('DATE(uds.record_date) = :yesterday', { yesterday: yesterdayStr })
|
|
|
+ .getOne();
|
|
|
+
|
|
|
+ // 获取今天的粉丝数:优先使用统计数据,如果没有则使用账号表的当前值
|
|
|
+ let accountFansCount: number;
|
|
|
+ let accountWorksCount: number;
|
|
|
+ if (todayUserStat) {
|
|
|
+ accountFansCount = todayUserStat.fansCount || 0;
|
|
|
+ accountWorksCount = todayUserStat.worksCount || 0;
|
|
|
+ } else {
|
|
|
+ // 今天没有统计数据,使用账号表的当前值
|
|
|
+ accountFansCount = account.fansCount || 0;
|
|
|
+ accountWorksCount = account.worksCount || 0;
|
|
|
}
|
|
|
-
|
|
|
- // 计算昨日涨粉(今天最新粉丝数 - 昨天最新粉丝数)
|
|
|
- // 如果今天有统计数据,用今天数据中的最大粉丝数;否则用账号表的当前粉丝数
|
|
|
- const todayMaxFans = todayStats.length > 0
|
|
|
- ? Math.max(...todayStats.map(s => parseInt(s.fansCount) || 0))
|
|
|
- : accountFansCount;
|
|
|
|
|
|
- // 如果昨天有统计数据,用昨天数据中的最大粉丝数;否则需要找最近一天的数据作为基准
|
|
|
- let yesterdayMaxFans: number;
|
|
|
- if (yesterdayStats.length > 0) {
|
|
|
- // 昨天有数据,直接用昨天的最大粉丝数
|
|
|
- yesterdayMaxFans = Math.max(...yesterdayStats.map(s => parseInt(s.fansCount) || 0));
|
|
|
+ // 计算昨日涨粉(今天粉丝数 - 昨天粉丝数)
|
|
|
+ let yesterdayFans: number;
|
|
|
+ if (yesterdayUserStat) {
|
|
|
+ // 昨天有数据,直接使用
|
|
|
+ yesterdayFans = yesterdayUserStat.fansCount || 0;
|
|
|
+ logger.debug(`[WorkDayStatistics] Account ${account.id} - yesterday has data: ${yesterdayFans}, today: ${accountFansCount}`);
|
|
|
} else {
|
|
|
- // 昨天没有数据,需要找最近一天的数据作为基准
|
|
|
- // 查询该账号最近一天(早于今天)的统计数据
|
|
|
- const recentStatsQuery = this.statisticsRepository
|
|
|
- .createQueryBuilder('wds')
|
|
|
- .select('MAX(wds.fans_count)', 'fansCount')
|
|
|
- .where('wds.work_id IN (:...workIds)', { workIds })
|
|
|
- .andWhere('wds.record_date < :today', { today: todayStr });
|
|
|
+ // 昨天没有数据,查找最近一天(早于今天)的数据作为基准
|
|
|
+ const recentUserStat = await this.userDayStatisticsRepository
|
|
|
+ .createQueryBuilder('uds')
|
|
|
+ .where('uds.account_id = :accountId', { accountId: account.id })
|
|
|
+ .andWhere('DATE(uds.record_date) < :today', { today: todayStr })
|
|
|
+ .orderBy('uds.record_date', 'DESC')
|
|
|
+ .getOne();
|
|
|
|
|
|
- const recentStat = await recentStatsQuery.getRawOne();
|
|
|
- if (recentStat && recentStat.fansCount) {
|
|
|
- // 找到了最近一天的数据,用它作为基准
|
|
|
- yesterdayMaxFans = parseInt(recentStat.fansCount) || accountFansCount;
|
|
|
+ if (recentUserStat) {
|
|
|
+ yesterdayFans = recentUserStat.fansCount || 0;
|
|
|
+ logger.debug(`[WorkDayStatistics] Account ${account.id} - using recent data: ${yesterdayFans}, today: ${accountFansCount}`);
|
|
|
} else {
|
|
|
- // 完全没有历史数据,用账号表的当前粉丝数作为基准(但这样计算出来的增量可能不准确)
|
|
|
- yesterdayMaxFans = accountFansCount;
|
|
|
+ // 完全没有历史数据,用账号表的当前粉丝数作为基准(涨粉为 0)
|
|
|
+ yesterdayFans = accountFansCount;
|
|
|
+ logger.debug(`[WorkDayStatistics] Account ${account.id} - no history data, using current: ${accountFansCount}`);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- accountYesterdayFansIncrease = todayMaxFans - yesterdayMaxFans;
|
|
|
+ // 计算涨粉数(今天 - 昨天),确保不为负数
|
|
|
+ accountYesterdayFansIncrease = Math.max(0, accountFansCount - yesterdayFans);
|
|
|
+ logger.info(`[WorkDayStatistics] Account ${account.id} - fans increase: ${accountFansCount} - ${yesterdayFans} = ${accountYesterdayFansIncrease}`);
|
|
|
|
|
|
accountList.push({
|
|
|
id: account.id,
|