guard-wxwork.md 14 KB

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 行(标记为 ⭐)

路由配置

基础配置

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") - 查询参数

配置示例

// 带 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

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

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

⭐ 核心方法:getCurrentUser()

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

// 获取当前用户的 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() - 获取缓存的授权信息

// 从 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() - 查询或创建用户记录

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() 使用

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

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()

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 {
      // 外部用户逻辑
    }
  }
}

传统方式对比

// ❌ 不推荐:手动查询(代码冗长)
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 表(企业员工)

UserSocial 表(外部用户)

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

_User 表(Parse 用户)

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

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:判断用户类型

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

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:更新用户信息

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

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

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

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

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 参数

// ✅ 正确:路径中包含 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

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

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

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

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

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

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

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

5. user 指针同步

守卫会自动同步 user 字段,但如果需要手动同步:

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

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

2. 查看当前登录用户

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

3. 查看 Profile/UserSocial

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

4. 清除缓存重新授权

// 清除用户信息缓存
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 权限

总结

使用 WxworkAuthGuardWxworkSDK.getCurrentUser() 可以极大简化企微页面开发:

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

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