diff --git a/pom.xml b/pom.xml index 90dc7e9..9da31b1 100644 --- a/pom.xml +++ b/pom.xml @@ -48,7 +48,7 @@ 3.0.2 2.11.0 - 1.78 + 1.81 33.1.0-jre 5.2.5 2.17.0 @@ -94,7 +94,12 @@ org.bouncycastle bcprov-jdk18on - ${bcprov-jdk18on.version} + ${bouncycastle-jdk18on.version} + + + org.bouncycastle + bcpkix-jdk18on + ${bouncycastle-jdk18on.version} diff --git a/src/main/java/com/yexuejc/base/constant/DateConsts.java b/src/main/java/com/yexuejc/base/constant/DateConsts.java index 57509d1..9f30617 100644 --- a/src/main/java/com/yexuejc/base/constant/DateConsts.java +++ b/src/main/java/com/yexuejc/base/constant/DateConsts.java @@ -7,15 +7,17 @@ package com.yexuejc.base.constant; * @date 2023/05/17 13:45 */ public class DateConsts { - public static final String BAR = "-"; - public static final CharSequence DATE_KEY_AM = "AM"; - public static final CharSequence DATE_KEY_PM = "PM"; - public static final String DATE_TIMESTAMP_LINUX = "M/dd/yy, h:mm a"; - public static final CharSequence SLASH = "/"; - public static final CharSequence COLON = ":"; - public static final String DATE_YYYY_MM_DD_SLASH = "yyyy/MM/dd"; - public static final CharSequence DATE_KEY_T = "T"; + public static final String DATE_KEY_T = "T"; + public static final String DATE_KEY_AM = "AM"; + public static final String DATE_KEY_PM = "PM"; public static final String DATE_KEY_Z = "Z"; + public static final String DATE_TIMESTAMP_LINUX = "M/dd/yy, h:mm a"; + public static final String DATE_YYYY_MM_DD_SLASH = "yyyy/MM/dd"; public static final String DATE_TIMESTAMP = "yyyy/MM/dd H:m:s"; - public static final String DATE_YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss"; + public static final String DATE_PATTERN = "yyyy-MM-dd"; + public static final String TIME_PATTERN = "HH:mm:ss"; + public static final String DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss"; + public static final String DATE_TIME_MS_PATTERN = "yyyy-MM-dd HH:mm:ss.SSS"; + public static final String ISO_8601_PATTERN = "yyyy-MM-dd'T'HH:mm:ssxxx"; + public static final String ISO_8601_MS_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSxxx"; } diff --git a/src/main/java/com/yexuejc/base/constant/RespsConsts.java b/src/main/java/com/yexuejc/base/constant/RespConsts.java similarity index 96% rename from src/main/java/com/yexuejc/base/constant/RespsConsts.java rename to src/main/java/com/yexuejc/base/constant/RespConsts.java index 4d9539f..c3b128b 100644 --- a/src/main/java/com/yexuejc/base/constant/RespsConsts.java +++ b/src/main/java/com/yexuejc/base/constant/RespConsts.java @@ -8,9 +8,9 @@ package com.yexuejc.base.constant; * @author: maxf * @date: 2017/12/27 16:47 */ -public class RespsConsts { +public class RespConsts { - private RespsConsts() { + private RespConsts() { } /** diff --git a/src/main/java/com/yexuejc/base/constant/SymbolicConstant.java b/src/main/java/com/yexuejc/base/constant/SymbolicConstant.java new file mode 100644 index 0000000..d9a4256 --- /dev/null +++ b/src/main/java/com/yexuejc/base/constant/SymbolicConstant.java @@ -0,0 +1,21 @@ +package com.yexuejc.base.constant; + +/** + * 常用符号常量 + * + * @author maxiaofeng + * @date 2025/8/25 15:15 + */ +public class SymbolicConstant { + /**常量:换行符*/ + public static final String NEW_LINE = "\n"; + public static final String EMPTY = ""; + public static final String EQUAL = "="; + public static final String COMMA = ","; + public static final String BAR = "-"; + public static final String SLASH = "/"; + public static final String COLON = ":"; + + private SymbolicConstant() { + } +} diff --git a/src/main/java/com/yexuejc/base/converter/LocalDateDeserializer.java b/src/main/java/com/yexuejc/base/converter/LocalDateDeserializer.java index 72be746..347e31f 100644 --- a/src/main/java/com/yexuejc/base/converter/LocalDateDeserializer.java +++ b/src/main/java/com/yexuejc/base/converter/LocalDateDeserializer.java @@ -9,6 +9,7 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.yexuejc.base.constant.DateConsts; +import com.yexuejc.base.constant.SymbolicConstant; import com.yexuejc.base.util.StrUtil; /** @@ -26,16 +27,16 @@ public class LocalDateDeserializer extends JsonDeserializer { if (StrUtil.isEmpty(timeString)) { return null; } - if (timeString.contains(DateConsts.BAR)) { + if (timeString.contains(SymbolicConstant.BAR)) { return LocalDate.parse(timeString, DateTimeFormatter.ISO_DATE); } else if (timeString.contains(DateConsts.DATE_KEY_AM) || timeString.contains(DateConsts.DATE_KEY_PM)) { return LocalDate.parse(timeString, DateTimeFormatter.ofPattern(DateConsts.DATE_TIMESTAMP_LINUX, Locale.ENGLISH)); - } else if (timeString.contains(DateConsts.SLASH) && timeString.contains(DateConsts.COLON)) { + } else if (timeString.contains(SymbolicConstant.SLASH) && timeString.contains(SymbolicConstant.COLON)) { return LocalDate.parse(timeString.substring(0, 10), DateTimeFormatter.ofPattern(DateConsts.DATE_YYYY_MM_DD_SLASH)); - } else if (timeString.contains(DateConsts.SLASH)) { + } else if (timeString.contains(SymbolicConstant.SLASH)) { return LocalDate.parse(timeString, DateTimeFormatter.ofPattern(DateConsts.DATE_YYYY_MM_DD_SLASH)); } else { return LocalDate.parse(timeString, DateTimeFormatter.BASIC_ISO_DATE); diff --git a/src/main/java/com/yexuejc/base/converter/LocalDateTimeDeserializer.java b/src/main/java/com/yexuejc/base/converter/LocalDateTimeDeserializer.java index e1bd9a2..6c7bd1c 100644 --- a/src/main/java/com/yexuejc/base/converter/LocalDateTimeDeserializer.java +++ b/src/main/java/com/yexuejc/base/converter/LocalDateTimeDeserializer.java @@ -9,6 +9,7 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.yexuejc.base.constant.DateConsts; +import com.yexuejc.base.constant.SymbolicConstant; import com.yexuejc.base.util.StrUtil; /** @@ -33,11 +34,11 @@ public class LocalDateTimeDeserializer extends JsonDeserializer { DateTimeFormatter.ofPattern(DateConsts.DATE_TIMESTAMP_LINUX, Locale.ENGLISH)); } else if (timeString.endsWith(DateConsts.DATE_KEY_Z)) { return LocalDateTime.parse(timeString, DateTimeFormatter.ISO_INSTANT); - } else if (timeString.contains(DateConsts.SLASH)) { + } else if (timeString.contains(SymbolicConstant.SLASH)) { return LocalDateTime.parse(timeString, DateTimeFormatter.ofPattern(DateConsts.DATE_TIMESTAMP)); } else { return LocalDateTime.parse(timeString, - DateTimeFormatter.ofPattern(DateConsts.DATE_YYYY_MM_DD_HH_MM_SS)); + DateTimeFormatter.ofPattern(DateConsts.DATE_TIME_PATTERN)); } } diff --git a/src/main/java/com/yexuejc/base/converter/TimestampDeserializer.java b/src/main/java/com/yexuejc/base/converter/TimestampDeserializer.java index 4a2e8b7..eb623bd 100644 --- a/src/main/java/com/yexuejc/base/converter/TimestampDeserializer.java +++ b/src/main/java/com/yexuejc/base/converter/TimestampDeserializer.java @@ -10,6 +10,7 @@ import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.yexuejc.base.constant.DateConsts; +import com.yexuejc.base.constant.SymbolicConstant; import com.yexuejc.base.util.StrUtil; /** @@ -34,12 +35,12 @@ public class TimestampDeserializer extends JsonDeserializer { DateTimeFormatter.ofPattern(DateConsts.DATE_TIMESTAMP_LINUX, Locale.ENGLISH))); } else if (timeString.endsWith(DateConsts.DATE_KEY_Z)) { return Timestamp.valueOf(LocalDateTime.parse(timeString, DateTimeFormatter.ISO_INSTANT)); - } else if (timeString.contains(DateConsts.SLASH)) { + } else if (timeString.contains(SymbolicConstant.SLASH)) { return Timestamp.valueOf( LocalDateTime.parse(timeString, DateTimeFormatter.ofPattern(DateConsts.DATE_TIMESTAMP))); } else { return Timestamp.valueOf(LocalDateTime.parse(timeString, - DateTimeFormatter.ofPattern(DateConsts.DATE_YYYY_MM_DD_HH_MM_SS))); + DateTimeFormatter.ofPattern(DateConsts.DATE_TIME_PATTERN))); } } diff --git a/src/main/java/com/yexuejc/base/encrypt/RSA.java b/src/main/java/com/yexuejc/base/encrypt/RSA.java index cbf84c7..9772451 100644 --- a/src/main/java/com/yexuejc/base/encrypt/RSA.java +++ b/src/main/java/com/yexuejc/base/encrypt/RSA.java @@ -1,20 +1,36 @@ package com.yexuejc.base.encrypt; -import org.apache.commons.codec.binary.Base64; -import org.bouncycastle.jce.provider.BouncyCastleProvider; - -import javax.crypto.Cipher; -import javax.crypto.NoSuchPaddingException; -import java.io.*; -import java.security.*; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.Key; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.Security; +import java.security.Signature; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; +import java.util.Arrays; import java.util.HashMap; import java.util.Map; -import java.util.logging.Logger; +import javax.crypto.Cipher; +import javax.crypto.NoSuchPaddingException; + +import com.yexuejc.base.constant.SymbolicConstant; +import com.yexuejc.base.exception.BaseException; +import com.yexuejc.base.http.RequestHeader; +import com.yexuejc.base.util.StrUtil; +import org.apache.commons.codec.binary.Base64; +import org.bouncycastle.jce.provider.BouncyCastleProvider; /** * RSA加解密 配置模式 @@ -25,8 +41,8 @@ import java.util.logging.Logger; * @date: 2018/5/15 14:39 */ public class RSA { - private static Logger log = Logger.getLogger(RSA.class.getName()); - public static final String CHARSET = "UTF-8"; + private static System.Logger log = System.getLogger(RSA.class.getName()); + public static final Charset CHARSET = StandardCharsets.UTF_8; public static final String RSA_ALGORITHM = "RSA"; /** * 加密方式 @@ -36,12 +52,12 @@ public class RSA { * RSA/ECB/PKCS1Padding 改变加密结果 * */ - public static String RSA_ALGORITHM_ECB = "RSA"; + public static final String RSA_ALGORITHM_ECB = "RSA"; /** * 是否每次改变加密结果 * 只针对于RSA_ALGORITHM_ECB = "RSA"有效 */ - public static boolean isChangeSign = true; + public static final boolean isChangeSign = true; /** * 是否使用 Base64URL 方式加密 默认正常加密 *
@@ -66,7 +82,7 @@ public class RSA {
     /**
      * 签名算法
      */
-    public static SignAlgorithm signAlgorithm = SignAlgorithm.SHA1withRSA;
+    public static final SignAlgorithm signAlgorithm = SignAlgorithm.SHA256withRSA;
 
     /**
      * 生成密钥对
@@ -103,8 +119,8 @@ public class RSA {
         Key publicKey = keyPair.getPublic();
         //得到私钥
         Key privateKey = keyPair.getPrivate();
-        String privateKeyStr = null;
-        String publicKeyStr = null;
+        String privateKeyStr;
+        String publicKeyStr;
         if (encodeBase64URLSafe) {
             publicKeyStr = Base64.encodeBase64URLSafeString(publicKey.getEncoded());
             privateKeyStr = Base64.encodeBase64URLSafeString(privateKey.getEncoded());
@@ -112,12 +128,33 @@ public class RSA {
             publicKeyStr = Base64.encodeBase64String(publicKey.getEncoded());
             privateKeyStr = Base64.encodeBase64String(privateKey.getEncoded());
         }
-        Map keyPairMap = new HashMap(2);
+        Map keyPairMap = new HashMap<>(2);
         keyPairMap.put("publicKey", publicKeyStr);
         keyPairMap.put("privateKey", privateKeyStr);
         return keyPairMap;
     }
 
+    /**
+     * 生成密钥文件
+     * 

会在指定路径下生成 + * private.keypublic.key + *

+ * + * @param filePath 密钥文件路径 + * @throws BaseException + */ + public static void initKey4File(String filePath) throws BaseException { + Map keys = initKeys(2048, false); + try { + Files.write(Paths.get(filePath, "private.key"), StrUtil.chunkString(keys.get("privateKey"), 64) + .getBytes(CHARSET)); + Files.write(Paths.get(filePath, "public.key"), StrUtil.chunkString(keys.get("publicKey"), 64) + .getBytes(CHARSET)); + } catch (IOException e) { + throw new BaseException(e, "生成密钥文件失败"); + } + } + /** * 得到公钥 * @@ -171,9 +208,11 @@ public class RSA { Cipher cipher = getCipher(); cipher.init(Cipher.ENCRYPT_MODE, publicKey); if (encodeBase64URLSafe) { - return Base64.encodeBase64URLSafeString(rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE, data.getBytes(CHARSET), publicKey.getModulus().bitLength())); + return Base64.encodeBase64URLSafeString(rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE, data.getBytes(CHARSET), publicKey.getModulus() + .bitLength())); } else { - return Base64.encodeBase64String(rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE, data.getBytes(CHARSET), publicKey.getModulus().bitLength())); + return Base64.encodeBase64String(rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE, data.getBytes(CHARSET), publicKey.getModulus() + .bitLength())); } } catch (Exception e) { throw new RuntimeException("加密字符串[" + data + "]时遇到异常", e); @@ -191,7 +230,8 @@ public class RSA { try { Cipher cipher = getCipher(); cipher.init(Cipher.DECRYPT_MODE, privateKey); - return new String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, Base64.decodeBase64(data), privateKey.getModulus().bitLength()), CHARSET); + return new String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, Base64.decodeBase64(data), privateKey.getModulus() + .bitLength()), CHARSET); } catch (Exception e) { throw new RuntimeException("解密字符串[" + data + "]时遇到异常", e); } @@ -222,9 +262,11 @@ public class RSA { Cipher cipher = getCipher(); cipher.init(Cipher.ENCRYPT_MODE, privateKey); if (encodeBase64URLSafe) { - return Base64.encodeBase64URLSafeString(rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE, data.getBytes(CHARSET), privateKey.getModulus().bitLength())); + return Base64.encodeBase64URLSafeString(rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE, data.getBytes(CHARSET), privateKey.getModulus() + .bitLength())); } else { - return Base64.encodeBase64String(rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE, data.getBytes(CHARSET), privateKey.getModulus().bitLength())); + return Base64.encodeBase64String(rsaSplitCodec(cipher, Cipher.ENCRYPT_MODE, data.getBytes(CHARSET), privateKey.getModulus() + .bitLength())); } } catch (Exception e) { throw new RuntimeException("加密字符串[" + data + "]时遇到异常", e); @@ -243,7 +285,8 @@ public class RSA { try { Cipher cipher = getCipher(); cipher.init(Cipher.DECRYPT_MODE, publicKey); - return new String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, Base64.decodeBase64(data), publicKey.getModulus().bitLength()), CHARSET); + return new String(rsaSplitCodec(cipher, Cipher.DECRYPT_MODE, Base64.decodeBase64(data), publicKey.getModulus() + .bitLength()), CHARSET); } catch (Exception e) { throw new RuntimeException("解密字符串[" + data + "]时遇到异常", e); } @@ -304,7 +347,7 @@ public class RSA { private static Signature signature; /** - * 私钥签名:默认算法SHA1withRSA + * 私钥签名:默认算法SHA256withRSA *

* 签名算法 {@link SignAlgorithm} *

@@ -313,48 +356,38 @@ public class RSA { * @param privateKey 签名私钥 * @param base64URLSafe 是否生成 base64URL 格式的密钥:默认false * @return - * @throws NoSuchAlgorithmException + * @throws BaseException */ - public static String sign(String plaintext, RSAPrivateKey privateKey, boolean base64URLSafe) throws NoSuchAlgorithmException { + public static String sign(String plaintext, RSAPrivateKey privateKey, boolean base64URLSafe) throws BaseException { encodeBase64URLSafe = base64URLSafe; return sign(plaintext, privateKey); } /** - * 私钥签名:默认算法SHA1withRSA + * 私钥签名:默认算法SHA256withRSA *

* 签名算法 {@link SignAlgorithm} *

* * @param plaintext 签名字符串 * @param privateKey 签名私钥 - * @return - * @throws NoSuchAlgorithmException + * @return 签名串 + * @throws BaseException */ - public static String sign(String plaintext, RSAPrivateKey privateKey) throws NoSuchAlgorithmException { - signature = Signature.getInstance(signAlgorithm.getValue()); - String signBase64Str = ""; - + public static String sign(String plaintext, RSAPrivateKey privateKey) throws BaseException { try { + signature = Signature.getInstance(signAlgorithm.getValue()); + String signBase64Str = ""; signature.initSign(privateKey); - try { - signature.update(plaintext.getBytes(CHARSET)); - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); - throw new RuntimeException("签名字符串[" + plaintext + "]的数据时发生异常", e); - } + signature.update(plaintext.getBytes(CHARSET)); if (encodeBase64URLSafe) { signBase64Str = Base64.encodeBase64URLSafeString(signature.sign()); } else { signBase64Str = Base64.encodeBase64String(signature.sign()); } return signBase64Str; - } catch (InvalidKeyException var6) { - var6.printStackTrace(); - throw new RuntimeException("签名字符串[" + plaintext + "]的数据时发生异常", var6); - } catch (SignatureException var7) { - var7.printStackTrace(); - throw new RuntimeException("签名字符串[" + plaintext + "]的数据时发生异常", var7); + } catch (Exception e) { + throw new BaseException(e, "签名字符串[" + plaintext + "]的数据时发生异常"); } } @@ -364,24 +397,89 @@ public class RSA { * @param plaintext 原串 * @param signStr 签名串 * @param publicKey 公钥 - * @return - * @throws UnsupportedEncodingException + * @return true:校验成功 / false:校验失败 + * @throws BaseException */ - public static boolean verify(String plaintext, String signStr, RSAPublicKey publicKey) throws UnsupportedEncodingException, NoSuchAlgorithmException { - signature = Signature.getInstance(signAlgorithm.getValue()); - boolean isValid = false; + public static boolean verify(String plaintext, String signStr, RSAPublicKey publicKey) throws BaseException { try { + signature = Signature.getInstance(signAlgorithm.getValue()); signature.initVerify(publicKey); signature.update(plaintext.getBytes(CHARSET)); - isValid = signature.verify(Base64.decodeBase64(signStr)); - } catch (InvalidKeyException var6) { - var6.printStackTrace(); - throw new RuntimeException("校验签名字符串[" + plaintext + "]的数据时发生异常", var6); - } catch (SignatureException var7) { - var7.printStackTrace(); - throw new RuntimeException("校验签名字符串[" + plaintext + "]的数据时发生异常", var7); + return signature.verify(Base64.decodeBase64(signStr)); + } catch (Exception e) { + throw new BaseException(e, "校验签名字符串[" + plaintext + "]的数据时发生异常"); } + } - return isValid; + /** + * 公钥校验签名 + *

校验格式:

+ *
+     * [HTTP-METHOD] [Response-URI]
+     * [Client-Id].[Response-Time].[Response-Body]
+     * 
+ * + * @param uri 请求地址 + * @param respHeader 请求响应头 + * @param data 数据 + * @param publicKeyPath 公钥文件地址 + * @return true:校验成功 / false:校验失败 + * @throws BaseException 签名校验异常 + */ + public static boolean verifyByApi(String uri, RequestHeader respHeader, String data, String publicKeyPath) throws BaseException { + try { + // @formatter:off + String signContent = "POST {uri}\n{clientId}.{respTime}.{body}" + .replace("{uri}", uri) + .replace("{clientId}", respHeader.getClientId()) + .replace("{respTime}", respHeader.geRespTime()) + .replace("{body}", data); + // @formatter:on + String sign = respHeader.getSignature(); + if (sign.contains(SymbolicConstant.COMMA)) { + sign = Arrays.stream(sign.split(SymbolicConstant.COMMA)) + .map(s -> s.split(SymbolicConstant.EQUAL)) + .filter(subSplit -> subSplit.length > 1 && RequestHeader.SIGNATURE.equalsIgnoreCase(subSplit[0])) + .map(subSplit -> subSplit[1]) + .findFirst() + .orElse(sign); + } + log.log(System.Logger.Level.DEBUG, "签名内容:=====\n{0}\n=====", signContent); + return verify(signContent, sign, RSA2.getPublicKey(publicKeyPath)); + } catch (Exception e) { + throw new BaseException(e, "校验签名时发生异常"); + } + } + + + /** + * 签名 + *

签名格式:

+ *
+     * [HTTP-METHOD] [Request-URI]
+     * [Client-Id].[Request-Time].[Request-Body]
+     * 
+ * + * @param uri 请求地址 + * @param requestHeader 请求头 + * @param data 数据 + * @param privateKeyPath 私钥文件地址 + * @return 签名串 + * @throws BaseException 签名异常 + */ + public static String signByApi(String uri, RequestHeader requestHeader, String data, String privateKeyPath) throws BaseException { + try { + // @formatter:off + String signContent = "POST {uri}\n{clientId}.{reqTime}.{body}" + .replace("{uri}", uri) + .replace("{clientId}", requestHeader.getClientId()) + .replace("{reqTime}", requestHeader.getReqTime()) + .replace("{body}", data); + // @formatter:on + log.log(System.Logger.Level.DEBUG, "签名内容:=====\n{0}\n=====", signContent); + return sign(signContent, RSA2.getPrivateKeyFromPKCS1(privateKeyPath), true); + } catch (Exception e) { + throw new BaseException(e, "签名时发生异常"); + } } } diff --git a/src/main/java/com/yexuejc/base/encrypt/RSA2.java b/src/main/java/com/yexuejc/base/encrypt/RSA2.java index 68e7099..2cf6d7c 100644 --- a/src/main/java/com/yexuejc/base/encrypt/RSA2.java +++ b/src/main/java/com/yexuejc/base/encrypt/RSA2.java @@ -1,16 +1,41 @@ package com.yexuejc.base.encrypt; -import com.yexuejc.base.util.StrUtil; - -import java.io.*; -import java.security.*; +import java.io.ByteArrayInputStream; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.StringReader; +import java.nio.charset.StandardCharsets; +import java.security.Key; +import java.security.KeyFactory; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.Security; +import java.security.UnrecoverableKeyException; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.Base64; import java.util.Enumeration; +import com.yexuejc.base.exception.BaseException; +import com.yexuejc.base.util.StrUtil; +import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.openssl.PEMKeyPair; +import org.bouncycastle.openssl.PEMParser; +import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; +import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder; +import org.bouncycastle.operator.InputDecryptorProvider; +import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo; + /** * RSA加解密 证书模式 * 依赖 {@link RSA} @@ -22,8 +47,22 @@ import java.util.Enumeration; */ public class RSA2 { - public static final String CHARSET = "UTF-8"; - public static final String RSA_ALGORITHM = "RSA"; + public static final String KEY_RSA = "RSA"; + public static final String KEY_PKCS1 = "PKCS81"; + public static final String KEY_PKCS8 = "PKCS8"; + public static final String KEY_PKCS12 = "PKCS12"; + public static final String KEY_JKS = "JKS"; + public static final String PEM_FILE_START_CE = "-----BEGIN CERTIFICATE-----"; + public static final String PEM_FILE_END_CE = "-----END CERTIFICATE-----"; + public static final String PEM_FILE_START_EN = "-----BEGIN ENCRYPTED PRIVATE KEY-----"; + public static final String PEM_FILE_END_EN = "-----END ENCRYPTED PRIVATE KEY-----"; + public static final String PEM_FILE_START_PUBLIC = "-----BEGIN PUBLIC KEY-----"; + public static final String PEM_FILE_END_PUBLIC = "-----END PUBLIC KEY-----"; + public static final String PEM_FILE_START_PRIVATE = "-----BEGIN PRIVATE KEY-----"; + public static final String PEM_FILE_END_PRIVATE = "-----END PRIVATE KEY-----"; + public static final String PEM_FILE_START_RSA = "-----BEGIN RSA PRIVATE KEY-----"; + public static final String PEM_FILE_END_RSA = "-----END RSA PRIVATE KEY-----"; + public static final String PEM_FILE_START_PREFIX = "-----BEGIN"; /** * 得到公钥 @@ -32,11 +71,24 @@ public class RSA2 { * @throws Exception */ public static RSAPublicKey getPublicKey(String filepath) throws CertificateException, FileNotFoundException { - //通过证书,获取公钥 - CertificateFactory cf = null; - cf = CertificateFactory.getInstance("X.509"); - Certificate c = cf.generateCertificate(new FileInputStream(filepath)); - return (RSAPublicKey) c.getPublicKey(); + try (FileInputStream pubKeyIn = new FileInputStream(filepath)) { + byte[] certData = pubKeyIn.readAllBytes(); + // 检查是否为PEM格式 + String certString = new String(certData, StandardCharsets.UTF_8); + if (certString.contains(PEM_FILE_START_PREFIX)) { + // 这可能不是证书文件,而是公钥文件 + return getPublicKey4Pem(certString); + } + //通过证书,获取公钥 + try (ByteArrayInputStream bis = new ByteArrayInputStream(certData)) { + return getPublicKey(bis); + } catch (Exception e) { + // 在尝试一次读取 + return getPublicKey4Pem(certString); + } + } catch (Exception e) { + throw new CertificateException("无法解析证书: " + e.getMessage(), e); + } } /** @@ -48,12 +100,96 @@ public class RSA2 { */ public static RSAPublicKey getPublicKey(InputStream pubKeyIn) throws CertificateException { //通过证书,获取公钥 - CertificateFactory cf = null; - cf = CertificateFactory.getInstance("X.509"); + CertificateFactory cf = CertificateFactory.getInstance("X.509"); Certificate c = cf.generateCertificate(pubKeyIn); return (RSAPublicKey) c.getPublicKey(); } + /** + * 从PKCS12证书中获取公钥 + * + * @param pubKeyPath 密钥文件路径 + * @param alias 别名 + * @param password 密码 + * @return RSAPublicKey + * @throws BaseException + */ + public static RSAPublicKey getPublicKeyFromPKCS12(String pubKeyPath, String alias, String password) throws BaseException { + try (FileInputStream fis = new FileInputStream(pubKeyPath)) { + return getPublicKeyFromPKCS12(fis, alias, password); + } catch (Exception e) { + throw new BaseException(e, "无法从PKCS12证书中获取公钥"); + } + } + + /** + * 从PKCS12证书中获取公钥 + * + * @param pubKeyIn 密钥文件流 + * @param alias 别名 + * @param password 密码 + * @return RSAPublicKey + * @throws BaseException + */ + public static RSAPublicKey getPublicKeyFromPKCS12(InputStream pubKeyIn, String alias, String password) throws BaseException { + try { + KeyStore keyStore = KeyStore.getInstance(KEY_PKCS12); + keyStore.load(pubKeyIn, password.toCharArray()); + Certificate cert = keyStore.getCertificate(alias); + return (RSAPublicKey) cert.getPublicKey(); + } catch (Exception e) { + throw new BaseException(e, "无法从PKCS12证书中获取公钥"); + } + } + + /** + * 从PEM格式的公钥文件中提取公钥 + * + * @param pemContent PEM格式的内容 + * @return RSAPublicKey + * @throws CertificateException + */ + private static RSAPublicKey getPublicKey4Pem(String pemContent) throws CertificateException { + try { + if (pemContent.contains(PEM_FILE_START_CE)) { + String base64Cert = pemContent.replace(PEM_FILE_START_CE, "") + .replace(PEM_FILE_END_CE, "") + .replaceAll("\\s", ""); + + byte[] certBytes = Base64.getDecoder() + .decode(base64Cert); + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + Certificate c = cf.generateCertificate(new ByteArrayInputStream(certBytes)); + return (RSAPublicKey) c.getPublicKey(); + } + // 如果是公钥PEM格式 + else if (pemContent.contains(PEM_FILE_START_PUBLIC)) { + String base64Key = pemContent.replace(PEM_FILE_START_PUBLIC, "") + .replace(PEM_FILE_END_PUBLIC, "") + .replaceAll("\\s", ""); + + byte[] keyBytes = Base64.getDecoder() + .decode(base64Key); + KeyFactory kf = KeyFactory.getInstance(KEY_RSA); + X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes); + return (RSAPublicKey) kf.generatePublic(keySpec); + } else { + String trimmedString = pemContent.replaceAll("\\s", "") + .trim(); + if (!pemContent.equals(trimmedString)) { + byte[] keyBytes = Base64.getDecoder() + .decode(trimmedString); + KeyFactory kf = KeyFactory.getInstance(KEY_RSA); + X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes); + return (RSAPublicKey) kf.generatePublic(keySpec); + } + } + } catch (Exception e) { + throw new CertificateException("无法从PEM内容解析公钥: " + e.getMessage(), e); + } + throw new CertificateException("无法从PEM内容解析公钥"); + } + /** * 读取JKS格式的key(私钥)keystore格式 * @@ -61,14 +197,10 @@ public class RSA2 { * @param alias 证书别名 * @param password 证书密码 * @return - * @throws NoSuchAlgorithmException - * @throws KeyStoreException - * @throws IOException - * @throws CertificateException - * @throws UnrecoverableKeyException + * @throws BaseException */ - public static RSAPrivateKey getPrivateKey(String filepath, String alias, String password) throws NoSuchAlgorithmException, KeyStoreException, IOException, CertificateException, UnrecoverableKeyException { - return getPrivateKey(filepath, alias, password, "JKS"); + public static RSAPrivateKey getPrivateKey(String filepath, String alias, String password) throws BaseException { + return getPrivateKey(filepath, alias, password, KEY_JKS); } /** @@ -78,14 +210,10 @@ public class RSA2 { * @param alias 证书别名 * @param password 证书密码 * @return - * @throws NoSuchAlgorithmException - * @throws KeyStoreException - * @throws IOException - * @throws CertificateException - * @throws UnrecoverableKeyException + * @throws BaseException */ - public static RSAPrivateKey getPrivateKey(InputStream priKeyIn, String alias, String password) throws NoSuchAlgorithmException, KeyStoreException, IOException, CertificateException, UnrecoverableKeyException { - return getPrivateKey(priKeyIn, alias, password, "JKS"); + public static RSAPrivateKey getPrivateKey(InputStream priKeyIn, String alias, String password) throws BaseException { + return getPrivateKey(priKeyIn, alias, password, KEY_JKS); } /** @@ -95,14 +223,10 @@ public class RSA2 { * @param alias 证书别名 可空 * @param password 证书密码 * @return - * @throws NoSuchAlgorithmException - * @throws KeyStoreException - * @throws IOException - * @throws CertificateException - * @throws UnrecoverableKeyException + * @throws BaseException */ - public static RSAPrivateKey getPrivateKeyFromPKCS12(String filepath, String alias, String password) throws NoSuchAlgorithmException, KeyStoreException, IOException, CertificateException, UnrecoverableKeyException { - return getPrivateKey(filepath, alias, password, "PKCS12"); + public static RSAPrivateKey getPrivateKeyFromPKCS12(String filepath, String alias, String password) throws BaseException { + return getPrivateKey(filepath, alias, password, KEY_PKCS12); } /** @@ -112,14 +236,54 @@ public class RSA2 { * @param alias 证书别名 可空 * @param password 证书密码 * @return - * @throws NoSuchAlgorithmException - * @throws KeyStoreException - * @throws IOException - * @throws CertificateException - * @throws UnrecoverableKeyException + * @throws BaseException */ - public static RSAPrivateKey getPrivateKeyFromPKCS12(InputStream priKeyIn, String alias, String password) throws NoSuchAlgorithmException, KeyStoreException, IOException, CertificateException, UnrecoverableKeyException { - return getPrivateKey(priKeyIn, alias, password, "PKCS12"); + public static RSAPrivateKey getPrivateKeyFromPKCS12(InputStream priKeyIn, String alias, String password) throws BaseException { + return getPrivateKey(priKeyIn, alias, password, KEY_PKCS12); + } + + /** + * 读取PKCS8格式的key(私钥)pem格式 + * + * @param filepath 私钥路径 + * @return + * @throws BaseException + */ + public static RSAPrivateKey getPrivateKeyFromPKCS8(String filepath, String password) throws BaseException { + return getPrivateKey(filepath, null, password, KEY_PKCS8); + } + + /** + * 读取PKCS8格式的key(私钥)pem格式 + * + * @param priKeyIn 私钥文件流 + * @return + * @throws BaseException + */ + public static RSAPrivateKey getPrivateKeyFromPKCS8(InputStream priKeyIn, String password) throws BaseException { + return getPrivateKey(priKeyIn, null, password, KEY_PKCS8); + } + + /** + * 读取PKCS8格式的key(私钥)pem格式 + * + * @param filepath 私钥路径 + * @return + * @throws BaseException + */ + public static RSAPrivateKey getPrivateKeyFromPKCS1(String filepath) throws BaseException { + return getPrivateKey(filepath, null, null, KEY_PKCS1); + } + + /** + * 读取PKCS8格式的key(私钥)pem格式 + * + * @param priKeyIn 私钥文件流 + * @return + * @throws BaseException + */ + public static RSAPrivateKey getPrivateKeyFromPKCS1(InputStream priKeyIn) throws BaseException { + return getPrivateKey(priKeyIn, null, null, KEY_PKCS1); } /** @@ -130,60 +294,107 @@ public class RSA2 { * @param password 证书密码 * @param type 证书格式 * @return - * @throws NoSuchAlgorithmException - * @throws KeyStoreException - * @throws IOException - * @throws CertificateException - * @throws UnrecoverableKeyException + * @throws BaseException */ - public static RSAPrivateKey getPrivateKey(String filepath, String alias, String password, String type) throws NoSuchAlgorithmException, KeyStoreException, IOException, CertificateException, UnrecoverableKeyException { - KeyStore ks = KeyStore.getInstance(type); - FileInputStream fileInputStream = null; + public static RSAPrivateKey getPrivateKey(String filepath, String alias, String password, String type) throws BaseException { + try (FileInputStream fileInputStream = new FileInputStream(filepath)) { + return getPrivateKey(fileInputStream, alias, password, type); + } catch (Exception e) { + throw new BaseException(e, "私钥读取失败。"); + } + } + + /** + * 读取key(私钥) + * + * @param priKeyIn 私钥文件流 + * @param alias 证书别名 可空 + * @param password 证书密码 + * @param type 证书格式 + * @return + * @throws BaseException + */ + public static RSAPrivateKey getPrivateKey(InputStream priKeyIn, String alias, String password, String type) throws BaseException { try { - fileInputStream = new FileInputStream(filepath); - ks.load(fileInputStream, password.toCharArray()); - if (StrUtil.isEmpty(alias)) { - Enumeration aliases = ks.aliases(); - if (aliases != null) { - if (aliases.hasMoreElements()) { + if (KEY_PKCS8.equalsIgnoreCase(type) || KEY_PKCS1.equalsIgnoreCase(type)) { + byte[] keyBytes = priKeyIn.readAllBytes(); + String keyString = new String(keyBytes, StandardCharsets.UTF_8); + if (keyString.contains(PEM_FILE_START_PREFIX)) { + // 标准PEM格式内容 + return getPrivateKeyByPKCS8(keyString, password); + } + // 非标准PEM格式内容,尝试直接读取 + String trimmedString = keyString.replaceAll("\\s", "") + .trim(); + if (!keyString.equals(trimmedString)) { + keyBytes = Base64.getDecoder() + .decode(trimmedString); + } + // 验证数据是否有效 + if (keyBytes.length == 0) { + throw new BaseException("Private key data is empty"); + } + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes); + KeyFactory kf = KeyFactory.getInstance(KEY_RSA); + return (RSAPrivateKey) kf.generatePrivate(keySpec); + } else { + KeyStore ks = KeyStore.getInstance(type); + ks.load(priKeyIn, password.toCharArray()); + if (StrUtil.isEmpty(alias)) { + Enumeration aliases = ks.aliases(); + if (aliases != null && aliases.hasMoreElements()) { alias = (String) aliases.nextElement(); } } + return (RSAPrivateKey) ks.getKey(alias, password.toCharArray()); } - } finally { - if (fileInputStream != null) { - fileInputStream.close(); - } + } catch (Exception e) { + throw new BaseException(e, "私钥读取失败。"); } - return (RSAPrivateKey) ks.getKey(alias, password.toCharArray()); } /** - * 读取key(私钥) + * 解密并解析加密的PKCS#8私钥 * - * @param priKeyIn 私钥文件流 - * @param alias 证书别名 可空 - * @param password 证书密码 - * @param type 证书格式 - * @return - * @throws NoSuchAlgorithmException - * @throws KeyStoreException - * @throws IOException - * @throws CertificateException - * @throws UnrecoverableKeyException + * @param pemContent PEM格式的加密私钥内容 + * @param password 密码 + * @return RSAPrivateKey + * @throws Exception */ - public static RSAPrivateKey getPrivateKey(InputStream priKeyIn, String alias, String password, String type) throws NoSuchAlgorithmException, KeyStoreException, IOException, CertificateException, UnrecoverableKeyException { - KeyStore ks = KeyStore.getInstance(type); - ks.load(priKeyIn, password.toCharArray()); - if (StrUtil.isEmpty(alias)) { - Enumeration aliases = ks.aliases(); - if (aliases != null) { - if (aliases.hasMoreElements()) { - alias = (String) aliases.nextElement(); - } - } + private static RSAPrivateKey getPrivateKeyByPKCS8(String pemContent, String password) throws Exception { + if (Security.getProvider("BC") == null) { + Security.addProvider(new BouncyCastleProvider()); + } + try (PEMParser pemParser = new PEMParser(new StringReader(pemContent))) { + Object object = pemParser.readObject(); + + // 处理PKCS#1格式的RSA私钥 + if (object instanceof org.bouncycastle.asn1.pkcs.RSAPrivateKey) { + JcaPEMKeyConverter converter = new JcaPEMKeyConverter(); + PrivateKeyInfo privateKeyInfo = PrivateKeyInfo.getInstance(object); + return (RSAPrivateKey) converter.getPrivateKey(privateKeyInfo); + } else if (object instanceof PEMKeyPair) { + // 处理PKCS#1格式的RSA私钥 + JcaPEMKeyConverter converter = new JcaPEMKeyConverter(); + return (RSAPrivateKey) converter.getPrivateKey(((PEMKeyPair) object).getPrivateKeyInfo()); + } else if (object instanceof PKCS8EncryptedPrivateKeyInfo) { + // 处理加密的PKCS#8私钥 + PKCS8EncryptedPrivateKeyInfo encryptedInfo = (PKCS8EncryptedPrivateKeyInfo) object; + InputDecryptorProvider decryptorProvider = new JceOpenSSLPKCS8DecryptorProviderBuilder().setProvider("BC") + .build(password.toCharArray()); + + PrivateKeyInfo privateKeyInfo = encryptedInfo.decryptPrivateKeyInfo(decryptorProvider); + JcaPEMKeyConverter converter = new JcaPEMKeyConverter(); + return (RSAPrivateKey) converter.getPrivateKey(privateKeyInfo); + } else if (object instanceof PrivateKeyInfo) { + // 处理未加密的PKCS#8私钥 + JcaPEMKeyConverter converter = new JcaPEMKeyConverter(); + return (RSAPrivateKey) converter.getPrivateKey((PrivateKeyInfo) object); + } + throw new IllegalArgumentException("不支持的PEM格式"); + } catch (Exception e) { + throw new BaseException(e, "获取私钥失败"); } - return (RSAPrivateKey) ks.getKey(alias, password.toCharArray()); } /** @@ -218,8 +429,8 @@ public class RSA2 { */ public static void cover2Pfx(FileInputStream fis, FileOutputStream out, char[] oPwd, char[] nPwd) { try { - KeyStore inputKeyStore = KeyStore.getInstance("JKS"); - cover(fis, out, oPwd, nPwd, inputKeyStore, "PKCS12"); + KeyStore inputKeyStore = KeyStore.getInstance(KEY_JKS); + cover(fis, out, oPwd, nPwd, inputKeyStore, KEY_PKCS12); } catch (Exception e) { e.printStackTrace(); } @@ -258,8 +469,8 @@ public class RSA2 { */ public static void cover2keyStore(FileInputStream fis, FileOutputStream out, char[] oPwd, char[] nPwd) { try { - KeyStore inputKeyStore = KeyStore.getInstance("PKCS12"); - cover(fis, out, oPwd, nPwd, inputKeyStore, "JKS"); + KeyStore inputKeyStore = KeyStore.getInstance(KEY_PKCS12); + cover(fis, out, oPwd, nPwd, inputKeyStore, KEY_JKS); } catch (Exception e) { e.printStackTrace(); } @@ -301,7 +512,7 @@ public class RSA2 { outputKeyStore.store(out, nPwd); } - public static void main(String[] args) { - cover2Pfx("D:\\mykeystore.keystore", "D:\\m1.pfx", "123456", null); - } +// public static void main(String[] args) { +// cover2Pfx("D:\\mykeystore.keystore", "D:\\m1.pfx", "123456", null); +// } } diff --git a/src/main/java/com/yexuejc/base/exception/BaseException.java b/src/main/java/com/yexuejc/base/exception/BaseException.java new file mode 100644 index 0000000..db3d71b --- /dev/null +++ b/src/main/java/com/yexuejc/base/exception/BaseException.java @@ -0,0 +1,147 @@ +package com.yexuejc.base.exception; + +import java.text.MessageFormat; +import java.util.ServiceLoader; + +import com.yexuejc.base.constant.RespConsts; +import com.yexuejc.base.constant.SymbolicConstant; +import com.yexuejc.base.service.MessageService; +import com.yexuejc.base.util.StrUtil; + +/** + * 异常处理 + * @author yexuejc + * @date 2019/8/25 15:38 + */ +public class BaseException extends Exception { + private static final System.Logger LOG = System.getLogger(BaseException.class.getName()); + /** 序列化 */ + private static final long serialVersionUID = 1L; + /** 异常消息CODE */ + protected final String errorCode; + /** 异常消息内容 */ + @SuppressWarnings("java:S1165") + protected String errorMessage; + + /** + * 构造函数 + * + * @param cause 异常对象 + */ + public BaseException(Throwable cause) { + this(cause, RespConsts.CODE_ERROR, (Object) null); + } + + /** + * 构造函数 + * + * @param cause 异常对象 + * @param errorCode 异常消息CODE + */ + public BaseException(Throwable cause, String errorCode) { + this(cause, errorCode, (Object) null); + } + + /** + * 构造函数 + * + * @param errorCode 异常消息CODE + */ + public BaseException(String errorCode) { + this((Throwable) null, errorCode, (Object) null); + } + + /** + * 构造函数 + * + * @param errorCode 异常消息CODE + * @param args 异常消息参数 + */ + public BaseException(String errorCode, Object... args) { + this(null, errorCode, args); + } + + + /** + * 构造函数 + * + * @param cause 异常对象 + * @param errorCode 异常消息CODE + * @param args 异常消息的参数 + */ + public BaseException(Throwable cause, String errorCode, Object... args) { + super(cause); + // 消息内容 + if (StrUtil.isEmpty(errorCode)) { + errorCode = RespConsts.CODE_ERROR; + } + this.errorCode = errorCode; + this.errorMessage = getMessageByCode(errorCode, args); + if (cause instanceof BaseException) { + BaseException comExp = (BaseException) cause; + if (StrUtil.isNotEmpty(comExp.errorMessage)) { + this.errorMessage = StrUtil.isEmpty(this.errorMessage) ? comExp.errorMessage : + this.errorMessage + SymbolicConstant.NEW_LINE + comExp.errorMessage; + } + } + } + + /** + * 获取异常消息CODE + * + * @return 异常消息CODE + */ + public String getErrorCode() { + return errorCode; + } + + + /** + * 获取异常消息内容 + * + * @return + */ + @Override + public String getMessage() { + if (StrUtil.isNotEmpty(super.getMessage())) { + return super.getMessage(); + } + return getErrorCode().equals(getFaultMessage()) ? getFaultMessage() : getErrorCode() + ":" + getFaultMessage(); + } + + /** + * 获取异常消息内容 + * + * @return 异常消息内容,不包含异常信息 + */ + public String getFaultMessage() { + return this.errorMessage; + } + + /** + * 获取消息内容 + * + * @param errorCode 异常消息CODE + * @param args 异常消息的参数 + * @return + */ + protected String getMessageByCode(String errorCode, Object... args) { + try { + // 使用 ServiceLoader 动态加载 MessageService 实现 + ServiceLoader loader = ServiceLoader.load(MessageService.class); + for (MessageService service : loader) { + if (service != null) { + return service.getMessage(errorCode, args); + } + } + // 如果没有找到实现,使用默认处理方式 + LOG.log(System.Logger.Level.DEBUG, "No MessageService implementation found, using default message format."); + return MessageFormat.format(errorCode, args); + } catch (Exception e) { + LOG.log(System.Logger.Level.WARNING, "Error getting message for code: " + errorCode + ", using default format.", e); + return MessageFormat.format(errorCode, args); + } + } + + +} diff --git a/src/main/java/com/yexuejc/base/http/RequestHeader.java b/src/main/java/com/yexuejc/base/http/RequestHeader.java new file mode 100644 index 0000000..95ee733 --- /dev/null +++ b/src/main/java/com/yexuejc/base/http/RequestHeader.java @@ -0,0 +1,51 @@ +package com.yexuejc.base.http; + +import java.util.HashMap; + +import com.yexuejc.base.util.DateTimeUtil; + +public class RequestHeader extends HashMap { + public static final String CLIENT_ID = "Client-Id"; + public static final String REQUEST_TIME = "Request-Time"; + public static final String RESPONSE_TIME = "Response-Time"; + public static final String SIGNATURE = "Signature"; + + public RequestHeader(String clientId) { + put("Content-Type", "application/json"); + put(CLIENT_ID, clientId); + } + + public static RequestHeader requestBuilder(String clientId) { + RequestHeader requestHeader = new RequestHeader(clientId); + // 2019-05-28T12:12:12+08:00 + requestHeader.put(REQUEST_TIME, DateTimeUtil.formatIso8601BySystemZone()); + return requestHeader; + } + + public static RequestHeader responseBuilder(String clientId, String time, String signature) { + RequestHeader requestHeader = new RequestHeader(clientId); + requestHeader.put(RESPONSE_TIME, time); + requestHeader.put(SIGNATURE, signature); + return requestHeader; + } + + public String getClientId() { + return get(CLIENT_ID); + } + + public String getReqTime() { + return get(REQUEST_TIME); + } + + public void setSignature(String signature) { + put(SIGNATURE, signature); + } + + public String getSignature() { + return get(SIGNATURE); + } + + public String geRespTime() { + return get(RESPONSE_TIME); + } +} diff --git a/src/main/java/com/yexuejc/base/http/Resps.java b/src/main/java/com/yexuejc/base/http/Resps.java index 4a1c784..d049a71 100644 --- a/src/main/java/com/yexuejc/base/http/Resps.java +++ b/src/main/java/com/yexuejc/base/http/Resps.java @@ -1,6 +1,6 @@ package com.yexuejc.base.http; -import com.yexuejc.base.constant.RespsConsts; +import com.yexuejc.base.constant.RespConsts; import com.yexuejc.base.util.JsonUtil; import java.io.Serializable; @@ -90,27 +90,27 @@ public class Resps implements Serializable { } public static Resps success(String[] msg) { - return new Resps(RespsConsts.CODE_SUCCESS, msg); + return new Resps(RespConsts.CODE_SUCCESS, msg); } public static Resps success(String msg) { - return new Resps(RespsConsts.CODE_SUCCESS, msg); + return new Resps(RespConsts.CODE_SUCCESS, msg); } public static Resps success() { - return new Resps(RespsConsts.CODE_SUCCESS, RespsConsts.MSG_SUCCESS_OPERATE); + return new Resps(RespConsts.CODE_SUCCESS, RespConsts.MSG_SUCCESS_OPERATE); } public static Resps error() { - return new Resps(RespsConsts.CODE_ERROR, RespsConsts.MSG_ERROT_OPERATE); + return new Resps(RespConsts.CODE_ERROR, RespConsts.MSG_ERROT_OPERATE); } public static Resps error(String msg) { - return new Resps(RespsConsts.CODE_ERROR, msg); + return new Resps(RespConsts.CODE_ERROR, msg); } public static Resps error(String[] msg) { - return new Resps(RespsConsts.CODE_ERROR, msg); + return new Resps(RespConsts.CODE_ERROR, msg); } public static Resps error(String code, String msg) { @@ -122,15 +122,15 @@ public class Resps implements Serializable { } public static Resps fail() { - return new Resps(RespsConsts.CODE_FAIL, RespsConsts.MSG_FAIL_OPERATE); + return new Resps(RespConsts.CODE_FAIL, RespConsts.MSG_FAIL_OPERATE); } public static Resps fail(String msg) { - return new Resps(RespsConsts.CODE_FAIL, msg); + return new Resps(RespConsts.CODE_FAIL, msg); } public static Resps fail(String[] msg) { - return new Resps(RespsConsts.CODE_FAIL, msg); + return new Resps(RespConsts.CODE_FAIL, msg); } public static Resps fail(String code, String msg) { diff --git a/src/main/java/com/yexuejc/base/service/MessageService.java b/src/main/java/com/yexuejc/base/service/MessageService.java new file mode 100644 index 0000000..4d9cf12 --- /dev/null +++ b/src/main/java/com/yexuejc/base/service/MessageService.java @@ -0,0 +1,19 @@ +package com.yexuejc.base.service; + +/** + * 消息获取接口 + * + * @author yexuejc + * @date 2025/8/25 15:33 + */ +public interface MessageService { + /** + * 通过异常CODE和参数获取对应的错误信息(多语言实现) + * + * @param code 异常CODE + * @param args 参数 + * @return 错误信息 + */ + String getMessage(String code, Object... args); +} + \ No newline at end of file diff --git a/src/main/java/com/yexuejc/base/util/DateTimeUtil.java b/src/main/java/com/yexuejc/base/util/DateTimeUtil.java index 95924e4..ec644fd 100644 --- a/src/main/java/com/yexuejc/base/util/DateTimeUtil.java +++ b/src/main/java/com/yexuejc/base/util/DateTimeUtil.java @@ -1,12 +1,20 @@ package com.yexuejc.base.util; -import java.text.ParseException; -import java.time.*; +import java.time.DayOfWeek; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.time.temporal.TemporalAdjuster; import java.time.temporal.TemporalAdjusters; import java.util.Date; +import com.yexuejc.base.constant.DateConsts; + /** * 新版时间日期出来 工具 @@ -15,10 +23,6 @@ import java.util.Date; * @date: 2018/3/27 10:44 */ public class DateTimeUtil { - public static String DATE_PATTERN = "yyyy-MM-dd"; - public static String TIME_PATTERN = "HH:mm:ss"; - public static String DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss"; - public static String DATE_TIME_MS_PATTERN = "yyyy-MM-dd HH:mm:ss.SSS"; private DateTimeUtil() { } @@ -116,8 +120,8 @@ public class DateTimeUtil { * @return */ public static LocalDate getWeek4First(LocalDate date) { - TemporalAdjuster firstOfWeek = TemporalAdjusters.ofDateAdjuster(localDate -> - localDate.minusDays(localDate.getDayOfWeek().getValue() - DayOfWeek.MONDAY.getValue())); + TemporalAdjuster firstOfWeek = TemporalAdjusters.ofDateAdjuster(localDate -> localDate.minusDays(localDate.getDayOfWeek() + .getValue() - DayOfWeek.MONDAY.getValue())); return date.with(firstOfWeek); } @@ -137,8 +141,9 @@ public class DateTimeUtil { * @return */ public static LocalDate getWeek4Last(LocalDate date) { - TemporalAdjuster lastOfWeek = TemporalAdjusters.ofDateAdjuster(localDate -> - localDate.plusDays(DayOfWeek.SUNDAY.getValue() - localDate.getDayOfWeek().getValue())); + TemporalAdjuster lastOfWeek = + TemporalAdjusters.ofDateAdjuster(localDate -> localDate.plusDays(DayOfWeek.SUNDAY.getValue() - localDate.getDayOfWeek() + .getValue())); return date.with(lastOfWeek); } @@ -163,7 +168,9 @@ public class DateTimeUtil { */ public static Date parseDate(LocalDate localDate) { ZoneId zone = ZoneId.systemDefault(); - Instant instant = localDate.atStartOfDay().atZone(zone).toInstant(); + Instant instant = localDate.atStartOfDay() + .atZone(zone) + .toInstant(); return Date.from(instant); } @@ -200,7 +207,8 @@ public class DateTimeUtil { public static Date parseDate(LocalDate localDate, LocalTime localTime) { LocalDateTime localDateTime = LocalDateTime.of(localDate, localTime); ZoneId zone = ZoneId.systemDefault(); - Instant instant = localDateTime.atZone(zone).toInstant(); + Instant instant = localDateTime.atZone(zone) + .toInstant(); return Date.from(instant); } @@ -213,7 +221,8 @@ public class DateTimeUtil { public static ZonedDateTime parseZonedDateTime(Date date) { Instant instant = date.toInstant(); ZoneId zoneId = ZoneId.systemDefault(); - return instant.atZone(zoneId).withZoneSameInstant(zoneId); + return instant.atZone(zoneId) + .withZoneSameInstant(zoneId); } /** @@ -225,7 +234,8 @@ public class DateTimeUtil { public static LocalDateTime parseLocalDateTime(Date date) { Instant instant = date.toInstant(); ZoneId zoneId = ZoneId.systemDefault(); - return instant.atZone(zoneId).toLocalDateTime(); + return instant.atZone(zoneId) + .toLocalDateTime(); } /** @@ -237,7 +247,8 @@ public class DateTimeUtil { public static LocalDate parseLocalDate(Date date) { Instant instant = date.toInstant(); ZoneId zoneId = ZoneId.systemDefault(); - return instant.atZone(zoneId).toLocalDate(); + return instant.atZone(zoneId) + .toLocalDate(); } /** @@ -304,7 +315,8 @@ public class DateTimeUtil { * @return */ public static LocalDateTime parserUTC(Long timestamp) { - if (String.valueOf(timestamp).length() == 10) { + if (String.valueOf(timestamp) + .length() == 10) { timestamp = timestamp * 1000; } return parseLocalDateTime13(timestamp, ZoneId.of("UTC")); @@ -318,7 +330,8 @@ public class DateTimeUtil { */ public static long parseLong(LocalDateTime localDateTime) { ZoneId zone = ZoneId.systemDefault(); - Instant instant = localDateTime.atZone(zone).toInstant(); + Instant instant = localDateTime.atZone(zone) + .toInstant(); return instant.toEpochMilli(); } @@ -330,7 +343,8 @@ public class DateTimeUtil { */ public static long parseLong(LocalDate localDate) { ZoneId zone = ZoneId.systemDefault(); - Instant instant = localDate.atStartOfDay(zone).toInstant(); + Instant instant = localDate.atStartOfDay(zone) + .toInstant(); return instant.toEpochMilli(); } @@ -355,7 +369,7 @@ public class DateTimeUtil { */ public static String format(LocalDate dateTime, String pattern) { if (StrUtil.isEmpty(pattern)) { - pattern = DATE_PATTERN; + pattern = DateConsts.DATE_PATTERN; } DateTimeFormatter df = DateTimeFormatter.ofPattern(pattern); return df.format(dateTime); @@ -381,7 +395,32 @@ public class DateTimeUtil { */ public static String format(LocalDateTime dateTime, String pattern) { if (StrUtil.isEmpty(pattern)) { - pattern = DATE_TIME_PATTERN; + pattern = DateConsts.DATE_TIME_PATTERN; + } + DateTimeFormatter df = DateTimeFormatter.ofPattern(pattern); + return df.format(dateTime); + } + /** + * 格式化时间
+ * 格式 yyyy-MM-dd HH:mm:ss + * + * @param dateTime + * @return + */ + public static String format(OffsetDateTime dateTime) { + return format(dateTime, null); + } + + /** + * 格式化时间 + * + * @param dateTime + * @param pattern 格式 默认:yyyy-MM-dd HH:mm:ss + * @return + */ + public static String format(OffsetDateTime dateTime, String pattern) { + if (StrUtil.isEmpty(pattern)) { + pattern = DateConsts.DATE_TIME_PATTERN; } DateTimeFormatter df = DateTimeFormatter.ofPattern(pattern); return df.format(dateTime); @@ -425,7 +464,39 @@ public class DateTimeUtil { currentZone = ZoneId.of(currentZoneId); } ZoneId targetZone = ZoneId.of(targetZoneId); - return date.atZone(currentZone).withZoneSameInstant(targetZone).toLocalDateTime(); + return date.atZone(currentZone) + .withZoneSameInstant(targetZone) + .toLocalDateTime(); + } + + /** + * 获取 ISO8601 格式时间 + * + * @return 2019-05-28T12:12:12+08:00 + */ + public static String formatIso8601BySystemZone() { + return DateTimeUtil.format(OffsetDateTime.now(), DateConsts.ISO_8601_PATTERN); + } + + /** + * 时间转换成ISO8601格式 + * + * @param localDateTime 时间 + * @return 2019-05-28T12:12:12+08:00 + */ + public static String formatIso8601BySystemZone(LocalDateTime localDateTime) { + return DateTimeUtil.format(localDateTime, DateConsts.ISO_8601_PATTERN); + } + + /** + * 时间转换成ISO8601格式 + * + * @param timestamp 时间戳 + * @param zoneId 时区 + * @return 2019-05-28T12:12:12+08:00 + */ + public static String formatIso8601(long timestamp, ZoneId zoneId) { + return DateTimeUtil.format(parseLocalDateTime13(timestamp, zoneId), DateConsts.ISO_8601_PATTERN); } diff --git a/src/main/java/com/yexuejc/base/util/StrUtil.java b/src/main/java/com/yexuejc/base/util/StrUtil.java index 3bdca00..c90bed7 100644 --- a/src/main/java/com/yexuejc/base/util/StrUtil.java +++ b/src/main/java/com/yexuejc/base/util/StrUtil.java @@ -37,7 +37,8 @@ public final class StrUtil { return ((Optional) obj).isEmpty(); } else if (obj instanceof CharSequence) { return ((CharSequence) obj).length() == 0; - } else if (obj.getClass().isArray()) { + } else if (obj.getClass() + .isArray()) { return Array.getLength(obj) == 0; } else if (obj instanceof Collection) { return ((Collection) obj).isEmpty(); @@ -56,7 +57,9 @@ public final class StrUtil { * @return */ public static String genUUID() { - return UUID.randomUUID().toString().replaceAll("-", ""); + return UUID.randomUUID() + .toString() + .replaceAll("-", ""); } /** @@ -91,7 +94,9 @@ public final class StrUtil { * @return */ public static String genNum() { - int hashCode = UUID.randomUUID().toString().hashCode(); + int hashCode = UUID.randomUUID() + .toString() + .hashCode(); StringBuilder num = new StringBuilder(); if (hashCode < 0) { hashCode = -hashCode; @@ -99,7 +104,8 @@ public final class StrUtil { } else { num.append("1"); } - return num.append(String.format("%010d", hashCode)).substring(0, 8); + return num.append(String.format("%010d", hashCode)) + .substring(0, 8); } /** @@ -296,7 +302,10 @@ public final class StrUtil { for (String key : keys) { Object value = sortedParams.get(key); if (isNotEmpty(key) && isNotEmpty(value)) { - content.append(index == 0 ? "" : "&").append(key).append("=").append(value); + content.append(index == 0 ? "" : "&") + .append(key) + .append("=") + .append(value); ++index; } } @@ -417,14 +426,17 @@ public final class StrUtil { if (cause != null) { StringBuilder msg = new StringBuilder(); if (isNotEmpty(cause.getMessage())) { - msg.append(cause.getMessage()).append(NEW_LINE); + msg.append(cause.getMessage()) + .append(NEW_LINE); } String causedMsg = printStackTrace(cause, (eMessage) -> { if (isNotEmpty(eMessage)) { - msg.append(eMessage).append(NEW_LINE); + msg.append(eMessage) + .append(NEW_LINE); } }); - return msg.append(causedMsg).toString(); + return msg.append(causedMsg) + .toString(); } return ""; } @@ -444,21 +456,28 @@ public final class StrUtil { private static String printStackTrace(Throwable cause, Consumer consumer) { if (cause != null) { StringBuilder sb = new StringBuilder(); - String cClass = cause.getClass().getName(); + String cClass = cause.getClass() + .getName(); String eMessage = cause.getMessage(); StackTraceElement[] stackTrace = cause.getStackTrace(); Throwable caused = cause.getCause(); while (caused != null) { - cClass = caused.getClass().getName(); + cClass = caused.getClass() + .getName(); eMessage = caused.getMessage(); stackTrace = caused.getStackTrace(); caused = caused.getCause(); consumer.accept(eMessage); } - sb.append("Caused by: ").append(cClass).append(": ").append(eMessage).append(NEW_LINE); + sb.append("Caused by: ") + .append(cClass) + .append(": ") + .append(eMessage) + .append(NEW_LINE); for (StackTraceElement element : stackTrace) { sb.append("\tat "); - sb.append(String.format(ERROR_MESSAGE_FORMAT, element.getClassName(), element.getMethodName(), element.getFileName(), element.getLineNumber())); + sb.append(String.format(ERROR_MESSAGE_FORMAT, element.getClassName(), element.getMethodName(), element.getFileName(), + element.getLineNumber())); sb.append(NEW_LINE); } return sb.toString(); @@ -539,13 +558,13 @@ public final class StrUtil { * 国家代码二进制转国家代码 * * @param countryCode 国家代码二进制转:010000000 - *
-     *  1     0     1    0    1       1     1     1     1 
- * 日本 韓国  泰国 新加坡 中国内陸 台湾  香港 澳门 其他 - *
- * 右→左:0位:其他、1位:中国澳门、2位:中国香港、3位:中国台湾、4位:中国内陸、5位:新加坡、6位:泰国、7位:韓国、8位:日本 - *
1:在该国表示、0:不表示该国 - *
+ *
+     *                     1     0     1    0    1       1     1     1     1 
+ * 日本 韓国  泰国 新加坡 中国内陸 台湾  香港 澳门 其他 + *
+ * 右→左:0位:其他、1位:中国澳门、2位:中国香港、3位:中国台湾、4位:中国内陸、5位:新加坡、6位:泰国、7位:韓国、8位:日本 + *
1:在该国表示、0:不表示该国 + *
* @return JPN:日本、KOR:韓国、THA:泰国、CHN:中国内陸、SGP:新加坡、TWN:中国台湾、HKG:中国香港、MAC:中国澳门、999:其他、0:不明 */ public static String getCountryByCode(String countryCode) { @@ -571,4 +590,23 @@ public final class StrUtil { return false; } + /** + * 将长字符串分割为多行 + * + * @param str 原始字符串 + * @param chunkSize 每行长度 + * @return 分割后的字符串 + */ + public static String chunkString(String str, int chunkSize) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < str.length(); i += chunkSize) { + int end = Math.min(str.length(), i + chunkSize); + builder.append(str, i, end); + if (end != str.length()) { + builder.append("\n"); + } + } + return builder.toString(); + } + }