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;
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;
public ConfigController(DouyinCookieService dyCookieService, DouyinCommonService commonService, DouyinQuartzJobService quartzJobService, DouyinFollowService douyinFollowService, DouyinCookieService douyinCookieService, DouyinHttpClientService httpClientService)
{
this.dyCookieService = dyCookieService;
this.commonService = commonService;
this.quartzJobService = quartzJobService;
this.douyinFollowService = douyinFollowService;
this.douyinCookieService = douyinCookieService;
this.httpClientService = httpClientService;
}
///
/// 导出配置
///
///
[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)
{
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);
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();
// 2. 路径权限校验
var (Success, Message) = ValidatePaths(dyUserCookies);
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)
{
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)
{
RemoveCookieLineString(dyUserCookies);
var checkCk = await httpClientService.CheckCookie(dyUserCookies);
if (!checkCk)
{
return ApiResult.Fail("Cookie无效或已过期,请按照文档提示重新获取有效Cookie,不要使用插件获取cookie");
}
dyUserCookies.StatusCode = 0;
dyUserCookies.StatusMsg = "正常";
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.UpdateCookieAsync(dyUserCookies);
if (result)
{
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;
}
}
///
/// 批量删除用户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()
{
var config = commonService.GetConfig();
if (config != null)
await quartzJobService.InitOrReStartAllJobs(config.Cron.ToString());
return ApiResult.Success();
}
///
/// 重置指定Cookie的同步状态,强制重新同步
///
/// Cookie ID
/// 重置类型:collect=收藏, favorite=喜欢, all=全部
///
[Authorize]
[HttpPost("ResetSyncStatus")]
public async Task ResetSyncStatus(string id, string type = "all")
{
try
{
var cookie = await dyCookieService.GetByIdAsync(id);
if (cookie == null)
{
return BadRequest(new { code = -1, error = "Cookie不存在" });
}
if (type == "collect" || type == "all")
{
cookie.CollHasSyncd = 0;
}
if (type == "favorite" || type == "all")
{
cookie.FavHasSyncd = 0;
}
if (type == "all")
{
cookie.UperSyncd = 0;
}
var result = await dyCookieService.UpdateAsync(cookie);
if (result)
{
Serilog.Log.Information($"重置Cookie[{cookie.UserName}]的同步状态,类型: {type}");
return Ok(new { code = 0, message = "同步状态已重置" });
}
else
{
return BadRequest(new { code = -1, error = "重置失败" });
}
}
catch (Exception ex)
{
Serilog.Log.Error(ex, "重置同步状态失败");
return StatusCode(500, new { code = -1, error = "重置失败:" + ex.Message });
}
}
///
/// 获取抖音扫码登录二维码
///
[HttpGet("qrcode")]
[Authorize]
public async Task GetQrCode()
{
try
{
var (token, qrBase64) = await _qrCodeService.GetQrCodeAsync();
return Ok(new { code = 0, data = new { token, qrBase64 } });
}
catch (Exception ex)
{
Serilog.Log.Error($"获取二维码失败: {ex.Message}");
return Ok(new { code = -1, error = "获取二维码失败:" + ex.Message });
}
}
///
/// 轮询扫码状态(前端每2秒调一次)
/// status: 1=待扫码, 2=已扫码待确认, 3=登录成功, -1=已过期
///
[HttpGet("qrstatus")]
[Authorize]
public async Task GetQrStatus([FromQuery] string token)
{
try
{
var (status, cookies, secUserId) = await _qrCodeService.CheckStatusAsync(token);
return Ok(new { code = 0, data = new { status, hasCookies = !string.IsNullOrEmpty(cookies), secUserId } });
}
catch (Exception ex)
{
Serilog.Log.Error($"查询扫码状态失败: {ex.Message}");
return Ok(new { code = -1, error = "查询失败:" + ex.Message });
}
}
///
/// 扫码成功后,将 Cookie 绑定到指定账号
///
[HttpPost("qrbind")]
[Authorize]
public async Task BindQrCode([FromQuery] string token, [FromQuery] string id)
{
try
{
var (cookies, secUserId) = _qrCodeService.GetSessionResult(token);
if (string.IsNullOrEmpty(cookies))
return Ok(new { code = -1, error = "扫码尚未完成或已过期" });
var account = await dyCookieService.GetByIdAsync(id);
if (account == null)
return Ok(new { code = -1, error = "账号不存在" });
account.Cookies = cookies;
if (!string.IsNullOrEmpty(secUserId))
account.SecUserId = secUserId;
account.Status = 1;
var result = await dyCookieService.UpdateAsync(account);
_qrCodeService.RemoveSession(token);
if (result)
{
await ReStartJob();
return Ok(new { code = 0, data = new { secUserId } });
}
return Ok(new { code = -1, error = "保存失败" });
}
catch (Exception ex)
{
Serilog.Log.Error($"绑定Cookie失败: {ex.Message}");
return Ok(new { code = -1, error = "绑定失败:" + 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("文件不存在");
}
}
}
}