-
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 remote-tracking branch 'origin' into #53-change-product-status
- Loading branch information
Showing
35 changed files
with
928 additions
and
160 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
29 changes: 29 additions & 0 deletions
29
src/main/java/com/market/saessag/domain/email/controller/PasswordController.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,29 @@ | ||
package com.market.saessag.domain.email.controller; | ||
|
||
import com.market.saessag.domain.email.dto.EmailRequest; | ||
import com.market.saessag.domain.email.dto.PasswordChangeRequest; | ||
import com.market.saessag.domain.email.service.PasswordService; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.validation.annotation.Validated; | ||
import org.springframework.web.bind.annotation.*; | ||
|
||
@RestController | ||
@RequiredArgsConstructor | ||
@RequestMapping("/api/password") | ||
public class PasswordController { | ||
private final PasswordService passwordService; | ||
|
||
@PostMapping("/find") | ||
public ResponseEntity<?> findPassword(@RequestBody @Validated EmailRequest request) { | ||
// 이메일로 임시 비밀번호 발급 | ||
passwordService.sendTemporaryPassword(request.getEmail()); | ||
return ResponseEntity.ok("임시 비밀번호가 이메일로 발송되었습니다."); // 응답 포맷 통일 필요함 | ||
} | ||
|
||
@PatchMapping("/change") | ||
public ResponseEntity<?> changePassword(@RequestBody @Validated PasswordChangeRequest request) { | ||
passwordService.changePassword(request); | ||
return ResponseEntity.ok("비밀번호가 성공적으로 변경되었습니다."); // 응답 포맷 통일 필요함 | ||
} | ||
} |
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
13 changes: 13 additions & 0 deletions
13
src/main/java/com/market/saessag/domain/email/dto/PasswordChangeRequest.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,13 @@ | ||
package com.market.saessag.domain.email.dto; | ||
|
||
import lombok.AccessLevel; | ||
import lombok.Getter; | ||
import lombok.NoArgsConstructor; | ||
|
||
@Getter | ||
@NoArgsConstructor(access = AccessLevel.PROTECTED) | ||
public class PasswordChangeRequest { | ||
private String email; | ||
private String currentPassword; | ||
private String newPassword; // 새로 설정할 비밀번호 | ||
} |
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
129 changes: 129 additions & 0 deletions
129
src/main/java/com/market/saessag/domain/email/service/PasswordService.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,129 @@ | ||
package com.market.saessag.domain.email.service; | ||
|
||
import com.market.saessag.domain.email.dto.PasswordChangeRequest; | ||
import com.market.saessag.domain.user.entity.User; | ||
import com.market.saessag.domain.user.repository.UserRepository; | ||
import com.market.saessag.global.exception.CustomException; | ||
import com.market.saessag.global.exception.ErrorCode; | ||
import com.market.saessag.global.util.TemporaryPassword; | ||
import jakarta.mail.MessagingException; | ||
import jakarta.mail.internet.MimeMessage; | ||
import jakarta.transaction.Transactional; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.mail.javamail.JavaMailSender; | ||
import org.springframework.mail.javamail.MimeMessageHelper; | ||
import org.springframework.scheduling.annotation.Scheduled; | ||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; | ||
import org.springframework.stereotype.Service; | ||
|
||
import java.time.LocalDateTime; | ||
import java.util.Map; | ||
import java.util.Random; | ||
import java.util.concurrent.ConcurrentHashMap; | ||
|
||
@Service | ||
@RequiredArgsConstructor | ||
public class PasswordService { | ||
private final UserRepository userRepository; | ||
private final JavaMailSender mailSender; // 이메일 발송을 위한 스프링 제공 인터페이스 | ||
private final BCryptPasswordEncoder passwordEncoder; | ||
private final Map<String, TemporaryPassword> temporaryPasswordStore = new ConcurrentHashMap<>(); // 임시 저장소 추가 | ||
|
||
|
||
// 임시 비밀번호 발급 | ||
public void sendTemporaryPassword(String email) { | ||
try { | ||
// 1. 사용자 존재 여부 확인 | ||
User user = userRepository.findByEmail(email) | ||
.orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND)); | ||
|
||
// 2. 임시 비밀번호 생성 | ||
String temporaryPassword = generateTemporaryPassword(); | ||
|
||
// 3. 임시 비밀번호 저장 (5분 유효) | ||
LocalDateTime expirationTime = LocalDateTime.now().plusMinutes(5); | ||
TemporaryPassword tempPassword = new TemporaryPassword(temporaryPassword, expirationTime); | ||
temporaryPasswordStore.put(email, tempPassword); | ||
|
||
// 4. 이메일 발송 | ||
sendPasswordEmail(email, temporaryPassword); | ||
|
||
// 5. 이메일 발송 성공 시 DB 업데이트 | ||
user.updatePassword(passwordEncoder.encode(temporaryPassword)); | ||
userRepository.save(user); | ||
|
||
} catch (CustomException e) { | ||
temporaryPasswordStore.remove(email); | ||
throw new CustomException(ErrorCode.EMAIL_SEND_FAILED); | ||
} | ||
} | ||
|
||
// 임시 비밀번호 생성 (10자리) | ||
private String generateTemporaryPassword() { | ||
StringBuilder password = new StringBuilder(); | ||
Random random = new Random(); | ||
|
||
// 대문자, 소문자, 숫자 포함 | ||
String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; | ||
|
||
for (int i = 0; i < 10; i++) { | ||
password.append(chars.charAt(random.nextInt(chars.length()))); | ||
} | ||
|
||
return password.toString(); | ||
} | ||
|
||
private void sendPasswordEmail(String email, String temporaryPassword) { | ||
try { | ||
// 이메일 메시지 생성 | ||
MimeMessage message = mailSender.createMimeMessage(); | ||
MimeMessageHelper helper = new MimeMessageHelper(message, true); | ||
|
||
helper.setTo(email); | ||
helper.setSubject("[새싹마켓] 임시 비밀번호 발급"); | ||
helper.setText(createEmailContent(temporaryPassword), true); | ||
|
||
// 이메일 발송 | ||
mailSender.send(message); | ||
} catch (MessagingException e) {// 이메일 발송 실패 시 임시 저장소에서 제거 | ||
temporaryPasswordStore.remove(email); | ||
throw new CustomException(ErrorCode.EMAIL_SEND_FAILED); // 이메일 발송 실패 | ||
} | ||
} | ||
|
||
private String createEmailContent(String temporaryPassword) { | ||
return String.format(""" | ||
<div style='text-align: center; margin: 30px;'> | ||
<h3> saessagMarket </h3> | ||
<h2>임시 비밀번호 발급</h2> | ||
<p> 본 메일은 saessagMarket 임시 비밀번호 발급을 위한 이메일입니다.</p> | ||
<p>아래의 임시 비밀번호로 로그인해 주세요.</p> | ||
<p>보안을 위해 로그인 후 비밀번호를 변경해 주세요.</p> | ||
<p style='font-size: 24px; font-weight: bold; margin: 20px;'>%s</p> | ||
</div> | ||
""", temporaryPassword); | ||
} | ||
|
||
// 5분마다 만료된 임시 비밀번호 정리 | ||
@Scheduled(fixedRate = 300000) | ||
public void cleanupExpiredPasswords() { | ||
temporaryPasswordStore.entrySet().removeIf(entry -> entry.getValue().isExpired()); | ||
} | ||
|
||
// 비밀번호 변경 | ||
@Transactional | ||
public void changePassword(PasswordChangeRequest request) { | ||
User user = userRepository.findByEmail(request.getEmail()) | ||
.orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND)); | ||
|
||
// 현재 비밀번호 확인 | ||
if (!passwordEncoder.matches(request.getCurrentPassword(), user.getPassword())) { | ||
throw new CustomException(ErrorCode.INVALID_PASSWORD); | ||
} | ||
|
||
// 새 비밀번호 암호화 및 업데이트 | ||
user.updatePassword(passwordEncoder.encode(request.getNewPassword())); | ||
userRepository.save(user); | ||
} | ||
|
||
} |
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
53 changes: 53 additions & 0 deletions
53
src/main/java/com/market/saessag/domain/photo/controller/ProfileImageController.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,53 @@ | ||
package com.market.saessag.domain.photo.controller; | ||
|
||
import com.market.saessag.domain.photo.service.S3Service; | ||
import com.market.saessag.global.exception.ErrorCode; | ||
import com.market.saessag.global.response.ApiResponse; | ||
import com.market.saessag.global.response.SuccessCode; | ||
import jakarta.servlet.http.HttpSession; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.web.bind.annotation.*; | ||
import org.springframework.web.multipart.MultipartFile; | ||
|
||
import java.io.IOException; | ||
import java.util.Map; | ||
|
||
@RestController | ||
@RequestMapping("/api/profile") | ||
@RequiredArgsConstructor | ||
public class ProfileImageController { | ||
private final S3Service s3Service; | ||
|
||
/* | ||
프로필 사진 업로드 | ||
사용자 관점에서는 프로필 사진 업로드와 수정이 동일한 방식으로 진행됨. | ||
따라서 하나의 Patch 메서드에서 동작함. | ||
*/ | ||
@PatchMapping("/upload-image") | ||
public ApiResponse<String> uploadProfileImage(@RequestParam("file") MultipartFile file, HttpSession session) { | ||
try { | ||
String email = (String) session.getAttribute("email"); | ||
if (email == null) { | ||
return ApiResponse.error(ErrorCode.UNAUTHORIZED); | ||
} | ||
|
||
String fileUrl = s3Service.uploadProfileImage(file, email); | ||
return ApiResponse.success(SuccessCode.UPLOAD_SUCCESS, fileUrl); | ||
} catch (IOException e) { | ||
return ApiResponse.error(ErrorCode.FILE_UPLOAD_ERROR); | ||
} | ||
} | ||
|
||
// 프로필 사진 조회 | ||
@GetMapping | ||
public ApiResponse<Map<String, String>> getProfileImageUrl(HttpSession session) { | ||
String email = (String) session.getAttribute("email"); | ||
if (email == null) { | ||
return ApiResponse.error(ErrorCode.UNAUTHORIZED); | ||
} | ||
|
||
Map<String, String> urls = s3Service.getProfileImageUrl(email); | ||
return ApiResponse.success(SuccessCode.OK, urls); | ||
} | ||
|
||
} |
16 changes: 16 additions & 0 deletions
16
src/main/java/com/market/saessag/domain/photo/dto/UserProfileImageResponse.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,16 @@ | ||
package com.market.saessag.domain.photo.dto; | ||
|
||
import com.market.saessag.domain.user.entity.User; | ||
import lombok.Getter; | ||
|
||
// 프로필 사진 조회 | ||
@Getter | ||
public class UserProfileImageResponse { | ||
private String profileUrl; | ||
|
||
public static UserProfileImageResponse from(User user) { | ||
UserProfileImageResponse response = new UserProfileImageResponse(); | ||
response.profileUrl = user.getProfileUrl(); | ||
return response; | ||
} | ||
} |
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.