上篇博客实现了通过工厂类获取设备连接器: Java设计模式实战 - 02 工厂类获取设备连接器 DeviceConnectorFactory 。这篇博客会在上篇博客的基础上通过工厂类获取数据同步器;
文章目录
- 1. 项目背景
- 2. 设备类型枚举 SourceDeviceEnum
- 3. 同步数据类型枚举 SyncDataTypeEnum
- 4. 自定义注解用于标识向哪个设备同步哪种数据 @SupportType
- 5. 数据同步器接口 OpenDataSynchronizer
- 6. Xdr安全告警处置状态同步器类 XdrAlertDealStatusSynchronizer
- 7. Xdr安全事件处置状态同步器类 XdrIncidentDealStatusSynchronizer
- 8. 数据同步器工厂类 OpenDataSynchronizerFactory
- 9. 数据同步分发 OpenDataSyncDispatchServiceImpl
1. 项目背景
SIR平台上,从XDR平台接入的安全告警数据,为了保证两个平台安全告警数据的处置状态一致,SIR平台需要将已经处置闭环的安全告警数据的处置状态同步给XDR平台,从而更新XDR平台上安全告警数据的处置状态。
2. 设备类型枚举 SourceDeviceEnum
考虑到将来可能会接入SIP或其他平台的数据,这里设计的时候做了扩展,将来如果想继续接入新的第三方连接设备,只需要在设备类型枚举 SourceDeviceEnum 中加入一个设备即可。
public enum SourceDeviceEnum {
/**
* XDR设备
*/
XDR;
public static SourceDeviceEnum build(String srcDevice) {
for (SourceDeviceEnum sourceDeviceEnum : values()) {
if (sourceDeviceEnum.name().equals(srcDevice)) {
return sourceDeviceEnum;
}
}
return null;
}
}
3. 同步数据类型枚举 SyncDataTypeEnum
考虑到将来可能会接入更多类型的数据,比如资产数据,脆弱性数据,这里设计的时候做了扩展,将来如果想同步其他类型数据,只需要在数据类型枚举 SyncDataTypeEnum 中加入一个类型即可。
/**
* 同步的数据类型
*/
public enum SyncDataTypeEnum {
/**
* 事件处置状态
*/
INCIDENT_DEAL_STATUS,
/**
* 告警处置状态
*/
ALERT_DEAL_STATUS;
}
4. 自定义注解用于标识向哪个设备同步哪种数据 @SupportType
/**
* 用于
* {@link OpenDataSynchronizer} 标注支持的同步类型
* {@link DeviceConnector} 标注支持的设备连接器
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SupportType {
/**
* @return 支持的同步数据类型
*/
SyncDataTypeEnum[] syncDataType() default {};
/**
* @return 支持的设备类型
*/
SourceDeviceEnum[] sourceDevice() default {};
}
5. 数据同步器接口 OpenDataSynchronizer
public interface OpenDataSynchronizer<DATA, RETURN> {
/**
* 同步数据
*
* @param connection 设备连接
* @param data 同步数据内容
* @return 同步结果
*/
RETURN sync(DeviceConnection connection, DATA data);
}
6. Xdr安全告警处置状态同步器类 XdrAlertDealStatusSynchronizer
@Slf4j
@Service
// 注解标识设备连接为XDR,数据状态为ALERT_DEAL_STATUS
@SupportType(sourceDevice = SourceDeviceEnum.XDR, syncDataType = SyncDataTypeEnum.ALERT_DEAL_STATUS)
public class XdrAlertDealStatusSynchronizer implements OpenDataSynchronizer<List<KafkaDealStatusSyncDto>, ApiResponse<?>> {
/**
* XDR 接口单次上限100
*/
final private static int BATCH_SIZE = 100;
@Setter(onMethod_ = @Autowired)
private LinkageBusClient linkageBusClient;
@Override
public ApiResponse<?> sync(DeviceConnection connection, List<KafkaDealStatusSyncDto> dataList) {
if (Objects.nonNull(connection) && !CollectionUtils.isEmpty(dataList)) {
XdrDeviceConnection xdrDeviceConnection = (XdrDeviceConnection) connection;
// feign调用之前设置本地线程AKSK信息
AkSkHolder.set(xdrDeviceConnection.getAkSk());
// 分批次推送
ArrayList<KafkaDealStatusSyncDto> batchList = new ArrayList<>();
int size = dataList.size();
for (int i = 0; i < dataList.size(); i++) {
KafkaDealStatusSyncDto data = dataList.get(i);
XdrDisposalSceneEnum disposalScene = XdrDisposalSceneEnum.buildByName(data.getModifyType());
assert disposalScene != null;
data.setModifyType(disposalScene.getXdrScene());
batchList.add(data);
if (batchList.size() == BATCH_SIZE || i == size - 1) {
// 转换数据为XDR状态同步接口请求入参
XdrSyncIncidentStatusQo xdrSyncIncidentStatusQo = convertToXdrSyncIncidentStatusQo(batchList);
XdrApiResponse<SyncDealStatusResponse> res = linkageBusClient.syncAlertStatus(
URI.create(xdrDeviceConnection.getLinkageBusUrl()), xdrDeviceConnection.getCompanyId(), xdrSyncIncidentStatusQo
);
log.info("feign call response: {}, {} data pushed", res, batchList.size());
// 一个出错就失败,todo: 和XDR确认批量部分成功返回码
if (!XdrApiResponse.CODE_SUCCESS.equals(res.getCode()) || res.getData().getCode() == ApiResponse.CODE_ERR_COMMON) {
throw new DataSyncException("exception.xdr.sync.data.failed");
}
batchList.clear();
}
}
return ApiResponse.newInstance(ApiResponse.CODE_OK, null);
}
throw new DataSyncException("exception.xdr.sync.data.failed");
}
/**
* 将同步的数据转换为请求入参
*
* @param dataList 同步数据
* @return 请求入参
*/
private XdrSyncIncidentStatusQo convertToXdrSyncIncidentStatusQo(List<KafkaDealStatusSyncDto> dataList) {
return new XdrSyncIncidentStatusQo(dataList);
}
}
7. Xdr安全事件处置状态同步器类 XdrIncidentDealStatusSynchronizer
@Slf4j
@Service
@SupportType(sourceDevice = SourceDeviceEnum.XDR, syncDataType = SyncDataTypeEnum.INCIDENT_DEAL_STATUS)
public class XdrIncidentDealStatusSynchronizer implements OpenDataSynchronizer<List<KafkaDealStatusSyncDto>, ApiResponse<?>> {
/**
* XDR 接口单次上限100
*/
final private static int BATCH_SIZE = 100;
@Setter(onMethod_ = @Autowired)
private LinkageBusClient linkageBusClient;
@Override
public ApiResponse<?> sync(DeviceConnection connection, List<KafkaDealStatusSyncDto> dataList) {
if (Objects.nonNull(connection) && !CollectionUtils.isEmpty(dataList)) {
XdrDeviceConnection xdrDeviceConnection = (XdrDeviceConnection) connection;
// feign调用之前设置本地线程AKSK信息
AkSkHolder.set(xdrDeviceConnection.getAkSk());
// 分批次推送
ArrayList<KafkaDealStatusSyncDto> batchList = new ArrayList<>();
int size = dataList.size();
for (int i = 0; i < dataList.size(); i++) {
KafkaDealStatusSyncDto data = dataList.get(i);
XdrDisposalSceneEnum disposalScene = XdrDisposalSceneEnum.buildByName(data.getModifyType());
assert disposalScene != null;
data.setModifyType(disposalScene.getXdrScene());
batchList.add(data);
if (batchList.size() == BATCH_SIZE || i == size - 1) {
// 转换数据为XDR状态同步接口请求入参
XdrSyncIncidentStatusQo xdrSyncIncidentStatusQo = convertToXdrSyncIncidentStatusQo(batchList);
XdrApiResponse<SyncDealStatusResponse> res = linkageBusClient.syncIncidentStatus(
URI.create(xdrDeviceConnection.getLinkageBusUrl()), xdrDeviceConnection.getCompanyId(), xdrSyncIncidentStatusQo
);
log.info("feign call response: {}, {} data pushed", res, batchList.size());
// 一个出错就失败,todo: 和XDR确认批量部分成功返回码
if (!XdrApiResponse.CODE_SUCCESS.equals(res.getCode()) || res.getData().getCode() == ApiResponse.CODE_ERR_COMMON) {
throw new DataSyncException("exception.xdr.sync.data.failed");
}
batchList.clear();
}
}
return ApiResponse.newInstance(ApiResponse.CODE_OK, null);
}
throw new DataSyncException("exception.xdr.sync.data.failed");
}
/**
* 将同步的数据转换为请求入参
*
* @param dataList 同步数据
* @return 请求入参
*/
private XdrSyncIncidentStatusQo convertToXdrSyncIncidentStatusQo(List<KafkaDealStatusSyncDto> dataList) {
return new XdrSyncIncidentStatusQo(dataList);
}
}
8. 数据同步器工厂类 OpenDataSynchronizerFactory
@Service
public class OpenDataSynchronizerFactory<DATA, RETURN> {
// <设备类型,<数据类型,数据同步器>>
private final ConcurrentHashMap<SourceDeviceEnum, ConcurrentHashMap<SyncDataTypeEnum, OpenDataSynchronizer<DATA, RETURN>>> SYNCHRONIZERS = new ConcurrentHashMap<>();
public OpenDataSynchronizerFactory(List<OpenDataSynchronizer<DATA, RETURN>> synchronizes/*Spring容器中的数据同步器*/) {
for (OpenDataSynchronizer<DATA, RETURN> synchronizer : synchronizes) {
// 获取数据同步器上的 @SupportType 注解
SupportType supportTypeAnno = AnnotationUtils.findAnnotation(synchronizer.getClass(), SupportType.class);
if (Objects.nonNull(supportTypeAnno)) {
// 获取注解的属性值
// 设备类型
SourceDeviceEnum[] sourceDevices = supportTypeAnno.sourceDevice();
// 数据类型
SyncDataTypeEnum[] syncDataTypes = supportTypeAnno.syncDataType();
// 缓存所有所有数据同步器
for (SourceDeviceEnum sourceDevice : sourceDevices) {
final ConcurrentHashMap<SyncDataTypeEnum, OpenDataSynchronizer<DATA, RETURN>> synchronizesWithDevice = SYNCHRONIZERS.getOrDefault(sourceDevice, new ConcurrentHashMap<>());
for (SyncDataTypeEnum syncDataType : syncDataTypes) {
synchronizesWithDevice.put(syncDataType, synchronizer);
}
SYNCHRONIZERS.put(sourceDevice, synchronizesWithDevice);
}
}
}
}
// 获取一个数据同步器
public OpenDataSynchronizer<DATA, RETURN> get(SourceDeviceEnum sourceDevice, SyncDataTypeEnum syncDataType) {
if (Objects.nonNull(sourceDevice) && Objects.nonNull(syncDataType)) {
final ConcurrentHashMap<SyncDataTypeEnum, OpenDataSynchronizer<DATA, RETURN>> synchronizesWithDevice = SYNCHRONIZERS.get(sourceDevice);
if (!CollectionUtils.isEmpty(synchronizesWithDevice)) {
OpenDataSynchronizer<DATA, RETURN> synchronizer = synchronizesWithDevice.get(syncDataType);
if (Objects.nonNull(synchronizer)) {
return synchronizer;
}
}
}
throw new DataSyncException("exception.sync.data.type.not.support");
}
}
9. 数据同步分发 OpenDataSyncDispatchServiceImpl
@Service
public class OpenDataSyncDispatchServiceImpl<DATA, RETURN> implements OpenDataSyncDispatchService<DATA, RETURN> {
@Setter(onMethod_ = @Autowired)
private DeviceConnectorFactory deviceConnectorFactory;
@Setter(onMethod_ = @Autowired)
private OpenDataSynchronizerFactory<DATA, RETURN> openDataSynchronizerFactory;
@Setter(onMethod_ = @Autowired)
private ThreadPoolTaskExecutor executor;
@Override
public RETURN dispatch(SyncDataWrapper<DATA> syncDataWrapper) {
// 设备类型
SourceDeviceEnum sourceDevice = syncDataWrapper.getDeviceInfo().getSourceDevice();
// 同步的数据类型
SyncDataTypeEnum syncDataType = syncDataWrapper.getSyncDataType();
// 通过日志来源设备和同步数据类型获取一个同步器
OpenDataSynchronizer<DATA, RETURN> synchronizer = openDataSynchronizerFactory.get(sourceDevice, syncDataType);
// 通过日志来源设备获取一个设备连接器
DeviceConnector connector = deviceConnectorFactory.get(sourceDevice);
// 通过设备连接器打开一个连接
DeviceConnection connection = connector.connect(syncDataWrapper.getDeviceInfo());
// 同步数据
return synchronizer.sync(connection, syncDataWrapper.getData());
}
@Override
public Future<RETURN> dispatchAsync(SyncDataWrapper<DATA> syncDataWrapper) {
return executor.submit(() -> dispatch(syncDataWrapper));
}
}