Skip to content

Commit

Permalink
Merge pull request #59 from Mu-necting/feat/#56
Browse files Browse the repository at this point in the history
회원가입 시 닉네임 생성 로직 수정 완료
  • Loading branch information
mingmingmon authored Nov 10, 2024
2 parents 39f8589 + 0aafdfd commit f3ea079
Show file tree
Hide file tree
Showing 9 changed files with 141 additions and 58 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,6 @@ public OidcUserInfo authenticate(String idToken) {
Map<String, String> header = getHeader(idToken);
Claims claims = getClaimsWithVerifySign(idToken, header);

return OidcUserInfo.of(claims.getSubject(), claims.get("email").toString());
return OidcUserInfo.of(claims.getSubject());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,6 @@ public OidcUserInfo authenticate(String idToken) {
Map<String, String> header = getHeader(idToken);
Claims claims = getClaimsWithVerifySign(idToken, header);

return OidcUserInfo.of(claims.getSubject(), claims.get("email").toString());
return OidcUserInfo.of(claims.getSubject());
}
}
15 changes: 15 additions & 0 deletions src/main/java/com/munecting/api/domain/user/entity/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public class User extends BaseEntity {
private String socialId;

@NotBlank
@Column(unique = true)
private String nickname;

@Column(nullable = true)
Expand All @@ -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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public class AuthService {
private final RedisTemplate<String, String> redisTemplate;
private final OidcService oidcService;
private final UserRepository userRepository;
private final UserNicknameService nicknameService;

@Value("${jwt.refresh.expiration}")
private long refreshTokenExpiration;
Expand Down Expand Up @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
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.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;

import static com.munecting.api.global.common.dto.response.Status.*;

@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 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();
}

validateNickname(user, nickname);
return user.updateNickname(nickname);
}

private void validateNickname(User existingUser, String nickname) {
if (isInvalidNicknameLength(nickname)) {
throw new InvalidValueException(INVALID_NICKNAME_LENGTH);
}

if (isInvalidNamingRule(nickname)) {
throw new InvalidValueException(INVALID_NICKNAME_VALUE);
}

if (isDuplicatedNickname(existingUser, nickname)) {
throw new InvalidValueException(DUPLICATED_NICKNAME);
}
}

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);
}

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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,11 @@
import com.munecting.api.domain.user.dto.response.GetProfileResponseDto;
import com.munecting.api.domain.user.dto.response.UpdateProfileResponseDto;
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;
Expand All @@ -29,17 +26,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) {
Expand Down Expand Up @@ -75,32 +63,11 @@ 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);
}

@Transactional(readOnly = true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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", "잠시 후에 시도해주세요."),
Expand Down

0 comments on commit f3ea079

Please sign in to comment.