From c90169ab00674a81db7df43559cfa7e7ff54858e Mon Sep 17 00:00:00 2001 From: FCL Date: Wed, 29 Oct 2025 17:27:51 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E6=8A=A5=E5=91=8A=E7=AD=BE=E5=90=8D?= =?UTF-8?q?=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/ConfigUserSignatureService.java | 1 + .../ConfigUserSignatureServiceImpl.java | 7 + .../admin/ReportDocumentMainController.java | 39 ++- .../ReportDocumentMainServiceImpl.java | 4 +- .../qms/DatabusApiInvocationExample.java | 277 ++++++++++++++++++ 5 files changed, 326 insertions(+), 2 deletions(-) create mode 100644 zt-module-qms/zt-module-qms-server/src/test/java/com/zt/plat/module/qms/DatabusApiInvocationExample.java diff --git a/zt-module-qms/zt-module-qms-server/src/main/java/com/zt/plat/module/qms/business/config/service/ConfigUserSignatureService.java b/zt-module-qms/zt-module-qms-server/src/main/java/com/zt/plat/module/qms/business/config/service/ConfigUserSignatureService.java index 1e30349..6fa9b90 100644 --- a/zt-module-qms/zt-module-qms-server/src/main/java/com/zt/plat/module/qms/business/config/service/ConfigUserSignatureService.java +++ b/zt-module-qms/zt-module-qms-server/src/main/java/com/zt/plat/module/qms/business/config/service/ConfigUserSignatureService.java @@ -17,6 +17,7 @@ import com.zt.plat.framework.common.pojo.PageResult; public interface ConfigUserSignatureService { ConfigUserSignatureDO getByUserId(Long userId); + List getByIdList(List ids); /** * 创建手写签名配置 diff --git a/zt-module-qms/zt-module-qms-server/src/main/java/com/zt/plat/module/qms/business/config/service/ConfigUserSignatureServiceImpl.java b/zt-module-qms/zt-module-qms-server/src/main/java/com/zt/plat/module/qms/business/config/service/ConfigUserSignatureServiceImpl.java index 1d162c1..b8eb700 100644 --- a/zt-module-qms/zt-module-qms-server/src/main/java/com/zt/plat/module/qms/business/config/service/ConfigUserSignatureServiceImpl.java +++ b/zt-module-qms/zt-module-qms-server/src/main/java/com/zt/plat/module/qms/business/config/service/ConfigUserSignatureServiceImpl.java @@ -43,6 +43,13 @@ public class ConfigUserSignatureServiceImpl implements ConfigUserSignatureServic return CollUtil.isEmpty(list) ? null : list.get(0); } + @Override + public List getByIdList(List ids) { + LambdaQueryWrapper query = new LambdaQueryWrapper<>(); + query.in(ConfigUserSignatureDO::getId, ids); + return configUserSignatureMapper.selectList(query); + } + @Override public ConfigUserSignatureRespVO createConfigUserSignature(ConfigUserSignatureSaveReqVO createReqVO) { // 插入 diff --git a/zt-module-qms/zt-module-qms-server/src/main/java/com/zt/plat/module/qms/business/reportdoc/controller/admin/ReportDocumentMainController.java b/zt-module-qms/zt-module-qms-server/src/main/java/com/zt/plat/module/qms/business/reportdoc/controller/admin/ReportDocumentMainController.java index 7a6c06c..7c39b56 100644 --- a/zt-module-qms/zt-module-qms-server/src/main/java/com/zt/plat/module/qms/business/reportdoc/controller/admin/ReportDocumentMainController.java +++ b/zt-module-qms/zt-module-qms-server/src/main/java/com/zt/plat/module/qms/business/reportdoc/controller/admin/ReportDocumentMainController.java @@ -11,6 +11,8 @@ import com.zt.plat.framework.common.pojo.PageResult; import com.zt.plat.framework.common.pojo.vo.BatchDeleteReqVO; import com.zt.plat.framework.common.util.object.BeanUtils; import com.zt.plat.framework.excel.core.util.ExcelUtils; +import com.zt.plat.module.qms.business.config.dal.dataobject.ConfigUserSignatureDO; +import com.zt.plat.module.qms.business.config.service.ConfigUserSignatureService; import com.zt.plat.module.qms.business.reportdoc.controller.vo.*; import com.zt.plat.module.qms.business.reportdoc.dal.dataobject.ReportDocumentMainDO; import com.zt.plat.module.qms.business.reportdoc.dal.dataobject.ReportDocumentTypeDO; @@ -24,10 +26,12 @@ import jakarta.annotation.Resource; import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.Valid; import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.util.ObjectUtils; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import java.io.IOException; +import java.util.ArrayList; import java.util.List; import static com.zt.plat.framework.apilog.core.enums.OperateTypeEnum.EXPORT; @@ -51,6 +55,7 @@ public class ReportDocumentMainController extends AbstractFileUploadController i @Resource private ReportDocumentMainService reportDocumentMainService; @Resource private ReportDocumentTypeService reportDocumentTypeService; + @Resource private ConfigUserSignatureService configUserSignatureService; @PostMapping("/create") @Operation(summary = "创建检测报告") @@ -146,7 +151,39 @@ public class ReportDocumentMainController extends AbstractFileUploadController i //@PreAuthorize("@ss.hasPermission('qms:report-document-main:query')") public CommonResult getReportDocumentMain(@RequestParam("id") Long id) { ReportDocumentMainDO reportDocumentMain = reportDocumentMainService.getReportDocumentMain(id); - return success(BeanUtils.toBean(reportDocumentMain, ReportDocumentMainRespVO.class)); + + ReportDocumentMainRespVO vo = BeanUtils.toBean(reportDocumentMain, ReportDocumentMainRespVO.class); + + //处理签名 + String docSig = vo.getDocumentSignature(); + if(!ObjectUtils.isEmpty(docSig)){ + JSONObject docSigJson = JSONObject.parseObject(docSig); + List sigIds = new ArrayList<>(); + docSigJson.forEach((key, value) -> { + JSONObject obj = docSigJson.getJSONObject( key); + String signatureId = obj.getString("signatureId"); + if(!ObjectUtils.isEmpty(signatureId)) + sigIds.add(Long.parseLong(signatureId)); + }); + + if(!sigIds.isEmpty()){ + List sigList = configUserSignatureService.getByIdList(sigIds); + docSigJson.forEach((key, value) -> { + JSONObject obj = docSigJson.getJSONObject( key); + String signatureId = obj.getString("signatureId"); + if(!ObjectUtils.isEmpty(signatureId)){ + ConfigUserSignatureDO sig = sigList.stream().filter(item -> item.getId().equals(Long.parseLong(signatureId))).findFirst().orElse(null); + if(sig != null){ + String base64 = sig.getSignatureContent(); + obj.put("signatureIdBase64", base64); + } + } + }); + } + vo.setDocumentSignature(docSigJson.toJSONString()); + } + + return success(vo); } @GetMapping("/page") diff --git a/zt-module-qms/zt-module-qms-server/src/main/java/com/zt/plat/module/qms/business/reportdoc/service/ReportDocumentMainServiceImpl.java b/zt-module-qms/zt-module-qms-server/src/main/java/com/zt/plat/module/qms/business/reportdoc/service/ReportDocumentMainServiceImpl.java index 9653f38..aa6434e 100644 --- a/zt-module-qms/zt-module-qms-server/src/main/java/com/zt/plat/module/qms/business/reportdoc/service/ReportDocumentMainServiceImpl.java +++ b/zt-module-qms/zt-module-qms-server/src/main/java/com/zt/plat/module/qms/business/reportdoc/service/ReportDocumentMainServiceImpl.java @@ -337,6 +337,7 @@ public class ReportDocumentMainServiceImpl implements ReportDocumentMainService, } private void assembleSignature(String currentActivityId, ReportDocumentMainDO entity){ + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String sign = entity.getDocumentSignature(); JSONObject signObj = new JSONObject(); if(!ObjectUtils.isEmpty( sign)) @@ -350,9 +351,10 @@ public class ReportDocumentMainServiceImpl implements ReportDocumentMainService, String nickName = SecurityFrameworkUtils.getLoginUserNickname(); ConfigUserSignatureDO configUserSignatureDO = configUserSignatureService.getByUserId(userId); if(configUserSignatureDO != null) - obj.put("fileId", configUserSignatureDO.getFileId()); + obj.put("signatureId", configUserSignatureDO.getId()); obj.put("userId", userId); obj.put("userName", nickName); + obj.put("signTime", sdf.format(new Date())); signObj.put(currentActivityId, obj); entity.setDocumentSignature(signObj.toJSONString()); } diff --git a/zt-module-qms/zt-module-qms-server/src/test/java/com/zt/plat/module/qms/DatabusApiInvocationExample.java b/zt-module-qms/zt-module-qms-server/src/test/java/com/zt/plat/module/qms/DatabusApiInvocationExample.java new file mode 100644 index 0000000..c0d0c1e --- /dev/null +++ b/zt-module-qms/zt-module-qms-server/src/test/java/com/zt/plat/module/qms/DatabusApiInvocationExample.java @@ -0,0 +1,277 @@ +package com.zt.plat.module.qms; + +import com.alibaba.fastjson.JSONObject; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.zt.plat.framework.common.util.security.CryptoSignatureUtils; + +import java.io.IOException; +import java.io.PrintStream; +import java.net.URI; +import java.net.URLEncoder; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.time.Duration; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.TreeMap; +import java.util.UUID; + +public class DatabusApiInvocationExample { + public static final String TIMESTAMP = Long.toString(System.currentTimeMillis()); + private static final String APP_ID = "iwork"; + private static final String APP_SECRET = "lpGXiNe/GMLk0vsbYGLa8eYxXq8tGhTbuu3/D4MJzIk="; + // private static final String APP_ID = "test"; +// private static final String APP_SECRET = "RSYtKXrXPLMy3oeh0cOro6QCioRUgqfnKCkDkNq78sI="; +// private static final String APP_ID = "testAnnoy"; +// private static final String APP_SECRET = "jyGCymUjCFL2i3a4Tm3qBIkUrUl4ZgKPYvOU/47ZWcM="; + private static final String ENCRYPTION_TYPE = CryptoSignatureUtils.ENCRYPT_TYPE_AES; + private static final String TARGET_API = "http://172.16.46.63:30081/admin-api/databus/api/portal/qms.ytjc.iwork/v1"; + // private static final String TARGET_API = "http://127.0.0.1:48080/admin-api/databus/api/portal/lgstOpenApi/v1"; + private static final HttpClient HTTP_CLIENT = HttpClient.newBuilder() + .connectTimeout(Duration.ofSeconds(5)) + .build(); + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final PrintStream OUT = buildConsolePrintStream(); + public static final String ZT_APP_ID = "ZT-App-Id"; + public static final String ZT_TIMESTAMP = "ZT-Timestamp"; + public static final String ZT_NONCE = "ZT-Nonce"; + public static final String ZT_SIGNATURE = "ZT-Signature"; + public static final String ZT_AUTH_TOKEN = "ZT-Auth-Token"; + public static final String CONTENT_TYPE = "Content-Type"; + + private DatabusApiInvocationExample() { + } + + public static void main(String[] args) throws Exception { + OUT.println("=== GET 请求示例 ==="); +// executeGetExample(); +// OUT.println(); +// OUT.println("=== POST 请求示例 ==="); + executePostExample(); + } + + private static void executeGetExample() throws Exception { + Map queryParams = new LinkedHashMap<>(); + queryParams.put("businessCode", "11"); + queryParams.put("fileId", "11"); + queryParams.put("null", null); + String signature = generateSignature(queryParams, Map.of()); + URI requestUri = buildUri(TARGET_API, queryParams); + String nonce = "171615676c7d4d96b9f55f3d90ad27e0"; + + HttpRequest request = HttpRequest.newBuilder(requestUri) + .timeout(Duration.ofSeconds(10)) + .header(ZT_APP_ID, APP_ID) + .header(ZT_TIMESTAMP, TIMESTAMP) + .header(ZT_NONCE, nonce) + .header(ZT_SIGNATURE, signature) +// .header("ZT-Auth-Token", "a75c0ea94c7f4a88b86b60bbc0b432c3") + .GET() + .build(); + + HttpResponse response = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)); + printResponse(response); + } + + private static void executePostExample() throws Exception { + Map queryParams = new LinkedHashMap<>(); + + JSONObject jsonObject = new JSONObject(); + + jsonObject.put("id", "1968843568287817730"); + jsonObject.put("sampleName", "545(委检样)"); + jsonObject.put("materialName", "ff"); + jsonObject.put("materialId", "123"); + jsonObject.put("dictionaryBusinessId", "1965289399255388162"); + + long extraTimestamp = 1761556157185L; + String bodyJson = String.format(jsonObject.toJSONString(), extraTimestamp); + + Map bodyParams = parseBodyJson(bodyJson); + String signature = generateSignature(queryParams, bodyParams); + URI requestUri = buildUri(TARGET_API, queryParams); + String nonce = randomNonce(); + String cipherBody = encryptPayload(bodyJson); + OUT.println("原始 Request Body: " + bodyJson); + OUT.println("加密 Request Body: " + cipherBody); + + HttpRequest request = HttpRequest.newBuilder(requestUri) + .timeout(Duration.ofSeconds(10)) + .header(ZT_APP_ID, APP_ID) + .header(ZT_TIMESTAMP, TIMESTAMP) + .header(ZT_NONCE, nonce) + .header(ZT_SIGNATURE, signature) + .header(ZT_AUTH_TOKEN, "843304444f65423f901a3efe2f252a75") + .header(CONTENT_TYPE, "application/json") + .header("visit-company-id", "101") + .header("visit-dept-id", "103") + .POST(HttpRequest.BodyPublishers.ofString(cipherBody, StandardCharsets.UTF_8)) + .build(); + + HttpResponse response = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)); + printResponse(response); + } + + private static String encryptPayload(String plaintext) { + try { + return CryptoSignatureUtils.encrypt(plaintext, APP_SECRET, ENCRYPTION_TYPE); + } catch (Exception ex) { + throw new IllegalStateException("Failed to encrypt request body", ex); + } + } + + private static void printResponse(HttpResponse response) { + OUT.println("HTTP Status: " + response.statusCode()); + String cipherText = response.body(); + OUT.println("加密 Response: " + cipherText); + String plain = tryDecrypt(cipherText); + OUT.println("原始 Response: " + normalizePotentialMojibake(plain)); + } + + private static String randomNonce() { + return UUID.randomUUID().toString().replace("-", ""); + } + + private static URI buildUri(String baseUrl, Map queryParams) { + if (queryParams == null || queryParams.isEmpty()) { + return URI.create(baseUrl); + } + StringBuilder builder = new StringBuilder(baseUrl); + builder.append(baseUrl.contains("?") ? '&' : '?'); + boolean first = true; + for (Map.Entry entry : queryParams.entrySet()) { + if (!first) { + builder.append('&'); + } + first = false; + builder.append(URLEncoder.encode(entry.getKey(), StandardCharsets.UTF_8)); + builder.append('='); + builder.append(URLEncoder.encode(String.valueOf(entry.getValue()), StandardCharsets.UTF_8)); + } + return URI.create(builder.toString()); + } + + private static String generateSignature(Map queryParams, Map bodyParams) { + TreeMap sorted = new TreeMap<>(); + if (queryParams != null) { + queryParams.forEach((key, value) -> sorted.put(key, normalizeValue(value))); + } + if (bodyParams != null) { + bodyParams.forEach((key, value) -> sorted.put(key, normalizeValue(value))); + } + sorted.put(ZT_APP_ID, APP_ID); + sorted.put(ZT_TIMESTAMP, TIMESTAMP); + StringBuilder canonical = new StringBuilder(); + sorted.forEach((key, value) -> { + if (value == null) { + return; + } + if (canonical.length() > 0) { + canonical.append('&'); + } + canonical.append(key).append('=').append(value); + }); + OUT.println("原始 签名串: " + canonical); + String md5Hex = md5Hex(canonical.toString()); + OUT.println("原始签名: " + md5Hex); + return md5Hex; + } + + private static Object normalizeValue(Object value) { + if (value == null) { + return null; + } + if (value instanceof Map || value instanceof Iterable) { + try { + return OBJECT_MAPPER.writeValueAsString(value); + } catch (JsonProcessingException ignored) { + return value.toString(); + } + } + return value; + } + + private static Map parseBodyJson(String bodyJson) { + if (bodyJson == null || bodyJson.isBlank()) { + return Map.of(); + } + try { + return OBJECT_MAPPER.readValue(bodyJson, new TypeReference>() { }); + } catch (IOException ex) { + throw new IllegalArgumentException("Failed to parse request body JSON", ex); + } + } + + private static String md5Hex(String input) { + try { + MessageDigest digest = MessageDigest.getInstance("MD5"); + byte[] bytes = digest.digest(input.getBytes(StandardCharsets.UTF_8)); + StringBuilder hex = new StringBuilder(bytes.length * 2); + for (byte b : bytes) { + String segment = Integer.toHexString(b & 0xFF); + if (segment.length() == 1) { + hex.append('0'); + } + hex.append(segment); + } + return hex.toString(); + } catch (NoSuchAlgorithmException ex) { + throw new IllegalStateException("MD5 algorithm not available", ex); + } + } + + private static String tryDecrypt(String cipherText) { + if (cipherText == null || cipherText.isBlank()) { + return cipherText; + } + try { + // Databus 会在凭证开启加密时返回密文,这里做一次解密展示真实响应。 + return CryptoSignatureUtils.decrypt(cipherText, APP_SECRET, ENCRYPTION_TYPE); + } catch (Exception ex) { + return " " + ex.getMessage(); + } + } + + // 解决控制台打印 乱码问题 + private static String normalizePotentialMojibake(String value) { + if (value == null || value.isEmpty()) { + return value; + } + long suspectCount = value.chars().filter(ch -> ch >= 0x80 && ch <= 0xFF).count(); + long highCount = value.chars().filter(ch -> ch > 0xFF).count(); + if (suspectCount > 0 && highCount == 0) { + try { + byte[] decoded = value.getBytes(StandardCharsets.ISO_8859_1); + String converted = new String(decoded, StandardCharsets.UTF_8); + if (converted.chars().anyMatch(ch -> ch > 0xFF)) { + return converted; + } + } catch (Exception ignored) { + return value; + } + } + return value; + } + + /** + * 输出流编码与当前控制台保持一致,避免中文字符再次出现编码差异。 + */ + private static PrintStream buildConsolePrintStream() { + try { + String consoleEncoding = System.getProperty("sun.stdout.encoding"); + if (consoleEncoding != null && !consoleEncoding.isBlank()) { + return new PrintStream(System.out, true, Charset.forName(consoleEncoding)); + } + return new PrintStream(System.out, true, Charset.defaultCharset()); + } catch (Exception ignored) { + return System.out; + } + } +}