Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ desktop.ini
# Environment files
application.properties
credentials.json
service_account_key.json

#프론트
templeates/
Expand Down
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
121 changes: 121 additions & 0 deletions src/main/java/com/boot/swlugweb/v1/DriveQuickstart.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
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 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;
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<String> SCOPES =
Collections.singletonList(DriveScopes.DRIVE);
// 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.
* @throws IOException If the credentials.json file cannot be found.
*/
// 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 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();
}
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, (com.google.api.client.http.HttpRequestInitializer) getDriveService())
.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<File> 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());
}
}
}


}
152 changes: 86 additions & 66 deletions src/main/java/com/boot/swlugweb/v1/blog/BlogController.java
Original file line number Diff line number Diff line change
@@ -1,33 +1,30 @@
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.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;

public BlogController(BlogService blogService) {
public BlogController(BlogService blogService,GoogleDriveService googleDriveService)
{
this.blogService = blogService;
this.googleDriveService = googleDriveService;
}

@GetMapping
Expand All @@ -49,78 +46,130 @@ public ResponseEntity<BlogDetailResponseDto> getBlogDetail(@RequestBody Map<Stri
return ResponseEntity.ok(blog);
}

// BlogController.java
//구글 버전
@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<MultipartFile> 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<String> updateBlogPost(
@RequestPart(name = "blogUpdateRequestDto") BlogUpdateRequestDto blogUpdateRequestDto,
public ResponseEntity<?> updateBlogPost(
@RequestPart("blogUpdateRequestDto") BlogUpdateRequestDto blogUpdateRequestDto,
@RequestPart(name = "imageFiles", required = false) List<MultipartFile> imageFiles,
HttpSession session
) {
HttpSession session) {

String userId = (String) session.getAttribute("USER");
if (userId == null) {
return ResponseEntity.status(401).build();
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
}

try {
if (imageFiles != null && !imageFiles.isEmpty()) {
blogUpdateRequestDto.setImageFiles(imageFiles);
}
// 새 이미지 파일들을 DTO에 주입
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) {
e.printStackTrace();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}




@PostMapping("/delete")
public ResponseEntity<String> 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<String> imageUrls = blogService.getImageUrlsByBlogId(blogDeleteRequestDto.getId());


for (String url : imageUrls) {
String fileId = extractFileIdFromUrl(url);
if (fileId == null) {
log.warn("유효하지 않은 Google Drive 이미지 URL입니다: {}", url);
continue;
}

googleDriveService.deleteFile(fileId);
}


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();
}
}

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;
}



@GetMapping("/tags")
public ResponseEntity<List<String>> getTags() {
List<String> tags = blogService.getAllTags();
Expand Down Expand Up @@ -152,33 +201,4 @@ public ResponseEntity<Map<String, Object>> uploadImage(@RequestParam("upload") M
}
}

// 이미지 조회 엔드포인트
@GetMapping("/images/{filename:.+}")
@ResponseBody
public ResponseEntity<Resource> 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();
}
}
}
4 changes: 3 additions & 1 deletion src/main/java/com/boot/swlugweb/v1/blog/BlogCreateDto.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<String> imageUrl;
private List<MultipartFile> imageFiles;
}
Loading