refactor: 客户端目录统一添加 kenaito-conf- 前缀
- go-client → kenaito-conf-go-client - python-client → kenaito-conf-python-client - python-client-demo → kenaito-conf-python-client-demo - nodejs-client → kenaito-conf-nodejs-client - nodejs-client-demo → kenaito-conf-nodejs-client-demo - 更新所有 README 和 package.json 中的路径引用
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
import { Config } from './config.js';
|
||||
import { Cache } from './cache.js';
|
||||
import { Listener } from './listener.js';
|
||||
import { decryptConfig } from './crypto.js';
|
||||
|
||||
export class ConfigClient {
|
||||
constructor(config) {
|
||||
this.config = config;
|
||||
this.cache = new Cache(config.cacheDir);
|
||||
this.listener = new Listener();
|
||||
this.configs = {};
|
||||
this.secret = '';
|
||||
this.globalSecret = '';
|
||||
this.lastTime = 0;
|
||||
this.started = false;
|
||||
this.timer = null;
|
||||
}
|
||||
|
||||
onChange(handler) {
|
||||
this.listener.onChange(handler);
|
||||
}
|
||||
|
||||
get(key) {
|
||||
return this.configs[key] ?? null;
|
||||
}
|
||||
|
||||
getAll() {
|
||||
return { ...this.configs };
|
||||
}
|
||||
|
||||
async start() {
|
||||
if (this.started) {
|
||||
return;
|
||||
}
|
||||
this.started = true;
|
||||
|
||||
try {
|
||||
await this.fetchAll();
|
||||
} catch (err) {
|
||||
console.warn('[ConfigClient] fetch all configs error:', err.message, ', trying cache');
|
||||
const cached = this.cache.read();
|
||||
if (cached) {
|
||||
this.applyConfigs(cached);
|
||||
console.info('[ConfigClient] loaded', Object.keys(this.configs).length, 'configs from cache');
|
||||
} else {
|
||||
throw new Error(`fetch configs failed and no cache: ${err.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
this.timer = setInterval(() => {
|
||||
this.fetchChanged().catch((err) => {
|
||||
console.error('[ConfigClient] sync error:', err);
|
||||
});
|
||||
}, this.config.syncInterval * 1000);
|
||||
}
|
||||
|
||||
stop() {
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer);
|
||||
this.timer = null;
|
||||
}
|
||||
}
|
||||
|
||||
async fetchAll() {
|
||||
const url = `${this.config.baseUrl}/api/fetchAllConfigs`;
|
||||
const payload = {
|
||||
appName: this.config.appName,
|
||||
env: this.config.env,
|
||||
version: this.config.version,
|
||||
ignoreGlobal: this.config.ignoreGlobal,
|
||||
token: this.config.token,
|
||||
};
|
||||
const resp = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
signal: AbortSignal.timeout(10000),
|
||||
});
|
||||
if (!resp.ok) {
|
||||
throw new Error(`HTTP ${resp.status}`);
|
||||
}
|
||||
const data = await resp.json();
|
||||
if (data.code != null && data.code !== 0) {
|
||||
throw new Error(`server error: ${data.msg || 'unknown'}`);
|
||||
}
|
||||
|
||||
const secret = data['kenaito.config.encrypt-secret'];
|
||||
if (secret) {
|
||||
this.secret = secret;
|
||||
}
|
||||
const globalSecret = data['kenaito.config.global-encrypt-secret'];
|
||||
if (globalSecret) {
|
||||
this.globalSecret = globalSecret;
|
||||
}
|
||||
const ts = data['latest.fetch.timestamp'];
|
||||
if (ts) {
|
||||
this.lastTime = parseInt(ts, 10);
|
||||
}
|
||||
|
||||
this.cache.write(data);
|
||||
this.applyConfigs(data);
|
||||
console.info('[ConfigClient] fetched', Object.keys(this.configs).length, 'configs');
|
||||
}
|
||||
|
||||
async fetchChanged() {
|
||||
const url = `${this.config.baseUrl}/api/fetchChangedConfigs`;
|
||||
const payload = {
|
||||
appName: this.config.appName,
|
||||
env: this.config.env,
|
||||
version: this.config.version,
|
||||
lastTime: this.lastTime,
|
||||
ignoreGlobal: this.config.ignoreGlobal,
|
||||
token: this.config.token,
|
||||
};
|
||||
const resp = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...(this.config.token ? { Authorization: `Bearer ${this.config.token}` } : {}),
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
signal: AbortSignal.timeout(10000),
|
||||
});
|
||||
if (!resp.ok) {
|
||||
throw new Error(`HTTP ${resp.status}`);
|
||||
}
|
||||
const result = await resp.json();
|
||||
if (result.code !== 0) {
|
||||
throw new Error(`server error code: ${result.code}`);
|
||||
}
|
||||
|
||||
const changedData = result.data || {};
|
||||
const ts = changedData['latest.fetch.timestamp'];
|
||||
if (ts) {
|
||||
this.lastTime = parseInt(ts, 10);
|
||||
}
|
||||
delete changedData['latest.fetch.timestamp'];
|
||||
|
||||
const changed = {};
|
||||
for (const [k, v] of Object.entries(changedData)) {
|
||||
changed[k] = this.tryDecrypt(v);
|
||||
}
|
||||
|
||||
Object.assign(this.configs, changed);
|
||||
this.listener.notify(changed);
|
||||
console.info('[ConfigClient]', Object.keys(changed).length, 'configs changed');
|
||||
}
|
||||
|
||||
applyConfigs(data) {
|
||||
for (const [k, v] of Object.entries(data)) {
|
||||
if (k.startsWith('kenaito.') || k === 'latest.fetch.timestamp') {
|
||||
continue;
|
||||
}
|
||||
if (typeof v !== 'string') {
|
||||
continue;
|
||||
}
|
||||
this.configs[k] = this.tryDecrypt(v);
|
||||
}
|
||||
}
|
||||
|
||||
tryDecrypt(value) {
|
||||
let decrypted = decryptConfig(this.secret, value);
|
||||
if (decrypted === value && this.globalSecret) {
|
||||
decrypted = decryptConfig(this.globalSecret, value);
|
||||
}
|
||||
return decrypted;
|
||||
}
|
||||
}
|
||||
|
||||
export { Config } from './config.js';
|
||||
Reference in New Issue
Block a user