article-writer-cn
Version:
AI 驱动的智能写作系统 - 专注公众号/自媒体文章创作
151 lines • 4.91 kB
JavaScript
/**
* Cloudflare R2 图床提供者
* 使用 S3 兼容 API 上传图片到 Cloudflare R2
*
* 参考文档: https://developers.cloudflare.com/r2/api/s3/api/
*/
import { S3Client, PutObjectCommand, HeadBucketCommand } from '@aws-sdk/client-s3';
import fs from 'fs-extra';
import path from 'path';
import crypto from 'crypto';
export class CloudflareR2Provider {
type = 'cloudflare-r2';
config;
s3Client;
constructor(config) {
this.config = config;
// 初始化 S3 客户端
// R2 端点格式: https://<accountId>.r2.cloudflarestorage.com
const endpoint = `https://${config.accountId}.r2.cloudflarestorage.com`;
this.s3Client = new S3Client({
region: 'auto', // R2 使用 'auto' 作为 region
endpoint,
credentials: {
accessKeyId: config.accessKeyId,
secretAccessKey: config.secretAccessKey,
},
});
}
/**
* 上传单张图片到 R2
*/
async upload(imagePath, remotePath) {
try {
// 检查文件是否存在
if (!await fs.pathExists(imagePath)) {
return {
success: false,
url: '',
originalPath: imagePath,
error: '文件不存在',
provider: this.type,
};
}
// 读取文件
const fileBuffer = await fs.readFile(imagePath);
const fileStat = await fs.stat(imagePath);
// 生成远程路径
const key = remotePath || this.generateRemotePath(imagePath);
// 确定 Content-Type
const contentType = this.getContentType(imagePath);
// 上传到 R2
const command = new PutObjectCommand({
Bucket: this.config.bucket,
Key: key,
Body: fileBuffer,
ContentType: contentType,
});
await this.s3Client.send(command);
// 生成访问 URL
const url = this.buildPublicUrl(key);
return {
success: true,
url,
originalPath: imagePath,
size: fileStat.size,
provider: this.type,
};
}
catch (error) {
return {
success: false,
url: '',
originalPath: imagePath,
error: error instanceof Error ? error.message : '上传失败',
provider: this.type,
};
}
}
/**
* 批量上传图片
*/
async uploadBatch(imagePaths) {
return Promise.all(imagePaths.map(path => this.upload(path)));
}
/**
* 验证配置是否有效
*/
async validateConfig() {
try {
// 尝试访问 bucket
const command = new HeadBucketCommand({
Bucket: this.config.bucket,
});
await this.s3Client.send(command);
return true;
}
catch (error) {
console.error('Cloudflare R2 配置验证失败:', error);
return false;
}
}
/**
* 生成远程文件路径
* 格式: images/{year}/{month}/{hash}-{filename}
*/
generateRemotePath(localPath) {
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
const fileName = path.basename(localPath);
const ext = path.extname(fileName);
const nameWithoutExt = path.basename(fileName, ext);
// 生成 8 位哈希
const hash = crypto
.createHash('md5')
.update(localPath + Date.now())
.digest('hex')
.slice(0, 8);
return `images/${year}/${month}/${nameWithoutExt}-${hash}${ext}`;
}
/**
* 根据文件扩展名确定 Content-Type
*/
getContentType(filePath) {
const ext = path.extname(filePath).toLowerCase();
const contentTypes = {
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.gif': 'image/gif',
'.webp': 'image/webp',
'.svg': 'image/svg+xml',
};
return contentTypes[ext] || 'application/octet-stream';
}
/**
* 构建公开访问 URL
*/
buildPublicUrl(key) {
if (this.config.publicDomain) {
// 使用自定义域名
return `https://${this.config.publicDomain}/${key}`;
}
else {
// 使用 R2.dev 子域名(需要在 R2 控制台启用)
// 格式: https://<bucket>.r2.dev/<key>
return `https://${this.config.bucket}.r2.dev/${key}`;
}
}
}
//# sourceMappingURL=cloudflare-r2-provider.js.map