fix: Admin Retry 幂等键从 timestamp 改为 operationId
All checks were successful
Deploy API Server / build-and-unit (push) Successful in 34s
Deploy API Server / current-integration (push) Successful in 30s
Deploy API Server / backward-compat (push) Successful in 0s
Deploy API Server / m-ai-03-synthetic-e2e (push) Successful in 9s
Deploy API Server / deploy (push) Successful in 1m1s
All checks were successful
Deploy API Server / build-and-unit (push) Successful in 34s
Deploy API Server / current-integration (push) Successful in 30s
Deploy API Server / backward-compat (push) Successful in 0s
Deploy API Server / m-ai-03-synthetic-e2e (push) Successful in 9s
Deploy API Server / deploy (push) Successful in 1m1s
- retryJob 新增 operationId 参数(必填,调用方提供稳定操作标识) - 幂等键: admin-retry:<jobId>:<operationId>(同 operationId → 同 Job) - Admin Controller: operationId 校验(1-128 字符,alphanumeric + hyphen/underscore) - 不同 operationId 允许管理员多次独立重试同一失败 Job Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
d0f75531ab
commit
6962db3f1a
@ -1,4 +1,4 @@
|
||||
import { Controller, Get, Post, Param, Query, Body, UseGuards } from '@nestjs/common';
|
||||
import { Controller, Get, Post, Param, Query, Body, UseGuards, BadRequestException } from '@nestjs/common';
|
||||
import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger';
|
||||
import { InjectQueue } from '@nestjs/bullmq';
|
||||
import { Queue } from 'bullmq';
|
||||
@ -47,8 +47,14 @@ export class AiJobAdminController {
|
||||
@Post(':jobId/retry')
|
||||
@AdminRoles('SUPER_ADMIN' as AdminRole)
|
||||
@ApiOperation({ summary: 'Admin 重试(创建新 Job,parentJobId 关联原 Job)' })
|
||||
async retry(@Param('jobId') jobId: string) {
|
||||
return this.service.retryJob('admin', jobId);
|
||||
async retry(@Param('jobId') jobId: string, @Query('operationId') operationId: string) {
|
||||
if (!operationId || operationId.trim().length === 0) {
|
||||
throw new BadRequestException('operationId is required for Admin retry');
|
||||
}
|
||||
if (operationId.length > 128 || !operationId.match(/^[a-zA-Z0-9_-]+$/)) {
|
||||
throw new BadRequestException('operationId must be 1-128 alphanumeric chars, hyphens, or underscores');
|
||||
}
|
||||
return this.service.retryJob('admin', jobId, operationId.trim());
|
||||
}
|
||||
|
||||
@Get('queues/status')
|
||||
|
||||
@ -139,7 +139,7 @@ export class AiJobService {
|
||||
};
|
||||
}
|
||||
|
||||
async retryJob(adminUserId: string, jobId: string) {
|
||||
async retryJob(adminUserId: string, jobId: string, operationId: string) {
|
||||
// 1. Load original Job
|
||||
const original = await this.prisma.aiJob.findUnique({
|
||||
where: { id: jobId },
|
||||
@ -163,9 +163,10 @@ export class AiJobService {
|
||||
throw new ConflictException('Original job missing targetType/targetId — cannot retry');
|
||||
}
|
||||
|
||||
// 5. Idempotency key: allows same admin operation to return same retry job,
|
||||
// but different retry operations produce different jobs
|
||||
const retryIdempotencyKey = `admin-retry:${jobId}:${Date.now()}`;
|
||||
// 5. Stable idempotency key using caller-provided operationId.
|
||||
// Same (jobId, operationId) → same retry Job (幂等).
|
||||
// Different operationId → different retry Job (允许管理员多次独立重试).
|
||||
const retryIdempotencyKey = `admin-retry:${jobId}:${operationId}`;
|
||||
|
||||
// 6. Delegate to AiJobCreationService (Registry + Snapshot + Outbox + atomic transaction)
|
||||
const newJob = await this.creationService.createJob({
|
||||
@ -185,7 +186,7 @@ export class AiJobService {
|
||||
|
||||
this.logger.log(
|
||||
`Admin retry: original=${jobId} → new=${newJob.id} ` +
|
||||
`type=${newJob.jobType} parentJobId=${original.id}`,
|
||||
`operationId=${operationId} parentJobId=${original.id}`,
|
||||
);
|
||||
|
||||
return this.toAdminResponse(newJob);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user