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

[POT_63][Feat] 레포트 조회 API 구현 #86

Merged
merged 27 commits into from
Jul 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
91b4d40
[Feat] 기간에 대한 Enum 클래스 선언
lcj1204 Jul 8, 2024
bcc340e
[Feat] HttpRequest를 즉시 Enum으로 변경하기 위한 Converter 구현
lcj1204 Jul 8, 2024
d4410fd
[Feat] WebConfig에 ReportPeriodRequestConverter 등록
lcj1204 Jul 8, 2024
88dd23d
[Feat] ReportService 인터페이스 선언
lcj1204 Jul 8, 2024
d74e86b
[Feat] ReportPeriod 예외 추가
lcj1204 Jul 8, 2024
c28156d
[Feat] ReportPeriod 예외 추가
lcj1204 Jul 8, 2024
64da3cf
[Feat] 오탈자 수정
lcj1204 Jul 8, 2024
95853d1
[Fix] 머지 충돌 해결
lcj1204 Jul 20, 2024
89bc50b
[Fix] upstream develop 브랜치 병합
lcj1204 Jul 20, 2024
93acdb6
[Feat] 쿼리 인자값 확인 라이브러리 추가
lcj1204 Jul 20, 2024
fa57168
[Fix] upstream develop 브랜치 병합
lcj1204 Jul 20, 2024
8fe1fe8
[Feat] 쿼리 포맷터 구현(yml에서 properties.hibernate.format_sql: true 제거)
lcj1204 Jul 20, 2024
0edaa8c
[Feat] 중복 WebConfig 삭제 및 manager용 EnumFormatter 구현
lcj1204 Jul 20, 2024
d678b03
[Feat] p6spy 버전 업데이트
lcj1204 Jul 24, 2024
6743809
[Refactor] 클래스명 변경
lcj1204 Jul 24, 2024
e1a8dd2
[Feat] 속성값 변경
lcj1204 Jul 24, 2024
fd94026
[Feat] ReportService 인터페이스 구현
lcj1204 Jul 24, 2024
3237364
[Feat] ReportService 구현체 구현
lcj1204 Jul 24, 2024
309cb2d
[Feat] 자동기간계산기 구현
lcj1204 Jul 24, 2024
d8d15c3
[Feat] ReportRepository 및 getPotDngrCntByPeriod 쿼리 구현
lcj1204 Jul 24, 2024
f592cb1
[Feat] 기간, 위험도 별 포트홀 통계 응답 Dto 구현
lcj1204 Jul 24, 2024
8a3fef7
[Feat] ReportController 구현 및 기간, 위험도 별 포트홀 통계 API 구현
lcj1204 Jul 24, 2024
cc5edc6
[Feat] 메소드명 변경
lcj1204 Jul 24, 2024
d8735c1
[Refactor] 디렉토리 변경
lcj1204 Jul 24, 2024
3d9c1c4
[Feat] 날짜 자동 변경 기능 구현
lcj1204 Jul 24, 2024
9cc2e68
[Feat] API 요청 시 놓친 파라미터에 대한 예외처리 추가
lcj1204 Jul 24, 2024
5ab32be
[Refactor] 파라미터 타입 불일치에 대한 예외로 수정
lcj1204 Jul 24, 2024
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
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ subprojects {
// postgreSQL
runtimeOnly 'org.postgresql:postgresql'

// 쿼리 인자값 확인
implementation 'com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.9.1'

// redis
implementation 'org.springframework.boot:spring-boot-starter-data-redis'

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package pothole_solution.core.global.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import pothole_solution.core.global.util.formatter.LocalDateFormatter;

@Configuration
public class AppConfig {
@Bean
public LocalDateFormatter localDateFormatter() {
return new LocalDateFormatter();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public class CustomException extends RuntimeException {
public static final CustomException INVALID_URL = new CustomException(ExceptionStatus.INVALID_URL);
public static final CustomException INTERNAL_SERVER_ERROR = new CustomException(ExceptionStatus.INTERNAL_SERVER_ERROR);
public static final CustomException NOT_EXISTED_FILE = new CustomException(ExceptionStatus.NOT_EXISTED_FILE);
public static final CustomException MISSING_PARAMETER = new CustomException(ExceptionStatus.MISSING_PARAMETER);

// session exception
public static final CustomException UNAUTHORIZED_SESSION = new CustomException(ExceptionStatus.UNAUTHORIZED_SESSION);
Expand All @@ -38,4 +39,7 @@ public class CustomException extends RuntimeException {
public static final CustomException INVALID_POTHOLE_IMG_URL = new CustomException(ExceptionStatus.INVALID_POTHOLE_IMG_URL);
public static final CustomException INVALID_POTHOLE_IMG_NAME = new CustomException(ExceptionStatus.INVALID_POTHOLE_IMG_NAME);
public static final CustomException INVALID_POTHOLE_IMG = new CustomException(ExceptionStatus.INVALID_POTHOLE_IMG);

// Report Exception
public static final CustomException MISMATCH_PERIOD = new CustomException(ExceptionStatus.MISMATCH_PERIOD);
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public enum ExceptionStatus {
INVALID_URL(BAD_REQUEST, 2001, "잘못된 URL 요청입니다."),
INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, 2002,"서버 내부 오류입니다."),
NOT_EXISTED_FILE(NOT_EXTENDED, 2003,"존재하지 않는 파일입니다."),
MISSING_PARAMETER(BAD_REQUEST, 2004,"놓친 파라미터가 존재합니다."),

// session exception
UNAUTHORIZED_SESSION(UNAUTHORIZED, 2100, "인증되지 않은 세션입니다. 로그인을 해주세요."),
Expand All @@ -37,7 +38,10 @@ public enum ExceptionStatus {
FAILED_UPLOAD(HttpStatus.INTERNAL_SERVER_ERROR, 5000, "포트홀 이미지 업로드에 실패했습니다."),
INVALID_POTHOLE_IMG_URL(BAD_REQUEST, 5001, "잘못된 포트홀 이미지 URL 요청입니다."),
INVALID_POTHOLE_IMG_NAME(BAD_REQUEST, 5002, "포트홀 이미지의 이름이 존재하지 않거나 잘못되었습니다."),
INVALID_POTHOLE_IMG(BAD_REQUEST, 5003, "포트홀 이미지가 존재하지 않거나 잘못되었습니다.");
INVALID_POTHOLE_IMG(BAD_REQUEST, 5003, "포트홀 이미지가 존재하지 않거나 잘못되었습니다."),

// Report exception
MISMATCH_PERIOD(BAD_REQUEST, 6000, "존재하지 않는 기간입니다.");

private final HttpStatus httpStatus;
private final int code;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
Expand Down Expand Up @@ -30,11 +31,22 @@ public ResponseEntity<Object> handleValidException() {
}

/**
* Progress Converter Exception Handler
* Invalid Parameter Exception Handler
*/
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
public ResponseEntity<Object> handleProgressConverterValidException() {
CustomException exception = CustomException.NONE_PROGRESS_STATUS;
CustomException exception = CustomException.INVALID_PARAMETER;
return ResponseEntity
.status(exception.getStatus().getHttpStatus())
.body(new BaseResponse<>(exception.getStatus()));
}

/**
* Missing Request Parameter Exception Handler
*/
@ExceptionHandler(MissingServletRequestParameterException.class)
public ResponseEntity<Object> handleMissingServletRequestParameterException() {
CustomException exception = CustomException.MISSING_PARAMETER;
return ResponseEntity
.status(exception.getStatus().getHttpStatus())
.body(new BaseResponse<>(exception.getStatus()));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package pothole_solution.core.global.util.formatter;

import org.jetbrains.annotations.NotNull;
import org.springframework.format.Formatter;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Locale;

public class LocalDateFormatter implements Formatter<LocalDate> {
@NotNull
@Override
public LocalDate parse(@NotNull String text, @NotNull Locale locale) {
return LocalDate.parse(text, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
}

@NotNull
@Override
public String print(@NotNull LocalDate object, @NotNull Locale locale) {
return DateTimeFormatter.ofPattern("yyyy-MM-dd").format(object);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package pothole_solution.core.infra.p6spy;

import com.p6spy.engine.common.ConnectionInformation;
import com.p6spy.engine.event.JdbcEventListener;
import com.p6spy.engine.spy.P6SpyOptions;
import com.p6spy.engine.spy.appender.MessageFormattingStrategy;
import org.hibernate.engine.jdbc.internal.FormatStyle;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.sql.SQLException;

@Component
public class P6SpyFormatter extends JdbcEventListener implements MessageFormattingStrategy {

@Override
public void onAfterGetConnection(ConnectionInformation connectionInformation, SQLException e) {
P6SpyOptions.getActiveInstance().setLogMessageFormat(getClass().getName());
}

@Override
public String formatMessage(int connectionId, String now, long elapsed, String category, String prepared, String sql, String url) {
StringBuilder sb = new StringBuilder();
sb.append(category).append(" ").append(elapsed).append("ms");
if (StringUtils.hasText(sql)) {
// sb.append(highlight(format(sql)));
sb.append(format(sql));
}
return sb.toString();
}

private String format(String sql) {
if (isDDL(sql)) {
return FormatStyle.DDL.getFormatter().format(sql);
} else if (isBasic(sql)) {
return FormatStyle.BASIC.getFormatter().format(sql);
}
return sql;
}

private String highlight(String sql) {
return FormatStyle.HIGHLIGHT.getFormatter().format(sql);
}

private boolean isDDL(String sql) {
return sql.startsWith("create") || sql.startsWith("alter") || sql.startsWith("comment");
}

private boolean isBasic(String sql) {
return sql.startsWith("select") || sql.startsWith("insert") || sql.startsWith("update") || sql.startsWith("delete");
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package pothole_solution.manager.global.converter;

import org.jetbrains.annotations.NotNull;
import org.springframework.core.convert.converter.Converter;
import pothole_solution.manager.report.entity.ReportPeriod;

public class ReportPeriodEnumConverter implements Converter<String, ReportPeriod> {
@Override
public ReportPeriod convert(@NotNull String period) {
return ReportPeriod.enumOf(period);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package pothole_solution.manager.global.formatter;

import org.jetbrains.annotations.NotNull;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import pothole_solution.manager.global.converter.ReportPeriodEnumConverter;

@Configuration
public class EnumFormatter implements WebMvcConfigurer {
@Override
public void addFormatters(@NotNull FormatterRegistry registry) {
registry.addConverter(new ReportPeriodEnumConverter());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package pothole_solution.manager.report.controller;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import pothole_solution.core.global.util.response.BaseResponse;
import pothole_solution.manager.report.dto.RespPotDngrCntByPeriodDto;
import pothole_solution.manager.report.entity.ReportPeriod;
import pothole_solution.manager.report.service.ReportService;

import java.time.LocalDate;
import java.util.List;

@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/pothole/v1/manager")
public class ReportController {
private final ReportService reportService;

@GetMapping("/pothole-report")
public BaseResponse<List<RespPotDngrCntByPeriodDto>> getPotDngrCntByPeriod(@RequestParam(value = "startDate") @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate startDate,
@RequestParam(value = "endDate") @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate endDate,
@RequestParam(value = "reportPeriod") ReportPeriod reportPeriod) {

List<RespPotDngrCntByPeriodDto> periodPotholeCounts = reportService.getPeriodPotholeDangerousCount(startDate, endDate, reportPeriod);

return new BaseResponse<>(periodPotholeCounts);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package pothole_solution.manager.report.dto;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public class RespPotDngrCntByPeriodDto {
String period;
Long dangerous0to20;
Long dangerous20to40;
Long dangerous40to60;
Long dangerous60to80;
Long dangerous80to100;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package pothole_solution.manager.report.entity;

import lombok.AllArgsConstructor;
import lombok.Getter;
import pothole_solution.core.global.exception.CustomException;

@Getter
@AllArgsConstructor
public enum ReportPeriod {
MONTHLY("YYYY-MM"),
WEEKLY("YYYY-MM-W"),
DAILY("YYYY-MM-DD"),
AUTO("auto");

private final String queryOfPeriod;

public static ReportPeriod enumOf(String period) {
try {
return ReportPeriod.valueOf(period.toUpperCase());
} catch (RuntimeException e) {
throw CustomException.MISMATCH_PERIOD;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package pothole_solution.manager.report.repository;

import com.querydsl.core.types.Projections;
import com.querydsl.core.types.dsl.Expressions;
import com.querydsl.core.types.dsl.NumberExpression;
import com.querydsl.core.types.dsl.StringTemplate;
import com.querydsl.jpa.impl.JPAQueryFactory;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Repository;
import pothole_solution.manager.report.dto.RespPotDngrCntByPeriodDto;

import java.time.LocalDateTime;
import java.util.List;

import static pothole_solution.core.domain.pothole.entity.QPothole.pothole;

@Slf4j
@Repository
@RequiredArgsConstructor
public class ReportQueryDslRepository {
private final JPAQueryFactory jpaQueryFactory;

public List<RespPotDngrCntByPeriodDto> getPotDngrCntByPeriod(LocalDateTime startDate, LocalDateTime endDate, String queryOfPeriod) {
return jpaQueryFactory
.select(
Projections.constructor(RespPotDngrCntByPeriodDto.class,
convertDateFormat(queryOfPeriod),
countDangerousBetween(1, 20),
countDangerousBetween(21, 40),
countDangerousBetween(41, 60),
countDangerousBetween(61, 80),
countDangerousBetween(81, 100)
)
)
.from(pothole)
.where(pothole.createdAt.between(startDate, endDate))
.groupBy(convertDateFormat(queryOfPeriod))
.orderBy(convertDateFormat(queryOfPeriod).asc())
.fetch();
}

private static StringTemplate convertDateFormat(String queryOfPeriod) {
return Expressions.stringTemplate(
"to_char({0},'" + queryOfPeriod + "')"
, pothole.createdAt);
}

private static NumberExpression<Long> countDangerousBetween(int from, int to) {
return Expressions.cases()
.when(pothole.dangerous.between(from, to))
.then(1L)
.otherwise(0L)
.sum();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package pothole_solution.manager.report.service;

import java.time.LocalDate;
import java.time.temporal.ChronoUnit;

public class AutoPeriodCalculator {
private static final int CRITERIA_OF_MONTHLY = 3;
private static final int CRITERIA_OF_WEEKLY = 3;

protected static boolean isMonthly(LocalDate startDate, LocalDate endDate) {
return ChronoUnit.MONTHS.between(startDate, endDate) >= CRITERIA_OF_MONTHLY;
}

protected static boolean isWeekly(LocalDate startDate, LocalDate endDate) {
return ChronoUnit.MONTHS.between(startDate, endDate) < CRITERIA_OF_MONTHLY
&& ChronoUnit.WEEKS.between(startDate, endDate) >= CRITERIA_OF_WEEKLY;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package pothole_solution.manager.report.service;

import pothole_solution.manager.report.entity.ReportPeriod;
import pothole_solution.manager.report.dto.RespPotDngrCntByPeriodDto;

import java.time.LocalDate;
import java.util.List;

public interface ReportService {
List<RespPotDngrCntByPeriodDto> getPeriodPotholeDangerousCount(LocalDate startDate, LocalDate endDate, ReportPeriod period);
}
Loading
Loading