apaas-oapi-client 0.1.0

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/README.md ADDED
File without changes
@@ -0,0 +1,149 @@
1
+ import { LoggerLevel } from './logger';
2
+ /**
3
+ * Client 初始化配置
4
+ */
5
+ interface ClientOptions {
6
+ /** 命名空间,例如 app_xxx */
7
+ namespace: string;
8
+ /** 应用 clientId */
9
+ clientId: string;
10
+ /** 应用 clientSecret */
11
+ clientSecret: string;
12
+ /** 是否禁用 token 缓存,每次调用强制刷新 token,默认 false */
13
+ disableTokenCache?: boolean;
14
+ }
15
+ /**
16
+ * records_query 接口请求参数
17
+ */
18
+ interface RecordsQueryParams {
19
+ /** 对象名称,例如 object_store */
20
+ object_name: string;
21
+ /** 请求体数据 */
22
+ data: any;
23
+ }
24
+ /**
25
+ * aPaaS OpenAPI 客户端
26
+ */
27
+ declare class Client {
28
+ private clientId;
29
+ private clientSecret;
30
+ private namespace;
31
+ private disableTokenCache;
32
+ private accessToken;
33
+ private expireTime;
34
+ private axiosInstance;
35
+ private loggerLevel;
36
+ /**
37
+ * 构造函数
38
+ * @param options ClientOptions
39
+ */
40
+ constructor(options: ClientOptions);
41
+ /**
42
+ * 设置日志等级
43
+ * @param level LoggerLevel
44
+ */
45
+ setLoggerLevel(level: LoggerLevel): void;
46
+ /**
47
+ * 日志打印方法
48
+ * @param level LoggerLevel
49
+ * @param args 打印内容
50
+ */
51
+ private log;
52
+ /**
53
+ * 初始化 client,自动获取 token
54
+ */
55
+ init(): Promise<void>;
56
+ /**
57
+ * 获取 accessToken
58
+ */
59
+ private getAccessToken;
60
+ /**
61
+ * 确保 token 有效,若过期则刷新
62
+ */
63
+ private ensureTokenValid;
64
+ /**
65
+ * 获取当前 accessToken
66
+ */
67
+ get token(): string | null;
68
+ /**
69
+ * 获取当前 namespace
70
+ */
71
+ get currentNamespace(): string;
72
+ /**
73
+ * 对象模块
74
+ */
75
+ object: {
76
+ search: {
77
+ /**
78
+ * 单条记录查询
79
+ * @param params 请求参数
80
+ * @returns 接口返回结果
81
+ */
82
+ record: (params: {
83
+ object_name: string;
84
+ record_id: string;
85
+ select: string[];
86
+ }) => Promise<any>;
87
+ /**
88
+ * records_query 接口
89
+ * @param params 请求参数
90
+ * @returns 接口返回结果
91
+ */
92
+ records: (params: RecordsQueryParams) => Promise<any>;
93
+ /**
94
+ * 分页查询所有记录
95
+ * @param params 请求参数
96
+ * @returns { total, items }
97
+ */
98
+ recordsWithIterator: (params: RecordsQueryParams) => Promise<{
99
+ total: number;
100
+ items: any[];
101
+ }>;
102
+ };
103
+ update: {
104
+ /**
105
+ * 单条更新
106
+ * @param params 请求参数
107
+ * @returns 接口返回结果
108
+ */
109
+ record: (params: {
110
+ object_name: string;
111
+ record_id: string;
112
+ record: any;
113
+ }) => Promise<any>;
114
+ /**
115
+ * 批量更新
116
+ * @param params 请求参数
117
+ * @returns 所有子请求的返回结果数组
118
+ */
119
+ recordsBatchUpdate: (params: {
120
+ object_name: string;
121
+ records: any[];
122
+ }) => Promise<any[]>;
123
+ };
124
+ delete: {
125
+ /**
126
+ * 单条删除
127
+ * @param params 请求参数
128
+ * @returns 接口返回结果
129
+ */
130
+ record: (params: {
131
+ object_name: string;
132
+ record_id: string;
133
+ }) => Promise<any>;
134
+ /**
135
+ * 批量删除
136
+ * @param params 请求参数
137
+ * @returns 所有子请求的返回结果数组
138
+ */
139
+ recordsBatchDelete: (params: {
140
+ object_name: string;
141
+ ids: string[];
142
+ }) => Promise<any[]>;
143
+ };
144
+ };
145
+ }
146
+ export declare const apaas: {
147
+ Client: typeof Client;
148
+ };
149
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,293 @@
1
+ 'use strict';
2
+
3
+ var dayjs = require('dayjs');
4
+ var axios = require('axios');
5
+
6
+ /**
7
+ * 日志等级枚举
8
+ */
9
+ var LoggerLevel;
10
+ (function (LoggerLevel) {
11
+ LoggerLevel[LoggerLevel["fatal"] = 0] = "fatal";
12
+ LoggerLevel[LoggerLevel["error"] = 1] = "error";
13
+ LoggerLevel[LoggerLevel["warn"] = 2] = "warn";
14
+ LoggerLevel[LoggerLevel["info"] = 3] = "info";
15
+ LoggerLevel[LoggerLevel["debug"] = 4] = "debug";
16
+ LoggerLevel[LoggerLevel["trace"] = 5] = "trace";
17
+ })(LoggerLevel || (LoggerLevel = {}));
18
+
19
+ const { functionLimiter } = require('./limiter');
20
+ /**
21
+ * aPaaS OpenAPI 客户端
22
+ */
23
+ class Client {
24
+ /**
25
+ * 构造函数
26
+ * @param options ClientOptions
27
+ */
28
+ constructor(options) {
29
+ this.accessToken = null;
30
+ this.expireTime = null;
31
+ this.loggerLevel = LoggerLevel.info;
32
+ /**
33
+ * 对象模块
34
+ */
35
+ this.object = {
36
+ search: {
37
+ /**
38
+ * 单条记录查询
39
+ * @param params 请求参数
40
+ * @returns 接口返回结果
41
+ */
42
+ record: async (params) => {
43
+ const { object_name, record_id, select } = params;
44
+ const url = `/v1/data/namespaces/${this.namespace}/objects/${object_name}/records/${record_id}`;
45
+ this.log(LoggerLevel.info, `[单条查询记录] 🔍 开始查询 record_id: ${record_id}`);
46
+ const res = await functionLimiter(async () => {
47
+ await this.ensureTokenValid();
48
+ const response = await this.axiosInstance.post(url, { select }, { headers: { Authorization: `${this.accessToken}` } });
49
+ this.log(LoggerLevel.info, `[单条查询记录] 🔍 record_id: ${record_id} 查询完成,返回 code: ${response.data.code}`);
50
+ return response.data;
51
+ });
52
+ return res;
53
+ },
54
+ /**
55
+ * records_query 接口
56
+ * @param params 请求参数
57
+ * @returns 接口返回结果
58
+ */
59
+ records: async (params) => {
60
+ const { object_name, data } = params;
61
+ await this.ensureTokenValid();
62
+ const url = `/v1/data/namespaces/${this.namespace}/objects/${object_name}/records_query`;
63
+ const res = await this.axiosInstance.post(url, data, {
64
+ headers: { Authorization: `${this.accessToken}` }
65
+ });
66
+ this.log(LoggerLevel.debug, `[批量查询记录] 🔍 records_query 调用完成,object_name: ${object_name}`);
67
+ return res.data;
68
+ },
69
+ /**
70
+ * 分页查询所有记录
71
+ * @param params 请求参数
72
+ * @returns { total, items }
73
+ */
74
+ recordsWithIterator: async (params) => {
75
+ const { object_name, data } = params;
76
+ let results = [];
77
+ let nextPageToken = '';
78
+ let total = 0;
79
+ let page = 0;
80
+ do {
81
+ await functionLimiter(async () => {
82
+ const mergedData = { ...data, page_token: nextPageToken || '' };
83
+ const res = await this.object.search.records({
84
+ object_name,
85
+ data: mergedData
86
+ });
87
+ page += 1;
88
+ if (res.data && Array.isArray(res.data.items)) {
89
+ results = results.concat(res.data.items);
90
+ }
91
+ if (page === 1) {
92
+ total = res.data.total || 0;
93
+ this.log(LoggerLevel.info, '[批量查询记录] 🔍 接口返回 total:', total);
94
+ }
95
+ nextPageToken = res.data.next_page_token;
96
+ this.log(LoggerLevel.debug, `[批量查询记录] 🔍 第 ${page} 页查询完成,items.length: ${res.data.items.length}`);
97
+ return res;
98
+ });
99
+ } while (nextPageToken);
100
+ return { total, items: results };
101
+ }
102
+ },
103
+ update: {
104
+ /**
105
+ * 单条更新
106
+ * @param params 请求参数
107
+ * @returns 接口返回结果
108
+ */
109
+ record: async (params) => {
110
+ const { object_name, record_id, record } = params;
111
+ const url = `/v1/data/namespaces/${this.namespace}/objects/${object_name}/records/${record_id}`;
112
+ this.log(LoggerLevel.info, `[单条更新记录] 💾 开始更新 record_id: ${record_id}`);
113
+ const res = await functionLimiter(async () => {
114
+ await this.ensureTokenValid();
115
+ const response = await this.axiosInstance.patch(url, { record }, { headers: { Authorization: `${this.accessToken}` } });
116
+ this.log(LoggerLevel.info, `[单条更新记录] 💾 record_id: ${record_id} 更新完成,返回 code: ${response.data.code}`);
117
+ return response.data;
118
+ });
119
+ return res;
120
+ },
121
+ /**
122
+ * 批量更新
123
+ * @param params 请求参数
124
+ * @returns 所有子请求的返回结果数组
125
+ */
126
+ recordsBatchUpdate: async (params) => {
127
+ const { object_name, records } = params;
128
+ const url = `/v1/data/namespaces/${this.namespace}/objects/${object_name}/records_batch`;
129
+ const chunkSize = 100;
130
+ const chunks = [];
131
+ for (let i = 0; i < records.length; i += chunkSize) {
132
+ chunks.push(records.slice(i, i + chunkSize));
133
+ }
134
+ this.log(LoggerLevel.info, `[批量更新记录] 💾 总共 ${records.length} 条记录,拆分为 ${chunks.length} 组,每组最多 ${chunkSize} 条`);
135
+ const results = [];
136
+ for (const [index, chunk] of chunks.entries()) {
137
+ this.log(LoggerLevel.info, `[批量更新记录] 💾 开始更新第 ${index + 1} 组,共 ${chunk.length} 条`);
138
+ const res = await functionLimiter(async () => {
139
+ await this.ensureTokenValid();
140
+ const response = await this.axiosInstance.patch(url, { records: chunk }, { headers: { Authorization: `${this.accessToken}` } });
141
+ this.log(LoggerLevel.info, `[批量更新记录] 💾 第 ${index + 1} 组更新完成,返回 code: ${response.data.code}`);
142
+ return response.data;
143
+ });
144
+ results.push(res);
145
+ }
146
+ return results;
147
+ }
148
+ },
149
+ delete: {
150
+ /**
151
+ * 单条删除
152
+ * @param params 请求参数
153
+ * @returns 接口返回结果
154
+ */
155
+ record: async (params) => {
156
+ const { object_name, record_id } = params;
157
+ const url = `/v1/data/namespaces/${this.namespace}/objects/${object_name}/records/${record_id}`;
158
+ this.log(LoggerLevel.info, `[单条删除记录] 🗑️ 开始删除 record_id: ${record_id}`);
159
+ const res = await functionLimiter(async () => {
160
+ await this.ensureTokenValid();
161
+ const response = await this.axiosInstance.delete(url, {
162
+ headers: { Authorization: `${this.accessToken}` }
163
+ });
164
+ this.log(LoggerLevel.info, `[单条删除记录] 🗑️ record_id: ${record_id} 删除完成,返回 code: ${response.data.code}`);
165
+ return response.data;
166
+ });
167
+ return res;
168
+ },
169
+ /**
170
+ * 批量删除
171
+ * @param params 请求参数
172
+ * @returns 所有子请求的返回结果数组
173
+ */
174
+ recordsBatchDelete: async (params) => {
175
+ const { object_name, ids } = params;
176
+ const url = `/v1/data/namespaces/${this.namespace}/objects/${object_name}/records_batch`;
177
+ const chunkSize = 100;
178
+ const chunks = [];
179
+ for (let i = 0; i < ids.length; i += chunkSize) {
180
+ chunks.push(ids.slice(i, i + chunkSize));
181
+ }
182
+ this.log(LoggerLevel.info, `[批量删除记录] 🗑️ 总共 ${ids.length} 条记录,拆分为 ${chunks.length} 组,每组最多 ${chunkSize} 条`);
183
+ const results = [];
184
+ for (const [index, chunk] of chunks.entries()) {
185
+ this.log(LoggerLevel.info, `[批量删除记录] 🗑️ 开始删除第 ${index + 1} 组,共 ${chunk.length} 条`);
186
+ const res = await functionLimiter(async () => {
187
+ await this.ensureTokenValid();
188
+ const response = await this.axiosInstance.delete(url, {
189
+ headers: { Authorization: `${this.accessToken}` },
190
+ data: { ids: chunk }
191
+ });
192
+ this.log(LoggerLevel.info, `[批量删除记录] 🗑️ 第 ${index + 1} 组删除完成,返回 code: ${response.data.code}`);
193
+ return response.data;
194
+ });
195
+ results.push(res);
196
+ }
197
+ return results;
198
+ }
199
+ }
200
+ };
201
+ this.clientId = options.clientId;
202
+ this.clientSecret = options.clientSecret;
203
+ this.namespace = options.namespace;
204
+ this.disableTokenCache = options.disableTokenCache || false;
205
+ this.axiosInstance = axios.create({
206
+ baseURL: 'https://ae-openapi.feishu.cn',
207
+ headers: { 'Content-Type': 'application/json' }
208
+ });
209
+ this.log(LoggerLevel.info, 'client initialized');
210
+ }
211
+ /**
212
+ * 设置日志等级
213
+ * @param level LoggerLevel
214
+ */
215
+ setLoggerLevel(level) {
216
+ this.loggerLevel = level;
217
+ this.log(LoggerLevel.info, `logger level set to ${LoggerLevel[level]}`);
218
+ }
219
+ /**
220
+ * 日志打印方法
221
+ * @param level LoggerLevel
222
+ * @param args 打印内容
223
+ */
224
+ log(level, ...args) {
225
+ if (this.loggerLevel >= level) {
226
+ const levelStr = LoggerLevel[level];
227
+ const timestamp = dayjs().format('YYYY-MM-DD HH:mm:ss:SSS');
228
+ console.log(`[${levelStr}] [${timestamp}]`, ...args);
229
+ }
230
+ }
231
+ /**
232
+ * 初始化 client,自动获取 token
233
+ */
234
+ async init() {
235
+ await this.ensureTokenValid();
236
+ this.log(LoggerLevel.info, 'client ready');
237
+ }
238
+ /**
239
+ * 获取 accessToken
240
+ */
241
+ async getAccessToken() {
242
+ const url = '/auth/v1/appToken';
243
+ const res = await this.axiosInstance.post(url, {
244
+ clientId: this.clientId,
245
+ clientSecret: this.clientSecret
246
+ });
247
+ if (res.data.code !== '0') {
248
+ this.log(LoggerLevel.error, `[获取认证] 获取 accessToken 失败: ${res.data.msg}`);
249
+ throw new Error(`获取 accessToken 失败: ${res.data.msg}`);
250
+ }
251
+ this.accessToken = res.data.data.accessToken;
252
+ this.expireTime = res.data.data.expireTime;
253
+ this.log(LoggerLevel.info, '[获取认证] accessToken refreshed');
254
+ }
255
+ /**
256
+ * 确保 token 有效,若过期则刷新
257
+ */
258
+ async ensureTokenValid() {
259
+ if (this.disableTokenCache) {
260
+ this.log(LoggerLevel.debug, '[获取认证] token cache disabled, refreshing token');
261
+ await this.getAccessToken();
262
+ return;
263
+ }
264
+ if (!this.accessToken || !this.expireTime) {
265
+ this.log(LoggerLevel.debug, '[获取认证] no token cached, fetching new token');
266
+ await this.getAccessToken();
267
+ return;
268
+ }
269
+ const now = dayjs().valueOf();
270
+ if (now + 60 * 1000 > this.expireTime) {
271
+ this.log(LoggerLevel.debug, '[获取认证] token expired, refreshing');
272
+ await this.getAccessToken();
273
+ }
274
+ }
275
+ /**
276
+ * 获取当前 accessToken
277
+ */
278
+ get token() {
279
+ return this.accessToken;
280
+ }
281
+ /**
282
+ * 获取当前 namespace
283
+ */
284
+ get currentNamespace() {
285
+ this.log(LoggerLevel.debug, `[获取命名空间] 当前命名空间: ${this.namespace}`);
286
+ return this.namespace;
287
+ }
288
+ }
289
+ const apaas = {
290
+ Client
291
+ };
292
+
293
+ exports.apaas = apaas;
@@ -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>;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * 日志等级枚举
3
+ */
4
+ export declare enum LoggerLevel {
5
+ fatal = 0,
6
+ error = 1,
7
+ warn = 2,
8
+ info = 3,
9
+ debug = 4,
10
+ trace = 5
11
+ }
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "apaas-oapi-client",
3
+ "version": "0.1.0",
4
+ "exports": {
5
+ "./node-sdk": "./dist/index.js"
6
+ },
7
+ "main": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "type": "commonjs",
10
+ "scripts": {
11
+ "build": "rollup -c",
12
+ "test": "jest",
13
+ "dev": "ts-node src/index.ts"
14
+ },
15
+ "keywords": [],
16
+ "author": "",
17
+ "license": "ISC",
18
+ "dependencies": {
19
+ "axios": "^1.10.0",
20
+ "bottleneck": "^2.19.5",
21
+ "dayjs": "^1.11.13"
22
+ },
23
+ "directories": {
24
+ "test": "test"
25
+ },
26
+ "devDependencies": {
27
+ "@types/jest": "^30.0.0",
28
+ "@types/node": "^24.0.7",
29
+ "jest": "^30.0.3",
30
+ "rollup": "^4.44.1",
31
+ "rollup-plugin-typescript2": "^0.36.0",
32
+ "ts-jest": "^29.4.0",
33
+ "ts-node": "^10.9.2",
34
+ "typescript": "^5.8.3"
35
+ },
36
+ "description": ""
37
+ }
package/src/index.ts ADDED
@@ -0,0 +1,382 @@
1
+ import dayjs from 'dayjs';
2
+ import axios, { AxiosInstance } from 'axios';
3
+ import { LoggerLevel } from './logger';
4
+ const { functionLimiter } = require('./limiter');
5
+
6
+ /**
7
+ * Client 初始化配置
8
+ */
9
+ interface ClientOptions {
10
+ /** 命名空间,例如 app_xxx */
11
+ namespace: string;
12
+ /** 应用 clientId */
13
+ clientId: string;
14
+ /** 应用 clientSecret */
15
+ clientSecret: string;
16
+ /** 是否禁用 token 缓存,每次调用强制刷新 token,默认 false */
17
+ disableTokenCache?: boolean;
18
+ }
19
+
20
+ /**
21
+ * 获取 token 接口返回体
22
+ */
23
+ interface TokenResponse {
24
+ code: string;
25
+ data: {
26
+ accessToken: string;
27
+ expireTime: number; // 过期时间戳
28
+ };
29
+ msg: string;
30
+ }
31
+
32
+ /**
33
+ * records_query 接口请求参数
34
+ */
35
+ interface RecordsQueryParams {
36
+ /** 对象名称,例如 object_store */
37
+ object_name: string;
38
+ /** 请求体数据 */
39
+ data: any;
40
+ }
41
+
42
+ /**
43
+ * aPaaS OpenAPI 客户端
44
+ */
45
+ class Client {
46
+ private clientId: string;
47
+ private clientSecret: string;
48
+ private namespace: string;
49
+ private disableTokenCache: boolean;
50
+ private accessToken: string | null = null;
51
+ private expireTime: number | null = null;
52
+ private axiosInstance: AxiosInstance;
53
+ private loggerLevel: LoggerLevel = LoggerLevel.info;
54
+
55
+ /**
56
+ * 构造函数
57
+ * @param options ClientOptions
58
+ */
59
+ constructor(options: ClientOptions) {
60
+ this.clientId = options.clientId;
61
+ this.clientSecret = options.clientSecret;
62
+ this.namespace = options.namespace;
63
+ this.disableTokenCache = options.disableTokenCache || false;
64
+
65
+ this.axiosInstance = axios.create({
66
+ baseURL: 'https://ae-openapi.feishu.cn',
67
+ headers: { 'Content-Type': 'application/json' }
68
+ });
69
+ this.log(LoggerLevel.info, 'client initialized');
70
+ }
71
+
72
+ /**
73
+ * 设置日志等级
74
+ * @param level LoggerLevel
75
+ */
76
+ setLoggerLevel(level: LoggerLevel) {
77
+ this.loggerLevel = level;
78
+ this.log(LoggerLevel.info, `logger level set to ${LoggerLevel[level]}`);
79
+ }
80
+
81
+ /**
82
+ * 日志打印方法
83
+ * @param level LoggerLevel
84
+ * @param args 打印内容
85
+ */
86
+
87
+ private log(level: LoggerLevel, ...args: any[]) {
88
+ if (this.loggerLevel >= level) {
89
+ const levelStr = LoggerLevel[level];
90
+ const timestamp = dayjs().format('YYYY-MM-DD HH:mm:ss:SSS');
91
+ console.log(`[${levelStr}] [${timestamp}]`, ...args);
92
+ }
93
+ }
94
+ /**
95
+ * 初始化 client,自动获取 token
96
+ */
97
+ async init() {
98
+ await this.ensureTokenValid();
99
+ this.log(LoggerLevel.info, 'client ready');
100
+ }
101
+
102
+ /**
103
+ * 获取 accessToken
104
+ */
105
+ private async getAccessToken(): Promise<void> {
106
+ const url = '/auth/v1/appToken';
107
+ const res = await this.axiosInstance.post<TokenResponse>(url, {
108
+ clientId: this.clientId,
109
+ clientSecret: this.clientSecret
110
+ });
111
+
112
+ if (res.data.code !== '0') {
113
+ this.log(LoggerLevel.error, `[获取认证] 获取 accessToken 失败: ${res.data.msg}`);
114
+ throw new Error(`获取 accessToken 失败: ${res.data.msg}`);
115
+ }
116
+
117
+ this.accessToken = res.data.data.accessToken;
118
+ this.expireTime = res.data.data.expireTime;
119
+ this.log(LoggerLevel.info, '[获取认证] accessToken refreshed');
120
+ }
121
+
122
+ /**
123
+ * 确保 token 有效,若过期则刷新
124
+ */
125
+ private async ensureTokenValid() {
126
+ if (this.disableTokenCache) {
127
+ this.log(LoggerLevel.debug, '[获取认证] token cache disabled, refreshing token');
128
+ await this.getAccessToken();
129
+ return;
130
+ }
131
+
132
+ if (!this.accessToken || !this.expireTime) {
133
+ this.log(LoggerLevel.debug, '[获取认证] no token cached, fetching new token');
134
+ await this.getAccessToken();
135
+ return;
136
+ }
137
+
138
+ const now = dayjs().valueOf();
139
+ if (now + 60 * 1000 > this.expireTime) {
140
+ this.log(LoggerLevel.debug, '[获取认证] token expired, refreshing');
141
+ await this.getAccessToken();
142
+ }
143
+ }
144
+
145
+ /**
146
+ * 获取当前 accessToken
147
+ */
148
+ get token() {
149
+ return this.accessToken;
150
+ }
151
+
152
+ /**
153
+ * 获取当前 namespace
154
+ */
155
+ get currentNamespace() {
156
+ this.log(LoggerLevel.debug, `[获取命名空间] 当前命名空间: ${this.namespace}`);
157
+ return this.namespace;
158
+ }
159
+
160
+ /**
161
+ * 对象模块
162
+ */
163
+ public object = {
164
+ search: {
165
+ /**
166
+ * 单条记录查询
167
+ * @param params 请求参数
168
+ * @returns 接口返回结果
169
+ */
170
+ record: async (params: { object_name: string; record_id: string; select: string[] }): Promise<any> => {
171
+ const { object_name, record_id, select } = params;
172
+ const url = `/v1/data/namespaces/${this.namespace}/objects/${object_name}/records/${record_id}`;
173
+
174
+ this.log(LoggerLevel.info, `[单条查询记录] 🔍 开始查询 record_id: ${record_id}`);
175
+
176
+ const res = await functionLimiter(async () => {
177
+ await this.ensureTokenValid();
178
+
179
+ const response = await this.axiosInstance.post(url, { select }, { headers: { Authorization: `${this.accessToken}` } });
180
+
181
+ this.log(LoggerLevel.info, `[单条查询记录] 🔍 record_id: ${record_id} 查询完成,返回 code: ${response.data.code}`);
182
+ return response.data;
183
+ });
184
+
185
+ return res;
186
+ },
187
+
188
+ /**
189
+ * records_query 接口
190
+ * @param params 请求参数
191
+ * @returns 接口返回结果
192
+ */
193
+ records: async (params: RecordsQueryParams): Promise<any> => {
194
+ const { object_name, data } = params;
195
+ await this.ensureTokenValid();
196
+
197
+ const url = `/v1/data/namespaces/${this.namespace}/objects/${object_name}/records_query`;
198
+
199
+ const res = await this.axiosInstance.post(url, data, {
200
+ headers: { Authorization: `${this.accessToken}` }
201
+ });
202
+
203
+ this.log(LoggerLevel.debug, `[批量查询记录] 🔍 records_query 调用完成,object_name: ${object_name}`);
204
+ return res.data;
205
+ },
206
+
207
+ /**
208
+ * 分页查询所有记录
209
+ * @param params 请求参数
210
+ * @returns { total, items }
211
+ */
212
+ recordsWithIterator: async (params: RecordsQueryParams): Promise<{ total: number; items: any[] }> => {
213
+ const { object_name, data } = params;
214
+
215
+ let results: any[] = [];
216
+ let nextPageToken: string | undefined = '';
217
+ let total = 0;
218
+ let page = 0;
219
+
220
+ do {
221
+ const pageRes = await functionLimiter(async () => {
222
+ const mergedData = { ...data, page_token: nextPageToken || '' };
223
+
224
+ const res = await this.object.search.records({
225
+ object_name,
226
+ data: mergedData
227
+ });
228
+
229
+ page += 1;
230
+
231
+ if (res.data && Array.isArray(res.data.items)) {
232
+ results = results.concat(res.data.items);
233
+ }
234
+
235
+ if (page === 1) {
236
+ total = res.data.total || 0;
237
+ this.log(LoggerLevel.info, '[批量查询记录] 🔍 接口返回 total:', total);
238
+ }
239
+
240
+ nextPageToken = res.data.next_page_token;
241
+
242
+ this.log(LoggerLevel.debug, `[批量查询记录] 🔍 第 ${page} 页查询完成,items.length: ${res.data.items.length}`);
243
+ return res;
244
+ });
245
+ } while (nextPageToken);
246
+
247
+ return { total, items: results };
248
+ }
249
+ },
250
+
251
+ update: {
252
+ /**
253
+ * 单条更新
254
+ * @param params 请求参数
255
+ * @returns 接口返回结果
256
+ */
257
+ record: async (params: { object_name: string; record_id: string; record: any }): Promise<any> => {
258
+ const { object_name, record_id, record } = params;
259
+ const url = `/v1/data/namespaces/${this.namespace}/objects/${object_name}/records/${record_id}`;
260
+
261
+ this.log(LoggerLevel.info, `[单条更新记录] 💾 开始更新 record_id: ${record_id}`);
262
+
263
+ const res = await functionLimiter(async () => {
264
+ await this.ensureTokenValid();
265
+
266
+ const response = await this.axiosInstance.patch(url, { record }, { headers: { Authorization: `${this.accessToken}` } });
267
+
268
+ this.log(LoggerLevel.info, `[单条更新记录] 💾 record_id: ${record_id} 更新完成,返回 code: ${response.data.code}`);
269
+ return response.data;
270
+ });
271
+
272
+ return res;
273
+ },
274
+
275
+ /**
276
+ * 批量更新
277
+ * @param params 请求参数
278
+ * @returns 所有子请求的返回结果数组
279
+ */
280
+ recordsBatchUpdate: async (params: { object_name: string; records: any[] }): Promise<any[]> => {
281
+ const { object_name, records } = params;
282
+ const url = `/v1/data/namespaces/${this.namespace}/objects/${object_name}/records_batch`;
283
+
284
+ const chunkSize = 100;
285
+ const chunks: any[][] = [];
286
+ for (let i = 0; i < records.length; i += chunkSize) {
287
+ chunks.push(records.slice(i, i + chunkSize));
288
+ }
289
+
290
+ this.log(LoggerLevel.info, `[批量更新记录] 💾 总共 ${records.length} 条记录,拆分为 ${chunks.length} 组,每组最多 ${chunkSize} 条`);
291
+
292
+ const results: any[] = [];
293
+ for (const [index, chunk] of chunks.entries()) {
294
+ this.log(LoggerLevel.info, `[批量更新记录] 💾 开始更新第 ${index + 1} 组,共 ${chunk.length} 条`);
295
+
296
+ const res = await functionLimiter(async () => {
297
+ await this.ensureTokenValid();
298
+
299
+ const response = await this.axiosInstance.patch(url, { records: chunk }, { headers: { Authorization: `${this.accessToken}` } });
300
+
301
+ this.log(LoggerLevel.info, `[批量更新记录] 💾 第 ${index + 1} 组更新完成,返回 code: ${response.data.code}`);
302
+ return response.data;
303
+ });
304
+
305
+ results.push(res);
306
+ }
307
+
308
+ return results;
309
+ }
310
+ },
311
+
312
+ delete: {
313
+ /**
314
+ * 单条删除
315
+ * @param params 请求参数
316
+ * @returns 接口返回结果
317
+ */
318
+ record: async (params: { object_name: string; record_id: string }): Promise<any> => {
319
+ const { object_name, record_id } = params;
320
+ const url = `/v1/data/namespaces/${this.namespace}/objects/${object_name}/records/${record_id}`;
321
+
322
+ this.log(LoggerLevel.info, `[单条删除记录] 🗑️ 开始删除 record_id: ${record_id}`);
323
+
324
+ const res = await functionLimiter(async () => {
325
+ await this.ensureTokenValid();
326
+
327
+ const response = await this.axiosInstance.delete(url, {
328
+ headers: { Authorization: `${this.accessToken}` }
329
+ });
330
+
331
+ this.log(LoggerLevel.info, `[单条删除记录] 🗑️ record_id: ${record_id} 删除完成,返回 code: ${response.data.code}`);
332
+ return response.data;
333
+ });
334
+
335
+ return res;
336
+ },
337
+
338
+ /**
339
+ * 批量删除
340
+ * @param params 请求参数
341
+ * @returns 所有子请求的返回结果数组
342
+ */
343
+ recordsBatchDelete: async (params: { object_name: string; ids: string[] }): Promise<any[]> => {
344
+ const { object_name, ids } = params;
345
+ const url = `/v1/data/namespaces/${this.namespace}/objects/${object_name}/records_batch`;
346
+
347
+ const chunkSize = 100;
348
+ const chunks: string[][] = [];
349
+ for (let i = 0; i < ids.length; i += chunkSize) {
350
+ chunks.push(ids.slice(i, i + chunkSize));
351
+ }
352
+
353
+ this.log(LoggerLevel.info, `[批量删除记录] 🗑️ 总共 ${ids.length} 条记录,拆分为 ${chunks.length} 组,每组最多 ${chunkSize} 条`);
354
+
355
+ const results: any[] = [];
356
+ for (const [index, chunk] of chunks.entries()) {
357
+ this.log(LoggerLevel.info, `[批量删除记录] 🗑️ 开始删除第 ${index + 1} 组,共 ${chunk.length} 条`);
358
+
359
+ const res = await functionLimiter(async () => {
360
+ await this.ensureTokenValid();
361
+
362
+ const response = await this.axiosInstance.delete(url, {
363
+ headers: { Authorization: `${this.accessToken}` },
364
+ data: { ids: chunk }
365
+ });
366
+
367
+ this.log(LoggerLevel.info, `[批量删除记录] 🗑️ 第 ${index + 1} 组删除完成,返回 code: ${response.data.code}`);
368
+ return response.data;
369
+ });
370
+
371
+ results.push(res);
372
+ }
373
+
374
+ return results;
375
+ }
376
+ }
377
+ };
378
+ }
379
+
380
+ export const apaas = {
381
+ Client
382
+ };
package/src/limiter.ts ADDED
@@ -0,0 +1,29 @@
1
+ import Bottleneck from 'bottleneck';
2
+
3
+ /**
4
+ * 默认 apaas 限流配置
5
+ */
6
+ export const apaasLimiterOptions = {
7
+ minTime: 200, // 每秒最多发起 5 个数据库操作
8
+ reservoir: 20, // 最多同时查询 50 个数据库操作
9
+ reservoirRefreshAmount: 20, // 每次查询完毕后,重置为 50 个数据库操作
10
+ reservoirRefreshInterval: 1000 // 重置时间间隔为 1 秒
11
+ };
12
+
13
+ /**
14
+ * 创建限流器
15
+ * @param fn 被限流函数
16
+ * @param options 自定义限流配置
17
+ * @returns 包装后的限流函数
18
+ */
19
+ export async function functionLimiter<T>(fn: () => Promise<T>, options: Partial<Bottleneck.ConstructorOptions> = {}): Promise<T> {
20
+ const limiter = new Bottleneck({
21
+ minTime: options.minTime || apaasLimiterOptions.minTime,
22
+ reservoir: options.reservoir || apaasLimiterOptions.reservoir,
23
+ reservoirRefreshAmount: options.reservoirRefreshAmount || apaasLimiterOptions.reservoirRefreshAmount,
24
+ reservoirRefreshInterval: options.reservoirRefreshInterval || apaasLimiterOptions.reservoirRefreshInterval
25
+ });
26
+
27
+ const wrapped = limiter.wrap(fn);
28
+ return wrapped();
29
+ }
package/src/logger.ts ADDED
@@ -0,0 +1,11 @@
1
+ /**
2
+ * 日志等级枚举
3
+ */
4
+ export enum LoggerLevel {
5
+ fatal = 0,
6
+ error = 1,
7
+ warn = 2,
8
+ info = 3,
9
+ debug = 4,
10
+ trace = 5,
11
+ }