feat: 提交资源

This commit is contained in:
han_han9
2025-10-28 21:55:41 +08:00
parent 591f398085
commit 55c4fcd9ae
2146 changed files with 172747 additions and 456 deletions

View File

@@ -0,0 +1,229 @@
import { createHash } from "crypto";
import fs from "fs";
import path from "path";
/**
* MinIO上传配置接口
*/
export interface MinIOConfig {
/** MinIO服务器地址 */
endpoint: string;
/** 访问密钥ID */
accessKeyId: string;
/** 访问密钥Secret */
secretAccessKey: string;
/** 存储桶名称 */
bucketName: string;
/** 是否使用SSL */
useSSL: boolean;
/** 上传路径前缀 */
pathPrefix?: string;
}
/**
* 上传结果接口
*/
export interface UploadResult {
/** 是否成功 */
success: boolean;
/** 错误信息 */
error?: string;
/** 上传的文件列表 */
uploadedFiles: string[];
}
/**
* MinIO上传器类
*/
export class MinIOUploader {
private config: MinIOConfig;
constructor(config: MinIOConfig) {
this.config = config;
}
/**
* 验证MinIO配置
*/
private validateConfig(): boolean {
const required = ['endpoint', 'accessKeyId', 'secretAccessKey', 'bucketName'];
for (const field of required) {
if (!this.config[field as keyof MinIOConfig] ||
String(this.config[field as keyof MinIOConfig]).trim() === '') {
console.error(`MinIO配置错误: ${field} 不能为空`);
return false;
}
}
return true;
}
/**
* 上传目录到MinIO
* @param localDir 本地目录路径
* @param remotePrefix 远程路径前缀
* @returns 上传结果
*/
async uploadDirectory(localDir: string, remotePrefix: string = ''): Promise<UploadResult> {
const result: UploadResult = {
success: false,
uploadedFiles: []
};
try {
// 验证配置
if (!this.validateConfig()) {
result.error = 'MinIO配置验证失败';
return result;
}
// 验证本地目录是否存在
if (!fs.existsSync(localDir)) {
result.error = `本地目录不存在: ${localDir}`;
return result;
}
console.log(`开始上传目录到MinIO: ${localDir} -> ${remotePrefix}`);
// 动态导入minio模块
const MinIO = await this.loadMinIOModule();
if (!MinIO) {
result.error = 'MinIO模块加载失败请确保已安装minio依赖';
return result;
}
// 创建MinIO客户端
const minioClient = new MinIO.Client({
endPoint: this.config.endpoint.replace(/^https?:\/\//, ''),
port: this.getPortFromEndpoint(),
useSSL: this.config.useSSL,
accessKey: this.config.accessKeyId,
secretKey: this.config.secretAccessKey
});
// 检查存储桶是否存在
const bucketExists = await minioClient.bucketExists(this.config.bucketName);
if (!bucketExists) {
console.log(`存储桶不存在,尝试创建: ${this.config.bucketName}`);
await minioClient.makeBucket(this.config.bucketName);
}
// 获取所有文件
const files = this.getAllFiles(localDir);
console.log(`找到 ${files.length} 个文件需要上传`);
// 上传文件
for (const filePath of files) {
try {
const relativePath = path.relative(localDir, filePath);
const objectName = this.buildObjectName(remotePrefix, relativePath);
console.log(`上传文件: ${filePath} -> ${objectName}`);
await minioClient.fPutObject(this.config.bucketName, objectName, filePath);
result.uploadedFiles.push(objectName);
console.log(`文件上传成功: ${objectName}`);
} catch (fileError) {
console.error(`文件上传失败: ${filePath}`, fileError);
// 继续上传其他文件,不中断整个流程
}
}
result.success = result.uploadedFiles.length > 0;
if (!result.success) {
result.error = '没有文件上传成功';
}
console.log(`MinIO上传完成成功上传 ${result.uploadedFiles.length} 个文件`);
} catch (error) {
console.error('MinIO上传过程中发生错误:', error);
result.error = error instanceof Error ? error.message : String(error);
}
return result;
}
/**
* 动态加载MinIO模块
*/
private async loadMinIOModule(): Promise<any> {
try {
// 尝试动态导入minio模块
const minio = await import('minio');
return minio;
} catch (error) {
console.error('MinIO模块导入失败:', error);
console.log('请运行以下命令安装MinIO依赖:');
console.log('npm install minio');
return null;
}
}
/**
* 从endpoint中提取端口号
*/
private getPortFromEndpoint(): number | undefined {
try {
const url = new URL(this.config.endpoint.startsWith('http') ?
this.config.endpoint : `http://${this.config.endpoint}`);
return url.port ? parseInt(url.port) : (this.config.useSSL ? 443 : 80);
} catch {
return this.config.useSSL ? 443 : 80;
}
}
/**
* 构建对象名称
*/
private buildObjectName(remotePrefix: string, relativePath: string): string {
const prefix = this.config.pathPrefix || '';
const parts = [prefix, remotePrefix, relativePath].filter(part => part && part.trim() !== '');
return parts.join('/').replace(/\/+/g, '/');
}
/**
* 递归获取目录下的所有文件
*/
private getAllFiles(dir: string): string[] {
const files: string[] = [];
const traverse = (currentDir: string) => {
const items = fs.readdirSync(currentDir);
for (const item of items) {
// 跳过以点开头的隐藏文件和隐藏目录
if (item.startsWith('.')) {
continue;
}
const fullPath = path.join(currentDir, item);
const stat = fs.statSync(fullPath);
if (stat.isDirectory()) {
traverse(fullPath);
} else if (stat.isFile()) {
files.push(fullPath);
}
}
};
traverse(dir);
return files;
}
/**
* 计算文件MD5
*/
private calculateFileMD5(filePath: string): string {
const content = fs.readFileSync(filePath);
return createHash('md5').update(content).digest('hex');
}
}
/**
* 创建MinIO上传器实例
*/
export function createMinIOUploader(config: MinIOConfig): MinIOUploader {
return new MinIOUploader(config);
}