feat:多机证书导入功能

This commit is contained in:
waner
2025-05-08 16:46:30 +08:00
parent db80b1dd83
commit 55a9cfbdbe
11 changed files with 773 additions and 19 deletions
+4
View File
@@ -8,6 +8,8 @@ import com.sunyard.inf.customize.SydQNUCApi;
import com.sunyard.inf.customize.SydUnionPayApi;
import racal.sunyard.main.SydApiBuilder;
import java.util.List;
/**
* Created by Cheney on 2017/11/11.
* 程序调用接口
@@ -117,4 +119,6 @@ public interface SydApi extends
public Object getBindObject();
public void setBindObject(Object bindObject);
void importCertToServers(List<String> addressList, int timeout, String cert);
}
@@ -0,0 +1,86 @@
package com.sunyard.entity;
import java.util.List;
public class ServerConfig {
/**
* 主机节点信息
*/
private List<ServerAddress> masterNodeList;
/**
* 备机节点信息
*/
private List<ServerAddress> slaveNodeList;
/**
* 连接超时时间
*/
private Integer timeout;
public ServerConfig(List<ServerAddress> masterNodeList, List<ServerAddress> slaveNodeList, Integer timeout) {
this.masterNodeList = masterNodeList;
this.slaveNodeList = slaveNodeList;
this.timeout = timeout;
}
public List<ServerAddress> getMasterNodeList() {
return masterNodeList;
}
public void setMasterNodeList(List<ServerAddress> masterNodeList) {
this.masterNodeList = masterNodeList;
}
public Integer getTimeout() {
return timeout;
}
public void setTimeout(Integer timeout) {
this.timeout = timeout;
}
public List<ServerAddress> getSlaveNodeList() {
return slaveNodeList;
}
public void setSlaveNodeList(List<ServerAddress> slaveNodeList) {
this.slaveNodeList = slaveNodeList;
}
public static class ServerAddress {
private String ip;
private Integer port;
public ServerAddress(String ip, Integer port) {
this.ip = ip;
this.port = port;
}
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public Integer getPort() {
return port;
}
public void setPort(Integer port) {
this.port = port;
}
}
}
@@ -0,0 +1,134 @@
package com.sunyard.snmp;
import org.snmp4j.*;
import org.snmp4j.mp.*;
import org.snmp4j.security.*;
import org.snmp4j.smi.*;
import org.snmp4j.transport.DefaultUdpTransportMapping;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import java.util.Properties;
import java.util.Vector;
public class SnmpTrapSender {
private static volatile SnmpTrapSender instance;
private Snmp snmp;
private Properties config;
// 配置参数
private String ip;
private int port;
private String enterpriseOid;
private String community;
private int version;
private SnmpTrapSender() throws IOException {
// loadConfig();
ip = "172.16.18.113";
port = 162;
enterpriseOid = "1.3.6.1.4.1.2021.251.1";
community = "public";
version = 1;
initializeSNMP();
}
public static SnmpTrapSender getInstance() throws IOException {
if (instance == null) {
synchronized (SnmpTrapSender.class) {
if (instance == null) {
instance = new SnmpTrapSender();
}
}
}
return instance;
}
private void loadConfig() {
try (InputStream input = getClass().getClassLoader().getResourceAsStream("test.properties")) {
config = new Properties();
config.load(input);
ip = config.getProperty("snmp.target.ip");
port = Integer.parseInt(config.getProperty("snmp.target.port"));
enterpriseOid = config.getProperty("enterprise.oid");
community = config.getProperty("snmp.community");
version = Integer.parseInt(config.getProperty("snmp.version"));
} catch (IOException | NumberFormatException e) {
throw new RuntimeException("加载配置文件失败", e);
}
}
public void loadConfig(Map<String, String> map) {
ip = map.getOrDefault("snmp.target.ip", null);
// port = Integer.parseInt(map.getOrDefault("snmp.target.port", "0"));
enterpriseOid = map.getOrDefault("enterprise.oid", null);
community = map.getOrDefault("snmp.community", null);
// version = Integer.parseInt(map.getOrDefault("snmp.version", "1"));
}
private void initializeSNMP() throws IOException {
TransportMapping<?> transport = new DefaultUdpTransportMapping();
snmp = new Snmp(transport);
snmp.listen();
}
public void sendTrap(String message, int severity) throws IOException {
switch (version) {
case SnmpConstants.version1:
sendV1Trap(message, severity);
break;
case SnmpConstants.version2c:
sendV2Trap(message, severity);
break;
default:
throw new IllegalArgumentException("不支持的SNMP版本");
}
}
private void sendV1Trap(String message, int severity) throws IOException {
CommunityTarget target = createCommunityTarget(SnmpConstants.version1);
PDUv1 pdu = new PDUv1();
configureCommonTrap(pdu, message, severity);
pdu.setGenericTrap(PDUv1.ENTERPRISE_SPECIFIC);
snmp.send(pdu, target);
}
private void sendV2Trap(String message, int severity) throws IOException {
CommunityTarget target = createCommunityTarget(SnmpConstants.version2c);
PDU pdu = new PDU();
configureCommonTrap(pdu, message, severity);
pdu.setType(PDU.TRAP);
snmp.send(pdu, target);
}
private void configureCommonTrap(PDU pdu, String message, int severity) {
pdu.add(new VariableBinding(SnmpConstants.sysUpTime,
new TimeTicks(System.currentTimeMillis()/1000)));
pdu.add(new VariableBinding(SnmpConstants.snmpTrapOID,
new OID(enterpriseOid + ".0.1")));
pdu.add(new VariableBinding(new OID(enterpriseOid + ".1.1"),
new OctetString(message)));
pdu.add(new VariableBinding(new OID(enterpriseOid + ".1.2"),
new Integer32(severity)));
}
private CommunityTarget createCommunityTarget(int version) {
CommunityTarget target = new CommunityTarget();
target.setCommunity(new OctetString(community));
target.setAddress(GenericAddress.parse("udp:"+ip+"/"+port));
target.setVersion(version);
target.setTimeout(5000);
target.setRetries(3);
return target;
}
public void close() throws IOException {
if (snmp != null) {
snmp.close();
}
}
}
@@ -0,0 +1,141 @@
package com.sunyard.task;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.*;
import java.util.function.Supplier;
import java.util.stream.Collectors;
public class AsyncTaskExecutor {
private final ExecutorService taskExecutor;
private final ScheduledExecutorService retryScheduler;
public AsyncTaskExecutor(int taskThreads, int retryThreads) {
this.taskExecutor = Executors.newFixedThreadPool(taskThreads);
this.retryScheduler = Executors.newScheduledThreadPool(retryThreads);
}
// 默认配置构造函数
public AsyncTaskExecutor() {
this(4, 2);
}
public List<TaskOutcome<String>> execTasks(List<TaskContext<String>> taskContexts) {
List<CompletableFuture<String>> futures =
taskContexts.stream().map(this::retryWithDelay).collect(Collectors.toList());
List<TaskOutcome<String>> taskOutcomes = new ArrayList<>();
taskContexts.forEach(task ->
retryWithDelay(task).whenCompleteAsync((result, ex) -> {
if (ex != null) {
// 提取原始异常
Throwable rootCause = ex instanceof CompletionException ? ex.getCause() : ex;
taskOutcomes.add(new TaskOutcome<>(task, rootCause));
} else {
taskOutcomes.add(new TaskOutcome<>(task, result));
}
}));
return taskOutcomes;
}
public List<TaskOutcome<String>> execBatchTasks(List<TaskContext<String>> taskContexts) {
// 转换每个Future为明确的TaskOutcome类型
List<CompletableFuture<TaskOutcome<String>>> futures = taskContexts.stream()
.map(task ->
retryWithDelay(task)
.<TaskOutcome<String>>handle((result, ex) -> {
if (ex != null) {
// 提取原始异常
Throwable cause = ex instanceof CompletionException ? ex.getCause() : ex;
return new TaskOutcome<>(task, cause);
} else {
return new TaskOutcome<>(task, result);
}
})
)
.collect(Collectors.toList());
// 等待所有任务完成
CompletableFuture<Void> allFutures = CompletableFuture.allOf(
futures.toArray(new CompletableFuture[0])
);
// 收集处理结果
return allFutures.thenApply(v ->
futures.stream()
.map(CompletableFuture::join)
.collect(Collectors.toList())
).join();
}
/**
* 带延迟的重试逻辑
*/
private <T> CompletableFuture<T> retryWithDelay(TaskContext<T> context) {
return CompletableFuture.supplyAsync(context.getTask(), taskExecutor)
.handleAsync((result, ex) -> {
if (ex == null) {
return CompletableFuture.completedFuture(result);
} else {
return handleRetry(context, ex);
}
}, taskExecutor)
.thenCompose(f -> f);
}
/**
* 处理重试逻辑
*/
private <T> CompletableFuture<T> handleRetry(TaskContext<T> context, Throwable ex) {
if (context.canRetry()) {
context.decreaseRetry();
System.out.printf("[重试调度] ID: %s | 延迟: %ss | 剩余重试: %d | 错误: %s%n",
context.getTaskId(),
context.getRetryDelay().getSeconds(),
context.getRetriesLeft(),
ex.getCause().getMessage());
// 创建延迟重试的Future
CompletableFuture<T> delayedRetry = new CompletableFuture<>();
retryScheduler.schedule(() -> {
retryWithDelay(context).whenComplete((retryResult, retryEx) -> {
if (retryEx != null) {
delayedRetry.completeExceptionally(retryEx);
} else {
delayedRetry.complete(retryResult);
}
});
}, context.getRetryDelay().toMillis(), TimeUnit.MILLISECONDS);
return delayedRetry;
} else {
// 重试耗尽,传递原始异常
CompletableFuture<T> failed = new CompletableFuture<>();
failed.completeExceptionally(ex);
return failed;
}
}
/**
* 关闭线程池
*/
public void shutdown() {
taskExecutor.shutdown();
retryScheduler.shutdown();
try {
if (!taskExecutor.awaitTermination(1, TimeUnit.SECONDS)) {
taskExecutor.shutdownNow();
}
if (!retryScheduler.awaitTermination(1, TimeUnit.SECONDS)) {
retryScheduler.shutdownNow();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
@@ -0,0 +1,50 @@
package com.sunyard.task;
import java.time.Duration;
import java.util.UUID;
import java.util.function.Supplier;
public class TaskContext<T> {
private final String taskId; // 唯一任务ID
private final Supplier<T> task; // 原始任务逻辑
private int retriesLeft; // 剩余重试次数
private final Duration retryDelay; // 重试延迟
private final String address;
public TaskContext(Supplier<T> task, int maxRetries, Duration retryDelay, String address) {
this.taskId = UUID.randomUUID().toString();
this.task = task;
this.retriesLeft = maxRetries;
this.retryDelay = retryDelay;
this.address = address;
}
public String getAddress() {
return address;
}
public String getTaskId() {
return taskId;
}
public Supplier<T> getTask() {
return task;
}
public int getRetriesLeft() {
return retriesLeft;
}
public boolean canRetry() {
return retriesLeft > 0;
}
public void decreaseRetry() {
retriesLeft--;
}
public Duration getRetryDelay() {
return retryDelay;
}
}
@@ -0,0 +1,35 @@
package com.sunyard.task;
public class TaskOutcome<T> {
private final TaskContext<T> context;
private final T result;
private final Throwable error;
public TaskOutcome(TaskContext<T> context, T result) {
this.context = context;
this.result = result;
this.error = null;
}
public TaskOutcome(TaskContext<T> context, Throwable error) {
this.context = context;
this.result = null;
this.error = error;
}
public boolean isSuccess() {
return error == null;
}
public TaskContext<T> getContext() {
return context;
}
public T getResult() {
return result;
}
public Throwable getError() {
return error;
}
}
+80 -2
View File
@@ -1,11 +1,12 @@
package racal.sunyard.main;
import cn.hutool.core.bean.BeanUtil;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.sunyard.RetWrap;
import com.sunyard.SYMEnDeLongData;
import com.sunyard.SydApi;
import com.sunyard.SydApiException;
import com.sunyard.cert.Cert;
import com.sunyard.cert.X509;
import com.sunyard.constant.CertUsage;
import com.sunyard.entity.Struct;
@@ -15,11 +16,16 @@ import com.sunyard.proto.Packet;
import com.sunyard.proto.PacketSection;
import com.sunyard.proto.Util;
import com.sunyard.proto.section.SectionValue;
import com.sunyard.snmp.SnmpTrapSender;
import com.sunyard.task.TaskContext;
import com.sunyard.task.AsyncTaskExecutor;
import com.sunyard.task.TaskOutcome;
import com.sunyard.trans.Alg;
import com.sunyard.trans.FullMode;
import com.sunyard.trans.PacketSN;
import com.sunyard.trans.RoundMode;
import com.sunyard.util.*;
import org.apache.commons.collections4.CollectionUtils;
import org.bouncycastle.asn1.*;
import org.bouncycastle.asn1.cms.ContentInfo;
import org.bouncycastle.cms.SydCmsUtil;
@@ -32,7 +38,6 @@ import racal.sunyard.main.dto.DTO7A;
import racal.sunyard.main.proto.*;
import javax.security.auth.x500.X500Principal;
import java.beans.FeatureDescriptor;
import java.io.*;
import java.net.InetSocketAddress;
import java.net.Socket;
@@ -46,7 +51,10 @@ import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.time.Duration;
import java.util.*;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import static com.sunyard.util.DnUtil.verifyDn;
import static org.bouncycastle.asn1.ASN1Encoding.DL;
@@ -1296,6 +1304,76 @@ public class SydApi4j implements SydApi {
return dn;
}
public void initSnmpTrapConfig(String config) {
Gson gson = new Gson();
Map<String, String> map =
gson.fromJson(config, new TypeToken<Map<String, String>>() {}.getType());
try {
SnmpTrapSender.getInstance().loadConfig(map);
} catch (IOException e) {
throw new SydApiException("snmp配置错误", -1);
}
}
@Override
public void importCertToServers(List<String> addressList, int timeout, String cert) {
if (CollectionUtils.isEmpty(addressList)) {
throw new RuntimeException("签名服务器地址不能为空");
}
List<TaskContext<String>> taskContexts =
addressList.stream().map(p -> createTaskContext(p, timeout, cert)).collect(Collectors.toList());
AsyncTaskExecutor executor = new AsyncTaskExecutor();
List<TaskOutcome<String>> taskOutcomes = executor.execBatchTasks(taskContexts);
//失败的记录日志
List<String> failures = taskOutcomes.stream()
.filter(out -> !out.isSuccess()).map(task -> task.getContext().getAddress())
.collect(Collectors.toList());
if (CollectionUtils.isNotEmpty(failures)) {
//异步发送snmp trap消息
String result = "[" + failures.stream()
.map(Object::toString)
.collect(Collectors.joining(",")) + "]";
System.out.println(result);
String message = "servers:" + result + ",import cert:" + cert + ",failed!";
try {
SnmpTrapSender.getInstance().sendTrap(message, 1);
} catch (Exception e) {
System.out.println("消息发送失败!");
}
}
}
private TaskContext<String> createTaskContext(String address, int timeout, String cert) {
Supplier<String> task = () -> importCertToServer(address, timeout, cert);
//重试2次,间隔1s
return new TaskContext<>(task, 2, Duration.ofSeconds(1), address);
}
private String importCertToServer(String address, int timeout, String cert){
//address 格式为 ip:port,拆分成ip和port,需要进行格式判断
String[] addressParts = address.split(":");
String ip = addressParts[0];
int port = Integer.parseInt(addressParts[1]);
String dn = null;
SydApi4j sydApi4j = null;
try {
//创建sydapi4j连接对象
sydApi4j = (SydApi4j) new SydApi4j().connect(ip, port, null, timeout);
//导入证书
dn = sydApi4j.importCertAndGetDN(cert);
} catch (Exception e) {
throw e;
} finally {
if (sydApi4j != null) {
sydApi4j.disconnect();
}
}
return dn;
}
public boolean attachedVerifyAll(int rFlag, String sign){
try {
boolean sm2Cert = CertUtil.isSignedBySM2Cert(sign);