Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 67x 67x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 16x 16x 16x 16x 16x 16x 16x 13x 13x 13x 16x 15x 15x 15x 16x 16x 14x 14x 14x 14x 14x 14x 14x 2x 2x 2x 2x 2x 12x 12x 12x 12x 12x 14x 3x 3x 3x 3x 3x 3x 2x 2x 2x 3x 1x 1x 1x 14x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 2x 2x 2x 2x 3x 3x 5x 5x 5x 5x 5x 5x 5x 5x 5x 4x | /**
* 状态系统 - 阶段管理器
* 线性阶段模型:统一的状态字段 + 阶段索引比较
*/
import { prisma } from '../../models';
import { ChapterGenStage, BookGenStage } from './book-generator.types';
// 重新导出通用类型(统一从 book-generator.types.ts 获取定义)
export type { ChapterGenStage, BookGenStage };
// ============ 阶段索引(用于比较大小) ============
const CHAPTER_STAGE_ORDER: ChapterGenStage[] = [
'idle', // 0
'outline_completed', // 1
'content_generating', // 2
'content_completed', // 3
'audio_generating', // 4
'audio_completed', // 5
'video_generating', // 6
'video_completed', // 7
'failed', // 8(终态)
];
export function chapterStageIndex(s: ChapterGenStage): number {
return CHAPTER_STAGE_ORDER.indexOf(s);
}
// ============ 章节转移矩阵 ============
const CHAPTER_VALID_TRANSITIONS: Record<ChapterGenStage, ChapterGenStage[]> = {
'idle': ['content_generating', 'failed'],
'outline_completed': ['content_generating', 'failed'],
'content_generating': ['content_completed', 'failed'],
'content_completed': ['audio_generating', 'audio_completed', 'content_generating', 'failed'],
'audio_generating': ['audio_completed', 'content_completed', 'failed'], // 允许音频失败回退到 content_completed
'audio_completed': ['video_generating', 'audio_generating', 'content_generating', 'failed'],
'video_generating': ['video_completed', 'failed'],
'video_completed': ['video_generating', 'audio_generating', 'content_generating', 'failed'],
'failed': ['content_generating', 'audio_generating', 'video_generating', 'idle'],
};
// ============ 资源清理规则 ============
/**
* 判断本次章节转移是否需要清理下游资源。
* 如果目标阶段比某资源的阶段索引更小,则清除该资源。
*/
export function getCleanupForChapterRegression(
targetStage: ChapterGenStage,
): { audioUrl?: null; audioDuration?: number; videoUrl?: null; videoDuration?: number | null } {
const targetIdx = chapterStageIndex(targetStage);
const audioStageIdx = chapterStageIndex('audio_completed'); // 4
const videoStageIdx = chapterStageIndex('video_completed'); // 6
const clean: any = {};
if (targetIdx < audioStageIdx) {
clean.audioUrl = null;
clean.audioDuration = 0;
}
if (targetIdx < videoStageIdx) {
clean.videoUrl = null;
clean.videoDuration = null;
}
return clean;
}
// ============ 安全转移函数 ============
/**
* 安全转移章节状态
* 验证转移矩阵 + 乐观锁 + 资源清理
*/
export async function safeTransitionChapter(
chapterId: number,
currentStage: ChapterGenStage,
targetStage: ChapterGenStage,
): Promise<void> {
// ① 验证是否在转移矩阵中
const allowed = CHAPTER_VALID_TRANSITIONS[currentStage];
if (!allowed || !allowed.includes(targetStage)) {
throw new Error(
`非法状态转移:Chapter ${chapterId} 当前 ${currentStage},` +
`不允许转到 ${targetStage}。允许的转移:[${allowed?.join(', ') || '无'}]`
);
}
// ② 判断是否需要清理下游资源
const cleanData = getCleanupForChapterRegression(targetStage);
// ③ 乐观锁写入
const result = await prisma.bookChapter.updateMany({
where: { id: chapterId, genStage: currentStage },
data: { genStage: targetStage, ...cleanData },
});
if (result.count === 0) {
// 如果状态已经被其他操作改变,查询当前实际状态
const currentChapter = await prisma.bookChapter.findUnique({
where: { id: chapterId },
select: { genStage: true }
});
const actualStage = currentChapter?.genStage || 'unknown';
// 如果目标阶段和实际阶段相同,说明已经转移过了,这不是错误
if (actualStage === targetStage) {
console.log(`[safeTransition] 章节 ${chapterId} 已处于 ${targetStage},跳过`);
return;
}
// 如果是回退到 failed 状态(可能之前已经失败了),也跳过
if (currentStage === 'failed' && targetStage !== 'idle') {
console.log(`[safeTransition] 章节 ${chapterId} 已在 failed 状态,跳过转移到 ${targetStage}`);
return;
}
// 其他情况:记录警告但不崩溃
console.warn(`[safeTransition] 章节 ${chapterId} 状态冲突:预期 ${currentStage},实际 ${actualStage},目标 ${targetStage}`);
return;
}
}
/**
* 安全转移书籍状态
*/
// ============ 上层封装 ============
/**
* 前进:推进到下一个阶段。只能前进不能回退。
* 例:content_completed → audio_generating
*/
export async function advanceChapter(
chapterId: number,
targetStage: ChapterGenStage,
): Promise<void> {
const chapter = await prisma.bookChapter.findUniqueOrThrow({
where: { id: chapterId },
});
const cur = chapter.genStage as ChapterGenStage;
const curIdx = chapterStageIndex(cur);
const tgtIdx = chapterStageIndex(targetStage);
if (tgtIdx <= curIdx) {
// 不允许回退或同级跳转(由 regenerateChapter 处理)
throw new Error(
`advanceChapter 不能用于回退:当前 ${cur},目标 ${targetStage}。` +
`请使用 regenerateChapter()`
);
}
await safeTransitionChapter(chapterId, cur, targetStage);
}
/**
* 重新生成:回退到上游阶段(或同级 generating 状态)以重新生成。
* 自动清除下游资源。
* 例:audio_completed → audio_generating(重新生成音频)
* audio_completed → content_generating(从内容重新开始)
* failed → audio_generating(失败重试)
*/
export async function regenerateChapter(
chapterId: number,
targetStage: ChapterGenStage,
): Promise<void> {
const chapter = await prisma.bookChapter.findUniqueOrThrow({
where: { id: chapterId },
});
const cur = chapter.genStage as ChapterGenStage;
await safeTransitionChapter(chapterId, cur, targetStage);
}
// ============ Book 联动回退逻辑 ============
|