《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的三个核心概念

代码提交
持续集成 CI
持续交付 CD
持续部署 CD
自动构建
自动测试
自动打包
自动发布到测试
自动发布到生产

持续集成(CI):频繁集成代码到主干,每次集成都通过自动化测试

持续交付(CD):自动将代码打包成可部署的产物

持续部署(CD):自动将产物部署到生产环境

注意:两个CD虽然缩写一样,但含义不同。Continuous Delivery(持续交付)和 Continuous Deployment(持续部署)

二:GitHub Actions

我们以github为例,当然各公司有单独部署的gitlab,大同小异这里不在赘述。。。

2.1 GitHub Actions工作原理

GitHub Actions不是魔法,而是GitHub提供的自动化执行环境。想象一下:

你的代码仓库
事件推送/PR
GitHub Actions服务器
分配虚拟机
你的工作流
运行你的脚本

核心组件解析

# 工作流组件关系图
工作流文件 (.github/workflows/ci.yml)
    ├── 触发器: 什么情况下运行 (push, pull_request)
    ├── 任务: 在什么环境下运行 (ubuntu-latest)
    └── 步骤: 具体执行什么 (安装Flutter、运行测试)

2.2 创建你的第一个工作流

别被吓到,其实创建一个基础的CI流程只需要5分钟:

  1. 在项目根目录创建文件夹
mkdir -p .github/workflows
  1. 创建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
  1. 提交并推送代码
git add .github/workflows/flutter-ci.yml
git commit -m "添加CI工作流"
git push origin main

推送到GitHub后,打开你的仓库页面,点击"Actions"标签,你会看到一个工作流正在运行!

2.3 GitHub Actions架构

GitHub Actions架构
Runner执行环境
工作流步骤
触发事件
你的代码仓库
结果反馈
GitHub UI显示
邮件/通知
创建虚拟机
GitHub Actions Runner
检出代码
执行工作流
环境配置
执行脚本
产出物

核心概念解释

  1. Runner:GitHub提供的虚拟机(或你自己的服务器),用来执行工作流
  2. Workflow:工作流,一个完整的自动化流程
  3. Job:任务,工作流中的独立单元
  4. Step:步骤,任务中的具体操作
  5. 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 测试流水线

开发者Git仓库CI服务器单元测试服务Widget测试服务集成测试服务报告服务通知服务推送代码触发Webhook解析工作流配置分配测试资源启动单元测试准备环境执行测试分析覆盖率返回结果启动Widget测试准备UI环境执行测试截图对比返回结果启动集成测试准备设备执行测试端到端验证返回结果par[并行执行]收集所有结果请求生成报告生成详细报告返回报告发送成功通知通知开发者请求生成错误报告生成错误报告返回报告发送失败通知警报开发者alt[所有测试通过][有测试失败]开发者Git仓库CI服务器单元测试服务Widget测试服务集成测试服务报告服务通知服务

四:自动打包与发布流水线

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 打包发布流水线

00:0000:1500:3000:4501:0001:1501:3001:45代码提交检测 环境初始化 密钥加载 依赖安装 Android环境准备 iOS环境准备 环境变量设置 Android代码编译 配置文件解析 iOS代码编译 版本号处理 Android代码签名 Android打包 iOS证书配置 iOS打包 上传到测试平台 测试人员通知 测试执行周期 测试结果评估 生产环境准备 提交到应用商店 商店审核等待 发布完成通知 触发与准备Android构建iOS构建测试分发生产发布环境配置管理Flutter打包发布流水线

五:环境配置管理

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

  1. 打开GitHub仓库 → Settings → Secrets and variables → Actions
  2. 点击"New repository secret"
  3. 输入名称和值

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 设计环境配置

安全实践
环境配置管理架构
B[优先级]
C[敏感信息处理]
D[环境类型]
.env文件到Git
永远不要提交
忽略敏感文件
使用.gitignore
密钥和令牌
定期轮换
仅授予必要权限
最小权限原则
优先级
配置来源
敏感信息处理
环境类型
配置合并
最终配置
应用启动
API调用
功能开关
本地调试
开发环境
CI/CD测试
测试环境
生产前验证
预发环境
线上用户
生产环境
GitHub Secrets
密钥/密码
环境变量注入
API令牌
运行时获取
数据库连接
最高优先级
1. 运行时环境变量
中等优先级
2. 配置文件
最低优先级
3. 默认值

六:常见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

当前问题

  1. 手动测试,经常漏测
  2. 打包需要20分钟,且容易出错
  3. 不同开发者环境不一致
  4. 发布流程繁琐

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的核心知识,一个完美的流水线是一次次迭代出来的,需要不断优化。如果觉得文章对你有帮助,别忘了一键三连,支持一下


有任何问题或想法,欢迎在评论区交流讨论。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

QuantumLeap丶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值