# CLAUDE.md — Flutter 企业级脚手架(sunny_mochi) > 本文件为 Claude Code 提供项目上下文。**所有 AI 辅助开发会话必须首先读取本文件**, > 再按需加载 `docs/` 下的补充文档。 --- ## 项目身份信息 > **以下带 `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 → ScreenUtilInit → App → MaterialApp.router ├── i18n/ # Slang 翻译(zh-CN 基准 + en 备用) ├── core/ │ ├── 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/ # ★ 认证(完整骨架,业务项目修改 LoginPage UI) ├── dev_panel/ # Talker 面板(Internal 包,勿在 release 中暴露入口) └── error_report/ # 本地错误日志 + 上报(完整实现) ``` --- ## 开发命令 ```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(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)| --- ## 关键架构决策(勿重议) 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 --- ## 新项目接手 Checklist > 完成后删除本节或移至项目 wiki - [ ] 更新本文件"项目身份信息"表格 - [ ] 填入 `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 项目描述 --- ## 参考文档 | 文档 | 内容 | |------|------| | [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 / 签名详细配置 |