Files
Toolbox/dev_test_scripts/runners/run_oss_browser_zip_transit.js
yangxiangyuan 6dc79a2c12 feat: 新增多项功能并添加新工具
- 私人剪贴板新增限时直通URL生成功能
- doc_cloud_keeper新增标题复制按钮
- 新增贪蛇出动游戏工具
- 新增OSS中转直链工具
- 添加批量抽帧测试脚本
2026-08-11 10:22:31 +08:00

169 lines
6.0 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.
/**
* OSS 中转脚本:把 oss://android-o1-images/trae-files-temp/oss-browser-win32-x64.zip
* 下载到本地 public/download_files/,成功后立即删除 OSS 对象。
*
* 密钥来源:~/Toolbox_local_creds.env.local(DOC_CLOUD_KEEPER_OSS_ACCESS_KEY_ID / SECRET)
* 用法:node dev_test_scripts/runners/run_oss_browser_zip_transit.js [--keep-oss]
* --keep-oss 仅下载不删除 OSS 对象(调试用,默认下载成功后删除)
*/
const fs = require('fs')
const os = require('os')
const path = require('path')
const crypto = require('crypto')
const https = require('https')
const BUCKET = 'android-o1-images'
const ENDPOINT = 'oss-cn-shanghai.aliyuncs.com'
const OSS_PREFIX = 'trae-files-temp/'
const TARGET_NAME = 'oss-browser-win32-x64.zip'
const LOCAL_DIR = path.join(process.cwd(), 'public', 'download_files')
const LOCAL_FILE = path.join(LOCAL_DIR, TARGET_NAME)
const loadEnv = () => {
const out = {}
try {
const p = path.join(os.homedir(), 'Toolbox_local_creds.env.local')
const text = fs.readFileSync(p, 'utf8')
for (const line of text.split(/\r?\n/)) {
const t = line.trim()
if (!t || t.startsWith('#')) continue
const eq = t.indexOf('=')
if (eq <= 0) continue
out[t.slice(0, eq).trim()] = t.slice(eq + 1).trim()
}
} catch {}
return out
}
const ENV = loadEnv()
const AK = ENV.DOC_CLOUD_KEEPER_OSS_ACCESS_KEY_ID || ''
const SK = ENV.DOC_CLOUD_KEEPER_OSS_ACCESS_KEY_SECRET || ''
if (!AK || !SK) {
console.error('[FAIL] 缺少 DOC_CLOUD_KEEPER_OSS_ACCESS_KEY_ID / SECRET(~/Toolbox_local_creds.env.local)')
process.exit(1)
}
const sign = (method, date, resource, contentType = '', canonicalHeaders = '') => {
const stringToSign = `${method}\n\n${contentType}\n${date}\n${canonicalHeaders ? canonicalHeaders + '\n' : ''}${resource}`
return crypto.createHmac('sha1', SK).update(stringToSign).digest('base64')
}
const ossRequest = (method, urlPath, resource) => new Promise((resolve, reject) => {
const date = new Date().toUTCString()
const req = https.request({
hostname: `${BUCKET}.${ENDPOINT}`,
path: urlPath,
method,
headers: {
Host: `${BUCKET}.${ENDPOINT}`,
Date: date,
Authorization: `OSS ${AK}:${sign(method, date, resource)}`
}
}, (res) => {
const chunks = []
res.on('data', c => chunks.push(c))
res.on('end', () => resolve({ status: res.statusCode, headers: res.headers, body: Buffer.concat(chunks) }))
})
req.on('error', reject)
req.setTimeout(120000, () => req.destroy(new Error('timeout')))
req.end()
})
const listPrefix = async (prefix) => {
const resp = await ossRequest('GET', `/?prefix=${encodeURIComponent(prefix)}&max-keys=100`, `/${BUCKET}/`)
if (resp.status !== 200) throw new Error(`OSS list 失败: ${resp.status} ${resp.body.toString('utf8').slice(0, 200)}`)
const xml = resp.body.toString('utf8')
const keys = []
let idx = 0
while (true) {
const s = xml.indexOf('<Key>', idx)
if (s < 0) break
const e = xml.indexOf('</Key>', s)
if (e < 0) break
keys.push(xml.slice(s + 5, e))
idx = e
}
return keys
}
const downloadToFile = (key, localPath) => new Promise((resolve, reject) => {
const date = new Date().toUTCString()
const resource = `/${BUCKET}/${key}`
const req = https.request({
hostname: `${BUCKET}.${ENDPOINT}`,
path: `/${key.split('/').map(encodeURIComponent).join('/')}`,
method: 'GET',
headers: {
Host: `${BUCKET}.${ENDPOINT}`,
Date: date,
Authorization: `OSS ${AK}:${sign('GET', date, resource)}`
}
}, (res) => {
if (res.statusCode !== 200) {
const chunks = []
res.on('data', c => chunks.push(c))
res.on('end', () => reject(new Error(`OSS 下载失败: ${res.statusCode} ${Buffer.concat(chunks).toString('utf8').slice(0, 200)}`)))
return
}
const ws = fs.createWriteStream(localPath)
let received = 0
res.on('data', c => { received += c.length })
res.pipe(ws)
ws.on('finish', () => resolve(received))
ws.on('error', reject)
})
req.on('error', reject)
req.setTimeout(600000, () => req.destroy(new Error('下载超时')))
req.end()
})
const deleteObject = async (key) => {
const resource = `/${BUCKET}/${key}`
const urlPath = `/${key.split('/').map(encodeURIComponent).join('/')}`
const resp = await ossRequest('DELETE', urlPath, resource)
if (resp.status !== 204 && resp.status !== 200) {
throw new Error(`OSS 删除失败: ${resp.status} ${resp.body.toString('utf8').slice(0, 200)}`)
}
return true
}
const bjNow = () => new Date().toLocaleString('sv-SE', { timeZone: 'Asia/Shanghai' })
;(async () => {
console.log(`[${bjNow()}] 列举 ${BUCKET}/${OSS_PREFIX} ...`)
const keys = await listPrefix(OSS_PREFIX)
console.log(` 前缀下共 ${keys.length} 个对象`)
const zipKey = keys.find(k => k.endsWith('/' + TARGET_NAME) || k === TARGET_NAME)
if (!zipKey) {
console.error(`[FAIL] 未找到 ${OSS_PREFIX}${TARGET_NAME},现有对象:`)
keys.forEach(k => console.error(' - ' + k))
process.exit(1)
}
console.log(` 找到对象: ${zipKey}`)
if (!fs.existsSync(LOCAL_DIR)) fs.mkdirSync(LOCAL_DIR, { recursive: true })
const tmpFile = LOCAL_FILE + '.part'
console.log(`[${bjNow()}] 开始下载到 ${LOCAL_FILE} ...`)
const received = await downloadToFile(zipKey, tmpFile)
const size = fs.statSync(tmpFile).size
if (size < 1024 * 1024) {
fs.unlinkSync(tmpFile)
throw new Error(`下载文件过小(${size} 字节),疑似异常,已丢弃临时文件`)
}
if (fs.existsSync(LOCAL_FILE)) fs.unlinkSync(LOCAL_FILE)
fs.renameSync(tmpFile, LOCAL_FILE)
console.log(` 下载完成: ${(size / 1024 / 1024).toFixed(2)} MB`)
if (process.argv.includes('--keep-oss')) {
console.log('[SKIP] --keep-oss:保留 OSS 对象不删除')
} else {
console.log(`[${bjNow()}] 删除 OSS 对象 ${zipKey} ...`)
await deleteObject(zipKey)
console.log(' OSS 对象已删除(中转完成)')
}
console.log(`[${bjNow()}] 中转成功:本地 ${LOCAL_FILE}`)
})().catch(e => {
console.error('[FAIL]', e.message || e)
process.exit(1)
})