From f9b6a7def4c50e0be798fdab9e6237413749a11a Mon Sep 17 00:00:00 2001 From: popeye Date: Thu, 14 May 2026 00:44:58 +0900 Subject: [PATCH] feat(user): refresh profile onboarding --- .../common/domain/enums/ContactFrequency.java | 23 ++++ .../dto/member/RealNameResponseDto.java | 6 + .../domain/enums/ContactFrequencyTest.java | 38 ++++++ .../auth/service/SignupServiceImpl.java | 2 +- .../member/dto/ProfileUpdateRequest.java | 2 + .../user/domain/member/entity/Member.java | 1 + .../user/domain/member/entity/Profile.java | 5 +- .../member/seed/DummyUserSeedRunner.java | 113 ++++++++++++++-- .../domain/member/service/MemberService.java | 2 + .../member/service/MemberServiceImpl.java | 29 +++- .../member/service/ProfileServiceImpl.java | 53 ++++---- .../infra/controller/MemberController.java | 11 ++ .../src/main/resources/application-aws.yml | 13 +- .../src/main/resources/application.yml | 7 +- ...r_female 1.png => animal_bear_female1.png} | Bin ...{bear_male 1.png => animal_bear_male1.png} | Bin ...at_female 1.png => animal_cat_female1.png} | Bin .../{cat_male 1.png => animal_cat_male1.png} | Bin .../{dinosaur 1.png => animal_dinosaur1.png} | Bin ...og_female 1.png => animal_dog_female1.png} | Bin .../{dog_male 1.png => animal_dog_male1.png} | Bin ...ox_female 1.png => animal_fox_female1.png} | Bin .../{fox_male 1.png => animal_fox_male1.png} | Bin .../{horse_male.png => animal_horse_male.png} | Bin ..._female 1.png => animal_otter_female1.png} | Bin ...tter_male 1.png => animal_otter_male1.png} | Bin ...female 1.png => animal_rabbit_female1.png} | Bin ...bit_male 1.png => animal_rabbit_male1.png} | Bin ...ake_female.png => animal_snake_female.png} | Bin ...f_female 1.png => animal_wolf_female1.png} | Bin ...{Wolf_male 1.png => animal_wolf_male1.png} | Bin .../src/main/resources/images/default.png | Bin 0 -> 3206 bytes .../auth/service/SignupServiceImplTest.java | 126 ++++++++++++++++++ .../member/dto/ProfileUpdateRequestTest.java | 37 +++++ .../domain/member/entity/ProfileTest.java | 37 +++++ .../member/service/MemberServiceImplTest.java | 61 +++++++++ .../service/ProfileServiceImplTest.java | 11 +- 37 files changed, 521 insertions(+), 56 deletions(-) create mode 100644 common-module/src/main/java/com/comatching/common/dto/member/RealNameResponseDto.java create mode 100644 common-module/src/test/java/com/comatching/common/domain/enums/ContactFrequencyTest.java rename user-service/src/main/resources/images/{bear_female 1.png => animal_bear_female1.png} (100%) rename user-service/src/main/resources/images/{bear_male 1.png => animal_bear_male1.png} (100%) rename user-service/src/main/resources/images/{cat_female 1.png => animal_cat_female1.png} (100%) rename user-service/src/main/resources/images/{cat_male 1.png => animal_cat_male1.png} (100%) rename user-service/src/main/resources/images/{dinosaur 1.png => animal_dinosaur1.png} (100%) rename user-service/src/main/resources/images/{dog_female 1.png => animal_dog_female1.png} (100%) rename user-service/src/main/resources/images/{dog_male 1.png => animal_dog_male1.png} (100%) rename user-service/src/main/resources/images/{fox_female 1.png => animal_fox_female1.png} (100%) rename user-service/src/main/resources/images/{fox_male 1.png => animal_fox_male1.png} (100%) rename user-service/src/main/resources/images/{horse_male.png => animal_horse_male.png} (100%) rename user-service/src/main/resources/images/{otter_female 1.png => animal_otter_female1.png} (100%) rename user-service/src/main/resources/images/{otter_male 1.png => animal_otter_male1.png} (100%) rename user-service/src/main/resources/images/{rabbit_female 1.png => animal_rabbit_female1.png} (100%) rename user-service/src/main/resources/images/{rabbit_male 1.png => animal_rabbit_male1.png} (100%) rename user-service/src/main/resources/images/{snake_female.png => animal_snake_female.png} (100%) rename user-service/src/main/resources/images/{Wolf_female 1.png => animal_wolf_female1.png} (100%) rename user-service/src/main/resources/images/{Wolf_male 1.png => animal_wolf_male1.png} (100%) create mode 100644 user-service/src/main/resources/images/default.png create mode 100644 user-service/src/test/java/com/comatching/user/domain/auth/service/SignupServiceImplTest.java create mode 100644 user-service/src/test/java/com/comatching/user/domain/member/dto/ProfileUpdateRequestTest.java diff --git a/common-module/src/main/java/com/comatching/common/domain/enums/ContactFrequency.java b/common-module/src/main/java/com/comatching/common/domain/enums/ContactFrequency.java index a819d95..b6138de 100644 --- a/common-module/src/main/java/com/comatching/common/domain/enums/ContactFrequency.java +++ b/common-module/src/main/java/com/comatching/common/domain/enums/ContactFrequency.java @@ -1,5 +1,9 @@ package com.comatching.common.domain.enums; +import java.util.Arrays; + +import com.fasterxml.jackson.annotation.JsonCreator; + import lombok.Getter; import lombok.RequiredArgsConstructor; @@ -13,4 +17,23 @@ public enum ContactFrequency { private final String code; private final String description; + + @JsonCreator(mode = JsonCreator.Mode.DELEGATING) + public static ContactFrequency from(String value) { + if (value == null) { + return null; + } + + String normalizedValue = value.trim(); + if (normalizedValue.isEmpty()) { + throw new IllegalArgumentException("Contact frequency value is blank"); + } + + return Arrays.stream(values()) + .filter(frequency -> frequency.name().equalsIgnoreCase(normalizedValue) + || frequency.code.equals(normalizedValue) + || frequency.description.equals(normalizedValue)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("Unknown contact frequency: " + value)); + } } diff --git a/common-module/src/main/java/com/comatching/common/dto/member/RealNameResponseDto.java b/common-module/src/main/java/com/comatching/common/dto/member/RealNameResponseDto.java new file mode 100644 index 0000000..e6f935d --- /dev/null +++ b/common-module/src/main/java/com/comatching/common/dto/member/RealNameResponseDto.java @@ -0,0 +1,6 @@ +package com.comatching.common.dto.member; + +public record RealNameResponseDto( + String realName +) { +} diff --git a/common-module/src/test/java/com/comatching/common/domain/enums/ContactFrequencyTest.java b/common-module/src/test/java/com/comatching/common/domain/enums/ContactFrequencyTest.java new file mode 100644 index 0000000..e0bf1fe --- /dev/null +++ b/common-module/src/test/java/com/comatching/common/domain/enums/ContactFrequencyTest.java @@ -0,0 +1,38 @@ +package com.comatching.common.domain.enums; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import com.fasterxml.jackson.databind.json.JsonMapper; + +@DisplayName("ContactFrequency Enum 테스트") +class ContactFrequencyTest { + + private final JsonMapper objectMapper = JsonMapper.builder().build(); + + @Test + @DisplayName("영문 enum 이름으로 역직렬화할 수 있다") + void shouldDeserializeFromEnumName() throws Exception { + ContactFrequency result = objectMapper.readValue("\"FREQUENT\"", ContactFrequency.class); + + assertThat(result).isEqualTo(ContactFrequency.FREQUENT); + } + + @Test + @DisplayName("한글 코드로 역직렬화할 수 있다") + void shouldDeserializeFromKoreanCode() throws Exception { + ContactFrequency result = objectMapper.readValue("\"자주\"", ContactFrequency.class); + + assertThat(result).isEqualTo(ContactFrequency.FREQUENT); + } + + @Test + @DisplayName("한글 설명으로 역직렬화할 수 있다") + void shouldDeserializeFromKoreanDescription() throws Exception { + ContactFrequency result = objectMapper.readValue("\"보통 연락\"", ContactFrequency.class); + + assertThat(result).isEqualTo(ContactFrequency.NORMAL); + } +} diff --git a/user-service/src/main/java/com/comatching/user/domain/auth/service/SignupServiceImpl.java b/user-service/src/main/java/com/comatching/user/domain/auth/service/SignupServiceImpl.java index 170b863..055a0b9 100644 --- a/user-service/src/main/java/com/comatching/user/domain/auth/service/SignupServiceImpl.java +++ b/user-service/src/main/java/com/comatching/user/domain/auth/service/SignupServiceImpl.java @@ -82,7 +82,7 @@ public CompleteSignupResponse completeSignup(MemberInfo memberInfo, ProfileCreat return CompleteSignupResponse.builder() .profile(profileResponse) - .isOnboardingFinished(false) + .isOnboardingFinished(true) .build(); } } diff --git a/user-service/src/main/java/com/comatching/user/domain/member/dto/ProfileUpdateRequest.java b/user-service/src/main/java/com/comatching/user/domain/member/dto/ProfileUpdateRequest.java index 564d1ee..3f2f350 100644 --- a/user-service/src/main/java/com/comatching/user/domain/member/dto/ProfileUpdateRequest.java +++ b/user-service/src/main/java/com/comatching/user/domain/member/dto/ProfileUpdateRequest.java @@ -8,11 +8,13 @@ import com.comatching.common.domain.enums.SocialAccountType; import com.comatching.common.dto.member.HobbyDto; import com.comatching.common.dto.member.ProfileTagDto; +import com.fasterxml.jackson.annotation.JsonAlias; public record ProfileUpdateRequest( String nickname, String intro, String mbti, + @JsonAlias("profileImageKey") String profileImageUrl, Gender gender, LocalDate birthDate, diff --git a/user-service/src/main/java/com/comatching/user/domain/member/entity/Member.java b/user-service/src/main/java/com/comatching/user/domain/member/entity/Member.java index 6604be8..008d924 100644 --- a/user-service/src/main/java/com/comatching/user/domain/member/entity/Member.java +++ b/user-service/src/main/java/com/comatching/user/domain/member/entity/Member.java @@ -89,6 +89,7 @@ public void withdraw() { this.email = "withdrawn_" + this.id + "_" + uuid + "@deleted.com"; this.password = null; + this.realName = null; this.socialType = null; this.socialId = null; diff --git a/user-service/src/main/java/com/comatching/user/domain/member/entity/Profile.java b/user-service/src/main/java/com/comatching/user/domain/member/entity/Profile.java index 815f02b..1a8390a 100644 --- a/user-service/src/main/java/com/comatching/user/domain/member/entity/Profile.java +++ b/user-service/src/main/java/com/comatching/user/domain/member/entity/Profile.java @@ -111,12 +111,15 @@ public void clearProfileData() { this.nickname = "탈퇴한 사용자"; this.intro = null; this.profileImageUrl = null; - this.birthDate = null; + this.birthDate = LocalDate.of(1970, 1, 1); + this.mbti = "UNKNOWN"; + this.university = "(알 수 없음)"; this.socialAccountType = null; this.socialAccountId = null; this.major = "(알 수 없음)"; this.song = null; this.point = 0; + this.isMatchable = false; } public void update( diff --git a/user-service/src/main/java/com/comatching/user/domain/member/seed/DummyUserSeedRunner.java b/user-service/src/main/java/com/comatching/user/domain/member/seed/DummyUserSeedRunner.java index 3f7fa94..666566d 100644 --- a/user-service/src/main/java/com/comatching/user/domain/member/seed/DummyUserSeedRunner.java +++ b/user-service/src/main/java/com/comatching/user/domain/member/seed/DummyUserSeedRunner.java @@ -22,6 +22,7 @@ import com.comatching.common.domain.enums.DefaultHobby; import com.comatching.common.domain.enums.Gender; import com.comatching.common.domain.enums.ProfileTagItem; +import com.comatching.common.domain.enums.SocialAccountType; import com.comatching.common.dto.member.HobbyDto; import com.comatching.common.dto.member.ProfileCreateRequest; import com.comatching.common.dto.member.ProfileTagDto; @@ -98,14 +99,19 @@ public class DummyUserSeedRunner implements ApplicationRunner { "자유전공학부" ); private static final List IMAGE_KEY_POOL = List.of( - "default_dog", "default_cat", "default_bear", "default_fox", "default_rabbit", "default_otter" + "default", + "default_dog", "default_cat", "default_bear", "default_fox", "default_rabbit", "default_otter", + "default_wolf", "default_horse", "default_snake", "default_dinosaur" ); private static final List INTRO_POOL = List.of( - "hello there", - "coffee and coding", - "weekend hiking", - "always learning", - "music and books" + "새로운 사람을 알아가는 시간을 좋아해요.", + "대화가 잘 통하는 사람과 편하게 친해지고 싶어요.", + "주말에는 카페나 산책으로 기분 전환하는 편이에요.", + "취향을 나누고 같이 웃을 수 있는 만남을 기대해요.", + "천천히 알아가면서 좋은 인연을 만들고 싶어요.", + "맛있는 음식과 좋은 음악 이야기를 좋아해요.", + "처음엔 조용해도 친해지면 장난기가 많아요.", + "서로의 일상을 자연스럽게 응원하는 관계가 좋아요." ); private final DummyUserSeedService dummyUserSeedService; @@ -136,6 +142,12 @@ public class DummyUserSeedRunner implements ApplicationRunner { @Value("${dummy.seed.raw-password:Dummy!1234}") private String rawPassword; + @Value("${dummy.seed.balanced-gender:false}") + private boolean balancedGender; + + @Value("${dummy.seed.instagram-account-ratio:0.0}") + private double instagramAccountRatio; + @Value("${dummy.seed.require-allowed-profile:true}") private boolean requireAllowedProfile; @@ -160,6 +172,8 @@ public void run(ApplicationArguments args) { List universities = resolveValuePool(universitiesRaw, DEFAULT_UNIVERSITIES); List majors = resolveValuePool(majorsRaw, DEFAULT_MAJORS); Random random = new Random(); + List genderPlan = createGenderPlan(random, targetCount); + List instagramAccountPlan = createInstagramAccountPlan(random, targetCount); String encodedPassword = passwordEncoder.encode(rawPassword); int created = 0; @@ -179,7 +193,11 @@ public void run(ApplicationArguments args) { sequence++; try { - createSingleDummyUser(random, email, nickname, encodedPassword, universities, majors); + Gender gender = resolveGender(random, genderPlan, created); + boolean useInstagramAccount = resolveInstagramAccount(instagramAccountPlan, created); + createSingleDummyUser( + random, email, nickname, encodedPassword, universities, majors, gender, useInstagramAccount + ); created++; if (created % 100 == 0) { @@ -215,17 +233,19 @@ private void createSingleDummyUser( String nickname, String encodedPassword, List universities, - List majors + List majors, + Gender gender, + boolean useInstagramAccount ) { ProfileCreateRequest request = ProfileCreateRequest.builder() .nickname(nickname) - .gender(randomEnum(random, Gender.values())) + .gender(gender) .birthDate(randomBirthDate(random)) .mbti(randomElement(random, MBTI_POOL)) .intro(randomElement(random, INTRO_POOL)) .profileImageKey(randomElement(random, IMAGE_KEY_POOL)) - .socialType(null) - .socialAccountId(null) + .socialType(useInstagramAccount ? SocialAccountType.INSTAGRAM : null) + .socialAccountId(useInstagramAccount ? buildInstagramAccountId(nickname) : null) .university(randomElement(random, universities)) .major(randomElement(random, majors)) .contactFrequency(randomEnum(random, ContactFrequency.values())) @@ -243,10 +263,67 @@ private List randomHobbies(Random random) { int hobbyCount = 2 + random.nextInt(4); // 2~5 return pool.subList(0, hobbyCount).stream() - .map(hobby -> new HobbyDto(hobby.getCategory(), hobby.name())) + .map(hobby -> new HobbyDto(hobby.getCategory(), removeLeadingEmoji(hobby.getDisplayName()))) .toList(); } + private List createGenderPlan(Random random, int count) { + if (!balancedGender) { + return List.of(); + } + + List genders = new ArrayList<>(count); + int maleCount = count / 2; + int femaleCount = count / 2; + if (count % 2 == 1) { + if (random.nextBoolean()) { + maleCount++; + } else { + femaleCount++; + } + } + + for (int i = 0; i < maleCount; i++) { + genders.add(Gender.MALE); + } + for (int i = 0; i < femaleCount; i++) { + genders.add(Gender.FEMALE); + } + Collections.shuffle(genders, random); + return genders; + } + + private Gender resolveGender(Random random, List genderPlan, int createdCount) { + if (!balancedGender) { + return randomEnum(random, Gender.values()); + } + return genderPlan.get(createdCount); + } + + private List createInstagramAccountPlan(Random random, int count) { + if (instagramAccountRatio <= 0) { + return List.of(); + } + + int instagramAccountCount = (int) Math.round(count * Math.min(instagramAccountRatio, 1.0)); + List instagramAccountPlan = new ArrayList<>(count); + for (int i = 0; i < instagramAccountCount; i++) { + instagramAccountPlan.add(true); + } + for (int i = instagramAccountCount; i < count; i++) { + instagramAccountPlan.add(false); + } + Collections.shuffle(instagramAccountPlan, random); + return instagramAccountPlan; + } + + private boolean resolveInstagramAccount(List instagramAccountPlan, int createdCount) { + if (instagramAccountRatio <= 0) { + return false; + } + return instagramAccountPlan.get(createdCount); + } + private List randomTags(Random random) { List pool = new ArrayList<>(Arrays.asList(ProfileTagItem.values())); Collections.shuffle(pool, random); @@ -321,6 +398,18 @@ private String buildNickname(long sequence) { return String.format("%s_%06d", nicknamePrefix, sequence); } + private static String buildInstagramAccountId(String nickname) { + return nickname.toLowerCase(Locale.ROOT).replace("_", ".") + ".ig"; + } + + private static String removeLeadingEmoji(String displayName) { + int firstSpaceIndex = displayName.indexOf(' '); + if (firstSpaceIndex < 0) { + return displayName; + } + return displayName.substring(firstSpaceIndex + 1).trim(); + } + private static T randomElement(Random random, List list) { return list.get(random.nextInt(list.size())); } diff --git a/user-service/src/main/java/com/comatching/user/domain/member/service/MemberService.java b/user-service/src/main/java/com/comatching/user/domain/member/service/MemberService.java index 655a833..4c6b6c2 100644 --- a/user-service/src/main/java/com/comatching/user/domain/member/service/MemberService.java +++ b/user-service/src/main/java/com/comatching/user/domain/member/service/MemberService.java @@ -23,5 +23,7 @@ public interface MemberService { void updateRealName(Long memberId, String realName); + String getRealName(Long memberId); + OrdererInfoDto getOrdererInfo(Long memberId); } diff --git a/user-service/src/main/java/com/comatching/user/domain/member/service/MemberServiceImpl.java b/user-service/src/main/java/com/comatching/user/domain/member/service/MemberServiceImpl.java index d02c522..ca242ee 100644 --- a/user-service/src/main/java/com/comatching/user/domain/member/service/MemberServiceImpl.java +++ b/user-service/src/main/java/com/comatching/user/domain/member/service/MemberServiceImpl.java @@ -2,13 +2,15 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.TransactionSynchronization; +import org.springframework.transaction.support.TransactionSynchronizationManager; import org.springframework.util.StringUtils; import com.comatching.common.domain.enums.MemberRole; import com.comatching.common.domain.enums.MemberStatus; import com.comatching.common.dto.auth.MemberLoginDto; -import com.comatching.common.dto.member.OrdererInfoDto; import com.comatching.common.dto.auth.SocialLoginRequestDto; +import com.comatching.common.dto.member.OrdererInfoDto; import com.comatching.common.exception.BusinessException; import com.comatching.user.domain.event.UserEventPublisher; import com.comatching.user.domain.member.entity.Member; @@ -77,8 +79,21 @@ public void withdrawMember(Long memberId) { String email = member.getEmail(); member.withdraw(); - // Kafka 이벤트 발행 - eventPublisher.sendWithdrawEvent(memberId, email); + publishWithdrawEventAfterCommit(memberId, email); + } + + private void publishWithdrawEventAfterCommit(Long memberId, String email) { + if (!TransactionSynchronizationManager.isSynchronizationActive()) { + eventPublisher.sendWithdrawEvent(memberId, email); + return; + } + + TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { + @Override + public void afterCommit() { + eventPublisher.sendWithdrawEvent(memberId, email); + } + }); } @Override @@ -99,6 +114,14 @@ public void updateRealName(Long memberId, String realName) { member.updateRealName(realName.trim()); } + @Override + public String getRealName(Long memberId) { + Member member = memberRepository.findById(memberId) + .orElseThrow(() -> new BusinessException(UserErrorCode.USER_NOT_EXIST)); + + return member.getRealName(); + } + @Override public OrdererInfoDto getOrdererInfo(Long memberId) { Member member = memberRepository.findById(memberId) diff --git a/user-service/src/main/java/com/comatching/user/domain/member/service/ProfileServiceImpl.java b/user-service/src/main/java/com/comatching/user/domain/member/service/ProfileServiceImpl.java index d873d66..c5f5522 100644 --- a/user-service/src/main/java/com/comatching/user/domain/member/service/ProfileServiceImpl.java +++ b/user-service/src/main/java/com/comatching/user/domain/member/service/ProfileServiceImpl.java @@ -41,34 +41,32 @@ public class ProfileServiceImpl implements ProfileCreateService, ProfileManageSe private static final String DEFAULT_IMAGE_VALUE = "default"; private static final String DEFAULT_IMAGE_PREFIX = "default_"; - private static final String DEFAULT_MALE_IMAGE_FILENAME = "dog_male 1.png"; - private static final String DEFAULT_FEMALE_IMAGE_FILENAME = "cat_female 1.png"; - private static final String DEFAULT_NEUTRAL_IMAGE_FILENAME = "dinosaur 1.png"; + private static final String DEFAULT_IMAGE_FILENAME = "default.png"; private static final Map MALE_ANIMAL_IMAGE_FILENAMES = Map.of( - "dog", "dog_male 1.png", - "cat", "cat_male 1.png", - "bear", "bear_male 1.png", - "fox", "fox_male 1.png", - "rabbit", "rabbit_male 1.png", - "otter", "otter_male 1.png", - "wolf", "Wolf_male 1.png", - "horse", "horse_male.png" + "dog", "animal_dog_male1.png", + "cat", "animal_cat_male1.png", + "bear", "animal_bear_male1.png", + "fox", "animal_fox_male1.png", + "rabbit", "animal_rabbit_male1.png", + "otter", "animal_otter_male1.png", + "wolf", "animal_wolf_male1.png", + "horse", "animal_horse_male.png" ); private static final Map FEMALE_ANIMAL_IMAGE_FILENAMES = Map.of( - "dog", "dog_female 1.png", - "cat", "cat_female 1.png", - "bear", "bear_female 1.png", - "fox", "fox_female 1.png", - "rabbit", "rabbit_female 1.png", - "otter", "otter_female 1.png", - "wolf", "Wolf_female 1.png", - "snake", "snake_female.png" + "dog", "animal_dog_female1.png", + "cat", "animal_cat_female1.png", + "bear", "animal_bear_female1.png", + "fox", "animal_fox_female1.png", + "rabbit", "animal_rabbit_female1.png", + "otter", "animal_otter_female1.png", + "wolf", "animal_wolf_female1.png", + "snake", "animal_snake_female.png" ); private static final Map NEUTRAL_ANIMAL_IMAGE_FILENAMES = Map.of( - "dinosaur", "dinosaur 1.png" + "dinosaur", "animal_dinosaur1.png" ); private final MemberRepository memberRepository; @@ -256,14 +254,14 @@ private String resolveProfileImageUrlForUpdate(String profileImageValue, Gender private String resolveProfileImageUrl(String profileImageValue, Gender gender) { if (!StringUtils.hasText(profileImageValue)) { - return buildDefaultProfileImageUrl(resolveDefaultProfileImageFilename(gender)); + return buildDefaultProfileImageUrl(resolveDefaultProfileImageFilename()); } String normalizedValue = profileImageValue.trim(); String loweredValue = normalizedValue.toLowerCase(Locale.ROOT); if (DEFAULT_IMAGE_VALUE.equals(loweredValue)) { - return buildDefaultProfileImageUrl(resolveDefaultProfileImageFilename(gender)); + return buildDefaultProfileImageUrl(resolveDefaultProfileImageFilename()); } if (loweredValue.startsWith(DEFAULT_IMAGE_PREFIX)) { @@ -286,11 +284,8 @@ private String buildDefaultProfileImageUrl(String filename) { return profileImageProperties.baseUrl() + encodedFilename; } - private String resolveDefaultProfileImageFilename(Gender gender) { - if (gender == Gender.FEMALE) { - return DEFAULT_FEMALE_IMAGE_FILENAME; - } - return DEFAULT_MALE_IMAGE_FILENAME; + private String resolveDefaultProfileImageFilename() { + return DEFAULT_IMAGE_FILENAME; } private String resolveAnimalProfileImageFilename(String animalName, Gender gender) { @@ -299,7 +294,7 @@ private String resolveAnimalProfileImageFilename(String animalName, Gender gende if (StringUtils.hasText(filename)) { return filename; } - return resolveDefaultProfileImageFilename(gender); + return resolveDefaultProfileImageFilename(); } private String findAnimalProfileImageByGender(String animalName, Gender gender) { @@ -370,7 +365,7 @@ private ProfileResponse toProfileResponse(Profile profile) { .socialAccountId(profile.getSocialAccountId()) .university(profile.getUniversity()) .major(profile.getMajor()) - .contactFrequency(profile.getContactFrequency().getCode()) + .contactFrequency(profile.getContactFrequency().name()) .song(profile.getSong()) .hobbies(profile.getHobbies().stream() .map(h -> new HobbyDto(h.getCategory(), h.getName())) diff --git a/user-service/src/main/java/com/comatching/user/infra/controller/MemberController.java b/user-service/src/main/java/com/comatching/user/infra/controller/MemberController.java index e56907a..0e5a7e8 100644 --- a/user-service/src/main/java/com/comatching/user/infra/controller/MemberController.java +++ b/user-service/src/main/java/com/comatching/user/infra/controller/MemberController.java @@ -1,6 +1,7 @@ package com.comatching.user.infra.controller; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -10,6 +11,7 @@ import com.comatching.common.annotation.RequireRole; import com.comatching.common.domain.enums.MemberRole; import com.comatching.common.dto.member.MemberInfo; +import com.comatching.common.dto.member.RealNameResponseDto; import com.comatching.common.dto.member.RealNameUpdateRequestDto; import com.comatching.common.dto.response.ApiResponse; import com.comatching.user.domain.member.service.MemberService; @@ -23,6 +25,15 @@ public class MemberController { private final MemberService memberService; + @RequireRole(MemberRole.ROLE_USER) + @GetMapping("/real-name") + public ResponseEntity> getRealName( + @CurrentMember MemberInfo memberInfo + ) { + String realName = memberService.getRealName(memberInfo.memberId()); + return ResponseEntity.ok(ApiResponse.ok(new RealNameResponseDto(realName))); + } + @RequireRole(MemberRole.ROLE_USER) @PatchMapping("/real-name") public ResponseEntity> updateRealName( diff --git a/user-service/src/main/resources/application-aws.yml b/user-service/src/main/resources/application-aws.yml index cf2380e..3381ccb 100644 --- a/user-service/src/main/resources/application-aws.yml +++ b/user-service/src/main/resources/application-aws.yml @@ -13,14 +13,21 @@ server: client: url: ${CLIENT_URL:https://comatching.site} +auth: + cookie: + secure: ${AUTH_COOKIE_SECURE:true} + domain: ${AUTH_COOKIE_DOMAIN:comatching.site} + same-site: ${AUTH_COOKIE_SAME_SITE:Lax} + comatching: profile: default-images: base-url: ${DEFAULT_PROFILE_IMAGE_BASE_URL:https://comatching.site/api/public/profile-images/} filenames: - - "dog_male 1.png" - - "cat_female 1.png" - - "dinosaur 1.png" + - "default.png" + - "animal_dog_male1.png" + - "animal_cat_female1.png" + - "animal_dinosaur1.png" spring: application: diff --git a/user-service/src/main/resources/application.yml b/user-service/src/main/resources/application.yml index 1724768..c4efee3 100644 --- a/user-service/src/main/resources/application.yml +++ b/user-service/src/main/resources/application.yml @@ -23,9 +23,10 @@ comatching: default-images: base-url: ${DEFAULT_PROFILE_IMAGE_BASE_URL:http://localhost:8080/api/public/profile-images/} filenames: - - "dog_male 1.png" - - "cat_female 1.png" - - "dinosaur 1.png" + - "default.png" + - "animal_dog_male1.png" + - "animal_cat_female1.png" + - "animal_dinosaur1.png" spring: application: diff --git a/user-service/src/main/resources/images/bear_female 1.png b/user-service/src/main/resources/images/animal_bear_female1.png similarity index 100% rename from user-service/src/main/resources/images/bear_female 1.png rename to user-service/src/main/resources/images/animal_bear_female1.png diff --git a/user-service/src/main/resources/images/bear_male 1.png b/user-service/src/main/resources/images/animal_bear_male1.png similarity index 100% rename from user-service/src/main/resources/images/bear_male 1.png rename to user-service/src/main/resources/images/animal_bear_male1.png diff --git a/user-service/src/main/resources/images/cat_female 1.png b/user-service/src/main/resources/images/animal_cat_female1.png similarity index 100% rename from user-service/src/main/resources/images/cat_female 1.png rename to user-service/src/main/resources/images/animal_cat_female1.png diff --git a/user-service/src/main/resources/images/cat_male 1.png b/user-service/src/main/resources/images/animal_cat_male1.png similarity index 100% rename from user-service/src/main/resources/images/cat_male 1.png rename to user-service/src/main/resources/images/animal_cat_male1.png diff --git a/user-service/src/main/resources/images/dinosaur 1.png b/user-service/src/main/resources/images/animal_dinosaur1.png similarity index 100% rename from user-service/src/main/resources/images/dinosaur 1.png rename to user-service/src/main/resources/images/animal_dinosaur1.png diff --git a/user-service/src/main/resources/images/dog_female 1.png b/user-service/src/main/resources/images/animal_dog_female1.png similarity index 100% rename from user-service/src/main/resources/images/dog_female 1.png rename to user-service/src/main/resources/images/animal_dog_female1.png diff --git a/user-service/src/main/resources/images/dog_male 1.png b/user-service/src/main/resources/images/animal_dog_male1.png similarity index 100% rename from user-service/src/main/resources/images/dog_male 1.png rename to user-service/src/main/resources/images/animal_dog_male1.png diff --git a/user-service/src/main/resources/images/fox_female 1.png b/user-service/src/main/resources/images/animal_fox_female1.png similarity index 100% rename from user-service/src/main/resources/images/fox_female 1.png rename to user-service/src/main/resources/images/animal_fox_female1.png diff --git a/user-service/src/main/resources/images/fox_male 1.png b/user-service/src/main/resources/images/animal_fox_male1.png similarity index 100% rename from user-service/src/main/resources/images/fox_male 1.png rename to user-service/src/main/resources/images/animal_fox_male1.png diff --git a/user-service/src/main/resources/images/horse_male.png b/user-service/src/main/resources/images/animal_horse_male.png similarity index 100% rename from user-service/src/main/resources/images/horse_male.png rename to user-service/src/main/resources/images/animal_horse_male.png diff --git a/user-service/src/main/resources/images/otter_female 1.png b/user-service/src/main/resources/images/animal_otter_female1.png similarity index 100% rename from user-service/src/main/resources/images/otter_female 1.png rename to user-service/src/main/resources/images/animal_otter_female1.png diff --git a/user-service/src/main/resources/images/otter_male 1.png b/user-service/src/main/resources/images/animal_otter_male1.png similarity index 100% rename from user-service/src/main/resources/images/otter_male 1.png rename to user-service/src/main/resources/images/animal_otter_male1.png diff --git a/user-service/src/main/resources/images/rabbit_female 1.png b/user-service/src/main/resources/images/animal_rabbit_female1.png similarity index 100% rename from user-service/src/main/resources/images/rabbit_female 1.png rename to user-service/src/main/resources/images/animal_rabbit_female1.png diff --git a/user-service/src/main/resources/images/rabbit_male 1.png b/user-service/src/main/resources/images/animal_rabbit_male1.png similarity index 100% rename from user-service/src/main/resources/images/rabbit_male 1.png rename to user-service/src/main/resources/images/animal_rabbit_male1.png diff --git a/user-service/src/main/resources/images/snake_female.png b/user-service/src/main/resources/images/animal_snake_female.png similarity index 100% rename from user-service/src/main/resources/images/snake_female.png rename to user-service/src/main/resources/images/animal_snake_female.png diff --git a/user-service/src/main/resources/images/Wolf_female 1.png b/user-service/src/main/resources/images/animal_wolf_female1.png similarity index 100% rename from user-service/src/main/resources/images/Wolf_female 1.png rename to user-service/src/main/resources/images/animal_wolf_female1.png diff --git a/user-service/src/main/resources/images/Wolf_male 1.png b/user-service/src/main/resources/images/animal_wolf_male1.png similarity index 100% rename from user-service/src/main/resources/images/Wolf_male 1.png rename to user-service/src/main/resources/images/animal_wolf_male1.png diff --git a/user-service/src/main/resources/images/default.png b/user-service/src/main/resources/images/default.png new file mode 100644 index 0000000000000000000000000000000000000000..95232e9af85a3ca57a2294a6bbece031e161c71b GIT binary patch literal 3206 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 literal 0 HcmV?d00001 diff --git a/user-service/src/test/java/com/comatching/user/domain/auth/service/SignupServiceImplTest.java b/user-service/src/test/java/com/comatching/user/domain/auth/service/SignupServiceImplTest.java new file mode 100644 index 0000000..99d55b6 --- /dev/null +++ b/user-service/src/test/java/com/comatching/user/domain/auth/service/SignupServiceImplTest.java @@ -0,0 +1,126 @@ +package com.comatching.user.domain.auth.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.verify; + +import java.nio.charset.StandardCharsets; +import java.time.LocalDate; +import java.util.Base64; +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.security.crypto.password.PasswordEncoder; + +import com.comatching.common.domain.enums.Gender; +import com.comatching.common.dto.member.MemberInfo; +import com.comatching.common.dto.member.ProfileCreateRequest; +import com.comatching.common.dto.member.ProfileResponse; +import com.comatching.common.util.JwtUtil; +import com.comatching.user.domain.auth.dto.CompleteSignupResponse; +import com.comatching.user.domain.auth.entity.RefreshToken; +import com.comatching.user.domain.auth.repository.RefreshTokenRepository; +import com.comatching.user.domain.mail.service.EmailService; +import com.comatching.user.domain.member.service.MemberService; +import com.comatching.user.domain.member.service.ProfileCreateService; +import com.comatching.user.global.security.cookie.AuthCookieFactory; + +import io.jsonwebtoken.Claims; + +@ExtendWith(MockitoExtension.class) +@DisplayName("SignupServiceImpl 테스트") +class SignupServiceImplTest { + + @Mock + private EmailService emailService; + + @Mock + private MemberService memberService; + + @Mock + private ProfileCreateService profileService; + + @Mock + private PasswordEncoder passwordEncoder; + + @Mock + private RefreshTokenRepository refreshTokenRepository; + + @Test + @DisplayName("온보딩 완료 후 ROLE_USER 토큰 쿠키와 완료 상태를 반환한다") + void shouldReturnUserTokenCookieAfterCompleteSignup() { + // given + JwtUtil jwtUtil = new JwtUtil(base64Secret(), 86_400_000L, 604_800_000L); + AuthCookieFactory authCookieFactory = new AuthCookieFactory(true, "comatching.site", "Lax"); + SignupServiceImpl signupService = new SignupServiceImpl( + emailService, + memberService, + profileService, + passwordEncoder, + jwtUtil, + refreshTokenRepository, + authCookieFactory + ); + + ProfileResponse profileResponse = ProfileResponse.builder() + .memberId(810L) + .email("guest@test.com") + .nickname("온보딩완료") + .gender(Gender.MALE) + .birthDate(LocalDate.of(2000, 1, 1)) + .mbti("INTJ") + .university("가톨릭대학교") + .major("컴퓨터공학부") + .contactFrequency("FREQUENT") + .hobbies(List.of()) + .tags(List.of()) + .build(); + ProfileCreateRequest request = ProfileCreateRequest.builder().build(); + MockHttpServletResponse response = new MockHttpServletResponse(); + + given(profileService.createProfile(810L, request)).willReturn(profileResponse); + + // when + CompleteSignupResponse result = signupService.completeSignup( + new MemberInfo(810L, "guest@test.com", "ROLE_GUEST"), + request, + response + ); + + // then + assertThat(result.isOnboardingFinished()).isTrue(); + + String accessToken = extractCookieValue(response, "accessToken"); + Claims claims = jwtUtil.parseToken(accessToken); + assertThat(claims.getSubject()).isEqualTo("810"); + assertThat(claims.get("role", String.class)).isEqualTo("ROLE_USER"); + assertThat(claims.get("status", String.class)).isEqualTo("ACTIVE"); + assertThat(claims.get("nickname", String.class)).isEqualTo("온보딩완료"); + + assertThat(extractCookieValue(response, "refreshToken")).isNotBlank(); + + ArgumentCaptor refreshTokenCaptor = ArgumentCaptor.forClass(RefreshToken.class); + verify(refreshTokenRepository).save(refreshTokenCaptor.capture()); + assertThat(refreshTokenCaptor.getValue().getMemberId()).isEqualTo(810L); + } + + private static String base64Secret() { + return Base64.getEncoder() + .encodeToString("01234567890123456789012345678901".getBytes(StandardCharsets.UTF_8)); + } + + private static String extractCookieValue(MockHttpServletResponse response, String cookieName) { + return response.getHeaders("Set-Cookie") + .stream() + .filter(cookie -> cookie.startsWith(cookieName + "=")) + .findFirst() + .map(cookie -> cookie.substring((cookieName + "=").length(), cookie.indexOf(';'))) + .orElseThrow(); + } +} diff --git a/user-service/src/test/java/com/comatching/user/domain/member/dto/ProfileUpdateRequestTest.java b/user-service/src/test/java/com/comatching/user/domain/member/dto/ProfileUpdateRequestTest.java new file mode 100644 index 0000000..00a6516 --- /dev/null +++ b/user-service/src/test/java/com/comatching/user/domain/member/dto/ProfileUpdateRequestTest.java @@ -0,0 +1,37 @@ +package com.comatching.user.domain.member.dto; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import com.comatching.common.domain.enums.ContactFrequency; +import com.fasterxml.jackson.databind.json.JsonMapper; + +@DisplayName("ProfileUpdateRequest 테스트") +class ProfileUpdateRequestTest { + + private final JsonMapper objectMapper = JsonMapper.builder().build(); + + @Test + @DisplayName("한글 연락 빈도 값을 수정 요청으로 받을 수 있다") + void shouldDeserializeKoreanContactFrequency() throws Exception { + ProfileUpdateRequest request = objectMapper.readValue( + "{\"contactFrequency\":\"자주\"}", + ProfileUpdateRequest.class + ); + + assertThat(request.contactFrequency()).isEqualTo(ContactFrequency.FREQUENT); + } + + @Test + @DisplayName("profileImageKey를 profileImageUrl 별칭으로 받을 수 있다") + void shouldDeserializeProfileImageKeyAlias() throws Exception { + ProfileUpdateRequest request = objectMapper.readValue( + "{\"profileImageKey\":\"default_cat\"}", + ProfileUpdateRequest.class + ); + + assertThat(request.profileImageUrl()).isEqualTo("default_cat"); + } +} diff --git a/user-service/src/test/java/com/comatching/user/domain/member/entity/ProfileTest.java b/user-service/src/test/java/com/comatching/user/domain/member/entity/ProfileTest.java index b3a74ae..5eb542b 100644 --- a/user-service/src/test/java/com/comatching/user/domain/member/entity/ProfileTest.java +++ b/user-service/src/test/java/com/comatching/user/domain/member/entity/ProfileTest.java @@ -14,6 +14,7 @@ import com.comatching.common.domain.enums.ContactFrequency; import com.comatching.common.domain.enums.Gender; import com.comatching.common.domain.enums.ProfileTagItem; +import com.comatching.common.domain.enums.SocialAccountType; import com.comatching.common.exception.BusinessException; @DisplayName("Profile 태그 관리 테스트") @@ -33,6 +34,42 @@ void setUp() { .build(); } + @Test + @DisplayName("탈퇴 프로필 마스킹은 필수 컬럼을 null로 만들지 않는다") + void shouldMaskWithdrawnProfileWithoutNullingRequiredFields() { + // given + Profile profile = Profile.builder() + .nickname("손뻗는 조향사") + .gender(Gender.MALE) + .birthDate(LocalDate.of(2000, 1, 1)) + .intro("intro") + .mbti("INTJ") + .profileImageUrl("https://example.com/profile.png") + .socialAccountType(SocialAccountType.INSTAGRAM) + .socialAccountId("@id") + .university("가톨릭대학교") + .major("정보통신전자공학부") + .contactFrequency(ContactFrequency.FREQUENT) + .song("song") + .build(); + + // when + profile.clearProfileData(); + + // then + assertThat(profile.getNickname()).isEqualTo("탈퇴한 사용자"); + assertThat(profile.getBirthDate()).isEqualTo(LocalDate.of(1970, 1, 1)); + assertThat(profile.getMbti()).isEqualTo("UNKNOWN"); + assertThat(profile.getUniversity()).isEqualTo("(알 수 없음)"); + assertThat(profile.getMajor()).isEqualTo("(알 수 없음)"); + assertThat(profile.isMatchable()).isFalse(); + assertThat(profile.getIntro()).isNull(); + assertThat(profile.getProfileImageUrl()).isNull(); + assertThat(profile.getSocialAccountType()).isNull(); + assertThat(profile.getSocialAccountId()).isNull(); + assertThat(profile.getSong()).isNull(); + } + @Nested @DisplayName("addTags 메서드") class AddTags { diff --git a/user-service/src/test/java/com/comatching/user/domain/member/service/MemberServiceImplTest.java b/user-service/src/test/java/com/comatching/user/domain/member/service/MemberServiceImplTest.java index 2d2940a..78a4f13 100644 --- a/user-service/src/test/java/com/comatching/user/domain/member/service/MemberServiceImplTest.java +++ b/user-service/src/test/java/com/comatching/user/domain/member/service/MemberServiceImplTest.java @@ -2,6 +2,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; + +import java.util.Optional; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -9,10 +13,14 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.transaction.support.TransactionSynchronization; +import org.springframework.transaction.support.TransactionSynchronizationManager; import com.comatching.common.domain.enums.MemberRole; import com.comatching.common.domain.enums.MemberStatus; +import com.comatching.common.domain.enums.SocialType; import com.comatching.user.domain.event.UserEventPublisher; +import com.comatching.user.domain.member.entity.Member; import com.comatching.user.domain.member.repository.MemberRepository; @ExtendWith(MockitoExtension.class) @@ -40,4 +48,57 @@ void shouldReturnActiveUserCount() { // then assertThat(result).isEqualTo(321L); } + + @Test + @DisplayName("사용자 실명을 조회한다") + void shouldReturnRealName() { + // given + Member member = Member.builder() + .email("user@test.com") + .password("password") + .socialType(SocialType.KAKAO) + .socialId("12345") + .role(MemberRole.ROLE_USER) + .status(MemberStatus.ACTIVE) + .build(); + member.updateRealName("홍길동"); + given(memberRepository.findById(805L)).willReturn(Optional.of(member)); + + // when + String result = memberService.getRealName(805L); + + // then + assertThat(result).isEqualTo("홍길동"); + } + + @Test + @DisplayName("회원 탈퇴 이벤트는 트랜잭션 커밋 이후 발행한다") + void shouldPublishWithdrawEventAfterCommit() { + // given + Member member = Member.builder() + .email("user@test.com") + .password("password") + .socialType(SocialType.KAKAO) + .socialId("12345") + .role(MemberRole.ROLE_USER) + .status(MemberStatus.ACTIVE) + .build(); + given(memberRepository.findById(805L)).willReturn(Optional.of(member)); + + TransactionSynchronizationManager.initSynchronization(); + try { + // when + memberService.withdrawMember(805L); + + // then + assertThat(member.getStatus()).isEqualTo(MemberStatus.WITHDRAWN); + verifyNoInteractions(eventPublisher); + + TransactionSynchronizationManager.getSynchronizations() + .forEach(TransactionSynchronization::afterCommit); + verify(eventPublisher).sendWithdrawEvent(805L, "user@test.com"); + } finally { + TransactionSynchronizationManager.clearSynchronization(); + } + } } diff --git a/user-service/src/test/java/com/comatching/user/domain/member/service/ProfileServiceImplTest.java b/user-service/src/test/java/com/comatching/user/domain/member/service/ProfileServiceImplTest.java index c8b5bff..c077854 100644 --- a/user-service/src/test/java/com/comatching/user/domain/member/service/ProfileServiceImplTest.java +++ b/user-service/src/test/java/com/comatching/user/domain/member/service/ProfileServiceImplTest.java @@ -100,6 +100,8 @@ void shouldCreateProfileWithTags() { // then assertThat(response).isNotNull(); + assertThat(response.profileImageUrl()).isEqualTo("https://img.com/default.png"); + assertThat(response.contactFrequency()).isEqualTo("FREQUENT"); assertThat(response.tags()).hasSize(2); assertThat(response.tags()).extracting(ProfileTagDto::tag) .containsExactly("계란형 얼굴", "밝은 분위기"); @@ -280,7 +282,7 @@ void shouldUseAnimalDefaultImageWhenCreatingProfile() { ProfileResponse response = profileService.createProfile(memberId, request); // then - assertThat(response.profileImageUrl()).isEqualTo("https://img.com/defaults/profile/dog_male%201.png"); + assertThat(response.profileImageUrl()).isEqualTo("https://img.com/defaults/profile/animal_dog_male1.png"); } @Test @@ -376,7 +378,7 @@ void shouldThrowWhenExceedingTotalTagLimitOnUpdate() { } @Test - @DisplayName("프로필 이미지 값이 default면 기본 프로필 이미지로 변경된다") + @DisplayName("프로필 이미지 값이 default면 공용 기본 프로필 이미지로 변경된다") void shouldSetDefaultProfileImageOnUpdateWhenDefaultValueProvided() { // given Long memberId = 1L; @@ -395,7 +397,7 @@ void shouldSetDefaultProfileImageOnUpdateWhenDefaultValueProvided() { ProfileResponse response = profileService.updateProfile(memberId, request); // then - assertThat(response.profileImageUrl()).isEqualTo("https://img.com/defaults/profile/dog_male%201.png"); + assertThat(response.profileImageUrl()).isEqualTo("https://img.com/defaults/profile/default.png"); } @Test @@ -418,7 +420,7 @@ void shouldUseFemaleAnimalImageOnUpdateWhenGenderIsFemale() { ProfileResponse response = profileService.updateProfile(memberId, request); // then - assertThat(response.profileImageUrl()).isEqualTo("https://img.com/defaults/profile/fox_female%201.png"); + assertThat(response.profileImageUrl()).isEqualTo("https://img.com/defaults/profile/animal_fox_female1.png"); } } @@ -471,6 +473,7 @@ void shouldReturnProfileWithTags() { // then assertThat(response).isNotNull(); + assertThat(response.contactFrequency()).isEqualTo("FREQUENT"); assertThat(response.tags()).extracting(ProfileTagDto::tag) .containsExactly("계란형 얼굴", "밝은 분위기"); }