diff --git a/.github/workflows/cicd-dev.yml b/.github/workflows/cicd-dev.yml index bec9e75b..2e6cf805 100644 --- a/.github/workflows/cicd-dev.yml +++ b/.github/workflows/cicd-dev.yml @@ -23,10 +23,10 @@ jobs: - name: Replace SQL Queries run: | - file_path="${{ secrets.TARGET_SQL_PATH }}" + dir_path="./module-api/src/main/resources/db/migration" old_text="changeRequired" new_text="${{ secrets.PUBLIC_KEY }}" - sed -i "s/$old_text/$new_text/g" $file_path + find "$dir_path" -type f -name "*.sql" -exec sed -i "s|$old_text|$new_text|g" {} + - name: Set application yml file (api) uses: microsoft/variable-substitution@v1 @@ -65,7 +65,7 @@ jobs: with: files: ./module-batch/src/main/resources/application-dev.yml env: - spring.datasource.url: ${{ secrets.DB_URL }} + spring.datasource.url: ${{ secrets.VULTR_DB_URL }} spring.datasource.username: ${{ secrets.DB_USER }} spring.datasource.password: ${{ secrets.DB_PW }} external.ecolife-api.path: ${{ secrets.ECOLIFE_PATH }} diff --git a/.github/workflows/cicd-prod.yml b/.github/workflows/cicd-prod.yml index b5381194..09300cda 100644 --- a/.github/workflows/cicd-prod.yml +++ b/.github/workflows/cicd-prod.yml @@ -23,10 +23,10 @@ jobs: - name: Replace SQL Queries run: | - file_path="${{ secrets.TARGET_SQL_PATH }}" + dir_path="./module-api/src/main/resources/db/migration" old_text="changeRequired" new_text="${{ secrets.PUBLIC_KEY }}" - sed -i "s/$old_text/$new_text/g" $file_path + find "$dir_path" -type f -name "*.sql" -exec sed -i "s|$old_text|$new_text|g" {} + - name: Set application yml file (api) uses: microsoft/variable-substitution@v1 diff --git a/module-admin/src/main/java/com/kernel360/member/code/MemberBusinessCode.java b/module-admin/src/main/java/com/kernel360/member/code/MemberBusinessCode.java new file mode 100644 index 00000000..10cf42c2 --- /dev/null +++ b/module-admin/src/main/java/com/kernel360/member/code/MemberBusinessCode.java @@ -0,0 +1,30 @@ +package com.kernel360.member.code; + +import com.kernel360.code.BusinessCode; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@RequiredArgsConstructor +public enum MemberBusinessCode implements BusinessCode { + + SUCCESS_REQUEST_ALL_MEMBER_LIST(HttpStatus.OK.value(), "BMC001", "전체회원목록 조회 성공"); + + private final int status; + private final String code; + private final String message; + + @Override + public int getStatus() { + return status; + } + + @Override + public String getCode() { + return code; + } + + @Override + public String getMessage() { + return message; + } +} diff --git a/module-admin/src/main/java/com/kernel360/member/controller/MemberController.java b/module-admin/src/main/java/com/kernel360/member/controller/MemberController.java new file mode 100644 index 00000000..63b837de --- /dev/null +++ b/module-admin/src/main/java/com/kernel360/member/controller/MemberController.java @@ -0,0 +1,26 @@ +package com.kernel360.member.controller; + +import com.kernel360.member.code.MemberBusinessCode; +import com.kernel360.member.dto.MemberResponse; +import com.kernel360.member.service.MemberService; +import com.kernel360.response.ApiResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/admin") +public class MemberController { + + private final MemberService memberService; + + @GetMapping("members") + public ResponseEntity>> getMemberList(Pageable pageable) { + Page members = memberService.getAllMembers(pageable); + + return ApiResponse.toResponseEntity(MemberBusinessCode.SUCCESS_REQUEST_ALL_MEMBER_LIST, members); + } +} diff --git a/module-admin/src/main/java/com/kernel360/member/dto/MemberDto.java b/module-admin/src/main/java/com/kernel360/member/dto/MemberDto.java new file mode 100644 index 00000000..f003ca58 --- /dev/null +++ b/module-admin/src/main/java/com/kernel360/member/dto/MemberDto.java @@ -0,0 +1,46 @@ +package com.kernel360.member.dto; + + +import java.time.LocalDateTime; + + +public record MemberDto(Long memberNo, + String id, + String email, + String password, + String gender, + String age, + LocalDateTime createdAt, + String createdBy, + LocalDateTime modifiedAt, + String modifiedBy +) { + + public static MemberDto of( + Long memberNo, + String id, + String email, + String password, + String gender, + String age, + LocalDateTime createdAt, + String createdBy, + LocalDateTime modifiedAt, + String modifiedBy + ) { + return new MemberDto( + memberNo, + id, + email, + password, + gender, + age, + createdAt, + createdBy, + modifiedAt, + modifiedBy + ); + } + + +} \ No newline at end of file diff --git a/module-admin/src/main/java/com/kernel360/member/dto/MemberResponse.java b/module-admin/src/main/java/com/kernel360/member/dto/MemberResponse.java new file mode 100644 index 00000000..3eba9363 --- /dev/null +++ b/module-admin/src/main/java/com/kernel360/member/dto/MemberResponse.java @@ -0,0 +1,38 @@ +package com.kernel360.member.dto; + + +import com.kernel360.carinfo.entity.CarInfo; +import com.kernel360.washinfo.entity.WashInfo; +import com.querydsl.core.annotations.QueryProjection; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Getter +@NoArgsConstructor +public class MemberResponse { + private Long memberNo; + private String id; + private String email; + private int gender; + private int age; + private LocalDateTime registerDate; + private String accountType; + private WashInfo washInfo; + private CarInfo carInfo; + @QueryProjection + public MemberResponse(Long memberNo, String id, String email, int gender, int age, LocalDateTime registerDate, + String accountType, WashInfo washInfo, CarInfo carInfo) { + this.memberNo = memberNo; + this.id = id; + this.email = email; + this.gender = gender; + this.age = age; + this.registerDate = registerDate; + this.accountType = accountType; + this.washInfo = washInfo; + this.carInfo = carInfo; + } + +} diff --git a/module-admin/src/main/java/com/kernel360/member/dto/MemberSearchDto.java b/module-admin/src/main/java/com/kernel360/member/dto/MemberSearchDto.java new file mode 100644 index 00000000..5c3c78d5 --- /dev/null +++ b/module-admin/src/main/java/com/kernel360/member/dto/MemberSearchDto.java @@ -0,0 +1,19 @@ +package com.kernel360.member.dto; + +import com.kernel360.carinfo.entity.CarInfo; +import com.kernel360.washinfo.entity.WashInfo; +import lombok.Builder; + +import java.time.LocalDateTime; + +@Builder +public record MemberSearchDto( + String id, + String name, + String email, + String age, + LocalDateTime registerDate, + WashInfo washInfo, + CarInfo carInfo +) { +} diff --git a/module-admin/src/main/java/com/kernel360/member/enumset/Age.java b/module-admin/src/main/java/com/kernel360/member/enumset/Age.java new file mode 100644 index 00000000..8fc70e47 --- /dev/null +++ b/module-admin/src/main/java/com/kernel360/member/enumset/Age.java @@ -0,0 +1,16 @@ +package com.kernel360.member.enumset; + +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public enum Age { + AGE_20(20), + AGE_30(30), + AGE_40(40), + AGE_50(50), + AGE_60(60), + AGE_99(99); + + private final int value; + +} diff --git a/module-admin/src/main/java/com/kernel360/member/enumset/Gender.java b/module-admin/src/main/java/com/kernel360/member/enumset/Gender.java new file mode 100644 index 00000000..050ffa4e --- /dev/null +++ b/module-admin/src/main/java/com/kernel360/member/enumset/Gender.java @@ -0,0 +1,13 @@ +package com.kernel360.member.enumset; + +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public enum Gender { + MALE(0), + FEMALE(1), + OTHERS(99); + + private final int value; + +} diff --git a/module-admin/src/main/java/com/kernel360/member/enumset/Sort.java b/module-admin/src/main/java/com/kernel360/member/enumset/Sort.java new file mode 100644 index 00000000..7538f52f --- /dev/null +++ b/module-admin/src/main/java/com/kernel360/member/enumset/Sort.java @@ -0,0 +1,23 @@ +package com.kernel360.member.enumset; + +public enum Sort { + ID_ORDER("id-order"), + GENDER_ORDER("gender-order"), + AGE_ORDER("age-order"), + REGISTER_ORDER("register-order"), + RECENT_PRODUCT_ORDER("recent-order"); + + + private final String orderType; + + Sort(String orderType) { + this.orderType = orderType; + } + + public String getOrderType() { + return orderType; + } +} + + + diff --git a/module-admin/src/main/java/com/kernel360/member/repository/MemberRepository.java b/module-admin/src/main/java/com/kernel360/member/repository/MemberRepository.java new file mode 100644 index 00000000..b6b56a89 --- /dev/null +++ b/module-admin/src/main/java/com/kernel360/member/repository/MemberRepository.java @@ -0,0 +1,5 @@ +package com.kernel360.member.repository; + + +public interface MemberRepository extends MemberRepositoryJpa, MemberRepositoryDsl { +} diff --git a/module-admin/src/main/java/com/kernel360/member/repository/MemberRepositoryDsl.java b/module-admin/src/main/java/com/kernel360/member/repository/MemberRepositoryDsl.java new file mode 100644 index 00000000..b0bdb1e3 --- /dev/null +++ b/module-admin/src/main/java/com/kernel360/member/repository/MemberRepositoryDsl.java @@ -0,0 +1,10 @@ +package com.kernel360.member.repository; + +import com.kernel360.member.dto.MemberResponse; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +public interface MemberRepositoryDsl { + Page findAllMember(Pageable pageable); + +} diff --git a/module-admin/src/main/java/com/kernel360/member/repository/MemberRepositoryImpl.java b/module-admin/src/main/java/com/kernel360/member/repository/MemberRepositoryImpl.java new file mode 100644 index 00000000..3618b67d --- /dev/null +++ b/module-admin/src/main/java/com/kernel360/member/repository/MemberRepositoryImpl.java @@ -0,0 +1,59 @@ +package com.kernel360.member.repository; + + +import com.kernel360.member.dto.MemberResponse; +import com.kernel360.member.dto.QMemberResponse; + +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.jpa.impl.JPAQueryFactory; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.support.PageableExecutionUtils; + +import java.util.List; + +import static com.kernel360.carinfo.entity.QCarInfo.*; +import static com.kernel360.member.entity.QMember.*; +import static com.kernel360.washinfo.entity.QWashInfo.*; + + +@RequiredArgsConstructor +public class MemberRepositoryImpl implements MemberRepositoryDsl { + + private final JPAQueryFactory query; + + @Override + public Page findAllMember(Pageable pageable) { + List members = query.select(new QMemberResponse( + member.memberNo, + member.id, + member.email, + member.gender, + member.age, + member.createdAt, + member.accountType, + washInfo, + carInfo + )) + .from(member) + .leftJoin(member.washInfo, washInfo).on(isWashInfoNotNull()) + .leftJoin(member.carInfo, carInfo).on(isCarInfoNotNull()) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .orderBy(member.memberNo.desc()) + .fetch(); + + return PageableExecutionUtils.getPage(members, pageable, members::size); + } + + private static BooleanExpression isCarInfoNotNull() { + return member.carInfo.carNo.isNotNull(); + } + + private static BooleanExpression isWashInfoNotNull() { + return member.washInfo.washNo.isNotNull(); + } + + +} diff --git a/module-admin/src/main/java/com/kernel360/member/service/MemberService.java b/module-admin/src/main/java/com/kernel360/member/service/MemberService.java new file mode 100644 index 00000000..74d872a5 --- /dev/null +++ b/module-admin/src/main/java/com/kernel360/member/service/MemberService.java @@ -0,0 +1,23 @@ +package com.kernel360.member.service; + +import com.kernel360.member.dto.MemberResponse; +import com.kernel360.member.repository.MemberRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; + + +@Slf4j +@Service +@RequiredArgsConstructor +public class MemberService { + + private final MemberRepository memberRepository; + + public Page getAllMembers(Pageable pageable) { + + return memberRepository.findAllMember(pageable); + } +} diff --git a/module-admin/src/main/java/com/kernel360/product/dto/ProductDetailDto.java b/module-admin/src/main/java/com/kernel360/product/dto/ProductDetailDto.java index 1a2c1bd2..b01d036f 100644 --- a/module-admin/src/main/java/com/kernel360/product/dto/ProductDetailDto.java +++ b/module-admin/src/main/java/com/kernel360/product/dto/ProductDetailDto.java @@ -3,6 +3,7 @@ import com.kernel360.product.entity.Product; import java.time.LocalDate; +import java.time.LocalDateTime; /** * DTO for {@link com.kernel360.product.entity.Product} @@ -36,9 +37,9 @@ public record ProductDetailDto( String manufactureMethod, String manufactureNation, String violationInfo, - LocalDate createdAt, + LocalDateTime createdAt, String createdBy, - LocalDate modifiedAt, + LocalDateTime modifiedAt, String modifiedBy //TODO 브랜드 엔티티 ) { @@ -72,9 +73,9 @@ public static ProductDetailDto of( String manufactureMethod, String manufactureNation, String violationInfo, - LocalDate createdAt, + LocalDateTime createdAt, String createdBy, - LocalDate modifiedAt, + LocalDateTime modifiedAt, String modifiedBy ) { return new ProductDetailDto( diff --git a/module-admin/src/main/java/com/kernel360/product/dto/ProductDto.java b/module-admin/src/main/java/com/kernel360/product/dto/ProductDto.java index 3ae97ccc..e264b18c 100644 --- a/module-admin/src/main/java/com/kernel360/product/dto/ProductDto.java +++ b/module-admin/src/main/java/com/kernel360/product/dto/ProductDto.java @@ -1,9 +1,10 @@ package com.kernel360.product.dto; import com.kernel360.product.entity.Product; -import com.kernel360.product.entity.SafetyStatus; +import com.kernel360.product.enumset.SafetyStatus; import java.time.LocalDate; +import java.time.LocalDateTime; /** * DTO for {@link com.kernel360.product.entity.Product} @@ -18,9 +19,9 @@ public record ProductDto( Integer viewCount, String brand, String upperItem, - LocalDate createdAt, + LocalDateTime createdAt, String createdBy, - LocalDate modifiedAt, + LocalDateTime modifiedAt, String modifiedBy ) { @@ -34,9 +35,9 @@ public static ProductDto of( String brand, String upperItem, Integer viewCount, - LocalDate createdAt, + LocalDateTime createdAt, String createdBy, - LocalDate modifiedAt, + LocalDateTime modifiedAt, String modifiedBy ) { return new ProductDto( @@ -73,11 +74,4 @@ public static ProductDto from(Product entity) { entity.getModifiedBy() ); } - - public Product toEntity() { - return Product.of( - productNo, - productName - ); - } } diff --git a/module-admin/src/main/java/com/kernel360/product/service/ProductService.java b/module-admin/src/main/java/com/kernel360/product/service/ProductService.java index 85128a2f..85c214d2 100644 --- a/module-admin/src/main/java/com/kernel360/product/service/ProductService.java +++ b/module-admin/src/main/java/com/kernel360/product/service/ProductService.java @@ -8,7 +8,7 @@ import com.kernel360.product.dto.ProductUpdateRequest; import com.kernel360.product.dto.RecommendProductsDto; import com.kernel360.product.entity.Product; -import com.kernel360.product.entity.SafetyStatus; +import com.kernel360.product.enumset.SafetyStatus; import com.kernel360.product.repository.ProductRepositoryJpa; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; diff --git a/module-admin/src/main/java/com/kernel360/review/dto/AdminReviewDto.java b/module-admin/src/main/java/com/kernel360/review/dto/AdminReviewDto.java index da76a2e9..eddee705 100644 --- a/module-admin/src/main/java/com/kernel360/review/dto/AdminReviewDto.java +++ b/module-admin/src/main/java/com/kernel360/review/dto/AdminReviewDto.java @@ -3,16 +3,16 @@ import com.kernel360.product.dto.ProductDto; import com.kernel360.review.entity.Review; import java.math.BigDecimal; -import java.time.LocalDate; +import java.time.LocalDateTime; public record AdminReviewDto(Long reviewNo, ProductDto productDto, BigDecimal starRating, String title, String contents, - LocalDate createdAt, + LocalDateTime createdAt, String createdBy, - LocalDate modifiedAt, + LocalDateTime modifiedAt, String modifiedBy) { public static AdminReviewDto of( @@ -21,9 +21,9 @@ public static AdminReviewDto of( BigDecimal starRating, String title, String contents, - LocalDate createdAt, + LocalDateTime createdAt, String createdBy, - LocalDate modifiedAt, + LocalDateTime modifiedAt, String modifiedBy ) { return new AdminReviewDto( diff --git a/module-admin/src/main/java/com/kernel360/review/repository/ReviewRepository.java b/module-admin/src/main/java/com/kernel360/review/repository/ReviewRepository.java new file mode 100644 index 00000000..619f004f --- /dev/null +++ b/module-admin/src/main/java/com/kernel360/review/repository/ReviewRepository.java @@ -0,0 +1,4 @@ +package com.kernel360.review.repository; + +public interface ReviewRepository extends ReviewRepositoryJpa, ReviewRepositoryDsl{ +} diff --git a/module-admin/src/main/java/com/kernel360/review/respository/ReviewRepositoryDsl.java b/module-admin/src/main/java/com/kernel360/review/repository/ReviewRepositoryDsl.java similarity index 89% rename from module-admin/src/main/java/com/kernel360/review/respository/ReviewRepositoryDsl.java rename to module-admin/src/main/java/com/kernel360/review/repository/ReviewRepositoryDsl.java index 55c9e501..6480742b 100644 --- a/module-admin/src/main/java/com/kernel360/review/respository/ReviewRepositoryDsl.java +++ b/module-admin/src/main/java/com/kernel360/review/repository/ReviewRepositoryDsl.java @@ -1,4 +1,4 @@ -package com.kernel360.review.respository; +package com.kernel360.review.repository; import com.kernel360.review.dto.ReviewSearchDto; import com.kernel360.review.entity.Review; diff --git a/module-admin/src/main/java/com/kernel360/review/respository/ReviewRepositoryImpl.java b/module-admin/src/main/java/com/kernel360/review/repository/ReviewRepositoryImpl.java similarity index 97% rename from module-admin/src/main/java/com/kernel360/review/respository/ReviewRepositoryImpl.java rename to module-admin/src/main/java/com/kernel360/review/repository/ReviewRepositoryImpl.java index 6885b68c..5aee21d1 100644 --- a/module-admin/src/main/java/com/kernel360/review/respository/ReviewRepositoryImpl.java +++ b/module-admin/src/main/java/com/kernel360/review/repository/ReviewRepositoryImpl.java @@ -1,4 +1,4 @@ -package com.kernel360.review.respository; +package com.kernel360.review.repository; import static com.kernel360.review.entity.QReview.review; @@ -9,7 +9,6 @@ import com.querydsl.jpa.impl.JPAQuery; import com.querydsl.jpa.impl.JPAQueryFactory; import java.util.List; -import java.util.function.LongSupplier; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Page; diff --git a/module-admin/src/main/java/com/kernel360/review/respository/ReviewRepository.java b/module-admin/src/main/java/com/kernel360/review/respository/ReviewRepository.java deleted file mode 100644 index e7edbc87..00000000 --- a/module-admin/src/main/java/com/kernel360/review/respository/ReviewRepository.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.kernel360.review.respository; - -import com.kernel360.review.repository.ReviewRepositoryJpa; - -public interface ReviewRepository extends ReviewRepositoryJpa, ReviewRepositoryDsl{ -} diff --git a/module-admin/src/main/java/com/kernel360/review/service/ReviewServiceImpl.java b/module-admin/src/main/java/com/kernel360/review/service/ReviewServiceImpl.java index 09fe8732..d47b317d 100644 --- a/module-admin/src/main/java/com/kernel360/review/service/ReviewServiceImpl.java +++ b/module-admin/src/main/java/com/kernel360/review/service/ReviewServiceImpl.java @@ -4,7 +4,7 @@ import com.kernel360.review.code.ReviewErrorCode; import com.kernel360.review.dto.AdminReviewDto; import com.kernel360.review.dto.ReviewSearchDto; -import com.kernel360.review.respository.ReviewRepository; +import com.kernel360.review.repository.ReviewRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Page; @@ -50,7 +50,8 @@ public Page getReviews(String sortBy, Pageable pageable) { public AdminReviewDto getReview(Long reviewNo) { log.info("리뷰 단건 조회 -> review_no {}", reviewNo); - return AdminReviewDto.from(reviewRepository.findByReviewNo(reviewNo)); + return AdminReviewDto.from(null); + //FIXME:: 찬규님 고쳐주세요 (null로 바꿔놨어요 reviewRepository.findByReviewNo(reviewNo) -> null) } @Override diff --git a/module-admin/src/main/resources/application-local.yml b/module-admin/src/main/resources/application-local.yml index cd2835bb..209d802a 100644 --- a/module-admin/src/main/resources/application-local.yml +++ b/module-admin/src/main/resources/application-local.yml @@ -12,6 +12,8 @@ spring: format_sql: true dialect: org.hibernate.dialect.PostgreSQLDialect ddl-auto: validate + naming: + physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl jasypt: encryptor: diff --git a/module-admin/src/main/resources/application.yml b/module-admin/src/main/resources/application.yml index 5dae5ae8..c74f5dee 100644 --- a/module-admin/src/main/resources/application.yml +++ b/module-admin/src/main/resources/application.yml @@ -5,7 +5,13 @@ spring: multipart: max-file-size: 50MB max-request-size: 500MB + jpa: + hibernate: + naming: + physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl server: port: 8082 +module: + name: admin diff --git a/module-admin/src/test/java/com/kernel360/ModuleAdminApplicationTests.java b/module-admin/src/test/java/com/kernel360/ModuleAdminApplicationTests.java index c9c52f46..5bb370a0 100644 --- a/module-admin/src/test/java/com/kernel360/ModuleAdminApplicationTests.java +++ b/module-admin/src/test/java/com/kernel360/ModuleAdminApplicationTests.java @@ -1,8 +1,9 @@ package com.kernel360; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; - +@Disabled @SpringBootTest class ModuleAdminApplicationTests { diff --git a/module-api/build.gradle b/module-api/build.gradle index c8ea3de5..ce1cb973 100644 --- a/module-api/build.gradle +++ b/module-api/build.gradle @@ -84,6 +84,14 @@ dependencies { annotationProcessor 'com.querydsl:querydsl-apt:5.0.0:jakarta' annotationProcessor 'jakarta.annotation:jakarta.annotation-api' annotationProcessor 'jakarta.persistence:jakarta.persistence-api' + + //badword Filtering + implementation 'io.github.vaneproject:badwordfiltering:1.0.0' + + + // https://mvnrepository.com/artifact/org.apache.commons/commons-collections4 + implementation group: 'org.apache.commons', name: 'commons-collections4', version: '4.4' + } tasks.named('test') { diff --git a/module-api/src/main/java/com/kernel360/bbs/code/BBSBusinessCode.java b/module-api/src/main/java/com/kernel360/bbs/code/BBSBusinessCode.java new file mode 100644 index 00000000..e6989293 --- /dev/null +++ b/module-api/src/main/java/com/kernel360/bbs/code/BBSBusinessCode.java @@ -0,0 +1,32 @@ +package com.kernel360.bbs.code; + +import com.kernel360.code.BusinessCode; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@RequiredArgsConstructor +public enum BBSBusinessCode implements BusinessCode { + + SUCCESS_REQUEST_GET_BBS(HttpStatus.OK.value(), "BBC001", "게시판 목록 조회 성공"), + SUCCESS_REQUEST_GET_BBS_VIEW(HttpStatus.OK.value(), "BBC002", "게시판 상세 조회 성공"), + SUCCESS_REQUEST_CREATED_BBS(HttpStatus.CREATED.value(), "BBC003", "게시글 작성 성공"), + SUCCESS_REQUEST_MODIFIED_BBS(HttpStatus.OK.value(), "BBC004", "게시글 수정 성공"), + SUCCESS_REQUEST_DELETE_BBS(HttpStatus.OK.value(), "BBC005", "게시글 삭제 성공"); + + private final int status; + private final String code; + private final String message; + + @Override + public int getStatus() { + return status; + } + + @Override + public String getCode() { return code; } + + @Override + public String getMessage() { + return message; + } +} diff --git a/module-api/src/main/java/com/kernel360/bbs/code/BBSErrorCode.java b/module-api/src/main/java/com/kernel360/bbs/code/BBSErrorCode.java new file mode 100644 index 00000000..93574ada --- /dev/null +++ b/module-api/src/main/java/com/kernel360/bbs/code/BBSErrorCode.java @@ -0,0 +1,31 @@ +package com.kernel360.bbs.code; + +import com.kernel360.code.ErrorCode; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@RequiredArgsConstructor +public enum BBSErrorCode implements ErrorCode { + + FAILED_GET_BBS_LIST(HttpStatus.NO_CONTENT.value(), "BMC001", "게시판 목록을 찾을 수 없음."), + FAILED_GET_BBS_VIEW(HttpStatus.NO_CONTENT.value(), "BMC002", "게시글을 찾을 수 없음."); + + private final int status; + private final String code; + private final String message; + + @Override + public int getStatus() { + return status; + } + + @Override + public String getCode() { + return code; + } + + @Override + public String getMessage() { + return message; + } +} diff --git a/module-api/src/main/java/com/kernel360/bbs/controller/BBSController.java b/module-api/src/main/java/com/kernel360/bbs/controller/BBSController.java new file mode 100644 index 00000000..4c65732d --- /dev/null +++ b/module-api/src/main/java/com/kernel360/bbs/controller/BBSController.java @@ -0,0 +1,64 @@ +package com.kernel360.bbs.controller; + +import com.kernel360.bbs.code.BBSBusinessCode; +import com.kernel360.bbs.dto.BBSDto; +import com.kernel360.bbs.dto.BBSListDto; +import com.kernel360.bbs.service.BBSService; +import com.kernel360.response.ApiResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequiredArgsConstructor +public class BBSController { + private final BBSService bbsService; + + @GetMapping("/bbs") + public ResponseEntity>> getBBS( + @RequestParam(value = "type", defaultValue = "") String type, + @RequestParam(value = "keyword", required = false) String keyword, Pageable pageable + ){ + Page result = bbsService.getBBSWithCondition(type, keyword, pageable); + + return ApiResponse.toResponseEntity(BBSBusinessCode.SUCCESS_REQUEST_GET_BBS, result); + } + + @GetMapping("/bbs/{bbsNo}") + public ResponseEntity> getBBSView(@PathVariable Long bbsNo){ + + return ApiResponse.toResponseEntity(BBSBusinessCode.SUCCESS_REQUEST_GET_BBS_VIEW, bbsService.getBBSView(bbsNo)); + } + + @GetMapping("/bbs/reply") + public ResponseEntity>> getBBSReply(@RequestParam Long upperNo, Pageable pageable){ + + return ApiResponse.toResponseEntity(BBSBusinessCode.SUCCESS_REQUEST_GET_BBS_VIEW, bbsService.getBBSReply(upperNo, pageable)); + } + + @PostMapping("/bbs") + public ResponseEntity> saveBBS(@RequestBody BBSDto bbsDto, @RequestHeader("Id") String id){ + + bbsService.saveBBS(bbsDto, id); + + return ApiResponse.toResponseEntity(BBSBusinessCode.SUCCESS_REQUEST_CREATED_BBS); + } + + @PatchMapping("/bbs") + public ResponseEntity> modifyBBS(@RequestBody BBSDto bbsDto, @RequestHeader("Id") String id){ + + bbsService.saveBBS(bbsDto, id); + + return ApiResponse.toResponseEntity(BBSBusinessCode.SUCCESS_REQUEST_MODIFIED_BBS); + } + + @DeleteMapping("/bbs") + public ResponseEntity> deleteBBS(@RequestParam Long bbsNo){ + + bbsService.deleteBBS(bbsNo); + + return ApiResponse.toResponseEntity(BBSBusinessCode.SUCCESS_REQUEST_DELETE_BBS); + } +} diff --git a/module-api/src/main/java/com/kernel360/bbs/dto/BBSDto.java b/module-api/src/main/java/com/kernel360/bbs/dto/BBSDto.java new file mode 100644 index 00000000..fc3f7b33 --- /dev/null +++ b/module-api/src/main/java/com/kernel360/bbs/dto/BBSDto.java @@ -0,0 +1,86 @@ +package com.kernel360.bbs.dto; + +import com.kernel360.bbs.entity.BBS; +import com.kernel360.member.dto.MemberDto; + +import java.time.LocalDateTime; + +/** + * DTO for {@link com.kernel360.bbs.entity.BBS} + */ +public record BBSDto( + Long bbsNo, + Long upperNo, + String type, + String title, + String contents, + Boolean isVisible, + LocalDateTime createdAt, + String createdBy, + LocalDateTime modifiedAt, + String modifiedBy, + Long viewCount, + MemberDto memberDto + ) { + + public static BBSDto of( + Long bbsNo, + Long upperNo, + String type, + String title, + String contents, + Boolean isVisible, + LocalDateTime createdAt, + String createdBy, + LocalDateTime modifiedAt, + String modifiedBy, + Long viewCount, + MemberDto memberDto + ){ + return new BBSDto( + bbsNo, + upperNo, + type, + title, + contents, + isVisible, + createdAt, + createdBy, + modifiedAt, + modifiedBy, + viewCount, + memberDto + ); + } + + public static BBSDto from(BBS entity){ + return new BBSDto( + entity.getBbsNo(), + entity.getUpperNo(), + entity.getType(), + entity.getTitle(), + entity.getContents(), + entity.getIsVisible(), + entity.getCreatedAt(), + entity.getCreatedBy(), + entity.getModifiedAt(), + entity.getModifiedBy(), + entity.getViewCount(), + MemberDto.from(entity.getMember()) + ); + } + + +// public BBS toEntity() { +// return BBS.create( +// this.bbsNo(), +// this.upperNo(), +// this.title(), +// this.contents() +// +// ); +// } + + + +} \ No newline at end of file diff --git a/module-api/src/main/java/com/kernel360/bbs/dto/BBSListDto.java b/module-api/src/main/java/com/kernel360/bbs/dto/BBSListDto.java new file mode 100644 index 00000000..ca905667 --- /dev/null +++ b/module-api/src/main/java/com/kernel360/bbs/dto/BBSListDto.java @@ -0,0 +1,19 @@ +package com.kernel360.bbs.dto; + +import lombok.Data; +import lombok.RequiredArgsConstructor; + +import java.time.LocalDateTime; + +@Data +@RequiredArgsConstructor +public class BBSListDto { + Long bbsNo; + String type; + String title; + LocalDateTime createdAt; + String createdBy; + Long viewCount; + Long memberNo; + String id; +} diff --git a/module-api/src/main/java/com/kernel360/bbs/enumset/BBSType.java b/module-api/src/main/java/com/kernel360/bbs/enumset/BBSType.java new file mode 100644 index 00000000..54fceb00 --- /dev/null +++ b/module-api/src/main/java/com/kernel360/bbs/enumset/BBSType.java @@ -0,0 +1,10 @@ +package com.kernel360.bbs.enumset; + +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public enum BBSType { + QNA, FREE, BOAST, RECOMMAND, NOTICE, REPLY; + + String type; +} diff --git a/module-api/src/main/java/com/kernel360/bbs/repository/BBSRepository.java b/module-api/src/main/java/com/kernel360/bbs/repository/BBSRepository.java new file mode 100644 index 00000000..b60ae6c4 --- /dev/null +++ b/module-api/src/main/java/com/kernel360/bbs/repository/BBSRepository.java @@ -0,0 +1,16 @@ +package com.kernel360.bbs.repository; + +import com.kernel360.bbs.dto.BBSListDto; +import com.kernel360.bbs.entity.BBS; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +public interface BBSRepository extends BBSRepositoryJPA, BBSRepositoryDSL { + Page getBBSWithCondition(String type, String keyword, Pageable pageable); + + BBS findOneByBbsNo(Long bbsNo); + + Page findAllByUpperNo(Long upperNo, Pageable pageable); + + void deleteByBbsNo(Long bbsNo); +} diff --git a/module-api/src/main/java/com/kernel360/bbs/repository/BBSRepositoryDSL.java b/module-api/src/main/java/com/kernel360/bbs/repository/BBSRepositoryDSL.java new file mode 100644 index 00000000..6adfa692 --- /dev/null +++ b/module-api/src/main/java/com/kernel360/bbs/repository/BBSRepositoryDSL.java @@ -0,0 +1,9 @@ +package com.kernel360.bbs.repository; + +import com.kernel360.bbs.dto.BBSListDto; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +public interface BBSRepositoryDSL { + Page getBBSWithCondition(String bbsType, String keyword, Pageable pageable); +} diff --git a/module-api/src/main/java/com/kernel360/bbs/repository/BBSRepositoryDSLImpl.java b/module-api/src/main/java/com/kernel360/bbs/repository/BBSRepositoryDSLImpl.java new file mode 100644 index 00000000..c6b2d1d3 --- /dev/null +++ b/module-api/src/main/java/com/kernel360/bbs/repository/BBSRepositoryDSLImpl.java @@ -0,0 +1,81 @@ +package com.kernel360.bbs.repository; + +import com.kernel360.bbs.dto.BBSListDto; +import com.kernel360.bbs.enumset.BBSType; +import com.querydsl.core.types.Predicate; +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.jpa.impl.JPAQuery; +import com.querydsl.jpa.impl.JPAQueryFactory; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; + +import java.util.List; +import java.util.Objects; + +import static com.kernel360.bbs.entity.QBBS.bBS; +import static com.kernel360.member.entity.QMember.member; +import static com.querydsl.core.types.Projections.fields; +import static org.springframework.util.StringUtils.hasText; + +@RequiredArgsConstructor +public class BBSRepositoryDSLImpl implements BBSRepositoryDSL { + + private final JPAQueryFactory queryFactory; + + @Override + public Page getBBSWithCondition(String type, String keyword, Pageable pageable) { + + Predicate finalPredicate = bBS.isVisible.eq(true) + .and(bBS.type.eq(BBSType.valueOf(type).name())); + + List bbs = getBBSListWithMember(). + where( + finalPredicate, + keywordContains(keyword) + ) + .orderBy(bBS.createdAt.desc()) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .fetch(); + + Long totalCount = queryFactory + .select(bBS.count()) + .from(bBS) + .where( + finalPredicate, + keywordContains(keyword) + ) + .fetchOne(); + + return new PageImpl<>(bbs, pageable, totalCount); + } + + private JPAQuery getBBSListWithMember() { + return queryFactory + .select(fields(BBSListDto.class, + bBS.bbsNo, + bBS.title, + bBS.type, + bBS.createdAt, + bBS.createdBy, + bBS.viewCount, + bBS.member.memberNo, + bBS.member.id + ) + + ) + .from(bBS) + .join(member).on(bBS.member.memberNo.eq(member.memberNo)); + } + + private BooleanExpression keywordContains(String keyword) { //우선 참이 된다면 하위 조건 결과는 포함 안됨. + return hasText(keyword) ? + bBS.title.contains(keyword) + .or(bBS.contents.contains(keyword)) + .or(bBS.createdBy.eq(keyword)) + : null; + } + +} diff --git a/module-api/src/main/java/com/kernel360/bbs/service/BBSService.java b/module-api/src/main/java/com/kernel360/bbs/service/BBSService.java new file mode 100644 index 00000000..bab40728 --- /dev/null +++ b/module-api/src/main/java/com/kernel360/bbs/service/BBSService.java @@ -0,0 +1,48 @@ +package com.kernel360.bbs.service; + +import com.kernel360.bbs.dto.BBSDto; +import com.kernel360.bbs.dto.BBSListDto; +import com.kernel360.bbs.entity.BBS; +import com.kernel360.bbs.repository.BBSRepository; +import com.kernel360.member.dto.MemberDto; +import com.kernel360.member.service.MemberService; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class BBSService { + private final BBSRepository bbsRepository; + private final MemberService memberService; + + public Page getBBSWithCondition(String type, String keyword, Pageable pageable) { + + return bbsRepository.getBBSWithCondition(type, keyword, pageable); + } + + public BBSDto getBBSView(Long bbsNo) { + + return BBSDto.from(bbsRepository.findOneByBbsNo(bbsNo)); + } + + public Page getBBSReply(Long upperNo, Pageable pageable) { + + return bbsRepository.findAllByUpperNo(upperNo, pageable).map(BBSDto::from); + } + + @Transactional + public void saveBBS(BBSDto bbsDto, String id) { + + MemberDto memberDto = memberService.findByMemberId(id); + + bbsRepository.save(BBS.save(bbsDto.bbsNo(), bbsDto.upperNo(), bbsDto.type(), bbsDto.title(), bbsDto.contents(), true, 0L, memberDto.toEntity())); + } + + @Transactional + public void deleteBBS(Long bbsNo) { + bbsRepository.deleteByBbsNo(bbsNo); + } +} diff --git a/module-api/src/main/java/com/kernel360/commoncode/dto/CommonCodeDto.java b/module-api/src/main/java/com/kernel360/commoncode/dto/CommonCodeDto.java index ef367307..5d1e5ad5 100644 --- a/module-api/src/main/java/com/kernel360/commoncode/dto/CommonCodeDto.java +++ b/module-api/src/main/java/com/kernel360/commoncode/dto/CommonCodeDto.java @@ -3,22 +3,23 @@ import com.kernel360.commoncode.entity.CommonCode; import java.time.LocalDate; +import java.time.LocalDateTime; /** * DTO for {@link com.kernel360.commoncode.entity.CommonCode} */ public record CommonCodeDto(Long codeNo, String codeName, - Integer upperNo, + Long upperNo, String upperName, Integer sortOrder, Boolean isUsed, String description, - LocalDate createdAt, + LocalDateTime createdAt, String createdBy, - LocalDate modifiedAt, + LocalDateTime modifiedAt, String modifiedBy) { /** * @param codeNo, codeName @@ -27,14 +28,14 @@ public record CommonCodeDto(Long codeNo, public static CommonCodeDto of( Long codeNo, String codeName, - Integer upperNo, + Long upperNo, String upperName, Integer sortOrder, Boolean isUsed, String description, - LocalDate createdAt, + LocalDateTime createdAt, String createdBy, - LocalDate modifiedAt, + LocalDateTime modifiedAt, String modifiedBy ) { return new CommonCodeDto( diff --git a/module-api/src/main/java/com/kernel360/file/repository/FileRepository.java b/module-api/src/main/java/com/kernel360/file/repository/FileRepository.java new file mode 100644 index 00000000..42dc1afb --- /dev/null +++ b/module-api/src/main/java/com/kernel360/file/repository/FileRepository.java @@ -0,0 +1,4 @@ +package com.kernel360.file.repository; + +public interface FileRepository extends FileRepositoryJpa { +} diff --git a/module-api/src/main/java/com/kernel360/global/Interceptor/InterceptorConfig.java b/module-api/src/main/java/com/kernel360/global/Interceptor/InterceptorConfig.java index 85aa2a07..6770b200 100644 --- a/module-api/src/main/java/com/kernel360/global/Interceptor/InterceptorConfig.java +++ b/module-api/src/main/java/com/kernel360/global/Interceptor/InterceptorConfig.java @@ -15,6 +15,7 @@ public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(acceptInterceptor) .addPathPatterns("/auth/**") //** 인증 JWT 토큰 관련 **// .addPathPatterns("/reviews/**") + .addPathPatterns("/reviews-washzone/**") .addPathPatterns("/likes/**") .addPathPatterns("/mypage/**"); //.excludePathPatterns("/public/**"); // 제외할 URL 패턴 diff --git a/module-api/src/main/java/com/kernel360/global/annotation/BadWordFilter.java b/module-api/src/main/java/com/kernel360/global/annotation/BadWordFilter.java new file mode 100644 index 00000000..9e8d304d --- /dev/null +++ b/module-api/src/main/java/com/kernel360/global/annotation/BadWordFilter.java @@ -0,0 +1,23 @@ +package com.kernel360.global.annotation; + +import com.kernel360.global.badwordfilter.BadWordValidator; +import jakarta.validation.Constraint; +import jakarta.validation.Payload; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; + +@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Constraint(validatedBy = BadWordValidator.class) +public @interface BadWordFilter { + String message() default ""; + Class[] groups() default {}; + Class[] payload() default {}; + boolean ignoreCase() default false; +} diff --git a/module-api/src/main/java/com/kernel360/global/aop/LogAspect.java b/module-api/src/main/java/com/kernel360/global/aop/LogAspect.java index 037e8951..922830ff 100644 --- a/module-api/src/main/java/com/kernel360/global/aop/LogAspect.java +++ b/module-api/src/main/java/com/kernel360/global/aop/LogAspect.java @@ -2,8 +2,10 @@ import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; @Slf4j @@ -21,4 +23,21 @@ public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable { return proceed; } + + @Pointcut("within(*..*Controller)") + public void controller() {} + + @Around("controller()") + public Object logApiExecTime(ProceedingJoinPoint joinPoint) throws Throwable { + long start = System.currentTimeMillis(); + Object proceed = joinPoint.proceed(); + + long executionTime = System.currentTimeMillis() - start; + Signature signature = joinPoint.getSignature(); + + log.info(String.format("##### @API Execution Time ##### [%dms] → %s.%s", + executionTime, signature.getDeclaringTypeName(), signature.getName())); + + return proceed; + } } \ No newline at end of file diff --git a/module-api/src/main/java/com/kernel360/global/badwordfilter/BadWordValidator.java b/module-api/src/main/java/com/kernel360/global/badwordfilter/BadWordValidator.java new file mode 100644 index 00000000..70bbd902 --- /dev/null +++ b/module-api/src/main/java/com/kernel360/global/badwordfilter/BadWordValidator.java @@ -0,0 +1,33 @@ +package com.kernel360.global.badwordfilter; + +import com.kernel360.global.annotation.BadWordFilter; +import com.vane.badwordfiltering.BadWordFiltering; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; + +import java.util.List; + +public class BadWordValidator implements ConstraintValidator { + private BadWordFiltering badWordFiltering; + @Override + public void initialize(BadWordFilter constraintAnnotation) { + + badWordFiltering = new BadWordFiltering(); + } + + @Override + public boolean isValid(Object value, ConstraintValidatorContext context) { + if (value instanceof String string) { + return !badWordFiltering.check(string); + } else if (value instanceof List list) { + return handleStringList(list); + } + return true; + } + + private boolean handleStringList(List list) { + return list.stream() + .map(String.class::cast) + .noneMatch(badWordFiltering::check); + } +} \ No newline at end of file diff --git a/module-api/src/main/java/com/kernel360/member/config/AuditConfig.java b/module-api/src/main/java/com/kernel360/global/config/AuditConfig.java similarity index 83% rename from module-api/src/main/java/com/kernel360/member/config/AuditConfig.java rename to module-api/src/main/java/com/kernel360/global/config/AuditConfig.java index 802402cf..20589539 100644 --- a/module-api/src/main/java/com/kernel360/member/config/AuditConfig.java +++ b/module-api/src/main/java/com/kernel360/global/config/AuditConfig.java @@ -1,4 +1,4 @@ -package com.kernel360.member.config; +package com.kernel360.global.config; import jakarta.servlet.http.HttpServletRequest; import org.springframework.context.annotation.Configuration; @@ -13,7 +13,7 @@ public class AuditConfig implements AuditorAware { @Override public Optional getCurrentAuditor() { HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest(); - String createId = Optional.ofNullable(request.getParameter("id")).orElse("admin"); + String createId = Optional.ofNullable(request.getHeader("Id")).orElse("admin"); return Optional.of(createId); } diff --git a/module-api/src/main/java/com/kernel360/global/filter/LogFilter.java b/module-api/src/main/java/com/kernel360/global/filter/LogFilter.java index dfea3ef2..660d933d 100644 --- a/module-api/src/main/java/com/kernel360/global/filter/LogFilter.java +++ b/module-api/src/main/java/com/kernel360/global/filter/LogFilter.java @@ -4,6 +4,8 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections4.Trie; +import org.apache.commons.collections4.trie.PatriciaTrie; import org.springframework.stereotype.Component; import org.springframework.web.util.ContentCachingRequestWrapper; import org.springframework.web.util.ContentCachingResponseWrapper; @@ -18,6 +20,11 @@ public class LogFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { ContentCachingRequestWrapper request = new ContentCachingRequestWrapper((HttpServletRequest) req); ContentCachingResponseWrapper response = new ContentCachingResponseWrapper((HttpServletResponse) res); + + printLog(chain, request, response); + } + + private static void printLog(FilterChain chain, ContentCachingRequestWrapper request, ContentCachingResponseWrapper response) throws IOException, ServletException { log.info("##### INIT URI: {}", request.getRequestURI()); chain.doFilter(request, response); @@ -54,8 +61,16 @@ public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) }); String responseBody = new String(response.getContentAsByteArray()); - log.info("##### RESPONSE ##### uri: {}, method: {}, header: {}, body: {}", uri, method, responseHeaderValues, responseBody); - + if(!deninedList().containsKey(uri)) { + log.info("##### RESPONSE ##### uri: {}, method: {}, header: {}, body: {}", uri, method, responseHeaderValues, responseBody); + } response.copyBodyToResponse(); } + + private static Trie deninedList(){ + Trie trie = new PatriciaTrie<>(); + + trie.put("/actuator/prometheus",0); + return trie; + } } diff --git a/module-api/src/main/java/com/kernel360/likes/controller/LikeController.java b/module-api/src/main/java/com/kernel360/likes/controller/LikeController.java index fc29687a..893fb215 100644 --- a/module-api/src/main/java/com/kernel360/likes/controller/LikeController.java +++ b/module-api/src/main/java/com/kernel360/likes/controller/LikeController.java @@ -5,7 +5,7 @@ import com.kernel360.likes.dto.LikeSearchDto; import com.kernel360.likes.entity.Like; import com.kernel360.likes.service.LikeService; -import com.kernel360.main.controller.Sort; +import com.kernel360.main.enumset.Sort; import com.kernel360.product.dto.ProductResponse; import com.kernel360.product.service.ProductService; import com.kernel360.response.ApiResponse; diff --git a/module-api/src/main/java/com/kernel360/likes/dto/LikeSearchDto.java b/module-api/src/main/java/com/kernel360/likes/dto/LikeSearchDto.java index 877bdf59..bfdb036a 100644 --- a/module-api/src/main/java/com/kernel360/likes/dto/LikeSearchDto.java +++ b/module-api/src/main/java/com/kernel360/likes/dto/LikeSearchDto.java @@ -1,6 +1,6 @@ package com.kernel360.likes.dto; -import com.kernel360.main.controller.Sort; +import com.kernel360.main.enumset.Sort; public record LikeSearchDto( String token, diff --git a/module-api/src/main/java/com/kernel360/main/controller/MainController.java b/module-api/src/main/java/com/kernel360/main/controller/MainController.java index ab55c173..9767c89e 100644 --- a/module-api/src/main/java/com/kernel360/main/controller/MainController.java +++ b/module-api/src/main/java/com/kernel360/main/controller/MainController.java @@ -3,6 +3,7 @@ import com.kernel360.main.code.BannerBusinessCode; import com.kernel360.main.dto.BannerDto; import com.kernel360.main.dto.RecommendProductsDto; +import com.kernel360.main.enumset.Sort; import com.kernel360.main.service.MainService; import com.kernel360.product.code.ProductsBusinessCode; import com.kernel360.product.dto.ProductResponse; diff --git a/module-api/src/main/java/com/kernel360/main/conveter/StringToSortConverter.java b/module-api/src/main/java/com/kernel360/main/conveter/StringToSortConverter.java index 956cb688..747ab2c2 100644 --- a/module-api/src/main/java/com/kernel360/main/conveter/StringToSortConverter.java +++ b/module-api/src/main/java/com/kernel360/main/conveter/StringToSortConverter.java @@ -2,7 +2,7 @@ import com.kernel360.exception.BusinessException; import com.kernel360.main.code.ConverterErrorCode; -import com.kernel360.main.controller.Sort; +import com.kernel360.main.enumset.Sort; import org.springframework.core.convert.converter.Converter; import org.springframework.stereotype.Component; diff --git a/module-api/src/main/java/com/kernel360/main/controller/Sort.java b/module-api/src/main/java/com/kernel360/main/enumset/Sort.java similarity index 82% rename from module-api/src/main/java/com/kernel360/main/controller/Sort.java rename to module-api/src/main/java/com/kernel360/main/enumset/Sort.java index 7d6808e6..a2a8e56a 100644 --- a/module-api/src/main/java/com/kernel360/main/controller/Sort.java +++ b/module-api/src/main/java/com/kernel360/main/enumset/Sort.java @@ -1,4 +1,4 @@ -package com.kernel360.main.controller; +package com.kernel360.main.enumset; import com.kernel360.product.dto.ProductResponse; import com.kernel360.product.dto.ProductSearchDto; @@ -10,7 +10,7 @@ public enum Sort { VIEW_COUNT_PRODUCT_ORDER("viewCnt-order") { @Override - Page sort(ProductService productService, Pageable pageable) { + public Page sort(ProductService productService, Pageable pageable) { return productService.getProductListOrderByViewCount(pageable); } @@ -25,7 +25,7 @@ public Page withKeywordSort(ProductService productService, Stri VIOLATION_PRODUCT_LIST("violation-products") { @Override - Page sort(ProductService productService, Pageable pageable) { + public Page sort(ProductService productService, Pageable pageable) { return productService.getViolationProducts(pageable); } @@ -39,7 +39,7 @@ public Page withKeywordSort(ProductService productService, Stri RECOMMENDATION_PRODUCT_ORDER("recommend-order") { @Override - Page sort(ProductService productService, Pageable pageable) { + public Page sort(ProductService productService, Pageable pageable) { return productService.getFavoriteProducts(pageable); } @@ -53,7 +53,7 @@ public Page withKeywordSort(ProductService productService, Stri RECENT_PRODUCT_ORDER("recent-order") { @Override - Page sort(ProductService productService, Pageable pageable) { + public Page sort(ProductService productService, Pageable pageable) { return productService.getRecentProducts(pageable); } @@ -75,7 +75,7 @@ public String getOrderType() { return orderType; } - abstract Page sort(ProductService productService, Pageable pageable); + public abstract Page sort(ProductService productService, Pageable pageable); public abstract Page withKeywordSort(ProductService productService, String keyword, Pageable pageable); diff --git a/module-api/src/main/java/com/kernel360/member/controller/MemberController.java b/module-api/src/main/java/com/kernel360/member/controller/MemberController.java index 531537a4..f6249e25 100644 --- a/module-api/src/main/java/com/kernel360/member/controller/MemberController.java +++ b/module-api/src/main/java/com/kernel360/member/controller/MemberController.java @@ -92,7 +92,7 @@ public ResponseEntity> sendMemberIdByEmail(@RequestBody Memb @PostMapping("/find-password") public ResponseEntity> sendPasswordResetUriByEmail(@RequestBody MemberCredentialDto dto) { //--입력받은 아이디를 데이터베이스에 조회, 없으면 예외 발생--/ - MemberDto memberDto = memberService.findByMemberId(dto.memberId()); + MemberDto memberDto = memberService.findOneByIdForAccountTypeByPlatform(dto.memberId()); //--유효성이 검증된 아이디에 대해서 만료시간이 있는 비밀번호 초기화 (호스트 + UUID) 링크 생성 --// String resetUri = findCredentialService.generatePasswordResetPageUri(memberDto); //-- 가입시 입력한 이메일로 비밀번호 초기화 이메일 발송 --// diff --git a/module-api/src/main/java/com/kernel360/member/dto/MemberDto.java b/module-api/src/main/java/com/kernel360/member/dto/MemberDto.java index 6c5b9af9..1d0176c0 100644 --- a/module-api/src/main/java/com/kernel360/member/dto/MemberDto.java +++ b/module-api/src/main/java/com/kernel360/member/dto/MemberDto.java @@ -5,6 +5,7 @@ import com.kernel360.member.enumset.Gender; import java.time.LocalDate; +import java.time.LocalDateTime; /** * DTO for {@link com.kernel360.member.entity.Member} @@ -15,9 +16,9 @@ public record MemberDto(Long memberNo, String password, String gender, String age, - LocalDate createdAt, + LocalDateTime createdAt, String createdBy, - LocalDate modifiedAt, + LocalDateTime modifiedAt, String modifiedBy, String jwtToken ) { @@ -29,9 +30,9 @@ public static MemberDto of( String password, String gender, String age, - LocalDate createdAt, + LocalDateTime createdAt, String createdBy, - LocalDate modifiedAt, + LocalDateTime modifiedAt, String modifiedBy, String jwtToken ) { @@ -153,4 +154,26 @@ public static MemberDto of( null ); } + + /** find review **/ + public static MemberDto of( + Long memberNo, + String id, + int age, + int gender + ){ + return new MemberDto( + memberNo, + id, + null, + null, + Gender.ordinalToName(gender), + Age.ordinalToValue(age), + null, + null, + null, + null, + null + ); + } } \ No newline at end of file diff --git a/module-api/src/main/java/com/kernel360/member/dto/PasswordDto.java b/module-api/src/main/java/com/kernel360/member/dto/PasswordDto.java index 5c40f7bf..ec09a031 100644 --- a/module-api/src/main/java/com/kernel360/member/dto/PasswordDto.java +++ b/module-api/src/main/java/com/kernel360/member/dto/PasswordDto.java @@ -1,6 +1,13 @@ package com.kernel360.member.dto; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; + public record PasswordDto( + @NotBlank(message = "비밀번호는 비어 있을수 없습니다.") + @Size(min = 8, max = 16, message = "비밀번호는 8~16글자입니다.") + @Pattern(regexp = "^(?=.*\\d)(?=.*[a-zA-Z])(?=.*\\W).{8,16}$", message = "8~16자의 영문자(대/소문자 구분 없음), 숫자, 특수문자를 사용해 주세요.") String password ) { } diff --git a/module-api/src/main/java/com/kernel360/member/repository/MemberRepository.java b/module-api/src/main/java/com/kernel360/member/repository/MemberRepository.java new file mode 100644 index 00000000..4c7196b4 --- /dev/null +++ b/module-api/src/main/java/com/kernel360/member/repository/MemberRepository.java @@ -0,0 +1,3 @@ +package com.kernel360.member.repository; +public interface MemberRepository extends MemberRepositoryJpa { +} diff --git a/module-api/src/main/java/com/kernel360/member/service/KakaoRequest.java b/module-api/src/main/java/com/kernel360/member/service/KakaoRequest.java index 15164ff7..3551c959 100644 --- a/module-api/src/main/java/com/kernel360/member/service/KakaoRequest.java +++ b/module-api/src/main/java/com/kernel360/member/service/KakaoRequest.java @@ -22,7 +22,8 @@ public KakaoUserDto getKakaoUserByToken(String accessToken) { headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); headers.set("Authorization", "Bearer "+accessToken); - headers.set("charset", "utf-8"); + headers.set("Content-type", "application/x-www-form-urlencoded;charset=utf-8"); + HttpEntity requestEntity = new HttpEntity<>(headers); RestTemplate restTemplate = new RestTemplate(); @@ -38,9 +39,8 @@ public KakaoUserDto getKakaoUserByToken(String accessToken) { } Map kakaoAccount = mapper.convertValue(kakaoResponse.get("kakao_account"), HashMap.class); - KakaoUserDto dto = KakaoUserDto.of(kakaoResponse.get("id").toString(),kakaoAccount.get("email").toString()); + return KakaoUserDto.of(kakaoResponse.get("id").toString(),kakaoAccount.get("email").toString()); - return dto; } diff --git a/module-api/src/main/java/com/kernel360/member/service/MemberService.java b/module-api/src/main/java/com/kernel360/member/service/MemberService.java index d523cc7e..0792169d 100644 --- a/module-api/src/main/java/com/kernel360/member/service/MemberService.java +++ b/module-api/src/main/java/com/kernel360/member/service/MemberService.java @@ -101,7 +101,7 @@ public boolean idDuplicationCheck(String id) { public boolean emailDuplicationCheck(String email) { Member member = memberRepository.findOneByEmailForAccountTypeByPlatform(email); - return member != null; + return Objects.nonNull(member); } public MemberDto findMemberByToken(String token) { @@ -238,7 +238,7 @@ public MemberDto findByEmail(String email) { } @Transactional(readOnly = true) - public MemberDto findByMemberId(String memberId) { + public MemberDto findOneByIdForAccountTypeByPlatform(String memberId) { Member member = memberRepository.findOneByIdForAccountTypeByPlatform(memberId); if (member == null) { throw new BusinessException(MemberErrorCode.FAILED_FIND_MEMBER_INFO); @@ -284,4 +284,9 @@ public boolean validatePassword(String password, String token) { return member.getPassword().equals(ConvertSHA256.convertToSHA256(password)); } + + public MemberDto findByMemberId(String id) { + Member member = memberRepository.findOneById(id); + return MemberDto.from(member); + } } diff --git a/module-api/src/main/java/com/kernel360/product/controller/ProductController.java b/module-api/src/main/java/com/kernel360/product/controller/ProductController.java index b84d8c55..885fdf17 100644 --- a/module-api/src/main/java/com/kernel360/product/controller/ProductController.java +++ b/module-api/src/main/java/com/kernel360/product/controller/ProductController.java @@ -1,6 +1,6 @@ package com.kernel360.product.controller; -import com.kernel360.main.controller.Sort; +import com.kernel360.main.enumset.Sort; import com.kernel360.product.code.ProductsBusinessCode; import com.kernel360.product.dto.ProductDetailDto; import com.kernel360.product.dto.ProductDto; diff --git a/module-api/src/main/java/com/kernel360/product/dto/ProductDetailDto.java b/module-api/src/main/java/com/kernel360/product/dto/ProductDetailDto.java index d7f48dca..c6d2e299 100644 --- a/module-api/src/main/java/com/kernel360/product/dto/ProductDetailDto.java +++ b/module-api/src/main/java/com/kernel360/product/dto/ProductDetailDto.java @@ -1,12 +1,9 @@ package com.kernel360.product.dto; import com.kernel360.product.entity.Product; -import jakarta.persistence.Column; -import org.springframework.data.annotation.CreatedBy; -import org.springframework.data.annotation.LastModifiedBy; -import org.springframework.data.annotation.LastModifiedDate; import java.time.LocalDate; +import java.time.LocalDateTime; /** * DTO for {@link com.kernel360.product.entity.Product} @@ -40,9 +37,9 @@ public record ProductDetailDto( String manufactureMethod, String manufactureNation, String violationInfo, - LocalDate createdAt, + LocalDateTime createdAt, String createdBy, - LocalDate modifiedAt, + LocalDateTime modifiedAt, String modifiedBy //TODO 브랜드 엔티티 ) { @@ -76,9 +73,9 @@ public static ProductDetailDto of( String manufactureMethod, String manufactureNation, String violationInfo, - LocalDate createdAt, + LocalDateTime createdAt, String createdBy, - LocalDate modifiedAt, + LocalDateTime modifiedAt, String modifiedBy ) { return new ProductDetailDto( @@ -117,6 +114,51 @@ public static ProductDetailDto of( ); } + /** find review **/ + public static ProductDetailDto of( + Long productNo, + String productName, + String imageSource, + String companyName, + String upperItem, + String item + ) { + return new ProductDetailDto( + productNo, + productName, + null, + imageSource, + null, + null, + null, + companyName, + null, + null, + null, + upperItem, + item, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null + ); + } + public static ProductDetailDto from(Product entity) { return ProductDetailDto.of( entity.getProductNo(), diff --git a/module-api/src/main/java/com/kernel360/product/dto/ProductDto.java b/module-api/src/main/java/com/kernel360/product/dto/ProductDto.java index 3ae97ccc..f4930e28 100644 --- a/module-api/src/main/java/com/kernel360/product/dto/ProductDto.java +++ b/module-api/src/main/java/com/kernel360/product/dto/ProductDto.java @@ -1,9 +1,8 @@ package com.kernel360.product.dto; import com.kernel360.product.entity.Product; -import com.kernel360.product.entity.SafetyStatus; - -import java.time.LocalDate; +import com.kernel360.product.enumset.SafetyStatus; +import java.time.LocalDateTime; /** * DTO for {@link com.kernel360.product.entity.Product} @@ -18,9 +17,9 @@ public record ProductDto( Integer viewCount, String brand, String upperItem, - LocalDate createdAt, + LocalDateTime createdAt, String createdBy, - LocalDate modifiedAt, + LocalDateTime modifiedAt, String modifiedBy ) { @@ -34,9 +33,9 @@ public static ProductDto of( String brand, String upperItem, Integer viewCount, - LocalDate createdAt, + LocalDateTime createdAt, String createdBy, - LocalDate modifiedAt, + LocalDateTime modifiedAt, String modifiedBy ) { return new ProductDto( @@ -73,11 +72,4 @@ public static ProductDto from(Product entity) { entity.getModifiedBy() ); } - - public Product toEntity() { - return Product.of( - productNo, - productName - ); - } } diff --git a/module-api/src/main/java/com/kernel360/product/dto/ProductSearchDto.java b/module-api/src/main/java/com/kernel360/product/dto/ProductSearchDto.java index 9bd4f627..47ca77ca 100644 --- a/module-api/src/main/java/com/kernel360/product/dto/ProductSearchDto.java +++ b/module-api/src/main/java/com/kernel360/product/dto/ProductSearchDto.java @@ -1,6 +1,6 @@ package com.kernel360.product.dto; -import com.kernel360.main.controller.Sort; +import com.kernel360.main.enumset.Sort; public record ProductSearchDto( String token, diff --git a/module-api/src/main/java/com/kernel360/product/service/ProductService.java b/module-api/src/main/java/com/kernel360/product/service/ProductService.java index a20e8880..e7404b02 100644 --- a/module-api/src/main/java/com/kernel360/product/service/ProductService.java +++ b/module-api/src/main/java/com/kernel360/product/service/ProductService.java @@ -9,7 +9,7 @@ import com.kernel360.product.dto.ProductResponse; import com.kernel360.product.dto.ProductSearchDto; import com.kernel360.product.entity.Product; -import com.kernel360.product.entity.SafetyStatus; +import com.kernel360.product.enumset.SafetyStatus; import com.kernel360.product.repository.ProductRepository; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; diff --git a/module-api/src/main/java/com/kernel360/review/code/ReviewErrorCode.java b/module-api/src/main/java/com/kernel360/review/code/ReviewErrorCode.java index 7ff710ec..a2d0d5f3 100644 --- a/module-api/src/main/java/com/kernel360/review/code/ReviewErrorCode.java +++ b/module-api/src/main/java/com/kernel360/review/code/ReviewErrorCode.java @@ -7,7 +7,11 @@ @RequiredArgsConstructor public enum ReviewErrorCode implements ErrorCode { INVALID_STAR_RATING_VALUE(HttpStatus.BAD_REQUEST.value(), "ERV001", "유효하지 않은 별점입니다."), - INVALID_REVIEW_WRITE_REQUEST(HttpStatus.BAD_REQUEST.value(), "ERV002", "리뷰가 중복되거나 유효하지 않습니다."); + DUPLICATE_REVIEW_EXISTS(HttpStatus.BAD_REQUEST.value(), "ERV002", "중복된 리뷰가 존재합니다."), + NOT_FOUND_REVIEW(HttpStatus.BAD_REQUEST.value(), "ERV003", "리뷰가 존재하지 않습니다."), + NOT_FOUND_PRODUCT_FOR_REVIEW_CREATION(HttpStatus.BAD_REQUEST.value(), "ERV004", "제품이 존재하지 않습니다."), + NOT_FOUND_MEMBER_FOR_REVIEW_CREATION(HttpStatus.BAD_REQUEST.value(), "ERV005", "회원이 존재하지 않습니다."), + MISMATCHED_MEMBER_NO_AND_ID(HttpStatus.BAD_REQUEST.value(), "ERV006", "회원 번호와 아이디가 일치하지 않습니다."); private final int status; private final String code; diff --git a/module-api/src/main/java/com/kernel360/review/controller/ReviewController.java b/module-api/src/main/java/com/kernel360/review/controller/ReviewController.java index 0eaddc7c..1e677498 100644 --- a/module-api/src/main/java/com/kernel360/review/controller/ReviewController.java +++ b/module-api/src/main/java/com/kernel360/review/controller/ReviewController.java @@ -2,13 +2,18 @@ import com.kernel360.response.ApiResponse; import com.kernel360.review.code.ReviewBusinessCode; -import com.kernel360.review.dto.ReviewDto; +import com.kernel360.review.dto.ReviewRequestDto; +import com.kernel360.review.dto.ReviewResponseDto; import com.kernel360.review.service.ReviewService; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; @RestController @RequiredArgsConstructor @@ -17,38 +22,55 @@ public class ReviewController { private final ReviewService reviewService; - @GetMapping("") - public ResponseEntity>> getReviewsByProduct( - @RequestParam(name = "productNo") Long productNo, + @GetMapping("/product/{productNo}") + public ResponseEntity>> getReviewsByProduct( + @PathVariable Long productNo, @RequestParam(name = "sortBy", defaultValue = "reviewNo", required = false) String sortBy, Pageable pageable) { return ApiResponse.toResponseEntity(ReviewBusinessCode.SUCCESS_GET_REVIEWS, reviewService.getReviewsByProduct(productNo, sortBy, pageable)); } + @GetMapping("/member/{memberNo}") + public ResponseEntity>> getReviewsByMember( + @PathVariable Long memberNo, + @RequestParam(name = "sortBy", defaultValue = "reviewNo", required = false) String sortBy, + Pageable pageable) { + + return ApiResponse.toResponseEntity(ReviewBusinessCode.SUCCESS_GET_REVIEWS, reviewService.getReviewsByMember(memberNo, sortBy, pageable)); + } + @GetMapping("/{reviewNo}") - public ResponseEntity> getReview(@PathVariable Long reviewNo) { + public ResponseEntity> getReview(@PathVariable Long reviewNo) { return ApiResponse.toResponseEntity(ReviewBusinessCode.SUCCESS_GET_REVIEW, reviewService.getReview(reviewNo)); } - @PostMapping("") - public ResponseEntity> createReview(@RequestBody ReviewDto reviewDto) { - reviewService.createReview(reviewDto); + @PostMapping + public ResponseEntity> createReview( + @Valid @RequestPart ReviewRequestDto review, + @RequestPart(required = false) List files, + @RequestHeader("Id") String id) { + reviewService.createReview(review, files, id); return ApiResponse.toResponseEntity(ReviewBusinessCode.SUCCESS_CREATE_REVIEW); } - @PatchMapping("") - public ResponseEntity> updateReview(@RequestBody ReviewDto reviewDto) { - reviewService.updateReview(reviewDto); + @PatchMapping + public ResponseEntity> updateReview( + @Valid @RequestPart ReviewRequestDto review, + @RequestPart(required = false) List files, + @RequestHeader("Id") String id) { + reviewService.updateReview(review, files, id); return ApiResponse.toResponseEntity(ReviewBusinessCode.SUCCESS_UPDATE_REVIEW); } @DeleteMapping("/{reviewNo}") - public ResponseEntity> deleteReview(@PathVariable Long reviewNo) { - reviewService.deleteReview(reviewNo); + public ResponseEntity> deleteReview( + @PathVariable Long reviewNo, + @RequestHeader("Id") String id) { + reviewService.deleteReview(reviewNo, id); return ApiResponse.toResponseEntity(ReviewBusinessCode.SUCCESS_DELETE_REVIEW); } diff --git a/module-api/src/main/java/com/kernel360/review/dto/ReviewDto.java b/module-api/src/main/java/com/kernel360/review/dto/ReviewDto.java deleted file mode 100644 index 06579d25..00000000 --- a/module-api/src/main/java/com/kernel360/review/dto/ReviewDto.java +++ /dev/null @@ -1,75 +0,0 @@ -package com.kernel360.review.dto; - -import com.kernel360.member.dto.MemberDto; -import com.kernel360.product.dto.ProductDto; -import com.kernel360.review.entity.Review; - -import java.math.BigDecimal; -import java.time.LocalDate; - -/** - * DTO for {@link com.kernel360.review.dto.ReviewDto} - */ -public record ReviewDto(Long reviewNo, - ProductDto productDto, - MemberDto memberDto, - BigDecimal starRating, - String title, - String contents, - LocalDate createdAt, - String createdBy, - LocalDate modifiedAt, - String modifiedBy) { - - public static ReviewDto of( - Long reviewNo, - ProductDto productDto, - MemberDto memberDto, - BigDecimal starRating, - String title, - String contents, - LocalDate createdAt, - String createdBy, - LocalDate modifiedAt, - String modifiedBy - ) { - return new ReviewDto( - reviewNo, - productDto, - memberDto, - starRating, - title, - contents, - createdAt, - createdBy, - modifiedAt, - modifiedBy - ); - } - - public static ReviewDto from(Review entity) { - return ReviewDto.of( - entity.getReviewNo(), - ProductDto.from(entity.getProduct()), - MemberDto.from(entity.getMember()), - entity.getStarRating(), - entity.getTitle(), - entity.getContents(), - entity.getCreatedAt(), - entity.getCreatedBy(), - entity.getModifiedAt(), - entity.getModifiedBy() - ); - } - - public Review toEntity() { - return Review.of( - reviewNo, - productDto.toEntity(), - memberDto.toEntity(), - starRating, - title, - contents - ); - } -} \ No newline at end of file diff --git a/module-api/src/main/java/com/kernel360/review/dto/ReviewRequestDto.java b/module-api/src/main/java/com/kernel360/review/dto/ReviewRequestDto.java new file mode 100644 index 00000000..824a03c2 --- /dev/null +++ b/module-api/src/main/java/com/kernel360/review/dto/ReviewRequestDto.java @@ -0,0 +1,77 @@ +package com.kernel360.review.dto; + +import com.kernel360.code.common.ValidationMessage; +import com.kernel360.global.annotation.BadWordFilter; +import com.kernel360.member.entity.Member; +import com.kernel360.product.entity.Product; +import com.kernel360.review.entity.Review; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +/** + * DTO for {@link ReviewRequestDto} + */ +public record ReviewRequestDto(Long reviewNo, + Long productNo, + Long memberNo, + BigDecimal starRating, + @BadWordFilter(message = ValidationMessage.INVALID_WORD_PARAMETER) String title, + @BadWordFilter(message = ValidationMessage.INVALID_WORD_PARAMETER) String contents, + LocalDateTime createdAt, + String createdBy, + LocalDateTime modifiedAt, + String modifiedBy, + List files) { + + public static ReviewRequestDto of( + Long reviewNo, + Long productNo, + Long memberNo, + BigDecimal starRating, + String title, + String contents, + LocalDateTime createdAt, + String createdBy, + LocalDateTime modifiedAt, + String modifiedBy, + List files + ) { + return new ReviewRequestDto( + reviewNo, + productNo, + memberNo, + starRating, + title, + contents, + createdAt, + createdBy, + modifiedAt, + modifiedBy, + files + ); + } + + public Review toEntity() { + return Review.of( + reviewNo, + Product.of(productNo), + Member.of(memberNo), + starRating, + title, + contents, + true + ); + } + + public Review toEntityForUpdate() { + return Review.of( + reviewNo, + starRating, + title, + contents, + true + ); + } +} \ No newline at end of file diff --git a/module-api/src/main/java/com/kernel360/review/dto/ReviewResponseDto.java b/module-api/src/main/java/com/kernel360/review/dto/ReviewResponseDto.java new file mode 100644 index 00000000..64852752 --- /dev/null +++ b/module-api/src/main/java/com/kernel360/review/dto/ReviewResponseDto.java @@ -0,0 +1,51 @@ +package com.kernel360.review.dto; + +import com.kernel360.member.dto.MemberDto; +import com.kernel360.product.dto.ProductDetailDto; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +/** + * DTO for {@link com.kernel360.review.dto.ReviewResponseDto} + */ +public record ReviewResponseDto(Long reviewNo, + ProductDetailDto product, + MemberDto member, + BigDecimal starRating, + String title, + String contents, + LocalDateTime createdAt, + String createdBy, + LocalDateTime modifiedAt, + String modifiedBy, + List files) { + + public static ReviewResponseDto of( + Long reviewNo, + ProductDetailDto productDto, + MemberDto memberDto, + BigDecimal starRating, + String title, + String contents, + LocalDateTime createdAt, + String createdBy, + LocalDateTime modifiedAt, + String modifiedBy, + List files + ) { + return new ReviewResponseDto( + reviewNo, + productDto, + memberDto, + starRating, + title, + contents, + createdAt, + createdBy, + modifiedAt, + modifiedBy, + files + ); + } +} \ No newline at end of file diff --git a/module-api/src/main/java/com/kernel360/review/dto/ReviewSearchDto.java b/module-api/src/main/java/com/kernel360/review/dto/ReviewSearchDto.java index 4b7b52cc..b6ed0107 100644 --- a/module-api/src/main/java/com/kernel360/review/dto/ReviewSearchDto.java +++ b/module-api/src/main/java/com/kernel360/review/dto/ReviewSearchDto.java @@ -13,7 +13,6 @@ public static ReviewSearchDto byProductNo(Long productNo, String sortBy) { return ReviewSearchDto.of(productNo, null, sortBy); } - // TODO: 추후 mypage 리뷰 관리에서 사용 예정 public static ReviewSearchDto byMemberNo(Long memberNo, String sortBy) { return ReviewSearchDto.of(null, memberNo, sortBy); } diff --git a/module-api/src/main/java/com/kernel360/review/dto/ReviewSearchResult.java b/module-api/src/main/java/com/kernel360/review/dto/ReviewSearchResult.java new file mode 100644 index 00000000..ab1191a8 --- /dev/null +++ b/module-api/src/main/java/com/kernel360/review/dto/ReviewSearchResult.java @@ -0,0 +1,81 @@ +package com.kernel360.review.dto; + +import com.kernel360.member.dto.MemberDto; +import com.kernel360.product.dto.ProductDetailDto; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +/** + * DTO for {@link ReviewSearchResult} + */ +@Getter +@NoArgsConstructor +public class ReviewSearchResult { + // review + Long reviewNo; + BigDecimal starRating; + String title; + String contents; + LocalDateTime createdAt; + String createdBy; + LocalDateTime modifiedAt; + String modifiedBy; + + // member + Long memberNo; + String id; + int age; + int gender; + + // product + Long productNo; + String productName; + String companyName; + String imageSource; + String upperItem; + String item; + + // file + String fileUrls; + + public static ReviewResponseDto toDto(ReviewSearchResult response) { + List fileUrls = new ArrayList<>(); + + if (Objects.nonNull(response.getFileUrls())) { + fileUrls = Arrays.stream(response.getFileUrls().split("\\|")).toList(); + } + + return ReviewResponseDto.of( + response.getReviewNo(), + ProductDetailDto.of( + response.getProductNo(), + response.getProductName(), + response.getImageSource(), + response.getCompanyName(), + response.getUpperItem(), + response.getItem() + ), + MemberDto.of( + response.getMemberNo(), + response.getId(), + response.getAge(), + response.getGender() + ), + response.getStarRating(), + response.getTitle(), + response.getContents(), + response.getCreatedAt(), + response.getCreatedBy(), + response.getModifiedAt(), + response.getModifiedBy(), + fileUrls + ); + } +} \ No newline at end of file diff --git a/module-api/src/main/java/com/kernel360/review/repository/ReviewRepository.java b/module-api/src/main/java/com/kernel360/review/repository/ReviewRepository.java index 70be6d3d..ed4f23a3 100644 --- a/module-api/src/main/java/com/kernel360/review/repository/ReviewRepository.java +++ b/module-api/src/main/java/com/kernel360/review/repository/ReviewRepository.java @@ -1,4 +1,9 @@ package com.kernel360.review.repository; +import com.kernel360.review.entity.Review; + +import java.util.Optional; + public interface ReviewRepository extends ReviewRepositoryJpa, ReviewRepositoryDsl { + Optional findByReviewNoAndIsVisibleTrue(Long reviewNo); } diff --git a/module-api/src/main/java/com/kernel360/review/repository/ReviewRepositoryDsl.java b/module-api/src/main/java/com/kernel360/review/repository/ReviewRepositoryDsl.java index 45cbc0dd..e98fc962 100644 --- a/module-api/src/main/java/com/kernel360/review/repository/ReviewRepositoryDsl.java +++ b/module-api/src/main/java/com/kernel360/review/repository/ReviewRepositoryDsl.java @@ -1,10 +1,12 @@ package com.kernel360.review.repository; import com.kernel360.review.dto.ReviewSearchDto; -import com.kernel360.review.entity.Review; +import com.kernel360.review.dto.ReviewSearchResult; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; public interface ReviewRepositoryDsl { - Page findAllByCondition(ReviewSearchDto condition, Pageable pageable); + Page findAllByCondition(ReviewSearchDto condition, Pageable pageable); + + ReviewSearchResult findByReviewNo(Long reviewNo); } diff --git a/module-api/src/main/java/com/kernel360/review/repository/ReviewRepositoryImpl.java b/module-api/src/main/java/com/kernel360/review/repository/ReviewRepositoryImpl.java index 1794fc7f..de3eb5d7 100644 --- a/module-api/src/main/java/com/kernel360/review/repository/ReviewRepositoryImpl.java +++ b/module-api/src/main/java/com/kernel360/review/repository/ReviewRepositoryImpl.java @@ -1,18 +1,25 @@ package com.kernel360.review.repository; +import com.kernel360.file.entity.FileReferType; import com.kernel360.review.dto.ReviewSearchDto; -import com.kernel360.review.entity.Review; +import com.kernel360.review.dto.ReviewSearchResult; import com.querydsl.core.types.OrderSpecifier; +import com.querydsl.core.types.Projections; import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.jpa.impl.JPAQuery; import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; +import org.springframework.data.support.PageableExecutionUtils; import java.util.List; +import static com.kernel360.file.entity.QFile.file; +import static com.kernel360.member.entity.QMember.member; +import static com.kernel360.product.entity.QProduct.product; import static com.kernel360.review.entity.QReview.review; +import static com.querydsl.core.types.dsl.Expressions.stringTemplate; @RequiredArgsConstructor public class ReviewRepositoryImpl implements ReviewRepositoryDsl { @@ -20,27 +27,75 @@ public class ReviewRepositoryImpl implements ReviewRepositoryDsl { private final JPAQueryFactory queryFactory; @Override - public Page findAllByCondition(ReviewSearchDto condition, Pageable pageable) { - List reviews = queryFactory - .select(review) - .from(review) - .where( - productNoEq(condition.productNo()), - memberNoEq(condition.memberNo())) - .offset(pageable.getOffset()) - .limit(pageable.getPageSize()) - .orderBy(sort(condition.sortBy())) - .fetch(); + public Page findAllByCondition(ReviewSearchDto condition, Pageable pageable) { + List reviews = + getJoinedResults() + .where( + productNoEq(condition.productNo()), + memberNoEq(condition.memberNo()) + ) + .groupBy(review.reviewNo, member.memberNo, member.id, member.age, member.gender, product.productNo) + .orderBy(sort(condition.sortBy())) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .fetch(); - Long totalCount = queryFactory + JPAQuery totalCountQuery = queryFactory .select(review.count()) .from(review) .where( + review.isVisible.eq(true), productNoEq(condition.productNo()), - memberNoEq(condition.memberNo())) + memberNoEq(condition.memberNo()) + ); + + return PageableExecutionUtils.getPage(reviews, pageable, totalCountQuery::fetchOne); + } + + @Override + public ReviewSearchResult findByReviewNo(Long reviewNo) { + return getJoinedResults() + .where( + review.isVisible.eq(true), + review.reviewNo.eq(reviewNo) + ) + .groupBy(review.reviewNo, member.memberNo, member.id, member.age, member.gender, product.productNo) .fetchOne(); + } - return new PageImpl<>(reviews, pageable, totalCount); + private JPAQuery getJoinedResults() { + return queryFactory + .select(Projections.fields(ReviewSearchResult.class, + review.reviewNo, + review.starRating, + review.title, + review.contents, + review.createdAt, + review.createdBy, + review.modifiedAt, + review.modifiedBy, + member.memberNo, + stringTemplate("SUBSTRING({0}, 1, 2) || REPEAT('*', LENGTH({0}) - 2)", member.id).as("id"), + member.age, + member.gender, + product.productNo, + product.productName, + product.companyName, + product.imageSource, + product.upperItem, + product.item, + stringTemplate("STRING_AGG({0}, '|')", file.fileUrl).as("fileUrls") + )) + .from(review) + .leftJoin(file) + .on( + file.referenceType.eq(FileReferType.REVIEW.getCode()), + file.referenceNo.eq(review.reviewNo) + ) + .join(member) + .on(review.member.memberNo.eq(member.memberNo)) + .join(product) + .on(review.product.productNo.eq(product.productNo)); } private BooleanExpression productNoEq(Long productNo) { diff --git a/module-api/src/main/java/com/kernel360/review/service/ReviewService.java b/module-api/src/main/java/com/kernel360/review/service/ReviewService.java index 2be8b398..5b3659fd 100644 --- a/module-api/src/main/java/com/kernel360/review/service/ReviewService.java +++ b/module-api/src/main/java/com/kernel360/review/service/ReviewService.java @@ -1,20 +1,32 @@ package com.kernel360.review.service; import com.kernel360.exception.BusinessException; +import com.kernel360.file.entity.File; +import com.kernel360.file.entity.FileReferType; +import com.kernel360.file.repository.FileRepository; +import com.kernel360.member.repository.MemberRepository; import com.kernel360.review.code.ReviewErrorCode; -import com.kernel360.review.dto.ReviewDto; +import com.kernel360.review.dto.ReviewRequestDto; +import com.kernel360.review.dto.ReviewResponseDto; import com.kernel360.review.dto.ReviewSearchDto; +import com.kernel360.review.dto.ReviewSearchResult; import com.kernel360.review.entity.Review; import com.kernel360.review.repository.ReviewRepository; +import com.kernel360.utils.file.FileUtils; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; import java.math.BigDecimal; +import java.util.List; +import java.util.Objects; +import java.util.Optional; @Slf4j @Service @@ -22,62 +34,150 @@ public class ReviewService { private final ReviewRepository reviewRepository; + private final MemberRepository memberRepository; + private final FileRepository fileRepository; + private final FileUtils fileUtils; + + @Value("${aws.s3.bucket.url}") + private String bucketUrl; private static final double MAX_STAR_RATING = 5.0; + private static final String REVIEW_DOMAIN = FileReferType.REVIEW.getDomain(); + private static final String REVIEW_CODE = FileReferType.REVIEW.getCode(); @Transactional(readOnly = true) - public Page getReviewsByProduct(Long productNo, String sortBy, Pageable pageable) { - log.info("제품 리뷰 목록 조회 -> product_no {}", productNo); - // TODO: 유효하지 않은 productNo 인 경우, custom error 보내기 + public Page getReviewsByProduct(Long productNo, String sortBy, Pageable pageable) { + log.info("제품별 리뷰 목록 조회 -> product_no {}", productNo); return reviewRepository.findAllByCondition(ReviewSearchDto.byProductNo(productNo, sortBy), pageable) - .map(ReviewDto::from); + .map(ReviewSearchResult::toDto); + } + + @Transactional(readOnly = true) + public Page getReviewsByMember(Long memberNo, String sortBy, Pageable pageable) { + log.info("멤버별 제품 리뷰 목록 조회 -> member_no {}", memberNo); + + return reviewRepository.findAllByCondition(ReviewSearchDto.byMemberNo(memberNo, sortBy), pageable) + .map(ReviewSearchResult::toDto); } @Transactional(readOnly = true) - public ReviewDto getReview(Long reviewNo) { - log.info("리뷰 단건 조회 -> review_no {}", reviewNo); + public ReviewResponseDto getReview(Long reviewNo) { + log.info("제품 리뷰 단건 조회 -> review_no {}", reviewNo); + ReviewSearchResult review = reviewRepository.findByReviewNo(reviewNo); + + if (Objects.isNull(review)) { + throw new BusinessException(ReviewErrorCode.NOT_FOUND_REVIEW); + } - return ReviewDto.from(reviewRepository.findByReviewNo(reviewNo)); + return ReviewSearchResult.toDto(review); } @Transactional - public Review createReview(ReviewDto reviewDto) { - isValidStarRating(reviewDto.starRating()); + public Review createReview(ReviewRequestDto reviewRequestDto, List files, String id) { + isValidMemberInfo(id, reviewRequestDto.memberNo()); + isValidStarRating(reviewRequestDto.starRating()); Review review; try { - review = reviewRepository.saveAndFlush(reviewDto.toEntity()); + review = reviewRepository.saveAndFlush(reviewRequestDto.toEntity()); + log.info("제품 리뷰 등록 -> review_no {}", review.getReviewNo()); + + if (Objects.nonNull(files)) { + uploadFiles(files, reviewRequestDto.productNo(), review.getReviewNo()); + } } catch (DataIntegrityViolationException e) { - throw new BusinessException(ReviewErrorCode.INVALID_REVIEW_WRITE_REQUEST); + String msg = e.getMessage().toString(); + + if (msg.contains("review_product_no_fkey")) { + throw new BusinessException(ReviewErrorCode.NOT_FOUND_PRODUCT_FOR_REVIEW_CREATION); + } + + if (msg.contains("review_member_no_fkey")) { + throw new BusinessException(ReviewErrorCode.NOT_FOUND_MEMBER_FOR_REVIEW_CREATION); + } + + throw new BusinessException(ReviewErrorCode.DUPLICATE_REVIEW_EXISTS); } - log.info("리뷰 등록 -> review_no {}", review.getReviewNo()); return review; } + private void uploadFiles(List files, Long productNo, Long reviewNo) { + files.stream().forEach(file -> { + String path = String.join("/", REVIEW_DOMAIN, productNo.toString()); + String fileKey = fileUtils.upload(path, file); + String fileUrl = String.join("/", bucketUrl, fileKey); + + File fileInfo = fileRepository.save(File.of(null, file.getOriginalFilename(), fileKey, fileUrl, REVIEW_CODE, reviewNo)); + log.info("제품 리뷰 파일 등록 -> file_no {}", fileInfo.getFileNo()); + }); + } @Transactional - public void updateReview(ReviewDto reviewDto) { - isValidStarRating(reviewDto.starRating()); + public void updateReview(ReviewRequestDto reviewRequestDto, List files, String id) { + Review review = isVisibleReview(reviewRequestDto.reviewNo()); + isValidMemberInfo(id, review.getMember().getMemberNo()); + isValidStarRating(reviewRequestDto.starRating()); + + long productNo = review.getProduct().getProductNo(); try { - reviewRepository.saveAndFlush(reviewDto.toEntity()); + reviewRepository.saveAndFlush(reviewRequestDto.toEntityForUpdate()); + log.info("제품 리뷰 수정 -> review_no {}", reviewRequestDto.reviewNo()); + + fileRepository.findByReferenceTypeAndReferenceNo(REVIEW_CODE, reviewRequestDto.reviewNo()) + .stream() + .forEach(file -> { + if (!reviewRequestDto.files().contains(file.getFileUrl())) { + fileUtils.delete(file.getFileKey()); + fileRepository.deleteById(file.getFileNo()); + log.info("제품 리뷰 파일 삭제 -> file_no {}", file.getFileNo()); + } + }); + + if (Objects.nonNull(files)) { + uploadFiles(files, productNo, reviewRequestDto.reviewNo()); + } } catch (DataIntegrityViolationException e) { - throw new BusinessException(ReviewErrorCode.INVALID_REVIEW_WRITE_REQUEST); + throw new BusinessException(ReviewErrorCode.DUPLICATE_REVIEW_EXISTS); } - - log.info("리뷰 수정 -> review_no {}", reviewDto.reviewNo()); } @Transactional - public void deleteReview(Long reviewNo) { + public void deleteReview(Long reviewNo, String id) { + Review review = isVisibleReview(reviewNo); + isValidMemberInfo(id, review.getMember().getMemberNo()); + reviewRepository.deleteById(reviewNo); - log.info("리뷰 삭제 -> review_no {}", reviewNo); + log.info("제품 리뷰 삭제 -> review_no {}", reviewNo); + + fileRepository.findByReferenceTypeAndReferenceNo(REVIEW_CODE, reviewNo) + .stream() + .forEach(file -> { + fileUtils.delete(file.getFileKey()); + log.info("제품 리뷰 파일 삭제 -> file_no {}", file.getFileNo()); + }); + fileRepository.deleteByReferenceTypeAndReferenceNo(REVIEW_CODE, reviewNo); + } + + private Review isVisibleReview(Long reviewNo) { + Optional review = reviewRepository.findByReviewNoAndIsVisibleTrue(reviewNo); + + if (review.isEmpty()) { + throw new BusinessException(ReviewErrorCode.NOT_FOUND_REVIEW); + } + + return review.get(); + } + + private void isValidMemberInfo(String id, Long memberNo) { + memberRepository.findOneByIdAndMemberNo(id, memberNo) + .orElseThrow(() -> new BusinessException(ReviewErrorCode.MISMATCHED_MEMBER_NO_AND_ID)); } - private static void isValidStarRating(BigDecimal starRating) { + private void isValidStarRating(BigDecimal starRating) { if (BigDecimal.ZERO.compareTo(starRating) > 0) { throw new BusinessException(ReviewErrorCode.INVALID_STAR_RATING_VALUE); } diff --git a/module-api/src/main/java/com/kernel360/washzone/dto/WashZoneDto.java b/module-api/src/main/java/com/kernel360/washzone/dto/WashZoneDto.java index a17a03d5..27be880e 100644 --- a/module-api/src/main/java/com/kernel360/washzone/dto/WashZoneDto.java +++ b/module-api/src/main/java/com/kernel360/washzone/dto/WashZoneDto.java @@ -7,6 +7,7 @@ * DTO for {@link com.kernel360.washzone.entity.WashZone} */ public record WashZoneDto( + Long washZoneNo, String name, String address, Double latitude, @@ -23,6 +24,7 @@ public static WashZoneDto of ( String remarks ){ return new WashZoneDto( + null, name, address, latitude, @@ -52,4 +54,21 @@ public WashZone toEntity(){ this.remarks ); } + + /** find review **/ + public static WashZoneDto of( + Long washZoneNo, + String name, + String address, + String type + ){ + return new WashZoneDto( + washZoneNo, + name, + address, + null, + null, + type, + null); + } } \ No newline at end of file diff --git a/module-api/src/main/java/com/kernel360/washzonereview/code/WashzoneReviewBusinessCode.java b/module-api/src/main/java/com/kernel360/washzonereview/code/WashzoneReviewBusinessCode.java new file mode 100644 index 00000000..2fc38c06 --- /dev/null +++ b/module-api/src/main/java/com/kernel360/washzonereview/code/WashzoneReviewBusinessCode.java @@ -0,0 +1,33 @@ +package com.kernel360.washzonereview.code; + +import com.kernel360.code.BusinessCode; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@RequiredArgsConstructor +public enum WashzoneReviewBusinessCode implements BusinessCode { + SUCCESS_GET_WASHZONE_REVIEWS(HttpStatus.OK.value(), "BWZRV001", "세차장 리뷰 목록 조회 성공"), + SUCCESS_GET_WASHZONE_REVIEW(HttpStatus.OK.value(), "BWZRV002", "세차장 리뷰 단건 조회 성공"), + SUCCESS_CREATE_WASHZONE_REVIEW(HttpStatus.OK.value(), "BWZRV003", "세차장 리뷰 등록 성공"), + SUCCESS_UPDATE_WASHZONE_REVIEW(HttpStatus.OK.value(), "BWZRV004", "세차장 리뷰 수정 성공"), + SUCCESS_DELETE_WASHZONE_REVIEW(HttpStatus.OK.value(), "BWZRV005", "세차장 리뷰 삭제 성공"); + + private final int status; + private final String code; + private final String message; + + @Override + public int getStatus() { + return status; + } + + @Override + public String getCode() { + return code; + } + + @Override + public String getMessage() { + return message; + } +} diff --git a/module-api/src/main/java/com/kernel360/washzonereview/code/WashzoneReviewErrorCode.java b/module-api/src/main/java/com/kernel360/washzonereview/code/WashzoneReviewErrorCode.java new file mode 100644 index 00000000..30519b44 --- /dev/null +++ b/module-api/src/main/java/com/kernel360/washzonereview/code/WashzoneReviewErrorCode.java @@ -0,0 +1,34 @@ +package com.kernel360.washzonereview.code; + +import com.kernel360.code.ErrorCode; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@RequiredArgsConstructor +public enum WashzoneReviewErrorCode implements ErrorCode { + INVALID_STAR_RATING_VALUE(HttpStatus.BAD_REQUEST.value(), "EWZRV001", "유효하지 않은 별점입니다."), + DUPLICATE_WASHZONE_REVIEW_EXISTS(HttpStatus.BAD_REQUEST.value(), "EWZRV002", "중복된 세차장 리뷰가 존재합니다."), + NOT_FOUND_WASHZONE_REVIEW(HttpStatus.BAD_REQUEST.value(), "EWZRV003", "세차장 리뷰가 존재하지 않습니다."), + NOT_FOUND_WASHZONE_FOR_WASHZONE_REVIEW_CREATION(HttpStatus.BAD_REQUEST.value(), "EWZRV004", "세차장이 존재하지 않습니다."), + NOT_FOUND_MEMBER_FOR_WASHZONE_REVIEW_CREATION(HttpStatus.BAD_REQUEST.value(), "EWZRV005", "회원이 존재하지 않습니다."), + MISMATCHED_MEMBER_NO_AND_ID(HttpStatus.BAD_REQUEST.value(), "ERV006", "회원 번호와 아이디가 일치하지 않습니다."); + + private final int status; + private final String code; + private final String message; + + @Override + public int getStatus() { + return status; + } + + @Override + public String getCode() { + return code; + } + + @Override + public String getMessage() { + return message; + } +} diff --git a/module-api/src/main/java/com/kernel360/washzonereview/controller/WashzoneReviewController.java b/module-api/src/main/java/com/kernel360/washzonereview/controller/WashzoneReviewController.java new file mode 100644 index 00000000..27756964 --- /dev/null +++ b/module-api/src/main/java/com/kernel360/washzonereview/controller/WashzoneReviewController.java @@ -0,0 +1,77 @@ +package com.kernel360.washzonereview.controller; + +import com.kernel360.response.ApiResponse; +import com.kernel360.washzonereview.code.WashzoneReviewBusinessCode; +import com.kernel360.washzonereview.dto.WashzoneReviewRequestDto; +import com.kernel360.washzonereview.dto.WashzoneReviewResponseDto; +import com.kernel360.washzonereview.service.WashzoneReviewService; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/reviews-washzone") +public class WashzoneReviewController { + + private final WashzoneReviewService washzoneReviewService; + + @GetMapping("/washzone/{washzoneNo}") + public ResponseEntity>> getWashzoneReviewsByWashzone( + @PathVariable Long washzoneNo, + @RequestParam(name = "sortBy", defaultValue = "washzoneReviewNo", required = false) String sortBy, + Pageable pageable) { + + return ApiResponse.toResponseEntity(WashzoneReviewBusinessCode.SUCCESS_GET_WASHZONE_REVIEWS, washzoneReviewService.getWashzoneReviewsByWashzone(washzoneNo, sortBy, pageable)); + } + + @GetMapping("/member/{memberNo}") + public ResponseEntity>> getWashzoneReviewsByMember( + @PathVariable Long memberNo, + @RequestParam(name = "sortBy", defaultValue = "washzoneReviewNo", required = false) String sortBy, + Pageable pageable) { + + return ApiResponse.toResponseEntity(WashzoneReviewBusinessCode.SUCCESS_GET_WASHZONE_REVIEWS, washzoneReviewService.getWashzoneReviewsByMember(memberNo, sortBy, pageable)); + } + + @GetMapping("/{washzoneReviewNo}") + public ResponseEntity> getWashzoneReview(@PathVariable Long washzoneReviewNo) { + + return ApiResponse.toResponseEntity(WashzoneReviewBusinessCode.SUCCESS_GET_WASHZONE_REVIEW, washzoneReviewService.getWashzoneReview(washzoneReviewNo)); + } + + @PostMapping + public ResponseEntity> createWashzoneReview( + @Valid @RequestPart WashzoneReviewRequestDto washzoneReview, + @RequestPart(required = false) List files, + @RequestHeader("Id") String id) { + washzoneReviewService.createWashzoneReview(washzoneReview, files, id); + + return ApiResponse.toResponseEntity(WashzoneReviewBusinessCode.SUCCESS_CREATE_WASHZONE_REVIEW); + } + + @PatchMapping + public ResponseEntity> updateWashzoneReview( + @Valid @RequestPart WashzoneReviewRequestDto washzoneReview, + @RequestPart(required = false) List files, + @RequestHeader("Id") String id) { + washzoneReviewService.updateWashzoneReview(washzoneReview, files, id); + + return ApiResponse.toResponseEntity(WashzoneReviewBusinessCode.SUCCESS_UPDATE_WASHZONE_REVIEW); + } + + @DeleteMapping("/{washzoneReviewNo}") + public ResponseEntity> deleteWashzoneReview( + @PathVariable Long washzoneReviewNo, + @RequestHeader("Id") String id) { + washzoneReviewService.deleteWashzoneReview(washzoneReviewNo, id); + + return ApiResponse.toResponseEntity(WashzoneReviewBusinessCode.SUCCESS_DELETE_WASHZONE_REVIEW); + } +} diff --git a/module-api/src/main/java/com/kernel360/washzonereview/dto/WashzoneReviewRequestDto.java b/module-api/src/main/java/com/kernel360/washzonereview/dto/WashzoneReviewRequestDto.java new file mode 100644 index 00000000..53651c05 --- /dev/null +++ b/module-api/src/main/java/com/kernel360/washzonereview/dto/WashzoneReviewRequestDto.java @@ -0,0 +1,77 @@ +package com.kernel360.washzonereview.dto; + +import com.kernel360.code.common.ValidationMessage; +import com.kernel360.global.annotation.BadWordFilter; +import com.kernel360.member.entity.Member; +import com.kernel360.washzone.entity.WashZone; +import com.kernel360.washzonereview.entity.WashzoneReview; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +/** + * DTO for {@link WashzoneReviewRequestDto} + */ +public record WashzoneReviewRequestDto(Long washzoneReviewNo, + Long washzoneNo, + Long memberNo, + BigDecimal starRating, + @BadWordFilter(message = ValidationMessage.INVALID_WORD_PARAMETER) String title, + @BadWordFilter(message = ValidationMessage.INVALID_WORD_PARAMETER) String contents, + LocalDateTime createdAt, + String createdBy, + LocalDateTime modifiedAt, + String modifiedBy, + List files) { + + public static WashzoneReviewRequestDto of( + Long washzoneReviewNo, + Long washzoneNo, + Long memberNo, + BigDecimal starRating, + String title, + String contents, + LocalDateTime createdAt, + String createdBy, + LocalDateTime modifiedAt, + String modifiedBy, + List files + ) { + return new WashzoneReviewRequestDto( + washzoneReviewNo, + washzoneNo, + memberNo, + starRating, + title, + contents, + createdAt, + createdBy, + modifiedAt, + modifiedBy, + files + ); + } + + public WashzoneReview toEntity() { + return WashzoneReview.of( + washzoneReviewNo, + WashZone.of(washzoneNo), + Member.of(memberNo), + starRating, + title, + contents, + true + ); + } + + public WashzoneReview toEntityForUpdate() { + return WashzoneReview.of( + washzoneReviewNo, + starRating, + title, + contents, + true + ); + } +} \ No newline at end of file diff --git a/module-api/src/main/java/com/kernel360/washzonereview/dto/WashzoneReviewResponseDto.java b/module-api/src/main/java/com/kernel360/washzonereview/dto/WashzoneReviewResponseDto.java new file mode 100644 index 00000000..9706621b --- /dev/null +++ b/module-api/src/main/java/com/kernel360/washzonereview/dto/WashzoneReviewResponseDto.java @@ -0,0 +1,52 @@ +package com.kernel360.washzonereview.dto; + +import com.kernel360.member.dto.MemberDto; +import com.kernel360.washzone.dto.WashZoneDto; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +/** + * DTO for {@link WashzoneReviewResponseDto} + */ +public record WashzoneReviewResponseDto(Long washzoneReviewNo, + WashZoneDto washzone, + MemberDto member, + BigDecimal starRating, + String title, + String contents, + LocalDateTime createdAt, + String createdBy, + LocalDateTime modifiedAt, + String modifiedBy, + List files) { + + public static WashzoneReviewResponseDto of( + Long washzoneReviewNo, + WashZoneDto washzoneDto, + MemberDto memberDto, + BigDecimal starRating, + String title, + String contents, + LocalDateTime createdAt, + String createdBy, + LocalDateTime modifiedAt, + String modifiedBy, + List files + ) { + return new WashzoneReviewResponseDto( + washzoneReviewNo, + washzoneDto, + memberDto, + starRating, + title, + contents, + createdAt, + createdBy, + modifiedAt, + modifiedBy, + files + ); + } +} \ No newline at end of file diff --git a/module-api/src/main/java/com/kernel360/washzonereview/dto/WashzoneReviewSearchDto.java b/module-api/src/main/java/com/kernel360/washzonereview/dto/WashzoneReviewSearchDto.java new file mode 100644 index 00000000..388ebcc2 --- /dev/null +++ b/module-api/src/main/java/com/kernel360/washzonereview/dto/WashzoneReviewSearchDto.java @@ -0,0 +1,19 @@ +package com.kernel360.washzonereview.dto; + +public record WashzoneReviewSearchDto( + Long washzoneNo, + Long memberNo, + String sortBy +) { + public static WashzoneReviewSearchDto of(Long washzoneNo, Long memberNo, String sortBy) { + return new WashzoneReviewSearchDto(washzoneNo, memberNo, sortBy); + } + + public static WashzoneReviewSearchDto byWashzoneNo(Long washzoneNo, String sortBy) { + return WashzoneReviewSearchDto.of(washzoneNo, null, sortBy); + } + + public static WashzoneReviewSearchDto byMemberNo(Long memberNo, String sortBy) { + return WashzoneReviewSearchDto.of(null, memberNo, sortBy); + } +} diff --git a/module-api/src/main/java/com/kernel360/washzonereview/dto/WashzoneReviewSearchResult.java b/module-api/src/main/java/com/kernel360/washzonereview/dto/WashzoneReviewSearchResult.java new file mode 100644 index 00000000..7873ef41 --- /dev/null +++ b/module-api/src/main/java/com/kernel360/washzonereview/dto/WashzoneReviewSearchResult.java @@ -0,0 +1,77 @@ +package com.kernel360.washzonereview.dto; + +import com.kernel360.member.dto.MemberDto; +import com.kernel360.washzone.dto.WashZoneDto; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +/** + * DTO for {@link WashzoneReviewSearchResult} + */ +@Getter +@NoArgsConstructor +public class WashzoneReviewSearchResult { + // washzone review + Long washzoneReviewNo; + BigDecimal starRating; + String title; + String contents; + LocalDateTime createdAt; + String createdBy; + LocalDateTime modifiedAt; + String modifiedBy; + + // member + Long memberNo; + String id; + int age; + int gender; + + // washzone + Long washzoneNo; + String name; + String address; + String type; + + // file + String fileUrls; + + public static WashzoneReviewResponseDto toDto(WashzoneReviewSearchResult response) { + List fileUrls = new ArrayList<>(); + + if (Objects.nonNull(response.getFileUrls())) { + fileUrls = Arrays.stream(response.getFileUrls().split("\\|")).toList(); + } + + return WashzoneReviewResponseDto.of( + response.getWashzoneReviewNo(), + WashZoneDto.of( + response.getWashzoneNo(), + response.getName(), + response.getAddress(), + response.getType() + ), + MemberDto.of( + response.getMemberNo(), + response.getId(), + response.getAge(), + response.getGender() + ), + response.getStarRating(), + response.getTitle(), + response.getContents(), + response.getCreatedAt(), + response.getCreatedBy(), + response.getModifiedAt(), + response.getModifiedBy(), + fileUrls + ); + } +} \ No newline at end of file diff --git a/module-api/src/main/java/com/kernel360/washzonereview/repository/WashzoneReviewRepository.java b/module-api/src/main/java/com/kernel360/washzonereview/repository/WashzoneReviewRepository.java new file mode 100644 index 00000000..671b2514 --- /dev/null +++ b/module-api/src/main/java/com/kernel360/washzonereview/repository/WashzoneReviewRepository.java @@ -0,0 +1,9 @@ +package com.kernel360.washzonereview.repository; + +import com.kernel360.washzonereview.entity.WashzoneReview; + +import java.util.Optional; + +public interface WashzoneReviewRepository extends WashzoneReviewRepositoryJpa, WashzoneReviewRepositoryDsl { + Optional findByWashzoneReviewNoAndIsVisibleTrue(Long washzoneReviewNo); +} diff --git a/module-api/src/main/java/com/kernel360/washzonereview/repository/WashzoneReviewRepositoryDsl.java b/module-api/src/main/java/com/kernel360/washzonereview/repository/WashzoneReviewRepositoryDsl.java new file mode 100644 index 00000000..089e4415 --- /dev/null +++ b/module-api/src/main/java/com/kernel360/washzonereview/repository/WashzoneReviewRepositoryDsl.java @@ -0,0 +1,12 @@ +package com.kernel360.washzonereview.repository; + +import com.kernel360.washzonereview.dto.WashzoneReviewSearchDto; +import com.kernel360.washzonereview.dto.WashzoneReviewSearchResult; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +public interface WashzoneReviewRepositoryDsl { + Page findAllByCondition(WashzoneReviewSearchDto condition, Pageable pageable); + + WashzoneReviewSearchResult findByWashzoneReviewNo(Long washzoneReviewNo); +} diff --git a/module-api/src/main/java/com/kernel360/washzonereview/repository/WashzoneReviewRepositoryImpl.java b/module-api/src/main/java/com/kernel360/washzonereview/repository/WashzoneReviewRepositoryImpl.java new file mode 100644 index 00000000..d2d25277 --- /dev/null +++ b/module-api/src/main/java/com/kernel360/washzonereview/repository/WashzoneReviewRepositoryImpl.java @@ -0,0 +1,118 @@ +package com.kernel360.washzonereview.repository; + +import com.kernel360.file.entity.FileReferType; +import com.kernel360.washzonereview.dto.WashzoneReviewSearchDto; +import com.kernel360.washzonereview.dto.WashzoneReviewSearchResult; +import com.querydsl.core.types.OrderSpecifier; +import com.querydsl.core.types.Projections; +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.jpa.impl.JPAQuery; +import com.querydsl.jpa.impl.JPAQueryFactory; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.support.PageableExecutionUtils; + +import java.util.List; + +import static com.kernel360.file.entity.QFile.file; +import static com.kernel360.member.entity.QMember.member; +import static com.kernel360.washzone.entity.QWashZone.washZone; +import static com.kernel360.washzonereview.entity.QWashzoneReview.washzoneReview; +import static com.querydsl.core.types.dsl.Expressions.stringTemplate; + +@RequiredArgsConstructor +public class WashzoneReviewRepositoryImpl implements WashzoneReviewRepositoryDsl { + + private final JPAQueryFactory queryFactory; + + @Override + public Page findAllByCondition(WashzoneReviewSearchDto condition, Pageable pageable) { + List reviews = + getJoinedResults() + .where( + washzoneNoEq(condition.washzoneNo()), + memberNoEq(condition.memberNo()) + ) + .groupBy(washzoneReview.washzoneReviewNo, member.memberNo, member.id, member.age, member.gender, washZone.washZoneNo) + .orderBy(sort(condition.sortBy())) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .fetch(); + + JPAQuery totalCountQuery = queryFactory + .select(washzoneReview.count()) + .from(washzoneReview) + .where( + washzoneReview.isVisible.eq(true), + washzoneNoEq(condition.washzoneNo()), + memberNoEq(condition.memberNo()) + ); + + return PageableExecutionUtils.getPage(reviews, pageable, totalCountQuery::fetchOne); + } + + @Override + public WashzoneReviewSearchResult findByWashzoneReviewNo(Long washzoneReviewNo) { + return getJoinedResults() + .where( + washzoneReview.isVisible.eq(true), + washzoneReview.washzoneReviewNo.eq(washzoneReviewNo) + ) + .groupBy(washzoneReview.washzoneReviewNo, member.memberNo, member.id, member.age, member.gender, washZone.washZoneNo) + .fetchOne(); + } + + private JPAQuery getJoinedResults() { + return queryFactory + .select(Projections.fields(WashzoneReviewSearchResult.class, + washzoneReview.washzoneReviewNo, + washzoneReview.starRating, + washzoneReview.title, + washzoneReview.contents, + washzoneReview.createdAt, + washzoneReview.createdBy, + washzoneReview.modifiedAt, + washzoneReview.modifiedBy, + member.memberNo, + stringTemplate("SUBSTRING({0}, 1, 2) || REPEAT('*', LENGTH({0}) - 2)", member.id).as("id"), + member.age, + member.gender, + washZone.washZoneNo.as("washzoneNo"), + washZone.name, + washZone.address, + washZone.type, + stringTemplate("STRING_AGG({0}, '|')", file.fileUrl).as("fileUrls") + )) + .from(washzoneReview) + .leftJoin(file) + .on( + file.referenceType.eq(FileReferType.WASHZONE_REVIEW.getCode()), + file.referenceNo.eq(washzoneReview.washzoneReviewNo) + ) + .join(member) + .on(washzoneReview.member.memberNo.eq(member.memberNo)) + .join(washZone) + .on(washzoneReview.washzone.washZoneNo.eq(washZone.washZoneNo)); + } + + private BooleanExpression washzoneNoEq(Long washzoneNo) { + return washzoneNo == null ? null : washzoneReview.washzone.washZoneNo.eq(washzoneNo); + } + + private BooleanExpression memberNoEq(Long memberNo) { + return memberNo == null ? null : washzoneReview.member.memberNo.eq(memberNo); + } + + private static OrderSpecifier sort(String sortBy) { + if ("topRated".equals(sortBy)) { + return washzoneReview.starRating.desc(); + } + + if ("lowRated".equals(sortBy)) { + return washzoneReview.starRating.asc(); + } + + return washzoneReview.washzoneReviewNo.desc(); + } +} diff --git a/module-api/src/main/java/com/kernel360/washzonereview/service/WashzoneReviewService.java b/module-api/src/main/java/com/kernel360/washzonereview/service/WashzoneReviewService.java new file mode 100644 index 00000000..3bc6bc32 --- /dev/null +++ b/module-api/src/main/java/com/kernel360/washzonereview/service/WashzoneReviewService.java @@ -0,0 +1,188 @@ +package com.kernel360.washzonereview.service; + +import com.kernel360.exception.BusinessException; +import com.kernel360.file.entity.File; +import com.kernel360.file.entity.FileReferType; +import com.kernel360.file.repository.FileRepository; +import com.kernel360.member.repository.MemberRepository; +import com.kernel360.utils.file.FileUtils; +import com.kernel360.washzonereview.code.WashzoneReviewErrorCode; +import com.kernel360.washzonereview.dto.WashzoneReviewRequestDto; +import com.kernel360.washzonereview.dto.WashzoneReviewResponseDto; +import com.kernel360.washzonereview.dto.WashzoneReviewSearchDto; +import com.kernel360.washzonereview.dto.WashzoneReviewSearchResult; +import com.kernel360.washzonereview.entity.WashzoneReview; +import com.kernel360.washzonereview.repository.WashzoneReviewRepository; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.dao.DataIntegrityViolationException; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +import java.math.BigDecimal; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +@Slf4j +@Service +@RequiredArgsConstructor +public class WashzoneReviewService { + private final WashzoneReviewRepository washzoneReviewRepository; + private final MemberRepository memberRepository; + private final FileRepository fileRepository; + private final FileUtils fileUtils; + + @Value("${aws.s3.bucket.url}") + private String bucketUrl; + + private static final double MAX_STAR_RATING = 5.0; + private static final String WASHZONE_REVIEW_DOMAIN = FileReferType.WASHZONE_REVIEW.getDomain(); + private static final String WASHZONE_REVIEW_CODE = FileReferType.WASHZONE_REVIEW.getCode(); + + @Transactional(readOnly = true) + public Page getWashzoneReviewsByWashzone(Long washzoneNo, String sortBy, Pageable pageable) { + log.info("세차창별 리뷰 목록 조회 -> washzone_no {}", washzoneNo); + + return washzoneReviewRepository.findAllByCondition(WashzoneReviewSearchDto.byWashzoneNo(washzoneNo, sortBy), pageable) + .map(WashzoneReviewSearchResult::toDto); + } + + @Transactional(readOnly = true) + public Page getWashzoneReviewsByMember(Long memberNo, String sortBy, Pageable pageable) { + log.info("멤버별 세차장 리뷰 목록 조회 -> member_no {}", memberNo); + + return washzoneReviewRepository.findAllByCondition(WashzoneReviewSearchDto.byMemberNo(memberNo, sortBy), pageable) + .map(WashzoneReviewSearchResult::toDto); + } + + @Transactional(readOnly = true) + public WashzoneReviewResponseDto getWashzoneReview(Long washzoneReviewNo) { + log.info("세차장 리뷰 단건 조회 -> washzone_review_no {}", washzoneReviewNo); + WashzoneReviewSearchResult washzoneReview = washzoneReviewRepository.findByWashzoneReviewNo(washzoneReviewNo); + + if (Objects.isNull(washzoneReview)) { + throw new BusinessException(WashzoneReviewErrorCode.NOT_FOUND_WASHZONE_REVIEW); + } + + return WashzoneReviewSearchResult.toDto(washzoneReview); + } + + @Transactional + public WashzoneReview createWashzoneReview(WashzoneReviewRequestDto requestDto, List files, String id) { + isValidMemberInfo(id, requestDto.memberNo()); + isValidStarRating(requestDto.starRating()); + + WashzoneReview washzoneReview; + + try { + washzoneReview = washzoneReviewRepository.saveAndFlush(requestDto.toEntity()); + log.info("세차장 리뷰 등록 -> washzone_review_no {}", washzoneReview.getWashzoneReviewNo()); + + if (Objects.nonNull(files)) { + uploadFiles(files, requestDto.washzoneNo(), washzoneReview.getWashzoneReviewNo()); + } + } catch (DataIntegrityViolationException e) { + String msg = e.getMessage().toString(); + + if (msg.contains("washzone_review_washzone_no_fkey")) { + throw new BusinessException(WashzoneReviewErrorCode.NOT_FOUND_WASHZONE_FOR_WASHZONE_REVIEW_CREATION); + } + + if (msg.contains("washzone_review_member_no_fkey")) { + throw new BusinessException(WashzoneReviewErrorCode.NOT_FOUND_MEMBER_FOR_WASHZONE_REVIEW_CREATION); + } + + throw new BusinessException(WashzoneReviewErrorCode.DUPLICATE_WASHZONE_REVIEW_EXISTS); + } + + return washzoneReview; + } + + private void uploadFiles(List files, Long washzoneNo, Long washzoneReviewNo) { + files.stream().forEach(file -> { + String path = String.join("/", WashzoneReviewService.WASHZONE_REVIEW_DOMAIN, washzoneNo.toString()); + String fileKey = fileUtils.upload(path, file); + String fileUrl = String.join("/", bucketUrl, fileKey); + + File fileInfo = fileRepository.save(File.of(null, file.getOriginalFilename(), fileKey, fileUrl, WashzoneReviewService.WASHZONE_REVIEW_CODE, washzoneReviewNo)); + log.info("세차장 리뷰 파일 등록 -> file_no {}", fileInfo.getFileNo()); + }); + } + + @Transactional + public void updateWashzoneReview(WashzoneReviewRequestDto requestDto, List files, String id) { + WashzoneReview washzoneReview = isVisibleStatus(requestDto.washzoneReviewNo()); + isValidMemberInfo(id, washzoneReview.getMember().getMemberNo()); + isValidStarRating(requestDto.starRating()); + + long washzoneNo = washzoneReview.getWashzone().getWashZoneNo(); + + try { + washzoneReviewRepository.saveAndFlush(requestDto.toEntityForUpdate()); + log.info("세차장 리뷰 수정 -> washzone_review_no {}", requestDto.washzoneReviewNo()); + + fileRepository.findByReferenceTypeAndReferenceNo(WashzoneReviewService.WASHZONE_REVIEW_CODE, requestDto.washzoneReviewNo()) + .stream() + .forEach(file -> { + if (!requestDto.files().contains(file.getFileUrl())) { + fileUtils.delete(file.getFileKey()); + fileRepository.deleteById(file.getFileNo()); + log.info("세차장 리뷰 파일 삭제 -> file_no {}", file.getFileNo()); + } + }); + + if (Objects.nonNull(files)) { + uploadFiles(files, washzoneNo, requestDto.washzoneReviewNo()); + } + } catch (DataIntegrityViolationException e) { + throw new BusinessException(WashzoneReviewErrorCode.DUPLICATE_WASHZONE_REVIEW_EXISTS); + } + } + + @Transactional + public void deleteWashzoneReview(Long washzoneReviewNo, String id) { + WashzoneReview washzoneReview = isVisibleStatus(washzoneReviewNo); + isValidMemberInfo(id, washzoneReview.getMember().getMemberNo()); + + washzoneReviewRepository.deleteById(washzoneReviewNo); + log.info("세차장 리뷰 삭제 -> washzone_review_no {}", washzoneReviewNo); + + fileRepository.findByReferenceTypeAndReferenceNo(WashzoneReviewService.WASHZONE_REVIEW_CODE, washzoneReviewNo) + .stream() + .forEach(file -> { + fileUtils.delete(file.getFileKey()); + log.info("세차장 리뷰 파일 삭제 -> file_no {}", file.getFileNo()); + }); + fileRepository.deleteByReferenceTypeAndReferenceNo(WashzoneReviewService.WASHZONE_REVIEW_CODE, washzoneReviewNo); + } + + private WashzoneReview isVisibleStatus(Long washzoneReviewNo) { + Optional washzoneReview = washzoneReviewRepository.findByWashzoneReviewNoAndIsVisibleTrue(washzoneReviewNo); + + if (washzoneReview.isEmpty()) { + throw new BusinessException(WashzoneReviewErrorCode.NOT_FOUND_WASHZONE_REVIEW); + } + + return washzoneReview.get(); + } + + private void isValidMemberInfo(String id, Long memberNo) { + memberRepository.findOneByIdAndMemberNo(id, memberNo) + .orElseThrow(() -> new BusinessException(WashzoneReviewErrorCode.MISMATCHED_MEMBER_NO_AND_ID)); + } + + private void isValidStarRating(BigDecimal starRating) { + if (BigDecimal.ZERO.compareTo(starRating) > 0) { + throw new BusinessException(WashzoneReviewErrorCode.INVALID_STAR_RATING_VALUE); + } + + if (BigDecimal.valueOf(MAX_STAR_RATING).compareTo(starRating) < 0) { + throw new BusinessException(WashzoneReviewErrorCode.INVALID_STAR_RATING_VALUE); + } + } +} diff --git a/module-api/src/main/resources/application-dev.yml b/module-api/src/main/resources/application-dev.yml index 87fd4b0d..e0d2bc7b 100644 --- a/module-api/src/main/resources/application-dev.yml +++ b/module-api/src/main/resources/application-dev.yml @@ -11,6 +11,8 @@ spring: format_sql: true dialect: org.hibernate.dialect.PostgreSQLDialect ddl-auto: validate + naming: + physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl flyway: enabled: true baseline-on-migrate: true @@ -57,10 +59,10 @@ management: endpoints: web: exposure: - include: "*" + include: "prometheus, health" endpoint: health: - show-details: always + show-details: never constants: host-url: ${DEV_HOST_URL} diff --git a/module-api/src/main/resources/application-local.yml b/module-api/src/main/resources/application-local.yml index 174d58f5..ad4b7b56 100644 --- a/module-api/src/main/resources/application-local.yml +++ b/module-api/src/main/resources/application-local.yml @@ -12,6 +12,8 @@ spring: format_sql: true dialect: org.hibernate.dialect.PostgreSQLDialect ddl-auto: validate + naming: + physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl flyway: enabled: true baseline-on-migrate: true @@ -63,10 +65,10 @@ management: endpoints: web: exposure: - include: "*" + include: "prometheus, health" endpoint: health: - show-details: always + show-details: never constants: host-url: "http://localhost:3000" @@ -83,4 +85,4 @@ aws: s3: bucket: name: ENC(JQIi11b8LB+99FnX02wCGwdXTOEax3VkuzgNqAVshK4=) - url: ENC(9P2gRaZoGkR4SCgoTS/6sEQP0kVWwVWFaDckr1/FUoRV1MPnGXQL6OJKsGlHegk8h1d69uFDKTuZpLntfyn3nVMXLz18t8ls) \ No newline at end of file + url: ENC(vhIgYYu6Nz9zFBDC3Rd3IoRGZBoT2zFnYDODVl/3f9MN/rDj9/9ArlT3B1shs2Y+A67BebgtMkp9v8jP3EN5owVEbXPu0vYY) \ No newline at end of file diff --git a/module-api/src/main/resources/application-prod.yml b/module-api/src/main/resources/application-prod.yml index 55eb3f00..937581e9 100644 --- a/module-api/src/main/resources/application-prod.yml +++ b/module-api/src/main/resources/application-prod.yml @@ -11,6 +11,8 @@ spring: format_sql: true dialect: org.hibernate.dialect.PostgreSQLDialect ddl-auto: validate + naming: + physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl flyway: enabled: true baseline-on-migrate: true @@ -57,10 +59,10 @@ management: endpoints: web: exposure: - include: "*" + include: "prometheus, health" endpoint: health: - show-details: always + show-details: never constants: host-url: ${PROD_HOST_URL} diff --git a/module-api/src/main/resources/application.yml b/module-api/src/main/resources/application.yml index cc7635e9..3af0882f 100644 --- a/module-api/src/main/resources/application.yml +++ b/module-api/src/main/resources/application.yml @@ -4,4 +4,7 @@ spring: servlet: multipart: max-file-size: 50MB - max-request-size: 500MB \ No newline at end of file + max-request-size: 500MB + +module: + name: api \ No newline at end of file diff --git a/module-api/src/main/resources/db/migration/V1.0.11__create_file_table.sql b/module-api/src/main/resources/db/migration/V1.0.11__create_file_table.sql new file mode 100644 index 00000000..66d0d722 --- /dev/null +++ b/module-api/src/main/resources/db/migration/V1.0.11__create_file_table.sql @@ -0,0 +1,15 @@ +CREATE TABLE IF NOT EXISTS file +( + file_no BIGSERIAL PRIMARY KEY, + file_name VARCHAR(255) NOT NULL, + file_key VARCHAR(255) NOT NULL UNIQUE, + file_url VARCHAR(500) NOT NULL UNIQUE, + reference_type VARCHAR(50), + reference_no BIGINT, + created_at DATE NOT NULL, + created_by VARCHAR NOT NULL, + modified_at DATE, + modified_by VARCHAR +); + +alter sequence file_file_no_seq increment by 50; \ No newline at end of file diff --git a/module-api/src/main/resources/db/migration/V1.0.12__modify_review_table.sql b/module-api/src/main/resources/db/migration/V1.0.12__modify_review_table.sql new file mode 100644 index 00000000..e50868cc --- /dev/null +++ b/module-api/src/main/resources/db/migration/V1.0.12__modify_review_table.sql @@ -0,0 +1,8 @@ +alter table review + alter column title type varchar(255) using title::varchar(255); + +alter table review + alter column contents type varchar(4000) using contents::varchar(4000); + +alter table review + add is_visible bool default true not null; \ No newline at end of file diff --git a/module-api/src/main/resources/db/migration/V1.0.13__modify_datetime_type.sql b/module-api/src/main/resources/db/migration/V1.0.13__modify_datetime_type.sql new file mode 100644 index 00000000..fd6d632e --- /dev/null +++ b/module-api/src/main/resources/db/migration/V1.0.13__modify_datetime_type.sql @@ -0,0 +1,66 @@ +ALTER TABLE if exists admin + ALTER COLUMN created_at TYPE TIMESTAMP, + ALTER COLUMN modified_at TYPE TIMESTAMP; + +ALTER TABLE if exists all_ingredient_product + ALTER COLUMN created_at TYPE TIMESTAMP, + ALTER COLUMN modified_at TYPE TIMESTAMP; + +ALTER TABLE if exists auth + ALTER COLUMN created_at TYPE TIMESTAMP, + ALTER COLUMN modified_at TYPE TIMESTAMP; + +ALTER TABLE if exists brand + ALTER COLUMN created_at TYPE TIMESTAMP, + ALTER COLUMN modified_at TYPE TIMESTAMP; + +ALTER TABLE if exists car_info + ALTER COLUMN created_at TYPE TIMESTAMP, + ALTER COLUMN modified_at TYPE TIMESTAMP; + +ALTER TABLE if exists common_code + ALTER COLUMN created_at TYPE TIMESTAMP, + ALTER COLUMN modified_at TYPE TIMESTAMP; + +ALTER TABLE if exists concerned_product + ALTER COLUMN created_at TYPE TIMESTAMP, + ALTER COLUMN modified_at TYPE TIMESTAMP; + +ALTER TABLE if exists file + ALTER COLUMN created_at TYPE TIMESTAMP, + ALTER COLUMN modified_at TYPE TIMESTAMP; + +ALTER TABLE if exists likes + ALTER COLUMN created_at TYPE TIMESTAMP, + ALTER COLUMN modified_at TYPE TIMESTAMP; + +-- 멤버 뷰 삭제 -- +DROP VIEW if exists member_view; + +ALTER TABLE if exists member + ALTER COLUMN created_at TYPE TIMESTAMP, + ALTER COLUMN modified_at TYPE TIMESTAMP; + +ALTER TABLE if exists product + ALTER COLUMN created_at TYPE TIMESTAMP, + ALTER COLUMN modified_at TYPE TIMESTAMP; + +ALTER TABLE if exists reported_product + ALTER COLUMN created_at TYPE TIMESTAMP, + ALTER COLUMN modified_at TYPE TIMESTAMP; + +ALTER TABLE if exists review + ALTER COLUMN created_at TYPE TIMESTAMP, + ALTER COLUMN modified_at TYPE TIMESTAMP; + +ALTER TABLE if exists violated_product + ALTER COLUMN created_at TYPE TIMESTAMP, + ALTER COLUMN modified_at TYPE TIMESTAMP; + +ALTER TABLE if exists wash_info + ALTER COLUMN created_at TYPE TIMESTAMP, + ALTER COLUMN modified_at TYPE TIMESTAMP; + +ALTER TABLE if exists withdraw_member + ALTER COLUMN created_at TYPE TIMESTAMP, + ALTER COLUMN modified_at TYPE TIMESTAMP; \ No newline at end of file diff --git a/module-api/src/main/resources/db/migration/V1.0.14__recreate_member_view.sql b/module-api/src/main/resources/db/migration/V1.0.14__recreate_member_view.sql new file mode 100644 index 00000000..37d4e003 --- /dev/null +++ b/module-api/src/main/resources/db/migration/V1.0.14__recreate_member_view.sql @@ -0,0 +1,138 @@ +-- V1.0.2 와 완전히 동일 -> 스크립트 통합 필요 +-- AES 암호화를 위한 확장 모듈 설치 +CREATE EXTENSION IF NOT EXISTS pgcrypto; + + +-- 테이블 복호화 함수 +CREATE OR REPLACE FUNCTION washpedia_member_decrypt() RETURNS TRIGGER AS +$$ +BEGIN + -- 비밀번호 복호화 + NEW.password := pgp_sym_decrypt(NEW.password, 'changeRequired'); + -- 이메일 복호화 + NEW.email := pgp_sym_decrypt(NEW.email, 'changeRequired'); + + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + + +-- 테이블 복호화 트리거 insert, update 이벤트 후 member_view 업데이트 +DROP TRIGGER IF EXISTS washpedia_member_decrypt_trigger ON member; + +CREATE TRIGGER washpedia_member_decrypt_trigger + AFTER INSERT OR UPDATE + ON member + FOR EACH ROW +EXECUTE FUNCTION washpedia_member_decrypt(); + + +-- 복호화 뷰 생성 +DROP VIEW IF EXISTS member_view; + +CREATE OR REPLACE VIEW member_view AS +SELECT member_no, + id, + encode(pgp_sym_decrypt(password, 'changeRequired')::bytea, 'escape') as password, + encode(pgp_sym_decrypt(email, 'changeRequired')::bytea, 'escape') as email, + gender, + age, + created_at, + created_by, + modified_at, + modified_by, + account_type +FROM member; + + +/** 뷰 TO 테이블 바인딩 **/ + +-- 뷰 insert 함수 +CREATE + OR REPLACE FUNCTION member_view_insert_trigger() + RETURNS TRIGGER AS +$$ +BEGIN + INSERT INTO member (member_no, id, "password", email, gender, age, created_at, created_by, modified_at, modified_by, account_type) + VALUES (nextval('member_member_no_seq'::regclass), NEW.id, pgp_sym_encrypt(NEW.password::TEXT, 'changeRequired'), + pgp_sym_encrypt(NEW.email::TEXT, 'changeRequired'), NEW.gender, NEW.age, NEW.created_at, NEW.created_by, + NEW.modified_at, NEW.modified_by, NEW.account_type); + + RETURN NEW; + +END; +$$ + LANGUAGE plpgsql; + + +-- 뷰 insert 트리거 +DROP TRIGGER IF EXISTS member_view_insert_trigger ON member; + +CREATE TRIGGER member_view_insert_trigger + INSTEAD OF INSERT + ON member_view + FOR EACH ROW +EXECUTE FUNCTION member_view_insert_trigger(); + + +-- 뷰 update 함수 +CREATE + OR REPLACE FUNCTION member_view_update_trigger() + RETURNS TRIGGER AS +$$ +BEGIN + UPDATE member + SET id = NEW.id, + "password" = pgp_sym_encrypt(NEW.password::TEXT, 'changeRequired'), + email = pgp_sym_encrypt(NEW.email::TEXT, 'changeRequired'), + gender = NEW.gender, + age = NEW.age, + created_at = NEW.created_at, + created_by = NEW.created_by, + modified_at = NEW.modified_at, + modified_by = NEW.modified_by, + account_type = NEW.account_type + WHERE member_no = NEW.member_no; + RETURN NEW; +END; +$$ + LANGUAGE plpgsql; + + +-- 뷰 update 트리거 +DROP TRIGGER IF EXISTS member_view_update_trigger ON member; + +CREATE TRIGGER member_view_update_trigger + INSTEAD OF UPDATE + ON member_view + FOR EACH ROW +EXECUTE FUNCTION member_view_update_trigger(); + + +-- 뷰 delete 함수 +CREATE + OR REPLACE FUNCTION member_view_delete_trigger() + RETURNS TRIGGER AS +$$ +BEGIN + DELETE + FROM member + WHERE id = OLD.id; + RETURN OLD; +END; +$$ + LANGUAGE plpgsql; + + +-- 뷰 delete 트리거 +DROP TRIGGER IF EXISTS member_view_delete_trigger ON member; + +CREATE TRIGGER member_view_delete_trigger + INSTEAD OF DELETE + ON member_view + FOR EACH ROW +EXECUTE FUNCTION member_view_delete_trigger(); + + +-- 테이블 member 권한 회수 설정 +REVOKE INSERT, UPDATE, DELETE, TRUNCATE ON member FROM wash_admin; \ No newline at end of file diff --git a/module-api/src/main/resources/db/migration/V1.0.15__create_BBS.sql b/module-api/src/main/resources/db/migration/V1.0.15__create_BBS.sql new file mode 100644 index 00000000..8384b6e9 --- /dev/null +++ b/module-api/src/main/resources/db/migration/V1.0.15__create_BBS.sql @@ -0,0 +1,17 @@ +CREATE TABLE IF NOT EXISTS bbs ( + bbs_no BIGSERIAL PRIMARY KEY, + upper_no BIGINT, + member_no BIGINT NOT NULL, + type VARCHAR NOT NULL, + title VARCHAR NOT NULL, + contents VARCHAR NOT NULL, + is_visible BOOL NOT NULL DEFAULT TRUE, + view_count BIGINT NOT NULL, + created_at TIMESTAMP NOT NULL, + created_by VARCHAR NOT NULL, + modified_at TIMESTAMP, + modified_by VARCHAR, + FOREIGN KEY (member_no) REFERENCES Member (member_no) + ); + +AlTER SEQUENCE bbs_bbs_no_seq increment by 50; diff --git a/module-api/src/main/resources/db/migration/V1.0.16__create_washzone_review_table.sql b/module-api/src/main/resources/db/migration/V1.0.16__create_washzone_review_table.sql new file mode 100644 index 00000000..7e8a0b61 --- /dev/null +++ b/module-api/src/main/resources/db/migration/V1.0.16__create_washzone_review_table.sql @@ -0,0 +1,19 @@ +CREATE TABLE if not exists Washzone_Review +( + washzone_review_no BIGSERIAL PRIMARY KEY, + washzone_no BIGINT NOT NULL, + member_no BIGINT NOT NULL, + star_rating NUMERIC(3, 1) NOT NULL, + title VARCHAR(255) NOT NULL, + contents VARCHAR(4000) NOT NULL, + is_visible BOOL NOT NULL DEFAULT TRUE, + created_at TIMESTAMP NOT NULL, + created_by VARCHAR NOT NULL, + modified_at TIMESTAMP NULL, + modified_by VARCHAR NULL, + FOREIGN KEY (washzone_no) REFERENCES Wash_zone (washzone_no), + FOREIGN KEY (member_no) REFERENCES Member (member_no), + CONSTRAINT washzone_review_ukey UNIQUE (member_no, washzone_no) +); + +AlTER SEQUENCE washzone_review_washzone_review_no_seq increment by 50; diff --git a/module-api/src/test/java/com/kernel360/commoncode/controller/CommonCodeControllerRestDocsTest.java b/module-api/src/test/java/com/kernel360/commoncode/controller/CommonCodeControllerRestDocsTest.java index 42dc0b54..75ca50da 100644 --- a/module-api/src/test/java/com/kernel360/commoncode/controller/CommonCodeControllerRestDocsTest.java +++ b/module-api/src/test/java/com/kernel360/commoncode/controller/CommonCodeControllerRestDocsTest.java @@ -1,24 +1,26 @@ package com.kernel360.commoncode.controller; -import com.kernel360.common.ControllerTest; -import com.kernel360.commoncode.dto.CommonCodeDto; -import org.junit.jupiter.api.Test; -import org.springframework.restdocs.payload.JsonFieldType; -import org.springframework.test.web.servlet.ResultActions; - -import java.time.LocalDate; -import java.util.Arrays; -import java.util.List; - import static com.kernel360.common.utils.RestDocumentUtils.getDocumentRequest; import static com.kernel360.common.utils.RestDocumentUtils.getDocumentResponse; import static org.mockito.BDDMockito.given; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; -import static org.springframework.restdocs.payload.PayloadDocumentation.*; -import static org.springframework.restdocs.request.RequestDocumentation.*; +import static org.springframework.restdocs.payload.PayloadDocumentation.beneathPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import com.kernel360.common.ControllerTest; +import com.kernel360.commoncode.dto.CommonCodeDto; +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.springframework.restdocs.payload.JsonFieldType; +import org.springframework.test.web.servlet.ResultActions; + // FIXME :: 삭제 예정 파일입니다 class CommonCodeControllerRestDocsTest extends ControllerTest { @@ -29,24 +31,24 @@ void commmonCodeSearch() throws Exception { CommonCodeDto.of( 11L, "Sedan", - 10, + 10L, "cartype", 1, true, "세단", - LocalDate.now(), + LocalDateTime.now(), "admin", null, null), CommonCodeDto.of( 12L, "Hatchback", - 10, + 10L, "cartype", 2, true, "해치백", - LocalDate.now(), + LocalDateTime.now(), "admin", null, null) diff --git a/module-api/src/test/java/com/kernel360/commoncode/controller/CommonCodeControllerTest.java b/module-api/src/test/java/com/kernel360/commoncode/controller/CommonCodeControllerTest.java index 4d99dff9..22d4b7f7 100644 --- a/module-api/src/test/java/com/kernel360/commoncode/controller/CommonCodeControllerTest.java +++ b/module-api/src/test/java/com/kernel360/commoncode/controller/CommonCodeControllerTest.java @@ -1,17 +1,16 @@ package com.kernel360.commoncode.controller; +import static org.mockito.BDDMockito.given; + import com.kernel360.common.ControllerTest; import com.kernel360.commoncode.dto.CommonCodeDto; +import java.time.LocalDateTime; +import java.util.Collections; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.result.MockMvcResultMatchers; -import java.time.LocalDate; -import java.util.Collections; - -import static org.mockito.BDDMockito.given; - class CommonCodeControllerTest extends ControllerTest { @Test @@ -21,14 +20,14 @@ class CommonCodeControllerTest extends ControllerTest { String pathVariable = "color"; Long codeNo = 1L; String codeName = "테스트용 코드"; - Integer upperNo = null; + Long upperNo = null; String upperName = null; Integer sortOrder = 1; Boolean isUsed = true; String description = "테스트용 코드값"; - LocalDate createdAt = LocalDate.now(); + LocalDateTime createdAt = LocalDateTime.now(); String createdBy = "admin"; - LocalDate modifiedAt = null; + LocalDateTime modifiedAt = null; String modifiedBy = null; CommonCodeDto item = CommonCodeDto.of(codeNo,codeName,upperNo,upperName,sortOrder,isUsed,description,createdAt,createdBy,modifiedAt,modifiedBy); diff --git a/module-api/src/test/java/com/kernel360/main/controller/MainControllerTest.java b/module-api/src/test/java/com/kernel360/main/controller/MainControllerTest.java index 5354df07..a492eafc 100644 --- a/module-api/src/test/java/com/kernel360/main/controller/MainControllerTest.java +++ b/module-api/src/test/java/com/kernel360/main/controller/MainControllerTest.java @@ -4,7 +4,7 @@ //import com.kernel360.main.dto.BannerDto; //import com.kernel360.main.dto.RecommendProductsDto; //import com.kernel360.product.dto.ProductDto; -//import com.kernel360.product.entity.SafetyStatus; +//import com.kernel360.product.enumset.SafetyStatus; //import com.navercorp.fixturemonkey.FixtureMonkey; //import com.navercorp.fixturemonkey.api.introspector.*; //import org.junit.jupiter.api.BeforeEach; diff --git a/module-api/src/test/java/com/kernel360/member/controller/MemberControllerTest.java b/module-api/src/test/java/com/kernel360/member/controller/MemberControllerTest.java index c8352c07..6899198d 100644 --- a/module-api/src/test/java/com/kernel360/member/controller/MemberControllerTest.java +++ b/module-api/src/test/java/com/kernel360/member/controller/MemberControllerTest.java @@ -7,15 +7,12 @@ import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; -import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; -import static org.springframework.restdocs.request.RequestDocumentation.queryParameters; import com.fasterxml.jackson.databind.ObjectMapper; import com.kernel360.common.ControllerTest; import com.kernel360.member.dto.MemberCredentialDto; import com.kernel360.member.dto.MemberDto; -import java.time.LocalDate; -import org.junit.jupiter.api.Disabled; +import java.time.LocalDateTime; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.http.MediaType; @@ -56,7 +53,7 @@ class MemberControllerTest extends ControllerTest { "", null, null, - LocalDate.now(), + LocalDateTime.now(), "test01", null, null, @@ -151,7 +148,7 @@ class MemberControllerTest extends ControllerTest { MemberCredentialDto credentialDto = MemberCredentialDto.of(null, null, "kernel360", null); MemberDto memberDto = MemberDto.of("testMemberId", "testPassword001"); - given(memberService.findByMemberId(credentialDto.memberId())).willReturn(memberDto); + given(memberService.findOneByIdForAccountTypeByPlatform(credentialDto.memberId())).willReturn(memberDto); given(findCredentialService.generatePasswordResetPageUri(memberDto)).willReturn("테스트 URI"); ObjectMapper objectMapper = new ObjectMapper(); diff --git a/module-api/src/test/java/com/kernel360/product/controller/ProductControllerTest.java b/module-api/src/test/java/com/kernel360/product/controller/ProductControllerTest.java index fc485618..af9d5874 100644 --- a/module-api/src/test/java/com/kernel360/product/controller/ProductControllerTest.java +++ b/module-api/src/test/java/com/kernel360/product/controller/ProductControllerTest.java @@ -4,19 +4,13 @@ import com.kernel360.product.dto.ProductDetailDto; import com.kernel360.product.dto.ProductDto; import com.kernel360.product.entity.Product; -import com.kernel360.product.entity.SafetyStatus; import com.navercorp.fixturemonkey.FixtureMonkey; import com.navercorp.fixturemonkey.api.introspector.*; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureWebMvc; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; import org.springframework.http.MediaType; -import static org.mockito.ArgumentMatchers.any; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; import static org.hamcrest.Matchers.*; @@ -24,7 +18,6 @@ import static org.springframework.restdocs.request.RequestDocumentation.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; -import java.util.ArrayList; import java.util.Arrays; import java.util.List; diff --git a/module-batch/build.gradle b/module-batch/build.gradle index a00334fb..2ff9891f 100644 --- a/module-batch/build.gradle +++ b/module-batch/build.gradle @@ -21,11 +21,6 @@ repositories { mavenCentral() } -ext { - set('springCloudVersion', "2023.0.0") -} - - dependencies { implementation project(':module-domain') @@ -33,7 +28,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-batch' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' runtimeOnly 'org.postgresql:postgresql' - runtimeOnly 'com.h2database:h2' + // jackson implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.15.3' @@ -51,12 +46,6 @@ dependencies { testImplementation 'com.navercorp.fixturemonkey:fixture-monkey-starter:1.0.0' } -dependencyManagement { - imports { - mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}" - } -} - tasks.named('test') { useJUnitPlatform() } diff --git a/module-batch/src/main/java/com/kernel360/modulebatch/ModuleBatchApplication.java b/module-batch/src/main/java/com/kernel360/modulebatch/ModuleBatchApplication.java index 32a759b3..3fad8bb6 100644 --- a/module-batch/src/main/java/com/kernel360/modulebatch/ModuleBatchApplication.java +++ b/module-batch/src/main/java/com/kernel360/modulebatch/ModuleBatchApplication.java @@ -2,8 +2,12 @@ import java.util.Optional; import org.springframework.batch.core.configuration.annotation.EnableBatchProcessing; +import org.springframework.batch.core.explore.JobExplorer; +import org.springframework.batch.core.launch.JobLauncher; +import org.springframework.batch.core.repository.JobRepository; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.batch.JobLauncherApplicationRunner; import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.context.annotation.Bean; import org.springframework.data.domain.AuditorAware; @@ -15,15 +19,17 @@ @EnableJpaAuditing @EnableBatchProcessing @SpringBootApplication -@EnableJpaRepositories({"com.kernel360.ecolife.repository", "com.kernel360.product.repository", - "com.kernel360.brand.repository"}) -@EntityScan({"com.kernel360.ecolife.entity", "com.kernel360.product.entity", "com.kernel360.brand.entity"}) +@EnableJpaRepositories({"com.kernel360"}) +@EntityScan({"com.kernel360"}) public class ModuleBatchApplication { public static void main(String[] args) { SpringApplication.run(ModuleBatchApplication.class, args); } - + @Bean + public JobLauncherApplicationRunner jobLauncherApplicationRunner(JobLauncher jobLauncher, JobExplorer jobExplorer, JobRepository jobRepository){ + return new JobLauncherApplicationRunner(jobLauncher,jobExplorer, jobRepository); + } @Bean public AuditorAware auditorProvider() { return () -> Optional.of("admin-batch"); diff --git a/module-batch/src/main/java/com/kernel360/modulebatch/Quartz/FetchReportedProductWithBrandSchedule.java b/module-batch/src/main/java/com/kernel360/modulebatch/Quartz/FetchReportedProductWithBrandSchedule.java deleted file mode 100644 index 5823f13f..00000000 --- a/module-batch/src/main/java/com/kernel360/modulebatch/Quartz/FetchReportedProductWithBrandSchedule.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.kernel360.modulebatch.Quartz; - -import java.time.LocalDateTime; -import lombok.NoArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.quartz.JobDataMap; -import org.quartz.JobExecutionContext; -import org.springframework.batch.core.BatchStatus; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersBuilder; -import org.springframework.batch.core.launch.JobLauncher; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.context.ApplicationContext; -import org.springframework.stereotype.Component; - -@Slf4j -@Component -public class FetchReportedProductWithBrandSchedule implements org.quartz.Job { - public FetchReportedProductWithBrandSchedule() {} - - @Override - public void execute(JobExecutionContext context) { - JobDataMap jobDataMap = context.getMergedJobDataMap(); - ApplicationContext applicationContext = (ApplicationContext) jobDataMap.get("applicationContext"); - - JobLauncher jobLauncher = applicationContext.getBean(JobLauncher.class); - Job fetchReportedProductFromBrandJob = (Job)applicationContext.getBean("fetchReportedProductFromBrandJob"); - Job fetchReportedProductDetailJob = (Job)applicationContext.getBean("fetchReportedProductDetailJob"); - - try { - JobParameters jobParameters = new JobParametersBuilder() - .addString("DATETIME", LocalDateTime.now().toString()) - .toJobParameters(); - - JobExecution jobExecution = jobLauncher.run(fetchReportedProductFromBrandJob, jobParameters); - - // 첫 번째 작업이 완료되면 두 번째 작업 실행 - if (jobExecution.getStatus() == BatchStatus.COMPLETED) { - log.info("{} by Quartz Scheduler Finished", fetchReportedProductFromBrandJob.getName()); - log.info("Starting {} by Quartz Scheduler", fetchReportedProductDetailJob.getName()); - jobLauncher.run(fetchReportedProductDetailJob, jobParameters); - } - } catch (org.springframework.batch.core.JobExecutionException e) { - log.error("JobExecutionException 예외 발생 ", e); - }finally { - log.info("{} by Quartz Scheduler Finished", fetchReportedProductDetailJob.getName()); - } - } -} diff --git a/module-batch/src/main/java/com/kernel360/modulebatch/Quartz/ImportProductFromReportedProductSchedule.java b/module-batch/src/main/java/com/kernel360/modulebatch/Quartz/ImportProductFromReportedProductSchedule.java deleted file mode 100644 index ade4a113..00000000 --- a/module-batch/src/main/java/com/kernel360/modulebatch/Quartz/ImportProductFromReportedProductSchedule.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.kernel360.modulebatch.Quartz; - -import java.time.LocalDateTime; -import lombok.extern.slf4j.Slf4j; -import org.quartz.JobDataMap; -import org.quartz.JobExecutionContext; -import org.springframework.batch.core.BatchStatus; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobParameters; -import org.springframework.batch.core.JobParametersBuilder; -import org.springframework.batch.core.launch.JobLauncher; -import org.springframework.context.ApplicationContext; -import org.springframework.stereotype.Component; - -@Slf4j -@Component -public class ImportProductFromReportedProductSchedule implements org.quartz.Job { - public ImportProductFromReportedProductSchedule() { - } - - @Override - public void execute(JobExecutionContext context) { - JobDataMap jobDataMap = context.getMergedJobDataMap(); - ApplicationContext applicationContext = (ApplicationContext) jobDataMap.get("applicationContext"); - - JobLauncher jobLauncher = applicationContext.getBean(JobLauncher.class); - Job importProductFromReportedProductJob = (Job) applicationContext.getBean( - "importProductFromReportedProductJob"); - - try { - JobParameters jobParameters = new JobParametersBuilder() - .addString("DATETIME", LocalDateTime.now().toString()) - .toJobParameters(); - - JobExecution jobExecution = jobLauncher.run(importProductFromReportedProductJob, jobParameters); - - if (jobExecution.getStatus() == BatchStatus.COMPLETED) { - log.info("{} by Quartz Scheduler Finished", importProductFromReportedProductJob.getName()); - } - - } catch (org.springframework.batch.core.JobExecutionException e) { - log.error("JobExecutionException by Quartz Scheduler Exception occurred ", e); - } - } -} diff --git a/module-batch/src/main/java/com/kernel360/modulebatch/Quartz/QuartzConfig.java b/module-batch/src/main/java/com/kernel360/modulebatch/Quartz/QuartzConfig.java deleted file mode 100644 index 3ed56007..00000000 --- a/module-batch/src/main/java/com/kernel360/modulebatch/Quartz/QuartzConfig.java +++ /dev/null @@ -1,102 +0,0 @@ -package com.kernel360.modulebatch.Quartz; - -import lombok.RequiredArgsConstructor; -import org.quartz.CronScheduleBuilder; -import org.quartz.JobBuilder; -import org.quartz.JobDataMap; -import org.quartz.JobDetail; -import org.quartz.Trigger; -import org.quartz.TriggerBuilder; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.scheduling.quartz.SchedulerFactoryBean; - -@Configuration -@RequiredArgsConstructor -public class QuartzConfig { - - private final ApplicationContext applicationContext; - - //-- JobDetail setting --// - @Bean - public JobDetail fetchReportedProductWithBrand() { - JobDataMap jobDataMap = new JobDataMap(); - jobDataMap.put("fetchReportedProductFromBrandJob", - applicationContext.getBean("fetchReportedProductFromBrandJob")); - jobDataMap.put("fetchReportedProductDetailJob", - applicationContext.getBean("fetchReportedProductDetailJob")); - - jobDataMap.put("applicationContext", applicationContext); - - return JobBuilder.newJob(FetchReportedProductWithBrandSchedule.class) - .withIdentity("fetchReportedProductWithBrand") - .setJobData(jobDataMap) - .storeDurably() - .build(); - } - - @Bean - public JobDetail importProductFromReportedProduct() { - JobDataMap jobDataMap = new JobDataMap(); - - jobDataMap.put("importProductFromReportedProductJob", - applicationContext.getBean("importProductFromReportedProductJob")); - - jobDataMap.put("applicationContext", applicationContext); - - return JobBuilder.newJob(ImportProductFromReportedProductSchedule.class) - .withIdentity("importProductFromReportedProductJob") - .setJobData(jobDataMap) - .storeDurably() - .build(); - } - - //-- Trigger --// - @Bean - public Trigger fetchReportedProductWithBrandExecuteTrigger( - @Qualifier("fetchReportedProductWithBrand") JobDetail jobDetail) { - String cronExpression = "0 0 1 ? * FRI *"; // 매주 금요일 새벽 1시 - - CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression) - .withMisfireHandlingInstructionDoNothing(); - - return TriggerBuilder.newTrigger() - .forJob(jobDetail) - .withIdentity("fetchReportedProductWithBrandExecuteTrigger") - .withSchedule(scheduleBuilder) - .build(); - } - - @Bean - public Trigger importProductFromReportedProductExecuteTrigger( - @Qualifier("importProductFromReportedProduct") JobDetail jobDetail) { - String cronExpression = "0 0 2 ? * FRI *"; // 매주 금요일 새벽 2시 - - CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression) - .withMisfireHandlingInstructionDoNothing(); - - return TriggerBuilder.newTrigger() - .forJob(jobDetail) - .withIdentity("importProductFromReportedProductJobExecuteTrigger") - .withSchedule(scheduleBuilder) - .build(); - } - - //-- Scheduler --// - - @Bean - public SchedulerFactoryBean schedulerFactoryBean( - @Qualifier("fetchReportedProductWithBrand") JobDetail fetchReportedProductWithBrand, - @Qualifier("fetchReportedProductWithBrandExecuteTrigger") Trigger fetchReportedProductWithBrandExecuteTrigger, - @Qualifier("importProductFromReportedProduct") JobDetail importProductFromReportedProduct, - @Qualifier("importProductFromReportedProductExecuteTrigger") Trigger importProductFromReportedProductExecuteTrigger) { - SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean(); - schedulerFactoryBean.setJobDetails(fetchReportedProductWithBrand, importProductFromReportedProduct); - schedulerFactoryBean.setTriggers(fetchReportedProductWithBrandExecuteTrigger, - importProductFromReportedProductExecuteTrigger); - - return schedulerFactoryBean; - } -} diff --git a/module-batch/src/main/java/com/kernel360/modulebatch/concernedproduct/job/core/FetchConcernedProductDetailJobConfig.java b/module-batch/src/main/java/com/kernel360/modulebatch/concernedproduct/job/core/FetchConcernedProductDetailJobConfig.java index 5f511356..0ca406ab 100644 --- a/module-batch/src/main/java/com/kernel360/modulebatch/concernedproduct/job/core/FetchConcernedProductDetailJobConfig.java +++ b/module-batch/src/main/java/com/kernel360/modulebatch/concernedproduct/job/core/FetchConcernedProductDetailJobConfig.java @@ -2,16 +2,13 @@ import com.kernel360.ecolife.entity.ConcernedProduct; import com.kernel360.modulebatch.concernedproduct.job.infra.ConcernedProductDetailItemProcessor; +import com.kernel360.modulebatch.global.BaseJobExecutionListener; +import com.kernel360.modulebatch.global.BaseStepExecutionListener; import jakarta.persistence.EntityManagerFactory; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.batch.core.ExitStatus; import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobExecutionListener; import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.StepExecutionListener; import org.springframework.batch.core.configuration.annotation.StepScope; import org.springframework.batch.core.job.builder.JobBuilder; import org.springframework.batch.core.launch.support.RunIdIncrementer; @@ -21,6 +18,7 @@ import org.springframework.batch.item.database.JpaPagingItemReader; import org.springframework.batch.item.database.builder.JpaPagingItemReaderBuilder; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.transaction.PlatformTransactionManager; @@ -28,21 +26,23 @@ @Slf4j @Configuration @RequiredArgsConstructor +@ConditionalOnProperty(name = "job.name", havingValue = FetchConcernedProductDetailJobConfig.JOB_NAME) public class FetchConcernedProductDetailJobConfig { - + public static final String JOB_NAME = "FetchConcernedProductDetailJob"; private final ConcernedProductDetailItemProcessor concernedProductDetailItemProcessor; - + private final BaseJobExecutionListener baseJobExecutionListener; + private final BaseStepExecutionListener baseStepExecutionListener; private final EntityManagerFactory emf; @Bean - public Job fetchConcernedProductDetailJob(JobRepository jobRepository, + public Job FetchConcernedProductDetailJob(JobRepository jobRepository, @Qualifier("fetchConcernedProductDetailStep") Step fetchConcernedProductDetailStep) { log.info("FetchConcernedProductDetailJobConfig initialized"); - return new JobBuilder("fetchConcernedProductDetailJobConfig", jobRepository) + return new JobBuilder(JOB_NAME, jobRepository) .start(fetchConcernedProductDetailStep) .incrementer(new RunIdIncrementer()) - .listener(new FetchConcernedProductDetailJobListener()) + .listener(baseJobExecutionListener) .build(); } @@ -52,7 +52,7 @@ public Step fetchConcernedProductDetailStep(JobRepository jobRepository, return new StepBuilder("fetchConcernedProductDetailStep", jobRepository) .chunk(100, transactionManager) - .listener(new FetchConcernedProductDetailStepListener()) + .listener(baseStepExecutionListener) .reader(concernedProductDetailItemReader()) .processor(concernedProductDetailItemProcessor) .writer(concernedProductItemWriter(emf)) @@ -81,34 +81,4 @@ public JpaItemWriter concernedProductItemWriter(EntityManagerF return jpaItemWriter; } - - //-- Execution Listener --// - - public static class FetchConcernedProductDetailJobListener implements JobExecutionListener { - @Override - public void beforeJob(JobExecution jobExecution) { - log.info("{} starts", jobExecution.getJobInstance().getJobName()); - } - - @Override - public void afterJob(JobExecution jobExecution) { - log.info("{} ends", jobExecution.getJobInstance().getJobName()); - } - } - - public static class FetchConcernedProductDetailStepListener implements StepExecutionListener{ - @Override - public void beforeStep(StepExecution stepExecution) { - log.info("{} starts", stepExecution.getStepName()); - } - - @Override - public ExitStatus afterStep(StepExecution stepExecution) { - log.info("StepExecutionListener - afterStep, step name: {}, status: {}", stepExecution.getStepName(), stepExecution.getStatus()); - log.info("StepExecutionListener - ReadCount: {}, WriteCount: {}, FilterCount: {}, ReadSkipCount: {}, ProcessSkipCount: {}, WriteSkipCount: {}", - stepExecution.getReadCount(), stepExecution.getWriteCount(), stepExecution.getFilterCount(), - stepExecution.getReadSkipCount(), stepExecution.getProcessSkipCount(), stepExecution.getWriteSkipCount()); - return StepExecutionListener.super.afterStep(stepExecution); - } - } } diff --git a/module-batch/src/main/java/com/kernel360/modulebatch/concernedproduct/job/core/FetchConcernedProductListFromBrandJobConfig.java b/module-batch/src/main/java/com/kernel360/modulebatch/concernedproduct/job/core/FetchConcernedProductListFromBrandJobConfig.java index acffb77c..8f87e9c1 100644 --- a/module-batch/src/main/java/com/kernel360/modulebatch/concernedproduct/job/core/FetchConcernedProductListFromBrandJobConfig.java +++ b/module-batch/src/main/java/com/kernel360/modulebatch/concernedproduct/job/core/FetchConcernedProductListFromBrandJobConfig.java @@ -1,9 +1,11 @@ package com.kernel360.modulebatch.concernedproduct.job.core; import com.kernel360.brand.entity.Brand; +import com.kernel360.modulebatch.global.BaseJobExecutionListener; import com.kernel360.modulebatch.concernedproduct.dto.ConcernedProductDto; import com.kernel360.modulebatch.concernedproduct.job.infra.ConcernedProductListItemProcessor; import com.kernel360.modulebatch.concernedproduct.job.infra.ConcernedProductListItemWriter; +import com.kernel360.modulebatch.global.BaseStepExecutionListener; import jakarta.persistence.EntityManagerFactory; import java.util.List; import lombok.RequiredArgsConstructor; @@ -16,6 +18,7 @@ import org.springframework.batch.core.step.builder.StepBuilder; import org.springframework.batch.item.database.JpaPagingItemReader; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.transaction.PlatformTransactionManager; @@ -24,21 +27,23 @@ @Slf4j @Configuration @RequiredArgsConstructor +@ConditionalOnProperty(name = "job.name", havingValue = FetchConcernedProductListFromBrandJobConfig.JOB_NAME) public class FetchConcernedProductListFromBrandJobConfig { - + public static final String JOB_NAME = "FetchConcernedProductListFromBrandJob"; private final ConcernedProductListItemProcessor concernedProductListItemProcessor; - private final ConcernedProductListItemWriter concernedProductListItemWriter; - + private final BaseJobExecutionListener baseJobExecutionListener; + private final BaseStepExecutionListener baseStepExecutionListener; private final EntityManagerFactory emf; @Bean - public Job fetchConcernedProductListFromBrandJob(JobRepository jobRepository, + public Job FetchConcernedProductListFromBrandJob(JobRepository jobRepository, @Qualifier("fetchConcernedProductListFromBrandStep") Step fetchConcernedProductListStep) { log.info("FetchConcernedProductListFromBrandJobConfig initialized"); - return new JobBuilder("fetchConcernedProductListFromBrandJob", jobRepository) + return new JobBuilder(JOB_NAME, jobRepository) .start(fetchConcernedProductListStep) + .listener(baseJobExecutionListener) .build(); } @@ -50,6 +55,7 @@ public Step fetchConcernedProductListFromBrandStep(JobRepository jobRepository, return new StepBuilder("fetchConcernedProductListFromBrandStep", jobRepository) .>chunk(1, transactionManager) + .listener(baseStepExecutionListener) .reader(readBrandForConcernedProduct()) .processor(concernedProductListItemProcessor) .writer(concernedProductListItemWriter) diff --git a/module-batch/src/main/java/com/kernel360/modulebatch/global/BaseJobExecutionListener.java b/module-batch/src/main/java/com/kernel360/modulebatch/global/BaseJobExecutionListener.java new file mode 100644 index 00000000..b4787a13 --- /dev/null +++ b/module-batch/src/main/java/com/kernel360/modulebatch/global/BaseJobExecutionListener.java @@ -0,0 +1,30 @@ +package com.kernel360.modulebatch.global; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.batch.core.BatchStatus; +import org.springframework.batch.core.JobExecution; +import org.springframework.batch.core.JobExecutionListener; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +@RequiredArgsConstructor +public class BaseJobExecutionListener implements JobExecutionListener { + @Override + public void beforeJob(JobExecution jobExecution) { + log.info("{} starts", jobExecution.getJobInstance().getJobName()); + } + + @Override + public void afterJob(JobExecution jobExecution) { + log.info("{} ends", jobExecution.getJobInstance().getJobName()); + + if (jobExecution.getStatus() == BatchStatus.FAILED) { + System.exit(1); + } + if (jobExecution.getStatus() == BatchStatus.COMPLETED) { + System.exit(0); + } + } +} diff --git a/module-batch/src/main/java/com/kernel360/modulebatch/global/BaseStepExecutionListener.java b/module-batch/src/main/java/com/kernel360/modulebatch/global/BaseStepExecutionListener.java new file mode 100644 index 00000000..590bc742 --- /dev/null +++ b/module-batch/src/main/java/com/kernel360/modulebatch/global/BaseStepExecutionListener.java @@ -0,0 +1,28 @@ +package com.kernel360.modulebatch.global; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.batch.core.ExitStatus; +import org.springframework.batch.core.StepExecution; +import org.springframework.batch.core.StepExecutionListener; +import org.springframework.stereotype.Component; + +@Slf4j +@Component +@RequiredArgsConstructor +public class BaseStepExecutionListener implements StepExecutionListener { + @Override + public void beforeStep(StepExecution stepExecution) { + log.info("{} starts", stepExecution.getStepName()); + } + + @Override + public ExitStatus afterStep(StepExecution stepExecution) { + log.info("StepExecutionListener - afterStep, step name: {}, status: {}", stepExecution.getStepName(), stepExecution.getStatus()); + log.info("StepExecutionListener - ReadCount: {}, WriteCount: {}, FilterCount: {}, ReadSkipCount: {}, ProcessSkipCount: {}, WriteSkipCount: {}", + stepExecution.getReadCount(), stepExecution.getWriteCount(), stepExecution.getFilterCount(), + stepExecution.getReadSkipCount(), stepExecution.getProcessSkipCount(), + stepExecution.getWriteSkipCount()); + return StepExecutionListener.super.afterStep(stepExecution); + } +} diff --git a/module-batch/src/main/java/com/kernel360/modulebatch/product/dto/ProductDto.java b/module-batch/src/main/java/com/kernel360/modulebatch/product/dto/ProductDto.java index 4971c243..b729072c 100644 --- a/module-batch/src/main/java/com/kernel360/modulebatch/product/dto/ProductDto.java +++ b/module-batch/src/main/java/com/kernel360/modulebatch/product/dto/ProductDto.java @@ -1,7 +1,7 @@ package com.kernel360.modulebatch.product.dto; import com.kernel360.product.entity.Product; -import com.kernel360.product.entity.SafetyStatus; +import com.kernel360.product.enumset.SafetyStatus; import java.time.LocalDate; public record ProductDto(String productName, String barcode, String imageSource, diff --git a/module-batch/src/main/java/com/kernel360/modulebatch/product/job/core/ImportProductFromConcernedProductJobConfig.java b/module-batch/src/main/java/com/kernel360/modulebatch/product/job/core/ImportProductFromConcernedProductJobConfig.java index 92a387b7..eb58a6bb 100644 --- a/module-batch/src/main/java/com/kernel360/modulebatch/product/job/core/ImportProductFromConcernedProductJobConfig.java +++ b/module-batch/src/main/java/com/kernel360/modulebatch/product/job/core/ImportProductFromConcernedProductJobConfig.java @@ -1,20 +1,17 @@ package com.kernel360.modulebatch.product.job.core; import com.kernel360.brand.entity.Brand; -import com.kernel360.modulebatch.product.job.infra.JpaProductListWriter; +import com.kernel360.modulebatch.global.BaseJobExecutionListener; +import com.kernel360.modulebatch.global.BaseStepExecutionListener; import com.kernel360.modulebatch.product.job.infra.ConcernedProductToProductListItemProcessor; +import com.kernel360.modulebatch.product.job.infra.JpaProductListWriter; import com.kernel360.product.entity.Product; import jakarta.persistence.EntityManagerFactory; import java.util.List; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.batch.core.ExitStatus; import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobExecutionListener; import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.StepExecutionListener; import org.springframework.batch.core.configuration.annotation.JobScope; import org.springframework.batch.core.configuration.annotation.StepScope; import org.springframework.batch.core.job.builder.JobBuilder; @@ -24,6 +21,7 @@ import org.springframework.batch.item.database.JpaPagingItemReader; import org.springframework.batch.item.database.builder.JpaPagingItemReaderBuilder; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.transaction.PlatformTransactionManager; @@ -31,20 +29,21 @@ @Slf4j @Configuration @RequiredArgsConstructor -//@ComponentScan("com.kernel360.product") +@ConditionalOnProperty(name = "job.name", havingValue = ImportProductFromConcernedProductJobConfig.JOB_NAME) public class ImportProductFromConcernedProductJobConfig { - + public static final String JOB_NAME = "ImportProductFromConcernedProductJob"; private final ConcernedProductToProductListItemProcessor concernedProductToProductListItemProcessor; - + private final BaseJobExecutionListener baseJobExecutionListener; + private final BaseStepExecutionListener baseStepExecutionListener; private final EntityManagerFactory emf; @Bean - public Job importProductFromConcernedProductJob(JobRepository jobRepository, + public Job ImportProductFromConcernedProductJob(JobRepository jobRepository, @Qualifier("importProductFromConcernedProductStep") Step importProductFromConcernedProductStep) { - return new JobBuilder("importProductFromConcernedProductJob", jobRepository) + return new JobBuilder(JOB_NAME, jobRepository) .start(importProductFromConcernedProductStep) - .listener(new importProductFromConcernedProductJobListener()) + .listener(baseJobExecutionListener) .build(); } @@ -55,7 +54,7 @@ public Step importProductFromConcernedProductStep(JobRepository jobRepository, return new StepBuilder("importProductFromConcernedProductStep", jobRepository) .>chunk(10, transactionManager) - .listener(new importProductFromConcernedProductStepListener()) + .listener(baseStepExecutionListener) .reader(importProductFromConcernedProductJpaPagingItemReader()) .processor(concernedProductToProductListItemProcessor) .writer(productListWriter()) @@ -81,38 +80,4 @@ private JpaProductListWriter productListWriter() { return new JpaProductListWriter<>(writer); } - - //-- Execution Listener --// - - public static class importProductFromConcernedProductJobListener implements JobExecutionListener { - @Override - public void beforeJob(JobExecution jobExecution) { - log.info("{} starts", jobExecution.getJobInstance().getJobName()); - } - - @Override - public void afterJob(JobExecution jobExecution) { - log.info("{} ends", jobExecution.getJobInstance().getJobName()); - } - } - - - public static class importProductFromConcernedProductStepListener implements StepExecutionListener { - @Override - public void beforeStep(StepExecution stepExecution) { - log.info("{} starts", stepExecution.getStepName()); - } - - @Override - public ExitStatus afterStep(StepExecution stepExecution) { - log.info("StepExecutionListener - afterStep, step name: {}, status: {}", stepExecution.getStepName(), - stepExecution.getStatus()); - log.info( - "StepExecutionListener - ReadCount: {}, WriteCount: {}, FilterCount: {}, ReadSkipCount: {}, ProcessSkipCount: {}, WriteSkipCount: {}", - stepExecution.getReadCount(), stepExecution.getWriteCount(), stepExecution.getFilterCount(), - stepExecution.getReadSkipCount(), stepExecution.getProcessSkipCount(), - stepExecution.getWriteSkipCount()); - return StepExecutionListener.super.afterStep(stepExecution); - } - } } diff --git a/module-batch/src/main/java/com/kernel360/modulebatch/product/job/core/ImportProductFromReportedProductJobConfig.java b/module-batch/src/main/java/com/kernel360/modulebatch/product/job/core/ImportProductFromReportedProductJobConfig.java index 87b9cf00..72805d87 100644 --- a/module-batch/src/main/java/com/kernel360/modulebatch/product/job/core/ImportProductFromReportedProductJobConfig.java +++ b/module-batch/src/main/java/com/kernel360/modulebatch/product/job/core/ImportProductFromReportedProductJobConfig.java @@ -1,19 +1,16 @@ package com.kernel360.modulebatch.product.job.core; import com.kernel360.ecolife.entity.ReportedProduct; +import com.kernel360.modulebatch.global.BaseJobExecutionListener; +import com.kernel360.modulebatch.global.BaseStepExecutionListener; import com.kernel360.modulebatch.product.job.infra.FilterUnusedProductTasklet; import com.kernel360.modulebatch.product.job.infra.ReportedProductToProductItemProcessor; import com.kernel360.product.entity.Product; import jakarta.persistence.EntityManagerFactory; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.batch.core.ExitStatus; import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobExecutionListener; import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.StepExecutionListener; import org.springframework.batch.core.configuration.annotation.JobScope; import org.springframework.batch.core.configuration.annotation.StepScope; import org.springframework.batch.core.job.builder.JobBuilder; @@ -26,6 +23,7 @@ import org.springframework.batch.item.database.builder.JpaPagingItemReaderBuilder; import org.springframework.batch.item.database.orm.JpaNativeQueryProvider; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.dao.DataIntegrityViolationException; @@ -35,25 +33,27 @@ @Slf4j @Configuration @RequiredArgsConstructor +@ConditionalOnProperty(name = "job.name", havingValue = ImportProductFromReportedProductJobConfig.JOB_NAME) public class ImportProductFromReportedProductJobConfig { + public static final String JOB_NAME = "ImportProductFromReportedProductJob"; private final ReportedProductToProductItemProcessor reportedProductToProductItemProcessor; - private final FilterUnusedProductTasklet filterUnusedProductTasklet; - + private final BaseJobExecutionListener baseJobExecutionListener; + private final BaseStepExecutionListener baseStepExecutionListener; private final EntityManagerFactory emf; @Bean - public Job importProductFromReportedProductJob(JobRepository jobRepository, + public Job ImportProductFromReportedProductJob(JobRepository jobRepository, @Qualifier("importProductFromReportedProductStep") Step importProductFromReportedProductStep, @Qualifier("filterUnusedProductStep") Step filterUnusedProductStep) { log.info("Import Product from ReportedProduct by Brand Job Build Configuration"); - return new JobBuilder("ImportProductFromReportedProductJob", jobRepository) + return new JobBuilder(JOB_NAME, jobRepository) .start(importProductFromReportedProductStep) .next(filterUnusedProductStep) .incrementer(new RunIdIncrementer()) - .listener(new ImportProductFromReportedProductJobListener()) + .listener(baseJobExecutionListener) .build(); } @@ -65,7 +65,7 @@ public Step importProductFromReportedProductStep(JobRepository jobRepository, return new StepBuilder("ImportProductFromReportedProductStep", jobRepository) .chunk(100, transactionManager) - .listener(new ImportProductFromReportedProductStepListener()) + .listener(baseStepExecutionListener) .reader(importProductFromReportedProductItemReader()) .processor(reportedProductToProductItemProcessor) .writer(productJpaItemWriter()) @@ -111,36 +111,8 @@ public Step filterUnusedProductStep(JobRepository jobRepository, PlatformTransac return new StepBuilder("filterUnusedProductStep", jobRepository) .tasklet(filterUnusedProductTasklet, transactionManager) - .listener(new ImportProductFromReportedProductStepListener()) + .listener(baseStepExecutionListener) .build(); } - -//-- Execution Listener --// - - public static class ImportProductFromReportedProductJobListener implements JobExecutionListener { - @Override - public void beforeJob(JobExecution jobExecution) { - log.info("{} starts", jobExecution.getJobInstance().getJobName()); - } - - @Override - public void afterJob(JobExecution jobExecution) { - log.info("{} ends", jobExecution.getJobInstance().getJobName()); - } - } - - public static class ImportProductFromReportedProductStepListener implements StepExecutionListener { - @Override - public void beforeStep(StepExecution stepExecution) { - log.info("{} starts", stepExecution.getStepName()); - } - - @Override - public ExitStatus afterStep(StepExecution stepExecution) { - log.info("{} ends", stepExecution.getStepName()); - return stepExecution.getExitStatus(); - } - } - } diff --git a/module-batch/src/main/java/com/kernel360/modulebatch/product/job/core/UpdateProductFromViolatedProductJobConfig.java b/module-batch/src/main/java/com/kernel360/modulebatch/product/job/core/UpdateProductFromViolatedProductJobConfig.java index cf51596f..6c23eb62 100644 --- a/module-batch/src/main/java/com/kernel360/modulebatch/product/job/core/UpdateProductFromViolatedProductJobConfig.java +++ b/module-batch/src/main/java/com/kernel360/modulebatch/product/job/core/UpdateProductFromViolatedProductJobConfig.java @@ -1,18 +1,15 @@ package com.kernel360.modulebatch.product.job.core; +import com.kernel360.modulebatch.global.BaseJobExecutionListener; +import com.kernel360.modulebatch.global.BaseStepExecutionListener; import com.kernel360.modulebatch.product.dto.ProductJoinDto; import com.kernel360.modulebatch.product.job.infra.UpdateProductFromViolatedProductItemProcessor; import com.kernel360.product.entity.Product; import jakarta.persistence.EntityManagerFactory; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.batch.core.ExitStatus; import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobExecutionListener; import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.StepExecutionListener; import org.springframework.batch.core.configuration.annotation.StepScope; import org.springframework.batch.core.job.builder.JobBuilder; import org.springframework.batch.core.repository.JobRepository; @@ -23,6 +20,7 @@ import org.springframework.batch.item.database.builder.JpaItemWriterBuilder; import org.springframework.batch.item.database.orm.JpaNativeQueryProvider; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.transaction.PlatformTransactionManager; @@ -30,19 +28,22 @@ @Slf4j @Configuration @RequiredArgsConstructor +@ConditionalOnProperty(name = "job.name", havingValue = UpdateProductFromViolatedProductJobConfig.JOB_NAME) public class UpdateProductFromViolatedProductJobConfig { + public static final String JOB_NAME = "UpdateProductFromViolatedProductJob"; private final EntityManagerFactory emf; - - private final UpdateProductFromViolatedProductItemProcessor updateProductFromViolatedProductItemProcessor; + private final UpdateProductFromViolatedProductItemProcessor updateProductItemProcessor; + private final BaseJobExecutionListener baseJobExecutionListener; + private final BaseStepExecutionListener baseStepExecutionListener; @Bean - public Job updateProductFromViolatedProductJob(JobRepository jobRepository, + public Job UpdateProductFromViolatedProductJob(JobRepository jobRepository, @Qualifier("updateProductFromViolatedProductStep") Step updateProductFromViolatedProductStep) { - return new JobBuilder("updateProductFromViolatedProductJob", jobRepository) + return new JobBuilder(JOB_NAME, jobRepository) .start(updateProductFromViolatedProductStep) - .listener(new UpdateProductFromViolatedProductJobListener()) + .listener(baseJobExecutionListener) .build(); } @@ -53,9 +54,9 @@ public Step updateProductFromViolatedProductStep(JobRepository jobRepository, return new StepBuilder("updateProductFromViolatedProductStep", jobRepository) .chunk(100, transactionManager) .reader(updateProductFromViolatedProductJpaPagingItemReader()) - .processor(updateProductFromViolatedProductItemProcessor) + .processor(updateProductItemProcessor) .writer(updateProductFromViolatedProductJpaItemWriter()) - .listener(new UpdateProductFromViolatedProductStepListener()) + .listener(baseStepExecutionListener) .build(); } @@ -90,37 +91,4 @@ public JpaItemWriter updateProductFromViolatedProductJpaItemWriter() { .entityManagerFactory(emf) .build(); } - - - //-- Execution Listener --// - public static class UpdateProductFromViolatedProductJobListener implements JobExecutionListener { - @Override - public void beforeJob(JobExecution jobExecution) { - log.info("{} starts", jobExecution.getJobInstance().getJobName()); - } - - @Override - public void afterJob(JobExecution jobExecution) { - log.info("{} ends", jobExecution.getJobInstance().getJobName()); - } - } - - public static class UpdateProductFromViolatedProductStepListener implements StepExecutionListener { - @Override - public void beforeStep(StepExecution stepExecution) { - log.info("{} starts", stepExecution.getStepName()); - } - - @Override - public ExitStatus afterStep(StepExecution stepExecution) { - log.info("StepExecutionListener - afterStep, step name: {}, status: {}", stepExecution.getStepName(), - stepExecution.getStatus()); - log.info( - "StepExecutionListener - ReadCount: {}, WriteCount: {}, FilterCount: {}, ReadSkipCount: {}, ProcessSkipCount: {}, WriteSkipCount: {}", - stepExecution.getReadCount(), stepExecution.getWriteCount(), stepExecution.getFilterCount(), - stepExecution.getReadSkipCount(), stepExecution.getProcessSkipCount(), - stepExecution.getWriteSkipCount()); - return StepExecutionListener.super.afterStep(stepExecution); - } - } } diff --git a/module-batch/src/main/java/com/kernel360/modulebatch/product/job/infra/ConcernedProductToProductListItemProcessor.java b/module-batch/src/main/java/com/kernel360/modulebatch/product/job/infra/ConcernedProductToProductListItemProcessor.java index bc511a00..084d18ac 100644 --- a/module-batch/src/main/java/com/kernel360/modulebatch/product/job/infra/ConcernedProductToProductListItemProcessor.java +++ b/module-batch/src/main/java/com/kernel360/modulebatch/product/job/infra/ConcernedProductToProductListItemProcessor.java @@ -6,7 +6,7 @@ import com.kernel360.ecolife.repository.ConcernedProductRepository; import com.kernel360.modulebatch.product.dto.ProductDto; import com.kernel360.product.entity.Product; -import com.kernel360.product.entity.SafetyStatus; +import com.kernel360.product.enumset.SafetyStatus; import java.time.LocalDate; import java.util.ArrayList; @@ -33,11 +33,11 @@ public class ConcernedProductToProductListItemProcessor implements ItemProcessor private final BrandRepository brandRepository; @Override - public List process(Brand brand) throws Exception { + public List process(Brand brand) { List concernedProductList = concernedProductRepository .findByBrandNameAndCompanyName( - "%" + brand.getBrandName().replaceAll(" ", "%") + "%", - "%" + brand.getCompanyName().replaceAll(" ", "%") + "%"); + "%" + brand.getBrandName().replace(" ", "%") + "%", + "%" + brand.getCompanyName().replace(" ", "%") + "%"); List productDtoList = concernedProductList.stream() .filter(cp -> cp.getInspectedOrganization() != null) diff --git a/module-batch/src/main/java/com/kernel360/modulebatch/product/job/infra/ReportedProductToProductItemProcessor.java b/module-batch/src/main/java/com/kernel360/modulebatch/product/job/infra/ReportedProductToProductItemProcessor.java index 21dc2569..d2cec392 100644 --- a/module-batch/src/main/java/com/kernel360/modulebatch/product/job/infra/ReportedProductToProductItemProcessor.java +++ b/module-batch/src/main/java/com/kernel360/modulebatch/product/job/infra/ReportedProductToProductItemProcessor.java @@ -5,7 +5,7 @@ import com.kernel360.ecolife.entity.ReportedProduct; import com.kernel360.modulebatch.product.dto.ProductDto; import com.kernel360.product.entity.Product; -import com.kernel360.product.entity.SafetyStatus; +import com.kernel360.product.enumset.SafetyStatus; import java.time.LocalDate; import java.util.List; diff --git a/module-batch/src/main/java/com/kernel360/modulebatch/reportedproduct/job/core/FetchReportedProductDetailJobConfig.java b/module-batch/src/main/java/com/kernel360/modulebatch/reportedproduct/job/core/FetchReportedProductDetailJobConfig.java index f123178a..c856929d 100644 --- a/module-batch/src/main/java/com/kernel360/modulebatch/reportedproduct/job/core/FetchReportedProductDetailJobConfig.java +++ b/module-batch/src/main/java/com/kernel360/modulebatch/reportedproduct/job/core/FetchReportedProductDetailJobConfig.java @@ -1,6 +1,8 @@ package com.kernel360.modulebatch.reportedproduct.job.core; import com.kernel360.ecolife.entity.ReportedProduct; +import com.kernel360.modulebatch.global.BaseJobExecutionListener; +import com.kernel360.modulebatch.global.BaseStepExecutionListener; import com.kernel360.modulebatch.reportedproduct.job.infra.ReportedProductDetailItemProcessor; import jakarta.persistence.EntityManagerFactory; import java.net.ConnectException; @@ -8,8 +10,6 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobExecutionListener; import org.springframework.batch.core.Step; import org.springframework.batch.core.configuration.annotation.StepScope; import org.springframework.batch.core.job.builder.JobBuilder; @@ -19,6 +19,7 @@ import org.springframework.batch.item.database.JpaItemWriter; import org.springframework.batch.item.database.JpaPagingItemReader; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.dao.DataIntegrityViolationException; @@ -28,20 +29,23 @@ @Slf4j @Configuration @RequiredArgsConstructor +@ConditionalOnProperty(name = "job.name", havingValue = FetchReportedProductDetailJobConfig.JOB_NAME) public class FetchReportedProductDetailJobConfig { + public static final String JOB_NAME = "FetchReportedProductDetailJob"; private final ReportedProductDetailItemProcessor reportedProductDetailItemProcessor; - + private final BaseJobExecutionListener baseJobExecutionListener; + private final BaseStepExecutionListener baseStepExecutionListener; private final EntityManagerFactory emf; @Bean - public Job fetchReportedProductDetailJob(JobRepository jobRepository, + public Job FetchReportedProductDetailJob(JobRepository jobRepository, @Qualifier("fetchReportedProductDetailStep") Step fetchReportedProductDetailStep) { log.info("Fetch ReportedProduct detail Job Build Configuration"); - return new JobBuilder("fetchReportedProductDetailJob", jobRepository) + return new JobBuilder(JOB_NAME, jobRepository) .start(fetchReportedProductDetailStep) .incrementer(new RunIdIncrementer()) - .listener(new FetchReportedProductDetailExecutionListener()) + .listener(baseJobExecutionListener) .build(); } @@ -50,6 +54,7 @@ public Step fetchReportedProductDetailStep(JobRepository jobRepository, PlatformTransactionManager transactionManager) { log.info("Fetch ReportedProduct detail Step Build Configuration"); return new StepBuilder("fetchReportedProductDetailStep", jobRepository) + .listener(baseStepExecutionListener) .chunk(10, transactionManager) .reader(productDetailItemReader()) // reported_product 테이블 읽어서 엔티티를 전달 .processor(reportedProductDetailItemProcessor) // 전달받은 엔티티로 detail 조회, 엔티티로 변환 @@ -83,19 +88,4 @@ public JpaItemWriter productDetailJpaItemWriter(EntityManagerFa return jpaItemWriter; } - - //-- Execution Listener --// - - public static class FetchReportedProductDetailExecutionListener implements JobExecutionListener { - @Override - public void beforeJob(JobExecution jobExecution) { - log.info("{} starts", jobExecution.getJobInstance().getJobName()); - } - - @Override - public void afterJob(JobExecution jobExecution) { - log.info("{} ends", jobExecution.getJobInstance().getJobName()); - } - } - } diff --git a/module-batch/src/main/java/com/kernel360/modulebatch/reportedproduct/job/core/FetchReportedProductListJobConfig.java b/module-batch/src/main/java/com/kernel360/modulebatch/reportedproduct/job/core/FetchReportedProductListJobConfig.java index 0734288c..b99bbe72 100644 --- a/module-batch/src/main/java/com/kernel360/modulebatch/reportedproduct/job/core/FetchReportedProductListJobConfig.java +++ b/module-batch/src/main/java/com/kernel360/modulebatch/reportedproduct/job/core/FetchReportedProductListJobConfig.java @@ -1,5 +1,7 @@ package com.kernel360.modulebatch.reportedproduct.job.core; +import com.kernel360.modulebatch.global.BaseJobExecutionListener; +import com.kernel360.modulebatch.global.BaseStepExecutionListener; import com.kernel360.modulebatch.reportedproduct.dto.ReportedProductDto; import com.kernel360.modulebatch.reportedproduct.job.infra.ReportedProductListItemReader; import com.kernel360.modulebatch.reportedproduct.job.infra.ReportedProductListItemWriter; @@ -7,14 +9,13 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobExecutionListener; import org.springframework.batch.core.Step; import org.springframework.batch.core.job.builder.JobBuilder; import org.springframework.batch.core.launch.support.RunIdIncrementer; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.builder.StepBuilder; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.dao.DataIntegrityViolationException; @@ -24,21 +25,25 @@ @Slf4j @Configuration @RequiredArgsConstructor +@ConditionalOnProperty(name = "job.name", havingValue = FetchReportedProductListJobConfig.JOB_NAME) public class FetchReportedProductListJobConfig { + public static final String JOB_NAME = "FetchReportedProductJob"; private final ReportedProductListItemReader reportedProductListItemReader; - private final ReportedProductListItemWriter reportedProductListItemWriter; + private final BaseJobExecutionListener baseJobExecutionListener; + private final BaseStepExecutionListener baseStepExecutionListener; + @Bean - public Job fetchReportedProductJob(JobRepository jobRepository, + public Job FetchReportedProductJob(JobRepository jobRepository, @Qualifier("fetchReportedProductListStep") Step fetchReportedProductListStep) { log.info("Fetch ReportedProduct List Job Build Configuration"); - return new JobBuilder("fetchReportedProductJob", jobRepository) + return new JobBuilder(JOB_NAME, jobRepository) .start(fetchReportedProductListStep) .incrementer(new RunIdIncrementer()) - .listener(new FetchReportedProductExecutionListener()) + .listener(baseJobExecutionListener) .build(); } @@ -50,6 +55,7 @@ public Step fetchReportedProductListStep(JobRepository jobRepository, log.info("Fetch ReportedProduct List Step Build Configuration"); return new StepBuilder("fetchReportedProductListStep", jobRepository) + .listener(baseStepExecutionListener) ., List>chunk(10, transactionManager) .reader(reportedProductListItemReader) // API 요청, 응답을 DTO 리스트로 반환 .writer(reportedProductListItemWriter) // DTO 리스트 입력, 저장 @@ -60,19 +66,4 @@ public Step fetchReportedProductListStep(JobRepository jobRepository, .skip(DataIntegrityViolationException.class) .build(); } - - //-- Execution Listener --// - - public static class FetchReportedProductExecutionListener implements JobExecutionListener { - @Override - public void beforeJob(JobExecution jobExecution) { - log.info("{} starts", jobExecution.getJobInstance().getJobName()); - } - - @Override - public void afterJob(JobExecution jobExecution) { - log.info("{} ends", jobExecution.getJobInstance().getJobName()); - } - } - } \ No newline at end of file diff --git a/module-batch/src/main/java/com/kernel360/modulebatch/reportedproduct/job/core/ReportedProductFromBrandJobConfig.java b/module-batch/src/main/java/com/kernel360/modulebatch/reportedproduct/job/core/ReportedProductFromBrandJobConfig.java index 691dc8c5..60540002 100644 --- a/module-batch/src/main/java/com/kernel360/modulebatch/reportedproduct/job/core/ReportedProductFromBrandJobConfig.java +++ b/module-batch/src/main/java/com/kernel360/modulebatch/reportedproduct/job/core/ReportedProductFromBrandJobConfig.java @@ -1,6 +1,8 @@ package com.kernel360.modulebatch.reportedproduct.job.core; import com.kernel360.brand.entity.Brand; +import com.kernel360.modulebatch.global.BaseJobExecutionListener; +import com.kernel360.modulebatch.global.BaseStepExecutionListener; import com.kernel360.modulebatch.reportedproduct.dto.ReportedProductDto; import com.kernel360.modulebatch.reportedproduct.job.infra.FetchReportedProductListFromBrandItemProcessor; import com.kernel360.modulebatch.reportedproduct.job.infra.ReportedProductListItemWriter; @@ -17,6 +19,7 @@ import org.springframework.batch.core.step.builder.StepBuilder; import org.springframework.batch.item.database.JpaPagingItemReader; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.transaction.PlatformTransactionManager; @@ -25,21 +28,25 @@ @Slf4j @Configuration @RequiredArgsConstructor +@ConditionalOnProperty(name = "job.name", havingValue = ReportedProductFromBrandJobConfig.JOB_NAME) public class ReportedProductFromBrandJobConfig { + public static final String JOB_NAME = "FetchReportedProductFromBrandJob"; private final ReportedProductListItemWriter reportedProductListItemWriter; - - private final FetchReportedProductListFromBrandItemProcessor fetchReportedProductListFromBrandItemProcessor; - + private final FetchReportedProductListFromBrandItemProcessor fetchReportedProductItemProcessor; private final EntityManagerFactory emf; + private final BaseJobExecutionListener baseJobExecutionListener; + private final BaseStepExecutionListener baseStepExecutionListener; + @Bean - public Job fetchReportedProductFromBrandJob(JobRepository jobRepository, + public Job FetchReportedProductFromBrandJob(JobRepository jobRepository, @Qualifier("fetchReportedProductFromBrandStep") Step fetchReportedProductFromBrandStep) { - return new JobBuilder("fetchReportedProductFromBrandJob", jobRepository) + return new JobBuilder(JOB_NAME, jobRepository) .incrementer(new RunIdIncrementer()) .start(fetchReportedProductFromBrandStep) + .listener(baseJobExecutionListener) .build(); } @@ -50,8 +57,9 @@ public Step fetchReportedProductFromBrandStep(JobRepository jobRepository, return new StepBuilder("fetchReportedProductFromBrandStep", jobRepository) .>chunk(1, transactionManager) + .listener(baseStepExecutionListener) .reader(readBrand()) // brand 목록을 읽어와서 전달 - .processor(fetchReportedProductListFromBrandItemProcessor) // 브랜드 정보를 통해서 API 요청, reportedProductDto 리스트 반환 + .processor(fetchReportedProductItemProcessor) // 브랜드 정보를 통해서 API 요청, reportedProductDto 리스트 반환 .writer(reportedProductListItemWriter) .faultTolerant() .retryLimit(2) @@ -69,5 +77,4 @@ public JpaPagingItemReader readBrand() throws Exception { return jpaPagingItemReader; } - } diff --git a/module-batch/src/main/java/com/kernel360/modulebatch/reportedproduct/job/infra/ReportedProductListItemWriter.java b/module-batch/src/main/java/com/kernel360/modulebatch/reportedproduct/job/infra/ReportedProductListItemWriter.java index 1fc453ce..ebc6a7e9 100644 --- a/module-batch/src/main/java/com/kernel360/modulebatch/reportedproduct/job/infra/ReportedProductListItemWriter.java +++ b/module-batch/src/main/java/com/kernel360/modulebatch/reportedproduct/job/infra/ReportedProductListItemWriter.java @@ -3,6 +3,7 @@ import com.kernel360.modulebatch.reportedproduct.dto.ReportedProductDto; import com.kernel360.modulebatch.reportedproduct.service.ReportedProductService; import java.util.List; +import lombok.RequiredArgsConstructor; import org.springframework.batch.core.configuration.annotation.StepScope; import org.springframework.batch.item.Chunk; import org.springframework.batch.item.ItemWriter; @@ -10,14 +11,10 @@ @Component @StepScope +@RequiredArgsConstructor public class ReportedProductListItemWriter implements ItemWriter> { - private final ReportedProductService service; - public ReportedProductListItemWriter(ReportedProductService service) { - this.service = service; - } - /** * 각 chunk 마다 포함하고 있는 리스트들을 내부 순회하며 리스트의 원소인 제품을 저장한다. * diff --git a/module-batch/src/main/java/com/kernel360/modulebatch/scheduler/ConcernedProductScheduler.java b/module-batch/src/main/java/com/kernel360/modulebatch/scheduler/ConcernedProductScheduler.java deleted file mode 100644 index 5de2b8f2..00000000 --- a/module-batch/src/main/java/com/kernel360/modulebatch/scheduler/ConcernedProductScheduler.java +++ /dev/null @@ -1,74 +0,0 @@ -package com.kernel360.modulebatch.scheduler; - -import java.time.LocalDateTime; -import lombok.extern.slf4j.Slf4j; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecutionException; -import org.springframework.batch.core.JobParametersBuilder; -import org.springframework.batch.core.launch.JobLauncher; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.context.annotation.Profile; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Component; - -@Slf4j -@Component -@Profile({"local", "dev"}) -public class ConcernedProductScheduler { - private final Job fetchConcernedProductListFromBrandJob; - private final Job fetchConcernedProductDetailJob; - private final Job importProductFromConcernedProductJob; - private final JobLauncher jobLauncher; - - @Autowired - public ConcernedProductScheduler( - @Qualifier("fetchConcernedProductListFromBrandJob") Job fetchConcernedProductListJob, - @Qualifier("fetchConcernedProductDetailJob") Job fetchConcernedProductDetailJob, - @Qualifier("importProductFromConcernedProductJob") Job importProductFromConcernedProductJob, - JobLauncher jobLauncher) { - - this.fetchConcernedProductListFromBrandJob = fetchConcernedProductListJob; - this.fetchConcernedProductDetailJob = fetchConcernedProductDetailJob; - this.importProductFromConcernedProductJob = importProductFromConcernedProductJob; - this.jobLauncher = jobLauncher; - } - - /** - * 매주 목요일 19시 00분 실행 - */ - @Scheduled(cron = "0 0 19 * * THU", zone = "Asia/Seoul") - public void executeFetchConcernedProductListJob() { - executeJob(fetchConcernedProductListFromBrandJob); - } - - /** - * 매주 목요일 19시 30분 실행 - */ - @Scheduled(cron = "0 30 19 * * THU", zone = "Asia/Seoul") - public void executeFetchConcernedProductDetailJob() { - executeJob(fetchConcernedProductDetailJob); - } - - /** - * 매주 목요일 20시 00분 실행 - */ - @Scheduled(cron = "0 0 20 * * THU", zone = "Asia/Seoul") - public void executeImportProductFromConcernedProductJob() { - executeJob(importProductFromConcernedProductJob); - } - - private synchronized void executeJob(Job job) { - - try { - jobLauncher.run( - job, - new JobParametersBuilder() - .addString("DATETIME", LocalDateTime.now().toString()) - .toJobParameters() - ); - } catch (JobExecutionException je) { - log.error("JobExecutionException Occurred", je); - } - } -} diff --git a/module-batch/src/main/java/com/kernel360/modulebatch/scheduler/ReportedProductScheduler.java b/module-batch/src/main/java/com/kernel360/modulebatch/scheduler/ReportedProductScheduler.java deleted file mode 100644 index 1edb7c37..00000000 --- a/module-batch/src/main/java/com/kernel360/modulebatch/scheduler/ReportedProductScheduler.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.kernel360.modulebatch.scheduler; - -import java.time.LocalDateTime; -import lombok.extern.slf4j.Slf4j; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecutionException; -import org.springframework.batch.core.JobParametersBuilder; -import org.springframework.batch.core.launch.JobLauncher; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.context.annotation.Profile; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Component; - -@Slf4j -@Component -@Profile({"local", "prod"}) -public class ReportedProductScheduler { - - private final Job importProductFromReportedProductJob; - private final Job fetchReportedProductFromBrandJob; - private final Job fetchReportedProductDetailJob; - private final JobLauncher jobLauncher; - - @Autowired - public ReportedProductScheduler( - @Qualifier("importProductFromReportedProductJob") Job importProductFromReportedProductJob, - @Qualifier("fetchReportedProductDetailJob") Job fetchReportedProductDetailJob, - @Qualifier("fetchReportedProductFromBrandJob") Job fetchReportedProductFromBrandJob, - JobLauncher jobLauncher) { - - this.importProductFromReportedProductJob = importProductFromReportedProductJob; - this.fetchReportedProductDetailJob = fetchReportedProductDetailJob; - this.fetchReportedProductFromBrandJob = fetchReportedProductFromBrandJob; - this.jobLauncher = jobLauncher; - } - - /** - * Brand 테이블의 brand 정보에 매칭되는 신고대상 생활화학제품 목록 정보 읽어오기 - */ - @Scheduled(cron = "0 0 1 * * WED", zone = "Asia/Seoul") - public void fetchReportedProductFromBrandJob() { - executeJob(fetchReportedProductFromBrandJob); - } - - /** - * 초록누리 API 를 통해 신고대상 생활화학제품 상세 정보 읽어오기 - */ - @Scheduled(cron = "0 0 2,14 * * WED", zone = "Asia/Seoul") - public void executeFetchReportedProductDetailJob() { - executeJob(fetchReportedProductDetailJob); - } - - /** - * Reported Product 테이블에서 Product 테이블로 필요한 제품 정보 옮기기 - */ - @Scheduled(cron = "0 30 2,14 * * WED", zone = "Asia/Seoul") - public void executeImportProductJob() { - executeJob(importProductFromReportedProductJob); - } - - - private synchronized void executeJob(Job job) { - try { - jobLauncher.run( - job, - new JobParametersBuilder() - .addString("DATETIME", LocalDateTime.now().toString()) - .toJobParameters() - ); - } catch (JobExecutionException je) { - log.error("JobExecutionException Occurred", je); - } - } - -} \ No newline at end of file diff --git a/module-batch/src/main/java/com/kernel360/modulebatch/scheduler/ViolatedProductScheduler.java b/module-batch/src/main/java/com/kernel360/modulebatch/scheduler/ViolatedProductScheduler.java deleted file mode 100644 index e5d3d9a2..00000000 --- a/module-batch/src/main/java/com/kernel360/modulebatch/scheduler/ViolatedProductScheduler.java +++ /dev/null @@ -1,77 +0,0 @@ -package com.kernel360.modulebatch.scheduler; - -import java.time.LocalDateTime; -import lombok.extern.slf4j.Slf4j; -import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecutionException; -import org.springframework.batch.core.JobParametersBuilder; -import org.springframework.batch.core.launch.JobLauncher; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.context.annotation.Profile; -import org.springframework.scheduling.annotation.Scheduled; -import org.springframework.stereotype.Component; - -@Slf4j -@Component -@Profile({"local", "dev", "prod"}) -public class ViolatedProductScheduler { - private final Job fetchViolatedProductListJob; - private final Job fetchViolatedProductDetailJob; - - private final Job updateProductFromViolatedProductJob; - - private final JobLauncher jobLauncher; - - @Autowired - public ViolatedProductScheduler( - @Qualifier("fetchViolatedProductListJob") Job fetchViolatedProductListJob, - @Qualifier("fetchViolatedProductDetailJob") Job fetchViolatedProductDetailJob, - @Qualifier("updateProductFromViolatedProductJob") Job updateProductFromViolatedProductJob, - JobLauncher jobLauncher) { - this.fetchViolatedProductListJob = fetchViolatedProductListJob; - this.fetchViolatedProductDetailJob = fetchViolatedProductDetailJob; - this.updateProductFromViolatedProductJob = updateProductFromViolatedProductJob; - this.jobLauncher = jobLauncher; - } - - - /** - * 매주 목요일 새벽 1시 실행 - */ - @Scheduled(cron = "0 0 1 * * THU", zone = "Asia/Seoul") - public void executeFetchViolatedProductListJob() { - executeJob(fetchViolatedProductListJob); - } - - /** - * 매주 목요일 2시, 14시 실행 - */ - @Scheduled(cron = "0 0 2,14 * * THU", zone = "Asia/Seoul") - public void executeFetchViolatedProductDetailJob() { - executeJob(fetchViolatedProductDetailJob); - } - - /** - * 매주 목요일 2시 30분, 14시 30분 실행 - */ - @Scheduled(cron = "0 30 2,14 * * THU", zone = "Asia/Seoul") - public void executeUpdateProductFromViolatedProductJob() { - executeJob(updateProductFromViolatedProductJob); - } - - private synchronized void executeJob(Job job) { - - try { - jobLauncher.run( - job, - new JobParametersBuilder() - .addString("DATETIME", LocalDateTime.now().toString()) - .addString("PRODUCT_ARM_CODE", "07") - .toJobParameters() - ); - } catch (JobExecutionException je) { - log.error("JobExecutionException Occurred", je); - } - } -} diff --git a/module-batch/src/main/java/com/kernel360/modulebatch/violatedproduct/job/core/FetchViolatedProductDetailJobConfig.java b/module-batch/src/main/java/com/kernel360/modulebatch/violatedproduct/job/core/FetchViolatedProductDetailJobConfig.java index 0c0817c5..dc51ed33 100644 --- a/module-batch/src/main/java/com/kernel360/modulebatch/violatedproduct/job/core/FetchViolatedProductDetailJobConfig.java +++ b/module-batch/src/main/java/com/kernel360/modulebatch/violatedproduct/job/core/FetchViolatedProductDetailJobConfig.java @@ -1,17 +1,14 @@ package com.kernel360.modulebatch.violatedproduct.job.core; import com.kernel360.ecolife.entity.ViolatedProduct; +import com.kernel360.modulebatch.global.BaseJobExecutionListener; +import com.kernel360.modulebatch.global.BaseStepExecutionListener; import com.kernel360.modulebatch.violatedproduct.job.infra.FetchViolatedProductDetailItemProcessor; import jakarta.persistence.EntityManagerFactory; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.batch.core.ExitStatus; import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobExecutionListener; import org.springframework.batch.core.Step; -import org.springframework.batch.core.StepExecution; -import org.springframework.batch.core.StepExecutionListener; import org.springframework.batch.core.configuration.annotation.StepScope; import org.springframework.batch.core.job.builder.JobBuilder; import org.springframework.batch.core.launch.support.RunIdIncrementer; @@ -22,6 +19,7 @@ import org.springframework.batch.item.database.builder.JpaCursorItemReaderBuilder; import org.springframework.batch.item.database.builder.JpaItemWriterBuilder; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.transaction.PlatformTransactionManager; @@ -29,21 +27,23 @@ @Slf4j @Configuration @RequiredArgsConstructor +@ConditionalOnProperty(name = "job.name", havingValue = FetchViolatedProductDetailJobConfig.JOB_NAME) public class FetchViolatedProductDetailJobConfig { + public static final String JOB_NAME = "FetchViolatedProductDetailJob"; private final FetchViolatedProductDetailItemProcessor fetchViolatedProductDetailItemProcessor; - + private final BaseJobExecutionListener baseJobExecutionListener; + private final BaseStepExecutionListener baseStepExecutionListener; private final EntityManagerFactory emf; - private final int chunkSize = 100; @Bean - public Job fetchViolatedProductDetailJob(JobRepository jobRepository, + public Job FetchViolatedProductDetailJob(JobRepository jobRepository, @Qualifier("fetchViolatedProductDetailStep") Step fetchViolatedProductDetailStep) { - return new JobBuilder("fetchViolatedProductDetailJob", jobRepository) + return new JobBuilder(JOB_NAME, jobRepository) .start(fetchViolatedProductDetailStep) .incrementer(new RunIdIncrementer()) - .listener(new FetchViolatedProductDetailJobListener()) + .listener(baseJobExecutionListener) .build(); } @@ -52,11 +52,11 @@ public Step fetchViolatedProductDetailStep(JobRepository jobRepository, PlatformTransactionManager transactionManager) { return new StepBuilder("fetchViolatedProductDetailStep", jobRepository) - .chunk(chunkSize, transactionManager) + .chunk(100, transactionManager) .reader(fetchViolatedProductDetailJpaCursorItemReader()) .processor(fetchViolatedProductDetailItemProcessor) .writer(fetchViolatedProductDetailJpaItemWriter()) - .listener(new FetchViolatedProductDetailStepListener()) + .listener(baseStepExecutionListener) .faultTolerant() .build(); } @@ -81,37 +81,4 @@ public JpaItemWriter fetchViolatedProductDetailJpaItemWriter() .entityManagerFactory(emf) .build(); } - - //-- Execution Listener --// - public static class FetchViolatedProductDetailJobListener implements JobExecutionListener { - @Override - public void beforeJob(JobExecution jobExecution) { - log.info("{} starts", jobExecution.getJobInstance().getJobName()); - } - - @Override - public void afterJob(JobExecution jobExecution) { - log.info("{} ends", jobExecution.getJobInstance().getJobName()); - } - } - - public static class FetchViolatedProductDetailStepListener implements StepExecutionListener { - @Override - public void beforeStep(StepExecution stepExecution) { - log.info("{} starts", stepExecution.getStepName()); - } - - @Override - public ExitStatus afterStep(StepExecution stepExecution) { - log.info("StepExecutionListener - afterStep, step name: {}, status: {}", stepExecution.getStepName(), - stepExecution.getStatus()); - log.info( - "StepExecutionListener - ReadCount: {}, WriteCount: {}, FilterCount: {}, ReadSkipCount: {}, ProcessSkipCount: {}, WriteSkipCount: {}", - stepExecution.getReadCount(), stepExecution.getWriteCount(), stepExecution.getFilterCount(), - stepExecution.getReadSkipCount(), stepExecution.getProcessSkipCount(), - stepExecution.getWriteSkipCount()); - return StepExecutionListener.super.afterStep(stepExecution); - } - } - } diff --git a/module-batch/src/main/java/com/kernel360/modulebatch/violatedproduct/job/core/FetchViolatedProductListJobConfig.java b/module-batch/src/main/java/com/kernel360/modulebatch/violatedproduct/job/core/FetchViolatedProductListJobConfig.java index 8b926e0f..2f7ce077 100644 --- a/module-batch/src/main/java/com/kernel360/modulebatch/violatedproduct/job/core/FetchViolatedProductListJobConfig.java +++ b/module-batch/src/main/java/com/kernel360/modulebatch/violatedproduct/job/core/FetchViolatedProductListJobConfig.java @@ -1,5 +1,6 @@ package com.kernel360.modulebatch.violatedproduct.job.core; +import com.kernel360.modulebatch.global.BaseJobExecutionListener; import com.kernel360.modulebatch.violatedproduct.dto.ViolatedProductDto; import com.kernel360.modulebatch.violatedproduct.job.infra.FetchViolatedProductListItemReader; import com.kernel360.modulebatch.violatedproduct.job.infra.FetchViolatedProductListItemWriter; @@ -8,8 +9,6 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.batch.core.ExitStatus; import org.springframework.batch.core.Job; -import org.springframework.batch.core.JobExecution; -import org.springframework.batch.core.JobExecutionListener; import org.springframework.batch.core.Step; import org.springframework.batch.core.StepExecution; import org.springframework.batch.core.StepExecutionListener; @@ -17,6 +16,7 @@ import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.step.builder.StepBuilder; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.transaction.PlatformTransactionManager; @@ -24,18 +24,20 @@ @Slf4j @Configuration @RequiredArgsConstructor +@ConditionalOnProperty(name = "job.name", havingValue = FetchViolatedProductListJobConfig.JOB_NAME) public class FetchViolatedProductListJobConfig { + public static final String JOB_NAME = "FetchViolatedProductListJob"; private final FetchViolatedProductListItemReader fetchViolatedProductListItemReader; - private final FetchViolatedProductListItemWriter fetchViolatedProductListItemWriter; + private final BaseJobExecutionListener baseJobExecutionListener; @Bean - public Job fetchViolatedProductListJob(JobRepository jobRepository, + public Job FetchViolatedProductListJob(JobRepository jobRepository, @Qualifier("fetchViolatedProductListStep") Step fetchViolatedProductListStep) { - return new JobBuilder("fetchViolatedProductListJob", jobRepository) + return new JobBuilder("FetchViolatedProductListJob", jobRepository) .start(fetchViolatedProductListStep) - .listener(new FetchViolatedProductListJobListener()) + .listener(baseJobExecutionListener) .build(); } @@ -43,7 +45,7 @@ public Job fetchViolatedProductListJob(JobRepository jobRepository, public Step fetchViolatedProductListStep(JobRepository jobRepository, PlatformTransactionManager transactionManager) { - return new StepBuilder("fetchViolatedProductListStep", jobRepository) + return new StepBuilder(JOB_NAME, jobRepository) ., List>chunk(10, transactionManager) .reader(fetchViolatedProductListItemReader) .writer(fetchViolatedProductListItemWriter) @@ -53,18 +55,6 @@ public Step fetchViolatedProductListStep(JobRepository jobRepository, } //-- Execution Listener --// - public static class FetchViolatedProductListJobListener implements JobExecutionListener { - @Override - public void beforeJob(JobExecution jobExecution) { - log.info("{} starts", jobExecution.getJobInstance().getJobName()); - } - - @Override - public void afterJob(JobExecution jobExecution) { - log.info("{} ends", jobExecution.getJobInstance().getJobName()); - } - } - public static class FetchViolatedProductListStepListener implements StepExecutionListener { @Override public void beforeStep(StepExecution stepExecution) { diff --git a/module-batch/src/main/resources/application-dev.yml b/module-batch/src/main/resources/application-dev.yml index 37673f76..b2cf05e6 100644 --- a/module-batch/src/main/resources/application-dev.yml +++ b/module-batch/src/main/resources/application-dev.yml @@ -13,7 +13,7 @@ spring: datasource: driver-class-name: org.postgresql.Driver - url: ${DB_URL} + url: ${VULTR_DB_URL} username: ${DB_USER} password: ${DB_PW} hikari: diff --git a/module-batch/src/main/resources/application.yml b/module-batch/src/main/resources/application.yml index 281fb115..7f889555 100644 --- a/module-batch/src/main/resources/application.yml +++ b/module-batch/src/main/resources/application.yml @@ -1,6 +1,9 @@ spring: profiles: active: local + batch: + job: + name: ${job.name:NONE} jasypt: encryptor: diff --git a/module-common/build.gradle b/module-common/build.gradle index 5d09994c..3f6c2125 100644 --- a/module-common/build.gradle +++ b/module-common/build.gradle @@ -44,6 +44,9 @@ dependencies { // aws s3 implementation 'com.amazonaws:aws-java-sdk-s3:1.12.625' + + // commons-lang3 + implementation 'org.apache.commons:commons-lang3:3.12.0' } tasks.named('test') { diff --git a/module-common/src/main/java/com/kernel360/code/common/CommonErrorCode.java b/module-common/src/main/java/com/kernel360/code/common/CommonErrorCode.java index 75e1c04a..d9705e73 100644 --- a/module-common/src/main/java/com/kernel360/code/common/CommonErrorCode.java +++ b/module-common/src/main/java/com/kernel360/code/common/CommonErrorCode.java @@ -11,7 +11,8 @@ public enum CommonErrorCode implements ErrorCode { INVALID_REQUEST_HEADERS(HttpStatus.BAD_REQUEST.value(), "E005", "요청한 헤더가 존재하지 않음"), INVALID_ARGUMENT(HttpStatus.BAD_REQUEST.value(), "E006", "요청 파라미터가 없거나 비어있거나, 요청 파라미터의 이름이 메서드 인수의 이름과 일치하지 않습니다"), INVALID_HTTP_REQUEST_METHOD(HttpStatus.BAD_REQUEST.value(), "E007", "요청 URL 에서 지원하지 않는 HTTP Method 입니다."), - INVALID_REQUEST_PARAMETER(HttpStatus.BAD_REQUEST.value(), "E008", "요청한 파라미터가 존재하지 않음"); + INVALID_REQUEST_PARAMETER(HttpStatus.BAD_REQUEST.value(), "E008", "요청한 파라미터가 존재하지 않음"), + ARGUMENT_VALIDATION_FAILED(HttpStatus.BAD_REQUEST.value(), "E009", "유효하지 않은 데이터가 존재함"); private final int status; private final String code; diff --git a/module-common/src/main/java/com/kernel360/code/common/ValidationMessage.java b/module-common/src/main/java/com/kernel360/code/common/ValidationMessage.java new file mode 100644 index 00000000..e4ecf0d5 --- /dev/null +++ b/module-common/src/main/java/com/kernel360/code/common/ValidationMessage.java @@ -0,0 +1,5 @@ +package com.kernel360.code.common; + +public class ValidationMessage { + public static final String INVALID_WORD_PARAMETER = "비속어를 포함할 수 없습니다"; +} diff --git a/module-common/src/main/java/com/kernel360/handler/GlobalExceptionHandler.java b/module-common/src/main/java/com/kernel360/handler/GlobalExceptionHandler.java index def82f82..0ff7b527 100644 --- a/module-common/src/main/java/com/kernel360/handler/GlobalExceptionHandler.java +++ b/module-common/src/main/java/com/kernel360/handler/GlobalExceptionHandler.java @@ -8,9 +8,11 @@ import io.jsonwebtoken.MalformedJwtException; import io.jsonwebtoken.security.SignatureException; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.HttpRequestMethodNotSupportedException; +import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.MissingRequestHeaderException; import org.springframework.web.bind.MissingServletRequestParameterException; import org.springframework.web.bind.annotation.ExceptionHandler; @@ -75,21 +77,21 @@ protected ResponseEntity handleMissingHeaderException(final Missi } @ExceptionHandler(IllegalArgumentException.class) - protected ResponseEntity handleIllegalArgumentException(final IllegalArgumentException e){ - log.error("handleIllegalArgumentException",e); + protected ResponseEntity handleIllegalArgumentException(final IllegalArgumentException e) { + log.error("handleIllegalArgumentException", e); final ErrorResponse response = ErrorResponse.of(CommonErrorCode.INVALID_ARGUMENT); - return new ResponseEntity<>(response,HttpStatus.BAD_REQUEST); + return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST); } @ExceptionHandler(HttpRequestMethodNotSupportedException.class) - protected ResponseEntity handleHttpRequestMethodNotSupportedException(final HttpRequestMethodNotSupportedException e){ - log.error("handleHttpRequestMethodNotSupportedException",e); + protected ResponseEntity handleHttpRequestMethodNotSupportedException(final HttpRequestMethodNotSupportedException e) { + log.error("handleHttpRequestMethodNotSupportedException", e); - final ErrorResponse response =ErrorResponse.of(CommonErrorCode.INVALID_HTTP_REQUEST_METHOD); + final ErrorResponse response = ErrorResponse.of(CommonErrorCode.INVALID_HTTP_REQUEST_METHOD); - return new ResponseEntity<>(response,HttpStatus.BAD_REQUEST); + return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST); } @ExceptionHandler(MissingServletRequestParameterException.class) @@ -100,4 +102,18 @@ protected ResponseEntity handleMissingParameterException(final Mi return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST); } + + @ExceptionHandler(MethodArgumentNotValidException.class) + protected ResponseEntity handleMethodArgumentNotValidException(final MethodArgumentNotValidException e) { + log.error("handleMethodArgumentNotValidException", e); + + String defaultMessage = e.getBindingResult().getFieldError().getDefaultMessage(); + if (!StringUtils.isBlank(defaultMessage)) { + defaultMessage = String.format(" (%s)", defaultMessage); + } + + final ErrorResponse response = ErrorResponse.of(CommonErrorCode.ARGUMENT_VALIDATION_FAILED, defaultMessage); + + return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST); + } } diff --git a/module-common/src/main/java/com/kernel360/response/ErrorResponse.java b/module-common/src/main/java/com/kernel360/response/ErrorResponse.java index c4dde2ed..80175ab5 100644 --- a/module-common/src/main/java/com/kernel360/response/ErrorResponse.java +++ b/module-common/src/main/java/com/kernel360/response/ErrorResponse.java @@ -14,4 +14,12 @@ public static ErrorResponse of(ErrorCode code) { code.getMessage() ); } + + public static ErrorResponse of(ErrorCode code, String detailMessage) { + return new ErrorResponse( + code.getStatus(), + code.getCode(), + code.getMessage() + detailMessage + ); + } } diff --git a/module-common/src/main/java/com/kernel360/utils/file/FileUtils.java b/module-common/src/main/java/com/kernel360/utils/file/FileUtils.java index bd7ddbca..b54a26b0 100644 --- a/module-common/src/main/java/com/kernel360/utils/file/FileUtils.java +++ b/module-common/src/main/java/com/kernel360/utils/file/FileUtils.java @@ -25,14 +25,14 @@ public class FileUtils { @Value("${aws.s3.bucket.name}") private String bucketName; - @Value("${aws.s3.bucket.url}") - private String bucketUrl; - @Value("${spring.profiles.active}") private String profile; - public String upload(S3BucketPath s3BucketPath, MultipartFile multipartFile) { - String filePath = makeFilePath(s3BucketPath); + @Value("${module.name}") + private String moduleName; + + public String upload(String path, MultipartFile multipartFile) { + String filePath = makeFilePath(path); String filename = makeFileName(); String fileExtension = getFileExtension(multipartFile.getOriginalFilename()); String fileKey = String.join("", filePath, filename, fileExtension); @@ -54,17 +54,15 @@ public String upload(S3BucketPath s3BucketPath, MultipartFile multipartFile) { throw new BusinessException(CommonErrorCode.FAIL_FILE_UPLOAD); } - return amazonS3.getUrl(bucketName, fileKey).toString(); + return fileKey; } - private String makeFilePath(S3BucketPath s3BucketPath) { + private String makeFilePath(String path) { + if (!path.endsWith("/")) { + path += "/"; + } - return String.join( - "/", - profile, - s3BucketPath.getModulePath(), - s3BucketPath.getDomainPath(), - s3BucketPath.getCustomPath()); + return String.join("/", profile, moduleName, path); } private String makeFileName() { @@ -76,6 +74,7 @@ private String makeFileName() { } private String getFileExtension(String originalFilename) { + // TODO: 확장자에 대한 검사 로직 추가할 수 있을지 체크 try { return originalFilename.substring(originalFilename.lastIndexOf(".")); } catch (StringIndexOutOfBoundsException e) { @@ -83,7 +82,7 @@ private String getFileExtension(String originalFilename) { } } - public void delete(String fileUrl) { - amazonS3.deleteObject(bucketName, fileUrl.split(bucketUrl)[1]); + public void delete(String fileKey) { + amazonS3.deleteObject(bucketName, fileKey); } } diff --git a/module-common/src/main/java/com/kernel360/utils/file/S3BucketPath.java b/module-common/src/main/java/com/kernel360/utils/file/S3BucketPath.java deleted file mode 100644 index 3a7d0f27..00000000 --- a/module-common/src/main/java/com/kernel360/utils/file/S3BucketPath.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.kernel360.utils.file; - -import lombok.Builder; -import lombok.Getter; - -@Getter -@Builder -public class S3BucketPath { - private final String modulePath; - private final String domainPath; - private final String customPath; -} diff --git a/module-domain/src/main/java/com/kernel360/member/entity/Admin.java b/module-domain/src/main/java/com/kernel360/admin/entity/Admin.java similarity index 97% rename from module-domain/src/main/java/com/kernel360/member/entity/Admin.java rename to module-domain/src/main/java/com/kernel360/admin/entity/Admin.java index 42d0865b..41f691ee 100644 --- a/module-domain/src/main/java/com/kernel360/member/entity/Admin.java +++ b/module-domain/src/main/java/com/kernel360/admin/entity/Admin.java @@ -1,4 +1,4 @@ -package com.kernel360.member.entity; +package com.kernel360.admin.entity; import com.kernel360.base.BaseEntity; diff --git a/module-domain/src/main/java/com/kernel360/admin/repository/AdminRepository.java b/module-domain/src/main/java/com/kernel360/admin/repository/AdminRepository.java new file mode 100644 index 00000000..c7bea761 --- /dev/null +++ b/module-domain/src/main/java/com/kernel360/admin/repository/AdminRepository.java @@ -0,0 +1,7 @@ +package com.kernel360.admin.repository; + +import com.kernel360.admin.entity.Admin; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface AdminRepository extends JpaRepository { +} diff --git a/module-domain/src/main/java/com/kernel360/base/BaseEntity.java b/module-domain/src/main/java/com/kernel360/base/BaseEntity.java index 917e6ec9..84d319bf 100644 --- a/module-domain/src/main/java/com/kernel360/base/BaseEntity.java +++ b/module-domain/src/main/java/com/kernel360/base/BaseEntity.java @@ -3,6 +3,7 @@ import jakarta.persistence.Column; import jakarta.persistence.EntityListeners; import jakarta.persistence.MappedSuperclass; +import java.time.LocalDateTime; import lombok.Getter; import org.springframework.data.annotation.CreatedBy; import org.springframework.data.annotation.CreatedDate; @@ -10,8 +11,6 @@ import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; -import java.time.LocalDate; - @Getter @MappedSuperclass @EntityListeners(AuditingEntityListener.class) @@ -21,7 +20,7 @@ public abstract class BaseEntity { @Column(name = "created_at", nullable = false, updatable = false) @CreatedDate - private LocalDate createdAt; + private LocalDateTime createdAt; @Column(name = "created_by", nullable = false, length = MAX_STRING_LENGTH, updatable = false) @CreatedBy @@ -29,7 +28,7 @@ public abstract class BaseEntity { @Column(name = "modified_at") @LastModifiedDate - private LocalDate modifiedAt; + private LocalDateTime modifiedAt; @Column(name = "modified_by", length = MAX_STRING_LENGTH ) @LastModifiedBy diff --git a/module-domain/src/main/java/com/kernel360/base/BaseRawEntity.java b/module-domain/src/main/java/com/kernel360/base/BaseRawEntity.java index 0ed81785..dc57bfb3 100644 --- a/module-domain/src/main/java/com/kernel360/base/BaseRawEntity.java +++ b/module-domain/src/main/java/com/kernel360/base/BaseRawEntity.java @@ -3,7 +3,7 @@ import jakarta.persistence.Column; import jakarta.persistence.EntityListeners; import jakarta.persistence.MappedSuperclass; -import java.time.LocalDate; +import java.time.LocalDateTime; import lombok.Getter; import org.springframework.data.annotation.CreatedBy; import org.springframework.data.annotation.CreatedDate; @@ -18,7 +18,7 @@ public class BaseRawEntity { // FIXME :: createdAt 자동 생성시, update 로직 진행중에 null or transient 문제가 발생. 임시방편으로 직접 지정하였으나 이후 수정 필요 @Column(name = "created_at", nullable = false) @CreatedDate - private LocalDate createdAt = LocalDate.now(); + private LocalDateTime createdAt = LocalDateTime.now(); @Column(name = "created_by", nullable = false) @CreatedBy @@ -26,7 +26,7 @@ public class BaseRawEntity { @Column(name = "modified_at") @LastModifiedDate - private LocalDate modifiedAt; + private LocalDateTime modifiedAt; @Column(name = "modified_by") @LastModifiedBy diff --git a/module-domain/src/main/java/com/kernel360/bbs/entity/BBS.java b/module-domain/src/main/java/com/kernel360/bbs/entity/BBS.java new file mode 100644 index 00000000..d250ce54 --- /dev/null +++ b/module-domain/src/main/java/com/kernel360/bbs/entity/BBS.java @@ -0,0 +1,52 @@ +package com.kernel360.bbs.entity; + +import com.kernel360.base.BaseEntity; +import com.kernel360.member.entity.Member; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Entity +@Table(name = "bbs") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class BBS extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "bbs_id_gen") + @SequenceGenerator(name = "bbs_id_gen", sequenceName = "bbs_bbs_no_seq") + private Long bbsNo; + + private Long upperNo; + + private String type; + + private String title; + + private String contents; + + private Boolean isVisible; + + private Long viewCount; + + @ManyToOne(fetch = FetchType.LAZY, optional = false) + @JoinColumn(name = "member_no", nullable = false, updatable = false) + private Member member; + + private BBS(Long bbsNo, Long upperNo, String type, String title, String contents, Boolean isVisible, Long viewConut, Member member) { + this.bbsNo = bbsNo; + this.upperNo = upperNo; + this.type = type; + this.title = title; + this.contents = contents; + this.isVisible = isVisible; + this.member = member; + this.viewCount = viewConut; + } + + public static BBS save(Long bbsNo, Long upperNo, String type, String title, String contents, Boolean isVisible, Long viewCount, Member member){ + + return new BBS (bbsNo, upperNo, type, title, contents, isVisible, viewCount, member); + } + +} diff --git a/module-domain/src/main/java/com/kernel360/bbs/repository/BBSRepositoryJPA.java b/module-domain/src/main/java/com/kernel360/bbs/repository/BBSRepositoryJPA.java new file mode 100644 index 00000000..b8d0a451 --- /dev/null +++ b/module-domain/src/main/java/com/kernel360/bbs/repository/BBSRepositoryJPA.java @@ -0,0 +1,16 @@ +package com.kernel360.bbs.repository; + +import com.kernel360.bbs.entity.BBS; +import jakarta.persistence.Id; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface BBSRepositoryJPA extends JpaRepository { + BBS findOneByBbsNo(Long bbsNo); + + Page findAllByUpperNo(Long upperNo, Pageable pageable); + + + void deleteByBbsNo(Long bbsNo); +} diff --git a/module-domain/src/main/java/com/kernel360/carinfo/entity/CarInfo.java b/module-domain/src/main/java/com/kernel360/carinfo/entity/CarInfo.java index c20d8852..ad106037 100644 --- a/module-domain/src/main/java/com/kernel360/carinfo/entity/CarInfo.java +++ b/module-domain/src/main/java/com/kernel360/carinfo/entity/CarInfo.java @@ -29,19 +29,14 @@ public class CarInfo extends BaseEntity { @OneToOne @JoinColumn(name = "member_no") private Member member; - @Column(name = "car_type") private Integer carType; - @Column(name = "car_size") private Integer carSize; - @Column(name = "car_color") private Integer carColor; - @Column(name = "driving_env") private Integer drivingEnv; - @Column(name = "parking_env") private Integer parkingEnv; diff --git a/module-domain/src/main/java/com/kernel360/commoncode/entity/CommonCode.java b/module-domain/src/main/java/com/kernel360/commoncode/entity/CommonCode.java index ff8fd1d2..3c3402ff 100644 --- a/module-domain/src/main/java/com/kernel360/commoncode/entity/CommonCode.java +++ b/module-domain/src/main/java/com/kernel360/commoncode/entity/CommonCode.java @@ -23,7 +23,7 @@ public class CommonCode extends BaseEntity { private String codeName; @Column(name = "upper_no") - private Integer upperNo; + private Long upperNo; @Column(name = "upper_name", length = Integer.MAX_VALUE) private String upperName; diff --git a/module-domain/src/main/java/com/kernel360/file/entity/File.java b/module-domain/src/main/java/com/kernel360/file/entity/File.java new file mode 100644 index 00000000..b51aada0 --- /dev/null +++ b/module-domain/src/main/java/com/kernel360/file/entity/File.java @@ -0,0 +1,51 @@ +package com.kernel360.file.entity; + +import com.kernel360.base.BaseEntity; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Entity +@Table(name = "file") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class File extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "file_id_gen") + @SequenceGenerator(name = "file_id_gen", sequenceName = "file_file_no_seq") + @Column(name = "file_no", nullable = false) + private Long fileNo; + + @Column(name = "file_name", nullable = false) + private String fileName; + + @Column(name = "file_key", nullable = false) + private String fileKey; + + @Column(name = "file_url", nullable = false, length = 500) + private String fileUrl; + + @Column(name = "reference_type", nullable = false, length = 50) + private String referenceType; + + @Column(name = "reference_no", nullable = false) + private Long referenceNo; + + private File(Long fileNo, String fileName, String fileKey, String fileUrl, String referenceType, Long referenceNo) { + this.fileNo = fileNo; + this.fileName = fileName; + this.fileKey = fileKey; + this.fileUrl = fileUrl; + this.referenceType = referenceType; + this.referenceNo = referenceNo; + } + + public static File of(Long fileNo, String fileName, String fileKey, String fileUrl, String referenceType, Long referenceNo) { + return new File(fileNo, fileName, fileKey, fileUrl, referenceType, referenceNo); + } + + public static File of(Long fileNo, String fileName, String fileKey, String fileUrl) { + return new File(fileNo, fileName, fileKey, fileUrl, null, null); + } +} \ No newline at end of file diff --git a/module-domain/src/main/java/com/kernel360/file/entity/FileReferType.java b/module-domain/src/main/java/com/kernel360/file/entity/FileReferType.java new file mode 100644 index 00000000..8f1887f4 --- /dev/null +++ b/module-domain/src/main/java/com/kernel360/file/entity/FileReferType.java @@ -0,0 +1,20 @@ +package com.kernel360.file.entity; + +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public enum FileReferType { + REVIEW("review", "RV"), + WASHZONE_REVIEW("washzone-review", "WZRV"); + + private final String domain; + private final String code; + + public String getDomain() { + return domain; + } + + public String getCode() { + return code; + } +} diff --git a/module-domain/src/main/java/com/kernel360/file/repository/FileRepositoryJpa.java b/module-domain/src/main/java/com/kernel360/file/repository/FileRepositoryJpa.java new file mode 100644 index 00000000..a92374a6 --- /dev/null +++ b/module-domain/src/main/java/com/kernel360/file/repository/FileRepositoryJpa.java @@ -0,0 +1,12 @@ +package com.kernel360.file.repository; + +import com.kernel360.file.entity.File; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface FileRepositoryJpa extends JpaRepository { + List findByReferenceTypeAndReferenceNo(String referenceType, Long referenceNo); + + void deleteByReferenceTypeAndReferenceNo(String referenceType, Long referenceNo); +} diff --git a/module-domain/src/main/java/com/kernel360/member/entity/Member.java b/module-domain/src/main/java/com/kernel360/member/entity/Member.java index 27e154e7..1dbc557c 100644 --- a/module-domain/src/main/java/com/kernel360/member/entity/Member.java +++ b/module-domain/src/main/java/com/kernel360/member/entity/Member.java @@ -122,4 +122,18 @@ public void updateFromInfo(int gender, int age) { this.gender = gender; this.age = age; } + + /** + * review request + **/ + private Member (Long memberNo) { + this.memberNo = memberNo; + } + + /** + * review request + **/ + public static Member of(Long memberNo) { + return new Member(memberNo); + } } \ No newline at end of file diff --git a/module-domain/src/main/java/com/kernel360/member/repository/MemberRepository.java b/module-domain/src/main/java/com/kernel360/member/repository/MemberRepositoryJpa.java similarity index 81% rename from module-domain/src/main/java/com/kernel360/member/repository/MemberRepository.java rename to module-domain/src/main/java/com/kernel360/member/repository/MemberRepositoryJpa.java index bf021316..9d207130 100644 --- a/module-domain/src/main/java/com/kernel360/member/repository/MemberRepository.java +++ b/module-domain/src/main/java/com/kernel360/member/repository/MemberRepositoryJpa.java @@ -6,8 +6,10 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; +import java.util.Optional; -public interface MemberRepository extends JpaRepository { + +public interface MemberRepositoryJpa extends JpaRepository { Member findOneByIdAndPassword(String id, String password); @@ -21,4 +23,6 @@ public interface MemberRepository extends JpaRepository { @Query("SELECT m FROM Member m WHERE m.email = :email AND m.accountType = 'PLATFORM'") Member findOneByEmailForAccountTypeByPlatform(@Param("email") String email); + + Optional findOneByIdAndMemberNo(String id, Long memberNo); } diff --git a/module-domain/src/main/java/com/kernel360/product/entity/SafetyStatusConverter.java b/module-domain/src/main/java/com/kernel360/product/converter/SafetyStatusConverter.java similarity index 89% rename from module-domain/src/main/java/com/kernel360/product/entity/SafetyStatusConverter.java rename to module-domain/src/main/java/com/kernel360/product/converter/SafetyStatusConverter.java index 7712e96d..1e919ec6 100644 --- a/module-domain/src/main/java/com/kernel360/product/entity/SafetyStatusConverter.java +++ b/module-domain/src/main/java/com/kernel360/product/converter/SafetyStatusConverter.java @@ -1,5 +1,6 @@ -package com.kernel360.product.entity; +package com.kernel360.product.converter; +import com.kernel360.product.enumset.SafetyStatus; import jakarta.persistence.AttributeConverter; import jakarta.persistence.Converter; import java.util.stream.Stream; diff --git a/module-domain/src/main/java/com/kernel360/product/entity/Product.java b/module-domain/src/main/java/com/kernel360/product/entity/Product.java index 730a0973..4e6f2b09 100644 --- a/module-domain/src/main/java/com/kernel360/product/entity/Product.java +++ b/module-domain/src/main/java/com/kernel360/product/entity/Product.java @@ -1,6 +1,7 @@ package com.kernel360.product.entity; import com.kernel360.base.BaseEntity; +import com.kernel360.product.enumset.SafetyStatus; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; @@ -170,12 +171,11 @@ private Product( this.violationInfo = violationInfo; } - private Product( - Long productNo, - String productName - ) { + /** + * review request + **/ + private Product(Long productNo) { this.productNo = productNo; - this.productName = productName; } public static Product of(String productName, @@ -213,8 +213,11 @@ public static Product of(String productName, fluorescentWhitening, manufactureType, manufactureMethod, manufactureNation, violation_info); } - public static Product of(Long productNo, String productName) { - return new Product(productNo, productName); + /** + * review request + **/ + public static Product of(Long productNo) { + return new Product(productNo); } public void updateDetail( diff --git a/module-domain/src/main/java/com/kernel360/product/entity/SafetyStatus.java b/module-domain/src/main/java/com/kernel360/product/enumset/SafetyStatus.java similarity index 83% rename from module-domain/src/main/java/com/kernel360/product/entity/SafetyStatus.java rename to module-domain/src/main/java/com/kernel360/product/enumset/SafetyStatus.java index 9d4846ae..1fa6d3bb 100644 --- a/module-domain/src/main/java/com/kernel360/product/entity/SafetyStatus.java +++ b/module-domain/src/main/java/com/kernel360/product/enumset/SafetyStatus.java @@ -1,4 +1,4 @@ -package com.kernel360.product.entity; +package com.kernel360.product.enumset; import lombok.Getter; diff --git a/module-domain/src/main/java/com/kernel360/product/repository/ProductRepositoryJpa.java b/module-domain/src/main/java/com/kernel360/product/repository/ProductRepositoryJpa.java index b6f742c7..d5094ab3 100644 --- a/module-domain/src/main/java/com/kernel360/product/repository/ProductRepositoryJpa.java +++ b/module-domain/src/main/java/com/kernel360/product/repository/ProductRepositoryJpa.java @@ -1,7 +1,7 @@ package com.kernel360.product.repository; import com.kernel360.product.entity.Product; -import com.kernel360.product.entity.SafetyStatus; +import com.kernel360.product.enumset.SafetyStatus; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; @@ -12,7 +12,7 @@ import java.util.List; import java.util.Optional; -public interface ProductRepositoryJpa extends JpaRepository { +public interface ProductRepositoryJpa extends JpaRepository { Page findAllByOrderByViewCountDesc(Pageable pageable); @@ -75,6 +75,6 @@ Page findByProductWithKeywordAndSafetyStatus(@Param("keyword") String k @Param("safetyStatus") SafetyStatus safetyStatus, Pageable pageable); - Page findByProductNameContaining(String keyword, Pageable pageable); + Page findByProductNameContaining(String keyword, Pageable pageable); } diff --git a/module-domain/src/main/java/com/kernel360/review/entity/Review.java b/module-domain/src/main/java/com/kernel360/review/entity/Review.java index 641348a9..c209aa6b 100644 --- a/module-domain/src/main/java/com/kernel360/review/entity/Review.java +++ b/module-domain/src/main/java/com/kernel360/review/entity/Review.java @@ -32,22 +32,38 @@ public class Review extends BaseEntity { @Column(name = "star_rating", nullable = false, precision = 3, scale = 1) private BigDecimal starRating; - @Column(name = "title", nullable = false, length = Integer.MAX_VALUE) + @Column(name = "title", nullable = false) private String title; - @Column(name = "contents", nullable = false, length = Integer.MAX_VALUE) + @Column(name = "contents", nullable = false, length = 4000) private String contents; - private Review(Long reviewNo, Product product, Member member, BigDecimal starRating, String title, String contents) { + @Column(name = "is_visible", nullable = false) + private Boolean isVisible; + + private Review(Long reviewNo, Product product, Member member, BigDecimal starRating, String title, String contents, Boolean isVisible) { this.reviewNo = reviewNo; this.product = product; this.member = member; this.starRating = starRating; this.title = title; this.contents = contents; + this.isVisible = isVisible; + } + + private Review(Long reviewNo, BigDecimal starRating, String title, String contents, Boolean isVisible) { + this.reviewNo = reviewNo; + this.starRating = starRating; + this.title = title; + this.contents = contents; + this.isVisible = isVisible; + } + + public static Review of(Long reviewNo, Product product, Member member, BigDecimal starRating, String title, String contents, Boolean isVisible) { + return new Review(reviewNo, product, member, starRating, title, contents, isVisible); } - public static Review of(Long reviewNo, Product product, Member member, BigDecimal starRating, String title, String contents) { - return new Review(reviewNo, product, member, starRating, title, contents); + public static Review of(Long reviewNo, BigDecimal starRating, String title, String contents, Boolean isVisible) { + return new Review(reviewNo, starRating, title, contents, isVisible); } } \ No newline at end of file diff --git a/module-domain/src/main/java/com/kernel360/review/repository/ReviewRepositoryJpa.java b/module-domain/src/main/java/com/kernel360/review/repository/ReviewRepositoryJpa.java index 23628bcd..f3d6c05e 100644 --- a/module-domain/src/main/java/com/kernel360/review/repository/ReviewRepositoryJpa.java +++ b/module-domain/src/main/java/com/kernel360/review/repository/ReviewRepositoryJpa.java @@ -4,5 +4,4 @@ import org.springframework.data.jpa.repository.JpaRepository; public interface ReviewRepositoryJpa extends JpaRepository { - Review findByReviewNo(Long reviewNo); } diff --git a/module-domain/src/main/java/com/kernel360/washinfo/entity/WashInfo.java b/module-domain/src/main/java/com/kernel360/washinfo/entity/WashInfo.java index 730534a3..0cb44133 100644 --- a/module-domain/src/main/java/com/kernel360/washinfo/entity/WashInfo.java +++ b/module-domain/src/main/java/com/kernel360/washinfo/entity/WashInfo.java @@ -25,18 +25,16 @@ public class WashInfo extends BaseEntity { @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "wash_info_id_gen") @SequenceGenerator(name = "wash_info_id_gen", sequenceName = "wash_info_wash_no_seq", allocationSize = 50) @Column(name = "wash_no", nullable = false) - private Integer washNo; + private Long washNo; @OneToOne(fetch = FetchType.LAZY) @JoinColumn(name = "member_no", nullable = false) private Member member; - @Column(name = "wash_count") + @Column(name= "wash_count") private Integer washCount; - @Column(name = "monthly_expense") private Integer monthlyExpense; - @Column(name = "interest") private Integer interest; diff --git a/module-domain/src/main/java/com/kernel360/washzone/entity/WashZone.java b/module-domain/src/main/java/com/kernel360/washzone/entity/WashZone.java index 85e886d1..4e835e1f 100644 --- a/module-domain/src/main/java/com/kernel360/washzone/entity/WashZone.java +++ b/module-domain/src/main/java/com/kernel360/washzone/entity/WashZone.java @@ -48,4 +48,18 @@ public static WashZone of(String name, String address, Double latitude, Double longitude, String type, String remarks) { return new WashZone(name, address, latitude, longitude, type, remarks); } + + /** + * review request + **/ + public WashZone(Long washZoneNo) { + this.washZoneNo = washZoneNo; + } + + /** + * review request + **/ + public static WashZone of(Long washZoneNo) { + return new WashZone(washZoneNo); + } } diff --git a/module-domain/src/main/java/com/kernel360/washzonereview/entity/WashzoneReview.java b/module-domain/src/main/java/com/kernel360/washzonereview/entity/WashzoneReview.java new file mode 100644 index 00000000..66c3bef5 --- /dev/null +++ b/module-domain/src/main/java/com/kernel360/washzonereview/entity/WashzoneReview.java @@ -0,0 +1,70 @@ +package com.kernel360.washzonereview.entity; + +import com.kernel360.base.BaseEntity; +import com.kernel360.member.entity.Member; +import com.kernel360.washzone.entity.WashZone; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.math.BigDecimal; + +@Getter +@Entity +@Table(name = "washzone_review") +@NoArgsConstructor(access = AccessLevel.PROTECTED) + +public class WashzoneReview extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "washzone_review_id_gen") + @SequenceGenerator(name = "washzone_review_id_gen", sequenceName = "washzone_review_washzone_review_no_seq") + @Column(name = "washzone_review_no", nullable = false) + private Long washzoneReviewNo; + + @ManyToOne(fetch = FetchType.LAZY, optional = false) + @JoinColumn(name = "washzone_no", nullable = false, updatable = false) + private WashZone washzone; + + @ManyToOne(fetch = FetchType.LAZY, optional = false) + @JoinColumn(name = "member_no", nullable = false, updatable = false) + private Member member; + + @Column(name = "star_rating", nullable = false, precision = 3, scale = 1) + private BigDecimal starRating; + + @Column(name = "title", nullable = false) + private String title; + + @Column(name = "contents", nullable = false, length = 4000) + private String contents; + + @Column(name = "is_visible", nullable = false) + private Boolean isVisible; + + public WashzoneReview(Long washzoneReviewNo, WashZone washzone, Member member, BigDecimal starRating, String title, String contents, Boolean isVisible) { + this.washzoneReviewNo = washzoneReviewNo; + this.washzone = washzone; + this.member = member; + this.starRating = starRating; + this.title = title; + this.contents = contents; + this.isVisible = isVisible; + } + + public WashzoneReview(Long washzoneReviewNo, BigDecimal starRating, String title, String contents, Boolean isVisible) { + this.washzoneReviewNo = washzoneReviewNo; + this.starRating = starRating; + this.title = title; + this.contents = contents; + this.isVisible = isVisible; + } + + public static WashzoneReview of(Long washzoneReviewNo, WashZone washzone, Member member, BigDecimal starRating, String title, String contents, Boolean isVisible) { + return new WashzoneReview(washzoneReviewNo, washzone, member, starRating, title, contents, isVisible); + } + + public static WashzoneReview of(Long washzoneReviewNo, BigDecimal starRating, String title, String contents, Boolean isVisible) { + return new WashzoneReview(washzoneReviewNo, starRating, title, contents, isVisible); + } +} \ No newline at end of file diff --git a/module-domain/src/main/java/com/kernel360/washzonereview/repository/WashzoneReviewRepositoryJpa.java b/module-domain/src/main/java/com/kernel360/washzonereview/repository/WashzoneReviewRepositoryJpa.java new file mode 100644 index 00000000..08ea39ba --- /dev/null +++ b/module-domain/src/main/java/com/kernel360/washzonereview/repository/WashzoneReviewRepositoryJpa.java @@ -0,0 +1,7 @@ +package com.kernel360.washzonereview.repository; + +import com.kernel360.washzonereview.entity.WashzoneReview; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface WashzoneReviewRepositoryJpa extends JpaRepository { +}