import { Router } from 'express'; import { body, param } from 'express-validator'; import { PublishService } from '../services/PublishService.js'; import { taskQueueService } from '../services/TaskQueueService.js'; import { authenticate } from '../middleware/auth.js'; import { asyncHandler } from '../middleware/error.js'; import { validateRequest } from '../middleware/validate.js'; const router = Router(); const publishService = new PublishService(); router.use(authenticate); // 获取发布任务列表 router.get( '/', asyncHandler(async (req, res) => { const { page = 1, pageSize = 20, status } = req.query; const result = await publishService.getTasks(req.user!.userId, { page: Number(page), pageSize: Number(pageSize), status: status as string, }); res.json({ success: true, data: result }); }) ); // 获取任务详情 router.get( '/:id', [ param('id').isInt().withMessage('任务ID无效'), validateRequest, ], asyncHandler(async (req, res) => { const task = await publishService.getTaskById( req.user!.userId, Number(req.params.id) ); res.json({ success: true, data: task }); }) ); // 创建发布任务 router.post( '/', [ // 修复 #6069:videoPath 和 title 改为可选,具体校验由前端按平台要求完成 body('videoPath').optional({ nullable: true }).isString().withMessage('视频路径格式无效'), body('title').optional({ nullable: true }).isString().withMessage('标题格式无效'), body('targetAccounts').isArray({ min: 1 }).withMessage('至少选择一个目标账号'), body('publishProxy').optional({ nullable: true }).isObject().withMessage('发布代理配置无效'), validateRequest, ], asyncHandler(async (req, res) => { const userId = req.user!.userId; // 1. 创建数据库记录 const task = await publishService.createTask(userId, req.body); // 2. 如果不是定时任务,加入任务队列 if (!req.body.scheduledAt) { await taskQueueService.createTask(userId, { type: 'publish_video', title: `发布视频: ${req.body.title}`, description: `发布到 ${req.body.targetAccounts.length} 个账号`, data: { publishTaskId: task.id, }, }); } res.status(201).json({ success: true, data: task }); }) ); // 修改并重新发布(更新现有任务) router.put( '/:id', [ param('id').isInt().withMessage('任务ID无效'), body('targetAccounts').isArray({ min: 1 }).withMessage('至少选择一个目标账号'), body('publishProxy').optional({ nullable: true }).isObject().withMessage('发布代理配置无效'), validateRequest, ], asyncHandler(async (req, res) => { const userId = req.user!.userId; const taskId = Number(req.params.id); // 修复 Bug #6145:更新现有任务而非创建新任务 const task = await publishService.updateTask(userId, taskId, req.body); // 如果不是定时任务且状态允许,加入任务队列重新执行 if (!req.body.scheduledAt && task.status !== 'cancelled') { await taskQueueService.createTask(userId, { type: 'publish_video', title: `重新发布: ${task.title}`, description: `发布到 ${task.targetAccounts.length} 个账号`, data: { publishTaskId: task.id, }, }); } res.json({ success: true, data: task }); }) ); // 取消任务 router.post( '/:id/cancel', [ param('id').isInt().withMessage('任务ID无效'), validateRequest, ], asyncHandler(async (req, res) => { await publishService.cancelTask(req.user!.userId, Number(req.params.id)); res.json({ success: true, message: '任务已取消' }); }) ); // 重试任务 router.post( '/:id/retry', [ param('id').isInt().withMessage('任务ID无效'), validateRequest, ], asyncHandler(async (req, res) => { const userId = req.user!.userId; const taskId = Number(req.params.id); // 1. 更新数据库状态 const task = await publishService.retryTask(userId, taskId); // 2. 加入任务队列重新执行 await taskQueueService.createTask(userId, { type: 'publish_video', title: `重试发布: ${task.title}`, description: `重新发布到 ${task.targetAccounts.length} 个账号`, data: { publishTaskId: task.id, }, }); res.json({ success: true, data: task }); }) ); // 删除任务 router.delete( '/:id', [ param('id').isInt().withMessage('任务ID无效'), validateRequest, ], asyncHandler(async (req, res) => { await publishService.deleteTask(req.user!.userId, Number(req.params.id)); res.json({ success: true, message: '任务已删除' }); }) ); // 手动确认发布结果(如小红书"发布结果待确认"时用户确认已发布成功) router.post( '/:taskId/results/:resultId/confirm', [ param('taskId').isInt().withMessage('任务ID无效'), param('resultId').isInt().withMessage('结果ID无效'), validateRequest, ], asyncHandler(async (req, res) => { await publishService.confirmPublishResult( req.user!.userId, Number(req.params.taskId), Number(req.params.resultId) ); res.json({ success: true, message: '已确认发布成功' }); }) ); // 单账号有头浏览器重试发布(用于验证码场景) router.post( '/:taskId/retry-headful/:accountId', [ param('taskId').isInt().withMessage('任务ID无效'), param('accountId').isInt().withMessage('账号ID无效'), validateRequest, ], asyncHandler(async (req, res) => { const userId = req.user!.userId; const taskId = Number(req.params.taskId); const accountId = Number(req.params.accountId); // 调用服务层执行有头浏览器发布 const result = await publishService.retryAccountWithHeadfulBrowser( userId, taskId, accountId ); res.json({ success: true, data: result }); }) ); export default router;