오티스의개발일기

[SPRING BOOT] 리엑트 네이티브 인스타그램 클론 코딩 (10) 백엔드 회원가입 api 만들기 -git 첨부- 본문

개발/spring boot

[SPRING BOOT] 리엑트 네이티브 인스타그램 클론 코딩 (10) 백엔드 회원가입 api 만들기 -git 첨부-

안되면 될때까지.. 2022. 12. 30. 23:02
728x90

 


< 이전글

2022.12.30 - [개발/react-native] - [REACT NATIVE] 리엑트 네이티브 인스타그램 클론 코딩 (9) 구글 로그인후 데이터베이스에 저장하기 + 예외 처리를 통한 비동기 처리 -git 참조-

 

[REACT NATIVE] 리엑트 네이티브 인스타그램 클론 코딩 (9) 구글 로그인후 데이터베이스에 저장하기 +

오늘은 드디어 백엔드와 프론트엔드를 연결 하는날이다.... 여기까지 오느라 고생 내가 제일 많았다 ㅎㅎ 오늘은 구글로 로그인을 시도후 구글로 받은 정보를 실제 데이터베이스에 저장하고 일

otis.tistory.com

 



다음글 > 

2022.12.31 - [개발/spring boot] - [SPRING BOOT] 리엑트 네이티브 인스타그램 클론 코딩 (11) JWT 를 사용한 회원정보 상태관리 구현 -git참조-

 

[SPRING BOOT] 리엑트 네이티브 인스타그램 클론 코딩 (11) JWT 를 사용한 회원정보 상태관리 구현 -git

< 이전글 2022.12.30 - [개발/spring boot] - [SPRING BOOT] 리엑트 네이티브 인스타그램 클론 코딩 (10) 백엔드 회원가입 api 만들기 -git 첨부- [SPRING BOOT] 리엑트 네이티브 인스타그램 클론 코딩 (10) 백엔드 회

otis.tistory.com


 

 

오늘은 저번시간 프론트엔드에서 개발했던 sns로 회원가입 혹은 로그인 기능 백단 api 를 만들것이다.

 

만들 api 는 1가지이다.

 

1. sns 를 통해 들어오는 가입

 

일반 회원가입과 sns로그인(가입)은 기능은 거의 동일하다

하지만 sns로그인 에서는 여러가지 상황을 처리해야하는 비지니스 로직들이 있다.

가령 예로들면

1.  sns 로 가입한적이 없는사람

2. 같은 이메일을 가진 사람을찾고 찾은사람이 일반회원 가입한사람

3. 다른 sns 로 이미 가입한이력이 있는사람.

 

1번같은 경우는 자동으로 가입이되도록 할것이고

2번은 클라이언트단에 일반회원이니 연동하시겠습니까? 라는 에러를 뿝을것이다.

그리고 확인을 누르면 provider 와 providerid 가 db에 저장되 연동을 시킬것이다.

3번은 그냥 마지막으로 로그인한 사람의 sns계정에 대한 정보를 업데이트 시킬것이다.

대부분 google로 이미 가입한 계정이 있습니다 등등 이런식으로 로직 처리를 하는데

1인 1계정을 좀더 확실하게 만드려면 이방법이 더 편하기도하면서 쉽다.

 

 

오늘 우리가 수정 혹은 생성할 파일을 나열해보겠다.

 

1. UserContorller.java

2. ResponseMap.java

3. UserService.java

RequestUserRegisterDto.java <-- 그전시간에 만들어서 따로 다루지는 않겠음

 

 

 

시작하기전에 일반 회원가입 api에 대한 포스팅을 보려면 아래 포스팅을 확인하면 된다.


2022.12.29 - [개발/spring boot] - [SPRING BOOT + REACT-NATIVE] (8) jpaQueryFactory 와 queryDsl 를 사용하여 이메일 중복확인 기능 만들기 -git 첨부 -

 

[SPRING BOOT + REACT-NATIVE] (8) jpaQueryFactory 와 queryDsl 를 사용하여 이메일 중복확인 기능 만들기 -git 첨

< 이전글 2022.12.29 - [개발/spring boot] - [SPRING BOOT + REACT-NATIVE] jpaRepository 를 사용하여 유저 데이터베이스에 저장하기 [SPRING BOOT + REACT-NATIVE] jpaRepository 를 사용하여 유저 데이터베이스에 저장하기 오

otis.tistory.com


 

 

자그러면 시작해보겠다.

 

아그리고 그전 userRepositorySupport.java 에 있는 

userEmailDoubleCheck   -->  findbyEmail

라고 함수명을 바꿔놨다.

참고 바란다.

 

 

 

 

#1. UserController.java

 

전체코드

package com.example.backend.controller;

import com.example.backend.common.exception.CustomApiException;
import com.example.backend.dto.CMRespDto;
import com.example.backend.dto.ResponseMap;
import com.example.backend.dto.user.request.RequestUserEmailDoubleCheckDto;
import com.example.backend.dto.user.request.RequestUserRegisterDto;
import com.example.backend.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import javax.validation.constraints.Email;

@RestController
@RequiredArgsConstructor
@CrossOrigin(origins = {"*"})
@RequestMapping("/api/user")
public class UserController {

    private final UserService userService;

    /**
     * 일반 회원가입
     * @param requestUserRegisterDto
     * @return
     */
    @PostMapping("/register")
    public CMRespDto<?> register (@Valid @RequestBody RequestUserRegisterDto requestUserRegisterDto) {
        return new CMRespDto<>(200, "회원가입이 완료되었습니다.", userService.register(requestUserRegisterDto));
    }

    /**
     * 아이디 중복체크
     * @param requestUserRegisterDto
     * @return
     */
    @PostMapping("/userEmailDoubleCheck")
    public CMRespDto<?> userEmailDoubleCheck (@Valid @RequestBody RequestUserEmailDoubleCheckDto requestUserRegisterDto) {
        return new CMRespDto<>(200, "사용 가능한 이메일 입니다.", userService.userEmailDoubleCheck(requestUserRegisterDto));
    }

    /**
     * 소셜 로그인
     * @param requestUserRegisterDto
     * @return
     */
    @PostMapping("/snsLogin")
    public CMRespDto<?> snsLogin (@Valid @RequestBody RequestUserRegisterDto requestUserRegisterDto) {
        ResponseMap responseMap = userService.snsLogin(requestUserRegisterDto);
        return new CMRespDto<>(responseMap.getHttpStatus().value(), responseMap.getMessage(), null);
    }

    /**
     * 소셜 로그인 연동
     * @param requestUserRegisterDto
     * @return
     */
    @PostMapping("/updateProvider")
    public CMRespDto<?> updateProvider (@Valid @RequestBody RequestUserRegisterDto requestUserRegisterDto) {
        ResponseMap responseMap = userService.updateProvider(requestUserRegisterDto);
        return new CMRespDto<>(responseMap.getHttpStatus().value(), responseMap.getMessage(), null);
    }
}

 

 

 

주석에 어느정도 처리가 되어있어서 쭉 읽어보면 이해가 갈것이다.

나머지 상황들은 단순 요청과 반환이지만

일반회원인상태에서 연동하는 상황은 두번 요청이 들어가기때문에

 

이것에대해 흐름을 설명해주겠다.

 

1. 구글로 로그인시도

2. provider가 없는걸 봐서 일반회원 

3. 일반회원이니 연동할거냐는 메시지를 클라이언트에 전달

4. 클라이언트가 확인 혹은 연동을 누르면  http://localhost:8080/api/user/updateProvider 에 api 요청

5. provider와 providerId 가 정상적으로 들어오고 다시 클라이언트에 돌아가서

6. 정상적으로 5번이 실행된게 확인되면 http://localhost:8080/api/user/snsLogin api를 한번더 요청

 

조금 복잡하긴한데 이런식으로 이루어져있다.

 

알다시피 전 포스팅에 프론트도 같이 개발해놔서

 

이곳에 코드만 올려 두도록 하겠다.

 

 

 

#2. UserService.java

 

전체코드

package com.example.backend.service;

import com.example.backend.common.exception.CustomApiException;
import com.example.backend.domain.User;
import com.example.backend.dto.DataMap;
import com.example.backend.dto.ResponseMap;
import com.example.backend.dto.user.request.RequestUserEmailDoubleCheckDto;
import com.example.backend.dto.user.request.RequestUserRegisterDto;
import com.example.backend.repository.user.UserRepository;
import com.example.backend.repository.user.UserRepositorySupport;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class UserService {

    private final UserRepository userRepository;
    private final UserRepositorySupport userRepositorySupport;


    /**
     * 일반 회원가입
     * @param requestUserRegisterDto
     * @return
     */
    @Transactional(readOnly = false)
    public boolean register (RequestUserRegisterDto requestUserRegisterDto) {
        User user = requestUserRegisterDto.toEntity();
        User newUser = userRepository.save(user);
        if (newUser != null) {
            return true;
        } else {
            throw new CustomApiException("회원가입에 실패하였습니다.\n고객 센터에 문의해주세요.", HttpStatus.BAD_REQUEST);
        }
    }

    /**
     * 소셜 로그인
     * @param requestUserRegisterDto
     * @return
     */
    @Transactional(readOnly = false) // 로그인뿐만 아니라 회원가입일수도 있기때문 false
    public ResponseMap snsLogin (RequestUserRegisterDto requestUserRegisterDto) {
        ResponseMap responseMap = new ResponseMap();
        User user = userRepositorySupport.findByEmail(requestUserRegisterDto.getEmail());
        if (user == null) { // 같은 이메일로 가입한 회원이 없다면 (= sns로 첫 로그인 이라는 뜻)
            user = userRepository.save(requestUserRegisterDto.toEntity()); // 가입 진행
            responseMap.setHttpStatus(HttpStatus.OK);
            responseMap.setMessage("회원가입이 완료되었습니다");
            return responseMap;
        } else {
            if (user.getProvider().isBlank()) { // sns 로 가입한 이력이 없다면
                throw new CustomApiException("일반 이메일회원입니다.\n연동하시겠습니까?.", HttpStatus.SEE_OTHER); // 일반회원으로 가입한 경로가 존재함
            } else {
                user.setProvider(requestUserRegisterDto.getProvider()); // sns의 가입경로를 바꿔준다
                user.setProviderId(requestUserRegisterDto.getProviderId()); // sns의 고유 번호를 바꿔준다.

                responseMap.setHttpStatus(HttpStatus.OK);
                responseMap.setMessage("로그인 완료");
                return responseMap;
            }
        }
    }

    /**
     * 소셜 로그인 연동
     * @param requestUserRegisterDto
     * @return
     */
    @Transactional(readOnly = false) // 로그인뿐만 아니라 회원가입일수도 있기때문 false
    public ResponseMap updateProvider (RequestUserRegisterDto requestUserRegisterDto) {
        User user = userRepositorySupport.findByEmail(requestUserRegisterDto.getEmail());
        user.setProvider(requestUserRegisterDto.getProvider());
        user.setProviderId(requestUserRegisterDto.getProviderId());
        ResponseMap responseMap = new ResponseMap();
        responseMap.setHttpStatus(HttpStatus.OK);
        responseMap.setMessage("연동되었습니다.");
        return responseMap;
    }

    /**
     * 아이디 중복체크
     * @param requestUserRegisterDto
     * @return
     */
    @Transactional(readOnly = true)
    public Boolean userEmailDoubleCheck (RequestUserEmailDoubleCheckDto requestUserRegisterDto) {
        User user = userRepositorySupport.findByEmail(requestUserRegisterDto.getEmail());
        if (user != null) {
            throw new CustomApiException("이미 사용중인 아이디 입니다.", HttpStatus.BAD_REQUEST);
        } else {
            return true;
        }
    }
}

 

 

아 그래도 service 단에서 할말이 좀 있다.

 

원래는 CustomApiException 을 통해서 모든 api 에 대한 response 를 담당하려했었다

하지만 정말 중요한 transactional 이 문제였다

아무리 우리가 예외를 200으로 터트려 클라이언트에 내보내도

트랜잭션은 이것또한 오류라 생각하여 방금까지 저장했던 데이터들을 원상복구해주는

rollback 을 실행하는 것이다.

너무 고맙고 당연하지만 처음엔 이해가 안가서 1시간동안 별짓을 다해본것같다.

아니... 로그도 잘나오고 다 잘되는데 mysql만가면 변경이 안되지...?

ㅋㅋ

뭐어쨌든 그렇기 때문에 아래 보여줄 ResponseMap 를 만들었다.

이제 컨트롤러에서는 항상 ResponseMap 를 service에서 받아 하나하나 뽑아서 적어줄것이다.

 

 

#3. ResponseMap.java

 

전체코드

package com.example.backend.dto;

import lombok.Data;
import org.springframework.http.HttpStatus;

@Data
public class ResponseMap {

    private String message;

    private HttpStatus httpStatus;

    private Class<?> data;
}

 

 

 

이것으로 api 만들기는마치도록 하겠다

결과를 보고싶다면 이전 포스팅 보면 확인할수 있다.

 

아래에 한번더 올려놓을테니 확인바란다.

2022.12.30 - [개발/react-native] - [REACT NATIVE] 리엑트 네이티브 인스타그램 클론 코딩 (9) 구글 로그인후 데이터베이스에 저장하기 + 예외 처리를 통한 비동기 처리 -git 참조-

 

[REACT NATIVE] 리엑트 네이티브 인스타그램 클론 코딩 (9) 구글 로그인후 데이터베이스에 저장하기 +

오늘은 드디어 백엔드와 프론트엔드를 연결 하는날이다.... 여기까지 오느라 고생 내가 제일 많았다 ㅎㅎ 오늘은 구글로 로그인을 시도후 구글로 받은 정보를 실제 데이터베이스에 저장하고 일

otis.tistory.com

 

 

 

코드는 깃에 올려놨다

https://github.com/1domybest/react-native-ig-clone.git

 

GitHub - 1domybest/react-native-ig-clone

Contribute to 1domybest/react-native-ig-clone development by creating an account on GitHub.

github.com

 

 

다음 포스팅은 jwt 를 활용한 토큰관리 설정에대해 알아볼것이다.

 

 


다음글 >

2022.12.31 - [개발/spring boot] - [SPRING BOOT] 리엑트 네이티브 인스타그램 클론 코딩 (11) JWT 를 사용한 회원정보 상태관리 구현 -git참조-

 

[SPRING BOOT] 리엑트 네이티브 인스타그램 클론 코딩 (11) JWT 를 사용한 회원정보 상태관리 구현 -git

< 이전글 2022.12.30 - [개발/spring boot] - [SPRING BOOT] 리엑트 네이티브 인스타그램 클론 코딩 (10) 백엔드 회원가입 api 만들기 -git 첨부- [SPRING BOOT] 리엑트 네이티브 인스타그램 클론 코딩 (10) 백엔드 회

otis.tistory.com


 

728x90
Comments