diff --git a/src/main/java/gyeongdan/article/controller/ArticleController.java b/src/main/java/gyeongdan/article/controller/ArticleController.java index 6d2b319..b376313 100644 --- a/src/main/java/gyeongdan/article/controller/ArticleController.java +++ b/src/main/java/gyeongdan/article/controller/ArticleController.java @@ -7,9 +7,13 @@ import gyeongdan.article.service.ArticleService; import gyeongdan.util.CommonResponse; import gyeongdan.util.JwtUtil; +import gyeongdan.util.annotation.LoginAuthenticated; import jakarta.annotation.Nullable; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.*; import java.util.List; @@ -19,20 +23,22 @@ @RestController @RequestMapping("/api/article") @RequiredArgsConstructor +@Slf4j public class ArticleController { private final ArticleService articleService; private final JwtUtil jwtUtil; // 게시글 상세 조회 - // 단, 유저가 보면 조회기록 저장하고, 유저가 아닌 경우 조회수만 증가시키기. + // 단, 유저가 보면 조회기록 저장하고, 유저가 아닌 경우 조회수만 증가시키기 @GetMapping("/detail") public ResponseEntity getArticle(@RequestParam Long id, - @RequestHeader @Nullable String accessToken) { // id : 기사id, access token : 유저의 접근 권한 + @RequestHeader(name = "Authorization") @Nullable String accessToken) { // id : 기사id, access token : 유저의 접근 권한 Optional userId = Optional.empty(); if (accessToken != null && !accessToken.isEmpty()) { userId = jwtUtil.getUserId(jwtUtil.resolveToken(accessToken)); } + // 기사 조회 Article article = articleService.getValidArticleById(id, userId); // 조회수 증가 @@ -47,20 +53,19 @@ public ResponseEntity getArticle(@RequestParam Long id, @GetMapping("") public ResponseEntity getArticles() { List articles = articleService.getValidArticles(); - return ResponseEntity.ok(new CommonResponse<>(articles, "게시글 조회 성공", true)); + return ResponseEntity.ok(new CommonResponse<>(articles, "게시글 모두 조회 성공", true)); } // 최근 조회한 기사 3개 가져오기 + @LoginAuthenticated @GetMapping("/recent") - public ResponseEntity getRecentViewedArticles(@RequestHeader @Nullable String accessToken) { - // userId 디코딩 - Optional userId = Optional.empty(); - if (accessToken != null && !accessToken.isEmpty()) { - userId = jwtUtil.getUserId(jwtUtil.resolveToken(accessToken)); - } + public ResponseEntity getRecentViewedArticles() { + // 현재 로그인한 사용자의 ID를 가져옴 + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + Long userId = Long.valueOf(authentication.getName()); // 최근 조회한 기사 3개 가져오기 - List
recentViewedArticles = articleService.getRecentViewedArticles(userId.orElse(null)); + List
recentViewedArticles = articleService.getRecentViewedArticles(userId); List finalResponse = recentViewedArticles.stream() .map(article -> new ArticleAllResponse( @@ -77,7 +82,7 @@ public ResponseEntity getRecentViewedArticles(@RequestHeader @Nullable String return ResponseEntity.ok(new CommonResponse<>(finalResponse, "가장 최근에 조회한 게시글 3개 조회 성공", true)); } - // 이번주 가장 인기 있는 기사 10개 조회 (조회수 기준) + // 금주 가장 인기 있는 기사 10개 조회 (조회수 기준) @GetMapping("/popular") public ResponseEntity getPopularArticles() { List popularArticles = articleService.getPopularArticles(); diff --git a/src/main/java/gyeongdan/article/controller/RecommendController.java b/src/main/java/gyeongdan/article/controller/RecommendController.java index cf86655..b815040 100644 --- a/src/main/java/gyeongdan/article/controller/RecommendController.java +++ b/src/main/java/gyeongdan/article/controller/RecommendController.java @@ -3,32 +3,30 @@ import gyeongdan.article.dto.ArticleAllResponse; import gyeongdan.article.service.RecommendService; import gyeongdan.util.CommonResponse; -import gyeongdan.util.JwtUtil; -import jakarta.annotation.Nullable; +import gyeongdan.util.annotation.LoginAuthenticated; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.*; import java.util.List; -import java.util.Optional; @RestController @RequestMapping("/api/article") @RequiredArgsConstructor public class RecommendController { - private final JwtUtil jwtUtil; private final RecommendService recommendService; + @LoginAuthenticated @GetMapping("/recommend") - public ResponseEntity getUserTypeArticles(@RequestHeader @Nullable String accessToken) { // id : 기사id, access token : 유저의 접근 권한 - Optional userId = Optional.empty(); - if (accessToken != null && !accessToken.isEmpty()) { - userId = jwtUtil.getUserId(jwtUtil.resolveToken(accessToken)); - } + public ResponseEntity getUserTypeArticles() { + // 현재 로그인한 사용자의 ID를 가져옴 + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + Long userId = Long.valueOf(authentication.getName()); - // 추천 알고리즘 + // 추천 알고리즘 결과를 List에 저장 후 반환 List articles = recommendService.recommendArticleById(userId); - return ResponseEntity.ok(new CommonResponse<>(articles, "추천 기사 10개 가져오기 성공", true)); } } diff --git a/src/main/java/gyeongdan/article/repository/ArticleJpaRepository.java b/src/main/java/gyeongdan/article/repository/ArticleJpaRepository.java index aa797c6..3da5732 100644 --- a/src/main/java/gyeongdan/article/repository/ArticleJpaRepository.java +++ b/src/main/java/gyeongdan/article/repository/ArticleJpaRepository.java @@ -1,10 +1,8 @@ package gyeongdan.article.repository; import gyeongdan.article.domain.Article; -import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; import java.time.LocalDateTime; @@ -12,6 +10,12 @@ @Repository public interface ArticleJpaRepository extends JpaRepository { + // 게시물 전체 조회 : publishedAt이 없는 경우 createdAt으로 정렬. isValid 값이 True인 경우만 조회 + @Query("SELECT a FROM Article a WHERE a.isValid = true ORDER BY " + + "CASE WHEN a.publishedAt IS NULL THEN 1 ELSE 0 END, " + + "a.publishedAt DESC, " + + "a.createdAt DESC") + List
findAllValidOrderByPublishedAtOrCreatedAtDesc(); - List
findTop10ByPublishedAtBetweenOrderByViewCountDesc(LocalDateTime startOfWeek, LocalDateTime endOfDay); + List
findTop10ByPublishedAtBetweenAndIsValidTrueOrderByViewCountDesc(LocalDateTime startOfWeek, LocalDateTime endOfDay); } diff --git a/src/main/java/gyeongdan/article/repository/ArticleRepository.java b/src/main/java/gyeongdan/article/repository/ArticleRepository.java index e117bc5..25e0f08 100644 --- a/src/main/java/gyeongdan/article/repository/ArticleRepository.java +++ b/src/main/java/gyeongdan/article/repository/ArticleRepository.java @@ -16,4 +16,5 @@ public interface ArticleRepository { Article save(Article article); + List
findAllValidOrderByPublishedAtOrCreatedAtDesc(); } diff --git a/src/main/java/gyeongdan/article/repository/ArticleRepositoryImpl.java b/src/main/java/gyeongdan/article/repository/ArticleRepositoryImpl.java index e986cf6..b63b14d 100644 --- a/src/main/java/gyeongdan/article/repository/ArticleRepositoryImpl.java +++ b/src/main/java/gyeongdan/article/repository/ArticleRepositoryImpl.java @@ -26,5 +26,7 @@ public Article save(Article article) { return articleJpaRepository.save(article); } - + public List
findAllValidOrderByPublishedAtOrCreatedAtDesc() { + return articleJpaRepository.findAllValidOrderByPublishedAtOrCreatedAtDesc(); + } } diff --git a/src/main/java/gyeongdan/article/service/ArticleService.java b/src/main/java/gyeongdan/article/service/ArticleService.java index 7043011..0b1529d 100644 --- a/src/main/java/gyeongdan/article/service/ArticleService.java +++ b/src/main/java/gyeongdan/article/service/ArticleService.java @@ -18,7 +18,6 @@ import gyeongdan.article.repository.ArticleViewHistoryJpaRepository; import gyeongdan.user.service.UserManageService; import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Service; @Service @@ -30,15 +29,35 @@ public class ArticleService { private final UserManageService userManageService; private final ArticleJpaRepository articleJpaRepository; + // 게시글 상세 조회 public Article getValidArticleById(Long id, Optional userId) { - Article article = articleRepository.findById(id); - checkArticleVisible(article); // id에 해당하는 게시글이 있는지 확인. 없으면 예외 발생 + Article article = articleRepository.findById(id); // id에 해당하는 기사 가져오기 + + checkArticleVisible(article); // id에 해당하는 기사가 있는지 확인. 없으면 예외 발생 - userId.ifPresent(aLong -> saveViewHistory(aLong, article)); // 만약 userId가 존재하면 조회 기록 저장 + if (userId.isPresent()) { + saveViewHistory(userId.get(), article); // 조회 기록 저장 + } return article; } + // 게시물 전체 조회 + public List getValidArticles() { + List
articles = articleRepository.findAllValidOrderByPublishedAtOrCreatedAtDesc(); + return articles.stream() + .filter(Article::isValid) + .map(article -> new ArticleAllResponse( + article.getId(), + article.getSimpleTitle(), + article.getSimpleContent(), + article.getViewCount(), + article.getCategory(), + Optional.ofNullable(article.getImageUrl()), + article.getPublishedAt() + )) + .collect(Collectors.toList()); + } private static void checkArticleVisible(Article article) { if (Boolean.FALSE.equals(article.getIsValid())) { @@ -69,22 +88,6 @@ public Long updateArticle(ArticleUpdateRequest articleUpdateRequest) { } - public List getValidArticles() { - List
articles = articleRepository.findAll(); - return articles.stream() - .filter(Article::isValid) - .map(article -> new ArticleAllResponse( - article.getId(), - article.getSimpleTitle(), - article.getSimpleContent(), - article.getViewCount(), - article.getCategory(), - Optional.ofNullable(article.getImageUrl()), - article.getPublishedAt() - )) - .collect(Collectors.toList()); - } - // 조회수 증가 메서드 public void incrementViewCount(Article article) { article.setViewCount(article.getViewCount() + 1); @@ -109,14 +112,14 @@ public List
getRecentViewedArticles(Long userId) { .collect(Collectors.toList()); } - // 이번 주 가장 인기 있는 기사 5개 가져오는 메서드 (조회수 기준) + // 금주 가장 인기 있는 기사 10개 가져오는 메서드 (조회수 기준) public List getPopularArticles() { // 오늘을 기준으로 이번 주의 시작과 끝을 구함 (월요일부터 일요일까지) LocalDate today = LocalDate.now(); LocalDateTime mondayDateTime = today.with(DayOfWeek.MONDAY).atStartOfDay(); LocalDateTime sundayDateTime = today.with(DayOfWeek.SUNDAY).plusDays(1).atStartOfDay(); - List
articles = articleJpaRepository.findTop10ByPublishedAtBetweenOrderByViewCountDesc(mondayDateTime, sundayDateTime); + List
articles = articleJpaRepository.findTop10ByPublishedAtBetweenAndIsValidTrueOrderByViewCountDesc(mondayDateTime, sundayDateTime); return articles.stream() .map(article -> new PopularArticleResponse(article.getId(), article.getSimpleTitle(), article.getViewCount())) diff --git a/src/main/java/gyeongdan/article/service/RecommendService.java b/src/main/java/gyeongdan/article/service/RecommendService.java index 8028c30..71dd27f 100644 --- a/src/main/java/gyeongdan/article/service/RecommendService.java +++ b/src/main/java/gyeongdan/article/service/RecommendService.java @@ -19,14 +19,10 @@ public class RecommendService { private final UserTypeJpaRepository userTypeJpaRepository; private final ArticleJpaRepository articleJpaRepository; - public List recommendArticleById(Optional userId) { - if (userId.isEmpty()) { - throw new IllegalArgumentException( "아직 유형검사를 하지 않은 유저입니다."); - } - + public List recommendArticleById(Long userId) { // (1) 사용자 id에 해당하는 UserType을 가져옴 - UserType userType = userTypeJpaRepository.findByuserId(userId.get()) - .orElseThrow(() -> new IllegalArgumentException( "아직 유형검사를 하지 않은 유저입니다.")); + UserType userType = userTypeJpaRepository.findTopByUserIdOrderByIdDesc(userId) + .orElseThrow(() -> new IllegalArgumentException("아직 유형검사를 하지 않은 유저입니다.")); // (2) UserType에서 가장 값이 높은 3개의 타입값을 추출하기 Map userTypeValues = new HashMap<>(); diff --git a/src/main/java/gyeongdan/chatBot/term/controller/EconomicTermController.java b/src/main/java/gyeongdan/chatBot/term/controller/EconomicTermController.java index aff8d5f..e618669 100644 --- a/src/main/java/gyeongdan/chatBot/term/controller/EconomicTermController.java +++ b/src/main/java/gyeongdan/chatBot/term/controller/EconomicTermController.java @@ -15,7 +15,7 @@ public class EconomicTermController { private final EconomicTermService economicTermService; - @GetMapping("/search") + @PostMapping("/search") public ResponseEntity search(@RequestBody String question) throws Exception { Map data = economicTermService.findEconomicTerm(question); diff --git a/src/main/java/gyeongdan/user/repository/UserTypeJpaRepository.java b/src/main/java/gyeongdan/user/repository/UserTypeJpaRepository.java index d19f0d4..0f94321 100644 --- a/src/main/java/gyeongdan/user/repository/UserTypeJpaRepository.java +++ b/src/main/java/gyeongdan/user/repository/UserTypeJpaRepository.java @@ -9,8 +9,7 @@ @Repository public interface UserTypeJpaRepository extends JpaRepository { + Optional findTopByUserIdOrderByIdDesc(Long userId); - Optional findByuserId(Long user_id); - - Optional> findByUserId(Long userId); + Optional> findAllByUserId(Long userId); } diff --git a/src/main/java/gyeongdan/user/service/UserManageService.java b/src/main/java/gyeongdan/user/service/UserManageService.java index 3f4486e..2076ed8 100644 --- a/src/main/java/gyeongdan/user/service/UserManageService.java +++ b/src/main/java/gyeongdan/user/service/UserManageService.java @@ -81,7 +81,7 @@ public void saveUserType(Long userId, UserTypeTestResult userTypeTestResult) { public UserTypeRecord getUserType(Long userId) { checkUserExist(userId); - List userTypes = userTypeJpaRepository.findByUserId(userId) + List userTypes = userTypeJpaRepository.findAllByUserId(userId) .orElseThrow(() -> new IllegalArgumentException("사용자의 유저 타입이 존재하지 않습니다.")); if (userTypes.isEmpty()) {