因成本问题服务器带宽只有百兆,这就造成浏览器访问服务器PDF时间过长问题。
为解决该问题,就想到了浏览器304状态功能。
在后端服务增加了文件返回响应头
Cache-Control: public, max-age=31536000, immutable
经过测试在第二次访问文件时请求状态为304,说明文件缓存成功。
但经过后续测试发现一个问题,超过一定大小的文件不会走浏览器缓存,经过多次测试发现谷歌浏览器304缓存文件的大小为25MB,这明显不符合我们的需求,只能去想其他的方案。
查看文件请求的信息发现ETag属性可以控制文件是否缓存,但超过25MB的请求信息没有带,我们就想办法获取了第一次网络请求中的ETag信息,用于缓存请求中,经过不懈的努力,终于让超过25MB的文件请求带上了ETag请求信息,但发现浏览器还是会走网络请求而不会走缓存,经查找资料也没有发现绕过这一浏览器规则的地方,没办法,这个方案只能放弃。
后续还使用了Service Worker功能,想做离线访问发现也不行。
最后发现了一个浏览器自带的功能:IndexedDB,也不知道是什么时候上线,发现可以处理该问题。
最后附上代码
indexDb.ts
interface PdfCacheEntry {
key: string;
blob: Blob;
timestamp: number; // 缓存时间戳
ttl: number; // 过期时间(毫秒),默认7天
}
// IndexedDB 配置
const DB_CONFIG = {
name: 'PdfCacheDatabase',
version: 1,
storeName: 'pdfCache',
};
// 打开数据库连接
export const openDatabase = (): Promise<IDBDatabase> => {
return new Promise((resolve, reject) => {
const request = indexedDB.open(DB_CONFIG.name, DB_CONFIG.version);
// 数据库版本升级时触发
request.onupgradeneeded = (event) => {
const db = (event.target as IDBOpenDBRequest).result;
// 如果存储对象不存在则创建
if (!db.objectStoreNames.contains(DB_CONFIG.storeName)) {
db.createObjectStore(DB_CONFIG.storeName, { keyPath: 'key' });
// 使用自动生成的整数ID作为主键,避免键路径错误
const store = db.createObjectStore(DB_CONFIG.storeName, {
keyPath: 'id',
autoIncrement: true,
});
// 创建时间戳索引,用于查询最早的记录
store.createIndex('byTimestamp', 'timestamp', { unique: false });
// 创建业务键索引,便于查询
store.createIndex('byKey', 'key', { unique: false });
}
};
request.onsuccess = (event) => {
resolve((event.target as IDBOpenDBRequest).result);
};
request.onerror = (event) => {
console.error('IndexedDB 打开失败:', (event.target as IDBOpenDBRequest).error);
reject((event.target as IDBOpenDBRequest).error);
};
});
};
// 从IndexedDB获取缓存的PDF
export const getCachedPdf = async (key: string): Promise<Blob | null> => {
try {
const db = await openDatabase();
return new Promise((resolve) => {
const transaction = db.transaction(DB_CONFIG.storeName, 'readonly');
const store = transaction.objectStore(DB_CONFIG.storeName);
const request = store.get(key);
request.onsuccess = () => {
const entry = request.result as PdfCacheEntry | undefined;
if (entry) {
// 检查是否过期
const now = Date.now();
if (now - entry.timestamp < entry.ttl) {
resolve(entry.blob);
return;
} else {
console.log(`PDF缓存已过期: ${key}`);
// 删除过期缓存
deleteCachedPdf(key);
}
}
resolve(null);
};
request.onerror = () => {
console.error('获取PDF缓存失败:', request.error);
resolve(null);
};
});
} catch (error) {
console.error('获取PDF缓存出错:', error);
return null;
}
};
// 添加获取缓存总数的函数
const getCacheCount = async (db: IDBDatabase): Promise<number> => {
return new Promise((resolve) => {
const transaction = db.transaction(DB_CONFIG.storeName, 'readonly');
const store = transaction.objectStore(DB_CONFIG.storeName);
const request = store.count();
request.onsuccess = () => {
resolve(request.result);
};
request.onerror = () => {
console.error('获取缓存数量失败:', request.error);
resolve(0);
};
});
};
// 删除最早的一条缓存记录
// 添加删除最早缓存记录的函数
const deleteOldestCache = async (db: IDBDatabase): Promise<boolean> => {
return new Promise((resolve) => {
const transaction = db.transaction(DB_CONFIG.storeName, 'readwrite');
const store = transaction.objectStore(DB_CONFIG.storeName);
const index = store.index('byTimestamp'); // 使用时间戳索引
// 查找最早的记录(按时间戳升序)
const request = index.openCursor(null, 'next');
request.onsuccess = (event) => {
const cursor = (event.target as IDBRequest<IDBCursorWithValue>).result;
if (cursor) {
// 删除找到的最早记录
const deleteRequest = cursor.delete();
deleteRequest.onsuccess = () => {
console.log('已删除最早的缓存记录');
resolve(true);
};
deleteRequest.onerror = () => {
console.error('删除最早缓存失败:', deleteRequest.error);
resolve(false);
};
} else {
// 没有找到记录
resolve(false);
}
};
request.onerror = () => {
console.error('获取最早缓存失败:', request.error);
resolve(false);
};
});
};
// 修改缓存PDF的函数,添加存储空间检查和清理逻辑
export const cachePdf = async (key: string, blob: Blob, ttl: number = 7 * 24 * 60 * 60 * 1000): Promise<boolean> => {
const entry: PdfCacheEntry = {
key,
blob,
timestamp: Date.now(),
ttl
};
// 递归尝试存储函数
const tryStore = async (db: IDBDatabase): Promise<boolean> => {
return new Promise((resolve, reject) => {
const transaction = db.transaction(DB_CONFIG.storeName, 'readwrite');
const store = transaction.objectStore(DB_CONFIG.storeName);
const request = store.put(entry);
request.onsuccess = () => {
console.log(`PDF已缓存到IndexedDB: ${key}`);
resolve(true);
};
request.onerror = async () => {
console.error('PDF缓存失败:', request.error);
// 检查是否是存储空间不足错误
if (request.error && request.error.name === 'QuotaExceededError') {
console.log('存储空间不足,尝试删除最早的缓存记录...');
// 删除最早的记录
const deleted = await deleteOldestCache(db);
if (deleted) {
// 递归尝试再次存储
resolve(await tryStore(db));
} else {
reject(new Error('存储空间不足且无法删除旧缓存'));
}
} else {
reject(new Error(`缓存失败: ${request.error?.message}`));
}
};
});
};
try {
const db = await openDatabase();
console.log('数据库已经连接上');
return await tryStore(db);
} catch (error) {
console.error('缓存PDF出错:', error);
return false;
}
};
// 删除过期或无效的缓存
export const deleteCachedPdf = async (url: string): Promise<boolean> => {
try {
const db = await openDatabase();
return new Promise((resolve) => {
const transaction = db.transaction(DB_CONFIG.storeName, 'readwrite');
const store = transaction.objectStore(DB_CONFIG.storeName);
const request = store.delete(url);
request.onsuccess = () => {
console.log(`已删除过期缓存: ${url}`);
resolve(true);
};
request.onerror = () => {
console.error('删除缓存失败:', request.error);
resolve(false);
};
});
} catch (error) {
console.error('删除缓存出错:', error);
return false;
}
};
// 从网络获取PDF
export const fetchPdf = async (url: string): Promise<Blob> => {
try {
const response = await fetch(url, {
method: 'GET',
headers: {
Accept: 'application/pdf',
},
});
if (!response.ok) {
throw new Error(`请求失败: ${response.status} ${response.statusText}`);
}
const blob = await response.blob();
// 验证是否为PDF
if (!blob.type.includes('pdf')) {
throw new Error('获取的文件不是PDF格式');
}
return blob;
} catch (error) {
console.error('从网络获取PDF失败:', error);
throw error;
}
};
缓存使用
import { openDatabase,getCachedPdf,cachePdf,deleteCachedPdf,fetchPdf } from '@/utils/cache/indexDb'
const pdfUrlObject = ref<string | null>(null);
const loading = ref<boolean>(false);
const error = ref<string | null>(null);
// 处理PDF显示错误
const handlePdfError = () => {
error.value = '无法显示PDF文件,可能是文件损坏或格式不支持';
revokePdfUrl();
};
// 释放Object URL
const revokePdfUrl = () => {
if (pdfUrlObject.value) {
URL.revokeObjectURL(pdfUrlObject.value);
pdfUrlObject.value = null;
}
};
// 加载并显示PDF
const loadPdf = async (url,key) => {
// 重置状态
loading.value = true;
error.value = null;
revokePdfUrl();
try {
const urls = url;
// 1. 尝试从缓存获取
const cachedBlob = await getCachedPdf(key);
if (cachedBlob) {
console.log('从缓存加载PDF');
pdfUrlObject.value = URL.createObjectURL(cachedBlob);
return;
}
// 2. 缓存未命中,从网络获取
console.log('从网络加载PDF');
const blob = await fetchPdf(urls);
// 3. 缓存到IndexedDB
await cachePdf(key, blob);
// 4. 显示PDF
pdfUrlObject.value = URL.createObjectURL(blob);
} catch (err) {
error.value = err instanceof Error ? err.message : '加载PDF时发生错误';
} finally {
loading.value = false;
}
};
const key=iId+'-'+bId
loadPdf(res,key)
975

被折叠的 条评论
为什么被折叠?



