|
|
@@ -139,7 +139,7 @@ async function logSchoolOutbound(logger, phase, url, axiosMerge, opts = {}) {
|
|
|
* 对 lepao.ctbu.edu.cn 的 POST:优先隧道代理;失败快速直连并记日志(隧道池出口由服务商后台切换)。
|
|
|
*/
|
|
|
async function postLepaoSchool(url, data, options = {}) {
|
|
|
- const { headers = {}, timeout = 15000, logger = null } = options
|
|
|
+ const { headers = {}, timeout = 15000, logger = null, outboundMode = 'auto', mqTaskId = null } = options
|
|
|
|
|
|
const doPost = async (qgProxyFragment, requestTimeout = timeout) => {
|
|
|
const outbound = buildAxiosOutboundConfig(qgProxyFragment)
|
|
|
@@ -150,6 +150,12 @@ async function postLepaoSchool(url, data, options = {}) {
|
|
|
})
|
|
|
}
|
|
|
|
|
|
+ // 强制直连:策略 A 用(任务内固定出站,禁止中途切换)
|
|
|
+ if (outboundMode === 'direct') {
|
|
|
+ await logSchoolOutbound(logger, '(强制直连)', url, { proxy: false })
|
|
|
+ return doPost({ proxy: false })
|
|
|
+ }
|
|
|
+
|
|
|
if (debugProxyEnabled()) {
|
|
|
const dbg = debugProxyAxiosFragment()
|
|
|
await logSchoolOutbound(logger, 'Charles调试代理', url, dbg, { skipQgSnapshot: true })
|
|
|
@@ -167,10 +173,17 @@ async function postLepaoSchool(url, data, options = {}) {
|
|
|
try {
|
|
|
frag = await getOutboundWithBackoff({ forceRefresh: false }, 2)
|
|
|
} catch (e0) {
|
|
|
+ if (outboundMode === 'proxy') {
|
|
|
+ const err = new Error(`代理模式提取失败: ${e0.message || e0}`)
|
|
|
+ err.code = 'PROXY_REQUIRED_EXTRACT_FAILED'
|
|
|
+ err.retryable = true
|
|
|
+ throw err
|
|
|
+ }
|
|
|
logger?.error?.(`[lepaoSchoolHttp] 青果提取多次重试仍失败,改直连: ${e0.message || e0}`)
|
|
|
await logSchoolOutbound(logger, '(青果提取异常→直连)', url, { proxy: false })
|
|
|
await QgProxyManager.recordFallbackDirect({
|
|
|
reason: 'qg_extract_error',
|
|
|
+ mq_task_id: mqTaskId,
|
|
|
...summarizeAxiosError(e0)
|
|
|
})
|
|
|
return doPost({ proxy: false })
|
|
|
@@ -187,16 +200,28 @@ async function postLepaoSchool(url, data, options = {}) {
|
|
|
|
|
|
if (frag.proxy === false) {
|
|
|
logger?.warn?.('[lepaoSchoolHttp] 无可用青果节点,对学校 POST 将直连')
|
|
|
+ if (outboundMode === 'proxy') {
|
|
|
+ const err = new Error('代理模式无可用节点')
|
|
|
+ err.code = 'PROXY_REQUIRED_NO_NODE'
|
|
|
+ err.retryable = true
|
|
|
+ throw err
|
|
|
+ }
|
|
|
await logSchoolOutbound(logger, '(无缓存节点→直连)', url, { proxy: false })
|
|
|
- await QgProxyManager.recordFallbackDirect({ reason: 'no_proxy_available' })
|
|
|
+ await QgProxyManager.recordFallbackDirect({ reason: 'no_proxy_available', mq_task_id: mqTaskId })
|
|
|
return doPost({ proxy: false })
|
|
|
}
|
|
|
|
|
|
await logSchoolOutbound(logger, '首次请求', url, frag)
|
|
|
try {
|
|
|
- const proxyFirstTimeoutMs = 10000
|
|
|
+ const proxyFirstTimeoutMs = 20000
|
|
|
return await doPost(frag, proxyFirstTimeoutMs)
|
|
|
} catch (e1) {
|
|
|
+ if (outboundMode === 'proxy') {
|
|
|
+ const err = new Error(`代理模式请求失败: ${e1.message || e1}`)
|
|
|
+ err.code = 'PROXY_REQUIRED_REQUEST_FAILED'
|
|
|
+ err.retryable = true
|
|
|
+ throw err
|
|
|
+ }
|
|
|
if (!isQgProxyEligibleFailure(e1)) throw e1
|
|
|
logger?.warn?.(
|
|
|
`[lepaoSchoolHttp] 经代理首次请求失败,将直接回退直连。err=${e1.message || e1} ${JSON.stringify(
|
|
|
@@ -213,6 +238,7 @@ async function postLepaoSchool(url, data, options = {}) {
|
|
|
await QgProxyManager.recordFallbackDirect({
|
|
|
reason: 'tls_prefinish_reset_direct',
|
|
|
path: briefUrlPath(url),
|
|
|
+ mq_task_id: mqTaskId,
|
|
|
...summarizeAxiosError(e1)
|
|
|
})
|
|
|
return doPost({ proxy: false })
|
|
|
@@ -222,6 +248,7 @@ async function postLepaoSchool(url, data, options = {}) {
|
|
|
await QgProxyManager.recordFallbackDirect({
|
|
|
reason: 'proxy_post_failed_then_direct',
|
|
|
path: briefUrlPath(url),
|
|
|
+ mq_task_id: mqTaskId,
|
|
|
...summarizeAxiosError(e1)
|
|
|
})
|
|
|
return doPost({ proxy: false })
|