Skip to content
Merged

Dev #94

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
18 changes: 8 additions & 10 deletions DFApp.LotteryProxy/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
# 使用官方.NET 8运行时作为基础镜像
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base
# 使用官方.NET 10运行时作为基础镜像
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base
WORKDIR /app
EXPOSE 5000

# 使用.NET 9 SDK作为构建镜像
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
# 使用.NET 10 SDK作为构建镜像
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src

# 复制项目文件
Expand All @@ -26,16 +25,15 @@ RUN dotnet publish "DFApp.LotteryProxy.csproj" -c Release -o /app/publish /p:Use
FROM base AS final
WORKDIR /app

# 安装 curl(用于健康检查)
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*

# 创建非root用户
RUN adduser --disabled-password --gecos '' appuser && chown -R appuser /app
RUN useradd -m -s /bin/bash appuser && chown -R appuser /app
USER appuser

# 复制发布的应用
COPY --from=publish /app/publish .

# 设置健康检查
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost:5000/api/health || exit 1

# 启动应用
ENTRYPOINT ["dotnet", "DFApp.LotteryProxy.dll"]
55 changes: 28 additions & 27 deletions DFApp.LotteryProxy/Middleware/IpWhitelistMiddleware.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,16 @@ public IpWhitelistMiddleware(
public async Task InvokeAsync(HttpContext context)
{
var clientIp = GetClientIpAddress(context);

_logger.LogDebug("客户端IP: {ClientIP}", clientIp);

_logger.LogInformation("客户端IP: {ClientIP}", clientIp);
_logger.LogInformation("允许的IP列表: {AllowedIPs}", string.Join(", ", _proxySettings.AllowedIPs));
_logger.LogInformation("允许的IP数量: {Count}", _proxySettings.AllowedIPs?.Count ?? 0);

if (!IsIpAllowed(clientIp))
{
_logger.LogWarning("未授权的IP访问尝试: {ClientIP}", clientIp);
_logger.LogWarning("请求路径: {Path}", context.Request.Path);
_logger.LogWarning("请求方法: {Method}", context.Request.Method);
context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
await context.Response.WriteAsync("403 Forbidden: IP地址不在允许列表中");
return;
Expand All @@ -42,35 +46,25 @@ public async Task InvokeAsync(HttpContext context)

private string GetClientIpAddress(HttpContext context)
{
// 尝试从各种头部获取真实IP
var ip = context.Request.Headers["X-Forwarded-For"].FirstOrDefault();

if (!string.IsNullOrEmpty(ip))
{
// X-Forwarded-For可能包含多个IP,取第一个
var ips = ip.Split(',');
if (ips.Length > 0)
{
ip = ips[0].Trim();
}
}

if (string.IsNullOrEmpty(ip))
{
ip = context.Request.Headers["X-Real-IP"].FirstOrDefault();
}

if (string.IsNullOrEmpty(ip))
{
ip = context.Connection.RemoteIpAddress?.ToString();
}
// 仅使用 RemoteIpAddress,因为它来自TCP连接,无法被客户端伪造
// 不使用 X-Forwarded-For 和 X-Real-IP 头部,因为它们可以被伪造
var ip = context.Connection.RemoteIpAddress?.ToString();
_logger.LogInformation("RemoteIpAddress: {IP}", ip);

// 如果是IPv6回环地址,转换为IPv4
if (ip == "::1")
{
ip = "127.0.0.1";
_logger.LogInformation("IPv6回环地址转换为IPv4: {IP}", ip);
}
// 如果是IPv4映射的IPv6地址(::ffff:x.x.x.x),提取IPv4部分
else if (ip != null && ip.StartsWith("::ffff:"))
{
ip = ip.Substring(7); // 移除 "::ffff:" 前缀
_logger.LogInformation("IPv6映射的IPv4地址转换为IPv4: {IP}", ip);
}

_logger.LogInformation("最终获取的客户端IP: {IP}", ip);
return ip ?? "unknown";
}

Expand All @@ -81,13 +75,20 @@ private bool IsIpAllowed(string? clientIp)
return false;
}

// 如果允许列表为空,则允许所有IP(开发环境
if (_proxySettings.AllowedIPs == null || _proxySettings.AllowedIPs.Count == 0)
// 默认允许本地访问(127.0.0.1 和 ::1
if (clientIp == "127.0.0.1" || clientIp == "::1")
{
_logger.LogDebug("IP白名单为空,允许所有IP访问");
_logger.LogInformation("本地IP访问,自动允许: {IP}", clientIp);
return true;
}

// 如果允许列表为空,则拒绝所有IP(生产环境)
if (_proxySettings.AllowedIPs == null || _proxySettings.AllowedIPs.Count == 0)
{
_logger.LogInformation("IP白名单为空,拒绝所有IP访问(除本地外)");
return false;
}

return _proxySettings.AllowedIPs.Contains(clientIp);
}
}
Expand Down
6 changes: 2 additions & 4 deletions DFApp.LotteryProxy/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
version: '3.8'

services:
lottery-proxy:
build: .
container_name: lottery-proxy
ports:
- "5000:5000"
- "45000:8080"
environment:
- ASPNETCORE_ENVIRONMENT=Production
- ProxySettings__AllowedIPs__0=
Expand All @@ -17,7 +15,7 @@ services:
- ./logs:/app/logs
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:5000/api/health"]
test: ["CMD", "curl", "-f", "http://localhost:8080/api/health"]
interval: 30s
timeout: 10s
retries: 3
Expand Down
55 changes: 27 additions & 28 deletions src/DFApp.Application/Background/LotteryResultTimer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public override async Task Execute(IJobExecutionContext context)
private async Task StartWork(string lotteryType, string lotteryTypeEng, string code)
{
Logger.LogInformation($"开始任务......{lotteryType} (英文类型: {lotteryTypeEng}, 起始代码: {code})");

try
{
// 检查是否已有数据
Expand All @@ -91,7 +91,7 @@ private async Task StartWork(string lotteryType, string lotteryTypeEng, string c
|| DateTime.Now.DayOfWeek == DayOfWeek.Thursday
|| DateTime.Now.DayOfWeek == DayOfWeek.Tuesday
|| lotteryType == LotteryConst.KL8;

Logger.LogInformation($"是否需要获取最新数据: {shouldFetchLatest} (当前星期: {DateTime.Now.DayOfWeek})");

if (shouldFetchLatest)
Expand All @@ -100,7 +100,7 @@ private async Task StartWork(string lotteryType, string lotteryTypeEng, string c
Logger.LogInformation($"检查今天 ({day}) 是否已有数据");
List<LotteryResult> result1 = await _resultReadOnly.GetListAsync(item => item.Date != null && item.Date.StartsWith(day));
Logger.LogInformation($"今天的数据条数: {result1?.Count ?? 0}");

if (result1 == null || result1.Count <= 0)
{
Logger.LogInformation("今天没有数据,开始获取最新数据");
Expand All @@ -110,13 +110,12 @@ private async Task StartWork(string lotteryType, string lotteryTypeEng, string c
{
Logger.LogInformation("开始更新奖级信息");
await UpdatePrizegrades(lotteryType, lotteryTypeEng);

Logger.LogInformation("获取最新一期开奖结果作为起始点");
LotteryResult lotteryResult = (await _resultReadOnly.GetQueryableAsync()).OrderByDescending(x => x.Code).First();
string dayStart = (lotteryResult.Date!.Split('('))[0];
dayStart = DateTime.Parse(dayStart).AddDays(1).ToString("yyyy-MM-dd");
Logger.LogInformation($"从 {dayStart} 开始获取最新数据");

await GetCurrentLotteryResult(dayStart, 0, lotteryTypeEng);
await uom.CompleteAsync();
Logger.LogInformation("最新数据获取完成并提交事务");
Expand Down Expand Up @@ -149,9 +148,9 @@ private async Task GetCurrentLotteryResult(string dayStart, int pageNo, string l
{
string dayEnd = DateTime.Now.ToString("yyyy-MM-dd");
Logger.LogInformation($"获取当前彩票结果 - 起始日期: {dayStart}, 结束日期: {dayEnd}, 彩票类型: {lotteryType}, 页码: {pageNo}");
LotteryInputDto dto = await GetLotteryResult(dayStart, dayEnd, 1, lotteryType);

LotteryInputDto dto = await GetLotteryResult(dayStart, dayEnd, pageNo, lotteryType);

if (dto.Result != null && dto.Result.Count > 0)
{
Logger.LogInformation($"获取到 {dto.Result.Count} 条数据,开始映射并保存到数据库");
Expand Down Expand Up @@ -188,9 +187,9 @@ private async Task GetCurrentLotteryResult(string dayStart, int pageNo, string l
private async Task GetAllLotteryResults(string dayStart, string dayEnd, int pageNo, string lotteryType)
{
Logger.LogInformation($"获取所有历史彩票结果 - 起始日期: {dayStart}, 结束日期: {dayEnd}, 彩票类型: {lotteryType}, 页码: {pageNo}");

LotteryInputDto dto = await GetLotteryResult(dayStart, dayEnd, pageNo, lotteryType);

if (dto.Result != null && dto.Result.Count > 0)
{
Logger.LogInformation($"获取到 {dto.Result.Count} 条历史数据,开始映射并保存到数据库");
Expand Down Expand Up @@ -227,7 +226,7 @@ private async Task GetAllLotteryResults(string dayStart, string dayEnd, int page
private async Task UpdatePrizegrades(string lotteryType, string lotteryTypeEng)
{
Logger.LogInformation($"开始更新奖级信息 - 彩票类型: {lotteryType} (英文: {lotteryTypeEng})");

try
{
// 查询没有奖级信息的彩票结果
Expand All @@ -241,7 +240,7 @@ from z2 in z.DefaultIfEmpty()

var queryList = query.ToList();
Logger.LogInformation($"查询到 {queryList.Count} 条彩票结果记录");

int noPrizeCount = queryList.Count(x => x.prize == null);
Logger.LogInformation($"其中 {noPrizeCount} 条记录没有奖级信息");

Expand All @@ -251,7 +250,7 @@ from z2 in z.DefaultIfEmpty()
if (item.prize == null)
{
Logger.LogInformation($"处理彩票结果 ID: {item.result.Id}, 代码: {item.result.Code}, 日期: {item.result.Date}");

try
{
string dayStart = (item.result.Date!.Split('('))[0];
Expand All @@ -269,7 +268,7 @@ from z2 in z.DefaultIfEmpty()
if (prize.Prizegrades != null && prize.Prizegrades.Count > 0)
{
Logger.LogInformation($"为彩票结果 ID: {item.result.Id} 添加 {prize.Prizegrades.Count} 条奖级信息");

foreach (var prizeId in prize.Prizegrades)
{
prizeId.LotteryResultId = item.result.Id;
Expand All @@ -295,7 +294,7 @@ from z2 in z.DefaultIfEmpty()
}
}
}

Logger.LogInformation($"奖级信息更新完成,共处理 {processedCount} 条记录");
}
catch (Exception ex)
Expand All @@ -310,27 +309,27 @@ private async Task<LotteryInputDto> GetLotteryResult(string dayStart, string day
// 使用代理服务器获取数据
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}");
Logger.LogInformation($"代理请求URL: {requestUrl}");

try
{
using var client = _httpClientFactory.CreateClient();
// 设置超时时间
client.Timeout = TimeSpan.FromSeconds(60);

Logger.LogInformation("发送代理HTTP请求...");

HttpResponseMessage message = await client.GetAsync(requestUrl);

Logger.LogInformation($"代理HTTP响应状态码: {(int)message.StatusCode} ({message.StatusCode})");

message.EnsureSuccessStatusCode();

string responseContent = await message.Content.ReadAsStringAsync();
Logger.LogInformation($"代理响应内容长度: {responseContent.Length} 字符");

// 记录响应内容(仅前500字符,避免日志过长)
if (responseContent.Length > 500)
{
Expand All @@ -340,9 +339,9 @@ private async Task<LotteryInputDto> GetLotteryResult(string dayStart, string day
{
Logger.LogInformation($"代理响应内容: {responseContent}");
}

LotteryInputDto? dto = JsonSerializer.Deserialize<LotteryInputDto>(responseContent);

if (dto == null)
{
Logger.LogWarning("反序列化代理响应失败,响应为null,创建空对象");
Expand All @@ -351,11 +350,11 @@ private async Task<LotteryInputDto> GetLotteryResult(string dayStart, string day
else
{
Logger.LogInformation($"反序列化代理响应成功 - 总数据量: {dto.Total}, 当前页: {dto.PageNo}/{dto.PageNum}, 每页大小: {dto.PageSize}");

if (dto.Result != null)
{
Logger.LogInformation($"当前页数据条数: {dto.Result.Count}");

// 记录第一条数据的详细信息
if (dto.Result.Count > 0)
{
Expand Down
Loading