Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/develop' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
imenuuu committed Jan 16, 2024
2 parents 1c68915 + a8a6fc7 commit d030fe0
Show file tree
Hide file tree
Showing 13 changed files with 183 additions and 32 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/dev_api_ci_cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ jobs:
spring.redis.host: ${{ secrets.REDIS_HOST }}
oauth.apple.iss: ${{ secrets.APPLE_ISS }}
oauth.apple.client-id: ${{ secrets.APPLE_CLIENT_ID }}
slack.webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }}
slack.token: ${{ secrets.SLACK_TOKEN }}

- name: Set Environment - Domain DEV Yml
uses: microsoft/variable-substitution@v1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ public void provideFirstAnalysis(User user) {
@Async("connect_user")
@RedissonLock(LockName = "유저 연속 접속 확인", key = "#user.id")
public void checkActivityBadge(User user) {
UserConnection userConnection = user.getUserConnection();
UserConnection userConnection = userConnectionRepository.findByUser(user);
if(userConnection == null){
userConnectionRepository.save(userConnectionConverter.convertToUserConnection(user));
}else{
Expand Down
3 changes: 3 additions & 0 deletions Winey-Common/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ dependencies {
api group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.2'
api group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.2'

api 'com.slack.api:slack-api-client:1.27.2'


implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,15 @@ public Executor saveNotificationThreadExecutor() {
executor.initialize();
return executor;
}

@Bean(name = "slack-notification")
public Executor postSlackNotification(){
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10); // 기본적으로 실행 대기 중인 Thread 개수
executor.setMaxPoolSize(10); // 동시에 동작하는 최대 Thread 개수
executor.setQueueCapacity(500); // CorePool이 초과될때 Queue에 저장했다가 꺼내서 실행된다. (500개까지 저장함)
executor.setThreadNamePrefix("slack-notification-thread"); // Spring에서 생성하는 Thread 이름의 접두사
executor.initialize();
return executor;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.example.wineycommon.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.slack.api.Slack;
import com.slack.api.methods.MethodsClient;

@Configuration
public class SlackApiConfig {

@Value("${slack.token}")
private String token;

@Bean
public MethodsClient getClient() {
Slack slackClient = Slack.getInstance();
return slackClient.methods(token);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.example.wineycommon.constants;

public class SlackStatic {
public static final String EXCEPTION_CLASS_LABEL = "exception class : ";
public static final String CODE_BLOCK_START = "```";
public static final String CODE_BLOCK_END = "```";
public static final String USER_LABEL = "사용자 : ";
public static final String PROFILE_LABEL = "실행중인 환경 : ";
public static final String SERVER_LABEL = "실행중인 서버 : ";
public static final String URI_LABEL = "요청 URI : ";
public static final String METHOD_LABEL = "request method : ";
public static final String QUERY_STRING_LABEL = "request query string : ";
public static final String REMOTE_ADDR_LABEL = "request remote addr : ";
public static final String REMOTE_HOST_LABEL = "request remote host : ";
public static final String REMOTE_PORT_LABEL = "request remote port : ";
public static final String SERVER_PORT_LABEL = "request server port : ";
public static final String SERVLET_PATH_LABEL = "request servlet path : ";
public static final String EXCEPTION_LABEL = "request exception : ";
public static final String EXCEPTION_CLASS_MESSAGE_VALUE = "Error occurred in class : %s - at line : %s";
public static final String UNKNOWN_EXCEPTION_CLASS_VALUE = "Unknown Exception";
public static final String SLACK_IMG_URL = "https://avatars.slack-edge.com/2024-01-11/6460804813748_57623dce3b29216a9aa8_48.png";
public static final String SLACK_USER_NAME = "서버 에러 알림";
public static final String UNKNOWN_HOST_EXCEPTION_MESSAGE = "올바른 슬랙 URL이 아닙니다.";
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package com.example.wineycommon.exception;

import com.example.wineycommon.reponse.CommonResponse;
import com.example.wineycommon.service.SlackService;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.convert.ConversionFailedException;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
Expand All @@ -14,7 +16,6 @@
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.WebRequest;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
Expand All @@ -29,11 +30,10 @@
import java.util.stream.StreamSupport;

@Slf4j
@RequiredArgsConstructor
@RestControllerAdvice
public class ExceptionAdvice{



private final SlackService slackService;

@ExceptionHandler(ConstraintViolationException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
Expand Down Expand Up @@ -107,6 +107,7 @@ private void getExceptionStackTrace(Exception e, @AuthenticationPrincipal User u
pw.append("uid: " + user.getUsername() + "\n");
}
pw.append(e.getMessage());
pw.append(e.toString());
pw.append("\n=====================================================================");
log.error(sw.toString());
}
Expand Down Expand Up @@ -152,24 +153,16 @@ public ResponseEntity onUserException(UserException userException,
null, userException.getErrorReasonHttpStatus().getHttpStatus());
}



// @ExceptionHandler(value = Exception.class)
// public ResponseEntity onException(Exception exception, @AuthenticationPrincipal User user,
// HttpServletRequest request) {
// getExceptionStackTrace(exception, user, request);
// return new ResponseEntity<>(CommonResponse.onFailure("500", exception.getMessage(), null), null,
// HttpStatus.INTERNAL_SERVER_ERROR);
// }



// TODO : 최상위 예외라서 다른 핸들러들이 작동하지 않음.
// @ExceptionHandler(value = Exception.class)
// public ResponseEntity onException(Exception exception, @AuthenticationPrincipal User user,
// HttpServletRequest request) {
// getExceptionStackTrace(exception, user, request);
// return new ResponseEntity<>(CommonResponse.onFailure("500", exception.getMessage(), null), null,
// HttpStatus.INTERNAL_SERVER_ERROR);
// }
@ExceptionHandler(value = Exception.class)
public ResponseEntity onException(Exception exception, @AuthenticationPrincipal User user,
HttpServletRequest request) {
getExceptionStackTrace(exception, user, request);
if(user==null){
slackService.sendMessage("로그인 되지 않은 유저", exception, request);
}
else{
slackService.sendMessage(user.getUsername(), exception, request);
}
return new ResponseEntity<>(CommonResponse.onFailure("500", exception.getMessage(), null), null, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.example.wineycommon.service;

import static com.example.wineycommon.constants.SlackStatic.*;

import javax.servlet.http.HttpServletRequest;

import com.slack.api.webhook.Payload;

public class SlackConverter {

public static String errorToSlackMessage(String user, HttpServletRequest request, Exception exception, String profile) {
StringBuilder sb = new StringBuilder();
sb.append(CODE_BLOCK_START);
appendLabelAndValue(sb, USER_LABEL, user);
appendLabelAndValue(sb, PROFILE_LABEL, profile);
appendLabelAndValue(sb, SERVER_LABEL, request.getServerName());
appendLabelAndValue(sb, URI_LABEL, request.getRequestURI());
appendLabelAndValue(sb, METHOD_LABEL, request.getMethod());
appendLabelAndValue(sb, QUERY_STRING_LABEL, request.getQueryString());
appendLabelAndValue(sb, EXCEPTION_CLASS_LABEL, getErrorOccurredClassName(exception));
appendLabelAndValue(sb, REMOTE_ADDR_LABEL, request.getRemoteAddr());
appendLabelAndValue(sb, REMOTE_HOST_LABEL, request.getRemoteHost());
appendLabelAndValue(sb, REMOTE_PORT_LABEL, Integer.toString(request.getRemotePort()));
appendLabelAndValue(sb, SERVER_PORT_LABEL, Integer.toString(request.getServerPort()));
appendLabelAndValue(sb, SERVLET_PATH_LABEL, request.getServletPath());
appendLabelAndValue(sb, EXCEPTION_LABEL, exception.getMessage());
sb.append(CODE_BLOCK_END);
return sb.toString();
}

private static void appendLabelAndValue(StringBuilder sb, String label, String value) {
sb.append(label).append(value).append("\n");
}

private static String getErrorOccurredClassName(Exception exception) {
StackTraceElement[] stackTrace = exception.getStackTrace();
if (stackTrace.length > 0) {
StackTraceElement firstStackTraceElement = stackTrace[0];
return String.format(EXCEPTION_CLASS_MESSAGE_VALUE, firstStackTraceElement.getClassName(), firstStackTraceElement.getLineNumber());
}
return UNKNOWN_EXCEPTION_CLASS_VALUE;
}

public static Payload convertToPayload(String message) {
return Payload
.builder()
.text(message)
.username(SLACK_USER_NAME)
.iconUrl(SLACK_IMG_URL)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.example.wineycommon.service;


import static com.example.wineycommon.constants.SlackStatic.*;

import java.io.IOException;
import java.net.UnknownHostException;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import com.slack.api.Slack;
import com.slack.api.webhook.Payload;

import lombok.extern.slf4j.Slf4j;

@Service
@Slf4j
public class SlackService {
@Value("${slack.webhook-url}")
private String webhookUrl;

@Value("${spring.config.activate.on-profile}")
private String profile;


@Async("slack-notification")
public void sendMessage(String user, Exception exception, HttpServletRequest request){
final Slack slack = Slack.getInstance();
final String message = SlackConverter.errorToSlackMessage(user, request, exception, profile);
final Payload payload = SlackConverter.convertToPayload(message);
try {
String responseBody = slack.send(webhookUrl, payload).getBody();
if (!StringUtils.equals(responseBody, "ok")) {
throw new UnknownHostException(UNKNOWN_HOST_EXCEPTION_MESSAGE);
}
} catch (IOException e) {
log.error(e.getMessage(), e);
throw new RuntimeException(e);
}
}
}
3 changes: 3 additions & 0 deletions Winey-Common/src/main/resources/application-common.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,6 @@ spring:
host: ${REDIS_HOST}
port: 6379

slack:
webhook-url: ${SLACK_WEBHOOK_URL}
token: ${SLACK_TOKEN}
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,6 @@ public class QUser extends EntityPathBase<User> {
//inherited
public final DateTimePath<java.time.LocalDateTime> updatedAt = _super.updatedAt;

public final QUserConnection userConnection;

public final ListPath<UserFcmToken, QUserFcmToken> userFcmTokens = this.<UserFcmToken, QUserFcmToken>createList("userFcmTokens", UserFcmToken.class, QUserFcmToken.class, PathInits.DIRECT2);

public final StringPath username = createString("username");
Expand All @@ -81,7 +79,6 @@ public QUser(PathMetadata metadata, PathInits inits) {
public QUser(Class<? extends User> type, PathMetadata metadata, PathInits inits) {
super(type, metadata, inits);
this.preference = inits.isInitialized("preference") ? new com.example.wineydomain.preference.entity.QPreference(forProperty("preference"), inits.get("preference")) : null;
this.userConnection = inits.isInitialized("userConnection") ? new QUserConnection(forProperty("userConnection"), inits.get("userConnection")) : null;
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,6 @@ public class User extends BaseEntity implements UserDetails {
@Builder.Default
private boolean isTastingNoteAnalyzed = false;

@OneToOne(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private UserConnection userConnection;

@OneToMany(fetch = FetchType.LAZY)
@JoinColumn(name = "userId")
@BatchSize(size = 5)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@

public interface UserConnectionRepository extends JpaRepository<UserConnection, Long> {
void deleteByUser(User user);

UserConnection findByUser(User user);
}

0 comments on commit d030fe0

Please sign in to comment.