feat(sync): ov inbox ad 分支与 sender adId 推导(disabled/墓碑=删投影行)
- dto.ts SYNC_ENTITY_TYPES 追加 'ad'(对齐契约 SyncEntityType)。 - decideAndApply ③依赖检查豁免 ad:albumId 恒空串,不豁免则事件被永久 暂存且无解锁路径(slot-state 同款空 albumId 危害)。 - project() 新增 ad 分支:全量快照直投 ads_ov;墓碑或 enabled=false 均 deleteMany(disabled=公开不可见=无投影);albumIds 整体替换;createdAt 取 cn 原值、updatedAt 为 ov 应用时刻(载荷无该字段)。 - sender.toWireEvent 新增 ad 分支:entityId 取载荷 adId(outbox 表无列), 泛化 categoryId/assetId 兜底之前,缺料 fail-fast。 - e2e ads.e2e-spec:手工构造 outbox 行(绕过 CRUD,Task 7 才有)走真实 sender→HTTP→inbox 链路,5 用例覆盖快照落位/disable 删行/复活 upsert/ 迟到旧版本不回滚/墓碑删行;reset-ov 增清 ads_ov。 - db-ov schema 附带注释修:ads_ov updatedAt 语义纠正(迁移头过度声明, 迁移文件不动防 checksum 漂移)+ sync_inbox.entityType 枚举补 ad。 Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -27,6 +27,7 @@ export const SYNC_ENTITY_TYPES = [
|
||||
'photo',
|
||||
'asset',
|
||||
'slot-state',
|
||||
'ad',
|
||||
] as const;
|
||||
|
||||
/** 契约 SyncPriority。 */
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
// (同事件重投递走 ① 的 eventId 幂等锚,根本到不了版本比较);
|
||||
// ③ 依赖缺失暂存:非 album 事件在 albums_ov 无所属相册行时保持 appliedAt
|
||||
// null(暂存不 applied,不投影);album 事件应用后按 albumId 回扫暂存
|
||||
// 队列补应用(相册到达即解锁该相册全部暂存事件);
|
||||
// 队列补应用(相册到达即解锁该相册全部暂存事件);slot-state/ad 豁免
|
||||
// (albumPublicToken 弱关联 / 无相册依赖,见 decideAndApply ③ 注);
|
||||
// ④ 应用投影(版本不低于已应用):album → albums_ov upsert(不触碰
|
||||
// bannerAssetKey/emergencyDisabled——分别归 asset.updated / 紧急停用通道
|
||||
// Task 5;categories 仅当载荷携带全量清单(bootstrap/reconcile 快照事件)
|
||||
@@ -23,6 +24,8 @@
|
||||
// 即撤下语义);asset → banner 投影 albums_ov.bannerAssetKey(cover/logo
|
||||
// 仅推进版本,ov schema 无位);slot-state(P4b Task 2)→ embed_slots_ov
|
||||
// 全量快照直投(albumPublicToken 弱关联相册、无依赖暂存,见 ③ 注);
|
||||
// ad(广告位 2026-09-28)→ ads_ov 全量快照直投,墓碑或 enabled=false 均
|
||||
// 删行(disabled=公开不可见=无投影);
|
||||
// ⑤ sync_state upsert appliedVersion(watermark=false 实体行;水位行预留)。
|
||||
// ⑥ 实时转发(Task 7):photo 事件**应用事务提交后**发布 OV_PHOTO_APPLIED
|
||||
// 进程内事件 → realtime 网关向相册房间广播契约 photo.* 消息(stale/
|
||||
@@ -35,6 +38,7 @@ import { Injectable } from '@nestjs/common';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import type { components } from '@photo/contracts';
|
||||
import type {
|
||||
AdUpdatedPayload,
|
||||
AlbumUpdatedPayload,
|
||||
AssetUpdatedPayload,
|
||||
CategoryDeletedPayload,
|
||||
@@ -173,8 +177,14 @@ export class SyncInboxService {
|
||||
// ③ 依赖检查:非 album 事件须所属相册副本已存在,否则暂存。slot-state
|
||||
// 豁免:展示位副本以 albumPublicToken 弱关联相册(公开读时按 albums_ov
|
||||
// 现查,缺席=404 与相册下线同形),且解绑事件 albumId 为空串——相册行的
|
||||
// 到达顺序对副本正确性无影响,暂存反而会让解绑事件无限积压。
|
||||
if (event.entityType !== 'album' && event.entityType !== 'slot-state') {
|
||||
// 到达顺序对副本正确性无影响,暂存反而会让解绑事件无限积压。ad 豁免:
|
||||
// ad 无相册依赖(albumId 恒空串,绑定关系在载荷 albumIds),直接应用
|
||||
// ——不豁免则空串在 albums_ov 永远查不到,事件被永久暂存且无解锁路径。
|
||||
if (
|
||||
event.entityType !== 'album' &&
|
||||
event.entityType !== 'slot-state' &&
|
||||
event.entityType !== 'ad'
|
||||
) {
|
||||
const album = await tx.albums_ov.findUnique({
|
||||
where: { albumId: event.albumId },
|
||||
select: { albumId: true },
|
||||
@@ -436,6 +446,40 @@ export class SyncInboxService {
|
||||
});
|
||||
return undefined;
|
||||
}
|
||||
// ad(广告位 2026-09-28):全量快照直投 ads_ov;墓碑或 enabled=false 快照
|
||||
// 均表达为删行(disabled=公开不可见=无投影,设计 §2.2/§4)。albumIds
|
||||
// 整体替换;createdAt 取 cn 原值(排序 tie-break)。
|
||||
if (event.entityType === 'ad') {
|
||||
const p = event.payload as unknown as AdUpdatedPayload;
|
||||
if (event.tombstone === true || p.enabled === false) {
|
||||
await tx.ads_ov.deleteMany({ where: { adId: event.entityId } });
|
||||
} else {
|
||||
await tx.ads_ov.upsert({
|
||||
where: { adId: event.entityId },
|
||||
create: {
|
||||
adId: event.entityId,
|
||||
imageObjectKey: p.imageObjectKey,
|
||||
linkUrl: p.linkUrl,
|
||||
sortOrder: p.sortOrder,
|
||||
albumIds: p.albumIds as unknown as Prisma.InputJsonValue,
|
||||
entityVersion: event.entityVersion,
|
||||
createdAt: new Date(p.createdAt),
|
||||
// ov 应用时刻(载荷无 updatedAt——投影行该列非 cn 原值)。
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
update: {
|
||||
imageObjectKey: p.imageObjectKey,
|
||||
linkUrl: p.linkUrl,
|
||||
sortOrder: p.sortOrder,
|
||||
albumIds: p.albumIds as unknown as Prisma.InputJsonValue,
|
||||
entityVersion: event.entityVersion,
|
||||
createdAt: new Date(p.createdAt),
|
||||
updatedAt: new Date(),
|
||||
},
|
||||
});
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user