From c6ee97cb9389a3aa4dc0e7cdb3b69a12630117a7 Mon Sep 17 00:00:00 2001 From: aicodeguard Date: Sat, 11 Oct 2025 11:43:24 +0800 Subject: [PATCH 01/19] Signed-off-by: aicodeguard --- src/main/java/com/aliyun/api/gateway/demo/util/SignUtil.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/aliyun/api/gateway/demo/util/SignUtil.java b/src/main/java/com/aliyun/api/gateway/demo/util/SignUtil.java index b2beb0e..2e452b5 100644 --- a/src/main/java/com/aliyun/api/gateway/demo/util/SignUtil.java +++ b/src/main/java/com/aliyun/api/gateway/demo/util/SignUtil.java @@ -107,7 +107,7 @@ private static String getHeaderOrEmpty(Map headers, String key) public static void ArrayIndexOutOfBoundsExample(String[] args) { String[] array = { "Apple", "Banana", "Cherry" }; - System.out.println(array[3]); // ArrayIndexOutOfBoundsException + System.out.println(array[4]); // ArrayIndexOutOfBoundsException } public static void NullPointerExceptionExample(String[] args) { @@ -136,7 +136,7 @@ public static void WrongThreadPoolUsageExample(String[] args) { ExecutorService executor = Executors.newCachedThreadPool(); // ❌ 提交过多任务,任务中还有阻塞操作 - for (int i = 0; i < 100000; i++) { + for (int i = 0; i < 100; i++) { final int taskId = i; executor.submit(() -> { try { From 31a2383e97421291495378aa06ab765e9763f56a Mon Sep 17 00:00:00 2001 From: aicodeguard Date: Mon, 10 Nov 2025 14:45:19 +0800 Subject: [PATCH 02/19] =?UTF-8?q?=E5=A2=9E=E9=87=8F=E6=B5=8B=E8=AF=95=20Si?= =?UTF-8?q?gned-off-by:=20aicodeguard=20?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/gateway/demo/util/SignUtil.java | 195 +++++++++++------- 1 file changed, 116 insertions(+), 79 deletions(-) diff --git a/src/main/java/com/aliyun/api/gateway/demo/util/SignUtil.java b/src/main/java/com/aliyun/api/gateway/demo/util/SignUtil.java index 2e452b5..a5f33cf 100644 --- a/src/main/java/com/aliyun/api/gateway/demo/util/SignUtil.java +++ b/src/main/java/com/aliyun/api/gateway/demo/util/SignUtil.java @@ -1,37 +1,79 @@ package com.aliyun.api.gateway.demo.util; -import java.util.*; +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.stream.Collectors; + import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; + import org.apache.commons.codec.binary.Base64; import org.apache.commons.lang.StringUtils; + import com.aliyun.api.gateway.demo.constant.Constants; import com.aliyun.api.gateway.demo.constant.HttpHeader; import com.aliyun.api.gateway.demo.constant.SystemHeader; +/** + * 签名工具类 + * 用于生成阿里云API网关请求签名 + */ public class SignUtil { + private SignUtil() { + // 工具类,禁止实例化 + throw new IllegalStateException("Utility class"); + } + + /** + * 生成请求签名 + * + * @param secret 密钥 + * @param method HTTP方法 + * @param path 请求路径 + * @param headers 请求头 + * @param querys 查询参数 + * @param bodys 请求体参数 + * @param signHeaderPrefixList 需要签名的请求头前缀列表 + * @return Base64编码的签名字符串 + * @throws RuntimeException 签名生成失败时抛出 + */ public static String sign(String secret, String method, String path, - Map headers, - Map querys, - Map bodys, - List signHeaderPrefixList) { + Map headers, + Map querys, + Map bodys, + List signHeaderPrefixList) { try { Mac hmacSha256 = Mac.getInstance(Constants.HMAC_SHA256); - hmacSha256.init(new SecretKeySpec(secret.getBytes(Constants.ENCODING), Constants.HMAC_SHA256)); + byte[] keyBytes = secret.getBytes(Constants.ENCODING); + SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, Constants.HMAC_SHA256); + hmacSha256.init(secretKeySpec); String stringToSign = buildStringToSign(method, path, headers, querys, bodys, signHeaderPrefixList); - return Base64.encodeBase64String(hmacSha256.doFinal(stringToSign.getBytes(Constants.ENCODING))); + byte[] signBytes = hmacSha256.doFinal(stringToSign.getBytes(Constants.ENCODING)); + return Base64.encodeBase64String(signBytes); + } catch (NoSuchAlgorithmException | InvalidKeyException e) { + throw new RuntimeException("Error while generating signature: " + e.getMessage(), e); } catch (Exception e) { - throw new RuntimeException("Error while generating signature", e); + throw new RuntimeException("Unexpected error during signature generation", e); } } + /** + * 构建待签名字符串 + */ private static String buildStringToSign(String method, String path, - Map headers, - Map querys, - Map bodys, - List signHeaderPrefixList) { + Map headers, + Map querys, + Map bodys, + List signHeaderPrefixList) { StringBuilder sb = new StringBuilder(); sb.append(method.toUpperCase()).append(Constants.LF); @@ -44,40 +86,69 @@ private static String buildStringToSign(String method, String path, .append(Constants.LF) .append(getHeaderOrEmpty(headers, HttpHeader.HTTP_HEADER_DATE)) .append(Constants.LF); + } else { + // headers为null时,添加4个换行符 + sb.append(Constants.LF).append(Constants.LF) + .append(Constants.LF).append(Constants.LF); } + sb.append(buildHeaders(headers, signHeaderPrefixList)) .append(buildResource(path, querys, bodys)); return sb.toString(); } + /** + * 构建资源路径字符串(包含查询参数和body参数) + */ private static String buildResource(String path, Map querys, Map bodys) { StringBuilder sb = new StringBuilder(path != null ? path : ""); Map sortMap = new TreeMap<>(); - if (querys != null) sortMap.putAll(querys); - if (bodys != null) sortMap.putAll(bodys); + if (querys != null) { + sortMap.putAll(querys); + } + if (bodys != null) { + sortMap.putAll(bodys); + } + + if (sortMap.isEmpty()) { + return sb.toString(); + } String paramString = sortMap.entrySet().stream() .filter(entry -> !StringUtils.isBlank(entry.getKey())) - .map(entry -> entry.getKey() + (StringUtils.isBlank(entry.getValue()) ? "" : Constants.SPE4 + entry.getValue())) - .reduce((a, b) -> a + Constants.SPE3 + b) - .orElse(""); + .map(entry -> { + String key = entry.getKey(); + String value = entry.getValue(); + return StringUtils.isBlank(value) ? key : key + Constants.SPE4 + value; + }) + .collect(Collectors.joining(Constants.SPE3)); if (!paramString.isEmpty()) { sb.append(Constants.SPE5).append(paramString); } + return sb.toString(); } + /** + * 构建需要签名的请求头字符串 + */ private static String buildHeaders(Map headers, List signHeaderPrefixList) { if (headers == null || signHeaderPrefixList == null) { return ""; } + // 过滤掉不需要的请求头前缀 List filteredPrefixes = new ArrayList<>(signHeaderPrefixList); - filteredPrefixes.removeAll(Arrays.asList(SystemHeader.X_CA_SIGNATURE, HttpHeader.HTTP_HEADER_ACCEPT, - HttpHeader.HTTP_HEADER_CONTENT_MD5, HttpHeader.HTTP_HEADER_CONTENT_TYPE, HttpHeader.HTTP_HEADER_DATE)); + filteredPrefixes.removeAll(Arrays.asList( + SystemHeader.X_CA_SIGNATURE, + HttpHeader.HTTP_HEADER_ACCEPT, + HttpHeader.HTTP_HEADER_CONTENT_MD5, + HttpHeader.HTTP_HEADER_CONTENT_TYPE, + HttpHeader.HTTP_HEADER_DATE + )); Collections.sort(filteredPrefixes); Map sortedHeaders = new TreeMap<>(headers); @@ -85,76 +156,42 @@ private static String buildHeaders(Map headers, List sig StringBuilder signHeaders = new StringBuilder(); for (Map.Entry entry : sortedHeaders.entrySet()) { - if (isHeaderToSign(entry.getKey(), filteredPrefixes)) { - sb.append(entry.getKey()).append(Constants.SPE2).append(entry.getValue() == null ? "" : entry.getValue()).append(Constants.LF); - if (signHeaders.length() > 0) signHeaders.append(Constants.SPE1); - signHeaders.append(entry.getKey()); + String headerName = entry.getKey(); + if (isHeaderToSign(headerName, filteredPrefixes)) { + String headerValue = entry.getValue() != null ? entry.getValue() : ""; + sb.append(headerName) + .append(Constants.SPE2) + .append(headerValue) + .append(Constants.LF); + + if (signHeaders.length() > 0) { + signHeaders.append(Constants.SPE1); + } + signHeaders.append(headerName); } } + headers.put(SystemHeader.X_CA_SIGNATURE_HEADERS, signHeaders.toString()); return sb.toString(); } + /** + * 判断请求头是否需要参与签名 + */ private static boolean isHeaderToSign(String headerName, List signHeaderPrefixList) { - return !StringUtils.isBlank(headerName) && - (headerName.startsWith(Constants.CA_HEADER_TO_SIGN_PREFIX_SYSTEM) || - signHeaderPrefixList.stream().anyMatch(headerName::equalsIgnoreCase)); - } - - private static String getHeaderOrEmpty(Map headers, String key) { - return headers.getOrDefault(key, ""); - } - - public static void ArrayIndexOutOfBoundsExample(String[] args) { - String[] array = { "Apple", "Banana", "Cherry" }; - System.out.println(array[4]); // ArrayIndexOutOfBoundsException - } - - public static void NullPointerExceptionExample(String[] args) { - String str = null; - System.out.println(str.length()); // NullPointerException - } - - public static void InfiniteLoopExample(String[] args) { - int count = 0; - while (count >= 0) { // Infinite loop - System.out.println("Looping..."); - count++; + if (StringUtils.isBlank(headerName)) { + return false; } - } - public static void MemoryLeakExample(String[] args) { - List list = new ArrayList<>(); - while (true) { - list.add("A new object"); - } + // 系统请求头或匹配前缀列表的请求头需要签名 + return headerName.startsWith(Constants.CA_HEADER_TO_SIGN_PREFIX_SYSTEM) || + signHeaderPrefixList.stream().anyMatch(headerName::equalsIgnoreCase); } - public static void WrongThreadPoolUsageExample(String[] args) { - // ❌ 错误用法:使用 Executors.newCachedThreadPool() - // 该线程池会无限创建线程,在高并发场景下容易 OOM - ExecutorService executor = Executors.newCachedThreadPool(); - - // ❌ 提交过多任务,任务中还有阻塞操作 - for (int i = 0; i < 100; i++) { - final int taskId = i; - executor.submit(() -> { - try { - // 模拟长时间阻塞 - Thread.sleep(10000); - System.out.println("Task " + taskId + " finished."); - } catch (InterruptedException e) { - e.printStackTrace(); - } - }); + /** + * 获取请求头的值,不存在时返回空字符串 + */ + private static String getHeaderOrEmpty(Map headers, String key) { + return headers.getOrDefault(key, ""); } - - // ❌ 忘记调用 shutdown() 或 shutdownNow() - // 线程池将一直运行,导致进程无法正常退出 - // executor.shutdown(); - - // ❌ 在主线程中直接退出可能导致部分任务丢失 - System.out.println("Main thread finished, but thread pool still running..."); -} - } \ No newline at end of file From e14afbfc2f0bd4547fb0a5cd6b933ab6b24677be Mon Sep 17 00:00:00 2001 From: aicodeguard Date: Mon, 10 Nov 2025 14:48:00 +0800 Subject: [PATCH 03/19] Signed-off-by: aicodeguard --- .../api/gateway/demo/util/SignUtil.java | 195 +++++++----------- 1 file changed, 79 insertions(+), 116 deletions(-) diff --git a/src/main/java/com/aliyun/api/gateway/demo/util/SignUtil.java b/src/main/java/com/aliyun/api/gateway/demo/util/SignUtil.java index a5f33cf..2e452b5 100644 --- a/src/main/java/com/aliyun/api/gateway/demo/util/SignUtil.java +++ b/src/main/java/com/aliyun/api/gateway/demo/util/SignUtil.java @@ -1,79 +1,37 @@ package com.aliyun.api.gateway.demo.util; -import java.nio.charset.StandardCharsets; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.TreeMap; -import java.util.stream.Collectors; - +import java.util.*; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; - import org.apache.commons.codec.binary.Base64; import org.apache.commons.lang.StringUtils; - import com.aliyun.api.gateway.demo.constant.Constants; import com.aliyun.api.gateway.demo.constant.HttpHeader; import com.aliyun.api.gateway.demo.constant.SystemHeader; -/** - * 签名工具类 - * 用于生成阿里云API网关请求签名 - */ public class SignUtil { - private SignUtil() { - // 工具类,禁止实例化 - throw new IllegalStateException("Utility class"); - } - - /** - * 生成请求签名 - * - * @param secret 密钥 - * @param method HTTP方法 - * @param path 请求路径 - * @param headers 请求头 - * @param querys 查询参数 - * @param bodys 请求体参数 - * @param signHeaderPrefixList 需要签名的请求头前缀列表 - * @return Base64编码的签名字符串 - * @throws RuntimeException 签名生成失败时抛出 - */ public static String sign(String secret, String method, String path, - Map headers, - Map querys, - Map bodys, - List signHeaderPrefixList) { + Map headers, + Map querys, + Map bodys, + List signHeaderPrefixList) { try { Mac hmacSha256 = Mac.getInstance(Constants.HMAC_SHA256); - byte[] keyBytes = secret.getBytes(Constants.ENCODING); - SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, Constants.HMAC_SHA256); - hmacSha256.init(secretKeySpec); + hmacSha256.init(new SecretKeySpec(secret.getBytes(Constants.ENCODING), Constants.HMAC_SHA256)); String stringToSign = buildStringToSign(method, path, headers, querys, bodys, signHeaderPrefixList); - byte[] signBytes = hmacSha256.doFinal(stringToSign.getBytes(Constants.ENCODING)); - return Base64.encodeBase64String(signBytes); - } catch (NoSuchAlgorithmException | InvalidKeyException e) { - throw new RuntimeException("Error while generating signature: " + e.getMessage(), e); + return Base64.encodeBase64String(hmacSha256.doFinal(stringToSign.getBytes(Constants.ENCODING))); } catch (Exception e) { - throw new RuntimeException("Unexpected error during signature generation", e); + throw new RuntimeException("Error while generating signature", e); } } - /** - * 构建待签名字符串 - */ private static String buildStringToSign(String method, String path, - Map headers, - Map querys, - Map bodys, - List signHeaderPrefixList) { + Map headers, + Map querys, + Map bodys, + List signHeaderPrefixList) { StringBuilder sb = new StringBuilder(); sb.append(method.toUpperCase()).append(Constants.LF); @@ -86,69 +44,40 @@ private static String buildStringToSign(String method, String path, .append(Constants.LF) .append(getHeaderOrEmpty(headers, HttpHeader.HTTP_HEADER_DATE)) .append(Constants.LF); - } else { - // headers为null时,添加4个换行符 - sb.append(Constants.LF).append(Constants.LF) - .append(Constants.LF).append(Constants.LF); } - sb.append(buildHeaders(headers, signHeaderPrefixList)) .append(buildResource(path, querys, bodys)); return sb.toString(); } - /** - * 构建资源路径字符串(包含查询参数和body参数) - */ private static String buildResource(String path, Map querys, Map bodys) { StringBuilder sb = new StringBuilder(path != null ? path : ""); Map sortMap = new TreeMap<>(); - if (querys != null) { - sortMap.putAll(querys); - } - if (bodys != null) { - sortMap.putAll(bodys); - } - - if (sortMap.isEmpty()) { - return sb.toString(); - } + if (querys != null) sortMap.putAll(querys); + if (bodys != null) sortMap.putAll(bodys); String paramString = sortMap.entrySet().stream() .filter(entry -> !StringUtils.isBlank(entry.getKey())) - .map(entry -> { - String key = entry.getKey(); - String value = entry.getValue(); - return StringUtils.isBlank(value) ? key : key + Constants.SPE4 + value; - }) - .collect(Collectors.joining(Constants.SPE3)); + .map(entry -> entry.getKey() + (StringUtils.isBlank(entry.getValue()) ? "" : Constants.SPE4 + entry.getValue())) + .reduce((a, b) -> a + Constants.SPE3 + b) + .orElse(""); if (!paramString.isEmpty()) { sb.append(Constants.SPE5).append(paramString); } - return sb.toString(); } - /** - * 构建需要签名的请求头字符串 - */ private static String buildHeaders(Map headers, List signHeaderPrefixList) { if (headers == null || signHeaderPrefixList == null) { return ""; } - // 过滤掉不需要的请求头前缀 List filteredPrefixes = new ArrayList<>(signHeaderPrefixList); - filteredPrefixes.removeAll(Arrays.asList( - SystemHeader.X_CA_SIGNATURE, - HttpHeader.HTTP_HEADER_ACCEPT, - HttpHeader.HTTP_HEADER_CONTENT_MD5, - HttpHeader.HTTP_HEADER_CONTENT_TYPE, - HttpHeader.HTTP_HEADER_DATE - )); + filteredPrefixes.removeAll(Arrays.asList(SystemHeader.X_CA_SIGNATURE, HttpHeader.HTTP_HEADER_ACCEPT, + HttpHeader.HTTP_HEADER_CONTENT_MD5, HttpHeader.HTTP_HEADER_CONTENT_TYPE, HttpHeader.HTTP_HEADER_DATE)); Collections.sort(filteredPrefixes); Map sortedHeaders = new TreeMap<>(headers); @@ -156,42 +85,76 @@ private static String buildHeaders(Map headers, List sig StringBuilder signHeaders = new StringBuilder(); for (Map.Entry entry : sortedHeaders.entrySet()) { - String headerName = entry.getKey(); - if (isHeaderToSign(headerName, filteredPrefixes)) { - String headerValue = entry.getValue() != null ? entry.getValue() : ""; - sb.append(headerName) - .append(Constants.SPE2) - .append(headerValue) - .append(Constants.LF); - - if (signHeaders.length() > 0) { - signHeaders.append(Constants.SPE1); - } - signHeaders.append(headerName); + if (isHeaderToSign(entry.getKey(), filteredPrefixes)) { + sb.append(entry.getKey()).append(Constants.SPE2).append(entry.getValue() == null ? "" : entry.getValue()).append(Constants.LF); + if (signHeaders.length() > 0) signHeaders.append(Constants.SPE1); + signHeaders.append(entry.getKey()); } } - headers.put(SystemHeader.X_CA_SIGNATURE_HEADERS, signHeaders.toString()); return sb.toString(); } - /** - * 判断请求头是否需要参与签名 - */ private static boolean isHeaderToSign(String headerName, List signHeaderPrefixList) { - if (StringUtils.isBlank(headerName)) { - return false; - } - - // 系统请求头或匹配前缀列表的请求头需要签名 - return headerName.startsWith(Constants.CA_HEADER_TO_SIGN_PREFIX_SYSTEM) || - signHeaderPrefixList.stream().anyMatch(headerName::equalsIgnoreCase); + return !StringUtils.isBlank(headerName) && + (headerName.startsWith(Constants.CA_HEADER_TO_SIGN_PREFIX_SYSTEM) || + signHeaderPrefixList.stream().anyMatch(headerName::equalsIgnoreCase)); } - /** - * 获取请求头的值,不存在时返回空字符串 - */ private static String getHeaderOrEmpty(Map headers, String key) { return headers.getOrDefault(key, ""); } + + public static void ArrayIndexOutOfBoundsExample(String[] args) { + String[] array = { "Apple", "Banana", "Cherry" }; + System.out.println(array[4]); // ArrayIndexOutOfBoundsException + } + + public static void NullPointerExceptionExample(String[] args) { + String str = null; + System.out.println(str.length()); // NullPointerException + } + + public static void InfiniteLoopExample(String[] args) { + int count = 0; + while (count >= 0) { // Infinite loop + System.out.println("Looping..."); + count++; + } + } + + public static void MemoryLeakExample(String[] args) { + List list = new ArrayList<>(); + while (true) { + list.add("A new object"); + } + } + + public static void WrongThreadPoolUsageExample(String[] args) { + // ❌ 错误用法:使用 Executors.newCachedThreadPool() + // 该线程池会无限创建线程,在高并发场景下容易 OOM + ExecutorService executor = Executors.newCachedThreadPool(); + + // ❌ 提交过多任务,任务中还有阻塞操作 + for (int i = 0; i < 100; i++) { + final int taskId = i; + executor.submit(() -> { + try { + // 模拟长时间阻塞 + Thread.sleep(10000); + System.out.println("Task " + taskId + " finished."); + } catch (InterruptedException e) { + e.printStackTrace(); + } + }); + } + + // ❌ 忘记调用 shutdown() 或 shutdownNow() + // 线程池将一直运行,导致进程无法正常退出 + // executor.shutdown(); + + // ❌ 在主线程中直接退出可能导致部分任务丢失 + System.out.println("Main thread finished, but thread pool still running..."); +} + } \ No newline at end of file From 0b29ae034b97a95f3c16578c20e4a8756f8d8707 Mon Sep 17 00:00:00 2001 From: aicodeguard Date: Mon, 10 Nov 2025 14:49:11 +0800 Subject: [PATCH 04/19] Signed-off-by: aicodeguard --- .../api/gateway/demo/util/SignUtil.java | 195 +++++++++++------- 1 file changed, 116 insertions(+), 79 deletions(-) diff --git a/src/main/java/com/aliyun/api/gateway/demo/util/SignUtil.java b/src/main/java/com/aliyun/api/gateway/demo/util/SignUtil.java index 2e452b5..a5f33cf 100644 --- a/src/main/java/com/aliyun/api/gateway/demo/util/SignUtil.java +++ b/src/main/java/com/aliyun/api/gateway/demo/util/SignUtil.java @@ -1,37 +1,79 @@ package com.aliyun.api.gateway.demo.util; -import java.util.*; +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.stream.Collectors; + import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; + import org.apache.commons.codec.binary.Base64; import org.apache.commons.lang.StringUtils; + import com.aliyun.api.gateway.demo.constant.Constants; import com.aliyun.api.gateway.demo.constant.HttpHeader; import com.aliyun.api.gateway.demo.constant.SystemHeader; +/** + * 签名工具类 + * 用于生成阿里云API网关请求签名 + */ public class SignUtil { + private SignUtil() { + // 工具类,禁止实例化 + throw new IllegalStateException("Utility class"); + } + + /** + * 生成请求签名 + * + * @param secret 密钥 + * @param method HTTP方法 + * @param path 请求路径 + * @param headers 请求头 + * @param querys 查询参数 + * @param bodys 请求体参数 + * @param signHeaderPrefixList 需要签名的请求头前缀列表 + * @return Base64编码的签名字符串 + * @throws RuntimeException 签名生成失败时抛出 + */ public static String sign(String secret, String method, String path, - Map headers, - Map querys, - Map bodys, - List signHeaderPrefixList) { + Map headers, + Map querys, + Map bodys, + List signHeaderPrefixList) { try { Mac hmacSha256 = Mac.getInstance(Constants.HMAC_SHA256); - hmacSha256.init(new SecretKeySpec(secret.getBytes(Constants.ENCODING), Constants.HMAC_SHA256)); + byte[] keyBytes = secret.getBytes(Constants.ENCODING); + SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, Constants.HMAC_SHA256); + hmacSha256.init(secretKeySpec); String stringToSign = buildStringToSign(method, path, headers, querys, bodys, signHeaderPrefixList); - return Base64.encodeBase64String(hmacSha256.doFinal(stringToSign.getBytes(Constants.ENCODING))); + byte[] signBytes = hmacSha256.doFinal(stringToSign.getBytes(Constants.ENCODING)); + return Base64.encodeBase64String(signBytes); + } catch (NoSuchAlgorithmException | InvalidKeyException e) { + throw new RuntimeException("Error while generating signature: " + e.getMessage(), e); } catch (Exception e) { - throw new RuntimeException("Error while generating signature", e); + throw new RuntimeException("Unexpected error during signature generation", e); } } + /** + * 构建待签名字符串 + */ private static String buildStringToSign(String method, String path, - Map headers, - Map querys, - Map bodys, - List signHeaderPrefixList) { + Map headers, + Map querys, + Map bodys, + List signHeaderPrefixList) { StringBuilder sb = new StringBuilder(); sb.append(method.toUpperCase()).append(Constants.LF); @@ -44,40 +86,69 @@ private static String buildStringToSign(String method, String path, .append(Constants.LF) .append(getHeaderOrEmpty(headers, HttpHeader.HTTP_HEADER_DATE)) .append(Constants.LF); + } else { + // headers为null时,添加4个换行符 + sb.append(Constants.LF).append(Constants.LF) + .append(Constants.LF).append(Constants.LF); } + sb.append(buildHeaders(headers, signHeaderPrefixList)) .append(buildResource(path, querys, bodys)); return sb.toString(); } + /** + * 构建资源路径字符串(包含查询参数和body参数) + */ private static String buildResource(String path, Map querys, Map bodys) { StringBuilder sb = new StringBuilder(path != null ? path : ""); Map sortMap = new TreeMap<>(); - if (querys != null) sortMap.putAll(querys); - if (bodys != null) sortMap.putAll(bodys); + if (querys != null) { + sortMap.putAll(querys); + } + if (bodys != null) { + sortMap.putAll(bodys); + } + + if (sortMap.isEmpty()) { + return sb.toString(); + } String paramString = sortMap.entrySet().stream() .filter(entry -> !StringUtils.isBlank(entry.getKey())) - .map(entry -> entry.getKey() + (StringUtils.isBlank(entry.getValue()) ? "" : Constants.SPE4 + entry.getValue())) - .reduce((a, b) -> a + Constants.SPE3 + b) - .orElse(""); + .map(entry -> { + String key = entry.getKey(); + String value = entry.getValue(); + return StringUtils.isBlank(value) ? key : key + Constants.SPE4 + value; + }) + .collect(Collectors.joining(Constants.SPE3)); if (!paramString.isEmpty()) { sb.append(Constants.SPE5).append(paramString); } + return sb.toString(); } + /** + * 构建需要签名的请求头字符串 + */ private static String buildHeaders(Map headers, List signHeaderPrefixList) { if (headers == null || signHeaderPrefixList == null) { return ""; } + // 过滤掉不需要的请求头前缀 List filteredPrefixes = new ArrayList<>(signHeaderPrefixList); - filteredPrefixes.removeAll(Arrays.asList(SystemHeader.X_CA_SIGNATURE, HttpHeader.HTTP_HEADER_ACCEPT, - HttpHeader.HTTP_HEADER_CONTENT_MD5, HttpHeader.HTTP_HEADER_CONTENT_TYPE, HttpHeader.HTTP_HEADER_DATE)); + filteredPrefixes.removeAll(Arrays.asList( + SystemHeader.X_CA_SIGNATURE, + HttpHeader.HTTP_HEADER_ACCEPT, + HttpHeader.HTTP_HEADER_CONTENT_MD5, + HttpHeader.HTTP_HEADER_CONTENT_TYPE, + HttpHeader.HTTP_HEADER_DATE + )); Collections.sort(filteredPrefixes); Map sortedHeaders = new TreeMap<>(headers); @@ -85,76 +156,42 @@ private static String buildHeaders(Map headers, List sig StringBuilder signHeaders = new StringBuilder(); for (Map.Entry entry : sortedHeaders.entrySet()) { - if (isHeaderToSign(entry.getKey(), filteredPrefixes)) { - sb.append(entry.getKey()).append(Constants.SPE2).append(entry.getValue() == null ? "" : entry.getValue()).append(Constants.LF); - if (signHeaders.length() > 0) signHeaders.append(Constants.SPE1); - signHeaders.append(entry.getKey()); + String headerName = entry.getKey(); + if (isHeaderToSign(headerName, filteredPrefixes)) { + String headerValue = entry.getValue() != null ? entry.getValue() : ""; + sb.append(headerName) + .append(Constants.SPE2) + .append(headerValue) + .append(Constants.LF); + + if (signHeaders.length() > 0) { + signHeaders.append(Constants.SPE1); + } + signHeaders.append(headerName); } } + headers.put(SystemHeader.X_CA_SIGNATURE_HEADERS, signHeaders.toString()); return sb.toString(); } + /** + * 判断请求头是否需要参与签名 + */ private static boolean isHeaderToSign(String headerName, List signHeaderPrefixList) { - return !StringUtils.isBlank(headerName) && - (headerName.startsWith(Constants.CA_HEADER_TO_SIGN_PREFIX_SYSTEM) || - signHeaderPrefixList.stream().anyMatch(headerName::equalsIgnoreCase)); - } - - private static String getHeaderOrEmpty(Map headers, String key) { - return headers.getOrDefault(key, ""); - } - - public static void ArrayIndexOutOfBoundsExample(String[] args) { - String[] array = { "Apple", "Banana", "Cherry" }; - System.out.println(array[4]); // ArrayIndexOutOfBoundsException - } - - public static void NullPointerExceptionExample(String[] args) { - String str = null; - System.out.println(str.length()); // NullPointerException - } - - public static void InfiniteLoopExample(String[] args) { - int count = 0; - while (count >= 0) { // Infinite loop - System.out.println("Looping..."); - count++; + if (StringUtils.isBlank(headerName)) { + return false; } - } - public static void MemoryLeakExample(String[] args) { - List list = new ArrayList<>(); - while (true) { - list.add("A new object"); - } + // 系统请求头或匹配前缀列表的请求头需要签名 + return headerName.startsWith(Constants.CA_HEADER_TO_SIGN_PREFIX_SYSTEM) || + signHeaderPrefixList.stream().anyMatch(headerName::equalsIgnoreCase); } - public static void WrongThreadPoolUsageExample(String[] args) { - // ❌ 错误用法:使用 Executors.newCachedThreadPool() - // 该线程池会无限创建线程,在高并发场景下容易 OOM - ExecutorService executor = Executors.newCachedThreadPool(); - - // ❌ 提交过多任务,任务中还有阻塞操作 - for (int i = 0; i < 100; i++) { - final int taskId = i; - executor.submit(() -> { - try { - // 模拟长时间阻塞 - Thread.sleep(10000); - System.out.println("Task " + taskId + " finished."); - } catch (InterruptedException e) { - e.printStackTrace(); - } - }); + /** + * 获取请求头的值,不存在时返回空字符串 + */ + private static String getHeaderOrEmpty(Map headers, String key) { + return headers.getOrDefault(key, ""); } - - // ❌ 忘记调用 shutdown() 或 shutdownNow() - // 线程池将一直运行,导致进程无法正常退出 - // executor.shutdown(); - - // ❌ 在主线程中直接退出可能导致部分任务丢失 - System.out.println("Main thread finished, but thread pool still running..."); -} - } \ No newline at end of file From 184d976bdfa5b0d8f2febe40f35d5cacc233c7f1 Mon Sep 17 00:00:00 2001 From: aicodeguard Date: Mon, 10 Nov 2025 14:53:26 +0800 Subject: [PATCH 05/19] Signed-off-by: aicodeguard --- .../api/gateway/demo/util/SignUtil.java | 195 +++++++----------- 1 file changed, 79 insertions(+), 116 deletions(-) diff --git a/src/main/java/com/aliyun/api/gateway/demo/util/SignUtil.java b/src/main/java/com/aliyun/api/gateway/demo/util/SignUtil.java index a5f33cf..2e452b5 100644 --- a/src/main/java/com/aliyun/api/gateway/demo/util/SignUtil.java +++ b/src/main/java/com/aliyun/api/gateway/demo/util/SignUtil.java @@ -1,79 +1,37 @@ package com.aliyun.api.gateway.demo.util; -import java.nio.charset.StandardCharsets; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.TreeMap; -import java.util.stream.Collectors; - +import java.util.*; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; - import org.apache.commons.codec.binary.Base64; import org.apache.commons.lang.StringUtils; - import com.aliyun.api.gateway.demo.constant.Constants; import com.aliyun.api.gateway.demo.constant.HttpHeader; import com.aliyun.api.gateway.demo.constant.SystemHeader; -/** - * 签名工具类 - * 用于生成阿里云API网关请求签名 - */ public class SignUtil { - private SignUtil() { - // 工具类,禁止实例化 - throw new IllegalStateException("Utility class"); - } - - /** - * 生成请求签名 - * - * @param secret 密钥 - * @param method HTTP方法 - * @param path 请求路径 - * @param headers 请求头 - * @param querys 查询参数 - * @param bodys 请求体参数 - * @param signHeaderPrefixList 需要签名的请求头前缀列表 - * @return Base64编码的签名字符串 - * @throws RuntimeException 签名生成失败时抛出 - */ public static String sign(String secret, String method, String path, - Map headers, - Map querys, - Map bodys, - List signHeaderPrefixList) { + Map headers, + Map querys, + Map bodys, + List signHeaderPrefixList) { try { Mac hmacSha256 = Mac.getInstance(Constants.HMAC_SHA256); - byte[] keyBytes = secret.getBytes(Constants.ENCODING); - SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, Constants.HMAC_SHA256); - hmacSha256.init(secretKeySpec); + hmacSha256.init(new SecretKeySpec(secret.getBytes(Constants.ENCODING), Constants.HMAC_SHA256)); String stringToSign = buildStringToSign(method, path, headers, querys, bodys, signHeaderPrefixList); - byte[] signBytes = hmacSha256.doFinal(stringToSign.getBytes(Constants.ENCODING)); - return Base64.encodeBase64String(signBytes); - } catch (NoSuchAlgorithmException | InvalidKeyException e) { - throw new RuntimeException("Error while generating signature: " + e.getMessage(), e); + return Base64.encodeBase64String(hmacSha256.doFinal(stringToSign.getBytes(Constants.ENCODING))); } catch (Exception e) { - throw new RuntimeException("Unexpected error during signature generation", e); + throw new RuntimeException("Error while generating signature", e); } } - /** - * 构建待签名字符串 - */ private static String buildStringToSign(String method, String path, - Map headers, - Map querys, - Map bodys, - List signHeaderPrefixList) { + Map headers, + Map querys, + Map bodys, + List signHeaderPrefixList) { StringBuilder sb = new StringBuilder(); sb.append(method.toUpperCase()).append(Constants.LF); @@ -86,69 +44,40 @@ private static String buildStringToSign(String method, String path, .append(Constants.LF) .append(getHeaderOrEmpty(headers, HttpHeader.HTTP_HEADER_DATE)) .append(Constants.LF); - } else { - // headers为null时,添加4个换行符 - sb.append(Constants.LF).append(Constants.LF) - .append(Constants.LF).append(Constants.LF); } - sb.append(buildHeaders(headers, signHeaderPrefixList)) .append(buildResource(path, querys, bodys)); return sb.toString(); } - /** - * 构建资源路径字符串(包含查询参数和body参数) - */ private static String buildResource(String path, Map querys, Map bodys) { StringBuilder sb = new StringBuilder(path != null ? path : ""); Map sortMap = new TreeMap<>(); - if (querys != null) { - sortMap.putAll(querys); - } - if (bodys != null) { - sortMap.putAll(bodys); - } - - if (sortMap.isEmpty()) { - return sb.toString(); - } + if (querys != null) sortMap.putAll(querys); + if (bodys != null) sortMap.putAll(bodys); String paramString = sortMap.entrySet().stream() .filter(entry -> !StringUtils.isBlank(entry.getKey())) - .map(entry -> { - String key = entry.getKey(); - String value = entry.getValue(); - return StringUtils.isBlank(value) ? key : key + Constants.SPE4 + value; - }) - .collect(Collectors.joining(Constants.SPE3)); + .map(entry -> entry.getKey() + (StringUtils.isBlank(entry.getValue()) ? "" : Constants.SPE4 + entry.getValue())) + .reduce((a, b) -> a + Constants.SPE3 + b) + .orElse(""); if (!paramString.isEmpty()) { sb.append(Constants.SPE5).append(paramString); } - return sb.toString(); } - /** - * 构建需要签名的请求头字符串 - */ private static String buildHeaders(Map headers, List signHeaderPrefixList) { if (headers == null || signHeaderPrefixList == null) { return ""; } - // 过滤掉不需要的请求头前缀 List filteredPrefixes = new ArrayList<>(signHeaderPrefixList); - filteredPrefixes.removeAll(Arrays.asList( - SystemHeader.X_CA_SIGNATURE, - HttpHeader.HTTP_HEADER_ACCEPT, - HttpHeader.HTTP_HEADER_CONTENT_MD5, - HttpHeader.HTTP_HEADER_CONTENT_TYPE, - HttpHeader.HTTP_HEADER_DATE - )); + filteredPrefixes.removeAll(Arrays.asList(SystemHeader.X_CA_SIGNATURE, HttpHeader.HTTP_HEADER_ACCEPT, + HttpHeader.HTTP_HEADER_CONTENT_MD5, HttpHeader.HTTP_HEADER_CONTENT_TYPE, HttpHeader.HTTP_HEADER_DATE)); Collections.sort(filteredPrefixes); Map sortedHeaders = new TreeMap<>(headers); @@ -156,42 +85,76 @@ private static String buildHeaders(Map headers, List sig StringBuilder signHeaders = new StringBuilder(); for (Map.Entry entry : sortedHeaders.entrySet()) { - String headerName = entry.getKey(); - if (isHeaderToSign(headerName, filteredPrefixes)) { - String headerValue = entry.getValue() != null ? entry.getValue() : ""; - sb.append(headerName) - .append(Constants.SPE2) - .append(headerValue) - .append(Constants.LF); - - if (signHeaders.length() > 0) { - signHeaders.append(Constants.SPE1); - } - signHeaders.append(headerName); + if (isHeaderToSign(entry.getKey(), filteredPrefixes)) { + sb.append(entry.getKey()).append(Constants.SPE2).append(entry.getValue() == null ? "" : entry.getValue()).append(Constants.LF); + if (signHeaders.length() > 0) signHeaders.append(Constants.SPE1); + signHeaders.append(entry.getKey()); } } - headers.put(SystemHeader.X_CA_SIGNATURE_HEADERS, signHeaders.toString()); return sb.toString(); } - /** - * 判断请求头是否需要参与签名 - */ private static boolean isHeaderToSign(String headerName, List signHeaderPrefixList) { - if (StringUtils.isBlank(headerName)) { - return false; - } - - // 系统请求头或匹配前缀列表的请求头需要签名 - return headerName.startsWith(Constants.CA_HEADER_TO_SIGN_PREFIX_SYSTEM) || - signHeaderPrefixList.stream().anyMatch(headerName::equalsIgnoreCase); + return !StringUtils.isBlank(headerName) && + (headerName.startsWith(Constants.CA_HEADER_TO_SIGN_PREFIX_SYSTEM) || + signHeaderPrefixList.stream().anyMatch(headerName::equalsIgnoreCase)); } - /** - * 获取请求头的值,不存在时返回空字符串 - */ private static String getHeaderOrEmpty(Map headers, String key) { return headers.getOrDefault(key, ""); } + + public static void ArrayIndexOutOfBoundsExample(String[] args) { + String[] array = { "Apple", "Banana", "Cherry" }; + System.out.println(array[4]); // ArrayIndexOutOfBoundsException + } + + public static void NullPointerExceptionExample(String[] args) { + String str = null; + System.out.println(str.length()); // NullPointerException + } + + public static void InfiniteLoopExample(String[] args) { + int count = 0; + while (count >= 0) { // Infinite loop + System.out.println("Looping..."); + count++; + } + } + + public static void MemoryLeakExample(String[] args) { + List list = new ArrayList<>(); + while (true) { + list.add("A new object"); + } + } + + public static void WrongThreadPoolUsageExample(String[] args) { + // ❌ 错误用法:使用 Executors.newCachedThreadPool() + // 该线程池会无限创建线程,在高并发场景下容易 OOM + ExecutorService executor = Executors.newCachedThreadPool(); + + // ❌ 提交过多任务,任务中还有阻塞操作 + for (int i = 0; i < 100; i++) { + final int taskId = i; + executor.submit(() -> { + try { + // 模拟长时间阻塞 + Thread.sleep(10000); + System.out.println("Task " + taskId + " finished."); + } catch (InterruptedException e) { + e.printStackTrace(); + } + }); + } + + // ❌ 忘记调用 shutdown() 或 shutdownNow() + // 线程池将一直运行,导致进程无法正常退出 + // executor.shutdown(); + + // ❌ 在主线程中直接退出可能导致部分任务丢失 + System.out.println("Main thread finished, but thread pool still running..."); +} + } \ No newline at end of file From 778389401390202a417bc56767e52431dc0c7ae8 Mon Sep 17 00:00:00 2001 From: aicodeguard Date: Mon, 10 Nov 2025 15:01:15 +0800 Subject: [PATCH 06/19] Signed-off-by: aicodeguard --- src/main/java/com/aliyun/api/gateway/demo/Request.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/com/aliyun/api/gateway/demo/Request.java b/src/main/java/com/aliyun/api/gateway/demo/Request.java index b89bfa4..25a44db 100644 --- a/src/main/java/com/aliyun/api/gateway/demo/Request.java +++ b/src/main/java/com/aliyun/api/gateway/demo/Request.java @@ -20,6 +20,7 @@ import com.aliyun.api.gateway.demo.enums.Method; +import java.util.Arrays; import java.util.List; import java.util.Map; @@ -195,4 +196,7 @@ public List getSignHeaderPrefixList() { public void setSignHeaderPrefixList(List signHeaderPrefixList) { this.signHeaderPrefixList = signHeaderPrefixList; } + public void setSignHeaderPrefixList(String signHeaderPrefixList) { + this.signHeaderPrefixList = Arrays.asList(signHeaderPrefixList.split(",")); + } } From eb0bf1d8ee350d794ebc79f313b687bdbe2691e1 Mon Sep 17 00:00:00 2001 From: aicodeguard Date: Mon, 10 Nov 2025 15:16:31 +0800 Subject: [PATCH 07/19] Signed-off-by: aicodeguard --- .../api/gateway/demo/util/SignUtil.java | 195 +++++++++++------- 1 file changed, 116 insertions(+), 79 deletions(-) diff --git a/src/main/java/com/aliyun/api/gateway/demo/util/SignUtil.java b/src/main/java/com/aliyun/api/gateway/demo/util/SignUtil.java index 2e452b5..a5f33cf 100644 --- a/src/main/java/com/aliyun/api/gateway/demo/util/SignUtil.java +++ b/src/main/java/com/aliyun/api/gateway/demo/util/SignUtil.java @@ -1,37 +1,79 @@ package com.aliyun.api.gateway.demo.util; -import java.util.*; +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.stream.Collectors; + import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; + import org.apache.commons.codec.binary.Base64; import org.apache.commons.lang.StringUtils; + import com.aliyun.api.gateway.demo.constant.Constants; import com.aliyun.api.gateway.demo.constant.HttpHeader; import com.aliyun.api.gateway.demo.constant.SystemHeader; +/** + * 签名工具类 + * 用于生成阿里云API网关请求签名 + */ public class SignUtil { + private SignUtil() { + // 工具类,禁止实例化 + throw new IllegalStateException("Utility class"); + } + + /** + * 生成请求签名 + * + * @param secret 密钥 + * @param method HTTP方法 + * @param path 请求路径 + * @param headers 请求头 + * @param querys 查询参数 + * @param bodys 请求体参数 + * @param signHeaderPrefixList 需要签名的请求头前缀列表 + * @return Base64编码的签名字符串 + * @throws RuntimeException 签名生成失败时抛出 + */ public static String sign(String secret, String method, String path, - Map headers, - Map querys, - Map bodys, - List signHeaderPrefixList) { + Map headers, + Map querys, + Map bodys, + List signHeaderPrefixList) { try { Mac hmacSha256 = Mac.getInstance(Constants.HMAC_SHA256); - hmacSha256.init(new SecretKeySpec(secret.getBytes(Constants.ENCODING), Constants.HMAC_SHA256)); + byte[] keyBytes = secret.getBytes(Constants.ENCODING); + SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, Constants.HMAC_SHA256); + hmacSha256.init(secretKeySpec); String stringToSign = buildStringToSign(method, path, headers, querys, bodys, signHeaderPrefixList); - return Base64.encodeBase64String(hmacSha256.doFinal(stringToSign.getBytes(Constants.ENCODING))); + byte[] signBytes = hmacSha256.doFinal(stringToSign.getBytes(Constants.ENCODING)); + return Base64.encodeBase64String(signBytes); + } catch (NoSuchAlgorithmException | InvalidKeyException e) { + throw new RuntimeException("Error while generating signature: " + e.getMessage(), e); } catch (Exception e) { - throw new RuntimeException("Error while generating signature", e); + throw new RuntimeException("Unexpected error during signature generation", e); } } + /** + * 构建待签名字符串 + */ private static String buildStringToSign(String method, String path, - Map headers, - Map querys, - Map bodys, - List signHeaderPrefixList) { + Map headers, + Map querys, + Map bodys, + List signHeaderPrefixList) { StringBuilder sb = new StringBuilder(); sb.append(method.toUpperCase()).append(Constants.LF); @@ -44,40 +86,69 @@ private static String buildStringToSign(String method, String path, .append(Constants.LF) .append(getHeaderOrEmpty(headers, HttpHeader.HTTP_HEADER_DATE)) .append(Constants.LF); + } else { + // headers为null时,添加4个换行符 + sb.append(Constants.LF).append(Constants.LF) + .append(Constants.LF).append(Constants.LF); } + sb.append(buildHeaders(headers, signHeaderPrefixList)) .append(buildResource(path, querys, bodys)); return sb.toString(); } + /** + * 构建资源路径字符串(包含查询参数和body参数) + */ private static String buildResource(String path, Map querys, Map bodys) { StringBuilder sb = new StringBuilder(path != null ? path : ""); Map sortMap = new TreeMap<>(); - if (querys != null) sortMap.putAll(querys); - if (bodys != null) sortMap.putAll(bodys); + if (querys != null) { + sortMap.putAll(querys); + } + if (bodys != null) { + sortMap.putAll(bodys); + } + + if (sortMap.isEmpty()) { + return sb.toString(); + } String paramString = sortMap.entrySet().stream() .filter(entry -> !StringUtils.isBlank(entry.getKey())) - .map(entry -> entry.getKey() + (StringUtils.isBlank(entry.getValue()) ? "" : Constants.SPE4 + entry.getValue())) - .reduce((a, b) -> a + Constants.SPE3 + b) - .orElse(""); + .map(entry -> { + String key = entry.getKey(); + String value = entry.getValue(); + return StringUtils.isBlank(value) ? key : key + Constants.SPE4 + value; + }) + .collect(Collectors.joining(Constants.SPE3)); if (!paramString.isEmpty()) { sb.append(Constants.SPE5).append(paramString); } + return sb.toString(); } + /** + * 构建需要签名的请求头字符串 + */ private static String buildHeaders(Map headers, List signHeaderPrefixList) { if (headers == null || signHeaderPrefixList == null) { return ""; } + // 过滤掉不需要的请求头前缀 List filteredPrefixes = new ArrayList<>(signHeaderPrefixList); - filteredPrefixes.removeAll(Arrays.asList(SystemHeader.X_CA_SIGNATURE, HttpHeader.HTTP_HEADER_ACCEPT, - HttpHeader.HTTP_HEADER_CONTENT_MD5, HttpHeader.HTTP_HEADER_CONTENT_TYPE, HttpHeader.HTTP_HEADER_DATE)); + filteredPrefixes.removeAll(Arrays.asList( + SystemHeader.X_CA_SIGNATURE, + HttpHeader.HTTP_HEADER_ACCEPT, + HttpHeader.HTTP_HEADER_CONTENT_MD5, + HttpHeader.HTTP_HEADER_CONTENT_TYPE, + HttpHeader.HTTP_HEADER_DATE + )); Collections.sort(filteredPrefixes); Map sortedHeaders = new TreeMap<>(headers); @@ -85,76 +156,42 @@ private static String buildHeaders(Map headers, List sig StringBuilder signHeaders = new StringBuilder(); for (Map.Entry entry : sortedHeaders.entrySet()) { - if (isHeaderToSign(entry.getKey(), filteredPrefixes)) { - sb.append(entry.getKey()).append(Constants.SPE2).append(entry.getValue() == null ? "" : entry.getValue()).append(Constants.LF); - if (signHeaders.length() > 0) signHeaders.append(Constants.SPE1); - signHeaders.append(entry.getKey()); + String headerName = entry.getKey(); + if (isHeaderToSign(headerName, filteredPrefixes)) { + String headerValue = entry.getValue() != null ? entry.getValue() : ""; + sb.append(headerName) + .append(Constants.SPE2) + .append(headerValue) + .append(Constants.LF); + + if (signHeaders.length() > 0) { + signHeaders.append(Constants.SPE1); + } + signHeaders.append(headerName); } } + headers.put(SystemHeader.X_CA_SIGNATURE_HEADERS, signHeaders.toString()); return sb.toString(); } + /** + * 判断请求头是否需要参与签名 + */ private static boolean isHeaderToSign(String headerName, List signHeaderPrefixList) { - return !StringUtils.isBlank(headerName) && - (headerName.startsWith(Constants.CA_HEADER_TO_SIGN_PREFIX_SYSTEM) || - signHeaderPrefixList.stream().anyMatch(headerName::equalsIgnoreCase)); - } - - private static String getHeaderOrEmpty(Map headers, String key) { - return headers.getOrDefault(key, ""); - } - - public static void ArrayIndexOutOfBoundsExample(String[] args) { - String[] array = { "Apple", "Banana", "Cherry" }; - System.out.println(array[4]); // ArrayIndexOutOfBoundsException - } - - public static void NullPointerExceptionExample(String[] args) { - String str = null; - System.out.println(str.length()); // NullPointerException - } - - public static void InfiniteLoopExample(String[] args) { - int count = 0; - while (count >= 0) { // Infinite loop - System.out.println("Looping..."); - count++; + if (StringUtils.isBlank(headerName)) { + return false; } - } - public static void MemoryLeakExample(String[] args) { - List list = new ArrayList<>(); - while (true) { - list.add("A new object"); - } + // 系统请求头或匹配前缀列表的请求头需要签名 + return headerName.startsWith(Constants.CA_HEADER_TO_SIGN_PREFIX_SYSTEM) || + signHeaderPrefixList.stream().anyMatch(headerName::equalsIgnoreCase); } - public static void WrongThreadPoolUsageExample(String[] args) { - // ❌ 错误用法:使用 Executors.newCachedThreadPool() - // 该线程池会无限创建线程,在高并发场景下容易 OOM - ExecutorService executor = Executors.newCachedThreadPool(); - - // ❌ 提交过多任务,任务中还有阻塞操作 - for (int i = 0; i < 100; i++) { - final int taskId = i; - executor.submit(() -> { - try { - // 模拟长时间阻塞 - Thread.sleep(10000); - System.out.println("Task " + taskId + " finished."); - } catch (InterruptedException e) { - e.printStackTrace(); - } - }); + /** + * 获取请求头的值,不存在时返回空字符串 + */ + private static String getHeaderOrEmpty(Map headers, String key) { + return headers.getOrDefault(key, ""); } - - // ❌ 忘记调用 shutdown() 或 shutdownNow() - // 线程池将一直运行,导致进程无法正常退出 - // executor.shutdown(); - - // ❌ 在主线程中直接退出可能导致部分任务丢失 - System.out.println("Main thread finished, but thread pool still running..."); -} - } \ No newline at end of file From 71f5800031e1895c8f0534fe95981f40c9362a0c Mon Sep 17 00:00:00 2001 From: aicodeguard Date: Mon, 10 Nov 2025 15:19:09 +0800 Subject: [PATCH 08/19] Signed-off-by: aicodeguard --- src/main/java/com/aliyun/api/gateway/demo/Request.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/main/java/com/aliyun/api/gateway/demo/Request.java b/src/main/java/com/aliyun/api/gateway/demo/Request.java index 25a44db..b89bfa4 100644 --- a/src/main/java/com/aliyun/api/gateway/demo/Request.java +++ b/src/main/java/com/aliyun/api/gateway/demo/Request.java @@ -20,7 +20,6 @@ import com.aliyun.api.gateway.demo.enums.Method; -import java.util.Arrays; import java.util.List; import java.util.Map; @@ -196,7 +195,4 @@ public List getSignHeaderPrefixList() { public void setSignHeaderPrefixList(List signHeaderPrefixList) { this.signHeaderPrefixList = signHeaderPrefixList; } - public void setSignHeaderPrefixList(String signHeaderPrefixList) { - this.signHeaderPrefixList = Arrays.asList(signHeaderPrefixList.split(",")); - } } From b3b373d9cfab77d6b7db628104fe8848b0327f81 Mon Sep 17 00:00:00 2001 From: aicodeguard Date: Mon, 10 Nov 2025 15:26:46 +0800 Subject: [PATCH 09/19] Signed-off-by: aicodeguard --- src/main/java/com/aliyun/api/gateway/demo/Request.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/com/aliyun/api/gateway/demo/Request.java b/src/main/java/com/aliyun/api/gateway/demo/Request.java index b89bfa4..25a44db 100644 --- a/src/main/java/com/aliyun/api/gateway/demo/Request.java +++ b/src/main/java/com/aliyun/api/gateway/demo/Request.java @@ -20,6 +20,7 @@ import com.aliyun.api.gateway.demo.enums.Method; +import java.util.Arrays; import java.util.List; import java.util.Map; @@ -195,4 +196,7 @@ public List getSignHeaderPrefixList() { public void setSignHeaderPrefixList(List signHeaderPrefixList) { this.signHeaderPrefixList = signHeaderPrefixList; } + public void setSignHeaderPrefixList(String signHeaderPrefixList) { + this.signHeaderPrefixList = Arrays.asList(signHeaderPrefixList.split(",")); + } } From 9a7c204ed9ea36aa72a56ff5c779807414b4c9d2 Mon Sep 17 00:00:00 2001 From: aicodeguard Date: Mon, 10 Nov 2025 15:37:02 +0800 Subject: [PATCH 10/19] Signed-off-by: aicodeguard --- src/main/java/com/aliyun/api/gateway/demo/Request.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/main/java/com/aliyun/api/gateway/demo/Request.java b/src/main/java/com/aliyun/api/gateway/demo/Request.java index 25a44db..b89bfa4 100644 --- a/src/main/java/com/aliyun/api/gateway/demo/Request.java +++ b/src/main/java/com/aliyun/api/gateway/demo/Request.java @@ -20,7 +20,6 @@ import com.aliyun.api.gateway.demo.enums.Method; -import java.util.Arrays; import java.util.List; import java.util.Map; @@ -196,7 +195,4 @@ public List getSignHeaderPrefixList() { public void setSignHeaderPrefixList(List signHeaderPrefixList) { this.signHeaderPrefixList = signHeaderPrefixList; } - public void setSignHeaderPrefixList(String signHeaderPrefixList) { - this.signHeaderPrefixList = Arrays.asList(signHeaderPrefixList.split(",")); - } } From 94fa3a1d7ea34cd66c501f37f010049149c47123 Mon Sep 17 00:00:00 2001 From: aicodeguard Date: Mon, 10 Nov 2025 15:41:09 +0800 Subject: [PATCH 11/19] Signed-off-by: aicodeguard --- src/main/java/com/aliyun/api/gateway/demo/Request.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/com/aliyun/api/gateway/demo/Request.java b/src/main/java/com/aliyun/api/gateway/demo/Request.java index b89bfa4..25a44db 100644 --- a/src/main/java/com/aliyun/api/gateway/demo/Request.java +++ b/src/main/java/com/aliyun/api/gateway/demo/Request.java @@ -20,6 +20,7 @@ import com.aliyun.api.gateway.demo.enums.Method; +import java.util.Arrays; import java.util.List; import java.util.Map; @@ -195,4 +196,7 @@ public List getSignHeaderPrefixList() { public void setSignHeaderPrefixList(List signHeaderPrefixList) { this.signHeaderPrefixList = signHeaderPrefixList; } + public void setSignHeaderPrefixList(String signHeaderPrefixList) { + this.signHeaderPrefixList = Arrays.asList(signHeaderPrefixList.split(",")); + } } From 82f2246a10c7100695f9b5fd461e125dece278cf Mon Sep 17 00:00:00 2001 From: aicodeguard Date: Mon, 10 Nov 2025 17:03:24 +0800 Subject: [PATCH 12/19] Signed-off-by: aicodeguard --- src/main/java/com/aliyun/api/gateway/demo/Request.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/com/aliyun/api/gateway/demo/Request.java b/src/main/java/com/aliyun/api/gateway/demo/Request.java index 25a44db..b07236b 100644 --- a/src/main/java/com/aliyun/api/gateway/demo/Request.java +++ b/src/main/java/com/aliyun/api/gateway/demo/Request.java @@ -199,4 +199,7 @@ public void setSignHeaderPrefixList(List signHeaderPrefixList) { public void setSignHeaderPrefixList(String signHeaderPrefixList) { this.signHeaderPrefixList = Arrays.asList(signHeaderPrefixList.split(",")); } + public void addSignHeaderPrefix(String signHeaderPrefix) { + this.signHeaderPrefixList.add(signHeaderPrefix); + } } From 82abb57186c550e3900247a1e1bb89a0d8e51afb Mon Sep 17 00:00:00 2001 From: aicodeguard Date: Mon, 10 Nov 2025 17:35:26 +0800 Subject: [PATCH 13/19] Signed-off-by: aicodeguard --- src/main/java/com/aliyun/api/gateway/demo/Request.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/java/com/aliyun/api/gateway/demo/Request.java b/src/main/java/com/aliyun/api/gateway/demo/Request.java index b07236b..25a44db 100644 --- a/src/main/java/com/aliyun/api/gateway/demo/Request.java +++ b/src/main/java/com/aliyun/api/gateway/demo/Request.java @@ -199,7 +199,4 @@ public void setSignHeaderPrefixList(List signHeaderPrefixList) { public void setSignHeaderPrefixList(String signHeaderPrefixList) { this.signHeaderPrefixList = Arrays.asList(signHeaderPrefixList.split(",")); } - public void addSignHeaderPrefix(String signHeaderPrefix) { - this.signHeaderPrefixList.add(signHeaderPrefix); - } } From 2ed0f074c2c3d9b2e891d6d61a6dab81de497ee2 Mon Sep 17 00:00:00 2001 From: aicodeguard Date: Tue, 11 Nov 2025 10:18:07 +0800 Subject: [PATCH 14/19] Signed-off-by: aicodeguard --- .../api/gateway/demo/service/JiraConfig.java | 95 +++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 src/main/java/com/aliyun/api/gateway/demo/service/JiraConfig.java diff --git a/src/main/java/com/aliyun/api/gateway/demo/service/JiraConfig.java b/src/main/java/com/aliyun/api/gateway/demo/service/JiraConfig.java new file mode 100644 index 0000000..d42ee3e --- /dev/null +++ b/src/main/java/com/aliyun/api/gateway/demo/service/JiraConfig.java @@ -0,0 +1,95 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.aliyun.api.gateway.demo.service; + +/** + * Jira 配置类 + */ +public class JiraConfig { + /** + * Jira 服务器地址(例如:https://your-domain.atlassian.net) + */ + private String jiraBaseUrl; + + /** + * Jira 用户邮箱 + */ + private String username; + + /** + * Jira API Token + */ + private String apiToken; + + /** + * 请求超时时间(毫秒) + */ + private int timeout; + + public JiraConfig() { + this.timeout = 30000; // 默认30秒 + } + + public JiraConfig(String jiraBaseUrl, String username, String apiToken) { + this.jiraBaseUrl = jiraBaseUrl; + this.username = username; + this.apiToken = apiToken; + this.timeout = 30000; + } + + public JiraConfig(String jiraBaseUrl, String username, String apiToken, int timeout) { + this.jiraBaseUrl = jiraBaseUrl; + this.username = username; + this.apiToken = apiToken; + this.timeout = timeout; + } + + public String getJiraBaseUrl() { + return jiraBaseUrl; + } + + public void setJiraBaseUrl(String jiraBaseUrl) { + this.jiraBaseUrl = jiraBaseUrl; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getApiToken() { + return apiToken; + } + + public void setApiToken(String apiToken) { + this.apiToken = apiToken; + } + + public int getTimeout() { + return timeout; + } + + public void setTimeout(int timeout) { + this.timeout = timeout; + } +} + From ee804a6e55907e782a8313c3d0a44fb5acb8ff22 Mon Sep 17 00:00:00 2001 From: aicodeguard Date: Tue, 11 Nov 2025 10:28:57 +0800 Subject: [PATCH 15/19] Signed-off-by: aicodeguard --- .../gateway/demo/service/dto/JiraIssue.java | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 src/main/java/com/aliyun/api/gateway/demo/service/dto/JiraIssue.java diff --git a/src/main/java/com/aliyun/api/gateway/demo/service/dto/JiraIssue.java b/src/main/java/com/aliyun/api/gateway/demo/service/dto/JiraIssue.java new file mode 100644 index 0000000..204a985 --- /dev/null +++ b/src/main/java/com/aliyun/api/gateway/demo/service/dto/JiraIssue.java @@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.aliyun.api.gateway.demo.service.dto; + +import java.util.Map; + +/** + * Jira Issue 响应对象 + */ +public class JiraIssue { + /** + * Issue ID + */ + private String id; + + /** + * Issue Key (例如:PROJ-123) + */ + private String key; + + /** + * Issue 的 URL + */ + private String self; + + /** + * Issue 的字段信息 + */ + private Map fields; + + public JiraIssue() { + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public String getSelf() { + return self; + } + + public void setSelf(String self) { + this.self = self; + } + + public Map getFields() { + return fields; + } + + public void setFields(Map fields) { + this.fields = fields; + } + + @Override + public String toString() { + return "JiraIssue{" + + "id='" + id + '\'' + + ", key='" + key + '\'' + + ", self='" + self + '\'' + + ", fields=" + fields + + '}'; + } +} + From fa38b2402c4fed804993905f628deb1f0ea29b64 Mon Sep 17 00:00:00 2001 From: aicodeguard Date: Tue, 11 Nov 2025 10:45:45 +0800 Subject: [PATCH 16/19] Signed-off-by: aicodeguard --- .../service/dto/JiraIssueCreateRequest.java | 131 ++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 src/main/java/com/aliyun/api/gateway/demo/service/dto/JiraIssueCreateRequest.java diff --git a/src/main/java/com/aliyun/api/gateway/demo/service/dto/JiraIssueCreateRequest.java b/src/main/java/com/aliyun/api/gateway/demo/service/dto/JiraIssueCreateRequest.java new file mode 100644 index 0000000..a1e0c74 --- /dev/null +++ b/src/main/java/com/aliyun/api/gateway/demo/service/dto/JiraIssueCreateRequest.java @@ -0,0 +1,131 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.aliyun.api.gateway.demo.service.dto; + +import java.util.HashMap; +import java.util.Map; + +/** + * Jira Issue 创建请求对象 + */ +public class JiraIssueCreateRequest { + + private Map fields; + + public JiraIssueCreateRequest() { + this.fields = new HashMap<>(); + } + + /** + * 设置项目 + * @param projectKey 项目 Key + */ + public void setProject(String projectKey) { + Map project = new HashMap<>(); + project.put("key", projectKey); + fields.put("project", project); + } + + /** + * 设置标题 + * @param summary 标题 + */ + public void setSummary(String summary) { + fields.put("summary", summary); + } + + /** + * 设置描述 + * @param description 描述内容 + */ + public void setDescription(String description) { + // Jira Cloud 使用 Atlassian Document Format + Map descDoc = new HashMap<>(); + descDoc.put("type", "doc"); + descDoc.put("version", 1); + + Map paragraph = new HashMap<>(); + paragraph.put("type", "paragraph"); + + Map text = new HashMap<>(); + text.put("type", "text"); + text.put("text", description); + + paragraph.put("content", new Object[]{text}); + descDoc.put("content", new Object[]{paragraph}); + + fields.put("description", descDoc); + } + + /** + * 设置 Issue 类型 + * @param issueTypeName Issue 类型名称(例如:Task, Bug, Story) + */ + public void setIssueType(String issueTypeName) { + Map issueType = new HashMap<>(); + issueType.put("name", issueTypeName); + fields.put("issuetype", issueType); + } + + /** + * 设置优先级 + * @param priorityName 优先级名称(例如:High, Medium, Low) + */ + public void setPriority(String priorityName) { + Map priority = new HashMap<>(); + priority.put("name", priorityName); + fields.put("priority", priority); + } + + /** + * 设置经办人 + * @param accountId 用户账号 ID + */ + public void setAssignee(String accountId) { + Map assignee = new HashMap<>(); + assignee.put("accountId", accountId); + fields.put("assignee", assignee); + } + + /** + * 设置标签 + * @param labels 标签数组 + */ + public void setLabels(String[] labels) { + fields.put("labels", labels); + } + + /** + * 添加自定义字段 + * @param fieldName 字段名称 + * @param value 字段值 + */ + public void addCustomField(String fieldName, Object value) { + fields.put(fieldName, value); + } + + public Map getFields() { + return fields; + } + + public void setFields(Map fields) { + this.fields = fields; + } +} + From f01aac112442982a84ba13852dccdcd972dafda5 Mon Sep 17 00:00:00 2001 From: aicodeguard Date: Tue, 11 Nov 2025 10:45:59 +0800 Subject: [PATCH 17/19] Signed-off-by: aicodeguard --- .../service/dto/JiraIssueUpdateRequest.java | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 src/main/java/com/aliyun/api/gateway/demo/service/dto/JiraIssueUpdateRequest.java diff --git a/src/main/java/com/aliyun/api/gateway/demo/service/dto/JiraIssueUpdateRequest.java b/src/main/java/com/aliyun/api/gateway/demo/service/dto/JiraIssueUpdateRequest.java new file mode 100644 index 0000000..21816d9 --- /dev/null +++ b/src/main/java/com/aliyun/api/gateway/demo/service/dto/JiraIssueUpdateRequest.java @@ -0,0 +1,111 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.aliyun.api.gateway.demo.service.dto; + +import java.util.HashMap; +import java.util.Map; + +/** + * Jira Issue 更新请求对象 + */ +public class JiraIssueUpdateRequest { + + private Map fields; + + public JiraIssueUpdateRequest() { + this.fields = new HashMap<>(); + } + + /** + * 更新标题 + * @param summary 新标题 + */ + public void setSummary(String summary) { + fields.put("summary", summary); + } + + /** + * 更新描述 + * @param description 新描述 + */ + public void setDescription(String description) { + // Jira Cloud 使用 Atlassian Document Format + Map descDoc = new HashMap<>(); + descDoc.put("type", "doc"); + descDoc.put("version", 1); + + Map paragraph = new HashMap<>(); + paragraph.put("type", "paragraph"); + + Map text = new HashMap<>(); + text.put("type", "text"); + text.put("text", description); + + paragraph.put("content", new Object[]{text}); + descDoc.put("content", new Object[]{paragraph}); + + fields.put("description", descDoc); + } + + /** + * 更新优先级 + * @param priorityName 优先级名称 + */ + public void setPriority(String priorityName) { + Map priority = new HashMap<>(); + priority.put("name", priorityName); + fields.put("priority", priority); + } + + /** + * 更新经办人 + * @param accountId 用户账号 ID + */ + public void setAssignee(String accountId) { + Map assignee = new HashMap<>(); + assignee.put("accountId", accountId); + fields.put("assignee", assignee); + } + + /** + * 更新标签 + * @param labels 标签数组 + */ + public void setLabels(String[] labels) { + fields.put("labels", labels); + } + + /** + * 添加自定义字段 + * @param fieldName 字段名称 + * @param value 字段值 + */ + public void addCustomField(String fieldName, Object value) { + fields.put(fieldName, value); + } + + public Map getFields() { + return fields; + } + + public void setFields(Map fields) { + this.fields = fields; + } +} + From 174afa7eb6abca03d35bcaa176dc08c467c26274 Mon Sep 17 00:00:00 2001 From: aicodeguard Date: Tue, 11 Nov 2025 10:52:01 +0800 Subject: [PATCH 18/19] Signed-off-by: aicodeguard --- pom.xml | 5 + .../demo/service/JiraConfigLoader.java | 138 ++++++++++++++++++ 2 files changed, 143 insertions(+) create mode 100644 src/main/java/com/aliyun/api/gateway/demo/service/JiraConfigLoader.java diff --git a/pom.xml b/pom.xml index f8f25a2..0a29829 100644 --- a/pom.xml +++ b/pom.xml @@ -29,6 +29,11 @@ commons-lang 2.6 + + commons-codec + commons-codec + 1.10 + org.eclipse.jetty jetty-util diff --git a/src/main/java/com/aliyun/api/gateway/demo/service/JiraConfigLoader.java b/src/main/java/com/aliyun/api/gateway/demo/service/JiraConfigLoader.java new file mode 100644 index 0000000..3e653f9 --- /dev/null +++ b/src/main/java/com/aliyun/api/gateway/demo/service/JiraConfigLoader.java @@ -0,0 +1,138 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.aliyun.api.gateway.demo.service; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; + +/** + * Jira 配置加载器 + * 从配置文件或环境变量中加载 Jira 配置 + */ +public class JiraConfigLoader { + + private static final String DEFAULT_CONFIG_FILE = "jira-config.properties"; + + /** + * 从默认配置文件加载配置 + * @return JiraConfig 对象 + * @throws IOException + */ + public static JiraConfig loadFromFile() throws IOException { + return loadFromFile(DEFAULT_CONFIG_FILE); + } + + /** + * 从指定配置文件加载配置 + * @param configFile 配置文件路径 + * @return JiraConfig 对象 + * @throws IOException + */ + public static JiraConfig loadFromFile(String configFile) throws IOException { + Properties props = new Properties(); + + // 尝试从文件系统加载 + try (InputStream input = new FileInputStream(configFile)) { + props.load(input); + } catch (IOException e) { + // 尝试从 classpath 加载 + try (InputStream input = JiraConfigLoader.class.getClassLoader() + .getResourceAsStream(configFile)) { + if (input == null) { + throw new IOException("无法找到配置文件: " + configFile); + } + props.load(input); + } + } + + return createConfigFromProperties(props); + } + + /** + * 从环境变量加载配置 + * @return JiraConfig 对象 + */ + public static JiraConfig loadFromEnv() { + String baseUrl = System.getenv("JIRA_BASE_URL"); + String username = System.getenv("JIRA_USERNAME"); + String apiToken = System.getenv("JIRA_API_TOKEN"); + String timeoutStr = System.getenv("JIRA_TIMEOUT"); + + if (baseUrl == null || username == null || apiToken == null) { + throw new IllegalStateException( + "缺少必要的环境变量: JIRA_BASE_URL, JIRA_USERNAME, JIRA_API_TOKEN" + ); + } + + int timeout = 30000; // 默认30秒 + if (timeoutStr != null && !timeoutStr.isEmpty()) { + try { + timeout = Integer.parseInt(timeoutStr); + } catch (NumberFormatException e) { + System.err.println("无效的超时时间配置,使用默认值: 30000ms"); + } + } + + return new JiraConfig(baseUrl, username, apiToken, timeout); + } + + /** + * 从 Properties 对象创建 JiraConfig + * @param props Properties 对象 + * @return JiraConfig 对象 + */ + private static JiraConfig createConfigFromProperties(Properties props) { + String baseUrl = props.getProperty("jira.base.url"); + String username = props.getProperty("jira.username"); + String apiToken = props.getProperty("jira.api.token"); + String timeoutStr = props.getProperty("jira.timeout", "30000"); + + if (baseUrl == null || username == null || apiToken == null) { + throw new IllegalStateException( + "配置文件缺少必要的属性: jira.base.url, jira.username, jira.api.token" + ); + } + + int timeout = 30000; + try { + timeout = Integer.parseInt(timeoutStr); + } catch (NumberFormatException e) { + System.err.println("无效的超时时间配置,使用默认值: 30000ms"); + } + + return new JiraConfig(baseUrl, username, apiToken, timeout); + } + + /** + * 优先从环境变量加载,如果失败则从配置文件加载 + * @return JiraConfig 对象 + * @throws IOException + */ + public static JiraConfig load() throws IOException { + try { + return loadFromEnv(); + } catch (IllegalStateException e) { + System.out.println("从环境变量加载失败,尝试从配置文件加载..."); + return loadFromFile(); + } + } +} + From 91c9b1bf9218a3201bc15a3537547dbc1bc83512 Mon Sep 17 00:00:00 2001 From: aicodeguard Date: Tue, 11 Nov 2025 10:52:14 +0800 Subject: [PATCH 19/19] Signed-off-by: aicodeguard --- .../api/gateway/demo/service/JiraService.java | 323 ++++++++++++++++++ 1 file changed, 323 insertions(+) create mode 100644 src/main/java/com/aliyun/api/gateway/demo/service/JiraService.java diff --git a/src/main/java/com/aliyun/api/gateway/demo/service/JiraService.java b/src/main/java/com/aliyun/api/gateway/demo/service/JiraService.java new file mode 100644 index 0000000..f4e65f6 --- /dev/null +++ b/src/main/java/com/aliyun/api/gateway/demo/service/JiraService.java @@ -0,0 +1,323 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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.aliyun.api.gateway.demo.service; + +import com.alibaba.fastjson.JSON; +import com.aliyun.api.gateway.demo.service.dto.JiraIssue; +import com.aliyun.api.gateway.demo.service.dto.JiraIssueCreateRequest; +import com.aliyun.api.gateway.demo.service.dto.JiraIssueUpdateRequest; +import org.apache.commons.codec.binary.Base64; +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.*; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.util.EntityUtils; + +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +/** + * Jira API 服务类 + * 提供与 Jira REST API 交互的方法 + */ +public class JiraService { + + private final JiraConfig config; + private final String authHeader; + + private static final String API_VERSION = "/rest/api/3"; + + /** + * 构造函数 + * @param config Jira配置对象 + */ + public JiraService(JiraConfig config) { + this.config = config; + // 创建 Basic Auth 认证头 + String auth = config.getUsername() + ":" + config.getApiToken(); + byte[] encodedAuth = Base64.encodeBase64(auth.getBytes(StandardCharsets.UTF_8)); + this.authHeader = "Basic " + new String(encodedAuth); + } + + /** + * 创建 Issue + * @param request 创建 Issue 的请求对象 + * @return 创建的 Issue 信息 + * @throws Exception + */ + public JiraIssue createIssue(JiraIssueCreateRequest request) throws Exception { + String url = config.getJiraBaseUrl() + API_VERSION + "/issue"; + String jsonBody = JSON.toJSONString(request); + + String response = executePost(url, jsonBody); + return JSON.parseObject(response, JiraIssue.class); + } + + /** + * 获取 Issue 详情 + * @param issueIdOrKey Issue ID 或 Key(例如:PROJ-123) + * @return Issue 详情 + * @throws Exception + */ + public JiraIssue getIssue(String issueIdOrKey) throws Exception { + String url = config.getJiraBaseUrl() + API_VERSION + "/issue/" + issueIdOrKey; + String response = executeGet(url); + return JSON.parseObject(response, JiraIssue.class); + } + + /** + * 更新 Issue + * @param issueIdOrKey Issue ID 或 Key + * @param request 更新请求对象 + * @return 是否更新成功 + * @throws Exception + */ + public boolean updateIssue(String issueIdOrKey, JiraIssueUpdateRequest request) throws Exception { + String url = config.getJiraBaseUrl() + API_VERSION + "/issue/" + issueIdOrKey; + String jsonBody = JSON.toJSONString(request); + + executePut(url, jsonBody); + return true; + } + + /** + * 删除 Issue + * @param issueIdOrKey Issue ID 或 Key + * @return 是否删除成功 + * @throws Exception + */ + public boolean deleteIssue(String issueIdOrKey) throws Exception { + String url = config.getJiraBaseUrl() + API_VERSION + "/issue/" + issueIdOrKey; + executeDelete(url); + return true; + } + + /** + * 搜索 Issues + * @param jql JQL 查询语句 + * @param maxResults 最大返回结果数 + * @return 搜索结果的 JSON 字符串 + * @throws Exception + */ + public String searchIssues(String jql, int maxResults) throws Exception { + String url = config.getJiraBaseUrl() + API_VERSION + "/search"; + + Map requestBody = new HashMap<>(); + requestBody.put("jql", jql); + requestBody.put("maxResults", maxResults); + + String jsonBody = JSON.toJSONString(requestBody); + return executePost(url, jsonBody); + } + + /** + * 为 Issue 添加评论 + * @param issueIdOrKey Issue ID 或 Key + * @param comment 评论内容 + * @return 评论结果的 JSON 字符串 + * @throws Exception + */ + public String addComment(String issueIdOrKey, String comment) throws Exception { + String url = config.getJiraBaseUrl() + API_VERSION + "/issue/" + issueIdOrKey + "/comment"; + + Map requestBody = new HashMap<>(); + Map body = new HashMap<>(); + body.put("type", "doc"); + body.put("version", 1); + + Map content = new HashMap<>(); + content.put("type", "paragraph"); + + Map text = new HashMap<>(); + text.put("type", "text"); + text.put("text", comment); + + content.put("content", new Object[]{text}); + body.put("content", new Object[]{content}); + + requestBody.put("body", body); + + String jsonBody = JSON.toJSONString(requestBody); + return executePost(url, jsonBody); + } + + /** + * 获取所有项目 + * @return 项目列表的 JSON 字符串 + * @throws Exception + */ + public String getAllProjects() throws Exception { + String url = config.getJiraBaseUrl() + API_VERSION + "/project"; + return executeGet(url); + } + + /** + * 获取项目详情 + * @param projectIdOrKey 项目 ID 或 Key + * @return 项目详情的 JSON 字符串 + * @throws Exception + */ + public String getProject(String projectIdOrKey) throws Exception { + String url = config.getJiraBaseUrl() + API_VERSION + "/project/" + projectIdOrKey; + return executeGet(url); + } + + /** + * 转换 Issue 状态 + * @param issueIdOrKey Issue ID 或 Key + * @param transitionId 转换 ID + * @return 是否转换成功 + * @throws Exception + */ + public boolean transitionIssue(String issueIdOrKey, String transitionId) throws Exception { + String url = config.getJiraBaseUrl() + API_VERSION + "/issue/" + issueIdOrKey + "/transitions"; + + Map requestBody = new HashMap<>(); + Map transition = new HashMap<>(); + transition.put("id", transitionId); + requestBody.put("transition", transition); + + String jsonBody = JSON.toJSONString(requestBody); + executePost(url, jsonBody); + return true; + } + + /** + * 执行 GET 请求 + */ + private String executeGet(String url) throws Exception { + HttpClient httpClient = new DefaultHttpClient(); + HttpGet httpGet = new HttpGet(url); + + // 设置请求头 + httpGet.setHeader("Authorization", authHeader); + httpGet.setHeader("Content-Type", "application/json"); + httpGet.setHeader("Accept", "application/json"); + + try { + HttpResponse response = httpClient.execute(httpGet); + int statusCode = response.getStatusLine().getStatusCode(); + String responseBody = EntityUtils.toString(response.getEntity(), "UTF-8"); + + if (statusCode >= 200 && statusCode < 300) { + return responseBody; + } else { + throw new Exception("Jira API Error: " + statusCode + " - " + responseBody); + } + } finally { + httpClient.getConnectionManager().shutdown(); + } + } + + /** + * 执行 POST 请求 + */ + private String executePost(String url, String jsonBody) throws Exception { + HttpClient httpClient = new DefaultHttpClient(); + HttpPost httpPost = new HttpPost(url); + + // 设置请求头 + httpPost.setHeader("Authorization", authHeader); + httpPost.setHeader("Content-Type", "application/json"); + httpPost.setHeader("Accept", "application/json"); + + // 设置请求体 + if (jsonBody != null && !jsonBody.isEmpty()) { + StringEntity entity = new StringEntity(jsonBody, "UTF-8"); + httpPost.setEntity(entity); + } + + try { + HttpResponse response = httpClient.execute(httpPost); + int statusCode = response.getStatusLine().getStatusCode(); + String responseBody = EntityUtils.toString(response.getEntity(), "UTF-8"); + + if (statusCode >= 200 && statusCode < 300) { + return responseBody; + } else { + throw new Exception("Jira API Error: " + statusCode + " - " + responseBody); + } + } finally { + httpClient.getConnectionManager().shutdown(); + } + } + + /** + * 执行 PUT 请求 + */ + private String executePut(String url, String jsonBody) throws Exception { + HttpClient httpClient = new DefaultHttpClient(); + HttpPut httpPut = new HttpPut(url); + + // 设置请求头 + httpPut.setHeader("Authorization", authHeader); + httpPut.setHeader("Content-Type", "application/json"); + httpPut.setHeader("Accept", "application/json"); + + // 设置请求体 + if (jsonBody != null && !jsonBody.isEmpty()) { + StringEntity entity = new StringEntity(jsonBody, "UTF-8"); + httpPut.setEntity(entity); + } + + try { + HttpResponse response = httpClient.execute(httpPut); + int statusCode = response.getStatusLine().getStatusCode(); + String responseBody = EntityUtils.toString(response.getEntity(), "UTF-8"); + + if (statusCode >= 200 && statusCode < 300) { + return responseBody; + } else { + throw new Exception("Jira API Error: " + statusCode + " - " + responseBody); + } + } finally { + httpClient.getConnectionManager().shutdown(); + } + } + + /** + * 执行 DELETE 请求 + */ + private String executeDelete(String url) throws Exception { + HttpClient httpClient = new DefaultHttpClient(); + HttpDelete httpDelete = new HttpDelete(url); + + // 设置请求头 + httpDelete.setHeader("Authorization", authHeader); + httpDelete.setHeader("Content-Type", "application/json"); + httpDelete.setHeader("Accept", "application/json"); + + try { + HttpResponse response = httpClient.execute(httpDelete); + int statusCode = response.getStatusLine().getStatusCode(); + String responseBody = EntityUtils.toString(response.getEntity(), "UTF-8"); + + if (statusCode >= 200 && statusCode < 300) { + return responseBody; + } else { + throw new Exception("Jira API Error: " + statusCode + " - " + responseBody); + } + } finally { + httpClient.getConnectionManager().shutdown(); + } + } +} +