import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:http/http.dart' as http; import '../config.dart'; import '../models/class_cookie_entry.dart'; const _cookieKey = AppSharedPreferencesKey.cookieKey; final cookieProvider = StateNotifierProvider>((ref) { return CookieNotifier(); }); final cookieHeaderProvider = Provider((ref) { final cookies = ref.watch(cookieProvider); final now = DateTime.now(); return cookies.entries .where((e) => e.value.expires == null || e.value.expires!.isAfter(now)) .map((e) => e.value.toCookieString(e.key)) .join('; '); }); final apiClientProvider = Provider((ref) { final cookieHeader = ref.watch(cookieHeaderProvider); final client = ApiClient(ref: ref); ref.onDispose(client.dispose); return client; }); class ApiClient { final Ref ref; final http.Client _client; static const String baseUrl = AppConfig.apiBaseUrl; ApiClient({required this.ref, http.Client? client}) : _client = client ?? http.Client(); // 复用或新建 Map _buildHeaders({Map? extra}) { final cookieHeader = ref.read(cookieHeaderProvider); return { 'Content-Type': 'application/json', if (cookieHeader.isNotEmpty) 'Cookie': cookieHeader, ...?extra, }; } Future _send(Future Function(http.Client client) request) async { final response = await request(_client); final setCookie = response.headers['set-cookie']; if (setCookie != null) { await ref.read(cookieProvider.notifier).updateFromSetCookie(setCookie.split(',')); } return response; } Future get(String path, {Map? headers}) { return _send((client) => client.get( Uri.parse('$baseUrl/$path'), headers: _buildHeaders(extra: headers), )); } Future post(String path, {Map? headers, String? body}) { return _send((client) => client.post( Uri.parse('$baseUrl/$path'), headers: _buildHeaders(extra: headers), body: body, )); } void _checkResponse(http.Response res) { if (res.statusCode >= 300) { throw Exception('HTTP ${res.statusCode}: ${res.body}'); } } void dispose() { _client.close(); // 记得释放资源 } } class CookieNotifier extends StateNotifier> { CookieNotifier() : super({}) { _load(); } Future _load() async { final prefs = await SharedPreferences.getInstance(); final list = prefs.getStringList(_cookieKey) ?? []; final map = {}; for (final item in list) { final split = item.split('|'); if (split.length == 2) { final key = split[0]; final valueMap = Map.from(jsonDecode(split[1])); map[key] = CookieEntry.fromJson(valueMap); } } state = map; } Future _persist() async { final prefs = await SharedPreferences.getInstance(); final list = state.entries.map((e) => '${e.key}|${jsonEncode(e.value.toJson())}').toList(); await prefs.setStringList(_cookieKey, list); } Future setAll(Map map) async { state = Map.from(map); await _persist(); } Future clear() async { state = {}; final prefs = await SharedPreferences.getInstance(); await prefs.remove(_cookieKey); } Future updateFromSetCookie(List setCookies) async { final updated = Map.from(state); for (final raw in setCookies) { final parts = raw.split(';'); final kv = parts[0].split('='); if (kv.length < 2) continue; final name = kv[0].trim(); final entry = CookieEntry.fromSetCookie(raw); updated[name] = entry; } state = updated; await _persist(); } bool get isLoggedIn => state.containsKey('auth') || state.containsKey('sessionid'); }