쇼핑몰 만들기 프로젝트 - 회원 가입시 중복 값이 이미 존재하는지 확인하는 작업

yeom yaloo·2023년 6월 29일
0

쇼핑몰

목록 보기
10/19
post-thumbnail

msa 구조에서의 회원 가입을 알아보자

[회원 가입]

[중복되면 안 되는 요소들 확인 작업]

아래의 필드를 기준으로 중복되면 안 되는 요소를 처리하였습니다.

  • 로그인 아이디
  • 닉네임
  • 휴대전화 번호
  • 이메일 주소

또한 현재 예시들은 msa 환경에서의 확인 작업으로 진행됨을 알립니다.

1. API Server

1.1 Repository

import com.querydsl.core.types.Projections;
import com.querydsl.core.types.dsl.StringExpression;
import com.querydsl.jpa.impl.JPAQueryFactory;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;

import java.time.LocalDate;
import java.util.List;
import java.util.Optional;
import java.util.OptionalInt;

@Repository
@RequiredArgsConstructor
public class QuerydslMemberRepositoryImpl implements QuerydslMemberRepository {

    private final JPAQueryFactory queryFactory;

    /**
     * {@inheritDoc}
     * */
    @Override
    public boolean existMemberByNickname(String nickname) {
        QMember member = QMember.member;

        return Optional.ofNullable(queryFactory
                .selectFrom(member).where(member.nickname.eq(nickname)
                        .and(member.isSoftDelete.isFalse()))
                .fetchFirst()).isPresent();    }

    /**
     * {@inheritDoc}
     * */
    @Override
    public boolean existMemberByPhoneNumber(String phoneNumber) {
        QMember member = QMember.member;

        return Optional.ofNullable(queryFactory
                .selectFrom(member).where(member.phoneNumber.eq(phoneNumber)
                        .and(member.isSoftDelete.isFalse()))
                .fetchFirst()).isPresent();    
    }

    /**
     * {@inheritDoc}
     * */
    @Override
    public boolean existMemberByEmail(String email) {
        QMember member = QMember.member;

        return Optional.ofNullable(queryFactory
                .selectFrom(member).where(member.emailAddress.eq(email)
                        .and(member.isSoftDelete.isFalse()))
                .fetchFirst()).isPresent();
    }

    /**
     * {@inheritDoc}
     * */
    @Override
    public boolean existMemberByLoginId(String loginId) {
        QMember member = QMember.member;

        return Optional.ofNullable(queryFactory
                .selectFrom(member).where(member.id.eq(loginId)
                        .and(member.isSoftDelete.isFalse())).fetchFirst()).isPresent();
    }
}
  • querydsl을 사용해서 해당 로그인 아이디, 닉네임, 이메일 주소, 휴대전화 번호에 해당하는 회원이 이미 존재하는지를 확인하는 작업을 진행한다.
  • 이때 삭제 회원이 아닌 경우만 찾게 했다.

1.2 Service

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.elasticsearch.annotations.DateFormat;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;

@Slf4j
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class QueryMemberServiceImpl implements QueryMemberService {

    private final QuerydslMemberRepository querydslMemberRepository;
    private final QuerydslMemberRoleRepository querydslMemberRoleRepository;

        
    @Override
    public boolean existMemberByNickname(String nickname) {
        boolean result = querydslMemberRepository.existMemberByNickname(nickname);

        return result;
    }

    @Override
    public boolean existMemberByPhoneNumber(String phoneNumber) {
        boolean result = querydslMemberRepository.existMemberByPhoneNumber(phoneNumber);

        return result;    
    }

    @Override
    public boolean existMemberByEmail(String email) {
        boolean result = querydslMemberRepository.existMemberByEmail(email);

        return result;
    }

    @Override
    public boolean existMemberByLoginId(String loginId) {
        boolean result = querydslMemberRepository.existMemberByLoginId(loginId);

        return result;
    }
}
  • 해당 값들의 중복 여부를 boolean값으로 돌려준다.
  • 이미 해당하는 값의 회원이 있다면 true를 없다면 false를 돌려준다.

1.3 Rest Controller와 response에 사용할 dto 객체

[controller]

import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.apache.coyote.Response;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RequestMapping(value = "/api/service/members")
@RestController
@RequiredArgsConstructor
public class QueryMemberRestController {


    private final QueryMemberService queryMemberService;

    /**
     * 이메일로 중복 여부 확인
     * @param email 회원 이메일
     * @return 중복여부
     * */
    @GetMapping("/checkEmail/{email}")
    @ResponseStatus(HttpStatus.OK)
    public ResponseDto<MemberDuplicateDto> existMemberByEmail(@PathVariable String email){
        MemberDuplicateDto response = new MemberDuplicateDto(queryMemberService.existMemberByEmail(email));

        return ResponseDto.<MemberDuplicateDto>builder()
                .status(HttpStatus.OK)
                .success(true)
                .data(response)
                .build();


    }
    @GetMapping("/checkNickname/{nickname}")
    @ResponseStatus(HttpStatus.OK)
    public ResponseDto<MemberDuplicateDto> existMemberBy(@PathVariable String nickname){
        MemberDuplicateDto response =new MemberDuplicateDto(queryMemberService.existMemberByNickname(nickname));

        return ResponseDto.<MemberDuplicateDto>builder()
                .status(HttpStatus.OK)
                .success(true)
                .data(response)
                .build();

    }
    @GetMapping("/checkPhone/{phone}")
    @ResponseStatus(HttpStatus.OK)
    public ResponseDto<MemberDuplicateDto> existMemberByPhoneNumber(@PathVariable String phone){
        MemberDuplicateDto response =new MemberDuplicateDto(queryMemberService.existMemberByPhoneNumber(phone));

        return ResponseDto.<MemberDuplicateDto>builder()
                .status(HttpStatus.OK)
                .success(true)
                .data(response)
                .build();

    }

    @GetMapping("/checkLoginId/{loginId}")
    @ResponseStatus(HttpStatus.OK)
    public ResponseDto<MemberDuplicateDto> existMemberByLoginId(@PathVariable String loginId){

        MemberDuplicateDto data = new MemberDuplicateDto(queryMemberService.existMemberByLoginId(loginId));

        return ResponseDto.<MemberDuplicateDto>builder()
                .status(HttpStatus.OK)
                .success(true)
                .data(data)
                .build();
    }
}

[response dto]

public class MemberDuplicateDto {
    private boolean result;
    public MemberDuplicateDto(boolean result){
        this.result = result;
    }
}
  • 존재하는지 여부를 확인하는 결과값을 가지고 해당 결과값을 dto로 만들어서 통신하는 곳에 response로 넘겨준다.

2. Front Server

2.1 웹 API 호출 - 다른 서버에서 비지니스 객체를 가져오는 서비스 레이어 (service)

import com.yaloostore.front.member.service.inter.QueryMemberService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;

import java.net.URI;
import java.util.Objects;



@Slf4j
@RequiredArgsConstructor
@Service
public class QueryMemberServiceImpl implements QueryMemberService {

    private final RestTemplate restTemplate;
    private final GatewayConfig config;
    private final ObjectMapper objectMapper;

    private final static String PREFIX_CHECK_PATH ="/api/service/members/check";

    @Override
    public MemberDuplicateDto checkNickname(String nickname) {
        log.info("nickname : {}", nickname);

        HttpEntity entity = getEntity();


        URI uri = UriComponentsBuilder.fromUriString(config.getShopUrl())
                .path(PREFIX_CHECK_PATH+"Nickname/{nickname}")
                .encode()
                .build()
                .expand(nickname)
                .toUri();
        ResponseEntity<ResponseDto<MemberDuplicateDto>> response = restTemplate.exchange(uri, HttpMethod.GET, entity, new ParameterizedTypeReference<ResponseDto<MemberDuplicateDto>>() {
        });

        return response.getBody().getData();
    }

    @Override
    public MemberDuplicateDto checkPhoneNumber(String phone) {
        log.info("phone : {}", phone);

        HttpEntity entity = getEntity();


        URI uri = UriComponentsBuilder.fromUriString(config.getShopUrl())
                .path(PREFIX_CHECK_PATH+"Phone/{phone}")
                .encode()
                .build()
                .expand(phone)
                .toUri();
        ResponseEntity<ResponseDto<MemberDuplicateDto>> response = restTemplate.exchange(uri, HttpMethod.GET, entity, new ParameterizedTypeReference<ResponseDto<MemberDuplicateDto>>() {
        });

        return response.getBody().getData();
    }

    @Override
    public MemberDuplicateDto checkEmail(String email) {
        log.info("email : {}", email);

        HttpEntity entity = getEntity();


        URI uri = UriComponentsBuilder.fromUriString(config.getShopUrl())
                .path(PREFIX_CHECK_PATH+"Email/{email}")
                .encode()
                .build()
                .expand(email)
                .toUri();
        ResponseEntity<ResponseDto<MemberDuplicateDto>> response = restTemplate.exchange(uri, HttpMethod.GET, entity, new ParameterizedTypeReference<ResponseDto<MemberDuplicateDto>>() {
        });

        return response.getBody().getData();
    }

    @Override
    public MemberDuplicateDto checkLoginId(String loginId) {
        log.info("loginId : {}", loginId);

        HttpEntity entity = getEntity();

        URI uri = UriComponentsBuilder.fromUriString(config.getShopUrl())
                .path(PREFIX_CHECK_PATH+"LoginId/{loginId}")
                .encode()
                .build()
                .expand(loginId)
                .toUri();
        ResponseEntity<ResponseDto<MemberDuplicateDto>> response = restTemplate.exchange(uri, HttpMethod.GET, entity, new ParameterizedTypeReference<ResponseDto<MemberDuplicateDto>>() {
        });

        return response.getBody().getData();

    }

    private static HttpEntity getEntity() {
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        HttpEntity entity = new HttpEntity<>(headers);
        return entity;
    }
}
  • front server의 service layer로 DB에서 해당 비지니스 객체를 가져오는 형식이 아닌 API 서버와 연결된 DB에서 해당 비지니스 객체를 가져오는 형식으로 다른 서버에서 비지니스 객체를 가져오는 작업이다.
  • 해당 작업은 RestTemplate을 사용하여 요청하고 응답 받은 비지니스 객체를 돌려주는 형식이다.

2.2 RestController

import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


/**
 * 회원정보 수정, 회원가입 시 중복되면 안 되는 필드 확인 작업을 진행합니다.
 * */
@RestController
@RequiredArgsConstructor
public class MemberRestController {


    private final QueryMemberService queryMemberService;

    @GetMapping("/checkNickname/{nickname}")
    public MemberDuplicateDto checkNicknameDuplicate(@PathVariable String nickname){
        return queryMemberService.checkNickname(nickname);
    }
    @GetMapping("/checkEmail/{email}")
    public MemberDuplicateDto checkEmailDuplicate(@PathVariable String email){
        return queryMemberService.checkEmail(email);
    }
    @GetMapping("/checkPhone/{phone}")
    public MemberDuplicateDto checkPhoneDuplicate(@PathVariable String phone){
        return queryMemberService.checkPhoneNumber(phone);
    }

    @GetMapping("/checkLoginId/{loginId}")
    public MemberDuplicateDto checkLoginIdDuplicate(@PathVariable String loginId){
        return queryMemberService.checkLoginId(loginId);
    }
}
  • 다른 서버에서 비지니스 객체를 받아오는 서비스를 이용해서 rest controller를 작성해주고 우리는 이를 front server에서 사용하기 위해서 다시 javaScript를 사용해서 진행할 수 있게 한다.

2.3 sign up Form

[form]

<div class="mb-3 mb-4">
	<label for="email" class="form-label text-dark">Email </label>
	<div class="row">
        <div class="col-9">
        <input type="email" id="email" th:name="emailAddress" class="form-control border-1" placeholder="이메일 주소를 입력해주세요." required="">
        </div>
        <div class="col-3 d-flex justify-content-around">
        <button class="btn btn-light" type="button" id="checkEmailBth" onclick="checkEmail()">중복 확인</button>
        </div>
	</div>
</div>
  • 일부만 가져온 html로 이메일 확인 작업을 위해서 버튼에 작성한 자바스크립트 함수를 넣어준다.

[javaScript]

function checkPhoneNumber(){
    let inputPhoneNumber = document.getElementById("phoneNumber");
    let checkPhoneNumberBtn = document.getElementById("checkPhoneNumberBtn");
    let phoneNumberValue = inputPhoneNumber.value;
    const trimmedPhoneNumber = phoneNumberValue.replace(/-/g, '');

    console.log(phoneNumberValue);
    console.log(trimmedPhoneNumber);

    const url = `/checkPhone/${phoneNumberValue}`;

    let phoneRegex = /^01([0|1])\d{4}\d{4}$/;
    let emptyRegex = /\s/g;
    if(phoneRegex.test(trimmedPhoneNumber) && ! emptyRegex.test(trimmedPhoneNumber)){

        fetch(url, {
            Accept: "application/json",
            method:"GET"
        }).then((response) => {
            return response.json()
        }).then(data => {
                if(!data.result){
                    //사용 가능한 경우 input창은 고치지 못하게 바꾸고 버튼도 누르지 못하게 바꾼다,.
                    alert('사용 가능한 휴대전화 번호입니다.')
                    inputPhoneNumber.readOnly=true;
                    checkPhoneNumberBtn.disabled = true;
                }else{
                    alert('이미 사용 중인 휴대전화 번호입니다.')
            }
        });
    } else{
        alert('휴대전화 번호 형식에 맞게 작성해주세요.')
    }

}
  • javaScript의 fetch()로 API를 사용한다.
  • 호출한 api의 response 값을 돌려주고 그 값을 data로 사용한다.
  • 해당 api 호출의 json 결과는 아래와 같고 이를 data.result해서 사용하면 된다.

{
	result: true
}

[결과 화면]

회원가입 페이지

회원 가입시 표현식에 위배되지 않는다면 사용가능하다고 뜸

회원 가입 시 표현식에 위배되는 경우라면 사용불가하다고 뜸

profile
즐겁고 괴로운 개발😎

0개의 댓글