@xiaoluo_aigc0708/aigc-sdk
Version:
AI智能建筑 - 完整的AIGC图片生成SDK
291 lines • 10.6 kB
JavaScript
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