Initial commit

This commit is contained in:
admin1
2026-07-14 11:11:36 +08:00
commit 656499cf94
604 changed files with 119518 additions and 0 deletions
@@ -0,0 +1,119 @@
import 'package:dio/dio.dart';
import '../../domain/errors.dart';
import '../../contracts/error.dart';
import '../../domain/ports/server_sync_gateway.dart';
import '../../../shared/utils/app_logger.dart';
import '../../../shared/utils/url_utils.dart';
class ServerSyncClient implements ServerSyncGateway {
static const _tag = 'ServerSync';
final Dio _dio;
ServerSyncClient({Dio? dio})
: _dio =
dio ??
Dio(
BaseOptions(
connectTimeout: const Duration(seconds: 8),
receiveTimeout: const Duration(seconds: 15),
sendTimeout: const Duration(seconds: 15),
headers: {'Accept': 'application/json'},
validateStatus: (status) => status != null && status < 500,
),
);
@override
Future<ServerSyncSnapshot> pull({
required String baseUrl,
required String accessToken,
}) async {
final url = '${stripTrailingSlash(baseUrl.trim())}/api/user/server-sync';
try {
final res = await _dio.get<Map<String, dynamic>>(
url,
options: Options(headers: {'Authorization': 'Bearer $accessToken'}),
);
final status = res.statusCode ?? 0;
if (!_isSuccess(status)) {
throw _httpError(status, res.data);
}
final body = res.data ?? const <String, dynamic>{};
return ServerSyncSnapshot(blob: body['blob'] as String?);
} on DioException catch (e) {
throw _networkError(e);
}
}
@override
Future<void> push({
required String baseUrl,
required String accessToken,
required String blob,
}) async {
final url = '${stripTrailingSlash(baseUrl.trim())}/api/user/server-sync';
try {
final res = await _dio.put<Map<String, dynamic>>(
url,
data: {'blob': blob},
options: Options(headers: {'Authorization': 'Bearer $accessToken'}),
);
final status = res.statusCode ?? 0;
if (!_isSuccess(status)) {
throw _httpError(status, res.data);
}
} on DioException catch (e) {
throw _networkError(e);
}
}
DomainError _httpError(int status, Map<String, dynamic>? body) {
final message = (body?['message'] as String?)?.trim();
AppLogger.warn(_tag, 'HTTP $status ${message ?? ''}');
return switch (status) {
401 || 403 => DomainError(
ErrorCode.authForbidden,
message ?? '账号登录已过期,请重新登录',
details: {'status': status},
),
413 => DomainError(
ErrorCode.invalidParams,
message ?? '同步数据过大(上限 256KB',
details: {'status': status},
),
400 => DomainError(
ErrorCode.invalidParams,
message ?? '请求无效',
details: {'status': status},
),
_ => DomainError(
ErrorCode.serverHttpError,
message ?? '同步失败(HTTP $status',
retryable: true,
details: {'status': status},
),
};
}
DomainError _networkError(DioException e) {
AppLogger.warn(_tag, 'network error', e);
return switch (e.type) {
DioExceptionType.connectionTimeout ||
DioExceptionType.receiveTimeout ||
DioExceptionType.sendTimeout => DomainError(
ErrorCode.serverTimeout,
'连接超时,请检查网络',
retryable: true,
),
_ => DomainError(
ErrorCode.serverUnreachable,
'无法连接服务器,请检查网络',
retryable: true,
),
};
}
static bool _isSuccess(int status) => status >= 200 && status < 300;
}