diff --git a/build.gradle b/build.gradle index 3be3e43..61dd418 100644 --- a/build.gradle +++ b/build.gradle @@ -59,9 +59,6 @@ dependencies { runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.5' runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.5' - //JSON 타입 사용 - implementation 'io.hypersistence:hypersistence-utils-hibernate-62:3.7.0' - //queryDsl implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta' annotationProcessor "com.querydsl:querydsl-apt:5.0.0:jakarta" diff --git a/src/main/java/UMC/career_mate/domain/recruit/Recruit.java b/src/main/java/UMC/career_mate/domain/recruit/Recruit.java index 8bd4706..3360bb1 100644 --- a/src/main/java/UMC/career_mate/domain/recruit/Recruit.java +++ b/src/main/java/UMC/career_mate/domain/recruit/Recruit.java @@ -1,16 +1,12 @@ package UMC.career_mate.domain.recruit; -import io.hypersistence.utils.hibernate.type.json.JsonType; import jakarta.persistence.*; - import java.time.LocalDateTime; -import java.util.List; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import org.hibernate.annotations.Type; @Entity @Table(name = "recruits") @@ -45,9 +41,8 @@ public class Recruit { private String employmentName; // 고용(근무) 형태 값 private String salaryName; // 연봉 값 - @Type(JsonType.class) - @Column(columnDefinition = "json") - private List jobNames; // 직무 키워드 + @Column(columnDefinition = "TEXT") + private String hashtags; // 직무 키워드 private String region; // 근무 지역 private String industryName; // 산업군 diff --git a/src/main/java/UMC/career_mate/domain/recruit/controller/RecruitController.java b/src/main/java/UMC/career_mate/domain/recruit/controller/RecruitController.java index 30cd06c..b01ebb8 100644 --- a/src/main/java/UMC/career_mate/domain/recruit/controller/RecruitController.java +++ b/src/main/java/UMC/career_mate/domain/recruit/controller/RecruitController.java @@ -31,8 +31,8 @@ public class RecruitController { private final RecruitQueryService recruitQueryService; @Operation( - summary = "추천 채용 공고 조회 API", - description = """ + summary = "추천 채용 공고 조회 API", + description = """ 추천 채용 공고를 조회하는 API입니다.\n\n page의 값은 1부터 시작이고, 기본 값은 1입니다.\n\n size의 기본값은 6입니다.\n\n @@ -43,26 +43,26 @@ public class RecruitController { """) @GetMapping public ApiResponse> getRecommendRecruitList( - @RequestParam(defaultValue = "1", required = false) int page, - @RequestParam(defaultValue = "6", required = false) int size, - @RequestParam(defaultValue = "POSTING_DESC", required = false) RecruitSortType recruitSortType, - @LoginMember Member member + @RequestParam(defaultValue = "1", required = false) int page, + @RequestParam(defaultValue = "6", required = false) int size, + @RequestParam(defaultValue = "POSTING_DESC", required = false) RecruitSortType recruitSortType, + @LoginMember Member member ) { return ApiResponse.onSuccess( - recruitQueryService.getRecommendRecruitList(page, size, recruitSortType, member)); + recruitQueryService.getRecommendRecruitList(page, size, recruitSortType, member)); } @Operation( - summary = "채용 공고 요약 페이지 조회 API", - description = """ + summary = "채용 공고 요약 페이지 조회 API", + description = """ 채용 공고 요약 페이지를 조회하는 API입니다.\n\n recruitId : 조회하려는 채용 공고 pk 값 """) @GetMapping("/{recruitId}") public ResponseEntity> getRecruitInfo(@PathVariable Long recruitId, - @LoginMember Member member) { + @LoginMember Member member) { return ResponseEntity.ok( - ApiResponse.onSuccess(recruitQueryService.findRecruitInfo(member, recruitId))); + ApiResponse.onSuccess(recruitQueryService.findRecruitInfo(member, recruitId))); } @Deprecated diff --git a/src/main/java/UMC/career_mate/domain/recruit/converter/RecruitConverter.java b/src/main/java/UMC/career_mate/domain/recruit/converter/RecruitConverter.java index 2a54b93..82008f1 100644 --- a/src/main/java/UMC/career_mate/domain/recruit/converter/RecruitConverter.java +++ b/src/main/java/UMC/career_mate/domain/recruit/converter/RecruitConverter.java @@ -15,7 +15,6 @@ import java.time.LocalDateTime; import java.time.ZoneId; import java.time.temporal.ChronoUnit; -import java.util.Arrays; import java.util.List; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringEscapeUtils; @@ -42,7 +41,7 @@ public static Recruit toRecruit(Job job, String recruitUrl, String companyInfoUr .educationLevelName(educationLevel.getDescription()) .employmentName(job.position().jobType().name()) .salaryName(job.salary().name()) - .jobNames(Arrays.asList(job.position().jobCode().name())) + .hashtags(job.position().jobCode().name()) .region(StringEscapeUtils.unescapeHtml4( job.position().location().name())) .industryName(job.position().industry().name()) @@ -67,13 +66,6 @@ public static RecruitThumbNailInfoDTO toRecruitThumbNailInfoDTO(Recruit recruit, .title(recruit.getTitle()) .deadLine(formatDeadLine(recruit)) .isScrapped(isScrapped) - .experienceLevelCode(recruit.getExperienceLevelCode()) - .experienceLevelMin(recruit.getExperienceLevelMin()) - .experienceLevelMax(recruit.getExperienceLevelMax()) - .experienceLevelName(recruit.getExperienceLevelName()) - .educationLevelCode(recruit.getEducationLevelCode()) - .educationLevelName(recruit.getEducationLevelName()) - .postingDate(recruit.getPostingDate()) .build(); } diff --git a/src/main/java/UMC/career_mate/domain/recruit/dto/response/RecommendRecruitsDTO.java b/src/main/java/UMC/career_mate/domain/recruit/dto/response/RecommendRecruitsDTO.java index 4634a54..7838db5 100644 --- a/src/main/java/UMC/career_mate/domain/recruit/dto/response/RecommendRecruitsDTO.java +++ b/src/main/java/UMC/career_mate/domain/recruit/dto/response/RecommendRecruitsDTO.java @@ -1,6 +1,5 @@ package UMC.career_mate.domain.recruit.dto.response; -import java.time.LocalDateTime; import java.util.List; import lombok.Builder; @@ -16,18 +15,7 @@ public record RecruitThumbNailInfoDTO( String companyName, String title, String deadLine, - boolean isScrapped, - - /** - * TODO: 아래로는 (필터링, 정렬) 잘 되는지 확인용 데이터, 나중에 삭제 예정 - */ - Integer experienceLevelCode, // 경력 코드 : 0(경력무관), 1(신입), 2(경력), 3(신입/경력) - Integer experienceLevelMin, // 경력 년수 최소 값 - Integer experienceLevelMax, // 경력 년수 최대 값 - String experienceLevelName, - Integer educationLevelCode, // 학력 코드 0(학력무관), 1(고등학교졸업), 2(대학졸업(2,3년)), 3(대학졸업(4년)), 4(석사졸업), 5(박사졸업) 등 - String educationLevelName, - LocalDateTime postingDate + boolean isScrapped ) { } diff --git a/src/main/java/UMC/career_mate/domain/recruit/enums/RecruitKeyword.java b/src/main/java/UMC/career_mate/domain/recruit/enums/RecruitKeyword.java index 415cb9f..66c8ff7 100644 --- a/src/main/java/UMC/career_mate/domain/recruit/enums/RecruitKeyword.java +++ b/src/main/java/UMC/career_mate/domain/recruit/enums/RecruitKeyword.java @@ -7,116 +7,164 @@ public enum RecruitKeyword { BACKEND { @Override - public List getIncludeKeywordList() { - return List.of("Back", "Backend", "Back-end", "BACK", "백엔드", "서버", "시스템"); + public List getIncludeTitleKeywordList() { + return List.of("Back", "Backend", "Back-end", "백엔드", "서버", "시스템"); } @Override - public List getExcludeKeywordList() { + public List getExcludeTitleKeywordList() { + return null; + } + + @Override + public List getIncludeHashtagKeywordList() { return null; } } , BACKEND_SPRING { @Override - public List getIncludeKeywordList() { - return List.of("Back", "Backend", "Back-end", "BACK", "백엔드", "서버", "시스템"); + public List getIncludeTitleKeywordList() { + return List.of("Back", "Backend", "Back-end", "백엔드", "서버", "시스템"); } @Override - public List getExcludeKeywordList() { - return List.of("Node", "Node.js", "NODE", "javascript", "Python", "Django", "C++", "PHP", "C#"); + public List getExcludeTitleKeywordList() { + return List.of("Node", "Node.js", "javascript", "Python", "Django", "C++", "PHP", "C#"); + } + + @Override + public List getIncludeHashtagKeywordList() { + return List.of("Spring", "SpringBoot"); } }, BACKEND_NODE { @Override - public List getIncludeKeywordList() { - return List.of("Back", "Backend", "Back-end", "BACK", "백엔드", "서버", "시스템"); + public List getIncludeTitleKeywordList() { + return List.of("Back", "Backend", "Back-end", "백엔드", "서버", "시스템"); + } + + @Override + public List getExcludeTitleKeywordList() { + return List.of("JAVA", "Spring", "Python", "Django", "C++", "PHP", "C#"); } @Override - public List getExcludeKeywordList() { - return List.of("java", "JAVA", "spring", "Spring", "SPRING", "Python", "Django", "C++", "PHP", "C#"); + public List getIncludeHashtagKeywordList() { + return List.of("Node.js"); } }, BACKEND_DJANGO { @Override - public List getIncludeKeywordList() { - return List.of("Back", "Backend", "Back-end", "BACK", "백엔드", "서버", "시스템"); + public List getIncludeTitleKeywordList() { + return List.of("Back", "Backend", "Back-end", "백엔드", "서버", "시스템"); + } + + @Override + public List getExcludeTitleKeywordList() { + return List.of("JAVA", "Spring", "javascript", "Node", "Node.js", "C++", "PHP", "C#"); } @Override - public List getExcludeKeywordList() { - return List.of("java", "JAVA", "spring", "Spring", "SPRING", "javascript", "Node", - "Node.js", "NODE", "C++", "PHP", "C#"); + public List getIncludeHashtagKeywordList() { + return List.of("Django", "Python"); } }, FRONTEND { @Override - public List getIncludeKeywordList() { - return List.of("Front", "FRONT", "Frontend", "Front-end", "프론트엔드", "프론트"); + public List getIncludeTitleKeywordList() { + return List.of("Front", "Frontend", "Front-end", "프론트엔드", "프론트"); + } + + @Override + public List getExcludeTitleKeywordList() { + return null; } @Override - public List getExcludeKeywordList() { + public List getIncludeHashtagKeywordList() { return null; } }, FRONTEND_REACT { @Override - public List getIncludeKeywordList() { - return List.of("react"); + public List getIncludeTitleKeywordList() { + return List.of("React"); } @Override - public List getExcludeKeywordList() { + public List getExcludeTitleKeywordList() { return null; } + + @Override + public List getIncludeHashtagKeywordList() { + return List.of("React"); + } }, FRONTEND_IOS { @Override - public List getIncludeKeywordList() { - return List.of("ios"); + public List getIncludeTitleKeywordList() { + return List.of("iOS"); } @Override - public List getExcludeKeywordList() { + public List getExcludeTitleKeywordList() { return null; } + + @Override + public List getIncludeHashtagKeywordList() { + return List.of("iOS", "Swift"); + } }, FRONTEND_ANDROID { @Override - public List getIncludeKeywordList() { - return List.of("android"); + public List getIncludeTitleKeywordList() { + return List.of("Android"); } @Override - public List getExcludeKeywordList() { + public List getExcludeTitleKeywordList() { return null; } + + @Override + public List getIncludeHashtagKeywordList() { + return List.of("Android"); + } }, DESIGNER { @Override - public List getIncludeKeywordList() { - return List.of("웹 디자이너", "웹디자이너", "UI", "UX", "디자인", "design", "DESIGN"); + public List getIncludeTitleKeywordList() { + return List.of("웹 디자이너", "웹디자이너", "UI", "UX", "디자인", "design"); + } + + @Override + public List getExcludeTitleKeywordList() { + return List.of("영상디자인", "영상 디자인"); } @Override - public List getExcludeKeywordList() { - return List.of("영상디자인", "영상 디자인"); // 굿즈 디자인은? 뺴야하는가 + public List getIncludeHashtagKeywordList() { + return List.of("모바일디자인", "앱디자인", "웹디자인", "UI/UX디자인"); } }, PM { @Override - public List getIncludeKeywordList() { - return List.of("PM", "Project Manager", "Product Manager", "PROJECT MANAGER", - "PRODUCT MANAGER", "프로젝트 매니저", "프로덕트 매니저"); + public List getIncludeTitleKeywordList() { + return List.of("PM", "Project Manager", "Product Manager", "프로젝트 매니저", "프로덕트 매니저"); } @Override - public List getExcludeKeywordList() { + public List getExcludeTitleKeywordList() { return null; } + + @Override + public List getIncludeHashtagKeywordList() { + return List.of("개발PM", "PM(프로젝트매니저)", "PL(프로젝트리더)"); + } } ; @@ -141,6 +189,7 @@ public static RecruitKeyword getRecruitKeywordFromProfileJob(Job job) { } - public abstract List getIncludeKeywordList(); - public abstract List getExcludeKeywordList(); + public abstract List getIncludeTitleKeywordList(); + public abstract List getExcludeTitleKeywordList(); + public abstract List getIncludeHashtagKeywordList(); } diff --git a/src/main/java/UMC/career_mate/domain/recruit/repository/querydsl/RecruitQueryRepositoryImpl.java b/src/main/java/UMC/career_mate/domain/recruit/repository/querydsl/RecruitQueryRepositoryImpl.java index 8823f5c..2186d67 100644 --- a/src/main/java/UMC/career_mate/domain/recruit/repository/querydsl/RecruitQueryRepositoryImpl.java +++ b/src/main/java/UMC/career_mate/domain/recruit/repository/querydsl/RecruitQueryRepositoryImpl.java @@ -32,12 +32,17 @@ public Page findByKeywordWithBooleanBuilder(RecruitKeyword recruitKeywo Pageable pageable) { BooleanBuilder builder = new BooleanBuilder(); - // includeKeywordList에 있는 키워드 중 하나라도 포함되어야 함 - filterIncludeKeywordList(recruitKeyword.getIncludeKeywordList(), builder); + // includeTitleKeywordList에 있는 키워드 중 하나라도 포함되어야 함 + filterIncludeTitleKeywordList(recruitKeyword.getIncludeTitleKeywordList(), builder); - // excludeKeywordList에 있는 키워드가 포함되지 않아야 함 - if (Objects.nonNull(recruitKeyword.getExcludeKeywordList())) { - filterExcludeKeywordList(recruitKeyword.getExcludeKeywordList(), builder); + // excludeTitleKeywordList에 있는 키워드가 포함되지 않아야 함 + if (Objects.nonNull(recruitKeyword.getExcludeTitleKeywordList())) { + filterExcludeTitleKeywordList(recruitKeyword.getExcludeTitleKeywordList(), builder); + } + + // includeHashtagKeywordList에 있는 키워드 중 하나라도 포함되어야 함 + if (Objects.nonNull(recruitKeyword.getIncludeHashtagKeywordList())) { + filterIncludeHashtagKeywordList(recruitKeyword.getIncludeHashtagKeywordList(), builder); } // 학력 필터링 @@ -66,28 +71,42 @@ public Page findByKeywordWithBooleanBuilder(RecruitKeyword recruitKeywo return new PageImpl<>(result, pageable, total); } - private void filterIncludeKeywordList(List includekeywordList, BooleanBuilder builder) { - BooleanBuilder includeBuilder = new BooleanBuilder(); + private void filterIncludeTitleKeywordList(List includekeywordList, BooleanBuilder builder) { + BooleanBuilder includeTitleBuilder = new BooleanBuilder(); for (String keyword : includekeywordList) { if (isEnglish(keyword)) { - includeBuilder.or(recruit.title.containsIgnoreCase(keyword)); + includeTitleBuilder.or(recruit.title.containsIgnoreCase(keyword)); } else { - includeBuilder.or(recruit.title.contains(keyword)); + includeTitleBuilder.or(recruit.title.contains(keyword)); } } - builder.and(includeBuilder); + builder.and(includeTitleBuilder); } - private void filterExcludeKeywordList(List excludeKeywordList, BooleanBuilder builder) { - BooleanBuilder excludeBuilder = new BooleanBuilder(); + private void filterExcludeTitleKeywordList(List excludeKeywordList, BooleanBuilder builder) { + BooleanBuilder excludeTitleBuilder = new BooleanBuilder(); for (String keyword : excludeKeywordList) { if (isEnglish(keyword)) { - excludeBuilder.or(recruit.title.containsIgnoreCase(keyword)); + excludeTitleBuilder.or(recruit.title.containsIgnoreCase(keyword)); } else { - excludeBuilder.or(recruit.title.contains(keyword)); + excludeTitleBuilder.or(recruit.title.contains(keyword)); } } - builder.and(excludeBuilder.not()); + builder.and(excludeTitleBuilder.not()); + } + + private void filterIncludeHashtagKeywordList(List includeHashTagKeywordList, + BooleanBuilder builder) { + BooleanBuilder includeHashtagBuilder = new BooleanBuilder(); + for (String keyword : includeHashTagKeywordList) { + if (isEnglish(keyword)) { + includeHashtagBuilder.or(recruit.hashtags.containsIgnoreCase(keyword)); + } else { + includeHashtagBuilder.or(recruit.hashtags.contains(keyword)); + } + } + + builder.and(includeHashtagBuilder); } private void filterEducation(EducationLevel educationLevel, BooleanBuilder builder) {