Просмотр исходного кода

fix: 补全缺失的章节内容接口

新增4个接口解决前端调用404问题:
- GET  /books/:id/chapters/:chapterId/content  获取章节内容
- POST /books/:id/chapters/:chapterId/content 保存手工编辑内容
- POST /books/:id/chapters/batch-regenerate   批量重新生成
- POST /books/:id/chapters/batch-delete       批量删除

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
MyFramework User 4 месяцев назад
Родитель
Сommit
27b1f988c0
1 измененных файлов с 219 добавлено и 0 удалено
  1. 219 0
      server/src/modules/book-generator/langgraph-controller.ts

+ 219 - 0
server/src/modules/book-generator/langgraph-controller.ts

@@ -914,6 +914,225 @@ router.post('/books/:id/retry-chapter', async (ctx: Context) => {
   }
 });
 
+/**
+ * GET /api/book-generator/langgraph/books/:id/chapters/:chapterId/content
+ * 获取指定章节的内容
+ */
+router.get('/books/:id/chapters/:chapterId/content', optionalAuth, async (ctx: Context) => {
+  try {
+    const { id: bookId, chapterId } = ctx.params as { id: string; chapterId: string };
+    const chapterIdNum = parseInt(chapterId);
+
+    // 获取书籍
+    const book = await bookStore.getById(bookId);
+    if (!book) {
+      ctx.status = 404;
+      ctx.body = { code: 1, message: '书籍不存在' };
+      return;
+    }
+
+    // 获取章节
+    const chapter = await prisma.bookChapter.findUnique({
+      where: { id: chapterIdNum },
+    });
+    if (!chapter) {
+      ctx.status = 404;
+      ctx.body = { code: 1, message: '章节不存在' };
+      return;
+    }
+
+    // 检查是否属于该书籍
+    if (chapter.bookId !== parseInt(bookId)) {
+      ctx.status = 404;
+      ctx.body = { code: 1, message: '章节不属于该书籍' };
+      return;
+    }
+
+    ctx.body = {
+      code: 0,
+      message: 'success',
+      data: {
+        id: chapter.id,
+        bookId: chapter.bookId,
+        number: chapter.number,
+        title: chapter.title,
+        content: chapter.content || '',
+        wordCount: chapter.wordCount || 0,
+        status: chapter.status,
+        contentStatus: (chapter as any).contentStatus || 'pending',
+        level: chapter.level,
+        parentId: chapter.parentId,
+        summary: chapter.summary || '',
+        keyPoints: chapter.keyPoints ? JSON.parse(chapter.keyPoints) : [],
+        audioUrl: chapter.audioUrl || '',
+        videoUrl: chapter.videoUrl || '',
+        isPublic: chapter.isPublic || false,
+      },
+    };
+  } catch (error) {
+    console.error('获取章节内容失败:', error);
+    ctx.status = 500;
+    ctx.body = { code: 1, message: error instanceof Error ? error.message : '获取章节内容失败' };
+  }
+});
+
+/**
+ * POST /api/book-generator/langgraph/books/:id/chapters/:chapterId/content
+ * 保存章节内容(手工编辑后保存)
+ */
+router.post('/books/:id/chapters/:chapterId/content', optionalAuth, async (ctx: Context) => {
+  try {
+    const { id: bookId, chapterId } = ctx.params as { id: string; chapterId: string };
+    const chapterIdNum = parseInt(chapterId);
+    const { content } = ctx.request.body as { content: string };
+
+    if (content === undefined) {
+      ctx.status = 400;
+      ctx.body = { code: 1, message: '内容不能为空' };
+      return;
+    }
+
+    // 获取章节
+    const chapter = await prisma.bookChapter.findUnique({
+      where: { id: chapterIdNum },
+    });
+    if (!chapter) {
+      ctx.status = 404;
+      ctx.body = { code: 1, message: '章节不存在' };
+      return;
+    }
+
+    // 检查是否属于该书籍
+    if (chapter.bookId !== parseInt(bookId)) {
+      ctx.status = 404;
+      ctx.body = { code: 1, message: '章节不属于该书籍' };
+      return;
+    }
+
+    // 计算字数
+    const wordCount = content.replace(/<[^>]*>/g, '').length;
+
+    // 更新章节内容
+    await bookStore.updateChapterById(chapterIdNum, {
+      content,
+      wordCount,
+      contentStatus: 'completed',
+    });
+
+    ctx.body = {
+      code: 0,
+      message: '保存成功',
+      data: { chapterId: chapterIdNum, wordCount },
+    };
+  } catch (error) {
+    console.error('保存章节内容失败:', error);
+    ctx.status = 500;
+    ctx.body = { code: 1, message: error instanceof Error ? error.message : '保存章节内容失败' };
+  }
+});
+
+/**
+ * POST /api/book-generator/langgraph/books/:id/chapters/batch-regenerate
+ * 批量重新生成章节内容
+ */
+router.post('/books/:id/chapters/batch-regenerate', optionalAuth, async (ctx: Context) => {
+  try {
+    const { id: bookId } = ctx.params as { id: string };
+    const { chapterIds } = ctx.request.body as { chapterIds: number[] };
+
+    if (!chapterIds || !Array.isArray(chapterIds) || chapterIds.length === 0) {
+      ctx.status = 400;
+      ctx.body = { code: 1, message: '请选择要重新生成的章节' };
+      return;
+    }
+
+    // 获取书籍
+    const book = await bookStore.getById(bookId);
+    if (!book) {
+      ctx.status = 404;
+      ctx.body = { code: 1, message: '书籍不存在' };
+      return;
+    }
+
+    // 重置并触发每个选中章节的重新生成
+    for (const chapterId of chapterIds) {
+      await bookStore.updateChapterById(chapterId, {
+        status: 'pending',
+        contentStatus: 'pending',
+        content: null,
+      });
+      // 后台异步执行
+      bookStore.generateSingleChapterContent(bookId, chapterId).catch(err => {
+        console.error(`[BatchRegenerate] 章节${chapterId}内容生成失败:`, err);
+      });
+    }
+
+    ctx.body = {
+      code: 0,
+      message: `已提交 ${chapterIds.length} 个章节的重新生成任务`,
+      data: { count: chapterIds.length },
+    };
+  } catch (error) {
+    console.error('批量重新生成章节失败:', error);
+    ctx.status = 500;
+    ctx.body = { code: 1, message: error instanceof Error ? error.message : '批量重新生成失败' };
+  }
+});
+
+/**
+ * POST /api/book-generator/langgraph/books/:id/chapters/batch-delete
+ * 批量删除章节
+ */
+router.post('/books/:id/chapters/batch-delete', optionalAuth, async (ctx: Context) => {
+  try {
+    const { id: bookId } = ctx.params as { id: string };
+    const { chapterIds } = ctx.request.body as { chapterIds: number[] };
+
+    if (!chapterIds || !Array.isArray(chapterIds) || chapterIds.length === 0) {
+      ctx.status = 400;
+      ctx.body = { code: 1, message: '请选择要删除的章节' };
+      return;
+    }
+
+    // 获取书籍
+    const book = await bookStore.getById(bookId);
+    if (!book) {
+      ctx.status = 404;
+      ctx.body = { code: 1, message: '书籍不存在' };
+      return;
+    }
+
+    // 删除章节(同时会删除子章节,因为没有设置级联删除,需要手动处理)
+    // 对于每个要删除的章节,先删除其所有子节点,再删除自身
+    for (const chapterId of chapterIds) {
+      // 删除子章节(level > 当前章节)
+      await prisma.bookChapter.deleteMany({
+        where: {
+          bookId: parseInt(bookId),
+          parentId: chapterId,
+        },
+      });
+      // 删除自身
+      await prisma.bookChapter.deleteMany({
+        where: {
+          id: chapterId,
+          bookId: parseInt(bookId),
+        },
+      });
+    }
+
+    ctx.body = {
+      code: 0,
+      message: `已删除 ${chapterIds.length} 个章节`,
+      data: { count: chapterIds.length },
+    };
+  } catch (error) {
+    console.error('批量删除章节失败:', error);
+    ctx.status = 500;
+    ctx.body = { code: 1, message: error instanceof Error ? error.message : '批量删除失败' };
+  }
+});
+
 /**
  * POST /api/book-generator/langgraph/books/:id/chapters/:chapterId/regenerate
  * 重新生成指定章节的内容(仅叶节点允许)