Template
docs: 新增完整文档体系(setup-project / setup-android / setup-ios / CLAUDE.md)
This commit is contained in:
@@ -1,89 +1,242 @@
|
||||
# CLAUDE.md — sunny_mochi 脚手架项目规范
|
||||
# CLAUDE.md — Flutter 企业级脚手架(sunny_mochi)
|
||||
|
||||
## 项目简介
|
||||
> 本文件为 Claude Code 提供项目上下文。**所有 AI 辅助开发会话必须首先读取本文件**,
|
||||
> 再按需加载 `docs/` 下的补充文档。
|
||||
|
||||
`sunny_mochi` 是企业级 Flutter APP 脚手架模板,包含:
|
||||
- Flavor 三包体系(dev/staging/prod)
|
||||
- 完整 Core 基础设施层(网络/存储/崩溃/主题/观测性)
|
||||
- 认证骨架(Auth Feature)
|
||||
- 开发者工具面板(Dev Panel)
|
||||
- 本地错误日志 + 上报
|
||||
---
|
||||
|
||||
## 项目身份信息
|
||||
|
||||
> **以下带 `TODO` 的字段由业务项目接手时填入,脚手架默认保留占位符。**
|
||||
|
||||
| 字段 | 当前值(脚手架默认) | 说明 |
|
||||
|------|-----------------|------|
|
||||
| 项目名称 | sunny_mochi | TODO: 替换为实际项目名 |
|
||||
| Application ID (Android) | com.example.sunny_mochi | TODO: 如 com.company.projectname |
|
||||
| Bundle ID (iOS) | com.example.sunny-mochi | TODO: 与 Android 对应 |
|
||||
| API Base URL (dev) | http://localhost:8080 | TODO: 填入 `dart-defines/dev.json` |
|
||||
| API Base URL (staging) | https://staging.example.com | TODO: 填入 `dart-defines/staging.json` |
|
||||
| API Base URL (prod) | https://api.example.com | TODO: 填入 `dart-defines/prod.json` |
|
||||
| 后端鉴权框架 | sa-token(动态 header tokenName) | TODO: 若使用 Bearer/JWT 修改 auth_interceptor |
|
||||
| Sentry DSN | 空(未启用) | TODO: 填入 `dart-defines/prod.json` |
|
||||
| RSA 公钥 | REPLACE_WITH_RSA_PUBLIC_KEY | TODO: 填入三个 dart-defines JSON |
|
||||
|
||||
---
|
||||
|
||||
## 技术栈(脚手架锁定版本)
|
||||
|
||||
| 分类 | 技术 | 版本 |
|
||||
|------|------|------|
|
||||
| Flutter SDK | Flutter | ≥ 3.41.0 |
|
||||
| 状态管理 | Riverpod + riverpod_annotation | ^3.3.0 / ^4.0.0 |
|
||||
| 路由 | GoRouter + go_router_builder | ^17.0.0 / ^4.3.0 |
|
||||
| 网络 | Dio | ^5.9.0 |
|
||||
| 本地数据库 | Drift + SQLCipher | 2.31.0(锁定)|
|
||||
| 安全存储 | flutter_secure_storage | ^10.0.0 |
|
||||
| 序列化 | Freezed + json_serializable | ^3.x / 6.13.0(锁定)|
|
||||
| 日志 | Talker + talker_flutter | ^5.x |
|
||||
| 错误追踪 | Sentry Flutter | ^9.0.0 |
|
||||
| i18n | Slang | ^4.0.0 |
|
||||
| UI 自适应 | flutter_screenutil(基准 375×812)| ^5.9.0 |
|
||||
| 图片缓存 | cached_network_image | ^3.4.0 |
|
||||
| 字体 | HarmonyOS Sans SC(Regular/Medium/Bold/Black)| GB2312 子集 |
|
||||
|
||||
**版本锁定原因(勿随意升级):**
|
||||
- `drift: 2.31.0` — 2.32+ 需要 analyzer ^10.x,与 riverpod_generator 的 analyzer ^9.x 冲突
|
||||
- `json_serializable: 6.13.0` — 6.13.1+ 同样需要 analyzer ^10.x
|
||||
|
||||
---
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
lib/
|
||||
├── main.dart # 6 步启动序列
|
||||
├── app.dart # AppRoot → MaterialApp.router
|
||||
├── i18n/ # Slang 翻译文件
|
||||
├── main.dart # 6步启动序列(顺序固定,勿调整)
|
||||
├── app.dart # AppRoot → ScreenUtilInit → App → MaterialApp.router
|
||||
├── i18n/ # Slang 翻译(zh-CN 基准 + en 备用)
|
||||
├── core/
|
||||
│ ├── config/ # Env / ApiConfig / ApiPaths
|
||||
│ ├── router/ # GoRouter + TypedRoutes
|
||||
│ ├── network/ # Dio 7 拦截器 + MockAdapter
|
||||
│ ├── storage/ # Drift + SQLCipher + SecureStorage
|
||||
│ ├── crash/ # CrashReporter 3 步生命周期
|
||||
│ ├── error/ # sealed Failure 层级
|
||||
│ ├── theme/ # 5 色板 + 深色模式
|
||||
│ ├── observability/ # Talker + Sentry
|
||||
│ ├── sync/ # Offline-First 同步框架
|
||||
│ ├── crypto/ # RSA 加密工具
|
||||
│ ├── extensions/ # BuildContext / String / num / DateTime
|
||||
│ ├── utils/ # 验证器 / 密码强度 / SMS 倒计时
|
||||
│ ├── data/ # FreshnessPolicy 缓存策略
|
||||
│ └── widgets/ # 通用 UI 组件库
|
||||
│ ├── config/
|
||||
│ │ ├── env.dart # 从 dart-defines 读取环境变量(只读,勿改结构)
|
||||
│ │ ├── api_config.dart # baseUrl 按 Flavor 决策
|
||||
│ │ └── api_paths.dart # ★ 新项目必填:所有 API 路径常量
|
||||
│ ├── router/
|
||||
│ │ ├── app_router.dart # GoRouter provider(keepAlive)
|
||||
│ │ └── routes.dart # ★ 新项目扩展:添加 @TypedGoRoute
|
||||
│ ├── network/
|
||||
│ │ ├── dio_client.dart # 7 拦截器注册(顺序固定)
|
||||
│ │ ├── api_response.dart # 统一 envelope 解析(parseEnvelope / unwrapVoid)
|
||||
│ │ ├── response_code.dart# 业务状态码(成功=00000,token 类码分类)
|
||||
│ │ ├── interceptors/ # 7 个拦截器(勿修改拦截顺序)
|
||||
│ │ └── mock/ # MockAdapter + fixture JSON(dev 调试用)
|
||||
│ ├── storage/
|
||||
│ │ ├── app_database.dart # Drift @DriftDatabase(schemaVersion 从 1 开始)
|
||||
│ │ ├── secure_storage.dart# token / userId / tokenName 的唯一真源
|
||||
│ │ ├── db_key_provider.dart# AES-256 密钥派生(勿修改策略)
|
||||
│ │ ├── tables/ # Drift 表定义(Users + ErrorLogs + 业务表)
|
||||
│ │ └── daos/ # DAO(只放纯 DB 操作)
|
||||
│ ├── crash/ # CrashReporter(preInit/consumePending/installHooks)
|
||||
│ ├── error/
|
||||
│ │ ├── failures.dart # Sealed Failure 层级(唯一错误分类)
|
||||
│ │ └── exception_mapper.dart # 异常 → Failure 映射
|
||||
│ ├── theme/ # 5 色板 + 深色模式(ThemeNotifier + SharedPreferences)
|
||||
│ ├── observability/ # Talker(全局 appTalker)+ Sentry(Release 上报)
|
||||
│ ├── sync/ # Offline-First 同步框架骨架(SyncService)
|
||||
│ ├── crypto/ # RSA PKCS#1 v1.5(RsaHelper.encrypt)
|
||||
│ ├── extensions/ # BuildContextX / StringX / NumX / DateTimeX
|
||||
│ ├── utils/ # Validator / PasswordValidator / SmsCountdownMixin
|
||||
│ ├── data/ # FreshnessPolicy(缓存新鲜度判断)
|
||||
│ └── widgets/ # 通用 UI 组件(AppToast / AppButton / EmptyView 等)
|
||||
└── features/
|
||||
├── auth/ # 认证 Feature(完整 Clean Architecture)
|
||||
├── dev_panel/ # Talker 调试面板(Internal 包)
|
||||
└── error_report/ # 本地错误日志查看 + 上报
|
||||
├── auth/ # ★ 认证(完整骨架,业务项目修改 LoginPage UI)
|
||||
├── dev_panel/ # Talker 面板(Internal 包,勿在 release 中暴露入口)
|
||||
└── error_report/ # 本地错误日志 + 上报(完整实现)
|
||||
```
|
||||
|
||||
## 新项目初始化清单
|
||||
---
|
||||
|
||||
1. **替换包名**:`sunny_mochi` → 你的包名(android/app/build.gradle.kts + pubspec.yaml + import)
|
||||
2. **填入 API 路径**:`lib/core/config/api_paths.dart` 中所有空字符串
|
||||
3. **配置 Flavor**:`android/app/build.gradle.kts` applicationId + 签名配置
|
||||
4. **填入 Sentry DSN**:`dart-defines/prod.json`
|
||||
5. **填入 RSA 公钥**:`dart-defines/dev.json` 的 `rsaPublicKey` 字段
|
||||
6. **替换 i18n 字符串**:`lib/i18n/strings_zh_CN.i18n.json` 按需扩展
|
||||
7. **添加业务路由**:`lib/core/router/routes.dart` 新增 TypedGoRoute
|
||||
8. **实现 Tab 页面**:替换 HomeRoute / MineRoute 的 placeholder build
|
||||
## 开发命令
|
||||
|
||||
## 零容忍规则
|
||||
```bash
|
||||
# 代码生成(修改 Dart 文件后运行)
|
||||
make gen
|
||||
|
||||
# i18n 生成(修改 JSON 翻译文件后运行)
|
||||
make gen-i18n
|
||||
|
||||
# 运行
|
||||
make run-dev # dev 包(连 dev 服务器,mock 可开)
|
||||
make run-staging # staging 包(连 test 服务器,release 模式)
|
||||
|
||||
# 构建
|
||||
make build-apk-dev # dev APK(快速真机验证)
|
||||
make build-staging # staging APK(交测试团队)
|
||||
make build-prod # prod AAB(需 prod.json + key.properties)
|
||||
|
||||
# 质量检查
|
||||
flutter analyze # 目标 0 errors(103 条 info 为正常)
|
||||
flutter test # 全量测试
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 常用开发范式
|
||||
|
||||
### 新增 Feature
|
||||
|
||||
```
|
||||
lib/features/{feature_name}/
|
||||
├── domain/
|
||||
│ ├── entities/{name}_entity.dart # @freezed,纯 Dart 字段
|
||||
│ └── repositories/{name}_repository.dart # abstract interface
|
||||
├── data/
|
||||
│ ├── models/{name}_model.dart # @freezed + fromJson,含 toEntity()
|
||||
│ ├── datasources/{name}_remote_datasource.dart # @riverpod,只调 Dio
|
||||
│ └── repositories/{name}_repository_impl.dart # @riverpod,实现 domain 接口
|
||||
└── presentation/
|
||||
├── notifiers/{name}_notifier.dart # @riverpod class XxxNotifier
|
||||
└── pages/{name}_page.dart # ConsumerWidget / ConsumerStatefulWidget
|
||||
```
|
||||
|
||||
### 新增路由
|
||||
|
||||
在 `lib/core/router/routes.dart` 添加(然后运行 `make gen`):
|
||||
|
||||
```dart
|
||||
@TypedGoRoute<MyNewRoute>(path: '/my-new-path')
|
||||
class MyNewRoute extends GoRouteData with $MyNewRoute {
|
||||
const MyNewRoute();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, GoRouterState state) => const MyNewPage();
|
||||
}
|
||||
```
|
||||
|
||||
### 新增 API 路径
|
||||
|
||||
在 `lib/core/config/api_paths.dart` 添加:
|
||||
|
||||
```dart
|
||||
// ============== My Feature ==============
|
||||
static const String myFeatureList = '/api/v1/my-feature/list';
|
||||
```
|
||||
|
||||
### 新增 DB 表
|
||||
|
||||
1. 在 `lib/core/storage/tables/` 新建表文件
|
||||
2. 在 `lib/core/storage/app_database.dart` 的 `@DriftDatabase(tables: [...])` 添加
|
||||
3. 递增 `schemaVersion` 并在 `MigrationStrategy.onUpgrade` 中添加迁移
|
||||
4. 运行 `make gen`
|
||||
|
||||
### 错误处理标准模式
|
||||
|
||||
```dart
|
||||
// Notifier 层:捕获 + 映射
|
||||
} on Object catch (e, st) {
|
||||
final failure = ExceptionMapper().fromUnknown(e, st);
|
||||
state = MyState.error(failure.message);
|
||||
}
|
||||
|
||||
// UI 层:监听 + 显示
|
||||
ref.listen(myProvider, (_, next) {
|
||||
if (next is AsyncError) context.showError(ref, next.error!, next.stackTrace!);
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 零容忍规则(AI 必须遵守)
|
||||
|
||||
| 规则 | 正确 | 禁止 |
|
||||
|------|------|------|
|
||||
| API 路径 | `ApiPaths.xxx` | 字面量字符串 |
|
||||
| 错误处理 | `ExceptionMapper.fromUnknown()` | 散落的 `catch(e)` 直接显示 |
|
||||
| Token 存储 | `SecureStorage`(SoT) | 普通 SharedPreferences |
|
||||
| DB 加密 | SQLCipher(默认) | 无加密 sqflite |
|
||||
| 日志 | `appTalker.xxx()` | `print()` / `debugPrint()` |
|
||||
| 路由跳转 | `LoginRoute().go(context)` | `Navigator.push()` |
|
||||
| API 路径 | `ApiPaths.xxx` 常量 | 字符串字面量散落在代码中 |
|
||||
| 错误处理 | `ExceptionMapper().fromUnknown(e, st)` | 裸 `catch` 直接 `toString()` 显示 |
|
||||
| Token 存储 | `SecureStorage`(Keychain/EncryptedPrefs)| 普通 `SharedPreferences` |
|
||||
| 数据库加密 | SQLCipher(默认) | 无加密 `sqflite` |
|
||||
| 日志输出 | `appTalker.info/warning/error()` | `print()` / `debugPrint()` |
|
||||
| 路由跳转 | `XxxRoute().go(context)` / `XxxRoute().push(context)` | `Navigator.push()` |
|
||||
| 状态管理 | Riverpod `@riverpod` / `@Riverpod(keepAlive: true)` | `setState` 跨组件共享 / `Provider` 包 |
|
||||
| Riverpod 命名 | 生成 provider 名(`AuthNotifier` → `authProvider`)| 手写 `authNotifierProvider` |
|
||||
| 代码生成文件 | 只读,不手写 `.g.dart` / `.freezed.dart` | 手动修改生成文件 |
|
||||
| Domain 层 | 纯 Dart,无 Flutter / Drift / Dio 依赖 | domain 层 import `package:dio` |
|
||||
| Mock fixture | `assets/fixtures/{group}/{name}.json` | 在代码中硬编码 mock 数据 |
|
||||
| 注释语言 | 中文(团队约定) | 英文注释(除公共 API doc)|
|
||||
|
||||
## 常用命令
|
||||
---
|
||||
|
||||
```bash
|
||||
# 代码生成
|
||||
make gen
|
||||
## 关键架构决策(勿重议)
|
||||
|
||||
# i18n 生成
|
||||
make gen-i18n
|
||||
1. **SQLCipher 不可替换**:企业合规要求,部署后无法无损切换到无加密数据库
|
||||
2. **7 拦截器顺序固定**:CertPinning→Auth→TokenRefresh→Retry→Error→AuthLogout→Log,错误处理链依赖此顺序
|
||||
3. **TokenRefresh 使用 Completer 互斥**:防止并发 401 重复消耗 RefreshToken,不可改为简单 flag
|
||||
4. **SecureStorage 是 token 唯一真源**:DB 中的 refreshToken 仅作可观测性镜像
|
||||
5. **GoRouter 用 `ref.read`(非 `ref.watch`)构建**:router 是 keepAlive 单例,通过 `refreshListenable` 响应 auth 变化
|
||||
6. **Failure 不可绕过**:所有网络/DB 异常必须经 ExceptionMapper 映射,UI 只看 Failure.message
|
||||
|
||||
# 运行(dev 包)
|
||||
make run-dev
|
||||
---
|
||||
|
||||
# 构建 staging APK
|
||||
make build-staging
|
||||
## 新项目接手 Checklist
|
||||
|
||||
# 静态分析
|
||||
flutter analyze
|
||||
> 完成后删除本节或移至项目 wiki
|
||||
|
||||
# 测试
|
||||
flutter test --coverage
|
||||
```
|
||||
- [ ] 更新本文件"项目身份信息"表格
|
||||
- [ ] 填入 `lib/core/config/api_paths.dart` 所有路径
|
||||
- [ ] 配置三个 `dart-defines/*.json`(含 RSA 公钥、Sentry DSN)
|
||||
- [ ] 修改 `android/app/build.gradle.kts` 中的 `applicationId`
|
||||
- [ ] 修改 `pubspec.yaml` 中的 `name`(含相关 import 路径)
|
||||
- [ ] 完成 `docs/setup-android.md` 中的 Android 签名配置
|
||||
- [ ] 完成 `docs/setup-ios.md` 中的 iOS Scheme / Signing 配置
|
||||
- [ ] 替换登录页 UI 品牌(`lib/features/auth/presentation/pages/login_page.dart`)
|
||||
- [ ] 替换 App Icon(Android mipmap / iOS AppIcon.appiconset)
|
||||
- [ ] 实现业务 Tab 页面(替换 `routes.dart` 中的 placeholder build)
|
||||
- [ ] 更新 README.md 项目描述
|
||||
|
||||
## 关键技术决策
|
||||
---
|
||||
|
||||
- **SQLCipher**:企业合规要求加密存储,部署后无法无损迁移到无加密
|
||||
- **7 拦截器栈**:Token 刷新互斥(Completer)+ 证书绑定是安全基础,不可简化
|
||||
- **sealed Failure**:统一错误分类,UI / 日志 / Sentry 三层复用同一模型
|
||||
- **schemaVersion=1**:脚手架全新 DB,不携带任何历史迁移负担
|
||||
## 参考文档
|
||||
|
||||
| 文档 | 内容 |
|
||||
|------|------|
|
||||
| [README.md](README.md) | 项目概述、技术栈、快速开始 |
|
||||
| [docs/setup-project.md](docs/setup-project.md) | 完整项目配置指南(所有开发者必读)|
|
||||
| [docs/setup-android.md](docs/setup-android.md) | Android Flavor / 签名 / 构建详细配置 |
|
||||
| [docs/setup-ios.md](docs/setup-ios.md) | iOS Scheme / CocoaPods / 签名详细配置 |
|
||||
|
||||
@@ -1,123 +1,156 @@
|
||||
# sunny_mochi — Flutter 企业级脚手架
|
||||
|
||||
基于 Flutter 3.x 的企业级 APP 脚手架,开箱即用的基础设施底座。
|
||||
> **新项目接手后,请将本文件第一行替换为实际项目名和简介。**
|
||||
|
||||
## 包含能力
|
||||
|
||||
| 模块 | 说明 |
|
||||
|------|------|
|
||||
| Flavor 三包 | dev / staging / prod 环境共存 |
|
||||
| 加密数据库 | Drift + SQLCipher AES-256 |
|
||||
| 网络层 | 7 拦截器 Dio(Token 刷新互斥、证书绑定、错误映射) |
|
||||
| 崩溃日志 | 同步写文件 + 启动时弹窗上报 |
|
||||
| 错误分类 | Sealed Failure 体系(Network / Auth / Server / Cache) |
|
||||
| 主题系统 | 5 套色板 + 深色模式 + Riverpod 持久化 |
|
||||
| 开发者面板 | Talker UI(Internal 包可见,Release 编译期关闭) |
|
||||
| 认证骨架 | Clean Architecture:手机号 + 短信 / 密码双模式登录 |
|
||||
| 本地错误日志 | 查看 + 一键上报 |
|
||||
| i18n | Slang 4.x(zh-CN 基准 + en 备用) |
|
||||
基于 Flutter 3.x 的企业级 APP 模板底座,涵盖开发 / 测试 / 生产三包共存体系、加密本地数据库、完整网络安全栈、崩溃与错误日志、主题系统和认证骨架,让新项目从第一天就拥有生产级基础设施。
|
||||
|
||||
---
|
||||
|
||||
## 克隆后首次设置
|
||||
## 已实现的基础设施
|
||||
|
||||
### 构建体系
|
||||
- **Flavor 三包**:dev(Mock 可开)/ staging(连测试服)/ prod(正式签名)
|
||||
- **dart-defines JSON**:环境变量与代码完全解耦,CI/CD 通过 secret 注入 prod 配置
|
||||
- **Android productFlavors + iOS xcconfig**:三套应用名、ID、签名互不干扰
|
||||
|
||||
### 网络层(Dio,7 拦截器固定顺序)
|
||||
| 顺序 | 拦截器 | 作用 |
|
||||
|------|--------|------|
|
||||
| 1 | CertPinning | SSL 证书绑定,防 MITM |
|
||||
| 2 | Auth | 注入 sa-token 动态 header(tokenName 从 SecureStorage 读取)|
|
||||
| 3 | TokenRefresh | 401 时静默刷新,Completer 互斥防并发重复消耗 |
|
||||
| 4 | Retry | 网络超时 / 5xx 指数退避重试(最多 3 次)|
|
||||
| 5 | Error | DioException → sealed Failure 映射 |
|
||||
| 6 | AuthLogout | AuthFailure(unauthorized/refreshFailed) → 清 token + 跳登录 |
|
||||
| 7 | Log | TalkerDioLogger(仅 dev/Internal 包输出)|
|
||||
|
||||
### 本地存储
|
||||
- **Drift 2.31.0 + SQLCipher**:AES-256 加密 SQLite,密钥由 `DbKeyProvider` 派生并存入 Keychain
|
||||
- **SecureStorage**:token / refreshToken / tokenName / userId 的唯一真源(Keychain on iOS,EncryptedSharedPreferences on Android)
|
||||
- **Offline-First 同步框架**:`SyncService` + `SyncColumns` mixin,4 字段脏数据追踪骨架
|
||||
|
||||
### 错误体系
|
||||
```
|
||||
Failure (sealed)
|
||||
├── NetworkFailure → 超时 / 无网络 / 证书错误 / 请求取消
|
||||
├── AuthFailure → 未授权 / token 过期 / 刷新失败 / 无权限 / 加密失败
|
||||
├── ServerFailure → HTTP 4xx/5xx / 业务码非 00000
|
||||
├── CacheFailure → Drift / IO 异常
|
||||
└── UnknownFailure → 兜底
|
||||
```
|
||||
|
||||
### 崩溃日志
|
||||
三步生命周期:`preInit()` → 启动 → `consumePending()`(读取上次崩溃)→ `installHooks()`(接管 Flutter/Zone 全局异常)。崩溃数据同步写文件(不依赖异步),重启后弹窗展示并提供上报入口。
|
||||
|
||||
### 主题系统
|
||||
5 套色板(blue / red / green / purple / teal)× 浅色/深色模式,通过 `ThemeNotifier`(SharedPreferences 持久化)+ Riverpod 全局响应式切换。
|
||||
|
||||
### 认证骨架(Clean Architecture)
|
||||
```
|
||||
domain/entities/UserEntity ← 纯 Dart,无框架依赖
|
||||
domain/repositories/AuthRepository ← abstract interface
|
||||
data/models/UserModel ← Freezed + JSON,含 toEntity()
|
||||
data/datasources/AuthRemoteDatasource ← Dio 调用
|
||||
data/repositories/AuthRepositoryImpl ← 接口实现
|
||||
presentation/notifiers/AuthNotifier ← @riverpod,generation 计数防竞态
|
||||
presentation/notifiers/AuthStatusController ← ValueNotifier<bool?> 三态门面
|
||||
presentation/pages/LoginPage ← 手机号 + SMS/密码双模式骨架
|
||||
```
|
||||
|
||||
### 可观测性
|
||||
- **Talker**:全局 `appTalker`,dev/Internal 包输出,Release 编译期关闭
|
||||
- **Sentry**:`SentrySetup.init()` 包裹 `runApp`,空 DSN 时自动跳过,Release 才上报
|
||||
- **DevPanel Feature**:Internal 包内 TalkerScreen 入口,方便调试网络 / 状态变化
|
||||
|
||||
### 通用 Widget 库
|
||||
`AppToast`(Overlay 动画条)、`AppButton`(带 loading)、`AppTextField`、`EmptyView`、`ErrorView`、`LoadingOverlay`、`ConfirmDialog`、`SectionCard`、`SectionTitle`、`Skeleton`、`InfoRow`、`CountDownButton`、`AvatarWidget`、`TagChip`
|
||||
|
||||
---
|
||||
|
||||
## 技术栈版本
|
||||
|
||||
| 技术 | 版本 | 说明 |
|
||||
|------|------|------|
|
||||
| Flutter | ≥ 3.41.0 | |
|
||||
| Dart | ≥ 3.7.0 | |
|
||||
| flutter_riverpod | ^3.3.0 | |
|
||||
| go_router | ^17.0.0 | TypedRoutes + StatefulShellRoute |
|
||||
| drift | 2.31.0 | **锁定**,2.32+ analyzer 冲突 |
|
||||
| sqlcipher_flutter_libs | ^0.6.0 | |
|
||||
| freezed_annotation | ^3.0.0 | |
|
||||
| json_serializable | 6.13.0 | **锁定**,6.13.1+ analyzer 冲突 |
|
||||
| dio | ^5.9.0 | |
|
||||
| flutter_secure_storage | ^10.0.0 | |
|
||||
| slang | ^4.0.0 | i18n,zh-CN 基准 |
|
||||
| talker_flutter | ^5.0.0 | |
|
||||
| sentry_flutter | ^9.0.0 | |
|
||||
| flutter_screenutil | ^5.9.0 | 设计基准 375×812pt |
|
||||
| pointycastle + asn1lib | ^4.0.0 / ^1.5.0 | RSA PKCS#1 v1.5 |
|
||||
|
||||
---
|
||||
|
||||
## 快速开始
|
||||
|
||||
**前提**:已安装 Flutter ≥ 3.41.0、Android Studio(Android SDK)、Xcode 16+(iOS 开发)。
|
||||
|
||||
### 1. 安装依赖
|
||||
```bash
|
||||
# 1. 克隆(替换为业务项目实际地址)
|
||||
git clone https://gitea.example.com/your-org/your-project.git
|
||||
cd your-project
|
||||
|
||||
# 2. 安装依赖
|
||||
flutter pub get
|
||||
|
||||
# 3. 代码生成
|
||||
make gen # 生成 .g.dart / .freezed.dart
|
||||
make gen-i18n # 生成 lib/i18n/strings.g.dart
|
||||
|
||||
# 4. 运行(dev 包,Mock 模式)
|
||||
make run-dev
|
||||
```
|
||||
|
||||
### 2. 代码生成
|
||||
```bash
|
||||
make gen # build_runner → .g.dart / .freezed.dart
|
||||
make gen-i18n # slang → lib/i18n/strings.g.dart
|
||||
```
|
||||
|
||||
### 3. 填入项目 API 路径
|
||||
编辑 `lib/core/config/api_paths.dart`,将所有空字符串替换为实际路径。
|
||||
|
||||
### 4. 配置 Android 签名(发布版必须)
|
||||
```bash
|
||||
cp android/key.properties.template android/key.properties
|
||||
# 编辑 key.properties,填入 keystore 路径和密码
|
||||
```
|
||||
|
||||
### 5. 配置生产环境变量
|
||||
```bash
|
||||
cp dart-defines/prod.json.template dart-defines/prod.json
|
||||
# 编辑 prod.json,填入 Sentry DSN、RSA 公钥等
|
||||
```
|
||||
|
||||
### 6. iOS(首次或 Pod 变更后)
|
||||
```bash
|
||||
cd ios && pod install && cd ..
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 常用命令
|
||||
|
||||
```bash
|
||||
make run-dev # 开发版调试运行
|
||||
make run-staging # 测试版 Release 运行
|
||||
make build-staging # 打测试包 APK
|
||||
make build-prod # 打正式版 AAB(需先配置 prod.json + key.properties)
|
||||
make gen # 代码生成
|
||||
make gen-i18n # i18n 生成
|
||||
flutter analyze # 静态分析(目标 0 errors)
|
||||
flutter test # 单元测试
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 新项目初始化清单
|
||||
|
||||
- [ ] 替换包名 `com.example.sunny_mochi` → 项目包名(`android/app/build.gradle.kts` + `pubspec.yaml`)
|
||||
- [ ] 填入 API 路径(`lib/core/config/api_paths.dart`)
|
||||
- [ ] 配置 `android/key.properties`(从 `key.properties.template` 复制)
|
||||
- [ ] 配置 `dart-defines/prod.json`(从 `prod.json.template` 复制)
|
||||
- [ ] 替换 App 名称(`android/app/build.gradle.kts` `resValue`、`ios/Runner/Info.plist`)
|
||||
- [ ] 替换 App Icon(`android/app/src/main/res/mipmap-*/`、`ios/Runner/Assets.xcassets/AppIcon.appiconset/`)
|
||||
- [ ] 实现 Tab 页面(替换 `lib/core/router/routes.dart` 中的 placeholder build)
|
||||
- [ ] 添加业务路由(`lib/core/router/routes.dart` 新增 `@TypedGoRoute`)
|
||||
- [ ] 配置 Sentry DSN(`dart-defines/prod.json`)
|
||||
- [ ] 配置 RSA 公钥(`dart-defines/dev.json`、`staging.json`、`prod.json`)
|
||||
> 首次运行前请阅读 [docs/setup-project.md](docs/setup-project.md) 完成环境配置。
|
||||
|
||||
---
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
lib/
|
||||
├── main.dart # 6 步启动序列
|
||||
├── app.dart # AppRoot → MaterialApp.router
|
||||
├── i18n/ # Slang 翻译
|
||||
├── core/
|
||||
│ ├── config/ # Env / ApiConfig / ApiPaths(填入 API 路径)
|
||||
│ ├── router/ # GoRouter + TypedRoutes(添加业务路由)
|
||||
│ ├── network/ # Dio 7 拦截器 + MockAdapter
|
||||
│ ├── storage/ # Drift + SQLCipher + SecureStorage
|
||||
│ ├── crash/ # CrashReporter 生命周期
|
||||
│ ├── error/ # Sealed Failure 分类
|
||||
│ ├── theme/ # 5 色板 + 深色模式
|
||||
│ ├── observability/ # Talker + Sentry
|
||||
│ ├── sync/ # Offline-First 同步框架骨架
|
||||
│ ├── crypto/ # RSA 加密
|
||||
│ ├── extensions/ # BuildContext / String / num / DateTime
|
||||
│ ├── utils/ # 验证器 / 密码强度 / SMS 倒计时
|
||||
│ └── widgets/ # 通用 UI 组件库
|
||||
└── features/
|
||||
├── auth/ # 认证(完整 Clean Architecture,替换登录 UI 品牌)
|
||||
├── dev_panel/ # Talker 调试面板(Internal 包)
|
||||
└── error_report/ # 本地错误日志 + 上报
|
||||
|
||||
android/
|
||||
├── app/build.gradle.kts # Flavor 三包 + SQLCipher + 签名配置
|
||||
├── key.properties.template # 签名配置模板(复制为 key.properties 并填入密码)
|
||||
└── keystore/ # 放置 .jks 文件(.gitignore 中,勿提交)
|
||||
|
||||
dart-defines/
|
||||
├── dev.json # 开发环境变量(占位符,可提交)
|
||||
├── staging.json # 测试环境变量(占位符,可提交)
|
||||
├── prod.json.template # 生产环境变量模板
|
||||
└── prod.json # 生产真实配置(.gitignore 中,勿提交)
|
||||
├── android/
|
||||
│ ├── app/build.gradle.kts # Flavor 三包 + SQLCipher + 签名配置
|
||||
│ ├── key.properties.template # 签名配置模板(复制为 key.properties 并填入密码)
|
||||
│ └── keystore/ # 存放 .jks 文件(gitignore,勿提交)
|
||||
├── ios/
|
||||
│ ├── Flutter/flavors/ # dev / staging / prod xcconfig
|
||||
│ └── Podfile # iOS 15.0+,pod install 后生成 Pods/
|
||||
├── dart-defines/
|
||||
│ ├── dev.json # 开发环境(占位符,可提交)
|
||||
│ ├── staging.json # 测试环境(占位符,可提交)
|
||||
│ ├── prod.json.template # 生产模板(复制为 prod.json 并填入真实值)
|
||||
│ └── prod.json # 生产真实配置(gitignore,勿提交)
|
||||
├── assets/
|
||||
│ ├── fonts/ # HarmonyOS Sans SC(4 字重)
|
||||
│ ├── fixtures/ # Mock JSON(dev 调试用)
|
||||
│ └── themes/ # 主题图片(业务项目按 AppColorScheme 放置)
|
||||
├── lib/ # 详见 CLAUDE.md 目录结构
|
||||
├── docs/
|
||||
│ ├── setup-project.md # 完整项目配置指南
|
||||
│ ├── setup-android.md # Android Flavor / 签名详细配置
|
||||
│ └── setup-ios.md # iOS Scheme / CocoaPods / 签名详细配置
|
||||
├── CLAUDE.md # AI 辅助开发上下文(技术栈 / 规范 / 零容忍规则)
|
||||
├── Makefile # 常用命令
|
||||
├── pubspec.yaml # 依赖(含版本锁定 dependency_overrides)
|
||||
└── slang.yaml # i18n 配置(base_locale: zh-CN)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 文档导航
|
||||
|
||||
| 文档 | 适用人群 | 内容 |
|
||||
|------|---------|------|
|
||||
| **本文(README.md)** | 所有成员 | 项目概述、技术栈、快速开始 |
|
||||
| [docs/setup-project.md](docs/setup-project.md) | 所有开发者 | 环境配置、API 路径、dart-defines 填写 |
|
||||
| [docs/setup-android.md](docs/setup-android.md) | Android / 全栈 | Flavor、签名、keystore、构建 |
|
||||
| [docs/setup-ios.md](docs/setup-ios.md) | iOS / 全栈 | Scheme、CocoaPods、证书、Archive |
|
||||
| [CLAUDE.md](CLAUDE.md) | AI 辅助开发 | 架构规范、零容忍规则、开发范式 |
|
||||
|
||||
@@ -0,0 +1,233 @@
|
||||
# Android 配置指南
|
||||
|
||||
本文档覆盖 Android 端从 Flavor 验证到正式签名发布包所需的全部配置步骤。
|
||||
|
||||
---
|
||||
|
||||
## 前提
|
||||
|
||||
- Android Studio Meerkat (2024.3) 或更高版本
|
||||
- Android SDK:API 34(compileSdk)/ API 21(minSdk)
|
||||
- JDK 17(Android Gradle Plugin 要求)
|
||||
- 已完成 [setup-project.md](setup-project.md) 的 Step 1–4
|
||||
|
||||
---
|
||||
|
||||
## 1. 验证 Flavor 构建
|
||||
|
||||
脚手架预配置了三个 Flavor:`dev` / `staging` / `prod`,对应三套应用名和包名后缀。
|
||||
|
||||
```bash
|
||||
# 确认三个 Flavor 都能构建(debug 模式,速度最快)
|
||||
make build-apk-dev # com.example.sunny_mochi.dev
|
||||
|
||||
flutter build apk --flavor staging --debug \
|
||||
--dart-define-from-file=dart-defines/staging.json
|
||||
# com.example.sunny_mochi.staging
|
||||
|
||||
flutter build apk --flavor prod --debug \
|
||||
--dart-define-from-file=dart-defines/prod.json
|
||||
# com.example.sunny_mochi
|
||||
```
|
||||
|
||||
若构建失败,检查 `android/app/build.gradle.kts` 中的 `flavorDimensions` 和 `productFlavors` 配置。
|
||||
|
||||
---
|
||||
|
||||
## 2. 修改包名(业务项目必须)
|
||||
|
||||
编辑 `android/app/build.gradle.kts`,找到 `productFlavors` 和 `defaultConfig`:
|
||||
|
||||
```kotlin
|
||||
defaultConfig {
|
||||
applicationId = "com.your_company.your_app" // ← 修改
|
||||
// ...
|
||||
}
|
||||
|
||||
productFlavors {
|
||||
create("dev") {
|
||||
applicationIdSuffix = ".dev"
|
||||
// applicationId 实际为 com.your_company.your_app.dev
|
||||
resValue("string", "app_name", "YourApp Dev") // ← 修改应用名
|
||||
}
|
||||
create("staging") {
|
||||
applicationIdSuffix = ".staging"
|
||||
resValue("string", "app_name", "YourApp Beta") // ← 修改应用名
|
||||
}
|
||||
create("prod") {
|
||||
resValue("string", "app_name", "YourApp") // ← 修改应用名
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
同步修改 `android/app/src/main/AndroidManifest.xml` 确认使用 `@string/app_name`(脚手架默认已使用)。
|
||||
|
||||
---
|
||||
|
||||
## 3. 配置 Release 签名
|
||||
|
||||
### 3.1 生成 Keystore(首次)
|
||||
|
||||
```bash
|
||||
mkdir -p android/keystore
|
||||
keytool -genkey -v \
|
||||
-keystore android/keystore/release.jks \
|
||||
-alias your-key-alias \
|
||||
-keyalg RSA \
|
||||
-keysize 2048 \
|
||||
-validity 10000
|
||||
```
|
||||
|
||||
> ⚠️ **Keystore 丢失无法找回,必须妥善保管。** 建议:
|
||||
> - 备份到加密云存储(1Password / Bitwarden Vault)
|
||||
> - CI/CD 系统通过 secret 变量注入,不放在仓库中
|
||||
> - `android/keystore/` 目录已在 `.gitignore` 中
|
||||
|
||||
### 3.2 填写 key.properties
|
||||
|
||||
```bash
|
||||
cp android/key.properties.template android/key.properties
|
||||
```
|
||||
|
||||
编辑 `android/key.properties`(相对于 `android/app/` 目录):
|
||||
|
||||
```properties
|
||||
storePassword=your-keystore-password
|
||||
keyPassword=your-key-password
|
||||
keyAlias=your-key-alias
|
||||
storeFile=../keystore/release.jks
|
||||
```
|
||||
|
||||
> `key.properties` 已在 `android/.gitignore` 中,不会被提交。
|
||||
|
||||
### 3.3 验证签名配置
|
||||
|
||||
```bash
|
||||
# 构建 staging release APK(使用 release 签名)
|
||||
make build-staging
|
||||
|
||||
# 查看签名信息
|
||||
apksigner verify --print-certs \
|
||||
build/app/outputs/flutter-apk/app-staging-release.apk
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 配置 Firebase(可选)
|
||||
|
||||
若业务项目使用 Firebase(推送通知 / Analytics):
|
||||
|
||||
1. 在 [Firebase Console](https://console.firebase.google.com) 为每个 Flavor 创建应用(不同包名)
|
||||
2. 下载各包名对应的 `google-services.json`,放置到对应 Flavor 目录:
|
||||
|
||||
```
|
||||
android/app/src/
|
||||
├── dev/google-services.json
|
||||
├── staging/google-services.json
|
||||
└── main/google-services.json # prod 用 main
|
||||
```
|
||||
|
||||
3. 在 `android/app/build.gradle.kts` 添加 Google Services 插件:
|
||||
|
||||
```kotlin
|
||||
plugins {
|
||||
// ...
|
||||
id("com.google.gms.google-services")
|
||||
}
|
||||
```
|
||||
|
||||
4. 将 `google-services.json` 加入 `.gitignore`(含 API key,勿提交):
|
||||
|
||||
```
|
||||
# 在根 .gitignore 中添加
|
||||
**/google-services.json
|
||||
```
|
||||
|
||||
> `google-services.json` 已在 `android/.gitignore` 中有注释行,取消注释即可。
|
||||
|
||||
---
|
||||
|
||||
## 5. 配置 ProGuard / R8(Release 混淆)
|
||||
|
||||
脚手架的 `android/app/build.gradle.kts` 已预启用 R8:
|
||||
|
||||
```kotlin
|
||||
buildTypes {
|
||||
release {
|
||||
isMinifyEnabled = true
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`android/app/proguard-rules.pro` 中已预留 Drift / Sentry / Riverpod 保留规则注释,按需取消注释。
|
||||
|
||||
---
|
||||
|
||||
## 6. SQLCipher 注意事项
|
||||
|
||||
脚手架已在 `build.gradle.kts` 中处理 SQLCipher 的 native library 冲突:
|
||||
|
||||
```kotlin
|
||||
packaging {
|
||||
jniLibs {
|
||||
pickFirsts += setOf("**/libsqlite3.so")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
若后续新增包时出现 `libsqlite3.so` 重复错误,在此处追加相同模式即可。
|
||||
|
||||
---
|
||||
|
||||
## 7. CI/CD 构建(示例:GitHub Actions)
|
||||
|
||||
以下为生产包构建的 workflow 关键步骤:
|
||||
|
||||
```yaml
|
||||
- name: Decode keystore
|
||||
run: |
|
||||
echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 --decode \
|
||||
> android/keystore/release.jks
|
||||
|
||||
- name: Write key.properties
|
||||
run: |
|
||||
cat > android/key.properties <<EOF
|
||||
storePassword=${{ secrets.KEYSTORE_PASSWORD }}
|
||||
keyPassword=${{ secrets.KEY_PASSWORD }}
|
||||
keyAlias=${{ secrets.KEY_ALIAS }}
|
||||
storeFile=../keystore/release.jks
|
||||
EOF
|
||||
|
||||
- name: Write prod.json
|
||||
run: |
|
||||
echo '${{ secrets.PROD_DART_DEFINES }}' > dart-defines/prod.json
|
||||
|
||||
- name: Build AAB
|
||||
run: make build-prod
|
||||
```
|
||||
|
||||
Secrets 清单:`KEYSTORE_BASE64`、`KEYSTORE_PASSWORD`、`KEY_PASSWORD`、`KEY_ALIAS`、`PROD_DART_DEFINES`
|
||||
|
||||
---
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Gradle sync 失败,报找不到 compileSdk
|
||||
|
||||
确认 Android SDK Platform 34 已安装(Android Studio → SDK Manager → Android 14)。
|
||||
|
||||
### `libsqlite3.so` duplicate 错误
|
||||
|
||||
在 `build.gradle.kts` 的 `packaging.jniLibs.pickFirsts` 中追加冲突的 .so 路径。
|
||||
|
||||
### Release APK 运行时崩溃,Debug APK 正常
|
||||
|
||||
通常是 ProGuard 混淆了不应混淆的类。查看 `build/outputs/mapping/` 下的 `seeds.txt` 和 Logcat,在 `proguard-rules.pro` 添加对应的 `-keep` 规则。
|
||||
|
||||
### 签名 APK 安装时提示"应用已安装但签名不同"
|
||||
|
||||
设备上已安装其他签名版本,需先卸载再安装,或换测试设备。
|
||||
@@ -0,0 +1,243 @@
|
||||
# iOS 配置指南
|
||||
|
||||
本文档覆盖 iOS 端从 CocoaPods 安装到 Archive 发布所需的全部配置步骤。
|
||||
|
||||
> **平台要求**:macOS 系统 + Xcode 16+。Windows / Linux 无法进行 iOS 开发。
|
||||
|
||||
---
|
||||
|
||||
## 前提
|
||||
|
||||
- macOS 14 (Sonoma) 或更高版本
|
||||
- Xcode 16.0(从 App Store 安装,包含 iOS 18 SDK)
|
||||
- CocoaPods 1.15+:`sudo gem install cocoapods`
|
||||
- Apple Developer 账户(真机运行需 Free 账户,发布需 Paid 账户 $99/年)
|
||||
- 已完成 [setup-project.md](setup-project.md) 的 Step 1–4
|
||||
|
||||
---
|
||||
|
||||
## 1. 安装 CocoaPods 依赖
|
||||
|
||||
```bash
|
||||
cd ios
|
||||
pod install
|
||||
cd ..
|
||||
```
|
||||
|
||||
首次执行会拉取依赖,耗时 5–15 分钟(取决于网络)。成功后生成:
|
||||
- `ios/Pods/` 目录(gitignore,不提交)
|
||||
- `ios/Podfile.lock`(版本锁定,**应提交**)
|
||||
|
||||
> 如果 `pod install` 卡住,检查 CocoaPods 源:
|
||||
> ```bash
|
||||
> pod repo update
|
||||
> ```
|
||||
|
||||
---
|
||||
|
||||
## 2. 验证基础构建
|
||||
|
||||
```bash
|
||||
# 无签名编译验证(不需要 Apple 账户)
|
||||
flutter build ios --no-codesign --flavor dev \
|
||||
--dart-define-from-file=dart-defines/dev.json
|
||||
|
||||
# 预期输出:
|
||||
# ✓ Built build/ios/iphoneos/Runner.app
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 配置 Bundle Identifier
|
||||
|
||||
打开 Xcode(**必须通过 .xcworkspace 打开**):
|
||||
|
||||
```bash
|
||||
open ios/Runner.xcworkspace
|
||||
```
|
||||
|
||||
在 Xcode 中:
|
||||
1. 左侧导航选择 **Runner** 项目
|
||||
2. 选择 **Runner** Target → **General** 选项卡
|
||||
3. **Bundle Identifier** 改为业务项目实际 ID:
|
||||
|
||||
| Build Configuration | Bundle ID |
|
||||
|---------------------|-----------|
|
||||
| Debug(dev 开发调试)| `com.your_company.your_app.dev` |
|
||||
| Debug-staging | `com.your_company.your_app.staging` |
|
||||
| Release(prod 发布)| `com.your_company.your_app` |
|
||||
|
||||
> 脚手架默认使用单个 Bundle ID,Flavor 区分由 xcconfig 控制。若需要三个独立 Bundle ID,在 `ios/Flutter/flavors/` 的 xcconfig 文件中添加 `PRODUCT_BUNDLE_IDENTIFIER` 覆盖。
|
||||
|
||||
---
|
||||
|
||||
## 4. 配置 Xcode Build Configuration 和 Scheme
|
||||
|
||||
脚手架已在 `ios/Flutter/flavors/` 中提供三套 xcconfig:
|
||||
|
||||
| 文件 | 对应 Flavor |
|
||||
|------|-----------|
|
||||
| `dev.xcconfig` | 开发包(Debug-dev)|
|
||||
| `staging.xcconfig` | 测试包(Debug-staging / Release-staging)|
|
||||
| `prod.xcconfig` | 正式包(Release)|
|
||||
|
||||
### 关联 xcconfig 到 Build Configuration
|
||||
|
||||
1. Xcode → **Runner** 项目 → **Info** 选项卡 → **Configurations**
|
||||
2. 展开每个 Configuration,点击 Runner 旁的下拉:
|
||||
|
||||
| Configuration 名称 | 关联 xcconfig |
|
||||
|--------------------|---------------|
|
||||
| Debug | `Flutter/flavors/dev.xcconfig` |
|
||||
| Release | `Flutter/flavors/prod.xcconfig` |
|
||||
| Profile | `Flutter/flavors/prod.xcconfig` |
|
||||
|
||||
> 若需要 staging 独立 Configuration,在此添加 `Debug-staging` / `Release-staging`。
|
||||
|
||||
### 添加 dev / staging Scheme(推荐)
|
||||
|
||||
1. Xcode → **Product** → **Scheme** → **Manage Schemes**
|
||||
2. 复制 `Runner` Scheme,重命名为 `dev`
|
||||
3. 编辑 `dev` Scheme:
|
||||
- **Build Configuration**(Run)→ `Debug`
|
||||
- 在 **Arguments** 中确认无硬编码的 dart-defines(Flutter 通过 `--dart-define-from-file` 注入)
|
||||
|
||||
---
|
||||
|
||||
## 5. 配置代码签名
|
||||
|
||||
### 5.1 自动签名(开发调试,推荐)
|
||||
|
||||
1. Xcode → **Runner** Target → **Signing & Capabilities**
|
||||
2. 勾选 **Automatically manage signing**
|
||||
3. **Team** 选择你的 Apple Developer 账户
|
||||
4. Xcode 会自动创建 Provisioning Profile 和 Signing Certificate
|
||||
|
||||
### 5.2 手动签名(CI/CD 或发布版)
|
||||
|
||||
1. 在 [Apple Developer Portal](https://developer.apple.com/account) 创建:
|
||||
- Distribution Certificate(可签发 App Store / Ad Hoc 包)
|
||||
- App ID(对应 Bundle Identifier)
|
||||
- Provisioning Profile(Distribution 类型)
|
||||
2. 下载 `.mobileprovision` 文件,双击安装到 Xcode
|
||||
3. 取消勾选 **Automatically manage signing**
|
||||
4. 选择对应的 **Signing Certificate** 和 **Provisioning Profile**
|
||||
|
||||
---
|
||||
|
||||
## 6. 真机运行
|
||||
|
||||
```bash
|
||||
# 确认设备已连接
|
||||
flutter devices
|
||||
|
||||
# 运行到真机(dev 包)
|
||||
flutter run --flavor dev \
|
||||
--dart-define-from-file=dart-defines/dev.json \
|
||||
-d <device-id>
|
||||
|
||||
# 或通过 Xcode 直接运行(选择 dev Scheme + 目标设备)
|
||||
```
|
||||
|
||||
首次在设备上运行需要:
|
||||
- 设备 → **设置 → 通用 → VPN 与设备管理** → 信任开发者证书
|
||||
|
||||
---
|
||||
|
||||
## 7. 构建 TestFlight / App Store Archive
|
||||
|
||||
```bash
|
||||
# 1. 确保 prod.json 已配置正确
|
||||
cat dart-defines/prod.json
|
||||
|
||||
# 2. 构建 iOS Release
|
||||
flutter build ios --flavor prod --release \
|
||||
--dart-define-from-file=dart-defines/prod.json
|
||||
|
||||
# 3. 在 Xcode 中 Archive(需要 Distribution Certificate)
|
||||
# Product → Archive → Distribute App → App Store Connect
|
||||
```
|
||||
|
||||
### CI/CD Archive(Fastlane 示例)
|
||||
|
||||
```ruby
|
||||
# Fastfile
|
||||
lane :beta do
|
||||
build_app(
|
||||
workspace: "ios/Runner.xcworkspace",
|
||||
scheme: "Runner", # 或 prod scheme
|
||||
configuration: "Release",
|
||||
export_method: "app-store",
|
||||
export_options: {
|
||||
provisioningProfiles: {
|
||||
"com.your_company.your_app" => "Your App Store Profile"
|
||||
}
|
||||
}
|
||||
)
|
||||
upload_to_testflight
|
||||
end
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 常用 xcconfig 字段说明
|
||||
|
||||
`ios/Flutter/flavors/dev.xcconfig`:
|
||||
|
||||
```xcconfig
|
||||
// 覆盖 Display Name(桌面图标显示的应用名)
|
||||
DISPLAY_NAME=YourApp Dev
|
||||
|
||||
// 覆盖 Bundle ID(若三包使用不同 ID)
|
||||
// PRODUCT_BUNDLE_IDENTIFIER=com.your_company.your_app.dev
|
||||
|
||||
// 覆盖 App Icon(若三包使用不同图标)
|
||||
// ASSETCATALOG_COMPILER_APPICON_NAME=AppIconDev
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 最低 iOS 版本
|
||||
|
||||
`Podfile` 已锁定 iOS 15.0:
|
||||
|
||||
```ruby
|
||||
platform :ios, '15.0'
|
||||
```
|
||||
|
||||
若业务需要支持更低版本,修改此行并运行 `pod install`。同时在 Xcode → Target → **Deployment Info** → **iOS Deployment Target** 同步修改。
|
||||
|
||||
---
|
||||
|
||||
## 常见问题
|
||||
|
||||
### `pod install` 报 `CocoaPods could not find compatible versions`
|
||||
|
||||
```bash
|
||||
pod repo update # 更新本地 CocoaPods 源
|
||||
pod install --repo-update
|
||||
```
|
||||
|
||||
### Xcode 打开 `.xcodeproj` 而非 `.xcworkspace`
|
||||
|
||||
必须打开 `.xcworkspace`,否则 CocoaPods 的依赖不会加载:
|
||||
```bash
|
||||
open ios/Runner.xcworkspace # ✓ 正确
|
||||
# 不要 open ios/Runner.xcodeproj ✗
|
||||
```
|
||||
|
||||
### 真机运行报 `Untrusted Developer`
|
||||
|
||||
设备 → 设置 → 通用 → VPN 与设备管理 → 找到 Developer App → 信任。
|
||||
|
||||
### Archive 失败,报 `Provisioning profile doesn't include the entitlement`
|
||||
|
||||
在 Apple Developer Portal 重新生成 Provisioning Profile(选中全部所需 Entitlements),重新下载安装。
|
||||
|
||||
### Flutter build 报 `The iOS deployment target is set to xxx, but the range of supported deployment targets is`
|
||||
|
||||
更新 `Podfile` 中的 `platform :ios` 版本,运行 `pod install`,并在 Xcode Target → Deployment Info 同步修改。
|
||||
|
||||
### 模拟器运行正常,真机崩溃(SecureStorage 相关)
|
||||
|
||||
iOS 模拟器的 Keychain 行为与真机不同。确认 Xcode → Target → **Signing & Capabilities** → 已添加 **Keychain Sharing** Capability(脚手架默认不需要,但某些系统版本可能要求)。
|
||||
@@ -0,0 +1,234 @@
|
||||
# 项目配置指南(所有开发者必读)
|
||||
|
||||
本文档覆盖从克隆脚手架到可以运行第一个 dev 包所需的全部步骤。
|
||||
Android 和 iOS 的平台专属配置请分别参阅 [setup-android.md](setup-android.md) 和 [setup-ios.md](setup-ios.md)。
|
||||
|
||||
---
|
||||
|
||||
## 前提条件
|
||||
|
||||
| 工具 | 最低版本 | 安装方式 |
|
||||
|------|---------|---------|
|
||||
| Flutter SDK | 3.41.0 | [flutter.dev](https://docs.flutter.dev/get-started/install) |
|
||||
| Dart SDK | 3.7.0 | 随 Flutter 一起安装 |
|
||||
| Android Studio | Meerkat (2024.3) | [developer.android.com](https://developer.android.com/studio) |
|
||||
| Xcode | 16.0 | Mac App Store(仅 iOS 开发需要)|
|
||||
| CocoaPods | 1.15+ | `sudo gem install cocoapods` |
|
||||
| make | 系统内置 | macOS/Linux 自带;Windows 用 Git Bash 或 WSL |
|
||||
|
||||
验证安装:
|
||||
```bash
|
||||
flutter --version # 应显示 ≥ 3.41.0
|
||||
flutter doctor # 确认 Android toolchain + Xcode 全绿
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 1 — 克隆与初始化
|
||||
|
||||
```bash
|
||||
git clone https://gitea.example.com/your-org/your-project.git # TODO: 替换实际地址
|
||||
cd your-project
|
||||
|
||||
flutter pub get # 拉取所有依赖
|
||||
make gen # 生成 .g.dart / .freezed.dart(约 1 分钟)
|
||||
make gen-i18n # 生成 lib/i18n/strings.g.dart
|
||||
```
|
||||
|
||||
> **注意**:如果 `make gen` 报错 `build_runner` 找不到,请先确认 `dart` 在 PATH 中:
|
||||
> ```bash
|
||||
> dart --version
|
||||
> ```
|
||||
|
||||
---
|
||||
|
||||
## Step 2 — 填入项目 API 路径
|
||||
|
||||
编辑 `lib/core/config/api_paths.dart`,将所有空字符串替换为实际后端路径:
|
||||
|
||||
```dart
|
||||
// 示例(sa-token + Spring Boot)
|
||||
static const String authLoginSms = '/sys/auth/sms-login';
|
||||
static const String authLoginPwd = '/sys/auth/password-login';
|
||||
static const String authSmsSend = '/sys/auth/send-sms';
|
||||
static const String authLogout = '/sys/auth/logout';
|
||||
static const String authRefresh = '/sys/auth/refresh-token';
|
||||
static const String userProfile = '/sys/sys-user/app/userInfo';
|
||||
// ... 其余路径按业务填写
|
||||
```
|
||||
|
||||
所有 datasource 文件通过 `ApiPaths.xxx` 引用路径,**严禁字符串字面量散落**。
|
||||
|
||||
---
|
||||
|
||||
## Step 3 — 配置环境变量(dart-defines)
|
||||
|
||||
### 3.1 开发环境(dev.json)
|
||||
|
||||
```bash
|
||||
# 直接编辑,此文件可提交(含占位符,无真实密钥)
|
||||
nano dart-defines/dev.json
|
||||
```
|
||||
|
||||
需填入的字段:
|
||||
|
||||
```json
|
||||
{
|
||||
"ENV": "dev",
|
||||
"USE_MOCK": "true", // true = 使用 Mock Adapter(不调真实服务)
|
||||
"INTERNAL_BUILD": "true", // true = 显示 Dev Panel 入口
|
||||
"SENTRY_DSN": "", // dev 通常留空
|
||||
"PINNED_FINGERPRINTS": "", // 证书指纹(dev 可留空跳过 SSL 绑定)
|
||||
"API_BASE_URL": "http://192.168.1.100:8080", // TODO: 实际 dev 服务器
|
||||
"RSA_PUBLIC_KEY": "MIIBIjANBg..." // TODO: 后端 RSA 公钥(Base64 DER)
|
||||
}
|
||||
```
|
||||
|
||||
### 3.2 测试环境(staging.json)
|
||||
|
||||
```json
|
||||
{
|
||||
"ENV": "test",
|
||||
"USE_MOCK": "false",
|
||||
"INTERNAL_BUILD": "true",
|
||||
"SENTRY_DSN": "",
|
||||
"PINNED_FINGERPRINTS": "",
|
||||
"API_BASE_URL": "https://staging.your-domain.com", // TODO
|
||||
"RSA_PUBLIC_KEY": "MIIBIjANBg..." // TODO
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 生产环境(prod.json)
|
||||
|
||||
```bash
|
||||
cp dart-defines/prod.json.template dart-defines/prod.json
|
||||
nano dart-defines/prod.json
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"ENV": "prod",
|
||||
"USE_MOCK": "false",
|
||||
"INTERNAL_BUILD": "false",
|
||||
"SENTRY_DSN": "https://xxx@sentry.io/yyy", // TODO: Sentry 项目 DSN
|
||||
"PINNED_FINGERPRINTS": "AA:BB:CC:...", // TODO: 服务器证书 SHA-256 指纹
|
||||
"API_BASE_URL": "https://api.your-domain.com",// TODO
|
||||
"RSA_PUBLIC_KEY": "MIIBIjANBg..." // TODO
|
||||
}
|
||||
```
|
||||
|
||||
> **prod.json 已在 .gitignore 中**,不会被提交。CI/CD 系统通过 secret 变量注入。
|
||||
|
||||
### 获取 RSA 公钥
|
||||
|
||||
后端一般提供 RSA 公钥的 Base64 DER 编码。若后端使用 sa-token + RSA:
|
||||
|
||||
```bash
|
||||
# Java 后端:从 public.key 文件提取(去掉 PEM header/footer,拼接成单行)
|
||||
cat public.key | grep -v "BEGIN\|END" | tr -d '\n'
|
||||
```
|
||||
|
||||
### 获取证书 SHA-256 指纹
|
||||
|
||||
```bash
|
||||
# 方法 1:openssl(推荐)
|
||||
echo | openssl s_client -connect api.your-domain.com:443 2>/dev/null \
|
||||
| openssl x509 -fingerprint -sha256 -noout \
|
||||
| sed 's/SHA256 Fingerprint=//'
|
||||
|
||||
# 方法 2:Chrome → 锁图标 → 证书 → 指纹
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 4 — 验证首次运行
|
||||
|
||||
```bash
|
||||
# 方式 1:使用 Mock(推荐首次验证,不需要后端服务)
|
||||
# 确保 dart-defines/dev.json 中 USE_MOCK=true
|
||||
make run-dev
|
||||
|
||||
# 方式 2:连接真实后端
|
||||
# 确保 dart-defines/dev.json 中 USE_MOCK=false + API_BASE_URL 已填入
|
||||
make run-dev
|
||||
```
|
||||
|
||||
**期望结果:**
|
||||
- APP 启动,显示登录页(手机号 + SMS/密码双模式)
|
||||
- 右上角(或侧滑)可进入 Dev Panel(Talker 日志面板)
|
||||
- `flutter analyze` 输出 `103 issues found`(均为 info 级别,0 errors)
|
||||
|
||||
---
|
||||
|
||||
## Step 5 — 更新包名(业务项目必须)
|
||||
|
||||
脚手架默认包名为 `com.example.sunny_mochi`,接手后必须替换:
|
||||
|
||||
### Android
|
||||
编辑 `android/app/build.gradle.kts`:
|
||||
```kotlin
|
||||
// dev Flavor
|
||||
applicationId = "com.your_company.your_app.dev"
|
||||
|
||||
// staging Flavor
|
||||
applicationId = "com.your_company.your_app.staging"
|
||||
|
||||
// prod / release
|
||||
defaultConfig {
|
||||
applicationId = "com.your_company.your_app"
|
||||
}
|
||||
```
|
||||
|
||||
### iOS
|
||||
在 Xcode 中修改 Bundle Identifier(见 [setup-ios.md](setup-ios.md) Step 3)。
|
||||
|
||||
### pubspec.yaml + Dart 导入路径
|
||||
```yaml
|
||||
name: your_app_name # 修改后所有 import 路径也需要更新
|
||||
```
|
||||
|
||||
批量替换导入:
|
||||
```bash
|
||||
# macOS / Linux
|
||||
find lib -name "*.dart" -exec sed -i '' 's/package:sunny_mochi/package:your_app_name/g' {} \;
|
||||
|
||||
# Windows PowerShell
|
||||
Get-ChildItem -Path lib -Recurse -Filter "*.dart" |
|
||||
ForEach-Object { (Get-Content $_.FullName) -replace 'sunny_mochi', 'your_app_name' |
|
||||
Set-Content $_.FullName }
|
||||
```
|
||||
|
||||
运行 `make gen` 重新生成后确认无编译错误。
|
||||
|
||||
---
|
||||
|
||||
## Step 6 — 平台专属配置
|
||||
|
||||
- **Android**(签名 / Flavor 验证 / 发布包构建)→ [setup-android.md](setup-android.md)
|
||||
- **iOS**(Xcode Scheme / CocoaPods / 证书 / Archive)→ [setup-ios.md](setup-ios.md)
|
||||
|
||||
---
|
||||
|
||||
## 常见问题
|
||||
|
||||
### `build_runner` 生成失败,报 analyzer 版本冲突
|
||||
|
||||
确认 `pubspec.yaml` 中的 `dependency_overrides`:
|
||||
```yaml
|
||||
dependency_overrides:
|
||||
drift: 2.31.0
|
||||
json_serializable: 6.13.0
|
||||
```
|
||||
若被修改,恢复后重跑 `flutter pub get && make gen`。
|
||||
|
||||
### App 启动后 token 丢失 / 每次重启都跳登录
|
||||
|
||||
SecureStorage 在 iOS Simulator 上可能行为异常。请在真机测试,或检查 `Keychain Sharing` 是否开启。
|
||||
|
||||
### Mock 模式下请求崩溃 `Unable to load asset`
|
||||
|
||||
检查 `assets/fixtures/` 下是否有对应的 JSON 文件,且 `pubspec.yaml` 中的 `assets:` 包含了对应子目录。
|
||||
|
||||
### `flutter analyze` 报错(非 info 级别)
|
||||
|
||||
先运行 `make gen` 确保生成文件是最新的,再重新 analyze。
|
||||
Reference in New Issue
Block a user