Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
// accessToken 이 필요없는 경우 필터링 없이 처리
if (requestURI.startsWith("/api/auth/token") ||
requestURI.startsWith("/api/auth/login/facebook") ||
requestURI.startsWith("/api/quiz/**")) {
requestURI.startsWith("/api/quiz/**")||
requestURI.startsWith("/api/feed/detect")) {
chain.doFilter(request, response);
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti
"/api/auth/token",
"/api/auth/login/facebook",
"/api/quiz/**",
"/api/feed/detect",
"/swagger-ui/**",
"/webjars/**",
"/swagger-ui.html",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
package com.olive.pribee.infra.api.google.dlp;

import java.io.IOException;
import java.util.Base64;
import java.util.List;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.reactive.function.client.WebClient;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.olive.pribee.infra.api.google.dlp.dto.req.DlpReq;
import com.olive.pribee.infra.api.google.dlp.dto.res.DlpFinding;
import com.olive.pribee.infra.api.google.dlp.dto.res.DlpRes;
Expand All @@ -36,7 +40,6 @@ private WebClient getDlpWebClient() {

public Mono<List<DlpFinding>> analyzeText(String text) {
return getDlpWebClient().post()
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(DlpReq.ofText(text))
.retrieve()
.bodyToMono(DlpRes.class)
Expand All @@ -46,7 +49,6 @@ public Mono<List<DlpFinding>> analyzeText(String text) {

public Mono<List<DlpFinding>> analyzeImage(String imageUrl) {
return getDlpWebClient().post()
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(DlpReq.ofImage(imageUrl))
.retrieve()
.bodyToMono(JsonNode.class) // JSON 응답 로그 찍기
Expand All @@ -64,4 +66,59 @@ public Mono<List<DlpFinding>> analyzeImage(String imageUrl) {
.map(DlpRes::getFindings);
}

private final ObjectMapper objectMapper;

// TODO 추후에 있는 dto 로 매핑하기
public Mono<ObjectNode> analyzeRawData(String message, MultipartFile file) {
try {
String base64Image = Base64.getEncoder().encodeToString(file.getBytes());
return analyzeText(message)
.zipWith(analyzeImage(base64Image))
.map(tuple -> {
ObjectNode response = objectMapper.createObjectNode();
ArrayNode textResults = objectMapper.createArrayNode();
ArrayNode imageResults = objectMapper.createArrayNode();

// 텍스트 데이터 필터링
for (DlpFinding finding : tuple.getT1()) {
ObjectNode textNode = objectMapper.createObjectNode();
textNode.put("quote", finding.getQuote());
textNode.put("infoType", finding.getInfoType().getName());
textNode.put("likelihood", finding.getLikelihood());
if (finding.getLocation() != null && finding.getLocation().getByteRange() != null) {
textNode.put("start", finding.getLocation().getByteRange().getStart());
textNode.put("end", finding.getLocation().getByteRange().getEnd());
}
textResults.add(textNode);
}

// 이미지 데이터 필터링
for (DlpFinding finding : tuple.getT2()) {
ObjectNode imageNode = objectMapper.createObjectNode();
imageNode.put("quote", finding.getQuote());
imageNode.put("infoType", finding.getInfoType().getName());
imageNode.put("likelihood", finding.getLikelihood());
if (finding.getLocation() != null && finding.getLocation().getContentLocations() != null) {
ArrayNode boundingBoxes = objectMapper.createArrayNode();
finding.getLocation().getContentLocations().forEach(contentLocation -> {
if (contentLocation.getImageLocation() != null) {
boundingBoxes.add(objectMapper.valueToTree(
contentLocation.getImageLocation().getBoundingBoxes()));
}
});
imageNode.set("imageLocation", boundingBoxes);
}
imageResults.add(imageNode);
}

response.set("textRes", textResults);
response.set("imageRes", imageResults);
return response;
});
} catch (IOException e) {
log.error("Failed to process image file", e);
return Mono.error(e);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,8 @@ public static DlpReq ofText(String value) {
}

public static DlpReq ofImage(String imageUrl) {
String base64Image = encodeImageToBase64(imageUrl);
return new DlpReq(
new Item(Map.of("type", "IMAGE", "data", base64Image)),
new Item(Map.of("type", "IMAGE", "data", imageUrl)),
new InspectConfig(
DetectKeyword.getInfoTypes().stream().map(InfoType::new).toList(),
"POSSIBLE"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import com.fasterxml.jackson.databind.node.ObjectNode;
import com.mongodb.lang.Nullable;
import com.olive.pribee.global.common.DataResponseDto;
import com.olive.pribee.global.common.ResponseDto;
Expand Down Expand Up @@ -48,5 +51,11 @@ public ResponseEntity<ResponseDto> getTotalPost(
}

// 게시물 첨부 조회
@PostMapping("/detect")
public ResponseEntity<ResponseDto> postDetectPost(@RequestParam(name = "file") MultipartFile file,
@RequestParam(name = "message") String message) {
ObjectNode res = fbPostService.postDetectPost(file, message);
return ResponseEntity.status(200).body(DataResponseDto.of(res, 200));

}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package com.olive.pribee.module.feed.controller;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;

import com.mongodb.lang.Nullable;
import com.olive.pribee.global.common.ResponseDto;
import com.olive.pribee.global.enums.DetectKeyword;
import com.olive.pribee.module.auth.domain.entity.Member;
Expand Down Expand Up @@ -30,7 +33,7 @@ public interface FbPostControllerDocs {
+ " \"data\": {\n"
+ " \"createdTime\": \"2025-03-06T14:35:45\",\n"
+ " \"safeMessage\": null,\n"
+ " \"message\": \"2025년 2월 27일 ~ 3월 2일 대만 여행✈\\n\\n오랜만에 해외여행! 겨울이 끝나갈 즈음, 따뜻한 나라로 떠나고 싶어서 친구..."
+ " \"message\": \"2025년 2월 27일 ~ 3월 2일 대만 여행✈\\n\\n오랜만에 해외여행!...\",\n"
+ " \"permalinkUrl\": \"https://www.facebook.com/122100024608769823/posts/122110815812769823\",\n"
+ " \"messageDetectRes\": [\n"
+ " {\n"
Expand Down Expand Up @@ -85,16 +88,16 @@ public interface FbPostControllerDocs {
+ " ],\n"
+ " \"photoUrlRes\": [\n"
+ " {\n"
+ " \"pictureUrl\": \"https://scontent-gmp1-1.xx.fbcdn.net/v/t39.30808-6/481898235_122110815350769823_7830570790572853712_n.jpg?stp=dst-jpg_s960x960_tt6&_nc_cat=103&ccb=1-7&_nc_sid=127cfc&_nc_ohc=pqTxmMiaRmAQ7kNvgF_M2fo&_nc_oc=AdinZRm68WKJbnn4k8rmen-R1dYh-fVdY5Ln9wy9FUZzoYlqG6p3dTwjVynw9mzo0uM&_nc_zt=23&_nc_ht=scontent-gmp1-1.xx&edm=AP4hL3IEAAAA&_nc_gid=APgMhPiomivhYaHlQ0N7ogZ&oh=00_AYEzEWPriAvCi_kxV_i4eY7DSTzniaPtlf7qcPcVrlettQ&oe=67D07B53\",\n"
+ " \"pictureUrl\": \"https://scontent-gmp1-1.xx.fbcdn.net/v/t39.30808-6/481898235_122110815350769823_7830570790572853712_n.jpg?stp=dst-jpg_s960x960_tt6&_nc_cat=103&ccb=1-7&_nc_sid=127cfc&_nc_ohc=pqTxmMiaRmAQ7kNvgF_M2fo&_nc_oc=AdinZRm68WKJbnn4k8rmen-R1dYh-fVdY5Ln9wy9FUZzoYlqG6p3dTwjVynw9mzo0uM&_nc_zt=23&_nc_ht=scontent-gmp1-1.xx&edm=AP4hL3IEAAAA&_nc_gid=AivqHWL5xXiL5ZL42RVLVvt&oh=00_AYHT_TOYFwZUH_PdXyW6xy-V95142_T7cNqnXT4lG0G8cA&oe=67D0B393\",\n"
+ " \"photoDetectRes\": [\n"
+ " {\n"
+ " \"detectWord\": \"TAIPEI\",\n"
+ " \"keyword\": \"LOCATION\",\n"
+ " \"likelihood\": \"LIKELY\",\n"
+ " \"boundingBoxRes\": [\n"
+ " {\n"
+ " \"x\": 0,\n"
+ " \"y\": 0,\n"
+ " \"x\": 338,\n"
+ " \"y\": 513,\n"
+ " \"width\": 69,\n"
+ " \"height\": 130\n"
+ " }\n"
Expand All @@ -106,8 +109,8 @@ public interface FbPostControllerDocs {
+ " \"likelihood\": \"LIKELY\",\n"
+ " \"boundingBoxRes\": [\n"
+ " {\n"
+ " \"x\": 0,\n"
+ " \"y\": 0,\n"
+ " \"x\": 356,\n"
+ " \"y\": 416,\n"
+ " \"width\": 74,\n"
+ " \"height\": 142\n"
+ " }\n"
Expand All @@ -119,8 +122,8 @@ public interface FbPostControllerDocs {
+ " \"likelihood\": \"LIKELY\",\n"
+ " \"boundingBoxRes\": [\n"
+ " {\n"
+ " \"x\": 0,\n"
+ " \"y\": 0,\n"
+ " \"x\": 317,\n"
+ " \"y\": 517,\n"
+ " \"width\": 61,\n"
+ " \"height\": 112\n"
+ " }\n"
Expand All @@ -129,19 +132,19 @@ public interface FbPostControllerDocs {
+ " ]\n"
+ " },\n"
+ " {\n"
+ " \"pictureUrl\": \"https://scontent-gmp1-1.xx.fbcdn.net/v/t39.30808-6/481660333_122110815518769823_8695251980723261352_n.jpg?stp=dst-jpg_s960x960_tt6&_nc_cat=107&ccb=1-7&_nc_sid=127cfc&_nc_ohc=tNw5uyzpP4gQ7kNvgFaUSzy&_nc_oc=AdgNS_NCXQvdYi0SgeISTbhNFApOfOkD1Sl_JdC6fvn7fpMSPJBJkz6ouL23GQCQfuI&_nc_zt=23&_nc_ht=scontent-gmp1-1.xx&edm=AP4hL3IEAAAA&_nc_gid=APgMhPiomivhYaHlQ0N7ogZ&oh=00_AYFp1fMpIXr7D6TfKutspe9qE-b1q_3xwJ0RWlOOerkOvA&oe=67D06CCD\",\n"
+ " \"pictureUrl\": \"https://scontent-gmp1-1.xx.fbcdn.net/v/t39.30808-6/481660333_122110815518769823_8695251980723261352_n.jpg?stp=dst-jpg_s960x960_tt6&_nc_cat=107&ccb=1-7&_nc_sid=127cfc&_nc_ohc=tNw5uyzpP4gQ7kNvgFaUSzy&_nc_oc=AdgNS_NCXQvdYi0SgeISTbhNFApOfOkD1Sl_JdC6fvn7fpMSPJBJkz6ouL23GQCQfuI&_nc_zt=23&_nc_ht=scontent-gmp1-1.xx&edm=AP4hL3IEAAAA&_nc_gid=AivqHWL5xXiL5ZL42RVLVvt&oh=00_AYHqdXOGj9ev1RSy5M6vllYeQeG5LDAV9RDoTG3H9JW6mg&oe=67D0A50D\",\n"
+ " \"photoDetectRes\": []\n"
+ " },\n"
+ " {\n"
+ " \"pictureUrl\": \"https://scontent-gmp1-1.xx.fbcdn.net/v/t39.30808-6/482017104_122110815512769823_160186683350267742_n.jpg?stp=dst-jpg_s960x960_tt6&_nc_cat=101&ccb=1-7&_nc_sid=127cfc&_nc_ohc=tm6myDkjx3MQ7kNvgF5sYwJ&_nc_oc=AdhOyAWpZwVmm2tD2EEkbkhPotc9zV-WEOKIHDIL0TdDQZynqgcQAd0Rg_9wnqXUa-Y&_nc_zt=23&_nc_ht=scontent-gmp1-1.xx&edm=AP4hL3IEAAAA&_nc_gid=APgMhPiomivhYaHlQ0N7ogZ&oh=00_AYHz8uyuq3j_H8Bpk_L4b2eMtQQF03OajuYazQouyt-SzQ&oe=67D0555D\",\n"
+ " \"pictureUrl\": \"https://scontent-gmp1-1.xx.fbcdn.net/v/t39.30808-6/482017104_122110815512769823_160186683350267742_n.jpg?stp=dst-jpg_s960x960_tt6&_nc_cat=101&ccb=1-7&_nc_sid=127cfc&_nc_ohc=tm6myDkjx3MQ7kNvgF5sYwJ&_nc_oc=AdhOyAWpZwVmm2tD2EEkbkhPotc9zV-WEOKIHDIL0TdDQZynqgcQAd0Rg_9wnqXUa-Y&_nc_zt=23&_nc_ht=scontent-gmp1-1.xx&edm=AP4hL3IEAAAA&_nc_gid=AivqHWL5xXiL5ZL42RVLVvt&oh=00_AYFQg325NGwS_dFQSSFsf9vM2hyYg2p0lyskFRwrzYy53A&oe=67D08D9D\",\n"
+ " \"photoDetectRes\": []\n"
+ " },\n"
+ " {\n"
+ " \"pictureUrl\": \"https://scontent-gmp1-1.xx.fbcdn.net/v/t39.30808-6/481771934_122110815506769823_5550061559215650391_n.jpg?stp=dst-jpg_p720x720_tt6&_nc_cat=107&ccb=1-7&_nc_sid=127cfc&_nc_ohc=OL0Fw7PcimsQ7kNvgHOKkl5&_nc_oc=AdhoVSqUqxSa0f67GtTVnVZAyLWXH4ZZQ0RS5n9rZaSbj1bDkuuA79MDFkGcEQfVW2E&_nc_zt=23&_nc_ht=scontent-gmp1-1.xx&edm=AP4hL3IEAAAA&_nc_gid=APgMhPiomivhYaHlQ0N7ogZ&oh=00_AYFHmdkt3hxwMgV-JVz6U6HFM8g5VICr0lHYYPSipDHZgQ&oe=67D04EA9\",\n"
+ " \"pictureUrl\": \"https://scontent-gmp1-1.xx.fbcdn.net/v/t39.30808-6/481771934_122110815506769823_5550061559215650391_n.jpg?stp=dst-jpg_p720x720_tt6&_nc_cat=107&ccb=1-7&_nc_sid=127cfc&_nc_ohc=OL0Fw7PcimsQ7kNvgHOKkl5&_nc_oc=AdhoVSqUqxSa0f67GtTVnVZAyLWXH4ZZQ0RS5n9rZaSbj1bDkuuA79MDFkGcEQfVW2E&_nc_zt=23&_nc_ht=scontent-gmp1-1.xx&edm=AP4hL3IEAAAA&_nc_gid=AivqHWL5xXiL5ZL42RVLVvt&oh=00_AYFpIZy47V6giC5p30SmlhI-jXt9e-Kklhfez7Ez6gilEg&oe=67D086E9\",\n"
+ " \"photoDetectRes\": []\n"
+ " },\n"
+ " {\n"
+ " \"pictureUrl\": \"https://scontent-gmp1-1.xx.fbcdn.net/v/t39.30808-6/482010082_122110815596769823_3206669003289189272_n.jpg?stp=dst-jpg_s960x960_tt6&_nc_cat=101&ccb=1-7&_nc_sid=127cfc&_nc_ohc=0NL_Dl9eJPIQ7kNvgH_LG7T&_nc_oc=AdiJ1S12kB4r37eXclp5Sn7TsGKWQvkaMhb7Zci2YfSDRtUqFz3qe0A9TmkVCn8OE5g&_nc_zt=23&_nc_ht=scontent-gmp1-1.xx&edm=AP4hL3IEAAAA&_nc_gid=APgMhPiomivhYaHlQ0N7ogZ&oh=00_AYFDtbA0NTz8R74OOg1eBFY5YrdvuMZ_OiT5KI4-xFI39Q&oe=67D048A2\",\n"
+ " \"pictureUrl\": \"https://scontent-gmp1-1.xx.fbcdn.net/v/t39.30808-6/482010082_122110815596769823_3206669003289189272_n.jpg?stp=dst-jpg_s960x960_tt6&_nc_cat=101&ccb=1-7&_nc_sid=127cfc&_nc_ohc=0NL_Dl9eJPIQ7kNvgH_LG7T&_nc_oc=AdiJ1S12kB4r37eXclp5Sn7TsGKWQvkaMhb7Zci2YfSDRtUqFz3qe0A9TmkVCn8OE5g&_nc_zt=23&_nc_ht=scontent-gmp1-1.xx&edm=AP4hL3IEAAAA&_nc_gid=AivqHWL5xXiL5ZL42RVLVvt&oh=00_AYHWl7gwLIBPjBJqEtFZwag0TTRHXMvGmiVxJgNbX8rlnw&oe=67D080E2\",\n"
+ " \"photoDetectRes\": []\n"
+ " }\n"
+ " ]\n"
Expand Down Expand Up @@ -216,5 +219,92 @@ public interface FbPostControllerDocs {
})
ResponseEntity<ResponseDto> getTotalPost(Member member, DetectKeyword detectType, String keyword, int page,
int size);



@Operation(summary = "게시물 직접 입력 분석 요청", description = "게시물 파일 1개와 message 1개에 대한 분석을 제공합니다.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "Ok",
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = ResponseDto.class),
examples =
@ExampleObject(value = "{\n"
+ " \"code\": 200,\n"
+ " \"message\": \"OK\",\n"
+ " \"data\": {\n"
+ " \"textRes\": [\n"
+ " {\n"
+ " \"quote\": \"010-1234-1234\",\n"
+ " \"infoType\": \"PHONE_NUMBER\",\n"
+ " \"likelihood\": \"VERY_LIKELY\",\n"
+ " \"start\": 0,\n"
+ " \"end\": 13\n"
+ " }\n"
+ " ],\n"
+ " \"imageRes\": [\n"
+ " {\n"
+ " \"quote\": \"MAJALENGKA\",\n"
+ " \"infoType\": \"PERSON_NAME\",\n"
+ " \"likelihood\": \"POSSIBLE\",\n"
+ " \"imageLocation\": [\n"
+ " [\n"
+ " {\n"
+ " \"top\": 99,\n"
+ " \"left\": 236,\n"
+ " \"width\": 54,\n"
+ " \"height\": 8\n"
+ " }\n"
+ " ]\n"
+ " ]\n"
+ " },\n"
+ " {\n"
+ " \"quote\": \"MAJALENGKA\",\n"
+ " \"infoType\": \"LOCATION\",\n"
+ " \"likelihood\": \"LIKELY\",\n"
+ " \"imageLocation\": [\n"
+ " [\n"
+ " {\n"
+ " \"top\": 115,\n"
+ " \"left\": 411,\n"
+ " \"width\": 55,\n"
+ " \"height\": 8\n"
+ " }\n"
+ " ]\n"
+ " ]\n"
+ " },\n"
+ " {\n"
+ " \"quote\": \"BLORA\",\n"
+ " \"infoType\": \"LOCATION\",\n"
+ " \"likelihood\": \"LIKELY\",\n"
+ " \"imageLocation\": [\n"
+ " [\n"
+ " {\n"
+ " \"top\": 131,\n"
+ " \"left\": 124,\n"
+ " \"width\": 28,\n"
+ " \"height\": 9\n"
+ " }\n"
+ " ]\n"
+ " ]\n"
+ " }\n"
+ " ]\n"
+ " }\n"
+ "}")
)
),
@ApiResponse(responseCode = "400", description = "잘못된 요청입니다.",
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = ResponseDto.class),
examples = @ExampleObject(value = "[" +
"{ \"code\": 400, \"message\": \"파일은 필수 값입니다.\" }," +
"{ \"code\": 400, \"message\": \"메세지는 필수 값입니다.\" }" +
"]"
)
)
),
})
ResponseEntity<ResponseDto> postDetectPost(MultipartFile file,String message);
}

Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@

@Getter
public enum FbPostErrorCode implements ErrorCode {
INVALID_QUIZ_ID(HttpStatus.NOT_FOUND, "권한이 없거나 존재하지 않는 게시물입니다.");
INVALID_QUIZ_ID(HttpStatus.NOT_FOUND, "권한이 없거나 존재하지 않는 게시물입니다."),
FILE_CANNOT_BE_EMPTY(HttpStatus.BAD_REQUEST, "파일은 필수 값입니다."),
MESSAGE_CANNOT_BE_EMPTY(HttpStatus.BAD_REQUEST, "메세지는 필수 값입니다.");

private final HttpStatus httpStatus;
private final String message;
Expand Down
Loading