Skip to content

Commit

Permalink
Merge pull request #86 from lcj1204/feat/manager/POT-63
Browse files Browse the repository at this point in the history
[POT_63][Feat] 레포트 조회 API 구현
  • Loading branch information
donghyuk454 authored Jul 27, 2024
2 parents f348114 + 5ab32be commit de2d6de
Show file tree
Hide file tree
Showing 16 changed files with 348 additions and 3 deletions.
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

0 comments on commit de2d6de

Please sign in to comment.