From 19c563b3f35a34c2913eacb54004a6f30fd28bb4 Mon Sep 17 00:00:00 2001 From: nanChen Date: Wed, 6 May 2026 17:54:59 +0800 Subject: [PATCH] =?UTF-8?q?1=E3=80=81=E6=96=B0=E5=A2=9E=E9=99=8D=E5=99=AA?= =?UTF-8?q?=E6=97=A5=E5=BF=97=E6=95=B0=E6=8D=AE=E5=81=A5=E5=BA=B7=E7=9B=91?= =?UTF-8?q?=E6=B5=8B=E5=91=8A=E8=AD=A6=202=E3=80=81IP=E8=81=94=E5=8A=A8?= =?UTF-8?q?=E5=B0=81=E7=A6=81=E5=8A=9F=E8=83=BD=E5=AE=8C=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- haobang-security-xdr/syslog-consumer/pom.xml | 7 +- .../com/common/entity/DeviceInterlocking.java | 85 ++++ .../common/entity/DeviceInterlockingCmd.java | 147 ++++++ .../common/entity/DeviceInterlockingLog.java | 90 ++++ .../com/common/entity/WecomNotification.java | 74 +++ .../mapper/DeviceInterlockingCmdMapper.java | 129 ++++++ .../mapper/DeviceInterlockingLogMapper.java | 62 +++ .../mapper/DeviceInterlockingMapper.java | 52 +++ .../mapper/WecomNotificationMapper.java | 47 ++ .../schedule/AlarmHealthCheckScheduler.java | 56 +++ .../service/AlarmHealthCheckService.java | 167 +++++++ .../service/DeviceInterlockingCmdService.java | 84 ++++ .../service/DeviceInterlockingLogService.java | 67 +++ .../service/DeviceInterlockingService.java | 56 +++ .../service/WecomNotificationService.java | 121 +++++ .../com/haobang/config/ApiKeyInterceptor.java | 49 ++ .../java/com/haobang/config/WebMvcConfig.java | 24 + .../controller/InterlockingController.java | 417 +++++++++++++++++ haobang-security-xdr/syslog-serve/pom.xml | 22 +- .../controller/InterlockingController.java | 191 ++++++++ .../haobang/firewall/FirewallApiClient.java | 420 ++++++++++++++++++ .../interlocking/InterlockingApiClient.java | 176 ++++++++ .../interlocking/InterlockingService.java | 412 +++++++++++++++++ 23 files changed, 2953 insertions(+), 2 deletions(-) create mode 100644 haobang-security-xdr/syslog-consumer/src/main/java/com/common/entity/DeviceInterlocking.java create mode 100644 haobang-security-xdr/syslog-consumer/src/main/java/com/common/entity/DeviceInterlockingCmd.java create mode 100644 haobang-security-xdr/syslog-consumer/src/main/java/com/common/entity/DeviceInterlockingLog.java create mode 100644 haobang-security-xdr/syslog-consumer/src/main/java/com/common/entity/WecomNotification.java create mode 100644 haobang-security-xdr/syslog-consumer/src/main/java/com/common/mapper/DeviceInterlockingCmdMapper.java create mode 100644 haobang-security-xdr/syslog-consumer/src/main/java/com/common/mapper/DeviceInterlockingLogMapper.java create mode 100644 haobang-security-xdr/syslog-consumer/src/main/java/com/common/mapper/DeviceInterlockingMapper.java create mode 100644 haobang-security-xdr/syslog-consumer/src/main/java/com/common/mapper/WecomNotificationMapper.java create mode 100644 haobang-security-xdr/syslog-consumer/src/main/java/com/common/schedule/AlarmHealthCheckScheduler.java create mode 100644 haobang-security-xdr/syslog-consumer/src/main/java/com/common/service/AlarmHealthCheckService.java create mode 100644 haobang-security-xdr/syslog-consumer/src/main/java/com/common/service/DeviceInterlockingCmdService.java create mode 100644 haobang-security-xdr/syslog-consumer/src/main/java/com/common/service/DeviceInterlockingLogService.java create mode 100644 haobang-security-xdr/syslog-consumer/src/main/java/com/common/service/DeviceInterlockingService.java create mode 100644 haobang-security-xdr/syslog-consumer/src/main/java/com/common/service/WecomNotificationService.java create mode 100644 haobang-security-xdr/syslog-consumer/src/main/java/com/haobang/config/ApiKeyInterceptor.java create mode 100644 haobang-security-xdr/syslog-consumer/src/main/java/com/haobang/config/WebMvcConfig.java create mode 100644 haobang-security-xdr/syslog-consumer/src/main/java/com/haobang/controller/InterlockingController.java create mode 100644 haobang-security-xdr/syslog-serve/src/main/java/com/haobang/controller/InterlockingController.java create mode 100644 haobang-security-xdr/syslog-serve/src/main/java/com/haobang/firewall/FirewallApiClient.java create mode 100644 haobang-security-xdr/syslog-serve/src/main/java/com/haobang/interlocking/InterlockingApiClient.java create mode 100644 haobang-security-xdr/syslog-serve/src/main/java/com/haobang/interlocking/InterlockingService.java diff --git a/haobang-security-xdr/syslog-consumer/pom.xml b/haobang-security-xdr/syslog-consumer/pom.xml index 16a502f..ff7b6cb 100644 --- a/haobang-security-xdr/syslog-consumer/pom.xml +++ b/haobang-security-xdr/syslog-consumer/pom.xml @@ -197,7 +197,12 @@ DmJdbcDriver18 8.1.2.141 - + + + org.bouncycastle + bcprov-jdk15to18 + 1.78 + diff --git a/haobang-security-xdr/syslog-consumer/src/main/java/com/common/entity/DeviceInterlocking.java b/haobang-security-xdr/syslog-consumer/src/main/java/com/common/entity/DeviceInterlocking.java new file mode 100644 index 0000000..6ce07ee --- /dev/null +++ b/haobang-security-xdr/syslog-consumer/src/main/java/com/common/entity/DeviceInterlocking.java @@ -0,0 +1,85 @@ +package com.common.entity; + +import java.time.OffsetDateTime; + +/** + * 联动设备表实体类(防火墙设备信息) + * 对应表: device_interlocking + */ +public class DeviceInterlocking { + private Long id; + private String deviceName; // 联动安全设备名称(防火墙) + private String vendor; // 厂商 + private String deptId; // 所属组织机构(部门ID) + private Integer banLimit; // 封禁上限 + private String ip; // IP地址 + private String apiUrl; // API地址 + private String tenantId; + private Long createDept; + private Long createBy; + private OffsetDateTime createTime; + private Long updateBy; + private OffsetDateTime updateTime; + private String remark; + private String authUsername; // 用户名 + private String authPassword; // 密码 + + // Getter and Setter + public Long getId() { return id; } + public void setId(Long id) { this.id = id; } + + public String getDeviceName() { return deviceName; } + public void setDeviceName(String deviceName) { this.deviceName = deviceName; } + + public String getVendor() { return vendor; } + public void setVendor(String vendor) { this.vendor = vendor; } + + public String getDeptId() { return deptId; } + public void setDeptId(String deptId) { this.deptId = deptId; } + + public Integer getBanLimit() { return banLimit; } + public void setBanLimit(Integer banLimit) { this.banLimit = banLimit; } + + public String getIp() { return ip; } + public void setIp(String ip) { this.ip = ip; } + + public String getApiUrl() { return apiUrl; } + public void setApiUrl(String apiUrl) { this.apiUrl = apiUrl; } + + public String getTenantId() { return tenantId; } + public void setTenantId(String tenantId) { this.tenantId = tenantId; } + + public Long getCreateDept() { return createDept; } + public void setCreateDept(Long createDept) { this.createDept = createDept; } + + public Long getCreateBy() { return createBy; } + public void setCreateBy(Long createBy) { this.createBy = createBy; } + + public OffsetDateTime getCreateTime() { return createTime; } + public void setCreateTime(OffsetDateTime createTime) { this.createTime = createTime; } + + public Long getUpdateBy() { return updateBy; } + public void setUpdateBy(Long updateBy) { this.updateBy = updateBy; } + + public OffsetDateTime getUpdateTime() { return updateTime; } + public void setUpdateTime(OffsetDateTime updateTime) { this.updateTime = updateTime; } + + public String getRemark() { return remark; } + public void setRemark(String remark) { this.remark = remark; } + + public String getAuthUsername() { return authUsername; } + public void setAuthUsername(String authUsername) { this.authUsername = authUsername; } + + public String getAuthPassword() { return authPassword; } + public void setAuthPassword(String authPassword) { this.authPassword = authPassword; } + + @Override + public String toString() { + return "DeviceInterlocking{" + + "id=" + id + + ", deviceName='" + deviceName + '\'' + + ", vendor='" + vendor + '\'' + + ", ip='" + ip + '\'' + + '}'; + } +} diff --git a/haobang-security-xdr/syslog-consumer/src/main/java/com/common/entity/DeviceInterlockingCmd.java b/haobang-security-xdr/syslog-consumer/src/main/java/com/common/entity/DeviceInterlockingCmd.java new file mode 100644 index 0000000..29343a0 --- /dev/null +++ b/haobang-security-xdr/syslog-consumer/src/main/java/com/common/entity/DeviceInterlockingCmd.java @@ -0,0 +1,147 @@ +package com.common.entity; + +import com.Modules.etl.handler.ArrayIntegerTypeHandler; +import com.Modules.etl.handler.ArrayStringTypeHandler; +import org.apache.ibatis.type.JdbcType; +import org.apache.ibatis.type.TypeHandler; +import org.apache.ibatis.type.ArrayTypeHandler; + +import java.time.OffsetDateTime; +import java.util.Arrays; +import java.util.List; + +/** + * 封禁指令表实体类 + * 对应表: device_interlocking_cmd + */ +public class DeviceInterlockingCmd { + private Long id; + private Long probeId; // 探针ID + private String probeIp; // 探针IP + private Integer[] deviceInterlockingId; // 联动设备ID(数组) + private String[] deviceInterlockingIp; // 联动设备IP(数组) + private String[] banIps; // 封禁IP(数组) + private String banMethod; // 封禁方式(0:手工、1:自动) + private String banType; // 封禁类型(1:白名单、0:黑名单) + private String cmdStatus; // 指令状态(0:未执行、1:已完成、2:执行中) + private Integer banDuration; // 封禁时长(秒,-1表示永久) + private OffsetDateTime createTime; + private OffsetDateTime updateTime; + private String tenantId; + private Long createDept; + private Long createBy; + private Long updateBy; + private String remark; + private Integer banOperationType; // 封禁操作类型(0:新增、1:删除) + + // Getter and Setter + public Long getId() { return id; } + public void setId(Long id) { this.id = id; } + + public Long getProbeId() { return probeId; } + public void setProbeId(Long probeId) { this.probeId = probeId; } + + public String getProbeIp() { return probeIp; } + public void setProbeIp(String probeIp) { this.probeIp = probeIp; } + + public Integer[] getDeviceInterlockingId() { return deviceInterlockingId; } + public void setDeviceInterlockingId(Integer[] deviceInterlockingId) { this.deviceInterlockingId = deviceInterlockingId; } + + public String[] getDeviceInterlockingIp() { return deviceInterlockingIp; } + public void setDeviceInterlockingIp(String[] deviceInterlockingIp) { this.deviceInterlockingIp = deviceInterlockingIp; } + + public String[] getBanIps() { return banIps; } + public void setBanIps(String[] banIps) { this.banIps = banIps; } + + public String getBanMethod() { return banMethod; } + public void setBanMethod(String banMethod) { this.banMethod = banMethod; } + + public String getBanType() { return banType; } + public void setBanType(String banType) { this.banType = banType; } + + public String getCmdStatus() { return cmdStatus; } + public void setCmdStatus(String cmdStatus) { this.cmdStatus = cmdStatus; } + + public Integer getBanDuration() { return banDuration; } + public void setBanDuration(Integer banDuration) { this.banDuration = banDuration; } + + public OffsetDateTime getCreateTime() { return createTime; } + public void setCreateTime(OffsetDateTime createTime) { this.createTime = createTime; } + + public OffsetDateTime getUpdateTime() { return updateTime; } + public void setUpdateTime(OffsetDateTime updateTime) { this.updateTime = updateTime; } + + public String getTenantId() { return tenantId; } + public void setTenantId(String tenantId) { this.tenantId = tenantId; } + + public Long getCreateDept() { return createDept; } + public void setCreateDept(Long createDept) { this.createDept = createDept; } + + public Long getCreateBy() { return createBy; } + public void setCreateBy(Long createBy) { this.createBy = createBy; } + + public Long getUpdateBy() { return updateBy; } + public void setUpdateBy(Long updateBy) { this.updateBy = updateBy; } + + public String getRemark() { return remark; } + public void setRemark(String remark) { this.remark = remark; } + + public Integer getBanOperationType() { return banOperationType; } + public void setBanOperationType(Integer banOperationType) { this.banOperationType = banOperationType; } + + // ========== 便利方法:数组与List互转 ========== + + /** + * 获取设备联动ID列表 + */ + public List getDeviceInterlockingIdList() { + return deviceInterlockingId != null ? Arrays.asList(deviceInterlockingId) : null; + } + + /** + * 设置设备联动ID列表 + */ + public void setDeviceInterlockingIdList(List list) { + this.deviceInterlockingId = list != null ? list.toArray(new Integer[0]) : null; + } + + /** + * 获取设备联动IP列表 + */ + public List getDeviceInterlockingIpList() { + return deviceInterlockingIp != null ? Arrays.asList(deviceInterlockingIp) : null; + } + + /** + * 设置设备联动IP列表 + */ + public void setDeviceInterlockingIpList(List list) { + this.deviceInterlockingIp = list != null ? list.toArray(new String[0]) : null; + } + + /** + * 获取封禁IP列表 + */ + public List getBanIpsList() { + return banIps != null ? Arrays.asList(banIps) : null; + } + + /** + * 设置封禁IP列表 + */ + public void setBanIpsList(List list) { + this.banIps = list != null ? list.toArray(new String[0]) : null; + } + + @Override + public String toString() { + return "DeviceInterlockingCmd{" + + "id=" + id + + ", probeId=" + probeId + + ", probeIp='" + probeIp + '\'' + + ", banType='" + banType + '\'' + + ", cmdStatus='" + cmdStatus + '\'' + + ", banOperationType=" + banOperationType + + '}'; + } +} diff --git a/haobang-security-xdr/syslog-consumer/src/main/java/com/common/entity/DeviceInterlockingLog.java b/haobang-security-xdr/syslog-consumer/src/main/java/com/common/entity/DeviceInterlockingLog.java new file mode 100644 index 0000000..516c829 --- /dev/null +++ b/haobang-security-xdr/syslog-consumer/src/main/java/com/common/entity/DeviceInterlockingLog.java @@ -0,0 +1,90 @@ +package com.common.entity; + +import java.time.OffsetDateTime; + +/** + * 封禁记录表实体类 + * 对应表: device_interlocking_log + */ +public class DeviceInterlockingLog { + private Long id; + private Long deviceInterlockingCmdId; // 封禁指令ID + private Long deviceInterlockingId; // 封禁设备ID + private String banIp; // 封禁IP地址 + private String deviceName; // 封禁设备名称 + private OffsetDateTime banTime; // 封禁时间 + private String banMethod; // 封禁方式(0.人工、1.自动化封禁) + private Integer banResult; // 联动结果(成功:1、失败:0) + private String tenantId; + private Long createDept; + private Long createBy; + private OffsetDateTime createTime; + private Long updateBy; + private OffsetDateTime updateTime; + private String remark; + private String respBody; // 响应body + private String reqBody; // 请求body + + // Getter and Setter + public Long getId() { return id; } + public void setId(Long id) { this.id = id; } + + public Long getDeviceInterlockingCmdId() { return deviceInterlockingCmdId; } + public void setDeviceInterlockingCmdId(Long deviceInterlockingCmdId) { this.deviceInterlockingCmdId = deviceInterlockingCmdId; } + + public Long getDeviceInterlockingId() { return deviceInterlockingId; } + public void setDeviceInterlockingId(Long deviceInterlockingId) { this.deviceInterlockingId = deviceInterlockingId; } + + public String getBanIp() { return banIp; } + public void setBanIp(String banIp) { this.banIp = banIp; } + + public String getDeviceName() { return deviceName; } + public void setDeviceName(String deviceName) { this.deviceName = deviceName; } + + public OffsetDateTime getBanTime() { return banTime; } + public void setBanTime(OffsetDateTime banTime) { this.banTime = banTime; } + + public String getBanMethod() { return banMethod; } + public void setBanMethod(String banMethod) { this.banMethod = banMethod; } + + public Integer getBanResult() { return banResult; } + public void setBanResult(Integer banResult) { this.banResult = banResult; } + + public String getTenantId() { return tenantId; } + public void setTenantId(String tenantId) { this.tenantId = tenantId; } + + public Long getCreateDept() { return createDept; } + public void setCreateDept(Long createDept) { this.createDept = createDept; } + + public Long getCreateBy() { return createBy; } + public void setCreateBy(Long createBy) { this.createBy = createBy; } + + public OffsetDateTime getCreateTime() { return createTime; } + public void setCreateTime(OffsetDateTime createTime) { this.createTime = createTime; } + + public Long getUpdateBy() { return updateBy; } + public void setUpdateBy(Long updateBy) { this.updateBy = updateBy; } + + public OffsetDateTime getUpdateTime() { return updateTime; } + public void setUpdateTime(OffsetDateTime updateTime) { this.updateTime = updateTime; } + + public String getRemark() { return remark; } + public void setRemark(String remark) { this.remark = remark; } + + public String getRespBody() { return respBody; } + public void setRespBody(String respBody) { this.respBody = respBody; } + + public String getReqBody() { return reqBody; } + public void setReqBody(String reqBody) { this.reqBody = reqBody; } + + @Override + public String toString() { + return "DeviceInterlockingLog{" + + "id=" + id + + ", deviceInterlockingCmdId=" + deviceInterlockingCmdId + + ", banIp='" + banIp + '\'' + + ", deviceName='" + deviceName + '\'' + + ", banResult=" + banResult + + '}'; + } +} diff --git a/haobang-security-xdr/syslog-consumer/src/main/java/com/common/entity/WecomNotification.java b/haobang-security-xdr/syslog-consumer/src/main/java/com/common/entity/WecomNotification.java new file mode 100644 index 0000000..4167c88 --- /dev/null +++ b/haobang-security-xdr/syslog-consumer/src/main/java/com/common/entity/WecomNotification.java @@ -0,0 +1,74 @@ +package com.common.entity; + +import lombok.Data; +import java.time.LocalDateTime; + +/** + * 微信通知实体类 + * 对应表: wecom_notification + */ +@Data +public class WecomNotification { + + /** 企微通知id */ + private Long wecomNotificationId; + + /** 用户信息 */ + private String userId; + + /** 通知名称 */ + private String wecomNotificationName; + + /** 通知IP */ + private String wecomNotificationIp; + + /** 通知类型 */ + private String wecomNotificationType; + + /** 通知等级 */ + private String wecomNotificationLevel; + + /** 通知内容 */ + private String wecomNotificationContent; + + /** 告警通知时间 */ + private LocalDateTime wecomNotificationTime; + + /** 租户编号 */ + private String tenantId; + + /** 创建部门 */ + private Long createDept; + + /** 创建者 */ + private Long createBy; + + /** 创建时间 */ + private LocalDateTime createTime; + + /** 更新者 */ + private Long updateBy; + + /** 更新时间 */ + private LocalDateTime updateTime; + + /** 备注 */ + private String remark; + + /** 通知状态(0:已发送, 1:未发送) */ + private String wecomNotificationStatus; + + public WecomNotification() { + } + + public WecomNotification(String wecomNotificationName, String wecomNotificationType, + String wecomNotificationLevel, String wecomNotificationContent) { + this.wecomNotificationName = wecomNotificationName; + this.wecomNotificationType = wecomNotificationType; + this.wecomNotificationLevel = wecomNotificationLevel; + this.wecomNotificationContent = wecomNotificationContent; + this.wecomNotificationTime = LocalDateTime.now(); + this.wecomNotificationStatus = "1"; // 默认未发送 + this.createTime = LocalDateTime.now(); + } +} diff --git a/haobang-security-xdr/syslog-consumer/src/main/java/com/common/mapper/DeviceInterlockingCmdMapper.java b/haobang-security-xdr/syslog-consumer/src/main/java/com/common/mapper/DeviceInterlockingCmdMapper.java new file mode 100644 index 0000000..57bfe7e --- /dev/null +++ b/haobang-security-xdr/syslog-consumer/src/main/java/com/common/mapper/DeviceInterlockingCmdMapper.java @@ -0,0 +1,129 @@ +package com.common.mapper; + +import com.Modules.etl.handler.ArrayIntegerTypeHandler; +import com.Modules.etl.handler.ArrayStringTypeHandler; +import com.common.entity.DeviceInterlockingCmd; +import org.apache.ibatis.annotations.*; +import org.apache.ibatis.type.JdbcType; + +import java.util.List; + +@Mapper +public interface DeviceInterlockingCmdMapper { + + /** + * 根据ID查询 + */ + @Select("SELECT * FROM device_interlocking_cmd WHERE id = #{id}") + @Results({ + @Result(column = "device_interlocking_id", property = "deviceInterlockingId", + typeHandler = ArrayIntegerTypeHandler.class), + @Result(column = "device_interlocking_ip", property = "deviceInterlockingIp", + typeHandler = ArrayStringTypeHandler.class), + @Result(column = "ban_ips", property = "banIps", + typeHandler = ArrayStringTypeHandler.class) + }) + DeviceInterlockingCmd selectById(Long id); + + /** + * 查询待执行的指令 + * @param probeId 探针ID + * @return 待执行指令列表 + */ + @Select("SELECT * FROM device_interlocking_cmd WHERE probe_id = #{probeId} AND cmd_status = '0' ORDER BY create_time ASC") + @Results({ + @Result(column = "device_interlocking_id", property = "deviceInterlockingId", + typeHandler = ArrayIntegerTypeHandler.class), + @Result(column = "device_interlocking_ip", property = "deviceInterlockingIp", + typeHandler = ArrayStringTypeHandler.class), + @Result(column = "ban_ips", property = "banIps", + typeHandler = ArrayStringTypeHandler.class) + }) + List selectPendingCommands(@Param("probeId") Long probeId); + + /** + * 根据探针ID和状态查询 + */ + @Select("SELECT * FROM device_interlocking_cmd WHERE probe_id = #{probeId} AND cmd_status = #{cmdStatus}") + @Results({ + @Result(column = "device_interlocking_id", property = "deviceInterlockingId", + typeHandler = ArrayIntegerTypeHandler.class), + @Result(column = "device_interlocking_ip", property = "deviceInterlockingIp", + typeHandler = ArrayStringTypeHandler.class), + @Result(column = "ban_ips", property = "banIps", + typeHandler = ArrayStringTypeHandler.class) + }) + List selectByProbeIdAndStatus(@Param("probeId") Long probeId, @Param("cmdStatus") String cmdStatus); + + /** + * 插入指令 + */ + @Insert("INSERT INTO device_interlocking_cmd (probe_id, probe_ip, device_interlocking_id, device_interlocking_ip, " + + "ban_ips, ban_method, ban_type, cmd_status, ban_duration, create_time, update_time, " + + "tenant_id, create_dept, create_by, remark, ban_operation_type) " + + "VALUES (#{probeId}, #{probeIp}, ARRAY[:ids], ARRAY[:ips], ARRAY[:banIps], " + + "#{banMethod}, #{banType}, #{cmdStatus}, #{banDuration}, NOW(), NOW(), " + + "#{tenantId}, #{createDept}, #{createBy}, #{remark}, #{banOperationType})") + @Options(useGeneratedKeys = true, keyProperty = "id") + int insert(DeviceInterlockingCmd cmd); + + /** + * 更新指令状态 + * @param id 指令ID + * @param cmdStatus 新状态 + * @return 影响行数 + */ + @Update("UPDATE device_interlocking_cmd SET cmd_status = #{cmdStatus}, update_time = NOW() WHERE id = #{id}") + int updateStatus(@Param("id") Long id, @Param("cmdStatus") String cmdStatus); + + /** + * 更新指令状态为执行中 + */ + @Update("UPDATE device_interlocking_cmd SET cmd_status = '2', update_time = NOW() WHERE id = #{id}") + int updateStatusToExecuting(@Param("id") Long id); + + /** + * 更新指令状态为执行完成 + */ + @Update("UPDATE device_interlocking_cmd SET cmd_status = '1', update_time = NOW() WHERE id = #{id}") + int updateStatusToCompleted(@Param("id") Long id); + + /** + * 更新指令状态为执行失败 + */ + @Update("UPDATE device_interlocking_cmd SET cmd_status = '3', update_time = NOW() WHERE id = #{id}") + int updateStatusToFailed(@Param("id") Long id); + + /** + * 根据多个ID查询 + */ + @Select("") + @Results({ + @Result(column = "device_interlocking_id", property = "deviceInterlockingId", + typeHandler = ArrayIntegerTypeHandler.class), + @Result(column = "device_interlocking_ip", property = "deviceInterlockingIp", + typeHandler = ArrayStringTypeHandler.class), + @Result(column = "ban_ips", property = "banIps", + typeHandler = ArrayStringTypeHandler.class) + }) + List selectByIds(@Param("ids") List ids); + + /** + * 查询所有指令 + */ + @Select("SELECT * FROM device_interlocking_cmd ORDER BY create_time DESC") + @Results({ + @Result(column = "device_interlocking_id", property = "deviceInterlockingId", + typeHandler = ArrayIntegerTypeHandler.class), + @Result(column = "device_interlocking_ip", property = "deviceInterlockingIp", + typeHandler = ArrayStringTypeHandler.class), + @Result(column = "ban_ips", property = "banIps", + typeHandler = ArrayStringTypeHandler.class) + }) + List selectAll(); +} diff --git a/haobang-security-xdr/syslog-consumer/src/main/java/com/common/mapper/DeviceInterlockingLogMapper.java b/haobang-security-xdr/syslog-consumer/src/main/java/com/common/mapper/DeviceInterlockingLogMapper.java new file mode 100644 index 0000000..ceedf13 --- /dev/null +++ b/haobang-security-xdr/syslog-consumer/src/main/java/com/common/mapper/DeviceInterlockingLogMapper.java @@ -0,0 +1,62 @@ +package com.common.mapper; + +import com.common.entity.DeviceInterlockingLog; +import org.apache.ibatis.annotations.*; +import java.util.List; + +@Mapper +public interface DeviceInterlockingLogMapper { + + /** + * 根据ID查询 + */ + @Select("SELECT * FROM device_interlocking_log WHERE id = #{id}") + DeviceInterlockingLog selectById(Long id); + + /** + * 根据指令ID查询 + */ + @Select("SELECT * FROM device_interlocking_log WHERE device_interlocking_cmd_id = #{cmdId}") + List selectByCmdId(@Param("cmdId") Long cmdId); + + /** + * 插入记录 + */ + @Insert("INSERT INTO device_interlocking_log (device_interlocking_cmd_id, device_interlocking_id, ban_ip, " + + "device_name, ban_time, ban_method, ban_result, tenant_id, create_dept, create_by, " + + "create_time, remark, resp_body, req_body) " + + "VALUES (#{deviceInterlockingCmdId}, #{deviceInterlockingId}, #{banIp}, #{deviceName}, " + + "#{banTime}, #{banMethod}, #{banResult}, #{tenantId}, #{createDept}, #{createBy}, " + + "NOW(), #{remark}, #{respBody}, #{reqBody})") + @Options(useGeneratedKeys = true, keyProperty = "id") + int insert(DeviceInterlockingLog log); + + /** + * 批量插入记录 + */ + @Insert("") + int batchInsert(@Param("logs") List logs); + + /** + * 查询所有记录 + */ + @Select("SELECT * FROM device_interlocking_log ORDER BY create_time DESC") + List selectAll(); + + /** + * 统计某指令的成功/失败数量 + */ + @Select("SELECT ban_result, COUNT(*) as count FROM device_interlocking_log " + + "WHERE device_interlocking_cmd_id = #{cmdId} GROUP BY ban_result") + List> countByCmdId(@Param("cmdId") Long cmdId); +} diff --git a/haobang-security-xdr/syslog-consumer/src/main/java/com/common/mapper/DeviceInterlockingMapper.java b/haobang-security-xdr/syslog-consumer/src/main/java/com/common/mapper/DeviceInterlockingMapper.java new file mode 100644 index 0000000..a5beb84 --- /dev/null +++ b/haobang-security-xdr/syslog-consumer/src/main/java/com/common/mapper/DeviceInterlockingMapper.java @@ -0,0 +1,52 @@ +package com.common.mapper; + +import com.common.entity.DeviceInterlocking; +import org.apache.ibatis.annotations.*; +import java.util.List; + +@Mapper +public interface DeviceInterlockingMapper { + + /** + * 根据ID查询 + */ + @Select("SELECT * FROM device_interlocking WHERE id = #{id}") + DeviceInterlocking selectById(Long id); + + /** + * 根据部门ID查询联动设备 + */ + @Select("SELECT * FROM device_interlocking WHERE dept_id = #{deptId}") + List selectByDeptId(@Param("deptId") String deptId); + + /** + * 根据厂商查询 + */ + @Select("SELECT * FROM device_interlocking WHERE vendor = #{vendor}") + List selectByVendor(@Param("vendor") String vendor); + + /** + * 插入记录 + */ + @Insert("INSERT INTO device_interlocking (device_name, vendor, dept_id, ban_limit, ip, api_url, " + + "tenant_id, create_dept, create_by, create_time, update_time, remark, auth_username, auth_password) " + + "VALUES (#{deviceName}, #{vendor}, #{deptId}, #{banLimit}, #{ip}, #{apiUrl}, " + + "#{tenantId}, #{createDept}, #{createBy}, NOW(), NOW(), #{remark}, #{authUsername}, #{authPassword})") + @Options(useGeneratedKeys = true, keyProperty = "id") + int insert(DeviceInterlocking device); + + /** + * 更新记录 + */ + @Update("UPDATE device_interlocking SET device_name = #{deviceName}, vendor = #{vendor}, " + + "dept_id = #{deptId}, ban_limit = #{banLimit}, ip = #{ip}, api_url = #{apiUrl}, " + + "update_time = NOW(), remark = #{remark}, auth_username = #{authUsername}, " + + "auth_password = #{authPassword} WHERE id = #{id}") + int update(DeviceInterlocking device); + + /** + * 查询所有记录 + */ + @Select("SELECT * FROM device_interlocking ORDER BY create_time DESC") + List selectAll(); +} diff --git a/haobang-security-xdr/syslog-consumer/src/main/java/com/common/mapper/WecomNotificationMapper.java b/haobang-security-xdr/syslog-consumer/src/main/java/com/common/mapper/WecomNotificationMapper.java new file mode 100644 index 0000000..abd6f61 --- /dev/null +++ b/haobang-security-xdr/syslog-consumer/src/main/java/com/common/mapper/WecomNotificationMapper.java @@ -0,0 +1,47 @@ +package com.common.mapper; + +import com.common.entity.WecomNotification; +import org.apache.ibatis.annotations.*; + +/** + * 微信通知 Mapper 接口 + */ +@Mapper +public interface WecomNotificationMapper { + + /** + * 插入单条记录(使用序列生成ID) + */ + @Insert("INSERT INTO wecom_notification (" + + "wecom_notification_id, user_id, wecom_notification_name, wecom_notification_ip, " + + "wecom_notification_type, wecom_notification_level, wecom_notification_content, " + + "wecom_notification_time, tenant_id, create_dept, create_by, create_time, " + + "update_by, update_time, remark, wecom_notification_status" + + ") VALUES (" + + "nextval('seq_wecom_notification'), #{userId}, #{wecomNotificationName}, #{wecomNotificationIp}, " + + "#{wecomNotificationType}, #{wecomNotificationLevel}, #{wecomNotificationContent}, " + + "#{wecomNotificationTime}, #{tenantId}, #{createDept}, #{createBy}, #{createTime}, " + + "#{updateBy}, #{updateTime}, #{remark}, #{wecomNotificationStatus}" + + ")") + @SelectKey(statement = "SELECT currval('seq_wecom_notification')", keyProperty = "wecomNotificationId", resultType = Long.class, before = false) + int insert(WecomNotification notification); + + /** + * 根据通知ID查询 + */ + @Select("SELECT * FROM wecom_notification WHERE wecom_notification_id = #{wecomNotificationId}") + WecomNotification selectById(@Param("wecomNotificationId") Long wecomNotificationId); + + /** + * 查询未发送的通知列表 + */ + @Select("SELECT * FROM wecom_notification WHERE wecom_notification_status = '1' ORDER BY create_time DESC") + java.util.List selectUnsentNotifications(); + + /** + * 更新通知状态 + */ + @Update("UPDATE wecom_notification SET wecom_notification_status = #{status}, update_time = NOW() " + + "WHERE wecom_notification_id = #{wecomNotificationId}") + int updateStatus(@Param("wecomNotificationId") Long wecomNotificationId, @Param("status") String status); +} diff --git a/haobang-security-xdr/syslog-consumer/src/main/java/com/common/schedule/AlarmHealthCheckScheduler.java b/haobang-security-xdr/syslog-consumer/src/main/java/com/common/schedule/AlarmHealthCheckScheduler.java new file mode 100644 index 0000000..0d80291 --- /dev/null +++ b/haobang-security-xdr/syslog-consumer/src/main/java/com/common/schedule/AlarmHealthCheckScheduler.java @@ -0,0 +1,56 @@ +package com.common.schedule; + +import com.common.service.AlarmHealthCheckService; +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; + +/** + * 告警健康检查定时任务 + * 每5分钟巡检告警表和告警日志表 + */ +@Component +public class AlarmHealthCheckScheduler { + + private static final Logger logger = LoggerFactory.getLogger(AlarmHealthCheckScheduler.class); + + @Autowired + private AlarmHealthCheckService alarmHealthCheckService; + + /** + * 每5分钟执行一次告警健康检查 + * 巡检告警表 alarm 和告警日志表 alarm_visit + */ + @Scheduled(cron = "0 */5 * * * ?") + public void scheduledHealthCheck() { + logger.info("========== 开始执行告警健康检查 =========="); + long startTime = System.currentTimeMillis(); + + try { + boolean hasAlarm = alarmHealthCheckService.performHealthCheck(); + + long elapsedTime = System.currentTimeMillis() - startTime; + + if (hasAlarm) { + logger.warn("告警健康检查完成, 发现异常数据, 耗时: {}ms", elapsedTime); + } else { + logger.info("告警健康检查完成, 所有表数据正常, 耗时: {}ms", elapsedTime); + } + } catch (Exception e) { + logger.error("告警健康检查执行异常: {}", e.getMessage(), e); + } + + logger.info("========== 告警健康检查任务结束 =========="); + } + + /** + * 手动触发健康检查(供其他服务调用) + * @return 是否有异常 + */ + public boolean manualHealthCheck() { + logger.info("手动触发告警健康检查"); + return alarmHealthCheckService.performHealthCheck(); + } +} diff --git a/haobang-security-xdr/syslog-consumer/src/main/java/com/common/service/AlarmHealthCheckService.java b/haobang-security-xdr/syslog-consumer/src/main/java/com/common/service/AlarmHealthCheckService.java new file mode 100644 index 0000000..8bd999a --- /dev/null +++ b/haobang-security-xdr/syslog-consumer/src/main/java/com/common/service/AlarmHealthCheckService.java @@ -0,0 +1,167 @@ +package com.common.service; + +import com.common.entity.WecomNotification; +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.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +/** + * 告警健康检查服务 + * 巡检告警表和告警日志表是否有数据 + */ +@Service +public class AlarmHealthCheckService { + + private static final Logger logger = LoggerFactory.getLogger(AlarmHealthCheckService.class); + + @Autowired + private JdbcTemplate jdbcTemplate; + + @Autowired + private WecomNotificationService wecomNotificationService; + + @Value("${alarm.health-check.alarm-hours:2}") + private int alarmHoursThreshold; + + @Value("${alarm.health-check.alarm-visit-hours:4}") + private int alarmVisitHoursThreshold; + + private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd"); + + /** + * 执行告警健康检查 + * @return 是否有异常 + */ + public boolean performHealthCheck() { + boolean hasAlarm = false; + + // 1. 检查告警表 alarm + boolean alarmTableNormal = checkAlarmTable(); + if (!alarmTableNormal) { + hasAlarm = true; + generateAlarmNotification("alarm", alarmHoursThreshold); + } + + // 2. 检查告警日志表 alarm_visit + boolean alarmVisitTableNormal = checkAlarmVisitTable(); + if (!alarmVisitTableNormal) { + hasAlarm = true; + generateAlarmNotification("alarm_visit", alarmVisitHoursThreshold); + } + + return hasAlarm; + } + + /** + * 检查告警表 alarm_yyyyMMdd 最近是否无数据 + * @return true=正常有数据, false=异常无数据 + */ + public boolean checkAlarmTable() { + String partitionDate = LocalDateTime.now().format(DATE_FORMATTER); + String tableName = "alarm_" + partitionDate; + + String sql = "SELECT COUNT(*) FROM " + tableName + " WHERE created_at >= NOW() - INTERVAL '" + alarmHoursThreshold + " hours'"; + + try { + Long count = jdbcTemplate.queryForObject(sql, Long.class); + boolean hasData = count != null && count > 0; + + logger.info("告警表 {} 健康检查: {}小时内数据量={}, 状态={}", + tableName, alarmHoursThreshold, count, hasData ? "正常" : "异常"); + + return hasData; + } catch (Exception e) { + logger.error("检查告警表 {} 失败: {}", tableName, e.getMessage()); + // 表不存在或查询失败,认为异常 + return false; + } + } + + /** + * 检查告警日志表 alarm_visit_yyyyMMdd 最近是否无数据 + * @return true=正常有数据, false=异常无数据 + */ + public boolean checkAlarmVisitTable() { + String partitionDate = LocalDateTime.now().format(DATE_FORMATTER); + String tableName = "alarm_visit_" + partitionDate; + + String sql = "SELECT COUNT(*) FROM " + tableName + " WHERE created_at >= NOW() - INTERVAL '" + alarmVisitHoursThreshold + " hours'"; + + try { + Long count = jdbcTemplate.queryForObject(sql, Long.class); + boolean hasData = count != null && count > 0; + + logger.info("告警日志表 {} 健康检查: {}小时内数据量={}, 状态={}", + tableName, alarmVisitHoursThreshold, count, hasData ? "正常" : "异常"); + + return hasData; + } catch (Exception e) { + logger.error("检查告警日志表 {} 失败: {}", tableName, e.getMessage()); + // 表不存在或查询失败,认为异常 + return false; + } + } + + /** + * 生成告警通知并插入微信通知表 + * + * @param tableType 表类型(alarm 或 alarm_visit) + * @param hoursThreshold 小时阈值 + */ + public void generateAlarmNotification(String tableType, int hoursThreshold) { + String tableName = tableType + "_" + LocalDateTime.now().format(DATE_FORMATTER); + String content = String.format( + "【数据巡检告警】%n" + + "表名: %s%n" + + "告警时间: %s%n" + + "告警描述: 最近%d小时内无新数据%n" + + "建议: 请检查数据采集服务是否正常运行%n" + + "状态: 需要人工介入检查", + tableName, + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")), + hoursThreshold + ); + + WecomNotification notification = wecomNotificationService.sendAlert( + "数据巡检告警-" + tableName, + "alarm_health_check", + "3", // 高危 + content, + null + ); + + logger.warn("生成告警通知: 表={}, 通知ID={}", tableName, notification.getWecomNotificationId()); + } + + /** + * 微信通知告警数据插入函数方法 + * 提供给其他模块在运行有异常的情况下调用 + * + * @param alertName 告警名称 + * @param alertType 告警类型 + * @param alertLevel 告警等级(1:低, 2:中, 3:高, 4:紧急) + * @param alertContent 告警内容 + * @return 通知实体 + */ + public WecomNotification insertWecomAlert(String alertName, String alertType, + String alertLevel, String alertContent) { + return wecomNotificationService.sendAlert(alertName, alertType, alertLevel, alertContent, null); + } + + /** + * 快速插入告警通知(简化版) + * + * @param alertType 告警类型 + * @param alertContent 告警内容 + * @return 通知实体 + */ + public WecomNotification insertWecomAlert(String alertType, String alertContent) { + return wecomNotificationService.sendAlert(alertType, alertContent); + } +} diff --git a/haobang-security-xdr/syslog-consumer/src/main/java/com/common/service/DeviceInterlockingCmdService.java b/haobang-security-xdr/syslog-consumer/src/main/java/com/common/service/DeviceInterlockingCmdService.java new file mode 100644 index 0000000..48e117d --- /dev/null +++ b/haobang-security-xdr/syslog-consumer/src/main/java/com/common/service/DeviceInterlockingCmdService.java @@ -0,0 +1,84 @@ +package com.common.service; + +import com.common.entity.DeviceInterlockingCmd; +import com.common.mapper.DeviceInterlockingCmdMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import java.util.List; + +@Service +public class DeviceInterlockingCmdService { + + @Autowired + private DeviceInterlockingCmdMapper cmdMapper; + + /** + * 根据ID查询 + */ + public DeviceInterlockingCmd selectById(Long id) { + return cmdMapper.selectById(id); + } + + /** + * 查询待执行的指令 + */ + public List selectPendingCommands(Long probeId) { + return cmdMapper.selectPendingCommands(probeId); + } + + /** + * 根据探针ID和状态查询 + */ + public List selectByProbeIdAndStatus(Long probeId, String cmdStatus) { + return cmdMapper.selectByProbeIdAndStatus(probeId, cmdStatus); + } + + /** + * 插入指令 + */ + public int insert(DeviceInterlockingCmd cmd) { + return cmdMapper.insert(cmd); + } + + /** + * 更新指令状态 + */ + public int updateStatus(Long id, String cmdStatus) { + return cmdMapper.updateStatus(id, cmdStatus); + } + + /** + * 更新指令状态为执行中 + */ + public int updateStatusToExecuting(Long id) { + return cmdMapper.updateStatusToExecuting(id); + } + + /** + * 更新指令状态为执行完成 + */ + public int updateStatusToCompleted(Long id) { + return cmdMapper.updateStatusToCompleted(id); + } + + /** + * 更新指令状态为执行失败 + */ + public int updateStatusToFailed(Long id) { + return cmdMapper.updateStatusToFailed(id); + } + + /** + * 查询所有指令 + */ + public List selectAll() { + return cmdMapper.selectAll(); + } + + /** + * 根据多个ID查询 + */ + public List selectByIds(List ids) { + return cmdMapper.selectByIds(ids); + } +} diff --git a/haobang-security-xdr/syslog-consumer/src/main/java/com/common/service/DeviceInterlockingLogService.java b/haobang-security-xdr/syslog-consumer/src/main/java/com/common/service/DeviceInterlockingLogService.java new file mode 100644 index 0000000..b251092 --- /dev/null +++ b/haobang-security-xdr/syslog-consumer/src/main/java/com/common/service/DeviceInterlockingLogService.java @@ -0,0 +1,67 @@ +package com.common.service; + +import com.common.entity.DeviceInterlockingLog; +import com.common.mapper.DeviceInterlockingLogMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import java.time.OffsetDateTime; +import java.util.List; + +@Service +public class DeviceInterlockingLogService { + + @Autowired + private DeviceInterlockingLogMapper logMapper; + + /** + * 根据ID查询 + */ + public DeviceInterlockingLog selectById(Long id) { + return logMapper.selectById(id); + } + + /** + * 根据指令ID查询 + */ + public List selectByCmdId(Long cmdId) { + return logMapper.selectByCmdId(cmdId); + } + + /** + * 插入记录 + */ + public int insert(DeviceInterlockingLog log) { + if (log.getBanTime() == null) { + log.setBanTime(OffsetDateTime.now()); + } + return logMapper.insert(log); + } + + /** + * 批量插入记录 + */ + public int batchInsert(List logs) { + if (logs != null && !logs.isEmpty()) { + for (DeviceInterlockingLog log : logs) { + if (log.getBanTime() == null) { + log.setBanTime(OffsetDateTime.now()); + } + } + } + return logMapper.batchInsert(logs); + } + + /** + * 查询所有记录 + */ + public List selectAll() { + return logMapper.selectAll(); + } + + /** + * 统计某指令的成功/失败数量 + */ + public List> countByCmdId(Long cmdId) { + return logMapper.countByCmdId(cmdId); + } +} diff --git a/haobang-security-xdr/syslog-consumer/src/main/java/com/common/service/DeviceInterlockingService.java b/haobang-security-xdr/syslog-consumer/src/main/java/com/common/service/DeviceInterlockingService.java new file mode 100644 index 0000000..b6183c2 --- /dev/null +++ b/haobang-security-xdr/syslog-consumer/src/main/java/com/common/service/DeviceInterlockingService.java @@ -0,0 +1,56 @@ +package com.common.service; + +import com.common.entity.DeviceInterlocking; +import com.common.mapper.DeviceInterlockingMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import java.util.List; + +@Service +public class DeviceInterlockingService { + + @Autowired + private DeviceInterlockingMapper interlockingMapper; + + /** + * 根据ID查询 + */ + public DeviceInterlocking selectById(Long id) { + return interlockingMapper.selectById(id); + } + + /** + * 根据部门ID查询联动设备 + */ + public List selectByDeptId(String deptId) { + return interlockingMapper.selectByDeptId(deptId); + } + + /** + * 根据厂商查询 + */ + public List selectByVendor(String vendor) { + return interlockingMapper.selectByVendor(vendor); + } + + /** + * 插入记录 + */ + public int insert(DeviceInterlocking device) { + return interlockingMapper.insert(device); + } + + /** + * 更新记录 + */ + public int update(DeviceInterlocking device) { + return interlockingMapper.update(device); + } + + /** + * 查询所有记录 + */ + public List selectAll() { + return interlockingMapper.selectAll(); + } +} diff --git a/haobang-security-xdr/syslog-consumer/src/main/java/com/common/service/WecomNotificationService.java b/haobang-security-xdr/syslog-consumer/src/main/java/com/common/service/WecomNotificationService.java new file mode 100644 index 0000000..8ab3ae0 --- /dev/null +++ b/haobang-security-xdr/syslog-consumer/src/main/java/com/common/service/WecomNotificationService.java @@ -0,0 +1,121 @@ +package com.common.service; + +import com.common.entity.WecomNotification; +import com.common.mapper.WecomNotificationMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; + +/** + * 微信通知服务类 + */ +@Service +public class WecomNotificationService { + + private static final Logger logger = LoggerFactory.getLogger(WecomNotificationService.class); + + @Autowired + private WecomNotificationMapper wecomNotificationMapper; + + /** + * 插入微信通知 + * + * @param notification 通知实体 + * @return 影响的行数 + */ + public int insert(WecomNotification notification) { + try { + if (notification.getCreateTime() == null) { + notification.setCreateTime(LocalDateTime.now()); + } + if (notification.getWecomNotificationTime() == null) { + notification.setWecomNotificationTime(LocalDateTime.now()); + } + if (notification.getWecomNotificationStatus() == null) { + notification.setWecomNotificationStatus("1"); // 默认未发送 + } + if (notification.getTenantId() == null) { + notification.setTenantId("000000"); + } + int rows = wecomNotificationMapper.insert(notification); + logger.info("插入微信通知成功, ID: {}, 类型: {}, 内容: {}", + notification.getWecomNotificationId(), + notification.getWecomNotificationType(), + notification.getWecomNotificationName()); + return rows; + } catch (Exception e) { + logger.error("插入微信通知失败: {}", e.getMessage(), e); + throw e; + } + } + + /** + * 根据ID查询 + */ + public WecomNotification selectById(Long id) { + return wecomNotificationMapper.selectById(id); + } + + /** + * 查询未发送的通知列表 + */ + public java.util.List selectUnsentNotifications() { + return wecomNotificationMapper.selectUnsentNotifications(); + } + + /** + * 更新通知状态 + * + * @param id 通知ID + * @param status 状态(0:已发送, 1:未发送) + * @return 影响的行数 + */ + public int updateStatus(Long id, String status) { + return wecomNotificationMapper.updateStatus(id, status); + } + + /** + * 发送微信通知告警 + * 提供给其他模块在运行异常时调用 + * + * @param name 通知名称 + * @param type 通知类型(如:alarm_health_check) + * @param level 通知等级(1:低, 2:中, 3:高, 4:紧急) + * @param content 通知内容 + * @param ip 关联IP(可选) + * @return 通知实体 + */ + public WecomNotification sendAlert(String name, String type, String level, String content, String ip) { + WecomNotification notification = new WecomNotification(); + notification.setWecomNotificationName(name); + notification.setWecomNotificationType(type); + notification.setWecomNotificationLevel(level); + notification.setWecomNotificationContent(content); + notification.setWecomNotificationIp(ip); + notification.setWecomNotificationTime(LocalDateTime.now()); + notification.setWecomNotificationStatus("1"); // 未发送 + notification.setTenantId("000000"); + notification.setCreateTime(LocalDateTime.now()); + + insert(notification); + + logger.warn("发送微信告警通知 - 名称: {}, 类型: {}, 等级: {}, 内容: {}", + name, type, level, content); + + return notification; + } + + /** + * 发送告警通知(简化版本) + * + * @param alertType 告警类型 + * @param alertContent 告警内容 + * @return 通知实体 + */ + public WecomNotification sendAlert(String alertType, String alertContent) { + return sendAlert(alertType, alertType, "3", alertContent, null); + } +} \ No newline at end of file diff --git a/haobang-security-xdr/syslog-consumer/src/main/java/com/haobang/config/ApiKeyInterceptor.java b/haobang-security-xdr/syslog-consumer/src/main/java/com/haobang/config/ApiKeyInterceptor.java new file mode 100644 index 0000000..f5886e0 --- /dev/null +++ b/haobang-security-xdr/syslog-consumer/src/main/java/com/haobang/config/ApiKeyInterceptor.java @@ -0,0 +1,49 @@ +package com.haobang.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * API-KEY认证拦截器 + */ +@Component +public class ApiKeyInterceptor implements HandlerInterceptor { + + @Value("${interlocking.api-key:}") + private String configuredApiKey; + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + // 放行CORS预检请求 + if ("OPTIONS".equalsIgnoreCase(request.getMethod())) { + return true; + } + + String requestApiKey = request.getHeader("X-API-KEY"); + + // 如果配置了API-KEY,则进行校验 + if (configuredApiKey != null && !configuredApiKey.isEmpty()) { + if (requestApiKey == null || requestApiKey.isEmpty()) { + sendUnauthorized(response, "API-KEY header is missing"); + return false; + } + + if (!configuredApiKey.equals(requestApiKey)) { + sendUnauthorized(response, "Invalid API-KEY"); + return false; + } + } + + return true; + } + + private void sendUnauthorized(HttpServletResponse response, String message) throws Exception { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.setContentType("application/json;charset=UTF-8"); + response.getWriter().write("{\"code\":401,\"message\":\"" + message + "\"}"); + } +} diff --git a/haobang-security-xdr/syslog-consumer/src/main/java/com/haobang/config/WebMvcConfig.java b/haobang-security-xdr/syslog-consumer/src/main/java/com/haobang/config/WebMvcConfig.java new file mode 100644 index 0000000..d97b1a9 --- /dev/null +++ b/haobang-security-xdr/syslog-consumer/src/main/java/com/haobang/config/WebMvcConfig.java @@ -0,0 +1,24 @@ +package com.haobang.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * Web MVC配置类 + */ +@Configuration +public class WebMvcConfig implements WebMvcConfigurer { + + @Autowired + private ApiKeyInterceptor apiKeyInterceptor; + + @Override + public void addInterceptors(InterceptorRegistry registry) { + // 配置API-KEY拦截器,只拦截联动相关的接口 + registry.addInterceptor(apiKeyInterceptor) + .addPathPatterns("/interlocking/**") + .excludePathPatterns("/interlocking/health"); + } +} diff --git a/haobang-security-xdr/syslog-consumer/src/main/java/com/haobang/controller/InterlockingController.java b/haobang-security-xdr/syslog-consumer/src/main/java/com/haobang/controller/InterlockingController.java new file mode 100644 index 0000000..50f57d8 --- /dev/null +++ b/haobang-security-xdr/syslog-consumer/src/main/java/com/haobang/controller/InterlockingController.java @@ -0,0 +1,417 @@ +package com.haobang.controller; + +import com.common.entity.DeviceInterlockingCmd; +import com.common.entity.DeviceInterlockingLog; +import com.common.entity.DeviceInterlocking; +import com.common.service.DeviceInterlockingCmdService; +import com.common.service.DeviceInterlockingLogService; +import com.common.service.DeviceInterlockingService; +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; +import java.util.stream.Collectors; + +/** + * 探针联动控制器 + * 提供REST API接口给syslog-serve模块调用 + * API-KEY认证机制 + */ +@RestController +@RequestMapping("/interlocking") +public class InterlockingController { + + @Autowired + private DeviceInterlockingCmdService cmdService; + + @Autowired + private DeviceInterlockingLogService logService; + + @Autowired + private DeviceInterlockingService interlockingService; + + /** + * 健康检查接口(不需要API-KEY认证) + */ + @GetMapping("/health") + public ResponseEntity> health() { + Map result = new HashMap<>(); + result.put("code", 200); + result.put("message", "OK"); + result.put("service", "interlocking-api"); + return ResponseEntity.ok(result); + } + + // ==================== 封禁指令接口 ==================== + + /** + * 1.1 获取待执行的封禁指令记录 + * GET /interlocking/cmd/pending?probeId=1 + */ + @GetMapping("/cmd/pending") + public ResponseEntity> getPendingCommands(@RequestParam Long probeId) { + Map result = new HashMap<>(); + try { + List commands = cmdService.selectPendingCommands(probeId); + result.put("code", 200); + result.put("message", "success"); + result.put("data", commands); + result.put("total", commands.size()); + return ResponseEntity.ok(result); + } catch (Exception e) { + result.put("code", 500); + result.put("message", "获取待执行指令失败: " + e.getMessage()); + return ResponseEntity.status(500).body(result); + } + } + + /** + * 1.2 获取指定状态的封禁指令记录 + * GET /interlocking/cmd?probeId=1&cmdStatus=0 + */ + @GetMapping("/cmd") + public ResponseEntity> getCommands( + @RequestParam Long probeId, + @RequestParam(required = false) String cmdStatus) { + Map result = new HashMap<>(); + try { + List commands; + if (cmdStatus != null && !cmdStatus.isEmpty()) { + commands = cmdService.selectByProbeIdAndStatus(probeId, cmdStatus); + } else { + commands = cmdService.selectPendingCommands(probeId); + } + result.put("code", 200); + result.put("message", "success"); + result.put("data", commands); + result.put("total", commands.size()); + return ResponseEntity.ok(result); + } catch (Exception e) { + result.put("code", 500); + result.put("message", "获取指令失败: " + e.getMessage()); + return ResponseEntity.status(500).body(result); + } + } + + /** + * 1.3 获取单个指令详情 + * GET /interlocking/cmd/{id} + */ + @GetMapping("/cmd/{id}") + public ResponseEntity> getCommandById(@PathVariable Long id) { + Map result = new HashMap<>(); + try { + DeviceInterlockingCmd command = cmdService.selectById(id); + if (command == null) { + result.put("code", 404); + result.put("message", "指令不存在"); + return ResponseEntity.status(404).body(result); + } + result.put("code", 200); + result.put("message", "success"); + result.put("data", command); + return ResponseEntity.ok(result); + } catch (Exception e) { + result.put("code", 500); + result.put("message", "获取指令详情失败: " + e.getMessage()); + return ResponseEntity.status(500).body(result); + } + } + + /** + * 1.4 创建新的封禁指令 + * POST /interlocking/cmd + */ + @PostMapping("/cmd") + public ResponseEntity> createCommand(@RequestBody DeviceInterlockingCmd cmd) { + Map result = new HashMap<>(); + try { + // 设置默认状态为待执行 + if (cmd.getCmdStatus() == null || cmd.getCmdStatus().isEmpty()) { + cmd.setCmdStatus("0"); + } + int rows = cmdService.insert(cmd); + result.put("code", 200); + result.put("message", "指令创建成功"); + result.put("data", rows); + return ResponseEntity.ok(result); + } catch (Exception e) { + result.put("code", 500); + result.put("message", "创建指令失败: " + e.getMessage()); + return ResponseEntity.status(500).body(result); + } + } + + // ==================== 指令状态更新接口 ==================== + + /** + * 2.1 更新封禁指令状态为执行中 + * PUT /interlocking/cmd/{id}/status/executing + */ + @PutMapping("/cmd/{id}/status/executing") + public ResponseEntity> updateStatusToExecuting(@PathVariable Long id) { + Map result = new HashMap<>(); + try { + int rows = cmdService.updateStatusToExecuting(id); + if (rows > 0) { + result.put("code", 200); + result.put("message", "状态已更新为执行中"); + result.put("data", rows); + } else { + result.put("code", 404); + result.put("message", "指令不存在"); + } + return ResponseEntity.ok(result); + } catch (Exception e) { + result.put("code", 500); + result.put("message", "更新状态失败: " + e.getMessage()); + return ResponseEntity.status(500).body(result); + } + } + + /** + * 2.2 更新封禁指令状态为执行完成 + * PUT /interlocking/cmd/{id}/status/completed + */ + @PutMapping("/cmd/{id}/status/completed") + public ResponseEntity> updateStatusToCompleted(@PathVariable Long id) { + Map result = new HashMap<>(); + try { + int rows = cmdService.updateStatusToCompleted(id); + if (rows > 0) { + result.put("code", 200); + result.put("message", "状态已更新为执行完成"); + result.put("data", rows); + } else { + result.put("code", 404); + result.put("message", "指令不存在"); + } + return ResponseEntity.ok(result); + } catch (Exception e) { + result.put("code", 500); + result.put("message", "更新状态失败: " + e.getMessage()); + return ResponseEntity.status(500).body(result); + } + } + + /** + * 2.3 更新封禁指令状态为执行失败 + * PUT /interlocking/cmd/{id}/status/failed + */ + @PutMapping("/cmd/{id}/status/failed") + public ResponseEntity> updateStatusToFailed(@PathVariable Long id) { + Map result = new HashMap<>(); + try { + int rows = cmdService.updateStatusToFailed(id); + if (rows > 0) { + result.put("code", 200); + result.put("message", "状态已更新为执行失败"); + result.put("data", rows); + } else { + result.put("code", 404); + result.put("message", "指令不存在"); + } + return ResponseEntity.ok(result); + } catch (Exception e) { + result.put("code", 500); + result.put("message", "更新状态失败: " + e.getMessage()); + return ResponseEntity.status(500).body(result); + } + } + + /** + * 2.4 批量更新封禁指令状态 + * PUT /interlocking/cmd/status + */ + @PutMapping("/cmd/status") + public ResponseEntity> batchUpdateStatus(@RequestBody Map request) { + Map result = new HashMap<>(); + try { + @SuppressWarnings("unchecked") + List ids = (List) request.get("ids"); + String cmdStatus = (String) request.get("cmdStatus"); + + if (ids == null || ids.isEmpty()) { + result.put("code", 400); + result.put("message", "指令ID列表不能为空"); + return ResponseEntity.badRequest().body(result); + } + + int total = 0; + for (Long id : ids) { + total += cmdService.updateStatus(id, cmdStatus); + } + + result.put("code", 200); + result.put("message", "批量更新成功"); + result.put("data", total); + return ResponseEntity.ok(result); + } catch (Exception e) { + result.put("code", 500); + result.put("message", "批量更新状态失败: " + e.getMessage()); + return ResponseEntity.status(500).body(result); + } + } + + // ==================== 封禁记录接口 ==================== + + /** + * 3.1 批量插入封禁记录 + * POST /interlocking/log/batch + */ + @PostMapping("/log/batch") + public ResponseEntity> batchInsertLog(@RequestBody Map request) { + Map result = new HashMap<>(); + try { + // Spring无法直接将List转为List + // 需要手动转换 + Object logsObj = request.get("logs"); + List logs; + if (logsObj instanceof List) { + logs = ((List) logsObj).stream() + .map(item -> convertToDeviceInterlockingLog(item)) + .collect(Collectors.toList()); + } else { + result.put("code", 400); + result.put("message", "logs字段格式错误,应为数组"); + return ResponseEntity.badRequest().body(result); + } + + if (logs == null || logs.isEmpty()) { + result.put("code", 400); + result.put("message", "封禁记录列表不能为空"); + return ResponseEntity.badRequest().body(result); + } + + int rows = logService.batchInsert(logs); + result.put("code", 200); + result.put("message", "批量插入成功"); + result.put("data", rows); + result.put("total", logs.size()); + return ResponseEntity.ok(result); + } catch (Exception e) { + result.put("code", 500); + result.put("message", "批量插入记录失败: " + e.getMessage()); + return ResponseEntity.status(500).body(result); + } + } + + /** + * 将Map转换为DeviceInterlockingLog实体 + */ + private DeviceInterlockingLog convertToDeviceInterlockingLog(Object item) { + DeviceInterlockingLog log = new DeviceInterlockingLog(); + if (item instanceof Map) { + @SuppressWarnings("unchecked") + Map map = (Map) item; + + if (map.get("id") != null) log.setId(((Number) map.get("id")).longValue()); + if (map.get("deviceInterlockingCmdId") != null) log.setDeviceInterlockingCmdId(((Number) map.get("deviceInterlockingCmdId")).longValue()); + if (map.get("deviceInterlockingId") != null) log.setDeviceInterlockingId(((Number) map.get("deviceInterlockingId")).longValue()); + if (map.get("banIp") != null) log.setBanIp(String.valueOf(map.get("banIp"))); + if (map.get("deviceName") != null) log.setDeviceName(String.valueOf(map.get("deviceName"))); + if (map.get("banMethod") != null) log.setBanMethod(String.valueOf(map.get("banMethod"))); + if (map.get("banResult") != null) { + Object banResult = map.get("banResult"); + if (banResult instanceof Number) { + log.setBanResult(((Number) banResult).intValue()); + } else { + log.setBanResult(Integer.parseInt(String.valueOf(banResult))); + } + } + if (map.get("tenantId") != null) log.setTenantId(String.valueOf(map.get("tenantId"))); + if (map.get("createDept") != null) log.setCreateDept(((Number) map.get("createDept")).longValue()); + if (map.get("createBy") != null) log.setCreateBy(((Number) map.get("createBy")).longValue()); + if (map.get("remark") != null) log.setRemark(String.valueOf(map.get("remark"))); + if (map.get("respBody") != null) log.setRespBody(String.valueOf(map.get("respBody"))); + if (map.get("reqBody") != null) log.setReqBody(String.valueOf(map.get("reqBody"))); + } + return log; + } + + /** + * 3.2 获取封禁记录列表 + * GET /interlocking/log?cmdId=1 + */ + @GetMapping("/log") + public ResponseEntity> getLogs(@RequestParam(required = false) Long cmdId) { + Map result = new HashMap<>(); + try { + List logs; + if (cmdId != null) { + logs = logService.selectByCmdId(cmdId); + } else { + logs = logService.selectAll(); + } + result.put("code", 200); + result.put("message", "success"); + result.put("data", logs); + result.put("total", logs.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/device + */ + @GetMapping("/device") + public ResponseEntity> getDevices( + @RequestParam(required = false) String deptId, + @RequestParam(required = false) String vendor) { + Map result = new HashMap<>(); + try { + List devices; + if (deptId != null && !deptId.isEmpty()) { + devices = interlockingService.selectByDeptId(deptId); + } else if (vendor != null && !vendor.isEmpty()) { + devices = interlockingService.selectByVendor(vendor); + } else { + devices = interlockingService.selectAll(); + } + result.put("code", 200); + result.put("message", "success"); + result.put("data", devices); + result.put("total", devices.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/device/{id} + */ + @GetMapping("/device/{id}") + public ResponseEntity> getDeviceById(@PathVariable Long id) { + Map result = new HashMap<>(); + try { + DeviceInterlocking device = interlockingService.selectById(id); + if (device == null) { + result.put("code", 404); + result.put("message", "设备不存在"); + return ResponseEntity.status(404).body(result); + } + result.put("code", 200); + result.put("message", "success"); + result.put("data", device); + return ResponseEntity.ok(result); + } catch (Exception e) { + result.put("code", 500); + result.put("message", "获取设备详情失败: " + e.getMessage()); + return ResponseEntity.status(500).body(result); + } + } +} diff --git a/haobang-security-xdr/syslog-serve/pom.xml b/haobang-security-xdr/syslog-serve/pom.xml index 7467e0e..de54505 100644 --- a/haobang-security-xdr/syslog-serve/pom.xml +++ b/haobang-security-xdr/syslog-serve/pom.xml @@ -90,7 +90,6 @@ 1.4.2 - com.fasterxml.jackson.core @@ -180,8 +179,29 @@ ${pagehelper.version} + + org.apache.httpcomponents + httpclient + + + + + cn.hutool + hutool-all + 5.8.27 + + + + + org.bouncycastle + bcprov-jdk15to18 + 1.78 + + + + diff --git a/haobang-security-xdr/syslog-serve/src/main/java/com/haobang/controller/InterlockingController.java b/haobang-security-xdr/syslog-serve/src/main/java/com/haobang/controller/InterlockingController.java new file mode 100644 index 0000000..fd62d95 --- /dev/null +++ b/haobang-security-xdr/syslog-serve/src/main/java/com/haobang/controller/InterlockingController.java @@ -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> blockIp(@RequestBody Map request) { + Map 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 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> batchBlockIp(@RequestBody Map request) { + Map result = new HashMap<>(); + + try { + @SuppressWarnings("unchecked") + List ips = (List) 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 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> removeFromBlacklist(@PathVariable String ip) { + Map result = new HashMap<>(); + + try { + Map 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> batchRemoveFromBlacklist(@RequestBody Map request) { + Map result = new HashMap<>(); + + try { + @SuppressWarnings("unchecked") + List ips = (List) request.get("ips"); + + if (ips == null || ips.isEmpty()) { + result.put("code", 400); + result.put("message", "IP地址列表不能为空"); + return ResponseEntity.badRequest().body(result); + } + + Map 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> getStatus() { + Map result = new HashMap<>(); + + try { + result.put("code", 200); + result.put("message", "success"); + + Map 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> health() { + Map 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); + } +} diff --git a/haobang-security-xdr/syslog-serve/src/main/java/com/haobang/firewall/FirewallApiClient.java b/haobang-security-xdr/syslog-serve/src/main/java/com/haobang/firewall/FirewallApiClient.java new file mode 100644 index 0000000..7a9b20d --- /dev/null +++ b/haobang-security-xdr/syslog-serve/src/main/java/com/haobang/firewall/FirewallApiClient.java @@ -0,0 +1,420 @@ +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("${whitelist.api.username:apt-admin103}") + private String whitelistUsername; + + @Value("${whitelist.api.password:C9W2xYgfc%SN1}") + private String whitelistPassword; + + @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 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> ips) { + if (!firewallEnabled) { + logger.info("[测试模式] 批量添加黑名单: {}", ips); + return new FirewallResponse(true, "1", "测试模式-模拟成功"); + } + + List> entries = new ArrayList<>(); + for (Map ipInfo : ips) { + Map 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 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 ips) { + if (!firewallEnabled) { + logger.info("[测试模式] 批量删除黑名单: {}", ips); + return new FirewallResponse(true, "1", "测试模式-模拟成功"); + } + + List> entries = ips.stream() + .map(ip -> { + Map entry = new HashMap<>(); + entry.put("blist", ip); + return entry; + }) + .collect(Collectors.toList()); + + Map 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> addresses = new ArrayList<>(); + Map address = new HashMap<>(); + address.put("address", ip); + addresses.add(address); + + Map payload = new HashMap<>(); + payload.put("enable", "1"); + payload.put("name", name); + payload.put("desc", desc != null ? desc : ""); + payload.put("addr", addresses); + + return doPost(whitelistApiUrl, whitelistUsername, whitelistPassword, 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, whitelistUsername, whitelistPassword); + } + + /** + * 批量删除白名单 + * @param names 白名单名称列表 + * @return 操作结果 + */ + public FirewallResponse batchRemoveFromWhitelist(List names) { + if (!firewallEnabled) { + logger.info("[测试模式] 批量删除白名单: {}", names); + return new FirewallResponse(true, "1", "测试模式-模拟成功"); + } + + List> nameList = names.stream() + .map(name -> { + Map entry = new HashMap<>(); + entry.put("name", name); + return entry; + }) + .collect(Collectors.toList()); + + Map payload = new HashMap<>(); + payload.put("name_list", nameList); + + String batchDeleteUrl = whitelistApiUrl + "Batch"; + return doDelete(batchDeleteUrl, whitelistUsername, whitelistPassword, payload); + } + + /** + * 执行POST请求 + */ + private FirewallResponse doPost(String urlStr, String username, String password, Map 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 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 + '\'' + + '}'; + } + } +} diff --git a/haobang-security-xdr/syslog-serve/src/main/java/com/haobang/interlocking/InterlockingApiClient.java b/haobang-security-xdr/syslog-serve/src/main/java/com/haobang/interlocking/InterlockingApiClient.java new file mode 100644 index 0000000..d7b8fb3 --- /dev/null +++ b/haobang-security-xdr/syslog-serve/src/main/java/com/haobang/interlocking/InterlockingApiClient.java @@ -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> 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> 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 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> logs) { + String url = baseUrl + "/log/batch"; + try { + Map 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 entity = new HttpEntity<>(headers); + + ResponseEntity response = restTemplate.exchange(url, HttpMethod.GET, entity, String.class); + return response.getBody(); + } + + /** + * 执行PUT请求 + */ + private String doPut(String url, Map body) { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.set("X-API-KEY", apiKey); + + HttpEntity> entity = new HttpEntity<>(body, headers); + ResponseEntity response = restTemplate.exchange(url, HttpMethod.PUT, entity, String.class); + return response.getBody(); + } + + /** + * 执行POST请求 + */ + private String doPost(String url, Map body) { + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.set("X-API-KEY", apiKey); + + HttpEntity> entity = new HttpEntity<>(body, headers); + ResponseEntity 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; } +} diff --git a/haobang-security-xdr/syslog-serve/src/main/java/com/haobang/interlocking/InterlockingService.java b/haobang-security-xdr/syslog-serve/src/main/java/com/haobang/interlocking/InterlockingService.java new file mode 100644 index 0000000..3cb3e97 --- /dev/null +++ b/haobang-security-xdr/syslog-serve/src/main/java/com/haobang/interlocking/InterlockingService.java @@ -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> pendingCommands = apiClient.getPendingCommands(probeId.longValue()); + + if (pendingCommands.isEmpty()) { + logger.debug("没有待执行的封禁指令"); + return; + } + + logger.info("获取到 {} 条待执行的封禁指令", pendingCommands.size()); + + // 2. 逐条处理指令 + for (Map 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 cmd) { + Long cmdId = ((Number) cmd.get("id")).longValue(); + // JSON字段名使用驼峰格式 + List 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 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> allResults = new ArrayList<>(); + + // 遍历每个联动设备执行封禁 + for (String deviceIp : deviceIps) { + // 设置防火墙客户端的设备IP(这里简化处理,实际可能需要从配置获取) + List> results = executeBlockOperations( + deviceIp, banType, banOperationType, banIps, banDuration, cmdId); + allResults.addAll(results); + + for (Map result : results) { + // banResult: 1表示成功,0表示失败 + if ("1".equals(result.get("banResult"))) { + successCount++; + } else { + failCount++; + } + } + } + + // 5. 批量记录封禁结果 + for (Map 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 parseStringArray(Object obj) { + List 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> executeBlockOperations( + String deviceIp, String banType, int operationType, + List ipList, int duration, Long cmdId) { + + List> 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 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 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> addr = new ArrayList<>(); + Map 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 manualBlock(String ip, String cmdType, int age, String reason) { + Map 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 batchManualBlock(List ips, String cmdType, int age, String reason) { + Map result = new HashMap<>(); + List> ipList = new ArrayList<>(); + + for (String ip : ips) { + Map 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; } +}