feat: add search module

This commit is contained in:
Tim
2025-07-01 17:52:02 +08:00
parent 93c23dd9df
commit 2fa6af6304
7 changed files with 304 additions and 0 deletions

View File

@@ -0,0 +1,96 @@
package com.openisle.controller;
import com.openisle.model.Post;
import com.openisle.model.Comment;
import com.openisle.model.User;
import com.openisle.service.SearchService;
import lombok.Data;
import lombok.RequiredArgsConstructor;
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.util.List;
import java.util.stream.Collectors;
@RestController
@RequestMapping("/api/search")
@RequiredArgsConstructor
public class SearchController {
private final SearchService searchService;
@GetMapping("/users")
public List<UserDto> searchUsers(@RequestParam String keyword) {
return searchService.searchUsers(keyword).stream()
.map(this::toUserDto)
.collect(Collectors.toList());
}
@GetMapping("/posts")
public List<PostDto> searchPosts(@RequestParam String keyword) {
return searchService.searchPosts(keyword).stream()
.map(this::toPostDto)
.collect(Collectors.toList());
}
@GetMapping("/posts/content")
public List<PostDto> searchPostsByContent(@RequestParam String keyword) {
return searchService.searchPostsByContent(keyword).stream()
.map(this::toPostDto)
.collect(Collectors.toList());
}
@GetMapping("/posts/title")
public List<PostDto> searchPostsByTitle(@RequestParam String keyword) {
return searchService.searchPostsByTitle(keyword).stream()
.map(this::toPostDto)
.collect(Collectors.toList());
}
@GetMapping("/global")
public List<SearchResultDto> global(@RequestParam String keyword) {
return searchService.globalSearch(keyword).stream()
.map(r -> {
SearchResultDto dto = new SearchResultDto();
dto.setType(r.type());
dto.setId(r.id());
dto.setText(r.text());
return dto;
})
.collect(Collectors.toList());
}
private UserDto toUserDto(User user) {
UserDto dto = new UserDto();
dto.setId(user.getId());
dto.setUsername(user.getUsername());
return dto;
}
private PostDto toPostDto(Post post) {
PostDto dto = new PostDto();
dto.setId(post.getId());
dto.setTitle(post.getTitle());
return dto;
}
@Data
private static class UserDto {
private Long id;
private String username;
}
@Data
private static class PostDto {
private Long id;
private String title;
}
@Data
private static class SearchResultDto {
private String type;
private Long id;
private String text;
}
}

View File

@@ -12,4 +12,5 @@ public interface CommentRepository extends JpaRepository<Comment, Long> {
List<Comment> findByPostAndParentIsNullOrderByCreatedAtAsc(Post post);
List<Comment> findByParentOrderByCreatedAtAsc(Comment parent);
List<Comment> findByAuthorOrderByCreatedAtDesc(User author, Pageable pageable);
List<Comment> findByContentContainingIgnoreCase(String keyword);
}

View File

@@ -15,4 +15,7 @@ public interface PostRepository extends JpaRepository<Post, Long> {
List<Post> findByAuthorAndStatusOrderByCreatedAtDesc(User author, PostStatus status, Pageable pageable);
List<Post> findByCategoryInAndStatus(List<Category> categories, PostStatus status);
List<Post> findByCategoryInAndStatus(List<Category> categories, PostStatus status, Pageable pageable);
List<Post> findByTitleContainingIgnoreCaseOrContentContainingIgnoreCaseAndStatus(String titleKeyword, String contentKeyword, PostStatus status);
List<Post> findByContentContainingIgnoreCaseAndStatus(String keyword, PostStatus status);
List<Post> findByTitleContainingIgnoreCaseAndStatus(String keyword, PostStatus status);
}

View File

@@ -7,4 +7,5 @@ import java.util.Optional;
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
Optional<User> findByEmail(String email);
java.util.List<User> findByUsernameContainingIgnoreCase(String keyword);
}

View File

@@ -0,0 +1,60 @@
package com.openisle.service;
import com.openisle.model.Post;
import com.openisle.model.PostStatus;
import com.openisle.model.Comment;
import com.openisle.model.User;
import com.openisle.repository.PostRepository;
import com.openisle.repository.CommentRepository;
import com.openisle.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.stream.Stream;
@Service
@RequiredArgsConstructor
public class SearchService {
private final UserRepository userRepository;
private final PostRepository postRepository;
private final CommentRepository commentRepository;
public List<User> searchUsers(String keyword) {
return userRepository.findByUsernameContainingIgnoreCase(keyword);
}
public List<Post> searchPosts(String keyword) {
return postRepository
.findByTitleContainingIgnoreCaseOrContentContainingIgnoreCaseAndStatus(keyword, keyword, PostStatus.PUBLISHED);
}
public List<Post> searchPostsByContent(String keyword) {
return postRepository
.findByContentContainingIgnoreCaseAndStatus(keyword, PostStatus.PUBLISHED);
}
public List<Post> searchPostsByTitle(String keyword) {
return postRepository
.findByTitleContainingIgnoreCaseAndStatus(keyword, PostStatus.PUBLISHED);
}
public List<Comment> searchComments(String keyword) {
return commentRepository.findByContentContainingIgnoreCase(keyword);
}
public List<SearchResult> globalSearch(String keyword) {
Stream<SearchResult> users = searchUsers(keyword).stream()
.map(u -> new SearchResult("user", u.getId(), u.getUsername()));
Stream<SearchResult> posts = searchPosts(keyword).stream()
.map(p -> new SearchResult("post", p.getId(), p.getTitle()));
Stream<SearchResult> titles = searchPostsByTitle(keyword).stream()
.map(p -> new SearchResult("post_title", p.getId(), p.getTitle()));
Stream<SearchResult> comments = searchComments(keyword).stream()
.map(c -> new SearchResult("comment", c.getId(), c.getContent()));
return Stream.concat(Stream.concat(Stream.concat(users, posts), titles), comments)
.toList();
}
public record SearchResult(String type, Long id, String text) {}
}