diff --git a/CLAUDE.md b/CLAUDE.md index f3c8619..e578741 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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(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 / 签名详细配置 | diff --git a/README.md b/README.md index 5881221..2f5840d 100644 --- a/README.md +++ b/README.md @@ -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 三态门面 +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 辅助开发 | 架构规范、零容忍规则、开发范式 | diff --git a/docs/setup-android.md b/docs/setup-android.md new file mode 100644 index 0000000..863fbf3 --- /dev/null +++ b/docs/setup-android.md @@ -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 < 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 安装时提示"应用已安装但签名不同" + +设备上已安装其他签名版本,需先卸载再安装,或换测试设备。 diff --git a/docs/setup-ios.md b/docs/setup-ios.md new file mode 100644 index 0000000..2075420 --- /dev/null +++ b/docs/setup-ios.md @@ -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 + +# 或通过 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(脚手架默认不需要,但某些系统版本可能要求)。 diff --git a/docs/setup-project.md b/docs/setup-project.md new file mode 100644 index 0000000..50036db --- /dev/null +++ b/docs/setup-project.md @@ -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。