From ab0281eae8eb2c8d6f5fb795e1e6777599b69b51 Mon Sep 17 00:00:00 2001 From: bjkim Date: Mon, 20 Oct 2025 21:00:18 +0900 Subject: [PATCH] =?UTF-8?q?[ADD]=20S3=20=ED=8C=8C=EC=9D=BC=20=EC=97=85?= =?UTF-8?q?=EB=A1=9C=EB=93=9C=20=EB=B0=8F=20=EC=99=B8=EB=B6=80=20DB=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D=20API=20=EC=B6=94=EA=B0=80,=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20Service,=20Controller=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84,=20AWS=20S3=20=EB=B0=8F=20=EA=B4=80=EB=A0=A8?= =?UTF-8?q?=20Dependencies=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle.kts | 4 + .../kr/re/etri/autoflow/common/AwsConfig.java | 32 +++++ .../controllers/ExternalAuthController.java | 92 +++++++------- .../autoflow/payload/request/EdgeSWVO.java | 65 +++++++++- .../autoflow/service/EdgeSWUploadService.java | 119 ++++++++++++++++++ .../autoflow/service/ExternalAuthService.java | 112 ++++++++--------- src/main/resources/application.properties | 3 + 7 files changed, 317 insertions(+), 110 deletions(-) create mode 100644 src/main/java/kr/re/etri/autoflow/common/AwsConfig.java create mode 100644 src/main/java/kr/re/etri/autoflow/service/EdgeSWUploadService.java diff --git a/build.gradle.kts b/build.gradle.kts index 129e90a..3c828dd 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -59,6 +59,10 @@ dependencies { testCompileOnly("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.security:spring-security-test") diff --git a/src/main/java/kr/re/etri/autoflow/common/AwsConfig.java b/src/main/java/kr/re/etri/autoflow/common/AwsConfig.java new file mode 100644 index 0000000..eb245ad --- /dev/null +++ b/src/main/java/kr/re/etri/autoflow/common/AwsConfig.java @@ -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(); + } +} diff --git a/src/main/java/kr/re/etri/autoflow/controllers/ExternalAuthController.java b/src/main/java/kr/re/etri/autoflow/controllers/ExternalAuthController.java index dfd0212..2e9c00a 100644 --- a/src/main/java/kr/re/etri/autoflow/controllers/ExternalAuthController.java +++ b/src/main/java/kr/re/etri/autoflow/controllers/ExternalAuthController.java @@ -3,12 +3,16 @@ package kr.re.etri.autoflow.controllers; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; 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.tags.Tag; import jakarta.annotation.PostConstruct; import kr.re.etri.autoflow.payload.request.EdgeSWVO; +import kr.re.etri.autoflow.service.EdgeSWUploadService; import kr.re.etri.autoflow.service.ExternalAuthService; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.core.io.FileSystemResource; import org.springframework.http.*; import org.springframework.http.client.SimpleClientHttpRequestFactory; @@ -22,19 +26,23 @@ import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; +import java.io.File; import java.nio.file.Files; import java.nio.file.Path; import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.List; import java.util.Map; @RestController @RequestMapping("/api/external-auth") @Tag(name = "ExternalAuthController", description = "외부 백엔드 로그인 및 Bearer 토큰 조회 API") @RequiredArgsConstructor +@Slf4j public class ExternalAuthController { private final ExternalAuthService externalAuthService; - + private final EdgeSWUploadService edgeSWUploadService; private RestTemplate restTemplate; @PostConstruct @@ -87,64 +95,52 @@ public class ExternalAuthController { } } - - @Operation(summary = "Edge 패키지 목록 조회", description = "로그인 후 받은 id와 token을 사용해 외부 Edge 패키지 검색 API를 호출합니다.") - @GetMapping("/edge-search") - public ResponseEntity edgeSearch( - @RequestParam String id, - @RequestParam String token - ) { - try { - Map 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( - summary = "외부 Edge 패키지 등록", - description = "VO 정보와 파일을 업로드하여 외부 Edge 패키지를 등록합니다.", - security = {@SecurityRequirement(name = "bearerAuth")} + summary = "S3 업로드 + 외부 DB 등록", + description = "파일을 S3에 업로드하고 외부 DB에 등록합니다.", + security = @SecurityRequirement(name = "bearerAuth") // 여기서 SecurityScheme 연결 ) - public ResponseEntity addEdgePackage( - @RequestPart("sw_id") String sw_id, - @RequestPart("sw_name") String sw_name, - @RequestPart("sw_version") String sw_version, - @RequestPart("creation_datetime") String creation_datetime, - @RequestPart("auth_id") String auth_id, - @RequestPart(value = "files", required = false) MultipartFile file, // ✅ 수정: file → files - @RequestHeader("Authorization") String bearerToken + @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public ResponseEntity uploadEdgeSW( + @RequestParam("files") List files, + @RequestParam("sw_id") String swId, + @RequestParam("sw_version") String swVersion, + @RequestParam("sw_name") String swName, + @RequestParam("creation_datetime") String creationDatetime, + @RequestParam("auth_id") String authId ) { - try { - // VO 생성 EdgeSWVO edgeSWVO = new EdgeSWVO(); - edgeSWVO.setSw_id(sw_id); - edgeSWVO.setSw_name(sw_name); - edgeSWVO.setSw_version(sw_version); - edgeSWVO.setCreation_datetime(creation_datetime); - edgeSWVO.setAuth_id(auth_id); - edgeSWVO.setFile(file); // ✅ 반드시 추가해야 WebClient에서 파일 전송 가능 - - // 토큰 정제 - String token = bearerToken.startsWith("Bearer ") ? bearerToken.substring(7) : bearerToken; - - // 서비스 호출 - Map result = externalAuthService.addEdgePackage(token, edgeSWVO); - + edgeSWVO.setFiles(files); + edgeSWVO.setSw_id(swId); + edgeSWVO.setSw_version(swVersion); + edgeSWVO.setSw_name(swName); + edgeSWVO.setCreation_datetime(creationDatetime); + edgeSWVO.setAuth_id(authId); + + String result = edgeSWUploadService.uploadAndSend(edgeSWVO); return ResponseEntity.ok(result); } catch (Exception e) { - return ResponseEntity.badRequest().body(Map.of( - "success", false, - "message", e.getMessage() - )); + return ResponseEntity.internalServerError().body("업로드 실패: " + e.getMessage()); } } +// @Operation(summary = "외부 DB 등록", description = "S3 업로드된 파일 정보를 외부 DB에 등록합니다.", +// security = @SecurityRequirement(name = "bearerAuth")) +// @PostMapping("/register") +// public ResponseEntity 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: 요청 public static record SigninRequest(String id, String password) { } diff --git a/src/main/java/kr/re/etri/autoflow/payload/request/EdgeSWVO.java b/src/main/java/kr/re/etri/autoflow/payload/request/EdgeSWVO.java index 3d3bf67..7f30860 100644 --- a/src/main/java/kr/re/etri/autoflow/payload/request/EdgeSWVO.java +++ b/src/main/java/kr/re/etri/autoflow/payload/request/EdgeSWVO.java @@ -1,19 +1,72 @@ package kr.re.etri.autoflow.payload.request; -import lombok.Data; +import lombok.Getter; +import lombok.Setter; import org.springframework.web.multipart.MultipartFile; import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; -@Data -public class EdgeSWVO { +@Getter +@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_name; private String sw_version; + private String sw_name; + private String file_type; + private String file_name; 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 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 MultipartFile file; + private String secret_at; + private boolean admin_at; + + private String user_input_install_location; + + // 파일 리스트 getter/setter (복사본 반환) + public List getFiles() { + if (this.files != null) { + return new ArrayList<>(this.files); + } + return null; + } + + public void setFiles(List files) { + if (files != null) { + this.files = new ArrayList<>(files); + } else { + this.files = null; + } + } } diff --git a/src/main/java/kr/re/etri/autoflow/service/EdgeSWUploadService.java b/src/main/java/kr/re/etri/autoflow/service/EdgeSWUploadService.java new file mode 100644 index 0000000..167ac28 --- /dev/null +++ b/src/main/java/kr/re/etri/autoflow/service/EdgeSWUploadService.java @@ -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 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> requestEntity = new HttpEntity<>(body, headers); + + ResponseEntity response = restTemplate.postForEntity(externalApiUrl, requestEntity, String.class); + + return response.getBody(); + } + +// public String registerMetadata(EdgeSWVO edgeSWVO) { +// // 외부 API로 전송할 JSON Body 구성 +// Map 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> requestEntity = new HttpEntity<>(jsonBody, headers); +// +// ResponseEntity 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; + } +} diff --git a/src/main/java/kr/re/etri/autoflow/service/ExternalAuthService.java b/src/main/java/kr/re/etri/autoflow/service/ExternalAuthService.java index e8201b4..b3ef434 100644 --- a/src/main/java/kr/re/etri/autoflow/service/ExternalAuthService.java +++ b/src/main/java/kr/re/etri/autoflow/service/ExternalAuthService.java @@ -157,60 +157,60 @@ public class ExternalAuthService { } } - public Map addEdgePackage(String token, EdgeSWVO edgeSWVO) throws IOException { - - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.MULTIPART_FORM_DATA); - headers.setBearerAuth(token); - - MultiValueMap body = new LinkedMultiValueMap<>(); - - // 문자열 필드들 - body.add("sw_id", edgeSWVO.getSw_id()); - body.add("sw_name", edgeSWVO.getSw_name()); - body.add("sw_version", edgeSWVO.getSw_version()); - body.add("creation_datetime", edgeSWVO.getCreation_datetime()); - body.add("auth_id", edgeSWVO.getAuth_id()); - - // 파일 필드 - if (edgeSWVO.getFile() != null && !edgeSWVO.getFile().isEmpty()) { - MultipartFile file = edgeSWVO.getFile(); - - // Content-Type 지정 + 파일명 유지 - ByteArrayResource fileResource = new ByteArrayResource(file.getBytes()) { - @Override - public String getFilename() { - return file.getOriginalFilename(); - } - - @Override - public long contentLength() { - return file.getSize(); - } - }; - - HttpHeaders fileHeaders = new HttpHeaders(); - fileHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM); - - HttpEntity fileEntity = new HttpEntity<>(fileResource, fileHeaders); - body.add("files", fileEntity); // ✅ 반드시 "files" - } - - HttpEntity> requestEntity = new HttpEntity<>(body, headers); - - try { - ResponseEntity response = restTemplate.postForEntity(edgeAddUrl, requestEntity, Map.class); - - if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) { - return response.getBody(); - } else { - throw new RuntimeException("외부 Edge 패키지 등록 실패 (응답 코드: " + response.getStatusCode() + ")"); - } - - } catch (HttpClientErrorException | HttpServerErrorException e) { - throw new RuntimeException( - e.getStatusCode().value() + " on POST request for \"" + edgeAddUrl + "\": \"" + e.getResponseBodyAsString() + "\"" - ); - } - } +// public Map addEdgePackage(String token, EdgeSWVO edgeSWVO) throws IOException { +// +// HttpHeaders headers = new HttpHeaders(); +// headers.setContentType(MediaType.MULTIPART_FORM_DATA); +// headers.setBearerAuth(token); +// +// MultiValueMap body = new LinkedMultiValueMap<>(); +// +// // 문자열 필드들 +// body.add("sw_id", edgeSWVO.getSw_id()); +// body.add("sw_name", edgeSWVO.getSw_name()); +// body.add("sw_version", edgeSWVO.getSw_version()); +// body.add("creation_datetime", edgeSWVO.getCreation_datetime()); +// body.add("auth_id", edgeSWVO.getAuth_id()); +// +// // 파일 필드 +// if (edgeSWVO.getFile() != null && !edgeSWVO.getFile().isEmpty()) { +// MultipartFile file = edgeSWVO.getFile(); +// +// // Content-Type 지정 + 파일명 유지 +// ByteArrayResource fileResource = new ByteArrayResource(file.getBytes()) { +// @Override +// public String getFilename() { +// return file.getOriginalFilename(); +// } +// +// @Override +// public long contentLength() { +// return file.getSize(); +// } +// }; +// +// HttpHeaders fileHeaders = new HttpHeaders(); +// fileHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM); +// +// HttpEntity fileEntity = new HttpEntity<>(fileResource, fileHeaders); +// body.add("files", fileEntity); // ✅ 반드시 "files" +// } +// +// HttpEntity> requestEntity = new HttpEntity<>(body, headers); +// +// try { +// ResponseEntity response = restTemplate.postForEntity(edgeAddUrl, requestEntity, Map.class); +// +// if (response.getStatusCode().is2xxSuccessful() && response.getBody() != null) { +// return response.getBody(); +// } else { +// throw new RuntimeException("외부 Edge 패키지 등록 실패 (응답 코드: " + response.getStatusCode() + ")"); +// } +// +// } catch (HttpClientErrorException | HttpServerErrorException e) { +// throw new RuntimeException( +// e.getStatusCode().value() + " on POST request for \"" + edgeAddUrl + "\": \"" + e.getResponseBodyAsString() + "\"" +// ); +// } +// } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 488d8a1..78f19e7 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -75,3 +75,6 @@ spring.servlet.multipart.enabled=true 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 +cloud.aws.region.static=ap-northeast-2 +cloud.aws.credentials.access-key=AKIA2UC3EPERDDR4UOWN +cloud.aws.credentials.secret-key=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 \ No newline at end of file