- 新增 GameDataSync 类,封装数据同步逻辑,支持防抖与时间戳冲突解决 - 重构 SingletonModuleComp 的云端同步方法,统一调用 GameDataSync - 优化 TalentsComp 天赋升级流程,使用新的同步机制 - 添加本地缓存支持,提升离线体验与数据恢复能力
143 lines
5.6 KiB
TypeScript
143 lines
5.6 KiB
TypeScript
import { sys } from "cc";
|
||
import { WxCloudApi } from "../wx_clound_client_api/WxCloudApi";
|
||
import { mLogger } from "./Logger";
|
||
import { smc, GameDate, CloudData } from "./SingletonModuleComp";
|
||
|
||
export class GameDataSync {
|
||
private debugMode: boolean = false;
|
||
private _localDataDirty: boolean = false;
|
||
private _lastSyncTime: number = 0;
|
||
private _syncTimerId: any = null;
|
||
private readonly LOCAL_STORAGE_KEY = "Heros_GameData_Local";
|
||
|
||
/** 标记数据为脏,并更新时间戳,然后保存到本地 */
|
||
public markDataDirty() {
|
||
this._localDataDirty = true;
|
||
this.saveToLocal();
|
||
// 尝试触发异步同步
|
||
this.tryAsyncCloudSync();
|
||
}
|
||
|
||
/** 同步数据到本地 localStorage */
|
||
private saveToLocal() {
|
||
try {
|
||
const data = smc.getGameDate();
|
||
data.timestamp = Date.now(); // 更新时间戳
|
||
sys.localStorage.setItem(this.LOCAL_STORAGE_KEY, JSON.stringify(data));
|
||
} catch (error) {
|
||
mLogger.error(this.debugMode, 'GameDataSync', '保存本地数据失败:', error);
|
||
}
|
||
}
|
||
|
||
/** 从本地 localStorage 读取数据 */
|
||
private loadFromLocal(): GameDate | null {
|
||
try {
|
||
const str = sys.localStorage.getItem(this.LOCAL_STORAGE_KEY);
|
||
if (str) {
|
||
return JSON.parse(str) as GameDate;
|
||
}
|
||
} catch (error) {
|
||
mLogger.error(this.debugMode, 'GameDataSync', '读取本地数据失败:', error);
|
||
}
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* 判断是否为微信客户端
|
||
*/
|
||
public isWxClient(): boolean {
|
||
return sys.platform === sys.Platform.WECHAT_GAME;
|
||
}
|
||
|
||
public updateCloudData() {
|
||
this.markDataDirty();
|
||
return true;
|
||
}
|
||
|
||
/** 尝试异步同步云端数据,带有防抖(Debounce)保护 */
|
||
private tryAsyncCloudSync() {
|
||
if (!this.isWxClient()) return;
|
||
|
||
// 如果当前有同步在等待,清除之前的定时器
|
||
if (this._syncTimerId !== null) {
|
||
clearTimeout(this._syncTimerId);
|
||
}
|
||
|
||
// 防抖:延迟 3 秒同步,期间多次操作合并为一次同步请求
|
||
this._syncTimerId = setTimeout(() => {
|
||
this._syncTimerId = null;
|
||
this.executeCloudSync();
|
||
}, 3000);
|
||
}
|
||
|
||
/** 实际执行云端同步 */
|
||
private executeCloudSync() {
|
||
if (!this._localDataDirty) return;
|
||
|
||
let gameData = smc.getGameDate();
|
||
// 保证云端存一份时间戳,供下次登录对比
|
||
gameData.timestamp = Date.now();
|
||
|
||
WxCloudApi.save(gameData).then((result) => {
|
||
if (result.result.code === 200) {
|
||
mLogger.log(this.debugMode, 'GameDataSync', "静默云端保存成功", result.result);
|
||
// 同步成功,清除脏标记
|
||
this._localDataDirty = false;
|
||
this._lastSyncTime = Date.now();
|
||
} else {
|
||
mLogger.warn(this.debugMode, 'GameDataSync', `[GameDataSync]: 静默同步失败(等待下次重试): ${result.result.msg}`);
|
||
// 失败了不清除脏标记,下次有变化或定时器检查时会再次重试
|
||
}
|
||
}).catch((error) => {
|
||
mLogger.error(this.debugMode, 'GameDataSync', `[GameDataSync]: 静默同步异常(等待下次重试):`, error);
|
||
});
|
||
}
|
||
|
||
public getCloudData() {
|
||
const localData = this.loadFromLocal();
|
||
|
||
// 未登录微信云端前,先用本地数据顶上(让玩家秒进游戏)
|
||
if (localData && !this.isWxClient()) {
|
||
smc.overrideLocalDataWithRemote({ data: localData });
|
||
return;
|
||
}
|
||
|
||
WxCloudApi.get().then(async (result) => {
|
||
if(result.result.code === 200) {
|
||
let cloudData = result.result.data as CloudData;
|
||
mLogger.log(this.debugMode, 'GameDataSync', `[GameDataSync]: 获取游戏云端数据成功:`, cloudData);
|
||
|
||
// 冲突解决:基于时间戳比较(云端时间戳 > 本地时间戳 则覆盖)
|
||
let cloudTs = cloudData?.data?.timestamp || 0;
|
||
let localTs = localData?.timestamp || 0;
|
||
|
||
if (!localData || cloudTs > localTs) {
|
||
mLogger.log(this.debugMode, 'GameDataSync', `[GameDataSync]: 云端数据更新 (Cloud: ${cloudTs} > Local: ${localTs}), 执行覆盖。`);
|
||
smc.overrideLocalDataWithRemote(cloudData);
|
||
this.saveToLocal(); // 同步到本地
|
||
} else {
|
||
mLogger.log(this.debugMode, 'GameDataSync', `[GameDataSync]: 本地数据更新 (Local: ${localTs} >= Cloud: ${cloudTs}), 使用本地数据,触发强制云同步。`);
|
||
smc.overrideLocalDataWithRemote({ data: localData });
|
||
// 本地数据较新,需要强制推送到云端以保证多端一致
|
||
this._localDataDirty = true;
|
||
this.executeCloudSync();
|
||
}
|
||
|
||
return true
|
||
} else {
|
||
mLogger.warn(this.debugMode, 'GameDataSync', `[GameDataSync]: 获取游戏云端数据失败,使用本地缓存兜底。`);
|
||
if (localData) {
|
||
smc.overrideLocalDataWithRemote({ data: localData });
|
||
}
|
||
return false
|
||
}
|
||
}).catch((error) => {
|
||
mLogger.error(this.debugMode, 'GameDataSync', `[GameDataSync]: 获取游戏云端数据异常:`, error);
|
||
if (localData) {
|
||
smc.overrideLocalDataWithRemote({ data: localData });
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
export const gameDataSync = new GameDataSync(); |