1
This commit is contained in:
@@ -1,394 +0,0 @@
|
||||
package com.ruoyi.web.controller.common;
|
||||
import org.openqa.selenium.By;
|
||||
import org.openqa.selenium.WebDriver;
|
||||
import org.openqa.selenium.WebElement;
|
||||
import org.openqa.selenium.interactions.Actions;
|
||||
import org.openqa.selenium.support.ui.WebDriverWait;
|
||||
import org.openqa.selenium.support.ui.ExpectedConditions;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* 高级滑块验证码处理器
|
||||
* 支持多种滑块类型和重试机制
|
||||
*/
|
||||
public class AdvancedSliderCaptchaHandler {
|
||||
private final WebDriver driver;
|
||||
private final WebDriverWait wait;
|
||||
private final Actions actions;
|
||||
private final Random random;
|
||||
private static final int MAX_RETRY_ATTEMPTS = 3;
|
||||
private static final Duration WAIT_TIMEOUT = Duration.ofSeconds(15);
|
||||
|
||||
// 人类行为参数
|
||||
private static final int MIN_CLICK_HOLD_DURATION = 300;
|
||||
private static final int MAX_CLICK_HOLD_DURATION = 800;
|
||||
private static final int MIN_MOVE_DURATION = 1000;
|
||||
private static final int MAX_MOVE_DURATION = 2000;
|
||||
private static final double OVERSHOOT_FACTOR = 1.05; // 5%的过冲概率
|
||||
|
||||
public AdvancedSliderCaptchaHandler(WebDriver driver) {
|
||||
this.driver = driver;
|
||||
this.wait = new WebDriverWait(driver, WAIT_TIMEOUT.toSeconds());
|
||||
this.actions = new Actions(driver);
|
||||
this.random = new Random();
|
||||
}
|
||||
public boolean handleAnyCaptcha() {
|
||||
for (int attempt = 1; attempt <= MAX_RETRY_ATTEMPTS; attempt++) {
|
||||
System.out.println("第 " + attempt + " 次尝试处理验证码");
|
||||
|
||||
CaptchaType captchaType = detectCaptchaType();
|
||||
if (captchaType == CaptchaType.NONE) {
|
||||
System.out.println("未检测到验证码");
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean success = processCaptchaByType(captchaType);
|
||||
if (success) {
|
||||
System.out.println("验证码处理成功");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (attempt < MAX_RETRY_ATTEMPTS) {
|
||||
waitBeforeRetry();
|
||||
}
|
||||
}
|
||||
|
||||
System.err.println("验证码处理失败,已达到最大重试次数");
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测验证码类型
|
||||
*/
|
||||
private CaptchaType detectCaptchaType() {
|
||||
try {
|
||||
if (isElementPresent(By.id("nc_1_n1z"))) {
|
||||
return CaptchaType.ALIBABA_SLIDER;
|
||||
}
|
||||
|
||||
// 检测通用滑块
|
||||
if (isElementPresent(By.className("btn_slide")) ||
|
||||
isElementPresent(By.className("slider-btn"))) {
|
||||
return CaptchaType.GENERIC_SLIDER;
|
||||
}
|
||||
|
||||
// 检测其他常见滑块选择器
|
||||
String[] commonSelectors = {
|
||||
".captcha-slider-btn",
|
||||
".slide-verify-slider-mask-item",
|
||||
".slider_bg .slider_btn"
|
||||
};
|
||||
|
||||
for (String selector : commonSelectors) {
|
||||
if (isElementPresent(By.cssSelector(selector))) {
|
||||
return CaptchaType.GENERIC_SLIDER;
|
||||
}
|
||||
}
|
||||
|
||||
return CaptchaType.NONE;
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("检测验证码类型时发生错误: " + e.getMessage());
|
||||
return CaptchaType.NONE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据验证码类型进行处理
|
||||
*/
|
||||
private boolean processCaptchaByType(CaptchaType type) {
|
||||
switch (type) {
|
||||
case ALIBABA_SLIDER:
|
||||
return handleAlibabaSlider();
|
||||
case GENERIC_SLIDER:
|
||||
return handleGenericSlider();
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理阿里云滑块验证码
|
||||
*/
|
||||
private boolean handleAlibabaSlider() {
|
||||
try {
|
||||
WebElement sliderButton = wait.until(
|
||||
ExpectedConditions.elementToBeClickable(By.id("nc_1_n1z"))
|
||||
);
|
||||
WebElement sliderTrack = driver.findElement(By.id("nc_1_n1t"));
|
||||
|
||||
int moveDistance = calculateMoveDistance(sliderTrack, sliderButton);
|
||||
return performSmartSlide(sliderButton, moveDistance);
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("处理阿里云滑块失败: " + e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理通用滑块验证码
|
||||
*/
|
||||
private boolean handleGenericSlider() {
|
||||
try {
|
||||
List<WebElement> sliderButtons = driver.findElements(By.className("btn_slide"));
|
||||
if (sliderButtons.isEmpty()) {
|
||||
sliderButtons = driver.findElements(By.className("slider-btn"));
|
||||
}
|
||||
|
||||
if (sliderButtons.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
WebElement sliderButton = sliderButtons.get(0);
|
||||
WebElement sliderTrack = findSliderTrack(sliderButton);
|
||||
|
||||
if (sliderTrack == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int moveDistance = calculateMoveDistance(sliderTrack, sliderButton);
|
||||
System.out.println("处理通用滑块失败: ");
|
||||
return performSmartSlide(sliderButton, moveDistance);
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("处理通用滑块失败: " + e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 智能滑动算法
|
||||
*/
|
||||
// private boolean performSmartSlide(WebElement sliderButton, int totalDistance) {
|
||||
// try {
|
||||
// actions.clickAndHold(sliderButton).perform();
|
||||
// Thread.sleep(50 + random.nextInt(200));
|
||||
//
|
||||
// int moved = 0;
|
||||
// int segments = 20 + random.nextInt(5);
|
||||
//
|
||||
// for (int i = 0; i < segments && moved < totalDistance; i++) {
|
||||
// double progress = (double) i / segments;
|
||||
// int stepSize = (int) (totalDistance * getBezierValue(progress) - moved);
|
||||
//
|
||||
// if (stepSize > 0 && moved + stepSize <= totalDistance) {
|
||||
// int yOffset = (int) (Math.sin(progress * Math.PI * 2) * 2);
|
||||
// actions.moveByOffset(stepSize, yOffset).perform();
|
||||
// moved += stepSize;
|
||||
//
|
||||
// }
|
||||
// }
|
||||
// if (moved < totalDistance) {
|
||||
// actions.moveByOffset(totalDistance - moved, 0).perform();
|
||||
// }
|
||||
//
|
||||
// Thread.sleep(50 + random.nextInt(200));
|
||||
// actions.release().perform();
|
||||
//
|
||||
// return waitForVerificationResult();
|
||||
//
|
||||
// } catch (Exception e) {
|
||||
// System.err.println("智能滑动失败: " + e.getMessage());
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* 贝塞尔曲线值计算
|
||||
*/
|
||||
private double getBezierValue(double t) {
|
||||
return t * t * (3.0 - 2.0 * t);
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算移动距离
|
||||
*/
|
||||
private int calculateMoveDistance(WebElement track, WebElement button) {
|
||||
int trackWidth = track.getSize().getWidth() + 100;
|
||||
int buttonWidth = button.getSize().getWidth();
|
||||
return Math.max(trackWidth - buttonWidth - 5, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找滑块轨道
|
||||
*/
|
||||
private WebElement findSliderTrack(WebElement button) {
|
||||
try {
|
||||
return button.findElement(By.xpath("./parent::*"));
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 等待验证结果
|
||||
*/
|
||||
private boolean waitForVerificationResult() {
|
||||
try {
|
||||
Thread.sleep(2000);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查元素是否存在
|
||||
*/
|
||||
private boolean isElementPresent(By locator) {
|
||||
try {
|
||||
driver.findElement(locator);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重试前等待
|
||||
*/
|
||||
private void waitBeforeRetry() {
|
||||
try {
|
||||
Thread.sleep(2000 + random.nextInt(3000));
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证码类型枚举
|
||||
*/
|
||||
private enum CaptchaType {
|
||||
NONE,
|
||||
ALIBABA_SLIDER,
|
||||
GENERIC_SLIDER
|
||||
}
|
||||
|
||||
private boolean performSmartSlide(WebElement sliderButton, int totalDistance) {
|
||||
try {
|
||||
// 模拟人类点击前的短暂思考时间
|
||||
randomSleep(MIN_CLICK_HOLD_DURATION, MAX_CLICK_HOLD_DURATION);
|
||||
|
||||
// 点击并按住滑块
|
||||
actions.clickAndHold(sliderButton).perform();
|
||||
|
||||
// 生成更自然的移动轨迹
|
||||
List<MoveStep> moveSteps = generateHumanLikeTrajectory(totalDistance);
|
||||
|
||||
// 执行移动步骤
|
||||
long startTime = System.currentTimeMillis();
|
||||
long currentTime = startTime;
|
||||
long endTime = startTime + randomLong(MIN_MOVE_DURATION, MAX_MOVE_DURATION);
|
||||
|
||||
for (MoveStep step : moveSteps) {
|
||||
// 根据时间进度执行步骤,使整体移动速度更自然
|
||||
while (currentTime < endTime * step.timeProgress) {
|
||||
actions.moveByOffset(step.xOffset, step.yOffset).perform();
|
||||
currentTime = System.currentTimeMillis();
|
||||
|
||||
// 微小的随机停顿
|
||||
if (random.nextDouble() < 0.1) {
|
||||
Thread.sleep(random.nextInt(50));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 随机过冲然后回退,更像人类操作
|
||||
if (random.nextDouble() < OVERSHOOT_FACTOR) {
|
||||
int overshoot = random.nextInt(5) + 5;
|
||||
actions.moveByOffset(overshoot, 0).perform();
|
||||
Thread.sleep(random.nextInt(100) + 50);
|
||||
actions.moveByOffset(-overshoot, 0).perform();
|
||||
}
|
||||
|
||||
// 释放前的随机延迟
|
||||
randomSleep(100, 300);
|
||||
actions.release().perform();
|
||||
|
||||
// 模拟验证过程中的等待
|
||||
randomSleep(500, 1500);
|
||||
|
||||
return waitForVerificationResult();
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("智能滑动失败: " + e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成更接近人类行为的滑动轨迹
|
||||
*/
|
||||
private List<MoveStep> generateHumanLikeTrajectory(int totalDistance) {
|
||||
java.util.List<MoveStep> steps = new java.util.ArrayList<>();
|
||||
|
||||
// 总步数 - 随机但合理的范围
|
||||
int totalSteps = random.nextInt(15) + 25;
|
||||
|
||||
// 计算每个时间点的进度(0-1之间)
|
||||
double[] timeProgress = new double[totalSteps];
|
||||
for (int i = 0; i < totalSteps; i++) {
|
||||
timeProgress[i] = (double) i / totalSteps;
|
||||
}
|
||||
|
||||
// 生成更自然的S型速度曲线(开始和结束较慢,中间较快)
|
||||
double[] distanceProgress = new double[totalSteps];
|
||||
for (int i = 0; i < totalSteps; i++) {
|
||||
double t = timeProgress[i];
|
||||
// 使用改进的S型曲线函数
|
||||
distanceProgress[i] = t * t * t * (t * (t * 6 - 15) + 10);
|
||||
}
|
||||
|
||||
// 计算每一步的偏移量
|
||||
for (int i = 0; i < totalSteps; i++) {
|
||||
double currentDistance = i == 0 ?
|
||||
distanceProgress[i] * totalDistance :
|
||||
(distanceProgress[i] - distanceProgress[i-1]) * totalDistance;
|
||||
|
||||
// 添加小幅度的垂直偏移,模拟人类手部颤抖
|
||||
int yOffset = random.nextInt(3) - 1; // -1, 0, 1
|
||||
|
||||
steps.add(new MoveStep(
|
||||
(int) Math.round(currentDistance),
|
||||
yOffset,
|
||||
timeProgress[i]
|
||||
));
|
||||
}
|
||||
|
||||
return steps;
|
||||
}
|
||||
|
||||
/**
|
||||
* 随机睡眠一段时间
|
||||
*/
|
||||
private void randomSleep(int min, int max) throws InterruptedException {
|
||||
Thread.sleep(random.nextInt(max - min + 1) + min);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成随机长整型数
|
||||
*/
|
||||
private long randomLong(long min, long max) {
|
||||
return min + (long) (random.nextDouble() * (max - min));
|
||||
}
|
||||
|
||||
/**
|
||||
* 移动步骤类 - 封装每一步的移动信息
|
||||
*/
|
||||
private static class MoveStep {
|
||||
final int xOffset;
|
||||
final int yOffset;
|
||||
final double timeProgress;
|
||||
|
||||
MoveStep(int xOffset, int yOffset, double timeProgress) {
|
||||
this.xOffset = xOffset;
|
||||
this.yOffset = yOffset;
|
||||
this.timeProgress = timeProgress;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -198,7 +198,6 @@ public class ClientAccountController extends BaseController {
|
||||
Map<String, Object> claims = Jwts.parser().setSigningKey(jwtRsaKeyService.getPublicKey()).parseClaimsJws(token).getBody();
|
||||
String username = (String) claims.getOrDefault("sub", claims.get("subject"));
|
||||
String tokenClientId = (String) claims.get("clientId");
|
||||
|
||||
if (username == null || tokenClientId == null || !tokenClientId.equals(clientId)) {
|
||||
throw new RuntimeException("会话不匹配");
|
||||
}
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
package com.ruoyi.web.controller.monitor;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import com.ruoyi.common.annotation.Anonymous;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
@@ -12,8 +8,8 @@ import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.page.TableDataInfo;
|
||||
import com.ruoyi.system.domain.*;
|
||||
import com.ruoyi.web.service.IClientMonitorService;
|
||||
import com.ruoyi.common.utils.poi.ExcelUtil;
|
||||
import com.ruoyi.web.service.IClientMonitorService;
|
||||
|
||||
/**
|
||||
* 客户端监控控制器
|
||||
@@ -111,18 +107,7 @@ public class ClientMonitorController extends BaseController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 客户端心跳API
|
||||
*/
|
||||
@PostMapping("/api/heartbeat")
|
||||
public AjaxResult clientHeartbeat(@RequestBody Map<String, Object> heartbeatData) {
|
||||
try {
|
||||
clientMonitorService.recordHeartbeat(heartbeatData);
|
||||
return AjaxResult.success();
|
||||
} catch (Exception e) {
|
||||
return AjaxResult.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 客户端错误上报API
|
||||
@@ -167,67 +152,7 @@ public class ClientMonitorController extends BaseController {
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 获取客户端日志内容
|
||||
*/
|
||||
@GetMapping("/logs/{clientId}")
|
||||
public AjaxResult getClientLogs(@PathVariable String clientId) {
|
||||
try {
|
||||
return AjaxResult.success(clientMonitorService.getClientLogs(clientId));
|
||||
} catch (Exception e) {
|
||||
return AjaxResult.error("获取客户端日志失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载客户端日志文件
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('monitor:client:list')")
|
||||
@GetMapping("/logs/{clientId}/download")
|
||||
public void downloadClientLogs(@PathVariable String clientId, HttpServletResponse response) {
|
||||
try {
|
||||
clientMonitorService.downloadClientLogs(clientId, response);
|
||||
} catch (Exception e) {
|
||||
logger.error("下载客户端日志失败: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 客户端日志上传接口
|
||||
*/
|
||||
@PostMapping("/logs/upload")
|
||||
public AjaxResult uploadClientLogs(@RequestBody Map<String, Object> logData) {
|
||||
try {
|
||||
clientMonitorService.saveClientLogs(logData);
|
||||
return AjaxResult.success();
|
||||
} catch (Exception e) {
|
||||
return AjaxResult.error("保存客户端日志失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 客户端日志批量上传接口
|
||||
*/
|
||||
@PostMapping("/logs/batchUpload")
|
||||
public AjaxResult batchUploadClientLogs(@RequestBody Map<String, Object> batchLogData) {
|
||||
try {
|
||||
String clientId = (String) batchLogData.get("clientId");
|
||||
List<String> logEntries = (List<String>) batchLogData.get("logEntries");
|
||||
|
||||
if (clientId == null || logEntries == null || logEntries.isEmpty()) {
|
||||
return AjaxResult.error("无效的批量日志数据");
|
||||
}
|
||||
|
||||
clientMonitorService.saveBatchClientLogs(clientId, logEntries);
|
||||
return AjaxResult.success();
|
||||
} catch (Exception e) {
|
||||
return AjaxResult.error("批量保存客户端日志失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
|
||||
@@ -2,158 +2,127 @@ package com.ruoyi.web.service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.ruoyi.system.domain.ClientInfo;
|
||||
import com.ruoyi.system.domain.ClientErrorReport;
|
||||
import com.ruoyi.system.domain.ClientDataReport;
|
||||
import com.ruoyi.system.domain.ClientEventLog;
|
||||
import com.ruoyi.system.domain.*;
|
||||
|
||||
/**
|
||||
* 客户端监控服务接口
|
||||
*
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
public interface IClientMonitorService {
|
||||
|
||||
/**
|
||||
* 查询客户端信息列表
|
||||
* 查询客户端设备列表
|
||||
*/
|
||||
public List<ClientInfo> selectClientInfoList(ClientInfo clientInfo);
|
||||
List<ClientDevice> selectClientDeviceList(String username);
|
||||
|
||||
/**
|
||||
* 查询客户端错误报告列表
|
||||
*/
|
||||
public List<Map<String, Object>> selectClientErrorList(ClientErrorReport clientErrorReport);
|
||||
List<Map<String, Object>> selectClientErrorList(ClientErrorReport clientErrorReport);
|
||||
|
||||
/**
|
||||
* 查询客户端事件日志列表
|
||||
*/
|
||||
public List<ClientEventLog> selectClientEventLogList(ClientEventLog clientEventLog);
|
||||
List<ClientEventLog> selectClientEventLogList(ClientEventLog clientEventLog);
|
||||
|
||||
/**
|
||||
* 查询客户端数据采集报告列表
|
||||
*/
|
||||
public List<ClientDataReport> selectClientDataReportList(ClientDataReport clientDataReport);
|
||||
List<ClientDataReport> selectClientDataReportList(ClientDataReport clientDataReport);
|
||||
|
||||
/**
|
||||
* 查询在线客户端数量
|
||||
*/
|
||||
public int selectOnlineClientCount();
|
||||
|
||||
int selectOnlineClientCount();
|
||||
|
||||
/**
|
||||
* 检查客户端是否在线
|
||||
*/
|
||||
boolean isClientOnline(String clientId);
|
||||
|
||||
/**
|
||||
* 获取在线客户端ID列表
|
||||
*/
|
||||
List<String> getOnlineClientIds();
|
||||
|
||||
/**
|
||||
* 查询客户端总数
|
||||
*/
|
||||
public int selectTotalClientCount();
|
||||
|
||||
/**
|
||||
* 新增客户端信息
|
||||
*/
|
||||
public int insertClientInfo(ClientInfo clientInfo);
|
||||
|
||||
/**
|
||||
* 新增客户端错误报告
|
||||
*/
|
||||
public int insertClientError(ClientErrorReport clientErrorReport);
|
||||
|
||||
/**
|
||||
* 新增客户端事件日志
|
||||
*/
|
||||
public int insertClientEventLog(ClientEventLog clientEventLog);
|
||||
|
||||
/**
|
||||
* 新增客户端数据采集报告
|
||||
*/
|
||||
public int insertDataReport(ClientDataReport clientDataReport);
|
||||
int selectTotalClientCount();
|
||||
|
||||
/**
|
||||
* 获取客户端统计数据
|
||||
*/
|
||||
public Map<String, Object> getClientStatistics();
|
||||
Map<String, Object> getClientStatistics();
|
||||
|
||||
/**
|
||||
* 获取客户端活跃趋势
|
||||
*/
|
||||
public Map<String, Object> getClientActiveTrend();
|
||||
Map<String, Object> getClientActiveTrend();
|
||||
|
||||
/**
|
||||
* 获取数据采集类型分布
|
||||
*/
|
||||
public Map<String, Object> getDataTypeDistribution();
|
||||
Map<String, Object> getDataTypeDistribution();
|
||||
|
||||
/**
|
||||
* 获取近7天在线客户端趋势
|
||||
* 获取在线客户端趋势
|
||||
*/
|
||||
public Map<String, Object> getOnlineClientTrend();
|
||||
Map<String, Object> getOnlineClientTrend();
|
||||
|
||||
/**
|
||||
* 客户端认证
|
||||
*/
|
||||
public Map<String, Object> authenticateClient(String authKey, Map<String, Object> clientInfo);
|
||||
Map<String, Object> authenticateClient(String authKey, Map<String, Object> clientInfo);
|
||||
|
||||
/**
|
||||
* 记录客户端心跳
|
||||
* 记录错误报告
|
||||
*/
|
||||
public void recordHeartbeat(Map<String, Object> heartbeatData);
|
||||
void recordErrorReport(Map<String, Object> errorData);
|
||||
|
||||
/**
|
||||
* 记录客户端错误
|
||||
* 记录数据报告
|
||||
*/
|
||||
public void recordErrorReport(Map<String, Object> errorData);
|
||||
/**
|
||||
* 记录客户端数据采集报告
|
||||
*/
|
||||
public void recordDataReport(Map<String, Object> dataReport);
|
||||
void recordDataReport(Map<String, Object> dataReport);
|
||||
|
||||
/**
|
||||
* 获取客户端详细信息
|
||||
*/
|
||||
public Map<String, Object> getClientDetail(String clientId);
|
||||
Map<String, Object> getClientDetail(String clientId);
|
||||
|
||||
/**
|
||||
* 获取客户端版本分布
|
||||
* 获取版本分布
|
||||
*/
|
||||
public List<Map<String, Object>> getVersionDistribution();
|
||||
|
||||
List<Map<String, Object>> getVersionDistribution();
|
||||
|
||||
/**
|
||||
* 记录1688风控监控数据
|
||||
* 插入客户端错误报告
|
||||
*/
|
||||
public void recordAlibaba1688MonitorData(Map<String, Object> monitorData);
|
||||
|
||||
int insertClientError(ClientErrorReport clientErrorReport);
|
||||
|
||||
/**
|
||||
* 查询1688风控监控数据列表
|
||||
* 插入客户端设备
|
||||
*/
|
||||
public List<Map<String, Object>> selectAlibaba1688MonitorList(Map<String, Object> params);
|
||||
|
||||
int insertClientDevice(ClientDevice clientDevice);
|
||||
|
||||
/**
|
||||
* 获取1688风控数据统计
|
||||
* 插入客户端事件日志
|
||||
*/
|
||||
public Map<String, Object> getAlibaba1688Statistics();
|
||||
|
||||
int insertClientEventLog(ClientEventLog clientEventLog);
|
||||
|
||||
/**
|
||||
* 获取客户端日志内容
|
||||
* 插入数据报告
|
||||
*/
|
||||
public Map<String, Object> getClientLogs(String clientId);
|
||||
|
||||
int insertDataReport(ClientDataReport clientDataReport);
|
||||
|
||||
/**
|
||||
* 下载客户端日志文件
|
||||
* 查询客户端信息列表
|
||||
*/
|
||||
public void downloadClientLogs(String clientId, javax.servlet.http.HttpServletResponse response);
|
||||
|
||||
/**
|
||||
* 保存客户端日志
|
||||
*/
|
||||
public void saveClientLogs(Map<String, Object> logData);
|
||||
|
||||
/**
|
||||
* 批量保存客户端日志
|
||||
*
|
||||
* @param clientId 客户端ID
|
||||
* @param logEntries 日志条目列表
|
||||
*/
|
||||
public void saveBatchClientLogs(String clientId, List<String> logEntries);
|
||||
|
||||
List<ClientInfo> selectClientInfoList(ClientInfo clientInfo);
|
||||
|
||||
/**
|
||||
* 清理过期数据
|
||||
*/
|
||||
public void cleanExpiredData();
|
||||
void cleanExpiredData();
|
||||
}
|
||||
@@ -4,7 +4,6 @@ import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import com.ruoyi.system.domain.*;
|
||||
import com.ruoyi.common.core.redis.RedisCache;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
@@ -21,10 +20,11 @@ import org.slf4j.LoggerFactory;
|
||||
import com.ruoyi.web.service.IClientAccountService;
|
||||
import com.ruoyi.web.service.IClientMonitorService;
|
||||
import com.ruoyi.system.mapper.ClientMonitorMapper;
|
||||
import com.ruoyi.system.mapper.ClientDeviceMapper;
|
||||
|
||||
/**
|
||||
* 客户端监控服务实现
|
||||
*
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Service
|
||||
@@ -34,12 +34,13 @@ public class ClientMonitorServiceImpl implements IClientMonitorService {
|
||||
|
||||
@Autowired
|
||||
private ClientMonitorMapper clientMonitorMapper;
|
||||
|
||||
|
||||
@Autowired
|
||||
private ClientDeviceMapper clientDeviceMapper;
|
||||
|
||||
@Autowired
|
||||
private IClientAccountService clientAccountService;
|
||||
|
||||
@Autowired
|
||||
private RedisCache redisCache;
|
||||
|
||||
// 线程池用于异步处理日志记录
|
||||
private final ExecutorService logExecutor = new ThreadPoolExecutor(
|
||||
@@ -52,21 +53,16 @@ public class ClientMonitorServiceImpl implements IClientMonitorService {
|
||||
private final AtomicLong apiCallCounter = new AtomicLong(0);
|
||||
|
||||
/**
|
||||
* 查询客户端信息列表 - 增强Redis在线状态
|
||||
* 查询设备列表 - 基于ClientDevice表
|
||||
*/
|
||||
@Override
|
||||
public List<ClientInfo> selectClientInfoList(ClientInfo clientInfo) {
|
||||
logApiCallAsync("selectClientInfoList", null);
|
||||
List<ClientInfo> clientList = clientMonitorMapper.selectClientInfoList(clientInfo);
|
||||
|
||||
// 使用Redis检查并更新在线状态
|
||||
if (clientList != null) {
|
||||
for (ClientInfo client : clientList) {
|
||||
boolean isOnline = isClientOnline(client.getClientId());
|
||||
client.setOnline(isOnline ? "1" : "0");
|
||||
}
|
||||
public List<ClientDevice> selectClientDeviceList(String username) {
|
||||
logApiCallAsync("selectClientDeviceList", null);
|
||||
if (username != null && !username.isEmpty()) {
|
||||
return clientDeviceMapper.selectByUsername(username);
|
||||
} else {
|
||||
return clientMonitorMapper.selectOnlineDevices();
|
||||
}
|
||||
return clientList;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -97,36 +93,25 @@ public class ClientMonitorServiceImpl implements IClientMonitorService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询在线客户端数量 - 基于Redis TTL
|
||||
* 查询在线客户端数量 - 基于数据库
|
||||
*/
|
||||
@Override
|
||||
public int selectOnlineClientCount() {
|
||||
logApiCallAsync("selectOnlineClientCount", null);
|
||||
try {
|
||||
// 通过Redis检查所有心跳key
|
||||
Collection<String> heartbeatKeys = redisCache.keys("client:heartbeat:*");
|
||||
int onlineCount = heartbeatKeys != null ? heartbeatKeys.size() : 0;
|
||||
System.out.println("Redis在线客户端数量: " + onlineCount + ", keys: " + heartbeatKeys);
|
||||
return onlineCount;
|
||||
} catch (Exception e) {
|
||||
System.err.println("Redis查询在线客户端失败: " + e.getMessage());
|
||||
// Redis查询失败时使用数据库备用方案
|
||||
return clientMonitorMapper.selectOnlineClientCount();
|
||||
}
|
||||
return clientMonitorMapper.selectOnlineClientCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查客户端是否在线 - 基于Redis TTL
|
||||
* 检查客户端是否在线 - 基于数据库
|
||||
*/
|
||||
@Override
|
||||
public boolean isClientOnline(String clientId) {
|
||||
if (clientId == null || clientId.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
String heartbeatKey = "client:heartbeat:" + clientId;
|
||||
boolean isOnline = redisCache.hasKey(heartbeatKey);
|
||||
System.out.println("检查客户端在线状态: " + clientId + " -> " + isOnline);
|
||||
return isOnline;
|
||||
ClientInfo clientInfo = clientMonitorMapper.selectClientInfoByClientId(clientId);
|
||||
return clientInfo != null && "1".equals(clientInfo.getOnline());
|
||||
} catch (Exception e) {
|
||||
System.err.println("检查客户端在线状态失败: " + e.getMessage());
|
||||
return false;
|
||||
@@ -134,17 +119,17 @@ public class ClientMonitorServiceImpl implements IClientMonitorService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取在线客户端列表 - 基于Redis TTL
|
||||
* 获取在线客户端列表 - 基于数据库
|
||||
*/
|
||||
@Override
|
||||
public List<String> getOnlineClientIds() {
|
||||
try {
|
||||
Collection<String> heartbeatKeys = redisCache.keys("client:heartbeat:*");
|
||||
ClientInfo queryParam = new ClientInfo();
|
||||
queryParam.setOnline("1");
|
||||
List<ClientInfo> onlineClients = clientMonitorMapper.selectClientInfoList(queryParam);
|
||||
List<String> onlineClientIds = new ArrayList<>();
|
||||
if (heartbeatKeys != null) {
|
||||
for (String key : heartbeatKeys) {
|
||||
String clientId = key.replace("client:heartbeat:", "");
|
||||
onlineClientIds.add(clientId);
|
||||
}
|
||||
for (ClientInfo client : onlineClients) {
|
||||
onlineClientIds.add(client.getClientId());
|
||||
}
|
||||
return onlineClientIds;
|
||||
} catch (Exception e) {
|
||||
@@ -443,40 +428,6 @@ public class ClientMonitorServiceImpl implements IClientMonitorService {
|
||||
private void recordClientAuth(String username, String authKey, String clientId, Map<String, Object> clientInfo, String accessToken) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录客户端心跳 - 使用Redis TTL机制
|
||||
*/
|
||||
@Override
|
||||
public void recordHeartbeat(Map<String, Object> heartbeatData) {
|
||||
try {
|
||||
String clientId = (String) heartbeatData.get("clientId");
|
||||
if (clientId == null || clientId.isEmpty()) {
|
||||
System.err.println("心跳记录失败: clientId为空");
|
||||
return;
|
||||
}
|
||||
|
||||
// 使用Redis TTL记录心跳,600秒TTL(10分钟)
|
||||
String heartbeatKey = "client:heartbeat:" + clientId;
|
||||
Map<String, Object> clientData = new HashMap<>();
|
||||
clientData.put("clientId", clientId);
|
||||
clientData.put("timestamp", System.currentTimeMillis());
|
||||
clientData.put("cpuUsage", parseDouble(heartbeatData.get("cpuUsage"), 0.0));
|
||||
clientData.put("memoryUsage", parseDouble(heartbeatData.get("memoryUsage"), 0.0));
|
||||
clientData.put("diskUsage", parseDouble(heartbeatData.get("diskUsage"), 0.0));
|
||||
clientData.put("networkStatus", heartbeatData.getOrDefault("networkStatus", "normal"));
|
||||
|
||||
// 设置Redis key,TTL为600秒
|
||||
redisCache.setCacheObject(heartbeatKey, clientData, 600, TimeUnit.SECONDS);
|
||||
System.out.println("心跳记录成功: " + heartbeatKey + ", TTL: 600秒");
|
||||
|
||||
// 只在数据库中更新基本信息(减少频率)
|
||||
clientMonitorMapper.updateClientOnlineStatus(clientId, "1");
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("记录心跳失败: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 安全解析Double值
|
||||
*/
|
||||
@@ -662,27 +613,6 @@ public class ClientMonitorServiceImpl implements IClientMonitorService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理过期客户端数据和日志
|
||||
*/
|
||||
public void cleanExpiredData() {
|
||||
try {
|
||||
// 删除7天前的心跳记录(将离线状态的客户端设为离线)
|
||||
clientMonitorMapper.updateExpiredClientsOffline();
|
||||
|
||||
// 删除30天前的错误报告
|
||||
clientMonitorMapper.deleteExpiredErrorReports();
|
||||
|
||||
|
||||
|
||||
// 删除7天前的事件日志
|
||||
clientMonitorMapper.deleteExpiredEventLogs();
|
||||
|
||||
logger.info("✅ 清理过期数据完成");
|
||||
} catch (Exception e) {
|
||||
logger.error("清理过期数据失败: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 优雅关闭线程池
|
||||
@@ -726,304 +656,11 @@ public class ClientMonitorServiceImpl implements IClientMonitorService {
|
||||
return distribution;
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录1688爬取风控监控数据
|
||||
*/
|
||||
@Override
|
||||
public void recordAlibaba1688MonitorData(Map<String, Object> monitorData) {
|
||||
// 记录API调用日志
|
||||
logApiCall("recordAlibaba1688MonitorData", "记录1688爬取风控数据");
|
||||
|
||||
try {
|
||||
// 获取客户端ID
|
||||
String clientId = (String) monitorData.get("clientId");
|
||||
|
||||
|
||||
|
||||
// 如果没有客户端ID,则使用系统用户名生成
|
||||
if (clientId == null || clientId.isEmpty()) {
|
||||
clientId = "user";
|
||||
monitorData.put("clientId", clientId);
|
||||
}
|
||||
|
||||
// 尝试查找已有客户端信息,如果没有则不进行严格验证
|
||||
ClientInfo authInfo = null;
|
||||
try {
|
||||
authInfo = clientMonitorMapper.selectClientInfoByClientId(clientId);
|
||||
} catch (Exception e) {
|
||||
// 查询失败时忽略,允许匿名上报
|
||||
}
|
||||
|
||||
// 如果客户端不存在,创建一个简单的客户端记录
|
||||
if (authInfo == null) {
|
||||
ClientInfo newClientInfo = new ClientInfo();
|
||||
newClientInfo.setClientId(clientId);
|
||||
newClientInfo.setUsername((String) monitorData.getOrDefault("username", "user"));
|
||||
newClientInfo.setIpAddress((String) monitorData.get("ipAddress"));
|
||||
newClientInfo.setHostname((String) monitorData.get("hostname"));
|
||||
newClientInfo.setStatus("0");
|
||||
newClientInfo.setAuthTime(DateUtils.getNowDate());
|
||||
newClientInfo.setLastActiveTime(DateUtils.getNowDate());
|
||||
newClientInfo.setOnline("1");
|
||||
|
||||
// 添加系统信息
|
||||
newClientInfo.setOsName((String) monitorData.get("osName"));
|
||||
newClientInfo.setOsVersion((String) monitorData.get("osVersion"));
|
||||
newClientInfo.setJavaVersion((String) monitorData.get("javaVersion"));
|
||||
newClientInfo.setAppVersion((String) monitorData.get("appVersion"));
|
||||
|
||||
try {
|
||||
clientMonitorMapper.insertClientInfo(newClientInfo);
|
||||
} catch (Exception e) {
|
||||
// 插入失败时忽略,可能是因为主键冲突(客户端已存在)
|
||||
logger.warn("插入客户端信息失败,可能客户端已存在: {}", e.getMessage());
|
||||
}
|
||||
} else {
|
||||
// 更新现有客户端的活跃状态
|
||||
try {
|
||||
authInfo.setLastActiveTime(DateUtils.getNowDate());
|
||||
authInfo.setOnline("1");
|
||||
if (monitorData.get("ipAddress") != null) {
|
||||
authInfo.setIpAddress((String) monitorData.get("ipAddress"));
|
||||
}
|
||||
// 更新系统信息
|
||||
if (monitorData.get("osName") != null) {
|
||||
authInfo.setOsName((String) monitorData.get("osName"));
|
||||
}
|
||||
if (monitorData.get("osVersion") != null) {
|
||||
authInfo.setOsVersion((String) monitorData.get("osVersion"));
|
||||
}
|
||||
if (monitorData.get("javaVersion") != null) {
|
||||
authInfo.setJavaVersion((String) monitorData.get("javaVersion"));
|
||||
}
|
||||
if (monitorData.get("appVersion") != null) {
|
||||
authInfo.setAppVersion((String) monitorData.get("appVersion"));
|
||||
}
|
||||
clientMonitorMapper.updateClientInfo(authInfo);
|
||||
} catch (Exception e) {
|
||||
logger.warn("更新客户端信息失败: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// 设置当前时间
|
||||
monitorData.put("createTime", DateUtils.getNowDate());
|
||||
|
||||
// 插入监控数据
|
||||
clientMonitorMapper.insertAlibaba1688MonitorData(monitorData);
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("处理1688爬取风控监控数据失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询1688爬取风控监控数据列表
|
||||
*/
|
||||
@Override
|
||||
public List<Map<String, Object>> selectAlibaba1688MonitorList(Map<String, Object> params) {
|
||||
// 记录API调用日志
|
||||
logApiCall("selectAlibaba1688MonitorList", null);
|
||||
return clientMonitorMapper.selectAlibaba1688MonitorList(params);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取客户端日志内容
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Object> getClientLogs(String clientId) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
|
||||
try {
|
||||
// 构建日志文件路径
|
||||
String logDir = System.getProperty("user.dir") + "/logs/clients/";
|
||||
String logFilePath = logDir + clientId + ".txt";
|
||||
|
||||
File logFile = new File(logFilePath);
|
||||
if (logFile.exists()) {
|
||||
// 读取日志文件内容
|
||||
StringBuilder content = new StringBuilder();
|
||||
try (BufferedReader reader = new BufferedReader(new FileReader(logFile, StandardCharsets.UTF_8))) {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
content.append(line).append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
result.put("content", content.toString());
|
||||
result.put("lastUpdate", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(logFile.lastModified())));
|
||||
} else {
|
||||
result.put("content", "暂无日志文件");
|
||||
result.put("lastUpdate", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
|
||||
}
|
||||
|
||||
// 记录API调用日志
|
||||
logApiCall("getClientLogs", "客户端ID: " + clientId);
|
||||
|
||||
} catch (Exception e) {
|
||||
result.put("content", "读取日志文件失败: " + e.getMessage());
|
||||
result.put("lastUpdate", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载客户端日志文件
|
||||
*/
|
||||
@Override
|
||||
public void downloadClientLogs(String clientId, HttpServletResponse response) {
|
||||
try {
|
||||
// 构建日志文件路径
|
||||
String logDir = System.getProperty("user.dir") + "/logs/clients/";
|
||||
String logFilePath = logDir + clientId + ".txt";
|
||||
|
||||
File logFile = new File(logFilePath);
|
||||
if (!logFile.exists()) {
|
||||
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
|
||||
// 设置响应头
|
||||
response.setContentType("text/plain;charset=UTF-8");
|
||||
response.setHeader("Content-Disposition", "attachment; filename=\"client_" + clientId + "_logs.txt\"");
|
||||
response.setContentLength((int) logFile.length());
|
||||
|
||||
// 输出文件内容
|
||||
try (FileInputStream fis = new FileInputStream(logFile);
|
||||
OutputStream os = response.getOutputStream()) {
|
||||
|
||||
byte[] buffer = new byte[4096];
|
||||
int bytesRead;
|
||||
while ((bytesRead = fis.read(buffer)) != -1) {
|
||||
os.write(buffer, 0, bytesRead);
|
||||
}
|
||||
os.flush();
|
||||
}
|
||||
|
||||
// 记录API调用日志
|
||||
logApiCall("downloadClientLogs", "客户端ID: " + clientId);
|
||||
|
||||
} catch (Exception e) {
|
||||
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存客户端日志
|
||||
*/
|
||||
@Override
|
||||
public void saveClientLogs(Map<String, Object> logData) {
|
||||
try {
|
||||
String clientId = (String) logData.get("clientId");
|
||||
String logContent = (String) logData.get("logContent");
|
||||
|
||||
if (clientId == null || logContent == null) {
|
||||
throw new RuntimeException("缺少必要的日志参数");
|
||||
}
|
||||
|
||||
// 创建日志目录
|
||||
String logDir = System.getProperty("user.dir") + "/logs/clients/";
|
||||
File dir = new File(logDir);
|
||||
if (!dir.exists()) {
|
||||
dir.mkdirs();
|
||||
}
|
||||
|
||||
// 写入日志文件
|
||||
String logFilePath = logDir + clientId + ".txt";
|
||||
try (FileWriter writer = new FileWriter(logFilePath, StandardCharsets.UTF_8, true)) {
|
||||
writer.write(logContent);
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
// 记录API调用日志
|
||||
logApiCall("saveClientLogs", "客户端ID: " + clientId + ", 日志长度: " + logContent.length());
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("保存客户端日志失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量保存客户端日志
|
||||
*/
|
||||
@Override
|
||||
public void saveBatchClientLogs(String clientId, List<String> logEntries) {
|
||||
try {
|
||||
if (clientId == null || logEntries == null || logEntries.isEmpty()) {
|
||||
throw new RuntimeException("缺少必要的日志参数");
|
||||
}
|
||||
|
||||
// 创建日志目录
|
||||
String logDir = System.getProperty("user.dir") + "/logs/clients/";
|
||||
File dir = new File(logDir);
|
||||
if (!dir.exists()) {
|
||||
dir.mkdirs();
|
||||
}
|
||||
|
||||
// 写入日志文件
|
||||
String logFilePath = logDir + clientId + ".txt";
|
||||
try (FileWriter writer = new FileWriter(logFilePath, StandardCharsets.UTF_8, true)) {
|
||||
for (String logEntry : logEntries) {
|
||||
if (logEntry != null && !logEntry.isEmpty()) {
|
||||
writer.write(logEntry);
|
||||
writer.write(System.lineSeparator());
|
||||
}
|
||||
}
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
// 记录API调用日志
|
||||
logApiCall("saveBatchClientLogs", "客户端ID: " + clientId + ", 日志条目数: " + logEntries.size());
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("批量保存客户端日志失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取1688爬取风控数据统计
|
||||
*/
|
||||
@Override
|
||||
public Map<String, Object> getAlibaba1688Statistics() {
|
||||
// 记录API调用日志
|
||||
logApiCall("getAlibaba1688Statistics", null);
|
||||
|
||||
// 获取统计数据
|
||||
Map<String, Object> stats = clientMonitorMapper.selectAlibaba1688Statistics();
|
||||
if (stats == null) {
|
||||
stats = new HashMap<>();
|
||||
}
|
||||
|
||||
// 如果没有数据,设置默认值
|
||||
if (!stats.containsKey("totalEvents")) {
|
||||
stats.put("totalEvents", 0);
|
||||
}
|
||||
if (!stats.containsKey("mobileBlockedCount")) {
|
||||
stats.put("mobileBlockedCount", 0);
|
||||
}
|
||||
if (!stats.containsKey("desktopBlockedCount")) {
|
||||
stats.put("desktopBlockedCount", 0);
|
||||
}
|
||||
if (!stats.containsKey("avgMobileDuration")) {
|
||||
stats.put("avgMobileDuration", 0);
|
||||
}
|
||||
if (!stats.containsKey("avgDesktopDuration")) {
|
||||
stats.put("avgDesktopDuration", 0);
|
||||
}
|
||||
if (!stats.containsKey("uniqueIpCount")) {
|
||||
stats.put("uniqueIpCount", 0);
|
||||
}
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从客户端信息中获取IP地址
|
||||
*/
|
||||
private String getClientIp(Map<String, Object> clientInfo) {
|
||||
return clientInfo != null && clientInfo.containsKey("ipAddress")
|
||||
? (String) clientInfo.get("ipAddress")
|
||||
: "";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 生成会话令牌
|
||||
*/
|
||||
@@ -1043,8 +680,8 @@ public class ClientMonitorServiceImpl implements IClientMonitorService {
|
||||
* 新增客户端信息
|
||||
*/
|
||||
@Override
|
||||
public int insertClientInfo(ClientInfo clientInfo) {
|
||||
return clientMonitorMapper.insertClientInfo(clientInfo);
|
||||
public int insertClientDevice(ClientDevice clientDevice) {
|
||||
return clientDeviceMapper.insert(clientDevice);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1062,4 +699,37 @@ public class ClientMonitorServiceImpl implements IClientMonitorService {
|
||||
public int insertDataReport(ClientDataReport clientDataReport) {
|
||||
return clientMonitorMapper.insertDataReport(clientDataReport);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询客户端信息列表
|
||||
*/
|
||||
@Override
|
||||
public List<ClientInfo> selectClientInfoList(ClientInfo clientInfo) {
|
||||
return clientMonitorMapper.selectClientInfoList(clientInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理过期数据
|
||||
*/
|
||||
@Override
|
||||
public void cleanExpiredData() {
|
||||
try {
|
||||
// 清理过期的客户端(设置为离线状态)
|
||||
clientMonitorMapper.updateExpiredClientsOffline();
|
||||
|
||||
// 清理过期的设备(设置为离线状态)
|
||||
clientMonitorMapper.updateExpiredDevicesOffline();
|
||||
|
||||
// 删除过期的错误报告
|
||||
clientMonitorMapper.deleteExpiredErrorReports();
|
||||
|
||||
// 删除过期的事件日志
|
||||
clientMonitorMapper.deleteExpiredEventLogs();
|
||||
|
||||
logger.info("过期数据清理完成");
|
||||
} catch (Exception e) {
|
||||
logger.error("清理过期数据失败: {}", e.getMessage(), e);
|
||||
throw new RuntimeException("清理过期数据失败", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user