Prechádzať zdrojové kódy

feat(stories): 添加故事017.015实现人才小程序登录状态持久化验证

- 对比用人方小程序发现人才小程序缺少登录状态验证
- 创建useRequireAuth hook用于页面级认证保护
- 优化AuthContext启动时的token验证逻辑
- 添加页面级认证保护到首页、个人信息、考勤、就业、设置页
- 包含完整的测试策略和技术实现指南

🤖 Generated with [Claude Code](https://claude.com/claude-code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
yourname 3 týždňov pred
rodič
commit
8bd6c2d5fe
1 zmenil súbory, kde vykonal 446 pridanie a 0 odobranie
  1. 446 0
      docs/stories/017.015.story.md

+ 446 - 0
docs/stories/017.015.story.md

@@ -0,0 +1,446 @@
+# 故事017.015: 实现人才小程序登录状态持久化验证
+
+## 元信息
+- **史诗**: 017 - 人才小程序功能实现
+- **优先级**: P1 - 核心功能
+- **状态**: Approved
+- **创建日期**: 2025-12-26
+- **负责人**: 开发团队
+
+## 故事描述
+
+**作为** 人才小程序开发者,
+**我想要** 实现登录状态持久化验证功能,
+**以便** 用户重新打开小程序时,如果之前已经登录过,则自动恢复登录状态,无需再次登录。
+
+### 背景
+
+**现有系统状态:**
+- ✅ 故事017.002已完成登录与首页实现
+- ✅ 人才用户认证API已集成(`talentAuthClient`)
+- ✅ AuthContext基础框架已建立(包含登录、登出、用户信息管理)
+- ✅ token存储机制已实现(localStorage: `talent_token`)
+
+**发现问题:**
+对比用人方小程序(`mini-enterprise-auth-ui`)的实现,人才小程序的认证系统缺少以下关键功能:
+
+1. **缺少`useRequireAuth` hook** - 用人方小程序有独立的`useRequireAuth` hook用于检查登录状态并自动跳转
+2. **缺少应用启动时的登录状态验证** - 用人方小程序在AuthProvider初始化时会自动检查本地token并验证用户信息
+3. **缺少页面级的认证保护** - 需要的页面没有使用`useRequireAuth`进行保护
+
+**用人方小程序参考实现:**
+- `mini-enterprise-auth-ui/src/hooks/useRequireAuth.ts` - 认证检查hook
+- `mini-enterprise-auth-ui/src/hooks/useAuth.tsx` - AuthContext中的自动登录状态验证(第31-55行)
+
+**技术依赖:**
+- ✅ 故事017.001: rencai mini ui包基础框架搭建完成
+- ✅ 故事017.002: 登录与首页实现完成
+- ✅ 人才用户认证API正常工作(`talentAuthClient.me.$get`)
+
+**技术栈:**
+- React 19.1.0
+- Taro 4.1.4
+- @tanstack/react-query 5.83.0 (用于状态管理)
+- Jest 30.2.0 (mini项目使用Jest)
+
+## 验收标准
+
+### useRequireAuth Hook实现
+- [ ] 创建`useRequireAuth` hook(参考`mini-enterprise-auth-ui/src/hooks/useRequireAuth.ts`)
+- [ ] hook检查登录状态,未登录时显示提示并跳转到登录页
+- [ ] 支持loading状态,避免重复跳转
+- [ ] 导出hook供其他页面使用
+
+### AuthContext启动时验证优化
+- [ ] AuthContext在初始化时自动检查本地存储的token
+- [ ] 如果存在token,自动调用`talentAuthClient.me.$get`验证用户信息
+- [ ] 验证成功时恢复用户登录状态
+- [ ] 验证失败时清除本地token并保持未登录状态
+- [ ] 使用React Query的`useQuery`管理用户状态(避免重复请求)
+
+### 页面级认证保护
+- [ ] 在首页(Dashboard)中使用`useRequireAuth`进行保护
+- [ ] 在个人信息页使用`useRequireAuth`进行保护
+- [ ] 在考勤记录页使用`useRequireAuth`进行保护
+- [ ] 在就业信息页使用`useRequireAuth`进行保护
+- [ ] 在设置页使用`useRequireAuth`进行保护
+- [ ] 登录页不使用认证保护(避免死循环)
+
+### 用户体验优化
+- [ ] 验证过程中显示loading状态
+- [ ] 未登录时显示友好的提示信息
+- [ ] 自动跳转到登录页(使用`Taro.reLaunch`清除页面栈)
+- [ ] 登录成功后跳转回原页面(可选,后续实现)
+
+### 测试覆盖
+- [ ] 为`useRequireAuth`编写单元测试
+- [ ] 测试登录状态验证逻辑
+- [ ] 测试未登录时自动跳转
+- [ ] 测试token过期时的处理
+- [ ] 测试页面级认证保护
+- [ ] 所有测试通过
+- [ ] 类型检查通过
+
+## 任务列表
+
+### 任务1: 创建useRequireAuth Hook (AC: useRequireAuth Hook实现)
+- [ ] 1.1 在`@d8d/rencai-auth-ui/src/hooks/`目录下创建`useRequireAuth.ts`文件
+- [ ] 1.2 参考用人方小程序实现:
+  ```typescript
+  import { useEffect } from 'react'
+  import Taro from '@tarojs/taro'
+  import { useAuth } from './useAuth'
+
+  export const useRequireAuth = (): void => {
+    const { isLoggedIn, isLoading } = useAuth()
+
+    useEffect(() => {
+      if (!isLoading && !isLoggedIn) {
+        Taro.showToast({
+          title: '请先登录',
+          icon: 'none',
+          duration: 1500
+        })
+
+        setTimeout(() => {
+          Taro.redirectTo({
+            url: '/pages/login/index'
+          })
+        }, 1500)
+      }
+    }, [isLoggedIn, isLoading])
+  }
+  ```
+- [ ] 1.3 实现登录状态检查逻辑
+- [ ] 1.4 实现未登录时的提示和跳转
+- [ ] 1.5 更新`@d8d/rencai-auth-ui/src/hooks/index.ts`,导出`useRequireAuth`
+
+### 任务2: 优化AuthContext启动时验证 (AC: AuthContext启动时验证优化)
+- [ ] 2.1 检查`@d8d/rencai-auth-ui/src/hooks/useAuth.tsx`中的`useQuery`实现
+- [ ] 2.2 确认已实现自动检查本地token的逻辑(第38-61行)
+- [ ] 2.3 确认已实现调用`talentAuthClient.me.$get`验证用户信息
+- [ ] 2.4 确认验证成功时恢复用户登录状态(设置`isLoggedIn`和`user`)
+- [ ] 2.5 确认验证失败时清除本地token
+- [ ] 2.6 如需要,优化loading状态的同步逻辑
+- [ ] 2.7 验证React Query的`staleTime: Infinity`配置(避免重复请求)
+
+### 任务3: 添加页面级认证保护 (AC: 页面级认证保护)
+- [ ] 3.1 在首页(`@d8d/rencai-dashboard-ui/src/pages/Dashboard/Dashboard.tsx`)中使用`useRequireAuth`:
+  ```typescript
+  import { useRequireAuth } from '@d8d/rencai-auth-ui/hooks'
+
+  export const Dashboard: React.FC = () => {
+    useRequireAuth() // 添加这行
+
+    // ... 其他代码
+  }
+  ```
+- [ ] 3.2 在个人信息页使用`useRequireAuth`
+- [ ] 3.3 在考勤记录页使用`useRequireAuth`
+- [ ] 3.4 在就业信息页使用`useRequireAuth`
+- [ ] 3.5 在设置页使用`useRequireAuth`
+- [ ] 3.6 确认登录页不使用`useRequireAuth`(避免死循环)
+
+### 任务4: 优化用户体验 (AC: 用户体验优化)
+- [ ] 4.1 确保AuthContext在验证过程中正确设置loading状态
+- [ ] 4.2 确保页面组件在loading时显示加载提示
+- [ ] 4.3 确保未登录时显示友好的提示信息
+- [ ] 4.4 使用`Taro.reLaunch`跳转到登录页(清除页面栈,避免返回)
+- [ ] 4.5 测试登录流程的用户体验
+
+### 任务5: 编写测试 (AC: 测试覆盖)
+- [ ] 5.1 创建`tests/hooks/useRequireAuth.test.tsx`:
+  - 测试登录状态检查
+  - 测试未登录时显示提示
+  - 测试未登录时跳转到登录页
+  - 测试loading状态避免重复跳转
+- [ ] 5.2 更新`tests/hooks/useAuth.test.tsx`(如存在):
+  - 测试启动时自动检查本地token
+  - 测试验证成功时恢复用户状态
+  - 测试验证失败时清除token
+- [ ] 5.3 编写集成测试验证页面级认证保护
+- [ ] 5.4 运行所有测试确保通过
+- [ ] 5.5 运行`pnpm typecheck`确保类型检查通过
+
+### 任务6: 更新文档和文件列表 (AC: 完成)
+- [ ] 6.1 在本故事的"开发者记录"中更新完成说明
+- [ ] 6.2 在本故事的"文件列表"中记录所有新增和修改的文件
+- [ ] 6.3 更新故事017.002的"变更日志",添加本故事的功能说明
+
+## 开发者笔记
+
+### 前置故事见解
+
+**故事017.002完成状态:**
+- ✅ 登录页面功能完整实现
+- ✅ 首页(Dashboard)功能完整实现
+- ✅ AuthContext基础框架已建立
+- ✅ token存储机制已实现
+- ✅ 人才用户认证API已集成
+- ⚠️ **但缺少应用启动时的登录状态验证**
+
+**用人方小程序参考实现:**
+`mini-enterprise-auth-ui/src/hooks/useAuth.tsx` (第31-55行):
+```typescript
+const { data: user, isLoading } = useQuery<User | null, Error>({
+  queryKey: ['currentUser'],
+  queryFn: async () => {
+    const token = Taro.getStorageSync('enterprise_token')
+    if (!token) {
+      return null
+    }
+    try {
+      const response = await enterpriseAuthClient.me.$get({})
+      if (response.status !== 200) {
+        throw new Error('获取用户信息失败')
+      }
+      const user = await response.json()
+      Taro.setStorageSync('enterpriseUserInfo', JSON.stringify(user))
+      return user
+    } catch (error) {
+      Taro.removeStorageSync('enterprise_token')
+      Taro.removeStorageSync('enterpriseUserInfo')
+      return null
+    }
+  },
+  staleTime: Infinity,
+  refetchOnWindowFocus: false,
+  refetchOnReconnect: false,
+})
+```
+
+`mini-enterprise-auth-ui/src/hooks/useRequireAuth.ts`:
+```typescript
+import { useEffect } from 'react'
+import Taro from '@tarojs/taro'
+import { useAuth } from './useAuth'
+
+export const useRequireAuth = () => {
+  const { isLoggedIn, isLoading } = useAuth()
+
+  useEffect(() => {
+    if (!isLoading && !isLoggedIn) {
+      Taro.showToast({
+        title: '请先登录',
+        icon: 'none',
+        duration: 1500
+      })
+
+      setTimeout(() => {
+        Taro.redirectTo({
+          url: '/pages/login/index'
+        })
+      }, 1500)
+    }
+  }, [isLoggedIn, isLoading])
+}
+```
+
+### 技术栈要求
+
+**来源**: [architecture/tech-stack.md](../architecture/tech-stack.md)
+
+**运行时和框架:**
+- **Node.js**: 20.18.3
+- **React**: 19.1.0 (UI组件)
+- **Taro**: 4.1.4 (小程序框架)
+- **@tanstack/react-query**: 5.83.0 (状态管理)
+
+**测试框架:**
+- **Jest**: 30.2.0 (mini项目使用Jest,不是Vitest!)
+- **ts-jest**: 29.4.5 (TypeScript预处理器)
+- **@testing-library/react**: 16.3.0 (React组件测试)
+- **@d8d/mini-testing-utils**: workspace包 (Taro小程序测试工具)
+
+### Mini UI包开发规范
+
+**来源**: [architecture/mini-ui-package-standards.md](../architecture/mini-ui-package-standards.md)
+
+**关键规范要求:**
+
+#### 1. UI包内部导入规范
+**重要**: UI包内部导入必须使用相对路径,不要使用别名。
+
+**正确示例**:
+```typescript
+// ✅ 正确: 使用相对路径导入同一包内的模块
+import { useAuth } from './useAuth'
+import { talentAuthClient } from '../api'
+```
+
+**错误示例**:
+```typescript
+// ❌ 错误: 不要使用别名导入UI包内部的模块
+import { useAuth } from '@/hooks/useAuth'
+import { talentAuthClient } from '@/api'
+```
+
+#### 2. Hook规范
+- Hook名称必须以`use`开头
+- Hook必须遵循React Hook规则
+- Hook应该提供类型安全的返回值
+- Hook应该在单独的文件中定义
+
+#### 3. Taro导航规范
+- 使用`Taro.reLaunch`跳转到登录页(清除页面栈)
+- 使用`Taro.redirectTo`跳转到其他页面(不返回)
+- 使用`Taro.navigateTo`打开新页面(可返回)
+
+### 项目结构指南
+
+**来源**: [architecture/source-tree.md](../architecture/source-tree.md)
+
+**rencai-auth-ui包结构:**
+```
+mini-ui-packages/rencai-auth-ui/
+├── src/
+│   ├── api/
+│   │   ├── talentAuthClient.ts
+│   │   └── index.ts
+│   ├── hooks/
+│   │   ├── index.ts (本故事修改)
+│   │   ├── useAuth.tsx (已存在,可能需要优化)
+│   │   └── useRequireAuth.ts (本故事新增)
+│   ├── pages/
+│   │   └── LoginPage/
+│   │       └── LoginPage.tsx
+│   ├── types/
+│   │   └── index.ts
+│   └── index.ts
+├── tests/
+│   ├── hooks/
+│   │   ├── useAuth.test.tsx (可能需要更新)
+│   │   └── useRequireAuth.test.tsx (本故事新增)
+│   └── pages/
+│       └── LoginPage/
+│           └── LoginPage.test.tsx
+├── package.json
+├── jest.config.cjs
+└── tsconfig.json
+```
+
+### 需要认证保护的页面
+
+**mini-talent/src/pages/**:
+1. `index/index.tsx` - 首页/个人主页 ✅ 需要保护
+2. `attendance/index.tsx` - 考勤记录 ✅ 需要保护
+3. `personal-info/index.tsx` - 个人信息 ✅ 需要保护
+4. `employment/index.tsx` - 就业信息 ✅ 需要保护
+5. `settings/index.tsx` - 设置页 ✅ 需要保护
+6. `login/index.tsx` - 登录页 ❌ 不需要保护
+
+### 测试策略
+
+**来源**: [architecture/mini-ui-testing-standards.md](../architecture/mini-ui-testing-standards.md)
+
+**测试框架**: Jest (mini项目使用Jest,不是Vitest!)
+
+**测试要求**:
+1. 测试`useRequireAuth` hook的功能
+2. 测试登录状态验证逻辑
+3. 测试未登录时的提示和跳转
+4. 测试loading状态避免重复跳转
+5. 测试页面级认证保护
+6. 运行`pnpm typecheck`确保类型检查通过
+
+**Mock Taro API**:
+```typescript
+const mockReLaunch = jest.fn()
+Taro.reLaunch = mockReLaunch
+const mockToast = jest.fn()
+Taro.showToast = mockToast
+```
+
+### 技术约束
+
+1. **向后兼容**: 不影响现有mini-talent项目功能
+2. **类型安全**: 使用TypeScript严格模式,所有hook必须有类型定义
+3. **模块独立性**: hook遵循UI包内部导入规范
+4. **测试覆盖**: 所有新增代码必须有测试覆盖
+5. **代码规范**: 遵循Mini UI包开发规范
+
+### 风险和缓解措施
+
+**主要风险:**
+1. **页面跳转循环风险**: 登录页使用`useRequireAuth`可能导致死循环
+2. **Loading状态不一致**: AuthContext和`useRequireAuth`的loading状态可能不同步
+3. **页面栈管理**: 使用错误的跳转方法可能导致页面栈混乱
+
+**缓解措施:**
+1. **明确不在登录页使用**: 在文档中明确说明登录页不使用`useRequireAuth`
+2. **统一loading状态**: 使用AuthContext的loading状态作为唯一来源
+3. **使用正确的跳转方法**: 登录页使用`Taro.reLaunch`清除页面栈
+4. **测试覆盖**: 编写完整的测试验证各种场景
+
+## 测试
+
+### 测试框架和模式
+
+**来源**: [architecture/testing-strategy.md](../architecture/testing-strategy.md)
+
+**测试框架:**
+- **Jest**: 30.2.0 (mini项目使用Jest,不是Vitest!)
+- **ts-jest**: 29.4.5 (TypeScript预处理器)
+- **@testing-library/react**: 16.3.0 (React组件测试)
+- **@d8d/mini-testing-utils**: workspace包 (Taro小程序测试工具)
+
+### 测试要求
+
+1. **Hook测试**:
+   - 测试`useRequireAuth`的登录状态检查
+   - 测试未登录时的提示显示
+   - 测试未登录时的页面跳转
+   - 测试loading状态避免重复跳转
+
+2. **集成测试**:
+   - 测试AuthContext启动时的登录状态验证
+   - 测试token存在时的自动登录
+   - 测试token过期时的处理
+   - 测试页面级认证保护
+
+3. **回归测试**:
+   - 验证现有功能不受影响
+   - 运行`pnpm typecheck`确保类型检查通过
+
+### 测试执行
+
+```bash
+# 运行所有测试
+cd mini-ui-packages/rencai-auth-ui && pnpm test
+
+# 运行特定测试
+pnpm test --testNamePattern="useRequireAuth"
+
+# 生成覆盖率报告
+pnpm test:coverage
+```
+
+## 变更日志
+
+| 日期 | 版本 | 描述 | 作者 |
+|------|------|------|------|
+| 2025-12-26 | 1.0 | 创建故事文档 | James (Dev Agent) |
+
+## 开发者记录
+
+*此部分由开发代理在实施过程中填写*
+
+### 使用的代理模型
+
+Claude Sonnet (claude-sonnet-4-20250514)
+
+### 调试日志引用
+
+待实施时填写
+
+### 完成说明列表
+
+待实施时填写
+
+### 文件列表
+
+待实施时填写
+
+## QA结果
+
+*此部分由QA代理在审查完成后填写*