회원가입 기능 만들기

서지우·2023년 8월 11일

엔티티를 만들 때 오류가 있었다..😥
SaleEntity에서 ManyToOne이라고 해야하는데 ManyToAny라고 했드라..그래서 수정해준다!!

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_idx", referencedColumnName = "idx", updatable = false, nullable = false)
    private UserEntity userEntity;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "category_idx", referencedColumnName = "idx", updatable = false, nullable = false)
    private CategoryEntity categoryEntity;

h2DB를 사용할 거라 yml수정이랑 resource에 sql문을 넣어놓겠다.
application.yml

# yml이랑 같다
spring:
  thymeleaf:
    cache: false
  datasource:
    url: jdbc:h2:mem:farmerstory
    driverClassName: org.h2.Driver
    username: sa
    password: 
  h2:
    console:
      enabled: true
      path: /h2-console
  sql:
    init:
      encoding: UTF-8
      schema-locations: classpath:h2/schema.sql
      # dataLocations: classpath:h2/data.sql
      mode: always
  jpa:
    defer-datasource-initialization: true
    open-in-view: false # 트랜잭션 범위 밖에서 영속성 컨텍스트를 유지할지 여부
    database-platform: org.hibernate.dialect.H2Dialect
    hibernate:
      ddl-auto: none # create-drop, update, validate, none
      naming:
        physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
        implicit-strategy: org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl
    show-sql: true
    properties:
      hibernate:
        format_sql: true # 정렬 
        use_sql_comments: true
  servlet:
    multipart:
      max-request-size: 10MB
      max-file-size: 10MB


data.sql
DBeaver로 테이블 만들고 DDL을 가져온 것이다.

-- 유저테이블
INSERT INTO `user` (`idx`, `nickname`, `id`, `password`, `email`, `profile`, `authority`)
VALUES  (1, '관리자', 'admin', 'admin', 'admin@admin.com', NULL, '3'),
        (2, '농부', 'farmer', '1234', 'farmer@naver.com', NULL, '1'),
        (3, '일반인', 'temp', '1234', 'temp@naver.com', NULL, '2');

-- 카테고리테이블
INSERT INTO `category` (`idx`, `name`)
VALUES  (1, '곡류'),
        (2, '콩류'),
        (3, '서류'),
        (4, '채소'),
        (5, '과실류'),
        (6, '종실류'),
        (7, '특용작물'),
        (8, '약용작물'),
        (9, '전매작물'),
        (10, '화훼작물'),
        (11, '버섯류');

schema.sql
DDL을 가져와서 넣어줬다.

하지만 무작정 넣으면 안된다!!
H2 데이터베이스에서는 int(11)과 같은 숫자에 괄호를 사용하여 데이터 타입의 길이를 지정할 수 없기 때문에 int로 변경해야 한다.
또한 폴인키가 연결되어 있기 때문에 난 하나씩 넣을 생각이다.

일단 유저 테이블이랑 카테고리 테이블 필요해서 넣었다!

-- farmerstory.`user` definition
CREATE TABLE `user` (
  `idx` int NOT NULL AUTO_INCREMENT COMMENT '유저 번호',
  `nickname` varchar(100) NOT NULL COMMENT '유저닉네임',
  `id` varchar(100) NOT NULL COMMENT '유저id',
  `password` varchar(100) NOT NULL COMMENT '유저password',
  `email` varchar(100) NOT NULL COMMENT '유저email',
  `profile` longtext DEFAULT NULL COMMENT '유저 profile',
  `authority` varchar(100) NOT NULL COMMENT '권한(1은 farmer, 2는 일반)',
  PRIMARY KEY (`idx`)
);

회원가입 기능 구현

UserRepository.java

entity에서 가져올 Repository를 만들어준다.

package com.example.farmerstroy.model.user.repository;

import java.util.Optional;

import org.springframework.data.jpa.repository.JpaRepository;

import com.example.farmerstroy.model.user.entity.UserEntity;

public interface UserRepository extends JpaRepository<UserEntity, Long>{

    Optional <UserEntity> findById(String id);
    
}

난 id를 찾아오는 것만 필요해서 일단 이렇게 만들었다.


아 하기 전에 DTO를 하나 만들어준다.

ReqJoinDTO.java

화면에 필요한 것들만 적어준다.

package com.example.farmerstroy.domain.auth.dto;

import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@NoArgsConstructor
@AllArgsConstructor
@Getter
public class ReqJoinDTO {

    @Valid
    @NotNull(message = "회원 정보를 입력해주세요")
    private User user;

    @NoArgsConstructor
    @AllArgsConstructor
    @Builder
    @Getter
    public static class User{

        @NotBlank(message = "닉네임을 입력해주세요")
        private String nickname;

        @NotBlank(message = "아이디를 입력해주세요")
        private String id;

        @NotBlank(message = "비밀번호를 입력해주세요")
        private String psssword;

        @NotBlank(message = "이메일을 입력해주세요")
        private String email;
    }
}

여기서 @Valid어노테이션이 오류가 난다.
build.gradle에 꼭 붙여준다.

	implementation 'org.springframework.boot:spring-boot-starter-validation'

<br>

## ResponseDTO.java
그리고 응답 DTO를 만들어준다
```java
package com.example.farmerstroy.common.dto;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@NoArgsConstructor
@AllArgsConstructor
@Builder
@Getter
public class ResponseDTO<T> {
    private Integer code;
    private String message;
    private T data;
}

AuthServiceApiV1.java

repository가 가져온 것을 service로 가공해준다.
주석으로 설명을 어떤 걸 구현했는지 적어놨다!

package com.example.farmerstroy.domain.auth.service;

import java.util.Optional;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.example.farmerstroy.common.dto.ResponseDTO;
import com.example.farmerstroy.domain.auth.dto.ReqJoinDTO;
import com.example.farmerstroy.model.user.entity.UserEntity;
import com.example.farmerstroy.model.user.repository.UserRepository;

import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class AuthServiceApiV1 {
    
    private final UserRepository userRepository;

    public ResponseEntity<?> join(ReqJoinDTO dto){
        // 회원가입 정보 입력했는지 확인
        if (dto.getUser().getId() == null ||
            dto.getUser().getId().equals("") ||
            dto.getUser().getPsssword() == null ||
            dto.getUser().getPsssword().equals("") ||
            dto.getUser().getNickname() == null ||
            dto.getUser().getNickname().equals("") ||
            dto.getUser().getEmail() == null ||
            dto.getUser().getEmail().equals("")
        ) {
            return new ResponseEntity<>(
                ResponseDTO.builder()
                .code(1)
                .message("아이디나 비밀번호, 이름, 이메일을 입력해주세요")
                .build()
                ,HttpStatus.BAD_REQUEST
            );
        }

        //리파지토리에서 아이디로 유저 찾기
        Optional<UserEntity> userEntityOptional = userRepository.findById(dto.getUser().getId());

        // 있으면 있다고 메시지 리턴
        if (userEntityOptional.isPresent()) {
            return new ResponseEntity<>(
                ResponseDTO.builder()
                .code(1)
                .message("이미 존재하는 아이디입니다.")
                .build()
                ,HttpStatus.BAD_REQUEST
            );
        }

        // 없으면 회원가입 처리
        UserEntity userEntity = UserEntity.builder()
        .id(dto.getUser().getId())
        .password(dto.getUser().getPsssword())
        .nickname(dto.getUser().getNickname())
        .email(dto.getUser().getEmail())
        .build();

        userRepository.save(userEntity);

        // 응답데이터로 리턴하기
        return new ResponseEntity<>(
            ResponseDTO.builder()
                .code(0)
                .message("회원가입에 성공하였습니다.")
                .build(),
            HttpStatus.OK
        );
    }
}

AuthControllerApiV1.java

데이터를 뿌려주기 위한 controller를 만들어준다.

package com.example.farmerstroy.domain.auth.controller;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.example.farmerstroy.domain.auth.dto.ReqJoinDTO;
import com.example.farmerstroy.domain.auth.service.AuthServiceApiV1;

import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/auth")
public class AuthControllerApiV1 {
    
    private final AuthServiceApiV1 authServiceApiV1;

    @PostMapping("/join")
    public ResponseEntity<?> join(@Valid @RequestBody ReqJoinDTO dto){

        return authServiceApiV1.join(dto);
    }
}

이제 실행해본다
(두근두근..)

started가 떴다..하

포스트맨을 실행해본다..

오류..ㅎㅎㅎ

authority가 not null로 되어 있는데 안해줘서 그렇다..

그래서 임시로 1로 넣어서 해준다..

AuthControllerApiV1.java

여기에 넣어준다.

        // 없으면 회원가입 처리
        UserEntity userEntity = UserEntity.builder()
        .id(dto.getUser().getId())
        .password(dto.getUser().getPassword())
        .nickname(dto.getUser().getNickname())
        .email(dto.getUser().getEmail())
        .authority(1)
        .build();

실행하면 성공!!!!

h2에 들어가서 확인하면

됐다!!!!!

script를 만들오부자꾸나

아 그전에 badrequest만 처리할 수 있도록 만든다.


BadRequestException.java

예외를 처리해준다.

package com.example.farmerstroy.common.exception;

public class BadRequestException extends RuntimeException{
    
    public BadRequestException(String message){
        super(message);
    }

    public BadRequestException(){
        super("잘못된 요청입니다.");
    }
}

AuthServiceApiV1.java

그럼 이렇게 처리할 수 있다.

        // 회원가입 정보 입력했는지 확인
        if (dto.getUser().getId() == null ||
            dto.getUser().getId().equals("") ||
            dto.getUser().getPassword() == null ||
            dto.getUser().getPassword().equals("") ||
            dto.getUser().getNickname() == null ||
            dto.getUser().getNickname().equals("") ||
            dto.getUser().getEmail() == null ||
            dto.getUser().getEmail().equals("")
        ) {
            // return new ResponseEntity<>(
            //     ResponseDTO.builder()
            //     .code(1)
            //     .message("아이디나 비밀번호, 이름, 이메일을 입력해주세요")
            //     .build()
            //     ,HttpStatus.BAD_REQUEST
            // );

            throw new BadRequestException("아이디나 비밀번호, 이름, 이메일을 입력해주세요");
        }

        //리파지토리에서 아이디로 유저 찾기
        Optional<UserEntity> userEntityOptional = userRepository.findById(dto.getUser().getId());

        // 있으면 있다고 메시지 리턴
        if (userEntityOptional.isPresent()) {
            // return new ResponseEntity<>(
            //     ResponseDTO.builder()
            //     .code(1)
            //     .message("이미 존재하는 아이디입니다.")
            //     .build()
            //     ,HttpStatus.BAD_REQUEST
            // );
            
            throw new BadRequestException("이미 존재합니다.");
        }

실행하면 요렇게!!

db도 요렇게 된다!!

값을 null로 하면

입력하라고 아래 message가 뜬다.


join.html

<script>
    // 회원가입 요청
    // 회원가입 버튼을 누르면 실행되는 함수
    const requestJoin = () => {

        // 회원가입 요청
        const dto = {
            user: {
                nickname: document.querySelector("#nickname").value,
                id: document.querySelector("#id").value,
                password: document.querySelector("#password").value,
                email: document.querySelector("#email").value,
            },
        };
        fetch("/api/v1/auth/join",{
            method : "POST",
            headers : {
                "Content-Type" : "application/json;charset=utf-8"
            },
            body : JSON.stringify(dto)
        })
        .then((response) => response.json())
        .then((result) => {
            alert(result.message);
            if (result.code === 0) {
                // 회원가입 성공
                location.replace("/login");
            }
        })
    };
</script>

실행하면!!

db도!!

<script>
    // 회원가입 요청
    // 회원가입 버튼을 누르면 실행되는 함수
    const requestJoin = () => {
        // 서버와 통신하기 전에 입력값 검증
        if (!validateFields()) {
            return;
        }

        // 회원가입 요청
        const dto = {
            user: {
                nickname: document.querySelector("#nickname").value,
                id: document.querySelector("#id").value,
                password: document.querySelector("#password").value,
                email: document.querySelector("#email").value,
            },
        };
        fetch("/api/v1/auth/join",{
            method : "POST",
            headers : {
                "Content-Type" : "application/json;charset=utf-8"
            },
            body : JSON.stringify(dto)
        })
        .then((response) => response.json())
        .then((result) => {
            alert(result.message);
            if (result.code === 0) {
                // 회원가입 성공
                location.replace("/login");
            }
        })
    };

    // 유효성 검사
    const validateFields = () => {
        // id 속성을 이용해서 해당하는 요소를 가져온다
        const nicknameElement = document.querySelector("#nickname");
        const idElement = document.querySelector("#id");
        const passwordElement = document.querySelector("#password");
        const passwordCheckElement = document.querySelector("#passwordCheck");
        const emailElement = document.querySelector("#email");

        // 값들이 비어있는지 확인
        if (nicknameElement.value === "") {
            alert("닉네임을 확인해주세요");
            nicknameElement.focus();
            return false;
        }

        if (idElement.value === "") {
            alert("아이디를 입력해주세요");
            idElement.focus();
            return false;
        }
        
        if (passwordElement.value === "") {
            alert("비밀번호를 입력해주세요");
            passwordElement.focus();
            return false;
        }

        if (passwordElement.value !== passwordCheckElement.value) {
            alert("비밀번호가 일치하지 않습니다.");
            passwordCheckElement.focus();
            return false;
        }

        if (emailElement.value === "") {
            alert("이메일을 입력해주세요");
            emailElement.focus();
            return false;
        }

        return true;
    }
</script>



아이디를 두글자 밑으로 작성시 오류가 나도록 만들어본다.

ReqJoinDTO.java

어노테이션들을 이용해 설정한다.

package com.example.farmerstroy.domain.auth.dto;

import jakarta.validation.Valid;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@NoArgsConstructor
@AllArgsConstructor
@Getter
public class ReqJoinDTO {

    @Valid
    @NotNull(message = "회원 정보를 입력해주세요")
    private User user;

    @NoArgsConstructor
    @AllArgsConstructor
    @Builder
    @Getter
    public static class User{

        @NotBlank(message = "닉네임을 입력해주세요")
        @Pattern(regexp = "^[가-힣a-zA-Z0-9]{2,10}$" , message = "닉네임은 특수문자를 포함하지 않은 2~10자리여야 합니다.")
        private String nickname;

        @NotBlank(message = "아이디를 입력해주세요")
        @Pattern(regexp = "^[a-z0-9]{4,20}$", message = "아이디는 영어 소문자와 숫자만 사용하여 4~20자리여야 합니다.")
        private String id;

        @NotBlank(message = "비밀번호를 입력해주세요")
        @Size(min = 4, max = 20, message = "비밀번호는 4자 이상 20자 이하로 입력해주세요.")
        private String password;

        @NotBlank(message = "이메일 주소를 입력해주세요.")
        @Email(message = "올바른 이메일 주소를 입력해주세요.")
        private String email;
    
        
    }
}

그럼 이제 양식에 맞지 않게 입력하면 오류가 뜰 것인데 다 처리해주면 느려져서 한꺼번에 처리하도록 하겠다.


RestExceptionHandler.java

어떤 오류도 이 handler를 통해 처리된다.

package com.example.farmerstroy.common.exception.handler;

import java.util.HashMap;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import com.example.farmerstroy.common.dto.ResponseDTO;
import com.example.farmerstroy.common.exception.BadRequestException;

@RestControllerAdvice
// Rest방식으로 에러 처리
public class RestExceptionHandler {

    @ExceptionHandler(BindException.class)
    public ResponseEntity<?> handleBindException(BindException bindException){

        HashMap<String, String> errorMap = new HashMap<>();

        for (FieldError fieldError : bindException.getBindingResult().getFieldErrors()) {
            errorMap.put(fieldError.getField(), fieldError.getDefaultMessage());
        }

        return new ResponseEntity<>(
            ResponseDTO.builder()
            .code(1)
            .message("요청 데이터를 확인해주세요")
            .data(errorMap)
            .build(),
            HttpStatus.BAD_REQUEST
        );
    }

    // 처리할 에러를 특정함
    @ExceptionHandler(BadRequestException.class)
    public ResponseEntity<?> handleBadRequestException(Exception exception) {
        exception.printStackTrace();
        return new ResponseEntity<>(ResponseDTO.builder()
            .code(1)
            .message(exception.getMessage())
            .build(), HttpStatus.BAD_REQUEST);
    }

    // 모든 에러를 처리할 때
    // 이렇게 하면 찾아내기 어려움
    // class별로 에러를 처리하는게 나음
    // @ExceptionHandler(Exception.class)
    // public ResponseEntity<?> handleException(Exception exception){
    //     exception.printStackTrace();// 콘솔창에 에러를 뿌려주고 화면에 뿌려줌(하고 싶으면 하고)
    //     return new ResponseEntity<>(ResponseDTO.builder()
    //         .code(1)
    //         .message(exception.getMessage())
    //         .build(), HttpStatus.INTERNAL_SERVER_ERROR);
    // }
}

기타오류 고침

계속 하면서 data.sql을 만들어 놨는데 h2db에 들어가지 않았다.
그래서 보니 yml에 data.sql을 주석을 해놓고 유저 테이블은 만들어 졌으나 카테고리 테이블이 만들어지지 않았다!
데이터만 있고 테이블이 만들어지지 않았던 거다
오류를 고쳐본다.

application.yml

# yml이랑 같다
spring:
  thymeleaf:
    cache: false
  datasource:
    url: jdbc:h2:mem:farmerstory
    driverClassName: org.h2.Driver
    username: sa
    password: 
  h2:
    console:
      enabled: true
      path: /h2
  sql:
    init:
      encoding: UTF-8
      schema-locations: classpath:h2/schema.sql
      dataLocations: classpath:h2/data.sql
      mode: always
  jpa:
    defer-datasource-initialization: true
    open-in-view: false # 트랜잭션 범위 밖에서 영속성 컨텍스트를 유지할지 여부
    database-platform: org.hibernate.dialect.H2Dialect
    hibernate:
      ddl-auto: none # create-drop, update, validate, none
      naming:
        physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
        implicit-strategy: org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl
    show-sql: true
    properties:
      hibernate:
        format_sql: true # 정렬 
        use_sql_comments: true
  servlet:
    multipart:
      max-request-size: 10MB
      max-file-size: 10MB


schema.sql

추가 시켜준다.

-- farmerstory.category definition
CREATE TABLE `category` (
  `idx` int NOT NULL COMMENT 'Category Index',
  `name` varchar(100) NOT NULL COMMENT 'Category Name',
  PRIMARY KEY (`idx`)
);

build.gradle

h2db를 사용할꺼니깐 mysql은 주서처리해준다.

	// implementation 'mysql:mysql-connector-java'

그러고 실행하면 data가 잘 들어오는 걸 볼 수 있다.

profile
미래가 기대되는 풀스택개발자 공부 이야기~~

0개의 댓글