Skip to content

Commit 19bed69

Browse files
authored
merge: pull request #107 from feat/slack/1
feat: 슬랙 에러 알림 기능 추가
2 parents 2b34a70 + eb05c0f commit 19bed69

File tree

14 files changed

+264
-66
lines changed

14 files changed

+264
-66
lines changed

chat/build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ dependencies {
5454

5555
// Redis
5656
implementation("org.springframework.boot:spring-boot-starter-data-redis")
57+
58+
// Slack
59+
implementation("com.slack.api:slack-api-client:1.45.3")
5760
}
5861

5962
tasks.bootJar {

chat/src/main/java/org/example/soundlinkchat_java/global/auth/JwtProvider.java

Lines changed: 29 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
11
package org.example.soundlinkchat_java.global.auth;
22

3-
import io.jsonwebtoken.Claims;
4-
import io.jsonwebtoken.ExpiredJwtException;
5-
import io.jsonwebtoken.Jwts;
6-
import io.jsonwebtoken.SignatureAlgorithm;
73
import io.jsonwebtoken.security.Keys;
4+
import jakarta.annotation.PostConstruct;
85
import jakarta.servlet.http.Cookie;
96
import jakarta.servlet.http.HttpServletRequest;
107
import lombok.RequiredArgsConstructor;
8+
import lombok.extern.slf4j.Slf4j;
119
import org.springframework.beans.factory.annotation.Value;
1210
import org.springframework.data.redis.core.RedisTemplate;
1311
import org.springframework.stereotype.Component;
12+
import io.jsonwebtoken.*;
1413

1514
import javax.crypto.SecretKey;
16-
import java.util.Date;
15+
import java.util.*;
1716
import java.util.concurrent.TimeUnit;
1817

18+
@Slf4j
1919
@Component
2020
@RequiredArgsConstructor
2121
public class JwtProvider {
@@ -31,18 +31,25 @@ public class JwtProvider {
3131
private long REFRESH_EXPIRATION_TIME;
3232

3333
// 시크릿 키
34-
private final SecretKey SECRET_KEY = Keys.hmacShaKeyFor("ee7d4dcf88086125155386d999b3a2258d5c55671a390e608f49a2db31efc6e0".getBytes());
34+
@Value("${jwt.secret}")
35+
private String SECRET_KEY_STRING;
36+
private SecretKey SECRET_KEY;
37+
38+
@PostConstruct
39+
public void init() {
40+
this.SECRET_KEY = Keys.hmacShaKeyFor(SECRET_KEY_STRING.getBytes());
41+
}
3542

3643
// Access 토큰
3744
public String createAccessToken(long userId) {
3845
Claims claims = Jwts.claims().setSubject(String.valueOf(userId));
3946
Date now = new Date();
40-
4147
return Jwts.builder()
4248
.setClaims(claims)
4349
.setIssuedAt(now)
44-
.setExpiration(new Date(now.getTime()+ACCESS_EXPIRATION_TIME))
45-
.signWith(SECRET_KEY, SignatureAlgorithm.HS256)
50+
.setExpiration(new Date(now.getTime() + ACCESS_EXPIRATION_TIME))
51+
.setHeaderParam("typ", "JWT")
52+
.signWith(SECRET_KEY,SignatureAlgorithm.HS256)
4653
.compact();
4754
}
4855

@@ -54,6 +61,7 @@ public String createRefreshToken(long userId) {
5461
.setClaims(claims)
5562
.setIssuedAt(now)
5663
.setExpiration(new Date(now.getTime()+REFRESH_EXPIRATION_TIME))
64+
.setHeaderParam("typ", "JWT")
5765
.signWith(SECRET_KEY, SignatureAlgorithm.HS256)
5866
.compact();
5967
try {
@@ -66,30 +74,23 @@ public String createRefreshToken(long userId) {
6674
}
6775

6876
//토큰 검증(변조, 만료, 올바른 형식)
69-
public boolean validateToken(String token){
77+
public boolean validateToken(String token) {
7078
try {
7179
Jwts.parserBuilder()
72-
.setSigningKey(SECRET_KEY) //서명 검증
80+
.setSigningKey(SECRET_KEY) // 서명 검증
7381
.build()
74-
.parseClaimsJws(token); //토큰 유효한지 확인.
82+
.parseClaimsJws(token); // 토큰 유효한지 확인 (여기서 만료 시간도 체크)
83+
84+
// 토큰이 유효한 경우
7585
return true;
86+
}catch (ExpiredJwtException e) {
87+
log.warn("[ERROR] Token is expired.");
88+
throw e;
89+
} catch (JwtException e) {
90+
log.warn("[ERROR] Token validation failed: {}", e.getMessage());
91+
throw e;
7692
} catch (Exception e) {
77-
System.out.println("[ERROR] Token validation failed: ");
78-
return false;
79-
}
80-
}
81-
82-
public boolean isTokenExpired(String token) {
83-
try {
84-
Jwts.parserBuilder()
85-
.setSigningKey(SECRET_KEY)
86-
.build()
87-
.parseClaimsJws(token); // 만료된 토큰을 처리하려면 ExpiredJwtException이 발생함
88-
return false; // 만료되지 않으면 false
89-
} catch (ExpiredJwtException ex) {
90-
return true; // 만료된 경우 true
91-
} catch (Exception ex) {
92-
return false; // 다른 예외는 false
93+
throw e;
9394
}
9495
}
9596

@@ -123,5 +124,4 @@ public Long getUserId(String token){
123124
.getBody()
124125
.getSubject());
125126
}
126-
127127
}

chat/src/main/java/org/example/soundlinkchat_java/global/config/RedisConfig.java

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import org.springframework.data.redis.cache.RedisCacheConfiguration;
88
import org.springframework.data.redis.cache.RedisCacheManager;
99
import org.springframework.data.redis.connection.RedisConnectionFactory;
10+
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
1011
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
1112
import org.springframework.data.redis.core.RedisTemplate;
1213
import org.springframework.data.redis.repository.configuration.EnableRedisRepositories;
@@ -21,34 +22,47 @@
2122
public class RedisConfig {
2223
@Value("${spring.data.redis.host}")
2324
private String host;
25+
2426
@Value("${spring.data.redis.port}")
25-
private int port;
27+
private Integer port;
28+
29+
@Value("${spring.data.redis.password}")
30+
private String password;
2631

2732
@Bean
2833
public RedisConnectionFactory redisConnectionFactory(){
29-
return new LettuceConnectionFactory(host, port);
34+
RedisStandaloneConfiguration config = new RedisStandaloneConfiguration(host, port);
35+
config.setPassword(password);
36+
37+
return new LettuceConnectionFactory(config);
3038
}
3139

3240
@Bean
3341
public RedisTemplate<String,Object> redisTemplate(){
3442
RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();
3543
redisTemplate.setConnectionFactory(redisConnectionFactory());
36-
//key,value를 email,authCode로 구현. String으로 직렬화
37-
redisTemplate.setKeySerializer(new StringRedisSerializer());
38-
//redisTemplate.setValueSerializer(new StringRedisSerializer());
39-
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
44+
redisTemplate.setKeySerializer(keySerializer());
45+
redisTemplate.setValueSerializer(valueSerializer());
4046

4147
return redisTemplate;
4248
}
4349

4450
@Bean
4551
public CacheManager contentCacheManager(RedisConnectionFactory cf) {
4652
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
47-
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
48-
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())) // Value Serializer 변경
49-
.entryTtl(Duration.ofMinutes(60L)); // 캐시 수명 10분
53+
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(keySerializer()))
54+
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(valueSerializer()))
55+
.entryTtl(Duration.ofMinutes(30L));
5056

5157
return RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory(cf).cacheDefaults(redisCacheConfiguration).build();
5258
}
59+
60+
private GenericJackson2JsonRedisSerializer valueSerializer() {
61+
return new GenericJackson2JsonRedisSerializer();
62+
}
63+
64+
private StringRedisSerializer keySerializer() {
65+
return new StringRedisSerializer();
66+
}
5367
}
5468

default/build.gradle.kts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@ dependencies {
8888
implementation ("org.apache.kafka:kafka-streams") // Kafka의 스트림 API를 사용할 때 필요
8989
implementation ("org.apache.kafka:kafka-clients") // Kafka 브로커와 직접 통신하는 기본 클라이언트 라이브러리
9090

91+
// Slack
92+
implementation("com.slack.api:slack-api-client:1.45.3")
93+
9194
//QueryDSL 추가
9295
implementation ("com.querydsl:querydsl-apt:5.0.0")
9396
implementation ("com.querydsl:querydsl-jpa:5.0.0:jakarta")

default/src/main/java/org/dfbf/soundlink/domain/alert/service/AlertService.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import org.dfbf.soundlink.domain.alert.repository.AlertRepository;
77
import org.dfbf.soundlink.global.exception.ErrorCode;
88
import org.dfbf.soundlink.global.exception.ResponseResult;
9+
import org.dfbf.soundlink.global.slack.service.SlackService;
910
import org.springframework.http.MediaType;
1011
import org.springframework.stereotype.Service;
1112
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
@@ -21,6 +22,7 @@
2122
public class AlertService {
2223

2324
private final AlertRepository alertRepository;
25+
private final SlackService slackService;
2426

2527
// 60 * 1000 * 60 = 3,600,000{ms} = 1시간
2628
private static final Long DEFAULT_TIMEOUT = 60L * 1000 * 60;
@@ -54,6 +56,7 @@ public SseEmitter connectAlarm(Long id, String lastEventId) {
5456
.data("connect completed!!")
5557
);
5658
} catch (IOException e) {
59+
slackService.sendMsg(id, e.getMessage());
5760
log.error("Error sending ping", e);
5861
}
5962

@@ -98,6 +101,7 @@ public ResponseResult send(Long userId, String alertName, Object data) {
98101

99102
return new ResponseResult(ErrorCode.SUCCESS);
100103
} catch (IOException e) {
104+
slackService.sendMsg(data, e.getMessage());
101105
alertRepository.delete(userId, emitterId);
102106
return new ResponseResult(ErrorCode.BAD_REQUEST_STATUS, e.getMessage());
103107
}

default/src/main/java/org/dfbf/soundlink/domain/blocklist/service/BlockListService.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import org.dfbf.soundlink.domain.user.repository.UserRepository;
1515
import org.dfbf.soundlink.global.exception.ErrorCode;
1616
import org.dfbf.soundlink.global.exception.ResponseResult;
17+
import org.dfbf.soundlink.global.slack.service.SlackService;
1718
import org.springframework.stereotype.Service;
1819

1920
import java.util.List;
@@ -25,6 +26,7 @@ public class BlockListService {
2526
private final BlockListQueryRepository blockListQueryRepository;
2627
private final UserRepository userRepository;
2728
private final BlockListRepository blockListRepository;
29+
private final SlackService slackService;
2830

2931
@Transactional
3032
public ResponseResult blockUser(Long userId, BlockReq req) {
@@ -54,16 +56,19 @@ public ResponseResult blockUser(Long userId, BlockReq req) {
5456
ErrorCode.SUCCESS
5557
);
5658
} catch (BlockedUserNotFound e) {
59+
slackService.sendMsg(req, e.getMessage());
5760
return new ResponseResult(
5861
ErrorCode.BLOCKED_USER_NOT_FOUND,
5962
e.getMessage()
6063
);
6164
} catch (BlockingUserNotFound e) {
65+
slackService.sendMsg(req, e.getMessage());
6266
return new ResponseResult(
6367
ErrorCode.BLOCKING_USER_NOT_FOUND,
6468
e.getMessage()
6569
);
6670
} catch (AlreadyBlockedUser e) {
71+
slackService.sendMsg(req, e.getMessage());
6772
return new ResponseResult(
6873
ErrorCode.ALREADY_BLOCKED_USER,
6974
e.getMessage()
@@ -84,6 +89,7 @@ public ResponseResult unblockUser(Long userId, Long blocklistId) {
8489
ErrorCode.SUCCESS
8590
);
8691
} catch (BlockingUserNotFound e) {
92+
slackService.sendMsg(blocklistId, e.getMessage());
8793
return new ResponseResult(
8894
ErrorCode.BLOCKING_USER_NOT_FOUND,
8995
e.getMessage()

default/src/main/java/org/dfbf/soundlink/domain/chat/service/ChatRoomService.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.dfbf.soundlink.global.exception.ResponseResult;
2828
import org.dfbf.soundlink.global.feign.chat.DevChatClient;
2929
import org.dfbf.soundlink.global.kafka.KafkaProducer;
30+
import org.dfbf.soundlink.global.slack.service.SlackService;
3031
import org.springframework.data.redis.core.RedisTemplate;
3132
import org.springframework.security.core.annotation.AuthenticationPrincipal;
3233
import org.springframework.stereotype.Service;
@@ -51,6 +52,7 @@ public class ChatRoomService {
5152
private final DevChatClient devChatClient;
5253
private final KafkaProducer kafkaProducer;
5354
private final UserStatusService userStatusService;
55+
private final SlackService slackService;
5456

5557
private static final String CHAT_REQUEST_KEY = "chatRequest";
5658
private static final String TOPIC = "alert-topic";
@@ -111,13 +113,17 @@ public ResponseResult saveRequestToRedis(Long requestUserId, Long emotionRecordI
111113

112114
return new ResponseResult(ErrorCode.SUCCESS);
113115
} catch (IllegalArgumentException e) {
116+
slackService.sendMsg(requestUserId, e.getMessage());
114117
log.info(e.getMessage());
115118
return new ResponseResult(ErrorCode.CHAT_REQUEST_SSE_FAILED);
116119
} catch (EmotionRecordNotFoundException e) {
120+
slackService.sendMsg(requestUserId, e.getMessage());
117121
return new ResponseResult(ErrorCode.FAIL_TO_FIND_EMOTION_RECORD, e.getMessage());
118122
} catch (UserNotFoundException e) {
123+
slackService.sendMsg(requestUserId, e.getMessage());
119124
return new ResponseResult(ErrorCode.FAIL_TO_FIND_USER, e.getMessage());
120125
} catch (Exception e) {
126+
slackService.sendMsg(requestUserId, e.getMessage());
121127
return new ResponseResult(ErrorCode.CHAT_REQUEST_FAILED, e.getMessage());
122128
}
123129
}
@@ -144,11 +150,14 @@ public ResponseResult deleteRequestFromRedis(Long userId, Long emotionRecordId)
144150
}
145151

146152
} catch (EmotionRecordNotFoundException e) {
153+
slackService.sendMsg(userId, e.getMessage());
147154
return new ResponseResult(ErrorCode.FAIL_TO_FIND_EMOTION_RECORD);
148155
} catch (UserNotFoundException e) {
156+
slackService.sendMsg(userId, e.getMessage());
149157
return new ResponseResult(ErrorCode.FAIL_TO_FIND_USER);
150158
} catch (Exception e) {
151159
log.error(e.getMessage());
160+
slackService.sendMsg(userId, e.getMessage());
152161
return new ResponseResult(400, "Failed to delete the chat request.");
153162
}
154163
}
@@ -180,10 +189,13 @@ public ResponseResult requestRejected(Long responseUserId, ChatRejectDto chatRej
180189
}
181190

182191
} catch (EmotionRecordNotFoundException e) {
192+
slackService.sendMsg(chatRejectDto, e.getMessage());
183193
return new ResponseResult(ErrorCode.FAIL_TO_FIND_EMOTION_RECORD);
184194
} catch (UserNotFoundException e) {
195+
slackService.sendMsg(chatRejectDto, e.getMessage());
185196
return new ResponseResult(ErrorCode.FAIL_TO_FIND_USER);
186197
} catch (Exception e) {
198+
slackService.sendMsg(chatRejectDto, e.getMessage());
187199
log.error(e.getMessage());
188200
return new ResponseResult(400, "Failed to reject the chat request.");
189201
}
@@ -266,10 +278,13 @@ public ResponseResult createChatRoom(Long userId, Long recordId, String requestN
266278
return new ResponseResult(400, "ChatRequest not found or expired.");
267279
}
268280
} catch (EmotionRecordNotFoundException e) {
281+
slackService.sendMsg(userId, e.getMessage());
269282
return new ResponseResult(ErrorCode.FAIL_TO_FIND_EMOTION_RECORD, e.getMessage());
270283
} catch (NoUserDataException e) {
284+
slackService.sendMsg(userId, e.getMessage());
271285
return new ResponseResult(ErrorCode.FAIL_TO_FIND_USER, e.getMessage());
272286
} catch (Exception e) {
287+
slackService.sendMsg(userId, e.getMessage());
273288
return new ResponseResult(ErrorCode.INTERNAL_SERVER_ERROR, e.getMessage());
274289
}
275290
}
@@ -298,6 +313,7 @@ public ResponseResult closeChatRoom(@AuthenticationPrincipal Long userId, Long c
298313

299314
return new ResponseResult(ErrorCode.SUCCESS);
300315
} catch (Exception e) {
316+
slackService.sendMsg(userId, e.getMessage());
301317
return new ResponseResult(ErrorCode.INTERNAL_SERVER_ERROR, e.getMessage());
302318
}
303319
}
@@ -324,6 +340,7 @@ public ResponseResult getChatRoomList(@AuthenticationPrincipal Long userId) {
324340
.toList();
325341
return new ResponseResult(ErrorCode.SUCCESS, chatRoomList);
326342
} catch (Exception e) {
343+
slackService.sendMsg(userId, e.getMessage());
327344
return new ResponseResult(ErrorCode.INTERNAL_SERVER_ERROR, e.getMessage());
328345
}
329346
}
@@ -351,10 +368,13 @@ public ResponseResult getChatRoomInfo(Long chatRoomId, Long userId) {
351368
);
352369
return new ResponseResult(ErrorCode.SUCCESS, infoDto);
353370
} catch (ChatRoomNotFoundException e) {
371+
slackService.sendMsg(userId, e.getMessage());
354372
return new ResponseResult(ErrorCode.CHATROOM_NOT_FOUND, "채팅방을 찾을 수 없습니다.");
355373
}catch (UnauthorizedAccessException e) {
374+
slackService.sendMsg(userId, e.getMessage());
356375
return new ResponseResult(ErrorCode.CHAT_UNAUTHORIZED,"권한이 없습니다.");
357376
}catch (Exception e) {
377+
slackService.sendMsg(userId, e.getMessage());
358378
return new ResponseResult(ErrorCode.INTERNAL_SERVER_ERROR, "채팅방 세부 정보를 가져오는 데 실패했습니다.");
359379
}
360380
}

0 commit comments

Comments
 (0)