1、思路 把h5页面的静态资源缓存到沙箱、再请求时根据原资源如果命中沙箱保存的资源,则返回
import fs, { ReadOptions } from '@ohos.file.fs';
import { common } from '@kit.AbilityKit';
import { XLogUtil } from "../Utils/XLogUtil";
import { http } from '@kit.NetworkKit';
import { CommonFunctions } from "../CommonFunctions/CommonFunctions"
import fileIo from '@ohos.file.fs';
// 需要缓存的文件格式
const CACHEABLE_EXTENSIONS = [
// 图片
"png", "jpg", "jpeg", "gif", "webp", "svg", "bmp", "ico",
// 样式
"css",
// 脚本
"js", "xml", "json",
// 字体
"woff", "woff2", "ttf"
];
export class H5CacheManager {
/**
* 下载资源并保存到内存和磁盘缓存
* @param url 资源URL
*/
static isNeedResource(url: string, webResourceResponse: WebResourceResponse): WebResourceResponse | null {
//检查磁盘缓存是不是有了,有就不要下载了
let localResource = CommonFunctions.getH5Resource(url)
if (localResource.length > 0) {
XLogUtil.debug("返回的路径是" + localResource)
let data: string | number | Resource | ArrayBuffer
// 判断是否为图片资源(根据URL后缀)
const isImage = /\.(jpg|jpeg|png|gif|svg|webp)$/i.test(url);
if (isImage) {
let file = fs.openSync(localResource)
let stat = fs.statSync(file.fd)
let buf = new ArrayBuffer(stat.size)
fs.readSync(file.fd, buf)
data = buf;
fs.closeSync(file)
} else {
//否则
data = fileIo.readTextSync(localResource);
}
XLogUtil.debug("返回的路径的数据:" + JSON.stringify(data))
const mimeType = H5CacheManager.getMimeTypeFromUrl(url);
// 构造拦截响应
webResourceResponse.setResponseData(data); // 设置缓存数据
webResourceResponse.setResponseMimeType(mimeType); // MIME类型
webResourceResponse.setResponseEncoding('utf-8');
webResourceResponse.setResponseCode(200);
webResourceResponse.setReasonMessage('OK');
webResourceResponse.setResponseHeader([{ headerKey: 'from-cache', headerValue: 'true' }]); // 标记来自缓存
webResourceResponse.setResponseIsReady(true);
XLogUtil.debug(`有缓存,拦截并使用缓存 webResourceResponse:${JSON.stringify(webResourceResponse.getResponseData())}`);
return webResourceResponse;
}
XLogUtil.debug("没有缓存,去保存" + url)
H5CacheManager.prefetchResource(url)
return null
}
static async prefetchResource(url: string): Promise<void> {
try {
// 检查是否为需要缓存的格式,不符合则直接返回
if (!H5CacheManager.isCacheableResource(url)) {
XLogUtil.debug(`资源格式不支持缓存,跳过下载:${url}`);
return;
}
// 3. 从网络下载(遵循http模块规范)
const request = http.createHttp();
const response = await request.request(url, { method: http.RequestMethod.GET, expectDataType: 2 });
if (response.result && response.responseCode === 200) {
const arrayBuffer = response.result as ArrayBuffer;
XLogUtil.debug("资源数据" + JSON.stringify(arrayBuffer))
CommonFunctions.saveToFiles(arrayBuffer, url);
} else {
XLogUtil.warn(`资源下载失败,状态码:${response.responseCode},URL:${url}`);
}
} catch (error) {
XLogUtil.error(`资源下载失败:${url},错误:${error.message}`);
}
}
static isCacheableResource(url: string): boolean {
const ext = H5CacheManager.getFileExtension(url);
return CACHEABLE_EXTENSIONS.includes(ext);
}
/**
* 从URL中提取文件扩展名(不含`.`)
* @param url 资源URL
* @returns 扩展名(小写),如"png";无扩展名则返回空字符串
*/
static getFileExtension(url: string): string {
// 去除URL中的查询参数(?后面)和哈希(#后面)
const cleanUrl = url.split(/[?#]/)[0];
// 分割文件名与扩展名
const parts = cleanUrl.split('.');
// 无扩展名或扩展名长度为0的情况(如".gitignore"视为无有效扩展名)
if (parts.length < 2 || parts[parts.length - 1].length === 0) {
return '';
}
// 返回小写扩展名
return parts[parts.length - 1].toLowerCase();
}
/**
* 获取应用专属缓存目录路径(基于官方沙箱路径)
* @param context 应用上下文
* @returns 缓存目录完整路径
*/
getCacheDir(context: common.Context): string {
// 文档说明:cacheDir为应用专属缓存目录,无需手动创建
return context.cacheDir + '/h5_resources/';
}
/**
* 根据URL获取MIME类型(辅助方法)
* @param url 资源URL
* @returns MIME类型字符串
*/
static getMimeTypeFromUrl(url: string): string {
if (url.endsWith('.css')) {
return 'text/css';
}
if (url.endsWith('.js')) {
return 'application/javascript';
}
if (url.endsWith('.png')) {
return 'image/png';
}
if (url.endsWith('.jpg') || url.endsWith('.jpeg')) {
return 'image/jpeg';
}
if (url.endsWith('.svg')) {
return 'image/svg+xml';
}
if (url.endsWith('.json')) {
return 'application/json';
}
return 'text/plain';
}
}
2、保存方法
// 保存文件
static async saveToFiles( buffer: ArrayBuffer, url: string, callback?: (sanPath:string) => void): Promise<void> {
let fd : number | undefined = undefined;0
try {
let dirPath = getContext().cacheDir + `/h5_cache`;
// 如果目录不存在
if (!fs.accessSync(dirPath)) {
// 则创建
fs.mkdirSync(dirPath, true);
}
const fileName = CommonFunctions.getFileNameFromUrl(url);
const getDirPath = dirPath + "/"+ fileName
XLogUtil.debug(TAG,"存储路径:" + getDirPath)
const mode = fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE;
fd = (await fs.open(getDirPath, mode)).fd;
await fs.truncate(fd);
await fs.write(fd, buffer);
callback?.(getDirPath)
XLogUtil.debug(TAG,"创建目录并写入成功" + getDirPath)
} catch (err) {
XLogUtil.error(TAG,`保存文件失败${JSON.stringify(err)}`)
} finally {
if (fd) {
fs.close(fd);
}
}
}
3、使用
.onInterceptRequest((event) => {
XLogUtil.info('资源请求数量:'+ this.onInterceptRequestCount++)
const url = event.request.getRequestUrl();
let getData = H5CacheManager.isNeedResource(url,this.webResourceResponse)
if (getData){
return getData
}
})
4、欢迎留意讨论哦