/** * OSS 上传:直连阿里云(ali-oss SDK 自行签名);乐跑学校 API 仍经 RunForge 代理。 */ const OSS = require('ali-oss') const { forwardRequest } = require('../ProxyForwardClient') function ossLogLabel(traceId, taskId) { let s = '' if (traceId) s += `[${traceId}] ` s += '[ossPut]' if (taskId) s += ` [${taskId}]` return s } function buildOssClient(sts, timeoutMs) { return new OSS({ bucket: sts.bucket, region: sts.region || 'oss-cn-hangzhou', accessKeyId: sts.AccessKeyId, accessKeySecret: sts.AccessKeySecret, stsToken: sts.SecurityToken, secure: true, timeout: timeoutMs }) } /** ali-oss urllib 期望 { status, statusCode, headers, data, res } */ function toUrllibResponse(resp) { const status = resp?.status ?? resp?.statusCode if (status == null) { const err = new Error('代理转发未返回有效 HTTP status') err.code = 'PROXY_FORWARD_BAD_RESPONSE' throw err } let data = resp.data if (data == null || data === '') { data = Buffer.alloc(0) } else if (!Buffer.isBuffer(data)) { data = Buffer.from(typeof data === 'string' ? data : JSON.stringify(data)) } const headers = resp.headers || {} return { status, statusCode: status, headers, data, res: { statusCode: status, headers } } } function patchOssClientForForward(client, { outboundMode, timeout, logger, logPrefix, scene, ossPath }) { const urllibMod = client.urllib const originalRequest = urllibMod.request.bind(urllibMod) async function runForward(reqUrl, reqOpt) { const method = (reqOpt.method || 'GET').toUpperCase() const headers = reqOpt.headers || {} const body = reqOpt.content ?? reqOpt.body if (outboundMode === 'direct') { return originalRequest(reqUrl, reqOpt) } try { const resp = await forwardRequest({ method, url: reqUrl, data: body, headers, timeout: reqOpt.timeout || timeout, outboundMode, logger, logPrefix, scene: scene || 'oss_put', responseType: 'arraybuffer', validateStatus: () => true }) return toUrllibResponse(resp) } catch (e) { if (outboundMode === 'proxy') throw e logger?.warn?.(`${logPrefix} OSS 经代理失败,改 SDK 直连: ${e.message || e}`) return originalRequest(reqUrl, reqOpt) } } // 兼容 urllib 的 callback / Promise 双模式,避免 ali-oss 收到 undefined status urllibMod.request = function patchedRequest(url, args, callback) { if (arguments.length === 2 && typeof args === 'function') { callback = args args = null } args = args || {} if (typeof callback === 'function') { runForward(url, args).then( (result) => { if (!result || result.status == null) { callback(new Error('OSS HTTP 响应缺少 status')) return } callback(null, result.data, result.res) }, (err) => callback(err) ) return } return runForward(url, args) } logger?.info?.(`${logPrefix} PUT ${ossPath} mode=${outboundMode}`) } /** * @param {object} sts OSS STS 凭证 * @param {string} ossPath 对象 key * @param {Buffer|string} content * @param {{ logger?: object, traceId?: string, taskId?: string, outboundMode?: 'auto'|'direct'|'proxy', timeout?: number }} options */ async function putOssWithQgOutbound(sts, ossPath, content, options = {}) { const { logger = null, traceId = null, taskId = null, outboundMode = 'direct', timeout = 60000 } = options const logPrefix = () => ossLogLabel(traceId, taskId) if (outboundMode === 'direct') { logger?.info?.(`${logPrefix()} PUT 直连 ${ossPath}`) const client = buildOssClient(sts, timeout) return client.put(ossPath, content) } const client = buildOssClient(sts, timeout) patchOssClientForForward(client, { outboundMode, timeout, logger, logPrefix: logPrefix(), scene: 'oss_put', ossPath }) return client.put(ossPath, content) } module.exports = { putOssWithQgOutbound }