🔗实战项目:openharmonycrossplatform.csdn.net/content
📖 目录
🏗️ 微服务架构
🚀 CI/CD管道
📦 容器化部署
📊 监控与日志
🏗️ 一、微服务架构设计
1.1 微服务架构核心
dart
// lib/architecture/microservice_structure.dart class MicroserviceArchitecture { // 微服务模块定义 static const Map<String, List<String>> services = { 'user_service': [ 'auth', 'profile', 'preferences', ], 'product_service': [ 'catalog', 'inventory', 'pricing', ], 'order_service': [ 'cart', 'checkout', 'payment', ], 'notification_service': [ 'email', 'push', 'sms', ], }; // 服务发现配置 static const serviceDiscovery = { 'user_service': 'https://user.api.example.com', 'product_service': 'https://product.api.example.com', 'order_service': 'https://order.api.example.com', 'notification_service': 'https://notify.api.example.com', }; }1.2 服务网关实现
dart
// lib/services/api_gateway.dart import 'package:dio/dio.dart'; import 'package:retrofit/retrofit.dart'; part 'api_gateway.g.dart'; @RestApi(baseUrl: "https://gateway.api.example.com") abstract class ApiGateway { factory ApiGateway(Dio dio, {String baseUrl}) = _ApiGateway; // 用户服务 @GET("/users/{id}") Future<UserResponse> getUser(@Path("id") String userId); @POST("/users/login") Future<LoginResponse> login(@Body() LoginRequest request); // 产品服务 @GET("/products") Future<ProductListResponse> getProducts(@Query("page") int page); @GET("/products/{id}") Future<ProductResponse> getProduct(@Path("id") String productId); // 订单服务 @POST("/orders") Future<OrderResponse> createOrder(@Body() OrderRequest request); @GET("/orders/{id}") Future<OrderResponse> getOrder(@Path("id") String orderId); // 通知服务 @POST("/notifications") Future<NotificationResponse> sendNotification( @Body() NotificationRequest request, ); } // 错误处理中间件 class ApiGatewayInterceptor extends Interceptor { @override void onRequest(RequestOptions options, RequestInterceptorHandler handler) { // 添加认证头 options.headers.addAll({ 'X-Request-ID': _generateRequestId(), 'Authorization': 'Bearer ${_getAuthToken()}', }); // 记录请求日志 _logRequest(options); handler.next(options); } @override void onResponse(Response response, ResponseInterceptorHandler handler) { // 记录响应日志 _logResponse(response); // 统一响应格式处理 if (response.data is Map<String, dynamic>) { final data = response.data as Map<String, dynamic>; if (data['code'] != 0) { // 业务错误处理 handler.reject(DioException( requestOptions: response.requestOptions, response: response, type: DioExceptionType.badResponse, error: data['message'], )); return; } } handler.next(response); } @override void onError(DioException err, ErrorInterceptorHandler handler) { // 统一错误处理 _handleError(err); handler.next(err); } String _generateRequestId() { return '${DateTime.now().millisecondsSinceEpoch}_${Random().nextInt(9999)}'; } String? _getAuthToken() { // 从本地存储获取token return null; } void _logRequest(RequestOptions options) { print('🌐 请求: ${options.method} ${options.uri}'); } void _logResponse(Response response) { print('✅ 响应: ${response.statusCode} ${response.requestOptions.uri}'); } void _handleError(DioException error) { print('❌ 错误: ${error.type} ${error.message}'); } } // 服务熔断器 class CircuitBreaker { final Duration timeout; final int failureThreshold; final Duration resetTimeout; int _failureCount = 0; DateTime? _lastFailure; bool _circuitOpen = false; CircuitBreaker({ this.timeout = const Duration(seconds: 5), this.failureThreshold = 3, this.resetTimeout = const Duration(seconds: 30), }); Future<T> execute<T>(Future<T> Function() operation) async { if (_circuitOpen) { if (_lastFailure != null && DateTime.now().difference(_lastFailure!) > resetTimeout) { _circuitOpen = false; _failureCount = 0; } else { throw CircuitBreakerException('服务熔断中'); } } try { final result = await operation().timeout(timeout); _reset(); return result; } catch (e) { _recordFailure(); rethrow; } } void _recordFailure() { _failureCount++; _lastFailure = DateTime.now(); if (_failureCount >= failureThreshold) { _circuitOpen = true; } } void _reset() { _failureCount = 0; _circuitOpen = false; } } class CircuitBreakerException implements Exception { final String message; const CircuitBreakerException(this.message); @override String toString() => 'CircuitBreakerException: $message'; }🚀 二、CI/CD管道实现
2.1 GitHub Actions配置
yaml
# .github/workflows/flutter-ci.yml name: Flutter CI/CD Pipeline on: push: branches: [ main, develop ] pull_request: branches: [ main ] env: FLUTTER_VERSION: '3.19.0' jobs: # 1. 代码质量检查 quality-check: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Setup Flutter uses: subosito/flutter-action@v2 with: flutter-version: ${{ env.FLUTTER_VERSION }} cache: true - name: Analyze Code run: flutter analyze - name: Run Tests run: flutter test - name: Generate Coverage run: | flutter test --coverage genhtml coverage/lcov.info -o coverage/html - name: Upload Coverage uses: codecov/codecov-action@v3 with: file: ./coverage/lcov.info fail_ci_if_error: false # 2. 构建检查 build-check: needs: quality-check runs-on: ubuntu-latest strategy: matrix: platform: [android, ios] steps: - uses: actions/checkout@v3 - name: Setup Flutter uses: subosito/flutter-action@v2 with: flutter-version: ${{ env.FLUTTER_VERSION }} - name: Build APK if: matrix.platform == 'android' run: | flutter build apk --release --split-per-abi - name: Build AppBundle if: matrix.platform == 'android' run: | flutter build appbundle --release - name: Build iOS if: matrix.platform == 'ios' run: | flutter build ios --release --no-codesign - name: Upload Artifacts uses: actions/upload-artifact@v3 with: name: build-${{ matrix.platform }} path: | build/app/outputs/ # 3. 部署到测试环境 deploy-test: needs: build-check if: github.ref == 'refs/heads/develop' runs-on: ubuntu-latest environment: test steps: - uses: actions/download-artifact@v3 with: name: build-android - name: Deploy to Firebase run: | curl -X POST \ -H "Authorization: Bearer ${{ secrets.FIREBASE_TOKEN }}" \ -H "Content-Type: application/octet-stream" \ --data-binary @app-release.apk \ "https://firebaseappdistribution.googleapis.com/v1/projects/${{ secrets.FIREBASE_PROJECT_ID }}/releases:upload" - name: Notify Slack uses: 8398a7/action-slack@v3 with: status: ${{ job.status }} author_name: Flutter CI/CD env: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} # 4. 生产环境部署 deploy-prod: needs: build-check if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest environment: production steps: - uses: actions/download-artifact@v3 with: name: build-android - name: Upload to Google Play uses: r0adkll/upload-google-play@v1 with: serviceAccountJsonPlainText: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT }} packageName: com.example.app releaseFiles: app-release.aab track: production status: completed - name: Create GitHub Release uses: softprops/action-gh-release@v1 with: tag_name: v${{ github.run_number }} name: Release v${{ github.run_number }} body: | ### 🚀 新版本发布 **构建编号:** ${{ github.run_number }} **提交:** ${{ github.sha }} **时间:** ${{ github.event.head_commit.timestamp }} draft: false prerelease: false2.2 自动化测试配置
dart
// test/integration/api_integration_test.dart import 'package:flutter_test/flutter_test.dart'; import 'package:http/http.dart' as http; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; @GenerateMocks([http.Client]) void main() { group('API集成测试', () { late http.Client client; setUp(() { client = MockClient(); }); test('用户登录接口测试', () async { // 模拟成功响应 when(client.post( Uri.parse('https://api.example.com/login'), headers: anyNamed('headers'), body: anyNamed('body'), )).thenAnswer((_) async => http.Response( '{"success": true, "token": "test_token"}', 200, )); // 执行测试 final response = await client.post( Uri.parse('https://api.example.com/login'), headers: {'Content-Type': 'application/json'}, body: '{"email": "test@example.com", "password": "password"}', ); // 验证结果 expect(response.statusCode, 200); expect(response.body, contains('success')); }); test('商品列表接口测试', () async { // 模拟分页响应 when(client.get( Uri.parse('https://api.example.com/products?page=1'), headers: anyNamed('headers'), )).thenAnswer((_) async => http.Response( '{"products": [], "total": 0, "page": 1}', 200, )); final response = await client.get( Uri.parse('https://api.example.com/products?page=1'), headers: {'Authorization': 'Bearer test_token'}, ); expect(response.statusCode, 200); expect(response.body, contains('products')); }); }); } // test/widget/widget_test.dart import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { testWidgets('首页Widget测试', (WidgetTester tester) async { // 构建Widget await tester.pumpWidget(MaterialApp( home: Scaffold( appBar: AppBar(title: const Text('首页')), body: const Center(child: Text('欢迎使用')), ), )); // 查找文本 expect(find.text('首页'), findsOneWidget); expect(find.text('欢迎使用'), findsOneWidget); // 模拟点击 await tester.tap(find.byType(AppBar)); await tester.pump(); }); testWidgets('表单验证测试', (WidgetTester tester) async { await tester.pumpWidget(MaterialApp( home: Scaffold( body: Column( children: [ TextField( decoration: const InputDecoration(labelText: '邮箱'), controller: TextEditingController(), ), ElevatedButton( onPressed: () {}, child: const Text('提交'), ), ], ), ), )); // 输入测试 await tester.enterText(find.byType(TextField), 'test@example.com'); expect(find.text('test@example.com'), findsOneWidget); }); }📦 三、容器化部署
3.1 Docker配置
dockerfile
# Dockerfile FROM ubuntu:22.04 AS build # 安装Flutter依赖 RUN apt-get update && apt-get install -y \ curl \ git \ unzip \ xz-utils \ zip \ libglu1-mesa \ openjdk-17-jdk \ wget \ && rm -rf /var/lib/apt/lists/* # 安装Flutter ARG FLUTTER_VERSION=3.19.0 RUN git clone https://github.com/flutter/flutter.git -b $FLUTTER_VERSION /usr/local/flutter ENV PATH="/usr/local/flutter/bin:/usr/local/flutter/bin/cache/dart-sdk/bin:${PATH}" # 验证安装 RUN flutter doctor WORKDIR /app # 复制项目文件 COPY pubspec.yaml ./ COPY lib ./lib COPY assets ./assets COPY test ./test # 获取依赖 RUN flutter pub get # 构建Web版本 RUN flutter build web --release --web-renderer canvaskit # 使用Nginx服务 FROM nginx:alpine # 复制构建产物 COPY --from=build /app/build/web /usr/share/nginx/html # 复制Nginx配置 COPY nginx.conf /etc/nginx/nginx.conf # 暴露端口 EXPOSE 80 # 启动Nginx CMD ["nginx", "-g", "daemon off;"]nginx
# nginx.conf events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; # Gzip压缩 gzip on; gzip_vary on; gzip_min_length 1024; gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json; server { listen 80; server_name _; root /usr/share/nginx/html; index index.html; # 静态文件缓存 location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ { expires 1y; add_header Cache-Control "public, immutable"; } # HTML文件不缓存 location ~* \.html$ { expires -1; add_header Cache-Control "no-store, no-cache, must-revalidate"; } # SPA路由支持 location / { try_files $uri $uri/ /index.html; } # 健康检查 location /health { access_log off; return 200 "healthy\n"; add_header Content-Type text/plain; } } }3.2 Docker Compose配置
yaml
# docker-compose.yml version: '3.8' services: # Flutter Web应用 flutter-web: build: context: . dockerfile: Dockerfile ports: - "8080:80" environment: - NODE_ENV=production networks: - app-network healthcheck: test: ["CMD", "curl", "-f", "http://localhost/health"] interval: 30s timeout: 10s retries: 3 # 后端API服务 api-service: image: node:18-alpine ports: - "3000:3000" environment: - NODE_ENV=production - DATABASE_URL=postgresql://user:pass@db:5432/app volumes: - ./api:/app working_dir: /app command: ["npm", "start"] networks: - app-network depends_on: db: condition: service_healthy # 数据库 db: image: postgres:15-alpine environment: - POSTGRES_USER=user - POSTGRES_PASSWORD=pass - POSTGRES_DB=app volumes: - postgres-data:/var/lib/postgresql/data networks: - app-network healthcheck: test: ["CMD-SHELL", "pg_isready -U user"] interval: 10s timeout: 5s retries: 5 # Redis缓存 redis: image: redis:7-alpine ports: - "6379:6379" networks: - app-network # 监控服务 monitoring: image: grafana/grafana:latest ports: - "3001:3000" environment: - GF_SECURITY_ADMIN_PASSWORD=admin volumes: - grafana-data:/var/lib/grafana networks: - app-network networks: app-network: driver: bridge volumes: postgres-data: grafana-data:
📊 四、监控与日志系统
4.1 应用监控
dart
// lib/services/monitoring_service.dart import 'package:flutter/foundation.dart'; import 'package:firebase_analytics/firebase_analytics.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; class MonitoringService { static final FirebaseAnalytics _analytics = FirebaseAnalytics.instance; static final SentryClient _sentry = SentryClient(SentryOptions( dsn: 'YOUR_SENTRY_DSN', debug: kDebugMode, )); // 跟踪用户行为 static Future<void> trackEvent({ required String name, Map<String, dynamic>? parameters, }) async { try { await _analytics.logEvent( name: name, parameters: parameters, ); print('📊 事件跟踪: $name'); } catch (e) { _captureError(e, stackTrace: StackTrace.current); } } // 跟踪屏幕浏览 static Future<void> trackScreen({ required String screenName, String? screenClass, }) async { try { await _analytics.logScreenView( screenName: screenName, screenClass: screenClass, ); print('📱 屏幕浏览: $screenName'); } catch (e) { _captureError(e, stackTrace: StackTrace.current); } } // 捕获错误 static Future<void> captureError( dynamic error, { StackTrace? stackTrace, String? context, }) async { print('❌ 错误捕获: $error'); if (kReleaseMode) { await _sentry.captureException( error, stackTrace: stackTrace, hint: Hint.withMap({'context': context}), ); } } // 性能监控 static Future<void> trackPerformance({ required String name, required Duration duration, Map<String, dynamic>? attributes, }) async { print('⏱️ 性能监控 [$name]: ${duration.inMilliseconds}ms'); await _analytics.logEvent( name: 'performance_$name', parameters: { 'duration_ms': duration.inMilliseconds, ...?attributes, }, ); } // 用户属性设置 static Future<void> setUserProperties({ String? userId, String? userEmail, Map<String, dynamic>? properties, }) async { await _analytics.setUserId(id: userId); await _analytics.setUserProperty(name: 'email', value: userEmail); if (properties != null) { for (final entry in properties.entries) { await _analytics.setUserProperty( name: entry.key, value: entry.value.toString(), ); } } } }4.2 日志系统
dart
// lib/services/logging_service.dart import 'dart:developer' as developer; import 'package:logger/logger.dart'; class LoggingService { static final Logger _logger = Logger( printer: PrettyPrinter( methodCount: 0, errorMethodCount: 8, lineLength: 120, colors: true, printEmojis: true, printTime: true, ), ); // 调试日志 static void debug(String message, [dynamic error, StackTrace? stackTrace]) { if (kDebugMode) { _logger.d(message, error: error, stackTrace: stackTrace); developer.log(message, name: 'DEBUG'); } } // 信息日志 static void info(String message) { _logger.i(message); developer.log(message, name: 'INFO'); } // 警告日志 static void warning(String message, [dynamic error, StackTrace? stackTrace]) { _logger.w(message, error: error, stackTrace: stackTrace); developer.log(message, name: 'WARNING'); } // 错误日志 static void error(String message, [dynamic error, StackTrace? stackTrace]) { _logger.e(message, error: error, stackTrace: stackTrace); developer.log(message, name: 'ERROR'); // 发送到监控服务 MonitoringService.captureError( error ?? message, stackTrace: stackTrace, context: message, ); } // 网络请求日志 static void network( String method, String url, { int? statusCode, dynamic requestData, dynamic responseData, Duration? duration, }) { final logMessage = StringBuffer(); logMessage.write('🌐 $method $url'); if (statusCode != null) { logMessage.write(' [$statusCode]'); } if (duration != null) { logMessage.write(' (${duration.inMilliseconds}ms)'); } info(logMessage.toString()); if (kDebugMode && requestData != null) { debug('请求数据: $requestData'); } if (kDebugMode && responseData != null) { debug('响应数据: $responseData'); } } // 结构化日志 static void structured( String type, Map<String, dynamic> data, { LogLevel level = LogLevel.info, }) { final logEntry = { 'timestamp': DateTime.now().toIso8601String(), 'type': type, 'level': level.name, 'data': data, }; final jsonString = _encodeJson(logEntry); switch (level) { case LogLevel.debug: debug(jsonString); case LogLevel.info: info(jsonString); case LogLevel.warning: warning(jsonString); case LogLevel.error: error(jsonString); } } static String _encodeJson(Map<String, dynamic> data) { try { return const JsonEncoder.withIndent(' ').convert(data); } catch (e) { return data.toString(); } } } enum LogLevel { debug, info, warning, error, }🎯 五、实战示例:完整的CI/CD流水线
yaml
# .github/workflows/full-pipeline.yml name: Full CI/CD Pipeline on: workflow_dispatch: inputs: environment: description: '部署环境' required: true default: 'staging' type: choice options: - staging - production jobs: validate: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Setup Flutter uses: subosito/flutter-action@v2 with: flutter-version: '3.19.0' - name: Validate Pubspec run: | flutter pub get flutter analyze - name: Run Tests run: | flutter test --coverage - name: SonarQube Analysis uses: SonarSource/sonarqube-scan-action@master env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} build: needs: validate runs-on: ubuntu-latest strategy: matrix: target: [apk, appbundle, web] steps: - uses: actions/checkout@v3 - name: Setup Flutter uses: subosito/flutter-action@v2 - name: Build ${{ matrix.target }} run: | case "${{ matrix.target }}" in apk) flutter build apk --release --split-per-abi ;; appbundle) flutter build appbundle --release ;; web) flutter build web --release --web-renderer canvaskit ;; esac - name: Upload Artifact uses: actions/upload-artifact@v3 with: name: ${{ matrix.target }}-build path: build/ deploy: needs: build runs-on: ubuntu-latest environment: ${{ github.event.inputs.environment }} steps: - name: Download Artifacts uses: actions/download-artifact@v3 - name: Configure AWS uses: aws-actions/configure-aws-credentials@v2 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: us-east-1 - name: Deploy to S3 (Web) if: matrix.target == 'web' run: | aws s3 sync build/web s3://${{ secrets.S3_BUCKET }}/web \ --acl public-read \ --cache-control "max-age=31536000" - name: Deploy to ECR (Container) if: matrix.target == 'container' run: | docker build -t ${{ secrets.ECR_REPOSITORY }}:latest . docker push ${{ secrets.ECR_REPOSITORY }}:latest - name: Update ECS Service if: matrix.target == 'container' run: | aws ecs update-service \ --cluster ${{ secrets.ECS_CLUSTER }} \ --service ${{ secrets.ECS_SERVICE }} \ --force-new-deployment notify: needs: deploy runs-on: ubuntu-latest steps: - name: Send Success Notification uses: 8398a7/action-slack@v3 with: status: ${{ job.status }} author_name: CI/CD Pipeline fields: | environment=${{ github.event.inputs.environment }} commit=${{ github.sha }} runner=${{ runner.os }} env: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} - name: Create Deployment Record run: | curl -X POST \ -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ -H "Accept: application/vnd.github.v3+json" \ https://api.github.com/repos/${{ github.repository }}/deployments \ -d '{ "ref": "${{ github.ref }}", "environment": "${{ github.event.inputs.environment }}", "auto_merge": false, "required_contexts": [] }'📊 总结
微服务与CI/CD要点:
✅微服务架构:服务拆分、API网关、熔断器
✅CI/CD管道:自动化测试、构建、部署
✅容器化部署:Docker、编排、监控
✅监控日志:性能跟踪、错误捕获、日志管理
工具链推荐:
yaml
# pubspec.yaml 关键依赖 dependencies: retrofit: ^4.0.1 logger: ^2.0.1 sentry_flutter: ^7.10.0 firebase_analytics: ^10.4.3 dev_dependencies: build_runner: ^2.4.0 retrofit_generator: ^5.0.0
欢迎大家加入[开源鸿蒙跨平台开发者社区](https://openharmonycrossplatform.csdn.net),一起共建开源鸿蒙跨平台生态。