用户故事
其实用 Authing 团队治理 + Wechaty 机器人可能实现很多性能,来进步传统人事管理的效率。甚至还能够做一些数据的剖析、统计,来辅助决策。这里我就列了几个简略的事实场景,心愿能帮忙大家了解。
以 Authing 作为上游数据源
同步数据到其余平台,后期为飞书和微信。创立完用户后,用户能够通过手机号间接登录飞书。集体微信增加 Wechaty Bot,通过音讯发送手机号进行用户关联绑定。在 Authing 中创立组织架构,同步对应创立飞书的部门和微信的群组。同步删除飞书用户、微信群移除群聊。
原有飞书组织接入微信
飞书作为上游数据源。微信增加 Wechat Bot,通过音讯发送手机号进行用户关联绑定。
在飞书中增加、删除用户,(先设置好同步频率,比方每 10 分钟查看一次),对应微信群中进行邀请退出或者移除。在飞书中进行部门(组织)治理,同步在微信中创立对应的群聊(扁平化,无层级),并同步群成员治理(增加 / 移除)。
原有微信群组并接入飞书
抉择一个全员微信群进行接入,首先 Wechaty Bot 会为该群中所有成员创立 Authing 用户。微信增加 Wechaty Bot 或者 @提及的形式发送音讯手机号进行用户关联绑定,绑定手机号的成员能够通过该手机号进行飞书的登录。
在原有微信大群中增加、删除用户,(先设置好同步频率,比方每 10 分钟查看一次),对应飞书用户增加或者移除。
撸一个 Wechaty Authing 的插件
也能够通过插件的介绍页面间接去理解插件的应用阐明。该插件开源代码仓库位于:https://github.com/Authing/we…
设计思路
- 该插件该当封装好了一些罕用的办法,来买通 Wechaty 与 Authing 之间的连贯
- 装备了一些好用的工具类办法,进步开发效率
- 具备肯定的可扩展性,不便有一些非通用需要的实现
实现
在着手开发这个插件之前,我是别离以 Wechaty 作为用户上游和 Authing 用户池作为用户上游做了两个 MVP 我的项目。而后通过我的项目中的一些办法和性能,来实现这个插件的最后版本。
列举一下 Wechaty 作为用户上游时,须要用到的一些办法:
- 查看微信好友或群内的用户是否曾经存在 Authing 用户池
- 查看微信用户是否绑定了 Authing 账号(次要为手机号)
- 当邀请、移除群成员时候,对应创立、删除 Authing 用户
再列举一下将 Authing 作为用户上游时,须要用到的一些办法:
- 查看微信的好友申请、以及音讯,判断是否为 Authing 用户(通过音讯规定手机号校验)
- 将微信号与 Authing 用户进行关联绑定(通过音讯规定手机号校验)
而后这个过程中,常常会有两个用户列表的比拟,一个是 Wechaty Contact[]
列表,一个是 Authing User[]
列表,去判断联系人是不是 Authing 用户、判断联系人有没有被邀请入群、或者判断联系人是否须要被移除群聊。
最初代码大略的框架成了这个样子:
export class WechatyAuthing {constructor(config: WechatyAuthingConfig);
/**
* Get Authing SDK client
* 获取 Authing SDK 实例
*/
protected get client(): _ManagementClient1;
/**
* Get Authing User pool name
* @returns {Promise<string>}
*/
getPoolName(): Promise<string>;
/** ********* AS UPSTREAM ************* */
/**
* Batch check users exists from Authing
* 查看是否注册为 Authing 用户
* @param {Contact[]} contacts Wechaty Contact[]
* @returns {ContactsFilterResult} {registered: Contact[]; unregistered: Contact[]; fail: Contact[] }
*/
filterAuthingUsers<T = Contact>(contacts: T[]
): Promise<ContactsFilterResult<T>>;
/**
* Batch create users to Authing
* 向 Authing 用户池中批量创立用户
* @param {Contact[]} contacts Wechaty Contact[]
* @returns {ContactsOperationResult} {success: Contact[], fail: Contact[]}
*/
createAuthingUsers<T = Contact>(contacts: T[]
): Promise<ContactsOperationResult<T>>;
/**
* Batch delete users from Authing
* 从 Authing 用户池中批量删除 Wechaty 用户
* @param {Contact[]} contacts Wechaty Contact[]
* @returns {ContactsOperationResult} {success: Contact[], fail: Contact[]} */
deleteAuthingUsers<T = Contact>(contacts: T[]
): Promise<ContactsOperationResult<T>>;
/**
* Create or update a authing user with Wechaty contact and phone
* 绑定 Contact 和手机号码到 Authing 用户(或创立一个用户)* @param {Contact} contact
* @param {string} phone
* @returns {Promise<boolean>}
*/
bindAuthingPhone<T = Contact>(contact: T, phone: string): Promise<boolean>;
/** ********* AS DOWNSTREAM ************* */
/**
* Check if user with the phone number exists in Authing
* 查看手机号是否注册为 Authing 用户
* @param {string} phone
* @returns {Promise<boolean>}
*/
checkPhone(phone: string): Promise<boolean>;
/**
* Bind Wechaty contact to a Authing user by phone number
* 将 Wechaty Contact 绑定到 Authing 手机号的用户
* @param {string} phone
* @param {Contact} contact
* @returns {Promise<boolean>}
*/
bindPhoneContact<T = Contact>(phone: string, contact: T): Promise<boolean>;
/** ********* PROTECTED ************* */
/**
* Create Authing user
* 创立 Authing 用户
* @param {Contact} contact
* @returns {User | null}
*/
protected createAuthingUser<T = Contact>(contact: T): Promise<User | null>;
}
另外,在刚开始 POC 的时候,我应用了大量低效的代码去实现该性能,如:
// 筛选出用户中的好友
const friends = allFriends.filter((contact) => ~users.findIndex((user) => user.externalId === contact.id)
);
// 删除成员和揭示不确定状态
const members2Delete = memberList.filter((member) => ~deletedUsers.findIndex((user) => user.externalId === member.id)
);
const members2Warning = memberList.filter((member) =>
!~deletedUsers.findIndex((user) => user.externalId === member.id) &&
!~users.findIndex((user) => user.externalId === member.id)
);
// 查看未入群的好友
const members2Invite = friends.filter((friend) => !~memberList.findIndex((member) => member.id === friend.id)
);
在此基础上,优化了几个工具办法:
- 通过 Set 个性取两个数组的差集
- 获取 Wechaty 用户实在的惟一 ID
其实在集成过程中还有很多细节的点须要留神,我会都在文章最初的章节里进行整顿。
测试
目前的代码品质 A,测试覆盖率为 98%。如果你对于这个插件感兴趣,测试用例不仅仅是你参加奉献的最好开始,其实也是插件应用的绝佳案例。
其中也包含了示例初始化、各个办法调用和返回值期待、工具办法的应用,以及扩大开发的一些细节。
以拓展插件的测试用例为例:
//https://github.com/Authing/wechaty-authing/blob/main/test/extends.spec.ts
import {WechatyAuthing} from '../src';
describe('extension', () => {it('client', async () => {
class ExtendedWechatyAuthing extends WechatyAuthing {async totalUsers(): Promise<number> {const { totalCount} = await this.client.users.list();
return totalCount;
}
}
const client = new ExtendedWechatyAuthing({
userPoolId: process.env.AUTHING_USER_POOL_ID,
secret: process.env.AUTHING_USER_POOL_SECRET
});
const count = await client.totalUsers();
expect(count).toBeGreaterThan(0);
});
});
当然,具体的插件应用细节,能够通过我的项目 README 文件查看,提供了中英文两个版本。
上手做几个机器人吧
示例中(来自 POC 我的项目)应用的 Wechaty 版本为 0.75
和 puppty 为 padlocal
(基于 iPad 微信协定)。能够拜访示例代码仓库下载:https://github.com/Authing/we…
应用微信群作为上游用户数据
管理员邀请用户退出群组(人为操作)
侦听 room-join
事件触发,取得被邀请人员名单(_inviteeList_)。查看 Authing 用户池,筛选出未注册的用户列表,批量注册用户,并发送音讯提醒绑定用户手机号。
管理员踢出群聊用户(人为操作)
侦听 room-leave
事件触发,取得被移除人员名单(_leaverList_)。从 Authing 用户池中批量删除。提醒删除胜利的用户名(列表)。
如果有删除失败的(不确定起因引起),提醒删除失败的用户名(列表),请管理员手动删除。
用户 @Bot 提及音讯
侦听 message
事件触发,如果非提及音讯,或者音讯中不蕴含手机号,则不解决。
检查用户是否存在,如果存在,批改绑定的手机号为用户输出的(可能呈现反复手机号绑定失败);如果用户不存在注册一个新用户。
如果绑定胜利,发送音讯提醒。
实现
代码位于 POC 我的项目 upstream
目录。
首先在 Bot 启动时,进行一遍群成员的查看:
graph LR
Start1(Start)
--Bot 启动 --> check1[查看群内的非 Authing 用户]
--> addUser[增加 Authing 用户并音讯揭示绑定手机号]
--> End1(End)
同时还侦听了两个 Wechaty 事件:
room-join
:管理员邀请用户退出群组(人为操作)room-leave
:管理员踢出群聊用户(人为操作)
graph TD
Start1(Start)
--> add1[管理员人为增加群成员]
-- 治理 Bot 侦听入群事件 --> add2[Bot 向 Authing 注册用户]
-- 用户提及 Bot 发送手机号 --> add3[Bot 向 Authing 更新绑定用户手机号]
--> End1(End)
Start2(Start)
--> del1[管理员人为删除群成员]
-- 治理 Bot 侦听退群事件 --> del2[Bot 向 Authing 删除用户]
--> End2(End)
代码之外
当微信用户绑定到 Authing 用户池之后,还能够做些什么呢?比方同步企业成员信息到飞书,连贯到各种 SSO 单点登录利用等。施展想象力的工夫到了,扭转生产力,或者并不是那么遥不可及的事件。
以 Authing 用户作为上游,Wechaty 用户作为上游
用户数据起源也可能是同步中心里来自飞书或其余身份源的用户数据。 其中有一步,在 Authing 用户池中增加新用户后,要求用户被动增加 Bot 为好友,必须。
在 Bot 启动时,查看微信中是否存在全员群,如果没有,就创立一个(但如果企业成员数量不够 3 个,不能创立群聊的时候,机器人就会等被动增加的好友)。全员群存在之后,就开始定时工作查看是否有到职成员须要踢出群聊,以及新入职的成员邀请退出群聊了。
graph TD
Start1(Start)
--Bot 启动 --> check1{查看全员群是否存在}
-->|T| start2[启动 30s 轮询 CronJob]
--> End1(End)
check1-->|F| check2{用户中好友数是否超过 2 人}
-->|T| step1[创立群组]
--> start2
check2-->|F 距离 30s 复查 | check2
Start2(Start)
--> step21[查问全员群成员]
--> step22[筛选出不是 Authing 用户的成员并踢出]
--> step23[筛选出 Authing 用户中不在群内的好友并邀请]
--> End2(End)
-->|30s 轮询 | Start2
其中,关联 Authing 用户和 Wechaty 联系人的形式也很简略。就是通过好友申请或者音讯,发送手机号码,查看 Authing 用户池中是否存在。
graph TD
Start1(Start)
--Bot 启动 --> step11[侦听好友申请和文本音讯]
-->| 好友申请事件或文本音讯 | step12[依据手机号承受申请并邀请入群]
--> End1(End)
扩大 wechaty-authing 插件去实现更简单的业务
扩大 Authing SDK 的应用
wechaty-authing
提供了 protected 的 client 实例(对应着 authing sdk 里的 ManagementClient
)。
所以能够参考 Authing 官网的文档进行深度开发。示例:
class ExtendedWechatyAuthing extends WechatyAuthing {async totalUsers(): Promise<number> {const { totalCount} = await this.client.users.list();
return totalCount;
}
}
参考文档:https://docs.authing.cn/v2/re…
扩大 Wechaty 插件
同时开能够封装一些 Wechaty 插件性能。示例:
class ExtendedWechatyAuthing extends WechatyAuthing {plugin(): WechatyPlugin {return (bot: Wechaty): void => {bot.on('ready', async () => {const { totalCount} = await this.client.users.list();
log.info('total users', totalCount);
});
};
}
}
const authing = new ExtendedWechatyAuthing({
userPoolId: process.env.AUTHING_USER_POOL_ID,
secret: process.env.AUTHING_USER_POOL_SECRET
});
const bot = createBot(process.env.WECHATY_PADLOCAL_TOKEN);
bot.use(authing.plugin());
更多对于 Wechaty 插件的应用及开发,能够拜访:https://wechaty.js.org/docs/u…
注意事项
微信限度
- 微信好友下限:5000 人 https://kf.qq.com/faq/161223I…
- 被动增加好友:每日 30 次申请下限
- 被动增加好友:每日 180 次申请下限
-
邀请加群:https://kf.qq.com/faq/161223u…
- 超过 40 人群须要对方批准
- 超过 100 人群须要对方开明微信领取(实名、绑卡)
- 一般群人数下限 500
Wechaty Contact 惟一 ID 阐明
目前已知的 id 格局:
-
内部
$search$-weixin
$search$-13212341234
weixin
/wxid_xxxx
-
Payload 外部
$search$-
非好友v3_xxxxx@stranger
weixin
/wxid_xxxx
- 不晓得是啥的。如
qq1111
但 weixin 字段为微信号xxxx
Wechaty 可能会呈现的一些问题
版本
Wechaty 1.x 版本与之前的版本差距,如类型申明、创立实例的形式,须要留神你应用的 puppet
反对什么版本。
被动搜寻
有两种搜寻形式,通过手机号和通过微信号:
const contact = await this.Friendship.search({phone: user.phone!});
const contact = await this.Friendship.search({weixin: user.externalId!});
取得的后果均无奈获取到惟一 id:
昵称:Willin,id:$search$-132XXXXXXXX
昵称:XXXX,id:$search$-186XXXXXXXX
昵称:Willin,id:$search$-weixinXXXX
昵称:XXX,id:$search$-weixinXXXX
解决:如果是好友,则能够从 contact.payload.id 中取得。
异样申请失败
比方查找某个手机号或者微信号,填写的参数正确。Friendship.search
经常出现:
================================================================================
VError: [tid:e05dff73] request has been cancelled for reason: SERVER_ERROR: 2 UNKNOWN: [tid:e05dff73] wechat bad request error
at Request._failAllPendingRequest (/Users/v0/Projects/Authing/wechaty-authing-poc/node_modules/padlocal-client-ts/src/Request.ts:334:15)
at ClientDuplexStreamImpl.<anonymous> (/Users/v0/Projects/Authing/wechaty-authing-poc/node_modules/padlocal-client-ts/src/Request.ts:82:12)
at ClientDuplexStreamImpl.emit (node:events:390:28)
at ClientDuplexStreamImpl.emit (node:domain:475:12)
at Object.onReceiveStatus (/Users/v0/Projects/Authing/wechaty-authing-poc/node_modules/@grpc/grpc-js/src/client.ts:673:18)
at Object.onReceiveStatus (/Users/v0/Projects/Authing/wechaty-authing-poc/node_modules/@grpc/grpc-js/src/client-interceptors.ts:424:48)
at /Users/v0/Projects/Authing/wechaty-authing-poc/node_modules/@grpc/grpc-js/src/call-stream.ts:323:24
at processTicksAndRejections (node:internal/process/task_queues:78:11)
error Command failed with exit code 1.
获取的信息不精确
如 room.memberAll()
,该办法有时可能获取残缺群成员信息。有时只能返回 id 列表。
残缺的数据应该是这样:
[
{_events: {},
_eventsCount: 0,
id: 'xxxx',
payload: {
id: 'xxxx',
gender: 1,
type: 1,
name: 'Willin',
avatar:
'https://wx.qlogo.cn/mmhead/ver_1/lY8XLaibGJAiajvtTPx5kdDs2T3qGToUm0mHFTYGRzcaVydJZwnibQKMNKDzv2WosXJu2aUU8lteT1R6FCKVK3PUg/0',
alias: '',
weixin: '',
city: 'Lianyungang',
friend: true,
province: 'Jiangsu',
signature: '所有的登程 \n 都是为了回家',
phone: []}
}
];
有的时候会变成这样:
[
{_events: {},
_eventsCount: 0,
id: 'xxxx'
}
];
Refs
- Wechaty Authing 插件仓库:https://github.com/Authing/we…
- 基于该插件的 POC 示例代码仓库:https://github.com/Authing/we…