Skip to content
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

[BE] 코드잽 프로덕션 v1.1.7 배포 #919

Merged
merged 8 commits into from
Nov 16, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading