Skip to content

Commit

Permalink
Merge pull request #216 from ITA-OneByte/dev
Browse files Browse the repository at this point in the history
[Release] 드라이브 휴지통 API 로직 변경
  • Loading branch information
baekjaehyuk authored Jan 16, 2025
2 parents 537587a + c6140e0 commit 9cfc8c0
Show file tree
Hide file tree
Showing 10 changed files with 245 additions and 170 deletions.
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,16 @@ dependencies {
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.5.0' // swagger
implementation 'net.nurigo:sdk:4.3.0'


//jwt
implementation 'io.jsonwebtoken:jjwt-api:0.12.3'
implementation 'io.jsonwebtoken:jjwt-impl:0.12.3'
implementation 'io.jsonwebtoken:jjwt-jackson:0.12.3'

//s3
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'
implementation 'software.amazon.awssdk:s3:2.20.56'

}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,42 @@
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client;

@Configuration
public class S3Config {

@Value("${cloud.aws.credentials.access-key}")
private String accessKey;

@Value("${cloud.aws.credentials.secret-key}")
private String secretKey;

@Value("${cloud.aws.region.static}")
private String region;

@Bean
public AmazonS3Client amazonS3Client(){
public AmazonS3Client amazonS3Client() {
return (AmazonS3Client) AmazonS3ClientBuilder.standard()
.withCredentials(
new AWSStaticCredentialsProvider(new BasicAWSCredentials(accessKey,secretKey))
new AWSStaticCredentialsProvider(new BasicAWSCredentials(accessKey, secretKey))
)
.withRegion(region)
.build();
}

@Bean
public S3Client s3Client() {
return S3Client.builder()
.credentialsProvider(
StaticCredentialsProvider.create(
AwsBasicCredentials.create(accessKey, secretKey)
)
)
.region(Region.of(region))
.build();
}
}
34 changes: 2 additions & 32 deletions src/main/java/classfit/example/classfit/common/util/DriveUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,10 @@
import classfit.example.classfit.drive.dto.response.FileResponse;
import classfit.example.classfit.member.domain.Member;
import com.amazonaws.services.s3.model.S3ObjectSummary;
import com.amazonaws.services.s3.model.Tag;
import org.jetbrains.annotations.NotNull;
import org.springframework.http.HttpStatus;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;

import static classfit.example.classfit.drive.domain.DriveType.PERSONAL;
Expand All @@ -32,22 +29,6 @@ public static String generatedOriginPath(Member member, DriveType driveType, Str
throw new ClassfitException("지원하지 않는 드라이브 타입입니다.", HttpStatus.NO_CONTENT);
}

public static String generateTrashPath(Member member, DriveType driveType, String fileName) {

String basePath;

if (driveType == DriveType.PERSONAL) {
basePath = String.format("trash/personal/%d/", member.getId());
} else if (driveType == DriveType.SHARED) {
Long academyId = member.getAcademy().getId();
basePath = String.format("trash/shared/%d/", academyId);
} else {
throw new ClassfitException("지원하지 않는 드라이브 타입입니다.", HttpStatus.NO_CONTENT);
}

return (fileName != null && !fileName.trim().isEmpty()) ? basePath + fileName : basePath;
}

public static String buildPrefix(DriveType driveType, Member member, String folderPath) {
String basePrefix;

Expand All @@ -65,15 +46,6 @@ public static String buildPrefix(DriveType driveType, Member member, String fold
return basePrefix + folderPath + "/";
}

public static String extractTags(List<Tag> tags, String tagKey) {
return tags.stream()
.filter(tag -> tag.getKey().equals(tagKey))
.map(Tag::getValue)
.findFirst()
.orElse("");
}

@NotNull
public static FileResponse getFileResponse(S3ObjectSummary summary, String fileName,
String fileUrl,
Map<String, String> tagMap) {
Expand Down Expand Up @@ -120,12 +92,10 @@ private static LocalDateTime parseUploadedAt(String uploadedAtStr) {
private static String formatFileSize(long sizeInBytes) {
double sizeInKB = sizeInBytes / 1024.0;
if (sizeInKB >= 1024) {
// 1MB 이상이면 MB 단위로 표시
double sizeInMB = sizeInKB / 1024.0;
return String.format("%.1f MB", sizeInMB); // 소수점 한 자리까지 MB로 표시
return String.format("%.1f MB", sizeInMB);
}
// 1MB 미만이면 KB 단위로 표시
return String.format("%.1f KB", sizeInKB); // 소수점 한 자리까지 KB로 표시
return String.format("%.1f KB", sizeInKB);
}

private static String getFileNameWithoutPrefix(String objectKey) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Nullable;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

Expand Down Expand Up @@ -49,17 +48,4 @@ public ApiResponse<List<String>> getFolders(
List<String> folders = driveFolderService.getFolders(member, driveType, folderPath);
return ApiResponse.success(folders, 200, "폴더 목록 조회 성공");
}

@DeleteMapping("/folder")
@Operation(summary = "폴더 삭제", description = "폴더를 삭제하는 API입니다.")
public ApiResponse<Nullable> deleteFolder(
@AuthMember Member member,
@Parameter(description = "내 드라이브는 PERSONAL, 공용 드라이브는 SHARED 입니다.")
@RequestParam DriveType driveType,
@Parameter(description = "삭제할 폴더 이름입니다.")
@RequestParam String folderName
) {
driveFolderService.deleteFolder(member, driveType, folderName);
return ApiResponse.success(null, 200, "SUCCESS");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,17 @@ public class DriveTrashController {
public ApiResponse<List<FileResponse>> trashList(
@AuthMember Member member,
@Parameter(description = "내 드라이브는 PERSONAL, 공용 드라이브는 SHARED 입니다.")
@RequestParam DriveType driveType
@RequestParam DriveType driveType,
@Parameter(description = "폴더 경로입니다. 비어 있으면 루트 폴더로 지정됩니다.")
@RequestParam(required = false, defaultValue = "") String folderPath
) {
List<FileResponse> filesFromTrash = driveTrashService.getFilesFromTrash(member, driveType);
List<FileResponse> filesFromTrash = driveTrashService.getFilesFromTrash(member, driveType, folderPath);
return ApiResponse.success(filesFromTrash, 200, "조회 성공");
}

@PostMapping("/trash")
@Operation(summary = "휴지통 이동", description = "휴지통 이동 API 입니다.")
public ApiResponse<List<String>> moveToTrash(
public ApiResponse<List<String>> storeTrash(
@AuthMember Member member,
@Parameter(description = "내 드라이브는 PERSONAL, 공용 드라이브는 SHARED 입니다.")
@RequestParam DriveType driveType,
Expand All @@ -49,20 +51,20 @@ public ApiResponse<List<String>> moveToTrash(
@Parameter(description = "파일 이름")
@RequestParam List<String> fileNames
) {
List<String> trashPathList = driveTrashService.moveToTrash(member, driveType, folderPath, fileNames);
List<String> trashPathList = driveTrashService.storeTrash(member, driveType, folderPath, fileNames);
return ApiResponse.success(trashPathList, 200, "휴지통 이동 완료");
}

@PostMapping("/trash/restore")
@Operation(summary = "휴지통 복원", description = "휴지통 복원 API 입니다.")
public ApiResponse<List<String>> restoreFromTrash(
public ApiResponse<List<String>> restoreTrash(
@AuthMember Member member,
@Parameter(description = "내 드라이브는 PERSONAL, 공용 드라이브는 SHARED 입니다.")
@RequestParam DriveType driveType,
@Parameter(description = "파일 이름")
@RequestParam List<String> fileNames
) {
List<String> restorePathList = driveRestoreService.restoreFromTrash(member, driveType, fileNames);
List<String> restorePathList = driveRestoreService.restoreTrash(member, driveType, fileNames);
return ApiResponse.success(restorePathList, 200, "복원 성공");
}

Expand All @@ -72,10 +74,13 @@ public ApiResponse<Nullable> deleteFromTrash(
@AuthMember Member member,
@Parameter(description = "내 드라이브는 PERSONAL, 공용 드라이브는 SHARED 입니다.")
@RequestParam DriveType driveType,
@Parameter(description = "폴더 경로입니다. 비어 있으면 루트 폴더로 지정됩니다.")
@RequestParam(required = false, defaultValue = "") String folderPath,
@Parameter(description = "파일 이름")
@RequestParam List<String> fileName
@RequestParam List<String> fileNames

) {
driveDeleteService.deleteFromTrash(member, driveType, fileName);
driveDeleteService.deleteFromTrash(member, driveType, folderPath, fileNames);
return ApiResponse.success(null, 200, "삭제 성공");
}
}
Original file line number Diff line number Diff line change
@@ -1,43 +1,66 @@
package classfit.example.classfit.drive.service;

import classfit.example.classfit.common.exception.ClassfitException;
import classfit.example.classfit.common.util.DriveUtil;
import classfit.example.classfit.drive.domain.DriveType;
import classfit.example.classfit.member.domain.Member;
import com.amazonaws.services.s3.AmazonS3;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.DeleteObjectRequest;
import software.amazon.awssdk.services.s3.model.ListObjectVersionsRequest;
import software.amazon.awssdk.services.s3.model.ListObjectVersionsResponse;
import software.amazon.awssdk.services.s3.model.S3Exception;

import java.util.List;

@Service
@RequiredArgsConstructor
public class DriveDeleteService {
private final AmazonS3 amazonS3;
private final S3Client s3Client;

@Value("${cloud.aws.s3.bucket}")
private String bucketName;

public void deleteFromTrash(Member member, DriveType driveType, List<String> fileNames) {
for (String fileName : fileNames) {
public void deleteFromTrash(Member member, DriveType driveType, String folderPath, List<String> fileNames) {

fileNames.forEach(fileName -> {
String key = DriveUtil.generatedOriginPath(member, driveType, folderPath, fileName);

try {
String trashPath = generateTrashPath(member, driveType, fileName);
amazonS3.deleteObject(bucketName, trashPath);
} catch (Exception e) {
System.err.println("파일 삭제 중 오류 발생: " + fileName);
ListObjectVersionsRequest listVersionsRequest = ListObjectVersionsRequest.builder()
.bucket(bucketName)
.prefix(key)
.build();

ListObjectVersionsResponse response = s3Client.listObjectVersions(listVersionsRequest);

response.versions().forEach(version -> {
DeleteObjectRequest deleteRequest = DeleteObjectRequest.builder()
.bucket(bucketName)
.key(version.key())
.versionId(version.versionId())
.build();

s3Client.deleteObject(deleteRequest);
});

response.deleteMarkers().forEach(deleteMarker -> {
DeleteObjectRequest deleteMarkerRequest = DeleteObjectRequest.builder()
.bucket(bucketName)
.key(deleteMarker.key())
.versionId(deleteMarker.versionId())
.build();

s3Client.deleteObject(deleteMarkerRequest);
});

} catch (S3Exception e) {
System.err.println("Error deleting file: " + key + ", Error: " + e.awsErrorDetails().errorMessage());
}
}
});
}

private String generateTrashPath(Member member, DriveType driveType, String fileName) {
if (driveType == DriveType.PERSONAL) {
return String.format("trash/personal/%d/%s", member.getId(), fileName);
} else if (driveType == DriveType.SHARED) {
Long academyId = member.getAcademy().getId();
return String.format("trash/shared/%d/%s", academyId, fileName);
}
throw new ClassfitException("지원하지 않는 드라이브 타입입니다.", HttpStatus.NO_CONTENT);
}

}

Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import classfit.example.classfit.common.util.DriveUtil;
import classfit.example.classfit.drive.domain.DriveType;
import classfit.example.classfit.drive.dto.response.FileResponse;
import classfit.example.classfit.member.domain.Member;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.*;
Expand Down Expand Up @@ -101,16 +100,4 @@ public List<String> getFolders(Member member, DriveType driveType, String folder
.map(folder -> folder.substring(prefix.length()))
.collect(Collectors.toList());
}

public void deleteFolder(Member member, DriveType driveType, String folderName) {
List<FileResponse> filesFromS3 = driveGetService.getFilesFromS3(member, driveType, folderName);
List<String> strings = filesFromS3.stream()
.map(FileResponse::fileName)
.collect(Collectors.toList());

driveTrashService.moveToTrash(member, driveType, folderName, strings);

String prefix = DriveUtil.buildPrefix(driveType, member, folderName);
amazonS3.deleteObject(new DeleteObjectRequest(bucketName, prefix));
}
}
Loading

0 comments on commit 9cfc8c0

Please sign in to comment.