web_ppt_7.7 / backend /src /services /huggingfaceStorageService.js
CatPtain's picture
Upload 85 files
28e1dba verified
import { fileURLToPath } from 'url';
import { v4 as uuidv4 } from 'uuid';
import crypto from 'crypto';
import path from 'path';
import fs from 'fs/promises';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
/**
* Huggingface存储服务
* 管理PPT图片文件的内存存储、链接生成和版本控制
* 利用Huggingface Space的16G内存进行临时存储
*/
class HuggingfaceStorageService {
constructor() {
// 文件系统路径配置
this.usersDir = path.join(__dirname, '../../data/users');
// 内存存储配置
this.memoryStorage = new Map(); // 存储图片Buffer数据
this.linkMap = new Map(); // 图片链接映射
this.metadataMap = new Map(); // 图片元数据映射
this.userDataMap = new Map(); // 用户数据映射
// 内存管理配置
this.maxMemoryUsage = 14 * 1024 * 1024 * 1024; // 14GB 最大内存使用量
this.currentMemoryUsage = 0;
this.cleanupThreshold = 0.8; // 80%时开始清理
// 缓存策略配置
this.maxImageAge = 24 * 60 * 60 * 1000; // 24小时过期
this.maxImagesPerUser = 100; // 每用户最大图片数
this.initialized = false;
// 定期清理内存
this.startMemoryCleanup();
}
/**
* 初始化内存存储服务
*/
async initialize() {
if (this.initialized) return;
try {
// 初始化内存存储
this.memoryStorage.clear();
this.linkMap.clear();
this.metadataMap.clear();
this.userDataMap.clear();
this.currentMemoryUsage = 0;
this.initialized = true;
console.log('✅ HuggingfaceStorageService (Memory Mode) initialized successfully');
console.log(`💾 Max memory usage: ${(this.maxMemoryUsage / 1024 / 1024 / 1024).toFixed(1)}GB`);
console.log(`🧹 Cleanup threshold: ${(this.cleanupThreshold * 100)}%`);
console.log(`⏰ Image expiry: ${this.maxImageAge / 1000 / 60 / 60}h`);
} catch (error) {
console.error('❌ Failed to initialize HuggingfaceStorageService:', error);
throw error;
}
}
/**
* 启动内存清理定时器
*/
startMemoryCleanup() {
// 每5分钟检查一次内存使用情况
setInterval(() => {
this.performMemoryCleanup();
}, 5 * 60 * 1000);
console.log('🧹 Memory cleanup scheduler started (every 5 minutes)');
}
/**
* 执行内存清理
*/
performMemoryCleanup() {
const memoryUsageRatio = this.currentMemoryUsage / this.maxMemoryUsage;
if (memoryUsageRatio > this.cleanupThreshold) {
console.log(`🧹 Memory cleanup triggered (${(memoryUsageRatio * 100).toFixed(1)}% usage)`);
// 清理过期图片
this.cleanupExpiredImages();
// 如果内存使用仍然过高,清理最旧的图片
if (this.currentMemoryUsage / this.maxMemoryUsage > this.cleanupThreshold) {
this.cleanupOldestImages();
}
}
}
/**
* 清理过期图片
*/
cleanupExpiredImages() {
const now = Date.now();
let cleanedCount = 0;
let freedMemory = 0;
for (const [imageId, metadata] of this.metadataMap.entries()) {
const imageAge = now - new Date(metadata.createdAt).getTime();
if (imageAge > this.maxImageAge) {
const imageData = this.memoryStorage.get(imageId);
if (imageData) {
freedMemory += imageData.length;
this.memoryStorage.delete(imageId);
this.metadataMap.delete(imageId);
this.linkMap.delete(imageId);
cleanedCount++;
}
}
}
this.currentMemoryUsage -= freedMemory;
if (cleanedCount > 0) {
console.log(`🧹 Cleaned ${cleanedCount} expired images, freed ${(freedMemory / 1024 / 1024).toFixed(1)}MB`);
}
}
/**
* 清理最旧的图片
*/
cleanupOldestImages() {
const images = Array.from(this.metadataMap.entries())
.sort((a, b) => new Date(a[1].createdAt) - new Date(b[1].createdAt));
const targetCleanup = Math.floor(images.length * 0.2); // 清理20%最旧的图片
let cleanedCount = 0;
let freedMemory = 0;
for (let i = 0; i < targetCleanup && i < images.length; i++) {
const [imageId, metadata] = images[i];
const imageData = this.memoryStorage.get(imageId);
if (imageData) {
freedMemory += imageData.length;
this.memoryStorage.delete(imageId);
this.metadataMap.delete(imageId);
this.linkMap.delete(imageId);
cleanedCount++;
}
}
this.currentMemoryUsage -= freedMemory;
if (cleanedCount > 0) {
console.log(`🧹 Cleaned ${cleanedCount} oldest images, freed ${(freedMemory / 1024 / 1024).toFixed(1)}MB`);
}
}
/**
* 生成图片ID
* @param {string} userId - 用户ID
* @param {string} pptId - PPT ID
* @param {number} pageIndex - 页面索引
* @param {string} version - 版本号
* @returns {string} 图片ID
*/
generateImageId(userId, pptId, pageIndex, version = 'latest') {
const data = `${userId}-${pptId}-${pageIndex}-${version}`;
return crypto.createHash('sha256').update(data).digest('hex').substring(0, 16);
}
/**
* 生成版本号
* @returns {string} 版本号
*/
generateVersion() {
return Date.now().toString();
}
/**
* 存储图片到内存
* @param {string} userId - 用户ID
* @param {string} pptId - PPT ID
* @param {number} pageIndex - 页面索引
* @param {Buffer} imageBuffer - 图片数据
* @param {Object} options - 存储选项
* @returns {Promise<Object>} 存储结果
*/
async storeImage(userId, pptId, pageIndex, imageBuffer, options = {}) {
if (!this.initialized) {
await this.initialize();
}
const {
format = 'png',
quality = 0.9,
updateExisting = true
} = options;
try {
// 检查内存使用情况
if (this.currentMemoryUsage + imageBuffer.length > this.maxMemoryUsage) {
console.log('⚠️ Memory limit approaching, performing cleanup...');
this.performMemoryCleanup();
// 如果清理后仍然超出限制,拒绝存储
if (this.currentMemoryUsage + imageBuffer.length > this.maxMemoryUsage) {
throw new Error('Memory limit exceeded, cannot store image');
}
}
// 检查用户图片数量限制
const userImages = Array.from(this.metadataMap.values())
.filter(meta => meta.userId === userId);
if (userImages.length >= this.maxImagesPerUser) {
// 删除用户最旧的图片
const oldestImage = userImages
.sort((a, b) => new Date(a.createdAt) - new Date(b.createdAt))[0];
if (oldestImage) {
this.deleteImageFromMemory(oldestImage.imageId);
}
}
// 生成版本号
const version = this.generateVersion();
// 生成图片ID
const imageId = this.generateImageId(userId, pptId, pageIndex, 'latest');
const versionedImageId = this.generateImageId(userId, pptId, pageIndex, version);
// 如果已存在相同的图片ID,先删除旧的
if (this.memoryStorage.has(imageId)) {
this.deleteImageFromMemory(imageId);
}
// 存储图片到内存
this.memoryStorage.set(imageId, imageBuffer);
this.memoryStorage.set(versionedImageId, imageBuffer);
// 更新内存使用量
this.currentMemoryUsage += imageBuffer.length * 2; // 存储了两份(latest和versioned)
// 创建元数据
const metadata = {
imageId,
versionedImageId,
userId,
pptId,
pageIndex,
version,
format,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
size: imageBuffer.length,
memoryStored: true
};
// 存储元数据
this.metadataMap.set(imageId, metadata);
this.metadataMap.set(versionedImageId, { ...metadata, imageId: versionedImageId });
// 更新链接映射
this.linkMap.set(imageId, metadata);
this.linkMap.set(versionedImageId, { ...metadata, imageId: versionedImageId });
// 更新用户数据统计
if (!this.userDataMap.has(userId)) {
this.userDataMap.set(userId, { imageCount: 0, totalSize: 0 });
}
const userData = this.userDataMap.get(userId);
userData.imageCount++;
userData.totalSize += imageBuffer.length;
console.log(`✅ Image stored in memory: ${imageId} (${imageBuffer.length} bytes)`);
console.log(`💾 Memory usage: ${(this.currentMemoryUsage / 1024 / 1024).toFixed(1)}MB / ${(this.maxMemoryUsage / 1024 / 1024 / 1024).toFixed(1)}GB`);
return {
success: true,
imageId,
versionedImageId,
version,
url: `/api/images/${imageId}`,
versionedUrl: `/api/images/${versionedImageId}`,
memoryStored: true,
size: imageBuffer.length,
memoryUsage: {
current: this.currentMemoryUsage,
max: this.maxMemoryUsage,
percentage: (this.currentMemoryUsage / this.maxMemoryUsage * 100).toFixed(1)
}
};
} catch (error) {
console.error('❌ Failed to store image in memory:', error);
throw error;
}
}
/**
* 从内存中删除图片
* @param {string} imageId - 图片ID
*/
deleteImageFromMemory(imageId) {
const imageData = this.memoryStorage.get(imageId);
if (imageData) {
this.currentMemoryUsage -= imageData.length;
this.memoryStorage.delete(imageId);
this.metadataMap.delete(imageId);
this.linkMap.delete(imageId);
console.log(`🗑️ Deleted image from memory: ${imageId} (freed ${imageData.length} bytes)`);
}
}
/**
* 从内存中获取图片
* @param {string} imageId - 图片ID
* @returns {Promise<Object>} 图片数据和元信息
*/
async getImage(imageId) {
if (!this.initialized) {
await this.initialize();
}
// 从内存中获取图片数据
const imageBuffer = this.memoryStorage.get(imageId);
if (!imageBuffer) {
throw new Error(`Image not found in memory: ${imageId}`);
}
// 获取元数据
const metadata = this.metadataMap.get(imageId);
if (!metadata) {
throw new Error(`Image metadata not found: ${imageId}`);
}
try {
return {
success: true,
data: imageBuffer,
metadata: {
imageId,
format: metadata.format,
size: metadata.size,
createdAt: metadata.createdAt,
updatedAt: metadata.updatedAt,
version: metadata.version,
memoryStored: true
}
};
} catch (error) {
console.error(`❌ Failed to get image ${imageId}:`, error);
throw error;
}
}
/**
* 从内存中删除图片
* @param {string} imageId - 图片ID
* @returns {Promise<boolean>} 删除结果
*/
async deleteImage(imageId) {
if (!this.initialized) {
await this.initialize();
}
const metadata = this.metadataMap.get(imageId);
if (!metadata) {
return false;
}
try {
// 从内存中删除图片数据
this.deleteImageFromMemory(imageId);
// 如果有版本化的图片ID,也删除它
if (metadata.versionedImageId && metadata.versionedImageId !== imageId) {
this.deleteImageFromMemory(metadata.versionedImageId);
}
// 更新用户数据统计
const userData = this.userDataMap.get(metadata.userId);
if (userData) {
userData.imageCount = Math.max(0, userData.imageCount - 1);
userData.totalSize = Math.max(0, userData.totalSize - metadata.size);
}
console.log(`✅ Image deleted from memory: ${imageId}`);
return true;
} catch (error) {
console.error(`❌ Failed to delete image from memory ${imageId}:`, error);
return false;
}
}
/**
* 批量存储PPT所有页面图片
* @param {string} userId - 用户ID
* @param {string} pptId - PPT ID
* @param {Array} imageBuffers - 图片Buffer数组
* @param {Object} options - 存储选项
* @returns {Promise<Array>} 存储结果数组
*/
async storeAllImages(userId, pptId, imageBuffers, options = {}) {
const results = [];
for (let i = 0; i < imageBuffers.length; i++) {
try {
if (imageBuffers[i].success) {
const result = await this.storeImage(
userId,
pptId,
i,
imageBuffers[i].data,
options
);
results.push({
pageIndex: i,
success: true,
...result
});
} else {
results.push({
pageIndex: i,
success: false,
error: imageBuffers[i].error
});
}
} catch (error) {
results.push({
pageIndex: i,
success: false,
error: error.message
});
}
}
return results;
}
/**
* 存储PPT数据到Huggingface硬盘
* @param {string} userId - 用户ID
* @param {string} pptId - PPT ID
* @param {Object} pptData - PPT数据
* @returns {Promise<Object>} 存储结果
*/
async storePPTData(userId, pptId, pptData) {
if (!this.initialized) {
await this.initialize();
}
try {
const userDir = path.join(this.usersDir, userId);
const pptDir = path.join(userDir, pptId);
// 确保目录存在
await fs.mkdir(pptDir, { recursive: true });
// 存储PPT数据
const pptDataFile = path.join(pptDir, 'data.json');
await fs.writeFile(pptDataFile, JSON.stringify(pptData, null, 2));
// 存储元数据
const metadata = {
pptId,
userId,
title: pptData.title || '未命名演示文稿',
slidesCount: pptData.slides?.length || 0,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
storageType: 'huggingface',
size: JSON.stringify(pptData).length
};
const metaFile = path.join(pptDir, 'meta.json');
await fs.writeFile(metaFile, JSON.stringify(metadata, null, 2));
console.log(`✅ PPT data stored to Huggingface: ${pptId} for user ${userId}`);
return {
success: true,
pptId,
metadata
};
} catch (error) {
console.error(`❌ Failed to store PPT data ${pptId}:`, error);
throw error;
}
}
/**
* 从Huggingface硬盘获取PPT数据
* @param {string} userId - 用户ID
* @param {string} pptId - PPT ID
* @returns {Promise<Object>} PPT数据
*/
async getPPTData(userId, pptId) {
if (!this.initialized) {
await this.initialize();
}
try {
const pptDataFile = path.join(this.usersDir, userId, pptId, 'data.json');
const data = await fs.readFile(pptDataFile, 'utf-8');
const pptData = JSON.parse(data);
console.log(`✅ PPT data loaded from Huggingface: ${pptId} for user ${userId}`);
return pptData;
} catch (error) {
console.error(`❌ Failed to get PPT data ${pptId}:`, error);
throw error;
}
}
/**
* 获取用户的PPT列表
* @param {string} userId - 用户ID
* @returns {Promise<Array>} PPT列表
*/
async getUserPPTList(userId) {
if (!this.initialized) {
await this.initialize();
}
try {
const userDir = path.join(this.usersDir, userId);
// 检查用户目录是否存在
try {
await fs.access(userDir);
} catch {
console.log(`📁 No PPTs found for user ${userId} in Huggingface storage`);
return [];
}
const pptFolders = await fs.readdir(userDir, { withFileTypes: true });
const pptList = [];
for (const folder of pptFolders) {
if (folder.isDirectory()) {
try {
const metaFile = path.join(userDir, folder.name, 'meta.json');
const metaData = await fs.readFile(metaFile, 'utf-8');
const metadata = JSON.parse(metaData);
pptList.push({
name: folder.name,
title: metadata.title || '未命名演示文稿',
updatedAt: metadata.updatedAt || new Date().toISOString(),
slidesCount: metadata.slidesCount || 0,
storageType: 'huggingface',
size: metadata.size || 0,
repoUrl: 'Huggingface Storage'
});
} catch (error) {
console.warn(`⚠️ Skipping invalid PPT folder ${folder.name}:`, error.message);
}
}
}
console.log(`📁 Found ${pptList.length} PPTs in Huggingface storage for user ${userId}`);
return pptList.sort((a, b) => new Date(b.updatedAt) - new Date(a.updatedAt));
} catch (error) {
console.error(`❌ Failed to get PPT list for user ${userId}:`, error);
throw error;
}
}
/**
* 删除PPT数据
* @param {string} userId - 用户ID
* @param {string} pptId - PPT ID
* @returns {Promise<boolean>} 删除结果
*/
async deletePPTData(userId, pptId) {
if (!this.initialized) {
await this.initialize();
}
try {
const pptDir = path.join(this.usersDir, userId, pptId);
// 递归删除PPT目录
await fs.rm(pptDir, { recursive: true, force: true });
console.log(`✅ PPT data deleted from Huggingface: ${pptId} for user ${userId}`);
return true;
} catch (error) {
console.error(`❌ Failed to delete PPT data ${pptId}:`, error);
return false;
}
}
/**
* 获取用户的所有图片
* @param {string} userId - 用户ID
* @param {string} pptId - PPT ID (可选)
* @returns {Promise<Array>} 图片列表
*/
async getUserImages(userId, pptId = null) {
if (!this.initialized) {
await this.initialize();
}
const userImages = [];
for (const [imageId, linkInfo] of this.linkMap.entries()) {
if (linkInfo.userId === userId) {
if (!pptId || linkInfo.pptId === pptId) {
userImages.push({
imageId,
pptId: linkInfo.pptId,
pageIndex: linkInfo.pageIndex,
version: linkInfo.version,
format: linkInfo.format,
size: linkInfo.size,
url: `/api/images/${imageId}`,
createdAt: linkInfo.createdAt,
updatedAt: linkInfo.updatedAt
});
}
}
}
return userImages.sort((a, b) => {
if (a.pptId !== b.pptId) {
return a.pptId.localeCompare(b.pptId);
}
return a.pageIndex - b.pageIndex;
});
}
/**
* 清理过期图片
* @param {number} maxAge - 最大保留时间(毫秒)
* @returns {Promise<number>} 清理的图片数量
*/
async cleanupExpiredImages(maxAge = 30 * 24 * 60 * 60 * 1000) { // 默认30天
if (!this.initialized) {
await this.initialize();
}
const now = Date.now();
let cleanedCount = 0;
for (const [imageId, linkInfo] of this.linkMap.entries()) {
const createdAt = new Date(linkInfo.createdAt).getTime();
if (now - createdAt > maxAge) {
await this.deleteImage(imageId);
cleanedCount++;
}
}
console.log(`🧹 Cleaned up ${cleanedCount} expired images`);
return cleanedCount;
}
/**
* 获取存储统计信息
* @returns {Promise<Object>} 统计信息
*/
async getStorageStats() {
if (!this.initialized) {
await this.initialize();
}
let totalSize = 0;
let totalImages = 0;
const userStats = {};
// 从内存存储中统计
for (const [imageId, metadata] of this.metadataMap.entries()) {
totalSize += metadata.size || 0;
totalImages++;
if (!userStats[metadata.userId]) {
userStats[metadata.userId] = {
imageCount: 0,
totalSize: 0,
ppts: new Set()
};
}
userStats[metadata.userId].imageCount++;
userStats[metadata.userId].totalSize += metadata.size || 0;
userStats[metadata.userId].ppts.add(metadata.pptId);
}
// 转换Set为数组
for (const userId in userStats) {
userStats[userId].pptCount = userStats[userId].ppts.size;
delete userStats[userId].ppts;
}
return {
totalImages,
totalSize,
totalSizeMB: Math.round(totalSize / 1024 / 1024 * 100) / 100,
userCount: Object.keys(userStats).length,
userStats,
memoryUsage: {
current: this.currentMemoryUsage,
max: this.maxMemoryUsage,
currentMB: Math.round(this.currentMemoryUsage / 1024 / 1024 * 100) / 100,
maxGB: Math.round(this.maxMemoryUsage / 1024 / 1024 / 1024 * 100) / 100,
percentage: Math.round(this.currentMemoryUsage / this.maxMemoryUsage * 100 * 100) / 100,
imagesInMemory: this.memoryStorage.size
}
};
}
/**
* 获取内存使用情况
* @returns {Object} 内存使用统计
*/
getMemoryUsage() {
return {
current: this.currentMemoryUsage,
max: this.maxMemoryUsage,
currentMB: Math.round(this.currentMemoryUsage / 1024 / 1024 * 100) / 100,
maxGB: Math.round(this.maxMemoryUsage / 1024 / 1024 / 1024 * 100) / 100,
percentage: Math.round(this.currentMemoryUsage / this.maxMemoryUsage * 100 * 100) / 100,
imagesInMemory: this.memoryStorage.size,
metadataCount: this.metadataMap.size,
userCount: this.userDataMap.size
};
}
/**
* 清空所有内存数据
*/
clearAllMemory() {
this.memoryStorage.clear();
this.metadataMap.clear();
this.linkMap.clear();
this.userDataMap.clear();
this.currentMemoryUsage = 0;
console.log('🧹 All memory data cleared');
}
/**
* 健康检查
*/
async healthCheck() {
try {
if (!this.initialized) {
return {
status: 'unhealthy',
message: 'Service not initialized',
details: {
initialized: false,
memoryUsage: 0,
imagesCount: 0
}
};
}
// 检查内存使用情况
const memoryUsage = this.getMemoryUsage();
const isMemoryHealthy = memoryUsage.percentage < 90; // 90%以下认为健康
// 检查数据目录是否可访问
let dirAccessible = true;
try {
await fs.access(this.usersDir);
} catch (error) {
dirAccessible = false;
}
const isHealthy = isMemoryHealthy && dirAccessible;
return {
status: isHealthy ? 'healthy' : 'unhealthy',
message: isHealthy
? 'Huggingface Storage Service is running normally'
: 'Service has issues',
details: {
initialized: this.initialized,
memoryUsage: memoryUsage,
dirAccessible: dirAccessible,
usersDir: this.usersDir,
isMemoryHealthy: isMemoryHealthy,
maxMemoryUsageGB: Math.round(this.maxMemoryUsage / 1024 / 1024 / 1024 * 100) / 100,
cleanupThreshold: this.cleanupThreshold
}
};
} catch (error) {
return {
status: 'unhealthy',
message: 'Health check failed',
details: {
error: error.message,
stack: error.stack
}
};
}
}
/**
* 获取用户的图片列表
* @param {string} userId - 用户ID
* @returns {Array} 用户的图片列表
*/
getUserImageList(userId) {
const userImages = [];
for (const [imageId, metadata] of this.metadataMap.entries()) {
if (metadata.userId === userId) {
userImages.push({
imageId,
pptId: metadata.pptId,
pageIndex: metadata.pageIndex,
format: metadata.format,
size: metadata.size,
createdAt: metadata.createdAt,
version: metadata.version
});
}
}
return userImages;
}
/**
* 获取PPT的所有图片
* @param {string} userId - 用户ID
* @param {string} pptId - PPT ID
* @returns {Array} PPT的图片列表
*/
getPPTImages(userId, pptId) {
const pptImages = [];
for (const [imageId, metadata] of this.metadataMap.entries()) {
if (metadata.userId === userId && metadata.pptId === pptId) {
pptImages.push({
imageId,
pageIndex: metadata.pageIndex,
format: metadata.format,
size: metadata.size,
createdAt: metadata.createdAt,
version: metadata.version,
url: `/api/images/${imageId}`
});
}
}
return pptImages.sort((a, b) => a.pageIndex - b.pageIndex);
}
/**
* 公开访问:根据用户ID、PPT ID和页面索引获取图片
* @param {string} userId - 用户ID
* @param {string} pptId - PPT ID
* @param {number} pageIndex - 页面索引
* @returns {Promise<Object>} 图片数据和元信息
*/
async getPublicImage(userId, pptId, pageIndex) {
if (!this.initialized) {
await this.initialize();
}
// 生成图片ID
const imageId = this.generateImageId(userId, pptId, pageIndex, 'latest');
// 从内存中获取图片数据
const imageBuffer = this.memoryStorage.get(imageId);
if (!imageBuffer) {
throw new Error(`Public image not found: ${userId}/${pptId}/${pageIndex}`);
}
// 获取元数据
const metadata = this.metadataMap.get(imageId);
if (!metadata) {
throw new Error(`Public image metadata not found: ${userId}/${pptId}/${pageIndex}`);
}
console.log(`✅ Public image accessed: ${userId}/${pptId}/${pageIndex} (${imageBuffer.length} bytes)`);
return {
success: true,
data: imageBuffer,
metadata: {
imageId,
userId,
pptId,
pageIndex,
format: metadata.format,
size: metadata.size,
createdAt: metadata.createdAt,
updatedAt: metadata.updatedAt,
version: metadata.version,
memoryStored: true
}
};
}
/**
* 公开访问:检查图片是否存在
* @param {string} userId - 用户ID
* @param {string} pptId - PPT ID
* @param {number} pageIndex - 页面索引
* @returns {boolean} 图片是否存在
*/
hasPublicImage(userId, pptId, pageIndex) {
if (!this.initialized) {
return false;
}
const imageId = this.generateImageId(userId, pptId, pageIndex, 'latest');
return this.memoryStorage.has(imageId) && this.metadataMap.has(imageId);
}
/**
* 公开访问:获取用户的PPT列表(仅包含有图片的PPT)
* @param {string} userId - 用户ID
* @returns {Array} PPT列表
*/
getPublicPPTList(userId) {
if (!this.initialized) {
return [];
}
const pptMap = new Map();
for (const [imageId, metadata] of this.metadataMap.entries()) {
if (metadata.userId === userId) {
if (!pptMap.has(metadata.pptId)) {
pptMap.set(metadata.pptId, {
pptId: metadata.pptId,
userId: metadata.userId,
pageCount: 0,
totalSize: 0,
createdAt: metadata.createdAt,
updatedAt: metadata.updatedAt,
pages: []
});
}
const pptInfo = pptMap.get(metadata.pptId);
pptInfo.pageCount++;
pptInfo.totalSize += metadata.size;
pptInfo.pages.push({
pageIndex: metadata.pageIndex,
format: metadata.format,
size: metadata.size,
url: `/api/public/image/${userId}/${metadata.pptId}/${metadata.pageIndex}`
});
// 更新最新时间
if (new Date(metadata.updatedAt) > new Date(pptInfo.updatedAt)) {
pptInfo.updatedAt = metadata.updatedAt;
}
}
}
// 排序页面并返回结果
const result = Array.from(pptMap.values());
result.forEach(ppt => {
ppt.pages.sort((a, b) => a.pageIndex - b.pageIndex);
});
return result.sort((a, b) => new Date(b.updatedAt) - new Date(a.updatedAt));
}
}
// 创建单例实例
const huggingfaceStorageService = new HuggingfaceStorageService();
export default huggingfaceStorageService;