Skip to content

Commit

Permalink
refactor(entity): modify entity & tables (#85)
Browse files Browse the repository at this point in the history
* refactor: modify entity

* refactor: modify create table query

* fix: fix compile error

* refactor(base): separate createAt modifiedAt column

* feat(entity): add entity cost, cost_history, heart, meeting, meeting_request, team_member

* feat(enum): Apply JPA Attribute Converter to Enum field of entity (#86)

* feat(code): add initialize code sql on local envirionment (#87)

* feat(codeFixture): add getter methods

* feat(sql): add code insert sql to submodule

* feat(code): add find code by code_value query & test

* refactor(repository): change code setUp method in AbstractRepositoryUnitTest

---------

Co-authored-by: KAispread <[email protected]>

* refactor(image): change api request&response data (#88)

* refactor(s3): add s3Service

* refactor(member): add profileImage upload api

* refactor(profileImage): delete unused controller

* chore(application.yml): add multipart file and request size option

* test(member): add test case for profileImage upload API

* refactor(s3): add bucket parameter to upload function

* chore(member): change request url from underbar to dash

* refactor(member): move function for profileImage upload from MemberService to MemberImageService

* refactor(team): modify data type from Integer to DrinkRate Enum

* refactor(member): modify nickname length from 20 to 10

* refactor(member): change api request&response data (#89)

* refactor(error): change class name TeamAlreadyExistsException -> TeamExistsException

* feat(member): add Member Delete Method & team field & setting team

* feat(team): add setting teamLeader method

* feat(member): add member delete

* test(member): test member delete method

* refactor(member): change member api classification

* feat(code): add initialize code sql on local envirionment (#87)

* feat(codeFixture): add getter methods

* feat(sql): add code insert sql to submodule

* feat(code): add find code by code_value query & test

* refactor(repository): change code setUp method in AbstractRepositoryUnitTest

---------

Co-authored-by: KAispread <[email protected]>

* feat(valid): add bean validator for create Member RequestDto

* test(valid): test bean validator

* feat(error): add error messages for validator

* refactor(member): refactor CreateMemberRequestDto

* feat(code): add query findByCodePk and CodePk static factory method

* test(code): test query

* refactor(member): create member api refactoring

* test(member): test create member service method

* test(member): test create member api

* test(member): read member detail api test refactoring

* refactor(member): change MemberDetailResponseDto specification

* refactor(fixture): change MemberDetailResponseDto specification

* refactor(member): modify memberService specification

* feat(member): add find member with fetch join & test

* refactor(member): modify member spec

* refactor(member): change member api controller & test

* refactor(member): refactor read member service code

* test(member): test read member

* fix(member): fix compile error

* refactor(team): modify data type from Integer to DrinkRate Enum

* refactor(member): add Member update method and change nickname length

* feat(member): add MBTI exception

* refactor(team): remove unnecessary field on team constructor

* test(member): test member nickname length validation

* test(member): test update member

* feat(member): add update member (nickname, mbti)

* refactor(member): remove unused dto

* style(member): change number controller parameter

* refactor(member): change nickname length validation 3 -> 2

* feat(member): add nickname format validation

* refactor(validate): change CustomFormatValidator static REGX instance

* feat(member): add update value validation logic

* feat(member): add bean valid annotation that validate nickname format

---------

Co-authored-by: KAispread <[email protected]>
Co-authored-by: chaerim <[email protected]>

* fix(test): make test red to green (#91)

* test: fix test authorization to green

* test: fix test red to green

* refactor(auth): make load Member code clean

* test(auth): add findByPhoneNumber query test

* fix(auth): fix bug MemberPrincipal-toString()

* feat(code): add CodePk format validator

* test(code): test validateCodePkFormat method

* feat(code): add query find Code list by CodePk list

* refactor(code): refactoring findCodeList and test

* style(test): change display name

---------

Co-authored-by: KAispread <[email protected]>

* refactor(persist): refactor persist login (#92)

* refactor(member): move imageAuth field into ProfileImage

* refactor(persist): change PersistResponseDto field

* refactor(persist): remove PersistRepository from MemberRepository

* refactor(test): change PersistLoginRepository bean type

* test: add test before query refactoring

* fix(test): make test red to green (#91)

* test: fix test authorization to green

* test: fix test red to green

* refactor(auth): make load Member code clean

* test(auth): add findByPhoneNumber query test

* fix(auth): fix bug MemberPrincipal-toString()

* feat(code): add CodePk format validator

* test(code): test validateCodePkFormat method

* feat(code): add query find Code list by CodePk list

* refactor(code): refactoring findCodeList and test

* style(test): change display name

---------

Co-authored-by: KAispread <[email protected]>

* feat(persist): implement persist login

* fix(persist): fix compile error

* feat(profileImage): Add a method that returns boolean value

---------

Co-authored-by: KAispread <[email protected]>

* refactor(member): change input data validation logic

* refactor(team): change api request&response data (#90)

* refactor(s3): add s3Service

* refactor(member): add profileImage upload api

* feat(teamImage): add teamImage entity

* refactor(member): change member api classification

* refactor(team): change team api classification

* chore: do cherry-pick f73a733

* chore: do cherry-pick 73a07da

* feat(team): add setting teamLeader method

* refactor(team): add parameter to createTeam

* refactor(member): delete unused service function

* Merge into E2I-80-rim/modify-team-api from refactor-kai-83/modify-entity

* refactor(team): modify CreateTeamRequestDto field

* refactor(teamImage): change column name from order to sequence

* chore(member): change collegeType description

* feat(team): add api to create team

* feat(team): add api to update team

* feat(team): add api to delete team

* chore: change spring max-request-size

* feat(team): add api to read team

* refactor(team): add validation to create team

* refactor(team): add orphanRemoval = true option to teamMembers

* refactor(dto): delete builder annotation

* refactor(dto): change parameter to TeamMember for TeamMemberResponseDto

---------

Co-authored-by: KAispread <[email protected]>

* fix: Add exception handler and EndPointcheckfilter to security (#93)

* test: add RequestEndPointChecker to AbstractIntegrationTest

* feat(filter): add RequestEndPointChecker to filter

* test(filter): add api test for endpoint check filter

* feat(error): add exception handler caused by controller

* fix(member): Add handling null from profile image instance

---------

Co-authored-by: KAispread <[email protected]>

* chore(test): change collegeType data description

* fix: change test code error

* feat(log): add logging filter & AOP module (#94)

* feat(member): add DynamicUpdate to Member Entity for optimize query

* test: change test type to DynamicTest

* chore(log): add log4j2 dependency & configuration file

* cherry-pick: fix: Add exception handler and EndPointcheckfilter to security (#93)

* feat(log): add module counting query invocation in one http request

* feat(log): add filter logging request and response information

* feat(log): add logging filter to spring security

* feat(log): add memberId to MDC after authenticated

* feat(log): add Controller, Service Log AOP module

* refactor(log): change log format

* style: change error message and resolver logic

* refactor: change Aspect getMemberId() logic

* fix: fix compile error in Integration Test

* test: combine Test environment to reduce rising Spring Boot Server

* refactor(log): change log framework (#97)

* chore: change log framework log4j2 to logback

* refactor: change log module log4j2 to slf4j

---------

Co-authored-by: KAispread <[email protected]>

---------

Co-authored-by: KAispread <[email protected]>
Co-authored-by: chaerim <[email protected]>

* test(team): add controller, service test (#96)

* test(team): add test case for TeamService

* test(team): add test case for TeamController

---------

Co-authored-by: LeeKiWoo <[email protected]>

* feat(phone): phone validate response

* fix: change test code error

* refactor(member): change nickname length

* test(fix): fix member test fali

---------

Co-authored-by: KAispread <[email protected]>
Co-authored-by: Chaerim1001 <[email protected]>
Co-authored-by: chaerim <[email protected]>
  • Loading branch information
4 people authored Aug 14, 2023
1 parent df62b27 commit dfd530d
Show file tree
Hide file tree
Showing 230 changed files with 5,946 additions and 4,639 deletions.
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-redis:3.1.0'
implementation group: 'org.mariadb.jdbc', name: 'mariadb-java-client', version: '2.4.1'

implementation 'com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.9.0'

implementation 'com.auth0:java-jwt:4.4.0'
implementation 'org.flywaydb:flyway-core'
implementation 'org.flywaydb:flyway-mysql'
Expand Down
42 changes: 42 additions & 0 deletions src/main/java/com/e2i/wemeet/config/log/LogConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.e2i.wemeet.config.log;

import com.e2i.wemeet.config.log.aspect.ControllerLogAspect;
import com.e2i.wemeet.config.log.aspect.QueryCounterAspect;
import com.e2i.wemeet.config.log.aspect.ServiceLogAspect;
import com.e2i.wemeet.config.log.module.QueryCounter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.StopWatch;
import org.springframework.web.context.annotation.RequestScope;

@Configuration
public class LogConfig {

@RequestScope
@Bean
public StopWatch apiWatch() {
return new StopWatch();
}

@RequestScope
@Bean
public QueryCounter queryCounter() {
return new QueryCounter();
}

@Bean
public QueryCounterAspect queryCounterAspect() {
return new QueryCounterAspect(queryCounter());
}

@Bean
public ControllerLogAspect logControllerAspect() {
return new ControllerLogAspect();
}

@Bean
public ServiceLogAspect logServiceAspect() {
return new ServiceLogAspect();
}

}
129 changes: 129 additions & 0 deletions src/main/java/com/e2i/wemeet/config/log/LogFilter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package com.e2i.wemeet.config.log;

import static com.e2i.wemeet.config.log.MdcKey.CORRELATION_ID;
import static com.e2i.wemeet.config.log.MdcKey.MEMBER_ID;

import com.e2i.wemeet.config.log.module.QueryCounter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.UUID;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;

@Slf4j
@Component
public class LogFilter extends OncePerRequestFilter {

private static final String REQUEST_LOG_FORMAT = "REQUEST:: HTTP_METHOD: {}, URL: {}, AGENT: {}, BODY: {}";
private static final String RESPONSE_LOG_FORMAT = "RESPONSE:: HTTP_METHOD= {}, URL= {}, STATUS_CODE= {}, QUERY_COUNT= {}, TIME_TAKEN= {}ms, MEMBER_ID= {}, BODY= {}";
private static final String QUERY_COUNT_WARNING_LOG_FORMAT = "WARN QUERY EXECUTION TIME:: {}";

private static final int QUERY_COUNT_WARNING_STANDARD = 10;
private static final String NO_REQUEST_BODY = "none";
private static final String NOT_AUTHENTICATED = "none";

private final StopWatch apiWatch;
private final QueryCounter queryCounter;

public LogFilter(final StopWatch apiWatch, final QueryCounter queryCounter) {
this.apiWatch = apiWatch;
this.queryCounter = queryCounter;
}

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
final ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request);
final ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);

// Before request
addCorrelationIdToMdc();

apiWatch.start();
filterChain.doFilter(requestWrapper, responseWrapper);
apiWatch.stop();

// After request
loggingRequest(requestWrapper);
loggingResponse(requestWrapper, responseWrapper);
notifyQueryCountWarning();

MDC.clear();
responseWrapper.copyBodyToResponse();
}

// 쿼리 수행 개수가 QUERY_COUNT_WARNING_STANDARD 이상일 경우, 로그를 남긴다.
private void notifyQueryCountWarning() {
if (getQueryCount() >= QUERY_COUNT_WARNING_STANDARD) {
log.warn(QUERY_COUNT_WARNING_LOG_FORMAT, getQueryCount());
}
}

// Request Logging
private void loggingRequest(final ContentCachingRequestWrapper requestWrapper) {
final String method = requestWrapper.getMethod();
final String url = getUrlWithQueryParameters(requestWrapper);
final String agent = requestWrapper.getHeader("User-Agent");
final String body = new String(requestWrapper.getContentAsByteArray());

if (!StringUtils.hasText(body)) {
log.info(REQUEST_LOG_FORMAT, method, url, agent, NO_REQUEST_BODY);
return;
}

log.info(REQUEST_LOG_FORMAT, method, url, agent, body);
}

// Response Logging
private void loggingResponse(final ContentCachingRequestWrapper requestWrapper,
final ContentCachingResponseWrapper responseWrapper) {
final String method = requestWrapper.getMethod();
final String url = getUrlWithQueryParameters(requestWrapper);
final int statusCode = responseWrapper.getStatus();

final int queryCount = getQueryCount();
final long timeTaken = apiWatch.getTotalTimeMillis();
final String memberId = getMemberId();
final String body = new String(responseWrapper.getContentAsByteArray());

log.info(RESPONSE_LOG_FORMAT, method, url, statusCode, queryCount, timeTaken, memberId, body);
}

// MemberId를 가져온다. (memberId가 없을 경우, 인증된 사용자가 아니므로 NOT_AUTHENTICATED를 반환한다.)
private String getMemberId() {
String memberId = MDC.get(MEMBER_ID.getKey());
if (!StringUtils.hasText(memberId)) {
return NOT_AUTHENTICATED;
}
return memberId;
}

// API 요청에서 수행된 쿼리의 개수를 가져온다.
private int getQueryCount() {
return queryCounter.getQueryCount();
}

// MDC에 correlationId를 추가한다.
private void addCorrelationIdToMdc() {
MDC.put(CORRELATION_ID.getKey(), String.valueOf(UUID.randomUUID()));
}

// 쿼리 파라미터를 포함한 URL을 가져온다.
private String getUrlWithQueryParameters(final ContentCachingRequestWrapper requestWrapper) {
if (!StringUtils.hasText(requestWrapper.getQueryString())) {
return requestWrapper.getRequestURI();
}
return requestWrapper.getRequestURI() + "?" + requestWrapper.getQueryString();
}


}
16 changes: 16 additions & 0 deletions src/main/java/com/e2i/wemeet/config/log/MdcKey.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.e2i.wemeet.config.log;

import lombok.Getter;

@Getter
public enum MdcKey {
CORRELATION_ID("correlationId"),
MEMBER_ID("memberId"),
;

private final String key;

MdcKey(String key) {
this.key = key;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.e2i.wemeet.config.log.aspect;

import com.e2i.wemeet.config.log.MdcKey;
import java.util.HashMap;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.MDC;
import org.springframework.util.StringUtils;

@Slf4j
@Aspect
public class ControllerLogAspect {

private static final String CONTROLLER_LOG_FORMAT_BEFORE_PROCEED = "[CONTROLLER-START]:: MEMBER_ID: {}, CLASS: {}, METHOD: {}, PARAMS: {}";
private static final String CONTROLLER_LOG_FORMAT_AFTER_PROCEED = "[CONTROLLER-END]:: MEMBER_ID: {}, CLASS: {}, METHOD: {}, RETURN_TYPE: {}, RETURN_VALUE: {}";
private static final String NOT_AUTHENTICATED = "none";

@Around("execution(* com.e2i.wemeet.controller..*.*(..))")
public Object verify(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();

String className = joinPoint.getTarget().getClass().getSimpleName();
String methodName = signature.getMethod().getName();
Map<String, Object> parameters = getParameters(joinPoint, signature);
String memberId = getMemberId();

log.info(CONTROLLER_LOG_FORMAT_BEFORE_PROCEED, memberId, className, methodName, parameters);
Object proceed = joinPoint.proceed();

log.info(CONTROLLER_LOG_FORMAT_AFTER_PROCEED, memberId, className, methodName, signature.getReturnType().getSimpleName(), proceed);

return proceed;
}

private String getMemberId() {
String memberId = MDC.get(MdcKey.MEMBER_ID.getKey());
if (!StringUtils.hasText(memberId)) {
return NOT_AUTHENTICATED;
}
return memberId;
}

private Map<String, Object> getParameters(JoinPoint joinPoint, MethodSignature signature) {
Map<String, Object> parameterMap = new HashMap<>();

String[] parameterNames = signature.getParameterNames();
Object[] parameters = joinPoint.getArgs();

for (int i = 0; i < parameterNames.length; i++) {
parameterMap.put(parameterNames[i], parameters[i]);
}
return parameterMap;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.e2i.wemeet.config.log.aspect;

import com.e2i.wemeet.config.log.module.ConnectionInvocationHandler;
import com.e2i.wemeet.config.log.module.QueryCounter;
import java.lang.reflect.Proxy;
import lombok.RequiredArgsConstructor;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;

@RequiredArgsConstructor
@Aspect
public class QueryCounterAspect {

private final QueryCounter apiQueryCounter;

/*
* database 와 connection 이 일어날 때마다 queryCount를 늘림
* */
@Around("execution(* javax.sql.DataSource.getConnection())")
public Object getConnection(final ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
final Object connection = proceedingJoinPoint.proceed();
return Proxy.newProxyInstance(
connection.getClass().getClassLoader(),
connection.getClass().getInterfaces(),
new ConnectionInvocationHandler(connection, apiQueryCounter)
);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package com.e2i.wemeet.config.log.aspect;

import com.e2i.wemeet.config.log.MdcKey;
import java.util.HashMap;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.MDC;
import org.springframework.util.StringUtils;

@Slf4j
@Aspect
public class ServiceLogAspect {

private static final String SERVICE_LOG_FORMAT_BEFORE_PROCEED = "[SERVICE-START] :: MEMBER_ID: {}, CLASS: {}, METHOD: {}, PARAMS: {}";
private static final String SERVICE_LOG_FORMAT_AFTER_PROCEED = "[SERVICE-END] :: MEMBER_ID: {}, CLASS: {}, METHOD: {}, RETURN_TYPE: {}, RETURN_VALUE: {}";
private static final String NOT_AUTHENTICATED = "none";

@Around("execution(* com.e2i.wemeet.service..*.*(..))")
public Object verify(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();

String className = joinPoint.getTarget().getClass().getSimpleName();
String methodName = signature.getMethod().getName();
String memberId = getMemberId();

log.info(SERVICE_LOG_FORMAT_BEFORE_PROCEED, memberId, className, methodName, getParameters(joinPoint, signature));
Object proceed = joinPoint.proceed();

log.info(SERVICE_LOG_FORMAT_AFTER_PROCEED, memberId, className, methodName, signature.getReturnType().getSimpleName(), proceed);
return proceed;
}

private String getMemberId() {
String memberId = MDC.get(MdcKey.MEMBER_ID.getKey());
if (!StringUtils.hasText(memberId)) {
return NOT_AUTHENTICATED;
}
return memberId;
}

private Map<String, Object> getParameters(JoinPoint joinPoint, MethodSignature signature) {
Map<String, Object> parameterMap = new HashMap<>();

String[] parameterNames = signature.getParameterNames();
Object[] parameters = joinPoint.getArgs();

for (int i = 0; i < parameterNames.length; i++) {
parameterMap.put(parameterNames[i], parameters[i]);
}
return parameterMap;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.e2i.wemeet.config.log.module;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/*
* Jdbc Connection 을 받아 오는 동적 프록시 객체
* */
public class ConnectionInvocationHandler implements InvocationHandler {

private final Object connection;
private final QueryCounter queryCounter;

public ConnectionInvocationHandler(Object connection, QueryCounter queryCounter) {
this.connection = connection;
this.queryCounter = queryCounter;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
final Object result = method.invoke(connection, args);
if (method.getName().equals("prepareStatement")) {
return Proxy.newProxyInstance(
result.getClass().getClassLoader(),
result.getClass().getInterfaces(),
new PreparedStatementInvocationHandler(result, queryCounter)
);
}
return result;
}
}
Loading

0 comments on commit dfd530d

Please sign in to comment.