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:
2026-02-26 21:13:49 +08:00
parent 710659350d
commit 03a95abb4b
30 changed files with 14 additions and 14 deletions
+172
View File
@@ -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';