[feature] 完善“电子口岸 u-key 发生错误,需要重启“客户端控件”的错误类型,增加‘加签失败邮件通知告警’效果

This commit is contained in:
Weasley J
2023-08-10 14:09:26 +08:00
parent 94eadfbd30
commit fab95d6ee3
6 changed files with 191 additions and 30 deletions
+11
View File
@@ -77,6 +77,17 @@
![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`
<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)理论上支持中国内地的大部分**地方电子口岸**
@@ -13,6 +13,7 @@ import cn.alphahub.multiple.email.EmailTemplate.SimpleMailMessageDomain;
import cn.alphahub.multiple.email.annotation.Email;
import cn.hutool.core.lang.TypeReference;
import cn.hutool.json.JSONUtil;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
@@ -37,23 +38,21 @@ import java.util.concurrent.locks.LockSupport;
* @date 2022/2/15
*/
@Slf4j
@Getter
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class WebSocketClientHandler extends TextWebSocketHandler {
/**
* 电子口岸u-key的配置参数
*/
@Getter
private final UkeyProperties ukeyProperties;
/**
* WebSocket包装类
*/
@Getter
private final WebSocketWrapper webSocketWrapper;
/**
* X509Certificate证书判断
*/
@Getter
private final CertificateHandler certificateHandler;
@Autowired
private EmailTemplate emailTemplate;
@@ -79,21 +78,21 @@ public class WebSocketClientHandler extends TextWebSocketHandler {
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
super.handleTextMessage(session, message);
log.warn("收到ukey响应数据: {}", message.getPayload());
UkeyResponse response = JSONUtil.toBean(message.getPayload(), new TypeReference<>() {
UkeyResponse ukeyResponse = JSONUtil.toBean(message.getPayload(), new TypeReference<>() {
}, true);
if (Objects.equals(response.get_id(), webSocketWrapper.getRequest().getId())) {
if (Objects.equals(ukeyResponse.get_id(), webSocketWrapper.getRequest().getId())) {
try {
UkeyResponse.Args responseArgs = response.get_args();
UkeyResponse.Args responseArgs = ukeyResponse.get_args();
if (responseArgs.getResult().equals(true) && CollectionUtils.isNotEmpty(responseArgs.getData())) {
log.warn("电子口岸u-key加签数据成功:{}", JacksonUtil.toJson(responseArgs));
webSocketWrapper.getSignResult().setSuccess(true);
webSocketWrapper.getSignResult().setSignatureValue(responseArgs.getData().get(0));
webSocketWrapper.getSignResult().setCertNo(responseArgs.getData().get(1));
if (SignHandler.isSignXml(webSocketWrapper.getRequest())) {
webSocketWrapper.getSignResult().setX509Certificate(certificateHandler.getX509Certificate(response.get_method()));
webSocketWrapper.getSignResult().setX509Certificate(certificateHandler.getX509Certificate(ukeyResponse.get_method()));
}
} else {
sendAlertSignFailure(JacksonUtil.toPrettyJson(response));
sendAlertSignFailure(ukeyResponse, message.getPayload());
}
} catch (Exception e) {
webSocketWrapper.getSignResult().setSuccess(false);
@@ -115,31 +114,61 @@ public class WebSocketClientHandler extends TextWebSocketHandler {
* "Result": false,
* "Data": [],
* "Error": [
* "[读卡器底层库]复位读卡器失败:错误码\u003d50070",
* "[读卡器底层库]复位读卡器失败:错误码50070",
* "Err:Custom50070"
* ]
* }
* }
* </pre>
* <p>
* <figure>
* <img src="https://weasley.oss-cn-shanghai.aliyuncs.com/Photos/iShot_2023-08-10_13.56.34.png" alt="邮件通知效果">
* <figcaption>加签失败邮件通知</figcaption>
* </figure>
* </p>
*
* @param errorMessage error message
* @param ukeyResponse error response
* @param messagePayload 消息负载
* @implNote [读卡器底层库]复位读卡器失败会自动重启u-key的Windows进程,希望能提升自我容灾机制
* @since 2023-06-10
*/
@Email
public void sendAlertSignFailure(String errorMessage) {
public void sendAlertSignFailure(UkeyResponse ukeyResponse, String messagePayload) {
String errorMessage = JacksonUtil.toPrettyJson(ukeyResponse);
String subject = "电子口岸 u-key 加签失败提醒";
String payload = StringUtils.defaultIfBlank(messagePayload, "");
if (emailProperties.getEnable().equals(true)) {
log.warn("电子口岸u-key加签数据失败:{}", errorMessage);
SimpleMailMessageDomain message = new SimpleMailMessageDomain();
message.setTo(emailProperties.getTo());
message.setCc(StringUtils.split(emailProperties.getCc(), ","));
message.setSentDate(LocalDateTime.now());
message.setSubject("电子口岸u-key加签失败");
if (errorMessage.contains("[读卡器底层库]复位读卡器失败")) {
message.setText("电子口岸u-key加签失败,原因:\n" + errorMessage + "\n\n如遇:“[读卡器底层库]复位读卡器失败”等错误,请手动重启加签exe客户端程序。");
restartWindowsWebsocketClient(message);
message.setSubject(subject);
message.setText("");
boolean shouldBreak = false;
boolean existsError = false;
if (CollectionUtils.isNotEmpty(ukeyResponse.get_args().getError())) {
for (String errorText : ukeyResponse.get_args().getError()) {
for (UkeyError ukeyError : UkeyError.values()) {
if (StringUtils.containsIgnoreCase(errorText, ukeyError.getErr())) {
restartUkeyWindowsWebsocketClient(message);
shouldBreak = true;
existsError = true;
break;
}
}
if (shouldBreak) {
break;
}
}
}
StringBuilder text = new StringBuilder()
.append("原始错误:\n").append(errorMessage).append("\n")
.append("数据载荷:\n").append(payload).append("\n");
if (existsError) {
message.setText(text.append("提示,如遇 “[读卡器底层库]复位读卡器失败” 等错误,程序自动重启客户端后如果还是不能加签,请手动重启加签exe客户端程序。").toString());
} else {
message.setText("电子口岸u-key加签失败,原因:\n" + errorMessage);
message.setText(text.toString());
}
emailTemplate.send(message);
}
@@ -150,12 +179,35 @@ public class WebSocketClientHandler extends TextWebSocketHandler {
*
* @since 1.0.9
*/
public void restartWindowsWebsocketClient(SimpleMailMessageDomain message) {
public void restartUkeyWindowsWebsocketClient(SimpleMailMessageDomain message) {
ConsoleOutput output = ukeyHealthHelper.fixUkey(Command.RESTART);
String restartLog = message.getText()
.concat("\n\n加签程序【chinaport-data-signature】重启Windows Websocket客户端,cmd终端信息:\n")
.concat(JacksonUtil.toJson(output));
.concat(StringUtils.defaultIfBlank(JacksonUtil.toJson(output), ""));
message.setText(restartLog);
}
/**
* 电子口岸 u-key 发生错误,需要重启“客户端控件”的情况
*/
@Getter
@AllArgsConstructor
private enum UkeyError {
RESETTING_CARD_READER_FAILED(50070, "[读卡器底层库]复位读卡器失败:错误码=50070", "Custom50070"),
OPEN_THE_CARD_READER_FAILED(50200, "[读卡器底层库]打开读卡器失败:错误码=50200", "Custom50200"),
;
/**
* 错误码
*/
private final int code;
/**
* 描述
*/
private final String desc;
/**
* Err
*/
private final String err;
}
}
@@ -7,7 +7,8 @@ import java.util.Objects;
/**
* u-key加签返回结果包装类
* <p>以下对象模型禁止修改</p>
* <p>
* 以下对象模型禁止修改
*
* @author Weasley
* @date 2022-02-12 19:7:6
@@ -146,4 +147,4 @@ public class UkeyResponse implements Serializable {
return Objects.hash(getResult(), getData(), getError());
}
}
}
}
@@ -11,22 +11,25 @@ import jakarta.servlet.http.HttpServletRequest;
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 (ipAddress == null || ipAddress.length() == 0 || UNKNOWN.equalsIgnoreCase(ipAddress)) {
if (null == ipAddress || ipAddress.isEmpty() || UNKNOWN.equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("Proxy-Client-IP");
}
if (ipAddress == null || ipAddress.length() == 0 || UNKNOWN.equalsIgnoreCase(ipAddress)) {
if (null == ipAddress || ipAddress.isEmpty() || UNKNOWN.equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("WL-Proxy-Client-IP");
}
if (ipAddress == null || ipAddress.length() == 0 || UNKNOWN.equalsIgnoreCase(ipAddress)) {
if (null == ipAddress || ipAddress.isEmpty() || UNKNOWN.equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("HTTP_CLIENT_IP");
}
if (ipAddress == null || ipAddress.length() == 0 || UNKNOWN.equalsIgnoreCase(ipAddress)) {
if (null == ipAddress || ipAddress.isEmpty() || UNKNOWN.equalsIgnoreCase(ipAddress)) {
ipAddress = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (ipAddress == null || ipAddress.length() == 0 || UNKNOWN.equalsIgnoreCase(ipAddress)) {
if (null == ipAddress || ipAddress.isEmpty() || UNKNOWN.equalsIgnoreCase(ipAddress)) {
ipAddress = request.getRemoteAddr();
}
@@ -5,6 +5,7 @@ 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;
@@ -24,8 +25,9 @@ class WebSocketClientHandlerTest {
}
@Test
@DisplayName("复位读卡器失败发送邮件通知")
void handleTextMessage() {
String json = """
String ukeyErrJson = """
{
"_id": 1,
"_method": "cus-sec_SpcSignDataAsPEM",
@@ -40,9 +42,63 @@ class WebSocketClientHandlerTest {
}
}
""";
UkeyResponse response = JSONUtil.toBean(json,
new TypeReference<>() {
}, true);
handler.sendAlertSignFailure(JSONUtil.toJsonPrettyStr(response));
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);
}
}
@@ -0,0 +1,38 @@
package cn.alphahub.eport.signature.util;
import org.apache.commons.lang3.StringUtils;
/**
* 计算两个词的相似度
*/
@SuppressWarnings("deprecation")
public class StringSimilarityExample {
public static void main(String[] args) {
String str1 = "hello";
String str2 = "holla";
String str3 = "hello";
int distance = StringUtils.getLevenshteinDistance(str1, str2);
double similarity = 1.0 - (double) distance / Math.max(str1.length(), str2.length());
System.out.println("1 Levenshtein distance: " + distance);
System.out.println("1 Similarity: " + similarity);
int distance2 = StringUtils.getLevenshteinDistance(str1, str3);
double similarity2 = 1.0 - (double) distance / Math.max(str1.length(), str3.length());
System.out.println("2 Levenshtein distance: " + distance2);
System.out.println("2 Similarity: " + similarity2);
str1 = "你好啊, 还啊哈";
str3 = "你好啊!我在春天等你。";
distance2 = StringUtils.getLevenshteinDistance(str1, str3);
similarity2 = 1.0 - (double) distance / Math.max(str1.length(), str3.length());
System.out.println("3 Levenshtein distance: " + distance2);
System.out.println("3 Similarity: " + similarity2);
}
}