Skip to content

Commit

Permalink
Merge pull request #136 from JECT-Study/release/1.1.0
Browse files Browse the repository at this point in the history
release: 1.1.0
  • Loading branch information
junest66 authored Dec 21, 2024
2 parents 425f511 + efcf600 commit 0c18509
Show file tree
Hide file tree
Showing 224 changed files with 5,324 additions and 9,203 deletions.
1 change: 0 additions & 1 deletion .github/workflows/pr_review_reminder.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ jobs:
run: |
reviewer_map='[
{"github": "junest66", "discord": "<@444811214623735839>"},
{"github": "viaunixue", "discord": "<@386917108455309316>"},
{"github": "YeaChan05", "discord": "<@391487793995579403>"}
]'
prs=$(cat prs.json)
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ border-radius: 20px;" >

</br>

[✨ <치즈마켓> 사용해보기](https://chzzmarket.vercel.app/)
[✨ <치즈마켓> 사용해보기](https://chzzmarket.store/)

[//]: # ([📄 API 문서 바로가기]&#40;https://app.swaggerhub.com/apis-docs/CHLWNDKS333_1/chzz-market-api/1.0.0#/Products&#41;)

Expand Down Expand Up @@ -186,4 +186,4 @@ border-radius: 20px;" >
</tr>
</table>

<br>
<br>
4 changes: 2 additions & 2 deletions compose-local.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ services:
- "6379:6379"

chzz-frontend:
image: junest1010/test-app:latest
image: cloudoort/chzzmarket-frontend:1.0
container_name: react-app
ports:
- "3000:3000"
- "5173:5173"

# node-exporter:
# image: prom/node-exporter:latest
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public class DistributedLockAop {
private final RedissonClient redissonClient;
private final AopForTransaction aopForTransaction;

@Around("@annotation(org.chzz.market.common.aop.redisrock.DistributedLock)")
@Around("@annotation(DistributedLock)")
public Object lock(final ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
Expand All @@ -34,28 +34,27 @@ public Object lock(final ProceedingJoinPoint joinPoint) throws Throwable {
joinPoint.getArgs(), distributedLock.key());
RLock rLock = redissonClient.getLock(key); // (1) 락의 이름으로 RLock 인스턴스를 가져옴

log.info("Lock 획득 시도 중... [method: {}]", method.getName());
log.debug("Lock 획득 시도 중... [method: {}, key: {}]", method.getName(), key);

try {
boolean available = rLock.tryLock(distributedLock.waitTime(), distributedLock.leaseTime(),
distributedLock.timeUnit()); // (2) 정의된 waitTime까지 획득을 시도, 정의된 leaseTime이 지나면 잠금을 해제
if (!available) {
log.warn("Lock 획득 실패 [method: {}]", method.getName());
log.warn("Lock 획득 실패 [method: {}, key: {}]", method.getName(), key);
return false;
}
log.info("Lock 획득 성공 [method: {}]", method.getName());
log.debug("Lock 획득 성공 [method: {}, key: {}]", method.getName(), key);

return aopForTransaction.proceed(joinPoint); // (3) DistributedLock 어노테이션이 선언된 메서드를 별도의 트랜잭션으로 실행
} catch (InterruptedException e) {
log.error("Lock 획득 중 인터럽트가 발생 [method: {}]", method.getName(), e);
log.error("Lock 획득 중 인터럽트가 발생 [method: {}, key: {}]", method.getName(), key, e);
throw new InterruptedException();
} finally {
try {
rLock.unlock(); // (4) 종료 시 무조건 락을 해제
log.info("Lock 해제 [method: {}]", method.getName());

log.debug("Lock 해제 [method: {}, key: {}]", method.getName(), key);
} catch (IllegalMonitorStateException e) {
log.warn("이미 Lock 해제 [method: {}]", method.getName());
log.warn("이미 Lock 해제 [method: {}, key: {}]", method.getName(), key);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.chzz.market.common.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.client.endpoint.DefaultAuthorizationCodeTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;

@Configuration
public class OAuth2ClientConfig {

@Bean
public OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient() {
return new DefaultAuthorizationCodeTokenResponseClient();
}
}
13 changes: 13 additions & 0 deletions src/main/java/org/chzz/market/common/config/RestClientConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.chzz.market.common.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestClient;

@Configuration
public class RestClientConfig {
@Bean
RestClient restClient() {
return RestClient.create();
}
}
21 changes: 9 additions & 12 deletions src/main/java/org/chzz/market/common/config/SecurityConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@
import org.chzz.market.common.filter.JWTFilter;
import org.chzz.market.common.filter.NotFoundFilter;
import org.chzz.market.common.util.JWTUtil;
import org.chzz.market.domain.user.oauth2.CustomFailureHandler;
import org.chzz.market.domain.user.oauth2.CustomSuccessHandler;
import org.chzz.market.domain.user.service.CustomOAuth2UserService;
import org.chzz.market.domain.oauth2.service.CustomFailureHandler;
import org.chzz.market.domain.oauth2.service.CustomOAuth2LoginAuthenticationProvider;
import org.chzz.market.domain.oauth2.service.CustomOAuth2UserService;
import org.chzz.market.domain.oauth2.service.CustomSuccessHandler;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
Expand All @@ -42,6 +43,7 @@ public class SecurityConfig {
@Value("${client.url}")
private String clientUrl;
private final CustomOAuth2UserService customOAuth2UserService;
private final CustomOAuth2LoginAuthenticationProvider customOAuth2LoginAuthenticationProvider;
private final CustomAccessDeniedHandler customAccessDeniedHandler;
private final CustomAuthenticationEntryPoint customAuthenticationEntryPoint;
private final CustomSuccessHandler customSuccessHandler;
Expand All @@ -53,21 +55,16 @@ public class SecurityConfig {

@Bean
public SecurityFilterChain filterChain(final HttpSecurity http) throws Exception {
return http.authorizeHttpRequests(authorize -> authorize
return http
.authenticationProvider(customOAuth2LoginAuthenticationProvider)
.authorizeHttpRequests(authorize -> authorize
.requestMatchers(ACTUATOR).permitAll()
.requestMatchers("/metrics").permitAll()
.requestMatchers("/api-docs", "/swagger-ui/**", "/api/v3/api-docs/**").permitAll()
.requestMatchers(GET,
"/api/v1/auctions",
"/api/v1/auctions/{auctionId:\\d+}",
"/api/v1/auctions/{auctionId:\\d+}/simple",
"/api/v1/auctions/best",
"/api/v1/auctions/imminent",
"/api/v1/auctions/users/*",
"/api/v1/products",
"/api/v1/products/categories",
"/api/v1/products/{productId:\\d+}",
"/api/v1/products/users/*",
"/api/v1/auctions/categories",
"/api/v1/notifications/subscribe",
"/api/v1/users/*",
"/api/v1/users/check/nickname/*").permitAll()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package org.chzz.market.common.config;
package org.chzz.market.common.config.aws;

import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
Expand Down
24 changes: 24 additions & 0 deletions src/main/java/org/chzz/market/common/config/aws/BucketPrefix.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.chzz.market.common.config.aws;

import java.util.Arrays;
import java.util.UUID;
import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public enum BucketPrefix {
AUCTION("auction"),
PROFILE("profile");
private final String name;

public static boolean hasNameOf(String name) {
return Arrays.stream(values())
.anyMatch(bucketFolderName -> bucketFolderName.name.equals(name));
}

public String createPath(final String fileName) {
String fileId = UUID.randomUUID().toString();
return String.format("%s/%s/%s", this.name, fileId, fileName);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package org.chzz.market.common.config.aws;

import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.ListObjectsV2Request;
import com.amazonaws.services.s3.model.ListObjectsV2Result;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

/**
* 이미지가 업로드 가능한 파일 목록 세팅
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class S3PrefixVerifier {
private static final String DELIMITER = "/";

private final AmazonS3 s3;
private final String bucket;

@EventListener(ApplicationReadyEvent.class)
private boolean verifyPrefix() {
ListObjectsV2Request req = new ListObjectsV2Request()
.withBucketName(bucket)
.withDelimiter(DELIMITER);
ListObjectsV2Result result = s3.listObjectsV2(req);
return result.getCommonPrefixes().stream()
.map(prefix -> prefix.split(DELIMITER)[0])
.peek(prefix -> log.info("bucket prefix: {}", prefix))
.allMatch(BucketPrefix::hasNameOf);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
public enum GlobalErrorCode implements ErrorCode {
INVALID_REQUEST_PARAMETER(HttpStatus.BAD_REQUEST, "Invalid request parameter"),
UNSUPPORTED_PARAMETER_TYPE(HttpStatus.BAD_REQUEST, "Unsupported type of parameter included"),
UNSUPPORTED_PARAMETER_NAME(HttpStatus.BAD_REQUEST, "Unsupported productName of parameter included"),
UNSUPPORTED_PARAMETER_NAME(HttpStatus.BAD_REQUEST, "Unsupported auctionName of parameter included"),
VALIDATION_FAILED(HttpStatus.BAD_REQUEST, "Validation failed"),
COOKIE_NOT_FOUND(HttpStatus.BAD_REQUEST, "Required cookie is not found"),
AUTHENTICATION_REQUIRED(HttpStatus.UNAUTHORIZED, "Authentication is required"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import static org.chzz.market.common.error.GlobalErrorCode.EXTERNAL_API_ERROR;

import jakarta.validation.ConstraintViolationException;
import java.io.IOException;
import java.util.List;
import lombok.NonNull;
Expand All @@ -12,6 +13,7 @@
import org.chzz.market.common.error.GlobalErrorCode;
import org.chzz.market.common.error.GlobalException;
import org.chzz.market.common.error.exception.BusinessException;
import org.hibernate.validator.internal.engine.path.PathImpl;
import org.springframework.context.MessageSourceResolvable;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatusCode;
Expand Down Expand Up @@ -86,6 +88,25 @@ public ResponseEntity<Object> handleIOException(IOException e) {
}
}

@ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<?> handleConstraintViolation(ConstraintViolationException ex) {
GlobalErrorCode errorCode = GlobalErrorCode.VALIDATION_FAILED;

String[] errorMessages = ex.getConstraintViolations().stream()
.map(violation -> {
String field = ((PathImpl) violation.getPropertyPath()).getLeafNode().toString();
String message = violation.getMessage();
return String.format("%s: %s", field, message);
})
.toArray(String[]::new); // List가 아닌 배열로 변환
logException(ex, errorCode, errorMessages);

ErrorResponse errorResponse = ErrorResponse.of(errorCode, errorMessages);
return ResponseEntity
.status(errorCode.getHttpStatus())
.body(errorResponse);
}

// 2. ResponseEntityExceptionHandler에서 오버라이드된 핸들러

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package org.chzz.market.common.util;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

@Component
public class ApplicationContextProvider implements ApplicationContextAware {

private static ApplicationContext context;

@Override
public void setApplicationContext(ApplicationContext ctx) throws BeansException {
context = ctx;
}

public static <T> T getBean(Class<T> clazz) {
return context.getBean(clazz);
}
}
32 changes: 32 additions & 0 deletions src/main/java/org/chzz/market/common/util/StringCaseConverter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package org.chzz.market.common.util;

public class StringCaseConverter {

/**
* 문자열을 대문자로 변환하고, '-'를 '_'로 바꿈 예: "example-string" -> "EXAMPLE_STRING"
* 요청에서 Enum값을 매칭할때 클라이언트에선 소문자와 하이푼을 쓰기 때문에 변환
*
* @param source 변환할 입력 문자열
* @return 변환된 문자열
*/
public static String toUpperCaseWithUnderscores(String source) {
if (source == null) {
return null;
}
return source.trim().toUpperCase().replace("-", "_");
}

/**
* 문자열을 소문자로 변환하고, '_'를 '-'로 바꿈 예: "EXAMPLE_STRING" -> "example-string"
* Enum값을 내보낼때, 클라이언트에선 소문자와 하이푼을 쓰기 때문에 변환
*
* @param source 변환할 입력 문자열
* @return 변환된 문자열
*/
public static String toLowerCaseWithHyphens(String source) {
if (source == null) {
return null;
}
return source.trim().toLowerCase().replace("_", "-");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public T convert(String source) {
if (source == null || source.isEmpty()) {
return null;
}
return (T) Enum.valueOf(this.enumType, source.trim().toUpperCase().replace("-", "_"));
return (T) Enum.valueOf(this.enumType, StringCaseConverter.toUpperCaseWithUnderscores(source));
}
}
}
Loading

0 comments on commit 0c18509

Please sign in to comment.