Egret(微信)资源CRC管理

一、CRC - 循环冗余校验码(Cyclic Redundancy Check)

因为本人才疏学浅,这里就不做原理解释了,详细原理请自行百度~

二、Egret中生成CRC后缀

egret在 config.ts 文件中提供了相应的接口,在下方代码中可以看到 else if (command == ‘publish’) 这个判断,就是对 publish 命令的开放扩展,我们就是通过RenamePlugin(修改文件名插件) 这里对项目的资源进行添加CRC后缀。
在这里插入图片描述
这些是构造中参数的详细说明
其中 matchers 这个就是匹配机制,我们可以通过这个自定义命名

  • 满足 from 的文件输出为 to 格式的文件
  • from 采用 glob 表达式 , to 包含 [path][name][hash][ext] 四个变量 (我们可以在四个变量中间添加自己需要的分隔符,比如 _ - 等)
    需要注意的是 hash egret 目前只支持 crc32
type RenamePluginOptions = {

        /**
         * 是否输出日志
         * Whether to output the log
         */
        verbose?: boolean

        /**
         * 采用何种 hash 算法,目前暂时只支持 crc32
         * What hash algorithm is used, currently only crc32 is supported
         */
        hash?: "crc32"


        /**
         * 设置匹配规则,将指定文件进行改名
         * 该参数是个数组,允许设置多个匹配规则
         * Set up matching rules to copy specified files to other folders
         * This parameter is an array that allows multiple matching rules to be set
         */
        matchers: Matcher[]

        /**
         * 回调函数,返回值里包括文件的一些信息
         * The callback function, return value includes some information about the file
         */
        callback?: Function
    }

经过上面的修改后,就可以在Egret的控制台输入 egret publish进行打包尝试了,如果 verbose 设置的为true 的话就可以在控制台看到重命名的输出了
在这里插入图片描述

三、生成version文件

经过上面的操作后打出来的资源就有了我们想要的后缀,但是我们还需要一份 version 文件来进行资源对比

1. 添加自定义插件 可以在 config.ts 同级下创建一个自己的插件类,在config.ts

中导入就可以以上面的方式添加到**commands**中执行了

ResPlugin要实现plugins.Command接口,这里是接口的声明

    /**
     * 构建管线命令
     */
    interface Command {

        /**
         * 项目中的每个文件都会执行此函数,返回 file 表示保留此文件,返回 null 表示将此文件从构建管线中删除,即不会发布
         */
        onFile?(file: File): Promise<File | null>

        /**
         * 项目中所有文件均执行完后,最终会执行此函数。
         * 这个函数主要被用于创建新文件
         */
        onFinish?(pluginContext?: CommandContext): Promise<void>

        [options: string]: any;
    }

2. 实现ResPlugin插件

通过 onFile 方法 可以实现对文件进行过滤,不需要的文件返回 null 就可以了
下面以名为 ResPlugin 插件为例

export class ResPlugin implements plugins.Command { 
 
    // 文件信息 
    private versionConfig = {}; 
    // 用于判断资源是否在这个目录下 
    private asstesVersionPath = "resource/asset/"; 
    // 输出文件路径 
    private versionConfigPath = "resource/version"; 
    // 输出文件扩展名 
    private versionConfigExtname = ".json"; 
    // 截取的文本版本号 
    private versionNo: string; 
 
    constructor() { 
    } 
 
    async onFile(file: plugins.File) { 
        // 文件扩展名 
        const extname = file.extname; 
        // 文件初始名 
        const path = file.origin; 
        const _index = file.path.lastIndexOf("\\"); 
        // 真实文件名 
        const _path = file.path.substring(_index + 1); 

        if (path.indexOf(this.asstesVersionPath) != -1 && (extname === ".mp3" || extname === ".fnt" || extname === ".json" || extname === ".png" 
            || extname === ".jpg" || extname === ".xml" || extname === ".dbbin" || extname === ".txt" || extname === ".dat" || extname === ".bin") 
            || extname === ".json") { 
            this.versionConfig[path] = _path; 
        } else { 
            // 截取游戏版本号 
            if (path.indexOf('jslauncher.js') != -1 && !this.versionNo) { 
                let jslauncher = file.contents.toString();
                let arr = jslauncher.match('(?<=versionNo.*=.*").*?(?=\")');
                this.versionNo = arr ? arr[0] : '';
            } 
        } 
        return file; 
    } 
 
    async onFinish(commandContext: plugins.CommandContext) { 
        let fileName = this.versionConfigPath + this.versionNo + this.versionConfigExtname; 
        commandContext.createFile(fileName, new Buffer(JSON.stringify(this.versionConfig))); 
    } 
} 

3. 调用ResPlugin插件

回到 config.ts 中,通过 import { ResPlugin } from ‘./ResPlugin’;ResPlugin 导入,然后在 commands 中添加就可以了
需要注意的是from后面是相对路径,不需要写文件后缀
在这里插入图片描述

四、CRC添加完成

经过上面一顿忙活后就可以打包出来看效果了,用命令打包和Egret Launcher打包都是可以的
在这里插入图片描述
在这里插入图片描述

五、version文件的使用

1. 自定义版本控制器

实现 RES.IVersionController 接口,我们需要通过这两个接口去实现老文件的删除
该接口有两个方法

/**
 * 获取版本信息数据。<br/>
 * 这个方法的调用需要在应用程序进行任何资源加载之前,建议在应用程序的入口类(Main)的开始最先进行调用处理。此方法只负责获取版本信息,不负责资源的下载。
 * @version Egret 2.4
 * @platform Web,Native
 * @language zh_CN
 */
init(): Promise<any>;

/**
 * 获取资源文件实际的URL地址。<br/>
 * 由于版本控制实际已经对原来的资源文件的URL进行了改变,因此想获取指定资源文件实际的URL时需要调用此方法。<br/>
 * 在开发调试阶段,这个方法会直接返回传入的参数值。
 * @param url 游戏中使用的url
 * @returns 实际加载的url
 * @version Egret 2.4
 * @platform Web,Native
 * @language zh_CN
 */
getVirtualUrl(url: string): string;

思路:在 init 中获取到网络上最新的version.json文件,并读取本地的version.json文件,两个文件进行对比即可获得需要删除的老文件,然后删除他们,并把最新的version.json文件写入本地。

a. 获取最新version.josn文件

// 在游戏开始加载资源的时候就会调用这个函数
init(): Promise<any> {
    if (RELEASE) {//发布模式
        return new Promise((resolve, reject) => {
            // 这里通过httpReques获得配置资源信息
            let request = new egret.HttpRequest();
            request.responseType = egret.HttpResponseType.TEXT;
            request.open(this.versionConfigPath + "?v=" + Math.random(), egret.HttpMethod.GET);
            request.send();
            request.addEventListener(egret.Event.COMPLETE, (event: egret.Event) => {
                var request = <egret.HttpRequest>event.currentTarget;
                try {
                    this.versionConfig = JSON.parse(request.response);
                    // 处理文件资源的缓存
                    // 下面主要是对各种运行时的版本控制,
                    if (egret.Capabilities.runtimeType == egret.RuntimeType.WEB) {
                        this.versionCacheWeb();//浏览器 H5
                    } else {
                        this.versionCacheWxgame();
                    }
                }
                catch (e) {
                    Main.onRemoveSubPackLoading();
                    console.error("versionConfig error:" + e);
                }
                resolve();
            }, this);
            request.addEventListener(egret.IOErrorEvent.IO_ERROR, () => {
                //egret.log("post error : " + event);
                Main.onRemoveSubPackLoading();
                resolve();
            }, this);
        })
    } else {
        return Promise.resolve()
    }
}

b. 获取需要删除的资源路径:

根据本地缓存获取上次的资源版本,判断是否有版本变动,有的话就直接全部删除,然后写入新的version.json文件,没有变动就获取需要删除的资源路径

/**
 * 处理wxgame里面的无效资源
 */
private versionCacheWxgame = () => {
    // 简单处理,通过localStorage获取当前资源版本号resVersion,如果版本号不同,进行删除过期资源操作
    // 这里开发者可以自己控制版本号,不一定使用localStorage
    try {
        const localVersion = egret.localStorage.getItem("resVersion");
        console.log("resVersion -->> " + localVersion)
        // 资源版本变动
        if ((localVersion != this.currResourceVersion)) {
            egret.localStorage.setItem("resVersion", this.currResourceVersion);
            ClearLastLocalCache();
            mkdir(this.currResourceVersion);
            writeFile(this.currResourceVersion + "/version" + this.currentVersion + ".json", JSON.stringify(this.versionConfig));

        } else {
            // 这里检查要删除的旧资源
            let oldRes = this.getRemoveList();
            if (oldRes && oldRes.length > 0) {
                // 删除旧资源
                for (let i = 0; i < oldRes.length; i++) {
                    deleteLocalFile(oldRes[i]);
                }
            }
        }
    } catch (e) {

    }
    Main.onRemoveSubPackLoading();
}



/** 
* 获得上版本的过期资源列表,提高效率使用字符串的形式进行比对
*/
private getRemoveList(): Array<string> {
    try {
        let rootPath = GetUserDataPath();
        console.log('---------rootPath------ ' + rootPath)
        let versionPath = this.currResourceVersion + "/version" + this.currentVersion + ".json";
        let result = [];
        if (fileExist(versionPath)) {
            // 读取本地version文件
            let content = readFileSync(versionPath, 'utf-8');
            if (!content) {
                // 这里替换原来的version文件
                deleteLocalFile(versionPath);
                writeFile(this.currResourceVersion + "/version" + this.currentVersion + ".json", JSON.stringify(this.versionConfig));
                return result;
            }
            let oldVersion = JSON.parse(content);
            let temp = '';
            for (const key in oldVersion) {
                if (!this.versionConfig[key] || (this.versionConfig[key] != oldVersion[key])) {
                    console.log('key ---- > ' + key) // resource/asset/sound/challenge/match.mp3
                    console.log('value -- > ' + oldVersion[key]) // value -- > match_36ef1cca.mp3
                    // 把文件真实名拼接出来,
                    temp = this.currResourceVersion + key.substring(key.indexOf('/'), key.lastIndexOf('/')) + '/' + oldVersion[key];
                    console.log('拼接后的路径 -->> ' + temp) // 9.9.9.9/asset/sound/challenge/match_36ef1cca.mp3
                    result.push(temp);
                }
            }
            // 这里替换原来的version文件
            deleteLocalFile(versionPath);
            writeFile(this.currResourceVersion + "/version" + this.currentVersion + ".json", JSON.stringify(this.versionConfig));
    
        } else {
            // 没有老的version文件就直接生成
            mkdir(this.currResourceVersion);
            writeFile(this.currResourceVersion + "/version" + this.currentVersion + ".json", JSON.stringify(this.versionConfig));
        }
    
        return result;
    
    } catch (e) {
        console.error('getRemoveList -- error')
        return null;
    }
}

c. 实现 getVirtualUrl 接口,用于获取资源文件实际的URL地址

//在游戏运行时,每个资源加载url都要经过这个函数
getVirtualUrl(url: string) {
    return this.getResUrlByVersion(url);
}
/**
 * 获得版本控制之后的路径信息
 */
getResUrlByVersion(url: string): string {
    //判断是否为版本控制的资源,其他域的资源,比如玩家头像,或是初始包体里面的资源以原始url加载
    if (url.indexOf(this.resourceRoot) == -1) {
        return url;
    }
    console.log('version ---> ' + url) //  https://uqxsstest.pook.com/update/9.9.9.9/resource/asset/view/img/common/common_ui/common_ui.json
    const _url = url;
    //将文件的resourceRoot路径抹去,进而判读文件是否经过版本管理
    url = url.replace(this.resourceRoot, "");
    if (this.versionConfig) {
        // 部分文件可能存在?v=加数字进行控制的形式,在引擎底层这里是不支持加v=的,只会以原始url加载路径,
        // 如果项目中存在类似的情况,将其还原成普通形式
        const index = url.indexOf("?v=");
        if (index != -1) {
            url = url.slice(0, index);
        }

        //取版本控制的的version
        let versionKey = this.versionPath + url;
        let _key = url.substring(url.lastIndexOf("/") + 1);
        //console.log("versionKey:" + versionKey);
        if (this.versionConfig[versionKey]) {
            const version = this.versionConfig[versionKey];
            const ext = url.slice(url.lastIndexOf("."));
            // 原始的文件夹+crc32码+后缀扩展名
            url = url.replace(_key, version);
        } else {
            return _url;
        }
    }
    url = this.resourceRoot + url;
    console.log('version end --> ' + url) // https://uqxsstest.pook.com/update/9.9.9.9/resource/asset/view/img/common/common_ui/common_ui_948ba063.json
    return url;
}

完整代码:

declare var wx: { getFileSystemManager, env }
class VersionController implements RES.IVersionController {
    // 版本控制信息
    private versionConfig;
    // resource根路径
    private resourceRoot = getImgUrl() + "resource/";
    // 版本控制的文件夹
    private versionPath = "resource/";
    // 版本控制信息的所在路径,相对于resource文件夹
    private versionConfigPath = getImgUrl() + "/resource/version" + getVersionNo() + ".json";
    // wxgame的当前版本号
    private currentVersion = getVersionNo();
    // 当前资源版本号
    private currResourceVersion = getResourceVersion();

    public constructor() {
    }

    // 在游戏开始加载资源的时候就会调用这个函数
    init(): Promise<any> {
        if (RELEASE) {//发布模式
            return new Promise((resolve, reject) => {
                //通过httpReques获得配置资源信息
                let request = new egret.HttpRequest();
                request.responseType = egret.HttpResponseType.TEXT;
                request.open(this.versionConfigPath + "?v=" + Math.random(), egret.HttpMethod.GET);
                request.send();
                request.addEventListener(egret.Event.COMPLETE, (event: egret.Event) => {
                    var request = <egret.HttpRequest>event.currentTarget;
                    try {
                        this.versionConfig = JSON.parse(request.response);
                        //处理文件资源的缓存
                        // 下面主要是对各种运行时的版本控制,
                        if (egret.Capabilities.runtimeType == egret.RuntimeType.WEB) {
                            this.versionCacheWeb();//浏览器 H5
                        } else {
                            this.versionCacheWxgame();
                        }
                    }
                    catch (e) {
                        Main.onRemoveSubPackLoading();
                        console.error("versionConfig error:" + e);
                    }
                    resolve();
                }, this);
                request.addEventListener(egret.IOErrorEvent.IO_ERROR, () => {
                    Main.onRemoveSubPackLoading();
                    resolve();
                }, this);
            })
        } else {
            return Promise.resolve()
        }
    }

    /**
     * 处理wxgame里面的无效资源
     */
    private versionCacheWxgame = () => {
        // 简单处理,通过localStorage获取当前资源版本号resVersion,如果版本号不同,进行删除过期资源操作
        // 这里开发者可以自己控制版本号,不一定使用localStorage
        try {
            const localVersion = egret.localStorage.getItem("resVersion");
            // 资源版本变动
            if ((localVersion != this.currResourceVersion)) {
                egret.localStorage.setItem("resVersion", this.currResourceVersion);
                ClearLastLocalCache();
                mkdir(this.currResourceVersion);
                writeFile(this.currResourceVersion + "/version" + this.currentVersion + ".json", JSON.stringify(this.versionConfig));

            } else {
                // 这里检查要删除的旧资源
                let oldRes = this.getRemoveList();
                if (oldRes && oldRes.length > 0) {
                    // 删除旧资源
                    for (let i = 0; i < oldRes.length; i++) {
                        deleteLocalFile(oldRes[i]);
                    }
                }
            }
        } catch (e) {

        }
        Main.onRemoveSubPackLoading();
    }
    /**
    * 处理WebH5里面的更新,如果开发者有其他平台发布的版本控制,请参照这两种进行修改
    */
    private versionCacheWeb = () => {
        // 简单处理,通过localStorage获取当前资源版本号resVersion,如果版本号不同,进行删除过期资源操作
        // 这里开发者可以自己控制版本号,不一定使用localStorage
        const localVersion = egret.localStorage.getItem("resVersion");
        if (localVersion != this.currResourceVersion) {
            egret.localStorage.setItem("resVersion", this.currResourceVersion);
            console.log("Web版本更新");
        }
    }
    /** 
     * 获得上版本的过期资源列表,提高效率使用字符串的形式进行比对
     */
    private getRemoveList(): Array<string> {
        try {
            let rootPath = GetUserDataPath();
            let versionPath = this.currResourceVersion + "/version" + this.currentVersion + ".json";
            let result = [];
            if (fileExist(versionPath)) {
                let content = readFileSync(versionPath, 'utf-8');
                if (!content) {
                    // 这里替换原来的version文件
                    deleteLocalFile(versionPath);
                    writeFile(this.currResourceVersion + "/version" + this.currentVersion + ".json", JSON.stringify(this.versionConfig));
                    return result;
                }
                let oldVersion = JSON.parse(content);
                let temp = '';
                for (const key in oldVersion) {
                    if (!this.versionConfig[key] || (this.versionConfig[key] != oldVersion[key])) {
                        temp = this.currResourceVersion + key.substring(key.indexOf('/'), key.lastIndexOf('/')) + '/' + oldVersion[key];
                        result.push(temp);
                    }
                }
                // 这里替换原来的version文件
                deleteLocalFile(versionPath);
                writeFile(this.currResourceVersion + "/version" + this.currentVersion + ".json", JSON.stringify(this.versionConfig));

            } else {
                // 没有老的version文件就直接生成
                mkdir(this.currResourceVersion);
                writeFile(this.currResourceVersion + "/version" + this.currentVersion + ".json", JSON.stringify(this.versionConfig));
            }
            return result;
        } catch (e) {
            console.error('getRemoveList -- error')
            return null;
        }
    }
    //在游戏运行时,每个资源加载url都要经过这个函数
    getVirtualUrl(url: string) {
        return this.getResUrlByVersion(url);
    }
    /**
     * 获得版本控制之后的路径信息
     */
    getResUrlByVersion(url: string): string {
        //判断是否为版本控制的资源,其他域的资源,比如玩家头像,或是初始包体里面的资源以原始url加载
        if (url.indexOf(this.resourceRoot) == -1) {
            return url;
        }
        const _url = url;
        //将文件的resourceRoot路径抹去,进而判读文件是否经过版本管理
        url = url.replace(this.resourceRoot, "");
        if (this.versionConfig) {
            // 部分文件可能存在?v=加数字进行控制的形式,在引擎底层这里是不支持加v=的,只会以原始url加载路径,
            // 如果项目中存在类似的情况,将其还原成普通形式
            const index = url.indexOf("?v=");
            if (index != -1) {
                url = url.slice(0, index);
            }
            //取版本控制的的version
            let versionKey = this.versionPath + url;
            let _key = url.substring(url.lastIndexOf("/") + 1);
            if (this.versionConfig[versionKey]) {
                const version = this.versionConfig[versionKey];
                const ext = url.slice(url.lastIndexOf("."));
                // 原始的文件夹+crc32码+后缀扩展名
                url = url.replace(_key, version);
            } else {
                return _url;
            }
        }
        url = this.resourceRoot + url;
        return url;
    }
}

2. 注册版本控制器

根据他们的建议,我们在 Main.ts 的构造函数中注册自己实现的管理器 **RES.registerVersionController(new VersionController());**就可以了
在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值