단순 이관
This commit is contained in:
@@ -1,36 +1,191 @@
|
||||
using Bitget_Server.EFCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Net;
|
||||
using Bitget_Server.Application.BackgroundServices;
|
||||
using Bitget_Server.Application.Options;
|
||||
using Bitget_Server.Application.Services;
|
||||
using Bitget_Server.Application.State;
|
||||
using Bitget_Server.Infrastructure.Bitget;
|
||||
using Bitget_Server.Infrastructure.Utils;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Serilog;
|
||||
|
||||
namespace Bitget_Server
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
// =========================================================================
|
||||
// 1. Kestrel: HTTP only / port 5000
|
||||
// =========================================================================
|
||||
builder.WebHost.ConfigureKestrel(options =>
|
||||
{
|
||||
public class Program
|
||||
options.ListenAnyIP(5000); // HTTP 전용, HTTPS 사용하지 않음
|
||||
});
|
||||
|
||||
// =========================================================================
|
||||
// 2. Options 바인딩
|
||||
// =========================================================================
|
||||
builder.Services.Configure<BitgetOptions>(builder.Configuration.GetSection(BitgetOptions.SectionName));
|
||||
var bitgetOptions = builder.Configuration.GetSection(BitgetOptions.SectionName).Get<BitgetOptions>() ?? new BitgetOptions();
|
||||
|
||||
// =========================================================================
|
||||
// 3. 영속화 경로 설정 (BaseSettings<GlobalOptions>.StorageDirectory)
|
||||
// 컨테이너에서는 /data, 로컬에서는 ./data 로 마운트됨
|
||||
// =========================================================================
|
||||
var dataDir = Path.GetFullPath(bitgetOptions.DataDirectory);
|
||||
var logDir = Path.GetFullPath(bitgetOptions.LogDirectory);
|
||||
Directory.CreateDirectory(dataDir);
|
||||
Directory.CreateDirectory(logDir);
|
||||
BaseSettings<GlobalOptions>.StorageDirectory = dataDir;
|
||||
|
||||
// =========================================================================
|
||||
// 4. Serilog 구성 (Console + 일자별 File Sink)
|
||||
// =========================================================================
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.MinimumLevel.Is(bitgetOptions.LogDebugWrite
|
||||
? Serilog.Events.LogEventLevel.Debug
|
||||
: Serilog.Events.LogEventLevel.Information)
|
||||
.MinimumLevel.Override("Microsoft.AspNetCore", Serilog.Events.LogEventLevel.Warning)
|
||||
.Enrich.FromLogContext()
|
||||
.WriteTo.Console()
|
||||
.WriteTo.Map(
|
||||
keyPropertyName: "SourceContext",
|
||||
defaultKey: "general",
|
||||
configure: (key, wt) => wt.File(
|
||||
path: Path.Combine(logDir, "bitget-.log"),
|
||||
rollingInterval: RollingInterval.Day,
|
||||
retainedFileCountLimit: 30,
|
||||
outputTemplate: "[{Timestamp:HH:mm:ss.fff} {Level:u3}] {SourceContext} {Message:lj}{NewLine}{Exception}"))
|
||||
.CreateLogger();
|
||||
|
||||
builder.Host.UseSerilog();
|
||||
|
||||
// =========================================================================
|
||||
// 5. DI 등록 - 상태 / 설정 싱글톤
|
||||
// =========================================================================
|
||||
builder.Services.AddSingleton<MarketState>();
|
||||
builder.Services.AddSingleton(sp => GlobalOptions.Instance); // BaseSettings 기반 파일 영속화 싱글톤
|
||||
builder.Services.AddSingleton<PriceLevelTracker>();
|
||||
|
||||
// 기본 API 키 자동 선택 (GlobalOptions.SelectAPI 가 비어 있으면 appsettings.json 의 DefaultApiId 로 초기화)
|
||||
// 반드시 다른 HostedService 보다 먼저 등록되어야 함 (HostedServices 는 등록 순서대로 StartAsync).
|
||||
builder.Services.AddHostedService<ApiKeyBootstrapper>();
|
||||
|
||||
// =========================================================================
|
||||
// 6. DI 등록 - Bitget Infrastructure
|
||||
// =========================================================================
|
||||
builder.Services.AddSingleton(sp => new BitgetRateLimiter(
|
||||
bitgetOptions.RateLimitPerSecond,
|
||||
sp.GetRequiredService<ILogger<BitgetRateLimiter>>()));
|
||||
|
||||
// Bitget 단일 엔드포인트만 호출하므로 HttpClient 를 Singleton 으로 공유해도 안전.
|
||||
// (BackgroundService 가 Singleton 이므로 BitgetClient 도 Singleton 이어야 캡처 문제가 없음)
|
||||
builder.Services.AddSingleton(sp =>
|
||||
{
|
||||
var http = new HttpClient { Timeout = TimeSpan.FromSeconds(30) };
|
||||
http.DefaultRequestHeaders.Add("User-Agent", "Bitget_Server/1.0");
|
||||
return http;
|
||||
});
|
||||
builder.Services.AddSingleton<BitgetClient>();
|
||||
builder.Services.AddSingleton<BitgetWebSocketClient>();
|
||||
|
||||
// =========================================================================
|
||||
// 7. DI 등록 - Application Services (모두 Singleton: 상태 보관 + BackgroundService 에서 참조)
|
||||
// =========================================================================
|
||||
builder.Services.AddSingleton<SettingsService>();
|
||||
builder.Services.AddSingleton<BitgetTradingService>();
|
||||
builder.Services.AddSingleton<InitialCandleLoader>();
|
||||
builder.Services.AddSingleton<SignalProcessService>();
|
||||
|
||||
// =========================================================================
|
||||
// 8. DI 등록 - BackgroundServices (HostedServices)
|
||||
// =========================================================================
|
||||
builder.Services.AddHostedService<MarketInfoBackgroundService>();
|
||||
builder.Services.AddHostedService<WebSocketCalcBackgroundService>();
|
||||
builder.Services.AddHostedService<SignalProcessBackgroundService>();
|
||||
|
||||
// =========================================================================
|
||||
// 9. MVC / Swagger / ProblemDetails
|
||||
// =========================================================================
|
||||
builder.Services.AddControllers()
|
||||
.AddNewtonsoftJson(opts =>
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
builder.Services.AddDbContext<BitgetContext>();
|
||||
builder.Services.AddControllers();
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
builder.Services.AddSwaggerGen();
|
||||
builder.Services.AddControllersWithViews();
|
||||
opts.SerializerSettings.ReferenceLoopHandling =
|
||||
Newtonsoft.Json.ReferenceLoopHandling.Ignore;
|
||||
opts.SerializerSettings.NullValueHandling =
|
||||
Newtonsoft.Json.NullValueHandling.Ignore;
|
||||
});
|
||||
|
||||
var app = builder.Build();
|
||||
builder.Services.AddProblemDetails();
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
builder.Services.AddSwaggerGen(options =>
|
||||
{
|
||||
options.SwaggerDoc("v1", new OpenApiInfo
|
||||
{
|
||||
Title = "Bitget Server",
|
||||
Version = "v1",
|
||||
Description = "기존 Bitget WinForms 애플리케이션을 ASP.NET Core 8 REST API 로 이식한 서버."
|
||||
});
|
||||
});
|
||||
|
||||
using (var scope = app.Services.CreateScope())
|
||||
{
|
||||
var services = scope.ServiceProvider;
|
||||
var context = services.GetRequiredService<BitgetContext>();
|
||||
context.Database.Migrate();
|
||||
}
|
||||
// =========================================================================
|
||||
// 10. 미들웨어 파이프라인 - HTTPS 리디렉션 없음
|
||||
// =========================================================================
|
||||
var app = builder.Build();
|
||||
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI();
|
||||
app.MapControllers();
|
||||
app.UseExceptionHandler();
|
||||
app.UseStatusCodePages();
|
||||
|
||||
app.MapGet("/", () => "Hello World!");
|
||||
// Swagger 는 환경 불문 항상 활성화
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI(options =>
|
||||
{
|
||||
options.SwaggerEndpoint("/swagger/v1/swagger.json", "Bitget Server v1");
|
||||
options.RoutePrefix = "swagger";
|
||||
});
|
||||
|
||||
app.Run($"http://{IPAddress.Any}:{5045}");
|
||||
}
|
||||
}
|
||||
app.UseSerilogRequestLogging();
|
||||
|
||||
app.MapControllers();
|
||||
|
||||
// 루트 접속 시 Swagger 로 리다이렉트
|
||||
app.MapGet("/", () => Results.Redirect("/swagger"));
|
||||
|
||||
Log.Information("Bitget_Server 시작 — URL http://*:5000, DataDir={DataDir}, LogDir={LogDir}", dataDir, logDir);
|
||||
|
||||
try
|
||||
{
|
||||
app.Run();
|
||||
}
|
||||
finally
|
||||
{
|
||||
Log.CloseAndFlush();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 애플리케이션 시작 시 appsettings 의 DefaultApiId 로 GlobalOptions.SelectAPI 를 초기화한다.
|
||||
/// </summary>
|
||||
internal sealed class ApiKeyBootstrapper : IHostedService
|
||||
{
|
||||
private readonly IServiceProvider _sp;
|
||||
public ApiKeyBootstrapper(IServiceProvider sp) => _sp = sp;
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
var global = _sp.GetRequiredService<GlobalOptions>();
|
||||
var opts = _sp.GetRequiredService<Microsoft.Extensions.Options.IOptions<BitgetOptions>>().Value;
|
||||
|
||||
if (global.SelectAPI == null && opts.ApiKeys?.Count > 0)
|
||||
{
|
||||
global.SelectAPI = opts.ApiKeys.FirstOrDefault(k => k.Id == opts.DefaultApiId)
|
||||
?? opts.ApiKeys.First();
|
||||
global.Save();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "ApiKey 부트스트랩 실패 — 이후 /api/settings/api-key 로 수동 설정 필요");
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user