# WxworkAuthGuard 企微路由守卫使用指南

## 概述

`WxworkAuthGuard` 是一个用于企业微信页面的路由守卫，提供以下核心功能：

1. **企微授权认证** - 自动处理企业微信 OAuth 授权流程
2. **用户信息同步** - 同步用户信息到 Profile（企业员工）或 UserSocial（外部用户）
3. **自动登录/注册** - 自动创建/登录 Parse `_User` 账号
4. **用户指针同步** - 自动同步 `user` 字段到 Profile/UserSocial

## 核心特性

### ⭐ 自动登录机制

守卫会自动为通过企微认证的用户创建 Parse `_User` 账号：

- **用户名**: `userid`（企业员工）或 `external_userid`（外部用户）
- **密码**: userid/external_userid 的 **后6位**
- **自动注册**: 用户不存在时自动注册
- **自动登录**: 用户存在时自动登录
- **用户指针**: 自动同步 `user` 字段到 Profile/UserSocial

### 📍 标记位置

守卫中的自动登录逻辑位于：
- **文件**: `fmode-ng/src/lib/social/wxwork/wxwork-auth.guard.ts`
- **位置**: 第 71-157 行 `autoLogin()` 函数
- **调用点**: 第 21 行和第 66 行（标记为 ⭐）

## 路由配置

### 基础配置

```typescript
import { WxworkAuthGuard } from 'fmode-ng';

export const routes: Routes = [
  {
    path: ":cid/auth/user-confirm",
    canActivate: [WxworkAuthGuard],
    loadComponent: () => import("./page-user-confirm.component")
      .then((m) => m.PageUserConfirmComponent)
  }
];
```

### 路由参数

守卫会自动从路由中提取以下参数：

- **cid** (必需): 公司帐套ID（Company.objectId）
- **appId** (可选): 应用ID，默认为 "crm"

支持的参数来源：
- `route.paramMap.get("cid")` - 路径参数
- `route.queryParamMap.get("cid")` - 查询参数

### 配置示例

```typescript
// 带 cid 和 appId 的路由
{
  path: ":cid/auth/:appId/user-confirm",
  canActivate: [WxworkAuthGuard],
  loadComponent: () => ...
}

// 仅带 cid 的路由（appId 默认为 "crm"）
{
  path: ":cid/auth/user-confirm",
  canActivate: [WxworkAuthGuard],
  loadComponent: () => ...
}

// 查询参数方式
// 访问: /some-page?cid=E4KpGvTEto
{
  path: "some-page",
  canActivate: [WxworkAuthGuard],
  loadComponent: () => ...
}
```

## WxworkSDK 使用方法

### 初始化 SDK

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

// 在组件中初始化
this.wxwork = new WxworkSDK({
  cid: this.cid,      // 公司帐套ID
  appId: this.appId   // 应用ID，默认 "crm"
});
```

### ⭐ 核心方法：getCurrentUser()

**最简单的方式获取当前用户信息**

```typescript
// 获取当前用户的 Profile 或 UserSocial
const currentUser = await this.wxwork.getCurrentUser();

if (currentUser) {
  console.log("用户类型:", currentUser.className); // "Profile" 或 "UserSocial"
  console.log("用户ID:", currentUser.id);
  console.log("用户名:", currentUser.get("name"));
  console.log("手机号:", currentUser.get("mobile"));

  // Profile 特有字段
  if (currentUser.className === "Profile") {
    console.log("员工ID:", currentUser.get("userid"));
    console.log("部门:", currentUser.get("department"));
    console.log("职位:", currentUser.get("position"));
  }

  // UserSocial 特有字段
  if (currentUser.className === "UserSocial") {
    console.log("外部用户ID:", currentUser.get("data").external_userid);
    console.log("外部用户类型:", currentUser.get("type"));
  }
}
```

### 其他常用方法

#### getUserinfo() - 获取缓存的授权信息

```typescript
// 从 localStorage 获取 USERINFO
const userInfo = await this.wxwork.getUserinfo();

console.log(userInfo.userid);           // 企业员工ID
console.log(userInfo.external_userid);  // 外部用户ID
console.log(userInfo.mobile);           // 手机号
console.log(userInfo.name);             // 姓名
console.log(userInfo.avatar);           // 头像
```

**存储位置**: `localStorage[${cid}/USERINFO]`

#### getContactOrProfile() - 查询或创建用户记录

```typescript
const userInfo = await this.wxwork.getUserinfo();
const profileOrSocial = await this.wxwork.getContactOrProfile(userInfo);

console.log(profileOrSocial.className); // "Profile" 或 "UserSocial"
console.log(profileOrSocial.id);
```

## Parse.User.current() 使用

由于守卫自动登录，任何挂载守卫的页面都可以直接使用：

```typescript
import { FmodeParse } from 'fmode-ng/parse';
const Parse = FmodeParse.with("nova");

// 获取当前登录的 _User
const currentUser = Parse.User.current();

if (currentUser) {
  console.log("用户名:", currentUser.get("username")); // userid 或 external_userid
  console.log("手机号:", currentUser.get("mobilePhoneNumber"));
  console.log("邮箱:", currentUser.get("email"));

  // 获取关联的 Profile/UserSocial
  const wxwork = new WxworkSDK({ cid: this.cid, appId: "crm" });
  const profileOrSocial = await wxwork.getCurrentUser();
  console.log("关联用户:", profileOrSocial.get("name"));
}
```

## 页面开发最佳实践

### 推荐方式：使用 getCurrentUser()

```typescript
export class MyPageComponent implements OnInit {
  cid: string = '';
  appId: string = '';
  wxwork: WxworkSDK;
  currentUser: FmodeObject | null = null;

  constructor(private route: ActivatedRoute) {}

  async ngOnInit() {
    this.route.paramMap.subscribe(async params => {
      this.cid = params.get('cid') || '';
      this.appId = params.get('appId') || 'crm';

      // ⭐ 最简单的方式：直接获取当前用户
      this.wxwork = new WxworkSDK({ cid: this.cid, appId: this.appId });
      this.currentUser = await this.wxwork.getCurrentUser();

      if (this.currentUser) {
        console.log("用户类型:", this.currentUser.className);
        console.log("用户姓名:", this.currentUser.get("name"));

        // 直接使用用户信息
        this.loadUserData();
      }
    });
  }

  async loadUserData() {
    // 使用 this.currentUser 进行业务逻辑
    if (this.currentUser.className === "Profile") {
      // 企业员工逻辑
    } else {
      // 外部用户逻辑
    }
  }
}
```

### 传统方式对比

```typescript
// ❌ 不推荐：手动查询（代码冗长）
async loadUserInfo() {
  const userInfo = await this.wxwork.getUserinfo();

  if (userInfo.userid) {
    const query = new Parse.Query('Profile');
    query.equalTo('userid', userInfo.userid);
    const profile = await query.first();
    // ... 处理 Profile
  } else if (userInfo.external_userid) {
    const query = new Parse.Query('UserSocial');
    query.equalTo('data.external_userid', userInfo.external_userid);
    const userSocial = await query.first();
    // ... 处理 UserSocial
  }
}

// ✅ 推荐：使用 getCurrentUser()（简洁高效）
async loadUserInfo() {
  const currentUser = await this.wxwork.getCurrentUser();
  // 直接使用 currentUser，无需判断类型
}
```

## 数据结构

### Profile 表（企业员工）

| 字段 | 类型 | 说明 |
|------|------|------|
| userid | String | 企业员工微信ID |
| company | Pointer<Company> | 所属企业 |
| user | Pointer<_User> | 关联的 Parse 用户 |
| name | String | 姓名 |
| avatar | String | 头像URL |
| mobile | String | 手机号 |
| email | String | 邮箱 |
| department | String | 部门 |
| position | String | 职位 |
| corp_name | String | 企业名称 |
| isVerified | Boolean | 身份确认状态 |

### UserSocial 表（外部用户）

| 字段 | 类型 | 说明 |
|------|------|------|
| data.external_userid | String | 外部用户微信ID |
| company | Pointer<Company> | 所属企业 |
| user | Pointer<_User> | 关联的 Parse 用户 |
| name | String | 姓名 |
| avatar | String | 头像URL |
| mobile | String | 手机号 |
| type | String | 外部用户类型 |
| corp_name | String | 企业名称 |
| isVerified | Boolean | 身份确认状态 |

### _User 表（Parse 用户）

| 字段 | 类型 | 说明 |
|------|------|------|
| username | String | userid 或 external_userid |
| password | String | userid/external_userid 后6位 |
| mobilePhoneNumber | String | 手机号（同步自企微） |
| email | String | 邮箱（同步自企微） |

## 授权流程

### 1. 移动端（微信环境）

```
用户访问页面
    ↓
检测到微信环境
    ↓
OAuth 跳转授权
    ↓
回调获取 code
    ↓
调用 getUserinfo(code)
    ↓
同步用户信息到 Profile/UserSocial
    ↓
自动登录/注册 _User
    ↓
同步 user 指针
    ↓
进入页面
```

### 2. PC 端（非微信环境）

```
用户访问页面
    ↓
弹出企微扫码登录
    ↓
扫码成功获取 code
    ↓
调用 getUserinfo(code)
    ↓
同步用户信息到 Profile/UserSocial
    ↓
自动登录/注册 _User
    ↓
同步 user 指针
    ↓
进入页面
```

### 3. 已授权用户（有缓存）

```
用户访问页面
    ↓
从 localStorage 读取 USERINFO
    ↓
验证用户信息有效
    ↓
同步用户信息到 Profile/UserSocial
    ↓
自动登录（如未登录）
    ↓
同步 user 指针
    ↓
直接进入页面（无需重新授权）
```

## 常见场景示例

### 场景1：展示当前用户信息

```typescript
async showUserInfo() {
  const currentUser = await this.wxwork.getCurrentUser();

  if (currentUser) {
    const name = currentUser.get("name");
    const avatar = currentUser.get("avatar");
    const mobile = currentUser.get("mobile");

    console.log(`用户: ${name}, 手机: ${mobile}`);
  }
}
```

### 场景2：判断用户类型

```typescript
async checkUserType() {
  const currentUser = await this.wxwork.getCurrentUser();

  if (currentUser.className === "Profile") {
    console.log("这是企业员工");
    const userid = currentUser.get("userid");
    // 执行员工相关逻辑
  } else if (currentUser.className === "UserSocial") {
    console.log("这是外部用户");
    const externalUserId = currentUser.get("data").external_userid;
    // 执行外部用户相关逻辑
  }
}
```

### 场景3：访问 Parse User

```typescript
async accessParseUser() {
  // 方式1：通过 Parse.User.current()
  const parseUser = Parse.User.current();
  console.log("Parse 用户名:", parseUser.get("username"));

  // 方式2：通过 Profile/UserSocial 的 user 指针
  const currentUser = await this.wxwork.getCurrentUser();
  const userPointer = currentUser.get("user");

  if (userPointer) {
    const user = await new Parse.Query("_User").get(userPointer.id);
    console.log("关联的 Parse 用户:", user.get("username"));
  }
}
```

### 场景4：更新用户信息

```typescript
async updateUserInfo() {
  const currentUser = await this.wxwork.getCurrentUser();

  if (currentUser) {
    currentUser.set("name", "新名字");
    currentUser.set("mobile", "13800138000");
    await currentUser.save();

    console.log("用户信息更新成功");
  }
}
```

### 场景5：判断用户是否已确认身份

```typescript
async checkVerified() {
  const currentUser = await this.wxwork.getCurrentUser();

  if (currentUser.get("isVerified")) {
    console.log("用户已确认身份");
    // 允许访问完整功能
  } else {
    console.log("用户未确认身份");
    // 跳转到身份确认页面
    this.router.navigate([`/auth/${this.cid}/user-confirm`]);
  }
}
```

## 开发注意事项

### 1. 守卫必须配置 cid 参数

```typescript
// ✅ 正确：路径中包含 cid
{ path: ":cid/my-page", canActivate: [WxworkAuthGuard], ... }

// ✅ 正确：查询参数中包含 cid
// 访问: /my-page?cid=E4KpGvTEto
{ path: "my-page", canActivate: [WxworkAuthGuard], ... }

// ❌ 错误：缺少 cid 参数
{ path: "my-page", canActivate: [WxworkAuthGuard], ... }
```

### 2. 初始化 SDK 时必须提供 cid

```typescript
// ✅ 正确
this.wxwork = new WxworkSDK({ cid: this.cid, appId: this.appId });

// ❌ 错误：缺少 cid
this.wxwork = new WxworkSDK({ appId: this.appId });
```

### 3. getCurrentUser() 依赖守卫先执行

```typescript
// getCurrentUser() 会读取守卫写入的 localStorage
// 必须在守卫执行后调用
async ngOnInit() {
  // 守卫已执行，localStorage 中有 USERINFO
  const currentUser = await this.wxwork.getCurrentUser();
}
```

### 4. Parse.User.current() 可用性

```typescript
// 只有在守卫执行后，Parse.User.current() 才会有值
const currentUser = Parse.User.current();

if (currentUser) {
  // 用户已自动登录
} else {
  // 用户未登录（不应该出现在守卫保护的页面）
}
```

### 5. user 指针同步

守卫会自动同步 `user` 字段，但如果需要手动同步：

```typescript
async syncUserPointer() {
  const parseUser = Parse.User.current();
  const currentUser = await this.wxwork.getCurrentUser();

  if (parseUser && currentUser && !currentUser.get("user")) {
    currentUser.set("user", parseUser.toPointer());
    await currentUser.save();
  }
}
```

## 调试技巧

### 1. 查看 localStorage 中的 USERINFO

```typescript
const userInfoStr = localStorage.getItem(`${this.cid}/USERINFO`);
const userInfo = JSON.parse(userInfoStr);
console.log("缓存的用户信息:", userInfo);
```

### 2. 查看当前登录用户

```typescript
const currentUser = Parse.User.current();
console.log("Parse User:", currentUser?.toJSON());
```

### 3. 查看 Profile/UserSocial

```typescript
const currentUser = await this.wxwork.getCurrentUser();
console.log("Profile/UserSocial:", currentUser?.toJSON());
```

### 4. 清除缓存重新授权

```typescript
// 清除用户信息缓存
localStorage.removeItem(`${this.cid}/USERINFO`);

// 退出登录
await Parse.User.logOut();

// 刷新页面重新授权
location.reload();
```

## 安全性说明

### 密码生成规则

- **规则**: 使用 userid/external_userid 的后6位作为密码
- **示例**:
  - userid: `ZhangSan123456` → 密码: `123456`
  - external_userid: `wmABCDEF123456` → 密码: `123456`

### 安全建议

1. **企业内部使用**: 该机制适用于企业内部系统，通过企微授权保证安全性
2. **不建议外部暴露**: 不要将密码规则暴露给外部用户
3. **升级密码策略**: 如需更高安全性，可修改 `autoLogin()` 函数的密码生成逻辑
4. **ACL 控制**: 为 Profile/UserSocial 配置合适的 ACL 权限

## 总结

使用 `WxworkAuthGuard` 和 `WxworkSDK.getCurrentUser()` 可以极大简化企微页面开发：

✅ **一行代码获取用户** - `await this.wxwork.getCurrentUser()`
✅ **自动登录** - 无需手动处理 Parse 用户登录
✅ **统一接口** - Profile 和 UserSocial 统一处理
✅ **用户指针同步** - 自动关联 Parse _User
✅ **Parse.User.current() 可用** - 方便其他模块使用

开发企微功能页面时，只需配置守卫，然后使用 `getCurrentUser()` 即可快速开始业务逻辑开发！
