/** * 企业微信 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