Files
app_flutter/lib/providers/api.dart
2025-06-27 20:20:51 +08:00

147 lines
4.1 KiB
Dart

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<CookieNotifier, Map<String, CookieEntry>>((ref) {
return CookieNotifier();
});
final cookieHeaderProvider = Provider<String>((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<ApiClient>((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<String, String> _buildHeaders({Map<String, String>? extra}) {
final cookieHeader = ref.read(cookieHeaderProvider);
return {
'Content-Type': 'application/json',
if (cookieHeader.isNotEmpty) 'Cookie': cookieHeader,
...?extra,
};
}
Future<http.Response> _send(Future<http.Response> 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<http.Response> get(String path, {Map<String, String>? headers}) {
return _send((client) => client.get(
Uri.parse('$baseUrl/$path'),
headers: _buildHeaders(extra: headers),
));
}
Future<http.Response> post(String path, {Map<String, String>? 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<Map<String, CookieEntry>> {
CookieNotifier() : super({}) {
_load();
}
Future<void> _load() async {
final prefs = await SharedPreferences.getInstance();
final list = prefs.getStringList(_cookieKey) ?? [];
final map = <String, CookieEntry>{};
for (final item in list) {
final split = item.split('|');
if (split.length == 2) {
final key = split[0];
final valueMap = Map<String, dynamic>.from(jsonDecode(split[1]));
map[key] = CookieEntry.fromJson(valueMap);
}
}
state = map;
}
Future<void> _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<void> setAll(Map<String, CookieEntry> map) async {
state = Map<String, CookieEntry>.from(map);
await _persist();
}
Future<void> clear() async {
state = {};
final prefs = await SharedPreferences.getInstance();
await prefs.remove(_cookieKey);
}
Future<void> updateFromSetCookie(List<String> setCookies) async {
final updated = Map<String, CookieEntry>.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');
}