1、新增功能探针联动处置、心跳在线检测
2、syslog-consumer模块拆分 syslog-consumer-rule模块实现日志数据消费、解析、泛化入库。
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
package com.haobang.config;
|
||||
|
||||
import com.typesafe.config.Config;
|
||||
import com.typesafe.config.ConfigFactory;
|
||||
import java.io.File;
|
||||
import com.typesafe.config.ConfigValueFactory;
|
||||
|
||||
import java.util.Map;
|
||||
public class AppConfig {
|
||||
|
||||
private static final Config config;
|
||||
|
||||
static {
|
||||
// 加载配置文件
|
||||
File configFile = new File("application.properties");
|
||||
Config loadedConfig;
|
||||
|
||||
if (configFile.exists()) {
|
||||
loadedConfig = ConfigFactory.parseFile(configFile);
|
||||
} else {
|
||||
loadedConfig = ConfigFactory.load("application.properties");
|
||||
}
|
||||
|
||||
// 解析环境变量占位符
|
||||
config = resolveEnvironmentVariables(loadedConfig);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 解析环境变量占位符
|
||||
*/
|
||||
private static Config resolveEnvironmentVariables(Config originalConfig) {
|
||||
Config resolvedConfig = originalConfig;
|
||||
|
||||
// 遍历所有配置项,查找需要解析的占位符
|
||||
for (Map.Entry<String, com.typesafe.config.ConfigValue> entry : originalConfig.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
String value = originalConfig.getString(key);
|
||||
|
||||
if (value.contains("${")) {
|
||||
String resolvedValue = resolvePlaceholder(value);
|
||||
resolvedConfig = resolvedConfig.withValue(
|
||||
key,
|
||||
ConfigValueFactory.fromAnyRef(resolvedValue)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return resolvedConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析单个占位符值
|
||||
* 格式: ${ENV_VAR:default_value}
|
||||
*/
|
||||
private static String resolvePlaceholder(String value) {
|
||||
if (!value.startsWith("${") || !value.endsWith("}")) {
|
||||
return value;
|
||||
}
|
||||
|
||||
String placeholder = value.substring(2, value.length() - 1);
|
||||
String[] parts = placeholder.split(":");
|
||||
|
||||
if (parts.length == 0) {
|
||||
return value; // 无效格式,返回原值
|
||||
}
|
||||
|
||||
String envVarName = parts[0].trim();
|
||||
String defaultValue = parts.length > 1 ? parts[1].trim() : "";
|
||||
|
||||
// 1. 从系统环境变量获取
|
||||
String envValue = System.getenv(envVarName);
|
||||
if (envValue != null && !envValue.trim().isEmpty()) {
|
||||
return envValue.trim();
|
||||
}
|
||||
|
||||
// 2. 从系统属性获取 (java -D参数)
|
||||
String sysValue = System.getProperty(envVarName);
|
||||
if (sysValue != null && !sysValue.trim().isEmpty()) {
|
||||
return sysValue.trim();
|
||||
}
|
||||
|
||||
// 3. 返回默认值
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
// Syslog 配置
|
||||
public static int getSyslogTcpPort() {
|
||||
return config.getInt("syslog.tcp.port");
|
||||
}
|
||||
|
||||
public static int getSyslogUdpPort() {
|
||||
return config.getInt("syslog.udp.port");
|
||||
}
|
||||
|
||||
public static int getSyslogMaxFrameLength() {
|
||||
return config.getInt("syslog.max.frame.length");
|
||||
}
|
||||
|
||||
public static int getSyslogBufferSize() {
|
||||
return config.getInt("syslog.buffer.size");
|
||||
}
|
||||
|
||||
// app service 配置
|
||||
public static String getAppServieDeviceId() {
|
||||
return config.getString("app.service.device_id");
|
||||
}
|
||||
public static String getAppServieDeviceName() { return config.getString("app.service.device_name");
|
||||
}
|
||||
public static String getAppServieVendor() {
|
||||
return config.getString("app.service.vendor");
|
||||
}
|
||||
public static String getAppServieProductName() {
|
||||
return config.getString("app.service.product_name");
|
||||
}
|
||||
public static String getAppServieDataType() {
|
||||
return config.getString("app.service.data_type");
|
||||
}
|
||||
// kafka 配置
|
||||
public static String getKafkaProducerBootstrap() {
|
||||
return config.getString("spring.kafka.producer.bootstrap-servers");
|
||||
}
|
||||
public static String getKafkaProducerTopic() {
|
||||
return config.getString("spring.kafka.producer.topic");
|
||||
}
|
||||
|
||||
public static int getDeviceCollectId() {
|
||||
return config.getInt("app.service.device_collect_id");
|
||||
}
|
||||
|
||||
public static String getSM4Key() {
|
||||
return config.getString("syslog.sm4.generateKey");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.haobang.config;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.PropertySource;
|
||||
|
||||
|
||||
@ConfigurationProperties(prefix = "syslog")
|
||||
public class SyslogConfig {
|
||||
|
||||
// 读取字符串属性
|
||||
@Value("${syslog.tcp.port}")
|
||||
private int tcpPort;
|
||||
// 读取字符串属性
|
||||
@Value("${syslog.udp.port}")
|
||||
private int udpPort;
|
||||
// 读取字符串属性
|
||||
@Value("${syslog.max.frame.length}")
|
||||
private int maxFrameLength;
|
||||
// 读取字符串属性
|
||||
@Value("${syslog.buffer.size}")
|
||||
private int bufferSize;
|
||||
|
||||
// Getter 和 Setter 方法
|
||||
public int getTcpPort() {
|
||||
return tcpPort;
|
||||
}
|
||||
|
||||
public void setTcpPort(int tcpPort) {
|
||||
this.tcpPort = tcpPort;
|
||||
}
|
||||
|
||||
public int getUdpPort() {
|
||||
return udpPort;
|
||||
}
|
||||
|
||||
public void setUdpPort(int udpPort) {
|
||||
this.udpPort = udpPort;
|
||||
}
|
||||
|
||||
public int getMaxFrameLength() {
|
||||
return maxFrameLength;
|
||||
}
|
||||
|
||||
public void setMaxFrameLength(int maxFrameLength) {
|
||||
this.maxFrameLength = maxFrameLength;
|
||||
}
|
||||
|
||||
public int getBufferSize() {
|
||||
return bufferSize;
|
||||
}
|
||||
|
||||
public void setBufferSize(int bufferSize) {
|
||||
this.bufferSize = bufferSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("SyslogConfig[tcpPort=%d, udpPort=%d, maxFrameLength=%d, bufferSize=%d]",
|
||||
tcpPort, udpPort, maxFrameLength, bufferSize);
|
||||
}
|
||||
}
|
||||
+191
@@ -0,0 +1,191 @@
|
||||
package com.haobang.controller;
|
||||
|
||||
import com.haobang.interlocking.InterlockingService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 探针联动控制器
|
||||
* 提供手动触发封禁的REST API
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/interlocking")
|
||||
public class InterlockingController {
|
||||
|
||||
@Autowired
|
||||
private InterlockingService interlockingService;
|
||||
|
||||
/**
|
||||
* 手动封禁单个IP
|
||||
* POST /interlocking/block
|
||||
*
|
||||
* @param request 封禁请求参数
|
||||
* @return 执行结果
|
||||
*/
|
||||
@PostMapping("/block")
|
||||
public ResponseEntity<Map<String, Object>> blockIp(@RequestBody Map<String, Object> request) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
|
||||
try {
|
||||
String ip = (String) request.get("ip");
|
||||
String cmdType = (String) request.getOrDefault("cmdType", "add_blacklist");
|
||||
Integer age = request.containsKey("age") ? ((Number) request.get("age")).intValue() : -1;
|
||||
String reason = (String) request.getOrDefault("reason", "manual_block");
|
||||
|
||||
if (ip == null || ip.isEmpty()) {
|
||||
result.put("code", 400);
|
||||
result.put("message", "IP地址不能为空");
|
||||
return ResponseEntity.badRequest().body(result);
|
||||
}
|
||||
|
||||
Map<String, Object> response = interlockingService.manualBlock(ip, cmdType, age, reason);
|
||||
result.put("code", 200);
|
||||
result.put("message", "success");
|
||||
result.put("data", response);
|
||||
return ResponseEntity.ok(result);
|
||||
|
||||
} catch (Exception e) {
|
||||
result.put("code", 500);
|
||||
result.put("message", "封禁失败: " + e.getMessage());
|
||||
return ResponseEntity.status(500).body(result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量封禁IP
|
||||
* POST /interlocking/block/batch
|
||||
*
|
||||
* @param request 批量封禁请求参数
|
||||
* @return 执行结果
|
||||
*/
|
||||
@PostMapping("/block/batch")
|
||||
public ResponseEntity<Map<String, Object>> batchBlockIp(@RequestBody Map<String, Object> request) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
|
||||
try {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> ips = (List<String>) request.get("ips");
|
||||
String cmdType = (String) request.getOrDefault("cmdType", "add_blacklist");
|
||||
Integer age = request.containsKey("age") ? ((Number) request.get("age")).intValue() : -1;
|
||||
String reason = (String) request.getOrDefault("reason", "manual_batch_block");
|
||||
|
||||
if (ips == null || ips.isEmpty()) {
|
||||
result.put("code", 400);
|
||||
result.put("message", "IP地址列表不能为空");
|
||||
return ResponseEntity.badRequest().body(result);
|
||||
}
|
||||
|
||||
Map<String, Object> response = interlockingService.batchManualBlock(ips, cmdType, age, reason);
|
||||
result.put("code", 200);
|
||||
result.put("message", "success");
|
||||
result.put("data", response);
|
||||
result.put("total", ips.size());
|
||||
return ResponseEntity.ok(result);
|
||||
|
||||
} catch (Exception e) {
|
||||
result.put("code", 500);
|
||||
result.put("message", "批量封禁失败: " + e.getMessage());
|
||||
return ResponseEntity.status(500).body(result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从黑名单删除IP
|
||||
* DELETE /interlocking/blacklist/{ip}
|
||||
*/
|
||||
@DeleteMapping("/blacklist/{ip}")
|
||||
public ResponseEntity<Map<String, Object>> removeFromBlacklist(@PathVariable String ip) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
|
||||
try {
|
||||
Map<String, Object> response = interlockingService.manualBlock(ip, "del_blacklist", -1, "");
|
||||
result.put("code", 200);
|
||||
result.put("message", "success");
|
||||
result.put("data", response);
|
||||
return ResponseEntity.ok(result);
|
||||
|
||||
} catch (Exception e) {
|
||||
result.put("code", 500);
|
||||
result.put("message", "删除黑名单失败: " + e.getMessage());
|
||||
return ResponseEntity.status(500).body(result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量从黑名单删除IP
|
||||
* DELETE /interlocking/blacklist
|
||||
*/
|
||||
@DeleteMapping("/blacklist")
|
||||
public ResponseEntity<Map<String, Object>> batchRemoveFromBlacklist(@RequestBody Map<String, Object> request) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
|
||||
try {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<String> ips = (List<String>) request.get("ips");
|
||||
|
||||
if (ips == null || ips.isEmpty()) {
|
||||
result.put("code", 400);
|
||||
result.put("message", "IP地址列表不能为空");
|
||||
return ResponseEntity.badRequest().body(result);
|
||||
}
|
||||
|
||||
Map<String, Object> response = interlockingService.batchManualBlock(ips, "del_blacklist", -1, "");
|
||||
result.put("code", 200);
|
||||
result.put("message", "success");
|
||||
result.put("data", response);
|
||||
result.put("total", ips.size());
|
||||
return ResponseEntity.ok(result);
|
||||
|
||||
} catch (Exception e) {
|
||||
result.put("code", 500);
|
||||
result.put("message", "批量删除黑名单失败: " + e.getMessage());
|
||||
return ResponseEntity.status(500).body(result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取联动服务状态
|
||||
* GET /interlocking/status
|
||||
*/
|
||||
@GetMapping("/status")
|
||||
public ResponseEntity<Map<String, Object>> getStatus() {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
|
||||
try {
|
||||
result.put("code", 200);
|
||||
result.put("message", "success");
|
||||
|
||||
Map<String, Object> status = new HashMap<>();
|
||||
status.put("enabled", interlockingService.isInterlockingEnabled());
|
||||
status.put("probeId", interlockingService.getProbeId());
|
||||
result.put("data", status);
|
||||
|
||||
return ResponseEntity.ok(result);
|
||||
|
||||
} catch (Exception e) {
|
||||
result.put("code", 500);
|
||||
result.put("message", "获取状态失败: " + e.getMessage());
|
||||
return ResponseEntity.status(500).body(result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 健康检查
|
||||
* GET /interlocking/health
|
||||
*/
|
||||
@GetMapping("/health")
|
||||
public ResponseEntity<Map<String, Object>> health() {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("code", 200);
|
||||
result.put("message", "OK");
|
||||
result.put("service", "interlocking-client");
|
||||
result.put("probeId", interlockingService.getProbeId());
|
||||
result.put("enabled", interlockingService.isInterlockingEnabled());
|
||||
return ResponseEntity.ok(result);
|
||||
}
|
||||
}
|
||||
+414
@@ -0,0 +1,414 @@
|
||||
package com.haobang.firewall;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.net.ssl.*;
|
||||
import java.io.*;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.net.ProtocolException;
|
||||
|
||||
/**
|
||||
* 防火墙API客户端
|
||||
* 支持安恒信息-明御安全网关的黑名单/白名单操作
|
||||
*/
|
||||
@Component
|
||||
public class FirewallApiClient {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(FirewallApiClient.class);
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
// 黑名单API配置
|
||||
@Value("${blacklist.api.url:https://103.43.84.11/api/v3/Objects/Blacklist}")
|
||||
private String blacklistApiUrl;
|
||||
|
||||
@Value("${blacklist.api.username:apt-admin103}")
|
||||
private String blacklistUsername;
|
||||
|
||||
@Value("${blacklist.api.password:C9W2xYgfc%SN1}")
|
||||
private String blacklistPassword;
|
||||
|
||||
// 白名单API配置
|
||||
@Value("${whitelist.api.url:https://103.43.84.11/api/v3/Policies/GlobalWhitelist}")
|
||||
private String whitelistApiUrl;
|
||||
|
||||
@Value("${firewall.enabled:true}")
|
||||
private boolean firewallEnabled;
|
||||
|
||||
/**
|
||||
* 添加IP到黑名单
|
||||
* @param ip IP地址
|
||||
* @param age 生命周期(秒),-1表示永久
|
||||
* @param reason 原因/备注
|
||||
* @return 操作结果
|
||||
*/
|
||||
public FirewallResponse addToBlacklist(String ip, int age, String reason) {
|
||||
if (!firewallEnabled) {
|
||||
logger.info("[测试模式] 添加黑名单: ip={}, age={}, reason={}", ip, age, reason);
|
||||
return new FirewallResponse(true, "1", "测试模式-模拟成功");
|
||||
}
|
||||
|
||||
Map<String, Object> payload = new HashMap<>();
|
||||
payload.put("blist", ip);
|
||||
payload.put("age", String.valueOf(age));
|
||||
payload.put("enable", "1");
|
||||
if (reason != null && !reason.isEmpty()) {
|
||||
payload.put("reason", reason);
|
||||
}
|
||||
|
||||
return doPost(blacklistApiUrl, blacklistUsername, blacklistPassword, payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量添加IP到黑名单
|
||||
* @param ips IP列表,每个元素包含 ip, age, reason
|
||||
* @return 操作结果
|
||||
*/
|
||||
public FirewallResponse batchAddToBlacklist(List<Map<String, String>> ips) {
|
||||
if (!firewallEnabled) {
|
||||
logger.info("[测试模式] 批量添加黑名单: {}", ips);
|
||||
return new FirewallResponse(true, "1", "测试模式-模拟成功");
|
||||
}
|
||||
|
||||
List<Map<String, String>> entries = new ArrayList<>();
|
||||
for (Map<String, String> ipInfo : ips) {
|
||||
Map<String, String> entry = new HashMap<>();
|
||||
entry.put("blist", ipInfo.get("ip"));
|
||||
entry.put("age", ipInfo.getOrDefault("age", "-1"));
|
||||
entry.put("enable", "1");
|
||||
if (ipInfo.containsKey("reason")) {
|
||||
entry.put("reason", ipInfo.get("reason"));
|
||||
}
|
||||
entries.add(entry);
|
||||
}
|
||||
|
||||
Map<String, Object> payload = new HashMap<>();
|
||||
payload.put("blist_entry", entries);
|
||||
|
||||
return doPost(blacklistApiUrl, blacklistUsername, blacklistPassword, payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从黑名单删除IP
|
||||
* @param ip IP地址
|
||||
* @return 操作结果
|
||||
*/
|
||||
public FirewallResponse removeFromBlacklist(String ip) {
|
||||
if (!firewallEnabled) {
|
||||
logger.info("[测试模式] 删除黑名单: ip={}", ip);
|
||||
return new FirewallResponse(true, "1", "测试模式-模拟成功");
|
||||
}
|
||||
|
||||
String deleteUrl = blacklistApiUrl + "/blist/" + ip;
|
||||
return doDelete(deleteUrl, blacklistUsername, blacklistPassword);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量从黑名单删除IP
|
||||
* @param ips IP地址列表
|
||||
* @return 操作结果
|
||||
*/
|
||||
public FirewallResponse batchRemoveFromBlacklist(List<String> ips) {
|
||||
if (!firewallEnabled) {
|
||||
logger.info("[测试模式] 批量删除黑名单: {}", ips);
|
||||
return new FirewallResponse(true, "1", "测试模式-模拟成功");
|
||||
}
|
||||
|
||||
List<Map<String, String>> entries = ips.stream()
|
||||
.map(ip -> {
|
||||
Map<String, String> entry = new HashMap<>();
|
||||
entry.put("blist", ip);
|
||||
return entry;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Map<String, Object> payload = new HashMap<>();
|
||||
payload.put("blist_entry", entries);
|
||||
|
||||
return doDelete(blacklistApiUrl, blacklistUsername, blacklistPassword, payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加IP到白名单
|
||||
* @param ip IP地址
|
||||
* @param name 名称
|
||||
* @param desc 描述
|
||||
* @return 操作结果
|
||||
*/
|
||||
public FirewallResponse addToWhitelist(String ip, String name, String desc) {
|
||||
if (!firewallEnabled) {
|
||||
logger.info("[测试模式] 添加白名单: ip={}, name={}", ip, name);
|
||||
return new FirewallResponse(true, "1", "测试模式-模拟成功");
|
||||
}
|
||||
|
||||
List<Map<String, String>> addresses = new ArrayList<>();
|
||||
Map<String, String> address = new HashMap<>();
|
||||
address.put("address", ip);
|
||||
addresses.add(address);
|
||||
|
||||
Map<String, Object> payload = new HashMap<>();
|
||||
payload.put("enable", "1");
|
||||
payload.put("name", name);
|
||||
payload.put("desc", desc != null ? desc : "");
|
||||
payload.put("addr", addresses);
|
||||
|
||||
return doPost(whitelistApiUrl, blacklistUsername, blacklistPassword, payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除白名单
|
||||
* @param name 白名单名称
|
||||
* @return 操作结果
|
||||
*/
|
||||
public FirewallResponse removeFromWhitelist(String name) {
|
||||
if (!firewallEnabled) {
|
||||
logger.info("[测试模式] 删除白名单: name={}", name);
|
||||
return new FirewallResponse(true, "1", "测试模式-模拟成功");
|
||||
}
|
||||
|
||||
String deleteUrl = whitelistApiUrl + "/name/" + name;
|
||||
return doDelete(deleteUrl, blacklistUsername, blacklistPassword);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除白名单
|
||||
* @param names 白名单名称列表
|
||||
* @return 操作结果
|
||||
*/
|
||||
public FirewallResponse batchRemoveFromWhitelist(List<String> names) {
|
||||
if (!firewallEnabled) {
|
||||
logger.info("[测试模式] 批量删除白名单: {}", names);
|
||||
return new FirewallResponse(true, "1", "测试模式-模拟成功");
|
||||
}
|
||||
|
||||
List<Map<String, String>> nameList = names.stream()
|
||||
.map(name -> {
|
||||
Map<String, String> entry = new HashMap<>();
|
||||
entry.put("name", name);
|
||||
return entry;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Map<String, Object> payload = new HashMap<>();
|
||||
payload.put("name_list", nameList);
|
||||
|
||||
String batchDeleteUrl = whitelistApiUrl + "Batch";
|
||||
return doDelete(batchDeleteUrl, blacklistUsername, blacklistPassword, payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行POST请求
|
||||
*/
|
||||
private FirewallResponse doPost(String urlStr, String username, String password, Map<String, Object> payload) {
|
||||
HttpURLConnection conn = null;
|
||||
try {
|
||||
URL url = new URL(urlStr);
|
||||
conn = (HttpURLConnection) url.openConnection();
|
||||
setupConnection(conn, username, password, "POST");
|
||||
|
||||
// 发送请求体
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
String jsonBody = mapper.writeValueAsString(payload);
|
||||
|
||||
try (OutputStream os = conn.getOutputStream()) {
|
||||
os.write(jsonBody.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
// 获取响应
|
||||
int responseCode = conn.getResponseCode();
|
||||
String response = readResponse(conn);
|
||||
|
||||
logger.info("POST请求响应: url={}, code={}, response={}", urlStr, responseCode, response);
|
||||
|
||||
// 解析响应
|
||||
JsonNode jsonNode = mapper.readTree(response);
|
||||
int code = jsonNode.has("code") ? jsonNode.get("code").asInt() : responseCode;
|
||||
String msg = jsonNode.has("msg") && !jsonNode.get("msg").isNull() ? jsonNode.get("msg").asText() : "";
|
||||
|
||||
return new FirewallResponse(code == 1, String.valueOf(code), msg, response);
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("POST请求失败: url={}, error={}", urlStr, e.getMessage(), e);
|
||||
return new FirewallResponse(false, "-1", "请求失败: " + e.getMessage());
|
||||
} finally {
|
||||
if (conn != null) {
|
||||
conn.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行DELETE请求(无请求体)
|
||||
*/
|
||||
private FirewallResponse doDelete(String urlStr, String username, String password) {
|
||||
HttpURLConnection conn = null;
|
||||
try {
|
||||
URL url = new URL(urlStr);
|
||||
conn = (HttpURLConnection) url.openConnection();
|
||||
setupConnection(conn, username, password, "DELETE");
|
||||
|
||||
int responseCode = conn.getResponseCode();
|
||||
String response = readResponse(conn);
|
||||
|
||||
logger.info("DELETE请求响应: url={}, code={}, response={}", urlStr, responseCode, response);
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
JsonNode jsonNode = mapper.readTree(response);
|
||||
int code = jsonNode.has("code") ? jsonNode.get("code").asInt() : responseCode;
|
||||
String msg = jsonNode.has("msg") && !jsonNode.get("msg").isNull() ? jsonNode.get("msg").asText() : "";
|
||||
|
||||
return new FirewallResponse(code == 1, String.valueOf(code), msg, response);
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("DELETE请求失败: url={}, error={}", urlStr, e.getMessage(), e);
|
||||
return new FirewallResponse(false, "-1", "请求失败: " + e.getMessage());
|
||||
} finally {
|
||||
if (conn != null) {
|
||||
conn.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行DELETE请求(带请求体)
|
||||
*/
|
||||
private FirewallResponse doDelete(String urlStr, String username, String password, Map<String, Object> payload) {
|
||||
HttpURLConnection conn = null;
|
||||
try {
|
||||
URL url = new URL(urlStr);
|
||||
conn = (HttpURLConnection) url.openConnection();
|
||||
setupConnection(conn, username, password, "DELETE");
|
||||
|
||||
// 发送请求体
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
String jsonBody = mapper.writeValueAsString(payload);
|
||||
|
||||
try (OutputStream os = conn.getOutputStream()) {
|
||||
os.write(jsonBody.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
int responseCode = conn.getResponseCode();
|
||||
String response = readResponse(conn);
|
||||
|
||||
logger.info("DELETE请求响应: url={}, code={}, response={}", urlStr, responseCode, response);
|
||||
|
||||
JsonNode jsonNode = mapper.readTree(response);
|
||||
int code = jsonNode.has("code") ? jsonNode.get("code").asInt() : responseCode;
|
||||
String msg = jsonNode.has("msg") && !jsonNode.get("msg").isNull() ? jsonNode.get("msg").asText() : "";
|
||||
|
||||
return new FirewallResponse(code == 1, String.valueOf(code), msg, response);
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("DELETE请求失败: url={}, error={}", urlStr, e.getMessage(), e);
|
||||
return new FirewallResponse(false, "-1", "请求失败: " + e.getMessage());
|
||||
} finally {
|
||||
if (conn != null) {
|
||||
conn.disconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置HTTP连接
|
||||
*/
|
||||
private void setupConnection(HttpURLConnection conn, String username, String password, String method)
|
||||
throws NoSuchAlgorithmException, KeyManagementException, ProtocolException{
|
||||
conn.setRequestMethod(method);
|
||||
conn.setConnectTimeout(10000);
|
||||
conn.setReadTimeout(30000);
|
||||
conn.setDoInput(true);
|
||||
conn.setDoOutput(true);
|
||||
|
||||
// 设置请求头
|
||||
conn.setRequestProperty("Content-Type", "application/json");
|
||||
conn.setRequestProperty("Accept", "application/json");
|
||||
|
||||
// HTTP Basic Auth
|
||||
String auth = username + ":" + password;
|
||||
String encodedAuth = Base64.getEncoder().encodeToString(auth.getBytes(StandardCharsets.UTF_8));
|
||||
conn.setRequestProperty("Authorization", "Basic " + encodedAuth);
|
||||
|
||||
// 信任所有证书(用于HTTPS)
|
||||
if (conn instanceof HttpsURLConnection) {
|
||||
SSLContext sslContext = SSLContext.getInstance("TLS");
|
||||
sslContext.init(null, new TrustManager[]{new X509TrustManager() {
|
||||
public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; }
|
||||
public void checkClientTrusted(X509Certificate[] certs, String authType) {}
|
||||
public void checkServerTrusted(X509Certificate[] certs, String authType) {}
|
||||
}}, new java.security.SecureRandom());
|
||||
|
||||
((HttpsURLConnection) conn).setSSLSocketFactory(sslContext.getSocketFactory());
|
||||
((HttpsURLConnection) conn).setHostnameVerifier((hostname, session) -> true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取响应内容
|
||||
*/
|
||||
private String readResponse(HttpURLConnection conn) throws IOException {
|
||||
try (BufferedReader reader = new BufferedReader(
|
||||
new InputStreamReader(
|
||||
conn.getResponseCode() >= 400 ? conn.getErrorStream() : conn.getInputStream(),
|
||||
StandardCharsets.UTF_8))) {
|
||||
StringBuilder response = new StringBuilder();
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
response.append(line);
|
||||
}
|
||||
return response.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 防火墙响应结果类
|
||||
*/
|
||||
public static class FirewallResponse {
|
||||
private boolean success;
|
||||
private String code;
|
||||
private String message;
|
||||
private String rawResponse;
|
||||
|
||||
public FirewallResponse(boolean success, String code, String message) {
|
||||
this.success = success;
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public FirewallResponse(boolean success, String code, String message, String rawResponse) {
|
||||
this.success = success;
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
this.rawResponse = rawResponse;
|
||||
}
|
||||
|
||||
public boolean isSuccess() { return success; }
|
||||
public void setSuccess(boolean success) { this.success = success; }
|
||||
public String getCode() { return code; }
|
||||
public void setCode(String code) { this.code = code; }
|
||||
public String getMessage() { return message; }
|
||||
public void setMessage(String message) { this.message = message; }
|
||||
public String getRawResponse() { return rawResponse; }
|
||||
public void setRawResponse(String rawResponse) { this.rawResponse = rawResponse; }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "FirewallResponse{" +
|
||||
"success=" + success +
|
||||
", code='" + code + '\'' +
|
||||
", message='" + message + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
}
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
package com.haobang.interlocking;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.*;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 探针联动API客户端
|
||||
* 用于调用syslog-consumer模块提供的REST API
|
||||
*/
|
||||
@Component
|
||||
public class InterlockingApiClient {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(InterlockingApiClient.class);
|
||||
|
||||
private final RestTemplate restTemplate = new RestTemplate();
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
@Value("${interlocking.api-key:a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6}")
|
||||
private String apiKey;
|
||||
|
||||
@Value("${interlocking.api.base-url:http://localhost:8089/xdrservice/interlocking}")
|
||||
private String baseUrl;
|
||||
|
||||
/**
|
||||
* 获取待执行的封禁指令
|
||||
* @param probeId 探针ID
|
||||
* @return 指令列表
|
||||
*/
|
||||
public List<Map<String, Object>> getPendingCommands(Long probeId) {
|
||||
String url = baseUrl + "/cmd/pending?probeId=" + probeId;
|
||||
try {
|
||||
String response = doGet(url);
|
||||
JsonNode jsonNode = objectMapper.readTree(response);
|
||||
if (jsonNode.has("code") && jsonNode.get("code").asInt() == 200) {
|
||||
JsonNode dataNode = jsonNode.get("data");
|
||||
List<Map<String, Object>> commands = new ArrayList<>();
|
||||
if (dataNode.isArray()) {
|
||||
for (JsonNode node : dataNode) {
|
||||
commands.add(objectMapper.convertValue(node, Map.class));
|
||||
}
|
||||
}
|
||||
return commands;
|
||||
}
|
||||
logger.warn("获取待执行指令失败: {}", response);
|
||||
return Collections.emptyList();
|
||||
} catch (Exception e) {
|
||||
logger.error("获取待执行指令异常: {}", e.getMessage(), e);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新指令状态为执行中
|
||||
* @param cmdId 指令ID
|
||||
* @return 是否成功
|
||||
*/
|
||||
public boolean updateStatusToExecuting(Long cmdId) {
|
||||
String url = baseUrl + "/cmd/" + cmdId + "/status/executing";
|
||||
try {
|
||||
String response = doPut(url, null);
|
||||
JsonNode jsonNode = objectMapper.readTree(response);
|
||||
return jsonNode.has("code") && jsonNode.get("code").asInt() == 200;
|
||||
} catch (Exception e) {
|
||||
logger.error("更新指令状态为执行中失败: cmdId={}, error={}", cmdId, e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新指令状态为执行完成
|
||||
* @param cmdId 指令ID
|
||||
* @return 是否成功
|
||||
*/
|
||||
public boolean updateStatusToCompleted(Long cmdId) {
|
||||
String url = baseUrl + "/cmd/" + cmdId + "/status/completed";
|
||||
try {
|
||||
String response = doPut(url, null);
|
||||
JsonNode jsonNode = objectMapper.readTree(response);
|
||||
return jsonNode.has("code") && jsonNode.get("code").asInt() == 200;
|
||||
} catch (Exception e) {
|
||||
logger.error("更新指令状态为执行完成失败: cmdId={}, error={}", cmdId, e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新指令状态为执行失败
|
||||
* @param cmdId 指令ID
|
||||
* @param errorMessage 错误信息
|
||||
* @return 是否成功
|
||||
*/
|
||||
public boolean updateStatusToFailed(Long cmdId, String errorMessage) {
|
||||
String url = baseUrl + "/cmd/" + cmdId + "/status/failed";
|
||||
try {
|
||||
Map<String, Object> body = new HashMap<>();
|
||||
body.put("errorMessage", errorMessage);
|
||||
String response = doPut(url, body);
|
||||
JsonNode jsonNode = objectMapper.readTree(response);
|
||||
return jsonNode.has("code") && jsonNode.get("code").asInt() == 200;
|
||||
} catch (Exception e) {
|
||||
logger.error("更新指令状态为执行失败失败: cmdId={}, error={}", cmdId, e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量插入封禁记录
|
||||
* @param logs 封禁记录列表
|
||||
* @return 是否成功
|
||||
*/
|
||||
public boolean batchInsertLogs(List<Map<String, Object>> logs) {
|
||||
String url = baseUrl + "/log/batch";
|
||||
try {
|
||||
Map<String, Object> request = new HashMap<>();
|
||||
request.put("logs", logs);
|
||||
String response = doPost(url, request);
|
||||
JsonNode jsonNode = objectMapper.readTree(response);
|
||||
return jsonNode.has("code") && jsonNode.get("code").asInt() == 200;
|
||||
} catch (Exception e) {
|
||||
logger.error("批量插入封禁记录失败: error={}", e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行GET请求
|
||||
*/
|
||||
private String doGet(String url) {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.set("X-API-KEY", apiKey);
|
||||
HttpEntity<String> entity = new HttpEntity<>(headers);
|
||||
|
||||
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, entity, String.class);
|
||||
return response.getBody();
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行PUT请求
|
||||
*/
|
||||
private String doPut(String url, Map<String, Object> body) {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
headers.set("X-API-KEY", apiKey);
|
||||
|
||||
HttpEntity<Map<String, Object>> entity = new HttpEntity<>(body, headers);
|
||||
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.PUT, entity, String.class);
|
||||
return response.getBody();
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行POST请求
|
||||
*/
|
||||
private String doPost(String url, Map<String, Object> body) {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
headers.set("X-API-KEY", apiKey);
|
||||
|
||||
HttpEntity<Map<String, Object>> entity = new HttpEntity<>(body, headers);
|
||||
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, entity, String.class);
|
||||
return response.getBody();
|
||||
}
|
||||
|
||||
public String getApiKey() { return apiKey; }
|
||||
public void setApiKey(String apiKey) { this.apiKey = apiKey; }
|
||||
public String getBaseUrl() { return baseUrl; }
|
||||
public void setBaseUrl(String baseUrl) { this.baseUrl = baseUrl; }
|
||||
}
|
||||
+412
@@ -0,0 +1,412 @@
|
||||
package com.haobang.interlocking;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.haobang.firewall.FirewallApiClient;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* 探针联动封禁服务
|
||||
* 核心业务逻辑:
|
||||
* 1. 从syslog-consumer获取待执行的封禁指令
|
||||
* 2. 解析封禁指令,生成逐条指令
|
||||
* 3. 调用防火墙API执行封禁
|
||||
* 4. 记录封禁结果到syslog-consumer
|
||||
* 5. 更新指令状态
|
||||
*/
|
||||
@Service
|
||||
public class InterlockingService {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(InterlockingService.class);
|
||||
|
||||
@Autowired
|
||||
private InterlockingApiClient apiClient;
|
||||
|
||||
@Autowired
|
||||
private FirewallApiClient firewallClient;
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
// 探针ID,从配置读取
|
||||
@Value("${app.service.device_collect_id:1}")
|
||||
private Integer probeId;
|
||||
|
||||
// 是否启用联动功能
|
||||
@Value("${interlocking.enabled:true}")
|
||||
private boolean interlockingEnabled;
|
||||
|
||||
// 执行锁,防止重复执行
|
||||
private final AtomicBoolean executing = new AtomicBoolean(false);
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
logger.info("探针联动封禁服务初始化完成,探针ID: {}, 启用状态: {}", probeId, interlockingEnabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* 定时任务:检查并执行待处理的封禁指令
|
||||
* 每30秒执行一次
|
||||
*/
|
||||
@Scheduled(fixedDelay = 30000)
|
||||
public void processPendingCommands() {
|
||||
if (!interlockingEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 防止重复执行
|
||||
if (!executing.compareAndSet(false, true)) {
|
||||
logger.debug("上次执行尚未完成,跳过本次调度");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
logger.info("开始检查待执行的封禁指令...");
|
||||
|
||||
// 1. 获取待执行的封禁指令
|
||||
List<Map<String, Object>> pendingCommands = apiClient.getPendingCommands(probeId.longValue());
|
||||
|
||||
if (pendingCommands.isEmpty()) {
|
||||
logger.debug("没有待执行的封禁指令");
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info("获取到 {} 条待执行的封禁指令", pendingCommands.size());
|
||||
|
||||
// 2. 逐条处理指令
|
||||
for (Map<String, Object> cmd : pendingCommands) {
|
||||
try {
|
||||
processCommand(cmd);
|
||||
} catch (Exception e) {
|
||||
logger.error("处理封禁指令异常: cmdId={}, error={}", cmd.get("id"), e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("检查待执行指令失败: {}", e.getMessage(), e);
|
||||
} finally {
|
||||
executing.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理单条封禁指令
|
||||
* 指令数据结构(JSON字段名为驼峰格式):
|
||||
* - banIps: 封禁IP数组
|
||||
* - banType: 封禁类型(0:黑名单、1:白名单)
|
||||
* - banOperationType: 操作类型(0:新增、1:删除)
|
||||
* - banDuration: 封禁时长(秒)
|
||||
* - deviceInterlockingIp: 联动设备IP数组
|
||||
* - deviceInterlockingId: 联动设备ID数组
|
||||
*/
|
||||
private void processCommand(Map<String, Object> cmd) {
|
||||
Long cmdId = ((Number) cmd.get("id")).longValue();
|
||||
// JSON字段名使用驼峰格式
|
||||
List<String> banIps = parseStringArray(cmd.get("banIps"));
|
||||
String banType = (String) cmd.get("banType");
|
||||
Integer banOperationType = cmd.get("banOperationType") != null ?
|
||||
((Number) cmd.get("banOperationType")).intValue() : 0;
|
||||
Integer banDuration = cmd.get("banDuration") != null ?
|
||||
((Number) cmd.get("banDuration")).intValue() : -1;
|
||||
List<String> deviceIps = parseStringArray(cmd.get("deviceInterlockingIp"));
|
||||
|
||||
logger.info("开始处理封禁指令: cmdId={}, banType={}, operationType={}, ipCount={}",
|
||||
cmdId, banType, banOperationType, banIps.size());
|
||||
|
||||
try {
|
||||
// 3. 更新状态为执行中
|
||||
if (!apiClient.updateStatusToExecuting(cmdId)) {
|
||||
logger.warn("更新指令状态为执行中失败,跳过此指令: cmdId={}", cmdId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (banIps.isEmpty()) {
|
||||
logger.warn("封禁IP列表为空: cmdId={}", cmdId);
|
||||
apiClient.updateStatusToCompleted(cmdId);
|
||||
return;
|
||||
}
|
||||
|
||||
// 4. 根据设备列表执行封禁
|
||||
int successCount = 0;
|
||||
int failCount = 0;
|
||||
List<Map<String, Object>> allResults = new ArrayList<>();
|
||||
|
||||
// 遍历每个联动设备执行封禁
|
||||
for (String deviceIp : deviceIps) {
|
||||
// 设置防火墙客户端的设备IP(这里简化处理,实际可能需要从配置获取)
|
||||
List<Map<String, Object>> results = executeBlockOperations(
|
||||
deviceIp, banType, banOperationType, banIps, banDuration, cmdId);
|
||||
allResults.addAll(results);
|
||||
|
||||
for (Map<String, Object> result : results) {
|
||||
// banResult: 1表示成功,0表示失败
|
||||
if ("1".equals(result.get("banResult"))) {
|
||||
successCount++;
|
||||
} else {
|
||||
failCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 批量记录封禁结果
|
||||
for (Map<String, Object> result : allResults) {
|
||||
result.put("deviceInterlockingCmdId", cmdId);
|
||||
result.put("probeId", probeId);
|
||||
result.put("banMethod", "1"); // 自动化封禁
|
||||
}
|
||||
|
||||
boolean logSaved = apiClient.batchInsertLogs(allResults);
|
||||
if (!logSaved) {
|
||||
logger.warn("批量记录封禁结果失败: cmdId={}", cmdId);
|
||||
}
|
||||
|
||||
// 6. 更新指令状态为执行完成
|
||||
if (apiClient.updateStatusToCompleted(cmdId)) {
|
||||
logger.info("封禁指令执行完成: cmdId={}, 成功={}, 失败={}", cmdId, successCount, failCount);
|
||||
} else {
|
||||
logger.error("更新指令状态为执行完成失败: cmdId={}", cmdId);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("处理封禁指令异常: cmdId={}, error={}", cmdId, e.getMessage(), e);
|
||||
apiClient.updateStatusToFailed(cmdId, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析PostgreSQL数组格式
|
||||
*/
|
||||
private List<String> parseStringArray(Object obj) {
|
||||
List<String> result = new ArrayList<>();
|
||||
if (obj == null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
try {
|
||||
if (obj instanceof List) {
|
||||
for (Object item : (List<?>) obj) {
|
||||
result.add(String.valueOf(item));
|
||||
}
|
||||
} else if (obj instanceof String) {
|
||||
String str = (String) obj;
|
||||
// 处理PostgreSQL数组格式 {a,b,c}
|
||||
if (str.startsWith("{") && str.endsWith("}")) {
|
||||
str = str.substring(1, str.length() - 1);
|
||||
for (String item : str.split(",")) {
|
||||
if (!item.isEmpty()) {
|
||||
result.add(item.trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("解析数组失败: {}", e.getMessage());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行封禁操作
|
||||
* @param deviceIp 设备IP
|
||||
* @param banType 封禁类型(0:黑名单、1:白名单)
|
||||
* @param operationType 操作类型(0:新增、1:删除)
|
||||
* @param ipList IP列表
|
||||
* @param duration 封禁时长
|
||||
* @param cmdId 指令ID
|
||||
*/
|
||||
private List<Map<String, Object>> executeBlockOperations(
|
||||
String deviceIp, String banType, int operationType,
|
||||
List<String> ipList, int duration, Long cmdId) {
|
||||
|
||||
List<Map<String, Object>> results = new ArrayList<>();
|
||||
|
||||
// 确定操作类型
|
||||
String operationName;
|
||||
if ("0".equals(banType)) {
|
||||
// 黑名单
|
||||
operationName = operationType == 0 ? "add_blacklist" : "del_blacklist";
|
||||
} else {
|
||||
// 白名单
|
||||
operationName = operationType == 0 ? "add_whitelist" : "del_whitelist";
|
||||
}
|
||||
|
||||
for (String ip : ipList) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("banIp", ip);
|
||||
result.put("deviceInterlockingId", null); // 后续可关联
|
||||
result.put("deviceName", deviceIp);
|
||||
result.put("banResult", "0"); // 默认失败,0表示失败
|
||||
result.put("reqBody", "");
|
||||
result.put("respBody", "");
|
||||
|
||||
try {
|
||||
FirewallApiClient.FirewallResponse response;
|
||||
|
||||
switch (operationName) {
|
||||
case "add_blacklist":
|
||||
response = firewallClient.addToBlacklist(ip, duration, "联动封禁");
|
||||
break;
|
||||
case "del_blacklist":
|
||||
response = firewallClient.removeFromBlacklist(ip);
|
||||
break;
|
||||
case "add_whitelist":
|
||||
response = firewallClient.addToWhitelist(ip, "whitelist_" + ip, "联动封禁白名单");
|
||||
break;
|
||||
case "del_whitelist":
|
||||
response = firewallClient.removeFromWhitelist(ip);
|
||||
break;
|
||||
default:
|
||||
logger.warn("未知的指令类型: {}", operationName);
|
||||
result.put("respBody", "未知的指令类型: " + operationName);
|
||||
results.add(result);
|
||||
continue;
|
||||
}
|
||||
|
||||
// banResult: 1表示成功,0表示失败
|
||||
result.put("banResult", response.isSuccess() ? "1" : "0");
|
||||
result.put("respBody", response.getRawResponse());
|
||||
|
||||
// 构建请求body用于记录
|
||||
result.put("reqBody", buildRequestBody(operationName, ip, duration));
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("执行封禁操作异常: ip={}, operation={}, error={}", ip, operationName, e.getMessage());
|
||||
result.put("respBody", "执行异常: " + e.getMessage());
|
||||
}
|
||||
|
||||
results.add(result);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建请求body用于记录
|
||||
*/
|
||||
private String buildRequestBody(String cmdType, String ip, int duration) {
|
||||
try {
|
||||
Map<String, Object> body = new HashMap<>();
|
||||
switch (cmdType) {
|
||||
case "add_blacklist":
|
||||
body.put("blist", ip);
|
||||
body.put("age", String.valueOf(duration));
|
||||
body.put("enable", "1");
|
||||
break;
|
||||
case "del_blacklist":
|
||||
body.put("blist", ip);
|
||||
break;
|
||||
case "add_whitelist":
|
||||
body.put("enable", "1");
|
||||
body.put("name", "whitelist_" + ip);
|
||||
List<Map<String, String>> addr = new ArrayList<>();
|
||||
Map<String, String> addrItem = new HashMap<>();
|
||||
addrItem.put("address", ip);
|
||||
addr.add(addrItem);
|
||||
body.put("addr", addr);
|
||||
break;
|
||||
case "del_whitelist":
|
||||
body.put("name", ip);
|
||||
break;
|
||||
}
|
||||
return objectMapper.writeValueAsString(body);
|
||||
} catch (Exception e) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动触发封禁
|
||||
*/
|
||||
public Map<String, Object> manualBlock(String ip, String cmdType, int age, String reason) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
|
||||
try {
|
||||
FirewallApiClient.FirewallResponse response;
|
||||
|
||||
switch (cmdType) {
|
||||
case "add_blacklist":
|
||||
response = firewallClient.addToBlacklist(ip, age, reason);
|
||||
break;
|
||||
case "del_blacklist":
|
||||
response = firewallClient.removeFromBlacklist(ip);
|
||||
break;
|
||||
case "add_whitelist":
|
||||
response = firewallClient.addToWhitelist(ip, "manual_" + ip, reason);
|
||||
break;
|
||||
case "del_whitelist":
|
||||
response = firewallClient.removeFromWhitelist(ip);
|
||||
break;
|
||||
default:
|
||||
result.put("success", false);
|
||||
result.put("message", "未知的指令类型: " + cmdType);
|
||||
return result;
|
||||
}
|
||||
|
||||
result.put("success", response.isSuccess());
|
||||
result.put("code", response.getCode());
|
||||
result.put("message", response.getMessage());
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("手动封禁异常: ip={}, cmdType={}, error={}", ip, cmdType, e.getMessage());
|
||||
result.put("success", false);
|
||||
result.put("message", "执行异常: " + e.getMessage());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量手动封禁
|
||||
*/
|
||||
public Map<String, Object> batchManualBlock(List<String> ips, String cmdType, int age, String reason) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
List<Map<String, String>> ipList = new ArrayList<>();
|
||||
|
||||
for (String ip : ips) {
|
||||
Map<String, String> ipInfo = new HashMap<>();
|
||||
ipInfo.put("ip", ip);
|
||||
ipInfo.put("age", String.valueOf(age));
|
||||
ipInfo.put("reason", reason);
|
||||
ipList.add(ipInfo);
|
||||
}
|
||||
|
||||
try {
|
||||
FirewallApiClient.FirewallResponse response;
|
||||
|
||||
if ("add_blacklist".equals(cmdType)) {
|
||||
response = firewallClient.batchAddToBlacklist(ipList);
|
||||
} else if ("del_blacklist".equals(cmdType)) {
|
||||
response = firewallClient.batchRemoveFromBlacklist(ips);
|
||||
} else {
|
||||
result.put("success", false);
|
||||
result.put("message", "批量操作暂不支持此类型: " + cmdType);
|
||||
return result;
|
||||
}
|
||||
|
||||
result.put("success", response.isSuccess());
|
||||
result.put("code", response.getCode());
|
||||
result.put("message", response.getMessage());
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("批量手动封禁异常: cmdType={}, error={}", cmdType, e.getMessage());
|
||||
result.put("success", false);
|
||||
result.put("message", "执行异常: " + e.getMessage());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public Integer getProbeId() { return probeId; }
|
||||
public void setProbeId(Integer probeId) { this.probeId = probeId; }
|
||||
public boolean isInterlockingEnabled() { return interlockingEnabled; }
|
||||
public void setInterlockingEnabled(boolean interlockingEnabled) { this.interlockingEnabled = interlockingEnabled; }
|
||||
}
|
||||
+239
@@ -0,0 +1,239 @@
|
||||
package com.haobang.interlocking;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.http.*;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* 探针心跳客户端
|
||||
* 定期向平台端发送心跳,支持重试和指数退避
|
||||
*/
|
||||
@Component
|
||||
public class ProbeHeartbeatClient {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ProbeHeartbeatClient.class);
|
||||
|
||||
private final RestTemplate restTemplate = new RestTemplate();
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
@Value("${probe.heartbeat.enabled:true}")
|
||||
private boolean heartbeatEnabled;
|
||||
|
||||
@Value("${probe.heartbeat.interval-seconds:60}")
|
||||
private int heartbeatIntervalSeconds;
|
||||
|
||||
@Value("${app.service.device_collect_id:1}")
|
||||
private String collectId;
|
||||
|
||||
@Value("${app.service.device_collect_name:采集探针-01}")
|
||||
private String collectName;
|
||||
|
||||
@Value("${app.service.version:V1.0.0-20260509}")
|
||||
private String appVersion;
|
||||
|
||||
@Value("${probe.platform.api-url:http://localhost:8089/xdrservice/interlocking/probe/heartbeat}")
|
||||
private String platformApiUrl;
|
||||
|
||||
@Value("${interlocking.api-key:a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6}")
|
||||
private String apiKey;
|
||||
|
||||
/** 重试次数 */
|
||||
private final AtomicInteger retryCount = new AtomicInteger(0);
|
||||
|
||||
/** 最大重试次数 */
|
||||
private static final int MAX_RETRIES = 5;
|
||||
|
||||
/** 初始重试延迟(毫秒) */
|
||||
private static final long INITIAL_BACKOFF_MS = 1000;
|
||||
|
||||
/** 最大退避延迟(毫秒) */
|
||||
private static final long MAX_BACKOFF_MS = 60000;
|
||||
|
||||
/**
|
||||
* 发送心跳
|
||||
* @return 是否成功
|
||||
*/
|
||||
public boolean sendHeartbeat() {
|
||||
if (!heartbeatEnabled) {
|
||||
logger.debug("心跳发送已禁用");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 构建心跳数据
|
||||
Map<String, Object> heartbeatData = buildHeartbeatData();
|
||||
|
||||
try {
|
||||
// 发送HTTP POST请求
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
headers.set("X-API-KEY", apiKey);
|
||||
|
||||
HttpEntity<Map<String, Object>> entity = new HttpEntity<>(heartbeatData, headers);
|
||||
ResponseEntity<String> response = restTemplate.exchange(
|
||||
platformApiUrl,
|
||||
HttpMethod.POST,
|
||||
entity,
|
||||
String.class
|
||||
);
|
||||
|
||||
if (response.getStatusCode() == HttpStatus.OK) {
|
||||
// 重置重试计数
|
||||
retryCount.set(0);
|
||||
|
||||
// 检查响应
|
||||
String body = response.getBody();
|
||||
if (body != null && body.contains("\"code\":200")) {
|
||||
logger.debug("心跳发送成功: collectId={}, timestamp={}", collectId, heartbeatData.get("timestamp"));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 请求失败,准备重试
|
||||
handleFailure("HTTP " + response.getStatusCodeValue());
|
||||
return false;
|
||||
|
||||
} catch (Exception e) {
|
||||
handleFailure(e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送心跳(简化版,供其他服务调用)
|
||||
*/
|
||||
public boolean sendHeartbeat(String alertType, String alertContent) {
|
||||
// 直接发送普通心跳
|
||||
return sendHeartbeat();
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建心跳数据
|
||||
*/
|
||||
private Map<String, Object> buildHeartbeatData() {
|
||||
Map<String, Object> data = new HashMap<>();
|
||||
|
||||
// 使用配置的collectId或自动获取
|
||||
String probeId = collectId;
|
||||
if (probeId == null || probeId.isEmpty()) {
|
||||
probeId = getLocalHostIdentifier();
|
||||
}
|
||||
|
||||
data.put("collectId", probeId);
|
||||
data.put("collectName", collectName != null && !collectName.isEmpty() ? collectName : "SyslogServe");
|
||||
data.put("deviceIp", getLocalIp());
|
||||
data.put("appVersion", appVersion);
|
||||
data.put("timestamp", LocalDateTime.now().toString());
|
||||
|
||||
// 添加负载状态(可选)
|
||||
Map<String, Object> loadStatus = new HashMap<>();
|
||||
loadStatus.put("memoryUsage", getMemoryUsage());
|
||||
loadStatus.put("threadCount", Thread.activeCount());
|
||||
data.put("loadStatus", toJson(loadStatus));
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取本地主机标识符
|
||||
*/
|
||||
private String getLocalHostIdentifier() {
|
||||
try {
|
||||
InetAddress ip = InetAddress.getLocalHost();
|
||||
return ip.getHostName() + "-" + ip.getHostAddress();
|
||||
} catch (UnknownHostException e) {
|
||||
return "unknown-" + System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取本地IP地址
|
||||
*/
|
||||
private String getLocalIp() {
|
||||
try {
|
||||
InetAddress ip = InetAddress.getLocalHost();
|
||||
return ip.getHostAddress();
|
||||
} catch (UnknownHostException e) {
|
||||
return "127.0.0.1";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取内存使用情况
|
||||
*/
|
||||
private double getMemoryUsage() {
|
||||
Runtime runtime = Runtime.getRuntime();
|
||||
long totalMemory = runtime.totalMemory();
|
||||
long freeMemory = runtime.freeMemory();
|
||||
long usedMemory = totalMemory - freeMemory;
|
||||
return Math.round((double) usedMemory / totalMemory * 100 * 100) / 100.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对象转JSON字符串
|
||||
*/
|
||||
private String toJson(Object obj) {
|
||||
try {
|
||||
return objectMapper.writeValueAsString(obj);
|
||||
} catch (Exception e) {
|
||||
return "{}";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理发送失败,使用指数退避重试
|
||||
*/
|
||||
private void handleFailure(String errorMsg) {
|
||||
int currentRetry = retryCount.incrementAndGet();
|
||||
|
||||
if (currentRetry <= MAX_RETRIES) {
|
||||
// 计算退避延迟
|
||||
long backoffMs = Math.min(INITIAL_BACKOFF_MS * (1L << (currentRetry - 1)), MAX_BACKOFF_MS);
|
||||
|
||||
logger.warn("心跳发送失败 (尝试 {}/{}): {}, {}ms后将重试",
|
||||
currentRetry, MAX_RETRIES, errorMsg, backoffMs);
|
||||
|
||||
try {
|
||||
Thread.sleep(backoffMs);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
} else {
|
||||
logger.error("心跳发送失败,已达到最大重试次数 {}: {}", MAX_RETRIES, errorMsg);
|
||||
// 重置重试计数,下次将重新开始
|
||||
retryCount.set(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取配置的心跳间隔
|
||||
*/
|
||||
public int getHeartbeatIntervalSeconds() {
|
||||
return heartbeatIntervalSeconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取探针ID
|
||||
*/
|
||||
public String getCollectId() {
|
||||
return collectId != null && !collectId.isEmpty() ? collectId : getLocalHostIdentifier();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查心跳功能是否启用
|
||||
*/
|
||||
public boolean isEnabled() {
|
||||
return heartbeatEnabled;
|
||||
}
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
package com.haobang.schedule;
|
||||
|
||||
import com.haobang.interlocking.ProbeHeartbeatClient;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 探针心跳调度器
|
||||
* 定时向平台端发送心跳
|
||||
*/
|
||||
@Component
|
||||
public class ProbeHeartbeatScheduler {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(ProbeHeartbeatScheduler.class);
|
||||
|
||||
@Autowired
|
||||
private ProbeHeartbeatClient probeHeartbeatClient;
|
||||
|
||||
/**
|
||||
* 定时发送心跳
|
||||
* 使用动态间隔(根据配置)
|
||||
* 默认每60秒发送一次
|
||||
*/
|
||||
@Scheduled(fixedDelayString = "${probe.heartbeat.interval-ms:60000}", initialDelayString = "${probe.heartbeat.initial-delay-ms:5000}")
|
||||
public void sendHeartbeat() {
|
||||
if (!probeHeartbeatClient.isEnabled()) {
|
||||
logger.debug("探针心跳已禁用,跳过本次心跳");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
boolean success = probeHeartbeatClient.sendHeartbeat();
|
||||
|
||||
if (success) {
|
||||
logger.debug("探针心跳发送成功, collectId={}", probeHeartbeatClient.getCollectId());
|
||||
} else {
|
||||
logger.warn("探针心跳发送失败, collectId={}", probeHeartbeatClient.getCollectId());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("探针心跳发送异常: {}", e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动触发心跳发送
|
||||
* @return 是否成功
|
||||
*/
|
||||
public boolean manualSendHeartbeat() {
|
||||
logger.info("手动触发探针心跳");
|
||||
return probeHeartbeatClient.sendHeartbeat();
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,58 @@
|
||||
package com.haobang.syslog;
|
||||
|
||||
import org.graylog2.syslog4j.SyslogConstants;
|
||||
import org.graylog2.syslog4j.server.*;
|
||||
|
||||
import java.net.SocketAddress;
|
||||
|
||||
public class MySyslogServer {
|
||||
private static final String HOST = "127.0.0.1";
|
||||
private static final int PORT = 514;
|
||||
//514
|
||||
|
||||
private void receiveSyslogMessage() throws InterruptedException {
|
||||
SyslogServerIF server = SyslogServer.getInstance(SyslogConstants.UDP);
|
||||
SyslogServerConfigIF config = server.getConfig();
|
||||
config.setHost(HOST);
|
||||
config.setPort(PORT);
|
||||
config.addEventHandler(new SyslogServerSessionEventHandlerIF() {
|
||||
@Override
|
||||
public Object sessionOpened(SyslogServerIF syslogServerIF, SocketAddress socketAddress) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void event(Object o, SyslogServerIF syslogServerIF, SocketAddress socketAddress,
|
||||
SyslogServerEventIF syslogServerEventIF) {
|
||||
System.out.println("receive from:" + socketAddress + "\tmessage" + syslogServerEventIF.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exception(Object o, SyslogServerIF syslogServerIF, SocketAddress socketAddress, Exception e) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sessionClosed(Object o, SyslogServerIF syslogServerIF, SocketAddress socketAddress, boolean b) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize(SyslogServerIF syslogServerIF) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy(SyslogServerIF syslogServerIF) {
|
||||
|
||||
}
|
||||
});
|
||||
SyslogServer.getThreadedInstance(SyslogConstants.UDP);
|
||||
Thread.sleep(100000);
|
||||
}
|
||||
|
||||
|
||||
public static void main(String[] args) throws InterruptedException {
|
||||
new MySyslogServer().receiveSyslogMessage();
|
||||
}
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
package com.haobang.syslog;
|
||||
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
|
||||
public class TestSyslogServer {
|
||||
public static void main(String[] args) throws Throwable {
|
||||
// TODO Auto-generated method stub
|
||||
DatagramSocket datagramSocket = new DatagramSocket(514);
|
||||
while (true) {
|
||||
DatagramPacket packet = new DatagramPacket(new byte[1000], 1000);
|
||||
try {
|
||||
//不会造成死循环,因为receive是阻塞式方法,若发送方不发送数据,则阻塞在该处
|
||||
datagramSocket.receive(packet);
|
||||
String msg = new String(packet.getData(), 0, packet.getLength());
|
||||
//handelMessage(msg);
|
||||
|
||||
System.out.println(packet.getAddress() + "/" + packet.getPort() + ":" + msg);
|
||||
packet.setData("I am server!!!".getBytes());
|
||||
datagramSocket.send(packet);
|
||||
Thread.sleep(1000);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void handelMessage(String messageFragment){
|
||||
StringBuilder currentMessage = new StringBuilder();
|
||||
boolean isSplitMessage = messageFragment.endsWith("..."); // 根据拆分标志判断消息是否被拆分
|
||||
|
||||
if (isSplitMessage) {
|
||||
// 拆分的消息片段,将其合并到当前消息
|
||||
currentMessage.append(messageFragment.substring(0, messageFragment.length() - 3));
|
||||
} else {
|
||||
// 完整的消息,处理并重置当前消息
|
||||
currentMessage.append(messageFragment);
|
||||
handleCompleteMessage(currentMessage.toString());
|
||||
currentMessage.setLength(0);
|
||||
}
|
||||
}
|
||||
public static void handleCompleteMessage(String completeMessage){
|
||||
System.out.println("接收到完整消息:" + completeMessage);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
package com.haobang.util;
|
||||
|
||||
|
||||
import com.common.entity.DeviceDevice;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.haobang.config.AppConfig;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
public class DeviceInfoUtil {
|
||||
|
||||
/**
|
||||
* 补充设备配置信息 key value值
|
||||
* @param strSyslog
|
||||
* @return
|
||||
*/
|
||||
public static String getFullLogString(String strSyslog)
|
||||
{
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
// 创建设备配置信息MAP
|
||||
// 获取当前时间(精度到毫秒)
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
// 定义格式化器,包含毫秒
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS");
|
||||
// 转换为字符串
|
||||
String timeString = now.format(formatter);
|
||||
|
||||
Map<String, String> map = new LinkedHashMap<>();
|
||||
map.put("receive_time",timeString);
|
||||
map.put("device_id", AppConfig.getAppServieDeviceId());
|
||||
map.put("device_name", AppConfig.getAppServieDeviceName());
|
||||
map.put("vendor", AppConfig.getAppServieVendor());
|
||||
map.put("data_type", AppConfig.getAppServieDataType());
|
||||
map.put("device_collect_id", Long.toString(AppConfig.getDeviceCollectId()));
|
||||
|
||||
String formattedString = formatDeviceInfo(map);
|
||||
//return formattedString + "" + strSyslog.substring(34);
|
||||
|
||||
//测试环境截取34位之后字符串
|
||||
return formattedString + strSyslog;
|
||||
}
|
||||
/**
|
||||
* 补充设备配置信息 key value值
|
||||
* @param strSyslog
|
||||
* @return
|
||||
*/
|
||||
public static String getFullLogString(DeviceDevice deviceDevice,String strSyslog)
|
||||
{
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
// 创建设备配置信息MAP
|
||||
// 获取当前时间(精度到毫秒)
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
// 定义格式化器,包含毫秒
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS");
|
||||
// 转换为字符串
|
||||
String timeString = now.format(formatter);
|
||||
Map<String, String> map = new LinkedHashMap<>();
|
||||
map.put("receive_time",timeString);
|
||||
map.put("device_id",deviceDevice.getId().toString());
|
||||
map.put("device_name", deviceDevice.getName());
|
||||
map.put("vendor", deviceDevice.getVendor());
|
||||
map.put("data_type", AppConfig.getAppServieDataType());
|
||||
map.put("device_collect_id", Long.toString(AppConfig.getDeviceCollectId()));
|
||||
String formattedString = formatDeviceInfo(map);
|
||||
//return formattedString + "" + strSyslog.substring(34);
|
||||
//测试环境截取34位之后字符串
|
||||
return formattedString + strSyslog;
|
||||
}
|
||||
|
||||
public static String getFullLogString(DeviceDevice deviceDevice,String strSyslog,String receive_time)
|
||||
{
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
// 创建设备配置信息MAP
|
||||
// 获取当前时间(精度到毫秒)
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
// 定义格式化器,包含毫秒
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS");
|
||||
// 转换为字符串
|
||||
String timeString = now.format(formatter);
|
||||
Map<String, String> map = new LinkedHashMap<>();
|
||||
map.put("receive_time",receive_time);
|
||||
map.put("device_id",deviceDevice.getId().toString());
|
||||
map.put("device_name", deviceDevice.getName());
|
||||
map.put("vendor", deviceDevice.getVendor());
|
||||
map.put("data_type", AppConfig.getAppServieDataType());
|
||||
map.put("device_collect_id", Long.toString(AppConfig.getDeviceCollectId()));
|
||||
String formattedString = formatDeviceInfo(map);
|
||||
//return formattedString + "" + strSyslog.substring(34);
|
||||
//测试环境截取34位之后字符串
|
||||
return formattedString + strSyslog;
|
||||
}
|
||||
|
||||
|
||||
public static String formatDeviceInfo(Map<String, String> deviceData) {
|
||||
if (deviceData == null || deviceData.isEmpty()) {
|
||||
return "[]"; // 返回空括号表示无数据
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("["); // 开始字符
|
||||
|
||||
// 使用 LinkedHashMap 保持插入顺序
|
||||
boolean firstEntry = true;
|
||||
for (Map.Entry<String, String> entry : deviceData.entrySet()) {
|
||||
if (!firstEntry) {
|
||||
sb.append(" "); // 键值对之间用空格分隔
|
||||
}
|
||||
sb.append(entry.getKey())
|
||||
.append("=")
|
||||
.append(entry.getValue());
|
||||
firstEntry = false;
|
||||
}
|
||||
sb.append("]"); // 结束字符
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package com.haobang.util;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List ;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Component
|
||||
public class SafeCacheUtil {
|
||||
|
||||
@Autowired
|
||||
private RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
@Autowired
|
||||
private ObjectMapper objectMapper;
|
||||
|
||||
/**
|
||||
* 安全的缓存获取方法
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getSafe(String key, Class<T> clazz, Supplier<T> loader) {
|
||||
try {
|
||||
Object cached = redisTemplate.opsForValue().get(key);
|
||||
|
||||
if (cached == null) {
|
||||
// 缓存不存在,从数据源加载
|
||||
T value = loader.get();
|
||||
if (value != null) {
|
||||
redisTemplate.opsForValue().set(key, value, 30, TimeUnit.MINUTES);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
// 类型匹配,直接返回
|
||||
if (clazz.isInstance(cached)) {
|
||||
return (T) cached;
|
||||
}
|
||||
|
||||
// 类型不匹配,尝试转换
|
||||
if (cached instanceof LinkedHashMap) {
|
||||
return objectMapper.convertValue(cached, clazz);
|
||||
}
|
||||
|
||||
// 无法转换,重新加载
|
||||
T value = loader.get();
|
||||
if (value != null) {
|
||||
redisTemplate.opsForValue().set(key, value, 30, TimeUnit.MINUTES);
|
||||
}
|
||||
return value;
|
||||
|
||||
} catch (Exception e) {
|
||||
// 缓存出错,降级到数据源
|
||||
return loader.get();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全的列表缓存获取
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> List<T> getSafeList(String key, Class<T> elementClass, Supplier<List<T>> loader) {
|
||||
try {
|
||||
Object cached = redisTemplate.opsForValue().get(key);
|
||||
|
||||
if (cached == null) {
|
||||
List<T> value = loader.get();
|
||||
if (value != null && !value.isEmpty()) {
|
||||
redisTemplate.opsForValue().set(key, value, 30, TimeUnit.MINUTES);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
// 已经是正确的类型
|
||||
if (cached instanceof List &&
|
||||
!((List<?>) cached).isEmpty() &&
|
||||
elementClass.isInstance(((List<?>) cached).get(0))) {
|
||||
return (List<T>) cached;
|
||||
}
|
||||
|
||||
// 需要转换
|
||||
if (cached instanceof List) {
|
||||
List<LinkedHashMap> rawList = (List<LinkedHashMap>) cached;
|
||||
List<T> convertedList = rawList.stream()
|
||||
.map(item -> objectMapper.convertValue(item, elementClass))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 更新缓存为正确格式
|
||||
redisTemplate.opsForValue().set(key, convertedList, 30, TimeUnit.MINUTES);
|
||||
return convertedList;
|
||||
}
|
||||
|
||||
// 无法处理,重新加载
|
||||
List<T> value = loader.get();
|
||||
if (value != null && !value.isEmpty()) {
|
||||
redisTemplate.opsForValue().set(key, value, 30, TimeUnit.MINUTES);
|
||||
}
|
||||
return value;
|
||||
|
||||
} catch (Exception e) {
|
||||
return loader.get();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
package com.haobang.util;
|
||||
|
||||
import cn.hutool.core.util.HexUtil;
|
||||
import cn.hutool.crypto.Mode;
|
||||
import cn.hutool.crypto.Padding;
|
||||
import cn.hutool.crypto.SmUtil;
|
||||
import cn.hutool.crypto.symmetric.SM4;
|
||||
import cn.hutool.crypto.symmetric.SymmetricCrypto;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.Security;
|
||||
import java.util.Base64;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
|
||||
import cn.hutool.core.util.HexUtil;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
/**
|
||||
* 国密SM4加解密工具类
|
||||
* 基于 Hutool + Bouncy Castle 实现,支持 ECB 和 CBC 模式
|
||||
*/
|
||||
public class Sm4Util {
|
||||
|
||||
/**
|
||||
* 生成 SM4 密钥(16字节,128位)
|
||||
* @return 十六进制字符串格式的密钥
|
||||
*/
|
||||
public static String generateKey() {
|
||||
byte[] key = SmUtil.sm4().getSecretKey().getEncoded();
|
||||
return HexUtil.encodeHexStr(key);
|
||||
}
|
||||
|
||||
// ==================== ECB 模式(电子密码本模式) ====================
|
||||
// 注意:ECB 模式不需要 IV,但安全性较低,不适合加密大量数据
|
||||
|
||||
/**
|
||||
* 使用 ECB 模式加密
|
||||
* @param plainText 明文
|
||||
* @param hexKey 十六进制字符串格式的密钥
|
||||
* @return Base64 编码的密文
|
||||
*/
|
||||
public static String encryptEcb(String plainText, String hexKey) {
|
||||
SM4 sm4 = new SM4(HexUtil.decodeHex(hexKey));
|
||||
return sm4.encryptBase64(plainText);
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用 ECB 模式解密
|
||||
* @param cipherText Base64 编码的密文
|
||||
* @param hexKey 十六进制字符串格式的密钥
|
||||
* @return 明文
|
||||
*/
|
||||
public static String decryptEcb(String cipherText, String hexKey) {
|
||||
SM4 sm4 = new SM4(HexUtil.decodeHex(hexKey));
|
||||
return sm4.decryptStr(cipherText);
|
||||
}
|
||||
|
||||
// ==================== CBC 模式(密码分组链接模式) ====================
|
||||
// 推荐使用:需要 IV(初始化向量),安全性更高
|
||||
|
||||
|
||||
static {
|
||||
// 静态代码块中统一注册 Provider,确保全局生效
|
||||
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* CBC 模式加密(推荐)
|
||||
* @param plainText 明文
|
||||
* @param hexKey 十六进制字符串格式的密钥
|
||||
* @return 格式为 "Base64(IV):Base64(密文)" 的字符串
|
||||
*/
|
||||
public static String encryptCbc(String plainText, String hexKey) {
|
||||
// 1. 随机生成16字节 IV
|
||||
byte[] iv = RandomUtil.randomBytes(16);
|
||||
// 2. 将 IV 包装为 AlgorithmParameterSpec
|
||||
IvParameterSpec ivSpec = new IvParameterSpec(iv);
|
||||
// 3. 将十六进制密钥转为字节数组并包装为 SecretKey
|
||||
byte[] keyBytes = HexUtil.decodeHex(hexKey);
|
||||
SecretKey secretKey = new SecretKeySpec(keyBytes, "SM4");
|
||||
// 4. 创建加解密器
|
||||
SymmetricCrypto sm4 = new SymmetricCrypto("SM4/CBC/PKCS5Padding", secretKey, ivSpec);
|
||||
// 5. 执行加密
|
||||
String encryptedBase64 = sm4.encryptBase64(plainText);
|
||||
String ivBase64 = Base64.getEncoder().encodeToString(iv);
|
||||
return ivBase64 + ":" + encryptedBase64;
|
||||
}
|
||||
|
||||
/**
|
||||
* CBC 模式解密
|
||||
* @param cipherTextWithIv 由 encryptCbc 生成的字符串
|
||||
* @param hexKey 十六进制密钥
|
||||
* @return 明文
|
||||
*/
|
||||
public static String decryptCbc(String cipherTextWithIv, String hexKey) {
|
||||
String[] parts = cipherTextWithIv.split(":");
|
||||
if (parts.length != 2) {
|
||||
throw new IllegalArgumentException("无效的密文格式,应为 'IV:密文'");
|
||||
}
|
||||
// 1. 解析 IV
|
||||
byte[] iv = Base64.getDecoder().decode(parts[0]);
|
||||
IvParameterSpec ivSpec = new IvParameterSpec(iv);
|
||||
// 2. 解析密钥
|
||||
byte[] keyBytes = HexUtil.decodeHex(hexKey);
|
||||
SecretKey secretKey = new SecretKeySpec(keyBytes, "SM4");
|
||||
// 3. 创建解密器(算法参数必须与加密时完全一致)
|
||||
SymmetricCrypto sm4 = new SymmetricCrypto("SM4/CBC/PKCS5Padding", secretKey, ivSpec);
|
||||
// 4. 解密
|
||||
return sm4.decryptStr(parts[1]);
|
||||
}
|
||||
public static void main(String[] args) {
|
||||
// 1. 生成 SM4 密钥(128位,十六进制字符串)
|
||||
String hexKey = Sm4Util.generateKey();
|
||||
System.out.println("生成的密钥(十六进制): " + hexKey);
|
||||
System.out.println("密钥长度: " + HexUtil.decodeHex(hexKey).length + " 字节");
|
||||
|
||||
// 2. 原始明文(包含中文和英文,测试字符集兼容性)
|
||||
String plainText = "Hello 国密算法!SM4 加解密测试。123456";
|
||||
System.out.println("原始明文: " + plainText);
|
||||
|
||||
// 3. 使用 CBC 模式加密
|
||||
String encryptedWithIv = Sm4Util.encryptCbc(plainText, hexKey);
|
||||
System.out.println("加密结果(IV:密文): " + encryptedWithIv);
|
||||
|
||||
// 4. 使用 CBC 模式解密
|
||||
String decryptedText = Sm4Util.decryptCbc(encryptedWithIv, hexKey);
|
||||
System.out.println("解密后的明文: " + decryptedText);
|
||||
|
||||
// 5. 验证加解密是否一致
|
||||
boolean success = plainText.equals(decryptedText);
|
||||
System.out.println("加解密验证结果: " + (success ? "成功" : " 失败"));
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package com.haobang.util;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class SpringContextUtil implements ApplicationContextAware {
|
||||
|
||||
private static ApplicationContext applicationContext;
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
SpringContextUtil.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
public static ApplicationContext getApplicationContext() {
|
||||
return applicationContext;
|
||||
}
|
||||
|
||||
public static <T> T getBean(Class<T> clazz) {
|
||||
return applicationContext.getBean(clazz);
|
||||
}
|
||||
|
||||
public static <T> T getBean(String name, Class<T> clazz) {
|
||||
return applicationContext.getBean(name, clazz);
|
||||
}
|
||||
|
||||
// 专门获取 Mapper 的方法
|
||||
public static <T> T getMapper(Class<T> mapperClass) {
|
||||
return applicationContext.getBean(mapperClass);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.haobang.util;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
public class TimeUtils {
|
||||
|
||||
private static final DateTimeFormatter DEFAULT_FORMATTER =
|
||||
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
|
||||
|
||||
/**
|
||||
* 获取当前时间的字符串表示(包含毫秒)
|
||||
*/
|
||||
public static String getCurrentTimeString() {
|
||||
return LocalDateTime.now().format(DEFAULT_FORMATTER);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定格式的当前时间字符串
|
||||
*/
|
||||
public static String getCurrentTimeString(String pattern) {
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
|
||||
return LocalDateTime.now().format(formatter);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取时间戳格式的字符串(无分隔符)
|
||||
*/
|
||||
public static String getTimestampString() {
|
||||
return getCurrentTimeString("yyyyMMddHHmmssSSS");
|
||||
}
|
||||
/**
|
||||
* 获取当前时间戳(毫秒)
|
||||
*/
|
||||
public static long getCurrentTimestamp() {
|
||||
return System.currentTimeMillis();
|
||||
}
|
||||
|
||||
// 使用示例
|
||||
public static void main(String[] args) {
|
||||
System.out.println("默认格式: " + getCurrentTimeString());
|
||||
System.out.println("时间戳格式: " + getTimestampString());
|
||||
System.out.println("自定义格式: " + getCurrentTimeString("yyyy/MM/dd HH:mm:ss.SSS"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user