import 'dart:convert'; import 'package:dio/dio.dart'; abstract class ApiException implements Exception { final String message; final int? statusCode; const ApiException(this.message, {this.statusCode}); @override String toString() => message; factory ApiException.fromDioError(Object error) { if (error is ApiException) return error; if (error is DioException) { final response = error.response; final statusCode = response?.statusCode; final data = response?.data; String detail; final decodedData = _normalizeErrorData(data); if (decodedData is Map) { detail = (decodedData['detail'] ?? decodedData['message'] ?? decodedData['error']) ?.toString() ?? '请求失败'; } else { detail = _networkErrorMessage(error); } final localized = _localizeError(detail, statusCode); if (statusCode == 401) { return UnauthorizedException(localized); } if (statusCode == 422) { return ValidationException( localized, errors: data, statusCode: statusCode, ); } return ServerException(localized, statusCode: statusCode); } return const ServerException('网络错误'); } static Map? _normalizeErrorData(dynamic data) { if (data is Map) { return data; } if (data is Map) { return data.map((key, value) => MapEntry(key.toString(), value)); } if (data is String && data.trim().isNotEmpty) { try { final decoded = jsonDecode(data); if (decoded is Map) { return decoded; } if (decoded is Map) { return decoded.map((key, value) => MapEntry(key.toString(), value)); } } catch (_) { return null; } } return null; } static String _localizeError(String detail, int? statusCode) { if (statusCode == 403) { return '没有权限执行此操作'; } if (statusCode == 404) { return '请求的资源不存在'; } if (statusCode == 429) { final normalized = detail.trim(); if (normalized.isEmpty || normalized == '请求失败') { return '请求过于频繁,请稍后再试'; } return detail; } if (statusCode != null && statusCode >= 500) { return '服务器错误,请稍后再试'; } return detail; } static String _networkErrorMessage(DioException error) { if (error.type == DioExceptionType.connectionTimeout || error.type == DioExceptionType.sendTimeout || error.type == DioExceptionType.receiveTimeout) { return '网络超时,请确认手机与服务端在同一网络后重试'; } if (error.type == DioExceptionType.connectionError || error.type == DioExceptionType.unknown) { return '无法连接服务器。请在 iPhone 设置中为本应用开启“无线数据(WLAN与蜂窝网络)”,并确认本地网络权限已开启。'; } return '请求失败'; } } class ServerException extends ApiException { const ServerException(super.message, {super.statusCode}); } class UnauthorizedException extends ApiException { const UnauthorizedException([super.message = '请重新登录']) : super(statusCode: 401); } class ValidationException extends ApiException { final Map? errors; const ValidationException(super.message, {this.errors, super.statusCode}); }