1. 新增微信云函数双向同步接口,将时间戳对比逻辑移至云端完成 2. 简化初始化流程,合并云端登录与数据同步步骤 3. 重构GameDataSync类,统一同步流程并改为异步调用 4. 替换旧的getCloudData接口为新的syncFromCloud方法
159 lines
6.0 KiB
TypeScript
159 lines
6.0 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);
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 登录阶段与云端双向同步数据(sync 模式)。
|
||
* 时间戳对比在云端完成:
|
||
* - 本地较新:云端已被本地数据覆盖,无需额外处理
|
||
* - 云端较新:用云端数据覆盖本地,并写入 localStorage
|
||
* @returns true=本次以云端数据为准覆盖了本地;false=以本地数据为准或同步失败
|
||
*/
|
||
public async syncFromCloud(): Promise<boolean> {
|
||
const localData = this.loadFromLocal();
|
||
|
||
// 非微信客户端:仅使用本地缓存
|
||
if (!this.isWxClient()) {
|
||
if (localData) {
|
||
smc.overrideLocalDataWithRemote({ data: localData });
|
||
}
|
||
return false;
|
||
}
|
||
|
||
try {
|
||
// 本地无缓存时用空对象参与对比,云端会直接下发
|
||
const gameData = localData || ({} as GameDate);
|
||
gameData.timestamp = gameData.timestamp || 0;
|
||
|
||
const result = await WxCloudApi.sync(gameData);
|
||
const response = result.result;
|
||
|
||
if (response && response.code === 200 && response.data) {
|
||
const cloudData: CloudData = {
|
||
openid: response.data.openid || '',
|
||
data: (response.data.game_data || {}) as GameDate,
|
||
};
|
||
|
||
if (response.updated) {
|
||
// 本地数据较新,云端已被覆盖
|
||
mLogger.log(this.debugMode, 'GameDataSync', `[GameDataSync]: 本地数据较新,已覆盖云端。`);
|
||
if (localData) {
|
||
smc.overrideLocalDataWithRemote({ data: localData });
|
||
}
|
||
return false;
|
||
} else {
|
||
// 云端数据较新,覆盖本地
|
||
mLogger.log(this.debugMode, 'GameDataSync', `[GameDataSync]: 云端数据较新,覆盖本地。`);
|
||
smc.overrideLocalDataWithRemote(cloudData);
|
||
this.saveToLocal();
|
||
return true;
|
||
}
|
||
}
|
||
|
||
mLogger.warn(this.debugMode, 'GameDataSync', `[GameDataSync]: 云端同步失败,使用本地缓存兜底。`, response?.msg);
|
||
if (localData) {
|
||
smc.overrideLocalDataWithRemote({ data: localData });
|
||
}
|
||
return false;
|
||
} catch (error) {
|
||
mLogger.error(this.debugMode, 'GameDataSync', `[GameDataSync]: 云端同步异常,使用本地缓存兜底。`, error);
|
||
if (localData) {
|
||
smc.overrideLocalDataWithRemote({ data: localData });
|
||
}
|
||
return false;
|
||
}
|
||
}
|
||
}
|
||
|
||
export const gameDataSync = new GameDataSync(); |