From 4caea50f80c9feedbbdcc8a826fca03fb71e292b Mon Sep 17 00:00:00 2001 From: mungsil Date: Sun, 10 Nov 2024 00:02:12 +0900 Subject: [PATCH 1/5] =?UTF-8?q?refactor:=20=EB=8B=89=EB=84=A4=EC=9E=84=20?= =?UTF-8?q?=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8=20=EB=A1=9C=EC=A7=81?= =?UTF-8?q?=EC=9D=84=20UserNicknameService=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= =?UTF-8?q?=20#56?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/service/UserNicknameService.java | 69 +++++++++++++++++++ .../api/domain/user/service/UserService.java | 38 ++-------- 2 files changed, 73 insertions(+), 34 deletions(-) create mode 100644 src/main/java/com/munecting/api/domain/user/service/UserNicknameService.java diff --git a/src/main/java/com/munecting/api/domain/user/service/UserNicknameService.java b/src/main/java/com/munecting/api/domain/user/service/UserNicknameService.java new file mode 100644 index 0000000..ef83e05 --- /dev/null +++ b/src/main/java/com/munecting/api/domain/user/service/UserNicknameService.java @@ -0,0 +1,69 @@ +package com.munecting.api.domain.user.service; + +import com.munecting.api.domain.user.dao.UserRepository; +import com.munecting.api.domain.user.entity.User; +import com.munecting.api.global.common.dto.response.Status; +import com.munecting.api.global.error.exception.InternalServerException; +import com.munecting.api.global.error.exception.InvalidValueException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import java.util.Random; + +@Service +@Slf4j +@RequiredArgsConstructor +public class UserNicknameService { + + private final UserRepository userRepository; + + private static final int MIN_LENGTH = 2; + private static final int MAX_LENGTH = 15; + + private static final String NAME_VALUE_RULES = "^(?=.*[a-zA-Z0-9가-힣])[a-zA-Z가-힣][a-zA-Z0-9가-힣_]*$"; + private static final String NICKNAME_LENGTH_ERROR_MESSAGE = "닉네임은 2-15자 사이여야 합니다."; + private static final String NICKNAME_WRONG_VALUE_ERROR_MESSAGE = "닉네임은 한글, 영문, 숫자, 언더바(_)만 사용 가능하며, 첫 글자는 언더바 또는 숫자일 수 없습니다."; + private static final String NICKNAME_DUPLICATED_ERROR_MESSAGE = "이미 사용 중인 닉네임입니다."; + + public String updateNickname(User user, String nickname) { + if (!StringUtils.hasText(nickname)) { + return user.getNickname(); + } + + validateNickname(user, nickname); + return user.updateNickname(nickname); + } + + private void validateNickname(User existingUser, String nickname) { + if (isInvalidNicknameLength(nickname)) { + throw new InvalidValueException(Status.BAD_REQUEST, NICKNAME_LENGTH_ERROR_MESSAGE); + } + + if (isInvalidNamingRule(nickname)) { + throw new InvalidValueException(Status.BAD_REQUEST, NICKNAME_WRONG_VALUE_ERROR_MESSAGE); + } + + if (isDuplicatedNickname(existingUser, nickname)) { + throw new InvalidValueException(Status.CONFLICT, NICKNAME_DUPLICATED_ERROR_MESSAGE); + } + } + + private boolean isInvalidNicknameLength(String nickname) { + return nickname.length() < MIN_LENGTH || nickname.length() > MAX_LENGTH; + } + + private boolean isInvalidNamingRule(String nickname) { + return !nickname.matches(NAME_VALUE_RULES); + } + + private boolean isDuplicatedNickname(User existingUser, String nickname) { + // 기존 닉네임과 일치하는 경우 중복 체크 제외 + if (existingUser.getNickname().equals(nickname)) { + return false; + } + + return userRepository.existsByNickname(nickname); + } +} diff --git a/src/main/java/com/munecting/api/domain/user/service/UserService.java b/src/main/java/com/munecting/api/domain/user/service/UserService.java index a0308c4..b8778e6 100644 --- a/src/main/java/com/munecting/api/domain/user/service/UserService.java +++ b/src/main/java/com/munecting/api/domain/user/service/UserService.java @@ -29,17 +29,8 @@ public class UserService { private final CommentRepository commentRepository; private final LikeRepository likeRepository; private final UploadedMusicRepository uploadedMusicRepository; - private final UserProfileImageService userProfileImageService; - - private static final int MIN_LENGTH = 2; - private static final int MAX_LENGTH = 15; - - private static final String NAME_VALUE_RULES = "^[a-zA-Z0-9가-힣]+$"; - - private static final String NICKNAME_LENGTH_ERROR_MESSAGE = "닉네임은 2-15자 사이여야 합니다."; - private static final String NICKNAME_WRONG_VALUE_ERROR_MESSAGE = "닉네임은 한글, 영문, 숫자만 포함할 수 있습니다."; - private static final String NICKNAME_DUPLICATED_ERROR_MESSAGE = "이미 사용 중인 닉네임입니다."; - + private final UserProfileImageService profileImageService; + private final UserNicknameService nicknameService; @Transactional public void deleteUser(Long userId) { @@ -75,31 +66,10 @@ public User findUserByIdOrThrow (Long userId) { } private String updateNickname(User user, String nickname) { - if (!StringUtils.hasText(nickname)) { - return user.getNickname(); - } - - validateNickname(user, nickname); - return user.updateNickname(nickname); - } - - private void validateNickname(User existingUser, String nickname) { - if (nickname.length() < MIN_LENGTH || nickname.length() > MAX_LENGTH) { - throw new InvalidValueException(Status.BAD_REQUEST, NICKNAME_LENGTH_ERROR_MESSAGE); - } - - if (!nickname.matches(NAME_VALUE_RULES)) { - throw new InvalidValueException(Status.BAD_REQUEST, NICKNAME_WRONG_VALUE_ERROR_MESSAGE); - } - - // 중복 검사 - if (!existingUser.getNickname().equals(nickname) - && userRepository.existsByNickname(nickname)) { - throw new InvalidValueException(Status.CONFLICT, NICKNAME_DUPLICATED_ERROR_MESSAGE); - } + return nicknameService.updateNickname(user, nickname); } private String updateProfileImage(User user, MultipartFile imgFile) { - return userProfileImageService.updateImage(user, imgFile); + return profileImageService.updateImage(user, imgFile); } } From bb0fd691b840a0541ab0b722ef9402b79034595a Mon Sep 17 00:00:00 2001 From: mungsil Date: Sun, 10 Nov 2024 00:04:57 +0900 Subject: [PATCH 2/5] =?UTF-8?q?refactor:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20import=EB=AC=B8=20=EC=82=AD=EC=A0=9C=20#56?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/munecting/api/domain/user/service/UserService.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/main/java/com/munecting/api/domain/user/service/UserService.java b/src/main/java/com/munecting/api/domain/user/service/UserService.java index b8778e6..bb1387d 100644 --- a/src/main/java/com/munecting/api/domain/user/service/UserService.java +++ b/src/main/java/com/munecting/api/domain/user/service/UserService.java @@ -6,16 +6,12 @@ import com.munecting.api.domain.user.dao.UserRepository; import com.munecting.api.domain.user.dto.request.UpdateProfileRequestDto; import com.munecting.api.domain.user.dto.response.UpdateProfileResponseDto; -import com.munecting.api.domain.user.dto.response.UserResponseDto; import com.munecting.api.domain.user.entity.User; -import com.munecting.api.global.common.dto.response.Status; import com.munecting.api.global.error.exception.EntityNotFoundException; -import com.munecting.api.global.error.exception.InvalidValueException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.springframework.util.StringUtils; import org.springframework.web.multipart.MultipartFile; import static com.munecting.api.global.common.dto.response.Status.USER_NOT_FOUND; From 1bc8ac0fe14fb47edb1cbb67a902d621f490f0b0 Mon Sep 17 00:00:00 2001 From: mungsil Date: Sun, 10 Nov 2024 00:08:44 +0900 Subject: [PATCH 3/5] =?UTF-8?q?fix:=20=ED=9A=8C=EC=9B=90=EA=B0=80=EC=9E=85?= =?UTF-8?q?=20=EC=8B=9C=20=EA=B3=A0=EC=9C=A0=ED=95=9C=20=EB=9E=9C=EB=8D=A4?= =?UTF-8?q?=20=EB=8B=89=EB=84=A4=EC=9E=84=EC=9D=84=20=EB=B6=80=EC=97=AC?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95=20#56?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/domain/user/entity/User.java | 15 ++++++++ .../api/domain/user/service/AuthService.java | 25 +++++++------ .../user/service/UserNicknameService.java | 35 +++++++++++++++++++ 3 files changed, 62 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/munecting/api/domain/user/entity/User.java b/src/main/java/com/munecting/api/domain/user/entity/User.java index 5f0417b..880511a 100644 --- a/src/main/java/com/munecting/api/domain/user/entity/User.java +++ b/src/main/java/com/munecting/api/domain/user/entity/User.java @@ -25,6 +25,7 @@ public class User extends BaseEntity { private String socialId; @NotBlank + @Column(unique = true) private String nickname; @Column(nullable = true) @@ -38,6 +39,20 @@ public class User extends BaseEntity { @Enumerated(EnumType.STRING) private SocialType socialType; + public static User toEntity(String socialId, String nickname, Role role, SocialType socialType) { + return toEntity(socialId, nickname, null, role, socialType); + } + + public static User toEntity(String socialId, String nickname, String profileImageUrl, Role role, SocialType socialType) { + return User.builder() + .socialId(socialId) + .nickname(nickname) + .profileImageUrl(profileImageUrl) + .role(role) + .socialType(socialType) + .build(); + } + public String updateNickname(String nickname) { this.nickname = nickname; diff --git a/src/main/java/com/munecting/api/domain/user/service/AuthService.java b/src/main/java/com/munecting/api/domain/user/service/AuthService.java index a330168..22de6da 100644 --- a/src/main/java/com/munecting/api/domain/user/service/AuthService.java +++ b/src/main/java/com/munecting/api/domain/user/service/AuthService.java @@ -32,6 +32,7 @@ public class AuthService { private final RedisTemplate redisTemplate; private final OidcService oidcService; private final UserRepository userRepository; + private final UserNicknameService nicknameService; @Value("${jwt.refresh.expiration}") private long refreshTokenExpiration; @@ -77,25 +78,23 @@ private String getRedisKey(Long userId) { @Transactional public UserTokenResponseDto getOrCreateUser(LoginRequestDto dto) { - OidcUserInfo oidcUserInfo = oidcService.getOidcUserInfo(dto.socialType(), dto.idToken()); - String socialId = dto.socialType().toString() + "_" + oidcUserInfo.sub(); + String socialId = generateSocialId(dto); User user = userRepository.findBySocialId(socialId) - .orElseGet(() -> - createUser(socialId, oidcUserInfo.email(), dto.socialType()) - ); + .orElseGet(() -> createUser(socialId, dto.socialType())); return issueTokensForUser(user); } - //TODO: 이메일 제거, 닉네임 자체 생성 - private User createUser(String socialId, String email, SocialType socialType) { - User newUser = User.builder() - .socialId(socialId) - .nickname(email.split("@")[0]) - .role(Role.USER) - .socialType(socialType) - .build(); + private String generateSocialId(LoginRequestDto dto) { + OidcUserInfo oidcUserInfo = oidcService.getOidcUserInfo(dto.socialType(), dto.idToken()); + + return dto.socialType().toString() + "_" + oidcUserInfo.sub(); + } + + private User createUser(String socialId, SocialType socialType) { + User newUser = User.toEntity( + socialId, nicknameService.generateUniqueNickname(), Role.USER, socialType); return userRepository.save(newUser); } diff --git a/src/main/java/com/munecting/api/domain/user/service/UserNicknameService.java b/src/main/java/com/munecting/api/domain/user/service/UserNicknameService.java index ef83e05..3832c3d 100644 --- a/src/main/java/com/munecting/api/domain/user/service/UserNicknameService.java +++ b/src/main/java/com/munecting/api/domain/user/service/UserNicknameService.java @@ -27,6 +27,13 @@ public class UserNicknameService { private static final String NICKNAME_WRONG_VALUE_ERROR_MESSAGE = "닉네임은 한글, 영문, 숫자, 언더바(_)만 사용 가능하며, 첫 글자는 언더바 또는 숫자일 수 없습니다."; private static final String NICKNAME_DUPLICATED_ERROR_MESSAGE = "이미 사용 중인 닉네임입니다."; + private static final String DEFAULT_NICKNAME = "뮤넥터"; + private static final String DELIMITER = "_"; + private static final int MAX_UNIQUE_STRING_LENGTH = 10; + private static final int MAX_NICKNAME_RETRY_COUNT = 100; + private static final String CHARACTERS_POOL = "0123456789abcdefghijklmnopqrstuvwxyz"; + private final Random random = new Random(); + public String updateNickname(User user, String nickname) { if (!StringUtils.hasText(nickname)) { return user.getNickname(); @@ -66,4 +73,32 @@ private boolean isDuplicatedNickname(User existingUser, String nickname) { return userRepository.existsByNickname(nickname); } + + public String generateUniqueNickname() { + String nickname; + int attemptCount = 0; + + do { + if (attemptCount >= MAX_NICKNAME_RETRY_COUNT) { + throw new InternalServerException("닉네임 생성에 실패하였습니다."); + } + nickname = DEFAULT_NICKNAME + DELIMITER + generateUniqueString(); + attemptCount++; + + } while (userRepository.existsByNickname(nickname)); + + return nickname; + } + + private String generateUniqueString() { + int uniqueStringLength = random.nextInt(MAX_UNIQUE_STRING_LENGTH); + log.info("uniqueStringLength : {}", uniqueStringLength); + + StringBuilder result = new StringBuilder(); + for (int i = 0; i < uniqueStringLength; i++) { + result.append(CHARACTERS_POOL.charAt(random.nextInt(CHARACTERS_POOL.length()))); + } + + return result.toString(); + } } From d7ef3a06a36aea9cbaf3f7185d2bad20a4eeebf5 Mon Sep 17 00:00:00 2001 From: mungsil Date: Sun, 10 Nov 2024 00:12:40 +0900 Subject: [PATCH 4/5] =?UTF-8?q?fix:=20=EB=8B=89=EB=84=A4=EC=9E=84=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=EC=97=90=20=EB=94=B0=EB=9D=BC=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=B4=EC=A7=84=20=EC=9D=B4=EB=A9=94=EC=9D=BC=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=EC=82=AD=EC=A0=9C=20#56?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/munecting/api/domain/oidc/dto/OidcUserInfo.java | 6 ++---- .../api/domain/oidc/strategy/impl/AppleOidcStrategy.java | 2 +- .../api/domain/oidc/strategy/impl/GoogleOidcStrategy.java | 3 +-- .../api/domain/oidc/strategy/impl/KakaoOidcStrategy.java | 2 +- 4 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/munecting/api/domain/oidc/dto/OidcUserInfo.java b/src/main/java/com/munecting/api/domain/oidc/dto/OidcUserInfo.java index ff2430b..b58f726 100644 --- a/src/main/java/com/munecting/api/domain/oidc/dto/OidcUserInfo.java +++ b/src/main/java/com/munecting/api/domain/oidc/dto/OidcUserInfo.java @@ -4,14 +4,12 @@ @Builder public record OidcUserInfo( - String sub, - String email + String sub ) { - public static OidcUserInfo of(String sub, String email) { + public static OidcUserInfo of(String sub) { return OidcUserInfo.builder() .sub(sub) - .email(email) .build(); } } diff --git a/src/main/java/com/munecting/api/domain/oidc/strategy/impl/AppleOidcStrategy.java b/src/main/java/com/munecting/api/domain/oidc/strategy/impl/AppleOidcStrategy.java index 508937a..d463e00 100644 --- a/src/main/java/com/munecting/api/domain/oidc/strategy/impl/AppleOidcStrategy.java +++ b/src/main/java/com/munecting/api/domain/oidc/strategy/impl/AppleOidcStrategy.java @@ -30,6 +30,6 @@ public OidcUserInfo authenticate(String idToken) { Map header = getHeader(idToken); Claims claims = getClaimsWithVerifySign(idToken, header); - return OidcUserInfo.of(claims.getSubject(), claims.get("email").toString()); + return OidcUserInfo.of(claims.getSubject()); } } diff --git a/src/main/java/com/munecting/api/domain/oidc/strategy/impl/GoogleOidcStrategy.java b/src/main/java/com/munecting/api/domain/oidc/strategy/impl/GoogleOidcStrategy.java index b0f1b0e..15be12a 100644 --- a/src/main/java/com/munecting/api/domain/oidc/strategy/impl/GoogleOidcStrategy.java +++ b/src/main/java/com/munecting/api/domain/oidc/strategy/impl/GoogleOidcStrategy.java @@ -26,8 +26,7 @@ public OidcUserInfo authenticate(String idToken) { GoogleIdToken googleIdToken = verifyIdToken(idToken); String subject = googleIdToken.getPayload().getSubject(); - String email = googleIdToken.getPayload().getEmail(); - return OidcUserInfo.of(subject, email); + return OidcUserInfo.of(subject); } private GoogleIdToken verifyIdToken(final String idToken) { diff --git a/src/main/java/com/munecting/api/domain/oidc/strategy/impl/KakaoOidcStrategy.java b/src/main/java/com/munecting/api/domain/oidc/strategy/impl/KakaoOidcStrategy.java index 90fa3f3..8c84af2 100644 --- a/src/main/java/com/munecting/api/domain/oidc/strategy/impl/KakaoOidcStrategy.java +++ b/src/main/java/com/munecting/api/domain/oidc/strategy/impl/KakaoOidcStrategy.java @@ -31,6 +31,6 @@ public OidcUserInfo authenticate(String idToken) { Map header = getHeader(idToken); Claims claims = getClaimsWithVerifySign(idToken, header); - return OidcUserInfo.of(claims.getSubject(), claims.get("email").toString()); + return OidcUserInfo.of(claims.getSubject()); } } From 0aafdfdb6dddd83fdca6ea2fa542e4a248ed95e5 Mon Sep 17 00:00:00 2001 From: mungsil Date: Sun, 10 Nov 2024 18:11:28 +0900 Subject: [PATCH 5/5] =?UTF-8?q?refactor:=20=EC=97=90=EB=9F=AC=20=EB=A9=94?= =?UTF-8?q?=EC=8B=9C=EC=A7=80=20ENUM=20=EC=A0=95=EC=9D=98=20#56?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/domain/user/service/UserNicknameService.java | 12 +++++------- .../api/global/common/dto/response/Status.java | 3 +++ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/munecting/api/domain/user/service/UserNicknameService.java b/src/main/java/com/munecting/api/domain/user/service/UserNicknameService.java index 3832c3d..9c0467d 100644 --- a/src/main/java/com/munecting/api/domain/user/service/UserNicknameService.java +++ b/src/main/java/com/munecting/api/domain/user/service/UserNicknameService.java @@ -2,7 +2,6 @@ import com.munecting.api.domain.user.dao.UserRepository; import com.munecting.api.domain.user.entity.User; -import com.munecting.api.global.common.dto.response.Status; import com.munecting.api.global.error.exception.InternalServerException; import com.munecting.api.global.error.exception.InvalidValueException; import lombok.RequiredArgsConstructor; @@ -12,6 +11,8 @@ import java.util.Random; +import static com.munecting.api.global.common.dto.response.Status.*; + @Service @Slf4j @RequiredArgsConstructor @@ -23,9 +24,6 @@ public class UserNicknameService { private static final int MAX_LENGTH = 15; private static final String NAME_VALUE_RULES = "^(?=.*[a-zA-Z0-9가-힣])[a-zA-Z가-힣][a-zA-Z0-9가-힣_]*$"; - private static final String NICKNAME_LENGTH_ERROR_MESSAGE = "닉네임은 2-15자 사이여야 합니다."; - private static final String NICKNAME_WRONG_VALUE_ERROR_MESSAGE = "닉네임은 한글, 영문, 숫자, 언더바(_)만 사용 가능하며, 첫 글자는 언더바 또는 숫자일 수 없습니다."; - private static final String NICKNAME_DUPLICATED_ERROR_MESSAGE = "이미 사용 중인 닉네임입니다."; private static final String DEFAULT_NICKNAME = "뮤넥터"; private static final String DELIMITER = "_"; @@ -45,15 +43,15 @@ public String updateNickname(User user, String nickname) { private void validateNickname(User existingUser, String nickname) { if (isInvalidNicknameLength(nickname)) { - throw new InvalidValueException(Status.BAD_REQUEST, NICKNAME_LENGTH_ERROR_MESSAGE); + throw new InvalidValueException(INVALID_NICKNAME_LENGTH); } if (isInvalidNamingRule(nickname)) { - throw new InvalidValueException(Status.BAD_REQUEST, NICKNAME_WRONG_VALUE_ERROR_MESSAGE); + throw new InvalidValueException(INVALID_NICKNAME_VALUE); } if (isDuplicatedNickname(existingUser, nickname)) { - throw new InvalidValueException(Status.CONFLICT, NICKNAME_DUPLICATED_ERROR_MESSAGE); + throw new InvalidValueException(DUPLICATED_NICKNAME); } } diff --git a/src/main/java/com/munecting/api/global/common/dto/response/Status.java b/src/main/java/com/munecting/api/global/common/dto/response/Status.java index dee66d7..47fdace 100644 --- a/src/main/java/com/munecting/api/global/common/dto/response/Status.java +++ b/src/main/java/com/munecting/api/global/common/dto/response/Status.java @@ -44,6 +44,9 @@ public enum Status { // User 오류 응답 USER_NOT_FOUND(HttpStatus.NOT_FOUND, "USER404", "존재하지 않는 회원입니다."), + INVALID_NICKNAME_LENGTH(HttpStatus.BAD_REQUEST, "USER_NICKNAME400", "닉네임은 2-15자 사이여야 합니다."), + INVALID_NICKNAME_VALUE(HttpStatus.BAD_REQUEST, "USER_NICKNAME400", "닉네임은 한글, 영문, 숫자, 언더바(_)만 사용 가능하며, 첫 글자는 언더바 또는 숫자일 수 없습니다."), + DUPLICATED_NICKNAME(HttpStatus.CONFLICT, "USER_NICKNAME409","이미 사용 중인 닉네임입니다."), // 분산락 오류 응답 DISTRIBUTED_LOCK_ACQUISITION_FAILURE(HttpStatus.CONFLICT, "LOCK409", "잠시 후에 시도해주세요."),