1. 프로젝트 소개

 

 

 

2. 주요 기능

  • Spring Security, JWT를 이용한 회원가입/로그인
  • 이메일 인증을 통한 로그인 구현
  • AWS S3를 이용한 다중 이미지 업로드
  • JPA Pageable을 이용한 페이지 무한 스크롤
  • 타입별, 가격별 필터링 기능
  • 키워드 검색 기능
  • 숙소 좋아요 기능
  • swagger 적용
  • 다중 이미지 업로드 CRUD(조회 시 이미지 preview)

 

3. 서비스 아키텍쳐

 

4. 기술 스택

🎨 Front-end Stack

  • React , javascript
  • Redux
  • Redux Toolkit
  • mui , styled-components
  • axios

🛠 Back-end Stack

  • Spring boot
  • Spring Security, JWT
  • AWS S3, RDS(MySQL)
  • OAuth 2.0

🌐 Infrastructure

  • AWS EC2
  • AWS S3

🗂 Dev tools

  • Swagger
  • Git, Github

 

5. 트러블 슈팅

 

6. 팀 노션

7. 깃허브

8. 다음에 사용해보고싶은 기술

  • S3를 이용한 다중 이미지 업로드의 개별 이미지 수정
  • refresh 토큰
  • https 적용
  • logging
  • 소셜 로그인 ( google, naver )
  • bucket4j
  • 지도 api
  • 주소 찾기 api 기능
  • grid속성을 조절해 카드 컴포넌트 크기 키우기
  • 웹소캣 채팅 기능

@Enablejpaauditing

문제

  • 게시글을 수정할 때, CreatedAt/ModifiedAt 값이 null로 반환되는 문제

해결

  • @Enablejpaauditing 어노테이션 추가
@EnableJpaAuditing
@SpringBootApplication
public class HanghaebnbApplication {

    public static void main(String[] args) {
        SpringApplication.run(HanghaebnbApplication.class, args);
    }

}

+추가 자료)

  • Jpa Auditing 를 활성화시키는 annotation 입니다.
  • Spring Audit 기능 : Spring Data JPA 에서 시간에 대해서 자동으로 값을 넣어주는 기능입니다. 도메인을 영속성 컨텍스트에 저장하거나 조회를 수행한 후에 update를 하는 경우 매번 시간 데이터를 입력하여 주어야 하는데, audit을 이용하면 자동으로 시간을 매핑하여 데이터베이스의 테이블에 넣어주게 됩니다.

 

createdAt, modifiedAt 값이 String 타입이 아니라 배열로 반환됨

문제

  • createdAt, modifiedAt 값이 String 타입이 아니라 배열로 반환됨

해결

  • ResponseDto에 @JsonFormat(shape = JsonFormat.Shape.*STRING*, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul") 추가

기술 매니저님 피드백)

경우에 따라 배열로 반환해 주는게 프론트엔드에서 값을 처리하기 편할 수도 있다. 따라서 이 부분은 상황에 맞게 반환타입을 주는게 좋다.

 

필터링 + 페이징 (with Spring Data JPA)

문제

  • 페이징과 카테고리별/가격별 조회를 한 번에 하려다보니 쿼리가 복잡해져서 Spring Data JPA의 Query Method만으로는 조회가 어려운 상황이었다.
  • @Query 어노테이션과 native query를 이용하여 해결하려 하였으나 native query와 페이징을 함께 사용하기가 까다로웠다.

해결

  • countQuery를 이용하여 query문을 작성하고, @Param 어노테이션을 함께 사용하여 메서드를 생성하여 해결하였다.
@Query(countQuery = "select count(*) from room r where (r.price between :minPrice and :maxPrice) and r.type = :type", nativeQuery = true)
Page<Room> findByPriceBetweenAndType(@Param("minPrice") int minPrice,
                                     @Param("maxPrice") int maxPrice,
                                     @Param("type") String type,
                                     Pageable pageable);

 

데이터 전달 시 타입 지정

문제

  • String type으로 매개변수를 받아올 때 공백문자가 섞이는 에러발생
  • 포스트맨에서 body - text로 놓고 {}없이 그냥 String 썼어야 함. 여태까지는 왜 이런 에러가 발생 안 했는지 생각해보니 여태까지는 dto로 받았었음.

해결

  • 이 문제를 해결하기 위해 제네릭스를 사용해서 해결했다가, 코드의 통일성위해 dto로 responsebody로 json형식으로 받아오는 방식으로 바꿈.

 

if-else와 try-catch

문제

  • if-else문 내부 throw → 특정 조건에서만 던져지는 exception

해결

  • try-catch문으로 변경.
  • catch시 try코드에서 어떤 exception이 터질지 알고있으니 그게 맞게 작성해주면 된다.
  • ? → 지금은 IOException이나 직접만든 CustomException 두개로 catch를 하고있지만 에러가 더욱 많아지면 계속 해서 catch문을 추가해서 해당하는 에러를 잡아야하나?
  • ! → 자바가 기본 제공하는 Exception중 해당 exception이 상속받는 상위 상위 상위 exception이 존재한다 초기에는 적절한 exception을 catch문으로 사용하여 잡으면 넓은 범위의 catch로 핸들링 할 수 있으며 이 범위는 최적화 과정에서 줄여나가면 된다.

CustomException

package com.cloneweek.hanghaebnb.util.exception;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public class CustomException extends RuntimeException{
    private final StatusMsgCode statusMsgCode;
}

 

StatusMsgCode

package com.cloneweek.hanghaebnb.util.exception;

import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.http.HttpStatus;

@Getter
@AllArgsConstructor
public enum StatusMsgCode {

    /* 400 BAD_REQUEST : 잘못된 요청 */
    USER_NOT_FOUND(HttpStatus.BAD_REQUEST, "회원을 찾을 수 없습니다."),
    INVALID_PASSWORD(HttpStatus.BAD_REQUEST, "비밀번호가 일치하지 않습니다."),
    BAD_ID_PASSWORD(HttpStatus.BAD_REQUEST, "아이디나 비밀번호 패턴이 맞지 않습니다."),
    ROOM_NOT_FOUND(HttpStatus.BAD_REQUEST, "숙소를 찾을 수 없습니다."),
    COMMENT_NOT_FOUND(HttpStatus.BAD_REQUEST, "댓글을 찾을 수 없습니다."),
    INVALID_USER(HttpStatus.BAD_REQUEST, "작성자만 삭제/수정할 수 있습니다."),
    FILE_UPLOAD_FAILED(HttpStatus.BAD_REQUEST, "파일 업로드 실패"),
    FILE_DELETE_FAILED(HttpStatus.BAD_REQUEST, "파일 삭제 실패"),


    /* 409 CONFLICT : Resource의 현재 상태와 충돌, 보통 중복된 데이터 존재 */
    DUPLICATE_RESOURCE(HttpStatus.CONFLICT, "데이터가 이미 존재합니다."),
    ALREADY_CLICKED_LIKE(HttpStatus.CONFLICT, "이미 좋아요를 눌렀습니다"),
    ALREADY_CANCEL_LIKE(HttpStatus.CONFLICT, "이미 좋아요 취소를 눌렀습니다"),
    EXIST_USER(HttpStatus.CONFLICT, "중복된 이메일입니다."),
    EXIST_NICK(HttpStatus.CONFLICT, "중복된 닉네임입니다."),
    UNMATCH_CODE(HttpStatus.BAD_REQUEST, "코드가 일치하지 않습니다."),


    /* 200 SUCCESS */
    SIGN_UP(HttpStatus.OK, "회원가입에 성공했습니다."),
    LOG_IN(HttpStatus.OK, "로그인에 성공했습니다"),
    LIKE(HttpStatus.OK, "좋아요 성공"),
    CANCEL_LIKE(HttpStatus.OK, "좋아요 취소"),
    DELETE_POST(HttpStatus.OK, "숙소를 삭제하였습니다"),
    DONE_POST(HttpStatus.OK, "숙소 등록 완료"),
    DELETE_COMMENT(HttpStatus.OK, "댓글을 삭제하였습니다"),
    NICKNAME(HttpStatus.OK, "사용 가능한 닉네임입니다."),
    EMAIL(HttpStatus.OK, "사용 가능한 이메일입니다."),
    EMAIL_CONFIRM(HttpStatus.OK, "해당 이메일로 회원가입 가능합니다."),
    UPDATE(HttpStatus.OK, "게시글 수정 완료"),
    MATCH_CODE(HttpStatus.OK, "코드가 일치합니다.");


    /* 401 UNAUTHORIZED : 인증되지 않은 사용자 */
//    INVALID_AUTH_TOKEN(HttpStatus.BAD_REQUEST, "토큰이 유효하지 않습니다."),
//    INVALID_AUTH_TOKEN(HttpStatus.UNAUTHORIZED, "권한 정보가 없는 토큰입니다."),
//    UNAUTHORIZED_USER(HttpStatus.UNAUTHORIZED, "존재하지 않는 유저입니다."),

    /* 404 NOT_FOUND : Resource를 찾을 수 없음 */
//    USER_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 유저 정보를 찾을 수 없습니다."),
//    REFRESH_TOKEN_NOT_FOUND(HttpStatus.NOT_FOUND, "로그아웃 한 유저입니다."),
//    NOT_FOLLOW(HttpStatus.NOT_FOUND, "팔로우 중이지 않습니다"),

    /* 500 INTERNAL_SERVER_ERROR */
//    INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "서버가 없습니다.");


    private final HttpStatus httpStatus;
    private final String detail;
}

 

GlobalExceptionHandler

package com.cloneweek.hanghaebnb.util.exception;

import lombok.extern.slf4j.Slf4j;
import org.hibernate.exception.ConstraintViolationException;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import static com.cloneweek.hanghaebnb.util.exception.StatusMsgCode.BAD_ID_PASSWORD;
import static com.cloneweek.hanghaebnb.util.exception.StatusMsgCode.DUPLICATE_RESOURCE;

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(value = { ConstraintViolationException.class, DataIntegrityViolationException.class})
    protected ResponseEntity<ErrorResponse> handleDataException() {
        log.error("handleDataException throw Exception : {}", DUPLICATE_RESOURCE);
        return ErrorResponse.toResponseEntity(DUPLICATE_RESOURCE);
    }

    @ExceptionHandler(value = { CustomException.class })
    protected ResponseEntity<ErrorResponse> handleCustomException(CustomException e) {
        log.error("handleCustomException throw CustomException : {}", e.getStatusMsgCode());
        return ErrorResponse.toResponseEntity(e.getStatusMsgCode());
    }

    @ExceptionHandler(value = {MethodArgumentNotValidException.class})
    protected ResponseEntity<ErrorResponse> processValidationException(MethodArgumentNotValidException e) {
        log.error("handleCustomException throw CustomException : {}", e.getMessage());
        return ErrorResponse.toResponseEntity(BAD_ID_PASSWORD);
    }

}

 

ErrorResponse

package com.cloneweek.hanghaebnb.util.exception;

import lombok.Builder;
import lombok.Getter;
import org.springframework.http.ResponseEntity;

@Getter
@Builder
public class
ErrorResponse {

    private final int statusCode;
    private final String message;

    public static ResponseEntity<ErrorResponse> toResponseEntity(StatusMsgCode statusMsgCode) {
        return ResponseEntity
                .status(statusMsgCode.getHttpStatus())
                .body(ErrorResponse.builder()
                        .statusCode(statusMsgCode.getHttpStatus().value())
                        .message(statusMsgCode.getDetail())
                        .build());
    }
}

 

Swagger란?

API에 대한 정보를 전달하기 위해 일일이 문서화하는 것은 매우 번거로운 작업이다. 매번 Rest API를 개발하고 수정하면서 API문서를 변경하는 것은 개발자의 생산성 또한 떨어뜨린다.

Swagger는 이러한 API문서를 자동으로 생성하여 HTML로 만들어주는 오픈 소스 프레임워크이다.

 

build.gradle

// Swagger
implementation 'io.springfox:springfox-swagger2:3.0.0'
implementation 'io.springfox:springfox-swagger-ui:3.0.0'
implementation 'io.springfox:springfox-boot-starter:3.0.0'

 

SwaggerConfig

package com.cloneweek.hanghaebnb.util.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2
@EnableAsync
@EnableWebMvc
public class SwaggerConfig implements WebMvcConfigurer {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("swagger-ui.html")
                .addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**")
                .addResourceLocations("classpath:/META-INF/resources/webjars/");
    }

    @Bean
    public Docket swagger() {
        return new Docket(DocumentationType.SWAGGER_2)
                .select()
                .apis(RequestHandlerSelectors.any())
                .paths(PathSelectors.any())
                .build()
                .apiInfo(apiInfo())
                .useDefaultResponseMessages(false);
    }

    private ApiInfo apiInfo() {
        ApiInfo apiInfo =
                new ApiInfo("항해bnb API", "항해 10기 B반 클론프로젝트 3조 API 명세서 입니다", "진짜 최종 ver", "https://github.com/hanghaebnb/BE", "contact", "3조 항해bnb 노션", "https://www.notion.so/eunsolan/3-bnb-a8edbe218a684cd2977937a5fc45fc7f");
        return apiInfo;
    }
}

 

application 실행 후 아래 링크에서 API명세 조회 가능 :) 

http://localhost:8080/swagger-ui/index.html#/


[ 참고 자료 ]

 

build.gradle

// 이메일 인증을 위한 설정
implementation 'org.springframework.boot:spring-boot-starter-mail'

 

EmailConfig

package com.cloneweek.hanghaebnb.util.email;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.JavaMailSenderImpl;

import java.util.Properties;

@Configuration
@PropertySource("classpath:application-email.properties")
public class EmailConfig {

    @Value("${mail.smtp.port}")
    private int port;
    @Value("${mail.smtp.socketFactory.port}")
    private int socketPort;
    @Value("${mail.smtp.auth}")
    private boolean auth;
    @Value("${mail.smtp.starttls.enable}")
    private boolean starttls;
    @Value("${mail.smtp.starttls.required}")
    private boolean startlls_required;
    @Value("${mail.smtp.socketFactory.fallback}")
    private boolean fallback;
    @Value("${AdminMail.id}")
    private String id;
    @Value("${AdminMail.password}")
    private String password;

    @Bean
    public JavaMailSender javaMailService() {
        JavaMailSenderImpl javaMailSender = new JavaMailSenderImpl();
        javaMailSender.setHost("smtp.gmail.com");
        javaMailSender.setUsername(id);
        javaMailSender.setPassword(password);
        javaMailSender.setPort(port);
        javaMailSender.setJavaMailProperties(getMailProperties());
        javaMailSender.setDefaultEncoding("UTF-8");
        return javaMailSender;
    }
    private Properties getMailProperties()
    {
        Properties pt = new Properties();
        pt.put("mail.smtp.socketFactory.port", socketPort);
        pt.put("mail.smtp.auth", auth);
        pt.put("mail.smtp.starttls.enable", starttls);
        pt.put("mail.smtp.starttls.required", startlls_required);
        pt.put("mail.smtp.socketFactory.fallback",fallback);
        pt.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");
        return pt;
    }
}

 

EmailController

package com.cloneweek.hanghaebnb.util.email;

import com.cloneweek.hanghaebnb.dto.RequestDto.DupliCheckDto;
import com.cloneweek.hanghaebnb.dto.ResponseDto.ResponseBoolDto;
import com.cloneweek.hanghaebnb.util.exception.StatusMsgCode;
import com.cloneweek.hanghaebnb.dto.ResponseDto.ResponseMsgDto;
import com.cloneweek.hanghaebnb.dto.RequestDto.SignupRequestDto;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import java.util.Map;

import static com.cloneweek.hanghaebnb.util.exception.StatusMsgCode.EXIST_NICK;
import static com.cloneweek.hanghaebnb.util.exception.StatusMsgCode.NICKNAME;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/users/email")
public class EmailController {
    private final EmailService emailService;

    // 입력한 이메일로 인증코드 발송
    @PostMapping("/confirm")
    public ResponseEntity<?> emailConfirm(@RequestBody EmailConfirmDto emailConfirmDto) throws Exception {
        emailService.sendSimpleMessage(emailConfirmDto.getEmail());
        return ResponseEntity.ok(new ResponseMsgDto(StatusMsgCode.EMAIL_CONFIRM));
    }

    // 인증코드 발송받은 이메일과 인증번호 입력
    @PostMapping("/verifycode")
    public ResponseEntity<ResponseMsgDto> verifyCode(@RequestParam String issuedCode, String email) {
//        return emailService.verifyCode(issuedCode, email);
        return ResponseEntity.ok(emailService.verifyCode(issuedCode, email));
    }

    // 회원가입 로직
    @PostMapping("/signup")
    public ResponseEntity<ResponseMsgDto> emailSignup(@RequestBody @Valid SignupRequestDto dto) {
        emailService.emailSignup(dto);
        return ResponseEntity.ok(new ResponseMsgDto(StatusMsgCode.SIGN_UP));
    }
}

 

EmailEntity

package com.cloneweek.hanghaebnb.util.email;

import lombok.Getter;
import lombok.NoArgsConstructor;
import javax.persistence.*;

@Getter
@Entity
@NoArgsConstructor
public class Code {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String email;

    @Column(nullable = false)
    private String randomCode;

    public Code(String randomCode, String email) {
        this.randomCode = randomCode;
        this.email = email;
    }
}

 

EmailConfirmDto

package com.cloneweek.hanghaebnb.util.email;

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

@Getter
@NoArgsConstructor
@AllArgsConstructor
public class EmailConfirmDto {
    private String email;
}

 

EmailRepository

package com.cloneweek.hanghaebnb.util.email;

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

import java.util.List;

public interface CodeRepository extends JpaRepository<Code, Long> {
    List<Code> findByEmail(String email);
    Code findByEmailAndId(String email, Long id);
}

 

EmailService

package com.cloneweek.hanghaebnb.util.email;

import com.cloneweek.hanghaebnb.dto.ResponseDto.ResponseBoolDto;
import com.cloneweek.hanghaebnb.dto.ResponseDto.ResponseMsgDto;
import com.cloneweek.hanghaebnb.util.exception.CustomException;
import com.cloneweek.hanghaebnb.dto.RequestDto.SignupRequestDto;
import com.cloneweek.hanghaebnb.entity.User;
import com.cloneweek.hanghaebnb.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.mail.MailException;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import java.util.List;
import java.util.Random;

import static com.cloneweek.hanghaebnb.util.exception.StatusMsgCode.*;

@Service
@RequiredArgsConstructor
public class EmailService {
    private final JavaMailSender emailSender;
    private final PasswordEncoder passwordEncoder;
    private final UserRepository userRepository;
    private final CodeRepository codeRepository;


    // 랜덤하게 만든 인증코드 메일로 발송하는 메서드
    public String sendSimpleMessage(String email) throws Exception {
        if (userRepository.findByEmail(email).isPresent()) {
            throw new CustomException(EXIST_USER);
        }
        String randomCode = "";
        StringBuffer key = new StringBuffer();
        Random rnd = new Random();

        // 인증코드 랜덤하게 만들기. 인증 요청될 때마다 새롭게만들어 메일로 보냄
        for (int i = 0; i < 8; i++) { // 인증코드 8자리
            int index = rnd.nextInt(3); // 0~2 까지 랜덤

            switch (index) {
                case 0:
                    key.append((char) ((int) (rnd.nextInt(26)) + 97));
                    //  a~z  (ex. 1+97=98 => (char)98 = 'b')
                    break;
                case 1:
                    key.append((char) ((int) (rnd.nextInt(26)) + 65));
                    //  A~Z
                    break;
                case 2:
                    key.append((rnd.nextInt(10)));
                    // 0~9
                    break;
            }
        }
        randomCode = key.toString();

        // 메세지 내용 작성
        MimeMessage message = emailSender.createMimeMessage();

        message.addRecipients(MimeMessage.RecipientType.TO, email);
        message.setSubject("hanghaebnb 회원가입 이메일 인증");
        String msgg = "";
        msgg += "<div style='margin:100px;'>";
        msgg += "<h1> 안녕하세요 hanghaebnb입니다. </h1>";
        msgg += "<br>";
        msgg += "<p>아래 코드를 회원가입 창으로 돌아가 입력해주세요<p>";
        msgg += "<br>";
        msgg += "<p>감사합니다!<p>";
        msgg += "<br>";
        msgg += "<div align='center' style='border:1px solid black; font-family:verdana';>";
        msgg += "<h3 style='color:blue;'>회원가입 인증 코드입니다.</h3>";
        msgg += "<div style='font-size:130%'>";
        msgg += "CODE : <strong>";
        msgg += randomCode + "</strong><div><br/> ";
        msgg += "</div>";
        message.setText(msgg, "utf-8", "html");//내용
        message.setFrom(new InternetAddress("hanghaebnb@gmail.com", "test"));//보내는 사람

        // 메세지 발송
        try {
            emailSender.send(message);
        } catch (MailException es) {
            es.printStackTrace();
            throw new IllegalArgumentException();
        }

        // 만들었던 랜덤코드 DB에 저장하기
        Code code = new Code(randomCode, email);
        codeRepository.save(code);
        return randomCode;
    }

    // 회원가입
    public void emailSignup(SignupRequestDto dto) {
        String email = dto.getEmail();
        String password = passwordEncoder.encode(dto.getPassword());
        String nickname = dto.getNickname();

        // DB에 이메일과 닉네임 중복 있는지 체크
        if (userRepository.findByEmail(email).isPresent()) {
            throw new CustomException(EXIST_USER);
        }
        if (userRepository.findByNickname(nickname).isPresent()) {
            throw new CustomException(EXIST_NICK);
        }

        // DB에 회원가입할 데이터 저장. builder 패턴으로 적용해봄.
        // User user = new User(email, password, nickname);
        User user = User.builder().email(email)
                .kakaoNickname(nickname)
                .password(password)
                .build();                              // 생성자 호출

        userRepository.save(user);
    }

    // 이메일로 받은 랜덤코드 대조
    public ResponseMsgDto verifyCode(String issuedCode, String email) {

        // 리파지토리에서 해당 이메일로 저장된 모든 랜덤코드 불러오기
        // 리파지토리에서 불러온 랜덤코드 중 가장 최신순으로 발행된 코드 찾기
        List<Code> codeList = codeRepository.findByEmail(email);
        long id = 0L;
        for (Code code : codeList) {
            if (code.getId() > id) {
                id = code.getId();
            }
        }

        // 해당 email로 보낸 가장 최신 랜덤코드 불러오기
        Code code = codeRepository.findByEmailAndId(email, id);
        Boolean result = code.getRandomCode().equals(issuedCode);
        return new ResponseBoolDto(result? MATCH_CODE : UNMATCH_CODE, result);
    }
}

 

Email Properties

mail.smtp.auth=true
mail.smtp.starttls.required=true
mail.smtp.starttls.enable=true
mail.smtp.socketFactory.class=javax.net.ssl.SSLSocketFactory
mail.smtp.socketFactory.fallback=false
mail.smtp.port=465
mail.smtp.socketFactory.port=465

#admin ?? ??
AdminMail.id= 계정 이메일 주소
AdminMail.password= 계정 이메일 비밀번호

[ 참고 자료 ]

 

https://hyunmin1906.tistory.com/276

 

[Go] Google Gmail SMTP 설정 방법 및 메일 전송

■ SMTP 간이 우편 전송 프로토콜(Simple Mail Transfer Protocol)의 약자. 이메일 전송에 사용되는 네트워크 프로토콜이다. 인터넷에서 메일 전송에 사용되는 표준이다. 1982년 RFC821에서 표준화되어 현재

hyunmin1906.tistory.com

https://javaju.tistory.com/100

https://badstorage.tistory.com/38

 

+ Recent posts