This commit is contained in:
2026-03-23 00:08:19 +08:00
parent ca33e0d3c5
commit 2fdd842b89
11 changed files with 1196 additions and 380 deletions

View File

@@ -0,0 +1,74 @@
/// API 响应状态码
class ResponseCode {
static const String success = '0000';
static const String unauthorized = '0002';
}
/// API 响应模型
class ApiResponse<T> {
final bool success;
final String? message;
final T? data;
final String? code;
ApiResponse({
required this.success,
this.message,
this.data,
this.code,
});
factory ApiResponse.success(T data, [String? message]) {
return ApiResponse(
success: true,
data: data,
message: message,
code: ResponseCode.success,
);
}
factory ApiResponse.fail(String message, [String? code]) {
return ApiResponse(
success: false,
message: message,
code: code,
);
}
factory ApiResponse.unauthorized(String message) {
return ApiResponse(
success: false,
message: message,
code: ResponseCode.unauthorized,
);
}
factory ApiResponse.fromJson(
Map<String, dynamic> json,
T Function(dynamic)? fromJsonT,
) {
final code = json['code'] as String? ?? '';
final msg = json['msg'] as String? ?? '';
return switch (code) {
ResponseCode.success => _parseSuccess(json, msg, fromJsonT),
ResponseCode.unauthorized => ApiResponse.unauthorized(msg),
_ => ApiResponse.fail(msg, code),
};
}
static ApiResponse<T> _parseSuccess<T>(
Map<String, dynamic> json,
String msg,
T Function(dynamic)? fromJsonT,
) {
final data = json['data'];
if (fromJsonT != null && data != null) {
return ApiResponse.success(fromJsonT(data), msg);
}
return ApiResponse.success(data as T, msg);
}
bool get isSuccess => success;
bool get isUnauthorized => code == ResponseCode.unauthorized;
}

View File

@@ -1,65 +1,13 @@
import 'package:dio/dio.dart';
import '../storage/local_storage.dart';
import 'api_exception.dart';
import 'api_response.dart';
/// API 响应模型
class ApiResponse<T> {
final bool success;
final String? message;
final T? data;
final String? code;
ApiResponse({
required this.success,
this.message,
this.data,
this.code,
});
factory ApiResponse.success(T data, [String? message]) {
return ApiResponse(
success: true,
data: data,
message: message,
code: '0000',
);
}
factory ApiResponse.fail(String message, [String? code]) {
return ApiResponse(
success: false,
message: message,
code: code,
);
}
factory ApiResponse.unauthorized(String message) {
return ApiResponse(
success: false,
message: message,
code: '0002',
);
}
factory ApiResponse.fromJson(
Map<String, dynamic> json,
T Function(dynamic)? fromJsonT,
) {
final code = json['code'] as String? ?? '';
final msg = json['msg'] as String? ?? '';
if (code == '0000') {
final data = json['data'];
if (fromJsonT != null && data != null) {
return ApiResponse.success(fromJsonT(data), msg);
}
return ApiResponse.success(data as T, msg);
} else if (code == '0002') {
return ApiResponse.unauthorized(msg);
} else {
return ApiResponse.fail(msg, code);
}
}
/// 网络配置常量
class NetworkConfig {
static const String baseUrl = 'http://localhost:5010';
static const Duration connectTimeout = Duration(seconds: 30);
static const Duration receiveTimeout = Duration(seconds: 30);
}
/// Dio 网络客户端
@@ -67,23 +15,30 @@ class DioClient {
late final Dio _dio;
DioClient() {
_dio = Dio(BaseOptions(
baseUrl: 'http://localhost:5010',
connectTimeout: const Duration(seconds: 30),
receiveTimeout: const Duration(seconds: 30),
headers: {
'Content-Type': 'application/json',
},
));
_dio = _createDio();
_setupInterceptors();
}
_dio.interceptors.add(_AuthInterceptor());
_dio.interceptors.add(LogInterceptor(
requestHeader: false,
responseHeader: false,
error: true,
Dio _createDio() {
return Dio(BaseOptions(
baseUrl: NetworkConfig.baseUrl,
connectTimeout: NetworkConfig.connectTimeout,
receiveTimeout: NetworkConfig.receiveTimeout,
headers: {'Content-Type': 'application/json'},
));
}
void _setupInterceptors() {
_dio.interceptors.addAll([
_AuthInterceptor(),
LogInterceptor(
requestHeader: false,
responseHeader: false,
error: true,
),
]);
}
/// GET 请求
Future<ApiResponse<T>> get<T>(
String path, {
@@ -112,7 +67,6 @@ class DioClient {
}
}
/// 处理响应
ApiResponse<T> _handleResponse<T>(
Response response,
T Function(dynamic)? fromJson,
@@ -124,13 +78,39 @@ class DioClient {
return ApiResponse.fail('响应数据格式错误');
}
/// 处理错误
ApiResponse<T> _handleError<T>(DioException e) {
if (e.response?.statusCode == 401) {
LocalStorage.clearUserData();
if (_isUnauthorized(e)) {
_clearUserData();
return ApiResponse.unauthorized('登录已过期,请重新登录');
}
return ApiResponse.fail(e.message ?? '网络请求失败');
final message = _getErrorMessage(e);
return ApiResponse.fail(message);
}
bool _isUnauthorized(DioException e) {
return e.response?.statusCode == 401;
}
void _clearUserData() {
LocalStorage.clearUserData();
}
String _getErrorMessage(DioException e) {
switch (e.type) {
case DioExceptionType.connectionTimeout:
return '连接超时,请检查网络';
case DioExceptionType.sendTimeout:
return '发送超时,请重试';
case DioExceptionType.receiveTimeout:
return '响应超时,请重试';
case DioExceptionType.connectionError:
return '网络连接失败';
case DioExceptionType.badResponse:
return '服务器错误 (${e.response?.statusCode})';
default:
return e.message ?? '网络请求失败';
}
}
}
@@ -139,7 +119,7 @@ class _AuthInterceptor extends Interceptor {
@override
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
final token = LocalStorage.getToken();
if (token != null && token.isNotEmpty) {
if (token?.isNotEmpty == true) {
options.headers['Authorization'] = 'Bearer $token';
}
super.onRequest(options, handler);