@wq-hook/anti-cheating-monitor 1.0.0

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.
@@ -0,0 +1,671 @@
1
+ /**
2
+ * 阿里云凭证接口
3
+ */
4
+ export declare interface AntiCheatingCredentials {
5
+ /** 访问密钥ID */
6
+ accessKeyId: string;
7
+ /** 访问密钥Secret */
8
+ accessKeySecret: string;
9
+ /** 安全令牌(可选) */
10
+ securityToken?: string;
11
+ /** 区域 */
12
+ region?: string;
13
+ }
14
+
15
+ /**
16
+ * 审计日志管理器类
17
+ * 负责管理和分析配置变更审计日志
18
+ */
19
+ export declare class AuditLogger {
20
+ private logs;
21
+ private readonly MAX_LOGS;
22
+ /**
23
+ * 添加审计日志
24
+ */
25
+ addLog(log: Omit<ConfigAuditLog, 'id' | 'timestamp'>): ConfigAuditLog;
26
+ /**
27
+ * 生成日志ID
28
+ */
29
+ private generateLogId;
30
+ /**
31
+ * 查询审计日志
32
+ */
33
+ queryLogs(options?: AuditLogQueryOptions): ConfigAuditLog[];
34
+ /**
35
+ * 获取所有日志
36
+ */
37
+ getAllLogs(): ConfigAuditLog[];
38
+ /**
39
+ * 获取日志统计信息
40
+ */
41
+ getStats(): AuditLogStats;
42
+ /**
43
+ * 清理旧日志
44
+ */
45
+ cleanup(olderThan: number): number;
46
+ /**
47
+ * 清空所有日志
48
+ */
49
+ clear(): void;
50
+ /**
51
+ * 导出日志为JSON
52
+ */
53
+ exportLogs(format?: 'json' | 'csv'): string;
54
+ /**
55
+ * 导入日志
56
+ */
57
+ importLogs(logsData: string, format?: 'json' | 'csv'): number;
58
+ /**
59
+ * 验证日志格式
60
+ */
61
+ private validateLog;
62
+ /**
63
+ * 转换为CSV格式
64
+ */
65
+ private convertToCSV;
66
+ /**
67
+ * 持久化日志到本地存储
68
+ */
69
+ private persistLogs;
70
+ /**
71
+ * 从本地存储加载日志
72
+ */
73
+ loadFromStorage(): void;
74
+ /**
75
+ * 获取最近的日志
76
+ */
77
+ getRecentLogs(count?: number): ConfigAuditLog[];
78
+ /**
79
+ * 根据配置键获取日志
80
+ */
81
+ getLogsByConfigKey(configKey: string): ConfigAuditLog[];
82
+ /**
83
+ * 根据操作者获取日志
84
+ */
85
+ getLogsByOperator(operator: string): ConfigAuditLog[];
86
+ }
87
+
88
+ /**
89
+ * 审计日志查询选项
90
+ */
91
+ export declare interface AuditLogQueryOptions {
92
+ /** 开始时间戳 */
93
+ startTime?: number;
94
+ /** 结束时间戳 */
95
+ endTime?: number;
96
+ /** 操作类型过滤 */
97
+ operation?: ConfigAuditLog['operation'];
98
+ /** 配置项过滤 */
99
+ configKey?: string;
100
+ /** 操作者过滤 */
101
+ operator?: string;
102
+ /** 来源过滤 */
103
+ source?: ConfigAuditLog['source'];
104
+ /** 分页选项 */
105
+ limit?: number;
106
+ offset?: number;
107
+ }
108
+
109
+ /**
110
+ * 审计日志统计信息
111
+ */
112
+ export declare interface AuditLogStats {
113
+ /** 总日志数 */
114
+ totalLogs: number;
115
+ /** 按操作类型统计 */
116
+ operationStats: Record<ConfigAuditLog['operation'], number>;
117
+ /** 按来源统计 */
118
+ sourceStats: Record<ConfigAuditLog['source'], number>;
119
+ /** 最活跃的配置项 */
120
+ mostActiveConfigs: Array<{
121
+ configKey: string;
122
+ count: number;
123
+ }>;
124
+ /** 最活跃的操作者 */
125
+ mostActiveOperators: Array<{
126
+ operator: string;
127
+ count: number;
128
+ }>;
129
+ }
130
+
131
+ /**
132
+ * 作弊类型显示名称映射
133
+ */
134
+ export declare const CHEATING_TYPE_LABELS: Record<CheatingType, string>;
135
+
136
+ /**
137
+ * 作弊类型严重程度映射
138
+ */
139
+ export declare const CHEATING_TYPE_SEVERITY: Record<CheatingType, ViolationLevel>;
140
+
141
+ /**
142
+ * 作弊类型枚举
143
+ * 基于Java后端逻辑映射的前端检测类型
144
+ */
145
+ export declare enum CheatingType {
146
+ /** 人数异常 - 检测到多人 */
147
+ PERSON_COUNT_ANOMALY = "PERSON_COUNT_ANOMALY",
148
+ /** 检测到耳机 */
149
+ EARPHONE_DETECTED = "EARPHONE_DETECTED",
150
+ /** 检测到手机 */
151
+ CELLPHONE_DETECTED = "CELLPHONE_DETECTED",
152
+ /** 头部姿态异常 */
153
+ HEAD_POSTURE_ABNORMAL = "HEAD_POSTURE_ABNORMAL",
154
+ /** 人脸缺失 */
155
+ FACE_MISSING = "FACE_MISSING",
156
+ /** 人脸被遮挡 */
157
+ FACE_OCCLUDED = "FACE_OCCLUDED",
158
+ /** 未检测到人脸 */
159
+ NO_FACE_DETECTED = "NO_FACE_DETECTED",
160
+ /** 检测到多张人脸 */
161
+ MULTIPLE_FACES_DETECTED = "MULTIPLE_FACES_DETECTED",
162
+ /** 未居中 */
163
+ NOT_CENTERED = "NOT_CENTERED",
164
+ /** 可疑移动 */
165
+ SUSPICIOUS_MOVEMENT = "SUSPICIOUS_MOVEMENT"
166
+ }
167
+
168
+ /**
169
+ * 作弊违规信息
170
+ */
171
+ export declare interface CheatingViolation {
172
+ /** 违规类型 */
173
+ type: CheatingType;
174
+ /** 严重程度 */
175
+ level: ViolationLevel;
176
+ /** 置信度 (0-1) */
177
+ confidence: number;
178
+ /** 违规描述 */
179
+ description: string;
180
+ /** 检测到的具体数据 */
181
+ data?: any;
182
+ }
183
+
184
+ /**
185
+ * 配置审计日志接口
186
+ */
187
+ export declare interface ConfigAuditLog {
188
+ /** 日志ID */
189
+ id: string;
190
+ /** 操作时间戳 */
191
+ timestamp: number;
192
+ /** 操作类型 */
193
+ operation: 'create' | 'update' | 'delete' | 'sync';
194
+ /** 配置项名称 */
195
+ configKey: string;
196
+ /** 旧值 */
197
+ oldValue?: any;
198
+ /** 新值 */
199
+ newValue?: any;
200
+ /** 操作者 */
201
+ operator?: string;
202
+ /** 操作来源 */
203
+ source: 'local' | 'remote' | 'backend_sync';
204
+ }
205
+
206
+ /**
207
+ * 配置管理器类
208
+ * 负责阈值配置的管理、热更新和审计
209
+ */
210
+ export declare class ConfigManager {
211
+ private static instance;
212
+ private config;
213
+ private auditLogs;
214
+ private listeners;
215
+ private syncTimer?;
216
+ private readonly STORAGE_KEY;
217
+ private constructor();
218
+ /**
219
+ * 获取单例实例
220
+ */
221
+ static getInstance(): ConfigManager;
222
+ /**
223
+ * 获取默认配置
224
+ */
225
+ private getDefaultConfig;
226
+ /**
227
+ * 从本地存储加载配置
228
+ */
229
+ private loadFromStorage;
230
+ /**
231
+ * 保存配置到本地存储
232
+ */
233
+ private saveToStorage;
234
+ /**
235
+ * 启动热更新监听
236
+ */
237
+ private startHotUpdate;
238
+ /**
239
+ * 添加审计日志
240
+ */
241
+ private addAuditLog;
242
+ /**
243
+ * 验证配置有效性
244
+ */
245
+ private validateConfig;
246
+ /**
247
+ * 获取当前配置
248
+ */
249
+ getConfig(): DetectionConfig;
250
+ /**
251
+ * 更新配置
252
+ */
253
+ updateConfig(newConfig: Partial<DetectionConfig>, options?: ConfigUpdateOptions): {
254
+ success: boolean;
255
+ errors?: string[];
256
+ };
257
+ /**
258
+ * 重置配置为默认值
259
+ */
260
+ resetConfig(source?: ConfigAuditLog['source'], operator?: string): void;
261
+ /**
262
+ * 添加配置变更监听器
263
+ */
264
+ addListener(listener: (config: DetectionConfig) => void): () => void;
265
+ /**
266
+ * 通知所有监听器
267
+ */
268
+ private notifyListeners;
269
+ /**
270
+ * 获取审计日志
271
+ */
272
+ getAuditLogs(): ConfigAuditLog[];
273
+ /**
274
+ * 清理审计日志
275
+ */
276
+ clearAuditLogs(olderThan?: number): void;
277
+ /**
278
+ * 销毁实例
279
+ */
280
+ destroy(): void;
281
+ }
282
+
283
+ /**
284
+ * 配置更新选项
285
+ */
286
+ export declare interface ConfigUpdateOptions {
287
+ /** 是否强制更新(忽略验证) */
288
+ force?: boolean;
289
+ /** 更新来源 */
290
+ source?: "local" | "remote" | "backend_sync";
291
+ /** 操作者标识 */
292
+ operator?: string;
293
+ }
294
+
295
+ /**
296
+ * 默认检测配置
297
+ */
298
+ export declare const DEFAULT_DETECTION_CONFIG: DetectionConfig;
299
+
300
+ /**
301
+ * 检测配置接口
302
+ */
303
+ export declare interface DetectionConfig {
304
+ /** 检测间隔(毫秒) */
305
+ checkInterval: number;
306
+ /** 是否检测多人 */
307
+ detectMultiplePeople: boolean;
308
+ /** 是否检测设备使用 */
309
+ detectDeviceUsage: boolean;
310
+ /** 是否检测离席 */
311
+ detectAbsence: boolean;
312
+ /** 视频分辨率 */
313
+ resolution: {
314
+ /** 视频宽度 */
315
+ width: number;
316
+ /** 视频高度 */
317
+ height: number;
318
+ };
319
+ /** 检测类型
320
+ * 0: 屏幕聊天工具检测
321
+ * 1: 考生状态检测
322
+ */
323
+ type: number;
324
+ /** 耳机检测阈值 */
325
+ earphoneThreshold: number;
326
+ /** 手机检测阈值 */
327
+ cellphoneThreshold: number;
328
+ /** 头部姿态阈值 */
329
+ headPoseThreshold: {
330
+ /** 俯仰角阈值 */
331
+ pitch: number;
332
+ /** 翻滚角阈值 */
333
+ roll: number;
334
+ /** 偏航角阈值 */
335
+ yaw: number;
336
+ };
337
+ /** 人脸完整度阈值 */
338
+ faceCompletenessThreshold: number;
339
+ /** 人脸居中阈值 */
340
+ centeringThreshold: number;
341
+ /** 可疑移动检测阈值 */
342
+ movementThreshold: number;
343
+ }
344
+
345
+ /**
346
+ * 检测引擎类
347
+ * 负责执行反作弊检测逻辑,集成OSS上传和阿里云VIAPI调用
348
+ */
349
+ export declare class DetectionEngine {
350
+ private config;
351
+ private credentials;
352
+ private ossConfig;
353
+ private ossClient;
354
+ constructor(config: DetectionConfig, credentials: AntiCheatingCredentials, ossConfig: OSSConfig);
355
+ /**
356
+ * 更新配置
357
+ */
358
+ updateConfig(config: DetectionConfig): void;
359
+ /**
360
+ * 更新凭证
361
+ */
362
+ updateCredentials(credentials: AntiCheatingCredentials): void;
363
+ /**
364
+ * 更新OSS配置
365
+ */
366
+ updateOSSConfig(ossConfig: OSSConfig): void;
367
+ /**
368
+ * 执行检测
369
+ * @param imageData 图像数据(Base64或Blob)
370
+ */
371
+ detect(imageData: string | Blob): Promise<DetectionResult>;
372
+ /**
373
+ * Base64转Blob
374
+ * @private
375
+ */
376
+ private base64ToBlob;
377
+ /**
378
+ * 调用阿里云VIAPI
379
+ * @private
380
+ */
381
+ private callVIAPI;
382
+ /**
383
+ * 使用HMAC-SHA1算法生成签名
384
+ * @private
385
+ */
386
+ private hmacSha1;
387
+ /**
388
+ * URL编码函数
389
+ * @private
390
+ */
391
+ private percentEncode;
392
+ /**
393
+ * 获取当前时间戳
394
+ * @private
395
+ */
396
+ private getTimestamp;
397
+ /**
398
+ * 生成随机字符串
399
+ * @private
400
+ */
401
+ private generateNonce;
402
+ /**
403
+ * 处理API响应,生成检测结果
404
+ * @private
405
+ */
406
+ private processAPIResponse;
407
+ /**
408
+ * 创建违规对象
409
+ * @private
410
+ */
411
+ private createViolation;
412
+ /**
413
+ * 计算居中程度
414
+ * @private
415
+ */
416
+ private calculateCenteringScore;
417
+ /**
418
+ * 获取检测统计信息
419
+ */
420
+ getDetectionStats(): {
421
+ totalDetections: number;
422
+ averageProcessingTime: number;
423
+ violationRate: number;
424
+ mostCommonViolations: Array<{
425
+ type: CheatingType;
426
+ count: number;
427
+ }>;
428
+ };
429
+ }
430
+
431
+ /**
432
+ * 检测结果接口
433
+ */
434
+ export declare interface DetectionResult {
435
+ /** 检测时间戳 */
436
+ timestamp: number;
437
+ /** 人脸数量 */
438
+ faceCount: number;
439
+ /** 人脸完整度 (0-1) */
440
+ faceCompleteness: number;
441
+ /** 人员数量 */
442
+ personCount: number;
443
+ /** 人脸姿态信息 */
444
+ pose?: {
445
+ pitch: number;
446
+ roll: number;
447
+ yaw: number;
448
+ };
449
+ /** 检测到的作弊类型列表 */
450
+ violations: CheatingViolation[];
451
+ /** API 原始响应 */
452
+ rawResponse?: any;
453
+ /** 检测耗时(毫秒) */
454
+ processingTime?: number;
455
+ }
456
+
457
+ /**
458
+ * 库信息
459
+ */
460
+ export declare const LIB_INFO: {
461
+ readonly name: "anti-cheating-monitor";
462
+ readonly version: "2.0.0";
463
+ readonly description: "基于阿里云视觉智能平台的实时反作弊监控系统";
464
+ readonly author: "Anti-Cheating Monitor Team";
465
+ readonly license: "MIT";
466
+ readonly repository: "https://github.com/coding-daily-wq/anti-cheating-monitor.git";
467
+ readonly homepage: "https://coding-daily-wq.github.io/anti-cheating-monitor";
468
+ };
469
+
470
+ /**
471
+ * 监控统计信息
472
+ */
473
+ export declare interface MonitorStats {
474
+ /** 检测次数 */
475
+ checkCount: number;
476
+ /** 异常次数 */
477
+ abnormalCount: number;
478
+ /** 网络延迟(毫秒) */
479
+ latency: number;
480
+ /** 网络质量 */
481
+ networkQuality: 'excellent' | 'good' | 'fair' | 'poor';
482
+ /** 帧率 */
483
+ fps: number;
484
+ /** 平均处理时间 */
485
+ avgProcessingTime: number;
486
+ /** 违规统计 */
487
+ violationStats: Record<CheatingType, number>;
488
+ }
489
+
490
+ /**
491
+ * 阿里云OSS配置接口
492
+ */
493
+ export declare interface OSSConfig {
494
+ /** OSS存储桶名称 */
495
+ bucket: string;
496
+ /** OSS区域 */
497
+ region: string;
498
+ /** 访问域名(可选) */
499
+ endpoint?: string;
500
+ /** 安全令牌(可选,用于STS临时访问) */
501
+ securityToken?: string;
502
+ }
503
+
504
+ /**
505
+ * 反作弊监控Hook
506
+ * 集成阿里云OSS上传和VIAPI人脸检测服务
507
+ *
508
+ * @param props Hook参数
509
+ * @returns Hook返回值
510
+ *
511
+ * @example
512
+ * ```tsx
513
+ * const {
514
+ * videoRef,
515
+ * startMonitoring,
516
+ * stopMonitoring,
517
+ * isMonitoring,
518
+ * latestResult,
519
+ * stats,
520
+ * config,
521
+ * updateConfig
522
+ * } = useAntiCheatingMonitor({
523
+ * credentials: {
524
+ * accessKeyId: 'your-access-key-id',
525
+ * accessKeySecret: 'your-access-key-secret'
526
+ * },
527
+ * ossConfig: {
528
+ * bucket: 'your-bucket-name',
529
+ * region: 'oss-cn-hangzhou'
530
+ * },
531
+ * config: {
532
+ * checkInterval: 3000,
533
+ * detectMultiplePeople: true
534
+ * },
535
+ * onViolation: (violation) => {
536
+ * console.log('检测到违规:', violation);
537
+ * },
538
+ * onDetectionResult: (result) => {
539
+ * console.log('检测结果:', result);
540
+ * },
541
+ * onError: (error) => {
542
+ * console.error('监控错误:', error);
543
+ * }
544
+ * });
545
+ * ```
546
+ */
547
+ export declare function useAntiCheatingMonitor(props: UseAntiCheatingMonitorProps): UseAntiCheatingMonitorReturn;
548
+
549
+ /**
550
+ * Hook参数接口
551
+ */
552
+ export declare interface UseAntiCheatingMonitorProps {
553
+ /** 阿里云访问凭证 */
554
+ credentials: AntiCheatingCredentials;
555
+ /** OSS配置 */
556
+ ossConfig: OSSConfig;
557
+ /** 检测配置 */
558
+ config?: Partial<DetectionConfig>;
559
+ /** 违规检测回调函数 */
560
+ onViolation?: (violation: CheatingViolation) => void;
561
+ /** 检测结果回调函数 */
562
+ onDetectionResult?: (result: DetectionResult) => void;
563
+ /** 错误回调函数 */
564
+ onError?: (error: Error) => void;
565
+ /** 配置变更回调函数 */
566
+ onConfigChange?: (config: DetectionConfig) => void;
567
+ }
568
+
569
+ /**
570
+ * Hook返回值接口
571
+ */
572
+ export declare interface UseAntiCheatingMonitorReturn {
573
+ /** 视频元素引用 */
574
+ videoRef: React.RefObject<HTMLVideoElement | null>;
575
+ /** 画布元素引用 */
576
+ canvasRef: React.RefObject<HTMLCanvasElement | null>;
577
+ /** 开始监控 */
578
+ startMonitoring: () => Promise<void>;
579
+ /** 停止监控 */
580
+ stopMonitoring: () => void;
581
+ /** 强制检测一次 */
582
+ forceCheck: () => Promise<void>;
583
+ /** 是否正在监控 */
584
+ isMonitoring: boolean;
585
+ /** 最新检测结果 */
586
+ latestResult: DetectionResult | null;
587
+ /** 监控统计信息 */
588
+ stats: MonitorStats;
589
+ /** 当前配置 */
590
+ config: DetectionConfig;
591
+ /** 更新配置 */
592
+ updateConfig: (newConfig: Partial<DetectionConfig>) => Promise<{
593
+ success: boolean;
594
+ errors?: string[];
595
+ }>;
596
+ /** 获取配置管理器实例 */
597
+ getConfigManager: () => ConfigManager;
598
+ }
599
+
600
+ export declare const VERSION = "2.0.0";
601
+
602
+ /**
603
+ * VIAPI请求参数
604
+ */
605
+ export declare interface VIAPIRequestParams {
606
+ /** 图片URL或Base64数据 */
607
+ image: string;
608
+ /** 检测类型 */
609
+ type: number;
610
+ /** 返回额外信息 */
611
+ returnExtraInfo?: boolean;
612
+ }
613
+
614
+ /**
615
+ * VIAPI响应数据
616
+ */
617
+ export declare interface VIAPIResponse {
618
+ /** 请求ID */
619
+ RequestId: string;
620
+ /** 检测结果 */
621
+ Data: {
622
+ /** 人脸信息 */
623
+ FaceInfo?: {
624
+ /** 人脸完整度 */
625
+ Completeness: number;
626
+ /** 人脸姿态 */
627
+ Pose?: {
628
+ Pitch: number;
629
+ Roll: number;
630
+ Yaw: number;
631
+ };
632
+ /** 人脸数量 */
633
+ FaceNumber: number;
634
+ };
635
+ /** 人员信息 */
636
+ PersonInfo?: {
637
+ /** 人员数量 */
638
+ PersonNumber: number;
639
+ /** 耳机检测结果 */
640
+ EarPhone?: {
641
+ Score: number;
642
+ Threshold: number;
643
+ };
644
+ /** 手机检测结果 */
645
+ CellPhone?: {
646
+ Score: number;
647
+ Threshold: number;
648
+ };
649
+ };
650
+ };
651
+ /** 错误码 */
652
+ code?: string;
653
+ /** 错误信息 */
654
+ message?: string;
655
+ }
656
+
657
+ /**
658
+ * 违规严重程度
659
+ */
660
+ export declare enum ViolationLevel {
661
+ /** 低风险 - 轻微违规 */
662
+ LOW = "low",
663
+ /** 中风险 - 需要注意 */
664
+ MEDIUM = "medium",
665
+ /** 高风险 - 严重违规 */
666
+ HIGH = "high",
667
+ /** 紧急 - 需要立即处理 */
668
+ CRITICAL = "critical"
669
+ }
670
+
671
+ export { }
@@ -0,0 +1,3 @@
1
+ import{useState as t,useRef as e,useEffect as i,useCallback as o}from"react";import n from"ali-oss";const s={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 a={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 c=/* @__PURE__ */(t=>(t.LOW="low",t.MEDIUM="medium",t.HIGH="high",t.CRITICAL="critical",t))(c||{});const h={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{...s}}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",s){const r={id:`${Date.now()}-${Math.random().toString(36).substr(2,9)}`,timestamp:Date.now(),operation:t,configKey:e,oldValue:i,newValue:o,operator:s,source:n};this.auditLogs.push(r),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");this.client=new n({region:this.config.region,accessKeyId:this.credentials.accessKeyId,accessKeySecret:this.credentials.accessKeySecret,stsToken:this.credentials.securityToken,bucket:this.config.bucket}),this.isInitialized=!0}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();try{let i;if(t instanceof Blob){i=(await this.ossClient.put(t)).url}else{const e=this.base64ToBlob(t);i=(await this.ossClient.put(e)).url}const o=await this.callVIAPI(i),n=this.processAPIResponse(o);return n.processingTime=Date.now()-e,n}catch(i){return{timestamp:Date.now(),faceCount:0,faceCompleteness:0,personCount:0,violations:[{type:r.NO_FACE_DETECTED,level:c.HIGH,confidence:1,description:`检测失败: ${i instanceof Error?i.message:"未知错误"}`}],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),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(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),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(t,e){const i=new TextEncoder,o=i.encode(t),n=i.encode(e),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(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){const e=[],i=t.Data?.FaceInfo||{},o=t.Data?.PersonInfo||{},n=i.FaceNumber||0,s=o.PersonNumber||0,a=i.Completeness||0,h=i.Pose||{};if(0===n?e.push(this.createViolation(r.NO_FACE_DETECTED,c.HIGH,1,"未检测到人脸")):n>1&&e.push(this.createViolation(r.MULTIPLE_FACES_DETECTED,c.HIGH,1,`检测到${n}张人脸`)),this.config.detectMultiplePeople&&s>1&&e.push(this.createViolation(r.PERSON_COUNT_ANOMALY,c.HIGH,1,`检测到${s}人`)),a<this.config.faceCompletenessThreshold&&e.push(this.createViolation(r.FACE_OCCLUDED,c.MEDIUM,1-a,`人脸完整度${(100*a).toFixed(0)}%`)),h){const{Pitch:t,Roll:i,Yaw:o}=h,{pitch:n,roll:s,yaw:a}=this.config.headPoseThreshold;(Math.abs(t)>n||Math.abs(i)>s||Math.abs(o)>a)&&e.push(this.createViolation(r.HEAD_POSTURE_ABNORMAL,c.MEDIUM,Math.max(Math.abs(t)/n,Math.abs(i)/s,Math.abs(o)/a)-1,`头部姿态异常: ${t>0?"抬头":"低头"}${t.toFixed(1)}° 偏头${i.toFixed(1)}° ${o>0?"左转":"右转"}${o.toFixed(1)}°`))}if(this.config.detectDeviceUsage){const t=o.EarPhone?.Score||0;t>this.config.earphoneThreshold&&e.push(this.createViolation(r.EARPHONE_DETECTED,c.MEDIUM,t,`检测到耳机 (置信度: ${(100*t).toFixed(0)}%)`));const i=o.CellPhone?.Score||0;i>this.config.cellphoneThreshold&&e.push(this.createViolation(r.CELLPHONE_DETECTED,c.HIGH,i,`检测到手机 (置信度: ${(100*i).toFixed(0)}%)`))}if(h){const t=this.calculateCenteringScore(h);t<this.config.centeringThreshold&&e.push(this.createViolation(r.NOT_CENTERED,c.LOW,1-t,`未居中 (居中度: ${(100*t).toFixed(0)}%)`))}return{timestamp:Date.now(),faceCount:n,faceCompleteness:a,personCount:s,pose:h,violations:e,rawResponse:t}}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})),s=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:s}}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(r){throw new Error("JSON格式错误")}const o=i.filter(t=>this.validateLog(t)),n=new Set(this.logs.map(t=>t.id)),s=o.filter(t=>!n.has(t.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(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(n){const{credentials:a,ossConfig:c,config:h,onViolation:l,onDetectionResult:g,onError:u,onConfigChange:d}=n,[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=H().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 s=Date.now();if(M.current>0){const t=s-M.current;e.fps=Math.round(1e3/t)}return M.current=s,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:s},[]),H=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:H}}const l="2.0.0",g={name:"anti-cheating-monitor",version:l,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,a as CHEATING_TYPE_LABELS,h as CHEATING_TYPE_SEVERITY,r as CheatingType,ConfigManager,s as DEFAULT_DETECTION_CONFIG,DetectionEngine,g as LIB_INFO,l as VERSION,c as ViolationLevel,useAntiCheatingMonitor};
3
+ //# sourceMappingURL=index.es.js.map