标准收发器客户端

This commit is contained in:
xydkj 2026-05-22 11:31:04 +08:00
parent 9ec88c3093
commit 3800a19528
14 changed files with 139 additions and 221 deletions

View File

@ -6,31 +6,22 @@
<artifactId>openapi-sdk</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.11</version>
</parent>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<java.version>1.8</java.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
<version>1.18.42</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
@ -43,24 +34,19 @@
<version>1.83</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.12.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.swagger.core.v3</groupId>
<artifactId>swagger-annotations-jakarta</artifactId>
<version>2.2.28</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.19.4</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,34 @@
package com.cisd.openapi;
import com.cisd.openapi.config.OkHttpClientHolder;
import com.cisd.openapi.config.SignServerProperties;
import com.cisd.openapi.config.SignServerPropertiesLoader;
import com.fasterxml.jackson.databind.ObjectMapper;
import okhttp3.OkHttpClient;
import java.nio.charset.StandardCharsets;
public class Main
{
public static void main(String[] args) {
OkHttpClient okHttpClient = OkHttpClientHolder.getClient();
ObjectMapper objectMapper = new ObjectMapper();
SignServerProperties properties = SignServerPropertiesLoader.getProperties();
SignClient signClient = new SignClient(
okHttpClient,
objectMapper,
properties
);
try {
signClient.rawSign("test".getBytes(StandardCharsets.UTF_8), "dn");
} catch (Exception e) {
System.out.println("签名失败:" + e.getMessage());
Integer code = signClient.lastReturnCode();
System.out.println("lastReturnCode = " + code);
}
}
}

View File

@ -1,15 +0,0 @@
package com.cisd.openapi;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
@SpringBootApplication
@ConfigurationPropertiesScan
public class OpenapiSdkApplication
{
public static void main( String[] args ) {
SpringApplication.run(OpenapiSdkApplication.class, args);
}
}

View File

@ -7,14 +7,13 @@ import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import okhttp3.*;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.security.cert.X509Certificate;
import java.util.Base64;
import java.util.UUID;
@Component
public class SignClient{
private static final MediaType JSON_MEDIA_TYPE =
@ -22,8 +21,8 @@ public class SignClient {
private final OkHttpClient okHttpClient;
private final ObjectMapper objectMapper;
private final SignServerProperties properties;
private final SignServerProperties properties;
private final ThreadLocal<Integer> lastReturnCode = new ThreadLocal<>();
public SignClient(OkHttpClient okHttpClient,
@ -44,7 +43,7 @@ public class SignClient {
throw new IllegalArgumentException("origBytes不能为空");
}
if (dn == null || dn.isBlank()) {
if (dn == null || dn.trim().isEmpty()) {
lastReturnCode.set(-100100);
throw new IllegalArgumentException("dn不能为空");
}
@ -101,7 +100,7 @@ public class SignClient {
}
} catch (IOException e) {
throw new RuntimeException("调用rawSign异常", e);
throw new RuntimeException("调用rawSign异常:" + e.getMessage(), e);
}
}
@ -115,12 +114,12 @@ public class SignClient {
throw new IllegalArgumentException("origBytes不能为空");
}
if (dn == null || dn.isBlank()) {
if (dn == null || dn.trim().isEmpty()) {
lastReturnCode.set(-100100);
throw new IllegalArgumentException("dn不能为空");
}
if (certStr == null || certStr.isBlank()) {
if (certStr == null || certStr.trim().isEmpty()) {
lastReturnCode.set(-100100);
throw new IllegalArgumentException("certStr签名串不能为空");
}
@ -190,7 +189,7 @@ public class SignClient {
throw new IllegalArgumentException("origBytes不能为空");
}
if (dn == null || dn.isBlank()) {
if (dn == null || dn.trim().isEmpty()) {
throw new IllegalArgumentException("dn不能为空");
}
@ -256,7 +255,7 @@ public class SignClient {
throw new IllegalArgumentException("origBytes不能为空");
}
if (certStr == null || certStr.isBlank()) {
if (certStr == null || certStr.trim().isEmpty()) {
throw new IllegalArgumentException("certStr签名串不能为空");
}
@ -326,7 +325,7 @@ public class SignClient {
throw new IllegalArgumentException("origBytes不能为空");
}
if (certStr == null || certStr.isBlank()) {
if (certStr == null || certStr.trim().isEmpty()) {
throw new IllegalArgumentException("certStr签名串不能为空");
}
@ -389,7 +388,7 @@ public class SignClient {
String sessionId = UUID.randomUUID().toString().replace("-", "");
if (bankCode == null || bankCode.isBlank()) {
if (bankCode == null || bankCode.trim().isEmpty()) {
throw new IllegalArgumentException("origBytes不能为空");
}

View File

@ -0,0 +1,21 @@
package com.cisd.openapi.config;
import okhttp3.OkHttpClient;
import java.time.Duration;
public class OkHttpClientHolder {
private static final OkHttpClient CLIENT = new OkHttpClient.Builder()
.connectTimeout(Duration.ofSeconds(5))
.writeTimeout(Duration.ofSeconds(10))
.readTimeout(Duration.ofSeconds(30))
.build();
private OkHttpClientHolder() {
}
public static OkHttpClient getClient() {
return CLIENT;
}
}

View File

@ -1,20 +0,0 @@
package com.cisd.openapi.config;
import okhttp3.OkHttpClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.time.Duration;
@Configuration
public class OkHttpConfig {
@Bean
public OkHttpClient okHttpClient() {
return new OkHttpClient.Builder()
.connectTimeout(Duration.ofSeconds(5))
.writeTimeout(Duration.ofSeconds(10))
.readTimeout(Duration.ofSeconds(30))
.build();
}
}

View File

@ -1,11 +1,7 @@
package com.cisd.openapi.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "sign-server")
@Data
public class SignServerProperties {

View File

@ -0,0 +1,48 @@
package com.cisd.openapi.config;
import java.io.InputStream;
import java.util.Properties;
public class SignServerPropertiesLoader {
private static final SignServerProperties PROPERTIES = load();
private SignServerPropertiesLoader() {
}
public static SignServerProperties getProperties() {
return PROPERTIES;
}
private static SignServerProperties load() {
Properties props = new Properties();
try (InputStream inputStream = SignServerPropertiesLoader.class
.getClassLoader()
.getResourceAsStream("sign-server.properties")) {
if (inputStream == null) {
throw new RuntimeException("未找到配置文件 sign-server.properties");
}
props.load(inputStream);
SignServerProperties config = new SignServerProperties();
config.setBaseUrl(props.getProperty("sign-server.baseUrl"));
config.setApiKey(props.getProperty("sign-server.apiKey"));
config.setRawSignPath(props.getProperty("sign-server.rawSignPath"));
config.setRawVerifyPath(props.getProperty("sign-server.rawVerifyPath"));
config.setDettachedSignPath(props.getProperty("sign-server.dettachedSignPath"));
config.setDettachedVerifyPath(props.getProperty("sign-server.dettachedVerifyPath"));
config.setDettachedVerifySimplePath(props.getProperty("sign-server.dettachedVerifySimplePath"));
config.setUploadCertPath(props.getProperty("sign-server.uploadCertPath"));
config.setLastReturnCodePath(props.getProperty("sign-server.lastReturnCodePath"));
return config;
} catch (Exception e) {
throw new RuntimeException("加载 sign-server 配置失败", e);
}
}
}

View File

@ -1,19 +0,0 @@
package com.cisd.openapi.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
public class DeviceActivateInfoResponse {
@Schema(description = "设备型号", example = "SYD7108")
private String deviceModel;
@Schema(description = "设备名称", example = "跨境支付终端一体机")
private String deviceName;
@Schema(description = "设备版本", example = "1.0.0")
private String version;
@Schema(description = "设备序列号", example = "1234567890ABCDEF")
private String serialNumber;
@Schema(description = "设备状态", example = "INITIALIZED")
private String deviceStatus;
}

View File

@ -1,11 +0,0 @@
package com.cisd.openapi.dto;
import lombok.Data;
@Data
public class DeviceActivateRequest {
private String userId;
private String credential;
private String activationCode;
}

View File

@ -1,13 +0,0 @@
server:
port: 8680
sign-server:
base-url: http://172.1.41.249:8080/api/v1/openapi
api-key:
raw-sign-path: /rawSign
raw-verify-path: /rawVerify
dettached-sign-path: /dettachedSign
dettached-verify-path: /dettachedVerify
dettached-verify-simple-path: /dettachedVerifySimple
upload-cert-path: /uploadCert
last-return-code-path: /lastReturnCode

View File

@ -0,0 +1,12 @@
sign-server.baseUrl=http://127.0.0.1:8080
sign-server.apiKey=key
sign-server.rawSignPath=/raw/sign
sign-server.rawVerifyPath=/raw/verify
sign-server.dettachedSignPath=/dettached/sign
sign-server.dettachedVerifyPath=/dettached/verify
sign-server.dettachedVerifySimplePath=/dettached/verify/simple
sign-server.uploadCertPath=/cert/upload
sign-server.lastReturnCodePath=/last/return/code

View File

@ -1,90 +0,0 @@
package com.cisd.openapi;
import com.cisd.openapi.dto.GenericCertificate;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import java.io.FileInputStream;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
@SpringBootTest
@ActiveProfiles("test")
class SignClientTest {
@Autowired
private SignClient signClient;
private final byte[] origBytes = "11111111".getBytes();
private final String dn = "C=CN,ST=Beijing,L=Beijing,O=Initial Org,OU=Initial OU,CN=Initial Entity";
@Test
void testRawSign() {
String signature = signClient.rawSign(origBytes, dn);
System.out.println("rawSign: " + signature);
}
@Test
void testRawVerify() {
String signature = signClient.rawSign(origBytes, dn);
boolean verified = signClient.rawVerify(origBytes, signature, dn);
System.out.println("rawVerify: " + verified);
}
@Test
void testDettachedSign() {
String dettachedSig = signClient.dettachedSign(origBytes, dn);
System.out.println("dettachedSign: " + dettachedSig);
}
@Test
void testDettachedVerify() {
String dettachedSignature = signClient.dettachedSign(origBytes, dn);
GenericCertificate cert = signClient.dettachedVerify(origBytes, dettachedSignature);
System.out.println("dettachedVerify 证书主题: " + cert.getSubject());
}
@Test
void testDettachedVerifySimple() {
String dettachedSig = signClient.dettachedSign(origBytes, dn);
GenericCertificate cert = signClient.dettachedVerifySimple(origBytes, dettachedSig);
System.out.println("dettachedVerifySimple 证书主题: " + cert.getSubject());
}
@Test
void testUploadCert() throws Exception {
String certFilePath = "path";
X509Certificate certificate;
try {
certificate = loadCertificate(certFilePath);
} catch (Exception e) {
System.out.println("加载失败" + certFilePath);
return;
}
boolean success = signClient.uploadCert("bankCode", certificate);
System.out.println("uploadCert : " + success);
}
@Test
void testLastReturnCode() {
Integer code = signClient.lastReturnCode();
System.out.println("lastReturnCode: " + code);
}
private X509Certificate loadCertificate(String filePath) throws Exception {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
try (FileInputStream fis = new FileInputStream(filePath)) {
return (X509Certificate) cf.generateCertificate(fis);
}
}
}

View File

@ -1,10 +0,0 @@
sign-server:
base-url: http://172.1.41.249:8080/api/v1/openapi
api-key:
raw-sign-path: /rawSign
raw-verify-path: /rawVerify
dettached-sign-path: /dettachedSign
dettached-verify-path: /dettachedVerify
dettached-verify-simple-path: /dettachedVerifySimple
upload-cert-path: /uploadCert
last-return-code-path: /lastReturnCode