Skip to content

Commit

Permalink
[BE] 코드잽 프로덕션 v1.1.7 배포 (#919)
Browse files Browse the repository at this point in the history
  • Loading branch information
zeus6768 authored Nov 16, 2024
2 parents 4fd6e6e + 0b4fcda commit e4c8fad
Show file tree
Hide file tree
Showing 73 changed files with 1,489 additions and 785 deletions.
4 changes: 4 additions & 0 deletions backend/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ dependencies {
runtimeOnly 'com.mysql:mysql-connector-j:9.0.0'
implementation 'org.flywaydb:flyway-core'
implementation 'org.flywaydb:flyway-mysql'
implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
annotationProcessor 'com.querydsl:querydsl-apt:5.0.0:jakarta'
annotationProcessor 'jakarta.persistence:jakarta.persistence-api:3.1.0'
annotationProcessor 'jakarta.annotation:jakarta.annotation-api:2.1.1'

testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'io.rest-assured:rest-assured:5.5.0'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,9 @@
import org.springframework.data.jpa.repository.JpaRepository;

import codezap.category.domain.Category;
import codezap.global.exception.CodeZapException;
import codezap.global.exception.ErrorCode;
import codezap.member.domain.Member;

@SuppressWarnings("unused")
public interface CategoryJpaRepository extends CategoryRepository, JpaRepository<Category, Long> {

default Category fetchById(Long id) {
return findById(id).orElseThrow(
() -> new CodeZapException(ErrorCode.RESOURCE_NOT_FOUND, "식별자 " + id + "에 해당하는 카테고리가 존재하지 않습니다."));
}
public interface CategoryJpaRepository extends JpaRepository<Category, Long> {

List<Category> findAllByMemberIdOrderById(Long memberId);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,42 @@

import java.util.List;

import org.springframework.stereotype.Repository;

import codezap.category.domain.Category;
import codezap.global.exception.CodeZapException;
import codezap.global.exception.ErrorCode;
import codezap.member.domain.Member;
import lombok.RequiredArgsConstructor;

@Repository
@RequiredArgsConstructor
public class CategoryRepository {

public interface CategoryRepository {
private final CategoryJpaRepository categoryJpaRepository;

Category fetchById(Long id);
public Category save(Category category) {
return categoryJpaRepository.save(category);
}

List<Category> findAllByMemberIdOrderById(Long memberId);
public Category fetchById(Long id) {
return categoryJpaRepository.findById(id).orElseThrow(
() -> new CodeZapException(ErrorCode.RESOURCE_NOT_FOUND, "식별자 " + id + "에 해당하는 카테고리가 존재하지 않습니다."));
}

List<Category> findAll();
public List<Category> findAllByMemberIdOrderById(Long memberId) {
return categoryJpaRepository.findAllByMemberIdOrderById(memberId);
}

boolean existsByNameAndMember(String categoryName, Member member);
public List<Category> findAll() {
return categoryJpaRepository.findAll();
}

Category save(Category category);
public boolean existsByNameAndMember(String categoryName, Member member) {
return categoryJpaRepository.existsByNameAndMember(categoryName, member);
}

void deleteById(Long id);
public void deleteById(Long id) {
categoryJpaRepository.deleteById(id);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

@ExceptionHandler
public ResponseEntity<ProblemDetail> handleCodeZapException(CodeZapException codeZapException) {
log.info("[CodeZapException] {}가 발생했습니다.", codeZapException.getClass().getName(), codeZapException);
log.warn("[CodeZapException] {}: {}", codeZapException.getClass().getName(), codeZapException.getMessage());

return ResponseEntity.status(codeZapException.getErrorCode().getHttpStatus())
.body(codeZapException.toProblemDetail());
Expand All @@ -43,7 +43,7 @@ public ResponseEntity<ProblemDetail> handleCodeZapException(CodeZapException cod
protected ResponseEntity<Object> handleMethodArgumentNotValid(
MethodArgumentNotValidException exception, HttpHeaders headers, HttpStatusCode status, WebRequest request
) {
log.info("[MethodArgumentNotValidException] {}가 발생했습니다. \n", exception.getClass().getName(), exception);
log.warn("[MethodArgumentNotValidException] {}: {}", exception.getClass().getName(), exception.getMessage());

BindingResult bindingResult = exception.getBindingResult();
List<String> errorMessages = bindingResult.getAllErrors().stream()
Expand All @@ -58,7 +58,8 @@ protected ResponseEntity<Object> handleMethodArgumentNotValid(

@Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(
HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request) {
HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatusCode status, WebRequest request
) {
String exceptionMessage = "잘못된 JSON 형식입니다.";
if (ex.getCause() instanceof JsonMappingException jsonMappingException) {
exceptionMessage = jsonMappingException.getPath().stream()
Expand All @@ -75,7 +76,7 @@ protected ResponseEntity<Object> handleHttpMessageNotReadable(

@ExceptionHandler
public ResponseEntity<ProblemDetail> handleException(Exception exception) {
log.error("[Exception] 예상치 못한 오류 {} 가 발생했습니다.", exception.getClass().getName(), exception);
log.error("[Exception] 예상치 못한 오류 {} 가 발생했습니다. \n", exception.getClass().getName(), exception);
CodeZapException codeZapException =
new CodeZapException(ErrorCode.INTERNAL_SERVER_ERROR, "서버에서 예상치 못한 오류가 발생하였습니다.");
return ResponseEntity.internalServerError()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package codezap.global.logger;

import java.io.IOException;
import java.util.stream.Collectors;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
Expand All @@ -14,13 +16,21 @@
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.ContentCachingResponseWrapper;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Component
@Order(2)
public class RequestResponseLogger extends OncePerRequestFilter {

private static final int ERROR_CODE = 500;
private static final int WARN_CODE = 400;

private final ObjectMapper objectMapper = new ObjectMapper();

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
Expand All @@ -31,29 +41,69 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
filterChain.doFilter(requestWrapper, responseWrapper);
long duration = System.currentTimeMillis() - startTime;

log.info("[Request] {} {}, 헤더 값: {} \n", request.getMethod(), request.getRequestURI(),
getHeaderAndValue(requestWrapper));
log.info("[Response] Status: {}, Duration: {}ms, 헤더 값: {} \n", response.getStatus(), duration,
getHeaderAndValue(responseWrapper));
logResponse(request, response, requestWrapper, duration, responseWrapper);

responseWrapper.copyBodyToResponse();
}

private String getHeaderAndValue(ContentCachingRequestWrapper requestWrapper) {
StringBuilder headerAndValue = new StringBuilder();
requestWrapper.getHeaderNames().asIterator().forEachRemaining(headerName -> {
String headerValue = requestWrapper.getHeader(headerName);
headerAndValue.append(headerName).append(" : ").append(headerValue).append("\n");
});
private void logResponse(HttpServletRequest request, HttpServletResponse response,
ContentCachingRequestWrapper requestWrapper, long duration, ContentCachingResponseWrapper responseWrapper
) {
int status = response.getStatus();
String requestMessage = String.format(
"[Request] %s %s, 헤더 값: %s",
request.getMethod(),
request.getRequestURI(),
getHeaderAsJson(requestWrapper));
String responseMessage = String.format(
"[Response] status: %d, duration: %dms, headers: %s",
status,
duration,
getHeaderAsJson(responseWrapper));

logByStatus(status, requestMessage, responseMessage);
}

private String getHeaderAsJson(ContentCachingRequestWrapper requestWrapper) {
Map<String, String> headersMap = new HashMap<>();
Enumeration<String> headerNames = requestWrapper.getHeaderNames();
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
headersMap.put(headerName, requestWrapper.getHeader(headerName));
}
return convertMapToJson(headersMap);
}

private String getHeaderAsJson(ContentCachingResponseWrapper responseWrapper) {
Map<String, String> headersMap = new HashMap<>();
for (String headerName : responseWrapper.getHeaderNames()) {
headersMap.put(headerName, responseWrapper.getHeader(headerName));
}
return convertMapToJson(headersMap);
}

return headerAndValue.toString();
private String convertMapToJson(Map<String, String> map) {
try {
return objectMapper.writeValueAsString(map);
} catch (JsonProcessingException e) {
log.error("헤더 정보를 JSON으로 변환하는 중 오류 발생", e);
return "{}";
}
}

private String getHeaderAndValue(ContentCachingResponseWrapper requestWrapper) {
return requestWrapper.getHeaderNames().stream().map(headerName -> {
String headerValue = requestWrapper.getHeader(headerName);
return headerName + " : " + headerValue;
}).collect(Collectors.joining("\n"));
private void logByStatus(int status, String requestMessage, String responseMessage) {
if (status >= ERROR_CODE) {
log.error(requestMessage);
log.error(responseMessage);
return;
}
if (status >= WARN_CODE) {
log.warn(requestMessage);
log.warn(responseMessage);
return;
}
log.info(requestMessage);
log.info(responseMessage);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package codezap.global.pagination;

import java.util.List;

public record FixedPage<T> (List<T> contents, int nextPages) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package codezap.global.pagination;

import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Component;

import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.core.types.dsl.EntityPathBase;
import com.querydsl.jpa.impl.JPAQueryFactory;

@Component
public class FixedPageCounter {

private static final int MAXIMUM_PAGE = 5;

public int countNextFixedPage(
JPAQueryFactory queryFactory,
EntityPathBase<?> entityPath,
Pageable pageable,
BooleanExpression... conditions
) {
int maximumElementsCount = pageable.getPageSize() * MAXIMUM_PAGE;
long nextFixedElementCounts = queryFactory
.selectFrom(entityPath)
.where(conditions)
.offset(pageable.getOffset())
.limit(maximumElementsCount)
.fetch()
.size();

return (int) Math.ceil((double) nextFixedElementCounts / pageable.getPageSize());
}
}
21 changes: 21 additions & 0 deletions backend/src/main/java/codezap/global/querydsl/QueryDSLConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package codezap.global.querydsl;

import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.querydsl.jpa.impl.JPAQueryFactory;

@Configuration
public class QueryDSLConfig {

@PersistenceContext
private EntityManager entityManager;

@Bean
public JPAQueryFactory jpaQueryFactory() {
return new JPAQueryFactory(entityManager);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Profile;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.jdbc.datasource.LazyConnectionDataSourceProxy;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,16 @@
package codezap.likes.repository;

import java.util.List;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import codezap.likes.domain.Likes;
import codezap.member.domain.Member;
import codezap.template.domain.Template;

public interface LikesJpaRepository extends LikesRepository, JpaRepository<Likes, Long> {
public interface LikesJpaRepository extends JpaRepository<Likes, Long> {

void deleteByMemberAndTemplate(Member member, Template template);

@Modifying(clearAutomatically = true)
@Query("DELETE FROM Likes l WHERE l.template.id in :templateIds")
void deleteAllByTemplateIds(@Param(value = "templateIds") List<Long> templateIds);
boolean existsByMemberAndTemplate(Member member, Template template);

@Query("""
SELECT l.template
FROM Likes l
WHERE l.member.id = :memberId AND (l.template.member.id = :memberId OR l.template.visibility = 'PUBLIC')
""")
Page<Template> findAllByMemberId(@Param(value = "memberId") Long memberId, Pageable pageable);
long countByTemplate(Template template);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package codezap.likes.repository;

import java.util.List;

import org.springframework.data.jpa.repository.Modifying;
import org.springframework.stereotype.Repository;

import com.querydsl.jpa.impl.JPAQueryFactory;

import codezap.likes.domain.QLikes;
import lombok.RequiredArgsConstructor;

@Repository
@RequiredArgsConstructor
public class LikesQueryDslRepository {

private final JPAQueryFactory jpaQueryFactory;

@Modifying(clearAutomatically = true)
public void deleteAllByTemplateIds(List<Long> templateIds) {
jpaQueryFactory.delete(QLikes.likes)
.where(QLikes.likes.template.id.in(templateIds))
.execute();
}
}
Loading

0 comments on commit e4c8fad

Please sign in to comment.