@midwayjs/redis 3.20.4 → 4.0.0-beta.1
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/configuration.js +2 -2
- package/dist/extension/serviceDiscovery.d.ts +68 -0
- package/dist/extension/serviceDiscovery.js +312 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/interface.d.ts +63 -1
- package/dist/manager.d.ts +2 -2
- package/dist/manager.js +23 -21
- package/index.d.ts +2 -2
- package/package.json +6 -6
package/dist/configuration.js
CHANGED
|
@@ -34,7 +34,8 @@ let RedisConfiguration = class RedisConfiguration {
|
|
|
34
34
|
};
|
|
35
35
|
}
|
|
36
36
|
};
|
|
37
|
-
RedisConfiguration =
|
|
37
|
+
exports.RedisConfiguration = RedisConfiguration;
|
|
38
|
+
exports.RedisConfiguration = RedisConfiguration = __decorate([
|
|
38
39
|
(0, core_1.Configuration)({
|
|
39
40
|
namespace: 'redis',
|
|
40
41
|
importConfigs: [
|
|
@@ -46,5 +47,4 @@ RedisConfiguration = __decorate([
|
|
|
46
47
|
],
|
|
47
48
|
})
|
|
48
49
|
], RedisConfiguration);
|
|
49
|
-
exports.RedisConfiguration = RedisConfiguration;
|
|
50
50
|
//# sourceMappingURL=configuration.js.map
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { ServiceDiscovery, ServiceDiscoveryClient, DataListener, ILogger } from '@midwayjs/core';
|
|
2
|
+
import { RedisServiceDiscoveryOptions, RedisInstanceMetadata } from '../interface';
|
|
3
|
+
import Redis from 'ioredis';
|
|
4
|
+
declare class RedisDataListener extends DataListener<RedisInstanceMetadata[]> {
|
|
5
|
+
protected readonly client: Redis;
|
|
6
|
+
protected readonly pubsub: Redis;
|
|
7
|
+
protected readonly options: RedisServiceDiscoveryOptions;
|
|
8
|
+
protected readonly logger: ILogger;
|
|
9
|
+
private unsubscribe;
|
|
10
|
+
private ttlTimeout;
|
|
11
|
+
private instance;
|
|
12
|
+
private instanceKey;
|
|
13
|
+
private serviceName;
|
|
14
|
+
constructor(serviceName: string, client: Redis, pubsub: Redis, options: RedisServiceDiscoveryOptions, logger: ILogger);
|
|
15
|
+
init(): Promise<void>;
|
|
16
|
+
initData(): Promise<RedisInstanceMetadata[]>;
|
|
17
|
+
onData(setData: any): void;
|
|
18
|
+
destroyListener(): Promise<void>;
|
|
19
|
+
onlineInstance(instance?: RedisInstanceMetadata): Promise<void>;
|
|
20
|
+
offlineInstance(instance?: RedisInstanceMetadata): Promise<void>;
|
|
21
|
+
/**
|
|
22
|
+
* 生成实例唯一 key
|
|
23
|
+
* 格式: <prefix><serviceName>:instance:<instanceId>
|
|
24
|
+
* 例: services:order:instance:abc123
|
|
25
|
+
*/
|
|
26
|
+
private getInstanceKey;
|
|
27
|
+
private startTTLRefresh;
|
|
28
|
+
private clearTTLRefresh;
|
|
29
|
+
/**
|
|
30
|
+
* 使用 SCAN 替代 KEYS,游标式遍历所有匹配的 key
|
|
31
|
+
*/
|
|
32
|
+
private scanKeys;
|
|
33
|
+
/**
|
|
34
|
+
* 批量获取实例 key 并反序列化为对象数组
|
|
35
|
+
*/
|
|
36
|
+
private fetchInstancesByKeys;
|
|
37
|
+
}
|
|
38
|
+
export declare class RedisServiceDiscoverClient extends ServiceDiscoveryClient<Redis, RedisServiceDiscoveryOptions, RedisInstanceMetadata> {
|
|
39
|
+
protected redis: Redis;
|
|
40
|
+
protected serviceDiscoveryOptions: RedisServiceDiscoveryOptions;
|
|
41
|
+
protected logger: ILogger;
|
|
42
|
+
protected getListener: (serviceName: string) => Promise<RedisDataListener>;
|
|
43
|
+
private registeredInstance;
|
|
44
|
+
private onlineInstanceData;
|
|
45
|
+
constructor(redis: Redis, serviceDiscoveryOptions: RedisServiceDiscoveryOptions, logger: ILogger, getListener: (serviceName: string) => Promise<RedisDataListener>);
|
|
46
|
+
register(instance: RedisInstanceMetadata): Promise<void>;
|
|
47
|
+
deregister(): Promise<void>;
|
|
48
|
+
online(): Promise<void>;
|
|
49
|
+
offline(): Promise<void>;
|
|
50
|
+
beforeStop(): Promise<void>;
|
|
51
|
+
}
|
|
52
|
+
export declare class RedisServiceDiscovery extends ServiceDiscovery<Redis, RedisServiceDiscoveryOptions, RedisInstanceMetadata, RedisInstanceMetadata, string> {
|
|
53
|
+
private redisServiceFactory;
|
|
54
|
+
private configService;
|
|
55
|
+
private coreLogger;
|
|
56
|
+
private redisServiceDiscoveryOptions;
|
|
57
|
+
private listenerStore;
|
|
58
|
+
private pubsub;
|
|
59
|
+
init(): Promise<void>;
|
|
60
|
+
getServiceClient(): Redis;
|
|
61
|
+
protected createServiceDiscoverClientImpl(options: RedisServiceDiscoveryOptions): RedisServiceDiscoverClient;
|
|
62
|
+
protected getDefaultServiceDiscoveryOptions(): RedisServiceDiscoveryOptions;
|
|
63
|
+
private getListener;
|
|
64
|
+
getInstances(serviceName: string): Promise<RedisInstanceMetadata[]>;
|
|
65
|
+
beforeStop(): Promise<void>;
|
|
66
|
+
}
|
|
67
|
+
export {};
|
|
68
|
+
//# sourceMappingURL=serviceDiscovery.d.ts.map
|
|
@@ -0,0 +1,312 @@
|
|
|
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.RedisServiceDiscovery = exports.RedisServiceDiscoverClient = void 0;
|
|
13
|
+
const core_1 = require("@midwayjs/core");
|
|
14
|
+
const manager_1 = require("../manager");
|
|
15
|
+
class RedisDataListener extends core_1.DataListener {
|
|
16
|
+
constructor(serviceName, client, pubsub, options, logger) {
|
|
17
|
+
super();
|
|
18
|
+
this.client = client;
|
|
19
|
+
this.pubsub = pubsub;
|
|
20
|
+
this.options = options;
|
|
21
|
+
this.logger = logger;
|
|
22
|
+
this.unsubscribe = null;
|
|
23
|
+
// 新增:定时刷新 TTL
|
|
24
|
+
this.ttlTimeout = null;
|
|
25
|
+
this.instance = null;
|
|
26
|
+
this.instanceKey = null;
|
|
27
|
+
this.serviceName = serviceName;
|
|
28
|
+
}
|
|
29
|
+
async init() {
|
|
30
|
+
await super.init();
|
|
31
|
+
}
|
|
32
|
+
async initData() {
|
|
33
|
+
// 1. 用 SCAN 替代 KEYS 获取所有实例 key
|
|
34
|
+
const keys = await this.scanKeys(`${this.options.prefix}${this.serviceName}:instance:*`);
|
|
35
|
+
return this.fetchInstancesByKeys(keys);
|
|
36
|
+
}
|
|
37
|
+
onData(setData) {
|
|
38
|
+
// 订阅当前 serviceName 的变更消息
|
|
39
|
+
const channel = `service:change:${this.serviceName}`;
|
|
40
|
+
const handler = async (channelName, message) => {
|
|
41
|
+
this.logger.debug(`[midway:redis] pubsub event received: channel=${channelName}, message=${message}`);
|
|
42
|
+
if (channelName !== channel)
|
|
43
|
+
return;
|
|
44
|
+
try {
|
|
45
|
+
// 变更时重新拉取全量数据
|
|
46
|
+
const keys = await this.scanKeys(`${this.options.prefix}${this.serviceName}:instance:*`);
|
|
47
|
+
setData(await this.fetchInstancesByKeys(keys));
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
this.logger.error('[midway:redis] Error on service change:', err);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
this.pubsub.subscribe(channel);
|
|
54
|
+
this.pubsub.on('message', handler);
|
|
55
|
+
// 提供取消订阅方法
|
|
56
|
+
this.unsubscribe = () => {
|
|
57
|
+
this.pubsub.off('message', handler);
|
|
58
|
+
this.pubsub.unsubscribe(channel);
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
async destroyListener() {
|
|
62
|
+
this.clearTTLRefresh();
|
|
63
|
+
if (this.unsubscribe) {
|
|
64
|
+
this.unsubscribe();
|
|
65
|
+
this.unsubscribe = null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// 新增:上线时启动 TTL 刷新
|
|
69
|
+
async onlineInstance(instance) {
|
|
70
|
+
instance = this.instance = instance || this.instance;
|
|
71
|
+
this.instanceKey = this.getInstanceKey(instance.serviceName, instance.id);
|
|
72
|
+
const data = { ...instance, status: 'UP' };
|
|
73
|
+
const ttl = instance.ttl || this.options.ttl || 30;
|
|
74
|
+
this.logger.debug(`[midway:redis] set key: ${this.instanceKey}, value: ${JSON.stringify(data)}, ttl: ${ttl}`);
|
|
75
|
+
await this.client.set(this.instanceKey, JSON.stringify(data), 'EX', ttl);
|
|
76
|
+
this.startTTLRefresh();
|
|
77
|
+
const testKeys = await this.client.scan('0', 'MATCH', `${this.options.prefix}${instance.serviceName}:instance:*`, 'COUNT', 100);
|
|
78
|
+
this.logger.debug(`[midway:redis] after set, scan test: ${JSON.stringify(testKeys)}`);
|
|
79
|
+
}
|
|
80
|
+
// 新增:下线时清理 TTL 刷新
|
|
81
|
+
async offlineInstance(instance) {
|
|
82
|
+
instance = instance || this.instance;
|
|
83
|
+
this.clearTTLRefresh();
|
|
84
|
+
const instanceKey = this.getInstanceKey(instance.serviceName, instance.id);
|
|
85
|
+
await this.client.del(instanceKey);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* 生成实例唯一 key
|
|
89
|
+
* 格式: <prefix><serviceName>:instance:<instanceId>
|
|
90
|
+
* 例: services:order:instance:abc123
|
|
91
|
+
*/
|
|
92
|
+
getInstanceKey(serviceName, instanceId) {
|
|
93
|
+
return `${this.options.prefix}${serviceName}:instance:${instanceId}`;
|
|
94
|
+
}
|
|
95
|
+
startTTLRefresh() {
|
|
96
|
+
this.clearTTLRefresh();
|
|
97
|
+
if (!this.instance)
|
|
98
|
+
return;
|
|
99
|
+
const ttl = this.instance.ttl || this.options.ttl || 30;
|
|
100
|
+
const interval = Math.max(ttl * 500, 1000);
|
|
101
|
+
const refresh = async () => {
|
|
102
|
+
try {
|
|
103
|
+
if (this.instanceKey && this.instance) {
|
|
104
|
+
const data = { ...this.instance, status: 'UP' };
|
|
105
|
+
await this.client.set(this.instanceKey, JSON.stringify(data), 'EX', ttl);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
this.logger.error('[midway:redis] TTL refresh failed:', err);
|
|
110
|
+
}
|
|
111
|
+
this.ttlTimeout = setTimeout(refresh, interval);
|
|
112
|
+
};
|
|
113
|
+
refresh();
|
|
114
|
+
}
|
|
115
|
+
clearTTLRefresh() {
|
|
116
|
+
if (this.ttlTimeout) {
|
|
117
|
+
clearTimeout(this.ttlTimeout);
|
|
118
|
+
this.ttlTimeout = null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* 使用 SCAN 替代 KEYS,游标式遍历所有匹配的 key
|
|
123
|
+
*/
|
|
124
|
+
async scanKeys(pattern, count) {
|
|
125
|
+
let cursor = '0';
|
|
126
|
+
let keys = [];
|
|
127
|
+
const scanCount = count || this.options.scanCount || 100;
|
|
128
|
+
do {
|
|
129
|
+
const [nextCursor, foundKeys] = await this.client.scan(cursor, 'MATCH', pattern, 'COUNT', scanCount);
|
|
130
|
+
cursor = nextCursor;
|
|
131
|
+
keys = keys.concat(foundKeys);
|
|
132
|
+
} while (cursor !== '0');
|
|
133
|
+
this.logger.debug(`[midway:redis] scanKeys pattern=${pattern}, found keys=${JSON.stringify(keys)}`);
|
|
134
|
+
return keys;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* 批量获取实例 key 并反序列化为对象数组
|
|
138
|
+
*/
|
|
139
|
+
async fetchInstancesByKeys(keys) {
|
|
140
|
+
if (keys.length === 0)
|
|
141
|
+
return [];
|
|
142
|
+
const values = await this.client.mget(keys);
|
|
143
|
+
return values
|
|
144
|
+
.map(value => {
|
|
145
|
+
try {
|
|
146
|
+
return JSON.parse(value);
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
})
|
|
152
|
+
.filter(Boolean);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
class RedisServiceDiscoverClient extends core_1.ServiceDiscoveryClient {
|
|
156
|
+
constructor(redis, serviceDiscoveryOptions, logger, getListener) {
|
|
157
|
+
super(redis, serviceDiscoveryOptions);
|
|
158
|
+
this.redis = redis;
|
|
159
|
+
this.serviceDiscoveryOptions = serviceDiscoveryOptions;
|
|
160
|
+
this.logger = logger;
|
|
161
|
+
this.getListener = getListener;
|
|
162
|
+
this.registeredInstance = null;
|
|
163
|
+
this.onlineInstanceData = null;
|
|
164
|
+
}
|
|
165
|
+
async register(instance) {
|
|
166
|
+
this.registeredInstance = instance;
|
|
167
|
+
if (!instance.serviceName) {
|
|
168
|
+
throw new core_1.MidwayConfigMissingError('instance.serviceName is required when register service in redis');
|
|
169
|
+
}
|
|
170
|
+
// 注册时默认上线
|
|
171
|
+
await this.online();
|
|
172
|
+
this.logger.info(`[midway:redis] register instance: ${instance.id} for service: ${instance.serviceName}`);
|
|
173
|
+
}
|
|
174
|
+
async deregister() {
|
|
175
|
+
if (this.onlineInstanceData) {
|
|
176
|
+
await this.offline();
|
|
177
|
+
}
|
|
178
|
+
if (this.registeredInstance) {
|
|
179
|
+
// 发布服务变更消息
|
|
180
|
+
await this.client.publish(`service:change:${this.registeredInstance.serviceName}`, JSON.stringify({
|
|
181
|
+
type: 'deregister',
|
|
182
|
+
service: this.registeredInstance.serviceName,
|
|
183
|
+
instance: this.registeredInstance,
|
|
184
|
+
}));
|
|
185
|
+
this.logger.info(`[midway:redis] deregister instance: ${this.registeredInstance.id} for service: ${this.registeredInstance.serviceName}`);
|
|
186
|
+
this.registeredInstance = null;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
async online() {
|
|
190
|
+
this.logger.debug('[midway:redis] online() called');
|
|
191
|
+
this.logger.debug(`[midway:redis] registeredInstance: ${JSON.stringify(this.registeredInstance)}`);
|
|
192
|
+
if (!this.registeredInstance) {
|
|
193
|
+
this.logger.error('[midway:redis] online() failed: No instance registered.');
|
|
194
|
+
throw new Error('No instance registered, cannot online.');
|
|
195
|
+
}
|
|
196
|
+
// 已经上线则忽略
|
|
197
|
+
if (this.onlineInstanceData) {
|
|
198
|
+
this.logger.debug('[midway:redis] online() ignored: already online.');
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
const listener = await this.getListener(this.registeredInstance.serviceName);
|
|
202
|
+
await listener.onlineInstance(this.registeredInstance);
|
|
203
|
+
this.onlineInstanceData = this.registeredInstance;
|
|
204
|
+
this.logger.info(`[midway:redis] online() success: instance ${this.registeredInstance.id} for service ${this.registeredInstance.serviceName}`);
|
|
205
|
+
// 发布上线消息
|
|
206
|
+
await this.client.publish(`service:change:${this.registeredInstance.serviceName}`, JSON.stringify({
|
|
207
|
+
type: 'online',
|
|
208
|
+
service: this.registeredInstance.serviceName,
|
|
209
|
+
instance: this.registeredInstance,
|
|
210
|
+
}));
|
|
211
|
+
}
|
|
212
|
+
async offline() {
|
|
213
|
+
this.logger.debug('[midway:redis] offline() called');
|
|
214
|
+
this.logger.debug(`[midway:redis] onlineInstanceData: ${JSON.stringify(this.onlineInstanceData)}`);
|
|
215
|
+
// 已经下线则忽略
|
|
216
|
+
if (!this.onlineInstanceData) {
|
|
217
|
+
this.logger.info('[midway:redis] offline() ignored: already offline.');
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
const listener = await this.getListener(this.onlineInstanceData.serviceName);
|
|
221
|
+
await listener.offlineInstance(this.onlineInstanceData);
|
|
222
|
+
this.logger.info(`[midway:redis] offline() success: instance ${this.onlineInstanceData.id} for service ${this.onlineInstanceData.serviceName}`);
|
|
223
|
+
// 发布下线消息
|
|
224
|
+
await this.client.publish(`service:change:${this.onlineInstanceData.serviceName}`, JSON.stringify({
|
|
225
|
+
type: 'offline',
|
|
226
|
+
service: this.onlineInstanceData.serviceName,
|
|
227
|
+
instance: this.onlineInstanceData,
|
|
228
|
+
}));
|
|
229
|
+
this.onlineInstanceData = null;
|
|
230
|
+
}
|
|
231
|
+
async beforeStop() { }
|
|
232
|
+
}
|
|
233
|
+
exports.RedisServiceDiscoverClient = RedisServiceDiscoverClient;
|
|
234
|
+
let RedisServiceDiscovery = class RedisServiceDiscovery extends core_1.ServiceDiscovery {
|
|
235
|
+
constructor() {
|
|
236
|
+
super(...arguments);
|
|
237
|
+
this.listenerStore = new Map();
|
|
238
|
+
}
|
|
239
|
+
async init() {
|
|
240
|
+
this.redisServiceDiscoveryOptions = this.configService.getConfiguration('redis.serviceDiscovery', {});
|
|
241
|
+
this.redisServiceDiscoveryOptions.prefix =
|
|
242
|
+
this.redisServiceDiscoveryOptions.prefix || 'services:';
|
|
243
|
+
}
|
|
244
|
+
getServiceClient() {
|
|
245
|
+
return this.redisServiceFactory.get(this.redisServiceDiscoveryOptions.serviceDiscoveryClient ||
|
|
246
|
+
this.redisServiceFactory.getDefaultClientName() ||
|
|
247
|
+
'default');
|
|
248
|
+
}
|
|
249
|
+
createServiceDiscoverClientImpl(options) {
|
|
250
|
+
return new RedisServiceDiscoverClient(this.getServiceClient(), options, this.coreLogger, serviceName => this.getListener(serviceName));
|
|
251
|
+
}
|
|
252
|
+
getDefaultServiceDiscoveryOptions() {
|
|
253
|
+
return this.redisServiceDiscoveryOptions;
|
|
254
|
+
}
|
|
255
|
+
async getListener(serviceName) {
|
|
256
|
+
if (!this.pubsub) {
|
|
257
|
+
this.pubsub = this.getServiceClient().duplicate();
|
|
258
|
+
}
|
|
259
|
+
// 日志:打印当前请求的 serviceName
|
|
260
|
+
this.coreLogger.debug(`[midway:redis] getListener called for serviceName: ${serviceName}`);
|
|
261
|
+
// 日志:打印当前 listenerStore keys
|
|
262
|
+
this.coreLogger.debug(`[midway:redis] listenerStore keys: ${Array.from(this.listenerStore.keys()).join(',')}`);
|
|
263
|
+
if (!this.listenerStore.has(serviceName)) {
|
|
264
|
+
this.coreLogger.debug(`[midway:redis] listener for ${serviceName} not found, creating new listener.`);
|
|
265
|
+
const listener = new RedisDataListener(serviceName, this.getServiceClient(), this.pubsub, this.redisServiceDiscoveryOptions, this.coreLogger);
|
|
266
|
+
this.listenerStore.set(serviceName, listener);
|
|
267
|
+
await listener.init();
|
|
268
|
+
this.coreLogger.debug(`[midway:redis] listener for ${serviceName} initialized.`);
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
this.coreLogger.debug(`[midway:redis] listener for ${serviceName} found in cache.`);
|
|
272
|
+
}
|
|
273
|
+
return this.listenerStore.get(serviceName);
|
|
274
|
+
}
|
|
275
|
+
async getInstances(serviceName) {
|
|
276
|
+
// 优先从 listener 的缓存获取数据
|
|
277
|
+
const listener = await this.getListener(serviceName);
|
|
278
|
+
return listener.getData();
|
|
279
|
+
}
|
|
280
|
+
async beforeStop() {
|
|
281
|
+
await Promise.all(Array.from(this.listenerStore.values()).map(listener => listener.destroyListener()));
|
|
282
|
+
this.listenerStore.clear();
|
|
283
|
+
if (this.pubsub) {
|
|
284
|
+
await this.pubsub.unsubscribe();
|
|
285
|
+
await this.pubsub.quit();
|
|
286
|
+
this.pubsub = null;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
exports.RedisServiceDiscovery = RedisServiceDiscovery;
|
|
291
|
+
__decorate([
|
|
292
|
+
(0, core_1.Inject)(),
|
|
293
|
+
__metadata("design:type", manager_1.RedisServiceFactory)
|
|
294
|
+
], RedisServiceDiscovery.prototype, "redisServiceFactory", void 0);
|
|
295
|
+
__decorate([
|
|
296
|
+
(0, core_1.Inject)(),
|
|
297
|
+
__metadata("design:type", core_1.MidwayConfigService)
|
|
298
|
+
], RedisServiceDiscovery.prototype, "configService", void 0);
|
|
299
|
+
__decorate([
|
|
300
|
+
(0, core_1.Logger)(),
|
|
301
|
+
__metadata("design:type", Object)
|
|
302
|
+
], RedisServiceDiscovery.prototype, "coreLogger", void 0);
|
|
303
|
+
__decorate([
|
|
304
|
+
(0, core_1.Init)(),
|
|
305
|
+
__metadata("design:type", Function),
|
|
306
|
+
__metadata("design:paramtypes", []),
|
|
307
|
+
__metadata("design:returntype", Promise)
|
|
308
|
+
], RedisServiceDiscovery.prototype, "init", null);
|
|
309
|
+
exports.RedisServiceDiscovery = RedisServiceDiscovery = __decorate([
|
|
310
|
+
(0, core_1.Singleton)()
|
|
311
|
+
], RedisServiceDiscovery);
|
|
312
|
+
//# sourceMappingURL=serviceDiscovery.js.map
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -21,4 +21,5 @@ var configuration_1 = require("./configuration");
|
|
|
21
21
|
Object.defineProperty(exports, "Configuration", { enumerable: true, get: function () { return configuration_1.RedisConfiguration; } });
|
|
22
22
|
__exportStar(require("./manager"), exports);
|
|
23
23
|
__exportStar(require("./interface"), exports);
|
|
24
|
+
__exportStar(require("./extension/serviceDiscovery"), exports);
|
|
24
25
|
//# sourceMappingURL=index.js.map
|
package/dist/interface.d.ts
CHANGED
|
@@ -1,7 +1,69 @@
|
|
|
1
1
|
import * as Redis from 'ioredis';
|
|
2
2
|
import { ClusterNode, ClusterOptions } from 'ioredis';
|
|
3
|
+
import { DefaultInstanceMetadata, ServiceDiscoveryBaseInstance, ServiceDiscoveryOptions, ServiceFactoryConfigOption } from '@midwayjs/core';
|
|
3
4
|
export type RedisConfigOptions = Redis.RedisOptions | ({
|
|
4
|
-
cluster?: boolean;
|
|
5
5
|
nodes?: ClusterNode[];
|
|
6
6
|
} & ClusterOptions);
|
|
7
|
+
export interface RedisInstanceMetadata extends ServiceDiscoveryBaseInstance {
|
|
8
|
+
/**
|
|
9
|
+
* 服务名称
|
|
10
|
+
*/
|
|
11
|
+
serviceName: string;
|
|
12
|
+
/**
|
|
13
|
+
* 服务实例 ID
|
|
14
|
+
*/
|
|
15
|
+
id: string;
|
|
16
|
+
/**
|
|
17
|
+
* 服务实例的地址
|
|
18
|
+
*/
|
|
19
|
+
host: string;
|
|
20
|
+
/**
|
|
21
|
+
* 服务实例的端口
|
|
22
|
+
*/
|
|
23
|
+
port: number;
|
|
24
|
+
/**
|
|
25
|
+
* 服务实例的过期时间(秒)
|
|
26
|
+
*/
|
|
27
|
+
ttl?: number;
|
|
28
|
+
/**
|
|
29
|
+
* 服务实例的标签
|
|
30
|
+
*/
|
|
31
|
+
tags?: string[];
|
|
32
|
+
/**
|
|
33
|
+
* 服务实例的元数据
|
|
34
|
+
*/
|
|
35
|
+
meta?: Record<string, string>;
|
|
36
|
+
/**
|
|
37
|
+
* 服务实例的状态
|
|
38
|
+
*/
|
|
39
|
+
status?: 'UP' | 'DOWN';
|
|
40
|
+
}
|
|
41
|
+
export interface RedisServiceDiscoveryOptions extends ServiceDiscoveryOptions<RedisInstanceMetadata> {
|
|
42
|
+
/**
|
|
43
|
+
* 服务信息的过期时间(秒)
|
|
44
|
+
*/
|
|
45
|
+
ttl?: number;
|
|
46
|
+
/**
|
|
47
|
+
* 服务信息的 key 前缀
|
|
48
|
+
*/
|
|
49
|
+
prefix?: string;
|
|
50
|
+
/**
|
|
51
|
+
* 服务实例配置
|
|
52
|
+
*/
|
|
53
|
+
serviceOptions?: RedisInstanceMetadata | ((meta: DefaultInstanceMetadata) => RedisInstanceMetadata);
|
|
54
|
+
/**
|
|
55
|
+
* 下线状态的 TTL(秒)
|
|
56
|
+
*/
|
|
57
|
+
downTTL?: number;
|
|
58
|
+
/**
|
|
59
|
+
* SCAN 命令每次遍历的数量,默认 100
|
|
60
|
+
*/
|
|
61
|
+
scanCount?: number;
|
|
62
|
+
}
|
|
63
|
+
export type MidwayRedisConfigOptions = ServiceFactoryConfigOption<RedisConfigOptions> & {
|
|
64
|
+
/**
|
|
65
|
+
* Redis 服务发现配置
|
|
66
|
+
*/
|
|
67
|
+
serviceDiscovery?: RedisServiceDiscoveryOptions;
|
|
68
|
+
};
|
|
7
69
|
//# sourceMappingURL=interface.d.ts.map
|
package/dist/manager.d.ts
CHANGED
|
@@ -5,9 +5,9 @@ export declare class RedisServiceFactory extends ServiceFactory<Redis> {
|
|
|
5
5
|
protected redisConfig: ServiceFactoryConfigOption<RedisConfigOptions>;
|
|
6
6
|
protected init(): Promise<void>;
|
|
7
7
|
protected logger: ILogger;
|
|
8
|
-
protected createClient(config: any): Promise<Redis>;
|
|
8
|
+
protected createClient(config: any, name: string): Promise<Redis>;
|
|
9
9
|
getName(): string;
|
|
10
|
-
protected destroyClient(redisInstance:
|
|
10
|
+
protected destroyClient(redisInstance: Redis, name: string): Promise<void>;
|
|
11
11
|
}
|
|
12
12
|
export declare class RedisService implements Redis {
|
|
13
13
|
private serviceFactory;
|
package/dist/manager.js
CHANGED
|
@@ -15,38 +15,40 @@ const ioredis_1 = require("ioredis");
|
|
|
15
15
|
const assert = require("assert");
|
|
16
16
|
let RedisServiceFactory = class RedisServiceFactory extends core_1.ServiceFactory {
|
|
17
17
|
async init() {
|
|
18
|
-
await this.initClients(this.redisConfig
|
|
18
|
+
await this.initClients(this.redisConfig, {
|
|
19
|
+
concurrent: true,
|
|
20
|
+
});
|
|
19
21
|
}
|
|
20
|
-
async createClient(config) {
|
|
22
|
+
async createClient(config, name) {
|
|
21
23
|
let client;
|
|
22
24
|
if (config.cluster === true) {
|
|
23
|
-
assert(config.nodes && config.nodes.length !== 0,
|
|
25
|
+
assert.ok(config.nodes && config.nodes.length !== 0, `[midway:redis] client(${name}) cluster nodes configuration is required when use cluster redis`);
|
|
24
26
|
config.nodes.forEach(client => {
|
|
25
|
-
assert(client.host && client.port, `[midway:redis] 'host: ${client.host}', 'port: ${client.port}' are required on config`);
|
|
27
|
+
assert.ok(client.host && client.port, `[midway:redis] client(${name}) 'host: ${client.host}', 'port: ${client.port}' are required on config`);
|
|
26
28
|
});
|
|
27
|
-
this.logger.info('[midway:redis] cluster connecting');
|
|
28
29
|
client = new ioredis_1.default.Cluster(config.nodes, config);
|
|
30
|
+
this.logger.info('[midway:redis] cluster is connecting');
|
|
29
31
|
}
|
|
30
32
|
else if (config.sentinels) {
|
|
31
|
-
assert(config.sentinels && config.sentinels.length !== 0,
|
|
33
|
+
assert.ok(config.sentinels && config.sentinels.length !== 0, `[midway:redis] client(${name}) sentinels configuration is required when use redis sentinel`);
|
|
32
34
|
config.sentinels.forEach(sentinel => {
|
|
33
|
-
assert(sentinel.host && sentinel.port, `[midway:redis] 'host: ${sentinel.host}', 'port: ${sentinel.port}' are required on config`);
|
|
35
|
+
assert.ok(sentinel.host && sentinel.port, `[midway:redis] client(${name}) 'host: ${sentinel.host}', 'port: ${sentinel.port}' are required on config`);
|
|
34
36
|
});
|
|
35
|
-
this.logger.info('[midway:redis] sentinel connecting start');
|
|
36
37
|
client = new ioredis_1.default(config);
|
|
38
|
+
this.logger.info(`[midway:redis] client(${name}) sentinel is connecting`);
|
|
37
39
|
}
|
|
38
40
|
else {
|
|
39
|
-
assert(config.host && config.port, `[midway:redis] 'host: ${config.host}', 'port: ${config.port}' are required on config`);
|
|
40
|
-
this.logger.info('[midway:redis] server connecting redis://:***@%s:%s', config.host, config.port);
|
|
41
|
+
assert.ok(config.host && config.port, `[midway:redis] client(${name}) 'host: ${config.host}', 'port: ${config.port}' are required on config`);
|
|
41
42
|
client = new ioredis_1.default(config);
|
|
43
|
+
this.logger.info(`[midway:redis] client(${name}) server is connecting redis://:***@${config.host}:${config.port}`);
|
|
42
44
|
}
|
|
43
45
|
await new Promise((resolve, reject) => {
|
|
44
|
-
client.on('
|
|
45
|
-
this.logger.info(
|
|
46
|
+
client.on('ready', () => {
|
|
47
|
+
this.logger.info(`[midway:redis] client(${name}) connect success`);
|
|
46
48
|
resolve();
|
|
47
49
|
});
|
|
48
50
|
client.on('error', err => {
|
|
49
|
-
this.logger.error(
|
|
51
|
+
this.logger.error(`[midway:redis] client(${name}) error: ${err}`);
|
|
50
52
|
reject(err);
|
|
51
53
|
});
|
|
52
54
|
});
|
|
@@ -55,20 +57,22 @@ let RedisServiceFactory = class RedisServiceFactory extends core_1.ServiceFactor
|
|
|
55
57
|
getName() {
|
|
56
58
|
return 'redis';
|
|
57
59
|
}
|
|
58
|
-
async destroyClient(redisInstance) {
|
|
60
|
+
async destroyClient(redisInstance, name) {
|
|
59
61
|
try {
|
|
60
62
|
if (redisInstance) {
|
|
61
63
|
const canQuit = !['end', 'close'].includes(redisInstance.status);
|
|
62
64
|
if (canQuit) {
|
|
63
65
|
await redisInstance.quit();
|
|
66
|
+
this.logger.info(`[midway:redis] client(${name}) quit success`);
|
|
64
67
|
}
|
|
65
68
|
}
|
|
66
69
|
}
|
|
67
70
|
catch (error) {
|
|
68
|
-
this.logger.error(
|
|
71
|
+
this.logger.error(`[midway:redis] client(${name}) Redis quit failed.`, error);
|
|
69
72
|
}
|
|
70
73
|
}
|
|
71
74
|
};
|
|
75
|
+
exports.RedisServiceFactory = RedisServiceFactory;
|
|
72
76
|
__decorate([
|
|
73
77
|
(0, core_1.Config)('redis'),
|
|
74
78
|
__metadata("design:type", Object)
|
|
@@ -83,20 +87,19 @@ __decorate([
|
|
|
83
87
|
(0, core_1.Logger)('coreLogger'),
|
|
84
88
|
__metadata("design:type", Object)
|
|
85
89
|
], RedisServiceFactory.prototype, "logger", void 0);
|
|
86
|
-
RedisServiceFactory = __decorate([
|
|
90
|
+
exports.RedisServiceFactory = RedisServiceFactory = __decorate([
|
|
87
91
|
(0, core_1.Provide)(),
|
|
88
92
|
(0, core_1.Scope)(core_1.ScopeEnum.Singleton)
|
|
89
93
|
], RedisServiceFactory);
|
|
90
|
-
exports.RedisServiceFactory = RedisServiceFactory;
|
|
91
94
|
let RedisService = class RedisService {
|
|
92
95
|
async init() {
|
|
93
|
-
|
|
94
|
-
this.instance = this.serviceFactory.get(((_b = (_a = this.serviceFactory).getDefaultClientName) === null || _b === void 0 ? void 0 : _b.call(_a)) || 'default');
|
|
96
|
+
this.instance = this.serviceFactory.get(this.serviceFactory.getDefaultClientName?.() || 'default');
|
|
95
97
|
if (!this.instance) {
|
|
96
98
|
throw new core_1.MidwayCommonError('redis default instance not found.');
|
|
97
99
|
}
|
|
98
100
|
}
|
|
99
101
|
};
|
|
102
|
+
exports.RedisService = RedisService;
|
|
100
103
|
__decorate([
|
|
101
104
|
(0, core_1.Inject)(),
|
|
102
105
|
__metadata("design:type", RedisServiceFactory)
|
|
@@ -107,11 +110,10 @@ __decorate([
|
|
|
107
110
|
__metadata("design:paramtypes", []),
|
|
108
111
|
__metadata("design:returntype", Promise)
|
|
109
112
|
], RedisService.prototype, "init", null);
|
|
110
|
-
RedisService = __decorate([
|
|
113
|
+
exports.RedisService = RedisService = __decorate([
|
|
111
114
|
(0, core_1.Provide)(),
|
|
112
115
|
(0, core_1.Scope)(core_1.ScopeEnum.Singleton)
|
|
113
116
|
], RedisService);
|
|
114
|
-
exports.RedisService = RedisService;
|
|
115
117
|
(0, core_1.delegateTargetAllPrototypeMethod)(RedisService, ioredis_1.default);
|
|
116
118
|
RedisService.prototype.defineCommand = function (name, definition) {
|
|
117
119
|
this.instance.defineCommand(name, definition);
|
package/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { MidwayRedisConfigOptions } from './dist';
|
|
2
2
|
export * from './dist/index';
|
|
3
3
|
|
|
4
4
|
// Single Redis
|
|
@@ -48,6 +48,6 @@ export * from './dist/index';
|
|
|
48
48
|
|
|
49
49
|
declare module '@midwayjs/core/dist/interface' {
|
|
50
50
|
interface MidwayConfig {
|
|
51
|
-
redis?:
|
|
51
|
+
redis?: MidwayRedisConfigOptions;
|
|
52
52
|
}
|
|
53
53
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@midwayjs/redis",
|
|
3
3
|
"description": "midway redis component",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "4.0.0-beta.1",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"typings": "index.d.ts",
|
|
7
7
|
"files": [
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
"index.d.ts"
|
|
11
11
|
],
|
|
12
12
|
"devDependencies": {
|
|
13
|
-
"@midwayjs/core": "^
|
|
14
|
-
"@midwayjs/mock": "^
|
|
13
|
+
"@midwayjs/core": "^4.0.0-beta.1",
|
|
14
|
+
"@midwayjs/mock": "^4.0.0-beta.1"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"ioredis": "5.4.2"
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
"license": "MIT",
|
|
24
24
|
"scripts": {
|
|
25
25
|
"build": "tsc",
|
|
26
|
-
"test": "node
|
|
27
|
-
"cov": "node
|
|
26
|
+
"test": "node -r ts-node/register ../../node_modules/jest/bin/jest.js --runInBand",
|
|
27
|
+
"cov": "node -r ts-node/register ../../node_modules/jest/bin/jest.js --runInBand --coverage --forceExit",
|
|
28
28
|
"ci": "npm run test",
|
|
29
29
|
"lint": "mwts check"
|
|
30
30
|
},
|
|
@@ -35,5 +35,5 @@
|
|
|
35
35
|
"type": "git",
|
|
36
36
|
"url": "https://github.com/midwayjs/midway.git"
|
|
37
37
|
},
|
|
38
|
-
"gitHead": "
|
|
38
|
+
"gitHead": "832961ec3aff123c033197d8c00cb2bc9bad7ff8"
|
|
39
39
|
}
|