V5.0 安全模块大扩展:Meteor Client 全功能反作弊 + AI行为分析 + 服务器保护
新增 45 个安全模块,覆盖 Meteor Client 全部 hack 类别: 【反作弊基础扩展 6 模块】 - AntiTimerService: 游戏加速检测(移动tick间隔分析) - AntiVelocityService: 击退修改检测(伤害后速度分析) - AntiPhaseService: 穿墙检测(固体方块射线追踪) - AntiBlinkService: 闪烁检测(伤害-断连-重连时序) - AntiFastBreakService: 快速破坏检测(方块标准破坏时间对比) - AntiElytraFlyService: 鞘翅飞行检测(速度/高度操控分析) 【Meteor Combat 10 模块】 - AntiCriticalsService: 自动暴击检测(跳跃/地面状态验证) - AntiAutoCrystalService: 水晶Aura检测(放置/爆炸时序分析) - AntiAutoTotemService: 自动图腾检测(副手装备速度分析) - AntiSurroundService: 自动包围检测(脚下方块精确放置模式) - AntiAutoTrapService: 自动陷阱检测(目标周围方块网格分析) - AntiAutoArmorService: 自动装备检测(多槽位同tick交换) - AntiChestSwapService: 自动胸甲交换检测(鞘翅-胸甲乒乓切换) - AntiAutoLogService: 自动断线检测(伤害-断连500ms时序) - AntiHitboxesService: 扩展碰撞箱检测(射线-边界边缘命中率) - AntiBowAimBotService: 弓箭自瞄检测(移动目标精度分析) 【Meteor Movement 10 模块】 - AntiNoSlowService: 无减速检测(物品使用+移动速度组合) - AntiSpiderService: 爬墙检测(无攀爬方块垂直上升) - AntiStepService: 自动跨步检测(非跳跃1格上升) - AntiPacketFlyService: 数据包飞行检测(连续悬浮/弹跳模式) - AntiAirJumpService: 空中跳跃检测(离地跳跃包) - AntiLongJumpService: 远跳检测(跳跃后水平速度衰减分析) - AntiAntiHungerService: 免饥饿检测(活动量vs饥饿消耗率) - AntiFastFallService: 快速下落检测(超终端速度持续下落) - AntiVClipService: 垂直穿墙检测(固体方块间瞬移) - AntiNoFallService 增强: 新增GroundSpoof/数据包地面/坠落累积/瞬移坠落检测 【Meteor World+Player 10 模块】 - AntiSpeedMineService: 加速挖掘检测(方块标准破坏时间对比) - AntiFastUseService: 快速使用检测(物品冷却时间分析) - AntiNoInteractService: 交互绕过检测(持剑开箱等异常操作) - AntiAutoMineService: 自动挖掘检测(挖掘间隔方差/视角追踪) - AntiVeinMinerService: 连锁挖矿检测(相邻矿石快速序列) - AntiAutoToolService: 自动工具检测(同tick切工具+开挖) - AntiFakePlayerService: 假人检测(心跳响应/交互缺失/移动模式) - AntiPistonAuraService: 活塞光环检测(战斗上下文活塞激活率) - AntiAnchorService: 锚定检测(受击零位移+封闭空间) - AntiStashFinderService: 仓库扫描检测(网格区块加载+零交互) 【网络协议安全 5 模块】 - ProtocolStateValidator: 协议状态机验证(四态转换违规检测) - TokenBucketRateLimiter: 令牌桶速率限制器(可复用速率控制基元) - BotFingerprintDetector: 机器人指纹检测(五维行为指纹分析) - NBTExploitPrevention: NBT漏洞防护(深度/大小/书页/告示牌验证) - ConnectionHandshakeValidator: 握手完整性验证(协议版本/状态/主机名) 【服务器保护 4 模块】 - ChunkLoadRateLimiter: 区块加载限制器(WARN/LIMIT/BLOCK三级响应) - EntityCountEnforcer: 实体数量执行器(分块限制+优先级清理) - RedstoneUpdateLimiter: 红石更新限制器(三级渐进+观察者钟检测) - CrashExploitSignatureDB: 崩溃漏洞签名库(12种已知漏洞+4级自动响应) 【AI/ML 行为分析 4 模块】 - BehavioralProfilingEngine: 行为分析引擎(5维特征+香农熵+Z分数) - ThreatScoreAggregator: 威胁分数聚合器(指数衰减+4级上报) - MovementPatternAnalyzer: 移动模式分析器(FFT频谱+旋转分析) - CombatPatternRecognizer: 战斗模式识别器(CPS方差+多目标熵+扫视分析) 配置更新:AlertType新增39个枚举值,SuperEvolutionConfig新增39个开关, application.yml新增39个配置项。323项全量测试通过,0失败。
This commit is contained in:
@@ -0,0 +1,552 @@
|
||||
package com.aluer.ml;
|
||||
|
||||
import com.aluer.config.ServerGuardConfig;
|
||||
import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics;
|
||||
import org.apache.commons.math3.stat.inference.TTest;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedDeque;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
/**
|
||||
* 玩家行为画像引擎 — 基于统计分析的玩家行为模式识别
|
||||
*
|
||||
* 核心算法:
|
||||
* 1. 对每个在线玩家维护5维行为特征向量(移动熵、战斗率、资源采集率、社交率、探索率)
|
||||
* 2. 使用滑动窗口(~200事件)计算每个特征的统计分布
|
||||
* 3. 将玩家特征与全服基线进行Z-score比较,计算复合异常分数(0-100)
|
||||
* 4. 将玩家行为分类为六种画像:NORMAL / GRINDER / EXPLORER / PVPER / SUSPICIOUS / BOT_LIKE
|
||||
* 5. 追踪画像转换序列 — NORMAL→SUSPICIOUS 是高风险信号
|
||||
* 6. 30分钟无活动自动清除玩家画像数据
|
||||
*
|
||||
* 数学基础:
|
||||
* - 香农熵:H(X) = -Sum(p_i * log2(p_i)) 用于度量移动方向随机性
|
||||
* - Z-score: z = (x - mu_pop) / sigma_pop 衡量玩家偏离全服均值的标准差倍数
|
||||
* - 复合异常分数:加权各维度的Z-score绝对值,映射到0-100区间
|
||||
*
|
||||
* 依赖:Apache Commons Math3 — DescriptiveStatistics(统计描述)、TTest(假设检验)
|
||||
*/
|
||||
@Service
|
||||
public class BehavioralProfilingEngine {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(BehavioralProfilingEngine.class);
|
||||
|
||||
private final ServerGuardConfig config;
|
||||
|
||||
/** 每个玩家的行为事件滑动窗口(playerName -> Deque<BehaviorEvent>) */
|
||||
private final Map<String, ConcurrentLinkedDeque<BehaviorEvent>> playerEventWindows = new ConcurrentHashMap<>();
|
||||
|
||||
/** 每个玩家的当前画像(playerName -> 画像枚举) */
|
||||
private final Map<String, PlayerProfile> playerProfiles = new ConcurrentHashMap<>();
|
||||
|
||||
/** 每个玩家最近一次画像及转换时间(playerName -> ProfileTransition) */
|
||||
private final Map<String, List<ProfileTransition>> profileTransitions = new ConcurrentHashMap<>();
|
||||
|
||||
/** 全服各维度的基线统计量 — 使用 DescriptiveStatistics 增量维护 */
|
||||
private final Map<String, DescriptiveStatistics> populationBaselines = new ConcurrentHashMap<>();
|
||||
|
||||
private final AtomicLong totalEventsProcessed = new AtomicLong(0);
|
||||
private final AtomicLong anomalyFlags = new AtomicLong(0);
|
||||
|
||||
/** 滑动窗口最大事件数 */
|
||||
private static final int MAX_WINDOW_SIZE = 200;
|
||||
|
||||
/** 画像计算所需最小样本数 */
|
||||
private static final int MIN_SAMPLES_FOR_PROFILE = 30;
|
||||
|
||||
/** 非活跃玩家清理超时(毫秒) */
|
||||
private static final long INACTIVITY_CLEANUP_MS = 30 * 60 * 1000L;
|
||||
|
||||
/** 异常Z-score阈值 — 超过此值认为该维度显著偏离 */
|
||||
private static final double ANOMALY_Z_THRESHOLD = 2.5;
|
||||
|
||||
/** 各维度的异常分数权重(总和为1.0,可根据业务调整) */
|
||||
private static final double[] FEATURE_WEIGHTS = {0.25, 0.20, 0.15, 0.15, 0.25};
|
||||
|
||||
/** 画像转换风险权重乘数 — NORMAL→SUSPICIOUS 方向性加分 */
|
||||
private static final double TRANSITION_RISK_MULTIPLIER = 1.5;
|
||||
|
||||
public BehavioralProfilingEngine() {
|
||||
this(new ServerGuardConfig());
|
||||
}
|
||||
|
||||
@Autowired
|
||||
public BehavioralProfilingEngine(ServerGuardConfig config) {
|
||||
this.config = config;
|
||||
// 初始化全服基线统计容器
|
||||
for (String feature : FeatureDimension.FEATURE_NAMES) {
|
||||
populationBaselines.put(feature, new DescriptiveStatistics());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录一次玩家行为事件,更新玩家特征向量并触发画像评估。
|
||||
*
|
||||
* 调用时机:每次玩家产生可观测行为(移动、攻击、挖掘、发言、探索新区块)时调用。
|
||||
*
|
||||
* @param playerName 玩家名称
|
||||
* @param eventType 事件类型(MOVE / ATTACK / BREAK_BLOCK / CHAT / EXPLORE)
|
||||
* @param eventValue 事件关联数值(如移动距离、攻击次数等)
|
||||
* @param timestamp 事件时间戳
|
||||
* @return 画像评估结果
|
||||
*/
|
||||
public ProfilingResult recordEvent(String playerName, String eventType,
|
||||
double eventValue, Instant timestamp) {
|
||||
totalEventsProcessed.incrementAndGet();
|
||||
|
||||
// 获取或创建玩家事件窗口
|
||||
ConcurrentLinkedDeque<BehaviorEvent> window = playerEventWindows.computeIfAbsent(
|
||||
playerName, k -> new ConcurrentLinkedDeque<>());
|
||||
|
||||
// 添加新事件并维护窗口大小
|
||||
window.addLast(new BehaviorEvent(timestamp, eventType, eventValue));
|
||||
while (window.size() > MAX_WINDOW_SIZE) {
|
||||
window.removeFirst();
|
||||
}
|
||||
|
||||
// 样本不足时返回正常(避免小样本误判)
|
||||
if (window.size() < MIN_SAMPLES_FOR_PROFILE) {
|
||||
return ProfilingResult.clean(playerName, PlayerProfile.NORMAL, 0.0);
|
||||
}
|
||||
|
||||
// 从滑动窗口中提取5维特征向量
|
||||
double[] features = extractFeatureVector(window, timestamp);
|
||||
|
||||
// 将当前特征向量加入全服基线
|
||||
updatePopulationBaselines(features);
|
||||
|
||||
// 计算各维度Z-score
|
||||
double[] zScores = calculateZScores(features);
|
||||
|
||||
// 计算加权复合异常分数(0-100)
|
||||
double anomalyScore = computeCompositeAnomalyScore(zScores);
|
||||
|
||||
// 基于特征向量确定玩家画像
|
||||
PlayerProfile newProfile = classifyProfile(features, anomalyScore);
|
||||
|
||||
// 追踪画像转换并检测异常转换
|
||||
PlayerProfile previousProfile = playerProfiles.get(playerName);
|
||||
playerProfiles.put(playerName, newProfile);
|
||||
|
||||
boolean isSuspiciousTransition = false;
|
||||
List<String> flags = new ArrayList<>();
|
||||
|
||||
if (previousProfile != null && previousProfile != newProfile) {
|
||||
// 记录画像转换
|
||||
List<ProfileTransition> transitions = profileTransitions.computeIfAbsent(
|
||||
playerName, k -> new ArrayList<>());
|
||||
transitions.add(new ProfileTransition(previousProfile, newProfile, timestamp));
|
||||
|
||||
// 保留最近20次转换记录
|
||||
while (transitions.size() > 20) {
|
||||
transitions.remove(0);
|
||||
}
|
||||
|
||||
// 检测高风险转换:NORMAL/GRINDER → SUSPICIOUS/BOT_LIKE
|
||||
if (isRiskyTransition(previousProfile, newProfile)) {
|
||||
isSuspiciousTransition = true;
|
||||
anomalyScore = Math.min(100.0, anomalyScore * TRANSITION_RISK_MULTIPLIER);
|
||||
flags.add("RISKY_TRANSITION: " + previousProfile + " -> " + newProfile);
|
||||
}
|
||||
}
|
||||
|
||||
// 检查各维度是否超过异常阈值
|
||||
for (int i = 0; i < zScores.length; i++) {
|
||||
if (Math.abs(zScores[i]) > ANOMALY_Z_THRESHOLD) {
|
||||
flags.add("FEATURE_ANOMALY[" + FeatureDimension.FEATURE_NAMES[i]
|
||||
+ "]: z-score=" + String.format("%.2f", zScores[i]));
|
||||
}
|
||||
}
|
||||
|
||||
if (anomalyScore > 60.0 || isSuspiciousTransition || !flags.isEmpty()) {
|
||||
anomalyFlags.incrementAndGet();
|
||||
return ProfilingResult.flagged(playerName, newProfile, anomalyScore, flags);
|
||||
}
|
||||
|
||||
if (anomalyScore > 30.0) {
|
||||
return ProfilingResult.suspicious(playerName, newProfile, anomalyScore, flags);
|
||||
}
|
||||
|
||||
return ProfilingResult.clean(playerName, newProfile, anomalyScore);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从玩家事件滑动窗口提取5维特征向量。
|
||||
*
|
||||
* 特征维度:
|
||||
* [0] 移动熵 — 方向变化的香农熵(正常玩家方向多变,bot方向高度一致)
|
||||
* [1] 战斗参与率 — 每秒攻击次数(PVPer高,普通玩家低)
|
||||
* [2] 资源采集率 — 每秒方块破坏数(矿工/挂机脚本高)
|
||||
* [3] 社交交互率 — 每秒聊天消息数
|
||||
* [4] 探索率 — 每分钟新探索区块数
|
||||
*/
|
||||
private double[] extractFeatureVector(Deque<BehaviorEvent> window, Instant now) {
|
||||
double moveCount = 0, attackCount = 0, breakCount = 0, chatCount = 0, exploreCount = 0;
|
||||
List<Double> moveDeltas = new ArrayList<>();
|
||||
|
||||
Instant oldest = window.isEmpty() ? now : window.peekFirst().timestamp;
|
||||
double durationMinutes = Math.max(1.0 / 60.0,
|
||||
(now.toEpochMilli() - oldest.toEpochMilli()) / 60000.0);
|
||||
|
||||
for (BehaviorEvent e : window) {
|
||||
switch (e.eventType) {
|
||||
case "MOVE":
|
||||
moveCount++;
|
||||
moveDeltas.add(e.eventValue);
|
||||
break;
|
||||
case "ATTACK":
|
||||
attackCount++;
|
||||
break;
|
||||
case "BREAK_BLOCK":
|
||||
breakCount++;
|
||||
break;
|
||||
case "CHAT":
|
||||
chatCount++;
|
||||
break;
|
||||
case "EXPLORE":
|
||||
exploreCount++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// [0] 移动熵 — 方向变化的信息熵
|
||||
double movementEntropy = calculateDirectionEntropy(moveDeltas);
|
||||
|
||||
// [1] 战斗参与率 = 攻击次数 / 分钟
|
||||
double combatRate = attackCount / durationMinutes;
|
||||
|
||||
// [2] 资源采集率 = 方块破坏数 / 分钟
|
||||
double gatheringRate = breakCount / durationMinutes;
|
||||
|
||||
// [3] 社交交互率 = 聊天消息数 / 分钟
|
||||
double socialRate = chatCount / durationMinutes;
|
||||
|
||||
// [4] 探索率 = 新区块探索数 / 分钟
|
||||
double explorationRate = exploreCount / durationMinutes;
|
||||
|
||||
return new double[]{movementEntropy, combatRate, gatheringRate, socialRate, explorationRate};
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算移动方向变量的香农熵。
|
||||
* 将方向值离散化到12个桶(每30度一个桶),计算频率分布的熵。
|
||||
* 高熵 = 方向随机多变 = 人类玩家;低熵 = 方向单一 = bot/脚本。
|
||||
*/
|
||||
private double calculateDirectionEntropy(List<Double> deltas) {
|
||||
if (deltas.size() < 10) return 0.0;
|
||||
|
||||
int[] buckets = new int[12]; // 360度 / 30度 = 12个方向桶
|
||||
for (double d : deltas) {
|
||||
int idx = ((int) (Math.abs(d) % 360.0) / 30) % 12;
|
||||
buckets[idx]++;
|
||||
}
|
||||
|
||||
double entropy = 0.0;
|
||||
double n = deltas.size();
|
||||
for (int count : buckets) {
|
||||
if (count > 0) {
|
||||
double p = count / n;
|
||||
entropy -= p * (Math.log(p) / Math.log(2));
|
||||
}
|
||||
}
|
||||
return entropy;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将当前特征向量更新至全服基线DescriptiveStatistics。
|
||||
* DescriptiveStatistics 自动维护均值、方差、百分位数等统计量。
|
||||
*/
|
||||
private void updatePopulationBaselines(double[] features) {
|
||||
for (int i = 0; i < features.length; i++) {
|
||||
DescriptiveStatistics stats = populationBaselines.get(
|
||||
FeatureDimension.FEATURE_NAMES[i]);
|
||||
if (stats != null && !Double.isNaN(features[i]) && !Double.isInfinite(features[i])) {
|
||||
stats.addValue(features[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算各特征维度的Z-score(标准分数)。
|
||||
* Z = (x - mu_population) / sigma_population
|
||||
* 如果总体标准差为0(所有玩家值相同),Z-score设为0。
|
||||
*/
|
||||
private double[] calculateZScores(double[] features) {
|
||||
double[] zScores = new double[features.length];
|
||||
for (int i = 0; i < features.length; i++) {
|
||||
DescriptiveStatistics stats = populationBaselines.get(
|
||||
FeatureDimension.FEATURE_NAMES[i]);
|
||||
if (stats != null && stats.getN() >= MIN_SAMPLES_FOR_PROFILE) {
|
||||
double mu = stats.getMean();
|
||||
double sigma = stats.getStandardDeviation();
|
||||
zScores[i] = (sigma > 1e-10) ? (features[i] - mu) / sigma : 0.0;
|
||||
} else {
|
||||
zScores[i] = 0.0;
|
||||
}
|
||||
}
|
||||
return zScores;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算加权复合异常分数(0-100)。
|
||||
* 将各维度Z-score的绝对值加权求和,再通过sigmoid-like函数映射到0-100区间。
|
||||
*/
|
||||
private double computeCompositeAnomalyScore(double[] zScores) {
|
||||
double weightedSum = 0.0;
|
||||
for (int i = 0; i < zScores.length; i++) {
|
||||
weightedSum += Math.abs(zScores[i]) * FEATURE_WEIGHTS[i];
|
||||
}
|
||||
// 使用双曲正切映射到0-100:score = 100 * tanh(weightedSum / 2)
|
||||
// tanh(0) = 0, tanh(1.5) ≈ 0.9, tanh(3) ≈ 0.995
|
||||
return Math.min(100.0, 100.0 * Math.tanh(weightedSum / 2.0));
|
||||
}
|
||||
|
||||
/**
|
||||
* 基于特征向量和异常分数对玩家画像进行分类。
|
||||
*
|
||||
* 分类规则(启发式阈值):
|
||||
* - BOT_LIKE: 移动熵极低(< 0.5)且资源采集率极高(> 30/min)
|
||||
* - SUSPICIOUS: 异常分数 > 50
|
||||
* - PVPER: 战斗参与率 > 5/min
|
||||
* - GRINDER: 资源采集率 > 20/min 或 探索率 > 15/min
|
||||
* - EXPLORER: 探索率 > 10/min
|
||||
* - NORMAL: 以上均不满足
|
||||
*/
|
||||
private PlayerProfile classifyProfile(double[] features, double anomalyScore) {
|
||||
double movementEntropy = features[0];
|
||||
double combatRate = features[1];
|
||||
double gatheringRate = features[2];
|
||||
double socialRate = features[3];
|
||||
double explorationRate = features[4];
|
||||
|
||||
// Bot特征:移动高度规律 + 高采集频率
|
||||
if (movementEntropy < 0.5 && gatheringRate > 30.0) {
|
||||
return PlayerProfile.BOT_LIKE;
|
||||
}
|
||||
|
||||
// 显著异常
|
||||
if (anomalyScore > 50.0) {
|
||||
return PlayerProfile.SUSPICIOUS;
|
||||
}
|
||||
|
||||
// 高战斗率 = PVP玩家
|
||||
if (combatRate > 5.0) {
|
||||
return PlayerProfile.PVPER;
|
||||
}
|
||||
|
||||
// 高采集率 = 刷资源型
|
||||
if (gatheringRate > 20.0) {
|
||||
return PlayerProfile.GRINDER;
|
||||
}
|
||||
|
||||
// 高探索率 = 探险型
|
||||
if (explorationRate > 10.0) {
|
||||
return PlayerProfile.EXPLORER;
|
||||
}
|
||||
|
||||
return PlayerProfile.NORMAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断画像转换是否为高风险转换。
|
||||
* 从无害画像(NORMAL/GRINDER/EXPLORER)跳转到可疑画像(SUSPICIOUS/BOT_LIKE)视为危险信号。
|
||||
*/
|
||||
private boolean isRiskyTransition(PlayerProfile from, PlayerProfile to) {
|
||||
Set<PlayerProfile> benignSet = EnumSet.of(PlayerProfile.NORMAL,
|
||||
PlayerProfile.GRINDER, PlayerProfile.EXPLORER, PlayerProfile.PVPER);
|
||||
Set<PlayerProfile> maliciousSet = EnumSet.of(PlayerProfile.SUSPICIOUS,
|
||||
PlayerProfile.BOT_LIKE);
|
||||
|
||||
return benignSet.contains(from) && maliciousSet.contains(to);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理超过30分钟无活动的玩家数据,释放内存。
|
||||
*/
|
||||
public void cleanupInactiveProfiles() {
|
||||
Instant cutoff = Instant.now().minusMillis(INACTIVITY_CLEANUP_MS);
|
||||
|
||||
Iterator<Map.Entry<String, ConcurrentLinkedDeque<BehaviorEvent>>> it =
|
||||
playerEventWindows.entrySet().iterator();
|
||||
while (it.hasNext()) {
|
||||
Map.Entry<String, ConcurrentLinkedDeque<BehaviorEvent>> entry = it.next();
|
||||
ConcurrentLinkedDeque<BehaviorEvent> window = entry.getValue();
|
||||
if (window.isEmpty() || window.peekLast().timestamp.isBefore(cutoff)) {
|
||||
it.remove();
|
||||
playerProfiles.remove(entry.getKey());
|
||||
profileTransitions.remove(entry.getKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定玩家的当前画像。
|
||||
*/
|
||||
public PlayerProfile getPlayerProfile(String playerName) {
|
||||
return playerProfiles.getOrDefault(playerName, PlayerProfile.NORMAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定玩家的行为特征向量快照。
|
||||
*/
|
||||
public double[] getPlayerFeatureVector(String playerName) {
|
||||
ConcurrentLinkedDeque<BehaviorEvent> window = playerEventWindows.get(playerName);
|
||||
if (window == null || window.isEmpty()) {
|
||||
return new double[]{0, 0, 0, 0, 0};
|
||||
}
|
||||
return extractFeatureVector(window, Instant.now());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定玩家的画像转换历史。
|
||||
*/
|
||||
public List<ProfileTransition> getTransitionHistory(String playerName) {
|
||||
return profileTransitions.getOrDefault(playerName, Collections.emptyList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除指定玩家的所有数据。
|
||||
*/
|
||||
public void clearPlayer(String playerName) {
|
||||
playerEventWindows.remove(playerName);
|
||||
playerProfiles.remove(playerName);
|
||||
profileTransitions.remove(playerName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取引擎运行状态统计。
|
||||
*/
|
||||
public Map<String, Object> getStatus() {
|
||||
Map<String, Object> status = new LinkedHashMap<>();
|
||||
status.put("totalEvents", totalEventsProcessed.get());
|
||||
status.put("anomalyFlags", anomalyFlags.get());
|
||||
status.put("activePlayers", playerEventWindows.size());
|
||||
status.put("populationSampleSize",
|
||||
populationBaselines.values().iterator().next().getN());
|
||||
// 各画像玩家分布
|
||||
Map<PlayerProfile, Long> profileDistribution = new EnumMap<>(PlayerProfile.class);
|
||||
for (PlayerProfile p : playerProfiles.values()) {
|
||||
profileDistribution.merge(p, 1L, Long::sum);
|
||||
}
|
||||
status.put("profileDistribution", profileDistribution);
|
||||
return status;
|
||||
}
|
||||
|
||||
// ==================== 内部数据类 ====================
|
||||
|
||||
/**
|
||||
* 行为事件记录 — 包含时间戳、事件类型和关联数值。
|
||||
*/
|
||||
private static class BehaviorEvent {
|
||||
final Instant timestamp;
|
||||
final String eventType;
|
||||
final double eventValue;
|
||||
|
||||
BehaviorEvent(Instant timestamp, String eventType, double eventValue) {
|
||||
this.timestamp = timestamp;
|
||||
this.eventType = eventType;
|
||||
this.eventValue = eventValue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 特征维度常量定义。
|
||||
*/
|
||||
private static final class FeatureDimension {
|
||||
static final String[] FEATURE_NAMES = {
|
||||
"MOVEMENT_ENTROPY", "COMBAT_RATE", "GATHERING_RATE",
|
||||
"SOCIAL_RATE", "EXPLORATION_RATE"
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 画像转换记录 — 记录从哪个画像切换到哪个画像及时间。
|
||||
*/
|
||||
public static class ProfileTransition {
|
||||
public final PlayerProfile from;
|
||||
public final PlayerProfile to;
|
||||
public final Instant timestamp;
|
||||
|
||||
ProfileTransition(PlayerProfile from, PlayerProfile to, Instant timestamp) {
|
||||
this.from = from;
|
||||
this.to = to;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return from + " -> " + to + " @ " + timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 枚举与结果类 ====================
|
||||
|
||||
/**
|
||||
* 玩家行为画像枚举。
|
||||
*/
|
||||
public enum PlayerProfile {
|
||||
NORMAL, // 正常玩家 — 无异常行为模式
|
||||
GRINDER, // 刷资源型 — 高频率采集/挖掘
|
||||
EXPLORER, // 探险型 — 高频率新区块探索
|
||||
PVPER, // PVP型 — 高频率战斗参与
|
||||
SUSPICIOUS, // 可疑 — 多维度偏离基线
|
||||
BOT_LIKE // Bot特征 — 移动极度规律 + 自动化采集
|
||||
}
|
||||
|
||||
/**
|
||||
* 行为画像评估结果 — 不可变结果类。
|
||||
*/
|
||||
public static class ProfilingResult {
|
||||
private final boolean flagged;
|
||||
private final boolean suspicious;
|
||||
private final String playerName;
|
||||
private final PlayerProfile profile;
|
||||
private final double anomalyScore;
|
||||
private final List<String> flags;
|
||||
|
||||
private ProfilingResult(boolean flagged, boolean suspicious, String playerName,
|
||||
PlayerProfile profile, double anomalyScore, List<String> flags) {
|
||||
this.flagged = flagged;
|
||||
this.suspicious = suspicious;
|
||||
this.playerName = playerName;
|
||||
this.profile = profile;
|
||||
this.anomalyScore = anomalyScore;
|
||||
this.flags = flags;
|
||||
}
|
||||
|
||||
/** 正常 — 无任何异常信号 */
|
||||
public static ProfilingResult clean(String playerName, PlayerProfile profile,
|
||||
double anomalyScore) {
|
||||
return new ProfilingResult(false, false, playerName, profile,
|
||||
anomalyScore, List.of());
|
||||
}
|
||||
|
||||
/** 可疑 — 存在异常信号但未达到标记阈值 */
|
||||
public static ProfilingResult suspicious(String playerName, PlayerProfile profile,
|
||||
double anomalyScore, List<String> flags) {
|
||||
return new ProfilingResult(false, true, playerName, profile,
|
||||
anomalyScore, flags);
|
||||
}
|
||||
|
||||
/** 已标记 — 检测到显著异常行为 */
|
||||
public static ProfilingResult flagged(String playerName, PlayerProfile profile,
|
||||
double anomalyScore, List<String> flags) {
|
||||
return new ProfilingResult(true, true, playerName, profile,
|
||||
anomalyScore, flags);
|
||||
}
|
||||
|
||||
public boolean isFlagged() { return flagged; }
|
||||
public boolean isSuspicious() { return suspicious; }
|
||||
public boolean isClean() { return !flagged && !suspicious; }
|
||||
public String getPlayerName() { return playerName; }
|
||||
public PlayerProfile getProfile() { return profile; }
|
||||
public double getAnomalyScore() { return anomalyScore; }
|
||||
public List<String> getFlags() { return flags; }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user