From 725cad510eefcc81f268ef9ea8a55d88da75f02d Mon Sep 17 00:00:00 2001 From: teddy Date: Tue, 18 Nov 2025 11:35:28 +0900 Subject: [PATCH 1/8] =?UTF-8?q?=EA=B3=B5=ED=86=B5=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../code/CommonCodeApiController.java | 52 ++++++++++++ .../cd/kamcoback/code/dto/CommonCodeDto.java | 83 ++++++++++++++++++ .../code/service/CommonCodeService.java | 26 ++++++ .../kamcoback/postgres/CommonDateEntity.java | 4 +- .../postgres/core/CommonCodeCoreService.java | 53 ++++++++++++ .../postgres/entity/CommonCodeEntity.java | 85 +++++++++++++++++++ .../repository/CommonCodeRepository.java | 8 ++ .../CommonCodeRepositoryCustom.java | 5 ++ .../repository/CommonCodeRepositoryImpl.java | 5 ++ 9 files changed, 319 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/kamco/cd/kamcoback/code/CommonCodeApiController.java create mode 100644 src/main/java/com/kamco/cd/kamcoback/code/dto/CommonCodeDto.java create mode 100644 src/main/java/com/kamco/cd/kamcoback/code/service/CommonCodeService.java create mode 100644 src/main/java/com/kamco/cd/kamcoback/postgres/core/CommonCodeCoreService.java create mode 100644 src/main/java/com/kamco/cd/kamcoback/postgres/entity/CommonCodeEntity.java create mode 100644 src/main/java/com/kamco/cd/kamcoback/postgres/repository/CommonCodeRepository.java create mode 100644 src/main/java/com/kamco/cd/kamcoback/postgres/repository/CommonCodeRepositoryCustom.java create mode 100644 src/main/java/com/kamco/cd/kamcoback/postgres/repository/CommonCodeRepositoryImpl.java diff --git a/src/main/java/com/kamco/cd/kamcoback/code/CommonCodeApiController.java b/src/main/java/com/kamco/cd/kamcoback/code/CommonCodeApiController.java new file mode 100644 index 00000000..5b866700 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/code/CommonCodeApiController.java @@ -0,0 +1,52 @@ +package com.kamco.cd.kamcoback.code; + +import com.kamco.cd.kamcoback.code.dto.CommonCodeDto; +import com.kamco.cd.kamcoback.code.service.CommonCodeService; +import com.kamco.cd.kamcoback.config.api.ApiResponseDto; +import com.kamco.cd.kamcoback.zoo.dto.AnimalDto; +import com.kamco.cd.kamcoback.zoo.dto.ZooDto; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@Tag(name = "공통코드 관리", description = "공통코드 관리 API") +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/code") +public class CommonCodeApiController { + + private final CommonCodeService commonCodeService; + + @Operation(summary = "저장", description = "공통코드를 저장 합니다.") + @ApiResponses( + value = { + @ApiResponse(responseCode = "201", description = "공통코드 저장 성공", content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = Long.class))), + @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @PostMapping + public ApiResponseDto save( + @io.swagger.v3.oas.annotations.parameters.RequestBody( + description = "공통코드 생성 요청 정보", + required = true, + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = CommonCodeDto.AddReq.class))) + @RequestBody + CommonCodeDto.AddReq req) { + Long id = commonCodeService.save(req); + return ApiResponseDto.createOK(id); + } +} diff --git a/src/main/java/com/kamco/cd/kamcoback/code/dto/CommonCodeDto.java b/src/main/java/com/kamco/cd/kamcoback/code/dto/CommonCodeDto.java new file mode 100644 index 00000000..10e16cea --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/code/dto/CommonCodeDto.java @@ -0,0 +1,83 @@ +package com.kamco.cd.kamcoback.code.dto; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.kamco.cd.kamcoback.postgres.entity.CommonCodeEntity; +import com.kamco.cd.kamcoback.zoo.dto.AnimalDto.Category; +import com.kamco.cd.kamcoback.zoo.dto.AnimalDto.Species; +import io.swagger.v3.oas.annotations.media.Schema; +import java.time.Instant; +import java.time.ZonedDateTime; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + + +public class CommonCodeDto { + + @Schema(name = "CodeAddReq", description = "코드저장 요청") + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class AddReq { + + private String code; + private String name; + private String description; + private int order; + private boolean used; + private Long parentId; + } + + @Schema(name = "AnimalBasic", description = "동물 기본 정보") + @Getter + public static class Basic { + + @JsonIgnore + private Long id; + private String codeCd; + private String cdCt; + private String cdNm; + private Integer cdOdr; + private Boolean used; + private Boolean deleted; + CommonCodeEntity parent; + + @JsonFormat( + shape = JsonFormat.Shape.STRING, + pattern = "yyyy-MM-dd'T'HH:mm:ssXXX", + timezone = "Asia/Seoul") + private ZonedDateTime createdAt; + + @JsonFormat( + shape = JsonFormat.Shape.STRING, + pattern = "yyyy-MM-dd'T'HH:mm:ssXXX", + timezone = "Asia/Seoul") + private ZonedDateTime updatedAt; + + public Basic( + Long id, + String codeCd, + String cdCt, + String cdNm, + Integer cdOdr, + Boolean used, + Boolean deleted, + CommonCodeEntity parent, + ZonedDateTime createdAt, + ZonedDateTime updatedAt) { + this.id = id; + this.codeCd = codeCd; + this.cdCt = cdCt; + this.cdNm = cdNm; + this.cdOdr = cdOdr; + this.used = used; + this.deleted = deleted; + this.parent = parent; + this.createdAt = createdAt; + this.updatedAt = updatedAt; + } + } +} diff --git a/src/main/java/com/kamco/cd/kamcoback/code/service/CommonCodeService.java b/src/main/java/com/kamco/cd/kamcoback/code/service/CommonCodeService.java new file mode 100644 index 00000000..e5e5e8f5 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/code/service/CommonCodeService.java @@ -0,0 +1,26 @@ +package com.kamco.cd.kamcoback.code.service; + +import com.kamco.cd.kamcoback.code.dto.CommonCodeDto; +import com.kamco.cd.kamcoback.postgres.core.CommonCodeCoreService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class CommonCodeService { + + private final CommonCodeCoreService commonCodeCoreService; + + /** + * 공통코드 생성 요청 + * @param req 생성요청 정보 + * @return 생성된 코드 id + */ + @Transactional + public Long save(CommonCodeDto.AddReq req) { + Long id = commonCodeCoreService.save(req).getId(); + return id; + } +} diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/CommonDateEntity.java b/src/main/java/com/kamco/cd/kamcoback/postgres/CommonDateEntity.java index 9d230de1..b10e54c0 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/CommonDateEntity.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/CommonDateEntity.java @@ -14,11 +14,11 @@ import org.springframework.data.annotation.LastModifiedDate; public class CommonDateEntity { @CreatedDate - @Column(name = "created_date", updatable = false, nullable = false) + @Column(name = "created_dttm", updatable = false, nullable = false) private ZonedDateTime createdDate; @LastModifiedDate - @Column(name = "modified_date", nullable = false) + @Column(name = "updated_dttm", nullable = false) private ZonedDateTime modifiedDate; @PrePersist diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/core/CommonCodeCoreService.java b/src/main/java/com/kamco/cd/kamcoback/postgres/core/CommonCodeCoreService.java new file mode 100644 index 00000000..762847a3 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/core/CommonCodeCoreService.java @@ -0,0 +1,53 @@ +package com.kamco.cd.kamcoback.postgres.core; + +import com.kamco.cd.kamcoback.code.dto.CommonCodeDto; +import com.kamco.cd.kamcoback.code.dto.CommonCodeDto.Basic; +import com.kamco.cd.kamcoback.common.service.BaseCoreService; +import com.kamco.cd.kamcoback.postgres.entity.AnimalEntity; +import com.kamco.cd.kamcoback.postgres.entity.CommonCodeEntity; +import com.kamco.cd.kamcoback.postgres.entity.ZooEntity; +import com.kamco.cd.kamcoback.postgres.repository.CommonCodeRepository; +import com.kamco.cd.kamcoback.zoo.dto.AnimalDto.SearchReq; +import jakarta.persistence.EntityNotFoundException; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class CommonCodeCoreService implements BaseCoreService { + + private final CommonCodeRepository commonCodeRepository; + + public CommonCodeDto.Basic save(CommonCodeDto.AddReq req) { + CommonCodeEntity code = null; +// if (req.getZooUuid() != null) { +// zoo = +// zooRepository +// .getZooByUuid(req.getZooUuid()) +// .orElseThrow( +// () -> +// new EntityNotFoundException("Zoo not found with uuid: " + req.getZooUuid())); +// } + CommonCodeEntity entity = new CommonCodeEntity(req.getCode(), req.getName(), req.getDescription(), req.getOrder(), req.isUsed()); + CommonCodeEntity saved = commonCodeRepository.save(entity); + return saved.toDto(); + } + + @Override + public void remove(Long aLong) { + + } + + @Override + public Basic getOneById(Long aLong) { + return null; + } + + @Override + public Page search(SearchReq searchReq) { + return null; + } +} diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/entity/CommonCodeEntity.java b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/CommonCodeEntity.java new file mode 100644 index 00000000..52efafbe --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/CommonCodeEntity.java @@ -0,0 +1,85 @@ +package com.kamco.cd.kamcoback.postgres.entity; + +import com.kamco.cd.kamcoback.code.dto.CommonCodeDto; +import com.kamco.cd.kamcoback.postgres.CommonDateEntity; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import java.time.ZonedDateTime; +import java.util.LinkedHashSet; +import java.util.Set; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Table(name = "tb_cm_cd") +public class CommonCodeEntity extends CommonDateEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "code_id", nullable = false) + private Long id; + + @Size(max = 255) + @Column(name = "code_cd") + private String codeCd; + + @Size(max = 255) + @Column(name = "cd_ct") + private String cdCt; + + @Size(max = 255) + @Column(name = "cd_nm") + private String cdNm; + + @Column(name = "cd_odr") + private Integer cdOdr; + + @Column(name = "used") + private Boolean used; + + @NotNull + @Column(name = "deleted", nullable = false) + private Boolean deleted = false; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "parent_id") + private CommonCodeEntity parent; + + @OneToMany(mappedBy = "parent") + private Set tbCmCds = new LinkedHashSet<>(); + + public CommonCodeEntity(String codeCd, String cdNm, String cdCt, Integer cdOdr, Boolean used) { + this.codeCd = codeCd; + this.cdNm = cdNm; + this.cdCt = cdCt; + this.cdOdr = cdOdr; + this.used = used; + } + + public CommonCodeDto.Basic toDto() { + return new CommonCodeDto.Basic( + this.id, + this.codeCd, + this.cdCt, + this.cdNm, + this.cdOdr, + this.used, + this.deleted, + this.parent, + super.getCreatedDate(), + super.getModifiedDate()); + } +} diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/CommonCodeRepository.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/CommonCodeRepository.java new file mode 100644 index 00000000..31894bbd --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/CommonCodeRepository.java @@ -0,0 +1,8 @@ +package com.kamco.cd.kamcoback.postgres.repository; + +import com.kamco.cd.kamcoback.postgres.entity.CommonCodeEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface CommonCodeRepository extends JpaRepository, CommonCodeRepositoryCustom { + +} diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/CommonCodeRepositoryCustom.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/CommonCodeRepositoryCustom.java new file mode 100644 index 00000000..b954cfe9 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/CommonCodeRepositoryCustom.java @@ -0,0 +1,5 @@ +package com.kamco.cd.kamcoback.postgres.repository; + +public interface CommonCodeRepositoryCustom { + +} diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/CommonCodeRepositoryImpl.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/CommonCodeRepositoryImpl.java new file mode 100644 index 00000000..ec5dd034 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/CommonCodeRepositoryImpl.java @@ -0,0 +1,5 @@ +package com.kamco.cd.kamcoback.postgres.repository; + +public class CommonCodeRepositoryImpl { + +} From 4e489b73339762d1350a47da7b369ed5b57761ea Mon Sep 17 00:00:00 2001 From: "gayoun.park" Date: Tue, 18 Nov 2025 12:03:20 +0900 Subject: [PATCH 2/8] =?UTF-8?q?update=5Fdttm=20=EC=9D=84=20nullable=20=3D?= =?UTF-8?q?=20true=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/kamco/cd/kamcoback/postgres/CommonDateEntity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/CommonDateEntity.java b/src/main/java/com/kamco/cd/kamcoback/postgres/CommonDateEntity.java index b10e54c0..195b182d 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/CommonDateEntity.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/CommonDateEntity.java @@ -18,7 +18,7 @@ public class CommonDateEntity { private ZonedDateTime createdDate; @LastModifiedDate - @Column(name = "updated_dttm", nullable = false) + @Column(name = "updated_dttm", nullable = true) //update_dttm 이 없는 테이블이 존재하기 때문에 true 로 선언함 private ZonedDateTime modifiedDate; @PrePersist From af4fe2b2ed10a9a7d3c9acee6269150716be37f5 Mon Sep 17 00:00:00 2001 From: "gayoun.park" Date: Tue, 18 Nov 2025 12:16:25 +0900 Subject: [PATCH 3/8] =?UTF-8?q?create=EC=9A=A9=20=EA=B8=B0=EB=B3=B8?= =?UTF-8?q?=EC=97=94=ED=8B=B0=ED=8B=B0,=20create/update=EA=B9=8C=EC=A7=80?= =?UTF-8?q?=20=EC=9E=88=EB=8A=94=20=EC=97=94=ED=8B=B0=ED=8B=B0=EB=A1=9C=20?= =?UTF-8?q?=EA=B5=AC=EB=B6=84=ED=95=98=EC=97=AC=20=EC=BB=A4=EB=B0=8B?= =?UTF-8?q?=ED=95=98=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../postgres/CommonCreateEntity.java | 23 +++++++++++++++++++ .../kamcoback/postgres/CommonDateEntity.java | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/kamco/cd/kamcoback/postgres/CommonCreateEntity.java diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/CommonCreateEntity.java b/src/main/java/com/kamco/cd/kamcoback/postgres/CommonCreateEntity.java new file mode 100644 index 00000000..e0950848 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/CommonCreateEntity.java @@ -0,0 +1,23 @@ +package com.kamco.cd.kamcoback.postgres; + +import jakarta.persistence.Column; +import jakarta.persistence.MappedSuperclass; +import jakarta.persistence.PrePersist; +import lombok.Getter; +import org.springframework.data.annotation.CreatedDate; + +import java.time.ZonedDateTime; + +@Getter +@MappedSuperclass +public class CommonCreateEntity { + + @CreatedDate + @Column(name = "created_dttm", updatable = false, nullable = false) + private ZonedDateTime createdDate; + + @PrePersist + protected void onPersist() { + this.createdDate = ZonedDateTime.now(); + } +} diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/CommonDateEntity.java b/src/main/java/com/kamco/cd/kamcoback/postgres/CommonDateEntity.java index 195b182d..b10e54c0 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/CommonDateEntity.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/CommonDateEntity.java @@ -18,7 +18,7 @@ public class CommonDateEntity { private ZonedDateTime createdDate; @LastModifiedDate - @Column(name = "updated_dttm", nullable = true) //update_dttm 이 없는 테이블이 존재하기 때문에 true 로 선언함 + @Column(name = "updated_dttm", nullable = false) private ZonedDateTime modifiedDate; @PrePersist From a63b4c33f3e6fe17c6a959f909b492fda4edaf48 Mon Sep 17 00:00:00 2001 From: teddy Date: Tue, 18 Nov 2025 14:15:52 +0900 Subject: [PATCH 4/8] JsonFormat --- build.gradle | 1 + .../code/CommonCodeApiController.java | 5 ++-- .../cd/kamcoback/code/dto/CommonCodeDto.java | 26 +++++++++---------- .../utils/interfaces/JsonFormatDttm.java | 18 +++++++++++++ .../postgres/core/CommonCodeCoreService.java | 18 ++++++------- .../postgres/entity/CommonCodeEntity.java | 6 ++++- 6 files changed, 47 insertions(+), 27 deletions(-) create mode 100644 src/main/java/com/kamco/cd/kamcoback/common/utils/interfaces/JsonFormatDttm.java diff --git a/build.gradle b/build.gradle index b11f19ac..fbc870ea 100644 --- a/build.gradle +++ b/build.gradle @@ -33,6 +33,7 @@ dependencies { annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + implementation 'org.springframework.boot:spring-boot-starter-validation' //geometry implementation 'com.fasterxml.jackson.core:jackson-databind' diff --git a/src/main/java/com/kamco/cd/kamcoback/code/CommonCodeApiController.java b/src/main/java/com/kamco/cd/kamcoback/code/CommonCodeApiController.java index 5b866700..157f1009 100644 --- a/src/main/java/com/kamco/cd/kamcoback/code/CommonCodeApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/code/CommonCodeApiController.java @@ -3,14 +3,13 @@ package com.kamco.cd.kamcoback.code; import com.kamco.cd.kamcoback.code.dto.CommonCodeDto; import com.kamco.cd.kamcoback.code.service.CommonCodeService; import com.kamco.cd.kamcoback.config.api.ApiResponseDto; -import com.kamco.cd.kamcoback.zoo.dto.AnimalDto; -import com.kamco.cd.kamcoback.zoo.dto.ZooDto; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -45,7 +44,7 @@ public class CommonCodeApiController { mediaType = "application/json", schema = @Schema(implementation = CommonCodeDto.AddReq.class))) @RequestBody - CommonCodeDto.AddReq req) { + @Valid CommonCodeDto.AddReq req) { Long id = commonCodeService.save(req); return ApiResponseDto.createOK(id); } diff --git a/src/main/java/com/kamco/cd/kamcoback/code/dto/CommonCodeDto.java b/src/main/java/com/kamco/cd/kamcoback/code/dto/CommonCodeDto.java index 10e16cea..498bf5e5 100644 --- a/src/main/java/com/kamco/cd/kamcoback/code/dto/CommonCodeDto.java +++ b/src/main/java/com/kamco/cd/kamcoback/code/dto/CommonCodeDto.java @@ -2,10 +2,12 @@ package com.kamco.cd.kamcoback.code.dto; import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.kamco.cd.kamcoback.common.utils.interfaces.JsonFormatDttm; import com.kamco.cd.kamcoback.postgres.entity.CommonCodeEntity; import com.kamco.cd.kamcoback.zoo.dto.AnimalDto.Category; import com.kamco.cd.kamcoback.zoo.dto.AnimalDto.Species; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; import java.time.Instant; import java.time.ZonedDateTime; import lombok.AllArgsConstructor; @@ -23,7 +25,9 @@ public class CommonCodeDto { @AllArgsConstructor public static class AddReq { + @NotEmpty private String code; + @NotEmpty private String name; private String description; private int order; @@ -45,17 +49,11 @@ public class CommonCodeDto { private Boolean deleted; CommonCodeEntity parent; - @JsonFormat( - shape = JsonFormat.Shape.STRING, - pattern = "yyyy-MM-dd'T'HH:mm:ssXXX", - timezone = "Asia/Seoul") - private ZonedDateTime createdAt; + @JsonFormatDttm + private ZonedDateTime createdDttm; - @JsonFormat( - shape = JsonFormat.Shape.STRING, - pattern = "yyyy-MM-dd'T'HH:mm:ssXXX", - timezone = "Asia/Seoul") - private ZonedDateTime updatedAt; + @JsonFormatDttm + private ZonedDateTime updatedDttm; public Basic( Long id, @@ -66,8 +64,8 @@ public class CommonCodeDto { Boolean used, Boolean deleted, CommonCodeEntity parent, - ZonedDateTime createdAt, - ZonedDateTime updatedAt) { + ZonedDateTime createdDttm, + ZonedDateTime updatedDttm) { this.id = id; this.codeCd = codeCd; this.cdCt = cdCt; @@ -76,8 +74,8 @@ public class CommonCodeDto { this.used = used; this.deleted = deleted; this.parent = parent; - this.createdAt = createdAt; - this.updatedAt = updatedAt; + this.createdDttm = createdDttm; + this.updatedDttm = updatedDttm; } } } diff --git a/src/main/java/com/kamco/cd/kamcoback/common/utils/interfaces/JsonFormatDttm.java b/src/main/java/com/kamco/cd/kamcoback/common/utils/interfaces/JsonFormatDttm.java new file mode 100644 index 00000000..44341876 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/common/utils/interfaces/JsonFormatDttm.java @@ -0,0 +1,18 @@ +package com.kamco.cd.kamcoback.common.utils.interfaces; + +import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; +import com.fasterxml.jackson.annotation.JsonFormat; +import java.lang.annotation.*; + +@Target({ ElementType.FIELD, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@JacksonAnnotationsInside +@JsonFormat( + shape = JsonFormat.Shape.STRING, + pattern = "yyyy-MM-dd'T'HH:mm:ssXXX", + timezone = "Asia/Seoul" +) +public @interface JsonFormatDttm { + +} diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/core/CommonCodeCoreService.java b/src/main/java/com/kamco/cd/kamcoback/postgres/core/CommonCodeCoreService.java index 762847a3..904aca9a 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/core/CommonCodeCoreService.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/core/CommonCodeCoreService.java @@ -22,15 +22,15 @@ public class CommonCodeCoreService implements BaseCoreService -// new EntityNotFoundException("Zoo not found with uuid: " + req.getZooUuid())); -// } + if(req.getParentId() != null){ + CommonCodeEntity parentCommonCodeEntity = commonCodeRepository.findById(req.getParentId()) + .orElseThrow(() -> new EntityNotFoundException("parent id 를 찾을 수 없습니다. id : " + req.getParentId())); + + CommonCodeEntity entity = new CommonCodeEntity(req.getCode(), req.getName(), req.getDescription(), req.getOrder(), req.isUsed()); + entity.addParent(parentCommonCodeEntity); + return commonCodeRepository.save(entity).toDto(); + } + CommonCodeEntity entity = new CommonCodeEntity(req.getCode(), req.getName(), req.getDescription(), req.getOrder(), req.isUsed()); CommonCodeEntity saved = commonCodeRepository.save(entity); return saved.toDto(); diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/entity/CommonCodeEntity.java b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/CommonCodeEntity.java index 52efafbe..27b43cc0 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/entity/CommonCodeEntity.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/CommonCodeEntity.java @@ -80,6 +80,10 @@ public class CommonCodeEntity extends CommonDateEntity { this.deleted, this.parent, super.getCreatedDate(), - super.getModifiedDate()); + super.getModifiedDate()); + } + + public void addParent(CommonCodeEntity parent) { + this.parent = parent; } } From 3053b0552e9c1776bef1c7253fe6115ab6bf859d Mon Sep 17 00:00:00 2001 From: teddy Date: Wed, 19 Nov 2025 10:09:06 +0900 Subject: [PATCH 5/8] =?UTF-8?q?=EA=B3=B5=ED=86=B5=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- http/CommonCode.http | 4 + .../code/CommonCodeApiController.java | 97 ++++++++++++++++++- .../cd/kamcoback/code/dto/CommonCodeDto.java | 53 +++++----- .../code/service/CommonCodeService.java | 39 +++++++- .../postgres/core/CommonCodeCoreService.java | 39 ++++++-- .../postgres/entity/CommonCodeEntity.java | 64 +++++++----- .../CommonCodeRepositoryCustom.java | 7 +- .../repository/CommonCodeRepositoryImpl.java | 49 +++++++++- 8 files changed, 288 insertions(+), 64 deletions(-) create mode 100644 http/CommonCode.http diff --git a/http/CommonCode.http b/http/CommonCode.http new file mode 100644 index 00000000..6083e221 --- /dev/null +++ b/http/CommonCode.http @@ -0,0 +1,4 @@ +### GET getByCodeId +GET http://localhost:8080/api/code/1 +Content-Type: application/json +### diff --git a/src/main/java/com/kamco/cd/kamcoback/code/CommonCodeApiController.java b/src/main/java/com/kamco/cd/kamcoback/code/CommonCodeApiController.java index 157f1009..8fcd7f70 100644 --- a/src/main/java/com/kamco/cd/kamcoback/code/CommonCodeApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/code/CommonCodeApiController.java @@ -10,8 +10,13 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; +import java.util.List; import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -24,13 +29,53 @@ public class CommonCodeApiController { private final CommonCodeService commonCodeService; + @Operation(summary = "목록 조회", description = "모든 공통코드 조회") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "200", + description = "조회 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = CommonCodeDto.Basic.class))), + @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @GetMapping + public ApiResponseDto> getFindAll() { + return ApiResponseDto.createOK(commonCodeService.getFindAll()); + } + + @Operation(summary = "단건 조회", description = "단건 조회") + @ApiResponses( + value = { + @ApiResponse( + responseCode = "200", + description = "조회 성공", + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = CommonCodeDto.Basic.class))), + @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @GetMapping("/{id}") + public ApiResponseDto getOneById( + @io.swagger.v3.oas.annotations.parameters.RequestBody( + description = "단건 조회", + required = true) + @PathVariable Long id) { + return ApiResponseDto.ok(commonCodeService.getOneById(id)); + } + @Operation(summary = "저장", description = "공통코드를 저장 합니다.") @ApiResponses( value = { @ApiResponse(responseCode = "201", description = "공통코드 저장 성공", content = - @Content( - mediaType = "application/json", - schema = @Schema(implementation = Long.class))), + @Content( + mediaType = "application/json", + schema = @Schema(implementation = Long.class))), @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) }) @@ -45,7 +90,49 @@ public class CommonCodeApiController { schema = @Schema(implementation = CommonCodeDto.AddReq.class))) @RequestBody @Valid CommonCodeDto.AddReq req) { - Long id = commonCodeService.save(req); - return ApiResponseDto.createOK(id); + return ApiResponseDto.createOK(commonCodeService.save(req)); + } + + @Operation(summary = "수정", description = "공통코드를 수정 합니다.") + @ApiResponses( + value = { + @ApiResponse(responseCode = "201", description = "공통코드 수정 성공", content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = Long.class))), + @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @PutMapping("/{id}") + public ApiResponseDto update( + @io.swagger.v3.oas.annotations.parameters.RequestBody( + description = "공통코드 수정 요청 정보", + required = true, + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = CommonCodeDto.ModifyReq.class))) + @PathVariable Long id, @RequestBody @Valid CommonCodeDto.ModifyReq req) { + return ApiResponseDto.createOK(commonCodeService.update(id, req)); + } + + @Operation(summary = "삭제", description = "공통코드를 삭제 합니다.") + @ApiResponses( + value = { + @ApiResponse(responseCode = "201", description = "공통코드 삭제 성공", content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = Long.class))), + @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @DeleteMapping("/{id}") + public ApiResponseDto remove( + @io.swagger.v3.oas.annotations.parameters.RequestBody( + description = "공통코드 삭제 요청 정보", + required = true) + @PathVariable Long id) { + commonCodeService.remove(id); + return ApiResponseDto.deleteOk(id); } } diff --git a/src/main/java/com/kamco/cd/kamcoback/code/dto/CommonCodeDto.java b/src/main/java/com/kamco/cd/kamcoback/code/dto/CommonCodeDto.java index 498bf5e5..7081fc1c 100644 --- a/src/main/java/com/kamco/cd/kamcoback/code/dto/CommonCodeDto.java +++ b/src/main/java/com/kamco/cd/kamcoback/code/dto/CommonCodeDto.java @@ -1,15 +1,12 @@ package com.kamco.cd.kamcoback.code.dto; -import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonIgnore; import com.kamco.cd.kamcoback.common.utils.interfaces.JsonFormatDttm; import com.kamco.cd.kamcoback.postgres.entity.CommonCodeEntity; -import com.kamco.cd.kamcoback.zoo.dto.AnimalDto.Category; -import com.kamco.cd.kamcoback.zoo.dto.AnimalDto.Species; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotEmpty; -import java.time.Instant; import java.time.ZonedDateTime; +import java.util.List; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; @@ -18,7 +15,7 @@ import lombok.Setter; public class CommonCodeDto { - @Schema(name = "CodeAddReq", description = "코드저장 요청") + @Schema(name = "CodeAddReq", description = "공통코드 저장 정보") @Getter @Setter @NoArgsConstructor @@ -35,19 +32,31 @@ public class CommonCodeDto { private Long parentId; } - @Schema(name = "AnimalBasic", description = "동물 기본 정보") + @Schema(name = "CodeModifyReq", description = "공통코드 수정 정보") + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class ModifyReq { + @NotEmpty + private String name; + private String description; + private int order; + private boolean used; + } + + @Schema(name = "CommonCode Basic", description = "공통코드 기본 정보") @Getter public static class Basic { - @JsonIgnore private Long id; - private String codeCd; - private String cdCt; - private String cdNm; - private Integer cdOdr; + private String code; + private String description; + private String name; + private Integer order; private Boolean used; private Boolean deleted; - CommonCodeEntity parent; + private List children; @JsonFormatDttm private ZonedDateTime createdDttm; @@ -57,23 +66,23 @@ public class CommonCodeDto { public Basic( Long id, - String codeCd, - String cdCt, - String cdNm, - Integer cdOdr, + String code, + String description, + String name, + Integer order, Boolean used, Boolean deleted, - CommonCodeEntity parent, + List children, ZonedDateTime createdDttm, ZonedDateTime updatedDttm) { this.id = id; - this.codeCd = codeCd; - this.cdCt = cdCt; - this.cdNm = cdNm; - this.cdOdr = cdOdr; + this.code = code; + this.description = description; + this.name = name; + this.order = order; this.used = used; this.deleted = deleted; - this.parent = parent; + this.children = children; this.createdDttm = createdDttm; this.updatedDttm = updatedDttm; } diff --git a/src/main/java/com/kamco/cd/kamcoback/code/service/CommonCodeService.java b/src/main/java/com/kamco/cd/kamcoback/code/service/CommonCodeService.java index e5e5e8f5..a573fa4c 100644 --- a/src/main/java/com/kamco/cd/kamcoback/code/service/CommonCodeService.java +++ b/src/main/java/com/kamco/cd/kamcoback/code/service/CommonCodeService.java @@ -2,6 +2,7 @@ package com.kamco.cd.kamcoback.code.service; import com.kamco.cd.kamcoback.code.dto.CommonCodeDto; import com.kamco.cd.kamcoback.postgres.core.CommonCodeCoreService; +import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -13,6 +14,24 @@ public class CommonCodeService { private final CommonCodeCoreService commonCodeCoreService; + + /** + * 공통코드 목록 조회 + * @return + */ + public List getFindAll() { + return commonCodeCoreService.findAll(); + } + + /** + * 공통코드 단건 조회 + * @param id + * @return + */ + public CommonCodeDto.Basic getOneById(Long id) { + return commonCodeCoreService.getOneById(id); + } + /** * 공통코드 생성 요청 * @param req 생성요청 정보 @@ -20,7 +39,23 @@ public class CommonCodeService { */ @Transactional public Long save(CommonCodeDto.AddReq req) { - Long id = commonCodeCoreService.save(req).getId(); - return id; + return commonCodeCoreService.save(req).getId(); } + + /** + * 공통코드 수정 요청 + * @param id 코드 아이디 + * @param req 수정요청 정보 + * @return + */ + @Transactional + public Long update(Long id, CommonCodeDto.ModifyReq req) { + return commonCodeCoreService.update(id, req).getId(); + } + + @Transactional + public void remove(Long id) { + commonCodeCoreService.remove(id); + } + } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/core/CommonCodeCoreService.java b/src/main/java/com/kamco/cd/kamcoback/postgres/core/CommonCodeCoreService.java index 904aca9a..cc3270e7 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/core/CommonCodeCoreService.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/core/CommonCodeCoreService.java @@ -3,24 +3,27 @@ package com.kamco.cd.kamcoback.postgres.core; import com.kamco.cd.kamcoback.code.dto.CommonCodeDto; import com.kamco.cd.kamcoback.code.dto.CommonCodeDto.Basic; import com.kamco.cd.kamcoback.common.service.BaseCoreService; -import com.kamco.cd.kamcoback.postgres.entity.AnimalEntity; import com.kamco.cd.kamcoback.postgres.entity.CommonCodeEntity; -import com.kamco.cd.kamcoback.postgres.entity.ZooEntity; import com.kamco.cd.kamcoback.postgres.repository.CommonCodeRepository; import com.kamco.cd.kamcoback.zoo.dto.AnimalDto.SearchReq; import jakarta.persistence.EntityNotFoundException; +import java.util.List; +import java.util.NoSuchElementException; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; @Service @RequiredArgsConstructor -@Transactional(readOnly = true) -public class CommonCodeCoreService implements BaseCoreService { +public class CommonCodeCoreService implements BaseCoreService { private final CommonCodeRepository commonCodeRepository; + + public List findAll() { + return commonCodeRepository.findByAll().stream().map(CommonCodeEntity::toDto).toList(); + } + public CommonCodeDto.Basic save(CommonCodeDto.AddReq req) { if(req.getParentId() != null){ CommonCodeEntity parentCommonCodeEntity = commonCodeRepository.findById(req.getParentId()) @@ -32,18 +35,34 @@ public class CommonCodeCoreService implements BaseCoreServicenew NoSuchElementException("common code 를 찾을 수 없습니다. id : " + id)); + + CommonCodeEntity entity = new CommonCodeEntity( id, req.getName(), req.getDescription(), req.getOrder(), req.isUsed(), found.getDeleted()); + + return commonCodeRepository.save(entity).toDto(); } @Override - public void remove(Long aLong) { + public void remove(Long id) { + CommonCodeEntity entity = commonCodeRepository.findByCodeId(id) + .orElseThrow(()->new EntityNotFoundException("code를 찾을 수 없습니다. id " + id)); + // 하위 코드 deleted = false 업데이트 + entity.getChildren().forEach(CommonCodeEntity::deleted); + // id 코드 deleted = false 업데이트 + entity.deleted(); } @Override - public Basic getOneById(Long aLong) { - return null; + public Basic getOneById(Long id) { + CommonCodeEntity entity = commonCodeRepository.findByCodeId(id) + .orElseThrow(()->new EntityNotFoundException("code를 찾을 수 없습니다. id " + id)); + return entity.toDto(); } @Override diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/entity/CommonCodeEntity.java b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/CommonCodeEntity.java index 27b43cc0..e5eb4a14 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/entity/CommonCodeEntity.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/CommonCodeEntity.java @@ -2,6 +2,7 @@ package com.kamco.cd.kamcoback.postgres.entity; import com.kamco.cd.kamcoback.code.dto.CommonCodeDto; import com.kamco.cd.kamcoback.postgres.CommonDateEntity; +import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; @@ -14,9 +15,8 @@ import jakarta.persistence.OneToMany; import jakarta.persistence.Table; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; -import java.time.ZonedDateTime; -import java.util.LinkedHashSet; -import java.util.Set; +import java.util.ArrayList; +import java.util.List; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; @@ -29,23 +29,23 @@ public class CommonCodeEntity extends CommonDateEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "code_id", nullable = false) + @Column(name = "code_id", nullable = false, updatable = false) private Long id; @Size(max = 255) - @Column(name = "code_cd") - private String codeCd; + @Column(name = "code_cd", updatable = false) + private String code; @Size(max = 255) @Column(name = "cd_ct") - private String cdCt; + private String description; @Size(max = 255) @Column(name = "cd_nm") - private String cdNm; + private String name; @Column(name = "cd_odr") - private Integer cdOdr; + private Integer order; @Column(name = "used") private Boolean used; @@ -55,35 +55,53 @@ public class CommonCodeEntity extends CommonDateEntity { private Boolean deleted = false; @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "parent_id") + @JoinColumn(name = "parent_id", updatable = false) private CommonCodeEntity parent; - @OneToMany(mappedBy = "parent") - private Set tbCmCds = new LinkedHashSet<>(); + @OneToMany(mappedBy = "parent", fetch = FetchType.LAZY, cascade = CascadeType.ALL) + private List children = new ArrayList<>(); - public CommonCodeEntity(String codeCd, String cdNm, String cdCt, Integer cdOdr, Boolean used) { - this.codeCd = codeCd; - this.cdNm = cdNm; - this.cdCt = cdCt; - this.cdOdr = cdOdr; + public CommonCodeEntity(String code, String name, String description, Integer order, Boolean used) { + this.code = code; + this.name = name; + this.description = description; + this.order = order; this.used = used; } + public CommonCodeEntity(Long id, String name, String description, Integer order, Boolean used, Boolean deleted) { + this.id = id; + this.name = name; + this.description = description; + this.order = order; + this.used = used; + this.deleted = deleted; + } + public CommonCodeDto.Basic toDto() { return new CommonCodeDto.Basic( this.id, - this.codeCd, - this.cdCt, - this.cdNm, - this.cdOdr, + this.code, + this.description, + this.name, + this.order, this.used, this.deleted, - this.parent, + this.children.stream().map(CommonCodeEntity::toDto).toList(), super.getCreatedDate(), - super.getModifiedDate()); + super.getModifiedDate()); } + public void addParent(CommonCodeEntity parent) { this.parent = parent; } + + public boolean isDeleted() { + return deleted; + } + + public void deleted() { + this.deleted = true; + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/CommonCodeRepositoryCustom.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/CommonCodeRepositoryCustom.java index b954cfe9..5d48e955 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/CommonCodeRepositoryCustom.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/CommonCodeRepositoryCustom.java @@ -1,5 +1,10 @@ package com.kamco.cd.kamcoback.postgres.repository; -public interface CommonCodeRepositoryCustom { +import com.kamco.cd.kamcoback.postgres.entity.CommonCodeEntity; +import java.util.List; +import java.util.Optional; +public interface CommonCodeRepositoryCustom { + Optional findByCodeId(Long id); + List findByAll(); } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/CommonCodeRepositoryImpl.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/CommonCodeRepositoryImpl.java index ec5dd034..ea928778 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/CommonCodeRepositoryImpl.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/CommonCodeRepositoryImpl.java @@ -1,5 +1,52 @@ package com.kamco.cd.kamcoback.postgres.repository; -public class CommonCodeRepositoryImpl { +import static com.kamco.cd.kamcoback.postgres.entity.QCommonCodeEntity.commonCodeEntity; +import com.kamco.cd.kamcoback.postgres.entity.CommonCodeEntity; +import com.kamco.cd.kamcoback.postgres.entity.QCommonCodeEntity; +import com.querydsl.jpa.impl.JPAQueryFactory; +import java.util.List; +import java.util.Optional; +import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport; + +public class CommonCodeRepositoryImpl extends QuerydslRepositorySupport implements CommonCodeRepositoryCustom { + + private final JPAQueryFactory queryFactory; + + public CommonCodeRepositoryImpl(JPAQueryFactory queryFactory) { + super(CommonCodeEntity.class); + this.queryFactory = queryFactory; + } + + @Override + public Optional findByCodeId(Long id) { + QCommonCodeEntity child = new QCommonCodeEntity("child"); + return Optional.ofNullable( + queryFactory + .selectFrom(commonCodeEntity) + .leftJoin(commonCodeEntity.children, child) + .fetchJoin() + .where( + commonCodeEntity.id.eq(id), + commonCodeEntity.deleted.isFalse() + ) + .orderBy(commonCodeEntity.order.asc(), child.order.asc()) + .fetchOne() + ); + } + + @Override + public List findByAll() { + QCommonCodeEntity child = new QCommonCodeEntity("child"); + return queryFactory + .selectFrom(commonCodeEntity) + .leftJoin(commonCodeEntity.children, child) + .fetchJoin() + .where( + commonCodeEntity.parent.isNull(), + commonCodeEntity.deleted.isFalse() + ) + .orderBy(commonCodeEntity.order.asc(), child.order.asc()) + .fetch(); + } } From ad471df3e86aad8062036ba028db7180dfe276ec Mon Sep 17 00:00:00 2001 From: teddy Date: Wed, 19 Nov 2025 11:40:33 +0900 Subject: [PATCH 6/8] =?UTF-8?q?=EA=B3=B5=ED=86=B5=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=EA=B8=B0=EB=B0=98=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../code/CommonCodeApiController.java | 60 +++++++++++++++++-- .../cd/kamcoback/code/dto/CommonCodeDto.java | 23 +++++++ .../code/service/CommonCodeService.java | 21 +++++++ .../postgres/core/CommonCodeCoreService.java | 12 +++- .../postgres/entity/CommonCodeEntity.java | 4 ++ .../CommonCodeRepositoryCustom.java | 3 + .../repository/CommonCodeRepositoryImpl.java | 52 ++++++++++++++++ 7 files changed, 168 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/kamco/cd/kamcoback/code/CommonCodeApiController.java b/src/main/java/com/kamco/cd/kamcoback/code/CommonCodeApiController.java index 8fcd7f70..a5dbe6e6 100644 --- a/src/main/java/com/kamco/cd/kamcoback/code/CommonCodeApiController.java +++ b/src/main/java/com/kamco/cd/kamcoback/code/CommonCodeApiController.java @@ -19,6 +19,7 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @Tag(name = "공통코드 관리", description = "공통코드 관리 API") @@ -77,6 +78,7 @@ public class CommonCodeApiController { mediaType = "application/json", schema = @Schema(implementation = Long.class))), @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), + @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) }) @PostMapping @@ -96,15 +98,16 @@ public class CommonCodeApiController { @Operation(summary = "수정", description = "공통코드를 수정 합니다.") @ApiResponses( value = { - @ApiResponse(responseCode = "201", description = "공통코드 수정 성공", content = + @ApiResponse(responseCode = "204", description = "공통코드 수정 성공", content = @Content( mediaType = "application/json", schema = @Schema(implementation = Long.class))), @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), + @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) }) @PutMapping("/{id}") - public ApiResponseDto update( + public ApiResponseDto update( @io.swagger.v3.oas.annotations.parameters.RequestBody( description = "공통코드 수정 요청 정보", required = true, @@ -113,17 +116,19 @@ public class CommonCodeApiController { mediaType = "application/json", schema = @Schema(implementation = CommonCodeDto.ModifyReq.class))) @PathVariable Long id, @RequestBody @Valid CommonCodeDto.ModifyReq req) { - return ApiResponseDto.createOK(commonCodeService.update(id, req)); + commonCodeService.update(id, req); + return ApiResponseDto.deleteOk(null); } @Operation(summary = "삭제", description = "공통코드를 삭제 합니다.") @ApiResponses( value = { - @ApiResponse(responseCode = "201", description = "공통코드 삭제 성공", content = + @ApiResponse(responseCode = "204", description = "공통코드 삭제 성공", content = @Content( mediaType = "application/json", schema = @Schema(implementation = Long.class))), @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), + @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) }) @DeleteMapping("/{id}") @@ -132,7 +137,52 @@ public class CommonCodeApiController { description = "공통코드 삭제 요청 정보", required = true) @PathVariable Long id) { - commonCodeService.remove(id); + commonCodeService.remove(id); return ApiResponseDto.deleteOk(id); } + + @Operation(summary = "순서 변경", description = "공통코드 순서를 변경 합니다.") + @ApiResponses( + value = { + @ApiResponse(responseCode = "204", description = "공통코드 순서 변경 성공", content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = Long.class))), + @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), + @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @PutMapping("/order") + public ApiResponseDto updateOrder( + @io.swagger.v3.oas.annotations.parameters.RequestBody( + description = "공통코드 순서변경 요청 정보", + required = true, + content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = CommonCodeDto.OrderReq.class))) + @RequestBody @Valid CommonCodeDto.OrderReq req) { + commonCodeService.updateOrder(req); + return ApiResponseDto.deleteOk(null); + } + + @Operation(summary = "code 기반 조회", description = "code 기반 조회") + @ApiResponses( + value = { + @ApiResponse(responseCode = "200", description = "공통코드 순서 변경 성공", content = + @Content( + mediaType = "application/json", + schema = @Schema(implementation = Long.class))), + @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content), + @ApiResponse(responseCode = "404", description = "코드를 찾을 수 없음", content = @Content), + @ApiResponse(responseCode = "500", description = "서버 오류", content = @Content) + }) + @GetMapping("/used") + public ApiResponseDto> getByCode( + @io.swagger.v3.oas.annotations.parameters.RequestBody( + description = "공통코드 순서변경 요청 정보", + required = true) + @RequestParam String code) { + return ApiResponseDto.ok(commonCodeService.findByCode(code)); + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/code/dto/CommonCodeDto.java b/src/main/java/com/kamco/cd/kamcoback/code/dto/CommonCodeDto.java index 7081fc1c..244cd71a 100644 --- a/src/main/java/com/kamco/cd/kamcoback/code/dto/CommonCodeDto.java +++ b/src/main/java/com/kamco/cd/kamcoback/code/dto/CommonCodeDto.java @@ -5,6 +5,8 @@ import com.kamco.cd.kamcoback.common.utils.interfaces.JsonFormatDttm; import com.kamco.cd.kamcoback.postgres.entity.CommonCodeEntity; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.PositiveOrZero; import java.time.ZonedDateTime; import java.util.List; import lombok.AllArgsConstructor; @@ -45,6 +47,27 @@ public class CommonCodeDto { private boolean used; } + @Schema(name = "CodeOrderReq", description = "공통코드 순서 변경 정보") + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class OrderReq { + List orders; + } + + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class OrderReqDetail { + @NotNull + private Long id; + + @NotNull + private Integer order; + } + @Schema(name = "CommonCode Basic", description = "공통코드 기본 정보") @Getter public static class Basic { diff --git a/src/main/java/com/kamco/cd/kamcoback/code/service/CommonCodeService.java b/src/main/java/com/kamco/cd/kamcoback/code/service/CommonCodeService.java index a573fa4c..ed779e35 100644 --- a/src/main/java/com/kamco/cd/kamcoback/code/service/CommonCodeService.java +++ b/src/main/java/com/kamco/cd/kamcoback/code/service/CommonCodeService.java @@ -53,9 +53,30 @@ public class CommonCodeService { return commonCodeCoreService.update(id, req).getId(); } + /** + * 공통코드 삭제 처리 + * @param id 코드 아이디 + */ @Transactional public void remove(Long id) { commonCodeCoreService.remove(id); } + /** + * 공통코드 순서 변경 + * @param req + */ + @Transactional + public void updateOrder(CommonCodeDto.OrderReq req) { + commonCodeCoreService.updateOrder(req); + } + + /** + * 코드기반 조회 + * @param code + * @return + */ + public List findByCode(String code) { + return commonCodeCoreService.findByCode(code); + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/core/CommonCodeCoreService.java b/src/main/java/com/kamco/cd/kamcoback/postgres/core/CommonCodeCoreService.java index cc3270e7..59705297 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/core/CommonCodeCoreService.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/core/CommonCodeCoreService.java @@ -8,7 +8,7 @@ import com.kamco.cd.kamcoback.postgres.repository.CommonCodeRepository; import com.kamco.cd.kamcoback.zoo.dto.AnimalDto.SearchReq; import jakarta.persistence.EntityNotFoundException; import java.util.List; -import java.util.NoSuchElementException; +import java.util.Optional; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.stereotype.Service; @@ -40,13 +40,21 @@ public class CommonCodeCoreService implements BaseCoreServicenew NoSuchElementException("common code 를 찾을 수 없습니다. id : " + id)); + .orElseThrow(()->new EntityNotFoundException("common code 를 찾을 수 없습니다. id : " + id)); CommonCodeEntity entity = new CommonCodeEntity( id, req.getName(), req.getDescription(), req.getOrder(), req.isUsed(), found.getDeleted()); return commonCodeRepository.save(entity).toDto(); } + public void updateOrder(CommonCodeDto.OrderReq req) { + commonCodeRepository.updateOrder(req); + } + + public List findByCode(String code) { + return commonCodeRepository.findByCode(code).stream().map(CommonCodeEntity::toDto).toList(); + } + @Override public void remove(Long id) { CommonCodeEntity entity = commonCodeRepository.findByCodeId(id) diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/entity/CommonCodeEntity.java b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/CommonCodeEntity.java index e5eb4a14..9ecbd892 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/entity/CommonCodeEntity.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/CommonCodeEntity.java @@ -104,4 +104,8 @@ public class CommonCodeEntity extends CommonDateEntity { public void deleted() { this.deleted = true; } + + public void updateOrder(int order) { + this.order = order; + } } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/CommonCodeRepositoryCustom.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/CommonCodeRepositoryCustom.java index 5d48e955..2a635907 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/CommonCodeRepositoryCustom.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/CommonCodeRepositoryCustom.java @@ -1,10 +1,13 @@ package com.kamco.cd.kamcoback.postgres.repository; +import com.kamco.cd.kamcoback.code.dto.CommonCodeDto; import com.kamco.cd.kamcoback.postgres.entity.CommonCodeEntity; import java.util.List; import java.util.Optional; public interface CommonCodeRepositoryCustom { Optional findByCodeId(Long id); + Optional findByCode(String code); List findByAll(); + void updateOrder(CommonCodeDto.OrderReq req); } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/CommonCodeRepositoryImpl.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/CommonCodeRepositoryImpl.java index ea928778..98bc7720 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/CommonCodeRepositoryImpl.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/CommonCodeRepositoryImpl.java @@ -2,11 +2,19 @@ package com.kamco.cd.kamcoback.postgres.repository; import static com.kamco.cd.kamcoback.postgres.entity.QCommonCodeEntity.commonCodeEntity; +import com.kamco.cd.kamcoback.code.dto.CommonCodeDto; +import com.kamco.cd.kamcoback.code.dto.CommonCodeDto.OrderReqDetail; import com.kamco.cd.kamcoback.postgres.entity.CommonCodeEntity; import com.kamco.cd.kamcoback.postgres.entity.QCommonCodeEntity; +import com.querydsl.core.types.dsl.Expressions; import com.querydsl.jpa.impl.JPAQueryFactory; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport; public class CommonCodeRepositoryImpl extends QuerydslRepositorySupport implements CommonCodeRepositoryCustom { @@ -35,6 +43,25 @@ public class CommonCodeRepositoryImpl extends QuerydslRepositorySupport implemen ); } + @Override + public Optional findByCode(String code) { + QCommonCodeEntity child = new QCommonCodeEntity("child"); + return Optional.ofNullable( + queryFactory + .selectFrom(commonCodeEntity) + .leftJoin(commonCodeEntity.children, child) + .fetchJoin() + .where( + commonCodeEntity.parent.isNull(), + commonCodeEntity.code.eq(code), + commonCodeEntity.used.isTrue(), + commonCodeEntity.deleted.isFalse() + ) + .orderBy(child.order.asc()) + .fetchOne() + ); + } + @Override public List findByAll() { QCommonCodeEntity child = new QCommonCodeEntity("child"); @@ -49,4 +76,29 @@ public class CommonCodeRepositoryImpl extends QuerydslRepositorySupport implemen .orderBy(commonCodeEntity.order.asc(), child.order.asc()) .fetch(); } + + @Override + public void updateOrder(CommonCodeDto.OrderReq req) { + Map orderMap = req.getOrders().stream(). + collect(Collectors.toMap(OrderReqDetail::getId, OrderReqDetail::getOrder)); + + List entity = findAllByIds(orderMap.keySet()); + + entity.forEach(commonCodeEntity -> { + Integer order = orderMap.get(commonCodeEntity.getId()); + if(order != null) { + commonCodeEntity.updateOrder(order); + } + }); + } + + private List findAllByIds(Set ids) { + return queryFactory + .selectFrom(commonCodeEntity) + .where( + commonCodeEntity.id.in(ids), + commonCodeEntity.deleted.isFalse() + ) + .fetch(); + } } From aaabd85c9c3b8f6cdab007642e126057f521de62 Mon Sep 17 00:00:00 2001 From: teddy Date: Wed, 19 Nov 2025 12:00:56 +0900 Subject: [PATCH 7/8] =?UTF-8?q?=EA=B3=B5=ED=86=B5=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EC=88=9C=EC=84=9C=20=EB=B3=80=EA=B2=BD=20=ED=8C=8C=EB=9D=BC?= =?UTF-8?q?=EB=AF=B8=ED=84=B0=20validation=20=EC=B2=B4=ED=81=AC=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cd/kamcoback/code/dto/CommonCodeDto.java | 2 ++ .../code/service/CommonCodeService.java | 30 ++++++++++--------- .../postgres/core/CommonCodeCoreService.java | 1 - 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/kamco/cd/kamcoback/code/dto/CommonCodeDto.java b/src/main/java/com/kamco/cd/kamcoback/code/dto/CommonCodeDto.java index 244cd71a..b30cf2db 100644 --- a/src/main/java/com/kamco/cd/kamcoback/code/dto/CommonCodeDto.java +++ b/src/main/java/com/kamco/cd/kamcoback/code/dto/CommonCodeDto.java @@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.kamco.cd.kamcoback.common.utils.interfaces.JsonFormatDttm; import com.kamco.cd.kamcoback.postgres.entity.CommonCodeEntity; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.Valid; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.PositiveOrZero; @@ -53,6 +54,7 @@ public class CommonCodeDto { @NoArgsConstructor @AllArgsConstructor public static class OrderReq { + @Valid List orders; } diff --git a/src/main/java/com/kamco/cd/kamcoback/code/service/CommonCodeService.java b/src/main/java/com/kamco/cd/kamcoback/code/service/CommonCodeService.java index ed779e35..54261d81 100644 --- a/src/main/java/com/kamco/cd/kamcoback/code/service/CommonCodeService.java +++ b/src/main/java/com/kamco/cd/kamcoback/code/service/CommonCodeService.java @@ -1,6 +1,9 @@ package com.kamco.cd.kamcoback.code.service; -import com.kamco.cd.kamcoback.code.dto.CommonCodeDto; +import com.kamco.cd.kamcoback.code.dto.CommonCodeDto.AddReq; +import com.kamco.cd.kamcoback.code.dto.CommonCodeDto.Basic; +import com.kamco.cd.kamcoback.code.dto.CommonCodeDto.ModifyReq; +import com.kamco.cd.kamcoback.code.dto.CommonCodeDto.OrderReq; import com.kamco.cd.kamcoback.postgres.core.CommonCodeCoreService; import java.util.List; import lombok.RequiredArgsConstructor; @@ -17,18 +20,18 @@ public class CommonCodeService { /** * 공통코드 목록 조회 - * @return + * @return 모튼 코드 정보 */ - public List getFindAll() { + public List getFindAll() { return commonCodeCoreService.findAll(); } /** * 공통코드 단건 조회 * @param id - * @return + * @return 코드 아이디로 조회한 코드 정보 */ - public CommonCodeDto.Basic getOneById(Long id) { + public Basic getOneById(Long id) { return commonCodeCoreService.getOneById(id); } @@ -38,7 +41,7 @@ public class CommonCodeService { * @return 생성된 코드 id */ @Transactional - public Long save(CommonCodeDto.AddReq req) { + public Long save(AddReq req) { return commonCodeCoreService.save(req).getId(); } @@ -46,11 +49,10 @@ public class CommonCodeService { * 공통코드 수정 요청 * @param id 코드 아이디 * @param req 수정요청 정보 - * @return */ @Transactional - public Long update(Long id, CommonCodeDto.ModifyReq req) { - return commonCodeCoreService.update(id, req).getId(); + public void update(Long id, ModifyReq req) { + commonCodeCoreService.update(id, req); } /** @@ -64,19 +66,19 @@ public class CommonCodeService { /** * 공통코드 순서 변경 - * @param req + * @param req id, order 정보를 가진 List */ @Transactional - public void updateOrder(CommonCodeDto.OrderReq req) { + public void updateOrder(OrderReq req) { commonCodeCoreService.updateOrder(req); } /** * 코드기반 조회 - * @param code - * @return + * @param code 코드 + * @return 코드로 조회한 공통코드 정보 */ - public List findByCode(String code) { + public List findByCode(String code) { return commonCodeCoreService.findByCode(code); } } diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/core/CommonCodeCoreService.java b/src/main/java/com/kamco/cd/kamcoback/postgres/core/CommonCodeCoreService.java index 59705297..743ce101 100644 --- a/src/main/java/com/kamco/cd/kamcoback/postgres/core/CommonCodeCoreService.java +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/core/CommonCodeCoreService.java @@ -8,7 +8,6 @@ import com.kamco.cd.kamcoback.postgres.repository.CommonCodeRepository; import com.kamco.cd.kamcoback.zoo.dto.AnimalDto.SearchReq; import jakarta.persistence.EntityNotFoundException; import java.util.List; -import java.util.Optional; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.stereotype.Service; From 107bd6b20f312dca5046c89e0f86bfd5a1c37eeb Mon Sep 17 00:00:00 2001 From: "gayoun.park" Date: Thu, 20 Nov 2025 14:36:59 +0900 Subject: [PATCH 8/8] =?UTF-8?q?API=20=EB=A1=9C=EA=B7=B8=EC=A0=80=EC=9E=A5,?= =?UTF-8?q?=20ExceptionHandler=20=EC=A0=80=EC=9E=A5,=20=EA=B0=90=EC=82=AC,?= =?UTF-8?q?=EC=97=90=EB=9F=AC=EB=A1=9C=EA=B7=B8API=20=EC=9E=91=EC=97=85=20?= =?UTF-8?q?=EC=A7=84=ED=96=89=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/GlobalExceptionHandler.java | 163 ++++++- .../cd/kamcoback/config/api/ApiLogFilter.java | 29 ++ .../kamcoback/config/api/ApiLogFunction.java | 111 +++++ .../config/api/ApiResponseAdvice.java | 29 ++ .../kamcoback/config/api/ApiResponseDto.java | 32 +- .../kamcoback/log/AuditLogApiController.java | 127 ++++++ .../kamcoback/log/ErrorLogApiController.java | 47 ++ .../cd/kamcoback/log/dto/AuditLogDto.java | 181 ++++++++ .../cd/kamcoback/log/dto/ErrorLogDto.java | 121 +++++ .../cd/kamcoback/log/dto/EventStatus.java | 20 + .../kamco/cd/kamcoback/log/dto/EventType.java | 25 + .../postgres/core/AuditLogCoreService.java | 58 +++ .../postgres/core/ErrorLogCoreService.java | 38 ++ .../postgres/entity/AuditLogEntity.java | 85 ++++ .../postgres/entity/ErrorLogEntity.java | 50 ++ .../kamcoback/postgres/entity/MenuEntity.java | 53 +++ .../kamcoback/postgres/entity/UserEntity.java | 41 ++ .../repository/log/AuditLogRepository.java | 6 + .../log/AuditLogRepositoryCustom.java | 21 + .../log/AuditLogRepositoryImpl.java | 427 ++++++++++++++++++ .../repository/log/ErrorLogRepository.java | 6 + .../log/ErrorLogRepositoryCustom.java | 12 + .../log/ErrorLogRepositoryImpl.java | 117 +++++ 23 files changed, 1791 insertions(+), 8 deletions(-) create mode 100644 src/main/java/com/kamco/cd/kamcoback/config/api/ApiLogFilter.java create mode 100644 src/main/java/com/kamco/cd/kamcoback/config/api/ApiLogFunction.java create mode 100644 src/main/java/com/kamco/cd/kamcoback/log/AuditLogApiController.java create mode 100644 src/main/java/com/kamco/cd/kamcoback/log/ErrorLogApiController.java create mode 100644 src/main/java/com/kamco/cd/kamcoback/log/dto/AuditLogDto.java create mode 100644 src/main/java/com/kamco/cd/kamcoback/log/dto/ErrorLogDto.java create mode 100644 src/main/java/com/kamco/cd/kamcoback/log/dto/EventStatus.java create mode 100644 src/main/java/com/kamco/cd/kamcoback/log/dto/EventType.java create mode 100644 src/main/java/com/kamco/cd/kamcoback/postgres/core/AuditLogCoreService.java create mode 100644 src/main/java/com/kamco/cd/kamcoback/postgres/core/ErrorLogCoreService.java create mode 100644 src/main/java/com/kamco/cd/kamcoback/postgres/entity/AuditLogEntity.java create mode 100644 src/main/java/com/kamco/cd/kamcoback/postgres/entity/ErrorLogEntity.java create mode 100644 src/main/java/com/kamco/cd/kamcoback/postgres/entity/MenuEntity.java create mode 100644 src/main/java/com/kamco/cd/kamcoback/postgres/entity/UserEntity.java create mode 100644 src/main/java/com/kamco/cd/kamcoback/postgres/repository/log/AuditLogRepository.java create mode 100644 src/main/java/com/kamco/cd/kamcoback/postgres/repository/log/AuditLogRepositoryCustom.java create mode 100644 src/main/java/com/kamco/cd/kamcoback/postgres/repository/log/AuditLogRepositoryImpl.java create mode 100644 src/main/java/com/kamco/cd/kamcoback/postgres/repository/log/ErrorLogRepository.java create mode 100644 src/main/java/com/kamco/cd/kamcoback/postgres/repository/log/ErrorLogRepositoryCustom.java create mode 100644 src/main/java/com/kamco/cd/kamcoback/postgres/repository/log/ErrorLogRepositoryImpl.java diff --git a/src/main/java/com/kamco/cd/kamcoback/config/GlobalExceptionHandler.java b/src/main/java/com/kamco/cd/kamcoback/config/GlobalExceptionHandler.java index b097f56b..b3809687 100644 --- a/src/main/java/com/kamco/cd/kamcoback/config/GlobalExceptionHandler.java +++ b/src/main/java/com/kamco/cd/kamcoback/config/GlobalExceptionHandler.java @@ -1,24 +1,177 @@ package com.kamco.cd.kamcoback.config; +import com.kamco.cd.kamcoback.config.api.ApiLogFunction; import com.kamco.cd.kamcoback.config.api.ApiResponseDto; +import com.kamco.cd.kamcoback.config.api.ApiResponseDto.ApiResponseCode; +import com.kamco.cd.kamcoback.log.dto.ErrorLogDto; +import com.kamco.cd.kamcoback.postgres.entity.ErrorLogEntity; +import com.kamco.cd.kamcoback.postgres.repository.log.ErrorLogRepository; import jakarta.persistence.EntityNotFoundException; +import jakarta.servlet.http.HttpServletRequest; import lombok.extern.slf4j.Slf4j; import org.springframework.core.annotation.Order; +import org.springframework.dao.DataIntegrityViolationException; import org.springframework.http.HttpStatus; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.client.HttpServerErrorException; + +import java.nio.file.AccessDeniedException; +import java.time.ZonedDateTime; +import java.util.Arrays; +import java.util.NoSuchElementException; +import java.util.Optional; +import java.util.stream.Collectors; @Slf4j @Order(value = 1) @RestControllerAdvice public class GlobalExceptionHandler { - // 로그인 정보가 잘못됐습니다 권한 없음 - @org.springframework.web.bind.annotation.ResponseStatus(HttpStatus.NOT_FOUND) + private final ErrorLogRepository errorLogRepository; + + public GlobalExceptionHandler(ErrorLogRepository errorLogRepository) { + this.errorLogRepository = errorLogRepository; + } + + @ResponseStatus(HttpStatus.NOT_FOUND) @ExceptionHandler(EntityNotFoundException.class) - public ApiResponseDto handlerEntityNotFoundException(EntityNotFoundException e) { + public ApiResponseDto handlerEntityNotFoundException(EntityNotFoundException e, HttpServletRequest request) { log.warn("[EntityNotFoundException] resource :{} ", e.getMessage()); - String message = String.format("%s [%s]", e.getMessage(), e.getCause()); - return ApiResponseDto.createException(ApiResponseDto.ApiResponseCode.NOT_FOUND, message); + String codeName = "NOT_FOUND"; + ErrorLogEntity errorLog = saveErrerLogData(request, ApiResponseCode.getCode(codeName), + HttpStatus.valueOf(codeName), ErrorLogDto.LogErrorLevel.ERROR, e.getStackTrace()); + + return ApiResponseDto.createException(ApiResponseCode.getCode(codeName), ApiResponseCode.getMessage(codeName), HttpStatus.valueOf(codeName), errorLog.getId()); + } + + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ExceptionHandler(HttpMessageNotReadableException.class) + public ApiResponseDto handleUnreadable(HttpMessageNotReadableException e, HttpServletRequest request) { + log.warn("[HttpMessageNotReadableException] resource :{} ", e.getMessage()); + String codeName = "BAD_REQUEST"; + ErrorLogEntity errorLog = saveErrerLogData(request, ApiResponseCode.getCode(codeName), + HttpStatus.valueOf(codeName), ErrorLogDto.LogErrorLevel.WARNING, e.getStackTrace()); + + return ApiResponseDto.createException(ApiResponseCode.getCode(codeName), ApiResponseCode.getMessage(codeName), HttpStatus.valueOf(codeName), errorLog.getId()); + } + + @ResponseStatus(HttpStatus.NOT_FOUND) + @ExceptionHandler(NoSuchElementException.class) + public ApiResponseDto handlerNoSuchElementException(NoSuchElementException e, HttpServletRequest request) { + log.warn("[NoSuchElementException] resource :{} ", e.getMessage()); + String codeName = "NOT_FOUND_DATA"; + ErrorLogEntity errorLog = saveErrerLogData(request, ApiResponseCode.getCode(codeName), + HttpStatus.valueOf(codeName), ErrorLogDto.LogErrorLevel.WARNING, e.getStackTrace()); + + return ApiResponseDto.createException(ApiResponseCode.getCode(codeName), ApiResponseCode.getMessage(codeName), HttpStatus.valueOf("NOT_FOUND"), errorLog.getId()); + } + + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ExceptionHandler(IllegalArgumentException.class) + public ApiResponseDto handlerIllegalArgumentException(IllegalArgumentException e, HttpServletRequest request) { + log.warn("[handlerIllegalArgumentException] resource :{} ", e.getMessage()); + String codeName = "BAD_REQUEST"; + ErrorLogEntity errorLog = saveErrerLogData(request, ApiResponseCode.getCode(codeName), + HttpStatus.valueOf(codeName), ErrorLogDto.LogErrorLevel.WARNING, e.getStackTrace()); + + return ApiResponseDto.createException(ApiResponseCode.getCode(codeName), ApiResponseCode.getMessage(codeName), HttpStatus.valueOf(codeName), errorLog.getId()); + } + + @ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY) + @ExceptionHandler(DataIntegrityViolationException.class) + public ApiResponseDto handlerDataIntegrityViolationException(DataIntegrityViolationException e, HttpServletRequest request) { + log.warn("[DataIntegrityViolationException] resource :{} ", e.getMessage()); + String codeName = "DATA_INTEGRITY_ERROR"; + ErrorLogEntity errorLog = saveErrerLogData(request, ApiResponseCode.getCode(codeName), + HttpStatus.valueOf(codeName), ErrorLogDto.LogErrorLevel.CRITICAL, e.getStackTrace()); + + return ApiResponseDto.createException(ApiResponseCode.getCode(codeName), ApiResponseCode.getMessage(codeName), HttpStatus.valueOf("UNPROCESSABLE_ENTITY"), errorLog.getId()); + } + + @ResponseStatus(HttpStatus.BAD_REQUEST) + @ExceptionHandler(MethodArgumentNotValidException.class) + public ApiResponseDto handlerMethodArgumentNotValidException(MethodArgumentNotValidException e, HttpServletRequest request) { + log.warn("[MethodArgumentNotValidException] resource :{} ", e.getMessage()); + String codeName = "BAD_REQUEST"; + ErrorLogEntity errorLog = saveErrerLogData(request, ApiResponseCode.getCode(codeName), + HttpStatus.valueOf(codeName), ErrorLogDto.LogErrorLevel.WARNING, e.getStackTrace()); + + return ApiResponseDto.createException(ApiResponseCode.getCode(codeName), ApiResponseCode.getMessage(codeName), HttpStatus.valueOf(codeName), errorLog.getId()); + } + + @ResponseStatus(HttpStatus.UNAUTHORIZED) + @ExceptionHandler(AccessDeniedException.class) + public ApiResponseDto handlerAccessDeniedException(AccessDeniedException e, HttpServletRequest request) { + log.warn("[AccessDeniedException] resource :{} ", e.getMessage()); + String codeName = "UNAUTHORIZED"; + ErrorLogEntity errorLog = saveErrerLogData(request, ApiResponseCode.getCode(codeName), + HttpStatus.valueOf(codeName), ErrorLogDto.LogErrorLevel.ERROR, e.getStackTrace()); + + return ApiResponseDto.createException(ApiResponseCode.getCode(codeName), ApiResponseCode.getMessage(codeName), HttpStatus.valueOf(codeName), errorLog.getId()); + } + + @ResponseStatus(HttpStatus.BAD_GATEWAY) + @ExceptionHandler(HttpServerErrorException.BadGateway.class) + public ApiResponseDto handlerHttpServerErrorException(HttpServerErrorException e, HttpServletRequest request) { + log.warn("[HttpServerErrorException] resource :{} ", e.getMessage()); + String codeName = "BAD_GATEWAY"; + ErrorLogEntity errorLog = saveErrerLogData(request, ApiResponseCode.getCode(codeName), + HttpStatus.valueOf(codeName), ErrorLogDto.LogErrorLevel.CRITICAL, e.getStackTrace()); + + return ApiResponseDto.createException(ApiResponseCode.getCode(codeName), ApiResponseCode.getMessage(codeName), HttpStatus.valueOf(codeName), errorLog.getId()); + } + + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + @ExceptionHandler(RuntimeException.class) + public ApiResponseDto handlerRuntimeException(RuntimeException e, HttpServletRequest request) { + log.warn("[RuntimeException] resource :{} ", e.getMessage()); + + String codeName = "INTERNAL_SERVER_ERROR"; + ErrorLogEntity errorLog = saveErrerLogData(request, ApiResponseCode.getCode(codeName), + HttpStatus.valueOf(codeName), ErrorLogDto.LogErrorLevel.CRITICAL, e.getStackTrace()); + + return ApiResponseDto.createException(ApiResponseCode.getCode(codeName), ApiResponseCode.getMessage(codeName), HttpStatus.valueOf(codeName), errorLog.getId()); + } + + @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) + @ExceptionHandler(Exception.class) + public ApiResponseDto handlerException(Exception e, HttpServletRequest request) { + log.warn("[Exception] resource :{} ", e.getMessage()); + + String codeName = "INTERNAL_SERVER_ERROR"; + ErrorLogEntity errorLog = saveErrerLogData(request, ApiResponseCode.getCode(codeName), + HttpStatus.valueOf(codeName), ErrorLogDto.LogErrorLevel.CRITICAL, e.getStackTrace()); + + return ApiResponseDto.createException(ApiResponseCode.getCode(codeName), ApiResponseCode.getMessage(codeName), HttpStatus.valueOf(codeName), errorLog.getId()); + } + + /** + * 에러 로그 테이블 저장 로직 + * @param request : request + * @param errorCode : 정의된 enum errorCode + * @param httpStatus : HttpStatus 값 + * @param logErrorLevel : WARNING, ERROR, CRITICAL + * @param stackTrace : 에러 내용 + * @return : insert하고 결과로 받은 Entity + */ + private ErrorLogEntity saveErrerLogData(HttpServletRequest request, ApiResponseCode errorCode, + HttpStatus httpStatus, ErrorLogDto.LogErrorLevel logErrorLevel, StackTraceElement[] stackTrace) { + //TODO : 로그인 개발되면 이것도 연결해야 함 + Long userid = Long.valueOf(Optional.ofNullable(ApiLogFunction.getUserId(request)).orElse("1")); + + //TODO : stackTrace limit 10줄? 확인 필요 + String stackTraceStr = Arrays.stream(stackTrace) + .limit(10) + .map(StackTraceElement::toString) + .collect(Collectors.joining("\n")); + + ErrorLogEntity errorLogEntity = new ErrorLogEntity(request.getRequestURI(), ApiLogFunction.getEventType(request), logErrorLevel, + String.valueOf(httpStatus.value()), errorCode.getText(), stackTraceStr, userid, ZonedDateTime.now()); + + return errorLogRepository.save(errorLogEntity); } } diff --git a/src/main/java/com/kamco/cd/kamcoback/config/api/ApiLogFilter.java b/src/main/java/com/kamco/cd/kamcoback/config/api/ApiLogFilter.java new file mode 100644 index 00000000..f678cfb4 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/config/api/ApiLogFilter.java @@ -0,0 +1,29 @@ +package com.kamco.cd.kamcoback.config.api; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; +import org.springframework.web.util.ContentCachingRequestWrapper; +import org.springframework.web.util.ContentCachingResponseWrapper; + +import java.io.IOException; + +@Component +public class ApiLogFilter extends OncePerRequestFilter { + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + ContentCachingRequestWrapper wrappedRequest = + new ContentCachingRequestWrapper(request); + + ContentCachingResponseWrapper wrappedResponse = + new ContentCachingResponseWrapper(response); + + filterChain.doFilter(wrappedRequest, wrappedResponse); + + // 반드시 response body copy + wrappedResponse.copyBodyToResponse(); + } +} diff --git a/src/main/java/com/kamco/cd/kamcoback/config/api/ApiLogFunction.java b/src/main/java/com/kamco/cd/kamcoback/config/api/ApiLogFunction.java new file mode 100644 index 00000000..4e9581e2 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/config/api/ApiLogFunction.java @@ -0,0 +1,111 @@ +package com.kamco.cd.kamcoback.config.api; + +import com.kamco.cd.kamcoback.log.dto.EventStatus; +import com.kamco.cd.kamcoback.log.dto.EventType; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.web.util.ContentCachingRequestWrapper; + +import java.io.UnsupportedEncodingException; +import java.util.Map; +import java.util.stream.Collectors; + +public class ApiLogFunction { + // 클라이언트 IP 추출 + public static String getClientIp(HttpServletRequest request) { + String[] headers = { + "X-Forwarded-For", + "Proxy-Client-IP", + "WL-Proxy-Client-IP", + "HTTP_CLIENT_IP", + "HTTP_X_FORWARDED_FOR" + }; + for (String header : headers) { + String ip = request.getHeader(header); + if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) { + return ip.split(",")[0]; + } + } + String ip = request.getRemoteAddr(); + if ("0:0:0:0:0:0:0:1".equals(ip)) { //local 일 때 + ip = "127.0.0.1"; + } + return ip; + } + + // 사용자 ID 추출 예시 (Spring Security 기준) + public static String getUserId(HttpServletRequest request) { + try { + return request.getUserPrincipal() != null ? request.getUserPrincipal().getName() : null; + } catch (Exception e) { + return null; + } + } + + public static EventType getEventType(HttpServletRequest request) { + String method = request.getMethod().toUpperCase(); + String uri = request.getRequestURI().toLowerCase(); + + //URL 기반 DOWNLOAD/PRINT 분류 + if(uri.contains("/download") || uri.contains("/export")) return EventType.DOWNLOAD; + if(uri.contains("/print")) return EventType.PRINT; + + // 일반 CRUD + return switch (method) { + case "POST" -> EventType.CREATE; + case "GET" -> EventType.READ; + case "DELETE" -> EventType.DELETE; + case "PUT", "PATCH" -> EventType.UPDATE; + default -> EventType.OTHER; + }; + } + + public static String getRequestBody(HttpServletRequest servletRequest, ContentCachingRequestWrapper contentWrapper) { + StringBuilder resultBody = new StringBuilder(); + // GET, form-urlencoded POST 파라미터 + Map paramMap = servletRequest.getParameterMap(); + + String queryParams = paramMap.entrySet().stream() + .map(e -> e.getKey() + "=" + String.join(",", e.getValue())) + .collect(Collectors.joining("&")); + + resultBody.append(queryParams.isEmpty() ? "" : queryParams); + + // JSON Body + if ("POST".equalsIgnoreCase(servletRequest.getMethod()) + && servletRequest.getContentType() != null + && servletRequest.getContentType().contains("application/json")) { + try { + //json인 경우는 Wrapper를 통해 가져오기 + resultBody.append(getBodyData(contentWrapper)); + + } catch (Exception e) { + resultBody.append("cannot read JSON body ").append(e.toString()); + } + } + + // Multipart form-data + if ("POST".equalsIgnoreCase(servletRequest.getMethod()) + && servletRequest.getContentType() != null + && servletRequest.getContentType().startsWith("multipart/form-data")) { + resultBody.append("multipart/form-data request"); + } + + return resultBody.toString(); + } + + // JSON Body 읽기 + public static String getBodyData(ContentCachingRequestWrapper request) { + byte[] buf = request.getContentAsByteArray(); + if (buf.length == 0) return null; + try { + return new String(buf, request.getCharacterEncoding()); + } catch (UnsupportedEncodingException e) { + return new String(buf); + } + } + + //ApiResponse 의 Status가 2xx 범위이면 SUCCESS, 아니면 FAILED + public static EventStatus isSuccessFail(ApiResponseDto apiResponse){ + return apiResponse.getHttpStatus().is2xxSuccessful() ? EventStatus.SUCCESS : EventStatus.FAILED; + } +} diff --git a/src/main/java/com/kamco/cd/kamcoback/config/api/ApiResponseAdvice.java b/src/main/java/com/kamco/cd/kamcoback/config/api/ApiResponseAdvice.java index eb8cc764..3cc2fdef 100644 --- a/src/main/java/com/kamco/cd/kamcoback/config/api/ApiResponseAdvice.java +++ b/src/main/java/com/kamco/cd/kamcoback/config/api/ApiResponseAdvice.java @@ -1,12 +1,19 @@ package com.kamco.cd.kamcoback.config.api; +import com.kamco.cd.kamcoback.postgres.entity.AuditLogEntity; +import com.kamco.cd.kamcoback.postgres.repository.log.AuditLogRepository; +import jakarta.servlet.http.HttpServletRequest; import org.springframework.core.MethodParameter; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; +import org.springframework.http.server.ServletServerHttpRequest; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; +import org.springframework.web.util.ContentCachingRequestWrapper; + +import java.util.Optional; /** * ApiResponseDto의 내장된 HTTP 상태 코드를 실제 HTTP 응답에 적용하는 Advice @@ -16,6 +23,12 @@ import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; @RestControllerAdvice public class ApiResponseAdvice implements ResponseBodyAdvice { + private final AuditLogRepository auditLogRepository; + + public ApiResponseAdvice(AuditLogRepository auditLogRepository) { + this.auditLogRepository = auditLogRepository; + } + @Override public boolean supports( MethodParameter returnType, Class> converterType) { @@ -32,9 +45,25 @@ public class ApiResponseAdvice implements ResponseBodyAdvice { ServerHttpRequest request, ServerHttpResponse response) { + HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest(); + ContentCachingRequestWrapper contentWrapper = (ContentCachingRequestWrapper) servletRequest; + if (body instanceof ApiResponseDto apiResponse) { // ApiResponseDto에 설정된 httpStatus를 실제 HTTP 응답에 적용 response.setStatusCode(apiResponse.getHttpStatus()); + + String ip = ApiLogFunction.getClientIp(servletRequest); + //TODO : userid 가 계정명인지, uid 인지 확인 후 로직 수정 필요함 + Long userid = Long.valueOf(Optional.ofNullable(ApiLogFunction.getUserId(servletRequest)).orElse("1")); + + //TODO: menuUid 를 동적으로 가져오게끔 해야함 + AuditLogEntity log = new AuditLogEntity(userid, ApiLogFunction.getEventType(servletRequest), + ApiLogFunction.isSuccessFail(apiResponse), "MU_01_01", ip, servletRequest.getRequestURI(), ApiLogFunction.getRequestBody(servletRequest, contentWrapper), + apiResponse.getErrorLogUid() + ); + + //tb_audit_log 테이블 저장 + auditLogRepository.save(log); } return body; diff --git a/src/main/java/com/kamco/cd/kamcoback/config/api/ApiResponseDto.java b/src/main/java/com/kamco/cd/kamcoback/config/api/ApiResponseDto.java index 2146a543..287b9114 100644 --- a/src/main/java/com/kamco/cd/kamcoback/config/api/ApiResponseDto.java +++ b/src/main/java/com/kamco/cd/kamcoback/config/api/ApiResponseDto.java @@ -20,7 +20,9 @@ public class ApiResponseDto { @JsonInclude(JsonInclude.Include.NON_NULL) private T errorData; - @JsonIgnore private HttpStatus httpStatus = HttpStatus.OK; + @JsonIgnore private HttpStatus httpStatus; + + @JsonIgnore private Long errorLogUid; public ApiResponseDto(T data) { this.data = data; @@ -38,6 +40,15 @@ public class ApiResponseDto { public ApiResponseDto(ApiResponseCode code, String message) { this.error = new Error(code.getId(), message); } + public ApiResponseDto(ApiResponseCode code, String message, HttpStatus httpStatus) { + this.error = new Error(code.getId(), message); + this.httpStatus = httpStatus; + } + public ApiResponseDto(ApiResponseCode code, String message, HttpStatus httpStatus, Long errorLogUid) { + this.error = new Error(code.getId(), message); + this.httpStatus = httpStatus; + this.errorLogUid = errorLogUid; + } public ApiResponseDto(ApiResponseCode code, String message, T errorData) { this.error = new Error(code.getId(), message); @@ -64,6 +75,12 @@ public class ApiResponseDto { public static ApiResponseDto createException(ApiResponseCode code, String message) { return new ApiResponseDto<>(code, message); } + public static ApiResponseDto createException(ApiResponseCode code, String message, HttpStatus httpStatus) { + return new ApiResponseDto<>(code, message, httpStatus); + } + public static ApiResponseDto createException(ApiResponseCode code, String message, HttpStatus httpStatus, Long errorLogUid) { + return new ApiResponseDto<>(code, message, httpStatus, errorLogUid); + } public static ApiResponseDto createException( ApiResponseCode code, String message, T data) { @@ -89,6 +106,7 @@ public class ApiResponseDto { // @formatter:off OK("요청이 성공하였습니다."), BAD_REQUEST("요청 파라미터가 잘못되었습니다."), + BAD_GATEWAY("네트워크 상태가 불안정합니다."), ALREADY_EXIST_MALL("이미 등록된 쇼핑센터입니다."), NOT_FOUND_MAP("지도를 찾을 수 없습니다."), UNAUTHORIZED("권한이 없습니다."), @@ -111,8 +129,8 @@ public class ApiResponseDto { REQUIRED_EMAIL("이메일은 필수 항목입니다."), WRONG_PASSWORD("잘못된 패스워드입니다.."), DUPLICATE_EMAIL("이미 가입된 이메일입니다."), - DUPLICATE_DATA("이미 등록되여 있습니다."), - DATA_INTEGRITY_ERROR("요청을 처리할수 없습니다."), + DUPLICATE_DATA("이미 등록되어 있습니다."), + DATA_INTEGRITY_ERROR("데이터 무결성이 위반되어 요청을 처리할수 없습니다."), FOREIGN_KEY_ERROR("참조 중인 데이터가 있어 삭제할 수 없습니다."), DUPLICATE_EMPLOYEEID("이미 가입된 사번입니다."), NOT_FOUND_USER_FOR_EMAIL("이메일로 유저를 찾을 수 없습니다."), @@ -134,5 +152,13 @@ public class ApiResponseDto { public String getText() { return message; } + + public static ApiResponseCode getCode(String name) { + return ApiResponseCode.valueOf(name.toUpperCase()); + } + + public static String getMessage(String name) { + return ApiResponseCode.valueOf(name.toUpperCase()).getText(); + } } } diff --git a/src/main/java/com/kamco/cd/kamcoback/log/AuditLogApiController.java b/src/main/java/com/kamco/cd/kamcoback/log/AuditLogApiController.java new file mode 100644 index 00000000..cbf8c3fe --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/log/AuditLogApiController.java @@ -0,0 +1,127 @@ +package com.kamco.cd.kamcoback.log; + +import com.kamco.cd.kamcoback.config.api.ApiResponseDto; +import com.kamco.cd.kamcoback.log.dto.AuditLogDto; +import com.kamco.cd.kamcoback.postgres.core.AuditLogCoreService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +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 java.time.LocalDate; + +@Tag(name = "감사 로그", description = "감사 로그 관리 API") +@RequiredArgsConstructor +@RestController +@RequestMapping({"/api/log/audit", "/v1/api/log/audit"}) +public class AuditLogApiController { + + private final AuditLogCoreService auditLogCoreService; + + @Operation(summary = "일자별 로그 조회") + @GetMapping("/daily") + public ApiResponseDto> getDailyLogs( + @RequestParam(required = false) LocalDate startDate, + @RequestParam(required = false) LocalDate endDate, + @RequestParam int page, + @RequestParam(defaultValue = "20") int size + ) { + AuditLogDto.DailySearchReq searchReq = new AuditLogDto.DailySearchReq(startDate, endDate, null, page, size, "created_dttm,desc"); + + Page result = auditLogCoreService.getLogByDaily( + searchReq, + startDate, + endDate + ); + + return ApiResponseDto.ok(result); + } + + @Operation(summary = "일자별 로그 상세") + @GetMapping("/daily/result") + public ApiResponseDto> getDailyResultLogs( + @RequestParam LocalDate logDate, + @RequestParam int page, + @RequestParam(defaultValue = "20") int size + ) { + AuditLogDto.DailySearchReq searchReq = new AuditLogDto.DailySearchReq(null, null, logDate, page, size, "created_dttm,desc"); + Page result = auditLogCoreService.getLogByDailyResult( + searchReq, + logDate + ); + + return ApiResponseDto.ok(result); + } + + @Operation(summary = "메뉴별 로그 조회") + + @GetMapping("/menu") + public ApiResponseDto> getMenuLogs( + @RequestParam(required = false) String searchValue, + @RequestParam int page, + @RequestParam(defaultValue = "20") int size + ) { + AuditLogDto.MenuUserSearchReq searchReq = new AuditLogDto.MenuUserSearchReq(searchValue, null, null, page, size, "created_dttm,desc"); + Page result = auditLogCoreService.getLogByMenu( + searchReq, searchValue + ); + + return ApiResponseDto.ok(result); + } + + @Operation(summary = "메뉴별 로그 상세") + + @GetMapping("/menu/result") + public ApiResponseDto> getMenuResultLogs( + @RequestParam String menuId, + @RequestParam int page, + @RequestParam(defaultValue = "20") int size + ) { + AuditLogDto.MenuUserSearchReq searchReq = new AuditLogDto.MenuUserSearchReq(null, menuId, null, page, size, "created_dttm,desc"); + Page result = auditLogCoreService.getLogByMenuResult( + searchReq, + menuId + ); + + return ApiResponseDto.ok(result); + } + + @Operation(summary = "사용자별 로그 조회") + + @GetMapping("/account") + public ApiResponseDto> getAccountLogs( + @RequestParam(required = false) String searchValue, + @RequestParam int page, + @RequestParam(defaultValue = "20") int size + ) { + AuditLogDto.MenuUserSearchReq searchReq = new AuditLogDto.MenuUserSearchReq(searchValue, null, null, page, size, "created_dttm,desc"); + Page result = auditLogCoreService.getLogByAccount( + searchReq, searchValue + ); + + return ApiResponseDto.ok(result); + } + + @Operation(summary = "사용자별 로그 상세") + + @GetMapping("/account/result") + public ApiResponseDto> getAccountResultLogs( + @RequestParam Long userUid, + @RequestParam int page, + @RequestParam(defaultValue = "20") int size + ) { + AuditLogDto.MenuUserSearchReq searchReq = new AuditLogDto.MenuUserSearchReq(null, null, userUid, page, size, "created_dttm,desc"); + Page result = auditLogCoreService.getLogByAccountResult( + searchReq, + userUid + ); + + return ApiResponseDto.ok(result); + } + +} diff --git a/src/main/java/com/kamco/cd/kamcoback/log/ErrorLogApiController.java b/src/main/java/com/kamco/cd/kamcoback/log/ErrorLogApiController.java new file mode 100644 index 00000000..1a7d2973 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/log/ErrorLogApiController.java @@ -0,0 +1,47 @@ +package com.kamco.cd.kamcoback.log; + +import com.kamco.cd.kamcoback.config.api.ApiResponseDto; +import com.kamco.cd.kamcoback.log.dto.AuditLogDto; +import com.kamco.cd.kamcoback.log.dto.ErrorLogDto; +import com.kamco.cd.kamcoback.log.dto.EventType; +import com.kamco.cd.kamcoback.postgres.core.AuditLogCoreService; +import com.kamco.cd.kamcoback.postgres.core.ErrorLogCoreService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +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 java.time.LocalDate; +import java.util.List; + +@Tag(name = "에러 로그", description = "에러 로그 관리 API") +@RequiredArgsConstructor +@RestController +@RequestMapping({"/api/log/error", "/v1/api/log/error"}) +public class ErrorLogApiController { + + private final ErrorLogCoreService errorLogCoreService; + + @Operation(summary = "에러로그 조회") + + @GetMapping("/error") + public ApiResponseDto> getErrorLogs( + @RequestParam(required = false) ErrorLogDto.LogErrorLevel logErrorLevel, + @RequestParam(required = false) EventType eventType, + @RequestParam(required = false) LocalDate startDate, + @RequestParam(required = false) LocalDate endDate, + @RequestParam int page, + @RequestParam(defaultValue = "20") int size + ) { + ErrorLogDto.ErrorSearchReq searchReq = new ErrorLogDto.ErrorSearchReq(logErrorLevel, eventType, startDate, endDate, page, size, "created_dttm,desc"); + Page result = errorLogCoreService.findLogByError(searchReq); + + return ApiResponseDto.ok(result); + } +} diff --git a/src/main/java/com/kamco/cd/kamcoback/log/dto/AuditLogDto.java b/src/main/java/com/kamco/cd/kamcoback/log/dto/AuditLogDto.java new file mode 100644 index 00000000..d4dc30ac --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/log/dto/AuditLogDto.java @@ -0,0 +1,181 @@ +package com.kamco.cd.kamcoback.log.dto; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.kamco.cd.kamcoback.common.utils.interfaces.JsonFormatDttm; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZonedDateTime; + +public class AuditLogDto { + + @Schema(name = "AuditLogBasic", description = "감사로그 기본 정보") + @Getter + public static class Basic { + + @JsonIgnore + private final Long id; + private final Long userUid; + private final EventType eventType; + private final EventStatus eventStatus; + private final String menuUid; + private final String ipAddress; + private final String requestUri; + private final String requestBody; + private final Long errorLogUid; + + @JsonFormatDttm + private final ZonedDateTime createdDttm; + + public Basic( + Long id, + Long userUid, + EventType eventType, + EventStatus eventStatus, + String menuUid, + String ipAddress, + String requestUri, + String requestBody, + Long errorLogUid, + ZonedDateTime createdDttm) { + this.id = id; + this.userUid = userUid; + this.eventType = eventType; + this.eventStatus = eventStatus; + this.menuUid = menuUid; + this.ipAddress = ipAddress; + this.requestUri = requestUri; + this.requestBody = requestBody; + this.errorLogUid = errorLogUid; + this.createdDttm = createdDttm; + } + } + + @Schema(name = "AuditList", description = "감사 로그 목록") + @Getter + @AllArgsConstructor + public static class AuditList { + private int readCount; + private int cudCount; + private int printCount; + private int downloadCount; + private Long totalCount; + + private Long accountId; + private String loginId; + private String username; + private LocalDateTime baseDate; + private Long menuId; + private String menuName; + + public AuditList(LocalDateTime baseDate, int readCount, int cudCount, int printCount, int downloadCount, Long totalCount){ + this.baseDate = baseDate; + this.readCount = readCount; + this.cudCount = cudCount; + this.printCount = printCount; + this.downloadCount = downloadCount; + this.totalCount = totalCount; + } + } + + @Schema(name = "AuditDetail", description = "감사 로그 상세") + @Getter + @AllArgsConstructor + public static class AuditDetail { + private Long logId; + private LocalDateTime logDateTime; + private EventType eventType; + private LogDetail detail; + + private String userName; + private String loginId; + private String menuName; + } + + @Getter + @Setter + @AllArgsConstructor + public static class LogDetail{ + String serviceName; + String parentMenuName; + String menuName; + String menuUrl; + String menuDescription; + int sortOrder; + boolean used; + } + + @Schema(name = "LogDailySearchReq", description = "일자별 로그 검색 요청") + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class DailySearchReq { + + private LocalDate startDate; + private LocalDate endDate; + + // 일자별 로그 검색 조건 + private LocalDate logDate; + + // 페이징 파라미터 + private int page = 0; + private int size = 20; + private String sort; + + public Pageable toPageable() { + if (sort != null && !sort.isEmpty()) { + String[] sortParams = sort.split(","); + String property = sortParams[0]; + Sort.Direction direction = + sortParams.length > 1 ? Sort.Direction.fromString(sortParams[1]) : Sort.Direction.ASC; + return PageRequest.of(page, size, Sort.by(direction, property)); + } + return PageRequest.of(page, size); + } + } + + @Schema(name = "MenuUserSearchReq", description = "메뉴별,사용자별 로그 검색 요청") + @Getter + @Setter + @NoArgsConstructor + public static class MenuUserSearchReq { + + // 메뉴별, 사용자별 로그 검색 조건 + private String searchValue; + private String menuUid; + private Long userUid; //menuId, userUid 조회 + + // 페이징 파라미터 + private int page = 0; + private int size = 20; + private String sort; + + public MenuUserSearchReq(String searchValue, String menuUid, Long userUid, int page, int size, String sort) { + this.searchValue = searchValue; + this.menuUid = menuUid; + this.page = page; + this.size = size; + this.sort = sort; + } + + public Pageable toPageable() { + if (sort != null && !sort.isEmpty()) { + String[] sortParams = sort.split(","); + String property = sortParams[0]; + Sort.Direction direction = + sortParams.length > 1 ? Sort.Direction.fromString(sortParams[1]) : Sort.Direction.ASC; + return PageRequest.of(page, size, Sort.by(direction, property)); + } + return PageRequest.of(page, size); + } + } +} diff --git a/src/main/java/com/kamco/cd/kamcoback/log/dto/ErrorLogDto.java b/src/main/java/com/kamco/cd/kamcoback/log/dto/ErrorLogDto.java new file mode 100644 index 00000000..8b50504e --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/log/dto/ErrorLogDto.java @@ -0,0 +1,121 @@ +package com.kamco.cd.kamcoback.log.dto; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.kamco.cd.kamcoback.common.utils.interfaces.JsonFormatDttm; +import com.kamco.cd.kamcoback.config.enums.EnumType; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZonedDateTime; + +public class ErrorLogDto { + + @Schema(name = "ErrorLogBasic", description = "에러로그 기본 정보") + @Getter + public static class Basic { + + @JsonIgnore + private final Long id; + private final String requestId; + private final EventType errorType; + private final LogErrorLevel errorLevel; + private final String errorCode; + private final String errorMessage; + private final String stackTrace; + private final Long handlerUid; + + @JsonFormatDttm + private final ZonedDateTime handledDttm; + + @JsonFormatDttm + private final ZonedDateTime createdDttm; + + public Basic( + Long id, + String requestId, + EventType errorType, + LogErrorLevel errorLevel, + String errorCode, + String errorMessage, + String stackTrace, + Long handlerUid, + ZonedDateTime handledDttm, + ZonedDateTime createdDttm) { + this.id = id; + this.requestId = requestId; + this.errorType = errorType; + this.errorLevel = errorLevel; + this.errorCode = errorCode; + this.errorMessage = errorMessage; + this.stackTrace = stackTrace; + this.handlerUid = handlerUid; + this.handledDttm = handledDttm; + this.createdDttm = createdDttm; + } + } + + @Schema(name = "ErrorSearchReq", description = "에러로그 검색 요청") + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class ErrorSearchReq { + + LogErrorLevel errorLevel; + EventType eventType; + LocalDate startDate; + LocalDate endDate; + + // 페이징 파라미터 + private int page = 0; + private int size = 20; + private String sort; + + public ErrorSearchReq(LogErrorLevel errorLevel, EventType eventType, LocalDate startDate, LocalDate endDate, int page, int size) { + this.errorLevel = errorLevel; + this.eventType = eventType; + this.startDate = startDate; + this.endDate = endDate; + this.page = page; + this.size = size; + } + + public Pageable toPageable() { + if (sort != null && !sort.isEmpty()) { + String[] sortParams = sort.split(","); + String property = sortParams[0]; + Sort.Direction direction = + sortParams.length > 1 ? Sort.Direction.fromString(sortParams[1]) : Sort.Direction.ASC; + return PageRequest.of(page, size, Sort.by(direction, property)); + } + return PageRequest.of(page, size); + } + } + + public enum LogErrorLevel implements EnumType { + WARNING("Warning"), + ERROR("Error"), + CRITICAL("Critical"); + + private final String desc; + + LogErrorLevel(String desc) { + this.desc = desc; + } + + @Override + public String getId() { return name(); } + + @Override + public String getText() { return desc; } + } + +} diff --git a/src/main/java/com/kamco/cd/kamcoback/log/dto/EventStatus.java b/src/main/java/com/kamco/cd/kamcoback/log/dto/EventStatus.java new file mode 100644 index 00000000..ed50fd47 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/log/dto/EventStatus.java @@ -0,0 +1,20 @@ +package com.kamco.cd.kamcoback.log.dto; + +import com.kamco.cd.kamcoback.config.enums.EnumType; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum EventStatus implements EnumType { + SUCCESS("이벤트 결과 성공"), + FAILED("이벤트 결과 실패"); + + private final String desc; + + @Override + public String getId() { return name(); } + + @Override + public String getText() { return desc; } +} diff --git a/src/main/java/com/kamco/cd/kamcoback/log/dto/EventType.java b/src/main/java/com/kamco/cd/kamcoback/log/dto/EventType.java new file mode 100644 index 00000000..414a3386 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/log/dto/EventType.java @@ -0,0 +1,25 @@ +package com.kamco.cd.kamcoback.log.dto; + +import com.kamco.cd.kamcoback.config.enums.EnumType; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum EventType implements EnumType { + CREATE("생성"), + READ("조회"), + UPDATE("수정"), + DELETE("삭제"), + DOWNLOAD("다운로드"), + PRINT("출력"), + OTHER("기타"); + + private final String desc; + + @Override + public String getId() { return name(); } + + @Override + public String getText() { return desc; } +} diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/core/AuditLogCoreService.java b/src/main/java/com/kamco/cd/kamcoback/postgres/core/AuditLogCoreService.java new file mode 100644 index 00000000..fdcc902f --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/core/AuditLogCoreService.java @@ -0,0 +1,58 @@ +package com.kamco.cd.kamcoback.postgres.core; + +import com.kamco.cd.kamcoback.common.service.BaseCoreService; +import com.kamco.cd.kamcoback.log.dto.AuditLogDto; +import com.kamco.cd.kamcoback.postgres.repository.log.AuditLogRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDate; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class AuditLogCoreService implements BaseCoreService { + + private final AuditLogRepository auditLogRepository; + + @Override + public void remove(Long aLong) { + + } + + @Override + public AuditLogDto.AuditList getOneById(Long aLong) { + return null; + } + + @Override + public Page search(AuditLogDto.DailySearchReq searchReq) { + return null; + } + + public Page getLogByDaily(AuditLogDto.DailySearchReq searchRange, LocalDate startDate, LocalDate endDate) { + return auditLogRepository.findLogByDaily(searchRange, startDate, endDate); + } + + public Page getLogByMenu(AuditLogDto.MenuUserSearchReq searchRange, String searchValue) { + return auditLogRepository.findLogByMenu(searchRange, searchValue); + } + + public Page getLogByAccount(AuditLogDto.MenuUserSearchReq searchRange, String searchValue) { + return auditLogRepository.findLogByAccount(searchRange, searchValue); + } + + public Page getLogByDailyResult(AuditLogDto.DailySearchReq searchRange, LocalDate logDate) { + return auditLogRepository.findLogByDailyResult(searchRange, logDate); + } + + public Page getLogByMenuResult(AuditLogDto.MenuUserSearchReq searchRange, String menuId) { + return auditLogRepository.findLogByMenuResult(searchRange, menuId); + } + + public Page getLogByAccountResult(AuditLogDto.MenuUserSearchReq searchRange, Long accountId) { + return auditLogRepository.findLogByAccountResult(searchRange, accountId); + } +} diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/core/ErrorLogCoreService.java b/src/main/java/com/kamco/cd/kamcoback/postgres/core/ErrorLogCoreService.java new file mode 100644 index 00000000..b833f3d0 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/core/ErrorLogCoreService.java @@ -0,0 +1,38 @@ +package com.kamco.cd.kamcoback.postgres.core; + +import com.kamco.cd.kamcoback.common.service.BaseCoreService; +import com.kamco.cd.kamcoback.log.dto.AuditLogDto; +import com.kamco.cd.kamcoback.log.dto.ErrorLogDto; +import com.kamco.cd.kamcoback.postgres.repository.log.AuditLogRepository; +import com.kamco.cd.kamcoback.postgres.repository.log.ErrorLogRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDate; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class ErrorLogCoreService implements BaseCoreService { + + private final ErrorLogRepository errorLogRepository; + + public Page findLogByError(ErrorLogDto.ErrorSearchReq searchReq) { + return errorLogRepository.findLogByError(searchReq); + } + + @Override + public void remove(Long aLong) {} + + @Override + public ErrorLogDto.Basic getOneById(Long aLong) { + return null; + } + + @Override + public Page search(ErrorLogDto.ErrorSearchReq searchReq) { + return null; + } +} diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/entity/AuditLogEntity.java b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/AuditLogEntity.java new file mode 100644 index 00000000..7570f66d --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/AuditLogEntity.java @@ -0,0 +1,85 @@ +package com.kamco.cd.kamcoback.postgres.entity; + +import com.kamco.cd.kamcoback.log.dto.AuditLogDto; +import com.kamco.cd.kamcoback.log.dto.EventStatus; +import com.kamco.cd.kamcoback.log.dto.EventType; +import com.kamco.cd.kamcoback.postgres.CommonCreateEntity; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Table(name = "tb_audit_log") +public class AuditLogEntity extends CommonCreateEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "audit_log_uid") + private Long id; + + @Column(name = "user_uid") + private Long userUid; + + @Enumerated(EnumType.STRING) + private EventType eventType; + + @Enumerated(EnumType.STRING) + private EventStatus eventStatus; + + @Column(name = "menu_uid") + private String menuUid; + + @Column(name = "ip_address") + private String ipAddress; + + @Column(name = "request_uri") + private String requestUri; + + @Column(name = "request_body") + private String requestBody; + + @Column(name = "error_log_uid") + private Long errorLogUid; + + public AuditLogEntity(Long userUid, EventType eventType, EventStatus eventStatus, String menuUid, String ipAddress, String requestUri, String requestBody, Long errorLogUid) { + this.userUid = userUid; + this.eventType = eventType; + this.eventStatus = eventStatus; + this.menuUid = menuUid; + this.ipAddress = ipAddress; + this.requestUri = requestUri; + this.requestBody = requestBody; + this.errorLogUid = errorLogUid; + } + + public AuditLogDto.Basic toDto() { + return new AuditLogDto.Basic( + this.id, + this.userUid, + this.eventType, + this.eventStatus, + this.menuUid, + this.ipAddress, + this.requestUri, + this.requestBody, + this.errorLogUid, + super.getCreatedDate()); + } + + @Override + public String toString(){ + StringBuilder sb = new StringBuilder(); + sb.append(this.id).append("\n") + .append(this.userUid).append("\n") + .append(this.eventType).append("\n") + .append(this.eventStatus).append("\n") + .append(this.menuUid).append("\n") + .append(this.ipAddress).append("\n") + .append(this.requestUri).append("\n") + .append(this.requestBody).append("\n") + .append(this.errorLogUid); + return sb.toString(); + } +} diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/entity/ErrorLogEntity.java b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/ErrorLogEntity.java new file mode 100644 index 00000000..281b3d0c --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/ErrorLogEntity.java @@ -0,0 +1,50 @@ +package com.kamco.cd.kamcoback.postgres.entity; + +import com.kamco.cd.kamcoback.log.dto.ErrorLogDto; +import com.kamco.cd.kamcoback.log.dto.EventType; +import com.kamco.cd.kamcoback.postgres.CommonCreateEntity; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; +import java.time.ZonedDateTime; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Table(name = "tb_error_log") +public class ErrorLogEntity extends CommonCreateEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "error_log_uid") + private Long id; + + @Column(name = "request_id") + private String requestId; + + @Column(name = "error_type") + @Enumerated(EnumType.STRING) + private EventType errorType; + + @Enumerated(EnumType.STRING) + private ErrorLogDto.LogErrorLevel errorLevel; + private String errorCode; + private String errorMessage; + private String stackTrace; + private Long handlerUid; + private ZonedDateTime handledDttm; + + public ErrorLogEntity(String requestId, EventType errorType, ErrorLogDto.LogErrorLevel errorLevel, String errorCode, String errorMessage, String stackTrace + , Long handlerUid, ZonedDateTime handledDttm) { + this.requestId = requestId; + this.errorType = errorType; + this.errorLevel = errorLevel; + this.errorCode = errorCode; + this.errorMessage = errorMessage; + this.stackTrace = stackTrace; + this.handlerUid = handlerUid; + this.handledDttm = handledDttm; + } +} diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MenuEntity.java b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MenuEntity.java new file mode 100644 index 00000000..bfb65394 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/MenuEntity.java @@ -0,0 +1,53 @@ +package com.kamco.cd.kamcoback.postgres.entity; + +import com.kamco.cd.kamcoback.log.dto.EventStatus; +import com.kamco.cd.kamcoback.log.dto.EventType; +import com.kamco.cd.kamcoback.postgres.CommonDateEntity; +import jakarta.persistence.*; +import jakarta.validation.constraints.NotNull; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Table(name = "tb_menu") +public class MenuEntity extends CommonDateEntity { + @Id + @Column(name = "menu_uid") + private String menuUid; + + @Column(name = "menu_nm") + private String menuNm; + + @Column(name = "menu_url") + private String menuUrl; + + @Column(name = "description") + private String description; + + @Column(name = "menu_order") + private Long menuOrder; + + @NotNull + @Column(name = "is_use", nullable = true) + private Boolean isUse = true; + + @NotNull + @Column(name = "deleted", nullable = false) + private Boolean deleted = false; + + private Long createdUid; + private Long updatedUid; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "parent_menu_uid") + private MenuEntity parent; + + @OneToMany(mappedBy = "parent", fetch = FetchType.LAZY, cascade = CascadeType.ALL) + private List children = new ArrayList<>(); +} diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/entity/UserEntity.java b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/UserEntity.java new file mode 100644 index 00000000..df7631b0 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/entity/UserEntity.java @@ -0,0 +1,41 @@ +package com.kamco.cd.kamcoback.postgres.entity; + +import com.kamco.cd.kamcoback.log.dto.ErrorLogDto; +import com.kamco.cd.kamcoback.log.dto.EventType; +import com.kamco.cd.kamcoback.postgres.CommonDateEntity; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.ZonedDateTime; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Table(name = "tb_user") +public class UserEntity extends CommonDateEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "user_uid") + private Long id; + + @Column(name = "user_nm") + private String userNm; + + @Column(name = "user_id") + private String userId; + + @Column(name = "pswd") + private String pswd; //TODO: 암호화 + + //@Enumerated(EnumType.STRING) + private String state; //TODO: 추후 enum -> ACTIVE : 정상, LOCKED : 잠김, EXPIRED : 만료, WITHDRAWAL : 탈퇴 + + private ZonedDateTime dateWithdrawal; + + private String userEmail; + private Long createdUid; + private Long updatedUid; + +} diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/log/AuditLogRepository.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/log/AuditLogRepository.java new file mode 100644 index 00000000..1bffa354 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/log/AuditLogRepository.java @@ -0,0 +1,6 @@ +package com.kamco.cd.kamcoback.postgres.repository.log; + +import com.kamco.cd.kamcoback.postgres.entity.AuditLogEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface AuditLogRepository extends JpaRepository, AuditLogRepositoryCustom {} diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/log/AuditLogRepositoryCustom.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/log/AuditLogRepositoryCustom.java new file mode 100644 index 00000000..b979ff97 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/log/AuditLogRepositoryCustom.java @@ -0,0 +1,21 @@ +package com.kamco.cd.kamcoback.postgres.repository.log; + +import com.kamco.cd.kamcoback.log.dto.AuditLogDto; +import org.springframework.data.domain.Page; + +import java.time.LocalDate; + +public interface AuditLogRepositoryCustom { + + Page findLogByDaily(AuditLogDto.DailySearchReq searchReq, LocalDate startDate, LocalDate endDate); + + Page findLogByMenu(AuditLogDto.MenuUserSearchReq searchReq, String searchValue); + + Page findLogByAccount(AuditLogDto.MenuUserSearchReq searchReq, String searchValue); + + Page findLogByDailyResult(AuditLogDto.DailySearchReq searchReq, LocalDate logDate); + + Page findLogByMenuResult(AuditLogDto.MenuUserSearchReq searchReq, String menuId); + + Page findLogByAccountResult(AuditLogDto.MenuUserSearchReq searchReq, Long accountId); +} diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/log/AuditLogRepositoryImpl.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/log/AuditLogRepositoryImpl.java new file mode 100644 index 00000000..06f4aa43 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/log/AuditLogRepositoryImpl.java @@ -0,0 +1,427 @@ +package com.kamco.cd.kamcoback.postgres.repository.log; + +import com.kamco.cd.kamcoback.log.dto.AuditLogDto; +import com.kamco.cd.kamcoback.log.dto.ErrorLogDto; +import com.kamco.cd.kamcoback.log.dto.EventStatus; +import com.kamco.cd.kamcoback.log.dto.EventType; +import com.kamco.cd.kamcoback.postgres.entity.AuditLogEntity; +import com.querydsl.core.types.Projections; +import com.querydsl.core.types.dsl.*; +import com.querydsl.jpa.impl.JPAQuery; +import com.querydsl.jpa.impl.JPAQueryFactory; +import io.micrometer.common.util.StringUtils; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZonedDateTime; +import java.util.List; +import java.util.Objects; + +import static com.kamco.cd.kamcoback.postgres.entity.QUserEntity.userEntity; +import static com.kamco.cd.kamcoback.postgres.entity.QAuditLogEntity.auditLogEntity; +import static com.kamco.cd.kamcoback.postgres.entity.QErrorLogEntity.errorLogEntity; +import static com.kamco.cd.kamcoback.postgres.entity.QMenuEntity.menuEntity; +import com.kamco.cd.kamcoback.postgres.entity.QMenuEntity; + +public class AuditLogRepositoryImpl extends QuerydslRepositorySupport implements AuditLogRepositoryCustom { + private final JPAQueryFactory queryFactory; + private final StringExpression NULL_STRING = Expressions.stringTemplate("cast(null as text)"); + + public AuditLogRepositoryImpl(JPAQueryFactory queryFactory) { + super(AuditLogEntity.class); + this.queryFactory = queryFactory; + } + + @Override + public Page findLogByDaily(AuditLogDto.DailySearchReq searchReq, LocalDate startDate, LocalDate endDate) { + DateTimeExpression groupDateTime = + Expressions.dateTimeTemplate(LocalDateTime.class, "date_trunc('day', {0})", auditLogEntity.createdDate); + + Pageable pageable = searchReq.toPageable(); + List foundContent = queryFactory + .select( + Projections.constructor( + AuditLogDto.AuditList.class, + groupDateTime.as("baseDate"), + readCount().as("readCount"), + cudCount().as("cudCount"), + printCount().as("printCount"), + downloadCount().as("downloadCount"), + auditLogEntity.count().as("totalCount") + ) + ) + .from(auditLogEntity) + .where( + eventEndedAtBetween(startDate, endDate) + ) + .groupBy(groupDateTime) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .orderBy(groupDateTime.desc()) + .fetch(); + + Long countQuery = queryFactory + .select(groupDateTime.countDistinct()) + .from(auditLogEntity) + .where( + eventEndedAtBetween(startDate, endDate) + ) + .fetchOne(); + + return new PageImpl<>(foundContent, pageable, countQuery); + } + + @Override + public Page findLogByMenu(AuditLogDto.MenuUserSearchReq searchReq, String searchValue) { + Pageable pageable = searchReq.toPageable(); + List foundContent = queryFactory + .select( + Projections.constructor( + AuditLogDto.AuditList.class, + auditLogEntity.menuUid.as("menuId"), + menuEntity.menuNm.max().as("menuName"), + readCount().as("readCount"), + cudCount().as("cudCount"), + printCount().as("printCount"), + downloadCount().as("downloadCount"), + auditLogEntity.count().as("totalCount") + ) + ) + .from(auditLogEntity) + .leftJoin(menuEntity).on(auditLogEntity.menuUid.eq(menuEntity.menuUid)) + .where( + menuNameEquals(searchValue) + ) + .groupBy(auditLogEntity.menuUid) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .orderBy(auditLogEntity.createdDate.max().desc()) + .fetch(); + + // count query group by 를 지정하면 하나의 row 가 아니라 그룹핑된 여러 row 가 나올 수 있다. + // select query 의 group by 대상의 컬럼을 count query 에선 select distinct 로 처리 한다. + Long countQuery = queryFactory + .select(auditLogEntity.menuUid.countDistinct()) + .from(auditLogEntity) + .leftJoin(menuEntity).on(auditLogEntity.menuUid.eq(menuEntity.menuUid)) + .where(menuNameEquals(searchValue)) + .fetchOne(); + + return new PageImpl<>(foundContent, pageable, countQuery); + } + + @Override + public Page findLogByAccount(AuditLogDto.MenuUserSearchReq searchReq, String searchValue) { + Pageable pageable = searchReq.toPageable(); + List foundContent = queryFactory + .select( + Projections.constructor( + AuditLogDto.AuditList.class, + auditLogEntity.userUid.as("accountId"), + userEntity.userId.as("loginId"), + userEntity.userNm.as("username"), + readCount().as("readCount"), + cudCount().as("cudCount"), + printCount().as("printCount"), + downloadCount().as("downloadCount"), + auditLogEntity.count().as("totalCount") + ) + ) + .from(auditLogEntity) + .leftJoin(userEntity).on(auditLogEntity.userUid.eq(userEntity.id)) + .where( + loginIdOrUsernameContains(searchValue) + ) + .groupBy(auditLogEntity.userUid, userEntity.userId, userEntity.userNm) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) +// .orderBy(auditLogEntity.eventEndedAt.max().desc()) + .fetch(); + + Long countQuery = queryFactory + .select(auditLogEntity.userUid.countDistinct()) + .from(auditLogEntity) + .leftJoin(userEntity).on(auditLogEntity.userUid.eq(userEntity.id)) + .where(loginIdOrUsernameContains(searchValue)) + .fetchOne(); + + return new PageImpl<>(foundContent, pageable, countQuery); + } + + @Override + public Page findLogByDailyResult(AuditLogDto.DailySearchReq searchReq, LocalDate logDate) { + Pageable pageable = searchReq.toPageable(); + QMenuEntity parent = new QMenuEntity("parent"); + // 1depth menu name + StringExpression parentMenuName = + new CaseBuilder() + .when(parent.menuUid.isNull()).then(menuEntity.menuNm) + .otherwise(parent.menuNm); + + // 2depth menu name + StringExpression menuName = + new CaseBuilder() + .when(parent.menuUid.isNull()).then(NULL_STRING) + .otherwise(menuEntity.menuNm); + + List foundContent = queryFactory + .select( + Projections.constructor( + AuditLogDto.AuditDetail.class, + auditLogEntity.id.as("logId"), + userEntity.userNm.as("userName"), + userEntity.userId.as("loginId"), + menuEntity.menuNm.as("menuName"), + auditLogEntity.eventType.as("eventType"), + Projections.constructor( + AuditLogDto.LogDetail.class, + Expressions.constant("한국자산관리공사"), //serviceName + parentMenuName.as("parentMenuName"), + menuName, + menuEntity.menuUrl.as("menuUrl"), + menuEntity.description.as("menuDescription"), + menuEntity.menuOrder.as("sortOrder"), + menuEntity.isUse.as("used") + ) + ) + ) + .from(auditLogEntity) + .leftJoin(menuEntity).on(auditLogEntity.menuUid.eq(menuEntity.menuUid)) + .leftJoin(menuEntity.parent, parent) + .leftJoin(userEntity).on(auditLogEntity.userUid.eq(userEntity.id)) + .where( + eventEndedAtEqDate(logDate) + ) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .orderBy(auditLogEntity.createdDate.desc()) + .fetch(); + + Long countQuery = queryFactory + .select(auditLogEntity.id.countDistinct()) + .from(auditLogEntity) + .leftJoin(menuEntity).on(auditLogEntity.menuUid.eq(menuEntity.menuUid)) + .leftJoin(menuEntity.parent, parent) + .leftJoin(userEntity).on(auditLogEntity.userUid.eq(userEntity.id)) + .where( + eventEndedAtEqDate(logDate) + ) + .fetchOne(); + + return new PageImpl<>(foundContent, pageable, countQuery); + } + + @Override + public Page findLogByMenuResult(AuditLogDto.MenuUserSearchReq searchReq, String menuUid) { + Pageable pageable = searchReq.toPageable(); + QMenuEntity parent = new QMenuEntity("parent"); + // 1depth menu name + StringExpression parentMenuName = + new CaseBuilder() + .when(parent.menuUid.isNull()).then(menuEntity.menuNm) + .otherwise(parent.menuNm); + + // 2depth menu name + StringExpression menuName = + new CaseBuilder() + .when(parent.menuUid.isNull()).then(NULL_STRING) + .otherwise(menuEntity.menuNm); + + List foundContent = queryFactory + .select( + Projections.constructor( + AuditLogDto.AuditDetail.class, + auditLogEntity.id.as("logId"), + auditLogEntity.createdDate.as("logDateTime"), + userEntity.userNm.as("userName"), + userEntity.userId.as("loginId"), + auditLogEntity.eventType.as("eventType"), + Projections.constructor( + AuditLogDto.LogDetail.class, + Expressions.constant("한국자산관리공사"), //serviceName + parentMenuName.as("parentMenuName"), + menuName, + menuEntity.menuUrl.as("menuUrl"), + menuEntity.description.as("menuDescription"), + menuEntity.menuOrder.as("sortOrder"), + menuEntity.isUse.as("used") + ) + ) + ) + .from(auditLogEntity) + .leftJoin(menuEntity).on(auditLogEntity.menuUid.eq(menuEntity.menuUid)) + .leftJoin(menuEntity.parent, parent) + .leftJoin(userEntity).on(auditLogEntity.userUid.eq(userEntity.id)) + .where( + menuUidEq(menuUid) + ) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .orderBy(auditLogEntity.createdDate.desc()) + .fetch(); + + Long countQuery = queryFactory + .select(auditLogEntity.id.countDistinct()) + .from(auditLogEntity) + .leftJoin(menuEntity).on(auditLogEntity.menuUid.eq(menuEntity.menuUid)) + .leftJoin(menuEntity.parent, parent) + .leftJoin(userEntity).on(auditLogEntity.userUid.eq(userEntity.id)) + .where( + menuUidEq(menuUid) + ).fetchOne(); + + return new PageImpl<>(foundContent, pageable, countQuery); + } + + @Override + public Page findLogByAccountResult(AuditLogDto.MenuUserSearchReq searchReq, Long userUid) { + Pageable pageable = searchReq.toPageable(); + QMenuEntity parent = new QMenuEntity("parent"); + // 1depth menu name + StringExpression parentMenuName = + new CaseBuilder() + .when(parent.menuUid.isNull()).then(menuEntity.menuNm) + .otherwise(parent.menuNm); + + // 2depth menu name + StringExpression menuName = + new CaseBuilder() + .when(parent.menuUid.isNull()).then(NULL_STRING) + .otherwise(menuEntity.menuNm); + + List foundContent = queryFactory + .select( + Projections.constructor( + AuditLogDto.AuditDetail.class, + auditLogEntity.id.as("logId"), + auditLogEntity.createdDate.as("logDateTime"), + menuEntity.menuNm.as("menuName"), + auditLogEntity.eventType.as("eventType"), + Projections.constructor( + AuditLogDto.LogDetail.class, + Expressions.constant("한국자산관리공사"), //serviceName + parentMenuName.as("parentMenuName"), + menuName, + menuEntity.menuUrl.as("menuUrl"), + menuEntity.description.as("menuDescription"), + menuEntity.menuOrder.as("sortOrder"), + menuEntity.isUse.as("used") + ) + ) + ) + .from(auditLogEntity) + .leftJoin(menuEntity).on(auditLogEntity.menuUid.eq(menuEntity.menuUid)) + .leftJoin(menuEntity.parent, parent) + .leftJoin(userEntity).on(auditLogEntity.userUid.eq(userEntity.id)) + .where( + userUidEq(userUid) + ) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .orderBy(auditLogEntity.createdDate.desc()) + .fetch(); + + Long countQuery = queryFactory + .select(auditLogEntity.id.countDistinct()) + .from(auditLogEntity) + .leftJoin(menuEntity).on(auditLogEntity.menuUid.eq(menuEntity.menuUid)) + .leftJoin(menuEntity.parent, parent) + .leftJoin(userEntity).on(auditLogEntity.userUid.eq(userEntity.id)) + .where( + userUidEq(userUid) + ) + .fetchOne(); + + return new PageImpl<>(foundContent, pageable, countQuery); + } + + private BooleanExpression eventEndedAtBetween(LocalDate startDate, LocalDate endDate) { + if (Objects.isNull(startDate) || Objects.isNull(endDate)) { + return null; + } + LocalDateTime startDateTime = startDate.atStartOfDay(); + LocalDateTime endDateTime = endDate.plusDays(1).atStartOfDay(); + return auditLogEntity.createdDate.goe(ZonedDateTime.from(startDateTime)) + .and(auditLogEntity.createdDate.lt(ZonedDateTime.from(endDateTime))); + } + + private BooleanExpression menuNameEquals(String searchValue) { + if (StringUtils.isBlank(searchValue)) { + return null; + } + return menuEntity.menuNm.contains(searchValue); + } + + private BooleanExpression loginIdOrUsernameContains(String searchValue) { + if (StringUtils.isBlank(searchValue)) { + return null; + } + return userEntity.userId.contains(searchValue).or(userEntity.userNm.contains(searchValue)); + } + + private BooleanExpression eventStatusEqFailed() { + return auditLogEntity.eventStatus.eq(EventStatus.FAILED); + } + + private BooleanExpression eventTypeEq(EventType eventType) { + if (Objects.isNull(eventType)) { + return null; + } + return auditLogEntity.eventType.eq(eventType); + } + + private BooleanExpression errorLevelEq(ErrorLogDto.LogErrorLevel level) { + if (Objects.isNull(level)) { + return null; + } + return errorLogEntity.errorLevel.eq(ErrorLogDto.LogErrorLevel.valueOf(level.name())); + } + + private BooleanExpression eventEndedAtEqDate(LocalDate logDate) { + DateTimeExpression eventEndedDate = + Expressions.dateTimeTemplate(LocalDateTime.class, "date_trunc('day', {0})", auditLogEntity.createdDate); + LocalDateTime comparisonDate = logDate.atStartOfDay(); + + return eventEndedDate.eq(comparisonDate); + } + + private BooleanExpression menuUidEq(String menuUid) { + return auditLogEntity.menuUid.eq(menuUid); + } + + private BooleanExpression userUidEq(Long userUid) { + return auditLogEntity.userUid.eq(userUid); + } + + private NumberExpression readCount() { + return new CaseBuilder() + .when(auditLogEntity.eventType.eq(EventType.READ)).then(1) + .otherwise(0) + .sum(); + } + + private NumberExpression cudCount() { + return new CaseBuilder() + .when(auditLogEntity.eventType.in(EventType.CREATE, EventType.UPDATE, EventType.DELETE)).then(1) + .otherwise(0) + .sum(); + } + + private NumberExpression printCount() { + return new CaseBuilder() + .when(auditLogEntity.eventType.eq(EventType.PRINT)).then(1) + .otherwise(0) + .sum(); + } + + private NumberExpression downloadCount() { + return new CaseBuilder() + .when(auditLogEntity.eventType.eq(EventType.DOWNLOAD)).then(1) + .otherwise(0) + .sum(); + } +} diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/log/ErrorLogRepository.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/log/ErrorLogRepository.java new file mode 100644 index 00000000..b617f5dd --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/log/ErrorLogRepository.java @@ -0,0 +1,6 @@ +package com.kamco.cd.kamcoback.postgres.repository.log; + +import com.kamco.cd.kamcoback.postgres.entity.ErrorLogEntity; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ErrorLogRepository extends JpaRepository, ErrorLogRepositoryCustom {} diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/log/ErrorLogRepositoryCustom.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/log/ErrorLogRepositoryCustom.java new file mode 100644 index 00000000..0aca6186 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/log/ErrorLogRepositoryCustom.java @@ -0,0 +1,12 @@ +package com.kamco.cd.kamcoback.postgres.repository.log; + +import com.kamco.cd.kamcoback.log.dto.AuditLogDto; +import com.kamco.cd.kamcoback.log.dto.ErrorLogDto; +import com.kamco.cd.kamcoback.postgres.entity.AuditLogEntity; +import org.springframework.data.domain.Page; + +import java.time.LocalDate; + +public interface ErrorLogRepositoryCustom { + public Page findLogByError(ErrorLogDto.ErrorSearchReq searchReq); +} diff --git a/src/main/java/com/kamco/cd/kamcoback/postgres/repository/log/ErrorLogRepositoryImpl.java b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/log/ErrorLogRepositoryImpl.java new file mode 100644 index 00000000..35054b63 --- /dev/null +++ b/src/main/java/com/kamco/cd/kamcoback/postgres/repository/log/ErrorLogRepositoryImpl.java @@ -0,0 +1,117 @@ +package com.kamco.cd.kamcoback.postgres.repository.log; + +import com.kamco.cd.kamcoback.log.dto.ErrorLogDto; +import com.kamco.cd.kamcoback.log.dto.EventStatus; +import com.kamco.cd.kamcoback.log.dto.EventType; +import com.kamco.cd.kamcoback.postgres.entity.AuditLogEntity; +import com.querydsl.core.types.Projections; +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.core.types.dsl.Expressions; +import com.querydsl.core.types.dsl.StringExpression; +import com.querydsl.jpa.impl.JPAQueryFactory; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZonedDateTime; +import java.util.List; +import java.util.Objects; + +import static com.kamco.cd.kamcoback.postgres.entity.QAuditLogEntity.auditLogEntity; +import static com.kamco.cd.kamcoback.postgres.entity.QErrorLogEntity.errorLogEntity; +import static com.kamco.cd.kamcoback.postgres.entity.QMenuEntity.menuEntity; +import static com.kamco.cd.kamcoback.postgres.entity.QUserEntity.userEntity; + +public class ErrorLogRepositoryImpl extends QuerydslRepositorySupport implements ErrorLogRepositoryCustom { + private final JPAQueryFactory queryFactory; + private final StringExpression NULL_STRING = Expressions.stringTemplate("cast(null as text)"); + + public ErrorLogRepositoryImpl(JPAQueryFactory queryFactory) { + super(AuditLogEntity.class); + this.queryFactory = queryFactory; + } + + @Override + public Page findLogByError(ErrorLogDto.ErrorSearchReq searchReq) { + Pageable pageable = searchReq.toPageable(); + List foundContent = queryFactory + .select( + Projections.constructor( + ErrorLogDto.Basic.class, + errorLogEntity.id.as("logId"), + Expressions.constant("한국자산관리공사"), //serviceName + menuEntity.menuNm.as("menuName"), + userEntity.userId.as("loginId"), + userEntity.userNm.as("userName"), + errorLogEntity.errorType.as("eventType"), + errorLogEntity.errorMessage.as("errorName"), // 기존에는 errorName 값이 있었는데 신규 테이블에는 없음. 에러 메세지와 동일 + errorLogEntity.errorLevel.as("errorLevel"), + errorLogEntity.errorCode.as("errorCode"), + errorLogEntity.errorMessage.as("errorMessage"), + errorLogEntity.stackTrace.as("errorDetail"), + errorLogEntity.createdDate + ) + ) + .from(errorLogEntity) + .leftJoin(auditLogEntity).on(errorLogEntity.id.eq(auditLogEntity.errorLogUid)) + .leftJoin(menuEntity).on(auditLogEntity.menuUid.eq(menuEntity.menuUid)) + .leftJoin(userEntity).on(errorLogEntity.handlerUid.eq(userEntity.id)) + .where( + eventStatusEqFailed(), + eventEndedAtBetween(searchReq.getStartDate(), searchReq.getEndDate()), + eventTypeEq(searchReq.getEventType()), + errorLevelEq(searchReq.getErrorLevel()) + ) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .orderBy(errorLogEntity.createdDate.desc()) + .fetch(); + + Long countQuery = queryFactory + .select(errorLogEntity.id.countDistinct()) + .from(errorLogEntity) + .leftJoin(auditLogEntity).on(errorLogEntity.id.eq(auditLogEntity.errorLogUid)) + .leftJoin(menuEntity).on(auditLogEntity.menuUid.eq(menuEntity.menuUid)) + .leftJoin(userEntity).on(errorLogEntity.handlerUid.eq(userEntity.id)) + .where( + eventStatusEqFailed(), + eventEndedAtBetween(searchReq.getStartDate(), searchReq.getEndDate()), + eventTypeEq(searchReq.getEventType()), + errorLevelEq(searchReq.getErrorLevel()) + ) + .fetchOne(); + + return new PageImpl<>(foundContent, pageable, countQuery); + } + + private BooleanExpression eventEndedAtBetween(LocalDate startDate, LocalDate endDate) { + if (Objects.isNull(startDate) || Objects.isNull(endDate)) { + return null; + } + LocalDateTime startDateTime = startDate.atStartOfDay(); + LocalDateTime endDateTime = endDate.plusDays(1).atStartOfDay(); + return auditLogEntity.createdDate.goe(ZonedDateTime.from(startDateTime)) + .and(auditLogEntity.createdDate.lt(ZonedDateTime.from(endDateTime))); + } + + private BooleanExpression eventStatusEqFailed() { + return auditLogEntity.eventStatus.eq(EventStatus.FAILED); + } + + private BooleanExpression eventTypeEq(EventType eventType) { + if (Objects.isNull(eventType)) { + return null; + } + return auditLogEntity.eventType.eq(eventType); + } + + private BooleanExpression errorLevelEq(ErrorLogDto.LogErrorLevel level) { + if (Objects.isNull(level)) { + return null; + } + return errorLogEntity.errorLevel.eq(ErrorLogDto.LogErrorLevel.valueOf(level.name())); + } +}