|
@@ -16,7 +16,7 @@ export async function processLessonExport(call){
|
|
|
const book=XLSX.utils.book_new();XLSX.utils.book_append_sheet(book,sheet,'训练记录');
|
|
const book=XLSX.utils.book_new();XLSX.utils.book_append_sheet(book,sheet,'训练记录');
|
|
|
const base64=XLSX.write(book,{type:'base64',bookType:'xlsx',compression:true});
|
|
const base64=XLSX.write(book,{type:'base64',bookType:'xlsx',compression:true});
|
|
|
await call('finish',{...identity,rowCount:offset,base64});return true;
|
|
await call('finish',{...identity,rowCount:offset,base64});return true;
|
|
|
- }catch(e){await call(e?.transient?'release':'fail',identity).catch(()=>{});throw Object.assign(Error('export_job_failed'),{transient:e?.transient===true});}
|
|
|
|
|
|
|
+ }catch(e){await call(e?.transient?'release':'fail',identity).catch(()=>{});throw Object.assign(Error('export_job_failed'),{transient:e?.transient===true,reason:/^export_(?:transport|gateway_\d{3})$/.test(e?.message||'')?e.message:'export_processing'});}
|
|
|
}
|
|
}
|
|
|
async function main(){
|
|
async function main(){
|
|
|
try{setPriority(0,10);}catch{} // Background XLSX compression yields CPU to interactive requests.
|
|
try{setPriority(0,10);}catch{} // Background XLSX compression yields CPU to interactive requests.
|
|
@@ -24,8 +24,16 @@ async function main(){
|
|
|
for(const line of readFileSync(config,'utf8').split('\n')){const m=line.match(/^(?:export\s+)?(XIAOSHU_[A-Z0-9_]+)=(.*)$/);if(m&&!env[m[1]])env[m[1]]=m[2].replace(/^['"]|['"]$/g,'');}
|
|
for(const line of readFileSync(config,'utf8').split('\n')){const m=line.match(/^(?:export\s+)?(XIAOSHU_[A-Z0-9_]+)=(.*)$/);if(m&&!env[m[1]])env[m[1]]=m[2].replace(/^['"]|['"]$/g,'');}
|
|
|
const token=env.XIAOSHU_EXPORT_OPERATOR_TOKEN||env.XIAOSHU_SYNC_OPERATOR_TOKEN;if(!token)throw Error('export_operator_missing');
|
|
const token=env.XIAOSHU_EXPORT_OPERATOR_TOKEN||env.XIAOSHU_SYNC_OPERATOR_TOKEN;if(!token)throw Error('export_operator_missing');
|
|
|
const base=env.XIAOSHU_EXPORT_GATEWAY_URL||'https://server.xiaoshu.pro/api/functions/xiaoshu/ops/gateway-v3';
|
|
const base=env.XIAOSHU_EXPORT_GATEWAY_URL||'https://server.xiaoshu.pro/api/functions/xiaoshu/ops/gateway-v3';
|
|
|
- const call=async(action,params)=>{let r;try{r=await fetch(base,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({token,params:{operation:'ops/lessons/export/'+action,...params}}),signal:AbortSignal.timeout(action==='snapshot'?90_000:20_000)});}catch{throw Object.assign(Error('export_transport'),{transient:true});}let data;try{data=await r.json();}catch{throw Object.assign(Error('export_gateway_'+r.status),{transient:true});}if(!r.ok||!data.success)throw Object.assign(Error('export_gateway_'+r.status),{transient:[429,500,502,503,504].includes(r.status)});return data.data;};
|
|
|
|
|
|
|
+ const call=async(action,params)=>{
|
|
|
|
|
+ const started=Date.now();let status=0;
|
|
|
|
|
+ try{
|
|
|
|
|
+ let r;try{r=await fetch(base,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({token,params:{operation:'ops/lessons/export/'+action,...params}}),signal:AbortSignal.timeout(['snapshot','finish'].includes(action)?90_000:20_000)});}catch{throw Object.assign(Error('export_transport'),{transient:true});}
|
|
|
|
|
+ status=r.status;let data;try{data=await r.json();}catch{throw Object.assign(Error('export_gateway_'+r.status),{transient:r.ok||r.status===429||r.status>=500});}
|
|
|
|
|
+ if(!r.ok||!data.success)throw Object.assign(Error('export_gateway_'+r.status),{transient:[408,429,500,502,503,504].includes(r.status)});return data.data;
|
|
|
|
|
+ }finally{const durationMs=Date.now()-started;if(durationMs>=1000||status>=400||!status)console.info(JSON.stringify({event:'lesson_export_rpc',operation:action,status,durationMs}));}
|
|
|
|
|
+ };
|
|
|
let lastMaintenance=0;let stopped=false;for(const event of ['SIGINT','SIGTERM'])process.on(event,()=>{stopped=true;});
|
|
let lastMaintenance=0;let stopped=false;for(const event of ['SIGINT','SIGTERM'])process.on(event,()=>{stopped=true;});
|
|
|
- do{try{if(Date.now()-lastMaintenance>86_400_000){await call('maintenance',{});lastMaintenance=Date.now();}await processLessonExport(call);}catch{console.error('导出任务失败,错误状态已回写或将由租约恢复');}if(process.argv.includes('--once'))break;if(!stopped)await new Promise(r=>setTimeout(r,5000));}while(!stopped);
|
|
|
|
|
|
|
+ do{try{if(Date.now()-lastMaintenance>86_400_000){await call('maintenance',{});lastMaintenance=Date.now();}await processLessonExport(call);}catch(error){console.error('导出任务失败,错误状态已回写或将由租约恢复:'+(error?.reason||'export_service'));}if(process.argv.includes('--once'))break;if(!stopped)await new Promise(r=>setTimeout(r,5000));}while(!stopped);
|
|
|
}
|
|
}
|
|
|
-if(process.argv[1]&&import.meta.url===pathToFileURL(process.argv[1]).href)main().catch(()=>{console.error('导出 worker 启动失败,请检查私密配置和权限');process.exitCode=1;});
|
|
|
|
|
|
|
+export function isLessonExportEntrypoint(url,argv=process.argv,env=process.env){return[argv[1],env.pm_exec_path].some(entry=>Boolean(entry&&url===pathToFileURL(entry).href));}
|
|
|
|
|
+if(isLessonExportEntrypoint(import.meta.url))main().catch(()=>{console.error('导出 worker 启动失败,请检查私密配置和权限');process.exitCode=1;});
|