Skip to content

Commit

Permalink
Merge pull request #261 from ASAP-as-soon-as-possible/develop
Browse files Browse the repository at this point in the history
release 1.0.4
  • Loading branch information
KWY0218 authored May 13, 2024
2 parents e231f45 + 0ad9984 commit 6993379
Show file tree
Hide file tree
Showing 19 changed files with 394 additions and 116 deletions.
37 changes: 26 additions & 11 deletions .github/workflows/dev-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,34 @@ permissions:

jobs:
build:

runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18.x, 20.x]
redis-version: [6, 7]

steps:
- name: Setup MySQL
uses: samin/mysql-action@v1
uses: mirromutth/mysql-action@v1
with:
character set server: 'utf8'
mysql database: 'asap_dev'
mysql user: 'asap_dev_admin'
mysql password: ${{ secrets.DatabasePassword }}

mysql root password: 'asap'
mysql user: 'asap'
mysql password: 'asap'
host port: 3306
container port: 3306

- name: Set up redis
uses: supercharge/[email protected]
with:
node-version: ${{ matrix.node-version }}

- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'

- name: make application.properties 파일 생성
run: |
## create application.yml
Expand All @@ -51,10 +60,16 @@ jobs:
# application.yml 파일 확인
cat ./application.yml
shell: bash

# 이 워크플로우는 gradle build
- name: Grant execute permission for gradlew
run: chmod +x gradlew


- name: Wait for MySQL
run: |
while ! mysqladmin ping --host=127.0.0.1 --password='asap' --silent; do
sleep 1
done
- name: Build with Gradle # 실제 application build
run: ./gradlew build -PactiveProfiles=local
run: ./gradlew clean --stacktrace --info build
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,6 @@ output/
*.log
*.log.gz
*.log-*.gz
logs/
logs/

/src/main/resources/application.yml
6 changes: 4 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ repositories {
}

dependencies {
// QueryDSL Implementation
implementation "org.redisson:redisson:3.29.0"

// QueryDSL Implementation
implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta"
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
Expand All @@ -41,7 +43,7 @@ dependencies {

// JPA & Database
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'mysql:mysql-connector-java:8.0.32'
implementation 'mysql:mysql-connector-java:8.0.33'

// SWAGGER
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,13 @@
import com.asap.server.exception.model.ConflictException;
import com.asap.server.exception.model.ForbiddenException;
import com.asap.server.exception.model.HostTimeForbiddenException;
import com.asap.server.exception.model.InternalErrorException;
import com.asap.server.exception.model.NotFoundException;
import com.asap.server.exception.model.TooManyRequestException;
import com.asap.server.exception.model.UnauthorizedException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.ConstraintViolationException;
import jakarta.validation.ValidationException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
Expand All @@ -24,9 +29,6 @@
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.NoHandlerFoundException;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.ConstraintViolationException;
import jakarta.validation.ValidationException;
import java.io.IOException;

import static com.asap.server.exception.Error.METHOD_NOT_ALLOWED_EXCEPTION;
Expand Down Expand Up @@ -133,9 +135,25 @@ protected ErrorResponse handleConflictException(final ConflictException e) {
return ErrorResponse.error(e.getError());
}

/*
* 429 Too Many Requests
*/
@ResponseStatus(HttpStatus.TOO_MANY_REQUESTS)
@ExceptionHandler(TooManyRequestException.class)
protected ErrorResponse handleTooManyConflictException(final TooManyRequestException e) {
return ErrorResponse.error(e.getError());
}

/**
* 500 Internal Server
*/

@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler(InternalErrorException.class)
protected ErrorResponse handleInternalErrorException(final InternalErrorException e) {
return ErrorResponse.error(e.getError());
}

@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler(Exception.class)
protected ErrorResponse handleException(final Exception error, final HttpServletRequest request) throws IOException {
Expand Down
13 changes: 4 additions & 9 deletions src/main/java/com/asap/server/common/aspect/LoggingAspect.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.asap.server.common.aspect;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.asap.server.common.filter.CustomHttpServletRequestWrapper;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
Expand All @@ -9,9 +10,7 @@
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.util.ContentCachingRequestWrapper;

import jakarta.servlet.http.HttpServletRequest;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
Expand All @@ -21,8 +20,6 @@
@Slf4j
public class LoggingAspect {

private final ObjectMapper objectMapper = new ObjectMapper();

@Pointcut("execution(* com.asap.server.controller..*(..)) || ( execution(* com.asap.server.common.advice..*(..)) && !execution(* com.asap.server.common.advice.ControllerExceptionAdvice.handleException*(..)))")
public void controllerInfoLevelExecute() {
}
Expand All @@ -34,15 +31,14 @@ public void controllerErrorLevelExecute() {
@Around("com.asap.server.common.aspect.LoggingAspect.controllerInfoLevelExecute()")
public Object requestInfoLevelLogging(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
final ContentCachingRequestWrapper cachingRequest = (ContentCachingRequestWrapper) request;
long startAt = System.currentTimeMillis();
Object returnValue = proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
long endAt = System.currentTimeMillis();

log.info("================================================NEW===============================================");
log.info("====> Request: {} {} ({}ms)\n *Header = {}", request.getMethod(), request.getRequestURL(), endAt - startAt, getHeaders(request));
if ("POST".equalsIgnoreCase(request.getMethod())) {
log.info("====> Body: {}", objectMapper.readTree(cachingRequest.getContentAsByteArray()));
log.info("====> Body: {}", ((CustomHttpServletRequestWrapper) request).getBody());
}
if (returnValue != null) {
log.info("====> Response: {}", returnValue);
Expand All @@ -54,14 +50,13 @@ public Object requestInfoLevelLogging(ProceedingJoinPoint proceedingJoinPoint) t
@Around("com.asap.server.common.aspect.LoggingAspect.controllerErrorLevelExecute()")
public Object requestErrorLevelLogging(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
final ContentCachingRequestWrapper cachingRequest = (ContentCachingRequestWrapper) request;
long startAt = System.currentTimeMillis();
Object returnValue = proceedingJoinPoint.proceed(proceedingJoinPoint.getArgs());
long endAt = System.currentTimeMillis();

log.error("====> Request: {} {} ({}ms)\n *Header = {}", request.getMethod(), request.getRequestURL(), endAt - startAt, getHeaders(request));
if ("POST".equalsIgnoreCase(request.getMethod())) {
log.error("====> Body: {}", objectMapper.readTree(cachingRequest.getContentAsByteArray()));
log.error("====> Body: {}", ((CustomHttpServletRequestWrapper) request).getBody());
}
if (returnValue != null) {
log.error("====> Response: {}", returnValue);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package com.asap.server.common.filter;

import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import lombok.Getter;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;

@Getter
public class CustomHttpServletRequestWrapper extends HttpServletRequestWrapper {
private final String body;

public CustomHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
super(request);
this.body = readBody(request);
}

@Override
public ServletInputStream getInputStream() {
ByteArrayInputStream byteInputStream = new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8));
return new ServletInputStream() {
@Override
public boolean isFinished() {
return false;
}

@Override
public boolean isReady() {
return false;
}

@Override
public void setReadListener(ReadListener readListener) {

}

@Override
public int read() throws IOException {
return byteInputStream.read();
}
};
}

private String readBody(HttpServletRequest request) throws IOException {
BufferedReader input = new BufferedReader(new InputStreamReader(request.getInputStream()));
StringBuilder sb = new StringBuilder();

var line = input.readLine();
while (line != null) {
sb.append(line.trim());
line = input.readLine();
}
return sb.toString();
}
}
Original file line number Diff line number Diff line change
@@ -1,27 +1,19 @@
package com.asap.server.common.filter;

import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;

import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class CustomServletWrappingFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(final HttpServletRequest request,
final HttpServletResponse response,
final FilterChain chain) throws ServletException, IOException {
ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request);
ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);
import java.io.IOException;

chain.doFilter(requestWrapper, responseWrapper);
public class CustomServletWrappingFilter implements Filter {

responseWrapper.copyBodyToResponse();
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = new CustomHttpServletRequestWrapper((HttpServletRequest) servletRequest);
filterChain.doFilter(request, servletResponse);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.asap.server.common.interceptor;


import com.asap.server.common.filter.CustomHttpServletRequestWrapper;
import com.asap.server.exception.Error;
import com.asap.server.exception.model.TooManyRequestException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.redisson.api.RMap;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

@Component
@RequiredArgsConstructor
public class DuplicatedInterceptor implements HandlerInterceptor {
private static final String REDIS_KEY = "ASAP_REDIS";
private static final String RMAP_VALUE = "ASAP";
private static final String RMAP_KEY_FORMAT = "LOCK [ ip : %s , body : %s ]";
private static final String USER_IP_HEADER = "x-real-ip";
private final RedissonClient redissonClient;

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
if (lock(request)) return true;
throw new TooManyRequestException(Error.TOO_MANY_REQUEST_EXCEPTION);
}

@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
unLock(request);
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
unLock(request);
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}

private String getRmapKey(HttpServletRequest request) {
final String body = ((CustomHttpServletRequestWrapper) request).getBody();
final String userIp = request.getHeader(USER_IP_HEADER);
return String.format(RMAP_KEY_FORMAT, userIp, body);
}

private boolean lock(HttpServletRequest request) {
final String rmapKey = getRmapKey(request);
RMap<String, String> redissonClientMap = redissonClient.getMap(REDIS_KEY);
return redissonClientMap.putIfAbsent(rmapKey, RMAP_VALUE) == null;
}

private void unLock(HttpServletRequest request) {
final String rmapKey = getRmapKey(request);
RMap<String, String> redissonClientMap = redissonClient.getMap(REDIS_KEY);
redissonClientMap.remove(rmapKey);
}
}
9 changes: 9 additions & 0 deletions src/main/java/com/asap/server/config/WebConfig.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.asap.server.config;

import com.asap.server.common.interceptor.DuplicatedInterceptor;
import com.asap.server.config.resolver.meeting.MeetingPathVariableResolver;
import com.asap.server.config.resolver.user.UserIdResolver;
import lombok.RequiredArgsConstructor;
Expand All @@ -9,6 +10,7 @@
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.List;
Expand All @@ -18,6 +20,7 @@
public class WebConfig implements WebMvcConfigurer {
private final UserIdResolver userIdResolver;
private final MeetingPathVariableResolver meetingPathVariableResolver;
private final DuplicatedInterceptor duplicatedInterceptor;

@Bean
public PasswordEncoder getPasswordEncoder() {
Expand All @@ -37,4 +40,10 @@ public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers)
resolvers.add(userIdResolver);
resolvers.add(meetingPathVariableResolver);
}

@Override
public void addInterceptors(InterceptorRegistry interceptorRegistry) {
interceptorRegistry.addInterceptor(duplicatedInterceptor)
.addPathPatterns("/meeting", "/user/{meetingId}/time", "/user/host/{meetingId}/time");
}
}
Loading

0 comments on commit 6993379

Please sign in to comment.