koishi-plugin-wordpress-notifier 2.9.1 → 2.9.4

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/lib/wordpress.js CHANGED
@@ -1,23 +1,103 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
37
  };
5
38
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.WordPressService = void 0;
39
+ exports.WordPressService = exports.AuthCooldownError = void 0;
7
40
  const axios_1 = __importDefault(require("axios"));
41
+ // 动态导入 p-limit
42
+ let pLimit = null;
43
+ // 异步初始化 p-limit
44
+ async function initPLimit() {
45
+ try {
46
+ const module = await Promise.resolve().then(() => __importStar(require('p-limit')));
47
+ pLimit = module.default || module;
48
+ }
49
+ catch (error) {
50
+ // 降级为内部队列实现
51
+ pLimit = null;
52
+ }
53
+ }
54
+ // 立即初始化
55
+ initPLimit();
56
+ // 自定义认证冷却错误
57
+ class AuthCooldownError extends Error {
58
+ constructor(message) {
59
+ super(message);
60
+ this.name = 'AuthCooldownError';
61
+ }
62
+ }
63
+ exports.AuthCooldownError = AuthCooldownError;
8
64
  // 简化实现,避免继承 Service 类的问题
9
65
  class WordPressService {
10
66
  constructor(ctx, config) {
11
67
  this.ctx = ctx;
12
- this.isAuthenticated = true; // 认证状态缓存
13
- this.authFailureTime = 0; // 认证失败时间戳
14
- this.AUTH_FAILURE_COOLDOWN = 5 * 60 * 1000; // 认证失败冷却时间(5分钟)
15
- this.interceptorRegistered = false; // 拦截器注册状态
16
- this.MAX_RETRIES = 2; // 最大重试次数
17
- this.retryCounts = new WeakMap(); // 用于跟踪请求重试次数
68
+ this.isAuthenticated = true;
69
+ this.authFailureTime = 0;
70
+ this.AUTH_FAILURE_COOLDOWN = 5 * 60 * 1000;
71
+ this.interceptorId = null;
72
+ this.MAX_RETRIES = 2;
73
+ this.MAX_CONCURRENT_REQUESTS = 5;
74
+ this.activeRequests = 0;
75
+ this.requestQueue = [];
76
+ this.cache = new Map();
77
+ this.CACHE_DURATION = 10 * 1000; // 10s 缓存
78
+ this.cacheCleanupInterval = null;
79
+ // 监控统计
80
+ this.stats = {
81
+ totalRequests: 0,
82
+ successfulRequests: 0,
83
+ failedRequests: 0,
84
+ totalResponseTime: 0,
85
+ commandCount: 0
86
+ };
18
87
  this.config = config;
88
+ // 初始化时使用内部队列实现,p-limit 会在异步初始化后替换
89
+ this.limit = null;
90
+ this.ctx.logger.info('初始化 WordPressService,使用内部队列实现');
19
91
  this.client = this.createClient();
20
92
  this.setupResponseInterceptor();
93
+ this.setupCacheCleanup();
94
+ // 等待 p-limit 初始化完成
95
+ initPLimit().then(() => {
96
+ if (pLimit) {
97
+ this.limit = pLimit(this.MAX_CONCURRENT_REQUESTS);
98
+ this.ctx.logger.info('p-limit 初始化成功,切换到 p-limit 实现');
99
+ }
100
+ });
21
101
  }
22
102
  createClient() {
23
103
  const { wordpressUrl, authType, username, password } = this.config;
@@ -25,33 +105,6 @@ class WordPressService {
25
105
  baseURL: wordpressUrl,
26
106
  timeout: 10000,
27
107
  });
28
- // 添加响应重试拦截器
29
- client.interceptors.response.use((response) => {
30
- // 请求成功,清除重试计数
31
- this.retryCounts.delete(response.config);
32
- return response;
33
- }, async (error) => {
34
- const { config } = error;
35
- // 如果不是网络错误或超时,不重试
36
- if (!error.code || !['ECONNABORTED', 'ETIMEDOUT', 'ECONNREFUSED', 'NETWORK_ERROR'].includes(error.code)) {
37
- return Promise.reject(error);
38
- }
39
- // 检查是否已经达到最大重试次数
40
- const currentRetryCount = this.retryCounts.get(config) || 0;
41
- const newRetryCount = currentRetryCount + 1;
42
- if (newRetryCount > this.MAX_RETRIES) {
43
- // 达到最大重试次数,清除计数并拒绝
44
- this.retryCounts.delete(config);
45
- return Promise.reject(error);
46
- }
47
- // 更新重试计数
48
- this.retryCounts.set(config, newRetryCount);
49
- // 指数退避策略
50
- const delay = Math.pow(2, newRetryCount - 1) * 1000;
51
- this.ctx.logger.info(`Retrying request (${newRetryCount}/${this.MAX_RETRIES}) after ${delay}ms...`);
52
- await new Promise(resolve => setTimeout(resolve, delay));
53
- return client(config);
54
- });
55
108
  // 配置认证
56
109
  if (authType === 'basic' && username && password) {
57
110
  client.defaults.auth = {
@@ -60,67 +113,219 @@ class WordPressService {
60
113
  };
61
114
  }
62
115
  else if (authType === 'application-password' && username && password) {
63
- // Use btoa for better compatibility instead of Buffer
64
116
  const credentials = `${username}:${password}`;
65
- const encodedCredentials = typeof btoa === 'function'
66
- ? btoa(credentials)
67
- : Buffer.from(credentials).toString('base64');
68
- client.defaults.headers.common['Authorization'] = `Basic ${encodedCredentials}`;
117
+ let encodedCredentials;
118
+ try {
119
+ if (typeof Buffer !== 'undefined') {
120
+ encodedCredentials = Buffer.from(credentials).toString('base64');
121
+ }
122
+ else if (typeof btoa === 'function') {
123
+ encodedCredentials = btoa(credentials);
124
+ }
125
+ else {
126
+ throw new Error('No Base64 encoding method available');
127
+ }
128
+ client.defaults.headers.common['Authorization'] = `Basic ${encodedCredentials}`;
129
+ }
130
+ catch (error) {
131
+ this.ctx.logger.error('Failed to configure authentication:', error);
132
+ }
69
133
  }
70
134
  return client;
71
135
  }
72
136
  // 设置响应拦截器
73
137
  setupResponseInterceptor() {
74
- if (this.interceptorRegistered) {
75
- return; // 拦截器已经注册,避免重复注册
138
+ if (this.interceptorId !== null) {
139
+ return;
76
140
  }
77
- this.client.interceptors.response.use((response) => {
78
- // 认证成功,重置认证状态
141
+ this.interceptorId = this.client.interceptors.response.use((response) => {
79
142
  this.isAuthenticated = true;
80
143
  this.authFailureTime = 0;
81
144
  return response;
82
145
  }, (error) => {
83
- // 检测认证失败
84
- if (error.response && (error.response.status === 401 || error.response.status === 403)) {
85
- this.isAuthenticated = false;
86
- this.authFailureTime = Date.now();
87
- this.ctx.logger.warn('WordPress API authentication failed, blocking subsequent requests');
146
+ if (error.response) {
147
+ const status = error.response.status;
148
+ const url = error.config?.url || '';
149
+ if (status === 401 && (url.includes('/posts') || url.includes('/users'))) {
150
+ this.isAuthenticated = false;
151
+ this.authFailureTime = Date.now();
152
+ this.ctx.logger.warn('WordPress API authentication failed for public endpoint, blocking subsequent requests');
153
+ }
154
+ else if (status === 403) {
155
+ this.ctx.logger.warn(`WordPress API permission denied for resource: ${url}`);
156
+ }
88
157
  }
89
158
  return Promise.reject(error);
90
159
  });
91
- this.interceptorRegistered = true;
160
+ }
161
+ // 移除响应拦截器
162
+ removeResponseInterceptor() {
163
+ if (this.interceptorId !== null) {
164
+ this.client.interceptors.response.eject(this.interceptorId);
165
+ this.interceptorId = null;
166
+ }
92
167
  }
93
168
  // 检查认证状态
94
169
  checkAuthStatus() {
95
- // 如果未认证且在冷却期内,阻止请求
96
170
  if (!this.isAuthenticated && (Date.now() - this.authFailureTime) < this.AUTH_FAILURE_COOLDOWN) {
97
171
  this.ctx.logger.warn('Skipping request due to authentication failure cooldown');
98
172
  return false;
99
173
  }
100
- // 冷却期过后,尝试重新认证
101
174
  if (!this.isAuthenticated && (Date.now() - this.authFailureTime) >= this.AUTH_FAILURE_COOLDOWN) {
102
175
  this.isAuthenticated = true;
103
176
  this.ctx.logger.info('Authentication failure cooldown expired, allowing new requests');
104
177
  }
105
178
  return true;
106
179
  }
107
- // 获取最新文章
108
- async getLatestPosts(perPage = 10, page = 1) {
180
+ // 执行请求(带并发控制和重试)
181
+ async executeWithConcurrencyControl(requestFn, config) {
109
182
  if (!this.checkAuthStatus()) {
110
- throw new Error('Authentication failed and in cooldown period');
183
+ throw new AuthCooldownError('Authentication failed and in cooldown period');
111
184
  }
112
- try {
113
- const response = await this.client.get(`${this.config.apiEndpoint || '/wp-json/wp/v2'}/posts`, {
114
- params: {
115
- per_page: Math.min(perPage, 100), // 限制每页最多100条
116
- page: Math.max(1, page), // 确保页码至少为1
117
- orderby: 'date',
118
- order: 'desc',
119
- _embed: true // 包含关联数据,如作者信息
185
+ // 使用 p-limit 或内部队列实现
186
+ if (this.limit) {
187
+ return this.limit(async () => {
188
+ return this.executeWithRetry(requestFn, config);
189
+ });
190
+ }
191
+ else {
192
+ // 使用内部队列实现
193
+ return new Promise((resolve, reject) => {
194
+ const executeRequest = async () => {
195
+ try {
196
+ this.activeRequests++;
197
+ const result = await this.executeWithRetry(requestFn, config);
198
+ resolve(result);
199
+ }
200
+ catch (error) {
201
+ reject(error);
202
+ }
203
+ finally {
204
+ this.activeRequests--;
205
+ this.processNextRequest();
206
+ }
207
+ };
208
+ if (this.activeRequests < this.MAX_CONCURRENT_REQUESTS) {
209
+ executeRequest();
210
+ }
211
+ else {
212
+ this.ctx.logger.info(`Request queued. Active: ${this.activeRequests}, Queue: ${this.requestQueue.length + 1}`);
213
+ this.requestQueue.push(executeRequest);
120
214
  }
121
215
  });
122
- // 处理响应数据,确保数据结构一致
123
- return response.data.map((post) => ({
216
+ }
217
+ }
218
+ // 执行请求(带重试)
219
+ async executeWithRetry(requestFn, config) {
220
+ let lastError;
221
+ for (let attempt = 0; attempt <= this.MAX_RETRIES; attempt++) {
222
+ const startTime = Date.now();
223
+ this.stats.totalRequests++;
224
+ try {
225
+ const result = await requestFn();
226
+ const endTime = Date.now();
227
+ this.stats.successfulRequests++;
228
+ this.stats.totalResponseTime += endTime - startTime;
229
+ return result;
230
+ }
231
+ catch (error) {
232
+ const endTime = Date.now();
233
+ this.stats.failedRequests++;
234
+ this.stats.totalResponseTime += endTime - startTime;
235
+ lastError = error;
236
+ // 仅对 GET 请求进行重试,防止重复写操作
237
+ const isGetRequest = !config || (config.method?.toUpperCase() ?? 'GET') === 'GET';
238
+ if (attempt < this.MAX_RETRIES && this.isNetworkError(error) && isGetRequest) {
239
+ const delay = Math.pow(2, attempt) * 1000;
240
+ this.ctx.logger.info(`Retrying request (${attempt + 1}/${this.MAX_RETRIES}) after ${delay}ms...`);
241
+ await new Promise(resolve => setTimeout(resolve, delay));
242
+ }
243
+ else {
244
+ throw error;
245
+ }
246
+ }
247
+ }
248
+ throw lastError;
249
+ }
250
+ // 处理下一个请求
251
+ processNextRequest() {
252
+ if (this.requestQueue.length > 0 && this.activeRequests < this.MAX_CONCURRENT_REQUESTS) {
253
+ const nextRequest = this.requestQueue.shift();
254
+ if (nextRequest) {
255
+ this.ctx.logger.info(`Processing next request. Active: ${this.activeRequests}, Queue: ${this.requestQueue.length}`);
256
+ nextRequest();
257
+ }
258
+ }
259
+ }
260
+ // 检查是否为网络错误
261
+ isNetworkError(error) {
262
+ const axiosError = error;
263
+ return !axiosError.response && (axiosError.code === 'ECONNABORTED' ||
264
+ axiosError.code === 'ETIMEDOUT' ||
265
+ axiosError.code === 'ECONNREFUSED' ||
266
+ axiosError.code === 'NETWORK_ERROR');
267
+ }
268
+ // 获取缓存
269
+ getCache(key) {
270
+ const item = this.cache.get(key);
271
+ if (item && (Date.now() - item.timestamp) < this.CACHE_DURATION) {
272
+ return item.data;
273
+ }
274
+ this.cache.delete(key);
275
+ return null;
276
+ }
277
+ // 设置缓存
278
+ setCache(key, data) {
279
+ this.cache.set(key, {
280
+ data,
281
+ timestamp: Date.now()
282
+ });
283
+ // 清理过期缓存
284
+ this.cleanupCache();
285
+ }
286
+ // 设置缓存定时清理
287
+ setupCacheCleanup() {
288
+ // 每30秒清理一次过期缓存
289
+ this.cacheCleanupInterval = setInterval(() => {
290
+ this.cleanupCache();
291
+ }, 30 * 1000);
292
+ }
293
+ // 清理过期缓存
294
+ cleanupCache() {
295
+ const now = Date.now();
296
+ let cleanedCount = 0;
297
+ for (const [key, item] of this.cache.entries()) {
298
+ if (now - item.timestamp >= this.CACHE_DURATION) {
299
+ this.cache.delete(key);
300
+ cleanedCount++;
301
+ }
302
+ }
303
+ if (cleanedCount > 0) {
304
+ this.ctx.logger.debug(`Cleaned ${cleanedCount} expired cache items`);
305
+ }
306
+ }
307
+ // 获取最新文章
308
+ async getLatestPosts(perPage = 10, page = 1) {
309
+ this.stats.commandCount++;
310
+ const cacheKey = `latest-posts-${perPage}-${page}`;
311
+ const cachedData = this.getCache(cacheKey);
312
+ if (cachedData) {
313
+ return cachedData;
314
+ }
315
+ try {
316
+ // 简化 config,只保留 params
317
+ const requestParams = {
318
+ per_page: Math.min(perPage, 100),
319
+ page: Math.max(1, page),
320
+ orderby: 'date',
321
+ order: 'desc',
322
+ _embed: true
323
+ };
324
+ // 修复:axios.get 第二个参数只传 params
325
+ const response = await this.executeWithConcurrencyControl(() => this.client.get(`${this.config.apiEndpoint || '/wp-json/wp/v2'}/posts`, {
326
+ params: requestParams
327
+ }), { method: 'GET', params: requestParams }); // 仅传递必要的 config 信息
328
+ const posts = response.data.map((post) => ({
124
329
  id: post.id,
125
330
  date: post.date,
126
331
  modified: post.modified,
@@ -137,6 +342,8 @@ class WordPressService {
137
342
  author_name: post._embedded?.author?.[0]?.name || '未知作者',
138
343
  featured_image: post._embedded?.['wp:featuredmedia']?.[0]?.source_url
139
344
  }));
345
+ this.setCache(cacheKey, posts);
346
+ return posts;
140
347
  }
141
348
  catch (error) {
142
349
  this.logError('Failed to get latest posts:', error);
@@ -145,17 +352,23 @@ class WordPressService {
145
352
  }
146
353
  // 获取文章详情
147
354
  async getPostById(id) {
148
- if (!this.checkAuthStatus()) {
149
- throw new Error('Authentication failed and in cooldown period');
355
+ this.stats.commandCount++;
356
+ const cacheKey = `post-${id}`;
357
+ const cachedData = this.getCache(cacheKey);
358
+ if (cachedData) {
359
+ return cachedData;
150
360
  }
151
361
  try {
152
- const response = await this.client.get(`${this.config.apiEndpoint || '/wp-json/wp/v2'}/posts/${id}`, {
153
- params: {
154
- _embed: true // 包含关联数据
155
- }
156
- });
362
+ // 简化 config,只保留 params
363
+ const requestParams = {
364
+ _embed: true
365
+ };
366
+ // 修复:axios.get 第二个参数只传 params
367
+ const response = await this.executeWithConcurrencyControl(() => this.client.get(`${this.config.apiEndpoint || '/wp-json/wp/v2'}/posts/${id}`, {
368
+ params: requestParams
369
+ }), { method: 'GET', params: requestParams }); // 仅传递必要的 config 信息
157
370
  const post = response.data;
158
- return {
371
+ const result = {
159
372
  id: post.id,
160
373
  date: post.date,
161
374
  modified: post.modified,
@@ -172,129 +385,117 @@ class WordPressService {
172
385
  author_name: post._embedded?.author?.[0]?.name || '未知作者',
173
386
  featured_image: post._embedded?.['wp:featuredmedia']?.[0]?.source_url
174
387
  };
388
+ this.setCache(cacheKey, result);
389
+ return result;
175
390
  }
176
391
  catch (error) {
177
392
  this.logError(`Failed to get post ${id}:`, error);
178
393
  throw error;
179
394
  }
180
395
  }
181
- // 获取用户列表
182
- async getUsers() {
183
- if (!this.checkAuthStatus()) {
184
- throw new Error('Authentication failed and in cooldown period');
396
+ // 获取最新注册的用户
397
+ async getLatestUsers(perPage = 10, page = 1) {
398
+ this.stats.commandCount++;
399
+ const cacheKey = `latest-users-${perPage}-${page}`;
400
+ const cachedData = this.getCache(cacheKey);
401
+ if (cachedData) {
402
+ return cachedData;
185
403
  }
186
404
  try {
187
- const response = await this.client.get(`${this.config.apiEndpoint || '/wp-json/wp/v2'}/users`, {
188
- params: {
189
- per_page: 100 // 限制最多100个用户
190
- }
191
- });
192
- return response.data.map((user) => ({
405
+ // 获取最新注册的用户
406
+ const requestParams = {
407
+ per_page: Math.min(perPage, 100),
408
+ page: Math.max(1, page),
409
+ orderby: 'registered_date',
410
+ order: 'desc'
411
+ };
412
+ const response = await this.executeWithConcurrencyControl(() => this.client.get(`${this.config.apiEndpoint || '/wp-json/wp/v2'}/users`, {
413
+ params: requestParams
414
+ }), { method: 'GET', params: requestParams });
415
+ const users = response.data.map((user) => ({
193
416
  id: user.id,
194
417
  name: user.name,
195
- username: user.username,
418
+ registered_date: user.registered_date,
196
419
  email: user.email,
197
- registered_date: user.register_date || user.registered_date,
198
- updated_date: user.updated_date || user.modified || user.register_date || user.registered_date,
199
- display_name: user.display_name || user.name
420
+ link: user.link
200
421
  }));
422
+ this.setCache(cacheKey, users);
423
+ return users;
201
424
  }
202
425
  catch (error) {
203
- this.logError('Failed to get users:', error);
426
+ this.logError('Failed to get latest users:', error);
204
427
  throw error;
205
428
  }
206
429
  }
207
- // 获取单个用户信息
208
- async getUser(userId) {
209
- if (!this.checkAuthStatus()) {
210
- throw new Error('Authentication failed and in cooldown period');
211
- }
212
- try {
213
- const response = await this.client.get(`${this.config.apiEndpoint || '/wp-json/wp/v2'}/users/${userId}`);
214
- const user = response.data;
215
- return {
216
- id: user.id,
217
- name: user.name,
218
- username: user.username,
219
- email: user.email,
220
- registered_date: user.register_date || user.registered_date,
221
- updated_date: user.updated_date || user.modified || user.register_date || user.registered_date,
222
- display_name: user.display_name || user.name
223
- };
224
- }
225
- catch (error) {
226
- this.logError(`Failed to get user ${userId}:`, error);
227
- return null;
228
- }
430
+ // 获取服务状态
431
+ getStatus() {
432
+ const successRate = this.stats.totalRequests > 0
433
+ ? (this.stats.successfulRequests / this.stats.totalRequests * 100).toFixed(2)
434
+ : '0.00';
435
+ const avgResponseTime = this.stats.successfulRequests > 0
436
+ ? (this.stats.totalResponseTime / this.stats.successfulRequests).toFixed(2)
437
+ : '0.00';
438
+ return {
439
+ wordpressUrl: this.config.wordpressUrl,
440
+ apiEndpoint: this.config.apiEndpoint || '/wp-json/wp/v2',
441
+ isAuthenticated: this.isAuthenticated,
442
+ inCooldown: !this.isAuthenticated && (Date.now() - this.authFailureTime) < this.AUTH_FAILURE_COOLDOWN,
443
+ cooldownTimeRemaining: Math.max(0, Math.floor((this.AUTH_FAILURE_COOLDOWN - (Date.now() - this.authFailureTime)) / 1000)),
444
+ stats: {
445
+ totalRequests: this.stats.totalRequests,
446
+ successfulRequests: this.stats.successfulRequests,
447
+ failedRequests: this.stats.failedRequests,
448
+ successRate: `${successRate}%`,
449
+ averageResponseTime: `${avgResponseTime}ms`,
450
+ commandCount: this.stats.commandCount
451
+ },
452
+ cache: {
453
+ size: this.cache.size,
454
+ duration: this.CACHE_DURATION / 1000,
455
+ cleanupInterval: 30 // 30秒
456
+ },
457
+ concurrency: {
458
+ max: this.MAX_CONCURRENT_REQUESTS
459
+ },
460
+ features: {
461
+ retryEnabled: true,
462
+ retryLimit: this.MAX_RETRIES,
463
+ cacheEnabled: true,
464
+ concurrencyControl: true
465
+ }
466
+ };
229
467
  }
230
- // 搜索文章
231
- async searchPosts(query, perPage = 10) {
232
- if (!this.checkAuthStatus()) {
233
- throw new Error('Authentication failed and in cooldown period');
234
- }
235
- try {
236
- const response = await this.client.get(`${this.config.apiEndpoint || '/wp-json/wp/v2'}/posts`, {
237
- params: {
238
- search: query,
239
- per_page: Math.min(perPage, 100),
240
- orderby: 'relevance',
241
- _embed: true
242
- }
243
- });
244
- return response.data.map((post) => ({
245
- id: post.id,
246
- date: post.date,
247
- modified: post.modified,
248
- slug: post.slug,
249
- status: post.status,
250
- type: post.type,
251
- title: post.title,
252
- content: post.content,
253
- excerpt: post.excerpt,
254
- author: post.author,
255
- featured_media: post.featured_media,
256
- link: post.link,
257
- _embedded: post._embedded,
258
- author_name: post._embedded?.author?.[0]?.name || '未知作者',
259
- featured_image: post._embedded?.['wp:featuredmedia']?.[0]?.source_url
260
- }));
468
+ // 清理资源
469
+ cleanup() {
470
+ this.removeResponseInterceptor();
471
+ this.cache.clear();
472
+ // 清理定时任务
473
+ if (this.cacheCleanupInterval) {
474
+ clearInterval(this.cacheCleanupInterval);
475
+ this.cacheCleanupInterval = null;
261
476
  }
262
- catch (error) {
263
- this.logError('Failed to search posts:', error);
264
- throw error;
477
+ // 重置所有并发相关状态
478
+ this.requestQueue = [];
479
+ this.activeRequests = 0;
480
+ // 重置 p-limit(可选)
481
+ if (pLimit) {
482
+ this.limit = pLimit(this.MAX_CONCURRENT_REQUESTS);
265
483
  }
484
+ this.ctx.logger.info('WordPressService resources cleaned up');
266
485
  }
267
- // 获取分类列表
268
- async getCategories() {
269
- if (!this.checkAuthStatus()) {
270
- throw new Error('Authentication failed and in cooldown period');
271
- }
272
- try {
273
- const response = await this.client.get(`${this.config.apiEndpoint || '/wp-json/wp/v2'}/categories`);
274
- return response.data;
275
- }
276
- catch (error) {
277
- this.logError('Failed to get categories:', error);
278
- throw error;
279
- }
280
- }
281
- // 获取标签列表
282
- async getTags() {
283
- if (!this.checkAuthStatus()) {
284
- throw new Error('Authentication failed and in cooldown period');
285
- }
286
- try {
287
- const response = await this.client.get(`${this.config.apiEndpoint || '/wp-json/wp/v2'}/tags`);
288
- return response.data;
289
- }
290
- catch (error) {
291
- this.logError('Failed to get tags:', error);
292
- throw error;
293
- }
486
+ // 重置统计数据
487
+ resetStats() {
488
+ this.stats = {
489
+ totalRequests: 0,
490
+ successfulRequests: 0,
491
+ failedRequests: 0,
492
+ totalResponseTime: 0,
493
+ commandCount: 0
494
+ };
495
+ this.ctx.logger.info('Statistics reset');
294
496
  }
295
497
  // 脱敏日志记录
296
498
  logError(message, error) {
297
- // 检查并脱敏错误对象中的敏感信息
298
499
  const sanitizedError = this.sanitizeError(error);
299
500
  this.ctx.logger.error(message, sanitizedError);
300
501
  }
@@ -302,9 +503,7 @@ class WordPressService {
302
503
  sanitizeError(error) {
303
504
  if (!error)
304
505
  return error;
305
- // 创建错误副本
306
506
  const sanitized = { ...error };
307
- // 脱敏 config 中的敏感信息
308
507
  if (sanitized.config) {
309
508
  sanitized.config = {
310
509
  ...sanitized.config,
@@ -315,7 +514,6 @@ class WordPressService {
315
514
  } : undefined
316
515
  };
317
516
  }
318
- // 脱敏 response 中的敏感信息
319
517
  if (sanitized.response) {
320
518
  sanitized.response = {
321
519
  ...sanitized.response,
@@ -324,72 +522,5 @@ class WordPressService {
324
522
  }
325
523
  return sanitized;
326
524
  }
327
- // 重置认证状态
328
- resetAuthStatus() {
329
- this.isAuthenticated = true;
330
- this.authFailureTime = 0;
331
- this.ctx.logger.info('Authentication status reset');
332
- }
333
- // 获取认证状态
334
- getAuthStatus() {
335
- return this.isAuthenticated;
336
- }
337
- // 检查 WordPress 连接状态
338
- async checkConnection() {
339
- try {
340
- // 尝试访问 WordPress 站点的根目录
341
- const rootResponse = await this.client.get('/', {
342
- timeout: 5000
343
- });
344
- // 尝试访问 WordPress REST API
345
- const apiResponse = await this.client.get(`${this.config.apiEndpoint || '/wp-json/wp/v2'}/posts`, {
346
- params: {
347
- per_page: 1,
348
- _embed: false
349
- },
350
- timeout: 5000
351
- });
352
- return {
353
- success: true,
354
- message: 'WordPress site is accessible and API is working',
355
- details: {
356
- rootStatus: rootResponse.status,
357
- apiStatus: apiResponse.status,
358
- apiVersion: this.config.apiEndpoint || 'wp/v2'
359
- }
360
- };
361
- }
362
- catch (error) {
363
- this.logError('Connection check failed:', error);
364
- if (error.code === 'ECONNREFUSED') {
365
- return {
366
- success: false,
367
- message: 'Connection refused: WordPress site is not reachable'
368
- };
369
- }
370
- else if (error.code === 'ETIMEDOUT') {
371
- return {
372
- success: false,
373
- message: 'Connection timed out: WordPress site is slow or unresponsive'
374
- };
375
- }
376
- else if (error.response?.status === 401 || error.response?.status === 403) {
377
- return {
378
- success: false,
379
- message: 'Authentication failed: Invalid credentials or insufficient permissions'
380
- };
381
- }
382
- else if (error.response?.status === 404) {
383
- return {
384
- success: false,
385
- message: 'API endpoint not found: WordPress REST API may not be enabled'
386
- };
387
- }
388
- return {
389
- success: false,
390
- message: `Connection failed: ${error.message || 'Unknown error'}`
391
- };
392
- }
393
- }
394
525
  }
395
526
  exports.WordPressService = WordPressService;