This commit is contained in:
jianzhichu
2026-01-26 22:42:05 +08:00
parent 85986cdaa3
commit edbcc13086
19 changed files with 438 additions and 330 deletions
+4 -1
View File
@@ -169,6 +169,8 @@ namespace dy.net.Controllers
if (!cookieValid) if (!cookieValid)
return ApiResult.Fail("Cookie无效,请按照文档提示重新获取有效Cookie,不要使用插件获取cookie"); return ApiResult.Fail("Cookie无效,请按照文档提示重新获取有效Cookie,不要使用插件获取cookie");
dyUserCookies.StatusCode = 0;
dyUserCookies.StatusMsg = "正常";
// 4. 保存到数据库 // 4. 保存到数据库
var saved = await dyCookieService.Add(dyUserCookies); var saved = await dyCookieService.Add(dyUserCookies);
return saved ? ApiResult.Success() : ApiResult.Fail("添加失败"); return saved ? ApiResult.Success() : ApiResult.Fail("添加失败");
@@ -229,7 +231,8 @@ namespace dy.net.Controllers
{ {
return ApiResult.Fail("Cookie无效,请按照文档提示重新获取有效Cookie,不要使用插件获取cookie"); return ApiResult.Fail("Cookie无效,请按照文档提示重新获取有效Cookie,不要使用插件获取cookie");
} }
dyUserCookies.StatusCode = 0;
dyUserCookies.StatusMsg = "正常";
if (dyUserCookies.Id == "0") if (dyUserCookies.Id == "0")
{ {
dyUserCookies.Id = IdGener.GetLong().ToString(); dyUserCookies.Id = IdGener.GetLong().ToString();
+5 -1
View File
@@ -103,7 +103,11 @@ namespace dy.net.Controllers
return await OpenOrCloseSync(dto); return await OpenOrCloseSync(dto);
} }
/// <summary>
/// 删除关注对象
/// </summary>
/// <param name="dto"></param>
/// <returns></returns>
[HttpPost("delete")] [HttpPost("delete")]
public async Task<IActionResult> DeleteFollow(FollowUpdateDto dto) public async Task<IActionResult> DeleteFollow(FollowUpdateDto dto)
{ {
+2 -1
View File
@@ -229,8 +229,9 @@ namespace dy.net
var config = commonService.InitConfig(); var config = commonService.InitConfig();
//Serilog.Log.Debug("isRestart1=" + config.IsFirstRunning); //Serilog.Log.Debug("isRestart1=" + config.IsFirstRunning);
await cookieService.UpdateCookieToSupportOldVersionAsync();
//if (!isDevelopment) if (!isDevelopment)
{ {
var cookie = await cookieService.GetOpendCookies(); var cookie = await cookieService.GetOpendCookies();
if (cookie != null && cookie.Any()) if (cookie != null && cookie.Any())
+1 -1
View File
@@ -17,6 +17,6 @@
<SelfContained>false</SelfContained> <SelfContained>false</SelfContained>
<!--<DockerArch>x86</DockerArch>--> <!--<DockerArch>x86</DockerArch>-->
<!--<DockerArch>arm</DockerArch> --> <!--<DockerArch>arm</DockerArch> -->
<!--<DockerCoreVersion>2.0.5</DockerCoreVersion>--> <!--<DockerCoreVersion>2.0.6</DockerCoreVersion>-->
</PropertyGroup> </PropertyGroup>
</Project> </Project>
@@ -3,7 +3,7 @@
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<_PublishTargetUrl>E:\code\dysync\bin\Release\net6.0\publish\</_PublishTargetUrl> <_PublishTargetUrl>E:\code\dysync\bin\Release\net6.0\publish\</_PublishTargetUrl>
<History>True|2026-01-25T13:27:13.9893777Z||;True|2026-01-24T21:45:22.8493624+08:00||;True|2026-01-24T21:31:35.4635265+08:00||;True|2026-01-24T21:27:31.3098506+08:00||;True|2026-01-24T15:47:01.6934638+08:00||;True|2026-01-24T15:32:35.6316009+08:00||;True|2026-01-24T15:16:28.7216639+08:00||;True|2026-01-23T22:14:26.8066324+08:00||;True|2026-01-23T13:36:48.4086406+08:00||;True|2026-01-23T13:30:27.2924204+08:00||;True|2026-01-23T13:02:53.3219368+08:00||;True|2026-01-23T13:01:54.3466638+08:00||;True|2026-01-23T13:01:28.8560809+08:00||;True|2026-01-22T23:18:32.0400436+08:00||;True|2026-01-22T22:29:29.5746209+08:00||;True|2026-01-22T21:32:21.7284081+08:00||;True|2026-01-22T21:20:26.5985955+08:00||;False|2026-01-22T21:20:21.1372539+08:00||;True|2026-01-22T21:11:18.3771739+08:00||;True|2026-01-22T20:58:55.9862778+08:00||;True|2026-01-22T20:51:48.6699461+08:00||;False|2026-01-22T20:51:42.2079210+08:00||;True|2026-01-22T20:50:34.7280114+08:00||;True|2026-01-22T20:50:11.1588492+08:00||;True|2026-01-22T16:11:14.0626037+08:00||;True|2026-01-22T15:53:16.6377347+08:00||;True|2026-01-22T15:45:34.0935091+08:00||;True|2026-01-22T12:44:28.1577014+08:00||;True|2026-01-22T12:40:45.7449335+08:00||;True|2026-01-22T12:39:07.0683214+08:00||;True|2026-01-22T09:55:49.7086190+08:00||;True|2026-01-22T01:37:53.6972395+08:00||;True|2026-01-22T01:34:14.2142463+08:00||;True|2026-01-22T01:32:50.9228911+08:00||;True|2026-01-22T01:29:43.8871986+08:00||;True|2026-01-22T01:29:40.7838709+08:00||;True|2026-01-22T01:27:34.4763970+08:00||;True|2026-01-22T01:26:55.0117367+08:00||;True|2026-01-22T01:23:19.4789587+08:00||;True|2026-01-19T10:19:44.8997385+08:00||;True|2026-01-19T10:11:10.2305307+08:00||;True|2026-01-17T00:52:08.8180346+08:00||;False|2026-01-17T00:52:03.1829000+08:00||;True|2026-01-17T00:45:04.0498090+08:00||;True|2026-01-16T21:00:57.7239379+08:00||;True|2026-01-16T21:00:03.3912989+08:00||;True|2026-01-16T20:56:08.5505592+08:00||;True|2026-01-16T20:46:32.3067302+08:00||;True|2026-01-15T22:18:57.9835738+08:00||;True|2026-01-15T22:07:25.4753938+08:00||;True|2026-01-15T22:07:14.8243754+08:00||;True|2026-01-15T21:40:13.2613927+08:00||;True|2026-01-15T20:57:56.6291682+08:00||;True|2026-01-15T20:57:47.1363536+08:00||;True|2026-01-15T20:08:44.4710883+08:00||;True|2026-01-15T20:04:51.8284314+08:00||;False|2026-01-15T20:04:45.3258155+08:00||;True|2026-01-15T19:59:11.7907672+08:00||;True|2026-01-15T01:10:48.9066941+08:00||;True|2026-01-15T01:03:23.6216975+08:00||;True|2026-01-15T01:03:16.3364591+08:00||;False|2026-01-15T01:03:10.5654436+08:00||;True|2026-01-15T00:49:21.6559622+08:00||;True|2026-01-15T00:10:42.7288439+08:00||;True|2026-01-15T00:06:28.4241682+08:00||;True|2026-01-13T22:34:15.2372411+08:00||;True|2026-01-13T22:26:46.4522947+08:00||;True|2026-01-13T22:15:56.9735179+08:00||;True|2026-01-13T22:15:51.8250677+08:00||;False|2026-01-13T22:15:46.6608690+08:00||;True|2026-01-13T22:03:06.4683237+08:00||;False|2026-01-13T22:03:01.2036027+08:00||;True|2026-01-13T21:36:05.8776209+08:00||;True|2026-01-13T21:33:02.0766289+08:00||;False|2026-01-13T21:32:57.1976141+08:00||;True|2026-01-13T19:14:35.2519439+08:00||;True|2026-01-13T15:07:50.0836745+08:00||;False|2026-01-13T15:06:35.5929685+08:00||;True|2026-01-13T10:21:41.6012776+08:00||;True|2026-01-13T09:57:25.4788847+08:00||;True|2026-01-13T09:53:14.4900360+08:00||;True|2026-01-13T09:23:53.6465295+08:00||;True|2026-01-12T22:04:14.5886955+08:00||;False|2026-01-12T22:04:08.2008101+08:00||;True|2026-01-12T21:53:46.9947176+08:00||;True|2026-01-12T21:11:15.8358024+08:00||;True|2026-01-12T21:09:51.8663228+08:00||;True|2026-01-11T21:25:07.5052845+08:00||;False|2026-01-11T21:24:27.5744557+08:00||;True|2026-01-11T19:59:15.4734611+08:00||;False|2026-01-11T19:59:04.9543339+08:00||;True|2026-01-11T18:51:30.6649269+08:00||;True|2026-01-08T21:07:57.5094466+08:00||;True|2026-01-08T14:51:18.2266231+08:00||;True|2026-01-08T14:31:55.1137120+08:00||;True|2026-01-08T00:16:24.4451490+08:00||;True|2026-01-08T00:14:20.7430793+08:00||;True|2026-01-08T00:07:46.7228194+08:00||;True|2026-01-07T23:52:13.3290082+08:00||;True|2026-01-07T23:49:28.4838650+08:00||;</History> <History>True|2026-01-26T14:30:43.5083924Z||;False|2026-01-26T22:30:33.7355213+08:00||;True|2026-01-26T22:26:26.0218548+08:00||;True|2026-01-26T22:21:32.3171217+08:00||;True|2026-01-26T22:20:40.3916780+08:00||;True|2026-01-26T22:16:19.1017273+08:00||;True|2026-01-26T22:13:34.2028883+08:00||;True|2026-01-26T21:46:23.7117812+08:00||;True|2026-01-26T21:41:19.6963033+08:00||;True|2026-01-25T21:27:13.9893777+08:00||;True|2026-01-24T21:45:22.8493624+08:00||;True|2026-01-24T21:31:35.4635265+08:00||;True|2026-01-24T21:27:31.3098506+08:00||;True|2026-01-24T15:47:01.6934638+08:00||;True|2026-01-24T15:32:35.6316009+08:00||;True|2026-01-24T15:16:28.7216639+08:00||;True|2026-01-23T22:14:26.8066324+08:00||;True|2026-01-23T13:36:48.4086406+08:00||;True|2026-01-23T13:30:27.2924204+08:00||;True|2026-01-23T13:02:53.3219368+08:00||;True|2026-01-23T13:01:54.3466638+08:00||;True|2026-01-23T13:01:28.8560809+08:00||;True|2026-01-22T23:18:32.0400436+08:00||;True|2026-01-22T22:29:29.5746209+08:00||;True|2026-01-22T21:32:21.7284081+08:00||;True|2026-01-22T21:20:26.5985955+08:00||;False|2026-01-22T21:20:21.1372539+08:00||;True|2026-01-22T21:11:18.3771739+08:00||;True|2026-01-22T20:58:55.9862778+08:00||;True|2026-01-22T20:51:48.6699461+08:00||;False|2026-01-22T20:51:42.2079210+08:00||;True|2026-01-22T20:50:34.7280114+08:00||;True|2026-01-22T20:50:11.1588492+08:00||;True|2026-01-22T16:11:14.0626037+08:00||;True|2026-01-22T15:53:16.6377347+08:00||;True|2026-01-22T15:45:34.0935091+08:00||;True|2026-01-22T12:44:28.1577014+08:00||;True|2026-01-22T12:40:45.7449335+08:00||;True|2026-01-22T12:39:07.0683214+08:00||;True|2026-01-22T09:55:49.7086190+08:00||;True|2026-01-22T01:37:53.6972395+08:00||;True|2026-01-22T01:34:14.2142463+08:00||;True|2026-01-22T01:32:50.9228911+08:00||;True|2026-01-22T01:29:43.8871986+08:00||;True|2026-01-22T01:29:40.7838709+08:00||;True|2026-01-22T01:27:34.4763970+08:00||;True|2026-01-22T01:26:55.0117367+08:00||;True|2026-01-22T01:23:19.4789587+08:00||;True|2026-01-19T10:19:44.8997385+08:00||;True|2026-01-19T10:11:10.2305307+08:00||;True|2026-01-17T00:52:08.8180346+08:00||;False|2026-01-17T00:52:03.1829000+08:00||;True|2026-01-17T00:45:04.0498090+08:00||;True|2026-01-16T21:00:57.7239379+08:00||;True|2026-01-16T21:00:03.3912989+08:00||;True|2026-01-16T20:56:08.5505592+08:00||;True|2026-01-16T20:46:32.3067302+08:00||;True|2026-01-15T22:18:57.9835738+08:00||;True|2026-01-15T22:07:25.4753938+08:00||;True|2026-01-15T22:07:14.8243754+08:00||;True|2026-01-15T21:40:13.2613927+08:00||;True|2026-01-15T20:57:56.6291682+08:00||;True|2026-01-15T20:57:47.1363536+08:00||;True|2026-01-15T20:08:44.4710883+08:00||;True|2026-01-15T20:04:51.8284314+08:00||;False|2026-01-15T20:04:45.3258155+08:00||;True|2026-01-15T19:59:11.7907672+08:00||;True|2026-01-15T01:10:48.9066941+08:00||;True|2026-01-15T01:03:23.6216975+08:00||;True|2026-01-15T01:03:16.3364591+08:00||;False|2026-01-15T01:03:10.5654436+08:00||;True|2026-01-15T00:49:21.6559622+08:00||;True|2026-01-15T00:10:42.7288439+08:00||;True|2026-01-15T00:06:28.4241682+08:00||;True|2026-01-13T22:34:15.2372411+08:00||;True|2026-01-13T22:26:46.4522947+08:00||;True|2026-01-13T22:15:56.9735179+08:00||;True|2026-01-13T22:15:51.8250677+08:00||;False|2026-01-13T22:15:46.6608690+08:00||;True|2026-01-13T22:03:06.4683237+08:00||;False|2026-01-13T22:03:01.2036027+08:00||;True|2026-01-13T21:36:05.8776209+08:00||;True|2026-01-13T21:33:02.0766289+08:00||;False|2026-01-13T21:32:57.1976141+08:00||;True|2026-01-13T19:14:35.2519439+08:00||;True|2026-01-13T15:07:50.0836745+08:00||;False|2026-01-13T15:06:35.5929685+08:00||;True|2026-01-13T10:21:41.6012776+08:00||;True|2026-01-13T09:57:25.4788847+08:00||;True|2026-01-13T09:53:14.4900360+08:00||;True|2026-01-13T09:23:53.6465295+08:00||;True|2026-01-12T22:04:14.5886955+08:00||;False|2026-01-12T22:04:08.2008101+08:00||;True|2026-01-12T21:53:46.9947176+08:00||;True|2026-01-12T21:11:15.8358024+08:00||;True|2026-01-12T21:09:51.8663228+08:00||;True|2026-01-11T21:25:07.5052845+08:00||;False|2026-01-11T21:24:27.5744557+08:00||;True|2026-01-11T19:59:15.4734611+08:00||;False|2026-01-11T19:59:04.9543339+08:00||;</History>
<LastFailureDetails /> <LastFailureDetails />
</PropertyGroup> </PropertyGroup>
</Project> </Project>
+3 -3
View File
@@ -95,8 +95,8 @@ Cookie 及 `sec_user_id` 是同步功能的核心,需严格按步骤获取,
| 镜像标签 | 架构 | | 镜像标签 | 架构 |
| ----------------- | -------------- | | ----------------- | -------------- |
| `beta_2.0.5` | x86_64 (amd64) | | `beta_2.0.6` | x86_64 (amd64) |
| `arm_2.0.5` | ARM64 | | `arm_2.0.6` | ARM64 |
### 构建命令示例 ### 构建命令示例
@@ -115,7 +115,7 @@ version: '3.8'
services: services:
dysync: dysync:
image: ccr.ccs.tencentyun.com/jianzhichu/dysync:beta_2.0.5 image: ccr.ccs.tencentyun.com/jianzhichu/dysync:beta_2.0.6
container_name: dysync2026 # 容器名称 container_name: dysync2026 # 容器名称
restart: unless-stopped # 始终重启容器,除非容器被手动停止或Docker服务停止 restart: unless-stopped # 始终重启容器,除非容器被手动停止或Docker服务停止
ports: ports:
+104 -113
View File
@@ -55,12 +55,15 @@ type DataItem = {
upSecUserIds?: string; upSecUserIds?: string;
upSavePath?: string; upSavePath?: string;
// imgSavePath?: string; // imgSavePath?: string;
useSinglePath?: boolean; // 新增:是否全部用一个地址 // useSinglePath?: boolean; // 新增:是否全部用一个地址
useCollectFolder?: boolean; useCollectFolder?: boolean;
downMix?: boolean; downMix?: boolean;
downSeries?: boolean; downSeries?: boolean;
mixPath?: string; mixPath?: string;
seriesPath?: string; seriesPath?: string;
downCollect?: boolean;
downFavorite?: boolean;
downFollowd?: boolean;
}; };
const loading = ref(false); const loading = ref(false);
@@ -120,12 +123,15 @@ const newCookie = (cookie?: DataItem) => {
cookie.upSecUserIdsJson = undefined; cookie.upSecUserIdsJson = undefined;
cookie.upSavePath = undefined; cookie.upSavePath = undefined;
// cookie.imgSavePath = undefined; // cookie.imgSavePath = undefined;
cookie.useSinglePath = false; // 新增:默认不使用单一路径 // cookie.useSinglePath = false; // 新增:默认不使用单一路径
cookie.useCollectFolder = false; //是否按收藏夹来下载。 cookie.useCollectFolder = false; //是否按收藏夹来下载。
cookie.downMix = false; //是否下载收藏夹的合集 cookie.downMix = false; //是否下载收藏夹的合集
cookie.downSeries = false; //是否下载短剧 cookie.downSeries = false; //是否下载短剧
cookie.mixPath = undefined; //合集存储路径 cookie.mixPath = undefined; //合集存储路径
cookie.seriesPath = undefined; //短剧存储路径 cookie.seriesPath = undefined; //短剧存储路径
cookie.downCollect = false;
cookie.downFavorite = false;
cookie.downFollowd = false;
return cookie; return cookie;
}; };
@@ -139,17 +145,17 @@ const copyObject = (target: any, source?: any) => {
const form = reactive<DataItem>(newCookie()); const form = reactive<DataItem>(newCookie());
// 新增:监听收藏路径变化,当启用单一路径时同步到其他路径 // 新增:监听收藏路径变化,当启用单一路径时同步到其他路径
watch( // watch(
[() => form.savePath, () => form.useSinglePath], // [() => form.savePath, () => form.useSinglePath],
([newSavePath, useSinglePath]) => { // ([newSavePath, useSinglePath]) => {
if (useSinglePath && newSavePath) { // // if (useSinglePath && newSavePath) {
form.favSavePath = newSavePath; // // form.favSavePath = newSavePath;
form.upSavePath = newSavePath; // // form.upSavePath = newSavePath;
// form.imgSavePath = newSavePath; // // // form.imgSavePath = newSavePath;
} // // }
}, // },
{ immediate: true } // { immediate: true }
); // );
function reset() { function reset() {
return newCookie(form); return newCookie(form);
@@ -181,11 +187,11 @@ function submit() {
loading.value = false; loading.value = false;
if (res.code === 0) { if (res.code === 0) {
showModal.value = false; showModal.value = false;
message.success('修改成功,同步任务将在5-10秒按新配置运行...'); message.success('保存成功,同步任务将在5-10秒后重新启动...');
reset(); reset();
GetRecords(); GetRecords();
}else{ } else {
message.success('修改失败'+res.message); message.error('保存失败' + res.message);
} }
}); });
}) })
@@ -231,9 +237,9 @@ function edit(record: DataItem) {
console.log(record); console.log(record);
copyObject(form, record); copyObject(form, record);
// 确保useSinglePath有默认值 // 确保useSinglePath有默认值
if (form.useSinglePath === undefined) { // if (form.useSinglePath === undefined) {
form.useSinglePath = false; // form.useSinglePath = false;
} // }
showModal.value = true; showModal.value = true;
} }
@@ -311,7 +317,7 @@ const removeRow = (index: number) => {
form.upSecUserIdsJson.splice(index, 1); form.upSecUserIdsJson.splice(index, 1);
} }
}; };
const rowCount = 5; const rowCount = 10;
// 组件挂载时获取配置 // 组件挂载时获取配置
onMounted(() => { onMounted(() => {
@@ -496,6 +502,10 @@ const saveDrawerData = () => {
drawerPagination.loading = false; drawerPagination.loading = false;
}); });
}; };
const switchdownCollect = (e: any) => {
if (!e) form.useCollectFolder = e;
};
</script> </script>
<template> <template>
@@ -520,132 +530,113 @@ const saveDrawerData = () => {
</div> </div>
</a-form-item> </a-form-item>
<!-- 收藏的存储路径 --> <!-- 修复下载收藏视频 - 拆分Form.Item用a-form-item-rest包裹非收集字段 -->
<a-form-item label="收藏的存储路径" name="savePath"> <a-form-item label="下载收藏视频" name="downCollect">
<div style="display: flex; align-items: center; gap: 12px; width: 100%;">
<a-input v-model:value="form.savePath" class="form-item-div" />
<a-alert message="不想同步收藏的视频就空着" type="info" size="small" style="flex: 1; margin-bottom: 0;" />
</div>
</a-form-item>
<!-- 新增是否全部用一个地址开关 -->
<a-form-item v-if="form.savePath&&form.savePath.length>0" label="统一存储路径" name="useSinglePath">
<div style="display: flex; align-items: center; gap: 12px;"> <div style="display: flex; align-items: center; gap: 12px;">
<div class="form-item-div"> <div class="form-item-div">
<a-switch v-model:checked="form.useSinglePath" :checked-value="true" :un-checked-value="false" size="default" /> <a-switch v-model:checked="form.downCollect" @change="switchdownCollect" :checked-value="true" :un-checked-value="false" size="default" />
<span style="margin-left:10px;">{{form.useSinglePath ?'':''}}</span> <!-- 非收集字段用a-form-item-rest包裹 -->
<a-form-item-rest v-if="form.downCollect">
<a-form-item name="savePath" noStyle>
<a-input v-model:value="form.savePath" placeholder='请输入容器路径' class="form-item-div-input" />
</a-form-item>
</a-form-item-rest>
</div> </div>
<a-alert message="开启后自动下载默认收藏夹视频,记得填写映射路径(容器内部路径)" type="info" size="small" style="flex: 1; margin-bottom: 0;" />
<a-alert message="开启后,喜欢、关注、收藏都存储在一个目录,如果是容器部署:docker-compose配置,只需要映射一个路径 (合集短剧存储路径需要另外设置映射)" :type="form.useSinglePath?'error':'info'" size="small" style="flex: 1; margin-bottom: 0;" />
</div> </div>
</a-form-item> </a-form-item>
<a-form-item v-if="form.savePath&&form.savePath.length>0" label="下载自定义收藏夹" name="useCollectFolder">
<!-- 修复自定义收藏夹 - 拆分Form.Item -->
<a-form-item v-if="form.savePath && form.savePath.length>0" label="自定义收藏夹" name="useCollectFolder">
<div style="display: flex; align-items: center; gap: 12px;"> <div style="display: flex; align-items: center; gap: 12px;">
<div class="form-item-div"> <div class="form-item-div">
<a-switch v-model:checked="form.useCollectFolder" :checked-value="true" :un-checked-value="false" size="default" /> <a-switch v-model:checked="form.useCollectFolder" :checked-value="true" :un-checked-value="false" size="default" />
<span style="margin-left:10px;">{{ form.useCollectFolder ? '' : '' }}</span> <a-form-item-rest v-if="form.useCollectFolder">
<span>存储路径=收藏的存储路径</span> <a-input v-model:value="form.savePath" :disabled="form.useCollectFolder&&form.downCollect" placeholder="" class="form-item-div-input" />
<a-button @click="openCollectFolderSetModal" shape="circle" type="dashed" style="margin-left:10px;" v-if="form.useCollectFolder"> <a-button @click="openCollectFolderSetModal" shape="circle" type="dashed" style="margin-left:5px;" v-if="form.useCollectFolder">
<tag-outlined /> <star-outlined />
</a-button> </a-button>
</a-form-item-rest>
</div> </div>
<a-alert message="开启后自动下载自定义分类后的收藏夹,开启后不在下载默认收藏夹视频,存储路径与默认收藏夹存储路径一致" :type="form.useCollectFolder?'error':'info'" size="small" style="flex: 1; margin-bottom: 0;" />
<a-alert message="开启后按照自定义收藏进行下载(需要开启收藏文件夹同步开关)且不再下载默认收藏夹视频" :type="form.useCollectFolder?'error':'info'" size="small" style="flex: 1; margin-bottom: 0;" />
</div>
</a-form-item>
<!-- 喜欢的存储路径 -->
<a-form-item label="喜欢的存储路径" name="favSavePath">
<div style="display: flex; align-items: center; gap: 12px; width: 100%;">
<a-input v-model:value="form.favSavePath" :disabled="form.useSinglePath" placeholder="" class="form-item-div" />
<a-alert message="不想同步喜欢的视频就空着" type="info" size="small" style="flex: 1; margin-bottom: 0;" />
</div> </div>
</a-form-item> </a-form-item>
<!-- 关注的存储路径 --> <!-- 修复下载喜欢视频 - 拆分Form.Item -->
<a-form-item label="关注的存储路径" name="upSavePath"> <a-form-item label="下载喜欢视频" name="downFavorite">
<div style="display: flex; align-items: center; gap: 12px; width: 100%;"> <div style="display: flex; align-items: center; gap: 12px;">
<a-input v-model:value="form.upSavePath" :disabled="form.useSinglePath" placeholder="" class="form-item-div" /> <div class="form-item-div">
<a-alert message="不想同步关注列表博主的视频就空着" type="info" size="small" style="flex: 1; margin-bottom: 0;" /> <a-switch v-model:checked="form.downFavorite" :checked-value="true" :un-checked-value="false" size="default" />
<a-form-item-rest v-if="form.downFavorite">
<a-form-item name="favSavePath" noStyle>
<a-input v-model:value="form.favSavePath" placeholder='请输入容器路径' class="form-item-div-input" />
</a-form-item>
<a-button shape="circle" @click="()=>{message.success('别点了,这只是为了好看的😄')}" type="dashed" style="margin-left:5px;">
<like-outlined />
</a-button>
</a-form-item-rest>
</div>
<a-alert message="开启后自动下载喜欢(点赞)的视频,记得填写映射路径(容器内部路径)" type="info" size="small" style="flex: 1; margin-bottom: 0;" />
</div> </div>
</a-form-item> </a-form-item>
<!-- 下载合集 - 新增name="mixPath" --> <!-- 修复下载关注视频 - 拆分Form.Item -->
<a-form-item label="下载合集" name="downMix"> <a-form-item label="下载关注视频" name="downFollowd">
<div style="display: flex; align-items: center; gap: 12px;">
<div class="form-item-div">
<a-switch v-model:checked="form.downFollowd" :checked-value="true" :un-checked-value="false" size="default" />
<a-form-item-rest v-if="form.downFollowd">
<a-form-item name="upSavePath" noStyle>
<a-input v-model:value="form.upSavePath" placeholder='请输入容器路径' class="form-item-div-input" />
</a-form-item>
<a-button shape="circle" @click="()=>{message.success('别点了,这只是为了好看的😄')}" type="dashed" style="margin-left:5px;">
<heart-outlined />
</a-button>
</a-form-item-rest>
</div>
<a-alert message="开启后自动下载关注的博主视频,记得填写映射路径(容器内部路径)" type="info" size="small" style="flex: 1; margin-bottom: 0;" />
</div>
</a-form-item>
<!-- 修复下载合集视频 - 拆分Form.Item -->
<a-form-item label="下载合集视频" name="downMix">
<div style="display: flex; align-items: center; gap: 12px;"> <div style="display: flex; align-items: center; gap: 12px;">
<div class="form-item-div"> <div class="form-item-div">
<a-switch v-model:checked="form.downMix" :checked-value="true" :un-checked-value="false" size="default" /> <a-switch v-model:checked="form.downMix" :checked-value="true" :un-checked-value="false" size="default" />
<span style="margin-left:10px;">{{ form.downMix ? '' : '' }}</span> <a-form-item-rest v-if="form.downMix">
<!-- 给输入框对应的form-item添加name属性 --> <a-form-item name="mixPath" noStyle>
<a-form-item name="mixPath" noStyle v-if="form.downMix"> <a-input v-model:value="form.mixPath" class="form-item-div-input" placeholder='默认使用收藏夹路径' />
<a-input v-model:value="form.mixPath" class="form-item-div-input" />
</a-form-item> </a-form-item>
<a-button @click="openMixDownSetModal" shape="circle" type="dashed" style="margin-left:5px;">
<a-button @click="openMixDownSetModal" shape="circle" type="dashed" style="margin-left:10px;" v-if="form.downMix"> <tag-outlined /> <gift-outlined />
</a-button> </a-button>
</a-form-item-rest>
</div> </div>
<a-alert message="开启后自动下载收藏的合集视频(还需要开启合集同步开关,不填目录径默认存储到收藏目录,设置后记得docker里面加映射,注意:付费视频下载后无法播放)" :type="form.downMix?'error':'info'" size="small" style="flex: 1; margin-bottom: 0;" />
<a-alert message="开启后会同步下载收藏的合集(需要开启合集同步开关,不设置路径默认存储到收藏,设置后记得docker里面加映射)" :type="form.downMix?'error':'info'" size="small" style="flex: 1; margin-bottom: 0;" />
</div> </div>
</a-form-item> </a-form-item>
<!-- 下载短剧 - 新增name="seriesPath" --> <!-- 修复下载短剧视频 - 拆分Form.Item -->
<a-form-item label="下载短剧" name="downSeries"> <a-form-item label="下载短剧视频" name="downSeries">
<div style="display: flex; align-items: center; gap: 12px;"> <div style="display: flex; align-items: center; gap: 12px;">
<div class="form-item-div"> <div class="form-item-div">
<a-switch v-model:checked="form.downSeries" :checked-value="true" :un-checked-value="false" size="default" /> <a-switch v-model:checked="form.downSeries" :checked-value="true" :un-checked-value="false" size="default" />
<span style="margin-left:10px;">{{ form.downSeries ? '' : '' }}</span> <a-form-item-rest v-if="form.downSeries">
<!-- 给输入框对应的form-item添加name属性 --> <a-form-item name="seriesPath" noStyle>
<a-form-item name="seriesPath" noStyle v-if="form.downSeries"> <a-input v-model:value="form.seriesPath" placeholder='默认使用收藏夹路径' class="form-item-div-input" />
<a-input v-model:value="form.seriesPath" class="form-item-div-input" />
</a-form-item> </a-form-item>
<a-button @click="openSeriesDownSetModal" shape="circle" type="dashed" style="margin-left:5px;">
<a-button @click="openSeriesDownSetModal" shape="circle" type="dashed" style="margin-left:10px;" v-if="form.downSeries"> <tag-outlined /> <fire-outlined />
</a-button> </a-button>
</a-form-item-rest>
</div> </div>
<a-alert message="开启后自动下载收藏的短剧视频(还需要开启短剧同步开关,不填目录径默认存储到收藏目录,设置后记得docker里面加映射,注意:付费视频下载后无法播放)
<a-alert message="开启后会同步下载收藏的短剧(需要开启短剧同步开关,不设置路径默认存储到收藏,设置后记得docker里面加映射)" :type="form.downSeries?'error':'info'" size="small" style="flex: 1; margin-bottom: 0;" /> " :type="form.downSeries?'error':'info'" size="small" style="flex: 1; margin-bottom: 0;" />
</div>
</a-form-item>
<!--
<a-form-item v-if="form.savePath&&form.savePath.length>0" label="下载合集" name="downMix">
<div style="display: flex; align-items: center; gap: 12px;">
<div class="form-item-div">
<a-switch v-model:checked="form.downMix" :checked-value="true" :un-checked-value="false" size="default" />
<span style="margin-left:10px;">{{ form.downMix ? '是' : '否' }}</span>
<a-input v-model:value="form.mixPath" class="form-item-div-input" />
<a-button @click="openMixDownSetModal" shape="circle" type="dashed" style="margin-left:10px;" v-if="form.downMix"> <tag-outlined />
</a-button>
</div>
<a-alert message="开启后会同步下载收藏的合集(需要开启合集同步开关,不设置路径默认存储到收藏,设置后记得docker里面加映射)," :type="form.downMix?'error':'info'" size="small" style="flex: 1; margin-bottom: 0;" />
</div> </div>
</a-form-item> </a-form-item>
<a-form-item v-if="form.savePath&&form.savePath.length>0" label="下载短剧" name="downSeries"> <!-- 任务同步状态 - 保持原有结构仅一个Switch无警告 -->
<div style="display: flex; align-items: center; gap: 12px;"> <a-form-item label="任务同步状态" name="status">
<div class="form-item-div">
<a-switch v-model:checked="form.downSeries" :checked-value="true" :un-checked-value="false" size="default" />
<span style="margin-left:10px;">{{ form.downSeries ? '是' : '否' }}</span>
<a-input v-model:value="form.seriesPath" class="form-item-div-input" />
<a-button @click="openSeriesDownSetModal" shape="circle" type="dashed" style="margin-left:10px;" v-if="form.downSeries"> <tag-outlined />
</a-button>
</div>
<a-alert message="开启后会同步下载收藏的短剧(需要开启短剧同步开关,不设置路径默认存储到收藏,设置后记得docker里面加映射)" :type="form.downSeries?'error':'info'" size="small" style="flex: 1; margin-bottom: 0;" />
</div>
</a-form-item> -->
<!-- 图文的存储路径 -->
<!-- <a-form-item label="图文的存储路径" name="imgSavePath">
<div style="display: flex; align-items: center; gap: 12px; width: 100%;">
<a-input v-model:value="form.imgSavePath" :disabled="form.useSinglePath" style="width: 200px;" />
<a-alert message="如果系统配置页面开启了同步图文视频,且开启了单独存储,则必填!!!" type="info" size="small" style="flex: 1; margin-bottom: 0;" />
</div>
</a-form-item> -->
<!-- 同步状态开关 -->
<a-form-item label="同步状态" name="status">
<div style="display: flex; align-items: center; gap: 8px;"> <div style="display: flex; align-items: center; gap: 8px;">
<a-switch v-model:checked="form.status" :checked-value="1" :un-checked-value="0" size="default" /> <a-switch v-model:checked="form.status" :checked-value="1" :un-checked-value="0" size="default" />
<span>{{ form.status === 1 ? '' : '' }}</span> <span>{{ form.status === 1 ? '' : '' }}</span>
@@ -825,7 +816,7 @@ const saveDrawerData = () => {
width: 300px; width: 300px;
} }
.form-item-div-input { .form-item-div-input {
width: 150px; width: 180px;
margin-left: 10px; margin-left: 10px;
} }
/* Firefox 透明滚动条适配 */ /* Firefox 透明滚动条适配 */
+1 -1
View File
@@ -130,7 +130,7 @@
</ItemGroup> </ItemGroup>
</Target> </Target>
<!-- 核心配置:仅用核心版本号2.0.5,自动拼接前缀 --> <!-- 核心配置:仅用核心版本号2.0.6,自动拼接前缀 -->
<!--<Target Name="DockerBuildAndPushAfterPublish" AfterTargets="Publish"> <!--<Target Name="DockerBuildAndPushAfterPublish" AfterTargets="Publish">
<Exec Command="docker build -t jianzhichu/dysync.net:beta_$(DockerCoreVersion) . &amp;&amp; docker tag jianzhichu/dysync.net:beta_$(DockerCoreVersion) ccr.ccs.tencentyun.com/jianzhichu/dysync:beta_$(DockerCoreVersion) &amp;&amp; docker push ccr.ccs.tencentyun.com/jianzhichu/dysync:beta_$(DockerCoreVersion)" <Exec Command="docker build -t jianzhichu/dysync.net:beta_$(DockerCoreVersion) . &amp;&amp; docker tag jianzhichu/dysync.net:beta_$(DockerCoreVersion) ccr.ccs.tencentyun.com/jianzhichu/dysync:beta_$(DockerCoreVersion) &amp;&amp; docker push ccr.ccs.tencentyun.com/jianzhichu/dysync:beta_$(DockerCoreVersion)"
Condition="'$(DockerArch)' == 'x86'" /> Condition="'$(DockerArch)' == 'x86'" />
-11
View File
@@ -34,17 +34,6 @@ namespace dy.net.job
return await douyinHttpClientService.SyncCollectVideos(cursor, count, cookie.Cookies); return await douyinHttpClientService.SyncCollectVideos(cursor, count, cookie.Cookies);
} }
//protected override bool ShouldContinueSync(DouyinCookie cookie, DouyinVideoInfoResponse data, DouyinFollowed followed , AppConfig config)
//{
// return data != null && data.HasMore == 1 && cookie.CollHasSyncd == 0;
//}
//protected override async Task HandleSyncCompletion(DouyinCookie cookie, int syncCount,DouyinFollowed followed,DouyinCollectCate cate)
//{
// cookie.CollHasSyncd = 1;
// await douyinCookieService.UpdateAsync(cookie);
// Log.Debug($"[{VideoType}]-[{cookie.UserName}],本次成功同步{syncCount}条视频");
//}
+85 -71
View File
@@ -7,6 +7,7 @@ using dy.net.utils;
using Newtonsoft.Json; using Newtonsoft.Json;
using Quartz; using Quartz;
using Serilog; using Serilog;
using System.Net;
namespace dy.net.job namespace dy.net.job
{ {
@@ -125,8 +126,6 @@ namespace dy.net.job
/// <returns>一个表示异步操作的任务</returns> /// <returns>一个表示异步操作的任务</returns>
public async Task Execute(IJobExecutionContext context) public async Task Execute(IJobExecutionContext context)
{ {
//if (VideoType.GetVideoTypeDesc() != VideoType.GetVideoTypeDesc()Enum.dy_follows)
// return;
// 获取应用配置 // 获取应用配置
var config = douyinCommonService.GetConfig(); var config = douyinCommonService.GetConfig();
@@ -180,16 +179,6 @@ namespace dy.net.job
/// <returns>视频信息对象,包含视频列表和分页信息</returns> /// <returns>视频信息对象,包含视频列表和分页信息</returns>
protected abstract Task<DouyinVideoInfoResponse> FetchVideoData(DouyinCookie cookie, string cursor, DouyinFollowed followed, DouyinCollectCate cate); protected abstract Task<DouyinVideoInfoResponse> FetchVideoData(DouyinCookie cookie, string cursor, DouyinFollowed followed, DouyinCollectCate cate);
///// <summary>
///// 判断是否应该继续同步下一页数据
///// </summary>
///// <param name="cookie">用户Cookie</param>
///// <param name="data">当前获取到的视频数据</param>
///// <param name="followed">关注博主</param>
///// <returns>如果应该继续同步,则为true;否则为false</returns>
//protected bool ShouldContinueSync(DouyinCookie cookie, DouyinVideoInfoResponse data, DouyinFollowed followed, AppConfig config) {
//}
/// <summary> /// <summary>
/// 获取下一页数据的游标 /// 获取下一页数据的游标
@@ -304,13 +293,12 @@ namespace dy.net.job
{ {
try try
{ {
if (VideoType != VideoTypeEnum.dy_custom_collect || cookie.UseCollectFolder)
Log.Debug($"[{VideoType.GetDesc()}][{cookie.UserName}]开始同步...");
switch (VideoType) switch (VideoType)
{ {
case VideoTypeEnum.dy_follows: case VideoTypeEnum.dy_follows:
if(cookie.DownFollowd)
{ {
Log.Debug($"[{cookie.UserName}][{VideoType.GetDesc()}]开始同步...");
//查询关注列表开启了同步的关注 //查询关注列表开启了同步的关注
var follows = await douyinFollowService.GetSyncFollows(cookie.MyUserId); var follows = await douyinFollowService.GetSyncFollows(cookie.MyUserId);
if (follows != null && follows.Any()) if (follows != null && follows.Any())
@@ -325,15 +313,32 @@ namespace dy.net.job
} }
} }
} }
else
{
Log.Debug($"[{cookie.UserName}][{VideoType.GetDesc()}]同步未开启");
}
break; break;
case VideoTypeEnum.dy_favorite: case VideoTypeEnum.dy_favorite:
case VideoTypeEnum.dy_collects: if(cookie.DownFavorite)
if (VideoType == VideoTypeEnum.dy_collects && cookie.UseCollectFolder)
{ {
Log.Debug($"[{VideoType.GetDesc()}]-已开启自定义收藏夹同步...break;"); Log.Debug($"[{cookie.UserName}][{VideoType.GetDesc()}]开始同步...");
break; int syncCount = 0;
string cursor = "0";
bool hasMore = true;
(syncCount, cursor, hasMore) = await GetAndSaveViedos(cookie, config, syncCount, cursor, hasMore);
await HandleSyncCompletion(cookie, syncCount);
} }
else else
{
Log.Debug($"[{cookie.UserName}][{VideoType.GetDesc()}]同步未开启");
}
break;
case VideoTypeEnum.dy_collects:
if (cookie.UseCollectFolder)
{
Log.Debug($"[{VideoType.GetDesc()}]-已开启自定义收藏夹同步...break;");
}
else if (cookie.DownCollect)
{ {
int syncCount = 0; int syncCount = 0;
string cursor = "0"; string cursor = "0";
@@ -341,19 +346,37 @@ namespace dy.net.job
(syncCount, cursor, hasMore) = await GetAndSaveViedos(cookie, config, syncCount, cursor, hasMore); (syncCount, cursor, hasMore) = await GetAndSaveViedos(cookie, config, syncCount, cursor, hasMore);
await HandleSyncCompletion(cookie, syncCount); await HandleSyncCompletion(cookie, syncCount);
} }
else
{
Log.Debug($"[{cookie.UserName}][{VideoType.GetDesc()}]同步未开启");
}
break; break;
case VideoTypeEnum.dy_mix: case VideoTypeEnum.dy_mix:
if (cookie.DownMix) if (cookie.DownMix)
{
await SyncCustomListVideos(cookie, config); await SyncCustomListVideos(cookie, config);
}
else
{
Log.Debug($"[{cookie.UserName}][{VideoType.GetDesc()}]同步未开启");
}
break; break;
case VideoTypeEnum.dy_series: case VideoTypeEnum.dy_series:
if (cookie.DownSeries) if (cookie.DownSeries)
await SyncCustomListVideos(cookie, config); await SyncCustomListVideos(cookie, config);
else
{
Log.Debug($"[{cookie.UserName}][{VideoType.GetDesc()}]同步未开启");
}
break; break;
case VideoTypeEnum.dy_custom_collect: case VideoTypeEnum.dy_custom_collect:
if (cookie.UseCollectFolder) if (cookie.UseCollectFolder)
await SyncCustomListVideos(cookie, config); await SyncCustomListVideos(cookie, config);
else
{
Log.Debug($"[{cookie.UserName}][{VideoType.GetDesc()}]同步未开启");
}
break; break;
case VideoTypeEnum.ImageVideo: case VideoTypeEnum.ImageVideo:
default: default:
@@ -364,7 +387,7 @@ namespace dy.net.job
} }
catch (Exception ex) catch (Exception ex)
{ {
Log.Error(ex, $"[{VideoType.GetDesc()}][{cookie.UserName}]同步出错!!!,{ex.StackTrace}"); Log.Error(ex, $"[{cookie.UserName}][{VideoType.GetDesc()}]同步出错!!!,{ex.StackTrace}");
} }
} }
/// <summary> /// <summary>
@@ -390,7 +413,7 @@ namespace dy.net.job
} }
else else
{ {
Serilog.Log.Debug($"[{VideoType.GetDesc()}]没有查询到已开启的对象"); Serilog.Log.Debug($"[{cookie.UserName}][{VideoType.GetDesc()}]没有查询到已开启的对象");
} }
} }
@@ -403,9 +426,16 @@ namespace dy.net.job
var data = await FetchVideoData(cookie, cursor, followed, cate); var data = await FetchVideoData(cookie, cursor, followed, cate);
if (data == null || data.AwemeList == null || !data.AwemeList.Any()) if (data == null || data.AwemeList == null || !data.AwemeList.Any())
{ {
Serilog.Log.Debug($"[{VideoType.GetDesc()}][{cookie.UserName}] 没有新的视频"); Serilog.Log.Debug($"[{cookie.UserName}][{VideoType.GetDesc()}] 没有新的视频");
break; break;
} }
if (data.StatusCode == 0 && cookie.StatusMsg != "正常")
{
cookie.StatusCode = data.StatusCode;
cookie.StatusMsg = "正常";
await douyinCookieService.UpdateAsync(cookie);
}
// 判断是否还有更多数据 // 判断是否还有更多数据
//hasMore = ShouldContinueSync(cookie, data, followed, config); //hasMore = ShouldContinueSync(cookie, data, followed, config);
@@ -419,7 +449,7 @@ namespace dy.net.job
if (videos != null && videos.Any()) if (videos != null && videos.Any())
{ {
// 保存视频信息到数据库 // 保存视频信息到数据库
await SaveVideos(videos); await SaveVideos(cookie,videos);
} }
syncCount += syncCountx; syncCount += syncCountx;
@@ -451,7 +481,7 @@ namespace dy.net.job
{ {
if (syncCount >= 15) if (syncCount >= 15)
{ {
Log.Debug($"[{VideoType.GetDesc()}][{cookie.UserName}]:本次同步数量{syncCount},等下次任务继续同步"); Log.Debug($"[{cookie.UserName}][{VideoType.GetDesc()}]:本次同步数量{syncCount},等下次任务继续同步");
return true; return true;
} }
@@ -460,7 +490,7 @@ namespace dy.net.job
{ {
if (syncCount >= config.BatchCount) if (syncCount >= config.BatchCount)
{ {
Log.Debug($"[{VideoType.GetDesc()}][{cookie.UserName}]:本次同步数量{syncCount},已达配置上限{config.BatchCount},等下次任务继续同步"); Log.Debug($"[{cookie.UserName}][{VideoType.GetDesc()}]:本次同步数量{syncCount},已达配置上限{config.BatchCount},等下次任务继续同步");
return true; return true;
} }
@@ -480,7 +510,7 @@ namespace dy.net.job
//// 状态满足则记录日志并返回「终止循环」 //// 状态满足则记录日志并返回「终止循环」
//if (isSyncCompleted.Invoke(cookie)) //if (isSyncCompleted.Invoke(cookie))
//{ //{
// Log.Debug($"[{VideoType.GetDesc()}][{cookie.UserName}]本次同步达到上限{config.BatchCount},停止同步!!!"); // Log.Debug($"[{cookie.UserName}][{VideoType.GetDesc()}]本次同步达到上限{config.BatchCount},停止同步!!!");
// return true; // return true;
//} //}
@@ -522,7 +552,7 @@ namespace dy.net.job
{ {
bool Goon = await AutoDistinct(config, exitVideo); bool Goon = await AutoDistinct(config, exitVideo, cookie);
if (!Goon) if (!Goon)
{ {
continue; continue;
@@ -598,7 +628,7 @@ namespace dy.net.job
if (!string.IsNullOrEmpty(dynamicVideo.DynamicVideos)) if (!string.IsNullOrEmpty(dynamicVideo.DynamicVideos))
{ {
var dynamicVideos = JsonConvert.DeserializeObject<List<DouyinDynamicVideoDto>>(dynamicVideo.DynamicVideos); var dynamicVideos = JsonConvert.DeserializeObject<List<DouyinDynamicVideoDto>>(dynamicVideo.DynamicVideos);
Log.Debug($"[{VideoType.GetDesc()}][{cookie.UserName}]-动态视频[{item.Desc}],下载成功 ,共{dynamicVideos?.Count}个视频..."); Log.Debug($"[{cookie.UserName}][{VideoType.GetDesc()}]-动态视频[{item.Desc}],下载成功 ,共{dynamicVideos?.Count}个视频...");
if (dynamicVideos != null && dynamicVideos.Count > 0) if (dynamicVideos != null && dynamicVideos.Count > 0)
{ {
@@ -640,7 +670,7 @@ namespace dy.net.job
} }
else else
{ {
Log.Debug($"[{VideoType.GetDesc()}][{cookie.UserName}]-动态视频[{item.Desc}],下载失败..."); Log.Debug($"[{cookie.UserName}][{VideoType.GetDesc()}]-动态视频[{item.Desc}],下载失败...");
} }
} }
else else
@@ -665,7 +695,7 @@ namespace dy.net.job
return (videos, syncCount); return (videos, syncCount);
} }
private async Task<bool> AutoDistinct(AppConfig config, DouyinVideo exitVideo) private async Task<bool> AutoDistinct(AppConfig config, DouyinVideo exitVideo,DouyinCookie cookie)
{ {
// 去重,检查视频是否已存在(按优先级下载) // 去重,检查视频是否已存在(按优先级下载)
if (config.AutoDistinct) if (config.AutoDistinct)
@@ -724,7 +754,7 @@ namespace dy.net.job
} }
catch (Exception ex) catch (Exception ex)
{ {
Log.Error($"[{VideoType.GetDesc()}]-删除重复的文件失败:{ex.Message}", ex); Log.Error($"[{cookie.UserName}][{VideoType.GetDesc()}]-删除重复的文件[{exitVideo.VideoTitle}]失败:{ex.Message}", ex);
// 即使删除失败,仍继续下载(新文件会覆盖旧文件,或按路径规则重命名) // 即使删除失败,仍继续下载(新文件会覆盖旧文件,或按路径规则重命名)
} }
@@ -809,7 +839,6 @@ namespace dy.net.job
/// </summary> /// </summary>
/// <param name="cookie">用户Cookie</param> /// <param name="cookie">用户Cookie</param>
/// <param name="item">视频信息</param> /// <param name="item">视频信息</param>
/// <param name="data">视频信息对象</param>
/// <param name="config">应用配置</param> /// <param name="config">应用配置</param>
/// <param name="followed">关注</param> /// <param name="followed">关注</param>
/// <param name="cate"></param> /// <param name="cate"></param>
@@ -823,14 +852,14 @@ namespace dy.net.job
if (v == null) if (v == null)
{ {
Serilog.Log.Error($"[{VideoType}][{cookie.UserName}][{item.Desc}]未获取到下载地址"); Serilog.Log.Error($"[{cookie.UserName}][{VideoType.GetDesc()}][{item.Desc}]未获取到下载地址");
return null; return null;
} }
//Serilog.Log.Debug($"{v.QualityType}-{v.BitRateValue}-{v.IsH265}-{v.HdrBit}"); //Serilog.Log.Debug($"{v.QualityType}-{v.BitRateValue}-{v.IsH265}-{v.HdrBit}");
var videoUrl = v.PlayAddr.UrlList.Where(x => !string.IsNullOrEmpty(x))?.LastOrDefault(); var videoUrl = v.PlayAddr.UrlList.Where(x => !string.IsNullOrEmpty(x))?.LastOrDefault();
if (string.IsNullOrWhiteSpace(videoUrl)) if (string.IsNullOrWhiteSpace(videoUrl))
{ {
Serilog.Log.Error($"[{VideoType}][{cookie.UserName}][{item.Desc}]未获取到下载地址"); Serilog.Log.Error($"[{cookie.UserName}][{VideoType.GetDesc()}]未获取到下载地址");
return null; return null;
} }
// 创建保存文件夹 // 创建保存文件夹
@@ -848,7 +877,7 @@ namespace dy.net.job
return null; return null;
} }
Log.Debug($"[{VideoType.GetDesc()}][{item?.Author?.Nickname ?? ""}]-视频[{DouyinFileNameHelper.SanitizeLinuxFileName(item.Desc, item.AwemeId)}]开始下载..."); Log.Debug($"[{cookie.UserName}][{VideoType.GetDesc()}][{item?.Author?.Nickname ?? ""}]-视频[{DouyinFileNameHelper.SanitizeLinuxFileName(item.Desc, item.AwemeId)}]开始下载...");
// 随机延迟 // 随机延迟
await Task.Delay(_random.Next(1, 4) * 1000); await Task.Delay(_random.Next(1, 4) * 1000);
@@ -856,7 +885,7 @@ namespace dy.net.job
var (Success, _) = await douyinHttpClientService.DownloadAsync(videoUrl, savePath, cookie.Cookies); var (Success, _) = await douyinHttpClientService.DownloadAsync(videoUrl, savePath, cookie.Cookies);
if (!Success) if (!Success)
{ {
Log.Error($"[{VideoType.GetDesc()}][{item?.Author?.Nickname ?? ""}]-视频[{DouyinFileNameHelper.SanitizeLinuxFileName(item.Desc, item.AwemeId)}]下载失败!!!"); Log.Error($"[{cookie.UserName}][{VideoType.GetDesc()}][{item?.Author?.Nickname ?? ""}]-视频[{DouyinFileNameHelper.SanitizeLinuxFileName(item.Desc, item.AwemeId)}]下载失败!!!");
(bool flowControl, DouyinVideo value) = await SwitchOtherUrlAddressDown(cookie, item, videoUrl, savePath); (bool flowControl, DouyinVideo value) = await SwitchOtherUrlAddressDown(cookie, item, videoUrl, savePath);
if (!flowControl) if (!flowControl)
@@ -866,7 +895,7 @@ namespace dy.net.job
} }
else else
{ {
Log.Debug($"[{VideoType.GetDesc()}][{item?.Author?.Nickname ?? ""}]-视频[{DouyinFileNameHelper.SanitizeLinuxFileName(item.Desc, item.AwemeId)}]下载完成."); Log.Debug($"[{cookie.UserName}][{VideoType.GetDesc()}][{item?.Author?.Nickname ?? ""}]-视频[{DouyinFileNameHelper.SanitizeLinuxFileName(item.Desc, item.AwemeId)}]下载完成.");
} }
// 下载视频封面 // 下载视频封面
await GetDownVideoCover(item, saveFolder, cookie, config, cate); await GetDownVideoCover(item, saveFolder, cookie, config, cate);
@@ -921,7 +950,7 @@ namespace dy.net.job
// 拼接视频保存路径 // 拼接视频保存路径
var savePath = Path.Combine(saveFolder, fileName); var savePath = Path.Combine(saveFolder, fileName);
Log.Debug($"[{VideoType.GetDesc()}]-动态视频[{DouyinFileNameHelper.SanitizeLinuxFileName(item.Desc, item.AwemeId)}]开始下载..."); Log.Debug($"[{cookie.UserName}][{VideoType.GetDesc()}]-动态视频[{DouyinFileNameHelper.SanitizeLinuxFileName(item.Desc, item.AwemeId)}]开始下载...");
// 随机延迟 // 随机延迟
int i = 1; int i = 1;
@@ -943,12 +972,12 @@ namespace dy.net.job
var (Success, ActualSavePath) = await douyinHttpClientService.DownloadAsync(dynamicUrl.Path, dynamicSavePath, cookie.Cookies); var (Success, ActualSavePath) = await douyinHttpClientService.DownloadAsync(dynamicUrl.Path, dynamicSavePath, cookie.Cookies);
if (!Success) if (!Success)
{ {
Log.Error($"[{VideoType.GetDesc()}][{item?.Author?.Nickname ?? ""}]-动态视频[{dynamicSavePath}]-00{i},下载失败!!!"); Log.Error($"[{cookie.UserName}][{VideoType.GetDesc()}][{item?.Author?.Nickname ?? ""}]-动态视频[{dynamicSavePath}]-00{i},下载失败!!!");
} }
else else
{ {
dynamicSavePaths.Add(v); dynamicSavePaths.Add(v);
Log.Debug($"[{VideoType.GetDesc()}][{item?.Author?.Nickname ?? ""}]-动态视频[{dynamicSavePath}]-00{i},下载完成."); Log.Debug($"[{cookie.UserName}][{VideoType.GetDesc()}][{item?.Author?.Nickname ?? ""}]-动态视频[{dynamicSavePath}]-00{i},下载完成.");
} }
await Task.Delay(_random.Next(1, 4) * 1000); await Task.Delay(_random.Next(1, 4) * 1000);
} }
@@ -1013,17 +1042,17 @@ namespace dy.net.job
var (Success, ActualSavePath) = await douyinHttpClientService.DownloadAsync(videoUrl, savePath, cookie.Cookies, otherUrls); var (Success, ActualSavePath) = await douyinHttpClientService.DownloadAsync(videoUrl, savePath, cookie.Cookies, otherUrls);
if (Success) if (Success)
{ {
Log.Debug($"[{VideoType.GetDesc()}]-尝试多个地址后,下载成功,{savePath}"); Log.Debug($"[{cookie.UserName}][{VideoType.GetDesc()}]-尝试多个地址后,下载成功,{savePath}");
} }
else else
{ {
Log.Error($"[{VideoType.GetDesc()}]-尝试多个地址后,依旧下载失败,{item.Desc}"); Log.Error($"[{cookie.UserName}][{VideoType.GetDesc()}]-尝试多个地址后,依旧下载失败,{item.Desc}");
return (flowControl: false, value: null); return (flowControl: false, value: null);
} }
} }
else else
{ {
Log.Error($"[{VideoType.GetDesc()}]-没有找到额外下载链接可以下载了," + item.Desc); Log.Error($"[{cookie.UserName}][{VideoType.GetDesc()}]-没有找到额外可以尝试下载链接了," + item.Desc);
return (flowControl: false, value: null); return (flowControl: false, value: null);
} }
@@ -1098,7 +1127,7 @@ namespace dy.net.job
var mergeResult = await douyinMergeVideoService.MergeToVideo(cookie.Cookies, AppContext.BaseDirectory, reqParams, savePath, fileNamefolder, config.DownImageVideo, config.DownImage, config.DownMp3); var mergeResult = await douyinMergeVideoService.MergeToVideo(cookie.Cookies, AppContext.BaseDirectory, reqParams, savePath, fileNamefolder, config.DownImageVideo, config.DownImage, config.DownMp3);
if (!mergeResult) if (!mergeResult)
{ {
Log.Error($"[{VideoType.GetDesc()}]-图文视频-[{DouyinFileNameHelper.SanitizeLinuxFileName(item.Desc, item.AwemeId)}]合成失败!!!"); Log.Error($"[{cookie.UserName}][{VideoType.GetDesc()}]-图文视频-[{DouyinFileNameHelper.SanitizeLinuxFileName(item.Desc, item.AwemeId)}]合成失败!!!");
return null; return null;
} }
@@ -1110,13 +1139,13 @@ namespace dy.net.job
// 检查合成后的视频文件是否有效 // 检查合成后的视频文件是否有效
if (!File.Exists(savePath) || new FileInfo(savePath).Length <= 0) if (!File.Exists(savePath) || new FileInfo(savePath).Length <= 0)
{ {
Log.Error($"[{VideoType.GetDesc()}]-图文视频-[{DouyinFileNameHelper.SanitizeLinuxFileName(item.Desc, item.AwemeId)}]合成失败!!!"); Log.Error($"[{cookie.UserName}][{VideoType.GetDesc()}]-图文视频-[{DouyinFileNameHelper.SanitizeLinuxFileName(item.Desc, item.AwemeId)}]合成失败!!!");
// 清理无效的文件和文件夹 // 清理无效的文件和文件夹
if (Directory.Exists(fileNamefolder)) if (Directory.Exists(fileNamefolder))
{ {
File.Delete(savePath); File.Delete(savePath);
Directory.Delete(fileNamefolder, true); Directory.Delete(fileNamefolder, true);
Log.Error($"[{VideoType.GetDesc()}]-图文视频-删除合成失败的视频文件和目录..."); Log.Error($"[{cookie.UserName}][{VideoType.GetDesc()}]-图文视频-删除合成失败的视频文件和目录...");
} }
return null; return null;
} }
@@ -1160,7 +1189,7 @@ namespace dy.net.job
} }
catch (Exception ex) catch (Exception ex)
{ {
Log.Error(ex, $"[{VideoType.GetDesc()}]-图片视频同步-处理图片集并合成视频时出错"); Log.Error(ex, $"[{cookie.UserName}][{VideoType.GetDesc()}]-图片视频同步-处理图片集并合成视频时出错");
return null; return null;
} }
} }
@@ -1169,9 +1198,10 @@ namespace dy.net.job
/// 保存视频信息到数据库 /// 保存视频信息到数据库
/// 批量插入视频实体列表到数据库中 /// 批量插入视频实体列表到数据库中
/// </summary> /// </summary>
/// <param name="cookie"></param>
/// <param name="videos">要保存的视频实体列表</param> /// <param name="videos">要保存的视频实体列表</param>
/// <returns>保存成功的视频数量</returns> /// <returns>保存成功的视频数量</returns>
protected async Task<int> SaveVideos(List<DouyinVideo> videos) protected async Task<int> SaveVideos(DouyinCookie cookie, List<DouyinVideo> videos)
{ {
if (!videos.Any()) return 0; if (!videos.Any()) return 0;
try try
@@ -1181,9 +1211,9 @@ namespace dy.net.job
} }
catch (Exception ex) catch (Exception ex)
{ {
Log.Error(ex, $"[{VideoType.GetDesc()}]-批量保存视频到数据库失败"); Log.Error(ex, $"[{cookie.UserName}][{VideoType.GetDesc()}]-批量保存视频到数据库失败");
// 清理保存失败的视频文件 // 清理保存失败的视频文件
await CleanupFailedVideos(videos); await CleanupFailedVideos(cookie,videos);
return 0; return 0;
} }
} }
@@ -1284,11 +1314,12 @@ namespace dy.net.job
/// 清理保存失败的视频文件 /// 清理保存失败的视频文件
/// 当数据库保存失败时,删除已下载的视频文件和文件夹 /// 当数据库保存失败时,删除已下载的视频文件和文件夹
/// </summary> /// </summary>
/// <param name="cookie"></param>
/// <param name="videos">保存失败的视频实体列表</param> /// <param name="videos">保存失败的视频实体列表</param>
/// <returns>一个表示异步操作的任务</returns> /// <returns>一个表示异步操作的任务</returns>
private async Task CleanupFailedVideos(List<DouyinVideo> videos) private async Task CleanupFailedVideos(DouyinCookie cookie, List<DouyinVideo> videos)
{ {
Log.Debug($"[{VideoType.GetDesc()}]-数据库保存失败,开始清理本次下载的视频目录..."); Log.Debug($"[{cookie.UserName}][{VideoType.GetDesc()}]-数据库保存失败,开始清理本次下载的视频目录...");
foreach (var video in videos) foreach (var video in videos)
{ {
@@ -1306,12 +1337,12 @@ namespace dy.net.job
{ {
Directory.Delete(directory); Directory.Delete(directory);
} }
Log.Debug($"[{VideoType.GetDesc()}]-清理失败视频文件成功: {video.VideoSavePath}!!!"); Log.Debug($"[{cookie.UserName}][{VideoType.GetDesc()}]-清理失败视频文件成功: {video.VideoSavePath}!!!");
} }
catch (Exception ex) catch (Exception ex)
{ {
Log.Warning(ex, $"[{VideoType.GetDesc()}]-清理失败视频文件出错: {video.VideoSavePath}!!!"); Log.Warning(ex, $"[{cookie.UserName}][{VideoType.GetDesc()}]-清理失败视频文件出错: {video.VideoSavePath}!!!");
} }
}); });
} }
@@ -1403,23 +1434,6 @@ namespace dy.net.job
return video; return video;
} }
/// <summary>
///
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
//private string GetVideoSimplifyTitle(Aweme item)
//{
// var config = douyinCommonService.GetConfig();
// string simplifiedTitle = string.Empty;
// if (config?.UperUseViedoTitle ?? false)
// {
// simplifiedTitle = DouyinFileNameHelper.SanitizeLinuxFileName(item.Desc, item.AwemeId);
// }
// return simplifiedTitle;
//}
#endregion #endregion
+13 -17
View File
@@ -115,7 +115,7 @@ namespace dy.net.job
LOG_TAG_SERIES); LOG_TAG_SERIES);
} }
Log.Debug($"[{ck.UserName}]所有[基础数据(list)]同步完成,包括 [收藏列表、关注列表、合集列表、短剧列表] "); Log.Debug($"[{ck.UserName}][基础数据]同步完成,包括 [收藏列表、关注列表、合集列表、短剧列表],这不是同步视频!!! ");
} }
} }
@@ -189,7 +189,7 @@ namespace dy.net.job
{ {
if (string.IsNullOrWhiteSpace(cookie.SecUserId)) if (string.IsNullOrWhiteSpace(cookie.SecUserId))
{ {
Log.Debug($"[{cookie.UserName}][{LOG_TAG_FOLLOW}]:未设置SecUserId跳过"); Log.Debug($"[{cookie.UserName}][{LOG_TAG_FOLLOW}]:未设置SecUserId如果要同步关注列表(你关注的博主),需要在授权页面填写你的sce_user_id");
return; return;
} }
@@ -197,29 +197,25 @@ namespace dy.net.job
string offset = "0"; string offset = "0";
bool hasMore = true; bool hasMore = true;
int total = 0; int total = 0;
FollowErrorDto currentError = null;
try try
{ {
while (hasMore) while (hasMore)
{ {
var data = await _douyinService.SyncMyFollows( var data = await _douyinService.SyncMyFollows(
DEFAULT_FOLLOW_COUNT, offset, cookie.SecUserId, cookie.Cookies, DEFAULT_FOLLOW_COUNT, offset, cookie.SecUserId, cookie.Cookies);
async (err) =>
{
currentError = err;
cookie.StatusMsg = err.StatusCode == INVALID_COOKIE_STATUS_CODE ? "无效" : "正常";
cookie.StatusCode = err.StatusCode;
await _dyCookieService.UpdateAsync(cookie);
});
if (currentError != null && currentError.StatusCode != 0) if (data == null)
{ {
Log.Error($"[{cookie.UserName}] - {LOG_TAG_FOLLOW}:错误 状态码:{currentError.StatusCode} 信息:{currentError.StatusMsg}"); Log.Error($"[{cookie.UserName}] - {LOG_TAG_FOLLOW}同步异常,请检查cookie");
if (currentError.StatusCode == INVALID_COOKIE_STATUS_CODE) break; break;
}
else
{
cookie.StatusCode = data.StatusCode;
cookie.StatusMsg = data.StatusCode == 0 ? "正常" : "异常";
await _dyCookieService.UpdateAsync(cookie);
} }
if (data == null) break;
if (string.IsNullOrWhiteSpace(cookie.MyUserId)) if (string.IsNullOrWhiteSpace(cookie.MyUserId))
{ {
@@ -243,7 +239,7 @@ namespace dy.net.job
if (followList.Any()) if (followList.Any())
{ {
var (add, update, succ) = await _followService.Sync(followList, cookie); var (add, update, succ) = await _followService.Sync(followList, cookie);
Log.Debug($"[{cookie.UserName}][{LOG_TAG_FOLLOW}]:同步完成 新增:{add} 更新:{update} 成功:{succ} 总关注数:{total}"); Log.Debug($"[{cookie.UserName}][{LOG_TAG_FOLLOW}]:同步完成 新增:{add} 更新:{update} 成功:{succ} 总关注数:{total},这是同步关注列表,这不是同步视频!!!");
} }
await _douyinCommonService.SetConfigNotFirstRunning(); await _douyinCommonService.SetConfigNotFirstRunning();
} }
-24
View File
@@ -1,24 +0,0 @@
using Newtonsoft.Json;
namespace dy.net.model.dto
{
public class FollowErrorDto
{
/// <summary>
///
/// </summary>
[JsonProperty("followings")]
public object Followings { get; set; }
/// <summary>
/// 8 - 用户未登录
/// </summary>
[JsonProperty("status_code")]
public int StatusCode { get; set; }
/// <summary>
/// 用户未登录
/// </summary>
[JsonProperty("status_msg")]
public string StatusMsg { get; set; }
}
}
+2
View File
@@ -50,4 +50,6 @@
Description = description ?? throw new ArgumentNullException(nameof(description), "任务描述不能为空"); Description = description ?? throw new ArgumentNullException(nameof(description), "任务描述不能为空");
} }
} }
} }
+8
View File
@@ -38,6 +38,14 @@
/// </summary> /// </summary>
//[Description("收藏短剧")] //[Description("收藏短剧")]
dy_series = 7, dy_series = 7,
/// <summary>
/// 同步关注列表--
/// </summary>
dy_followuser=1000,
/// <summary>
/// 同步关注列表一次
/// </summary>
dy_followuser_once = 2000,
} }
+13
View File
@@ -43,6 +43,19 @@ namespace dy.net.model.entity
/// </summary> /// </summary>
public bool DownSeries { get; set; } public bool DownSeries { get; set; }
/// <summary> /// <summary>
/// 是否下载收藏夹视频
/// </summary>
public bool DownCollect { get; set; }
/// <summary>
/// 是否下载点赞(喜欢的)视频
/// </summary>
public bool DownFavorite { get; set; }
/// <summary>
/// 是否下载关注的博主的作品
/// </summary>
public bool DownFollowd { get; set; }
/// <summary>
/// 1 开启同步 0 关闭同步 /// 1 开启同步 0 关闭同步
/// </summary> /// </summary>
public int Status { get; set; } public int Status { get; set; }
+18
View File
@@ -72,6 +72,24 @@ namespace dy.net.service
return _cookieRepository.Insert(cookie); return _cookieRepository.Insert(cookie);
} }
/// <summary>
/// 新增字段,兼容旧版本
/// </summary>
/// <returns></returns>
public async Task<bool> UpdateCookieToSupportOldVersionAsync()
{
var cookies = _cookieRepository.GetAll();
foreach (var item in cookies)
{
item.DownCollect = !string.IsNullOrWhiteSpace(item.SavePath) && !item.UseCollectFolder;
item.DownFavorite = !string.IsNullOrWhiteSpace(item.FavSavePath);
item.DownFollowd = !string.IsNullOrWhiteSpace(item.UpSavePath);
}
await _cookieRepository.UpdateRangeAsync(cookies);
return true;
}
// 查询单个 // 查询单个
public async Task<DouyinCookie> GetByIdAsync(string id) public async Task<DouyinCookie> GetByIdAsync(string id)
{ {
+7
View File
@@ -125,6 +125,13 @@ namespace dy.net.service
/// <returns></returns> /// <returns></returns>
public async Task<bool> OpenOrCloseSync(FollowUpdateDto dto) public async Task<bool> OpenOrCloseSync(FollowUpdateDto dto)
{ {
var followdOpendCount = await _followRepository.CountAsync(x => x.OpenSync);
if (followdOpendCount > 20)
{
throw new Exception("为了减少被风控的可能,关注列表最多同时开启20个,抱歉");
}
var followed = await _followRepository.GetByIdAsync(dto.Id); var followed = await _followRepository.GetByIdAsync(dto.Id);
if (followed != null) if (followed != null)
{ {
+3 -27
View File
@@ -642,9 +642,8 @@ namespace dy.net.service
/// <param name="offset"></param> /// <param name="offset"></param>
/// <param name="secUserId"></param> /// <param name="secUserId"></param>
/// <param name="cookie"></param> /// <param name="cookie"></param>
/// <param name="callBack"></param>
/// <returns></returns> /// <returns></returns>
public async Task<DouyinFollowInfoResponse> SyncMyFollows(string count, string offset, string secUserId, string cookie, Action<FollowErrorDto> callBack) public async Task<DouyinFollowInfoResponse> SyncMyFollows(string count, string offset, string secUserId, string cookie)
{ {
#region #region
@@ -686,29 +685,6 @@ namespace dy.net.service
{ {
var data = await respose.Content.ReadAsStringAsync(); var data = await respose.Content.ReadAsStringAsync();
var res = JsonConvert.DeserializeObject<DouyinFollowInfoResponse>(data); var res = JsonConvert.DeserializeObject<DouyinFollowInfoResponse>(data);
if (res != null)
{
if (res.Followings == null)
{
var err = JsonConvert.DeserializeObject<FollowErrorDto>(data);
if (err != null)
{
Serilog.Log.Error($"SyncMyFollows error: {err.StatusMsg}");
callBack?.Invoke(err);
}
Serilog.Log.Error($"SyncMyFollows error: 关注列表为空 :{data}");
}
else
{
callBack?.Invoke(new FollowErrorDto { StatusCode = 0 });
}
}
else
{
Serilog.Log.Error($"SyncMyFollows error: 反序列化结果为空,接口返回数据:{data}");
callBack?.Invoke(new FollowErrorDto { StatusCode = 8, StatusMsg = "未知" });
}
return res; return res;
} }
else else
@@ -739,12 +715,12 @@ namespace dy.net.service
if (!string.IsNullOrWhiteSpace(douyinCookie.SecUserId)) if (!string.IsNullOrWhiteSpace(douyinCookie.SecUserId))
{ {
var res = await SyncMyFollows("1", "1", douyinCookie.SecUserId, douyinCookie.Cookies, null); var res = await SyncMyFollows("1", "10", douyinCookie.SecUserId, douyinCookie.Cookies);
return res != null && res.StatusCode == 0; return res != null && res.StatusCode == 0;
} }
else else
{ {
var res = await SyncCollectVideos("0", "1", douyinCookie.Cookies); var res = await SyncCollectVideos("0", "10", douyinCookie.Cookies);
return res != null && res.StatusCode == 0; return res != null && res.StatusCode == 0;
} }
} }
+162 -52
View File
@@ -1,5 +1,6 @@
using dy.net.job; using dy.net.job;
using dy.net.model.dto; using dy.net.model.dto;
using dy.net.model.entity;
using dy.net.utils; using dy.net.utils;
using Quartz; using Quartz;
using Serilog; using Serilog;
@@ -12,16 +13,17 @@ namespace dy.net.service
public class DouyinQuartzJobService public class DouyinQuartzJobService
{ {
private readonly ISchedulerFactory _schedulerFactory; private readonly ISchedulerFactory _schedulerFactory;
private readonly DouyinCookieService douyinCookieService;
private const string DefaultJobGroup = "dysync.net"; private const string DefaultJobGroup = "dysync.net";
private const int DefaultIntervalMinutes = 30; private const int DefaultIntervalMinutes = 30;
private const int DefaultCronStartDelaySeconds = 30; private const int DefaultCronStartDelaySeconds = 30;
private const int DefaultSimpleStartDelaySeconds = 3; private const int DefaultSimpleStartDelaySeconds = 3;
// 任务配置信息(修复了series任务的Key重复问题,确保每个任务Key唯一) // 任务配置信息(修复了series任务的Key重复问题,确保每个任务Key唯一)
public static Dictionary<string, JobConfig> JobConfigs { get; } = new() public static Dictionary<VideoTypeEnum, JobConfig> JobConfigs { get; } = new()
{ {
{ {
VideoTypeEnum.dy_collects.GetDesc(), VideoTypeEnum.dy_collects,
new JobConfig( new JobConfig(
typeof(DouyinCollectSyncJob), typeof(DouyinCollectSyncJob),
"dy.job.key.collect", "dy.job.key.collect",
@@ -29,7 +31,7 @@ namespace dy.net.service
"抖音收藏同步任务") "抖音收藏同步任务")
}, },
{ {
VideoTypeEnum.dy_favorite.GetDesc(), VideoTypeEnum.dy_favorite,
new JobConfig( new JobConfig(
typeof(DouyinFavoritSyncJob), typeof(DouyinFavoritSyncJob),
"dy.job.key.favorite", "dy.job.key.favorite",
@@ -37,7 +39,7 @@ namespace dy.net.service
"抖音点赞同步任务") "抖音点赞同步任务")
}, },
{ {
VideoTypeEnum.dy_follows.GetDesc(), VideoTypeEnum.dy_follows,
new JobConfig( new JobConfig(
typeof(DouyinFollowedSyncJob), typeof(DouyinFollowedSyncJob),
"dy.job.key.followed", "dy.job.key.followed",
@@ -45,7 +47,7 @@ namespace dy.net.service
"抖音关注博主作品同步任务") "抖音关注博主作品同步任务")
}, },
{ {
"关注列表", VideoTypeEnum.dy_followuser,
new JobConfig( new JobConfig(
typeof(DouyinFollowsAndCollnectsSyncJob), typeof(DouyinFollowsAndCollnectsSyncJob),
"dy.job.key.follow_user", "dy.job.key.follow_user",
@@ -53,7 +55,7 @@ namespace dy.net.service
"抖音关注列表同步任务") "抖音关注列表同步任务")
}, },
{ {
VideoTypeEnum.dy_custom_collect.GetDesc(), VideoTypeEnum.dy_custom_collect,
new JobConfig( new JobConfig(
typeof(DouyinCollectCustomSyncJob), typeof(DouyinCollectCustomSyncJob),
"dy.job.key.custom_collect", "dy.job.key.custom_collect",
@@ -61,7 +63,7 @@ namespace dy.net.service
"抖音自定义收藏夹列表同步任务") "抖音自定义收藏夹列表同步任务")
}, },
{ {
VideoTypeEnum.dy_mix.GetDesc(), VideoTypeEnum.dy_mix,
new JobConfig( new JobConfig(
typeof(DouyinMixSyncJob), typeof(DouyinMixSyncJob),
"dy.job.key.mix", "dy.job.key.mix",
@@ -69,7 +71,7 @@ namespace dy.net.service
"抖音收藏夹合集同步任务") "抖音收藏夹合集同步任务")
}, },
{ {
VideoTypeEnum.dy_series.GetDesc(), VideoTypeEnum.dy_series,
new JobConfig( new JobConfig(
typeof(DouyinSeriesSyncJob), typeof(DouyinSeriesSyncJob),
"dy.job.key.series", "dy.job.key.series",
@@ -77,7 +79,7 @@ namespace dy.net.service
"抖音收藏夹短剧同步任务") "抖音收藏夹短剧同步任务")
}, },
{ {
"sync_follow_user_once", VideoTypeEnum.dy_followuser_once,
new JobConfig( new JobConfig(
typeof(DouyinFollowsAndCollnectsSyncJob), typeof(DouyinFollowsAndCollnectsSyncJob),
"dy.job.key.sync_follow_user_once", "dy.job.key.sync_follow_user_once",
@@ -86,68 +88,160 @@ namespace dy.net.service
} }
}; };
public DouyinQuartzJobService(ISchedulerFactory schedulerFactory) public DouyinQuartzJobService(ISchedulerFactory schedulerFactory,DouyinCookieService douyinCookieService)
{ {
_schedulerFactory = schedulerFactory ?? throw new ArgumentNullException(nameof(schedulerFactory)); _schedulerFactory = schedulerFactory ?? throw new ArgumentNullException(nameof(schedulerFactory));
this.douyinCookieService = douyinCookieService;
} }
/// <summary> /// <summary>
/// 启所有抖音相关定时任务(所有任务独立执行) /// 初始化或重启所有抖音定时任务
/// </summary> /// </summary>
/// <param name="expression">Cron表达式或间隔分钟数(所有任务使用相同的执行频率</param> /// <param name="cronExpression">定时任务表达式(分钟数</param>
/// <returns>是否启动成功</returns> /// <returns>是否成功初始化</returns>
public async Task<bool> InitOrReStartAllJobs(string expression) public async Task<bool> InitOrReStartAllJobs(string cronExpression)
{ {
if (string.IsNullOrWhiteSpace(expression))
{
Log.Debug("定时任务表达式为空,使用默认配置({DefaultMinutes}分钟)", DefaultIntervalMinutes);
expression = DefaultIntervalMinutes.ToString();
}
try try
{ {
var scheduler = await _schedulerFactory.GetScheduler(); // 1. 获取并验证Cookie
var validCookies = await douyinCookieService.GetOpendCookiesAsync();
if (validCookies == null || !validCookies.Any())
{
Serilog.Log.Debug("没有有效的抖音Cookie,无法启动定时任务");
return false;
}
// 2. 处理定时任务表达式
var taskIntervalExpression = ResolveTaskExpression(cronExpression);
// 3. 获取调度器并清理现有任务
var scheduler = await _schedulerFactory.GetScheduler();
if (scheduler == null)
{
Log.Error("获取任务调度器失败,无法初始化定时任务");
return false;
}
// 移除所有已存在的任务(避免重复调度)
await RemoveAllExistingJobs(scheduler); await RemoveAllExistingJobs(scheduler);
// 4. 检查各类型任务的启用条件
var taskEnableConditions = GetTaskEnableConditions(validCookies);
// 执行任务启动逻辑 // 5. 启动符合条件的定时任务
int successfullyStartedJobs = 0;
foreach (var jobKey in JobConfigs.Keys) foreach (var jobKey in JobConfigs.Keys)
{ {
if (jobKey == "sync_follow_user_once") // 跳过一次性关注用户任务
if (jobKey == VideoTypeEnum.dy_followuser_once)
continue; 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} 个定时任务");
// 处理关注用户任务(固定60分钟执行频率)
if (jobKey == VideoTypeEnum.dy_followuser)
{
bool startSuccess = await StartSingleJobAsync(jobKey, "60");
if (startSuccess) successfullyStartedJobs++;
continue;
}
// 根据不同任务类型和启用条件启动任务
bool isTaskEnabled = jobKey switch
{
VideoTypeEnum.dy_favorite => taskEnableConditions.IsFavoriteEnabled,
VideoTypeEnum.dy_collects => taskEnableConditions.IsCollectEnabled,
VideoTypeEnum.dy_follows => taskEnableConditions.IsFollowedEnabled,
VideoTypeEnum.dy_custom_collect => taskEnableConditions.IsCustomCollectEnabled,
VideoTypeEnum.dy_mix => taskEnableConditions.IsMixEnabled,
VideoTypeEnum.dy_series => taskEnableConditions.IsSeriesEnabled,
_ => false
};
if (isTaskEnabled)
{
bool startSuccess = await StartSingleJobAsync(jobKey, taskIntervalExpression);
if (startSuccess) successfullyStartedJobs++;
}
}
// 6. 输出任务启动统计日志
Log.Information($"定时任务初始化完成,共尝试启动 {JobConfigs.Count - 1} 个任务,成功启动 {successfullyStartedJobs} 个");
//await StartJobAsync(VideoTypeEnum.dy_custom_collect.GetDesc(), expression);
//await StartFollowJobOnceAsync();
return true; return true;
} }
catch (Exception ex) catch (Exception ex)
{ {
Log.Error(ex, "【任务服务】初始化所有定时任务异常"); Log.Error(ex, "【quartz】初始化所有抖音定时任务时发生异常");
return false; return false;
} }
} }
/// <summary>
/// 解析任务执行表达式,为空时使用默认值
/// </summary>
/// <param name="inputExpression">输入的表达式</param>
/// <returns>处理后的表达式</returns>
private static string ResolveTaskExpression(string inputExpression)
{
if (string.IsNullOrWhiteSpace(inputExpression))
{
Log.Debug("定时任务表达式为空,使用默认配置({DefaultMinutes}分钟)", DefaultIntervalMinutes);
return DefaultIntervalMinutes.ToString();
}
return inputExpression;
}
/// <summary>
/// 获取各类型任务的启用条件
/// </summary>
/// <param name="cookies">有效的抖音Cookie列表</param>
/// <returns>任务启用条件集合</returns>
private static TaskEnableConditions GetTaskEnableConditions(IEnumerable<DouyinCookie> cookies)
{
return new TaskEnableConditions
{
IsCollectEnabled = cookies.Any(x => x.DownCollect && !x.UseCollectFolder && !string.IsNullOrWhiteSpace(x.SavePath)),
IsFavoriteEnabled = cookies.Any(x => x.DownFavorite && !string.IsNullOrWhiteSpace(x.FavSavePath)),
IsFollowedEnabled = cookies.Any(x => x.DownFollowd && !string.IsNullOrWhiteSpace(x.UpSavePath)),
IsMixEnabled = cookies.Any(x => x.DownMix && !string.IsNullOrWhiteSpace(x.MixPath) && !string.IsNullOrWhiteSpace(x.SavePath)),
IsSeriesEnabled = cookies.Any(x => x.DownSeries && !string.IsNullOrWhiteSpace(x.SeriesPath) && !string.IsNullOrWhiteSpace(x.SavePath)),
IsCustomCollectEnabled = cookies.Any(x => x.UseCollectFolder && !string.IsNullOrWhiteSpace(x.SavePath))
};
}
/// <summary>
/// 启动单个定时任务(封装重复的启动逻辑)
/// </summary>
/// <param name="jobKey">任务类型</param>
/// <param name="expression">执行频率表达式</param>
/// <returns>是否启动成功</returns>
private async Task<bool> StartSingleJobAsync(VideoTypeEnum jobKey, string expression)
{
try
{
bool startSuccess = await StartJobAsync(jobKey, expression);
if (startSuccess)
{
Log.Debug($"【quartz】成功启动任务:{jobKey},执行频率:{expression}分钟");
}
else
{
Log.Error($"【quartz】启动任务失败:{jobKey}");
}
return startSuccess;
}
catch (Exception ex)
{
Log.Error(ex, $"【quartz】启动任务 {jobKey} 时发生异常");
return false;
}
}
/// <summary> /// <summary>
/// 启动关注同步任务(单次执行) /// 启动关注同步任务(单次执行)
/// </summary> /// </summary>
public async Task<bool> StartFollowJobOnceAsync() public async Task<bool> StartFollowJobOnceAsync()
{ {
return await StartOneTimeJobAsync("sync_follow_user_once"); return await StartOneTimeJobAsync(VideoTypeEnum.dy_followuser_once);
} }
/// <summary> /// <summary>
@@ -160,7 +254,7 @@ namespace dy.net.service
{ {
if (await scheduler.CheckExists(jobKey)) if (await scheduler.CheckExists(jobKey))
{ {
Log.Information("【任务服务】移除已存在的任务: {JobKey}", jobKey); Log.Debug("【quartz】移除已存在的任务: {JobKey}", jobKey);
await scheduler.DeleteJob(jobKey); await scheduler.DeleteJob(jobKey);
} }
} }
@@ -172,11 +266,11 @@ namespace dy.net.service
/// <param name="configKey">任务配置Key(如:collect、favorite</param> /// <param name="configKey">任务配置Key(如:collect、favorite</param>
/// <param name="expression">定时表达式(Cron或间隔分钟数)</param> /// <param name="expression">定时表达式(Cron或间隔分钟数)</param>
/// <returns>是否启动成功</returns> /// <returns>是否启动成功</returns>
public async Task<bool> StartJobAsync(string configKey, string expression) public async Task<bool> StartJobAsync(VideoTypeEnum configKey, string expression)
{ {
if (!JobConfigs.TryGetValue(configKey, out var jobConfig)) if (!JobConfigs.TryGetValue(configKey, out var jobConfig))
{ {
Log.Error("【任务服务】找不到任务配置: {ConfigKey}", configKey); Log.Error("【quartz】找不到任务配置: {ConfigKey}", configKey);
return false; return false;
} }
@@ -201,7 +295,7 @@ namespace dy.net.service
// 调度任务 // 调度任务
await scheduler.ScheduleJob(jobDetail, trigger); await scheduler.ScheduleJob(jobDetail, trigger);
Log.Information("【任务服务】启动任务成功 - 任务描述: {JobDescription}, 执行频率: {Expression}", Log.Information("【quartz】启动任务成功 - 任务描述: {JobDescription}, 执行频率: {Expression}",
jobConfig.Description, jobConfig.Description,
expression); expression);
@@ -209,7 +303,7 @@ namespace dy.net.service
} }
catch (Exception ex) catch (Exception ex)
{ {
Log.Error(ex, "【任务服务】启动任务失败 - 任务描述: {JobDescription}", jobConfig.Description); Log.Error(ex, "【quartz】启动任务失败 - 任务描述: {JobDescription}", jobConfig.Description);
return false; return false;
} }
} }
@@ -217,11 +311,11 @@ namespace dy.net.service
/// <summary> /// <summary>
/// 启动单次执行任务 /// 启动单次执行任务
/// </summary> /// </summary>
private async Task<bool> StartOneTimeJobAsync(string configKey) private async Task<bool> StartOneTimeJobAsync(VideoTypeEnum configKey)
{ {
if (!JobConfigs.TryGetValue(configKey, out var jobConfig)) if (!JobConfigs.TryGetValue(configKey, out var jobConfig))
{ {
Log.Error("【任务服务】找不到任务配置: {ConfigKey}", configKey); Log.Error("【quartz】找不到任务配置: {ConfigKey}", configKey);
return false; return false;
} }
@@ -246,13 +340,13 @@ namespace dy.net.service
.Build(); .Build();
await scheduler.ScheduleJob(jobDetail, trigger); await scheduler.ScheduleJob(jobDetail, trigger);
Log.Debug("【任务服务】启动单次任务成功 - 任务描述: {JobDescription}", jobConfig.Description); Log.Debug("【quartz】启动单次任务成功 - 任务描述: {JobDescription}", jobConfig.Description);
return true; return true;
} }
catch (Exception ex) catch (Exception ex)
{ {
Log.Error(ex, "【任务服务】启动单次任务失败 - 任务描述: {JobDescription}", jobConfig.Description); Log.Error(ex, "【quartz】启动单次任务失败 - 任务描述: {JobDescription}", jobConfig.Description);
return false; return false;
} }
} }
@@ -288,8 +382,8 @@ namespace dy.net.service
} }
// 无效表达式,使用默认配置 // 无效表达式,使用默认配置
Log.Warning("【任务服务】无效的任务表达式: {Expression},使用默认间隔{DefaultMinutes}分钟", //Log.Debug("【任务服务】无效的任务表达式: {Expression},使用默认间隔{DefaultMinutes}分钟",
expression, DefaultIntervalMinutes); // expression, DefaultIntervalMinutes);
return TriggerBuilder.Create() return TriggerBuilder.Create()
.WithIdentity(triggerKey) .WithIdentity(triggerKey)
@@ -308,9 +402,25 @@ namespace dy.net.service
{ {
if (await scheduler.CheckExists(jobKey)) if (await scheduler.CheckExists(jobKey))
{ {
Log.Information("【任务服务】移除已存在的任务: {JobKey}", jobKey); Log.Debug("【quartz】移除已存在的任务: {JobKey}", jobKey);
await scheduler.DeleteJob(jobKey); await scheduler.DeleteJob(jobKey);
} }
} }
/// <summary>
/// 任务启用条件模型
/// </summary>
private class TaskEnableConditions
{
public bool IsCollectEnabled { get; set; }
public bool IsFavoriteEnabled { get; set; }
public bool IsFollowedEnabled { get; set; }
public bool IsMixEnabled { get; set; }
public bool IsSeriesEnabled { get; set; }
public bool IsCustomCollectEnabled { get; set; }
}
} }
} }