Jelajahi Sumber

抖音作品同步

Ethanfly 2 hari lalu
induk
melakukan
ac62cb7b28

+ 7 - 3
client/src/stores/taskQueue.ts

@@ -465,12 +465,16 @@ export const useTaskQueueStore = defineStore('taskQueue', () => {
     });
   }
 
-  // 快捷方法:创建同步作品任务
-  async function syncWorks(accountId?: number, accountName?: string) {
+  // 快捷方法:创建同步作品任务(按账号、按平台或同步全部)
+  async function syncWorks(accountId?: number, accountName?: string, platform?: string, platformName?: string) {
+    let title = '同步所有作品';
+    if (accountName) title = `同步作品 - ${accountName}`;
+    else if (platformName) title = `同步作品 - ${platformName}`;
     return createTask({
       type: 'sync_works',
-      title: accountName ? `同步作品 - ${accountName}` : '同步所有作品',
+      title,
       accountId,
+      platform,
     });
   }
 

+ 13 - 5
client/src/views/Works/index.vue

@@ -559,12 +559,20 @@ async function refreshAllWorks() {
   
   refreshing.value = true;
   try {
-    // 使用任务队列
-    await taskStore.syncWorks();
-    ElMessage.success('作品同步任务已创建,请在任务队列中查看进度');
-    // 打开任务队列弹框
+    // 根据筛选:选了账号只同步该账号;只选了平台则只同步该平台;都没选则同步所有
+    const accountId = filter.accountId;
+    const platform = filter.platform || undefined;
+    const account = accountId ? accounts.value.find((a) => a.id === accountId) : undefined;
+    const accountName = account ? `${account.accountName || '账号'} (${getPlatformName(account.platform as PlatformType)})` : undefined;
+    const platformName = platform ? getPlatformName(platform) : undefined;
+    await taskStore.syncWorks(accountId, accountName, platform, platformName);
+    const msg = accountId
+      ? '已创建同步任务,仅同步当前选中账号'
+      : platform
+        ? `已创建同步任务,仅同步「${platformName}」平台账号`
+        : '已创建同步任务,将同步所有账号';
+    ElMessage.success(msg);
     taskStore.openDialog();
-    // 任务完成后会通过 watch 自动刷新列表
   } catch (error) {
     ElMessage.error((error as Error)?.message || '创建同步任务失败');
   } finally {

TEMPAT SAMPAH
server/python/platforms/__pycache__/douyin.cpython-311.pyc


+ 4 - 1
server/python/platforms/douyin.py

@@ -736,7 +736,10 @@ class DouyinPublisher(BasePublisher):
             
             aweme_list = response.get('aweme_list', []) or []
             has_more = response.get('has_more', False)
-            next_cursor = response.get('max_cursor', 0)
+            # 下一页游标:优先 max_cursor,兼容 next_cursor(与创作者中心 work_list 一致)
+            next_cursor = response.get('max_cursor') if 'max_cursor' in response else response.get('next_cursor')
+            if next_cursor is None:
+                next_cursor = 0
             
             # 从第一个作品的 author.aweme_count 获取总作品数
             if aweme_list and len(aweme_list) > 0:

+ 7 - 3
server/src/routes/works.ts

@@ -50,13 +50,17 @@ router.post(
   '/sync',
   asyncHandler(async (req, res) => {
     const userId = req.user!.userId;
-    const { accountId, accountName } = req.body;
+    const { accountId, accountName, platform, platformName } = req.body;
 
-    // 创建同步作品任务
+    // 创建同步作品任务:支持按账号、按平台或同步全部
+    let title = '同步所有作品';
+    if (accountName) title = `同步作品 - ${accountName}`;
+    else if (platformName) title = `同步作品 - ${platformName}`;
     const task = taskQueueService.createTask(userId, {
       type: 'sync_works',
-      title: accountName ? `同步作品 - ${accountName}` : '同步所有作品',
+      title,
       accountId: accountId ? Number(accountId) : undefined,
+      platform: platform ? String(platform) : undefined,
     });
 
     res.json({ 

+ 12 - 5
server/src/services/HeadlessBrowserService.ts

@@ -716,7 +716,8 @@ class HeadlessBrowserService {
     const cookieString = JSON.stringify(cookies);
     const pythonPlatform = platform === 'weixin_video' ? 'weixin' : platform;
 
-    const pageSize = platform === 'xiaohongshu' ? 20 : 50;
+    // 抖音 work_list 接口 count 最大 20,需与创作者中心一致
+    const pageSize = platform === 'xiaohongshu' ? 20 : platform === 'douyin' ? 20 : 50;
     let maxPages = 30;
     const allWorks: WorkItem[] = [];
     const seenIds = new Set<string>();
@@ -822,19 +823,25 @@ class HeadlessBrowserService {
 
       if (useCursorPagination) {
         const next = result.next_page;
-        const expectedMore = declaredTotal && declaredTotal > 0 ? allWorks.length < declaredTotal : !!result.has_more;
+        const hasNextCursor = next !== undefined && next !== null && next !== '' && next !== -1 && next !== '-1';
 
-        if (next !== undefined && next !== null && next !== '' && next !== -1 && next !== '-1') {
+        if (hasNextCursor) {
           const key = String(next);
           if (seenCursors.has(key)) break;
           seenCursors.add(key);
           cursor = next;
         } else {
-          if (platform === 'douyin' && (next === 0 || next === '0')) break;
+          if (platform === 'douyin') break;
           cursor = (typeof cursor === 'number' ? cursor + 1 : pageIndex + 1);
         }
 
-        if (!expectedMore || pageWorks.length === 0 || newCount === 0) break;
+        // 抖音:仅当无下一页游标或本页 0 条时停止(不依赖 has_more/declaredTotal,避免只同步 20 条)
+        if (platform === 'douyin') {
+          if (!hasNextCursor || pageWorks.length === 0) break;
+        } else {
+          const expectedMore = declaredTotal && declaredTotal > 0 ? allWorks.length < declaredTotal : !!result.has_more;
+          if (!expectedMore || pageWorks.length === 0 || newCount === 0) break;
+        }
       } else {
         if (!result.has_more || pageWorks.length === 0 || newCount === 0) break;
       }

+ 1 - 0
server/src/services/RedisTaskQueue.ts

@@ -243,6 +243,7 @@ class RedisTaskQueueService {
       priority: request.priority || 'normal',
       createdAt: new Date().toISOString(),
       accountId: request.accountId,
+      platform: request.platform,
       userId,
       ...(request.data || {}),
     };

+ 1 - 0
server/src/services/TaskQueueService.ts

@@ -59,6 +59,7 @@ class TaskQueueService {
       silent: request.silent || false, // 静默执行标记
       createdAt: new Date().toISOString(),
       accountId: request.accountId,
+      platform: request.platform,
       userId, // 存储 userId 用于任务执行
       // 合并额外数据
       ...(request.data || {}),

+ 4 - 1
server/src/services/WorkService.ts

@@ -91,6 +91,7 @@ export class WorkService {
   async syncWorks(
     userId: number,
     accountId?: number,
+    platform?: string,
     onProgress?: (progress: number, currentStep: string) => void
   ): Promise<{
     synced: number;
@@ -105,7 +106,7 @@ export class WorkService {
       syncedCount: number;
     }>;
   }> {
-    logger.info(`[SyncWorks] Starting sync for userId: ${userId}, accountId: ${accountId || 'all'}`);
+    logger.info(`[SyncWorks] Starting sync for userId: ${userId}, accountId: ${accountId || 'all'}, platform: ${platform || 'all'}`);
 
     // 先查看所有账号(调试用)
     const allAccounts = await this.accountRepository.find({ where: { userId } });
@@ -119,6 +120,8 @@ export class WorkService {
 
     if (accountId) {
       queryBuilder.andWhere('account.id = :accountId', { accountId });
+    } else if (platform) {
+      queryBuilder.andWhere('account.platform = :platform', { platform });
     }
 
     const accounts = await queryBuilder.getMany();

+ 1 - 1
server/src/services/taskExecutors.ts

@@ -64,7 +64,7 @@ async function syncWorksExecutor(task: Task, updateProgress: ProgressUpdater): P
     throw new Error('缺少用户ID');
   }
 
-  const result = await workService.syncWorks(userId, task.accountId, (progress, currentStep) => {
+  const result = await workService.syncWorks(userId, task.accountId, task.platform, (progress, currentStep) => {
     updateProgress({ progress, currentStep });
   });
 

+ 1 - 0
shared/src/types/task.ts

@@ -121,5 +121,6 @@ export interface CreateTaskRequest {
   priority?: TaskPriority;
   silent?: boolean;        // 静默执行,前台不弹框显示
   accountId?: number;
+  platform?: string;      // 按平台筛选(如 sync_works 时只同步该平台的账号)
   data?: Record<string, unknown>;
 }