단순 이관
This commit is contained in:
47
Bitget_Server/Api/Controllers/CoinsController.cs
Normal file
47
Bitget_Server/Api/Controllers/CoinsController.cs
Normal file
@@ -0,0 +1,47 @@
|
||||
using Bitget_Server.Application.Services;
|
||||
using Bitget_Server.Domain.Models;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Bitget_Server.Api.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// 기존 WinForms 코인 선택 그리드 데이터를 REST 화.
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/coins")]
|
||||
[Produces("application/json")]
|
||||
public class CoinsController : ControllerBase
|
||||
{
|
||||
private readonly SettingsService _settings;
|
||||
private readonly ILogger<CoinsController> _logger;
|
||||
|
||||
public CoinsController(SettingsService settings, ILogger<CoinsController> logger)
|
||||
{
|
||||
_settings = settings;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// USDT 기준 Bitget 선물 마켓 정보 목록을 반환한다.
|
||||
/// 내부적으로 BitgetClient 캐시(_state.marketInfoList)를 우선 사용하고 비어 있으면 호출.
|
||||
/// </summary>
|
||||
[HttpGet]
|
||||
[ProducesResponseType(typeof(IEnumerable<MarketInfo>), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status502BadGateway)]
|
||||
public async Task<ActionResult<IEnumerable<MarketInfo>>> GetCoins(CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var coins = await _settings.GetCoinsAsync(ct);
|
||||
return Ok(coins);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "코인 조회 실패");
|
||||
return Problem(
|
||||
detail: ex.Message,
|
||||
statusCode: StatusCodes.Status502BadGateway,
|
||||
title: "Failed to fetch coins from Bitget");
|
||||
}
|
||||
}
|
||||
}
|
||||
21
Bitget_Server/Api/Controllers/HealthController.cs
Normal file
21
Bitget_Server/Api/Controllers/HealthController.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Bitget_Server.Api.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// 단순 헬스체크. Docker / 로드밸런서에서 사용.
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/health")]
|
||||
[Produces("application/json")]
|
||||
public class HealthController : ControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
public IActionResult Get() => Ok(new
|
||||
{
|
||||
status = "ok",
|
||||
timestampUtc = DateTime.UtcNow,
|
||||
version = typeof(HealthController).Assembly.GetName().Version?.ToString() ?? "unknown"
|
||||
});
|
||||
}
|
||||
148
Bitget_Server/Api/Controllers/SettingsController.cs
Normal file
148
Bitget_Server/Api/Controllers/SettingsController.cs
Normal file
@@ -0,0 +1,148 @@
|
||||
using Bitget_Server.Api.Dtos;
|
||||
using Bitget_Server.Application.Services;
|
||||
using Bitget_Server.Infrastructure.Utils;
|
||||
using Bitget_Server.Domain.Enums;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Bitget_Server.Api.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// 기존 WinForms MainForm 의 ComboBox / 텍스트박스 / API 선택 라디오버튼을 REST 화.
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/settings")]
|
||||
[Produces("application/json")]
|
||||
public class SettingsController : ControllerBase
|
||||
{
|
||||
private readonly SettingsService _settings;
|
||||
|
||||
public SettingsController(SettingsService settings)
|
||||
{
|
||||
_settings = settings;
|
||||
}
|
||||
|
||||
// ===== 캔들 간격 =====
|
||||
|
||||
/// <summary>현재 선택된 캔들 간격(분)을 반환.</summary>
|
||||
[HttpGet("candle-interval")]
|
||||
[ProducesResponseType(typeof(CandleIntervalDto), StatusCodes.Status200OK)]
|
||||
public ActionResult<CandleIntervalDto> GetCandleInterval()
|
||||
{
|
||||
var minutes = _settings.GetCandleIntervalMinutes();
|
||||
var interval = (MCandleTimeType)minutes;
|
||||
return Ok(new CandleIntervalDto(minutes, Convertor.GetEnumDescription(interval)));
|
||||
}
|
||||
|
||||
/// <summary>캔들 간격을 변경.</summary>
|
||||
[HttpPut("candle-interval")]
|
||||
[ProducesResponseType(typeof(CandleIntervalDto), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
public ActionResult<CandleIntervalDto> UpdateCandleInterval([FromBody] UpdateCandleIntervalRequest req)
|
||||
{
|
||||
try
|
||||
{
|
||||
_settings.SetCandleIntervalMinutes(req.Minutes);
|
||||
var interval = (MCandleTimeType)req.Minutes;
|
||||
return Ok(new CandleIntervalDto(req.Minutes, Convertor.GetEnumDescription(interval)));
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
return Problem(detail: ex.Message, statusCode: StatusCodes.Status400BadRequest, title: "Invalid candle interval");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>지원되는 캔들 간격 목록.</summary>
|
||||
[HttpGet("candle-interval/options")]
|
||||
[ProducesResponseType(typeof(IEnumerable<object>), StatusCodes.Status200OK)]
|
||||
public ActionResult<IEnumerable<object>> ListCandleIntervals()
|
||||
=> Ok(_settings.ListCandleIntervals());
|
||||
|
||||
// ===== 선택 코인 =====
|
||||
|
||||
[HttpGet("selected-coin")]
|
||||
[ProducesResponseType(typeof(SelectedCoinDto), StatusCodes.Status200OK)]
|
||||
public ActionResult<SelectedCoinDto> GetSelectedCoin()
|
||||
=> Ok(new SelectedCoinDto(_settings.GetSelectedCoin()));
|
||||
|
||||
[HttpPut("selected-coin")]
|
||||
[ProducesResponseType(typeof(SelectedCoinDto), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
public ActionResult<SelectedCoinDto> UpdateSelectedCoin([FromBody] UpdateSelectedCoinRequest req)
|
||||
{
|
||||
try
|
||||
{
|
||||
_settings.SetSelectedCoin(req.MarketName);
|
||||
return Ok(new SelectedCoinDto(req.MarketName));
|
||||
}
|
||||
catch (ArgumentException ex)
|
||||
{
|
||||
return Problem(detail: ex.Message, statusCode: StatusCodes.Status400BadRequest, title: "Invalid coin");
|
||||
}
|
||||
}
|
||||
|
||||
// ===== 레버리지 =====
|
||||
|
||||
[HttpGet("leverage")]
|
||||
[ProducesResponseType(typeof(LeverageDto), StatusCodes.Status200OK)]
|
||||
public ActionResult<LeverageDto> GetLeverage()
|
||||
=> Ok(new LeverageDto(_settings.GetLeverage()));
|
||||
|
||||
[HttpPut("leverage")]
|
||||
[ProducesResponseType(typeof(LeverageDto), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
public ActionResult<LeverageDto> UpdateLeverage([FromBody] UpdateLeverageRequest req)
|
||||
{
|
||||
if (req.Leverage <= 0)
|
||||
return Problem(detail: "Leverage must be > 0", statusCode: StatusCodes.Status400BadRequest, title: "Invalid leverage");
|
||||
|
||||
_settings.SetLeverage(req.Leverage);
|
||||
return Ok(new LeverageDto(req.Leverage));
|
||||
}
|
||||
|
||||
// ===== 주문 수량 =====
|
||||
|
||||
[HttpGet("order-amount")]
|
||||
[ProducesResponseType(typeof(OrderAmountDto), StatusCodes.Status200OK)]
|
||||
public ActionResult<OrderAmountDto> GetOrderAmount()
|
||||
=> Ok(new OrderAmountDto(_settings.GetOrderAmount()));
|
||||
|
||||
[HttpPut("order-amount")]
|
||||
[ProducesResponseType(typeof(OrderAmountDto), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
|
||||
public ActionResult<OrderAmountDto> UpdateOrderAmount([FromBody] UpdateOrderAmountRequest req)
|
||||
{
|
||||
if (req.OrderAmount <= 0)
|
||||
return Problem(detail: "OrderAmount must be > 0", statusCode: StatusCodes.Status400BadRequest, title: "Invalid order amount");
|
||||
|
||||
_settings.SetOrderAmount(req.OrderAmount);
|
||||
return Ok(new OrderAmountDto(req.OrderAmount));
|
||||
}
|
||||
|
||||
// ===== Selected API 키 =====
|
||||
|
||||
[HttpGet("api-key")]
|
||||
[ProducesResponseType(typeof(SelectedApiDto), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
public ActionResult<SelectedApiDto> GetSelectedApi()
|
||||
{
|
||||
var id = _settings.GetSelectedApiId();
|
||||
if (id == null) return NoContent();
|
||||
return Ok(new SelectedApiDto(id.Value, string.Empty));
|
||||
}
|
||||
|
||||
[HttpPut("api-key")]
|
||||
[ProducesResponseType(typeof(SelectedApiDto), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
public ActionResult<SelectedApiDto> UpdateSelectedApi([FromBody] UpdateSelectedApiRequest req)
|
||||
{
|
||||
try
|
||||
{
|
||||
_settings.SetSelectedApiId(req.Id);
|
||||
return Ok(new SelectedApiDto(req.Id, string.Empty));
|
||||
}
|
||||
catch (KeyNotFoundException ex)
|
||||
{
|
||||
return Problem(detail: ex.Message, statusCode: StatusCodes.Status404NotFound, title: "API key not found");
|
||||
}
|
||||
}
|
||||
}
|
||||
97
Bitget_Server/Api/Controllers/SignalsController.cs
Normal file
97
Bitget_Server/Api/Controllers/SignalsController.cs
Normal file
@@ -0,0 +1,97 @@
|
||||
using Bitget_Server.Api.Dtos;
|
||||
using Bitget_Server.Application.Services;
|
||||
using Bitget_Server.Application.State;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Bitget_Server.Api.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// 기존 WinForms 의 구름떼/영호 조건식 체크박스 및 신호검색 그리드를 REST 화.
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/signals")]
|
||||
[Produces("application/json")]
|
||||
public class SignalsController : ControllerBase
|
||||
{
|
||||
private readonly SignalProcessService _signalProcess;
|
||||
private readonly MarketState _state;
|
||||
|
||||
public SignalsController(SignalProcessService signalProcess, MarketState state)
|
||||
{
|
||||
_signalProcess = signalProcess;
|
||||
_state = state;
|
||||
}
|
||||
|
||||
/// <summary>현재 활성화된 시그널 목록 + 카운트.</summary>
|
||||
[HttpGet]
|
||||
[ProducesResponseType(typeof(SignalListResponse), StatusCodes.Status200OK)]
|
||||
public ActionResult<SignalListResponse> GetSignals()
|
||||
{
|
||||
var result = new SignalListResponse(
|
||||
_signalProcess.SignalsLongUp.Select(Map),
|
||||
_signalProcess.SignalsShortDown.Select(Map),
|
||||
_signalProcess.SignalsLongDown.Select(Map),
|
||||
_signalProcess.SignalsShortUp.Select(Map),
|
||||
_signalProcess.LongCount,
|
||||
_signalProcess.ShortCount);
|
||||
return Ok(result);
|
||||
|
||||
static SignalSummaryDto Map(Domain.Models.SignalInfoData s)
|
||||
=> new(s.MarketName, s.Description, s.SignalStart, s.Price, s.Rate,
|
||||
s.IndicatorPrice, s.IndicatorRate, s.MCandleTimeType);
|
||||
}
|
||||
|
||||
/// <summary>조건식 상태 조회.</summary>
|
||||
[HttpGet("conditions")]
|
||||
[ProducesResponseType(typeof(SignalConditionsDto), StatusCodes.Status200OK)]
|
||||
public ActionResult<SignalConditionsDto> GetConditions()
|
||||
=> Ok(new SignalConditionsDto(
|
||||
_signalProcess.Checked구름떼상향돌파,
|
||||
_signalProcess.Checked구름떼하향돌파,
|
||||
_signalProcess.영호조건식_상향돌파,
|
||||
_signalProcess.영호조건식_하향돌파));
|
||||
|
||||
/// <summary>조건식 상태 변경.</summary>
|
||||
[HttpPut("conditions")]
|
||||
[ProducesResponseType(typeof(SignalConditionsDto), StatusCodes.Status200OK)]
|
||||
public ActionResult<SignalConditionsDto> UpdateConditions([FromBody] UpdateSignalConditionsRequest req)
|
||||
{
|
||||
_signalProcess.Checked구름떼상향돌파 = req.구름떼상향돌파;
|
||||
_signalProcess.Checked구름떼하향돌파 = req.구름떼하향돌파;
|
||||
_signalProcess.영호조건식_상향돌파 = req.영호조건식_상향돌파;
|
||||
_signalProcess.영호조건식_하향돌파 = req.영호조건식_하향돌파;
|
||||
|
||||
return Ok(new SignalConditionsDto(
|
||||
_signalProcess.Checked구름떼상향돌파,
|
||||
_signalProcess.Checked구름떼하향돌파,
|
||||
_signalProcess.영호조건식_상향돌파,
|
||||
_signalProcess.영호조건식_하향돌파));
|
||||
}
|
||||
|
||||
/// <summary>체크된 마켓/인터벌 목록 조회 (신호검색 대상).</summary>
|
||||
[HttpGet("checked-markets")]
|
||||
[ProducesResponseType(typeof(CheckedMarketsDto), StatusCodes.Status200OK)]
|
||||
public ActionResult<CheckedMarketsDto> GetCheckedMarkets()
|
||||
{
|
||||
lock (_state.SyncRoot)
|
||||
{
|
||||
return Ok(new CheckedMarketsDto(
|
||||
_state.CheckedMarkets.Select(m => m?.ToString() ?? string.Empty).ToList(),
|
||||
_state.IntervalList.Select(i => i?.ToString() ?? string.Empty).ToList()));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>체크된 마켓/인터벌 일괄 설정.</summary>
|
||||
[HttpPut("checked-markets")]
|
||||
[ProducesResponseType(typeof(CheckedMarketsDto), StatusCodes.Status200OK)]
|
||||
public ActionResult<CheckedMarketsDto> UpdateCheckedMarkets([FromBody] UpdateCheckedMarketsRequest req)
|
||||
{
|
||||
lock (_state.SyncRoot)
|
||||
{
|
||||
_state.CheckedMarkets = req.Markets.Cast<object>().ToList();
|
||||
_state.IntervalList = req.Intervals.Cast<object>().ToList();
|
||||
}
|
||||
|
||||
return Ok(new CheckedMarketsDto(req.Markets, req.Intervals));
|
||||
}
|
||||
}
|
||||
122
Bitget_Server/Api/Controllers/TradingController.cs
Normal file
122
Bitget_Server/Api/Controllers/TradingController.cs
Normal file
@@ -0,0 +1,122 @@
|
||||
using Bitget_Server.Api.Dtos;
|
||||
using Bitget_Server.Application.Services;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Bitget_Server.Api.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// 기존 WinForms 의 주문/취소/잔고 버튼을 REST 화.
|
||||
/// Bitget Private API 를 호출하므로 반드시 선택된 API 키가 설정되어 있어야 한다.
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/trading")]
|
||||
[Produces("application/json")]
|
||||
public class TradingController : ControllerBase
|
||||
{
|
||||
private readonly BitgetTradingService _trading;
|
||||
private readonly ILogger<TradingController> _logger;
|
||||
|
||||
public TradingController(BitgetTradingService trading, ILogger<TradingController> logger)
|
||||
{
|
||||
_trading = trading;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>Bitget 서버 타임스탬프 조회 (헬스체크 용도로도 활용).</summary>
|
||||
[HttpGet("timestamp")]
|
||||
[ProducesResponseType(typeof(long), StatusCodes.Status200OK)]
|
||||
public async Task<ActionResult<long>> GetTimestamp(CancellationToken ct)
|
||||
=> Ok(await _trading.GetTimeStampAsync(ct));
|
||||
|
||||
/// <summary>선물 계좌 잔고/포지션 조회.</summary>
|
||||
[HttpGet("account")]
|
||||
[ProducesResponseType(typeof(List<Dictionary<string, object>>), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status502BadGateway)]
|
||||
public async Task<IActionResult> GetAccount(CancellationToken ct)
|
||||
{
|
||||
var result = await _trading.GetFutureAccountListAsync(ct);
|
||||
if (result == null)
|
||||
return Problem(detail: "Bitget API 응답 없음", statusCode: StatusCodes.Status502BadGateway, title: "Account fetch failed");
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
/// <summary>미체결 주문 조회.</summary>
|
||||
[HttpGet("orders/pending")]
|
||||
[ProducesResponseType(typeof(Dictionary<string, object>), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status502BadGateway)]
|
||||
public async Task<IActionResult> GetPendingOrders(CancellationToken ct)
|
||||
{
|
||||
var result = await _trading.GetPendingOrdersAsync(ct);
|
||||
if (result == null)
|
||||
return Problem(detail: "Bitget API 응답 없음", statusCode: StatusCodes.Status502BadGateway, title: "Pending orders fetch failed");
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
/// <summary>주문 상세 조회.</summary>
|
||||
[HttpGet("orders/{symbol}/{orderId}")]
|
||||
[ProducesResponseType(typeof(Dictionary<string, object>), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> GetOrderDetail(string symbol, string orderId, CancellationToken ct)
|
||||
{
|
||||
var result = await _trading.GetFutureOrderDetailAsync(symbol, orderId, ct);
|
||||
if (result == null)
|
||||
return Problem(detail: "주문 상세 응답 없음", statusCode: StatusCodes.Status404NotFound, title: "Order detail not found");
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
/// <summary>신규 주문.</summary>
|
||||
[HttpPost("orders")]
|
||||
[ProducesResponseType(typeof(Dictionary<string, object>), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status502BadGateway)]
|
||||
public async Task<IActionResult> PlaceOrder([FromBody] PlaceOrderRequest req, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _trading.FutureOrderAsync(
|
||||
req.Symbol,
|
||||
req.MarginMode,
|
||||
req.Size,
|
||||
req.Price,
|
||||
req.Side,
|
||||
req.OrderType,
|
||||
req.Leverage,
|
||||
req.ClientOrderId ?? string.Empty,
|
||||
ct);
|
||||
|
||||
if (result == null)
|
||||
return Problem(detail: "주문 실패 (MarginMode/Leverage/Order 체인 중 단계 실패)",
|
||||
statusCode: StatusCodes.Status502BadGateway, title: "Order failed");
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "주문 처리 실패: {Symbol}", req.Symbol);
|
||||
return Problem(detail: ex.Message, statusCode: StatusCodes.Status500InternalServerError, title: "Order processing error");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>주문 취소.</summary>
|
||||
[HttpDelete("orders")]
|
||||
[ProducesResponseType(typeof(Dictionary<string, object>), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status502BadGateway)]
|
||||
public async Task<IActionResult> CancelOrder([FromBody] CancelOrderRequest req, CancellationToken ct)
|
||||
{
|
||||
var result = await _trading.CancelFutureOrderAsync(req.Symbol, req.OrderId, ct);
|
||||
if (result == null)
|
||||
return Problem(detail: "취소 응답 없음", statusCode: StatusCodes.Status502BadGateway, title: "Cancel failed");
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
/// <summary>포지션 모드 변경(hedge / one_way).</summary>
|
||||
[HttpPut("position-mode")]
|
||||
[ProducesResponseType(typeof(Dictionary<string, object>), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status502BadGateway)]
|
||||
public async Task<IActionResult> ChangePositionMode([FromBody] ChangePositionModeRequest req, CancellationToken ct)
|
||||
{
|
||||
var result = await _trading.ChangeFuturePositionModeAsync(req.Mode, ct);
|
||||
if (result == null)
|
||||
return Problem(detail: "포지션 모드 변경 실패", statusCode: StatusCodes.Status502BadGateway, title: "Position mode change failed");
|
||||
return Ok(result);
|
||||
}
|
||||
}
|
||||
66
Bitget_Server/Api/Controllers/WebSocketController.cs
Normal file
66
Bitget_Server/Api/Controllers/WebSocketController.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using Bitget_Server.Domain.Enums;
|
||||
using Bitget_Server.Infrastructure.Bitget;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Bitget_Server.Api.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// 기존 WinForms 의 BitGetWebSocektStart / closeWebSocket 버튼 REST 화.
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/websocket")]
|
||||
[Produces("application/json")]
|
||||
public class WebSocketController : ControllerBase
|
||||
{
|
||||
private readonly BitgetWebSocketClient _ws;
|
||||
private readonly ILogger<WebSocketController> _logger;
|
||||
|
||||
public WebSocketController(BitgetWebSocketClient ws, ILogger<WebSocketController> logger)
|
||||
{
|
||||
_ws = ws;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>현재 WebSocket 연결 상태.</summary>
|
||||
[HttpGet("status")]
|
||||
[ProducesResponseType(typeof(object), StatusCodes.Status200OK)]
|
||||
public IActionResult GetStatus() => Ok(new { connected = _ws.IsConnected });
|
||||
|
||||
/// <summary>
|
||||
/// WebSocket 연결 시작. subscribeArgs 는 Bitget 공식 포맷의 args 리스트.
|
||||
/// 예: [{"instType":"USDT-FUTURES","channel":"ticker","instId":"BTCUSDT"}]
|
||||
/// </summary>
|
||||
[HttpPost("start")]
|
||||
[ProducesResponseType(StatusCodes.Status202Accepted)]
|
||||
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status500InternalServerError)]
|
||||
public async Task<IActionResult> Start(
|
||||
[FromQuery] BitgetWebSocketType type,
|
||||
[FromBody] IEnumerable<object> subscribeArgs,
|
||||
CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var dic = new Dictionary<string, object>
|
||||
{
|
||||
{ "op", "subscribe" },
|
||||
{ "args", subscribeArgs ?? Array.Empty<object>() }
|
||||
};
|
||||
await _ws.StartAsync(type, dic, ct);
|
||||
return Accepted();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "WebSocket Start 실패");
|
||||
return Problem(detail: ex.Message, statusCode: StatusCodes.Status500InternalServerError, title: "WebSocket start failed");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>WebSocket 연결 종료.</summary>
|
||||
[HttpPost("stop")]
|
||||
[ProducesResponseType(StatusCodes.Status204NoContent)]
|
||||
public async Task<IActionResult> Stop(CancellationToken ct)
|
||||
{
|
||||
await _ws.StopAsync(ct);
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user