UNPKG

@xiaoluo_aigc0708/aigc-sdk

Version:

AI智能建筑 - 完整的AIGC图片生成SDK

291 lines 10.6 kB
import { createHmac } from 'crypto'; import { AIGCError } from '../../types/src/index'; /** * OSS存储客户端类 * 提供阿里云OSS的完整集成 */ export class OSSStorageClient { constructor(config) { this.config = config; } /** * 上传文件到OSS */ async uploadFile(file, path = '/images/upload/', options = {}) { try { // 验证文件 const validation = this.validateFile(file, options); if (!validation.isValid) { throw new AIGCError(validation.errors.join(', '), 'INVALID_FILE'); } // 生成唯一文件名 const timestamp = Date.now(); const originalName = file.name.replace(/[^a-zA-Z0-9.-]/g, '_'); const fileName = `${timestamp}_${originalName}`; const objectKey = `${path}${fileName}`; // 构建OSS上传URL const ossHost = `${this.config.bucket}.${this.config.region}.aliyuncs.com`; const uploadUrl = `https://${ossHost}${objectKey}`; // 生成OSS授权信息 const { authorization, date } = this.generateOSSAuth('PUT', file.type, objectKey, this.config.bucket, this.config.accessKeyId, this.config.accessKeySecret); // 获取文件buffer const fileArrayBuffer = await file.arrayBuffer(); const fileBuffer = Buffer.from(fileArrayBuffer); // 上传到OSS(带重试机制) const uploadResponse = await this.fetchWithRetry(uploadUrl, { method: 'PUT', headers: { 'Authorization': authorization, 'Date': date, 'Content-Type': file.type, 'Content-Length': file.size.toString(), }, body: fileBuffer, }); if (uploadResponse.ok) { return { success: true, url: uploadUrl, key: fileName, message: '文件上传成功' }; } else { const errorText = await uploadResponse.text(); // 返回降级响应,前端可以使用本地预览 return { success: false, error: `OSS upload failed: ${uploadResponse.status}`, fallback: true, localFile: { name: file.name, size: file.size, type: file.type } }; } } catch (ossError) { // 网络错误或超时 - 使用降级方案 return { success: false, error: `OSS connection failed: ${ossError.message}`, fallback: true, localFile: { name: file.name, size: file.size, type: file.type } }; } } /** * 上传风格参考图片 */ async uploadStyleImage(file) { return this.uploadFile(file, '/images/upload/', { maxSize: 5 * 1024 * 1024, // 5MB allowedTypes: ['image/jpeg', 'image/jpg', 'image/png', 'image/webp'] }); } /** * 上传基底图片 */ async uploadBaseImage(file) { return this.uploadFile(file, '/images/InputImages/', { maxSize: 10 * 1024 * 1024, // 10MB allowedTypes: ['image/jpeg', 'image/jpg', 'image/png', 'image/webp'] }); } /** * 验证文件 */ validateFile(file, options) { const errors = []; // 检查文件类型 const allowedTypes = options.allowedTypes || ['image/jpeg', 'image/jpg', 'image/png', 'image/webp']; if (!allowedTypes.includes(file.type)) { errors.push(`Invalid file type. Only ${allowedTypes.join(', ')} are allowed.`); } // 检查文件大小 const maxSize = options.maxSize || 5 * 1024 * 1024; // 默认5MB if (file.size > maxSize) { errors.push(`File size too large. Maximum ${Math.round(maxSize / 1024 / 1024)}MB allowed.`); } return { isValid: errors.length === 0, errors }; } /** * 生成OSS签名 */ generateOSSSignature(stringToSign, accessKeySecret) { return createHmac('sha1', accessKeySecret).update(stringToSign).digest('base64'); } /** * 生成OSS授权头 */ generateOSSAuth(method, contentType, objectKey, bucket, accessKeyId, accessKeySecret) { const date = new Date().toUTCString(); const stringToSign = `${method}\n\n${contentType}\n${date}\n/${bucket}${objectKey}`; const signature = this.generateOSSSignature(stringToSign, accessKeySecret); const authorization = `OSS ${accessKeyId}:${signature}`; return { authorization, date }; } /** * 带重试机制的fetch函数 */ async fetchWithRetry(url, options, maxRetries = 3) { let lastError = new Error('Unknown error'); for (let i = 0; i <= maxRetries; i++) { try { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 30000); // 30秒超时 const response = await fetch(url, { ...options, signal: controller.signal, }); clearTimeout(timeoutId); return response; } catch (error) { lastError = error; if (i < maxRetries) { const delay = Math.pow(2, i) * 1000; // 指数退避:1s, 2s, 4s await new Promise(resolve => setTimeout(resolve, delay)); } } } throw lastError; } /** * 生成预签名URL (用于客户端直传) */ generatePresignedUrl(objectKey, expiration = 3600, method = 'PUT') { const expires = Math.floor(Date.now() / 1000) + expiration; const stringToSign = `${method}\n\n\n${expires}\n/${this.config.bucket}${objectKey}`; const signature = this.generateOSSSignature(stringToSign, this.config.accessKeySecret); const ossHost = `${this.config.bucket}.${this.config.region}.aliyuncs.com`; return `https://${ossHost}${objectKey}?OSSAccessKeyId=${this.config.accessKeyId}&Expires=${expires}&Signature=${encodeURIComponent(signature)}`; } /** * 检查文件是否存在 */ async fileExists(objectKey) { try { const ossHost = `${this.config.bucket}.${this.config.region}.aliyuncs.com`; const url = `https://${ossHost}${objectKey}`; const { authorization, date } = this.generateOSSAuth('HEAD', '', objectKey, this.config.bucket, this.config.accessKeyId, this.config.accessKeySecret); const response = await fetch(url, { method: 'HEAD', headers: { 'Authorization': authorization, 'Date': date, }, }); return response.ok; } catch { return false; } } /** * 删除文件 */ async deleteFile(objectKey) { try { const ossHost = `${this.config.bucket}.${this.config.region}.aliyuncs.com`; const url = `https://${ossHost}${objectKey}`; const { authorization, date } = this.generateOSSAuth('DELETE', '', objectKey, this.config.bucket, this.config.accessKeyId, this.config.accessKeySecret); const response = await fetch(url, { method: 'DELETE', headers: { 'Authorization': authorization, 'Date': date, }, }); return response.ok; } catch { return false; } } /** * 获取文件信息 */ async getFileInfo(objectKey) { try { const ossHost = `${this.config.bucket}.${this.config.region}.aliyuncs.com`; const url = `https://${ossHost}${objectKey}`; const { authorization, date } = this.generateOSSAuth('HEAD', '', objectKey, this.config.bucket, this.config.accessKeyId, this.config.accessKeySecret); const response = await fetch(url, { method: 'HEAD', headers: { 'Authorization': authorization, 'Date': date, }, }); if (response.ok) { return { size: parseInt(response.headers.get('content-length') || '0'), lastModified: new Date(response.headers.get('last-modified') || ''), contentType: response.headers.get('content-type') || undefined, }; } return null; } catch { return null; } } } /** * OSS工具函数 */ export class OSSUtils { /** * 从OSS URL中提取对象键 */ static extractObjectKeyFromUrl(url) { try { const urlObj = new URL(url); // 匹配 bucket.region.aliyuncs.com 格式 if (urlObj.hostname.includes('.aliyuncs.com')) { return urlObj.pathname; } return null; } catch { return null; } } /** * 构建OSS访问URL */ static buildOSSUrl(bucket, region, objectKey) { const ossHost = `${bucket}.${region}.aliyuncs.com`; return `https://${ossHost}${objectKey.startsWith('/') ? objectKey : '/' + objectKey}`; } /** * 验证OSS配置 */ static validateConfig(config) { const errors = []; if (!config.region) errors.push('OSS region is required'); if (!config.accessKeyId) errors.push('OSS accessKeyId is required'); if (!config.accessKeySecret) errors.push('OSS accessKeySecret is required'); if (!config.bucket) errors.push('OSS bucket is required'); if (!config.endpoint) errors.push('OSS endpoint is required'); return { isValid: errors.length === 0, errors }; } } //# sourceMappingURL=index.js.map