mirror of
https://github.com/Gongxh0901/kunpolibrary
synced 2025-12-05 20:29:10 +00:00
添加项目规则;修复几处潜在问题
This commit is contained in:
413
.cursor/rules/hot-update.mdc
Normal file
413
.cursor/rules/hot-update.mdc
Normal file
@@ -0,0 +1,413 @@
|
||||
---
|
||||
description: "热更新系统开发规范"
|
||||
globs: ["src/hotupdate/**/*.ts"]
|
||||
alwaysApply: false
|
||||
type: "system-module"
|
||||
---
|
||||
|
||||
# 热更新系统开发规范
|
||||
|
||||
## 热更新架构设计
|
||||
|
||||
### 管理器单例模式
|
||||
```typescript
|
||||
export class HotUpdateManager {
|
||||
private static _instance: HotUpdateManager;
|
||||
|
||||
/** 获取单例实例 */
|
||||
public static getInstance(): HotUpdateManager {
|
||||
if (!this._instance) {
|
||||
this._instance = new HotUpdateManager();
|
||||
}
|
||||
return this._instance;
|
||||
}
|
||||
|
||||
/** 禁用直接构造 */
|
||||
private constructor() {}
|
||||
|
||||
/** 配置属性 */
|
||||
public manifestUrl: string = "";
|
||||
public versionUrl: string = "";
|
||||
|
||||
/** 初始化热更新 */
|
||||
public init(manifestUrl: string, versionUrl: string): void {
|
||||
this.manifestUrl = manifestUrl;
|
||||
this.versionUrl = versionUrl;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 热更新状态码定义
|
||||
```typescript
|
||||
export enum HotUpdateCode {
|
||||
/** 成功 */
|
||||
Succeed = 0,
|
||||
/** 已是最新版本 */
|
||||
LatestVersion = 1,
|
||||
/** 检查更新失败 */
|
||||
CheckFailed = 2,
|
||||
/** 下载失败 */
|
||||
DownloadFailed = 3,
|
||||
/** 解压失败 */
|
||||
UnzipFailed = 4,
|
||||
/** 网络错误 */
|
||||
NetworkError = 5,
|
||||
/** 空间不足 */
|
||||
NoSpace = 6,
|
||||
/** 未知错误 */
|
||||
UnknownError = 7
|
||||
}
|
||||
```
|
||||
|
||||
## Promise 结果模式
|
||||
|
||||
### 统一结果接口
|
||||
```typescript
|
||||
export interface IPromiseResult {
|
||||
/** 状态码 */
|
||||
code: HotUpdateCode;
|
||||
|
||||
/** 消息描述 */
|
||||
message: string;
|
||||
|
||||
/** 扩展数据 */
|
||||
data?: any;
|
||||
}
|
||||
```
|
||||
|
||||
### 热更新配置接口
|
||||
```typescript
|
||||
export interface IHotUpdateConfig {
|
||||
/** 版本号 */
|
||||
version: string;
|
||||
|
||||
/** 远程manifest文件URL */
|
||||
remoteManifestUrl: string;
|
||||
|
||||
/** 远程version文件URL */
|
||||
remoteVersionUrl: string;
|
||||
|
||||
/** 资源包URL */
|
||||
packageUrl: string;
|
||||
|
||||
/** 资源文件列表 */
|
||||
assets?: { [key: string]: any };
|
||||
|
||||
/** 搜索路径 */
|
||||
searchPaths?: string[];
|
||||
}
|
||||
```
|
||||
|
||||
## 热更新核心实现
|
||||
|
||||
### HotUpdate 核心类
|
||||
```typescript
|
||||
export class HotUpdate {
|
||||
private _am: jsb.AssetsManager;
|
||||
private _updating: boolean = false;
|
||||
|
||||
constructor(manifestUrl: string) {
|
||||
// 初始化 AssetsManager
|
||||
this._am = new jsb.AssetsManager(manifestUrl, jsb.fileUtils.getWritablePath() + 'remote-assets');
|
||||
this._am.setEventCallback(this.onUpdateEvent.bind(this));
|
||||
this._am.setVerifyCallback(this.onVerifyCallback.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查更新
|
||||
* @returns Promise<IPromiseResult>
|
||||
*/
|
||||
public checkUpdate(): Promise<IPromiseResult> {
|
||||
return new Promise((resolve) => {
|
||||
if (this._updating) {
|
||||
resolve({ code: HotUpdateCode.UnknownError, message: "正在更新中" });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._am.getLocalManifest() || !this._am.getLocalManifest().isLoaded()) {
|
||||
resolve({ code: HotUpdateCode.CheckFailed, message: "本地manifest加载失败" });
|
||||
return;
|
||||
}
|
||||
|
||||
this._am.setEventCallback((event) => {
|
||||
switch (event.getEventCode()) {
|
||||
case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST:
|
||||
resolve({ code: HotUpdateCode.CheckFailed, message: "本地manifest不存在" });
|
||||
break;
|
||||
case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST:
|
||||
resolve({ code: HotUpdateCode.NetworkError, message: "下载manifest失败" });
|
||||
break;
|
||||
case jsb.EventAssetsManager.ALREADY_UP_TO_DATE:
|
||||
resolve({ code: HotUpdateCode.LatestVersion, message: "已是最新版本" });
|
||||
break;
|
||||
case jsb.EventAssetsManager.NEW_VERSION_FOUND:
|
||||
resolve({ code: HotUpdateCode.Succeed, message: "发现新版本" });
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
this._am.checkUpdate();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行热更新
|
||||
* @returns Promise<IPromiseResult>
|
||||
*/
|
||||
public hotUpdate(): Promise<IPromiseResult> {
|
||||
return new Promise((resolve) => {
|
||||
if (this._updating) {
|
||||
resolve({ code: HotUpdateCode.UnknownError, message: "正在更新中" });
|
||||
return;
|
||||
}
|
||||
|
||||
this._updating = true;
|
||||
|
||||
this._am.setEventCallback((event) => {
|
||||
switch (event.getEventCode()) {
|
||||
case jsb.EventAssetsManager.UPDATE_FINISHED:
|
||||
this._updating = false;
|
||||
resolve({ code: HotUpdateCode.Succeed, message: "更新完成" });
|
||||
break;
|
||||
case jsb.EventAssetsManager.UPDATE_FAILED:
|
||||
this._updating = false;
|
||||
resolve({ code: HotUpdateCode.DownloadFailed, message: "更新失败" });
|
||||
break;
|
||||
case jsb.EventAssetsManager.ERROR_DECOMPRESS:
|
||||
this._updating = false;
|
||||
resolve({ code: HotUpdateCode.UnzipFailed, message: "解压失败" });
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
this._am.update();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 事件回调处理
|
||||
*/
|
||||
private onUpdateEvent(event: jsb.EventAssetsManager): void {
|
||||
const code = event.getEventCode();
|
||||
debug(`热更新事件: ${code}`);
|
||||
|
||||
switch (code) {
|
||||
case jsb.EventAssetsManager.UPDATE_PROGRESSION:
|
||||
const progress = event.getPercent();
|
||||
debug(`更新进度: ${progress}%`);
|
||||
break;
|
||||
case jsb.EventAssetsManager.ASSET_UPDATED:
|
||||
const assetId = event.getAssetId();
|
||||
debug(`资源更新: ${assetId}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证回调
|
||||
*/
|
||||
private onVerifyCallback(path: string, asset: any): boolean {
|
||||
// 资源验证逻辑
|
||||
return true;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## manifest 管理
|
||||
|
||||
### 本地 manifest 刷新
|
||||
```typescript
|
||||
/**
|
||||
* 替换 project.manifest 中的内容并刷新本地manifest
|
||||
*/
|
||||
private refreshLocalManifest(manifest: IHotUpdateConfig, versionManifest: IHotUpdateConfig): Promise<IPromiseResult> {
|
||||
return new Promise((resolve) => {
|
||||
// 版本比较
|
||||
if (Utils.compareVersion(manifest.version, versionManifest.version) >= 0) {
|
||||
resolve({ code: HotUpdateCode.LatestVersion, message: "已是最新版本" });
|
||||
return;
|
||||
}
|
||||
|
||||
// 更新 manifest 配置
|
||||
manifest.remoteManifestUrl = Utils.addUrlParam(versionManifest.remoteManifestUrl, "timeStamp", `${Time.now()}`);
|
||||
manifest.remoteVersionUrl = Utils.addUrlParam(versionManifest.remoteVersionUrl, "timeStamp", `${Time.now()}`);
|
||||
manifest.packageUrl = versionManifest.packageUrl;
|
||||
|
||||
// 计算 manifest 根目录
|
||||
let manifestRoot = "";
|
||||
let manifestUrl = HotUpdateManager.getInstance().manifestUrl;
|
||||
let found = manifestUrl.lastIndexOf("/");
|
||||
if (found === -1) {
|
||||
found = manifestUrl.lastIndexOf("\\");
|
||||
}
|
||||
if (found !== -1) {
|
||||
manifestRoot = manifestUrl.substring(0, found + 1);
|
||||
}
|
||||
|
||||
// 解析并设置本地 manifest
|
||||
this._am.getLocalManifest().parseJSONString(JSON.stringify(manifest), manifestRoot);
|
||||
|
||||
resolve({ code: HotUpdateCode.Succeed, message: "更新热更新配置成功" });
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
## 版本比较工具
|
||||
|
||||
### 版本号比较函数
|
||||
```typescript
|
||||
export class Utils {
|
||||
/**
|
||||
* 版本号比较
|
||||
* @param versionA 版本A
|
||||
* @param versionB 版本B
|
||||
* @returns 0: 相等, 1: A > B, -1: A < B
|
||||
*/
|
||||
public static compareVersion(versionA: string, versionB: string): number {
|
||||
const a = versionA.split('.');
|
||||
const b = versionB.split('.');
|
||||
|
||||
for (let i = 0; i < Math.max(a.length, b.length); i++) {
|
||||
const numA = parseInt(a[i] || '0', 10);
|
||||
const numB = parseInt(b[i] || '0', 10);
|
||||
|
||||
if (numA > numB) return 1;
|
||||
if (numA < numB) return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 给URL添加参数
|
||||
*/
|
||||
public static addUrlParam(url: string, key: string, value: string): string {
|
||||
const separator = url.indexOf('?') !== -1 ? '&' : '?';
|
||||
return `${url}${separator}${key}=${encodeURIComponent(value)}`;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 进度监控
|
||||
|
||||
### 更新进度回调
|
||||
```typescript
|
||||
export interface HotUpdateProgress {
|
||||
/** 当前进度百分比 (0-100) */
|
||||
percent: number;
|
||||
|
||||
/** 已下载字节数 */
|
||||
downloadedBytes: number;
|
||||
|
||||
/** 总字节数 */
|
||||
totalBytes: number;
|
||||
|
||||
/** 当前下载的文件 */
|
||||
currentFile?: string;
|
||||
}
|
||||
|
||||
export interface HotUpdateCallbacks {
|
||||
/** 进度回调 */
|
||||
onProgress?: (progress: HotUpdateProgress) => void;
|
||||
|
||||
/** 完成回调 */
|
||||
onComplete?: (result: IPromiseResult) => void;
|
||||
|
||||
/** 错误回调 */
|
||||
onError?: (error: IPromiseResult) => void;
|
||||
}
|
||||
|
||||
export class HotUpdate {
|
||||
public updateWithCallbacks(callbacks: HotUpdateCallbacks): void {
|
||||
this._am.setEventCallback((event) => {
|
||||
switch (event.getEventCode()) {
|
||||
case jsb.EventAssetsManager.UPDATE_PROGRESSION:
|
||||
callbacks.onProgress?.({
|
||||
percent: event.getPercent(),
|
||||
downloadedBytes: event.getDownloadedBytes(),
|
||||
totalBytes: event.getTotalBytes()
|
||||
});
|
||||
break;
|
||||
case jsb.EventAssetsManager.UPDATE_FINISHED:
|
||||
callbacks.onComplete?.({ code: HotUpdateCode.Succeed, message: "更新完成" });
|
||||
break;
|
||||
case jsb.EventAssetsManager.UPDATE_FAILED:
|
||||
callbacks.onError?.({ code: HotUpdateCode.DownloadFailed, message: "更新失败" });
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
this._am.update();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 错误处理和重试
|
||||
|
||||
### 重试机制
|
||||
```typescript
|
||||
export class HotUpdate {
|
||||
private _retryCount: number = 0;
|
||||
private readonly _maxRetryCount: number = 3;
|
||||
|
||||
/**
|
||||
* 带重试的热更新
|
||||
*/
|
||||
public async hotUpdateWithRetry(): Promise<IPromiseResult> {
|
||||
for (let i = 0; i < this._maxRetryCount; i++) {
|
||||
try {
|
||||
const result = await this.hotUpdate();
|
||||
|
||||
if (result.code === HotUpdateCode.Succeed) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// 网络错误可以重试
|
||||
if (result.code === HotUpdateCode.NetworkError && i < this._maxRetryCount - 1) {
|
||||
warn(`热更新失败,准备重试 (${i + 1}/${this._maxRetryCount})`);
|
||||
await this.delay(1000 * (i + 1)); // 递增延迟
|
||||
continue;
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
error('热更新异常', error);
|
||||
if (i === this._maxRetryCount - 1) {
|
||||
return { code: HotUpdateCode.UnknownError, message: `重试${this._maxRetryCount}次后仍然失败` };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private delay(ms: number): Promise<void> {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 热更新最佳实践
|
||||
|
||||
### 1. 版本管理
|
||||
- 使用语义化版本号 (major.minor.patch)
|
||||
- 提供版本比较和检查功能
|
||||
- 记录版本更新历史
|
||||
|
||||
### 2. 网络处理
|
||||
- 实现重试机制处理网络不稳定
|
||||
- 添加超时控制
|
||||
- 支持断点续传
|
||||
|
||||
### 3. 用户体验
|
||||
- 提供详细的进度反馈
|
||||
- 支持后台下载
|
||||
- 提供更新取消选项
|
||||
|
||||
### 4. 错误恢复
|
||||
- 验证下载文件完整性
|
||||
- 支持回滚到上一版本
|
||||
- 提供修复工具清理损坏文件
|
||||
|
||||
### 5. 安全性
|
||||
- 验证manifest签名
|
||||
- 检查文件哈希值
|
||||
- 使用HTTPS传输
|
||||
Reference in New Issue
Block a user