| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311 |
- /**
- * 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
- }
|