# 企业微信认证方法文档

## 概述

`WxworkAuth` 是一个灵活的企业微信认证工具类，提供可自由搭配的用户信息验证和登录方法。相比路由守卫 `WxworkAuthGuard` 的强制性限制，`WxworkAuth` 允许在页面中灵活调用，实现页面先加载、再执行用户信息同步和注册的流程。

**文件位置**: `fmode-ng/core.ts`

---

## 主要特性

1. **灵活可组合**: 提供独立的方法，可根据需求自由搭配使用
2. **页面内调用**: 可在组件的 `constructor` 或生命周期钩子中调用
3. **自动登录注册**: 使用 `userid` 作为用户名，后6位作为密码自动注册和登录
4. **支持多种场景**:
   - 只获取用户信息（不登录）
   - 只同步用户信息到数据库（不登录）
   - 自动登录/注册
   - 一站式完整流程

---

## 快速开始

### 1. 基础引入

```typescript
import { WxworkAuth } from 'fmode-ng/core';

// 初始化
const wxAuth = new WxworkAuth({
  cid: 'cDL6R1hgSi',  // 公司帐套ID
  appId: 'crm'        // 应用ID，可选，默认为 'crm'
});
```

### 2. 在组件中使用（推荐场景）

```typescript
import { Component } from '@angular/core';
import { WxworkAuth } from 'fmode-ng/core';

@Component({
  selector: 'app-my-page',
  templateUrl: './my-page.component.html'
})
export class MyPageComponent {
  wxAuth: WxworkAuth;
  userInfo: any;

  constructor() {
    this.wxAuth = new WxworkAuth({ cid: 'cDL6R1hgSi' });

    // 页面加载后执行用户认证
    await this.initAuth();

    // 验证后随时获取当前员工/外部联系人
    console.log("current Profile:FmodeObject",await wwauth.currentProfile())
    console.log("current ContactInfo:FmodeObject",await wwauth.currentContact())
  }

  async initAuth() {
    try {
      // 场景: 一站式认证和登录
      const { userInfo, profile, user } = await this.wxAuth.authenticateAndLogin();
      console.log('用户信息:', userInfo);
      console.log('Profile ID:', profile.id);
      console.log('登录用户:', user?.get('username'));

      this.userInfo = userInfo;
    } catch (error) {
      console.error('认证失败:', error);
    }
  }
}
```

---

## API 文档

### 构造函数

```typescript
constructor(options: {
  cid: string;      // 公司帐套ID，必填
  appId?: string;   // 应用ID，可选，默认为 'crm'
})
```

**示例**:
```typescript
const wxAuth = new WxworkAuth({ cid: 'cDL6R1hgSi' });
```

---

### 方法列表

#### 1. `getUserInfo(code?: string): Promise<any>`

获取企业微信用户信息（不登录）

**参数**:
- `code` (可选): 授权码，如果不提供则尝试从缓存或URL获取

**返回值**: 企业微信用户信息对象
```typescript
{
  userid?: string;           // 企业员工ID
  external_userid?: string;  // 外部联系人ID
  openid?: string;           // 微信OpenID
  name?: string;             // 姓名
  mobile?: string;           // 手机号
  email?: string;            // 邮箱
  avatar?: string;           // 头像URL
  gender?: number;           // 性别 (1:男, 2:女)
  // ... 其他字段
}
```

**使用场景**: 只需要获取用户信息，不需要登录系统

**示例**:
```typescript
const userInfo = await wxAuth.getUserInfo();
console.log('用户ID:', userInfo.userid);
console.log('姓名:', userInfo.name);
console.log('手机:', userInfo.mobile);
```

---

#### 2. `syncUserInfo(userInfo?: any): Promise<any>`

同步用户信息到 `Profile`/`UserSocial` 表（不登录）

**参数**:
- `userInfo` (可选): 用户信息对象，如果不提供则自动调用 `getUserInfo()` 获取

**返回值**: `Profile` 或 `UserSocial` Parse Object

**使用场景**: 只需要同步用户信息到数据库，不需要登录系统

**示例**:
```typescript
// 方式1: 自动获取用户信息并同步
const profile = await wxAuth.syncUserInfo();
console.log('同步成功，Profile ID:', profile.id);

// 方式2: 使用已有的用户信息
const userInfo = await wxAuth.getUserInfo();
const profile = await wxAuth.syncUserInfo(userInfo);
```

---

#### 3. `autoLogin(userInfo?: any): Promise<FmodeUser | null>`

自动登录或注册用户（使用 `userid` 和后6位作为密码）

**参数**:
- `userInfo` (可选): 用户信息对象，如果不提供则自动获取

**返回值**: `FmodeUser` 对象或 `null`

**登录逻辑**:
1. 检查是否已登录，如果已登录直接返回当前用户
2. 使用 `userid` 或 `external_userid` 或 `openid` 作为用户名
3. 使用用户名的后6位作为密码
4. 尝试登录，如果用户不存在则自动注册
5. 同步 `user` 指针到 `Profile`/`UserSocial`

**使用场景**: 需要用户登录系统才能访问功能

**示例**:
```typescript
const user = await wxAuth.autoLogin();
if (user) {
  console.log('登录成功，用户名:', user.get('username'));
} else {
  console.log('登录失败');
}
```

---

#### 4. `authenticateAndLogin(code?: string): Promise<{ userInfo, profile, user }>`

一站式方法：获取用户信息、同步、并自动登录/注册

**参数**:
- `code` (可选): 授权码

**返回值**:
```typescript
{
  userInfo: any;           // 企业微信用户信息
  profile: any;            // Profile 或 UserSocial 对象
  user: FmodeUser | null;  // 登录的用户对象
}
```

**执行流程**:
1. 获取企业微信用户信息
2. 同步到 `Profile`/`UserSocial` 表
3. 自动登录/注册用户

**使用场景**: 最常用的场景，一次调用完成所有认证流程

**示例**:
```typescript
const { userInfo, profile, user } = await wxAuth.authenticateAndLogin();
console.log('用户信息:', userInfo);
console.log('Profile ID:', profile.id);
console.log('登录用户:', user?.get('username'));
```

---

#### 5. `oauth(scope?, renew?): Promise<string | null>`

触发 OAuth 授权流程

**参数**:
- `scope` (可选): 授权类型
  - `'snsapi_base'`: 静默授权（默认）
  - `'snsapi_privateinfo'`: 敏感信息授权
- `renew` (可选): 是否强制重新授权（清除缓存），默认 `false`

**返回值**: 授权码或 `null`（如果跳转）

**使用场景**: 需要手动触发授权流程

**示例**:
```typescript
// 静默授权
await wxAuth.oauth('snsapi_base');

// 敏感信息授权
await wxAuth.oauth('snsapi_privateinfo');

// 强制重新授权
await wxAuth.oauth('snsapi_base', true);
```

---

#### 6. `loginPC(): Promise<string>`

PC端登录（弹出扫码面板）

**返回值**: 授权码

**使用场景**: PC端扫码登录

**示例**:
```typescript
const code = await wxAuth.loginPC();
console.log('获得授权码:', code);
```

---

#### 7. `getSDK(): WxworkSDK`

获取 `WxworkSDK` 实例（用于高级操作）

**返回值**: `WxworkSDK` 实例

**使用场景**: 需要使用 SDK 的其他高级功能

**示例**:
```typescript
const sdk = wxAuth.getSDK();

// 获取当前聊天对象
const { GroupChat, Contact } = await sdk.getCurrentChatObject();

// 同步群聊信息
const group = await sdk.syncGroupChat(groupInfo);
```

---

## 使用场景示例

### 场景1: 页面加载后自动登录

```typescript
@Component({
  selector: 'app-dashboard',
  templateUrl: './dashboard.component.html'
})
export class DashboardComponent implements OnInit {
  wxAuth: WxworkAuth;
  currentUser: any;

  constructor() {
    this.wxAuth = new WxworkAuth({ cid: 'cDL6R1hgSi' });
  }

  async ngOnInit() {
    // 页面加载后自动认证和登录
    const { user } = await this.wxAuth.authenticateAndLogin();
    this.currentUser = user;

    if (!user) {
      // 处理登录失败的情况
      console.error('用户登录失败');
    }
  }
}
```

---

### 场景2: 只获取用户信息显示，不登录

```typescript
@Component({
  selector: 'app-profile-preview',
  templateUrl: './profile-preview.component.html'
})
export class ProfilePreviewComponent implements OnInit {
  wxAuth: WxworkAuth;
  userInfo: any;

  constructor() {
    this.wxAuth = new WxworkAuth({ cid: 'cDL6R1hgSi' });
  }

  async ngOnInit() {
    // 只获取用户信息，不登录
    this.userInfo = await this.wxAuth.getUserInfo();
    console.log('用户名:', this.userInfo.name);
  }
}
```

---

### 场景3: 分步骤执行认证流程

```typescript
@Component({
  selector: 'app-onboarding',
  templateUrl: './onboarding.component.html'
})
export class OnboardingComponent {
  wxAuth: WxworkAuth;
  step = 1;
  userInfo: any;
  profile: any;

  constructor() {
    this.wxAuth = new WxworkAuth({ cid: 'cDL6R1hgSi' });
  }

  async step1_getUserInfo() {
    this.userInfo = await this.wxAuth.getUserInfo();
    this.step = 2;
  }

  async step2_syncProfile() {
    this.profile = await this.wxAuth.syncUserInfo(this.userInfo);
    this.step = 3;
  }

  async step3_login() {
    const user = await this.wxAuth.autoLogin(this.userInfo);
    if (user) {
      console.log('登录成功！');
      // 跳转到主页
    }
  }
}
```

---

### 场景4: 检查登录状态，未登录时才执行认证

```typescript
import { FmodeParse } from 'projects/fmode-ng/src/lib/core/parse';

const Parse = FmodeParse.with("nova");

async function ensureAuthenticated() {
  const currentUser = Parse.User.current();

  if (currentUser) {
    console.log('用户已登录:', currentUser.get('username'));
    return currentUser;
  }

  // 未登录，执行认证
  const wxAuth = new WxworkAuth({ cid: 'cDL6R1hgSi' });
  const { user } = await wxAuth.authenticateAndLogin();

  return user;
}
```

---

## 与路由守卫的区别

### `WxworkAuthGuard` (路由守卫)

**特点**:
- 在路由激活前强制执行认证
- 阻塞页面加载，直到认证完成
- 适合需要强制登录的路由

**限制**:
- 页面无法先加载
- 灵活性较低
- 无法根据页面逻辑自定义认证流程

**使用方式**:
```typescript
const routes: Routes = [
  {
    path: 'protected',
    component: ProtectedComponent,
    canActivate: [WxworkAuthGuard]
  }
];
```

---

### `WxworkAuth` (认证工具类)

**特点**:
- 页面可以先加载
- 在 `constructor` 或生命周期钩子中灵活调用
- 可根据页面逻辑自定义认证流程
- 可选择性执行部分认证步骤

**优势**:
- 更好的用户体验（页面先显示）
- 灵活可组合
- 适合复杂的业务逻辑

**使用方式**:
```typescript
// 在组件中直接调用
const wxAuth = new WxworkAuth({ cid: 'cDL6R1hgSi' });
await wxAuth.authenticateAndLogin();
```

---

## 最佳实践

### 1. 统一认证服务

创建一个 Angular Service 来统一管理认证逻辑：

```typescript
import { Injectable } from '@angular/core';
import { WxworkAuth } from 'fmode-ng/core';

@Injectable({
  providedIn: 'root'
})
export class AuthService {
  private wxAuth: WxworkAuth;

  constructor() {
    // 从环境变量或配置中获取 cid
    this.wxAuth = new WxworkAuth({ cid: 'cDL6R1hgSi' });
  }

  async authenticate() {
    return await this.wxAuth.authenticateAndLogin();
  }

  async getUserInfo() {
    return await this.wxAuth.getUserInfo();
  }
}
```

在组件中使用：

```typescript
@Component({
  selector: 'app-my-page',
  templateUrl: './my-page.component.html'
})
export class MyPageComponent implements OnInit {
  constructor(private authService: AuthService) {}

  async ngOnInit() {
    const { user } = await this.authService.authenticate();
    console.log('当前用户:', user?.get('username'));
  }
}
```

---

### 2. 错误处理

```typescript
async initAuth() {
  try {
    const { user } = await this.wxAuth.authenticateAndLogin();

    if (!user) {
      // 认证成功但登录失败
      this.showMessage('登录失败，请稍后再试');
      return;
    }

    // 登录成功
    this.onLoginSuccess(user);
  } catch (error: any) {
    // 认证失败
    console.error('认证错误:', error);

    if (error.message?.includes('无法获取用户信息')) {
      this.showMessage('无法获取用户信息，请检查网络');
    } else {
      this.showMessage('认证失败，请重试');
    }
  }
}
```

---

### 3. 配置管理

使用环境变量管理不同环境的配置：

```typescript
// environment.ts
export const environment = {
  production: false,
  wxwork: {
    cid: 'cDL6R1hgSi',
    appId: 'crm'
  }
};

// 使用
import { environment } from 'src/environments/environment';

const wxAuth = new WxworkAuth({
  cid: environment.wxwork.cid,
  appId: environment.wxwork.appId
});
```

---

## 注意事项

1. **cid 配置**: 确保 `cid` 在 `WxworkSDK.companyMap` 中有对应的配置
2. **错误处理**: 所有方法都会抛出错误，请使用 `try-catch` 捕获
3. **缓存机制**: `getUserInfo()` 会优先使用 localStorage 缓存，传入 `code` 可强制刷新
4. **密码规则**: 自动登录使用 `userid` 后6位作为密码，确保 `userid` 长度 ≥ 6
5. **类型转换**: `Parse.User` 需要转换为 `FmodeUser` 类型：`user as FmodeUser`

---

## 相关文件

- **认证工具类**: `fmode-ng/core.ts`
- **路由守卫**: `projects/fmode-ng/src/lib/social/wxwork/wxwork-auth.guard.ts`
- **SDK**: `projects/fmode-ng/src/lib/core/social/wxwork/wxwork.sdk.ts`
- **企业API**: `projects/fmode-ng/src/lib/core/social/wxwork/wxwork.corp.ts`

---

## 技术支持

如遇问题，请检查：
1. 企业微信配置是否正确（`corpId`, `agentId`, `suiteId`）
2. 回调域名是否已配置
3. `cid` 是否在 `companyMap` 中存在
4. 网络请求是否正常
5. 浏览器控制台是否有错误信息
