diff --git a/Skills.md b/Skills.md new file mode 100644 index 0000000..f325bea --- /dev/null +++ b/Skills.md @@ -0,0 +1,17 @@ +# 项目通用技术设计(Skills) + +本文档已拆分为独立 Skill 文件,统一放在 **`skills/`** 目录下。 + +👉 **请查看 [skills/README.md](skills/README.md)** 获取完整索引与各块设计说明。 + +## 快速索引 + +| 设计块 | 文件 | +|--------|------| +| 登录与注册机制 | [skills/auth-login-register.md](skills/auth-login-register.md) | +| 顶部导航栏(App Bar) | [skills/top-app-bar.md](skills/top-app-bar.md) | +| 系统状态栏与导航栏 | [skills/system-bars.md](skills/system-bars.md) | +| 底部导航栏(Tab 栏) | [skills/bottom-navigation.md](skills/bottom-navigation.md) | +| Android 路由与导航 | [skills/android-navigation-routing.md](skills/android-navigation-routing.md) | +| 错误处理 | [skills/error-handling.md](skills/error-handling.md) | +| API 与后端约定 | [skills/api-backend-conventions.md](skills/api-backend-conventions.md) | diff --git a/skills/README.md b/skills/README.md new file mode 100644 index 0000000..798547f --- /dev/null +++ b/skills/README.md @@ -0,0 +1,21 @@ +# 项目通用技术设计(Skills) + +本目录描述与本项目**具体业务无关**的技术设计与约定,供新功能开发、重构或跨项目复用时参考。 + +## Skill 列表 + +| Skill | 说明 | +|-------|------| +| [auth-login-register.md](auth-login-register.md) | 登录与注册机制(服务端 API + 客户端 Android) | +| [top-app-bar.md](top-app-bar.md) | 顶部导航栏(App Bar)与透明化 | +| [system-bars.md](system-bars.md) | 系统状态栏与系统导航栏 | +| [bottom-navigation.md](bottom-navigation.md) | 底部导航栏(Tab 栏) | +| [android-navigation-routing.md](android-navigation-routing.md) | Android 路由与页面导航 | +| [error-handling.md](error-handling.md) | 错误处理(Android 友好错误展示) | +| [api-backend-conventions.md](api-backend-conventions.md) | API 与后端约定 | + +## 使用约定 + +- 新增与「登录注册、导航栏、系统栏、路由、错误展示」相关的功能时,优先符合对应 Skill 中的设计,保持体验一致。 +- 复用到其他项目时,可按需裁剪(例如只保留认证 + 顶部栏 + 错误处理),并同步调整客户端与服务端约定。 +- 业务相关逻辑(如回忆录、对话、付费等)不写入本目录,仅保留通用技术部分。 diff --git a/skills/android-navigation-routing.md b/skills/android-navigation-routing.md new file mode 100644 index 0000000..d2c196b --- /dev/null +++ b/skills/android-navigation-routing.md @@ -0,0 +1,24 @@ +# Skill:Android 路由与页面导航 + +与具体业务无关的 Android 路由与 NavHost 设计:路由定义、转场与返回栈。 + +--- + +## 路由定义 + +- 使用 **Screen** sealed class,所有路由字符串集中定义(如 `Screen.ConversationList.route`、`Screen.CreateMemory.createRoute(conversationId)` 等)。 + +## NavHost + +- 在 **AppNavigation** 中统一注册 `composable(route = ..., enterTransition = ..., exitTransition = ..., popEnterTransition = ..., popExitTransition = ...)`。 +- 转场动画由 **NavigationTransitions** 提供:水平进出(如 `slideInHorizontally`、`slideOutHorizontally`)、从左侧返回(`slideInHorizontallyFromLeft`、`slideOutHorizontallyToRight`)、淡入淡出、缩放等,按页面类型选用。 + +## 起始目的地 + +- `startDestination = if (isLoggedIn) Screen.ConversationList.route else Screen.Login.route`,与 TokenManager 的登录状态一致。 + +## 返回栈 + +- 登录后进入主界面:`popUpTo(Screen.Login.route) { inclusive = true }`。 +- 登出:`popUpTo(0) { inclusive = true }` 清空栈再 navigate 到 Login。 +- Tab 切换时按设计选择 `popUpTo(ConversationList)` 等,避免栈过深。 diff --git a/skills/api-backend-conventions.md b/skills/api-backend-conventions.md new file mode 100644 index 0000000..913025e --- /dev/null +++ b/skills/api-backend-conventions.md @@ -0,0 +1,18 @@ +# Skill:API 与后端约定 + +与具体业务无关的后端 API 与配置约定:前缀、敏感信息与数据库。 + +--- + +## 前缀与路由 + +- 认证:`/api/auth`。 +- 其他业务按模块分 router(如 user、conversations、books、chapters 等),在 `main.py` 中挂载,统一带 `/api` 前缀时在挂载处配置。 + +## 敏感配置 + +- 环境变量中敏感项(SECRET_KEY、API_KEY、PASSWORD、TOKEN 等)在日志中脱敏(只打 key 或前后几位)。 + +## 数据库 + +- 异步 SQLAlchemy + `get_async_db`;需当前用户时 `Depends(get_current_user)`。 diff --git a/skills/auth-login-register.md b/skills/auth-login-register.md new file mode 100644 index 0000000..1444ff8 --- /dev/null +++ b/skills/auth-login-register.md @@ -0,0 +1,62 @@ +# Skill:登录与注册机制 + +与具体业务无关的认证设计:服务端 API 与客户端 Android 的登录、注册、令牌与鉴权约定。 + +--- + +## 服务端(API) + +### 认证方式 + +- **访问令牌(Access Token)**:JWT,用于接口鉴权,默认有效期 2 小时(`ACCESS_TOKEN_EXPIRE_MINUTES`)。 +- **刷新令牌(Refresh Token)**:随机字符串,存库,默认 30 天有效;用于在访问令牌过期后换取新访问令牌。 + +### 密码 + +- 使用 **bcrypt** 哈希存储,不存明文。 +- 注册/登录时由 `auth_service.hash_password` / `verify_password` 处理。 + +### 登录/注册入口 + +- 手机号 + 密码:`POST /api/auth/login`、`POST /api/auth/register`。 +- 手机号 + 短信验证码:`POST /api/auth/login/sms`(未注册则自动注册,需提供昵称)。 +- 所有入口均要求请求体中 `agreed_to_terms == true`,否则 400。 + +### 令牌刷新 + +- `POST /api/auth/refresh`,Body: `{ "refresh_token": "..." }`。 +- 校验:存在、未撤销、未过期、对应用户存在;通过后返回新的 `access_token`,`refresh_token` 原样返回(不轮换)。 + +### 鉴权依赖 + +- 需要登录的接口:`Depends(get_current_user)`,从 `Authorization: Bearer ` 解析 JWT,校验 `type == "access"` 并加载用户。 +- 可选登录:`Depends(get_optional_user)`,无 token 或无效时返回 `None`。 + +### 路由与安全 + +- 认证相关路由统一前缀:`/api/auth`(如 login、register、refresh、logout 等)。 +- OAuth2 约定:`OAuth2PasswordBearer(tokenUrl="/api/auth/login")`,仅用于声明从哪里取 token,实际登录仍用上述接口。 + +--- + +## 客户端(Android) + +### 令牌存储 + +- 使用 **DataStore Preferences**(`TokenPreferences`)存 `access_token`、`refresh_token`、`user_id`。 +- 通过单例 **TokenManager** 读写;提供 `initialize(context)`、`saveTokens`、`getAccessToken`/`getRefreshToken`(含 suspend 与 sync 版本)、`clearTokens`。 + +### 登录状态 + +- TokenManager 维护 `isLoggedIn`(有有效 access_token 即为 true),并提供 `rememberIsLoggedIn()` 供 Compose 使用。 +- 登出或刷新失败时调用 `clearTokens()` 并 `notifyTokenRefreshFailed()`,由回调统一处理(如清栈并跳转登录页)。 + +### 请求头与刷新 + +- 使用 **AuthInterceptor**(Ktor Client 插件):请求前自动附加 `Authorization: Bearer `。 +- 收到 401 时:用当前 refresh_token 调 `AuthService.refreshToken()`;成功则写回新 token,失败则 `clearTokens()` + `notifyTokenRefreshFailed()`,由 App 层跳转登录。 + +### 登录成功/登出与导航 + +- 登录成功:由各登录入口回调 `onLoginSuccess`,主流程将 `isLoggedIn = true` 并导航到主界面(如会话列表),并 `popUpTo(Login)` 清掉登录页。 +- 登出或刷新失败:`onLogout` / TokenManager 回调中执行 `navController.navigate(Screen.Login.route) { popUpTo(0) { inclusive = true } }`,保证回到登录且无法返回。 diff --git a/skills/bottom-navigation.md b/skills/bottom-navigation.md new file mode 100644 index 0000000..7ebecdb --- /dev/null +++ b/skills/bottom-navigation.md @@ -0,0 +1,20 @@ +# Skill:底部导航栏(Tab 栏) + +与具体业务无关的底部 Tab 栏设计:显示条件、实现方式与选中态。 + +--- + +## 显示条件 + +- 仅在部分主 Tab 页显示:如会话列表、回忆录、我的(以 `Screen.ConversationList`、`Screen.MyMemoir`、`Screen.Profile` 等当前路由判断)。 +- 子页(如设置、关于、FAQ)不显示底部栏。 + +## 实现 + +- 在 `Scaffold.bottomBar` 中按 `currentRoute` 条件渲染底部栏;使用 `Surface` + `windowInsetsPadding(WindowInsets.navigationBars)` 避免与系统导航栏重叠。 +- 底部项使用 **AppDestinations** 枚举(如 CHAT、MEMOIR、PROFILE),每项含 label、icon;点击时 `navController.navigate(对应 Screen.route)` 并配合 `popUpTo` 控制返回栈。 + +## 选中态 + +- 通过 `currentDestination` 与当前路由同步(`LaunchedEffect(currentRoute)`),保证高亮与实际页面一致。 +- 可选:选中缩放/按下缩放等动画(如 `BottomNavItem` 中的 `animateFloatAsState`)。 diff --git a/skills/error-handling.md b/skills/error-handling.md new file mode 100644 index 0000000..6e0fb26 --- /dev/null +++ b/skills/error-handling.md @@ -0,0 +1,27 @@ +# Skill:错误处理(Android) + +与具体业务无关的 Android 错误展示设计:错误类型、友好文案与组件。 + +--- + +## 错误类型 + +- **ErrorType** 枚举:NETWORK、SERVER、TIMEOUT、AUTH、NOT_FOUND、VALIDATION、UNKNOWN。 + +## ErrorHandler + +- `getFriendlyError(errorType, originalMessage)`:生产环境返回友好文案,开发环境可带出 `originalMessage`(如 VALIDATION)。 +- `detectErrorType(exception)`:根据异常 message 关键词推断类型。 +- `detectErrorTypeByStatusCode(statusCode)`:401/403→AUTH,404→NOT_FOUND,5xx→SERVER 等。 +- `getDisplayMessage` / `handleException`:统一得到最终展示文案。 + +## 组件 + +- **FriendlyErrorView**:全屏错误页,图标+标题+描述+重试/去登录按钮,可选动画。 +- **FriendlyErrorDialog**:弹窗版,带确认/取消或重试。 +- **InlineErrorMessage**:表单等内联错误,生产环境对部分技术性文案做友好替换。 +- **Snackbar**:`showFriendlyError` 扩展,用于轻量提示。 + +## 约定 + +- 生产环境不直接暴露后端异常原文;开发环境可通过 `AppConfig.isDebugMode` 显示原始信息。 diff --git a/skills/system-bars.md b/skills/system-bars.md new file mode 100644 index 0000000..3504e51 --- /dev/null +++ b/skills/system-bars.md @@ -0,0 +1,19 @@ +# Skill:系统状态栏与系统导航栏 + +与具体业务无关的系统栏设计:边缘到边缘、显示/隐藏与外观。 + +--- + +## 边缘到边缘 + +- `MainActivity` 中 `enableEdgeToEdge()`,`WindowCompat.setDecorFitsSystemWindows(window, false)`,内容可延伸到系统栏下。 + +## 显示/隐藏 + +- 使用 **SystemUiController** Composable:`LaunchedEffect` 内通过 `WindowInsetsController` 的 `show`/`hide` 控制 `statusBars()`、`navigationBars()`。 +- 本项目主界面常将状态栏与导航栏隐藏(`isStatusBarVisible = false`, `isNavigationBarVisible = false`),由自定义顶部栏和底部栏替代。 + +## 行为与外观 + +- `systemBarsBehavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE`:隐藏时仍可从边缘滑出临时显示。 +- 图标颜色随主题:`isAppearanceLightStatusBars = !darkMode`,亮色主题用深色图标,暗色主题用浅色图标。 diff --git a/skills/top-app-bar.md b/skills/top-app-bar.md new file mode 100644 index 0000000..4f8e224 --- /dev/null +++ b/skills/top-app-bar.md @@ -0,0 +1,23 @@ +# Skill:顶部导航栏(App Bar) + +与具体业务无关的顶部导航栏设计:透明化、状态栏占位与使用约定。 + +--- + +## 透明化 + +- 使用 **TransparentTopAppBar**(Material3 TopAppBar 封装),支持三种模式: + - `FULLY_TRANSPARENT`:背景完全透明。 + - `SEMI_TRANSPARENT`:半透明,可设 `alpha`。 + - `GRADIENT`:自上而下渐变透明(常用)。 +- 已处理状态栏占位:`windowInsetsPadding(WindowInsets.statusBars)`。 +- 标题/返回/图标颜色需根据背景选:浅底用 `onSurface`,深色底用浅色,以保证可读性。 + +## 使用约定 + +- 普通页:在 `Scaffold.topBar` 中放 `TransparentTopAppBar`,按需选 `transparencyType`、`gradientColors`/`alpha`。 +- 聊天页等:可用 **ChatHeader**,通过 `isTransparent`、`transparencyType`、`alpha` 控制透明效果。 + +## 文档 + +- 详细用法见:`app-android/doc/透明化导航栏使用指南.md`。