Files

183 lines
5.2 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @fileoverview 运行时配置工具
*
* 处理命令行参数解析和运行时环境变量配置,包括:
* - 命令行参数解析(--key=value 格式)
* - CLI 参数覆盖环境变量(proxy、platform、port 等)
* - 代理地址解析与缓存
*
* @module runtime
* @requires url - URL 解析(用于代理地址解析)
*/
const { URL } = require('url');
/**
* 缓存的代理地址原始字符串(用于避免重复解析)
* @type {string | undefined}
*/
let cachedProxyRaw;
/**
* 缓存的代理配置对象(AxiosProxyConfig 格式)
* @type {import('axios').AxiosProxyConfig | null}
*/
let cachedProxy;
/**
* 解析命令行参数
*
* 仅解析 `--key=value` 格式的参数,忽略其他格式。
* 解析规则:
* - 必须以 `--` 开头
* - 必须包含 `=` 分隔符
* - key 和 value 均不能为空
*
* @param {string[]} [args] - 参数数组,默认使用 process.argv.slice(2)
* @returns {Record<string, string>} 解析后的键值对对象
*
* @example
* // 命令行: node app.js --proxy=http://127.0.0.1:8080 --platform=lite
* parseCliArgs() // => { proxy: 'http://127.0.0.1:8080', platform: 'lite' }
*/
function parseCliArgs(args) {
const source = Array.isArray(args) ? args : process.argv.slice(2);
return source.reduce((acc, rawArg) => {
if (typeof rawArg !== 'string') {
return acc;
}
const arg = rawArg.trim();
// 必须以 -- 开头
if (!arg.startsWith('--')) {
return acc;
}
const eqIndex = arg.indexOf('=');
// = 必须在 -- 之后且不在末尾(确保 key 和 value 都存在)
if (eqIndex <= 2 || eqIndex === arg.length - 1) {
return acc;
}
const key = arg.slice(2, eqIndex).trim();
const value = arg.slice(eqIndex + 1).trim();
if (!key || !value) return acc;
acc[key] = value;
return acc;
}, {});
}
/**
* 应用命令行参数覆盖环境变量
*
* 支持的 CLI 参数:
* - --proxy: 设置 KUGOU_API_PROXY(代理地址)
* - --platform: 设置 platform(平台类型,如 "lite"
* - --guid: 设置 KUGOU_API_GUID(设备 GUID
* - --dev: 设置 KUGOU_API_DEV(开发设备标识)
* - --mac: 设置 KUGOU_API_MAC(设备 MAC 地址)
* - --port: 设置 PORT(服务器端口,需为正整数)
*
* @param {string[]} [args] - 参数数组,默认使用 process.argv.slice(2)
*/
function applyCliOverrides(args) {
const parsed = parseCliArgs(args);
if (parsed.proxy) {
process.env.KUGOU_API_PROXY = parsed.proxy;
}
if (parsed.platform) {
process.env.platform = parsed.platform;
}
if (parsed.guid) {
process.env.KUGOU_API_GUID = parsed.guid;
}
if (parsed.dev) {
process.env.KUGOU_API_DEV = parsed.dev;
}
if (parsed.mac) {
process.env.KUGOU_API_MAC = parsed.mac;
}
if (parsed.port) {
const port = Number(parsed.port);
if (!Number.isNaN(port) && port > 0) {
process.env.PORT = String(port);
} else {
console.warn(`[cli] Invalid port value "${parsed.port}", fallback to default.`);
}
}
}
/**
* 解析代理配置
*
* 从环境变量 KUGOU_API_PROXY 读取代理地址,解析为 Axios 兼容的代理配置对象。
* 支持 HTTP/HTTPS 协议,支持带认证信息的代理(user:password@host:port)。
*
* 解析结果会被缓存,避免每次请求都重复解析。
* 当环境变量值未变化时直接返回缓存结果。
*
* @returns {import('axios').AxiosProxyConfig | null} 代理配置对象,无代理时返回 null
*
* @example
* // KUGOU_API_PROXY=http://user:pass@127.0.0.1:8080
* resolveProxy()
* // => { protocol: 'http', host: '127.0.0.1', port: 8080, auth: { username: 'user', password: 'pass' } }
*/
function resolveProxy() {
const rawProxyEnv = typeof process.env.KUGOU_API_PROXY === 'string' ? process.env.KUGOU_API_PROXY.trim() : undefined;
const rawProxy = rawProxyEnv && rawProxyEnv.length > 0 ? rawProxyEnv : undefined;
// 无代理配置,清空缓存
if (!rawProxy) {
cachedProxyRaw = undefined;
cachedProxy = null;
return null;
}
// 缓存命中,直接返回
if (cachedProxyRaw === rawProxy) {
return cachedProxy;
}
// 缓存未命中,重新解析
cachedProxyRaw = rawProxy;
try {
const parsed = new URL(rawProxy);
// 仅支持 HTTP/HTTPS 代理协议
if (!/^https?:$/.test(parsed.protocol)) {
console.warn(`[proxy] Unsupported proxy protocol: ${parsed.protocol}`);
cachedProxy = null;
return null;
}
// 构建 Axios 代理配置
const proxyConfig = {
protocol: parsed.protocol.replace(':', ''), // 去掉末尾的冒号
host: parsed.hostname,
port: parsed.port ? Number(parsed.port) : parsed.protocol === 'https:' ? 443 : 80, // 默认端口
};
// 如果代理地址包含认证信息
if (parsed.username || parsed.password) {
proxyConfig.auth = {
username: parsed.username,
password: parsed.password,
};
}
cachedProxy = proxyConfig;
console.info(`[proxy] Using proxy ${parsed.protocol}//${parsed.host}`);
} catch (error) {
console.warn(`[proxy] Failed to parse proxy address "${rawProxy}": ${error.message}`);
cachedProxy = null;
}
return cachedProxy;
}
module.exports = { applyCliOverrides, parseCliArgs, resolveProxy };