From b78065223b3d7af2801c67d0fd90be6b3c7f3c4c Mon Sep 17 00:00:00 2001 From: qitz <18810291185@163.com> Date: Thu, 12 Oct 2023 10:04:57 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E7=94=A8=E5=8D=B0=E7=AD=BE?= =?UTF-8?q?=E5=90=8D=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ruoyi-common/pom.xml | 14 +- .../domain/SealSignRecord.java | 161 ++++++ .../utils/EsignApplicaConfig.java | 13 + .../utils/EsignCoreSdkInfo.java | 24 + .../utils/EsignEncryption.java | 368 ++++++++++++++ .../wisdomarbitrate/utils/EsignFileBean.java | 53 ++ .../utils/EsignHeaderConstant.java | 26 + .../utils/EsignHttpCfgHelper.java | 479 ++++++++++++++++++ .../utils/EsignHttpHelper.java | 168 ++++++ .../utils/EsignHttpResponse.java | 27 + .../utils/EsignInterfacException.java | 34 ++ .../utils/EsignRequestType.java | 39 ++ .../utils/FileTransformation.java | 280 ++++++++++ .../wisdomarbitrate/utils/SignAward.java | 336 ++++++++++++ 14 files changed, 2021 insertions(+), 1 deletion(-) create mode 100644 ruoyi-system/src/main/java/com/ruoyi/wisdomarbitrate/domain/SealSignRecord.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/wisdomarbitrate/utils/EsignApplicaConfig.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/wisdomarbitrate/utils/EsignCoreSdkInfo.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/wisdomarbitrate/utils/EsignEncryption.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/wisdomarbitrate/utils/EsignFileBean.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/wisdomarbitrate/utils/EsignHeaderConstant.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/wisdomarbitrate/utils/EsignHttpCfgHelper.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/wisdomarbitrate/utils/EsignHttpHelper.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/wisdomarbitrate/utils/EsignHttpResponse.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/wisdomarbitrate/utils/EsignInterfacException.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/wisdomarbitrate/utils/EsignRequestType.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/wisdomarbitrate/utils/FileTransformation.java create mode 100644 ruoyi-system/src/main/java/com/ruoyi/wisdomarbitrate/utils/SignAward.java diff --git a/ruoyi-common/pom.xml b/ruoyi-common/pom.xml index 3b55c2b..52dfb1d 100644 --- a/ruoyi-common/pom.xml +++ b/ruoyi-common/pom.xml @@ -138,12 +138,24 @@ tencentcloud-sdk-java 3.1.270 - + + + com.tencentcloudapi + tencentcloud-sdk-java-faceid + LATEST + + + com.tencentcloudapi + tencentcloud-sdk-java-common + LATEST + + cn.hutool diff --git a/ruoyi-system/src/main/java/com/ruoyi/wisdomarbitrate/domain/SealSignRecord.java b/ruoyi-system/src/main/java/com/ruoyi/wisdomarbitrate/domain/SealSignRecord.java new file mode 100644 index 0000000..d7ae6fd --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/wisdomarbitrate/domain/SealSignRecord.java @@ -0,0 +1,161 @@ +package com.ruoyi.wisdomarbitrate.domain; + +import com.ruoyi.common.core.domain.BaseEntity; + +public class SealSignRecord extends BaseEntity { + private static final long serialVersionUID = 1L; + + /** ID */ + private Long id; + /** 文件id */ + private String fileid; + /** 文件名称 */ + private String filename; + /** 流程id */ + private String signFlowid; + /** 签名人账户 */ + private String pensonAccount; + /** 签名人姓名 */ + private String pensonName; + /** 机构名称 */ + private String orgnizeName; + /** 机构经办人 */ + private String orgnizeNamePsnAccount; + /** 机构经办人名称 */ + private String orgnizeNamepsnName; + + public String getFileid() { + return fileid; + } + + public void setFileid(String fileid) { + this.fileid = fileid; + } + + public String getFilename() { + return filename; + } + + public void setFilename(String filename) { + this.filename = filename; + } + + public String getSignFlowid() { + return signFlowid; + } + + public void setSignFlowid(String signFlowid) { + this.signFlowid = signFlowid; + } + + public String getPensonAccount() { + return pensonAccount; + } + + public void setPensonAccount(String pensonAccount) { + this.pensonAccount = pensonAccount; + } + + public String getPensonName() { + return pensonName; + } + + public void setPensonName(String pensonName) { + this.pensonName = pensonName; + } + + public String getOrgnizeName() { + return orgnizeName; + } + + public void setOrgnizeName(String orgnizeName) { + this.orgnizeName = orgnizeName; + } + + public String getOrgnizeNamePsnAccount() { + return orgnizeNamePsnAccount; + } + + public void setOrgnizeNamePsnAccount(String orgnizeNamePsnAccount) { + this.orgnizeNamePsnAccount = orgnizeNamePsnAccount; + } + + public String getOrgnizeNamepsnName() { + return orgnizeNamepsnName; + } + + public void setOrgnizeNamepsnName(String orgnizeNamepsnName) { + this.orgnizeNamepsnName = orgnizeNamepsnName; + } + + public String getPositionPagepsn() { + return positionPagepsn; + } + + public void setPositionPagepsn(String positionPagepsn) { + this.positionPagepsn = positionPagepsn; + } + + public double getPositionXpsn() { + return positionXpsn; + } + + public void setPositionXpsn(double positionXpsn) { + this.positionXpsn = positionXpsn; + } + + public double getPositionYpsn() { + return positionYpsn; + } + + public void setPositionYpsn(double positionYpsn) { + this.positionYpsn = positionYpsn; + } + + public String getPositionPageorg() { + return positionPageorg; + } + + public void setPositionPageorg(String positionPageorg) { + this.positionPageorg = positionPageorg; + } + + public double getPositionXorg() { + return positionXorg; + } + + public void setPositionXorg(double positionXorg) { + this.positionXorg = positionXorg; + } + + public double getPositionYorg() { + return positionYorg; + } + + public void setPositionYorg(double positionYorg) { + this.positionYorg = positionYorg; + } + + /** 签名位置页数 */ + private String positionPagepsn; + /** 签名位置x坐标 */ + private double positionXpsn; + /** 签名位置y坐标 */ + private double positionYpsn; + /** 印章位置页数 */ + private String positionPageorg; + /** 印章位置x坐标 */ + private double positionXorg; + /** 印章位置y坐标 */ + private double positionYorg; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/wisdomarbitrate/utils/EsignApplicaConfig.java b/ruoyi-system/src/main/java/com/ruoyi/wisdomarbitrate/utils/EsignApplicaConfig.java new file mode 100644 index 0000000..4a1fdd1 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/wisdomarbitrate/utils/EsignApplicaConfig.java @@ -0,0 +1,13 @@ +package com.ruoyi.wisdomarbitrate.utils; + +public class EsignApplicaConfig { + + // 应用ID + public static final String EsignAppId = "7438987614"; + // 应用密钥 + public static final String EsignAppSecret = "9d7844f13830931037772b9d20cf1529"; + // e签宝接口调用域名(模拟环境) + public static final String EsignHost = "https://smlopenapi.esign.cn"; + // e签宝接口调用域名(正式环境) + // public static final String EsignHost = "https://openapi.esign.cn"; +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/wisdomarbitrate/utils/EsignCoreSdkInfo.java b/ruoyi-system/src/main/java/com/ruoyi/wisdomarbitrate/utils/EsignCoreSdkInfo.java new file mode 100644 index 0000000..113465e --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/wisdomarbitrate/utils/EsignCoreSdkInfo.java @@ -0,0 +1,24 @@ +package com.ruoyi.wisdomarbitrate.utils; +/** + * esignSDK-core信息类 + * @author 澄泓 + * @date 2022/2/22 13:59 + * @version + */ +public class EsignCoreSdkInfo { + private static final String SdkVersion="Esign-Sdk-Core1.0"; + private static final String SupportedVersion="JDK1.7 MORE THAN"; + + private static final String Info="sdk-esign-api核心工具包,主要处理e签宝公有云产品接口调用时的签名计算以及网络请求,通过EsignHttpHelper.signAndBuildSignAndJsonHeader构造签名鉴权+json数据格式的请求头,通过HttpHelper.doCommHttp方法入参发起网络请求。让开发者无需关注具体的请求签名算法,专注于接口业务的json参数构造"; + public static String getSdkVersion() { + return SdkVersion; + } + + public static String getInfo() { + return Info; + } + + public static String getSupportedVersion() { + return SupportedVersion; + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/wisdomarbitrate/utils/EsignEncryption.java b/ruoyi-system/src/main/java/com/ruoyi/wisdomarbitrate/utils/EsignEncryption.java new file mode 100644 index 0000000..88cf70b --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/wisdomarbitrate/utils/EsignEncryption.java @@ -0,0 +1,368 @@ +/* + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package com.ruoyi.wisdomarbitrate.utils; + +import org.apache.commons.codec.binary.Base64; +import org.apache.http.message.BasicNameValuePair; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import java.io.UnsupportedEncodingException; +import java.security.InvalidKeyException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.text.Collator; +import java.text.MessageFormat; +import java.util.*; + +/** + * @description 请求数据通用处理类 + */ +public class EsignEncryption { + + /** + * 不允许外部创建实例 + */ + private EsignEncryption(){} + + /** + * 拼接待签名字符串 + * @param httpMethod + * @param url + * @return + */ + public static String appendSignDataString(String httpMethod, String contentMd5,String accept,String contentType,String headers,String date, String url) throws EsignInterfacException { + StringBuffer sb = new StringBuffer(); + sb.append(httpMethod).append("\n").append(accept).append("\n").append(contentMd5).append("\n") + .append(contentType).append("\n"); + + if ("".equals(date) || date == null) { + sb.append("\n"); + } else { + sb.append(date).append("\n"); + } + if ("".equals(headers) || headers == null) { + sb.append(url); + } else { + sb.append(headers).append("\n").append(url); + } + return new String(sb); + } + + /*** + * Content-MD5的计算方法 + * @param str 待计算的消息 + * @return MD5计算后摘要值的Base64编码(ContentMD5) + * @throws EsignInterfacException 加密过程中的异常信息 + */ + public static String doContentMD5(String str) throws EsignInterfacException { + byte[] md5Bytes = null; + MessageDigest md5 = null; + String contentMD5 = null; + try { + md5 = MessageDigest.getInstance("MD5"); + // 计算md5函数 + md5.update(str.getBytes("UTF-8")); + // 获取文件MD5的二进制数组(128位) + md5Bytes = md5.digest(); + // 把MD5摘要后的二进制数组md5Bytes使用Base64进行编码(而不是对32位的16进制字符串进行编码) + contentMD5 = Base64.encodeBase64String(md5Bytes); + + } catch (NoSuchAlgorithmException e) { + EsignInterfacException ex = new EsignInterfacException("不支持此算法",e); + ex.initCause(e); + throw ex; + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + return contentMD5; + } + + /*** + * 计算请求签名值-HmacSHA256摘要 + * @param message 待签名字符串 + * @param secret 密钥APP KEY + * @return reqSignature HmacSHA256计算后摘要值的Base64编码 + * @throws EsignInterfacException 加密过程中的异常信息 + */ + public static String doSignatureBase64(String message, String secret) throws EsignInterfacException { + String algorithm = "HmacSHA256"; + Mac hmacSha256; + String digestBase64 = null; + try { + hmacSha256 = Mac.getInstance(algorithm); + byte[] keyBytes = secret.getBytes("UTF-8"); + byte[] messageBytes = message.getBytes("UTF-8"); + hmacSha256.init(new SecretKeySpec(keyBytes, 0, keyBytes.length, algorithm)); + // 使用HmacSHA256对二进制数据消息Bytes计算摘要 + byte[] digestBytes = hmacSha256.doFinal(messageBytes); + // 把摘要后的结果digestBytes使用Base64进行编码 + digestBase64 = Base64.encodeBase64String(digestBytes); + } catch (NoSuchAlgorithmException e) { + EsignInterfacException ex = new EsignInterfacException("不支持此算法",e); + ex.initCause(e); + throw ex; + } catch (InvalidKeyException e) { + EsignInterfacException ex = new EsignInterfacException("无效的密钥规范",e); + ex.initCause(e); + throw ex; + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + return digestBase64; + } + + /** + * 获取时间戳 + * @return + */ + public static String timeStamp() { + long timeStamp = System.currentTimeMillis(); + return String.valueOf(timeStamp); + } + + /** + * byte字节数组转换成字符串 + * @param b + * @return + */ + public static String byteArrayToHexString(byte[] b) { + StringBuilder hs = new StringBuilder(); + String stmp; + for (int n = 0; b != null && n < b.length; n++) { + stmp = Integer.toHexString(b[n] & 0XFF); + if (stmp.length() == 1) + hs.append('0'); + hs.append(stmp); + } + return hs.toString().toLowerCase(); + } + + /** + * hash散列加密算法 + * @return + */ + public static String Hmac_SHA256(String message,String key) throws EsignInterfacException { + byte[] rawHmac=null; + try { + SecretKeySpec sk = new SecretKeySpec(key.getBytes(), "HmacSHA256"); + Mac mac = Mac.getInstance("HmacSHA256"); + mac.init(sk); + rawHmac = mac.doFinal(message.getBytes()); + }catch (InvalidKeyException e){ + EsignInterfacException ex = new EsignInterfacException("无效的密钥规范",e); + ex.initCause(e); + throw ex; + } catch (NoSuchAlgorithmException e) { + EsignInterfacException ex = new EsignInterfacException("不支持此算法",e); + ex.initCause(e); + throw ex; + }catch (Exception e){ + EsignInterfacException ex = new EsignInterfacException("hash散列加密算法报错",e); + ex.initCause(e); + throw ex; + }finally { + return byteArrayToHexString(rawHmac); + } + + } + + /** + * MD5加密32位 + */ + public static String MD5Digest(String text) throws EsignInterfacException { + byte[] digest=null; + try { + MessageDigest md5 = MessageDigest.getInstance("MD5"); + md5.update(text.getBytes()); + digest = md5.digest(); + }catch (NoSuchAlgorithmException e){ + EsignInterfacException ex = new EsignInterfacException("不支持此算法",e); + ex.initCause(e); + throw ex; + }finally { + return byteArrayToHexString(digest); + } + + } + + public static void formDataSort(List param) { + Collections.sort(param, new Comparator() { + @Override + public int compare(BasicNameValuePair o1, BasicNameValuePair o2) { + Comparator com = Collator.getInstance(Locale.CHINA); + return com.compare(o1.getName(), o2.getName()); + } + }); + } + + /*** + * 字符串是否为空(含空格校验) + * @param str + * @return + */ + public static boolean isBlank(String str) { + if (null == str || 0 == str.length()) { + return true; + } + + int strLen = str.length(); + + for (int i = 0; i < strLen; i++) { + if (!Character.isWhitespace(str.charAt(i))) { + return false; + } + } + return true; + } + + + /*** + * 对请求URL中的Query参数按照字段名的 ASCII 码从小到大排序(字典排序) + * + * @param apiUrl + * @return 排序后的API接口地址 + * @throws Exception + */ + public static String sortApiUrl(String apiUrl) throws EsignInterfacException { + + if (!apiUrl.contains("?")) { + return apiUrl; + } + + int queryIndex = apiUrl.indexOf("?"); + String apiUrlPath =apiUrl.substring(0,queryIndex+1); + String apiUrlQuery = apiUrl.substring(queryIndex+1); + //apiUrlQuery为空时返回 + if(isBlank(apiUrlQuery)){ + return apiUrl.substring(0,apiUrl.length()-1); + } + // 请求URL中Query参数转成Map + Map queryParamsMap = new HashMap(); + String[] params = apiUrlQuery.split("&"); + for (String str : params) { + int index = str.indexOf("="); + String key = str.substring(0, index); + String value = str.substring(index + 1); + if (queryParamsMap.containsKey(key)) { + String msg = MessageFormat.format("请求URL中的Query参数的{0}重复", key); + throw new EsignInterfacException(msg); + } + queryParamsMap.put(key, value); + } + + ArrayList queryMapKeys = new ArrayList(); + for (Map.Entry entry : queryParamsMap.entrySet()) { + queryMapKeys.add((String) entry.getKey()); + } + // 按照字段名的 ASCII 码从小到大排序(字典排序) + Collections.sort(queryMapKeys, new Comparator() { + @Override + public int compare(String o1, String o2) { + return (o1.compareToIgnoreCase(o2) == 0 ? -o1.compareTo(o2) : o1.compareToIgnoreCase(o2)); + } + }); + + StringBuffer queryString = new StringBuffer(); + // 构造Query参数键值对值对的格式 + for (int i = 0; i < queryMapKeys.size(); i++) { + String key = queryMapKeys.get(i); + String value = (String) queryParamsMap.get(key); + queryString.append(key); + queryString.append("="); + queryString.append(value); + queryString.append("&"); + } + if (queryString.length() > 0) { + queryString = queryString.deleteCharAt(queryString.length() - 1); + } + + // Query参数排序后的接口请求地址 + StringBuffer sortApiUrl = new StringBuffer(); + sortApiUrl.append(apiUrlPath); + sortApiUrl.append(queryString.toString()); + return sortApiUrl.toString(); + } + + /** + *获取query + * @param apiUrl + * @return + * @throws EsignInterfacException + */ + public static ArrayList getQuery(String apiUrl) throws EsignInterfacException { + ArrayList BasicNameValuePairList = new ArrayList<>(); + + if (!apiUrl.contains("?")) { + return BasicNameValuePairList; + } + + int queryIndex = apiUrl.indexOf("\\?"); + String apiUrlQuery = apiUrl.substring(queryIndex,apiUrl.length()); + + // 请求URL中Query参数转成Map + Map queryParamsMap = new HashMap(); + String[] params = apiUrlQuery.split("&"); + for (String str : params) { + int index = str.indexOf("="); + String key = str.substring(0, index); + String value = str.substring(index + 1); + if (queryParamsMap.containsKey(key)) { + String msg = MessageFormat.format("请求URL中的Query参数的{0}重复", key); + throw new EsignInterfacException(msg); + } + BasicNameValuePairList.add(new BasicNameValuePair(key,value)); + queryParamsMap.put(key, value); + } + return BasicNameValuePairList; + } + /** + * + */ + public static boolean callBackCheck(String timestamp,String requestQuery,String body,String key,String signature){ + String algorithm="HmacSHA256"; + String encoding="UTF-8"; + Mac mac = null; + try { + String data = timestamp + requestQuery + body; + mac = Mac.getInstance(algorithm); + SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(encoding), algorithm); + mac.init(secretKey); + mac.update(data.getBytes(encoding)); + } catch (NoSuchAlgorithmException | InvalidKeyException | UnsupportedEncodingException e) { + e.printStackTrace(); + System.out.println("获取Signature签名信息异常:" + e.getMessage()); + return false; + } + return byte2hex(mac.doFinal()).equalsIgnoreCase(signature); + } + + /*** + * 将byte[]转成16进制字符串 + * + * @param data + * + * @return 16进制字符串 + */ + public static String byte2hex(byte[] data) { + StringBuilder hash = new StringBuilder(); + String stmp; + for (int n = 0; data != null && n < data.length; n++) { + stmp = Integer.toHexString(data[n] & 0XFF); + if (stmp.length() == 1) + hash.append('0'); + hash.append(stmp); + } + return hash.toString(); + } + +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/wisdomarbitrate/utils/EsignFileBean.java b/ruoyi-system/src/main/java/com/ruoyi/wisdomarbitrate/utils/EsignFileBean.java new file mode 100644 index 0000000..b8cc861 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/wisdomarbitrate/utils/EsignFileBean.java @@ -0,0 +1,53 @@ +package com.ruoyi.wisdomarbitrate.utils; + +import java.io.File; + +/** + * @description 文件基础信息封装类 + * @author 澄泓 + * @date 2020/10/26 14:54 + * @version JDK1.7 + */ +public class EsignFileBean { + //文件名称 + private String fileName; + //文件大小 + private int fileSize; + //文件内容MD5 + private String fileContentMD5; + //文件地址 + private String filePath; + + + public EsignFileBean(String filePath) throws EsignInterfacException { + this.filePath=filePath; + this.fileContentMD5 = FileTransformation.getFileContentMD5(filePath); + File file = new File(filePath); + if (!file.exists()) { + throw new EsignInterfacException("文件不存在"); + } + this.fileName = file.getName(); + this.fileSize = (int) file.length(); + } + + public String getFileName() { + return fileName; + } + + public int getFileSize() { + return fileSize; + } + + public String getFileContentMD5() { + return fileContentMD5; + } + + /** + * 传入本地文件地址获取二进制数据 + * @return + * @throws EsignInterfacException + */ + public byte[] getFileBytes() throws EsignInterfacException { + return FileTransformation.fileToBytes(filePath); + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/wisdomarbitrate/utils/EsignHeaderConstant.java b/ruoyi-system/src/main/java/com/ruoyi/wisdomarbitrate/utils/EsignHeaderConstant.java new file mode 100644 index 0000000..9b93c3d --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/wisdomarbitrate/utils/EsignHeaderConstant.java @@ -0,0 +1,26 @@ +package com.ruoyi.wisdomarbitrate.utils; +/** + * @description 头部信息常量 + * @author 澄泓 + * @date 2020/10/22 15:05 + * @version JDK1.7 + */ +public enum EsignHeaderConstant { + ACCEPT("*/*"), + DATE(""), + HEADERS( ""), + CONTENTTYPE_FORMDATA("application/x-www-form-urlencoded"), + CONTENTTYPE_JSON("application/json; charset=UTF-8"), + CONTENTTYPE_PDF("application/pdf"), + CONTENTTYPE_STREAM("application/octet-stream"), + AUTHMODE("Signature"); + + private String value; + private EsignHeaderConstant(String value) { + this.value=value; + } + + public String VALUE(){ + return this.value; + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/wisdomarbitrate/utils/EsignHttpCfgHelper.java b/ruoyi-system/src/main/java/com/ruoyi/wisdomarbitrate/utils/EsignHttpCfgHelper.java new file mode 100644 index 0000000..bd43927 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/wisdomarbitrate/utils/EsignHttpCfgHelper.java @@ -0,0 +1,479 @@ +/* + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package com.ruoyi.wisdomarbitrate.utils; + +import org.apache.http.*; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.client.HttpRequestRetryHandler; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpRequestBase; +import org.apache.http.client.protocol.HttpClientContext; +import org.apache.http.config.Registry; +import org.apache.http.config.RegistryBuilder; +import org.apache.http.conn.ConnectTimeoutException; +import org.apache.http.conn.socket.ConnectionSocketFactory; +import org.apache.http.conn.socket.LayeredConnectionSocketFactory; +import org.apache.http.conn.socket.PlainConnectionSocketFactory; +import org.apache.http.conn.ssl.NoopHostnameVerifier; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.entity.ByteArrayEntity; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.BasicCredentialsProvider; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.apache.http.protocol.HttpContext; +import org.apache.http.util.EntityUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.net.ssl.*; +import java.io.IOException; +import java.io.InterruptedIOException; +import java.net.UnknownHostException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.text.MessageFormat; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +/** + * @description Http请求 辅助类 + */ +public class EsignHttpCfgHelper { + + private static Logger LOGGER = LoggerFactory.getLogger(EsignHttpCfgHelper.class); + /** + * 超时时间,默认15000毫秒 + */ + private static int MAX_TIMEOUT = 15000; + /** + * 请求池最大连接数,默认100个 + */ + private static int MAX_TOTAL=100; + /** + * 单域名最大的连接数,默认50个 + */ + private static int ROUTE_MAX_TOTAL=50; + /** + * 请求失败重试次数,默认3次 + */ + private static int MAX_RETRY = 3; + /** + * 是否需要域名校验,默认不需要校验 + */ + private static boolean SSL_VERIFY=false; + + /** + * 正向代理IP + */ + private static String PROXY_IP; + /** + * 正向代理端口,默认8888 + */ + private static int PROXY_PORT=8888; + /** + * 代理协议,默认http + */ + private static String PROXY_AGREEMENT="http"; + + /** + * 是否开启代理,默认false + */ + private static boolean OPEN_PROXY=false; + + /** + * 代理服务器用户名 + */ + private static String PROXY_USERNAME=""; + + /** + * 代理服务器密码 + */ + private static String PROXY_PASSWORD=""; + + + private static PoolingHttpClientConnectionManager connMgr; //连接池 + private static HttpRequestRetryHandler retryHandler; //重试机制 + + private static CloseableHttpClient httpClient=null; + + public static int getMaxTimeout() { + return MAX_TIMEOUT; + } + + public static void setMaxTimeout(int maxTimeout) { + MAX_TIMEOUT = maxTimeout; + } + + public static int getMaxTotal() { + return MAX_TOTAL; + } + + public static void setMaxTotal(int maxTotal) { + MAX_TOTAL = maxTotal; + } + + public static int getRouteMaxTotal() { + return ROUTE_MAX_TOTAL; + } + + public static void setRouteMaxTotal(int routeMaxTotal) { + ROUTE_MAX_TOTAL = routeMaxTotal; + } + + public static int getMaxRetry() { + return MAX_RETRY; + } + + public static void setMaxRetry(int maxRetry) { + MAX_RETRY = maxRetry; + } + + public static boolean isSslVerify() { + return SSL_VERIFY; + } + + public static void setSslVerify(boolean sslVerify) { + SSL_VERIFY = sslVerify; + } + + public static String getProxyIp() { + return PROXY_IP; + } + + public static void setProxyIp(String proxyIp) { + PROXY_IP = proxyIp; + } + + public static int getProxyPort() { + return PROXY_PORT; + } + + public static void setProxyPort(int proxyPort) { + PROXY_PORT = proxyPort; + } + + public static String getProxyAgreement() { + return PROXY_AGREEMENT; + } + + public static void setProxyAgreement(String proxyAgreement) { + PROXY_AGREEMENT = proxyAgreement; + } + + public static boolean getOpenProxy() { + return OPEN_PROXY; + } + + public static void setOpenProxy(boolean openProxy) { + OPEN_PROXY = openProxy; + } + + public static String getProxyUsername() { + return PROXY_USERNAME; + } + + public static void setProxyUserame(String proxyUsername) { + PROXY_USERNAME = proxyUsername; + } + + public static String getProxyPassword() { + return PROXY_PASSWORD; + } + + public static void setProxyPassword(String proxyPassword) { + PROXY_PASSWORD = proxyPassword; + } + + + + + /** + * 不允许外部创建实例 + */ + private EsignHttpCfgHelper() { + } + + //------------------------------公有方法start-------------------------------------------- + + + /** + * @description 发起HTTP / HTTPS 请求 + * + * @param reqType + * {@link EsignRequestType} 请求类型 GET、 POST 、 DELETE 、 PUT + * @param httpUrl + * {@link String} 请求目标地址 + * @param headers + * {@link Map} 请求头 + * @param param + * {@link Object} 参数 + * @return + * @throws EsignInterfacException + * @author 澄泓 + */ + public static EsignHttpResponse sendHttp(EsignRequestType reqType, String httpUrl, Map headers, Object param, boolean debug) + throws EsignInterfacException { + HttpRequestBase reqBase=null; + if(httpUrl.startsWith("http")){ + reqBase=reqType.getHttpType(httpUrl); + }else{ + throw new EsignInterfacException("请求url地址格式错误"); + } + if(debug){ + LOGGER.info("请求头:{}",headers+"\n"); + LOGGER.info("请求参数\n{}", param+"\n"); + LOGGER.info("请求地址\n:{}\n请求方式\n:{}",reqBase.getURI(),reqType+"\n"); + } + //请求方法不是GET或者DELETE时传入body体,否则不传入。 + String[] methods = {"DELETE", "GET"}; + if(param instanceof String&&Arrays.binarySearch(methods, reqType.name())<0){//POST或者PUT请求 + ((HttpEntityEnclosingRequest) reqBase).setEntity( + new StringEntity(String.valueOf(param), ContentType.create("application/json", "UTF-8"))); + } + //参数时字节流数组 + else if(param instanceof byte[]) { + reqBase=reqType.getHttpType(httpUrl); + byte[] paramBytes = (byte[])param; + ((HttpEntityEnclosingRequest) reqBase).setEntity(new ByteArrayEntity(paramBytes)); + } + //参数是form表单时 + else if(param instanceof List){ + ((HttpEntityEnclosingRequest) reqBase).setEntity(new UrlEncodedFormEntity((Iterable) param)); + } + httpClient = getHttpClient(); + config(reqBase); + + //设置请求头 + if(headers != null &&headers.size()>0) { + for(Map.Entry entry :headers.entrySet()) { + reqBase.setHeader(entry.getKey(), entry.getValue()); + } + } + //响应对象 + CloseableHttpResponse res = null; + //响应内容 + String resCtx = null; + int status; + EsignHttpResponse esignHttpResponse = new EsignHttpResponse(); + try { + //执行请求 + res = httpClient.execute(reqBase); + status=res.getStatusLine().getStatusCode(); + + //获取请求响应对象和响应entity + HttpEntity httpEntity = res.getEntity(); + if(httpEntity != null) { + resCtx = EntityUtils.toString(httpEntity,"utf-8"); + } + if(debug) { + LOGGER.info("响应\n{}", resCtx + "\n"); + LOGGER.info("----------------------------end------------------------"); + } + } catch (NoHttpResponseException e) { + throw new EsignInterfacException("服务器丢失了",e); + } catch (SSLHandshakeException e){ + String msg = MessageFormat.format("SSL握手异常", e); + EsignInterfacException ex = new EsignInterfacException(msg, e); + throw ex; + } catch (UnknownHostException e){ + EsignInterfacException ex = new EsignInterfacException("服务器找不到", e); + ex.initCause(e); + throw ex; + } catch(ConnectTimeoutException e){ + EsignInterfacException ex = new EsignInterfacException("连接超时", e); + ex.initCause(e); + throw ex; + } catch(SSLException e){ + EsignInterfacException ex = new EsignInterfacException("SSL异常",e); + ex.initCause(e); + throw ex; + } catch (ClientProtocolException e) { + EsignInterfacException ex = new EsignInterfacException("请求头异常",e); + ex.initCause(e); + throw ex; + } catch (IOException e) { + EsignInterfacException ex = new EsignInterfacException("网络请求失败",e); + ex.initCause(e); + throw ex; + } finally { + if(res != null) { + try { + res.close(); + } catch (IOException e) { + EsignInterfacException ex = new EsignInterfacException("--->>关闭请求响应失败",e); + ex.initCause(e); + throw ex; + } + } + } + esignHttpResponse.setStatus(status); + esignHttpResponse.setBody(resCtx); + return esignHttpResponse; + } + //------------------------------公有方法end---------------------------------------------- + + //------------------------------私有方法start-------------------------------------------- + + /** + * @description 请求头和超时时间配置 + * + * @param httpReqBase + * @author 澄泓 + */ + private static void config(HttpRequestBase httpReqBase) { + // 配置请求的超时设置 + RequestConfig.Builder builder = RequestConfig.custom() + .setConnectionRequestTimeout(MAX_TIMEOUT) + .setConnectTimeout(MAX_TIMEOUT) + .setSocketTimeout(MAX_TIMEOUT); + if(OPEN_PROXY){ + HttpHost proxy=new HttpHost(PROXY_IP,PROXY_PORT,PROXY_AGREEMENT); + builder.setProxy(proxy); + } + RequestConfig requestConfig = builder.build(); + httpReqBase.setConfig(requestConfig); + } + + /** + * @description 连接池配置 + * + * @return + * @author 澄泓 + */ + private static void cfgPoolMgr() throws EsignInterfacException { + ConnectionSocketFactory plainsf = PlainConnectionSocketFactory.getSocketFactory(); + LayeredConnectionSocketFactory sslsf = SSLConnectionSocketFactory.getSocketFactory(); + if(!SSL_VERIFY){ + sslsf=sslConnectionSocketFactory(); + } + + Registry registry = RegistryBuilder.create() + .register("http", plainsf) + .register("https", sslsf) + .build(); + + //连接池管理器 + connMgr = new PoolingHttpClientConnectionManager(registry); + //请求池最大连接数 + connMgr.setMaxTotal(MAX_TOTAL); + //但域名最大的连接数 + connMgr.setDefaultMaxPerRoute(ROUTE_MAX_TOTAL); + } + + + + + /** + * @description 设置重试机制 + * + * @author 澄泓 + */ + private static void cfgRetryHandler() { + retryHandler = new HttpRequestRetryHandler() { + + @Override + public boolean retryRequest(IOException e, int excCount, HttpContext ctx) { + //超过最大重试次数,就放弃 + if(excCount > MAX_RETRY) { + return false; + } + //服务器丢掉了链接,就重试 + if(e instanceof NoHttpResponseException) { + return true; + } + //不重试SSL握手异常 + if(e instanceof SSLHandshakeException) { + return false; + } + //中断 + if(e instanceof InterruptedIOException) { + return false; + } + //目标服务器不可达 + if(e instanceof UnknownHostException) { + return false; + } + //连接超时 + //SSL异常 + if(e instanceof SSLException) { + return false; + } + + HttpClientContext clientCtx = HttpClientContext.adapt(ctx); + HttpRequest req = clientCtx.getRequest(); + //如果是幂等请求,就再次尝试 + if(!(req instanceof HttpEntityEnclosingRequest)) { + return true; + } + return false; + } + }; + } + + /** + * 忽略域名校验 + */ + private static SSLConnectionSocketFactory sslConnectionSocketFactory() throws EsignInterfacException { + try { + SSLContext ctx = SSLContext.getInstance("TLS"); // 创建一个上下文(此处指定的协议类型似乎不是重点) + X509TrustManager tm = new X509TrustManager() { // 创建一个跳过SSL证书的策略 + public X509Certificate[] getAcceptedIssuers() { + return null; + } + + public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException { + } + + public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException { + } + }; + ctx.init(null, new TrustManager[] { tm }, null); // 使用上面的策略初始化上下文 + return new SSLConnectionSocketFactory(ctx, new String[] { "SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2" }, null, NoopHostnameVerifier.INSTANCE); + }catch (Exception e){ + EsignInterfacException ex = new EsignInterfacException("忽略域名校验失败",e); + ex.initCause(e); + throw ex; + } + + } + + /** + * @description 获取单例HttpClient + * + * @return + * @author 澄泓 + */ + private static synchronized CloseableHttpClient getHttpClient() throws EsignInterfacException { + if(httpClient==null) { + CredentialsProvider credsProvider = new BasicCredentialsProvider(); + credsProvider.setCredentials(new AuthScope(PROXY_IP,PROXY_PORT),new UsernamePasswordCredentials(PROXY_USERNAME, PROXY_PASSWORD)); + cfgPoolMgr(); + cfgRetryHandler(); + HttpClientBuilder httpClientBuilder = HttpClientBuilder.create(); + httpClient = httpClientBuilder.setDefaultCredentialsProvider(credsProvider).setConnectionManager(connMgr).setRetryHandler(retryHandler).build(); + } + return httpClient; + + } + //------------------------------私有方法end---------------------------------------------- + + +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/wisdomarbitrate/utils/EsignHttpHelper.java b/ruoyi-system/src/main/java/com/ruoyi/wisdomarbitrate/utils/EsignHttpHelper.java new file mode 100644 index 0000000..b798d76 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/wisdomarbitrate/utils/EsignHttpHelper.java @@ -0,0 +1,168 @@ +package com.ruoyi.wisdomarbitrate.utils; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.Map; + +/** + * @description Http 请求 辅助类 + * @author 澄泓 + * @since JDK1.7 + */ +public class EsignHttpHelper { + private static final Logger LOGGER = LoggerFactory.getLogger(EsignHttpHelper.class); + + /** + * 不允许外部创建实例 + */ + private EsignHttpHelper() { + + } + + /** + * @description 发送常规HTTP 请求 + * + * @param reqType 请求方式 + * @param url 请求路径 + * @param paramStr 请求参数 + * @return + * @throws EsignInterfacException + * @author 澄泓 + */ + public static EsignHttpResponse doCommHttp(String host, String url,EsignRequestType reqType, Object paramStr ,Map httpHeader,boolean debug) throws EsignInterfacException { + return EsignHttpCfgHelper.sendHttp(reqType, host+url,httpHeader, paramStr, debug); + } + + + /** + * @description 发送文件流上传 HTTP 请求 + * + * @param reqType 请求方式 + * @param uploadUrl 请求路径 + * @param param 请求参数 + * @param fileContentMd5 文件fileContentMd5 + * @param contentType 文件MIME类型 + * @return + * @throws EsignInterfacException + * @author 澄泓 + */ + public static EsignHttpResponse doUploadHttp( String uploadUrl,EsignRequestType reqType,byte[] param, String fileContentMd5, + String contentType, boolean debug) throws EsignInterfacException { + Map uploadHeader = buildUploadHeader(fileContentMd5, contentType); + if(debug){ + LOGGER.info("----------------------------start------------------------"); + LOGGER.info("fileContentMd5:{}",fileContentMd5); + LOGGER.info("contentType:{}",contentType); + } + return EsignHttpCfgHelper.sendHttp(reqType,uploadUrl, uploadHeader, param,debug); + } + + + + /** + * @description 构建一个签名鉴权+json数据的esign请求头 + * @return + * @author 澄泓 + */ + public static Map buildSignAndJsonHeader(String projectId,String contentMD5,String accept,String contentType,String authMode) { + + Map header = new HashMap<>(); + header.put("X-Tsign-Open-App-Id", projectId); + header.put("X-Tsign-Open-Version-Sdk",EsignCoreSdkInfo.getSdkVersion()); + header.put("X-Tsign-Open-Ca-Timestamp", EsignEncryption.timeStamp()); + header.put("Accept",accept); + header.put("Content-MD5",contentMD5); + header.put("Content-Type", contentType); + header.put("X-Tsign-Open-Auth-Mode", authMode); + return header; + } + + /** + * 签名计算并且构建一个签名鉴权+json数据的esign请求头 + * @param httpMethod + * * The name of a supported {@linkplain java.nio.charset.Charset + * * charset} + * @return + */ + public static Map signAndBuildSignAndJsonHeader(String projectId, String secret,String paramStr,String httpMethod,String url,boolean debug) throws EsignInterfacException { + String contentMD5=""; + //统一转大写处理 + httpMethod = httpMethod.toUpperCase(); + if("GET".equals(httpMethod)||"DELETE".equals(httpMethod)){ + paramStr=null; + contentMD5=""; + } else if("PUT".equals(httpMethod)||"POST".equals(httpMethod)){ + //对body体做md5摘要 + contentMD5=EsignEncryption.doContentMD5(paramStr); + }else{ + throw new EsignInterfacException(String.format("不支持的请求方法%s",httpMethod)); + } + //构造一个初步的请求头 + Map esignHeaderMap = buildSignAndJsonHeader(projectId, contentMD5, EsignHeaderConstant.ACCEPT.VALUE(), EsignHeaderConstant.CONTENTTYPE_JSON.VALUE(), EsignHeaderConstant.AUTHMODE.VALUE()); + //排序 + url=EsignEncryption.sortApiUrl(url); + //传入生成的bodyMd5,加上其他请求头部信息拼接成字符串 + String message = EsignEncryption.appendSignDataString(httpMethod, esignHeaderMap.get("Content-MD5"),esignHeaderMap.get("Accept"),esignHeaderMap.get("Content-Type"),esignHeaderMap.get("Headers"),esignHeaderMap.get("Date"), url); + //整体做sha256签名 + String reqSignature = EsignEncryption.doSignatureBase64(message, secret); + //请求头添加签名值 + esignHeaderMap.put("X-Tsign-Open-Ca-Signature",reqSignature); + if(debug){ + LOGGER.info("----------------------------start------------------------"); + LOGGER.info("待计算body值:{}", paramStr+"\n"); + LOGGER.info("MD5值:{}",contentMD5+"\n"); + LOGGER.info("待签名字符串:{}",message+"\n"); + LOGGER.info("签名值:{}",reqSignature+"\n"); + } + return esignHeaderMap; + } + + + /** + * @description 构建一个Token鉴权+jsons数据的esign请求头 + * @return + * @author 澄泓 + */ + public static Map buildTokenAndJsonHeader(String appid,String token) { + Map esignHeader = new HashMap<>(); + esignHeader.put("X-Tsign-Open-Version-Sdk",EsignCoreSdkInfo.getSdkVersion()); + esignHeader.put("Content-Type", EsignHeaderConstant.CONTENTTYPE_JSON.VALUE()); + esignHeader.put("X-Tsign-Open-App-Id", appid); + esignHeader.put("X-Tsign-Open-Token", token); + return esignHeader; + } + + /** + * @description 构建一个form表单数据的esign请求头 + * @return + * @author 澄泓 + */ + public static Map buildFormDataHeader(String appid) { + Map esignHeader = new HashMap<>(); + esignHeader.put("X-Tsign-Open-Version-Sdk",EsignCoreSdkInfo.getSdkVersion()); + esignHeader.put("X-Tsign-Open-Authorization-Version","v2"); + esignHeader.put("Content-Type", EsignHeaderConstant.CONTENTTYPE_FORMDATA.VALUE()); + esignHeader.put("X-Tsign-Open-App-Id", appid); + return esignHeader; + } + + /** + * @description 创建文件流上传 请求头 + * + * @param fileContentMd5 + * @param contentType + * @return + * @author 澄泓 + */ + public static Map buildUploadHeader(String fileContentMd5, String contentType) { + Map header = new HashMap<>(); + header.put("Content-MD5", fileContentMd5); + header.put("Content-Type", contentType); + + return header; + } + + // ------------------------------私有方法end---------------------------------------------- +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/wisdomarbitrate/utils/EsignHttpResponse.java b/ruoyi-system/src/main/java/com/ruoyi/wisdomarbitrate/utils/EsignHttpResponse.java new file mode 100644 index 0000000..ac7a516 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/wisdomarbitrate/utils/EsignHttpResponse.java @@ -0,0 +1,27 @@ +package com.ruoyi.wisdomarbitrate.utils; +/** + * 网络请求的response类 + * @author 澄泓 + * @date 2022/2/21 17:28 + * @version + */ +public class EsignHttpResponse { + private int status; + private String body; + + public int getStatus() { + return status; + } + + public void setStatus(int status) { + this.status = status; + } + + public String getBody() { + return body; + } + + public void setBody(String body) { + this.body = body; + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/wisdomarbitrate/utils/EsignInterfacException.java b/ruoyi-system/src/main/java/com/ruoyi/wisdomarbitrate/utils/EsignInterfacException.java new file mode 100644 index 0000000..4cd44b2 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/wisdomarbitrate/utils/EsignInterfacException.java @@ -0,0 +1,34 @@ +package com.ruoyi.wisdomarbitrate.utils; + +/** + * description 自定义全局异常 + */ +public class EsignInterfacException extends Exception { + + private static final long serialVersionUID = 4359180081622082792L; + private Exception e; + + public EsignInterfacException(String msg) { + super(msg); + } + + public EsignInterfacException(String msg, Throwable cause) { + super(msg,cause); + } + + public EsignInterfacException(){ + + } + + public Exception getE() { + return e; + } + + public void setE(Exception e) { + this.e = e; + } + + + + +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/wisdomarbitrate/utils/EsignRequestType.java b/ruoyi-system/src/main/java/com/ruoyi/wisdomarbitrate/utils/EsignRequestType.java new file mode 100644 index 0000000..57d8116 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/wisdomarbitrate/utils/EsignRequestType.java @@ -0,0 +1,39 @@ +package com.ruoyi.wisdomarbitrate.utils; + +import org.apache.http.client.methods.*; + +/** + * @description 请求类型 + * @author 澄泓 + * @since JDK1.7 + */ +public enum EsignRequestType { + + POST{ + @Override + public HttpRequestBase getHttpType(String url) { + return new HttpPost(url); + } + }, + GET{ + @Override + public HttpRequestBase getHttpType(String url) { + return new HttpGet(url); + } + }, + DELETE{ + @Override + public HttpRequestBase getHttpType(String url) { + return new HttpDelete(url); + } + }, + PUT{ + @Override + public HttpRequestBase getHttpType(String url) { + return new HttpPut(url); + } + }, + ; + + public abstract HttpRequestBase getHttpType(String url); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/wisdomarbitrate/utils/FileTransformation.java b/ruoyi-system/src/main/java/com/ruoyi/wisdomarbitrate/utils/FileTransformation.java new file mode 100644 index 0000000..77ae2f8 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/wisdomarbitrate/utils/FileTransformation.java @@ -0,0 +1,280 @@ +/* + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package com.ruoyi.wisdomarbitrate.utils; + +import org.apache.commons.codec.binary.Base64; + +import java.io.*; +import java.net.HttpURLConnection; +import java.net.URL; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.HashMap; +import java.util.Map; + +/** + * @description 文件转换类 + */ +public class FileTransformation { + + /** + * 传入本地文件路径转二进制byte + * + * @param srcFilePath 本地文件路径 + * @return + * @throws EsignInterfacException + */ + public static byte[] fileToBytes(String srcFilePath) throws EsignInterfacException { + return getBytes(srcFilePath); + } + + /** + * 图片转base64 + * + * @param filePath 本地文件路径 + * @return + * @throws EsignInterfacException + */ + public static String fileToBase64(String filePath) throws EsignInterfacException { + byte[] bytes; + String base64 = null; + bytes = fileToBytes(filePath); + base64 = Base64.encodeBase64String(bytes); + base64 = base64.replaceAll("\r\n", ""); + return base64; + } + + public static void main(String[] args) throws EsignInterfacException { + System.out.println(getFileContentMD5("D:\\文档\\PLT2022-02124CT.pdf")); + } + + /*** + * 计算文件内容的Content-MD5 + * @param filePath 文件路径 + * @return + */ + public static String getFileContentMD5(String filePath) throws EsignInterfacException { + // 获取文件MD5的二进制数组(128位) + byte[] bytes = getFileMD5Bytes128(filePath); + // 对文件MD5的二进制数组进行base64编码 + return new String(Base64.encodeBase64String(bytes)); + } + + /** + * 下载文件 + * + * @param httpUrl 网络文件地址url + * @return + */ + public static boolean downLoadFileByUrl(String httpUrl, String dir) throws EsignInterfacException { + InputStream fis = null; + FileOutputStream fileOutputStream = null; + try { + URL url = new URL(httpUrl); + HttpURLConnection httpConn = (HttpURLConnection) url.openConnection(); + httpConn.connect(); + fis = httpConn.getInputStream(); + fileOutputStream = new FileOutputStream(new File(dir)); + byte[] md5Bytes = null; + + byte[] buffer = new byte[1024]; + int length = -1; + while ((length = fis.read(buffer, 0, 1024)) != -1) { + fileOutputStream.write(buffer, 0, length); + } + } catch (IOException e) { + EsignInterfacException ex = new EsignInterfacException("获取文件流异常", e); + ex.initCause(e); + throw ex; + } finally { + try { + if (fis != null) { + fis.close(); + } + if (fileOutputStream != null) { + fileOutputStream.close(); + } + } catch (IOException e) { + EsignInterfacException ex = new EsignInterfacException("关闭文件流异常", e); + ex.initCause(e); + throw ex; + } + } + return true; + } + + + /** + * 网络文件转二进制MD5数组并获取文件大小 + * + * @param fileUrl 网络文件地址url + * @return + */ + public static Map fileUrlToBytes(String fileUrl) throws EsignInterfacException { + HashMap map = new HashMap(); + try { + URL url = new URL(fileUrl); + HttpURLConnection httpConn = (HttpURLConnection) url.openConnection(); + httpConn.connect(); + InputStream fis = httpConn.getInputStream(); + ByteArrayOutputStream outStream = new ByteArrayOutputStream(); + outStream.close(); + map.put("fileSize", fis.available()); + byte[] md5Bytes = null; + MessageDigest md5 = MessageDigest.getInstance("MD5"); + byte[] buffer = new byte[1024]; + int length = -1; + while ((length = fis.read(buffer, 0, 1024)) != -1) { + md5.update(buffer, 0, length); + outStream.write(buffer, 0, length); + } + md5Bytes = md5.digest(); + byte[] fileData = outStream.toByteArray(); + map.put("fileData", fileData); + outStream.close(); + fis.close(); + map.put("md5Bytes", md5Bytes); + } catch (IOException e) { + EsignInterfacException ex = new EsignInterfacException("获取文件流异常", e); + ex.initCause(e); + throw ex; + } catch (NoSuchAlgorithmException e) { + EsignInterfacException ex = new EsignInterfacException("文件计算异常", e); + ex.initCause(e); + throw ex; + } + return map; + } + + /*** + * 获取文件MD5的二进制数组(128位) + * @param filePath + * @return + * @throws EsignInterfacException + */ + public static byte[] getFileMD5Bytes128(String filePath) throws EsignInterfacException { + FileInputStream fis = null; + byte[] md5Bytes = null; + try { + File file = new File(filePath); + fis = new FileInputStream(file); + MessageDigest md5 = MessageDigest.getInstance("MD5"); + byte[] buffer = new byte[1024]; + int length = -1; + while ((length = fis.read(buffer, 0, 1024)) != -1) { + md5.update(buffer, 0, length); + } + md5Bytes = md5.digest(); + fis.close(); + } catch (FileNotFoundException e) { + EsignInterfacException ex = new EsignInterfacException("文件找不到", e); + ex.initCause(e); + throw ex; + } catch (NoSuchAlgorithmException e) { + EsignInterfacException ex = new EsignInterfacException("不支持此算法", e); + ex.initCause(e); + throw ex; + } catch (IOException e) { + EsignInterfacException ex = new EsignInterfacException("输入流或输出流异常", e); + ex.initCause(e); + throw ex; + } finally { + if (fis != null) { + try { + fis.close(); + } catch (IOException e) { + EsignInterfacException ex = new EsignInterfacException("关闭文件输入流失败", e); + ex.initCause(e); + throw ex; + } + } + } + return md5Bytes; + } + + /** + * @param path + * @return + * @throws EsignInterfacException + * @description 根据文件路径,获取文件base64 + * @author 宫清 + * @date 2019年7月21日 下午4:22:08 + */ + public static String getBase64Str(String path) throws EsignInterfacException { + InputStream is = null; + try { + is = new FileInputStream(new File(path)); + byte[] bytes = new byte[is.available()]; + is.read(bytes); + return Base64.encodeBase64String(bytes); + } catch (Exception e) { + EsignInterfacException ex = new EsignInterfacException("获取文件输入流失败", e); + ex.initCause(e); + throw ex; + } finally { + if (is != null) { + try { + is.close(); + } catch (IOException e) { + EsignInterfacException ex = new EsignInterfacException("关闭文件输入流失败", e); + ex.initCause(e); + throw ex; + } + } + } + } + + /** + * @param path 文件路径 + * @return + * @description 获取文件名称 + * @author 宫清 + * @date 2019年7月21日 下午8:21:16 + */ + public static String getFileName(String path) { + return new File(path).getName(); + } + + /** + * @param filePath {@link String} 文件地址 + * @return + * @throws EsignInterfacException + * @description 获取文件字节流 + * @date 2019年7月10日 上午9:17:00 + * @author 宫清 + */ + public static byte[] getBytes(String filePath) throws EsignInterfacException { + File file = new File(filePath); + FileInputStream fis = null; + byte[] buffer = null; + try { + fis = new FileInputStream(file); + buffer = new byte[(int) file.length()]; + fis.read(buffer); + } catch (Exception e) { + EsignInterfacException ex = new EsignInterfacException("获取文件字节流失败", e); + ex.initCause(e); + throw ex; + } finally { + if (fis != null) { + try { + fis.close(); + } catch (IOException e) { + EsignInterfacException ex = new EsignInterfacException("关闭文件字节流失败", e); + ex.initCause(e); + throw ex; + } + } + } + return buffer; + } +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/wisdomarbitrate/utils/SignAward.java b/ruoyi-system/src/main/java/com/ruoyi/wisdomarbitrate/utils/SignAward.java new file mode 100644 index 0000000..d0e599a --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/wisdomarbitrate/utils/SignAward.java @@ -0,0 +1,336 @@ +package com.ruoyi.wisdomarbitrate.utils; + +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.ruoyi.wisdomarbitrate.domain.SealSignRecord; + +import java.util.Map; + +public class SignAward { + + private static String eSignHost= EsignApplicaConfig.EsignHost; + private static String eSignAppId=EsignApplicaConfig.EsignAppId; + private static String eSignAppSecret=EsignApplicaConfig.EsignAppSecret; +// public static String fileId = "95d0c307d91e4985bdb8874f6f84daa5"; + public static String fileId = "a0c2ad21065f48ff8b872412c39d5d3a"; + + + + public static void main(String[] args) throws EsignInterfacException { + Gson gson = new Gson(); + + SealSignRecord sealSignRecord = new SealSignRecord(); + + sealSignRecord.setFileid("a808f1f39a744357a2f018e4ab34c55d"); + sealSignRecord.setFilename("23893bfd3f2249ffa5c82850c11c482e.pdf"); + sealSignRecord.setSignFlowid("41e6732b48c54c63a91b2379c352212d"); + + + sealSignRecord.setPensonAccount("18209231185"); + sealSignRecord.setPensonName("秦桃则"); + sealSignRecord.setOrgnizeName("西安云美电子科技有限公司"); + sealSignRecord.setOrgnizeNamePsnAccount("17691338406"); + sealSignRecord.setOrgnizeNamepsnName("韩超勃"); + sealSignRecord.setPositionPagepsn("2"); + + sealSignRecord.setPositionXpsn(279+20); + sealSignRecord.setPositionYpsn(216.336-20); + + sealSignRecord.setPositionPageorg("2"); + + sealSignRecord.setPositionXorg(342+30); + sealSignRecord.setPositionYorg(185.136); + + + + + /* 发起签署*/ +// EsignHttpResponse createByFile = createByFile(sealSignRecord); +// JsonObject createByFileJsonObject = gson.fromJson(createByFile.getBody(), JsonObject.class); +// JsonObject createByFileData = createByFileJsonObject.getAsJsonObject("data"); +// String signFlowId = createByFileData.get("signFlowId").getAsString(); +// System.err.println("流程id:"+signFlowId); + + /* 获取文件签名印章位置*/ +// EsignHttpResponse positions = getPositions(sealSignRecord); +// JsonObject positionsJsonObject = gson.fromJson(positions.getBody(), JsonObject.class); +// JsonObject positionsData = positionsJsonObject.getAsJsonObject("data"); +// JsonArray keywordPositions = positionsData.get("keywordPositions").getAsJsonArray(); +// System.out.println("获取文件签名印章位置:" +keywordPositions.toString()); + +// String signFlowId = "c9955453716344f9971d308abdc13464"; + //获取合同文件签名链接 +// EsignHttpResponse signUrl = signUrl(sealSignRecord); +// JsonObject signUrlJsonObject = gson.fromJson(signUrl.getBody(), JsonObject.class); +// JsonObject signUrlData = signUrlJsonObject.getAsJsonObject("data"); +// String shortUrl = signUrlData.get("shortUrl").getAsString(); +// String url = signUrlData.get("url").getAsString(); +// System.out.println("签署短链接:" +shortUrl); +// System.out.println("签署长链接:"+url); + + //获取合同文件用印链接 + EsignHttpResponse usesealUrl = usesealUrl(sealSignRecord); + JsonObject usesealUrlJsonObject = gson.fromJson(usesealUrl.getBody(), JsonObject.class); + JsonObject usesealUrlData = usesealUrlJsonObject.getAsJsonObject("data"); + String shortusesealUrl = usesealUrlData.get("shortUrl").getAsString(); + String sealUrl = usesealUrlData.get("url").getAsString(); + System.out.println("签署长链接:" +shortusesealUrl); + System.out.println("签署短链接:"+sealUrl); + + + } + + + /** + * 发起签署 + * @return + * @throws EsignInterfacException + */ + public static EsignHttpResponse createByFile(SealSignRecord sealSignRecord) throws EsignInterfacException { + String apiaddr = "/v3/sign-flow/create-by-file"; + + String fileId = sealSignRecord.getFileid(); + String fileName = sealSignRecord.getFilename(); + + String psnAccount = sealSignRecord.getPensonAccount(); + String psnName = sealSignRecord.getPensonName(); + + String orgName = sealSignRecord.getOrgnizeName(); + String orgNamePsnAccount = sealSignRecord.getOrgnizeNamePsnAccount(); + String orgNamepsnName = sealSignRecord.getOrgnizeNamepsnName(); + + String positionPagepsn = sealSignRecord.getPositionPagepsn(); + double positionXpsn = sealSignRecord.getPositionXpsn(); + double positionYpsn = sealSignRecord.getPositionYpsn(); + + String positionPageorg = sealSignRecord.getPositionPageorg(); + double positionXorg = sealSignRecord.getPositionXorg(); + double positionYorg = sealSignRecord.getPositionYorg(); + + + String jsonParm = "{\n" + + " \"docs\": [\n" + + " {\n" + + +// " \"fileId\": \"5bd34a81e8084acaab3287c019e82fe8\",\n" + + " \"fileId\": \"" + fileId + "\",\n" + + +// " \"fileName\": \"477470a7741b4536a200c792b6ddf966.pdf\"\n" + + " \"fileName\": \"" + fileName + "\"\n" + + + " }\n" + + " ],\n" + + " \"signFlowConfig\": {\n" + + " \"signFlowTitle\": \"测试合同\",\n" + + " \"autoStart\": true,\n" + + " \"authConfig\": {\n" + + " \"willingnessAuthModes\": [\n" + + " \"CODE_SMS\"\n" + + " ],\n" + + " \"psnAvailableAuthModes\": [\n" + + " \"PSN_MOBILE3\"\n" + + " ],\n" + + " \"orgAvailableAuthModes\": [\n" + + " \"ORG_LEGALREP\"\n" + + " ]\n" + + " },\n" + + " \"autoFinish\": true\n" + + " },\n" + + + " \"signers\": [\n" + + + " {\n" + + + " \"psnSignerInfo\": {\n" + + +// " \"psnAccount\": \"18209231185\",\n" + + " \"psnAccount\": \"" + psnAccount + "\",\n" + + + " \"psnInfo\": {\n" + + +// " \"psnName\": \"秦桃则\"\n" + + " \"psnName\": \"" + psnName + "\"\n" + + " }\n" + + + + " },\n" + + + + " \"signFields\": [\n" + + " {\n" + + +// " \"fileId\": \"5bd34a81e8084acaab3287c019e82fe8\",\n" + + " \"fileId\": \"" + fileId + "\",\n" + + + " \"normalSignFieldConfig\": {\n" + + " \"autoSign\": false,\n" + + " \"freeMode\": false,\n" + + " \"movableSignField\": false,\n" + + " \"signFieldPosition\": {\n" + + +// " \"positionPage\": \"2\",\n" + + " \"positionPage\": \"" + positionPagepsn + "\",\n" + + +// " \"positionX\": 310.0,\n" + + " \"positionX\": " + positionXpsn + ",\n" + + +// " \"positionY\": 247.536\n" + + " \"positionY\": " + positionYpsn + "\n" + + + " },\n" + + " \"signFieldStyle\": 1\n" + + " },\n" + + " \"signFieldType\": 0\n" + + " }\n" + + " ],\n" + + " \"signerType\": 0\n" + + " },\n" + + + + " {\n" + + " \"orgSignerInfo\": {\n" + + +// " \"orgName\": \"西安云美电子科技有限公司\",\n" + + " \"orgName\": \"" + orgName + "\",\n" + + + " \"transactorInfo\": {\n" + + + +// " \"psnAccount\": \"17691338406\",\n" + + " \"psnAccount\": \"" + orgNamePsnAccount + "\",\n" + + + " \"psnInfo\": {\n" + + +// " \"psnName\": \"韩超勃\"\n" + + " \"psnName\": \"" + orgNamepsnName + "\"\n" + + " }\n" + + + " }\n" + + " },\n" + + + " \"signFields\": [\n" + + " {\n" + + +// " \"fileId\": \"5bd34a81e8084acaab3287c019e82fe8\",\n" + + " \"fileId\": \"" + fileId + "\",\n" + + + " \"normalSignFieldConfig\": {\n" + + " \"autoSign\": false,\n" + + " \"freeMode\": false,\n" + + + " \"signFieldPosition\": {\n" + + +// " \"positionPage\": \"2\",\n" + + " \"positionPage\": \"" + positionPageorg + "\",\n" + + +// " \"positionX\": 340.0,\n" + + " \"positionX\": " + positionXorg + ",\n" + + +// " \"positionY\": 340.736\n" + + " \"positionY\": " + positionYorg + "\n" + + " },\n" + + " \"signFieldStyle\": 1\n" + + " },\n" + + " \"signFieldType\": 0\n" + + " }\n" + + " ],\n" + + " \"signerType\": 1\n" + + " }\n" + + + " ]\n" + + "}"; + + //请求方法 + EsignRequestType requestType = EsignRequestType.POST; + //生成请求签名鉴权方式的Header + Map header = EsignHttpHelper.signAndBuildSignAndJsonHeader(eSignAppId, eSignAppSecret, jsonParm, requestType.name(), apiaddr, true); + //发起接口请求 + return EsignHttpHelper.doCommHttp(eSignHost, apiaddr, requestType, jsonParm, header, true); + } + + /** + * 获取合同文件签名链接 + * @return + * @throws EsignInterfacException + */ + public static EsignHttpResponse signUrl(SealSignRecord sealSignRecord) throws EsignInterfacException { + + String signFlowId = sealSignRecord.getSignFlowid(); + String psnAccount = sealSignRecord.getPensonAccount(); + + String apiaddr = "/v3/sign-flow/" + signFlowId + "/sign-url"; + String jsonParm = "{\n" + + " \"operator\": {\n" + +// " \"psnAccount\":\"18209231185\"\n" + + " \"psnAccount\": \"" + psnAccount + "\"\n" + + " }\n" + + "}"; + //请求方法 + EsignRequestType requestType = EsignRequestType.POST; + //生成请求签名鉴权方式的Header + Map header = EsignHttpHelper.signAndBuildSignAndJsonHeader(eSignAppId, eSignAppSecret, jsonParm, requestType.name(), apiaddr, true); + //发起接口请求 + return EsignHttpHelper.doCommHttp(eSignHost, apiaddr, requestType, jsonParm, header, true); + } + + /** + * 获取合同文件用印链接 + * @return + * @throws EsignInterfacException + */ + public static EsignHttpResponse usesealUrl(SealSignRecord sealSignRecord) throws EsignInterfacException { + String signFlowId = sealSignRecord.getSignFlowid(); + String apiaddr = "/v3/sign-flow/" + signFlowId + "/sign-url"; + + String psnAccount = sealSignRecord.getOrgnizeNamePsnAccount(); + String orgName = sealSignRecord.getOrgnizeName(); + + String jsonParm = "{\n" + +// " \"needLogin\": true,\n" + + " \"operator\": {\n" + + +// " \"psnAccount\":\"17691338406\"\n" + + " \"psnAccount\": \"" + psnAccount + "\"\n" + + " },\n" + + " \"organization\": {\n" + + +// " \"orgName\": \"西安云美电子科技有限公司\"\n" + + " \"orgName\": \"" + orgName + "\"\n" + + + " }\n" + + "}"; + //请求方法 + EsignRequestType requestType = EsignRequestType.POST; + //生成请求签名鉴权方式的Header + Map header = EsignHttpHelper.signAndBuildSignAndJsonHeader(eSignAppId, eSignAppSecret, jsonParm, requestType.name(), apiaddr, true); + //发起接口请求 + return EsignHttpHelper.doCommHttp(eSignHost, apiaddr, requestType, jsonParm, header, true); + } + + /** + * 获取文件签名印章位置 + * @return + * @throws EsignInterfacException + */ + public static EsignHttpResponse getPositions(SealSignRecord sealSignRecord) throws EsignInterfacException { + String fileId = sealSignRecord.getFileid(); + String apiaddr = "/v3/files/" + fileId + "/keyword-positions"; + String jsonParm = "{\n" + + " \"keywords\": [\n" + + " \"仲裁员:\",\n" + + " \"(仲裁委员会盖章)\"\n" + + " ]\n" + + "}"; + //请求方法 + EsignRequestType requestType = EsignRequestType.POST; + //生成请求签名鉴权方式的Header + Map header = EsignHttpHelper.signAndBuildSignAndJsonHeader(eSignAppId, eSignAppSecret, jsonParm, requestType.name(), apiaddr, true); + //发起接口请求 + return EsignHttpHelper.doCommHttp(eSignHost, apiaddr, requestType, jsonParm, header, true); + } + + + + +}