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
+156 -7
View File
@@ -4,6 +4,7 @@ using dy.net.service;
using dy.net.utils;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Serilog;
namespace dy.net.Controllers
{
@@ -14,11 +15,19 @@ namespace dy.net.Controllers
{
private readonly DouyinFollowService _douyinFollowService;
private readonly DouyinQuartzJobService _douyinQuartzJobService;
private readonly DouyinUserLookupService _douyinUserLookupService;
private readonly DouyinLiveStatusService _douyinLiveStatusService;
public FollowController(DouyinFollowService douyinFollowService, DouyinQuartzJobService douyinQuartzJobService)
public FollowController(
DouyinFollowService douyinFollowService,
DouyinQuartzJobService douyinQuartzJobService,
DouyinUserLookupService douyinUserLookupService,
DouyinLiveStatusService douyinLiveStatusService)
{
this._douyinFollowService = douyinFollowService;
_douyinQuartzJobService = douyinQuartzJobService;
_douyinUserLookupService = douyinUserLookupService;
_douyinLiveStatusService = douyinLiveStatusService;
}
@@ -52,20 +61,107 @@ namespace dy.net.Controllers
[HttpGet("sync")]
public async Task<IActionResult> SyncFollowList()
{
//后台异步
_douyinQuartzJobService.StartFollowJobOnceAsync();
await Task.Delay(1000);
return ApiResult.Success();
try
{
var started = await _douyinQuartzJobService.StartFollowJobOnceAsync();
return started
? ApiResult.Success(new { message = "关注列表更新任务已启动" })
: ApiResult.Fail("关注列表更新任务启动失败,请查看错误日志。");
}
catch (Exception ex)
{
Log.Error(ex, "手动启动关注列表更新任务失败");
return ApiResult.Fail("关注列表更新任务启动失败:" + ex.GetBaseException().Message);
}
}
[HttpPost("add")]
public async Task<IActionResult> AddFollow(DouyinFollowed followed)
{
if (followed == null)
{
return ApiResult.Fail("新增信息不能为空");
}
if (string.IsNullOrWhiteSpace(followed.mySelfId))
{
return ApiResult.Fail("请先配置抖音授权信息,抖音授权配置里面填写你的uid");
}
var res = await _douyinFollowService.AddAsync(followed);
return ApiResult.SuccOrFail(res, "", res ? "" : "添加失败,或者已存在相同secuid和uid");
if (string.IsNullOrWhiteSpace(followed.SecUid))
{
return ApiResult.Fail("博主 SecUid 不能为空");
}
if (string.IsNullOrWhiteSpace(followed.UperId))
{
return ApiResult.Fail("博主 UID 不能为空");
}
if (string.IsNullOrWhiteSpace(followed.UperName))
{
return ApiResult.Fail("博主姓名不能为空");
}
followed.mySelfId = followed.mySelfId.Trim();
followed.SecUid = followed.SecUid.Trim();
followed.UperId = followed.UperId.Trim();
followed.UperName = followed.UperName.Trim();
followed.DouyinNo = followed.DouyinNo?.Trim();
followed.SavePath = followed.SavePath?.Trim();
followed.Signature = Limit(followed.Signature, 500);
followed.UperAvatar = Limit(followed.UperAvatar, 1000);
followed.Enterprise = Limit(followed.Enterprise, 200);
followed.FullSync = followed.OpenSync && followed.FullSync;
if (followed.mySelfId.Length > 200 || followed.SecUid.Length > 500 ||
followed.UperId.Length > 100 || followed.UperName.Length > 200)
{
return ApiResult.Fail("新增信息长度超出限制,请检查 UID、SecUid 和博主姓名");
}
if (!string.IsNullOrWhiteSpace(followed.DouyinNo) && followed.DouyinNo.Length > 100)
{
return ApiResult.Fail("抖音号长度不能超过 100 个字符");
}
if (!string.IsNullOrWhiteSpace(followed.SavePath))
{
if (followed.SavePath.Length > 20)
{
return ApiResult.Fail("保存文件夹名称最长 20 个字符");
}
if (!DouyinFileNameHelper.IsValidWithoutSpecialChars(followed.SavePath))
{
return ApiResult.Fail("保存文件夹名称只能包含字母、数字或中文");
}
}
var result = await _douyinFollowService.TryAddAsync(followed);
return result switch
{
AddFollowResult.Added => ApiResult.Success("新增非关注博主成功"),
AddFollowResult.AlreadyExists => ApiResult.Fail("该博主已存在,无需重复添加"),
_ => ApiResult.Fail("添加失败,请稍后重试")
};
}
/// <summary>
/// 使用展示在抖音资料页上的抖音号查找博主。结果需要用户确认后再调用 add。
/// </summary>
[HttpPost("resolve-by-douyin-no")]
public async Task<IActionResult> ResolveByDouyinNo(
DouyinFollowLookupRequest request,
CancellationToken cancellationToken)
{
try
{
var result = await _douyinUserLookupService.ResolveAsync(request, cancellationToken);
return ApiResult.Success(result);
}
catch (InvalidOperationException ex)
{
return ApiResult.Fail(ex.Message);
}
}
private static string Limit(string value, int maxLength)
{
value = value?.Trim();
return value != null && value.Length > maxLength ? value[..maxLength] : value;
}
/// <summary>
@@ -107,6 +203,59 @@ namespace dy.net.Controllers
return await OpenOrCloseSync(dto);
}
[HttpPost("live-monitor")]
public async Task<IActionResult> UpdateLiveMonitor(
FollowLiveMonitorUpdateDto dto,
CancellationToken cancellationToken)
{
try
{
return ApiResult.Success(await _douyinLiveStatusService.SetMonitorAsync(dto, cancellationToken));
}
catch (Exception ex) when (ex is InvalidOperationException or KeyNotFoundException)
{
return ApiResult.Fail(ex.Message);
}
}
[HttpPost("live-status/refresh")]
public async Task<IActionResult> RefreshLiveStatus(
FollowLiveStatusRefreshDto dto,
CancellationToken cancellationToken)
{
try
{
if (dto == null || string.IsNullOrWhiteSpace(dto.Id))
return ApiResult.Fail("博主记录不能为空");
return ApiResult.Success(await _douyinLiveStatusService.RefreshAsync(dto.Id, cancellationToken));
}
catch (Exception ex) when (ex is InvalidOperationException or KeyNotFoundException)
{
return ApiResult.Fail(ex.Message);
}
}
[HttpPost("live-status/query")]
public async Task<IActionResult> QueryLiveStatus(FollowLiveStatusQueryDto dto)
{
if (dto?.Ids == null || dto.Ids.Count == 0)
return ApiResult.Success(Array.Empty<FollowLiveStatusDto>());
return ApiResult.Success(await _douyinLiveStatusService.QueryAsync(dto.Ids));
}
[HttpPost("live-email")]
public async Task<IActionResult> UpdateLiveEmail(FollowLiveEmailUpdateDto dto)
{
try
{
return ApiResult.Success(await _douyinLiveStatusService.SetEmailNotificationAsync(dto));
}
catch (Exception ex) when (ex is InvalidOperationException or KeyNotFoundException)
{
return ApiResult.Fail(ex.Message);
}
}
/// <summary>
/// 删除关注对象
/// </summary>