diff --git a/src/main/java/com/api/TaveShot/domain/recommend/controller/RecommendController.java b/src/main/java/com/api/TaveShot/domain/recommend/controller/RecommendController.java index ec0a0bb..2fd5c2e 100644 --- a/src/main/java/com/api/TaveShot/domain/recommend/controller/RecommendController.java +++ b/src/main/java/com/api/TaveShot/domain/recommend/controller/RecommendController.java @@ -1,6 +1,7 @@ package com.api.TaveShot.domain.recommend.controller; import com.api.TaveShot.domain.post.post.dto.response.PostResponse; +import com.api.TaveShot.domain.recommend.dto.RecProRequestDto; import com.api.TaveShot.domain.recommend.dto.RecProResponseDto; import com.api.TaveShot.domain.recommend.dto.RecResponseDto; import com.api.TaveShot.domain.recommend.service.RecommendService; @@ -8,6 +9,7 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.parameters.RequestBody; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import lombok.RequiredArgsConstructor; @@ -15,6 +17,7 @@ import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.io.IOException; @@ -30,9 +33,7 @@ public class RecommendController { @Operation(summary = "사용자 기반 문제 추천", description = "해당 유저가 푼 문제들을 바탕으로" + " 같은 티어의 사용자가 푼 문제들을 관련성이 높은 순으로 추천합니다.") @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "추천 성공", - content = @Content(mediaType = MediaType.MULTIPART_FORM_DATA_VALUE, - schema = @Schema(implementation = PostResponse.class))) + @ApiResponse(responseCode = "200", description = "추천 성공") }) @GetMapping("/rival") public SuccessResponse getUserBasedProList() throws IOException { @@ -42,13 +43,11 @@ public SuccessResponse getUserBasedProList() throws IOException @Operation(summary = "태그 관련 문제 추천", description = "원하는 문제와 태그가 같은 관련성이 높은 문제를 추천합니다.") @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "추천 성공", - content = @Content(mediaType = MediaType.MULTIPART_FORM_DATA_VALUE, - schema = @Schema(implementation = PostResponse.class))) + @ApiResponse(responseCode = "200", description = "추천 성공") }) @GetMapping("/problem") - public SuccessResponse getSolvedProList() throws IOException { - RecResponseDto responseDto = recommendService.getListByProblem(); + public SuccessResponse getSolvedProList(@RequestParam(value = "solvedRecentId") int solvedNumber) throws IOException { + RecResponseDto responseDto = recommendService.getListByProblem(solvedNumber); return new SuccessResponse<>(responseDto); } } diff --git a/src/main/java/com/api/TaveShot/domain/recommend/converter/StringListConverter.java b/src/main/java/com/api/TaveShot/domain/recommend/converter/StringListConverter.java new file mode 100644 index 0000000..7d14d94 --- /dev/null +++ b/src/main/java/com/api/TaveShot/domain/recommend/converter/StringListConverter.java @@ -0,0 +1,32 @@ +package com.api.TaveShot.domain.recommend.converter; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Converter; + +import java.util.List; + +@Converter +public class StringListConverter implements AttributeConverter, String> { + + private final ObjectMapper mapper = new ObjectMapper(); + + @Override + public String convertToDatabaseColumn(List dataList){ + try{ + return mapper.writeValueAsString(dataList); + } catch(JsonProcessingException e){ + throw new RuntimeException(e); + } + } + + @Override + public List convertToEntityAttribute(String data) { + try{ + return mapper.readValue(data, List.class); + } catch (JsonProcessingException e){ + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/com/api/TaveShot/domain/recommend/domain/ProblemElement.java b/src/main/java/com/api/TaveShot/domain/recommend/domain/ProblemElement.java new file mode 100644 index 0000000..40b6880 --- /dev/null +++ b/src/main/java/com/api/TaveShot/domain/recommend/domain/ProblemElement.java @@ -0,0 +1,30 @@ +package com.api.TaveShot.domain.recommend.domain; + +import com.api.TaveShot.domain.recommend.converter.StringListConverter; +import jakarta.persistence.*; +import lombok.*; + +import java.util.ArrayList; +import java.util.List; + +@Entity +@Table(name = "Problem") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +@AllArgsConstructor +@Builder +public class ProblemElement { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private Integer problemId; + + private String title; + private Integer acceptedUserCount; + private Integer bojLevel; + + private String bojKey; + private String bojTagId; +} diff --git a/src/main/java/com/api/TaveShot/domain/recommend/dto/RecProDetailResponseDto.java b/src/main/java/com/api/TaveShot/domain/recommend/dto/RecProDetailResponseDto.java new file mode 100644 index 0000000..2d31245 --- /dev/null +++ b/src/main/java/com/api/TaveShot/domain/recommend/dto/RecProDetailResponseDto.java @@ -0,0 +1,18 @@ +package com.api.TaveShot.domain.recommend.dto; + +import lombok.Builder; +import lombok.Getter; +import lombok.ToString; + +import java.util.List; + +@Getter +@ToString +@Builder +public class RecProDetailResponseDto { + + private String title; + private String problemId; + private String tier; + private List bojTag; +} diff --git a/src/main/java/com/api/TaveShot/domain/recommend/dto/RecProRequestDto.java b/src/main/java/com/api/TaveShot/domain/recommend/dto/RecProRequestDto.java new file mode 100644 index 0000000..b35866d --- /dev/null +++ b/src/main/java/com/api/TaveShot/domain/recommend/dto/RecProRequestDto.java @@ -0,0 +1,13 @@ +package com.api.TaveShot.domain.recommend.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Builder +public class RecProRequestDto { + + private int solvedRecentId; +} diff --git a/src/main/java/com/api/TaveShot/domain/recommend/dto/RecResponseDto.java b/src/main/java/com/api/TaveShot/domain/recommend/dto/RecResponseDto.java index f6bf5c6..a8cb0db 100644 --- a/src/main/java/com/api/TaveShot/domain/recommend/dto/RecResponseDto.java +++ b/src/main/java/com/api/TaveShot/domain/recommend/dto/RecResponseDto.java @@ -12,7 +12,7 @@ @Builder public class RecResponseDto { - private List result; + private List result; private String rightCnt; private String wrongCnt; diff --git a/src/main/java/com/api/TaveShot/domain/recommend/dto/UserCrawlingDto.java b/src/main/java/com/api/TaveShot/domain/recommend/dto/UserCrawlingDto.java index f7d9a20..0c9a789 100644 --- a/src/main/java/com/api/TaveShot/domain/recommend/dto/UserCrawlingDto.java +++ b/src/main/java/com/api/TaveShot/domain/recommend/dto/UserCrawlingDto.java @@ -1,5 +1,6 @@ package com.api.TaveShot.domain.recommend.dto; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; import lombok.Getter; import lombok.ToString; @@ -10,9 +11,19 @@ @Builder @ToString public class UserCrawlingDto { + + @Schema(description = "유저 순위") private String rank; + + @Schema(description = "맞은 문제 수") private String rightCnt; + + @Schema(description = "틀린 문제 수") private String wrongCnt; + + @Schema(description = "유저 레벨") private String tier; + + @Schema(description = "맞은 문제 리스트") private List solvedProblemList; } diff --git a/src/main/java/com/api/TaveShot/domain/recommend/repository/ProblemElementRepository.java b/src/main/java/com/api/TaveShot/domain/recommend/repository/ProblemElementRepository.java new file mode 100644 index 0000000..03c9429 --- /dev/null +++ b/src/main/java/com/api/TaveShot/domain/recommend/repository/ProblemElementRepository.java @@ -0,0 +1,16 @@ +package com.api.TaveShot.domain.recommend.repository; + +import com.api.TaveShot.domain.recommend.domain.ProblemElement; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface ProblemElementRepository extends JpaRepository { + + @Query("select p from ProblemElement as p where p.problemId=:problemId") + ProblemElement findByProblemId(Integer problemId); + +} diff --git a/src/main/java/com/api/TaveShot/domain/recommend/service/RecommendService.java b/src/main/java/com/api/TaveShot/domain/recommend/service/RecommendService.java index 8d2bf98..4d34039 100644 --- a/src/main/java/com/api/TaveShot/domain/recommend/service/RecommendService.java +++ b/src/main/java/com/api/TaveShot/domain/recommend/service/RecommendService.java @@ -2,10 +2,9 @@ import com.api.TaveShot.domain.Member.domain.Member; import com.api.TaveShot.domain.Member.repository.MemberRepository; -import com.api.TaveShot.domain.recommend.domain.TierCount; -import com.api.TaveShot.domain.recommend.dto.RecResponseDto; -import com.api.TaveShot.domain.recommend.dto.UserCrawlingDto; -import com.api.TaveShot.domain.recommend.dto.RecProResponseDto; +import com.api.TaveShot.domain.recommend.domain.ProblemElement; +import com.api.TaveShot.domain.recommend.dto.*; +import com.api.TaveShot.domain.recommend.repository.ProblemElementRepository; import com.api.TaveShot.domain.recommend.repository.TierCountRepository; import com.api.TaveShot.global.exception.ApiException; import com.api.TaveShot.global.exception.ErrorType; @@ -18,6 +17,10 @@ import org.springframework.web.reactive.function.client.WebClient; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; @Service @Slf4j @@ -27,6 +30,7 @@ public class RecommendService { private final CrawlingService crawlingService; private final MemberRepository memberRepository; private final TierCountRepository tierCountRepository; + private final ProblemElementRepository problemElementRepository; @Value("${lambda.secret.url1}") private String lambda1; @@ -36,10 +40,7 @@ public class RecommendService { // 사용자 기반 추천 서비스 public RecResponseDto getListByUser() throws IOException { - Member currentMember1 = getCurrentMember(); - String bojName = currentMember1.getBojName(); - - UserCrawlingDto dto = crawlingService.getUserInfo(bojName); + UserCrawlingDto dto = getUserInfo(); WebClient webClient = WebClient.builder() .baseUrl(lambda1) @@ -53,54 +54,128 @@ public RecResponseDto getListByUser() throws IOException { Integer tierCount = tierCountRepository.findByTier(Integer.parseInt(dto.getTier())); + List proDetailResponseDtos = getProblemDetail(proList); + RecResponseDto responseDto = RecResponseDto.builder() .rivalCnt(Integer.toString(tierCount)) .rightCnt(dto.getRightCnt()) .wrongCnt(dto.getWrongCnt()) .userRank(dto.getRank()) - .result(proList.getResult()) + .result(proDetailResponseDtos) .build(); return responseDto; } // 문제 기반 추천 서비스 - public RecResponseDto getListByProblem() throws IOException { - Member currentMember2 = getCurrentMember(); - String bojName = currentMember2.getBojName(); + public RecResponseDto getListByProblem(int solvedRecentId) throws IOException { - UserCrawlingDto dto = crawlingService.getUserInfo(bojName); + UserCrawlingDto dto = getUserInfo(); WebClient webClient = WebClient.builder() .baseUrl(lambda2) .build(); + RecProRequestDto requestDto = RecProRequestDto.builder() + .solvedRecentId(solvedRecentId) + .build(); + RecProResponseDto proList = webClient.post() - .body(BodyInserters.fromValue(dto)) + .body(BodyInserters.fromValue(requestDto)) .retrieve() .bodyToMono(RecProResponseDto.class) .block(); Integer tierCount = tierCountRepository.findByTier(Integer.parseInt(dto.getTier())); + List proDetailResponseDtos = getProblemDetail(proList); + RecResponseDto responseDto = RecResponseDto.builder() .rivalCnt(Integer.toString(tierCount)) .rightCnt(dto.getRightCnt()) .wrongCnt(dto.getWrongCnt()) .userRank(dto.getRank()) - .result(proList.getResult()) + .result(proDetailResponseDtos) .build(); return responseDto; } - private Member getCurrentMember(){ + public UserCrawlingDto getUserInfo() throws IOException { + Member currentMember2 = getCurrentMember(); + String bojName = currentMember2.getBojName(); +// String bojName = "wjdrhs3473"; + UserCrawlingDto dto = crawlingService.getUserInfo(bojName); + + return dto; + } + + private Member getCurrentMember(){ Member currentMember = SecurityUtil.getCurrentMember(); Long currentMemberId = currentMember.getId(); return memberRepository.findById(currentMemberId) .orElseThrow(() -> new ApiException(ErrorType._USER_NOT_FOUND_DB)); } + private String getTierName(Integer bojLevel){ + String[] tiers = {"BRONZE", "SILVER", "GOLD", "PLATINUM", "DIAMOND", "RUBY", "MASTER"}; + if(bojLevel <= 5) + return tiers[0]; + else if(bojLevel <= 10) + return tiers[1]; + else if(bojLevel <= 15) + return tiers[2]; + else if(bojLevel <= 20) + return tiers[3]; + else if(bojLevel <= 25) + return tiers[4]; + else if(bojLevel <= 30) + return tiers[5]; + + return tiers[6]; + } + + private List extractWords(String bojTags){ + List tags = new ArrayList<>(); + + Pattern pattern = Pattern.compile("\\b\\w+\\b"); + Matcher matcher = pattern.matcher(bojTags); + + while(matcher.find()){ + tags.add(matcher.group()); + } + + return tags; + } + + private List getProblemDetail(RecProResponseDto proList){ + List result = proList.getResult(); + List proDetailResponseDto = new ArrayList<>(); + + // 문제 세부 정보 찾기 + for(int i=0;i<15;i++){ + Integer num = Integer.parseInt(result.get(i)); + log.info("num:{}", num); + try { + ProblemElement problemElement = problemElementRepository.findByProblemId(num); + log.info("{}, {}, {}", problemElement.getProblemId(), problemElement.getBojLevel(), problemElement.getBojKey()); + String tierName = getTierName(problemElement.getBojLevel()); + List tags = extractWords(problemElement.getBojKey()); + RecProDetailResponseDto detailResponseDto = RecProDetailResponseDto.builder() + .title(problemElement.getTitle()) + .problemId(String.valueOf(num)) + .tier(tierName) + .bojTag(tags) + .build(); + proDetailResponseDto.add(detailResponseDto); + } catch(Exception e){ + continue; + } + + } + + return proDetailResponseDto; + } } diff --git a/src/main/java/com/api/TaveShot/domain/search/controller/SearchController.java b/src/main/java/com/api/TaveShot/domain/search/controller/SearchController.java index 04905d0..7dd6e9d 100644 --- a/src/main/java/com/api/TaveShot/domain/search/controller/SearchController.java +++ b/src/main/java/com/api/TaveShot/domain/search/controller/SearchController.java @@ -32,9 +32,7 @@ public class SearchController { @Operation(summary = "해답 블로그 반환", description = "원하는 문제와 언어를 검색하여 " + "백준 해답 블로그 리스트를 제공합니다.") @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "해답 제시 성공", - content = @Content(mediaType = MediaType.MULTIPART_FORM_DATA_VALUE, - schema = @Schema(implementation = PostResponse.class))) + @ApiResponse(responseCode = "200", description = "해답 제시 성공") }) @GetMapping public SuccessResponse getList(@RequestParam String query){ @@ -45,9 +43,7 @@ public SuccessResponse getList(@RequestParam String query @Operation(summary = "해답 블로그 새로고침 수행", description = "처음 반환한 해답 블로그 리스트 외에 더 보고 싶은 블로그가 있다면" + " 새로고침을 통해 다른 리스트 제공") @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "새로고침 성공", - content = @Content(mediaType = MediaType.MULTIPART_FORM_DATA_VALUE, - schema = @Schema(implementation = PostResponse.class))) + @ApiResponse(responseCode = "200", description = "새로고침 성공") }) @GetMapping("/refresh") public SuccessResponse getRefresh(@RequestParam String query, @RequestParam(value = "start") int index){