@jayfong/x-server 2.110.1 → 2.111.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/lib/_cjs/cli/cli.js +0 -0
- package/lib/_cjs/services/cache.js +158 -23
- package/lib/services/cache.d.ts +40 -2
- package/lib/services/cache.js +158 -23
- package/package.json +2 -1
package/lib/_cjs/cli/cli.js
CHANGED
|
File without changes
|
|
@@ -4,24 +4,46 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau
|
|
|
4
4
|
exports.__esModule = true;
|
|
5
5
|
exports.CacheService = void 0;
|
|
6
6
|
var _cuid = _interopRequireDefault(require("cuid"));
|
|
7
|
+
var _superjson = _interopRequireDefault(require("superjson"));
|
|
7
8
|
var _vtils = require("vtils");
|
|
8
9
|
var _x = require("../x");
|
|
10
|
+
const nativeJsonSerializer = {
|
|
11
|
+
stringify: JSON.stringify,
|
|
12
|
+
parse: JSON.parse
|
|
13
|
+
};
|
|
14
|
+
const superJsonSerializer = {
|
|
15
|
+
stringify: _superjson.default.stringify,
|
|
16
|
+
parse: _superjson.default.parse
|
|
17
|
+
};
|
|
9
18
|
class CacheService {
|
|
10
19
|
constructor(options) {
|
|
11
20
|
this.options = options;
|
|
12
21
|
this.serviceName = 'cache';
|
|
13
22
|
this.prefix = void 0;
|
|
23
|
+
this.hasMemoryCache = false;
|
|
24
|
+
this.hasMemoryCacheOnly = false;
|
|
14
25
|
this.memoryCache = void 0;
|
|
26
|
+
this.serializer = void 0;
|
|
15
27
|
this.prefix = `${_x.x.appId}:`;
|
|
16
|
-
this.
|
|
28
|
+
this.hasMemoryCache = !!options.memory;
|
|
29
|
+
this.hasMemoryCacheOnly = options.memory === 'only';
|
|
30
|
+
this.serializer = options.serializer == null || options.serializer === 'nativeJson' ? nativeJsonSerializer : options.serializer === 'superJson' ? superJsonSerializer : options.serializer;
|
|
31
|
+
this.memoryCache = !this.hasMemoryCache ? undefined : new Map();
|
|
17
32
|
}
|
|
18
33
|
toRedisKey(key) {
|
|
19
34
|
return `${this.prefix}${Array.isArray(key) ? key.join('_') : String(key)}`;
|
|
20
35
|
}
|
|
36
|
+
serialize(value) {
|
|
37
|
+
return this.serializer.stringify(value);
|
|
38
|
+
}
|
|
39
|
+
deserialize(value) {
|
|
40
|
+
return this.serializer.parse(value);
|
|
41
|
+
}
|
|
21
42
|
|
|
22
43
|
/**
|
|
23
44
|
* 设置缓存内容。对象类内容尽量避免使用,以免造成问题。
|
|
24
45
|
*
|
|
46
|
+
* @memory 支持
|
|
25
47
|
* @param key 键
|
|
26
48
|
* @param value 值
|
|
27
49
|
* @param ttlOrOptions 缓存时间或选项
|
|
@@ -35,11 +57,13 @@ class CacheService {
|
|
|
35
57
|
// Redis 的 TTL 不能小于等于 0
|
|
36
58
|
if (redisTtlMs > 0) {
|
|
37
59
|
const redisKey = this.toRedisKey(key);
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
60
|
+
if (!this.hasMemoryCacheOnly) {
|
|
61
|
+
const redisValue = this.serialize(value);
|
|
62
|
+
await _x.x.redis.set(redisKey, redisValue,
|
|
63
|
+
// 毫秒
|
|
64
|
+
'PX', redisTtlMs);
|
|
65
|
+
}
|
|
66
|
+
if (this.hasMemoryCache) {
|
|
43
67
|
this.memoryCache.set(redisKey, value);
|
|
44
68
|
}
|
|
45
69
|
}
|
|
@@ -50,6 +74,7 @@ class CacheService {
|
|
|
50
74
|
/**
|
|
51
75
|
* 存储值然后返回其对应的键,该键 URL 友好。
|
|
52
76
|
*
|
|
77
|
+
* @memory 支持
|
|
53
78
|
* @param value 值
|
|
54
79
|
* @param ttl 存活时间
|
|
55
80
|
*/
|
|
@@ -62,20 +87,25 @@ class CacheService {
|
|
|
62
87
|
/**
|
|
63
88
|
* 获取缓存内容。
|
|
64
89
|
*
|
|
90
|
+
* @memory 支持
|
|
65
91
|
* @param key 键
|
|
66
92
|
* @param options 选项
|
|
67
93
|
* @returns 返回获取到的缓存内容
|
|
68
94
|
*/
|
|
69
95
|
async get(key) {
|
|
70
96
|
const redisKey = this.toRedisKey(key);
|
|
71
|
-
if (this.
|
|
72
|
-
|
|
97
|
+
if (this.hasMemoryCache) {
|
|
98
|
+
if (this.memoryCache.has(redisKey)) {
|
|
99
|
+
return this.memoryCache.get(redisKey);
|
|
100
|
+
} else if (this.hasMemoryCacheOnly) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
73
103
|
}
|
|
74
104
|
const gotValue = await _x.x.redis.get(redisKey);
|
|
75
105
|
if (gotValue) {
|
|
76
106
|
try {
|
|
77
|
-
const parsedValue =
|
|
78
|
-
if (this.
|
|
107
|
+
const parsedValue = this.deserialize(gotValue);
|
|
108
|
+
if (this.hasMemoryCache) {
|
|
79
109
|
this.memoryCache.set(redisKey, parsedValue);
|
|
80
110
|
}
|
|
81
111
|
return parsedValue;
|
|
@@ -87,17 +117,47 @@ class CacheService {
|
|
|
87
117
|
/**
|
|
88
118
|
* 获取多个缓存内容。
|
|
89
119
|
*
|
|
120
|
+
* @memory 支持
|
|
90
121
|
* @param keys 键
|
|
91
122
|
* @returns 返回获取到的缓存内容
|
|
92
123
|
*/
|
|
93
124
|
async getMany(...keys) {
|
|
94
125
|
const redisKeys = keys.map(key => this.toRedisKey(key));
|
|
95
126
|
if (!redisKeys.length) return [];
|
|
127
|
+
if (this.hasMemoryCacheOnly) {
|
|
128
|
+
return redisKeys.map(redisKey => this.memoryCache.has(redisKey) ? this.memoryCache.get(redisKey) : null);
|
|
129
|
+
}
|
|
130
|
+
if (this.hasMemoryCache) {
|
|
131
|
+
const result = new Array(keys.length).fill(null);
|
|
132
|
+
const missIndexes = [];
|
|
133
|
+
const missRedisKeys = [];
|
|
134
|
+
for (let i = 0; i < redisKeys.length; i++) {
|
|
135
|
+
if (this.memoryCache.has(redisKeys[i])) {
|
|
136
|
+
result[i] = this.memoryCache.get(redisKeys[i]);
|
|
137
|
+
} else {
|
|
138
|
+
missIndexes.push(i);
|
|
139
|
+
missRedisKeys.push(redisKeys[i]);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (missRedisKeys.length) {
|
|
143
|
+
const gotValues = await _x.x.redis.mget(...missRedisKeys);
|
|
144
|
+
gotValues.forEach((value, idx) => {
|
|
145
|
+
if (value != null) {
|
|
146
|
+
try {
|
|
147
|
+
const parsed = this.deserialize(value);
|
|
148
|
+
this.memoryCache.set(missRedisKeys[idx], parsed);
|
|
149
|
+
result[missIndexes[idx]] = parsed;
|
|
150
|
+
} catch {}
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
return result;
|
|
155
|
+
}
|
|
96
156
|
const gotValues = await _x.x.redis.mget(...redisKeys);
|
|
97
157
|
return gotValues.map(value => {
|
|
98
158
|
if (value == null) return null;
|
|
99
159
|
try {
|
|
100
|
-
return
|
|
160
|
+
return this.deserialize(value);
|
|
101
161
|
} catch {
|
|
102
162
|
return null;
|
|
103
163
|
}
|
|
@@ -106,11 +166,16 @@ class CacheService {
|
|
|
106
166
|
|
|
107
167
|
/**
|
|
108
168
|
* 获取距离过期时间的毫秒数(未设置过期时间返回-1,键不存在返回-2)。
|
|
169
|
+
* memory=only 模式下内存缓存无 TTL 概念,始终返回-1。
|
|
109
170
|
*
|
|
171
|
+
* @memory 支持
|
|
110
172
|
* @param key 键
|
|
111
173
|
* @returns 返回距离过期时间的毫秒数
|
|
112
174
|
*/
|
|
113
175
|
async ttl(key) {
|
|
176
|
+
if (this.hasMemoryCacheOnly) {
|
|
177
|
+
return -1;
|
|
178
|
+
}
|
|
114
179
|
const redisKey = this.toRedisKey(key);
|
|
115
180
|
return _x.x.redis.pttl(redisKey);
|
|
116
181
|
}
|
|
@@ -118,6 +183,7 @@ class CacheService {
|
|
|
118
183
|
/**
|
|
119
184
|
* 缓存内容。
|
|
120
185
|
*
|
|
186
|
+
* @memory 支持
|
|
121
187
|
* @param key 键
|
|
122
188
|
* @param action 获取缓存内容的操作
|
|
123
189
|
* @param ttlOrOptions 缓存时间或选项
|
|
@@ -153,17 +219,29 @@ class CacheService {
|
|
|
153
219
|
/**
|
|
154
220
|
* 自增。
|
|
155
221
|
*
|
|
222
|
+
* @memory 支持
|
|
156
223
|
* @param key 键
|
|
157
224
|
* @param increment 增量
|
|
158
225
|
*/
|
|
159
226
|
async increase(key, increment = 1) {
|
|
160
227
|
const redisKey = this.toRedisKey(key);
|
|
161
|
-
|
|
228
|
+
if (this.hasMemoryCacheOnly) {
|
|
229
|
+
const current = this.memoryCache.get(redisKey) ?? 0;
|
|
230
|
+
const next = current + increment;
|
|
231
|
+
this.memoryCache.set(redisKey, next);
|
|
232
|
+
return next;
|
|
233
|
+
}
|
|
234
|
+
const result = await _x.x.redis.incrby(redisKey, increment);
|
|
235
|
+
if (this.hasMemoryCache) {
|
|
236
|
+
this.memoryCache.delete(redisKey);
|
|
237
|
+
}
|
|
238
|
+
return result;
|
|
162
239
|
}
|
|
163
240
|
|
|
164
241
|
/**
|
|
165
242
|
* 自减。
|
|
166
243
|
*
|
|
244
|
+
* @memory 支持
|
|
167
245
|
* @param key 键
|
|
168
246
|
* @param decrement 减量
|
|
169
247
|
*/
|
|
@@ -174,37 +252,63 @@ class CacheService {
|
|
|
174
252
|
/**
|
|
175
253
|
* 新增元素。
|
|
176
254
|
*
|
|
255
|
+
* @memory 支持
|
|
177
256
|
* @param key 键
|
|
178
257
|
* @param values 元素列表
|
|
179
258
|
*/
|
|
180
259
|
async push(key, ...values) {
|
|
181
260
|
const redisKey = this.toRedisKey(key);
|
|
182
|
-
|
|
261
|
+
if (this.hasMemoryCacheOnly) {
|
|
262
|
+
const arr = this.memoryCache.get(redisKey) ?? [];
|
|
263
|
+
arr.push(...values);
|
|
264
|
+
this.memoryCache.set(redisKey, arr);
|
|
265
|
+
return arr.length;
|
|
266
|
+
}
|
|
267
|
+
const result = await _x.x.redis.rpush(redisKey, ...values.map(value => this.serialize(value)));
|
|
268
|
+
if (this.hasMemoryCache) {
|
|
269
|
+
this.memoryCache.delete(redisKey);
|
|
270
|
+
}
|
|
271
|
+
return result;
|
|
183
272
|
}
|
|
184
273
|
|
|
185
274
|
/**
|
|
186
275
|
* 从头部弹出元素并返回。
|
|
187
276
|
*
|
|
277
|
+
* @memory 支持
|
|
188
278
|
* @param key 键
|
|
189
279
|
* @param count 弹出元素个数
|
|
190
280
|
*/
|
|
191
281
|
async shift(key, count = 1) {
|
|
192
282
|
const redisKey = this.toRedisKey(key);
|
|
283
|
+
if (this.hasMemoryCacheOnly) {
|
|
284
|
+
const arr = this.memoryCache.get(redisKey) ?? [];
|
|
285
|
+
const res = arr.splice(0, count);
|
|
286
|
+
if (arr.length) {
|
|
287
|
+
this.memoryCache.set(redisKey, arr);
|
|
288
|
+
} else {
|
|
289
|
+
this.memoryCache.delete(redisKey);
|
|
290
|
+
}
|
|
291
|
+
return res;
|
|
292
|
+
}
|
|
193
293
|
const res = [];
|
|
194
294
|
for (let i = 0; i < count; i++) {
|
|
195
295
|
const item = await _x.x.redis.lpop(redisKey);
|
|
196
296
|
if (item) {
|
|
197
|
-
res.push(
|
|
297
|
+
res.push(this.deserialize(item));
|
|
198
298
|
} else {
|
|
199
299
|
break;
|
|
200
300
|
}
|
|
201
301
|
}
|
|
302
|
+
if (this.hasMemoryCache && res.length) {
|
|
303
|
+
this.memoryCache.delete(redisKey);
|
|
304
|
+
}
|
|
202
305
|
return res;
|
|
203
306
|
}
|
|
204
307
|
|
|
205
308
|
/**
|
|
206
309
|
* 删除缓存。
|
|
207
310
|
*
|
|
311
|
+
* @memory 支持
|
|
208
312
|
* @param keys 键列表
|
|
209
313
|
*/
|
|
210
314
|
async remove(...keys) {
|
|
@@ -212,42 +316,73 @@ class CacheService {
|
|
|
212
316
|
return 0;
|
|
213
317
|
}
|
|
214
318
|
const redisKeys = keys.map(key => this.toRedisKey(key));
|
|
215
|
-
|
|
216
|
-
if (this.
|
|
319
|
+
let count = 0;
|
|
320
|
+
if (!this.hasMemoryCacheOnly) {
|
|
321
|
+
count = await _x.x.redis.del(...redisKeys);
|
|
322
|
+
}
|
|
323
|
+
if (this.hasMemoryCache) {
|
|
324
|
+
let deletedCount = 0;
|
|
217
325
|
redisKeys.forEach(redisKey => {
|
|
218
|
-
this.memoryCache.delete(redisKey);
|
|
326
|
+
const isDeleted = this.memoryCache.delete(redisKey);
|
|
327
|
+
if (isDeleted) {
|
|
328
|
+
deletedCount++;
|
|
329
|
+
}
|
|
219
330
|
});
|
|
331
|
+
if (this.hasMemoryCacheOnly) {
|
|
332
|
+
count = deletedCount;
|
|
333
|
+
}
|
|
220
334
|
}
|
|
221
335
|
return count;
|
|
222
336
|
}
|
|
223
337
|
|
|
224
338
|
/**
|
|
225
339
|
* 清空全部缓存。
|
|
340
|
+
*
|
|
341
|
+
* @memory 支持
|
|
226
342
|
*/
|
|
227
343
|
async clearAll() {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
344
|
+
let count = 0;
|
|
345
|
+
if (!this.hasMemoryCacheOnly) {
|
|
346
|
+
const keys = await _x.x.redis.keys(`${this.prefix}*`);
|
|
347
|
+
count = keys.length ? await _x.x.redis.del(...keys) : 0;
|
|
348
|
+
}
|
|
349
|
+
if (this.hasMemoryCache) {
|
|
350
|
+
const memoryCacheSize = this.memoryCache.size;
|
|
231
351
|
this.memoryCache.clear();
|
|
352
|
+
if (this.hasMemoryCacheOnly) {
|
|
353
|
+
count = memoryCacheSize;
|
|
354
|
+
}
|
|
232
355
|
}
|
|
233
356
|
return count;
|
|
234
357
|
}
|
|
235
358
|
|
|
236
359
|
/**
|
|
237
360
|
* 清空指定前缀的缓存。
|
|
361
|
+
*
|
|
362
|
+
* @memory 支持
|
|
238
363
|
*/
|
|
239
364
|
async clear(...prefixes) {
|
|
240
365
|
if (!prefixes.length) {
|
|
241
366
|
return 0;
|
|
242
367
|
}
|
|
243
|
-
|
|
244
|
-
|
|
368
|
+
let count = 0;
|
|
369
|
+
if (!this.hasMemoryCacheOnly) {
|
|
370
|
+
const keys = (await Promise.all(prefixes.map(prefix => _x.x.redis.keys(`${this.toRedisKey(prefix)}*`)))).flat();
|
|
371
|
+
count = keys.length ? await _x.x.redis.del(...keys) : 0;
|
|
372
|
+
}
|
|
245
373
|
if (this.options.memory) {
|
|
374
|
+
let deletedCount = 0;
|
|
246
375
|
this.memoryCache.forEach((_v, k) => {
|
|
247
376
|
if (prefixes.some(prefix => k.startsWith(this.toRedisKey(prefix)))) {
|
|
248
|
-
this.memoryCache.delete(k);
|
|
377
|
+
const isDeleted = this.memoryCache.delete(k);
|
|
378
|
+
if (isDeleted) {
|
|
379
|
+
deletedCount++;
|
|
380
|
+
}
|
|
249
381
|
}
|
|
250
382
|
});
|
|
383
|
+
if (this.hasMemoryCacheOnly) {
|
|
384
|
+
count = deletedCount;
|
|
385
|
+
}
|
|
251
386
|
}
|
|
252
387
|
return count;
|
|
253
388
|
}
|
package/lib/services/cache.d.ts
CHANGED
|
@@ -1,11 +1,28 @@
|
|
|
1
1
|
import { type MsValue } from 'vtils';
|
|
2
2
|
import type { AsyncOrSync, IsEmptyArray } from 'vtils/types';
|
|
3
3
|
import type { BaseService } from './base';
|
|
4
|
+
export interface CacheSerializer {
|
|
5
|
+
stringify(value: unknown): string;
|
|
6
|
+
parse<T>(value: string): T;
|
|
7
|
+
}
|
|
4
8
|
export interface CacheServiceOptions {
|
|
5
9
|
/** 默认过期时间 */
|
|
6
10
|
ttl: MsValue;
|
|
7
|
-
/**
|
|
8
|
-
|
|
11
|
+
/**
|
|
12
|
+
* 启用内存缓存
|
|
13
|
+
*
|
|
14
|
+
* - `true` 表示启用内存缓存
|
|
15
|
+
* - `'only'` 表示仅使用内存缓存,不使用 Redis
|
|
16
|
+
*/
|
|
17
|
+
memory?: boolean | 'only';
|
|
18
|
+
/**
|
|
19
|
+
* 序列化工具
|
|
20
|
+
*
|
|
21
|
+
* - `'nativeJson'` 使用原生 JSON 进行序列化和反序列化(默认)
|
|
22
|
+
* - `'superJson'` 使用 SuperJson 进行序列化和反序列化,支持更多数据类型(比如Date、RegExp等)但性能较原生 JSON 略差
|
|
23
|
+
* - 也可直接传入自定义 `{ stringify, parse }`
|
|
24
|
+
*/
|
|
25
|
+
serializer?: 'nativeJson' | 'superJson' | CacheSerializer;
|
|
9
26
|
}
|
|
10
27
|
type Data = {
|
|
11
28
|
[K in string]: (...args: any[]) => any;
|
|
@@ -29,12 +46,18 @@ export declare class CacheService<TData extends Data = Data, TKey extends Key<TD
|
|
|
29
46
|
options: CacheServiceOptions;
|
|
30
47
|
serviceName: string;
|
|
31
48
|
private prefix;
|
|
49
|
+
private hasMemoryCache;
|
|
50
|
+
private hasMemoryCacheOnly;
|
|
32
51
|
private memoryCache;
|
|
52
|
+
private serializer;
|
|
33
53
|
constructor(options: CacheServiceOptions);
|
|
34
54
|
private toRedisKey;
|
|
55
|
+
private serialize;
|
|
56
|
+
private deserialize;
|
|
35
57
|
/**
|
|
36
58
|
* 设置缓存内容。对象类内容尽量避免使用,以免造成问题。
|
|
37
59
|
*
|
|
60
|
+
* @memory 支持
|
|
38
61
|
* @param key 键
|
|
39
62
|
* @param value 值
|
|
40
63
|
* @param ttlOrOptions 缓存时间或选项
|
|
@@ -44,6 +67,7 @@ export declare class CacheService<TData extends Data = Data, TKey extends Key<TD
|
|
|
44
67
|
/**
|
|
45
68
|
* 存储值然后返回其对应的键,该键 URL 友好。
|
|
46
69
|
*
|
|
70
|
+
* @memory 支持
|
|
47
71
|
* @param value 值
|
|
48
72
|
* @param ttl 存活时间
|
|
49
73
|
*/
|
|
@@ -51,6 +75,7 @@ export declare class CacheService<TData extends Data = Data, TKey extends Key<TD
|
|
|
51
75
|
/**
|
|
52
76
|
* 获取缓存内容。
|
|
53
77
|
*
|
|
78
|
+
* @memory 支持
|
|
54
79
|
* @param key 键
|
|
55
80
|
* @param options 选项
|
|
56
81
|
* @returns 返回获取到的缓存内容
|
|
@@ -59,13 +84,16 @@ export declare class CacheService<TData extends Data = Data, TKey extends Key<TD
|
|
|
59
84
|
/**
|
|
60
85
|
* 获取多个缓存内容。
|
|
61
86
|
*
|
|
87
|
+
* @memory 支持
|
|
62
88
|
* @param keys 键
|
|
63
89
|
* @returns 返回获取到的缓存内容
|
|
64
90
|
*/
|
|
65
91
|
getMany<K extends TKeyPath, V extends Value<TData, K>>(...keys: K[]): Promise<Array<V | null>>;
|
|
66
92
|
/**
|
|
67
93
|
* 获取距离过期时间的毫秒数(未设置过期时间返回-1,键不存在返回-2)。
|
|
94
|
+
* memory=only 模式下内存缓存无 TTL 概念,始终返回-1。
|
|
68
95
|
*
|
|
96
|
+
* @memory 支持
|
|
69
97
|
* @param key 键
|
|
70
98
|
* @returns 返回距离过期时间的毫秒数
|
|
71
99
|
*/
|
|
@@ -73,6 +101,7 @@ export declare class CacheService<TData extends Data = Data, TKey extends Key<TD
|
|
|
73
101
|
/**
|
|
74
102
|
* 缓存内容。
|
|
75
103
|
*
|
|
104
|
+
* @memory 支持
|
|
76
105
|
* @param key 键
|
|
77
106
|
* @param action 获取缓存内容的操作
|
|
78
107
|
* @param ttlOrOptions 缓存时间或选项
|
|
@@ -83,6 +112,7 @@ export declare class CacheService<TData extends Data = Data, TKey extends Key<TD
|
|
|
83
112
|
/**
|
|
84
113
|
* 自增。
|
|
85
114
|
*
|
|
115
|
+
* @memory 支持
|
|
86
116
|
* @param key 键
|
|
87
117
|
* @param increment 增量
|
|
88
118
|
*/
|
|
@@ -90,6 +120,7 @@ export declare class CacheService<TData extends Data = Data, TKey extends Key<TD
|
|
|
90
120
|
/**
|
|
91
121
|
* 自减。
|
|
92
122
|
*
|
|
123
|
+
* @memory 支持
|
|
93
124
|
* @param key 键
|
|
94
125
|
* @param decrement 减量
|
|
95
126
|
*/
|
|
@@ -97,6 +128,7 @@ export declare class CacheService<TData extends Data = Data, TKey extends Key<TD
|
|
|
97
128
|
/**
|
|
98
129
|
* 新增元素。
|
|
99
130
|
*
|
|
131
|
+
* @memory 支持
|
|
100
132
|
* @param key 键
|
|
101
133
|
* @param values 元素列表
|
|
102
134
|
*/
|
|
@@ -104,6 +136,7 @@ export declare class CacheService<TData extends Data = Data, TKey extends Key<TD
|
|
|
104
136
|
/**
|
|
105
137
|
* 从头部弹出元素并返回。
|
|
106
138
|
*
|
|
139
|
+
* @memory 支持
|
|
107
140
|
* @param key 键
|
|
108
141
|
* @param count 弹出元素个数
|
|
109
142
|
*/
|
|
@@ -111,15 +144,20 @@ export declare class CacheService<TData extends Data = Data, TKey extends Key<TD
|
|
|
111
144
|
/**
|
|
112
145
|
* 删除缓存。
|
|
113
146
|
*
|
|
147
|
+
* @memory 支持
|
|
114
148
|
* @param keys 键列表
|
|
115
149
|
*/
|
|
116
150
|
remove<K extends TKeyPath>(...keys: K[]): Promise<number>;
|
|
117
151
|
/**
|
|
118
152
|
* 清空全部缓存。
|
|
153
|
+
*
|
|
154
|
+
* @memory 支持
|
|
119
155
|
*/
|
|
120
156
|
clearAll(): Promise<number>;
|
|
121
157
|
/**
|
|
122
158
|
* 清空指定前缀的缓存。
|
|
159
|
+
*
|
|
160
|
+
* @memory 支持
|
|
123
161
|
*/
|
|
124
162
|
clear(...prefixes: Array<TKey | TKeyPartialPath>): Promise<number>;
|
|
125
163
|
/**
|
package/lib/services/cache.js
CHANGED
|
@@ -1,22 +1,44 @@
|
|
|
1
1
|
import cuid from 'cuid';
|
|
2
|
+
import superjson from 'superjson';
|
|
2
3
|
import { ms } from 'vtils';
|
|
3
4
|
import { x } from "../x";
|
|
5
|
+
const nativeJsonSerializer = {
|
|
6
|
+
stringify: JSON.stringify,
|
|
7
|
+
parse: JSON.parse
|
|
8
|
+
};
|
|
9
|
+
const superJsonSerializer = {
|
|
10
|
+
stringify: superjson.stringify,
|
|
11
|
+
parse: superjson.parse
|
|
12
|
+
};
|
|
4
13
|
export class CacheService {
|
|
5
14
|
constructor(options) {
|
|
6
15
|
this.options = options;
|
|
7
16
|
this.serviceName = 'cache';
|
|
8
17
|
this.prefix = void 0;
|
|
18
|
+
this.hasMemoryCache = false;
|
|
19
|
+
this.hasMemoryCacheOnly = false;
|
|
9
20
|
this.memoryCache = void 0;
|
|
21
|
+
this.serializer = void 0;
|
|
10
22
|
this.prefix = `${x.appId}:`;
|
|
11
|
-
this.
|
|
23
|
+
this.hasMemoryCache = !!options.memory;
|
|
24
|
+
this.hasMemoryCacheOnly = options.memory === 'only';
|
|
25
|
+
this.serializer = options.serializer == null || options.serializer === 'nativeJson' ? nativeJsonSerializer : options.serializer === 'superJson' ? superJsonSerializer : options.serializer;
|
|
26
|
+
this.memoryCache = !this.hasMemoryCache ? undefined : new Map();
|
|
12
27
|
}
|
|
13
28
|
toRedisKey(key) {
|
|
14
29
|
return `${this.prefix}${Array.isArray(key) ? key.join('_') : String(key)}`;
|
|
15
30
|
}
|
|
31
|
+
serialize(value) {
|
|
32
|
+
return this.serializer.stringify(value);
|
|
33
|
+
}
|
|
34
|
+
deserialize(value) {
|
|
35
|
+
return this.serializer.parse(value);
|
|
36
|
+
}
|
|
16
37
|
|
|
17
38
|
/**
|
|
18
39
|
* 设置缓存内容。对象类内容尽量避免使用,以免造成问题。
|
|
19
40
|
*
|
|
41
|
+
* @memory 支持
|
|
20
42
|
* @param key 键
|
|
21
43
|
* @param value 值
|
|
22
44
|
* @param ttlOrOptions 缓存时间或选项
|
|
@@ -30,11 +52,13 @@ export class CacheService {
|
|
|
30
52
|
// Redis 的 TTL 不能小于等于 0
|
|
31
53
|
if (redisTtlMs > 0) {
|
|
32
54
|
const redisKey = this.toRedisKey(key);
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
55
|
+
if (!this.hasMemoryCacheOnly) {
|
|
56
|
+
const redisValue = this.serialize(value);
|
|
57
|
+
await x.redis.set(redisKey, redisValue,
|
|
58
|
+
// 毫秒
|
|
59
|
+
'PX', redisTtlMs);
|
|
60
|
+
}
|
|
61
|
+
if (this.hasMemoryCache) {
|
|
38
62
|
this.memoryCache.set(redisKey, value);
|
|
39
63
|
}
|
|
40
64
|
}
|
|
@@ -45,6 +69,7 @@ export class CacheService {
|
|
|
45
69
|
/**
|
|
46
70
|
* 存储值然后返回其对应的键,该键 URL 友好。
|
|
47
71
|
*
|
|
72
|
+
* @memory 支持
|
|
48
73
|
* @param value 值
|
|
49
74
|
* @param ttl 存活时间
|
|
50
75
|
*/
|
|
@@ -57,20 +82,25 @@ export class CacheService {
|
|
|
57
82
|
/**
|
|
58
83
|
* 获取缓存内容。
|
|
59
84
|
*
|
|
85
|
+
* @memory 支持
|
|
60
86
|
* @param key 键
|
|
61
87
|
* @param options 选项
|
|
62
88
|
* @returns 返回获取到的缓存内容
|
|
63
89
|
*/
|
|
64
90
|
async get(key) {
|
|
65
91
|
const redisKey = this.toRedisKey(key);
|
|
66
|
-
if (this.
|
|
67
|
-
|
|
92
|
+
if (this.hasMemoryCache) {
|
|
93
|
+
if (this.memoryCache.has(redisKey)) {
|
|
94
|
+
return this.memoryCache.get(redisKey);
|
|
95
|
+
} else if (this.hasMemoryCacheOnly) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
68
98
|
}
|
|
69
99
|
const gotValue = await x.redis.get(redisKey);
|
|
70
100
|
if (gotValue) {
|
|
71
101
|
try {
|
|
72
|
-
const parsedValue =
|
|
73
|
-
if (this.
|
|
102
|
+
const parsedValue = this.deserialize(gotValue);
|
|
103
|
+
if (this.hasMemoryCache) {
|
|
74
104
|
this.memoryCache.set(redisKey, parsedValue);
|
|
75
105
|
}
|
|
76
106
|
return parsedValue;
|
|
@@ -82,17 +112,47 @@ export class CacheService {
|
|
|
82
112
|
/**
|
|
83
113
|
* 获取多个缓存内容。
|
|
84
114
|
*
|
|
115
|
+
* @memory 支持
|
|
85
116
|
* @param keys 键
|
|
86
117
|
* @returns 返回获取到的缓存内容
|
|
87
118
|
*/
|
|
88
119
|
async getMany(...keys) {
|
|
89
120
|
const redisKeys = keys.map(key => this.toRedisKey(key));
|
|
90
121
|
if (!redisKeys.length) return [];
|
|
122
|
+
if (this.hasMemoryCacheOnly) {
|
|
123
|
+
return redisKeys.map(redisKey => this.memoryCache.has(redisKey) ? this.memoryCache.get(redisKey) : null);
|
|
124
|
+
}
|
|
125
|
+
if (this.hasMemoryCache) {
|
|
126
|
+
const result = new Array(keys.length).fill(null);
|
|
127
|
+
const missIndexes = [];
|
|
128
|
+
const missRedisKeys = [];
|
|
129
|
+
for (let i = 0; i < redisKeys.length; i++) {
|
|
130
|
+
if (this.memoryCache.has(redisKeys[i])) {
|
|
131
|
+
result[i] = this.memoryCache.get(redisKeys[i]);
|
|
132
|
+
} else {
|
|
133
|
+
missIndexes.push(i);
|
|
134
|
+
missRedisKeys.push(redisKeys[i]);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (missRedisKeys.length) {
|
|
138
|
+
const gotValues = await x.redis.mget(...missRedisKeys);
|
|
139
|
+
gotValues.forEach((value, idx) => {
|
|
140
|
+
if (value != null) {
|
|
141
|
+
try {
|
|
142
|
+
const parsed = this.deserialize(value);
|
|
143
|
+
this.memoryCache.set(missRedisKeys[idx], parsed);
|
|
144
|
+
result[missIndexes[idx]] = parsed;
|
|
145
|
+
} catch {}
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
return result;
|
|
150
|
+
}
|
|
91
151
|
const gotValues = await x.redis.mget(...redisKeys);
|
|
92
152
|
return gotValues.map(value => {
|
|
93
153
|
if (value == null) return null;
|
|
94
154
|
try {
|
|
95
|
-
return
|
|
155
|
+
return this.deserialize(value);
|
|
96
156
|
} catch {
|
|
97
157
|
return null;
|
|
98
158
|
}
|
|
@@ -101,11 +161,16 @@ export class CacheService {
|
|
|
101
161
|
|
|
102
162
|
/**
|
|
103
163
|
* 获取距离过期时间的毫秒数(未设置过期时间返回-1,键不存在返回-2)。
|
|
164
|
+
* memory=only 模式下内存缓存无 TTL 概念,始终返回-1。
|
|
104
165
|
*
|
|
166
|
+
* @memory 支持
|
|
105
167
|
* @param key 键
|
|
106
168
|
* @returns 返回距离过期时间的毫秒数
|
|
107
169
|
*/
|
|
108
170
|
async ttl(key) {
|
|
171
|
+
if (this.hasMemoryCacheOnly) {
|
|
172
|
+
return -1;
|
|
173
|
+
}
|
|
109
174
|
const redisKey = this.toRedisKey(key);
|
|
110
175
|
return x.redis.pttl(redisKey);
|
|
111
176
|
}
|
|
@@ -113,6 +178,7 @@ export class CacheService {
|
|
|
113
178
|
/**
|
|
114
179
|
* 缓存内容。
|
|
115
180
|
*
|
|
181
|
+
* @memory 支持
|
|
116
182
|
* @param key 键
|
|
117
183
|
* @param action 获取缓存内容的操作
|
|
118
184
|
* @param ttlOrOptions 缓存时间或选项
|
|
@@ -148,17 +214,29 @@ export class CacheService {
|
|
|
148
214
|
/**
|
|
149
215
|
* 自增。
|
|
150
216
|
*
|
|
217
|
+
* @memory 支持
|
|
151
218
|
* @param key 键
|
|
152
219
|
* @param increment 增量
|
|
153
220
|
*/
|
|
154
221
|
async increase(key, increment = 1) {
|
|
155
222
|
const redisKey = this.toRedisKey(key);
|
|
156
|
-
|
|
223
|
+
if (this.hasMemoryCacheOnly) {
|
|
224
|
+
const current = this.memoryCache.get(redisKey) ?? 0;
|
|
225
|
+
const next = current + increment;
|
|
226
|
+
this.memoryCache.set(redisKey, next);
|
|
227
|
+
return next;
|
|
228
|
+
}
|
|
229
|
+
const result = await x.redis.incrby(redisKey, increment);
|
|
230
|
+
if (this.hasMemoryCache) {
|
|
231
|
+
this.memoryCache.delete(redisKey);
|
|
232
|
+
}
|
|
233
|
+
return result;
|
|
157
234
|
}
|
|
158
235
|
|
|
159
236
|
/**
|
|
160
237
|
* 自减。
|
|
161
238
|
*
|
|
239
|
+
* @memory 支持
|
|
162
240
|
* @param key 键
|
|
163
241
|
* @param decrement 减量
|
|
164
242
|
*/
|
|
@@ -169,37 +247,63 @@ export class CacheService {
|
|
|
169
247
|
/**
|
|
170
248
|
* 新增元素。
|
|
171
249
|
*
|
|
250
|
+
* @memory 支持
|
|
172
251
|
* @param key 键
|
|
173
252
|
* @param values 元素列表
|
|
174
253
|
*/
|
|
175
254
|
async push(key, ...values) {
|
|
176
255
|
const redisKey = this.toRedisKey(key);
|
|
177
|
-
|
|
256
|
+
if (this.hasMemoryCacheOnly) {
|
|
257
|
+
const arr = this.memoryCache.get(redisKey) ?? [];
|
|
258
|
+
arr.push(...values);
|
|
259
|
+
this.memoryCache.set(redisKey, arr);
|
|
260
|
+
return arr.length;
|
|
261
|
+
}
|
|
262
|
+
const result = await x.redis.rpush(redisKey, ...values.map(value => this.serialize(value)));
|
|
263
|
+
if (this.hasMemoryCache) {
|
|
264
|
+
this.memoryCache.delete(redisKey);
|
|
265
|
+
}
|
|
266
|
+
return result;
|
|
178
267
|
}
|
|
179
268
|
|
|
180
269
|
/**
|
|
181
270
|
* 从头部弹出元素并返回。
|
|
182
271
|
*
|
|
272
|
+
* @memory 支持
|
|
183
273
|
* @param key 键
|
|
184
274
|
* @param count 弹出元素个数
|
|
185
275
|
*/
|
|
186
276
|
async shift(key, count = 1) {
|
|
187
277
|
const redisKey = this.toRedisKey(key);
|
|
278
|
+
if (this.hasMemoryCacheOnly) {
|
|
279
|
+
const arr = this.memoryCache.get(redisKey) ?? [];
|
|
280
|
+
const res = arr.splice(0, count);
|
|
281
|
+
if (arr.length) {
|
|
282
|
+
this.memoryCache.set(redisKey, arr);
|
|
283
|
+
} else {
|
|
284
|
+
this.memoryCache.delete(redisKey);
|
|
285
|
+
}
|
|
286
|
+
return res;
|
|
287
|
+
}
|
|
188
288
|
const res = [];
|
|
189
289
|
for (let i = 0; i < count; i++) {
|
|
190
290
|
const item = await x.redis.lpop(redisKey);
|
|
191
291
|
if (item) {
|
|
192
|
-
res.push(
|
|
292
|
+
res.push(this.deserialize(item));
|
|
193
293
|
} else {
|
|
194
294
|
break;
|
|
195
295
|
}
|
|
196
296
|
}
|
|
297
|
+
if (this.hasMemoryCache && res.length) {
|
|
298
|
+
this.memoryCache.delete(redisKey);
|
|
299
|
+
}
|
|
197
300
|
return res;
|
|
198
301
|
}
|
|
199
302
|
|
|
200
303
|
/**
|
|
201
304
|
* 删除缓存。
|
|
202
305
|
*
|
|
306
|
+
* @memory 支持
|
|
203
307
|
* @param keys 键列表
|
|
204
308
|
*/
|
|
205
309
|
async remove(...keys) {
|
|
@@ -207,42 +311,73 @@ export class CacheService {
|
|
|
207
311
|
return 0;
|
|
208
312
|
}
|
|
209
313
|
const redisKeys = keys.map(key => this.toRedisKey(key));
|
|
210
|
-
|
|
211
|
-
if (this.
|
|
314
|
+
let count = 0;
|
|
315
|
+
if (!this.hasMemoryCacheOnly) {
|
|
316
|
+
count = await x.redis.del(...redisKeys);
|
|
317
|
+
}
|
|
318
|
+
if (this.hasMemoryCache) {
|
|
319
|
+
let deletedCount = 0;
|
|
212
320
|
redisKeys.forEach(redisKey => {
|
|
213
|
-
this.memoryCache.delete(redisKey);
|
|
321
|
+
const isDeleted = this.memoryCache.delete(redisKey);
|
|
322
|
+
if (isDeleted) {
|
|
323
|
+
deletedCount++;
|
|
324
|
+
}
|
|
214
325
|
});
|
|
326
|
+
if (this.hasMemoryCacheOnly) {
|
|
327
|
+
count = deletedCount;
|
|
328
|
+
}
|
|
215
329
|
}
|
|
216
330
|
return count;
|
|
217
331
|
}
|
|
218
332
|
|
|
219
333
|
/**
|
|
220
334
|
* 清空全部缓存。
|
|
335
|
+
*
|
|
336
|
+
* @memory 支持
|
|
221
337
|
*/
|
|
222
338
|
async clearAll() {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
339
|
+
let count = 0;
|
|
340
|
+
if (!this.hasMemoryCacheOnly) {
|
|
341
|
+
const keys = await x.redis.keys(`${this.prefix}*`);
|
|
342
|
+
count = keys.length ? await x.redis.del(...keys) : 0;
|
|
343
|
+
}
|
|
344
|
+
if (this.hasMemoryCache) {
|
|
345
|
+
const memoryCacheSize = this.memoryCache.size;
|
|
226
346
|
this.memoryCache.clear();
|
|
347
|
+
if (this.hasMemoryCacheOnly) {
|
|
348
|
+
count = memoryCacheSize;
|
|
349
|
+
}
|
|
227
350
|
}
|
|
228
351
|
return count;
|
|
229
352
|
}
|
|
230
353
|
|
|
231
354
|
/**
|
|
232
355
|
* 清空指定前缀的缓存。
|
|
356
|
+
*
|
|
357
|
+
* @memory 支持
|
|
233
358
|
*/
|
|
234
359
|
async clear(...prefixes) {
|
|
235
360
|
if (!prefixes.length) {
|
|
236
361
|
return 0;
|
|
237
362
|
}
|
|
238
|
-
|
|
239
|
-
|
|
363
|
+
let count = 0;
|
|
364
|
+
if (!this.hasMemoryCacheOnly) {
|
|
365
|
+
const keys = (await Promise.all(prefixes.map(prefix => x.redis.keys(`${this.toRedisKey(prefix)}*`)))).flat();
|
|
366
|
+
count = keys.length ? await x.redis.del(...keys) : 0;
|
|
367
|
+
}
|
|
240
368
|
if (this.options.memory) {
|
|
369
|
+
let deletedCount = 0;
|
|
241
370
|
this.memoryCache.forEach((_v, k) => {
|
|
242
371
|
if (prefixes.some(prefix => k.startsWith(this.toRedisKey(prefix)))) {
|
|
243
|
-
this.memoryCache.delete(k);
|
|
372
|
+
const isDeleted = this.memoryCache.delete(k);
|
|
373
|
+
if (isDeleted) {
|
|
374
|
+
deletedCount++;
|
|
375
|
+
}
|
|
244
376
|
}
|
|
245
377
|
});
|
|
378
|
+
if (this.hasMemoryCacheOnly) {
|
|
379
|
+
count = deletedCount;
|
|
380
|
+
}
|
|
246
381
|
}
|
|
247
382
|
return count;
|
|
248
383
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jayfong/x-server",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.111.0",
|
|
4
4
|
"license": "ISC",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"main": "lib/_cjs/index.js",
|
|
@@ -70,6 +70,7 @@
|
|
|
70
70
|
"prisma": "^7.2.0",
|
|
71
71
|
"randomstring": "^1.3.1",
|
|
72
72
|
"select-run": "^1.1.2",
|
|
73
|
+
"superjson": "^2.2.6",
|
|
73
74
|
"supports-color": "^8",
|
|
74
75
|
"svg-captcha": "^1.4.0",
|
|
75
76
|
"tencentcloud-sdk-nodejs-sms": "^4.0.681",
|