diff --git a/src/DFApp.Web/Background/LotteryResultJob.cs b/src/DFApp.Web/Background/LotteryResultJob.cs index 97c209e0..28874e81 100644 --- a/src/DFApp.Web/Background/LotteryResultJob.cs +++ b/src/DFApp.Web/Background/LotteryResultJob.cs @@ -152,7 +152,7 @@ private async Task StartWork(string lotteryType, string lotteryTypeEng, string c /// /// 逐条处理开奖结果,每条独立写入数据库,某条失败不影响其他条目 /// - private async Task ProcessResultsIndividually(List items) + private async Task ProcessResultsIndividually(List items) { int successCount = 0; int skipCount = 0; @@ -171,7 +171,7 @@ private async Task ProcessResultsIndividually(List continue; } - LotteryResult entity = _mapper.MapToEntityFromExternalResultItem(item); + LotteryResult entity = _mapper.MapToEntityFromResultItem(item); // 使用 InsertReturnIdAsync 获取自增 Id,InsertAsync 不会回填自增主键 entity.Id = await _lotteryResultRepository.InsertReturnIdAsync(entity); successCount++; @@ -181,7 +181,7 @@ private async Task ProcessResultsIndividually(List { var prizeEntities = item.Prizegrades.Select(pg => { - var prizeEntity = _mapper.MapToEntityFromExternalPrizegradesItem(pg); + var prizeEntity = _mapper.MapToEntityFromPrizegradesItem(pg); prizeEntity.LotteryResultId = entity.Id; return prizeEntity; }).ToList(); @@ -305,7 +305,7 @@ private async Task UpdatePrizegrades(string lotteryType, string lotteryTypeEng) var prizeEntities = resultItem.Prizegrades.Select(pg => { - var entity = _mapper.MapToEntityFromExternalPrizegradesItem(pg); + var entity = _mapper.MapToEntityFromPrizegradesItem(pg); entity.LotteryResultId = item.Id; return entity; }).ToList(); @@ -342,89 +342,126 @@ private async Task UpdatePrizegrades(string lotteryType, string lotteryTypeEng) private async Task GetLotteryResult(string dayStart, string dayEnd, int pageNo, string lotteryType) { - // 使用代理服务器获取数据 string proxyServerUrl = LotteryConst.GetLotteryProxyUrl(_configuration); string requestUrl = $"{proxyServerUrl}/api/proxy/lottery/findDrawNotice?name={lotteryType}&dayStart={dayStart}&dayEnd={dayEnd}&pageNo={pageNo}&pageSize=30&week=&systemType=PC"; _logger.LogInformation("开始通过代理获取彩票数据 - 彩票类型: {LotteryType}, 开始日期: {DayStart}, 结束日期: {DayEnd}, 页码: {PageNo}", lotteryType, dayStart, dayEnd, pageNo); - _logger.LogInformation("代理请求URL: {RequestUrl}", requestUrl); - try + const int maxRetries = 3; + const int retryDelaySeconds = 5; + + for (int attempt = 1; attempt <= maxRetries; attempt++) { - using var client = _httpClientFactory.CreateClient(); - // 设置超时时间 - client.Timeout = TimeSpan.FromSeconds(60); + try + { + using var client = _httpClientFactory.CreateClient(); + client.Timeout = TimeSpan.FromSeconds(60); - _logger.LogInformation("发送代理HTTP请求..."); + _logger.LogInformation("发送代理HTTP请求 (尝试 {Attempt}/{MaxRetries})...", attempt, maxRetries); - HttpResponseMessage message = await client.GetAsync(requestUrl); + using HttpResponseMessage message = await client.GetAsync(requestUrl); - _logger.LogInformation("代理HTTP响应状态码: {StatusCode} ({Status})", (int)message.StatusCode, message.StatusCode); + _logger.LogInformation("代理HTTP响应状态码: {StatusCode} ({Status})", (int)message.StatusCode, message.StatusCode); - message.EnsureSuccessStatusCode(); + string responseContent = await message.Content.ReadAsStringAsync(); - string responseContent = await message.Content.ReadAsStringAsync(); - _logger.LogInformation("代理响应内容长度: {Length} 字符", responseContent.Length); + if (!message.IsSuccessStatusCode) + { + _logger.LogError("代理请求失败 - 状态码: {StatusCode}, 响应内容: {Content}", + (int)message.StatusCode, + responseContent.Length > 500 ? responseContent.Substring(0, 500) : responseContent); - // 记录响应内容(仅前500字符,避免日志过长) - if (responseContent.Length > 500) - { - _logger.LogInformation("代理响应内容前500字符: {Content}...", responseContent.Substring(0, 500)); - } - else - { - _logger.LogInformation("代理响应内容: {Content}", responseContent); - } + if ((int)message.StatusCode == 502 || (int)message.StatusCode == 504) + { + if (attempt < maxRetries) + { + _logger.LogWarning("遇到网关错误 {StatusCode},等待 {Delay} 秒后重试 (尝试 {Attempt}/{MaxRetries})", + (int)message.StatusCode, retryDelaySeconds * attempt, attempt, maxRetries); + await Task.Delay(TimeSpan.FromSeconds(retryDelaySeconds * attempt)); + continue; + } - LotteryInputDto? dto = JsonSerializer.Deserialize(responseContent); + throw new HttpRequestException( + $"代理请求在 {maxRetries} 次尝试后仍然失败,最后状态码: {(int)message.StatusCode},响应: {(responseContent.Length > 200 ? responseContent.Substring(0, 200) : responseContent)}"); + } - if (dto == null) - { - _logger.LogWarning("反序列化代理响应失败,响应为null,创建空对象"); - dto = new LotteryInputDto(); - } - else - { - _logger.LogInformation("反序列化代理响应成功 - 总数据量: {Total}, 当前页: {PageNo}/{PageNum}, 每页大小: {PageSize}", dto.Total, dto.PageNo, dto.PageNum, dto.PageSize); + throw new HttpRequestException( + $"代理请求失败,状态码: {(int)message.StatusCode},响应: {(responseContent.Length > 200 ? responseContent.Substring(0, 200) : responseContent)}"); + } + + _logger.LogInformation("代理响应内容长度: {Length} 字符", responseContent.Length); + + if (responseContent.Length > 500) + { + _logger.LogInformation("代理响应内容前500字符: {Content}...", responseContent.Substring(0, 500)); + } + else + { + _logger.LogInformation("代理响应内容: {Content}", responseContent); + } + + LotteryInputDto? dto = JsonSerializer.Deserialize(responseContent); - if (dto.Result != null) + if (dto == null) + { + _logger.LogWarning("反序列化代理响应失败,响应为null,创建空对象"); + dto = new LotteryInputDto(); + } + else { - _logger.LogInformation("当前页数据条数: {Count}", dto.Result.Count); + _logger.LogInformation("反序列化代理响应成功 - 总数据量: {Total}, 当前页: {PageNo}/{PageNum}, 每页大小: {PageSize}", dto.Total, dto.PageNo, dto.PageNum, dto.PageSize); - // 记录第一条数据的详细信息 - if (dto.Result.Count > 0) + if (dto.Result != null) { - var firstResult = dto.Result[0]; - _logger.LogInformation("第一条数据 - 彩票类型: {Name}, 期号: {Code}, 开奖日期: {Date}, 红球: {Red}, 蓝球: {Blue}", firstResult.Name, firstResult.Code, firstResult.Date, firstResult.Red, firstResult.Blue); + _logger.LogInformation("当前页数据条数: {Count}", dto.Result.Count); + + if (dto.Result.Count > 0) + { + var firstResult = dto.Result[0]; + _logger.LogInformation("第一条数据 - 彩票类型: {Name}, 期号: {Code}, 开奖日期: {Date}, 红球: {Red}, 蓝球: {Blue}", firstResult.Name, firstResult.Code, firstResult.Date, firstResult.Red, firstResult.Blue); + } + } + else + { + _logger.LogWarning("代理响应中的Result字段为null"); } } - else + + return dto; + } + catch (HttpRequestException) + { + throw; + } + catch (JsonException ex) + { + _logger.LogError(ex, "代理JSON解析异常: {Message}", ex.Message); + throw; + } + catch (TaskCanceledException ex) + { + _logger.LogError(ex, "代理请求超时 (尝试 {Attempt}/{MaxRetries}): {Message}", attempt, maxRetries, ex.Message); + + if (attempt == maxRetries) { - _logger.LogWarning("代理响应中的Result字段为null"); + throw; } + + await Task.Delay(TimeSpan.FromSeconds(retryDelaySeconds * attempt)); } + catch (Exception ex) + { + _logger.LogError(ex, "代理未知异常 (尝试 {Attempt}/{MaxRetries}): {Message}", attempt, maxRetries, ex.Message); - return dto; - } - catch (HttpRequestException ex) - { - _logger.LogError(ex, "代理HTTP请求异常: {Message}", ex.Message); - throw; - } - catch (TaskCanceledException ex) - { - _logger.LogError(ex, "代理请求超时: {Message}", ex.Message); - throw; - } - catch (JsonException ex) - { - _logger.LogError(ex, "代理JSON解析异常: {Message}", ex.Message); - throw; - } - catch (Exception ex) - { - _logger.LogError(ex, "代理未知异常: {Message}", ex.Message); - throw; + if (attempt == maxRetries) + { + throw; + } + + await Task.Delay(TimeSpan.FromSeconds(retryDelaySeconds * attempt)); + } } + + throw new InvalidOperationException($"获取彩票数据失败,已重试 {maxRetries} 次"); } } diff --git a/src/DFApp.Web/DTOs/Lottery/LotteryInputDto.cs b/src/DFApp.Web/DTOs/Lottery/LotteryInputDto.cs index 0f2adc55..e660422c 100644 --- a/src/DFApp.Web/DTOs/Lottery/LotteryInputDto.cs +++ b/src/DFApp.Web/DTOs/Lottery/LotteryInputDto.cs @@ -1,50 +1,28 @@ using System.Collections.Generic; using System.Text.Json.Serialization; -using ResultItemDtoType = DFApp.Lottery.ResultItemDto; namespace DFApp.Web.DTOs.Lottery { - /// - /// 彩票数据输入 DTO,用于反序列化代理服务器返回的彩票开奖数据 - /// public class LotteryInputDto { - /// - /// 无参构造函数 - /// public LotteryInputDto() { - Result = new List(); + Result = new List(); } - /// - /// 数据总数 - /// [JsonPropertyName("total")] public int Total { get; set; } - /// - /// 当前页码 - /// [JsonPropertyName("pageNo")] public int PageNo { get; set; } - /// - /// 总页数 - /// [JsonPropertyName("pageNum")] public int PageNum { get; set; } - /// - /// 每页大小 - /// [JsonPropertyName("pageSize")] public int PageSize { get; set; } - /// - /// 开奖结果列表(使用旧命名空间类型,与 LotteryMapper 的 External 映射方法签名匹配) - /// [JsonPropertyName("result")] - public List Result { get; set; } + public List Result { get; set; } } } diff --git a/src/DFApp.Web/Services/Lottery/LotteryDataFetchService.cs b/src/DFApp.Web/Services/Lottery/LotteryDataFetchService.cs index e70f8041..9e6138f7 100644 --- a/src/DFApp.Web/Services/Lottery/LotteryDataFetchService.cs +++ b/src/DFApp.Web/Services/Lottery/LotteryDataFetchService.cs @@ -132,13 +132,13 @@ public async Task FetchLotteryData(LotteryDataFetch foreach (var item in dto.Result) { - var lotteryResult = _mapper.MapToEntityFromExternalResultItem(item); + var lotteryResult = _mapper.MapToEntityFromResultItem(item); // 使用映射器处理 Prizegrades if (item.Prizegrades != null && item.Prizegrades.Count > 0) { lotteryResult.Prizegrades = item.Prizegrades - .Select(p => _mapper.MapToEntityFromExternalPrizegradesItem(p)) + .Select(p => _mapper.MapToEntityFromPrizegradesItem(p)) .ToList(); }