From 8c746f95bbc458d99eca20381287a099b34adcff Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Fri, 18 Sep 2026 21:24:08 +0000 Subject: [PATCH 1/5] =?UTF-8?q?=EC=84=B1=EB=8A=A5=20=EC=B5=9C=EC=A0=81?= =?UTF-8?q?=ED=99=94:=20=ED=86=A0=ED=81=B0=20=ED=8C=8C=EC=8B=B1=20?= =?UTF-8?q?=EC=8B=9C=20=EB=B6=88=ED=95=84=EC=9A=94=ED=95=9C=20String.join?= =?UTF-8?q?=20=EA=B0=9D=EC=B2=B4=20=EC=83=9D=EC=84=B1=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .jules/bolt.md | 3 +++ .../viewer/artifact/ArtifactLinkService.java | 15 +++++++++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/.jules/bolt.md b/.jules/bolt.md index 284d180a7..a41cada71 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -19,3 +19,6 @@ ## 2026-07-13 - 단일 패스 문자열 치환 최적화 (O(N) 단일 스캔 및 지연 할당) **Learning:** `String.replace()`를 여러 번 체이닝하여 호출하면, 문자열 치환이 발생하지 않는 경우에도 내부적으로 불필요한 스캔이 중복 발생하고, 치환 시마다 새로운 문자열 객체와 char 배열이 할당되어 메모리 낭비와 성능 저하(GC 압박)가 발생한다. **Action:** 여러 문자를 한 번에 치환해야 하는 경우, O(N) 단일 스캔을 통해 `charAt()`으로 문자를 확인하고, 치환이 실제로 필요한 경우에만 `StringBuilder`를 지연 할당(Lazy allocation)하여 성능을 최적화하고 불필요한 메모리 할당을 방지한다. +## 2026-07-14 - 토큰 파싱 시 불필요한 String.join 객체 생성 방지 성능 최적화 +**Learning:** 문자열을 특정 구분자로 나눌 때 `String.split()` 후 일부 배열 요소를 다시 `String.join()`으로 합치는 것은 불필요한 배열 복사와 문자열 생성으로 인한 가비지 컬렉션(GC) 압박과 성능 저하를 유발합니다. +**Action:** `lastIndexOf`와 `substring`을 사용하여 서명과 페이로드를 먼저 분리한 뒤, 필요한 부분만 `split()`하여 불필요한 `Arrays.copyOf` 및 `String.join` 연산을 방지함으로써 메모리 사용량과 성능을 최적화해야 합니다. diff --git a/src/main/java/com/clearfolio/viewer/artifact/ArtifactLinkService.java b/src/main/java/com/clearfolio/viewer/artifact/ArtifactLinkService.java index 79a972e3d..63bf7ef6a 100644 --- a/src/main/java/com/clearfolio/viewer/artifact/ArtifactLinkService.java +++ b/src/main/java/com/clearfolio/viewer/artifact/ArtifactLinkService.java @@ -333,16 +333,23 @@ public static String resolveToken(String queryToken, String authorizationHeader) } private ArtifactTokenClaims parseAndVerify(String token) { - String[] parts = token.split("\\.", -1); - if (parts.length != TOKEN_FIELD_COUNT + 1) { + int lastDotIndex = token.lastIndexOf('.'); + if (lastDotIndex == -1) { + throw new ArtifactTokenException(HttpStatus.UNAUTHORIZED, "artifact token invalid"); + } + + String payload = token.substring(0, lastDotIndex); + String providedSignature = token.substring(lastDotIndex + 1); + + String[] parts = payload.split("\\.", -1); + if (parts.length != TOKEN_FIELD_COUNT) { throw new ArtifactTokenException(HttpStatus.UNAUTHORIZED, "artifact token invalid"); } - String payload = String.join(".", Arrays.copyOf(parts, TOKEN_FIELD_COUNT)); String expectedSignature = hmac(payload); if (!MessageDigest.isEqual( expectedSignature.getBytes(StandardCharsets.US_ASCII), - parts[TOKEN_FIELD_COUNT].getBytes(StandardCharsets.US_ASCII))) { + providedSignature.getBytes(StandardCharsets.US_ASCII))) { throw new ArtifactTokenException(HttpStatus.UNAUTHORIZED, "artifact token invalid"); } From 6b4989943304d86f612f3903dfb4d3349e8ada63 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Sat, 19 Sep 2026 09:34:36 +0000 Subject: [PATCH 2/5] =?UTF-8?q?=EC=84=B1=EB=8A=A5=20=EC=B5=9C=EC=A0=81?= =?UTF-8?q?=ED=99=94:=20=ED=86=A0=ED=81=B0=20=ED=8C=8C=EC=8B=B1=20?= =?UTF-8?q?=EC=8B=9C=20=EB=B6=88=ED=95=84=EC=9A=94=ED=95=9C=20String.join?= =?UTF-8?q?=20=EA=B0=9D=EC=B2=B4=20=EC=83=9D=EC=84=B1=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From db16950e47a75bc9fa566a3e739939fb19ba87f7 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Sat, 19 Sep 2026 18:41:54 +0000 Subject: [PATCH 3/5] =?UTF-8?q?=EC=84=B1=EB=8A=A5=20=EC=B5=9C=EC=A0=81?= =?UTF-8?q?=ED=99=94:=20=ED=86=A0=ED=81=B0=20=ED=8C=8C=EC=8B=B1=20?= =?UTF-8?q?=EC=8B=9C=20=EB=B6=88=ED=95=84=EC=9A=94=ED=95=9C=20String.join?= =?UTF-8?q?=20=EA=B0=9D=EC=B2=B4=20=EC=83=9D=EC=84=B1=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From a154224c830a7ac6f04d43eb40df7fb51d0d3b3d Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Sun, 20 Sep 2026 07:04:05 +0000 Subject: [PATCH 4/5] =?UTF-8?q?=EC=84=B1=EB=8A=A5=20=EC=B5=9C=EC=A0=81?= =?UTF-8?q?=ED=99=94:=20=ED=86=A0=ED=81=B0=20=ED=8C=8C=EC=8B=B1=20?= =?UTF-8?q?=EC=8B=9C=20=EB=B6=88=ED=95=84=EC=9A=94=ED=95=9C=20String.join?= =?UTF-8?q?=20=EA=B0=9D=EC=B2=B4=20=EC=83=9D=EC=84=B1=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From 6dcd8fbfb2d62f851cf8d39ae6c9f3639970f6ee Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Mon, 21 Sep 2026 01:07:30 +0000 Subject: [PATCH 5/5] =?UTF-8?q?=EC=84=B1=EB=8A=A5=20=EC=B5=9C=EC=A0=81?= =?UTF-8?q?=ED=99=94:=20=ED=86=A0=ED=81=B0=20=ED=8C=8C=EC=8B=B1=20?= =?UTF-8?q?=EC=8B=9C=20=EB=B6=88=ED=95=84=EC=9A=94=ED=95=9C=20String.join?= =?UTF-8?q?=20=EA=B0=9D=EC=B2=B4=20=EC=83=9D=EC=84=B1=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit