apaas-oapi-client 0.1.30 → 0.1.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env +5 -0
- package/dist/index.d.ts +513 -0
- package/dist/index.js +322 -24
- package/dist/limiter.d.ts +17 -0
- package/dist/logger.d.ts +11 -0
- package/dist/src/index.d.ts +92 -8
- package/package.json +1 -1
- package/src/index.ts +462 -31
package/dist/index.js
CHANGED
|
@@ -43,6 +43,64 @@ async function functionLimiter(fn, options = {}) {
|
|
|
43
43
|
return wrapped();
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
/**
|
|
47
|
+
* 判断错误是否可重试
|
|
48
|
+
*/
|
|
49
|
+
function isRetryableError(error) {
|
|
50
|
+
// 网络错误
|
|
51
|
+
if (error.code === 'ECONNRESET' || error.code === 'ETIMEDOUT' || error.code === 'ENOTFOUND') {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
// Axios 错误
|
|
55
|
+
if (axios.isAxiosError(error)) {
|
|
56
|
+
const axiosError = error;
|
|
57
|
+
// 没有响应(网络错误)
|
|
58
|
+
if (!axiosError.response) {
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
// 5xx 服务器错误
|
|
62
|
+
if (axiosError.response.status >= 500) {
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
// 429 限流
|
|
66
|
+
if (axiosError.response.status === 429) {
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* 带重试的函数执行
|
|
74
|
+
*/
|
|
75
|
+
async function executeWithRetry(fn, options = {}, logContext = '', logger) {
|
|
76
|
+
const { maxRetries = 3, initialDelay = 1000, maxDelay = 10000, backoffMultiplier = 2 } = options;
|
|
77
|
+
let lastError;
|
|
78
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
79
|
+
try {
|
|
80
|
+
return await fn();
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
lastError = error;
|
|
84
|
+
// 最后一次尝试,直接抛出
|
|
85
|
+
if (attempt === maxRetries) {
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
// 判断是否可重试
|
|
89
|
+
if (!isRetryableError(error)) {
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
// 计算延迟时间(指数退避)
|
|
93
|
+
const delay = Math.min(initialDelay * Math.pow(backoffMultiplier, attempt), maxDelay);
|
|
94
|
+
if (logger) {
|
|
95
|
+
logger(LoggerLevel.warn, `${logContext} Attempt ${attempt + 1}/${maxRetries + 1} failed, retrying in ${delay}ms...`, error instanceof Error ? error.message : String(error));
|
|
96
|
+
}
|
|
97
|
+
// 等待后重试
|
|
98
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
// 所有重试都失败
|
|
102
|
+
throw lastError;
|
|
103
|
+
}
|
|
46
104
|
/**
|
|
47
105
|
* aPaaS OpenAPI 客户端
|
|
48
106
|
*/
|
|
@@ -61,11 +119,14 @@ class Client {
|
|
|
61
119
|
this.object = {
|
|
62
120
|
/**
|
|
63
121
|
* 列出所有对象(数据表)
|
|
64
|
-
* @param params 请求参数 { offset
|
|
65
|
-
* @returns
|
|
122
|
+
* @param params 请求参数 { offset?, filter?, limit? }
|
|
123
|
+
* @returns 接口返回结果,包含 has_more 字段表示是否还有更多数据
|
|
66
124
|
*/
|
|
67
125
|
list: async (params) => {
|
|
68
|
-
|
|
126
|
+
var _a, _b;
|
|
127
|
+
const offset = (_a = params === null || params === void 0 ? void 0 : params.offset) !== null && _a !== void 0 ? _a : 0;
|
|
128
|
+
const limit = (_b = params === null || params === void 0 ? void 0 : params.limit) !== null && _b !== void 0 ? _b : 50;
|
|
129
|
+
const filter = params === null || params === void 0 ? void 0 : params.filter;
|
|
69
130
|
await this.ensureTokenValid();
|
|
70
131
|
const url = `/api/data/v1/namespaces/${this.namespace}/meta/objects/list`;
|
|
71
132
|
this.log(LoggerLevel.debug, `[object.list] Fetching objects list: offset=${offset}, limit=${limit}`);
|
|
@@ -78,8 +139,66 @@ class Client {
|
|
|
78
139
|
});
|
|
79
140
|
this.log(LoggerLevel.debug, `[object.list] Objects list fetched successfully: code=${res.data.code}`);
|
|
80
141
|
this.log(LoggerLevel.trace, `[object.list] Response: ${JSON.stringify(res.data)}`);
|
|
142
|
+
// 添加 has_more 字段判断是否还有更多数据
|
|
143
|
+
if (res.data && res.data.data) {
|
|
144
|
+
const total = res.data.data.total || 0;
|
|
145
|
+
const currentEnd = offset + limit;
|
|
146
|
+
res.data.has_more = currentEnd < total;
|
|
147
|
+
this.log(LoggerLevel.debug, `[object.list] has_more=${res.data.has_more}, total=${total}, currentEnd=${currentEnd}`);
|
|
148
|
+
}
|
|
81
149
|
return res.data;
|
|
82
150
|
},
|
|
151
|
+
/**
|
|
152
|
+
* 列出所有对象(数据表)- 支持自动分页查询
|
|
153
|
+
* @description 该方法会自动处理分页,直到没有更多数据为止
|
|
154
|
+
* @param params 请求参数 { filter?, limit? }
|
|
155
|
+
* @returns { total, items }
|
|
156
|
+
*/
|
|
157
|
+
listWithIterator: async (params) => {
|
|
158
|
+
var _a, _b, _c, _d;
|
|
159
|
+
const filter = params === null || params === void 0 ? void 0 : params.filter;
|
|
160
|
+
const limit = (_a = params === null || params === void 0 ? void 0 : params.limit) !== null && _a !== void 0 ? _a : 50;
|
|
161
|
+
let results = [];
|
|
162
|
+
let offset = 0;
|
|
163
|
+
let total = 0;
|
|
164
|
+
let hasMore = true;
|
|
165
|
+
let page = 0;
|
|
166
|
+
let totalPages = 0;
|
|
167
|
+
this.log(LoggerLevel.info, `[object.listWithIterator] Starting paginated query with limit=${limit}`);
|
|
168
|
+
while (hasMore) {
|
|
169
|
+
const res = await this.object.list({
|
|
170
|
+
offset,
|
|
171
|
+
limit,
|
|
172
|
+
filter
|
|
173
|
+
});
|
|
174
|
+
if (res.code !== '0') {
|
|
175
|
+
this.log(LoggerLevel.error, `[object.listWithIterator] Error querying objects: code=${res.code}, msg=${res.msg}`);
|
|
176
|
+
throw new Error(res.msg || `Query failed with code ${res.code}`);
|
|
177
|
+
}
|
|
178
|
+
page += 1;
|
|
179
|
+
if (res.data && Array.isArray(res.data.items)) {
|
|
180
|
+
results = results.concat(res.data.items);
|
|
181
|
+
}
|
|
182
|
+
if (res.data && (res.data.total !== undefined && res.data.total !== null)) {
|
|
183
|
+
total = res.data.total;
|
|
184
|
+
}
|
|
185
|
+
if (page === 1) {
|
|
186
|
+
totalPages = Math.ceil(total / limit);
|
|
187
|
+
this.log(LoggerLevel.info, `[object.listWithIterator] Total objects: ${total}, pages: ${totalPages}`);
|
|
188
|
+
}
|
|
189
|
+
// 判断是否还有更多数据
|
|
190
|
+
hasMore = res.has_more === true;
|
|
191
|
+
offset += limit;
|
|
192
|
+
const padLength = totalPages.toString().length;
|
|
193
|
+
const pageStr = page.toString().padStart(padLength, '0');
|
|
194
|
+
const totalPagesStr = totalPages.toString().padStart(padLength, '0');
|
|
195
|
+
this.log(LoggerLevel.info, `[object.listWithIterator] Page completed: [${pageStr}/${totalPagesStr}]`);
|
|
196
|
+
this.log(LoggerLevel.debug, `[object.listWithIterator] Page ${page} details: items=${(_c = (_b = res.data) === null || _b === void 0 ? void 0 : _b.items) === null || _c === void 0 ? void 0 : _c.length}, hasMore=${hasMore}`);
|
|
197
|
+
this.log(LoggerLevel.trace, `[object.listWithIterator] Page ${page} data: ${JSON.stringify((_d = res.data) === null || _d === void 0 ? void 0 : _d.items)}`);
|
|
198
|
+
}
|
|
199
|
+
this.log(LoggerLevel.info, `[object.listWithIterator] Completed: total=${total}, fetched=${results.length}`);
|
|
200
|
+
return { total, items: results };
|
|
201
|
+
},
|
|
83
202
|
metadata: {
|
|
84
203
|
/**
|
|
85
204
|
* 获取指定对象下指定字段的元数据
|
|
@@ -214,6 +333,45 @@ class Client {
|
|
|
214
333
|
});
|
|
215
334
|
}
|
|
216
335
|
return { total, items: results };
|
|
336
|
+
},
|
|
337
|
+
/**
|
|
338
|
+
* 统计记录数量
|
|
339
|
+
* @description 统计指定对象中的记录总数,支持按条件统计
|
|
340
|
+
* @param params 请求参数 { object_name, data? }
|
|
341
|
+
* @returns 接口返回结果 { code, total, msg }
|
|
342
|
+
*/
|
|
343
|
+
count: async (params) => {
|
|
344
|
+
var _a;
|
|
345
|
+
const { object_name, data } = params;
|
|
346
|
+
// 默认查询参数:最小化数据传输,只获取总数
|
|
347
|
+
const defaultData = {
|
|
348
|
+
offset: 0,
|
|
349
|
+
page_size: 1,
|
|
350
|
+
need_total_count: true,
|
|
351
|
+
use_page_token: true,
|
|
352
|
+
select: ['_id'],
|
|
353
|
+
query_deleted_record: false
|
|
354
|
+
};
|
|
355
|
+
// 合并用户传入的参数(用户参数优先)
|
|
356
|
+
const queryData = data ? { ...defaultData, ...data } : defaultData;
|
|
357
|
+
this.log(LoggerLevel.info, `[object.search.count] Counting records in: ${object_name}`);
|
|
358
|
+
this.log(LoggerLevel.debug, `[object.search.count] Query data: ${JSON.stringify(queryData)}`);
|
|
359
|
+
const res = await this.object.search.records({
|
|
360
|
+
object_name,
|
|
361
|
+
data: queryData
|
|
362
|
+
});
|
|
363
|
+
if (res.code !== '0') {
|
|
364
|
+
this.log(LoggerLevel.error, `[object.search.count] Error counting records: code=${res.code}, msg=${res.msg}`);
|
|
365
|
+
throw new Error(res.msg || `Count failed with code ${res.code}`);
|
|
366
|
+
}
|
|
367
|
+
const total = ((_a = res.data) === null || _a === void 0 ? void 0 : _a.total) || 0;
|
|
368
|
+
this.log(LoggerLevel.info, `[object.search.count] Total records in ${object_name}: ${total}`);
|
|
369
|
+
// 返回格式:{ code, total, msg }
|
|
370
|
+
return {
|
|
371
|
+
code: res.code,
|
|
372
|
+
total: total,
|
|
373
|
+
msg: res.msg
|
|
374
|
+
};
|
|
217
375
|
}
|
|
218
376
|
},
|
|
219
377
|
create: {
|
|
@@ -631,10 +789,10 @@ class Client {
|
|
|
631
789
|
/**
|
|
632
790
|
* 批量部门 ID 交换
|
|
633
791
|
* @param params 请求参数
|
|
634
|
-
* @returns
|
|
792
|
+
* @returns 批量操作结果,包含成功和失败的详细信息
|
|
635
793
|
*/
|
|
636
794
|
batchExchange: async (params) => {
|
|
637
|
-
const { department_id_type, department_ids } = params;
|
|
795
|
+
const { department_id_type, department_ids, retryOptions } = params;
|
|
638
796
|
// department_id_type 可选值:
|
|
639
797
|
// - 'department_id' (如 "1758534140403815")
|
|
640
798
|
// - 'external_department_id' (外部平台 department_id, 无固定格式)
|
|
@@ -646,37 +804,177 @@ class Client {
|
|
|
646
804
|
}
|
|
647
805
|
if (department_ids.length === 0) {
|
|
648
806
|
this.log(LoggerLevel.warn, '[department.batchExchange] Empty department_ids array provided, returning empty result');
|
|
649
|
-
return [];
|
|
807
|
+
return { success: [], failed: [], successCount: 0, failedCount: 0, total: 0 };
|
|
650
808
|
}
|
|
651
809
|
const url = '/api/integration/v2/feishu/getDepartments';
|
|
652
|
-
const chunkSize =
|
|
810
|
+
const chunkSize = 200; // 最大支持200个
|
|
653
811
|
const chunks = [];
|
|
654
812
|
for (let i = 0; i < department_ids.length; i += chunkSize) {
|
|
655
813
|
chunks.push(department_ids.slice(i, i + chunkSize));
|
|
656
814
|
}
|
|
657
815
|
this.log(LoggerLevel.info, `[department.batchExchange] Chunking ${department_ids.length} department IDs into ${chunks.length} groups of ${chunkSize}`);
|
|
658
|
-
const
|
|
816
|
+
const successResults = [];
|
|
817
|
+
const failedResults = [];
|
|
659
818
|
for (const [index, chunk] of chunks.entries()) {
|
|
660
819
|
this.log(LoggerLevel.info, `[department.batchExchange] Processing chunk ${index + 1}/${chunks.length}: ${chunk.length} IDs`);
|
|
661
|
-
|
|
662
|
-
await
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
820
|
+
try {
|
|
821
|
+
const res = await executeWithRetry(async () => {
|
|
822
|
+
return await functionLimiter(async () => {
|
|
823
|
+
await this.ensureTokenValid();
|
|
824
|
+
const response = await this.axiosInstance.post(url, {
|
|
825
|
+
department_id_type,
|
|
826
|
+
department_ids: chunk
|
|
827
|
+
}, {
|
|
828
|
+
headers: { Authorization: `${this.accessToken}` },
|
|
829
|
+
timeout: 30000 // 30秒超时
|
|
830
|
+
});
|
|
831
|
+
this.log(LoggerLevel.debug, `[department.batchExchange] Chunk ${index + 1} completed: code=${response.data.code}`);
|
|
832
|
+
this.log(LoggerLevel.trace, `[department.batchExchange] Chunk ${index + 1} response: ${JSON.stringify(response.data)}`);
|
|
833
|
+
if (response.data.code !== '0') {
|
|
834
|
+
this.log(LoggerLevel.error, `[department.batchExchange] Error exchanging departments: code=${response.data.code}, msg=${response.data.msg}`);
|
|
835
|
+
throw new Error(response.data.msg || `Exchange failed with code ${response.data.code}`);
|
|
836
|
+
}
|
|
837
|
+
return response.data.data || [];
|
|
838
|
+
});
|
|
839
|
+
}, retryOptions, `[department.batchExchange] Chunk ${index + 1}/${chunks.length}`, this.log.bind(this));
|
|
840
|
+
successResults.push(...res);
|
|
841
|
+
this.log(LoggerLevel.info, `[department.batchExchange] Chunk ${index + 1} succeeded: ${res.length} departments`);
|
|
842
|
+
}
|
|
843
|
+
catch (error) {
|
|
844
|
+
// 部分失败:记录失败的chunk
|
|
845
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
846
|
+
this.log(LoggerLevel.error, `[department.batchExchange] Chunk ${index + 1} failed after retries: ${errorMsg}`);
|
|
847
|
+
chunk.forEach(id => {
|
|
848
|
+
failedResults.push({
|
|
849
|
+
id,
|
|
850
|
+
error: errorMsg
|
|
851
|
+
});
|
|
668
852
|
});
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
const result = {
|
|
856
|
+
success: successResults,
|
|
857
|
+
failed: failedResults,
|
|
858
|
+
successCount: successResults.length,
|
|
859
|
+
failedCount: failedResults.length,
|
|
860
|
+
total: department_ids.length
|
|
861
|
+
};
|
|
862
|
+
this.log(LoggerLevel.info, `[department.batchExchange] Completed: total=${result.total}, success=${result.successCount}, failed=${result.failedCount}`);
|
|
863
|
+
return result;
|
|
864
|
+
}
|
|
865
|
+
};
|
|
866
|
+
/**
|
|
867
|
+
* 用户 ID 交换模块
|
|
868
|
+
*/
|
|
869
|
+
this.user = {
|
|
870
|
+
/**
|
|
871
|
+
* 单个用户 ID 交换
|
|
872
|
+
* @param params 请求参数
|
|
873
|
+
* @returns 单个用户映射结果
|
|
874
|
+
*/
|
|
875
|
+
exchange: async (params) => {
|
|
876
|
+
const { user_id_type, user_id, feishu_app_id } = params;
|
|
877
|
+
// user_id_type 可选值:
|
|
878
|
+
// - 'user_id' (如 "1758534140403815")
|
|
879
|
+
// - 'external_user_id' (外部平台 user_id, 无固定格式)
|
|
880
|
+
// - 'external_open_id' (以 'ou_' 开头的 open_id)
|
|
881
|
+
const url = '/api/integration/v2/feishu/getUsers';
|
|
882
|
+
this.log(LoggerLevel.info, `[user.exchange] Exchanging user ID: ${user_id}`);
|
|
883
|
+
const res = await functionLimiter(async () => {
|
|
884
|
+
await this.ensureTokenValid();
|
|
885
|
+
const response = await this.axiosInstance.post(url, {
|
|
886
|
+
user_id_type,
|
|
887
|
+
feishu_app_id,
|
|
888
|
+
user_ids: [user_id]
|
|
889
|
+
}, {
|
|
890
|
+
headers: { Authorization: `${this.accessToken}` }
|
|
676
891
|
});
|
|
677
|
-
|
|
892
|
+
this.log(LoggerLevel.debug, `[user.exchange] User ID exchanged: ${user_id}, code=${response.data.code}`);
|
|
893
|
+
this.log(LoggerLevel.trace, `[user.exchange] Response: ${JSON.stringify(response.data)}`);
|
|
894
|
+
if (response.data.code !== '0') {
|
|
895
|
+
this.log(LoggerLevel.error, `[user.exchange] Error exchanging user: code=${response.data.code}, msg=${response.data.msg}`);
|
|
896
|
+
throw new Error(response.data.msg || `Exchange failed with code ${response.data.code}`);
|
|
897
|
+
}
|
|
898
|
+
return response.data.data && response.data.data[0]; // 返回第一个元素
|
|
899
|
+
});
|
|
900
|
+
return res;
|
|
901
|
+
},
|
|
902
|
+
/**
|
|
903
|
+
* 批量用户 ID 交换
|
|
904
|
+
* @param params 请求参数
|
|
905
|
+
* @returns 批量操作结果,包含成功和失败的详细信息
|
|
906
|
+
*/
|
|
907
|
+
batchExchange: async (params) => {
|
|
908
|
+
const { user_id_type, user_ids, feishu_app_id, retryOptions } = params;
|
|
909
|
+
// user_id_type 可选值:
|
|
910
|
+
// - 'user_id' (如 "1758534140403815")
|
|
911
|
+
// - 'external_user_id' (外部平台 user_id, 无固定格式)
|
|
912
|
+
// - 'external_open_id' (以 'ou_' 开头的 open_id)
|
|
913
|
+
// 参数校验
|
|
914
|
+
if (!user_ids || !Array.isArray(user_ids)) {
|
|
915
|
+
this.log(LoggerLevel.error, '[user.batchExchange] Invalid user_ids parameter: must be a non-empty array');
|
|
916
|
+
throw new Error('参数 user_ids 必须是一个数组');
|
|
917
|
+
}
|
|
918
|
+
if (user_ids.length === 0) {
|
|
919
|
+
this.log(LoggerLevel.warn, '[user.batchExchange] Empty user_ids array provided, returning empty result');
|
|
920
|
+
return { success: [], failed: [], successCount: 0, failedCount: 0, total: 0 };
|
|
921
|
+
}
|
|
922
|
+
const url = '/api/integration/v2/feishu/getUsers';
|
|
923
|
+
const chunkSize = 200; // 最大支持200个
|
|
924
|
+
const chunks = [];
|
|
925
|
+
for (let i = 0; i < user_ids.length; i += chunkSize) {
|
|
926
|
+
chunks.push(user_ids.slice(i, i + chunkSize));
|
|
927
|
+
}
|
|
928
|
+
this.log(LoggerLevel.info, `[user.batchExchange] Chunking ${user_ids.length} user IDs into ${chunks.length} groups of ${chunkSize}`);
|
|
929
|
+
const successResults = [];
|
|
930
|
+
const failedResults = [];
|
|
931
|
+
for (const [index, chunk] of chunks.entries()) {
|
|
932
|
+
this.log(LoggerLevel.info, `[user.batchExchange] Processing chunk ${index + 1}/${chunks.length}: ${chunk.length} IDs`);
|
|
933
|
+
try {
|
|
934
|
+
const res = await executeWithRetry(async () => {
|
|
935
|
+
return await functionLimiter(async () => {
|
|
936
|
+
await this.ensureTokenValid();
|
|
937
|
+
const response = await this.axiosInstance.post(url, {
|
|
938
|
+
user_id_type,
|
|
939
|
+
feishu_app_id,
|
|
940
|
+
user_ids: chunk
|
|
941
|
+
}, {
|
|
942
|
+
headers: { Authorization: `${this.accessToken}` },
|
|
943
|
+
timeout: 30000 // 30秒超时
|
|
944
|
+
});
|
|
945
|
+
this.log(LoggerLevel.debug, `[user.batchExchange] Chunk ${index + 1} completed: code=${response.data.code}`);
|
|
946
|
+
this.log(LoggerLevel.trace, `[user.batchExchange] Chunk ${index + 1} response: ${JSON.stringify(response.data)}`);
|
|
947
|
+
if (response.data.code !== '0') {
|
|
948
|
+
this.log(LoggerLevel.error, `[user.batchExchange] Error exchanging users: code=${response.data.code}, msg=${response.data.msg}`);
|
|
949
|
+
throw new Error(response.data.msg || `Exchange failed with code ${response.data.code}`);
|
|
950
|
+
}
|
|
951
|
+
return response.data.data || [];
|
|
952
|
+
});
|
|
953
|
+
}, retryOptions, `[user.batchExchange] Chunk ${index + 1}/${chunks.length}`, this.log.bind(this));
|
|
954
|
+
successResults.push(...res);
|
|
955
|
+
this.log(LoggerLevel.info, `[user.batchExchange] Chunk ${index + 1} succeeded: ${res.length} users`);
|
|
956
|
+
}
|
|
957
|
+
catch (error) {
|
|
958
|
+
// 部分失败:记录失败的chunk
|
|
959
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
960
|
+
this.log(LoggerLevel.error, `[user.batchExchange] Chunk ${index + 1} failed after retries: ${errorMsg}`);
|
|
961
|
+
chunk.forEach(id => {
|
|
962
|
+
failedResults.push({
|
|
963
|
+
id,
|
|
964
|
+
error: errorMsg
|
|
965
|
+
});
|
|
966
|
+
});
|
|
967
|
+
}
|
|
678
968
|
}
|
|
679
|
-
|
|
969
|
+
const result = {
|
|
970
|
+
success: successResults,
|
|
971
|
+
failed: failedResults,
|
|
972
|
+
successCount: successResults.length,
|
|
973
|
+
failedCount: failedResults.length,
|
|
974
|
+
total: user_ids.length
|
|
975
|
+
};
|
|
976
|
+
this.log(LoggerLevel.info, `[user.batchExchange] Completed: total=${result.total}, success=${result.successCount}, failed=${result.failedCount}`);
|
|
977
|
+
return result;
|
|
680
978
|
}
|
|
681
979
|
};
|
|
682
980
|
/**
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import Bottleneck from 'bottleneck';
|
|
2
|
+
/**
|
|
3
|
+
* 默认 apaas 限流配置
|
|
4
|
+
*/
|
|
5
|
+
export declare const apaasLimiterOptions: {
|
|
6
|
+
minTime: number;
|
|
7
|
+
reservoir: number;
|
|
8
|
+
reservoirRefreshAmount: number;
|
|
9
|
+
reservoirRefreshInterval: number;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* 创建限流器
|
|
13
|
+
* @param fn 被限流函数
|
|
14
|
+
* @param options 自定义限流配置
|
|
15
|
+
* @returns 包装后的限流函数
|
|
16
|
+
*/
|
|
17
|
+
export declare function functionLimiter<T>(fn: () => Promise<T>, options?: Partial<Bottleneck.ConstructorOptions>): Promise<T>;
|
package/dist/logger.d.ts
ADDED
package/dist/src/index.d.ts
CHANGED
|
@@ -1,4 +1,35 @@
|
|
|
1
1
|
import { LoggerLevel } from './logger';
|
|
2
|
+
/**
|
|
3
|
+
* 批量操作结果
|
|
4
|
+
*/
|
|
5
|
+
interface BatchResult<T> {
|
|
6
|
+
/** 成功的项 */
|
|
7
|
+
success: T[];
|
|
8
|
+
/** 失败的项 */
|
|
9
|
+
failed: Array<{
|
|
10
|
+
id: string;
|
|
11
|
+
error: string;
|
|
12
|
+
}>;
|
|
13
|
+
/** 成功数量 */
|
|
14
|
+
successCount: number;
|
|
15
|
+
/** 失败数量 */
|
|
16
|
+
failedCount: number;
|
|
17
|
+
/** 总数 */
|
|
18
|
+
total: number;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* 重试配置
|
|
22
|
+
*/
|
|
23
|
+
interface RetryOptions {
|
|
24
|
+
/** 最大重试次数 */
|
|
25
|
+
maxRetries?: number;
|
|
26
|
+
/** 初始延迟时间(ms) */
|
|
27
|
+
initialDelay?: number;
|
|
28
|
+
/** 最大延迟时间(ms) */
|
|
29
|
+
maxDelay?: number;
|
|
30
|
+
/** 延迟倍数 */
|
|
31
|
+
backoffMultiplier?: number;
|
|
32
|
+
}
|
|
2
33
|
/**
|
|
3
34
|
* Client 初始化配置
|
|
4
35
|
*/
|
|
@@ -71,17 +102,33 @@ declare class Client {
|
|
|
71
102
|
object: {
|
|
72
103
|
/**
|
|
73
104
|
* 列出所有对象(数据表)
|
|
74
|
-
* @param params 请求参数 { offset
|
|
75
|
-
* @returns
|
|
105
|
+
* @param params 请求参数 { offset?, filter?, limit? }
|
|
106
|
+
* @returns 接口返回结果,包含 has_more 字段表示是否还有更多数据
|
|
76
107
|
*/
|
|
77
|
-
list: (params
|
|
78
|
-
offset
|
|
108
|
+
list: (params?: {
|
|
109
|
+
offset?: number;
|
|
79
110
|
filter?: {
|
|
80
111
|
type?: string;
|
|
81
112
|
quickQuery?: string;
|
|
82
113
|
};
|
|
83
|
-
limit
|
|
114
|
+
limit?: number;
|
|
84
115
|
}) => Promise<any>;
|
|
116
|
+
/**
|
|
117
|
+
* 列出所有对象(数据表)- 支持自动分页查询
|
|
118
|
+
* @description 该方法会自动处理分页,直到没有更多数据为止
|
|
119
|
+
* @param params 请求参数 { filter?, limit? }
|
|
120
|
+
* @returns { total, items }
|
|
121
|
+
*/
|
|
122
|
+
listWithIterator: (params?: {
|
|
123
|
+
filter?: {
|
|
124
|
+
type?: string;
|
|
125
|
+
quickQuery?: string;
|
|
126
|
+
};
|
|
127
|
+
limit?: number;
|
|
128
|
+
}) => Promise<{
|
|
129
|
+
total: number;
|
|
130
|
+
items: any[];
|
|
131
|
+
}>;
|
|
85
132
|
metadata: {
|
|
86
133
|
/**
|
|
87
134
|
* 获取指定对象下指定字段的元数据
|
|
@@ -138,6 +185,16 @@ declare class Client {
|
|
|
138
185
|
total: number;
|
|
139
186
|
items: any[];
|
|
140
187
|
}>;
|
|
188
|
+
/**
|
|
189
|
+
* 统计记录数量
|
|
190
|
+
* @description 统计指定对象中的记录总数,支持按条件统计
|
|
191
|
+
* @param params 请求参数 { object_name, data? }
|
|
192
|
+
* @returns 接口返回结果 { code, total, msg }
|
|
193
|
+
*/
|
|
194
|
+
count: (params: {
|
|
195
|
+
object_name: string;
|
|
196
|
+
data?: any;
|
|
197
|
+
}) => Promise<any>;
|
|
141
198
|
};
|
|
142
199
|
create: {
|
|
143
200
|
/**
|
|
@@ -295,12 +352,39 @@ declare class Client {
|
|
|
295
352
|
/**
|
|
296
353
|
* 批量部门 ID 交换
|
|
297
354
|
* @param params 请求参数
|
|
298
|
-
* @returns
|
|
355
|
+
* @returns 批量操作结果,包含成功和失败的详细信息
|
|
299
356
|
*/
|
|
300
357
|
batchExchange: (params: {
|
|
301
358
|
department_id_type: "department_id" | "external_department_id" | "external_open_department_id";
|
|
302
359
|
department_ids: string[];
|
|
303
|
-
|
|
360
|
+
retryOptions?: RetryOptions;
|
|
361
|
+
}) => Promise<BatchResult<any>>;
|
|
362
|
+
};
|
|
363
|
+
/**
|
|
364
|
+
* 用户 ID 交换模块
|
|
365
|
+
*/
|
|
366
|
+
user: {
|
|
367
|
+
/**
|
|
368
|
+
* 单个用户 ID 交换
|
|
369
|
+
* @param params 请求参数
|
|
370
|
+
* @returns 单个用户映射结果
|
|
371
|
+
*/
|
|
372
|
+
exchange: (params: {
|
|
373
|
+
user_id_type: "user_id" | "external_user_id" | "external_open_id";
|
|
374
|
+
user_id: string;
|
|
375
|
+
feishu_app_id: string;
|
|
376
|
+
}) => Promise<any>;
|
|
377
|
+
/**
|
|
378
|
+
* 批量用户 ID 交换
|
|
379
|
+
* @param params 请求参数
|
|
380
|
+
* @returns 批量操作结果,包含成功和失败的详细信息
|
|
381
|
+
*/
|
|
382
|
+
batchExchange: (params: {
|
|
383
|
+
user_id_type: "user_id" | "external_user_id" | "external_open_id";
|
|
384
|
+
user_ids: string[];
|
|
385
|
+
feishu_app_id: string;
|
|
386
|
+
retryOptions?: RetryOptions;
|
|
387
|
+
}) => Promise<BatchResult<any>>;
|
|
304
388
|
};
|
|
305
389
|
/**
|
|
306
390
|
* 云函数模块
|
|
@@ -549,4 +633,4 @@ declare class Client {
|
|
|
549
633
|
export declare const apaas: {
|
|
550
634
|
Client: typeof Client;
|
|
551
635
|
};
|
|
552
|
-
export {};
|
|
636
|
+
export type { BatchResult, RetryOptions };
|