[회원관리] 회원가입
2022. 12. 30. 21:25ㆍ프로젝트/라이어게임
728x90
SMALL
회원가입 API
기능 | Method | URL | Request | Response | 비고 |
회원가입 | POST | /lier/signup | { ”email”:””, ”password”:””, ”passwordConfirm”:””, ”nickname”:”” } |
data{ ”회원가입 성공 ” } |
- 회원가입 성공 시, nickname 뒤에 난수 키워드가 붙어서 저장됨 |
- post 형식
- Request 요청은 json형태의 DTO 객체를 이용해 요청하였고, 필요한 정보는 이메일, 비밀번호, 재확인용 비밀번호, 닉네임 이렇게 네가지이다.
- Response 응답 값은 json 형태의 data 에 "회원가입 성공" 이라는 성공 문구가 나오면 정상완료되었다는 뜻이다.
- 회원가입 성공을 하게되면 각 유저의 구별을 위해 닉네임 뒤에 난수값을 붙여서 생성되게 된다.
회원가입 시 저장될 Member Entity 부터 생성하도록 한다.
Member
package com.example.finalproject.domain;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.*;
import org.hibernate.Hibernate;
import org.springframework.security.crypto.password.PasswordEncoder;
import javax.persistence.*;
import java.util.List;
import java.util.Objects;
@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
public class Member extends Timestamped{
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Id
private Long memberId;
@Column(nullable = false)
private String email;
@Column(nullable = false)
private String password;
@Column(nullable = false)
private String nickname;
@Column(nullable = false)
private Long winNum;
@Column(nullable = false)
private Long winLIER;
@Column(nullable = false)
private Long winCITIZEN;
@Column(nullable = false)
private Long lossNum;
@Column(nullable = false)
private Long lossLIER;
@Column(nullable = false)
private Long lossCITIZEN;
@JsonIgnore
@OneToMany(mappedBy = "member",fetch = FetchType.LAZY,cascade = CascadeType.ALL, orphanRemoval = true)
private List<Post> posts;
@JsonIgnore
@OneToMany(mappedBy = "member",fetch = FetchType.LAZY,cascade = CascadeType.ALL, orphanRemoval = true)
private List<Comment> comments;
@JsonIgnore
@JoinColumn(name="gameroommember_id")
@ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private GameRoomMember gameRoomMember;
@JsonIgnore
@OneToOne(mappedBy = "member",cascade = CascadeType.ALL, orphanRemoval = true)
private MemberActive memberActive;
@JsonIgnore
@JoinColumn(name="memberreward_id")
@OneToOne
private MemberReward memberReward;
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) {
return false;
}
Member member = (Member) o;
return memberId != null && Objects.equals(memberId, member.memberId);
}
@Override
public int hashCode() {
return getClass().hashCode();
}
public boolean validatePassword(PasswordEncoder passwordEncoder, String password) {
return passwordEncoder.matches(password, this.password);
}
}
- 유저의 정보에 들어갈 주요 내용으로는
- memberId : 유저 고유 ID
- email : 유저 이메일
- password : 계정 비밀번호
- nickname : 닉네임
- winNum : 총 승리 전적 (초기값 0)
- winLIER : 라이어로써 승리한 전적 (초기값 0)
- winCITIZEN : 시민으로써 승리한 전적 (초기값 0)
- lossNum : 총 패배 전적 (초기값 0)
- lossLIER : 라이어로써 패배한 전적 (초기값 0)
- lossCITIZEN : 시민으로써 패배한 전적 (초기값 0)
- Post, Comment, GameRoomMember, MemberActive, MemberReward 는 연관관계를 맺은 DB들이다.
- MemberActive는 회원가입으로 계정이 새로 생성이 된다면 같이 생성이 되는 유저의 활동 이력을 저장하는 DB 이다.
MemberRepository
package com.example.finalproject.repository;
import com.example.finalproject.domain.Member;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface MemberRepository extends JpaRepository<Member, Long> {
Optional<Member> findByEmail(String email);
}
- Member 전용 Repository를 생성한다.
- QueryDSL만 사용하는 것이 아니라 왜 JPA를 추가로 사용하느냐?
- JPA가 단순 save()명령어로 저장을 쉽게 할 수 있기 때문이다. 이후 모든 entity에 저장을 하게 될 경우 JPA save()명령어를 사용하게 될것이다.
MemberActive
package com.example.finalproject.domain;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import javax.persistence.*;
import java.time.LocalDateTime;
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Builder
@Entity
public class MemberActive {
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Id
private Long memberactiveId;
@Column(nullable = false)
private Long createNum;
@Column(nullable = false)
private Long ownerNum;
@Column(nullable = false)
private Long enterNum;
@Column(nullable = false)
private Long exitNum;
@Column(nullable = false)
private Long gamestartNum;
@Column(nullable = false)
private Long gamereadyNum;
@Column(nullable = false)
private Long voteNum;
@Column(nullable = false)
private Long correctanswerNum;
@Column
private LocalDateTime starttime;
@Column
private LocalDateTime endplaytime;
@Column
private Long playhour;
@Column
private Long playminute;
@OneToOne(fetch = FetchType.LAZY)
private Member member;
}
- 유저 활동 이력으로써 들어갈 주요 내용은
- memberactiveId : 유저 활동이력 고유 ID
- createNum : 유저가 방을 만든 활동이력 (초기값 0)
- ownerNum : 유저가 방장이 된 활동이력 (초기값 0)
- enterNum : 유저가 방에 입장한 횟수 (초기값 0)
- exitNum : 유저가 방을 나간 횟수 (초기값 0)
- gamestartNum : 방장만이 게임시작을 할 수 있는데, 따라서 방장으로써 게임시작을 진행한 횟수 (초기값 0)
- gamereadyNum : 게임준비 한 횟수 (초기값 0)
- voteNum : 투표한 횟수 (초기값 0)
- correctanswerNum : 라이어로써 정답을 맞춘 횟수 (초기값 0)
- starttime : 유저가 게임을 플레이할 때마다 시작한 시간대를 저장
- endplaytime : 게임종료 시 게임이 종료된 시간대를 저장
- playhour : 유저가 플레이한 시간 (단위 - 시간)
- playMinute : 유저가 플레이한 시간 (단위 - 분)
- Member 한명 당 하나의 MemberActive가 생성이 되야하므로 연관관계 형식은 OneToOne 으로써 맺는다.
MemberActiveRepository
package com.example.finalproject.repository;
import com.example.finalproject.domain.MemberActive;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface MemberActiveRepository extends JpaRepository<MemberActive, Long> {
}
- MemberActive 전용 Repository를 생성한다. (저장용으로만 사용)
Timestamped
package com.example.finalproject.domain;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;
import java.time.LocalDateTime;
@Getter
@MappedSuperclass
@AllArgsConstructor(access = AccessLevel.PROTECTED)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@EntityListeners(AuditingEntityListener.class)
public abstract class Timestamped {
@JsonFormat(timezone = "Asia/Seoul")
@CreatedDate
private LocalDateTime createdAt;
@JsonFormat(timezone = "Asia/Seoul")
@LastModifiedDate
private LocalDateTime modifiedAt;
}
유저가 생성이 되면 언제 생성이 되었는지에 대한 시간을 알기 위해 Timestamped를 생성하여 자동으로 생성시간이 저장되게 해주었다.
- @EntityListeners(AuditingEntityListener.class) : 해당 클래스에 Auditing 기능을 포함시켜준다. Auditing 기능은 자동으로 반영되어 저장할 수 있게끔하는 기능이다.
- @MappedSuperclass : JPA Entity 클래스들이 해당 추상 클래스를 상속할 경우 createDate, modifiedDate를 컬럼으로 인식할 수 있게 한다.
- @CreatedDate : Entity가 생성되어 저장될 때 생성시간이 자동 저장된다.
- @LastModifiedDate : 조회한 Entity의 값을 변경할 때 수정시간이 자동 저장된다.
- @JsonFormat(timezone = "Asia/Seoul") : JSON 응답값의 형식을 지정할 때 사용한다. 날짜 형식을 아시아 서울 시간대로 맞춘다는 뜻이다.
Auditing 기능을 사용하여 자동으로 반영되게 하려면 추가적으로 application 실행 객체에서 @EnableJpaAuditing을 설정해주어야 한다.
application
package com.example.finalproject;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import javax.annotation.PostConstruct;
import java.util.TimeZone;
@EnableJpaAuditing
@SpringBootApplication
@EnableWebSocket
public class FinalprojectApplication {
//@PostConstruct는 Bean이 완전히 초기화 된 후,단 한번만 호출되는 메서드 이다.
//애플리케이션이 처음 구동될때 한번 실행된다
@PostConstruct
public void started() {
// timezone 세팅
TimeZone.setDefault(TimeZone.getTimeZone("Asia/Seoul"));
}
public static void main(String[] args) {
SpringApplication.run(FinalprojectApplication.class, args);
System.out.println("실행 확인~~~~~~~");
}
}
이로써 유저 정보가 저장될 entity 생성은 완료했다.
이제 본격적으로 회원가입에 대한 api를 구성해보도록 하자.
MemberController
package com.example.finalproject.controller;
import com.example.finalproject.configuration.SwaggerAnnotation;
import com.example.finalproject.controller.request.LoginRequestDto;
import com.example.finalproject.controller.request.MemberRequestDto;
import com.example.finalproject.exception.PrivateResponseBody;
import com.example.finalproject.service.MemberService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Slf4j
@RequiredArgsConstructor
@RequestMapping("/lier")
@Controller
public class MemberController {
private final MemberService memberService;
// 회원가입
@PostMapping("/signup")
public ResponseEntity<PrivateResponseBody> signup(
@RequestBody MemberRequestDto memberRequestDto){
// 회원가입 진행시 재확인용 비밀번호는 입력하지 않는지 확인
log.info("회원가입 - 이메일 : {}, 닉네임 : {}, 패스워드 : {}", memberRequestDto.getEmail(), memberRequestDto.getNickname(), memberRequestDto.getPassword());
return memberService.signup(memberRequestDto);
}
}
회원가입을 구현 MemberController이다.
앞으로 회원가입 뿐만이 아니라 로그인, 로그아웃과 같은 회원관리 api 들의 이벤트들을 관리할 컨트롤러 단이다.
- @Slf4j : 로그 형식으로 출력값을 확인하기 위한 어노테이션이다.
- @RequiredArgsConstructor : 초기화 되지않은 final 필드나, @NonNull 이 붙은 필드에 대해 생성자를 생성해준다. 주로 의존성 주입(Dependency Injection) 편의성을 위해서 사용되곤 한다.
- @RequestMapping : 컨트롤러에 있는 api들의 공통 호출 주소를 기입함으로써, 각 api 들이 호출될때 일일히 해당 공통 주소부분을 기입하지 않아도 되도록 해준다.
- @Controller : 해당 클래스가 컨트롤러라는 것을 명시하는 어노테이션이다. 이제 동작에 대한 이벤트가 발생하게 된다면 해당 컨트롤러로 넘어가서 동작을 관리하게 될 것이다.
- @PostMapping : post형식으로 기입한 url주소로 호출된다는 것을 뜻한다.
- @RequestBody : 단순 값으로 요청하는 것이 아니라 DTO와 같은 객체를 통해 객체에 존재하는 Body(변수값)으로 요청하고자하는 어노테이션이다.실질적으로 로직이 동작되는 Service 단에 DTO 객체를 넘겨준다.
- 반환값 타입은 ResponseEntity를 사용하였다. ResponseEntity는 제네릭을 사용함으로써 일일히 반환되어야하는 변수타입을 지정하지 않아도 된다.
MemberService
package com.example.finalproject.service;
import com.example.finalproject.controller.request.LoginRequestDto;
import com.example.finalproject.controller.request.MemberRequestDto;
import com.example.finalproject.controller.request.TokenDto;
import com.example.finalproject.domain.Member;
import com.example.finalproject.domain.MemberActive;
import com.example.finalproject.domain.MemberReward;
import com.example.finalproject.exception.PrivateResponseBody;
import com.example.finalproject.exception.StatusCode;
import com.example.finalproject.jwt.TokenProvider;
import com.example.finalproject.repository.MemberActiveRepository;
import com.example.finalproject.repository.MemberRepository;
import com.example.finalproject.repository.MemberRewardRepository;
import com.querydsl.jpa.impl.JPAQueryFactory;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Optional;
import static com.example.finalproject.domain.QMember.member;
import static com.example.finalproject.domain.QMemberActive.memberActive;
import static com.example.finalproject.domain.QMemberReward.memberReward;
@Slf4j
@RequiredArgsConstructor
@Service
public class MemberService {
private final TokenProvider tokenProvider;
private final PasswordEncoder passwordEncoder;
private final JPAQueryFactory jpaQueryFactory;
private final MemberRepository memberRepository;
private final MemberActiveRepository memberActiveRepository;
// 회원가입
public ResponseEntity<PrivateResponseBody> signup(MemberRequestDto memberRequestDto) {
// 아이디 중복 확인
if (null != isPresentMember(memberRequestDto.getEmail())) {
return new ResponseEntity<>(new PrivateResponseBody
(StatusCode.DUPLICATED_EMAIL, null), HttpStatus.BAD_REQUEST);
}
// 비밀번호 중복 확인
if (!memberRequestDto.getPassword().equals(memberRequestDto.getPasswordConfirm())) { // 비밀번호 encode 하기 이전에 비교
return new ResponseEntity<>(new PrivateResponseBody
(StatusCode.DUPLICATED_PASSWORD, null), HttpStatus.BAD_REQUEST);
}
// 닉네임 중복 확인
if (!(jpaQueryFactory
.selectFrom(member)
.where(member.nickname.eq(memberRequestDto.getNickname()))
.fetchOne() == null)) {
return new ResponseEntity<>(new PrivateResponseBody
(StatusCode.DUPLICATED_NICKNAME, null), HttpStatus.BAD_REQUEST);
}
// 회원 정보 저장
Member member = Member.builder()
.email(memberRequestDto.getEmail()) // 이메일
.password(passwordEncoder.encode(memberRequestDto.getPassword())) // 비밀번호 인코딩하여 저장
.nickname(memberRequestDto.getNickname() + "#" + (int) (Math.random() * 9999)) // 닉네임 + 고유 숫자값 부여
.winNum(0L) // 전체 승리 횟수
.lossNum(0L) // 전체 패배 횟수
.winCITIZEN(0L) // 시민으로써 승리한 횟수
.winLIER(0L) // 라이어로써 승리한 횟수
.lossCITIZEN(0L) // 시민으로써 패배한 횟수
.lossLIER(0L) // 라이어로써 패배한 횟수
.build();
memberRepository.save(member);
// 유저 활동 기록 초기화 (업적용)
MemberActive memberActive = MemberActive.builder()
.createNum(0L) // 방 생성 횟수
.ownerNum(0L) // 방장이 된 횟수
.enterNum(0L) // 방에 들어간 횟수
.exitNum(0L) // 방을 나간 횟수
.gamereadyNum(0L) // 게임준비 한 횟수
.gamestartNum(0L) // 게임시작 한 횟수
.voteNum(0L) // 투표한 횟수
.correctanswerNum(0L) // 정답을 맞춘 횟수
.starttime(null)
.endplaytime(null)
.playhour(0L)
.playminute(0L)
.member(member)
.build();
// 활동 기록 초기화 저장
memberActiveRepository.save(memberActive);
return new ResponseEntity<>(new PrivateResponseBody
(StatusCode.OK, "회원가입 성공"), HttpStatus.OK);
}
}
- isPresentMember 에 DTO로 전달받은 유저의 이메일을 넣어 이미 존재하는 계정이 있다면 중복된 유저로 판단하고 오류 처리를 한다.
- DTO로 전달받은 password와 passwordconfirm의 값이 동일하다면 정상, 동일하지 않다면 잘못된 정보입력으로 판단하여 오류 처리를 한다.
- 초기에 QueryDSL을 세팅을 하였고, JPAQueryFactory 의존성 주입하여 Query문을 구성하여 실행한다.
jpaQueryFactory
.selectFrom(member) // member 테이블에 대해서 조회를 한다.
.where(member.nickname.eq(memberRequestDto.getNickname())) // member 닉네임 컬럼 속성에서 DTO로 전달받은 닉네임값과 일치하는 데이터가 있는지에 대한 조건
.fetchOne() // fetchone은 하나의 데이터만을 조회한다.
- 위의 조건대로 이미 동일한 닉네임을 가지고 있는지 확인하여 중복된다면 오류 처리를 한다.
- 이메일 중복, 비밀번호 재확인, 닉네임 중복의 조건들을 무사히 통과하게 된다면 builder 로 Member에 유저 정보를 저장하고, 동시에 각 유저마다 가지고 있는 활동이력 정보(MemberActive)도 builder를 통해 생성하여 저장한다.
- 정상적으로 회원가입이 완료가 되었다면 "회원가입 성공" 문구 반환한다.
회원가입 성공!
다음 시간은 로그인 구현을 정리해보자
728x90
반응형
LIST
'프로젝트 > 라이어게임' 카테고리의 다른 글
[회원관리] 로그인 (0) | 2023.01.03 |
---|---|
프로젝트 세팅 (0) | 2022.12.30 |
[회원관리] Jwt를 활용한 회원관리 기능 세팅 (0) | 2022.12.28 |
서비스 아키텍쳐 (0) | 2022.12.15 |