Skip to content

Commit 10ee9ce

Browse files
authored
RINGUS-84 feat: 멘토링 신청 알림 전송 시스템 구현 (#73)
* RINGUS-74 feat: Notification Domain 구현 * RINGUS-84 feat: 멘토링 신청 알림 기능 구현
1 parent be67c01 commit 10ee9ce

File tree

16 files changed

+355
-5
lines changed

16 files changed

+355
-5
lines changed

docker-compose.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ services:
1111
- redis
1212
ports:
1313
- ${SPRING_PORT}:${SPRING_PORT}
14-
restart: always
14+
restart: "always"
1515

1616
database:
1717
image: mysql:8.4.4
@@ -27,7 +27,7 @@ services:
2727
- ${DB_PORT}
2828
ports:
2929
- ${DB_PORT}:${DB_PORT}
30-
restart: no
30+
restart: "no"
3131
volumes:
3232
- ring-us-database:/var/lib/mysql
3333

@@ -39,9 +39,9 @@ services:
3939
- ${REDIS_PORT}
4040
ports:
4141
- ${REDIS_PORT}:${REDIS_PORT}
42-
restart: always
42+
restart: "always"
4343
volumes:
4444
- ring-us-redis:/data
4545
volumes:
4646
ring-us-database:
47-
ring-us-redis:
47+
ring-us-redis:

src/main/java/es/princip/ringus/application/mentoring/MentoringService.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package es.princip.ringus.application.mentoring;
22

3+
import es.princip.ringus.application.notification.service.NotificationService;
34
import es.princip.ringus.domain.exception.MenteeErrorCode;
45
import es.princip.ringus.domain.exception.MentorErrorCode;
56
import es.princip.ringus.domain.exception.MentoringErrorCode;
@@ -11,6 +12,7 @@
1112
import es.princip.ringus.domain.mentoring.MentoringRepository;
1213
import es.princip.ringus.domain.mentoring.MentoringStatus;
1314
import es.princip.ringus.global.exception.CustomRuntimeException;
15+
import es.princip.ringus.global.sender.dto.MentoringRequestMessage;
1416
import es.princip.ringus.presentation.mentoring.dto.*;
1517
import lombok.RequiredArgsConstructor;
1618
import org.springframework.stereotype.Service;
@@ -24,6 +26,7 @@ public class MentoringService {
2426
private final MentorRepository mentorRepository;
2527
private final MenteeRepository menteeRepository;
2628

29+
private final NotificationService notificationService;
2730
/**
2831
* 멘토링 신청 생성
2932
*/
@@ -39,11 +42,13 @@ public MentoringResponse createMentoring(CreateMentoringRequest request, Long me
3942
request.applyTimes(),
4043
request.mentoringMessage(),
4144
mentor,
42-
mentee);
45+
mentee
46+
);
4347

4448
mentee.addMentoring(mentoring);
4549
mentor.addMentoring(mentoring);
4650

51+
notificationService.notify(MentoringRequestMessage.from(mentee, mentor, mentoring));
4752
return MentoringResponse.from(mentoringRepository.save(mentoring));
4853
}
4954

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package es.princip.ringus.application.notification.service;
2+
3+
import es.princip.ringus.domain.notification.Notification;
4+
import es.princip.ringus.domain.notification.NotificationRepository;
5+
import es.princip.ringus.global.factory.NotificationMessageFactory;
6+
import es.princip.ringus.global.sender.NotificationSender;
7+
import es.princip.ringus.global.sender.dto.MentoringRequestMessage;
8+
import lombok.RequiredArgsConstructor;
9+
import org.springframework.stereotype.Service;
10+
import org.springframework.transaction.annotation.Transactional;
11+
12+
@Service
13+
@Transactional(readOnly = true)
14+
@RequiredArgsConstructor
15+
public class NotificationService {
16+
17+
private final NotificationSender notificationSender;
18+
private final NotificationMessageFactory notificationMessageFactory;
19+
private final NotificationRepository notificationRepository;
20+
21+
public void notify(MentoringRequestMessage request) {
22+
Notification notification = notificationMessageFactory.mentoringRequestMessage(request);
23+
notificationRepository.save(notification);
24+
notificationSender.send(notification);
25+
}
26+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package es.princip.ringus.domain.notification;
2+
3+
import es.princip.ringus.domain.base.BaseTimeEntity;
4+
import jakarta.persistence.*;
5+
import lombok.AccessLevel;
6+
import lombok.Builder;
7+
import lombok.Getter;
8+
import lombok.NoArgsConstructor;
9+
10+
@Getter
11+
@Entity
12+
@Table(name = "notification")
13+
@NoArgsConstructor(access = AccessLevel.PROTECTED)
14+
public class Notification extends BaseTimeEntity {
15+
16+
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
17+
@Column(name = "notification_id")
18+
private Long id;
19+
20+
@Column(name = "title", nullable = false, length = 255)
21+
private String title;
22+
23+
@Column(name = "content", nullable = false, length = 500)
24+
private String content;
25+
26+
@Enumerated(EnumType.STRING)
27+
@Column(name = "type", nullable = false)
28+
private NotificationType type;
29+
30+
@Column(name = "is_read", nullable = false)
31+
private boolean isRead = false;
32+
33+
@Column(name = "sender_id", nullable = false)
34+
private Long senderId;
35+
36+
@Column(name = "receiver_id", nullable = false)
37+
private Long receiverId;
38+
39+
@Builder
40+
private Notification(
41+
String title,
42+
String content,
43+
NotificationType type,
44+
Long senderId,
45+
Long receiverId
46+
) {
47+
this.title = title;
48+
this.content = content;
49+
this.type = type;
50+
this.senderId = senderId;
51+
this.receiverId = receiverId;
52+
}
53+
54+
public void markAsRead() { this.isRead = true; }
55+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package es.princip.ringus.domain.notification;
2+
3+
import org.springframework.data.jpa.repository.JpaRepository;
4+
5+
public interface NotificationRepository extends JpaRepository<Notification, Long> {
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package es.princip.ringus.domain.notification;
2+
3+
public enum NotificationType {
4+
MENTORING_REQUEST,
5+
MENTORING_APPROVED
6+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package es.princip.ringus.global.factory;
2+
3+
import es.princip.ringus.domain.notification.Notification;
4+
import es.princip.ringus.domain.notification.NotificationType;
5+
import es.princip.ringus.global.sender.dto.MentoringRequestMessage;
6+
import lombok.RequiredArgsConstructor;
7+
import org.springframework.stereotype.Component;
8+
9+
@Component
10+
@RequiredArgsConstructor
11+
public class NotificationMessageFactory {
12+
13+
public Notification mentoringRequestMessage(MentoringRequestMessage request) {
14+
String title = request.menteeName() + " 멘티님께서 " + request.mentorName() + " 멘토님께 멘토링을 신청했습니다.";
15+
String content = "[링어스 멘토링 신청 알림]\n" +
16+
"멘토링 주제" + request.mentoringTopic().name() + "\n" +
17+
"신청 시간: " + request.applyTimes().toString() + "\n" +
18+
"멘토링 신청 메시지: " + request.mentoringMessage() + "\n"+
19+
"\n\n";
20+
return Notification.builder()
21+
.title(title)
22+
.content(content)
23+
.type(NotificationType.MENTORING_REQUEST)
24+
.senderId(request.senderId())
25+
.receiverId(request.receiverId())
26+
.build();
27+
}
28+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package es.princip.ringus.global.sender;
2+
3+
import lombok.RequiredArgsConstructor;
4+
import lombok.extern.slf4j.Slf4j;
5+
import org.springframework.beans.factory.annotation.Value;
6+
import org.springframework.stereotype.Component;
7+
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
8+
9+
import java.util.Map;
10+
import java.util.Optional;
11+
import java.util.concurrent.ConcurrentHashMap;
12+
13+
@Slf4j
14+
@Component
15+
@RequiredArgsConstructor
16+
public class EmitterRepository {
17+
private final Map<Long, SseEmitter> emitters = new ConcurrentHashMap<>();
18+
@Value("${app.notification.emitter.timeout}")
19+
private Long TIMEOUT;
20+
21+
public SseEmitter save(Long receiverId) {
22+
log.info("Sending message to emitter {}", receiverId);
23+
log.info("Emitter timeout set to {} ms", TIMEOUT);
24+
25+
SseEmitter emitter = new SseEmitter(TIMEOUT);
26+
emitters.put(receiverId, emitter);
27+
28+
emitter.onCompletion(() -> emitters.remove(receiverId));
29+
emitter.onTimeout(() -> emitters.remove(receiverId));
30+
return emitter;
31+
}
32+
33+
public Optional<SseEmitter> get(Long receiverId) {
34+
return Optional.ofNullable(emitters.get(receiverId));
35+
}
36+
37+
public void remove(Long receiverId) {
38+
emitters.remove(receiverId);
39+
}
40+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package es.princip.ringus.global.sender;
2+
3+
public enum NotificationChannel {
4+
SSE,
5+
EMAIL,
6+
KAKAO,
7+
SMS
8+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package es.princip.ringus.global.sender;
2+
3+
import es.princip.ringus.domain.notification.Notification;
4+
5+
public interface NotificationSender {
6+
void send(Notification notification);
7+
NotificationChannel getChannelType();
8+
}

0 commit comments

Comments
 (0)