diff --git a/.gitignore b/.gitignore index 392de6f7..30d6cee1 100644 --- a/.gitignore +++ b/.gitignore @@ -45,5 +45,6 @@ Thumbs.db *.md !README.md !CLAUDE.md +!docs/**/*.md src/assets/img/original-backup/ diff --git a/docs/API_DOCS_profile_completeness.md b/docs/API_DOCS_profile_completeness.md new file mode 100644 index 00000000..1a1e7780 --- /dev/null +++ b/docs/API_DOCS_profile_completeness.md @@ -0,0 +1,458 @@ +# 用户资料完整度 API 文档 + +## 接口概述 + +**接口名称**:获取用户资料完整度 +**接口路径**:`/api/account/profile-completeness` +**请求方法**:`GET` +**接口描述**:获取当前登录用户的资料完整度信息,包括各项必填信息的完成状态、完整度百分比、缺失项列表等。 +**业务场景**:用于在用户登录后提醒用户完善个人资料,提升平台服务质量。 + +--- + +## 请求参数 + +### Headers + +| 参数名 | 类型 | 必填 | 描述 | +|--------|------|------|------| +| `Cookie` | string | 是 | 包含用户会话信息(session cookie),用于身份认证 | + +### Query Parameters + +无 + +### Body Parameters + +无(GET 请求无 Body) + +--- + +## 响应格式 + +### 成功响应 (200 OK) + +**Content-Type**: `application/json` + +```json +{ + "success": true, + "data": { + "completeness": { + "hasPassword": true, + "hasPhone": true, + "hasEmail": false, + "isWechatUser": false + }, + "completenessPercentage": 66, + "needsAttention": false, + "missingItems": ["邮箱"], + "isComplete": false, + "showReminder": false + } +} +``` + +### 响应字段说明 + +#### 顶层字段 + +| 字段名 | 类型 | 描述 | +|--------|------|------| +| `success` | boolean | 请求是否成功,`true` 表示成功 | +| `data` | object | 资料完整度数据对象 | + +#### `data` 对象字段 + +| 字段名 | 类型 | 描述 | +|--------|------|------| +| `completeness` | object | 各项资料的完成状态详情 | +| `completenessPercentage` | number | 资料完整度百分比(0-100) | +| `needsAttention` | boolean | 是否需要用户注意(提醒用户完善) | +| `missingItems` | array[string] | 缺失项的中文描述列表 | +| `isComplete` | boolean | 资料是否完全完整(100%) | +| `showReminder` | boolean | 是否显示提醒横幅(同 `needsAttention`) | + +#### `completeness` 对象字段 + +| 字段名 | 类型 | 描述 | +|--------|------|------| +| `hasPassword` | boolean | 是否已设置登录密码 | +| `hasPhone` | boolean | 是否已绑定手机号 | +| `hasEmail` | boolean | 是否已设置有效邮箱(排除临时邮箱) | +| `isWechatUser` | boolean | 是否为微信登录用户 | + +--- + +## 业务逻辑说明 + +### 资料完整度计算规则 + +1. **必填项**(共 3 项): + - 登录密码(`hasPassword`) + - 手机号(`hasPhone`) + - 邮箱(`hasEmail`) + +2. **完整度计算公式**: + ``` + completenessPercentage = (已完成项数 / 3) × 100 + ``` + 示例: + - 已完成 2 项 → 66% + - 已完成 3 项 → 100% + +3. **邮箱有效性判断**: + - 必须包含 `@` 符号 + - 不能是临时邮箱(如 `*@valuefrontier.temp`) + +### 提醒逻辑(`needsAttention`) + +**仅对微信登录用户进行提醒**,需同时满足以下条件: + +1. `isWechatUser === true`(微信登录用户) +2. `completenessPercentage < 100`(资料不完整) + +**后端额外的智能提醒策略**(Mock 模式未实现): + +- 新用户(注册 7 天内):更容易触发提醒 +- 每 7 天最多提醒一次(通过 session 记录) +- 完整度低于 50% 时优先提醒 + +### 缺失项列表(`missingItems`) + +根据 `completeness` 对象生成中文描述: + +| 条件 | 添加到 `missingItems` | +|------|----------------------| +| `!hasPassword` | `"登录密码"` | +| `!hasPhone` | `"手机号"` | +| `!hasEmail` | `"邮箱"` | + +--- + +## 响应示例 + +### 示例 1:手机号登录用户,资料完整 + +**场景**:手机号登录,已设置密码和邮箱 + +```json +{ + "success": true, + "data": { + "completeness": { + "hasPassword": true, + "hasPhone": true, + "hasEmail": true, + "isWechatUser": false + }, + "completenessPercentage": 100, + "needsAttention": false, + "missingItems": [], + "isComplete": true, + "showReminder": false + } +} +``` + +### 示例 2:微信登录用户,未绑定手机号 + +**场景**:微信登录,未设置密码和手机号,触发提醒 + +```json +{ + "success": true, + "data": { + "completeness": { + "hasPassword": false, + "hasPhone": false, + "hasEmail": true, + "isWechatUser": true + }, + "completenessPercentage": 33, + "needsAttention": true, + "missingItems": ["登录密码", "手机号"], + "isComplete": false, + "showReminder": true + } +} +``` + +### 示例 3:微信登录用户,只缺邮箱 + +**场景**:微信登录,已设置密码和手机号,只缺邮箱 + +```json +{ + "success": true, + "data": { + "completeness": { + "hasPassword": true, + "hasPhone": true, + "hasEmail": false, + "isWechatUser": true + }, + "completenessPercentage": 66, + "needsAttention": true, + "missingItems": ["邮箱"], + "isComplete": false, + "showReminder": true + } +} +``` + +--- + +## 错误响应 + +### 401 Unauthorized - 未登录 + +**场景**:用户未登录或 Session 已过期 + +```json +{ + "success": false, + "error": "用户未登录" +} +``` + +**HTTP 状态码**:`401` + +### 500 Internal Server Error - 服务器错误 + +**场景**:服务器内部错误(如数据库连接失败) + +```json +{ + "success": false, + "error": "获取资料完整性错误: [错误详情]" +} +``` + +**HTTP 状态码**:`500` + +--- + +## 调用示例 + +### JavaScript (Fetch API) + +```javascript +async function checkProfileCompleteness() { + try { + const response = await fetch('/api/account/profile-completeness', { + method: 'GET', + credentials: 'include', // 重要:携带 Cookie + headers: { + 'Content-Type': 'application/json' + } + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + + if (data.success) { + console.log('资料完整度:', data.data.completenessPercentage + '%'); + console.log('是否需要提醒:', data.data.needsAttention); + + if (data.data.needsAttention) { + console.log('缺失项:', data.data.missingItems.join('、')); + } + } + } catch (error) { + console.error('检查资料完整性失败:', error); + } +} +``` + +### cURL + +```bash +curl -X GET 'http://localhost:5001/api/account/profile-completeness' \ + -H 'Cookie: session=your_session_cookie_here' \ + -H 'Content-Type: application/json' +``` + +### Axios + +```javascript +import axios from 'axios'; + +async function checkProfileCompleteness() { + try { + const { data } = await axios.get('/api/account/profile-completeness', { + withCredentials: true // 携带 Cookie + }); + + if (data.success) { + return data.data; + } + } catch (error) { + if (error.response?.status === 401) { + console.error('用户未登录'); + } else { + console.error('检查失败:', error.message); + } + } +} +``` + +--- + +## 调用时机建议 + +### ✅ 推荐调用场景 + +1. **用户登录后**:首次登录或刷新页面后检查一次 +2. **资料更新后**:用户修改个人资料后重新检查 +3. **手动触发**:用户点击"检查资料完整度"按钮 + +### ❌ 避免的场景 + +1. **导航栏每次 render 时**:会导致频繁请求 +2. **组件重新渲染时**:应使用缓存或标志位避免重复 +3. **轮询调用**:此接口不需要轮询,用户资料变化频率低 + +### 最佳实践 + +```javascript +// 使用 React Hooks 的最佳实践 +function useProfileCompleteness() { + const [completeness, setCompleteness] = useState(null); + const hasChecked = useRef(false); + const { isAuthenticated, user } = useAuth(); + + const check = useCallback(async () => { + // 避免重复检查 + if (hasChecked.current) return; + + try { + const response = await fetch('/api/account/profile-completeness', { + credentials: 'include' + }); + + if (response.ok) { + const data = await response.json(); + if (data.success) { + setCompleteness(data.data); + hasChecked.current = true; // 标记已检查 + } + } + } catch (error) { + console.warn('检查失败:', error); + } + }, []); + + // 仅在登录后检查一次 + useEffect(() => { + if (isAuthenticated && user && !hasChecked.current) { + check(); + } + }, [isAuthenticated, user, check]); + + // 提供手动刷新方法 + const refresh = useCallback(() => { + hasChecked.current = false; + check(); + }, [check]); + + return { completeness, refresh }; +} +``` + +--- + +## Mock 模式说明 + +在 Mock 模式下(`REACT_APP_ENABLE_MOCK=true`),此接口由 MSW (Mock Service Worker) 拦截: + +### Mock 实现位置 + +- **Handler**: `src/mocks/handlers/account.js` +- **数据源**: `src/mocks/data/users.js` (getCurrentUser) + +### Mock 特点 + +1. **真实计算**:基于当前登录用户的实际数据计算完整度 +2. **状态同步**:与登录状态同步,登录后才返回真实用户数据 +3. **未登录返回 401**:模拟真实后端行为 +4. **延迟模拟**:300ms 网络延迟,模拟真实请求 + +### Mock 测试数据 + +| 测试账号 | 手机号 | 密码 | 邮箱 | 微信 | 完整度 | +|---------|--------|------|------|------|--------| +| 测试用户 | 13800138000 | ✅ | ❌ | ❌ | 66% | +| 投资达人 | 13900139000 | ✅ | ✅ | ✅ | 100% | + +--- + +## 前端集成示例 + +### 显示资料完整度横幅 + +```jsx +import { useProfileCompleteness } from './hooks/useProfileCompleteness'; + +function App() { + const { completeness } = useProfileCompleteness(); + + return ( + <> + {/* 资料完整度提醒横幅 */} + {completeness?.showReminder && ( + + + + 完善资料,享受更好服务 + + 您还需要设置:{completeness.missingItems.join('、')} + ({completeness.completenessPercentage}% 完成) + + + + + )} + + {/* 主要内容 */} + + + ); +} +``` + +--- + +## 版本历史 + +| 版本 | 日期 | 变更说明 | +|------|------|----------| +| v1.0 | 2024-10-17 | 初始版本,支持资料完整度检查和智能提醒 | + +--- + +## 相关接口 + +- `GET /api/auth/session` - 检查登录状态 +- `GET /api/account/profile` - 获取完整用户资料 +- `PUT /api/account/profile` - 更新用户资料 +- `POST /api/auth/logout` - 退出登录 + +--- + +## 技术支持 + +如有问题,请联系开发团队或查看: +- **Mock 配置指南**: [MOCK_GUIDE.md](./MOCK_GUIDE.md) +- **项目文档**: [CLAUDE.md](./CLAUDE.md) + +--- + +**文档生成日期**:2024-10-17 +**API 版本**:v1.0 +**Mock 支持**:✅ 已实现 diff --git a/docs/API_ENDPOINTS.md b/docs/API_ENDPOINTS.md new file mode 100644 index 00000000..632cbc54 --- /dev/null +++ b/docs/API_ENDPOINTS.md @@ -0,0 +1,415 @@ +# API 接口文档 + +本文档记录了项目中所有 API 接口的详细信息。 + +## 目录 +- [认证相关 API](#认证相关-api) +- [个人中心相关 API](#个人中心相关-api) +- [事件相关 API](#事件相关-api) +- [股票相关 API](#股票相关-api) +- [公司相关 API](#公司相关-api) +- [订阅/支付相关 API](#订阅支付相关-api) + +--- + +## 认证相关 API + +### POST /api/auth/send-verification-code +发送验证码到手机号或邮箱 + +**请求参数**: +```json +{ + "credential": "13800138000", // 手机号或邮箱 + "type": "phone", // 'phone' | 'email' + "purpose": "login" // 'login' | 'register' +} +``` + +**响应示例**: +```json +{ + "success": true, + "message": "验证码已发送到 13800138000", + "dev_code": "123456" // 仅开发环境返回 +} +``` + +**错误响应**: +```json +{ + "success": false, + "error": "发送验证码失败" +} +``` + +**Mock 数据**: ✅ `src/mocks/handlers/auth.js` 行 21-44 + +**涉及文件**: +- `src/components/Auth/AuthFormContent.js` 行 164-207 + +--- + +### POST /api/auth/login-with-code +使用验证码登录(支持自动注册新用户) + +**请求参数**: +```json +{ + "credential": "13800138000", + "verification_code": "123456", + "login_type": "phone" // 'phone' | 'email' +} +``` + +**响应示例**: +```json +{ + "success": true, + "message": "登录成功", + "isNewUser": false, + "user": { + "id": 1, + "phone": "13800138000", + "nickname": "用户昵称", + "email": null, + "avatar_url": "https://...", + "has_wechat": false + }, + "token": "mock_token_1_1234567890" +} +``` + +**错误响应**: +```json +{ + "success": false, + "error": "验证码错误" +} +``` + +**Mock 数据**: ✅ `src/mocks/handlers/auth.js` 行 47-115 + +**涉及文件**: +- `src/components/Auth/AuthFormContent.js` 行 252-327 + +**注意事项**: +- 后端需要支持自动注册新用户(当用户不存在时) +- 前端已添加 `.trim()` 防止空格问题 + +--- + +### GET /api/auth/session +检查当前登录状态 + +**响应示例**: +```json +{ + "success": true, + "isAuthenticated": true, + "user": { + "id": 1, + "phone": "13800138000", + "nickname": "用户昵称" + } +} +``` + +**Mock 数据**: ✅ `src/mocks/handlers/auth.js` 行 269-290 + +--- + +### POST /api/auth/logout +退出登录 + +**响应示例**: +```json +{ + "success": true, + "message": "退出成功" +} +``` + +**Mock 数据**: ✅ `src/mocks/handlers/auth.js` 行 317-329 + +--- + +## 个人中心相关 API + +### GET /api/account/watchlist +获取用户自选股列表 + +**响应示例**: +```json +{ + "success": true, + "data": [ + { + "id": 1, + "stock_code": "000001.SZ", + "stock_name": "平安银行", + "added_at": "2024-01-01T00:00:00Z" + } + ] +} +``` + +**Mock 数据**: ❌ 待创建 `src/mocks/handlers/account.js` + +**涉及文件**: +- `src/views/Dashboard/Center.js` 行 94 + +--- + +### GET /api/account/watchlist/realtime +获取自选股实时行情 + +**响应示例**: +```json +{ + "success": true, + "data": { + "000001.SZ": { + "price": 12.34, + "change": 0.56, + "change_percent": 4.76, + "volume": 123456789 + } + } +} +``` + +**Mock 数据**: ❌ 待创建 + +**涉及文件**: +- `src/views/Dashboard/Center.js` 行 133 + +--- + +### GET /api/account/events/following +获取用户关注的事件列表 + +**响应示例**: +```json +{ + "success": true, + "data": [ + { + "id": 1, + "title": "事件标题", + "followed_at": "2024-01-01T00:00:00Z" + } + ] +} +``` + +**Mock 数据**: ❌ 待创建 + +**涉及文件**: +- `src/views/Dashboard/Center.js` 行 95 + +--- + +### GET /api/account/events/comments +获取用户的事件评论 + +**响应示例**: +```json +{ + "success": true, + "data": [ + { + "id": 1, + "event_id": 123, + "content": "评论内容", + "created_at": "2024-01-01T00:00:00Z" + } + ] +} +``` + +**Mock 数据**: ❌ 待创建 + +**涉及文件**: +- `src/views/Dashboard/Center.js` 行 96 + +--- + +### GET /api/subscription/current +获取当前订阅信息 + +**响应示例**: +```json +{ + "success": true, + "data": { + "plan": "premium", + "expires_at": "2025-01-01T00:00:00Z", + "auto_renew": true + } +} +``` + +**Mock 数据**: ❌ 待创建 `src/mocks/handlers/subscription.js` + +**涉及文件**: +- `src/views/Dashboard/Center.js` 行 97 + +--- + +## 事件相关 API + +### GET /api/events +获取事件列表 + +**查询参数**: +- `page`: 页码(默认 1) +- `per_page`: 每页数量(默认 10) +- `sort`: 排序方式 ('new' | 'hot' | 'returns') +- `importance`: 重要性筛选 ('all' | 'high' | 'medium' | 'low') +- `date_range`: 日期范围 +- `q`: 搜索关键词 +- `industry_classification`: 行业分类 +- `industry_code`: 行业代码 + +**响应示例**: +```json +{ + "success": true, + "data": { + "events": [ + { + "id": 1, + "title": "事件标题", + "importance": "high", + "created_at": "2024-01-01T00:00:00Z" + } + ], + "pagination": { + "page": 1, + "per_page": 10, + "total": 100 + } + } +} +``` + +**Mock 数据**: ⚠️ 部分实现(需完善) + +**涉及文件**: +- `src/views/Community/index.js` 行 148 + +--- + +### GET /api/events/:id +获取事件详情 + +**响应示例**: +```json +{ + "success": true, + "data": { + "id": 1, + "title": "事件标题", + "content": "事件内容", + "importance": "high", + "created_at": "2024-01-01T00:00:00Z" + } +} +``` + +**Mock 数据**: ❌ 待创建 + +--- + +### GET /api/events/:id/stocks +获取事件相关股票 + +**响应示例**: +```json +{ + "success": true, + "data": [ + { + "stock_code": "000001.SZ", + "stock_name": "平安银行", + "correlation": 0.85 + } + ] +} +``` + +**Mock 数据**: ✅ `src/mocks/handlers/event.js` 行 12-38 + +--- + +### GET /api/events/popular-keywords +获取热门关键词 + +**查询参数**: +- `limit`: 返回数量(默认 20) + +**响应示例**: +```json +{ + "success": true, + "data": [ + { + "keyword": "人工智能", + "count": 123, + "trend": "up" + } + ] +} +``` + +**Mock 数据**: ❌ 待创建 + +**涉及文件**: +- `src/views/Community/index.js` 行 180 + +--- + +### GET /api/events/hot +获取热点事件 + +**查询参数**: +- `days`: 天数范围(默认 5) +- `limit`: 返回数量(默认 4) + +**响应示例**: +```json +{ + "success": true, + "data": [ + { + "id": 1, + "title": "热点事件标题", + "heat_score": 95.5 + } + ] +} +``` + +**Mock 数据**: ❌ 待创建 + +**涉及文件**: +- `src/views/Community/index.js` 行 192 + +--- + +## 待补充 API + +以下 API 将在重构其他文件时逐步添加: + +- 股票相关 API +- 公司相关 API +- 订阅/支付相关 API +- 用户资料相关 API +- 行业分类相关 API + +--- + +## 更新日志 + +- 2024-XX-XX: 创建文档,记录认证和个人中心相关 API diff --git a/docs/AUTHENTICATION_SYSTEM_GUIDE.md b/docs/AUTHENTICATION_SYSTEM_GUIDE.md new file mode 100644 index 00000000..c01738c4 --- /dev/null +++ b/docs/AUTHENTICATION_SYSTEM_GUIDE.md @@ -0,0 +1,1879 @@ +# 价值前沿认证系统完整文档 + +> **版本**: 2.0 +> **更新日期**: 2025-01-16 +> **作者**: Claude Code +> **适用范围**: 前端 React + 后端 Flask + +--- + +## 📖 目录 + +1. [系统架构概览](#1-系统架构概览) +2. [认证流程详解](#2-认证流程详解) + - [2.1 手机验证码登录](#21-手机验证码登录) + - [2.2 微信PC扫码登录](#22-微信pc扫码登录) + - [2.3 微信H5网页授权](#23-微信h5网页授权) +3. [路由配置与跳转逻辑](#3-路由配置与跳转逻辑) +4. [API接口文档](#4-api接口文档) +5. [前端组件架构](#5-前端组件架构) +6. [状态管理机制](#6-状态管理机制) +7. [Session持久化](#7-session持久化) +8. [错误处理策略](#8-错误处理策略) +9. [安全机制](#9-安全机制) +10. [调试指南](#10-调试指南) + +--- + +## 1. 系统架构概览 + +### 1.1 技术栈 + +**前端**: +- React 18.3.1 +- Chakra UI 2.8.2 +- React Router 6.x +- Context API (状态管理) + +**后端**: +- Flask (Python) +- Session-based Authentication +- HttpOnly Cookies +- Flask-Session + +**认证方式**: +1. **手机验证码登录** (短信验证码) +2. **微信PC扫码登录** (二维码扫码) +3. **微信H5网页授权** (移动端跳转授权) + +### 1.2 架构图 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ 用户界面层 (UI) │ +│ ┌───────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ HomeNavbar │ │ AuthModal │ │ProtectedRoute│ │ +│ │ (登录按钮) │ │ (认证弹窗) │ │ (路由保护) │ │ +│ └───────────────┘ └──────────────┘ └──────────────┘ │ +└─────────────────────────────────────────────────────────────┘ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ 状态管理层 (Context) │ +│ ┌──────────────────────┐ ┌───────────────────────┐ │ +│ │ AuthContext │ │ AuthModalContext │ │ +│ │ - user │ │ - isAuthModalOpen │ │ +│ │ - isAuthenticated │ │ - openAuthModal() │ │ +│ │ - checkSession() │ │ - handleLoginSuccess()│ │ +│ └──────────────────────┘ └───────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ 业务逻辑层 (Components) │ +│ ┌──────────────────┐ ┌─────────────┐ ┌────────────┐ │ +│ │ AuthFormContent │ │WechatRegister│ │VerifyCodeInput│ │ +│ │ (认证表单) │ │ (微信扫码) │ │ (验证码输入)│ │ +│ └──────────────────┘ └─────────────┘ └────────────┘ │ +└─────────────────────────────────────────────────────────────┘ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ 服务层 (API Service) │ +│ ┌──────────────┐ │ +│ │ authService │ │ +│ │ - getWechatQRCode() │ +│ │ - checkWechatStatus() │ +│ │ - loginWithWechat() │ +│ └──────────────┘ │ +└─────────────────────────────────────────────────────────────┘ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ 后端 API (Flask) │ +│ ┌─────────────────────────────────────────────────┐ │ +│ │ /api/auth/session - Session检查 │ │ +│ │ /api/auth/send-verification-code - 发送验证码 │ │ +│ │ /api/auth/login-with-code - 验证码登录 │ │ +│ │ /api/auth/wechat/qrcode - 获取微信二维码 │ │ +│ │ /api/auth/wechat/check - 检查扫码状态 │ │ +│ │ /api/auth/login/wechat - 微信登录 │ │ +│ │ /api/auth/wechat/h5-auth - 微信H5授权 │ │ +│ │ /api/auth/wechat/h5-callback - 微信H5回调 │ │ +│ │ /api/auth/logout - 退出登录 │ │ +│ └─────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ 数据存储层 (Database) │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ User表 │ │ Session存储 │ │ 验证码缓存 │ │ +│ └──────────────┘ └──────────────┘ └──────────────┘ │ +└─────────────────────────────────────────────────────────────┘ +``` + +--- + +## 2. 认证流程详解 + +### 2.1 手机验证码登录 + +**适用场景**: PC端和移动端通用登录方式 + +#### 流程图 + +``` +用户点击"登录/注册"按钮 + ↓ +打开 AuthModal 弹窗 + ↓ +输入手机号 → 点击"获取验证码" + ↓ +前端: AuthFormContent.sendVerificationCode() + ↓ +POST /api/auth/send-verification-code + Body: { + credential: "13800138000", + type: "phone", + purpose: "login" + } + ↓ +后端: 生成6位验证码 → 发送短信 → 存入Session/Redis (5分钟有效期) + ↓ +返回: { success: true } + ↓ +前端显示: "验证码已发送" + 60秒倒计时 + ↓ +用户输入验证码 → 点击"登录/注册" + ↓ +前端: AuthFormContent.handleSubmit() + ↓ +POST /api/auth/login-with-code + Body: { + credential: "13800138000", + verification_code: "123456", + login_type: "phone" + } + ↓ +后端验证: + 1. 验证码是否存在 ✓ + 2. 验证码是否过期 ✓ + 3. 验证码是否匹配 ✓ + 4. 查询用户是否存在 + - 存在: 登录 (isNewUser: false) + - 不存在: 自动注册 (isNewUser: true) + ↓ +设置 Session Cookie (HttpOnly) + ↓ +返回: { + success: true, + isNewUser: true/false, + user: { id, phone, nickname, ... } +} + ↓ +前端: + 1. checkSession() 更新全局状态 + 2. 显示成功提示 + 3. 如果 isNewUser=true → 显示昵称设置引导 + 4. 关闭弹窗,留在当前页面 + ↓ +登录完成 ✅ +``` + +#### 关键代码位置 + +**前端**: +- `src/components/Auth/AuthFormContent.js:130` - 发送验证码 +- `src/components/Auth/AuthFormContent.js:207` - 提交登录 +- `src/components/Auth/VerificationCodeInput.js` - 验证码输入组件 + +**后端**: +- `app.py:1826` - POST /api/auth/send-verification-code +- `app.py:1884` - POST /api/auth/login-with-code + +--- + +### 2.2 微信PC扫码登录 + +**适用场景**: PC端桌面浏览器 + +#### 流程图 + +``` +用户打开登录弹窗 (桌面端) + ↓ +右侧显示微信二维码区域 (WechatRegister组件) + ↓ +初始状态: 灰色二维码图标 + "获取二维码"按钮 + ↓ +用户点击"获取二维码" + ↓ +前端: WechatRegister.getWechatQRCode() + ↓ +GET /api/auth/wechat/qrcode + ↓ +后端: + 1. 生成唯一 session_id (UUID) + 2. 构建微信开放平台授权URL + 3. 存储到临时状态 (5分钟有效期) + ↓ +返回: { + code: 0, + data: { + auth_url: "https://open.weixin.qq.com/connect/qrconnect?...", + session_id: "uuid-xxxxx" + } +} + ↓ +前端: + 1. 在 iframe 中显示微信二维码 + 2. 启动轮询: 每2秒检查扫码状态 + 3. 启动备用轮询: 每3秒检查 (防止丢失) + 4. 设置超时: 5分钟后二维码过期 + ↓ +【轮询检查】 +POST /api/auth/wechat/check + Body: { session_id: "uuid-xxxxx" } + ↓ +后端返回状态: + - waiting: 等待扫码 + - scanned: 已扫码,等待确认 + - authorized: 已授权 + - login_success: 登录成功 (老用户) + - register_success: 注册成功 (新用户) + - expired: 二维码过期 + ↓ +如果状态 = login_success / register_success: + ↓ +前端: WechatRegister.handleLoginSuccess() + ↓ +POST /api/auth/login/wechat + Body: { session_id: "uuid-xxxxx" } + ↓ +后端: + 1. 从临时状态获取微信用户信息 + 2. 查询数据库是否存在该微信用户 + - 存在: 登录 + - 不存在: 自动注册 + 3. 设置 Session Cookie + ↓ +返回: { + success: true, + user: { id, nickname, avatar_url, ... }, + token: "optional-token" +} + ↓ +前端: + 1. 停止轮询 + 2. checkSession() 更新状态 + 3. 显示成功提示 + 4. 1秒后跳转 /home + ↓ +登录完成 ✅ +``` + +#### 关键代码位置 + +**前端**: +- `src/components/Auth/WechatRegister.js:199` - 获取二维码 +- `src/components/Auth/WechatRegister.js:120` - 检查扫码状态 +- `src/components/Auth/WechatRegister.js:85` - 登录成功处理 +- `src/services/authService.js:69` - API服务 + +**后端**: +- `app.py:2487` - GET /api/auth/wechat/qrcode +- `app.py:2560` - POST /api/auth/wechat/check +- `app.py:2743` - POST /api/auth/login/wechat + +--- + +### 2.3 微信H5网页授权 + +**适用场景**: 移动端浏览器中打开 + +#### 流程图 + +``` +移动端用户点击验证码输入框下方的微信图标 + ↓ +前端: AuthFormContent.handleWechatH5Login() + ↓ +构建回调URL: https://yourdomain.com/home/wechat-callback + ↓ +POST /api/auth/wechat/h5-auth + Body: { redirect_url: "https://..." } + ↓ +后端: + 1. 构建微信网页授权URL (snsapi_userinfo) + 2. 生成 state 参数防止CSRF + ↓ +返回: { + auth_url: "https://open.weixin.qq.com/connect/oauth2/authorize?..." +} + ↓ +前端: 延迟500ms后跳转到微信授权页面 + ↓ +window.location.href = auth_url + ↓ +【用户在微信授权页面确认】 + ↓ +微信回调: https://yourdomain.com/home/wechat-callback?code=xxx&state=yyy + ↓ +前端: WechatCallback 组件接收回调 + ↓ +POST /api/auth/wechat/h5-callback + Body: { code: "xxx", state: "yyy" } + ↓ +后端: + 1. 验证 state 参数 + 2. 使用 code 换取 access_token + 3. 使用 access_token 获取微信用户信息 + 4. 查询数据库 + - 存在: 登录 + - 不存在: 自动注册 + 5. 设置 Session Cookie + ↓ +返回: { + success: true, + user: { ... }, + token: "optional" +} + ↓ +前端: + 1. 存储 token (可选) + 2. checkSession() 更新状态 + 3. 显示"登录成功" + 4. 1.5秒后跳转 /home + ↓ +登录完成 ✅ +``` + +#### 关键代码位置 + +**前端**: +- `src/components/Auth/AuthFormContent.js:308` - 发起H5授权 +- `src/views/Pages/WechatCallback.js:34` - 处理回调 +- `src/services/authService.js:78` - H5授权API +- `src/services/authService.js:91` - H5回调API + +**后端**: +- `app.py:2487+` - POST /api/auth/wechat/h5-auth (需确认) +- `app.py:2610+` - POST /api/auth/wechat/h5-callback (需确认) + +--- + +## 3. 路由配置与跳转逻辑 + +### 3.1 路由结构 + +```javascript +// src/App.js + + + {/* 公开路由 - 无需登录 */} + } /> + } /> + + {/* 受保护路由 - 需要登录 */} + } /> + + + +// src/layouts/Home.js (公开路由) + + } /> + } /> + } /> + } /> + + {/* 需要登录的页面 */} + + + + } /> + + + + } /> + +``` + +### 3.2 路由保护机制 + +**ProtectedRoute 组件** (`src/components/ProtectedRoute.js`) + +```javascript +const ProtectedRoute = ({ children }) => { + const { isAuthenticated, isLoading, user } = useAuth(); + const { openAuthModal, isAuthModalOpen } = useAuthModal(); + + // 未登录时自动弹出认证窗口 + useEffect(() => { + if (!isLoading && !isAuthenticated && !user && !isAuthModalOpen) { + openAuthModal(currentPath); // 记录当前路径 + } + }, [isAuthenticated, user, isLoading]); + + // 加载中: 显示 Spinner + if (isLoading) { + return ; + } + + // 未登录: 显示页面 + 自动打开弹窗 (非阻断式) + // 已登录: 正常显示页面 + return children; +}; +``` + +**特点**: +- ✅ 非阻断式保护 (弹窗而非重定向) +- ✅ 记录原始路径 (登录后可返回) +- ✅ 避免白屏 (先显示页面骨架) + +### 3.3 跳转逻辑总结 + +| 场景 | 触发位置 | 跳转目标 | 说明 | +|------|---------|---------|------| +| 未登录访问受保护页面 | ProtectedRoute | 留在当前页 + 弹窗 | 非阻断式 | +| 点击导航栏登录按钮 | HomeNavbar:740 | 打开 AuthModal | 弹窗形式 | +| 手机验证码登录成功 | AuthFormContent:284 | 留在当前页 | 关闭弹窗即可 | +| 微信扫码登录成功 | WechatRegister:106 | 跳转 /home | 1秒延迟 | +| 微信H5授权成功 | WechatCallback:69 | 跳转 /home | 1.5秒延迟 | +| 用户点击退出登录 | HomeNavbar:227 | 跳转 /home | 清除Session | +| 关闭弹窗未登录 | AuthModalContext:58 | 跳转 /home | 防止停留受保护页 | + +--- + +## 4. API接口文档 + +### 4.1 Session检查 + +**接口**: `GET /api/auth/session` + +**用途**: 检查当前用户登录状态 + +**请求**: +```http +GET /api/auth/session HTTP/1.1 +Cookie: session=xxx +``` + +**响应**: +```json +{ + "isAuthenticated": true, + "user": { + "id": 123, + "phone": "13800138000", + "nickname": "价小前用户", + "email": "user@example.com", + "avatar_url": "https://...", + "has_wechat": true + } +} +``` + +**前端调用**: +```javascript +// src/contexts/AuthContext.js:85 +const checkSession = async () => { + const response = await fetch('/api/auth/session', { + credentials: 'include' + }); + const data = await response.json(); + if (data.isAuthenticated) { + setUser(data.user); + setIsAuthenticated(true); + } +}; +``` + +--- + +### 4.2 发送验证码 + +**接口**: `POST /api/auth/send-verification-code` + +**用途**: 发送手机验证码 + +**请求**: +```http +POST /api/auth/send-verification-code HTTP/1.1 +Content-Type: application/json + +{ + "credential": "13800138000", + "type": "phone", + "purpose": "login" +} +``` + +**响应**: +```json +{ + "success": true, + "message": "验证码已发送" +} +``` + +**错误响应**: +```json +{ + "success": false, + "error": "手机号格式不正确" +} +``` + +**前端调用**: +```javascript +// src/components/Auth/AuthFormContent.js:153 +const response = await fetch('/api/auth/send-verification-code', { + method: 'POST', + credentials: 'include', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + credential: phone, + type: 'phone', + purpose: 'login' + }) +}); +``` + +**限流规则**: +- 同一手机号: 60秒内只能发送1次 +- 验证码有效期: 5分钟 + +--- + +### 4.3 验证码登录 + +**接口**: `POST /api/auth/login-with-code` + +**用途**: 使用验证码登录/注册 + +**请求**: +```http +POST /api/auth/login-with-code HTTP/1.1 +Content-Type: application/json + +{ + "credential": "13800138000", + "verification_code": "123456", + "login_type": "phone" +} +``` + +**响应 (登录成功)**: +```json +{ + "success": true, + "isNewUser": false, + "user": { + "id": 123, + "phone": "13800138000", + "nickname": "价小前用户" + } +} +``` + +**响应 (注册成功)**: +```json +{ + "success": true, + "isNewUser": true, + "user": { + "id": 124, + "phone": "13900139000", + "nickname": "用户13900139000" + } +} +``` + +**错误响应**: +```json +{ + "success": false, + "error": "验证码已过期或不存在" +} +``` + +**自动注册逻辑**: +- 如果手机号不存在 → 自动创建新用户 +- 默认昵称: `用户{手机号}` +- isNewUser 标记用于前端引导设置昵称 + +--- + +### 4.4 获取微信二维码 + +**接口**: `GET /api/auth/wechat/qrcode` + +**用途**: 获取微信PC扫码登录二维码 + +**请求**: +```http +GET /api/auth/wechat/qrcode HTTP/1.1 +``` + +**响应**: +```json +{ + "code": 0, + "data": { + "auth_url": "https://open.weixin.qq.com/connect/qrconnect?appid=xxx&redirect_uri=xxx&response_type=code&scope=snsapi_login&state=yyy#wechat_redirect", + "session_id": "550e8400-e29b-41d4-a716-446655440000" + }, + "message": "success" +} +``` + +**前端调用**: +```javascript +// src/services/authService.js:69 +getWechatQRCode: async () => { + return await apiRequest('/api/auth/wechat/qrcode'); +} +``` + +**二维码有效期**: 5分钟 + +--- + +### 4.5 检查微信扫码状态 + +**接口**: `POST /api/auth/wechat/check` + +**用途**: 轮询检查微信扫码状态 + +**请求**: +```http +POST /api/auth/wechat/check HTTP/1.1 +Content-Type: application/json + +{ + "session_id": "550e8400-e29b-41d4-a716-446655440000" +} +``` + +**响应状态**: + +| status | 说明 | 前端行为 | +|--------|------|---------| +| waiting | 等待扫码 | 继续轮询 | +| scanned | 已扫码,等待确认 | 显示提示 + 继续轮询 | +| authorized | 已授权 | 继续轮询 | +| login_success | 登录成功 (老用户) | 停止轮询 + 调用登录接口 | +| register_success | 注册成功 (新用户) | 停止轮询 + 调用登录接口 | +| expired | 二维码过期 | 停止轮询 + 显示刷新按钮 | + +**响应示例**: +```json +{ + "status": "login_success", + "user_info": { + "openid": "xxx", + "nickname": "微信用户", + "avatar_url": "https://..." + } +} +``` + +**前端轮询**: +```javascript +// src/components/Auth/WechatRegister.js:180 +pollIntervalRef.current = setInterval(() => { + checkWechatStatus(); +}, 2000); // 每2秒检查一次 +``` + +--- + +### 4.6 微信登录 + +**接口**: `POST /api/auth/login/wechat` + +**用途**: 使用微信 session_id 完成登录 + +**请求**: +```http +POST /api/auth/login/wechat HTTP/1.1 +Content-Type: application/json + +{ + "session_id": "550e8400-e29b-41d4-a716-446655440000" +} +``` + +**响应**: +```json +{ + "success": true, + "user": { + "id": 123, + "nickname": "微信用户", + "avatar_url": "https://...", + "has_wechat": true + }, + "token": "optional-token" +} +``` + +**前端调用**: +```javascript +// src/components/Auth/WechatRegister.js:87 +const response = await authService.loginWithWechat(sessionId); +if (response?.success) { + // 存储用户信息 + if (response.user) { + localStorage.setItem('user', JSON.stringify(response.user)); + } + // 跳转首页 + navigate('/home'); +} +``` + +--- + +### 4.7 微信H5授权 + +**接口**: `POST /api/auth/wechat/h5-auth` + +**用途**: 获取微信H5网页授权链接 + +**请求**: +```http +POST /api/auth/wechat/h5-auth HTTP/1.1 +Content-Type: application/json + +{ + "redirect_url": "https://yourdomain.com/home/wechat-callback" +} +``` + +**响应**: +```json +{ + "auth_url": "https://open.weixin.qq.com/connect/oauth2/authorize?appid=xxx&redirect_uri=xxx&response_type=code&scope=snsapi_userinfo&state=yyy#wechat_redirect" +} +``` + +**前端调用**: +```javascript +// src/components/Auth/AuthFormContent.js:323 +const response = await authService.getWechatH5AuthUrl(redirectUrl); +if (response?.auth_url) { + window.location.href = response.auth_url; +} +``` + +--- + +### 4.8 微信H5回调 + +**接口**: `POST /api/auth/wechat/h5-callback` + +**用途**: 处理微信H5授权回调 + +**请求**: +```http +POST /api/auth/wechat/h5-callback HTTP/1.1 +Content-Type: application/json + +{ + "code": "wechat-code-xxx", + "state": "csrf-state-yyy" +} +``` + +**响应**: +```json +{ + "success": true, + "user": { + "id": 123, + "nickname": "微信用户", + "avatar_url": "https://..." + }, + "token": "optional-token" +} +``` + +**前端调用**: +```javascript +// src/views/Pages/WechatCallback.js:46 +const response = await authService.handleWechatH5Callback(code, state); +if (response?.success) { + await checkSession(); + navigate('/home'); +} +``` + +--- + +### 4.9 退出登录 + +**接口**: `POST /api/auth/logout` + +**用途**: 清除用户Session + +**请求**: +```http +POST /api/auth/logout HTTP/1.1 +Cookie: session=xxx +``` + +**响应**: +```json +{ + "success": true, + "message": "已退出登录" +} +``` + +**前端调用**: +```javascript +// src/contexts/AuthContext.js:120 +const logout = async () => { + await fetch('/api/auth/logout', { + method: 'POST', + credentials: 'include' + }); + setUser(null); + setIsAuthenticated(false); + navigate('/home'); +}; +``` + +--- + +## 5. 前端组件架构 + +### 5.1 核心组件关系图 + +``` +App.js (根组件) + └── AuthProvider (全局认证状态) + └── AuthModalProvider (弹窗管理) + ├── HomeNavbar (导航栏) + │ └── Button onClick={openAuthModal} (登录按钮) + │ + ├── AuthModalManager (弹窗管理器) + │ └── Modal + │ └── AuthFormContent (认证表单) + │ ├── VerificationCodeInput (验证码输入) + │ └── WechatRegister (微信二维码) + │ + └── ProtectedRoute (路由保护) + └── 受保护的页面组件 +``` + +### 5.2 组件详解 + +#### AuthContext (`src/contexts/AuthContext.js`) + +**职责**: 全局认证状态管理 + +**状态**: +```javascript +{ + user: { + id: 123, + phone: "13800138000", + nickname: "价小前用户", + email: "user@example.com", + avatar_url: "https://...", + has_wechat: true + }, + isAuthenticated: true, + isLoading: false +} +``` + +**方法**: +- `checkSession()` - 检查当前登录状态 +- `logout()` - 退出登录 + +**使用**: +```javascript +import { useAuth } from '../../contexts/AuthContext'; + +const { user, isAuthenticated, isLoading, checkSession, logout } = useAuth(); +``` + +--- + +#### AuthModalContext (`src/contexts/AuthModalContext.js`) + +**职责**: 认证弹窗状态管理 + +**状态**: +```javascript +{ + isAuthModalOpen: false, + redirectUrl: null, // 登录成功后跳转的URL (可选) +} +``` + +**方法**: +- `openAuthModal(url, callback)` - 打开认证弹窗 +- `closeModal()` - 关闭弹窗 (未登录则跳转首页) +- `handleLoginSuccess(user)` - 登录成功处理 + +**使用**: +```javascript +import { useAuthModal } from '../../contexts/AuthModalContext'; + +const { isAuthModalOpen, openAuthModal, handleLoginSuccess } = useAuthModal(); + +// 打开弹窗 +openAuthModal(); + +// 登录成功 +handleLoginSuccess(userData); +``` + +--- + +#### AuthModalManager (`src/components/Auth/AuthModalManager.js`) + +**职责**: 渲染认证弹窗 UI + +**特点**: +- 响应式尺寸 (移动端 md, 桌面端 xl) +- 条件渲染 (仅在打开时渲染,避免不必要的Portal) +- 半透明背景 + 模糊效果 +- 禁止点击背景关闭 (防止误操作) + +**响应式配置**: +```javascript +const modalSize = useBreakpointValue({ + base: "md", // 移动端 + md: "lg", // 中屏 + lg: "xl" // 大屏 +}); + +const modalMaxW = useBreakpointValue({ + base: "90%", // 移动端占90%宽度 + md: "700px" // 桌面端固定700px +}); +``` + +--- + +#### AuthFormContent (`src/components/Auth/AuthFormContent.js`) + +**职责**: 认证表单主体内容 + +**功能**: +1. 手机号输入 +2. 验证码输入 + 发送验证码 +3. 登录/注册按钮 +4. 微信扫码 (桌面端右侧) +5. 微信H5登录 (移动端验证码下方图标) +6. 隐私协议链接 + +**布局**: +``` +桌面端 (Desktop): +┌─────────────────────────────────────┐ +│ 价值前沿 - 开启您的投资之旅 │ +├──────────────────┬──────────────────┤ +│ 登陆/注册 │ 微信扫码 │ +│ │ │ +│ 手机号输入框 │ [二维码区域] │ +│ 验证码输入框 │ │ +│ [获取验证码] │ 请使用微信扫码 │ +│ │ │ +│ [登录/注册按钮] │ │ +│ │ │ +│ 《隐私政策》 │ │ +└──────────────────┴──────────────────┘ + +移动端 (Mobile): +┌─────────────────────┐ +│ 价值前沿 │ +│ 开启您的投资之旅 │ +├─────────────────────┤ +│ 登陆/注册 │ +│ │ +│ 手机号输入框 │ +│ │ +│ 验证码输入框 │ +│ [获取验证码] │ +│ 其他登录方式: [微信] │ +│ │ +│ [登录/注册按钮] │ +│ │ +│ 《隐私政策》 │ +└─────────────────────┘ +``` + +**关键逻辑**: +```javascript +// 发送验证码 (60秒倒计时) +const sendVerificationCode = async () => { + const response = await fetch('/api/auth/send-verification-code', { + method: 'POST', + credentials: 'include', + body: JSON.stringify({ + credential: phone, + type: 'phone', + purpose: 'login' + }) + }); + + if (response.ok) { + setCountdown(60); // 启动60秒倒计时 + } +}; + +// 提交登录 +const handleSubmit = async (e) => { + e.preventDefault(); + + const response = await fetch('/api/auth/login-with-code', { + method: 'POST', + credentials: 'include', + body: JSON.stringify({ + credential: phone, + verification_code: verificationCode, + login_type: 'phone' + }) + }); + + if (response.ok) { + await checkSession(); // 更新全局状态 + + if (data.isNewUser) { + // 新用户引导设置昵称 + setShowNicknamePrompt(true); + } else { + handleLoginSuccess(); // 关闭弹窗 + } + } +}; +``` + +--- + +#### WechatRegister (`src/components/Auth/WechatRegister.js`) + +**职责**: 微信PC扫码登录组件 + +**状态机**: +``` +NONE (初始) → 点击"获取二维码" + ↓ + WAITING (显示二维码 + 轮询) + ↓ + SCANNED (提示"请在手机上确认") + ↓ + LOGIN_SUCCESS / REGISTER_SUCCESS + ↓ + 调用登录接口 → 跳转首页 + + EXPIRED (二维码过期 → 显示"点击刷新") +``` + +**轮询机制**: +```javascript +// 主轮询: 每2秒检查 +pollIntervalRef.current = setInterval(() => { + checkWechatStatus(); +}, 2000); + +// 备用轮询: 每3秒检查 (防止主轮询失败) +backupPollIntervalRef.current = setInterval(() => { + checkWechatStatus().catch(error => { + console.warn('备用轮询检查失败(静默处理):', error); + }); +}, 3000); + +// 超时机制: 5分钟后停止轮询 +timeoutRef.current = setTimeout(() => { + clearTimers(); + setWechatStatus(WECHAT_STATUS.EXPIRED); +}, 300000); +``` + +**二维码缩放**: +```javascript +// 使用 ResizeObserver 监听容器尺寸变化 +const calculateScale = () => { + const { width, height } = containerRef.current.getBoundingClientRect(); + + const scaleX = width / 300; // 二维码原始宽度300px + const scaleY = height / 350; // 二维码原始高度350px + const newScale = Math.min(scaleX, scaleY, 1.0); + + setScale(Math.max(newScale, 0.3)); // 最小0.3,最大1.0 +}; + +// iframe 缩放 +