[ADD] S3 파일 업로드 및 외부 DB 등록 API 추가, 관련 Service, Controller 로직 구현, AWS S3 및 관련 Dependencies 설정

main
bjkim 8 months ago
parent a976415504
commit ab0281eae8

@ -59,6 +59,10 @@ dependencies {
testCompileOnly("org.projectlombok:lombok:1.18.42") testCompileOnly("org.projectlombok:lombok:1.18.42")
testAnnotationProcessor("org.projectlombok:lombok:1.18.42") testAnnotationProcessor("org.projectlombok:lombok:1.18.42")
implementation("io.awspring.cloud:spring-cloud-aws-starter:3.4.0")
implementation("software.amazon.awssdk:s3:2.35.10")
// 테스트 // 테스트
testImplementation("org.springframework.boot:spring-boot-starter-test") testImplementation("org.springframework.boot:spring-boot-starter-test")
testImplementation("org.springframework.security:spring-security-test") testImplementation("org.springframework.security:spring-security-test")

@ -0,0 +1,32 @@
package kr.re.etri.autoflow.common;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client;
@Configuration
public class AwsConfig {
@Value("${cloud.aws.credentials.access-key}")
private String accessKey;
@Value("${cloud.aws.credentials.secret-key}")
private String secretKey;
@Value("${cloud.aws.region.static}")
private String region;
@Bean
public S3Client s3Client() {
AwsBasicCredentials credentials = AwsBasicCredentials.create(accessKey, secretKey);
return S3Client.builder()
.region(Region.of(region))
.credentialsProvider(StaticCredentialsProvider.create(credentials))
.build();
}
}

@ -3,12 +3,16 @@ package kr.re.etri.autoflow.controllers;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn; import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
import kr.re.etri.autoflow.payload.request.EdgeSWVO; import kr.re.etri.autoflow.payload.request.EdgeSWVO;
import kr.re.etri.autoflow.service.EdgeSWUploadService;
import kr.re.etri.autoflow.service.ExternalAuthService; import kr.re.etri.autoflow.service.ExternalAuthService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.FileSystemResource;
import org.springframework.http.*; import org.springframework.http.*;
import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.http.client.SimpleClientHttpRequestFactory;
@ -22,19 +26,23 @@ import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext; import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager; import javax.net.ssl.X509TrustManager;
import java.io.File;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map; import java.util.Map;
@RestController @RestController
@RequestMapping("/api/external-auth") @RequestMapping("/api/external-auth")
@Tag(name = "ExternalAuthController", description = "외부 백엔드 로그인 및 Bearer 토큰 조회 API") @Tag(name = "ExternalAuthController", description = "외부 백엔드 로그인 및 Bearer 토큰 조회 API")
@RequiredArgsConstructor @RequiredArgsConstructor
@Slf4j
public class ExternalAuthController { public class ExternalAuthController {
private final ExternalAuthService externalAuthService; private final ExternalAuthService externalAuthService;
private final EdgeSWUploadService edgeSWUploadService;
private RestTemplate restTemplate; private RestTemplate restTemplate;
@PostConstruct @PostConstruct
@ -87,64 +95,52 @@ public class ExternalAuthController {
} }
} }
@Operation(summary = "Edge 패키지 목록 조회", description = "로그인 후 받은 id와 token을 사용해 외부 Edge 패키지 검색 API를 호출합니다.")
@GetMapping("/edge-search")
public ResponseEntity<ApiResponse> edgeSearch(
@RequestParam String id,
@RequestParam String token
) {
try {
Map<String, Object> edgeResult = externalAuthService.getEdgePackageList(id, token);
return ResponseEntity.ok(ApiResponse.success(edgeResult));
} catch (Exception e) {
return ResponseEntity.ok(ApiResponse.failure(e.getMessage()));
}
}
private final String TARGET_URL = "https://cuuva.com:24443/api/datamanager/edge-pkg/add";
@PostMapping(value = "/add", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@Operation( @Operation(
summary = "외부 Edge 패키지 등록", summary = "S3 업로드 + 외부 DB 등록",
description = "VO 정보와 파일을 업로드하여 외부 Edge 패키지를 등록합니다.", description = "파일을 S3에 업로드하고 외부 DB에 등록합니다.",
security = {@SecurityRequirement(name = "bearerAuth")} security = @SecurityRequirement(name = "bearerAuth") // 여기서 SecurityScheme 연결
) )
public ResponseEntity<?> addEdgePackage( @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@RequestPart("sw_id") String sw_id, public ResponseEntity<String> uploadEdgeSW(
@RequestPart("sw_name") String sw_name, @RequestParam("files") List<MultipartFile> files,
@RequestPart("sw_version") String sw_version, @RequestParam("sw_id") String swId,
@RequestPart("creation_datetime") String creation_datetime, @RequestParam("sw_version") String swVersion,
@RequestPart("auth_id") String auth_id, @RequestParam("sw_name") String swName,
@RequestPart(value = "files", required = false) MultipartFile file, // ✅ 수정: file → files @RequestParam("creation_datetime") String creationDatetime,
@RequestHeader("Authorization") String bearerToken @RequestParam("auth_id") String authId
) { ) {
try { try {
// VO 생성
EdgeSWVO edgeSWVO = new EdgeSWVO(); EdgeSWVO edgeSWVO = new EdgeSWVO();
edgeSWVO.setSw_id(sw_id); edgeSWVO.setFiles(files);
edgeSWVO.setSw_name(sw_name); edgeSWVO.setSw_id(swId);
edgeSWVO.setSw_version(sw_version); edgeSWVO.setSw_version(swVersion);
edgeSWVO.setCreation_datetime(creation_datetime); edgeSWVO.setSw_name(swName);
edgeSWVO.setAuth_id(auth_id); edgeSWVO.setCreation_datetime(creationDatetime);
edgeSWVO.setFile(file); // ✅ 반드시 추가해야 WebClient에서 파일 전송 가능 edgeSWVO.setAuth_id(authId);
// 토큰 정제 String result = edgeSWUploadService.uploadAndSend(edgeSWVO);
String token = bearerToken.startsWith("Bearer ") ? bearerToken.substring(7) : bearerToken;
// 서비스 호출
Map<String, Object> result = externalAuthService.addEdgePackage(token, edgeSWVO);
return ResponseEntity.ok(result); return ResponseEntity.ok(result);
} catch (Exception e) { } catch (Exception e) {
return ResponseEntity.badRequest().body(Map.of( return ResponseEntity.internalServerError().body("업로드 실패: " + e.getMessage());
"success", false,
"message", e.getMessage()
));
} }
} }
// @Operation(summary = "외부 DB 등록", description = "S3 업로드된 파일 정보를 외부 DB에 등록합니다.",
// security = @SecurityRequirement(name = "bearerAuth"))
// @PostMapping("/register")
// public ResponseEntity<String> registerMetadata(
// @RequestBody EdgeSWVO edgeSWVO
// ) {
// try {
// String result = edgeSWUploadService.registerMetadata(edgeSWVO);
// return ResponseEntity.ok(result);
// } catch (Exception e) {
// log.error("DB 등록 실패", e);
// return ResponseEntity.internalServerError().body("DB 등록 실패: " + e.getMessage());
// }
// }
// DTO: 요청 // DTO: 요청
public static record SigninRequest(String id, String password) { } public static record SigninRequest(String id, String password) { }

@ -1,19 +1,72 @@
package kr.re.etri.autoflow.payload.request; package kr.re.etri.autoflow.payload.request;
import lombok.Data; import lombok.Getter;
import lombok.Setter;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import java.io.Serializable; import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
@Data @Getter
public class EdgeSWVO { @Setter
public class EdgeSWVO implements Serializable {
private int sw_serial = -1;
private int ed_pkg_serial = -1;
private int sw_group = -1;
private String sw_group_name;
private int sw_type = -1;
private String sw_type_name;
private String sw_manufacturer;
private String package_id;
private String sw_id; private String sw_id;
private String sw_name;
private String sw_version; private String sw_version;
private String sw_name;
private String file_type;
private String file_name;
private String creation_datetime; private String creation_datetime;
private long file_size = -1;
private String file_location;
private int install_os = -1;
private String install_os_name;
private List<MultipartFile> files = new ArrayList<>();
private boolean newest_version = false;
private String sw_serial_list;
private int archive_type = -1;
private boolean hmac = false;
private String user_id;
private String window_root_location;
private String window_exe_name;
private String linux_root_location;
private String linux_exe_name;
private String edge_id;
private String location_number;
private String exec_yn;
private String download_location;
private String auth_id; private String auth_id;
// 파일 업로드용 private String secret_at;
private MultipartFile file; private boolean admin_at;
private String user_input_install_location;
// 파일 리스트 getter/setter (복사본 반환)
public List<MultipartFile> getFiles() {
if (this.files != null) {
return new ArrayList<>(this.files);
}
return null;
}
public void setFiles(List<MultipartFile> files) {
if (files != null) {
this.files = new ArrayList<>(files);
} else {
this.files = null;
}
}
} }

@ -0,0 +1,119 @@
package kr.re.etri.autoflow.service;
import kr.re.etri.autoflow.payload.request.EdgeSWVO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
import software.amazon.awssdk.core.sync.RequestBody;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@Service
public class EdgeSWUploadService {
private static final Logger log = LoggerFactory.getLogger(EdgeSWUploadService.class);
private final S3Client s3Client;
private final RestTemplate restTemplate;
private final String s3Bucket = "etri-advehicle-s3-bucket"; // S3 버킷 이름
private final String externalApiUrl = "https://a659120d3e2ff43ff94087b29396fd96-1057696791.ap-northeast-2.elb.amazonaws.com/api/datamanager/edge-sw/add-db";
public EdgeSWUploadService(S3Client s3Client, RestTemplate restTemplate) {
this.s3Client = s3Client;
this.restTemplate = restTemplate;
}
public String uploadAndSend(EdgeSWVO edgeSWVO) throws IOException {
// 1⃣ S3 업로드
for (MultipartFile file : edgeSWVO.getFiles()) {
File tempFile = convertMultiPartToFile(file);
String key = file.getOriginalFilename();
s3Client.putObject(
PutObjectRequest.builder()
.bucket(s3Bucket)
.key(key)
.build(),
RequestBody.fromFile(tempFile)
);
edgeSWVO.setFile_name(file.getOriginalFilename());
edgeSWVO.setFile_size(file.getSize());
edgeSWVO.setFile_location(key);
tempFile.delete();
}
// 2⃣ 외부 API 전송
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
for (MultipartFile file : edgeSWVO.getFiles()) {
File tempFile = convertMultiPartToFile(file);
body.add("files", new FileSystemResource(tempFile));
}
// EdgeSWVO의 다른 필드들을 body에 추가
body.add("sw_id", edgeSWVO.getSw_id());
body.add("sw_version", edgeSWVO.getSw_version());
body.add("sw_name", edgeSWVO.getSw_name());
body.add("creation_datetime", edgeSWVO.getCreation_datetime());
body.add("file_location", edgeSWVO.getFile_location());
body.add("auth_id", edgeSWVO.getAuth_id());
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
if (edgeSWVO.getAuth_id() != null && !edgeSWVO.getAuth_id().isEmpty()) {
headers.setBearerAuth(edgeSWVO.getAuth_id());
}
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body, headers);
ResponseEntity<String> response = restTemplate.postForEntity(externalApiUrl, requestEntity, String.class);
return response.getBody();
}
// public String registerMetadata(EdgeSWVO edgeSWVO) {
// // 외부 API로 전송할 JSON Body 구성
// Map<String, Object> jsonBody = new HashMap<>();
// jsonBody.put("sw_id", edgeSWVO.getSw_id());
// jsonBody.put("sw_version", edgeSWVO.getSw_version());
// jsonBody.put("sw_name", edgeSWVO.getSw_name());
// jsonBody.put("creation_datetime", edgeSWVO.getCreation_datetime());
// jsonBody.put("file_name", edgeSWVO.getFile_name());
// jsonBody.put("file_size", edgeSWVO.getFile_size());
// jsonBody.put("file_location", edgeSWVO.getFile_location());
// jsonBody.put("auth_id", edgeSWVO.getAuth_id());
// // 필요에 따라 추가 필드도 넣기
//
// HttpHeaders headers = new HttpHeaders();
// headers.setContentType(MediaType.APPLICATION_JSON);
//
// // JWT 인증 헤더 추가
// if (edgeSWVO.getAuth_id() != null && !edgeSWVO.getAuth_id().isEmpty()) {
// headers.setBearerAuth(edgeSWVO.getAuth_id());
// }
//
// HttpEntity<Map<String, Object>> requestEntity = new HttpEntity<>(jsonBody, headers);
//
// ResponseEntity<String> response = restTemplate.postForEntity(externalApiUrl, requestEntity, String.class);
//
// return response.getBody();
// }
private File convertMultiPartToFile(MultipartFile file) throws IOException {
File convFile = File.createTempFile("upload-", file.getOriginalFilename());
try (FileOutputStream fos = new FileOutputStream(convFile)) {
fos.write(file.getBytes());
}
return convFile;
}
}

@ -157,60 +157,60 @@ public class ExternalAuthService {
} }
} }
public Map<String, Object> addEdgePackage(String token, EdgeSWVO edgeSWVO) throws IOException { // public Map<String, Object> addEdgePackage(String token, EdgeSWVO edgeSWVO) throws IOException {
//
HttpHeaders headers = new HttpHeaders(); // HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA); // headers.setContentType(MediaType.MULTIPART_FORM_DATA);
headers.setBearerAuth(token); // headers.setBearerAuth(token);
//
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>(); // MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
//
// 문자열 필드들 // // 문자열 필드들
body.add("sw_id", edgeSWVO.getSw_id()); // body.add("sw_id", edgeSWVO.getSw_id());
body.add("sw_name", edgeSWVO.getSw_name()); // body.add("sw_name", edgeSWVO.getSw_name());
body.add("sw_version", edgeSWVO.getSw_version()); // body.add("sw_version", edgeSWVO.getSw_version());
body.add("creation_datetime", edgeSWVO.getCreation_datetime()); // body.add("creation_datetime", edgeSWVO.getCreation_datetime());
body.add("auth_id", edgeSWVO.getAuth_id()); // body.add("auth_id", edgeSWVO.getAuth_id());
//
// 파일 필드 // // 파일 필드
if (edgeSWVO.getFile() != null && !edgeSWVO.getFile().isEmpty()) { // if (edgeSWVO.getFile() != null && !edgeSWVO.getFile().isEmpty()) {
MultipartFile file = edgeSWVO.getFile(); // MultipartFile file = edgeSWVO.getFile();
//
// Content-Type 지정 + 파일명 유지 // // Content-Type 지정 + 파일명 유지
ByteArrayResource fileResource = new ByteArrayResource(file.getBytes()) { // ByteArrayResource fileResource = new ByteArrayResource(file.getBytes()) {
@Override // @Override
public String getFilename() { // public String getFilename() {
return file.getOriginalFilename(); // return file.getOriginalFilename();
} // }
//
@Override // @Override
public long contentLength() { // public long contentLength() {
return file.getSize(); // return file.getSize();
} // }
}; // };
//
HttpHeaders fileHeaders = new HttpHeaders(); // HttpHeaders fileHeaders = new HttpHeaders();
fileHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM); // fileHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
//
HttpEntity<ByteArrayResource> fileEntity = new HttpEntity<>(fileResource, fileHeaders); // HttpEntity<ByteArrayResource> fileEntity = new HttpEntity<>(fileResource, fileHeaders);
body.add("files", fileEntity); // ✅ 반드시 "files" // body.add("files", fileEntity); // ✅ 반드시 "files"
} // }
//
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body, headers); // HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body, headers);
//
try { // try {
ResponseEntity<Map> response = restTemplate.postForEntity(edgeAddUrl, requestEntity, Map.class); // ResponseEntity<Map> response = restTemplate.postForEntity(edgeAddUrl, requestEntity, Map.class);
//
if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) { // if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) {
return response.getBody(); // return response.getBody();
} else { // } else {
throw new RuntimeException("외부 Edge 패키지 등록 실패 (응답 코드: " + response.getStatusCode() + ")"); // throw new RuntimeException("외부 Edge 패키지 등록 실패 (응답 코드: " + response.getStatusCode() + ")");
} // }
//
} catch (HttpClientErrorException | HttpServerErrorException e) { // } catch (HttpClientErrorException | HttpServerErrorException e) {
throw new RuntimeException( // throw new RuntimeException(
e.getStatusCode().value() + " on POST request for \"" + edgeAddUrl + "\": \"" + e.getResponseBodyAsString() + "\"" // e.getStatusCode().value() + " on POST request for \"" + edgeAddUrl + "\": \"" + e.getResponseBodyAsString() + "\""
); // );
} // }
} // }
} }

@ -75,3 +75,6 @@ spring.servlet.multipart.enabled=true
external.auth.signin-url=https://cuuva.com:24443/api/datamanager/user/signin external.auth.signin-url=https://cuuva.com:24443/api/datamanager/user/signin
external.auth.edge-search-url=https://cuuva.com:24443/api/datamanager/edge-pkg/search external.auth.edge-search-url=https://cuuva.com:24443/api/datamanager/edge-pkg/search
cloud.aws.region.static=ap-northeast-2
cloud.aws.credentials.access-key=AKIA2UC3EPERDDR4UOWN
cloud.aws.credentials.secret-key=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
Loading…
Cancel
Save