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
+154
View File
@@ -0,0 +1,154 @@
using dy.net.model.dto;
using dy.net.service;
using dy.net.storage;
using jzc.http.lib;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace dy.net.Controllers
{
[Route("api/[controller]")]
[ApiController]
[Authorize]
public class StorageController : ControllerBase
{
private readonly DouyinCommonService _commonService;
private readonly OpenListSettingsService _openListSettingsService;
private readonly OpenListMediaStorage _openListStorage;
private readonly DouyinQuartzJobService _quartzJobService;
private readonly DouyinVideoService _videoService;
public StorageController(
DouyinCommonService commonService,
OpenListSettingsService openListSettingsService,
OpenListMediaStorage openListStorage,
DouyinQuartzJobService quartzJobService,
DouyinVideoService videoService)
{
_commonService = commonService;
_openListSettingsService = openListSettingsService;
_openListStorage = openListStorage;
_quartzJobService = quartzJobService;
_videoService = videoService;
}
[HttpGet("inventory")]
public async Task<IActionResult> GetInventory()
{
var type = _commonService.GetConfig()?.StorageType ?? StorageType.Local;
return ApiResult.Success(await _videoService.GetStorageInventoryAsync(type));
}
[HttpGet("config")]
public async Task<IActionResult> GetConfig()
{
var type = _commonService.GetConfig()?.StorageType ?? StorageType.Local;
var inventory = await _videoService.GetStorageInventoryAsync(type);
var saved = await _openListSettingsService.HasSavedSettingsAsync();
var dto = await _openListSettingsService.ToDtoAsync(type);
dto.RequiresCutover = type == StorageType.WebDav || inventory.WebDavRecordCount > 0;
dto.LegacyWebDavRecordCount = inventory.WebDavRecordCount;
dto.SuggestedFromLegacy = !saved;
return ApiResult.Success(dto);
}
[HttpPost("test")]
public async Task<IActionResult> Test(
[FromBody] StorageTestRequest request, CancellationToken cancellationToken)
{
if (request == null) return ApiResult.Fail("测试请求不能为空");
if (request.StorageType != StorageType.OpenList)
return ApiResult.Fail("新远端存储只支持 OpenList 原生 API。");
var candidate = await _openListSettingsService.BuildCandidateAsync(
new OpenListTestRequest
{
Endpoint = request.Endpoint,
BasePath = request.BasePath,
LocalStagingPath = request.LocalStagingPath,
SourcePath = request.SourcePath,
UserName = request.UserName,
Password = request.Password
});
var result = await _openListStorage.ProbeAsync(candidate, cancellationToken);
return result.Success
? ApiResult.Success(new { message = result.Message })
: ApiResult.Fail(result.Message);
}
[HttpPost("openlist/directories")]
public async Task<IActionResult> ListOpenListDirectories(
[FromBody] OpenListDirectoryRequest request,
CancellationToken cancellationToken)
{
if (request == null) return ApiResult.Fail("目录请求不能为空");
try
{
var candidate = await _openListSettingsService.BuildCandidateAsync(request);
return ApiResult.Success(await _openListStorage.ListDirectoriesAsync(
candidate, request.Path, cancellationToken));
}
catch (Exception ex) when (ex is InvalidOperationException or HttpRequestException)
{ return ApiResult.Fail(ex.GetBaseException().Message); }
}
[HttpPut("config")]
public async Task<IActionResult> Update(
[FromBody] StorageSettingsDto dto, CancellationToken cancellationToken)
{
if (dto == null) return ApiResult.Fail("配置不能为空");
if (dto.StorageType == StorageType.WebDav)
return ApiResult.Fail("WebDAV 已停止作为新存储目标,请改用 OpenList 原生 API。");
if (dto.StorageType == StorageType.OpenList)
{
var candidate = await _openListSettingsService.BuildCandidateAsync(
new OpenListTestRequest
{
Endpoint = dto.Endpoint,
BasePath = dto.BasePath,
LocalStagingPath = dto.LocalStagingPath,
SourcePath = dto.SourcePath,
UserName = dto.UserName,
Password = dto.Password
});
var result = await _openListStorage.ProbeAsync(candidate, cancellationToken);
if (!result.Success) return ApiResult.Fail("无法启用 OpenList" + result.Message);
await _openListSettingsService.SaveAsync(candidate, true, result.Message);
await _openListSettingsService.InitializeCookiePathsAsync();
var config = _commonService.GetConfig();
if (config == null) return ApiResult.Fail("系统配置不存在");
config.StorageType = dto.StorageType;
if (!await _commonService.UpdateConfig(config))
return ApiResult.Fail("存储模式保存失败");
await _quartzJobService.InitOrReStartAllJobs(config.Cron.ToString());
var response = await _openListSettingsService.ToDtoAsync(config.StorageType);
var inventory = await _videoService.GetStorageInventoryAsync(config.StorageType);
response.RequiresCutover = inventory.WebDavRecordCount > 0;
response.LegacyWebDavRecordCount = inventory.WebDavRecordCount;
return ApiResult.Success(response);
}
if (dto.StorageType == StorageType.Local)
{
var config = _commonService.GetConfig();
if (config == null) return ApiResult.Fail("系统配置不存在");
config.StorageType = StorageType.Local;
if (!await _commonService.UpdateConfig(config))
return ApiResult.Fail("存储模式保存失败");
await _quartzJobService.InitOrReStartAllJobs(config.Cron.ToString());
var response = await _openListSettingsService.ToDtoAsync(config.StorageType);
var inventory = await _videoService.GetStorageInventoryAsync(config.StorageType);
response.RequiresCutover = inventory.WebDavRecordCount > 0;
response.LegacyWebDavRecordCount = inventory.WebDavRecordCount;
return ApiResult.Success(response);
}
return ApiResult.Fail("不支持的存储类型");
}
}
}