Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 100 additions & 63 deletions src/DFApp.Web/Background/LotteryResultJob.cs
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ private async Task StartWork(string lotteryType, string lotteryTypeEng, string c
/// <summary>
/// 逐条处理开奖结果,每条独立写入数据库,某条失败不影响其他条目
/// </summary>
private async Task ProcessResultsIndividually(List<DFApp.Lottery.ResultItemDto> items)
private async Task ProcessResultsIndividually(List<DFApp.Web.DTOs.Lottery.ResultItemDto> items)
{
int successCount = 0;
int skipCount = 0;
Expand All @@ -171,7 +171,7 @@ private async Task ProcessResultsIndividually(List<DFApp.Lottery.ResultItemDto>
continue;
}

LotteryResult entity = _mapper.MapToEntityFromExternalResultItem(item);
LotteryResult entity = _mapper.MapToEntityFromResultItem(item);
// 使用 InsertReturnIdAsync 获取自增 Id,InsertAsync 不会回填自增主键
entity.Id = await _lotteryResultRepository.InsertReturnIdAsync(entity);
successCount++;
Expand All @@ -181,7 +181,7 @@ private async Task ProcessResultsIndividually(List<DFApp.Lottery.ResultItemDto>
{
var prizeEntities = item.Prizegrades.Select(pg =>
{
var prizeEntity = _mapper.MapToEntityFromExternalPrizegradesItem(pg);
var prizeEntity = _mapper.MapToEntityFromPrizegradesItem(pg);
prizeEntity.LotteryResultId = entity.Id;
return prizeEntity;
}).ToList();
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -342,89 +342,126 @@ private async Task UpdatePrizegrades(string lotteryType, string lotteryTypeEng)

private async Task<LotteryInputDto> 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<LotteryInputDto>(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<LotteryInputDto>(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} 次");
}
}
26 changes: 2 additions & 24 deletions src/DFApp.Web/DTOs/Lottery/LotteryInputDto.cs
Original file line number Diff line number Diff line change
@@ -1,50 +1,28 @@
using System.Collections.Generic;
using System.Text.Json.Serialization;
using ResultItemDtoType = DFApp.Lottery.ResultItemDto;

namespace DFApp.Web.DTOs.Lottery
{
/// <summary>
/// 彩票数据输入 DTO,用于反序列化代理服务器返回的彩票开奖数据
/// </summary>
public class LotteryInputDto
{
/// <summary>
/// 无参构造函数
/// </summary>
public LotteryInputDto()
{
Result = new List<ResultItemDtoType>();
Result = new List<ResultItemDto>();
}

/// <summary>
/// 数据总数
/// </summary>
[JsonPropertyName("total")]
public int Total { get; set; }

/// <summary>
/// 当前页码
/// </summary>
[JsonPropertyName("pageNo")]
public int PageNo { get; set; }

/// <summary>
/// 总页数
/// </summary>
[JsonPropertyName("pageNum")]
public int PageNum { get; set; }

/// <summary>
/// 每页大小
/// </summary>
[JsonPropertyName("pageSize")]
public int PageSize { get; set; }

/// <summary>
/// 开奖结果列表(使用旧命名空间类型,与 LotteryMapper 的 External 映射方法签名匹配)
/// </summary>
[JsonPropertyName("result")]
public List<ResultItemDtoType> Result { get; set; }
public List<ResultItemDto> Result { get; set; }
}
}
4 changes: 2 additions & 2 deletions src/DFApp.Web/Services/Lottery/LotteryDataFetchService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -132,13 +132,13 @@ public async Task<LotteryDataFetchResponseDto> 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();
}

Expand Down
Loading