fix: sessionsCount 修复 + 手动会话结束更新 DailyLearningActivity
- upsertFromReadingEvent 加 isNewSession 参数,material_opened 时 increment sessionsCount - LearningSessionService.end() 调用 incrementDailySessions 更新日活会话计数 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
c97b58fc5f
commit
d23b9953ec
@ -27,9 +27,10 @@ export class LearningActivityRepository {
|
|||||||
activeSecondsDelta: number;
|
activeSecondsDelta: number;
|
||||||
isNewMaterial?: boolean;
|
isNewMaterial?: boolean;
|
||||||
isMarkedRead?: boolean;
|
isMarkedRead?: boolean;
|
||||||
|
isNewSession?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const activityDate = this.computeActivityDate(data.clientTimestampMs, data.clientTimezoneOffsetMinutes);
|
const activityDate = this.computeActivityDate(data.clientTimestampMs, data.clientTimezoneOffsetMinutes);
|
||||||
const { userId, activeSecondsDelta, isNewMaterial, isMarkedRead } = data;
|
const { userId, activeSecondsDelta, isNewMaterial, isMarkedRead, isNewSession } = data;
|
||||||
|
|
||||||
return tx.dailyLearningActivity.upsert({
|
return tx.dailyLearningActivity.upsert({
|
||||||
where: { userId_activityDate: { userId, activityDate } },
|
where: { userId_activityDate: { userId, activityDate } },
|
||||||
@ -40,16 +41,28 @@ export class LearningActivityRepository {
|
|||||||
readingSeconds: activeSecondsDelta,
|
readingSeconds: activeSecondsDelta,
|
||||||
materialsReadCount: isNewMaterial ? 1 : 0,
|
materialsReadCount: isNewMaterial ? 1 : 0,
|
||||||
markedReadCount: isMarkedRead ? 1 : 0,
|
markedReadCount: isMarkedRead ? 1 : 0,
|
||||||
|
sessionsCount: isNewSession ? 1 : 0,
|
||||||
},
|
},
|
||||||
update: {
|
update: {
|
||||||
durationSeconds: { increment: activeSecondsDelta },
|
durationSeconds: { increment: activeSecondsDelta },
|
||||||
readingSeconds: { increment: activeSecondsDelta },
|
readingSeconds: { increment: activeSecondsDelta },
|
||||||
materialsReadCount: isNewMaterial ? { increment: 1 } : undefined,
|
materialsReadCount: isNewMaterial ? { increment: 1 } : undefined,
|
||||||
markedReadCount: isMarkedRead ? { increment: 1 } : undefined,
|
markedReadCount: isMarkedRead ? { increment: 1 } : undefined,
|
||||||
|
sessionsCount: isNewSession ? { increment: 1 } : undefined,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Increment sessionsCount for a given day (called from manual session end). */
|
||||||
|
async incrementDailySessions(userId: string, activityDate: Date) {
|
||||||
|
const date = new Date(Date.UTC(activityDate.getUTCFullYear(), activityDate.getUTCMonth(), activityDate.getUTCDate()));
|
||||||
|
return this.prisma.dailyLearningActivity.upsert({
|
||||||
|
where: { userId_activityDate: { userId, activityDate: date } },
|
||||||
|
create: { userId, activityDate: date, sessionsCount: 1 },
|
||||||
|
update: { sessionsCount: { increment: 1 } },
|
||||||
|
}).catch(() => {});
|
||||||
|
}
|
||||||
|
|
||||||
/** Compute local date from timestamp and timezone offset. */
|
/** Compute local date from timestamp and timezone offset. */
|
||||||
private computeActivityDate(timestampMs: bigint, offsetMinutes: number | null): Date {
|
private computeActivityDate(timestampMs: bigint, offsetMinutes: number | null): Date {
|
||||||
const offsetMs = (offsetMinutes ?? 0) * 60 * 1000;
|
const offsetMs = (offsetMinutes ?? 0) * 60 * 1000;
|
||||||
|
|||||||
@ -1,9 +1,13 @@
|
|||||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||||
import { LearningSessionRepository } from './learning-session.repository';
|
import { LearningSessionRepository } from './learning-session.repository';
|
||||||
|
import { LearningActivityRepository } from '../learning-activity/learning-activity.repository';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class LearningSessionService {
|
export class LearningSessionService {
|
||||||
constructor(private readonly repository: LearningSessionRepository) {}
|
constructor(
|
||||||
|
private readonly repository: LearningSessionRepository,
|
||||||
|
private readonly activityRepo: LearningActivityRepository,
|
||||||
|
) {}
|
||||||
|
|
||||||
async start(userId: string, dto: any) {
|
async start(userId: string, dto: any) {
|
||||||
return this.repository.create(userId, dto);
|
return this.repository.create(userId, dto);
|
||||||
@ -12,6 +16,10 @@ export class LearningSessionService {
|
|||||||
async end(id: string) {
|
async end(id: string) {
|
||||||
const session = await this.repository.end(id);
|
const session = await this.repository.end(id);
|
||||||
if (!session) throw new NotFoundException('会话不存在');
|
if (!session) throw new NotFoundException('会话不存在');
|
||||||
|
// 更新 DailyLearningActivity 的会话计数
|
||||||
|
if (session.userId) {
|
||||||
|
this.activityRepo.incrementDailySessions(session.userId, session.startedAt ?? new Date());
|
||||||
|
}
|
||||||
return session;
|
return session;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -148,6 +148,7 @@ export class ReadingEventProcessorService {
|
|||||||
activeSecondsDelta: validated.activeSecondsDelta,
|
activeSecondsDelta: validated.activeSecondsDelta,
|
||||||
isNewMaterial: validated.eventType === 'material_opened',
|
isNewMaterial: validated.eventType === 'material_opened',
|
||||||
isMarkedRead: validated.eventType === 'marked_as_read',
|
isMarkedRead: validated.eventType === 'marked_as_read',
|
||||||
|
isNewSession: validated.eventType === 'material_opened',
|
||||||
});
|
});
|
||||||
|
|
||||||
// 5d. Write LearningRecord (first open / closed / marked read)
|
// 5d. Write LearningRecord (first open / closed / marked read)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user