初始化模版工程
This commit is contained in:
163
remote-control/shared/file-uploader.ts
Normal file
163
remote-control/shared/file-uploader.ts
Normal file
@@ -0,0 +1,163 @@
|
||||
import { createCustomOSSUploader } from '@bty/uploader'
|
||||
import type { STSTokenResponse } from '@/package/uploader/src/index'
|
||||
import { AESUtils } from '@/package/utils/crypto'
|
||||
import { tryParseToObject } from '@/package/utils/parse'
|
||||
import { isSupportedFileType, getMimeByExtension } from './file-types'
|
||||
import { HTTPClient } from '@/http'
|
||||
|
||||
const MAX_FILE_SIZE = 100 * 1024 * 1024 // 100MB
|
||||
|
||||
export interface BotAttachment {
|
||||
url: string
|
||||
fileName: string
|
||||
mimeType?: string
|
||||
size?: number
|
||||
}
|
||||
|
||||
interface UploadResult {
|
||||
fileId: string
|
||||
fileName: string
|
||||
}
|
||||
|
||||
// Reuse a single HTTPClient for Nova API calls
|
||||
const novaClient = new HTTPClient({
|
||||
baseURL: process.env.NOVA_BASE_URL,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Tenant-Id': process.env.NOVA_TENANT_ID!,
|
||||
'Authorization': process.env.NOVA_ACCESS_KEY!,
|
||||
},
|
||||
})
|
||||
|
||||
async function getServerSTSToken(): Promise<STSTokenResponse> {
|
||||
const env = process.env.NODE_ENV
|
||||
const res = await novaClient.get<string | { data?: string }>('/v1/oss/upload_sts')
|
||||
|
||||
const dataToDecrypt = typeof res === 'string'
|
||||
? res
|
||||
: res?.data
|
||||
|
||||
if (typeof dataToDecrypt !== 'string') {
|
||||
throw new Error('Invalid encrypted STS payload from Nova API')
|
||||
}
|
||||
|
||||
const decryptedData = new AESUtils(env).decryptAES_CBC(dataToDecrypt)
|
||||
return tryParseToObject(decryptedData) as STSTokenResponse
|
||||
}
|
||||
|
||||
async function downloadFile(
|
||||
url: string,
|
||||
headers?: Record<string, string>,
|
||||
): Promise<{ buffer: Buffer; size: number }> {
|
||||
const response = await fetch(url, { headers })
|
||||
if (!response.ok) {
|
||||
throw new Error(`Download failed: ${response.status} ${response.statusText}`)
|
||||
}
|
||||
const arrayBuffer = await response.arrayBuffer()
|
||||
const buffer = Buffer.from(arrayBuffer)
|
||||
return { buffer, size: buffer.length }
|
||||
}
|
||||
|
||||
async function uploadSingleAttachment(
|
||||
attachment: BotAttachment,
|
||||
headers?: Record<string, string>,
|
||||
): Promise<UploadResult | null> {
|
||||
// 1. Validate file type
|
||||
if (!isSupportedFileType(attachment.fileName)) {
|
||||
console.warn(`[File Uploader] Unsupported file type: ${attachment.fileName}`)
|
||||
return null
|
||||
}
|
||||
|
||||
// 2. Pre-check size if available
|
||||
if (attachment.size && attachment.size > MAX_FILE_SIZE) {
|
||||
console.warn(`[File Uploader] File too large (${attachment.size} bytes): ${attachment.fileName}`)
|
||||
return null
|
||||
}
|
||||
|
||||
// 3. Download file
|
||||
const { buffer, size } = await downloadFile(attachment.url, headers)
|
||||
|
||||
if (size > MAX_FILE_SIZE) {
|
||||
console.warn(`[File Uploader] Downloaded file too large (${size} bytes): ${attachment.fileName}`)
|
||||
return null
|
||||
}
|
||||
|
||||
// 4. Determine MIME type
|
||||
const mimeType = attachment.mimeType || getMimeByExtension(attachment.fileName) || 'application/octet-stream'
|
||||
|
||||
// 5. Construct OSS path (same pattern as frontend useFileUploader)
|
||||
const uuid = crypto.randomUUID()
|
||||
const timestamp = Date.now()
|
||||
const filePath = `super_agent/user_upload_file/${uuid}/_${timestamp}_${attachment.fileName}`
|
||||
|
||||
// 6. Upload to OSS
|
||||
const ossUploader = createCustomOSSUploader(getServerSTSToken)
|
||||
|
||||
// Note: We use `upload` (which maps to `put` in ali-oss) instead of `multipartUpload`
|
||||
// and pass the raw `Buffer` directly. In Node.js, ali-oss natively supports Buffer
|
||||
// for `put`, whereas `multipartUpload` expects a file path or a browser File object
|
||||
// (which causes "FileReader is not defined" when polyfilled).
|
||||
await ossUploader.upload({
|
||||
filePath,
|
||||
file: buffer as unknown as File,
|
||||
options: {
|
||||
headers: {
|
||||
'Content-Type': mimeType,
|
||||
'Content-Disposition': 'inline',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// 7. Create file record via Nova API
|
||||
const lastDotIndex = attachment.fileName.lastIndexOf('.')
|
||||
const splitName = lastDotIndex !== -1
|
||||
? [attachment.fileName.substring(0, lastDotIndex), attachment.fileName.substring(lastDotIndex + 1)]
|
||||
: [attachment.fileName]
|
||||
|
||||
const safeName = `${splitName[0]}-${Math.random().toString(36).substring(2, 5)}${splitName.length > 1 ? `.${splitName[1]}` : ''}`
|
||||
|
||||
const res = await novaClient.post<{ file_upload_record_id: string }>(
|
||||
'/v1/super_agent/file_upload_record/create',
|
||||
{
|
||||
file_url: filePath,
|
||||
file_type: mimeType,
|
||||
file_name: safeName,
|
||||
file_byte_size: size,
|
||||
conversation_id: uuid,
|
||||
},
|
||||
)
|
||||
|
||||
console.log(`[File Uploader] Uploaded: ${safeName} → ${res.file_upload_record_id}`)
|
||||
|
||||
return {
|
||||
fileId: res.file_upload_record_id,
|
||||
fileName: safeName,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload multiple bot attachments to Nova OSS and create file records.
|
||||
* Returns array of upload_file_ids (only successful uploads).
|
||||
* Never throws — individual failures are logged and skipped.
|
||||
*/
|
||||
export async function uploadBotAttachments(
|
||||
attachments: BotAttachment[],
|
||||
headers?: Record<string, string>,
|
||||
): Promise<string[]> {
|
||||
if (attachments.length === 0) return []
|
||||
|
||||
const results = await Promise.allSettled(
|
||||
attachments.map(att => uploadSingleAttachment(att, headers)),
|
||||
)
|
||||
|
||||
const fileIds: string[] = []
|
||||
for (const result of results) {
|
||||
if (result.status === 'fulfilled' && result.value) {
|
||||
fileIds.push(result.value.fileId)
|
||||
} else if (result.status === 'rejected') {
|
||||
console.error(`[File Uploader] Attachment upload failed:`, result.reason)
|
||||
}
|
||||
}
|
||||
|
||||
return fileIds
|
||||
}
|
||||
Reference in New Issue
Block a user