@jiangnian520/midway-repeat-submit 0.0.0-stage → 1.0.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/dist/config/config.default.d.ts +5 -0
- package/dist/config/config.default.js +11 -0
- package/dist/configuration.d.ts +8 -0
- package/dist/configuration.js +135 -0
- package/dist/decorator/repeat-submit.decorator.d.ts +14 -0
- package/dist/decorator/repeat-submit.decorator.js +26 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +23 -0
- package/dist/interface.d.ts +37 -0
- package/dist/interface.js +5 -0
- package/dist/storage/memory.storage.d.ts +8 -0
- package/dist/storage/memory.storage.js +65 -0
- package/dist/storage/redis.storage.d.ts +7 -0
- package/dist/storage/redis.storage.js +15 -0
- package/dist/storage/storage.interface.d.ts +10 -0
- package/dist/storage/storage.interface.js +3 -0
- package/dist/utils/index.d.ts +9 -0
- package/dist/utils/index.js +70 -0
- package/index.d.ts +7 -0
- package/package.json +44 -3
- package/README.md +0 -3
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.default = {
|
|
4
|
+
repeatSubmit: {
|
|
5
|
+
enable: true,
|
|
6
|
+
interval: 5,
|
|
7
|
+
errorMessage: '请勿重复提交,请稍候再试',
|
|
8
|
+
useRedis: false,
|
|
9
|
+
},
|
|
10
|
+
};
|
|
11
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29uZmlnLmRlZmF1bHQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvY29uZmlnL2NvbmZpZy5kZWZhdWx0LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBRUEsa0JBQWU7SUFDWCxZQUFZLEVBQUU7UUFDVixNQUFNLEVBQUUsSUFBSTtRQUNaLFFBQVEsRUFBRSxDQUFDO1FBQ1gsWUFBWSxFQUFFLGNBQWM7UUFDNUIsUUFBUSxFQUFFLEtBQUs7S0FDSTtDQUMxQixDQUFDIn0=
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { MidwayDecoratorService } from '@midwayjs/core';
|
|
2
|
+
import type { ILifeCycle } from '@midwayjs/core';
|
|
3
|
+
import type { RepeatSubmitConfig } from './interface';
|
|
4
|
+
export declare class RepeatSubmitConfiguration implements ILifeCycle {
|
|
5
|
+
decoratorService: MidwayDecoratorService;
|
|
6
|
+
config: RepeatSubmitConfig;
|
|
7
|
+
onReady(): Promise<void>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
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 __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
19
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
20
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
21
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
22
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
23
|
+
};
|
|
24
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
25
|
+
var ownKeys = function(o) {
|
|
26
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
27
|
+
var ar = [];
|
|
28
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
29
|
+
return ar;
|
|
30
|
+
};
|
|
31
|
+
return ownKeys(o);
|
|
32
|
+
};
|
|
33
|
+
return function (mod) {
|
|
34
|
+
if (mod && mod.__esModule) return mod;
|
|
35
|
+
var result = {};
|
|
36
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
37
|
+
__setModuleDefault(result, mod);
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
})();
|
|
41
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
42
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
43
|
+
};
|
|
44
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
+
exports.RepeatSubmitConfiguration = void 0;
|
|
46
|
+
const core_1 = require("@midwayjs/core");
|
|
47
|
+
const DefaultConfig = __importStar(require("./config/config.default"));
|
|
48
|
+
const interface_1 = require("./interface");
|
|
49
|
+
const memory_storage_1 = require("./storage/memory.storage");
|
|
50
|
+
const redis_storage_1 = require("./storage/redis.storage");
|
|
51
|
+
const utils_1 = require("./utils");
|
|
52
|
+
let RepeatSubmitConfiguration = class RepeatSubmitConfiguration {
|
|
53
|
+
async onReady() {
|
|
54
|
+
if (this.config.enable === false)
|
|
55
|
+
return;
|
|
56
|
+
// 动态选择存储驱动
|
|
57
|
+
const storage = this.config.useRedis && this.config.redisDb
|
|
58
|
+
? new redis_storage_1.RedisStorage(this.config.redisDb)
|
|
59
|
+
: new memory_storage_1.MemoryStorage();
|
|
60
|
+
if (storage instanceof memory_storage_1.MemoryStorage) {
|
|
61
|
+
await storage.init();
|
|
62
|
+
}
|
|
63
|
+
this.decoratorService.registerMethodHandler(interface_1.REPEAT_SUBMIT_KEY, (options) => {
|
|
64
|
+
const meta = options.metadata;
|
|
65
|
+
const targetClass = options.target;
|
|
66
|
+
const methodName = options.propertyName;
|
|
67
|
+
const interval = meta.interval ?? this.config.interval;
|
|
68
|
+
const errorMessage = meta.errorMessage ?? this.config.errorMessage;
|
|
69
|
+
const fromSources = meta.from ?? ['body', 'query'];
|
|
70
|
+
return {
|
|
71
|
+
around: async (joinPoint) => {
|
|
72
|
+
const ctx = joinPoint.target[core_1.REQUEST_OBJ_CTX_KEY];
|
|
73
|
+
if (!ctx)
|
|
74
|
+
return joinPoint.proceed(...joinPoint.args);
|
|
75
|
+
// 1. 收集用于对比的参数数据
|
|
76
|
+
const sourceData = {};
|
|
77
|
+
if (fromSources.includes('body') && ctx.request.body)
|
|
78
|
+
sourceData.body = ctx.request.body;
|
|
79
|
+
if (fromSources.includes('query') && ctx.query)
|
|
80
|
+
sourceData.query = ctx.query;
|
|
81
|
+
if (fromSources.includes('params') && ctx.params)
|
|
82
|
+
sourceData.params = ctx.params;
|
|
83
|
+
// 2. 提取参与防重的核心数据
|
|
84
|
+
let coreData = sourceData;
|
|
85
|
+
const keys = Array.isArray(meta.keys) ? meta.keys : meta.keys ? [meta.keys] : [];
|
|
86
|
+
if (keys.length > 0) {
|
|
87
|
+
coreData = {};
|
|
88
|
+
for (const k of keys) {
|
|
89
|
+
// 如果 keys 带了前缀,或者默认全查,这里简单处理:在拼接后的数据里找
|
|
90
|
+
let val;
|
|
91
|
+
if (k.includes('.')) {
|
|
92
|
+
// 优先在合并后的对象中找路径
|
|
93
|
+
val = (0, utils_1.getValueByPath)(sourceData, k);
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
val = sourceData.body?.[k] ?? sourceData.query?.[k] ?? sourceData.params?.[k];
|
|
97
|
+
}
|
|
98
|
+
if (val !== undefined)
|
|
99
|
+
coreData[k] = val;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// 3. 生成稳定 Hash
|
|
103
|
+
const ip = (0, utils_1.getClientIp)(ctx);
|
|
104
|
+
const stableStr = JSON.stringify((0, utils_1.sortKeys)(coreData));
|
|
105
|
+
// Key 的组成:业务前缀 + IP + 类名.方法名 + 参数哈希
|
|
106
|
+
const finalKey = `repeat:${(0, utils_1.md5)(`${ip}:${targetClass.name}.${methodName}:${stableStr}`)}`;
|
|
107
|
+
// 4. 尝试抢占锁/占位
|
|
108
|
+
const acquired = await storage.acquire(finalKey, stableStr, interval);
|
|
109
|
+
if (!acquired) {
|
|
110
|
+
ctx.status = 429; // 可选:429 Too Many Requests
|
|
111
|
+
ctx.throw(400, errorMessage); // 抛出业务异常,交给你的全局错误过滤器
|
|
112
|
+
}
|
|
113
|
+
// 5. 放行
|
|
114
|
+
return joinPoint.proceed(...joinPoint.args);
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
exports.RepeatSubmitConfiguration = RepeatSubmitConfiguration;
|
|
121
|
+
__decorate([
|
|
122
|
+
(0, core_1.Inject)(),
|
|
123
|
+
__metadata("design:type", core_1.MidwayDecoratorService)
|
|
124
|
+
], RepeatSubmitConfiguration.prototype, "decoratorService", void 0);
|
|
125
|
+
__decorate([
|
|
126
|
+
(0, core_1.Config)('repeatSubmit'),
|
|
127
|
+
__metadata("design:type", Object)
|
|
128
|
+
], RepeatSubmitConfiguration.prototype, "config", void 0);
|
|
129
|
+
exports.RepeatSubmitConfiguration = RepeatSubmitConfiguration = __decorate([
|
|
130
|
+
(0, core_1.Configuration)({
|
|
131
|
+
namespace: 'repeatSubmit',
|
|
132
|
+
importConfigs: [{ default: DefaultConfig }],
|
|
133
|
+
})
|
|
134
|
+
], RepeatSubmitConfiguration);
|
|
135
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29uZmlndXJhdGlvbi5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9jb25maWd1cmF0aW9uLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztBQUFBLHlDQU93QjtBQUV4Qix1RUFBeUQ7QUFDekQsMkNBQWdEO0FBRWhELDZEQUF5RDtBQUN6RCwyREFBdUQ7QUFDdkQsbUNBQXFFO0FBTTlELElBQU0seUJBQXlCLEdBQS9CLE1BQU0seUJBQXlCO0lBT3BDLEtBQUssQ0FBQyxPQUFPO1FBQ1gsSUFBSSxJQUFJLENBQUMsTUFBTSxDQUFDLE1BQU0sS0FBSyxLQUFLO1lBQUUsT0FBTztRQUV6QyxXQUFXO1FBQ1gsTUFBTSxPQUFPLEdBQUcsSUFBSSxDQUFDLE1BQU0sQ0FBQyxRQUFRLElBQUksSUFBSSxDQUFDLE1BQU0sQ0FBQyxPQUFPO1lBQ3pELENBQUMsQ0FBQyxJQUFJLDRCQUFZLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxPQUFPLENBQUM7WUFDdkMsQ0FBQyxDQUFDLElBQUksOEJBQWEsRUFBRSxDQUFDO1FBRXhCLElBQUksT0FBTyxZQUFZLDhCQUFhLEVBQUUsQ0FBQztZQUNyQyxNQUFNLE9BQU8sQ0FBQyxJQUFJLEVBQUUsQ0FBQztRQUN2QixDQUFDO1FBRUQsSUFBSSxDQUFDLGdCQUFnQixDQUFDLHFCQUFxQixDQUFDLDZCQUFpQixFQUFFLENBQUMsT0FBTyxFQUFFLEVBQUU7WUFDekUsTUFBTSxJQUFJLEdBQUcsT0FBTyxDQUFDLFFBQStCLENBQUM7WUFDckQsTUFBTSxXQUFXLEdBQUcsT0FBTyxDQUFDLE1BQU0sQ0FBQztZQUNuQyxNQUFNLFVBQVUsR0FBRyxPQUFPLENBQUMsWUFBWSxDQUFDO1lBRXhDLE1BQU0sUUFBUSxHQUFHLElBQUksQ0FBQyxRQUFRLElBQUksSUFBSSxDQUFDLE1BQU0sQ0FBQyxRQUFTLENBQUM7WUFDeEQsTUFBTSxZQUFZ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
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { RepeatSubmitOptions } from '../interface';
|
|
2
|
+
/**
|
|
3
|
+
* # 防重复提交装饰器
|
|
4
|
+
*
|
|
5
|
+
* 标记在需要防重的接口上。
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* // 默认 5 秒内不允许重复提交相同参数
|
|
9
|
+
* @RepeatSubmit()
|
|
10
|
+
*
|
|
11
|
+
* // 10 秒内,同一个手机号不允许重复提交
|
|
12
|
+
* @RepeatSubmit({ interval: 10, keys: ['phone'] })
|
|
13
|
+
*/
|
|
14
|
+
export declare function RepeatSubmit(options?: RepeatSubmitOptions): MethodDecorator;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RepeatSubmit = RepeatSubmit;
|
|
4
|
+
const core_1 = require("@midwayjs/core");
|
|
5
|
+
const interface_1 = require("../interface");
|
|
6
|
+
/**
|
|
7
|
+
* # 防重复提交装饰器
|
|
8
|
+
*
|
|
9
|
+
* 标记在需要防重的接口上。
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* // 默认 5 秒内不允许重复提交相同参数
|
|
13
|
+
* @RepeatSubmit()
|
|
14
|
+
*
|
|
15
|
+
* // 10 秒内,同一个手机号不允许重复提交
|
|
16
|
+
* @RepeatSubmit({ interval: 10, keys: ['phone'] })
|
|
17
|
+
*/
|
|
18
|
+
function RepeatSubmit(options = {}) {
|
|
19
|
+
const defaultOptions = {
|
|
20
|
+
interval: 5,
|
|
21
|
+
from: ['body', 'query'],
|
|
22
|
+
...options,
|
|
23
|
+
};
|
|
24
|
+
return core_1.DecoratorManager.createCustomMethodDecorator(interface_1.REPEAT_SUBMIT_KEY, defaultOptions);
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVwZWF0LXN1Ym1pdC5kZWNvcmF0b3IuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvZGVjb3JhdG9yL3JlcGVhdC1zdWJtaXQuZGVjb3JhdG9yLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBZ0JBLG9DQU9DO0FBdkJELHlDQUFrRDtBQUVsRCw0Q0FBaUQ7QUFFakQ7Ozs7Ozs7Ozs7O0dBV0c7QUFDSCxTQUFnQixZQUFZLENBQUMsVUFBK0IsRUFBRTtJQUMxRCxNQUFNLGNBQWMsR0FBd0I7UUFDeEMsUUFBUSxFQUFFLENBQUM7UUFDWCxJQUFJLEVBQUUsQ0FBQyxNQUFNLEVBQUUsT0FBTyxDQUFDO1FBQ3ZCLEdBQUcsT0FBTztLQUNiLENBQUM7SUFDRixPQUFPLHVCQUFnQixDQUFDLDJCQUEyQixDQUFDLDZCQUFpQixFQUFFLGNBQWMsQ0FBQyxDQUFDO0FBQzNGLENBQUMifQ==
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
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 __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.RepeatSubmit = exports.Configuration = void 0;
|
|
18
|
+
var configuration_1 = require("./configuration");
|
|
19
|
+
Object.defineProperty(exports, "Configuration", { enumerable: true, get: function () { return configuration_1.RepeatSubmitConfiguration; } });
|
|
20
|
+
var repeat_submit_decorator_1 = require("./decorator/repeat-submit.decorator");
|
|
21
|
+
Object.defineProperty(exports, "RepeatSubmit", { enumerable: true, get: function () { return repeat_submit_decorator_1.RepeatSubmit; } });
|
|
22
|
+
__exportStar(require("./interface"), exports);
|
|
23
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFBQSxpREFBNkU7QUFBcEUsOEdBQUEseUJBQXlCLE9BQWlCO0FBQ25ELCtFQUFtRTtBQUExRCx1SEFBQSxZQUFZLE9BQUE7QUFDckIsOENBQTRCIn0=
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export declare const REPEAT_SUBMIT_KEY = "decorator:repeat_submit";
|
|
2
|
+
/** 装饰器入参 */
|
|
3
|
+
export interface RepeatSubmitOptions {
|
|
4
|
+
/**
|
|
5
|
+
* 重复提交拦截间隔时间(秒),默认 5 秒
|
|
6
|
+
*/
|
|
7
|
+
interval?: number;
|
|
8
|
+
/**
|
|
9
|
+
* 参与防重的额外字段(基于请求参数),比如 ['phone', 'userId']。
|
|
10
|
+
* 支持嵌套,如 'user.id'
|
|
11
|
+
* 如果传了,防重规则将是:当前用户IP + 方法名 + 这些字段的值。
|
|
12
|
+
* 如果不传,防重规则将是:当前用户IP + 方法名 + 所有请求参数的值(类似你的原代码)。
|
|
13
|
+
*/
|
|
14
|
+
keys?: string | string[];
|
|
15
|
+
/**
|
|
16
|
+
* 从哪些来源获取参数,默认 ['body', 'query']
|
|
17
|
+
*/
|
|
18
|
+
from?: Array<'body' | 'query' | 'params'>;
|
|
19
|
+
/** 自定义错误提示 */
|
|
20
|
+
errorMessage?: string;
|
|
21
|
+
}
|
|
22
|
+
/** 业务方全局配置 */
|
|
23
|
+
export interface RepeatSubmitConfig {
|
|
24
|
+
/** 是否启用组件,默认 true */
|
|
25
|
+
enable?: boolean;
|
|
26
|
+
/** 默认间隔时间(秒) */
|
|
27
|
+
interval?: number;
|
|
28
|
+
/** 默认错误提示 */
|
|
29
|
+
errorMessage?: string;
|
|
30
|
+
/**
|
|
31
|
+
* 是否使用 Redis 存储
|
|
32
|
+
* 默认为 false(内存存储),开启后需自行安装 ioredis 并在下方提供 db 实例
|
|
33
|
+
*/
|
|
34
|
+
useRedis?: boolean;
|
|
35
|
+
/** ioredis 实例 */
|
|
36
|
+
redisDb?: any;
|
|
37
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.REPEAT_SUBMIT_KEY = void 0;
|
|
4
|
+
exports.REPEAT_SUBMIT_KEY = 'decorator:repeat_submit';
|
|
5
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW50ZXJmYWNlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL2ludGVyZmFjZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBYSxRQUFBLGlCQUFpQixHQUFHLHlCQUF5QixDQUFDIn0=
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { IRepeatStorage } from './storage.interface';
|
|
2
|
+
export declare class MemoryStorage implements IRepeatStorage {
|
|
3
|
+
private cache;
|
|
4
|
+
private timer;
|
|
5
|
+
init(): Promise<void>;
|
|
6
|
+
acquire(key: string, value: string, ttlSeconds: number): Promise<boolean>;
|
|
7
|
+
destroy(): Promise<void>;
|
|
8
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.MemoryStorage = void 0;
|
|
13
|
+
const core_1 = require("@midwayjs/core");
|
|
14
|
+
let MemoryStorage = class MemoryStorage {
|
|
15
|
+
constructor() {
|
|
16
|
+
this.cache = new Map();
|
|
17
|
+
}
|
|
18
|
+
async init() {
|
|
19
|
+
// 每分钟清理一次过期数据,防止内存泄漏
|
|
20
|
+
this.timer = setInterval(() => {
|
|
21
|
+
const now = Date.now();
|
|
22
|
+
for (const [key, item] of this.cache.entries()) {
|
|
23
|
+
if (item.expire < now)
|
|
24
|
+
this.cache.delete(key);
|
|
25
|
+
}
|
|
26
|
+
}, 60000);
|
|
27
|
+
}
|
|
28
|
+
async acquire(key, value, ttlSeconds) {
|
|
29
|
+
const now = Date.now();
|
|
30
|
+
const existing = this.cache.get(key);
|
|
31
|
+
if (existing && existing.expire > now) {
|
|
32
|
+
return false; // 尚未过期,说明重复了
|
|
33
|
+
}
|
|
34
|
+
// 写入新值
|
|
35
|
+
this.cache.set(key, {
|
|
36
|
+
val: value,
|
|
37
|
+
expire: now + ttlSeconds * 1000,
|
|
38
|
+
});
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
// ✅ 核心修复:在组件销毁时清理定时器,消除警告并防止内存泄漏
|
|
42
|
+
async destroy() {
|
|
43
|
+
if (this.timer) {
|
|
44
|
+
clearInterval(this.timer);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
exports.MemoryStorage = MemoryStorage;
|
|
49
|
+
__decorate([
|
|
50
|
+
(0, core_1.Init)(),
|
|
51
|
+
__metadata("design:type", Function),
|
|
52
|
+
__metadata("design:paramtypes", []),
|
|
53
|
+
__metadata("design:returntype", Promise)
|
|
54
|
+
], MemoryStorage.prototype, "init", null);
|
|
55
|
+
__decorate([
|
|
56
|
+
(0, core_1.Destroy)(),
|
|
57
|
+
__metadata("design:type", Function),
|
|
58
|
+
__metadata("design:paramtypes", []),
|
|
59
|
+
__metadata("design:returntype", Promise)
|
|
60
|
+
], MemoryStorage.prototype, "destroy", null);
|
|
61
|
+
exports.MemoryStorage = MemoryStorage = __decorate([
|
|
62
|
+
(0, core_1.Provide)(),
|
|
63
|
+
(0, core_1.Scope)(core_1.ScopeEnum.Singleton)
|
|
64
|
+
], MemoryStorage);
|
|
65
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWVtb3J5LnN0b3JhZ2UuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvc3RvcmFnZS9tZW1vcnkuc3RvcmFnZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7Ozs7QUFBQSx5Q0FBMEU7QUFLbkUsSUFBTSxhQUFhLEdBQW5CLE1BQU0sYUFBYTtJQUFuQjtRQUNLLFVBQUssR0FBRyxJQUFJLEdBQUcsRUFBMkMsQ0FBQztJQXFDdkUsQ0FBQztJQWpDUyxBQUFOLEtBQUssQ0FBQyxJQUFJO1FBQ04scUJBQXFCO1FBQ3JCLElBQUksQ0FBQyxLQUFLLEdBQUcsV0FBVyxDQUFDLEdBQUcsRUFBRTtZQUMxQixNQUFNLEdBQUcsR0FBRyxJQUFJLENBQUMsR0FBRyxFQUFFLENBQUM7WUFDdkIsS0FBSyxNQUFNLENBQUMsR0FBRyxFQUFFLElBQUksQ0FBQyxJQUFJLElBQUksQ0FBQyxLQUFLLENBQUMsT0FBTyxFQUFFLEVBQUUsQ0FBQztnQkFDN0MsSUFBSSxJQUFJLENBQUMsTUFBTSxHQUFHLEdBQUc7b0JBQUUsSUFBSSxDQUFDLEtBQUssQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUM7WUFDbEQsQ0FBQztRQUNMLENBQUMsRUFBRSxLQUFLLENBQUMsQ0FBQztJQUNkLENBQUM7SUFFRCxLQUFLLENBQUMsT0FBTyxDQUFDLEdBQVcsRUFBRSxLQUFhLEVBQUUsVUFBa0I7UUFDeEQsTUFBTSxHQUFHLEdBQUcsSUFBSSxDQUFDLEdBQUcsRUFBRSxDQUFDO1FBQ3ZCLE1BQU0sUUFBUSxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLEdBQUcsQ0FBQyxDQUFDO1FBRXJDLElBQUksUUFBUSxJQUFJLFFBQVEsQ0FBQyxNQUFNLEdBQUcsR0FBRyxFQUFFLENBQUM7WUFDcEMsT0FBTyxLQUFLLENBQUMsQ0FBQyxhQUFhO1FBQy9CLENBQUM7UUFFRCxPQUFPO1FBQ1AsSUFBSSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsR0FBRyxFQUFFO1lBQ2hCLEdBQUcsRUFBRSxLQUFLO1lBQ1YsTUFBTSxFQUFFLEdBQUcsR0FBRyxVQUFVLEdBQUcsSUFBSTtTQUNsQyxDQUFDLENBQUM7UUFDSCxPQUFPLElBQUksQ0FBQztJQUNoQixDQUFDO0lBRUQsaUNBQWlDO0lBRTNCLEFBQU4sS0FBSyxDQUFDLE9BQU87UUFDVCxJQUFJLElBQUksQ0FBQyxLQUFLLEVBQUUsQ0FBQztZQUNiLGFBQWEsQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUM7UUFDOUIsQ0FBQztJQUNMLENBQUM7Q0FDSixDQUFBO0FBdENZLHNDQUFhO0FBS2hCO0lBREwsSUFBQSxXQUFJLEdBQUU7Ozs7eUNBU047QUFvQks7SUFETCxJQUFBLGNBQU8sR0FBRTs7Ozs0Q0FLVDt3QkFyQ1EsYUFBYTtJQUZ6QixJQUFBLGNBQU8sR0FBRTtJQUNULElBQUEsWUFBSyxFQUFDLGdCQUFTLENBQUMsU0FBUyxDQUFDO0dBQ2QsYUFBYSxDQXNDekIifQ==
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { IRepeatStorage } from './storage.interface';
|
|
2
|
+
import type { Redis } from 'ioredis';
|
|
3
|
+
export declare class RedisStorage implements IRepeatStorage {
|
|
4
|
+
private redis;
|
|
5
|
+
constructor(redis: Redis);
|
|
6
|
+
acquire(key: string, value: string, ttlSeconds: number): Promise<boolean>;
|
|
7
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RedisStorage = void 0;
|
|
4
|
+
class RedisStorage {
|
|
5
|
+
constructor(redis) {
|
|
6
|
+
this.redis = redis;
|
|
7
|
+
}
|
|
8
|
+
async acquire(key, value, ttlSeconds) {
|
|
9
|
+
// ✅ 使用原子操作 SET NX EX 彻底杜绝并发穿透问题
|
|
10
|
+
const result = await this.redis.set(key, value, 'EX', ttlSeconds, 'NX');
|
|
11
|
+
return result === 'OK';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
exports.RedisStorage = RedisStorage;
|
|
15
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVkaXMuc3RvcmFnZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9zdG9yYWdlL3JlZGlzLnN0b3JhZ2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBR0EsTUFBYSxZQUFZO0lBQ3JCLFlBQW9CLEtBQVk7UUFBWixVQUFLLEdBQUwsS0FBSyxDQUFPO0lBQUksQ0FBQztJQUVyQyxLQUFLLENBQUMsT0FBTyxDQUFDLEdBQVcsRUFBRSxLQUFhLEVBQUUsVUFBa0I7UUFDeEQsZ0NBQWdDO1FBQ2hDLE1BQU0sTUFBTSxHQUFHLE1BQU0sSUFBSSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsR0FBRyxFQUFFLEtBQUssRUFBRSxJQUFJLEVBQUUsVUFBVSxFQUFFLElBQUksQ0FBQyxDQUFDO1FBQ3hFLE9BQU8sTUFBTSxLQUFLLElBQUksQ0FBQztJQUMzQixDQUFDO0NBQ0o7QUFSRCxvQ0FRQyJ9
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface IRepeatStorage {
|
|
2
|
+
/**
|
|
3
|
+
* 尝试获取锁/占位
|
|
4
|
+
* @param key 防重 key
|
|
5
|
+
* @param value 存储的数据(参数 JSON 字符串)
|
|
6
|
+
* @param ttlSeconds 过期时间(秒)
|
|
7
|
+
* @returns 如果成功占位返回 true,如果已存在返回 false
|
|
8
|
+
*/
|
|
9
|
+
acquire(key: string, value: string, ttlSeconds: number): Promise<boolean>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic3RvcmFnZS5pbnRlcmZhY2UuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvc3RvcmFnZS9zdG9yYWdlLmludGVyZmFjZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiIn0=
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Context } from '@midwayjs/koa';
|
|
2
|
+
/** 计算 MD5 哈希 */
|
|
3
|
+
export declare function md5(str: string): string;
|
|
4
|
+
/** 递归排序对象键,保证 JSON.stringify 结果稳定 */
|
|
5
|
+
export declare function sortKeys(obj: any): any;
|
|
6
|
+
/** 从对象中根据路径提取值,如 a.b.c */
|
|
7
|
+
export declare function getValueByPath(obj: any, path: string): any;
|
|
8
|
+
/** 获取客户端真实 IP */
|
|
9
|
+
export declare function getClientIp(ctx: Context): string;
|
|
@@ -0,0 +1,70 @@
|
|
|
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
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.md5 = md5;
|
|
37
|
+
exports.sortKeys = sortKeys;
|
|
38
|
+
exports.getValueByPath = getValueByPath;
|
|
39
|
+
exports.getClientIp = getClientIp;
|
|
40
|
+
const crypto = __importStar(require("crypto"));
|
|
41
|
+
/** 计算 MD5 哈希 */
|
|
42
|
+
function md5(str) {
|
|
43
|
+
return crypto.createHash('md5').update(str).digest('hex');
|
|
44
|
+
}
|
|
45
|
+
/** 递归排序对象键,保证 JSON.stringify 结果稳定 */
|
|
46
|
+
function sortKeys(obj) {
|
|
47
|
+
if (typeof obj !== 'object' || obj === null)
|
|
48
|
+
return obj;
|
|
49
|
+
if (Array.isArray(obj))
|
|
50
|
+
return obj.map(sortKeys);
|
|
51
|
+
return Object.keys(obj)
|
|
52
|
+
.sort()
|
|
53
|
+
.reduce((acc, key) => {
|
|
54
|
+
acc[key] = sortKeys(obj[key]);
|
|
55
|
+
return acc;
|
|
56
|
+
}, {});
|
|
57
|
+
}
|
|
58
|
+
/** 从对象中根据路径提取值,如 a.b.c */
|
|
59
|
+
function getValueByPath(obj, path) {
|
|
60
|
+
return path.split('.').reduce((o, k) => (o === null || o === undefined ? o : o[k]), obj);
|
|
61
|
+
}
|
|
62
|
+
/** 获取客户端真实 IP */
|
|
63
|
+
function getClientIp(ctx) {
|
|
64
|
+
const headers = ctx.headers;
|
|
65
|
+
return headers['x-forwarded-for']?.split(',')[0] ||
|
|
66
|
+
headers['x-real-ip'] ||
|
|
67
|
+
ctx.ip ||
|
|
68
|
+
'unknown';
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvdXRpbHMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFJQSxrQkFFQztBQUdELDRCQVNDO0FBR0Qsd0NBRUM7QUFHRCxrQ0FNQztBQWhDRCwrQ0FBaUM7QUFHakMsZ0JBQWdCO0FBQ2hCLFNBQWdCLEdBQUcsQ0FBQyxHQUFXO0lBQzNCLE9BQU8sTUFBTSxDQUFDLFVBQVUsQ0FBQyxLQUFLLENBQUMsQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUMsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDO0FBQzlELENBQUM7QUFFRCxxQ0FBcUM7QUFDckMsU0FBZ0IsUUFBUSxDQUFDLEdBQVE7SUFDN0IsSUFBSSxPQUFPLEdBQUcsS0FBSyxRQUFRLElBQUksR0FBRyxLQUFLLElBQUk7UUFBRSxPQUFPLEdBQUcsQ0FBQztJQUN4RCxJQUFJLEtBQUssQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDO1FBQUUsT0FBTyxHQUFHLENBQUMsR0FBRyxDQUFDLFFBQVEsQ0FBQyxDQUFDO0lBQ2pELE9BQU8sTUFBTSxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUM7U0FDbEIsSUFBSSxFQUFFO1NBQ04sTUFBTSxDQUFDLENBQUMsR0FBd0IsRUFBRSxHQUFHLEVBQUUsRUFBRTtRQUN0QyxHQUFHLENBQUMsR0FBRyxDQUFDLEdBQUcsUUFBUSxDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDO1FBQzlCLE9BQU8sR0FBRyxDQUFDO0lBQ2YsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDO0FBQ2YsQ0FBQztBQUVELDBCQUEwQjtBQUMxQixTQUFnQixjQUFjLENBQUMsR0FBUSxFQUFFLElBQVk7SUFDakQsT0FBTyxJQUFJLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUMsQ0FBQyxLQUFLLElBQUksSUFBSSxDQUFDLEtBQUssU0FBUyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxFQUFFLEdBQUcsQ0FBQyxDQUFDO0FBQzdGLENBQUM7QUFFRCxpQkFBaUI7QUFDakIsU0FBZ0IsV0FBVyxDQUFDLEdBQVk7SUFDcEMsTUFBTSxPQUFPLEdBQUcsR0FBRyxDQUFDLE9BQU8sQ0FBQztJQUM1QixPQUFRLE9BQU8sQ0FBQyxpQkFBaUIsQ0FBWSxFQUFFLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLENBQUM7UUFDdkQsT0FBTyxDQUFDLFdBQVcsQ0FBWTtRQUNoQyxHQUFHLENBQUMsRUFBRTtRQUNOLFNBQVMsQ0FBQztBQUNsQixDQUFDIn0=
|
package/index.d.ts
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,47 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jiangnian520/midway-repeat-submit",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "专为 Midway 4.0 打造的防重复提交组件。支持内存与 Redis 双驱动,基于切面与原子锁实现高并发防重。",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"typings": "index.d.ts",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"midway",
|
|
9
|
+
"midwayjs",
|
|
10
|
+
"repeat-submit",
|
|
11
|
+
"anti-duplicate",
|
|
12
|
+
"idempotent",
|
|
13
|
+
"decorator"
|
|
14
|
+
],
|
|
15
|
+
"author": "jiangnian520",
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"files": [
|
|
18
|
+
"dist/**/*.js",
|
|
19
|
+
"dist/**/*.d.ts",
|
|
20
|
+
"index.d.ts"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "mwtsc --cleanOutDir"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {},
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"@midwayjs/core": ">=4.0.0",
|
|
28
|
+
"@midwayjs/koa": ">=4.0.0",
|
|
29
|
+
"ioredis": ">=4.0.0"
|
|
30
|
+
},
|
|
31
|
+
"peerDependenciesMeta": {
|
|
32
|
+
"ioredis": {
|
|
33
|
+
"optional": true
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@midwayjs/core": "^4.0.0",
|
|
38
|
+
"@midwayjs/koa": "^4.0.0",
|
|
39
|
+
"ioredis": "^5.4.1",
|
|
40
|
+
"@types/node": "^20.0.0",
|
|
41
|
+
"mwtsc": "^1.4.0",
|
|
42
|
+
"typescript": "^5.3.0"
|
|
43
|
+
},
|
|
44
|
+
"publishConfig": {
|
|
45
|
+
"access": "public"
|
|
46
|
+
}
|
|
6
47
|
}
|
package/README.md
DELETED