Flutter for OpenHarmony 电子合同签署App实战 - API集成实现

API集成是应用与后端服务通信的关键。这个功能需要提供安全的API调用机制、错误处理、请求拦截和缓存管理。在这篇文章中,我们将详细讲解如何实现一个功能完整、高效可靠的API集成系统。

API集成的设计目标

API集成功能需要实现以下设计目标:

  1. 安全的通信:使用HTTPS加密通信,添加认证信息,确保数据传输的安全性
  2. 错误处理:完善的错误处理机制,包括网络错误、服务器错误、超时等各种情况
  3. 请求拦截:使用拦截器添加认证、日志记录等功能,统一管理所有请求
  4. 缓存机制:缓存API响应,减少不必要的网络请求,提高应用性能
  5. 超时设置:设置合理的超时时间,防止请求无限期等待
  6. 重试机制:失败请求自动重试,提高可靠性
  7. 请求队列:管理并发请求,避免过多的同时请求

HTTP客户端的选择与配置

在实际应用中,我们使用dio包来进行HTTP请求。dio包提供了更多的功能,比如拦截器、超时设置、请求取消等。相比原生的http包,dio提供了更强大的功能和更灵活的配置选项。首先,我们需要在pubspec.yaml中添加依赖。这样可以确保我们能够使用dio包提供的所有功能。

首先在pubspec.yaml中添加依赖。这一步是API集成的基础,我们需要引入必要的第三方库来支持HTTP通信、屏幕适配和状态管理。dio是一个功能强大的HTTP客户端库,提供了拦截器、超时设置、请求取消等高级功能。flutter_screenutil用于处理不同屏幕尺寸的适配,确保应用在各种设备上都能正常显示。get是一个轻量级的状态管理和路由框架,可以简化应用的状态管理逻辑。通过这些包的组合,我们可以构建一个完整、高效的API集成系统。

yaml 复制代码
dependencies:
  dio: ^5.0.0
  flutter_screenutil: ^5.9.0
  get: ^4.6.0

这些依赖包括了HTTP客户端、屏幕适配和状态管理框架。通过这些包,我们可以构建一个完整的API集成系统。

API客户端的单例模式实现

为了确保全局只有一个Dio实例,我们使用单例模式来创建API客户端。单例模式是一种经典的设计模式,确保一个类在整个应用生命周期中只有一个实例。这样做的好处是可以共享连接池,提高HTTP请求的性能,同时也便于管理全局的拦截器和配置。通过单例模式,我们避免了创建多个HTTP客户端实例导致的资源浪费。在API集成中,使用单例模式可以确保所有的HTTP请求都使用同一个连接池,从而提高网络通信的效率和稳定性。

dart 复制代码
class ApiClient {
  static final ApiClient _instance = ApiClient._internal();
  late Dio _dio;
  
  factory ApiClient() {
    return _instance;
  }

  ApiClient._internal() {
    _initDio();
  }
}

这个单例模式确保了全局只有一个Dio实例。通过工厂构造函数,我们可以在任何地方获取同一个实例。这样可以确保所有的HTTP请求都使用同一个连接池,提高性能。

Dio的初始化配置

Dio的初始化是API集成的第一步,也是最关键的一步。我们需要配置基础URL、超时时间、内容类型等参数。这些配置对所有请求都适用,避免了在每个请求中重复配置。通过BaseOptions,我们可以设置所有请求的默认选项,包括连接超时、接收超时和发送超时。合理的超时设置可以防止请求无限期地等待,提高应用的响应性。这样可以确保所有请求都有一致的配置,并且能够在网络不稳定的情况下快速失败并重试。

dart 复制代码
void _initDio() {
  _dio = Dio(
    BaseOptions(
      baseUrl: 'https://api.example.com',
      connectTimeout: const Duration(seconds: 30),
      receiveTimeout: const Duration(seconds: 30),
      sendTimeout: const Duration(seconds: 30),
      contentType: 'application/json',
      responseType: ResponseType.json,
    ),
  );
}

这个配置设置了基础URL、超时时间和内容类型。通过这些配置,我们可以确保所有请求都有一致的行为。超时时间的设置可以防止请求无限期地等待。

请求拦截器的实现

请求拦截器是API集成中非常重要的一部分,它允许我们在每个请求发送前进行统一的处理。通过拦截器,我们可以自动添加认证令牌、请求ID、自定义请求头等信息,确保所有请求都包含必要的元数据。这样可以避免在每个API调用中重复添加这些信息,提高代码的可维护性。请求拦截器还可以用于请求日志记录、请求参数验证等功能。通过拦截器,我们可以在一个地方管理所有请求的共同逻辑,实现关注点分离的设计原则。

dart 复制代码
_dio.interceptors.add(
  InterceptorsWrapper(
    onRequest: (options, handler) {
      final token = _getAuthToken();
      if (token != null) {
        options.headers['Authorization'] = 'Bearer $token';
      }
      options.headers['X-Request-ID'] = _generateRequestId();
      return handler.next(options);
    },
  ),
);

在请求拦截器中,我们首先获取存储的认证令牌,然后将其添加到请求头中。这样每个请求都会自动包含认证信息,无需在每个API调用中重复添加。同时,我们生成一个唯一的请求ID,用于在日志中追踪请求。

响应拦截器和错误处理

响应拦截器用于处理API响应,是API集成中的关键环节。我们可以在这里检查响应状态码,处理错误响应,或者对响应数据进行统一的处理。响应拦截器是处理API响应的关键部分,它允许我们在一个地方处理所有响应,避免在每个API调用中重复处理。通过拦截器,我们可以实现统一的错误处理策略、响应数据转换、日志记录等功能。这样可以提高代码的可维护性和一致性,同时也便于调试和监控应用的API调用。

dart 复制代码
_dio.interceptors.add(
  InterceptorsWrapper(
    onResponse: (response, handler) {
      if (kDebugMode) {
        print('Response: ${response.statusCode}');
      }
      return handler.next(response);
    },
    onError: (error, handler) {
      if (kDebugMode) {
        print('Error: ${error.message}');
      }
      return handler.next(error);
    },
  ),
);

在响应拦截器中,我们记录响应状态码和错误信息。这对于调试和监控应用的API调用非常有帮助。通过日志,我们可以快速定位问题所在。

日志拦截器的添加

为了更好地调试和监控API调用,我们添加一个日志拦截器。这个拦截器会记录所有请求和响应的详细信息,包括请求头、请求体、响应体等。日志拦截器对于调试API问题非常有用,通过详细的日志信息,我们可以快速定位问题所在。在开发阶段,我们可以启用详细的日志记录,在生产环境中,我们可以根据需要调整日志级别,以避免过多的日志输出影响应用性能。通过日志,我们可以看到请求的详细信息、响应数据和错误信息,这对于性能分析和问题诊断非常有帮助。

dart 复制代码
_dio.interceptors.add(
  LogInterceptor(
    requestBody: true,
    responseBody: true,
    error: true,
    logPrint: (object) {
      if (kDebugMode) {
        print(object);
      }
    },
  ),
);

日志拦截器会记录请求体、响应体和错误信息。这对于调试API问题非常有用。在生产环境中,我们可以根据需要调整日志级别。

API服务类的设计

现在让我们创建一个API服务类来处理具体的API调用。这个类会封装所有与合同相关的API操作,提供一个清晰的接口供应用的其他部分使用。API服务类是应用与后端API之间的桥梁,通过API服务类,我们可以将API调用逻辑与UI逻辑分离。这样可以使代码更加清晰和易于维护,同时也便于单元测试和代码复用。API服务类还可以处理数据转换、错误处理等逻辑,提供一个统一的数据访问接口。

dart 复制代码
class ContractApiService {
  final ApiClient _apiClient = ApiClient();

  Future<List<ContractModel>> getContracts({
    int page = 1,
    int pageSize = 20,
    String? status,
  }) async {
    try {
      final response = await _apiClient.dio.get(
        '/contracts',
        queryParameters: {
          'page': page,
          'pageSize': pageSize,
          if (status != null) 'status': status,
        },
      );
      if (response.statusCode == 200) {
        final data = response.data as Map<String, dynamic>;
        final contracts = (data['data'] as List)
            .map((item) => ContractModel.fromJson(item))
            .toList();
        return contracts;
      } else {
        throw ApiException(
          message: 'Failed to fetch contracts',
          statusCode: response.statusCode,
        );
      }
    } on DioException catch (e) {
      throw _handleDioException(e);
    }
  }
}

这个方法展示了如何获取合同列表。我们使用查询参数来支持分页和过滤。如果响应状态码是200,我们解析响应数据并返回合同列表。否则,我们抛出一个自定义异常。

获取单个合同详情

获取单个合同的详情是一个常见的操作,通常用于显示合同的详细页面。我们需要根据合同ID来获取详细信息,包括合同的完整内容、签署状态、参与者信息等。通过合同ID,我们可以从服务器获取该合同的所有信息,这样可以确保用户看到的是最新的合同信息。这个操作使用RESTful风格的URL设计,将ID作为路径参数,这样的设计更加清晰和易于理解。通过单个合同详情的获取,我们可以为用户提供完整的合同信息展示。

dart 复制代码
Future<ContractModel> getContractDetail(String contractId) async {
  try {
    final response = await _apiClient.dio.get(
      '/contracts/$contractId'
    );
    if (response.statusCode == 200) {
      return ContractModel.fromJson(response.data);
    } else {
      throw ApiException(
        message: 'Failed to fetch contract detail',
        statusCode: response.statusCode,
      );
    }
  } on DioException catch (e) {
    throw _handleDioException(e);
  }
}

这个方法通过合同ID来获取详细信息。我们使用RESTful风格的URL,将ID作为路径参数。这样的设计更加清晰和易于理解。

创建合同的API调用

创建合同是应用中的重要操作,需要发送POST请求来完成。创建合同时,我们需要发送合同的基本信息到服务器,包括合同标题、内容、参与者等信息。服务器会验证这些信息,然后创建新的合同。如果创建成功,服务器会返回201状态码和新创建的合同信息。这个操作涉及到数据验证、错误处理等重要逻辑。通过创建合同的API,我们可以为用户提供完整的合同创建功能,支持用户快速生成新的合同。

dart 复制代码
Future<ContractModel> createContract(
  ContractCreateRequest request
) async {
  try {
    final response = await _apiClient.dio.post(
      '/contracts',
      data: request.toJson(),
    );
    if (response.statusCode == 201) {
      return ContractModel.fromJson(response.data);
    } else {
      throw ApiException(
        message: 'Failed to create contract',
        statusCode: response.statusCode,
      );
    }
  } on DioException catch (e) {
    throw _handleDioException(e);
  }
}

创建合同时,我们发送POST请求,并将请求数据作为JSON体发送。如果创建成功,服务器会返回201状态码和新创建的合同信息。

更新合同的API调用

更新合同是修改现有合同信息的操作,需要发送PUT请求来完成。更新合同时,我们需要发送修改后的合同信息到服务器,包括更新的标题、内容、参与者等信息。服务器会验证这些信息,然后更新合同。如果更新成功,服务器会返回200状态码和更新后的合同信息。这个操作需要确保只有有权限的用户才能更新合同,同时需要处理并发更新的情况。通过更新合同的API,我们可以为用户提供修改合同信息的功能。

dart 复制代码
Future<ContractModel> updateContract(
  String contractId,
  ContractUpdateRequest request,
) async {
  try {
    final response = await _apiClient.dio.put(
      '/contracts/$contractId',
      data: request.toJson(),
    );
    if (response.statusCode == 200) {
      return ContractModel.fromJson(response.data);
    } else {
      throw ApiException(
        message: 'Failed to update contract',
        statusCode: response.statusCode,
      );
    }
  } on DioException catch (e) {
    throw _handleDioException(e);
  }
}

更新合同时,我们发送PUT请求,并将修改后的数据作为JSON体发送。如果更新成功,服务器会返回200状态码和更新后的合同信息。

删除合同的API调用

删除合同是一个敏感的操作,需要谨慎处理。我们使用DELETE请求来删除合同。删除操作通常返回204(No Content)或200(OK)状态码。我们检查响应状态码,如果不是这两个之一,就抛出异常。这样可以确保删除操作的安全性。

dart 复制代码
Future<void> deleteContract(String contractId) async {
  try {
    final response = await _apiClient.dio.delete(
      '/contracts/$contractId'
    );
    if (response.statusCode != 204 && response.statusCode != 200) {
      throw ApiException(
        message: 'Failed to delete contract',
        statusCode: response.statusCode,
      );
    }
  } on DioException catch (e) {
    throw _handleDioException(e);
  }
}

删除操作通常返回204(No Content)或200(OK)状态码。我们检查响应状态码,如果不是这两个之一,就抛出异常。

签署合同的API调用

签署合同是应用的核心功能。我们需要发送签名数据到服务器。签署合同时,我们发送签名数据到服务器。服务器会验证签名并更新合同状态。这是一个关键的操作,需要确保数据的完整性和安全性。

dart 复制代码
Future<void> signContract(
  String contractId,
  SignatureData signature
) async {
  try {
    final response = await _apiClient.dio.post(
      '/contracts/$contractId/sign',
      data: signature.toJson(),
    );
    if (response.statusCode != 200) {
      throw ApiException(
        message: 'Failed to sign contract',
        statusCode: response.statusCode,
      );
    }
  } on DioException catch (e) {
    throw _handleDioException(e);
  }
}

签署合同时,我们发送签名数据到服务器。服务器会验证签名并更新合同状态。

下载合同文件

下载合同文件是一个常见的需求。我们需要处理文件下载和进度跟踪。下载文件时,我们使用download方法而不是get方法。这样可以更有效地处理大文件。我们还提供了进度回调,允许UI显示下载进度。

dart 复制代码
Future<void> downloadContract(
  String contractId,
  String savePath
) async {
  try {
    await _apiClient.dio.download(
      '/contracts/$contractId/download',
      savePath,
      onReceiveProgress: (received, total) {
        if (total != -1) {
          final progress = (received / total * 100);
          print('Download progress: ${progress.toStringAsFixed(0)}%');
        }
      },
    );
  } on DioException catch (e) {
    throw _handleDioException(e);
  }
}

下载文件时,我们使用download方法而不是get方法。这样可以更有效地处理大文件。我们还提供了进度回调,允许UI显示下载进度。

错误处理机制

完善的错误处理是API集成的重要部分。我们需要将不同类型的错误转换为有意义的异常。错误处理机制可以帮助我们快速定位问题。通过将不同类型的错误转换为自定义异常,我们可以在应用的不同地方统一处理错误。

dart 复制代码
ApiException _handleDioException(DioException e) {
  switch (e.type) {
    case DioExceptionType.connectionTimeout:
      return ApiException(message: 'Connection timeout');
    case DioExceptionType.sendTimeout:
      return ApiException(message: 'Send timeout');
    case DioExceptionType.receiveTimeout:
      return ApiException(message: 'Receive timeout');
    case DioExceptionType.badResponse:
      return ApiException(
        message: e.response?.data['message'] ?? 'Server error',
        statusCode: e.response?.statusCode,
      );
    case DioExceptionType.cancel:
      return ApiException(message: 'Request cancelled');
    default:
      return ApiException(message: 'Unknown error');
  }
}

这个方法将Dio异常转换为自定义的ApiException。这样做的好处是可以统一处理所有类型的错误,提供一致的错误信息给用户。

自定义异常类

我们定义一个自定义异常类来表示API错误。这样可以更清晰地区分API错误和其他类型的错误。自定义异常类包含错误消息和HTTP状态码。这样可以提供更详细的错误信息,帮助调试和用户理解问题。

dart 复制代码
class ApiException implements Exception {
  final String message;
  final int? statusCode;

  ApiException({
    required this.message,
    this.statusCode,
  });

  @override
  String toString() => 'ApiException: $message (Status: $statusCode)';
}

自定义异常类包含错误消息和HTTP状态码。这样可以提供更详细的错误信息。

数据模型的定义

为了确保类型安全,我们定义了数据模型来表示API响应。数据模型提供了类型安全的方式来处理API响应。通过fromJson工厂方法,我们可以轻松地将JSON数据转换为Dart对象。这样可以避免类型错误和数据不一致的问题。

dart 复制代码
class ContractModel {
  final String id;
  final String title;
  final String content;
  final String status;
  final DateTime createdDate;
  final DateTime expiryDate;

  ContractModel({
    required this.id,
    required this.title,
    required this.content,
    required this.status,
    required this.createdDate,
    required this.expiryDate,
  });

  factory ContractModel.fromJson(Map<String, dynamic> json) {
    return ContractModel(
      id: json['id'] as String,
      title: json['title'] as String,
      content: json['content'] as String,
      status: json['status'] as String,
      createdDate: DateTime.parse(json['createdDate'] as String),
      expiryDate: DateTime.parse(json['expiryDate'] as String),
    );
  }
}

数据模型提供了类型安全的方式来处理API响应。通过fromJson工厂方法,我们可以轻松地将JSON数据转换为Dart对象。

在页面中使用API

现在让我们看看如何在页面中使用这个API服务。在页面中,我们创建API服务实例,然后调用相应的方法来获取数据。通过这样的设计,我们可以将API调用逻辑与UI逻辑分离。

dart 复制代码
class ContractListPage extends StatefulWidget {
  const ContractListPage({Key? key}) : super(key: key);

  @override
  State<ContractListPage> createState() => _ContractListPageState();
}

class _ContractListPageState extends State<ContractListPage> {
  final ContractApiService _apiService = ContractApiService();
  List<ContractModel> _contracts = [];
  bool _isLoading = false;
  String? _errorMessage;

  @override
  void initState() {
    super.initState();
    _loadContracts();
  }

  Future<void> _loadContracts() async {
    setState(() {
      _isLoading = true;
      _errorMessage = null;
    });

    try {
      final contracts = await _apiService.getContracts();
      setState(() {
        _contracts = contracts;
        _isLoading = false;
      });
    } on ApiException catch (e) {
      setState(() {
        _errorMessage = e.message;
        _isLoading = false;
      });
    }
  }
}

在页面中,我们创建API服务实例,然后在initState中调用_loadContracts方法。这个方法会获取合同列表并更新UI。

缓存机制的实现

为了提高应用的性能,我们可以实现一个简单的缓存机制。这样可以减少不必要的网络请求。缓存管理器使用单例模式,存储缓存数据和过期时间。当获取缓存时,我们检查是否已过期,如果过期则删除缓存。

dart 复制代码
class CacheManager {
  static final CacheManager _instance = CacheManager._internal();
  final Map<String, CacheEntry> _cache = {};

  factory CacheManager() {
    return _instance;
  }

  CacheManager._internal();

  void set(String key, dynamic value, {Duration? expiration}) {
    _cache[key] = CacheEntry(
      value: value,
      expiresAt: expiration != null
          ? DateTime.now().add(expiration)
          : DateTime.now().add(const Duration(hours: 1)),
    );
  }

  dynamic get(String key) {
    final entry = _cache[key];
    if (entry == null) return null;

    if (DateTime.now().isAfter(entry.expiresAt)) {
      _cache.remove(key);
      return null;
    }

    return entry.value;
  }
}

class CacheEntry {
  final dynamic value;
  final DateTime expiresAt;

  CacheEntry({
    required this.value,
    required this.expiresAt,
  });
}

缓存管理器提供了简单的缓存功能。通过缓存,我们可以减少不必要的网络请求,提高应用的性能。

关键功能说明

这个API集成系统包含了以下核心功能:

  1. 单例模式:确保全局只有一个Dio实例,节省资源
  2. 请求拦截:自动添加认证信息和请求ID
  3. 错误处理:详细的错误分类和处理
  4. 数据模型:使用类型安全的数据模型
  5. 缓存管理:减少不必要的网络请求
  6. 下载支持:支持文件下载和进度跟踪

设计考虑

API集成的设计需要考虑以下几个方面:

  1. 安全性:使用HTTPS、添加认证信息、验证SSL证书
  2. 可靠性:实现重试机制、超时设置、错误恢复
  3. 性能:使用缓存、连接池、请求队列
  4. 可维护性:清晰的代码结构、完善的错误处理
  5. 可扩展性:易于添加新的API端点和功能

总结

这个API集成系统为应用提供了一个安全、可靠、高效的后端通信机制。通过使用Dio框架、拦截器、缓存管理等技术,我们能够构建一个企业级的API集成解决方案。


欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

相关推荐
lili-felicity5 分钟前
进阶实战 Flutter for OpenHarmony:app_settings 第三方库实战 - 系统设置跳转
flutter
恋猫de小郭14 小时前
iOS + AI ,国外一个叫 Rork Max 的项目打算替换掉 Xcode
android·前端·flutter
左手厨刀右手茼蒿16 小时前
Flutter for OpenHarmony:dart_console 打造炫酷命令行界面,绘制表格、控制光标与进度条(CLI 交互库) 深度解析与鸿蒙适配指南
flutter·交互·harmonyos·绘制
加农炮手Jinx16 小时前
Flutter for OpenHarmony 实战:疯狂头像 App(三)— 复合动画与交互反馈 — 让 UI 跃动起来
flutter·ui·交互·harmonyos·鸿蒙
王码码203516 小时前
lutter for OpenHarmony 实战之基础组件:第六十二篇 SystemChannels — 探秘 Flutter 与系统交互的捷径
flutter·microsoft·交互·harmonyos
RaidenLiu18 小时前
别再手写 MethodChannel 了:Flutter Pigeon 工程级实践与架构设计
前端·flutter·前端框架
Bowen_J21 小时前
HarmonyOS 主流跨平台开发框架对比: ArkUI、Flutter、React Native、KMP、UniApp
flutter·react native·harmonyos
九狼JIULANG1 天前
Flutter SSE 流式响应用 Dio 实现 OpenAI 兼容接口的逐 Token 输出
flutter
恋猫de小郭1 天前
你是不是觉得 R8 很讨厌,但 Android 为什么选择 R8 ?也许你对 R8 还不够了解
android·前端·flutter
前端不太难1 天前
Flutter 页面切换后为什么会“状态丢失”或“状态常驻”?
flutter·状态模式