Template
11 KiB
11 KiB
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/ # 本地错误日志 + 上报(完整实现)
开发命令
# 代码生成(修改 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):
@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 添加:
// ============== My Feature ==============
static const String myFeatureList = '/api/v1/my-feature/list';
新增 DB 表
- 在
lib/core/storage/tables/新建表文件 - 在
lib/core/storage/app_database.dart的@DriftDatabase(tables: [...])添加 - 递增
schemaVersion并在MigrationStrategy.onUpgrade中添加迁移 - 运行
make gen
错误处理标准模式
// 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) |
关键架构决策(勿重议)
- SQLCipher 不可替换:企业合规要求,部署后无法无损切换到无加密数据库
- 7 拦截器顺序固定:CertPinning→Auth→TokenRefresh→Retry→Error→AuthLogout→Log,错误处理链依赖此顺序
- TokenRefresh 使用 Completer 互斥:防止并发 401 重复消耗 RefreshToken,不可改为简单 flag
- SecureStorage 是 token 唯一真源:DB 中的 refreshToken 仅作可观测性镜像
- GoRouter 用
ref.read(非ref.watch)构建:router 是 keepAlive 单例,通过refreshListenable响应 auth 变化 - 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 | 项目概述、技术栈、快速开始 |
| docs/setup-project.md | 完整项目配置指南(所有开发者必读) |
| docs/setup-android.md | Android Flavor / 签名 / 构建详细配置 |
| docs/setup-ios.md | iOS Scheme / CocoaPods / 签名详细配置 |