Sfoglia il codice sorgente

接口初版提交

9060 5 mesi fa
parent
commit
6865b6991d

+ 99 - 0
src/com/kingdee/eas/custom/esign/tsign/hz/comm/AESUtils.java

@@ -0,0 +1,99 @@
+/*
+ *
+ * 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.kingdee.eas.custom.esign.tsign.hz.comm;
+
+import org.apache.commons.codec.binary.Base64;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.spec.GCMParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * AES对称加密工具类
+ *
+ * @author 澄泓
+ * @date 2019年7月18日
+ */
+public class AESUtils {
+
+    private static final String KEY_ALGORITHM = "AES";
+    // AES/GCM加密算法,不用补位
+    private static final String DEFAULT_CIPHER_ALGORITHM = "AES/GCM/NoPadding";
+
+    /**
+     * AES 加密操作
+     *
+     * @param content   待加密内容
+     * @param AESSecret AES秘钥
+     * @return 返回Base64转码后的加密数据
+     */
+    public static String encrypt(String content, String AESSecret) {
+        try {
+            Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);
+            cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(Base64.decodeBase64(AESSecret), KEY_ALGORITHM));
+            byte[] iv = cipher.getIV();
+            assert iv.length == 12;
+            byte[] encryptData = cipher.doFinal(content.getBytes());
+            assert encryptData.length == content.getBytes().length + 16;
+            byte[] message = new byte[12 + content.getBytes().length + 16];
+            System.arraycopy(iv, 0, message, 0, 12);
+            System.arraycopy(encryptData, 0, message, 12, encryptData.length);
+            return Base64.encodeBase64URLSafeString(message);
+        } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException | IllegalBlockSizeException
+                | BadPaddingException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    /**
+     * AES 解密操作
+     *
+     * @param base64Content
+     * @param AESSecret     AES秘钥
+     * @return
+     */
+    public static String decrypt(String base64Content, String AESSecret) {
+        byte[] content = Base64.decodeBase64(base64Content);
+        if (content.length < 12 + 16)
+            throw new IllegalArgumentException();
+        GCMParameterSpec params = new GCMParameterSpec(128, content, 0, 12);
+        try {
+            Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);
+            cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(Base64.decodeBase64(AESSecret), KEY_ALGORITHM), params);
+            byte[] decryptData = cipher.doFinal(content, 12, content.length - 12);
+            return new String(decryptData);
+        } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException
+                | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    public static void main(String[] args) {
+        // 待加密的字符串
+        String s = "{\"name\":\"张三\",\"idNo\":\"320333xxxxxxx12522\"}";
+        // 秘钥
+        String pass = "D/RA+2esbSbfSVOQsTGlpg==";
+        // 加密
+        String encoded = encrypt(s, pass);
+        System.out.println("加密之前:" + s);
+        System.out.println("加密结果:" + encoded);
+        System.out.println("解密结果:" + decrypt(encoded, pass));
+    }
+}

+ 24 - 0
src/com/kingdee/eas/custom/esign/tsign/hz/comm/EsignCoreSdkInfo.java

@@ -0,0 +1,24 @@
+package com.kingdee.eas.custom.esign.tsign.hz.comm;
+/**
+ * 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;
+    }
+}

+ 372 - 0
src/com/kingdee/eas/custom/esign/tsign/hz/comm/EsignEncryption.java

@@ -0,0 +1,372 @@
+/*
+ *
+ * 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.kingdee.eas.custom.esign.tsign.hz.comm;
+
+import com.kingdee.eas.custom.esign.tsign.hz.exception.EsignException;
+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 请求数据通用处理类
+ * @author 澄泓
+ * @date 2020年10月22日 下午14:25:31
+ * @since JDK1.7
+ */
+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 EsignException {
+        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 EsignException 加密过程中的异常信息
+     */
+    public static String doContentMD5(String str) throws EsignException {
+        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) {
+            EsignException ex = new EsignException("不支持此算法",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 EsignException 加密过程中的异常信息
+     */
+    public static String doSignatureBase64(String message, String secret) throws EsignException {
+        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) {
+            EsignException ex = new EsignException("不支持此算法",e);
+            ex.initCause(e);
+            throw ex;
+        } catch (InvalidKeyException e) {
+            EsignException ex = new EsignException("无效的密钥规范",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 EsignException {
+        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){
+            EsignException ex = new EsignException("无效的密钥规范",e);
+            ex.initCause(e);
+            throw ex;
+        } catch (NoSuchAlgorithmException e) {
+            EsignException ex = new EsignException("不支持此算法",e);
+            ex.initCause(e);
+            throw ex;
+        }catch (Exception e){
+            EsignException ex = new EsignException("hash散列加密算法报错",e);
+            ex.initCause(e);
+            throw ex;
+        }finally {
+            return byteArrayToHexString(rawHmac);
+        }
+
+    }
+
+    /**
+     * MD5加密32位
+     */
+    public static String MD5Digest(String text) throws EsignException {
+        byte[] digest=null;
+        try {
+            MessageDigest md5 = MessageDigest.getInstance("MD5");
+            md5.update(text.getBytes());
+            digest = md5.digest();
+        }catch (NoSuchAlgorithmException e){
+            EsignException ex = new EsignException("不支持此算法",e);
+            ex.initCause(e);
+            throw ex;
+        }finally {
+            return byteArrayToHexString(digest);
+        }
+
+    }
+
+    public static void formDataSort(List<BasicNameValuePair> param) {
+        Collections.sort(param, new Comparator<BasicNameValuePair>() {
+            @Override
+            public int compare(BasicNameValuePair o1, BasicNameValuePair o2) {
+                Comparator<Object> 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 EsignException {
+
+        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<Object, Object> queryParamsMap = new HashMap<Object, Object>();
+        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 EsignException(msg);
+            }
+            queryParamsMap.put(key, value);
+        }
+
+        ArrayList<String> queryMapKeys = new ArrayList<String>();
+        for (Map.Entry<Object, Object> entry : queryParamsMap.entrySet()) {
+            queryMapKeys.add((String) entry.getKey());
+        }
+        // 按照字段名的 ASCII 码从小到大排序(字典排序)
+        Collections.sort(queryMapKeys, new Comparator<String>() {
+            @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 EsignException
+     */
+    public static ArrayList<BasicNameValuePair> getQuery(String apiUrl) throws EsignException {
+        ArrayList<BasicNameValuePair> BasicNameValuePairList = new ArrayList<>();
+
+        if (!apiUrl.contains("?")) {
+            return BasicNameValuePairList;
+        }
+
+        int queryIndex = apiUrl.indexOf("\\?");
+        String apiUrlQuery = apiUrl.substring(queryIndex,apiUrl.length());
+
+        // 请求URL中Query参数转成Map
+        Map<Object, Object> queryParamsMap = new HashMap<Object, Object>();
+        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 EsignException(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();
+    }
+
+}

+ 55 - 0
src/com/kingdee/eas/custom/esign/tsign/hz/comm/EsignFileBean.java

@@ -0,0 +1,55 @@
+package com.kingdee.eas.custom.esign.tsign.hz.comm;
+
+import com.kingdee.eas.custom.esign.tsign.hz.exception.EsignException;
+
+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 EsignException {
+            this.filePath=filePath;
+            this.fileContentMD5 = FileTransformation.getFileContentMD5(filePath);
+            File file = new File(filePath);
+            if (!file.exists()) {
+                throw new EsignException("文件不存在");
+            }
+            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 EsignException
+     */
+    public byte[] getFileBytes() throws EsignException {
+        return FileTransformation.fileToBytes(filePath);
+    }
+}

+ 482 - 0
src/com/kingdee/eas/custom/esign/tsign/hz/comm/EsignHttpCfgHelper.java

@@ -0,0 +1,482 @@
+/*
+ *
+ * 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.kingdee.eas.custom.esign.tsign.hz.comm;
+import com.kingdee.eas.custom.esign.tsign.hz.enums.EsignRequestType;
+import com.kingdee.eas.custom.esign.tsign.hz.exception.EsignException;
+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请求 辅助类
+ * @author 澄泓
+ * @since JDK1.7
+ */
+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 EsignException
+	 * @author 澄泓
+	 */
+	public static EsignHttpResponse sendHttp(EsignRequestType reqType, String httpUrl, Map<String, String> headers, Object param, boolean debug)
+			throws EsignException {
+		HttpRequestBase reqBase=null;
+		if(httpUrl.startsWith("http")){
+			reqBase=reqType.getHttpType(httpUrl);
+		}else{
+			throw new EsignException("请求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<? extends NameValuePair>) param));
+		}
+		httpClient = getHttpClient();
+		config(reqBase);
+
+		//设置请求头
+		if(headers != null &&headers.size()>0) {
+			for(Map.Entry<String, String> 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 EsignException("服务器丢失了",e);
+		} catch (SSLHandshakeException e){
+			String msg = MessageFormat.format("SSL握手异常", e);
+			EsignException ex = new EsignException(msg, e);
+			throw ex;
+		} catch (UnknownHostException e){
+			EsignException ex = new EsignException("服务器找不到", e);
+			ex.initCause(e);
+			throw ex;
+		} catch(ConnectTimeoutException e){
+			EsignException ex = new EsignException("连接超时", e);
+			ex.initCause(e);
+			throw ex;
+		} catch(SSLException e){
+			EsignException ex = new EsignException("SSL异常",e);
+			ex.initCause(e);
+			throw ex;
+		} catch (ClientProtocolException e) {
+			EsignException ex = new EsignException("请求头异常",e);
+			ex.initCause(e);
+			throw ex;
+		} catch (IOException e) {
+			EsignException ex = new EsignException("网络请求失败",e);
+			ex.initCause(e);
+			throw ex;
+		} finally {
+			if(res != null) {
+				try {
+					res.close();
+				} catch (IOException e) {
+					EsignException ex = new EsignException("--->>关闭请求响应失败",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 EsignException {
+		ConnectionSocketFactory plainsf = PlainConnectionSocketFactory.getSocketFactory();
+		LayeredConnectionSocketFactory sslsf = SSLConnectionSocketFactory.getSocketFactory();
+		if(!SSL_VERIFY){
+            sslsf=sslConnectionSocketFactory();
+        }
+
+		Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>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 EsignException {
+        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){
+            EsignException ex = new EsignException("忽略域名校验失败",e);
+            ex.initCause(e);
+            throw ex;
+        }
+
+    }
+
+	/**
+	 * @description 获取单例HttpClient
+	 *
+	 * @return
+	 * @author 澄泓
+	 */
+	private static synchronized CloseableHttpClient getHttpClient() throws EsignException {
+		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----------------------------------------------
+
+
+}

+ 171 - 0
src/com/kingdee/eas/custom/esign/tsign/hz/comm/EsignHttpHelper.java

@@ -0,0 +1,171 @@
+package com.kingdee.eas.custom.esign.tsign.hz.comm;
+
+import com.kingdee.eas.custom.esign.tsign.hz.enums.EsignHeaderConstant;
+import com.kingdee.eas.custom.esign.tsign.hz.enums.EsignRequestType;
+import com.kingdee.eas.custom.esign.tsign.hz.exception.EsignException;
+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 EsignException
+	 * @author 澄泓
+	 */
+	public static EsignHttpResponse doCommHttp(String host, String url,EsignRequestType reqType, Object paramStr ,Map<String,String> httpHeader,boolean debug) throws EsignException {
+		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 EsignException
+	 * @author 澄泓
+	 */
+	public static EsignHttpResponse doUploadHttp( String uploadUrl,EsignRequestType reqType,byte[] param, String fileContentMd5,
+												 String contentType, boolean debug) throws EsignException {
+		Map<String, String> 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<String, String> buildSignAndJsonHeader(String projectId,String contentMD5,String accept,String contentType,String authMode) {
+
+		Map<String, String> 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<String,String> signAndBuildSignAndJsonHeader(String projectId, String secret,String paramStr,String httpMethod,String url,boolean debug) throws EsignException {
+		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 EsignException(String.format("不支持的请求方法%s",httpMethod));
+		}
+		//构造一个初步的请求头
+		Map<String, String> 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<String, String> buildTokenAndJsonHeader(String appid,String token) {
+		Map<String, String> 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<String, String> buildFormDataHeader(String appid) {
+		Map<String, String> 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<String, String> buildUploadHeader(String fileContentMd5, String contentType) {
+		Map<String, String> header = new HashMap<>();
+		header.put("Content-MD5", fileContentMd5);
+		header.put("Content-Type", contentType);
+
+		return header;
+	}
+
+	// ------------------------------私有方法end----------------------------------------------
+}

+ 27 - 0
src/com/kingdee/eas/custom/esign/tsign/hz/comm/EsignHttpResponse.java

@@ -0,0 +1,27 @@
+package com.kingdee.eas.custom.esign.tsign.hz.comm;
+/**
+ * 网络请求的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;
+    }
+}

+ 284 - 0
src/com/kingdee/eas/custom/esign/tsign/hz/comm/FileTransformation.java

@@ -0,0 +1,284 @@
+/*
+ *
+ * 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.kingdee.eas.custom.esign.tsign.hz.comm;
+
+import com.kingdee.eas.custom.esign.tsign.hz.exception.EsignException;
+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;
+
+/**
+ * @author 澄泓
+ * @version JDK1.7
+ * @description 文件转换类
+ * @date 2020/10/26 10:47
+ */
+public class FileTransformation {
+
+    /**
+     * 传入本地文件路径转二进制byte
+     *
+     * @param srcFilePath 本地文件路径
+     * @return
+     * @throws EsignException
+     */
+    public static byte[] fileToBytes(String srcFilePath) throws EsignException {
+        return getBytes(srcFilePath);
+    }
+
+    /**
+     * 图片转base64
+     *
+     * @param filePath 本地文件路径
+     * @return
+     * @throws EsignException
+     */
+    public static String fileToBase64(String filePath) throws EsignException {
+        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 EsignException {
+        System.out.println(getFileContentMD5("D:\\文档\\PLT2022-02124CT.pdf"));
+    }
+
+    /***
+     * 计算文件内容的Content-MD5
+     * @param filePath 文件路径
+     * @return
+     */
+    public static String getFileContentMD5(String filePath) throws EsignException {
+        // 获取文件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 EsignException {
+        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) {
+            EsignException ex = new EsignException("获取文件流异常", e);
+            ex.initCause(e);
+            throw ex;
+        } finally {
+            try {
+                if (fis != null) {
+                    fis.close();
+                }
+                if (fileOutputStream != null) {
+                    fileOutputStream.close();
+                }
+            } catch (IOException e) {
+                EsignException ex = new EsignException("关闭文件流异常", e);
+                ex.initCause(e);
+                throw ex;
+            }
+        }
+        return true;
+    }
+
+
+    /**
+     * 网络文件转二进制MD5数组并获取文件大小
+     *
+     * @param fileUrl 网络文件地址url
+     * @return
+     */
+    public static Map fileUrlToBytes(String fileUrl) throws EsignException {
+        HashMap<String, Object> map = new HashMap<String, Object>();
+        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) {
+            EsignException ex = new EsignException("获取文件流异常", e);
+            ex.initCause(e);
+            throw ex;
+        } catch (NoSuchAlgorithmException e) {
+            EsignException ex = new EsignException("文件计算异常", e);
+            ex.initCause(e);
+            throw ex;
+        }
+        return map;
+    }
+
+    /***
+     * 获取文件MD5的二进制数组(128位)
+     * @param filePath
+     * @return
+     * @throws EsignException
+     */
+    public static byte[] getFileMD5Bytes128(String filePath) throws EsignException {
+        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) {
+            EsignException ex = new EsignException("文件找不到", e);
+            ex.initCause(e);
+            throw ex;
+        } catch (NoSuchAlgorithmException e) {
+            EsignException ex = new EsignException("不支持此算法", e);
+            ex.initCause(e);
+            throw ex;
+        } catch (IOException e) {
+            EsignException ex = new EsignException("输入流或输出流异常", e);
+            ex.initCause(e);
+            throw ex;
+        } finally {
+            if (fis != null) {
+                try {
+                    fis.close();
+                } catch (IOException e) {
+                    EsignException ex = new EsignException("关闭文件输入流失败", e);
+                    ex.initCause(e);
+                    throw ex;
+                }
+            }
+        }
+        return md5Bytes;
+    }
+
+    /**
+     * @param path
+     * @return
+     * @throws EsignException
+     * @description 根据文件路径,获取文件base64
+     * @author 宫清
+     * @date 2019年7月21日 下午4:22:08
+     */
+    public static String getBase64Str(String path) throws EsignException {
+        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) {
+            EsignException ex = new EsignException("获取文件输入流失败", e);
+            ex.initCause(e);
+            throw ex;
+        } finally {
+            if (is != null) {
+                try {
+                    is.close();
+                } catch (IOException e) {
+                    EsignException ex = new EsignException("关闭文件输入流失败", 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 EsignException
+     * @description 获取文件字节流
+     * @date 2019年7月10日 上午9:17:00
+     * @author 宫清
+     */
+    public static byte[] getBytes(String filePath) throws EsignException {
+        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) {
+            EsignException ex = new EsignException("获取文件字节流失败", e);
+            ex.initCause(e);
+            throw ex;
+        } finally {
+            if (fis != null) {
+                try {
+                    fis.close();
+                } catch (IOException e) {
+                    EsignException ex = new EsignException("关闭文件字节流失败", e);
+                    ex.initCause(e);
+                    throw ex;
+                }
+            }
+        }
+        return buffer;
+    }
+}

+ 26 - 0
src/com/kingdee/eas/custom/esign/tsign/hz/enums/EsignHeaderConstant.java

@@ -0,0 +1,26 @@
+package com.kingdee.eas.custom.esign.tsign.hz.enums;
+/**
+ * @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;
+    }
+}

+ 39 - 0
src/com/kingdee/eas/custom/esign/tsign/hz/enums/EsignRequestType.java

@@ -0,0 +1,39 @@
+package com.kingdee.eas.custom.esign.tsign.hz.enums;
+
+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);
+}

+ 36 - 0
src/com/kingdee/eas/custom/esign/tsign/hz/exception/EsignException.java

@@ -0,0 +1,36 @@
+package com.kingdee.eas.custom.esign.tsign.hz.exception;
+
+/**
+ * description 自定义全局异常
+ * @author 澄泓
+ * datetime 2019年7月1日上午10:43:24
+ */
+public class EsignException extends Exception {
+
+	private static final long serialVersionUID = 4359180081622082792L;
+	private Exception e;
+
+	public EsignException(String msg) {
+		super(msg);
+	}
+
+	public EsignException(String msg, Throwable cause) {
+		super(msg,cause);
+	}
+
+	public EsignException(){
+
+	}
+
+	public Exception getE() {
+		return e;
+	}
+
+	public void setE(Exception e) {
+		this.e = e;
+	}
+
+
+
+
+}

+ 55 - 0
src/com/kingdee/eas/custom/esign/util/EsignConfig.java

@@ -0,0 +1,55 @@
+package com.kingdee.eas.custom.esign.util;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+public class EsignConfig {
+
+    private Properties propt = new Properties();//共用参数
+
+    private static EsignConfig instance = new EsignConfig();
+    public static EsignConfig getInstance() {
+        return instance;
+    }
+    // 应用ID
+    public String getEsignAppId() {
+        return this.propt.getProperty("EsignAppId");
+    }
+    // 应用密钥
+    public String getEsignAppSecret() {
+        return this.propt.getProperty("EsignAppSecret");
+    }
+    // e签宝接口调用域名(模拟环境)
+    //"https://smlopenapi.esign.cn";
+    // e签宝接口调用域名(正式环境)
+    //"https://openapi.esign.cn";
+    public String getEsignHost() {
+        return this.propt.getProperty("EsignHost");
+    }
+    public String get(String key) {
+        return this.propt.getProperty(key);
+    }
+    private EsignConfig() {
+        this.initConfig(System.getProperty("EAS_HOME") + "/server/properties/esign/esignConfig.properties");
+    }
+    public void initConfig(String filePath) {
+        try {
+            this.propt.load(new FileInputStream(filePath));
+        }catch (IOException e){
+            e.printStackTrace();
+
+        }
+    }
+    public Map<String,String> getConfig() {
+        Map<String,String> map = new HashMap<>();
+        Set<Map.Entry<Object, Object>> set = this.propt.entrySet();
+        for(Map.Entry<Object, Object> entry : set){
+            map.put((String)entry.getKey(),(String)entry.getValue());
+        }
+        return map;
+    }
+}

+ 197 - 0
src/com/kingdee/eas/custom/esign/util/EsignHttpUtil.java

@@ -0,0 +1,197 @@
+package com.kingdee.eas.custom.esign.util;
+
+import com.kingdee.eas.custom.esign.tsign.hz.comm.EsignHttpHelper;
+import com.kingdee.eas.custom.esign.tsign.hz.comm.EsignHttpResponse;
+import com.kingdee.eas.custom.esign.tsign.hz.enums.EsignRequestType;
+import com.kingdee.eas.custom.esign.tsign.hz.exception.EsignException;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.http.client.utils.URIBuilder;
+
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.Map;
+
+/**
+ * description: EsignHttpUtil <br>
+ * date: 17/11/2025 下午 4:26 <br>
+ * author: lhbj <br>
+ * version: 1.0 <br>
+ */
+public class EsignHttpUtil {
+    /**
+     * 基础请求接口
+     *
+     * @param url         请求地址
+     * @param jsonParm    JSON字符串参数
+     * @param requestType 请求类型 POST/GET
+     * @param debug       输出日志
+     * @return
+     * @throws EsignException
+     */
+    public static EsignHttpResponse doCommHttp(String url, String jsonParm, EsignRequestType requestType, Boolean debug) throws EsignException {
+        //
+        //生成签名鉴权方式的的header
+        Map<String, String> header = EsignHttpHelper.signAndBuildSignAndJsonHeader(EsignConfig.getInstance().getEsignAppId(),
+                EsignConfig.getInstance().getEsignAppSecret(), jsonParm, requestType.name(), url, debug);
+        //发起接口请求
+        return EsignHttpHelper.doCommHttp(EsignConfig.getInstance().getEsignHost(), url, requestType, jsonParm, header, debug);
+    }
+
+    /**
+     * POST请求接口
+     *
+     * @param url      请求地址
+     * @param jsonParm JSON字符串参数
+     * @param debug    输出日志
+     * @return
+     * @throws EsignException
+     */
+    public static EsignHttpResponse POST(String url, String jsonParm, Boolean debug) throws EsignException {
+        //请求方法
+        EsignRequestType requestType = EsignRequestType.POST;
+        return doCommHttp(url, jsonParm, requestType, debug);
+    }
+
+    public static EsignHttpResponse POST(String url, String jsonParm) throws EsignException {
+        return POST(url, jsonParm, false);
+    }
+
+    /**
+     * GET请求接口
+     *
+     * @param url      请求地址
+     * @param jsonParm JSON字符串参数
+     * @param debug    输出日志
+     * @return
+     * @throws EsignException
+     */
+    public static EsignHttpResponse GET(String url, String jsonParm, Boolean debug) throws EsignException {
+        //请求方法
+        EsignRequestType requestType = EsignRequestType.GET;
+        return doCommHttp(url, jsonParm, requestType, debug);
+    }
+
+    public static EsignHttpResponse GET(String url, String jsonParm) throws EsignException {
+        return GET(url, jsonParm, false);
+    }
+
+    /**
+     * 查询合同模板中控件详情
+     * 参考文档:https://open.esign.cn/doc/opendoc/pdf-sign3/aoq509
+     * 接口地址:https://{host}/v3/doc-templates/{docTemplateId}
+     * 请求方法:GET
+     *
+     * @return
+     * @throws EsignException
+     */
+    public static EsignHttpResponse getDocTemplatesDetailById(String id) throws EsignException {
+        String apiaddr = EsignConfig.getInstance().get("docTemplatesDetailById") + id;
+        String jsonParm = null;
+        return GET(apiaddr, jsonParm);
+    }
+
+    /**
+     * 填写模板生成文件
+     * 参考文档:https://open.esign.cn/doc/opendoc/pdf-sign3/mv8a3i
+     * 接口地址:https://{host}/v3/files/create-by-doc-template
+     * 请求方法:POST
+     *
+     * @param json 注意事项:componentId与componentKey两个字段不能同时传值,只能一个有值
+     * @return
+     * @throws EsignException
+     */
+    public static EsignHttpResponse createByDocTemplate(String json) throws EsignException {
+        String apiaddr = EsignConfig.getInstance().get("createByDocTemplate");
+        return POST(apiaddr, json);
+    }
+
+    /**
+     * 查询机构认证信息
+     * 参考文档:https://open.esign.cn/doc/opendoc/auth3/xxz4tc
+     * 接口地址:https://{host}/v3/organizations/identity-info
+     * 请求方法:GET
+     * 注意事项:
+     * 入参中orgId、orgName和orgIDCardNum三个参数只选择一个传入即可查询机构认证信息。
+     * 查询优先级为 orgId > orgName > orgIDCardNum。
+     *
+     * @param orgId
+     * @param orgName
+     * @param orgIDCardNum
+     * @param psnIDCardType 组织机构证件类型(传orgIDCardNum时,该参数为必传)
+     *                      CRED_ORG_USCC - 统一社会信用代码
+     *                      CRED_ORG_REGCODE - 工商注册号
+     * @return
+     * @throws EsignException
+     */
+    public static EsignHttpResponse getOrgIdentity_info(String orgId, String orgName,String orgIDCardNum,String psnIDCardType) throws EsignException, URISyntaxException {
+        String apiaddr = EsignConfig.getInstance().get("organizations.identity-info");
+        if (StringUtils.isBlank(orgId)&&StringUtils.isBlank(orgName)) {
+            if (StringUtils.isNotBlank(orgIDCardNum)&&StringUtils.isBlank(psnIDCardType)) {
+                throw new EsignException("传orgIDCardNum时,psnIDCardType参数为必传");
+            }else {
+                throw new EsignException("orgId与orgName与orgIDCardNum不能都为空");
+            }
+        }
+        URIBuilder uriBuilder = new URIBuilder(apiaddr);
+        if (StringUtils.isNotBlank(orgId)) {
+            uriBuilder.addParameter("orgId", orgId);
+        }
+        if (StringUtils.isNotBlank(orgName)) {
+            uriBuilder.addParameter("orgName", orgName);
+        }
+        if (StringUtils.isNotBlank(orgIDCardNum)&&StringUtils.isNotBlank(psnIDCardType)) {
+            uriBuilder.addParameter("orgIDCardNum", orgIDCardNum);
+            uriBuilder.addParameter("psnIDCardType", psnIDCardType);
+        }
+        URI uri = uriBuilder.build(); // 自动编码
+        apiaddr = uri.toString();
+        return GET(apiaddr, null);
+    }
+    /**
+     * 查询个人认证信息
+     * 参考文档:https://open.esign.cn/doc/opendoc/auth3/xxz4tc
+     * 接口地址:https://{host}/v3/persons/identity-info
+     * 请求方法:GET
+     * 注意事项:
+     * 入参中psnId、psnAccount和psnIDCardNum三个参数只选择一个传入即可查询个人的认证信息。
+     * 查询优先级为 psnId > psnAccount > psnIDCardNum
+     * @param psnId
+     * @param psnAccount
+     * @param psnIDCardNum
+     * @param psnIDCardType 个人证件号类型 (传psnIDCardNum时,证件类型为必传项)
+     *                CRED_PSN_CH_IDCARD - 中国大陆居民身份证
+     *                CRED_PSN_CH_HONGKONG - 香港来往大陆通行证
+     *                CRED_PSN_CH_MACAO - 澳门来往大陆通行证
+     *                CRED_PSN_CH_TWCARD - 台湾来往大陆通行证
+     *                CRED_PSN_PASSPORT - 护照
+
+     * @return
+     * @throws EsignException
+     */
+    public static EsignHttpResponse getPersonIdentity_info(String psnId, String psnAccount,String psnIDCardNum,String psnIDCardType) throws EsignException, URISyntaxException {
+        String apiaddr = EsignConfig.getInstance().get("persons.identity-info");
+        if (StringUtils.isBlank(psnId)&&StringUtils.isBlank(psnAccount)) {
+            if (StringUtils.isNotBlank(psnIDCardNum)&&StringUtils.isBlank(psnIDCardType)) {
+                throw new EsignException("传orgIDCardNum时,psnIDCardType参数为必传");
+            }else {
+                throw new EsignException("orgId与orgName与orgIDCardNum不能都为空");
+            }
+        }
+        URIBuilder uriBuilder = new URIBuilder(apiaddr);
+        if (StringUtils.isNotBlank(psnId)) {
+            uriBuilder.addParameter("psnId", psnId);
+        }
+        if (StringUtils.isNotBlank(psnAccount)) {
+            uriBuilder.addParameter("psnAccount", psnAccount);
+        }
+        if (StringUtils.isNotBlank(psnIDCardNum)&&StringUtils.isNotBlank(psnIDCardType)) {
+            uriBuilder.addParameter("orgIDCardNum", psnIDCardNum);
+            uriBuilder.addParameter("psnIDCardType", psnIDCardType);
+        }
+        URI uri = uriBuilder.build(); // 自动编码
+        apiaddr = uri.toString();
+        return GET(apiaddr, null);
+    }
+}