Skip to content

Commit

Permalink
feat: sseEmitter 기능구현
Browse files Browse the repository at this point in the history
  • Loading branch information
mixedtape committed Jan 21, 2024
1 parent 067c150 commit b0325b7
Show file tree
Hide file tree
Showing 10 changed files with 148 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,26 @@

import com.HHive.hhive.domain.notification.dto.NotificationRequestDTO;
import com.HHive.hhive.domain.notification.dto.NotificationResponseDTO;
import com.HHive.hhive.domain.notification.entity.CustomSseEmitter;
import com.HHive.hhive.domain.notification.service.NotificationService;
import com.HHive.hhive.domain.user.UserDetailsImpl;
import com.HHive.hhive.global.common.CommonResponse;
import java.io.IOException;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

@RestController
@RequestMapping("/api/notifications")
Expand All @@ -29,12 +34,24 @@ public class NotificationController {
public ResponseEntity<CommonResponse> sendNotification(
@RequestBody NotificationRequestDTO notificationRequestDTO)
{
NotificationResponseDTO response = notificationService.sendNotification(
List<CustomSseEmitter> response = notificationService.sendNotification(
notificationRequestDTO);

return ResponseEntity.status(HttpStatus.CREATED)
.body(CommonResponse.of(HttpStatus.CREATED.value(), "메시지 전송 성공", response));
}

@GetMapping(value = "/get" )
public ResponseEntity<SseEmitter> handleNotifications(
@AuthenticationPrincipal UserDetailsImpl userDetails
) {
CustomSseEmitter emitter = notificationService.createUserEmitter(userDetails.getUser().getId());

notificationService.addSseEmitter(emitter);

return ResponseEntity.ok(emitter);
}

@GetMapping("/{userId}")
public ResponseEntity<CommonResponse> getNotificationsByUserId(
@PathVariable(name = "userId") Long userId
Expand Down Expand Up @@ -64,6 +81,14 @@ public ResponseEntity<CommonResponse> deleteNotification(
return ResponseEntity.ok()
.body(CommonResponse.of(HttpStatus.OK.value(), "알림 개수 조회 완료", null));
}
@PatchMapping("/read")
public ResponseEntity<CommonResponse> readNotification(
@AuthenticationPrincipal UserDetailsImpl userDetails
){
notificationService.readNotification(userDetails.getUser().getId());
return ResponseEntity.ok()
.body(CommonResponse.of(HttpStatus.OK.value(), "알림 열람 완료", null));
}


}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@ public class NotificationResponseDTO {

private Long notificationId;
private String message;
private String status;
private String groupName;

public static NotificationResponseDTO fromEntity(Notification notification) {
return new NotificationResponseDTO(
notification.getId(),
notification.getMessage(),
notification.getStatus()
notification.getGroupName()
);


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.HHive.hhive.domain.notification.entity;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
@Getter
@Setter
@AllArgsConstructor
public class CustomSseEmitter extends SseEmitter {
private Long userId;
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,19 @@ public class Notification extends BaseTimeEntity {
@Column(name = "message")
private String message;

@Column(name = "status")
private String status;
@Column(name = "group_name")
private String groupName;

@Builder
public Notification(String message, String status) {
public Notification(String message) {
this.message = message;
this.status = "unread";
this.groupName = "group";
}

public void setGroupName(String groupName){
this.groupName=groupName;
}



}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.HHive.hhive.domain.hive.repository.HiveRepository;
import com.HHive.hhive.domain.notification.dto.NotificationRequestDTO;
import com.HHive.hhive.domain.notification.dto.NotificationResponseDTO;
import com.HHive.hhive.domain.notification.entity.CustomSseEmitter;
import com.HHive.hhive.domain.notification.entity.Notification;
import com.HHive.hhive.domain.notification.repository.NotificationRepository;
import com.HHive.hhive.domain.party.repository.PartyRepository;
Expand All @@ -12,60 +13,89 @@
import com.HHive.hhive.domain.relationship.notificationuser.repository.UserNotificationRepository;
import com.HHive.hhive.domain.relationship.partyuser.entity.PartyUser;
import com.HHive.hhive.domain.relationship.partyuser.repository.PartyUserRepository;
import com.HHive.hhive.domain.user.entity.User;
import com.HHive.hhive.domain.user.repository.UserRepository;
import com.HHive.hhive.global.common.CommonResponse;
import com.HHive.hhive.global.exception.hive.NotFoundHiveException;
import com.HHive.hhive.global.exception.notification.EmitterNotFoundException;
import com.HHive.hhive.global.exception.notification.NotificationNotFoundException;
import com.HHive.hhive.global.exception.party.PartyNotFoundException;
import com.HHive.hhive.global.exception.user.UserNotFoundException;
import jakarta.transaction.Transactional;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

@Service
@Getter
@RequiredArgsConstructor
public class NotificationService {

private final NotificationRepository notificationRepository;
private final PartyUserRepository partyUserRepository;
private final HiveUserRepository hiveUserRepository;
private final UserNotificationRepository userNotificationRepository;
private final UserRepository userRepository;
private final PartyRepository partyRepository;
private final HiveRepository hiveRepository;


private final List<CustomSseEmitter> emitters = new ArrayList<>();


public void addSseEmitter(CustomSseEmitter emitter) {
emitters.add(emitter);
emitter.onCompletion(() -> {
emitters.remove(emitter);
});
emitter.onTimeout(() -> {
emitters.remove(emitter);
});
System.out.println("SseEmitter가 추가 되었습니다" + emitter.getUserId());
}

public void sendNotificationToClients(CustomSseEmitter emitter, Notification notification) {
try {
emitter.send(notification);
System.out.println("알림 전송 성공");
} catch (Exception e) {
System.err.println("전송 실패");
}
}

@Transactional
public NotificationResponseDTO sendNotification(NotificationRequestDTO notificationRequestDTO) {
public List<CustomSseEmitter> sendNotification(NotificationRequestDTO notificationRequestDTO) {

String type = notificationRequestDTO.getType();
Notification notification = Notification.builder()
.message(notificationRequestDTO.getMessage())
.build();
notificationRepository.save(notification);

if (type.equals("party")) {
partyRepository.findById(notificationRequestDTO.getId())
.orElseThrow(PartyNotFoundException::new);
notification.setGroupName(partyRepository.findById(notificationRequestDTO.getId())
.orElseThrow(PartyNotFoundException::new).getTitle());
notificationRepository.save(notification);
List<PartyUser> partyUserList = partyUserRepository.findUsersByPartyId(
notificationRequestDTO.getId());
sendNotificationToUserListParty(partyUserList, notification);
} else if (type.equals("hive")) {
hiveRepository.findById(notificationRequestDTO.getId())
.orElseThrow(NotFoundHiveException::new);
notification.setGroupName(hiveRepository.findById(notificationRequestDTO.getId())
.orElseThrow(NotFoundHiveException::new).getTitle());
notificationRepository.save(notification);
List<HiveUser> hiveUserList = hiveUserRepository.findHiveUsersByHiveId(
notificationRequestDTO.getId());
sendNotificationToUserListHive(hiveUserList, notification);
}

return NotificationResponseDTO.fromEntity(notification);
return emitters;
}


public CustomSseEmitter createUserEmitter(Long userId) {
return new CustomSseEmitter(userId);
}
public List<NotificationResponseDTO> getNotificationsByUserId(Long userId) {
List<Notification> notifications = userNotificationRepository
.findNotificationsByUserIdAndStatus(userId, "unread");
.findNotificationsByUserId(userId);

if (notifications.isEmpty()) {

Expand All @@ -91,18 +121,23 @@ public Long showUnreadNotificationCountForUser(Long userId) {
return userNotificationRepository
.countUnreadNotificationsByUserId(userId, "unread");
}
public void readNotification(Long userId){
List<UserNotification> userNotifications =userNotificationRepository.findByUserIdAndNotificationId(userId);
for (UserNotification userNotification : userNotifications) {
userNotification.changeStatus();
}
userNotificationRepository.saveAll(userNotifications);
}

private void sendNotificationToUserListParty(List<PartyUser> partyUserList,
Notification notification) {
List<Long> userIdList = partyUserList.stream()
.map(partyUser -> partyUser.getUser().getId())
.toList();

for (Long userId : userIdList) {
User user = userRepository.findById(userId)
.orElseThrow(UserNotFoundException::new);
for (PartyUser partyUser : partyUserList) {
Long userId = partyUser.getUser().getId();
CustomSseEmitter emitter = findSseEmitterByUserId(userId);
sendNotificationToClients(emitter,notification);
UserNotification userNotification = UserNotification.builder()
.user(user)
.user(partyUser.getUser())
.notification(notification)
.build();
userNotificationRepository.save(userNotification);
Expand All @@ -111,19 +146,27 @@ private void sendNotificationToUserListParty(List<PartyUser> partyUserList,

private void sendNotificationToUserListHive(List<HiveUser> hiveUserList,
Notification notification) {
List<Long> userIdList = hiveUserList.stream()
.map(hiveUser -> hiveUser.getUser().getId())
.toList();

for (Long userId : userIdList) {
User user = userRepository.findById(userId)
.orElseThrow(UserNotFoundException::new);
for (HiveUser hiveUser : hiveUserList) {
Long userId = hiveUser.getUser().getId();
CustomSseEmitter emitter = findSseEmitterByUserId(userId);
if(emitter.getUserId().equals(userId)) {
sendNotificationToClients(emitter, notification);
}
UserNotification userNotification = UserNotification.builder()
.user(user)
.user(hiveUser.getUser())
.notification(notification)
.build();
userNotificationRepository.save(userNotification);


}
}

private CustomSseEmitter findSseEmitterByUserId(Long userId) {
return emitters.stream()
.filter(emitter -> userId.equals(emitter.getUserId()))
.findFirst()
.orElse(createUserEmitter(0L));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.HHive.hhive.domain.notification.entity.Notification;
import com.HHive.hhive.domain.user.entity.User;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.EmbeddedId;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
Expand Down Expand Up @@ -31,6 +32,9 @@ public class UserNotification {
@MapsId("notificationId")
private Notification notification;

@Column(name = "status")
private String status;

@Builder
public UserNotification(User user,Notification notification){
this.user=user;
Expand All @@ -39,6 +43,10 @@ public UserNotification(User user,Notification notification){
.userId(user.getId())
.notificationId(notification.getId())
.build();
this.status="unread";
}
public void changeStatus(){
this.status="read";
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.HHive.hhive.domain.relationship.notificationuser.entity.UserNotification;
import com.HHive.hhive.domain.relationship.notificationuser.entity.UserNotificationPK;
import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
Expand All @@ -15,17 +16,18 @@ public interface UserNotificationRepository extends JpaRepository<UserNotificati

List<UserNotification> findByUserId(Long userId);

@Query("SELECT un.notification FROM UserNotification un WHERE un.user.id = :userId AND un.notification.status = :status")
List<Notification> findNotificationsByUserIdAndStatus(@Param("userId") Long userId,
@Param("status") String status);
@Query("SELECT un.notification FROM UserNotification un WHERE un.user.id = :userId")
List<Notification> findNotificationsByUserId(@Param("userId") Long userId);

@Query("SELECT COUNT(un.notification) FROM UserNotification un WHERE un.user.id = :userId AND un.notification.status = :status")
@Query("SELECT COUNT(un.notification) FROM UserNotification un WHERE un.user.id = :userId AND un.status = :status")
Long countUnreadNotificationsByUserId(@Param("userId") Long userId,
@Param("status") String status);

@Modifying
@Query("DELETE FROM UserNotification un WHERE un.notification.id = :notificationId")
void deleteByNotificationId(@Param("notificationId") Long notificationId);

@Query("SELECT un FROM UserNotification un WHERE un.userNotificationPK.userId = :userId")
List<UserNotification> findByUserIdAndNotificationId(@Param("userId") Long userId);
}

Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti
.requestMatchers(PathRequest.toStaticResources().atCommonLocations())
.permitAll() // resource 접근 허용 설정
.requestMatchers("/api/users/**").permitAll() // '/api/users/'로 시작하는 요청 모두 접근 허가
.requestMatchers("/api/notifications/**").permitAll()
.anyRequest().authenticated() // 그 외 모든 요청 인증처리
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public enum ErrorCode {

//Notification
NOT_FOUND_NOTIFICATION_EXCEPTION(401,"알림이 존재하지 않습니다"),
NOT_FOUND_EMITTER_EXCEPTION(401,"구독된 알림이 없습니다"),

//HiveUser
HIVE_USER_NOT_FOUND_EXCEPTION(403, "요청하신 하이브에 가입하지 않은 사용자입니다."),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.HHive.hhive.global.exception.notification;

import com.HHive.hhive.global.exception.common.CustomException;
import com.HHive.hhive.global.exception.common.ErrorCode;

public class EmitterNotFoundException extends CustomException {
public EmitterNotFoundException() {
super(ErrorCode.NOT_FOUND_EMITTER_EXCEPTION);
}

}

0 comments on commit b0325b7

Please sign in to comment.