feat(infrastructure): add pagination, cache, and sync infrastructure
- Add pagination infrastructure with usePagination and usePaginationManager hooks - Add connection state management hook for network status tracking - Add media caching infrastructure with useMediaCache hook - Add differential message synchronization infrastructure - Export new hooks from central index file - Remove backup file MainNavigator.tsx.bak
This commit is contained in:
726
src/infrastructure/cache/MediaCacheManager.ts
vendored
Normal file
726
src/infrastructure/cache/MediaCacheManager.ts
vendored
Normal file
@@ -0,0 +1,726 @@
|
||||
/**
|
||||
* 媒体缓存管理器
|
||||
* @module src/infrastructure/cache/MediaCacheManager
|
||||
* @description 管理图片、视频、音频缓存,支持多种清理策略
|
||||
*/
|
||||
|
||||
import { File, Paths } from 'expo-file-system';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import {
|
||||
MediaType,
|
||||
CleanupTrigger,
|
||||
CacheEntry,
|
||||
CacheStats,
|
||||
CleanupPolicy,
|
||||
CleanupResult,
|
||||
MediaCacheConfig,
|
||||
DEFAULT_MEDIA_CACHE_CONFIG,
|
||||
DEFAULT_CLEANUP_POLICY,
|
||||
} from './types';
|
||||
|
||||
/**
|
||||
* 缓存记录存储键前缀
|
||||
*/
|
||||
const CACHE_RECORD_PREFIX = 'media_cache_record_';
|
||||
|
||||
/**
|
||||
* 获取缓存目录路径
|
||||
*/
|
||||
function getCacheDir(): string {
|
||||
const cachePath = Paths.cache;
|
||||
return `${cachePath}media_cache/`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取图片缓存目录
|
||||
*/
|
||||
function getImageDir(): string {
|
||||
return `${getCacheDir()}images/`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取视频缓存目录
|
||||
*/
|
||||
function getVideoDir(): string {
|
||||
return `${getCacheDir()}videos/`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取音频缓存目录
|
||||
*/
|
||||
function getAudioDir(): string {
|
||||
return `${getCacheDir()}audios/`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 媒体缓存管理器类
|
||||
* @class MediaCacheManager
|
||||
* @description 单例模式,管理所有媒体文件的缓存
|
||||
*/
|
||||
export class MediaCacheManager {
|
||||
private static instance: MediaCacheManager;
|
||||
|
||||
private cache: Map<string, CacheEntry> = new Map();
|
||||
private config: MediaCacheConfig;
|
||||
private policy: CleanupPolicy;
|
||||
private cleanupTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
private lastCleanupTime: number = 0;
|
||||
private isInitialized: boolean = false;
|
||||
|
||||
/**
|
||||
* 获取单例实例
|
||||
*/
|
||||
public static getInstance(): MediaCacheManager {
|
||||
if (!MediaCacheManager.instance) {
|
||||
MediaCacheManager.instance = new MediaCacheManager();
|
||||
}
|
||||
return MediaCacheManager.instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 私有构造函数
|
||||
*/
|
||||
private constructor(
|
||||
config?: Partial<MediaCacheConfig>,
|
||||
policy?: Partial<CleanupPolicy>
|
||||
) {
|
||||
this.config = { ...DEFAULT_MEDIA_CACHE_CONFIG, ...config };
|
||||
this.policy = { ...DEFAULT_CLEANUP_POLICY, ...policy };
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化缓存管理器
|
||||
* 确保缓存目录存在并加载缓存记录
|
||||
*/
|
||||
public async initialize(): Promise<void> {
|
||||
if (this.isInitialized) return;
|
||||
|
||||
try {
|
||||
// 确保缓存目录存在
|
||||
await this.ensureDirectories();
|
||||
|
||||
// 加载缓存记录
|
||||
await this.loadCacheRecords();
|
||||
|
||||
// 启动时清理
|
||||
if (this.policy.cleanupOnStart) {
|
||||
await this.cleanup(CleanupTrigger.STARTUP);
|
||||
}
|
||||
|
||||
// 启动定期清理
|
||||
this.startPeriodicCleanup();
|
||||
|
||||
this.isInitialized = true;
|
||||
} catch (error) {
|
||||
console.error('[MediaCacheManager] 初始化失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 确保缓存目录存在
|
||||
*/
|
||||
private async ensureDirectories(): Promise<void> {
|
||||
try {
|
||||
const dirs = [getImageDir(), getVideoDir(), getAudioDir()];
|
||||
for (const dir of dirs) {
|
||||
const dirFile = new File(dir);
|
||||
if (!await dirFile.exists) {
|
||||
await dirFile.create();
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[MediaCacheManager] 创建缓存目录失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从持久化存储加载缓存记录
|
||||
*/
|
||||
private async loadCacheRecords(): Promise<void> {
|
||||
try {
|
||||
const keys = await AsyncStorage.getAllKeys();
|
||||
const recordKeys = keys.filter(k => k.startsWith(CACHE_RECORD_PREFIX));
|
||||
|
||||
if (recordKeys.length === 0) return;
|
||||
|
||||
const records = await AsyncStorage.multiGet(recordKeys);
|
||||
|
||||
for (const [key, value] of records) {
|
||||
if (value) {
|
||||
const entry: CacheEntry = JSON.parse(value);
|
||||
// 验证文件是否存在
|
||||
const exists = await this.checkFileExists(entry.localPath);
|
||||
if (exists) {
|
||||
this.cache.set(entry.key, entry);
|
||||
} else {
|
||||
// 文件不存在,删除记录
|
||||
await AsyncStorage.removeItem(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[MediaCacheManager] 加载缓存记录失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查文件是否存在
|
||||
*/
|
||||
private async checkFileExists(path: string): Promise<boolean> {
|
||||
try {
|
||||
const file = new File(path);
|
||||
return await file.exists;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录缓存访问(用于LRU)
|
||||
*/
|
||||
public async recordAccess(key: string): Promise<void> {
|
||||
const entry = this.cache.get(key);
|
||||
if (entry) {
|
||||
entry.lastAccessedAt = Date.now();
|
||||
await this.saveCacheRecord(entry);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 缓存媒体文件
|
||||
*/
|
||||
public async cacheMedia(
|
||||
uri: string,
|
||||
type: MediaType,
|
||||
options: {
|
||||
conversationId?: string;
|
||||
messageId?: string;
|
||||
size?: number;
|
||||
} = {}
|
||||
): Promise<string> {
|
||||
const { conversationId, messageId, size = 0 } = options;
|
||||
|
||||
// 生成唯一键
|
||||
const key = this.generateCacheKey(uri, type);
|
||||
const localPath = this.getMediaPath(type, key);
|
||||
|
||||
// 检查是否已缓存
|
||||
const existingEntry = this.cache.get(key);
|
||||
if (existingEntry) {
|
||||
await this.recordAccess(key);
|
||||
return existingEntry.localPath;
|
||||
}
|
||||
|
||||
try {
|
||||
// 下载文件
|
||||
const destination = new File(localPath);
|
||||
const downloaded = await File.downloadFileAsync(uri, destination);
|
||||
|
||||
// 获取实际文件大小
|
||||
let fileSize = size;
|
||||
// fileSize 为 0 时使用默认大小,实际应用中可以通过其他方式获取
|
||||
|
||||
// 创建缓存条目
|
||||
const entry: CacheEntry = {
|
||||
key,
|
||||
type,
|
||||
uri,
|
||||
localPath: downloaded.uri,
|
||||
size: fileSize,
|
||||
conversationId,
|
||||
messageId,
|
||||
createdAt: Date.now(),
|
||||
lastAccessedAt: Date.now(),
|
||||
};
|
||||
|
||||
// 保存记录
|
||||
this.cache.set(key, entry);
|
||||
await this.saveCacheRecord(entry);
|
||||
|
||||
// 检查是否需要清理
|
||||
await this.checkAndCleanup();
|
||||
|
||||
return downloaded.uri;
|
||||
} catch (error) {
|
||||
console.error('[MediaCacheManager] 缓存媒体失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取缓存的媒体路径
|
||||
*/
|
||||
public async getCachedMedia(uri: string, type: MediaType): Promise<string | null> {
|
||||
const key = this.generateCacheKey(uri, type);
|
||||
const entry = this.cache.get(key);
|
||||
|
||||
if (entry) {
|
||||
const exists = await this.checkFileExists(entry.localPath);
|
||||
if (exists) {
|
||||
await this.recordAccess(key);
|
||||
return entry.localPath;
|
||||
} else {
|
||||
// 文件不存在,删除记录
|
||||
this.cache.delete(key);
|
||||
await this.removeCacheRecord(key);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取缓存统计信息
|
||||
*/
|
||||
public getCacheStats(): CacheStats {
|
||||
const stats: CacheStats = {
|
||||
totalSize: 0,
|
||||
imageCount: 0,
|
||||
videoCount: 0,
|
||||
audioCount: 0,
|
||||
totalEntries: this.cache.size,
|
||||
oldestItem: Date.now(),
|
||||
newestItem: 0,
|
||||
};
|
||||
|
||||
for (const entry of this.cache.values()) {
|
||||
stats.totalSize += entry.size;
|
||||
stats.oldestItem = Math.min(stats.oldestItem, entry.createdAt);
|
||||
stats.newestItem = Math.max(stats.newestItem, entry.createdAt);
|
||||
|
||||
switch (entry.type) {
|
||||
case MediaType.IMAGE:
|
||||
stats.imageCount++;
|
||||
break;
|
||||
case MediaType.VIDEO:
|
||||
stats.videoCount++;
|
||||
break;
|
||||
case MediaType.AUDIO:
|
||||
stats.audioCount++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理操作入口
|
||||
*/
|
||||
public async cleanup(trigger: CleanupTrigger): Promise<CleanupResult> {
|
||||
const errors: string[] = [];
|
||||
let deletedCount = 0;
|
||||
let freedSize = 0;
|
||||
const deletedItems: string[] = [];
|
||||
|
||||
try {
|
||||
// 1. 清理过期缓存
|
||||
const expiredResult = await this.cleanupExpired();
|
||||
deletedCount += expiredResult.deletedCount;
|
||||
freedSize += expiredResult.freedSize;
|
||||
deletedItems.push(...expiredResult.deletedItems || []);
|
||||
errors.push(...expiredResult.errors);
|
||||
|
||||
// 2. LRU清理(如果启用)
|
||||
if (this.policy.enableLRU) {
|
||||
const lruResult = await this.cleanupLRU();
|
||||
deletedCount += lruResult.deletedCount;
|
||||
freedSize += lruResult.freedSize;
|
||||
deletedItems.push(...lruResult.deletedItems || []);
|
||||
errors.push(...lruResult.errors);
|
||||
}
|
||||
|
||||
// 3. 阈值清理
|
||||
const thresholdResult = await this.cleanupByThreshold();
|
||||
deletedCount += thresholdResult.deletedCount;
|
||||
freedSize += thresholdResult.freedSize;
|
||||
deletedItems.push(...thresholdResult.deletedItems || []);
|
||||
errors.push(...thresholdResult.errors);
|
||||
|
||||
this.lastCleanupTime = Date.now();
|
||||
} catch (error) {
|
||||
errors.push(`清理失败: ${error instanceof Error ? error.message : '未知错误'}`);
|
||||
}
|
||||
|
||||
return {
|
||||
trigger,
|
||||
timestamp: Date.now(),
|
||||
deletedCount,
|
||||
freedSize,
|
||||
errors,
|
||||
deletedItems,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理过期缓存
|
||||
*/
|
||||
public async cleanupExpired(): Promise<CleanupResult> {
|
||||
const now = Date.now();
|
||||
const maxAge = this.policy.maxAge;
|
||||
const errors: string[] = [];
|
||||
let deletedCount = 0;
|
||||
let freedSize = 0;
|
||||
const deletedItems: string[] = [];
|
||||
|
||||
const entriesToDelete: string[] = [];
|
||||
|
||||
for (const [key, entry] of this.cache.entries()) {
|
||||
if (now - entry.lastAccessedAt > maxAge) {
|
||||
entriesToDelete.push(key);
|
||||
}
|
||||
}
|
||||
|
||||
for (const key of entriesToDelete) {
|
||||
try {
|
||||
const entry = this.cache.get(key);
|
||||
if (entry) {
|
||||
const file = new File(entry.localPath);
|
||||
if (await file.exists) {
|
||||
await file.delete();
|
||||
}
|
||||
this.cache.delete(key);
|
||||
await this.removeCacheRecord(key);
|
||||
|
||||
deletedCount++;
|
||||
freedSize += entry.size;
|
||||
deletedItems.push(key);
|
||||
}
|
||||
} catch (error) {
|
||||
errors.push(`删除 ${key} 失败: ${error instanceof Error ? error.message : '未知错误'}`);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
trigger: CleanupTrigger.MANUAL,
|
||||
timestamp: Date.now(),
|
||||
deletedCount,
|
||||
freedSize,
|
||||
errors,
|
||||
deletedItems,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* LRU清理 - 删除最近最少使用的缓存
|
||||
*/
|
||||
public async cleanupLRU(targetSize?: number): Promise<CleanupResult> {
|
||||
const errors: string[] = [];
|
||||
let deletedCount = 0;
|
||||
let freedSize = 0;
|
||||
const deletedItems: string[] = [];
|
||||
|
||||
// 如果没有指定目标大小,使用最大缓存大小的一半
|
||||
const target = targetSize || Math.floor(this.policy.maxSize / 2);
|
||||
const currentStats = this.getCacheStats();
|
||||
|
||||
if (currentStats.totalSize <= target) {
|
||||
return {
|
||||
trigger: CleanupTrigger.MANUAL,
|
||||
timestamp: Date.now(),
|
||||
deletedCount: 0,
|
||||
freedSize: 0,
|
||||
errors: [],
|
||||
deletedItems: [],
|
||||
};
|
||||
}
|
||||
|
||||
// 按最后访问时间排序
|
||||
const sortedEntries = Array.from(this.cache.entries())
|
||||
.sort((a, b) => a[1].lastAccessedAt - b[1].lastAccessedAt);
|
||||
|
||||
let currentSize = currentStats.totalSize;
|
||||
|
||||
for (const [key, entry] of sortedEntries) {
|
||||
if (currentSize <= target) break;
|
||||
|
||||
try {
|
||||
const file = new File(entry.localPath);
|
||||
if (await file.exists) {
|
||||
await file.delete();
|
||||
}
|
||||
this.cache.delete(key);
|
||||
await this.removeCacheRecord(key);
|
||||
|
||||
deletedCount++;
|
||||
freedSize += entry.size;
|
||||
deletedItems.push(key);
|
||||
currentSize -= entry.size;
|
||||
} catch (error) {
|
||||
errors.push(`删除 ${key} 失败: ${error instanceof Error ? error.message : '未知错误'}`);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
trigger: CleanupTrigger.MANUAL,
|
||||
timestamp: Date.now(),
|
||||
deletedCount,
|
||||
freedSize,
|
||||
errors,
|
||||
deletedItems,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 按阈值清理 - 超过最大大小时清理
|
||||
*/
|
||||
private async cleanupByThreshold(): Promise<CleanupResult> {
|
||||
const currentStats = this.getCacheStats();
|
||||
const errors: string[] = [];
|
||||
let deletedCount = 0;
|
||||
let freedSize = 0;
|
||||
const deletedItems: string[] = [];
|
||||
|
||||
// 检查总大小是否超过限制
|
||||
if (currentStats.totalSize <= this.policy.maxSize) {
|
||||
// 检查条目数是否超过限制
|
||||
if (currentStats.totalEntries <= this.policy.maxEntries) {
|
||||
return {
|
||||
trigger: CleanupTrigger.MANUAL,
|
||||
timestamp: Date.now(),
|
||||
deletedCount: 0,
|
||||
freedSize: 0,
|
||||
errors: [],
|
||||
deletedItems: [],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 需要清理
|
||||
const targetSize = Math.floor(this.policy.maxSize * 0.8); // 清理到80%
|
||||
const targetEntries = Math.floor(this.policy.maxEntries * 0.8);
|
||||
|
||||
// 按LRU排序
|
||||
const sortedEntries = Array.from(this.cache.entries())
|
||||
.sort((a, b) => a[1].lastAccessedAt - b[1].lastAccessedAt);
|
||||
|
||||
let currentSize = currentStats.totalSize;
|
||||
let currentEntries = currentStats.totalEntries;
|
||||
|
||||
for (const [key, entry] of sortedEntries) {
|
||||
// 两个条件:大小或条目数
|
||||
if (currentSize <= targetSize && currentEntries <= targetEntries) break;
|
||||
|
||||
try {
|
||||
const file = new File(entry.localPath);
|
||||
if (await file.exists) {
|
||||
await file.delete();
|
||||
}
|
||||
this.cache.delete(key);
|
||||
await this.removeCacheRecord(key);
|
||||
|
||||
deletedCount++;
|
||||
freedSize += entry.size;
|
||||
deletedItems.push(key);
|
||||
currentSize -= entry.size;
|
||||
currentEntries--;
|
||||
} catch (error) {
|
||||
errors.push(`删除 ${key} 失败: ${error instanceof Error ? error.message : '未知错误'}`);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
trigger: CleanupTrigger.MANUAL,
|
||||
timestamp: Date.now(),
|
||||
deletedCount,
|
||||
freedSize,
|
||||
errors,
|
||||
deletedItems,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除指定会话的所有媒体缓存
|
||||
*/
|
||||
public async clearConversationMedia(conversationId: string): Promise<CleanupResult> {
|
||||
const errors: string[] = [];
|
||||
let deletedCount = 0;
|
||||
let freedSize = 0;
|
||||
const deletedItems: string[] = [];
|
||||
|
||||
const entriesToDelete: string[] = [];
|
||||
|
||||
for (const [key, entry] of this.cache.entries()) {
|
||||
if (entry.conversationId === conversationId) {
|
||||
entriesToDelete.push(key);
|
||||
}
|
||||
}
|
||||
|
||||
for (const key of entriesToDelete) {
|
||||
try {
|
||||
const entry = this.cache.get(key);
|
||||
if (entry) {
|
||||
const file = new File(entry.localPath);
|
||||
if (await file.exists) {
|
||||
await file.delete();
|
||||
}
|
||||
this.cache.delete(key);
|
||||
await this.removeCacheRecord(key);
|
||||
|
||||
deletedCount++;
|
||||
freedSize += entry.size;
|
||||
deletedItems.push(key);
|
||||
}
|
||||
} catch (error) {
|
||||
errors.push(`删除 ${key} 失败: ${error instanceof Error ? error.message : '未知错误'}`);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
trigger: CleanupTrigger.CONVERSATION_DELETED,
|
||||
timestamp: Date.now(),
|
||||
deletedCount,
|
||||
freedSize,
|
||||
errors,
|
||||
deletedItems,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空所有缓存
|
||||
*/
|
||||
public async clearAll(): Promise<void> {
|
||||
for (const entry of this.cache.values()) {
|
||||
try {
|
||||
const file = new File(entry.localPath);
|
||||
if (await file.exists) {
|
||||
await file.delete();
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`[MediaCacheManager] 删除文件失败: ${entry.localPath}`, error);
|
||||
}
|
||||
}
|
||||
|
||||
this.cache.clear();
|
||||
|
||||
// 清除所有记录
|
||||
const keys = await AsyncStorage.getAllKeys();
|
||||
const recordKeys = keys.filter(k => k.startsWith(CACHE_RECORD_PREFIX));
|
||||
if (recordKeys.length > 0) {
|
||||
await AsyncStorage.multiRemove(recordKeys);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动定期清理
|
||||
*/
|
||||
public startPeriodicCleanup(): void {
|
||||
if (this.cleanupTimer) {
|
||||
clearInterval(this.cleanupTimer);
|
||||
}
|
||||
|
||||
this.cleanupTimer = setInterval(async () => {
|
||||
// 检查是否需要清理
|
||||
if (Date.now() - this.lastCleanupTime >= this.policy.cleanupInterval) {
|
||||
await this.cleanup(CleanupTrigger.SCHEDULED);
|
||||
}
|
||||
}, this.policy.cleanupInterval);
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止定期清理
|
||||
*/
|
||||
public stopPeriodicCleanup(): void {
|
||||
if (this.cleanupTimer) {
|
||||
clearInterval(this.cleanupTimer);
|
||||
this.cleanupTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否需要清理
|
||||
*/
|
||||
private async checkAndCleanup(): Promise<void> {
|
||||
const stats = this.getCacheStats();
|
||||
|
||||
// 检查大小
|
||||
if (stats.totalSize > this.policy.maxSize) {
|
||||
await this.cleanupLRU();
|
||||
}
|
||||
|
||||
// 检查条目数
|
||||
if (stats.totalEntries > this.policy.maxEntries) {
|
||||
await this.cleanupLRU();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存缓存记录到持久化存储
|
||||
*/
|
||||
private async saveCacheRecord(entry: CacheEntry): Promise<void> {
|
||||
try {
|
||||
const key = `${CACHE_RECORD_PREFIX}${entry.key}`;
|
||||
await AsyncStorage.setItem(key, JSON.stringify(entry));
|
||||
} catch (error) {
|
||||
console.error('[MediaCacheManager] 保存缓存记录失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除缓存记录
|
||||
*/
|
||||
private async removeCacheRecord(key: string): Promise<void> {
|
||||
try {
|
||||
const storageKey = `${CACHE_RECORD_PREFIX}${key}`;
|
||||
await AsyncStorage.removeItem(storageKey);
|
||||
} catch (error) {
|
||||
console.error('[MediaCacheManager] 删除缓存记录失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成缓存键
|
||||
*/
|
||||
private generateCacheKey(uri: string, type: MediaType): string {
|
||||
const hash = this.hashString(uri);
|
||||
return `${type.toLowerCase()}_${hash}_${Date.now()}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取媒体文件存储路径
|
||||
*/
|
||||
private getMediaPath(type: MediaType, key: string): string {
|
||||
const ext = this.getExtension(key, type);
|
||||
switch (type) {
|
||||
case MediaType.IMAGE:
|
||||
return `${getImageDir()}${key}.${ext}`;
|
||||
case MediaType.VIDEO:
|
||||
return `${getVideoDir()}${key}.${ext}`;
|
||||
case MediaType.AUDIO:
|
||||
return `${getAudioDir()}${key}.${ext}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据类型获取文件扩展名
|
||||
*/
|
||||
private getExtension(key: string, type: MediaType): string {
|
||||
if (type === MediaType.IMAGE) {
|
||||
if (key.includes('gif')) return 'gif';
|
||||
if (key.includes('webp')) return 'webp';
|
||||
return 'jpg';
|
||||
}
|
||||
if (type === MediaType.VIDEO) return 'mp4';
|
||||
if (type === MediaType.AUDIO) return 'mp3';
|
||||
return 'bin';
|
||||
}
|
||||
|
||||
/**
|
||||
* 字符串哈希函数
|
||||
*/
|
||||
private hashString(str: string): string {
|
||||
let hash = 0;
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const char = str.charCodeAt(i);
|
||||
hash = ((hash << 5) - hash) + char;
|
||||
hash = hash & hash;
|
||||
}
|
||||
return Math.abs(hash).toString(16);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出单例实例
|
||||
*/
|
||||
export const mediaCacheManager = MediaCacheManager.getInstance();
|
||||
Reference in New Issue
Block a user