Files
douyin/service/DouyinQuartzJobService.cs
T
2026-01-23 22:24:00 +08:00

316 lines
12 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using dy.net.job;
using dy.net.model.dto;
using dy.net.utils;
using Quartz;
using Serilog;
namespace dy.net.service
{
/// <summary>
/// 抖音相关定时任务服务
/// </summary>
public class DouyinQuartzJobService
{
private readonly ISchedulerFactory _schedulerFactory;
private const string DefaultJobGroup = "dysync.net";
private const int DefaultIntervalMinutes = 30;
private const int DefaultCronStartDelaySeconds = 30;
private const int DefaultSimpleStartDelaySeconds = 3;
// 任务配置信息(修复了series任务的Key重复问题,确保每个任务Key唯一)
public static Dictionary<string, JobConfig> JobConfigs { get; } = new()
{
{
VideoTypeEnum.dy_collects.GetDesc(),
new JobConfig(
typeof(DouyinCollectSyncJob),
"dy.job.key.collect",
"dy.trigger.key.collect",
"抖音收藏同步任务")
},
{
VideoTypeEnum.dy_favorite.GetDesc(),
new JobConfig(
typeof(DouyinFavoritSyncJob),
"dy.job.key.favorite",
"dy.trigger.key.favorite",
"抖音点赞同步任务")
},
{
VideoTypeEnum.dy_follows.GetDesc(),
new JobConfig(
typeof(DouyinFollowedSyncJob),
"dy.job.key.followed",
"dy.trigger.key.followed",
"抖音关注博主作品同步任务")
},
{
"关注列表",
new JobConfig(
typeof(DouyinFollowsAndCollnectsSyncJob),
"dy.job.key.follow_user",
"dy.trigger.key.follow_user",
"抖音关注列表同步任务")
},
{
VideoTypeEnum.dy_custom_collect.GetDesc(),
new JobConfig(
typeof(DouyinCollectCustomSyncJob),
"dy.job.key.custom_collect",
"dy.trigger.key.custom_collect",
"抖音自定义收藏夹列表同步任务")
},
{
VideoTypeEnum.dy_mix.GetDesc(),
new JobConfig(
typeof(DouyinMixSyncJob),
"dy.job.key.mix",
"dy.trigger.key.mix",
"抖音收藏夹合集同步任务")
},
{
VideoTypeEnum.dy_series.GetDesc(),
new JobConfig(
typeof(DouyinSeriesSyncJob),
"dy.job.key.series",
"dy.trigger.key.series",
"抖音收藏夹短剧同步任务")
},
{
"follow_user_once",
new JobConfig(
typeof(DouyinFollowsAndCollnectsSyncJob),
"dy.job.key.follow_user_once",
"dy.trigger.key.follow_user_once",
"抖音关注同步任务(单次执行)")
}
};
public DouyinQuartzJobService(ISchedulerFactory schedulerFactory)
{
_schedulerFactory = schedulerFactory ?? throw new ArgumentNullException(nameof(schedulerFactory));
}
/// <summary>
/// 启动所有抖音相关定时任务(所有任务独立执行)
/// </summary>
/// <param name="expression">Cron表达式或间隔分钟数(所有任务使用相同的执行频率)</param>
/// <returns>是否启动成功</returns>
public async Task<bool> InitOrReStartAllJobs(string expression)
{
if (string.IsNullOrWhiteSpace(expression))
{
Log.Debug("定时任务表达式为空,使用默认配置({DefaultMinutes}分钟)", DefaultIntervalMinutes);
expression = DefaultIntervalMinutes.ToString();
}
try
{
var scheduler = await _schedulerFactory.GetScheduler();
// 移除所有已存在的任务(避免重复调度)
await RemoveAllExistingJobs(scheduler);
// 执行任务启动逻辑
foreach (var jobKey in JobConfigs.Keys)
{
if (jobKey == "follow_user_once")
continue;
if (jobKey == "follow_user") expression = "60";
var startSuccess = await StartJobAsync(jobKey, expression);
if (startSuccess)
{
Log.Debug($"成功启动任务:{jobKey},执行频率:{expression}");
}
else
{
Log.Error($"启动任务失败:{jobKey}");
}
}
Log.Information($"共启动 {JobConfigs.Count - 1} 个定时任务");
//await StartJobAsync(VideoTypeEnum.dy_custom_collect.GetDesc(), expression);
//await StartFollowJobOnceAsync();
return true;
}
catch (Exception ex)
{
Log.Error(ex, "【任务服务】初始化所有定时任务异常");
return false;
}
}
/// <summary>
/// 启动关注同步任务(单次执行)
/// </summary>
public async Task<bool> StartFollowJobOnceAsync()
{
return await StartOneTimeJobAsync("follow_user_once");
}
/// <summary>
/// 移除所有已存在的任务(避免重复调度)
/// </summary>
private async Task RemoveAllExistingJobs(IScheduler scheduler)
{
var jobKeys = JobConfigs.Values.Select(config => new JobKey(config.JobKey, DefaultJobGroup)).ToList();
foreach (var jobKey in jobKeys)
{
if (await scheduler.CheckExists(jobKey))
{
Log.Information("【任务服务】移除已存在的任务: {JobKey}", jobKey);
await scheduler.DeleteJob(jobKey);
}
}
}
/// <summary>
/// 启动指定定时任务(独立执行,无依赖触发)
/// </summary>
/// <param name="configKey">任务配置Key(如:collect、favorite</param>
/// <param name="expression">定时表达式(Cron或间隔分钟数)</param>
/// <returns>是否启动成功</returns>
public async Task<bool> StartJobAsync(string configKey, string expression)
{
if (!JobConfigs.TryGetValue(configKey, out var jobConfig))
{
Log.Error("【任务服务】找不到任务配置: {ConfigKey}", configKey);
return false;
}
try
{
var scheduler = await _schedulerFactory.GetScheduler();
var jobKey = new JobKey(jobConfig.JobKey, DefaultJobGroup);
var triggerKey = new TriggerKey(jobConfig.TriggerKey, DefaultJobGroup);
// 移除已存在的任务(防止重复执行)
await RemoveExistingJobAsync(scheduler, jobKey);
// 创建任务详情(保留禁止并发执行,避免同一任务重复运行)
var jobDetail = JobBuilder.Create(jobConfig.JobType)
.WithIdentity(jobKey)
.WithDescription(jobConfig.Description)
.DisallowConcurrentExecution() // 禁止同一任务并发执行
.Build();
// 创建定时触发器(仅使用定时触发,移除依赖触发逻辑)
ITrigger trigger = CreateScheduledTrigger(triggerKey, expression, jobConfig.Description);
// 调度任务
await scheduler.ScheduleJob(jobDetail, trigger);
Log.Information("【任务服务】启动任务成功 - 任务描述: {JobDescription}, 执行频率: {Expression}",
jobConfig.Description,
expression);
return true;
}
catch (Exception ex)
{
Log.Error(ex, "【任务服务】启动任务失败 - 任务描述: {JobDescription}", jobConfig.Description);
return false;
}
}
/// <summary>
/// 启动单次执行任务
/// </summary>
private async Task<bool> StartOneTimeJobAsync(string configKey)
{
if (!JobConfigs.TryGetValue(configKey, out var jobConfig))
{
Log.Error("【任务服务】找不到任务配置: {ConfigKey}", configKey);
return false;
}
try
{
var scheduler = await _schedulerFactory.GetScheduler();
var jobKey = new JobKey(jobConfig.JobKey, DefaultJobGroup);
var triggerKey = new TriggerKey(jobConfig.TriggerKey, DefaultJobGroup);
await RemoveExistingJobAsync(scheduler, jobKey);
var jobDetail = JobBuilder.Create(jobConfig.JobType)
.WithIdentity(jobKey)
.WithDescription(jobConfig.Description)
.DisallowConcurrentExecution()
.Build();
var trigger = TriggerBuilder.Create()
.WithIdentity(triggerKey)
.WithDescription($"{jobConfig.Description} - 单次执行")
.StartNow()
.Build();
await scheduler.ScheduleJob(jobDetail, trigger);
Log.Information("【任务服务】启动单次任务成功 - 任务描述: {JobDescription}", jobConfig.Description);
return true;
}
catch (Exception ex)
{
Log.Error(ex, "【任务服务】启动单次任务失败 - 任务描述: {JobDescription}", jobConfig.Description);
return false;
}
}
/// <summary>
/// 创建定时触发器(支持Cron表达式或分钟间隔)
/// </summary>
private static ITrigger CreateScheduledTrigger(TriggerKey triggerKey, string expression, string jobDescription)
{
// Cron表达式格式
if (CronExpression.IsValidExpression(expression))
{
return TriggerBuilder.Create()
.WithIdentity(triggerKey)
.WithDescription($"{jobDescription} - Cron调度")
.WithCronSchedule(expression)
.StartAt(DateTime.Now.AddSeconds(DefaultCronStartDelaySeconds))
.Build();
}
// 数字间隔格式(分钟)
if (int.TryParse(expression, out int intervalMinutes))
{
intervalMinutes = Math.Max(1, intervalMinutes); // 最小间隔1分钟
return TriggerBuilder.Create()
.WithIdentity(triggerKey)
.WithDescription($"{jobDescription} - 间隔{intervalMinutes}分钟调度")
.StartAt(DateTime.Now.AddSeconds(DefaultSimpleStartDelaySeconds))
.WithSimpleSchedule(x => x
.WithIntervalInMinutes(intervalMinutes)
.RepeatForever())
.Build();
}
// 无效表达式,使用默认配置
Log.Warning("【任务服务】无效的任务表达式: {Expression},使用默认间隔{DefaultMinutes}分钟",
expression, DefaultIntervalMinutes);
return TriggerBuilder.Create()
.WithIdentity(triggerKey)
.WithDescription($"{jobDescription} - 默认间隔调度")
.StartAt(DateTime.Now.AddSeconds(DefaultSimpleStartDelaySeconds))
.WithSimpleSchedule(x => x
.WithIntervalInMinutes(DefaultIntervalMinutes)
.RepeatForever())
.Build();
}
/// <summary>
/// 移除已存在的任务
/// </summary>
private static async Task RemoveExistingJobAsync(IScheduler scheduler, JobKey jobKey)
{
if (await scheduler.CheckExists(jobKey))
{
Log.Information("【任务服务】移除已存在的任务: {JobKey}", jobKey);
await scheduler.DeleteJob(jobKey);
}
}
}
}