89 lines
2.7 KiB
Dart
89 lines
2.7 KiB
Dart
import 'dart:convert';
|
||
|
||
import 'package:http/http.dart' as http;
|
||
|
||
class ApiException implements Exception {
|
||
const ApiException(this.code, this.message);
|
||
|
||
final int code;
|
||
final String message;
|
||
|
||
@override
|
||
String toString() => message;
|
||
}
|
||
|
||
class ApiClient {
|
||
ApiClient(
|
||
this._tokenProvider, {
|
||
http.Client? client,
|
||
String? baseUrl,
|
||
}) : _client = client ?? http.Client(),
|
||
baseUrl =
|
||
baseUrl ??
|
||
const String.fromEnvironment(
|
||
'API_BASE_URL',
|
||
defaultValue: 'http://10.0.2.2:12426',
|
||
);
|
||
|
||
final String baseUrl;
|
||
final String Function() _tokenProvider;
|
||
final http.Client _client;
|
||
|
||
Future<Object?> get(String path, {bool authenticated = true}) =>
|
||
_send('GET', path, authenticated: authenticated);
|
||
|
||
Future<Object?> post(
|
||
String path, {
|
||
Map<String, Object?>? body,
|
||
bool authenticated = true,
|
||
}) => _send('POST', path, body: body, authenticated: authenticated);
|
||
|
||
Future<Object?> put(String path, {Map<String, Object?>? body}) => _send('PUT', path, body: body);
|
||
|
||
Future<Object?> delete(String path, {Map<String, Object?>? body}) =>
|
||
_send('DELETE', path, body: body);
|
||
|
||
Future<Object?> _send(
|
||
String method,
|
||
String path, {
|
||
Map<String, Object?>? body,
|
||
bool authenticated = true,
|
||
}) async {
|
||
final request = http.Request(method, Uri.parse('$baseUrl$path'));
|
||
request.headers['accept'] = 'application/json';
|
||
if (authenticated) {
|
||
final token = _tokenProvider();
|
||
if (token.isNotEmpty) request.headers['authorization'] = token;
|
||
}
|
||
if (body != null) {
|
||
request.headers['content-type'] = 'application/json; charset=UTF-8';
|
||
request.body = jsonEncode(body);
|
||
}
|
||
final streamed = await _client.send(request);
|
||
final response = await http.Response.fromStream(streamed);
|
||
if (response.statusCode < 200 || response.statusCode >= 300) {
|
||
throw ApiException(response.statusCode, '网络请求失败(${response.statusCode})');
|
||
}
|
||
final decoded = jsonDecode(response.body);
|
||
if (decoded is! Map<String, Object?>) {
|
||
throw const ApiException(500, '服务端响应格式错误');
|
||
}
|
||
final code = (decoded['code'] as num?)?.toInt() ?? 500;
|
||
if (code != 0) {
|
||
throw ApiException(code, decoded['message'] as String? ?? '操作失败');
|
||
}
|
||
return decoded['details'];
|
||
}
|
||
}
|
||
|
||
Map<String, Object?> jsonMap(Object? value) {
|
||
if (value is Map<String, Object?>) return value;
|
||
if (value is Map) return value.map((key, item) => MapEntry(key.toString(), item));
|
||
throw const ApiException(500, '服务端数据格式错误');
|
||
}
|
||
|
||
List<Map<String, Object?>> jsonList(Object? value) {
|
||
if (value is! List) return const [];
|
||
return value.map<Map<String, Object?>>(jsonMap).toList(growable: false);
|
||
}
|