Flutter工具类

Flutter工具类

 次点击
39 分钟阅读

相关链接

https://blog.csdn.net/2501_94507526/article/details/155642279https://juejin.cn/post/7478558847411896360#heading-5

所需依赖项

  shared_preferences: ^2.5.4
  dio: ^5.9.0
  connectivity_plus: ^7.0.0

完整代码

系统配置

lib/utils/config.dart

import 'package:shared_preferences/shared_preferences.dart';

enum APPENVMODE { dev, prod, local }

class AppConfig {
  static const String appName = "Flutter Demo";
  static const String appVersion = "1.0.0";

  static const String _localServer = "";
  static const String _devServer = "yycc.hc8.ren";
  static const String _prodServer = "";

  static const APPENVMODE _appEnvMode = APPENVMODE.dev;
  static const String _urlSuffix = "/home";
  static const String _wsUrlSuffix = "/socket?authorization=";

  // baseUrl Api接口地址
  String baseUrl() {
    switch (_appEnvMode) {
      case APPENVMODE.dev:
        return "https://${_devServer}${_urlSuffix}";
      case APPENVMODE.prod:
        return "https://${_prodServer}${_urlSuffix}";
      default:
        return "http://${_localServer}${_urlSuffix}";
    }
  }

  // staticUrl 静态资源地址
  String staticUrl(){
    switch (_appEnvMode) {
      case APPENVMODE.dev:
        return "https://${_devServer}";
      case APPENVMODE.prod:
        return "https://${_prodServer}";
      default:
        return "http://${_localServer}";
    }
  }

  // wsUrl WebSocket接口地址
  String wsUrl() {
    switch (_appEnvMode) {
      case APPENVMODE.dev:
        return "wss://${_devServer}${_wsUrlSuffix}${AppTokenOperate.getToken()}";
      case APPENVMODE.prod:
        return "wss://${_prodServer}${_wsUrlSuffix}${AppTokenOperate.getToken()}";
      default:
        return "ws://${_localServer}${_wsUrlSuffix}${AppTokenOperate.getToken()}";
    }
  }
}


enum AppTokenType {
  accessToken,
  userInfo,
  appConfig,
  userPermission
}

class AppTokenOperate {
  static const Map<AppTokenType,String> appTokenName = {
    AppTokenType.accessToken: "yycc_erp_app_access_token",
    AppTokenType.userInfo: "yycc_erp_app_user_info",
    AppTokenType.appConfig: "yycc_erp_app_config",
    AppTokenType.userPermission: "yycc_erp_app_user_permission"
  };
  // getToken 获取token
  static Future<dynamic> getToken({AppTokenType tokenType=AppTokenType.accessToken}) async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    return prefs.getString(appTokenName[tokenType]!);
  }
  // setToken 设置token
  static Future<bool> setToken({required String token, AppTokenType tokenType=AppTokenType.accessToken}) async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    return prefs.setString(appTokenName[tokenType]!, token);
  }
  // removeToken 删除token
  static Future<bool> removeToken({AppTokenType tokenType=AppTokenType.accessToken}) async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    return prefs.remove(appTokenName[tokenType]!);
  }

  // isLogin 判断是否登录
  static Future<bool> isLogin() async {
    String? token = await getToken();
    if (token == null) {
      removeToken();
      removeToken(tokenType: AppTokenType.userInfo);
      return false;
    }else{
      return true;
    }
  }

  // logout 退出登录
  static Future<void> logout({bool msg=false}) async {
    String? token = await getToken();
    if(token!=null){
      // TODO:退出登录接口调用
    }
    await removeToken();
    await removeToken(tokenType: AppTokenType.userInfo);
    if(msg){
      // TODO:退出登录提示
      return;
    }
    // TODO:退出登录跳转
  }

}

工具类

lib/utils/utils.dart

class AppUtils {

  // getDateDiff 时间戳转多少分钟之前(带异常处理)
  String getDateDiff(String dateTimeStamp) {
    // 解析时间
    DateTime? parsedTime = DateTime.tryParse(dateTimeStamp);
    if (parsedTime == null) {
      throw FormatException("日期格式错误: $dateTimeStamp");
    }

    int timestamp = parsedTime.millisecondsSinceEpoch;
    int now = DateTime.now().millisecondsSinceEpoch;
    int diffValue = now - timestamp;

    if (diffValue < 0) {
      throw FormatException("时间不能是未来时间: $dateTimeStamp");
    }

    const minute = 1000 * 60;
    const hour = minute * 60;
    const day = hour * 24;
    const month = day * 30;
    const year = day * 365;

    double yearC = diffValue / year;
    double monthC = diffValue / month;
    double weekC = diffValue / (7 * day);
    double dayC = diffValue / day;
    double hourC = diffValue / hour;
    double minC = diffValue / minute;

    if (yearC >= 1) {
      return "${yearC.toInt()}年前";
    } else if (monthC >= 1) {
      return "${monthC.toInt()}个月前";
    } else if (weekC >= 1) {
      return "${weekC.toInt()}周前";
    } else if (dayC >= 1) {
      return "${dayC.toInt()}天前";
    } else if (hourC >= 1) {
      return "${hourC.toInt()}小时前";
    } else if (minC >= 1) {
      return "${minC.toInt()}分钟前";
    } else {
      return "刚刚";
    }
  }

}

请求封装

lib/request/request.dart

import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter_test_2/utils/config.dart';

/// 网络请求方法枚举
enum HttpMethod { get, post, put, delete }

/// 常量配置
class HttpConstants {
  static const int timeout = 10; // 秒
  static const int cacheExpireMinutes = 30; // 分钟
}

/// 统一接口响应对象
class ApiResponse<T> {
  final int code;
  final String message;
  final T? data;

  ApiResponse({required this.code, required this.message, this.data});

  factory ApiResponse.fromMap(Map<String, dynamic> map) {
    return ApiResponse(
      code: map['code'] ?? -1,
      message: map['message'] ?? '',
      data: map['data'],
    );
  }
}

/// 网络请求管理类(单例)
class HttpManager {
  static HttpManager? _instance;
  late Dio _dio;
  final Connectivity _connectivity = Connectivity();

  HttpManager._internal() {
    _initDio();
    _initInterceptors();
  }

  static HttpManager get instance {
    _instance ??= HttpManager._internal();
    return _instance!;
  }

  /// 初始化 Dio
  void _initDio() {
    _dio = Dio(
      BaseOptions(
        baseUrl: AppConfig().baseUrl(),
        connectTimeout: Duration(seconds: HttpConstants.timeout),
        receiveTimeout: Duration(seconds: HttpConstants.timeout),
        headers: {
          'Content-Type': 'application/json;charset=utf-8',
          'Accept': 'application/json',
        },
        responseType: ResponseType.json,
      ),
    );
  }

  /// 初始化拦截器
  void _initInterceptors() {
    // 仅 debug 打印日志
    if (kDebugMode) {
      _dio.interceptors.add(
        LogInterceptor(
          requestBody: true,
          responseBody: true,
          logPrint: (log) => print('Dio日志:$log'),
        ),
      );
    }

    _dio.interceptors.add(
      InterceptorsWrapper(
        onRequest:
            (RequestOptions options, RequestInterceptorHandler handler) async {
          final sp = await SharedPreferences.getInstance();
          final token = sp.getString('token');
          if (token != null && token.isNotEmpty) {
            options.headers['Authorization'] = token;
          }
          handler.next(options);
        },
        onResponse: (Response response, ResponseInterceptorHandler handler) {
          handler.next(response);
        },
        onError: (DioException error, ErrorInterceptorHandler handler) {
          final errorMsg = _handleError(error);
          handler.reject(
            DioException(
              requestOptions: error.requestOptions,
              error: errorMsg,
              type: error.type,
              response: error.response,
            ),
          );
        },
      ),
    );
  }

  /// 错误统一处理
  String _handleError(DioException error) {
    switch (error.type) {
      case DioExceptionType.connectionTimeout:
        return '网络连接超时,请检查网络';
      case DioExceptionType.sendTimeout:
        return '请求发送超时,请稍后重试';
      case DioExceptionType.receiveTimeout:
        return '响应接收超时,请稍后重试';
      case DioExceptionType.badResponse:
        final statusCode = error.response?.statusCode ?? 0;
        String? msg;
        if (error.response?.data is Map<String, dynamic>) {
          msg = error.response?.data['message'];
        }
        if (statusCode == 401) {
          _clearToken();
          return '登录状态过期,请重新登录';
        } else if (statusCode == 403) {
          return '权限不足,无法访问';
        } else if (statusCode == 404) {
          return '请求接口不存在';
        } else if (statusCode == 500) {
          return '服务器内部错误,请稍后重试';
        } else {
          return '请求失败($statusCode):${msg ?? '未知错误'}';
        }
      case DioExceptionType.cancel:
        return '请求已取消';
      case DioExceptionType.connectionError:
        return '网络连接错误,请检查网络';
      default:
        return '未知错误:${error.message ?? '请稍后重试'}';
    }
  }

  /// 清空 token
  Future<void> _clearToken() async {
    final sp = await SharedPreferences.getInstance();
    await sp.remove('token');
  }

  /// 网络检查
  Future<bool> _checkNetwork() async {
    final result = await _connectivity.checkConnectivity();
    return result != ConnectivityResult.none;
  }

  /// 通用请求
  Future<ApiResponse> request({
    required String path,
    required HttpMethod method,
    Map<String, dynamic>? params,
    Map<String, dynamic>? queryParams,
    bool needCache = false,
    bool forceRefresh = false,
  }) async {
    final hasNetwork = await _checkNetwork();
    if (!hasNetwork) {
      if (needCache) {
        final cacheData = await _getCache(path, method, params);
        if (cacheData != null) return ApiResponse.fromMap(cacheData);
      }
      throw DioException(
        requestOptions: RequestOptions(path: path),
        error: '当前无网络连接,无法完成请求',
        type: DioExceptionType.connectionError,
      );
    }

    if (needCache && !forceRefresh) {
      final cacheData = await _getCache(path, method, params);
      if (cacheData != null) return ApiResponse.fromMap(cacheData);
    }

    Response response;
    try {
      switch (method) {
        case HttpMethod.get:
          response = await _dio.get(path, queryParameters: queryParams ?? params);
          break;
        case HttpMethod.post:
          response = await _dio.post(path, data: params, queryParameters: queryParams);
          break;
        case HttpMethod.put:
          response = await _dio.put(path, data: params, queryParameters: queryParams);
          break;
        case HttpMethod.delete:
          response = await _dio.delete(path, data: params, queryParameters: queryParams);
          break;
      }
    } catch (e) {
      rethrow;
    }

    // 仅缓存 GET 请求
    if (needCache && method == HttpMethod.get && response.statusCode == 200) {
      await _setCache(path, method, params, response.data);
    }

    // 返回安全的 ApiResponse
    if (response.data is! Map<String, dynamic>) {
      throw DioException(
        requestOptions: response.requestOptions,
        error: '返回数据格式异常',
        type: DioExceptionType.badResponse,
      );
    }

    final apiResponse = ApiResponse.fromMap(response.data);
    if (apiResponse.code != 0) {
      throw DioException(
        requestOptions: response.requestOptions,
        error: apiResponse.message,
        type: DioExceptionType.badResponse,
        response: response,
      );
    }
    return apiResponse;
  }

  /// 生成缓存 key
  String _generateCacheKey(String path, HttpMethod method, Map<String, dynamic>? params) {
    final paramsStr = params != null ? json.encode(params) : '';
    return '$method-$path-$paramsStr';
  }

  /// 设置缓存
  Future<void> _setCache(String path, HttpMethod method, Map<String, dynamic>? params, dynamic data) async {
    if (method != HttpMethod.get) return; // 仅缓存 GET
    final sp = await SharedPreferences.getInstance();
    final cacheKey = _generateCacheKey(path, method, params);
    final cacheData = json.encode({
      'data': data,
      'timestamp': DateTime.now().millisecondsSinceEpoch,
    });
    await sp.setString(cacheKey, cacheData);
  }

  /// 获取缓存
  Future<Map<String, dynamic>?> _getCache(String path, HttpMethod method, Map<String, dynamic>? params) async {
    final sp = await SharedPreferences.getInstance();
    final cacheKey = _generateCacheKey(path, method, params);
    final cacheStr = sp.getString(cacheKey);
    if (cacheStr == null) return null;

    final cacheData = json.decode(cacheStr);
    final timestamp = cacheData['timestamp'] as int;
    final now = DateTime.now().millisecondsSinceEpoch;
    final expireTime = HttpConstants.cacheExpireMinutes * 60 * 1000;
    if (now - timestamp > expireTime) {
      await sp.remove(cacheKey);
      return null;
    }
    return cacheData['data'] as Map<String, dynamic>;
  }

  /// 带重试请求(只对网络异常重试)
  Future<ApiResponse> requestWithRetry({
    required String path,
    required HttpMethod method,
    Map<String, dynamic>? params,
    Map<String, dynamic>? queryParams,
    bool needCache = false,
    bool forceRefresh = false,
    int retryCount = 2,
  }) async {
    int currentRetry = 0;
    while (true) {
      try {
        return await request(
          path: path,
          method: method,
          params: params,
          queryParams: queryParams,
          needCache: needCache,
          forceRefresh: forceRefresh,
        );
      } catch (e) {
        if (e is DioException &&
            (e.type == DioExceptionType.connectionError ||
                e.type == DioExceptionType.connectionTimeout ||
                e.type == DioExceptionType.receiveTimeout)) {
          currentRetry++;
          if (currentRetry > retryCount) rethrow;
          await Future.delayed(Duration(milliseconds: 500 * currentRetry));
        } else {
          rethrow; // 业务错误直接抛
        }
      }
    }
  }
}

© 本文著作权归作者所有,未经许可不得转载使用。