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

[REFACTOR/#174] MemberService 로직 리팩 및 회원가입 테스트 코드 작성 #176

Merged
merged 5 commits into from
Feb 11, 2025
Merged
Show file tree
Hide file tree
Changes from 3 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
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ dependencies {
runtimeOnly 'com.mysql:mysql-connector-j'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
testRuntimeOnly 'com.h2database:h2'

// Lombok
compileOnly 'org.projectlombok:lombok'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
@Entity
@Getter
@Builder
@NoArgsConstructor(access = PROTECTED)
@NoArgsConstructor
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이부분 protected 접근제어는 왜 제거된건가요?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

엇 이거 다시 추가해놀을게요!!

@AllArgsConstructor(access = PRIVATE)
public class Image {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import com.favoriteplace.app.member.controller.dto.MemberDto.EmailCheckReqDto;
import com.favoriteplace.app.member.controller.dto.MemberDto.EmailDuplicateResDto;
import com.favoriteplace.app.member.controller.dto.MemberDto.EmailSendResDto;
import com.favoriteplace.app.member.controller.dto.MemberDto.MemberSignUpReqDto;
import com.favoriteplace.app.member.controller.dto.MemberSignUpReqDto;
import com.favoriteplace.app.member.service.MailSendService;
import com.favoriteplace.app.member.service.MemberService;
import com.favoriteplace.global.util.SecurityUtil;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,4 @@ public record KaKaoSignUpRequestDto(
Boolean snsAllow,
String introduction
) {
public Member toEntity(String profileImg, Item titleItem, String email) {
return Member.builder()
.nickname(nickname)
.email(email)
.alarmAllowance(snsAllow)
.description(introduction)
.profileImageUrl(profileImg)
.point(0L)
.loginType(LoginType.KAKAO)
.profileTitle(titleItem)
.status(MemberStatus.Y)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,34 +13,6 @@
import lombok.NoArgsConstructor;

public class MemberDto {
@Getter
@NoArgsConstructor
@AllArgsConstructor
public static class MemberSignUpReqDto {
@NotBlank(message = "닉네임은 필수값입니다.")
public String nickname;

public String email;
public String password;
public Boolean snsAllow;
public String introduction;

public Member toEntity(String encodedPassword, String profileImg, Item titleItem) {
return Member.builder()
.nickname(nickname)
.email(email)
.password(encodedPassword)
.alarmAllowance(snsAllow)
.description(introduction)
.profileImageUrl(profileImg)
.point(0L)
.loginType(LoginType.SELF)
.profileTitle(titleItem)
.status(MemberStatus.Y)
.build();
}
}

@Builder
@Getter
@AllArgsConstructor
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.favoriteplace.app.member.controller.dto;

import jakarta.validation.constraints.NotBlank;

public record MemberSignUpReqDto(
@NotBlank(message = "닉네임은 필수값입니다.")
String nickname,
String email,
String password,
Boolean snsAllow,
String introduction
) {
}
20 changes: 19 additions & 1 deletion src/main/java/com/favoriteplace/app/member/domain/Member.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public class Member extends BaseTimeEntity {
@JoinColumn(name = "profile_icon_id")
private Item profileIcon;

@Column(nullable = false)
@Column(nullable = false, unique = true)
private String email;

private String password;
Expand Down Expand Up @@ -76,6 +76,24 @@ public class Member extends BaseTimeEntity {

private String fcmToken;

public static Member create(
String nickname, String email,
boolean snsAllow, String introduction,
String profileImage, Item titleItem)
{
return Member.builder()
.nickname(nickname)
.email(email)
.alarmAllowance(snsAllow)
.description(introduction)
.profileImageUrl(profileImage)
.point(0L)
.loginType(LoginType.KAKAO)
.profileTitle(titleItem)
.status(MemberStatus.Y)
.build();
}

public void updatePassword(String password) { this.password = password; }

public void updateRefreshToken(String refreshToken) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
package com.favoriteplace.app.member.repository;

import com.favoriteplace.app.member.domain.Member;

import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;

public interface MemberRepository extends JpaRepository<Member, Long> {

Optional<Member> findByEmail(String email);
boolean existsById(Long id);

List<Member> findAllByEmail(String email);
}


128 changes: 76 additions & 52 deletions src/main/java/com/favoriteplace/app/member/service/MemberService.java
Original file line number Diff line number Diff line change
@@ -1,30 +1,30 @@
package com.favoriteplace.app.member.service;

import static com.favoriteplace.global.exception.ErrorCode.NOT_SIGNUP_WITH_KAKAO;
import static com.favoriteplace.global.exception.ErrorCode.TOKEN_NOT_VALID;
import static com.favoriteplace.global.exception.ErrorCode.USER_ALREADY_EXISTS;
import static com.favoriteplace.global.exception.ErrorCode.USER_NOT_FOUND;

import com.favoriteplace.app.member.controller.dto.MemberSignUpReqDto;
import com.favoriteplace.app.member.controller.dto.TokenInfoDto;
import com.favoriteplace.app.member.domain.Member;
import com.favoriteplace.app.item.domain.Item;
import com.favoriteplace.app.item.repository.ItemRepository;
import com.favoriteplace.app.member.controller.dto.AuthKakaoLoginDto;
import com.favoriteplace.app.member.controller.dto.UserInfoResponseDto;
import com.favoriteplace.app.member.controller.dto.KaKaoSignUpRequestDto;
import com.favoriteplace.app.member.controller.dto.MemberDto;
import com.favoriteplace.app.member.controller.dto.TokenInfoDto;
import com.favoriteplace.app.member.controller.dto.UserInfoResponseDto;
import com.favoriteplace.app.member.domain.Member;
import com.favoriteplace.app.member.controller.dto.MemberDto.EmailDuplicateResDto;
import com.favoriteplace.app.member.controller.dto.MemberDto.EmailSendReqDto;
import com.favoriteplace.app.item.repository.ItemRepository;
import com.favoriteplace.app.member.repository.MemberRepository;
import com.favoriteplace.global.exception.RestApiException;
import com.favoriteplace.global.s3Image.AmazonS3ImageManager;
import com.favoriteplace.global.auth.kakao.KakaoClient;
import com.favoriteplace.global.auth.provider.JwtTokenProvider;
import com.favoriteplace.global.util.SecurityUtil;
import com.favoriteplace.global.exception.RestApiException;
import com.favoriteplace.global.s3Image.AmazonS3ImageManager;

import java.io.IOException;
import java.util.List;
import java.util.concurrent.TimeUnit;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.core.Authentication;
Expand All @@ -36,26 +36,23 @@
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
@Slf4j
public class MemberService {

private final MemberRepository memberRepository;
private final PasswordEncoder passwordEncoder;
private final JwtTokenProvider jwtTokenProvider;
private final SecurityUtil securityUtil;
private final AmazonS3ImageManager amazonS3ImageManager;
private final ItemRepository itemRepository;
private final RedisTemplate redisTemplate;
private final KakaoClient kakaoClient;

public TokenInfoDto kakaoLogin(final String token) {
AuthKakaoLoginDto userInfo = kakaoClient.getUserInfo(token);
String userEmail = getUserEmailFromKakao(token);

// 최초 로그인이라면 회원가입 API로 통신하도록
Member member = memberRepository.findByEmail(userInfo.kakaoAccount().email())
.orElseThrow(() -> new RestApiException(NOT_SIGNUP_WITH_KAKAO));
Member member = findMember(userEmail);

return jwtTokenProvider.generateToken(userInfo.kakaoAccount().email());
return issueToken(userEmail);
}

@Transactional
Expand All @@ -65,24 +62,22 @@ public MemberDto.MemberSignUpResDto kakaoSignUp(
final List<MultipartFile> images
) throws IOException {

String userEmail = kakaoClient.getUserInfo(token).kakaoAccount().email();
String userEmail = getUserEmailFromKakao(token);

memberRepository.findByEmail(userEmail)
.ifPresent(a -> {
throw new RestApiException(USER_ALREADY_EXISTS);
});

String profileImageUrl = null;
if (images != null && !images.get(0).isEmpty()) {
profileImageUrl = uploadProfileImage(images.get(0));
}
String profileImage = getProfileImageFromRequest(images);

Item titleItem = itemRepository.findByName("새싹회원").get();
Item titleItem = getDefaultProfileItem();

Member member = memberSignUpReqDto.toEntity(profileImageUrl, titleItem, userEmail);
memberRepository.save(member);
Member member = saveUser(memberSignUpReqDto.nickname(), userEmail,
memberSignUpReqDto.snsAllow(), memberSignUpReqDto.introduction(),
profileImage, titleItem);

TokenInfoDto tokenInfo = jwtTokenProvider.generateToken(userEmail);
TokenInfoDto tokenInfo = issueToken(userEmail);
member.updateRefreshToken(tokenInfo.refreshToken());

return MemberDto.MemberSignUpResDto.from(member, tokenInfo);
Expand All @@ -91,30 +86,29 @@ public MemberDto.MemberSignUpResDto kakaoSignUp(

@Transactional
public MemberDto.MemberSignUpResDto signup(
final MemberDto.MemberSignUpReqDto memberSignUpReqDto,
final MemberSignUpReqDto memberSignUpReqDto,
final List<MultipartFile> images
) throws IOException {

memberRepository.findByEmail(memberSignUpReqDto.getEmail())
.ifPresent(
existingMember -> {
throw new RestApiException(USER_ALREADY_EXISTS);
}
);
// memberRepository.findByEmail(memberSignUpReqDto.getEmail())
// .ifPresent(
// existingMember -> {
// throw new RestApiException(USER_ALREADY_EXISTS);
// }
// );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

사용하지 않는 코드라면 지워주세요!!


String profileImageUrl = null;
if (images != null && !images.get(0).isEmpty()) {
profileImageUrl = uploadProfileImage(images.get(0));
}

String password = passwordEncoder.encode(memberSignUpReqDto.getPassword());
String password = passwordEncoder.encode(memberSignUpReqDto.password());

Item titleItem = itemRepository.findByName("새싹회원").get();
Item titleItem = getDefaultProfileItem();

Member member = memberSignUpReqDto.toEntity(password, profileImageUrl, titleItem);
memberRepository.save(member);
String profileImage = getProfileImageFromRequest(images);

TokenInfoDto tokenInfo = jwtTokenProvider.generateToken(member.getEmail());
Member member = saveUser(memberSignUpReqDto.nickname(), memberSignUpReqDto.email(),
memberSignUpReqDto.snsAllow(), memberSignUpReqDto.introduction(),
profileImage, titleItem);

TokenInfoDto tokenInfo= issueToken(member.getEmail());
member.updateRefreshToken(tokenInfo.refreshToken());

return MemberDto.MemberSignUpResDto.from(member, tokenInfo);
Expand All @@ -124,12 +118,11 @@ public String uploadProfileImage(MultipartFile profileImage) throws IOException
return amazonS3ImageManager.upload(profileImage).join();
}

@Transactional
public MemberDto.EmailDuplicateResDto emailDuplicateCheck(MemberDto.EmailSendReqDto emailSendReqDto) {
public MemberDto.EmailDuplicateResDto emailDuplicateCheck(EmailSendReqDto emailSendReqDto) {
String email = emailSendReqDto.getEmail();
Boolean isExists = memberRepository.findByEmail(email).isPresent();

return new MemberDto.EmailDuplicateResDto(isExists);
return new EmailDuplicateResDto(isExists);
}

@Transactional
Expand All @@ -140,7 +133,6 @@ public void setNewPassword(String email, String password) {
member.updatePassword(newPassword);
}

@Transactional
public UserInfoResponseDto getUserInfo(Member member) {
if (member != null) {
return UserInfoResponseDto.of(member);
Expand All @@ -149,25 +141,57 @@ public UserInfoResponseDto getUserInfo(Member member) {
}

@Transactional
public void logout(String token){
Authentication authentication = jwtTokenProvider.getAuthentication(token);
public void logout(String accessToken) {
/*1. Access Token 검증 */
if (!jwtTokenProvider.validateToken(accessToken)) {
new RestApiException(TOKEN_NOT_VALID);
}

Authentication authentication = jwtTokenProvider.getAuthentication(accessToken);
Member member = findMember(authentication.getName());

if (member.getRefreshToken() != null && !member.getRefreshToken().isEmpty()) {
member.deleteRefreshToken(member.getRefreshToken());
}

/* 해당 asscess token 유효시간을 계산해서 blacklist로 저장 */
Long expriation = jwtTokenProvider.getExpiration(token);
log.info(String.valueOf(expriation));
Long expriation = jwtTokenProvider.getExpiration(accessToken);
redisTemplate.opsForValue()
.set(token, "logout", expriation, TimeUnit.MILLISECONDS);

.set(accessToken, "logout", expriation, TimeUnit.MICROSECONDS);
}

public Member findMember(final String email) {
private Member findMember(final String email) {
return memberRepository.findByEmail(email)
.orElseThrow(() -> new RestApiException(USER_NOT_FOUND));
}

private String getUserEmailFromKakao(String token) {
return kakaoClient.getUserInfo(token).kakaoAccount().email();
}

private Item getDefaultProfileItem() {
return itemRepository.findByName("새싹회원").get();
}

private TokenInfoDto issueToken(String email) {
return jwtTokenProvider.generateToken(email);
}

private Member saveUser(
String nickname, String email,
boolean snsAllow, String introduction,
String profileImage, Item titleItem)
{
Member member = Member.create(nickname, email, snsAllow, introduction, profileImage, titleItem);
return memberRepository.save(member);
}

private String getProfileImageFromRequest(List<MultipartFile> images) throws IOException {
String profileImageUrl = null;
if (images != null && !images.get(0).isEmpty()) {
profileImageUrl = uploadProfileImage(images.get(0));
}
return profileImageUrl;
}

}
Loading
Loading