feat(FEAT-DS-001): 接入AKTools超短行情补充数据源
This commit is contained in:
@@ -36,6 +36,12 @@ const disabledAiEnv = {
|
||||
AI_TIMEOUT_MS: '30000',
|
||||
};
|
||||
|
||||
const enabledAktoolsEnv = {
|
||||
AKTOOLS_ENABLED: 'true',
|
||||
AKTOOLS_BASE_URL: 'http://127.0.0.1:8080',
|
||||
AKTOOLS_TIMEOUT_MS: '10000',
|
||||
};
|
||||
|
||||
const disabledRocketMqEnv = {
|
||||
ROCKETMQ_ENABLED: 'false',
|
||||
ROCKETMQ_NAMESRV_ADDR: '127.0.0.1:9876',
|
||||
@@ -44,6 +50,27 @@ const disabledRocketMqEnv = {
|
||||
};
|
||||
|
||||
describe('进程级运行配置', () => {
|
||||
it('API 接受仅限本机 8080 的 AKTools 配置', () => {
|
||||
const config = loadApiConfig({
|
||||
APP_ENV: 'development',
|
||||
LOG_LEVEL: 'debug',
|
||||
API_HOST: '127.0.0.1',
|
||||
API_PORT: '3000',
|
||||
TDX_BASE_URL: 'http://127.0.0.1:17710',
|
||||
TDX_TIMEOUT_MS: '5000',
|
||||
...baseRedisEnv,
|
||||
...disabledMysqlEnv,
|
||||
...disabledAiEnv,
|
||||
...enabledAktoolsEnv,
|
||||
});
|
||||
|
||||
expect(config.aktools).toEqual({
|
||||
enabled: true,
|
||||
baseUrl: 'http://127.0.0.1:8080',
|
||||
timeoutMs: 10000,
|
||||
});
|
||||
});
|
||||
|
||||
it('API 只返回自己的配置且不要求 Scheduler 或 Worker 字段', () => {
|
||||
const config = loadApiConfig({
|
||||
APP_ENV: 'development',
|
||||
|
||||
@@ -41,6 +41,13 @@ export interface TdxRuntimeConfig {
|
||||
timeoutMs: number;
|
||||
}
|
||||
|
||||
/** AKTools 仅作为本机内网补充源,启用时固定回环地址和端口。 */
|
||||
export interface AktoolsRuntimeConfig {
|
||||
enabled: boolean;
|
||||
baseUrl: string;
|
||||
timeoutMs: number;
|
||||
}
|
||||
|
||||
/** RocketMQ 公共寻址配置;实际生产/消费参数由相应进程扩展。 */
|
||||
export interface RocketMqRuntimeConfig {
|
||||
enabled: boolean;
|
||||
@@ -61,6 +68,7 @@ export interface ApiRuntimeConfig extends CommonRuntimeConfig {
|
||||
processName: 'api';
|
||||
api: Readonly<{ host: string; port: number }>;
|
||||
tdx: Readonly<TdxRuntimeConfig>;
|
||||
aktools: Readonly<AktoolsRuntimeConfig>;
|
||||
mysql: MysqlRuntimeConfig;
|
||||
ai: AiRuntimeConfig;
|
||||
}
|
||||
@@ -227,6 +235,35 @@ function loadTdxConfig(env: EnvironmentSource): TdxRuntimeConfig {
|
||||
};
|
||||
}
|
||||
|
||||
function loadAktoolsConfig(env: EnvironmentSource): AktoolsRuntimeConfig {
|
||||
const enabled = env.AKTOOLS_ENABLED === undefined ? false : parseBoolean(env, 'AKTOOLS_ENABLED');
|
||||
const baseUrl = optionalText(env, 'AKTOOLS_BASE_URL') || 'http://127.0.0.1:8080';
|
||||
let parsed: URL;
|
||||
try {
|
||||
parsed = new URL(baseUrl);
|
||||
} catch {
|
||||
throw new Error('AKTOOLS_BASE_URL 必须为有效 URL');
|
||||
}
|
||||
|
||||
if (parsed.protocol !== 'http:') throw new Error('AKTOOLS_BASE_URL 必须使用 http 协议');
|
||||
if (parsed.hostname !== '127.0.0.1') {
|
||||
throw new Error('AKTOOLS_BASE_URL 必须指向本机回环地址');
|
||||
}
|
||||
if (parsed.port !== '8080') throw new Error('AKTOOLS_BASE_URL 必须使用 8080 端口');
|
||||
if (parsed.pathname !== '' && parsed.pathname !== '/') {
|
||||
throw new Error('AKTOOLS_BASE_URL 不得包含额外路径');
|
||||
}
|
||||
|
||||
return {
|
||||
enabled,
|
||||
baseUrl: parsed.toString().replace(/\/$/, ''),
|
||||
timeoutMs:
|
||||
env.AKTOOLS_TIMEOUT_MS === undefined
|
||||
? 10_000
|
||||
: parseInteger(env, 'AKTOOLS_TIMEOUT_MS', { min: 100, max: 30_000 }),
|
||||
};
|
||||
}
|
||||
|
||||
function loadRocketMqConfig(env: EnvironmentSource): RocketMqRuntimeConfig {
|
||||
const enabled = parseBoolean(env, 'ROCKETMQ_ENABLED');
|
||||
const nameServerAddress = optionalText(env, 'ROCKETMQ_NAMESRV_ADDR');
|
||||
@@ -257,6 +294,7 @@ export function loadApiConfig(env: EnvironmentSource): ApiRuntimeConfig {
|
||||
port: parseInteger(env, 'API_PORT', { min: 1, max: 65_535 }),
|
||||
},
|
||||
tdx: loadTdxConfig(env),
|
||||
aktools: loadAktoolsConfig(env),
|
||||
redis: loadRedisConfig(env),
|
||||
mysql: loadMysqlConfig(env),
|
||||
ai: loadAiConfig(env),
|
||||
@@ -335,6 +373,7 @@ export function toSafeRuntimeSummary(config: RuntimeConfig): Readonly<Record<str
|
||||
};
|
||||
|
||||
if (config.processName === 'api') summary.api = config.api;
|
||||
if (config.processName === 'api') summary.aktools = { enabled: config.aktools.enabled };
|
||||
if (config.processName === 'scheduler') summary.scheduler = config.scheduler;
|
||||
if (config.processName === 'worker') summary.marketData = { mode: config.marketData.mode };
|
||||
|
||||
|
||||
Reference in New Issue
Block a user