/** * 视频生成模块 - API 路由控制器 */ import Router from '@koa/router'; import { requireAuth } from '../../middleware/auth'; import { createVideoProject, getVideoProjects, getVideoProject, updateVideoProject, deleteVideoProject, generateVideoForProject, getGenerateProgress, getMaterials, uploadMaterial, deleteMaterial, createVideoProjectFromBook, } from './video-generator.service'; const router = new Router(); // 测试用户兜底(与其他模块一致) const TEST_USER_ID = 1; function currentUserId(ctx: any): number { const uid = ctx.state.user?.userId; return uid ? parseInt(String(uid)) : TEST_USER_ID; } // ============ 视频项目管理 ============ /** * GET /api/video/projects * 获取视频项目列表(只返回当前用户的项目) */ router.get('/projects', requireAuth, async (ctx) => { const query = { userId: currentUserId(ctx), // 强制用当前登录用户,忽略客户端传入的 userId status: ctx.query.status as any, page: ctx.query.page ? Number(ctx.query.page) : 1, pageSize: ctx.query.pageSize ? Number(ctx.query.pageSize) : 10, }; const result = await getVideoProjects(query); ctx.body = { success: true, data: result }; }); /** * POST /api/video/projects * 创建视频项目 */ router.post('/projects', requireAuth, async (ctx) => { const body = ctx.request.body as any; const userId = currentUserId(ctx); const project = await createVideoProject(body, userId); ctx.body = { success: true, data: project }; }); /** * GET /api/video/projects/:id * 获取视频项目详情 */ router.get('/projects/:id', requireAuth, async (ctx) => { const id = Number(ctx.params.id); const project = await getVideoProject(id, currentUserId(ctx)); if (!project) { ctx.status = 404; ctx.body = { success: false, error: '项目不存在' }; return; } ctx.body = { success: true, data: project }; }); /** * PUT /api/video/projects/:id * 更新视频项目 */ router.put('/projects/:id', requireAuth, async (ctx) => { const id = Number(ctx.params.id); const body = ctx.request.body as any; const project = await updateVideoProject(id, body, currentUserId(ctx)); if (!project) { ctx.status = 404; ctx.body = { success: false, error: '项目不存在' }; return; } ctx.body = { success: true, data: project }; }); /** * DELETE /api/video/projects/:id * 删除视频项目 */ router.delete('/projects/:id', requireAuth, async (ctx) => { const id = Number(ctx.params.id); const success = await deleteVideoProject(id, currentUserId(ctx)); if (!success) { ctx.status = 404; ctx.body = { success: false, error: '项目不存在' }; return; } ctx.body = { success: true }; }); // ============ 视频生成 ============ /** * POST /api/video/projects/:id/generate * 开始生成视频 */ router.post('/projects/:id/generate', requireAuth, async (ctx) => { const id = Number(ctx.params.id); const result = await generateVideoForProject(id, currentUserId(ctx)); if (!result.success) { ctx.status = 400; ctx.body = { success: false, error: result.error }; return; } ctx.body = { success: true, data: { outputUrl: result.outputUrl, duration: result.duration, fileSize: result.fileSize, }, }; }); /** * GET /api/video/projects/:id/status * 获取生成状态 */ router.get('/projects/:id/status', requireAuth, async (ctx) => { const id = Number(ctx.params.id); const status = await getGenerateProgress(id, currentUserId(ctx)); ctx.body = { success: true, data: status }; }); // ============ 素材管理 ============ /** * GET /api/video/materials * 获取素材列表 */ router.get('/materials', requireAuth, async (ctx) => { const query = { userId: currentUserId(ctx), type: ctx.query.type as any, category: ctx.query.category as string, page: ctx.query.page ? Number(ctx.query.page) : 1, pageSize: ctx.query.pageSize ? Number(ctx.query.pageSize) : 20, }; const result = await getMaterials(query); ctx.body = { success: true, data: result }; }); /** * POST /api/video/materials/upload * 上传素材(处理 multipart/form-data 文件上传) */ router.post('/materials/upload', requireAuth, async (ctx) => { const userId = currentUserId(ctx); // 处理 multipart form data const body = ctx.request.body as any; const files = (ctx.request as any).files as any; // 获取上传的文件 const file = files?.file; let materialUrl = ''; if (file) { // 文件已上传,url 就是文件的路径 materialUrl = `/uploads/materials/${file.newFilename || file.filename}`; } else if (body.url) { // 如果没有文件,使用传入的url materialUrl = body.url; } else { ctx.status = 400; ctx.body = { success: false, error: 'No file uploaded' }; return; } const material = await uploadMaterial({ type: body.type || 'image', name: body.name || file?.newFilename || 'unnamed', url: materialUrl, thumbnail: body.thumbnail, tags: body.tags ? JSON.parse(body.tags) : [], category: body.category, duration: body.duration ? Number(body.duration) : undefined, size: body.size ? Number(body.size) : undefined, width: body.width ? Number(body.width) : undefined, height: body.height ? Number(body.height) : undefined, }, userId); ctx.body = { success: true, data: material }; }); /** * DELETE /api/video/materials/:id * 删除素材 */ router.delete('/materials/:id', requireAuth, async (ctx) => { const id = Number(ctx.params.id); const success = await deleteMaterial(id); if (!success) { ctx.status = 404; ctx.body = { success: false, error: '素材不存在' }; return; } ctx.body = { success: true }; }); // ============ 快捷入口 ============ /** * POST /api/video/books/:bookId/generate * 从书籍生成视频项目 */ router.post('/books/:bookId/generate', requireAuth, async (ctx) => { const bookId = Number(ctx.params.bookId); const userId = currentUserId(ctx); const project = await createVideoProjectFromBook(bookId, undefined, userId); if (!project) { ctx.status = 404; ctx.body = { success: false, error: '书籍不存在' }; return; } ctx.body = { success: true, data: project }; }); export default router;