258 lines
11 KiB
C#
258 lines
11 KiB
C#
using dy.net.model.dto;
|
|
using dy.net.model.entity;
|
|
using dy.net.model.response;
|
|
using dy.net.service;
|
|
using Quartz;
|
|
using Serilog;
|
|
|
|
namespace dy.net.job
|
|
{
|
|
[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;
|
|
private readonly DouyinLiveStatusService _douyinLiveStatusService;
|
|
|
|
// 常量定义
|
|
private const string DEFAULT_FOLLOW_COUNT = "20";
|
|
private const int INVALID_COOKIE_STATUS_CODE = 8;
|
|
private const string LOG_TAG_COLLECT = "收藏列表";
|
|
private const string LOG_TAG_MIX = "合集列表";
|
|
private const string LOG_TAG_SERIES = "短剧列表";
|
|
private const string LOG_TAG_FOLLOW = "关注列表";
|
|
|
|
// 构造函数注入
|
|
public DouyinFollowsAndCollnectsSyncJob(
|
|
DouyinCookieService dyCookieService,
|
|
DouyinHttpClientService douyinService,
|
|
DouyinFollowService followService,
|
|
DouyinCommonService douyinCommonService,
|
|
DouyinCollectCateService douyinCollectCateService,
|
|
DouyinLiveStatusService douyinLiveStatusService)
|
|
{
|
|
_dyCookieService = dyCookieService;
|
|
_douyinService = douyinService;
|
|
_followService = followService;
|
|
_douyinCommonService = douyinCommonService;
|
|
_douyinCollectCateService = douyinCollectCateService;
|
|
_douyinLiveStatusService = douyinLiveStatusService;
|
|
}
|
|
|
|
public async Task Execute(IJobExecutionContext context)
|
|
{
|
|
var cookies = await _dyCookieService.GetOpendCookiesAsync();
|
|
if (cookies == null || !cookies.Any())
|
|
{
|
|
Log.Debug("当前无可用的抖音Cookie,同步任务跳过");
|
|
return;
|
|
}
|
|
|
|
var conf = _douyinCommonService.GetConfig();
|
|
|
|
foreach (var ck in cookies)
|
|
{
|
|
//同步关注列表
|
|
await SyncFollowListAsync(ck, conf);
|
|
|
|
if (ck.UseCollectFolder)
|
|
{
|
|
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 = item.CollectsName,
|
|
Sync = false,
|
|
XId = item.CollectsId,
|
|
Total = item.TotalNumber
|
|
},
|
|
data => data?.HasMore ?? false,
|
|
data => data?.Cursor.ToString() ?? "0",
|
|
data => data?.CollectsList,
|
|
LOG_TAG_COLLECT);
|
|
}
|
|
|
|
if (ck.DownMix)
|
|
{
|
|
await SyncCollectGenericAsync(ck, VideoTypeEnum.dy_mix,
|
|
(cookie, offset) => _douyinService.SyncMixList(cookie.Cookies, offset),
|
|
item => new DouyinCollectCate
|
|
{
|
|
CookieId = ck.Id,
|
|
CateType = VideoTypeEnum.dy_mix,
|
|
Name = item.MixName,
|
|
Sync = false,
|
|
XId = item.MixId,
|
|
Total = item?.Statis?.UpdatedToEpisode ?? 0,
|
|
CoverUrl = item.CoverUrl?.UrlList?.LastOrDefault()
|
|
},
|
|
data => (data?.HasMore ?? 0) == 1,
|
|
data => data?.Cursor.ToString() ?? "0",
|
|
data => data?.MixInfos,
|
|
LOG_TAG_MIX);
|
|
}
|
|
|
|
if (ck.DownSeries)
|
|
{
|
|
await SyncCollectGenericAsync(ck, VideoTypeEnum.dy_series,
|
|
(cookie, offset) => _douyinService.SyncSeriesList(cookie.Cookies, offset),
|
|
item => new DouyinCollectCate
|
|
{
|
|
CookieId = ck.Id,
|
|
CateType = VideoTypeEnum.dy_series,
|
|
Name = item.SeriesName,
|
|
Sync = false,
|
|
XId = item.SeriesId,
|
|
Total = item?.Stats?.TotalEpisodeCount ?? 0,
|
|
CoverUrl = item.CoverImage?.ImageUrlList?.FirstOrDefault()
|
|
},
|
|
data => (data?.HasMore ?? 0) == 1,
|
|
data => data?.Cursor.ToString() ?? "0",
|
|
data => data?.SeriesList,
|
|
LOG_TAG_SERIES);
|
|
}
|
|
|
|
Log.Debug($"[{ck.UserName}][基础数据]同步完成,包括 [收藏列表、关注列表、合集列表、短剧列表],这不是同步视频!!! ");
|
|
}
|
|
|
|
}
|
|
|
|
#region 通用核心方法(无冗余,支持调试)
|
|
/// <summary>
|
|
/// 通用收藏类数据同步方法(无复杂委托,参数直观,便于调试)
|
|
/// </summary>
|
|
/// <typeparam name="TData">分页数据类型</typeparam>
|
|
/// <typeparam name="TItem">列表项类型</typeparam>
|
|
private async Task SyncCollectGenericAsync<TData, TItem>(
|
|
DouyinCookie cookie,
|
|
VideoTypeEnum cateType,
|
|
// 数据获取方法(最简委托,仅传必要参数)
|
|
Func<DouyinCookie, string, Task<TData>> dataFetchFunc,
|
|
// 实体转换方法(内联lambda,调试可直接看到转换逻辑)
|
|
Func<TItem, DouyinCollectCate> entityConvertFunc,
|
|
// 分页判断方法
|
|
Func<TData, bool> hasMoreFunc,
|
|
// 游标获取方法
|
|
Func<TData, string> getCursorFunc,
|
|
// 列表提取方法
|
|
Func<TData, List<TItem>> getDataListFunc,
|
|
string logTag)
|
|
{
|
|
var collectDataList = new List<DouyinCollectCate>();
|
|
string offset = "0";
|
|
bool hasMore = true;
|
|
|
|
try
|
|
{
|
|
while (hasMore)
|
|
{
|
|
// 1. 获取分页数据(直接调用,调试可断点到具体Service方法)
|
|
var pageData = await dataFetchFunc(cookie, offset);
|
|
if (pageData == null) break;
|
|
|
|
// 2. 更新分页状态(逻辑透明)
|
|
hasMore = hasMoreFunc(pageData);
|
|
offset = getCursorFunc(pageData);
|
|
|
|
// 3. 提取并转换数据(内联逻辑,调试可看每一项转换结果)
|
|
var currentItems = getDataListFunc(pageData);
|
|
if (currentItems?.Any() == true)
|
|
{
|
|
collectDataList.AddRange(currentItems.Select(entityConvertFunc));
|
|
Log.Debug($"[{cookie.UserName}][{logTag}]:获取{currentItems.Count}条,累计{collectDataList.Count}条");
|
|
}
|
|
}
|
|
|
|
// 4. 同步到数据库
|
|
if (collectDataList.Any())
|
|
{
|
|
var (add, update, delete, succ) = await _douyinCollectCateService.Sync(collectDataList, cookie.Id, cateType);
|
|
Log.Debug($"[{cookie.UserName}][{logTag}]:同步完成 新增:{add} 更新:{update} 删除:{delete} 成功:{succ}");
|
|
}
|
|
else
|
|
{
|
|
Log.Debug($"[{cookie.UserName}][{logTag}]:无有效数据");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log.Error(ex, $"[{cookie.UserName}][{logTag}]:同步失败");
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region 关注列表同步(逻辑独立,无重复)
|
|
private async Task SyncFollowListAsync(DouyinCookie cookie, AppConfig config)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(cookie.SecUserId))
|
|
{
|
|
Log.Debug($"[{cookie.UserName}][{LOG_TAG_FOLLOW}]:未设置SecUserId,如果要同步关注列表(你关注的博主),需要在授权页面填写你的sce_user_id");
|
|
return;
|
|
}
|
|
|
|
var followList = new List<FollowingsItem>();
|
|
string offset = "0";
|
|
bool hasMore = true;
|
|
int total = 0;
|
|
|
|
try
|
|
{
|
|
while (hasMore)
|
|
{
|
|
var data = await _douyinService.SyncMyFollows(
|
|
DEFAULT_FOLLOW_COUNT, offset, cookie.SecUserId, cookie.Cookies);
|
|
|
|
if (data == null)
|
|
{
|
|
Log.Error($"[{cookie.UserName}] - {LOG_TAG_FOLLOW}同步异常,请检查cookie");
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
cookie.StatusCode = data.StatusCode;
|
|
cookie.StatusMsg = data.StatusCode == 0 ? "正常" : "异常";
|
|
await _dyCookieService.UpdateAsync(cookie);
|
|
}
|
|
|
|
if (string.IsNullOrWhiteSpace(cookie.MyUserId))
|
|
{
|
|
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}条");
|
|
}
|
|
//Serilog.Log.Debug("isRestart2=" + config.IsFirstRunning);
|
|
if (!config.IsFirstRunning) hasMore = false;
|
|
}
|
|
|
|
if (followList.Any())
|
|
{
|
|
var (add, update, succ) = await _followService.Sync(followList, cookie);
|
|
await _douyinLiveStatusService.ApplyFollowListStatusesAsync(followList, cookie);
|
|
Log.Debug($"[{cookie.UserName}][{LOG_TAG_FOLLOW}]同步完成 新增:{add} 更新:{update} 成功:{succ} 总关注数:{total},这是同步关注列表,这不是同步视频!!!");
|
|
}
|
|
await _douyinCommonService.SetConfigNotFirstRunning();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log.Error(ex, $"[{cookie.UserName}][{LOG_TAG_FOLLOW}]:同步失败");
|
|
}
|
|
}
|
|
#endregion
|
|
}
|
|
}
|