| 123456789101112131415161718192021222324252627282930313233343536373839 |
- #!/usr/bin/env node
- // Read-only: never creates a user/session/helper and never changes a lesson.
- const base=(process.env.XIAOSHU_SERVER_URL||'https://server.xiaoshu.pro').replace(/\/$/,''),companyId=process.env.XIAOSHU_COMPANY_ID||'7pIbDBJmKx';
- const appId=process.env.XIAOSHU_PARSE_APP_ID||'7pIbDBJmKx_main',master=process.env.XIAOSHU_MASTER_KEY;
- const date=process.env.XIAOSHU_AUDIT_DATE||'2026-10-11',uid=Number(process.env.XIAOSHU_AUDIT_TEACHER_ID||0);
- const company={__type:'Pointer',className:'Company',objectId:companyId};
- if(!master)throw Error('请在运行环境配置 XIAOSHU_MASTER_KEY;本脚本只读');
- if(!/^\d{4}-\d{2}-\d{2}$/.test(date))throw Error('日期格式应为 YYYY-MM-DD');
- async function get(path){
- const response=await fetch(base+'/parse'+path,{headers:{'X-Parse-Application-Id':appId,'X-Parse-Master-Key':master},signal:AbortSignal.timeout(20000)});
- const result=await response.json();if(!response.ok||result.error)throw Error(result.error||'HTTP '+response.status);return result;
- }
- async function rows(table,where,keys){
- const result=[];let skip=0;
- while(true){const query=new URLSearchParams({where:JSON.stringify(where),keys,order:'objectId',limit:'500',skip:String(skip)});const page=(await get('/classes/'+table+'?'+query)).results||[];result.push(...page);if(page.length<500)break;skip+=500;}
- return result;
- }
- const teachers=await rows('_User',{company,legacyGroupId:3,...(uid?{legacyUserId:uid}:{$or:[{realName:{$regex:'钱'}},{nickname:{$regex:'钱'}}]})},'objectId,legacyUserId,legacyGroupId,realName,nickname,isDisabled');
- if(!uid){console.log(JSON.stringify({requiresTeacherId:true,message:'候选仅用于人工识别,请指定 XIAOSHU_AUDIT_TEACHER_ID 后重跑;归属使用编号校验',teachers},null,2));process.exit(0);}
- if(teachers.length!==1)throw Error('教师编号在目标帐套未唯一匹配');
- const all=await rows('CourseAppointment',{company,pl:String(uid)},'id,pl,szyh,kcid,bindingId,bxrq,yysj,sdsd,dszt,sourceKey,teachingManaged,createdAt,updatedAt');
- const day=value=>String(value?.iso||value||'').replace(/-/g,'').slice(0,8);
- const selected=all.filter(a=>day(a.bxrq||a.yysj)===date.replace(/-/g,'')||new Date(a.createdAt).toLocaleDateString('sv-SE',{timeZone:'Asia/Shanghai'})===date);
- const findings=[];
- for(const appointment of selected){
- const common=await rows('CommonModel',{company,modelId:54,itemId:appointment.id},'objectId,generalId,itemId,status,updatedAt');
- const binding=appointment.bindingId?await rows('CourseBinding',{company,objectId:appointment.bindingId},'objectId,yhid,kcid,categoryKey,enrollmentStatus'):[];
- findings.push({appointment,common,binding,identityMatches:binding.length===1&&String(binding[0].yhid)===String(appointment.szyh)&&String(binding[0].kcid)===String(appointment.kcid)});
- }
- let gateway=null;
- // An explicitly supplied teacher session permits exact list/detail verification; no impersonation.
- if(process.env.XIAOSHU_AUDIT_TEACHER_SESSION){
- async function call(action,params={}){const response=await fetch(base+'/api/functions/xiaoshu/app/gateway',{method:'POST',headers:{'Content-Type':'application/json','X-Parse-Application-Id':appId},body:JSON.stringify({token:process.env.XIAOSHU_AUDIT_TEACHER_SESSION,params:{action,...params}}),signal:AbortSignal.timeout(20000)});const data=await response.json();return{httpStatus:response.status,retcode:data.retcode,retmsg:data.retmsg,result:data.result};}
- const pages=[];let index=1;
- do{const page=await call('app_companion_overview',{uid,source:'database',mode:'coach',dateStart:date,dateEnd:date,page:index,pageSize:100});pages.push(page);if(page.httpStatus!==200||index>=Number(page.result?.page?.pageCount||1))break;index++;}while(true);
- const details=[];for(const item of findings)for(const common of item.common){const r=await call('e_order_detail',{id:common.generalId});details.push({generalId:common.generalId,httpStatus:r.httpStatus,retcode:r.retcode,retmsg:r.retmsg,rows:Array.isArray(r.result)?r.result.map(a=>({GeneralID:a.GeneralID,pl:a.pl,szyh:a.szyh,bindingId:a.bindingId,dszt:a.dszt,allowedActions:a.allowedActions,blockedReason:a.blockedReason})):[]});}
- gateway={pages:pages.map(p=>({httpStatus:p.httpStatus,retcode:p.retcode,page:p.result?.page,scheduleIds:p.result?.schedules?.map(a=>a.GeneralID)})),details};
- }
- console.log(JSON.stringify({readOnly:true,companyId,date,teacher:teachers[0],findings,gateway,limitations:gateway?[]:['未提供老师会话,尚未验证老师账号实际列表与详情响应']},null,2));
|