Skip to content

Commit 6c53e18

Browse files
authored
[T3-211] 슬랙 첨부파일 전송 개발 (#84)
* feat: 슬랙 메세지 커스터마이징 구현 및 파일 업로더 구현 * feat: KPI 추출을 위한 엑셀 처리 * feat: kpi 추출 이벤트 처리 * chore: 서브모듈 업데이트(개발 서버에서 슬랙 발송안되도록 처리)
1 parent bf36d36 commit 6c53e18

File tree

15 files changed

+380
-72
lines changed

15 files changed

+380
-72
lines changed

build.gradle

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ dependencies {
6060
// slack
6161
implementation 'com.slack.api:slack-api-client:1.45.3'
6262

63+
// excel
64+
implementation 'org.apache.poi:poi-ooxml:5.3.0'
65+
6366
// bouncycastle
6467
implementation 'org.bouncycastle:bcpkix-jdk18on:1.80'
6568

@@ -95,4 +98,4 @@ processResources {
9598
include 'application-prod.yml'
9699
into ''
97100
}
98-
}
101+
}

config

src/main/java/bitnagil/bitnagil_backend/file/service/FileService.java

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,20 @@
66
import com.amazonaws.services.s3.AmazonS3;
77
import com.amazonaws.services.s3.Headers;
88
import com.amazonaws.services.s3.model.CannedAccessControlList;
9+
import com.amazonaws.services.s3.model.ObjectMetadata;
10+
import com.amazonaws.services.s3.model.PutObjectRequest;
911
import com.amazonaws.services.s3.model.GeneratePresignedUrlRequest;
1012
import lombok.RequiredArgsConstructor;
1113
import org.springframework.beans.factory.annotation.Value;
1214
import org.springframework.stereotype.Service;
1315

16+
import java.io.ByteArrayInputStream;
1417
import java.net.URL;
15-
import java.util.*;
18+
import java.util.Date;
19+
import java.util.HashMap;
20+
import java.util.List;
21+
import java.util.Map;
22+
import java.util.UUID;
1623

1724
@Service
1825
@RequiredArgsConstructor
@@ -41,6 +48,22 @@ public Map<String, String> getPresignedUrls(List<PresignedUrlRequest> requests)
4148
return responseMap;
4249
}
4350

51+
public String uploadBytes(String key, byte[] data, String contentType) {
52+
ObjectMetadata metadata = new ObjectMetadata();
53+
metadata.setContentLength(data.length);
54+
metadata.setContentType(contentType);
55+
56+
PutObjectRequest request = new PutObjectRequest(
57+
bucket,
58+
key,
59+
new ByteArrayInputStream(data),
60+
metadata
61+
).withCannedAcl(CannedAccessControlList.PublicRead);
62+
63+
amazonS3.putObject(request);
64+
return amazonS3.getUrl(bucket, key).toString();
65+
}
66+
4467
// S3 Presigned URL 생성
4568
private GeneratePresignedUrlRequest getGeneratePresignedUrlRequest(String bucket, String fileName) {
4669
GeneratePresignedUrlRequest generatePresignedUrlRequest = new GeneratePresignedUrlRequest(bucket, fileName)
@@ -74,4 +97,3 @@ private String createPath(String prefix, String fileName) {
7497
return String.format("%s/%s", prefix, fileId + "-" + fileName);
7598
}
7699
}
77-

src/main/java/bitnagil/bitnagil_backend/global/SlackService.java

Lines changed: 0 additions & 58 deletions
This file was deleted.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package bitnagil.bitnagil_backend.global.config;
2+
3+
import org.springframework.context.annotation.Bean;
4+
import org.springframework.context.annotation.Configuration;
5+
import org.springframework.scheduling.annotation.EnableAsync;
6+
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
7+
8+
import java.util.concurrent.Executor;
9+
10+
@Configuration
11+
@EnableAsync
12+
public class AsyncConfig {
13+
14+
@Bean
15+
public Executor asyncExecutor() {
16+
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
17+
executor.setCorePoolSize(2);
18+
executor.setMaxPoolSize(4);
19+
executor.setQueueCapacity(100);
20+
executor.setThreadNamePrefix("async-");
21+
executor.initialize();
22+
return executor;
23+
}
24+
}

src/main/java/bitnagil/bitnagil_backend/global/handler/GlobalExceptionHandler.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package bitnagil.bitnagil_backend.global.handler;
22

3-
import bitnagil.bitnagil_backend.global.SlackService;
3+
import bitnagil.bitnagil_backend.global.slack.SlackMessageOptions;
4+
import bitnagil.bitnagil_backend.global.slack.SlackService;
45
import bitnagil.bitnagil_backend.global.errorcode.ErrorCode;
56
import bitnagil.bitnagil_backend.global.exception.CustomException;
67
import bitnagil.bitnagil_backend.global.response.ErrorResponseDto;
@@ -197,7 +198,11 @@ private void sendSlackMessage(Exception e, ErrorCode errorCode) {
197198
String title = "에러 코드: " + errorCode.getCode() + "\n"
198199
+ "상태 코드: " + errorCode.getHttpStatus().value() + "\n"
199200
+ "메시지: " + errorCode.getMessage();
200-
slackService.sendMessage(title, messageMap);
201+
SlackMessageOptions options = SlackMessageOptions.builder()
202+
.title(title)
203+
.data(messageMap)
204+
.build();
205+
slackService.sendMessage(options);
201206
}
202207

203208
/**
@@ -212,7 +217,11 @@ private void sendSlackMessage(Exception e, ErrorCode errorCode, String message)
212217
+ "상태 코드: " + errorCode.getHttpStatus().value() + "\n"
213218
+ "메시지: " + errorCode.getMessage() + "\n"
214219
+ "추가 메시지: " + message;
215-
slackService.sendMessage(title, messageMap);
220+
SlackMessageOptions options = SlackMessageOptions.builder()
221+
.title(title)
222+
.data(messageMap)
223+
.build();
224+
slackService.sendMessage(options);
216225
}
217226

218227
/**

src/main/java/bitnagil/bitnagil_backend/global/Color.java renamed to src/main/java/bitnagil/bitnagil_backend/global/slack/Color.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package bitnagil.bitnagil_backend.global;
1+
package bitnagil.bitnagil_backend.global.slack;
22

33
import lombok.Getter;
44
import lombok.RequiredArgsConstructor;
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package bitnagil.bitnagil_backend.global.slack;
2+
3+
import com.slack.api.Slack;
4+
import com.slack.api.methods.MethodsClient;
5+
import com.slack.api.methods.request.files.FilesUploadV2Request;
6+
import com.slack.api.methods.request.files.FilesUploadV2Request.UploadFile;
7+
import com.slack.api.methods.response.files.FilesUploadV2Response;
8+
import lombok.extern.slf4j.Slf4j;
9+
import org.springframework.stereotype.Service;
10+
11+
import java.util.List;
12+
13+
@Service
14+
@Slf4j
15+
public class SlackFileSender {
16+
17+
private final Slack slack = Slack.getInstance();
18+
19+
public void upload(SlackFileUploadOptions options) {
20+
if (options == null) {
21+
return;
22+
}
23+
24+
if (options.getBotToken() == null || options.getBotToken().isEmpty()) {
25+
return;
26+
}
27+
28+
if (options.getChannelId() == null || options.getChannelId().isEmpty()) {
29+
return;
30+
}
31+
32+
if (options.getFilename() == null || options.getFilename().isEmpty()) {
33+
return;
34+
}
35+
36+
if (options.getFileBytes() == null || options.getFileBytes().length == 0) {
37+
return;
38+
}
39+
40+
try {
41+
MethodsClient client = slack.methods(options.getBotToken());
42+
UploadFile uploadFile = UploadFile.builder()
43+
.filename(options.getFilename())
44+
.fileData(options.getFileBytes())
45+
.title(options.getFilename())
46+
.build();
47+
48+
FilesUploadV2Request request = FilesUploadV2Request.builder()
49+
.channel(options.getChannelId())
50+
.initialComment(options.getInitialComment())
51+
.uploadFiles(List.of(uploadFile))
52+
.build();
53+
54+
FilesUploadV2Response response = client.filesUploadV2(request);
55+
if (!response.isOk()) {
56+
log.error("Slack 파일 업로드 실패: {}", response.getError());
57+
}
58+
} catch (Exception e) {
59+
log.error("Slack 파일 업로드에 실패했습니다.", e);
60+
}
61+
}
62+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package bitnagil.bitnagil_backend.global.slack;
2+
3+
import lombok.Builder;
4+
import lombok.Getter;
5+
6+
@Getter
7+
@Builder
8+
public class SlackFileUploadOptions {
9+
private final String botToken;
10+
private final String channelId;
11+
private final String filename;
12+
private final byte[] fileBytes;
13+
private final String initialComment;
14+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package bitnagil.bitnagil_backend.global.slack;
2+
3+
import lombok.Builder;
4+
import lombok.Getter;
5+
6+
import java.util.Map;
7+
8+
@Getter
9+
@Builder
10+
public class SlackMessageOptions {
11+
private final String title;
12+
private final Map<String, String> data;
13+
private final String webhookUrl;
14+
private final String colorCode;
15+
@Builder.Default
16+
private final boolean enabled = true;
17+
}

0 commit comments

Comments
 (0)