Cocos Creator 热更新地址动态化方案

基于 Cocos Creator 的 AssetsManager 热更新机制,本方案实现以下能力:

  • CDN 地址运行时动态下发,无需发包即可切换更新源
  • 主备容灾自动切换,降低单点故障风险
  • 灰度发布,支持按比例逐步放量
  • 本地缓存优化,优先复用上次可用地址
  • 可扩展配置体系,便于后续增加策略与字段

一、整体流程

复制代码
    ┌──────────────┐
    │   App 启动    │
    └──────┬───────┘
           ↓
┌────────────────────┐
│ 读取本地缓存 URL     │
└──────┬─────────────┘
       ↓
┌────────────────────┐
│ 拉取远程配置 JSON    │
└──────┬─────────────┘
       ↓
┌────────────────────┐
│ URL 决策(灰度/容灾)│
└──────┬─────────────┘
       ↓
┌────────────────────┐
│ 重写 Manifest        │
└──────┬─────────────┘
       ↓
┌────────────────────┐
│ 初始化 AssetsManager │
└──────┬─────────────┘
       ↓
┌────────────────────┐
│ checkUpdate         │
└────────────────────┘

二、模块拆分

模块 文件 职责
ConfigService config-service.ts 拉取远程配置
UrlSelector url-selector.ts 灰度与容灾决策
ManifestUtil manifest-util.ts 动态重写 manifest
HotUpdateManager hot-update.ts 热更新主流程控制

三、代码实现


1. ConfigService(远程配置拉取)

负责请求配置中心,获取 CDN 列表、灰度比例等信息。建议配合 HTTPS、签名校验与缓存策略进一步增强安全性与稳定性。

tsx 复制代码
export interface RemoteConfig {
    version: number;
    cdn: string[];
    gray?: number;
}

const CONFIG_URL = "https://config.xxx.com/game.json";
const TIMEOUT = 3000;

export class ConfigService {

    static async fetch(): Promise<RemoteConfig | null> {
        return new Promise((resolve) => {
            const xhr = new XMLHttpRequest();
            xhr.timeout = TIMEOUT;

            xhr.open("GET", CONFIG_URL);

            xhr.onreadystatechange = () => {
                if (xhr.readyState === 4) {
                    if (xhr.status >= 200 && xhr.status < 300) {
                        try {
                            const json = JSON.parse(xhr.responseText);
                            resolve(json);
                        } catch {
                            resolve(null);
                        }
                    } else {
                        resolve(null);
                    }
                }
            };

            xhr.ontimeout = () => resolve(null);
            xhr.send();
        });
    }
}

2. UrlSelector(灰度 + 容灾)

将"分流"和"地址选择"抽离成独立模块,便于后续替换为更稳定的 hash(如 CRC32 库)或引入更复杂的路由策略。

tsx 复制代码
export class UrlSelector {

    // CRC32 简化实现(可替换为成熟库)
    static hash(str: string): number {
        let hash = 0;
        for (let i = 0; i < str.length; i++) {
            hash = (hash << 5) - hash + str.charCodeAt(i);
            hash |= 0;
        }
        return Math.abs(hash);
    }

    // 基于 uid 的稳定分流:同一 uid 结果尽量保持一致
    static pick(uid: string, cdns: string[]): string {
        const index = this.hash(uid) % cdns.length;
        return cdns[index];
    }

    // 按百分比灰度:返回一个候选列表,供后续 pick 或容灾逻辑使用
    static applyGray(cdns: string[], gray: number): string[] {
        if (!gray) return cdns;

        const rand = Math.random() * 100;
        if (rand < gray) {
            return [cdns[0]]; // 新 CDN
        }
        return [cdns[1] || cdns[0]]; // 老 CDN(兜底)
    }
}

3. ManifestUtil(动态重写)

通过重写 manifest 中的地址字段,实现热更新源在运行时切换。注意 baseUrl 的尾斜杠处理,避免拼接出错。

tsx 复制代码
export class ManifestUtil {

    static rewrite(content: string, baseUrl: string): string {
        const manifest = JSON.parse(content);

        const root = baseUrl.endsWith("/") ? baseUrl : baseUrl + "/";

        manifest.packageUrl = root;
        manifest.remoteManifestUrl = root + "project.manifest";
        manifest.remoteVersionUrl = root + "version.manifest";

        return JSON.stringify(manifest);
    }

    static generateTemp(manifestStr: string): string {
        const path = jsb.fileUtils.getWritablePath() + "temp.manifest";
        jsb.fileUtils.writeStringToFile(manifestStr, path);
        return path;
    }
}

4. HotUpdateManager(核心流程)

主流程包含:读取缓存、拉取配置、灰度处理、选择最终 URL、重写 manifest、初始化 AssetsManager、触发更新。

tsx 复制代码
const DEFAULT_URL = "https://cdn-default.xxx.com/game/";
const STORAGE_PATH = jsb.fileUtils.getWritablePath() + "update/";

export class HotUpdateManager {

    private _am: jsb.AssetsManager | null = null;

    async start(localManifestPath: string, uid: string) {

        // 1. 本地缓存优先
        const cacheUrl = cc.sys.localStorage.getItem("HOT_UPDATE_URL");

        // 2. 拉取远程配置
        const config = await ConfigService.fetch();

        let cdns: string[] = [DEFAULT_URL];

        if (config && config.cdn && config.cdn.length > 0) {
            cdns = config.cdn;

            // 灰度处理
            cdns = UrlSelector.applyGray(cdns, config.gray || 0);
        }

        // 3. 选最终 URL(缓存命中则直接用缓存)
        const finalUrl = cacheUrl || UrlSelector.pick(uid, cdns);

        // 4. 读取本地 manifest
        const content = jsb.fileUtils.getStringFromFile(localManifestPath);

        // 5. 重写 manifest
        const newManifest = ManifestUtil.rewrite(content, finalUrl);

        // 6. 生成临时文件
        const tempPath = ManifestUtil.generateTemp(newManifest);

        // 7. 初始化
        this._am = new jsb.AssetsManager(tempPath, STORAGE_PATH);

        // 8. 绑定回调
        this._am.setEventCallback(this._onUpdateEvent.bind(this));

        // 9. 开始检测
        this._am.checkUpdate();
    }

    private _onUpdateEvent(event: jsb.EventAssetsManager) {
        switch (event.getEventCode()) {

            case jsb.EventAssetsManager.NEW_VERSION_FOUND:
                this._am?.update();
                break;

            case jsb.EventAssetsManager.UPDATE_FINISHED:
                console.log("更新完成");
                break;

            case jsb.EventAssetsManager.UPDATE_FAILED:
                console.warn("更新失败,尝试重试");
                this._am?.downloadFailedAssets();
                break;

            case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST:
                console.error("本地 manifest 缺失");
                break;

            case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST:
                console.error("manifest 下载失败");
                break;
        }
    }
}

四、关键数据结构

远程配置 JSON 示例

json 复制代码
{
  "version": 3,
  "cdn": [
    "https://cdn-a.xxx.com/game/",
    "https://cdn-b.xxx.com/game/"
  ],
  "gray": 20
}

五、容灾策略对比

策略 描述 优点 缺点
单 CDN 固定地址 简单 无容灾
主备切换 失败后切换 稳定 切换慢
并行探测 同时检测多个 CDN 切换快 成本高
灰度路由 按用户分流 可控 实现复杂

六、核心时序

复制代码
Client                    ConfigServer             CDN
  |                           |                    |
  |---- 请求配置 ------------>|                    |
  |<--- 返回 JSON ------------|                    |
  |                           |                    |
  |---- 请求 version -------->|------------------->|
  |<--- 返回 version ---------|<-------------------|
  |                           |                    |
  |---- 下载资源 ----------->|------------------->|
  |<--- 返回资源 ------------|<-------------------|
相关推荐
weixin_409383122 个月前
cocosshader像素风沙消散
shader·cocos
weixin_409383122 个月前
cocos shader消失
shader·cocos
weixin_409383122 个月前
cocos魔法阵shader
shader·cocos
weixin_409383122 个月前
cocos抛物线掉落装备 游戏中的抛物线应用x²=-2py 开口向下
游戏·cocos·抛物线
weixin_409383122 个月前
cocos 按钮光环shader
shader·cocos
weixin_409383124 个月前
a星学习记录 通过父节点从目的地格子坐标回溯起点
学习·cocos·a星
weixin_409383124 个月前
简单四方向a*寻路学习记录2 先做个数组地图 在cocos编辑器模式上运行出格子 计算角色世界坐标跟数组地图的联系
学习·编辑器·cocos
weixin_409383125 个月前
cocos 用widget将ui组件固定在屏 随着分辨率自适应 编辑器界面canvas作为手机屏参考 将ui组件放进去 deepseek解答
ui·cocos
centor8 个月前
Cocos Creator 制作微信试玩广告
cocos