rtc_prd/airhub_app/CLAUDE.md
2026-03-26 11:12:02 +08:00

295 lines
8.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Airhub App 开发规范
> 所有新功能开发、代码修改、Bug 修复均需遵循本规范
---
## 一、项目架构
```
lib/
├── core/ # 基础设施层(全局共享)
│ ├── errors/ # 异常类 & Failure 类
│ ├── network/ # ApiClient, TokenManager, ApiConfig
│ ├── router/ # go_router 路由配置
│ └── services/ # 全局服务LogCenter 等)
├── features/ # 功能模块(按业务领域划分)
│ ├── auth/ # 认证
│ ├── badge/ # 电子吧唧
│ ├── device/ # 设备管理
│ ├── notification/ # 通知
│ ├── spirit/ # 精灵/角色
│ ├── user/ # 用户
│ └── system/ # 系统(版本检查等)
├── pages/ # 独立页面(未归入 feature 的页面)
├── widgets/ # 全局可复用 Widget
├── theme/ # 主题、颜色、Design Tokens
├── services/ # 应用级服务TTS、音乐生成等
└── main.dart # 入口
```
### Feature 模块内部结构
```
features/xxx/
├── data/
│ ├── datasources/ # 远程/本地数据源
│ ├── models/ # 数据模型Freezed
│ └── repositories/ # Repository 实现
├── domain/
│ └── repositories/ # Repository 接口
└── presentation/
├── controllers/ # Riverpod 控制器
├── pages/ # 页面
└── widgets/ # 功能专属 Widget
```
---
## 二、状态管理Riverpod
### 规则
1. **使用 `@riverpod` 注解**声明 Provider不手写 `StateNotifierProvider`
2. Controller 使用 `AsyncValue<T>` 管理异步状态
3. Repository 返回 `Either<Failure, T>`fpdartController 内 `fold` 处理
4. **页面中优先使用 `ConsumerWidget`**(无状态);需要动画/本地状态时用 `ConsumerStatefulWidget`
5. **`ref.watch()` 用于 UI 绑定**(触发重建),**`ref.read()` 用于事件处理**(不触发重建)
6. **`ref.listen()` 用于副作用**弹窗、导航、Toast不触发 Widget 重建
### 示例
```dart
@riverpod
class MyController extends _$MyController {
@override
FutureOr<MyState> build() async {
final repo = ref.watch(myRepositoryProvider);
final result = await repo.fetchData();
return result.fold(
(failure) => throw failure,
(data) => MyState(data: data),
);
}
}
```
---
## 三、网络层
### 规则
1. 所有 API 调用通过 `ApiClient`Dio 封装),不直接使用 `http` 包或 `Dio()`
2. `ApiClient` 自动处理Token 附加、401 自动刷新、错误上报
3. 后端统一返回 `{code, message, data}``ApiClient._request()` 自动解析
4. 异常类型:`ServerException`code != 0`NetworkException`(连接失败)
5. **不在 Widget/Controller 中直接调用 ApiClient**,应通过 Repository 层
### 错误处理链
```
DataSource (throw Exception)
→ Repository (catch → Either<Failure, T>)
→ Controller (fold → AsyncValue)
→ UI (AsyncValue.when: loading/data/error)
```
---
## 四、路由go_router
### 规则
1. 所有路由定义在 `core/router/app_router.dart`
2. 使用 `context.go()` 进行声明式导航,避免 `Navigator.push()`(页面内二级跳转除外)
3. 登录态检查在 `redirect` 中统一处理
4. 产品类型 → 路由映射在 `_productRoutes` Map 中维护
---
## 五、性能规范
### 内存管理
1. **所有 Stream 订阅必须存入变量dispose 时取消**
```dart
// ✅ 正确
StreamSubscription<Duration>? _positionSub;
_positionSub = stream.listen((data) { ... });
@override
void dispose() {
_positionSub?.cancel();
super.dispose();
}
// ❌ 错误 — 无法取消,造成内存泄漏
stream.listen((data) { ... });
```
2. **AnimationController、VideoPlayerController、AudioPlayer 等必须在 dispose 中释放**
3. **页面离开时停止 BLE 扫描**
```dart
@override
void dispose() {
FlutterBluePlus.stopScan();
_scanSubscription?.cancel();
super.dispose();
}
```
### Widget 重建优化
4. **能用 `const` 的地方必须加 `const`**
```dart
// ✅
const SizedBox(height: 16)
const EdgeInsets.all(16)
const Text('Hello')
// ❌
SizedBox(height: 16)
EdgeInsets.all(16)
```
5. **动画区域用 `RepaintBoundary` 包裹**,防止全页面重绘
```dart
RepaintBoundary(
child: AnimatedGradientBackground(),
)
```
6. **`AnimatedBuilder` 必须使用 `child` 参数**,将不变的子树传入 child不要在 builder 中重建
```dart
// ✅
AnimatedBuilder(
animation: _controller,
child: const HeavyWidget(), // 只构建一次
builder: (context, child) => Transform.rotate(
angle: _controller.value,
child: child, // 复用
),
)
```
### API 调用
7. **多个独立 API 调用使用 `Future.wait` 并行**,不要顺序 await
```dart
// ✅ 并行
final results = await Future.wait([
api.get('/shelves/'),
api.get('/stories/'),
]);
// ❌ 顺序(慢 N 倍)
final shelves = await api.get('/shelves/');
final stories = await api.get('/stories/');
```
8. **耗时计算JSON 解析、图片处理)放到 Isolate**
```dart
final result = await Isolate.run(() => jsonDecode(bigJson));
```
### 图片
9. **网络图片使用 `Image.network` + `fit: BoxFit.cover`**,不要手动指定 `cacheWidth/cacheHeight`(会导致显示异常)
10. **大量图片列表使用 `ListView.builder`**,不要 `ListView(children: [...])`
---
## 六、构建配置
### Android Release
- `build.gradle.kts` 中 release 配置:
- `isMinifyEnabled = true` — R8 代码压缩
- `isShrinkResources = true` — 移除未使用资源
- ProGuard 规则在 `proguard-rules.pro` 维护
- 打包命令:`flutter build apk --release --split-per-abi --obfuscate --split-debug-info=./debug-info`
### iOS Release
- 构建命令:`flutter build ipa --release --obfuscate --split-debug-info=./debug-info`
---
## 七、命名规范
### 文件命名
| 类型 | 格式 | 示例 |
|------|------|------|
| 页面 | `xxx_page.dart` | `login_page.dart` |
| 控制器 | `xxx_controller.dart` | `auth_controller.dart` |
| 数据源 | `xxx_remote_data_source.dart` | `spirit_remote_data_source.dart` |
| 仓库实现 | `xxx_repository_impl.dart` | `auth_repository_impl.dart` |
| 仓库接口 | `xxx_repository.dart` | `auth_repository.dart` |
| 模型 | `xxx_model.dart` | `user_model.dart` |
| Widget | `xxx_widget.dart` 或直接描述 | `gradient_button.dart` |
| 生成文件 | `*.g.dart` / `*.freezed.dart` | 自动生成,不手动编辑 |
### 类命名
- PascalCase`AuthController`、`DeviceControlPage`、`ServerException`
- 私有前缀:`_buildHeader()`、`_positionSub`
### 变量命名
- camelCase`currentUser`、`isPlaying`、`_audioPlayer`
- Provider`xxxProvider`(由 `@riverpod` 自动生成)
---
## 八、主题系统
### 颜色
- 全局颜色定义在 `theme/app_colors.dart`
- 产品主题颜色在 `theme/product_theme.dart`4 种产品类型)
- **不要在 Widget 中硬编码颜色值**,优先使用 `AppColors.xxx` 或 `Theme.of(context)`
### 字体
- 主字体DM Sans正文、Outfit标题、Press Start 2P品牌像素风
- 通过 `google_fonts` 包引用,**运行时自动下载并缓存**
- 本地字体Inter在 `pubspec.yaml` 中声明)
### 间距
- 使用 `AppSpacing.xs/sm/md/lg/xl` 常量
- 不要在 Widget 中硬编码间距数值
---
## 九、Git 规范
### 分支命名
- 功能分支:`fix/auto-YYYYMMDD-HHMMSS`
- 合并目标:`main`
### Commit 规范
- `fix:` — Bug 修复
- `feat:` — 新功能
- `refactor:` — 重构(不改变行为)
- `chore:` — 构建配置、依赖更新等
---
## 十、检查清单PR 提交前)
- [ ] `dart analyze` 无 ErrorWarning/Info 可接受)
- [ ] 所有 Stream 订阅已在 dispose 中取消
- [ ] 所有 Controller/Player 已在 dispose 中释放
- [ ] 多个独立 API 调用已用 `Future.wait` 并行化
- [ ] 动画 Widget 使用了 `RepaintBoundary` 或 `AnimatedBuilder(child:)`
- [ ] 新增的 Widget 构造函数尽可能标记为 `const`
- [ ] 列表使用 `ListView.builder`,而非 `ListView(children:)`
- [ ] 未引入新的硬编码颜色值(使用 AppColors 或 theme
- [ ] Android release 构建正常minify + shrinkResources 不会崩溃)