This commit is contained in:
2026-01-12 18:01:04 +08:00
commit 94784ee1ec
39 changed files with 2847 additions and 0 deletions
+287
View File
@@ -0,0 +1,287 @@
/**
* 企业微信 API 客户端
*/
import { WeChatError } from './common/wechat-error.js';
/**
* 企业微信API客户端
*/
export class WeChatClient {
config;
accessToken;
tokenExpireTime;
constructor(config) {
this.config = {
baseUrl: process.env.WEIXIN_CORP_URL ?? 'https://qyapi.weixin.qq.com',
...config,
};
}
/**
* 获取access_token
* @returns access_token
*/
async getAccessToken() {
// 检查token是否有效
if (this.accessToken && this.tokenExpireTime && Date.now() < this.tokenExpireTime) {
return this.accessToken;
}
const url = new URL('/cgi-bin/gettoken', this.config.baseUrl);
url.searchParams.set('corpid', this.config.corpId);
url.searchParams.set('corpsecret', this.config.corpSecret);
const response = await fetch(url);
const data = await response.json();
if (data.errcode !== 0 || !data.access_token) {
throw new Error(`获取access_token失败: ${data.errmsg}`);
}
this.accessToken = data.access_token;
// 提前5分钟过期,避免临界点问题
this.tokenExpireTime = Date.now() + (data.expires_in || 7200) * 1000 - 5 * 60 * 1000;
return this.accessToken;
}
/**
* 获取客户详情
* @param externalUserId 外部联系人的userid
* @param cursor 上次请求返回的next_cursor,用于分页
* @returns 客户详情
*/
async getExternalContact(externalUserId, cursor) {
const token = await this.getAccessToken();
const url = new URL('/cgi-bin/externalcontact/get', this.config.baseUrl);
url.searchParams.set('access_token', token);
url.searchParams.set('external_userid', externalUserId);
if (cursor) {
url.searchParams.set('cursor', cursor);
}
const response = await fetch(url);
const data = await response.json();
// 检查API错误
if (data.errcode !== 0) {
const error = new WeChatError(data.errcode, data.errmsg);
error.errcode = data.errcode;
error.errmsg = data.errmsg;
throw error;
}
return data;
}
/**
* 获取客户详情(支持分页,自动获取所有跟进人)
* @param externalUserId 外部联系人的userid
* @returns 客户详情(包含所有跟进人)
*/
async getExternalContactFull(externalUserId) {
let result = null;
let cursor;
do {
const data = await this.getExternalContact(externalUserId, cursor);
if (!result) {
// 第一次请求,直接使用返回的数据
result = data;
}
else {
// 后续请求,合并follow_user列表
result.follow_user.push(...data.follow_user);
// 更新next_cursor
if (data.next_cursor !== undefined) {
result.next_cursor = data.next_cursor;
}
}
cursor = data.next_cursor;
} while (cursor);
return result;
}
/**
* 获取配置了客户联系功能的成员列表
* @returns 配置了客户联系功能的成员userid列表
*/
async getFollowUserList() {
const token = await this.getAccessToken();
const url = new URL('/cgi-bin/externalcontact/get_follow_user_list', this.config.baseUrl);
url.searchParams.set('access_token', token);
const response = await fetch(url);
const data = await response.json();
// 检查API错误
if (data.errcode !== 0) {
const error = new WeChatError(data.errcode, data.errmsg);
error.errcode = data.errcode;
error.errmsg = data.errmsg;
throw error;
}
return data;
}
/**
* 获取客户列表
* @param userId 企业成员的userid
* @returns 外部联系人的userid列表
*/
async getExternalContactList(userId) {
const token = await this.getAccessToken();
const url = new URL('/cgi-bin/externalcontact/list', this.config.baseUrl);
url.searchParams.set('access_token', token);
url.searchParams.set('userid', userId);
const response = await fetch(url);
const data = await response.json();
// 检查API错误
if (data.errcode !== 0) {
const error = new WeChatError(data.errcode, data.errmsg);
error.errcode = data.errcode;
error.errmsg = data.errmsg;
throw error;
}
return data;
}
/**
* 批量获取客户详情
* @param userId 企业成员的userid
* @param cursor 上次请求返回的next_cursor,用于分页
* @param limit 返回的最大记录数,整型,最大值1000,默认值为100
* @returns 客户详情列表
*/
async batchGetExternalContact(userId, cursor, limit = 100) {
const token = await this.getAccessToken();
const url = new URL('/cgi-bin/externalcontact/batch/get_by_user', this.config.baseUrl);
url.searchParams.set('access_token', token);
url.searchParams.set('userid', userId);
if (cursor) {
url.searchParams.set('cursor', cursor);
}
url.searchParams.set('limit', limit.toString());
const response = await fetch(url);
const data = await response.json();
// 检查API错误
if (data.errcode !== 0) {
const error = new WeChatError(data.errcode, data.errmsg);
error.errcode = data.errcode;
error.errmsg = data.errmsg;
throw error;
}
return data;
}
/**
* 批量获取客户详情(自动分页,获取所有客户)
* @param userId 企业成员的userid
* @returns 所有客户详情列表
*/
async batchGetExternalContactFull(userId) {
const allContacts = [];
let cursor;
do {
const data = await this.batchGetExternalContact(userId, cursor);
if (data.external_contact_list) {
allContacts.push(...data.external_contact_list);
}
cursor = data.next_cursor;
} while (cursor);
return allContacts;
}
/**
* 添加「联系我」方式
* @param params 联系方式配置参数
* @returns 联系方式配置信息(包含config_id和qr_code)
*/
async addContactWay(params) {
const token = await this.getAccessToken();
const url = new URL('/cgi-bin/externalcontact/add_contact_way', this.config.baseUrl);
url.searchParams.set('access_token', token);
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(params),
});
const data = await response.json();
// 检查API错误
if (data.errcode !== 0) {
const error = new WeChatError(data.errcode, data.errmsg);
error.errcode = data.errcode;
error.errmsg = data.errmsg;
throw error;
}
return data;
}
/**
* 获取「联系我」方式
* @param params 查询参数(包含config_id)
* @returns 联系方式配置信息
*/
async getContactWay(params) {
const token = await this.getAccessToken();
const url = new URL('/cgi-bin/externalcontact/get_contact_way', this.config.baseUrl);
url.searchParams.set('access_token', token);
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(params),
});
const data = await response.json();
// 检查API错误
if (data.errcode !== 0) {
const error = new WeChatError(data.errcode, data.errmsg);
error.errcode = data.errcode;
error.errmsg = data.errmsg;
throw error;
}
return data;
}
/**
* 更新「联系我」方式
* @param params 更新参数(包含config_id和要更新的字段)
* @returns 更新结果
*/
async updateContactWay(params) {
const token = await this.getAccessToken();
const url = new URL('/cgi-bin/externalcontact/update_contact_way', this.config.baseUrl);
url.searchParams.set('access_token', token);
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(params),
});
const data = await response.json();
// 检查API错误
if (data.errcode !== 0) {
const error = new WeChatError(data.errcode, data.errmsg);
error.errcode = data.errcode;
error.errmsg = data.errmsg;
throw error;
}
return data;
}
/**
* 删除「联系我」方式
* @param params 删除参数(包含config_id)
* @returns 删除结果
*/
async deleteContactWay(params) {
const token = await this.getAccessToken();
const url = new URL('/cgi-bin/externalcontact/del_contact_way', this.config.baseUrl);
url.searchParams.set('access_token', token);
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(params),
});
const data = await response.json();
// 检查API错误
if (data.errcode !== 0) {
const error = new WeChatError(data.errcode, data.errmsg);
error.errcode = data.errcode;
error.errmsg = data.errmsg;
throw error;
}
return data;
}
/**
* 重置access_token(强制重新获取)
*/
resetAccessToken() {
this.accessToken = undefined;
this.tokenExpireTime = undefined;
}
}
//# sourceMappingURL=wechat-client.js.map