|
|
@@ -2974,6 +2974,7 @@ const LEGACY_MUTATIONS={
|
|
|
};
|
|
|
function requireSuperAdmin(context){if(!context.isSuperAdmin)fail(403,'只有超级管理员可以访问数据同步中心');}
|
|
|
function syncConfig(){const env=typeof process!=='undefined'&&process.env?process.env:{};return{baseUrl:text(env.XIAOSHU_LEGACY_SYNC_BASE_URL).replace(/\/$/,''),keyId:text(env.XIAOSHU_LEGACY_SYNC_KEY_ID),secret:text(env.XIAOSHU_LEGACY_SYNC_SECRET)};}
|
|
|
+function syncBridgeConfigured(){const config=syncConfig();return Boolean(config.baseUrl&&config.keyId&&config.secret);}
|
|
|
async function operationalSyncIndicator(context,input){const config=syncConfig();if(!config.baseUrl||!config.keyId||!config.secret)return{state:'not_configured',label:'尚未启用持续同步',lastSuccessAt:'',lagSeconds:null};const rows=await newClassObjects('LegacySyncCheckpoint',context,input,1000),critical=rows.filter((row)=>['users','appointments','lessons'].includes(text(row.get('dataset')))),lastSuccessAt=critical.map((row)=>dateIso(row.get('lastSuccessAt'))).filter(Boolean).sort()[0]||'',lagSeconds=lastSuccessAt?Math.max(0,Math.floor((Date.now()-new Date(lastSuccessAt).getTime())/1000)):null,healthy=critical.length===3&&critical.every((row)=>text(row.get('state'))==='healthy'||text(row.get('state'))==='catching_up')&&lagSeconds!==null&&lagSeconds<=120;return{state:healthy?'synced':'delayed',label:healthy?'核心数据刚刚同步':'数据同步延迟,正在追平',lastSuccessAt,lagSeconds};}
|
|
|
async function syncBridgeRequest(path,options={}){
|
|
|
const config=syncConfig();if(!config.baseUrl||!config.keyId||!config.secret)fail(503,'旧系统同步桥尚未配置');
|
|
|
@@ -3017,7 +3018,10 @@ async function handler(request,response) {
|
|
|
if(operation==='ops/sync/status') return response.json({success:true,data:await syncStatus(context,input)});
|
|
|
if(operation==='ops/sync/run') return response.json({success:true,data:await runSync(context,input)});
|
|
|
if(operation==='ops/sync/retry') return response.json({success:true,data:await retrySync(context,input)});
|
|
|
- if(LEGACY_MUTATIONS[operation]) return response.json({success:true,data:await bridgeFirstWrite(context,input,operation,payload)});
|
|
|
+ // Keep the current Parse implementation available until the old-server bridge is
|
|
|
+ // actually configured. Enforcing bridge-first writes before that point would turn
|
|
|
+ // a production deployment into an outage for scheduling and member operations.
|
|
|
+ if(LEGACY_MUTATIONS[operation]&&syncBridgeConfigured()) return response.json({success:true,data:await bridgeFirstWrite(context,input,operation,payload)});
|
|
|
if(operation==='ops/dashboard/summary') return response.json({success:true,data:await professionalDashboard(context,input)});
|
|
|
if(operation==='ops/dashboard/schedule-trend') return response.json({success:true,data:await scheduleTrendData(context,input)});
|
|
|
if(operation==='ops/coaches/list') return response.json({success:true,data:await professionalCoachRows(context,input)});
|