Skip to content

Commit

Permalink
[BE] Feat #217: Hangul Initial Constant Search
Browse files Browse the repository at this point in the history
- User search feature with Hangul Initial Constant

Change-Id: I24564d36b03bfd150cbcd3472cbece8442bc064a
  • Loading branch information
404-not-foundl committed Feb 15, 2024
1 parent 7304658 commit e604085
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 9 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.ggums.ggumtle.common.handler;

import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

@Component
public class HangulHandler {

private static final int HANGUL_UNICODE_START = 44032;

private static final int HANGUL_UNICODE_END = 55203;

private static final int INITIAL_CONSONANT_UNICODE_START = 12593;

private static final int INITIAL_CONSONANT_UNICODE_END = 12622;

private static final char[] INITIAL_CONSONANT_UNICODES = {
'ㄱ', 'ㄲ', 'ㄴ', 'ㄷ', 'ㄸ', 'ㄹ', 'ㅁ', 'ㅂ', 'ㅃ', 'ㅅ',
'ㅆ', 'ㅇ', 'ㅈ', 'ㅉ', 'ㅊ', 'ㅋ', 'ㅌ', 'ㅍ', 'ㅎ'
};

public static List<String> separateInitialConsonants(String word) {
List<String> initials = new ArrayList<>();
for (char c : word.toCharArray()) {
if (isHangul(c)) {
int consonantIndex = (c - HANGUL_UNICODE_START) / 28 / 21;
char initialConsonant = INITIAL_CONSONANT_UNICODES[consonantIndex];
initials.add(String.valueOf(initialConsonant));
} else {
initials.add(String.valueOf(c));
}
}
for(String initial : initials){
System.out.println(initial);
}
return initials;
}

public static String getFirstInitialConsonant(String word) {
if (word == null || word.isEmpty()) {
return "";
}

char firstChar = word.charAt(0);
if (isHangul(firstChar)) {
int consonantIndex = (firstChar - HANGUL_UNICODE_START) / 28 / 21;
if (consonantIndex >= 0 && consonantIndex < INITIAL_CONSONANT_UNICODES.length) {
return String.valueOf(INITIAL_CONSONANT_UNICODES[consonantIndex]);
}
}

return String.valueOf(firstChar);
}

private static boolean isHangul(char c) {
return c >= HANGUL_UNICODE_START && c <= HANGUL_UNICODE_END;
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
package com.ggums.ggumtle.repository;

import com.ggums.ggumtle.entity.User;
import io.lettuce.core.dynamic.annotation.Param;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.Optional;

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByIdAndDeletedDateIsNull(Long userId);
Optional<User> findByUserNickname(String nickname);
Page<User> findByUserNicknameContainingAndDeletedDateIsNull(String userNickname, Pageable pageable);

List<User> findAllByDeletedDateIsNull();
}
49 changes: 41 additions & 8 deletions backend/src/main/java/com/ggums/ggumtle/service/UserService.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.ggums.ggumtle.service;

import com.ggums.ggumtle.common.handler.AlarmHandler;
import com.ggums.ggumtle.common.handler.HangulHandler;
import com.ggums.ggumtle.common.handler.ImageHandler;
import com.ggums.ggumtle.common.handler.TransactionHandler;
import com.ggums.ggumtle.common.jwt.JwtTokenManager;
Expand All @@ -19,9 +20,9 @@
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.server.authorization.AuthorizationWebFilter;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
Expand Down Expand Up @@ -52,6 +53,7 @@ public class UserService {
private final JwtTokenManager jwtTokenManager;
private final ImageHandler imageHandler;
private final AlarmHandler alarmHandler;
private final HangulHandler hangulHandler;
private final PasswordEncoder passwordEncoder;

public String updateUser(User user, MultipartFile userImage, UserUpdateRequestDto requestDto){
Expand Down Expand Up @@ -158,10 +160,45 @@ public String representativeBucket(User user, Long bucketId){

@Transactional(readOnly = true)
public UserListResponseDto searchUsers(String word, Pageable pageable, User currentUser) {
Page<User> users = userRepository.findByUserNicknameContainingAndDeletedDateIsNull(word, pageable);
List<User> allUsers = userRepository.findAllByDeletedDateIsNull();

List<String> searchKeywords = HangulHandler.separateInitialConsonants(word);

List<User> filteredUsers = allUsers.stream()
.filter(user -> {
String userNickname = user.getUserNickname();

if (userNickname.length() < searchKeywords.size()) {
return false;
}

List<String> userInitials = new ArrayList<>();
for (char c : userNickname.toCharArray()) {
String initialConsonant = HangulHandler.getFirstInitialConsonant(String.valueOf(c));
userInitials.add(initialConsonant);
}

int keywordIndex = 0;
for (String userInitial : userInitials) {
if (userInitial.equals(searchKeywords.get(keywordIndex))) {
keywordIndex++;
}
if (keywordIndex == searchKeywords.size()) {
return true;
}
}
return false;
})
.collect(Collectors.toList());

int start = (int) pageable.getOffset();
int end = Math.min((start + pageable.getPageSize()), filteredUsers.size());
List<User> paginatedUsers = filteredUsers.subList(start, end);
Page<User> users = new PageImpl<>(paginatedUsers, pageable, filteredUsers.size());

List<Long> userIds = users.getContent().stream()
.filter(u -> !u.getId().equals(currentUser.getId()))
.map(User::getId) // only one user needs to be filtered
.map(User::getId)
.filter(id -> !id.equals(currentUser.getId()))
.collect(Collectors.toList());

Map<Long, Boolean> followingMap = getFollowingMap(currentUser, userIds);
Expand All @@ -170,10 +207,6 @@ public UserListResponseDto searchUsers(String word, Pageable pageable, User curr
return UserListResponseDto.builder().searchList(searchList).build();
}

private Map<Long, Bucket> getRepresentativeBucketsMap(List<Long> userIds) {
List<Bucket> repBucket = bucketRepository.findByUserIdIn(userIds);
return repBucket.stream().collect(Collectors.toConcurrentMap(rb -> rb.getUser().getId(), Function.identity()));
}

private Map<Long, Boolean> getFollowingMap(User currentUser, List<Long> userIds) {
List<Follow> follows = followRepository.findByFollowerIdAndFolloweeIdIn(currentUser.getId(), userIds);
Expand Down

0 comments on commit e604085

Please sign in to comment.