feat(sync): cn event coverage, bootstrap and outbox sender
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -29,8 +29,10 @@
|
||||
// presign 为 SigV4 本地计算,无网络副作用。
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import type { components } from '@photo/contracts';
|
||||
import type { Prisma } from '@photo/db-cn';
|
||||
import type { StoragePort } from '@photo/storage';
|
||||
import type { AuthUser } from '../auth/session.guard';
|
||||
import { buildAssetUpdatedPayload } from '../sync/payloads';
|
||||
import { STORAGE_PORT } from '../uploads/storage.provider';
|
||||
import {
|
||||
assetsObjectKey,
|
||||
@@ -164,13 +166,28 @@ export class AssetsService {
|
||||
);
|
||||
}
|
||||
|
||||
// ② 事务:条件更新抢占(并发 complete 恰一人胜出)+ 胜者独占 audit。
|
||||
// ② 事务:条件更新抢占(并发 complete 恰一人胜出,state pending→effective
|
||||
// 迁移同时自增 entityVersion 并写 outbox asset.updated——P2 Task 3 起仅
|
||||
// 生效迁移产生事件,幂等重复 complete 不再发)+ 胜者独占 audit。
|
||||
await this.prisma.$transaction(async (tx) => {
|
||||
const race = await tx.album_assets.updateMany({
|
||||
where: { id: asset.id, state: 'pending' },
|
||||
data: { state: 'effective' },
|
||||
data: { state: 'effective', entityVersion: { increment: 1 } },
|
||||
});
|
||||
if (race.count === 1) {
|
||||
const effective = await tx.album_assets.findUniqueOrThrow({
|
||||
where: { id: asset.id },
|
||||
});
|
||||
await tx.outbox_events.create({
|
||||
data: {
|
||||
type: 'asset.updated',
|
||||
albumId,
|
||||
entityVersion: effective.entityVersion,
|
||||
payload: buildAssetUpdatedPayload(
|
||||
effective,
|
||||
) as unknown as Prisma.InputJsonValue,
|
||||
},
|
||||
});
|
||||
await tx.audit_logs.create({
|
||||
data: {
|
||||
actorId: user.id,
|
||||
|
||||
Reference in New Issue
Block a user