完成合集、短剧
This commit is contained in:
@@ -5,105 +5,107 @@ using Serilog;
|
||||
using static Quartz.Logging.OperationName;
|
||||
|
||||
namespace dy.net.job
|
||||
{ /// <summary>
|
||||
/// 抖音任务依赖监听器(独立公共类)
|
||||
/// 作用:监听任务执行完成事件,触发下一个依赖任务,实现顺序执行
|
||||
/// </summary>
|
||||
public class DouyinJobDependencyListener : IJobListener
|
||||
{
|
||||
// 监听器名称(唯一标识,不可重复)
|
||||
public string Name => "DouyinJobDependencyListener";
|
||||
{
|
||||
|
||||
// /// <summary>
|
||||
///// 抖音任务依赖监听器(独立公共类)
|
||||
///// 作用:监听任务执行完成事件,触发下一个依赖任务,实现顺序执行
|
||||
///// </summary>
|
||||
// public class DouyinJobDependencyListener : IJobListener
|
||||
// {
|
||||
// // 监听器名称(唯一标识,不可重复)
|
||||
// public string Name => "DouyinJobDependencyListener";
|
||||
|
||||
/// <summary>
|
||||
/// 任务配置字典(从外部注入)
|
||||
/// </summary>
|
||||
private readonly Dictionary<string, JobConfig> _jobConfigs;
|
||||
// /// <summary>
|
||||
// /// 任务配置字典(从外部注入)
|
||||
// /// </summary>
|
||||
// private readonly Dictionary<string, JobConfig> _jobConfigs;
|
||||
|
||||
/// <summary>
|
||||
/// 任务依赖关系(从外部注入,定义执行顺序)
|
||||
/// </summary>
|
||||
private readonly Dictionary<string, string> _jobDependency;
|
||||
// /// <summary>
|
||||
// /// 任务依赖关系(从外部注入,定义执行顺序)
|
||||
// /// </summary>
|
||||
// private readonly Dictionary<string, string> _jobDependency;
|
||||
|
||||
/// <summary>
|
||||
/// 任务服务(用于触发下一个任务,从外部注入)
|
||||
/// </summary>
|
||||
private readonly DouyinQuartzJobService _jobService;
|
||||
// /// <summary>
|
||||
// /// 任务服务(用于触发下一个任务,从外部注入)
|
||||
// /// </summary>
|
||||
// private readonly DouyinQuartzJobService _jobService;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数(依赖注入)
|
||||
/// </summary>
|
||||
/// <param name="jobConfigs">任务配置</param>
|
||||
/// <param name="jobDependency">任务依赖关系</param>
|
||||
/// <param name="jobService">任务服务</param>
|
||||
public DouyinJobDependencyListener(
|
||||
Dictionary<string, JobConfig> jobConfigs,
|
||||
Dictionary<string, string> jobDependency,
|
||||
DouyinQuartzJobService jobService)
|
||||
{
|
||||
_jobConfigs = jobConfigs ?? throw new ArgumentNullException(nameof(jobConfigs), "任务配置不能为空");
|
||||
_jobDependency = jobDependency ?? throw new ArgumentNullException(nameof(jobDependency), "任务依赖关系不能为空");
|
||||
_jobService = jobService ?? throw new ArgumentNullException(nameof(jobService), "任务服务不能为空");
|
||||
}
|
||||
// /// <summary>
|
||||
// /// 构造函数(依赖注入)
|
||||
// /// </summary>
|
||||
// /// <param name="jobConfigs">任务配置</param>
|
||||
// /// <param name="jobDependency">任务依赖关系</param>
|
||||
// /// <param name="jobService">任务服务</param>
|
||||
// public DouyinJobDependencyListener(
|
||||
// Dictionary<string, JobConfig> jobConfigs,
|
||||
// Dictionary<string, string> jobDependency,
|
||||
// DouyinQuartzJobService jobService)
|
||||
// {
|
||||
// _jobConfigs = jobConfigs ?? throw new ArgumentNullException(nameof(jobConfigs), "任务配置不能为空");
|
||||
// _jobDependency = jobDependency ?? throw new ArgumentNullException(nameof(jobDependency), "任务依赖关系不能为空");
|
||||
// _jobService = jobService ?? throw new ArgumentNullException(nameof(jobService), "任务服务不能为空");
|
||||
// }
|
||||
|
||||
/// <summary>
|
||||
/// 任务执行前触发(无需处理)
|
||||
/// </summary>
|
||||
public Task JobToBeExecuted(IJobExecutionContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
// /// <summary>
|
||||
// /// 任务执行前触发(无需处理)
|
||||
// /// </summary>
|
||||
// public Task JobToBeExecuted(IJobExecutionContext context, CancellationToken cancellationToken = default)
|
||||
// {
|
||||
// return Task.CompletedTask;
|
||||
// }
|
||||
|
||||
/// <summary>
|
||||
/// 任务被否决执行时触发(无需处理)
|
||||
/// </summary>
|
||||
public Task JobExecutionVetoed(IJobExecutionContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
// /// <summary>
|
||||
// /// 任务被否决执行时触发(无需处理)
|
||||
// /// </summary>
|
||||
// public Task JobExecutionVetoed(IJobExecutionContext context, CancellationToken cancellationToken = default)
|
||||
// {
|
||||
// return Task.CompletedTask;
|
||||
// }
|
||||
|
||||
/// <summary>
|
||||
/// 任务执行完成后触发(核心逻辑:触发下一个依赖任务)
|
||||
/// </summary>
|
||||
public async Task JobWasExecuted(IJobExecutionContext context, JobExecutionException? jobException, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var currentJobKey = context.JobDetail.Key;
|
||||
Log.Information("【任务监听】任务执行完成 - 任务名称: {JobName}, 执行状态: {Status}",
|
||||
currentJobKey.Name, jobException == null ? "成功" : "失败");
|
||||
// /// <summary>
|
||||
// /// 任务执行完成后触发(核心逻辑:触发下一个依赖任务)
|
||||
// /// </summary>
|
||||
// public async Task JobWasExecuted(IJobExecutionContext context, JobExecutionException? jobException, CancellationToken cancellationToken = default)
|
||||
// {
|
||||
// var currentJobKey = context.JobDetail.Key;
|
||||
// Log.Information("【任务监听】任务执行完成 - 任务名称: {JobName}, 执行状态: {Status}",
|
||||
// currentJobKey.Name, jobException == null ? "成功" : "失败");
|
||||
|
||||
// 1. 若当前任务执行失败,终止后续依赖任务(避免无效执行)
|
||||
if (jobException != null)
|
||||
{
|
||||
Log.Error(jobException, "【任务监听】任务 {JobName} 执行失败,终止后续任务链条", currentJobKey.Name);
|
||||
return;
|
||||
}
|
||||
// // 1. 若当前任务执行失败,终止后续依赖任务(避免无效执行)
|
||||
// if (jobException != null)
|
||||
// {
|
||||
// Log.Error(jobException, "【任务监听】任务 {JobName} 执行失败,终止后续任务链条", currentJobKey.Name);
|
||||
// return;
|
||||
// }
|
||||
|
||||
// 2. 根据当前任务的 JobKey,找到对应的配置 Key(如:dy.job.key.collect → collect)
|
||||
var currentConfigKey = _jobConfigs.FirstOrDefault(kv => kv.Value.JobKey == currentJobKey.Name).Key;
|
||||
if (string.IsNullOrEmpty(currentConfigKey))
|
||||
{
|
||||
Log.Warning("【任务监听】未找到任务 {JobName} 的配置信息,任务链条终止", currentJobKey.Name);
|
||||
return;
|
||||
}
|
||||
// // 2. 根据当前任务的 JobKey,找到对应的配置 Key(如:dy.job.key.collect → collect)
|
||||
// var currentConfigKey = _jobConfigs.FirstOrDefault(kv => kv.Value.JobKey == currentJobKey.Name).Key;
|
||||
// if (string.IsNullOrEmpty(currentConfigKey))
|
||||
// {
|
||||
// Log.Warning("【任务监听】未找到任务 {JobName} 的配置信息,任务链条终止", currentJobKey.Name);
|
||||
// return;
|
||||
// }
|
||||
|
||||
// 3. 查找下一个依赖任务的配置 Key
|
||||
if (!_jobDependency.TryGetValue(currentConfigKey, out var nextConfigKey) || string.IsNullOrEmpty(nextConfigKey))
|
||||
{
|
||||
Log.Information("【任务监听】任务 {JobName} 是最后一个任务,本次任务链条执行完毕", currentJobKey.Name);
|
||||
return;
|
||||
}
|
||||
// // 3. 查找下一个依赖任务的配置 Key
|
||||
// if (!_jobDependency.TryGetValue(currentConfigKey, out var nextConfigKey) || string.IsNullOrEmpty(nextConfigKey))
|
||||
// {
|
||||
// Log.Information("【任务监听】任务 {JobName} 是最后一个任务,本次任务链条执行完毕", currentJobKey.Name);
|
||||
// return;
|
||||
// }
|
||||
|
||||
// 4. 触发下一个任务(标记为「依赖触发」,立即执行)
|
||||
Log.Information("【任务监听】准备触发下一个任务: {NextJobName}(依赖触发)", nextConfigKey);
|
||||
var triggerSuccess = await _jobService.StartJobAsync(nextConfigKey, "", isDependencyTrigger: true);
|
||||
// // 4. 触发下一个任务(标记为「依赖触发」,立即执行)
|
||||
// Log.Information("【任务监听】准备触发下一个任务: {NextJobName}(依赖触发)", nextConfigKey);
|
||||
// var triggerSuccess = await _jobService.StartJobAsync(nextConfigKey, "", isDependencyTrigger: true);
|
||||
|
||||
if (triggerSuccess)
|
||||
{
|
||||
Log.Information("【任务监听】下一个任务 {NextJobName} 触发成功", nextConfigKey);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error("【任务监听】下一个任务 {NextJobName} 触发失败,任务链条中断", nextConfigKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
// if (triggerSuccess)
|
||||
// {
|
||||
// Log.Information("【任务监听】下一个任务 {NextJobName} 触发成功", nextConfigKey);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// Log.Error("【任务监听】下一个任务 {NextJobName} 触发失败,任务链条中断", nextConfigKey);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
+20
-14
@@ -3,6 +3,7 @@ using dy.net.model.entity;
|
||||
using dy.net.model.response;
|
||||
using dy.net.service;
|
||||
using dy.net.utils;
|
||||
using Serilog;
|
||||
using System;
|
||||
|
||||
namespace dy.net.job
|
||||
@@ -25,6 +26,11 @@ namespace dy.net.job
|
||||
}
|
||||
|
||||
|
||||
protected override string GetAuthorAvatarBasePath(DouyinCookie cookie)
|
||||
{
|
||||
return Path.Combine(cookie.SavePath, "author");
|
||||
}
|
||||
|
||||
protected override async Task<DouyinVideoInfoResponse> FetchVideoData(DouyinCookie cookie, string cursor, DouyinFollowed followed, DouyinCollectCate cate)
|
||||
{
|
||||
return await douyinHttpClientService.SyncCollectVideos(cursor, count, cookie.Cookies);
|
||||
@@ -35,27 +41,27 @@ namespace dy.net.job
|
||||
return data != null && data.HasMore == 1 && cookie.CollHasSyncd == 0;
|
||||
}
|
||||
|
||||
protected override async Task HandleSyncCompletion(DouyinCookie cookie, int syncCount,DouyinFollowed followed)
|
||||
protected override async Task HandleSyncCompletion(DouyinCookie cookie, int syncCount,DouyinFollowed followed,DouyinCollectCate cate)
|
||||
{
|
||||
if (syncCount > 0)
|
||||
{
|
||||
Serilog.Log.Debug($"{VideoType}-Cookie-[{cookie.UserName}],本次共同步成功{syncCount}条视频");
|
||||
cookie.CollHasSyncd = 1;
|
||||
await douyinCookieService.UpdateAsync(cookie);
|
||||
}
|
||||
else
|
||||
{
|
||||
Serilog.Log.Debug($"{VideoType}-Cookie-[{cookie.UserName}],没有可以同步的新视频");
|
||||
}
|
||||
cookie.CollHasSyncd = 1;
|
||||
await douyinCookieService.UpdateAsync(cookie);
|
||||
Log.Debug($"[{VideoType}]-[{cookie.UserName}],本次成功同步{syncCount}条视频");
|
||||
}
|
||||
|
||||
|
||||
|
||||
protected override string CreateSaveFolder(DouyinCookie cookie, Aweme item, AppConfig config, DouyinFollowed followed, DouyinCollectCate cate)
|
||||
{
|
||||
var (tag1, _, _) = GetVideoTags(item);
|
||||
var safeTag1 = string.IsNullOrWhiteSpace(tag1) ? "other" : DouyinFileNameHelper.SanitizeLinuxFileName(tag1, "", true);
|
||||
var folder = Path.Combine(cookie.SavePath, safeTag1, $"{DouyinFileNameHelper.SanitizeLinuxFileName(item.Desc, item.AwemeId, true)}");
|
||||
string authorFolder;
|
||||
if (string.IsNullOrWhiteSpace(item.Author?.Nickname) && string.IsNullOrWhiteSpace(item.Author?.Uid))
|
||||
{
|
||||
authorFolder = "未知博主";
|
||||
}
|
||||
else
|
||||
{
|
||||
authorFolder = $"{DouyinFileNameHelper.SanitizeLinuxFileName(item.Author?.Nickname, item.Author?.Uid, true)}";
|
||||
}
|
||||
var folder = Path.Combine(cookie.SavePath, authorFolder, $"{DouyinFileNameHelper.SanitizeLinuxFileName(item.Desc, item.AwemeId, true)}");
|
||||
if (!Directory.Exists(folder)) Directory.CreateDirectory(folder);
|
||||
return folder;
|
||||
|
||||
|
||||
+19
-14
@@ -15,6 +15,11 @@ namespace dy.net.job
|
||||
|
||||
protected override VideoTypeEnum VideoType => VideoTypeEnum.dy_favorite;
|
||||
|
||||
protected override string GetAuthorAvatarBasePath(DouyinCookie cookie)
|
||||
{
|
||||
return Path.Combine(cookie.FavSavePath, "author");
|
||||
}
|
||||
|
||||
protected override async Task<List<DouyinCookie>> GetSyncCookies()
|
||||
{
|
||||
return await douyinCookieService.GetOpendCookiesAsync(x=> !string.IsNullOrWhiteSpace(x.FavSavePath)&&!string.IsNullOrWhiteSpace(x.SecUserId));
|
||||
@@ -31,25 +36,25 @@ namespace dy.net.job
|
||||
}
|
||||
|
||||
|
||||
protected override async Task HandleSyncCompletion(DouyinCookie cookie, int syncCount, DouyinFollowed followed)
|
||||
protected override async Task HandleSyncCompletion(DouyinCookie cookie, int syncCount, DouyinFollowed followed,DouyinCollectCate cate)
|
||||
{
|
||||
if (syncCount > 0)
|
||||
{
|
||||
Serilog.Log.Debug($"{VideoType}-Cookie-[{cookie.UserName}],本次共同步成功{syncCount}条视频");
|
||||
cookie.FavHasSyncd = 1;
|
||||
await douyinCookieService.UpdateAsync(cookie);
|
||||
}
|
||||
else
|
||||
{
|
||||
Serilog.Log.Debug($"{VideoType}-Cookie-[{cookie.UserName}],没有可以同步的新视频");
|
||||
}
|
||||
cookie.FavHasSyncd = 1;
|
||||
await douyinCookieService.UpdateAsync(cookie);
|
||||
await base.HandleSyncCompletion(cookie, syncCount, followed, cate);
|
||||
}
|
||||
|
||||
protected override string CreateSaveFolder(DouyinCookie cookie, Aweme item, AppConfig config, DouyinFollowed followed,DouyinCollectCate cate)
|
||||
{
|
||||
var (tag1, _, _) = GetVideoTags(item);
|
||||
var safeTag1 = string.IsNullOrWhiteSpace(tag1) ? "other" : DouyinFileNameHelper.SanitizeLinuxFileName(tag1,"",true);
|
||||
var folder = Path.Combine(cookie.FavSavePath, safeTag1, $"{DouyinFileNameHelper.SanitizeLinuxFileName(item.Desc, item.AwemeId,true)}");
|
||||
string authorFolder;
|
||||
if (string.IsNullOrWhiteSpace(item.Author?.Nickname) && string.IsNullOrWhiteSpace(item.Author?.Uid))
|
||||
{
|
||||
authorFolder = "未知博主";
|
||||
}
|
||||
else
|
||||
{
|
||||
authorFolder = $"{DouyinFileNameHelper.SanitizeLinuxFileName(item.Author?.Nickname, item.Author?.Uid, true)}";
|
||||
}
|
||||
var folder = Path.Combine(cookie.FavSavePath, authorFolder, $"{DouyinFileNameHelper.SanitizeLinuxFileName(item.Desc, item.AwemeId, true)}");
|
||||
if (!Directory.Exists(folder)) Directory.CreateDirectory(folder);
|
||||
return folder;
|
||||
}
|
||||
|
||||
+269
-220
File diff suppressed because it is too large
Load Diff
@@ -15,14 +15,32 @@ namespace dy.net.job
|
||||
|
||||
protected override VideoTypeEnum VideoType => VideoTypeEnum.dy_custom_collect;
|
||||
|
||||
protected override string CreateSaveFolder(DouyinCookie cookie, Aweme item, AppConfig config, DouyinFollowed followed, DouyinCollectCate cate)
|
||||
{
|
||||
if (cate != null)
|
||||
{
|
||||
var folder = Path.Combine(cookie.SavePath, DouyinFileNameHelper.SanitizeLinuxFileName(cate.SaveFolder, "", true), DouyinFileNameHelper.SanitizeLinuxFileName(item.Desc, item.AwemeId, true));
|
||||
if (!Directory.Exists(folder)) Directory.CreateDirectory(folder);
|
||||
return folder;
|
||||
}
|
||||
else
|
||||
{
|
||||
return base.CreateSaveFolder(cookie, item, config,followed,cate);
|
||||
}
|
||||
}
|
||||
protected override string GetAuthorAvatarBasePath(DouyinCookie cookie)
|
||||
{
|
||||
return Path.Combine(cookie.SavePath, "author");
|
||||
}
|
||||
|
||||
protected override async Task<DouyinVideoInfoResponse> FetchVideoData(DouyinCookie cookie, string cursor, DouyinFollowed followed, DouyinCollectCate cate)
|
||||
{
|
||||
return await douyinHttpClientService.SyncCollectVideos(cursor, count, cookie.Cookies);
|
||||
return await douyinHttpClientService.SyncCollectVideosByCollectId(cursor,count,cookie.Cookies,cate.XId);
|
||||
}
|
||||
|
||||
protected override bool ShouldContinueSync(DouyinCookie cookie, DouyinVideoInfoResponse data, DouyinFollowed followed = null)
|
||||
{
|
||||
return data != null && data.HasMore == 1 && cookie.CollHasSyncd == 0;
|
||||
return data != null && data.HasMore == 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -11,9 +11,9 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace dy.net.job
|
||||
{
|
||||
public class DouyinFollowedViedoSyncJob : DouyinBasicSyncJob
|
||||
public class DouyinFollowedSyncJob : DouyinBasicSyncJob
|
||||
{
|
||||
public DouyinFollowedViedoSyncJob(DouyinCookieService douyinCookieService, DouyinHttpClientService douyinHttpClientService, DouyinVideoService douyinVideoService, DouyinCommonService douyinCommonService, DouyinFollowService douyinFollowService, DouyinMergeVideoService douyinMergeVideoService, DouyinCollectCateService douyinCollectCateService) : base(douyinCookieService, douyinHttpClientService, douyinVideoService, douyinCommonService, douyinFollowService, douyinMergeVideoService, douyinCollectCateService)
|
||||
public DouyinFollowedSyncJob(DouyinCookieService douyinCookieService, DouyinHttpClientService douyinHttpClientService, DouyinVideoService douyinVideoService, DouyinCommonService douyinCommonService, DouyinFollowService douyinFollowService, DouyinMergeVideoService douyinMergeVideoService, DouyinCollectCateService douyinCollectCateService) : base(douyinCookieService, douyinHttpClientService, douyinVideoService, douyinCommonService, douyinFollowService, douyinMergeVideoService, douyinCollectCateService)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -33,7 +33,10 @@ namespace dy.net.job
|
||||
{
|
||||
return data != null && data.HasMore == 1 && cookie.UperSyncd == 0 && followed.FullSync;
|
||||
}
|
||||
|
||||
protected override string GetAuthorAvatarBasePath(DouyinCookie cookie)
|
||||
{
|
||||
return Path.Combine(cookie.UpSavePath, "author");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 关注用户特殊处理文件夹存储路径,用户可自定义保存路径
|
||||
@@ -41,6 +44,7 @@ namespace dy.net.job
|
||||
/// <param name="cookie"></param>
|
||||
/// <param name="item"></param>
|
||||
/// <param name="followed"></param>
|
||||
/// <param name="cate"></param>
|
||||
/// <param name="config"></param>
|
||||
/// <returns></returns>
|
||||
protected override string CreateSaveFolder(DouyinCookie cookie, Aweme item, AppConfig config, DouyinFollowed followed,DouyinCollectCate cate)
|
||||
@@ -49,7 +53,7 @@ namespace dy.net.job
|
||||
// 1. 优先获取有效的作者名称(遵循原有优先级:followed.UperName > item.Author.Nickname > 默认值)
|
||||
var rawAuthorName = followed?.UperName ?? item?.Author?.Nickname;
|
||||
var authorName = string.IsNullOrWhiteSpace(rawAuthorName)
|
||||
? "UnknownAuthor"
|
||||
? "未知博主"
|
||||
: DouyinFileNameHelper.SanitizeLinuxFileName(rawAuthorName, "", true);
|
||||
// 2. 确定最终文件夹路径(遵循原有优先级:followed.SavePath > authorName > 基础路径)
|
||||
var targetFolderName = !string.IsNullOrWhiteSpace(followed?.SavePath) ? followed.SavePath : authorName;
|
||||
@@ -58,17 +62,10 @@ namespace dy.net.job
|
||||
if (!Directory.Exists(folder)) Directory.CreateDirectory(folder);
|
||||
#endregion
|
||||
|
||||
if (config.UperSaveTogether)
|
||||
{
|
||||
return folder;
|
||||
}
|
||||
else
|
||||
{
|
||||
var sampleName = DouyinFileNameHelper.SanitizeLinuxFileName(item.Desc, item.AwemeId,true);
|
||||
var (existingName, _) = douyinVideoService.GetUperLastViedoFileName(item.Author.Uid, sampleName).Result;
|
||||
var fileNameFolder = string.IsNullOrWhiteSpace(existingName) ? sampleName : existingName;
|
||||
return Path.Combine(folder, fileNameFolder);
|
||||
}
|
||||
var sampleName = DouyinFileNameHelper.SanitizeLinuxFileName(item.Desc, item.AwemeId, true);
|
||||
var (existingName, _) = douyinVideoService.GetUperLastViedoFileName(item.Author.Uid, sampleName);
|
||||
var fileNameFolder = string.IsNullOrWhiteSpace(existingName) ? sampleName : existingName;
|
||||
return Path.Combine(folder, fileNameFolder);
|
||||
}
|
||||
/// <summary>
|
||||
/// 关注的视频,生成文件名称
|
||||
@@ -76,8 +73,9 @@ namespace dy.net.job
|
||||
/// <param name="cookie"></param>
|
||||
/// <param name="item"></param>
|
||||
/// <param name="config"></param>
|
||||
/// <param name="cate"></param>
|
||||
/// <returns></returns>
|
||||
protected override string GetVideoFileName(DouyinCookie cookie, Aweme item,AppConfig config)
|
||||
protected override string GetVideoFileName(DouyinCookie cookie, Aweme item,AppConfig config,DouyinCollectCate cate)
|
||||
{
|
||||
|
||||
string Format = "mp4";
|
||||
@@ -109,7 +107,7 @@ namespace dy.net.job
|
||||
if (config?.UperUseViedoTitle ?? false)//优先
|
||||
{
|
||||
var sampleName = DouyinFileNameHelper.SanitizeLinuxFileName(item.Desc, item.AwemeId);
|
||||
var (existingName, _) = douyinVideoService.GetUperLastViedoFileName(item.Author.Uid, sampleName).Result;
|
||||
var (existingName, _) = douyinVideoService.GetUperLastViedoFileName(item.Author.Uid, sampleName);
|
||||
fileName = string.IsNullOrWhiteSpace(existingName) ? $"{sampleName}.{Format}" : $"{existingName}.{Format}";
|
||||
}
|
||||
else
|
||||
@@ -146,47 +144,20 @@ namespace dy.net.job
|
||||
/// <param name="item"></param>
|
||||
/// <param name="config"></param>
|
||||
/// <param name="imageType"></param>
|
||||
/// <param name="cate"></param>
|
||||
/// <returns></returns>
|
||||
protected override string GetNfoFileName(DouyinCookie cookie, Aweme item, AppConfig config, string imageType)
|
||||
protected override string GetNfoFileName(DouyinCookie cookie, Aweme item, AppConfig config, string imageType,DouyinCollectCate cate)
|
||||
{
|
||||
if (config.UperSaveTogether)
|
||||
{
|
||||
var videoFileName = GetVideoFileName(cookie, item,config);
|
||||
return $"{Path.GetFileNameWithoutExtension(videoFileName)}{imageType}";
|
||||
}
|
||||
else
|
||||
{
|
||||
return base.GetNfoFileName(cookie, item, config, imageType);
|
||||
}
|
||||
return base.GetNfoFileName(cookie, item, config, imageType, cate);
|
||||
}
|
||||
|
||||
protected override async Task HandleSyncCompletion(DouyinCookie cookie, int syncCount, DouyinFollowed followed)
|
||||
protected override async Task HandleSyncCompletion(DouyinCookie cookie, int syncCount, DouyinFollowed followed, DouyinCollectCate cate)
|
||||
{
|
||||
if (syncCount > 0)
|
||||
{
|
||||
Serilog.Log.Debug($"{VideoType}-Cookie-[{cookie.UserName}],本次共同步成功{syncCount}条视频");
|
||||
cookie.UperSyncd = 1;
|
||||
await douyinCookieService.UpdateAsync(cookie);
|
||||
}
|
||||
else
|
||||
{
|
||||
Serilog.Log.Debug($"{VideoType}-Cookie-[{cookie.UserName}]-{(followed == null ? "" : $"{followed.UperName}")},没有可以同步的新视频");
|
||||
}
|
||||
cookie.UperSyncd = 1;
|
||||
await douyinCookieService.UpdateAsync(cookie);
|
||||
await base.HandleSyncCompletion(cookie, syncCount, followed, cate);
|
||||
}
|
||||
|
||||
protected override VideoEntityDifferences GetVideoEntityDifferences(DouyinCookie cookie, Aweme item)
|
||||
{
|
||||
var config = douyinCommonService.GetConfig();
|
||||
string simplifiedTitle = string.Empty;
|
||||
|
||||
if (config?.UperUseViedoTitle ?? false)
|
||||
{
|
||||
simplifiedTitle = DouyinFileNameHelper.SanitizeLinuxFileName(item.Desc, item.AwemeId);
|
||||
}
|
||||
return new VideoEntityDifferences
|
||||
{
|
||||
VideoTitleSimplify = simplifiedTitle
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -3,45 +3,25 @@ using dy.net.model.entity;
|
||||
using dy.net.model.response;
|
||||
using dy.net.service;
|
||||
using Quartz;
|
||||
using Serilog;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace dy.net.job
|
||||
{
|
||||
[DisallowConcurrentExecution] // 禁止并发执行,确保同一时间只有一个实例在运行
|
||||
[DisallowConcurrentExecution]
|
||||
public class DouyinFollowsAndCollnectsSyncJob : IJob
|
||||
{
|
||||
// 服务依赖(统一下划线命名)
|
||||
private readonly DouyinCollectCateService _douyinCollectCateService;
|
||||
private readonly DouyinCookieService _dyCookieService;
|
||||
private readonly DouyinHttpClientService _douyinService;
|
||||
private readonly DouyinFollowService _followService;
|
||||
private readonly DouyinCommonService _douyinCommonService;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 抖音收藏夹服务
|
||||
/// </summary>
|
||||
protected readonly DouyinCollectCateService douyinCollectCateService;
|
||||
/// <summary>
|
||||
/// 抖音Cookie服务,用于获取和管理用户Cookie
|
||||
/// </summary>
|
||||
protected readonly DouyinCookieService _dyCookieService;
|
||||
|
||||
/// <summary>
|
||||
/// 抖音HTTP客户端服务,用于发送HTTP请求
|
||||
/// </summary>
|
||||
protected readonly DouyinHttpClientService _douyinService;
|
||||
|
||||
/// <summary>
|
||||
/// 抖音关注服务,用于管理关注数据
|
||||
/// </summary>
|
||||
protected readonly DouyinFollowService _followService;
|
||||
|
||||
protected readonly DouyinCommonService douyinCommonService;
|
||||
|
||||
public DouyinFollowsAndCollnectsSyncJob(DouyinCookieService dyCookieService, DouyinHttpClientService douyinService, DouyinFollowService followService, DouyinCommonService douyinCommonService, DouyinCollectCateService douyinCollectCateService)
|
||||
{
|
||||
_dyCookieService = dyCookieService;
|
||||
_douyinService = douyinService;
|
||||
_followService = followService;
|
||||
this.douyinCommonService = douyinCommonService;
|
||||
this.douyinCollectCateService = douyinCollectCateService;
|
||||
}
|
||||
|
||||
// 第一步:定义常量和枚举(建议放在单独的常量类中,此处为内联演示)
|
||||
// 常量定义
|
||||
private const string DEFAULT_FOLLOW_COUNT = "20";
|
||||
private const int INVALID_COOKIE_STATUS_CODE = 8;
|
||||
private const string LOG_TAG_COLLECT = "收藏夹同步";
|
||||
@@ -49,299 +29,234 @@ namespace dy.net.job
|
||||
private const string LOG_TAG_SERIES = "短剧列表同步";
|
||||
private const string LOG_TAG_FOLLOW = "关注列表同步";
|
||||
|
||||
/// <summary>
|
||||
/// 抖音数据同步任务执行方法(优化版)
|
||||
/// </summary>
|
||||
/// <param name="context">任务执行上下文</param>
|
||||
// 构造函数注入
|
||||
public DouyinFollowsAndCollnectsSyncJob(
|
||||
DouyinCookieService dyCookieService,
|
||||
DouyinHttpClientService douyinService,
|
||||
DouyinFollowService followService,
|
||||
DouyinCommonService douyinCommonService,
|
||||
DouyinCollectCateService douyinCollectCateService)
|
||||
{
|
||||
_dyCookieService = dyCookieService;
|
||||
_douyinService = douyinService;
|
||||
_followService = followService;
|
||||
_douyinCommonService = douyinCommonService;
|
||||
_douyinCollectCateService = douyinCollectCateService;
|
||||
}
|
||||
|
||||
public async Task Execute(IJobExecutionContext context)
|
||||
{
|
||||
// 1. 获取有效Cookie列表,提前做空值判断
|
||||
var cookies = await _dyCookieService.GetOpendCookiesAsync();
|
||||
if (cookies == null || !cookies.Any())
|
||||
{
|
||||
Serilog.Log.Debug("当前无可用的抖音Cookie,同步任务跳过");
|
||||
Log.Debug("当前无可用的抖音Cookie,同步任务跳过");
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 获取应用配置
|
||||
AppConfig conf = douyinCommonService.GetConfig();
|
||||
var conf = _douyinCommonService.GetConfig();
|
||||
|
||||
// 3. 遍历每个Cookie,执行各类数据同步
|
||||
foreach (var ck in cookies)
|
||||
{
|
||||
Serilog.Log.Information($"开始处理Cookie用户:[{ck.UserName}](ID:{ck.Id})");
|
||||
Log.Information($"开始处理Cookie用户:[{ck.UserName}](ID:{ck.Id})");
|
||||
|
||||
// 3.1 同步自定义收藏夹(UseCollectFolder=true时)
|
||||
// 调用通用方法,传入差异化参数(无委托,参数直观)
|
||||
if (ck.UseCollectFolder)
|
||||
{
|
||||
await SyncCollectDataAsync(
|
||||
cookie: ck,
|
||||
cateType: VideoTypeEnum.dy_custom_collect,
|
||||
dataFetchFunc: async (offset) => await _douyinService.SyncCollectFolderList(ck.Cookies, offset),
|
||||
entityConvertFunc: (collectItem) => new DouyinCollectCate
|
||||
await SyncCollectGenericAsync(ck, VideoTypeEnum.dy_custom_collect,
|
||||
(cookie, offset) => _douyinService.SyncCollectFolderList(cookie.Cookies, offset),
|
||||
item => new DouyinCollectCate
|
||||
{
|
||||
CookieId = ck.Id,
|
||||
CateType = VideoTypeEnum.dy_custom_collect,
|
||||
Name = collectItem.CollectsName,
|
||||
Name = item.CollectsName,
|
||||
Sync = false,
|
||||
XId = collectItem.CollectsId
|
||||
XId = item.CollectsId,
|
||||
Total = item.TotalNumber
|
||||
},
|
||||
hasMoreFunc: (data) => data?.HasMore ?? false,
|
||||
cursorFunc: (data) => data?.Cursor.ToString() ?? "0",
|
||||
dataListFunc: (data) => data?.CollectsList,
|
||||
logTag: LOG_TAG_COLLECT
|
||||
);
|
||||
data => data?.HasMore ?? false,
|
||||
data => data?.Cursor.ToString() ?? "0",
|
||||
data => data?.CollectsList,
|
||||
LOG_TAG_COLLECT);
|
||||
}
|
||||
|
||||
// 3.2 同步收藏夹合集(DownMix=true时)
|
||||
if (ck.DownMix)
|
||||
{
|
||||
await SyncCollectDataAsync(
|
||||
cookie: ck,
|
||||
cateType: VideoTypeEnum.dy_mix,
|
||||
dataFetchFunc: async (offset) => await _douyinService.SyncMixList(ck.Cookies, offset),
|
||||
entityConvertFunc: (mixItem) => new DouyinCollectCate
|
||||
await SyncCollectGenericAsync(ck, VideoTypeEnum.dy_mix,
|
||||
(cookie, offset) => _douyinService.SyncMixList(cookie.Cookies, offset),
|
||||
item => new DouyinCollectCate
|
||||
{
|
||||
CookieId = ck.Id,
|
||||
CateType = VideoTypeEnum.dy_mix,
|
||||
Name = mixItem.MixName,
|
||||
Name = item.MixName,
|
||||
Sync = false,
|
||||
XId = mixItem.MixId,
|
||||
CoverUrl = mixItem.CoverUrl?.UrlList?.FirstOrDefault() // 空值防护:避免CoverUrl为null
|
||||
XId = item.MixId,
|
||||
Total = item?.Statis?.UpdatedToEpisode ?? 0,
|
||||
CoverUrl = item.CoverUrl?.UrlList?.LastOrDefault()
|
||||
},
|
||||
hasMoreFunc: (data) => (data?.HasMore ?? 0) == 1,
|
||||
cursorFunc: (data) => data?.Cursor.ToString() ?? "0",
|
||||
dataListFunc: (data) => data?.MixInfos,
|
||||
logTag: LOG_TAG_MIX
|
||||
);
|
||||
data => (data?.HasMore ?? 0) == 1,
|
||||
data => data?.Cursor.ToString() ?? "0",
|
||||
data => data?.MixInfos,
|
||||
LOG_TAG_MIX);
|
||||
}
|
||||
|
||||
// 3.3 同步收藏夹短剧(DownSeries=true时)
|
||||
if (ck.DownSeries)
|
||||
{
|
||||
await SyncCollectDataAsync(
|
||||
cookie: ck,
|
||||
cateType: VideoTypeEnum.dy_series,
|
||||
dataFetchFunc: async (offset) => await _douyinService.SyncSeriesList(ck.Cookies, offset),
|
||||
entityConvertFunc: (seriesItem) => new DouyinCollectCate
|
||||
await SyncCollectGenericAsync(ck, VideoTypeEnum.dy_series,
|
||||
(cookie, offset) => _douyinService.SyncSeriesList(cookie.Cookies, offset),
|
||||
item => new DouyinCollectCate
|
||||
{
|
||||
CookieId = ck.Id,
|
||||
CateType = VideoTypeEnum.dy_series,
|
||||
Name = seriesItem.SeriesName,
|
||||
Name = item.SeriesName,
|
||||
Sync = false,
|
||||
XId = seriesItem.SeriesId,
|
||||
CoverUrl = seriesItem.CoverImage?.ImageUrlList?.FirstOrDefault() // 空值防护:避免CoverImage为null
|
||||
XId = item.SeriesId,
|
||||
Total = item?.Stats?.TotalEpisodeCount ?? 0,
|
||||
CoverUrl = item.CoverImage?.ImageUrlList?.FirstOrDefault()
|
||||
},
|
||||
hasMoreFunc: (data) => (data?.HasMore ?? 0) == 1,
|
||||
cursorFunc: (data) => data?.Cursor.ToString() ?? "0",
|
||||
dataListFunc: (data) => data?.SeriesList,
|
||||
logTag: LOG_TAG_SERIES
|
||||
);
|
||||
data => (data?.HasMore ?? 0) == 1,
|
||||
data => data?.Cursor.ToString() ?? "0",
|
||||
data => data?.SeriesList,
|
||||
LOG_TAG_SERIES);
|
||||
}
|
||||
|
||||
// 3.4 同步关注列表(单独处理,逻辑特殊不纳入通用方法)
|
||||
await SyncFollowListAsync(ck, conf);
|
||||
|
||||
Serilog.Log.Debug($"完成Cookie用户:[{ck.UserName}](ID:{ck.Id})的所有收藏夹信息、合集信息、关注列表信息、短剧信息同步");
|
||||
Log.Debug($"完成[{ck.UserName}] [列表]同步, 包括 [自定义收藏夹、关注、合集、短剧] ");
|
||||
}
|
||||
|
||||
// 4. 标记首次运行完成(仅执行一次)
|
||||
await douyinCommonService.SetConfigNotFirstRunning();
|
||||
await _douyinCommonService.SetConfigNotFirstRunning();
|
||||
}
|
||||
|
||||
#region 私有通用辅助方法
|
||||
#region 通用核心方法(无冗余,支持调试)
|
||||
/// <summary>
|
||||
/// 通用收藏类数据同步方法(泛型封装,消除冗余)
|
||||
/// 通用收藏类数据同步方法(无复杂委托,参数直观,便于调试)
|
||||
/// </summary>
|
||||
/// <typeparam name="TData">抖音返回的分页数据类型</typeparam>
|
||||
/// <typeparam name="TItem">抖音返回的列表项类型</typeparam>
|
||||
/// <param name="cookie">当前Cookie信息</param>
|
||||
/// <param name="cateType">分类类型</param>
|
||||
/// <param name="dataFetchFunc">分页数据获取委托</param>
|
||||
/// <param name="entityConvertFunc">抖音项转换为DouyinCollectCate的委托</param>
|
||||
/// <param name="hasMoreFunc">判断是否还有更多数据的委托</param>
|
||||
/// <param name="cursorFunc">获取下一页游标值的委托</param>
|
||||
/// <param name="dataListFunc">从分页数据中提取列表的委托</param>
|
||||
/// <param name="logTag">日志标签</param>
|
||||
private async Task SyncCollectDataAsync<TData, TItem>(
|
||||
/// <typeparam name="TData">分页数据类型</typeparam>
|
||||
/// <typeparam name="TItem">列表项类型</typeparam>
|
||||
private async Task SyncCollectGenericAsync<TData, TItem>(
|
||||
DouyinCookie cookie,
|
||||
VideoTypeEnum cateType,
|
||||
Func<string, Task<TData>> dataFetchFunc,
|
||||
// 数据获取方法(最简委托,仅传必要参数)
|
||||
Func<DouyinCookie, string, Task<TData>> dataFetchFunc,
|
||||
// 实体转换方法(内联lambda,调试可直接看到转换逻辑)
|
||||
Func<TItem, DouyinCollectCate> entityConvertFunc,
|
||||
// 分页判断方法
|
||||
Func<TData, bool> hasMoreFunc,
|
||||
Func<TData, string> cursorFunc,
|
||||
Func<TData, List<TItem>> dataListFunc,
|
||||
// 游标获取方法
|
||||
Func<TData, string> getCursorFunc,
|
||||
// 列表提取方法
|
||||
Func<TData, List<TItem>> getDataListFunc,
|
||||
string logTag)
|
||||
{
|
||||
var collectDataList = new List<DouyinCollectCate>();
|
||||
string offset = "0";
|
||||
bool hasMore = true;
|
||||
List<DouyinCollectCate> collectDataList = new List<DouyinCollectCate>();
|
||||
|
||||
try
|
||||
{
|
||||
// 1. 分页获取抖音数据
|
||||
while (hasMore)
|
||||
{
|
||||
var pageData = await dataFetchFunc(offset);
|
||||
if (pageData == null)
|
||||
{
|
||||
//Serilog.Log.Warning($"[{cookie.UserName}] - {logTag}:获取分页数据为空,停止分页");
|
||||
break;
|
||||
}
|
||||
// 1. 获取分页数据(直接调用,调试可断点到具体Service方法)
|
||||
var pageData = await dataFetchFunc(cookie, offset);
|
||||
if (pageData == null) break;
|
||||
|
||||
// 2. 更新分页状态
|
||||
// 2. 更新分页状态(逻辑透明)
|
||||
hasMore = hasMoreFunc(pageData);
|
||||
offset = cursorFunc(pageData);
|
||||
offset = getCursorFunc(pageData);
|
||||
|
||||
// 3. 提取当前页列表数据并转换
|
||||
var currentPageItems = dataListFunc(pageData);
|
||||
if (currentPageItems != null && currentPageItems.Any())
|
||||
// 3. 提取并转换数据(内联逻辑,调试可看每一项转换结果)
|
||||
var currentItems = getDataListFunc(pageData);
|
||||
if (currentItems?.Any() == true)
|
||||
{
|
||||
var convertItems = currentPageItems.Select(entityConvertFunc).ToList();
|
||||
collectDataList.AddRange(convertItems);
|
||||
Serilog.Log.Debug($"[{cookie.UserName}] - {logTag}:获取到{currentPageItems.Count}条数据,累计{collectDataList.Count}条");
|
||||
collectDataList.AddRange(currentItems.Select(entityConvertFunc));
|
||||
Log.Debug($"[{cookie.UserName}] - {logTag}:获取{currentItems.Count}条,累计{collectDataList.Count}条");
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 调用Sync方法同步到数据库
|
||||
// 4. 同步到数据库
|
||||
if (collectDataList.Any())
|
||||
{
|
||||
var (add, update, delete, succ) = await douyinCollectCateService.Sync(
|
||||
collectDataList,
|
||||
cookie.Id,
|
||||
cateType);
|
||||
|
||||
Serilog.Log.Debug($"[{cookie.UserName}] - {logTag}:同步完成,新增{add}条,更新{update}条,删除{delete}条,是否成功:{succ}");
|
||||
var (add, update, delete, succ) = await _douyinCollectCateService.Sync(collectDataList, cookie.Id, cateType);
|
||||
Log.Debug($"[{cookie.UserName}] - {logTag}:同步完成 新增:{add} 更新:{update} 删除:{delete} 成功:{succ}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Serilog.Log.Debug($"[{cookie.UserName}] - {logTag}:无有效数据需要同步");
|
||||
Log.Debug($"[{cookie.UserName}] - {logTag}:无有效数据");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Serilog.Log.Error(ex, $"[{cookie.UserName}] - {logTag}:分页同步失败,异常信息:{ex.Message}");
|
||||
Log.Error(ex, $"[{cookie.UserName}] - {logTag}:同步失败");
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// 关注列表单独同步方法(逻辑特殊,单独封装)
|
||||
/// </summary>
|
||||
/// <param name="cookie">当前Cookie信息</param>
|
||||
/// <param name="config">应用配置</param>
|
||||
#region 关注列表同步(逻辑独立,无重复)
|
||||
private async Task SyncFollowListAsync(DouyinCookie cookie, AppConfig config)
|
||||
{
|
||||
// 1. 校验SecUserId是否有效
|
||||
if (string.IsNullOrWhiteSpace(cookie.SecUserId))
|
||||
{
|
||||
Serilog.Log.Debug($"[{cookie.UserName}] - {LOG_TAG_FOLLOW}:未设置SecUserId,跳过");
|
||||
Log.Debug($"[{cookie.UserName}] - {LOG_TAG_FOLLOW}:未设置SecUserId,跳过");
|
||||
return;
|
||||
}
|
||||
|
||||
var followList = new List<FollowingsItem>();
|
||||
string offset = "0";
|
||||
bool hasMore = true;
|
||||
int total = 0;
|
||||
List<FollowingsItem> followList = new List<FollowingsItem>();
|
||||
FollowErrorDto currentError = null;
|
||||
|
||||
try
|
||||
{
|
||||
// 2. 分页获取关注列表数据
|
||||
while (hasMore)
|
||||
{
|
||||
var (data, error) = await FetchFollowPageDataAsync(cookie, offset);
|
||||
|
||||
// 3. 处理错误信息
|
||||
if (error != null && error.StatusCode != 0)
|
||||
{
|
||||
Serilog.Log.Error($"[{cookie.UserName}] - {LOG_TAG_FOLLOW}:发生错误,状态码:{error.StatusCode},错误信息:{error.StatusMsg}");
|
||||
// 无效Cookie(未登录)直接停止分页
|
||||
if (error.StatusCode == INVALID_COOKIE_STATUS_CODE)
|
||||
var data = await _douyinService.SyncMyFollows(
|
||||
DEFAULT_FOLLOW_COUNT, offset, cookie.SecUserId, cookie.Cookies,
|
||||
async (err) =>
|
||||
{
|
||||
cookie.StatusMsg = "无效";
|
||||
cookie.StatusCode = INVALID_COOKIE_STATUS_CODE;
|
||||
currentError = err;
|
||||
cookie.StatusMsg = err.StatusCode == INVALID_COOKIE_STATUS_CODE ? "无效" : "正常";
|
||||
cookie.StatusCode = err.StatusCode;
|
||||
await _dyCookieService.UpdateAsync(cookie);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 4. 处理有效数据
|
||||
if (data != null)
|
||||
if (currentError != null && currentError.StatusCode != 0)
|
||||
{
|
||||
// 5. 更新MyUserId(若未设置)
|
||||
if (string.IsNullOrWhiteSpace(cookie.MyUserId))
|
||||
{
|
||||
cookie.MyUserId = data.MySelfUserId;
|
||||
await _dyCookieService.UpdateAsync(cookie);
|
||||
}
|
||||
|
||||
// 6. 更新分页状态和数据
|
||||
total = data.Total;
|
||||
hasMore = data.HasMore;
|
||||
offset = data.Offset.ToString();
|
||||
|
||||
if (data.Followings != null && data.Followings.Any())
|
||||
{
|
||||
followList.AddRange(data.Followings);
|
||||
Serilog.Log.Debug($"[{cookie.UserName}] - {LOG_TAG_FOLLOW}:获取到{data.Followings.Count}条关注数据,累计{followList.Count}条");
|
||||
}
|
||||
|
||||
// 7. 非首次运行时,仅同步第一页数据
|
||||
if (!config.IsFirstRunning)
|
||||
{
|
||||
hasMore = false;
|
||||
//Serilog.Log.Debug($"[{cookie.UserName}] - {LOG_TAG_FOLLOW}:非首次运行,停止分页获取");
|
||||
}
|
||||
Log.Error($"[{cookie.UserName}] - {LOG_TAG_FOLLOW}:错误 状态码:{currentError.StatusCode} 信息:{currentError.StatusMsg}");
|
||||
if (currentError.StatusCode == INVALID_COOKIE_STATUS_CODE) break;
|
||||
}
|
||||
else
|
||||
|
||||
if (data == null) break;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(cookie.MyUserId))
|
||||
{
|
||||
Serilog.Log.Warning($"[{cookie.UserName}] - {LOG_TAG_FOLLOW}:获取分页数据为空,停止分页");
|
||||
break;
|
||||
cookie.MyUserId = data.MySelfUserId;
|
||||
await _dyCookieService.UpdateAsync(cookie);
|
||||
}
|
||||
|
||||
total = data.Total;
|
||||
hasMore = data.HasMore;
|
||||
offset = data.Offset.ToString();
|
||||
|
||||
if (data.Followings?.Any() == true)
|
||||
{
|
||||
followList.AddRange(data.Followings);
|
||||
Log.Debug($"[{cookie.UserName}] - {LOG_TAG_FOLLOW}:获取{data.Followings.Count}条,累计{followList.Count}条");
|
||||
}
|
||||
|
||||
if (!config.IsFirstRunning) hasMore = false;
|
||||
}
|
||||
|
||||
// 8. 同步关注列表到数据库
|
||||
if (followList.Any())
|
||||
{
|
||||
var (add, update, succ) = await _followService.Sync(followList, cookie);
|
||||
Serilog.Log.Information($"[{cookie.UserName}] - {LOG_TAG_FOLLOW}:同步完成,新增{add}条,更新{update}条,是否成功:{succ},总关注数:{total}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Serilog.Log.Debug($"[{cookie.UserName}] - {LOG_TAG_FOLLOW}:无有效关注数据需要同步");
|
||||
Log.Information($"[{cookie.UserName}] - {LOG_TAG_FOLLOW}:同步完成 新增:{add} 更新:{update} 成功:{succ} 总关注数:{total}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Serilog.Log.Error(ex, $"[{cookie.UserName}] - {LOG_TAG_FOLLOW}:同步失败,异常信息:{ex.Message}");
|
||||
Log.Error(ex, $"[{cookie.UserName}] - {LOG_TAG_FOLLOW}:同步失败");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 分页获取关注列表数据(封装回调逻辑,简化代码)
|
||||
/// </summary>
|
||||
/// <param name="cookie">当前Cookie信息</param>
|
||||
/// <param name="offset">分页偏移量</param>
|
||||
private async Task<(DouyinFollowInfoResponse Data, FollowErrorDto Error)> FetchFollowPageDataAsync(DouyinCookie cookie, string offset)
|
||||
{
|
||||
DouyinFollowInfoResponse resultData = null;
|
||||
FollowErrorDto resultError = null;
|
||||
resultData = await _douyinService.SyncMyFollows(
|
||||
DEFAULT_FOLLOW_COUNT,
|
||||
offset,
|
||||
cookie.SecUserId,
|
||||
cookie.Cookies,
|
||||
async (err) =>
|
||||
{
|
||||
resultError = err;
|
||||
// 更新Cookie状态(异步不阻塞)
|
||||
cookie.StatusMsg = err.StatusCode == INVALID_COOKIE_STATUS_CODE ? "无效" : "正常";
|
||||
cookie.StatusCode = err.StatusCode;
|
||||
await _dyCookieService.UpdateAsync(cookie);
|
||||
});
|
||||
|
||||
// 若原方法是回调式无返回值,需调整封装逻辑
|
||||
return (resultData, resultError);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
+20
-4
@@ -13,19 +13,35 @@ namespace dy.net.job
|
||||
}
|
||||
|
||||
|
||||
protected override VideoTypeEnum VideoType => VideoTypeEnum.dy_favorite;
|
||||
protected override VideoTypeEnum VideoType => VideoTypeEnum.dy_mix;
|
||||
|
||||
|
||||
protected override async Task<DouyinVideoInfoResponse> FetchVideoData(DouyinCookie cookie, string cursor,DouyinFollowed followed,DouyinCollectCate cate)
|
||||
{
|
||||
return await douyinHttpClientService.SyncFavoriteVideos(count, cursor, cookie.SecUserId, cookie.Cookies);
|
||||
return await douyinHttpClientService.SyncMixViedosByMixId(cursor,count,cookie.Cookies,cate.XId);
|
||||
}
|
||||
|
||||
protected override bool ShouldContinueSync(DouyinCookie cookie, DouyinVideoInfoResponse data, DouyinFollowed followed=null)
|
||||
{
|
||||
return data != null && data.HasMore == 1 && cookie.FavHasSyncd == 0;
|
||||
return data != null && data.HasMore == 1;
|
||||
}
|
||||
protected override string CreateSaveFolder(DouyinCookie cookie, Aweme item, AppConfig config, DouyinFollowed followed, DouyinCollectCate cate)
|
||||
{
|
||||
if (cate != null)
|
||||
{
|
||||
var folder = Path.Combine(cookie.SavePath,VideoType.GetDesc(), DouyinFileNameHelper.SanitizeLinuxFileName(cate.SaveFolder, cate.Name, true));
|
||||
if (!Directory.Exists(folder)) Directory.CreateDirectory(folder);
|
||||
return folder;
|
||||
}
|
||||
else
|
||||
{
|
||||
return base.CreateSaveFolder(cookie, item, config, followed, cate);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected override string GetAuthorAvatarBasePath(DouyinCookie cookie)
|
||||
{
|
||||
return Path.Combine(cookie.SavePath, "author");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,5 +25,24 @@ namespace dy.net.job
|
||||
{
|
||||
return data != null && data.HasMore == 1;
|
||||
}
|
||||
|
||||
protected override string CreateSaveFolder(DouyinCookie cookie, Aweme item, AppConfig config, DouyinFollowed followed, DouyinCollectCate cate)
|
||||
{
|
||||
if (cate != null)
|
||||
{
|
||||
var folder = Path.Combine(cookie.SavePath, VideoType.GetDesc(), DouyinFileNameHelper.SanitizeLinuxFileName(cate.SaveFolder, cate.Name, true));
|
||||
if (!Directory.Exists(folder)) Directory.CreateDirectory(folder);
|
||||
return folder;
|
||||
}
|
||||
else
|
||||
{
|
||||
return base.CreateSaveFolder(cookie, item, config, followed, cate);
|
||||
}
|
||||
}
|
||||
|
||||
protected override string GetAuthorAvatarBasePath(DouyinCookie cookie)
|
||||
{
|
||||
return Path.Combine(cookie.SavePath, "author");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user