基础与工程篇-多环境配置(dev/test/prod)与打包策略

17 阅读4分钟

多环境配置(dev/test/prod)与打包策略

这是 Flutter 实战系列第 3 篇。
目标:把“环境隔离 + 打包发布”做成一套可长期维护、可团队协作的工程方案。


1. 问题背景:业务场景 + 现象

中大型项目里,通常至少有三套环境:

  • dev:联调、快速验证
  • test:测试回归、提测验收
  • prod:正式线上

如果环境治理不到位,会出现这些高频事故:

  1. 测试包误连线上接口
  2. 正式包带了调试开关或测试埋点
  3. iOS/Android 包名(BundleId/ApplicationId)和渠道配置混乱
  4. 发布前靠“手改常量”,极易出错
  5. CI 无法稳定复用同一构建流程

2. 原因分析:核心原理 + 排查过程

2.1 核心原理

多环境的本质不是“多套 if-else”,而是 “编译时注入 + 运行时消费”

  • 编译时决定:入口、包名、签名、图标、构建参数
  • 运行时只读配置:API 域名、日志级别、开关策略

2.2 常见反模式(踩坑总结)

  • kDebugMode 区分 dev/test/prod(这是错误的,debug/release 不是业务环境)
  • 把域名写死在业务代码中
  • 通过手动改文件来切环境
  • Android 有 flavor,iOS 却没有 scheme 对齐
  • 本地能打包,CI 打不出来(流程不可脚本化)

3. 解决方案:方案对比 + 最终选择

3.1 方案对比

方案 A:单入口 + 大量环境判断

优点:上手快
缺点:后期逻辑污染严重,易误发

方案 B:多入口 + 原生 flavor/scheme + 配置中心

优点:清晰、可自动化、风险低
缺点:初期配置略多

3.2 最终选择

采用 方案 B

  1. Dart 多入口(main_dev.dart / main_test.dart / main_prod.dart
  2. Android 用 Product Flavor
  3. iOS 用 Scheme + Build Configuration
  4. 环境参数统一走 --dart-define(或 --dart-define-from-file
  5. 发布脚本统一命令,不靠手工点点点

4. 关键代码:最小必要代码片段


4.1 环境枚举与配置模型

enum AppEnv { dev, test, prod }

class EnvConfig {
  final AppEnv env;
  final String apiBaseUrl;
  final bool enableLog;
  final String appNameSuffix;

  const EnvConfig({
    required this.env,
    required this.apiBaseUrl,
    required this.enableLog,
    required this.appNameSuffix,
  });
}

4.2 统一环境初始化入口

class Env {
  static late EnvConfig current;

  static void init(EnvConfig config) {
    current = config;
  }
}

4.3 三个入口文件(示例)

main_dev.dart

void main() {
  Env.init(
    const EnvConfig(
      env: AppEnv.dev,
      apiBaseUrl: 'https://api-dev.example.com',
      enableLog: true,
      appNameSuffix: ' DEV',
    ),
  );
  bootstrap();
}

main_test.dart

void main() {
  Env.init(
    const EnvConfig(
      env: AppEnv.test,
      apiBaseUrl: 'https://api-test.example.com',
      enableLog: true,
      appNameSuffix: ' TEST',
    ),
  );
  bootstrap();
}

main_prod.dart

void main() {
  Env.init(
    const EnvConfig(
      env: AppEnv.prod,
      apiBaseUrl: 'https://api.example.com',
      enableLog: false,
      appNameSuffix: '',
    ),
  );
  bootstrap();
}

4.4 网络层读取环境配置(禁止写死)

class ApiClient {
  static String get baseUrl => Env.current.apiBaseUrl;
}

4.5 --dart-define 方式(推荐)

如果不想维护三套入口,也可以单入口 + define:

const envName = String.fromEnvironment('APP_ENV', defaultValue: 'dev');

启动命令:

flutter run --dart-define=APP_ENV=dev
flutter run --dart-define=APP_ENV=test
flutter run --dart-define=APP_ENV=prod

4.6 Android Flavor(示例)

android/app/build.gradle 关键结构:

android {
  flavorDimensions "env"
  productFlavors {
    dev {
      dimension "env"
      applicationIdSuffix ".dev"
      resValue "string", "app_name", "YourApp Dev"
    }
    test {
      dimension "env"
      applicationIdSuffix ".test"
      resValue "string", "app_name", "YourApp Test"
    }
    prod {
      dimension "env"
      resValue "string", "app_name", "YourApp"
    }
  }
}

构建示例:

flutter build apk --flavor dev -t lib/main_dev.dart
flutter build apk --flavor test -t lib/main_test.dart
flutter build apk --flavor prod -t lib/main_prod.dart

4.7 iOS Scheme(建议)

  • 创建 Dev / Test / Prod 三个 Scheme
  • 对应三套 Build Configuration(如 Debug-Dev, Release-Test, Release-Prod
  • Bundle Identifier 与显示名后缀区分(避免同机覆盖)

构建示例:

flutter build ipa --flavor prod -t lib/main_prod.dart

注:iOS 的 flavor 依赖你在 Xcode 侧 scheme/configuration 的映射是否配置正确。


5. 效果验证:数据 / 截图 / 日志

  1. 接口域名验证:启动日志打印当前 apiBaseUrl
  2. 环境标识验证:App 首页显示 env 角标(dev/test)
  3. 包名验证:安装 dev/test/prod 可并存
  4. 日志开关验证:prod 构建关闭 debug 日志
  5. 签名验证:Android keystore、iOS provisioning 与环境匹配
  6. CI 验证:同一脚本可稳定产出三环境包

建议保留一行启动日志(发布时极有用):

debugPrint('ENV=${Env.current.env}, BASE_URL=${Env.current.apiBaseUrl}');

6. 可复用结论:通用经验 + 避坑清单

6.1 通用经验

  1. 环境差异放“配置”,不要放“业务逻辑”
  2. Debug/Release 与 Dev/Test/Prod 是两套维度,必须分开治理
  3. Android Flavor 与 iOS Scheme 必须一一对应
  4. 打包命令必须脚本化,避免人工操作
  5. 线上包默认最小化信息暴露(关闭调试开关与冗余日志)

6.2 避坑清单

  • 不要把线上 key、域名硬编码进页面/VM
  • 不要在发版前手改常量切环境
  • 不要只配 Android 不配 iOS
  • 不要忽略“同机并存”能力(dev/test/prod 易互相覆盖)
  • 不要让 CI 与本地命令不一致

附:可直接复用的命令模板

# Dev 调试
flutter run --flavor dev -t lib/main_dev.dart

# Test 调试
flutter run --flavor test -t lib/main_test.dart

# Prod 预发验证(release)
flutter run --release --flavor prod -t lib/main_prod.dart

# Android 打包
flutter build apk --release --flavor prod -t lib/main_prod.dart
flutter build appbundle --release --flavor prod -t lib/main_prod.dart

# iOS 打包
flutter build ipa --release --flavor prod -t lib/main_prod.dart

这套方案的核心价值是:环境切换可重复、发布流程可自动化、线上风险可控。只要把入口、原生 flavor/scheme、配置中心三件事统一起来,后续多人协作和 CI/CD 都会顺畅很多。