@@ -10,4 +10,6 @@ import java.util.List;
|
|||||||
public interface ModerationFileRepository extends CrudRepository<ModerationPassportFile, Long> {
|
public interface ModerationFileRepository extends CrudRepository<ModerationPassportFile, Long> {
|
||||||
|
|
||||||
public List<ModerationPassportFile> findByStatus(String status);
|
public List<ModerationPassportFile> findByStatus(String status);
|
||||||
|
|
||||||
|
public List<ModerationPassportFile> findByUserIdAndStatus(Long userId, String status);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,11 @@ package ru.soune.nocopy.service.file;
|
|||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import ru.soune.nocopy.entity.file.FileEntity;
|
import ru.soune.nocopy.entity.file.FileEntity;
|
||||||
import ru.soune.nocopy.entity.file.moderation.ModerationPassportFile;
|
import ru.soune.nocopy.entity.file.moderation.ModerationPassportFile;
|
||||||
|
import ru.soune.nocopy.service.file.cloud.CloudStorageService;
|
||||||
import ru.soune.nocopy.service.file.moderation.ModerationFileService;
|
import ru.soune.nocopy.service.file.moderation.ModerationFileService;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
@@ -25,12 +27,17 @@ public class ZipService {
|
|||||||
|
|
||||||
private final ModerationFileService moderationFileService;
|
private final ModerationFileService moderationFileService;
|
||||||
|
|
||||||
|
private final CloudStorageService cloudStorageService;
|
||||||
|
|
||||||
private static final int MAX_SIZE_MB = 20;
|
private static final int MAX_SIZE_MB = 20;
|
||||||
|
|
||||||
private static final int FIRST_PART = 1;
|
private static final int FIRST_PART = 1;
|
||||||
|
|
||||||
private static final int SECOND_PART = 2;
|
private static final int SECOND_PART = 2;
|
||||||
|
|
||||||
|
@Value("${yandex.cloud.bucket-cold}")
|
||||||
|
private String bucketCold;
|
||||||
|
|
||||||
public void createAndSplitZip(Long userId, List<FileEntity> files) throws IOException {
|
public void createAndSplitZip(Long userId, List<FileEntity> files) throws IOException {
|
||||||
byte[] zipBytes = createZip(files);
|
byte[] zipBytes = createZip(files);
|
||||||
String base64String = Base64.getEncoder().encodeToString(zipBytes);
|
String base64String = Base64.getEncoder().encodeToString(zipBytes);
|
||||||
@@ -39,29 +46,29 @@ public class ZipService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public byte[] assembleZipFromSplitFiles(Long userId) throws IOException {
|
public byte[] assembleZipFromSplitFiles(Long userId) throws IOException {
|
||||||
List<ModerationPassportFile> activeFiles = moderationFileService.getActiveFiles();
|
List<ModerationPassportFile> activeFiles = moderationFileService.getActiveFilesByUserId(userId);
|
||||||
|
|
||||||
String part1Content = null;
|
String part1Key = null;
|
||||||
String part2Content = null;
|
String part2Key = null;
|
||||||
|
|
||||||
for (ModerationPassportFile file : activeFiles) {
|
for (ModerationPassportFile file : activeFiles) {
|
||||||
Path filePath = Paths.get(file.getPath());
|
|
||||||
String content = Files.readString(filePath);
|
|
||||||
|
|
||||||
if (file.getPart() == FIRST_PART) {
|
if (file.getPart() == FIRST_PART) {
|
||||||
part1Content = content;
|
part1Key = file.getPath();
|
||||||
} else if (file.getPart() == SECOND_PART) {
|
} else if (file.getPart() == SECOND_PART) {
|
||||||
part2Content = content;
|
part2Key = file.getPath();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (part1Content == null || part2Content == null) {
|
if (part1Key == null || part2Key == null) {
|
||||||
throw new IOException("Missing part 1 or part 2 for user: " + userId);
|
throw new IOException("Missing part 1 or part 2 for user: " + userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
String base64String = mergeBase64(part1Content, part2Content);
|
String part1Content = cloudStorageService.downloadPartFromBucket(bucketCold, part1Key);
|
||||||
|
String part2Content = cloudStorageService.downloadPartFromBucket(bucketCold, part2Key);
|
||||||
|
|
||||||
return Base64.getDecoder().decode(base64String);
|
String fullBase64 = part1Content + part2Content;
|
||||||
|
|
||||||
|
return Base64.getDecoder().decode(fullBase64);
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] createZip(List<FileEntity> files) throws IOException {
|
private byte[] createZip(List<FileEntity> files) throws IOException {
|
||||||
@@ -120,11 +127,16 @@ public class ZipService {
|
|||||||
Files.writeString(file1, part1);
|
Files.writeString(file1, part1);
|
||||||
Path file2 = userDir.resolve(userId + "___2.txt");
|
Path file2 = userDir.resolve(userId + "___2.txt");
|
||||||
Files.writeString(file2, part2);
|
Files.writeString(file2, part2);
|
||||||
Map<Integer, String> files = Map.of(FIRST_PART, file1.toString(), SECOND_PART, file2.toString());
|
|
||||||
|
Map<String, String> cloudKeys = cloudStorageService.saveSplitPartsToColdStorage(userId, file1, file2);
|
||||||
|
|
||||||
|
Map<Integer, String> files = Map.of(
|
||||||
|
FIRST_PART, cloudKeys.get("part1"),
|
||||||
|
SECOND_PART, cloudKeys.get("part2"));
|
||||||
|
|
||||||
moderationFileService.addFiles(userId, files);
|
moderationFileService.addFiles(userId, files);
|
||||||
|
|
||||||
log.info("Saved split files: {} and {}", file1, file2);
|
log.info("Saved split files to cold storage for userId: {}", userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String mergeBase64(String part1, String part2) {
|
private String mergeBase64(String part1, String part2) {
|
||||||
|
|||||||
@@ -23,8 +23,11 @@ import java.io.File;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@@ -49,6 +52,9 @@ public class CloudStorageService {
|
|||||||
@Value("${yandex.cloud.bucket}")
|
@Value("${yandex.cloud.bucket}")
|
||||||
private String bucket;
|
private String bucket;
|
||||||
|
|
||||||
|
@Value("${yandex.cloud.bucket-cold}")
|
||||||
|
private String bucketCold;
|
||||||
|
|
||||||
public void saveFilesToStorage(String mimeType, String extension, String sendToCloudFilePath) {
|
public void saveFilesToStorage(String mimeType, String extension, String sendToCloudFilePath) {
|
||||||
AwsCredentials credentials = AwsBasicCredentials.create(key, secretKey);
|
AwsCredentials credentials = AwsBasicCredentials.create(key, secretKey);
|
||||||
S3Client s3Client = S3Client.builder()
|
S3Client s3Client = S3Client.builder()
|
||||||
@@ -77,6 +83,77 @@ public class CloudStorageService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Map<String, String> saveSplitPartsToColdStorage(Long userId, Path file1Path, Path file2Path) {
|
||||||
|
AwsCredentials credentials = AwsBasicCredentials.create(key, secretKey);
|
||||||
|
S3Client s3Client = S3Client.builder()
|
||||||
|
.httpClient(ApacheHttpClient.create())
|
||||||
|
.region(Region.of(region))
|
||||||
|
.endpointOverride(URI.create(s3endpoint))
|
||||||
|
.credentialsProvider(StaticCredentialsProvider.create(credentials))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
String part1Key = uploadFileToBucket(s3Client, bucketCold, file1Path, userId, "1");
|
||||||
|
String part2Key = uploadFileToBucket(s3Client, bucketCold, file2Path, userId, "2");
|
||||||
|
|
||||||
|
try {
|
||||||
|
Files.deleteIfExists(file1Path);
|
||||||
|
Files.deleteIfExists(file2Path);
|
||||||
|
log.info("Deleted local split files for userId: {}", userId);
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("Failed to delete local split files for userId: {}", userId, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Map.of("part1", part1Key, "part2", part2Key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String downloadPartFromBucket(String bucket, String cloudKey) {
|
||||||
|
AwsCredentials credentials = AwsBasicCredentials.create(key, secretKey);
|
||||||
|
S3Client s3Client = S3Client.builder()
|
||||||
|
.httpClient(ApacheHttpClient.create())
|
||||||
|
.region(Region.of(region))
|
||||||
|
.endpointOverride(URI.create(s3endpoint))
|
||||||
|
.credentialsProvider(StaticCredentialsProvider.create(credentials))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
try {
|
||||||
|
GetObjectRequest getObjectRequest = GetObjectRequest.builder()
|
||||||
|
.bucket(bucket)
|
||||||
|
.key(cloudKey)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
ResponseBytes<GetObjectResponse> objectBytes = s3Client.getObjectAsBytes(getObjectRequest);
|
||||||
|
String content = new String(objectBytes.asByteArray(), StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
log.info("Downloaded from cold bucket, key: {}, size: {} chars", cloudKey, content.length());
|
||||||
|
return content;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Failed to download from cold bucket, key: {}", cloudKey, e);
|
||||||
|
throw new RuntimeException("Failed to download file from cold storage", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String uploadFileToBucket(S3Client s3Client, String bucket, Path filePath, Long userId, String partSuffix) {
|
||||||
|
String cloudKey = String.format("passport/%d/part_%s.txt", userId, partSuffix);
|
||||||
|
|
||||||
|
try {
|
||||||
|
byte[] bytes = Files.readAllBytes(filePath);
|
||||||
|
|
||||||
|
PutObjectRequest putObjectRequest = PutObjectRequest.builder()
|
||||||
|
.bucket(bucket)
|
||||||
|
.key(cloudKey)
|
||||||
|
.contentType("text/plain")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
s3Client.putObject(putObjectRequest, RequestBody.fromBytes(bytes));
|
||||||
|
log.info("Uploaded part {} to cold bucket, key: {}", partSuffix, cloudKey);
|
||||||
|
return cloudKey;
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("Failed to read file for upload: {}", filePath, e);
|
||||||
|
throw new RuntimeException("Failed to upload split part to cold storage", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public File readFileFromStorageByPath(String filePath) throws IOException {
|
public File readFileFromStorageByPath(String filePath) throws IOException {
|
||||||
GetObjectRequest objectRequest = GetObjectRequest.builder()
|
GetObjectRequest objectRequest = GetObjectRequest.builder()
|
||||||
.bucket(bucket)
|
.bucket(bucket)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import lombok.RequiredArgsConstructor;
|
|||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import ru.soune.nocopy.entity.file.moderation.ModerationPassportFile;
|
import ru.soune.nocopy.entity.file.moderation.ModerationPassportFile;
|
||||||
import ru.soune.nocopy.repository.ModerationFileRepository;
|
import ru.soune.nocopy.repository.ModerationFileRepository;
|
||||||
|
import ru.soune.nocopy.service.file.cloud.CloudStorageService;
|
||||||
import ru.soune.nocopy.service.user.moderation.UserVerificationService;
|
import ru.soune.nocopy.service.user.moderation.UserVerificationService;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -17,6 +18,8 @@ public class ModerationFileService {
|
|||||||
|
|
||||||
private final UserVerificationService userVerificationService;
|
private final UserVerificationService userVerificationService;
|
||||||
|
|
||||||
|
private final CloudStorageService cloudStorageService;
|
||||||
|
|
||||||
public void addFiles(Long userId, Map<Integer, String> files) {
|
public void addFiles(Long userId, Map<Integer, String> files) {
|
||||||
List<ModerationPassportFile> moderationFileRepositoryByStatus =
|
List<ModerationPassportFile> moderationFileRepositoryByStatus =
|
||||||
moderationFileRepository.findByStatus(ModerationFileStatus.ACTIVE.getName());
|
moderationFileRepository.findByStatus(ModerationFileStatus.ACTIVE.getName());
|
||||||
@@ -44,6 +47,10 @@ public class ModerationFileService {
|
|||||||
return moderationFileRepository.findByStatus(ModerationFileStatus.ACTIVE.getName());
|
return moderationFileRepository.findByStatus(ModerationFileStatus.ACTIVE.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<ModerationPassportFile> getActiveFilesByUserId(Long userId) {
|
||||||
|
return moderationFileRepository.findByUserIdAndStatus(userId, ModerationFileStatus.ACTIVE.getName());
|
||||||
|
}
|
||||||
|
|
||||||
private void disActiveDocuments(List<ModerationPassportFile> moderationFileRepositoryByStatus) {
|
private void disActiveDocuments(List<ModerationPassportFile> moderationFileRepositoryByStatus) {
|
||||||
for (ModerationPassportFile moderationFile : moderationFileRepositoryByStatus) {
|
for (ModerationPassportFile moderationFile : moderationFileRepositoryByStatus) {
|
||||||
moderationFile.setStatus(ModerationFileStatus.NOT_ACTIVE.getName());
|
moderationFile.setStatus(ModerationFileStatus.NOT_ACTIVE.getName());
|
||||||
|
|||||||
@@ -108,6 +108,7 @@ yandex:
|
|||||||
region: "ru-central1"
|
region: "ru-central1"
|
||||||
s3-endpoint: "https://storage.yandexcloud.net"
|
s3-endpoint: "https://storage.yandexcloud.net"
|
||||||
bucket: "no-copy-storage"
|
bucket: "no-copy-storage"
|
||||||
|
bucket-cold: "no-copy-storage-cold"
|
||||||
|
|
||||||
|
|
||||||
searchapi:
|
searchapi:
|
||||||
|
|||||||
Reference in New Issue
Block a user