import 'dart:convert'; import 'dart:io'; 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 get(String path, {bool authenticated = true}) => _send('GET', path, authenticated: authenticated); Future post( String path, { Map? body, bool authenticated = true, }) => _send('POST', path, body: body, authenticated: authenticated); Future put(String path, {Map? body}) => _send('PUT', path, body: body); Future upload(String filePath, {String contentType = 'image/jpeg'}) async { final request = http.MultipartRequest('POST', Uri.parse('$baseUrl/upload/file')); request.headers[HttpHeaders.authorizationHeader] = _tokenProvider(); request.fields['declared_content_type'] = contentType; request.files.add(await http.MultipartFile.fromPath('file', filePath)); final response = await http.Response.fromStream(await request.send()); final details = _decode(response); return jsonMap(details)['uri'] as String? ?? ''; } Future _send( String method, String path, { Map? body, bool authenticated = true, }) async { final request = http.Request(method, Uri.parse('$baseUrl$path')); request.headers[HttpHeaders.acceptHeader] = 'application/json'; if (authenticated && _tokenProvider().isNotEmpty) { request.headers[HttpHeaders.authorizationHeader] = _tokenProvider(); } if (body != null) { request.headers[HttpHeaders.contentTypeHeader] = 'application/json; charset=UTF-8'; request.body = jsonEncode(body); } final response = await http.Response.fromStream(await _client.send(request)); return _decode(response); } Object? _decode(http.Response response) { if (response.statusCode < 200 || response.statusCode >= 300) { throw ApiException(response.statusCode, '网络请求失败(${response.statusCode})'); } final decoded = jsonDecode(response.body); if (decoded is! Map) { 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 jsonMap(Object? value) { if (value is Map) return value; if (value is Map) return value.map((key, item) => MapEntry(key.toString(), item)); throw const ApiException(500, '服务端数据格式错误'); } List> jsonList(Object? value) { if (value is! List) return const []; return value.map>(jsonMap).toList(growable: false); }