Browse Source

created feishu客户端文档

warrior 2 days ago
parent
commit
216beb6529
1 changed files with 1132 additions and 0 deletions
  1. 1132 0
      modules/fmode-feishu-api/docs/feishu-login-integration.md

+ 1132 - 0
modules/fmode-feishu-api/docs/feishu-login-integration.md

@@ -0,0 +1,1132 @@
+# 飞书登录集成指南
+
+> 完整的飞书登录解决方案,支持客户端免登录、二维码扫码登录和账号密码登录
+
+## 目录
+
+- [概述](#概述)
+- [功能特性](#功能特性)
+- [架构设计](#架构设计)
+- [快速开始](#快速开始)
+- [详细配置](#详细配置)
+- [使用指南](#使用指南)
+- [API 参考](#api-参考)
+- [常见问题](#常见问题)
+- [最佳实践](#最佳实践)
+
+## 概述
+
+本项目实现了完整的飞书登录功能,支持三种登录方式:
+
+1. **飞书客户端内免登录**:在飞书 App 内自动获取用户信息
+2. **飞书二维码扫码登录**:在浏览器中使用飞书扫码登录
+3. **账号密码登录**:传统的用户名密码登录方式
+
+所有登录方式都与 Parse 用户系统无缝集成,提供统一的用户认证体验。
+
+## 功能特性
+
+- ✅ 环境自动检测(飞书客户端 vs 浏览器)
+- ✅ 三种登录方式无缝切换
+- ✅ Parse 用户系统集成
+- ✅ 会话自动保存和恢复
+- ✅ 路由守卫保护
+- ✅ 统一的 OAuth2 接口
+- ✅ 优雅的 UI 和动画效果
+- ✅ 响应式设计
+- ✅ TypeScript 类型支持
+
+## 架构设计
+
+### 核心组件
+
+```
+src/app/
+├── core/
+│   ├── services/
+│   │   ├── feishu-auth.service.ts    # 飞书认证服务
+│   │   └── auth.service.ts           # 应用认证服务
+│   └── guards/
+│       └── auth.guard.ts             # 路由守卫
+└── modules/
+    └── login/
+        ├── login.component.ts        # 登录组件
+        ├── login.component.html      # 登录模板
+        └── login.component.scss      # 登录样式
+```
+
+### 登录流程
+
+#### 1. 飞书客户端内免登录
+
+```
+用户在飞书 App 内打开应用
+    ↓
+检测到飞书环境
+    ↓
+调用飞书 JSSDK 获取授权码
+    ↓
+发送授权码到后端 API
+    ↓
+后端返回用户信息和 sessionToken
+    ↓
+使用 sessionToken 登录 Parse
+    ↓
+更新 AuthService 认证状态
+    ↓
+跳转到首页
+```
+
+#### 2. 飞书二维码扫码登录
+
+```
+用户在浏览器中打开应用
+    ↓
+显示飞书二维码
+    ↓
+用户使用飞书扫码
+    ↓
+二维码 SDK 返回 tmp_code
+    ↓
+重定向到飞书授权页面(携带 tmp_code)
+    ↓
+飞书服务器处理后重定向回应用(携带 code)
+    ↓
+发送 code 到后端 API
+    ↓
+后端返回用户信息和 sessionToken
+    ↓
+使用 sessionToken 登录 Parse
+    ↓
+更新 AuthService 认证状态
+    ↓
+跳转到首页
+```
+
+#### 3. 账号密码登录
+
+```
+用户输入用户名和密码
+    ↓
+调用 Parse.User.logIn()
+    ↓
+登录成功,获取 Parse User
+    ↓
+更新 AuthService 认证状态
+    ↓
+跳转到首页
+```
+
+## SDK 详细说明
+
+### 概述
+
+`feishu-auth.service.ts` 依赖三个核心 SDK 来实现完整的登录功能:
+
+1. **飞书客户端 JSSDK** - 用于飞书 App 内免登录
+2. **飞书二维码登录 SDK** - 用于浏览器扫码登录
+3. **Parse SDK** - 用于用户系统集成
+
+### 1. 飞书客户端 JSSDK
+
+#### SDK 信息
+
+- **CDN 地址**: `https://lf1-cdn-tos.bytegoofy.com/goofy/lark/op/h5-js-sdk-1.5.44.js`
+- **全局对象**: `window.tt`
+- **用途**: 在飞书客户端内获取用户授权码
+- **版本**: 1.5.44
+
+#### 加载方式
+
+```typescript
+private loadFeishuSDK(): Promise<void> {
+  return new Promise((resolve, reject) => {
+    // 检查飞书客户端 JSSDK 是否已加载
+    if (window['tt']) {
+      resolve();
+      return;
+    }
+
+    const script = document.createElement('script');
+    script.src = 'https://lf1-cdn-tos.bytegoofy.com/goofy/lark/op/h5-js-sdk-1.5.44.js';
+    script.onload = () => resolve();
+    script.onerror = () => reject(new Error('Failed to load Feishu SDK'));
+    document.head.appendChild(script);
+  });
+}
+```
+
+#### 核心方法
+
+##### tt.requestAccess()
+
+获取飞书客户端内的用户授权码。
+
+**参数:**
+
+```typescript
+{
+  appID: string;           // 飞书应用 ID
+  scopeList: string[];     // 权限范围列表(可为空)
+  success: (res: any) => void;  // 成功回调
+  fail: (error: any) => void;   // 失败回调
+}
+```
+
+**使用示例:**
+
+```typescript
+window.tt.requestAccess({
+  appID: 'cli_a9253658eef99cd2',
+  scopeList: [],
+  success: (res) => {
+    const code = res.code;  // 获取授权码
+    console.log('授权码:', code);
+  },
+  fail: (error) => {
+    console.error('获取授权码失败:', error);
+  }
+});
+```
+
+**返回值:**
+
+```typescript
+{
+  code: string;  // 授权码,用于后端换取用户信息
+}
+```
+
+#### 环境检测
+
+```typescript
+isInFeishuApp(): boolean {
+  // 检查 window.tt 是否存在
+  if (typeof window['tt'] !== 'undefined' && window['tt'].requestAccess) {
+    return true;
+  }
+
+  // 检查 User Agent
+  const ua = navigator.userAgent.toLowerCase();
+  return ua.includes('lark') || ua.includes('feishu');
+}
+```
+
+### 2. 飞书二维码登录 SDK
+
+#### SDK 信息
+
+- **CDN 地址**: `https://lf-package-cn.feishucdn.com/obj/feishu-static/lark/passport/qrcode/LarkSSOSDKWebQRCode-1.0.3.js`
+- **全局函数**: `window.QRLogin`
+- **用途**: 在浏览器中生成飞书登录二维码
+- **版本**: 1.0.3
+
+#### 加载方式
+
+```typescript
+private loadQRCodeSDK(): Promise<void> {
+  return new Promise((resolve, reject) => {
+    // 检查二维码 SDK 是否已加载
+    if (window['QRLogin']) {
+      resolve();
+      return;
+    }
+
+    const script = document.createElement('script');
+    script.src = 'https://lf-package-cn.feishucdn.com/obj/feishu-static/lark/passport/qrcode/LarkSSOSDKWebQRCode-1.0.3.js';
+    script.onload = () => resolve();
+    script.onerror = () => reject(new Error('Failed to load Feishu QRCode SDK'));
+    document.head.appendChild(script);
+  });
+}
+```
+
+#### 核心方法
+
+##### QRLogin()
+
+初始化并显示飞书登录二维码。
+
+**参数:**
+
+```typescript
+{
+  id: string;      // 二维码容器的 DOM ID
+  goto: string;    // 授权跳转 URL
+  width: string;   // 二维码宽度(如 "300")
+  height: string;  // 二维码高度(如 "300")
+}
+```
+
+**使用示例:**
+
+```typescript
+// 构造授权 URL
+const goto = `https://passport.feishu.cn/suite/passport/oauth/authorize?client_id=${appId}&redirect_uri=${encodeURIComponent(redirectUri)}&response_type=code&state=success_login`;
+
+// 初始化二维码
+const QRLoginObj = window.QRLogin({
+  id: 'qrcode-container',
+  goto: goto,
+  width: "300",
+  height: "300"
+});
+```
+
+**返回值:**
+
+```typescript
+{
+  matchOrigin: (origin: string) => boolean;  // 验证消息来源
+  matchData: (data: any) => boolean;         // 验证消息数据
+}
+```
+
+#### 监听扫码结果
+
+二维码 SDK 通过 `postMessage` 发送扫码结果:
+
+```typescript
+window.addEventListener('message', (event: MessageEvent) => {
+  // 验证消息来源和数据
+  if (QRLoginObj.matchOrigin(event.origin) &&
+      QRLoginObj.matchData(event.data)) {
+
+    const tmp_code = event.data.tmp_code;  // 临时码
+
+    // 使用 tmp_code 构造重定向 URL
+    const redirectUrl = `${goto}&tmp_code=${tmp_code}`;
+    window.location.href = redirectUrl;
+  }
+}, false);
+```
+
+**重要说明:**
+
+- `tmp_code` 不是最终的授权码,只是临时码
+- 需要用 `tmp_code` 重定向到飞书服务器
+- 飞书服务器处理后会重定向回应用,携带真正的授权码 `code`
+
+#### 授权 URL 格式
+
+```
+https://passport.feishu.cn/suite/passport/oauth/authorize
+  ?client_id=<APP_ID>
+  &redirect_uri=<REDIRECT_URI>
+  &response_type=code
+  &state=<STATE>
+```
+
+**参数说明:**
+
+- `client_id`: 飞书应用 ID
+- `redirect_uri`: 授权回调地址(需 URL 编码)
+- `response_type`: 固定为 `code`
+- `state`: 状态参数,用于区分登录类型(如 `success_login` 表示二维码登录)
+
+### 3. Parse SDK
+
+#### SDK 信息
+
+- **导入方式**: `import { FmodeParse as Parse } from 'fmode-ng/core/parse'`
+- **用途**: 用户系统集成和会话管理
+- **版本**: 由 fmode-ng 提供
+
+#### 初始化
+
+```typescript
+import { FmodeParse as Parse } from 'fmode-ng/core/parse';
+
+// 动态获取 Parse 实例
+const Parse = FmodeParse.with(PARSE_APP_ID);
+```
+
+#### 核心方法
+
+##### Parse.User.become()
+
+使用 sessionToken 恢复用户会话。
+
+**参数:**
+
+```typescript
+sessionToken: string  // Parse 会话令牌
+```
+
+**使用示例:**
+
+```typescript
+async loginWithSessionToken(sessionToken: string): Promise<any> {
+  try {
+    // 使用 sessionToken 成为当前用户
+    const user = await Parse.User.become(sessionToken);
+
+    console.log('Parse 用户登录成功:', user.id);
+
+    return {
+      parseUser: user,
+      sessionToken: sessionToken,
+      userId: user.id,
+      username: user.get('username')
+    };
+  } catch (error) {
+    console.error('Parse 用户登录失败:', error);
+    throw error;
+  }
+}
+```
+
+##### Parse.User.logIn()
+
+使用用户名和密码登录。
+
+**参数:**
+
+```typescript
+username: string  // 用户名
+password: string  // 密码
+```
+
+**使用示例:**
+
+```typescript
+async webLogin(username: string, password: string): Promise<AuthResult> {
+  try {
+    // 使用 Parse.User.logIn() 进行登录
+    const user = await Parse.User.logIn(username, password);
+
+    // 获取用户 sessionToken
+    const sessionToken = user.getSessionToken();
+
+    return {
+      success: true,
+      userInfo: {
+        userId: user.id,
+        username: user.get('username'),
+        sessionToken: sessionToken
+      },
+      loginType: 'web'
+    };
+  } catch (error) {
+    return {
+      success: false,
+      error: error.message,
+      loginType: 'web'
+    };
+  }
+}
+```
+
+##### Parse.User.logOut()
+
+退出登录,清除当前用户会话。
+
+**使用示例:**
+
+```typescript
+async logout(): Promise<void> {
+  // 使用 Parse SDK 登出
+  await Parse.User.logOut();
+
+  // 清除本地存储的用户信息
+  localStorage.removeItem('feishu_user_info');
+  localStorage.removeItem('feishu_session_token');
+  localStorage.removeItem('feishu_login_time');
+}
+```
+
+##### Parse.User.current()
+
+获取当前登录的用户。
+
+**使用示例:**
+
+```typescript
+async getCurrentParseUser(): Promise<any> {
+  try {
+    return Parse.User.current();
+  } catch (error) {
+    console.warn('获取当前 Parse 用户失败:', error);
+    return null;
+  }
+}
+```
+
+### SDK 类型声明
+
+为了在 TypeScript 中使用这些 SDK,需要添加全局类型声明:
+
+```typescript
+// 飞书 SDK 全局类型声明
+declare global {
+  interface Window {
+    tt?: any;       // 飞书客户端 JSSDK
+    QRLogin?: any;  // 飞书二维码登录 SDK
+  }
+}
+```
+
+### SDK 加载时机
+
+#### 自动加载
+
+`FeishuAuth` 构造函数会自动加载飞书客户端 JSSDK:
+
+```typescript
+constructor(config: FeishuAuthConfig) {
+  this.config = {
+    appId: config.appId,
+    redirectUri: config.redirectUri,
+    apiOauthWeb: config.apiOauthWeb || 'http://localhost:3000/api/feishu-v2/oauth2/login'
+  };
+
+  // 自动加载飞书 SDK
+  this.loadFeishuSDK();
+}
+```
+
+#### 按需加载
+
+二维码 SDK 只在需要时加载:
+
+```typescript
+initQRCodeLogin(containerId: string, onSuccess, onError): void {
+  // 确保 SDK 已加载
+  this.loadQRCodeSDK().then(() => {
+    // 初始化二维码
+    const QRLoginObj = window.QRLogin({...});
+  });
+}
+```
+
+### SDK 错误处理
+
+#### SDK 加载失败
+
+```typescript
+try {
+  await this.loadFeishuSDK();
+} catch (error) {
+  console.error('加载飞书 SDK 失败:', error);
+  // 降级处理:使用网页登录
+}
+```
+
+#### API 调用失败
+
+```typescript
+try {
+  window.tt.requestAccess({
+    appID: this.config.appId,
+    scopeList: [],
+    success: (res) => {
+      // 处理成功
+    },
+    fail: (error) => {
+      console.error('获取授权码失败:', error);
+      // 错误处理
+    }
+  });
+} catch (error) {
+  console.error('飞书 JSSDK 调用失败:', error);
+  // 降级处理
+}
+```
+
+### SDK 兼容性
+
+#### 浏览器兼容性
+
+- **飞书客户端 JSSDK**: 仅在飞书 App 内可用
+- **飞书二维码登录 SDK**: 支持所有现代浏览器(Chrome, Firefox, Safari, Edge)
+- **Parse SDK**: 支持所有现代浏览器
+
+#### 移动端支持
+
+- ✅ 飞书客户端内免登录:支持 iOS 和 Android
+- ✅ 二维码扫码登录:支持移动浏览器
+- ✅ 账号密码登录:支持所有平台
+
+## 快速开始
+
+### 1. 安装依赖
+
+确保项目已安装以下依赖:
+
+```bash
+npm install fmode-ng
+```
+
+### 2. 配置飞书应用
+
+在飞书开发者后台配置:
+
+1. **应用凭证**:
+   - 获取 App ID
+   - 获取 App Secret
+
+2. **安全设置**:
+   - 配置重定向 URL:`https://your-domain.com/login`
+   - 配置 H5 可信域名:`your-domain.com`
+
+3. **权限配置**:
+   - 开通 `auth:user.id:read`
+   - 开通 `auth:user:email:read`(如需邮箱)
+
+### 3. 复制核心文件
+
+将以下文件复制到你的项目:
+
+```
+src/app/core/services/feishu-auth.service.ts
+src/app/modules/login/login.component.ts
+src/app/modules/login/login.component.html
+src/app/modules/login/login.component.scss
+```
+
+### 4. 配置应用
+
+在 `login.component.ts` 中配置飞书应用信息:
+
+```typescript
+this.feishuAuth = new FeishuAuth({
+  appId: 'YOUR_FEISHU_APP_ID',
+  redirectUri: window.location.origin + '/login',
+  apiOauthWeb: 'https://your-backend.com/api/feishu-v2/oauth2/login'
+});
+```
+
+### 5. 配置路由
+
+在 `app.routes.ts` 中添加登录路由:
+
+```typescript
+export const routes: Routes = [
+  {
+    path: 'login',
+    component: LoginComponent
+  },
+  {
+    path: 'homepage',
+    component: HomepageComponent,
+    canActivate: [authGuard]
+  }
+];
+```
+
+## 详细配置
+
+### FeishuAuthConfig 接口
+
+```typescript
+export interface FeishuAuthConfig {
+  appId: string;           // 飞书应用 App ID(必填)
+  redirectUri: string;     // 回调地址(必填)
+  apiOauthWeb?: string;    // OAuth2 登录接口(可选)
+}
+```
+
+### 默认配置
+
+如果不指定 `apiOauthWeb`,将使用默认值:
+
+```typescript
+apiOauthWeb: 'http://localhost:3000/api/feishu-v2/oauth2/login'
+```
+
+### 后端 API 接口
+
+后端需要提供以下接口:
+
+#### POST /api/feishu-v2/oauth2/login
+
+**请求参数:**
+
+```json
+{
+  "appId": "cli_a9253658eef99cd2",
+  "code": "授权码",
+  "redirect_uri": "https://your-domain.com/login"
+}
+```
+
+**响应格式:**
+
+```json
+{
+  "code": 1,
+  "data": {
+    "userInfo": {
+      "objectId": "用户ID",
+      "username": "用户名",
+      "nickname": "昵称",
+      "avatar": "头像URL",
+      "email": "邮箱",
+      "mobile": "手机号",
+      "openId": "飞书 OpenID",
+      "unionId": "飞书 UnionID"
+    },
+    "sessionToken": "Parse Session Token"
+  },
+  "mess": "登录成功"
+}
+```
+
+## 使用指南
+
+### 在登录组件中使用
+
+```typescript
+import { Component, OnInit } from '@angular/core';
+import { Router, ActivatedRoute } from '@angular/router';
+import { AuthService } from '../../core/services/auth.service';
+import { FeishuAuth, AuthResult } from '../../core/services/feishu-auth.service';
+
+@Component({
+  selector: 'app-login',
+  templateUrl: './login.component.html',
+  styleUrls: ['./login.component.scss']
+})
+export class LoginComponent implements OnInit {
+  private feishuAuth: FeishuAuth;
+
+  constructor(
+    private authService: AuthService,
+    private router: Router,
+    private activatedRoute: ActivatedRoute
+  ) {
+    // 初始化飞书认证服务
+    this.feishuAuth = new FeishuAuth({
+      appId: 'YOUR_FEISHU_APP_ID',
+      redirectUri: window.location.origin + '/login'
+    });
+  }
+
+  async ngOnInit(): Promise<void> {
+    // 检查是否有回调参数
+    const code = this.activatedRoute.snapshot.queryParamMap.get('code');
+    const state = this.activatedRoute.snapshot.queryParamMap.get('state');
+
+    if (code) {
+      await this.handleAuthCallback(code, state || '');
+    }
+  }
+
+  // 处理认证回调
+  private async handleAuthCallback(code: string, state: string): Promise<void> {
+    const result = await this.feishuAuth.handleAuthCallback(code, state);
+
+    if (result.success) {
+      await this.handleLoginSuccess(result);
+    }
+  }
+
+  // 处理登录成功
+  private async handleLoginSuccess(result: AuthResult): Promise<void> {
+    // 保存用户信息
+    if (result.userInfo) {
+      this.feishuAuth.saveUserInfo(result.userInfo);
+    }
+
+    // 刷新认证状态
+    await this.authService.refreshAuthState();
+
+    // 跳转到首页
+    await this.router.navigate(['/homepage']);
+  }
+}
+```
+
+### 环境检测
+
+```typescript
+// 检查是否在飞书客户端内
+if (this.feishuAuth.isInFeishuApp()) {
+  console.log('在飞书客户端内');
+} else {
+  console.log('在浏览器中');
+}
+```
+
+### 飞书客户端内免登录
+
+```typescript
+async handleFeishuLogin(): Promise<void> {
+  const result = await this.feishuAuth.getFeishuAuthCode();
+
+  if (result.success) {
+    await this.handleLoginSuccess(result);
+  } else {
+    console.error('登录失败:', result.error);
+  }
+}
+```
+
+### 二维码登录
+
+```typescript
+initQRCodeLogin(): void {
+  this.feishuAuth.initQRCodeLogin(
+    'qrcode-container',
+    async (result: AuthResult) => {
+      if (result.success) {
+        await this.handleLoginSuccess(result);
+      }
+    },
+    (error: string) => {
+      console.error('二维码登录失败:', error);
+    }
+  );
+}
+```
+
+### 账号密码登录
+
+```typescript
+async handleWebLogin(username: string, password: string): Promise<void> {
+  const result = await this.feishuAuth.webLogin(username, password);
+
+  if (result.success) {
+    await this.handleLoginSuccess(result);
+  } else {
+    console.error('登录失败:', result.error);
+  }
+}
+```
+
+## API 参考
+
+### FeishuAuth 类
+
+#### 构造函数
+
+```typescript
+constructor(config: FeishuAuthConfig)
+```
+
+#### 方法
+
+##### isInFeishuApp(): boolean
+
+检查是否在飞书客户端环境内。
+
+**返回值:**
+- `true`:在飞书客户端内
+- `false`:在浏览器中
+
+##### getFeishuAuthCode(): Promise<AuthResult>
+
+飞书客户端内免登录,获取授权码并完成登录。
+
+**返回值:**
+```typescript
+{
+  success: boolean;
+  userInfo?: UserInfo;
+  error?: string;
+  loginType: 'feishu' | 'web' | 'qrcode';
+}
+```
+
+##### initQRCodeLogin(containerId, onSuccess, onError): void
+
+初始化二维码登录。
+
+**参数:**
+- `containerId: string` - 二维码容器的 DOM ID
+- `onSuccess: (result: AuthResult) => void` - 登录成功回调
+- `onError: (error: string) => void` - 登录失败回调
+
+##### handleAuthCallback(code, state): Promise<AuthResult>
+
+处理飞书授权回调。
+
+**参数:**
+- `code: string` - 授权码
+- `state: string` - 状态参数
+
+**返回值:**
+```typescript
+{
+  success: boolean;
+  userInfo?: UserInfo;
+  error?: string;
+  loginType: 'feishu' | 'web' | 'qrcode';
+}
+```
+
+##### webLogin(username, password): Promise<AuthResult>
+
+账号密码登录。
+
+**参数:**
+- `username: string` - 用户名
+- `password: string` - 密码
+
+**返回值:**
+```typescript
+{
+  success: boolean;
+  userInfo?: UserInfo;
+  error?: string;
+  loginType: 'web';
+}
+```
+
+##### saveUserInfo(userInfo): void
+
+保存用户信息到 localStorage。
+
+**参数:**
+- `userInfo: any` - 用户信息对象
+
+##### getUserInfo(): any
+
+从 localStorage 获取用户信息。
+
+**返回值:**
+- 用户信息对象,如果未登录则返回 `null`
+
+##### isLoggedIn(): boolean
+
+检查用户是否已登录(基于 localStorage)。
+
+**返回值:**
+- `true`:已登录且未过期(24小时内)
+- `false`:未登录或已过期
+
+##### logout(): Promise<void>
+
+退出登录,清除所有本地存储和 Parse 会话。
+
+##### validateConfig(): { isValid: boolean; errors: string[] }
+
+验证飞书应用配置。
+
+**返回值:**
+```typescript
+{
+  isValid: boolean;
+  errors: string[];
+}
+```
+
+### AuthResult 接口
+
+```typescript
+interface AuthResult {
+  success: boolean;
+  code?: string;
+  userInfo?: {
+    userId: string;
+    username: string;
+    avatar: string;
+    nickname?: string;
+    email?: string;
+    mobile?: string;
+    sessionToken?: string;
+    openId?: string;
+    unionId?: string;
+    rawUserInfo?: any;
+    parseUser?: any;
+  };
+  error?: string;
+  loginType: 'feishu' | 'web' | 'qrcode';
+}
+```
+
+## 常见问题
+
+### Q1: 二维码登录后无法跳转到首页?
+
+**原因:** `AuthService` 的认证状态没有更新,路由守卫拒绝跳转。
+
+**解决方案:** 在登录成功后调用 `authService.refreshAuthState()`:
+
+```typescript
+private async handleLoginSuccess(result: AuthResult): Promise<void> {
+  if (result.userInfo) {
+    this.feishuAuth.saveUserInfo(result.userInfo);
+  }
+
+  // 刷新认证状态
+  await this.authService.refreshAuthState();
+
+  await this.router.navigate(['/homepage']);
+}
+```
+
+### Q2: 二维码登录报错 4401?
+
+**原因:** 使用了旧版登录流程的 API 端点。
+
+**解决方案:** 确保使用旧版 goto URL(当前实现已正确):
+
+```typescript
+const goto = `https://passport.feishu.cn/suite/passport/oauth/authorize?client_id=${appId}&redirect_uri=${redirectUri}&response_type=code&state=success_login`;
+```
+
+### Q3: 如何区分二维码登录和客户端登录?
+
+**解决方案:** 通过 `state` 参数判断:
+
+```typescript
+// state 为 'success_login' 表示二维码登录
+const loginType = state === 'success_login' ? 'qrcode' : 'feishu';
+```
+
+### Q4: 如何自定义后端 API 地址?
+
+**解决方案:** 在初始化时指定 `apiOauthWeb`:
+
+```typescript
+this.feishuAuth = new FeishuAuth({
+  appId: 'YOUR_FEISHU_APP_ID',
+  redirectUri: window.location.origin + '/login',
+  apiOauthWeb: 'https://your-backend.com/api/feishu-v2/oauth2/login'
+});
+```
+
+### Q5: 如何处理登录过期?
+
+**解决方案:** `FeishuAuth` 内置了 24 小时过期检查:
+
+```typescript
+if (!this.feishuAuth.isLoggedIn()) {
+  // 登录已过期,跳转到登录页
+  this.router.navigate(['/login']);
+}
+```
+
+## 最佳实践
+
+### 1. 环境配置管理
+
+使用环境变量管理不同环境的配置:
+
+```typescript
+// environment.ts
+export const environment = {
+  production: false,
+  feishu: {
+    appId: 'dev_app_id',
+    apiOauthWeb: 'http://localhost:3000/api/feishu-v2/oauth2/login'
+  }
+};
+
+// environment.prod.ts
+export const environment = {
+  production: true,
+  feishu: {
+    appId: 'prod_app_id',
+    apiOauthWeb: 'https://api.your-domain.com/api/feishu-v2/oauth2/login'
+  }
+};
+
+// 使用
+this.feishuAuth = new FeishuAuth({
+  appId: environment.feishu.appId,
+  redirectUri: window.location.origin + '/login',
+  apiOauthWeb: environment.feishu.apiOauthWeb
+});
+```
+
+### 2. 错误处理
+
+统一处理登录错误:
+
+```typescript
+private async handleLoginError(error: string): Promise<void> {
+  console.error('登录失败:', error);
+
+  // 显示错误提示
+  this.errorMessage.set(error);
+
+  // 记录错误日志
+  this.logError('login_failed', { error });
+}
+```
+
+### 3. 加载状态管理
+
+使用 signal 管理加载状态:
+
+```typescript
+loading = signal(false);
+
+async handleFeishuLogin(): Promise<void> {
+  this.loading.set(true);
+
+  try {
+    const result = await this.feishuAuth.getFeishuAuthCode();
+    // 处理结果
+  } finally {
+    this.loading.set(false);
+  }
+}
+```
+
+### 4. 路由守卫配置
+
+确保所有需要登录的路由都配置了守卫:
+
+```typescript
+export const routes: Routes = [
+  {
+    path: 'login',
+    component: LoginComponent
+  },
+  {
+    path: '',
+    canActivate: [authGuard],
+    children: [
+      { path: 'homepage', component: HomepageComponent },
+      { path: 'profile', component: ProfileComponent }
+    ]
+  }
+];
+```
+
+### 5. 会话恢复
+
+在应用启动时自动恢复会话:
+
+```typescript
+// app.component.ts
+async ngOnInit(): Promise<void> {
+  // AuthService 会自动从 Parse 恢复会话
+  await this.authService.refreshAuthState();
+}
+```
+
+### 6. 安全最佳实践
+
+- 不要在前端代码中硬编码 App Secret
+- 使用 HTTPS 传输敏感信息
+- 定期更新 sessionToken
+- 实现登录失败次数限制
+- 记录登录日志用于安全审计
+
+### 7. 用户体验优化
+
+- 根据环境自动选择登录方式
+- 提供清晰的错误提示
+- 添加加载动画
+- 支持记住登录状态
+- 提供退出登录功能
+
+## 技术栈
+
+- Angular 17+
+- TypeScript 5.0+
+- Parse SDK
+- 飞书 JSSDK
+- 飞书二维码登录 SDK
+
+## 许可证
+
+MIT
+
+## 支持
+
+如有问题或建议,请联系开发团队。