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("文件不存在"); } } } }