2025-06-14 22:12:37 +08:00
|
|
|
|
|
|
|
|
|
|
using LMS.Common.Extensions;
|
|
|
|
|
|
using LMS.DAO;
|
|
|
|
|
|
using LMS.Repository.DB;
|
|
|
|
|
|
using Microsoft.AspNetCore.Mvc;
|
|
|
|
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
|
|
using Newtonsoft.Json;
|
|
|
|
|
|
using System.Net;
|
|
|
|
|
|
using System.Net.Sockets;
|
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
|
|
|
|
|
|
|
namespace LMS.Tools.MJPackage
|
|
|
|
|
|
{
|
|
|
|
|
|
public class TaskService(ITokenService tokenService, ILogger<TaskService> logger, ApplicationDbContext dbContext, ITaskConcurrencyManager taskConcurrencyManager) : ITaskService
|
|
|
|
|
|
{
|
|
|
|
|
|
private readonly ITokenService _tokenService = tokenService;
|
|
|
|
|
|
private readonly ILogger<TaskService> _logger = logger;
|
|
|
|
|
|
private readonly ApplicationDbContext _dbContext = dbContext;
|
|
|
|
|
|
private readonly ITaskConcurrencyManager _taskConcurrencyManager = taskConcurrencyManager;
|
|
|
|
|
|
|
|
|
|
|
|
public async Task<Dictionary<string, object>?> FetchTaskAsync(MJApiTasks mJApiTasks)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
// 获取UseToken,先尝试 Token,再尝试 TokenId
|
|
|
|
|
|
var tokenConfig = await _tokenService.GetTokenAsync(mJApiTasks.Token);
|
|
|
|
|
|
string useToken = string.Empty;
|
|
|
|
|
|
|
|
|
|
|
|
if (tokenConfig == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
// Token 没找到 尝试用 TokenId 查找
|
|
|
|
|
|
MJApiTokens? mJApiTokens = await _tokenService.GetMJapiTokenByIdAsync(mJApiTasks.TokenId);
|
|
|
|
|
|
if (mJApiTokens == null)
|
|
|
|
|
|
{
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
useToken = mJApiTokens.UseToken;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
useToken = tokenConfig.UseToken;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(useToken))
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger.LogInformation($"Token is empty for task ID: {mJApiTasks.TaskId}");
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-21 22:22:54 +08:00
|
|
|
|
// 尝试原始API
|
|
|
|
|
|
var originResult = await TryOriginApiAsync(mJApiTasks.ThirdPartyTaskId);
|
|
|
|
|
|
var properties = new Dictionary<string, object>();
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(originResult))
|
|
|
|
|
|
{
|
|
|
|
|
|
originResult = await TryBackupApiAsync(mJApiTasks.ThirdPartyTaskId, useToken);
|
|
|
|
|
|
}
|
2025-06-14 22:12:37 +08:00
|
|
|
|
|
2025-06-21 22:22:54 +08:00
|
|
|
|
if (string.IsNullOrWhiteSpace(originResult))
|
2025-06-14 22:12:37 +08:00
|
|
|
|
{
|
|
|
|
|
|
// 没有找到数据
|
|
|
|
|
|
_logger.LogInformation($"备用API没有返回数据,TaskId: {mJApiTasks.TaskId}");
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
// 不为空 开始解析数据
|
2025-06-21 22:22:54 +08:00
|
|
|
|
properties = JsonConvert.DeserializeObject<Dictionary<string, object>>(originResult);
|
2025-06-14 22:12:37 +08:00
|
|
|
|
}
|
|
|
|
|
|
catch (JsonException ex)
|
|
|
|
|
|
{
|
2025-06-21 22:22:54 +08:00
|
|
|
|
_logger.LogError($"解析API返回数据失败: {ex.Message}");
|
2025-06-14 22:12:37 +08:00
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (properties == null)
|
|
|
|
|
|
{
|
2025-06-21 22:22:54 +08:00
|
|
|
|
_logger.LogInformation($"API返回数据为空,TaskId: {mJApiTasks.TaskId}");
|
2025-06-14 22:12:37 +08:00
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
return properties;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
// 记录异常日志
|
|
|
|
|
|
_logger.LogError($"Error fetching task: {ex.Message}");
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-06-21 22:22:54 +08:00
|
|
|
|
|
|
|
|
|
|
private async Task<string?> TryOriginApiAsync(string id)
|
|
|
|
|
|
{
|
|
|
|
|
|
string originUrl = $"https://mjapi.bzu.cn/mj/task/{id}/fetch";
|
|
|
|
|
|
|
|
|
|
|
|
// 判断 原始token 不存在 直接 返回空
|
|
|
|
|
|
string orginToken = await _tokenService.GetOriginToken();
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(orginToken))
|
|
|
|
|
|
{
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
using var client = new HttpClient();
|
|
|
|
|
|
client.DefaultRequestHeaders.Add("Authorization", orginToken);
|
|
|
|
|
|
client.Timeout = TimeSpan.FromSeconds(30);
|
|
|
|
|
|
|
|
|
|
|
|
var response = await client.GetAsync(originUrl);
|
|
|
|
|
|
var content = await response.Content.ReadAsStringAsync();
|
|
|
|
|
|
// 判断是不是返回空
|
|
|
|
|
|
if ((int)response.StatusCode == 204 || string.IsNullOrWhiteSpace(content))
|
|
|
|
|
|
{
|
|
|
|
|
|
return string.Empty;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!response.IsSuccessStatusCode)
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger.LogWarning($"源API调用返回错误状态码,TaskId: {id}, StatusCode: {response.StatusCode}");
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
return content;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger.LogWarning(ex, "原始API调用失败,TaskId: {TaskId},准备尝试备用API", id);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-06-14 22:12:37 +08:00
|
|
|
|
private async Task<string?> TryBackupApiAsync(string id, string useToken)
|
|
|
|
|
|
{
|
2025-06-16 20:12:35 +08:00
|
|
|
|
string mjAPIBasicUrl = await _tokenService.GetMJAPIBasicUrl();
|
|
|
|
|
|
string backupUrl = $"{mjAPIBasicUrl}/mj/task/{id}/fetch";
|
2025-06-14 22:12:37 +08:00
|
|
|
|
const int maxRetries = 3;
|
|
|
|
|
|
const int baseDelayMs = 1000;
|
|
|
|
|
|
|
|
|
|
|
|
using var client = new HttpClient();
|
|
|
|
|
|
client.DefaultRequestHeaders.Add("Authorization", "sk-" + useToken);
|
|
|
|
|
|
client.Timeout = TimeSpan.FromSeconds(30);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (int attempt = 1; attempt <= maxRetries; attempt++)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var response = await client.GetAsync(backupUrl);
|
|
|
|
|
|
var content = await response.Content.ReadAsStringAsync();
|
|
|
|
|
|
// 判断请求是不是报错
|
|
|
|
|
|
if (!response.IsSuccessStatusCode)
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger.LogWarning("备用API调用返回错误状态码,TaskId: {TaskId}, Attempt: {Attempt}, StatusCode: {StatusCode}",
|
|
|
|
|
|
id, attempt, response.StatusCode);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
return content;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex) when (IsRetriableException(ex))
|
|
|
|
|
|
{
|
|
|
|
|
|
if (attempt < maxRetries)
|
|
|
|
|
|
{
|
|
|
|
|
|
var delay = baseDelayMs * (int)Math.Pow(2, attempt - 1);
|
|
|
|
|
|
_logger.LogWarning(ex, "备用API调用失败,TaskId: {TaskId}, Attempt: {Attempt}, 将在{Delay}ms后重试",
|
|
|
|
|
|
id, attempt, delay);
|
|
|
|
|
|
await Task.Delay(delay);
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger.LogError(ex, "备用API调用最终失败,TaskId: {TaskId}, MaxAttempts: {MaxAttempts}",
|
|
|
|
|
|
id, maxRetries);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
_logger.LogError(ex, "备用API调用发生不可重试异常,TaskId: {TaskId}, Attempt: {Attempt}",
|
|
|
|
|
|
id, attempt);
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static bool IsRetriableException(Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
return ex is HttpRequestException ||
|
|
|
|
|
|
ex is TaskCanceledException ||
|
|
|
|
|
|
ex is SocketException;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|