
package com.sparta.seoulmate.controller;
import com.sparta.seoulmate.dto.PostResponseDto;
import com.sparta.seoulmate.security.UserDetailsImpl;
import com.sparta.seoulmate.service.FollowService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Slf4j
@Controller
@RequiredArgsConstructor
@RequestMapping("/api/follow")
public class FollowController {
private final FollowService followService;
@GetMapping
public String follow(){
return "follow";
} // follow.html을 반환
// 나를 팔로우 한 사람, 내가 팔로우 한 사람, 내가 팔로우 한 사람의 포스트 확인하기
@GetMapping("/posts")
@ResponseBody
public List<PostResponseDto> postList(@AuthenticationPrincipal UserDetailsImpl userDetail){
return followService.viewFollowingPostList(userDetail.getUser());
}
@PostMapping("/{userId}")
@ResponseBody
public void following(@AuthenticationPrincipal UserDetailsImpl userDetails, @PathVariable Long userId) {
followService.following(userDetails, userId);
}
@DeleteMapping("/{userId}")
@ResponseBody
public void unfollowing(@AuthenticationPrincipal UserDetailsImpl userDetails, @PathVariable Long userId) {
followService.unfollowing(userDetails, userId);
}
}package com.sparta.seoulmate.controller;
import com.sparta.seoulmate.dto.ApiResponseDto;
import com.sparta.seoulmate.dto.PostResponseDto;
import com.sparta.seoulmate.security.UserDetailsImpl;
import com.sparta.seoulmate.service.FollowService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Slf4j
@Controller
@RequiredArgsConstructor
@RequestMapping("/api/follow")
public class FollowController {
private final FollowService followService;
@GetMapping
public String follow(){
return "follow";
} // follow.html을 반환
// 나를 팔로우 한 사람, 내가 팔로우 한 사람, 내가 팔로우 한 사람의 포스트 확인하기
@GetMapping("/posts")
@ResponseBody
public ResponseEntity<List<PostResponseDto>> viewFollowingPostList(@AuthenticationPrincipal UserDetailsImpl userDetail){
List<PostResponseDto> postResponseDto = followService.viewFollowingPostList(userDetail.getUser());
return ResponseEntity.ok().body(postResponseDto);
}
@PostMapping("/{userId}")
@ResponseBody
public ResponseEntity<ApiResponseDto> following(@AuthenticationPrincipal UserDetailsImpl userDetails, @PathVariable Long userId) {
followService.following(userDetails, userId);
return ResponseEntity.ok().body(new ApiResponseDto(userId +" 님을 팔로우 했습니다", HttpStatus.OK.value()));
}
@DeleteMapping("/{userId}")
@ResponseBody
public ResponseEntity<ApiResponseDto> unfollowing(@AuthenticationPrincipal UserDetailsImpl userDetails, @PathVariable Long userId) {
followService.unfollowing(userDetails, userId);
return ResponseEntity.ok().body(new ApiResponseDto(userId +" 님을 팔로우 해제 했습니다", HttpStatus.OK.value()));
}
}@PostMapping("/{userId}")
@ResponseBody
public ResponseEntity<ApiResponseDto> following(@AuthenticationPrincipal UserDetailsImpl userDetails, @PathVariable Long userId) {
String nickname = followService.following(userDetails, userId);
return ResponseEntity.ok().body(new ApiResponseDto(nickname + " 님을 팔로우 했습니다", HttpStatus.OK.value()));
}
@DeleteMapping("/{userId}")
@ResponseBody
public ResponseEntity<ApiResponseDto> unfollowing(@AuthenticationPrincipal UserDetailsImpl userDetails, @PathVariable Long userId) {
String nickname = followService.unfollowing(userDetails, userId);
return ResponseEntity.ok().body(new ApiResponseDto(nickname + " 님을 팔로우 해제 했습니다", HttpStatus.OK.value()));
}String nickname = 추가public void following(@AuthenticationPrincipal UserDetailsImpl userDetails, Long id)
public void unfollowing(@AuthenticationPrincipal UserDetailsImpl userDetails, Long id)public String following(@AuthenticationPrincipal UserDetailsImpl userDetails, Long id)
public String unfollowing(@AuthenticationPrincipal UserDetailsImpl userDetails, Long id)return followingUser.getNickname();@Transactional
public String following(@AuthenticationPrincipal UserDetailsImpl userDetails, Long id) {
// 토큰 체크
User followerUser = userDetails.getUser();
if (followerUser == null) {
throw new IllegalArgumentException("로그인을 해주세요");
}
// 팔로우 할 유저 조회
User followingUser = userRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("해당 유저가 없습니다."));
// 본인을 팔로우 할 경우 예외 발생
if (followerUser.getId().equals(followingUser.getId())) {
throw new IllegalArgumentException("본인을 팔로우 할 수 없습니다.");
}
// 중복 팔로우 예외 발생
// followRepository 에서 두 개의 Id 값이 존재하는지 확인
if (followRepository.findByUserAndFollowingUser(followerUser, followingUser).isPresent()) {
throw new IllegalArgumentException("팔로우가 중복되었습니다.");
}
// followRepository DB 저장
followRepository.save(Follow.builder()
.user(followerUser)
.followingUser(followingUser)
.followingDateTime(LocalDateTime.now())
.build());
return followingUser.getNickname();
}
@Transactional
public String unfollowing(@AuthenticationPrincipal UserDetailsImpl userDetails, Long id) {
// 토큰 체크
User followerUser = userDetails.getUser();
if (followerUser == null) {
throw new IllegalArgumentException("로그인을 해주세요");
}
// 언팔로우 할 유저 조회
User followingUser = userRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("해당 유저가 없습니다."));
Follow follow = followRepository.findByUserAndFollowingUser(followerUser, followingUser)
.orElseThrow(() -> new IllegalArgumentException("팔로우 관계가 아닙니다"));
// followRepository DB 삭제(팔로우 해제)
followRepository.delete(follow);
return followingUser.getNickname();
}@GetMapping("/postList")
@ResponseBody
public ResponseEntity<List<PostResponseDto>> viewFollowingPostList(@AuthenticationPrincipal UserDetailsImpl userDetail){
List<PostResponseDto> postResponseDto = followService.viewFollowingPostList(userDetail.getUser());
return ResponseEntity.ok().body(postResponseDto);
}public List<PostResponseDto> viewFollowingPostList(User user) {
List<PostResponseDto> postList = new ArrayList<>();
List<Follow> follows = followRepository.findAllByUser(user);
List<User> userList = new ArrayList<>();
for (Follow follow : follows) {
userList.add(follow.getFollowingUser());
}
for (User foundUser : userList){
List<Post> foundPostList = postRepository.findAllByAuthor(foundUser);
postList.addAll(foundPostList.stream().map(PostResponseDto::of).toList());
}
return postList;
}@GetMapping("/list")
@ResponseBody
public ResponseEntity<List<User>> viewFollowingList(@AuthenticationPrincipal UserDetailsImpl userDetail){
List<User> followRepositories = followService.viewFollowingList(userDetail.getUser());
return ResponseEntity.ok().body(followRepositories);
}@GetMapping("/postList")
@ResponseBody
public ResponseEntity<List<PostResponseDto>> viewFollowingPostList(@AuthenticationPrincipal UserDetailsImpl userDetail){
List<PostResponseDto> postResponseDto = followService.viewFollowingPostList(userDetail.getUser());
return ResponseEntity.ok().body(postResponseDto);
}public List<User> viewFollowingList(User user) {
List<Follow> followList = followRepository.findAllByUser(user);
List<User> users = new ArrayList<>();
for (Follow follow : followList) {
users.add(follow.getFollowingUser());
}
return users;
}public List<PostResponseDto> viewFollowingPostList(User user) {
List<PostResponseDto> postList = new ArrayList<>();
List<User> userList = new ArrayList<>();
for (User foundUser : userList){
List<Post> foundPostList = postRepository.findAllByAuthor(foundUser);
postList.addAll(foundPostList.stream().map(PostResponseDto::of).toList());
}
return postList;
}isPresent()는 자바의 Optional 클래스의 메서드 중 하나입니다. Optional은 null-safe한 프로그래밍을 도와주는 클래스로, 객체의 값이 있을 수도 있고 없을 수도 있는 상황을 다룰 때 유용하게 사용됩니다.public List<User> viewFollowingList(User user) {
List<Follow> followList = followRepository.findAllByUser(user);
List<User> users = new ArrayList<>();
for (Follow follow : followList) {
users.add(follow.getFollowingUser());
}
return users;
}public List<FollowResponseDto> viewFollowingList(User user) {
List<Follow> followList = followRepository.findAllByUser(user);
List<FollowResponseDto> followingResponseList = new ArrayList<>();
for (Follow follow : followList) {
FollowResponseDto followResponseDto = FollowResponseDto.builder()
.id(follow.getFollowingUser().getId())
.nickname(follow.getFollowingUser().getNickname())
.image(follow.getFollowingUser().getImage())
.build();
followingResponseList.add(followResponseDto);
}
return followingResponseList;
}public List<FollowResponseDto> viewFollowerList(User user) {
List<Follow> followList = followRepository.findAllByFollowingUser(user);
List<FollowResponseDto> followerResponseList = new ArrayList<>();
for (Follow follow : followList) {
FollowResponseDto followResponseDto = FollowResponseDto.builder()
.id(follow.getUser().getId())
.nickname(follow.getUser().getNickname())
.image(follow.getUser().getImage())
.build();
followerResponseList.add(followResponseDto);
}
return followerResponseList;
}InvalidDefinitionException 오류는 주로 Jackson 라이브러리가 사용되는 곳에서 발생하는 문제로, 객체를 JSON으로 변환할 때 발생할 수 있습니다. 주로 역직렬화나 객체를 JSON으로 직렬화하는 과정에서 발생합니다.
가장 일반적인 원인 중 하나는 순환 참조(circular reference)입니다. 예를 들어, 엔티티 클래스에서 서로를 참조하는 관계가 있는 경우 문제가 될 수 있습니다. 이 경우 Jackson이 객체를 JSON으로 변환하려고 할 때 무한 루프에 빠지는 상황이 발생할 수 있습니다.
해결 방법으로는 다음과 같은 방법들이 있습니다:
@JsonIgnore 사용: 순환 참조가 발생하는 필드에 @JsonIgnore 애노테이션을 사용하여 해당 필드를 JSON 변환에서 제외할 수 있습니다. 이는 어느 부분을 제외할지 개발자가 직접 제어할 수 있는 방법입니다.@JsonManagedReference와 @JsonBackReference 사용: 엔티티 클래스에서 상호 참조하는 관계가 있는 경우, @JsonManagedReference와 @JsonBackReference 애노테이션을 사용하여 양방향 참조를 표시하고, 한 방향의 참조를 무시하도록 설정할 수 있습니다.@JsonIdentityInfo 사용: 순환 참조가 발생하는 엔티티 클래스에 @JsonIdentityInfo 애노테이션을 사용하여 각 객체에 고유한 식별자를 부여하여 참조를 해결할 수 있습니다.@JsonIgnore 사용:javaCopy code
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
@JsonIgnore
@OneToMany(mappedBy = "user")
private List<Follow> followers;
// Getter and Setter methods
}
위 코드에서 @JsonIgnore 애노테이션은 followers 필드를 JSON 변환에서 제외시키는 역할을 합니다. 2. DTO 사용: DTO 클래스를 만들어서 필요한 정보만 전달하는 방법입니다.javaCopy code
public class UserDTO {
private Long id;
private String username;
// Getter and Setter methods
}
그리고 서비스 레이어나 컨트롤러에서 엔티티를 DTO로 변환하여 사용합니다.javaCopy code
@Service
public class UserService {
// ...
public List<UserDTO> viewFollowingList(User user) {
List<Follow> followList = followRepository.findAllByUser(user);
List<UserDTO> users = new ArrayList<>();
for (Follow follow : followList) {
users.add(new UserDTO(follow.getFollowingUser().getId(), follow.getFollowingUser().getUsername()));
}
return users;
}
}
위 코드에서는 UserDTO 클래스를 사용하여 필요한 필드만을 가지고 있으며, viewFollowingList 메서드에서 엔티티를 DTO로 변환하여 반환합니다. 이렇게 하면 엔티티의 불필요한 정보가 노출되지 않습니다.대용량 트래픽은 웹사이트, 애플리케이션 또는 네트워크에서 처리하는 데이터의 양을 나타냅니다. 이는 동시에 많은 사용자가 웹사이트를 방문하거나 애플리케이션을 사용할 때 발생하는 요청과 응답의 양을 말합니다. 대용량 트래픽은 일반적으로 갑작스런 인기 상승이나 이벤트, 프로모션 등으로 인해 발생할 수 있습니다.
대용량 트래픽 상황에서 중요한 것은 서버, 네트워크, 데이터베이스 등의 시스템이 이를 처리하고 수용할 수 있는 능력이 있는지입니다. 대용량 트래픽은 서버 과부하, 성능 저하, 응답 지연 등과 같은 문제를 초래할 수 있으며, 이를 해결하기 위해서는 로드 밸런싱, 캐싱, 데이터베이스 최적화 등 다양한 기술과 전략을 활용해야 합니다.
이러한 상황에서 대비책을 마련하고 충분한 서버 용량과 효과적인 인프라 관리를 통해 대용량 트래픽을 처리할 수 있어야 합니다.
CommentLikeService - public CommentResponseDto onClickCommentLike
// commentLikeRepository DB저장
CommentLike sevedCommentLike = CommentLike.builder()
.comment(comment)
.user(user)
.build();
commentLikeRepository.save(sevedCommentLike);
CommentLikeResponseDto 클래스는 응답을 위한 데이터 전달 객체이기 때문에, 실제로 데이터베이스에 저장하는 로직을 그 안에 두는 것은 좋지 않은 설계입니다. 이 클래스는 주로 응답용으로 사용되며, 데이터베이스 저장과 관련된 로직은 서비스나 리포지토리 클래스에서 처리하는 것이 바람직합니다. 따라서 CommentLikeResponseDto 클래스 내부에 데이터베이스 저장과 관련된 로직을 넣는 대신, 해당 로직은 서비스 클래스 내부에 둘 것을 권장합니다. 여기서 더 나은 구조를 위해, 이러한 로직을 CommentLikeService나 유사한 클래스로 옮겨 놓는 것이 좋습니다. 이렇게 하면 데이터 저장과 관련된 기능을 서비스 계층에 따로 분리하여 관리할 수 있게 됩니다.좋아요 누른 본인이거나 admin 경우만 삭제 가능
// 좋아요 누른 본인이거나 admin 경우만 삭제 가능
if (!user.getId().equals(commentLike.getUser().getId()) || !user.getRole().equals(ProjConst.ADMIN_ROLE)) {
throw new IllegalStateException("권한이 없습니다.");
}
user)의 아이디가 댓글 좋아요를 누른 사용자의 아이디와 같지 않거나|| 연산자로 묶여 있어서, 위의 어느 하나라도 참이라면 조건은 충족user)의 역할이 관리자 역할(ProjConst.ADMIN_ROLE)이 아니라면IllegalStateException 예외가 발생하며 메서드의 호출자에게 "권한이 없습니다."라는 메시지를 전달하게 됩니다. 댓글 좋아요를 삭제하는 commentLikeRepository.delete(commentLike);는 실행되지 않습니다.참고
private boolean checkValidUser(User user, CommentLike commentLike) {
boolean result = !(user.getId().equals(commentLike.getUser().getId()))
&& !(user.getRole().equals(ProjConst.ADMIN_ROLE));
return result;
}
// 좋아요 누른 본인이거나 admin 경우만 취소 가능
if (this.checkValidUser(user, commentLike)) {
throw new IllegalStateException("좋아요 취소 권한이 없습니다.");
}
commentLikeRepository.delete(commentLike);
변경
// 좋아요 누른 본인이거나 admin 경우만 삭제 가능
if (user.getId().equals(commentLike.getUser().getId()) || user.getRole().equals(ProjConst.ADMIN_ROLE)) {
commentLikeRepository.delete(commentLike);
} else {
throw new IllegalStateException("좋아요 취소 권한이 없습니다.");
}
변경 2
if (user.getId().equals(commentLike.getUser().getId()) || user.getRole() == UserRoleEnum.ADMIN) {
commentLikeRepository.delete(commentLike);
} else {
throw new IllegalStateException("좋아요 취소 권한이 없습니다.");
}
자신의 댓글에 좋아요 할 수 없음 → clear

관리자가 좋아요를 삭제할 수 있는 기능은 어떻게 구현하느냐에 문제가 아니라 필요성이 없음!
→ 있어도 이상한 느낌?! 그래서 과감히 포기! 삭제~!
2번(유저)가 3번(유저)의 댓글에 좋아요 → clear


댓글에 좋아요 취소 → clear

로그인한 사용자가 댓글 작성자이면 수정, 삭제 가능 & 관리자 ADMIN이면 수정, 삭제 가능하게 하기 → or(||) 사용
@Transactional
public CommentResponseDto updateComment(Long commentId, User user, CommentRequestDto commentRequestDto) {
Comment comment = findComment(commentId);
if(!comment.getAuthor().getId().equals(user.getId())) {
throw new RejectedExecutionException();
}
comment.updateContent(commentRequestDto.getContent());
return CommentResponseDto.of(comment);
}// 로그인한 사용자가 댓글 작성자이거나 관리자인 경우
if (user.getId().equals(comment.getAuthor().getId()) || user.getRole() == UserRoleEnum.ADMIN) {
comment.updateContent(commentRequestDto.getContent());
} else {
throw new IllegalStateException("댓글 수정 권한이 없습니다.");
}// Comment 삭제
@Transactional
public void deleteComment(Long commentId, User user) {
Comment comment = findComment(commentId);
if(!comment.getAuthor().getId().equals(user.getId())) {
throw new RejectedExecutionException();
}
// or 연산자 써서 사용자 계정이 admin일 때 삭제할 수 있도록
commentRepository.delete(comment);
}// 로그인한 사용자가 댓글 작성자이거나 관리자인 경우
if (user.getId().equals(comment.getAuthor().getId()) || user.getRole() == UserRoleEnum.ADMIN) {
commentRepository.delete(comment);
} else {
throw new IllegalStateException("댓글 삭제 권한이 없습니다.");
}2번(어드민) 유저가 3번(유저) 유저의 댓글 수정하기 → clear


2번(어드민) 유저가 3번(유저) 유저의 댓글 삭제하기 → clear

2번(유저)가 본인(유저) 댓글 수정하기 → clear


2번(유저)가 본인(유저) 댓글 삭제하기 → clear

2번(유저가) 3번(유저) 댓글 수정하기 → 실패가 성공! clear


2번(유저가) 3번(유저) 댓글 삭제하기 → 실패가 성공! clear
