Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
12 changes: 11 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,16 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation "org.springframework.boot:spring-boot-starter-webflux"

// Security
implementation 'org.springframework.boot:spring-boot-starter-security'
testImplementation 'org.springframework.security:spring-security-test'

// Jwt
implementation 'io.jsonwebtoken:jjwt-api:0.12.3'
implementation 'io.jsonwebtoken:jjwt-impl:0.12.3'
implementation 'io.jsonwebtoken:jjwt-jackson:0.12.3'
implementation 'org.springframework.boot:spring-boot-configuration-processor'

// QueryDSl
implementation "io.github.openfeign.querydsl:querydsl-jpa:7.0"
implementation "io.github.openfeign.querydsl:querydsl-core:7.0"
Expand All @@ -39,9 +49,9 @@ dependencies {

runtimeOnly 'com.mysql:mysql-connector-j'

//Lombok
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'

testCompileOnly 'org.projectlombok:lombok'
testAnnotationProcessor 'org.projectlombok:lombok'

Expand Down
13 changes: 11 additions & 2 deletions src/main/java/umc/domain/member/controller/MemberController.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import umc.domain.member.dto.MemberResDTO;
import umc.domain.member.exception.code.MemberSuccessCode;
import umc.domain.member.service.common.MemberCommandService;
import umc.domain.member.service.query.MemberQueryService;
import umc.global.apiPayload.ApiResponse;
import umc.global.apiPayload.code.GeneralSuccessCode;

Expand All @@ -16,7 +17,7 @@
public class MemberController {

private final MemberCommandService memberCommandService;

private final MemberQueryService memberQueryService;

@DeleteMapping("/{memberId}/hard")
ApiResponse<Void> hardDelete(
Expand All @@ -33,7 +34,15 @@ ApiResponse<Void> hardDelete(

@PostMapping("/sign-up")
ApiResponse<MemberResDTO.JoinDTO> signUp(
@RequestBody @Valid MemberReqDTO.JoinDTO dto) {
@RequestBody @Valid MemberReqDTO.JoinDTO dto
) {
return ApiResponse.onSuccess(MemberSuccessCode.CREATED, memberCommandService.signUp(dto));
}

@PostMapping("/login")
ApiResponse<MemberResDTO.LoginDTO> login(
@RequestBody @Valid MemberReqDTO.LoginDTO dto
) {
return ApiResponse.onSuccess(MemberSuccessCode.FOUND, memberQueryService.login(dto));
}
Comment on lines +42 to +47
Copy link
Collaborator

Choose a reason for hiding this comment

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

로그인/회원가입 같은 권한 관련 코드는 따로 Auth 패키지 파서 분리해줘도 좋아보여요!

}
21 changes: 18 additions & 3 deletions src/main/java/umc/domain/member/converter/MemberConverter.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import umc.domain.member.entity.Member;
import umc.domain.member.entity.MemberStatus;
import umc.domain.member.entity.MemberType;
import umc.global.security.CustomUserDetails;

public class MemberConverter {

Expand All @@ -17,20 +18,34 @@ public static MemberResDTO.JoinDTO toJoinDTO(
.build();
}

public static MemberResDTO.LoginDTO toLoginDTO(
Member member,
String accessToken
){
return MemberResDTO.LoginDTO.builder()
.memberId(member.getId())
.accessToken(accessToken)
.build();
}

public static Member toMember(
MemberReqDTO.JoinDTO dto
MemberReqDTO.JoinDTO dto,
String password,
MemberType memberType
){
return Member.builder()
.name(dto.name())
.email(dto.email())
.password(password)
.gender(dto.gender())
.birth(dto.birth())
.address(dto.address())
.email(dto.email())
.point(0)
.memberType(MemberType.USER)
.memberType(memberType)
.phoneNumber(dto.phoneNumber())
.memberStatus(MemberStatus.ACTIVE)
.build();

}

}
7 changes: 7 additions & 0 deletions src/main/java/umc/domain/member/dto/MemberReqDTO.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,13 @@ public record JoinDTO(
List<Long> preferredFoods
) {}

public record LoginDTO(
@NotBlank
String email,
@NotBlank
String password
){}

public record AgreementDTO(
Long policyId,
boolean agreed
Expand Down
6 changes: 6 additions & 0 deletions src/main/java/umc/domain/member/dto/MemberResDTO.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,10 @@ public record JoinDTO(
LocalDateTime createAt
) {
}

@Builder
public record LoginDTO(
Long memberId,
String accessToken
){}
}
5 changes: 5 additions & 0 deletions src/main/java/umc/domain/member/entity/Member.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ public class Member extends BaseEntity {
@Column(nullable = false, length = 100)
private String email;

@Column(nullable = false)
private String password;

@Column(nullable = false)
private Integer point;

Expand Down Expand Up @@ -86,6 +89,7 @@ public Member(
LocalDate birth,
@NonNull String address,
@NonNull String email,
@NonNull String password,
Integer point,
SocialType socialType,
String socialUid,
Expand All @@ -98,6 +102,7 @@ public Member(
this.birth = birth;
this.address = address;
this.email = email;
this.password = password;
this.point = (point != null) ? point : 0;
this.socialType = socialType;
this.socialUid = socialUid;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,21 @@
@AllArgsConstructor
public enum MemberErrorCode implements BaseErrorCode {

NOT_FOUND_MEMBER(HttpStatus.NOT_FOUND, "MEMBER404_1", "존재하지 않는 회원입니다."),
NOT_OWNER(HttpStatus.FORBIDDEN, "MEMBER403_1", "해당 회원은 OWNER 권한이 아닙니다.");
NOT_FOUND_MEMBER(
HttpStatus.NOT_FOUND,
"MEMBER404_1",
"존재하지 않는 회원입니다."),

NOT_OWNER(
HttpStatus.FORBIDDEN,
"MEMBER403_1",
"해당 회원은 OWNER 권한이 아닙니다."),

INVALID_PASSWORD(
HttpStatus.UNAUTHORIZED,
"MEMBER401_1",
"비밀번호가 올바르지 않습니다."
);

private final HttpStatus status;
private final String code;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,5 @@ public interface MemberRepository extends JpaRepository<Member, Long> {
@Query("delete from Member m where m.id = :id")
int hardDeleteById(@Param("id") Long id);

Optional<Member> findByEmail(String email);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import umc.domain.inquiry.repository.InquiryRepository;
Expand All @@ -15,6 +16,7 @@
import umc.domain.member.dto.MemberResDTO;
import umc.domain.member.entity.Food;
import umc.domain.member.entity.Member;
import umc.domain.member.entity.MemberType;
import umc.domain.member.entity.Policy;
import umc.domain.member.entity.PolicyType;
import umc.domain.member.entity.mapping.MemberFood;
Expand Down Expand Up @@ -49,18 +51,15 @@ public class MemberCommandServiceImpl implements MemberCommandService {
private final PolicyRepository policyRepository;
private final MemberPolicyRepository memberPolicyRepository;

@Transactional
@Override
public MemberResDTO.JoinDTO signUp(
MemberReqDTO.JoinDTO dto
) {
private final PasswordEncoder passwordEncoder;

Member member = MemberConverter.toMember(dto);

memberRepository.save(member);
@Transactional
@Override
public MemberResDTO.JoinDTO signUp(MemberReqDTO.JoinDTO dto) {

// 약관 동의 처리
if(dto.agreements()== null || dto.agreements().isEmpty()){
// 1) 약관 동의 처리 (검증 먼저)
if (dto.agreements() == null || dto.agreements().isEmpty()) {
throw new PolicyException(PolicyErrorCode.REQUIRED_POLICY_NOT_ACCEPTED);
}

Expand All @@ -84,6 +83,24 @@ public MemberResDTO.JoinDTO signUp(
throw new PolicyException(PolicyErrorCode.REQUIRED_POLICY_NOT_ACCEPTED);
}

// 2) 선호 음식 검증도 save 전에 수행
List<Food> foods = List.of();
List<Long> foodIds = dto.preferredFoods();

if (foodIds != null && !foodIds.isEmpty()) {
foods = foodRepository.findAllById(foodIds);

if (foods.size() != foodIds.size()) {
throw new FoodException(FoodErrorCode.NOT_FOUND_FOOD);
}
}

// 3) 모든 검증 끝난 후 회원 저장
String salt = passwordEncoder.encode(dto.password());
Member member = MemberConverter.toMember(dto, salt, MemberType.USER);
memberRepository.save(member);

// 4) 약관 매핑 저장
List<MemberPolicy> memberPolicies = policies.stream()
.map(policy -> MemberPolicy.builder()
.member(member)
Expand All @@ -94,17 +111,8 @@ public MemberResDTO.JoinDTO signUp(

memberPolicyRepository.saveAll(memberPolicies);


// 선호 음식 매핑
if (dto.preferredFoods() != null && !dto.preferredFoods().isEmpty()) {

List<Long> foodIds = dto.preferredFoods();
List<Food> foods = foodRepository.findAllById(foodIds);

if (foods.size() != foodIds.size()) {
throw new FoodException(FoodErrorCode.NOT_FOUND_FOOD);
}

// 5) 선호 음식 매핑 저장 (검증 때 조회한 foods 재사용)
if (!foods.isEmpty()) {
List<MemberFood> memberFoods = foods.stream()
.map(food -> MemberFood.builder()
.member(member)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
package umc.domain.member.service.query;

import jakarta.validation.Valid;
import umc.domain.member.dto.GetMemberResponse;
import umc.domain.member.dto.MemberReqDTO;
import umc.domain.member.dto.MemberResDTO.LoginDTO;

public interface MemberQueryService {
GetMemberResponse getMember(Long memberId);

LoginDTO login(MemberReqDTO.@Valid LoginDTO dto);
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,51 @@
package umc.domain.member.service.query;

import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import umc.domain.member.converter.MemberConverter;
import umc.domain.member.dto.GetMemberResponse;
import umc.domain.member.dto.MemberReqDTO;
import umc.domain.member.dto.MemberResDTO;
import umc.domain.member.dto.MemberResDTO.LoginDTO;
import umc.domain.member.entity.Member;
import umc.domain.member.exception.MemberException;
import umc.domain.member.exception.code.MemberErrorCode;
import umc.domain.member.repository.MemberRepository;
import umc.global.security.CustomUserDetails;
import umc.global.security.JwtUtil;

@Transactional(readOnly = true)
@Service
@RequiredArgsConstructor
public class MemberQueryServiceImpl implements MemberQueryService{

private final MemberRepository memberRepository;
private final PasswordEncoder passwordEncoder;
private final JwtUtil jwtUtil;

public GetMemberResponse getMember(Long memberId) {
return memberRepository.findMemberInfo(memberId);
}

@Override
public MemberResDTO.LoginDTO login(MemberReqDTO.@Valid LoginDTO dto) {

Member member = memberRepository.findByEmail(dto.email())
.orElseThrow(() -> new MemberException(MemberErrorCode.NOT_FOUND_MEMBER));

if (!passwordEncoder.matches(dto.password(), member.getPassword())){
throw new MemberException(MemberErrorCode.INVALID_PASSWORD);
}

CustomUserDetails userDetails = new CustomUserDetails(member);

String accessToken = jwtUtil.createAccessToken(userDetails);

return MemberConverter.toLoginDTO(member, accessToken);
}


}
Loading