feat(ov): windowed dedup visit counting with retention cleanup
Task 8 (P2): POST /public/albums/{token}/visits with visit_sessions
window dedup (VISIT_WINDOW_MINUTES), requestId idempotent replay
(echoes stored outcome; expired beyond VISIT_REQUEST_TTL_MINUTES
rejected), album_stats viewCount/statVersion authority + outbox_stat_events
views snapshots, WS stats.updated broadcast, visitorId+IP fixed-window
rate limit (VISIT_WINDOW_REQUESTS), and ov CleanupModule scheduler
(setInterval, CLEANUP_INTERVAL_MINUTES) deleting like_batches /
visit_requests / visit_sessions past their windows — totals never
touched. Contract prose aligned to replay semantics; constants
VISIT_WINDOW_REQUESTS / CLEANUP_INTERVAL_MINUTES added.
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
// services/api/src/realtime/ov-events.ts —— ov 侧「业务提交 → 实时广播」的
|
||||
// 进程内事件契约(P2 Task 7)。点赞端点(interactions)与收件箱应用
|
||||
// (sync-inbox)在**事务提交后**发布这些事件;WS 网关(realtime.gateway)
|
||||
// 经 @OnEvent 监听并广播给相册房间。事件名/载荷只在本进程内有效。
|
||||
// 进程内事件契约(P2 Task 7;Task 8 增浏览计数)。点赞端点(interactions)、
|
||||
// 浏览计数(visits)与收件箱应用(sync-inbox)在**事务提交后**发布这些事件;
|
||||
// WS 网关(realtime.gateway)经 @OnEvent 监听并广播给相册房间。事件名/载荷
|
||||
// 只在本进程内有效。
|
||||
//
|
||||
// 单进程假设(登记偏差,P5 移交):EventEmitter2 是进程内总线——ov 入口
|
||||
// 当前单实例部署(compose 仿真),多实例横向扩展需换 Redis pub/sub 跨进程
|
||||
@@ -24,6 +25,16 @@ export interface LikeBatchCommittedEvent {
|
||||
sessionId: string;
|
||||
}
|
||||
|
||||
/** 浏览已计入(visits 事务提交后发布;网关原样转 stats.updated)。 */
|
||||
export const OV_VISIT_COUNTED = 'ov.visit.counted';
|
||||
|
||||
export interface VisitCountedEvent {
|
||||
albumId: string;
|
||||
/** 提交事务后的权威累计浏览与统计版本(stats.updated 的 viewCount/countVersion)。 */
|
||||
viewCount: number;
|
||||
statVersion: number;
|
||||
}
|
||||
|
||||
/** 副本照片可见性已应用(sync-inbox 事务提交后发布;网关原样转 photo.*)。 */
|
||||
export const OV_PHOTO_APPLIED = 'ov.photo.applied';
|
||||
|
||||
|
||||
@@ -44,8 +44,10 @@ import { isAlbumVisibleOv } from '../shared/album-visibility';
|
||||
import {
|
||||
OV_LIKE_BATCH_COMMITTED,
|
||||
OV_PHOTO_APPLIED,
|
||||
OV_VISIT_COUNTED,
|
||||
type LikeBatchCommittedEvent,
|
||||
type PhotoAppliedEvent,
|
||||
type VisitCountedEvent,
|
||||
} from './ov-events';
|
||||
|
||||
/** likes.burst 合并窗口毫秒(惰性读 env,0=逐批即时广播;e2e 可调)。 */
|
||||
@@ -227,6 +229,17 @@ export class RealtimeGateway implements OnModuleDestroy {
|
||||
pending.originBatchIds.push(event.batchId);
|
||||
}
|
||||
|
||||
/** 浏览已计入 → 原样转发契约 stats.updated 消息到相册房间(Task 8)。 */
|
||||
@OnEvent(OV_VISIT_COUNTED)
|
||||
onVisitCounted(event: VisitCountedEvent): void {
|
||||
this.sendToRoom(event.albumId, {
|
||||
type: 'stats.updated',
|
||||
albumId: event.albumId,
|
||||
viewCount: event.viewCount,
|
||||
countVersion: event.statVersion,
|
||||
});
|
||||
}
|
||||
|
||||
/** 副本照片可见性应用 → 原样转发契约 photo.* 消息到相册房间。 */
|
||||
@OnEvent(OV_PHOTO_APPLIED)
|
||||
onPhotoApplied(event: PhotoAppliedEvent): void {
|
||||
|
||||
Reference in New Issue
Block a user