This commit is contained in:
sion
2026-04-06 18:45:04 +08:00
parent 2e34072f45
commit ee8979f471
37 changed files with 5 additions and 2500 deletions

View File

@@ -1,72 +0,0 @@
import '../../core/constants/api_endpoints.dart';
import '../../core/network/api_response.dart';
import '../../core/network/dio_client.dart';
import '../models/kline_candle.dart';
/// K线 REST API 服务
class KlineService {
final DioClient _client;
KlineService(this._client);
/// 获取历史K线数据
Future<ApiResponse<List<KlineCandle>>> fetchHistory({
required String coinCode,
required String interval,
int limit = 200,
int? before,
}) async {
final params = <String, dynamic>{
'coinCode': coinCode,
'interval': interval,
'limit': limit,
};
if (before != null) params['before'] = before;
final response = await _client.get<Map<String, dynamic>>(
ApiEndpoints.klineHistory,
queryParameters: params,
);
if (response.success && response.data != null) {
final list = response.data!['list'] as List? ?? [];
final candles = list
.map((e) => KlineCandle.fromHistoryJson(e as Map<String, dynamic>))
.toList();
return ApiResponse.success(candles, response.message);
}
return ApiResponse.fail(response.message ?? '获取K线数据失败');
}
/// 获取当前进行中的K线
Future<ApiResponse<KlineCandle>> fetchCurrentCandle({
required String coinCode,
required String interval,
}) async {
final response = await _client.get<Map<String, dynamic>>(
ApiEndpoints.klineCurrent,
queryParameters: {'coinCode': coinCode, 'interval': interval},
);
if (response.success && response.data != null) {
return ApiResponse.success(
KlineCandle.fromJson(response.data!),
response.message,
);
}
return ApiResponse.fail(response.message ?? '获取当前K线失败');
}
/// 获取支持的周期列表
Future<ApiResponse<List<String>>> fetchIntervals() async {
final response = await _client.get<Map<String, dynamic>>(
ApiEndpoints.klineIntervals,
);
if (response.success && response.data != null) {
final list = response.data!['list'] as List? ?? [];
return ApiResponse.success(list.cast<String>(), response.message);
}
return ApiResponse.fail(response.message ?? '获取周期列表失败');
}
}

View File

@@ -1,135 +0,0 @@
import 'dart:async';
import 'dart:convert';
import 'package:stomp_dart_client/stomp_dart_client.dart';
import '../../core/constants/api_endpoints.dart';
import '../models/kline_candle.dart';
/// K线 WebSocket 服务STOMP 协议)
class KlineWebSocketService {
StompClient? _stompClient;
final Map<String, dynamic> _subscriptions = {};
final Map<String, StreamController<KlineCandle>> _controllers = {};
bool _isConnected = false;
bool _isConnecting = false;
int _reconnectDelay = 2000; // 初始重连延迟
static const int _maxReconnectDelay = 30000;
/// 订阅某个币种的K线数据
Stream<KlineCandle> subscribe(String coinCode) {
final key = coinCode.toUpperCase();
if (!_controllers.containsKey(key)) {
_controllers[key] = StreamController<KlineCandle>.broadcast();
}
_doSubscribe(key);
return _controllers[key]!.stream;
}
/// 取消订阅
void unsubscribe(String coinCode) {
final key = coinCode.toUpperCase();
_subscriptions[key]?.call();
_subscriptions.remove(key);
}
/// 连接状态
bool get isConnected => _isConnected;
/// 连接 WebSocket
void connect() {
if (_isConnecting || _isConnected) return;
_isConnecting = true;
final wsUrl = ApiEndpoints.klineWs;
_stompClient = StompClient(
config: StompConfig(
url: wsUrl,
onConnect: _onConnect,
onDisconnect: _onDisconnect,
onStompError: _onError,
onWebSocketError: _onError,
reconnectDelay: const Duration(milliseconds: 5000),
heartbeatIncoming: const Duration(seconds: 20),
heartbeatOutgoing: const Duration(seconds: 20),
),
);
_stompClient!.activate();
}
/// 断开连接
void disconnect() {
_isConnecting = false;
_stompClient?.deactivate();
_stompClient = null;
_isConnected = false;
_subscriptions.clear();
}
void _onConnect(StompFrame frame) {
_isConnected = true;
_isConnecting = false;
_reconnectDelay = 2000; // 重置重连延迟
// 重新订阅所有已注册的币种
for (final key in _controllers.keys) {
_doSubscribe(key);
}
}
void _onDisconnect(StompFrame? frame) {
_isConnected = false;
_isConnecting = false;
_scheduleReconnect();
}
void _onError(dynamic error) {
_isConnected = false;
_isConnecting = false;
_scheduleReconnect();
}
void _scheduleReconnect() {
Future.delayed(Duration(milliseconds: _reconnectDelay), () {
if (!_isConnected && !_isConnecting) {
_reconnectDelay = (_reconnectDelay * 2).clamp(2000, _maxReconnectDelay);
connect();
}
});
}
void _doSubscribe(String coinCode) {
if (_stompClient == null || !_isConnected) {
connect(); // 触发连接,连接成功后会自动重新订阅
return;
}
// 避免重复订阅
if (_subscriptions.containsKey(coinCode)) return;
final dest = '/topic/kline/$coinCode';
final sub = _stompClient!.subscribe(
destination: dest,
callback: (StompFrame frame) {
if (frame.body != null) {
try {
final json = jsonDecode(frame.body!) as Map<String, dynamic>;
final candle = KlineCandle.fromJson(json);
_controllers[coinCode]?.add(candle);
} catch (_) {}
}
},
);
_subscriptions[coinCode] = sub;
}
/// 释放资源
void dispose() {
disconnect();
for (final controller in _controllers.values) {
controller.close();
}
_controllers.clear();
}
}