[UMC-3rd & Springboot] rest API 서버 구현 - 회원정보 변경 및 조회 #2

조윤희·2023년 2월 19일
0

UMC_badjang_Server

목록 보기
3/4

👌필요한 annotation

Annotation
스프링 부트는 어노테이션을 다양하게 아는 것이 중요하다. SpringBoot의 시작점을 알리는 @SpringBootApplication 어노테이션 뿐만 아니라 스프링 부트 어노테이션 등의 키워드로 구글링 해서 스프링 부트에서 자주 사용되는 다양한 어노테이션을 이해하고 외워두자.

  • @ResponseBody
    -> return되는 자바 객체를 JSON으로 바꿔서 HTTP body에 담는 어노테이션.
    (JSON은 HTTP 통신 시, 데이터를 주고받을 때 많이 쓰이는 데이터 포맷.)
  • @Transactional
  • @GetMapping
    -> GET 방식의 요청을 매핑하기 위한 어노테이션
  • @PatchMapping
    -> PATCH 방식의 요청을 매핑하기 위한 어노테이션
  • @PathVariable
    -> RESTful(URL)에서 명시된 파라미터({})를 받는 어노테이션
  • @RequestBody

👌API 구현

model > GetUserRes

package com.example.demo.src.user.model;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;


@Getter // 해당 클래스에 대한 접근자 생성
@Setter // 해당 클래스에 대한 설정자 생성
@AllArgsConstructor // 해당 클래스의 모든 멤버 변수(userIdx, nickname, email, password)를 받는 생성자를 생성
/**
 * Res.java: From Server To Client
 * 하나 또는 복수개의 회원정보 조회 요청(Get Request)의 결과(Respone)를 보여주는 데이터의 형태
 *
 * GetUserRes는 클라이언트한테 response줄 때 DTO고
 * User 클래스는 스프링에서 사용하는 Objec이다.
 */
public class GetUserRes1 {
    //private int user_idx;
    private String user_email;
}

model > PatchUserReq

package com.example.demo.src.user.model;

import lombok.*;

@Getter // 해당 클래스에 대한 접근자 생성
@Setter // 해당 클래스에 대한 설정자 생성
@AllArgsConstructor // 해당 클래스의 모든 멤버 변수(userIdx, nickname)를 받는 생성자를 생성
@NoArgsConstructor(access = AccessLevel.PROTECTED)  // 해당 클래스의 파라미터가 없는 생성자를 생성, 접근제한자를 PROTECTED로 설정.
/**
 * Req.java: From Client To Server
 * 회원정보 수정 요청(Patch Request)을 하기 위해 서버에 전달할 데이터의 형태
 */
public class PatchUserReq {
    private String user_email;
    private String user_password;
}

model > User

package com.example.demo.src.user.model;

import lombok.*;

@Getter // 해당 클래스에 대한 접근자 생성
@Setter // 해당 클래스에 대한 설정자 생성
@AllArgsConstructor // 해당 클래스의 모든 멤버 변수(userIdx, nickname, email, password)를 받는 생성자를 생성
@NoArgsConstructor(access = AccessLevel.PROTECTED)
/**
 * 유저관련된 정보들을 담고 있는 클래스(유저 관련정보를 추출할 때 해당 클래스에서 Getter를 사용해서 가져온다.)
 * GetUserRes는 클라이언트한테 response줄 때 DTO고(DTO란 DB의 정보를 Service나 Controller등에 보낼때 사용하는 객체를 의미한다.)
 * User 클래스는 스프링에서 사용하는 Objec이다.(내부에서 사용하기 위한 객체라고 보면 된다.)
 */
public class User {
    private int user_idx;
    private String user_email;
    private String user_password;
    private String user_status;
}

UserController

아이디찾기(이메일 조회)api로 문자인증에서 사용되는 전화번호를 파라미터로 받는다.

 /**
     * 이메일조회 API
     * [GET] /users/:user_phone
     */
    // Path-variable
    @ResponseBody
    @GetMapping("/{user_phone}") // (GET) 127.0.0.1:9000/app/users/:userIdx
    public BaseResponse<GetUserRes1> getUser(@PathVariable("user_phone") String user_phone) {
        try {
            GetUserRes1 getUserRes = userProvider.getUser(user_phone);
            return new BaseResponse<>(getUserRes);
        } catch (BaseException exception) {
            return new BaseResponse<>((exception.getStatus()));
        }

    }

❓여기서 궁금했던 점

  1. 문자 인증 시 필요한 전화번호나 이메일에대한 형식적validation과 의미적validation은 문자인증(fcm)api에서 구현해줘야하나? 아님 회원정보 조회 및 변경api에서 구현해줘야하나?
    👉 회원정보api에서 구현하면 문자인증이 필요한 api마다 validation처리를 해줘야하기 때문에 나는 문자인증api에 구현하였다.
  2. 비밀번호 변경이 문자 인증 후에 이루어지는데, 그러면 비밀번호 변경api랑 문자인증api를 컨트롤러상에서 연결시켜줘여하나.?아님 그냥 각각따로 만들면 되나?
    👉 api는 따로따로 있다고 보면 된다.

회원가입 때와 마찬가지로, 비밀번호 형식에대한 validation이 필요하다.

 /**
     * 비밀번호변경 API
     * [PATCH] /users/:user_email
     */
    @ResponseBody
    @PatchMapping("/{user_email}")
    public BaseResponse<String> modifyUserPassword(@PathVariable("user_email") String user_email, @RequestBody User user) {
        /*if(!isRegexEmail(user_email)){
            return new BaseResponse<>(POST_USERS_INVALID_EMAIL);
        }*/
        if (user.getUser_password() == null) {
            return new BaseResponse<>(POST_USERS_EMPTY_PW);
        }
        if(!isRegexPw(user.getUser_password())){
            return new BaseResponse<>(POST_USERS_INVALID_PW);
        }

        try {
            PatchUserReq patchUserReq = new PatchUserReq(user_email, user.getUser_password());
            userService.modifyUserPassword(patchUserReq);

            String result = "회원정보가 수정되었습니다.";
            return new BaseResponse<>(result);
        } catch (BaseException exception) {
            return new BaseResponse<>((exception.getStatus()));
        }
    }

Provider와 Service

비즈니스 로직을 다루는 곳 (DB 접근[CRUD], DB에서 받아온 것 형식화)
-> Request의 의미적 Validation 처리 (DB를 거쳐야 검사할 수 있는)
Service와 Provider는 비즈니스 로직을 다루는 곳이다. CRUD 중 R(Read) 에 해당하는 코드가 긴 경우가 많기 때문에 R(Read) 만 따로 분리해 Service는 CUD(Create, Update, Delete) 를, Provider는 R(Read) 를 다루도록 한다. 유지 보수가 용이해진다.
1. Provider
R(Read) 와 관련된 곳이다. DB에서 select 해서 얻어온 값을 가공해서 뱉어준다.
2. Service
CUD(Create, Update, Delete) 와 관련된 곳이다. CUD에서 R이 필요한 경우가 있는데, 그럴 때는 Provider에 구성되어 있는 것을 Service에서 사용하면 된다.

UserProvider

// 해당 user_phone를 갖는 User의 정보 **조회**
    public GetUserRes1 getUser(String user_phone) throws BaseException {
        try {
            GetUserRes1 getUserRes = userDao.getUser(user_phone);
            return getUserRes;
        } catch (Exception exception) {
            throw new BaseException(DATABASE_ERROR);
        }
    }

db상에 회원email이 존재하는지 확인하는 메소드

public int checkEmail(String user_email) throws BaseException {
       try{
           return userDao.checkEmail(user_email);
       } catch (Exception exception) {
           throw new BaseException(DATABASE_ERROR);
       }
    }

UserService

// 회원정보 **수정(Patch)**
    public void modifyUserPassword(PatchUserReq patchUserReq) throws BaseException {
        if(userProvider.checkEmail(patchUserReq.getUser_email()) == 0) {
            throw new BaseException(NON_EXISTENT_EMAIL);
        }
        String pwd;
        try {
            pwd = new SHA256().encrypt(patchUserReq.getUser_password());
            patchUserReq.setUser_password(pwd);
            int result = userDao.modifyUserPassword(patchUserReq); // 해당 과정이 무사히 수행되면 True(1), 그렇지 않으면 False(0)입니다.
            if (result == 0) { // result값이 0이면 과정이 실패한 것이므로 에러 메서지를 보냅니다.
                throw new BaseException(MODIFY_FAIL_USERPASSWORD);
            }
        } catch (Exception exception) { // DB에 이상이 있는 경우 에러 메시지를 보냅니다.
            throw new BaseException(DATABASE_ERROR);
        }
    }

UserDao

public int checkEmail(String user_email) {
        String checkEmailQuery = "select exists(select user_email from User where user_email = ?)";
        String checkEmailParams = user_email;
        return this.jdbcTemplate.queryForObject(checkEmailQuery,
                int.class,
                checkEmailParams);
    }

select exists(select 구문)

EXISTS 절은 서브쿼리(select 구문)가 반환하는 결과값이 이미 존재하는지 조사하여, 있으면 TRUE를 반환한다.

// 해당 user_phone을 갖는 유저조회
    public GetUserRes1 getUser(String user_phone) {
        String getUserQuery = "select user_email from User where user_phone = ?"; // 해당 user_id를 만족하는 유저를 조회하는 쿼리문
        String getUserParams = user_phone;
        return this.jdbcTemplate.queryForObject(getUserQuery,
                (rs, rowNum) -> new GetUserRes1(
                        rs.getString("user_email")),// RowMapper(위의 링크 참조): 원하는 결과값 형태로 받기
                getUserParams); // 한 개의 회원정보를 얻기 위한 jdbcTemplate 함수(Query, 객체 매핑 정보, Params)의 결과 반환
    }

update

update 쿼리문을 사용하여 테이블의 데이터를 수정한다.
UPDATE [테이블] SET [열] = '변경할값' WHERE [조건]

// 회원정보 변경
    public int modifyUserPassword(PatchUserReq patchUserReq) {
        String modifyUserNameQuery = "update User set user_password = ? where user_email = ? "; // 해당 user_email를 만족하는 User를 해당 nickname으로 변경한다.
        Object[] modifyUserNameParams = new Object[]{patchUserReq.getUser_password(), patchUserReq.getUser_email()}; // 주입될 값들(password, user_idx) 순

        return this.jdbcTemplate.update(modifyUserNameQuery, modifyUserNameParams); // 대응시켜 매핑시켜 쿼리 요청(생성했으면 1, 실패했으면 0)
    }

👌API 명세서 & 테스트

/users/:user_phone

/users/:user_email

0개의 댓글