@wq-hook/anti-cheating-monitor 1.0.2 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +2 -0
- package/dist/index.es.js +2 -2
- package/dist/index.es.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
package/dist/index.es.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{useState as t,useRef as e,useEffect as i,useCallback as o}from"react";const n={checkInterval:3e3,detectMultiplePeople:!0,detectDeviceUsage:!0,detectAbsence:!0,resolution:{width:640,height:480},type:1,earphoneThreshold:.6,cellphoneThreshold:.6,headPoseThreshold:{pitch:30,roll:30,yaw:30},faceCompletenessThreshold:.8,centeringThreshold:.7,movementThreshold:.3};var
|
|
2
|
-
return(new Date).toISOString().replace(/\.\d{3}/,"")}generateNonce(){return Math.random().toString(36).substring(2,15)+Math.random().toString(36).substring(2,15)}processAPIResponse(t){const
|
|
1
|
+
import{useState as t,useRef as e,useEffect as i,useCallback as o}from"react";const n={checkInterval:3e3,detectMultiplePeople:!0,detectDeviceUsage:!0,detectAbsence:!0,resolution:{width:640,height:480},type:1,earphoneThreshold:.6,cellphoneThreshold:.6,headPoseThreshold:{pitch:30,roll:30,yaw:30},faceCompletenessThreshold:.8,centeringThreshold:.7,movementThreshold:.3};var r=/* @__PURE__ */(t=>(t.PERSON_COUNT_ANOMALY="PERSON_COUNT_ANOMALY",t.EARPHONE_DETECTED="EARPHONE_DETECTED",t.CELLPHONE_DETECTED="CELLPHONE_DETECTED",t.HEAD_POSTURE_ABNORMAL="HEAD_POSTURE_ABNORMAL",t.FACE_MISSING="FACE_MISSING",t.FACE_OCCLUDED="FACE_OCCLUDED",t.NO_FACE_DETECTED="NO_FACE_DETECTED",t.MULTIPLE_FACES_DETECTED="MULTIPLE_FACES_DETECTED",t.NOT_CENTERED="NOT_CENTERED",t.SUSPICIOUS_MOVEMENT="SUSPICIOUS_MOVEMENT",t))(r||{});const s={PERSON_COUNT_ANOMALY:"人数异常",EARPHONE_DETECTED:"检测到耳机",CELLPHONE_DETECTED:"检测到手机",HEAD_POSTURE_ABNORMAL:"头部姿态异常",FACE_MISSING:"人脸缺失",FACE_OCCLUDED:"人脸被遮挡",NO_FACE_DETECTED:"未检测到人脸",MULTIPLE_FACES_DETECTED:"检测到多张人脸",NOT_CENTERED:"未居中",SUSPICIOUS_MOVEMENT:"可疑移动"};var a=/* @__PURE__ */(t=>(t.LOW="low",t.MEDIUM="medium",t.HIGH="high",t.CRITICAL="critical",t))(a||{});const c={PERSON_COUNT_ANOMALY:"high",EARPHONE_DETECTED:"medium",CELLPHONE_DETECTED:"high",HEAD_POSTURE_ABNORMAL:"medium",FACE_MISSING:"high",FACE_OCCLUDED:"medium",NO_FACE_DETECTED:"high",MULTIPLE_FACES_DETECTED:"high",NOT_CENTERED:"low",SUSPICIOUS_MOVEMENT:"medium"};class ConfigManager{constructor(){this.auditLogs=[],this.listeners=/* @__PURE__ */new Set,this.STORAGE_KEY="anti-cheating-config",this.config=this.getDefaultConfig(),this.loadFromStorage(),this.startHotUpdate()}static getInstance(){return ConfigManager.instance||(ConfigManager.instance=new ConfigManager),ConfigManager.instance}getDefaultConfig(){return{...n}}loadFromStorage(){try{const t=localStorage.getItem(this.STORAGE_KEY);if(t){const e=JSON.parse(t);this.config={...this.config,...e}}}catch(t){}}saveToStorage(){try{localStorage.setItem(this.STORAGE_KEY,JSON.stringify(this.config))}catch(t){}}startHotUpdate(){window.addEventListener("storage",t=>{if(t.key===this.STORAGE_KEY&&t.newValue)try{const e=JSON.parse(t.newValue);this.updateConfig(e,{source:"remote"})}catch(e){}})}addAuditLog(t,e,i,o,n="local",r){const s={id:`${Date.now()}-${Math.random().toString(36).substr(2,9)}`,timestamp:Date.now(),operation:t,configKey:e,oldValue:i,newValue:o,operator:r,source:n};this.auditLogs.push(s),this.auditLogs.length>1e3&&(this.auditLogs=this.auditLogs.slice(-500))}validateConfig(t){const e=[];return void 0!==t.checkInterval&&(t.checkInterval<1e3||t.checkInterval>6e4)&&e.push("检测间隔应在1000-60000毫秒之间"),t.resolution&&((t.resolution.width<320||t.resolution.width>1920)&&e.push("视频宽度应在320-1920之间"),(t.resolution.height<240||t.resolution.height>1080)&&e.push("视频高度应在240-1080之间")),void 0!==t.earphoneThreshold&&(t.earphoneThreshold<0||t.earphoneThreshold>1)&&e.push("耳机检测阈值应在0-1之间"),void 0!==t.cellphoneThreshold&&(t.cellphoneThreshold<0||t.cellphoneThreshold>1)&&e.push("手机检测阈值应在0-1之间"),e}getConfig(){return{...this.config}}updateConfig(t,e={}){const{force:i=!1,source:o="local",operator:n}=e;if(!i){const e=this.validateConfig(t);if(e.length>0)return{success:!1,errors:e}}return Object.entries(t).forEach(([t,e])=>{const i=this.config[t];JSON.stringify(i)!==JSON.stringify(e)&&this.addAuditLog("update",t,i,e,o,n)}),this.config={...this.config,...t},"local"===o&&this.saveToStorage(),this.notifyListeners(),{success:!0}}resetConfig(t="local",e){const i={...this.config};this.config=this.getDefaultConfig(),Object.keys(i).forEach(o=>{this.addAuditLog("update",o,i[o],this.config[o],t,e)}),this.saveToStorage(),this.notifyListeners()}addListener(t){return this.listeners.add(t),()=>{this.listeners.delete(t)}}notifyListeners(){this.listeners.forEach(t=>{try{t(this.getConfig())}catch(e){}})}getAuditLogs(){return[...this.auditLogs]}clearAuditLogs(t){this.auditLogs=t?this.auditLogs.filter(e=>e.timestamp>t):[]}destroy(){this.syncTimer&&clearInterval(this.syncTimer),this.listeners.clear()}}class OSSClient{constructor(t,e){this.isInitialized=!1,this.client=null,this.config=t,this.credentials=e}async initialize(){if(this.isInitialized)return Promise.resolve();if(!this.config.bucket||!this.config.region)throw new Error("OSS配置不完整:缺少bucket或region");if(!this.credentials.accessKeyId||!this.credentials.accessKeySecret)throw new Error("OSS凭证不完整:缺少accessKeyId或accessKeySecret");try{const t=await import("./aliyun-oss-sdk.min-y6IHPbHR.js").then(t=>t.a),e=t.default||t;this.client=new e({region:this.config.region,accessKeyId:this.credentials.accessKeyId,accessKeySecret:this.credentials.accessKeySecret,stsToken:this.credentials.securityToken,bucket:this.config.bucket}),this.isInitialized=!0}catch(t){throw new Error("无法加载 OSS 客户端库")}}async put(t,e="monitor/"){if(this.isInitialized||await this.initialize(),!this.client)throw new Error("OSS客户端未初始化");const i=`${Date.now()}-${Math.random().toString(36).slice(2)}.jpg`,o=e+i;try{const e=await this.client.put(o,t,{headers:{"Cache-Control":"no-cache","Content-Disposition":`inline; filename="${i}"`}});return{url:e.url||`https://${this.config.bucket}.${this.config.region}.aliyuncs.com/${o}`}}catch(n){throw new Error(`阿里云 OSS 上传失败: ${n instanceof Error?n.message:"未知错误"}`)}}get ready(){return this.isInitialized}updateConfig(t){this.config={...this.config,...t},this.isInitialized=!1}updateCredentials(t){this.credentials={...this.credentials,...t},this.isInitialized=!1}}class DetectionEngine{constructor(t,e,i){this.config=t,this.credentials=e,this.ossConfig=i,this.ossClient=new OSSClient(i,e)}updateConfig(t){this.config=t}updateCredentials(t){this.credentials=t,this.ossClient.updateCredentials(t)}updateOSSConfig(t){this.ossConfig=t,this.ossClient.updateConfig(t)}async detect(t){const e=Date.now(),i=t instanceof Blob?t:this.base64ToBlob(t);try{let t;t=(await this.ossClient.put(i)).url;const o=await this.callVIAPI(t),n=this.processAPIResponse(o,i);return n.processingTime=Date.now()-e,n}catch(o){return{timestamp:Date.now(),faceCount:0,faceCompleteness:0,personCount:0,violations:[{type:r.NO_FACE_DETECTED,level:a.HIGH,confidence:1,description:`检测失败: ${o instanceof Error?o.message:"未知错误"}`}],imageData:i,processingTime:Date.now()-e}}}base64ToBlob(t){const e=t.split(","),i=e[0].match(/:(.*?);/)?.[1]||"image/jpeg",o=atob(e[1]),n=new ArrayBuffer(o.length),r=new Uint8Array(n);for(let s=0;s<o.length;s++)r[s]=o.charCodeAt(s);return new Blob([n],{type:i})}async callVIAPI(t){const e={Action:"MonitorExamination",Version:"2019-12-30",Format:"JSON",AccessKeyId:this.credentials.accessKeyId,SignatureMethod:"HMAC-SHA1",Timestamp:this.getTimestamp(),SignatureVersion:"1.0",SignatureNonce:this.generateNonce(),RegionId:"cn-shanghai",Type:String(this.config.type),ImageURL:t};this.credentials.securityToken&&(e.SecurityToken=this.credentials.securityToken);const i=Object.keys(e).sort().map(t=>`${this.percentEncode(t)}=${this.percentEncode(e[t])}`).join("&"),o=`POST&${this.percentEncode("/")}&${this.percentEncode(i)}`,n=await this.hmacSha1(`${this.credentials.accessKeySecret}&`,o),r=`${i}&Signature=${this.percentEncode(n)}`,s=await fetch("https://facebody.cn-shanghai.aliyuncs.com",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:r,mode:"cors"});if(!s.ok)throw new Error(`API Error: ${s.status} ${s.statusText}`);return await s.json()}async hmacSha1(t,e){const i=new TextEncoder,o=i.encode(t),n=i.encode(e),r=await window.crypto.subtle.importKey("raw",o,{name:"HMAC",hash:"SHA-1"},!1,["sign"]),s=await window.crypto.subtle.sign("HMAC",r,n);return btoa(String.fromCharCode(...Array.from(new Uint8Array(s))))}percentEncode(t){return encodeURIComponent(t).replace(/!/g,"%21").replace(/'/g,"%27").replace(/\(/g,"%28").replace(/\)/g,"%29").replace(/\*/g,"%2A")}getTimestamp(){/* @__PURE__ */
|
|
2
|
+
return(new Date).toISOString().replace(/\.\d{3}/,"")}generateNonce(){return Math.random().toString(36).substring(2,15)+Math.random().toString(36).substring(2,15)}processAPIResponse(t,e){const i=[],o=t.Data?.FaceInfo||{},n=t.Data?.PersonInfo||{},s=o.FaceNumber||0,c=n.PersonNumber||0,h=o.Completeness||0,l=o.Pose||{};if(0===s?i.push(this.createViolation(r.NO_FACE_DETECTED,a.HIGH,1,"未检测到人脸")):s>1&&i.push(this.createViolation(r.MULTIPLE_FACES_DETECTED,a.HIGH,1,`检测到${s}张人脸`)),this.config.detectMultiplePeople&&c>1&&i.push(this.createViolation(r.PERSON_COUNT_ANOMALY,a.HIGH,1,`检测到${c}人`)),h<this.config.faceCompletenessThreshold&&i.push(this.createViolation(r.FACE_OCCLUDED,a.MEDIUM,1-h,`人脸完整度${(100*h).toFixed(0)}%`)),l){const{Pitch:t,Roll:e,Yaw:o}=l,{pitch:n,roll:s,yaw:c}=this.config.headPoseThreshold;(Math.abs(t)>n||Math.abs(e)>s||Math.abs(o)>c)&&i.push(this.createViolation(r.HEAD_POSTURE_ABNORMAL,a.MEDIUM,Math.max(Math.abs(t)/n,Math.abs(e)/s,Math.abs(o)/c)-1,`头部姿态异常: ${t>0?"抬头":"低头"}${t.toFixed(1)}° 偏头${e.toFixed(1)}° ${o>0?"左转":"右转"}${o.toFixed(1)}°`))}if(this.config.detectDeviceUsage){const t=n.EarPhone?.Score||0;t>this.config.earphoneThreshold&&i.push(this.createViolation(r.EARPHONE_DETECTED,a.MEDIUM,t,`检测到耳机 (置信度: ${(100*t).toFixed(0)}%)`));const e=n.CellPhone?.Score||0;e>this.config.cellphoneThreshold&&i.push(this.createViolation(r.CELLPHONE_DETECTED,a.HIGH,e,`检测到手机 (置信度: ${(100*e).toFixed(0)}%)`))}if(l){const t=this.calculateCenteringScore(l);t<this.config.centeringThreshold&&i.push(this.createViolation(r.NOT_CENTERED,a.LOW,1-t,`未居中 (居中度: ${(100*t).toFixed(0)}%)`))}return{timestamp:Date.now(),faceCount:s,faceCompleteness:h,personCount:c,pose:l,violations:i,rawResponse:t,imageData:e}}createViolation(t,e,i,o,n){return{type:t,level:e,confidence:Math.max(0,Math.min(1,i)),description:o,data:n}}calculateCenteringScore(t){const{Pitch:e,Roll:i,Yaw:o}=t;return(Math.max(0,1-Math.abs(e)/30)+Math.max(0,1-Math.abs(i)/45)+Math.max(0,1-Math.abs(o)/60))/3}getDetectionStats(){return{totalDetections:0,averageProcessingTime:0,violationRate:0,mostCommonViolations:[]}}}class AuditLogger{constructor(){this.logs=[],this.MAX_LOGS=1e3}addLog(t){const e={...t,id:this.generateLogId(),timestamp:Date.now()};return this.logs.unshift(e),this.logs.length>this.MAX_LOGS&&(this.logs=this.logs.slice(0,this.MAX_LOGS)),this.persistLogs(),e}generateLogId(){return`${Date.now()}-${Math.random().toString(36).substr(2,9)}`}queryLogs(t={}){let e=[...this.logs];void 0!==t.startTime&&(e=e.filter(e=>e.timestamp>=t.startTime)),void 0!==t.endTime&&(e=e.filter(e=>e.timestamp<=t.endTime)),t.operation&&(e=e.filter(e=>e.operation===t.operation)),t.configKey&&(e=e.filter(e=>e.configKey===t.configKey)),t.operator&&(e=e.filter(e=>e.operator===t.operator)),t.source&&(e=e.filter(e=>e.source===t.source));const i=t.offset||0,o=t.limit||50;return e=e.slice(i,i+o),e}getAllLogs(){return[...this.logs]}getStats(){const t={create:0,update:0,delete:0,sync:0},e={local:0,remote:0,backend_sync:0},i={},o={};this.logs.forEach(n=>{t[n.operation]++,e[n.source]++,i[n.configKey]=(i[n.configKey]||0)+1,n.operator&&(o[n.operator]=(o[n.operator]||0)+1)});const n=Object.entries(i).sort(([,t],[,e])=>e-t).slice(0,5).map(([t,e])=>({configKey:t,count:e})),r=Object.entries(o).sort(([,t],[,e])=>e-t).slice(0,5).map(([t,e])=>({operator:t,count:e}));return{totalLogs:this.logs.length,operationStats:t,sourceStats:e,mostActiveConfigs:n,mostActiveOperators:r}}cleanup(t){const e=this.logs.length;this.logs=this.logs.filter(e=>e.timestamp>t);const i=e-this.logs.length;return i>0&&this.persistLogs(),i}clear(){this.logs=[],this.persistLogs()}exportLogs(t="json"){if("json"===t)return JSON.stringify(this.logs,null,2);if("csv"===t)return this.convertToCSV(this.logs);throw new Error(`不支持的导出格式: ${t}`)}importLogs(t,e="json"){let i;if("json"!==e)throw new Error(`不支持的导入格式: ${e}`);try{i=JSON.parse(t)}catch(s){throw new Error("JSON格式错误")}const o=i.filter(t=>this.validateLog(t)),n=new Set(this.logs.map(t=>t.id)),r=o.filter(t=>!n.has(t.id));return this.logs=[...r,...this.logs],this.logs.length>this.MAX_LOGS&&(this.logs=this.logs.slice(0,this.MAX_LOGS)),this.persistLogs(),r.length}validateLog(t){return"object"==typeof t&&"string"==typeof t.id&&"number"==typeof t.timestamp&&["create","update","delete","sync"].includes(t.operation)&&"string"==typeof t.configKey&&["local","remote","backend_sync"].includes(t.source)}convertToCSV(t){const e=t.map(t=>[t.id,new Date(t.timestamp).toISOString(),t.operation,t.configKey,JSON.stringify(t.oldValue||""),JSON.stringify(t.newValue||""),t.operator||"",t.source]);return[["ID","Timestamp","Operation","Config Key","Old Value","New Value","Operator","Source"].join(","),...e.map(t=>t.map(t=>`"${t}"`).join(","))].join("\n")}persistLogs(){try{localStorage.setItem("anti-cheating-audit-logs",JSON.stringify(this.logs))}catch(t){}}loadFromStorage(){try{const t=localStorage.getItem("anti-cheating-audit-logs");if(t){const e=JSON.parse(t);Array.isArray(e)&&(this.logs=e.filter(t=>this.validateLog(t)))}}catch(t){this.logs=[]}}getRecentLogs(t=10){return this.logs.slice(0,t)}getLogsByConfigKey(t){return this.logs.filter(e=>e.configKey===t)}getLogsByOperator(t){return this.logs.filter(e=>e.operator===t)}}function useAntiCheatingMonitor(s){const{credentials:a,ossConfig:c,config:h,onViolation:l,onDetectionResult:g,onError:u,onConfigChange:d}=s,[E,f]=t(!1),[p,C]=t(null),[m,T]=t({checkCount:0,abnormalCount:0,latency:0,networkQuality:"excellent",fps:0,avgProcessingTime:0,violationStats:Object.fromEntries(Object.values(r).map(t=>[t,0]))}),S=e(null),y=e(null),w=e(null),O=e(null),D=e(null),A=e(null),M=e(0),L=e(0);D.current||(D.current=ConfigManager.getInstance(),h&&D.current.updateConfig(h,{source:"local",operator:"hook-initialization"})),i(()=>{if(!D.current)return()=>{};const t=D.current.addListener(t=>{A.current&&A.current.updateConfig(t),d?.(t)});return()=>{t()}},[h,d]),i(()=>{const t=D.current?.getConfig();t&&a&&c&&(A.current=new DetectionEngine(t,a,c))},[a,c]);const I=o(async()=>{try{const t=D.current?.getConfig(),e=await navigator.mediaDevices.getUserMedia({video:{width:t?.resolution.width||640,height:t?.resolution.height||480,facingMode:"user"},audio:!1});return w.current=e,S.current&&(S.current.srcObject=e,S.current.play().catch(t=>{})),e}catch(t){throw new Error(`获取摄像头权限失败: ${t instanceof Error?t.message:"未知错误"}`)}},[]),_=o(async()=>{const t=S.current,e=y.current;if(!t||!e)return null;if(t.readyState<2)return null;if(0===t.videoWidth||0===t.videoHeight)return null;const i=e.getContext("2d");return i?(e.width=t.videoWidth,e.height=t.videoHeight,i.clearRect(0,0,e.width,e.height),i.drawImage(t,0,0,e.width,e.height),new Promise(t=>{e.toBlob(t,"image/jpeg",.8)})):null},[]),N=o(async()=>{if(!A.current)return;const t=Date.now(),e=k().getConfig();if(!(t-L.current<e.checkInterval))try{const e=await _();if(!e)throw new Error("无法捕获图像");const i=Date.now(),o=await A.current.detect(e),n=Date.now();C(o),L.current=t,T(t=>{const e={...t};e.checkCount++,e.latency=n-i,o.processingTime&&(e.avgProcessingTime=(t.avgProcessingTime*(t.checkCount-1)+o.processingTime)/t.checkCount),o.violations.length>0&&(e.abnormalCount++,o.violations.forEach(t=>{e.violationStats[t.type]=(e.violationStats[t.type]||0)+1}));const r=Date.now();if(M.current>0){const t=r-M.current;e.fps=Math.round(1e3/t)}return M.current=r,e.latency<200?e.networkQuality="excellent":e.latency<500?e.networkQuality="good":e.latency<1e3?e.networkQuality="fair":e.networkQuality="poor",e}),g?.(o),o.violations.forEach(t=>{l?.(t)})}catch(i){const t=i instanceof Error?i:new Error("检测失败");u?.(t)}},[_,g,l,u]),v=o(async()=>{try{await I(),f(!0)}catch(t){const e=t instanceof Error?t:new Error("启动监控失败");throw u?.(e),e}},[I,u]),P=o(()=>{f(!1),w.current&&(w.current.getTracks().forEach(t=>t.stop()),w.current=null),S.current&&(S.current.srcObject=null),O.current&&(clearInterval(O.current),O.current=null)},[]),b=o(async()=>{await N()},[N]),R=o(async t=>D.current?D.current.updateConfig(t,{source:"local",operator:"hook-user"}):{success:!1,errors:["配置管理器未初始化"]},[]),U=o(()=>{const t=D.current?.getConfig();return t&&t.checkInterval?t:n},[]),k=o(()=>{if(!D.current)throw new Error("配置管理器未初始化");return D.current},[]);return i(()=>()=>{P()},[P]),i(()=>(E?O.current=window.setInterval(N,1e3):O.current&&(window.clearInterval(O.current),O.current=null),()=>{O.current&&window.clearInterval(O.current)}),[E,N]),i(()=>()=>{P()},[P]),{videoRef:S,canvasRef:y,startMonitoring:v,stopMonitoring:P,forceCheck:b,isMonitoring:E,latestResult:p,stats:m,config:U(),updateConfig:R,getConfigManager:k}}const h="2.0.0",l={name:"anti-cheating-monitor",version:h,description:"基于阿里云视觉智能平台的实时反作弊监控系统",author:"Anti-Cheating Monitor Team",license:"MIT",repository:"https://github.com/coding-daily-wq/anti-cheating-monitor.git",homepage:"https://coding-daily-wq.github.io/anti-cheating-monitor"};export{AuditLogger,s as CHEATING_TYPE_LABELS,c as CHEATING_TYPE_SEVERITY,r as CheatingType,ConfigManager,n as DEFAULT_DETECTION_CONFIG,DetectionEngine,l as LIB_INFO,h as VERSION,a as ViolationLevel,useAntiCheatingMonitor};
|
|
3
3
|
//# sourceMappingURL=index.es.js.map
|
package/dist/index.es.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.es.js","sources":["../src/types/config.ts","../src/constants/cheating-types.ts","../src/utils/config-manager.ts","../src/utils/oss-client.ts","../src/utils/detection-engine.ts","../src/utils/audit-logger.ts","../src/hooks/useAntiCheatingMonitor.ts","../src/index.ts"],"sourcesContent":["/**\n * 检测配置接口\n */\nexport interface DetectionConfig {\n /** 检测间隔(毫秒) */\n checkInterval: number;\n /** 是否检测多人 */\n detectMultiplePeople: boolean;\n /** 是否检测设备使用 */\n detectDeviceUsage: boolean;\n /** 是否检测离席 */\n detectAbsence: boolean;\n /** 视频分辨率 */\n resolution: {\n /** 视频宽度 */\n width: number;\n /** 视频高度 */\n height: number;\n };\n /** 检测类型\n * 0: 屏幕聊天工具检测\n * 1: 考生状态检测\n */\n type: number;\n /** 耳机检测阈值 */\n earphoneThreshold: number;\n /** 手机检测阈值 */\n cellphoneThreshold: number;\n /** 头部姿态阈值 */\n headPoseThreshold: {\n /** 俯仰角阈值 */\n pitch: number;\n /** 翻滚角阈值 */\n roll: number;\n /** 偏航角阈值 */\n yaw: number;\n };\n /** 人脸完整度阈值 */\n faceCompletenessThreshold: number;\n /** 人脸居中阈值 */\n centeringThreshold: number;\n /** 可疑移动检测阈值 */\n movementThreshold: number;\n}\n\n/**\n * 默认检测配置\n */\nexport const DEFAULT_DETECTION_CONFIG: DetectionConfig = {\n checkInterval: 3000,\n detectMultiplePeople: true,\n detectDeviceUsage: true,\n detectAbsence: true,\n resolution: {\n width: 640,\n height: 480,\n },\n type: 1,\n /** 耳机检测阈值 */\n earphoneThreshold: 0.6,\n /** 手机检测阈值 */\n cellphoneThreshold: 0.6,\n headPoseThreshold: {\n pitch: 30, // 俯仰角 ±30度\n roll: 30, // 翻滚角 ±30度\n yaw: 30, // 偏航角 ±30度\n },\n /** 人脸完整度阈值 */\n faceCompletenessThreshold: 0.8,\n /** 人脸居中阈值 */\n centeringThreshold: 0.7,\n /** 可疑移动检测阈值 */\n movementThreshold: 0.3,\n};\n\n/**\n * 阿里云OSS配置接口\n */\nexport interface OSSConfig {\n /** OSS存储桶名称 */\n bucket: string;\n /** OSS区域 */\n region: string;\n /** 访问域名(可选) */\n endpoint?: string;\n /** 安全令牌(可选,用于STS临时访问) */\n securityToken?: string;\n}\n\n/**\n * 配置更新选项\n */\nexport interface ConfigUpdateOptions {\n /** 是否强制更新(忽略验证) */\n force?: boolean;\n /** 更新来源 */\n source?: \"local\" | \"remote\" | \"backend_sync\";\n /** 操作者标识 */\n operator?: string;\n}\n","/**\n * 作弊类型枚举\n * 基于Java后端逻辑映射的前端检测类型\n */\nexport enum CheatingType {\n /** 人数异常 - 检测到多人 */\n PERSON_COUNT_ANOMALY = \"PERSON_COUNT_ANOMALY\",\n /** 检测到耳机 */\n EARPHONE_DETECTED = \"EARPHONE_DETECTED\", \n /** 检测到手机 */\n CELLPHONE_DETECTED = \"CELLPHONE_DETECTED\",\n /** 头部姿态异常 */\n HEAD_POSTURE_ABNORMAL = \"HEAD_POSTURE_ABNORMAL\",\n /** 人脸缺失 */\n FACE_MISSING = \"FACE_MISSING\",\n /** 人脸被遮挡 */\n FACE_OCCLUDED = \"FACE_OCCLUDED\",\n /** 未检测到人脸 */\n NO_FACE_DETECTED = \"NO_FACE_DETECTED\",\n /** 检测到多张人脸 */\n MULTIPLE_FACES_DETECTED = \"MULTIPLE_FACES_DETECTED\",\n /** 未居中 */\n NOT_CENTERED = \"NOT_CENTERED\",\n /** 可疑移动 */\n SUSPICIOUS_MOVEMENT = \"SUSPICIOUS_MOVEMENT\"\n}\n\n/**\n * 作弊类型显示名称映射\n */\nexport const CHEATING_TYPE_LABELS: Record<CheatingType, string> = {\n [CheatingType.PERSON_COUNT_ANOMALY]: \"人数异常\",\n [CheatingType.EARPHONE_DETECTED]: \"检测到耳机\",\n [CheatingType.CELLPHONE_DETECTED]: \"检测到手机\", \n [CheatingType.HEAD_POSTURE_ABNORMAL]: \"头部姿态异常\",\n [CheatingType.FACE_MISSING]: \"人脸缺失\",\n [CheatingType.FACE_OCCLUDED]: \"人脸被遮挡\",\n [CheatingType.NO_FACE_DETECTED]: \"未检测到人脸\",\n [CheatingType.MULTIPLE_FACES_DETECTED]: \"检测到多张人脸\",\n [CheatingType.NOT_CENTERED]: \"未居中\",\n [CheatingType.SUSPICIOUS_MOVEMENT]: \"可疑移动\"\n} as const;\n\n/**\n * 违规严重程度\n */\nexport enum ViolationLevel {\n /** 低风险 - 轻微违规 */\n LOW = \"low\",\n /** 中风险 - 需要注意 */\n MEDIUM = \"medium\", \n /** 高风险 - 严重违规 */\n HIGH = \"high\",\n /** 紧急 - 需要立即处理 */\n CRITICAL = \"critical\"\n}\n\n/**\n * 作弊类型严重程度映射\n */\nexport const CHEATING_TYPE_SEVERITY: Record<CheatingType, ViolationLevel> = {\n [CheatingType.PERSON_COUNT_ANOMALY]: ViolationLevel.HIGH,\n [CheatingType.EARPHONE_DETECTED]: ViolationLevel.MEDIUM,\n [CheatingType.CELLPHONE_DETECTED]: ViolationLevel.HIGH,\n [CheatingType.HEAD_POSTURE_ABNORMAL]: ViolationLevel.MEDIUM,\n [CheatingType.FACE_MISSING]: ViolationLevel.HIGH,\n [CheatingType.FACE_OCCLUDED]: ViolationLevel.MEDIUM,\n [CheatingType.NO_FACE_DETECTED]: ViolationLevel.HIGH,\n [CheatingType.MULTIPLE_FACES_DETECTED]: ViolationLevel.HIGH,\n [CheatingType.NOT_CENTERED]: ViolationLevel.LOW,\n [CheatingType.SUSPICIOUS_MOVEMENT]: ViolationLevel.MEDIUM\n} as const;","import { DetectionConfig, ConfigUpdateOptions, DEFAULT_DETECTION_CONFIG } from '../types';\nimport { ConfigAuditLog } from '../types';\n\n/**\n * 配置管理器类\n * 负责阈值配置的管理、热更新和审计\n */\nexport class ConfigManager {\n private static instance: ConfigManager;\n private config: DetectionConfig;\n private auditLogs: ConfigAuditLog[] = [];\n private listeners: Set<(config: DetectionConfig) => void> = new Set();\n private syncTimer?: number;\n private readonly STORAGE_KEY = 'anti-cheating-config';\n\n private constructor() {\n this.config = this.getDefaultConfig();\n this.loadFromStorage();\n this.startHotUpdate();\n }\n\n /**\n * 获取单例实例\n */\n public static getInstance(): ConfigManager {\n if (!ConfigManager.instance) {\n ConfigManager.instance = new ConfigManager();\n }\n return ConfigManager.instance;\n }\n\n /**\n * 获取默认配置\n */\n private getDefaultConfig(): DetectionConfig {\n return { ...DEFAULT_DETECTION_CONFIG };\n }\n\n /**\n * 从本地存储加载配置\n */\n private loadFromStorage(): void {\n try {\n const stored = localStorage.getItem(this.STORAGE_KEY);\n if (stored) {\n const parsedConfig = JSON.parse(stored);\n this.config = { ...this.config, ...parsedConfig };\n }\n } catch (error) {\n console.warn('加载本地配置失败:', error);\n }\n }\n\n /**\n * 保存配置到本地存储\n */\n private saveToStorage(): void {\n try {\n localStorage.setItem(this.STORAGE_KEY, JSON.stringify(this.config));\n } catch (error) {\n console.warn('保存本地配置失败:', error);\n }\n }\n\n /**\n * 启动热更新监听\n */\n private startHotUpdate(): void {\n // 监听storage事件,实现跨标签页配置同步\n const handleStorageChange = (e: StorageEvent) => {\n if (e.key === this.STORAGE_KEY && e.newValue) {\n try {\n const newConfig = JSON.parse(e.newValue);\n this.updateConfig(newConfig, { source: 'remote' });\n } catch (error) {\n console.warn('热更新配置失败:', error);\n }\n }\n };\n\n window.addEventListener('storage', handleStorageChange);\n }\n\n /**\n * 添加审计日志\n */\n private addAuditLog(\n operation: ConfigAuditLog['operation'],\n configKey: string,\n oldValue?: any,\n newValue?: any,\n source: ConfigAuditLog['source'] = 'local',\n operator?: string\n ): void {\n const log: ConfigAuditLog = {\n id: `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,\n timestamp: Date.now(),\n operation,\n configKey,\n oldValue,\n newValue,\n operator,\n source\n };\n\n this.auditLogs.push(log);\n \n // 限制日志数量,避免内存泄漏\n if (this.auditLogs.length > 1000) {\n this.auditLogs = this.auditLogs.slice(-500);\n }\n }\n\n /**\n * 验证配置有效性\n */\n private validateConfig(config: Partial<DetectionConfig>): string[] {\n const errors: string[] = [];\n\n if (config.checkInterval !== undefined) {\n if (config.checkInterval < 1000 || config.checkInterval > 60000) {\n errors.push('检测间隔应在1000-60000毫秒之间');\n }\n }\n\n if (config.resolution) {\n if (config.resolution.width < 320 || config.resolution.width > 1920) {\n errors.push('视频宽度应在320-1920之间');\n }\n if (config.resolution.height < 240 || config.resolution.height > 1080) {\n errors.push('视频高度应在240-1080之间');\n }\n }\n\n if (config.earphoneThreshold !== undefined) {\n if (config.earphoneThreshold < 0 || config.earphoneThreshold > 1) {\n errors.push('耳机检测阈值应在0-1之间');\n }\n }\n\n if (config.cellphoneThreshold !== undefined) {\n if (config.cellphoneThreshold < 0 || config.cellphoneThreshold > 1) {\n errors.push('手机检测阈值应在0-1之间');\n }\n }\n\n return errors;\n }\n\n /**\n * 获取当前配置\n */\n public getConfig(): DetectionConfig {\n return { ...this.config };\n }\n\n /**\n * 更新配置\n */\n public updateConfig(\n newConfig: Partial<DetectionConfig>, \n options: ConfigUpdateOptions = {}\n ): { success: boolean; errors?: string[] } {\n const { force = false, source = 'local', operator } = options;\n\n // 验证配置\n if (!force) {\n const errors = this.validateConfig(newConfig);\n if (errors.length > 0) {\n return { success: false, errors };\n }\n }\n\n // 记录变更\n Object.entries(newConfig).forEach(([key, value]) => {\n const oldValue = (this.config as any)[key];\n if (JSON.stringify(oldValue) !== JSON.stringify(value)) {\n this.addAuditLog('update', key, oldValue, value, source, operator);\n }\n });\n\n // 更新配置\n this.config = { ...this.config, ...newConfig };\n\n // 保存到本地存储\n if (source === 'local') {\n this.saveToStorage();\n }\n\n // 通知监听器\n this.notifyListeners();\n\n return { success: true };\n }\n\n /**\n * 重置配置为默认值\n */\n public resetConfig(source: ConfigAuditLog['source'] = 'local', operator?: string): void {\n const oldConfig = { ...this.config };\n this.config = this.getDefaultConfig();\n\n // 记录重置操作\n Object.keys(oldConfig).forEach(key => {\n this.addAuditLog('update', key, oldConfig[key as keyof DetectionConfig], this.config[key as keyof DetectionConfig], source, operator);\n });\n\n this.saveToStorage();\n this.notifyListeners();\n }\n\n /**\n * 添加配置变更监听器\n */\n public addListener(listener: (config: DetectionConfig) => void): () => void {\n this.listeners.add(listener);\n \n // 返回取消监听的函数\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n /**\n * 通知所有监听器\n */\n private notifyListeners(): void {\n this.listeners.forEach(listener => {\n try {\n listener(this.getConfig());\n } catch (error) {\n console.warn('配置监听器执行失败:', error);\n }\n });\n }\n\n /**\n * 获取审计日志\n */\n public getAuditLogs(): ConfigAuditLog[] {\n return [...this.auditLogs];\n }\n\n /**\n * 清理审计日志\n */\n public clearAuditLogs(olderThan?: number): void {\n if (olderThan) {\n this.auditLogs = this.auditLogs.filter(log => log.timestamp > olderThan);\n } else {\n this.auditLogs = [];\n }\n }\n\n /**\n * 销毁实例\n */\n public destroy(): void {\n if (this.syncTimer) {\n clearInterval(this.syncTimer);\n }\n this.listeners.clear();\n }\n}","import { OSSConfig } from '../types/config';\nimport { AntiCheatingCredentials } from '../types/detection';\nimport type OSS from 'ali-oss';\n\n/**\n * OSS客户端类\n * 用于上传图片到阿里云OSS\n */\nexport class OSSClient {\n private config: OSSConfig;\n private credentials: AntiCheatingCredentials;\n private isInitialized: boolean = false;\n private client: OSS | null = null;\n\n constructor(config: OSSConfig, credentials: AntiCheatingCredentials) {\n this.config = config;\n this.credentials = credentials;\n }\n\n /**\n * 初始化OSS客户端\n */\n public async initialize(): Promise<void> {\n if (this.isInitialized) {\n return Promise.resolve();\n }\n\n // 验证配置\n if (!this.config.bucket || !this.config.region) {\n throw new Error('OSS配置不完整:缺少bucket或region');\n }\n\n if (!this.credentials.accessKeyId || !this.credentials.accessKeySecret) {\n throw new Error('OSS凭证不完整:缺少accessKeyId或accessKeySecret');\n }\n\n // 初始化阿里云OSS客户端\n try {\n // 动态导入 ali-oss,避免在 SSR 阶段加载导致 exports is not defined 错误\n // 同时也避免了 Node.js 依赖 (proxy-agent) 的问题\n // @ts-ignore\n const OSSModule = await import('ali-oss/dist/aliyun-oss-sdk.min.js');\n const OSSClass = (OSSModule.default || OSSModule) as unknown as typeof OSS;\n\n this.client = new OSSClass({\n region: this.config.region,\n accessKeyId: this.credentials.accessKeyId,\n accessKeySecret: this.credentials.accessKeySecret,\n stsToken: this.credentials.securityToken,\n bucket: this.config.bucket,\n });\n\n this.isInitialized = true;\n } catch (error) {\n console.error('Failed to load ali-oss:', error);\n throw new Error('无法加载 OSS 客户端库');\n }\n }\n\n /**\n * 上传文件到OSS\n * @param blob 文件数据\n * @param dir 存储目录\n * @returns 文件URL\n */\n public async put(blob: Blob, dir: string = 'monitor/'): Promise<{ url: string }> {\n if (!this.isInitialized) {\n await this.initialize();\n }\n\n if (!this.client) {\n throw new Error('OSS客户端未初始化');\n }\n\n // 生成文件名\n const filename = `${Date.now()}-${Math.random().toString(36).slice(2)}.jpg`;\n const objectKey = dir + filename;\n\n try {\n // 使用阿里云OSS SDK进行真实上传\n const result = await this.client.put(objectKey, blob, {\n headers: {\n 'Cache-Control': 'no-cache',\n 'Content-Disposition': `inline; filename=\"${filename}\"`,\n },\n });\n\n // 确保返回的是公开访问的URL\n const publicUrl = result.url || `https://${this.config.bucket}.${this.config.region}.aliyuncs.com/${objectKey}`;\n \n return { url: publicUrl };\n\n } catch (error) {\n throw new Error(`阿里云 OSS 上传失败: ${error instanceof Error ? error.message : '未知错误'}`);\n }\n }\n\n \n\n /**\n * 检查客户端是否已初始化\n */\n public get ready(): boolean {\n return this.isInitialized;\n }\n\n /**\n * 更新配置\n */\n public updateConfig(config: Partial<OSSConfig>): void {\n this.config = { ...this.config, ...config };\n this.isInitialized = false; // 需要重新初始化\n }\n\n /**\n * 更新凭证\n */\n public updateCredentials(credentials: Partial<AntiCheatingCredentials>): void {\n this.credentials = { ...this.credentials, ...credentials };\n this.isInitialized = false; // 需要重新初始化\n }\n}","import { \n DetectionResult, \n DetectionConfig, \n VIAPIRequestParams, \n VIAPIResponse,\n CheatingViolation,\n AntiCheatingCredentials\n} from '../types';\nimport { CheatingType, ViolationLevel, CHEATING_TYPE_SEVERITY, CHEATING_TYPE_LABELS } from '../constants';\nimport { OSSConfig } from '../types/config';\nimport { OSSClient } from './oss-client';\n\n/**\n * 检测引擎类\n * 负责执行反作弊检测逻辑,集成OSS上传和阿里云VIAPI调用\n */\nexport class DetectionEngine {\n private config: DetectionConfig;\n private credentials: AntiCheatingCredentials;\n private ossConfig: OSSConfig;\n private ossClient: OSSClient;\n\n constructor(config: DetectionConfig, credentials: AntiCheatingCredentials, ossConfig: OSSConfig) {\n this.config = config;\n this.credentials = credentials;\n this.ossConfig = ossConfig;\n this.ossClient = new OSSClient(ossConfig, credentials);\n }\n\n /**\n * 更新配置\n */\n public updateConfig(config: DetectionConfig): void {\n this.config = config;\n }\n\n /**\n * 更新凭证\n */\n public updateCredentials(credentials: AntiCheatingCredentials): void {\n this.credentials = credentials;\n this.ossClient.updateCredentials(credentials);\n }\n\n /**\n * 更新OSS配置\n */\n public updateOSSConfig(ossConfig: OSSConfig): void {\n this.ossConfig = ossConfig;\n this.ossClient.updateConfig(ossConfig);\n }\n\n /**\n * 执行检测\n * @param imageData 图像数据(Base64或Blob)\n */\n public async detect(imageData: string | Blob): Promise<DetectionResult> {\n const startTime = Date.now();\n\n try {\n let imageUrl: string;\n\n // 如果是Blob,先上传到OSS\n if (imageData instanceof Blob) {\n const uploadResult = await this.ossClient.put(imageData);\n imageUrl = uploadResult.url;\n } else {\n // 如果是Base64,转换为Blob后上传\n const blob = this.base64ToBlob(imageData);\n const uploadResult = await this.ossClient.put(blob);\n imageUrl = uploadResult.url;\n }\n\n // 调用阿里云VIAPI\n const apiResponse = await this.callVIAPI(imageUrl);\n \n // 处理检测结果\n const result = this.processAPIResponse(apiResponse);\n \n // 计算处理时间\n result.processingTime = Date.now() - startTime;\n \n return result;\n } catch (error) {\n // 返回错误结果\n return {\n timestamp: Date.now(),\n faceCount: 0,\n faceCompleteness: 0,\n personCount: 0,\n violations: [{\n type: CheatingType.NO_FACE_DETECTED,\n level: ViolationLevel.HIGH,\n confidence: 1.0,\n description: `检测失败: ${error instanceof Error ? error.message : '未知错误'}`\n }],\n processingTime: Date.now() - startTime\n };\n }\n }\n\n /**\n * Base64转Blob\n * @private\n */\n private base64ToBlob(base64: string): Blob {\n const parts = base64.split(',');\n const mimeType = parts[0].match(/:(.*?);/)?.[1] || 'image/jpeg';\n const byteString = atob(parts[1]);\n const arrayBuffer = new ArrayBuffer(byteString.length);\n const uint8Array = new Uint8Array(arrayBuffer);\n \n for (let i = 0; i < byteString.length; i++) {\n uint8Array[i] = byteString.charCodeAt(i);\n }\n \n return new Blob([arrayBuffer], { type: mimeType });\n }\n\n /**\n * 调用阿里云VIAPI\n * @private\n */\n private async callVIAPI(imageUrl: string): Promise<VIAPIResponse> {\n // 阿里云API常量\n const API_ENDPOINT = \"https://facebody.cn-shanghai.aliyuncs.com\";\n const API_VERSION = \"2019-12-30\";\n\n // 构建请求参数\n const params: Record<string, string> = {\n Action: \"MonitorExamination\",\n Version: API_VERSION,\n Format: \"JSON\",\n AccessKeyId: this.credentials.accessKeyId,\n SignatureMethod: \"HMAC-SHA1\",\n Timestamp: this.getTimestamp(),\n SignatureVersion: \"1.0\",\n SignatureNonce: this.generateNonce(),\n RegionId: \"cn-shanghai\",\n Type: String(this.config.type),\n ImageURL: imageUrl,\n };\n\n // 如果有安全令牌,添加到参数中\n if (this.credentials.securityToken) {\n params.SecurityToken = this.credentials.securityToken;\n }\n\n // 1. 对参数进行排序\n const sortedKeys = Object.keys(params).sort();\n\n // 2. 构建规范化的查询字符串\n const canonicalizedQueryString = sortedKeys\n .map((key) => `${this.percentEncode(key)}=${this.percentEncode(params[key])}`)\n .join(\"&\");\n\n // 3. 构建待签名字符串\n const stringToSign = `POST&${this.percentEncode(\"/\")}&${this.percentEncode(\n canonicalizedQueryString\n )}`;\n\n // 4. 计算签名\n const signature = await this.hmacSha1(\n `${this.credentials.accessKeySecret}&`,\n stringToSign\n );\n\n // 5. 构建 Body 内容 (POST请求)\n const bodyString = `${canonicalizedQueryString}&Signature=${this.percentEncode(\n signature\n )}`;\n\n // 发送请求\n const response = await fetch(API_ENDPOINT, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n },\n body: bodyString,\n mode: \"cors\",\n });\n\n if (!response.ok) {\n throw new Error(\n `API Error: ${response.status} ${response.statusText}`\n );\n }\n\n const data = await response.json();\n\n // 直接返回API响应数据,已经是标准格式\n return data as VIAPIResponse;\n }\n\n /**\n * 使用HMAC-SHA1算法生成签名\n * @private\n */\n private async hmacSha1(key: string, data: string): Promise<string> {\n const encoder = new TextEncoder();\n const keyData = encoder.encode(key);\n const dataData = encoder.encode(data);\n\n const cryptoKey = await window.crypto.subtle.importKey(\n \"raw\",\n keyData,\n { name: \"HMAC\", hash: \"SHA-1\" },\n false,\n [\"sign\"]\n );\n\n const signature = await window.crypto.subtle.sign(\n \"HMAC\",\n cryptoKey,\n dataData\n );\n \n return btoa(String.fromCharCode(...Array.from(new Uint8Array(signature))));\n }\n\n /**\n * URL编码函数\n * @private\n */\n private percentEncode(str: string): string {\n return encodeURIComponent(str)\n .replace(/!/g, \"%21\")\n .replace(/'/g, \"%27\")\n .replace(/\\(/g, \"%28\")\n .replace(/\\)/g, \"%29\")\n .replace(/\\*/g, \"%2A\");\n }\n\n /**\n * 获取当前时间戳\n * @private\n */\n private getTimestamp(): string {\n return new Date().toISOString().replace(/\\.\\d{3}/, \"\");\n }\n\n /**\n * 生成随机字符串\n * @private\n */\n private generateNonce(): string {\n return (\n Math.random().toString(36).substring(2, 15) +\n Math.random().toString(36).substring(2, 15)\n );\n }\n\n /**\n * 处理API响应,生成检测结果\n * @private\n */\n private processAPIResponse(response: VIAPIResponse): DetectionResult {\n const violations: CheatingViolation[] = [];\n\n // 从正确的数据路径获取信息\n const faceInfo = response.Data?.FaceInfo as any || {};\n const personInfo = response.Data?.PersonInfo as any || {};\n const faceNumber = faceInfo.FaceNumber || 0;\n const personNumber = personInfo.PersonNumber || 0;\n const completeness = faceInfo.Completeness || 0;\n const pose = faceInfo.Pose || {};\n\n // 1. 检测人脸数量\n if (faceNumber === 0) {\n violations.push(this.createViolation(\n CheatingType.NO_FACE_DETECTED,\n ViolationLevel.HIGH,\n 1.0,\n '未检测到人脸'\n ));\n } else if (faceNumber > 1) {\n violations.push(this.createViolation(\n CheatingType.MULTIPLE_FACES_DETECTED,\n ViolationLevel.HIGH,\n 1.0,\n `检测到${faceNumber}张人脸`\n ));\n }\n\n // 2. 检测人员数量\n if (this.config.detectMultiplePeople && personNumber > 1) {\n violations.push(this.createViolation(\n CheatingType.PERSON_COUNT_ANOMALY,\n ViolationLevel.HIGH,\n 1.0,\n `检测到${personNumber}人`\n ));\n }\n\n // 3. 检测人脸完整度\n if (completeness < this.config.faceCompletenessThreshold) {\n violations.push(this.createViolation(\n CheatingType.FACE_OCCLUDED,\n ViolationLevel.MEDIUM,\n 1.0 - completeness,\n `人脸完整度${(completeness * 100).toFixed(0)}%`\n ));\n }\n\n // 4. 检测头部姿态\n if (pose) {\n const { Pitch, Roll, Yaw } = pose;\n const { pitch: pThresh, roll: rThresh, yaw: yThresh } = this.config.headPoseThreshold;\n\n if (Math.abs(Pitch) > pThresh || Math.abs(Roll) > rThresh || Math.abs(Yaw) > yThresh) {\n violations.push(this.createViolation(\n CheatingType.HEAD_POSTURE_ABNORMAL,\n ViolationLevel.MEDIUM,\n Math.max(\n Math.abs(Pitch) / pThresh,\n Math.abs(Roll) / rThresh,\n Math.abs(Yaw) / yThresh\n ) - 1,\n `头部姿态异常: ${Pitch > 0 ? '抬头' : '低头'}${Pitch.toFixed(1)}° 偏头${Roll.toFixed(1)}° ${Yaw > 0 ? '左转' : '右转'}${Yaw.toFixed(1)}°`\n ));\n }\n }\n\n // 5. 检测设备使用\n if (this.config.detectDeviceUsage) {\n // 耳机检测\n const earphoneScore = personInfo.EarPhone?.Score || 0;\n if (earphoneScore > this.config.earphoneThreshold) {\n violations.push(this.createViolation(\n CheatingType.EARPHONE_DETECTED,\n ViolationLevel.MEDIUM,\n earphoneScore,\n `检测到耳机 (置信度: ${(earphoneScore * 100).toFixed(0)}%)`\n ));\n }\n\n // 手机检测\n const cellphoneScore = personInfo.CellPhone?.Score || 0;\n if (cellphoneScore > this.config.cellphoneThreshold) {\n violations.push(this.createViolation(\n CheatingType.CELLPHONE_DETECTED,\n ViolationLevel.HIGH,\n cellphoneScore,\n `检测到手机 (置信度: ${(cellphoneScore * 100).toFixed(0)}%)`\n ));\n }\n }\n\n // 6. 检测居中程度(基于姿态数据估算)\n if (pose) {\n const centeringScore = this.calculateCenteringScore(pose);\n if (centeringScore < this.config.centeringThreshold) {\n violations.push(this.createViolation(\n CheatingType.NOT_CENTERED,\n ViolationLevel.LOW,\n 1.0 - centeringScore,\n `未居中 (居中度: ${(centeringScore * 100).toFixed(0)}%)`\n ));\n }\n }\n\n return {\n timestamp: Date.now(),\n faceCount: faceNumber,\n faceCompleteness: completeness,\n personCount: personNumber,\n pose: pose,\n violations,\n rawResponse: response\n };\n }\n\n /**\n * 创建违规对象\n * @private\n */\n private createViolation(\n type: CheatingType,\n level: ViolationLevel,\n confidence: number,\n description: string,\n data?: any\n ): CheatingViolation {\n return {\n type,\n level,\n confidence: Math.max(0, Math.min(1, confidence)),\n description,\n data\n };\n }\n\n /**\n * 计算居中程度\n * @private\n */\n private calculateCenteringScore(pose: { Pitch: number; Roll: number; Yaw: number }): number {\n const { Pitch, Roll, Yaw } = pose;\n \n // 基于姿态角度计算居中程度\n const pitchScore = Math.max(0, 1 - Math.abs(Pitch) / 30);\n const rollScore = Math.max(0, 1 - Math.abs(Roll) / 45);\n const yawScore = Math.max(0, 1 - Math.abs(Yaw) / 60);\n \n return (pitchScore + rollScore + yawScore) / 3;\n }\n\n /**\n * 获取检测统计信息\n */\n public getDetectionStats(): {\n totalDetections: number;\n averageProcessingTime: number;\n violationRate: number;\n mostCommonViolations: Array<{ type: CheatingType; count: number }>;\n } {\n // 这里应该从历史记录中计算统计信息\n // 为了简化,返回模拟数据\n return {\n totalDetections: 0,\n averageProcessingTime: 0,\n violationRate: 0,\n mostCommonViolations: []\n };\n }\n}","import { ConfigAuditLog, AuditLogQueryOptions, AuditLogStats } from '../types';\n\n/**\n * 审计日志管理器类\n * 负责管理和分析配置变更审计日志\n */\nexport class AuditLogger {\n private logs: ConfigAuditLog[] = [];\n private readonly MAX_LOGS = 1000; // 最大日志数量限制\n\n /**\n * 添加审计日志\n */\n public addLog(log: Omit<ConfigAuditLog, 'id' | 'timestamp'>): ConfigAuditLog {\n const auditLog: ConfigAuditLog = {\n ...log,\n id: this.generateLogId(),\n timestamp: Date.now()\n };\n\n this.logs.unshift(auditLog); // 新日志添加到开头\n\n // 限制日志数量\n if (this.logs.length > this.MAX_LOGS) {\n this.logs = this.logs.slice(0, this.MAX_LOGS);\n }\n\n // 持久化到本地存储\n this.persistLogs();\n\n return auditLog;\n }\n\n /**\n * 生成日志ID\n */\n private generateLogId(): string {\n return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;\n }\n\n /**\n * 查询审计日志\n */\n public queryLogs(options: AuditLogQueryOptions = {}): ConfigAuditLog[] {\n let filteredLogs = [...this.logs];\n\n // 时间范围过滤\n if (options.startTime !== undefined) {\n filteredLogs = filteredLogs.filter(log => log.timestamp >= options.startTime!);\n }\n if (options.endTime !== undefined) {\n filteredLogs = filteredLogs.filter(log => log.timestamp <= options.endTime!);\n }\n\n // 操作类型过滤\n if (options.operation) {\n filteredLogs = filteredLogs.filter(log => log.operation === options.operation);\n }\n\n // 配置项过滤\n if (options.configKey) {\n filteredLogs = filteredLogs.filter(log => log.configKey === options.configKey);\n }\n\n // 操作者过滤\n if (options.operator) {\n filteredLogs = filteredLogs.filter(log => log.operator === options.operator);\n }\n\n // 来源过滤\n if (options.source) {\n filteredLogs = filteredLogs.filter(log => log.source === options.source);\n }\n\n // 分页\n const offset = options.offset || 0;\n const limit = options.limit || 50;\n filteredLogs = filteredLogs.slice(offset, offset + limit);\n\n return filteredLogs;\n }\n\n /**\n * 获取所有日志\n */\n public getAllLogs(): ConfigAuditLog[] {\n return [...this.logs];\n }\n\n /**\n * 获取日志统计信息\n */\n public getStats(): AuditLogStats {\n const operationStats: Record<ConfigAuditLog['operation'], number> = {\n create: 0,\n update: 0,\n delete: 0,\n sync: 0\n };\n\n const sourceStats: Record<ConfigAuditLog['source'], number> = {\n local: 0,\n remote: 0,\n backend_sync: 0\n };\n\n const configCounts: Record<string, number> = {};\n const operatorCounts: Record<string, number> = {};\n\n this.logs.forEach(log => {\n // 操作类型统计\n operationStats[log.operation]++;\n\n // 来源统计\n sourceStats[log.source]++;\n\n // 配置项统计\n configCounts[log.configKey] = (configCounts[log.configKey] || 0) + 1;\n\n // 操作者统计\n if (log.operator) {\n operatorCounts[log.operator] = (operatorCounts[log.operator] || 0) + 1;\n }\n });\n\n // 最活跃的配置项(前5个)\n const mostActiveConfigs = Object.entries(configCounts)\n .sort(([, a], [, b]) => b - a)\n .slice(0, 5)\n .map(([configKey, count]) => ({ configKey, count }));\n\n // 最活跃的操作者(前5个)\n const mostActiveOperators = Object.entries(operatorCounts)\n .sort(([, a], [, b]) => b - a)\n .slice(0, 5)\n .map(([operator, count]) => ({ operator, count }));\n\n return {\n totalLogs: this.logs.length,\n operationStats,\n sourceStats,\n mostActiveConfigs,\n mostActiveOperators\n };\n }\n\n /**\n * 清理旧日志\n */\n public cleanup(olderThan: number): number {\n const beforeCount = this.logs.length;\n this.logs = this.logs.filter(log => log.timestamp > olderThan);\n const cleanedCount = beforeCount - this.logs.length;\n\n if (cleanedCount > 0) {\n this.persistLogs();\n }\n\n return cleanedCount;\n }\n\n /**\n * 清空所有日志\n */\n public clear(): void {\n this.logs = [];\n this.persistLogs();\n }\n\n /**\n * 导出日志为JSON\n */\n public exportLogs(format: 'json' | 'csv' = 'json'): string {\n if (format === 'json') {\n return JSON.stringify(this.logs, null, 2);\n } else if (format === 'csv') {\n return this.convertToCSV(this.logs);\n } else {\n throw new Error(`不支持的导出格式: ${format}`);\n }\n }\n\n /**\n * 导入日志\n */\n public importLogs(logsData: string, format: 'json' | 'csv' = 'json'): number {\n let importedLogs: ConfigAuditLog[];\n\n if (format === 'json') {\n try {\n importedLogs = JSON.parse(logsData);\n } catch (error) {\n throw new Error('JSON格式错误');\n }\n } else {\n throw new Error(`不支持的导入格式: ${format}`);\n }\n\n // 验证日志格式\n const validLogs = importedLogs.filter(log => this.validateLog(log));\n\n // 合并日志(避免重复)\n const existingIds = new Set(this.logs.map(log => log.id));\n const newLogs = validLogs.filter(log => !existingIds.has(log.id));\n\n this.logs = [...newLogs, ...this.logs];\n\n // 限制日志数量\n if (this.logs.length > this.MAX_LOGS) {\n this.logs = this.logs.slice(0, this.MAX_LOGS);\n }\n\n this.persistLogs();\n\n return newLogs.length;\n }\n\n /**\n * 验证日志格式\n */\n private validateLog(log: any): log is ConfigAuditLog {\n return (\n typeof log === 'object' &&\n typeof log.id === 'string' &&\n typeof log.timestamp === 'number' &&\n ['create', 'update', 'delete', 'sync'].includes(log.operation) &&\n typeof log.configKey === 'string' &&\n ['local', 'remote', 'backend_sync'].includes(log.source)\n );\n }\n\n /**\n * 转换为CSV格式\n */\n private convertToCSV(logs: ConfigAuditLog[]): string {\n const headers = [\n 'ID', 'Timestamp', 'Operation', 'Config Key', 'Old Value', \n 'New Value', 'Operator', 'Source'\n ];\n\n const rows = logs.map(log => [\n log.id,\n new Date(log.timestamp).toISOString(),\n log.operation,\n log.configKey,\n JSON.stringify(log.oldValue || ''),\n JSON.stringify(log.newValue || ''),\n log.operator || '',\n log.source\n ]);\n\n const csvContent = [\n headers.join(','),\n ...rows.map(row => row.map(cell => `\"${cell}\"`).join(','))\n ].join('\\n');\n\n return csvContent;\n }\n\n /**\n * 持久化日志到本地存储\n */\n private persistLogs(): void {\n try {\n localStorage.setItem('anti-cheating-audit-logs', JSON.stringify(this.logs));\n } catch (error) {\n console.warn('保存审计日志失败:', error);\n }\n }\n\n /**\n * 从本地存储加载日志\n */\n public loadFromStorage(): void {\n try {\n const stored = localStorage.getItem('anti-cheating-audit-logs');\n if (stored) {\n const logs = JSON.parse(stored);\n if (Array.isArray(logs)) {\n this.logs = logs.filter(log => this.validateLog(log));\n }\n }\n } catch (error) {\n console.warn('加载审计日志失败:', error);\n this.logs = [];\n }\n }\n\n /**\n * 获取最近的日志\n */\n public getRecentLogs(count: number = 10): ConfigAuditLog[] {\n return this.logs.slice(0, count);\n }\n\n /**\n * 根据配置键获取日志\n */\n public getLogsByConfigKey(configKey: string): ConfigAuditLog[] {\n return this.logs.filter(log => log.configKey === configKey);\n }\n\n /**\n * 根据操作者获取日志\n */\n public getLogsByOperator(operator: string): ConfigAuditLog[] {\n return this.logs.filter(log => log.operator === operator);\n }\n}","import { useState, useEffect, useRef, useCallback } from \"react\";\nimport {\n DetectionConfig,\n DetectionResult,\n MonitorStats,\n AntiCheatingCredentials,\n CheatingViolation\n} from '../types';\nimport { DEFAULT_DETECTION_CONFIG, OSSConfig } from '../types/config';\nimport { CheatingType } from '../constants';\nimport { ConfigManager } from '../utils';\nimport { DetectionEngine } from '../utils';\n\n/**\n * Hook参数接口\n */\nexport interface UseAntiCheatingMonitorProps {\n /** 阿里云访问凭证 */\n credentials: AntiCheatingCredentials;\n /** OSS配置 */\n ossConfig: OSSConfig;\n /** 检测配置 */\n config?: Partial<DetectionConfig>;\n /** 违规检测回调函数 */\n onViolation?: (violation: CheatingViolation) => void;\n /** 检测结果回调函数 */\n onDetectionResult?: (result: DetectionResult) => void;\n /** 错误回调函数 */\n onError?: (error: Error) => void;\n /** 配置变更回调函数 */\n onConfigChange?: (config: DetectionConfig) => void;\n}\n\n/**\n * Hook返回值接口\n */\nexport interface UseAntiCheatingMonitorReturn {\n /** 视频元素引用 */\n videoRef: React.RefObject<HTMLVideoElement | null>;\n /** 画布元素引用 */\n canvasRef: React.RefObject<HTMLCanvasElement | null>;\n /** 开始监控 */\n startMonitoring: () => Promise<void>;\n /** 停止监控 */\n stopMonitoring: () => void;\n /** 强制检测一次 */\n forceCheck: () => Promise<void>;\n /** 是否正在监控 */\n isMonitoring: boolean;\n /** 最新检测结果 */\n latestResult: DetectionResult | null;\n /** 监控统计信息 */\n stats: MonitorStats;\n /** 当前配置 */\n config: DetectionConfig;\n /** 更新配置 */\n updateConfig: (newConfig: Partial<DetectionConfig>) => Promise<{ success: boolean; errors?: string[] }>;\n /** 获取配置管理器实例 */\n getConfigManager: () => ConfigManager;\n}\n\n/**\n * 反作弊监控Hook\n * 集成阿里云OSS上传和VIAPI人脸检测服务\n * \n * @param props Hook参数\n * @returns Hook返回值\n * \n * @example\n * ```tsx\n * const {\n * videoRef,\n * startMonitoring,\n * stopMonitoring,\n * isMonitoring,\n * latestResult,\n * stats,\n * config,\n * updateConfig\n * } = useAntiCheatingMonitor({\n * credentials: {\n * accessKeyId: 'your-access-key-id',\n * accessKeySecret: 'your-access-key-secret'\n * },\n * ossConfig: {\n * bucket: 'your-bucket-name',\n * region: 'oss-cn-hangzhou'\n * },\n * config: {\n * checkInterval: 3000,\n * detectMultiplePeople: true\n * },\n * onViolation: (violation) => {\n * console.log('检测到违规:', violation);\n * },\n * onDetectionResult: (result) => {\n * console.log('检测结果:', result);\n * },\n * onError: (error) => {\n * console.error('监控错误:', error);\n * }\n * });\n * ```\n */\nexport function useAntiCheatingMonitor(props: UseAntiCheatingMonitorProps): UseAntiCheatingMonitorReturn {\n const {\n credentials,\n ossConfig,\n config: initialConfig,\n onViolation,\n onDetectionResult,\n onError,\n onConfigChange\n } = props;\n\n // 状态管理\n const [isMonitoring, setIsMonitoring] = useState(false);\n const [latestResult, setLatestResult] = useState<DetectionResult | null>(null);\n const [stats, setStats] = useState<MonitorStats>({\n checkCount: 0,\n abnormalCount: 0,\n latency: 0,\n networkQuality: 'excellent',\n fps: 0,\n avgProcessingTime: 0,\n violationStats: Object.fromEntries(\n Object.values(CheatingType).map(type => [type, 0])\n ) as Record<CheatingType, number>\n });\n\n // 引用管理\n const videoRef = useRef<HTMLVideoElement | null>(null);\n const canvasRef = useRef<HTMLCanvasElement | null>(null);\n const streamRef = useRef<MediaStream | null>(null);\n const detectionTimerRef = useRef<number | null>(null);\n const configManagerRef = useRef<ConfigManager | null>(null);\n const detectionEngineRef = useRef<DetectionEngine | null>(null);\n const lastFrameTimeRef = useRef<number>(0);\n const lastDetectionTimeRef = useRef<number>(0); // 上次检测时间,用于节流控制\n\n // 同步初始化配置管理器\n if (!configManagerRef.current) {\n configManagerRef.current = ConfigManager.getInstance();\n \n // 如果有初始配置,更新配置\n if (initialConfig) {\n configManagerRef.current.updateConfig(initialConfig, {\n source: 'local',\n operator: 'hook-initialization'\n });\n }\n }\n\n // 监听配置变更和初始化相关逻辑\n useEffect(() => {\n if (!configManagerRef.current) {\n return () => {};\n }\n\n // 监听配置变更\n const unsubscribe = configManagerRef.current.addListener((newConfig) => {\n if (detectionEngineRef.current) {\n detectionEngineRef.current.updateConfig(newConfig);\n }\n onConfigChange?.(newConfig);\n });\n\n return () => {\n unsubscribe();\n };\n }, [initialConfig, onConfigChange]);\n\n // 初始化检测引擎\n useEffect(() => {\n const config = configManagerRef.current?.getConfig();\n if (config && credentials && ossConfig) {\n detectionEngineRef.current = new DetectionEngine(config, credentials, ossConfig);\n }\n }, [credentials, ossConfig]);\n\n /**\n * 获取摄像头权限\n */\n const getCameraPermission = useCallback(async (): Promise<MediaStream> => {\n try {\n const config = configManagerRef.current?.getConfig();\n const stream = await navigator.mediaDevices.getUserMedia({\n video: {\n width: config?.resolution.width || 640,\n height: config?.resolution.height || 480,\n facingMode: 'user'\n },\n audio: false\n });\n\n streamRef.current = stream;\n \n if (videoRef.current) {\n videoRef.current.srcObject = stream;\n // 尝试自动播放\n videoRef.current\n .play()\n .catch((e) => console.warn(\"自动播放被阻止\", e));\n }\n\n return stream;\n } catch (error) {\n throw new Error(`获取摄像头权限失败: ${error instanceof Error ? error.message : '未知错误'}`);\n }\n }, []);\n\n /**\n * 捕获当前帧\n */\n const captureFrame = useCallback(async (): Promise<Blob | null> => {\n const video = videoRef.current;\n const canvas = canvasRef.current;\n \n if (!video || !canvas) {\n return null;\n }\n\n // 检查视频状态\n if (video.readyState < 2) { // HAVE_CURRENT_DATA\n console.warn('视频尚未准备好,当前状态:', video.readyState);\n return null;\n }\n\n // 检查视频尺寸\n if (video.videoWidth === 0 || video.videoHeight === 0) {\n console.warn('视频尺寸无效:', { videoWidth: video.videoWidth, videoHeight: video.videoHeight });\n return null;\n }\n\n const ctx = canvas.getContext('2d');\n if (!ctx) {\n return null;\n }\n\n // 设置画布尺寸\n canvas.width = video.videoWidth;\n canvas.height = video.videoHeight;\n\n // 清除画布\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n\n // 绘制当前帧\n ctx.drawImage(video, 0, 0, canvas.width, canvas.height);\n\n // 转换为Blob\n return new Promise((resolve) => {\n canvas.toBlob(resolve, 'image/jpeg', 0.8);\n });\n }, []);\n\n /**\n * 执行检测\n */\n const performDetection = useCallback(async (): Promise<void> => {\n if (!detectionEngineRef.current) {\n return;\n }\n\n // 节流控制 - 检查距离上次检测的时间是否足够\n const now = Date.now();\n const config = getConfigManager().getConfig();\n if (now - lastDetectionTimeRef.current < config.checkInterval) {\n return; // 还没到检测时间,直接返回\n }\n\n try {\n const imageData = await captureFrame();\n if (!imageData) {\n throw new Error('无法捕获图像');\n }\n\n const startTime = Date.now();\n const result = await detectionEngineRef.current.detect(imageData);\n const endTime = Date.now();\n\n // 更新最新结果\n setLatestResult(result);\n\n // 更新上次检测时间\n lastDetectionTimeRef.current = now;\n\n // 更新统计信息\n setStats(prevStats => {\n const newStats = { ...prevStats };\n newStats.checkCount++;\n newStats.latency = endTime - startTime;\n \n // 计算平均处理时间\n if (result.processingTime) {\n newStats.avgProcessingTime = \n (prevStats.avgProcessingTime * (prevStats.checkCount - 1) + result.processingTime) / \n prevStats.checkCount;\n }\n\n // 更新违规统计\n if (result.violations.length > 0) {\n newStats.abnormalCount++;\n result.violations.forEach(violation => {\n newStats.violationStats[violation.type] = \n (newStats.violationStats[violation.type] || 0) + 1;\n });\n }\n\n // 计算FPS\n const currentTime = Date.now();\n if (lastFrameTimeRef.current > 0) {\n const frameInterval = currentTime - lastFrameTimeRef.current;\n newStats.fps = Math.round(1000 / frameInterval);\n }\n lastFrameTimeRef.current = currentTime;\n\n // 评估网络质量(基于延迟)\n if (newStats.latency < 200) {\n newStats.networkQuality = 'excellent';\n } else if (newStats.latency < 500) {\n newStats.networkQuality = 'good';\n } else if (newStats.latency < 1000) {\n newStats.networkQuality = 'fair';\n } else {\n newStats.networkQuality = 'poor';\n }\n\n return newStats;\n });\n\n // 触发回调\n onDetectionResult?.(result);\n\n // 处理违规\n result.violations.forEach(violation => {\n onViolation?.(violation);\n });\n\n } catch (error) {\n const errorObj = error instanceof Error ? error : new Error('检测失败');\n onError?.(errorObj);\n }\n }, [captureFrame, onDetectionResult, onViolation, onError]);\n\n /**\n * 开始监控\n */\n const startMonitoring = useCallback(async (): Promise<void> => {\n try {\n // 获取摄像头权限\n await getCameraPermission();\n\n setIsMonitoring(true);\n } catch (error) {\n const errorObj = error instanceof Error ? error : new Error('启动监控失败');\n onError?.(errorObj);\n throw errorObj;\n }\n }, [getCameraPermission, onError]);\n\n /**\n * 停止监控\n */\n const stopMonitoring = useCallback((): void => {\n setIsMonitoring(false);\n\n // 停止所有轨道\n if (streamRef.current) {\n streamRef.current.getTracks().forEach(track => track.stop());\n streamRef.current = null;\n }\n\n // 清除视频源\n if (videoRef.current) {\n videoRef.current.srcObject = null;\n }\n\n // 清除定时器\n if (detectionTimerRef.current) {\n clearInterval(detectionTimerRef.current);\n detectionTimerRef.current = null;\n }\n }, []);\n\n /**\n * 强制检测\n */\n const forceCheck = useCallback(async (): Promise<void> => {\n await performDetection();\n }, [performDetection]);\n\n /**\n * 更新配置\n */\n const updateConfig = useCallback(async (newConfig: Partial<DetectionConfig>) => {\n if (!configManagerRef.current) {\n return { success: false, errors: ['配置管理器未初始化'] };\n }\n\n return configManagerRef.current.updateConfig(newConfig, {\n source: 'local',\n operator: 'hook-user'\n });\n }, []);\n\n /**\n * 获取当前配置\n */\n const getCurrentConfig = useCallback((): DetectionConfig => {\n const config = configManagerRef.current?.getConfig();\n \n // 如果配置管理器未初始化或返回空配置,使用默认值\n if (!config || !config.checkInterval) {\n return DEFAULT_DETECTION_CONFIG;\n }\n \n return config;\n }, []);\n\n /**\n * 获取配置管理器实例\n */\n const getConfigManager = useCallback((): ConfigManager => {\n if (!configManagerRef.current) {\n throw new Error('配置管理器未初始化');\n }\n return configManagerRef.current;\n }, []);\n\n // 清理资源\n useEffect(() => {\n return () => {\n stopMonitoring();\n };\n }, [stopMonitoring]);\n\n // 监控状态变化时启动或停止定时器\n useEffect(() => {\n if (isMonitoring) {\n // 启动循环\n detectionTimerRef.current = window.setInterval(performDetection, 1000); // 每秒检查一次是否准备就绪(内部有节流)\n } else {\n if (detectionTimerRef.current) {\n window.clearInterval(detectionTimerRef.current);\n detectionTimerRef.current = null;\n }\n }\n\n return () => {\n if (detectionTimerRef.current) {\n window.clearInterval(detectionTimerRef.current);\n }\n };\n }, [isMonitoring, performDetection]);\n\n // 组件卸载时清理资源\n useEffect(() => {\n return () => {\n stopMonitoring();\n };\n }, [stopMonitoring]);\n\n // 返回Hook接口\n return {\n videoRef,\n canvasRef,\n startMonitoring,\n stopMonitoring,\n forceCheck,\n isMonitoring,\n latestResult,\n stats,\n config: getCurrentConfig(),\n updateConfig,\n getConfigManager\n };\n}","/**\n * 反作弊监控库\n * \n * 基于阿里云视觉智能平台的实时反作弊监控系统\n * 提供人脸检测、异常行为分析和完整的在线监考解决方案\n * \n * @author Anti-Cheating Monitor Team\n * @version 2.0.0\n * @license MIT\n */\n\n// 导出主要Hook\nexport { useAntiCheatingMonitor } from './hooks';\nexport type { UseAntiCheatingMonitorProps, UseAntiCheatingMonitorReturn } from './hooks';\n\n// 导出类型定义\nexport * from './types';\n\n// 导出工具类\nexport { ConfigManager } from './utils';\nexport { DetectionEngine } from './utils';\nexport { AuditLogger } from './utils';\n\n// 导出常量\nexport { CheatingType, ViolationLevel, CHEATING_TYPE_LABELS, CHEATING_TYPE_SEVERITY } from './constants';\n\n// 版本信息\nexport const VERSION = '2.0.0';\n\n/**\n * 库信息\n */\nexport const LIB_INFO = {\n name: 'anti-cheating-monitor',\n version: VERSION,\n description: '基于阿里云视觉智能平台的实时反作弊监控系统',\n author: 'Anti-Cheating Monitor Team',\n license: 'MIT',\n repository: 'https://github.com/coding-daily-wq/anti-cheating-monitor.git',\n homepage: 'https://coding-daily-wq.github.io/anti-cheating-monitor'\n} as const;"],"names":["DEFAULT_DETECTION_CONFIG","checkInterval","detectMultiplePeople","detectDeviceUsage","detectAbsence","resolution","width","height","type","earphoneThreshold","cellphoneThreshold","headPoseThreshold","pitch","roll","yaw","faceCompletenessThreshold","centeringThreshold","movementThreshold","CheatingType","CHEATING_TYPE_LABELS","PERSON_COUNT_ANOMALY","EARPHONE_DETECTED","CELLPHONE_DETECTED","HEAD_POSTURE_ABNORMAL","FACE_MISSING","FACE_OCCLUDED","NO_FACE_DETECTED","MULTIPLE_FACES_DETECTED","NOT_CENTERED","SUSPICIOUS_MOVEMENT","ViolationLevel","CHEATING_TYPE_SEVERITY","ConfigManager","constructor","this","auditLogs","listeners","Set","STORAGE_KEY","config","getDefaultConfig","loadFromStorage","startHotUpdate","getInstance","instance","stored","localStorage","getItem","parsedConfig","JSON","parse","error","saveToStorage","setItem","stringify","window","addEventListener","e","key","newValue","newConfig","updateConfig","source","addAuditLog","operation","configKey","oldValue","operator","log","id","Date","now","Math","random","toString","substr","timestamp","push","length","slice","validateConfig","errors","getConfig","options","force","success","Object","entries","forEach","value","notifyListeners","resetConfig","oldConfig","keys","addListener","listener","add","delete","getAuditLogs","clearAuditLogs","olderThan","filter","destroy","syncTimer","clearInterval","clear","OSSClient","credentials","isInitialized","client","initialize","Promise","resolve","bucket","region","Error","accessKeyId","accessKeySecret","OSSModule","import","then","n","a","OSSClass","default","stsToken","securityToken","put","blob","dir","filename","objectKey","result","headers","url","message","ready","updateCredentials","DetectionEngine","ossConfig","ossClient","updateOSSConfig","detect","imageData","startTime","imageUrl","Blob","base64ToBlob","apiResponse","callVIAPI","processAPIResponse","processingTime","faceCount","faceCompleteness","personCount","violations","level","HIGH","confidence","description","base64","parts","split","mimeType","match","byteString","atob","arrayBuffer","ArrayBuffer","uint8Array","Uint8Array","i","charCodeAt","params","Action","Version","Format","AccessKeyId","SignatureMethod","Timestamp","getTimestamp","SignatureVersion","SignatureNonce","generateNonce","RegionId","Type","String","ImageURL","SecurityToken","canonicalizedQueryString","sort","map","percentEncode","join","stringToSign","signature","hmacSha1","bodyString","response","fetch","method","body","mode","ok","status","statusText","json","data","encoder","TextEncoder","keyData","encode","dataData","cryptoKey","crypto","subtle","importKey","name","hash","sign","btoa","fromCharCode","Array","from","str","encodeURIComponent","replace","toISOString","substring","faceInfo","Data","FaceInfo","personInfo","PersonInfo","faceNumber","FaceNumber","personNumber","PersonNumber","completeness","Completeness","pose","Pose","createViolation","MEDIUM","toFixed","Pitch","Roll","Yaw","pThresh","rThresh","yThresh","abs","max","earphoneScore","EarPhone","Score","cellphoneScore","CellPhone","centeringScore","calculateCenteringScore","LOW","rawResponse","min","getDetectionStats","totalDetections","averageProcessingTime","violationRate","mostCommonViolations","AuditLogger","logs","MAX_LOGS","addLog","auditLog","generateLogId","unshift","persistLogs","queryLogs","filteredLogs","endTime","offset","limit","getAllLogs","getStats","operationStats","create","update","sync","sourceStats","local","remote","backend_sync","configCounts","operatorCounts","mostActiveConfigs","b","count","mostActiveOperators","totalLogs","cleanup","beforeCount","cleanedCount","exportLogs","format","convertToCSV","importLogs","logsData","importedLogs","validLogs","validateLog","existingIds","newLogs","has","includes","rows","row","cell","isArray","getRecentLogs","getLogsByConfigKey","getLogsByOperator","useAntiCheatingMonitor","props","initialConfig","onViolation","onDetectionResult","onError","onConfigChange","isMonitoring","setIsMonitoring","useState","latestResult","setLatestResult","stats","setStats","checkCount","abnormalCount","latency","networkQuality","fps","avgProcessingTime","violationStats","fromEntries","values","videoRef","useRef","canvasRef","streamRef","detectionTimerRef","configManagerRef","detectionEngineRef","lastFrameTimeRef","lastDetectionTimeRef","current","useEffect","unsubscribe","getCameraPermission","useCallback","async","stream","navigator","mediaDevices","getUserMedia","video","facingMode","audio","srcObject","play","catch","captureFrame","canvas","readyState","videoWidth","videoHeight","ctx","getContext","clearRect","drawImage","toBlob","performDetection","getConfigManager","prevStats","newStats","violation","currentTime","frameInterval","round","errorObj","startMonitoring","stopMonitoring","getTracks","track","stop","forceCheck","getCurrentConfig","setInterval","VERSION","LIB_INFO","version","author","license","repository","homepage"],"mappings":"6EAgDO,MAAMA,EAA4C,CACvDC,cAAe,IACfC,sBAAsB,EACtBC,mBAAmB,EACnBC,eAAe,EACfC,WAAY,CACVC,MAAO,IACPC,OAAQ,KAEVC,KAAM,EAENC,kBAAmB,GAEnBC,mBAAoB,GACpBC,kBAAmB,CACjBC,MAAO,GACPC,KAAM,GACNC,IAAK,IAGPC,0BAA2B,GAE3BC,mBAAoB,GAEpBC,kBAAmB,ICpEd,IAAKC,kBAAAA,IAEVA,EAAA,qBAAuB,uBAEvBA,EAAA,kBAAoB,oBAEpBA,EAAA,mBAAqB,qBAErBA,EAAA,sBAAwB,wBAExBA,EAAA,aAAe,eAEfA,EAAA,cAAgB,gBAEhBA,EAAA,iBAAmB,mBAEnBA,EAAA,wBAA0B,0BAE1BA,EAAA,aAAe,eAEfA,EAAA,oBAAsB,sBApBZA,IAAAA,GAAA,CAAA,GA0BL,MAAMC,EAAqD,CAChEC,qBAAqC,OACrCC,kBAAkC,QAClCC,mBAAmC,QACnCC,sBAAsC,SACtCC,aAA6B,OAC7BC,cAA8B,QAC9BC,iBAAiC,SACjCC,wBAAwC,UACxCC,aAA6B,MAC7BC,oBAAoC,QAM/B,IAAKC,kBAAAA,IAEVA,EAAA,IAAM,MAENA,EAAA,OAAS,SAETA,EAAA,KAAO,OAEPA,EAAA,SAAW,WARDA,IAAAA,GAAA,CAAA,GAcL,MAAMC,EAA+D,CAC1EX,qBAAqC,OACrCC,kBAAkC,SAClCC,mBAAmC,OACnCC,sBAAsC,SACtCC,aAA6B,OAC7BC,cAA8B,SAC9BC,iBAAiC,OACjCC,wBAAwC,OACxCC,aAA6B,MAC7BC,oBAAoC,UC/D/B,MAAMG,cAQH,WAAAC,GALRC,KAAQC,UAA8B,GACtCD,KAAQE,6BAAwDC,IAEhEH,KAAiBI,YAAc,uBAG7BJ,KAAKK,OAASL,KAAKM,mBACnBN,KAAKO,kBACLP,KAAKQ,gBACP,CAKA,kBAAcC,GAIZ,OAHKX,cAAcY,WACjBZ,cAAcY,SAAW,IAAIZ,eAExBA,cAAcY,QACvB,CAKQ,gBAAAJ,GACN,MAAO,IAAKxC,EACd,CAKQ,eAAAyC,GACN,IACE,MAAMI,EAASC,aAAaC,QAAQb,KAAKI,aACzC,GAAIO,EAAQ,CACV,MAAMG,EAAeC,KAAKC,MAAML,GAChCX,KAAKK,OAAS,IAAKL,KAAKK,UAAWS,EACrC,CACF,OAASG,GAET,CACF,CAKQ,aAAAC,GACN,IACEN,aAAaO,QAAQnB,KAAKI,YAAaW,KAAKK,UAAUpB,KAAKK,QAC7D,OAASY,GAET,CACF,CAKQ,cAAAT,GAaNa,OAAOC,iBAAiB,UAXKC,IAC3B,GAAIA,EAAEC,MAAQxB,KAAKI,aAAemB,EAAEE,SAClC,IACE,MAAMC,EAAYX,KAAKC,MAAMO,EAAEE,UAC/BzB,KAAK2B,aAAaD,EAAW,CAAEE,OAAQ,UACzC,OAASX,GAET,GAKN,CAKQ,WAAAY,CACNC,EACAC,EACAC,EACAP,EACAG,EAAmC,QACnCK,GAEA,MAAMC,EAAsB,CAC1BC,GAAI,GAAGC,KAAKC,SAASC,KAAKC,SAASC,SAAS,IAAIC,OAAO,EAAG,KAC1DC,UAAWN,KAAKC,MAChBP,YACAC,YACAC,WACAP,WACAQ,WACAL,UAGF5B,KAAKC,UAAU0C,KAAKT,GAGhBlC,KAAKC,UAAU2C,OAAS,MAC1B5C,KAAKC,UAAYD,KAAKC,UAAU4C,OAAM,KAE1C,CAKQ,cAAAC,CAAezC,GACrB,MAAM0C,EAAmB,GA6BzB,YA3B6B,IAAzB1C,EAAOtC,gBACLsC,EAAOtC,cAAgB,KAAQsC,EAAOtC,cAAgB,MACxDgF,EAAOJ,KAAK,wBAIZtC,EAAOlC,cACLkC,EAAOlC,WAAWC,MAAQ,KAAOiC,EAAOlC,WAAWC,MAAQ,OAC7D2E,EAAOJ,KAAK,qBAEVtC,EAAOlC,WAAWE,OAAS,KAAOgC,EAAOlC,WAAWE,OAAS,OAC/D0E,EAAOJ,KAAK,0BAIiB,IAA7BtC,EAAO9B,oBACL8B,EAAO9B,kBAAoB,GAAK8B,EAAO9B,kBAAoB,IAC7DwE,EAAOJ,KAAK,sBAIkB,IAA9BtC,EAAO7B,qBACL6B,EAAO7B,mBAAqB,GAAK6B,EAAO7B,mBAAqB,IAC/DuE,EAAOJ,KAAK,iBAITI,CACT,CAKO,SAAAC,GACL,MAAO,IAAKhD,KAAKK,OACnB,CAKO,YAAAsB,CACLD,EACAuB,EAA+B,IAE/B,MAAMC,MAAEA,GAAQ,EAAAtB,OAAOA,EAAS,QAAAK,SAASA,GAAagB,EAGtD,IAAKC,EAAO,CACV,MAAMH,EAAS/C,KAAK8C,eAAepB,GACnC,GAAIqB,EAAOH,OAAS,EAClB,MAAO,CAAEO,SAAS,EAAOJ,SAE7B,CAqBA,OAlBAK,OAAOC,QAAQ3B,GAAW4B,QAAQ,EAAE9B,EAAK+B,MACvC,MAAMvB,EAAYhC,KAAKK,OAAemB,GAClCT,KAAKK,UAAUY,KAAcjB,KAAKK,UAAUmC,IAC9CvD,KAAK6B,YAAY,SAAUL,EAAKQ,EAAUuB,EAAO3B,EAAQK,KAK7DjC,KAAKK,OAAS,IAAKL,KAAKK,UAAWqB,GAGpB,UAAXE,GACF5B,KAAKkB,gBAIPlB,KAAKwD,kBAEE,CAAEL,SAAS,EACpB,CAKO,WAAAM,CAAY7B,EAAmC,QAASK,GAC7D,MAAMyB,EAAY,IAAK1D,KAAKK,QAC5BL,KAAKK,OAASL,KAAKM,mBAGnB8C,OAAOO,KAAKD,GAAWJ,QAAQ9B,IAC7BxB,KAAK6B,YAAY,SAAUL,EAAKkC,EAAUlC,GAA+BxB,KAAKK,OAAOmB,GAA+BI,EAAQK,KAG9HjC,KAAKkB,gBACLlB,KAAKwD,iBACP,CAKO,WAAAI,CAAYC,GAIjB,OAHA7D,KAAKE,UAAU4D,IAAID,GAGZ,KACL7D,KAAKE,UAAU6D,OAAOF,GAE1B,CAKQ,eAAAL,GACNxD,KAAKE,UAAUoD,QAAQO,IACrB,IACEA,EAAS7D,KAAKgD,YAChB,OAAS/B,GAET,GAEJ,CAKO,YAAA+C,GACL,MAAO,IAAIhE,KAAKC,UAClB,CAKO,cAAAgE,CAAeC,GAElBlE,KAAKC,UADHiE,EACelE,KAAKC,UAAUkE,OAAOjC,GAAOA,EAAIQ,UAAYwB,GAE7C,EAErB,CAKO,OAAAE,GACDpE,KAAKqE,WACPC,cAActE,KAAKqE,WAErBrE,KAAKE,UAAUqE,OACjB,EC9PK,MAAMC,UAMX,WAAAzE,CAAYM,EAAmBoE,GAH/BzE,KAAQ0E,eAAyB,EACjC1E,KAAQ2E,OAAqB,KAG3B3E,KAAKK,OAASA,EACdL,KAAKyE,YAAcA,CACrB,CAKA,gBAAaG,GACX,GAAI5E,KAAK0E,cACP,OAAOG,QAAQC,UAIjB,IAAK9E,KAAKK,OAAO0E,SAAW/E,KAAKK,OAAO2E,OACtC,MAAM,IAAIC,MAAM,4BAGlB,IAAKjF,KAAKyE,YAAYS,cAAgBlF,KAAKyE,YAAYU,gBACrD,MAAM,IAAIF,MAAM,0CAIlB,IAIE,MAAMG,QAAkBC,OAAO,oCAAoCC,KAAAC,GAAAA,EAAAC,GAC7DC,EAAYL,EAAUM,SAAWN,EAEvCpF,KAAK2E,OAAS,IAAIc,EAAS,CACzBT,OAAQhF,KAAKK,OAAO2E,OACpBE,YAAalF,KAAKyE,YAAYS,YAC9BC,gBAAiBnF,KAAKyE,YAAYU,gBAClCQ,SAAU3F,KAAKyE,YAAYmB,cAC3Bb,OAAQ/E,KAAKK,OAAO0E,SAGtB/E,KAAK0E,eAAgB,CACvB,OAASzD,GAEP,MAAM,IAAIgE,MAAM,gBAClB,CACF,CAQA,SAAaY,CAAIC,EAAYC,EAAc,YAKzC,GAJK/F,KAAK0E,qBACF1E,KAAK4E,cAGR5E,KAAK2E,OACR,MAAM,IAAIM,MAAM,cAIlB,MAAMe,EAAW,GAAG5D,KAAKC,SAASC,KAAKC,SAASC,SAAS,IAAIK,MAAM,SAC7DoD,EAAYF,EAAMC,EAExB,IAEE,MAAME,QAAelG,KAAK2E,OAAOkB,IAAII,EAAWH,EAAM,CACpDK,QAAS,CACP,gBAAiB,WACjB,sBAAuB,qBAAqBH,QAOhD,MAAO,CAAEI,IAFSF,EAAOE,KAAO,WAAWpG,KAAKK,OAAO0E,UAAU/E,KAAKK,OAAO2E,uBAAuBiB,IAItG,OAAShF,GACP,MAAM,IAAIgE,MAAM,iBAAiBhE,aAAiBgE,MAAQhE,EAAMoF,QAAU,SAC5E,CACF,CAOA,SAAWC,GACT,OAAOtG,KAAK0E,aACd,CAKO,YAAA/C,CAAatB,GAClBL,KAAKK,OAAS,IAAKL,KAAKK,UAAWA,GACnCL,KAAK0E,eAAgB,CACvB,CAKO,iBAAA6B,CAAkB9B,GACvBzE,KAAKyE,YAAc,IAAKzE,KAAKyE,eAAgBA,GAC7CzE,KAAK0E,eAAgB,CACvB,ECxGK,MAAM8B,gBAMX,WAAAzG,CAAYM,EAAyBoE,EAAsCgC,GACzEzG,KAAKK,OAASA,EACdL,KAAKyE,YAAcA,EACnBzE,KAAKyG,UAAYA,EACjBzG,KAAK0G,UAAY,IAAIlC,UAAUiC,EAAWhC,EAC5C,CAKO,YAAA9C,CAAatB,GAClBL,KAAKK,OAASA,CAChB,CAKO,iBAAAkG,CAAkB9B,GACvBzE,KAAKyE,YAAcA,EACnBzE,KAAK0G,UAAUH,kBAAkB9B,EACnC,CAKO,eAAAkC,CAAgBF,GACrBzG,KAAKyG,UAAYA,EACjBzG,KAAK0G,UAAU/E,aAAa8E,EAC9B,CAMA,YAAaG,CAAOC,GAClB,MAAMC,EAAY1E,KAAKC,MAEvB,IACE,IAAI0E,EAGJ,GAAIF,aAAqBG,KAAM,CAE7BD,SAD2B/G,KAAK0G,UAAUb,IAAIgB,IACtBT,GAC1B,KAAO,CAEL,MAAMN,EAAO9F,KAAKiH,aAAaJ,GAE/BE,SAD2B/G,KAAK0G,UAAUb,IAAIC,IACtBM,GAC1B,CAGA,MAAMc,QAAoBlH,KAAKmH,UAAUJ,GAGnCb,EAASlG,KAAKoH,mBAAmBF,GAKvC,OAFAhB,EAAOmB,eAAiBjF,KAAKC,MAAQyE,EAE9BZ,CACT,OAASjF,GAEP,MAAO,CACLyB,UAAWN,KAAKC,MAChBiF,UAAW,EACXC,iBAAkB,EAClBC,YAAa,EACbC,WAAY,CAAC,CACXnJ,KAAMU,EAAaQ,iBACnBkI,MAAO9H,EAAe+H,KACtBC,WAAY,EACZC,YAAa,SAAS5G,aAAiBgE,MAAQhE,EAAMoF,QAAU,WAEjEgB,eAAgBjF,KAAKC,MAAQyE,EAEjC,CACF,CAMQ,YAAAG,CAAaa,GACnB,MAAMC,EAAQD,EAAOE,MAAM,KACrBC,EAAWF,EAAM,GAAGG,MAAM,aAAa,IAAM,aAC7CC,EAAaC,KAAKL,EAAM,IACxBM,EAAc,IAAIC,YAAYH,EAAWvF,QACzC2F,EAAa,IAAIC,WAAWH,GAElC,IAAA,IAASI,EAAI,EAAGA,EAAIN,EAAWvF,OAAQ6F,IACrCF,EAAWE,GAAKN,EAAWO,WAAWD,GAGxC,OAAO,IAAIzB,KAAK,CAACqB,GAAc,CAAE/J,KAAM2J,GACzC,CAMA,eAAcd,CAAUJ,GAEtB,MAIM4B,EAAiC,CACrCC,OAAQ,qBACRC,QALkB,aAMlBC,OAAQ,OACRC,YAAa/I,KAAKyE,YAAYS,YAC9B8D,gBAAiB,YACjBC,UAAWjJ,KAAKkJ,eAChBC,iBAAkB,MAClBC,eAAgBpJ,KAAKqJ,gBACrBC,SAAU,cACVC,KAAMC,OAAOxJ,KAAKK,OAAO/B,MACzBmL,SAAU1C,GAIR/G,KAAKyE,YAAYmB,gBACnB+C,EAAOe,cAAgB1J,KAAKyE,YAAYmB,eAI1C,MAGM+D,EAHavG,OAAOO,KAAKgF,GAAQiB,OAIpCC,IAAKrI,GAAQ,GAAGxB,KAAK8J,cAActI,MAAQxB,KAAK8J,cAAcnB,EAAOnH,OACrEuI,KAAK,KAGFC,EAAe,QAAQhK,KAAK8J,cAAc,QAAQ9J,KAAK8J,cAC3DH,KAIIM,QAAkBjK,KAAKkK,SAC3B,GAAGlK,KAAKyE,YAAYU,mBACpB6E,GAIIG,EAAa,GAAGR,eAAsC3J,KAAK8J,cAC/DG,KAIIG,QAAiBC,MAhDF,4CAgDsB,CACzCC,OAAQ,OACRnE,QAAS,CACP,eAAgB,qCAElBoE,KAAMJ,EACNK,KAAM,SAGR,IAAKJ,EAASK,GACZ,MAAM,IAAIxF,MACR,cAAcmF,EAASM,UAAUN,EAASO,cAO9C,aAHmBP,EAASQ,MAI9B,CAMA,cAAcV,CAAS1I,EAAaqJ,GAClC,MAAMC,EAAU,IAAIC,YACdC,EAAUF,EAAQG,OAAOzJ,GACzB0J,EAAWJ,EAAQG,OAAOJ,GAE1BM,QAAkB9J,OAAO+J,OAAOC,OAAOC,UAC3C,MACAN,EACA,CAAEO,KAAM,OAAQC,KAAM,UACtB,EACA,CAAC,SAGGvB,QAAkB5I,OAAO+J,OAAOC,OAAOI,KAC3C,OACAN,EACAD,GAGF,OAAOQ,KAAKlC,OAAOmC,gBAAgBC,MAAMC,KAAK,IAAIrD,WAAWyB,KAC/D,CAMQ,aAAAH,CAAcgC,GACpB,OAAOC,mBAAmBD,GACvBE,QAAQ,KAAM,OACdA,QAAQ,KAAM,OACdA,QAAQ,MAAO,OACfA,QAAQ,MAAO,OACfA,QAAQ,MAAO,MACpB,CAMQ,YAAA9C;AACN,OAAA,IAAW9G,MAAO6J,cAAcD,QAAQ,UAAW,GACrD,CAMQ,aAAA3C,GACN,OACE/G,KAAKC,SAASC,SAAS,IAAI0J,UAAU,EAAG,IACxC5J,KAAKC,SAASC,SAAS,IAAI0J,UAAU,EAAG,GAE5C,CAMQ,kBAAA9E,CAAmBgD,GACzB,MAAM3C,EAAkC,GAGlC0E,EAAW/B,EAASgC,MAAMC,UAAmB,CAAA,EAC7CC,EAAalC,EAASgC,MAAMG,YAAqB,CAAA,EACjDC,EAAaL,EAASM,YAAc,EACpCC,EAAeJ,EAAWK,cAAgB,EAC1CC,EAAeT,EAASU,cAAgB,EACxCC,EAAOX,EAASY,MAAQ,CAAA,EAwC9B,GArCmB,IAAfP,EACF/E,EAAW9E,KAAK3C,KAAKgN,gBACnBhO,EAAaQ,iBACbI,EAAe+H,KACf,EACA,WAEO6E,EAAa,GACtB/E,EAAW9E,KAAK3C,KAAKgN,gBACnBhO,EAAaS,wBACbG,EAAe+H,KACf,EACA,MAAM6E,SAKNxM,KAAKK,OAAOrC,sBAAwB0O,EAAe,GACrDjF,EAAW9E,KAAK3C,KAAKgN,gBACnBhO,EAAaE,qBACbU,EAAe+H,KACf,EACA,MAAM+E,OAKNE,EAAe5M,KAAKK,OAAOxB,2BAC7B4I,EAAW9E,KAAK3C,KAAKgN,gBACnBhO,EAAaO,cACbK,EAAeqN,OACf,EAAML,EACN,SAAwB,IAAfA,GAAoBM,QAAQ,QAKrCJ,EAAM,CACR,MAAMK,MAAEA,EAAAC,KAAOA,EAAAC,IAAMA,GAAQP,GACrBpO,MAAO4O,EAAS3O,KAAM4O,EAAS3O,IAAK4O,GAAYxN,KAAKK,OAAO5B,mBAEhE6D,KAAKmL,IAAIN,GAASG,GAAWhL,KAAKmL,IAAIL,GAAQG,GAAWjL,KAAKmL,IAAIJ,GAAOG,IAC3E/F,EAAW9E,KAAK3C,KAAKgN,gBACnBhO,EAAaK,sBACbO,EAAeqN,OACf3K,KAAKoL,IACHpL,KAAKmL,IAAIN,GAASG,EAClBhL,KAAKmL,IAAIL,GAAQG,EACjBjL,KAAKmL,IAAIJ,GAAOG,GACd,EACJ,WAAWL,EAAQ,EAAI,KAAO,OAAOA,EAAMD,QAAQ,SAASE,EAAKF,QAAQ,OAAOG,EAAM,EAAI,KAAO,OAAOA,EAAIH,QAAQ,OAG1H,CAGA,GAAIlN,KAAKK,OAAOpC,kBAAmB,CAEjC,MAAM0P,EAAgBrB,EAAWsB,UAAUC,OAAS,EAChDF,EAAgB3N,KAAKK,OAAO9B,mBAC9BkJ,EAAW9E,KAAK3C,KAAKgN,gBACnBhO,EAAaG,kBACbS,EAAeqN,OACfU,EACA,gBAAgC,IAAhBA,GAAqBT,QAAQ,SAKjD,MAAMY,EAAiBxB,EAAWyB,WAAWF,OAAS,EAClDC,EAAiB9N,KAAKK,OAAO7B,oBAC/BiJ,EAAW9E,KAAK3C,KAAKgN,gBACnBhO,EAAaI,mBACbQ,EAAe+H,KACfmG,EACA,gBAAiC,IAAjBA,GAAsBZ,QAAQ,QAGpD,CAGA,GAAIJ,EAAM,CACR,MAAMkB,EAAiBhO,KAAKiO,wBAAwBnB,GAChDkB,EAAiBhO,KAAKK,OAAOvB,oBAC/B2I,EAAW9E,KAAK3C,KAAKgN,gBACnBhO,EAAaU,aACbE,EAAesO,IACf,EAAMF,EACN,cAA+B,IAAjBA,GAAsBd,QAAQ,QAGlD,CAEA,MAAO,CACLxK,UAAWN,KAAKC,MAChBiF,UAAWkF,EACXjF,iBAAkBqF,EAClBpF,YAAakF,EACbI,OACArF,aACA0G,YAAa/D,EAEjB,CAMQ,eAAA4C,CACN1O,EACAoJ,EACAE,EACAC,EACAgD,GAEA,MAAO,CACLvM,OACAoJ,QACAE,WAAYtF,KAAKoL,IAAI,EAAGpL,KAAK8L,IAAI,EAAGxG,IACpCC,cACAgD,OAEJ,CAMQ,uBAAAoD,CAAwBnB,GAC9B,MAAMK,MAAEA,EAAAC,KAAOA,EAAAC,IAAMA,GAAQP,EAO7B,OAJmBxK,KAAKoL,IAAI,EAAG,EAAIpL,KAAKmL,IAAIN,GAAS,IACnC7K,KAAKoL,IAAI,EAAG,EAAIpL,KAAKmL,IAAIL,GAAQ,IAClC9K,KAAKoL,IAAI,EAAG,EAAIpL,KAAKmL,IAAIJ,GAAO,KAEJ,CAC/C,CAKO,iBAAAgB,GAQL,MAAO,CACLC,gBAAiB,EACjBC,sBAAuB,EACvBC,cAAe,EACfC,qBAAsB,GAE1B,EClaK,MAAMC,YAAN,WAAA3O,GACLC,KAAQ2O,KAAyB,GACjC3O,KAAiB4O,SAAW,GAAA,CAKrB,MAAAC,CAAO3M,GACZ,MAAM4M,EAA2B,IAC5B5M,EACHC,GAAInC,KAAK+O,gBACTrM,UAAWN,KAAKC,OAalB,OAVArC,KAAK2O,KAAKK,QAAQF,GAGd9O,KAAK2O,KAAK/L,OAAS5C,KAAK4O,WAC1B5O,KAAK2O,KAAO3O,KAAK2O,KAAK9L,MAAM,EAAG7C,KAAK4O,WAItC5O,KAAKiP,cAEEH,CACT,CAKQ,aAAAC,GACN,MAAO,GAAG3M,KAAKC,SAASC,KAAKC,SAASC,SAAS,IAAIC,OAAO,EAAG,IAC/D,CAKO,SAAAyM,CAAUjM,EAAgC,IAC/C,IAAIkM,EAAe,IAAInP,KAAK2O,WAGF,IAAtB1L,EAAQ6D,YACVqI,EAAeA,EAAahL,OAAOjC,GAAOA,EAAIQ,WAAaO,EAAQ6D,iBAE7C,IAApB7D,EAAQmM,UACVD,EAAeA,EAAahL,OAAOjC,GAAOA,EAAIQ,WAAaO,EAAQmM,UAIjEnM,EAAQnB,YACVqN,EAAeA,EAAahL,OAAOjC,GAAOA,EAAIJ,YAAcmB,EAAQnB,YAIlEmB,EAAQlB,YACVoN,EAAeA,EAAahL,OAAOjC,GAAOA,EAAIH,YAAckB,EAAQlB,YAIlEkB,EAAQhB,WACVkN,EAAeA,EAAahL,OAAOjC,GAAOA,EAAID,WAAagB,EAAQhB,WAIjEgB,EAAQrB,SACVuN,EAAeA,EAAahL,OAAOjC,GAAOA,EAAIN,SAAWqB,EAAQrB,SAInE,MAAMyN,EAASpM,EAAQoM,QAAU,EAC3BC,EAAQrM,EAAQqM,OAAS,GAG/B,OAFAH,EAAeA,EAAatM,MAAMwM,EAAQA,EAASC,GAE5CH,CACT,CAKO,UAAAI,GACL,MAAO,IAAIvP,KAAK2O,KAClB,CAKO,QAAAa,GACL,MAAMC,EAA8D,CAClEC,OAAQ,EACRC,OAAQ,EACR5L,OAAQ,EACR6L,KAAM,GAGFC,EAAwD,CAC5DC,MAAO,EACPC,OAAQ,EACRC,aAAc,GAGVC,EAAuC,CAAA,EACvCC,EAAyC,CAAA,EAE/ClQ,KAAK2O,KAAKrL,QAAQpB,IAEhBuN,EAAevN,EAAIJ,aAGnB+N,EAAY3N,EAAIN,UAGhBqO,EAAa/N,EAAIH,YAAckO,EAAa/N,EAAIH,YAAc,GAAK,EAG/DG,EAAID,WACNiO,EAAehO,EAAID,WAAaiO,EAAehO,EAAID,WAAa,GAAK,KAKzE,MAAMkO,EAAoB/M,OAAOC,QAAQ4M,GACtCrG,KAAK,EAAC,CAAGpE,KAAO4K,KAAOA,EAAI5K,GAC3B3C,MAAM,EAAG,GACTgH,IAAI,EAAE9H,EAAWsO,MAAK,CAAStO,YAAWsO,WAGvCC,EAAsBlN,OAAOC,QAAQ6M,GACxCtG,KAAK,EAAC,CAAGpE,KAAO4K,KAAOA,EAAI5K,GAC3B3C,MAAM,EAAG,GACTgH,IAAI,EAAE5H,EAAUoO,MAAK,CAASpO,WAAUoO,WAE3C,MAAO,CACLE,UAAWvQ,KAAK2O,KAAK/L,OACrB6M,iBACAI,cACAM,oBACAG,sBAEJ,CAKO,OAAAE,CAAQtM,GACb,MAAMuM,EAAczQ,KAAK2O,KAAK/L,OAC9B5C,KAAK2O,KAAO3O,KAAK2O,KAAKxK,OAAOjC,GAAOA,EAAIQ,UAAYwB,GACpD,MAAMwM,EAAeD,EAAczQ,KAAK2O,KAAK/L,OAM7C,OAJI8N,EAAe,GACjB1Q,KAAKiP,cAGAyB,CACT,CAKO,KAAAnM,GACLvE,KAAK2O,KAAO,GACZ3O,KAAKiP,aACP,CAKO,UAAA0B,CAAWC,EAAyB,QACzC,GAAe,SAAXA,EACF,OAAO7P,KAAKK,UAAUpB,KAAK2O,KAAM,KAAM,GACzC,GAAsB,QAAXiC,EACT,OAAO5Q,KAAK6Q,aAAa7Q,KAAK2O,MAE9B,MAAM,IAAI1J,MAAM,aAAa2L,IAEjC,CAKO,UAAAE,CAAWC,EAAkBH,EAAyB,QAC3D,IAAII,EAEJ,GAAe,SAAXJ,EAOF,MAAM,IAAI3L,MAAM,aAAa2L,KAN7B,IACEI,EAAejQ,KAAKC,MAAM+P,EAC5B,OAAS9P,GACP,MAAM,IAAIgE,MAAM,WAClB,CAMF,MAAMgM,EAAYD,EAAa7M,UAAcnE,KAAKkR,YAAYhP,IAGxDiP,EAAc,IAAIhR,IAAIH,KAAK2O,KAAK9E,IAAI3H,GAAOA,EAAIC,KAC/CiP,EAAUH,EAAU9M,OAAOjC,IAAQiP,EAAYE,IAAInP,EAAIC,KAW7D,OATAnC,KAAK2O,KAAO,IAAIyC,KAAYpR,KAAK2O,MAG7B3O,KAAK2O,KAAK/L,OAAS5C,KAAK4O,WAC1B5O,KAAK2O,KAAO3O,KAAK2O,KAAK9L,MAAM,EAAG7C,KAAK4O,WAGtC5O,KAAKiP,cAEEmC,EAAQxO,MACjB,CAKQ,WAAAsO,CAAYhP,GAClB,MACiB,iBAARA,GACW,iBAAXA,EAAIC,IACc,iBAAlBD,EAAIQ,WACX,CAAC,SAAU,SAAU,SAAU,QAAQ4O,SAASpP,EAAIJ,YAC3B,iBAAlBI,EAAIH,WACX,CAAC,QAAS,SAAU,gBAAgBuP,SAASpP,EAAIN,OAErD,CAKQ,YAAAiP,CAAalC,GACnB,MAKM4C,EAAO5C,EAAK9E,IAAI3H,GAAO,CAC3BA,EAAIC,GACJ,IAAIC,KAAKF,EAAIQ,WAAWuJ,cACxB/J,EAAIJ,UACJI,EAAIH,UACJhB,KAAKK,UAAUc,EAAIF,UAAY,IAC/BjB,KAAKK,UAAUc,EAAIT,UAAY,IAC/BS,EAAID,UAAY,GAChBC,EAAIN,SAQN,MALmB,CAhBH,CACd,KAAM,YAAa,YAAa,aAAc,YAC9C,YAAa,WAAY,UAejBmI,KAAK,QACVwH,EAAK1H,IAAI2H,GAAOA,EAAI3H,IAAI4H,GAAQ,IAAIA,MAAS1H,KAAK,OACrDA,KAAK,KAGT,CAKQ,WAAAkF,GACN,IACErO,aAAaO,QAAQ,2BAA4BJ,KAAKK,UAAUpB,KAAK2O,MACvE,OAAS1N,GAET,CACF,CAKO,eAAAV,GACL,IACE,MAAMI,EAASC,aAAaC,QAAQ,4BACpC,GAAIF,EAAQ,CACV,MAAMgO,EAAO5N,KAAKC,MAAML,GACpBiL,MAAM8F,QAAQ/C,KAChB3O,KAAK2O,KAAOA,EAAKxK,UAAcnE,KAAKkR,YAAYhP,IAEpD,CACF,OAASjB,GAEPjB,KAAK2O,KAAO,EACd,CACF,CAKO,aAAAgD,CAActB,EAAgB,IACnC,OAAOrQ,KAAK2O,KAAK9L,MAAM,EAAGwN,EAC5B,CAKO,kBAAAuB,CAAmB7P,GACxB,OAAO/B,KAAK2O,KAAKxK,OAAOjC,GAAOA,EAAIH,YAAcA,EACnD,CAKO,iBAAA8P,CAAkB5P,GACvB,OAAOjC,KAAK2O,KAAKxK,OAAOjC,GAAOA,EAAID,WAAaA,EAClD,EC3MK,SAAS6P,uBAAuBC,GACrC,MAAMtN,YACJA,EAAAgC,UACAA,EACApG,OAAQ2R,EAAAC,YACRA,EAAAC,kBACAA,EAAAC,QACAA,EAAAC,eACAA,GACEL,GAGGM,EAAcC,GAAmBC,GAAS,IAC1CC,EAAcC,GAAmBF,EAAiC,OAClEG,EAAOC,GAAYJ,EAAuB,CAC/CK,WAAY,EACZC,cAAe,EACfC,QAAS,EACTC,eAAgB,YAChBC,IAAK,EACLC,kBAAmB,EACnBC,eAAgB9P,OAAO+P,YACrB/P,OAAOgQ,OAAOpU,GAAc6K,IAAIvL,GAAQ,CAACA,EAAM,OAK7C+U,EAAWC,EAAgC,MAC3CC,EAAYD,EAAiC,MAC7CE,EAAYF,EAA2B,MACvCG,EAAoBH,EAAsB,MAC1CI,EAAmBJ,EAA6B,MAChDK,EAAqBL,EAA+B,MACpDM,EAAmBN,EAAe,GAClCO,EAAuBP,EAAe,GAGvCI,EAAiBI,UACpBJ,EAAiBI,QAAUhU,cAAcW,cAGrCuR,GACF0B,EAAiBI,QAAQnS,aAAaqQ,EAAe,CACnDpQ,OAAQ,QACRK,SAAU,yBAMhB8R,EAAU,KACR,IAAKL,EAAiBI,QACpB,MAAO,OAIT,MAAME,EAAcN,EAAiBI,QAAQlQ,YAAalC,IACpDiS,EAAmBG,SACrBH,EAAmBG,QAAQnS,aAAaD,GAE1C0Q,IAAiB1Q,KAGnB,MAAO,KACLsS,MAED,CAAChC,EAAeI,IAGnB2B,EAAU,KACR,MAAM1T,EAASqT,EAAiBI,SAAS9Q,YACrC3C,GAAUoE,GAAegC,IAC3BkN,EAAmBG,QAAU,IAAItN,gBAAgBnG,EAAQoE,EAAagC,KAEvE,CAAChC,EAAagC,IAKjB,MAAMwN,EAAsBC,EAAYC,UACtC,IACE,MAAM9T,EAASqT,EAAiBI,SAAS9Q,YACnCoR,QAAeC,UAAUC,aAAaC,aAAa,CACvDC,MAAO,CACLpW,MAAOiC,GAAQlC,WAAWC,OAAS,IACnCC,OAAQgC,GAAQlC,WAAWE,QAAU,IACrCoW,WAAY,QAEdC,OAAO,IAaT,OAVAlB,EAAUM,QAAUM,EAEhBf,EAASS,UACXT,EAASS,QAAQa,UAAYP,EAE7Bf,EAASS,QACNc,OACAC,MAAOtT,QAGL6S,CACT,OAASnT,GACP,MAAM,IAAIgE,MAAM,cAAchE,aAAiBgE,MAAQhE,EAAMoF,QAAU,SACzE,GACC,IAKGyO,EAAeZ,EAAYC,UAC/B,MAAMK,EAAQnB,EAASS,QACjBiB,EAASxB,EAAUO,QAEzB,IAAKU,IAAUO,EACb,OAAO,KAIT,GAAIP,EAAMQ,WAAa,EAErB,OAAO,KAIT,GAAyB,IAArBR,EAAMS,YAA0C,IAAtBT,EAAMU,YAElC,OAAO,KAGT,MAAMC,EAAMJ,EAAOK,WAAW,MAC9B,OAAKD,GAKLJ,EAAO3W,MAAQoW,EAAMS,WACrBF,EAAO1W,OAASmW,EAAMU,YAGtBC,EAAIE,UAAU,EAAG,EAAGN,EAAO3W,MAAO2W,EAAO1W,QAGzC8W,EAAIG,UAAUd,EAAO,EAAG,EAAGO,EAAO3W,MAAO2W,EAAO1W,QAGzC,IAAIwG,QAASC,IAClBiQ,EAAOQ,OAAOzQ,EAAS,aAAc,OAf9B,MAiBR,IAKG0Q,EAAmBtB,EAAYC,UACnC,IAAKR,EAAmBG,QACtB,OAIF,MAAMzR,EAAMD,KAAKC,MACXhC,EAASoV,IAAmBzS,YAClC,KAAIX,EAAMwR,EAAqBC,QAAUzT,EAAOtC,eAIhD,IACE,MAAM8I,QAAkBiO,IACxB,IAAKjO,EACH,MAAM,IAAI5B,MAAM,UAGlB,MAAM6B,EAAY1E,KAAKC,MACjB6D,QAAeyN,EAAmBG,QAAQlN,OAAOC,GACjDuI,EAAUhN,KAAKC,MAGrBoQ,EAAgBvM,GAGhB2N,EAAqBC,QAAUzR,EAG/BsQ,EAAS+C,IACP,MAAMC,EAAW,IAAKD,GACtBC,EAAS/C,aACT+C,EAAS7C,QAAU1D,EAAUtI,EAGzBZ,EAAOmB,iBACTsO,EAAS1C,mBACNyC,EAAUzC,mBAAqByC,EAAU9C,WAAa,GAAK1M,EAAOmB,gBACnEqO,EAAU9C,YAIV1M,EAAOuB,WAAW7E,OAAS,IAC7B+S,EAAS9C,gBACT3M,EAAOuB,WAAWnE,QAAQsS,IACxBD,EAASzC,eAAe0C,EAAUtX,OAC/BqX,EAASzC,eAAe0C,EAAUtX,OAAS,GAAK,KAKvD,MAAMuX,EAAczT,KAAKC,MACzB,GAAIuR,EAAiBE,QAAU,EAAG,CAChC,MAAMgC,EAAgBD,EAAcjC,EAAiBE,QACrD6B,EAAS3C,IAAM1Q,KAAKyT,MAAM,IAAOD,EACnC,CAcA,OAbAlC,EAAiBE,QAAU+B,EAGvBF,EAAS7C,QAAU,IACrB6C,EAAS5C,eAAiB,YACjB4C,EAAS7C,QAAU,IAC5B6C,EAAS5C,eAAiB,OACjB4C,EAAS7C,QAAU,IAC5B6C,EAAS5C,eAAiB,OAE1B4C,EAAS5C,eAAiB,OAGrB4C,IAITzD,IAAoBhM,GAGpBA,EAAOuB,WAAWnE,QAAQsS,IACxB3D,IAAc2D,IAGlB,OAAS3U,GACP,MAAM+U,EAAW/U,aAAiBgE,MAAQhE,EAAQ,IAAIgE,MAAM,QAC5DkN,IAAU6D,EACZ,GACC,CAAClB,EAAc5C,EAAmBD,EAAaE,IAK5C8D,EAAkB/B,EAAYC,UAClC,UAEQF,IAEN3B,GAAgB,EAClB,OAASrR,GACP,MAAM+U,EAAW/U,aAAiBgE,MAAQhE,EAAQ,IAAIgE,MAAM,UAE5D,MADAkN,IAAU6D,GACJA,CACR,GACC,CAAC/B,EAAqB9B,IAKnB+D,EAAiBhC,EAAY,KACjC5B,GAAgB,GAGZkB,EAAUM,UACZN,EAAUM,QAAQqC,YAAY7S,QAAQ8S,GAASA,EAAMC,QACrD7C,EAAUM,QAAU,MAIlBT,EAASS,UACXT,EAASS,QAAQa,UAAY,MAI3BlB,EAAkBK,UACpBxP,cAAcmP,EAAkBK,SAChCL,EAAkBK,QAAU,OAE7B,IAKGwC,EAAapC,EAAYC,gBACvBqB,KACL,CAACA,IAKE7T,EAAeuS,EAAYC,MAAOzS,GACjCgS,EAAiBI,QAIfJ,EAAiBI,QAAQnS,aAAaD,EAAW,CACtDE,OAAQ,QACRK,SAAU,cALH,CAAEkB,SAAS,EAAOJ,OAAQ,CAAC,cAOnC,IAKGwT,EAAmBrC,EAAY,KACnC,MAAM7T,EAASqT,EAAiBI,SAAS9Q,YAGzC,OAAK3C,GAAWA,EAAOtC,cAIhBsC,EAHEvC,GAIR,IAKG2X,EAAmBvB,EAAY,KACnC,IAAKR,EAAiBI,QACpB,MAAM,IAAI7O,MAAM,aAElB,OAAOyO,EAAiBI,SACvB,IAoCH,OAjCAC,EAAU,IACD,KACLmC,KAED,CAACA,IAGJnC,EAAU,KACJ1B,EAEFoB,EAAkBK,QAAUzS,OAAOmV,YAAYhB,EAAkB,KAE7D/B,EAAkBK,UACpBzS,OAAOiD,cAAcmP,EAAkBK,SACvCL,EAAkBK,QAAU,MAIzB,KACDL,EAAkBK,SACpBzS,OAAOiD,cAAcmP,EAAkBK,WAG1C,CAACzB,EAAcmD,IAGlBzB,EAAU,IACD,KACLmC,KAED,CAACA,IAGG,CACL7C,WACAE,YACA0C,kBACAC,iBACAI,aACAjE,eACAG,eACAE,QACArS,OAAQkW,IACR5U,eACA8T,mBAEJ,CCjcO,MAAMgB,EAAU,QAKVC,EAAW,CACtBnL,KAAM,wBACNoL,QAASF,EACT5O,YAAa,wBACb+O,OAAQ,6BACRC,QAAS,MACTC,WAAY,+DACZC,SAAU"}
|
|
1
|
+
{"version":3,"file":"index.es.js","sources":["../src/types/config.ts","../src/constants/cheating-types.ts","../src/utils/config-manager.ts","../src/utils/oss-client.ts","../src/utils/detection-engine.ts","../src/utils/audit-logger.ts","../src/hooks/useAntiCheatingMonitor.ts","../src/index.ts"],"sourcesContent":["/**\n * 检测配置接口\n */\nexport interface DetectionConfig {\n /** 检测间隔(毫秒) */\n checkInterval: number;\n /** 是否检测多人 */\n detectMultiplePeople: boolean;\n /** 是否检测设备使用 */\n detectDeviceUsage: boolean;\n /** 是否检测离席 */\n detectAbsence: boolean;\n /** 视频分辨率 */\n resolution: {\n /** 视频宽度 */\n width: number;\n /** 视频高度 */\n height: number;\n };\n /** 检测类型\n * 0: 屏幕聊天工具检测\n * 1: 考生状态检测\n */\n type: number;\n /** 耳机检测阈值 */\n earphoneThreshold: number;\n /** 手机检测阈值 */\n cellphoneThreshold: number;\n /** 头部姿态阈值 */\n headPoseThreshold: {\n /** 俯仰角阈值 */\n pitch: number;\n /** 翻滚角阈值 */\n roll: number;\n /** 偏航角阈值 */\n yaw: number;\n };\n /** 人脸完整度阈值 */\n faceCompletenessThreshold: number;\n /** 人脸居中阈值 */\n centeringThreshold: number;\n /** 可疑移动检测阈值 */\n movementThreshold: number;\n}\n\n/**\n * 默认检测配置\n */\nexport const DEFAULT_DETECTION_CONFIG: DetectionConfig = {\n checkInterval: 3000,\n detectMultiplePeople: true,\n detectDeviceUsage: true,\n detectAbsence: true,\n resolution: {\n width: 640,\n height: 480,\n },\n type: 1,\n /** 耳机检测阈值 */\n earphoneThreshold: 0.6,\n /** 手机检测阈值 */\n cellphoneThreshold: 0.6,\n headPoseThreshold: {\n pitch: 30, // 俯仰角 ±30度\n roll: 30, // 翻滚角 ±30度\n yaw: 30, // 偏航角 ±30度\n },\n /** 人脸完整度阈值 */\n faceCompletenessThreshold: 0.8,\n /** 人脸居中阈值 */\n centeringThreshold: 0.7,\n /** 可疑移动检测阈值 */\n movementThreshold: 0.3,\n};\n\n/**\n * 阿里云OSS配置接口\n */\nexport interface OSSConfig {\n /** OSS存储桶名称 */\n bucket: string;\n /** OSS区域 */\n region: string;\n /** 访问域名(可选) */\n endpoint?: string;\n /** 安全令牌(可选,用于STS临时访问) */\n securityToken?: string;\n}\n\n/**\n * 配置更新选项\n */\nexport interface ConfigUpdateOptions {\n /** 是否强制更新(忽略验证) */\n force?: boolean;\n /** 更新来源 */\n source?: \"local\" | \"remote\" | \"backend_sync\";\n /** 操作者标识 */\n operator?: string;\n}\n","/**\n * 作弊类型枚举\n * 基于Java后端逻辑映射的前端检测类型\n */\nexport enum CheatingType {\n /** 人数异常 - 检测到多人 */\n PERSON_COUNT_ANOMALY = \"PERSON_COUNT_ANOMALY\",\n /** 检测到耳机 */\n EARPHONE_DETECTED = \"EARPHONE_DETECTED\", \n /** 检测到手机 */\n CELLPHONE_DETECTED = \"CELLPHONE_DETECTED\",\n /** 头部姿态异常 */\n HEAD_POSTURE_ABNORMAL = \"HEAD_POSTURE_ABNORMAL\",\n /** 人脸缺失 */\n FACE_MISSING = \"FACE_MISSING\",\n /** 人脸被遮挡 */\n FACE_OCCLUDED = \"FACE_OCCLUDED\",\n /** 未检测到人脸 */\n NO_FACE_DETECTED = \"NO_FACE_DETECTED\",\n /** 检测到多张人脸 */\n MULTIPLE_FACES_DETECTED = \"MULTIPLE_FACES_DETECTED\",\n /** 未居中 */\n NOT_CENTERED = \"NOT_CENTERED\",\n /** 可疑移动 */\n SUSPICIOUS_MOVEMENT = \"SUSPICIOUS_MOVEMENT\"\n}\n\n/**\n * 作弊类型显示名称映射\n */\nexport const CHEATING_TYPE_LABELS: Record<CheatingType, string> = {\n [CheatingType.PERSON_COUNT_ANOMALY]: \"人数异常\",\n [CheatingType.EARPHONE_DETECTED]: \"检测到耳机\",\n [CheatingType.CELLPHONE_DETECTED]: \"检测到手机\", \n [CheatingType.HEAD_POSTURE_ABNORMAL]: \"头部姿态异常\",\n [CheatingType.FACE_MISSING]: \"人脸缺失\",\n [CheatingType.FACE_OCCLUDED]: \"人脸被遮挡\",\n [CheatingType.NO_FACE_DETECTED]: \"未检测到人脸\",\n [CheatingType.MULTIPLE_FACES_DETECTED]: \"检测到多张人脸\",\n [CheatingType.NOT_CENTERED]: \"未居中\",\n [CheatingType.SUSPICIOUS_MOVEMENT]: \"可疑移动\"\n} as const;\n\n/**\n * 违规严重程度\n */\nexport enum ViolationLevel {\n /** 低风险 - 轻微违规 */\n LOW = \"low\",\n /** 中风险 - 需要注意 */\n MEDIUM = \"medium\", \n /** 高风险 - 严重违规 */\n HIGH = \"high\",\n /** 紧急 - 需要立即处理 */\n CRITICAL = \"critical\"\n}\n\n/**\n * 作弊类型严重程度映射\n */\nexport const CHEATING_TYPE_SEVERITY: Record<CheatingType, ViolationLevel> = {\n [CheatingType.PERSON_COUNT_ANOMALY]: ViolationLevel.HIGH,\n [CheatingType.EARPHONE_DETECTED]: ViolationLevel.MEDIUM,\n [CheatingType.CELLPHONE_DETECTED]: ViolationLevel.HIGH,\n [CheatingType.HEAD_POSTURE_ABNORMAL]: ViolationLevel.MEDIUM,\n [CheatingType.FACE_MISSING]: ViolationLevel.HIGH,\n [CheatingType.FACE_OCCLUDED]: ViolationLevel.MEDIUM,\n [CheatingType.NO_FACE_DETECTED]: ViolationLevel.HIGH,\n [CheatingType.MULTIPLE_FACES_DETECTED]: ViolationLevel.HIGH,\n [CheatingType.NOT_CENTERED]: ViolationLevel.LOW,\n [CheatingType.SUSPICIOUS_MOVEMENT]: ViolationLevel.MEDIUM\n} as const;","import { DetectionConfig, ConfigUpdateOptions, DEFAULT_DETECTION_CONFIG } from '../types';\nimport { ConfigAuditLog } from '../types';\n\n/**\n * 配置管理器类\n * 负责阈值配置的管理、热更新和审计\n */\nexport class ConfigManager {\n private static instance: ConfigManager;\n private config: DetectionConfig;\n private auditLogs: ConfigAuditLog[] = [];\n private listeners: Set<(config: DetectionConfig) => void> = new Set();\n private syncTimer?: number;\n private readonly STORAGE_KEY = 'anti-cheating-config';\n\n private constructor() {\n this.config = this.getDefaultConfig();\n this.loadFromStorage();\n this.startHotUpdate();\n }\n\n /**\n * 获取单例实例\n */\n public static getInstance(): ConfigManager {\n if (!ConfigManager.instance) {\n ConfigManager.instance = new ConfigManager();\n }\n return ConfigManager.instance;\n }\n\n /**\n * 获取默认配置\n */\n private getDefaultConfig(): DetectionConfig {\n return { ...DEFAULT_DETECTION_CONFIG };\n }\n\n /**\n * 从本地存储加载配置\n */\n private loadFromStorage(): void {\n try {\n const stored = localStorage.getItem(this.STORAGE_KEY);\n if (stored) {\n const parsedConfig = JSON.parse(stored);\n this.config = { ...this.config, ...parsedConfig };\n }\n } catch (error) {\n console.warn('加载本地配置失败:', error);\n }\n }\n\n /**\n * 保存配置到本地存储\n */\n private saveToStorage(): void {\n try {\n localStorage.setItem(this.STORAGE_KEY, JSON.stringify(this.config));\n } catch (error) {\n console.warn('保存本地配置失败:', error);\n }\n }\n\n /**\n * 启动热更新监听\n */\n private startHotUpdate(): void {\n // 监听storage事件,实现跨标签页配置同步\n const handleStorageChange = (e: StorageEvent) => {\n if (e.key === this.STORAGE_KEY && e.newValue) {\n try {\n const newConfig = JSON.parse(e.newValue);\n this.updateConfig(newConfig, { source: 'remote' });\n } catch (error) {\n console.warn('热更新配置失败:', error);\n }\n }\n };\n\n window.addEventListener('storage', handleStorageChange);\n }\n\n /**\n * 添加审计日志\n */\n private addAuditLog(\n operation: ConfigAuditLog['operation'],\n configKey: string,\n oldValue?: any,\n newValue?: any,\n source: ConfigAuditLog['source'] = 'local',\n operator?: string\n ): void {\n const log: ConfigAuditLog = {\n id: `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,\n timestamp: Date.now(),\n operation,\n configKey,\n oldValue,\n newValue,\n operator,\n source\n };\n\n this.auditLogs.push(log);\n \n // 限制日志数量,避免内存泄漏\n if (this.auditLogs.length > 1000) {\n this.auditLogs = this.auditLogs.slice(-500);\n }\n }\n\n /**\n * 验证配置有效性\n */\n private validateConfig(config: Partial<DetectionConfig>): string[] {\n const errors: string[] = [];\n\n if (config.checkInterval !== undefined) {\n if (config.checkInterval < 1000 || config.checkInterval > 60000) {\n errors.push('检测间隔应在1000-60000毫秒之间');\n }\n }\n\n if (config.resolution) {\n if (config.resolution.width < 320 || config.resolution.width > 1920) {\n errors.push('视频宽度应在320-1920之间');\n }\n if (config.resolution.height < 240 || config.resolution.height > 1080) {\n errors.push('视频高度应在240-1080之间');\n }\n }\n\n if (config.earphoneThreshold !== undefined) {\n if (config.earphoneThreshold < 0 || config.earphoneThreshold > 1) {\n errors.push('耳机检测阈值应在0-1之间');\n }\n }\n\n if (config.cellphoneThreshold !== undefined) {\n if (config.cellphoneThreshold < 0 || config.cellphoneThreshold > 1) {\n errors.push('手机检测阈值应在0-1之间');\n }\n }\n\n return errors;\n }\n\n /**\n * 获取当前配置\n */\n public getConfig(): DetectionConfig {\n return { ...this.config };\n }\n\n /**\n * 更新配置\n */\n public updateConfig(\n newConfig: Partial<DetectionConfig>, \n options: ConfigUpdateOptions = {}\n ): { success: boolean; errors?: string[] } {\n const { force = false, source = 'local', operator } = options;\n\n // 验证配置\n if (!force) {\n const errors = this.validateConfig(newConfig);\n if (errors.length > 0) {\n return { success: false, errors };\n }\n }\n\n // 记录变更\n Object.entries(newConfig).forEach(([key, value]) => {\n const oldValue = (this.config as any)[key];\n if (JSON.stringify(oldValue) !== JSON.stringify(value)) {\n this.addAuditLog('update', key, oldValue, value, source, operator);\n }\n });\n\n // 更新配置\n this.config = { ...this.config, ...newConfig };\n\n // 保存到本地存储\n if (source === 'local') {\n this.saveToStorage();\n }\n\n // 通知监听器\n this.notifyListeners();\n\n return { success: true };\n }\n\n /**\n * 重置配置为默认值\n */\n public resetConfig(source: ConfigAuditLog['source'] = 'local', operator?: string): void {\n const oldConfig = { ...this.config };\n this.config = this.getDefaultConfig();\n\n // 记录重置操作\n Object.keys(oldConfig).forEach(key => {\n this.addAuditLog('update', key, oldConfig[key as keyof DetectionConfig], this.config[key as keyof DetectionConfig], source, operator);\n });\n\n this.saveToStorage();\n this.notifyListeners();\n }\n\n /**\n * 添加配置变更监听器\n */\n public addListener(listener: (config: DetectionConfig) => void): () => void {\n this.listeners.add(listener);\n \n // 返回取消监听的函数\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n /**\n * 通知所有监听器\n */\n private notifyListeners(): void {\n this.listeners.forEach(listener => {\n try {\n listener(this.getConfig());\n } catch (error) {\n console.warn('配置监听器执行失败:', error);\n }\n });\n }\n\n /**\n * 获取审计日志\n */\n public getAuditLogs(): ConfigAuditLog[] {\n return [...this.auditLogs];\n }\n\n /**\n * 清理审计日志\n */\n public clearAuditLogs(olderThan?: number): void {\n if (olderThan) {\n this.auditLogs = this.auditLogs.filter(log => log.timestamp > olderThan);\n } else {\n this.auditLogs = [];\n }\n }\n\n /**\n * 销毁实例\n */\n public destroy(): void {\n if (this.syncTimer) {\n clearInterval(this.syncTimer);\n }\n this.listeners.clear();\n }\n}","import { OSSConfig } from '../types/config';\nimport { AntiCheatingCredentials } from '../types/detection';\nimport type OSS from 'ali-oss';\n\n/**\n * OSS客户端类\n * 用于上传图片到阿里云OSS\n */\nexport class OSSClient {\n private config: OSSConfig;\n private credentials: AntiCheatingCredentials;\n private isInitialized: boolean = false;\n private client: OSS | null = null;\n\n constructor(config: OSSConfig, credentials: AntiCheatingCredentials) {\n this.config = config;\n this.credentials = credentials;\n }\n\n /**\n * 初始化OSS客户端\n */\n public async initialize(): Promise<void> {\n if (this.isInitialized) {\n return Promise.resolve();\n }\n\n // 验证配置\n if (!this.config.bucket || !this.config.region) {\n throw new Error('OSS配置不完整:缺少bucket或region');\n }\n\n if (!this.credentials.accessKeyId || !this.credentials.accessKeySecret) {\n throw new Error('OSS凭证不完整:缺少accessKeyId或accessKeySecret');\n }\n\n // 初始化阿里云OSS客户端\n try {\n // 动态导入 ali-oss,避免在 SSR 阶段加载导致 exports is not defined 错误\n // 同时也避免了 Node.js 依赖 (proxy-agent) 的问题\n // @ts-ignore\n const OSSModule = await import('ali-oss/dist/aliyun-oss-sdk.min.js');\n const OSSClass = (OSSModule.default || OSSModule) as unknown as typeof OSS;\n\n this.client = new OSSClass({\n region: this.config.region,\n accessKeyId: this.credentials.accessKeyId,\n accessKeySecret: this.credentials.accessKeySecret,\n stsToken: this.credentials.securityToken,\n bucket: this.config.bucket,\n });\n\n this.isInitialized = true;\n } catch (error) {\n console.error('Failed to load ali-oss:', error);\n throw new Error('无法加载 OSS 客户端库');\n }\n }\n\n /**\n * 上传文件到OSS\n * @param blob 文件数据\n * @param dir 存储目录\n * @returns 文件URL\n */\n public async put(blob: Blob, dir: string = 'monitor/'): Promise<{ url: string }> {\n if (!this.isInitialized) {\n await this.initialize();\n }\n\n if (!this.client) {\n throw new Error('OSS客户端未初始化');\n }\n\n // 生成文件名\n const filename = `${Date.now()}-${Math.random().toString(36).slice(2)}.jpg`;\n const objectKey = dir + filename;\n\n try {\n // 使用阿里云OSS SDK进行真实上传\n const result = await this.client.put(objectKey, blob, {\n headers: {\n 'Cache-Control': 'no-cache',\n 'Content-Disposition': `inline; filename=\"${filename}\"`,\n },\n });\n\n // 确保返回的是公开访问的URL\n const publicUrl = result.url || `https://${this.config.bucket}.${this.config.region}.aliyuncs.com/${objectKey}`;\n \n return { url: publicUrl };\n\n } catch (error) {\n throw new Error(`阿里云 OSS 上传失败: ${error instanceof Error ? error.message : '未知错误'}`);\n }\n }\n\n \n\n /**\n * 检查客户端是否已初始化\n */\n public get ready(): boolean {\n return this.isInitialized;\n }\n\n /**\n * 更新配置\n */\n public updateConfig(config: Partial<OSSConfig>): void {\n this.config = { ...this.config, ...config };\n this.isInitialized = false; // 需要重新初始化\n }\n\n /**\n * 更新凭证\n */\n public updateCredentials(credentials: Partial<AntiCheatingCredentials>): void {\n this.credentials = { ...this.credentials, ...credentials };\n this.isInitialized = false; // 需要重新初始化\n }\n}","import { \n DetectionResult, \n DetectionConfig, \n VIAPIRequestParams, \n VIAPIResponse,\n CheatingViolation,\n AntiCheatingCredentials\n} from '../types';\nimport { CheatingType, ViolationLevel, CHEATING_TYPE_SEVERITY, CHEATING_TYPE_LABELS } from '../constants';\nimport { OSSConfig } from '../types/config';\nimport { OSSClient } from './oss-client';\n\n/**\n * 检测引擎类\n * 负责执行反作弊检测逻辑,集成OSS上传和阿里云VIAPI调用\n */\nexport class DetectionEngine {\n private config: DetectionConfig;\n private credentials: AntiCheatingCredentials;\n private ossConfig: OSSConfig;\n private ossClient: OSSClient;\n\n constructor(config: DetectionConfig, credentials: AntiCheatingCredentials, ossConfig: OSSConfig) {\n this.config = config;\n this.credentials = credentials;\n this.ossConfig = ossConfig;\n this.ossClient = new OSSClient(ossConfig, credentials);\n }\n\n /**\n * 更新配置\n */\n public updateConfig(config: DetectionConfig): void {\n this.config = config;\n }\n\n /**\n * 更新凭证\n */\n public updateCredentials(credentials: AntiCheatingCredentials): void {\n this.credentials = credentials;\n this.ossClient.updateCredentials(credentials);\n }\n\n /**\n * 更新OSS配置\n */\n public updateOSSConfig(ossConfig: OSSConfig): void {\n this.ossConfig = ossConfig;\n this.ossClient.updateConfig(ossConfig);\n }\n\n /**\n * 执行检测\n * @param imageData 图像数据(Base64或Blob)\n */\n public async detect(imageData: string | Blob): Promise<DetectionResult> {\n const startTime = Date.now();\n const imgBlob: Blob = imageData instanceof Blob ? imageData : this.base64ToBlob(imageData);\n\n try {\n let imageUrl: string;\n\n const uploadResult = await this.ossClient.put(imgBlob);\n imageUrl = uploadResult.url;\n\n // 调用阿里云VIAPI\n const apiResponse = await this.callVIAPI(imageUrl);\n \n // 处理检测结果\n const result = this.processAPIResponse(apiResponse, imgBlob);\n \n // 计算处理时间\n result.processingTime = Date.now() - startTime;\n \n return result;\n } catch (error) {\n // 返回错误结果\n return {\n timestamp: Date.now(),\n faceCount: 0,\n faceCompleteness: 0,\n personCount: 0,\n violations: [{\n type: CheatingType.NO_FACE_DETECTED,\n level: ViolationLevel.HIGH,\n confidence: 1.0,\n description: `检测失败: ${error instanceof Error ? error.message : '未知错误'}`\n }],\n imageData: imgBlob,\n processingTime: Date.now() - startTime\n };\n }\n }\n\n /**\n * Base64转Blob\n * @private\n */\n private base64ToBlob(base64: string): Blob {\n const parts = base64.split(',');\n const mimeType = parts[0].match(/:(.*?);/)?.[1] || 'image/jpeg';\n const byteString = atob(parts[1]);\n const arrayBuffer = new ArrayBuffer(byteString.length);\n const uint8Array = new Uint8Array(arrayBuffer);\n \n for (let i = 0; i < byteString.length; i++) {\n uint8Array[i] = byteString.charCodeAt(i);\n }\n \n return new Blob([arrayBuffer], { type: mimeType });\n }\n\n /**\n * 调用阿里云VIAPI\n * @private\n */\n private async callVIAPI(imageUrl: string): Promise<VIAPIResponse> {\n // 阿里云API常量\n const API_ENDPOINT = \"https://facebody.cn-shanghai.aliyuncs.com\";\n const API_VERSION = \"2019-12-30\";\n\n // 构建请求参数\n const params: Record<string, string> = {\n Action: \"MonitorExamination\",\n Version: API_VERSION,\n Format: \"JSON\",\n AccessKeyId: this.credentials.accessKeyId,\n SignatureMethod: \"HMAC-SHA1\",\n Timestamp: this.getTimestamp(),\n SignatureVersion: \"1.0\",\n SignatureNonce: this.generateNonce(),\n RegionId: \"cn-shanghai\",\n Type: String(this.config.type),\n ImageURL: imageUrl,\n };\n\n // 如果有安全令牌,添加到参数中\n if (this.credentials.securityToken) {\n params.SecurityToken = this.credentials.securityToken;\n }\n\n // 1. 对参数进行排序\n const sortedKeys = Object.keys(params).sort();\n\n // 2. 构建规范化的查询字符串\n const canonicalizedQueryString = sortedKeys\n .map((key) => `${this.percentEncode(key)}=${this.percentEncode(params[key])}`)\n .join(\"&\");\n\n // 3. 构建待签名字符串\n const stringToSign = `POST&${this.percentEncode(\"/\")}&${this.percentEncode(\n canonicalizedQueryString\n )}`;\n\n // 4. 计算签名\n const signature = await this.hmacSha1(\n `${this.credentials.accessKeySecret}&`,\n stringToSign\n );\n\n // 5. 构建 Body 内容 (POST请求)\n const bodyString = `${canonicalizedQueryString}&Signature=${this.percentEncode(\n signature\n )}`;\n\n // 发送请求\n const response = await fetch(API_ENDPOINT, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n },\n body: bodyString,\n mode: \"cors\",\n });\n\n if (!response.ok) {\n throw new Error(\n `API Error: ${response.status} ${response.statusText}`\n );\n }\n\n const data = await response.json();\n\n // 直接返回API响应数据,已经是标准格式\n return data as VIAPIResponse;\n }\n\n /**\n * 使用HMAC-SHA1算法生成签名\n * @private\n */\n private async hmacSha1(key: string, data: string): Promise<string> {\n const encoder = new TextEncoder();\n const keyData = encoder.encode(key);\n const dataData = encoder.encode(data);\n\n const cryptoKey = await window.crypto.subtle.importKey(\n \"raw\",\n keyData,\n { name: \"HMAC\", hash: \"SHA-1\" },\n false,\n [\"sign\"]\n );\n\n const signature = await window.crypto.subtle.sign(\n \"HMAC\",\n cryptoKey,\n dataData\n );\n \n return btoa(String.fromCharCode(...Array.from(new Uint8Array(signature))));\n }\n\n /**\n * URL编码函数\n * @private\n */\n private percentEncode(str: string): string {\n return encodeURIComponent(str)\n .replace(/!/g, \"%21\")\n .replace(/'/g, \"%27\")\n .replace(/\\(/g, \"%28\")\n .replace(/\\)/g, \"%29\")\n .replace(/\\*/g, \"%2A\");\n }\n\n /**\n * 获取当前时间戳\n * @private\n */\n private getTimestamp(): string {\n return new Date().toISOString().replace(/\\.\\d{3}/, \"\");\n }\n\n /**\n * 生成随机字符串\n * @private\n */\n private generateNonce(): string {\n return (\n Math.random().toString(36).substring(2, 15) +\n Math.random().toString(36).substring(2, 15)\n );\n }\n\n /**\n * 处理API响应,生成检测结果\n * @private\n */\n private processAPIResponse(response: VIAPIResponse, imageData: Blob): DetectionResult {\n const violations: CheatingViolation[] = [];\n\n // 从正确的数据路径获取信息\n const faceInfo = response.Data?.FaceInfo as any || {};\n const personInfo = response.Data?.PersonInfo as any || {};\n const faceNumber = faceInfo.FaceNumber || 0;\n const personNumber = personInfo.PersonNumber || 0;\n const completeness = faceInfo.Completeness || 0;\n const pose = faceInfo.Pose || {};\n\n // 1. 检测人脸数量\n if (faceNumber === 0) {\n violations.push(this.createViolation(\n CheatingType.NO_FACE_DETECTED,\n ViolationLevel.HIGH,\n 1.0,\n '未检测到人脸'\n ));\n } else if (faceNumber > 1) {\n violations.push(this.createViolation(\n CheatingType.MULTIPLE_FACES_DETECTED,\n ViolationLevel.HIGH,\n 1.0,\n `检测到${faceNumber}张人脸`\n ));\n }\n\n // 2. 检测人员数量\n if (this.config.detectMultiplePeople && personNumber > 1) {\n violations.push(this.createViolation(\n CheatingType.PERSON_COUNT_ANOMALY,\n ViolationLevel.HIGH,\n 1.0,\n `检测到${personNumber}人`\n ));\n }\n\n // 3. 检测人脸完整度\n if (completeness < this.config.faceCompletenessThreshold) {\n violations.push(this.createViolation(\n CheatingType.FACE_OCCLUDED,\n ViolationLevel.MEDIUM,\n 1.0 - completeness,\n `人脸完整度${(completeness * 100).toFixed(0)}%`\n ));\n }\n\n // 4. 检测头部姿态\n if (pose) {\n const { Pitch, Roll, Yaw } = pose;\n const { pitch: pThresh, roll: rThresh, yaw: yThresh } = this.config.headPoseThreshold;\n\n if (Math.abs(Pitch) > pThresh || Math.abs(Roll) > rThresh || Math.abs(Yaw) > yThresh) {\n violations.push(this.createViolation(\n CheatingType.HEAD_POSTURE_ABNORMAL,\n ViolationLevel.MEDIUM,\n Math.max(\n Math.abs(Pitch) / pThresh,\n Math.abs(Roll) / rThresh,\n Math.abs(Yaw) / yThresh\n ) - 1,\n `头部姿态异常: ${Pitch > 0 ? '抬头' : '低头'}${Pitch.toFixed(1)}° 偏头${Roll.toFixed(1)}° ${Yaw > 0 ? '左转' : '右转'}${Yaw.toFixed(1)}°`\n ));\n }\n }\n\n // 5. 检测设备使用\n if (this.config.detectDeviceUsage) {\n // 耳机检测\n const earphoneScore = personInfo.EarPhone?.Score || 0;\n if (earphoneScore > this.config.earphoneThreshold) {\n violations.push(this.createViolation(\n CheatingType.EARPHONE_DETECTED,\n ViolationLevel.MEDIUM,\n earphoneScore,\n `检测到耳机 (置信度: ${(earphoneScore * 100).toFixed(0)}%)`\n ));\n }\n\n // 手机检测\n const cellphoneScore = personInfo.CellPhone?.Score || 0;\n if (cellphoneScore > this.config.cellphoneThreshold) {\n violations.push(this.createViolation(\n CheatingType.CELLPHONE_DETECTED,\n ViolationLevel.HIGH,\n cellphoneScore,\n `检测到手机 (置信度: ${(cellphoneScore * 100).toFixed(0)}%)`\n ));\n }\n }\n\n // 6. 检测居中程度(基于姿态数据估算)\n if (pose) {\n const centeringScore = this.calculateCenteringScore(pose);\n if (centeringScore < this.config.centeringThreshold) {\n violations.push(this.createViolation(\n CheatingType.NOT_CENTERED,\n ViolationLevel.LOW,\n 1.0 - centeringScore,\n `未居中 (居中度: ${(centeringScore * 100).toFixed(0)}%)`\n ));\n }\n }\n\n return {\n timestamp: Date.now(),\n faceCount: faceNumber,\n faceCompleteness: completeness,\n personCount: personNumber,\n pose: pose,\n violations,\n rawResponse: response,\n imageData\n };\n }\n\n /**\n * 创建违规对象\n * @private\n */\n private createViolation(\n type: CheatingType,\n level: ViolationLevel,\n confidence: number,\n description: string,\n data?: any\n ): CheatingViolation {\n return {\n type,\n level,\n confidence: Math.max(0, Math.min(1, confidence)),\n description,\n data\n };\n }\n\n /**\n * 计算居中程度\n * @private\n */\n private calculateCenteringScore(pose: { Pitch: number; Roll: number; Yaw: number }): number {\n const { Pitch, Roll, Yaw } = pose;\n \n // 基于姿态角度计算居中程度\n const pitchScore = Math.max(0, 1 - Math.abs(Pitch) / 30);\n const rollScore = Math.max(0, 1 - Math.abs(Roll) / 45);\n const yawScore = Math.max(0, 1 - Math.abs(Yaw) / 60);\n \n return (pitchScore + rollScore + yawScore) / 3;\n }\n\n /**\n * 获取检测统计信息\n */\n public getDetectionStats(): {\n totalDetections: number;\n averageProcessingTime: number;\n violationRate: number;\n mostCommonViolations: Array<{ type: CheatingType; count: number }>;\n } {\n // 这里应该从历史记录中计算统计信息\n // 为了简化,返回模拟数据\n return {\n totalDetections: 0,\n averageProcessingTime: 0,\n violationRate: 0,\n mostCommonViolations: []\n };\n }\n}","import { ConfigAuditLog, AuditLogQueryOptions, AuditLogStats } from '../types';\n\n/**\n * 审计日志管理器类\n * 负责管理和分析配置变更审计日志\n */\nexport class AuditLogger {\n private logs: ConfigAuditLog[] = [];\n private readonly MAX_LOGS = 1000; // 最大日志数量限制\n\n /**\n * 添加审计日志\n */\n public addLog(log: Omit<ConfigAuditLog, 'id' | 'timestamp'>): ConfigAuditLog {\n const auditLog: ConfigAuditLog = {\n ...log,\n id: this.generateLogId(),\n timestamp: Date.now()\n };\n\n this.logs.unshift(auditLog); // 新日志添加到开头\n\n // 限制日志数量\n if (this.logs.length > this.MAX_LOGS) {\n this.logs = this.logs.slice(0, this.MAX_LOGS);\n }\n\n // 持久化到本地存储\n this.persistLogs();\n\n return auditLog;\n }\n\n /**\n * 生成日志ID\n */\n private generateLogId(): string {\n return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;\n }\n\n /**\n * 查询审计日志\n */\n public queryLogs(options: AuditLogQueryOptions = {}): ConfigAuditLog[] {\n let filteredLogs = [...this.logs];\n\n // 时间范围过滤\n if (options.startTime !== undefined) {\n filteredLogs = filteredLogs.filter(log => log.timestamp >= options.startTime!);\n }\n if (options.endTime !== undefined) {\n filteredLogs = filteredLogs.filter(log => log.timestamp <= options.endTime!);\n }\n\n // 操作类型过滤\n if (options.operation) {\n filteredLogs = filteredLogs.filter(log => log.operation === options.operation);\n }\n\n // 配置项过滤\n if (options.configKey) {\n filteredLogs = filteredLogs.filter(log => log.configKey === options.configKey);\n }\n\n // 操作者过滤\n if (options.operator) {\n filteredLogs = filteredLogs.filter(log => log.operator === options.operator);\n }\n\n // 来源过滤\n if (options.source) {\n filteredLogs = filteredLogs.filter(log => log.source === options.source);\n }\n\n // 分页\n const offset = options.offset || 0;\n const limit = options.limit || 50;\n filteredLogs = filteredLogs.slice(offset, offset + limit);\n\n return filteredLogs;\n }\n\n /**\n * 获取所有日志\n */\n public getAllLogs(): ConfigAuditLog[] {\n return [...this.logs];\n }\n\n /**\n * 获取日志统计信息\n */\n public getStats(): AuditLogStats {\n const operationStats: Record<ConfigAuditLog['operation'], number> = {\n create: 0,\n update: 0,\n delete: 0,\n sync: 0\n };\n\n const sourceStats: Record<ConfigAuditLog['source'], number> = {\n local: 0,\n remote: 0,\n backend_sync: 0\n };\n\n const configCounts: Record<string, number> = {};\n const operatorCounts: Record<string, number> = {};\n\n this.logs.forEach(log => {\n // 操作类型统计\n operationStats[log.operation]++;\n\n // 来源统计\n sourceStats[log.source]++;\n\n // 配置项统计\n configCounts[log.configKey] = (configCounts[log.configKey] || 0) + 1;\n\n // 操作者统计\n if (log.operator) {\n operatorCounts[log.operator] = (operatorCounts[log.operator] || 0) + 1;\n }\n });\n\n // 最活跃的配置项(前5个)\n const mostActiveConfigs = Object.entries(configCounts)\n .sort(([, a], [, b]) => b - a)\n .slice(0, 5)\n .map(([configKey, count]) => ({ configKey, count }));\n\n // 最活跃的操作者(前5个)\n const mostActiveOperators = Object.entries(operatorCounts)\n .sort(([, a], [, b]) => b - a)\n .slice(0, 5)\n .map(([operator, count]) => ({ operator, count }));\n\n return {\n totalLogs: this.logs.length,\n operationStats,\n sourceStats,\n mostActiveConfigs,\n mostActiveOperators\n };\n }\n\n /**\n * 清理旧日志\n */\n public cleanup(olderThan: number): number {\n const beforeCount = this.logs.length;\n this.logs = this.logs.filter(log => log.timestamp > olderThan);\n const cleanedCount = beforeCount - this.logs.length;\n\n if (cleanedCount > 0) {\n this.persistLogs();\n }\n\n return cleanedCount;\n }\n\n /**\n * 清空所有日志\n */\n public clear(): void {\n this.logs = [];\n this.persistLogs();\n }\n\n /**\n * 导出日志为JSON\n */\n public exportLogs(format: 'json' | 'csv' = 'json'): string {\n if (format === 'json') {\n return JSON.stringify(this.logs, null, 2);\n } else if (format === 'csv') {\n return this.convertToCSV(this.logs);\n } else {\n throw new Error(`不支持的导出格式: ${format}`);\n }\n }\n\n /**\n * 导入日志\n */\n public importLogs(logsData: string, format: 'json' | 'csv' = 'json'): number {\n let importedLogs: ConfigAuditLog[];\n\n if (format === 'json') {\n try {\n importedLogs = JSON.parse(logsData);\n } catch (error) {\n throw new Error('JSON格式错误');\n }\n } else {\n throw new Error(`不支持的导入格式: ${format}`);\n }\n\n // 验证日志格式\n const validLogs = importedLogs.filter(log => this.validateLog(log));\n\n // 合并日志(避免重复)\n const existingIds = new Set(this.logs.map(log => log.id));\n const newLogs = validLogs.filter(log => !existingIds.has(log.id));\n\n this.logs = [...newLogs, ...this.logs];\n\n // 限制日志数量\n if (this.logs.length > this.MAX_LOGS) {\n this.logs = this.logs.slice(0, this.MAX_LOGS);\n }\n\n this.persistLogs();\n\n return newLogs.length;\n }\n\n /**\n * 验证日志格式\n */\n private validateLog(log: any): log is ConfigAuditLog {\n return (\n typeof log === 'object' &&\n typeof log.id === 'string' &&\n typeof log.timestamp === 'number' &&\n ['create', 'update', 'delete', 'sync'].includes(log.operation) &&\n typeof log.configKey === 'string' &&\n ['local', 'remote', 'backend_sync'].includes(log.source)\n );\n }\n\n /**\n * 转换为CSV格式\n */\n private convertToCSV(logs: ConfigAuditLog[]): string {\n const headers = [\n 'ID', 'Timestamp', 'Operation', 'Config Key', 'Old Value', \n 'New Value', 'Operator', 'Source'\n ];\n\n const rows = logs.map(log => [\n log.id,\n new Date(log.timestamp).toISOString(),\n log.operation,\n log.configKey,\n JSON.stringify(log.oldValue || ''),\n JSON.stringify(log.newValue || ''),\n log.operator || '',\n log.source\n ]);\n\n const csvContent = [\n headers.join(','),\n ...rows.map(row => row.map(cell => `\"${cell}\"`).join(','))\n ].join('\\n');\n\n return csvContent;\n }\n\n /**\n * 持久化日志到本地存储\n */\n private persistLogs(): void {\n try {\n localStorage.setItem('anti-cheating-audit-logs', JSON.stringify(this.logs));\n } catch (error) {\n console.warn('保存审计日志失败:', error);\n }\n }\n\n /**\n * 从本地存储加载日志\n */\n public loadFromStorage(): void {\n try {\n const stored = localStorage.getItem('anti-cheating-audit-logs');\n if (stored) {\n const logs = JSON.parse(stored);\n if (Array.isArray(logs)) {\n this.logs = logs.filter(log => this.validateLog(log));\n }\n }\n } catch (error) {\n console.warn('加载审计日志失败:', error);\n this.logs = [];\n }\n }\n\n /**\n * 获取最近的日志\n */\n public getRecentLogs(count: number = 10): ConfigAuditLog[] {\n return this.logs.slice(0, count);\n }\n\n /**\n * 根据配置键获取日志\n */\n public getLogsByConfigKey(configKey: string): ConfigAuditLog[] {\n return this.logs.filter(log => log.configKey === configKey);\n }\n\n /**\n * 根据操作者获取日志\n */\n public getLogsByOperator(operator: string): ConfigAuditLog[] {\n return this.logs.filter(log => log.operator === operator);\n }\n}","import { useState, useEffect, useRef, useCallback } from \"react\";\nimport {\n DetectionConfig,\n DetectionResult,\n MonitorStats,\n AntiCheatingCredentials,\n CheatingViolation\n} from '../types';\nimport { DEFAULT_DETECTION_CONFIG, OSSConfig } from '../types/config';\nimport { CheatingType } from '../constants';\nimport { ConfigManager } from '../utils';\nimport { DetectionEngine } from '../utils';\n\n/**\n * Hook参数接口\n */\nexport interface UseAntiCheatingMonitorProps {\n /** 阿里云访问凭证 */\n credentials: AntiCheatingCredentials;\n /** OSS配置 */\n ossConfig: OSSConfig;\n /** 检测配置 */\n config?: Partial<DetectionConfig>;\n /** 违规检测回调函数 */\n onViolation?: (violation: CheatingViolation) => void;\n /** 检测结果回调函数 */\n onDetectionResult?: (result: DetectionResult) => void;\n /** 错误回调函数 */\n onError?: (error: Error) => void;\n /** 配置变更回调函数 */\n onConfigChange?: (config: DetectionConfig) => void;\n}\n\n/**\n * Hook返回值接口\n */\nexport interface UseAntiCheatingMonitorReturn {\n /** 视频元素引用 */\n videoRef: React.RefObject<HTMLVideoElement | null>;\n /** 画布元素引用 */\n canvasRef: React.RefObject<HTMLCanvasElement | null>;\n /** 开始监控 */\n startMonitoring: () => Promise<void>;\n /** 停止监控 */\n stopMonitoring: () => void;\n /** 强制检测一次 */\n forceCheck: () => Promise<void>;\n /** 是否正在监控 */\n isMonitoring: boolean;\n /** 最新检测结果 */\n latestResult: DetectionResult | null;\n /** 监控统计信息 */\n stats: MonitorStats;\n /** 当前配置 */\n config: DetectionConfig;\n /** 更新配置 */\n updateConfig: (newConfig: Partial<DetectionConfig>) => Promise<{ success: boolean; errors?: string[] }>;\n /** 获取配置管理器实例 */\n getConfigManager: () => ConfigManager;\n}\n\n/**\n * 反作弊监控Hook\n * 集成阿里云OSS上传和VIAPI人脸检测服务\n * \n * @param props Hook参数\n * @returns Hook返回值\n * \n * @example\n * ```tsx\n * const {\n * videoRef,\n * startMonitoring,\n * stopMonitoring,\n * isMonitoring,\n * latestResult,\n * stats,\n * config,\n * updateConfig\n * } = useAntiCheatingMonitor({\n * credentials: {\n * accessKeyId: 'your-access-key-id',\n * accessKeySecret: 'your-access-key-secret'\n * },\n * ossConfig: {\n * bucket: 'your-bucket-name',\n * region: 'oss-cn-hangzhou'\n * },\n * config: {\n * checkInterval: 3000,\n * detectMultiplePeople: true\n * },\n * onViolation: (violation) => {\n * console.log('检测到违规:', violation);\n * },\n * onDetectionResult: (result) => {\n * console.log('检测结果:', result);\n * },\n * onError: (error) => {\n * console.error('监控错误:', error);\n * }\n * });\n * ```\n */\nexport function useAntiCheatingMonitor(props: UseAntiCheatingMonitorProps): UseAntiCheatingMonitorReturn {\n const {\n credentials,\n ossConfig,\n config: initialConfig,\n onViolation,\n onDetectionResult,\n onError,\n onConfigChange\n } = props;\n\n // 状态管理\n const [isMonitoring, setIsMonitoring] = useState(false);\n const [latestResult, setLatestResult] = useState<DetectionResult | null>(null);\n const [stats, setStats] = useState<MonitorStats>({\n checkCount: 0,\n abnormalCount: 0,\n latency: 0,\n networkQuality: 'excellent',\n fps: 0,\n avgProcessingTime: 0,\n violationStats: Object.fromEntries(\n Object.values(CheatingType).map(type => [type, 0])\n ) as Record<CheatingType, number>\n });\n\n // 引用管理\n const videoRef = useRef<HTMLVideoElement | null>(null);\n const canvasRef = useRef<HTMLCanvasElement | null>(null);\n const streamRef = useRef<MediaStream | null>(null);\n const detectionTimerRef = useRef<number | null>(null);\n const configManagerRef = useRef<ConfigManager | null>(null);\n const detectionEngineRef = useRef<DetectionEngine | null>(null);\n const lastFrameTimeRef = useRef<number>(0);\n const lastDetectionTimeRef = useRef<number>(0); // 上次检测时间,用于节流控制\n\n // 同步初始化配置管理器\n if (!configManagerRef.current) {\n configManagerRef.current = ConfigManager.getInstance();\n \n // 如果有初始配置,更新配置\n if (initialConfig) {\n configManagerRef.current.updateConfig(initialConfig, {\n source: 'local',\n operator: 'hook-initialization'\n });\n }\n }\n\n // 监听配置变更和初始化相关逻辑\n useEffect(() => {\n if (!configManagerRef.current) {\n return () => {};\n }\n\n // 监听配置变更\n const unsubscribe = configManagerRef.current.addListener((newConfig) => {\n if (detectionEngineRef.current) {\n detectionEngineRef.current.updateConfig(newConfig);\n }\n onConfigChange?.(newConfig);\n });\n\n return () => {\n unsubscribe();\n };\n }, [initialConfig, onConfigChange]);\n\n // 初始化检测引擎\n useEffect(() => {\n const config = configManagerRef.current?.getConfig();\n if (config && credentials && ossConfig) {\n detectionEngineRef.current = new DetectionEngine(config, credentials, ossConfig);\n }\n }, [credentials, ossConfig]);\n\n /**\n * 获取摄像头权限\n */\n const getCameraPermission = useCallback(async (): Promise<MediaStream> => {\n try {\n const config = configManagerRef.current?.getConfig();\n const stream = await navigator.mediaDevices.getUserMedia({\n video: {\n width: config?.resolution.width || 640,\n height: config?.resolution.height || 480,\n facingMode: 'user'\n },\n audio: false\n });\n\n streamRef.current = stream;\n \n if (videoRef.current) {\n videoRef.current.srcObject = stream;\n // 尝试自动播放\n videoRef.current\n .play()\n .catch((e) => console.warn(\"自动播放被阻止\", e));\n }\n\n return stream;\n } catch (error) {\n throw new Error(`获取摄像头权限失败: ${error instanceof Error ? error.message : '未知错误'}`);\n }\n }, []);\n\n /**\n * 捕获当前帧\n */\n const captureFrame = useCallback(async (): Promise<Blob | null> => {\n const video = videoRef.current;\n const canvas = canvasRef.current;\n \n if (!video || !canvas) {\n return null;\n }\n\n // 检查视频状态\n if (video.readyState < 2) { // HAVE_CURRENT_DATA\n console.warn('视频尚未准备好,当前状态:', video.readyState);\n return null;\n }\n\n // 检查视频尺寸\n if (video.videoWidth === 0 || video.videoHeight === 0) {\n console.warn('视频尺寸无效:', { videoWidth: video.videoWidth, videoHeight: video.videoHeight });\n return null;\n }\n\n const ctx = canvas.getContext('2d');\n if (!ctx) {\n return null;\n }\n\n // 设置画布尺寸\n canvas.width = video.videoWidth;\n canvas.height = video.videoHeight;\n\n // 清除画布\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n\n // 绘制当前帧\n ctx.drawImage(video, 0, 0, canvas.width, canvas.height);\n\n // 转换为Blob\n return new Promise((resolve) => {\n canvas.toBlob(resolve, 'image/jpeg', 0.8);\n });\n }, []);\n\n /**\n * 执行检测\n */\n const performDetection = useCallback(async (): Promise<void> => {\n if (!detectionEngineRef.current) {\n return;\n }\n\n // 节流控制 - 检查距离上次检测的时间是否足够\n const now = Date.now();\n const config = getConfigManager().getConfig();\n if (now - lastDetectionTimeRef.current < config.checkInterval) {\n return; // 还没到检测时间,直接返回\n }\n\n try {\n const imageData = await captureFrame();\n if (!imageData) {\n throw new Error('无法捕获图像');\n }\n\n const startTime = Date.now();\n const result = await detectionEngineRef.current.detect(imageData);\n const endTime = Date.now();\n\n // 更新最新结果\n setLatestResult(result);\n\n // 更新上次检测时间\n lastDetectionTimeRef.current = now;\n\n // 更新统计信息\n setStats(prevStats => {\n const newStats = { ...prevStats };\n newStats.checkCount++;\n newStats.latency = endTime - startTime;\n \n // 计算平均处理时间\n if (result.processingTime) {\n newStats.avgProcessingTime = \n (prevStats.avgProcessingTime * (prevStats.checkCount - 1) + result.processingTime) / \n prevStats.checkCount;\n }\n\n // 更新违规统计\n if (result.violations.length > 0) {\n newStats.abnormalCount++;\n result.violations.forEach(violation => {\n newStats.violationStats[violation.type] = \n (newStats.violationStats[violation.type] || 0) + 1;\n });\n }\n\n // 计算FPS\n const currentTime = Date.now();\n if (lastFrameTimeRef.current > 0) {\n const frameInterval = currentTime - lastFrameTimeRef.current;\n newStats.fps = Math.round(1000 / frameInterval);\n }\n lastFrameTimeRef.current = currentTime;\n\n // 评估网络质量(基于延迟)\n if (newStats.latency < 200) {\n newStats.networkQuality = 'excellent';\n } else if (newStats.latency < 500) {\n newStats.networkQuality = 'good';\n } else if (newStats.latency < 1000) {\n newStats.networkQuality = 'fair';\n } else {\n newStats.networkQuality = 'poor';\n }\n\n return newStats;\n });\n\n // 触发回调\n onDetectionResult?.(result);\n\n // 处理违规\n result.violations.forEach(violation => {\n onViolation?.(violation);\n });\n\n } catch (error) {\n const errorObj = error instanceof Error ? error : new Error('检测失败');\n onError?.(errorObj);\n }\n }, [captureFrame, onDetectionResult, onViolation, onError]);\n\n /**\n * 开始监控\n */\n const startMonitoring = useCallback(async (): Promise<void> => {\n try {\n // 获取摄像头权限\n await getCameraPermission();\n\n setIsMonitoring(true);\n } catch (error) {\n const errorObj = error instanceof Error ? error : new Error('启动监控失败');\n onError?.(errorObj);\n throw errorObj;\n }\n }, [getCameraPermission, onError]);\n\n /**\n * 停止监控\n */\n const stopMonitoring = useCallback((): void => {\n setIsMonitoring(false);\n\n // 停止所有轨道\n if (streamRef.current) {\n streamRef.current.getTracks().forEach(track => track.stop());\n streamRef.current = null;\n }\n\n // 清除视频源\n if (videoRef.current) {\n videoRef.current.srcObject = null;\n }\n\n // 清除定时器\n if (detectionTimerRef.current) {\n clearInterval(detectionTimerRef.current);\n detectionTimerRef.current = null;\n }\n }, []);\n\n /**\n * 强制检测\n */\n const forceCheck = useCallback(async (): Promise<void> => {\n await performDetection();\n }, [performDetection]);\n\n /**\n * 更新配置\n */\n const updateConfig = useCallback(async (newConfig: Partial<DetectionConfig>) => {\n if (!configManagerRef.current) {\n return { success: false, errors: ['配置管理器未初始化'] };\n }\n\n return configManagerRef.current.updateConfig(newConfig, {\n source: 'local',\n operator: 'hook-user'\n });\n }, []);\n\n /**\n * 获取当前配置\n */\n const getCurrentConfig = useCallback((): DetectionConfig => {\n const config = configManagerRef.current?.getConfig();\n \n // 如果配置管理器未初始化或返回空配置,使用默认值\n if (!config || !config.checkInterval) {\n return DEFAULT_DETECTION_CONFIG;\n }\n \n return config;\n }, []);\n\n /**\n * 获取配置管理器实例\n */\n const getConfigManager = useCallback((): ConfigManager => {\n if (!configManagerRef.current) {\n throw new Error('配置管理器未初始化');\n }\n return configManagerRef.current;\n }, []);\n\n // 清理资源\n useEffect(() => {\n return () => {\n stopMonitoring();\n };\n }, [stopMonitoring]);\n\n // 监控状态变化时启动或停止定时器\n useEffect(() => {\n if (isMonitoring) {\n // 启动循环\n detectionTimerRef.current = window.setInterval(performDetection, 1000); // 每秒检查一次是否准备就绪(内部有节流)\n } else {\n if (detectionTimerRef.current) {\n window.clearInterval(detectionTimerRef.current);\n detectionTimerRef.current = null;\n }\n }\n\n return () => {\n if (detectionTimerRef.current) {\n window.clearInterval(detectionTimerRef.current);\n }\n };\n }, [isMonitoring, performDetection]);\n\n // 组件卸载时清理资源\n useEffect(() => {\n return () => {\n stopMonitoring();\n };\n }, [stopMonitoring]);\n\n // 返回Hook接口\n return {\n videoRef,\n canvasRef,\n startMonitoring,\n stopMonitoring,\n forceCheck,\n isMonitoring,\n latestResult,\n stats,\n config: getCurrentConfig(),\n updateConfig,\n getConfigManager\n };\n}","/**\n * 反作弊监控库\n * \n * 基于阿里云视觉智能平台的实时反作弊监控系统\n * 提供人脸检测、异常行为分析和完整的在线监考解决方案\n * \n * @author Anti-Cheating Monitor Team\n * @version 2.0.0\n * @license MIT\n */\n\n// 导出主要Hook\nexport { useAntiCheatingMonitor } from './hooks';\nexport type { UseAntiCheatingMonitorProps, UseAntiCheatingMonitorReturn } from './hooks';\n\n// 导出类型定义\nexport * from './types';\n\n// 导出工具类\nexport { ConfigManager } from './utils';\nexport { DetectionEngine } from './utils';\nexport { AuditLogger } from './utils';\n\n// 导出常量\nexport { CheatingType, ViolationLevel, CHEATING_TYPE_LABELS, CHEATING_TYPE_SEVERITY } from './constants';\n\n// 版本信息\nexport const VERSION = '2.0.0';\n\n/**\n * 库信息\n */\nexport const LIB_INFO = {\n name: 'anti-cheating-monitor',\n version: VERSION,\n description: '基于阿里云视觉智能平台的实时反作弊监控系统',\n author: 'Anti-Cheating Monitor Team',\n license: 'MIT',\n repository: 'https://github.com/coding-daily-wq/anti-cheating-monitor.git',\n homepage: 'https://coding-daily-wq.github.io/anti-cheating-monitor'\n} as const;"],"names":["DEFAULT_DETECTION_CONFIG","checkInterval","detectMultiplePeople","detectDeviceUsage","detectAbsence","resolution","width","height","type","earphoneThreshold","cellphoneThreshold","headPoseThreshold","pitch","roll","yaw","faceCompletenessThreshold","centeringThreshold","movementThreshold","CheatingType","CHEATING_TYPE_LABELS","PERSON_COUNT_ANOMALY","EARPHONE_DETECTED","CELLPHONE_DETECTED","HEAD_POSTURE_ABNORMAL","FACE_MISSING","FACE_OCCLUDED","NO_FACE_DETECTED","MULTIPLE_FACES_DETECTED","NOT_CENTERED","SUSPICIOUS_MOVEMENT","ViolationLevel","CHEATING_TYPE_SEVERITY","ConfigManager","constructor","this","auditLogs","listeners","Set","STORAGE_KEY","config","getDefaultConfig","loadFromStorage","startHotUpdate","getInstance","instance","stored","localStorage","getItem","parsedConfig","JSON","parse","error","saveToStorage","setItem","stringify","window","addEventListener","e","key","newValue","newConfig","updateConfig","source","addAuditLog","operation","configKey","oldValue","operator","log","id","Date","now","Math","random","toString","substr","timestamp","push","length","slice","validateConfig","errors","getConfig","options","force","success","Object","entries","forEach","value","notifyListeners","resetConfig","oldConfig","keys","addListener","listener","add","delete","getAuditLogs","clearAuditLogs","olderThan","filter","destroy","syncTimer","clearInterval","clear","OSSClient","credentials","isInitialized","client","initialize","Promise","resolve","bucket","region","Error","accessKeyId","accessKeySecret","OSSModule","import","then","n","a","OSSClass","default","stsToken","securityToken","put","blob","dir","filename","objectKey","result","headers","url","message","ready","updateCredentials","DetectionEngine","ossConfig","ossClient","updateOSSConfig","detect","imageData","startTime","imgBlob","Blob","base64ToBlob","imageUrl","apiResponse","callVIAPI","processAPIResponse","processingTime","faceCount","faceCompleteness","personCount","violations","level","HIGH","confidence","description","base64","parts","split","mimeType","match","byteString","atob","arrayBuffer","ArrayBuffer","uint8Array","Uint8Array","i","charCodeAt","params","Action","Version","Format","AccessKeyId","SignatureMethod","Timestamp","getTimestamp","SignatureVersion","SignatureNonce","generateNonce","RegionId","Type","String","ImageURL","SecurityToken","canonicalizedQueryString","sort","map","percentEncode","join","stringToSign","signature","hmacSha1","bodyString","response","fetch","method","body","mode","ok","status","statusText","json","data","encoder","TextEncoder","keyData","encode","dataData","cryptoKey","crypto","subtle","importKey","name","hash","sign","btoa","fromCharCode","Array","from","str","encodeURIComponent","replace","toISOString","substring","faceInfo","Data","FaceInfo","personInfo","PersonInfo","faceNumber","FaceNumber","personNumber","PersonNumber","completeness","Completeness","pose","Pose","createViolation","MEDIUM","toFixed","Pitch","Roll","Yaw","pThresh","rThresh","yThresh","abs","max","earphoneScore","EarPhone","Score","cellphoneScore","CellPhone","centeringScore","calculateCenteringScore","LOW","rawResponse","min","getDetectionStats","totalDetections","averageProcessingTime","violationRate","mostCommonViolations","AuditLogger","logs","MAX_LOGS","addLog","auditLog","generateLogId","unshift","persistLogs","queryLogs","filteredLogs","endTime","offset","limit","getAllLogs","getStats","operationStats","create","update","sync","sourceStats","local","remote","backend_sync","configCounts","operatorCounts","mostActiveConfigs","b","count","mostActiveOperators","totalLogs","cleanup","beforeCount","cleanedCount","exportLogs","format","convertToCSV","importLogs","logsData","importedLogs","validLogs","validateLog","existingIds","newLogs","has","includes","rows","row","cell","isArray","getRecentLogs","getLogsByConfigKey","getLogsByOperator","useAntiCheatingMonitor","props","initialConfig","onViolation","onDetectionResult","onError","onConfigChange","isMonitoring","setIsMonitoring","useState","latestResult","setLatestResult","stats","setStats","checkCount","abnormalCount","latency","networkQuality","fps","avgProcessingTime","violationStats","fromEntries","values","videoRef","useRef","canvasRef","streamRef","detectionTimerRef","configManagerRef","detectionEngineRef","lastFrameTimeRef","lastDetectionTimeRef","current","useEffect","unsubscribe","getCameraPermission","useCallback","async","stream","navigator","mediaDevices","getUserMedia","video","facingMode","audio","srcObject","play","catch","captureFrame","canvas","readyState","videoWidth","videoHeight","ctx","getContext","clearRect","drawImage","toBlob","performDetection","getConfigManager","prevStats","newStats","violation","currentTime","frameInterval","round","errorObj","startMonitoring","stopMonitoring","getTracks","track","stop","forceCheck","getCurrentConfig","setInterval","VERSION","LIB_INFO","version","author","license","repository","homepage"],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react"),t={checkInterval:3e3,detectMultiplePeople:!0,detectDeviceUsage:!0,detectAbsence:!0,resolution:{width:640,height:480},type:1,earphoneThreshold:.6,cellphoneThreshold:.6,headPoseThreshold:{pitch:30,roll:30,yaw:30},faceCompletenessThreshold:.8,centeringThreshold:.7,movementThreshold:.3};var i=(e=>(e.PERSON_COUNT_ANOMALY="PERSON_COUNT_ANOMALY",e.EARPHONE_DETECTED="EARPHONE_DETECTED",e.CELLPHONE_DETECTED="CELLPHONE_DETECTED",e.HEAD_POSTURE_ABNORMAL="HEAD_POSTURE_ABNORMAL",e.FACE_MISSING="FACE_MISSING",e.FACE_OCCLUDED="FACE_OCCLUDED",e.NO_FACE_DETECTED="NO_FACE_DETECTED",e.MULTIPLE_FACES_DETECTED="MULTIPLE_FACES_DETECTED",e.NOT_CENTERED="NOT_CENTERED",e.SUSPICIOUS_MOVEMENT="SUSPICIOUS_MOVEMENT",e))(i||{});const o={PERSON_COUNT_ANOMALY:"人数异常",EARPHONE_DETECTED:"检测到耳机",CELLPHONE_DETECTED:"检测到手机",HEAD_POSTURE_ABNORMAL:"头部姿态异常",FACE_MISSING:"人脸缺失",FACE_OCCLUDED:"人脸被遮挡",NO_FACE_DETECTED:"未检测到人脸",MULTIPLE_FACES_DETECTED:"检测到多张人脸",NOT_CENTERED:"未居中",SUSPICIOUS_MOVEMENT:"可疑移动"};var n=(e=>(e.LOW="low",e.MEDIUM="medium",e.HIGH="high",e.CRITICAL="critical",e))(n||{});const s={PERSON_COUNT_ANOMALY:"high",EARPHONE_DETECTED:"medium",CELLPHONE_DETECTED:"high",HEAD_POSTURE_ABNORMAL:"medium",FACE_MISSING:"high",FACE_OCCLUDED:"medium",NO_FACE_DETECTED:"high",MULTIPLE_FACES_DETECTED:"high",NOT_CENTERED:"low",SUSPICIOUS_MOVEMENT:"medium"};class ConfigManager{constructor(){this.auditLogs=[],this.listeners=new Set,this.STORAGE_KEY="anti-cheating-config",this.config=this.getDefaultConfig(),this.loadFromStorage(),this.startHotUpdate()}static getInstance(){return ConfigManager.instance||(ConfigManager.instance=new ConfigManager),ConfigManager.instance}getDefaultConfig(){return{...t}}loadFromStorage(){try{const e=localStorage.getItem(this.STORAGE_KEY);if(e){const t=JSON.parse(e);this.config={...this.config,...t}}}catch(e){}}saveToStorage(){try{localStorage.setItem(this.STORAGE_KEY,JSON.stringify(this.config))}catch(e){}}startHotUpdate(){window.addEventListener("storage",e=>{if(e.key===this.STORAGE_KEY&&e.newValue)try{const t=JSON.parse(e.newValue);this.updateConfig(t,{source:"remote"})}catch(t){}})}addAuditLog(e,t,i,o,n="local",s){const r={id:`${Date.now()}-${Math.random().toString(36).substr(2,9)}`,timestamp:Date.now(),operation:e,configKey:t,oldValue:i,newValue:o,operator:s,source:n};this.auditLogs.push(r),this.auditLogs.length>1e3&&(this.auditLogs=this.auditLogs.slice(-500))}validateConfig(e){const t=[];return void 0!==e.checkInterval&&(e.checkInterval<1e3||e.checkInterval>6e4)&&t.push("检测间隔应在1000-60000毫秒之间"),e.resolution&&((e.resolution.width<320||e.resolution.width>1920)&&t.push("视频宽度应在320-1920之间"),(e.resolution.height<240||e.resolution.height>1080)&&t.push("视频高度应在240-1080之间")),void 0!==e.earphoneThreshold&&(e.earphoneThreshold<0||e.earphoneThreshold>1)&&t.push("耳机检测阈值应在0-1之间"),void 0!==e.cellphoneThreshold&&(e.cellphoneThreshold<0||e.cellphoneThreshold>1)&&t.push("手机检测阈值应在0-1之间"),t}getConfig(){return{...this.config}}updateConfig(e,t={}){const{force:i=!1,source:o="local",operator:n}=t;if(!i){const t=this.validateConfig(e);if(t.length>0)return{success:!1,errors:t}}return Object.entries(e).forEach(([e,t])=>{const i=this.config[e];JSON.stringify(i)!==JSON.stringify(t)&&this.addAuditLog("update",e,i,t,o,n)}),this.config={...this.config,...e},"local"===o&&this.saveToStorage(),this.notifyListeners(),{success:!0}}resetConfig(e="local",t){const i={...this.config};this.config=this.getDefaultConfig(),Object.keys(i).forEach(o=>{this.addAuditLog("update",o,i[o],this.config[o],e,t)}),this.saveToStorage(),this.notifyListeners()}addListener(e){return this.listeners.add(e),()=>{this.listeners.delete(e)}}notifyListeners(){this.listeners.forEach(e=>{try{e(this.getConfig())}catch(t){}})}getAuditLogs(){return[...this.auditLogs]}clearAuditLogs(e){this.auditLogs=e?this.auditLogs.filter(t=>t.timestamp>e):[]}destroy(){this.syncTimer&&clearInterval(this.syncTimer),this.listeners.clear()}}class OSSClient{constructor(e,t){this.isInitialized=!1,this.client=null,this.config=e,this.credentials=t}async initialize(){if(this.isInitialized)return Promise.resolve();if(!this.config.bucket||!this.config.region)throw new Error("OSS配置不完整:缺少bucket或region");if(!this.credentials.accessKeyId||!this.credentials.accessKeySecret)throw new Error("OSS凭证不完整:缺少accessKeyId或accessKeySecret");try{const e=await Promise.resolve().then(()=>require("./aliyun-oss-sdk.min-DYAYfkYs.cjs")).then(e=>e.aliyunOssSdk_min),t=e.default||e;this.client=new t({region:this.config.region,accessKeyId:this.credentials.accessKeyId,accessKeySecret:this.credentials.accessKeySecret,stsToken:this.credentials.securityToken,bucket:this.config.bucket}),this.isInitialized=!0}catch(e){throw new Error("无法加载 OSS 客户端库")}}async put(e,t="monitor/"){if(this.isInitialized||await this.initialize(),!this.client)throw new Error("OSS客户端未初始化");const i=`${Date.now()}-${Math.random().toString(36).slice(2)}.jpg`,o=t+i;try{const t=await this.client.put(o,e,{headers:{"Cache-Control":"no-cache","Content-Disposition":`inline; filename="${i}"`}});return{url:t.url||`https://${this.config.bucket}.${this.config.region}.aliyuncs.com/${o}`}}catch(n){throw new Error(`阿里云 OSS 上传失败: ${n instanceof Error?n.message:"未知错误"}`)}}get ready(){return this.isInitialized}updateConfig(e){this.config={...this.config,...e},this.isInitialized=!1}updateCredentials(e){this.credentials={...this.credentials,...e},this.isInitialized=!1}}class DetectionEngine{constructor(e,t,i){this.config=e,this.credentials=t,this.ossConfig=i,this.ossClient=new OSSClient(i,t)}updateConfig(e){this.config=e}updateCredentials(e){this.credentials=e,this.ossClient.updateCredentials(e)}updateOSSConfig(e){this.ossConfig=e,this.ossClient.updateConfig(e)}async detect(e){const t=Date.now();try{let i;if(e instanceof Blob){i=(await this.ossClient.put(e)).url}else{const t=this.base64ToBlob(e);i=(await this.ossClient.put(t)).url}const o=await this.callVIAPI(i),n=this.processAPIResponse(o);return n.processingTime=Date.now()-t,n}catch(o){return{timestamp:Date.now(),faceCount:0,faceCompleteness:0,personCount:0,violations:[{type:i.NO_FACE_DETECTED,level:n.HIGH,confidence:1,description:`检测失败: ${o instanceof Error?o.message:"未知错误"}`}],processingTime:Date.now()-t}}}base64ToBlob(e){const t=e.split(","),i=t[0].match(/:(.*?);/)?.[1]||"image/jpeg",o=atob(t[1]),n=new ArrayBuffer(o.length),s=new Uint8Array(n);for(let r=0;r<o.length;r++)s[r]=o.charCodeAt(r);return new Blob([n],{type:i})}async callVIAPI(e){const t={Action:"MonitorExamination",Version:"2019-12-30",Format:"JSON",AccessKeyId:this.credentials.accessKeyId,SignatureMethod:"HMAC-SHA1",Timestamp:this.getTimestamp(),SignatureVersion:"1.0",SignatureNonce:this.generateNonce(),RegionId:"cn-shanghai",Type:String(this.config.type),ImageURL:e};this.credentials.securityToken&&(t.SecurityToken=this.credentials.securityToken);const i=Object.keys(t).sort().map(e=>`${this.percentEncode(e)}=${this.percentEncode(t[e])}`).join("&"),o=`POST&${this.percentEncode("/")}&${this.percentEncode(i)}`,n=await this.hmacSha1(`${this.credentials.accessKeySecret}&`,o),s=`${i}&Signature=${this.percentEncode(n)}`,r=await fetch("https://facebody.cn-shanghai.aliyuncs.com",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:s,mode:"cors"});if(!r.ok)throw new Error(`API Error: ${r.status} ${r.statusText}`);return await r.json()}async hmacSha1(e,t){const i=new TextEncoder,o=i.encode(e),n=i.encode(t),s=await window.crypto.subtle.importKey("raw",o,{name:"HMAC",hash:"SHA-1"},!1,["sign"]),r=await window.crypto.subtle.sign("HMAC",s,n);return btoa(String.fromCharCode(...Array.from(new Uint8Array(r))))}percentEncode(e){return encodeURIComponent(e).replace(/!/g,"%21").replace(/'/g,"%27").replace(/\(/g,"%28").replace(/\)/g,"%29").replace(/\*/g,"%2A")}getTimestamp(){return(new Date).toISOString().replace(/\.\d{3}/,"")}generateNonce(){return Math.random().toString(36).substring(2,15)+Math.random().toString(36).substring(2,15)}processAPIResponse(e){const t=[],o=e.Data?.FaceInfo||{},s=e.Data?.PersonInfo||{},r=o.FaceNumber||0,a=s.PersonNumber||0,c=o.Completeness||0,l=o.Pose||{};if(0===r?t.push(this.createViolation(i.NO_FACE_DETECTED,n.HIGH,1,"未检测到人脸")):r>1&&t.push(this.createViolation(i.MULTIPLE_FACES_DETECTED,n.HIGH,1,`检测到${r}张人脸`)),this.config.detectMultiplePeople&&a>1&&t.push(this.createViolation(i.PERSON_COUNT_ANOMALY,n.HIGH,1,`检测到${a}人`)),c<this.config.faceCompletenessThreshold&&t.push(this.createViolation(i.FACE_OCCLUDED,n.MEDIUM,1-c,`人脸完整度${(100*c).toFixed(0)}%`)),l){const{Pitch:e,Roll:o,Yaw:s}=l,{pitch:r,roll:a,yaw:c}=this.config.headPoseThreshold;(Math.abs(e)>r||Math.abs(o)>a||Math.abs(s)>c)&&t.push(this.createViolation(i.HEAD_POSTURE_ABNORMAL,n.MEDIUM,Math.max(Math.abs(e)/r,Math.abs(o)/a,Math.abs(s)/c)-1,`头部姿态异常: ${e>0?"抬头":"低头"}${e.toFixed(1)}° 偏头${o.toFixed(1)}° ${s>0?"左转":"右转"}${s.toFixed(1)}°`))}if(this.config.detectDeviceUsage){const e=s.EarPhone?.Score||0;e>this.config.earphoneThreshold&&t.push(this.createViolation(i.EARPHONE_DETECTED,n.MEDIUM,e,`检测到耳机 (置信度: ${(100*e).toFixed(0)}%)`));const o=s.CellPhone?.Score||0;o>this.config.cellphoneThreshold&&t.push(this.createViolation(i.CELLPHONE_DETECTED,n.HIGH,o,`检测到手机 (置信度: ${(100*o).toFixed(0)}%)`))}if(l){const e=this.calculateCenteringScore(l);e<this.config.centeringThreshold&&t.push(this.createViolation(i.NOT_CENTERED,n.LOW,1-e,`未居中 (居中度: ${(100*e).toFixed(0)}%)`))}return{timestamp:Date.now(),faceCount:r,faceCompleteness:c,personCount:a,pose:l,violations:t,rawResponse:e}}createViolation(e,t,i,o,n){return{type:e,level:t,confidence:Math.max(0,Math.min(1,i)),description:o,data:n}}calculateCenteringScore(e){const{Pitch:t,Roll:i,Yaw:o}=e;return(Math.max(0,1-Math.abs(t)/30)+Math.max(0,1-Math.abs(i)/45)+Math.max(0,1-Math.abs(o)/60))/3}getDetectionStats(){return{totalDetections:0,averageProcessingTime:0,violationRate:0,mostCommonViolations:[]}}}const r="2.0.0",a={name:"anti-cheating-monitor",version:r,description:"基于阿里云视觉智能平台的实时反作弊监控系统",author:"Anti-Cheating Monitor Team",license:"MIT",repository:"https://github.com/coding-daily-wq/anti-cheating-monitor.git",homepage:"https://coding-daily-wq.github.io/anti-cheating-monitor"};exports.AuditLogger=class{constructor(){this.logs=[],this.MAX_LOGS=1e3}addLog(e){const t={...e,id:this.generateLogId(),timestamp:Date.now()};return this.logs.unshift(t),this.logs.length>this.MAX_LOGS&&(this.logs=this.logs.slice(0,this.MAX_LOGS)),this.persistLogs(),t}generateLogId(){return`${Date.now()}-${Math.random().toString(36).substr(2,9)}`}queryLogs(e={}){let t=[...this.logs];void 0!==e.startTime&&(t=t.filter(t=>t.timestamp>=e.startTime)),void 0!==e.endTime&&(t=t.filter(t=>t.timestamp<=e.endTime)),e.operation&&(t=t.filter(t=>t.operation===e.operation)),e.configKey&&(t=t.filter(t=>t.configKey===e.configKey)),e.operator&&(t=t.filter(t=>t.operator===e.operator)),e.source&&(t=t.filter(t=>t.source===e.source));const i=e.offset||0,o=e.limit||50;return t=t.slice(i,i+o),t}getAllLogs(){return[...this.logs]}getStats(){const e={create:0,update:0,delete:0,sync:0},t={local:0,remote:0,backend_sync:0},i={},o={};this.logs.forEach(n=>{e[n.operation]++,t[n.source]++,i[n.configKey]=(i[n.configKey]||0)+1,n.operator&&(o[n.operator]=(o[n.operator]||0)+1)});const n=Object.entries(i).sort(([,e],[,t])=>t-e).slice(0,5).map(([e,t])=>({configKey:e,count:t})),s=Object.entries(o).sort(([,e],[,t])=>t-e).slice(0,5).map(([e,t])=>({operator:e,count:t}));return{totalLogs:this.logs.length,operationStats:e,sourceStats:t,mostActiveConfigs:n,mostActiveOperators:s}}cleanup(e){const t=this.logs.length;this.logs=this.logs.filter(t=>t.timestamp>e);const i=t-this.logs.length;return i>0&&this.persistLogs(),i}clear(){this.logs=[],this.persistLogs()}exportLogs(e="json"){if("json"===e)return JSON.stringify(this.logs,null,2);if("csv"===e)return this.convertToCSV(this.logs);throw new Error(`不支持的导出格式: ${e}`)}importLogs(e,t="json"){let i;if("json"!==t)throw new Error(`不支持的导入格式: ${t}`);try{i=JSON.parse(e)}catch(r){throw new Error("JSON格式错误")}const o=i.filter(e=>this.validateLog(e)),n=new Set(this.logs.map(e=>e.id)),s=o.filter(e=>!n.has(e.id));return this.logs=[...s,...this.logs],this.logs.length>this.MAX_LOGS&&(this.logs=this.logs.slice(0,this.MAX_LOGS)),this.persistLogs(),s.length}validateLog(e){return"object"==typeof e&&"string"==typeof e.id&&"number"==typeof e.timestamp&&["create","update","delete","sync"].includes(e.operation)&&"string"==typeof e.configKey&&["local","remote","backend_sync"].includes(e.source)}convertToCSV(e){const t=e.map(e=>[e.id,new Date(e.timestamp).toISOString(),e.operation,e.configKey,JSON.stringify(e.oldValue||""),JSON.stringify(e.newValue||""),e.operator||"",e.source]);return[["ID","Timestamp","Operation","Config Key","Old Value","New Value","Operator","Source"].join(","),...t.map(e=>e.map(e=>`"${e}"`).join(","))].join("\n")}persistLogs(){try{localStorage.setItem("anti-cheating-audit-logs",JSON.stringify(this.logs))}catch(e){}}loadFromStorage(){try{const e=localStorage.getItem("anti-cheating-audit-logs");if(e){const t=JSON.parse(e);Array.isArray(t)&&(this.logs=t.filter(e=>this.validateLog(e)))}}catch(e){this.logs=[]}}getRecentLogs(e=10){return this.logs.slice(0,e)}getLogsByConfigKey(e){return this.logs.filter(t=>t.configKey===e)}getLogsByOperator(e){return this.logs.filter(t=>t.operator===e)}},exports.CHEATING_TYPE_LABELS=o,exports.CHEATING_TYPE_SEVERITY=s,exports.CheatingType=i,exports.ConfigManager=ConfigManager,exports.DEFAULT_DETECTION_CONFIG=t,exports.DetectionEngine=DetectionEngine,exports.LIB_INFO=a,exports.VERSION=r,exports.ViolationLevel=n,exports.useAntiCheatingMonitor=function(o){const{credentials:n,ossConfig:s,config:r,onViolation:a,onDetectionResult:c,onError:l,onConfigChange:h}=o,[g,u]=e.useState(!1),[d,E]=e.useState(null),[f,p]=e.useState({checkCount:0,abnormalCount:0,latency:0,networkQuality:"excellent",fps:0,avgProcessingTime:0,violationStats:Object.fromEntries(Object.values(i).map(e=>[e,0]))}),C=e.useRef(null),T=e.useRef(null),m=e.useRef(null),S=e.useRef(null),y=e.useRef(null),w=e.useRef(null),O=e.useRef(0),D=e.useRef(0);y.current||(y.current=ConfigManager.getInstance(),r&&y.current.updateConfig(r,{source:"local",operator:"hook-initialization"})),e.useEffect(()=>{if(!y.current)return()=>{};const e=y.current.addListener(e=>{w.current&&w.current.updateConfig(e),h?.(e)});return()=>{e()}},[r,h]),e.useEffect(()=>{const e=y.current?.getConfig();e&&n&&s&&(w.current=new DetectionEngine(e,n,s))},[n,s]);const A=e.useCallback(async()=>{try{const e=y.current?.getConfig(),t=await navigator.mediaDevices.getUserMedia({video:{width:e?.resolution.width||640,height:e?.resolution.height||480,facingMode:"user"},audio:!1});return m.current=t,C.current&&(C.current.srcObject=t,C.current.play().catch(e=>{})),t}catch(e){throw new Error(`获取摄像头权限失败: ${e instanceof Error?e.message:"未知错误"}`)}},[]),I=e.useCallback(async()=>{const e=C.current,t=T.current;if(!e||!t)return null;if(e.readyState<2)return null;if(0===e.videoWidth||0===e.videoHeight)return null;const i=t.getContext("2d");return i?(t.width=e.videoWidth,t.height=e.videoHeight,i.clearRect(0,0,t.width,t.height),i.drawImage(e,0,0,t.width,t.height),new Promise(e=>{t.toBlob(e,"image/jpeg",.8)})):null},[]),_=e.useCallback(async()=>{if(!w.current)return;const e=Date.now(),t=P().getConfig();if(!(e-D.current<t.checkInterval))try{const t=await I();if(!t)throw new Error("无法捕获图像");const i=Date.now(),o=await w.current.detect(t),n=Date.now();E(o),D.current=e,p(e=>{const t={...e};t.checkCount++,t.latency=n-i,o.processingTime&&(t.avgProcessingTime=(e.avgProcessingTime*(e.checkCount-1)+o.processingTime)/e.checkCount),o.violations.length>0&&(t.abnormalCount++,o.violations.forEach(e=>{t.violationStats[e.type]=(t.violationStats[e.type]||0)+1}));const s=Date.now();if(O.current>0){const e=s-O.current;t.fps=Math.round(1e3/e)}return O.current=s,t.latency<200?t.networkQuality="excellent":t.latency<500?t.networkQuality="good":t.latency<1e3?t.networkQuality="fair":t.networkQuality="poor",t}),c?.(o),o.violations.forEach(e=>{a?.(e)})}catch(i){const e=i instanceof Error?i:new Error("检测失败");l?.(e)}},[I,c,a,l]),L=e.useCallback(async()=>{try{await A(),u(!0)}catch(e){const t=e instanceof Error?e:new Error("启动监控失败");throw l?.(t),t}},[A,l]),M=e.useCallback(()=>{u(!1),m.current&&(m.current.getTracks().forEach(e=>e.stop()),m.current=null),C.current&&(C.current.srcObject=null),S.current&&(clearInterval(S.current),S.current=null)},[]),N=e.useCallback(async()=>{await _()},[_]),v=e.useCallback(async e=>y.current?y.current.updateConfig(e,{source:"local",operator:"hook-user"}):{success:!1,errors:["配置管理器未初始化"]},[]),b=e.useCallback(()=>{const e=y.current?.getConfig();return e&&e.checkInterval?e:t},[]),P=e.useCallback(()=>{if(!y.current)throw new Error("配置管理器未初始化");return y.current},[]);return e.useEffect(()=>()=>{M()},[M]),e.useEffect(()=>(g?S.current=window.setInterval(_,1e3):S.current&&(window.clearInterval(S.current),S.current=null),()=>{S.current&&window.clearInterval(S.current)}),[g,_]),e.useEffect(()=>()=>{M()},[M]),{videoRef:C,canvasRef:T,startMonitoring:L,stopMonitoring:M,forceCheck:N,isMonitoring:g,latestResult:d,stats:f,config:b(),updateConfig:v,getConfigManager:P}};
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react"),t={checkInterval:3e3,detectMultiplePeople:!0,detectDeviceUsage:!0,detectAbsence:!0,resolution:{width:640,height:480},type:1,earphoneThreshold:.6,cellphoneThreshold:.6,headPoseThreshold:{pitch:30,roll:30,yaw:30},faceCompletenessThreshold:.8,centeringThreshold:.7,movementThreshold:.3};var i=(e=>(e.PERSON_COUNT_ANOMALY="PERSON_COUNT_ANOMALY",e.EARPHONE_DETECTED="EARPHONE_DETECTED",e.CELLPHONE_DETECTED="CELLPHONE_DETECTED",e.HEAD_POSTURE_ABNORMAL="HEAD_POSTURE_ABNORMAL",e.FACE_MISSING="FACE_MISSING",e.FACE_OCCLUDED="FACE_OCCLUDED",e.NO_FACE_DETECTED="NO_FACE_DETECTED",e.MULTIPLE_FACES_DETECTED="MULTIPLE_FACES_DETECTED",e.NOT_CENTERED="NOT_CENTERED",e.SUSPICIOUS_MOVEMENT="SUSPICIOUS_MOVEMENT",e))(i||{});const o={PERSON_COUNT_ANOMALY:"人数异常",EARPHONE_DETECTED:"检测到耳机",CELLPHONE_DETECTED:"检测到手机",HEAD_POSTURE_ABNORMAL:"头部姿态异常",FACE_MISSING:"人脸缺失",FACE_OCCLUDED:"人脸被遮挡",NO_FACE_DETECTED:"未检测到人脸",MULTIPLE_FACES_DETECTED:"检测到多张人脸",NOT_CENTERED:"未居中",SUSPICIOUS_MOVEMENT:"可疑移动"};var n=(e=>(e.LOW="low",e.MEDIUM="medium",e.HIGH="high",e.CRITICAL="critical",e))(n||{});const s={PERSON_COUNT_ANOMALY:"high",EARPHONE_DETECTED:"medium",CELLPHONE_DETECTED:"high",HEAD_POSTURE_ABNORMAL:"medium",FACE_MISSING:"high",FACE_OCCLUDED:"medium",NO_FACE_DETECTED:"high",MULTIPLE_FACES_DETECTED:"high",NOT_CENTERED:"low",SUSPICIOUS_MOVEMENT:"medium"};class ConfigManager{constructor(){this.auditLogs=[],this.listeners=new Set,this.STORAGE_KEY="anti-cheating-config",this.config=this.getDefaultConfig(),this.loadFromStorage(),this.startHotUpdate()}static getInstance(){return ConfigManager.instance||(ConfigManager.instance=new ConfigManager),ConfigManager.instance}getDefaultConfig(){return{...t}}loadFromStorage(){try{const e=localStorage.getItem(this.STORAGE_KEY);if(e){const t=JSON.parse(e);this.config={...this.config,...t}}}catch(e){}}saveToStorage(){try{localStorage.setItem(this.STORAGE_KEY,JSON.stringify(this.config))}catch(e){}}startHotUpdate(){window.addEventListener("storage",e=>{if(e.key===this.STORAGE_KEY&&e.newValue)try{const t=JSON.parse(e.newValue);this.updateConfig(t,{source:"remote"})}catch(t){}})}addAuditLog(e,t,i,o,n="local",s){const r={id:`${Date.now()}-${Math.random().toString(36).substr(2,9)}`,timestamp:Date.now(),operation:e,configKey:t,oldValue:i,newValue:o,operator:s,source:n};this.auditLogs.push(r),this.auditLogs.length>1e3&&(this.auditLogs=this.auditLogs.slice(-500))}validateConfig(e){const t=[];return void 0!==e.checkInterval&&(e.checkInterval<1e3||e.checkInterval>6e4)&&t.push("检测间隔应在1000-60000毫秒之间"),e.resolution&&((e.resolution.width<320||e.resolution.width>1920)&&t.push("视频宽度应在320-1920之间"),(e.resolution.height<240||e.resolution.height>1080)&&t.push("视频高度应在240-1080之间")),void 0!==e.earphoneThreshold&&(e.earphoneThreshold<0||e.earphoneThreshold>1)&&t.push("耳机检测阈值应在0-1之间"),void 0!==e.cellphoneThreshold&&(e.cellphoneThreshold<0||e.cellphoneThreshold>1)&&t.push("手机检测阈值应在0-1之间"),t}getConfig(){return{...this.config}}updateConfig(e,t={}){const{force:i=!1,source:o="local",operator:n}=t;if(!i){const t=this.validateConfig(e);if(t.length>0)return{success:!1,errors:t}}return Object.entries(e).forEach(([e,t])=>{const i=this.config[e];JSON.stringify(i)!==JSON.stringify(t)&&this.addAuditLog("update",e,i,t,o,n)}),this.config={...this.config,...e},"local"===o&&this.saveToStorage(),this.notifyListeners(),{success:!0}}resetConfig(e="local",t){const i={...this.config};this.config=this.getDefaultConfig(),Object.keys(i).forEach(o=>{this.addAuditLog("update",o,i[o],this.config[o],e,t)}),this.saveToStorage(),this.notifyListeners()}addListener(e){return this.listeners.add(e),()=>{this.listeners.delete(e)}}notifyListeners(){this.listeners.forEach(e=>{try{e(this.getConfig())}catch(t){}})}getAuditLogs(){return[...this.auditLogs]}clearAuditLogs(e){this.auditLogs=e?this.auditLogs.filter(t=>t.timestamp>e):[]}destroy(){this.syncTimer&&clearInterval(this.syncTimer),this.listeners.clear()}}class OSSClient{constructor(e,t){this.isInitialized=!1,this.client=null,this.config=e,this.credentials=t}async initialize(){if(this.isInitialized)return Promise.resolve();if(!this.config.bucket||!this.config.region)throw new Error("OSS配置不完整:缺少bucket或region");if(!this.credentials.accessKeyId||!this.credentials.accessKeySecret)throw new Error("OSS凭证不完整:缺少accessKeyId或accessKeySecret");try{const e=await Promise.resolve().then(()=>require("./aliyun-oss-sdk.min-DYAYfkYs.cjs")).then(e=>e.aliyunOssSdk_min),t=e.default||e;this.client=new t({region:this.config.region,accessKeyId:this.credentials.accessKeyId,accessKeySecret:this.credentials.accessKeySecret,stsToken:this.credentials.securityToken,bucket:this.config.bucket}),this.isInitialized=!0}catch(e){throw new Error("无法加载 OSS 客户端库")}}async put(e,t="monitor/"){if(this.isInitialized||await this.initialize(),!this.client)throw new Error("OSS客户端未初始化");const i=`${Date.now()}-${Math.random().toString(36).slice(2)}.jpg`,o=t+i;try{const t=await this.client.put(o,e,{headers:{"Cache-Control":"no-cache","Content-Disposition":`inline; filename="${i}"`}});return{url:t.url||`https://${this.config.bucket}.${this.config.region}.aliyuncs.com/${o}`}}catch(n){throw new Error(`阿里云 OSS 上传失败: ${n instanceof Error?n.message:"未知错误"}`)}}get ready(){return this.isInitialized}updateConfig(e){this.config={...this.config,...e},this.isInitialized=!1}updateCredentials(e){this.credentials={...this.credentials,...e},this.isInitialized=!1}}class DetectionEngine{constructor(e,t,i){this.config=e,this.credentials=t,this.ossConfig=i,this.ossClient=new OSSClient(i,t)}updateConfig(e){this.config=e}updateCredentials(e){this.credentials=e,this.ossClient.updateCredentials(e)}updateOSSConfig(e){this.ossConfig=e,this.ossClient.updateConfig(e)}async detect(e){const t=Date.now(),o=e instanceof Blob?e:this.base64ToBlob(e);try{let e;e=(await this.ossClient.put(o)).url;const i=await this.callVIAPI(e),n=this.processAPIResponse(i,o);return n.processingTime=Date.now()-t,n}catch(s){return{timestamp:Date.now(),faceCount:0,faceCompleteness:0,personCount:0,violations:[{type:i.NO_FACE_DETECTED,level:n.HIGH,confidence:1,description:`检测失败: ${s instanceof Error?s.message:"未知错误"}`}],imageData:o,processingTime:Date.now()-t}}}base64ToBlob(e){const t=e.split(","),i=t[0].match(/:(.*?);/)?.[1]||"image/jpeg",o=atob(t[1]),n=new ArrayBuffer(o.length),s=new Uint8Array(n);for(let r=0;r<o.length;r++)s[r]=o.charCodeAt(r);return new Blob([n],{type:i})}async callVIAPI(e){const t={Action:"MonitorExamination",Version:"2019-12-30",Format:"JSON",AccessKeyId:this.credentials.accessKeyId,SignatureMethod:"HMAC-SHA1",Timestamp:this.getTimestamp(),SignatureVersion:"1.0",SignatureNonce:this.generateNonce(),RegionId:"cn-shanghai",Type:String(this.config.type),ImageURL:e};this.credentials.securityToken&&(t.SecurityToken=this.credentials.securityToken);const i=Object.keys(t).sort().map(e=>`${this.percentEncode(e)}=${this.percentEncode(t[e])}`).join("&"),o=`POST&${this.percentEncode("/")}&${this.percentEncode(i)}`,n=await this.hmacSha1(`${this.credentials.accessKeySecret}&`,o),s=`${i}&Signature=${this.percentEncode(n)}`,r=await fetch("https://facebody.cn-shanghai.aliyuncs.com",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:s,mode:"cors"});if(!r.ok)throw new Error(`API Error: ${r.status} ${r.statusText}`);return await r.json()}async hmacSha1(e,t){const i=new TextEncoder,o=i.encode(e),n=i.encode(t),s=await window.crypto.subtle.importKey("raw",o,{name:"HMAC",hash:"SHA-1"},!1,["sign"]),r=await window.crypto.subtle.sign("HMAC",s,n);return btoa(String.fromCharCode(...Array.from(new Uint8Array(r))))}percentEncode(e){return encodeURIComponent(e).replace(/!/g,"%21").replace(/'/g,"%27").replace(/\(/g,"%28").replace(/\)/g,"%29").replace(/\*/g,"%2A")}getTimestamp(){return(new Date).toISOString().replace(/\.\d{3}/,"")}generateNonce(){return Math.random().toString(36).substring(2,15)+Math.random().toString(36).substring(2,15)}processAPIResponse(e,t){const o=[],s=e.Data?.FaceInfo||{},r=e.Data?.PersonInfo||{},a=s.FaceNumber||0,c=r.PersonNumber||0,l=s.Completeness||0,h=s.Pose||{};if(0===a?o.push(this.createViolation(i.NO_FACE_DETECTED,n.HIGH,1,"未检测到人脸")):a>1&&o.push(this.createViolation(i.MULTIPLE_FACES_DETECTED,n.HIGH,1,`检测到${a}张人脸`)),this.config.detectMultiplePeople&&c>1&&o.push(this.createViolation(i.PERSON_COUNT_ANOMALY,n.HIGH,1,`检测到${c}人`)),l<this.config.faceCompletenessThreshold&&o.push(this.createViolation(i.FACE_OCCLUDED,n.MEDIUM,1-l,`人脸完整度${(100*l).toFixed(0)}%`)),h){const{Pitch:e,Roll:t,Yaw:s}=h,{pitch:r,roll:a,yaw:c}=this.config.headPoseThreshold;(Math.abs(e)>r||Math.abs(t)>a||Math.abs(s)>c)&&o.push(this.createViolation(i.HEAD_POSTURE_ABNORMAL,n.MEDIUM,Math.max(Math.abs(e)/r,Math.abs(t)/a,Math.abs(s)/c)-1,`头部姿态异常: ${e>0?"抬头":"低头"}${e.toFixed(1)}° 偏头${t.toFixed(1)}° ${s>0?"左转":"右转"}${s.toFixed(1)}°`))}if(this.config.detectDeviceUsage){const e=r.EarPhone?.Score||0;e>this.config.earphoneThreshold&&o.push(this.createViolation(i.EARPHONE_DETECTED,n.MEDIUM,e,`检测到耳机 (置信度: ${(100*e).toFixed(0)}%)`));const t=r.CellPhone?.Score||0;t>this.config.cellphoneThreshold&&o.push(this.createViolation(i.CELLPHONE_DETECTED,n.HIGH,t,`检测到手机 (置信度: ${(100*t).toFixed(0)}%)`))}if(h){const e=this.calculateCenteringScore(h);e<this.config.centeringThreshold&&o.push(this.createViolation(i.NOT_CENTERED,n.LOW,1-e,`未居中 (居中度: ${(100*e).toFixed(0)}%)`))}return{timestamp:Date.now(),faceCount:a,faceCompleteness:l,personCount:c,pose:h,violations:o,rawResponse:e,imageData:t}}createViolation(e,t,i,o,n){return{type:e,level:t,confidence:Math.max(0,Math.min(1,i)),description:o,data:n}}calculateCenteringScore(e){const{Pitch:t,Roll:i,Yaw:o}=e;return(Math.max(0,1-Math.abs(t)/30)+Math.max(0,1-Math.abs(i)/45)+Math.max(0,1-Math.abs(o)/60))/3}getDetectionStats(){return{totalDetections:0,averageProcessingTime:0,violationRate:0,mostCommonViolations:[]}}}const r="2.0.0",a={name:"anti-cheating-monitor",version:r,description:"基于阿里云视觉智能平台的实时反作弊监控系统",author:"Anti-Cheating Monitor Team",license:"MIT",repository:"https://github.com/coding-daily-wq/anti-cheating-monitor.git",homepage:"https://coding-daily-wq.github.io/anti-cheating-monitor"};exports.AuditLogger=class{constructor(){this.logs=[],this.MAX_LOGS=1e3}addLog(e){const t={...e,id:this.generateLogId(),timestamp:Date.now()};return this.logs.unshift(t),this.logs.length>this.MAX_LOGS&&(this.logs=this.logs.slice(0,this.MAX_LOGS)),this.persistLogs(),t}generateLogId(){return`${Date.now()}-${Math.random().toString(36).substr(2,9)}`}queryLogs(e={}){let t=[...this.logs];void 0!==e.startTime&&(t=t.filter(t=>t.timestamp>=e.startTime)),void 0!==e.endTime&&(t=t.filter(t=>t.timestamp<=e.endTime)),e.operation&&(t=t.filter(t=>t.operation===e.operation)),e.configKey&&(t=t.filter(t=>t.configKey===e.configKey)),e.operator&&(t=t.filter(t=>t.operator===e.operator)),e.source&&(t=t.filter(t=>t.source===e.source));const i=e.offset||0,o=e.limit||50;return t=t.slice(i,i+o),t}getAllLogs(){return[...this.logs]}getStats(){const e={create:0,update:0,delete:0,sync:0},t={local:0,remote:0,backend_sync:0},i={},o={};this.logs.forEach(n=>{e[n.operation]++,t[n.source]++,i[n.configKey]=(i[n.configKey]||0)+1,n.operator&&(o[n.operator]=(o[n.operator]||0)+1)});const n=Object.entries(i).sort(([,e],[,t])=>t-e).slice(0,5).map(([e,t])=>({configKey:e,count:t})),s=Object.entries(o).sort(([,e],[,t])=>t-e).slice(0,5).map(([e,t])=>({operator:e,count:t}));return{totalLogs:this.logs.length,operationStats:e,sourceStats:t,mostActiveConfigs:n,mostActiveOperators:s}}cleanup(e){const t=this.logs.length;this.logs=this.logs.filter(t=>t.timestamp>e);const i=t-this.logs.length;return i>0&&this.persistLogs(),i}clear(){this.logs=[],this.persistLogs()}exportLogs(e="json"){if("json"===e)return JSON.stringify(this.logs,null,2);if("csv"===e)return this.convertToCSV(this.logs);throw new Error(`不支持的导出格式: ${e}`)}importLogs(e,t="json"){let i;if("json"!==t)throw new Error(`不支持的导入格式: ${t}`);try{i=JSON.parse(e)}catch(r){throw new Error("JSON格式错误")}const o=i.filter(e=>this.validateLog(e)),n=new Set(this.logs.map(e=>e.id)),s=o.filter(e=>!n.has(e.id));return this.logs=[...s,...this.logs],this.logs.length>this.MAX_LOGS&&(this.logs=this.logs.slice(0,this.MAX_LOGS)),this.persistLogs(),s.length}validateLog(e){return"object"==typeof e&&"string"==typeof e.id&&"number"==typeof e.timestamp&&["create","update","delete","sync"].includes(e.operation)&&"string"==typeof e.configKey&&["local","remote","backend_sync"].includes(e.source)}convertToCSV(e){const t=e.map(e=>[e.id,new Date(e.timestamp).toISOString(),e.operation,e.configKey,JSON.stringify(e.oldValue||""),JSON.stringify(e.newValue||""),e.operator||"",e.source]);return[["ID","Timestamp","Operation","Config Key","Old Value","New Value","Operator","Source"].join(","),...t.map(e=>e.map(e=>`"${e}"`).join(","))].join("\n")}persistLogs(){try{localStorage.setItem("anti-cheating-audit-logs",JSON.stringify(this.logs))}catch(e){}}loadFromStorage(){try{const e=localStorage.getItem("anti-cheating-audit-logs");if(e){const t=JSON.parse(e);Array.isArray(t)&&(this.logs=t.filter(e=>this.validateLog(e)))}}catch(e){this.logs=[]}}getRecentLogs(e=10){return this.logs.slice(0,e)}getLogsByConfigKey(e){return this.logs.filter(t=>t.configKey===e)}getLogsByOperator(e){return this.logs.filter(t=>t.operator===e)}},exports.CHEATING_TYPE_LABELS=o,exports.CHEATING_TYPE_SEVERITY=s,exports.CheatingType=i,exports.ConfigManager=ConfigManager,exports.DEFAULT_DETECTION_CONFIG=t,exports.DetectionEngine=DetectionEngine,exports.LIB_INFO=a,exports.VERSION=r,exports.ViolationLevel=n,exports.useAntiCheatingMonitor=function(o){const{credentials:n,ossConfig:s,config:r,onViolation:a,onDetectionResult:c,onError:l,onConfigChange:h}=o,[g,u]=e.useState(!1),[d,E]=e.useState(null),[f,p]=e.useState({checkCount:0,abnormalCount:0,latency:0,networkQuality:"excellent",fps:0,avgProcessingTime:0,violationStats:Object.fromEntries(Object.values(i).map(e=>[e,0]))}),C=e.useRef(null),T=e.useRef(null),m=e.useRef(null),S=e.useRef(null),y=e.useRef(null),O=e.useRef(null),w=e.useRef(0),D=e.useRef(0);y.current||(y.current=ConfigManager.getInstance(),r&&y.current.updateConfig(r,{source:"local",operator:"hook-initialization"})),e.useEffect(()=>{if(!y.current)return()=>{};const e=y.current.addListener(e=>{O.current&&O.current.updateConfig(e),h?.(e)});return()=>{e()}},[r,h]),e.useEffect(()=>{const e=y.current?.getConfig();e&&n&&s&&(O.current=new DetectionEngine(e,n,s))},[n,s]);const A=e.useCallback(async()=>{try{const e=y.current?.getConfig(),t=await navigator.mediaDevices.getUserMedia({video:{width:e?.resolution.width||640,height:e?.resolution.height||480,facingMode:"user"},audio:!1});return m.current=t,C.current&&(C.current.srcObject=t,C.current.play().catch(e=>{})),t}catch(e){throw new Error(`获取摄像头权限失败: ${e instanceof Error?e.message:"未知错误"}`)}},[]),I=e.useCallback(async()=>{const e=C.current,t=T.current;if(!e||!t)return null;if(e.readyState<2)return null;if(0===e.videoWidth||0===e.videoHeight)return null;const i=t.getContext("2d");return i?(t.width=e.videoWidth,t.height=e.videoHeight,i.clearRect(0,0,t.width,t.height),i.drawImage(e,0,0,t.width,t.height),new Promise(e=>{t.toBlob(e,"image/jpeg",.8)})):null},[]),_=e.useCallback(async()=>{if(!O.current)return;const e=Date.now(),t=P().getConfig();if(!(e-D.current<t.checkInterval))try{const t=await I();if(!t)throw new Error("无法捕获图像");const i=Date.now(),o=await O.current.detect(t),n=Date.now();E(o),D.current=e,p(e=>{const t={...e};t.checkCount++,t.latency=n-i,o.processingTime&&(t.avgProcessingTime=(e.avgProcessingTime*(e.checkCount-1)+o.processingTime)/e.checkCount),o.violations.length>0&&(t.abnormalCount++,o.violations.forEach(e=>{t.violationStats[e.type]=(t.violationStats[e.type]||0)+1}));const s=Date.now();if(w.current>0){const e=s-w.current;t.fps=Math.round(1e3/e)}return w.current=s,t.latency<200?t.networkQuality="excellent":t.latency<500?t.networkQuality="good":t.latency<1e3?t.networkQuality="fair":t.networkQuality="poor",t}),c?.(o),o.violations.forEach(e=>{a?.(e)})}catch(i){const e=i instanceof Error?i:new Error("检测失败");l?.(e)}},[I,c,a,l]),L=e.useCallback(async()=>{try{await A(),u(!0)}catch(e){const t=e instanceof Error?e:new Error("启动监控失败");throw l?.(t),t}},[A,l]),M=e.useCallback(()=>{u(!1),m.current&&(m.current.getTracks().forEach(e=>e.stop()),m.current=null),C.current&&(C.current.srcObject=null),S.current&&(clearInterval(S.current),S.current=null)},[]),N=e.useCallback(async()=>{await _()},[_]),v=e.useCallback(async e=>y.current?y.current.updateConfig(e,{source:"local",operator:"hook-user"}):{success:!1,errors:["配置管理器未初始化"]},[]),b=e.useCallback(()=>{const e=y.current?.getConfig();return e&&e.checkInterval?e:t},[]),P=e.useCallback(()=>{if(!y.current)throw new Error("配置管理器未初始化");return y.current},[]);return e.useEffect(()=>()=>{M()},[M]),e.useEffect(()=>(g?S.current=window.setInterval(_,1e3):S.current&&(window.clearInterval(S.current),S.current=null),()=>{S.current&&window.clearInterval(S.current)}),[g,_]),e.useEffect(()=>()=>{M()},[M]),{videoRef:C,canvasRef:T,startMonitoring:L,stopMonitoring:M,forceCheck:N,isMonitoring:g,latestResult:d,stats:f,config:b(),updateConfig:v,getConfigManager:P}};
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/types/config.ts","../src/constants/cheating-types.ts","../src/utils/config-manager.ts","../src/utils/oss-client.ts","../src/utils/detection-engine.ts","../src/index.ts","../src/utils/audit-logger.ts","../src/hooks/useAntiCheatingMonitor.ts"],"sourcesContent":["/**\n * 检测配置接口\n */\nexport interface DetectionConfig {\n /** 检测间隔(毫秒) */\n checkInterval: number;\n /** 是否检测多人 */\n detectMultiplePeople: boolean;\n /** 是否检测设备使用 */\n detectDeviceUsage: boolean;\n /** 是否检测离席 */\n detectAbsence: boolean;\n /** 视频分辨率 */\n resolution: {\n /** 视频宽度 */\n width: number;\n /** 视频高度 */\n height: number;\n };\n /** 检测类型\n * 0: 屏幕聊天工具检测\n * 1: 考生状态检测\n */\n type: number;\n /** 耳机检测阈值 */\n earphoneThreshold: number;\n /** 手机检测阈值 */\n cellphoneThreshold: number;\n /** 头部姿态阈值 */\n headPoseThreshold: {\n /** 俯仰角阈值 */\n pitch: number;\n /** 翻滚角阈值 */\n roll: number;\n /** 偏航角阈值 */\n yaw: number;\n };\n /** 人脸完整度阈值 */\n faceCompletenessThreshold: number;\n /** 人脸居中阈值 */\n centeringThreshold: number;\n /** 可疑移动检测阈值 */\n movementThreshold: number;\n}\n\n/**\n * 默认检测配置\n */\nexport const DEFAULT_DETECTION_CONFIG: DetectionConfig = {\n checkInterval: 3000,\n detectMultiplePeople: true,\n detectDeviceUsage: true,\n detectAbsence: true,\n resolution: {\n width: 640,\n height: 480,\n },\n type: 1,\n /** 耳机检测阈值 */\n earphoneThreshold: 0.6,\n /** 手机检测阈值 */\n cellphoneThreshold: 0.6,\n headPoseThreshold: {\n pitch: 30, // 俯仰角 ±30度\n roll: 30, // 翻滚角 ±30度\n yaw: 30, // 偏航角 ±30度\n },\n /** 人脸完整度阈值 */\n faceCompletenessThreshold: 0.8,\n /** 人脸居中阈值 */\n centeringThreshold: 0.7,\n /** 可疑移动检测阈值 */\n movementThreshold: 0.3,\n};\n\n/**\n * 阿里云OSS配置接口\n */\nexport interface OSSConfig {\n /** OSS存储桶名称 */\n bucket: string;\n /** OSS区域 */\n region: string;\n /** 访问域名(可选) */\n endpoint?: string;\n /** 安全令牌(可选,用于STS临时访问) */\n securityToken?: string;\n}\n\n/**\n * 配置更新选项\n */\nexport interface ConfigUpdateOptions {\n /** 是否强制更新(忽略验证) */\n force?: boolean;\n /** 更新来源 */\n source?: \"local\" | \"remote\" | \"backend_sync\";\n /** 操作者标识 */\n operator?: string;\n}\n","/**\n * 作弊类型枚举\n * 基于Java后端逻辑映射的前端检测类型\n */\nexport enum CheatingType {\n /** 人数异常 - 检测到多人 */\n PERSON_COUNT_ANOMALY = \"PERSON_COUNT_ANOMALY\",\n /** 检测到耳机 */\n EARPHONE_DETECTED = \"EARPHONE_DETECTED\", \n /** 检测到手机 */\n CELLPHONE_DETECTED = \"CELLPHONE_DETECTED\",\n /** 头部姿态异常 */\n HEAD_POSTURE_ABNORMAL = \"HEAD_POSTURE_ABNORMAL\",\n /** 人脸缺失 */\n FACE_MISSING = \"FACE_MISSING\",\n /** 人脸被遮挡 */\n FACE_OCCLUDED = \"FACE_OCCLUDED\",\n /** 未检测到人脸 */\n NO_FACE_DETECTED = \"NO_FACE_DETECTED\",\n /** 检测到多张人脸 */\n MULTIPLE_FACES_DETECTED = \"MULTIPLE_FACES_DETECTED\",\n /** 未居中 */\n NOT_CENTERED = \"NOT_CENTERED\",\n /** 可疑移动 */\n SUSPICIOUS_MOVEMENT = \"SUSPICIOUS_MOVEMENT\"\n}\n\n/**\n * 作弊类型显示名称映射\n */\nexport const CHEATING_TYPE_LABELS: Record<CheatingType, string> = {\n [CheatingType.PERSON_COUNT_ANOMALY]: \"人数异常\",\n [CheatingType.EARPHONE_DETECTED]: \"检测到耳机\",\n [CheatingType.CELLPHONE_DETECTED]: \"检测到手机\", \n [CheatingType.HEAD_POSTURE_ABNORMAL]: \"头部姿态异常\",\n [CheatingType.FACE_MISSING]: \"人脸缺失\",\n [CheatingType.FACE_OCCLUDED]: \"人脸被遮挡\",\n [CheatingType.NO_FACE_DETECTED]: \"未检测到人脸\",\n [CheatingType.MULTIPLE_FACES_DETECTED]: \"检测到多张人脸\",\n [CheatingType.NOT_CENTERED]: \"未居中\",\n [CheatingType.SUSPICIOUS_MOVEMENT]: \"可疑移动\"\n} as const;\n\n/**\n * 违规严重程度\n */\nexport enum ViolationLevel {\n /** 低风险 - 轻微违规 */\n LOW = \"low\",\n /** 中风险 - 需要注意 */\n MEDIUM = \"medium\", \n /** 高风险 - 严重违规 */\n HIGH = \"high\",\n /** 紧急 - 需要立即处理 */\n CRITICAL = \"critical\"\n}\n\n/**\n * 作弊类型严重程度映射\n */\nexport const CHEATING_TYPE_SEVERITY: Record<CheatingType, ViolationLevel> = {\n [CheatingType.PERSON_COUNT_ANOMALY]: ViolationLevel.HIGH,\n [CheatingType.EARPHONE_DETECTED]: ViolationLevel.MEDIUM,\n [CheatingType.CELLPHONE_DETECTED]: ViolationLevel.HIGH,\n [CheatingType.HEAD_POSTURE_ABNORMAL]: ViolationLevel.MEDIUM,\n [CheatingType.FACE_MISSING]: ViolationLevel.HIGH,\n [CheatingType.FACE_OCCLUDED]: ViolationLevel.MEDIUM,\n [CheatingType.NO_FACE_DETECTED]: ViolationLevel.HIGH,\n [CheatingType.MULTIPLE_FACES_DETECTED]: ViolationLevel.HIGH,\n [CheatingType.NOT_CENTERED]: ViolationLevel.LOW,\n [CheatingType.SUSPICIOUS_MOVEMENT]: ViolationLevel.MEDIUM\n} as const;","import { DetectionConfig, ConfigUpdateOptions, DEFAULT_DETECTION_CONFIG } from '../types';\nimport { ConfigAuditLog } from '../types';\n\n/**\n * 配置管理器类\n * 负责阈值配置的管理、热更新和审计\n */\nexport class ConfigManager {\n private static instance: ConfigManager;\n private config: DetectionConfig;\n private auditLogs: ConfigAuditLog[] = [];\n private listeners: Set<(config: DetectionConfig) => void> = new Set();\n private syncTimer?: number;\n private readonly STORAGE_KEY = 'anti-cheating-config';\n\n private constructor() {\n this.config = this.getDefaultConfig();\n this.loadFromStorage();\n this.startHotUpdate();\n }\n\n /**\n * 获取单例实例\n */\n public static getInstance(): ConfigManager {\n if (!ConfigManager.instance) {\n ConfigManager.instance = new ConfigManager();\n }\n return ConfigManager.instance;\n }\n\n /**\n * 获取默认配置\n */\n private getDefaultConfig(): DetectionConfig {\n return { ...DEFAULT_DETECTION_CONFIG };\n }\n\n /**\n * 从本地存储加载配置\n */\n private loadFromStorage(): void {\n try {\n const stored = localStorage.getItem(this.STORAGE_KEY);\n if (stored) {\n const parsedConfig = JSON.parse(stored);\n this.config = { ...this.config, ...parsedConfig };\n }\n } catch (error) {\n console.warn('加载本地配置失败:', error);\n }\n }\n\n /**\n * 保存配置到本地存储\n */\n private saveToStorage(): void {\n try {\n localStorage.setItem(this.STORAGE_KEY, JSON.stringify(this.config));\n } catch (error) {\n console.warn('保存本地配置失败:', error);\n }\n }\n\n /**\n * 启动热更新监听\n */\n private startHotUpdate(): void {\n // 监听storage事件,实现跨标签页配置同步\n const handleStorageChange = (e: StorageEvent) => {\n if (e.key === this.STORAGE_KEY && e.newValue) {\n try {\n const newConfig = JSON.parse(e.newValue);\n this.updateConfig(newConfig, { source: 'remote' });\n } catch (error) {\n console.warn('热更新配置失败:', error);\n }\n }\n };\n\n window.addEventListener('storage', handleStorageChange);\n }\n\n /**\n * 添加审计日志\n */\n private addAuditLog(\n operation: ConfigAuditLog['operation'],\n configKey: string,\n oldValue?: any,\n newValue?: any,\n source: ConfigAuditLog['source'] = 'local',\n operator?: string\n ): void {\n const log: ConfigAuditLog = {\n id: `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,\n timestamp: Date.now(),\n operation,\n configKey,\n oldValue,\n newValue,\n operator,\n source\n };\n\n this.auditLogs.push(log);\n \n // 限制日志数量,避免内存泄漏\n if (this.auditLogs.length > 1000) {\n this.auditLogs = this.auditLogs.slice(-500);\n }\n }\n\n /**\n * 验证配置有效性\n */\n private validateConfig(config: Partial<DetectionConfig>): string[] {\n const errors: string[] = [];\n\n if (config.checkInterval !== undefined) {\n if (config.checkInterval < 1000 || config.checkInterval > 60000) {\n errors.push('检测间隔应在1000-60000毫秒之间');\n }\n }\n\n if (config.resolution) {\n if (config.resolution.width < 320 || config.resolution.width > 1920) {\n errors.push('视频宽度应在320-1920之间');\n }\n if (config.resolution.height < 240 || config.resolution.height > 1080) {\n errors.push('视频高度应在240-1080之间');\n }\n }\n\n if (config.earphoneThreshold !== undefined) {\n if (config.earphoneThreshold < 0 || config.earphoneThreshold > 1) {\n errors.push('耳机检测阈值应在0-1之间');\n }\n }\n\n if (config.cellphoneThreshold !== undefined) {\n if (config.cellphoneThreshold < 0 || config.cellphoneThreshold > 1) {\n errors.push('手机检测阈值应在0-1之间');\n }\n }\n\n return errors;\n }\n\n /**\n * 获取当前配置\n */\n public getConfig(): DetectionConfig {\n return { ...this.config };\n }\n\n /**\n * 更新配置\n */\n public updateConfig(\n newConfig: Partial<DetectionConfig>, \n options: ConfigUpdateOptions = {}\n ): { success: boolean; errors?: string[] } {\n const { force = false, source = 'local', operator } = options;\n\n // 验证配置\n if (!force) {\n const errors = this.validateConfig(newConfig);\n if (errors.length > 0) {\n return { success: false, errors };\n }\n }\n\n // 记录变更\n Object.entries(newConfig).forEach(([key, value]) => {\n const oldValue = (this.config as any)[key];\n if (JSON.stringify(oldValue) !== JSON.stringify(value)) {\n this.addAuditLog('update', key, oldValue, value, source, operator);\n }\n });\n\n // 更新配置\n this.config = { ...this.config, ...newConfig };\n\n // 保存到本地存储\n if (source === 'local') {\n this.saveToStorage();\n }\n\n // 通知监听器\n this.notifyListeners();\n\n return { success: true };\n }\n\n /**\n * 重置配置为默认值\n */\n public resetConfig(source: ConfigAuditLog['source'] = 'local', operator?: string): void {\n const oldConfig = { ...this.config };\n this.config = this.getDefaultConfig();\n\n // 记录重置操作\n Object.keys(oldConfig).forEach(key => {\n this.addAuditLog('update', key, oldConfig[key as keyof DetectionConfig], this.config[key as keyof DetectionConfig], source, operator);\n });\n\n this.saveToStorage();\n this.notifyListeners();\n }\n\n /**\n * 添加配置变更监听器\n */\n public addListener(listener: (config: DetectionConfig) => void): () => void {\n this.listeners.add(listener);\n \n // 返回取消监听的函数\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n /**\n * 通知所有监听器\n */\n private notifyListeners(): void {\n this.listeners.forEach(listener => {\n try {\n listener(this.getConfig());\n } catch (error) {\n console.warn('配置监听器执行失败:', error);\n }\n });\n }\n\n /**\n * 获取审计日志\n */\n public getAuditLogs(): ConfigAuditLog[] {\n return [...this.auditLogs];\n }\n\n /**\n * 清理审计日志\n */\n public clearAuditLogs(olderThan?: number): void {\n if (olderThan) {\n this.auditLogs = this.auditLogs.filter(log => log.timestamp > olderThan);\n } else {\n this.auditLogs = [];\n }\n }\n\n /**\n * 销毁实例\n */\n public destroy(): void {\n if (this.syncTimer) {\n clearInterval(this.syncTimer);\n }\n this.listeners.clear();\n }\n}","import { OSSConfig } from '../types/config';\nimport { AntiCheatingCredentials } from '../types/detection';\nimport type OSS from 'ali-oss';\n\n/**\n * OSS客户端类\n * 用于上传图片到阿里云OSS\n */\nexport class OSSClient {\n private config: OSSConfig;\n private credentials: AntiCheatingCredentials;\n private isInitialized: boolean = false;\n private client: OSS | null = null;\n\n constructor(config: OSSConfig, credentials: AntiCheatingCredentials) {\n this.config = config;\n this.credentials = credentials;\n }\n\n /**\n * 初始化OSS客户端\n */\n public async initialize(): Promise<void> {\n if (this.isInitialized) {\n return Promise.resolve();\n }\n\n // 验证配置\n if (!this.config.bucket || !this.config.region) {\n throw new Error('OSS配置不完整:缺少bucket或region');\n }\n\n if (!this.credentials.accessKeyId || !this.credentials.accessKeySecret) {\n throw new Error('OSS凭证不完整:缺少accessKeyId或accessKeySecret');\n }\n\n // 初始化阿里云OSS客户端\n try {\n // 动态导入 ali-oss,避免在 SSR 阶段加载导致 exports is not defined 错误\n // 同时也避免了 Node.js 依赖 (proxy-agent) 的问题\n // @ts-ignore\n const OSSModule = await import('ali-oss/dist/aliyun-oss-sdk.min.js');\n const OSSClass = (OSSModule.default || OSSModule) as unknown as typeof OSS;\n\n this.client = new OSSClass({\n region: this.config.region,\n accessKeyId: this.credentials.accessKeyId,\n accessKeySecret: this.credentials.accessKeySecret,\n stsToken: this.credentials.securityToken,\n bucket: this.config.bucket,\n });\n\n this.isInitialized = true;\n } catch (error) {\n console.error('Failed to load ali-oss:', error);\n throw new Error('无法加载 OSS 客户端库');\n }\n }\n\n /**\n * 上传文件到OSS\n * @param blob 文件数据\n * @param dir 存储目录\n * @returns 文件URL\n */\n public async put(blob: Blob, dir: string = 'monitor/'): Promise<{ url: string }> {\n if (!this.isInitialized) {\n await this.initialize();\n }\n\n if (!this.client) {\n throw new Error('OSS客户端未初始化');\n }\n\n // 生成文件名\n const filename = `${Date.now()}-${Math.random().toString(36).slice(2)}.jpg`;\n const objectKey = dir + filename;\n\n try {\n // 使用阿里云OSS SDK进行真实上传\n const result = await this.client.put(objectKey, blob, {\n headers: {\n 'Cache-Control': 'no-cache',\n 'Content-Disposition': `inline; filename=\"${filename}\"`,\n },\n });\n\n // 确保返回的是公开访问的URL\n const publicUrl = result.url || `https://${this.config.bucket}.${this.config.region}.aliyuncs.com/${objectKey}`;\n \n return { url: publicUrl };\n\n } catch (error) {\n throw new Error(`阿里云 OSS 上传失败: ${error instanceof Error ? error.message : '未知错误'}`);\n }\n }\n\n \n\n /**\n * 检查客户端是否已初始化\n */\n public get ready(): boolean {\n return this.isInitialized;\n }\n\n /**\n * 更新配置\n */\n public updateConfig(config: Partial<OSSConfig>): void {\n this.config = { ...this.config, ...config };\n this.isInitialized = false; // 需要重新初始化\n }\n\n /**\n * 更新凭证\n */\n public updateCredentials(credentials: Partial<AntiCheatingCredentials>): void {\n this.credentials = { ...this.credentials, ...credentials };\n this.isInitialized = false; // 需要重新初始化\n }\n}","import { \n DetectionResult, \n DetectionConfig, \n VIAPIRequestParams, \n VIAPIResponse,\n CheatingViolation,\n AntiCheatingCredentials\n} from '../types';\nimport { CheatingType, ViolationLevel, CHEATING_TYPE_SEVERITY, CHEATING_TYPE_LABELS } from '../constants';\nimport { OSSConfig } from '../types/config';\nimport { OSSClient } from './oss-client';\n\n/**\n * 检测引擎类\n * 负责执行反作弊检测逻辑,集成OSS上传和阿里云VIAPI调用\n */\nexport class DetectionEngine {\n private config: DetectionConfig;\n private credentials: AntiCheatingCredentials;\n private ossConfig: OSSConfig;\n private ossClient: OSSClient;\n\n constructor(config: DetectionConfig, credentials: AntiCheatingCredentials, ossConfig: OSSConfig) {\n this.config = config;\n this.credentials = credentials;\n this.ossConfig = ossConfig;\n this.ossClient = new OSSClient(ossConfig, credentials);\n }\n\n /**\n * 更新配置\n */\n public updateConfig(config: DetectionConfig): void {\n this.config = config;\n }\n\n /**\n * 更新凭证\n */\n public updateCredentials(credentials: AntiCheatingCredentials): void {\n this.credentials = credentials;\n this.ossClient.updateCredentials(credentials);\n }\n\n /**\n * 更新OSS配置\n */\n public updateOSSConfig(ossConfig: OSSConfig): void {\n this.ossConfig = ossConfig;\n this.ossClient.updateConfig(ossConfig);\n }\n\n /**\n * 执行检测\n * @param imageData 图像数据(Base64或Blob)\n */\n public async detect(imageData: string | Blob): Promise<DetectionResult> {\n const startTime = Date.now();\n\n try {\n let imageUrl: string;\n\n // 如果是Blob,先上传到OSS\n if (imageData instanceof Blob) {\n const uploadResult = await this.ossClient.put(imageData);\n imageUrl = uploadResult.url;\n } else {\n // 如果是Base64,转换为Blob后上传\n const blob = this.base64ToBlob(imageData);\n const uploadResult = await this.ossClient.put(blob);\n imageUrl = uploadResult.url;\n }\n\n // 调用阿里云VIAPI\n const apiResponse = await this.callVIAPI(imageUrl);\n \n // 处理检测结果\n const result = this.processAPIResponse(apiResponse);\n \n // 计算处理时间\n result.processingTime = Date.now() - startTime;\n \n return result;\n } catch (error) {\n // 返回错误结果\n return {\n timestamp: Date.now(),\n faceCount: 0,\n faceCompleteness: 0,\n personCount: 0,\n violations: [{\n type: CheatingType.NO_FACE_DETECTED,\n level: ViolationLevel.HIGH,\n confidence: 1.0,\n description: `检测失败: ${error instanceof Error ? error.message : '未知错误'}`\n }],\n processingTime: Date.now() - startTime\n };\n }\n }\n\n /**\n * Base64转Blob\n * @private\n */\n private base64ToBlob(base64: string): Blob {\n const parts = base64.split(',');\n const mimeType = parts[0].match(/:(.*?);/)?.[1] || 'image/jpeg';\n const byteString = atob(parts[1]);\n const arrayBuffer = new ArrayBuffer(byteString.length);\n const uint8Array = new Uint8Array(arrayBuffer);\n \n for (let i = 0; i < byteString.length; i++) {\n uint8Array[i] = byteString.charCodeAt(i);\n }\n \n return new Blob([arrayBuffer], { type: mimeType });\n }\n\n /**\n * 调用阿里云VIAPI\n * @private\n */\n private async callVIAPI(imageUrl: string): Promise<VIAPIResponse> {\n // 阿里云API常量\n const API_ENDPOINT = \"https://facebody.cn-shanghai.aliyuncs.com\";\n const API_VERSION = \"2019-12-30\";\n\n // 构建请求参数\n const params: Record<string, string> = {\n Action: \"MonitorExamination\",\n Version: API_VERSION,\n Format: \"JSON\",\n AccessKeyId: this.credentials.accessKeyId,\n SignatureMethod: \"HMAC-SHA1\",\n Timestamp: this.getTimestamp(),\n SignatureVersion: \"1.0\",\n SignatureNonce: this.generateNonce(),\n RegionId: \"cn-shanghai\",\n Type: String(this.config.type),\n ImageURL: imageUrl,\n };\n\n // 如果有安全令牌,添加到参数中\n if (this.credentials.securityToken) {\n params.SecurityToken = this.credentials.securityToken;\n }\n\n // 1. 对参数进行排序\n const sortedKeys = Object.keys(params).sort();\n\n // 2. 构建规范化的查询字符串\n const canonicalizedQueryString = sortedKeys\n .map((key) => `${this.percentEncode(key)}=${this.percentEncode(params[key])}`)\n .join(\"&\");\n\n // 3. 构建待签名字符串\n const stringToSign = `POST&${this.percentEncode(\"/\")}&${this.percentEncode(\n canonicalizedQueryString\n )}`;\n\n // 4. 计算签名\n const signature = await this.hmacSha1(\n `${this.credentials.accessKeySecret}&`,\n stringToSign\n );\n\n // 5. 构建 Body 内容 (POST请求)\n const bodyString = `${canonicalizedQueryString}&Signature=${this.percentEncode(\n signature\n )}`;\n\n // 发送请求\n const response = await fetch(API_ENDPOINT, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n },\n body: bodyString,\n mode: \"cors\",\n });\n\n if (!response.ok) {\n throw new Error(\n `API Error: ${response.status} ${response.statusText}`\n );\n }\n\n const data = await response.json();\n\n // 直接返回API响应数据,已经是标准格式\n return data as VIAPIResponse;\n }\n\n /**\n * 使用HMAC-SHA1算法生成签名\n * @private\n */\n private async hmacSha1(key: string, data: string): Promise<string> {\n const encoder = new TextEncoder();\n const keyData = encoder.encode(key);\n const dataData = encoder.encode(data);\n\n const cryptoKey = await window.crypto.subtle.importKey(\n \"raw\",\n keyData,\n { name: \"HMAC\", hash: \"SHA-1\" },\n false,\n [\"sign\"]\n );\n\n const signature = await window.crypto.subtle.sign(\n \"HMAC\",\n cryptoKey,\n dataData\n );\n \n return btoa(String.fromCharCode(...Array.from(new Uint8Array(signature))));\n }\n\n /**\n * URL编码函数\n * @private\n */\n private percentEncode(str: string): string {\n return encodeURIComponent(str)\n .replace(/!/g, \"%21\")\n .replace(/'/g, \"%27\")\n .replace(/\\(/g, \"%28\")\n .replace(/\\)/g, \"%29\")\n .replace(/\\*/g, \"%2A\");\n }\n\n /**\n * 获取当前时间戳\n * @private\n */\n private getTimestamp(): string {\n return new Date().toISOString().replace(/\\.\\d{3}/, \"\");\n }\n\n /**\n * 生成随机字符串\n * @private\n */\n private generateNonce(): string {\n return (\n Math.random().toString(36).substring(2, 15) +\n Math.random().toString(36).substring(2, 15)\n );\n }\n\n /**\n * 处理API响应,生成检测结果\n * @private\n */\n private processAPIResponse(response: VIAPIResponse): DetectionResult {\n const violations: CheatingViolation[] = [];\n\n // 从正确的数据路径获取信息\n const faceInfo = response.Data?.FaceInfo as any || {};\n const personInfo = response.Data?.PersonInfo as any || {};\n const faceNumber = faceInfo.FaceNumber || 0;\n const personNumber = personInfo.PersonNumber || 0;\n const completeness = faceInfo.Completeness || 0;\n const pose = faceInfo.Pose || {};\n\n // 1. 检测人脸数量\n if (faceNumber === 0) {\n violations.push(this.createViolation(\n CheatingType.NO_FACE_DETECTED,\n ViolationLevel.HIGH,\n 1.0,\n '未检测到人脸'\n ));\n } else if (faceNumber > 1) {\n violations.push(this.createViolation(\n CheatingType.MULTIPLE_FACES_DETECTED,\n ViolationLevel.HIGH,\n 1.0,\n `检测到${faceNumber}张人脸`\n ));\n }\n\n // 2. 检测人员数量\n if (this.config.detectMultiplePeople && personNumber > 1) {\n violations.push(this.createViolation(\n CheatingType.PERSON_COUNT_ANOMALY,\n ViolationLevel.HIGH,\n 1.0,\n `检测到${personNumber}人`\n ));\n }\n\n // 3. 检测人脸完整度\n if (completeness < this.config.faceCompletenessThreshold) {\n violations.push(this.createViolation(\n CheatingType.FACE_OCCLUDED,\n ViolationLevel.MEDIUM,\n 1.0 - completeness,\n `人脸完整度${(completeness * 100).toFixed(0)}%`\n ));\n }\n\n // 4. 检测头部姿态\n if (pose) {\n const { Pitch, Roll, Yaw } = pose;\n const { pitch: pThresh, roll: rThresh, yaw: yThresh } = this.config.headPoseThreshold;\n\n if (Math.abs(Pitch) > pThresh || Math.abs(Roll) > rThresh || Math.abs(Yaw) > yThresh) {\n violations.push(this.createViolation(\n CheatingType.HEAD_POSTURE_ABNORMAL,\n ViolationLevel.MEDIUM,\n Math.max(\n Math.abs(Pitch) / pThresh,\n Math.abs(Roll) / rThresh,\n Math.abs(Yaw) / yThresh\n ) - 1,\n `头部姿态异常: ${Pitch > 0 ? '抬头' : '低头'}${Pitch.toFixed(1)}° 偏头${Roll.toFixed(1)}° ${Yaw > 0 ? '左转' : '右转'}${Yaw.toFixed(1)}°`\n ));\n }\n }\n\n // 5. 检测设备使用\n if (this.config.detectDeviceUsage) {\n // 耳机检测\n const earphoneScore = personInfo.EarPhone?.Score || 0;\n if (earphoneScore > this.config.earphoneThreshold) {\n violations.push(this.createViolation(\n CheatingType.EARPHONE_DETECTED,\n ViolationLevel.MEDIUM,\n earphoneScore,\n `检测到耳机 (置信度: ${(earphoneScore * 100).toFixed(0)}%)`\n ));\n }\n\n // 手机检测\n const cellphoneScore = personInfo.CellPhone?.Score || 0;\n if (cellphoneScore > this.config.cellphoneThreshold) {\n violations.push(this.createViolation(\n CheatingType.CELLPHONE_DETECTED,\n ViolationLevel.HIGH,\n cellphoneScore,\n `检测到手机 (置信度: ${(cellphoneScore * 100).toFixed(0)}%)`\n ));\n }\n }\n\n // 6. 检测居中程度(基于姿态数据估算)\n if (pose) {\n const centeringScore = this.calculateCenteringScore(pose);\n if (centeringScore < this.config.centeringThreshold) {\n violations.push(this.createViolation(\n CheatingType.NOT_CENTERED,\n ViolationLevel.LOW,\n 1.0 - centeringScore,\n `未居中 (居中度: ${(centeringScore * 100).toFixed(0)}%)`\n ));\n }\n }\n\n return {\n timestamp: Date.now(),\n faceCount: faceNumber,\n faceCompleteness: completeness,\n personCount: personNumber,\n pose: pose,\n violations,\n rawResponse: response\n };\n }\n\n /**\n * 创建违规对象\n * @private\n */\n private createViolation(\n type: CheatingType,\n level: ViolationLevel,\n confidence: number,\n description: string,\n data?: any\n ): CheatingViolation {\n return {\n type,\n level,\n confidence: Math.max(0, Math.min(1, confidence)),\n description,\n data\n };\n }\n\n /**\n * 计算居中程度\n * @private\n */\n private calculateCenteringScore(pose: { Pitch: number; Roll: number; Yaw: number }): number {\n const { Pitch, Roll, Yaw } = pose;\n \n // 基于姿态角度计算居中程度\n const pitchScore = Math.max(0, 1 - Math.abs(Pitch) / 30);\n const rollScore = Math.max(0, 1 - Math.abs(Roll) / 45);\n const yawScore = Math.max(0, 1 - Math.abs(Yaw) / 60);\n \n return (pitchScore + rollScore + yawScore) / 3;\n }\n\n /**\n * 获取检测统计信息\n */\n public getDetectionStats(): {\n totalDetections: number;\n averageProcessingTime: number;\n violationRate: number;\n mostCommonViolations: Array<{ type: CheatingType; count: number }>;\n } {\n // 这里应该从历史记录中计算统计信息\n // 为了简化,返回模拟数据\n return {\n totalDetections: 0,\n averageProcessingTime: 0,\n violationRate: 0,\n mostCommonViolations: []\n };\n }\n}","/**\n * 反作弊监控库\n * \n * 基于阿里云视觉智能平台的实时反作弊监控系统\n * 提供人脸检测、异常行为分析和完整的在线监考解决方案\n * \n * @author Anti-Cheating Monitor Team\n * @version 2.0.0\n * @license MIT\n */\n\n// 导出主要Hook\nexport { useAntiCheatingMonitor } from './hooks';\nexport type { UseAntiCheatingMonitorProps, UseAntiCheatingMonitorReturn } from './hooks';\n\n// 导出类型定义\nexport * from './types';\n\n// 导出工具类\nexport { ConfigManager } from './utils';\nexport { DetectionEngine } from './utils';\nexport { AuditLogger } from './utils';\n\n// 导出常量\nexport { CheatingType, ViolationLevel, CHEATING_TYPE_LABELS, CHEATING_TYPE_SEVERITY } from './constants';\n\n// 版本信息\nexport const VERSION = '2.0.0';\n\n/**\n * 库信息\n */\nexport const LIB_INFO = {\n name: 'anti-cheating-monitor',\n version: VERSION,\n description: '基于阿里云视觉智能平台的实时反作弊监控系统',\n author: 'Anti-Cheating Monitor Team',\n license: 'MIT',\n repository: 'https://github.com/coding-daily-wq/anti-cheating-monitor.git',\n homepage: 'https://coding-daily-wq.github.io/anti-cheating-monitor'\n} as const;","import { ConfigAuditLog, AuditLogQueryOptions, AuditLogStats } from '../types';\n\n/**\n * 审计日志管理器类\n * 负责管理和分析配置变更审计日志\n */\nexport class AuditLogger {\n private logs: ConfigAuditLog[] = [];\n private readonly MAX_LOGS = 1000; // 最大日志数量限制\n\n /**\n * 添加审计日志\n */\n public addLog(log: Omit<ConfigAuditLog, 'id' | 'timestamp'>): ConfigAuditLog {\n const auditLog: ConfigAuditLog = {\n ...log,\n id: this.generateLogId(),\n timestamp: Date.now()\n };\n\n this.logs.unshift(auditLog); // 新日志添加到开头\n\n // 限制日志数量\n if (this.logs.length > this.MAX_LOGS) {\n this.logs = this.logs.slice(0, this.MAX_LOGS);\n }\n\n // 持久化到本地存储\n this.persistLogs();\n\n return auditLog;\n }\n\n /**\n * 生成日志ID\n */\n private generateLogId(): string {\n return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;\n }\n\n /**\n * 查询审计日志\n */\n public queryLogs(options: AuditLogQueryOptions = {}): ConfigAuditLog[] {\n let filteredLogs = [...this.logs];\n\n // 时间范围过滤\n if (options.startTime !== undefined) {\n filteredLogs = filteredLogs.filter(log => log.timestamp >= options.startTime!);\n }\n if (options.endTime !== undefined) {\n filteredLogs = filteredLogs.filter(log => log.timestamp <= options.endTime!);\n }\n\n // 操作类型过滤\n if (options.operation) {\n filteredLogs = filteredLogs.filter(log => log.operation === options.operation);\n }\n\n // 配置项过滤\n if (options.configKey) {\n filteredLogs = filteredLogs.filter(log => log.configKey === options.configKey);\n }\n\n // 操作者过滤\n if (options.operator) {\n filteredLogs = filteredLogs.filter(log => log.operator === options.operator);\n }\n\n // 来源过滤\n if (options.source) {\n filteredLogs = filteredLogs.filter(log => log.source === options.source);\n }\n\n // 分页\n const offset = options.offset || 0;\n const limit = options.limit || 50;\n filteredLogs = filteredLogs.slice(offset, offset + limit);\n\n return filteredLogs;\n }\n\n /**\n * 获取所有日志\n */\n public getAllLogs(): ConfigAuditLog[] {\n return [...this.logs];\n }\n\n /**\n * 获取日志统计信息\n */\n public getStats(): AuditLogStats {\n const operationStats: Record<ConfigAuditLog['operation'], number> = {\n create: 0,\n update: 0,\n delete: 0,\n sync: 0\n };\n\n const sourceStats: Record<ConfigAuditLog['source'], number> = {\n local: 0,\n remote: 0,\n backend_sync: 0\n };\n\n const configCounts: Record<string, number> = {};\n const operatorCounts: Record<string, number> = {};\n\n this.logs.forEach(log => {\n // 操作类型统计\n operationStats[log.operation]++;\n\n // 来源统计\n sourceStats[log.source]++;\n\n // 配置项统计\n configCounts[log.configKey] = (configCounts[log.configKey] || 0) + 1;\n\n // 操作者统计\n if (log.operator) {\n operatorCounts[log.operator] = (operatorCounts[log.operator] || 0) + 1;\n }\n });\n\n // 最活跃的配置项(前5个)\n const mostActiveConfigs = Object.entries(configCounts)\n .sort(([, a], [, b]) => b - a)\n .slice(0, 5)\n .map(([configKey, count]) => ({ configKey, count }));\n\n // 最活跃的操作者(前5个)\n const mostActiveOperators = Object.entries(operatorCounts)\n .sort(([, a], [, b]) => b - a)\n .slice(0, 5)\n .map(([operator, count]) => ({ operator, count }));\n\n return {\n totalLogs: this.logs.length,\n operationStats,\n sourceStats,\n mostActiveConfigs,\n mostActiveOperators\n };\n }\n\n /**\n * 清理旧日志\n */\n public cleanup(olderThan: number): number {\n const beforeCount = this.logs.length;\n this.logs = this.logs.filter(log => log.timestamp > olderThan);\n const cleanedCount = beforeCount - this.logs.length;\n\n if (cleanedCount > 0) {\n this.persistLogs();\n }\n\n return cleanedCount;\n }\n\n /**\n * 清空所有日志\n */\n public clear(): void {\n this.logs = [];\n this.persistLogs();\n }\n\n /**\n * 导出日志为JSON\n */\n public exportLogs(format: 'json' | 'csv' = 'json'): string {\n if (format === 'json') {\n return JSON.stringify(this.logs, null, 2);\n } else if (format === 'csv') {\n return this.convertToCSV(this.logs);\n } else {\n throw new Error(`不支持的导出格式: ${format}`);\n }\n }\n\n /**\n * 导入日志\n */\n public importLogs(logsData: string, format: 'json' | 'csv' = 'json'): number {\n let importedLogs: ConfigAuditLog[];\n\n if (format === 'json') {\n try {\n importedLogs = JSON.parse(logsData);\n } catch (error) {\n throw new Error('JSON格式错误');\n }\n } else {\n throw new Error(`不支持的导入格式: ${format}`);\n }\n\n // 验证日志格式\n const validLogs = importedLogs.filter(log => this.validateLog(log));\n\n // 合并日志(避免重复)\n const existingIds = new Set(this.logs.map(log => log.id));\n const newLogs = validLogs.filter(log => !existingIds.has(log.id));\n\n this.logs = [...newLogs, ...this.logs];\n\n // 限制日志数量\n if (this.logs.length > this.MAX_LOGS) {\n this.logs = this.logs.slice(0, this.MAX_LOGS);\n }\n\n this.persistLogs();\n\n return newLogs.length;\n }\n\n /**\n * 验证日志格式\n */\n private validateLog(log: any): log is ConfigAuditLog {\n return (\n typeof log === 'object' &&\n typeof log.id === 'string' &&\n typeof log.timestamp === 'number' &&\n ['create', 'update', 'delete', 'sync'].includes(log.operation) &&\n typeof log.configKey === 'string' &&\n ['local', 'remote', 'backend_sync'].includes(log.source)\n );\n }\n\n /**\n * 转换为CSV格式\n */\n private convertToCSV(logs: ConfigAuditLog[]): string {\n const headers = [\n 'ID', 'Timestamp', 'Operation', 'Config Key', 'Old Value', \n 'New Value', 'Operator', 'Source'\n ];\n\n const rows = logs.map(log => [\n log.id,\n new Date(log.timestamp).toISOString(),\n log.operation,\n log.configKey,\n JSON.stringify(log.oldValue || ''),\n JSON.stringify(log.newValue || ''),\n log.operator || '',\n log.source\n ]);\n\n const csvContent = [\n headers.join(','),\n ...rows.map(row => row.map(cell => `\"${cell}\"`).join(','))\n ].join('\\n');\n\n return csvContent;\n }\n\n /**\n * 持久化日志到本地存储\n */\n private persistLogs(): void {\n try {\n localStorage.setItem('anti-cheating-audit-logs', JSON.stringify(this.logs));\n } catch (error) {\n console.warn('保存审计日志失败:', error);\n }\n }\n\n /**\n * 从本地存储加载日志\n */\n public loadFromStorage(): void {\n try {\n const stored = localStorage.getItem('anti-cheating-audit-logs');\n if (stored) {\n const logs = JSON.parse(stored);\n if (Array.isArray(logs)) {\n this.logs = logs.filter(log => this.validateLog(log));\n }\n }\n } catch (error) {\n console.warn('加载审计日志失败:', error);\n this.logs = [];\n }\n }\n\n /**\n * 获取最近的日志\n */\n public getRecentLogs(count: number = 10): ConfigAuditLog[] {\n return this.logs.slice(0, count);\n }\n\n /**\n * 根据配置键获取日志\n */\n public getLogsByConfigKey(configKey: string): ConfigAuditLog[] {\n return this.logs.filter(log => log.configKey === configKey);\n }\n\n /**\n * 根据操作者获取日志\n */\n public getLogsByOperator(operator: string): ConfigAuditLog[] {\n return this.logs.filter(log => log.operator === operator);\n }\n}","import { useState, useEffect, useRef, useCallback } from \"react\";\nimport {\n DetectionConfig,\n DetectionResult,\n MonitorStats,\n AntiCheatingCredentials,\n CheatingViolation\n} from '../types';\nimport { DEFAULT_DETECTION_CONFIG, OSSConfig } from '../types/config';\nimport { CheatingType } from '../constants';\nimport { ConfigManager } from '../utils';\nimport { DetectionEngine } from '../utils';\n\n/**\n * Hook参数接口\n */\nexport interface UseAntiCheatingMonitorProps {\n /** 阿里云访问凭证 */\n credentials: AntiCheatingCredentials;\n /** OSS配置 */\n ossConfig: OSSConfig;\n /** 检测配置 */\n config?: Partial<DetectionConfig>;\n /** 违规检测回调函数 */\n onViolation?: (violation: CheatingViolation) => void;\n /** 检测结果回调函数 */\n onDetectionResult?: (result: DetectionResult) => void;\n /** 错误回调函数 */\n onError?: (error: Error) => void;\n /** 配置变更回调函数 */\n onConfigChange?: (config: DetectionConfig) => void;\n}\n\n/**\n * Hook返回值接口\n */\nexport interface UseAntiCheatingMonitorReturn {\n /** 视频元素引用 */\n videoRef: React.RefObject<HTMLVideoElement | null>;\n /** 画布元素引用 */\n canvasRef: React.RefObject<HTMLCanvasElement | null>;\n /** 开始监控 */\n startMonitoring: () => Promise<void>;\n /** 停止监控 */\n stopMonitoring: () => void;\n /** 强制检测一次 */\n forceCheck: () => Promise<void>;\n /** 是否正在监控 */\n isMonitoring: boolean;\n /** 最新检测结果 */\n latestResult: DetectionResult | null;\n /** 监控统计信息 */\n stats: MonitorStats;\n /** 当前配置 */\n config: DetectionConfig;\n /** 更新配置 */\n updateConfig: (newConfig: Partial<DetectionConfig>) => Promise<{ success: boolean; errors?: string[] }>;\n /** 获取配置管理器实例 */\n getConfigManager: () => ConfigManager;\n}\n\n/**\n * 反作弊监控Hook\n * 集成阿里云OSS上传和VIAPI人脸检测服务\n * \n * @param props Hook参数\n * @returns Hook返回值\n * \n * @example\n * ```tsx\n * const {\n * videoRef,\n * startMonitoring,\n * stopMonitoring,\n * isMonitoring,\n * latestResult,\n * stats,\n * config,\n * updateConfig\n * } = useAntiCheatingMonitor({\n * credentials: {\n * accessKeyId: 'your-access-key-id',\n * accessKeySecret: 'your-access-key-secret'\n * },\n * ossConfig: {\n * bucket: 'your-bucket-name',\n * region: 'oss-cn-hangzhou'\n * },\n * config: {\n * checkInterval: 3000,\n * detectMultiplePeople: true\n * },\n * onViolation: (violation) => {\n * console.log('检测到违规:', violation);\n * },\n * onDetectionResult: (result) => {\n * console.log('检测结果:', result);\n * },\n * onError: (error) => {\n * console.error('监控错误:', error);\n * }\n * });\n * ```\n */\nexport function useAntiCheatingMonitor(props: UseAntiCheatingMonitorProps): UseAntiCheatingMonitorReturn {\n const {\n credentials,\n ossConfig,\n config: initialConfig,\n onViolation,\n onDetectionResult,\n onError,\n onConfigChange\n } = props;\n\n // 状态管理\n const [isMonitoring, setIsMonitoring] = useState(false);\n const [latestResult, setLatestResult] = useState<DetectionResult | null>(null);\n const [stats, setStats] = useState<MonitorStats>({\n checkCount: 0,\n abnormalCount: 0,\n latency: 0,\n networkQuality: 'excellent',\n fps: 0,\n avgProcessingTime: 0,\n violationStats: Object.fromEntries(\n Object.values(CheatingType).map(type => [type, 0])\n ) as Record<CheatingType, number>\n });\n\n // 引用管理\n const videoRef = useRef<HTMLVideoElement | null>(null);\n const canvasRef = useRef<HTMLCanvasElement | null>(null);\n const streamRef = useRef<MediaStream | null>(null);\n const detectionTimerRef = useRef<number | null>(null);\n const configManagerRef = useRef<ConfigManager | null>(null);\n const detectionEngineRef = useRef<DetectionEngine | null>(null);\n const lastFrameTimeRef = useRef<number>(0);\n const lastDetectionTimeRef = useRef<number>(0); // 上次检测时间,用于节流控制\n\n // 同步初始化配置管理器\n if (!configManagerRef.current) {\n configManagerRef.current = ConfigManager.getInstance();\n \n // 如果有初始配置,更新配置\n if (initialConfig) {\n configManagerRef.current.updateConfig(initialConfig, {\n source: 'local',\n operator: 'hook-initialization'\n });\n }\n }\n\n // 监听配置变更和初始化相关逻辑\n useEffect(() => {\n if (!configManagerRef.current) {\n return () => {};\n }\n\n // 监听配置变更\n const unsubscribe = configManagerRef.current.addListener((newConfig) => {\n if (detectionEngineRef.current) {\n detectionEngineRef.current.updateConfig(newConfig);\n }\n onConfigChange?.(newConfig);\n });\n\n return () => {\n unsubscribe();\n };\n }, [initialConfig, onConfigChange]);\n\n // 初始化检测引擎\n useEffect(() => {\n const config = configManagerRef.current?.getConfig();\n if (config && credentials && ossConfig) {\n detectionEngineRef.current = new DetectionEngine(config, credentials, ossConfig);\n }\n }, [credentials, ossConfig]);\n\n /**\n * 获取摄像头权限\n */\n const getCameraPermission = useCallback(async (): Promise<MediaStream> => {\n try {\n const config = configManagerRef.current?.getConfig();\n const stream = await navigator.mediaDevices.getUserMedia({\n video: {\n width: config?.resolution.width || 640,\n height: config?.resolution.height || 480,\n facingMode: 'user'\n },\n audio: false\n });\n\n streamRef.current = stream;\n \n if (videoRef.current) {\n videoRef.current.srcObject = stream;\n // 尝试自动播放\n videoRef.current\n .play()\n .catch((e) => console.warn(\"自动播放被阻止\", e));\n }\n\n return stream;\n } catch (error) {\n throw new Error(`获取摄像头权限失败: ${error instanceof Error ? error.message : '未知错误'}`);\n }\n }, []);\n\n /**\n * 捕获当前帧\n */\n const captureFrame = useCallback(async (): Promise<Blob | null> => {\n const video = videoRef.current;\n const canvas = canvasRef.current;\n \n if (!video || !canvas) {\n return null;\n }\n\n // 检查视频状态\n if (video.readyState < 2) { // HAVE_CURRENT_DATA\n console.warn('视频尚未准备好,当前状态:', video.readyState);\n return null;\n }\n\n // 检查视频尺寸\n if (video.videoWidth === 0 || video.videoHeight === 0) {\n console.warn('视频尺寸无效:', { videoWidth: video.videoWidth, videoHeight: video.videoHeight });\n return null;\n }\n\n const ctx = canvas.getContext('2d');\n if (!ctx) {\n return null;\n }\n\n // 设置画布尺寸\n canvas.width = video.videoWidth;\n canvas.height = video.videoHeight;\n\n // 清除画布\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n\n // 绘制当前帧\n ctx.drawImage(video, 0, 0, canvas.width, canvas.height);\n\n // 转换为Blob\n return new Promise((resolve) => {\n canvas.toBlob(resolve, 'image/jpeg', 0.8);\n });\n }, []);\n\n /**\n * 执行检测\n */\n const performDetection = useCallback(async (): Promise<void> => {\n if (!detectionEngineRef.current) {\n return;\n }\n\n // 节流控制 - 检查距离上次检测的时间是否足够\n const now = Date.now();\n const config = getConfigManager().getConfig();\n if (now - lastDetectionTimeRef.current < config.checkInterval) {\n return; // 还没到检测时间,直接返回\n }\n\n try {\n const imageData = await captureFrame();\n if (!imageData) {\n throw new Error('无法捕获图像');\n }\n\n const startTime = Date.now();\n const result = await detectionEngineRef.current.detect(imageData);\n const endTime = Date.now();\n\n // 更新最新结果\n setLatestResult(result);\n\n // 更新上次检测时间\n lastDetectionTimeRef.current = now;\n\n // 更新统计信息\n setStats(prevStats => {\n const newStats = { ...prevStats };\n newStats.checkCount++;\n newStats.latency = endTime - startTime;\n \n // 计算平均处理时间\n if (result.processingTime) {\n newStats.avgProcessingTime = \n (prevStats.avgProcessingTime * (prevStats.checkCount - 1) + result.processingTime) / \n prevStats.checkCount;\n }\n\n // 更新违规统计\n if (result.violations.length > 0) {\n newStats.abnormalCount++;\n result.violations.forEach(violation => {\n newStats.violationStats[violation.type] = \n (newStats.violationStats[violation.type] || 0) + 1;\n });\n }\n\n // 计算FPS\n const currentTime = Date.now();\n if (lastFrameTimeRef.current > 0) {\n const frameInterval = currentTime - lastFrameTimeRef.current;\n newStats.fps = Math.round(1000 / frameInterval);\n }\n lastFrameTimeRef.current = currentTime;\n\n // 评估网络质量(基于延迟)\n if (newStats.latency < 200) {\n newStats.networkQuality = 'excellent';\n } else if (newStats.latency < 500) {\n newStats.networkQuality = 'good';\n } else if (newStats.latency < 1000) {\n newStats.networkQuality = 'fair';\n } else {\n newStats.networkQuality = 'poor';\n }\n\n return newStats;\n });\n\n // 触发回调\n onDetectionResult?.(result);\n\n // 处理违规\n result.violations.forEach(violation => {\n onViolation?.(violation);\n });\n\n } catch (error) {\n const errorObj = error instanceof Error ? error : new Error('检测失败');\n onError?.(errorObj);\n }\n }, [captureFrame, onDetectionResult, onViolation, onError]);\n\n /**\n * 开始监控\n */\n const startMonitoring = useCallback(async (): Promise<void> => {\n try {\n // 获取摄像头权限\n await getCameraPermission();\n\n setIsMonitoring(true);\n } catch (error) {\n const errorObj = error instanceof Error ? error : new Error('启动监控失败');\n onError?.(errorObj);\n throw errorObj;\n }\n }, [getCameraPermission, onError]);\n\n /**\n * 停止监控\n */\n const stopMonitoring = useCallback((): void => {\n setIsMonitoring(false);\n\n // 停止所有轨道\n if (streamRef.current) {\n streamRef.current.getTracks().forEach(track => track.stop());\n streamRef.current = null;\n }\n\n // 清除视频源\n if (videoRef.current) {\n videoRef.current.srcObject = null;\n }\n\n // 清除定时器\n if (detectionTimerRef.current) {\n clearInterval(detectionTimerRef.current);\n detectionTimerRef.current = null;\n }\n }, []);\n\n /**\n * 强制检测\n */\n const forceCheck = useCallback(async (): Promise<void> => {\n await performDetection();\n }, [performDetection]);\n\n /**\n * 更新配置\n */\n const updateConfig = useCallback(async (newConfig: Partial<DetectionConfig>) => {\n if (!configManagerRef.current) {\n return { success: false, errors: ['配置管理器未初始化'] };\n }\n\n return configManagerRef.current.updateConfig(newConfig, {\n source: 'local',\n operator: 'hook-user'\n });\n }, []);\n\n /**\n * 获取当前配置\n */\n const getCurrentConfig = useCallback((): DetectionConfig => {\n const config = configManagerRef.current?.getConfig();\n \n // 如果配置管理器未初始化或返回空配置,使用默认值\n if (!config || !config.checkInterval) {\n return DEFAULT_DETECTION_CONFIG;\n }\n \n return config;\n }, []);\n\n /**\n * 获取配置管理器实例\n */\n const getConfigManager = useCallback((): ConfigManager => {\n if (!configManagerRef.current) {\n throw new Error('配置管理器未初始化');\n }\n return configManagerRef.current;\n }, []);\n\n // 清理资源\n useEffect(() => {\n return () => {\n stopMonitoring();\n };\n }, [stopMonitoring]);\n\n // 监控状态变化时启动或停止定时器\n useEffect(() => {\n if (isMonitoring) {\n // 启动循环\n detectionTimerRef.current = window.setInterval(performDetection, 1000); // 每秒检查一次是否准备就绪(内部有节流)\n } else {\n if (detectionTimerRef.current) {\n window.clearInterval(detectionTimerRef.current);\n detectionTimerRef.current = null;\n }\n }\n\n return () => {\n if (detectionTimerRef.current) {\n window.clearInterval(detectionTimerRef.current);\n }\n };\n }, [isMonitoring, performDetection]);\n\n // 组件卸载时清理资源\n useEffect(() => {\n return () => {\n stopMonitoring();\n };\n }, [stopMonitoring]);\n\n // 返回Hook接口\n return {\n videoRef,\n canvasRef,\n startMonitoring,\n stopMonitoring,\n forceCheck,\n isMonitoring,\n latestResult,\n stats,\n config: getCurrentConfig(),\n updateConfig,\n getConfigManager\n };\n}"],"names":["DEFAULT_DETECTION_CONFIG","checkInterval","detectMultiplePeople","detectDeviceUsage","detectAbsence","resolution","width","height","type","earphoneThreshold","cellphoneThreshold","headPoseThreshold","pitch","roll","yaw","faceCompletenessThreshold","centeringThreshold","movementThreshold","CheatingType","CHEATING_TYPE_LABELS","PERSON_COUNT_ANOMALY","EARPHONE_DETECTED","CELLPHONE_DETECTED","HEAD_POSTURE_ABNORMAL","FACE_MISSING","FACE_OCCLUDED","NO_FACE_DETECTED","MULTIPLE_FACES_DETECTED","NOT_CENTERED","SUSPICIOUS_MOVEMENT","ViolationLevel","CHEATING_TYPE_SEVERITY","ConfigManager","constructor","this","auditLogs","listeners","Set","STORAGE_KEY","config","getDefaultConfig","loadFromStorage","startHotUpdate","getInstance","instance","stored","localStorage","getItem","parsedConfig","JSON","parse","error","saveToStorage","setItem","stringify","window","addEventListener","e","key","newValue","newConfig","updateConfig","source","addAuditLog","operation","configKey","oldValue","operator","log","id","Date","now","Math","random","toString","substr","timestamp","push","length","slice","validateConfig","errors","getConfig","options","force","success","Object","entries","forEach","value","notifyListeners","resetConfig","oldConfig","keys","addListener","listener","add","delete","getAuditLogs","clearAuditLogs","olderThan","filter","destroy","syncTimer","clearInterval","clear","OSSClient","credentials","isInitialized","client","initialize","Promise","resolve","bucket","region","Error","accessKeyId","accessKeySecret","OSSModule","then","require","n","aliyunOssSdk_min","OSSClass","default","stsToken","securityToken","put","blob","dir","filename","objectKey","result","headers","url","message","ready","updateCredentials","DetectionEngine","ossConfig","ossClient","updateOSSConfig","detect","imageData","startTime","imageUrl","Blob","base64ToBlob","apiResponse","callVIAPI","processAPIResponse","processingTime","faceCount","faceCompleteness","personCount","violations","level","HIGH","confidence","description","base64","parts","split","mimeType","match","byteString","atob","arrayBuffer","ArrayBuffer","uint8Array","Uint8Array","i","charCodeAt","params","Action","Version","Format","AccessKeyId","SignatureMethod","Timestamp","getTimestamp","SignatureVersion","SignatureNonce","generateNonce","RegionId","Type","String","ImageURL","SecurityToken","canonicalizedQueryString","sort","map","percentEncode","join","stringToSign","signature","hmacSha1","bodyString","response","fetch","method","body","mode","ok","status","statusText","json","data","encoder","TextEncoder","keyData","encode","dataData","cryptoKey","crypto","subtle","importKey","name","hash","sign","btoa","fromCharCode","Array","from","str","encodeURIComponent","replace","toISOString","substring","faceInfo","Data","FaceInfo","personInfo","PersonInfo","faceNumber","FaceNumber","personNumber","PersonNumber","completeness","Completeness","pose","Pose","createViolation","MEDIUM","toFixed","Pitch","Roll","Yaw","pThresh","rThresh","yThresh","abs","max","earphoneScore","EarPhone","Score","cellphoneScore","CellPhone","centeringScore","calculateCenteringScore","LOW","rawResponse","min","getDetectionStats","totalDetections","averageProcessingTime","violationRate","mostCommonViolations","VERSION","LIB_INFO","version","author","license","repository","homepage","logs","MAX_LOGS","addLog","auditLog","generateLogId","unshift","persistLogs","queryLogs","filteredLogs","endTime","offset","limit","getAllLogs","getStats","operationStats","create","update","sync","sourceStats","local","remote","backend_sync","configCounts","operatorCounts","mostActiveConfigs","a","b","count","mostActiveOperators","totalLogs","cleanup","beforeCount","cleanedCount","exportLogs","format","convertToCSV","importLogs","logsData","importedLogs","validLogs","validateLog","existingIds","newLogs","has","includes","rows","row","cell","isArray","getRecentLogs","getLogsByConfigKey","getLogsByOperator","props","initialConfig","onViolation","onDetectionResult","onError","onConfigChange","isMonitoring","setIsMonitoring","useState","latestResult","setLatestResult","stats","setStats","checkCount","abnormalCount","latency","networkQuality","fps","avgProcessingTime","violationStats","fromEntries","values","videoRef","useRef","canvasRef","streamRef","detectionTimerRef","configManagerRef","detectionEngineRef","lastFrameTimeRef","lastDetectionTimeRef","current","useEffect","unsubscribe","getCameraPermission","useCallback","async","stream","navigator","mediaDevices","getUserMedia","video","facingMode","audio","srcObject","play","catch","captureFrame","canvas","readyState","videoWidth","videoHeight","ctx","getContext","clearRect","drawImage","toBlob","performDetection","getConfigManager","prevStats","newStats","violation","currentTime","frameInterval","round","errorObj","startMonitoring","stopMonitoring","getTracks","track","stop","forceCheck","getCurrentConfig","setInterval"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/types/config.ts","../src/constants/cheating-types.ts","../src/utils/config-manager.ts","../src/utils/oss-client.ts","../src/utils/detection-engine.ts","../src/index.ts","../src/utils/audit-logger.ts","../src/hooks/useAntiCheatingMonitor.ts"],"sourcesContent":["/**\n * 检测配置接口\n */\nexport interface DetectionConfig {\n /** 检测间隔(毫秒) */\n checkInterval: number;\n /** 是否检测多人 */\n detectMultiplePeople: boolean;\n /** 是否检测设备使用 */\n detectDeviceUsage: boolean;\n /** 是否检测离席 */\n detectAbsence: boolean;\n /** 视频分辨率 */\n resolution: {\n /** 视频宽度 */\n width: number;\n /** 视频高度 */\n height: number;\n };\n /** 检测类型\n * 0: 屏幕聊天工具检测\n * 1: 考生状态检测\n */\n type: number;\n /** 耳机检测阈值 */\n earphoneThreshold: number;\n /** 手机检测阈值 */\n cellphoneThreshold: number;\n /** 头部姿态阈值 */\n headPoseThreshold: {\n /** 俯仰角阈值 */\n pitch: number;\n /** 翻滚角阈值 */\n roll: number;\n /** 偏航角阈值 */\n yaw: number;\n };\n /** 人脸完整度阈值 */\n faceCompletenessThreshold: number;\n /** 人脸居中阈值 */\n centeringThreshold: number;\n /** 可疑移动检测阈值 */\n movementThreshold: number;\n}\n\n/**\n * 默认检测配置\n */\nexport const DEFAULT_DETECTION_CONFIG: DetectionConfig = {\n checkInterval: 3000,\n detectMultiplePeople: true,\n detectDeviceUsage: true,\n detectAbsence: true,\n resolution: {\n width: 640,\n height: 480,\n },\n type: 1,\n /** 耳机检测阈值 */\n earphoneThreshold: 0.6,\n /** 手机检测阈值 */\n cellphoneThreshold: 0.6,\n headPoseThreshold: {\n pitch: 30, // 俯仰角 ±30度\n roll: 30, // 翻滚角 ±30度\n yaw: 30, // 偏航角 ±30度\n },\n /** 人脸完整度阈值 */\n faceCompletenessThreshold: 0.8,\n /** 人脸居中阈值 */\n centeringThreshold: 0.7,\n /** 可疑移动检测阈值 */\n movementThreshold: 0.3,\n};\n\n/**\n * 阿里云OSS配置接口\n */\nexport interface OSSConfig {\n /** OSS存储桶名称 */\n bucket: string;\n /** OSS区域 */\n region: string;\n /** 访问域名(可选) */\n endpoint?: string;\n /** 安全令牌(可选,用于STS临时访问) */\n securityToken?: string;\n}\n\n/**\n * 配置更新选项\n */\nexport interface ConfigUpdateOptions {\n /** 是否强制更新(忽略验证) */\n force?: boolean;\n /** 更新来源 */\n source?: \"local\" | \"remote\" | \"backend_sync\";\n /** 操作者标识 */\n operator?: string;\n}\n","/**\n * 作弊类型枚举\n * 基于Java后端逻辑映射的前端检测类型\n */\nexport enum CheatingType {\n /** 人数异常 - 检测到多人 */\n PERSON_COUNT_ANOMALY = \"PERSON_COUNT_ANOMALY\",\n /** 检测到耳机 */\n EARPHONE_DETECTED = \"EARPHONE_DETECTED\", \n /** 检测到手机 */\n CELLPHONE_DETECTED = \"CELLPHONE_DETECTED\",\n /** 头部姿态异常 */\n HEAD_POSTURE_ABNORMAL = \"HEAD_POSTURE_ABNORMAL\",\n /** 人脸缺失 */\n FACE_MISSING = \"FACE_MISSING\",\n /** 人脸被遮挡 */\n FACE_OCCLUDED = \"FACE_OCCLUDED\",\n /** 未检测到人脸 */\n NO_FACE_DETECTED = \"NO_FACE_DETECTED\",\n /** 检测到多张人脸 */\n MULTIPLE_FACES_DETECTED = \"MULTIPLE_FACES_DETECTED\",\n /** 未居中 */\n NOT_CENTERED = \"NOT_CENTERED\",\n /** 可疑移动 */\n SUSPICIOUS_MOVEMENT = \"SUSPICIOUS_MOVEMENT\"\n}\n\n/**\n * 作弊类型显示名称映射\n */\nexport const CHEATING_TYPE_LABELS: Record<CheatingType, string> = {\n [CheatingType.PERSON_COUNT_ANOMALY]: \"人数异常\",\n [CheatingType.EARPHONE_DETECTED]: \"检测到耳机\",\n [CheatingType.CELLPHONE_DETECTED]: \"检测到手机\", \n [CheatingType.HEAD_POSTURE_ABNORMAL]: \"头部姿态异常\",\n [CheatingType.FACE_MISSING]: \"人脸缺失\",\n [CheatingType.FACE_OCCLUDED]: \"人脸被遮挡\",\n [CheatingType.NO_FACE_DETECTED]: \"未检测到人脸\",\n [CheatingType.MULTIPLE_FACES_DETECTED]: \"检测到多张人脸\",\n [CheatingType.NOT_CENTERED]: \"未居中\",\n [CheatingType.SUSPICIOUS_MOVEMENT]: \"可疑移动\"\n} as const;\n\n/**\n * 违规严重程度\n */\nexport enum ViolationLevel {\n /** 低风险 - 轻微违规 */\n LOW = \"low\",\n /** 中风险 - 需要注意 */\n MEDIUM = \"medium\", \n /** 高风险 - 严重违规 */\n HIGH = \"high\",\n /** 紧急 - 需要立即处理 */\n CRITICAL = \"critical\"\n}\n\n/**\n * 作弊类型严重程度映射\n */\nexport const CHEATING_TYPE_SEVERITY: Record<CheatingType, ViolationLevel> = {\n [CheatingType.PERSON_COUNT_ANOMALY]: ViolationLevel.HIGH,\n [CheatingType.EARPHONE_DETECTED]: ViolationLevel.MEDIUM,\n [CheatingType.CELLPHONE_DETECTED]: ViolationLevel.HIGH,\n [CheatingType.HEAD_POSTURE_ABNORMAL]: ViolationLevel.MEDIUM,\n [CheatingType.FACE_MISSING]: ViolationLevel.HIGH,\n [CheatingType.FACE_OCCLUDED]: ViolationLevel.MEDIUM,\n [CheatingType.NO_FACE_DETECTED]: ViolationLevel.HIGH,\n [CheatingType.MULTIPLE_FACES_DETECTED]: ViolationLevel.HIGH,\n [CheatingType.NOT_CENTERED]: ViolationLevel.LOW,\n [CheatingType.SUSPICIOUS_MOVEMENT]: ViolationLevel.MEDIUM\n} as const;","import { DetectionConfig, ConfigUpdateOptions, DEFAULT_DETECTION_CONFIG } from '../types';\nimport { ConfigAuditLog } from '../types';\n\n/**\n * 配置管理器类\n * 负责阈值配置的管理、热更新和审计\n */\nexport class ConfigManager {\n private static instance: ConfigManager;\n private config: DetectionConfig;\n private auditLogs: ConfigAuditLog[] = [];\n private listeners: Set<(config: DetectionConfig) => void> = new Set();\n private syncTimer?: number;\n private readonly STORAGE_KEY = 'anti-cheating-config';\n\n private constructor() {\n this.config = this.getDefaultConfig();\n this.loadFromStorage();\n this.startHotUpdate();\n }\n\n /**\n * 获取单例实例\n */\n public static getInstance(): ConfigManager {\n if (!ConfigManager.instance) {\n ConfigManager.instance = new ConfigManager();\n }\n return ConfigManager.instance;\n }\n\n /**\n * 获取默认配置\n */\n private getDefaultConfig(): DetectionConfig {\n return { ...DEFAULT_DETECTION_CONFIG };\n }\n\n /**\n * 从本地存储加载配置\n */\n private loadFromStorage(): void {\n try {\n const stored = localStorage.getItem(this.STORAGE_KEY);\n if (stored) {\n const parsedConfig = JSON.parse(stored);\n this.config = { ...this.config, ...parsedConfig };\n }\n } catch (error) {\n console.warn('加载本地配置失败:', error);\n }\n }\n\n /**\n * 保存配置到本地存储\n */\n private saveToStorage(): void {\n try {\n localStorage.setItem(this.STORAGE_KEY, JSON.stringify(this.config));\n } catch (error) {\n console.warn('保存本地配置失败:', error);\n }\n }\n\n /**\n * 启动热更新监听\n */\n private startHotUpdate(): void {\n // 监听storage事件,实现跨标签页配置同步\n const handleStorageChange = (e: StorageEvent) => {\n if (e.key === this.STORAGE_KEY && e.newValue) {\n try {\n const newConfig = JSON.parse(e.newValue);\n this.updateConfig(newConfig, { source: 'remote' });\n } catch (error) {\n console.warn('热更新配置失败:', error);\n }\n }\n };\n\n window.addEventListener('storage', handleStorageChange);\n }\n\n /**\n * 添加审计日志\n */\n private addAuditLog(\n operation: ConfigAuditLog['operation'],\n configKey: string,\n oldValue?: any,\n newValue?: any,\n source: ConfigAuditLog['source'] = 'local',\n operator?: string\n ): void {\n const log: ConfigAuditLog = {\n id: `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,\n timestamp: Date.now(),\n operation,\n configKey,\n oldValue,\n newValue,\n operator,\n source\n };\n\n this.auditLogs.push(log);\n \n // 限制日志数量,避免内存泄漏\n if (this.auditLogs.length > 1000) {\n this.auditLogs = this.auditLogs.slice(-500);\n }\n }\n\n /**\n * 验证配置有效性\n */\n private validateConfig(config: Partial<DetectionConfig>): string[] {\n const errors: string[] = [];\n\n if (config.checkInterval !== undefined) {\n if (config.checkInterval < 1000 || config.checkInterval > 60000) {\n errors.push('检测间隔应在1000-60000毫秒之间');\n }\n }\n\n if (config.resolution) {\n if (config.resolution.width < 320 || config.resolution.width > 1920) {\n errors.push('视频宽度应在320-1920之间');\n }\n if (config.resolution.height < 240 || config.resolution.height > 1080) {\n errors.push('视频高度应在240-1080之间');\n }\n }\n\n if (config.earphoneThreshold !== undefined) {\n if (config.earphoneThreshold < 0 || config.earphoneThreshold > 1) {\n errors.push('耳机检测阈值应在0-1之间');\n }\n }\n\n if (config.cellphoneThreshold !== undefined) {\n if (config.cellphoneThreshold < 0 || config.cellphoneThreshold > 1) {\n errors.push('手机检测阈值应在0-1之间');\n }\n }\n\n return errors;\n }\n\n /**\n * 获取当前配置\n */\n public getConfig(): DetectionConfig {\n return { ...this.config };\n }\n\n /**\n * 更新配置\n */\n public updateConfig(\n newConfig: Partial<DetectionConfig>, \n options: ConfigUpdateOptions = {}\n ): { success: boolean; errors?: string[] } {\n const { force = false, source = 'local', operator } = options;\n\n // 验证配置\n if (!force) {\n const errors = this.validateConfig(newConfig);\n if (errors.length > 0) {\n return { success: false, errors };\n }\n }\n\n // 记录变更\n Object.entries(newConfig).forEach(([key, value]) => {\n const oldValue = (this.config as any)[key];\n if (JSON.stringify(oldValue) !== JSON.stringify(value)) {\n this.addAuditLog('update', key, oldValue, value, source, operator);\n }\n });\n\n // 更新配置\n this.config = { ...this.config, ...newConfig };\n\n // 保存到本地存储\n if (source === 'local') {\n this.saveToStorage();\n }\n\n // 通知监听器\n this.notifyListeners();\n\n return { success: true };\n }\n\n /**\n * 重置配置为默认值\n */\n public resetConfig(source: ConfigAuditLog['source'] = 'local', operator?: string): void {\n const oldConfig = { ...this.config };\n this.config = this.getDefaultConfig();\n\n // 记录重置操作\n Object.keys(oldConfig).forEach(key => {\n this.addAuditLog('update', key, oldConfig[key as keyof DetectionConfig], this.config[key as keyof DetectionConfig], source, operator);\n });\n\n this.saveToStorage();\n this.notifyListeners();\n }\n\n /**\n * 添加配置变更监听器\n */\n public addListener(listener: (config: DetectionConfig) => void): () => void {\n this.listeners.add(listener);\n \n // 返回取消监听的函数\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n /**\n * 通知所有监听器\n */\n private notifyListeners(): void {\n this.listeners.forEach(listener => {\n try {\n listener(this.getConfig());\n } catch (error) {\n console.warn('配置监听器执行失败:', error);\n }\n });\n }\n\n /**\n * 获取审计日志\n */\n public getAuditLogs(): ConfigAuditLog[] {\n return [...this.auditLogs];\n }\n\n /**\n * 清理审计日志\n */\n public clearAuditLogs(olderThan?: number): void {\n if (olderThan) {\n this.auditLogs = this.auditLogs.filter(log => log.timestamp > olderThan);\n } else {\n this.auditLogs = [];\n }\n }\n\n /**\n * 销毁实例\n */\n public destroy(): void {\n if (this.syncTimer) {\n clearInterval(this.syncTimer);\n }\n this.listeners.clear();\n }\n}","import { OSSConfig } from '../types/config';\nimport { AntiCheatingCredentials } from '../types/detection';\nimport type OSS from 'ali-oss';\n\n/**\n * OSS客户端类\n * 用于上传图片到阿里云OSS\n */\nexport class OSSClient {\n private config: OSSConfig;\n private credentials: AntiCheatingCredentials;\n private isInitialized: boolean = false;\n private client: OSS | null = null;\n\n constructor(config: OSSConfig, credentials: AntiCheatingCredentials) {\n this.config = config;\n this.credentials = credentials;\n }\n\n /**\n * 初始化OSS客户端\n */\n public async initialize(): Promise<void> {\n if (this.isInitialized) {\n return Promise.resolve();\n }\n\n // 验证配置\n if (!this.config.bucket || !this.config.region) {\n throw new Error('OSS配置不完整:缺少bucket或region');\n }\n\n if (!this.credentials.accessKeyId || !this.credentials.accessKeySecret) {\n throw new Error('OSS凭证不完整:缺少accessKeyId或accessKeySecret');\n }\n\n // 初始化阿里云OSS客户端\n try {\n // 动态导入 ali-oss,避免在 SSR 阶段加载导致 exports is not defined 错误\n // 同时也避免了 Node.js 依赖 (proxy-agent) 的问题\n // @ts-ignore\n const OSSModule = await import('ali-oss/dist/aliyun-oss-sdk.min.js');\n const OSSClass = (OSSModule.default || OSSModule) as unknown as typeof OSS;\n\n this.client = new OSSClass({\n region: this.config.region,\n accessKeyId: this.credentials.accessKeyId,\n accessKeySecret: this.credentials.accessKeySecret,\n stsToken: this.credentials.securityToken,\n bucket: this.config.bucket,\n });\n\n this.isInitialized = true;\n } catch (error) {\n console.error('Failed to load ali-oss:', error);\n throw new Error('无法加载 OSS 客户端库');\n }\n }\n\n /**\n * 上传文件到OSS\n * @param blob 文件数据\n * @param dir 存储目录\n * @returns 文件URL\n */\n public async put(blob: Blob, dir: string = 'monitor/'): Promise<{ url: string }> {\n if (!this.isInitialized) {\n await this.initialize();\n }\n\n if (!this.client) {\n throw new Error('OSS客户端未初始化');\n }\n\n // 生成文件名\n const filename = `${Date.now()}-${Math.random().toString(36).slice(2)}.jpg`;\n const objectKey = dir + filename;\n\n try {\n // 使用阿里云OSS SDK进行真实上传\n const result = await this.client.put(objectKey, blob, {\n headers: {\n 'Cache-Control': 'no-cache',\n 'Content-Disposition': `inline; filename=\"${filename}\"`,\n },\n });\n\n // 确保返回的是公开访问的URL\n const publicUrl = result.url || `https://${this.config.bucket}.${this.config.region}.aliyuncs.com/${objectKey}`;\n \n return { url: publicUrl };\n\n } catch (error) {\n throw new Error(`阿里云 OSS 上传失败: ${error instanceof Error ? error.message : '未知错误'}`);\n }\n }\n\n \n\n /**\n * 检查客户端是否已初始化\n */\n public get ready(): boolean {\n return this.isInitialized;\n }\n\n /**\n * 更新配置\n */\n public updateConfig(config: Partial<OSSConfig>): void {\n this.config = { ...this.config, ...config };\n this.isInitialized = false; // 需要重新初始化\n }\n\n /**\n * 更新凭证\n */\n public updateCredentials(credentials: Partial<AntiCheatingCredentials>): void {\n this.credentials = { ...this.credentials, ...credentials };\n this.isInitialized = false; // 需要重新初始化\n }\n}","import { \n DetectionResult, \n DetectionConfig, \n VIAPIRequestParams, \n VIAPIResponse,\n CheatingViolation,\n AntiCheatingCredentials\n} from '../types';\nimport { CheatingType, ViolationLevel, CHEATING_TYPE_SEVERITY, CHEATING_TYPE_LABELS } from '../constants';\nimport { OSSConfig } from '../types/config';\nimport { OSSClient } from './oss-client';\n\n/**\n * 检测引擎类\n * 负责执行反作弊检测逻辑,集成OSS上传和阿里云VIAPI调用\n */\nexport class DetectionEngine {\n private config: DetectionConfig;\n private credentials: AntiCheatingCredentials;\n private ossConfig: OSSConfig;\n private ossClient: OSSClient;\n\n constructor(config: DetectionConfig, credentials: AntiCheatingCredentials, ossConfig: OSSConfig) {\n this.config = config;\n this.credentials = credentials;\n this.ossConfig = ossConfig;\n this.ossClient = new OSSClient(ossConfig, credentials);\n }\n\n /**\n * 更新配置\n */\n public updateConfig(config: DetectionConfig): void {\n this.config = config;\n }\n\n /**\n * 更新凭证\n */\n public updateCredentials(credentials: AntiCheatingCredentials): void {\n this.credentials = credentials;\n this.ossClient.updateCredentials(credentials);\n }\n\n /**\n * 更新OSS配置\n */\n public updateOSSConfig(ossConfig: OSSConfig): void {\n this.ossConfig = ossConfig;\n this.ossClient.updateConfig(ossConfig);\n }\n\n /**\n * 执行检测\n * @param imageData 图像数据(Base64或Blob)\n */\n public async detect(imageData: string | Blob): Promise<DetectionResult> {\n const startTime = Date.now();\n const imgBlob: Blob = imageData instanceof Blob ? imageData : this.base64ToBlob(imageData);\n\n try {\n let imageUrl: string;\n\n const uploadResult = await this.ossClient.put(imgBlob);\n imageUrl = uploadResult.url;\n\n // 调用阿里云VIAPI\n const apiResponse = await this.callVIAPI(imageUrl);\n \n // 处理检测结果\n const result = this.processAPIResponse(apiResponse, imgBlob);\n \n // 计算处理时间\n result.processingTime = Date.now() - startTime;\n \n return result;\n } catch (error) {\n // 返回错误结果\n return {\n timestamp: Date.now(),\n faceCount: 0,\n faceCompleteness: 0,\n personCount: 0,\n violations: [{\n type: CheatingType.NO_FACE_DETECTED,\n level: ViolationLevel.HIGH,\n confidence: 1.0,\n description: `检测失败: ${error instanceof Error ? error.message : '未知错误'}`\n }],\n imageData: imgBlob,\n processingTime: Date.now() - startTime\n };\n }\n }\n\n /**\n * Base64转Blob\n * @private\n */\n private base64ToBlob(base64: string): Blob {\n const parts = base64.split(',');\n const mimeType = parts[0].match(/:(.*?);/)?.[1] || 'image/jpeg';\n const byteString = atob(parts[1]);\n const arrayBuffer = new ArrayBuffer(byteString.length);\n const uint8Array = new Uint8Array(arrayBuffer);\n \n for (let i = 0; i < byteString.length; i++) {\n uint8Array[i] = byteString.charCodeAt(i);\n }\n \n return new Blob([arrayBuffer], { type: mimeType });\n }\n\n /**\n * 调用阿里云VIAPI\n * @private\n */\n private async callVIAPI(imageUrl: string): Promise<VIAPIResponse> {\n // 阿里云API常量\n const API_ENDPOINT = \"https://facebody.cn-shanghai.aliyuncs.com\";\n const API_VERSION = \"2019-12-30\";\n\n // 构建请求参数\n const params: Record<string, string> = {\n Action: \"MonitorExamination\",\n Version: API_VERSION,\n Format: \"JSON\",\n AccessKeyId: this.credentials.accessKeyId,\n SignatureMethod: \"HMAC-SHA1\",\n Timestamp: this.getTimestamp(),\n SignatureVersion: \"1.0\",\n SignatureNonce: this.generateNonce(),\n RegionId: \"cn-shanghai\",\n Type: String(this.config.type),\n ImageURL: imageUrl,\n };\n\n // 如果有安全令牌,添加到参数中\n if (this.credentials.securityToken) {\n params.SecurityToken = this.credentials.securityToken;\n }\n\n // 1. 对参数进行排序\n const sortedKeys = Object.keys(params).sort();\n\n // 2. 构建规范化的查询字符串\n const canonicalizedQueryString = sortedKeys\n .map((key) => `${this.percentEncode(key)}=${this.percentEncode(params[key])}`)\n .join(\"&\");\n\n // 3. 构建待签名字符串\n const stringToSign = `POST&${this.percentEncode(\"/\")}&${this.percentEncode(\n canonicalizedQueryString\n )}`;\n\n // 4. 计算签名\n const signature = await this.hmacSha1(\n `${this.credentials.accessKeySecret}&`,\n stringToSign\n );\n\n // 5. 构建 Body 内容 (POST请求)\n const bodyString = `${canonicalizedQueryString}&Signature=${this.percentEncode(\n signature\n )}`;\n\n // 发送请求\n const response = await fetch(API_ENDPOINT, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n },\n body: bodyString,\n mode: \"cors\",\n });\n\n if (!response.ok) {\n throw new Error(\n `API Error: ${response.status} ${response.statusText}`\n );\n }\n\n const data = await response.json();\n\n // 直接返回API响应数据,已经是标准格式\n return data as VIAPIResponse;\n }\n\n /**\n * 使用HMAC-SHA1算法生成签名\n * @private\n */\n private async hmacSha1(key: string, data: string): Promise<string> {\n const encoder = new TextEncoder();\n const keyData = encoder.encode(key);\n const dataData = encoder.encode(data);\n\n const cryptoKey = await window.crypto.subtle.importKey(\n \"raw\",\n keyData,\n { name: \"HMAC\", hash: \"SHA-1\" },\n false,\n [\"sign\"]\n );\n\n const signature = await window.crypto.subtle.sign(\n \"HMAC\",\n cryptoKey,\n dataData\n );\n \n return btoa(String.fromCharCode(...Array.from(new Uint8Array(signature))));\n }\n\n /**\n * URL编码函数\n * @private\n */\n private percentEncode(str: string): string {\n return encodeURIComponent(str)\n .replace(/!/g, \"%21\")\n .replace(/'/g, \"%27\")\n .replace(/\\(/g, \"%28\")\n .replace(/\\)/g, \"%29\")\n .replace(/\\*/g, \"%2A\");\n }\n\n /**\n * 获取当前时间戳\n * @private\n */\n private getTimestamp(): string {\n return new Date().toISOString().replace(/\\.\\d{3}/, \"\");\n }\n\n /**\n * 生成随机字符串\n * @private\n */\n private generateNonce(): string {\n return (\n Math.random().toString(36).substring(2, 15) +\n Math.random().toString(36).substring(2, 15)\n );\n }\n\n /**\n * 处理API响应,生成检测结果\n * @private\n */\n private processAPIResponse(response: VIAPIResponse, imageData: Blob): DetectionResult {\n const violations: CheatingViolation[] = [];\n\n // 从正确的数据路径获取信息\n const faceInfo = response.Data?.FaceInfo as any || {};\n const personInfo = response.Data?.PersonInfo as any || {};\n const faceNumber = faceInfo.FaceNumber || 0;\n const personNumber = personInfo.PersonNumber || 0;\n const completeness = faceInfo.Completeness || 0;\n const pose = faceInfo.Pose || {};\n\n // 1. 检测人脸数量\n if (faceNumber === 0) {\n violations.push(this.createViolation(\n CheatingType.NO_FACE_DETECTED,\n ViolationLevel.HIGH,\n 1.0,\n '未检测到人脸'\n ));\n } else if (faceNumber > 1) {\n violations.push(this.createViolation(\n CheatingType.MULTIPLE_FACES_DETECTED,\n ViolationLevel.HIGH,\n 1.0,\n `检测到${faceNumber}张人脸`\n ));\n }\n\n // 2. 检测人员数量\n if (this.config.detectMultiplePeople && personNumber > 1) {\n violations.push(this.createViolation(\n CheatingType.PERSON_COUNT_ANOMALY,\n ViolationLevel.HIGH,\n 1.0,\n `检测到${personNumber}人`\n ));\n }\n\n // 3. 检测人脸完整度\n if (completeness < this.config.faceCompletenessThreshold) {\n violations.push(this.createViolation(\n CheatingType.FACE_OCCLUDED,\n ViolationLevel.MEDIUM,\n 1.0 - completeness,\n `人脸完整度${(completeness * 100).toFixed(0)}%`\n ));\n }\n\n // 4. 检测头部姿态\n if (pose) {\n const { Pitch, Roll, Yaw } = pose;\n const { pitch: pThresh, roll: rThresh, yaw: yThresh } = this.config.headPoseThreshold;\n\n if (Math.abs(Pitch) > pThresh || Math.abs(Roll) > rThresh || Math.abs(Yaw) > yThresh) {\n violations.push(this.createViolation(\n CheatingType.HEAD_POSTURE_ABNORMAL,\n ViolationLevel.MEDIUM,\n Math.max(\n Math.abs(Pitch) / pThresh,\n Math.abs(Roll) / rThresh,\n Math.abs(Yaw) / yThresh\n ) - 1,\n `头部姿态异常: ${Pitch > 0 ? '抬头' : '低头'}${Pitch.toFixed(1)}° 偏头${Roll.toFixed(1)}° ${Yaw > 0 ? '左转' : '右转'}${Yaw.toFixed(1)}°`\n ));\n }\n }\n\n // 5. 检测设备使用\n if (this.config.detectDeviceUsage) {\n // 耳机检测\n const earphoneScore = personInfo.EarPhone?.Score || 0;\n if (earphoneScore > this.config.earphoneThreshold) {\n violations.push(this.createViolation(\n CheatingType.EARPHONE_DETECTED,\n ViolationLevel.MEDIUM,\n earphoneScore,\n `检测到耳机 (置信度: ${(earphoneScore * 100).toFixed(0)}%)`\n ));\n }\n\n // 手机检测\n const cellphoneScore = personInfo.CellPhone?.Score || 0;\n if (cellphoneScore > this.config.cellphoneThreshold) {\n violations.push(this.createViolation(\n CheatingType.CELLPHONE_DETECTED,\n ViolationLevel.HIGH,\n cellphoneScore,\n `检测到手机 (置信度: ${(cellphoneScore * 100).toFixed(0)}%)`\n ));\n }\n }\n\n // 6. 检测居中程度(基于姿态数据估算)\n if (pose) {\n const centeringScore = this.calculateCenteringScore(pose);\n if (centeringScore < this.config.centeringThreshold) {\n violations.push(this.createViolation(\n CheatingType.NOT_CENTERED,\n ViolationLevel.LOW,\n 1.0 - centeringScore,\n `未居中 (居中度: ${(centeringScore * 100).toFixed(0)}%)`\n ));\n }\n }\n\n return {\n timestamp: Date.now(),\n faceCount: faceNumber,\n faceCompleteness: completeness,\n personCount: personNumber,\n pose: pose,\n violations,\n rawResponse: response,\n imageData\n };\n }\n\n /**\n * 创建违规对象\n * @private\n */\n private createViolation(\n type: CheatingType,\n level: ViolationLevel,\n confidence: number,\n description: string,\n data?: any\n ): CheatingViolation {\n return {\n type,\n level,\n confidence: Math.max(0, Math.min(1, confidence)),\n description,\n data\n };\n }\n\n /**\n * 计算居中程度\n * @private\n */\n private calculateCenteringScore(pose: { Pitch: number; Roll: number; Yaw: number }): number {\n const { Pitch, Roll, Yaw } = pose;\n \n // 基于姿态角度计算居中程度\n const pitchScore = Math.max(0, 1 - Math.abs(Pitch) / 30);\n const rollScore = Math.max(0, 1 - Math.abs(Roll) / 45);\n const yawScore = Math.max(0, 1 - Math.abs(Yaw) / 60);\n \n return (pitchScore + rollScore + yawScore) / 3;\n }\n\n /**\n * 获取检测统计信息\n */\n public getDetectionStats(): {\n totalDetections: number;\n averageProcessingTime: number;\n violationRate: number;\n mostCommonViolations: Array<{ type: CheatingType; count: number }>;\n } {\n // 这里应该从历史记录中计算统计信息\n // 为了简化,返回模拟数据\n return {\n totalDetections: 0,\n averageProcessingTime: 0,\n violationRate: 0,\n mostCommonViolations: []\n };\n }\n}","/**\n * 反作弊监控库\n * \n * 基于阿里云视觉智能平台的实时反作弊监控系统\n * 提供人脸检测、异常行为分析和完整的在线监考解决方案\n * \n * @author Anti-Cheating Monitor Team\n * @version 2.0.0\n * @license MIT\n */\n\n// 导出主要Hook\nexport { useAntiCheatingMonitor } from './hooks';\nexport type { UseAntiCheatingMonitorProps, UseAntiCheatingMonitorReturn } from './hooks';\n\n// 导出类型定义\nexport * from './types';\n\n// 导出工具类\nexport { ConfigManager } from './utils';\nexport { DetectionEngine } from './utils';\nexport { AuditLogger } from './utils';\n\n// 导出常量\nexport { CheatingType, ViolationLevel, CHEATING_TYPE_LABELS, CHEATING_TYPE_SEVERITY } from './constants';\n\n// 版本信息\nexport const VERSION = '2.0.0';\n\n/**\n * 库信息\n */\nexport const LIB_INFO = {\n name: 'anti-cheating-monitor',\n version: VERSION,\n description: '基于阿里云视觉智能平台的实时反作弊监控系统',\n author: 'Anti-Cheating Monitor Team',\n license: 'MIT',\n repository: 'https://github.com/coding-daily-wq/anti-cheating-monitor.git',\n homepage: 'https://coding-daily-wq.github.io/anti-cheating-monitor'\n} as const;","import { ConfigAuditLog, AuditLogQueryOptions, AuditLogStats } from '../types';\n\n/**\n * 审计日志管理器类\n * 负责管理和分析配置变更审计日志\n */\nexport class AuditLogger {\n private logs: ConfigAuditLog[] = [];\n private readonly MAX_LOGS = 1000; // 最大日志数量限制\n\n /**\n * 添加审计日志\n */\n public addLog(log: Omit<ConfigAuditLog, 'id' | 'timestamp'>): ConfigAuditLog {\n const auditLog: ConfigAuditLog = {\n ...log,\n id: this.generateLogId(),\n timestamp: Date.now()\n };\n\n this.logs.unshift(auditLog); // 新日志添加到开头\n\n // 限制日志数量\n if (this.logs.length > this.MAX_LOGS) {\n this.logs = this.logs.slice(0, this.MAX_LOGS);\n }\n\n // 持久化到本地存储\n this.persistLogs();\n\n return auditLog;\n }\n\n /**\n * 生成日志ID\n */\n private generateLogId(): string {\n return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;\n }\n\n /**\n * 查询审计日志\n */\n public queryLogs(options: AuditLogQueryOptions = {}): ConfigAuditLog[] {\n let filteredLogs = [...this.logs];\n\n // 时间范围过滤\n if (options.startTime !== undefined) {\n filteredLogs = filteredLogs.filter(log => log.timestamp >= options.startTime!);\n }\n if (options.endTime !== undefined) {\n filteredLogs = filteredLogs.filter(log => log.timestamp <= options.endTime!);\n }\n\n // 操作类型过滤\n if (options.operation) {\n filteredLogs = filteredLogs.filter(log => log.operation === options.operation);\n }\n\n // 配置项过滤\n if (options.configKey) {\n filteredLogs = filteredLogs.filter(log => log.configKey === options.configKey);\n }\n\n // 操作者过滤\n if (options.operator) {\n filteredLogs = filteredLogs.filter(log => log.operator === options.operator);\n }\n\n // 来源过滤\n if (options.source) {\n filteredLogs = filteredLogs.filter(log => log.source === options.source);\n }\n\n // 分页\n const offset = options.offset || 0;\n const limit = options.limit || 50;\n filteredLogs = filteredLogs.slice(offset, offset + limit);\n\n return filteredLogs;\n }\n\n /**\n * 获取所有日志\n */\n public getAllLogs(): ConfigAuditLog[] {\n return [...this.logs];\n }\n\n /**\n * 获取日志统计信息\n */\n public getStats(): AuditLogStats {\n const operationStats: Record<ConfigAuditLog['operation'], number> = {\n create: 0,\n update: 0,\n delete: 0,\n sync: 0\n };\n\n const sourceStats: Record<ConfigAuditLog['source'], number> = {\n local: 0,\n remote: 0,\n backend_sync: 0\n };\n\n const configCounts: Record<string, number> = {};\n const operatorCounts: Record<string, number> = {};\n\n this.logs.forEach(log => {\n // 操作类型统计\n operationStats[log.operation]++;\n\n // 来源统计\n sourceStats[log.source]++;\n\n // 配置项统计\n configCounts[log.configKey] = (configCounts[log.configKey] || 0) + 1;\n\n // 操作者统计\n if (log.operator) {\n operatorCounts[log.operator] = (operatorCounts[log.operator] || 0) + 1;\n }\n });\n\n // 最活跃的配置项(前5个)\n const mostActiveConfigs = Object.entries(configCounts)\n .sort(([, a], [, b]) => b - a)\n .slice(0, 5)\n .map(([configKey, count]) => ({ configKey, count }));\n\n // 最活跃的操作者(前5个)\n const mostActiveOperators = Object.entries(operatorCounts)\n .sort(([, a], [, b]) => b - a)\n .slice(0, 5)\n .map(([operator, count]) => ({ operator, count }));\n\n return {\n totalLogs: this.logs.length,\n operationStats,\n sourceStats,\n mostActiveConfigs,\n mostActiveOperators\n };\n }\n\n /**\n * 清理旧日志\n */\n public cleanup(olderThan: number): number {\n const beforeCount = this.logs.length;\n this.logs = this.logs.filter(log => log.timestamp > olderThan);\n const cleanedCount = beforeCount - this.logs.length;\n\n if (cleanedCount > 0) {\n this.persistLogs();\n }\n\n return cleanedCount;\n }\n\n /**\n * 清空所有日志\n */\n public clear(): void {\n this.logs = [];\n this.persistLogs();\n }\n\n /**\n * 导出日志为JSON\n */\n public exportLogs(format: 'json' | 'csv' = 'json'): string {\n if (format === 'json') {\n return JSON.stringify(this.logs, null, 2);\n } else if (format === 'csv') {\n return this.convertToCSV(this.logs);\n } else {\n throw new Error(`不支持的导出格式: ${format}`);\n }\n }\n\n /**\n * 导入日志\n */\n public importLogs(logsData: string, format: 'json' | 'csv' = 'json'): number {\n let importedLogs: ConfigAuditLog[];\n\n if (format === 'json') {\n try {\n importedLogs = JSON.parse(logsData);\n } catch (error) {\n throw new Error('JSON格式错误');\n }\n } else {\n throw new Error(`不支持的导入格式: ${format}`);\n }\n\n // 验证日志格式\n const validLogs = importedLogs.filter(log => this.validateLog(log));\n\n // 合并日志(避免重复)\n const existingIds = new Set(this.logs.map(log => log.id));\n const newLogs = validLogs.filter(log => !existingIds.has(log.id));\n\n this.logs = [...newLogs, ...this.logs];\n\n // 限制日志数量\n if (this.logs.length > this.MAX_LOGS) {\n this.logs = this.logs.slice(0, this.MAX_LOGS);\n }\n\n this.persistLogs();\n\n return newLogs.length;\n }\n\n /**\n * 验证日志格式\n */\n private validateLog(log: any): log is ConfigAuditLog {\n return (\n typeof log === 'object' &&\n typeof log.id === 'string' &&\n typeof log.timestamp === 'number' &&\n ['create', 'update', 'delete', 'sync'].includes(log.operation) &&\n typeof log.configKey === 'string' &&\n ['local', 'remote', 'backend_sync'].includes(log.source)\n );\n }\n\n /**\n * 转换为CSV格式\n */\n private convertToCSV(logs: ConfigAuditLog[]): string {\n const headers = [\n 'ID', 'Timestamp', 'Operation', 'Config Key', 'Old Value', \n 'New Value', 'Operator', 'Source'\n ];\n\n const rows = logs.map(log => [\n log.id,\n new Date(log.timestamp).toISOString(),\n log.operation,\n log.configKey,\n JSON.stringify(log.oldValue || ''),\n JSON.stringify(log.newValue || ''),\n log.operator || '',\n log.source\n ]);\n\n const csvContent = [\n headers.join(','),\n ...rows.map(row => row.map(cell => `\"${cell}\"`).join(','))\n ].join('\\n');\n\n return csvContent;\n }\n\n /**\n * 持久化日志到本地存储\n */\n private persistLogs(): void {\n try {\n localStorage.setItem('anti-cheating-audit-logs', JSON.stringify(this.logs));\n } catch (error) {\n console.warn('保存审计日志失败:', error);\n }\n }\n\n /**\n * 从本地存储加载日志\n */\n public loadFromStorage(): void {\n try {\n const stored = localStorage.getItem('anti-cheating-audit-logs');\n if (stored) {\n const logs = JSON.parse(stored);\n if (Array.isArray(logs)) {\n this.logs = logs.filter(log => this.validateLog(log));\n }\n }\n } catch (error) {\n console.warn('加载审计日志失败:', error);\n this.logs = [];\n }\n }\n\n /**\n * 获取最近的日志\n */\n public getRecentLogs(count: number = 10): ConfigAuditLog[] {\n return this.logs.slice(0, count);\n }\n\n /**\n * 根据配置键获取日志\n */\n public getLogsByConfigKey(configKey: string): ConfigAuditLog[] {\n return this.logs.filter(log => log.configKey === configKey);\n }\n\n /**\n * 根据操作者获取日志\n */\n public getLogsByOperator(operator: string): ConfigAuditLog[] {\n return this.logs.filter(log => log.operator === operator);\n }\n}","import { useState, useEffect, useRef, useCallback } from \"react\";\nimport {\n DetectionConfig,\n DetectionResult,\n MonitorStats,\n AntiCheatingCredentials,\n CheatingViolation\n} from '../types';\nimport { DEFAULT_DETECTION_CONFIG, OSSConfig } from '../types/config';\nimport { CheatingType } from '../constants';\nimport { ConfigManager } from '../utils';\nimport { DetectionEngine } from '../utils';\n\n/**\n * Hook参数接口\n */\nexport interface UseAntiCheatingMonitorProps {\n /** 阿里云访问凭证 */\n credentials: AntiCheatingCredentials;\n /** OSS配置 */\n ossConfig: OSSConfig;\n /** 检测配置 */\n config?: Partial<DetectionConfig>;\n /** 违规检测回调函数 */\n onViolation?: (violation: CheatingViolation) => void;\n /** 检测结果回调函数 */\n onDetectionResult?: (result: DetectionResult) => void;\n /** 错误回调函数 */\n onError?: (error: Error) => void;\n /** 配置变更回调函数 */\n onConfigChange?: (config: DetectionConfig) => void;\n}\n\n/**\n * Hook返回值接口\n */\nexport interface UseAntiCheatingMonitorReturn {\n /** 视频元素引用 */\n videoRef: React.RefObject<HTMLVideoElement | null>;\n /** 画布元素引用 */\n canvasRef: React.RefObject<HTMLCanvasElement | null>;\n /** 开始监控 */\n startMonitoring: () => Promise<void>;\n /** 停止监控 */\n stopMonitoring: () => void;\n /** 强制检测一次 */\n forceCheck: () => Promise<void>;\n /** 是否正在监控 */\n isMonitoring: boolean;\n /** 最新检测结果 */\n latestResult: DetectionResult | null;\n /** 监控统计信息 */\n stats: MonitorStats;\n /** 当前配置 */\n config: DetectionConfig;\n /** 更新配置 */\n updateConfig: (newConfig: Partial<DetectionConfig>) => Promise<{ success: boolean; errors?: string[] }>;\n /** 获取配置管理器实例 */\n getConfigManager: () => ConfigManager;\n}\n\n/**\n * 反作弊监控Hook\n * 集成阿里云OSS上传和VIAPI人脸检测服务\n * \n * @param props Hook参数\n * @returns Hook返回值\n * \n * @example\n * ```tsx\n * const {\n * videoRef,\n * startMonitoring,\n * stopMonitoring,\n * isMonitoring,\n * latestResult,\n * stats,\n * config,\n * updateConfig\n * } = useAntiCheatingMonitor({\n * credentials: {\n * accessKeyId: 'your-access-key-id',\n * accessKeySecret: 'your-access-key-secret'\n * },\n * ossConfig: {\n * bucket: 'your-bucket-name',\n * region: 'oss-cn-hangzhou'\n * },\n * config: {\n * checkInterval: 3000,\n * detectMultiplePeople: true\n * },\n * onViolation: (violation) => {\n * console.log('检测到违规:', violation);\n * },\n * onDetectionResult: (result) => {\n * console.log('检测结果:', result);\n * },\n * onError: (error) => {\n * console.error('监控错误:', error);\n * }\n * });\n * ```\n */\nexport function useAntiCheatingMonitor(props: UseAntiCheatingMonitorProps): UseAntiCheatingMonitorReturn {\n const {\n credentials,\n ossConfig,\n config: initialConfig,\n onViolation,\n onDetectionResult,\n onError,\n onConfigChange\n } = props;\n\n // 状态管理\n const [isMonitoring, setIsMonitoring] = useState(false);\n const [latestResult, setLatestResult] = useState<DetectionResult | null>(null);\n const [stats, setStats] = useState<MonitorStats>({\n checkCount: 0,\n abnormalCount: 0,\n latency: 0,\n networkQuality: 'excellent',\n fps: 0,\n avgProcessingTime: 0,\n violationStats: Object.fromEntries(\n Object.values(CheatingType).map(type => [type, 0])\n ) as Record<CheatingType, number>\n });\n\n // 引用管理\n const videoRef = useRef<HTMLVideoElement | null>(null);\n const canvasRef = useRef<HTMLCanvasElement | null>(null);\n const streamRef = useRef<MediaStream | null>(null);\n const detectionTimerRef = useRef<number | null>(null);\n const configManagerRef = useRef<ConfigManager | null>(null);\n const detectionEngineRef = useRef<DetectionEngine | null>(null);\n const lastFrameTimeRef = useRef<number>(0);\n const lastDetectionTimeRef = useRef<number>(0); // 上次检测时间,用于节流控制\n\n // 同步初始化配置管理器\n if (!configManagerRef.current) {\n configManagerRef.current = ConfigManager.getInstance();\n \n // 如果有初始配置,更新配置\n if (initialConfig) {\n configManagerRef.current.updateConfig(initialConfig, {\n source: 'local',\n operator: 'hook-initialization'\n });\n }\n }\n\n // 监听配置变更和初始化相关逻辑\n useEffect(() => {\n if (!configManagerRef.current) {\n return () => {};\n }\n\n // 监听配置变更\n const unsubscribe = configManagerRef.current.addListener((newConfig) => {\n if (detectionEngineRef.current) {\n detectionEngineRef.current.updateConfig(newConfig);\n }\n onConfigChange?.(newConfig);\n });\n\n return () => {\n unsubscribe();\n };\n }, [initialConfig, onConfigChange]);\n\n // 初始化检测引擎\n useEffect(() => {\n const config = configManagerRef.current?.getConfig();\n if (config && credentials && ossConfig) {\n detectionEngineRef.current = new DetectionEngine(config, credentials, ossConfig);\n }\n }, [credentials, ossConfig]);\n\n /**\n * 获取摄像头权限\n */\n const getCameraPermission = useCallback(async (): Promise<MediaStream> => {\n try {\n const config = configManagerRef.current?.getConfig();\n const stream = await navigator.mediaDevices.getUserMedia({\n video: {\n width: config?.resolution.width || 640,\n height: config?.resolution.height || 480,\n facingMode: 'user'\n },\n audio: false\n });\n\n streamRef.current = stream;\n \n if (videoRef.current) {\n videoRef.current.srcObject = stream;\n // 尝试自动播放\n videoRef.current\n .play()\n .catch((e) => console.warn(\"自动播放被阻止\", e));\n }\n\n return stream;\n } catch (error) {\n throw new Error(`获取摄像头权限失败: ${error instanceof Error ? error.message : '未知错误'}`);\n }\n }, []);\n\n /**\n * 捕获当前帧\n */\n const captureFrame = useCallback(async (): Promise<Blob | null> => {\n const video = videoRef.current;\n const canvas = canvasRef.current;\n \n if (!video || !canvas) {\n return null;\n }\n\n // 检查视频状态\n if (video.readyState < 2) { // HAVE_CURRENT_DATA\n console.warn('视频尚未准备好,当前状态:', video.readyState);\n return null;\n }\n\n // 检查视频尺寸\n if (video.videoWidth === 0 || video.videoHeight === 0) {\n console.warn('视频尺寸无效:', { videoWidth: video.videoWidth, videoHeight: video.videoHeight });\n return null;\n }\n\n const ctx = canvas.getContext('2d');\n if (!ctx) {\n return null;\n }\n\n // 设置画布尺寸\n canvas.width = video.videoWidth;\n canvas.height = video.videoHeight;\n\n // 清除画布\n ctx.clearRect(0, 0, canvas.width, canvas.height);\n\n // 绘制当前帧\n ctx.drawImage(video, 0, 0, canvas.width, canvas.height);\n\n // 转换为Blob\n return new Promise((resolve) => {\n canvas.toBlob(resolve, 'image/jpeg', 0.8);\n });\n }, []);\n\n /**\n * 执行检测\n */\n const performDetection = useCallback(async (): Promise<void> => {\n if (!detectionEngineRef.current) {\n return;\n }\n\n // 节流控制 - 检查距离上次检测的时间是否足够\n const now = Date.now();\n const config = getConfigManager().getConfig();\n if (now - lastDetectionTimeRef.current < config.checkInterval) {\n return; // 还没到检测时间,直接返回\n }\n\n try {\n const imageData = await captureFrame();\n if (!imageData) {\n throw new Error('无法捕获图像');\n }\n\n const startTime = Date.now();\n const result = await detectionEngineRef.current.detect(imageData);\n const endTime = Date.now();\n\n // 更新最新结果\n setLatestResult(result);\n\n // 更新上次检测时间\n lastDetectionTimeRef.current = now;\n\n // 更新统计信息\n setStats(prevStats => {\n const newStats = { ...prevStats };\n newStats.checkCount++;\n newStats.latency = endTime - startTime;\n \n // 计算平均处理时间\n if (result.processingTime) {\n newStats.avgProcessingTime = \n (prevStats.avgProcessingTime * (prevStats.checkCount - 1) + result.processingTime) / \n prevStats.checkCount;\n }\n\n // 更新违规统计\n if (result.violations.length > 0) {\n newStats.abnormalCount++;\n result.violations.forEach(violation => {\n newStats.violationStats[violation.type] = \n (newStats.violationStats[violation.type] || 0) + 1;\n });\n }\n\n // 计算FPS\n const currentTime = Date.now();\n if (lastFrameTimeRef.current > 0) {\n const frameInterval = currentTime - lastFrameTimeRef.current;\n newStats.fps = Math.round(1000 / frameInterval);\n }\n lastFrameTimeRef.current = currentTime;\n\n // 评估网络质量(基于延迟)\n if (newStats.latency < 200) {\n newStats.networkQuality = 'excellent';\n } else if (newStats.latency < 500) {\n newStats.networkQuality = 'good';\n } else if (newStats.latency < 1000) {\n newStats.networkQuality = 'fair';\n } else {\n newStats.networkQuality = 'poor';\n }\n\n return newStats;\n });\n\n // 触发回调\n onDetectionResult?.(result);\n\n // 处理违规\n result.violations.forEach(violation => {\n onViolation?.(violation);\n });\n\n } catch (error) {\n const errorObj = error instanceof Error ? error : new Error('检测失败');\n onError?.(errorObj);\n }\n }, [captureFrame, onDetectionResult, onViolation, onError]);\n\n /**\n * 开始监控\n */\n const startMonitoring = useCallback(async (): Promise<void> => {\n try {\n // 获取摄像头权限\n await getCameraPermission();\n\n setIsMonitoring(true);\n } catch (error) {\n const errorObj = error instanceof Error ? error : new Error('启动监控失败');\n onError?.(errorObj);\n throw errorObj;\n }\n }, [getCameraPermission, onError]);\n\n /**\n * 停止监控\n */\n const stopMonitoring = useCallback((): void => {\n setIsMonitoring(false);\n\n // 停止所有轨道\n if (streamRef.current) {\n streamRef.current.getTracks().forEach(track => track.stop());\n streamRef.current = null;\n }\n\n // 清除视频源\n if (videoRef.current) {\n videoRef.current.srcObject = null;\n }\n\n // 清除定时器\n if (detectionTimerRef.current) {\n clearInterval(detectionTimerRef.current);\n detectionTimerRef.current = null;\n }\n }, []);\n\n /**\n * 强制检测\n */\n const forceCheck = useCallback(async (): Promise<void> => {\n await performDetection();\n }, [performDetection]);\n\n /**\n * 更新配置\n */\n const updateConfig = useCallback(async (newConfig: Partial<DetectionConfig>) => {\n if (!configManagerRef.current) {\n return { success: false, errors: ['配置管理器未初始化'] };\n }\n\n return configManagerRef.current.updateConfig(newConfig, {\n source: 'local',\n operator: 'hook-user'\n });\n }, []);\n\n /**\n * 获取当前配置\n */\n const getCurrentConfig = useCallback((): DetectionConfig => {\n const config = configManagerRef.current?.getConfig();\n \n // 如果配置管理器未初始化或返回空配置,使用默认值\n if (!config || !config.checkInterval) {\n return DEFAULT_DETECTION_CONFIG;\n }\n \n return config;\n }, []);\n\n /**\n * 获取配置管理器实例\n */\n const getConfigManager = useCallback((): ConfigManager => {\n if (!configManagerRef.current) {\n throw new Error('配置管理器未初始化');\n }\n return configManagerRef.current;\n }, []);\n\n // 清理资源\n useEffect(() => {\n return () => {\n stopMonitoring();\n };\n }, [stopMonitoring]);\n\n // 监控状态变化时启动或停止定时器\n useEffect(() => {\n if (isMonitoring) {\n // 启动循环\n detectionTimerRef.current = window.setInterval(performDetection, 1000); // 每秒检查一次是否准备就绪(内部有节流)\n } else {\n if (detectionTimerRef.current) {\n window.clearInterval(detectionTimerRef.current);\n detectionTimerRef.current = null;\n }\n }\n\n return () => {\n if (detectionTimerRef.current) {\n window.clearInterval(detectionTimerRef.current);\n }\n };\n }, [isMonitoring, performDetection]);\n\n // 组件卸载时清理资源\n useEffect(() => {\n return () => {\n stopMonitoring();\n };\n }, [stopMonitoring]);\n\n // 返回Hook接口\n return {\n videoRef,\n canvasRef,\n startMonitoring,\n stopMonitoring,\n forceCheck,\n isMonitoring,\n latestResult,\n stats,\n config: getCurrentConfig(),\n updateConfig,\n getConfigManager\n };\n}"],"names":["DEFAULT_DETECTION_CONFIG","checkInterval","detectMultiplePeople","detectDeviceUsage","detectAbsence","resolution","width","height","type","earphoneThreshold","cellphoneThreshold","headPoseThreshold","pitch","roll","yaw","faceCompletenessThreshold","centeringThreshold","movementThreshold","CheatingType","CHEATING_TYPE_LABELS","PERSON_COUNT_ANOMALY","EARPHONE_DETECTED","CELLPHONE_DETECTED","HEAD_POSTURE_ABNORMAL","FACE_MISSING","FACE_OCCLUDED","NO_FACE_DETECTED","MULTIPLE_FACES_DETECTED","NOT_CENTERED","SUSPICIOUS_MOVEMENT","ViolationLevel","CHEATING_TYPE_SEVERITY","ConfigManager","constructor","this","auditLogs","listeners","Set","STORAGE_KEY","config","getDefaultConfig","loadFromStorage","startHotUpdate","getInstance","instance","stored","localStorage","getItem","parsedConfig","JSON","parse","error","saveToStorage","setItem","stringify","window","addEventListener","e","key","newValue","newConfig","updateConfig","source","addAuditLog","operation","configKey","oldValue","operator","log","id","Date","now","Math","random","toString","substr","timestamp","push","length","slice","validateConfig","errors","getConfig","options","force","success","Object","entries","forEach","value","notifyListeners","resetConfig","oldConfig","keys","addListener","listener","add","delete","getAuditLogs","clearAuditLogs","olderThan","filter","destroy","syncTimer","clearInterval","clear","OSSClient","credentials","isInitialized","client","initialize","Promise","resolve","bucket","region","Error","accessKeyId","accessKeySecret","OSSModule","then","require","n","aliyunOssSdk_min","OSSClass","default","stsToken","securityToken","put","blob","dir","filename","objectKey","result","headers","url","message","ready","updateCredentials","DetectionEngine","ossConfig","ossClient","updateOSSConfig","detect","imageData","startTime","imgBlob","Blob","base64ToBlob","imageUrl","apiResponse","callVIAPI","processAPIResponse","processingTime","faceCount","faceCompleteness","personCount","violations","level","HIGH","confidence","description","base64","parts","split","mimeType","match","byteString","atob","arrayBuffer","ArrayBuffer","uint8Array","Uint8Array","i","charCodeAt","params","Action","Version","Format","AccessKeyId","SignatureMethod","Timestamp","getTimestamp","SignatureVersion","SignatureNonce","generateNonce","RegionId","Type","String","ImageURL","SecurityToken","canonicalizedQueryString","sort","map","percentEncode","join","stringToSign","signature","hmacSha1","bodyString","response","fetch","method","body","mode","ok","status","statusText","json","data","encoder","TextEncoder","keyData","encode","dataData","cryptoKey","crypto","subtle","importKey","name","hash","sign","btoa","fromCharCode","Array","from","str","encodeURIComponent","replace","toISOString","substring","faceInfo","Data","FaceInfo","personInfo","PersonInfo","faceNumber","FaceNumber","personNumber","PersonNumber","completeness","Completeness","pose","Pose","createViolation","MEDIUM","toFixed","Pitch","Roll","Yaw","pThresh","rThresh","yThresh","abs","max","earphoneScore","EarPhone","Score","cellphoneScore","CellPhone","centeringScore","calculateCenteringScore","LOW","rawResponse","min","getDetectionStats","totalDetections","averageProcessingTime","violationRate","mostCommonViolations","VERSION","LIB_INFO","version","author","license","repository","homepage","logs","MAX_LOGS","addLog","auditLog","generateLogId","unshift","persistLogs","queryLogs","filteredLogs","endTime","offset","limit","getAllLogs","getStats","operationStats","create","update","sync","sourceStats","local","remote","backend_sync","configCounts","operatorCounts","mostActiveConfigs","a","b","count","mostActiveOperators","totalLogs","cleanup","beforeCount","cleanedCount","exportLogs","format","convertToCSV","importLogs","logsData","importedLogs","validLogs","validateLog","existingIds","newLogs","has","includes","rows","row","cell","isArray","getRecentLogs","getLogsByConfigKey","getLogsByOperator","props","initialConfig","onViolation","onDetectionResult","onError","onConfigChange","isMonitoring","setIsMonitoring","useState","latestResult","setLatestResult","stats","setStats","checkCount","abnormalCount","latency","networkQuality","fps","avgProcessingTime","violationStats","fromEntries","values","videoRef","useRef","canvasRef","streamRef","detectionTimerRef","configManagerRef","detectionEngineRef","lastFrameTimeRef","lastDetectionTimeRef","current","useEffect","unsubscribe","getCameraPermission","useCallback","async","stream","navigator","mediaDevices","getUserMedia","video","facingMode","audio","srcObject","play","catch","captureFrame","canvas","readyState","videoWidth","videoHeight","ctx","getContext","clearRect","drawImage","toBlob","performDetection","getConfigManager","prevStats","newStats","violation","currentTime","frameInterval","round","errorObj","startMonitoring","stopMonitoring","getTracks","track","stop","forceCheck","getCurrentConfig","setInterval"],"mappings":"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"}
|