[feature] 优化模块命名

This commit is contained in:
Weasley J
2023-08-15 17:36:53 +08:00
parent fab95d6ee3
commit 1f954a992b
117 changed files with 27 additions and 3803 deletions
+22 -23
View File
@@ -77,31 +77,30 @@
![image-20230802165954038](https://weasley.oss-cn-shanghai.aliyuncs.com/Photos/image-20230802165954038.png)
- 加签失败邮件通知告警
- 加签失败邮件告警
> [此功能需手动开启](https://github.com/Weasley-J/chinaport-data-signature/blob/main/chinaport-data-signature-app/src/main/resources/application-dev.yml#L25-L43),开关属性: `spring.mail.enable`, 取值: `on/off`
> [此功能需手动开启](https://github.com/Weasley-J/chinaport-data-signature/blob/main/chinaport-data-signature-webapp/src/main/resources/application-dev.yml#L25-L43),开关属性: `spring.mail.enable`, 取值: `on/off`,也可以告警消息接入企业微信、钉钉、飞书、Discord等企业沟通软件。
<figure>
<img src="https://weasley.oss-cn-shanghai.aliyuncs.com/Photos/iShot_2023-08-10_13.56.34.png" alt="邮件通知效果">
<figcaption>加签失败邮件通知</figcaption>
</figure>
## 1 支持的电子口岸
> [核心加签接口](https://github.com/Weasley-J/chinaport-data-signature/blob/main/%E4%B8%AD%E5%9B%BD%E7%94%B5%E5%AD%90%E5%8F%A3%E5%B2%B8%E6%8A%A5%E6%96%87%E5%8A%A0%E7%AD%BE%E6%8E%A5%E5%8F%A3%E6%96%87%E6%A1%A3.md#%E6%B5%B7%E5%85%B3%E6%95%B0%E6%8D%AE%E5%8A%A0%E7%AD%BE)理论上支持中国内地的大部分**地方电子口岸**
| 电子口岸 | 是否支持 | 是否验证 |
| ------------ | -------- |-----|
| 海口电子口岸 | ✅ | ✅ |
| 广州电子口岸 | ✅ | ✅ |
| 郑州电子口岸 | ✅ | 待验证 |
| 上海电子口岸 | ✅ | 待验证 |
| 浙江电子口岸 | ✅ | 待验证 |
| ... | | |
> [核心加签接口](https://github.com/Weasley-J/chinaport-data-signature/blob/main/%E4%B8%AD%E5%9B%BD%E7%94%B5%E5%AD%90%E5%8F%A3%E5%B2%B8%E6%8A%A5%E6%96%87%E5%8A%A0%E7%AD%BE%E6%8E%A5%E5%8F%A3%E6%96%87%E6%A1%A3.md#%E6%B5%B7%E5%85%B3%E6%95%B0%E6%8D%AE%E5%8A%A0%E7%AD%BE)
> 理论上支持中国内地的大部分**地方电子口岸**
| 电子口岸 | 是否支持 | 是否验证 |
|--------|------|------|
| 海口电子口岸 | ✅ | ✅ |
| 广州电子口岸 | ✅ | ✅ |
| 郑州电子口岸 | ✅ | 待验证 |
| 上海电子口岸 | ✅ | 待验证 |
| 杭州电子口岸 | ✅ | 待验证 |
| ... | | |
## 2 项目代码质量
@@ -123,23 +122,23 @@
> 本应用是标准的`springboot`应用,支持`springboot`
> 的所有配置参数,推荐通过在[线`yaml`转`properties`](https://www.toyaml.com/index.html)将配置元数据复制到命令行参数中按需使用,
- [推荐元数据配置示例](https://github.com/Weasley-J/chinaport-data-signature/blob/main/chinaport-data-signature-app/src/main/resources/application-dev.yml#L43-L71)
- [推荐元数据配置示例](https://github.com/Weasley-J/chinaport-data-signature/blob/main/chinaport-data-signature-webapp/src/main/resources/application-dev.yml#L43-L71)
- [加签异常邮件通知配置示例](https://github.com/Weasley-J/chinaport-data-signature/blob/main/chinaport-data-signature-app/src/main/resources/application-dev.yml#L23-L41)
- [加签异常邮件通知配置示例](https://github.com/Weasley-J/chinaport-data-signature/blob/main/chinaport-data-signature-webapp/src/main/resources/application-dev.yml#L23-L41)
| 配置参数 | 是否必须(Y/N) | 解释 |
| ------------------------------------------------ | --------------- | ------------------------------------------------------------ |
| eport.signature.algorithm | N | [XML签名的算法类型](https://github.com/Weasley-J/chinaport-data-signature/blob/main/chinaport-data-signature-app/src/main/java/cn/alphahub/eport/signature/config/SignatureAlgorithmProperties.java#L20),不指定时使用程序自动推断的算法, 推荐使用程序自动推断的算法 |
| eport.signature.algorithm | N | [XML签名的算法类型](https://github.com/Weasley-J/chinaport-data-signature/blob/main/chinaport-data-signature-webapp/src/main/java/cn/alphahub/eport/signature/config/SignatureAlgorithmProperties.java#L20),不指定时使用程序自动推断的算法, 推荐使用程序自动推断的算法 |
| eport.signature.ukey.ws-url | Y | `u-key`所连接的`Windows`电脑的`socket`链接`url`,如: `ws://127.0.0.1:61232`,下载`release`直接运行的修改全局配置的`UKEY_HOST`即可 |
| eport.signature.ukey.password | N | u-key密码的密码,默认: `88888888`, 如果密码改过,需要指定下,下载`release`直接运行的修改全局配置的`UKEY_PASSWORD`即可 |
| eport.signature.ukey.health.endpoint.client-name | N | Windows上重启的ukey可执行文件全限定文件名称,不指定将自动查找(将程序安装到Windows平台上会生效,Linux、Uni环境无效) |
| eport.signature.auth.enable | N | [是否启用token鉴权](https://github.com/Weasley-J/chinaport-data-signature/blob/main/chinaport-data-signature-app/src/main/java/cn/alphahub/eport/signature/config/AuthenticationProperties.java#L17),取值:<u>on/off</u>,生产环境建议开启 |
| eport.signature.auth.token | N | 客户端请求鉴权token, 默认值: DefaultAuthToken, [请求头](https://github.com/Weasley-J/chinaport-data-signature/blob/main/chinaport-data-signature-app/src/main/java/cn/alphahub/eport/signature/config/AuthenticationProperties.java#L24),<u>生产环境不建议使用默认值</u> |
| eport.signature.report.ceb-message.cop-code | Y | [电子口岸XML报文](https://github.com/Weasley-J/chinaport-data-signature/blob/main/chinaport-data-signature-app/src/main/java/cn/alphahub/eport/signature/config/ChinaEportProperties.java#L20)传输企业代码 |
| eport.signature.auth.enable | N | [是否启用token鉴权](https://github.com/Weasley-J/chinaport-data-signature/blob/main/chinaport-data-signature-webapp/src/main/java/cn/alphahub/eport/signature/config/AuthenticationProperties.java#L17),取值:<u>on/off</u>,生产环境建议开启 |
| eport.signature.auth.token | N | 客户端请求鉴权token, 默认值: DefaultAuthToken, [请求头](https://github.com/Weasley-J/chinaport-data-signature/blob/main/chinaport-data-signature-webapp/src/main/java/cn/alphahub/eport/signature/config/AuthenticationProperties.java#L24),<u>生产环境不建议使用默认值</u> |
| eport.signature.report.ceb-message.cop-code | Y | [电子口岸XML报文](https://github.com/Weasley-J/chinaport-data-signature/blob/main/chinaport-data-signature-webapp/src/main/java/cn/alphahub/eport/signature/config/ChinaEportProperties.java#L20)传输企业代码 |
| eport.signature.report.ceb-message.cop-name | Y | 传输企业名称,报文传输的企业名称 |
| eport.signature.report.ceb-message.dxp-id | Y | 文传输编号,向中国电子口岸数据中心申请数据交换平台的用户编号 |
| eport.signature.report.ceb-message.server | N | [海关服务器地址](https://github.com/Weasley-J/chinaport-data-signature/blob/main/chinaport-data-signature-app/src/main/java/cn/alphahub/eport/signature/config/ChinaEportProperties.java#L43)<u>缺省则采用Client中的密文作文默认Server URL</u> |
| eport.signature.report.customs179.ebp-code | Y | [海关 179 数据上报](https://github.com/Weasley-J/chinaport-data-signature/blob/main/chinaport-data-signature-app/src/main/java/cn/alphahub/eport/signature/config/Customs179Properties.java#L24)的电商平台代码 |
| eport.signature.report.ceb-message.server | N | [海关服务器地址](https://github.com/Weasley-J/chinaport-data-signature/blob/main/chinaport-data-signature-webapp/src/main/java/cn/alphahub/eport/signature/config/ChinaEportProperties.java#L43)<u>缺省则采用Client中的密文作文默认Server URL</u> |
| eport.signature.report.customs179.ebp-code | Y | [海关 179 数据上报](https://github.com/Weasley-J/chinaport-data-signature/blob/main/chinaport-data-signature-webapp/src/main/java/cn/alphahub/eport/signature/config/Customs179Properties.java#L24)的电商平台代码 |
| eport.signature.report.customs179.server | N | 数据上报服务器URL地址,链接格式: https://域名联系海关/ceb2grab/grab/realTimeDataUpload<u>没有配置的话采用项目内置的URL密文地址</u> |
## 5 项目启动脚本
@@ -156,7 +155,7 @@
#!/bin/bash
# Environment args
APP="chinaport-data-signature-app"
APP="chinaport-data-signature-webapp"
UKEY_HOST="127.0.0.1"
UKEY_PASSWORD="88888888"
JVM_ARGS="-Xms1g -Xmx1g"
-11
View File
@@ -1,11 +0,0 @@
FROM azul/zulu-openjdk:17-jre
MAINTAINER weasley
WORKDIR /app
COPY target/chinaport-data-signature-*.jar /app/chinaport-data-signature.jar
EXPOSE 8080
ENV TZ=PRC
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ >/etc/timezone
ENV JAVA_OPTS=""
ENV SPRING_ARGS=""
ENTRYPOINT ["sh", "-c", "java ${JVM_OPTIONS} -jar chinaport-data-signature.jar ${SPRING_ARGS}"]
@@ -1,8 +0,0 @@
#!/bin/bash
TAG="1.1.1"
docker stop chinaport-data-signature && docker rm -f chinaport-data-signature
docker rmi -f weasleyj/chinaport-data-signature:${TAG}-arm64
docker build -t weasleyj/chinaport-data-signature:${TAG}-arm64 . -f ./Dockerfile
docker login -u weasleyj
docker push weasleyj/chinaport-data-signature:${TAG}-arm64
exit 0
@@ -1,8 +0,0 @@
#!/bin/bash
TAG="latest"
docker stop chinaport-data-signature && docker rm -f chinaport-data-signature
docker rmi -f weasleyj/chinaport-data-signature:${TAG}
docker build -t weasleyj/chinaport-data-signature:${TAG} . -f ./Dockerfile
docker login -u weasleyj
docker push weasleyj/chinaport-data-signature:${TAG}
exit 0
@@ -1,20 +0,0 @@
package cn.alphahub.eport.signature;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
/**
* 中国电子口岸(海口海关)报文加签推送应用
*
* @author Weasley J
* @date 2022-02-13
* @since 1.0.0
*/
@EnableWebSocket
@SpringBootApplication
public class ChinaEportSignatureApplication {
public static void main(String[] args) {
SpringApplication.run(ChinaEportSignatureApplication.class, args);
}
}
@@ -1,41 +0,0 @@
package cn.alphahub.eport.signature.base.exception;
import lombok.Getter;
import java.io.Serial;
/**
* The eport web client exception
*
* @author weasley
* @version 1.1.0
*/
@Getter
public class EportWebClientException extends RuntimeException {
@Serial
private static final long serialVersionUID = 1L;
/**
* 异常消息
*/
private String msg;
/**
* 错误码, 默认: 500
*/
private int code = 500;
public EportWebClientException(String msg) {
super(msg);
this.msg = msg;
}
public EportWebClientException(String msg, Throwable e) {
super(msg, e);
this.msg = msg;
}
public EportWebClientException(String msg, int code, Throwable e) {
super(msg, e);
this.msg = msg;
this.code = code;
}
}
@@ -1,49 +0,0 @@
package cn.alphahub.eport.signature.base.exception;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import java.io.Serial;
/**
* 自定义异常
*
* @author liuwenjing
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class SignException extends RuntimeException {
@Serial
private static final long serialVersionUID = 1L;
/**
* 异常消息
*/
private String msg;
/**
* 错误码, 默认500
*/
private int code = 500;
public SignException(String msg) {
super(msg);
this.msg = msg;
}
public SignException(String msg, Throwable e) {
super(msg, e);
this.msg = msg;
}
public SignException(String msg, int code, Throwable e) {
super(msg, e);
this.msg = msg;
this.code = code;
}
}
@@ -1,44 +0,0 @@
package cn.alphahub.eport.signature.config;
import cn.alphahub.eport.signature.core.ChinaEportReportClient;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.validation.annotation.Validated;
/**
* 电子口岸XML报文传输的企业元数据配置
*
* @author weasley
* @version 1.1.0
*/
@Data
@Validated
@Configuration
@ConfigurationProperties(prefix = ChinaEportProperties.PREFIX)
public class ChinaEportProperties {
public static final String PREFIX = "eport.signature.report.ceb-message";
/**
* 传输企业代码,报文传输的企业代码(需要与接入客户端的企业身份一致)
*/
@NotBlank(message = "配置文件中的报文传输的企业代码不能为空")
private String copCode;
/**
* 传输企业名称,报文传输的企业名称
*/
@NotBlank(message = "报文传输的企业名称不能为空")
private String copName;
/**
* 报文传输编号,向中国电子口岸数据中心申请数据交换平台的用户编号
*/
@NotBlank(message = "向中国电子口岸数据中心申请数据交换平台的用户编号不能为空")
private String dxpId;
/**
* 海关服务器地址,缺省则采用Client中的密文作文默认Server URL
*
* @apiNote CEMMessage XML数据上报服务器地址,格式: http://ip:port
* @see ChinaEportReportClient#EPORT_CEBMESSAGE_SERVER_ENCODE
*/
private String server;
}
@@ -1,33 +0,0 @@
package cn.alphahub.eport.signature.config;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;
/**
* 海关 179 数据上报配置元数据
*
* @author weasley
* @version 1.1.0
* @apiNote 如果接口没有传过来, 就取
*/
@Data
@Validated
@ConfigurationProperties(prefix = Customs179Properties.PREFIX)
public class Customs179Properties {
public static final String PREFIX = "eport.signature.report.customs179";
/**
* 电商平台代码
*
* @apiNote 电商平台海关注册登记编号或统一社会信用代码。境内企业需据实填写海关注册登记编号或统一社会信用代码,境外平台企业如无法提供电商平台代码可填“无”。
*/
@NotBlank(message = "电商平台代码不能为空;如果你想对接CEBMessage进出口单, 先随便填一个,后面改成正确的")
private String ebpCode;
/**
* 数据上报服务器URL地址,链接格式: https://域名联系海关获取/ceb2grab/grab/realTimeDataUpload
*
* @apiNote 没有配置的话采用项目内置的URL地址
*/
private String server;
}
@@ -1,45 +0,0 @@
package cn.alphahub.eport.signature.config;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
import java.util.Objects;
/**
* XML Signature Algorithm
*
* @author weasley
* @version 1.0.0
* @apiNote 签名算法
*/
@Getter
@AllArgsConstructor
public enum SignatureAlgorithm {
RSA_SHA1("rsa-sha1", "SHA1WITHRSA", "http://www.w3.org/2000/09/xmldsig#rsa-sha1"),
SM2_SM3("sm2-sm3", "SM3WITHSM2", "http://www.w3.org/2000/09/xmldsig#sm2-sm3"),
;
/**
* 算法类型
*/
private final String algorithmType;
/**
* 算法名称
*/
private final String algorithmName;
/**
* XML代码段中 ds:SignatureMethod 的算法值
* <p>
* XML Signature Method:
* <pre>
* {@code <ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#${algorithmType}"/>}
* </pre>
*/
private final String signatureMethod;
public static SignatureAlgorithm getSignatureAlgorithmSigAlgName(String sigAlgName) {
return Arrays.stream(values())
.filter(original -> Objects.equals(original.getAlgorithmName(), sigAlgName))
.findFirst().orElse(null);
}
}
@@ -1,158 +0,0 @@
package cn.alphahub.eport.signature.config;
import cn.alphahub.eport.signature.support.CommandClient;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.SystemUtils;
import org.apache.logging.log4j.util.Strings;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import static cn.alphahub.eport.signature.config.ICommand.EPORT_ACCESS_CONTROL;
import static cn.alphahub.eport.signature.config.ICommand.EPORT_WS_SERVER;
import static cn.alphahub.eport.signature.config.UkeyProperties.PREFIX;
/**
* An interface to build command
*/
@FunctionalInterface
interface ICommand extends Serializable {
/**
* WebSocketServer.exe
*/
String EPORT_WS_SERVER = "CnEport.Pub.WebSocketServer.exe";
/**
* SetAccessControl.exe
*/
String EPORT_ACCESS_CONTROL = "SetAccessControl.exe";
/**
* To build command
*
* @param exeClient e.g: C:\中国电子口岸客户端控件\SetAccessControl.exe
* @return command script
*/
String build(String exeClient);
}
/**
* Ukey自我重启相关
* <p>
* Windows上的可执行文件: C:\中国电子口岸客户端控件\SetAccessControl.exe
*
* @author weasley
* @version 1.0.0
* @apiNote 可执行文件 SetAccessControl.exe 的安装位置以实际为准
*/
@Data
@Slf4j
@ConfigurationProperties(prefix = PREFIX + ".health.endpoint")
public class UkeyAccessClientProperties {
/**
* Windows上重启的ukey可执行文件全限定文件名称
*
* @apiNote 建议指定 clientName,可以避免全盘查找 SetAccessControl.exe, 提高性能
* @apiNote 全限定文件名称,包含文件后缀, 建议不要安装到中文目录里面,如: C:\中国电子口岸客户端控件\SetAccessControl.exe
*/
private String clientName;
/**
* 查找中国电子口岸客户端控件重启客户端的Windows可执行文件的具体位置
*
* @return SetAccessControl.exe 文件的绝对路径
* @apiNote 以下命令行只适用于Windows平台,其他平台报错直接忽略
*/
public String findClient() {
if (!SystemUtils.IS_OS_WINDOWS) return null;
CommandClient sharedInstance = CommandClient.getSharedInstance();
List<String> accessControlStdOutList;
List<String> eportWsServerStdOutList;
sharedInstance.execute("where " + EPORT_ACCESS_CONTROL);
accessControlStdOutList = new ArrayList<>(sharedInstance.getStdOutList());
if (CollectionUtils.isEmpty(accessControlStdOutList)) return null;
sharedInstance.execute("where " + EPORT_WS_SERVER);
eportWsServerStdOutList = new ArrayList<>(sharedInstance.getStdOutList());
if (CollectionUtils.isEmpty(eportWsServerStdOutList)) return null;
String accessControlStdOut = FilenameUtils.getFullPathNoEndSeparator(accessControlStdOutList.get(0));
String eportWsServerStdOut = FilenameUtils.getFullPathNoEndSeparator(eportWsServerStdOutList.get(0));
if (accessControlStdOut.equals(eportWsServerStdOut)) {
log.info("找到u-key Windows客户端执行文件: {} {}", EPORT_WS_SERVER, EPORT_ACCESS_CONTROL);
return accessControlStdOutList.get(0);
}
return null;
}
/**
* 执行指令
* <pre>
* -h 或 -help: 帮助
* -restart: 重启控件
* -start: 启动控件
* -stop: 停止控件
* -repair: 修复证书
* </pre>
*/
@Getter
@AllArgsConstructor
public enum Command implements ICommand {
/**
* 重启控件
*/
RESTART("-restart", "重启控件"),
/**
* 启动控件
*/
START("-start", "启动控件"),
/**
* 停止控件
*/
STOP("-stop", "停止控件"),
/**
* 修复证书
*/
REPAIR("-repair", "修复证书"),
/**
* 缺省符,无业务意义
*/
DEFAULT("", "缺省符");
/**
* 指令
*/
private final String cmd;
/**
* 描述
*/
private final String desc;
public String getCmd() {
if (RESTART.cmd.equals(cmd)) {
return cmd + " -i";
}
return cmd;
}
@Override
public String build(String exeClient) {
if (null == exeClient) return Strings.EMPTY;
if (StringUtils.isBlank(this.getCmd())) {
return "cmd /c \"" + exeClient + "\"";
}
return "cmd /c \"" + exeClient + " " + this.getCmd() + "\"";
}
}
}
@@ -1,300 +0,0 @@
package cn.alphahub.eport.signature.config;
import cn.alphahub.dtt.plus.util.JacksonUtil;
import cn.alphahub.dtt.plus.util.SpringUtil;
import cn.alphahub.eport.signature.core.CertificateHandler;
import cn.alphahub.eport.signature.core.SignHandler;
import cn.alphahub.eport.signature.core.WebSocketClientHandler;
import cn.alphahub.eport.signature.entity.SignRequest;
import cn.alphahub.eport.signature.entity.SpcValidTime;
import cn.alphahub.eport.signature.entity.UkeyRequest;
import cn.alphahub.eport.signature.entity.UkeyResponse;
import cn.alphahub.eport.signature.entity.WebSocketWrapper;
import cn.alphahub.eport.signature.support.CertificateParser;
import cn.hutool.core.lang.TypeReference;
import lombok.Data;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.client.WebSocketConnectionManager;
import org.springframework.web.socket.client.standard.StandardWebSocketClient;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import java.io.IOException;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.LockSupport;
import static cn.alphahub.eport.signature.core.CertificateHandler.SING_DATA_METHOD;
import static cn.hutool.json.JSONUtil.toBean;
import static cn.hutool.json.JSONUtil.toJsonStr;
/**
* 初始化配置
*
* @author lwj
* @version 1.0
* @date 2022-01-11 15:29
*/
@Data
@Slf4j
@Configuration
@EnableConfigurationProperties({UkeyProperties.class, EmailProperties.class, SignatureAlgorithmProperties.class, Customs179Properties.class})
public class UkeyInitialConfig implements ApplicationRunner {
/**
* u-key默认密码8个8不要修改
*/
private static final String DEFAULT_PASSWORD = "88888888";
/**
* 取海关签名证书PEM, X509Certificate证书(未经HASH算法散列过)
*
* @return 取海关签名证书PEM, X509Certificate证书是加签证书,真正的X509Certificate证书
*/
public static String getX509CertificateParameter() {
UkeyRequest ukeyRequest = new UkeyRequest();
ukeyRequest.set_method("cus-sec_SpcGetSignCertAsPEM");
ukeyRequest.set_id(1);
ukeyRequest.setArgs(new HashMap<>());
return JacksonUtil.toJson(ukeyRequest);
}
/**
* 使用卡计算摘要,返回PEM格式信息, 这个不调也罢,已经硬核计算出来了
*
* @param sourceXml 不含ds:Signature节点的<ceb:CEBXxxMessage></ceb:CEBXxxMessage>源xml原文数据
* @param uniqueId uniqueId 唯一id, 用来区分是哪一次发送的消息,int32,最大32位大于0
* @return DigestValue的值
*/
public static String getDigestValueParameter(String sourceXml, Integer uniqueId) {
Map<String, Object> args = new LinkedHashMap<>(2);
args.put("szInfo", sourceXml);
args.put("passwd", ObjectUtils.defaultIfNull(SpringUtil.getBean(UkeyProperties.class).getPassword(), DEFAULT_PASSWORD));
UkeyRequest ukeyRequest = new UkeyRequest();
ukeyRequest.set_method("sec_SpcSHA1DigestAsPEM");
ukeyRequest.set_id(uniqueId);
ukeyRequest.setArgs(args);
return JacksonUtil.toJson(ukeyRequest);
}
/**
* 签名, 不对原文计算摘要, 请您自行计算好摘要传入
*
* @param request 原文入参
* @return SignatureValue的值, 返回的数组,包含您的证书编号,可作为KeyName的值
*/
public static String getSignDataAsPEM(SignRequest request) {
String initData = SignHandler.getInitData(request);
log.warn("发送给ukey的真正请求入参: {}\n原始报文: {}", initData, request.getData());
@SuppressWarnings({"all"}) UkeyRequest ukeyRequest = new UkeyRequest("cus-sec_SpcSignDataAsPEM", new HashMap<>() {{
put("inData", initData);
put("passwd", ObjectUtils.defaultIfNull(SpringUtil.getBean(UkeyProperties.class).getPassword(), DEFAULT_PASSWORD));
}});
ukeyRequest.set_id(request.getId());
return JacksonUtil.toJson(ukeyRequest);
}
/**
* 使用卡计算摘要,返回PEM格式信息
*
* @return ukey响应数据,示例: B959CD298E3BA6DAE360FB8CDB87B68B66D8BD221474046232910DD5FDA54354
*/
public static String getSHA1DigestAsPEMParams(SignRequest request) {
Map<String, Object> args = new LinkedHashMap<>();
String initData = SignHandler.getInitData(request);
log.warn("使用卡计算摘要,返回PEM格式信息: \n{}原始报文:\n{}", initData, request.getData());
args.put("szInfo", initData);
args.put("passwd", ObjectUtils.defaultIfNull(SpringUtil.getBean(UkeyProperties.class).getPassword(), DEFAULT_PASSWORD));
UkeyRequest ukeyRequest = new UkeyRequest();
ukeyRequest.set_method("cus-sec_SpcSHA1DigestAsPEM");
ukeyRequest.set_id(request.getId());
ukeyRequest.setArgs(args);
return JacksonUtil.toJson(ukeyRequest);
}
/**
* To park thread
*/
private static void parkThread(AtomicReference<Thread> reference, WebSocketConnectionManager socketConnectionManager) {
socketConnectionManager.start();
try {
LockSupport.parkNanos(reference.get(), 1000 * 1000 * 1000 * 3L);
} catch (Exception e) {
log.error("线程自动unpark异常 {}", e.getLocalizedMessage(), e);
} finally {
socketConnectionManager.stop();
}
}
/**
* Callback used to run the bean.
*
* @param args incoming application arguments
*/
@Override
public void run(ApplicationArguments args) throws IOException {
org.apache.xml.security.Init.init();
if (log.isDebugEnabled()) {
log.debug("XMl output format with C14n Initial Succeed.");
}
}
/**
* IOC中返回一个WebSocket包装类的实例对象
*
* @return WebSocket包装类
*/
@Bean
@Scope(scopeName = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public WebSocketWrapper webSocketWrapper() {
return new WebSocketWrapper();
}
/**
* 基于标准 Java WebSocket API 的 WebSocketClient
*
* @return An instance of StandardWebSocketClient
*/
@Bean
@ConditionalOnMissingBean({StandardWebSocketClient.class})
public StandardWebSocketClient standardWebSocketClient() {
return new StandardWebSocketClient();
}
/**
* 注入一个X509Certificate证书判断的Bean
*
* @return X509Certificate证书判断
*/
@Bean
@SneakyThrows
public CertificateHandler certificateHandler(UkeyProperties ukeyProperties, StandardWebSocketClient standardWebSocketClient) {
CertificateHandler certificateHandler = new CertificateHandler();
Map<String, String> certMap = new ConcurrentHashMap<>();
AtomicReference<Thread> reference = new AtomicReference<>();
reference.set(Thread.currentThread());
// 获取u-key内的证书相关
WebSocketConnectionManager certManager = new WebSocketConnectionManager(standardWebSocketClient, new TextWebSocketHandler() {
final UkeyRequest ukeyRequest = new UkeyRequest("cus-sec_SpcGetSignCertAsPEM", new HashMap<>());
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
session.sendMessage(new TextMessage(JacksonUtil.toJson(ukeyRequest)));
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) {
UkeyResponse response = toBean(message.getPayload(), new TypeReference<>() {
}, true);
try {
if (Objects.equals(response.get_id(), 1)) {
UkeyResponse.Args responseArgs = response.get_args();
if (responseArgs.getResult().equals(true) && CollectionUtils.isNotEmpty(responseArgs.getData())) {
certMap.put(SING_DATA_METHOD, responseArgs.getData().get(0));
log.warn("已从电子口岸u-key中获取到x509Certificate证书: {}", JacksonUtil.toJson(certMap));
}
}
} catch (Exception e) {
log.error("唤醒线程异常 {}", e.getLocalizedMessage(), e);
} finally {
LockSupport.unpark(reference.get());
}
}
}, ukeyProperties.getWsUrl());
parkThread(reference, certManager);
// 获取u-key签发日期, 查看海关证书有效期
WebSocketConnectionManager ukeyValidTimeManager = new WebSocketConnectionManager(standardWebSocketClient, new TextWebSocketHandler() {
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
session.sendMessage(new TextMessage(toJsonStr(new UkeyRequest("cus-sec_SpcGetValidTimeFromCert", 1, new LinkedHashMap<>()))));
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) {
UkeyResponse response = toBean(message.getPayload(), new TypeReference<>() {
}, true);
try {
if (Objects.equals(response.get_id(), 1)) {
UkeyResponse.Args responseArgs = response.get_args();
if (responseArgs.getResult().equals(true) && CollectionUtils.isNotEmpty(responseArgs.getData())) {
log.warn("从电子口岸u-key中获取到u-key有效期区间: {}", responseArgs.getData());
SpcValidTime validTime = toBean(responseArgs.getData().get(0), new TypeReference<>() {
}, true);
certificateHandler.setUkeyValidTimeBegin(validTime.getSzStartTime());
certificateHandler.setUkeyValidTimeEnd(validTime.getSzEndTime());
}
}
} catch (Exception e) {
log.error("唤醒线程异常 {}", e.getLocalizedMessage(), e);
} finally {
LockSupport.unpark(reference.get());
}
}
}, ukeyProperties.getWsUrl());
parkThread(reference, ukeyValidTimeManager);
//自动推断ukey使用的签名算法
WebSocketConnectionManager certSignatureAlgorithmManager = new WebSocketConnectionManager(standardWebSocketClient, new TextWebSocketHandler() {
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
log.info("开始推断ukey使用的签名算法:");
session.sendMessage(new TextMessage(toJsonStr(new UkeyRequest("cus-sec_SpcGetSignCertAsPEM", new LinkedHashMap<>()))));
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) {
UkeyResponse response = toBean(message.getPayload(), new TypeReference<>() {
}, true);
try {
if (Objects.equals(response.get_id(), 1)) {
UkeyResponse.Args responseArgs = response.get_args();
if (responseArgs.getResult().equals(true) && CollectionUtils.isNotEmpty(responseArgs.getData())) {
String x509Certificate = CertificateHandler.buildX509CertificateWithHeader(responseArgs.getData().get(0));
X509Certificate certificateByCertText = CertificateParser.parseCertificateByCertText(x509Certificate);
if (null != SignatureAlgorithm.getSignatureAlgorithmSigAlgName(certificateByCertText.getSigAlgName())) {
certificateHandler.setAlgorithm(SignatureAlgorithm.getSignatureAlgorithmSigAlgName(certificateByCertText.getSigAlgName()));
}
}
}
} catch (Exception e) {
log.error("唤醒线程异常 {}", e.getLocalizedMessage(), e);
} finally {
LockSupport.unpark(reference.get());
}
}
}, ukeyProperties.getWsUrl());
parkThread(reference, certSignatureAlgorithmManager);
certificateHandler.setX509Map(certMap);
return certificateHandler;
}
/**
* @return An instance of WebSocketClientHandler
*/
@Bean
@ConditionalOnMissingBean({WebSocketClientHandler.class})
public WebSocketClientHandler webSocketClientHandler(UkeyProperties ukeyProperties, WebSocketWrapper webSocketWrapper, CertificateHandler certificateHandler) {
return new WebSocketClientHandler(ukeyProperties, webSocketWrapper, certificateHandler);
}
}
@@ -1,29 +0,0 @@
package cn.alphahub.eport.signature.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;
import static cn.alphahub.eport.signature.config.UkeyProperties.PREFIX;
/**
* 电子口岸u-key的配置参数
*
* @author weasley
* @version 1.0
* @date 2022/2/13
*/
@Data
@Validated
@ConfigurationProperties(prefix = PREFIX)
public class UkeyProperties {
public static final String PREFIX = "eport.signature.ukey";
/**
* socket链接url
*/
private String wsUrl = "ws://127.0.0.1:61232";
/**
* u-key密码
*/
private String password = "88888888";
}
@@ -1,197 +0,0 @@
package cn.alphahub.eport.signature.config;
import cn.alphahub.eport.signature.base.exception.EportWebClientException;
import cn.alphahub.eport.signature.core.web.EportCebMessageHttpClient;
import cn.alphahub.eport.signature.core.web.EportCustoms179HttpClient;
import cn.alphahub.eport.signature.core.web.EportReportResultHttpClient;
import cn.alphahub.eport.signature.core.web.WebfluxDemoHttpClient;
import cn.alphahub.eport.signature.support.OriginalPropertyNamingStrategy;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.tomcat.util.codec.binary.Base64;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.codec.json.Jackson2JsonEncoder;
import org.springframework.web.reactive.function.client.ClientRequest;
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
import org.springframework.web.reactive.function.client.ExchangeStrategies;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.support.WebClientAdapter;
import org.springframework.web.service.invoker.HttpServiceProxyFactory;
import reactor.core.publisher.Mono;
import java.util.Objects;
import java.util.function.Consumer;
import static cn.alphahub.eport.signature.core.ChinaEportReportClient.EPORT_CEBMESSAGE_SERVER_ENCODE;
import static cn.alphahub.eport.signature.core.ChinaEportReportClient.REPORT_PROD_ENV_179_URL_ENCODE;
/**
* The WebClient Configuration
*
* @author weasley
* @version 1.0.0
*/
@Slf4j
@Configuration
@EnableConfigurationProperties({Customs179Properties.class, ChinaEportProperties.class})
public class WebClientConfiguration {
public static final ThreadLocal<Consumer<HttpHeaders>> HTTP_HEADERS = new ThreadLocal<>();
/**
* @return A proxy bean of EportCebMessageHttpClient
*/
@Bean
public EportCebMessageHttpClient eportCebMessageHttpClient(ObjectMapper objectMapper, ChinaEportProperties chinaEportProperties) {
WebClient webClient = WebClient.builder()
.exchangeStrategies(customExchangeStrategies(objectMapper))
.filter(((request, next) -> next.exchange(Objects.requireNonNull(injectHeader(request).block()))))
.filters(exchangeFilters -> {
exchangeFilters.add(logRequest());
exchangeFilters.add(logResponse());
})
.defaultStatusHandler(HttpStatusCode::isError, resp -> Mono.just(new EportWebClientException("Web Client 调用发生异常!")))
.baseUrl(StringUtils.defaultIfBlank(chinaEportProperties.getServer(), new String(Base64.decodeBase64(EPORT_CEBMESSAGE_SERVER_ENCODE))))
.build();
HttpServiceProxyFactory httpServiceProxyFactory = HttpServiceProxyFactory.builder(WebClientAdapter.forClient(webClient))
.build();
return httpServiceProxyFactory.createClient(EportCebMessageHttpClient.class);
}
/**
* @return A proxy bean of EportCustoms179HttpClient
*/
@Bean
public EportCustoms179HttpClient eportCustoms179HttpClient(ObjectMapper objectMapper, Customs179Properties customs179Properties) {
WebClient webClient = WebClient.builder()
.exchangeStrategies(ExchangeStrategies.builder().codecs(configurer -> {
configurer.defaultCodecs().jackson2JsonEncoder(new Jackson2JsonEncoder(enhanceSourceMapper(objectMapper)));
}).build())
.filters(exchangeFilters -> {
exchangeFilters.add(((request, next) -> next.exchange(Objects.requireNonNull(injectHeader(request).block()))));
exchangeFilters.add(logRequest());
exchangeFilters.add(logResponse());
})
.defaultStatusHandler(HttpStatusCode::isError, resp -> Mono.just(new EportWebClientException("Web Client 调用发生异常!")))
.baseUrl(StringUtils.defaultIfBlank(customs179Properties.getServer(), new String(Base64.decodeBase64(REPORT_PROD_ENV_179_URL_ENCODE))))
.build();
HttpServiceProxyFactory httpServiceProxyFactory = HttpServiceProxyFactory.builder(WebClientAdapter.forClient(webClient)).build();
return httpServiceProxyFactory.createClient(EportCustoms179HttpClient.class);
}
/**
* @return A proxy bean of EportReportResultHttpClient
*/
@Bean
public EportReportResultHttpClient eportReportResultHttpClient(ChinaEportProperties chinaEportProperties) {
String baseUrl = StringUtils.defaultIfBlank(chinaEportProperties.getServer(), new String(Base64.decodeBase64(EPORT_CEBMESSAGE_SERVER_ENCODE)));
WebClient webClient = WebClient.builder()
.filter(logRequest())
.filter(logResponse())
.defaultStatusHandler(HttpStatusCode::isError, resp -> Mono.just(new EportWebClientException("Web Client 调用发生异常!")))
.baseUrl(baseUrl)
.build();
return HttpServiceProxyFactory.builder(WebClientAdapter.forClient(webClient)).build()
.createClient(EportReportResultHttpClient.class);
}
/**
* @return A proxy bean of WebfluxDemoHttpClient
*/
@Bean
public WebfluxDemoHttpClient webfluxDemoHttpClient(ObjectMapper objectMapper) {
WebClient webClient = WebClient.builder()
.exchangeStrategies(customExchangeStrategies(objectMapper))
.filters(exchangeFilters -> {
exchangeFilters.add(((request, next) -> next.exchange(Objects.requireNonNull(injectHeader(request).block()))));
exchangeFilters.add(logRequest());
exchangeFilters.add(logResponse());
})
.defaultStatusHandler(HttpStatusCode::isError, resp -> Mono.just(new EportWebClientException("Web Client 调用发生异常!")))
.baseUrl("http://127.0.0.1:8080")
.build();
WebClientAdapter clientAdapter = WebClientAdapter.forClient(webClient);
HttpServiceProxyFactory httpServiceProxyFactory = HttpServiceProxyFactory.builder(clientAdapter).build();
return httpServiceProxyFactory.createClient(WebfluxDemoHttpClient.class);
}
/**
* To inject headers
*/
public Mono<ClientRequest> injectHeader(final ClientRequest clientRequest) {
if (null != HTTP_HEADERS.get()) {
Mono<ClientRequest> clientRequestMono = Mono.just(ClientRequest.from(clientRequest)
.headers(HTTP_HEADERS.get())
.build());
clearHttpHeaders();
return clientRequestMono;
}
return Mono.just(ClientRequest.from(clientRequest)
.build());
}
/**
* Clear Http Headers
*/
public void clearHttpHeaders() {
if (null != HTTP_HEADERS.get()) {
HTTP_HEADERS.remove();
}
}
/**
* Custom exchange strategies
*/
public ExchangeStrategies customExchangeStrategies(ObjectMapper objectMapper) {
return ExchangeStrategies.builder()
.codecs(configurer -> {
configurer.defaultCodecs().jackson2JsonEncoder(new Jackson2JsonEncoder(enhanceSourceMapper(objectMapper)));
}).build();
}
/**
* To enhance mapper
*/
private ObjectMapper enhanceSourceMapper(ObjectMapper objectMapper) {
ObjectMapper enhanceMapper = objectMapper.copy();
enhanceMapper.setPropertyNamingStrategy(new OriginalPropertyNamingStrategy());
return enhanceMapper;
}
/**
* log web request
*/
private ExchangeFilterFunction logRequest() {
return ExchangeFilterFunction.ofRequestProcessor(clientRequest -> {
if (log.isInfoEnabled()) {
log.info("Request URL: " + clientRequest.url());
log.info("Request Method: " + clientRequest.method());
log.info("Request Headers: " + clientRequest.headers());
if (clientRequest.method() == HttpMethod.POST || clientRequest.method() == HttpMethod.PUT) {
log.info("Request Body: " + clientRequest.body());
}
}
return Mono.just(clientRequest);
});
}
/**
* log web response
*/
private ExchangeFilterFunction logResponse() {
return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {
if (log.isInfoEnabled()) {
log.info("Response status: {}", clientResponse.statusCode());
log.info("Response headers: {}", clientResponse.headers().asHttpHeaders());
}
return Mono.just(clientResponse);
});
}
}
@@ -1,80 +0,0 @@
package cn.alphahub.eport.signature.controller.rpc;
import cn.alphahub.eport.signature.base.domain.Result;
import cn.alphahub.eport.signature.config.ChinaEportProperties;
import cn.alphahub.eport.signature.core.web.EportReportResultHttpClient;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
* 查询申报回执结果
*
* @author weasley
* @version 1.1.0
* @apiNote 目前仅实现:CEB311Message、CEB621Message 申报结果查询
*/
@Slf4j
@RestController
@RequestMapping("/rpc/eport/result")
public class EportReportResultController {
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
@Autowired
private ChinaEportProperties chinaEportProperties;
@Autowired
private EportReportResultHttpClient eportReportResultHttpClient;
/**
* 查询311进口单申报结果
*
* @param qryId 查询时间,日期时间格式:yyyyMMddHHmmss
* @response {
* "message": "操作成功",
* "success": true,
* "timestamp": "2023-08-09 15:18:25",
* "code": 200,
* "data": "[{org_id=1, instm=1691397836000, re_order_guid=NWIVQA-WEASLEY-20230807164347-GCVZ4S, returnstatus=2, ebpcode=46016602EV, returntime2=20230807164356, ebccode=46016602EV, trans_dxpid=DXPENT0000530815, returninfo=新增申报成功[NWIVQA-WEASLEY-20230807164347-GCVZ4S], returntime=1691397830000, orderno=JO_D8079556739228400022}]"
* }
* @apiNote 查询此时间点之后的数据,必须在当前时间点之前,默认查询当前时间前15秒的申报数据,当前时间:20230806144417,则 qryId=20230806144401,不传的情况适用于刚刚申报成功查询回执结果,海关的处理时间间隔约为15秒
* @since 1.1.1
*/
@GetMapping("/ceb312msg")
public Result<String> getCeb312msgResult(@RequestParam(required = false) @DateTimeFormat(pattern = "yyyyMMddHHmmss") LocalDateTime qryId) {
qryId = ObjectUtils.defaultIfNull(qryId, LocalDateTime.now().minusSeconds(15));
log.info("开始查询311申报回执结果, qryId {}", FORMATTER.format(qryId));
Mono<String> msgResult = eportReportResultHttpClient.getCeb312msgResult(chinaEportProperties.getDxpId(), FORMATTER.format(qryId));
return Result.ok(msgResult.block());
}
/**
* 查询621进口单申报结果
*
* @param qryId 查询时间,日期时间格式:yyyyMMddHHmmss
* @response {
* "message": "操作成功",
* "success": true,
* "timestamp": "2023-08-09 15:26:53",
* "code": 200,
* "data": "[{org_id=1, instm=1691397837000, agentcode=46016602EV, re_elist_guid_cus=9422f96b-ccbc-4a69-a899-6d9dd583d501, returnstatus=2, ebpcode=46016602EV, preno=B20230807570793612, returntime2=20230807164357, ebccode=46016602EV, createtime=20230807164351, trans_dxpid=DXPENT0000530815, returninfo=清单新增申报成功[3CQHDG-WEASLEY-20230807164348-7KYRQN][电商企业编码:46016602EV订单编号:JO_D8079556739228400022], returntime=1691397830000, re_elist_guid=3CQHDG-WEASLEY-20230807164348-7KYRQN, customscode=6409}, {org_id=1, instm=1691397847000, agentcode=46016602EV, re_elist_guid_cus=9422f96b-ccbc-4a69-a899-6d9dd583d501, returnstatus=120, ebpcode=46016602EV, preno=B20230807570793612, returntime2=20230807164407, ebccode=46016602EV, createtime=20230807164355, trans_dxpid=DXPENT0000530815, returninfo=[Code:1800;Desc:逻辑校验通过], returntime=1691397831000, re_elist_guid=4e7a2dab-064b-410b-9ce6-e322c590ae3d, customscode=6409, invtno=64092023I000722460}, {org_id=1, instm=1691397847000, agentcode=46016602EV, re_elist_guid_cus=9422f96b-ccbc-4a69-a899-6d9dd583d501, returnstatus=800, ebpcode=46016602EV, preno=B20230807570793612, returntime2=20230807164407, ebccode=46016602EV, createtime=20230807164355, trans_dxpid=DXPENT0000530815, returninfo=[Code:2600;Desc:放行], returntime=1691397832000, re_elist_guid=4e7a2dab-064b-410b-9ce6-e322c590ae3d, customscode=6409, invtno=64092023I000722460}]"
* }
* @apiNote 查询此时间点之后的数据,必须在当前时间点之前,默认查询当前时间前15秒的申报数据,当前时间:20230806144417,则 qryId=20230806144401,不传的情况适用于刚刚申报成功查询回执结果,海关的处理时间间隔约为15秒
* @since 1.1.1
*/
@GetMapping("/ceb622msg")
public Result<String> getCe622msgResult(@RequestParam(required = false) @DateTimeFormat(pattern = "yyyyMMddHHmmss") LocalDateTime qryId) {
qryId = ObjectUtils.defaultIfNull(qryId, LocalDateTime.now().minusSeconds(15));
log.info("开始查询621进口单申报回执结果, qryId {}", FORMATTER.format(qryId));
Mono<String> msgResult = eportReportResultHttpClient.getCe622msgResult(chinaEportProperties.getDxpId(), FORMATTER.format(qryId));
return Result.ok(msgResult.block());
}
}
@@ -1,130 +0,0 @@
package cn.alphahub.eport.signature.core;
import cn.alphahub.eport.signature.config.SignatureAlgorithm;
import cn.hutool.core.date.LocalDateTimeUtil;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.Map;
import static cn.alphahub.eport.signature.config.SignatureAlgorithm.RSA_SHA1;
/**
* X509Certificate证书
*
* @author weasley
* @version 1.0
* @date 2022/2/13
*/
@Data
@Slf4j
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class CertificateHandler implements Serializable {
/**
* 从u-key读取含发送推送 X509Certificate 证书对应的加签方法
*/
public static final String SING_DATA_METHOD = "cus-sec_SpcSignDataAsPEM";
/**
* 暂定2022-07-01为第一个时间分界
*/
public static final LocalDateTime DATE_TIME_202207 = LocalDateTimeUtil.parse("2022-07-01", "yyyy-MM-dd");
/**
* u-key的.cer证书是否存在
*/
private Boolean certExists = false;
/**
* XML 签名算法, RSA_SHA1 兜底
*/
private SignatureAlgorithm algorithm = RSA_SHA1;
/**
* 判断用那一个证书的集合
* <ul>
* <li>key -> 从u-key读取<ds:SignatureValue>的方法名</li>
* <li>value -> 从u-key读取<ds:SignatureValue>的方法名对应的X509Certificate证书</li>
* </ul>
*/
@Getter
private Map<String, String> x509Map;
/**
* 海关u-key证书有效期开始
*/
private LocalDateTime ukeyValidTimeBegin;
/**
* 海关u-key证书有效期结束
*/
private LocalDateTime ukeyValidTimeEnd;
/**
* 组装X509Certificate证书
* <p>
* 组装64位长度一行的pem分割的pem
*
* @param certPomFromUkey 从ukey里面读取的pem
* @return 64位长度一行的pem分割的pem
* @apiNote 不带Header
*/
public static String buildX509CertificateWithoutHeader(String certPomFromUkey) {
certPomFromUkey = certPomFromUkey.trim();
StringBuilder sb = new StringBuilder();
int count = 0;
for (char c : certPomFromUkey.toCharArray()) {
sb.append(c);
count++;
if (count == 64) {
sb.append("\n");
count = 0;
}
}
return sb.toString().trim();
}
/**
* 组装X509Certificate证书
* <p>
* 组装64位长度一行的pem分割的pem
*
* @param certPomFromUkey 从ukey里面读取的pem
* @return 64位长度一行的pem分割的pem
* @apiNote 带Header
*/
public static String buildX509CertificateWithHeader(String certPomFromUkey) {
return "-----BEGIN CERTIFICATE-----\n"
.concat(buildX509CertificateWithoutHeader(certPomFromUkey)).concat("\n")
.concat("-----END CERTIFICATE-----");
}
public LocalDateTime getUkeyValidTimeBegin() {
if (ukeyValidTimeBegin == null) {
return LocalDateTime.now();
}
return ukeyValidTimeBegin;
}
public void setX509Map(Map<String, String> x509Map) {
this.x509Map = x509Map;
}
/**
* 获取509Certificate证书
*
* @param method 发送给电子口岸u-key的入参的[_method]字段的值
* @return 509Certificate证书
*/
public String getX509Certificate(String method) {
return switch (method) {
case SING_DATA_METHOD -> this.getX509Map().get(SING_DATA_METHOD);
default -> throw new IllegalStateException("Unexpected value: " + method);
};
}
}
@@ -1,476 +0,0 @@
package cn.alphahub.eport.signature.core;
import cn.alphahub.eport.signature.base.exception.SignException;
import cn.alphahub.eport.signature.config.ChinaEportProperties;
import cn.alphahub.eport.signature.config.Customs179Properties;
import cn.alphahub.eport.signature.config.UkeyProperties;
import cn.alphahub.eport.signature.entity.Capture179DataRequest;
import cn.alphahub.eport.signature.entity.Capture179DataResponse;
import cn.alphahub.eport.signature.entity.SignRequest;
import cn.alphahub.eport.signature.entity.SignResult;
import cn.alphahub.eport.signature.entity.ThirdAbstractResponse;
import cn.alphahub.eport.signature.entity.UkeyRequest;
import cn.alphahub.eport.signature.entity.UkeyResponse.Args;
import cn.alphahub.eport.signature.entity.UploadCEBMessageRequest;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.lang.TypeReference;
import cn.hutool.http.ContentType;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;
import io.github.weasleyj.china.eport.sign.AbstractCebMessage;
import io.github.weasleyj.china.eport.sign.IMessageType;
import io.github.weasleyj.china.eport.sign.model.cebmessage.BaseTransfer;
import io.github.weasleyj.china.eport.sign.model.cebmessage.CEB311Message;
import io.github.weasleyj.china.eport.sign.model.cebmessage.CEB621Message;
import io.github.weasleyj.china.eport.sign.model.cebmessage.export.CEB303Message;
import io.github.weasleyj.china.eport.sign.model.customs179.Customs179Request;
import io.github.weasleyj.china.eport.sign.model.request.Message;
import io.github.weasleyj.china.eport.sign.model.request.MessageBody;
import io.github.weasleyj.china.eport.sign.model.request.MessageHead;
import io.github.weasleyj.china.eport.sign.model.request.MessageRequest;
import io.github.weasleyj.china.eport.sign.model.signature.CanonicalizationMethod;
import io.github.weasleyj.china.eport.sign.model.signature.DigestMethod;
import io.github.weasleyj.china.eport.sign.model.signature.KeyInfo;
import io.github.weasleyj.china.eport.sign.model.signature.Reference;
import io.github.weasleyj.china.eport.sign.model.signature.Signature;
import io.github.weasleyj.china.eport.sign.model.signature.SignatureMethod;
import io.github.weasleyj.china.eport.sign.model.signature.SignedInfo;
import io.github.weasleyj.china.eport.sign.model.signature.Transforms;
import io.github.weasleyj.china.eport.sign.model.signature.X509Data;
import io.github.weasleyj.china.eport.sign.util.GUIDUtil;
import io.github.weasleyj.china.eport.sign.util.JAXBUtil;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import static cn.alphahub.dtt.plus.util.JacksonUtil.readValue;
import static cn.alphahub.dtt.plus.util.JacksonUtil.toJson;
import static cn.alphahub.eport.signature.core.CertificateHandler.SING_DATA_METHOD;
/**
* 电子口岸上报客户端
*
* @author weasley
* @since 1.1.0
*/
@Slf4j
@Component
@AllArgsConstructor
public class ChinaEportReportClient {
/**
* 海关服务器地址,格式: http://ip:port
*
* @apiNote base64加密下,不适合直接公布到公网
*/
public static final String EPORT_CEBMESSAGE_SERVER_ENCODE = "aHR0cDovLzM2LjEwMS4yMDguMjMwOjgwOTA=";
/**
* 海关 179 数据抓取测试环境 URL (base64加密,不适合直接公布到外网)
*/
public static final String REPORT_TEST_ENV_179_URL_ENCODE = "aHR0cHM6Ly9zd2FwcHRlc3Quc2luZ2xld2luZG93LmNuL2NlYjJncmFiL2dyYWIvcmVhbFRpbWVEYXRhVXBsb2Fk";
/**
* 海关 179 数据抓取生产环境 URL (base64加密,不适合直接公布到外网)
*/
public static final String REPORT_PROD_ENV_179_URL_ENCODE = "aHR0cHM6Ly9jdXN0b21zLmNoaW5hcG9ydC5nb3YuY24vY2ViMmdyYWIvZ3JhYi9yZWFsVGltZURhdGFVcGxvYWQ=";
private final SignHandler signHandler;
private final UkeyProperties ukeyProperties;
private final Customs179Properties customs179Properties;
private final ChinaEportProperties chinaEportProperties;
/**
* 构建 ds:Signature 节点
*
* @param signResult 加签结果
* @return ds:Signature 节点
*/
public static String buildSignatureNode(SignResult signResult) {
KeyInfo keyInfo = KeyInfo.builder()
.keyName(signResult.getCertNo())
.x509Data(new X509Data(signResult.getX509Certificate()))
.build();
Reference reference = Reference.builder()
.digestMethod(new DigestMethod())
.uri("")
.transforms(new Transforms())
.digestValue(signResult.getDigestValue())
.build();
SignedInfo signedInfo = SignedInfo.builder()
.CanonicalizationMethod(new CanonicalizationMethod())
.SignatureMethod(new SignatureMethod().setAlgorithm(SignatureHandler.getSignatureMethodAlgorithm()))
.reference(reference)
.build();
Signature signature = Signature.builder()
.keyInfo(keyInfo)
.signatureValue(signResult.getSignatureValue())
.signedInfo(signedInfo)
.build();
return JAXBUtil.toXml(signature);
}
/**
* 构建 ds:SignedInfo 节点
*
* @param signResult 加签结果
* @return ds:SignedInfo 节点
*/
public static String buildSignedInfoNode(SignResult signResult) {
Reference reference = Reference.builder()
.digestMethod(new DigestMethod())
.uri("")
.transforms(new Transforms())
.digestValue(signResult.getDigestValue())
.build();
SignedInfo signedInfo = SignedInfo.builder()
.CanonicalizationMethod(new CanonicalizationMethod())
.SignatureMethod(new SignatureMethod().setAlgorithm(SignatureHandler.getSignatureMethodAlgorithm()))
.reference(reference)
.build();
return JAXBUtil.toXml(signedInfo);
}
/**
* CebXxxMessage数据上报海关
*
* @param cebMessage 原始xml对象
* @param messageType CEB311Message|CEB621Message|...
* @return 推送结果, OK: 表示成功, 报关结果需要自己查询回执
*/
public ThirdAbstractResponse<MessageRequest, String, String> report(AbstractCebMessage cebMessage, IMessageType messageType) {
log.info("数据上报海关xml入参: {}, {}", JSONUtil.toJsonStr(cebMessage), messageType.getType());
MessageRequest messageRequest = buildMessageRequest(cebMessage, messageType);
String requestServer = Base64.decodeStr(EPORT_CEBMESSAGE_SERVER_ENCODE);
requestServer = StringUtils.defaultIfBlank(chinaEportProperties.getServer(), requestServer);
String requestBody = JSONUtil.toJsonStr(messageRequest);
log.info("数据上报海关请求入参 {}\nRequest Server: {}", requestBody, requestServer);
HttpResponse httpResponse = HttpUtil.createPost(requestServer + "/cebcmsg")
.contentType(ContentType.JSON.getValue())
.body(requestBody)
.execute();
String responseBody = httpResponse.body();
log.info("数据上报Http响应结果: {}", responseBody);
if (!"OK".equals(responseBody)) {
log.error("上报海关请求异常, 请求入参: {}\n海关Http响应: {}", requestBody, responseBody);
}
ThirdAbstractResponse<MessageRequest, String, String> thirdResponse = ThirdAbstractResponse.getInstance();
thirdResponse.setPayload(messageRequest);
thirdResponse.setOriginal(responseBody);
thirdResponse.setExpected("OK");
return thirdResponse;
}
/**
* 海关 179 数据抓取
* <p>
* <ul>
* <li>
* 期望的成功数据返回示例
* <pre>
* {"code":"10000","message":"","serviceTime":1567050097628}
* </pre>
* </li>
* </ul>
*/
public ThirdAbstractResponse<Map<String, Object>, String, Capture179DataResponse> capture179Data(Capture179DataRequest request) {
log.info("海关 179 数据抓取 {}", JSONUtil.toJsonStr(request));
Customs179Request customs179Request = new Customs179Request();
customs179Request.setSessionID(request.getSessionID());
customs179Request.setPayExchangeInfoHead(request.getPayExchangeInfoHead());
customs179Request.setPayExchangeInfoLists(request.getPayExchangeInfoLists());
customs179Request.setServiceTime(request.getServiceTime());
String guid = GUIDUtil.getGuid();
customs179Request.setSessionID(guid);
customs179Request.getPayExchangeInfoHead().setGuid(guid);
customs179Request.getPayExchangeInfoHead().setEbpCode(customs179Properties.getEbpCode());
String testEnvServerUrl = Base64.decodeStr(REPORT_TEST_ENV_179_URL_ENCODE);
String prodEnvServerUrl = Base64.decodeStr(REPORT_PROD_ENV_179_URL_ENCODE);
String requestUrl = StringUtils.defaultIfBlank(customs179Properties.getServer(), prodEnvServerUrl);
String capture179DataUkeyRequest = "\"sessionID\":\"" + customs179Request.getSessionID() + "\"||" +
"\"payExchangeInfoHead\":\"" + toJson(customs179Request.getPayExchangeInfoHead()) + "\"||" +
"\"payExchangeInfoLists\":\"" + toJson(customs179Request.getPayExchangeInfoLists()) + "\"||" +
"\"serviceTime\":" + "\"" + customs179Request.getServiceTime() + "\"";
@SuppressWarnings("all") UkeyRequest ukeyRequest = new UkeyRequest(SING_DATA_METHOD, new HashMap<>() {{
put("inData", capture179DataUkeyRequest);
put("passwd", ukeyProperties.getPassword());
}});
String signParams = toJson(ukeyRequest);
SignRequest signRequest = new SignRequest(capture179DataUkeyRequest);
SignResult signResult = signHandler.sign(signRequest, signParams);
if (Boolean.FALSE.equals(signResult.getSuccess())) {
throw new SignException("海关 179 数据抓取加签失败, ukey加签入参: " + signParams + ", 原始入参: " + toJson(customs179Request));
}
customs179Request.setCertNo(signResult.getCertNo());
customs179Request.setSignValue(signResult.getSignatureValue());
Map<String, Object> formData = new LinkedHashMap<>();
formData.put("payExInfoStr", toJson(customs179Request));
log.info("海关179数据抓取URL: {}\n开始上报,请求数据:{}", requestUrl, toJson(formData));
HttpResponse httpResponse = HttpUtil.createPost(requestUrl)
.form(formData)
.header("Content-Type", "application/x-www-form-urlencoded", true)
.timeout(5 * 1000)
.execute();
log.info("海关 179 数据抓取返回结果 {}", httpResponse.body());
Capture179DataResponse expected = readValue("""
{"code":"10000","message":"上传成功","total":0,"serviceTime":null}
""", Capture179DataResponse.class);
expected.setServiceTime(Long.parseLong(customs179Request.getServiceTime()));
ThirdAbstractResponse<Map<String, Object>, String, Capture179DataResponse> thirdResponse = ThirdAbstractResponse.getInstance();
thirdResponse.setPayload(formData);
thirdResponse.setOriginal(httpResponse.body());
thirdResponse.setExpected(expected);
return thirdResponse;
}
/**
* 推断CebMessage的具体的XML入参解析成Java对象
*
* @implNote 已实现: CEB311Message,CEB621Message,CEB303Message
*/
@SuppressWarnings("all")
public AbstractCebMessage buildCebMessage(UploadCEBMessageRequest request) {
String guid = GUIDUtil.getGuid();
String cebMessage = String.valueOf(request.getCebMessage());
switch (request.getDataType()) {
case XML -> {
return switch (request.getMessageType()) {
// 进口单XML报文解析
case CEB311Message -> {
CEB311Message ceb311Message = Objects.requireNonNull(JAXBUtil.toBean(cebMessage, CEB311Message.class));
ceb311Message.setGuid(guid);
ceb311Message.getOrder().getOrderHead().setGuid(guid);
buildBaseTransfer(ceb311Message.getBaseTransfer());
yield ceb311Message;
}
case CEB312Message -> null;
case CEB411Message -> null;
case CEB412Message -> null;
case CEB511Message -> null;
case CEB512Message -> null;
case CEB513Message -> null;
case CEB514Message -> null;
case CEB621Message -> {
CEB621Message ceb621Message = Objects.requireNonNull(JAXBUtil.toBean(cebMessage, CEB621Message.class));
ceb621Message.setGuid(guid);
ceb621Message.getInventory().getInventoryHead().setGuid(guid);
buildBaseTransfer(ceb621Message.getBaseTransfer());
yield ceb621Message;
}
case CEB622Message -> null;
case CEB623Message -> null;
case CEB624Message -> null;
case CEB625Message -> null;
case CEB626Message -> null;
case CEB711Message -> null;
case CEB712Message -> null;
case CEB213Message -> null;
case CEB214Message -> null;
case CEB215Message -> null;
case CEB216Message -> null;
case CEB303Message -> {
CEB303Message ceb303Message = Objects.requireNonNull(JAXBUtil.toBean(cebMessage, CEB303Message.class));
ceb303Message.setGuid(guid);
ceb303Message.getOrder().getOrderHead().setGuid(guid);
buildBaseTransfer(ceb303Message.getBaseTransfer());
yield ceb303Message;
}
// 出口单XML报文
case CEB304Message -> null;
case CEB403Message -> null;
case CEB404Message -> null;
case CEB505Message -> null;
case CEB506Message -> null;
case CEB507Message -> null;
case CEB508Message -> null;
case CEB509Message -> null;
case CEB510Message -> null;
case CEB603Message -> null;
case CEB604Message -> null;
case CEB605Message -> null;
case CEB606Message -> null;
case CEB607Message -> null;
case CEB608Message -> null;
case CEB701Message -> null;
case CEB702Message -> null;
case CEB792Message -> null;
};
}
case JSON -> {
return switch (request.getMessageType()) {
case CEB311Message -> {
CEB311Message ceb311Message = JSONUtil.toBean(cebMessage, new TypeReference<>() {
}, false);
ceb311Message.setGuid(guid);
ceb311Message.getOrder().getOrderHead().setGuid(guid);
buildBaseTransfer(ceb311Message.getBaseTransfer());
yield ceb311Message;
}
// 进口单JSON报文解析
case CEB312Message -> null;
case CEB411Message -> null;
case CEB412Message -> null;
case CEB511Message -> null;
case CEB512Message -> null;
case CEB513Message -> null;
case CEB514Message -> null;
case CEB621Message -> {
CEB621Message ceb621Message = JSONUtil.toBean(cebMessage, new TypeReference<>() {
}, false);
ceb621Message.setGuid(guid);
ceb621Message.getInventory().getInventoryHead().setGuid(guid);
buildBaseTransfer(ceb621Message.getBaseTransfer());
yield ceb621Message;
}
case CEB622Message -> null;
case CEB623Message -> null;
case CEB624Message -> null;
case CEB625Message -> null;
case CEB626Message -> null;
case CEB711Message -> null;
case CEB712Message -> null;
case CEB213Message -> null;
case CEB214Message -> null;
case CEB215Message -> null;
case CEB216Message -> null;
// 出口单JSON报文解析
case CEB303Message -> {
CEB303Message ceb303Message = JSONUtil.toBean(cebMessage, new TypeReference<>() {
}, false);
ceb303Message.setGuid(guid);
ceb303Message.getOrder().getOrderHead().setGuid(guid);
buildBaseTransfer(ceb303Message.getBaseTransfer());
yield ceb303Message;
}
case CEB304Message -> null;
case CEB403Message -> null;
case CEB404Message -> null;
case CEB505Message -> null;
case CEB506Message -> null;
case CEB507Message -> null;
case CEB508Message -> null;
case CEB509Message -> null;
case CEB510Message -> null;
case CEB603Message -> null;
case CEB604Message -> null;
case CEB605Message -> null;
case CEB606Message -> null;
case CEB607Message -> null;
case CEB608Message -> null;
case CEB701Message -> null;
case CEB702Message -> null;
case CEB792Message -> null;
};
}
default -> throw new IllegalStateException("Unexpected value: " + toJson(request));
}
}
/**
* Build BaseTransfer XML Node
*/
@SuppressWarnings("all")
public BaseTransfer buildBaseTransfer(BaseTransfer baseTransfer) {
baseTransfer.setCopCode(StringUtils.defaultIfBlank(baseTransfer.getCopCode(), chinaEportProperties.getCopCode()));
baseTransfer.setCopName(StringUtils.defaultIfBlank(baseTransfer.getCopName(), chinaEportProperties.getCopName()));
baseTransfer.setDxpId(StringUtils.defaultIfBlank(baseTransfer.getDxpId(), chinaEportProperties.getDxpId()));
baseTransfer.setDxpMode(StringUtils.defaultIfBlank(baseTransfer.getDxpMode(), "DXP"));
return baseTransfer;
}
/**
* 组装最终请求数据,完成末三段加密
*/
public MessageRequest buildMessageRequest(AbstractCebMessage cebMessage, IMessageType messageType) {
if (cebMessage == null) {
return new MessageRequest();
}
String sourceXml = JAXBUtil.toXml(cebMessage);
MessageHead messageHead = new MessageHead();
messageHead.setMessageType(messageType.getType() + ".xml");
messageHead.setSendTime(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
sourceXml = sourceXml.replaceAll("xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\"", "");
SignRequest ukeyRequest = new SignRequest();
ukeyRequest.setId(1);
ukeyRequest.setData(sourceXml);
// 请求签名加密,2次组装数据, 请求加密(包含第1,2段加密)
String payload = signHandler.getSignDataParameter(ukeyRequest);
SignResult signResult = signHandler.sign(ukeyRequest, payload);
// 线程uppark时间可能会失败,增强方法兜底
if (Boolean.FALSE.equals(signResult.getSuccess())) {
this.enhanceSignResultIfDefective(signResult, sourceXml);
}
if (Boolean.FALSE.equals(signResult.getSuccess())) {
throw new SignException("CebXxxMessage XML数据加签失败, ukey加签入参: " + payload + ", 原始入参: " + toJson(ukeyRequest));
}
String xmlBody = buildXml(signResult, sourceXml, messageType);
// 第3次组装数据, 将加签后的 XM 进行 base64 加密
String encodedXml = Base64.encode(xmlBody);
MessageBody messageBody = MessageBody.builder().data(encodedXml).build();
Message message = Message.builder().MessageBody(messageBody).MessageHead(messageHead).build();
return new MessageRequest(message);
}
/**
* 增强 SignResult, 如果有缺陷: 如: 验签失败等场景
*
* @param signResult 签名结果
* @param sourceXml 原始XML
* @return 签名结果
* @apiNote 用于 SignHandler#sign 结果出现验签失败时的兜底手段
*/
@SuppressWarnings("all")
private SignResult enhanceSignResultIfDefective(SignResult signResult, String sourceXml) {
String signatureXml = SignHandler.getInitData(new SignRequest(sourceXml));
Map<String, Object> params = new HashMap<>();
params.put("inData", signatureXml);
params.put("passwd", ukeyProperties.getPassword());
Args args1 = signHandler.getUkeyResponseArgs(new UkeyRequest("cus-sec_SpcSignDataAsPEM", params));
Args args2 = signHandler.getUkeyResponseArgs(new UkeyRequest("cus-sec_SpcGetSignCertAsPEM", new HashMap<>()));
signResult.setCertNo(args1.getData().get(1));
signResult.setSignatureValue(args1.getData().get(0));
signResult.setX509Certificate(args2.getData().get(0));
if (signResult.getSuccess().equals(false)) {
signResult.setSuccess(true);
}
return signResult;
}
/**
* 组装Signature XML节点
*
* @param sourceXml 原始XML
*/
private String buildXml(SignResult signResult, String sourceXml, IMessageType messageType) {
String signatureNode = buildSignatureNode(signResult);
String xmlStart = "</ceb:" + messageType.getType() + ">";
int index = sourceXml.indexOf(xmlStart);
StringBuilder builder = new StringBuilder(sourceXml);
builder.insert(index, signatureNode.concat("\n"));
String finalXml = builder.toString();
if (finalXml.startsWith("<?xml ")) {
finalXml = finalXml.replace("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n", "");
}
log.info("上报海关的XML报文:\n{}", finalXml);
return finalXml;
}
}
@@ -1,173 +0,0 @@
package cn.alphahub.eport.signature.core;
import cn.alphahub.eport.signature.config.UkeyInitialConfig;
import cn.alphahub.eport.signature.config.UkeyProperties;
import cn.alphahub.eport.signature.entity.SignRequest;
import cn.alphahub.eport.signature.entity.SignResult;
import cn.alphahub.eport.signature.entity.UkeyRequest;
import cn.alphahub.eport.signature.entity.UkeyResponse;
import cn.alphahub.eport.signature.entity.UkeyResponse.Args;
import cn.alphahub.eport.signature.entity.UkeyResponseArgsWrapper;
import cn.alphahub.eport.signature.entity.WebSocketWrapper;
import cn.alphahub.eport.signature.support.XMLValidator;
import cn.hutool.core.lang.TypeReference;
import cn.hutool.json.JSONUtil;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.client.WebSocketConnectionManager;
import org.springframework.web.socket.client.standard.StandardWebSocketClient;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.LockSupport;
/**
* <p>电子口岸加签业务核心类</p>
* <a href='http://tool.qdhuaxun.cn/?signdoc'>华讯云业务帮助文档,签名的不要看误导人,华讯的业务文档可以参考</a>
*
* @author weasley
* @version 1.0
* @date 2022/2/12
*/
@Data
@Slf4j
@Service
@Validated
@Accessors(chain = true)
public class SignHandler {
@Autowired
private UkeyProperties ukeyProperties;
@Autowired
private WebSocketClientHandler webSocketClientHandler;
@Autowired
private StandardWebSocketClient standardWebSocketClient;
/**
* 获取ukey的socket入参的inData字段
*
* @param request 加签数据请求入参
* @return ukey的socket入参的inData字段
*/
public static String getInitData(@Valid SignRequest request) {
if (isSignXml(request)) {
return SignatureHandler.getSignatureNodeBeforeSend(request);
}
return request.getData();
}
/**
* 判断是否总署xml
*
* @param request 加签数据请求入参
* @return 总署xml返回true
*/
public static boolean isSignXml(@Valid SignRequest request) {
if (StringUtils.isBlank(request.getData())) {
throw new IllegalArgumentException("加签数据请求入参能为空!");
}
boolean isSignXmlString = XMLValidator.isValidXML(request.getData()) || StringUtils.startsWith(request.getData(), "<ceb:CEB");
boolean isSign179String = StringUtils.startsWith(request.getData(), "\"sessionID\"");
if (isSignXmlString && !isSign179String) {
return true;
}
if (isSign179String && !isSignXmlString) {
return false;
}
throw new IllegalArgumentException("加签数据请求入参不合法,请检查参数:" + request.getData());
}
/**
* 获取u-key签名参数
*
* @return 发送u-key的签名的入参
* @since 2022-11-27
*/
public String getSignDataParameter(@Valid SignRequest request) {
return UkeyInitialConfig.getSignDataAsPEM(request);
}
/**
* WebSocket发送消息给电子口岸u-key
*
* @param request 加签请求参数
* @param payload websocket发送的数据载荷
*/
public SignResult sign(@Valid SignRequest request, @NotBlank(message = "websocket发送的数据载荷不能为空") String payload) {
log.info("收到u-key加签数据: {}", payload);
WebSocketWrapper wrapper = webSocketClientHandler.getWebSocketWrapper();
wrapper.setPayload(payload);
wrapper.setRequest(request);
wrapper.setSignResult(new SignResult());
wrapper.setThreadReference(new AtomicReference<>(Thread.currentThread()));
if (SignHandler.isSignXml(request)) {
wrapper.getSignResult().setDigestValue(SignatureHandler.getDigestValueOfCEBXxxMessage(request.getData()));
wrapper.getSignResult().setSignatureNode(SignatureHandler.getSignatureNodeBeforeSend(request));
}
WebSocketConnectionManager manager = new WebSocketConnectionManager(standardWebSocketClient, webSocketClientHandler, ukeyProperties.getWsUrl());
manager.start();
try {
LockSupport.parkNanos(wrapper.getThreadReference().get(), 1000 * 1000 * 1000 * 5L);
} catch (Exception e) {
log.error("线程自动unpark异常 {}", e.getLocalizedMessage(), e);
wrapper.getSignResult().setSuccess(false);
} finally {
manager.stop();
}
return wrapper.getSignResult();
}
/**
* 获取u-key加签返回内层对象(我们要的数据在这里面)
*
* @apiNote 发送任意数据给ukey,实时响应结果
*/
public Args getUkeyResponseArgs(UkeyRequest ukeyRequest) {
AtomicReference<UkeyResponseArgsWrapper> reference = new AtomicReference<>();
reference.set(new UkeyResponseArgsWrapper(Thread.currentThread(), new Args()));
WebSocketConnectionManager webSocketConnectionManager = new WebSocketConnectionManager(standardWebSocketClient, new TextWebSocketHandler() {
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
session.sendMessage(new TextMessage(JSONUtil.toJsonStr(ukeyRequest)));
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) {
UkeyResponse response = JSONUtil.toBean(message.getPayload(), new TypeReference<>() {
}, true);
try {
if (Objects.equals(response.get_id(), ukeyRequest.get_id())) {
log.warn("从电子口岸ukey中获取获取到数据: {}", response.get_args());
reference.get().setResponseArgs(response.get_args());
}
} catch (Exception e) {
log.error("唤醒线程异常 {}", e.getLocalizedMessage(), e);
}
}
}, ukeyProperties.getWsUrl());
webSocketConnectionManager.start();
try {
LockSupport.parkNanos(reference.get().getThread(), 1000 * 1000 * 1000 * 1L);
} catch (Exception e) {
log.error("线程自动unpark异常 {}", e.getLocalizedMessage(), e);
} finally {
webSocketConnectionManager.stop();
}
return reference.get().getResponseArgs();
}
}
@@ -1,28 +0,0 @@
package cn.alphahub.eport.signature.entity;
import lombok.Data;
import java.io.Serializable;
/**
* 海关 179 数据抓取
* <p>
* 成功数据格式:
* <pre>
* {
* "code": "10000",
* "message": "",
* "serviceTime": 1567050097628
* }
* </pre>
*
* @author weasley
* @version 1.1.0
*/
@Data
public class Capture179DataResponse implements Serializable {
private String code;
private long total;
private String message;
private long serviceTime;
}
@@ -1,43 +0,0 @@
package cn.alphahub.eport.signature.util;
import jakarta.servlet.http.HttpServletRequest;
/**
* 获取客户端的真实 IP 地址
*
* @author weasley
* @since 1.1.0
*/
public final class ClientIPUtils {
static final String UNKNOWN = "unknown";
private ClientIPUtils() {
}
public static String getClientIP(HttpServletRequest request) {
String ipAddress = request.getHeader("X-Forwarded-For");
if (null == ipAddress || ipAddress.isEmpty() || UNKNOWN.equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("Proxy-Client-IP");
}
if (null == ipAddress || ipAddress.isEmpty() || UNKNOWN.equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("WL-Proxy-Client-IP");
}
if (null == ipAddress || ipAddress.isEmpty() || UNKNOWN.equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("HTTP_CLIENT_IP");
}
if (null == ipAddress || ipAddress.isEmpty() || UNKNOWN.equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (null == ipAddress || ipAddress.isEmpty() || UNKNOWN.equalsIgnoreCase(ipAddress)) {
ipAddress = request.getRemoteAddr();
}
// 如果客户端经过了多级反向代理,那么 X-Forwarded-For 中的值是以逗号分隔的 IP 地址列表,取第一个非 unknown 的 IP 地址
if (ipAddress != null && ipAddress.contains(",")) {
ipAddress = ipAddress.split(",")[0].trim();
}
return ipAddress;
}
}
@@ -1,86 +0,0 @@
logging:
level:
cn.alphahub.eport.signature: debug
org.apache.http: error
org.springframework.core: warn
org.springframework.beans: warn
org.springframework.context: warn
org.springframework.web: warn
org.springframework.web.servlet: info
org.springframework.cloud.openfeign: debug
org.springframework.web.servlet.DispatcherServlet: debug
org.springframework.context.support.PropertySourcesPlaceholderConfigurer: info
org.springframework.security.core.userdetails: debug
org.springframework.jdbc.datasource.DataSourceTransactionManager: debug
jdbc.sqltiming: debug
com.alibaba.cloud.nacos.client: error
com.alibaba.nacos: error
org.springframework.web.reactive.function.client.ExchangeFunctions: trace
reactor.netty.http.client.HttpClient: DEBUG
file:
path: ./logs/
name: ${logging.file.path}/${spring.application.name}.log
charset:
file: UTF-8
logback:
rollingpolicy:
max-file-size: 5MB
clean-history-on-start: true
spring:
jackson:
default-property-inclusion: non_null
#邮件通知配置,默认:不启用
mail:
enable: false
#主收件人邮箱
to: abc@qq.com
#抄送箱邮(非必填),抄送多个收件人箱邮用","隔开
cc: abc@outlook.com,cbd@qq.com
#以下发送邮件的客服端参数,使用哪个邮箱发出通知消息
host: smtp.189.cn #邮件服务商主机
port: 465 #端口号
username: xxx@189.cn
password: your_password
protocol: smtp" #协议
properties:
mail:
smtp:
ssl:
enable: true
debug: false #是否开始debug模式,终端会打印日志
mvc:
log-request-details: on
#中国电子口岸u-key的配置参数
eport:
signature:
algorithm: sm2_sm3
ukey:
ws-url: ws://127.0.0.1:61232
password: 88888888
health:
endpoint:
# Windows上重启的ukey可执行文件全限定文件名称
client-name: C:\中国电子口岸客户端控件\SetAccessControl.exe
# 客户端请求鉴权token, 生产环境不建议使用默认token
auth:
enable: on
token: DefaultAuthToken
# 报文传输企业的元数据配置(数据上报)
report:
# CEBMessage XML加签配置
ceb-message:
cop-code: 传输企业代码
cop-name: 海南省荣誉进出口贸易有限公司
dxp-id: 向中国电子口岸数据中心申请数据交换平台的用户编号
# 数据上报服务器地址,格式: http://ip:port, 缺省将采用默认server
server:
# 海关 179 数据抓取配置
customs179:
ebp-code: 电商平台代码
# server可为空,配置文件中指定时,将被优先使用
server: https://海关服务器域名不方便公开/ceb2grab/grab/realTimeDataUpload
@@ -1,10 +0,0 @@
${AnsiColor.BRIGHT_YELLOW}
________ _ ______ __ _____ _ __
/ ____/ /_ (_)___ ____ _ / ____/___ ____ _____/ /_ / ___/(_)___ _____ ____ _/ /___ __________
/ / / __ \/ / __ \/ __ `/ / __/ / __ \/ __ \/ ___/ __/ \__ \/ / __ `/ __ \/ __ `/ __/ / / / ___/ _ \
/ /___/ / / / / / / / /_/ / / /___/ /_/ / /_/ / / / /_ ___/ / / /_/ / / / / /_/ / /_/ /_/ / / / __/
\____/_/ /_/_/_/ /_/\__,_/ /_____/ .___/\____/_/ \__/ /____/_/\__, /_/ /_/\__,_/\__/\__,_/_/ \___/
/_/ /____/
${AnsiColor.BRIGHT_GREEN}${application.title} ${application.formatted-version}
Powered by Spring Boot ${spring-boot.formatted-version}${AnsiStyle.NORMAL}
File diff suppressed because one or more lines are too long
@@ -1,259 +0,0 @@
let api = [];
const apiDocListSize = 1
api.push({
name: 'default',
order: '1',
list: []
})
api[0].list.push({
alias: 'EportTestController',
order: '1',
link: '电子口岸报文加签(测试)',
desc: '电子口岸报文加签(测试)',
list: []
})
api[0].list[0].list.push({
order: '1',
deprecated: 'false',
url: 'http://localhost:8080/rpc/eport/test/cebmessage/signature',
desc: '海关总署XML数据加签',
});
api[0].list[0].list.push({
order: '2',
deprecated: 'false',
url: 'http://localhost:8080/rpc/eport/test/179/signature',
desc: '海关179数据抓取加签',
});
api[0].list[0].list.push({
order: '3',
deprecated: 'false',
url: 'http://localhost:8080/rpc/eport/test/endpoint/exec',
desc: '执行脚本命令',
});
api[0].list.push({
alias: 'EportReportResultController',
order: '2',
link: '查询申报回执结果',
desc: '查询申报回执结果',
list: []
})
api[0].list[1].list.push({
order: '1',
deprecated: 'false',
url: 'http://localhost:8080/rpc/eport/result/ceb312msg',
desc: '查询311进口单申报结果',
});
api[0].list[1].list.push({
order: '2',
deprecated: 'false',
url: 'http://localhost:8080/rpc/eport/result/ceb622msg',
desc: '查询621进口单申报结果',
});
api[0].list.push({
alias: 'UkeyHealthController',
order: '3',
link: 'ukey健康状态恢复',
desc: 'Ukey健康状态恢复',
list: []
})
api[0].list[2].list.push({
order: '1',
deprecated: 'false',
url: 'http://localhost:8080/rpc/eport/ukey/health/endpoint/{command}',
desc: 'ukey健康指令',
});
api[0].list.push({
alias: 'EportUploadController',
order: '4',
link: '电子口岸报文推送',
desc: '电子口岸报文推送',
list: []
})
api[0].list[3].list.push({
order: '1',
deprecated: 'false',
url: 'http://localhost:8080/rpc/eport/upload/CEBMessage',
desc: '推送CEBMessage报文',
});
api[0].list[3].list.push({
order: '2',
deprecated: 'false',
url: 'http://localhost:8080/rpc/eport/upload/179/data',
desc: '推送海关179号数据',
});
api[0].list.push({
alias: 'EportCertController',
order: '5',
link: '电子口岸x509证书',
desc: '电子口岸X509证书',
list: []
})
api[0].list[4].list.push({
order: '1',
deprecated: 'false',
url: 'http://localhost:8080/rpc/eport/cert/download',
desc: '下载证书',
});
api[0].list.push({
alias: 'EportSignController',
order: '6',
link: '电子口岸报文加签',
desc: '电子口岸报文加签',
list: []
})
api[0].list[5].list.push({
order: '1',
deprecated: 'false',
url: 'http://localhost:8080/rpc/eport/signature',
desc: '海关数据加签',
});
document.onkeydown = keyDownSearch;
function keyDownSearch(e) {
const theEvent = e;
const code = theEvent.keyCode || theEvent.which || theEvent.charCode;
if (code === 13) {
const search = document.getElementById('search');
const searchValue = search.value.toLocaleLowerCase();
let searchGroup = [];
for (let i = 0; i < api.length; i++) {
let apiGroup = api[i];
let searchArr = [];
for (let i = 0; i < apiGroup.list.length; i++) {
let apiData = apiGroup.list[i];
const desc = apiData.desc;
if (desc.toLocaleLowerCase().indexOf(searchValue) > -1) {
searchArr.push({
order: apiData.order,
desc: apiData.desc,
link: apiData.link,
list: apiData.list
});
} else {
let methodList = apiData.list || [];
let methodListTemp = [];
for (let j = 0; j < methodList.length; j++) {
const methodData = methodList[j];
const methodDesc = methodData.desc;
if (methodDesc.toLocaleLowerCase().indexOf(searchValue) > -1) {
methodListTemp.push(methodData);
break;
}
}
if (methodListTemp.length > 0) {
const data = {
order: apiData.order,
desc: apiData.desc,
link: apiData.link,
list: methodListTemp
};
searchArr.push(data);
}
}
}
if (apiGroup.name.toLocaleLowerCase().indexOf(searchValue) > -1) {
searchGroup.push({
name: apiGroup.name,
order: apiGroup.order,
list: searchArr
});
continue;
}
if (searchArr.length === 0) {
continue;
}
searchGroup.push({
name: apiGroup.name,
order: apiGroup.order,
list: searchArr
});
}
let html;
if (searchValue === '') {
const liClass = "";
const display = "display: none";
html = buildAccordion(api,liClass,display);
document.getElementById('accordion').innerHTML = html;
} else {
const liClass = "open";
const display = "display: block";
html = buildAccordion(searchGroup,liClass,display);
document.getElementById('accordion').innerHTML = html;
}
const Accordion = function (el, multiple) {
this.el = el || {};
this.multiple = multiple || false;
const links = this.el.find('.dd');
links.on('click', {el: this.el, multiple: this.multiple}, this.dropdown);
};
Accordion.prototype.dropdown = function (e) {
const $el = e.data.el;
let $this = $(this), $next = $this.next();
$next.slideToggle();
$this.parent().toggleClass('open');
if (!e.data.multiple) {
$el.find('.submenu').not($next).slideUp("20").parent().removeClass('open');
}
};
new Accordion($('#accordion'), false);
}
}
function buildAccordion(apiGroups, liClass, display) {
let html = "";
if (apiGroups.length > 0) {
if (apiDocListSize === 1) {
let apiData = apiGroups[0].list;
let order = apiGroups[0].order;
for (let j = 0; j < apiData.length; j++) {
html += '<li class="'+liClass+'">';
html += '<a class="dd" href="#_'+order+'_'+apiData[j].order+'_' + apiData[j].link + '">' + apiData[j].order + '.&nbsp;' + apiData[j].desc + '</a>';
html += '<ul class="sectlevel2" style="'+display+'">';
let doc = apiData[j].list;
for (let m = 0; m < doc.length; m++) {
let spanString;
if (doc[m].deprecated === 'true') {
spanString='<span class="line-through">';
} else {
spanString='<span>';
}
html += '<li><a href="#_'+order+'_' + apiData[j].order + '_' + doc[m].order + '_' + doc[m].desc + '">' + apiData[j].order + '.' + doc[m].order + '.&nbsp;' + spanString + doc[m].desc + '<span></a> </li>';
}
html += '</ul>';
html += '</li>';
}
} else {
for (let i = 0; i < apiGroups.length; i++) {
let apiGroup = apiGroups[i];
html += '<li class="'+liClass+'">';
html += '<a class="dd" href="#_'+apiGroup.order+'_' + apiGroup.name + '">' + apiGroup.order + '.&nbsp;' + apiGroup.name + '</a>';
html += '<ul class="sectlevel1">';
let apiData = apiGroup.list;
for (let j = 0; j < apiData.length; j++) {
html += '<li class="'+liClass+'">';
html += '<a class="dd" href="#_'+apiGroup.order+'_'+ apiData[j].order + '_'+ apiData[j].link + '">' +apiGroup.order+'.'+ apiData[j].order + '.&nbsp;' + apiData[j].desc + '</a>';
html += '<ul class="sectlevel2" style="'+display+'">';
let doc = apiData[j].list;
for (let m = 0; m < doc.length; m++) {
let spanString;
if (doc[m].deprecated === 'true') {
spanString='<span class="line-through">';
} else {
spanString='<span>';
}
html += '<li><a href="#_'+apiGroup.order+'_' + apiData[j].order + '_' + doc[m].order + '_' + doc[m].desc + '">'+apiGroup.order+'.' + apiData[j].order + '.' + doc[m].order + '.&nbsp;' + spanString + doc[m].desc + '<span></a> </li>';
}
html += '</ul>';
html += '</li>';
}
html += '</ul>';
html += '</li>';
}
}
}
return html;
}
@@ -1 +0,0 @@
.hljs{display:block;overflow-x:auto;color:#eaeaea;background:#000;padding:.5em}.hljs-subst{color:#eaeaea}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}.hljs-builtin-name,.hljs-type{color:#eaeaea}.hljs-params{color:#da0000}.hljs-literal,.hljs-name,.hljs-number{color:red;font-weight:bolder}.hljs-comment{color:#969896}.hljs-quote,.hljs-selector-id{color:#0ff}.hljs-template-variable,.hljs-title,.hljs-variable{color:#0ff;font-weight:700}.hljs-keyword,.hljs-selector-class,.hljs-symbol{color:#fff000}.hljs-bullet,.hljs-string{color:#0f0}.hljs-section,.hljs-tag{color:#000fff}.hljs-selector-tag{color:#000fff;font-weight:700}.hljs-attribute,.hljs-built_in,.hljs-link,.hljs-regexp{color:#f0f}.hljs-meta{color:#fff;font-weight:bolder}
@@ -1,115 +0,0 @@
<ceb:CEB621Message xmlns:ceb="http://www.chinaport.gov.cn/ceb" guid="CEB621_HNZB_FXJK_20220208175054_0034"
version="v1.0">
<ceb:Inventory>
<ceb:InventoryHead>
<ceb:guid>CEB621_HNZB_FXJK_20220208175055_0035</ceb:guid>
<ceb:appType>1</ceb:appType>
<ceb:appTime>20220208175055</ceb:appTime>
<ceb:appStatus>2</ceb:appStatus>
<ceb:orderNo>48037605991776256</ceb:orderNo>
<ceb:ebpCode>46121601BC</ceb:ebpCode>
<ceb:ebpName>海南星创互联网医药有限公司</ceb:ebpName>
<ceb:ebcCode>46121601BC</ceb:ebcCode>
<ceb:ebcName>海南星创互联网医药有限公司</ceb:ebcName>
<ceb:logisticsNo>2222222</ceb:logisticsNo>
<ceb:logisticsCode>4101986180</ceb:logisticsCode>
<ceb:logisticsName>圆通</ceb:logisticsName>
<ceb:copNo>4101W68006</ceb:copNo>
<ceb:assureCode>4101960AGP</ceb:assureCode>
<ceb:ieFlag>I</ceb:ieFlag>
<ceb:declTime>20220208</ceb:declTime>
<ceb:customsCode>4601</ceb:customsCode>
<ceb:portCode>4601</ceb:portCode>
<ceb:ieDate>20220208</ceb:ieDate>
<ceb:buyerIdType>1</ceb:buyerIdType>
<ceb:buyerIdNumber>612326199005306719</ceb:buyerIdNumber>
<ceb:buyerName>用户_15150368</ceb:buyerName>
<ceb:buyerTelephone>18209182500</ceb:buyerTelephone>
<ceb:consigneeAddress>哈布斯堡</ceb:consigneeAddress>
<ceb:agentCode>46121601BC</ceb:agentCode>
<ceb:agentName>海南星创互联网医药有限公司</ceb:agentName>
<ceb:tradeMode>9610</ceb:tradeMode>
<ceb:trafMode>W</ceb:trafMode>
<ceb:country>142</ceb:country>
<ceb:freight>0.00</ceb:freight>
<ceb:insuredFee>0</ceb:insuredFee>
<ceb:currency>142</ceb:currency>
<ceb:packNo>1</ceb:packNo>
<ceb:grossWeight>0.07</ceb:grossWeight>
<ceb:netWeight>0.06</ceb:netWeight>
</ceb:InventoryHead>
<ceb:InventoryList>
<ceb:gnum>1</ceb:gnum>
<ceb:itemRecordNo>0797776159482</ceb:itemRecordNo>
<ceb:itemNo>HNGGKJ00000057</ceb:itemNo>
<ceb:itemName>通用铁剂胶囊</ceb:itemName>
<ceb:gcode>2106909090</ceb:gcode>
<ceb:gname>ACTIVE IRON</ceb:gname>
<ceb:gmodel>4|3|乳清蛋白(牛奶)65%,胶囊壳(羟苯基甲基纤维素)21%,硫酸亚铁1.2%,酸度调节剂2%,填充剂 10.8%|30粒/盒|无中文名称/ACTIVE IRON</ceb:gmodel>
<ceb:country>306</ceb:country>
<ceb:currency>142</ceb:currency>
<ceb:qty>1</ceb:qty>
<ceb:unit>140</ceb:unit>
<ceb:qty1>0.06</ceb:qty1>
<ceb:unit1>035</ceb:unit1>
<ceb:price>0.01</ceb:price>
<ceb:totalPrice>0.01</ceb:totalPrice>
</ceb:InventoryList>
</ceb:Inventory>
<ceb:BaseTransfer>
<ceb:copCode>46121601BC</ceb:copCode>
<ceb:copName>海南星创互联网医药有限公司</ceb:copName>
<ceb:dxpMode>DXP</ceb:dxpMode>
<ceb:dxpId>DXPENT0000458763</ceb:dxpId>
</ceb:BaseTransfer>
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
<ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/>
<ds:Reference URI="">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/>
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/>
<ds:DigestValue>b2FtM/jHvW4fnJpMMPWk/177RwA=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>
mhWpJI3RsJZGiR8MqI3kcJAJPqgVOQi0IFJzp0CBWLG+Qa7ZnYJX+hi70J+/09diHYkCMDxcKuT5uogbFvi/9rOZnrn+YsL0h0/nGcO31lx/RpqT8i5GoSt/UcZe9COIbnrKwdh+90FLYIW4xS3Rsglv4CDL+e53GGmSdduSTUU=
</ds:SignatureValue>
<ds:KeyInfo>
<ds:KeyName>01691fe9</ds:KeyName>
<ds:X509Data>
<ds:X509Certificate>
bjEVMBMGA1UECh4MTi1W/XU1W1BT41y4MRUwEwYDVQQLHgyLwU5me6F0Bk4tX8Mx
wzENMAsGA1UEBx4EUxdOrDAeFw0yMTA1MzEwMDAwMDBaFw0zMTA1MzEwMDAwMDBa
MIIE+TCCBGKgAwIBAgIEAWkf6TANBgkqhkiG9w0BAQUFADB8MQswCQYDVQQGEwJj
DTALBgNVBAgeBFMXTqwxITAfBgNVBAMeGE4tVv11NVtQThpSoYvBTmZ7oXQGTi1f
A4GNADCBiQKBgQDt9T7JOQikKKL7oY7RWbRMp7y2VsrXIbFzSDh5DZQPimduUijZ
xgG4Kdr0TwxAQ+s3H85fCU81ROgqAS05/IzOR7eDSEYYT9CRmNYXIRTPVwIDAQAB
MBwxDTALBgNVBAMeBH8qdDMxCzAJBgNVBBIeAgAxMIGfMA0GCSqGSIb3DQEBAQUA
o4IC5jCCAuIwCwYDVR0PBAQDAgbAMAkGA1UdEwQCMAAwgacGA1UdIwSBnzCBnIAU
zZlK6AkZMDYJBM2/IJGI0QjWjBJVZ8hEUkgJb4UOOBCBM+cCGFCNY5LX/mAo5Boe
Ch4MTi1W/XU1W1BT41y4MRUwEwYDVQQLHgyLwU5me6F0Bk4tX8MxDTALBgNVBAge
Bx4EUxdOrIIBADAdBgNVHQ4EFgQU1eAz50lwZ6gidtkJNPIr7O0jKvUwQgYDVR0g
BDswOTA3BgYrgQcBAQIwLTArBggrBgEFBQcCARYfaHR0cDovL2Nwcy5jaGluYXBv
+XWjeEULQmjCBFOr68NOPlR4dGChgYCkfjB8MQswCQYDVQQGEwJjbjEVMBMGA1UE
BFMXTqwxITAfBgNVBAMeGE4tVv11NVtQThpSoYvBTmZ7oXQGTi1fwzENMAsGA1UE
cnQuZ292LmNuL0NQUzByBgNVHR8EazBpMDCgLqAshipsZGFwOi8vbGRhcC5jaGlu
YXBvcnQuZ292LmNuOjM4OS8wMDAtMS5jcmwwNaAzoDGGL2h0dHA6Ly9sZGFwLmNo
aW5hcG9ydC5nb3YuY246ODA4OC9kemthMDAwLTEuY3JsMG0GCCsGAQUFBwEBBGEw
LzAtBggrBgEFBQcwAYYhaHR0cDovL29jc3AuY2hpbmFwb3J0Lmdvdi5jbjo4MDg4
XzAuBggrBgEFBQcwAYYiaHR0cDovL29jc3AuY2hpbmFwb3J0Lmdvdi5jbjo4ODAw
KwYBBAGpQ2QFAwQEFgIwMTAiBgorBgEEAalDZAUIBBQWEjMxMDEwMjE5ODAwNzI0
MCoGCisGAQQBqUNkBQEEHBYauqPEz9DHtLS7pcGqzfjSvdKp09DP3rmry74wEgYK
CwQLFglNQTVUTFVBNTMwEgYKKwYBBAGpQ2QFDAQEFgIwMDASBgorBgEEAalDZAIE
MzIxNDAdBgorBgEEAalDZAUJBA8WDUpKMEc5MDAxNTkyNzIwGQYKKwYBBAGpQ2QF
MxAzM4bXvdlDcE6fODsCvQMFKctlA+LCllFQwl58HaBmcWx4T/ddKF9LBYc8A986
BAQWAjE0MBIGCisGAQQBqUNkAgEEBBYCMTIwDQYJKoZIhvcNAQEFBQADgYEAqSuO
LlcUw6Mkwxraj2WO+meXdDzRLEO8t3gyZk7tYp5aneV4zGYUsphwyMdLt8N8o4kV
gg16bQy43XgA1jRMv8nvhb908IqQQBxwv0SIuXU=
</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</ds:Signature>
</ceb:CEB621Message>
@@ -1,87 +0,0 @@
package cn.alphahub.eport.signature.after202207;
/**
* SignConstant
*
* @author weasley
* @version 1.0.0
*/
public interface SignConstant {
/**
* 179入参示例
*/
String sign179 = """
"sessionID":"ad2254-8hewyf32-55616249"||"payExchangeInfoHead":"{"guid":"9D55BA71-22DE-41F4-8B50-C36C83B3B530","initalRequest":"原始请求","initalResponse":"ok","ebpCode":"4404840022","payCode":"312226T001","payTransactionId":"2018121222001354081010726129","totalAmount":100,"currency":"142","verDept":"3","payType":"1","tradingTime":"20181212041803","note":"批量订单,测试订单优化,生成多个so订单"}"||"payExchangeInfoLists":"[{"orderNo":"SO1710301150602574003","goodsInfo":[{"gname":"lhy-gnsku3","itemLink":"http://m.yunjiweidian.com/yunjibuyer/static/vue-buyer/idc/index.html#/detail?itemId=999761&shopId=453"},{"gname":"lhy-gnsku2","itemLink":"http://m.yunjiweidian.com/yunjibuyer/static/vue-buyer/idc/index.html#/detail?itemId=999760&shopId=453"}],"recpAccount":"OSA571908863132601","recpCode":"","recpName":"YUNJIHONGKONGLIMITED"}]"||"serviceTime":"1544519952469"
""";
/**
* XML入参示例
*/
String signXml = """
<ceb:CEB621Message xmlns:ceb="http://www.chinaport.gov.cn/ceb" guid="CEB621_HNZB_FXJK_20220208175054_0034"
version="v1.0">
<ceb:Inventory>
<ceb:InventoryHead>
<ceb:guid>CEB621_HNZB_FXJK_20220208175055_0035</ceb:guid>
<ceb:appType>1</ceb:appType>
<ceb:appTime>20220208175055</ceb:appTime>
<ceb:appStatus>2</ceb:appStatus>
<ceb:orderNo>48037605991776256</ceb:orderNo>
<ceb:ebpCode>46121601BC</ceb:ebpCode>
<ceb:ebpName>海南星创互联网医药有限公司</ceb:ebpName>
<ceb:ebcCode>46121601BC</ceb:ebcCode>
<ceb:ebcName>海南星创互联网医药有限公司</ceb:ebcName>
<ceb:logisticsNo>2222222</ceb:logisticsNo>
<ceb:logisticsCode>4101986180</ceb:logisticsCode>
<ceb:logisticsName>圆通</ceb:logisticsName>
<ceb:copNo>4101W68006</ceb:copNo>
<ceb:assureCode>4101960AGP</ceb:assureCode>
<ceb:ieFlag>I</ceb:ieFlag>
<ceb:declTime>20220208</ceb:declTime>
<ceb:customsCode>4601</ceb:customsCode>
<ceb:portCode>4601</ceb:portCode>
<ceb:ieDate>20220208</ceb:ieDate>
<ceb:buyerIdType>1</ceb:buyerIdType>
<ceb:buyerIdNumber>612326199005306719</ceb:buyerIdNumber>
<ceb:buyerName>用户_15150368</ceb:buyerName>
<ceb:buyerTelephone>18209182500</ceb:buyerTelephone>
<ceb:consigneeAddress>哈布斯堡</ceb:consigneeAddress>
<ceb:agentCode>46121601BC</ceb:agentCode>
<ceb:agentName>海南星创互联网医药有限公司</ceb:agentName>
<ceb:tradeMode>9610</ceb:tradeMode>
<ceb:trafMode>W</ceb:trafMode>
<ceb:country>142</ceb:country>
<ceb:freight>0.00</ceb:freight>
<ceb:insuredFee>0</ceb:insuredFee>
<ceb:currency>142</ceb:currency>
<ceb:packNo>1</ceb:packNo>
<ceb:grossWeight>0.07</ceb:grossWeight>
<ceb:netWeight>0.06</ceb:netWeight>
</ceb:InventoryHead>
<ceb:InventoryList>
<ceb:gnum>1</ceb:gnum>
<ceb:itemRecordNo>0797776159482</ceb:itemRecordNo>
<ceb:itemNo>HNGGKJ00000057</ceb:itemNo>
<ceb:itemName>通用铁剂胶囊</ceb:itemName>
<ceb:gcode>2106909090</ceb:gcode>
<ceb:gname>ACTIVE IRON</ceb:gname>
<ceb:gmodel>4|3|乳清蛋白(牛奶)65%,胶囊壳(羟苯基甲基纤维素)21%,硫酸亚铁1.2%,酸度调节剂2%,填充剂 10.8%|30粒/盒|无中文名称/ACTIVE IRON</ceb:gmodel>
<ceb:country>306</ceb:country>
<ceb:currency>142</ceb:currency>
<ceb:qty>1</ceb:qty>
<ceb:unit>140</ceb:unit>
<ceb:qty1>0.06</ceb:qty1>
<ceb:unit1>035</ceb:unit1>
<ceb:price>0.01</ceb:price>
<ceb:totalPrice>0.01</ceb:totalPrice>
</ceb:InventoryList>
</ceb:Inventory>
<ceb:BaseTransfer>
<ceb:copCode>46121601BC</ceb:copCode>
<ceb:copName>海南星创互联网医药有限公司</ceb:copName>
<ceb:dxpMode>DXP</ceb:dxpMode>
<ceb:dxpId>DXPENT0000458763</ceb:dxpId>
</ceb:BaseTransfer>
</ceb:CEB621Message>
""";
}
@@ -1,36 +0,0 @@
package cn.alphahub.eport.signature.after202207;
import cn.alphahub.eport.signature.entity.UkeyRequest;
import cn.hutool.json.JSONConfig;
import cn.hutool.json.JSONUtil;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* 查看海关证书有效期
*
* @author weasley
* @version 1.0.7
*/
@SpringBootTest
public class TestCusSpcGetValidTimeFromCert {
@Autowired
private SignClient signClient;
@Test
@DisplayName("查看海关证书有效期")
void testCusSpcGetValidTimeFromCert() {
Map<String, Object> args = new LinkedHashMap<>();
//args.put("cert", null);//证书内容,可为空
UkeyRequest ukeyRequest = new UkeyRequest("cus-sec_SpcGetValidTimeFromCert", 1, args);
JSONConfig jsonConfig = new JSONConfig();
String jsonStr = JSONUtil.toJsonStr(ukeyRequest, jsonConfig);
System.err.println(jsonStr);
signClient.sign(ukeyRequest);
}
}
@@ -1,161 +0,0 @@
package cn.alphahub.eport.signature.c14;
import cn.alphahub.eport.signature.core.SignatureHandler;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
/**
* Sign CEB311Message With C14n
* <p>
*
* @author lwj
* @version 1.0
* @date 2022-01-11 14:23
*/
@SpringBootTest
class C14nEncodeTests {
@Test
void testSignCEB311MessageWithC14n() {
org.apache.xml.security.Init.init();
String sourceXml = """
<ceb:CEB311Message xmlns:ceb="http://www.chinaport.gov.cn/ceb" guid="4CDE1CFD-EDED-46B1-946C-B8022E42FC94"
version="1.0">
<link type="text/css" id="dark-mode" rel="stylesheet" href=""/>
<ceb:Order>
<ceb:OrderHead>
<ceb:guid>4CDE1CFD-EDED-46B1-946C-B8022E42FC94</ceb:guid>
<ceb:appType>1</ceb:appType>
<ceb:appTime>20160308112701</ceb:appTime>
<ceb:appStatus>2</ceb:appStatus>
<ceb:orderType>I</ceb:orderType>
<ceb:orderNo>order20160321116421002</ceb:orderNo>
<ceb:ebpCode>1105910159</ceb:ebpCode>
<ceb:ebpName>东方物通科技(北京)有限公司</ceb:ebpName>
<ceb:ebcCode>1105910159</ceb:ebcCode>
<ceb:ebcName>东方物通科技(北京)有限公司</ceb:ebcName>
<ceb:goodsValue>14000</ceb:goodsValue>
<ceb:freight>5000</ceb:freight>
<ceb:discount>0</ceb:discount>
<ceb:taxTotal>50</ceb:taxTotal>
<ceb:acturalPaid>19050</ceb:acturalPaid>
<ceb:currency>142</ceb:currency>
<ceb:buyerRegNo>ID20160001</ceb:buyerRegNo>
<ceb:buyerName>aa</ceb:buyerName>
<ceb:buyerTelephone>18813025940</ceb:buyerTelephone>
<ceb:buyerIdType>1</ceb:buyerIdType>
<ceb:buyerIdNumber>130681136250023332</ceb:buyerIdNumber>
<ceb:payCode>1105910159</ceb:payCode>
<ceb:payName>东方物通科技(北京)有限公司</ceb:payName>
<ceb:payTransactionId>20160001634226001</ceb:payTransactionId>
<ceb:batchNumbers>20160317</ceb:batchNumbers>
<ceb:consignee>焦洪宇</ceb:consignee>
<ceb:consigneeTelephone>13522652231</ceb:consigneeTelephone>
<ceb:consigneeAddress>北京市海淀区</ceb:consigneeAddress>
<ceb:consigneeDistrict>072750</ceb:consigneeDistrict>
<ceb:note>test</ceb:note>
</ceb:OrderHead>
<ceb:OrderList>
<ceb:gnum>1</ceb:gnum>
<ceb:itemNo>AF001-001</ceb:itemNo>
<ceb:itemName>b</ceb:itemName>
<ceb:gmodel>33mm</ceb:gmodel>
<ceb:itemDescribe>v</ceb:itemDescribe>
<ceb:barCode>2345123</ceb:barCode>
<ceb:unit>007</ceb:unit>
<ceb:qty>100</ceb:qty>
<ceb:price>20</ceb:price>
<ceb:totalPrice>2000</ceb:totalPrice>
<ceb:currency>142</ceb:currency>
<ceb:country>116</ceb:country>
<ceb:note/>
</ceb:OrderList>
</ceb:Order>
<ceb:BaseTransfer>
<ceb:copCode>1101180326</ceb:copCode>
<ceb:copName>物流企业</ceb:copName>
<ceb:dxpMode>DXP</ceb:dxpMode>
<ceb:dxpId>EXP2016522002580001</ceb:dxpId>
<ceb:note>test</ceb:note>
</ceb:BaseTransfer>
</ceb:CEB311Message>
""";
String messageWithC14n = SignatureHandler.getDigestValueOfCEBXxxMessage(sourceXml);
System.err.println(messageWithC14n);
assert "vDf4oKPVFtRft5U57TNW85kJT18=".equals(messageWithC14n);
}
@Test
void testSha1HexCEB621Message() {
String xml = """
<ceb:CEB621Message xmlns:ceb="http://www.chinaport.gov.cn/ceb" guid="CEB621_HNZB_FXJK_20220208175054_0034"
version="v1.0">
<ceb:Inventory>
<ceb:InventoryHead>
<ceb:guid>CEB621_HNZB_FXJK_20220208175055_0035</ceb:guid>
<ceb:appType>1</ceb:appType>
<ceb:appTime>20220208175055</ceb:appTime>
<ceb:appStatus>2</ceb:appStatus>
<ceb:orderNo>48037605991776256</ceb:orderNo>
<ceb:ebpCode>46121601BC</ceb:ebpCode>
<ceb:ebpName>海南星创互联网医药有限公司</ceb:ebpName>
<ceb:ebcCode>46121601BC</ceb:ebcCode>
<ceb:ebcName>海南星创互联网医药有限公司</ceb:ebcName>
<ceb:logisticsNo>2222222</ceb:logisticsNo>
<ceb:logisticsCode>4101986180</ceb:logisticsCode>
<ceb:logisticsName>圆通</ceb:logisticsName>
<ceb:copNo>4101W68006</ceb:copNo>
<ceb:assureCode>4101960AGP</ceb:assureCode>
<ceb:ieFlag>I</ceb:ieFlag>
<ceb:declTime>20220208</ceb:declTime>
<ceb:customsCode>4601</ceb:customsCode>
<ceb:portCode>4601</ceb:portCode>
<ceb:ieDate>20220208</ceb:ieDate>
<ceb:buyerIdType>1</ceb:buyerIdType>
<ceb:buyerIdNumber>612326199005306719</ceb:buyerIdNumber>
<ceb:buyerName>用户_15150368</ceb:buyerName>
<ceb:buyerTelephone>18209182500</ceb:buyerTelephone>
<ceb:consigneeAddress>哈布斯堡</ceb:consigneeAddress>
<ceb:agentCode>46121601BC</ceb:agentCode>
<ceb:agentName>海南星创互联网医药有限公司</ceb:agentName>
<ceb:tradeMode>9610</ceb:tradeMode>
<ceb:trafMode>W</ceb:trafMode>
<ceb:country>142</ceb:country>
<ceb:freight>0.00</ceb:freight>
<ceb:insuredFee>0</ceb:insuredFee>
<ceb:currency>142</ceb:currency>
<ceb:packNo>1</ceb:packNo>
<ceb:grossWeight>0.07</ceb:grossWeight>
<ceb:netWeight>0.06</ceb:netWeight>
</ceb:InventoryHead>
<ceb:InventoryList>
<ceb:gnum>1</ceb:gnum>
<ceb:itemRecordNo>0797776159482</ceb:itemRecordNo>
<ceb:itemNo>HNGGKJ00000057</ceb:itemNo>
<ceb:itemName>通用铁剂胶囊</ceb:itemName>
<ceb:gcode>2106909090</ceb:gcode>
<ceb:gname>ACTIVE IRON</ceb:gname>
<ceb:gmodel>4|3|乳清蛋白(牛奶)65%,胶囊壳(羟苯基甲基纤维素)21%,硫酸亚铁1.2%,酸度调节剂2%,填充剂 10.8%|30粒/盒|无中文名称/ACTIVE IRON</ceb:gmodel>
<ceb:country>306</ceb:country>
<ceb:currency>142</ceb:currency>
<ceb:qty>1</ceb:qty>
<ceb:unit>140</ceb:unit>
<ceb:qty1>0.06</ceb:qty1>
<ceb:unit1>035</ceb:unit1>
<ceb:price>0.01</ceb:price>
<ceb:totalPrice>0.01</ceb:totalPrice>
</ceb:InventoryList>
</ceb:Inventory>
<ceb:BaseTransfer>
<ceb:copCode>46121601BC</ceb:copCode>
<ceb:copName>海南星创互联网医药有限公司</ceb:copName>
<ceb:dxpMode>DXP</ceb:dxpMode>
<ceb:dxpId>DXPENT0000458763</ceb:dxpId>
</ceb:BaseTransfer>
</ceb:CEB621Message>
""";
System.out.println(xml);
}
}
@@ -1,308 +0,0 @@
package cn.alphahub.eport.signature.client;
import cn.alphahub.dtt.plus.util.SpringUtil;
import cn.alphahub.eport.signature.config.UkeyInitialConfig;
import cn.alphahub.eport.signature.config.UkeyInitialConfigTest;
import cn.alphahub.eport.signature.core.CertificateHandler;
import cn.alphahub.eport.signature.core.SignHandler;
import cn.alphahub.eport.signature.entity.SignRequest;
import cn.alphahub.eport.signature.entity.SignResult;
import cn.alphahub.eport.signature.entity.UkeyRequest;
import cn.alphahub.eport.signature.entity.UkeyResponse.Args;
import cn.alphahub.eport.signature.support.CertificateParser;
import cn.hutool.core.lang.TypeReference;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* WebSocketClient测试类
*
* @author weasley
* @version 1.0
* @date 2022/2/12
*/
@Slf4j
@SpringBootTest
class SignClientTest {
private static final String PASSWORD = "88888888";
@Autowired
private SignHandler signClient;
@Test
void sendInitDataAsPEMWithHashPTest() {
SignRequest request = new SignRequest(UkeyInitialConfigTest.CEB621Message);
String parameter = UkeyInitialConfig.getSignDataAsPEM(request);
SignResult result = signClient.sign(request, parameter);
System.out.println(JSONUtil.toJsonPrettyStr(result));
}
/**
* 获取版本号
*/
@Test
void ukeyVersion() {
String method = "cus-sec_SpcGetCardAttachInfo";
UkeyRequest ukeyRequest = new UkeyRequest(method, new LinkedHashMap<>() {{
put("passwd", PASSWORD);
}});
Args args = this.signClient.getUkeyResponseArgs(ukeyRequest);
System.out.println(JSONUtil.toJsonPrettyStr(args));
}
/**
* 使用卡计算摘要,返回PEM格式信息
*/
@Test
@DisplayName("使用卡计算摘要,返回PEM格式信息")
void calculationSummaryUsingCards() {
String method = "cus-sec_SpcSHA1Digest";
UkeyRequest ukeyRequest = new UkeyRequest(method, new LinkedHashMap<>() {{
put("szInfo", """
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#"><ds:SignedInfo><ds:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/><ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sm2-sm3"/><ds:Reference URI=""><ds:Transforms><ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"/></ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/><ds:DigestValue>8JmrLw4jIIsHx+qb7D7U9eKPlWo=</ds:DigestValue></ds:Reference></ds:SignedInfo>
""");
put("passwd", PASSWORD);
}});
Args args = this.signClient.getUkeyResponseArgs(ukeyRequest);
System.out.println(JSONUtil.toJsonPrettyStr(args));
}
@Test
void calculationSummaryUsingCards2() {
String method = "cus-sec_SpcSHA1DigestAsPEM";
String data = """
<?xml version="1.0" encoding="UTF-8"?>
<ceb:CEB311Message guid="tCEB311_HNZB_HNFX_20230707223752_003" version="1.0" xmlns:ceb="http://www.chinaport.gov.cn/ceb" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ceb:Order>
<ceb:OrderHead>
<ceb:guid>tCEB311_HNZB_HNFX_20230707223752_001</ceb:guid>
<ceb:appType>1</ceb:appType>
<ceb:appTime>20230704181028</ceb:appTime>
<ceb:appStatus>2</ceb:appStatus>
<ceb:orderType>I</ceb:orderType>
<ceb:orderNo>T_C5051511332138160010</ceb:orderNo>
<ceb:ebpCode>4601630004</ceb:ebpCode>
<ceb:ebpName>海南省荣誉进出口贸易有限公司</ceb:ebpName>
<ceb:ebcCode>4601630004</ceb:ebcCode>
<ceb:ebcName>海南省荣誉进出口贸易有限公司</ceb:ebcName>
<ceb:goodsValue>0.01</ceb:goodsValue>
<ceb:freight>0</ceb:freight>
<ceb:discount>0</ceb:discount>
<ceb:taxTotal>0</ceb:taxTotal>
<ceb:acturalPaid>0.01</ceb:acturalPaid>
<ceb:currency>142</ceb:currency>
<ceb:buyerRegNo>4</ceb:buyerRegNo>
<ceb:buyerName>袁晓雨</ceb:buyerName>
<ceb:buyerTelephone>13701727375</ceb:buyerTelephone>
<ceb:buyerIdType>1</ceb:buyerIdType>
<ceb:buyerIdNumber>130435200009241538</ceb:buyerIdNumber>
<ceb:consignee>袁晓雨</ceb:consignee>
<ceb:consigneeTelephone>13701727375</ceb:consigneeTelephone>
<ceb:consigneeAddress>北京北京市东城区</ceb:consigneeAddress>
<ceb:note>test</ceb:note>
</ceb:OrderHead>
<ceb:OrderList>
<ceb:gnum>1</ceb:gnum>
<ceb:itemNo>1</ceb:itemNo>
<ceb:itemName>LANNA兰纳</ceb:itemName>
<ceb:gmodel>10片/包</ceb:gmodel>
<ceb:itemDescribe></ceb:itemDescribe>
<ceb:barCode>1</ceb:barCode>
<ceb:unit>011</ceb:unit>
<ceb:qty>1</ceb:qty>
<ceb:price>1</ceb:price>
<ceb:totalPrice>1</ceb:totalPrice>
<ceb:currency>142</ceb:currency>
<ceb:country>136</ceb:country>
<ceb:note>test</ceb:note>
</ceb:OrderList>
</ceb:Order>
<ceb:BaseTransfer>
<ceb:copCode>4601630004</ceb:copCode>
<ceb:copName>海南省荣誉进出口贸易有限公司</ceb:copName>
<ceb:dxpMode>DXP</ceb:dxpMode>
<ceb:dxpId>DXPENT0000530815</ceb:dxpId>
<ceb:note>test</ceb:note>
</ceb:BaseTransfer>
</ceb:CEB311Message>
""";
SignRequest signRequest = new SignRequest(data);
UkeyRequest ukeyRequest = JSONUtil.toBean(UkeyInitialConfig.getSHA1DigestAsPEMParams(signRequest), new TypeReference<>() {
}, true);
Args args = this.signClient.getUkeyResponseArgs(ukeyRequest);
System.out.println(JSONUtil.toJsonPrettyStr(args));
}
@Test
@DisplayName("获取Usb-Key的序列号(ID)")
void f1() {
HashMap<String, Object> _args = new HashMap<>();
UkeyRequest ukeyRequest = new UkeyRequest("cus-sec_SpcGetCardID", _args);
Args args = signClient.getUkeyResponseArgs(ukeyRequest);
System.err.println("获取卡号: " + JSONUtil.toJsonStr(args));
ukeyRequest = new UkeyRequest("cus-cus-sec_SpcGetCardAttachInfo", _args);
args = signClient.getUkeyResponseArgs(ukeyRequest);
System.err.println("获取附加信息: " + JSONUtil.toJsonStr(args));
ukeyRequest = new UkeyRequest("cus-sec_SpcGetCertNo", _args);
args = signClient.getUkeyResponseArgs(ukeyRequest);
System.err.println("获取证书编号: " + JSONUtil.toJsonStr(args));
ukeyRequest = new UkeyRequest("cus-sec_SpcGetUName", _args);
args = signClient.getUkeyResponseArgs(ukeyRequest);
System.err.println("申请者名称: " + JSONUtil.toJsonStr(args));
ukeyRequest = new UkeyRequest("cus-sec_SpcGetEntID", _args);
args = signClient.getUkeyResponseArgs(ukeyRequest);
System.err.println("单位ID: " + JSONUtil.toJsonStr(args));
ukeyRequest = new UkeyRequest("cus-sec_SpcGetEntName", _args);
args = signClient.getUkeyResponseArgs(ukeyRequest);
System.err.println("单位名: " + JSONUtil.toJsonStr(args));
ukeyRequest = new UkeyRequest("cus-sec_SpcGetSignCert", _args);
args = signClient.getUkeyResponseArgs(ukeyRequest);
System.err.println("取海关签名证书: " + JSONUtil.toJsonStr(args));
ukeyRequest = new UkeyRequest("cus-sec_SpcGetSignCertAsPEM", _args);
args = signClient.getUkeyResponseArgs(ukeyRequest);
System.err.println("取海关签名证书PEM: " + JSONUtil.toJsonStr(args));
ukeyRequest = new UkeyRequest("cus-sec_SpcGetEnvCert", _args);
args = signClient.getUkeyResponseArgs(ukeyRequest);
System.err.println("取海关加密证书: " + JSONUtil.toJsonStr(args));
ukeyRequest = new UkeyRequest("cus-sec_SpcGetEnvCertAsPEM", _args);
args = signClient.getUkeyResponseArgs(ukeyRequest);
System.err.println("取海关加密证书PEM: " + JSONUtil.toJsonStr(args));
ukeyRequest = new UkeyRequest("cus-sec_SpcGetValidTimeFromCert", _args);
args = signClient.getUkeyResponseArgs(ukeyRequest);
System.err.println("查看海关证书有效期: " + JSONUtil.toJsonStr(args));
// inData 原始内容
_args.put("inData", "MIIEoDCCBESgAwIBAgIIAwAAAAAM3m8wDAYIKoEcz1UBg3UFADCBmDELMAkGA1UEBhMCQ04xDzANBgNVBAgMBuWMl+S6rDEPMA0GA1UEBwwG5YyX5LqsMRswGQYDVQQKDBLkuK3lm73nlLXlrZDlj6PlsrgxGzAZBgNVBAsMEuivgeS5pueuoeeQhuS4reW/gzEtMCsGA1UEAwwk5Lit5Zu955S15a2Q5Lia5Yqh6K+B5Lmm566h55CG5Lit5b+DMB4XDTIzMDMyOTAwMDAwMFoXDTMzMDMyOTAwMDAwMFowVjELMAkGA1UEBhMCQ04xMzAxBgNVBAsMKua1t+WNl+ecgeiNo+iqiei/m+WHuuWPo+i0uOaYk+aciemZkOWFrOWPuDESMBAGA1UEAwwJ5p2o5aaC6YeRMFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAE0vOQmplAr9igPZrA8F1msqnFd0U++6G6NhG5rNuIUWft0BwQn7eSJkt5/fvSSoe7pUg2/awHUWPnzkeeQc7oVqOCArUwggKxMBEGCWCGSAGG+EIBAQQEAwIFoDAOBgNVHQ8BAf8EBAMCBsAwCQYDVR0TBAIwADApBgNVHSUEIjAgBggrBgEFBQcDAgYIKwYBBQUHAwQGCisGAQQBgjcUAgIwHwYDVR0jBBgwFoAURCQxt0wEvoAVXmuo4N1bjKXTh0UwHQYDVR0OBBYEFAytGob5L0WqhOCZ5l6Lf2jUdNrAMGgGA1UdIARhMF8wXQYEVR0gADBVMFMGCCsGAQUFBwIBFkdodHRwczovL3d3dy5jaGluYXBvcnQuZ292LmNuL3RjbXNmaWxlL3UvY21zL3d3dy8yMDIyMDQvMTIxMzI5NDh4dDZwLnBkZjB/BgNVHR8EeDB2MHSgcqBwhm5sZGFwOi8vbGRhcC5jaGluYXBvcnQuZ292LmNuOjM4OS9jbj1jcmwwMzAwMDAsb3U9Y3JsMDAsb3U9Y3JsLGM9Y24/Y2VydGlmaWNhdGVSZXZvY2F0aW9uTGlzdD9iYXNlP2NuPWNybDAzMDAwMDA+BggrBgEFBQcBAQQyMDAwLgYIKwYBBQUHMAGGImh0dHA6Ly9vY3NwLmNoaW5hcG9ydC5nb3YuY246ODgwMC8wOgYKKwYBBAGpQ2QFAQQsDCrmtbfljZfnnIHojaPoqonov5vlh7rlj6PotLjmmJPmnInpmZDlhazlj7gwEgYKKwYBBAGpQ2QFAwQEDAIwMTAiBgorBgEEAalDZAUIBBQMEjUxMjMyNDE5NjQxMDE3Mjk3WDAgBgorBgEEAalDZAUJBBIMEDAzLUpKMEc5MDAyMjA3NTIwGQYKKwYBBAGpQ2QFCwQLDAlNQTVUTkZHWTkwEgYKKwYBBAGpQ2QFDAQEDAIwMDASBgorBgEEAalDZAIBBAQMAjEyMBIGCisGAQQBqUNkAgQEBAwCMTQwDAYIKoEcz1UBg3UFAANIADBFAiBM4OVAc8aaCZU4XFfcVMkC7bWIIenRnPLxrnwVeYO3CQIhANQ767YIurkJCoLtwyqQPbUZe/+3BjGZcIWqB1mAl9T+"); //原始内容
ukeyRequest = new UkeyRequest("cus-sec_SpcEncodePEM", _args);
args = signClient.getUkeyResponseArgs(ukeyRequest);
System.err.println("PEM编码: " + JSONUtil.toJsonStr(args));
//inData PEM编码的内容(字符串)
_args.put("inData", "MIIEoDCCBESgAwIBAgIIAwAAAAAM3m8wDAYIKoEcz1UBg3UFADCBmDELMAkGA1UEBhMCQ04xDzANBgNVBAgMBuWMl+S6rDEPMA0GA1UEBwwG5YyX5LqsMRswGQYDVQQKDBLkuK3lm73nlLXlrZDlj6PlsrgxGzAZBgNVBAsMEuivgeS5pueuoeeQhuS4reW/gzEtMCsGA1UEAwwk5Lit5Zu955S15a2Q5Lia5Yqh6K+B5Lmm566h55CG5Lit5b+DMB4XDTIzMDMyOTAwMDAwMFoXDTMzMDMyOTAwMDAwMFowVjELMAkGA1UEBhMCQ04xMzAxBgNVBAsMKua1t+WNl+ecgeiNo+iqiei/m+WHuuWPo+i0uOaYk+aciemZkOWFrOWPuDESMBAGA1UEAwwJ5p2o5aaC6YeRMFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAE0vOQmplAr9igPZrA8F1msqnFd0U++6G6NhG5rNuIUWft0BwQn7eSJkt5/fvSSoe7pUg2/awHUWPnzkeeQc7oVqOCArUwggKxMBEGCWCGSAGG+EIBAQQEAwIFoDAOBgNVHQ8BAf8EBAMCBsAwCQYDVR0TBAIwADApBgNVHSUEIjAgBggrBgEFBQcDAgYIKwYBBQUHAwQGCisGAQQBgjcUAgIwHwYDVR0jBBgwFoAURCQxt0wEvoAVXmuo4N1bjKXTh0UwHQYDVR0OBBYEFAytGob5L0WqhOCZ5l6Lf2jUdNrAMGgGA1UdIARhMF8wXQYEVR0gADBVMFMGCCsGAQUFBwIBFkdodHRwczovL3d3dy5jaGluYXBvcnQuZ292LmNuL3RjbXNmaWxlL3UvY21zL3d3dy8yMDIyMDQvMTIxMzI5NDh4dDZwLnBkZjB/BgNVHR8EeDB2MHSgcqBwhm5sZGFwOi8vbGRhcC5jaGluYXBvcnQuZ292LmNuOjM4OS9jbj1jcmwwMzAwMDAsb3U9Y3JsMDAsb3U9Y3JsLGM9Y24/Y2VydGlmaWNhdGVSZXZvY2F0aW9uTGlzdD9iYXNlP2NuPWNybDAzMDAwMDA+BggrBgEFBQcBAQQyMDAwLgYIKwYBBQUHMAGGImh0dHA6Ly9vY3NwLmNoaW5hcG9ydC5nb3YuY246ODgwMC8wOgYKKwYBBAGpQ2QFAQQsDCrmtbfljZfnnIHojaPoqonov5vlh7rlj6PotLjmmJPmnInpmZDlhazlj7gwEgYKKwYBBAGpQ2QFAwQEDAIwMTAiBgorBgEEAalDZAUIBBQMEjUxMjMyNDE5NjQxMDE3Mjk3WDAgBgorBgEEAalDZAUJBBIMEDAzLUpKMEc5MDAyMjA3NTIwGQYKKwYBBAGpQ2QFCwQLDAlNQTVUTkZHWTkwEgYKKwYBBAGpQ2QFDAQEDAIwMDASBgorBgEEAalDZAIBBAQMAjEyMBIGCisGAQQBqUNkAgQEBAwCMTQwDAYIKoEcz1UBg3UFAANIADBFAiBM4OVAc8aaCZU4XFfcVMkC7bWIIenRnPLxrnwVeYO3CQIhANQ767YIurkJCoLtwyqQPbUZe/+3BjGZcIWqB1mAl9T+"); //原始内容
ukeyRequest = new UkeyRequest("cus-sec_SpcDecodePEM", _args);
args = signClient.getUkeyResponseArgs(ukeyRequest);
System.err.println("PEM编码: " + JSONUtil.toJsonStr(args));
// inData PEM编码的内容(字符串), PEM解码,返回String类型的原文
_args.put("inData", "MIIEoDCCBESgAwIBAgIIAwAAAAAM3m8wDAYIKoEcz1UBg3UFADCBmDELMAkGA1UEBhMCQ04xDzANBgNVBAgMBuWMl+S6rDEPMA0GA1UEBwwG5YyX5LqsMRswGQYDVQQKDBLkuK3lm73nlLXlrZDlj6PlsrgxGzAZBgNVBAsMEuivgeS5pueuoeeQhuS4reW/gzEtMCsGA1UEAwwk5Lit5Zu955S15a2Q5Lia5Yqh6K+B5Lmm566h55CG5Lit5b+DMB4XDTIzMDMyOTAwMDAwMFoXDTMzMDMyOTAwMDAwMFowVjELMAkGA1UEBhMCQ04xMzAxBgNVBAsMKua1t+WNl+ecgeiNo+iqiei/m+WHuuWPo+i0uOaYk+aciemZkOWFrOWPuDESMBAGA1UEAwwJ5p2o5aaC6YeRMFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAE0vOQmplAr9igPZrA8F1msqnFd0U++6G6NhG5rNuIUWft0BwQn7eSJkt5/fvSSoe7pUg2/awHUWPnzkeeQc7oVqOCArUwggKxMBEGCWCGSAGG+EIBAQQEAwIFoDAOBgNVHQ8BAf8EBAMCBsAwCQYDVR0TBAIwADApBgNVHSUEIjAgBggrBgEFBQcDAgYIKwYBBQUHAwQGCisGAQQBgjcUAgIwHwYDVR0jBBgwFoAURCQxt0wEvoAVXmuo4N1bjKXTh0UwHQYDVR0OBBYEFAytGob5L0WqhOCZ5l6Lf2jUdNrAMGgGA1UdIARhMF8wXQYEVR0gADBVMFMGCCsGAQUFBwIBFkdodHRwczovL3d3dy5jaGluYXBvcnQuZ292LmNuL3RjbXNmaWxlL3UvY21zL3d3dy8yMDIyMDQvMTIxMzI5NDh4dDZwLnBkZjB/BgNVHR8EeDB2MHSgcqBwhm5sZGFwOi8vbGRhcC5jaGluYXBvcnQuZ292LmNuOjM4OS9jbj1jcmwwMzAwMDAsb3U9Y3JsMDAsb3U9Y3JsLGM9Y24/Y2VydGlmaWNhdGVSZXZvY2F0aW9uTGlzdD9iYXNlP2NuPWNybDAzMDAwMDA+BggrBgEFBQcBAQQyMDAwLgYIKwYBBQUHMAGGImh0dHA6Ly9vY3NwLmNoaW5hcG9ydC5nb3YuY246ODgwMC8wOgYKKwYBBAGpQ2QFAQQsDCrmtbfljZfnnIHojaPoqonov5vlh7rlj6PotLjmmJPmnInpmZDlhazlj7gwEgYKKwYBBAGpQ2QFAwQEDAIwMTAiBgorBgEEAalDZAUIBBQMEjUxMjMyNDE5NjQxMDE3Mjk3WDAgBgorBgEEAalDZAUJBBIMEDAzLUpKMEc5MDAyMjA3NTIwGQYKKwYBBAGpQ2QFCwQLDAlNQTVUTkZHWTkwEgYKKwYBBAGpQ2QFDAQEDAIwMDASBgorBgEEAalDZAIBBAQMAjEyMBIGCisGAQQBqUNkAgQEBAwCMTQwDAYIKoEcz1UBg3UFAANIADBFAiBM4OVAc8aaCZU4XFfcVMkC7bWIIenRnPLxrnwVeYO3CQIhANQ767YIurkJCoLtwyqQPbUZe/+3BjGZcIWqB1mAl9T+"); //原始内容
ukeyRequest = new UkeyRequest("cus-sec_SpcDecodePEMAsString", _args);
args = signClient.getUkeyResponseArgs(ukeyRequest);
System.err.println("PEM解码,返回String类型的原文: " + JSONUtil.toJsonStr(args));
}
@Test
@DisplayName("签名+取海关签名证书PEM+验证签名")
void f2() {
HashMap<String, Object> _args = new HashMap<>();
String xml = """
<ds:SignedInfo xmlns:ceb="http://www.chinaport.gov.cn/ceb" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><ds:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"></ds:CanonicalizationMethod><ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sm2-sm3"></ds:SignatureMethod><ds:Reference URI=""><ds:Transforms><ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"></ds:Transform></ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></ds:DigestMethod><ds:DigestValue>gTNsxKmXAJfylGtBHhi7HfMRnec=</ds:DigestValue></ds:Reference></ds:SignedInfo>
""";
_args.put("inData", xml);
_args.put("passwd", PASSWORD);
UkeyRequest ukeyRequest = new UkeyRequest("cus-sec_SpcSignDataAsPEM", _args);
Args args = signClient.getUkeyResponseArgs(ukeyRequest);
System.err.println("签名,返回PEM格式: " + args.getData().get(0));
ukeyRequest = new UkeyRequest("cus-sec_SpcGetSignCertAsPEM", _args);
Args args2 = signClient.getUkeyResponseArgs(ukeyRequest);
System.err.println("取海关签名证书PEM: " + JSONUtil.toJsonStr(args2));
CertificateHandler reportClient = SpringUtil.getBean(CertificateHandler.class);
_args.put("inData", xml); //原文信息
_args.put("signData", args.getData().get(0)); //签名信息
//海关签名证书PEM
//_args.put("certDataPEM", reportClient.buildX509CertificateWithoutHeader(args2.getData().get(0)).replaceAll("\n",""));
_args.put("certDataPEM", args2.getData().get(0));
args = signClient.getUkeyResponseArgs(new UkeyRequest("cus-sec_SpcVerifySignData", _args));
System.err.println("验证签名: " + args.getData().get(0));
//签名,返回PEM格式: PABavspV+F57kSl3bzcPmqqWg1fEq6e+TrdwuKr3jDGEtpTuxuBdY2IB+aTGyLFfagQZ6fo8Ph4541OCe6rhPQ==
//取海关签名证书PEM: {"Result":true,"Data":["MIIEoDCCBESgAwIBAgIIAwAAAAAM3m8wDAYIKoEcz1UBg3UFADCBmDELMAkGA1UEBhMCQ04xDzANBgNVBAgMBuWMl+S6rDEPMA0GA1UEBwwG5YyX5LqsMRswGQYDVQQKDBLkuK3lm73nlLXlrZDlj6PlsrgxGzAZBgNVBAsMEuivgeS5pueuoeeQhuS4reW/gzEtMCsGA1UEAwwk5Lit5Zu955S15a2Q5Lia5Yqh6K+B5Lmm566h55CG5Lit5b+DMB4XDTIzMDMyOTAwMDAwMFoXDTMzMDMyOTAwMDAwMFowVjELMAkGA1UEBhMCQ04xMzAxBgNVBAsMKua1t+WNl+ecgeiNo+iqiei/m+WHuuWPo+i0uOaYk+aciemZkOWFrOWPuDESMBAGA1UEAwwJ5p2o5aaC6YeRMFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAE0vOQmplAr9igPZrA8F1msqnFd0U++6G6NhG5rNuIUWft0BwQn7eSJkt5/fvSSoe7pUg2/awHUWPnzkeeQc7oVqOCArUwggKxMBEGCWCGSAGG+EIBAQQEAwIFoDAOBgNVHQ8BAf8EBAMCBsAwCQYDVR0TBAIwADApBgNVHSUEIjAgBggrBgEFBQcDAgYIKwYBBQUHAwQGCisGAQQBgjcUAgIwHwYDVR0jBBgwFoAURCQxt0wEvoAVXmuo4N1bjKXTh0UwHQYDVR0OBBYEFAytGob5L0WqhOCZ5l6Lf2jUdNrAMGgGA1UdIARhMF8wXQYEVR0gADBVMFMGCCsGAQUFBwIBFkdodHRwczovL3d3dy5jaGluYXBvcnQuZ292LmNuL3RjbXNmaWxlL3UvY21zL3d3dy8yMDIyMDQvMTIxMzI5NDh4dDZwLnBkZjB/BgNVHR8EeDB2MHSgcqBwhm5sZGFwOi8vbGRhcC5jaGluYXBvcnQuZ292LmNuOjM4OS9jbj1jcmwwMzAwMDAsb3U9Y3JsMDAsb3U9Y3JsLGM9Y24/Y2VydGlmaWNhdGVSZXZvY2F0aW9uTGlzdD9iYXNlP2NuPWNybDAzMDAwMDA+BggrBgEFBQcBAQQyMDAwLgYIKwYBBQUHMAGGImh0dHA6Ly9vY3NwLmNoaW5hcG9ydC5nb3YuY246ODgwMC8wOgYKKwYBBAGpQ2QFAQQsDCrmtbfljZfnnIHojaPoqonov5vlh7rlj6PotLjmmJPmnInpmZDlhazlj7gwEgYKKwYBBAGpQ2QFAwQEDAIwMTAiBgorBgEEAalDZAUIBBQMEjUxMjMyNDE5NjQxMDE3Mjk3WDAgBgorBgEEAalDZAUJBBIMEDAzLUpKMEc5MDAyMjA3NTIwGQYKKwYBBAGpQ2QFCwQLDAlNQTVUTkZHWTkwEgYKKwYBBAGpQ2QFDAQEDAIwMDASBgorBgEEAalDZAIBBAQMAjEyMBIGCisGAQQBqUNkAgQEBAwCMTQwDAYIKoEcz1UBg3UFAANIADBFAiBM4OVAc8aaCZU4XFfcVMkC7bWIIenRnPLxrnwVeYO3CQIhANQ767YIurkJCoLtwyqQPbUZe/+3BjGZcIWqB1mAl9T+"],"Error":[]}
//验证签名: 成功!
}
@Test
@DisplayName("签名+验证签名")
void f2bak() {
String inData = """
<ds:SignedInfo xmlns:ceb="http://www.chinaport.gov.cn/ceb" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><ds:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"></ds:CanonicalizationMethod><ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sm2-sm3"></ds:SignatureMethod><ds:Reference URI=""><ds:Transforms><ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature"></ds:Transform></ds:Transforms><ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"></ds:DigestMethod><ds:DigestValue>gTNsxKmXAJfylGtBHhi7HfMRnec=</ds:DigestValue></ds:Reference></ds:SignedInfo>
""";
String certDataPEM = """
MIIEoDCCBESgAwIBAgIIAwAAAAAM3m8wDAYIKoEcz1UBg3UFADCBmDELMAkGA1UEBhMCQ04xDzANBgNVBAgMBuWMl+S6rDEPMA0GA1UEBwwG5YyX5LqsMRswGQYDVQQKDBLkuK3lm73nlLXlrZDlj6PlsrgxGzAZBgNVBAsMEuivgeS5pueuoeeQhuS4reW/gzEtMCsGA1UEAwwk5Lit5Zu955S15a2Q5Lia5Yqh6K+B5Lmm566h55CG5Lit5b+DMB4XDTIzMDMyOTAwMDAwMFoXDTMzMDMyOTAwMDAwMFowVjELMAkGA1UEBhMCQ04xMzAxBgNVBAsMKua1t+WNl+ecgeiNo+iqiei/m+WHuuWPo+i0uOaYk+aciemZkOWFrOWPuDESMBAGA1UEAwwJ5p2o5aaC6YeRMFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAE0vOQmplAr9igPZrA8F1msqnFd0U++6G6NhG5rNuIUWft0BwQn7eSJkt5/fvSSoe7pUg2/awHUWPnzkeeQc7oVqOCArUwggKxMBEGCWCGSAGG+EIBAQQEAwIFoDAOBgNVHQ8BAf8EBAMCBsAwCQYDVR0TBAIwADApBgNVHSUEIjAgBggrBgEFBQcDAgYIKwYBBQUHAwQGCisGAQQBgjcUAgIwHwYDVR0jBBgwFoAURCQxt0wEvoAVXmuo4N1bjKXTh0UwHQYDVR0OBBYEFAytGob5L0WqhOCZ5l6Lf2jUdNrAMGgGA1UdIARhMF8wXQYEVR0gADBVMFMGCCsGAQUFBwIBFkdodHRwczovL3d3dy5jaGluYXBvcnQuZ292LmNuL3RjbXNmaWxlL3UvY21zL3d3dy8yMDIyMDQvMTIxMzI5NDh4dDZwLnBkZjB/BgNVHR8EeDB2MHSgcqBwhm5sZGFwOi8vbGRhcC5jaGluYXBvcnQuZ292LmNuOjM4OS9jbj1jcmwwMzAwMDAsb3U9Y3JsMDAsb3U9Y3JsLGM9Y24/Y2VydGlmaWNhdGVSZXZvY2F0aW9uTGlzdD9iYXNlP2NuPWNybDAzMDAwMDA+BggrBgEFBQcBAQQyMDAwLgYIKwYBBQUHMAGGImh0dHA6Ly9vY3NwLmNoaW5hcG9ydC5nb3YuY246ODgwMC8wOgYKKwYBBAGpQ2QFAQQsDCrmtbfljZfnnIHojaPoqonov5vlh7rlj6PotLjmmJPmnInpmZDlhazlj7gwEgYKKwYBBAGpQ2QFAwQEDAIwMTAiBgorBgEEAalDZAUIBBQMEjUxMjMyNDE5NjQxMDE3Mjk3WDAgBgorBgEEAalDZAUJBBIMEDAzLUpKMEc5MDAyMjA3NTIwGQYKKwYBBAGpQ2QFCwQLDAlNQTVUTkZHWTkwEgYKKwYBBAGpQ2QFDAQEDAIwMDASBgorBgEEAalDZAIBBAQMAjEyMBIGCisGAQQBqUNkAgQEBAwCMTQwDAYIKoEcz1UBg3UFAANIADBFAiBM4OVAc8aaCZU4XFfcVMkC7bWIIenRnPLxrnwVeYO3CQIhANQ767YIurkJCoLtwyqQPbUZe/+3BjGZcIWqB1mAl9T+
""";
UkeyRequest ukeyRequest = new UkeyRequest("cus-sec_SpcSignDataAsPEM", new HashMap<>() {{
put("inData", inData);
put("passwd", PASSWORD);
}});
Args _args = signClient.getUkeyResponseArgs(ukeyRequest);
String signValue = _args.getData().get(0);
System.err.println("signValue: " + signValue + ", certNo: " + _args.getData().get(1));
// signValue: 9cxkIKyiFi6X5v4JZsJQExJS+3pKmWcWzQjO0ziHHN9BZV5p04gb8XaCkeIQZFlVe93mI47az8xZAwSB1y1l+g==, certNo: 03000000000cde6f
Map<String, Object> argsMap = new LinkedHashMap<>();
argsMap.put("inData", "<ds:SignedInfo xmlns:ceb=\"http://www.chinaport.gov.cn/ceb\" xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"><ds:CanonicalizationMethod Algorithm=\"http://www.w3.org/TR/2001/REC-xml-c14n-20010315\"></ds:CanonicalizationMethod><ds:SignatureMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#sm2-sm3\"></ds:SignatureMethod><ds:Reference URI=\"\"><ds:Transforms><ds:Transform Algorithm=\"http://www.w3.org/2000/09/xmldsig#enveloped-signature\"></ds:Transform></ds:Transforms><ds:DigestMethod Algorithm=\"http://www.w3.org/2000/09/xmldsig#sha1\"></ds:DigestMethod><ds:DigestValue>tYLIrRGmnwZb42LRdVr0ITzkFkA=</ds:DigestValue></ds:Reference></ds:SignedInfo>");
argsMap.put("signData", "XvSyJzDPnePobS0kMCdCTevE4OYEKn5QTdHsx6SsZPzMfN9vks4Zlwngndn979VcZzaLw/U7G8aApb4tfarJ/A==");
if (StringUtils.isNotBlank(certDataPEM)) {
argsMap.put("certDataPEM", certDataPEM);
}
Args args = signClient.getUkeyResponseArgs(new UkeyRequest("cus-sec_SpcVerifySignData", argsMap));
System.err.println("验证签名结果: " + args.getData().get(0));
}
@Test
@DisplayName("取算法类型(取加密算法标识)")
void algorithmType() {
HashMap<String, Object> _args = new HashMap<>();
UkeyRequest ukeyRequest = new UkeyRequest("cus-sec_SpcGetCardID", _args);
Args args = signClient.getUkeyResponseArgs(ukeyRequest);
System.err.println("获取卡号: " + JSONUtil.toJsonStr(args));
_args = new HashMap<>();
_args.put("lReader", args.getData().get(0));
ukeyRequest = new UkeyRequest("ra_GetCryptAlgo", _args);
args = signClient.getUkeyResponseArgs(ukeyRequest);
System.err.println("取算法类型(取加密算法标识)" + args.getData().get(0));//s
}
@Test
@DisplayName("证书切割")
void certificateCutting() {
String pom = "MIIEoDCCBESgAwIBAgIIAwAAAAAM3m8wDAYIKoEcz1UBg3UFADCBmDELMAkGA1UEBhMCQ04xDzANBgNVBAgMBuWMl+S6rDEPMA0GA1UEBwwG5YyX5LqsMRswGQYDVQQKDBLkuK3lm73nlLXlrZDlj6PlsrgxGzAZBgNVBAsMEuivgeS5pueuoeeQhuS4reW/gzEtMCsGA1UEAwwk5Lit5Zu955S15a2Q5Lia5Yqh6K+B5Lmm566h55CG5Lit5b+DMB4XDTIzMDMyOTAwMDAwMFoXDTMzMDMyOTAwMDAwMFowVjELMAkGA1UEBhMCQ04xMzAxBgNVBAsMKua1t+WNl+ecgeiNo+iqiei/m+WHuuWPo+i0uOaYk+aciemZkOWFrOWPuDESMBAGA1UEAwwJ5p2o5aaC6YeRMFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAE0vOQmplAr9igPZrA8F1msqnFd0U++6G6NhG5rNuIUWft0BwQn7eSJkt5/fvSSoe7pUg2/awHUWPnzkeeQc7oVqOCArUwggKxMBEGCWCGSAGG+EIBAQQEAwIFoDAOBgNVHQ8BAf8EBAMCBsAwCQYDVR0TBAIwADApBgNVHSUEIjAgBggrBgEFBQcDAgYIKwYBBQUHAwQGCisGAQQBgjcUAgIwHwYDVR0jBBgwFoAURCQxt0wEvoAVXmuo4N1bjKXTh0UwHQYDVR0OBBYEFAytGob5L0WqhOCZ5l6Lf2jUdNrAMGgGA1UdIARhMF8wXQYEVR0gADBVMFMGCCsGAQUFBwIBFkdodHRwczovL3d3dy5jaGluYXBvcnQuZ292LmNuL3RjbXNmaWxlL3UvY21zL3d3dy8yMDIyMDQvMTIxMzI5NDh4dDZwLnBkZjB/BgNVHR8EeDB2MHSgcqBwhm5sZGFwOi8vbGRhcC5jaGluYXBvcnQuZ292LmNuOjM4OS9jbj1jcmwwMzAwMDAsb3U9Y3JsMDAsb3U9Y3JsLGM9Y24/Y2VydGlmaWNhdGVSZXZvY2F0aW9uTGlzdD9iYXNlP2NuPWNybDAzMDAwMDA+BggrBgEFBQcBAQQyMDAwLgYIKwYBBQUHMAGGImh0dHA6Ly9vY3NwLmNoaW5hcG9ydC5nb3YuY246ODgwMC8wOgYKKwYBBAGpQ2QFAQQsDCrmtbfljZfnnIHojaPoqonov5vlh7rlj6PotLjmmJPmnInpmZDlhazlj7gwEgYKKwYBBAGpQ2QFAwQEDAIwMTAiBgorBgEEAalDZAUIBBQMEjUxMjMyNDE5NjQxMDE3Mjk3WDAgBgorBgEEAalDZAUJBBIMEDAzLUpKMEc5MDAyMjA3NTIwGQYKKwYBBAGpQ2QFCwQLDAlNQTVUTkZHWTkwEgYKKwYBBAGpQ2QFDAQEDAIwMDASBgorBgEEAalDZAIBBAQMAjEyMBIGCisGAQQBqUNkAgQEBAwCMTQwDAYIKoEcz1UBg3UFAANIADBFAiBM4OVAc8aaCZU4XFfcVMkC7bWIIenRnPLxrnwVeYO3CQIhANQ767YIurkJCoLtwyqQPbUZe/+3BjGZcIWqB1mAl9T+";
CertificateHandler reportClient = SpringUtil.getBean(CertificateHandler.class);
System.out.println(CertificateHandler.buildX509CertificateWithoutHeader(pom));
}
@Test
@DisplayName("获取证书信息")
void getCertificateInformation() {
Args args = signClient.getUkeyResponseArgs(new UkeyRequest("cus-sec_SpcGetSignCertAsPEM", new LinkedHashMap<>()));
String x509Certificate = CertificateHandler.buildX509CertificateWithHeader(args.getData().get(0));
CertificateParser.parseCertificateByCertText(x509Certificate);
//2023-07-10T22:24:26.331+08:00 INFO 37111 --- [ main] c.a.e.signature.core.CertificateParser : 签发者: CN=中国电子业务证书管理中心,OU=证书管理中心,O=中国电子口岸,L=北京,ST=北京,C=CN
//2023-07-10T22:24:26.331+08:00 INFO 37111 --- [ main] c.a.e.signature.core.CertificateParser : 主体: CN=杨XX,OU=XX省XX进出口贸易有限公司,C=CN
//2023-07-10T22:24:26.344+08:00 INFO 37111 --- [ main] c.a.e.signature.core.CertificateParser : 有效期起始日期: Wed Mar 29 08:00:00 CST 2023
//2023-07-10T22:24:26.344+08:00 INFO 37111 --- [ main] c.a.e.signature.core.CertificateParser : 有效期结束日期: Tue Mar 29 08:00:00 CST 2033
//2023-07-10T22:24:26.427+08:00 INFO 37111 --- [ main] c.a.e.signature.core.CertificateParser : 公钥算法: EC
//2023-07-10T22:24:26.427+08:00 INFO 37111 --- [ main] c.a.e.signature.core.CertificateParser : 签名算法: SM3WITHSM2
}
}
@@ -1,183 +0,0 @@
package cn.alphahub.eport.signature.controller.rpc;
import cn.alphahub.eport.signature.config.ChinaEportProperties;
import cn.alphahub.eport.signature.core.ChinaEportReportClient;
import cn.alphahub.eport.signature.core.SignHandler;
import cn.alphahub.eport.signature.core.web.EportCebMessageHttpClient;
import cn.alphahub.eport.signature.entity.SignRequest;
import cn.alphahub.eport.signature.entity.SignResult;
import cn.alphahub.eport.signature.entity.UkeyRequest;
import cn.alphahub.eport.signature.entity.UkeyResponse.Args;
import cn.hutool.core.codec.Base64;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;
import io.github.weasleyj.china.eport.sign.constants.MessageType;
import io.github.weasleyj.china.eport.sign.model.cebmessage.CEB311Message;
import io.github.weasleyj.china.eport.sign.model.request.MessageRequest;
import io.github.weasleyj.china.eport.sign.util.GUIDUtil;
import io.github.weasleyj.china.eport.sign.util.JAXBUtil;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* Eport Sign Controller Test
*/
@Slf4j
@SpringBootTest
class EportSignControllerTest {
@Autowired
SignHandler signHandler;
@Autowired
EportSignController controller;
@Autowired
ChinaEportProperties chinaEportProperties;
@Autowired
ChinaEportReportClient chinaEportReportClient;
@Autowired
EportCebMessageHttpClient cebMessageHttpClient;
@Test
@DisplayName("海关XML数据加签+验正签名结果")
void signAndVerifyXMLSignature() {
String data = """
{
"id": 1,
"data": "<?xml version=\\"1.0\\" encoding=\\"UTF-8\\"?>\\n<ceb:CEB311Message guid=\\"tCEB311_HNZB_HNFX_20230707223752_003\\" version=\\"1.0\\" xmlns:ceb=\\"http://www.chinaport.gov.cn/ceb\\" xmlns:xsi=\\"http://www.w3.org/2001/XMLSchema-instance\\">\\n <ceb:Order>\\n <ceb:OrderHead>\\n <ceb:guid>tCEB311_HNZB_HNFX_20230707223752_001</ceb:guid>\\n <ceb:appType>1</ceb:appType>\\n <ceb:appTime>20230704181028</ceb:appTime>\\n <ceb:appStatus>2</ceb:appStatus>\\n <ceb:orderType>I</ceb:orderType>\\n <ceb:orderNo>T_C5051511332138160010</ceb:orderNo>\\n <ceb:ebpCode>4601630004</ceb:ebpCode>\\n <ceb:ebpName>海南省荣誉进出口贸易有限公司</ceb:ebpName>\\n <ceb:ebcCode>4601630004</ceb:ebcCode>\\n <ceb:ebcName>海南省荣誉进出口贸易有限公司</ceb:ebcName>\\n <ceb:goodsValue>0.01</ceb:goodsValue>\\n <ceb:freight>0</ceb:freight>\\n <ceb:discount>0</ceb:discount>\\n <ceb:taxTotal>0</ceb:taxTotal>\\n <ceb:acturalPaid>0.01</ceb:acturalPaid>\\n <ceb:currency>142</ceb:currency>\\n <ceb:buyerRegNo>4</ceb:buyerRegNo>\\n <ceb:buyerName>袁晓雨</ceb:buyerName>\\n <ceb:buyerTelephone>13701727375</ceb:buyerTelephone>\\n <ceb:buyerIdType>1</ceb:buyerIdType>\\n <ceb:buyerIdNumber>130435200009241538</ceb:buyerIdNumber>\\n <ceb:consignee>袁晓雨</ceb:consignee>\\n <ceb:consigneeTelephone>13701727375</ceb:consigneeTelephone>\\n <ceb:consigneeAddress>北京北京市东城区</ceb:consigneeAddress>\\n <ceb:note>test</ceb:note>\\n </ceb:OrderHead>\\n <ceb:OrderList>\\n <ceb:gnum>1</ceb:gnum>\\n <ceb:itemNo>1</ceb:itemNo>\\n <ceb:itemName>LANNA兰纳</ceb:itemName>\\n <ceb:gmodel>10片/包</ceb:gmodel>\\n <ceb:itemDescribe></ceb:itemDescribe>\\n <ceb:barCode>1</ceb:barCode>\\n <ceb:unit>011</ceb:unit>\\n <ceb:qty>1</ceb:qty>\\n <ceb:price>1</ceb:price>\\n <ceb:totalPrice>1</ceb:totalPrice>\\n <ceb:currency>142</ceb:currency>\\n <ceb:country>136</ceb:country>\\n <ceb:note>test</ceb:note>\\n </ceb:OrderList>\\n </ceb:Order>\\n <ceb:BaseTransfer>\\n <ceb:copCode>4601630004</ceb:copCode>\\n <ceb:copName>海南省荣誉进出口贸易有限公司</ceb:copName>\\n <ceb:dxpMode>DXP</ceb:dxpMode>\\n <ceb:dxpId>DXPENT0000530815</ceb:dxpId>\\n <ceb:note>test</ceb:note>\\n </ceb:BaseTransfer>\\n</ceb:CEB311Message>"
}
""";
SignRequest request = JSONUtil.toBean(data, SignRequest.class);
SignResult result = controller.signature(request).getData();
Map<String, Object> argsMap = new LinkedHashMap<>();
argsMap.put("inData", result.getSignatureNode());
argsMap.put("signData", result.getSignatureValue());
argsMap.put("certDataPEM", result.getX509Certificate());
Args args = signHandler.getUkeyResponseArgs(new UkeyRequest("cus-sec_SpcVerifySignData", argsMap));
System.err.println("验签结果: " + args.getData().get(0)); //验签结果: 成功!
}
@Test
@DisplayName("海关179数据加签+验正签名结果")
void signAndVerify179Signature() {
String data = """
{
"id": 1,
"data": "\\"sessionID\\":\\"DUYOK9-WEASLEY-20230715114316-0UTMKF\\"||\\"payExchangeInfoHead\\":\\"{\\"guid\\":\\"DUYOK9-WEASLEY-20230715114316-0UTMKF\\",\\"initalRequest\\":\\"<xml><appid><![CDATA[xwxwxwxwx112121]]></appid><mch_id>11111111</mch_id><body><![CDATA[测试测试]]></body><nonce_str><![CDATA[ashdgahgdhaghgliqwueyqu1]]></nonce_str><out_trade_no>202009231454212140210352</out_trade_no><total_fee>500</total_fee><spbill_create_ip><![CDATA[192.168.0.1]]></spbill_create_ip><notify_url><![CDATA[https://www.baidu.com]]></notify_url><openid><![CDATA[adasyduiyasdhjxzycua-Q]]></openid><trade_type><![CDATA[JSAPI]]></trade_type><sign><![CDATA[asd1sa56d4545wqe44wq5]]></sign></xml>\\",\\"initalResponse\\":\\"<xml><appid><![CDATA[xwxwxwxwx112121]]></appid><bank_type><![CDATA[OTHERS]]></bank_type><cash_fee>500</cash_fee><fee_type><![CDATA[CNY]]></fee_type><is_subscribe><![CDATA[N]]></is_subscribe><mch_id>11111111</mch_id><nonce_str><![CDATA[ashdgahgdhaghgliqwueyqu1]]></nonce_str><openid><![CDATA[adasyduiyasdhjxzycua-Q]]></openid><out_trade_no>202009231454212140210352</out_trade_no><result_code><![CDATA[SUCCESS]]></result_code><return_code><![CDATA[SUCCESS]]></return_code><sign><![CDATA[asgdhasgdhasgdhgasgdhasgdh]]></sign><time_end>20200923145426</time_end><total_fee>500</total_fee><trade_type><![CDATA[JSAPI]]></trade_type><transaction_id>4200000681202009235085032319</transaction_id></xml>\\",\\"ebpCode\\":\\"46016602EV\\",\\"payCode\\":\\"1537923071\\",\\"payTransactionId\\":\\"4200000681202009235085032319\\",\\"totalAmount\\":5.0,\\"currency\\":\\"142\\",\\"verDept\\":\\"3\\",\\"payType\\":\\"4\\",\\"tradingTime\\":\\"20200923145426\\",\\"note\\":\\"\\"}\\"||\\"payExchangeInfoLists\\":\\"[{\\"orderNo\\":\\"202009231454218421271832\\",\\"goodsInfo\\":[{\\"gname\\":\\"济州花梨精华面膜\\",\\"itemLink\\":\\"http://m.yunjiweidian.com/yunjibuyer/static/vue-buyer/idc/index.html#/detail?itemId=999761&shopId=453\\"}],\\"recpAccount\\":\\"46016602EV\\",\\"recpCode\\":\\"46016602EV\\",\\"recpName\\":\\"海南省荣誉进出口贸易有限公司\\"}]\\"||\\"serviceTime\\":\\"1689392596517\\"";
}
""";
SignRequest request = JSONUtil.toBean(data, SignRequest.class);
SignResult result = controller.signature(request).getData();
Map<String, Object> argsMap = new LinkedHashMap<>();
argsMap.put("inData", request.getData());
argsMap.put("signData", result.getSignatureValue());
argsMap.put("certDataPEM", result.getX509Certificate());
Args args = signHandler.getUkeyResponseArgs(new UkeyRequest("cus-sec_SpcVerifySignData", argsMap));
System.err.println("验签结果: " + args.getData().get(0));
}
@Test
@DisplayName("模拟海关XML数据加签推单")
void signAndVerifyXMLSignatureAndPush() throws Exception {
String xml = """
<?xml version="1.0" encoding="UTF-8"?>
<ceb:CEB311Message guid="CEB311_HNZB_HNFX_20230707223752_010" version="1.0" xmlns:ceb="http://www.chinaport.gov.cn/ceb" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ceb:Order>
<ceb:OrderHead>
<ceb:guid>CEB311_HNZB_HNFX_20230707223752_010</ceb:guid>
<ceb:appType>1</ceb:appType>
<ceb:appTime>20230704181028</ceb:appTime>
<ceb:appStatus>2</ceb:appStatus>
<ceb:orderType>I</ceb:orderType>
<ceb:orderNo>T_C5051511332138160010</ceb:orderNo>
<ceb:ebpCode>4601630004</ceb:ebpCode>
<ceb:ebpName>海南省荣誉进出口贸易有限公司</ceb:ebpName>
<ceb:ebcCode>4601630004</ceb:ebcCode>
<ceb:ebcName>海南省荣誉进出口贸易有限公司</ceb:ebcName>
<ceb:goodsValue>0.01</ceb:goodsValue>
<ceb:freight>0</ceb:freight>
<ceb:discount>0</ceb:discount>
<ceb:taxTotal>0</ceb:taxTotal>
<ceb:acturalPaid>0.01</ceb:acturalPaid>
<ceb:currency>142</ceb:currency>
<ceb:buyerRegNo>4</ceb:buyerRegNo>
<ceb:buyerName>袁晓雨</ceb:buyerName>
<ceb:buyerTelephone>13701727375</ceb:buyerTelephone>
<ceb:buyerIdType>1</ceb:buyerIdType>
<ceb:buyerIdNumber>130435200009241538</ceb:buyerIdNumber>
<ceb:consignee>袁晓雨</ceb:consignee>
<ceb:consigneeTelephone>13701727375</ceb:consigneeTelephone>
<ceb:consigneeAddress>北京北京市东城区</ceb:consigneeAddress>
<ceb:note>test</ceb:note>
</ceb:OrderHead>
<ceb:OrderList>
<ceb:gnum>1</ceb:gnum>
<ceb:itemNo>1</ceb:itemNo>
<ceb:itemName>LANNA兰纳</ceb:itemName>
<ceb:gmodel>10片/包</ceb:gmodel>
<ceb:itemDescribe></ceb:itemDescribe>
<ceb:barCode>1</ceb:barCode>
<ceb:unit>011</ceb:unit>
<ceb:qty>1</ceb:qty>
<ceb:price>1</ceb:price>
<ceb:totalPrice>1</ceb:totalPrice>
<ceb:currency>142</ceb:currency>
<ceb:country>136</ceb:country>
<ceb:note>test</ceb:note>
</ceb:OrderList>
</ceb:Order>
<ceb:BaseTransfer>
<ceb:copCode>4601630004</ceb:copCode>
<ceb:copName>海南省荣誉进出口贸易有限公司</ceb:copName>
<ceb:dxpMode>DXP</ceb:dxpMode>
<ceb:dxpId>DXPENT0000530815</ceb:dxpId>
<ceb:note>test</ceb:note>
</ceb:BaseTransfer>
</ceb:CEB311Message>
""";
SignRequest request = new SignRequest(xml);
// 组装数据
CEB311Message ceb311Message = JAXBUtil.toBean(xml, CEB311Message.class);
String guid = GUIDUtil.getGuid();
assert ceb311Message != null;
ceb311Message.setGuid(guid);
ceb311Message.getOrder().getOrderHead().setGuid(guid);
chinaEportReportClient.buildBaseTransfer(ceb311Message.getBaseTransfer());
MessageRequest messageRequest = chinaEportReportClient.buildMessageRequest(ceb311Message, MessageType.CEB311Message);
/*
String requestServer = StringUtils.defaultIfBlank(chinaEportProperties.getServer(), Base64.decodeStr(EPORT_CEBMESSAGE_SERVER_ENCODE));
String requestBody = JSONUtil.toJsonStr(messageRequest);
log.info("数据上报海关请求入参 {}\nRequest Server: {}", requestBody, requestServer);
HttpResponse httpResponse = HttpUtil.createPost(requestServer + "/cebcmsg")
.contentType(ContentType.JSON.getValue())
.body(requestBody)
.execute();
log.info("开始上报,Http响应结果: {}", httpResponse.body());
*/
cebMessageHttpClient.reportCebMessage(messageRequest).subscribe(data -> {
log.info("开始申报数据,Http响应结果: {}", data);
});
// 睡8秒,开始查询回执
LocalDateTime uploadTime = LocalDateTime.now();
int sleepTime = 15;
for (int i = sleepTime; i > 0; i--) {
log.warn("开始查询推送回执,海关后台正在处理中,线程挂起剩余 {} 秒。", i);
TimeUnit.SECONDS.sleep(1);
}
String url = "http://" + Base64.decodeStr("MzYuMTAxLjIwOC4yMzA=") + ":8090/ceb312msg?" + HttpUtil.toParams(new LinkedHashMap<>() {{
put("dxpid", chinaEportProperties.getDxpId());
put("qryid", DateTimeFormatter.ofPattern("yyyyMMddHHmmss").format(uploadTime));
}});
HttpResponse receiptResponse = HttpUtil.createGet(url).execute();
log.info("\n{}\n回执结果: {}", url, receiptResponse.body()); //回执结果: [{org_id=1, instm=1690556389000, re_order_guid=AOF0YM-WEASLEY-20230728225932-6QVICD, returnstatus=-301014, ebpcode=4601630004, returntime2=20230728225949, ebccode=4601630004, trans_dxpid=DXPENT0000530815, returninfo=订单报文处理失败,接收报文为旧报文数据,不做处理;, returntime=1690556377000, orderno=T_C5051511332138160010}]
}
}
@@ -1,109 +0,0 @@
package cn.alphahub.eport.signature.core;
import cn.alphahub.eport.signature.entity.SignRequest;
import cn.hutool.json.JSONUtil;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class SignHandlerTest {
@Resource
SignHandler signHandler;
@Resource
CertificateHandler certificateHandler;
@BeforeEach
void setUp() {
}
@AfterEach
void tearDown() {
}
@Test
void dynamicSignDataParameterTest() {
String xml = """
<ceb:CEB621Message xmlns:ceb="http://www.chinaport.gov.cn/ceb" guid="CEB621_HNZB_FXJK_20220208175054_0034"
version="v1.0">
<ceb:Inventory>
<ceb:InventoryHead>
<ceb:guid>CEB621_HNZB_FXJK_20220208175055_0035</ceb:guid>
<ceb:appType>1</ceb:appType>
<ceb:appTime>20220208175055</ceb:appTime>
<ceb:appStatus>2</ceb:appStatus>
<ceb:orderNo>48037605991776256</ceb:orderNo>
<ceb:ebpCode>46121601BC</ceb:ebpCode>
<ceb:ebpName>海南星创互联网医药有限公司</ceb:ebpName>
<ceb:ebcCode>46121601BC</ceb:ebcCode>
<ceb:ebcName>海南星创互联网医药有限公司</ceb:ebcName>
<ceb:logisticsNo>2222222</ceb:logisticsNo>
<ceb:logisticsCode>4101986180</ceb:logisticsCode>
<ceb:logisticsName>圆通</ceb:logisticsName>
<ceb:copNo>4101W68006</ceb:copNo>
<ceb:assureCode>4101960AGP</ceb:assureCode>
<ceb:ieFlag>I</ceb:ieFlag>
<ceb:declTime>20220208</ceb:declTime>
<ceb:customsCode>4601</ceb:customsCode>
<ceb:portCode>4601</ceb:portCode>
<ceb:ieDate>20220208</ceb:ieDate>
<ceb:buyerIdType>1</ceb:buyerIdType>
<ceb:buyerIdNumber>612326199005306719</ceb:buyerIdNumber>
<ceb:buyerName>用户_15150368</ceb:buyerName>
<ceb:buyerTelephone>18209182500</ceb:buyerTelephone>
<ceb:consigneeAddress>哈布斯堡</ceb:consigneeAddress>
<ceb:agentCode>46121601BC</ceb:agentCode>
<ceb:agentName>海南星创互联网医药有限公司</ceb:agentName>
<ceb:tradeMode>9610</ceb:tradeMode>
<ceb:trafMode>W</ceb:trafMode>
<ceb:country>142</ceb:country>
<ceb:freight>0.00</ceb:freight>
<ceb:insuredFee>0</ceb:insuredFee>
<ceb:currency>142</ceb:currency>
<ceb:packNo>1</ceb:packNo>
<ceb:grossWeight>0.07</ceb:grossWeight>
<ceb:netWeight>0.06</ceb:netWeight>
</ceb:InventoryHead>
<ceb:InventoryList>
<ceb:gnum>1</ceb:gnum>
<ceb:itemRecordNo>0797776159482</ceb:itemRecordNo>
<ceb:itemNo>HNGGKJ00000057</ceb:itemNo>
<ceb:itemName>通用铁剂胶囊</ceb:itemName>
<ceb:gcode>2106909090</ceb:gcode>
<ceb:gname>ACTIVE IRON</ceb:gname>
<ceb:gmodel>4|3|乳清蛋白(牛奶)65%,胶囊壳(羟苯基甲基纤维素)21%,硫酸亚铁1.2%,酸度调节剂2%,填充剂 10.8%|30粒/盒|无中文名称/ACTIVE IRON</ceb:gmodel>
<ceb:country>306</ceb:country>
<ceb:currency>142</ceb:currency>
<ceb:qty>1</ceb:qty>
<ceb:unit>140</ceb:unit>
<ceb:qty1>0.06</ceb:qty1>
<ceb:unit1>035</ceb:unit1>
<ceb:price>0.01</ceb:price>
<ceb:totalPrice>0.01</ceb:totalPrice>
</ceb:InventoryList>
</ceb:Inventory>
<ceb:BaseTransfer>
<ceb:copCode>46121601BC</ceb:copCode>
<ceb:copName>海南星创互联网医药有限公司</ceb:copName>
<ceb:dxpMode>DXP</ceb:dxpMode>
<ceb:dxpId>DXPENT0000458763</ceb:dxpId>
</ceb:BaseTransfer>
</ceb:CEB621Message>
""";
String dynamicSignDataParameter = signHandler.getSignDataParameter(new SignRequest(xml));
String method = JSONUtil.parseObj(dynamicSignDataParameter).getStr("_method");
String x509Certificate = certificateHandler.getX509Certificate(method);
System.err.println("\t\t\t\t" + x509Certificate + "\n");
dynamicSignDataParameter = signHandler.getSignDataParameter(new SignRequest(xml));
method = JSONUtil.parseObj(dynamicSignDataParameter).getStr("_method");
x509Certificate = certificateHandler.getX509Certificate(method);
System.err.println("\t\t\t\t" + x509Certificate);
}
}
@@ -1,104 +0,0 @@
package cn.alphahub.eport.signature.core;
import cn.alphahub.eport.signature.entity.UkeyResponse;
import cn.hutool.core.lang.TypeReference;
import cn.hutool.json.JSONUtil;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class WebSocketClientHandlerTest {
@Autowired
WebSocketClientHandler handler;
@BeforeEach
void setUp() {
}
@AfterEach
void tearDown() {
}
@Test
@DisplayName("复位读卡器失败发送邮件通知")
void handleTextMessage() {
String ukeyErrJson = """
{
"_id": 1,
"_method": "cus-sec_SpcSignDataAsPEM",
"_status": "00",
"_args": {
"Result": false,
"Data": [],
"Error": [
"[读卡器底层库]复位读卡器失败:错误码\\u003d50070",
"Err:Custom50070"
]
}
}
""";
String messagePayload = """
<ceb:CEB311Message guid="YGD8P9-WEASLEY-20230805155746-HOUDF2" version="1.0" xmlns:ceb="http://www.chinaport.gov.cn/ceb" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" >
<ceb:Order>
<ceb:OrderHead>
<ceb:guid>YGD8P9-WEASLEY-20230805155746-HOUDF2</ceb:guid>
<ceb:appType>1</ceb:appType>
<ceb:appTime>20230704181028</ceb:appTime>
<ceb:appStatus>2</ceb:appStatus>
<ceb:orderType>I</ceb:orderType>
<ceb:orderNo>T_C5051511332138160010</ceb:orderNo>
<ceb:ebpCode>4601630004</ceb:ebpCode>
<ceb:ebpName>海南省荣誉进出口贸易有限公司</ceb:ebpName>
<ceb:ebcCode>4601630004</ceb:ebcCode>
<ceb:ebcName>海南省荣誉进出口贸易有限公司</ceb:ebcName>
<ceb:goodsValue>0.01</ceb:goodsValue>
<ceb:freight>0</ceb:freight>
<ceb:discount>0</ceb:discount>
<ceb:taxTotal>0</ceb:taxTotal>
<ceb:acturalPaid>0.01</ceb:acturalPaid>
<ceb:currency>142</ceb:currency>
<ceb:buyerRegNo>4</ceb:buyerRegNo>
<ceb:buyerName>袁晓雨</ceb:buyerName>
<ceb:buyerTelephone>13701727375</ceb:buyerTelephone>
<ceb:buyerIdType>1</ceb:buyerIdType>
<ceb:buyerIdNumber>130435200009241538</ceb:buyerIdNumber>
<ceb:consignee>袁晓雨</ceb:consignee>
<ceb:consigneeTelephone>13701727375</ceb:consigneeTelephone>
<ceb:consigneeAddress>北京北京市东城区</ceb:consigneeAddress>
<ceb:note>test</ceb:note>
</ceb:OrderHead>
<ceb:OrderList>
<ceb:gnum>1</ceb:gnum>
<ceb:itemNo>1</ceb:itemNo>
<ceb:itemName>LANNA兰纳</ceb:itemName>
<ceb:gmodel>10片/包</ceb:gmodel>
<ceb:itemDescribe></ceb:itemDescribe>
<ceb:barCode>1</ceb:barCode>
<ceb:unit>011</ceb:unit>
<ceb:qty>1</ceb:qty>
<ceb:price>1</ceb:price>
<ceb:totalPrice>1</ceb:totalPrice>
<ceb:currency>142</ceb:currency>
<ceb:country>136</ceb:country>
<ceb:note>test</ceb:note>
</ceb:OrderList>
</ceb:Order>
<ceb:BaseTransfer>
<ceb:copCode>4601630004</ceb:copCode>
<ceb:copName>海南省荣誉进出口贸易有限公司</ceb:copName>
<ceb:dxpMode>DXP</ceb:dxpMode>
<ceb:dxpId>DXPENT0000530815</ceb:dxpId>
<ceb:note>test</ceb:note>
</ceb:BaseTransfer>
</ceb:CEB311Message>
""";
UkeyResponse response = JSONUtil.toBean(ukeyErrJson, new TypeReference<>() {
}, true);
handler.sendAlertSignFailure(response, messagePayload);
}
}
@@ -1,99 +0,0 @@
package cn.alphahub.eport.signature.core.web;
import cn.alphahub.eport.signature.core.SignHandler;
import cn.alphahub.eport.signature.entity.SignRequest;
import cn.alphahub.eport.signature.entity.SignResult;
import cn.alphahub.eport.signature.entity.UkeyRequest;
import cn.hutool.json.JSONUtil;
import io.github.weasleyj.china.eport.sign.model.customs179.Customs179Request;
import io.github.weasleyj.china.eport.sign.util.GUIDUtil;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.LinkedHashMap;
import java.util.Map;
import static cn.alphahub.dtt.plus.util.JacksonUtil.toJson;
import static cn.alphahub.eport.signature.core.CertificateHandler.SING_DATA_METHOD;
@Slf4j
@SpringBootTest
class EportCustoms179HttpClientTest {
@Autowired
SignHandler signHandler;
@Autowired
EportCustoms179HttpClient eportCustoms179HttpClient;
@Test
void report179Data() {
String json = """
{
"sessionID": "fe2374-8fnejf97-32839218",
"payExchangeInfoHead": {
"guid": "fe2374-8fnejf97-32839218",
"initalRequest": "<xml><appid>wxa3412c979c7c6fb7</appid><mch_id>1537923071</mch_id><nonce_str>20220214148382001542606848</nonce_str><sign>38198AD6D22E59B3CCC683D67D8BB402</sign><sign_type>MD5</sign_type><body>测试订单编号411312450000027161</body><attach><![CDATA[16dbeaa5-92d8-47f5-a0a8-813a4d45a5f9]]></attach><out_trade_no>20220214148382001542606848</out_trade_no><total_fee>1</total_fee><spbill_create_ip>10.0.12.208</spbill_create_ip><notify_url>https://boss-dev.fengyouhui.net/be/api/mall-payment-c/api/public/gateway/payment/notice/wx/pay/20220214148382001542606848</notify_url><trade_type>MWEB</trade_type><openid></openid></xml>",
"initalResponse": "<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg><result_code><![CDATA[SUCCESS]]></result_code><mch_id><![CDATA[1537923071]]></mch_id><appid><![CDATA[wxa3412c979c7c6fb7]]></appid><nonce_str><![CDATA[6SgxJ98xsr1nUnRG]]></nonce_str><sign><![CDATA[EB632B9C9F8BCB8027ACE9195878EE4F]]></sign><prepay_id><![CDATA[wx14105706563206ab24a00689936c800000]]></prepay_id><trade_type><![CDATA[MWEB]]></trade_type><mweb_url><![CDATA[https://wx.tenpay.com/cgi-bin/mmpayweb-bin/checkmweb?prepay_id=wx14105706563206ab24a00689936c800000&package=2518465012]]></mweb_url></xml>",
"ebpCode": "请替换",
"payCode": "1537923071",
"payTransactionId": "20220214148382001542606848",
"totalAmount": 0.01,
"currency": "502",
"verDept": "3",
"payType": "1",
"tradingTime": "20220214105714",
"note": "备注"
},
"payExchangeInfoLists": [
{
"orderNo": "202009231454218421271832",
"goodsInfo": [
{
"gname": "济州花梨精华面膜",
"itemLink": "http://m.yunjiweidian.com/yunjibuyer/static/vue-buyer/idc/index.html#/detail?itemId=999761&shopId=453"
}
],
"recpAccount": "请替换",
"recpCode": "请替换",
"recpName": "请替换"
}
],
"serviceTime": "1533282603450"
}
""";
Customs179Request customs179Request = JSONUtil.toBean(json, Customs179Request.class);
String guid = GUIDUtil.getGuid();
customs179Request.setSessionID(guid);
customs179Request.setServiceTime(String.valueOf(System.currentTimeMillis()));
customs179Request.getPayExchangeInfoHead().setGuid(guid);
// 加签
String dataInfo179 = "\"sessionID\":\"" + customs179Request.getSessionID() + "\"||" +
"\"payExchangeInfoHead\":\"" + toJson(customs179Request.getPayExchangeInfoHead()) + "\"||" +
"\"payExchangeInfoLists\":\"" + toJson(customs179Request.getPayExchangeInfoLists()) + "\"||" +
"\"serviceTime\":" + "\"" + customs179Request.getServiceTime() + "\"";
UkeyRequest ukeyRequest = new UkeyRequest(SING_DATA_METHOD, new LinkedHashMap<>() {{
put("inData", dataInfo179);
put("passwd", "88888888");
}});
String signParams = toJson(ukeyRequest);
SignRequest request = new SignRequest(dataInfo179);
SignResult result = signHandler.sign(request, signParams);
customs179Request.setCertNo(result.getCertNo());
customs179Request.setSignValue(result.getSignatureValue());
Map<String, Object> paramMap = new LinkedHashMap<>();
paramMap.put("payExInfoStr", toJson(customs179Request));
log.info("begin reportrequest data is {}", toJson(paramMap));
eportCustoms179HttpClient.report179Data(paramMap).subscribe(capture179DataResponse -> {
Assertions.assertNotNull(capture179DataResponse, "海关 179 数据返回不为空");
System.out.println(JSONUtil.toJsonPrettyStr(capture179DataResponse));
});
}
}
@@ -1,37 +0,0 @@
package cn.alphahub.eport.signature.core.web;
import cn.alphahub.eport.signature.config.ChinaEportProperties;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@Slf4j
@SpringBootTest
class EportReportResultHttpClientTest {
@Autowired
ChinaEportProperties chinaEportProperties;
@Autowired
EportReportResultHttpClient eportReportResultHttpClient;
@Test
@DisplayName("查询 311 申报回执")
void getCeb312msg() {
eportReportResultHttpClient.getCeb312msgResult(chinaEportProperties.getDxpId(), "20230806121200").subscribe(data -> {
log.info("311回执结果:{}", data);
Assertions.assertNotNull(data, "311回执结果不为空");
});
}
@Test
@DisplayName("查询 621 申报回执")
void getCe622msg() {
eportReportResultHttpClient.getCe622msgResult(chinaEportProperties.getDxpId(), "20230806121200").subscribe(data -> {
log.info("621回执结果:{}", data);
Assertions.assertNotNull(data, "621回执结果不为空");
});
}
}
@@ -1,47 +0,0 @@
package cn.alphahub.eport.signature.interview;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.LockSupport;
public class ExchangeOddEven {
final static AtomicInteger atomicInteger = new AtomicInteger(0);
public static void main(String[] args) {
Print print1 = new Print();
Print print2 = new Print();
Thread t1 = new Thread(print1);
Thread t2 = new Thread(print2);
print1.setCurrentThread(t2);
print2.setCurrentThread(t1);
t1.start();
t2.start();
LockSupport.unpark(t1);
}
static class Print implements Runnable {
private volatile Thread currentThread;
public void setCurrentThread(Thread currentThread) {
this.currentThread = currentThread;
}
@Override
public void run() {
for (; ; ) {
LockSupport.park();
if (atomicInteger.get() > 1000) {
LockSupport.unpark(currentThread);
return;
}
System.out.println(Thread.currentThread().getName() + ":" + atomicInteger.getAndIncrement());
LockSupport.unpark(currentThread);
}
}
}
}
@@ -10,9 +10,9 @@
</parent>
<groupId>cn.alphahub.eport.signature</groupId>
<artifactId>chinaport-data-signature-app</artifactId>
<artifactId>chinaport-data-signature-webapp</artifactId>
<version>1.1.1</version>
<name>chinaport-data-signature-app</name>
<name>chinaport-data-signature-webapp</name>
<url>https://github.com/Weasley-J/chinaport-data-signature.git</url>
<description>China E-Port data signature 中国电子口岸报文加签推送应用,2022年2月11日开源此解决方案</description>

Some files were not shown because too many files have changed in this diff Show More