Skip to content

Commit b133532

Browse files
authored
Merge pull request #265 from Modic-2025/feature/243-implement-notification
[Feature] 알림 기능 구현
2 parents a0a5c4c + 3a479e7 commit b133532

45 files changed

Lines changed: 792 additions & 37 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/main/java/hanium/modic/backend/common/property/config/PropertyConfig.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import hanium.modic.backend.common.property.property.CloudFrontProperties;
88
import hanium.modic.backend.common.property.property.CorsProperties;
99
import hanium.modic.backend.common.property.property.EmailProperty;
10+
import hanium.modic.backend.common.property.property.NotificationProperties;
1011
import hanium.modic.backend.common.property.property.RabbitMqProperties;
1112
import hanium.modic.backend.common.property.property.RedisProperty;
1213
import hanium.modic.backend.common.property.property.S3Properties;
@@ -28,7 +29,8 @@
2829
RabbitMqProperties.class,
2930
CloudFrontProperties.class,
3031
AiProperties.class,
31-
VoteProperties.class
32+
VoteProperties.class,
33+
NotificationProperties.class
3234
})
3335
public class PropertyConfig {
3436
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package hanium.modic.backend.common.property.property;
2+
3+
import java.time.Duration;
4+
5+
import org.springframework.boot.context.properties.ConfigurationProperties;
6+
7+
import lombok.Getter;
8+
import lombok.Setter;
9+
10+
@Getter
11+
@Setter
12+
@ConfigurationProperties(prefix = "notification")
13+
public class NotificationProperties {
14+
15+
/**
16+
* 읽은 알림을 얼마 동안 유지할지(분 단위)
17+
*/
18+
private long readRetentionMinutes = Duration.ofDays(30).toMinutes(); // 기본값: 30일
19+
20+
public long getReadRetentionMinutes() {
21+
return readRetentionMinutes;
22+
}
23+
24+
public void setReadRetentionMinutes(long readRetentionMinutes) {
25+
this.readRetentionMinutes = readRetentionMinutes;
26+
}
27+
28+
public Duration getReadRetentionDuration() {
29+
return Duration.ofMinutes(readRetentionMinutes);
30+
}
31+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package hanium.modic.backend.common.scheduler;
2+
3+
import org.springframework.scheduling.annotation.Scheduled;
4+
import org.springframework.stereotype.Component;
5+
6+
import hanium.modic.backend.domain.notification.service.NotificationService;
7+
import lombok.RequiredArgsConstructor;
8+
9+
@Component
10+
@RequiredArgsConstructor
11+
public class DailyScheduler {
12+
13+
private final NotificationService notificationService;
14+
15+
// 매일 자정(00:00:00)에 실행
16+
@Scheduled(cron = "0 0 0 * * *")
17+
public void runEveryMidnight() {
18+
notificationService.cleanupExpiredNotifications();
19+
}
20+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package hanium.modic.backend.common.scheduler.config;
2+
3+
import org.springframework.context.annotation.Configuration;
4+
import org.springframework.scheduling.annotation.EnableScheduling;
5+
6+
@Configuration
7+
@EnableScheduling
8+
public class SchedulingConfig {
9+
}

src/main/java/hanium/modic/backend/domain/ai/aiChat/service/AiImagePermissionService.java

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@
77

88
import hanium.modic.backend.common.error.exception.AppException;
99
import hanium.modic.backend.common.error.exception.LockException;
10-
import hanium.modic.backend.common.redis.distributedLock.LockManager;
10+
import hanium.modic.backend.domain.notification.dto.NotificationPayload;
11+
import hanium.modic.backend.domain.notification.enums.NotificationType;
12+
import hanium.modic.backend.domain.notification.service.NotificationService;
13+
import hanium.modic.backend.domain.user.entity.UserEntity;
14+
import hanium.modic.backend.domain.user.repository.UserEntityRepository;
15+
import hanium.modic.backend.infra.redis.distributedLock.LockManager;
1116
import hanium.modic.backend.domain.ai.aiChat.entity.AiChatRoomEntity;
1217
import hanium.modic.backend.domain.ai.aiChat.repository.AiChatRoomRepository;
1318
import hanium.modic.backend.domain.post.entity.PostEntity;
@@ -25,7 +30,9 @@ public class AiImagePermissionService {
2530
private final TicketService ticketService;
2631
private final PostEntityRepository postRepository;
2732
private final AiChatRoomRepository aiChatRoomRepository;
33+
private final NotificationService notificationService;
2834
private final LockManager lockManager;
35+
private final UserEntityRepository userEntityRepository;
2936

3037
private final int AI_IMAGE_PERMISSION_COUNT = 20; // 구매 시 제공되는 이미지 생성 횟수
3138

@@ -42,6 +49,19 @@ public void buyAiImagePermissionByCoin(Long userId, Long postId) {
4249

4350
// 3) 코인 후차감, 코인 거래는 별도의 트랜잭션으로 동작하여 후처리, 예외는 전파
4451
userCoinService.consumeCoin(userId, post.getNonCommercialPrice());
52+
53+
// 4) 알림
54+
UserEntity user = userEntityRepository.findById(userId)
55+
.orElseThrow(() -> new AppException(USER_NOT_FOUND_EXCEPTION));
56+
notificationService.createNotification(
57+
post.getUserId(),
58+
NotificationType.POST_PURCHASED_BY_COIN,
59+
NotificationPayload.builder(userId, user.getName(), user.getEmail())
60+
.postId(post.getId())
61+
.postTitle(post.getTitle())
62+
.amount(post.getNonCommercialPrice())
63+
.build()
64+
);
4565
}
4666

4767
// 티켓으로 AI 이미지 생성권 구매
@@ -56,6 +76,19 @@ public void buyAiImagePermissionByTicket(final Long userId, final Long postId) {
5676

5777
// 3) 티켓 후차감, 코인 거래는 별도의 트랜잭션으로 동작하여 후처리, 예외는 전파
5878
ticketService.useTicket(userId, post.getTicketPrice());
79+
80+
// 4) 알림
81+
UserEntity user = userEntityRepository.findById(userId)
82+
.orElseThrow(() -> new AppException(USER_NOT_FOUND_EXCEPTION));
83+
notificationService.createNotification(
84+
post.getUserId(),
85+
NotificationType.POST_PURCHASED_BY_TICKET,
86+
NotificationPayload.builder(userId, user.getName(), user.getEmail())
87+
.postId(post.getId())
88+
.postTitle(post.getTitle())
89+
.amount(post.getTicketPrice())
90+
.build()
91+
);
5992
}
6093

6194
// 이미지 생성권 소모

src/main/java/hanium/modic/backend/domain/ai/aiServer/listener/AiImageCreatedDlqListener.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package hanium.modic.backend.domain.ai.aiServer.listener;
22

3-
import static hanium.modic.backend.common.amqp.config.RabbitMqConfig.*;
3+
import static hanium.modic.backend.infra.amqp.config.RabbitMqConfig.*;
44

55
import java.util.Optional;
66

src/main/java/hanium/modic/backend/domain/ai/aiServer/listener/AiImageCreatedListener.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package hanium.modic.backend.domain.ai.aiServer.listener;
22

3-
import static hanium.modic.backend.common.amqp.config.RabbitMqConfig.*;
3+
import static hanium.modic.backend.infra.amqp.config.RabbitMqConfig.*;
44

55
import java.util.Optional;
66

src/main/java/hanium/modic/backend/domain/ai/aiServer/listener/DlqListener.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package hanium.modic.backend.domain.ai.aiServer.listener;
22

3-
import static hanium.modic.backend.common.amqp.config.RabbitMqConfig.*;
3+
import static hanium.modic.backend.infra.amqp.config.RabbitMqConfig.*;
44

55
import java.util.Optional;
66

src/main/java/hanium/modic/backend/domain/ai/aiServer/service/AiResponseSseService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
99

1010
import hanium.modic.backend.common.error.exception.AppException;
11-
import hanium.modic.backend.common.sse.service.EmitterService;
11+
import hanium.modic.backend.infra.sse.service.EmitterService;
1212
import hanium.modic.backend.domain.ai.aiChat.entity.AiChatMessageEntity;
1313
import hanium.modic.backend.domain.ai.aiChat.repository.AiChatMessageRepository;
1414
import hanium.modic.backend.domain.ai.aiServer.enums.SenderType;

src/main/java/hanium/modic/backend/domain/ai/aiServer/service/AiServerService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
import com.fasterxml.jackson.databind.ObjectMapper;
1414

15-
import hanium.modic.backend.common.amqp.service.MessageQueueService;
15+
import hanium.modic.backend.infra.amqp.service.MessageQueueService;
1616
import hanium.modic.backend.common.error.ErrorCode;
1717
import hanium.modic.backend.common.error.exception.AppException;
1818
import hanium.modic.backend.domain.ai.aiChat.entity.AiChatMessageEntity;

0 commit comments

Comments
 (0)