Template
P0 代码修复:删除 imSdkAppId、清除内网 IP 与原项目域名、修正 USE_MOCK 默认值 P1 CLAUDE.md 重组:零容忍规则前置,新增 dart-defines 字段对照表 P2 文档优化:先跑再配的 setup 流程,新增 architecture.md 架构图
11 KiB
11 KiB
CLAUDE.md — Flutter 企业级脚手架(sunny_mochi)
本文件为 Claude Code 提供项目上下文。每次新会话首先读取本文件, 需要时再按需加载
docs/下的补充文档。
⚡ 30 秒上手(开箱即用验证)
flutter pub get && make gen && make gen-i18n
# dev.json 默认 USE_MOCK=true,无需后端即可运行:
make run-dev
# 期望结果:App 启动 → 显示登录页 → 右上角可进 Dev Panel(Talker 日志)
这是脚手架,不是完整 App。以下内容"需要业务项目填写":
lib/core/config/api_paths.dart— 所有路径均为空字符串lib/core/config/api_config.dart— 三套 baseUrl 均为 localhost 占位符dart-defines/*.json— RSA 公钥 / Sentry DSN 均为占位符
项目身份信息
业务项目接手后,将下表 TODO 字段替换为真实值,AI 才能给出精准建议。
| 字段 | 当前值(脚手架占位) | 说明 |
|---|---|---|
| 项目名称 | sunny_mochi | TODO: 替换为实际项目名 |
| Application ID | com.example.sunny_mochi | TODO: 如 com.company.appname |
| API Base URL (dev) | http://localhost:8080 | TODO: 改 api_config.dart + dev.json |
| API Base URL (staging) | https://staging.your-domain.com | TODO: 改 api_config.dart |
| API Base URL (prod) | https://api.your-domain.com | TODO: 改 api_config.dart |
| 后端鉴权框架 | sa-token(动态 tokenName header) | TODO: 若用 Bearer JWT 改 auth_interceptor |
| Sentry DSN | 空(不上报) | TODO: prod.json |
| RSA 公钥 | 空(加密跳过) | TODO: 三个 dart-defines JSON |
零容忍规则(必须遵守,不可商量)
| 规则 | ✅ 正确 | ❌ 禁止 |
|---|---|---|
| API 路径 | ApiPaths.xxx 常量 |
字符串字面量散落代码中 |
| 错误处理 | ExceptionMapper().fromUnknown(e, st) |
裸 catch (e) 直接 toString() 显示 |
| Token 存储 | SecureStorage(Keychain / EncryptedPrefs) |
普通 SharedPreferences |
| 数据库加密 | SQLCipher(默认,禁止替换) | 无加密 sqflite |
| 日志 | appTalker.info/warning/error() |
print() / debugPrint() |
| 路由跳转 | XxxRoute().go(context) |
Navigator.push() |
| 状态管理 | @riverpod / @Riverpod(keepAlive: true) |
setState 跨组件 / Provider 包 |
| Riverpod provider 命名 | 生成名(AuthNotifier → authProvider) |
手写 authNotifierProvider |
| 生成文件 | 只读,运行 make gen 刷新 |
手动修改 .g.dart / .freezed.dart |
| Domain 层依赖 | 纯 Dart,无 Flutter/Drift/Dio | domain 层 import 'package:dio' |
禁止修改的文件(架构决策,除非全面评估影响)
lib/core/network/dio_client.dart # 7 拦截器顺序固定,顺序即语义
lib/core/storage/db_key_provider.dart # AES-256 密钥派生策略,改动会破坏已有 DB
lib/core/error/failures.dart # 全局错误分类,改 sealed 影响所有 switch
lib/core/network/response_code.dart # 业务码分类,影响 TokenRefresh 互斥逻辑
dart-defines 字段完整对照表
| JSON 键 | Env.dart 读取字段 |
类型 | 说明 |
|---|---|---|---|
ENV |
Env.name |
String | dev / test / release |
API_BASE_URL |
Env.apiBaseUrlOverride |
String | 留空则用 api_config.dart 中的固定 URL |
USE_MOCK |
Env.useMock |
bool | true = 走 MockAdapter,无需后端 |
INTERNAL_BUILD |
Env.isInternalBuild |
bool | true = 显示 Dev Panel 入口 |
SENTRY_DSN |
Env.sentryDsn |
String | 留空时 Sentry 自动跳过 |
PINNED_FINGERPRINTS |
Env.pinnedFingerprints |
List | 逗号分隔的 SHA-256 指纹,留空跳过 SSL 绑定 |
RSA_PUBLIC_KEY |
Env.rsaPublicKey |
String | DER-SPKI Base64,留空时密码不加密传输 |
技术栈(脚手架锁定版本)
| 分类 | 技术 | 版本 |
|---|---|---|
| 状态管理 | flutter_riverpod + riverpod_annotation | ^3.3.0 / ^4.0.0 |
| 路由 | go_router + 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(base_locale: zh-CN) | ^4.0.0 |
| UI 自适应 | flutter_screenutil(基准 375×812) | ^5.9.0 |
drift: 2.31.0和json_serializable: 6.13.0锁定原因: 更新版本需要 analyzer ^10.x,与 riverpod_generator 的 analyzer ^9.x 冲突。勿升级。
目录结构与关键文件
lib/
├── main.dart # 启动序列(固定顺序:preInit→consumePending→SentryInit→installHooks)
├── app.dart # AppRoot → ScreenUtilInit(375×812) → App → MaterialApp.router
├── i18n/ # Slang 翻译(zh-CN 基准 + en 备用)
├── core/
│ ├── config/
│ │ ├── env.dart # ← 读取 dart-defines,只读
│ │ ├── api_config.dart # ★ 填入三套环境 baseUrl
│ │ └── api_paths.dart # ★ 填入所有 API 路径常量
│ ├── router/
│ │ ├── app_router.dart # GoRouter provider(ref.read + refreshListenable)
│ │ └── routes.dart # ★ 添加业务 @TypedGoRoute(然后 make gen)
│ ├── network/
│ │ ├── dio_client.dart # 7 拦截器(禁止改顺序)
│ │ ├── api_response.dart # envelope 解析(parseEnvelope / unwrapVoid)
│ │ ├── response_code.dart # 业务状态码(成功=00000,token 类码分类)
│ │ ├── interceptors/ # 7 个拦截器(禁止改顺序)
│ │ └── mock/ # USE_MOCK=true 时生效,读 assets/fixtures/
│ ├── storage/
│ │ ├── app_database.dart # schemaVersion=1,@DriftDatabase([Users, ErrorLogs])
│ │ ├── secure_storage.dart # token / userId / tokenName 的唯一真源
│ │ ├── db_key_provider.dart # AES-256 密钥派生(禁止改策略)
│ │ ├── tables/ # ★ 业务项目在此添加 Drift 表
│ │ └── daos/ # DAO(只放纯 DB 操作)
│ ├── error/
│ │ ├── failures.dart # sealed Failure(禁止改结构)
│ │ └── exception_mapper.dart # 异常 → Failure 映射 + Riverpod provider
│ └── widgets/ # AppToast / AppButton / EmptyView / AvatarWidget 等
└── features/
├── auth/ # ★ 修改 LoginPage UI 品牌,业务路由骨架已就绪
├── dev_panel/ # Talker 面板(INTERNAL_BUILD=true 时可路由进入)
└── error_report/ # 本地错误日志 + 批量上报(完整实现)
常用开发范式
新增 Feature(标准目录结构)
lib/features/{feature}/
├── 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,实现接口
└── presentation/
├── notifiers/{name}_notifier.dart # @riverpod class XxxNotifier
└── pages/{name}_page.dart # ConsumerWidget
新增路由(routes.dart → make gen)
@TypedGoRoute<MyRoute>(path: '/my-path')
class MyRoute extends GoRouteData with $MyRoute {
const MyRoute();
@override
Widget build(BuildContext context, GoRouterState state) => const MyPage();
}
标准错误处理
// 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!);
});
新增 DB 表
lib/core/storage/tables/{name}_table.dart— 定义@DataClassName+ 字段app_database.dart—@DriftDatabase(tables: [..., NewTable])+ 递增 schemaVersion + 添加 onUpgrade 迁移lib/core/storage/daos/{name}_dao.dart—@DriftAccessor(tables: [NewTable])- 运行
make gen
开发命令
make gen # build_runner → .g.dart / .freezed.dart(改 Dart 文件后必跑)
make gen-i18n # slang → lib/i18n/strings.g.dart(改翻译 JSON 后必跑)
make run-dev # dev 包(USE_MOCK=true,无需后端)
make run-staging # staging 包(连 test 服务器)
make build-staging# staging APK
make build-prod # prod AAB(需先配置 prod.json + key.properties)
flutter analyze # 目标 0 errors(当前正常 info 数约 103 条)
flutter test # 全量测试
新项目接手 Checklist
完成并删除本节(或移至项目 wiki)
- 更新本文件"项目身份信息"表格(让 AI 上下文准确)
- 修改
lib/core/config/api_config.dart三套 baseUrl - 填入
lib/core/config/api_paths.dart所有路径 - 配置三个
dart-defines/*.json(RSA 公钥 / Sentry DSN) - 修改 Android 包名:
android/app/build.gradle.ktsapplicationId - 修改 iOS Bundle ID:Xcode → Runner Target → General
- 修改
pubspec.yamlname 并批量替换 import 路径(见 setup-project.md Step 5) - Android 签名:
cp android/key.properties.template android/key.properties并填写 - iOS 签名:Xcode → Signing & Capabilities(见 setup-ios.md)
- 替换登录页 UI 品牌(
lib/features/auth/presentation/pages/login_page.dart) - 替换 App Icon(Android mipmap / iOS AppIcon.appiconset)
- 实现 Tab 页面(
lib/core/router/routes.dartHomeRoute / MineRoute 的 build) - 更新 README.md 项目描述和快速开始地址
参考文档
| 文档 | 内容 |
|---|---|
| README.md | 项目概述、技术栈、基础设施一览 |
| docs/setup-project.md | 完整配置指南(dart-defines / 包名 / 首次运行) |
| docs/setup-android.md | Android Flavor / 签名 / 构建 / CI |
| docs/setup-ios.md | iOS Scheme / CocoaPods / 证书 / Archive |
| docs/architecture.md | 数据流 / 认证流 / 错误链 ASCII 架构图 |