feat: 增强配置生成逻辑,添加重试机制和日志记录

This commit is contained in:
ElyPrism 2026-06-13 21:21:33 +08:00
parent e3a1c041b6
commit c959866436
No known key found for this signature in database
3 changed files with 163 additions and 35 deletions

View File

@ -4,38 +4,167 @@ const { register_anonimous } = require('./main')
const { cookieToJson, generateRandomChineseIP } = require('./util/index')
const { getXeapiPublicKey } = require('./util/xeapiKey')
const tmpPath = require('os').tmpdir()
const logger = require('./util/logger')
async function generateConfig() {
global.cnIp = generateRandomChineseIP()
try {
const res = await register_anonimous()
const cookie = res.body.cookie
if (cookie) {
const cookieObj = cookieToJson(cookie)
const MAX_RETRIES = 3
const RETRY_DELAY_MS = 1000
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms))
}
function isRetryableError(err) {
const msg = (err && err.message) || ''
const status =
(err && err.status) || (err && err.response && err.response.status)
if (
msg.includes('ETIMEDOUT') ||
msg.includes('ECONNRESET') ||
msg.includes('ECONNREFUSED') ||
msg.includes('socket hang up') ||
msg.includes('request timeout') ||
msg.includes('timeout') ||
msg.includes('network') ||
msg.includes('Network')
) {
return true
}
if (status && status >= 500) {
return true
}
return false
}
/** @returns {{ success: boolean, error?: Error }} */
async function fetchAnonymousToken() {
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
try {
const res = await register_anonimous()
const cookie = res.body.cookie
if (cookie) {
const cookieObj = cookieToJson(cookie)
fs.writeFileSync(
path.resolve(tmpPath, 'anonymous_token'),
cookieObj.MUSIC_A,
'utf-8',
)
logger.success('[generateConfig] 匿名 token 注册成功')
return { success: true }
}
// 返回了但没有 cookie视为异常但不再重试
logger.warn(
`[generateConfig] 匿名注册返回了空 cookie (attempt ${attempt})`,
)
return {
success: false,
error: new Error('empty cookie from anonymous register'),
}
} catch (err) {
if (isRetryableError(err) && attempt < MAX_RETRIES) {
const delay = RETRY_DELAY_MS * Math.pow(2, attempt - 1)
logger.warn(
`[generateConfig] 获取匿名 token 失败 (attempt ${attempt}/${MAX_RETRIES}), ${delay}ms 后重试...`,
)
await sleep(delay)
continue
}
// 不可重试 或 已达最大次数
if (attempt >= MAX_RETRIES) {
logger.error(
`[generateConfig] 获取匿名 token 已达最大重试次数 (${MAX_RETRIES}):`,
err.message,
)
} else {
logger.error(
`[generateConfig] 获取匿名 token 失败 (不可重试):`,
err.message,
)
}
return { success: false, error: err }
}
}
return { success: false, error: new Error('unreachable') }
}
/**
* 获取 xeapi public key带重试
* @returns {{ success: boolean, error?: Error }}
*/
async function fetchXeapiPublicKey() {
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
try {
let currentPublicKey = {}
try {
currentPublicKey = JSON.parse(
fs.readFileSync(path.resolve(tmpPath, 'xeapi_public_key'), 'utf-8'),
)
} catch (_) {
// 本地无缓存文件,用空对象正常请求
}
const publicKey = await getXeapiPublicKey(
currentPublicKey,
global.deviceId,
)
fs.writeFileSync(
path.resolve(tmpPath, 'anonymous_token'),
cookieObj.MUSIC_A,
path.resolve(tmpPath, 'xeapi_public_key'),
JSON.stringify(publicKey),
'utf-8',
)
logger.success('[generateConfig] xeapi public key 获取成功')
return { success: true }
} catch (err) {
if (isRetryableError(err) && attempt < MAX_RETRIES) {
const delay = RETRY_DELAY_MS * Math.pow(2, attempt - 1)
logger.warn(
`[generateConfig] 获取 xeapi public key 失败 (attempt ${attempt}/${MAX_RETRIES}), ${delay}ms 后重试...`,
)
await sleep(delay)
continue
}
if (attempt >= MAX_RETRIES) {
logger.error(
`[generateConfig] 获取 xeapi public key 已达最大重试次数 (${MAX_RETRIES}):`,
err.message,
)
} else {
logger.error(
`[generateConfig] 获取 xeapi public key 失败 (不可重试):`,
err.message,
)
}
return { success: false, error: err }
}
} catch (error) {
console.log(error)
}
try {
let currentPublicKey = {}
try {
currentPublicKey = JSON.parse(
fs.readFileSync(path.resolve(tmpPath, 'xeapi_public_key'), 'utf-8'),
)
} catch (_) {}
const publicKey = await getXeapiPublicKey(currentPublicKey, global.deviceId)
fs.writeFileSync(
path.resolve(tmpPath, 'xeapi_public_key'),
JSON.stringify(publicKey),
'utf-8',
)
} catch (error) {
console.log(error)
return { success: false, error: new Error('unreachable') }
}
/**
* 生成配置匿名 token + xeapi public key带容错重试
* @returns {{ tokenOk: boolean, keyOk: boolean }}
*/
async function generateConfig() {
global.cnIp = generateRandomChineseIP()
// 两个任务并行执行,互不影响喵~
const [tokenResult, keyResult] = await Promise.all([
fetchAnonymousToken(),
fetchXeapiPublicKey(),
])
if (!tokenResult.success) {
logger.warn('[generateConfig] 匿名 token 获取失败')
}
if (!keyResult.success) {
logger.warn('[generateConfig] xeapi public key 获取失败')
}
if (tokenResult.success && keyResult.success) {
logger.success('[generateConfig] 配置初始化完成')
}
return {
tokenOk: tokenResult.success,
keyOk: keyResult.success,
}
}
module.exports = generateConfig

View File

@ -26,7 +26,6 @@ function cloudmusic_dll_encode_id(some_id) {
module.exports = async (query, request) => {
const deviceId = generateDeviceId()
logger.info(`Successfully registered anonimous token, deviceId: ${deviceId}`)
global.deviceId = deviceId
const encodedId = CryptoJS.enc.Base64.stringify(
CryptoJS.enc.Utf8.parse(

View File

@ -10,6 +10,7 @@ const { cookieToJson } = require('./util/index')
const fileUpload = require('express-fileupload')
const decode = require('safe-decode-uri-component')
const logger = require('./util/logger.js')
const { APP_CONF } = require('./util/config.json')
/**
* The version check result.
@ -299,15 +300,15 @@ async function constructServer(moduleDefs) {
)
try {
let usedCrypto = ''
const moduleResponse = await moduleDef.module(query, (...params) => {
// 参数注入客户端IP
const obj = [...params]
const options = obj[2] || {}
usedCrypto = options.crypto || ''
let ip = ''
if (options.randomCNIP) {
ip = global.cnIp
// logger.info('Using random Chinese IP for request:', ip)
} else {
ip = req.ip
@ -317,7 +318,6 @@ async function constructServer(moduleDefs) {
if (ip == '::1') {
ip = global.cnIp
}
// logger.info('Requested from ip:', ip)
}
obj[2] = {
@ -327,7 +327,10 @@ async function constructServer(moduleDefs) {
return request(...obj)
})
logger.info(`Request Success: ${decode(req.originalUrl)}`)
const displayCrypto = usedCrypto || (APP_CONF.encrypt ? 'eapi' : 'api')
logger.info(
`Request Success: [${displayCrypto}] ${decode(req.originalUrl)}`,
)
// 夹带私货部分如果开启了通用解锁并且是获取歌曲URL的接口则尝试解锁如果需要的话ヾ(≧▽≦*)o
if (
@ -445,10 +448,7 @@ async function serveNcmApi(options) {
`)
logger.info(`
- Server started successfully @ http://${host ? host : 'localhost'}:${port}
- Environment: ${process.env.NODE_ENV || 'development'}
- Node Version: ${process.version}
- Process ID: ${process.pid}`)
- Server started successfully @ http://${host ? host : 'localhost'}:${port}`)
})
return appExt