| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439 |
- /**
- * 飞书API客户端模块
- *
- * 功能说明:
- * - 封装飞书开放平台的所有API调用
- * - 支持tenant_access_token和user_access_token
- * - 提供用户信息、OAuth2认证等功能
- * - 统一错误处理和日志记录
- *
- * @module client
- * @author fmode
- * @date 2026
- */
- /**
- * Tenant Access Token 响应接口
- */
- export interface TenantAccessTokenResponse {
- tenantAccessToken: string;
- expire: number;
- }
- /**
- * App Access Token 响应接口
- */
- export interface AppAccessTokenResponse {
- appAccessToken: string;
- expire: number;
- }
- /**
- * User Access Token 响应接口
- */
- export interface UserAccessTokenResponse {
- accessToken: string;
- refreshToken: string;
- expiresIn: number;
- tokenType: string;
- refreshExpiresIn: number;
- }
- /**
- * 用户信息接口
- */
- export interface FeishuUserInfo {
- name: string;
- enName?: string;
- avatarUrl: string;
- avatarThumb?: string;
- avatarMiddle?: string;
- avatarBig?: string;
- openId: string;
- unionId: string;
- email?: string;
- enterpriseEmail?: string;
- userId: string;
- mobile?: string;
- tenantKey?: string;
- }
- /**
- * 登录用户信息响应接口
- */
- export interface LoginUserInfoResponse {
- accessToken: string;
- tokenType: string;
- expiresIn: number;
- refreshToken: string;
- refreshExpiresIn: number;
- scope: string;
- }
- /**
- * API 转发请求选项接口
- */
- export interface ForwardRequestOptions {
- path: string;
- method?: string;
- query?: Record<string, any>;
- body?: Record<string, any>;
- }
- /**
- * 飞书API客户端类
- * 提供所有飞书API的封装方法
- */
- export class FeishuClient {
- private baseUrl: string;
- private tokenCache: Map<string, any>;
- /**
- * 构造函数
- * 初始化飞书API基础URL
- */
- constructor() {
- this.baseUrl = 'https://open.feishu.cn/open-apis';
- this.tokenCache = new Map<string, any>();
- }
- /**
- * 获取tenant_access_token(企业自建应用)
- *
- * @static
- * @async
- * @param appId - 应用ID
- * @param appSecret - 应用密钥
- * @returns Promise<TenantAccessTokenResponse>
- * @throws {Error} 获取失败时抛出错误
- */
- static async getTenantAccessToken(appId: string, appSecret: string): Promise<TenantAccessTokenResponse> {
- try {
- const url = 'https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal';
- const response = await fetch(url, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json; charset=utf-8'
- },
- body: JSON.stringify({
- app_id: appId,
- app_secret: appSecret
- })
- });
- const result = await response.json();
- if (result.code !== 0) {
- throw new Error(`获取tenant_access_token失败: ${result.msg}, code: ${result.code}`);
- }
- return {
- tenantAccessToken: result.tenant_access_token,
- expire: result.expire
- };
- } catch (error) {
- console.error('获取tenant_access_token异常:', error);
- throw error;
- }
- }
- /**
- * 获取app_access_token(应用商店应用)
- *
- * @static
- * @async
- * @param appId - 应用ID
- * @param appSecret - 应用密钥
- * @returns Promise<AppAccessTokenResponse>
- * @throws {Error} 获取失败时抛出错误
- */
- static async getAppAccessToken(appId: string, appSecret: string): Promise<AppAccessTokenResponse> {
- try {
- const url = 'https://open.feishu.cn/open-apis/auth/v3/app_access_token/internal';
- const response = await fetch(url, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json; charset=utf-8'
- },
- body: JSON.stringify({
- app_id: appId,
- app_secret: appSecret
- })
- });
- const result = await response.json();
- if (result.code !== 0) {
- throw new Error(`获取app_access_token失败: ${result.msg}, code: ${result.code}`);
- }
- return {
- appAccessToken: result.app_access_token,
- expire: result.expire
- };
- } catch (error) {
- console.error('获取app_access_token异常:', error);
- throw error;
- }
- }
- /**
- * OAuth2方式获取用户访问令牌
- *
- * @static
- * @async
- * @param appId - 应用ID
- * @param appSecret - 应用密钥
- * @param code - 授权码
- * @param grantType - 授权类型
- * @returns Promise<UserAccessTokenResponse>
- * @throws {Error} 获取失败时抛出错误
- */
- static async getUserAccessToken(
- appId: string,
- appSecret: string,
- code: string,
- grantType: string = 'authorization_code'
- ): Promise<UserAccessTokenResponse> {
- try {
- const url = 'https://open.feishu.cn/open-apis/authen/v2/oauth/token';
- const response = await fetch(url, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json; charset=utf-8'
- },
- body: JSON.stringify({
- grant_type: grantType,
- client_id: appId,
- client_secret: appSecret,
- code: code
- })
- });
- const result = await response.json();
- if (result.code !== 0) {
- throw new Error(`获取user_access_token失败: ${result.error_description || result.msg}, code: ${result.code}`);
- }
- return {
- accessToken: result.access_token,
- refreshToken: result.refresh_token,
- expiresIn: result.expires_in,
- tokenType: result.token_type,
- refreshExpiresIn: result.refresh_token_expires_in
- };
- } catch (error) {
- console.error('获取user_access_token异常:', error);
- throw error;
- }
- }
- /**
- * 刷新用户访问令牌
- *
- * @static
- * @async
- * @param appId - 应用ID
- * @param appSecret - 应用密钥
- * @param refreshToken - 刷新令牌
- * @returns Promise<UserAccessTokenResponse>
- * @throws {Error} 刷新失败时抛出错误
- */
- static async refreshUserAccessToken(
- appId: string,
- appSecret: string,
- refreshToken: string
- ): Promise<UserAccessTokenResponse> {
- try {
- const url = 'https://open.feishu.cn/open-apis/authen/v2/oauth/token';
- const response = await fetch(url, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json; charset=utf-8'
- },
- body: JSON.stringify({
- grant_type: 'refresh_token',
- client_id: appId,
- client_secret: appSecret,
- refresh_token: refreshToken
- })
- });
- const result = await response.json();
- if (result.code !== 0) {
- throw new Error(`刷新user_access_token失败: ${result.error_description || result.msg}, code: ${result.code}`);
- }
- return {
- accessToken: result.access_token,
- refreshToken: result.refresh_token,
- expiresIn: result.expires_in,
- tokenType: result.token_type,
- refreshExpiresIn: result.refresh_token_expires_in
- };
- } catch (error) {
- console.error('刷新user_access_token异常:', error);
- throw error;
- }
- }
- /**
- * 获取用户信息(使用user_access_token)
- *
- * @static
- * @async
- * @param userAccessToken - 用户访问令牌
- * @returns Promise<FeishuUserInfo>
- * @throws {Error} 获取失败时抛出错误
- */
- static async getUserInfo(userAccessToken: string): Promise<FeishuUserInfo> {
- try {
- const url = 'https://open.feishu.cn/open-apis/authen/v1/user_info';
- const response = await fetch(url, {
- method: 'GET',
- headers: {
- 'Authorization': `Bearer ${userAccessToken}`,
- 'Content-Type': 'application/json; charset=utf-8'
- }
- });
- const result = await response.json();
- if (result.code !== 0) {
- throw new Error(`获取用户信息失败: ${result.msg}, code: ${result.code}`);
- }
- return {
- name: result.data.name,
- enName: result.data.en_name,
- avatarUrl: result.data.avatar_url,
- avatarThumb: result.data.avatar_thumb,
- avatarMiddle: result.data.avatar_middle,
- avatarBig: result.data.avatar_big,
- openId: result.data.open_id,
- unionId: result.data.union_id,
- email: result.data.email,
- enterpriseEmail: result.data.enterprise_email,
- userId: result.data.user_id,
- mobile: result.data.mobile,
- tenantKey: result.data.tenant_key
- };
- } catch (error) {
- console.error('获取用户信息异常:', error);
- throw error;
- }
- }
- /**
- * 获取登录用户身份(网页免登)
- *
- * @static
- * @async
- * @param tenantAccessToken - 租户访问令牌
- * @param code - 免登授权码
- * @returns Promise<LoginUserInfoResponse>
- * @throws {Error} 获取失败时抛出错误
- */
- static async getLoginUserInfo(tenantAccessToken: string, code: string): Promise<LoginUserInfoResponse> {
- try {
- const url = 'https://open.feishu.cn/open-apis/authen/v1/access_token';
- const response = await fetch(url, {
- method: 'POST',
- headers: {
- 'Authorization': `Bearer ${tenantAccessToken}`,
- 'Content-Type': 'application/json; charset=utf-8'
- },
- body: JSON.stringify({
- grant_type: 'authorization_code',
- code: code
- })
- });
- const result = await response.json();
- if (result.code !== 0) {
- throw new Error(`获取登录用户身份失败: ${result.msg}, code: ${result.code}`);
- }
- return {
- accessToken: result.data.access_token,
- tokenType: result.data.token_type,
- expiresIn: result.data.expires_in,
- refreshToken: result.data.refresh_token,
- refreshExpiresIn: result.data.refresh_expires_in,
- scope: result.data.scope
- };
- } catch (error) {
- console.error('获取登录用户身份异常:', error);
- throw error;
- }
- }
- /**
- * 转发请求到飞书API
- *
- * @static
- * @async
- * @param accessToken - 访问令牌
- * @param options - 请求选项
- * @returns Promise<any>
- * @throws {Error} 请求失败时抛出错误
- */
- static async forwardRequest(accessToken: string, options: ForwardRequestOptions): Promise<any> {
- try {
- const { path, method = 'GET', query, body } = options;
- let url = `https://open.feishu.cn/open-apis${path}`;
- if (query && typeof query === 'object') {
- const queryParams = new URLSearchParams();
- Object.keys(query).forEach(key => {
- if (query[key] !== undefined && query[key] !== null) {
- queryParams.append(key, query[key]);
- }
- });
- const queryString = queryParams.toString();
- if (queryString) {
- url += `?${queryString}`;
- }
- }
- const requestOptions: RequestInit = {
- method: method.toUpperCase(),
- headers: {
- 'Authorization': `Bearer ${accessToken}`,
- 'Content-Type': 'application/json; charset=utf-8'
- }
- };
- if (body && typeof body === 'object' && method.toUpperCase() !== 'GET') {
- requestOptions.body = JSON.stringify(body);
- }
- console.log('转发飞书API请求:', { url, method, body });
- const response = await fetch(url, requestOptions);
- const result = await response.json();
- console.log('飞书API响应:', result);
- return result;
- } catch (error) {
- console.error('转发请求异常:', error);
- throw error;
- }
- }
- }
- /**
- * 默认导出客户端类
- */
- export default FeishuClient;
|