Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat : 친구 태그 검색 개선 #454

Merged
merged 7 commits into from
May 29, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions backend/core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,6 @@ dependencies {
//aop
implementation 'org.springframework.boot:spring-boot-starter-aop'

//jnanoid
implementation 'com.aventrix.jnanoid:jnanoid:2.0.0'

compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.mysql:mysql-connector-j'
annotationProcessor 'org.projectlombok:lombok'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import site.timecapsulearchive.core.domain.auth.data.response.TokenResponse;
Expand All @@ -20,6 +21,7 @@
import site.timecapsulearchive.core.infra.sms.data.response.SmsApiResponse;
import site.timecapsulearchive.core.infra.sms.manager.SmsApiManager;

@Slf4j
@Service
@Transactional
@RequiredArgsConstructor
Expand Down Expand Up @@ -109,6 +111,14 @@ private Long updateToVerifiedMember(final Long memberId, final byte[] plain) {

memberTemporaryRepository.delete(memberTemporary);

boolean isDuplicateTag = memberRepository.checkTagDuplication(memberTemporary.getTag());
if (isDuplicateTag) {
log.warn("member tag duplicate - email:{}, tag:{}", memberTemporary.getEmail(),
memberTemporary.getTag());
memberTemporary.updateTagLowerCaseSocialType();
log.warn("member tag update - tag: {}", memberTemporary.getTag());
}

final Member verifiedMember = memberTemporary.toMember(hashEncryptionManager.encrypt(plain),
aesEncryptionManager.encryptWithPrefixIV(plain));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,12 @@ ResponseEntity<ApiSpec<SearchFriendsResponse>> searchMembersByPhones(
);

@Operation(
summary = "찬구 검색",
description = "친구의 tag로 친구 검색을 한다.",
summary = "친구 검색",
description = """
친구의 tag로 친구 검색을 한다.
<br>
태그가 일치하면 일치하는 태그를 가진 사용자를 일치하지 않으면 가장 비슷한 태그를 가진 사용자를 반환한다.
""",
security = {@SecurityRequirement(name = "user_token")},
tags = {"friend"}
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import org.springframework.web.bind.annotation.RestController;
import site.timecapsulearchive.core.domain.friend.data.dto.FriendSummaryDto;
import site.timecapsulearchive.core.domain.friend.data.dto.SearchFriendSummaryDto;
import site.timecapsulearchive.core.domain.friend.data.dto.SearchFriendSummaryDtoByTag;
import site.timecapsulearchive.core.domain.friend.data.request.FriendBeforeGroupInviteRequest;
import site.timecapsulearchive.core.domain.friend.data.request.SearchFriendsRequest;
import site.timecapsulearchive.core.domain.friend.data.response.FriendRequestsSliceResponse;
Expand Down Expand Up @@ -127,10 +128,13 @@ public ResponseEntity<ApiSpec<SearchTagFriendSummaryResponse>> searchFriendByTag
@AuthenticationPrincipal Long memberId,
@RequestParam(value = "friend_tag") final String tag
) {
SearchFriendSummaryDtoByTag searchResult = friendQueryService.searchFriend(
memberId, tag);

return ResponseEntity.ok(
ApiSpec.success(
SuccessCode.SUCCESS,
friendQueryService.searchFriend(memberId, tag)
searchResult.toResponse()
)
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@
import static site.timecapsulearchive.core.domain.member.entity.QMember.member;
import static site.timecapsulearchive.core.domain.member_group.entity.QMemberGroup.memberGroup;

import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.core.types.Projections;
import com.querydsl.core.types.dsl.CaseBuilder;
import com.querydsl.core.types.dsl.Expressions;
import com.querydsl.core.types.dsl.NumberTemplate;
import com.querydsl.jpa.impl.JPAQueryFactory;
import java.time.ZonedDateTime;
import java.util.List;
Expand All @@ -27,6 +31,11 @@
@RequiredArgsConstructor
public class MemberFriendQueryRepositoryImpl implements MemberFriendQueryRepository {

private static final double MATCH_THRESHOLD = 0;
private static final String MATCH_AGAINST_FUNCTION = "function('match_against', {0}, {1})";
private static final String FRIEND_INVITE_TO_ME_PATH = "friendInviteToMe";
private static final String FRIEND_INVITE_TO_FRIEND_PATH = "friendInviteToFriend";

private final JPAQueryFactory jpaQueryFactory;

public Slice<FriendSummaryDto> findFriendsSlice(
Expand Down Expand Up @@ -121,8 +130,8 @@ public List<SearchFriendSummaryDto> findFriendsByPhone(
final Long memberId,
final List<byte[]> hashes
) {
final QFriendInvite friendInviteToFriend = new QFriendInvite("friendInviteToFriend");
final QFriendInvite friendInviteToMe = new QFriendInvite("friendInviteToMe");
final QFriendInvite friendInviteToFriend = new QFriendInvite(FRIEND_INVITE_TO_FRIEND_PATH);
final QFriendInvite friendInviteToMe = new QFriendInvite(FRIEND_INVITE_TO_ME_PATH);

return jpaQueryFactory
.select(
Expand Down Expand Up @@ -157,8 +166,21 @@ public Optional<SearchFriendSummaryDtoByTag> findFriendsByTag(
final Long memberId,
final String tag
) {
final QFriendInvite friendInviteToFriend = new QFriendInvite("friendInviteToFriend");
final QFriendInvite friendInviteToMe = new QFriendInvite("friendInviteToMe");
if (tag != null && tag.isBlank()) {
return Optional.empty();
}

final QFriendInvite friendInviteToFriend = new QFriendInvite(FRIEND_INVITE_TO_FRIEND_PATH);
final QFriendInvite friendInviteToMe = new QFriendInvite(FRIEND_INVITE_TO_ME_PATH);

NumberTemplate<Double> tagMatchTemplate = Expressions.numberTemplate(Double.class,
MATCH_AGAINST_FUNCTION,
member.tag,
tag);

OrderSpecifier<Boolean> tagEqCaseDesc = new CaseBuilder().when(member.tag.eq(tag))
GaBaljaintheroom marked this conversation as resolved.
Show resolved Hide resolved
.then(Boolean.TRUE)
.otherwise(Boolean.FALSE).desc();

return Optional.ofNullable(jpaQueryFactory
.select(
Expand All @@ -179,9 +201,11 @@ public Optional<SearchFriendSummaryDtoByTag> findFriendsByTag(
.on(friendInviteToFriend.friend.id.eq(member.id)
.and(friendInviteToFriend.owner.id.eq(memberId)))
.leftJoin(friendInviteToMe)
.on(friendInviteToMe.friend.id.eq(memberId)
.and(friendInviteToMe.owner.id.eq(member.id)))
.where(member.tag.eq(tag))
.on(friendInviteToMe.owner.id.eq(member.id)
.and(friendInviteToMe.friend.id.eq(memberId)))
.where(tagMatchTemplate.gt(MATCH_THRESHOLD))
.orderBy(tagEqCaseDesc, tagMatchTemplate.desc())
.limit(1L)
.fetchOne()
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import site.timecapsulearchive.core.domain.friend.data.dto.SearchFriendSummaryDto;
import site.timecapsulearchive.core.domain.friend.data.dto.SearchFriendSummaryDtoByTag;
import site.timecapsulearchive.core.domain.friend.data.request.FriendBeforeGroupInviteRequest;
import site.timecapsulearchive.core.domain.friend.data.response.SearchTagFriendSummaryResponse;
import site.timecapsulearchive.core.domain.friend.exception.FriendNotFoundException;
import site.timecapsulearchive.core.domain.friend.repository.member_friend.MemberFriendRepository;
import site.timecapsulearchive.core.global.common.wrapper.ByteArrayWrapper;
Expand Down Expand Up @@ -53,12 +52,8 @@ public List<SearchFriendSummaryDto> findFriendsByPhone(
return memberFriendRepository.findFriendsByPhone(memberId, hashes);
}

public SearchTagFriendSummaryResponse searchFriend(final Long memberId, final String tag) {
final SearchFriendSummaryDtoByTag friendSummaryDto = memberFriendRepository
.findFriendsByTag(memberId, tag).orElseThrow(FriendNotFoundException::new);

return friendSummaryDto.toResponse();
public SearchFriendSummaryDtoByTag searchFriend(final Long memberId, final String tag) {
return memberFriendRepository.findFriendsByTag(memberId, tag)
.orElseThrow(FriendNotFoundException::new);
}


}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package site.timecapsulearchive.core.domain.member.data.dto;

import com.aventrix.jnanoid.jnanoid.NanoIdUtils;
import site.timecapsulearchive.core.domain.member.entity.MemberTemporary;
import site.timecapsulearchive.core.domain.member.entity.SocialType;
import site.timecapsulearchive.core.global.util.nickname.MakeRandomNickNameUtil;
Expand All @@ -12,14 +11,14 @@ public record SignUpRequestDto(
SocialType socialType
) {

public MemberTemporary toMemberTemporary() {
public MemberTemporary toMemberTemporary(final String tag) {
return MemberTemporary.builder()
.authId(authId)
.nickname(MakeRandomNickNameUtil.makeRandomNickName())
.email(email)
.profileUrl(profileUrl)
.socialType(socialType)
.tag(NanoIdUtils.randomNanoId())
.tag(tag)
.build();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package site.timecapsulearchive.core.domain.member.data.mapper;

import com.aventrix.jnanoid.jnanoid.NanoIdUtils;
import java.time.ZoneId;
import java.util.List;
import java.util.UUID;
Expand All @@ -12,6 +11,7 @@
import site.timecapsulearchive.core.domain.member.entity.Member;
import site.timecapsulearchive.core.domain.member.entity.SocialType;
import site.timecapsulearchive.core.global.security.oauth.dto.OAuth2UserInfo;
import site.timecapsulearchive.core.global.util.TagGenerator;
import site.timecapsulearchive.core.global.util.nickname.MakeRandomNickNameUtil;
import site.timecapsulearchive.core.infra.s3.manager.S3PreSignedUrlManager;

Expand All @@ -33,7 +33,7 @@ public Member OAuthToEntity(
.email(oAuth2UserInfo.getEmail())
.profileUrl(oAuth2UserInfo.getImageUrl())
.socialType(socialType)
.tag(NanoIdUtils.randomNanoId())
.tag(TagGenerator.generate(oAuth2UserInfo.getEmail(), socialType))
.build();
}

Expand Down Expand Up @@ -64,13 +64,15 @@ public MemberNotificationSliceResponse notificationSliceToResponse(
}

public Member createMemberWithEmail(String email, String password) {
SocialType socialType = SocialType.EMAIL;

return Member.builder()
.email(email)
.password(password)
.authId(String.valueOf(UUID.randomUUID()))
.profileUrl("")
.socialType(SocialType.EMAIL)
.tag(NanoIdUtils.randomNanoId())
.socialType(socialType)
.tag(TagGenerator.generate(email, socialType))
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import site.timecapsulearchive.core.domain.member_group.entity.MemberGroup;
import site.timecapsulearchive.core.global.entity.BaseEntity;
import site.timecapsulearchive.core.global.util.NullCheck;
import site.timecapsulearchive.core.global.util.TagGenerator;

@Entity
@Getter
Expand Down Expand Up @@ -107,4 +108,7 @@ private Member(String profileUrl, String nickname, SocialType socialType, String
this.phone_hash = phone_hash;
}

public void updateTagLowerCaseSocialType() {
this.tag = TagGenerator.lowercase(email, socialType);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import lombok.NoArgsConstructor;
import site.timecapsulearchive.core.global.entity.BaseEntity;
import site.timecapsulearchive.core.global.util.NullCheck;
import site.timecapsulearchive.core.global.util.TagGenerator;

@Entity
@Getter
Expand Down Expand Up @@ -73,4 +74,8 @@ public Member toMember(final byte[] phone_hash, final byte[] phone) {
.phone(phone)
.build();
}

public void updateTagLowerCaseSocialType() {
this.tag = TagGenerator.lowercase(email, socialType);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,6 @@ List<MemberNotificationDto> findMemberNotificationDtos(
Optional<Boolean> findIsAlarmByMemberId(final Long memberId);

List<Long> findMemberIdsByIds(List<Long> ids);

boolean checkTagDuplication(String tag);
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public class MemberQueryRepositoryImpl implements MemberQueryRepository {

private final JPAQueryFactory query;

@Override
public Boolean findIsVerifiedByAuthIdAndSocialType(
final String authId,
final SocialType socialType
Expand All @@ -40,6 +41,7 @@ public Boolean findIsVerifiedByAuthIdAndSocialType(
.fetchOne();
}

@Override
public Optional<VerifiedCheckDto> findVerifiedCheckDtoByAuthIdAndSocialType(
final String authId,
final SocialType socialType
Expand All @@ -59,6 +61,7 @@ public Optional<VerifiedCheckDto> findVerifiedCheckDtoByAuthIdAndSocialType(
);
}

@Override
public Optional<MemberDetailDto> findMemberDetailResponseDtoById(final Long memberId) {
return Optional.ofNullable(
query
Expand All @@ -85,6 +88,7 @@ private NumberExpression<Long> countDistinct(final NumberExpression<Long> expres
return Expressions.numberTemplate(Long.class, "COUNT(DISTINCT {0})", expression);
}

@Override
public Slice<MemberNotificationDto> findNotificationSliceByMemberId(
final Long memberId,
final int size,
Expand All @@ -101,6 +105,7 @@ public Slice<MemberNotificationDto> findNotificationSliceByMemberId(
return new SliceImpl<>(notifications, Pageable.ofSize(size), hasNext);
}

@Override
public List<MemberNotificationDto> findMemberNotificationDtos(
final Long memberId,
final int size,
Expand All @@ -126,6 +131,7 @@ public List<MemberNotificationDto> findMemberNotificationDtos(
.fetch();
}

@Override
public Optional<EmailVerifiedCheckDto> findEmailVerifiedCheckDtoByEmail(
final String email
) {
Expand All @@ -146,6 +152,7 @@ public Optional<EmailVerifiedCheckDto> findEmailVerifiedCheckDtoByEmail(
);
}

@Override
public Boolean checkEmailDuplication(final String email) {
final Integer count = query.selectOne()
.from(member)
Expand All @@ -155,6 +162,7 @@ public Boolean checkEmailDuplication(final String email) {
return count != null;
}

@Override
public Optional<Boolean> findIsAlarmByMemberId(final Long memberId) {
return Optional.ofNullable(
query.select(member.notificationEnabled)
Expand All @@ -164,11 +172,22 @@ public Optional<Boolean> findIsAlarmByMemberId(final Long memberId) {
);
}

@Override
public List<Long> findMemberIdsByIds(List<Long> ids) {
return query
.select(member.id)
.from(member)
.where(member.id.in(ids))
.fetch();
}

@Override
public boolean checkTagDuplication(String tag) {
final Integer count = query.selectOne()
.from(member)
.where(member.tag.eq(tag))
.fetchFirst();

return count != null;
}
}
Loading
Loading