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
+42 -1
View File
@@ -4,6 +4,7 @@ using dy.net.service;
using dy.net.utils;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using dy.net.storage;
namespace dy.net.Controllers
{
@@ -13,11 +14,13 @@ namespace dy.net.Controllers
{
private readonly DouyinVideoService douyinVideoService;
private readonly DouyinCommonService douyinCommonService;
private readonly MediaStorageRouter _storageRouter;
public VideoController(DouyinVideoService dyCollectVideoService, DouyinCommonService douyinCommonService)
public VideoController(DouyinVideoService dyCollectVideoService, DouyinCommonService douyinCommonService, MediaStorageRouter storageRouter)
{
this.douyinVideoService = dyCollectVideoService;
this.douyinCommonService = douyinCommonService;
_storageRouter = storageRouter;
}
/// <summary>
/// 分页查询收藏视频
@@ -88,6 +91,9 @@ namespace dy.net.Controllers
return ApiResult.Fail("视频信息不能为空");
}
if (video.StorageType.IsRemote())
return await PlayRemoteVideoAsync(video);
// 1. 获取完整物理路径并校验
string videoFullPath = video.VideoSavePath;
if (string.IsNullOrWhiteSpace(videoFullPath))
@@ -153,6 +159,34 @@ namespace dy.net.Controllers
}
}
private async Task<IActionResult> PlayRemoteVideoAsync(DouyinVideo video)
{
if (string.IsNullOrWhiteSpace(video.VideoSavePath)) return ApiResult.Fail("远端视频路径不能为空");
long? from = null;
long? to = null;
var range = Request.Headers.Range.ToString();
if (!string.IsNullOrWhiteSpace(range) && range.StartsWith("bytes=", StringComparison.OrdinalIgnoreCase))
{
var parts = range[6..].Split('-', 2);
if (long.TryParse(parts[0], out var parsedFrom)) from = parsedFrom;
if (parts.Length > 1 && long.TryParse(parts[1], out var parsedTo)) to = parsedTo;
}
await using var remote = await _storageRouter.Resolve(video.StorageType)
.OpenReadAsync(video.VideoSavePath, from, to, HttpContext.RequestAborted);
Response.StatusCode = remote.StatusCode;
Response.ContentType = remote.ContentType ?? GetContentType(video.VideoSavePath);
Response.Headers.AcceptRanges = "bytes";
if (remote.ContentLength.HasValue) Response.ContentLength = remote.ContentLength.Value;
if (!string.IsNullOrWhiteSpace(remote.ContentRange)) Response.Headers.ContentRange = remote.ContentRange;
await remote.Stream.CopyToAsync(Response.Body, 81920, HttpContext.RequestAborted);
await Response.Body.FlushAsync(HttpContext.RequestAborted);
return new EmptyResult();
}
/// <summary>
/// 处理分片请求(Range),异步安全处理流
/// </summary>
@@ -275,6 +309,7 @@ namespace dy.net.Controllers
/// </summary>
/// <param name="dto"></param>
/// <returns></returns>
[Authorize]
[HttpPost("redown")]
public async Task<IActionResult> ReDownload(ReDownViedoDto dto)
{
@@ -301,6 +336,7 @@ namespace dy.net.Controllers
/// </summary>
/// <param name="dto"></param>
/// <returns></returns>
[Authorize]
[HttpPost("vdelete/batch")]
public async Task<IActionResult> BathRealDelete(ReDownViedoDto dto)
{
@@ -321,6 +357,7 @@ namespace dy.net.Controllers
/// </summary>
/// <param name="vid"></param>
/// <returns></returns>
[Authorize]
[HttpGet("vdelete/{vid}")]
public async Task<IActionResult> DeleteVideo([FromRoute] string vid)
{
@@ -346,6 +383,7 @@ namespace dy.net.Controllers
/// 查询已删除视频列表
/// </summary>
/// <returns></returns>
[Authorize]
[HttpGet("vdelete/get")]
public async Task<IActionResult> GetDeleteVideo()
{
@@ -356,6 +394,7 @@ namespace dy.net.Controllers
/// </summary>
/// <param name="uperUid"></param>
/// <returns></returns>
[Authorize]
[HttpGet("vdelete/byauthor/{uperUid}")]
public async Task<IActionResult> DeleteByAuthor([FromRoute] string uperUid)
{
@@ -396,6 +435,7 @@ namespace dy.net.Controllers
/// 删除无效视频记录
/// </summary>
/// <returns></returns>
[Authorize]
[HttpGet("removeInvalid")]
public async Task<IActionResult> RemoveInvalidVideo()
{
@@ -463,6 +503,7 @@ namespace dy.net.Controllers
}
}
[Authorize]
[HttpGet("/Move")]
public async Task<IActionResult> Move()
{