feat: add fnOS packaging, storage workflows and release pipeline

This commit is contained in:
2026-08-11 18:05:49 +08:00
parent c5922f9b08
commit 95932f0199
181 changed files with 24024 additions and 1164 deletions
+81 -9
View File
@@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using System.Net;
using System.Text.RegularExpressions;
using dy.net.storage;
namespace dy.net.Controllers
{
@@ -25,10 +26,12 @@ namespace dy.net.Controllers
private readonly DouyinFollowService douyinFollowService;
private readonly DouyinCookieService douyinCookieService;
private readonly DouyinHttpClientService httpClientService;
private readonly WebDavSettingsService _webDavSettingsService;
private readonly VideoTaskService _videoTaskService;
public ConfigController(DouyinCookieService dyCookieService, DouyinCommonService commonService, DouyinQuartzJobService quartzJobService, DouyinFollowService douyinFollowService, DouyinCookieService douyinCookieService, DouyinHttpClientService httpClientService)
public ConfigController(DouyinCookieService dyCookieService, DouyinCommonService commonService, DouyinQuartzJobService quartzJobService, DouyinFollowService douyinFollowService, DouyinCookieService douyinCookieService, DouyinHttpClientService httpClientService, WebDavSettingsService webDavSettingsService, VideoTaskService videoTaskService)
{
this.dyCookieService = dyCookieService;
this.commonService = commonService;
@@ -36,6 +39,8 @@ namespace dy.net.Controllers
this.douyinFollowService = douyinFollowService;
this.douyinCookieService = douyinCookieService;
this.httpClientService = httpClientService;
_webDavSettingsService = webDavSettingsService;
_videoTaskService = videoTaskService;
}
@@ -102,6 +107,11 @@ namespace dy.net.Controllers
{
if (cookies?.Count > 0)
{
foreach (var cookie in cookies) ClearImportedSourceHealth(cookie);
if (commonService.GetConfig()?.StorageType.IsRemote() == true)
{
foreach (var cookie in cookies) _webDavSettingsService.ApplyDefaultCookiePaths(cookie);
}
var imported = await douyinCookieService.ImportCookies(cookies);
if (imported)
Serilog.Log.Debug("抖音Cookie配置导入成功");
@@ -164,6 +174,12 @@ namespace dy.net.Controllers
if (!cookieValid)
return ApiResult.Fail("Cookie无效或已过期,请按照文档提示重新获取有效Cookie,不要使用插件获取cookie");
var result = await dyCookieService.FastResetCookie(dto.id, dto.cookie);
if (result)
{
if (!string.IsNullOrWhiteSpace(dto.id)) await _videoTaskService.ResetSourceHealthAsync(dto.id);
else
foreach (var item in await dyCookieService.GetAllAsync()) await _videoTaskService.ResetSourceHealthAsync(item.Id);
}
return result ? ApiResult.Success() : ApiResult.Fail("cookie设置失败");
}
@@ -193,8 +209,12 @@ namespace dy.net.Controllers
// 1. 基础赋值
dyUserCookies.Id = IdGener.GetLong().ToString();
var storageType = commonService.GetConfig()?.StorageType ?? StorageType.Local;
if (storageType.IsRemote())
_webDavSettingsService.ApplyDefaultCookiePaths(dyUserCookies);
// 2. 路径权限校验
var (Success, Message) = ValidatePaths(dyUserCookies);
var (Success, Message) = ValidatePaths(dyUserCookies, storageType);
if (!Success)
return ApiResult.Fail(Message);
@@ -212,8 +232,28 @@ namespace dy.net.Controllers
return saved ? ApiResult.Success() : ApiResult.Fail("添加失败");
}
private static (bool Success, string Message) ValidatePaths(DouyinCookie cookie)
private static (bool Success, string Message) ValidatePaths(DouyinCookie cookie, StorageType storageType)
{
if (storageType.IsRemote())
{
var remotePaths = new Dictionary<string, string>
{
{ "收藏 OpenList 相对路径", cookie.WebDavCollectPath },
{ "喜欢 OpenList 相对路径", cookie.WebDavFavoritePath },
{ "关注 OpenList 相对路径", cookie.WebDavFollowPath },
{ "合集 OpenList 相对路径", cookie.WebDavMixPath },
{ "短剧 OpenList 相对路径", cookie.WebDavSeriesPath }
};
foreach (var (label, path) in remotePaths)
{
if (string.IsNullOrWhiteSpace(path)) return (false, $"{label}不能为空");
try { StoragePath.NormalizeRemote(path); }
catch (ArgumentException ex) { return (false, $"{label}无效:{ex.Message}"); }
}
return (true, string.Empty);
}
var pathsToCheck = new Dictionary<string, string>
{
{ "收藏存储路径", cookie.SavePath },
@@ -262,6 +302,19 @@ namespace dy.net.Controllers
[HttpPost("update")]
public async Task<IActionResult> AddOrUpdateAsync([FromBody] DouyinCookie dyUserCookies)
{
if (dyUserCookies == null) return ApiResult.Fail("配置不能为空");
var isNewCookie = string.IsNullOrWhiteSpace(dyUserCookies.Id) || dyUserCookies.Id == "0";
var storageType = commonService.GetConfig()?.StorageType ?? StorageType.Local;
if (storageType.IsRemote())
{
if (isNewCookie)
dyUserCookies.Id = IdGener.GetLong().ToString();
_webDavSettingsService.ApplyDefaultCookiePaths(dyUserCookies);
var (success, message) = ValidatePaths(dyUserCookies, storageType);
if (!success) return ApiResult.Fail(message);
}
RemoveCookieLineString(dyUserCookies);
var checkCk = await httpClientService.CheckCookie(dyUserCookies);
@@ -271,9 +324,10 @@ namespace dy.net.Controllers
}
dyUserCookies.StatusCode = 0;
dyUserCookies.StatusMsg = "正常";
if (dyUserCookies.Id == "0")
if (isNewCookie)
{
dyUserCookies.Id = IdGener.GetLong().ToString();
if (string.IsNullOrWhiteSpace(dyUserCookies.Id) || dyUserCookies.Id == "0")
dyUserCookies.Id = IdGener.GetLong().ToString();
var result = await dyCookieService.Add(dyUserCookies);
if (result)
{
@@ -287,6 +341,7 @@ namespace dy.net.Controllers
var result = await dyCookieService.UpdateCookieAsync(dyUserCookies);
if (result)
{
await _videoTaskService.ResetSourceHealthAsync(dyUserCookies.Id);
ReStartJob();
return ApiResult.Success();
}
@@ -305,6 +360,18 @@ namespace dy.net.Controllers
}
}
private static void ClearImportedSourceHealth(DouyinCookie cookie)
{
cookie.ConsecutiveSourceForbidden = 0;
cookie.SourceCooldownUntil = null;
cookie.SourceRequiresAuthorization = false;
cookie.SourceProbePending = false;
cookie.SourceProbeInProgress = false;
cookie.LastSourceStatusCode = null;
cookie.LastSourceError = null;
cookie.SourceHealthUpdatedAt = null;
}
/// <summary>
/// 批量删除用户Cookie
/// </summary>
@@ -351,10 +418,15 @@ namespace dy.net.Controllers
//[Authorize]
public async Task<IActionResult> ExecuteJobNow()
{
var config = commonService.GetConfig();
if (config != null)
await quartzJobService.InitOrReStartAllJobs(config.Cron.ToString());
return ApiResult.Success();
try
{
var tasks = await quartzJobService.TriggerVideoJobsNowAsync();
return ApiResult.Success(new { taskIds = tasks.Select(x => x.Id).ToList(), count = tasks.Count });
}
catch (InvalidOperationException ex)
{
return ApiResult.Fail(ex.Message);
}
}