using ClockSnowFlake;
using dy.net.extension;
using dy.net.model.dto;
using dy.net.model.entity;
using dy.net.service;
using dy.net.utils;
using jzc.http.lib;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using System.Net;
using System.Text.RegularExpressions;
using dy.net.storage;
namespace dy.net.Controllers
{
[Route("api/[controller]")]
[ApiController]
[Authorize]
public class ConfigController : ControllerBase
{
private readonly DouyinCookieService dyCookieService;
private readonly DouyinCommonService commonService;
private readonly DouyinQuartzJobService quartzJobService;
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, WebDavSettingsService webDavSettingsService, VideoTaskService videoTaskService)
{
this.dyCookieService = dyCookieService;
this.commonService = commonService;
this.quartzJobService = quartzJobService;
this.douyinFollowService = douyinFollowService;
this.douyinCookieService = douyinCookieService;
this.httpClientService = httpClientService;
_webDavSettingsService = webDavSettingsService;
_videoTaskService = videoTaskService;
}
///
/// 导出配置
///
///
[HttpGet("exportConf")]
public async Task ExportConf()
{
// 1. 组装导出数据
var dto = new AppConfigImportDto()
{
follows = await douyinFollowService.GetHandFollows(),
conf = commonService.GetConfig(),
cookies = await douyinCookieService.GetAllAsync()
};
return ApiResult.Success(dto);
}
[HttpPost("importConf")]
public async Task ImportConf(AppConfigImportDto dto)
{
if (dto == null)
return ApiResult.Fail("json数据为空");
await HandleFollowsImport(dto.follows);
await HandleConfigImport(dto.conf);
await HandleCookiesImport(dto.cookies);
return ApiResult.Success();
}
private async Task HandleFollowsImport(List follows)
{
if (follows?.Count > 0)
{
var added = await douyinFollowService.AddHandFollows(follows);
if (added)
Serilog.Log.Debug("关注列表导入成功");
}
}
private async Task HandleConfigImport(AppConfig conf)
{
if (conf == null) return;
if (conf.BatchCount > 30)
{
Serilog.Log.Debug("对不起,为了项目能长久稳定运行,还是最大不要超过30吧。。。");
conf.BatchCount = 30;
}
var updated = await commonService.UpdateConfig(conf);
if (updated)
Serilog.Log.Debug("系统配置导入成功");
}
private async Task HandleCookiesImport(List cookies)
{
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配置导入成功");
}
}
///
/// 分页查询
///
/// 分页结果
[HttpPost("paged")]
public async Task GetPagedAsync(
PageRequestDto dto)
{
var (list, totalCount) = await dyCookieService.GetPagedAsync(dto.PageIndex, dto.PageSize);
return ApiResult.Success(new
{
data = list,
total = totalCount,
pageIndex = dto.PageIndex,
pageSize = dto.PageSize
});
}
///
/// 查询所有用户Cookie
///
///
[HttpGet("list")]
public async Task GetAllList()
{
var follows = await douyinFollowService.GetGroupByCookieAsync();
return ApiResult.Success(follows);
}
///
/// 是否已经初始化了
///
///
[HttpGet("isInit")]
[AllowAnonymous]
public async Task IsInit()
{
var init = await dyCookieService.IsInit();
return ApiResult.Success(init);
}
///
/// 配合工具自动设置cookie
///
///
///
[HttpPost("FastResetCookie")]
[AllowAnonymous]
public async Task FastResetCookie([FromBody] DouyinCookieResetDto dto)
{
var cookieValid = await httpClientService.CheckCookie(new DouyinCookie { Cookies = dto.cookie });
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设置失败");
}
///
/// 配合工具自动设置cookie,获取当前设置的所有Cookie
///
///
[AllowAnonymous]
[HttpGet("Cookies")]
public async Task GetAllCookies()
{
var cookies = await dyCookieService.GetAllAsync();
if (cookies != null)
return ApiResult.Success(cookies.Select(x => new { id= x.Id,name= x.UserName, status=x.StatusMsg }));
return ApiResult.Success();
}
///
/// 非docker初始化
///
[HttpPost("deskinit")]
[AllowAnonymous]
public async Task DeskInitAsync([FromBody] DouyinCookie dyUserCookies)
{
// 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, storageType);
if (!Success)
return ApiResult.Fail(Message);
RemoveCookieLineString(dyUserCookies);
// 3. Cookie 有效性校验
var cookieValid = await httpClientService.CheckCookie(dyUserCookies);
if (!cookieValid)
return ApiResult.Fail("Cookie无效或已过期,请按照文档提示重新获取有效Cookie,不要使用插件获取cookie");
dyUserCookies.StatusCode = 0;
dyUserCookies.StatusMsg = "正常";
// 4. 保存到数据库
var saved = await dyCookieService.Add(dyUserCookies);
return saved ? ApiResult.Success() : ApiResult.Fail("添加失败");
}
private static (bool Success, string Message) ValidatePaths(DouyinCookie cookie, StorageType storageType)
{
if (storageType.IsRemote())
{
var remotePaths = new Dictionary
{
{ "收藏 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
{
{ "收藏存储路径", cookie.SavePath },
{ "喜欢视频存储路径", cookie.FavSavePath },
{ "上传视频存储路径", cookie.UpSavePath },
// { "图片存储路径", cookie.ImgSavePath } // 可随时启用
};
foreach (var (label, path) in pathsToCheck)
{
if (!string.IsNullOrWhiteSpace(path))
{
if (!DouyinFileUtils.HasDirectoryReadWritePermission(path))
{
return (false, $"请在飞牛应用设置里面将 {path} 添加读写权限({label})");
}
}
}
if (string.IsNullOrWhiteSpace(cookie.SavePath))
{
return (false, "收藏存储路径不能为空");
}
return (true, string.Empty);
}
///
/// 快速开启或停止
///
[HttpPost("switch")]
public async Task SwitchAsync([FromBody] DouyinCookieSwitchDto dto)
{
var result = await dyCookieService.Switch(dto);
if (result)
{
ReStartJob();
return ApiResult.Success();
}
return ApiResult.Fail("添加失败");
}
///
/// 更新用户Cookie
///
[HttpPost("update")]
public async Task 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);
if (!checkCk)
{
return ApiResult.Fail("Cookie无效或已过期,请按照文档提示重新获取有效Cookie,不要使用插件获取cookie");
}
dyUserCookies.StatusCode = 0;
dyUserCookies.StatusMsg = "正常";
if (isNewCookie)
{
if (string.IsNullOrWhiteSpace(dyUserCookies.Id) || dyUserCookies.Id == "0")
dyUserCookies.Id = IdGener.GetLong().ToString();
var result = await dyCookieService.Add(dyUserCookies);
if (result)
{
ReStartJob();
return ApiResult.Success();
}
return ApiResult.Fail("添加失败");
}
else
{
var result = await dyCookieService.UpdateCookieAsync(dyUserCookies);
if (result)
{
await _videoTaskService.ResetSourceHealthAsync(dyUserCookies.Id);
ReStartJob();
return ApiResult.Success();
}
return ApiResult.Fail("更新失败");
}
}
private static void RemoveCookieLineString(DouyinCookie dyUserCookies)
{
if (dyUserCookies != null && !string.IsNullOrWhiteSpace(dyUserCookies.Cookies))
{
var s = dyUserCookies.Cookies.Replace("\\r\\n", "\r\n");
var ss = s.Trim(new char[] { '\r', '\n' });
dyUserCookies.Cookies = ss;
}
}
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;
}
///
/// 批量删除用户Cookie
///
[HttpGet("delete")]
public async Task DeleteAsync(string id)
{
var count = await dyCookieService.DeleteByIdsAsync(new List { id });
if (count > 0)
{
ReStartJob();
}
return ApiResult.Success(count);
}
[HttpGet("GetConfig")]
public IActionResult GetConfig()
{
var data = commonService.GetConfig();
return ApiResult.Success(data);
}
[HttpPost("UpdateConfig")]
public async Task UpdateConfig(AppConfig config)
{
var update = await commonService.UpdateConfig(config);
if (update)
{
if (config.OnlySyncNew)
{
Serilog.Log.Debug("仅同步新视频配置已生效,后续所有类型的视频同步将只会读取最近一页约20条数据");
}
ReStartJob();
}
return ApiResult.Success(update);
}
///
///
///
///
[HttpGet("ExecuteJobNow")]
//[Authorize]
public async Task ExecuteJobNow()
{
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);
}
}
private void ReStartJob()
{
var config = commonService.GetConfig();
if (config != null)
quartzJobService.InitOrReStartAllJobs(config.Cron.ToString());
//避免前端等待
}
///
/// 镜像标签
///
///
[HttpGet("mytag")]
public async Task GetMyTag()
{
var deploy = Appsettings.Get("deploy");
var tag = Appsettings.Get("tagName");
tag = deploy == "fn" ? "fn_" + Appsettings.Get("fnVersion") : tag;
deploy = deploy == "fn" ? "fnos" : "docker";
return ApiResult.Success(new { tag, deploy });
}
[AllowAnonymous]
[HttpGet("checktag")]
public async Task CheckTag()
{
var deploy = Appsettings.Get("deploy");
if (string.IsNullOrWhiteSpace(deploy))
{
return await GetDockerTagVersions();
}
else
{
if (deploy == "fn")//飞牛
{
return ApiResult.Success(new List { "fn_" + Appsettings.Get("fnVersion") });
}
else
{
return await GetDockerTagVersions();
}
}
}
private static async Task GetDockerTagVersions()
{
var data = await JianZhiChuHttpHelper.GetTenImage(Appsettings.Get("tagName"));
if (data.IsSuccessStatusCode)
{
var content = await data.Content.ReadAsStringAsync();
var tagData = JsonConvert.DeserializeObject>>(content);
if (tagData != null && tagData.Data != null && tagData.Data.Count > 0)
return ApiResult.Success(tagData.Data);
return ApiResult.Fail();
}
else
{
return ApiResult.Fail("请求失败");
}
}
///
/// 查询mp3目录下有没有音频文件
///
///
[HttpGet("mp3List")]
public async Task GetExistMps()
{
var path = Path.Combine(AppContext.BaseDirectory, "mp3");
if (!string.IsNullOrWhiteSpace(ServiceExtension.FnDataFolder))
{
path = ServiceExtension.FnDataFolder;
}
if (Directory.Exists(path))
{
var allowedExtensions = new HashSet { ".mp3", ".wav" };
var customMusics = Directory.GetFiles(path)
.Where(filePath =>
allowedExtensions.Contains(Path.GetExtension(filePath).ToLowerInvariant()) &&
Path.GetFileNameWithoutExtension(filePath) != "silent_10")
.ToList();
var fileNames = customMusics.Select(f => new { filename = Path.GetFileName(f) }).Where(x => x.filename != "silent_10.mp3").ToList();
return ApiResult.Success(fileNames);
}
else
{
return ApiResult.Fail("没有找到默认音频文件");
}
}
///
/// 播放音频流
///
///
///
[AllowAnonymous]
[HttpGet("getmp3")]
public async Task GetMp3([FromQuery] string name)
{
var path = Path.Combine(AppContext.BaseDirectory, "mp3", name);
if (!string.IsNullOrWhiteSpace(ServiceExtension.FnDataFolder))
{
path = Path.Combine(ServiceExtension.FnDataFolder, name);
}
if (System.IO.File.Exists(path))
{
//返回mp3文件流
var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read);
return new FileStreamResult(fileStream, "audio/mpeg")
{
FileDownloadName = name
};
}
else
{
return ApiResult.Fail("文件不存在");
}
}
}
}