-
Notifications
You must be signed in to change notification settings - Fork 0
[REFACTOR] 코드 통합 #332
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[REFACTOR] 코드 통합 #332
Changes from all commits
b8767e4
b561196
b32c485
57148bf
2dc9ea1
9c99755
d08c539
c3468e6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,39 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| package com.samhap.kokomen.global.aop; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import lombok.extern.slf4j.Slf4j; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.aspectj.lang.ProceedingJoinPoint; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.aspectj.lang.annotation.Around; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.aspectj.lang.annotation.Aspect; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.core.annotation.Order; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.stereotype.Component; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.util.StopWatch; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Slf4j | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Order(1) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Aspect | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Component | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public class TosspaymentsLoggingAspect { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Around("execution(* com.samhap.kokomen.payment.external.TosspaymentsClient.*(..))") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public Object logTosspaymentsApiCall(ProceedingJoinPoint joinPoint) throws Throwable { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| String methodName = joinPoint.getSignature().getName(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Object[] args = joinPoint.getArgs(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| log.info("[토스페이먼츠 API 요청] {} - args: {}", methodName, args); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| StopWatch stopWatch = new StopWatch(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| stopWatch.start(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Object result = joinPoint.proceed(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| stopWatch.stop(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| log.info("[토스페이먼츠 API 응답] {} - {}ms - response: {}", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| methodName, stopWatch.getTotalTimeMillis(), result); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return result; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (Throwable e) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| stopWatch.stop(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| log.error("[토스페이먼츠 API 에러] {} - {}ms - error: {}", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| methodName, stopWatch.getTotalTimeMillis(), e.getMessage()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| throw e; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+18
to
+38
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 예외 발생 시 경과 시간 및 에러 로깅 누락
♻️ 예외 처리 추가 제안 `@Around`("execution(* com.samhap.kokomen.payment.external.TosspaymentsClient.*(..))")
public Object logTosspaymentsApiCall(ProceedingJoinPoint joinPoint) throws Throwable {
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
log.info("[토스페이먼츠 API 요청] {} - args: {}", methodName, args);
StopWatch stopWatch = new StopWatch();
stopWatch.start();
- Object result = joinPoint.proceed();
- stopWatch.stop();
- log.info("[토스페이먼츠 API 응답] {} - {}ms - response: {}",
- methodName, stopWatch.getTotalTimeMillis(), result);
- return result;
+ try {
+ Object result = joinPoint.proceed();
+ stopWatch.stop();
+ log.info("[토스페이먼츠 API 응답] {} - {}ms - response: {}",
+ methodName, stopWatch.getTotalTimeMillis(), result);
+ return result;
+ } catch (Throwable e) {
+ stopWatch.stop();
+ log.error("[토스페이먼츠 API 에러] {} - {}ms - error: {}",
+ methodName, stopWatch.getTotalTimeMillis(), e.getMessage());
+ throw e;
+ }
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| package com.samhap.kokomen.global.config; | ||
|
|
||
| import org.springframework.beans.factory.annotation.Value; | ||
| import org.springframework.context.annotation.Bean; | ||
| import org.springframework.context.annotation.Configuration; | ||
| import org.springframework.retry.backoff.ExponentialBackOffPolicy; | ||
| import org.springframework.retry.support.RetryTemplate; | ||
|
|
||
| @Configuration | ||
| public class RetryConfig { | ||
|
|
||
| @Value("${retry.tosspayments.max-attempts}") | ||
| private int maxAttempts; | ||
|
|
||
| @Value("${retry.tosspayments.initial-interval}") | ||
| private long initialInterval; | ||
|
|
||
| @Value("${retry.tosspayments.multiplier}") | ||
| private double multiplier; | ||
|
|
||
| @Value("${retry.tosspayments.max-interval}") | ||
| private long maxInterval; | ||
|
Comment on lines
+12
to
+22
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial
4개의 🤖 Prompt for AI Agents |
||
|
|
||
| @Bean | ||
| public RetryTemplate tosspaymentsConfirmRetryTemplate() { | ||
| RetryTemplate retryTemplate = new RetryTemplate(); | ||
|
|
||
| TosspaymentsConfirmRetryPolicy retryPolicy = new TosspaymentsConfirmRetryPolicy(maxAttempts); | ||
| retryTemplate.setRetryPolicy(retryPolicy); | ||
|
|
||
| ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy(); | ||
| backOffPolicy.setInitialInterval(initialInterval); | ||
| backOffPolicy.setMultiplier(multiplier); | ||
| backOffPolicy.setMaxInterval(maxInterval); | ||
| retryTemplate.setBackOffPolicy(backOffPolicy); | ||
|
|
||
| return retryTemplate; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| package com.samhap.kokomen.global.config; | ||
|
|
||
| import org.springframework.retry.RetryContext; | ||
| import org.springframework.retry.policy.SimpleRetryPolicy; | ||
| import org.springframework.web.client.HttpClientErrorException; | ||
| import org.springframework.web.client.HttpServerErrorException; | ||
| import org.springframework.web.client.ResourceAccessException; | ||
|
|
||
| public class TosspaymentsConfirmRetryPolicy extends SimpleRetryPolicy { | ||
|
|
||
| public TosspaymentsConfirmRetryPolicy(int maxAttempts) { | ||
| super(maxAttempts); | ||
| } | ||
|
|
||
| @Override | ||
| public boolean canRetry(RetryContext context) { | ||
| Throwable lastException = context.getLastThrowable(); | ||
| if (lastException != null && !isRetryableException(lastException)) { | ||
| return false; | ||
| } | ||
| return super.canRetry(context); | ||
| } | ||
|
|
||
| private boolean isRetryableException(Throwable throwable) { | ||
| if (throwable instanceof HttpServerErrorException) { | ||
| return true; | ||
| } | ||
| if (throwable instanceof ResourceAccessException) { | ||
| return true; | ||
| } | ||
| if (throwable instanceof HttpClientErrorException e) { | ||
| return e.getStatusCode().value() == 409; | ||
| } | ||
| return false; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,4 +7,8 @@ public class BadRequestException extends KokomenException { | |
| public BadRequestException(String message) { | ||
| super(message, HttpStatus.BAD_REQUEST); | ||
| } | ||
|
|
||
| public BadRequestException(String message, Throwable cause) { | ||
| super(message, cause, HttpStatus.BAD_REQUEST); | ||
| } | ||
|
Comment on lines
+11
to
+13
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,13 +6,13 @@ | |
| import org.springframework.http.HttpStatus; | ||
| import org.springframework.http.ResponseEntity; | ||
| import org.springframework.http.converter.HttpMessageNotReadableException; | ||
| import org.springframework.web.HttpMediaTypeNotSupportedException; | ||
| import org.springframework.web.bind.MethodArgumentNotValidException; | ||
| import org.springframework.web.bind.MissingServletRequestParameterException; | ||
| import org.springframework.web.bind.annotation.ExceptionHandler; | ||
| import org.springframework.web.bind.annotation.RestControllerAdvice; | ||
| import org.springframework.web.servlet.resource.NoResourceFoundException; | ||
|
|
||
| // TODO: HttpMessageNotReadableException 예외 처리 추가 | ||
| @Slf4j | ||
| @RestControllerAdvice | ||
| public class GlobalExceptionHandler { | ||
|
|
@@ -93,6 +93,14 @@ public ResponseEntity<Void> handleNoResourceFound(NoResourceFoundException e) { | |
| return ResponseEntity.status(HttpStatus.NOT_FOUND).build(); | ||
| } | ||
|
|
||
| @ExceptionHandler(HttpMediaTypeNotSupportedException.class) | ||
| public ResponseEntity<ErrorResponse> handleHttpMediaTypeNotSupportedException( | ||
| HttpMediaTypeNotSupportedException e) { | ||
| log.warn("HttpMediaTypeNotSupportedException :: message: {}", e.getMessage()); | ||
| return ResponseEntity.status(HttpStatus.UNSUPPORTED_MEDIA_TYPE) | ||
| .body(new ErrorResponse("지원하지 않는 Content-Type입니다.")); | ||
| } | ||
|
Comment on lines
+96
to
+102
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| @ExceptionHandler(Exception.class) | ||
| public ResponseEntity<ErrorResponse> handleException(Exception e) { | ||
| log.error("Exception :: status: {}, message: {}, stackTrace: ", HttpStatus.INTERNAL_SERVER_ERROR, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| package com.samhap.kokomen.global.exception; | ||
|
|
||
| import org.springframework.http.HttpStatus; | ||
|
|
||
| public class InternalServerErrorException extends KokomenException { | ||
|
|
||
| public InternalServerErrorException(String message) { | ||
| super(message, HttpStatus.INTERNAL_SERVER_ERROR); | ||
| } | ||
|
|
||
| public InternalServerErrorException(String message, Throwable cause) { | ||
| super(message, cause, HttpStatus.INTERNAL_SERVER_ERROR); | ||
| } | ||
| } | ||
|
Comment on lines
+1
to
+14
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| package com.samhap.kokomen.global.exception; | ||
|
|
||
| import org.springframework.http.HttpStatus; | ||
|
|
||
| public class NotFoundException extends KokomenException { | ||
|
|
||
| public NotFoundException(String message) { | ||
| super(message, HttpStatus.NOT_FOUND); | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| public NotFoundException(String message, Throwable cause) { | ||
| super(message, cause, HttpStatus.NOT_FOUND); | ||
| } | ||
| } | ||
|
Comment on lines
+1
to
+14
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| package com.samhap.kokomen.global.exception; | ||
|
|
||
| import lombok.Getter; | ||
|
|
||
| @Getter | ||
| public enum PaymentServiceErrorMessage { | ||
|
|
||
| MISSING_REQUEST_PARAMETER("필수 요청 파라미터가 누락되었습니다."), | ||
| JSON_PARSE_ERROR("JSON 파싱 오류: 유효하지 않은 값이 전달되었습니다."), | ||
| PAYMENT_NOT_FOUND_BY_ID("해당 id의 결제 정보가 존재하지 않습니다."), | ||
| PAYMENT_NOT_FOUND_BY_PAYMENT_KEY("해당 paymentKey의 결제 정보가 존재하지 않습니다."), | ||
| PAYMENT_RESULT_NOT_FOUND("해당 결제의 결과 정보가 존재하지 않습니다."), | ||
| CONFIRM_SERVER_ERROR("결제 처리 중 서버 오류가 발생했습니다."), | ||
| CANCEL_SERVER_ERROR("결제 취소 처리 중 서버 오류가 발생했습니다."), | ||
| CANCEL_NETWORK_ERROR("결제 취소 처리 중 네트워크 오류가 발생했습니다."), | ||
| INVALID_REQUEST("잘못된 요청입니다."), | ||
| INVALID_REQUEST_FORMAT("잘못된 요청 형식입니다."), | ||
| INTERNAL_SERVER_ERROR("서버에 문제가 발생하였습니다."); | ||
|
|
||
| private final String message; | ||
|
|
||
| PaymentServiceErrorMessage(String message) { | ||
| this.message = message; | ||
| } | ||
| } | ||
|
Comment on lines
+1
to
+25
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Comment on lines
+1
to
+25
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial
🤖 Prompt for AI Agents |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| package com.samhap.kokomen.global.infrastructure; | ||
|
|
||
| import com.fasterxml.jackson.core.JsonParser; | ||
| import com.fasterxml.jackson.databind.DeserializationContext; | ||
| import com.fasterxml.jackson.databind.JsonDeserializer; | ||
| import com.fasterxml.jackson.databind.JsonNode; | ||
| import com.fasterxml.jackson.databind.ObjectMapper; | ||
| import java.io.IOException; | ||
|
|
||
| public class ObjectToStringDeserializer extends JsonDeserializer<String> { | ||
|
|
||
| @Override | ||
| public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { | ||
| JsonNode node = p.getCodec().readTree(p); | ||
|
|
||
| // 이미 문자열인 경우 그대로 반환 | ||
| if (node.isTextual()) { | ||
| return node.asText(); | ||
| } | ||
|
|
||
| // 객체나 배열인 경우 JSON 문자열로 변환 | ||
| ObjectMapper mapper = new ObjectMapper(); | ||
| return mapper.writeValueAsString(node); | ||
|
Comment on lines
+12
to
+23
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # First, find and examine the file
find . -name "ObjectToStringDeserializer.java" -type fRepository: samhap-soft/kokomen-backend Length of output: 159 🏁 Script executed: # Search for the file to confirm it exists
git ls-files | grep -i objecttostringdeserializerRepository: samhap-soft/kokomen-backend Length of output: 157 🏁 Script executed: # If found, examine its content
fd ObjectToStringDeserializer.java -x cat -n {}Repository: samhap-soft/kokomen-backend Length of output: 1071 🏁 Script executed: # Check if there are tests for this deserializer
fd -e java | xargs grep -l "ObjectToStringDeserializer" 2>/dev/nullRepository: samhap-soft/kokomen-backend Length of output: 391 🏁 Script executed: # Search for `@NotBlank` usage that might depend on this deserializer
rg "@NotBlank" -A 2 -B 2 --max-count=5Repository: samhap-soft/kokomen-backend Length of output: 9407 🏁 Script executed: # Create a test to verify JsonNode and ObjectMapper behavior with null
python3 << 'EOF'
import subprocess
import json
# Create a test Java file to verify Jackson behavior
test_code = """
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
public class JacksonBehaviorTest {
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
// Test null JSON
String nullJson = "null";
JsonNode nullNode = mapper.readTree(nullJson);
System.out.println("nullNode.isNull(): " + nullNode.isNull());
System.out.println("nullNode.isMissingNode(): " + nullNode.isMissingNode());
System.out.println("nullNode.isTextual(): " + nullNode.isTextual());
String result1 = mapper.writeValueAsString(nullNode);
System.out.println("mapper.writeValueAsString(nullNode): " + result1);
System.out.println("Length: " + result1.length());
String result2 = nullNode.toString();
System.out.println("nullNode.toString(): " + result2);
System.out.println("Length: " + result2.length());
// Test object JSON
String objectJson = "{\"key\":\"value\"}";
JsonNode objectNode = mapper.readTree(objectJson);
System.out.println("\\nobjectNode.toString(): " + objectNode.toString());
System.out.println("mapper.writeValueAsString(objectNode): " + mapper.writeValueAsString(objectNode));
}
}
"""
print(test_code)
EOFRepository: samhap-soft/kokomen-backend Length of output: 1424 🏁 Script executed: # Check if there are any existing tests for ObjectToStringDeserializer
fd -e java | xargs grep -l "ObjectToStringDeserializer" | grep -i testRepository: samhap-soft/kokomen-backend Length of output: 53 🏁 Script executed: # Look at how metadata field is used in ConfirmRequest
cat -n src/main/java/com/samhap/kokomen/payment/service/dto/ConfirmRequest.java | head -30Repository: samhap-soft/kokomen-backend Length of output: 1607 🏁 Script executed: # Check the complete ConfirmRequest to understand the context
cat -n src/main/java/com/samhap/kokomen/payment/service/dto/ConfirmRequest.javaRepository: samhap-soft/kokomen-backend Length of output: 1839 🌐 Web query:
💡 Result:
Why
🌐 Web query:
💡 Result:
Rule of thumb: use Sources: [1] [2] [3]
JSON
🛠️ 제안 수정 public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
JsonNode node = p.getCodec().readTree(p);
+ if (node.isNull() || node.isMissingNode()) {
+ return null;
+ }
+
// 이미 문자열인 경우 그대로 반환
if (node.isTextual()) {
return node.asText();
}
// 객체나 배열인 경우 JSON 문자열로 변환
- ObjectMapper mapper = new ObjectMapper();
- return mapper.writeValueAsString(node);
+ return node.toString();
}🤖 Prompt for AI Agents |
||
| } | ||
| } | ||
|
Comment on lines
+1
to
+25
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
요청 인자를 INFO 레벨로 전체 로깅 시 민감 정보 노출 위험
args에는 결제 키, 금액, 사용자 정보 등 민감한 데이터가 포함될 수 있습니다. 전체 인자를INFO레벨로 로깅하면 로그 수집 시스템에 PII/결제 정보가 유출될 수 있습니다.DEBUG레벨로 변경하거나, 민감 필드를 마스킹하는 것을 권장합니다.🤖 Prompt for AI Agents