This commit is contained in:
kislovdm
2026-04-02 21:58:30 +03:00
parent 4c08ebd68b
commit 63900b25b9
5 changed files with 114 additions and 15 deletions
+8 -2
View File
@@ -1,4 +1,4 @@
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src WORKDIR /src
COPY ./src ./src COPY ./src ./src
@@ -6,10 +6,16 @@ COPY ./src ./src
RUN dotnet restore "src/Telegram2VkBot/Telegram2VkBot.csproj" RUN dotnet restore "src/Telegram2VkBot/Telegram2VkBot.csproj"
RUN dotnet publish "src/Telegram2VkBot/Telegram2VkBot.csproj" -c Release -o /app/publish --no-restore RUN dotnet publish "src/Telegram2VkBot/Telegram2VkBot.csproj" -c Release -o /app/publish --no-restore
FROM mcr.microsoft.com/dotnet/runtime:8.0 AS runtime FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime
RUN apt-get update \
&& apt-get install -y --no-install-recommends curl \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app WORKDIR /app
COPY --from=build /app/publish . COPY --from=build /app/publish .
ENV ASPNETCORE_URLS=http://+:8080
EXPOSE 8080
ENTRYPOINT ["dotnet", "Telegram2VkBot.dll"] ENTRYPOINT ["dotnet", "Telegram2VkBot.dll"]
+9
View File
@@ -15,6 +15,15 @@ services:
#VK__GroupId: ${VK_GROUP_ID} #VK__GroupId: ${VK_GROUP_ID}
#VK__ApiVersion: ${VK_API_VERSION:-5.199} #VK__ApiVersion: ${VK_API_VERSION:-5.199}
DB__Path: /data/mapping.sqlite DB__Path: /data/mapping.sqlite
ASPNETCORE_URLS: http://+:8080
ports:
- "8080:8080"
volumes: volumes:
- ./data:/data - ./data:/data
healthcheck:
test: ["CMD-SHELL", "curl -fsS http://127.0.0.1:8080/health >/dev/null || exit 1"]
interval: 30s
timeout: 12s
retries: 3
start_period: 45s
+18 -7
View File
@@ -1,13 +1,10 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace Telegram2VkBot; namespace Telegram2VkBot;
public static class Program public static class Program
{ {
public static async Task Main(string[] args) public static async Task Main(string[] args)
{ {
var builder = Host.CreateApplicationBuilder(args); var builder = WebApplication.CreateBuilder(args);
builder.Services.Configure<TelegramOptions>(builder.Configuration.GetSection("TELEGRAM")); builder.Services.Configure<TelegramOptions>(builder.Configuration.GetSection("TELEGRAM"));
builder.Services.Configure<VkOptions>(builder.Configuration.GetSection("VK")); builder.Services.Configure<VkOptions>(builder.Configuration.GetSection("VK"));
@@ -17,9 +14,20 @@ public static class Program
{ {
client.Timeout = TimeSpan.FromSeconds(60); client.Timeout = TimeSpan.FromSeconds(60);
}); });
builder.Services.AddHttpClient(TelegramApiHealthCheck.HttpClientName, client =>
{
client.Timeout = TimeSpan.FromSeconds(15);
});
builder.Services.AddSingleton<VkApiClient>(); builder.Services.AddSingleton<VkApiClient>();
builder.Services.AddSingleton<MappingRepository>(); builder.Services.AddSingleton<MappingRepository>();
builder.Services.AddHealthChecks()
.AddCheck<TelegramApiHealthCheck>(
name: "telegram",
tags: new[] { "telegram" });
builder.Services.AddHostedService<ForwardWorker>(); builder.Services.AddHostedService<ForwardWorker>();
builder.Logging.AddSimpleConsole(options => builder.Logging.AddSimpleConsole(options =>
@@ -28,8 +36,11 @@ public static class Program
options.TimestampFormat = "yyyy-MM-dd HH:mm:ss "; options.TimestampFormat = "yyyy-MM-dd HH:mm:ss ";
}); });
var host = builder.Build(); var app = builder.Build();
await host.RunAsync();
app.MapHealthChecks("/health");
app.MapGet("/", () => Results.Redirect("/health"));
await app.RunAsync().ConfigureAwait(false);
} }
} }
+3 -6
View File
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk.Worker"> <Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup> </PropertyGroup>
@@ -9,10 +9,7 @@
<None Update="appsettings.json"> <None Update="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None> </None>
<PackageReference Include="Microsoft.Data.Sqlite" Version="9.0.9" /> <PackageReference Include="Microsoft.Data.Sqlite" Version="10.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
<PackageReference Include="Telegram.Bot" Version="22.9.5.3" /> <PackageReference Include="Telegram.Bot" Version="22.9.5.3" />
<PackageReference Include="VkNet" Version="1.78.0" /> <PackageReference Include="VkNet" Version="1.78.0" />
</ItemGroup> </ItemGroup>
@@ -0,0 +1,76 @@
using System.Text.Json;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using Microsoft.Extensions.Options;
namespace Telegram2VkBot;
/// <summary>
/// Проверка доступности Telegram Bot API (getMe).
/// </summary>
public sealed class TelegramApiHealthCheck : IHealthCheck
{
public const string HttpClientName = "TelegramHealth";
private readonly TelegramOptions _telegram;
private readonly IHttpClientFactory _httpFactory;
public TelegramApiHealthCheck(IOptions<TelegramOptions> telegram, IHttpClientFactory httpFactory)
{
_telegram = telegram.Value;
_httpFactory = httpFactory;
}
public async Task<HealthCheckResult> CheckHealthAsync(
HealthCheckContext context,
CancellationToken cancellationToken = default)
{
if (string.IsNullOrWhiteSpace(_telegram.BotToken))
return HealthCheckResult.Unhealthy("TELEGRAM__BotToken не задан.");
var url = $"https://api.telegram.org/bot{_telegram.BotToken}/getMe";
var http = _httpFactory.CreateClient(HttpClientName);
try
{
using var response = await http.GetAsync(url, cancellationToken).ConfigureAwait(false);
var json = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
if (!response.IsSuccessStatusCode)
{
return HealthCheckResult.Unhealthy(
$"Telegram API HTTP {(int)response.StatusCode}",
data: new Dictionary<string, object> { ["body"] = Truncate(json, 500) });
}
using var doc = JsonDocument.Parse(json);
if (!doc.RootElement.TryGetProperty("ok", out var ok) || ok.ValueKind != JsonValueKind.True)
{
var desc = doc.RootElement.TryGetProperty("description", out var d) ? d.GetString() : json;
return HealthCheckResult.Unhealthy($"Telegram getMe: ok=false, {desc}");
}
var username = doc.RootElement.TryGetProperty("result", out var result)
&& result.TryGetProperty("username", out var un)
? un.GetString()
: null;
return HealthCheckResult.Healthy(
username is null ? "Telegram API доступен." : $"Telegram API: @{username}");
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
throw;
}
catch (Exception ex)
{
return HealthCheckResult.Unhealthy("Не удалось связаться с Telegram API.", ex);
}
}
private static string Truncate(string s, int max)
{
if (string.IsNullOrEmpty(s) || s.Length <= max)
return s;
return string.Concat(s.AsSpan(0, max), "…");
}
}