-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #81 from UMC-TripPiece/feature/79
- Loading branch information
Showing
15 changed files
with
470 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
package umc.TripPiece.config; | ||
|
||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.context.annotation.Bean; | ||
import org.springframework.context.annotation.Configuration; | ||
import org.springframework.mail.javamail.JavaMailSender; | ||
import org.springframework.mail.javamail.JavaMailSenderImpl; | ||
import umc.TripPiece.service.EmailService; | ||
|
||
import java.util.Properties; | ||
|
||
@Configuration | ||
public class EmailConfig { | ||
@Value("${spring.mail.host}") | ||
private String host; | ||
|
||
@Value("${spring.mail.port}") | ||
private int port; | ||
|
||
@Value("${spring.mail.username}") | ||
private String username; | ||
|
||
@Value("${spring.mail.password}") | ||
private String password; | ||
|
||
@Value("${spring.mail.properties.mail.smtp.auth}") | ||
private boolean auth; | ||
|
||
@Value("${spring.mail.properties.mail.smtp.starttls.enable}") | ||
private boolean starttlsEnable; | ||
|
||
@Value("${spring.mail.properties.mail.smtp.starttls.required}") | ||
private boolean starttlsRequired; | ||
|
||
@Value("${spring.mail.properties.mail.smtp.connectiontimeout}") | ||
private int connectionTimeout; | ||
|
||
@Value("${spring.mail.properties.mail.smtp.timeout}") | ||
private int timeout; | ||
|
||
@Value("${spring.mail.properties.mail.smtp.writetimeout}") | ||
private int writeTimeout; | ||
|
||
@Bean | ||
public EmailService emailService() { | ||
return new EmailService(javaMailSender()); | ||
} | ||
|
||
@Bean | ||
public JavaMailSender javaMailSender() { | ||
JavaMailSenderImpl mailSender = new JavaMailSenderImpl(); | ||
mailSender.setHost(host); | ||
mailSender.setPort(port); | ||
mailSender.setUsername(username); | ||
mailSender.setPassword(password); | ||
mailSender.setDefaultEncoding("UTF-8"); | ||
mailSender.setJavaMailProperties(getMailProperties()); | ||
|
||
return mailSender; | ||
} | ||
|
||
private Properties getMailProperties() { | ||
Properties properties = new Properties(); | ||
properties.put("mail.smtp.auth", auth); | ||
properties.put("mail.smtp.starttls.enable", starttlsEnable); | ||
properties.put("mail.smtp.starttls.required", starttlsRequired); | ||
properties.put("mail.smtp.connectiontimeout", connectionTimeout); | ||
properties.put("mail.smtp.timeout", timeout); | ||
properties.put("mail.smtp.writetimeout", writeTimeout); | ||
|
||
return properties; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package umc.TripPiece.domain; | ||
|
||
import jakarta.persistence.*; | ||
import lombok.Getter; | ||
import lombok.NoArgsConstructor; | ||
|
||
import java.time.LocalDateTime; | ||
|
||
@Getter | ||
@NoArgsConstructor | ||
@Entity | ||
public class VerificationCode { | ||
|
||
@Id | ||
@GeneratedValue(strategy = GenerationType.IDENTITY) | ||
private Long id; | ||
|
||
private String email; | ||
private String code; | ||
private LocalDateTime expirationTime; | ||
|
||
public VerificationCode(String email, String code, int expirationMinutes) { | ||
this.email = email; | ||
this.code = code; | ||
this.expirationTime = LocalDateTime.now().plusMinutes(expirationMinutes); | ||
} | ||
|
||
public boolean isExpired() { | ||
return LocalDateTime.now().isAfter(expirationTime); | ||
} | ||
} |
12 changes: 12 additions & 0 deletions
12
src/main/java/umc/TripPiece/repository/VerificationCodeRepository.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package umc.TripPiece.repository; | ||
|
||
import org.springframework.data.jpa.repository.JpaRepository; | ||
import org.springframework.stereotype.Repository; | ||
import umc.TripPiece.domain.VerificationCode; | ||
|
||
import java.util.Optional; | ||
|
||
@Repository | ||
public interface VerificationCodeRepository extends JpaRepository<VerificationCode, Long> { | ||
Optional<VerificationCode> findTopByEmailOrderByExpirationTimeDesc(String email); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
package umc.TripPiece.service; | ||
|
||
import jakarta.mail.MessagingException; | ||
import jakarta.mail.internet.MimeMessage; | ||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.springframework.mail.javamail.JavaMailSender; | ||
import org.springframework.mail.javamail.MimeMessageHelper; | ||
import org.springframework.stereotype.Service; | ||
import org.springframework.transaction.annotation.Transactional; | ||
|
||
import java.io.IOException; | ||
import java.nio.file.Files; | ||
import java.nio.file.Paths; | ||
import java.util.Random; | ||
|
||
@Slf4j | ||
@Transactional | ||
@Service | ||
@RequiredArgsConstructor | ||
public class EmailService { | ||
|
||
private final JavaMailSender emailSender; | ||
|
||
public String generateVerificationCode() { | ||
Random random = new Random(); | ||
int code = 100000 + random.nextInt(900000); // 6자리 숫자 생성 | ||
return String.valueOf(code); | ||
} | ||
|
||
public void sendVerificationCode(String toEmail, String code) throws MessagingException, IOException { | ||
String subject = "[여행조각(TripPiece)] 회원가입 시 이메일 인증번호 안내드립니다."; | ||
|
||
// HTML 파일을 읽고 코드 삽입 | ||
String content; | ||
try { | ||
content = getEmailHtmlContent(code); | ||
} catch (IOException e) { | ||
log.error("Failed to read email template", e); | ||
throw new MessagingException("이메일 템플릿을 읽는 중 오류가 발생했습니다."); | ||
} | ||
|
||
sendEmail(toEmail, subject, content); | ||
} | ||
|
||
private String getEmailHtmlContent(String code) throws IOException { | ||
String htmlTemplatePath = "src/main/resources/templates/email.html"; | ||
String content = new String(Files.readAllBytes(Paths.get(htmlTemplatePath))); | ||
|
||
// 인증 코드 삽입 | ||
content = content.replace("{code}", code); | ||
|
||
return content; | ||
} | ||
|
||
public void sendEmail(String toEmail, String title, String content) throws MessagingException { | ||
MimeMessage message = emailSender.createMimeMessage(); | ||
MimeMessageHelper helper = new MimeMessageHelper(message, true); | ||
helper.setTo(toEmail); | ||
helper.setSubject(title); | ||
helper.setText(content, true); | ||
try { | ||
emailSender.send(message); | ||
} catch (RuntimeException e) { | ||
throw new RuntimeException("이메일 전송 불가", e); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
91 changes: 91 additions & 0 deletions
91
src/main/java/umc/TripPiece/web/controller/EmailController.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
package umc.TripPiece.web.controller; | ||
|
||
import io.swagger.v3.oas.annotations.tags.Tag; | ||
import jakarta.mail.MessagingException; | ||
import io.swagger.v3.oas.annotations.Operation; | ||
import jakarta.validation.Valid; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.http.HttpStatus; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.validation.BindingResult; | ||
import org.springframework.web.bind.annotation.*; | ||
import umc.TripPiece.domain.VerificationCode; | ||
import umc.TripPiece.payload.ApiResponse; | ||
import umc.TripPiece.repository.VerificationCodeRepository; | ||
import umc.TripPiece.service.EmailService; | ||
import umc.TripPiece.web.dto.request.EmailRequestDto; | ||
|
||
import java.io.IOException; | ||
import java.util.Optional; | ||
|
||
@Tag(name = "Email", description = "이메일 인증 관련 API") | ||
@RestController | ||
@RequestMapping("/email") | ||
@RequiredArgsConstructor | ||
public class EmailController { | ||
|
||
private final EmailService emailService; | ||
private final VerificationCodeRepository verificationCodeRepository; | ||
|
||
@PostMapping("/send") | ||
@Operation(summary = "이메일 인증번호 전송 API", | ||
description = "이메일로 6자리 인증번호 발송") | ||
public ResponseEntity<ApiResponse<String>> sendVerificationCode(@RequestBody EmailRequestDto.SendCodeDto request) { | ||
String email = request.getEmail(); | ||
|
||
// 이메일 주소 유효성 체크 | ||
if (!isValidEmail(email)) { | ||
return ResponseEntity.badRequest().body(ApiResponse.onFailure("400", "유효한 이메일 주소여야 합니다.", null)); | ||
} | ||
|
||
String code = emailService.generateVerificationCode(); | ||
|
||
VerificationCode verificationCode = new VerificationCode(request.getEmail(), code, 3); // 인증코드 유효시간 (3분) | ||
verificationCodeRepository.save(verificationCode); | ||
|
||
try { | ||
emailService.sendVerificationCode(email, code); | ||
} catch (MessagingException e) { | ||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ApiResponse.onFailure("500", "이메일 전송에 실패했습니다.", null)); | ||
} catch (IOException e) { | ||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ApiResponse.onFailure("500", "이메일 템플릿을 읽는 중 오류가 발생했습니다.", null)); | ||
} | ||
|
||
return ResponseEntity.ok(ApiResponse.onSuccess("해당 이메일로 인증번호를 전송했습니다.")); | ||
} | ||
|
||
@PostMapping("/verify") | ||
@Operation(summary = "이메일 인증번호 검증 API", | ||
description = "이메일로 발송된 인증번호의 일치 여부 검증") | ||
public ResponseEntity<ApiResponse<String>> verifyCode(@Valid @RequestBody EmailRequestDto.VerifyCodeDto request, BindingResult bindingResult) { | ||
if (bindingResult.hasErrors()) { | ||
// 오류 메시지 추출 | ||
String errorMessage = bindingResult.getFieldError().getDefaultMessage(); | ||
return ResponseEntity.badRequest().body(ApiResponse.onFailure("400", errorMessage, null)); | ||
} | ||
|
||
Optional<VerificationCode> optionalCode = verificationCodeRepository.findTopByEmailOrderByExpirationTimeDesc(request.getEmail()); | ||
|
||
if (optionalCode.isEmpty()) { | ||
return ResponseEntity.badRequest().body(ApiResponse.onFailure("400", "인증코드가 발송되지 않은 이메일입니다.", null)); | ||
} | ||
|
||
VerificationCode verificationCode = optionalCode.get(); | ||
|
||
|
||
if (verificationCode.isExpired()) { | ||
return ResponseEntity.badRequest().body(ApiResponse.onFailure("400", "이메일 인증 시간인 3분을 초과했습니다.", null)); | ||
} | ||
|
||
if (!verificationCode.getCode().equals(request.getCode())) { | ||
return ResponseEntity.badRequest().body(ApiResponse.onFailure("400", "인증번호가 일치하지 않습니다.", null)); | ||
} | ||
|
||
return ResponseEntity.ok(ApiResponse.onSuccess("이메일 인증에 성공했습니다.")); | ||
} | ||
|
||
private boolean isValidEmail(String email) { | ||
String emailRegex = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9-]+(\\.[A-Za-z0-9-]+)*\\.(com|net|org|co\\.kr|ac\\.kr|gov|edu)$"; | ||
return email.matches(emailRegex); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.