From b31f45a6a0d8a3fe56c5656a724bee1b846e8a55 Mon Sep 17 00:00:00 2001 From: TigerDemon Date: Tue, 24 Jun 2025 20:05:03 +0900 Subject: [PATCH 1/5] drivetest1 --- .../com/boot/swlugweb/v1/DriveQuickstart.java | 99 ++++++ .../boot/swlugweb/v1/blog/BlogController.java | 219 ++++++++----- .../boot/swlugweb/v1/blog/BlogCreateDto.java | 4 +- .../v1/blog/BlogDetailResponseDto.java | 1 + .../com/boot/swlugweb/v1/blog/BlogDomain.java | 4 + .../com/boot/swlugweb/v1/blog/BlogDto.java | 21 +- .../boot/swlugweb/v1/blog/BlogService.java | 310 +++++++++++++----- .../v1/blog/BlogUpdateRequestDto.java | 1 + .../swlugweb/v1/blog/GoogleDriveService.java | 219 +++++++++++++ 9 files changed, 712 insertions(+), 166 deletions(-) create mode 100644 src/main/java/com/boot/swlugweb/v1/DriveQuickstart.java create mode 100644 src/main/java/com/boot/swlugweb/v1/blog/GoogleDriveService.java diff --git a/src/main/java/com/boot/swlugweb/v1/DriveQuickstart.java b/src/main/java/com/boot/swlugweb/v1/DriveQuickstart.java new file mode 100644 index 0000000..a74f435 --- /dev/null +++ b/src/main/java/com/boot/swlugweb/v1/DriveQuickstart.java @@ -0,0 +1,99 @@ +package com.boot.swlugweb.v1; + +import com.google.api.client.auth.oauth2.Credential; +import com.google.api.client.extensions.java6.auth.oauth2.AuthorizationCodeInstalledApp; +import com.google.api.client.extensions.jetty.auth.oauth2.LocalServerReceiver; +import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow; +import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets; +import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport; +import com.google.api.client.http.javanet.NetHttpTransport; +import com.google.api.client.json.JsonFactory; +import com.google.api.client.json.gson.GsonFactory; +import com.google.api.client.util.store.FileDataStoreFactory; +import com.google.api.services.drive.Drive; +import com.google.api.services.drive.DriveScopes; +import com.google.api.services.drive.model.File; +import com.google.api.services.drive.model.FileList; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.security.GeneralSecurityException; +import java.util.Collections; +import java.util.List; + +/* class to demonstrate use of Drive files list API */ +public class DriveQuickstart { + /** + * Application name. + */ + private static final String APPLICATION_NAME = "Google Drive API Java Quickstart"; + /** + * Global instance of the JSON factory. + */ + private static final JsonFactory JSON_FACTORY = GsonFactory.getDefaultInstance(); + /** + * Directory to store authorization tokens for this application. + */ + private static final String TOKENS_DIRECTORY_PATH = "tokens"; + + /** + * Global instance of the scopes required by this quickstart. + * If modifying these scopes, delete your previously saved tokens/ folder. + */ + private static final List SCOPES = + Collections.singletonList(DriveScopes.DRIVE_METADATA_READONLY); + private static final String CREDENTIALS_FILE_PATH = "credentials1.json"; + + /** + * Creates an authorized Credential object. + * + * @param HTTP_TRANSPORT The network HTTP Transport. + * @return An authorized Credential object. + * @throws IOException If the credentials.json file cannot be found. + */ + private static Credential getCredentials(final NetHttpTransport HTTP_TRANSPORT) + throws IOException { + // Load client secrets. + InputStream in = DriveQuickstart.class.getResourceAsStream(CREDENTIALS_FILE_PATH); + if (in == null) { + throw new FileNotFoundException("Resource not found: " + CREDENTIALS_FILE_PATH); + } + GoogleClientSecrets clientSecrets = + GoogleClientSecrets.load(JSON_FACTORY, new InputStreamReader(in)); + + // Build flow and trigger user authorization request. + GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder( + HTTP_TRANSPORT, JSON_FACTORY, clientSecrets, SCOPES) + .setDataStoreFactory(new FileDataStoreFactory(new java.io.File(TOKENS_DIRECTORY_PATH))) + .setAccessType("offline") + .build(); + LocalServerReceiver receiver = new LocalServerReceiver.Builder().setPort(8888).build(); + Credential credential = new AuthorizationCodeInstalledApp(flow, receiver).authorize("user"); + //returns an authorized Credential object. + return credential; + } + + public static void main(String... args) throws IOException, GeneralSecurityException { + // Build a new authorized API client service. + final NetHttpTransport HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport(); + Drive service = new Drive.Builder(HTTP_TRANSPORT, JSON_FACTORY, getCredentials(HTTP_TRANSPORT)) + .setApplicationName(APPLICATION_NAME) + .build(); + + // Print the names and IDs for up to 10 files. + FileList result = service.files().list() + .setPageSize(10) + .setFields("nextPageToken, files(id, name)") + .execute(); + List files = result.getFiles(); + if (files == null || files.isEmpty()) { + System.out.println("No files found."); + } else { + System.out.println("Files:"); + for (File file : files) { + System.out.printf("%s (%s)\n", file.getName(), file.getId()); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/com/boot/swlugweb/v1/blog/BlogController.java b/src/main/java/com/boot/swlugweb/v1/blog/BlogController.java index 7941c2e..c07f22c 100644 --- a/src/main/java/com/boot/swlugweb/v1/blog/BlogController.java +++ b/src/main/java/com/boot/swlugweb/v1/blog/BlogController.java @@ -12,6 +12,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -21,13 +22,16 @@ @RequestMapping("/api/blog") public class BlogController { - @Value("${file.upload-dir}") - private String uploadDir; +// @Value("${file.upload-dir}") +// private String uploadDir; private final BlogService blogService; + private final GoogleDriveService googleDriveService; - public BlogController(BlogService blogService) { + public BlogController(BlogService blogService,GoogleDriveService googleDriveService) + { this.blogService = blogService; + this.googleDriveService = googleDriveService; } @GetMapping @@ -49,77 +53,144 @@ public ResponseEntity getBlogDetail(@RequestBody Map saveBlog( +// @RequestPart("blogCreateDto") BlogCreateDto blogCreateDto, +// @RequestPart(name = "imageFiles", required = false) List imageFiles, +// HttpSession session) { +// +// String userId = (String) session.getAttribute("USER"); +// if (userId == null) return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); +// +// try { +// List imageUrls = new ArrayList<>(); +// +// if (imageFiles != null && !imageFiles.isEmpty()) { +// for (MultipartFile file : imageFiles) { +// String url = googleDriveService.uploadFile(file); +// imageUrls.add(url); +// } +// } +// +// blogCreateDto.setImageUrl(imageUrls); +// blogCreateDto.setImageFiles(imageFiles); +// blogService.createBlog(blogCreateDto, userId); +// +// return ResponseEntity.status(HttpStatus.FOUND) +// .header(HttpHeaders.LOCATION, "/api/blog") +// .build(); +// +// } catch (Exception e) { +// e.printStackTrace(); +// return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); +// } +// } + + //test1 0621 @PostMapping(value = "/save", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseEntity saveBlog( - @RequestPart(name = "blogCreateDto") BlogCreateDto blogCreateDto, + @RequestPart("blogCreateDto") BlogCreateDto blogCreateDto, @RequestPart(name = "imageFiles", required = false) List imageFiles, HttpSession session) { String userId = (String) session.getAttribute("USER"); - if (userId == null) { - return ResponseEntity.status(401).build(); - } + if (userId == null) return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); try { - // HTML 컨텐츠에서 이미지 처리를 위한 코드 추가 - if (blogCreateDto.getBoardContent() != null) { - blogCreateDto.setImageFiles(imageFiles); - } + // 👉 imageFiles만 DTO에 세팅 (업로드는 서비스에서 수행) + blogCreateDto.setImageFiles(imageFiles); + blogService.createBlog(blogCreateDto, userId); - return ResponseEntity.status(302) + + return ResponseEntity.status(HttpStatus.FOUND) .header(HttpHeaders.LOCATION, "/api/blog") .build(); + } catch (Exception e) { e.printStackTrace(); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } } + + + + + + //구글 버전 @PostMapping(value = "/update", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseEntity updateBlogPost( - @RequestPart(name = "blogUpdateRequestDto") BlogUpdateRequestDto blogUpdateRequestDto, + @RequestPart("blogUpdateRequestDto") BlogUpdateRequestDto blogUpdateRequestDto, @RequestPart(name = "imageFiles", required = false) List imageFiles, - HttpSession session - ) { + HttpSession session) { + String userId = (String) session.getAttribute("USER"); - if (userId == null) { - return ResponseEntity.status(401).build(); - } + if (userId == null) return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); try { + List imageUrls = new ArrayList<>(); if (imageFiles != null && !imageFiles.isEmpty()) { - blogUpdateRequestDto.setImageFiles(imageFiles); + for (MultipartFile file : imageFiles) { + String url = GoogleDriveService.uploadFile(file); + imageUrls.add(url); + } } + + blogUpdateRequestDto.setImageUrls(imageUrls); + blogUpdateRequestDto.setImageFiles(imageFiles); blogService.updateBlog(blogUpdateRequestDto, userId); - return ResponseEntity.status(302) + return ResponseEntity.status(HttpStatus.FOUND) .header(HttpHeaders.LOCATION, "/api/blog") .build(); + } catch (Exception e) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } } + @PostMapping("/delete") public ResponseEntity deleteBlog( @RequestBody BlogDeleteRequestDto blogDeleteRequestDto, - HttpSession session - ) { + HttpSession session) { + String userId = (String) session.getAttribute("USER"); - if (userId == null) { - return ResponseEntity.status(401).build(); - } + if (userId == null) return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); try { + List imageUrls = blogService.getImageUrlsByBlogId(blogDeleteRequestDto.getId()); + + for (String url : imageUrls) { +// String fileId = extractFileIdFromUrl(url); + googleDriveService.deleteFile(url); + } + blogService.deleteBlog(blogDeleteRequestDto, userId); - return ResponseEntity.status(302) + + return ResponseEntity.status(HttpStatus.FOUND) .header(HttpHeaders.LOCATION, "/api/blog") .build(); + } catch (Exception e) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } } +// +// // ✅ URL에서 fileId 추출 +// public String extractFileIdFromUrl(String url) { +// if (url.contains("drive.google.com/file/d/")) { +// int start = url.indexOf("/d/") + 3; +// int end = url.indexOf("/", start); +// if (start > 2 && end > start) { +// return url.substring(start, end); +// } +// } +// return null; +// } + + @GetMapping("/tags") public ResponseEntity> getTags() { @@ -134,51 +205,51 @@ public ResponseEntity> searchBlogsByAdjacent(@Reques return ResponseEntity.ok(adjacentBlogs); } - // 새로운 이미지 업로드 엔드포인트 - @PostMapping("/upload-image") - @ResponseBody - public ResponseEntity> uploadImage(@RequestParam("upload") MultipartFile file) { - try { - String imageUrl = blogService.saveImage(file); - Map response = new HashMap<>(); - response.put("uploaded", true); - response.put("url", imageUrl); - return ResponseEntity.ok(response); - } catch (Exception e) { - Map response = new HashMap<>(); - response.put("uploaded", false); - response.put("error", Map.of("message", "Image upload failed: " + e.getMessage())); - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response); - } - } - - // 이미지 조회 엔드포인트 - @GetMapping("/images/{filename:.+}") - @ResponseBody - public ResponseEntity serveImage(@PathVariable String filename) { - try { - Path imagePath = Paths.get(uploadDir).resolve(filename); - Resource resource = new UrlResource(imagePath.toUri()); - - if (resource.exists() && resource.isReadable()) { - String contentType = Files.probeContentType(imagePath); - if (contentType == null) { - contentType = "application/octet-stream"; - } - - // 캐시 설정 추가 - CacheControl cacheControl = CacheControl.maxAge(365, TimeUnit.DAYS); - - return ResponseEntity.ok() - .cacheControl(cacheControl) - .contentType(MediaType.parseMediaType(contentType)) - .header("Content-Disposition", "inline; filename=\"" + filename + "\"") - .body(resource); - } else { - return ResponseEntity.notFound().build(); - } - } catch (IOException e) { - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); - } - } +// // 새로운 이미지 업로드 엔드포인트 +// @PostMapping("/upload-image") +// @ResponseBody +// public ResponseEntity> uploadImage(@RequestParam("upload") MultipartFile file) { +// try { +// String imageUrl = blogService.saveImage(file); +// Map response = new HashMap<>(); +// response.put("uploaded", true); +// response.put("url", imageUrl); +// return ResponseEntity.ok(response); +// } catch (Exception e) { +// Map response = new HashMap<>(); +// response.put("uploaded", false); +// response.put("error", Map.of("message", "Image upload failed: " + e.getMessage())); +// return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response); +// } +// } +// +// // 이미지 조회 엔드포인트 +// @GetMapping("/images/{filename:.+}") +// @ResponseBody +// public ResponseEntity serveImage(@PathVariable String filename) { +// try { +// Path imagePath = Paths.get(uploadDir).resolve(filename); +// Resource resource = new UrlResource(imagePath.toUri()); +// +// if (resource.exists() && resource.isReadable()) { +// String contentType = Files.probeContentType(imagePath); +// if (contentType == null) { +// contentType = "application/octet-stream"; +// } +// +// // 캐시 설정 추가 +// CacheControl cacheControl = CacheControl.maxAge(365, TimeUnit.DAYS); +// +// return ResponseEntity.ok() +// .cacheControl(cacheControl) +// .contentType(MediaType.parseMediaType(contentType)) +// .header("Content-Disposition", "inline; filename=\"" + filename + "\"") +// .body(resource); +// } else { +// return ResponseEntity.notFound().build(); +// } +// } catch (IOException e) { +// return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); +// } +// } } \ No newline at end of file diff --git a/src/main/java/com/boot/swlugweb/v1/blog/BlogCreateDto.java b/src/main/java/com/boot/swlugweb/v1/blog/BlogCreateDto.java index f359c38..c5ebd32 100644 --- a/src/main/java/com/boot/swlugweb/v1/blog/BlogCreateDto.java +++ b/src/main/java/com/boot/swlugweb/v1/blog/BlogCreateDto.java @@ -3,13 +3,13 @@ import com.fasterxml.jackson.annotation.JsonFormat; import lombok.Builder; import lombok.Getter; -import lombok.NoArgsConstructor; import lombok.Setter; import org.springframework.web.multipart.MultipartFile; import java.time.LocalDateTime; import java.util.List; + @Setter @Getter @Builder @@ -22,6 +22,8 @@ public class BlogCreateDto { @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss") private LocalDateTime createAt; + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss") + private LocalDateTime updateAt; private List imageUrl; private List imageFiles; } diff --git a/src/main/java/com/boot/swlugweb/v1/blog/BlogDetailResponseDto.java b/src/main/java/com/boot/swlugweb/v1/blog/BlogDetailResponseDto.java index 6916aaf..a01dfde 100644 --- a/src/main/java/com/boot/swlugweb/v1/blog/BlogDetailResponseDto.java +++ b/src/main/java/com/boot/swlugweb/v1/blog/BlogDetailResponseDto.java @@ -14,6 +14,7 @@ public class BlogDetailResponseDto { private String boardTitle; private String boardContents; private LocalDateTime createAt; + private LocalDateTime updateAt; private String userId; private String nickname; private List tag; diff --git a/src/main/java/com/boot/swlugweb/v1/blog/BlogDomain.java b/src/main/java/com/boot/swlugweb/v1/blog/BlogDomain.java index 6ea6c93..7b72e7c 100644 --- a/src/main/java/com/boot/swlugweb/v1/blog/BlogDomain.java +++ b/src/main/java/com/boot/swlugweb/v1/blog/BlogDomain.java @@ -38,6 +38,10 @@ public class BlogDomain { @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") // JSON 직렬화 시 포맷 지정 private LocalDateTime createAt; + @Field("updated_at") + @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") + private LocalDateTime updateAt; + @Field("tag") private List tag; diff --git a/src/main/java/com/boot/swlugweb/v1/blog/BlogDto.java b/src/main/java/com/boot/swlugweb/v1/blog/BlogDto.java index 8988852..047c9a3 100644 --- a/src/main/java/com/boot/swlugweb/v1/blog/BlogDto.java +++ b/src/main/java/com/boot/swlugweb/v1/blog/BlogDto.java @@ -14,6 +14,7 @@ public class BlogDto { private Integer boardCategory; private String boardTitle; private LocalDateTime createAt; + private LocalDateTime updateAt; @JsonIgnore private String userId; private String nickname; @@ -28,13 +29,27 @@ public class BlogDto { private Integer isDelete = 0; private String thumbnailImage; // 필드는 유지 +// public String getThumbnailUrl() { +// if (image != null && !image.isEmpty()) { +// String firstImage = image.get(0); +// return firstImage.startsWith("/api/blog/images/") +// ? firstImage +// : "/api/blog/images/" + firstImage; +// } +// return "/img/apply_swlug.png"; +// } + + //test0621 public String getThumbnailUrl() { if (image != null && !image.isEmpty()) { String firstImage = image.get(0); - return firstImage.startsWith("/api/blog/images/") - ? firstImage - : "/api/blog/images/" + firstImage; + // 드라이브 URL이면 그대로 반환, 아니라면 경로 덧붙이기 + if (firstImage.startsWith("http")) { + return firstImage; + } + return "/api/blog/images/" + firstImage; } return "/img/apply_swlug.png"; } + } \ No newline at end of file diff --git a/src/main/java/com/boot/swlugweb/v1/blog/BlogService.java b/src/main/java/com/boot/swlugweb/v1/blog/BlogService.java index 910ae2e..b689079 100644 --- a/src/main/java/com/boot/swlugweb/v1/blog/BlogService.java +++ b/src/main/java/com/boot/swlugweb/v1/blog/BlogService.java @@ -10,16 +10,16 @@ import org.springframework.web.multipart.MultipartFile; import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.StandardCopyOption; + +import java.security.GeneralSecurityException; import java.time.LocalDateTime; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; +import static com.google.common.io.Files.getFileExtension; + @Service public class BlogService { @@ -28,82 +28,103 @@ public class BlogService { private final BlogRepository blogRepository; private final MyPageRepository myPageRepository; + private final GoogleDriveService googleDriveService; - public BlogService(BlogRepository blogRepository, MyPageRepository myPageRepository) { + public BlogService(BlogRepository blogRepository, MyPageRepository myPageRepository,GoogleDriveService googleDriveService) { this.blogRepository = blogRepository; this.myPageRepository = myPageRepository; + this.googleDriveService = googleDriveService; } - // BlogService.java - public String saveImage(MultipartFile file) throws IOException { - try { - // 파일 유효성 검사 - if (file.isEmpty()) { - throw new IllegalArgumentException("Empty file"); - } - - // 파일 크기 검사 (10MB) - if (file.getSize() > 20 * 1024 * 1024) { - throw new IllegalArgumentException("File size exceeds maximum limit"); - } - - // 파일 확장자 검사 - String originalFilename = StringUtils.cleanPath(file.getOriginalFilename()); - String extension = getFileExtension(originalFilename).toLowerCase(); - Set allowedExtensions = new HashSet<>(Arrays.asList( - "jpg", "jpeg", "png", "gif", "bmp", "webp", "heic", "heif", "tiff", "tif", "svg" - )); - - if (!allowedExtensions.contains(extension)) { - throw new IllegalArgumentException("Invalid file extension"); - } - - // 고유한 파일명 생성 - String newFilename = UUID.randomUUID().toString() + "." + extension; - Path uploadPath = Paths.get(uploadDir); - - // 업로드 디렉토리가 없으면 생성 - if (!Files.exists(uploadPath)) { - Files.createDirectories(uploadPath); - System.out.println("Created upload directory: " + uploadPath.toAbsolutePath()); - } - - // 파일 저장 - Path destinationFile = uploadPath.resolve(newFilename); - Files.copy(file.getInputStream(), destinationFile, StandardCopyOption.REPLACE_EXISTING); - System.out.println("File saved at: " + destinationFile.toAbsolutePath()); +// public String saveImage(MultipartFile file) throws Exception { +// if (file == null || file.isEmpty()) throw new IllegalArgumentException("Empty file"); +// +// if (file.getSize() > 20 * 1024 * 1024) // 20MB 제한 +// throw new IllegalArgumentException("File size exceeds maximum limit"); +// +// String originalFilename = StringUtils.cleanPath(file.getOriginalFilename()); +// String extension = getFileExtension(originalFilename).toLowerCase(); +// +// Set allowedExtensions = Set.of( +// "jpg", "jpeg", "png", "gif", "bmp", "webp", "heic", "heif", "tiff", "tif", "svg" +// ); +// +// if (!allowedExtensions.contains(extension)) { +// throw new IllegalArgumentException("Invalid file extension: " + extension); +// } +// +// return googleDriveService.uploadFile(file); +// } +// +// public BlogDomain createBlog(BlogCreateDto blogCreateDto, String userId) throws Exception { +// BlogDomain blogDomain = new BlogDomain(); +// +// blogDomain.setUserId(userId); +// blogDomain.setBoardCategory(blogCreateDto.getBoardCategory()); +// blogDomain.setBoardTitle(blogCreateDto.getBoardTitle()); +// blogDomain.setBoardContents(blogCreateDto.getBoardContent()); +// blogDomain.setCreateAt(LocalDateTime.now()); +// blogDomain.setUpdateAt(LocalDateTime.now()); +// blogDomain.setTag(blogCreateDto.getTag()); +// blogDomain.setIsPin(false); +// blogDomain.setIsSecure(0); +// blogDomain.setIsDelete(0); +// +// List uploadedImageUrls = new ArrayList<>(); +// +// try { +// // 1️⃣ 업로드된 이미지 파일 처리 +// if (blogCreateDto.getImageFiles() != null) { +// for (MultipartFile file : blogCreateDto.getImageFiles()) { +// uploadedImageUrls.add(saveImage(file)); +// } +// } +// +// // 2️⃣ 본문 HTML에 포함된 이미지 URL도 추출해서 저장 +// Pattern pattern = Pattern.compile("src=[\"']([^\"']+)[\"']"); +// Matcher matcher = pattern.matcher(blogCreateDto.getBoardContent()); +// +// while (matcher.find()) { +// String imageUrl = matcher.group(1); +// if (!uploadedImageUrls.contains(imageUrl)) { +// uploadedImageUrls.add(imageUrl); +// } +// } +// +// } catch (Exception e) { +// // 업로드한 이미지 모두 삭제 +// uploadedImageUrls.forEach(this::deleteImage); +// throw e; +// } +// +// blogDomain.setImage(uploadedImageUrls); +// +// return blogRepository.save(blogDomain); +// } + + + //test0621 + public String saveImage(MultipartFile file) throws Exception { + if (file == null || file.isEmpty()) throw new IllegalArgumentException("Empty file"); + + if (file.getSize() > 20 * 1024 * 1024) // 20MB 제한 + throw new IllegalArgumentException("File size exceeds maximum limit"); + + String originalFilename = StringUtils.cleanPath(file.getOriginalFilename()); + String extension = getFileExtension(originalFilename).toLowerCase(); + + Set allowedExtensions = Set.of( + "jpg", "jpeg", "png", "gif", "bmp", "webp", "heic", "heif", "tiff", "tif", "svg" + ); - // 접근 가능한 URL 반환 - return "/api/blog/images/" + newFilename; - } catch (IOException e) { - System.err.println("Error saving file: " + e.getMessage()); - e.printStackTrace(); - throw e; + if (!allowedExtensions.contains(extension)) { + throw new IllegalArgumentException("Invalid file extension: " + extension); } - } - // 파일 확장자 추출 메서드 - private String getFileExtension(String filename) { - int lastDotIndex = filename.lastIndexOf('.'); - if (lastDotIndex > 0) { - return filename.substring(lastDotIndex + 1); - } - return ""; - } - // 이미지 삭제 메소드 - public void deleteImage(String imageUrl) { - if (imageUrl != null && imageUrl.startsWith("/api/blog/images/")) { - String filename = imageUrl.substring("/api/blog/images/".length()); - try { - Path imagePath = Paths.get(uploadDir).resolve(filename); - Files.deleteIfExists(imagePath); - } catch (IOException e) { - e.printStackTrace(); - } - } + return googleDriveService.uploadFile(file); } - public BlogDomain createBlog(BlogCreateDto blogCreateDto, String userId) throws IOException { + public BlogDomain createBlog(BlogCreateDto blogCreateDto, String userId) throws Exception { BlogDomain blogDomain = new BlogDomain(); blogDomain.setUserId(userId); @@ -111,40 +132,143 @@ public BlogDomain createBlog(BlogCreateDto blogCreateDto, String userId) throws blogDomain.setBoardTitle(blogCreateDto.getBoardTitle()); blogDomain.setBoardContents(blogCreateDto.getBoardContent()); blogDomain.setCreateAt(LocalDateTime.now()); + blogDomain.setUpdateAt(LocalDateTime.now()); blogDomain.setTag(blogCreateDto.getTag()); blogDomain.setIsPin(false); blogDomain.setIsSecure(0); blogDomain.setIsDelete(0); List uploadedImageUrls = new ArrayList<>(); - if (blogCreateDto.getImageFiles() != null && !blogCreateDto.getImageFiles().isEmpty()) { - for (MultipartFile file : blogCreateDto.getImageFiles()) { - try { - String imageUrl = saveImage(file); - uploadedImageUrls.add(imageUrl); - } catch (Exception e) { - uploadedImageUrls.forEach(this::deleteImage); - throw e; + + try { + // 1️⃣ 업로드된 이미지 파일 처리 (saveImage 사용) + if (blogCreateDto.getImageFiles() != null) { + for (MultipartFile file : blogCreateDto.getImageFiles()) { + uploadedImageUrls.add(saveImage(file)); } } - } - // HTML 컨텐츠에서 이미지 URL 추출 - Pattern pattern = Pattern.compile("src=\"(/api/blog/images/[^\"]+)\""); - Matcher matcher = pattern.matcher(blogCreateDto.getBoardContent()); - while (matcher.find()) { - String imageUrl = matcher.group(1); - if (!uploadedImageUrls.contains(imageUrl)) { - uploadedImageUrls.add(imageUrl); + // 2️⃣ 본문 HTML에 포함된 이미지 URL도 추출해서 저장 + Pattern pattern = Pattern.compile("src=[\"']([^\"']+)[\"']"); + Matcher matcher = pattern.matcher(blogCreateDto.getBoardContent()); + + while (matcher.find()) { + String imageUrl = matcher.group(1); + if (!uploadedImageUrls.contains(imageUrl)) { + uploadedImageUrls.add(imageUrl); + } } + + } catch (Exception e) { + // 업로드한 이미지 모두 삭제 + uploadedImageUrls.forEach(this::deleteImage); + throw e; } blogDomain.setImage(uploadedImageUrls); + if (!uploadedImageUrls.isEmpty()) { + blogDomain.setThumbnailImage(uploadedImageUrls.get(0)); + } + return blogRepository.save(blogDomain); + } + + + // //구글버전 +// public String saveImage(MultipartFile file) throws Exception { +// try { +// if (file.isEmpty()) throw new IllegalArgumentException("Empty file"); +// if (file.getSize() > 20 * 1024 * 1024) throw new IllegalArgumentException("File size exceeds maximum limit"); +// +// String originalFilename = StringUtils.cleanPath(file.getOriginalFilename()); +// String extension = getFileExtension(originalFilename).toLowerCase(); +// Set allowedExtensions = new HashSet<>(Arrays.asList( +// "jpg", "jpeg", "png", "gif", "bmp", "webp", "heic", "heif", "tiff", "tif", "svg" +// )); +// if (!allowedExtensions.contains(extension)) { +// throw new IllegalArgumentException("Invalid file extension"); +// } +// +// // 👉 Google Drive 업로드로 대체 +// return googleDriveService.uploadFile(file); +// +// } catch (IOException e) { +// System.err.println("Error uploading file: " + e.getMessage()); +// e.printStackTrace(); +// throw e; +// } +// } +// +// // 파일 확장자 추출 메서드 +// private String getFileExtension(String filename) { +// int lastDotIndex = filename.lastIndexOf('.'); +// if (lastDotIndex > 0) { +// return filename.substring(lastDotIndex + 1); +// } +// return ""; +// } + //0624 + public void deleteImage(String imageUrl) { + try { + googleDriveService.deleteFile(imageUrl); + } catch (Exception e) { + e.printStackTrace(); + // e.printStackTrace(); 대신 + //log.error("이미지 삭제 중 오류 발생 - URL: {}", imageUrl, e); + } } - public void updateBlog(BlogUpdateRequestDto blogUpdateRequestDto, String userId) throws IOException { +// public BlogDomain createBlog(BlogCreateDto blogCreateDto, String userId) throws Exception { +// BlogDomain blogDomain = new BlogDomain(); +// +// blogDomain.setUserId(userId); +// blogDomain.setBoardCategory(blogCreateDto.getBoardCategory()); +// blogDomain.setBoardTitle(blogCreateDto.getBoardTitle()); +// blogDomain.setBoardContents(blogCreateDto.getBoardContent()); +// blogDomain.setCreateAt(LocalDateTime.now()); +// blogDomain.setUpdateAt(LocalDateTime.now()); +// blogDomain.setTag(blogCreateDto.getTag()); +// blogDomain.setIsPin(false); +// blogDomain.setIsSecure(0); +// blogDomain.setIsDelete(0); +// +// List uploadedImageUrls = new ArrayList<>(); +// if (blogCreateDto.getImageFiles() != null && !blogCreateDto.getImageFiles().isEmpty()) { +// for (MultipartFile file : blogCreateDto.getImageFiles()) { +// try { +// String imageUrl = saveImage(file); +// uploadedImageUrls.add(imageUrl); +// } catch (Exception e) { +// uploadedImageUrls.forEach(this::deleteImage); +// throw e; +// } +// } +// } +// +// // HTML 컨텐츠에서 이미지 URL 추출 +//// Pattern pattern = Pattern.compile("src=\"(/api/blog/images/[^"]+)\""); +// Pattern pattern = Pattern.compile("src=[\"']([^\"']+)[\"']"); +// Matcher matcher = pattern.matcher(blogCreateDto.getBoardContent()); +// while (matcher.find()) { +// String imageUrl = matcher.group(1); +// if (!uploadedImageUrls.contains(imageUrl)) { +// uploadedImageUrls.add(imageUrl); +// } +// } +// +// blogDomain.setImage(uploadedImageUrls); +// return blogRepository.save(blogDomain); +// +// } + + + + + + + + public void updateBlog(BlogUpdateRequestDto blogUpdateRequestDto, String userId) throws Exception { BlogDomain blog = blogRepository.findById(blogUpdateRequestDto.getId()) .orElseThrow(() -> new IllegalArgumentException("Blog not found")); @@ -185,7 +309,7 @@ public void updateBlog(BlogUpdateRequestDto blogUpdateRequestDto, String userId) } blog.setImage(updatedImageUrls); - blog.setCreateAt(LocalDateTime.now()); + blog.setUpdateAt(LocalDateTime.now()); blogRepository.save(blog); } @@ -288,6 +412,7 @@ public BlogDetailResponseDto getBlogDetail(String id) { blogDetailResponseDto.setBoardContents(blog.getBoardContents()); blogDetailResponseDto.setNickname(nickname); blogDetailResponseDto.setCreateAt(blog.getCreateAt()); + blogDetailResponseDto.setUpdateAt(blog.getUpdateAt()); blogDetailResponseDto.setTag(blog.getTag()); blogDetailResponseDto.setImage(blog.getImage()); blogDetailResponseDto.setThumbnailImage(blog.getThumbnailImage()); @@ -335,4 +460,13 @@ public Map getAdjacentBlogs(String id) { public List getAllTags() { return blogRepository.findAllTags(); } + + //구글 코드 + public List getImageUrlsByBlogId(String blogId) { + return blogRepository.findById(blogId) + .map(BlogDomain::getImage) + .orElse(Collections.emptyList()); + } + + } \ No newline at end of file diff --git a/src/main/java/com/boot/swlugweb/v1/blog/BlogUpdateRequestDto.java b/src/main/java/com/boot/swlugweb/v1/blog/BlogUpdateRequestDto.java index 8d93d2c..4a13835 100644 --- a/src/main/java/com/boot/swlugweb/v1/blog/BlogUpdateRequestDto.java +++ b/src/main/java/com/boot/swlugweb/v1/blog/BlogUpdateRequestDto.java @@ -19,4 +19,5 @@ public class BlogUpdateRequestDto { private String thumbnailImage; private List imageUrls; private List imageFiles; + private LocalDateTime updateAt; } diff --git a/src/main/java/com/boot/swlugweb/v1/blog/GoogleDriveService.java b/src/main/java/com/boot/swlugweb/v1/blog/GoogleDriveService.java new file mode 100644 index 0000000..4ab5702 --- /dev/null +++ b/src/main/java/com/boot/swlugweb/v1/blog/GoogleDriveService.java @@ -0,0 +1,219 @@ +package com.boot.swlugweb.v1.blog; + +import com.google.api.client.auth.oauth2.Credential; +import com.google.api.client.extensions.java6.auth.oauth2.AuthorizationCodeInstalledApp; +import com.google.api.client.extensions.jetty.auth.oauth2.LocalServerReceiver; +import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow; +import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets; +import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport; + +import com.google.api.client.http.InputStreamContent; +import com.google.api.client.http.javanet.NetHttpTransport; +import com.google.api.client.json.gson.GsonFactory; +import com.google.api.client.util.store.FileDataStoreFactory; +import com.google.api.services.drive.Drive; +import com.google.api.services.drive.DriveScopes; +import com.google.api.services.drive.model.FileList; + +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.security.GeneralSecurityException; +import java.util.Collections; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + + +@Service +public class GoogleDriveService { + +// private static final String APPLICATION_NAME = "Spring Boot Google Drive"; +// private static final GsonFactory JSON_FACTORY = GsonFactory.getDefaultInstance(); +// private static final List SCOPES = Collections.singletonList(DriveScopes.DRIVE); +// private static final String TOKENS_DIRECTORY_PATH = "tokens"; +// // private static final String CREDENTIALS_FILE_PATH = "/credentials_service.json"; +// private static final String CREDENTIALS_FILE_PATH = "/service_account_key.json"; +//// private static final String CREDENTIALS_FILE_PATH = "/credentials.json"; +// +// //credential-service +// public Drive getDriveService() throws Exception { +// InputStream in = getClass().getResourceAsStream(CREDENTIALS_FILE_PATH); +// if (in == null) { +// throw new RuntimeException("credentials_service.json not found"); +// } +// +// GoogleCredentials credentials = ServiceAccountCredentials.fromStream(in) +// .createScoped(Collections.singletonList("drive-uploader@swlugweb1.iam.gserviceaccount.com/auth/drive")); +// +// return new Drive.Builder( +// GoogleNetHttpTransport.newTrustedTransport(), +// JSON_FACTORY, +// new HttpCredentialsAdapter(credentials) +// ).setApplicationName(APPLICATION_NAME).build(); +// }} + +//test 0621 +// private static final String APPLICATION_NAME = "Spring Boot Google Drive"; +// private static final GsonFactory JSON_FACTORY = GsonFactory.getDefaultInstance(); +// private static final List SCOPES = Collections.singletonList(DriveScopes.DRIVE); + + //service 버전 + // private static final String CREDENTIALS_FILE_PATH = "/service_account_key.json"; +// +// public Drive getDriveService() throws Exception { +// InputStream in = getClass().getResourceAsStream(CREDENTIALS_FILE_PATH); +// if (in == null) { +// throw new RuntimeException("service_account_key.json not found"); +// } +// +// GoogleCredentials credentials = ServiceAccountCredentials.fromStream(in) +// .createScoped(SCOPES); +// +// return new Drive.Builder( +// GoogleNetHttpTransport.newTrustedTransport(), +// JSON_FACTORY, +// new HttpCredentialsAdapter(credentials) +// ).setApplicationName(APPLICATION_NAME).build(); +// } + +//test0624 +private static final String APPLICATION_NAME = "Google Drive API Java Quickstart"; + private static final GsonFactory JSON_FACTORY = GsonFactory.getDefaultInstance(); + private static final String TOKENS_DIRECTORY_PATH = "tokens"; + private static final List SCOPES = Collections.singletonList(DriveScopes.DRIVE_FILE); + private static final String CREDENTIALS_FILE_PATH = "credentials1.json"; + + + +// public static String uploadFile(MultipartFile file) throws IOException, GeneralSecurityException { +// NetHttpTransport HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport(); +// Drive service = new Drive.Builder(HTTP_TRANSPORT, JSON_FACTORY, getCredentials(HTTP_TRANSPORT)) +// .setApplicationName(APPLICATION_NAME) +// .build(); +// +// getFileList(service); +// return null; +// } + + public static String uploadFile(MultipartFile file) throws IOException, GeneralSecurityException { + NetHttpTransport HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport(); + Drive driveService = new Drive.Builder(HTTP_TRANSPORT, JSON_FACTORY, getCredentials(HTTP_TRANSPORT)) + .setApplicationName(APPLICATION_NAME) + .build(); + + com.google.api.services.drive.model.File fileMetadata = new com.google.api.services.drive.model.File(); + fileMetadata.setName(file.getOriginalFilename()); + + InputStream inputStream = file.getInputStream(); + InputStreamContent mediaContent = new InputStreamContent( + file.getContentType(), inputStream + ); + + com.google.api.services.drive.model.File uploadedFile = driveService.files().create(fileMetadata, mediaContent) + .setFields("id") + .execute(); + + String fileId = uploadedFile.getId(); + return "https://drive.google.com/file/d/" + fileId + "/view?usp=sharing"; + } + + +// public static void getFileList(Drive driveService) throws IOException { +// FileList result = driveService.files().list().setFields("nextPageToken, files(id, name, createdTime)").execute(); +// List files = result.getFiles(); +// for (com.google.api.services.drive.model.File file : files) { +// System.out.println("File Name: " + file.getName() + " File Id: " + file.getId()); +// } +// } + +// public static void deleteFile(String imageUrl) throws IOException, GeneralSecurityException { +// +// NetHttpTransport HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport(); +// Drive service = new Drive.Builder(HTTP_TRANSPORT, JSON_FACTORY, getCredentials(HTTP_TRANSPORT)) +// .setApplicationName(APPLICATION_NAME) +// .build(); +// +// service.files().delete(imageUrl).execute(); +// System.out.println("File with ID " + imageUrl + " has been deleted."); +// } +//0624 + public void deleteFile(String imageUrl) throws IOException, GeneralSecurityException { + String fileId = extractFileIdFromUrl(imageUrl); // fileId 추출 + if (fileId == null) { + throw new IllegalArgumentException("Invalid Google Drive URL: " + imageUrl); + } + + NetHttpTransport HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport(); + Drive service = new Drive.Builder(HTTP_TRANSPORT, JSON_FACTORY, getCredentials(HTTP_TRANSPORT)) + .setApplicationName(APPLICATION_NAME) + .build(); + + service.files().delete(fileId).execute(); // ✅ 여기 수정 + System.out.println("File with ID " + fileId + " has been deleted."); + } + + public static String extractFileIdFromUrl(String imageUrl) { + Pattern pattern = Pattern.compile("/d/([a-zA-Z0-9_-]{25,})"); + Matcher matcher = pattern.matcher(imageUrl); + if (matcher.find()) { + return matcher.group(1); + } + return null; + } +//0624 + +// public static String extractFileIdFromUrl(String imageurl) { +// if (imageurl.contains("drive.google.com/file/d/")) { +// int start = imageurl.indexOf("/d/") + 3; +// int end = imageurl.indexOf("/", start); +// if (start > 2 && end > start) { +// return imageurl.substring(start, end); +// } +// } +// return null; +// } + +// public void deleteFile(String imageUrl) throws IOException, GeneralSecurityException { +// String fileId = extractFileIdFromUrl(imageUrl); +// +// NetHttpTransport HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport(); +// com.google.api.services.drive.model.Drive service = new com.google.api.services.drive.model.Drive.Builder(HTTP_TRANSPORT, JSON_FACTORY, getCredentials(HTTP_TRANSPORT)) +// .setApplicationName(APPLICATION_NAME) +// .build(); +// +// service.files().delete(fileId).execute(); +// System.out.println("File with ID " + fileId + " has been deleted."); +// } +// +// public String extractFileIdFromUrl(String url) { +// String regex = "/d/([a-zA-Z0-9_-]{25,})"; +// Pattern pattern = Pattern.compile(regex); +// org.apache.tomcat.util.file.Matcher matcher = pattern.matcher(url); +// if (matcher.find()) { +// return matcher.group(1); +// } +// throw new IllegalArgumentException("Invalid Google Drive URL: " + url); +// } + + + private static Credential getCredentials(final NetHttpTransport HTTP_TRANSPORT) throws IOException { + InputStream in = GoogleDriveService.class.getResourceAsStream(CREDENTIALS_FILE_PATH); + GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(JSON_FACTORY, new InputStreamReader(in)); + + GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder( + HTTP_TRANSPORT, JSON_FACTORY, clientSecrets, SCOPES) + .setDataStoreFactory(new FileDataStoreFactory(new java.io.File(TOKENS_DIRECTORY_PATH))) + .setAccessType("offline") + .build(); + LocalServerReceiver receiver = new LocalServerReceiver.Builder().setPort(8888).build(); + return new AuthorizationCodeInstalledApp(flow, receiver).authorize("user"); + }} +//0624 + + + + From f93053bebaab5c2a3fe402a668762fa428732b7d Mon Sep 17 00:00:00 2001 From: TigerDemon Date: Fri, 27 Jun 2025 17:55:09 +0900 Subject: [PATCH 2/5] drivetest2 --- .../com/boot/swlugweb/v1/DriveQuickstart.java | 6 +- .../boot/swlugweb/v1/blog/BlogController.java | 96 ++---- .../boot/swlugweb/v1/blog/BlogService.java | 2 +- .../swlugweb/v1/blog/GoogleDriveService.java | 296 ++++++++---------- 4 files changed, 165 insertions(+), 235 deletions(-) diff --git a/src/main/java/com/boot/swlugweb/v1/DriveQuickstart.java b/src/main/java/com/boot/swlugweb/v1/DriveQuickstart.java index a74f435..f09da28 100644 --- a/src/main/java/com/boot/swlugweb/v1/DriveQuickstart.java +++ b/src/main/java/com/boot/swlugweb/v1/DriveQuickstart.java @@ -42,8 +42,8 @@ public class DriveQuickstart { * If modifying these scopes, delete your previously saved tokens/ folder. */ private static final List SCOPES = - Collections.singletonList(DriveScopes.DRIVE_METADATA_READONLY); - private static final String CREDENTIALS_FILE_PATH = "credentials1.json"; + Collections.singletonList(DriveScopes.DRIVE); + private static final String CREDENTIALS_FILE_PATH = "/credentials.json"; /** * Creates an authorized Credential object. @@ -96,4 +96,6 @@ public static void main(String... args) throws IOException, GeneralSecurityExcep } } } + + } \ No newline at end of file diff --git a/src/main/java/com/boot/swlugweb/v1/blog/BlogController.java b/src/main/java/com/boot/swlugweb/v1/blog/BlogController.java index c07f22c..f3617e3 100644 --- a/src/main/java/com/boot/swlugweb/v1/blog/BlogController.java +++ b/src/main/java/com/boot/swlugweb/v1/blog/BlogController.java @@ -53,41 +53,7 @@ public ResponseEntity getBlogDetail(@RequestBody Map saveBlog( -// @RequestPart("blogCreateDto") BlogCreateDto blogCreateDto, -// @RequestPart(name = "imageFiles", required = false) List imageFiles, -// HttpSession session) { -// -// String userId = (String) session.getAttribute("USER"); -// if (userId == null) return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); -// -// try { -// List imageUrls = new ArrayList<>(); -// -// if (imageFiles != null && !imageFiles.isEmpty()) { -// for (MultipartFile file : imageFiles) { -// String url = googleDriveService.uploadFile(file); -// imageUrls.add(url); -// } -// } -// -// blogCreateDto.setImageUrl(imageUrls); -// blogCreateDto.setImageFiles(imageFiles); -// blogService.createBlog(blogCreateDto, userId); -// -// return ResponseEntity.status(HttpStatus.FOUND) -// .header(HttpHeaders.LOCATION, "/api/blog") -// .build(); -// -// } catch (Exception e) { -// e.printStackTrace(); -// return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); -// } -// } - - //test1 0621 + //구글 버전 @PostMapping(value = "/save", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseEntity saveBlog( @RequestPart("blogCreateDto") BlogCreateDto blogCreateDto, @@ -119,36 +85,36 @@ public ResponseEntity saveBlog( //구글 버전 - @PostMapping(value = "/update", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) - public ResponseEntity updateBlogPost( - @RequestPart("blogUpdateRequestDto") BlogUpdateRequestDto blogUpdateRequestDto, - @RequestPart(name = "imageFiles", required = false) List imageFiles, - HttpSession session) { - - String userId = (String) session.getAttribute("USER"); - if (userId == null) return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); - - try { - List imageUrls = new ArrayList<>(); - if (imageFiles != null && !imageFiles.isEmpty()) { - for (MultipartFile file : imageFiles) { - String url = GoogleDriveService.uploadFile(file); - imageUrls.add(url); - } - } - - blogUpdateRequestDto.setImageUrls(imageUrls); - blogUpdateRequestDto.setImageFiles(imageFiles); - blogService.updateBlog(blogUpdateRequestDto, userId); - - return ResponseEntity.status(HttpStatus.FOUND) - .header(HttpHeaders.LOCATION, "/api/blog") - .build(); - - } catch (Exception e) { - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); - } - } +// @PostMapping(value = "/update", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) +// public ResponseEntity updateBlogPost( +// @RequestPart("blogUpdateRequestDto") BlogUpdateRequestDto blogUpdateRequestDto, +// @RequestPart(name = "imageFiles", required = false) List imageFiles, +// HttpSession session) { +// +// String userId = (String) session.getAttribute("USER"); +// if (userId == null) return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); +// +// try { +// List imageUrls = new ArrayList<>(); +// if (imageFiles != null && !imageFiles.isEmpty()) { +// for (MultipartFile file : imageFiles) { +// String url = GoogleDriveService.uploadFileToDrive(file); +// imageUrls.add(url); +// } +// } +// +// blogUpdateRequestDto.setImageUrls(imageUrls); +// blogUpdateRequestDto.setImageFiles(imageFiles); +// blogService.updateBlog(blogUpdateRequestDto, userId); +// +// return ResponseEntity.status(HttpStatus.FOUND) +// .header(HttpHeaders.LOCATION, "/api/blog") +// .build(); +// +// } catch (Exception e) { +// return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); +// } +// } @PostMapping("/delete") diff --git a/src/main/java/com/boot/swlugweb/v1/blog/BlogService.java b/src/main/java/com/boot/swlugweb/v1/blog/BlogService.java index b689079..59dc54e 100644 --- a/src/main/java/com/boot/swlugweb/v1/blog/BlogService.java +++ b/src/main/java/com/boot/swlugweb/v1/blog/BlogService.java @@ -121,7 +121,7 @@ public String saveImage(MultipartFile file) throws Exception { throw new IllegalArgumentException("Invalid file extension: " + extension); } - return googleDriveService.uploadFile(file); + return googleDriveService.uploadFileToDrive(file); } public BlogDomain createBlog(BlogCreateDto blogCreateDto, String userId) throws Exception { diff --git a/src/main/java/com/boot/swlugweb/v1/blog/GoogleDriveService.java b/src/main/java/com/boot/swlugweb/v1/blog/GoogleDriveService.java index 4ab5702..a43bbac 100644 --- a/src/main/java/com/boot/swlugweb/v1/blog/GoogleDriveService.java +++ b/src/main/java/com/boot/swlugweb/v1/blog/GoogleDriveService.java @@ -6,214 +6,176 @@ import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow; import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets; import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport; - +import com.google.api.client.googleapis.media.MediaHttpUploader; +import com.google.api.client.googleapis.media.MediaHttpUploaderProgressListener; +import com.google.api.client.http.FileContent; import com.google.api.client.http.InputStreamContent; import com.google.api.client.http.javanet.NetHttpTransport; import com.google.api.client.json.gson.GsonFactory; import com.google.api.client.util.store.FileDataStoreFactory; import com.google.api.services.drive.Drive; import com.google.api.services.drive.DriveScopes; -import com.google.api.services.drive.model.FileList; - +import com.google.api.services.drive.model.Permission; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; +import java.io.*; +import java.nio.file.Path; +import java.nio.file.Paths; import java.security.GeneralSecurityException; import java.util.Collections; import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; +import java.util.UUID; +import static com.google.api.client.googleapis.media.MediaHttpUploader.UploadState.INITIATION_STARTED; @Service public class GoogleDriveService { - -// private static final String APPLICATION_NAME = "Spring Boot Google Drive"; -// private static final GsonFactory JSON_FACTORY = GsonFactory.getDefaultInstance(); -// private static final List SCOPES = Collections.singletonList(DriveScopes.DRIVE); -// private static final String TOKENS_DIRECTORY_PATH = "tokens"; -// // private static final String CREDENTIALS_FILE_PATH = "/credentials_service.json"; -// private static final String CREDENTIALS_FILE_PATH = "/service_account_key.json"; -//// private static final String CREDENTIALS_FILE_PATH = "/credentials.json"; -// -// //credential-service -// public Drive getDriveService() throws Exception { -// InputStream in = getClass().getResourceAsStream(CREDENTIALS_FILE_PATH); -// if (in == null) { -// throw new RuntimeException("credentials_service.json not found"); -// } -// -// GoogleCredentials credentials = ServiceAccountCredentials.fromStream(in) -// .createScoped(Collections.singletonList("drive-uploader@swlugweb1.iam.gserviceaccount.com/auth/drive")); -// -// return new Drive.Builder( -// GoogleNetHttpTransport.newTrustedTransport(), -// JSON_FACTORY, -// new HttpCredentialsAdapter(credentials) -// ).setApplicationName(APPLICATION_NAME).build(); -// }} - -//test 0621 -// private static final String APPLICATION_NAME = "Spring Boot Google Drive"; -// private static final GsonFactory JSON_FACTORY = GsonFactory.getDefaultInstance(); -// private static final List SCOPES = Collections.singletonList(DriveScopes.DRIVE); - - //service 버전 - // private static final String CREDENTIALS_FILE_PATH = "/service_account_key.json"; -// -// public Drive getDriveService() throws Exception { -// InputStream in = getClass().getResourceAsStream(CREDENTIALS_FILE_PATH); -// if (in == null) { -// throw new RuntimeException("service_account_key.json not found"); -// } -// -// GoogleCredentials credentials = ServiceAccountCredentials.fromStream(in) -// .createScoped(SCOPES); -// -// return new Drive.Builder( -// GoogleNetHttpTransport.newTrustedTransport(), -// JSON_FACTORY, -// new HttpCredentialsAdapter(credentials) -// ).setApplicationName(APPLICATION_NAME).build(); -// } - -//test0624 -private static final String APPLICATION_NAME = "Google Drive API Java Quickstart"; + private static final String APPLICATION_NAME = "Google Drive API Java Quickstart"; private static final GsonFactory JSON_FACTORY = GsonFactory.getDefaultInstance(); private static final String TOKENS_DIRECTORY_PATH = "tokens"; - private static final List SCOPES = Collections.singletonList(DriveScopes.DRIVE_FILE); - private static final String CREDENTIALS_FILE_PATH = "credentials1.json"; + private static final List SCOPES = Collections.singletonList(DriveScopes.DRIVE); + private static final String CREDENTIALS_FILE_PATH = "/credentials.json"; + + private final Drive driveService; + + public GoogleDriveService() throws GeneralSecurityException, IOException { + final NetHttpTransport HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport(); + this.driveService = new Drive.Builder( + HTTP_TRANSPORT, + JSON_FACTORY, + getCredentials(HTTP_TRANSPORT) + ).setApplicationName(APPLICATION_NAME).build(); + } -// public static String uploadFile(MultipartFile file) throws IOException, GeneralSecurityException { -// NetHttpTransport HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport(); -// Drive service = new Drive.Builder(HTTP_TRANSPORT, JSON_FACTORY, getCredentials(HTTP_TRANSPORT)) -// .setApplicationName(APPLICATION_NAME) -// .build(); -// -// getFileList(service); -// return null; -// } - public static String uploadFile(MultipartFile file) throws IOException, GeneralSecurityException { - NetHttpTransport HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport(); - Drive driveService = new Drive.Builder(HTTP_TRANSPORT, JSON_FACTORY, getCredentials(HTTP_TRANSPORT)) - .setApplicationName(APPLICATION_NAME) + private static Credential getCredentials(final NetHttpTransport HTTP_TRANSPORT) throws IOException { + InputStream in = GoogleDriveService.class.getResourceAsStream(CREDENTIALS_FILE_PATH); + if (in == null) { + throw new FileNotFoundException("Resource not found: " + CREDENTIALS_FILE_PATH); + } + GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(JSON_FACTORY, new InputStreamReader(in)); + GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder( + HTTP_TRANSPORT, JSON_FACTORY, clientSecrets, SCOPES) + .setDataStoreFactory(new FileDataStoreFactory(new java.io.File(TOKENS_DIRECTORY_PATH))) + .setAccessType("offline") .build(); + LocalServerReceiver receiver = new LocalServerReceiver.Builder().setPort(8080).build(); + return new AuthorizationCodeInstalledApp(flow, receiver).authorize("user"); + } - com.google.api.services.drive.model.File fileMetadata = new com.google.api.services.drive.model.File(); - fileMetadata.setName(file.getOriginalFilename()); + public String uploadFile(MultipartFile file) throws IOException { + java.io.File convFile = new java.io.File(System.getProperty("java.io.tmpdir") + "/" + file.getOriginalFilename()); + file.transferTo(convFile); - InputStream inputStream = file.getInputStream(); - InputStreamContent mediaContent = new InputStreamContent( - file.getContentType(), inputStream - ); + com.google.api.services.drive.model.File fileMetaData = new com.google.api.services.drive.model.File(); + String randomFileName = UUID.randomUUID().toString() + ".jpg"; + fileMetaData.setName(randomFileName); - com.google.api.services.drive.model.File uploadedFile = driveService.files().create(fileMetadata, mediaContent) - .setFields("id") - .execute(); + FileContent fileContent = new FileContent("image/jpeg", convFile); + com.google.api.services.drive.model.File uploadedFile = driveService.files().create(fileMetaData, fileContent).execute(); - String fileId = uploadedFile.getId(); - return "https://drive.google.com/file/d/" + fileId + "/view?usp=sharing"; + return "https://drive.google.com/uc?id=" + uploadedFile.getId(); } -// public static void getFileList(Drive driveService) throws IOException { -// FileList result = driveService.files().list().setFields("nextPageToken, files(id, name, createdTime)").execute(); -// List files = result.getFiles(); -// for (com.google.api.services.drive.model.File file : files) { -// System.out.println("File Name: " + file.getName() + " File Id: " + file.getId()); -// } -// } - -// public static void deleteFile(String imageUrl) throws IOException, GeneralSecurityException { -// -// NetHttpTransport HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport(); -// Drive service = new Drive.Builder(HTTP_TRANSPORT, JSON_FACTORY, getCredentials(HTTP_TRANSPORT)) -// .setApplicationName(APPLICATION_NAME) -// .build(); -// -// service.files().delete(imageUrl).execute(); -// System.out.println("File with ID " + imageUrl + " has been deleted."); -// } -//0624 - public void deleteFile(String imageUrl) throws IOException, GeneralSecurityException { - String fileId = extractFileIdFromUrl(imageUrl); // fileId 추출 - if (fileId == null) { - throw new IllegalArgumentException("Invalid Google Drive URL: " + imageUrl); + + + + + + private Path saveTempFile(MultipartFile multipartFile) throws IOException { + Path tempDir = Paths.get(System.getProperty("java.io.tmpdir")); + Path tempFile = tempDir.resolve(multipartFile.getOriginalFilename()); + try (FileOutputStream fos = new FileOutputStream(tempFile.toFile())) { + fos.write(multipartFile.getBytes()); } + return tempFile; + } - NetHttpTransport HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport(); - Drive service = new Drive.Builder(HTTP_TRANSPORT, JSON_FACTORY, getCredentials(HTTP_TRANSPORT)) - .setApplicationName(APPLICATION_NAME) - .build(); - service.files().delete(fileId).execute(); // ✅ 여기 수정 - System.out.println("File with ID " + fileId + " has been deleted."); + public void deleteFile(String fileId) throws IOException, GeneralSecurityException { + Drive service = driveService; + service.files().delete(fileId).execute(); } - public static String extractFileIdFromUrl(String imageUrl) { - Pattern pattern = Pattern.compile("/d/([a-zA-Z0-9_-]{25,})"); - Matcher matcher = pattern.matcher(imageUrl); - if (matcher.find()) { - return matcher.group(1); + + + + private java.io.File convertMultipartFileToFile(MultipartFile file) throws IOException { + java.io.File convFile = new java.io.File(file.getOriginalFilename()); + try (FileOutputStream fos = new FileOutputStream(convFile)) { + fos.write(file.getBytes()); } - return null; + return convFile; } -//0624 - -// public static String extractFileIdFromUrl(String imageurl) { -// if (imageurl.contains("drive.google.com/file/d/")) { -// int start = imageurl.indexOf("/d/") + 3; -// int end = imageurl.indexOf("/", start); -// if (start > 2 && end > start) { -// return imageurl.substring(start, end); -// } -// } -// return null; -// } - -// public void deleteFile(String imageUrl) throws IOException, GeneralSecurityException { -// String fileId = extractFileIdFromUrl(imageUrl); -// -// NetHttpTransport HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport(); -// com.google.api.services.drive.model.Drive service = new com.google.api.services.drive.model.Drive.Builder(HTTP_TRANSPORT, JSON_FACTORY, getCredentials(HTTP_TRANSPORT)) -// .setApplicationName(APPLICATION_NAME) -// .build(); -// -// service.files().delete(fileId).execute(); -// System.out.println("File with ID " + fileId + " has been deleted."); -// } -// -// public String extractFileIdFromUrl(String url) { -// String regex = "/d/([a-zA-Z0-9_-]{25,})"; -// Pattern pattern = Pattern.compile(regex); -// org.apache.tomcat.util.file.Matcher matcher = pattern.matcher(url); -// if (matcher.find()) { -// return matcher.group(1); -// } -// throw new IllegalArgumentException("Invalid Google Drive URL: " + url); -// } - private static Credential getCredentials(final NetHttpTransport HTTP_TRANSPORT) throws IOException { - InputStream in = GoogleDriveService.class.getResourceAsStream(CREDENTIALS_FILE_PATH); - GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(JSON_FACTORY, new InputStreamReader(in)); + public String uploadFileToDrive(MultipartFile file) throws IOException { + // Google Drive에 업로드할 파일 메타데이터 설정 + com.google.api.services.drive.model.File fileMetadata = new com.google.api.services.drive.model.File(); + fileMetadata.setName(file.getOriginalFilename()); - GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder( - HTTP_TRANSPORT, JSON_FACTORY, clientSecrets, SCOPES) - .setDataStoreFactory(new FileDataStoreFactory(new java.io.File(TOKENS_DIRECTORY_PATH))) - .setAccessType("offline") - .build(); - LocalServerReceiver receiver = new LocalServerReceiver.Builder().setPort(8888).build(); - return new AuthorizationCodeInstalledApp(flow, receiver).authorize("user"); - }} -//0624 + // 업로드할 파일 내용 설정 + InputStreamContent mediaContent = new InputStreamContent( + "application/octet-stream", file.getInputStream() + ); + + // 파일을 구글 드라이브에 업로드 + Drive.Files.Create createRequest = driveService.files().create(fileMetadata, mediaContent); + + // 업로드 진행 상태를 모니터링 + createRequest.getMediaHttpUploader().setProgressListener( + new MediaHttpUploaderProgressListener() { + public void progressChanged(MediaHttpUploader uploader) throws IOException { + switch (uploader.getUploadState()) { + case INITIATION_STARTED: + System.out.println("Initiation Started"); + break; + case INITIATION_COMPLETE: + System.out.println("Initiation Complete"); + break; + case MEDIA_IN_PROGRESS: + System.out.println("Upload In Progress"); + break; + case MEDIA_COMPLETE: + System.out.println("Upload Complete"); + break; + } + } + } + ); + + // 파일 업로드 실행 + com.google.api.services.drive.model.File uploadedFile = createRequest.execute(); + String fileId = uploadedFile.getId(); + + // 파일 공개 권한 설정 (필수) + setFilePublic(fileId); + + // 이미지 원본 URL 반환 + return driveService.files().get(fileId).execute().getWebViewLink(); + // 업로드된 파일의 ID를 반환 (WebViewLink로 파일을 찾을 수 있음) + //return "https://drive.google.com/file/d/" + uploadedFile.getId() + "/view"; + } + + // 파일을 "공개"로 설정하는 메서드 수정 + private void setFilePublic(String fileId) throws IOException { + Permission permission = new Permission() + .setType("anyone") // 모든 사용자 접근 허용 + .setRole("reader") // 읽기 권한 부여 + .setAllowFileDiscovery(false); // 검색 엔진에서 노출되지 않도록 설정 + + driveService.permissions().create(fileId, permission) + .setFields("id") + .execute(); + + System.out.println("File is now public: " + fileId); + } +} From 78010ece46feb6fd08be1b4a04d81ffb77e1c00d Mon Sep 17 00:00:00 2001 From: TigerDemon Date: Sat, 28 Jun 2025 22:13:19 +0900 Subject: [PATCH 3/5] drivetest3 --- .../com/boot/swlugweb/v1/DriveQuickstart.java | 64 +++-- .../boot/swlugweb/v1/blog/BlogController.java | 27 ++ .../com/boot/swlugweb/v1/blog/BlogDto.java | 49 +++- .../boot/swlugweb/v1/blog/BlogService.java | 153 +++++++--- .../swlugweb/v1/blog/GoogleDriveService.java | 265 ++++++++++++------ 5 files changed, 408 insertions(+), 150 deletions(-) diff --git a/src/main/java/com/boot/swlugweb/v1/DriveQuickstart.java b/src/main/java/com/boot/swlugweb/v1/DriveQuickstart.java index f09da28..90cfbb8 100644 --- a/src/main/java/com/boot/swlugweb/v1/DriveQuickstart.java +++ b/src/main/java/com/boot/swlugweb/v1/DriveQuickstart.java @@ -14,6 +14,10 @@ import com.google.api.services.drive.DriveScopes; import com.google.api.services.drive.model.File; import com.google.api.services.drive.model.FileList; +import com.google.auth.http.HttpCredentialsAdapter; +import com.google.auth.oauth2.GoogleCredentials; +import com.google.auth.oauth2.ServiceAccountCredentials; + import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; @@ -43,41 +47,57 @@ public class DriveQuickstart { */ private static final List SCOPES = Collections.singletonList(DriveScopes.DRIVE); - private static final String CREDENTIALS_FILE_PATH = "/credentials.json"; +// private static final String CREDENTIALS_FILE_PATH = "/credentials1.json"; + private static final String CREDENTIALS_FILE_PATH = "/service_account_key.json"; /** * Creates an authorized Credential object. * - * @param HTTP_TRANSPORT The network HTTP Transport. - * @return An authorized Credential object. +// * @param HTTP_TRANSPORT The network HTTP Transport. +// * @return An authorized Credential object. * @throws IOException If the credentials.json file cannot be found. */ - private static Credential getCredentials(final NetHttpTransport HTTP_TRANSPORT) - throws IOException { - // Load client secrets. +// public static Credential getCredentials(final NetHttpTransport HTTP_TRANSPORT) +// throws IOException { +// // Load client secrets. +// InputStream in = DriveQuickstart.class.getResourceAsStream(CREDENTIALS_FILE_PATH); +// if (in == null) { +// throw new FileNotFoundException("Resource not found: " + CREDENTIALS_FILE_PATH); +// } +// GoogleClientSecrets clientSecrets = +// GoogleClientSecrets.load(JSON_FACTORY, new InputStreamReader(in)); +// +// // Build flow and trigger user authorization request. +// GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder( +// HTTP_TRANSPORT, JSON_FACTORY, clientSecrets, SCOPES) +// .setDataStoreFactory(new FileDataStoreFactory(new java.io.File(TOKENS_DIRECTORY_PATH))) +// .setAccessType("offline") +// .build(); +// LocalServerReceiver receiver = new LocalServerReceiver.Builder().setPort(8888).build(); +// Credential credential = new AuthorizationCodeInstalledApp(flow, receiver).authorize("user"); +// //returns an authorized Credential object. +// return credential; +// } + + public static Drive getDriveService() throws Exception { InputStream in = DriveQuickstart.class.getResourceAsStream(CREDENTIALS_FILE_PATH); if (in == null) { - throw new FileNotFoundException("Resource not found: " + CREDENTIALS_FILE_PATH); + throw new RuntimeException("service_account_key.json not found"); } - GoogleClientSecrets clientSecrets = - GoogleClientSecrets.load(JSON_FACTORY, new InputStreamReader(in)); - // Build flow and trigger user authorization request. - GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder( - HTTP_TRANSPORT, JSON_FACTORY, clientSecrets, SCOPES) - .setDataStoreFactory(new FileDataStoreFactory(new java.io.File(TOKENS_DIRECTORY_PATH))) - .setAccessType("offline") - .build(); - LocalServerReceiver receiver = new LocalServerReceiver.Builder().setPort(8888).build(); - Credential credential = new AuthorizationCodeInstalledApp(flow, receiver).authorize("user"); - //returns an authorized Credential object. - return credential; - } + GoogleCredentials credentials = ServiceAccountCredentials.fromStream(in) + .createScoped(SCOPES); - public static void main(String... args) throws IOException, GeneralSecurityException { + return new Drive.Builder( + GoogleNetHttpTransport.newTrustedTransport(), + JSON_FACTORY, + new HttpCredentialsAdapter(credentials) + ).setApplicationName(APPLICATION_NAME).build(); + } + public static void main(String... args) throws Exception { // Build a new authorized API client service. final NetHttpTransport HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport(); - Drive service = new Drive.Builder(HTTP_TRANSPORT, JSON_FACTORY, getCredentials(HTTP_TRANSPORT)) + Drive service = new Drive.Builder(HTTP_TRANSPORT, JSON_FACTORY, (com.google.api.client.http.HttpRequestInitializer) getDriveService()) .setApplicationName(APPLICATION_NAME) .build(); diff --git a/src/main/java/com/boot/swlugweb/v1/blog/BlogController.java b/src/main/java/com/boot/swlugweb/v1/blog/BlogController.java index f3617e3..b0d296e 100644 --- a/src/main/java/com/boot/swlugweb/v1/blog/BlogController.java +++ b/src/main/java/com/boot/swlugweb/v1/blog/BlogController.java @@ -116,6 +116,33 @@ public ResponseEntity saveBlog( // } // } + @PostMapping(value = "/update", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) + public ResponseEntity updateBlogPost( + @RequestPart("blogUpdateRequestDto") BlogUpdateRequestDto blogUpdateRequestDto, + @RequestPart(name = "imageFiles", required = false) List imageFiles, + HttpSession session) { + + String userId = (String) session.getAttribute("USER"); + if (userId == null) return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + + try { + // 새로 업로드할 파일만 세팅 (기존 imageUrls는 DTO에서 받아옴) + blogUpdateRequestDto.setImageFiles(imageFiles); + + blogService.updateBlog(blogUpdateRequestDto, userId); + + return ResponseEntity.status(HttpStatus.FOUND) + .header(HttpHeaders.LOCATION, "/api/blog") + .build(); + + } catch (Exception e) { + e.printStackTrace(); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); + } + } + + + @PostMapping("/delete") public ResponseEntity deleteBlog( diff --git a/src/main/java/com/boot/swlugweb/v1/blog/BlogDto.java b/src/main/java/com/boot/swlugweb/v1/blog/BlogDto.java index 047c9a3..c12a282 100644 --- a/src/main/java/com/boot/swlugweb/v1/blog/BlogDto.java +++ b/src/main/java/com/boot/swlugweb/v1/blog/BlogDto.java @@ -1,6 +1,7 @@ package com.boot.swlugweb.v1.blog; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; import lombok.Setter; import java.time.LocalDateTime; @@ -29,6 +30,41 @@ public class BlogDto { private Integer isDelete = 0; private String thumbnailImage; // 필드는 유지 + public String getThumbnailUrl() { + if (image != null && !image.isEmpty()) { + String firstImage = image.get(0); + return firstImage.startsWith("/api/blog/images/") + ? firstImage + : "/api/blog/images/" + firstImage; + } + return "/img/apply_swlug.png"; + } + + //test0621 +// public String getThumbnailUrl() { +// if (image != null && !image.isEmpty()) { +// String firstImage = image.get(0); +// // 드라이브 URL이면 그대로 반환, 아니라면 경로 덧붙이기 +// if (firstImage.startsWith("http")) { +// return firstImage; +// } +// return "/api/blog/images/" + firstImage; +// } +// return "/img/apply_swlug.png"; +// } + +// public String getThumbnailUrl() { +// if (image != null && !image.isEmpty()) { +// String firstImage = image.get(0); +// if (firstImage != null && !firstImage.isBlank()) { +// return firstImage.startsWith("/api/blog/images/") +// ? firstImage +// : "/api/blog/images/" + firstImage; +// } +// } +// return "/img/apply_swlug.png"; +// } + // public String getThumbnailUrl() { // if (image != null && !image.isEmpty()) { // String firstImage = image.get(0); @@ -39,17 +75,6 @@ public class BlogDto { // return "/img/apply_swlug.png"; // } - //test0621 - public String getThumbnailUrl() { - if (image != null && !image.isEmpty()) { - String firstImage = image.get(0); - // 드라이브 URL이면 그대로 반환, 아니라면 경로 덧붙이기 - if (firstImage.startsWith("http")) { - return firstImage; - } - return "/api/blog/images/" + firstImage; - } - return "/img/apply_swlug.png"; - } + } \ No newline at end of file diff --git a/src/main/java/com/boot/swlugweb/v1/blog/BlogService.java b/src/main/java/com/boot/swlugweb/v1/blog/BlogService.java index 59dc54e..d842c77 100644 --- a/src/main/java/com/boot/swlugweb/v1/blog/BlogService.java +++ b/src/main/java/com/boot/swlugweb/v1/blog/BlogService.java @@ -166,13 +166,58 @@ public BlogDomain createBlog(BlogCreateDto blogCreateDto, String userId) throws } blogDomain.setImage(uploadedImageUrls); - if (!uploadedImageUrls.isEmpty()) { - blogDomain.setThumbnailImage(uploadedImageUrls.get(0)); - } +// if (!uploadedImageUrls.isEmpty()) { +// blogDomain.setThumbnailImage(uploadedImageUrls.get(0)); +// } + + return blogRepository.save(blogDomain); } +//원본 +// public BlogDomain createBlog(BlogCreateDto blogCreateDto, String userId) throws Exception { +// BlogDomain blogDomain = new BlogDomain(); +// +// blogDomain.setUserId(userId); +// blogDomain.setBoardCategory(blogCreateDto.getBoardCategory()); +// blogDomain.setBoardTitle(blogCreateDto.getBoardTitle()); +// blogDomain.setBoardContents(blogCreateDto.getBoardContent()); +// blogDomain.setCreateAt(LocalDateTime.now()); +// blogDomain.setTag(blogCreateDto.getTag()); +// blogDomain.setIsPin(false); +// blogDomain.setIsSecure(0); +// blogDomain.setIsDelete(0); +// +// List uploadedImageUrls = new ArrayList<>(); +// if (blogCreateDto.getImageFiles() != null && !blogCreateDto.getImageFiles().isEmpty()) { +// for (MultipartFile file : blogCreateDto.getImageFiles()) { +// try { +// String imageUrl = saveImage(file); +// uploadedImageUrls.add(imageUrl); +// } catch (Exception e) { +// uploadedImageUrls.forEach(this::deleteImage); +// throw e; +// } +// } +// } +// +// // HTML 컨텐츠에서 이미지 URL 추출 +// Pattern pattern = Pattern.compile("src=\"(/api/blog/images/[^\"]+)\""); +// Matcher matcher = pattern.matcher(blogCreateDto.getBoardContent()); +// while (matcher.find()) { +// String imageUrl = matcher.group(1); +// if (!uploadedImageUrls.contains(imageUrl)) { +// uploadedImageUrls.add(imageUrl); +// } +// } +// +// blogDomain.setImage(uploadedImageUrls); +// return blogRepository.save(blogDomain); +// +// } + + // //구글버전 // public String saveImage(MultipartFile file) throws Exception { @@ -263,56 +308,94 @@ public void deleteImage(String imageUrl) { // } +//ㅅㄷㄴㅅ - - - - - public void updateBlog(BlogUpdateRequestDto blogUpdateRequestDto, String userId) throws Exception { - BlogDomain blog = blogRepository.findById(blogUpdateRequestDto.getId()) + public void updateBlog(BlogUpdateRequestDto dto, String userId) throws Exception { + BlogDomain blog = blogRepository.findById(dto.getId()) .orElseThrow(() -> new IllegalArgumentException("Blog not found")); if (!blog.getUserId().equals(userId)) { throw new SecurityException("Not authorized"); } - if (blogUpdateRequestDto.getBoardTitle() != null) { - blog.setBoardTitle(blogUpdateRequestDto.getBoardTitle()); - } - if (blogUpdateRequestDto.getBoardContent() != null) { - blog.setBoardContents(blogUpdateRequestDto.getBoardContent()); - } - if (blogUpdateRequestDto.getTag() != null) { - blog.setTag(blogUpdateRequestDto.getTag()); - } + // 제목, 내용, 태그 수정 + if (dto.getBoardTitle() != null) blog.setBoardTitle(dto.getBoardTitle()); + if (dto.getBoardContent() != null) blog.setBoardContents(dto.getBoardContent()); + if (dto.getTag() != null) blog.setTag(dto.getTag()); + // 기존 이미지 목록 List currentImageUrls = blog.getImage() != null ? new ArrayList<>(blog.getImage()) : new ArrayList<>(); - List updatedImageUrls = blogUpdateRequestDto.getImageUrls() != null ? - blogUpdateRequestDto.getImageUrls() : new ArrayList<>(); + List updatedImageUrls = dto.getImageUrls() != null ? new ArrayList<>(dto.getImageUrls()) : new ArrayList<>(); + // 삭제 대상 이미지 추출 (기존에 있었지만 dto.getImageUrls()에 없는 것들) List imagesToDelete = new ArrayList<>(currentImageUrls); imagesToDelete.removeAll(updatedImageUrls); + for (String imageUrl : imagesToDelete) { - deleteImage(imageUrl); + deleteImage(imageUrl); // imageUrl에서 fileId 추출 → Google Drive에서 삭제 } - if (blogUpdateRequestDto.getImageFiles() != null) { - for (MultipartFile file : blogUpdateRequestDto.getImageFiles()) { - try { - String imageUrl = saveImage(file); - updatedImageUrls.add(imageUrl); - } catch (Exception e) { - e.printStackTrace(); - throw e; - } - } + // 새로 추가할 이미지 업로드 + if (dto.getImageFiles() != null) { + for (MultipartFile file : dto.getImageFiles()) { + String imageUrl = saveImage(file); // Google Drive에 업로드 후 URL 반환 + updatedImageUrls.add(imageUrl); } - - blog.setImage(updatedImageUrls); - blog.setUpdateAt(LocalDateTime.now()); - blogRepository.save(blog); } + blog.setImage(updatedImageUrls); + blog.setUpdateAt(LocalDateTime.now()); + blogRepository.save(blog); +} +//ㅅㄷㄴㅅ + + +// +// public void updateBlog(BlogUpdateRequestDto blogUpdateRequestDto, String userId) throws Exception { +// BlogDomain blog = blogRepository.findById(blogUpdateRequestDto.getId()) +// .orElseThrow(() -> new IllegalArgumentException("Blog not found")); +// +// if (!blog.getUserId().equals(userId)) { +// throw new SecurityException("Not authorized"); +// } +// +// if (blogUpdateRequestDto.getBoardTitle() != null) { +// blog.setBoardTitle(blogUpdateRequestDto.getBoardTitle()); +// } +// if (blogUpdateRequestDto.getBoardContent() != null) { +// blog.setBoardContents(blogUpdateRequestDto.getBoardContent()); +// } +// if (blogUpdateRequestDto.getTag() != null) { +// blog.setTag(blogUpdateRequestDto.getTag()); +// } +// +// List currentImageUrls = blog.getImage() != null ? new ArrayList<>(blog.getImage()) : new ArrayList<>(); +// List updatedImageUrls = blogUpdateRequestDto.getImageUrls() != null ? +// blogUpdateRequestDto.getImageUrls() : new ArrayList<>(); +// +// List imagesToDelete = new ArrayList<>(currentImageUrls); +// imagesToDelete.removeAll(updatedImageUrls); +// for (String imageUrl : imagesToDelete) { +// deleteImage(imageUrl); +// } +// +// if (blogUpdateRequestDto.getImageFiles() != null) { +// for (MultipartFile file : blogUpdateRequestDto.getImageFiles()) { +// try { +// String imageUrl = saveImage(file); +// updatedImageUrls.add(imageUrl); +// } catch (Exception e) { +// e.printStackTrace(); +// throw e; +// } +// } +// } +// +// blog.setImage(updatedImageUrls); +// blog.setUpdateAt(LocalDateTime.now()); +// blogRepository.save(blog); +// } + public void deleteBlog(BlogDeleteRequestDto blogDeleteRequestDto, String userId) { BlogDomain blog = blogRepository.findById(blogDeleteRequestDto.getId()) .orElseThrow(() -> new IllegalArgumentException("Blog not found")); diff --git a/src/main/java/com/boot/swlugweb/v1/blog/GoogleDriveService.java b/src/main/java/com/boot/swlugweb/v1/blog/GoogleDriveService.java index a43bbac..1a81681 100644 --- a/src/main/java/com/boot/swlugweb/v1/blog/GoogleDriveService.java +++ b/src/main/java/com/boot/swlugweb/v1/blog/GoogleDriveService.java @@ -16,6 +16,9 @@ import com.google.api.services.drive.Drive; import com.google.api.services.drive.DriveScopes; import com.google.api.services.drive.model.Permission; +import com.google.auth.http.HttpCredentialsAdapter; +import com.google.auth.oauth2.GoogleCredentials; +import com.google.auth.oauth2.ServiceAccountCredentials; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; @@ -29,43 +32,143 @@ import static com.google.api.client.googleapis.media.MediaHttpUploader.UploadState.INITIATION_STARTED; + @Service public class GoogleDriveService { - private static final String APPLICATION_NAME = "Google Drive API Java Quickstart"; +// private static final String APPLICATION_NAME = "Google Drive API Java Quickstart"; +// private static final GsonFactory JSON_FACTORY = GsonFactory.getDefaultInstance(); +// private static final String TOKENS_DIRECTORY_PATH = "tokens"; +// private static final List SCOPES = Collections.singletonList(DriveScopes.DRIVE); +// private static final String CREDENTIALS_FILE_PATH = "/credentials1.json"; + + //서비스 코드 + private static final String APPLICATION_NAME = "Google Drive API Java with Service Account"; private static final GsonFactory JSON_FACTORY = GsonFactory.getDefaultInstance(); - private static final String TOKENS_DIRECTORY_PATH = "tokens"; - private static final List SCOPES = Collections.singletonList(DriveScopes.DRIVE); - private static final String CREDENTIALS_FILE_PATH = "/credentials.json"; - + private static final String SERVICE_ACCOUNT_KEY_PATH = "/service_account_key.json"; + // private final Drive driveService; - public GoogleDriveService() throws GeneralSecurityException, IOException { + //서비스 코드 + public GoogleDriveService() throws Exception { final NetHttpTransport HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport(); + GoogleCredentials credentials = getCredentials(); this.driveService = new Drive.Builder( HTTP_TRANSPORT, JSON_FACTORY, - getCredentials(HTTP_TRANSPORT) + new HttpCredentialsAdapter(credentials) ).setApplicationName(APPLICATION_NAME).build(); } + private GoogleCredentials getCredentials() throws IOException { + InputStream in = GoogleDriveService.class.getResourceAsStream(SERVICE_ACCOUNT_KEY_PATH); + if (in == null) { + throw new FileNotFoundException("Service account key not found at " + SERVICE_ACCOUNT_KEY_PATH); + } + return ServiceAccountCredentials.fromStream(in) + .createScoped(Collections.singletonList("https://www.googleapis.com/auth/drive")); + } + public String uploadFileToDrive(MultipartFile file) { + try { + // 1. 파일 메타데이터 설정 + com.google.api.services.drive.model.File fileMetadata = new com.google.api.services.drive.model.File(); + fileMetadata.setName(file.getOriginalFilename()); + + // 2. 파일 내용 설정 (application/octet-stream은 범용이지만 필요시 "image/jpeg" 등으로 교체 가능) + InputStreamContent mediaContent = new InputStreamContent( + "application/octet-stream", file.getInputStream() + ); + + // 3. 업로드 요청 생성 + Drive.Files.Create createRequest = driveService.files() + .create(fileMetadata, mediaContent) + .setFields("id, webViewLink"); + + // 4. 업로드 상태 로깅 리스너 등록 + createRequest.getMediaHttpUploader().setProgressListener( + uploader -> { + switch (uploader.getUploadState()) { + case INITIATION_STARTED: + System.out.println("✅ Upload Initiation Started"); + break; + case INITIATION_COMPLETE: + System.out.println("✅ Upload Initiation Complete"); + break; + case MEDIA_IN_PROGRESS: + System.out.printf("🔄 Upload Progress: %.2f%%\n", uploader.getProgress() * 100); + break; + case MEDIA_COMPLETE: + System.out.println("✅ Upload Complete"); + break; + default: + System.out.println("⚠️ Upload State: " + uploader.getUploadState()); + break; + } + } + ); - private static Credential getCredentials(final NetHttpTransport HTTP_TRANSPORT) throws IOException { - InputStream in = GoogleDriveService.class.getResourceAsStream(CREDENTIALS_FILE_PATH); - if (in == null) { - throw new FileNotFoundException("Resource not found: " + CREDENTIALS_FILE_PATH); + // 5. 실제 업로드 수행 + com.google.api.services.drive.model.File uploadedFile = createRequest.execute(); + String fileId = uploadedFile.getId(); + + System.out.println("✅ File Uploaded: " + fileId); + + // 6. 업로드된 파일 공개 권한 설정 + setFilePublic(fileId); + + // 7. 공유 가능한 웹 뷰 링크 반환 + return uploadedFile.getWebViewLink(); + + } catch (Exception e) { + System.err.println("❌ 파일 업로드 실패: " + file.getOriginalFilename()); + e.printStackTrace(); + throw new RuntimeException("Google Drive upload failed", e); } - GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(JSON_FACTORY, new InputStreamReader(in)); - GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder( - HTTP_TRANSPORT, JSON_FACTORY, clientSecrets, SCOPES) - .setDataStoreFactory(new FileDataStoreFactory(new java.io.File(TOKENS_DIRECTORY_PATH))) - .setAccessType("offline") - .build(); - LocalServerReceiver receiver = new LocalServerReceiver.Builder().setPort(8080).build(); - return new AuthorizationCodeInstalledApp(flow, receiver).authorize("user"); } + private void setFilePublic(String fileId) throws IOException { + Permission permission = new Permission() + .setType("anyone") // 누구나 접근 가능 + .setRole("reader") // 읽기 권한만 + .setAllowFileDiscovery(false); // 검색엔진에서 노출 방지 + + driveService.permissions().create(fileId, permission) + .setFields("id") + .execute(); + + System.out.println("🌍 File is now public: " + fileId); + } + + + + //서비스 + +// public GoogleDriveService() throws GeneralSecurityException, IOException { +// final NetHttpTransport HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport(); +// this.driveService = new Drive.Builder( +// HTTP_TRANSPORT, +// JSON_FACTORY, +// getCredentials(HTTP_TRANSPORT) +// ).setApplicationName(APPLICATION_NAME).build(); +// } + +// private static Credential getCredentials(final NetHttpTransport HTTP_TRANSPORT) throws IOException { +// InputStream in = GoogleDriveService.class.getResourceAsStream(CREDENTIALS_FILE_PATH); +// if (in == null) { +// throw new FileNotFoundException("Resource not found: " + CREDENTIALS_FILE_PATH); +// } +// GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(JSON_FACTORY, new InputStreamReader(in)); +// GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder( +// HTTP_TRANSPORT, JSON_FACTORY, clientSecrets, SCOPES) +// .setDataStoreFactory(new FileDataStoreFactory(new java.io.File(TOKENS_DIRECTORY_PATH))) +// .setAccessType("offline") +// .build(); +// LocalServerReceiver receiver = new LocalServerReceiver.Builder().setPort(8080).build(); +// return new AuthorizationCodeInstalledApp(flow, receiver).authorize("user"); +// } + + public String uploadFile(MultipartFile file) throws IOException { java.io.File convFile = new java.io.File(System.getProperty("java.io.tmpdir") + "/" + file.getOriginalFilename()); file.transferTo(convFile); @@ -113,68 +216,68 @@ private java.io.File convertMultipartFileToFile(MultipartFile file) throws IOExc } - public String uploadFileToDrive(MultipartFile file) throws IOException { - // Google Drive에 업로드할 파일 메타데이터 설정 - com.google.api.services.drive.model.File fileMetadata = new com.google.api.services.drive.model.File(); - fileMetadata.setName(file.getOriginalFilename()); - - // 업로드할 파일 내용 설정 - InputStreamContent mediaContent = new InputStreamContent( - "application/octet-stream", file.getInputStream() - ); - - // 파일을 구글 드라이브에 업로드 - Drive.Files.Create createRequest = driveService.files().create(fileMetadata, mediaContent); - - // 업로드 진행 상태를 모니터링 - createRequest.getMediaHttpUploader().setProgressListener( - new MediaHttpUploaderProgressListener() { - public void progressChanged(MediaHttpUploader uploader) throws IOException { - switch (uploader.getUploadState()) { - case INITIATION_STARTED: - System.out.println("Initiation Started"); - break; - case INITIATION_COMPLETE: - System.out.println("Initiation Complete"); - break; - case MEDIA_IN_PROGRESS: - System.out.println("Upload In Progress"); - break; - case MEDIA_COMPLETE: - System.out.println("Upload Complete"); - break; - } - } - } - ); - - // 파일 업로드 실행 - com.google.api.services.drive.model.File uploadedFile = createRequest.execute(); - String fileId = uploadedFile.getId(); - - // 파일 공개 권한 설정 (필수) - setFilePublic(fileId); - - // 이미지 원본 URL 반환 - return driveService.files().get(fileId).execute().getWebViewLink(); - - // 업로드된 파일의 ID를 반환 (WebViewLink로 파일을 찾을 수 있음) - //return "https://drive.google.com/file/d/" + uploadedFile.getId() + "/view"; - } - - // 파일을 "공개"로 설정하는 메서드 수정 - private void setFilePublic(String fileId) throws IOException { - Permission permission = new Permission() - .setType("anyone") // 모든 사용자 접근 허용 - .setRole("reader") // 읽기 권한 부여 - .setAllowFileDiscovery(false); // 검색 엔진에서 노출되지 않도록 설정 - - driveService.permissions().create(fileId, permission) - .setFields("id") - .execute(); - - System.out.println("File is now public: " + fileId); - } +// public String uploadFileToDrive(MultipartFile file) throws IOException { +// // Google Drive에 업로드할 파일 메타데이터 설정 +// com.google.api.services.drive.model.File fileMetadata = new com.google.api.services.drive.model.File(); +// fileMetadata.setName(file.getOriginalFilename()); +// +// // 업로드할 파일 내용 설정 +// InputStreamContent mediaContent = new InputStreamContent( +// "application/octet-stream", file.getInputStream() +// ); +// +// // 파일을 구글 드라이브에 업로드 +// Drive.Files.Create createRequest = driveService.files().create(fileMetadata, mediaContent); +// +// // 업로드 진행 상태를 모니터링 +// createRequest.getMediaHttpUploader().setProgressListener( +// new MediaHttpUploaderProgressListener() { +// public void progressChanged(MediaHttpUploader uploader) throws IOException { +// switch (uploader.getUploadState()) { +// case INITIATION_STARTED: +// System.out.println("Initiation Started"); +// break; +// case INITIATION_COMPLETE: +// System.out.println("Initiation Complete"); +// break; +// case MEDIA_IN_PROGRESS: +// System.out.println("Upload In Progress"); +// break; +// case MEDIA_COMPLETE: +// System.out.println("Upload Complete"); +// break; +// } +// } +// } +// ); +// +// // 파일 업로드 실행 +// com.google.api.services.drive.model.File uploadedFile = createRequest.execute(); +// String fileId = uploadedFile.getId(); +// +// // 파일 공개 권한 설정 (필수) +// setFilePublic(fileId); +// +// // 이미지 원본 URL 반환 +// return driveService.files().get(fileId).execute().getWebViewLink(); +// +// // 업로드된 파일의 ID를 반환 (WebViewLink로 파일을 찾을 수 있음) +// //return "https://drive.google.com/file/d/" + uploadedFile.getId() + "/view"; +// } +// +// // 파일을 "공개"로 설정하는 메서드 수정 +// private void setFilePublic(String fileId) throws IOException { +// Permission permission = new Permission() +// .setType("anyone") // 모든 사용자 접근 허용 +// .setRole("reader") // 읽기 권한 부여 +// .setAllowFileDiscovery(false); // 검색 엔진에서 노출되지 않도록 설정 +// +// driveService.permissions().create(fileId, permission) +// .setFields("id") +// .execute(); +// +// System.out.println("File is now public: " + fileId); +// } } From 549ad4a2c0aab1476af8974cd9805f19e5b712cc Mon Sep 17 00:00:00 2001 From: TigerDemon Date: Mon, 30 Jun 2025 11:20:33 +0900 Subject: [PATCH 4/5] drivetest3-service.ver --- build.gradle | 3 + .../boot/swlugweb/v1/blog/BlogController.java | 38 ++++- .../com/boot/swlugweb/v1/blog/BlogDto.java | 2 +- .../boot/swlugweb/v1/blog/BlogService.java | 147 ++++++------------ .../swlugweb/v1/blog/GoogleDriveService.java | 6 + 5 files changed, 93 insertions(+), 103 deletions(-) diff --git a/build.gradle b/build.gradle index 2b43b25..1215d8d 100644 --- a/build.gradle +++ b/build.gradle @@ -38,6 +38,9 @@ dependencies { implementation 'com.google.api-client:google-api-client:2.0.0' implementation 'com.google.auth:google-auth-library-oauth2-http:1.11.0' implementation 'com.google.code.gson:gson:2.11.0' + implementation 'com.google.api-client:google-api-client:2.2.0' + implementation 'com.google.auth:google-auth-library-credentials:1.16.1' + // JWT 관련 의존성 implementation 'io.jsonwebtoken:jjwt-api:0.11.5' runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5' diff --git a/src/main/java/com/boot/swlugweb/v1/blog/BlogController.java b/src/main/java/com/boot/swlugweb/v1/blog/BlogController.java index b0d296e..b160941 100644 --- a/src/main/java/com/boot/swlugweb/v1/blog/BlogController.java +++ b/src/main/java/com/boot/swlugweb/v1/blog/BlogController.java @@ -116,6 +116,32 @@ public ResponseEntity saveBlog( // } // } +// @PostMapping(value = "/update", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) +// public ResponseEntity updateBlogPost( +// @RequestPart("blogUpdateRequestDto") BlogUpdateRequestDto blogUpdateRequestDto, +// @RequestPart(name = "imageFiles", required = false) List imageFiles, +// HttpSession session) { +// +// String userId = (String) session.getAttribute("USER"); +// if (userId == null) return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); +// +// try { +// // 새로 업로드할 파일만 세팅 (기존 imageUrls는 DTO에서 받아옴) +// blogUpdateRequestDto.setImageFiles(imageFiles); +// +// blogService.updateBlog(blogUpdateRequestDto, userId); +// +// return ResponseEntity.status(HttpStatus.FOUND) +// .header(HttpHeaders.LOCATION, "/api/blog") +// .build(); +// +// } catch (Exception e) { +// e.printStackTrace(); +// return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); +// } +// } + + //0630 test @PostMapping(value = "/update", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseEntity updateBlogPost( @RequestPart("blogUpdateRequestDto") BlogUpdateRequestDto blogUpdateRequestDto, @@ -123,12 +149,13 @@ public ResponseEntity updateBlogPost( HttpSession session) { String userId = (String) session.getAttribute("USER"); - if (userId == null) return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + if (userId == null) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); + } try { - // 새로 업로드할 파일만 세팅 (기존 imageUrls는 DTO에서 받아옴) + // 새 이미지 파일들을 DTO에 주입 blogUpdateRequestDto.setImageFiles(imageFiles); - blogService.updateBlog(blogUpdateRequestDto, userId); return ResponseEntity.status(HttpStatus.FOUND) @@ -142,6 +169,9 @@ public ResponseEntity updateBlogPost( } + //test0630 + + @PostMapping("/delete") @@ -170,7 +200,7 @@ public ResponseEntity deleteBlog( return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } } -// + // // ✅ URL에서 fileId 추출 // public String extractFileIdFromUrl(String url) { // if (url.contains("drive.google.com/file/d/")) { diff --git a/src/main/java/com/boot/swlugweb/v1/blog/BlogDto.java b/src/main/java/com/boot/swlugweb/v1/blog/BlogDto.java index c12a282..698d612 100644 --- a/src/main/java/com/boot/swlugweb/v1/blog/BlogDto.java +++ b/src/main/java/com/boot/swlugweb/v1/blog/BlogDto.java @@ -31,7 +31,7 @@ public class BlogDto { private String thumbnailImage; // 필드는 유지 public String getThumbnailUrl() { - if (image != null && !image.isEmpty()) { + if (image != null && !image.isEmpty() && image.get(0) != null) { String firstImage = image.get(0); return firstImage.startsWith("/api/blog/images/") ? firstImage diff --git a/src/main/java/com/boot/swlugweb/v1/blog/BlogService.java b/src/main/java/com/boot/swlugweb/v1/blog/BlogService.java index d842c77..f8b29a7 100644 --- a/src/main/java/com/boot/swlugweb/v1/blog/BlogService.java +++ b/src/main/java/com/boot/swlugweb/v1/blog/BlogService.java @@ -219,126 +219,75 @@ public BlogDomain createBlog(BlogCreateDto blogCreateDto, String userId) throws - // //구글버전 -// public String saveImage(MultipartFile file) throws Exception { + //0624 +// public void deleteImage(String imageUrl) { // try { -// if (file.isEmpty()) throw new IllegalArgumentException("Empty file"); -// if (file.getSize() > 20 * 1024 * 1024) throw new IllegalArgumentException("File size exceeds maximum limit"); -// -// String originalFilename = StringUtils.cleanPath(file.getOriginalFilename()); -// String extension = getFileExtension(originalFilename).toLowerCase(); -// Set allowedExtensions = new HashSet<>(Arrays.asList( -// "jpg", "jpeg", "png", "gif", "bmp", "webp", "heic", "heif", "tiff", "tif", "svg" -// )); -// if (!allowedExtensions.contains(extension)) { -// throw new IllegalArgumentException("Invalid file extension"); -// } -// -// // 👉 Google Drive 업로드로 대체 -// return googleDriveService.uploadFile(file); -// -// } catch (IOException e) { -// System.err.println("Error uploading file: " + e.getMessage()); +// googleDriveService.deleteFile(imageUrl); +// } catch (Exception e) { // e.printStackTrace(); -// throw e; -// } -// } +// // e.printStackTrace(); 대신 +// //log.error("이미지 삭제 중 오류 발생 - URL: {}", imageUrl, e); // -// // 파일 확장자 추출 메서드 -// private String getFileExtension(String filename) { -// int lastDotIndex = filename.lastIndexOf('.'); -// if (lastDotIndex > 0) { -// return filename.substring(lastDotIndex + 1); // } -// return ""; // } - //0624 - public void deleteImage(String imageUrl) { + + private void deleteImage(String imageUrl) { try { - googleDriveService.deleteFile(imageUrl); + String fileId = extractFileIdFromUrl(imageUrl); // 🔍 fileId 추출 + googleDriveService.deleteFile(fileId); + System.out.println("🗑️ Deleted fileId: " + fileId); } catch (Exception e) { + System.err.println("❌ Failed to delete image: " + imageUrl); e.printStackTrace(); - // e.printStackTrace(); 대신 - //log.error("이미지 삭제 중 오류 발생 - URL: {}", imageUrl, e); + } + } + + // ✅ URL에서 fileId 추출 + public String extractFileIdFromUrl(String url) { + if (url.contains("drive.google.com/file/d/")) { + int start = url.indexOf("/d/") + 3; + int end = url.indexOf("/", start); + if (start > 2 && end > start) { + return url.substring(start, end); + } } + return null; } -// public BlogDomain createBlog(BlogCreateDto blogCreateDto, String userId) throws Exception { -// BlogDomain blogDomain = new BlogDomain(); -// -// blogDomain.setUserId(userId); -// blogDomain.setBoardCategory(blogCreateDto.getBoardCategory()); -// blogDomain.setBoardTitle(blogCreateDto.getBoardTitle()); -// blogDomain.setBoardContents(blogCreateDto.getBoardContent()); -// blogDomain.setCreateAt(LocalDateTime.now()); -// blogDomain.setUpdateAt(LocalDateTime.now()); -// blogDomain.setTag(blogCreateDto.getTag()); -// blogDomain.setIsPin(false); -// blogDomain.setIsSecure(0); -// blogDomain.setIsDelete(0); -// -// List uploadedImageUrls = new ArrayList<>(); -// if (blogCreateDto.getImageFiles() != null && !blogCreateDto.getImageFiles().isEmpty()) { -// for (MultipartFile file : blogCreateDto.getImageFiles()) { -// try { -// String imageUrl = saveImage(file); -// uploadedImageUrls.add(imageUrl); -// } catch (Exception e) { -// uploadedImageUrls.forEach(this::deleteImage); -// throw e; -// } -// } -// } -// -// // HTML 컨텐츠에서 이미지 URL 추출 -//// Pattern pattern = Pattern.compile("src=\"(/api/blog/images/[^"]+)\""); -// Pattern pattern = Pattern.compile("src=[\"']([^\"']+)[\"']"); -// Matcher matcher = pattern.matcher(blogCreateDto.getBoardContent()); -// while (matcher.find()) { -// String imageUrl = matcher.group(1); -// if (!uploadedImageUrls.contains(imageUrl)) { -// uploadedImageUrls.add(imageUrl); -// } -// } -// -// blogDomain.setImage(uploadedImageUrls); -// return blogRepository.save(blogDomain); -// -// } -//ㅅㄷㄴㅅ - public void updateBlog(BlogUpdateRequestDto dto, String userId) throws Exception { - BlogDomain blog = blogRepository.findById(dto.getId()) - .orElseThrow(() -> new IllegalArgumentException("Blog not found")); +//0630 test +public void updateBlog(BlogUpdateRequestDto dto, String userId) throws Exception { + BlogDomain blog = blogRepository.findById(dto.getId()) + .orElseThrow(() -> new IllegalArgumentException("Blog not found")); - if (!blog.getUserId().equals(userId)) { - throw new SecurityException("Not authorized"); - } + if (!blog.getUserId().equals(userId)) { + throw new SecurityException("Not authorized"); + } - // 제목, 내용, 태그 수정 - if (dto.getBoardTitle() != null) blog.setBoardTitle(dto.getBoardTitle()); - if (dto.getBoardContent() != null) blog.setBoardContents(dto.getBoardContent()); - if (dto.getTag() != null) blog.setTag(dto.getTag()); + // 제목, 내용, 태그 수정 + if (dto.getBoardTitle() != null) blog.setBoardTitle(dto.getBoardTitle()); + if (dto.getBoardContent() != null) blog.setBoardContents(dto.getBoardContent()); + if (dto.getTag() != null) blog.setTag(dto.getTag()); - // 기존 이미지 목록 - List currentImageUrls = blog.getImage() != null ? new ArrayList<>(blog.getImage()) : new ArrayList<>(); - List updatedImageUrls = dto.getImageUrls() != null ? new ArrayList<>(dto.getImageUrls()) : new ArrayList<>(); + // 기존 이미지 목록 + List currentImageUrls = blog.getImage() != null ? new ArrayList<>(blog.getImage()) : new ArrayList<>(); + List updatedImageUrls = dto.getImageUrls() != null ? new ArrayList<>(dto.getImageUrls()) : new ArrayList<>(); - // 삭제 대상 이미지 추출 (기존에 있었지만 dto.getImageUrls()에 없는 것들) - List imagesToDelete = new ArrayList<>(currentImageUrls); - imagesToDelete.removeAll(updatedImageUrls); + // 삭제 대상 이미지 계산 + List imagesToDelete = new ArrayList<>(currentImageUrls); + imagesToDelete.removeAll(updatedImageUrls); - for (String imageUrl : imagesToDelete) { - deleteImage(imageUrl); // imageUrl에서 fileId 추출 → Google Drive에서 삭제 - } + for (String imageUrl : imagesToDelete) { + deleteImage(imageUrl); // Google Drive에서 삭제 + } // 새로 추가할 이미지 업로드 - if (dto.getImageFiles() != null) { + if (dto.getImageFiles() != null && !dto.getImageFiles().isEmpty()) { for (MultipartFile file : dto.getImageFiles()) { - String imageUrl = saveImage(file); // Google Drive에 업로드 후 URL 반환 + String imageUrl = saveImage(file); // 업로드 후 URL 반환 updatedImageUrls.add(imageUrl); } } @@ -347,9 +296,11 @@ public void updateBlog(BlogUpdateRequestDto dto, String userId) throws Exception blog.setUpdateAt(LocalDateTime.now()); blogRepository.save(blog); } -//ㅅㄷㄴㅅ + + //test0630 + // // public void updateBlog(BlogUpdateRequestDto blogUpdateRequestDto, String userId) throws Exception { // BlogDomain blog = blogRepository.findById(blogUpdateRequestDto.getId()) diff --git a/src/main/java/com/boot/swlugweb/v1/blog/GoogleDriveService.java b/src/main/java/com/boot/swlugweb/v1/blog/GoogleDriveService.java index 1a81681..9fe2ac9 100644 --- a/src/main/java/com/boot/swlugweb/v1/blog/GoogleDriveService.java +++ b/src/main/java/com/boot/swlugweb/v1/blog/GoogleDriveService.java @@ -44,6 +44,7 @@ public class GoogleDriveService { //서비스 코드 private static final String APPLICATION_NAME = "Google Drive API Java with Service Account"; private static final GsonFactory JSON_FACTORY = GsonFactory.getDefaultInstance(); + private static final List SCOPES = Collections.singletonList(DriveScopes.DRIVE_FILE); private static final String SERVICE_ACCOUNT_KEY_PATH = "/service_account_key.json"; // @@ -142,6 +143,11 @@ private void setFilePublic(String fileId) throws IOException { + + + + + //서비스 // public GoogleDriveService() throws GeneralSecurityException, IOException { From 728a7d8cc9dba12c2f0625cdc1e179a13b632889 Mon Sep 17 00:00:00 2001 From: TigerDemon Date: Tue, 8 Jul 2025 14:20:40 +0900 Subject: [PATCH 5/5] =?UTF-8?q?drive-service.ver=20=EC=95=BD=EA=B0=84=20?= =?UTF-8?q?=EC=B5=9C=EC=A2=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + .../boot/swlugweb/v1/blog/BlogController.java | 194 ++++--------- .../com/boot/swlugweb/v1/blog/BlogDto.java | 37 --- .../boot/swlugweb/v1/blog/BlogService.java | 262 ++++-------------- .../swlugweb/v1/blog/GoogleDriveService.java | 122 +------- 5 files changed, 120 insertions(+), 496 deletions(-) diff --git a/.gitignore b/.gitignore index 26572e9..f97013b 100644 --- a/.gitignore +++ b/.gitignore @@ -111,6 +111,7 @@ desktop.ini # Environment files application.properties credentials.json +service_account_key.json #프론트 templeates/ diff --git a/src/main/java/com/boot/swlugweb/v1/blog/BlogController.java b/src/main/java/com/boot/swlugweb/v1/blog/BlogController.java index b160941..1a03471 100644 --- a/src/main/java/com/boot/swlugweb/v1/blog/BlogController.java +++ b/src/main/java/com/boot/swlugweb/v1/blog/BlogController.java @@ -1,30 +1,23 @@ package com.boot.swlugweb.v1.blog; import jakarta.servlet.http.HttpSession; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.core.io.Resource; -import org.springframework.core.io.UrlResource; import org.springframework.http.*; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import lombok.extern.slf4j.Slf4j; + +@Slf4j @RestController @RequestMapping("/api/blog") public class BlogController { -// @Value("${file.upload-dir}") -// private String uploadDir; - private final BlogService blogService; private final GoogleDriveService googleDriveService; @@ -83,65 +76,6 @@ public ResponseEntity saveBlog( - - //구글 버전 -// @PostMapping(value = "/update", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) -// public ResponseEntity updateBlogPost( -// @RequestPart("blogUpdateRequestDto") BlogUpdateRequestDto blogUpdateRequestDto, -// @RequestPart(name = "imageFiles", required = false) List imageFiles, -// HttpSession session) { -// -// String userId = (String) session.getAttribute("USER"); -// if (userId == null) return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); -// -// try { -// List imageUrls = new ArrayList<>(); -// if (imageFiles != null && !imageFiles.isEmpty()) { -// for (MultipartFile file : imageFiles) { -// String url = GoogleDriveService.uploadFileToDrive(file); -// imageUrls.add(url); -// } -// } -// -// blogUpdateRequestDto.setImageUrls(imageUrls); -// blogUpdateRequestDto.setImageFiles(imageFiles); -// blogService.updateBlog(blogUpdateRequestDto, userId); -// -// return ResponseEntity.status(HttpStatus.FOUND) -// .header(HttpHeaders.LOCATION, "/api/blog") -// .build(); -// -// } catch (Exception e) { -// return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); -// } -// } - -// @PostMapping(value = "/update", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) -// public ResponseEntity updateBlogPost( -// @RequestPart("blogUpdateRequestDto") BlogUpdateRequestDto blogUpdateRequestDto, -// @RequestPart(name = "imageFiles", required = false) List imageFiles, -// HttpSession session) { -// -// String userId = (String) session.getAttribute("USER"); -// if (userId == null) return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); -// -// try { -// // 새로 업로드할 파일만 세팅 (기존 imageUrls는 DTO에서 받아옴) -// blogUpdateRequestDto.setImageFiles(imageFiles); -// -// blogService.updateBlog(blogUpdateRequestDto, userId); -// -// return ResponseEntity.status(HttpStatus.FOUND) -// .header(HttpHeaders.LOCATION, "/api/blog") -// .build(); -// -// } catch (Exception e) { -// e.printStackTrace(); -// return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); -// } -// } - - //0630 test @PostMapping(value = "/update", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseEntity updateBlogPost( @RequestPart("blogUpdateRequestDto") BlogUpdateRequestDto blogUpdateRequestDto, @@ -169,9 +103,6 @@ public ResponseEntity updateBlogPost( } - //test0630 - - @PostMapping("/delete") @@ -185,11 +116,18 @@ public ResponseEntity deleteBlog( try { List imageUrls = blogService.getImageUrlsByBlogId(blogDeleteRequestDto.getId()); + for (String url : imageUrls) { -// String fileId = extractFileIdFromUrl(url); - googleDriveService.deleteFile(url); + String fileId = extractFileIdFromUrl(url); + if (fileId == null) { + log.warn("유효하지 않은 Google Drive 이미지 URL입니다: {}", url); + continue; + } + + googleDriveService.deleteFile(fileId); } + blogService.deleteBlog(blogDeleteRequestDto, userId); return ResponseEntity.status(HttpStatus.FOUND) @@ -201,17 +139,34 @@ public ResponseEntity deleteBlog( } } -// // ✅ URL에서 fileId 추출 -// public String extractFileIdFromUrl(String url) { -// if (url.contains("drive.google.com/file/d/")) { -// int start = url.indexOf("/d/") + 3; -// int end = url.indexOf("/", start); -// if (start > 2 && end > start) { -// return url.substring(start, end); -// } -// } -// return null; -// } + public String extractFileIdFromUrl(String url) { + if (url == null) return null; + + // 1. drive.google.com 링크 + if (url.contains("drive.google.com/file/d/")) { + int start = url.indexOf("/d/") + 3; + int end = url.indexOf("/", start); + if (start > 2 && end > start) { + return url.substring(start, end); + } + } + + // 2. lh3.googleusercontent.com 링크 + if (url.contains("lh3.googleusercontent.com/d/")) { + int start = url.indexOf("/d/") + 3; + int end = url.indexOf("?", start); // 쿼리 파라미터 제거 (optional) + if (end == -1) end = url.length(); // ?가 없는 경우 + return url.substring(start, end); + } + + // 3. https://drive.google.com/uc?id=FILE_ID + Pattern pattern = Pattern.compile("id=([^&]+)"); + Matcher matcher = pattern.matcher(url); + if (matcher.find()) { + return matcher.group(1); + } + return null; + } @@ -228,51 +183,22 @@ public ResponseEntity> searchBlogsByAdjacent(@Reques return ResponseEntity.ok(adjacentBlogs); } -// // 새로운 이미지 업로드 엔드포인트 -// @PostMapping("/upload-image") -// @ResponseBody -// public ResponseEntity> uploadImage(@RequestParam("upload") MultipartFile file) { -// try { -// String imageUrl = blogService.saveImage(file); -// Map response = new HashMap<>(); -// response.put("uploaded", true); -// response.put("url", imageUrl); -// return ResponseEntity.ok(response); -// } catch (Exception e) { -// Map response = new HashMap<>(); -// response.put("uploaded", false); -// response.put("error", Map.of("message", "Image upload failed: " + e.getMessage())); -// return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response); -// } -// } -// -// // 이미지 조회 엔드포인트 -// @GetMapping("/images/{filename:.+}") -// @ResponseBody -// public ResponseEntity serveImage(@PathVariable String filename) { -// try { -// Path imagePath = Paths.get(uploadDir).resolve(filename); -// Resource resource = new UrlResource(imagePath.toUri()); -// -// if (resource.exists() && resource.isReadable()) { -// String contentType = Files.probeContentType(imagePath); -// if (contentType == null) { -// contentType = "application/octet-stream"; -// } -// -// // 캐시 설정 추가 -// CacheControl cacheControl = CacheControl.maxAge(365, TimeUnit.DAYS); -// -// return ResponseEntity.ok() -// .cacheControl(cacheControl) -// .contentType(MediaType.parseMediaType(contentType)) -// .header("Content-Disposition", "inline; filename=\"" + filename + "\"") -// .body(resource); -// } else { -// return ResponseEntity.notFound().build(); -// } -// } catch (IOException e) { -// return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); -// } -// } + // 새로운 이미지 업로드 엔드포인트 + @PostMapping("/upload-image") + @ResponseBody + public ResponseEntity> uploadImage(@RequestParam("upload") MultipartFile file) { + try { + String imageUrl = blogService.saveImage(file); + Map response = new HashMap<>(); + response.put("uploaded", true); + response.put("url", imageUrl); + return ResponseEntity.ok(response); + } catch (Exception e) { + Map response = new HashMap<>(); + response.put("uploaded", false); + response.put("error", Map.of("message", "Image upload failed: " + e.getMessage())); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response); + } + } + } \ No newline at end of file diff --git a/src/main/java/com/boot/swlugweb/v1/blog/BlogDto.java b/src/main/java/com/boot/swlugweb/v1/blog/BlogDto.java index 698d612..87eb88a 100644 --- a/src/main/java/com/boot/swlugweb/v1/blog/BlogDto.java +++ b/src/main/java/com/boot/swlugweb/v1/blog/BlogDto.java @@ -40,41 +40,4 @@ public String getThumbnailUrl() { return "/img/apply_swlug.png"; } - //test0621 -// public String getThumbnailUrl() { -// if (image != null && !image.isEmpty()) { -// String firstImage = image.get(0); -// // 드라이브 URL이면 그대로 반환, 아니라면 경로 덧붙이기 -// if (firstImage.startsWith("http")) { -// return firstImage; -// } -// return "/api/blog/images/" + firstImage; -// } -// return "/img/apply_swlug.png"; -// } - -// public String getThumbnailUrl() { -// if (image != null && !image.isEmpty()) { -// String firstImage = image.get(0); -// if (firstImage != null && !firstImage.isBlank()) { -// return firstImage.startsWith("/api/blog/images/") -// ? firstImage -// : "/api/blog/images/" + firstImage; -// } -// } -// return "/img/apply_swlug.png"; -// } - -// public String getThumbnailUrl() { -// if (image != null && !image.isEmpty()) { -// String firstImage = image.get(0); -// return firstImage.startsWith("/api/blog/images/") -// ? firstImage -// : "/api/blog/images/" + firstImage; -// } -// return "/img/apply_swlug.png"; -// } - - - } \ No newline at end of file diff --git a/src/main/java/com/boot/swlugweb/v1/blog/BlogService.java b/src/main/java/com/boot/swlugweb/v1/blog/BlogService.java index f8b29a7..1888d5e 100644 --- a/src/main/java/com/boot/swlugweb/v1/blog/BlogService.java +++ b/src/main/java/com/boot/swlugweb/v1/blog/BlogService.java @@ -9,9 +9,7 @@ import org.springframework.util.StringUtils; import org.springframework.web.multipart.MultipartFile; -import java.io.IOException; -import java.security.GeneralSecurityException; import java.time.LocalDateTime; import java.util.*; import java.util.regex.Matcher; @@ -23,8 +21,8 @@ @Service public class BlogService { - @Value("${file.upload-dir}") - private String uploadDir; +// @Value("${file.upload-dir}") +// private String uploadDir; private final BlogRepository blogRepository; private final MyPageRepository myPageRepository; @@ -35,75 +33,6 @@ public BlogService(BlogRepository blogRepository, MyPageRepository myPageReposit this.myPageRepository = myPageRepository; this.googleDriveService = googleDriveService; } - -// public String saveImage(MultipartFile file) throws Exception { -// if (file == null || file.isEmpty()) throw new IllegalArgumentException("Empty file"); -// -// if (file.getSize() > 20 * 1024 * 1024) // 20MB 제한 -// throw new IllegalArgumentException("File size exceeds maximum limit"); -// -// String originalFilename = StringUtils.cleanPath(file.getOriginalFilename()); -// String extension = getFileExtension(originalFilename).toLowerCase(); -// -// Set allowedExtensions = Set.of( -// "jpg", "jpeg", "png", "gif", "bmp", "webp", "heic", "heif", "tiff", "tif", "svg" -// ); -// -// if (!allowedExtensions.contains(extension)) { -// throw new IllegalArgumentException("Invalid file extension: " + extension); -// } -// -// return googleDriveService.uploadFile(file); -// } -// -// public BlogDomain createBlog(BlogCreateDto blogCreateDto, String userId) throws Exception { -// BlogDomain blogDomain = new BlogDomain(); -// -// blogDomain.setUserId(userId); -// blogDomain.setBoardCategory(blogCreateDto.getBoardCategory()); -// blogDomain.setBoardTitle(blogCreateDto.getBoardTitle()); -// blogDomain.setBoardContents(blogCreateDto.getBoardContent()); -// blogDomain.setCreateAt(LocalDateTime.now()); -// blogDomain.setUpdateAt(LocalDateTime.now()); -// blogDomain.setTag(blogCreateDto.getTag()); -// blogDomain.setIsPin(false); -// blogDomain.setIsSecure(0); -// blogDomain.setIsDelete(0); -// -// List uploadedImageUrls = new ArrayList<>(); -// -// try { -// // 1️⃣ 업로드된 이미지 파일 처리 -// if (blogCreateDto.getImageFiles() != null) { -// for (MultipartFile file : blogCreateDto.getImageFiles()) { -// uploadedImageUrls.add(saveImage(file)); -// } -// } -// -// // 2️⃣ 본문 HTML에 포함된 이미지 URL도 추출해서 저장 -// Pattern pattern = Pattern.compile("src=[\"']([^\"']+)[\"']"); -// Matcher matcher = pattern.matcher(blogCreateDto.getBoardContent()); -// -// while (matcher.find()) { -// String imageUrl = matcher.group(1); -// if (!uploadedImageUrls.contains(imageUrl)) { -// uploadedImageUrls.add(imageUrl); -// } -// } -// -// } catch (Exception e) { -// // 업로드한 이미지 모두 삭제 -// uploadedImageUrls.forEach(this::deleteImage); -// throw e; -// } -// -// blogDomain.setImage(uploadedImageUrls); -// -// return blogRepository.save(blogDomain); -// } - - - //test0621 public String saveImage(MultipartFile file) throws Exception { if (file == null || file.isEmpty()) throw new IllegalArgumentException("Empty file"); @@ -166,70 +95,11 @@ public BlogDomain createBlog(BlogCreateDto blogCreateDto, String userId) throws } blogDomain.setImage(uploadedImageUrls); -// if (!uploadedImageUrls.isEmpty()) { -// blogDomain.setThumbnailImage(uploadedImageUrls.get(0)); -// } - return blogRepository.save(blogDomain); } -//원본 -// public BlogDomain createBlog(BlogCreateDto blogCreateDto, String userId) throws Exception { -// BlogDomain blogDomain = new BlogDomain(); -// -// blogDomain.setUserId(userId); -// blogDomain.setBoardCategory(blogCreateDto.getBoardCategory()); -// blogDomain.setBoardTitle(blogCreateDto.getBoardTitle()); -// blogDomain.setBoardContents(blogCreateDto.getBoardContent()); -// blogDomain.setCreateAt(LocalDateTime.now()); -// blogDomain.setTag(blogCreateDto.getTag()); -// blogDomain.setIsPin(false); -// blogDomain.setIsSecure(0); -// blogDomain.setIsDelete(0); -// -// List uploadedImageUrls = new ArrayList<>(); -// if (blogCreateDto.getImageFiles() != null && !blogCreateDto.getImageFiles().isEmpty()) { -// for (MultipartFile file : blogCreateDto.getImageFiles()) { -// try { -// String imageUrl = saveImage(file); -// uploadedImageUrls.add(imageUrl); -// } catch (Exception e) { -// uploadedImageUrls.forEach(this::deleteImage); -// throw e; -// } -// } -// } -// -// // HTML 컨텐츠에서 이미지 URL 추출 -// Pattern pattern = Pattern.compile("src=\"(/api/blog/images/[^\"]+)\""); -// Matcher matcher = pattern.matcher(blogCreateDto.getBoardContent()); -// while (matcher.find()) { -// String imageUrl = matcher.group(1); -// if (!uploadedImageUrls.contains(imageUrl)) { -// uploadedImageUrls.add(imageUrl); -// } -// } -// -// blogDomain.setImage(uploadedImageUrls); -// return blogRepository.save(blogDomain); -// -// } - - - - //0624 -// public void deleteImage(String imageUrl) { -// try { -// googleDriveService.deleteFile(imageUrl); -// } catch (Exception e) { -// e.printStackTrace(); -// // e.printStackTrace(); 대신 -// //log.error("이미지 삭제 중 오류 발생 - URL: {}", imageUrl, e); -// -// } -// } private void deleteImage(String imageUrl) { try { @@ -245,6 +115,9 @@ private void deleteImage(String imageUrl) { // ✅ URL에서 fileId 추출 public String extractFileIdFromUrl(String url) { + if (url == null) return null; + + // 1. drive.google.com 링크 if (url.contains("drive.google.com/file/d/")) { int start = url.indexOf("/d/") + 3; int end = url.indexOf("/", start); @@ -252,100 +125,63 @@ public String extractFileIdFromUrl(String url) { return url.substring(start, end); } } - return null; - } + // 2. lh3.googleusercontent.com 링크 + if (url.contains("lh3.googleusercontent.com/d/")) { + int start = url.indexOf("/d/") + 3; + int end = url.indexOf("?", start); // 쿼리 파라미터 제거 (optional) + if (end == -1) end = url.length(); // ?가 없는 경우 + return url.substring(start, end); + } + // 3. https://drive.google.com/uc?id=FILE_ID + Pattern pattern = Pattern.compile("id=([^&]+)"); + Matcher matcher = pattern.matcher(url); + if (matcher.find()) { + return matcher.group(1); + } + return null; + } -//0630 test -public void updateBlog(BlogUpdateRequestDto dto, String userId) throws Exception { - BlogDomain blog = blogRepository.findById(dto.getId()) - .orElseThrow(() -> new IllegalArgumentException("Blog not found")); - if (!blog.getUserId().equals(userId)) { - throw new SecurityException("Not authorized"); - } + public void updateBlog(BlogUpdateRequestDto dto, String userId) throws Exception { + BlogDomain blog = blogRepository.findById(dto.getId()) + .orElseThrow(() -> new IllegalArgumentException("Blog not found")); - // 제목, 내용, 태그 수정 - if (dto.getBoardTitle() != null) blog.setBoardTitle(dto.getBoardTitle()); - if (dto.getBoardContent() != null) blog.setBoardContents(dto.getBoardContent()); - if (dto.getTag() != null) blog.setTag(dto.getTag()); + if (!blog.getUserId().equals(userId)) { + throw new SecurityException("Not authorized"); + } + // 제목, 내용, 태그 수정 + if (dto.getBoardTitle() != null) blog.setBoardTitle(dto.getBoardTitle()); + if (dto.getBoardContent() != null) blog.setBoardContents(dto.getBoardContent()); + if (dto.getTag() != null) blog.setTag(dto.getTag()); - // 기존 이미지 목록 - List currentImageUrls = blog.getImage() != null ? new ArrayList<>(blog.getImage()) : new ArrayList<>(); - List updatedImageUrls = dto.getImageUrls() != null ? new ArrayList<>(dto.getImageUrls()) : new ArrayList<>(); + // 기존 이미지 목록 + List currentImageUrls = blog.getImage() != null ? new ArrayList<>(blog.getImage()) : new ArrayList<>(); + List updatedImageUrls = dto.getImageUrls() != null ? new ArrayList<>(dto.getImageUrls()) : new ArrayList<>(); - // 삭제 대상 이미지 계산 - List imagesToDelete = new ArrayList<>(currentImageUrls); - imagesToDelete.removeAll(updatedImageUrls); + // 삭제 대상 이미지 계산 + List imagesToDelete = new ArrayList<>(currentImageUrls); + imagesToDelete.removeAll(updatedImageUrls); - for (String imageUrl : imagesToDelete) { - deleteImage(imageUrl); // Google Drive에서 삭제 - } + for (String imageUrl : imagesToDelete) { + deleteImage(imageUrl); // Google Drive에서 삭제 + } - // 새로 추가할 이미지 업로드 - if (dto.getImageFiles() != null && !dto.getImageFiles().isEmpty()) { - for (MultipartFile file : dto.getImageFiles()) { - String imageUrl = saveImage(file); // 업로드 후 URL 반환 - updatedImageUrls.add(imageUrl); + // 새로 추가할 이미지 업로드 + if (dto.getImageFiles() != null && !dto.getImageFiles().isEmpty()) { + for (MultipartFile file : dto.getImageFiles()) { + String imageUrl = saveImage(file); // 업로드 후 URL 반환 + updatedImageUrls.add(imageUrl); + } } + + blog.setImage(updatedImageUrls); + blog.setUpdateAt(LocalDateTime.now()); + blogRepository.save(blog); } - blog.setImage(updatedImageUrls); - blog.setUpdateAt(LocalDateTime.now()); - blogRepository.save(blog); -} - - - - //test0630 - -// -// public void updateBlog(BlogUpdateRequestDto blogUpdateRequestDto, String userId) throws Exception { -// BlogDomain blog = blogRepository.findById(blogUpdateRequestDto.getId()) -// .orElseThrow(() -> new IllegalArgumentException("Blog not found")); -// -// if (!blog.getUserId().equals(userId)) { -// throw new SecurityException("Not authorized"); -// } -// -// if (blogUpdateRequestDto.getBoardTitle() != null) { -// blog.setBoardTitle(blogUpdateRequestDto.getBoardTitle()); -// } -// if (blogUpdateRequestDto.getBoardContent() != null) { -// blog.setBoardContents(blogUpdateRequestDto.getBoardContent()); -// } -// if (blogUpdateRequestDto.getTag() != null) { -// blog.setTag(blogUpdateRequestDto.getTag()); -// } -// -// List currentImageUrls = blog.getImage() != null ? new ArrayList<>(blog.getImage()) : new ArrayList<>(); -// List updatedImageUrls = blogUpdateRequestDto.getImageUrls() != null ? -// blogUpdateRequestDto.getImageUrls() : new ArrayList<>(); -// -// List imagesToDelete = new ArrayList<>(currentImageUrls); -// imagesToDelete.removeAll(updatedImageUrls); -// for (String imageUrl : imagesToDelete) { -// deleteImage(imageUrl); -// } -// -// if (blogUpdateRequestDto.getImageFiles() != null) { -// for (MultipartFile file : blogUpdateRequestDto.getImageFiles()) { -// try { -// String imageUrl = saveImage(file); -// updatedImageUrls.add(imageUrl); -// } catch (Exception e) { -// e.printStackTrace(); -// throw e; -// } -// } -// } -// -// blog.setImage(updatedImageUrls); -// blog.setUpdateAt(LocalDateTime.now()); -// blogRepository.save(blog); -// } public void deleteBlog(BlogDeleteRequestDto blogDeleteRequestDto, String userId) { BlogDomain blog = blogRepository.findById(blogDeleteRequestDto.getId()) diff --git a/src/main/java/com/boot/swlugweb/v1/blog/GoogleDriveService.java b/src/main/java/com/boot/swlugweb/v1/blog/GoogleDriveService.java index 9fe2ac9..77da034 100644 --- a/src/main/java/com/boot/swlugweb/v1/blog/GoogleDriveService.java +++ b/src/main/java/com/boot/swlugweb/v1/blog/GoogleDriveService.java @@ -1,24 +1,18 @@ package com.boot.swlugweb.v1.blog; -import com.google.api.client.auth.oauth2.Credential; -import com.google.api.client.extensions.java6.auth.oauth2.AuthorizationCodeInstalledApp; -import com.google.api.client.extensions.jetty.auth.oauth2.LocalServerReceiver; -import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow; -import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets; import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport; -import com.google.api.client.googleapis.media.MediaHttpUploader; -import com.google.api.client.googleapis.media.MediaHttpUploaderProgressListener; import com.google.api.client.http.FileContent; import com.google.api.client.http.InputStreamContent; import com.google.api.client.http.javanet.NetHttpTransport; import com.google.api.client.json.gson.GsonFactory; -import com.google.api.client.util.store.FileDataStoreFactory; +import org.springframework.beans.factory.annotation.Value; import com.google.api.services.drive.Drive; import com.google.api.services.drive.DriveScopes; import com.google.api.services.drive.model.Permission; import com.google.auth.http.HttpCredentialsAdapter; import com.google.auth.oauth2.GoogleCredentials; import com.google.auth.oauth2.ServiceAccountCredentials; +import org.springframework.stereotype.Component; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; @@ -30,27 +24,21 @@ import java.util.List; import java.util.UUID; -import static com.google.api.client.googleapis.media.MediaHttpUploader.UploadState.INITIATION_STARTED; - - +@Component @Service public class GoogleDriveService { -// private static final String APPLICATION_NAME = "Google Drive API Java Quickstart"; -// private static final GsonFactory JSON_FACTORY = GsonFactory.getDefaultInstance(); -// private static final String TOKENS_DIRECTORY_PATH = "tokens"; -// private static final List SCOPES = Collections.singletonList(DriveScopes.DRIVE); -// private static final String CREDENTIALS_FILE_PATH = "/credentials1.json"; - - //서비스 코드 private static final String APPLICATION_NAME = "Google Drive API Java with Service Account"; private static final GsonFactory JSON_FACTORY = GsonFactory.getDefaultInstance(); private static final List SCOPES = Collections.singletonList(DriveScopes.DRIVE_FILE); private static final String SERVICE_ACCOUNT_KEY_PATH = "/service_account_key.json"; - // private final Drive driveService; + @Value("${google.drive.folder-id}") + private String folderId; + + //서비스 코드 public GoogleDriveService() throws Exception { final NetHttpTransport HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport(); @@ -75,6 +63,7 @@ public String uploadFileToDrive(MultipartFile file) { // 1. 파일 메타데이터 설정 com.google.api.services.drive.model.File fileMetadata = new com.google.api.services.drive.model.File(); fileMetadata.setName(file.getOriginalFilename()); + fileMetadata.setParents(Collections.singletonList(folderId)); // 폴더 ID // 2. 파일 내용 설정 (application/octet-stream은 범용이지만 필요시 "image/jpeg" 등으로 교체 가능) InputStreamContent mediaContent = new InputStreamContent( @@ -118,8 +107,8 @@ public String uploadFileToDrive(MultipartFile file) { // 6. 업로드된 파일 공개 권한 설정 setFilePublic(fileId); - // 7. 공유 가능한 웹 뷰 링크 반환 - return uploadedFile.getWebViewLink(); + // 7. 공유 가능한 웹 뷰 링크 대신 직접 렌더링 URL 반환 + return "https://lh3.googleusercontent.com/d/" + uploadedFile.getId(); } catch (Exception e) { System.err.println("❌ 파일 업로드 실패: " + file.getOriginalFilename()); @@ -148,32 +137,6 @@ private void setFilePublic(String fileId) throws IOException { - //서비스 - -// public GoogleDriveService() throws GeneralSecurityException, IOException { -// final NetHttpTransport HTTP_TRANSPORT = GoogleNetHttpTransport.newTrustedTransport(); -// this.driveService = new Drive.Builder( -// HTTP_TRANSPORT, -// JSON_FACTORY, -// getCredentials(HTTP_TRANSPORT) -// ).setApplicationName(APPLICATION_NAME).build(); -// } - -// private static Credential getCredentials(final NetHttpTransport HTTP_TRANSPORT) throws IOException { -// InputStream in = GoogleDriveService.class.getResourceAsStream(CREDENTIALS_FILE_PATH); -// if (in == null) { -// throw new FileNotFoundException("Resource not found: " + CREDENTIALS_FILE_PATH); -// } -// GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(JSON_FACTORY, new InputStreamReader(in)); -// GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder( -// HTTP_TRANSPORT, JSON_FACTORY, clientSecrets, SCOPES) -// .setDataStoreFactory(new FileDataStoreFactory(new java.io.File(TOKENS_DIRECTORY_PATH))) -// .setAccessType("offline") -// .build(); -// LocalServerReceiver receiver = new LocalServerReceiver.Builder().setPort(8080).build(); -// return new AuthorizationCodeInstalledApp(flow, receiver).authorize("user"); -// } - public String uploadFile(MultipartFile file) throws IOException { java.io.File convFile = new java.io.File(System.getProperty("java.io.tmpdir") + "/" + file.getOriginalFilename()); @@ -221,70 +184,5 @@ private java.io.File convertMultipartFileToFile(MultipartFile file) throws IOExc return convFile; } - -// public String uploadFileToDrive(MultipartFile file) throws IOException { -// // Google Drive에 업로드할 파일 메타데이터 설정 -// com.google.api.services.drive.model.File fileMetadata = new com.google.api.services.drive.model.File(); -// fileMetadata.setName(file.getOriginalFilename()); -// -// // 업로드할 파일 내용 설정 -// InputStreamContent mediaContent = new InputStreamContent( -// "application/octet-stream", file.getInputStream() -// ); -// -// // 파일을 구글 드라이브에 업로드 -// Drive.Files.Create createRequest = driveService.files().create(fileMetadata, mediaContent); -// -// // 업로드 진행 상태를 모니터링 -// createRequest.getMediaHttpUploader().setProgressListener( -// new MediaHttpUploaderProgressListener() { -// public void progressChanged(MediaHttpUploader uploader) throws IOException { -// switch (uploader.getUploadState()) { -// case INITIATION_STARTED: -// System.out.println("Initiation Started"); -// break; -// case INITIATION_COMPLETE: -// System.out.println("Initiation Complete"); -// break; -// case MEDIA_IN_PROGRESS: -// System.out.println("Upload In Progress"); -// break; -// case MEDIA_COMPLETE: -// System.out.println("Upload Complete"); -// break; -// } -// } -// } -// ); -// -// // 파일 업로드 실행 -// com.google.api.services.drive.model.File uploadedFile = createRequest.execute(); -// String fileId = uploadedFile.getId(); -// -// // 파일 공개 권한 설정 (필수) -// setFilePublic(fileId); -// -// // 이미지 원본 URL 반환 -// return driveService.files().get(fileId).execute().getWebViewLink(); -// -// // 업로드된 파일의 ID를 반환 (WebViewLink로 파일을 찾을 수 있음) -// //return "https://drive.google.com/file/d/" + uploadedFile.getId() + "/view"; -// } -// -// // 파일을 "공개"로 설정하는 메서드 수정 -// private void setFilePublic(String fileId) throws IOException { -// Permission permission = new Permission() -// .setType("anyone") // 모든 사용자 접근 허용 -// .setRole("reader") // 읽기 권한 부여 -// .setAllowFileDiscovery(false); // 검색 엔진에서 노출되지 않도록 설정 -// -// driveService.permissions().create(fileId, permission) -// .setFields("id") -// .execute(); -// -// System.out.println("File is now public: " + fileId); -// } - - }