import { Injectable } from '@nestjs/common'; import { PrismaService } from '../../infrastructure/database/prisma.service'; import { Prisma } from '@prisma/client'; export const ARTIFACT_TYPES = [ 'analysis_result', 'focus_item', 'review_card', 'quiz', 'learning_analysis', 'recommendation', 'knowledge_import', ] as const; export type ArtifactType = (typeof ARTIFACT_TYPES)[number]; export interface CreateArtifactInput { jobId: string; artifactType: ArtifactType; artifactId: string; ordinal?: number; metadata?: Record; } /** * M-AI-02-06: Job 产物关联 Repository * * 多态引用:artifactType + artifactId 关联业务表,不设 FK 到各类型表。 * 唯一约束 (jobId, artifactType, artifactId) 保证幂等。 */ @Injectable() export class AiJobArtifactRepository { constructor(private readonly prisma: PrismaService) {} /** * 幂等创建:若已存在同 (jobId, artifactType, artifactId) 的记录则跳过。 * 使用 Prisma create + skip-duplicate 语义(Prisma 不支持 INSERT IGNORE MySQL 语法, * 但 Prisma 的唯一约束错误可通过 try/catch 转为幂等)。 */ async create(input: CreateArtifactInput) { try { return await this.prisma.aiJobArtifact.create({ data: { jobId: input.jobId, artifactType: input.artifactType, artifactId: input.artifactId, ordinal: input.ordinal ?? 0, metadata: input.metadata ?? undefined, }, }); } catch (error) { if (error instanceof Prisma.PrismaClientKnownRequestError && error.code === 'P2002') { // 唯一约束冲突 = 已存在,幂等返回已有记录 const existing = await this.prisma.aiJobArtifact.findUnique({ where: { jobId_artifactType_artifactId: { jobId: input.jobId, artifactType: input.artifactType, artifactId: input.artifactId, }, }, }); if (existing) return existing; // 极边缘情况:P2002 和 findUnique 之间父 Job 被 CASCADE 删除。 // 此时记录已不存在,重试 create 一次。 return this.prisma.aiJobArtifact.create({ data: { jobId: input.jobId, artifactType: input.artifactType, artifactId: input.artifactId, ordinal: input.ordinal ?? 0, metadata: input.metadata ?? undefined, }, }); } throw error; } } /** 批量幂等创建(逐个 try/catch,小批量场景适用) */ async createMany(inputs: CreateArtifactInput[]) { const results: Awaited>[] = []; for (const input of inputs) { results.push(await this.create(input)); } return results; } /** 按 Job 查询所有产物,按 ordinal 排序 */ async findByJobId(jobId: string) { return this.prisma.aiJobArtifact.findMany({ where: { jobId }, orderBy: { ordinal: 'asc' }, }); } /** * 反向查询:按产物查找关联的 Job。 * 通过 (artifactType, artifactId) 复合索引高效查询。 */ async findJobsByArtifact(artifactType: ArtifactType, artifactId: string) { const artifacts = await this.prisma.aiJobArtifact.findMany({ where: { artifactType, artifactId }, select: { jobId: true, ordinal: true, createdAt: true }, orderBy: { createdAt: 'asc' }, }); return artifacts; } }