147 lines
4.1 KiB
Dart
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');
|
|
}
|