// auth_api.dart — 控制面认证 HTTP 客户端 // // 职责:封装 /v1/auth/* 接口调用。 // 关键约束:所有 HTTP 错误统一包装为 AuthApiException, // UI 层通过 e.statusCode / e.messageZh 显示错误文案。 import 'dart:convert'; import 'package:http/http.dart' as http; /// 认证接口调用失败时抛出。 /// [statusCode] < 0 表示网络层错误。 class AuthApiException implements Exception { const AuthApiException({ required this.statusCode, required this.messageZh, required this.messageEn, }); final int statusCode; final String messageZh; final String messageEn; @override String toString() => 'AuthApiException($statusCode): $messageZh'; } /// 登录 / 注册成功后返回的 JWT 令牌对。 class AuthTokens { const AuthTokens({required this.accessToken, required this.refreshToken}); final String accessToken; final String refreshToken; factory AuthTokens.fromJson(Map m) => AuthTokens( accessToken: m['access_token'] as String? ?? '', refreshToken: m['refresh_token'] as String? ?? '', ); } /// [AuthApi] 封装 /v1/auth 接口族。 class AuthApi { AuthApi({required this.baseUrl, http.Client? client}) : _client = client ?? http.Client(); final String baseUrl; final http.Client _client; // ── 发送验证码:POST /v1/auth/code ───────────────────────────── /// 向 [email] 发送 6 位验证码。成功无返回,失败抛 [AuthApiException]。 Future sendCode(String email) async { final resp = await _post('/v1/auth/code', {'email': email}); if (resp.statusCode != 204 && resp.statusCode != 200) { _throwFromResponse(resp); } } // ── 注册:POST /v1/auth/register ─────────────────────────────── Future register({ required String email, required String code, required String password, }) async { final resp = await _post('/v1/auth/register', { 'email': email, 'code': code, 'password': password, }); if (resp.statusCode != 200 && resp.statusCode != 201) { _throwFromResponse(resp); } return AuthTokens.fromJson(jsonDecode(resp.body) as Map); } // ── 登录:POST /v1/auth/login ─────────────────────────────────── Future login({ required String email, required String password, }) async { final resp = await _post('/v1/auth/login', { 'email': email, 'password': password, }); if (resp.statusCode != 200) _throwFromResponse(resp); return AuthTokens.fromJson(jsonDecode(resp.body) as Map); } // ── 刷新 token:POST /v1/auth/refresh ────────────────────────── Future refresh(String refreshToken) async { final resp = await _post('/v1/auth/refresh', {'refresh_token': refreshToken}); if (resp.statusCode != 200) _throwFromResponse(resp); return AuthTokens.fromJson(jsonDecode(resp.body) as Map); } // ── 内部 ──────────────────────────────────────────────────────── Future _post(String path, Map body) async { final uri = Uri.parse('$baseUrl$path'); try { return await _client .post( uri, headers: {'Content-Type': 'application/json'}, body: jsonEncode(body), ) .timeout(const Duration(seconds: 15)); } on Exception catch (e) { throw AuthApiException( statusCode: -1, messageZh: '网络请求失败,请检查连接后重试', messageEn: 'Network error: $e', ); } } void _throwFromResponse(http.Response resp) { String messageZh = '操作失败 (HTTP ${resp.statusCode})'; String messageEn = 'Request failed (HTTP ${resp.statusCode})'; try { final body = jsonDecode(resp.body) as Map; messageZh = body['message_zh'] as String? ?? messageZh; messageEn = body['message_en'] as String? ?? messageEn; } catch (_) {} throw AuthApiException( statusCode: resp.statusCode, messageZh: messageZh, messageEn: messageEn, ); } void dispose() => _client.close(); }