Flutter微服务架构与CI/CD实战指南
·
📖 目录
-
🏗️ 微服务架构
-
🚀 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: false
2.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),一起共建开源鸿蒙跨平台生态。
更多推荐
所有评论(0)