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
@@ -0,0 +1,37 @@
package com.back.domain.user.controller;

import com.back.domain.user.service.UserService;
import com.back.domain.user.service.UserAuthService;
import com.back.global.rsData.RsData;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/me/account")
@RequiredArgsConstructor
public class UserAccountController {

private final UserService userService;
private final UserAuthService userAuthService;

@DeleteMapping
@Operation(summary = "계정 비활성화(Soft Delete)", description = "DELETE /me/account: 사용자 상태를 DELETED로 전환하고 세션/토큰을 정리합니다.")
public RsData<Void> deactivate(
@AuthenticationPrincipal(expression = "id") Long userId,
HttpServletRequest request,
HttpServletResponse response
) {
userService.deactivateAccount(userId);

// 현재 세션 쿠키 및 리프레시토큰 제거
userAuthService.logout(request, response);

return RsData.of(200, "계정 비활성화(탈퇴)가 완료되었습니다.");
}
}
23 changes: 22 additions & 1 deletion src/main/java/com/back/domain/user/entity/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.back.domain.post.post.entity.PostLike;
import jakarta.persistence.*;
import com.back.domain.user.enums.UserStatus;
import lombok.*;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
Expand Down Expand Up @@ -56,6 +57,13 @@ public class User {
@Column(nullable = false)
private boolean isFirstLogin = true;

@Builder.Default
@Enumerated(EnumType.STRING)
@Column(nullable = false, length = 20)
private UserStatus status = UserStatus.ACTIVE;

private LocalDateTime deletedAt;

// 양방향 매핑을 위한 필드
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
private List<PostLike> postLikes = new ArrayList<>();
Expand All @@ -82,4 +90,17 @@ public Collection<? extends GrantedAuthority> getAuthorities() {
.map(auth -> new SimpleGrantedAuthority("ROLE_" + auth))
.toList();
}
}

public boolean isDeleted() {
return this.status == UserStatus.DELETED;
}

public void markDeleted(String anonymizedNickname) {
this.status = UserStatus.DELETED;
this.deletedAt = LocalDateTime.now();
this.nickname = anonymizedNickname;
// 민감정보 최소화: 재가입 허용을 위해 이메일/OAuth 식별자 제거
this.email = null;
this.oauthId = null;
}
}
6 changes: 6 additions & 0 deletions src/main/java/com/back/domain/user/enums/UserStatus.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.back.domain.user.enums;

public enum UserStatus {
ACTIVE,
DELETED
}
18 changes: 17 additions & 1 deletion src/main/java/com/back/domain/user/service/UserService.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package com.back.domain.user.service;

import com.back.domain.user.entity.User;
import com.back.domain.user.enums.UserStatus;
import com.back.domain.user.repository.UserRepository;
import com.back.global.exception.ServiceException;
import com.back.global.jwt.refreshToken.service.RefreshTokenService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -11,14 +14,27 @@
public class UserService {

private final UserRepository userRepository;
private final RefreshTokenService refreshTokenService;

@Transactional(readOnly = true)
public User findById(Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("User not found. id=" + id));
}

@Transactional
public void deactivateAccount(Long id) {
User user = userRepository.findById(id)
.orElseThrow(() -> new ServiceException(404, "사용자를 찾을 수 없습니다."));

if (user.getStatus() == UserStatus.DELETED) {
throw new ServiceException(409, "이미 탈퇴한 사용자입니다.");
}

user.markDeleted("탈퇴한 사용자");
userRepository.save(user);

}
// 모든 세션(리프레시 토큰) 폐기
refreshTokenService.revokeAllForUser(id);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@ public void revokeToken(String token) {
refreshTokenRepository.deleteByToken(token);
}

// 사용자 전체 세션(리프레시 토큰) 폐기
@Transactional
public void revokeAllForUser(Long userId) {
refreshTokenRepository.deleteByUserId(userId);
}

//문자열 난수 조합
private String generateSecureToken() {
byte[] randomBytes = new byte[32];
Expand Down