Skip to content

Commit ad56353

Browse files
authored
Merge pull request #166 from dnd-side-project/refactor/first-sprint
feat(Chat,Playlist): Playlist ์‚ญ์ œ ์‹œ ์ฑ„ํŒ… ์‚ญ์ œ ์ด๋ฒคํŠธ ๋ฐœํ–‰
2 parents 0637b39 + d450541 commit ad56353

File tree

10 files changed

+167
-30
lines changed

10 files changed

+167
-30
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.example.demo.dto;
2+
3+
import lombok.Builder;
4+
5+
@Builder
6+
public record PlaylistDeleteEvent(String playlistId) {
7+
}

โ€Žchat-server/src/main/java/com/example/demo/entity/repository/ChatRepository.javaโ€Ž

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,10 @@
88
import org.springframework.beans.factory.annotation.Value;
99
import org.springframework.stereotype.Repository;
1010
import software.amazon.awssdk.enhanced.dynamodb.*;
11-
import software.amazon.awssdk.enhanced.dynamodb.model.Page;
12-
import software.amazon.awssdk.enhanced.dynamodb.model.PageIterable;
13-
import software.amazon.awssdk.enhanced.dynamodb.model.QueryConditional;
14-
import software.amazon.awssdk.enhanced.dynamodb.model.QueryEnhancedRequest;
11+
import software.amazon.awssdk.enhanced.dynamodb.model.*;
1512
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
1613

17-
import java.util.Base64;
18-
import java.util.List;
19-
import java.util.Map;
20-
import java.util.Optional;
14+
import java.util.*;
2115

2216
@Repository
2317
@RequiredArgsConstructor
@@ -140,4 +134,48 @@ public int countByRoomId(String roomId) {
140134

141135
return total;
142136
}
137+
138+
public void deleteAllByRoomId(String roomId) {
139+
DynamoDbTable<Chat> t = table();
140+
141+
Map<String, AttributeValue> lek = null;
142+
do {
143+
QueryEnhancedRequest req = QueryEnhancedRequest.builder()
144+
.queryConditional(QueryConditional.keyEqualTo(
145+
Key.builder().partitionValue(roomId).build()))
146+
.attributesToProject("roomId", "sentAt")
147+
.limit(1000)
148+
.exclusiveStartKey(lek)
149+
.build();
150+
151+
Page<Chat> page = t.query(req).stream().findFirst().orElse(null);
152+
if (page == null) break;
153+
154+
List<Chat> chats = page.items();
155+
for (int i = 0; i < chats.size(); i += 25) {
156+
int end = Math.min(i + 25, chats.size());
157+
158+
WriteBatch.Builder<Chat> wb = WriteBatch.builder(Chat.class)
159+
.mappedTableResource(t);
160+
161+
for (int j = i; j < end; j++) {
162+
Chat c = chats.get(j);
163+
wb.addDeleteItem(b -> b.key(
164+
Key.builder()
165+
.partitionValue(c.getRoomId())
166+
.sortValue(c.getSentAt())
167+
.build()
168+
));
169+
}
170+
171+
BatchWriteItemEnhancedRequest batchReq = BatchWriteItemEnhancedRequest.builder()
172+
.writeBatches(wb.build())
173+
.build();
174+
175+
enhancedClient.batchWriteItem(batchReq);
176+
}
177+
178+
lek = page.lastEvaluatedKey();
179+
} while (lek != null && !lek.isEmpty());
180+
}
143181
}

โ€Žchat-server/src/main/java/com/example/demo/global/redis/ChatRedisSubscriber.javaโ€Ž

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package com.example.demo.global.redis;
22

33
import com.example.demo.dto.ChatOutbound;
4+
import com.example.demo.dto.PlaylistDeleteEvent;
5+
import com.example.demo.service.ChatService;
46
import com.fasterxml.jackson.databind.ObjectMapper;
57
import lombok.RequiredArgsConstructor;
68
import org.springframework.beans.factory.annotation.Value;
@@ -15,18 +17,33 @@ public class ChatRedisSubscriber implements MessageListener {
1517

1618
private final SimpMessagingTemplate messagingTemplate;
1719
private final ObjectMapper objectMapper;
20+
private final ChatService chatService;
21+
22+
@Value("${chat.redis.topic-prefix:chat.}")
23+
private String topicPrefix;
1824

1925
//Redis Pub/Sub์œผ๋กœ ๋ฉ”์‹œ์ง€๋ฅผ ๋ฐ›์•˜์„ ๋•Œ ์‹คํ–‰๋จ
26+
@Override
2027
public void onMessage(Message message, byte[] pattern) {
2128
try {
22-
// Redis์—์„œ ์ „๋‹ฌ๋ฐ›์€ payload
29+
String channel = new String(message.getChannel());
2330
String body = new String(message.getBody());
2431

25-
ChatOutbound chatOutbound = objectMapper.readValue(body, ChatOutbound.class);
26-
27-
// STOMP ๊ตฌ๋…์ž์—๊ฒŒ ๋ธŒ๋กœ๋“œ์บ์ŠคํŠธ (/chat/topic/rooms/{roomId})
28-
String destination = "/chat/topic/rooms/" + chatOutbound.getRoomId();
29-
messagingTemplate.convertAndSend(destination, chatOutbound);
32+
if (channel.equals("chat.playlist.deleted")) {
33+
PlaylistDeleteEvent playlistDeleteEvent = objectMapper.readValue(body, PlaylistDeleteEvent.class);
34+
String roomId = playlistDeleteEvent.playlistId(); // == roomId
35+
if (roomId != null && !roomId.isBlank()) {
36+
chatService.deleteAllByRoomId(roomId);
37+
}
38+
return;
39+
}
40+
41+
// ์‹ค์‹œ๊ฐ„ ๋ฉ”์‹œ์ง€ ๋ธŒ๋กœ๋“œ์บ์ŠคํŠธ (์˜ˆ: chat.room.*)
42+
if (channel.startsWith(topicPrefix +"room.")) {
43+
ChatOutbound chatOutbound = objectMapper.readValue(body, ChatOutbound.class);
44+
String destination = "/chat/topic/rooms/" + chatOutbound.getRoomId();
45+
messagingTemplate.convertAndSend(destination, chatOutbound);
46+
}
3047

3148
} catch (Exception e) {
3249
e.printStackTrace();

โ€Žchat-server/src/main/java/com/example/demo/global/redis/RedisConfig.javaโ€Ž

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,11 @@ public class RedisConfig {
3232

3333
@Bean
3434
public RedisConnectionFactory redisConnectionFactory() {
35-
// Standalone ์„ค์ • (ํด๋Ÿฌ์Šคํ„ฐ/๋ฆฌํ”Œ๋ฆฌ์นด ์…‹์—…์ด ์•„๋‹ˆ๋ฉด ๋ณดํ†ต ์ด๊ฑธ๋กœ ์ถฉ๋ถ„)
3635
RedisStandaloneConfiguration standalone = new RedisStandaloneConfiguration(redisHost, redisPort);
3736
if (redisPassword != null && !redisPassword.isEmpty()) {
3837
standalone.setPassword(RedisPassword.of(redisPassword));
3938
}
4039

41-
// Lettuce ํด๋ผ์ด์–ธํŠธ ์˜ต์…˜ + SSL ์—ฌ๋ถ€
4240
LettuceClientConfiguration.LettuceClientConfigurationBuilder clientBuilder =
4341
LettuceClientConfiguration.builder();
4442

@@ -55,7 +53,6 @@ public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory cf) {
5553
return new StringRedisTemplate(cf);
5654
}
5755

58-
// ๋ชจ๋“  room ์ฑ„๋„์„ ํŒจํ„ด ๊ตฌ๋…
5956
@Bean
6057
public RedisMessageListenerContainer redisMessageListenerContainer(
6158
RedisConnectionFactory cf,
@@ -68,7 +65,6 @@ public RedisMessageListenerContainer redisMessageListenerContainer(
6865
return container;
6966
}
7067

71-
// Redis ์ˆ˜์‹  ๋ฉ”์‹œ์ง€๋ฅผ ์ฒ˜๋ฆฌํ•  ๋ฆฌ์Šค๋„ˆ ์–ด๋Œ‘ํ„ฐ (ChatRedisSubscriber.onMessage ํ˜ธ์ถœ)
7268
@Bean
7369
public MessageListenerAdapter chatMessageListenerAdapter(ChatRedisSubscriber subscriber) {
7470
// ๋ฆฌํ”Œ๋ ‰์…˜์œผ๋กœ subscriber.onMessage(Message, byte[]) ํ˜ธ์ถœ

โ€Žchat-server/src/main/java/com/example/demo/service/ChatService.javaโ€Ž

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public void handleInbound(String roomId, ChatInbound chatInbound) {
5050

5151
// 2) Redis Pub/Sub ๋ฐœํ–‰ (๋ชจ๋“  ์ธ์Šคํ„ด์Šค๊ฐ€ ์ˆ˜์‹ )
5252
try {
53-
String channel = topicPrefix + roomId; // ex) chat.room.room-1
53+
String channel = topicPrefix + "room." + roomId; // ex) chat.room.room-1
5454
String payload = objectMapper.writeValueAsString(chatOutbound);
5555
stringRedisTemplate.convertAndSend(channel, payload);
5656
} catch (Exception e) {
@@ -108,4 +108,9 @@ public void deleteMessage(String roomId, String messageId, String userId) {
108108
public int countByRoomId(String roomId) {
109109
return chatRepository.countByRoomId(roomId);
110110
}
111+
112+
public void deleteAllByRoomId(String roomId) {
113+
// ๋ฉฑ๋“ฑ์„ฑ: ์กด์žฌํ•˜์ง€ ์•Š์•„๋„ ์˜ˆ์™ธ ์—†์ด ํ†ต๊ณผ
114+
chatRepository.deleteAllByRoomId(roomId);
115+
}
111116
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.example.demo.domain.playlist.event;
2+
3+
import lombok.Builder;
4+
5+
@Builder
6+
public record PlaylistDeleteEvent(String playlistId) {
7+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.example.demo.domain.playlist.event;
2+
3+
import com.example.demo.global.redis.PlaylistEventPublisher;
4+
import lombok.RequiredArgsConstructor;
5+
import org.springframework.stereotype.Component;
6+
import org.springframework.transaction.event.TransactionPhase;
7+
import org.springframework.transaction.event.TransactionalEventListener;
8+
9+
@Component
10+
@RequiredArgsConstructor
11+
public class PlaylistEventHandler {
12+
13+
private final PlaylistEventPublisher playlistEventPublisher;
14+
15+
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
16+
public void handlePlaylistDeletedEvent(PlaylistDeleteEvent event) {
17+
playlistEventPublisher.publishPlaylistDelete(event.playlistId());
18+
}
19+
}

โ€Žmain-server/src/main/java/com/example/demo/domain/playlist/service/PlaylistServiceImpl.javaโ€Ž

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import com.example.demo.domain.playlist.dto.playlistdto.PlaylistCreateRequest;
1313
import com.example.demo.domain.playlist.dto.playlistdto.PlaylistWithSongsResponse;
1414
import com.example.demo.domain.playlist.entity.Playlist;
15+
import com.example.demo.domain.playlist.event.PlaylistDeleteEvent;
1516
import com.example.demo.domain.playlist.repository.PlaylistRepository;
1617
import com.example.demo.domain.recommendation.entity.UserPlaylistHistory;
1718
import com.example.demo.domain.recommendation.repository.UserPlaylistHistoryRepository;
@@ -23,6 +24,7 @@
2324
import java.util.*;
2425
import lombok.RequiredArgsConstructor;
2526
import lombok.extern.slf4j.Slf4j;
27+
import org.springframework.context.ApplicationEventPublisher;
2628
import org.springframework.stereotype.Service;
2729
import org.springframework.transaction.annotation.Transactional;
2830

@@ -38,6 +40,7 @@ public class PlaylistServiceImpl implements PlaylistService {
3840
private final SongRepository songRepository;
3941
private final CdService cdService;
4042
private final CdRepository cdRepository;
43+
private final ApplicationEventPublisher applicationEventPublisher;
4144

4245
@Override
4346
@Transactional
@@ -108,22 +111,15 @@ public void deletePlaylist(String userId, Long playlistId) {
108111
PlaylistErrorCode.PLAYLIST_NOT_FOUND
109112
));
110113

111-
// 2. ์œ ์ €๊ฐ€ ๊ฐ€์ง„ ํ”Œ๋ฆฌ ๊ฐœ์ˆ˜ ํ™•์ธ
112-
long totalCount = playlistRepository.countByUserIdNative(userId);
113-
if (totalCount <= 1) {
114-
throw new PlaylistException(
115-
"ํ”Œ๋ ˆ์ด๋ฆฌ์ŠคํŠธ๋Š” ์ตœ์†Œ 1๊ฐœ ์ด์ƒ ์กด์žฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.",
116-
PlaylistErrorCode.PLAYLIST_NOT_FOUND
117-
);
118-
}
119-
120-
// 4. ์ฐธ์กฐ ํ…Œ์ด๋ธ” ์ˆœ์ฐจ ์‚ญ์ œ (์ค‘์š”!)
114+
// 2. ์ฐธ์กฐ ํ…Œ์ด๋ธ” ์ˆœ์ฐจ ์‚ญ์ œ (์ค‘์š”!)
121115
cdRepository.deleteByPlaylistId(playlistId); // CD ํ…Œ์ด๋ธ”
122116
songRepository.deleteByPlaylistId(playlistId); // ๊ณก
123117
userPlaylistHistoryRepository.deleteByPlaylistId(playlistId); // ์žฌ์ƒ๊ธฐ๋ก
124118

125-
// 5. ํ”Œ๋ ˆ์ด๋ฆฌ์ŠคํŠธ ์‚ญ์ œ
119+
// 3. ํ”Œ๋ ˆ์ด๋ฆฌ์ŠคํŠธ ์‚ญ์ œ
126120
playlistRepository.delete(toDelete);
121+
122+
applicationEventPublisher.publishEvent(new PlaylistDeleteEvent(String.valueOf(playlistId)));
127123
}
128124

129125
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.example.demo.global.redis;
2+
3+
import com.fasterxml.jackson.databind.ObjectMapper;
4+
import lombok.RequiredArgsConstructor;
5+
import org.springframework.beans.factory.annotation.Value;
6+
import org.springframework.data.redis.core.StringRedisTemplate;
7+
import org.springframework.stereotype.Component;
8+
9+
import java.util.HashMap;
10+
import java.util.Map;
11+
12+
@Component
13+
@RequiredArgsConstructor
14+
public class PlaylistEventPublisher {
15+
16+
private final StringRedisTemplate redisTemplate;
17+
private final ObjectMapper objectMapper = new ObjectMapper();
18+
19+
@Value("${chat.redis.topic-playlist-deleted:chat.playlist.deleted}")
20+
private String playlistDeletedTopic;
21+
22+
public void publishPlaylistDelete(String playlistId) {
23+
try {
24+
Map<String, Object> payload = new HashMap<>();
25+
payload.put("playlistId", playlistId);
26+
27+
String message = objectMapper.writeValueAsString(payload);
28+
redisTemplate.convertAndSend(playlistDeletedTopic, message);
29+
} catch (Exception e) {
30+
//
31+
}
32+
}
33+
}

โ€Žmain-server/src/main/java/com/example/demo/global/redis/RedisConfig.javaโ€Ž

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,33 @@
11
package com.example.demo.global.redis;
22

3+
import org.springframework.beans.factory.annotation.Value;
34
import org.springframework.context.annotation.Bean;
45
import org.springframework.context.annotation.Configuration;
56
import org.springframework.data.redis.connection.RedisConnectionFactory;
7+
import org.springframework.data.redis.connection.RedisPassword;
8+
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
9+
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
10+
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
611
import org.springframework.data.redis.core.StringRedisTemplate;
712

813

914
@Configuration
1015
public class RedisConfig {
1116

17+
@Value("${spring.data.redis.host}") private String redisHost;
18+
@Value("${spring.data.redis.port}") private int redisPort;
19+
@Value("${spring.data.redis.password:}") private String redisPassword;
20+
@Bean
21+
public RedisConnectionFactory redisConnectionFactory() {
22+
RedisStandaloneConfiguration standalone = new RedisStandaloneConfiguration(redisHost, redisPort);
23+
if (redisPassword != null && !redisPassword.isEmpty()) {
24+
standalone.setPassword(RedisPassword.of(redisPassword));
25+
}
26+
LettuceClientConfiguration.LettuceClientConfigurationBuilder clientBuilder =
27+
LettuceClientConfiguration.builder();
28+
return new LettuceConnectionFactory(standalone, clientBuilder.build());
29+
}
30+
1231
@Bean
1332
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory cf) {
1433
return new StringRedisTemplate(cf);

0 commit comments

Comments
ย (0)