using ClockSnowFlake; using dy.net.dto; using dy.net.model; using dy.net.service; using dy.net.utils; using dy.sync.lib; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Newtonsoft.Json; using Quartz.Util; using System.Text; using System.Text.Json; using System.Xml.Linq; using static Dm.net.buffer.ByteArrayBuffer; 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 string[] _allowedAudioExtensions = { ".mp3", ".wav" }; // 定义允许的音频 MIME 类型(增强验证) private readonly string[] _allowedAudioMimeTypes = { "audio/mpeg", "audio/wav" }; // 最大文件大小:20MB(可根据需求调整) private const long _maxFileSize = 20 * 1024 * 1024; public ConfigController(DouyinCookieService dyCookieService, DouyinCommonService commonService, DouyinQuartzJobService quartzJobService, DouyinFollowService douyinFollowService, DouyinCookieService douyinCookieService) { this.dyCookieService = dyCookieService; this.commonService = commonService; this.quartzJobService = quartzJobService; this.douyinFollowService = douyinFollowService; this.douyinCookieService = douyinCookieService; } /// /// 导出配置 /// /// [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数据为空"); var follows = dto.follows; if (follows != null && follows.Count > 0) { var add = await douyinFollowService.AddHandFollows(follows); if (add) Serilog.Log.Debug("关注列表导入成功"); } var conf = dto.conf; if (conf != null) { var update = await commonService.UpdateConfig(conf); if (update) Serilog.Log.Debug("系统配置导入成功"); } var cookies = dto.cookies; if (cookies != null && cookies.Count > 0) { var importCookies = await douyinCookieService.ImportCookies(cookies); if (importCookies) { Serilog.Log.Debug("抖音Cookie配置导入成功"); } } return ApiResult.Success(); } /// /// 分页查询 /// /// 分页结果 [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("add")] public async Task AddAsync([FromBody] DouyinCookie dyUserCookies) { var result = await dyCookieService.Add(dyUserCookies); if (result) { ReStartJob(); return ApiResult.Success(); } return ApiResult.Fail("添加失败"); } /// /// 非docker初始化 /// [HttpPost("deskinit")] [AllowAnonymous] public async Task DeskInitAsync([FromBody] DouyinCookie dyUserCookies) { dyUserCookies.Id = IdGener.GetLong().ToString(); if(string.IsNullOrWhiteSpace(dyUserCookies.SavePath)) { return ApiResult.Fail("收藏存储路径不能为空"); } if (!DouyinFileUtils.HasDirectoryReadWritePermission(dyUserCookies.SavePath)) { return ApiResult.Fail($"请在飞牛应用设置里面将{dyUserCookies.SavePath}添加读写权限"); } if (!string.IsNullOrWhiteSpace(dyUserCookies.FavSavePath) && !DouyinFileUtils.HasDirectoryReadWritePermission(dyUserCookies.FavSavePath)) { return ApiResult.Fail($"请在飞牛应用设置里面将{dyUserCookies.FavSavePath}添加读写权限"); } if (!string.IsNullOrWhiteSpace(dyUserCookies.UpSavePath) && !DouyinFileUtils.HasDirectoryReadWritePermission(dyUserCookies.UpSavePath)) { return ApiResult.Fail($"请在飞牛应用设置里面将{dyUserCookies.UpSavePath}添加读写权限"); } if (!string.IsNullOrWhiteSpace(dyUserCookies.ImgSavePath) && !DouyinFileUtils.HasDirectoryReadWritePermission(dyUserCookies.ImgSavePath)) { return ApiResult.Fail($"请在飞牛应用设置里面将{dyUserCookies.ImgSavePath}添加读写权限"); } var result = await dyCookieService.Add(dyUserCookies); if (result) { return ApiResult.Success(); } return ApiResult.Fail("添加失败"); } /// /// /// /// [HttpGet("appport")] [AllowAnonymous] public async Task GetAppPort() { //return ApiResult.Success(10105); return ApiResult.Success(string.IsNullOrWhiteSpace(Appsettings.Get("appPort"))?10101: Convert.ToInt32(Appsettings.Get("appPort"))); } /// /// 快速开启或停止 /// [HttpPost("switch")] public async Task SwitchAsync([FromBody] DouyinCookieStopDto dto) { var result = await dyCookieService.Switch(dto); if (result) { ReStartJob(); return ApiResult.Success(); } return ApiResult.Fail("添加失败"); } /// /// 更新用户Cookie /// [HttpPost("update")] public async Task UpdateAsync([FromBody] DouyinCookie dyUserCookies) { if (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.UpdateAsync(dyUserCookies); if (result) { ReStartJob(); return ApiResult.Success(); } return ApiResult.Fail("更新失败"); } } /// /// 批量删除用户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 data = await commonService.UpdateConfig(config); if (data) { if (config.OnlySyncNew) { var d = await douyinCookieService.SetOnlySyncNew(); if (d) Serilog.Log.Debug("仅同步新视频配置已生效,后续所有类型的视频同步将只会读取最近一页约20条数据"); } ReStartJob(); } return ApiResult.Success(data); } /// /// /// /// [HttpGet("ExecuteJobNow")] //[Authorize] public async Task ExecuteJobNow() { var config = commonService.GetConfig(); if (config != null) await quartzJobService.InitOrReStartAllJobs(config.Cron.ToString()); return ApiResult.Success(); } private void ReStartJob() { var config = commonService.GetConfig(); if (config != null) quartzJobService.InitOrReStartAllJobs(config.Cron.ToString()); //避免前端等待 } /// /// 镜像标签 /// /// [HttpGet("mytag")] public async Task GetMyTag() { return ApiResult.Success(Appsettings.Get("tagName")); } [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 { "beta_"+Appsettings.Get("fnVersion") }); } else { return await GetDockerTagVersions(); } } } private static async Task GetDockerTagVersions() { var data = await DouyinHttpHelper.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("请求失败"); } } /// /// 上传音频文件接口 /// /// 要上传的音频文件 /// 上传结果 [HttpPost("UploadAudio")] public IActionResult UploadAudio(IFormFile file) { // 1. 验证文件是否为空 if (file == null || file.Length == 0) { return BadRequest(new { success = false, message = "请选择要上传的音频文件" }); } try { // 2. 验证文件大小 if (file.Length > _maxFileSize) { return BadRequest(new { success = false, message = $"文件大小超过限制(最大允许 {_maxFileSize / 1024 / 1024}MB)" }); } // 3. 获取文件扩展名并验证 var fileExtension = Path.GetExtension(file.FileName).ToLower(); if (!_allowedAudioExtensions.Contains(fileExtension)) { return BadRequest(new { success = false, message = $"不支持的音频格式,仅允许:{string.Join(", ", _allowedAudioExtensions)}" }); } // 4. 验证 MIME 类型(可选但推荐,防止扩展名伪造) var contentType = file.ContentType.ToLower(); if (!_allowedAudioMimeTypes.Contains(contentType)) { return BadRequest(new { success = false, message = "文件类型验证失败,请上传合法的音频文件" }); } //先删除 var uploadMp3 = Directory.GetFiles(Path.Combine(AppContext.BaseDirectory, "mp3")) .Where(filePath => Path.GetFileNameWithoutExtension(filePath) != "silent_10") .FirstOrDefault(); if (!string.IsNullOrWhiteSpace(uploadMp3) && System.IO.File.Exists(uploadMp3)) { System.IO.File.Delete(uploadMp3); } // 5. 生成唯一文件名(避免重复) var uniqueFileName = $"dysync_default{fileExtension}"; // 6. 定义文件保存路径(建议配置在 appsettings.json 中,这里简化处理) var uploadPath = Path.Combine(AppContext.BaseDirectory, "mp3"); // 确保目录存在 if (!Directory.Exists(uploadPath)) { Directory.CreateDirectory(uploadPath); } // 7. 保存文件 var filePath = Path.Combine(uploadPath, uniqueFileName); if (System.IO.File.Exists(filePath)) { System.IO.File.Delete(filePath); } using (var stream = new FileStream(filePath, FileMode.Create)) { file.CopyTo(stream); } // 8. 返回成功结果(可根据需求返回文件路径/URL 等) return ApiResult.Success(new { fileName = uniqueFileName, filePath }); } catch (Exception ex) { // 捕获异常并返回错误信息 return ApiResult.Fail(ex.Message); } } [AllowAnonymous] [HttpGet("defaudio")] public async Task GetDefaultAudioUrl() { var uploadMp3 = Directory.GetFiles(Path.Combine(AppContext.BaseDirectory, "mp3")) .Where(filePath => Path.GetFileNameWithoutExtension(filePath) != "silent_10") .FirstOrDefault(); if (!string.IsNullOrWhiteSpace(uploadMp3) && System.IO.File.Exists(uploadMp3)) { return File(System.IO.File.ReadAllBytes(uploadMp3), "application/octet-stream", Path.GetFileName(uploadMp3)); } return ApiResult.Fail("未上传音频"); } } }