Skip to content

Commit

Permalink
[MERGE/#151] 대학 이메일 인증 방식 변경
Browse files Browse the repository at this point in the history
[FIX] #151 - 대학 이메일 인증 방식 변경
  • Loading branch information
seokbeom00 authored Oct 30, 2024
2 parents 86cba02 + dab7f55 commit d65cecf
Show file tree
Hide file tree
Showing 22 changed files with 235 additions and 251 deletions.
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ dependencies {

// SWAGER
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.1.0'

// EMAIL
implementation 'org.springframework.boot:spring-boot-starter-mail'
}

ext {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,52 @@
import lombok.RequiredArgsConstructor;
import org.sopt.seonyakServer.domain.university.dto.SearchDeptResponse;
import org.sopt.seonyakServer.domain.university.dto.SearchUnivResponse;
import org.sopt.seonyakServer.domain.university.dto.UnivVerifyCodeRequest;
import org.sopt.seonyakServer.domain.university.dto.UnivVerifyRequest;
import org.sopt.seonyakServer.domain.university.service.UnivService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
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.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/search")
@RequestMapping("/api/v1/")
public class UnivController {

private final UnivService univService;

@GetMapping("/univ")
@GetMapping("search/univ")
public ResponseEntity<SearchUnivResponse> searchUniv(
@RequestParam final String univName
) {
return ResponseEntity.ok(univService.searchUniv(univName));
}

@GetMapping("/dept")
@GetMapping("search/dept")
public ResponseEntity<List<SearchDeptResponse>> searchDept(
@RequestParam final String univName,
@RequestParam final String deptName
) {
return ResponseEntity.ok(univService.searchDept(univName, deptName));
}

@PostMapping("/univ/verify")
public ResponseEntity<Void> verify(
@RequestBody UnivVerifyRequest univVerifyRequest
) {
univService.verifyEmail(univVerifyRequest);
return ResponseEntity.ok().build();
}

@PostMapping("/univ/verifycode")
public ResponseEntity<Void> verifyCode(
@RequestBody UnivVerifyCodeRequest univVerifyCodeRequest
) {
univService.verifyCode(univVerifyCodeRequest);
return ResponseEntity.ok().build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.sopt.seonyakServer.domain.university.dto;

public record UnivVerifyCodeRequest(
String univEmail,
String verificationCode
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package org.sopt.seonyakServer.domain.university.dto;

public record UnivVerifyRequest(
String univName,
String univMail
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.sopt.seonyakServer.domain.university.model;

import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.redis.core.RedisHash;

@RedisHash(value = "UnivVerificationCode", timeToLive = 5 * 60L) // TTL 5분
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class UnivCode {
@Id
private String univMail;

private String verificationCode;

@Builder
private UnivCode(
final String univMail,
final String verificationCode
) {
this.univMail = univMail;
this.verificationCode = verificationCode;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package org.sopt.seonyakServer.domain.university.model;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name = "university_email")
public class UniversityEmail {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;

@Column(name = "univ_name")
private String univName;

@Column(name = "email_domain")
private String emailDomain;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.sopt.seonyakServer.domain.university.repository;

import java.util.Optional;
import org.sopt.seonyakServer.domain.university.model.UnivCode;
import org.springframework.data.repository.CrudRepository;

public interface UnivCodeRepository extends CrudRepository<UnivCode, String> {
Optional<UnivCode> findByUnivMail(final String univMail);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.sopt.seonyakServer.domain.university.repository;

import java.util.Optional;
import org.sopt.seonyakServer.domain.university.model.UniversityEmail;
import org.sopt.seonyakServer.global.exception.enums.ErrorType;
import org.sopt.seonyakServer.global.exception.model.CustomException;
import org.springframework.data.jpa.repository.JpaRepository;

public interface UniversityEmailRepository extends JpaRepository<UniversityEmail, Long> {
Optional<UniversityEmail> findUniversityEmailByUnivName(String univName);

boolean existsByUnivName(String univName);

default UniversityEmail findUniversityEmailByUnivNameOrThrow(String univName) {
return findUniversityEmailByUnivName(univName)
.orElseThrow(() -> new CustomException(ErrorType.NOT_FOUND_UNIV_EMAIL_DOMAIN_ERROR));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package org.sopt.seonyakServer.domain.university.service;

import lombok.RequiredArgsConstructor;
import org.sopt.seonyakServer.domain.university.model.UnivCode;
import org.sopt.seonyakServer.domain.university.repository.UnivCodeRepository;
import org.sopt.seonyakServer.global.exception.enums.ErrorType;
import org.sopt.seonyakServer.global.exception.model.CustomException;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class UnivCodeService {
private final UnivCodeRepository univCodeRepository;

public void saveUnivVerificationCode(
final String univMail,
final String verificationCode
) {
univCodeRepository.save(
UnivCode.builder()
.univMail(univMail)
.verificationCode(verificationCode)
.build()
);
}

public String findCodeByUnivMail(final String univMail) {
UnivCode univCode = univCodeRepository.findByUnivMail(univMail).orElseThrow(
() -> new CustomException(ErrorType.NO_VERIFICATION_REQUEST_HISTORY)
);

return univCode.getVerificationCode();
}

public void deleteVerificationCode(final String univMail) {
UnivCode univCode = univCodeRepository.findByUnivMail(univMail).orElseThrow(
() -> new CustomException(ErrorType.NO_VERIFICATION_REQUEST_HISTORY)
);

univCodeRepository.delete(univCode);
}
}
Original file line number Diff line number Diff line change
@@ -1,25 +1,38 @@
package org.sopt.seonyakServer.domain.university.service;

import jakarta.mail.MessagingException;
import jakarta.mail.internet.MimeMessage;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.sopt.seonyakServer.domain.university.dto.SearchDeptResponse;
import org.sopt.seonyakServer.domain.university.dto.SearchUnivResponse;
import org.sopt.seonyakServer.domain.university.dto.UnivVerifyCodeRequest;
import org.sopt.seonyakServer.domain.university.dto.UnivVerifyRequest;
import org.sopt.seonyakServer.domain.university.model.Department;
import org.sopt.seonyakServer.domain.university.model.UniversityEmail;
import org.sopt.seonyakServer.domain.university.repository.DeptRepository;
import org.sopt.seonyakServer.domain.university.repository.UnivRepository;
import org.sopt.seonyakServer.domain.university.repository.UniversityEmailRepository;
import org.sopt.seonyakServer.global.exception.enums.ErrorType;
import org.sopt.seonyakServer.global.exception.model.CustomException;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class UnivService {

private final UnivRepository univRepository;
private final DeptRepository deptRepository;
private final JavaMailSender javaMailSender;
private final UniversityEmailRepository universityEmailRepository;
private final UnivCodeService univCodeService;

public SearchUnivResponse searchUniv(final String univNamePart) {
if (univNamePart == null || univNamePart.trim().isEmpty()) {
Expand Down Expand Up @@ -48,4 +61,57 @@ public List<SearchDeptResponse> searchDept(
)
.collect(Collectors.toList());
}

public void verifyEmail(UnivVerifyRequest univVerifyRequest) {
// 학교 이름이 테이블에 있는지
if (!universityEmailRepository.existsByUnivName(univVerifyRequest.univName())) {
throw new CustomException(ErrorType.NOT_FOUND_UNIV_NAME_ERROR);
}

UniversityEmail universityEmail = universityEmailRepository.findUniversityEmailByUnivNameOrThrow(
univVerifyRequest.univName());

// 사용자가 제공한 이메일 주소의 도메인 추출
String userDomain = univVerifyRequest.univMail().split("@")[1];

// 학교 이메일 도메인과 비교
if (!userDomain.equals(universityEmail.getEmailDomain())) {
throw new CustomException(ErrorType.INVALID_EMAIL_DOMAIN_ERROR);
}

MimeMessage mimeMessage = javaMailSender.createMimeMessage();

try {
MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(mimeMessage, false, "UTF-8");
mimeMessageHelper.setTo(univVerifyRequest.univMail());
mimeMessageHelper.setSubject("선약 인증번호");
String verificationCode = generateRandomNumber(4);
mimeMessageHelper.setText("[선약] 인증번호는 [" + verificationCode + "] 입니다.", false);
javaMailSender.send(mimeMessage);
univCodeService.saveUnivVerificationCode(univVerifyRequest.univMail(), verificationCode);
} catch (MessagingException e) {
throw new CustomException(ErrorType.SMTP_ERROR);
}
}

// 인증번호 일치 여부 확인
@Transactional
public void verifyCode(UnivVerifyCodeRequest univVerifyCodeRequest) {

if (univVerifyCodeRequest.verificationCode()
.equals(univCodeService.findCodeByUnivMail(univVerifyCodeRequest.univEmail()))) {
univCodeService.deleteVerificationCode(univVerifyCodeRequest.univEmail());

} else {
throw new CustomException(ErrorType.INVALID_VERIFICATION_CODE_ERROR);
}
}

private String generateRandomNumber(int digitCount) {
Random random = new Random();
int min = (int) Math.pow(10, digitCount - 1);
int max = (int) Math.pow(10, digitCount) - 1;

return String.valueOf(random.nextInt((max - min) + 1) + min);
}
}

This file was deleted.

This file was deleted.

This file was deleted.

Loading

0 comments on commit d65cecf

Please sign in to comment.