《Flutter全栈开发实战指南:从零到高级》- 26 -持续集成与部署
引言
代码写得再好,没有自动化的流水线,就像法拉利引擎装在牛车上!!!
什么是持续集成与部署?简单说就是:
- 你写代码 → 自动测试 → 自动打包 → 自动发布
- 就像工厂的流水线,代码进去,App出来
今天我们一起来搭建这条"代码流水线",让你的开发效率大幅提升!
一:CI/CD到底是什么?为什么每个团队都需要?
1.1 从手动操作到自动化流水线
先看看传统开发流程的痛点:
// 传统发布流程(手动版)
1. 本地运行测试(); // 某些测试可能忘记运行
2. 手动打包Android(); // 配置证书、签名、版本号...
3. 手动打包iOS(); // 证书、描述文件、上架截图...
4. 上传到测试平台(); // 找测试妹子要手机号
5. 收集反馈修复bug(); // 来回沟通,效率低下
6. 重复步骤1-5(); // 无限循环...
再看自动化流水线:
# 自动化发布流程(CI/CD版)
流程:
1. 推送代码到GitHub/Gitlab → 自动触发
2. 运行所有测试 → 失败自动通知
3. 打包所有平台 → 同时进行
4. 分发到测试环境 → 自动分发给测试人员
5. 发布到应用商店 → 条件触发
1.2 CI/CD的核心价值
很多新手觉得CI/CD是"大公司才需要的东西",其实完全错了!它解决的是这些痛点:
问题1:环境不一致
本地环境: Flutter 3.10, Dart 2.18, Mac M1
测试环境: Flutter 3.7, Dart 2.17, Windows
生产环境: ???
问题2:手动操作容易出错 之前遇到过同事把debug包发给了用户,因为打包时选错了构建变体。
问题3:反馈周期太长 代码提交 → 手动打包 → 发给测试 → 发现问题 → 已经过了半天
1.3 CI/CD的三个核心概念
graph LR
A[代码提交] --> B[持续集成 CI]
B --> C[持续交付 CD]
C --> D[持续部署 CD]
B --> E[自动构建]
B --> F[自动测试]
C --> G[自动打包]
C --> H[自动发布到测试]
D --> I[自动发布到生产]
style A fill:#e3f2fd
style B fill:#f3e5f5
style C fill:#e8f5e8
style D fill:#fff3e0
持续集成(CI):频繁集成代码到主干,每次集成都通过自动化测试
持续交付(CD):自动将代码打包成可部署的产物
持续部署(CD):自动将产物部署到生产环境
注意:两个CD虽然缩写一样,但含义不同。Continuous Delivery(持续交付)和 Continuous Deployment(持续部署)
二:GitHub Actions
我们以github为例,当然各公司有单独部署的gitlab,大同小异这里不在赘述。。。
2.1 GitHub Actions工作原理
GitHub Actions不是魔法,而是GitHub提供的自动化执行环境。想象一下:
graph LR
A[你的代码仓库] --> B[事件推送/PR]
B --> C[GitHub Actions服务器]
C --> D[分配虚拟机]
D --> E[你的工作流]
E --> F[运行你的脚本]
style A fill:#f9f,stroke:#333,stroke-width:1px
style C fill:#9f9,stroke:#333,stroke-width:1px
style E fill:#99f,stroke:#333,stroke-width:1px
核心组件解析:
# 工作流组件关系图
工作流文件 (.github/workflows/ci.yml)
├── 触发器: 什么情况下运行 (push, pull_request)
├── 任务: 在什么环境下运行 (ubuntu-latest)
└── 步骤: 具体执行什么 (安装Flutter、运行测试)
2.2 创建你的第一个工作流
别被吓到,其实创建一个基础的CI流程只需要5分钟:
- 在项目根目录创建文件夹:
mkdir -p .github/workflows
- 创建CI配置文件:
# .github/workflows/flutter-ci.yml
name: Flutter CI # 工作流名称
# 触发条件:当有代码推送到main分支,或者有PR时
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
# 设置权限
permissions:
contents: read # 只读权限,保证安全
# 工作流中的任务
jobs:
# 任务1:运行测试
test:
# 运行在Ubuntu最新版
runs-on: ubuntu-latest
# 任务步骤
steps:
# 步骤1:检出代码
- name: Checkout code
uses: actions/checkout@v3
# 步骤2:安装Flutter
- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: '3.10.x' # 指定Flutter版本
channel: 'stable' # 稳定版
# 步骤3:获取依赖
- name: Get dependencies
run: flutter pub get
# 步骤4:运行测试
- name: Run tests
run: flutter test
# 步骤5:检查代码格式
- name: Check formatting
run: flutter format --set-exit-if-changed .
# 步骤6:静态分析
- name: Analyze code
run: flutter analyze
- 提交并推送代码:
git add .github/workflows/flutter-ci.yml
git commit -m "添加CI工作流"
git push origin main
推送到GitHub后,打开你的仓库页面,点击"Actions"标签,你会看到一个工作流正在运行!
2.3 GitHub Actions架构
graph TB
subgraph "GitHub Actions架构"
A[你的代码仓库] --> B[触发事件]
B --> C[GitHub Actions Runner]
subgraph "Runner执行环境"
C --> D[创建虚拟机]
D --> E[执行工作流]
subgraph "工作流步骤"
E --> F[检出代码]
F --> G[环境配置]
G --> H[执行脚本]
H --> I[产出物]
end
end
I --> J[结果反馈]
J --> K[GitHub UI显示]
J --> L[邮件/通知]
end
style A fill:#e3f2fd
style C fill:#f3e5f5
style E fill:#e8f5e8
style I fill:#fff3e0
核心概念解释:
- Runner:GitHub提供的虚拟机(或你自己的服务器),用来执行工作流
- Workflow:工作流,一个完整的自动化流程
- Job:任务,工作流中的独立单元
- Step:步骤,任务中的具体操作
- Action:可复用的操作单元,如"安装Flutter"
三:自动化测试流水线
3.1 为什么自动化测试如此重要?
功能上线前,全部功能手动测试耗时长,易出bug。加入自动化测试,有效减少bug率。
测试金字塔理论:
/\
/ \ E2E测试(少量)
/____\
/ \ 集成测试(适中)
/________\
/ \ 单元测试(大量)
/____________\
对于Flutter,测试分为三层:
3.2 配置单元测试
单元测试是最基础的,测试单个函数或类:
# .github/workflows/unit-tests.yml
name: Unit Tests
on: [push, pull_request]
jobs:
unit-tests:
runs-on: ubuntu-latest
strategy:
matrix:
# 在不同版本的Flutter上运行测试
flutter: ['3.7.x', '3.10.x']
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup Flutter ${{ matrix.flutter }}
uses: subosito/flutter-action@v2
with:
flutter-version: ${{ matrix.flutter }}
- name: Get dependencies
run: flutter pub get
- name: Run unit tests
run: |
# 运行所有单元测试
flutter test
# 生成测试覆盖率报告
flutter test --coverage
# 上传覆盖率报告
bash <(curl -s https://codecov.io/bash)
单元测试:
// test/calculator_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:my_app/utils/calculator.dart';
void main() {
group('以Calculator测试为例', () {
late Calculator calculator;
// 准备工作
setUp(() {
calculator = Calculator();
});
test('两个正数相加', () {
expect(calculator.add(2, 3), 5);
});
test('正数与负数相加', () {
expect(calculator.add(5, -3), 2);
});
test('除以零应该抛出异常', () {
expect(() => calculator.divide(10, 0), throwsA(isA<ArgumentError>()));
});
});
}
3.3 配置集成测试
集成测试测试多个组件的交互:
# 集成测试工作流
jobs:
integration-tests:
runs-on: macos-latest # iOS集成测试需要macOS
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup Flutter
uses: subosito/flutter-action@v2
- name: Get dependencies
run: flutter pub get
- name: Run integration tests
run: |
# 启动模拟器
# flutter emulators --launch flutter_emulator
# 运行集成测试
flutter test integration_test/
# 如果集成测试失败,上传截图辅助调试
- name: Upload screenshots on failure
if: failure()
uses: actions/upload-artifact@v3
with:
name: integration-test-screenshots
path: screenshots/
3.4 配置Widget测试
Widget测试测试UI组件:
jobs:
widget-tests:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup Flutter
uses: subosito/flutter-action@v2
- name: Install dependencies
run: |
flutter pub get
- name: Run widget tests
run: |
# 运行所有widget测试
flutter test test/widget_test.dart
# 或者运行特定目录
flutter test test/widgets/
3.5 测试流水线
sequenceDiagram
participant D as 开发者
participant G as Git仓库
participant CI as CI服务器
participant UT as 单元测试服务
participant WT as Widget测试服务
participant IT as 集成测试服务
participant R as 报告服务
participant N as 通知服务
D->>G: 推送代码
G->>CI: 触发Webhook
CI->>CI: 解析工作流配置
CI->>CI: 分配测试资源
par 并行执行
CI->>UT: 启动单元测试
UT->>UT: 准备环境
UT->>UT: 执行测试
UT->>UT: 分析覆盖率
UT-->>CI: 返回结果
and
CI->>WT: 启动Widget测试
WT->>WT: 准备UI环境
WT->>WT: 执行测试
WT->>WT: 截图对比
WT-->>CI: 返回结果
and
CI->>IT: 启动集成测试
IT->>IT: 准备设备
IT->>IT: 执行测试
IT->>IT: 端到端验证
IT-->>CI: 返回结果
end
CI->>CI: 收集所有结果
alt 所有测试通过
CI->>R: 请求生成报告
R->>R: 生成详细报告
R-->>CI: 返回报告
CI->>N: 发送成功通知
N-->>D: 通知开发者
else 有测试失败
CI->>R: 请求生成错误报告
R->>R: 生成错误报告
R-->>CI: 返回报告
CI->>N: 发送失败通知
N-->>D: 警报开发者
end
四:自动打包与发布流水线
4.1 Android自动打包
Android打包相对简单,但要注意签名问题:
# .github/workflows/android-build.yml
name: Android Build
on:
push:
tags:
- 'v*' # 只有打tag时才触发打包
jobs:
build-android:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup Java
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '17'
- name: Setup Flutter
uses: subosito/flutter-action@v2
- name: Get dependencies
run: flutter pub get
- name: Setup keystore
# 从GitHub Secrets读取签名密钥
run: |
echo "${{ secrets.ANDROID_KEYSTORE }}" > android/app/key.jks.base64
base64 -d android/app/key.jks.base64 > android/app/key.jks
- name: Build APK
run: |
# 构建Release版APK
flutter build apk --release \
--dart-define=APP_VERSION=${{ github.ref_name }} \
--dart-define=BUILD_NUMBER=${{ github.run_number }}
- name: Build App Bundle
run: |
# 构建App Bundle
flutter build appbundle --release
- name: Upload artifacts
uses: actions/upload-artifact@v3
with:
name: android-build-${{ github.run_number }}
path: |
build/app/outputs/flutter-apk/app-release.apk
build/app/outputs/bundle/release/app-release.aab
4.2 iOS自动打包
iOS打包相对复杂,需要苹果开发者账号:
# .github/workflows/ios-build.yml
name: iOS Build
on:
push:
tags:
- 'v*'
jobs:
build-ios:
runs-on: macos-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup Flutter
uses: subosito/flutter-action@v2
- name: Install CocoaPods
run: |
cd ios
pod install
- name: Setup Xcode
run: |
# 设置Xcode版本
sudo xcode-select -s /Applications/Xcode_14.2.app
- name: Setup provisioning profiles
# 配置证书和描述文件
env:
BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE }}
P12_PASSWORD: ${{ secrets.P12_PASSWORD }}
BUILD_PROVISION_PROFILE_BASE64: ${{ secrets.BUILD_PROVISION_PROFILE }}
run: |
# 导入证书
echo $BUILD_CERTIFICATE_BASE64 | base64 --decode > certificate.p12
# 创建钥匙链
security create-keychain -p "" build.keychain
security default-keychain -s build.keychain
security unlock-keychain -p "" build.keychain
# 导入证书到钥匙链
security import certificate.p12 -k build.keychain \
-P $P12_PASSWORD -T /usr/bin/codesign
# 导入描述文件
echo $BUILD_PROVISION_PROFILE_BASE64 | base64 --decode > profile.mobileprovision
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
cp profile.mobileprovision ~/Library/MobileDevice/Provisioning\ Profiles/
- name: Build iOS
run: |
# 构建iOS应用
flutter build ipa --release \
--export-options-plist=ios/ExportOptions.plist \
--dart-define=APP_VERSION=${{ github.ref_name }} \
--dart-define=BUILD_NUMBER=${{ github.run_number }}
- name: Upload IPA
uses: actions/upload-artifact@v3
with:
name: ios-build-${{ github.run_number }}
path: build/ios/ipa/*.ipa
4.3 多环境构建配置
真实的项目通常有多个环境:
# 多环境构建配置
env:
# 根据分支选择环境
APP_ENV: ${{ github.ref == 'refs/heads/main' && 'production' || 'staging' }}
APP_NAME: ${{ github.ref == 'refs/heads/main' && '生产' || '测试' }}
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
# 同时构建多个Flavor
flavor: [development, staging, production]
platform: [android, ios]
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup Flutter
uses: subosito/flutter-action@v2
- name: Build ${{ matrix.platform }} for ${{ matrix.flavor }}
run: |
if [ "${{ matrix.platform }}" = "android" ]; then
flutter build apk --flavor ${{ matrix.flavor }} --release
else
flutter build ipa --flavor ${{ matrix.flavor }} --release
fi
- name: Upload ${{ matrix.flavor }} build
uses: actions/upload-artifact@v3
with:
name: ${{ matrix.platform }}-${{ matrix.flavor }}
path: |
build/app/outputs/flutter-apk/app-${{ matrix.flavor }}-release.apk
build/ios/ipa/*.ipa
4.4 自动化发布到测试平台
构建完成后,自动分发给测试人员:
# 分发到测试平台
jobs:
distribute:
runs-on: ubuntu-latest
needs: [build] # 依赖build任务
steps:
- name: Download artifacts
uses: actions/download-artifact@v3
with:
path: artifacts/
- name: Upload to Firebase App Distribution
# 分发到Firebase
run: |
# 安装Firebase CLI
curl -sL https://firebase.tools | bash
# 登录Firebase
echo "${{ secrets.FIREBASE_TOKEN }}" > firebase_token.json
# 分发Android APK
firebase appdistribution:distribute artifacts/android-production/app-release.apk \
--app ${{ secrets.FIREBASE_ANDROID_APP_ID }} \
--groups "testers" \
--release-notes-file CHANGELOG.md
- name: Upload to TestFlight
# iOS上传到TestFlight
if: matrix.platform == 'ios'
run: |
# 使用altool上传到App Store Connect
xcrun altool --upload-app \
-f artifacts/ios-production/*.ipa \
-t ios \
--apiKey ${{ secrets.APPSTORE_API_KEY }} \
--apiIssuer ${{ secrets.APPSTORE_API_ISSUER }}
- name: Notify testers
# 通知测试人员
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
fields: repo,message,commit,author,action,eventName,ref,workflow,job,took
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
4.5 打包发布流水线
gantt
title Flutter打包发布流水线
dateFormat HH:mm
axisFormat %H:%M
section 触发与准备
代码提交检测 :00:00, 2m
环境初始化 :00:02, 3m
依赖安装 :00:05, 4m
section Android构建
Android环境准备 :00:05, 2m
Android代码编译 :00:07, 6m
Android代码签名 :00:13, 3m
Android打包 :00:16, 2m
section iOS构建
iOS环境准备 :00:05, 3m
iOS代码编译 :00:08, 8m
iOS证书配置 :00:16, 4m
iOS打包 :00:20, 3m
section 测试分发
上传到测试平台 :00:23, 5m
测试人员通知 :00:28, 2m
测试执行周期 :00:30, 30m
section 生产发布
测试结果评估 :01:00, 3m
生产环境准备 :01:03, 5m
提交到应用商店 :01:08, 10m
商店审核等待 :01:18, 30m
发布完成通知 :01:48, 2m
section 环境配置管理
密钥加载 :00:02, 3m
环境变量设置 :00:05, 2m
配置文件解析 :00:07, 3m
版本号处理 :00:10, 2m
五:环境配置管理
5.1 为什么需要环境配置管理?
先看一个反面教材:我们项目早期,不同环境的API地址是硬编码的:
// 不推荐:硬编码配置
class ApiConfig {
static const String baseUrl = 'https://api.production.com';
// 测试时需要手动改成:'https://api.staging.com'
// 很容易忘记改回来!
}
结果就是:测试时调用了生产接口,把测试数据插到了生产数据库!💥
5.2 多环境配置方案
方案一:基于Flavor的配置
// lib/config/flavors.dart
enum AppFlavor {
development,
staging,
production,
}
class AppConfig {
final AppFlavor flavor;
final String appName;
final String apiBaseUrl;
final bool enableAnalytics;
AppConfig({
required this.flavor,
required this.appName,
required this.apiBaseUrl,
required this.enableAnalytics,
});
// 根据Flavor创建配置
factory AppConfig.fromFlavor(AppFlavor flavor) {
switch (flavor) {
case AppFlavor.development:
return AppConfig(
flavor: flavor,
appName: 'MyApp Dev',
apiBaseUrl: 'https://api.dev.xxxx.com',
enableAnalytics: false,
);
case AppFlavor.staging:
return AppConfig(
flavor: flavor,
appName: 'MyApp Staging',
apiBaseUrl: 'https://api.staging.xxxx.com',
enableAnalytics: true,
);
case AppFlavor.production:
return AppConfig(
flavor: flavor,
appName: 'MyApp',
apiBaseUrl: 'https://api.xxxx.com',
enableAnalytics: true,
);
}
}
}
方案二:使用dart-define传入配置
# CI配置中传入环境变量
- name: Build with environment variables
run: |
flutter build apk --release \
--dart-define=APP_FLAVOR=production \
--dart-define=API_BASE_URL=https://api.xxxx.com \
--dart-define=ENABLE_ANALYTICS=true
// 在代码中读取环境变量
class EnvConfig {
static const String flavor = String.fromEnvironment('APP_FLAVOR');
static const String apiBaseUrl = String.fromEnvironment('API_BASE_URL');
static const bool enableAnalytics = bool.fromEnvironment('ENABLE_ANALYTICS');
}
5.3 管理敏感信息
敏感信息绝不能写在代码里!
# 使用GitHub Secrets
steps:
- name: Use secrets
env:
# 从Secrets读取
API_KEY: ${{ secrets.API_KEY }}
DATABASE_URL: ${{ secrets.DATABASE_URL }}
SIGNING_KEY: ${{ secrets.ANDROID_SIGNING_KEY }}
run: |
# 在脚本中使用
echo "API Key: $API_KEY"
# 写入到配置文件
echo "{ \"apiKey\": \"$API_KEY\" }" > config.json
如何设置Secrets:
- 打开GitHub仓库 → Settings → Secrets and variables → Actions
- 点击"New repository secret"
- 输入名称和值
5.4 配置文件管理
推荐以下分层配置策略:
config/
├── .env.example # 示例文件,不含真实值
├── .env.development # 开发环境配置
├── .env.staging # 测试环境配置
├── .env.production # 生产环境配置
└── config_loader.dart # 配置加载器
// config/config_loader.dart
import 'package:flutter_dotenv/flutter_dotenv.dart';
class ConfigLoader {
static Future<void> load(String env) async {
// 根据环境加载对应的配置文件
await dotenv.load(fileName: '.env.$env');
}
static String get apiBaseUrl => dotenv.get('API_BASE_URL');
static String get apiKey => dotenv.get('API_KEY');
static bool get isDebug => dotenv.get('DEBUG') == 'true';
}
// main.dart
void main() async {
// 根据编译模式选择环境
const flavor = String.fromEnvironment('FLAVOR', defaultValue: 'development');
await ConfigLoader.load(flavor);
runApp(MyApp());
}
5.5 设计环境配置
graph TB
subgraph "环境配置管理架构"
A[配置来源] --> B[优先级]
subgraph "B[优先级]"
B1[1. 运行时环境变量] --> B2[最高优先级]
B3[2. 配置文件] --> B4[中等优先级]
B5[3. 默认值] --> B6[最低优先级]
end
A --> C[敏感信息处理]
subgraph "C[敏感信息处理]"
C1[密钥/密码] --> C2[GitHub Secrets]
C3[API令牌] --> C4[环境变量注入]
C5[数据库连接] --> C6[运行时获取]
end
A --> D[环境类型]
subgraph "D[环境类型]"
D1[开发环境] --> D2[本地调试]
D3[测试环境] --> D4[CI/CD测试]
D5[预发环境] --> D6[生产前验证]
D7[生产环境] --> D8[线上用户]
end
B --> E[配置合并]
C --> E
D --> E
E --> F[最终配置]
F --> G[应用启动]
F --> H[API调用]
F --> I[功能开关]
end
subgraph "安全实践"
J[永远不要提交] --> K[.env文件到Git]
L[使用.gitignore] --> M[忽略敏感文件]
N[定期轮换] --> O[密钥和令牌]
P[最小权限原则] --> Q[仅授予必要权限]
end
style A fill:#e3f2fd
style C fill:#f3e5f5
style D fill:#e8f5e8
style J fill:#fff3e0
六:常见CI/CD技巧
6.1 使用缓存加速构建
Flutter项目依赖下载很慢,使用缓存可以大幅提速:
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Cache Flutter dependencies
uses: actions/cache@v3
with:
path: |
/opt/hostedtoolcache/flutter
${{ github.workspace }}/.pub-cache
${{ github.workspace }}/build
key: ${{ runner.os }}-flutter-${{ hashFiles('pubspec.lock') }}
restore-keys: |
${{ runner.os }}-flutter-
- name: Cache Android dependencies
uses: actions/cache@v3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
6.2 构建策略
同时测试多个配置组合:
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
# 定义
os: [ubuntu-latest, macos-latest]
flutter-version: ['3.7.x', '3.10.x']
exclude:
- os: macos-latest
flutter-version: '3.7.x'
# 包含特定组合
include:
- os: windows-latest
flutter-version: '3.10.x'
channel: 'beta'
steps:
- name: Test on ${{ matrix.os }} with Flutter ${{ matrix.flutter-version }}
run: echo "Running tests..."
6.3 条件执行与工作流控制
jobs:
deploy:
# 只有特定分支才执行
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- name: Check changed files
# 只有特定文件改动才执行
uses: dorny/paths-filter@v2
id: changes
with:
filters: |
src:
- 'src/**'
configs:
- 'config/**'
- name: Run if src changed
if: steps.changes.outputs.src == 'true'
run: echo "Source code changed"
- name: Skip if only docs changed
if: github.event_name == 'pull_request' && contains(github.event.pull_request.title, '[skip-ci]')
run: |
echo "Skipping CI due to [skip-ci] in PR title"
exit 0
6.4 自定义Actions
当通用Actions不够用时,可以自定义:
# .github/actions/flutter-setup/action.yml
name: 'Flutter Setup with Custom Options'
description: 'Setup Flutter environment with custom configurations'
inputs:
flutter-version:
description: 'Flutter version'
required: true
default: 'stable'
channel:
description: 'Flutter channel'
required: false
default: 'stable'
enable-web:
description: 'Enable web support'
required: false
default: 'false'
runs:
using: "composite"
steps:
- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: ${{ inputs.flutter-version }}
channel: ${{ inputs.channel }}
- name: Enable web if needed
if: ${{ inputs.enable-web == 'true' }}
shell: bash
run: flutter config --enable-web
- name: Install licenses
shell: bash
run: flutter doctor --android-licenses
七:为现有项目添加CI/CD
7.1 分析现有项目
如果我们有一个现成的Flutter应用,需要添加CI/CD:
项目结构:
my_flutter_app/
├── lib/
├── test/
├── android/
├── ios/
└── pubspec.yaml
当前问题:
- 手动测试,经常漏测
- 打包需要20分钟,且容易出错
- 不同开发者环境不一致
- 发布流程繁琐
7.2 分阶段实施自动化
第一阶段:实现基础CI
- 添加基础测试流水线
- 代码质量检查
- 配置GitHub Actions
第二阶段:自动化构建
- Android自动打包
- iOS自动打包
- 多环境配置
第三阶段:自动化发布
- 测试环境自动分发
- 生产环境自动发布
- 监控与告警
7.3 配置文件
# .github/workflows/ecommerce-ci.yml
name: E-commerce App CI/CD
on:
push:
branches: [develop]
pull_request:
branches: [main, develop]
schedule:
# 每天凌晨2点跑一遍测试
- cron: '0 2 * * *'
jobs:
# 代码质量
quality-gate:
runs-on: ubuntu-latest
outputs:
passed: ${{ steps.quality-check.outputs.passed }}
steps:
- uses: actions/checkout@v3
- name: Quality Check
id: quality-check
run: |
# 代码规范检查
flutter analyze . || echo "::warning::Code analysis failed"
# 检查测试覆盖率
flutter test --coverage
PERCENTAGE=$(lcov --summary coverage/lcov.info | grep lines | awk '{print $4}' | sed 's/%//')
if (( $(echo "$PERCENTAGE < 80" | bc -l) )); then
echo "::error::Test coverage $PERCENTAGE% is below 80% threshold"
echo "passed=false" >> $GITHUB_OUTPUT
else
echo "passed=true" >> $GITHUB_OUTPUT
fi
# 集成测试
integration-test:
needs: quality-gate
if: needs.quality-gate.outputs.passed == 'true'
runs-on: macos-latest
services:
# 启动测试数据库
postgres:
image: postgres:14
env:
POSTGRES_PASSWORD: postgres
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v3
- name: Setup Flutter
uses: subosito/flutter-action@v2
- name: Run integration tests with database
env:
DATABASE_URL: postgres://postgres:postgres@postgres:5432/test_db
run: |
flutter test integration_test/ --dart-define=DATABASE_URL=$DATABASE_URL
# 性能测试
performance-test:
needs: integration-test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run performance benchmarks
run: |
# 运行性能测试
flutter drive --target=test_driver/app_perf.dart
# 分析性能数据
dart analyze_performance.dart perf_data.json
- name: Upload performance report
uses: actions/upload-artifact@v3
with:
name: performance-report
path: perf_report.json
# 安全扫描
security-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run security scan
uses: snyk/actions/dart@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
- name: Check for secrets in code
uses: trufflesecurity/trufflehog@main
with:
path: ./
# 报告
report:
needs: [quality-gate, integration-test, performance-test, security-scan]
runs-on: ubuntu-latest
if: always()
steps:
- name: Generate CI/CD Report
run: |
echo "# CI/CD Run Report" > report.md
echo "## Run: ${{ github.run_id }}" >> report.md
echo "## Status: ${{ job.status }}" >> report.md
echo "## Jobs:" >> report.md
echo "- Quality Gate: ${{ needs.quality-gate.result }}" >> report.md
echo "- Integration Test: ${{ needs.integration-test.result }}" >> report.md
echo "- Performance Test: ${{ needs.performance-test.result }}" >> report.md
echo "- Security Scan: ${{ needs.security-scan.result }}" >> report.md
- name: Upload report
uses: actions/upload-artifact@v3
with:
name: ci-cd-report
path: report.md
7.4 流程优化
CI/CD不是一次性的,需要持续优化:
# 监控CI/CD性能
name: CI/CD Performance Monitoring
on:
workflow_run:
workflows: ["E-commerce App CI/CD"]
types: [completed]
jobs:
analyze-performance:
runs-on: ubuntu-latest
steps:
- name: Download workflow artifacts
uses: actions/github-script@v6
with:
script: |
const { data: artifacts } = await github.rest.actions.listWorkflowRunArtifacts({
owner: context.repo.owner,
repo: context.repo.repo,
run_id: context.payload.workflow_run.id,
});
// 分析执行时间
const runDuration = new Date(context.payload.workflow_run.updated_at) -
new Date(context.payload.workflow_run.run_started_at);
console.log(`Workflow took ${runDuration / 1000} seconds`);
// 发送到监控系统
// ...
- name: Send to monitoring
run: |
# 发送指标到Prometheus/Grafana
echo "ci_duration_seconds $DURATION" | \
curl -X POST -H "Content-Type: text/plain" \
--data-binary @- http://monitoring.xxxx.com/metrics
八:常见问题
8.1 GitHub Actions常见问题
Q:工作流运行太慢怎么办?
A:优化手段:
# 1. 使用缓存
- uses: actions/cache@v3
with:
path: ~/.pub-cache
key: ${{ runner.os }}-pub-${{ hashFiles('pubspec.lock') }}
# 2. 并行执行独立任务
jobs:
test-android:
runs-on: ubuntu-latest
test-ios:
runs-on: macos-latest
# 两个任务会并行执行
# 3. 项目大可以考虑使用自托管Runner
runs-on: [self-hosted, linux, x64]
Q:iOS构建失败,证书问题?
A:iOS证书配置流程:
# 1. 导出开发证书
openssl pkcs12 -in certificate.p12 -out certificate.pem -nodes
# 2. 在GitHub Secrets中存储
# 使用base64编码
base64 -i certificate.p12 > certificate.txt
# 3. 在CI中还原
echo "${{ secrets.IOS_CERTIFICATE }}" | base64 --decode > certificate.p12
security import certificate.p12 -k build.keychain -P "${{ secrets.CERT_PASSWORD }}"
Q:如何调试失败的CI?
A:调试技巧:
# 1. 启用调试日志
run: |
# 显示详细日志
flutter build apk --verbose
# 或使用环境变量
env:
FLUTTER_VERBOSE: true
# 2. 上传构建日志
- name: Upload build logs
if: failure()
uses: actions/upload-artifact@v3
with:
name: build-logs
path: |
~/flutter/bin/cache/
build/
# 3. 使用tmate进行SSH调试
- name: Setup tmate session
uses: mxschmitt/action-tmate@v3
if: failure() && github.ref == 'refs/heads/main'
8.2 Flutter问题
Q:不同版本兼容性?
A:版本管理策略:
# 使用版本测试兼容性
strategy:
matrix:
flutter-version: ['3.7.x', '3.10.x', 'stable']
# 在代码中检查版本
void checkFlutterVersion() {
const minVersion = '3.7.0';
final currentVersion = FlutterVersion.instance.version;
if (Version.parse(currentVersion) < Version.parse(minVersion)) {
throw Exception('Flutter version $minVersion or higher required');
}
}
Q:Web构建失败?
A:Web构建配置:
# 确保启用Web支持
- name: Enable web
run: flutter config --enable-web
# 构建Web版本
- name: Build for web
run: |
flutter build web \
--web-renderer canvaskit \
--release \
--dart-define=FLUTTER_WEB_USE_SKIA=true
# 处理Web特定问题
- name: Fix web issues
run: |
# 清理缓存
flutter clean
# 更新Web引擎
flutter precache --web
8.3 安全与权限问题
Q:如何管理敏感信息?
A:安全实践:
# 1. 使用环境级别的Secrets
env:
SUPER_SECRET_KEY: ${{ secrets.PRODUCTION_KEY }}
# 2. 最小权限原则
permissions:
contents: read
packages: write # 只有需要时才写
# 3. 使用临时凭证
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
# 4. 定期轮换密钥
# 设置提醒每月更新一次Secrets
最后
通过这篇教程我们掌握了Flutter CI/CD的核心知识,一个完美的流水线是一次次迭代出来的,需要不断优化。如果觉得文章对你有帮助,别忘了一键三连,支持一下
有任何问题或想法,欢迎在评论区交流讨论。