@jayfong/x-server 2.16.4 → 2.17.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.
@@ -11,7 +11,9 @@ class CacheService {
11
11
  this.options = options;
12
12
  this.serviceName = 'cache';
13
13
  this.prefix = void 0;
14
+ this.memoryCache = void 0;
14
15
  this.prefix = `${_x.x.appId}:`;
16
+ this.memoryCache = !options.memory ? undefined : new Map();
15
17
  }
16
18
  toRedisKey(key) {
17
19
  return `${this.prefix}${Array.isArray(key) ? key.join('_') : String(key)}`;
@@ -22,10 +24,11 @@ class CacheService {
22
24
  *
23
25
  * @param key 键
24
26
  * @param value 值
25
- * @param ttl 缓存时间
27
+ * @param ttlOrOptions 缓存时间或选项
26
28
  * @returns 返回设置的缓存内容
27
29
  */
28
- async set(key, value, ttl = this.options.ttl) {
30
+ async set(key, value, ttlOrOptions) {
31
+ const ttl = ttlOrOptions == null ? this.options.ttl : typeof ttlOrOptions === 'object' ? ttlOrOptions.ttl : ttlOrOptions;
29
32
  const redisTtl = typeof ttl === 'function' ? ttl(value, this.options.ttl) : ttl;
30
33
  if (redisTtl) {
31
34
  const redisKey = this.toRedisKey(key);
@@ -33,6 +36,9 @@ class CacheService {
33
36
  await _x.x.redis.set(redisKey, redisValue,
34
37
  // 毫秒
35
38
  'PX', (0, _vtils.ms)(redisTtl));
39
+ if (this.options.memory) {
40
+ this.memoryCache.set(redisKey, value);
41
+ }
36
42
  }
37
43
  return value;
38
44
  }
@@ -53,10 +59,14 @@ class CacheService {
53
59
  * 获取缓存内容。
54
60
  *
55
61
  * @param key 键
62
+ * @param options 选项
56
63
  * @returns 返回获取到的缓存内容
57
64
  */
58
65
  async get(key) {
59
66
  const redisKey = this.toRedisKey(key);
67
+ if (this.options.memory && this.memoryCache.has(redisKey)) {
68
+ return this.memoryCache.get(redisKey);
69
+ }
60
70
  const gotValue = await _x.x.redis.get(redisKey);
61
71
  if (gotValue) {
62
72
  try {
@@ -102,18 +112,18 @@ class CacheService {
102
112
  *
103
113
  * @param key 键
104
114
  * @param action 获取缓存内容的操作
105
- * @param ttl 缓存时间
115
+ * @param ttlOrOptions 缓存时间或选项
106
116
  * @returns 返回获取到的内容
107
117
  */
108
- async remember(key, action, ttl = this.options.ttl) {
118
+ async remember(key, action, ttlOrOptions) {
109
119
  let value = await this.get(key);
110
120
  if (value === null) {
111
121
  value = await action();
112
- await this.set(key, value, ttl);
122
+ await this.set(key, value, ttlOrOptions);
113
123
  }
114
124
  return value;
115
125
  }
116
- async rememberMany(raws, key, action, ttl = this.options.ttl) {
126
+ async rememberMany(raws, key, action, ttlOrOptions) {
117
127
  const keys = raws.map(key);
118
128
  const values = await this.getMany(...keys);
119
129
  const shouldUpdateIndexes = [];
@@ -126,7 +136,7 @@ class CacheService {
126
136
  const nextValues = await action(shouldUpdateIndexes.map(i => raws[i]));
127
137
  await Promise.all(nextValues.map(async (nextValue, index) => {
128
138
  values[shouldUpdateIndexes[index]] = nextValue;
129
- await this.set(keys[shouldUpdateIndexes[index]], nextValue, ttl);
139
+ await this.set(keys[shouldUpdateIndexes[index]], nextValue, ttlOrOptions);
130
140
  }));
131
141
  }
132
142
  return values;
@@ -193,7 +203,13 @@ class CacheService {
193
203
  if (!keys.length) {
194
204
  return 0;
195
205
  }
196
- return _x.x.redis.del(...keys.map(key => this.toRedisKey(key)));
206
+ const redisKeys = keys.map(key => this.toRedisKey(key));
207
+ if (this.options.memory) {
208
+ redisKeys.forEach(redisKey => {
209
+ this.memoryCache.delete(redisKey);
210
+ });
211
+ }
212
+ return _x.x.redis.del(...redisKeys);
197
213
  }
198
214
 
199
215
  /**
@@ -3,36 +3,51 @@
3
3
  var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
4
4
  exports.__esModule = true;
5
5
  exports.CaptchaService = void 0;
6
- var _svgCaptcha = _interopRequireDefault(require("svg-captcha"));
7
6
  var _miniSvgDataUri = _interopRequireDefault(require("mini-svg-data-uri"));
7
+ var _svgCaptcha = _interopRequireDefault(require("svg-captcha"));
8
+ var _vtils = require("vtils");
8
9
  var _x = require("../x");
9
10
  class CaptchaService {
10
- constructor(options) {
11
- this.options = options;
11
+ constructor() {
12
12
  this.serviceName = 'captcha';
13
+ this.charsetMap = {
14
+ '09': '0123456789'.split(''),
15
+ '19': '123456789'.split(''),
16
+ 'az': 'abcdefghijklmnopqrstuvwxyz'.split(''),
17
+ 'AZ': 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''),
18
+ 'aZ': 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''),
19
+ '0z': '0123456789abcdefghijklmnopqrstuvwxyz'.split(''),
20
+ '0Z': '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''),
21
+ '1z': '123456789abcdefghijklmnopqrstuvwxyz'.split(''),
22
+ '1Z': '123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('')
23
+ };
13
24
  }
14
- async generate(scene) {
15
- const code = Math.random().toString().slice(0 - this.options.size);
25
+ async generate(payload) {
26
+ const chars = this.charsetMap[payload.charset];
27
+ let code = '';
28
+ for (let i = 0; i < payload.size; i++) {
29
+ code += (0, _vtils.sample)(chars);
30
+ }
16
31
  const token = await _x.x.cache.save({
17
- code,
18
- scene
19
- }, this.options.ttl);
32
+ code: code,
33
+ scene: payload.scene
34
+ }, payload.ttl);
20
35
  return {
21
36
  token: token,
22
37
  code: code
23
38
  };
24
39
  }
25
- async generateDataUrl(scene) {
40
+ async generateDataUrl(payload) {
26
41
  const {
27
42
  text: code,
28
43
  data: svg
29
44
  } = _svgCaptcha.default.create({
30
- size: this.options.size
45
+ size: payload.size
31
46
  });
32
47
  const token = await _x.x.cache.save({
33
48
  code,
34
- scene
35
- }, this.options.ttl);
49
+ scene: payload.scene
50
+ }, payload.ttl);
36
51
  const dataUrl = (0, _miniSvgDataUri.default)(svg);
37
52
  return {
38
53
  token: token,
@@ -41,9 +56,6 @@ class CaptchaService {
41
56
  };
42
57
  }
43
58
  async verify(options) {
44
- if (options.code.length !== this.options.size) {
45
- return false;
46
- }
47
59
  const expected = await _x.x.cache.get(options.token);
48
60
  if (expected == null) {
49
61
  return false;
@@ -1,9 +1,11 @@
1
+ import { MsValue } from 'vtils';
1
2
  import { AsyncOrSync, IsEmptyArray } from 'vtils/types';
2
3
  import { BaseService } from './base';
3
- import { MsValue } from 'vtils';
4
4
  export interface CacheServiceOptions {
5
5
  /** 默认过期时间 */
6
6
  ttl: MsValue;
7
+ /** 启用内存缓存,仅 get, set, remove, remember 支持 */
8
+ memory?: boolean;
7
9
  }
8
10
  type Data = {
9
11
  [K in string]: (...args: any[]) => any;
@@ -16,10 +18,15 @@ type KeyPathForNumberValue<T extends Data> = {
16
18
  [K in keyof T]: T[K] extends (...args: infer X) => number ? IsEmptyArray<X> extends true ? K : [K, ...X] : never;
17
19
  }[keyof T];
18
20
  type Value<T extends Data, P extends KeyPath<T>> = ReturnType<T[P extends any[] ? P[0] : P]>;
21
+ type CacheSetTTL<V> = MsValue | ((data: V, defaultTTL: MsValue) => MsValue);
22
+ type CacheSetOptions<V> = CacheSetTTL<V> | {
23
+ ttl?: CacheSetTTL<V>;
24
+ };
19
25
  export declare class CacheService<TData extends Data = Data, TKey extends Key<TData> = any, TKeyPath extends KeyPath<TData> = any, TKeyPathForNumberValue extends KeyPathForNumberValue<TData> = any> implements BaseService {
20
26
  options: CacheServiceOptions;
21
27
  serviceName: string;
22
28
  private prefix;
29
+ private memoryCache;
23
30
  constructor(options: CacheServiceOptions);
24
31
  private toRedisKey;
25
32
  /**
@@ -27,10 +34,10 @@ export declare class CacheService<TData extends Data = Data, TKey extends Key<TD
27
34
  *
28
35
  * @param key 键
29
36
  * @param value 值
30
- * @param ttl 缓存时间
37
+ * @param ttlOrOptions 缓存时间或选项
31
38
  * @returns 返回设置的缓存内容
32
39
  */
33
- set<K extends TKeyPath, V extends Value<TData, K>>(key: K, value: V, ttl?: MsValue | ((data: V, defaultTTL: MsValue) => MsValue)): Promise<V>;
40
+ set<K extends TKeyPath, V extends Value<TData, K>>(key: K, value: V, ttlOrOptions?: CacheSetOptions<V>): Promise<V>;
34
41
  /**
35
42
  * 存储值然后返回其对应的键,该键 URL 友好。
36
43
  *
@@ -42,6 +49,7 @@ export declare class CacheService<TData extends Data = Data, TKey extends Key<TD
42
49
  * 获取缓存内容。
43
50
  *
44
51
  * @param key 键
52
+ * @param options 选项
45
53
  * @returns 返回获取到的缓存内容
46
54
  */
47
55
  get<K extends TKeyPath, V extends Value<TData, K>>(key: K): Promise<V | null>;
@@ -64,11 +72,11 @@ export declare class CacheService<TData extends Data = Data, TKey extends Key<TD
64
72
  *
65
73
  * @param key 键
66
74
  * @param action 获取缓存内容的操作
67
- * @param ttl 缓存时间
75
+ * @param ttlOrOptions 缓存时间或选项
68
76
  * @returns 返回获取到的内容
69
77
  */
70
- remember<K extends TKeyPath, V extends Value<TData, K>>(key: K, action: () => V | Promise<V>, ttl?: MsValue | ((data: V, defaultTTL: MsValue) => MsValue)): Promise<V>;
71
- rememberMany<T, K extends TKeyPath, V extends Value<TData, K>>(raws: T[], key: (raw: T) => K, action: (raws: T[]) => V[] | Promise<V[]>, ttl?: MsValue | ((data: V, defaultTTL: MsValue) => MsValue)): Promise<V[]>;
78
+ remember<K extends TKeyPath, V extends Value<TData, K>>(key: K, action: () => V | Promise<V>, ttlOrOptions?: CacheSetOptions<V>): Promise<V>;
79
+ rememberMany<T, K extends TKeyPath, V extends Value<TData, K>>(raws: T[], key: (raw: T) => K, action: (raws: T[]) => V[] | Promise<V[]>, ttlOrOptions?: CacheSetOptions<V>): Promise<V[]>;
72
80
  /**
73
81
  * 自增。
74
82
  *
@@ -6,7 +6,9 @@ export class CacheService {
6
6
  this.options = options;
7
7
  this.serviceName = 'cache';
8
8
  this.prefix = void 0;
9
+ this.memoryCache = void 0;
9
10
  this.prefix = `${x.appId}:`;
11
+ this.memoryCache = !options.memory ? undefined : new Map();
10
12
  }
11
13
  toRedisKey(key) {
12
14
  return `${this.prefix}${Array.isArray(key) ? key.join('_') : String(key)}`;
@@ -17,10 +19,11 @@ export class CacheService {
17
19
  *
18
20
  * @param key 键
19
21
  * @param value 值
20
- * @param ttl 缓存时间
22
+ * @param ttlOrOptions 缓存时间或选项
21
23
  * @returns 返回设置的缓存内容
22
24
  */
23
- async set(key, value, ttl = this.options.ttl) {
25
+ async set(key, value, ttlOrOptions) {
26
+ const ttl = ttlOrOptions == null ? this.options.ttl : typeof ttlOrOptions === 'object' ? ttlOrOptions.ttl : ttlOrOptions;
24
27
  const redisTtl = typeof ttl === 'function' ? ttl(value, this.options.ttl) : ttl;
25
28
  if (redisTtl) {
26
29
  const redisKey = this.toRedisKey(key);
@@ -28,6 +31,9 @@ export class CacheService {
28
31
  await x.redis.set(redisKey, redisValue,
29
32
  // 毫秒
30
33
  'PX', ms(redisTtl));
34
+ if (this.options.memory) {
35
+ this.memoryCache.set(redisKey, value);
36
+ }
31
37
  }
32
38
  return value;
33
39
  }
@@ -48,10 +54,14 @@ export class CacheService {
48
54
  * 获取缓存内容。
49
55
  *
50
56
  * @param key 键
57
+ * @param options 选项
51
58
  * @returns 返回获取到的缓存内容
52
59
  */
53
60
  async get(key) {
54
61
  const redisKey = this.toRedisKey(key);
62
+ if (this.options.memory && this.memoryCache.has(redisKey)) {
63
+ return this.memoryCache.get(redisKey);
64
+ }
55
65
  const gotValue = await x.redis.get(redisKey);
56
66
  if (gotValue) {
57
67
  try {
@@ -97,18 +107,18 @@ export class CacheService {
97
107
  *
98
108
  * @param key 键
99
109
  * @param action 获取缓存内容的操作
100
- * @param ttl 缓存时间
110
+ * @param ttlOrOptions 缓存时间或选项
101
111
  * @returns 返回获取到的内容
102
112
  */
103
- async remember(key, action, ttl = this.options.ttl) {
113
+ async remember(key, action, ttlOrOptions) {
104
114
  let value = await this.get(key);
105
115
  if (value === null) {
106
116
  value = await action();
107
- await this.set(key, value, ttl);
117
+ await this.set(key, value, ttlOrOptions);
108
118
  }
109
119
  return value;
110
120
  }
111
- async rememberMany(raws, key, action, ttl = this.options.ttl) {
121
+ async rememberMany(raws, key, action, ttlOrOptions) {
112
122
  const keys = raws.map(key);
113
123
  const values = await this.getMany(...keys);
114
124
  const shouldUpdateIndexes = [];
@@ -121,7 +131,7 @@ export class CacheService {
121
131
  const nextValues = await action(shouldUpdateIndexes.map(i => raws[i]));
122
132
  await Promise.all(nextValues.map(async (nextValue, index) => {
123
133
  values[shouldUpdateIndexes[index]] = nextValue;
124
- await this.set(keys[shouldUpdateIndexes[index]], nextValue, ttl);
134
+ await this.set(keys[shouldUpdateIndexes[index]], nextValue, ttlOrOptions);
125
135
  }));
126
136
  }
127
137
  return values;
@@ -188,7 +198,13 @@ export class CacheService {
188
198
  if (!keys.length) {
189
199
  return 0;
190
200
  }
191
- return x.redis.del(...keys.map(key => this.toRedisKey(key)));
201
+ const redisKeys = keys.map(key => this.toRedisKey(key));
202
+ if (this.options.memory) {
203
+ redisKeys.forEach(redisKey => {
204
+ this.memoryCache.delete(redisKey);
205
+ });
206
+ }
207
+ return x.redis.del(...redisKeys);
192
208
  }
193
209
 
194
210
  /**
@@ -1,6 +1,11 @@
1
+ import { type MsValue } from 'vtils';
1
2
  import { BaseService } from './base';
2
- import type { MsValue } from 'vtils';
3
- export interface CaptchaServiceOptions {
3
+ export type CaptchaServiceGenerateCharset = '09' | '19' | 'az' | 'AZ' | 'aZ' | '0z' | '0Z' | '1z' | '1Z';
4
+ export interface CaptchaServiceGeneratePayload {
5
+ /**
6
+ * 场景值
7
+ */
8
+ scene: string;
4
9
  /**
5
10
  * 验证码长度
6
11
  */
@@ -9,6 +14,10 @@ export interface CaptchaServiceOptions {
9
14
  * 验证码存活时长
10
15
  */
11
16
  ttl: MsValue;
17
+ /**
18
+ * 字符集
19
+ */
20
+ charset: CaptchaServiceGenerateCharset;
12
21
  }
13
22
  export interface CaptchaServiceGenerateResult {
14
23
  token: string;
@@ -30,11 +39,10 @@ export interface CaptchaServiceVerifyOptions {
30
39
  scene: string;
31
40
  }
32
41
  export declare class CaptchaService implements BaseService {
33
- private options;
34
42
  serviceName: string;
35
- constructor(options: CaptchaServiceOptions);
36
- generate(scene: string): Promise<CaptchaServiceGenerateResult>;
37
- generateDataUrl(scene: string): Promise<CaptchaServiceGenerateDataUrlResult>;
43
+ private charsetMap;
44
+ generate(payload: CaptchaServiceGeneratePayload): Promise<CaptchaServiceGenerateResult>;
45
+ generateDataUrl(payload: CaptchaServiceGeneratePayload): Promise<CaptchaServiceGenerateDataUrlResult>;
38
46
  verify(options: CaptchaServiceVerifyOptions): Promise<boolean>;
39
47
  }
40
48
  declare module '../x' {
@@ -1,33 +1,48 @@
1
- import svgCaptcha from 'svg-captcha';
2
1
  import svgToDataUrl from 'mini-svg-data-uri';
2
+ import svgCaptcha from 'svg-captcha';
3
+ import { sample } from 'vtils';
3
4
  import { x } from "../x";
4
5
  export class CaptchaService {
5
- constructor(options) {
6
- this.options = options;
6
+ constructor() {
7
7
  this.serviceName = 'captcha';
8
+ this.charsetMap = {
9
+ '09': '0123456789'.split(''),
10
+ '19': '123456789'.split(''),
11
+ 'az': 'abcdefghijklmnopqrstuvwxyz'.split(''),
12
+ 'AZ': 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''),
13
+ 'aZ': 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''),
14
+ '0z': '0123456789abcdefghijklmnopqrstuvwxyz'.split(''),
15
+ '0Z': '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''),
16
+ '1z': '123456789abcdefghijklmnopqrstuvwxyz'.split(''),
17
+ '1Z': '123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('')
18
+ };
8
19
  }
9
- async generate(scene) {
10
- const code = Math.random().toString().slice(0 - this.options.size);
20
+ async generate(payload) {
21
+ const chars = this.charsetMap[payload.charset];
22
+ let code = '';
23
+ for (let i = 0; i < payload.size; i++) {
24
+ code += sample(chars);
25
+ }
11
26
  const token = await x.cache.save({
12
- code,
13
- scene
14
- }, this.options.ttl);
27
+ code: code,
28
+ scene: payload.scene
29
+ }, payload.ttl);
15
30
  return {
16
31
  token: token,
17
32
  code: code
18
33
  };
19
34
  }
20
- async generateDataUrl(scene) {
35
+ async generateDataUrl(payload) {
21
36
  const {
22
37
  text: code,
23
38
  data: svg
24
39
  } = svgCaptcha.create({
25
- size: this.options.size
40
+ size: payload.size
26
41
  });
27
42
  const token = await x.cache.save({
28
43
  code,
29
- scene
30
- }, this.options.ttl);
44
+ scene: payload.scene
45
+ }, payload.ttl);
31
46
  const dataUrl = svgToDataUrl(svg);
32
47
  return {
33
48
  token: token,
@@ -36,9 +51,6 @@ export class CaptchaService {
36
51
  };
37
52
  }
38
53
  async verify(options) {
39
- if (options.code.length !== this.options.size) {
40
- return false;
41
- }
42
54
  const expected = await x.cache.get(options.token);
43
55
  if (expected == null) {
44
56
  return false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jayfong/x-server",
3
- "version": "2.16.4",
3
+ "version": "2.17.0",
4
4
  "license": "ISC",
5
5
  "sideEffects": false,
6
6
  "main": "lib/_cjs/index.js",