Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,8 @@
import io.github.petty.users.jwt.JWTUtil;
import io.github.petty.users.service.EmailService;
import io.github.petty.users.service.RefreshTokenService;
import io.github.petty.users.service.UserService;
import io.github.petty.users.util.CookieUtils;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
Expand All @@ -26,12 +25,14 @@
public class UsersApiController {

private final JWTUtil jwtUtil;
private final UserService userService;
private final EmailService emailService;
private final RefreshTokenService refreshTokenService;


public UsersApiController(JWTUtil jwtUtil, EmailService emailService, RefreshTokenService refreshTokenService) {
public UsersApiController(JWTUtil jwtUtil, EmailService emailService, RefreshTokenService refreshTokenService, UserService userService) {
this.jwtUtil = jwtUtil;
this.userService = userService;
this.emailService = emailService;
this.refreshTokenService = refreshTokenService;
}
Expand Down Expand Up @@ -102,4 +103,27 @@ public ResponseEntity<?> refreshToken(
.body(Map.of("error", e.getMessage()));
}
}

// 닉네임 중복 검사 API 추가
@GetMapping("/check-displayname")
public ResponseEntity<Map<String, Object>> checkDisplayName(
@RequestParam String displayName) {

Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth == null || !auth.isAuthenticated() || auth instanceof AnonymousAuthenticationToken) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}

// 현재 사용자 ID 가져오기
UUID currentUserId = userService.getCurrentUserId(auth.getPrincipal());

// 한 번의 서비스 호출로 모든 정보 가져오기
Map<String, Object> result = userService.checkDisplayName(currentUserId, displayName);

Map<String, Object> response = new HashMap<>();
response.put("available", result.get("available"));
response.put("message", result.get("message"));

return ResponseEntity.ok(response);
}
}
47 changes: 27 additions & 20 deletions src/main/java/io/github/petty/users/controller/UsersController.java
Original file line number Diff line number Diff line change
@@ -1,22 +1,16 @@
package io.github.petty.users.controller;

import io.github.petty.users.dto.CustomUserDetails;
import io.github.petty.users.dto.JoinDTO;
import io.github.petty.users.dto.UserProfileEditDTO;
import io.github.petty.users.entity.Users;
import io.github.petty.users.service.JoinService;
import io.github.petty.users.service.UserService;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.springframework.security.core.Authentication;

Expand Down Expand Up @@ -63,21 +57,27 @@ public String loginForm() {
}

@GetMapping("/profile/edit")
public String editProfileForm(Model model) {
public String editProfileForm(Model model, RedirectAttributes redirectAttributes) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

if (authentication == null || !authentication.isAuthenticated()
|| authentication instanceof AnonymousAuthenticationToken) {
return "redirect:/login";
}

Object principal = authentication.getPrincipal();
UUID currentUserId = userService.getCurrentUserId(principal);
UserProfileEditDTO userProfile = userService.getUserById(currentUserId);
try {
// 현재 사용자 ID
UUID currentUserId = userService.getCurrentUserId(authentication.getPrincipal());

UserProfileEditDTO userProfile = userService.getUserById(currentUserId);

model.addAttribute("userProfile", userProfile);
model.addAttribute("userProfile", userProfile);
return "profile_edit";

return "profile_edit";
} catch (Exception e) {
redirectAttributes.addFlashAttribute("errorMessage", "프로필 정보를 가져오는 중 오류가 발생했습니다.");
return "redirect:/";
}
}

@PostMapping("/profile/update")
Expand All @@ -90,19 +90,26 @@ public String updateProfile(@ModelAttribute UserProfileEditDTO userProfileEditDT
return "redirect:/login";
}

Object principal = authentication.getPrincipal();
UUID currentUserId = userService.getCurrentUserId(principal);

try {
// 사용자 정보 수정
// 현재 사용자 ID
UUID currentUserId = userService.getCurrentUserId(authentication.getPrincipal());

userService.updateUserProfile(currentUserId, userProfileEditDTO);

redirectAttributes.addFlashAttribute("successMessage", "프로필이 성공적으로 수정되었습니다.");
return "redirect:/";

} catch (Exception e) {
redirectAttributes.addFlashAttribute("errorMessage", "프로필 수정 중 오류가 발생했습니다: " + e.getMessage());
}
// 모든 에러를 하나로 처리
String errorMessage = e.getMessage();
if (errorMessage == null || errorMessage.trim().isEmpty()) {
errorMessage = "프로필 수정 중 오류가 발생했습니다.";
}

// 수정 완료 후 메인 페이지로
return "redirect:/";
redirectAttributes.addFlashAttribute("errorMessage", errorMessage);
redirectAttributes.addFlashAttribute("userProfile", userProfileEditDTO); // 입력값 유지
return "redirect:/profile/edit";
}
}
}

Expand Down
3 changes: 1 addition & 2 deletions src/main/java/io/github/petty/users/dto/JoinDTO.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
public class JoinDTO {
private String username;
private String password;
private String displayName;
private String name;
private String phone;

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
@NoArgsConstructor
@AllArgsConstructor
public class UserProfileEditDTO {
private String displayName; // 사용자 이름
private String name; // 사용자 이름
private String displayName; // 닉네임
private String phone; // 전화번호
}
5 changes: 4 additions & 1 deletion src/main/java/io/github/petty/users/entity/Users.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,10 @@ public class Users {
private String password;

@Column(nullable = false, length = 50)
private String displayName;
private String name;

@Column(nullable = false, length = 50, unique = true)
private String displayName; // 닉네임 (자동 생성)

@Column(length = 20)
private String phone;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import io.github.petty.users.Role;
import io.github.petty.users.entity.Users;
import io.github.petty.users.repository.UsersRepository;
import io.github.petty.users.util.DisplayNameGenerator;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
Expand All @@ -21,6 +22,7 @@
public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {

private final UsersRepository usersRepository;
private final DisplayNameGenerator displayNameGenerator;

@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
Expand Down Expand Up @@ -87,27 +89,30 @@ private Users saveOrUpdate(String email, String provider, String providerId, Map
user.setPassword(UUID.randomUUID().toString()); // 임의의 패스워드 설정
user.setRole(Role.ROLE_USER.name());

// Provider 사용자명을 displayName으로 사용
// DisplayNameGenerator 유니크 displayName 생성
String uniqueDisplayName = displayNameGenerator.generateUniqueDisplayName();
user.setDisplayName(uniqueDisplayName);

// Provider 사용자명을 name으로 사용
if ("github".equals(provider)) {
if (attributes.containsKey("name") && attributes.get("name") != null) {
user.setDisplayName((String) attributes.get("name"));
user.setName((String) attributes.get("name"));
} else if (attributes.containsKey("login")) {
user.setDisplayName((String) attributes.get("login"));
user.setName((String) attributes.get("login"));
} else {
user.setDisplayName(email.split("@")[0]);
user.setName(email.split("@")[0]);
}
} else if ("kakao".equals(provider)) {
Map<String, Object> properties = (Map<String, Object>) attributes.get("properties");
if (properties != null && properties.containsKey("nickname")) {
user.setDisplayName((String) properties.get("nickname"));
user.setName((String) properties.get("nickname"));
} else {
user.setDisplayName(email.split("@")[0]);
user.setName(email.split("@")[0]);
}
}
return usersRepository.save(user);
}

// 기존 사용자 업데이트 로직 (필요한 경우)
return user;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ public interface UsersRepository extends JpaRepository<Users, UUID> {
Boolean existsByUsername(String username);

Users findByUsername(String username);

Boolean existsByDisplayName(String displayName);
}
22 changes: 11 additions & 11 deletions src/main/java/io/github/petty/users/service/EmailService.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;

import java.io.UnsupportedEncodingException;
import java.time.LocalDateTime;
import java.util.Random;

Expand All @@ -35,7 +36,7 @@ public boolean sendVerificationCode(String email) {
try {
sendEmail(email, code);
return true;
} catch (MessagingException e) {
} catch (MessagingException | UnsupportedEncodingException e) {
log.error("이메일 전송 실패: {}", e.getMessage());
return false;
}
Expand All @@ -46,39 +47,38 @@ private String generateCode() {
return String.format("%06d", random.nextInt(1000000)); // 000000 ~ 999999
}

private void sendEmail(String toEmail, String code) throws MessagingException {
private void sendEmail(String toEmail, String code) throws MessagingException, UnsupportedEncodingException {
MimeMessage mimeMessage = javaMailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true, "utf-8");

helper.setFrom("krpetty54@gmail.com"); // 보내는 사람 이메일 (본인의 실제 이메일 주소로 변경)
helper.setFrom("krpetty54@gmail.com", "Petty Team");
helper.setTo(toEmail);
helper.setSubject("[Petty] 이메일 인증 코드입니다.");
helper.setSubject("Petty 인증 코드");

// HTML 형식의 이메일 본문
String htmlContent = "<div style='background-color:#ffffff;padding:40px;border-radius:8px;text-align:center;max-width:400px;width:100%;margin:0 auto'>" +
"<a href='https://github.com/PETTY-HUB' style='display:block;margin-bottom:20px;text-decoration:none;color:#000000;width:5.5rem;font-weight:bold' target='_blank'>" +
"<div style='display:block;margin-bottom:20px;text-decoration:none;color:#000000;width:5.5rem;font-weight:bold' target='_blank'>" +
"<h2 style='color:#f39c12'>Petty</h2>" +
"</a>" +
"</div>" +
"<div style='text-align:center'>" +
"<p style='color:#212529;font-size:18px;font-weight:600'>인증 번호</p>" +
"</div>" +
"<div style='background-color:#f1f3f5;padding:20px;border-radius:8px;margin-bottom:30px;text-align:center'>" +
"<p style='font-size:35px;font-weight:bold;line-height:1.5;color:#f39c12;margin:10px 0 0'>" + code + "</p>" +
"</div>" +
"<p style='display:inline-block;padding:15px 0;color:#888888;text-decoration:none;border-radius:8px;font-size:16px;word-break:keep-all;text-align:left'>" +
"Petty의 더 많은 서비스를 이용하려면 이메일 인증이 필요해요.<br>" +
"인증번호를 입력하고 인증을 완료해 주세요!<br>" +
"(스팸함에 있을 수도 있으니 한 번 확인 부탁드려요!)" +
"위 코드를 입력하여 이메일 인증을 완료해주세요.<br>" +
"이 코드는 5분간 유효합니다.<br>" +
"</p>" +
"<div style='margin-top:20px; font-size:14px; color:#888;'>" +
" 메일은 5분간 유효합니다." +
" 메일은 발신전용입니다." +
"</div>" +
"</div>";

helper.setText(htmlContent, true);

javaMailSender.send(mimeMessage);
log.info("이메일 ({})로 인증 코드 ({})를 성공적으로 전송했습니다.", toEmail, code);
log.info("이메일 ({})로 인증 코드를 성공적으로 전송했습니다.", toEmail);
}

// 인증 코드 확인
Expand Down
10 changes: 8 additions & 2 deletions src/main/java/io/github/petty/users/service/JoinService.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import io.github.petty.users.dto.JoinDTO;
import io.github.petty.users.entity.Users;
import io.github.petty.users.repository.UsersRepository;
import io.github.petty.users.util.DisplayNameGenerator;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
Expand All @@ -14,11 +15,12 @@ public class JoinService {

private final UsersRepository userRepository;
private final BCryptPasswordEncoder bCryptPasswordEncoder;
private final DisplayNameGenerator displayNameGenerator;

public boolean joinProcess(JoinDTO joinDTO) {
String username = joinDTO.getUsername();
String password = joinDTO.getPassword();
String displayName = joinDTO.getDisplayName();
String name = joinDTO.getName();
String phone = joinDTO.getPhone();
String encodedPassword = bCryptPasswordEncoder.encode(password);

Expand All @@ -30,10 +32,14 @@ public boolean joinProcess(JoinDTO joinDTO) {
Users users = new Users();
users.setUsername(username);
users.setPassword(encodedPassword);
users.setDisplayName(displayName);
users.setName(name);
users.setPhone(phone);
users.setRole(Role.ROLE_USER.name());
users.setProvider("local");

String uniqueDisplayName = displayNameGenerator.generateUniqueDisplayName();
users.setDisplayName(uniqueDisplayName);

userRepository.save(users);

return true;
Expand Down
Loading