이번 프로젝트의 목표는 Spring의 기본 CRUD 감을 익히는 것이 목표이다. 때문에 Spring Security를 사용해볼 수도있겠지만 단계적으로 나아가야한다고 생각하여 가장 기본적인 것부터 실습.
추후 목표.
1. JavaScript를 활용하여 유효성 검사
2. Model이 아닌 JSON형식으로 구현해볼것(@RestController 사용)
3. 타임리프대신 리액트를 활용하여 구현.
현재 해야할 것이 많지만 너무 조급해하지말고 천천히 하자.
1.MemberController
package com.example.bravobra.controller;
import com.example.bravobra.dto.MemberDto;
import com.example.bravobra.dto.request.FindPasswordDto;
import com.example.bravobra.service.MemberService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Controller
@RequiredArgsConstructor
@RequestMapping("/members")
@Slf4j
public class MemberController {
private final MemberService memberService;
@GetMapping("/join")
public String createForm(Model model) {
// 1. model에 MemberDto객체를 생성해서 보냄.(빈객체)
model.addAttribute("memberDto", MemberDto.builder().build());
return "members/createMemberForm"; // 2. createMemberForm을 화면에 보여줌.
}
@PostMapping("/join")
public String create(@Valid MemberDto memberDto, BindingResult result) { // @Valid로 Dto에 할당한 값의 유효성 검사.
// result에 오류 정보를 저장. // hasErrors() - 바인딩과정에서 오류가 발생했는지 확인.
// 1. 오류가 발생했으면 다시 회원가입화면으로 이동.
if (result.hasErrors()) {
return "members/createMemberForm";
}
//2. 오류가 발생하지 않았으면 이메일 중복체크(Service단 처리)
memberService.join(memberDto);
return "redirect:/";
}
@GetMapping("/list") // dto로 받는 것이 좋음. entity에 직접 접근하는 것은 좋지 않음.
public String list(Model model) {
List<MemberDto> members = memberService.findAllMemberDto();
model.addAttribute("members", members); // 담은 다음 Form으로 전달.
return "members/MemberListForm";
}
}
2. Member(Entity)
package com.example.bravobra.domain;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.CreationTimestamp;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString
@Entity
public class Member {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
private String password;
private String phoneNumber;
private String nickName;
@CreationTimestamp()
private LocalDateTime registerDate; // 계정 생성일시
private LocalDateTime lastLoginDate; // 마지막 로그인 일시
@Enumerated(EnumType.STRING) // 문자열로 저장.
private MemberType memberType; // 사용자 유형
// 비밀번호 찾거나 변경할 때 사용. setter가 없어서 메서드로.
public void updatePassword(String newPassword) {
this.password = newPassword;
}
}
3.MemberDto
package com.example.bravobra.dto;
import com.example.bravobra.domain.MemberType;
import com.example.bravobra.validation.annotation.PasswordMatch;
import com.example.bravobra.validation.annotation.UniqueNickname;
import jakarta.validation.constraints.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import java.time.LocalDateTime;
@Getter
@Builder
@AllArgsConstructor
@PasswordMatch
public class MemberDto {
@NotEmpty(message = "이메일 입력은 필수 입니다")
@Email(message = "올바른 이메일 형식이 아닙니다.")
private String email;
@NotEmpty(message = "이름 입력은 필수 입니다.")
private String name;
@NotBlank(message = "비밀번호는 필수 입력 사항입니다.")
@Size(min = 8, max = 25, message = "비밀번호는 8 ~ 25자입니다.")
@Pattern(regexp = "(?=.*[0-9])(?=.*[a-zA-Z])(?=.*[@#$%^&+=!])(?=\\S+$).{8,}", message = "비밀번호는 최소 하나의 숫자, 하나의 영문자, 하나의 특수문자를 포함해야 합니다.")
private String password;
@NotBlank(message = "비밀번호 확인은 필수입니다.")
private String passwordConfirm;
// 비밀번호 일치 여부 체크 (어노테이션을 활용한 방법)
public boolean isPasswordMatch() {
return password != null && password.equals(passwordConfirm);
}
@NotBlank(message = "닉네임 입력은 필수 입력입니다.")
@UniqueNickname // 내가 만든 어노테이션.
private String nickname;
@Pattern(regexp = "^(\\d{2,3})-(\\d{3,4})-(\\d{4})$",message = "000-0000-0000 양식을 맞춰주세용")
private String phoneNumber;
private LocalDateTime registerDate;
private MemberType memberType;
}
4.MemberRepository
package com.example.bravobra.repository;
import com.example.bravobra.domain.Member;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
@Repository
public interface MemberRepository extends JpaRepository<Member, Long> {
Optional<Member> findByEmail(String email);
boolean existsByEmail(String email); //쿼리 메서드
List<Member> findAllByEmail(String email); // 조회할때 필요.
Optional<Member> findByPhoneNumberAndName(String phoneNumber, String name);
boolean existsByPhoneNumber(String phoneNumber); // 휴대폰 번호 중복 체크
boolean existsByNickName(String nickName);
Optional<Member> findByEmailAndNameAndPhoneNumber(String email, String name, String phoneNumber);
@Modifying
@Query("UPDATE Member m SET m.password = :password WHERE m.email = :email")
void updatePassword(@Param("email") String email, @Param("password") String password);
}
5.MemberService
package com.example.bravobra.service;
import com.example.bravobra.domain.Member;
import com.example.bravobra.domain.MemberType;
import com.example.bravobra.dto.MemberDto;
import com.example.bravobra.exception.DuplicateMemberException;
import com.example.bravobra.repository.MemberRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
public class MemberService {
private final MemberRepository memberRepository;
private final BCryptPasswordEncoder passwordEncoder; // BCryptPasswordEncoder 추가
// 회원 생성
@Transactional
public Long join(MemberDto memberDto) { // Controller에서 memberDto객체를 전달 받음.
// 1. dto의 이메일 중복체크 메서드.
validateDuplicateMember(memberDto.getEmail(), memberDto.getPhoneNumber());
// 비밀번호 암호화
String encryptedPassword = passwordEncoder.encode(memberDto.getPassword());
// 2. 중복이 아니면 member에 정보 저장.
Member member = Member.builder()
.email(memberDto.getEmail())
.name(memberDto.getName())
.password(encryptedPassword)
.nickName(memberDto.getNickname())
.phoneNumber(memberDto.getPhoneNumber())
.memberType(MemberType.MEMBER)
.build();
// 확인용
System.out.println("member = " + member);
// 3. 리포지터리에 저장.
memberRepository.save(member);
return member.getId(); // member id 리턴.( id를 왜 리턴한다고 적었지?)
}
//중복체크 이메일
private void validateDuplicateMember(String email, String phoneNumber) {
if(memberRepository.existsByEmail(email)){
throw new DuplicateMemberException("이미 등록된 이메일입니다"); // 이미등록된 이메일일때 예외처리 해주어야함.
}
if(memberRepository.existsByPhoneNumber(phoneNumber)){
throw new DuplicateMemberException("이미 등록된 휴대폰 번호입니다.");
}
}
public List<Member> findAllMember() {
return memberRepository.findAll();
}
// 검색 기능 할 떄 필요 이름 한명 정학 때.
public Member findOne(String email) {
return memberRepository.findByEmail(email).orElseThrow(() -> new IllegalStateException("해당 이메일을 가진 회원이 존재하지 않습니다."));
}
public List<MemberDto> findAllMemberDto(){
List<Member> members = memberRepository.findAll();
return members.stream()
.map(member -> MemberDto.builder()
.email(member.getEmail())
.name(member.getName())
.password(member.getPassword())
.nickname(member.getNickName())
.phoneNumber(member.getPhoneNumber())
.registerDate(member.getRegisterDate())
.memberType(member.getMemberType())
.build())
.collect(Collectors.toList());
}
}
'토이 프로젝트 2' 카테고리의 다른 글
| 로그인 횟수 제한 구현 (0) | 2024.12.28 |
|---|---|
| 비밀번호 찾기 구현 (1) | 2024.12.27 |
| Interceptor(권한) 구현 (1) | 2024.12.26 |
| 커스텀 어노테이션 추가, ExceptionHandler 추가 (0) | 2024.12.25 |
| 회원가입시 비밀번호 암호화(bCrypto) (0) | 2024.12.24 |