feat(sync): cn event coverage, bootstrap and outbox sender
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -19,12 +19,9 @@
|
||||
// + entityVersion 自增 + 事务内 outbox 事件(P2 副本按 entityVersion 单调消费)
|
||||
// + audit。幂等前态(同值 PATCH / 重复 DELETE)直接回显当前记录,不产生
|
||||
// 第二次事件/审计(entityVersion 不动——副本侧按版本去重的不变量)。
|
||||
import { HttpStatus, Injectable } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import type { components } from '@photo/contracts';
|
||||
import { ErrorCodes } from '@photo/contracts';
|
||||
import type { PhotoPublishedPayload } from '@photo/contracts';
|
||||
import type { Prisma } from '@photo/db-cn';
|
||||
import { ApiException } from '../api-exception';
|
||||
import type { AuthUser } from '../auth/session.guard';
|
||||
import { PermissionsService } from '../albums/permissions.service';
|
||||
import {
|
||||
@@ -38,6 +35,7 @@ import {
|
||||
encodeKeysetCursor,
|
||||
keysetWhereDesc,
|
||||
} from '../shared/cursor';
|
||||
import { buildPhotoPublishedPayload } from '../sync/payloads';
|
||||
import type { PhotoVisibility, PhotoVisibilityTarget } from './dto';
|
||||
import {
|
||||
photoForbidden,
|
||||
@@ -49,9 +47,6 @@ export type PhotoRecord = components['schemas']['PhotoRecord'];
|
||||
export type AdminPhotoListResponse = components['schemas']['AdminPhotoList'];
|
||||
|
||||
type PhotoRow = Prisma.photosGetPayload<Prisma.photosDefaultArgs>;
|
||||
type PhotoVariantRow = Prisma.photo_variantsGetPayload<
|
||||
Prisma.photo_variantsDefaultArgs
|
||||
>;
|
||||
|
||||
function toPhotoRecord(row: PhotoRow): PhotoRecord {
|
||||
return {
|
||||
@@ -303,13 +298,17 @@ export class PhotosService {
|
||||
const variants = await tx.photo_variants.findMany({
|
||||
where: { photoId: photo.id },
|
||||
});
|
||||
const payload = this.publishedPayload(photo, variants, nextVersion);
|
||||
// 载荷构建与 bootstrap/reconcile 快照共用同一出口(sync/payloads.ts),
|
||||
// 保证两类事件逐字节同形;post-publish 不变量破坏时 5xx 回滚整个迁移。
|
||||
const payload = buildPhotoPublishedPayload(photo, variants, nextVersion);
|
||||
await tx.outbox_events.create({
|
||||
data: {
|
||||
type: 'photo.published',
|
||||
albumId: photo.albumId,
|
||||
photoId: photo.id,
|
||||
entityVersion: nextVersion,
|
||||
// P2 Task 3:恢复发布为常规优先级(显式声明,不依赖列默认)。
|
||||
priority: 'normal',
|
||||
// 接口类型无隐式索引签名,Prisma InputJsonValue 需显式收窄
|
||||
//(同 worker publish.service 先例)。
|
||||
payload: payload as unknown as Prisma.InputJsonValue,
|
||||
@@ -322,6 +321,10 @@ export class PhotosService {
|
||||
albumId: photo.albumId,
|
||||
photoId: photo.id,
|
||||
entityVersion: nextVersion,
|
||||
// P2 Task 3:隐藏/删除是「即刻从公开面撤下」的墓碑类事件——高优先级,
|
||||
// 发送器批次内优先投递(worker 首次发布的 photo.published 走列默认
|
||||
// normal,无需改动)。
|
||||
priority: 'high',
|
||||
payload: {
|
||||
photoId: photo.id,
|
||||
albumId: photo.albumId,
|
||||
@@ -367,43 +370,4 @@ export class PhotosService {
|
||||
}
|
||||
return toPhotoRecord(updatedRow);
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复发布的 outbox 载荷(契约 PhotoPublishedPayload 全集)。能走到「隐藏」
|
||||
* 前态的照片必经发布事务(变体行+尺寸+publishedAt 齐备);缺任一项属
|
||||
* post-publish 不变量破坏——5xx 回滚整个迁移(照片保持 hidden,不向 P2
|
||||
* 副本发出键为空的事件),交由人工介入而非带病发布。
|
||||
*/
|
||||
private publishedPayload(
|
||||
photo: PhotoRow,
|
||||
variants: PhotoVariantRow[],
|
||||
entityVersion: number,
|
||||
): PhotoPublishedPayload {
|
||||
const thumb = variants.find((v) => v.variant === 'thumb');
|
||||
const browse = variants.find((v) => v.variant === 'browse');
|
||||
if (
|
||||
thumb === undefined ||
|
||||
browse === undefined ||
|
||||
photo.width === null ||
|
||||
photo.height === null ||
|
||||
photo.publishedAt === null
|
||||
) {
|
||||
throw new ApiException(
|
||||
ErrorCodes.INTERNAL_ERROR,
|
||||
'Internal server error',
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
return {
|
||||
photoId: photo.id,
|
||||
albumId: photo.albumId,
|
||||
objectKey: photo.objectKey,
|
||||
thumbKey: thumb.objectKey,
|
||||
browseKey: browse.objectKey,
|
||||
width: photo.width,
|
||||
height: photo.height,
|
||||
publishedAt: photo.publishedAt.toISOString(),
|
||||
entityVersion,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user