From 4a185e6f9af6c695fec9869e159e3e88f0004048 Mon Sep 17 00:00:00 2001 From: vladp Date: Tue, 27 Jan 2026 20:27:39 +0700 Subject: [PATCH] dev --- docker-compose.yaml | 7 +- .../configuration/file/FileStorageConfig.java | 25 ++++++++ .../nocopy/controller/FileController.java | 64 +++++++++++++++++++ .../soune/nocopy/dto/file/SimilarFileDTO.java | 7 ++ .../soune/nocopy/entity/file/FileEntity.java | 3 - .../repository/ImageSimilarityRepository.java | 13 +++- .../repository/SimilarImageProjection.java | 7 ++ .../nocopy/service/FileSimilarityService.java | 16 ++++- .../service/file/FileStorageService.java | 36 +++++++++++ src/main/resources/application-dev.yaml | 2 + src/main/resources/application-prod.yaml | 2 + src/main/resources/application.yaml | 24 ++++--- 12 files changed, 185 insertions(+), 21 deletions(-) create mode 100644 src/main/java/ru/soune/nocopy/configuration/file/FileStorageConfig.java create mode 100644 src/main/java/ru/soune/nocopy/controller/FileController.java create mode 100644 src/main/java/ru/soune/nocopy/service/file/FileStorageService.java create mode 100644 src/main/resources/application-dev.yaml create mode 100644 src/main/resources/application-prod.yaml diff --git a/docker-compose.yaml b/docker-compose.yaml index b16a68a..9fb10f7 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -88,17 +88,18 @@ services: # FILE_CHUNK_SIZE: 1048576 FILE_CHUNK_SIZE: 1000000 POSTGRES_DB: no_copy_ - # POSTGRES_USER: postgres - # POSTGRES_PASSWORD: postgres + POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} POSTGRES_PORT: 5432 POSTGRES_HOST: db STORAGE_SERVICE_URL: http://storage:8081 - SPRING_PROFILES_ACTIVE: docker + SPRING_PROFILES_ACTIVE: prod YANDEX_API_KEY: AQVNyaVaUmgUb1GMCtf5zSEqFxy0woXrcMOOB43q YANDEX_FOLDER_ID: b1gokpdbm6qfpsou8pcd YANDEX_SEARCH_URL: "https://searchapi.api.cloud.yandex.net/v2/image/search_by_image" +# POSTGRES_USER: postgres +# POSTGRES_PASSWORD: postgres # MAIL_HOST: postfix # MAIL_PORT: 25 # MAIL_USERNAME: noreply@no-copy.ru diff --git a/src/main/java/ru/soune/nocopy/configuration/file/FileStorageConfig.java b/src/main/java/ru/soune/nocopy/configuration/file/FileStorageConfig.java new file mode 100644 index 0000000..c64ba5c --- /dev/null +++ b/src/main/java/ru/soune/nocopy/configuration/file/FileStorageConfig.java @@ -0,0 +1,25 @@ +package ru.soune.nocopy.configuration.file; + +import jakarta.annotation.PostConstruct; +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; + +@Configuration +@ConfigurationProperties(prefix = "file.storage") +@Data +public class FileStorageConfig { + private String basePath; + private long maxFileSize; + private int chunkSize; + private int authTokenLifeHours; + + @PostConstruct + public void init() throws IOException { + Files.createDirectories(Paths.get(basePath)); + } +} diff --git a/src/main/java/ru/soune/nocopy/controller/FileController.java b/src/main/java/ru/soune/nocopy/controller/FileController.java new file mode 100644 index 0000000..51d687a --- /dev/null +++ b/src/main/java/ru/soune/nocopy/controller/FileController.java @@ -0,0 +1,64 @@ +package ru.soune.nocopy.controller; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.Resource; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import ru.soune.nocopy.entity.file.FileEntity; +import ru.soune.nocopy.repository.FileEntityRepository; +import ru.soune.nocopy.service.file.FileStorageService; + +import java.io.IOException; + +@RestController +@RequestMapping("/api/files") +@Slf4j +public class FileController { + + @Autowired + private FileStorageService fileStorageService; + + @Autowired + private FileEntityRepository fileRepository; + + @GetMapping("/public/{fileId}") + public ResponseEntity getPublicFile( + @PathVariable String fileId) throws IOException { + + FileEntity fileEntity = fileRepository.findById(fileId) + .orElseThrow(() -> new RuntimeException("File not found")); + +// if (fileEntity.getProtectionStatus() != ProtectionStatus.NOT_PROTECTED) { +// throw new RuntimeException("File is protected"); +// } + + Resource resource = fileStorageService.loadFileAsResource(fileEntity.getFilePath()); + + return ResponseEntity.ok() + .contentType(new MediaType(fileEntity.getMimeType(), fileEntity.getFileExtension())) + .header(HttpHeaders.CONTENT_DISPOSITION, + "inline; filename=\"" + fileEntity.getOriginalFileName() + "\"") + .body(resource); + } + + @GetMapping("/download/{fileId}") + public ResponseEntity downloadFile(@PathVariable String fileId) throws IOException { + + FileEntity fileEntity = fileRepository.findById(fileId) + .orElseThrow(() -> new RuntimeException("File not found")); + + Resource resource = fileStorageService.loadFileAsResource(fileEntity.getFilePath()); + + return ResponseEntity.ok() + .contentType(MediaType.APPLICATION_OCTET_STREAM) + .header(HttpHeaders.CONTENT_DISPOSITION, + "attachment; filename=\"" + fileEntity.getOriginalFileName() + "\"") + .body(resource); + } +} diff --git a/src/main/java/ru/soune/nocopy/dto/file/SimilarFileDTO.java b/src/main/java/ru/soune/nocopy/dto/file/SimilarFileDTO.java index 45fb75c..44d8495 100644 --- a/src/main/java/ru/soune/nocopy/dto/file/SimilarFileDTO.java +++ b/src/main/java/ru/soune/nocopy/dto/file/SimilarFileDTO.java @@ -2,6 +2,9 @@ package ru.soune.nocopy.dto.file; import lombok.Builder; import lombok.Value; +import ru.soune.nocopy.entity.file.ProtectionStatus; + +import java.time.LocalDateTime; @Value @Builder @@ -12,4 +15,8 @@ public class SimilarFileDTO { Integer hammingDistance; String similarityLevel; Long ownerId; + Long supportId; + ProtectionStatus status; + LocalDateTime uploadDate; + String url; } diff --git a/src/main/java/ru/soune/nocopy/entity/file/FileEntity.java b/src/main/java/ru/soune/nocopy/entity/file/FileEntity.java index dd193af..2d8d781 100644 --- a/src/main/java/ru/soune/nocopy/entity/file/FileEntity.java +++ b/src/main/java/ru/soune/nocopy/entity/file/FileEntity.java @@ -3,13 +3,10 @@ package ru.soune.nocopy.entity.file; import com.fasterxml.jackson.annotation.JsonIgnore; import jakarta.persistence.*; import lombok.*; -import org.hibernate.annotations.GenerationTime; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.LastModifiedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; -import java.sql.DriverManager; -import java.sql.SQLException; import java.time.LocalDateTime; @Data diff --git a/src/main/java/ru/soune/nocopy/repository/ImageSimilarityRepository.java b/src/main/java/ru/soune/nocopy/repository/ImageSimilarityRepository.java index 345cd43..578b158 100644 --- a/src/main/java/ru/soune/nocopy/repository/ImageSimilarityRepository.java +++ b/src/main/java/ru/soune/nocopy/repository/ImageSimilarityRepository.java @@ -16,7 +16,10 @@ public interface ImageSimilarityRepository f.id AS id, f.original_file_name AS originalFileName, f.file_size AS fileSize, - f.user_id AS userId, + f.user_id AS userId, + f.support_id AS supportId, + f.created_at AS uploadDate, + f.protection_status AS protectionStatus, h.hash64_hi AS hash64Hi, h.hash64_lo AS hash64Lo FROM image_hashes ref @@ -37,6 +40,9 @@ public interface ImageSimilarityRepository f.original_file_name AS originalFileName, f.file_size AS fileSize, f.user_id AS userId, + f.support_id AS supportId, + f.created_at AS uploadDate, + f.protection_status AS protectionStatus, h.hash64_hi AS hash64Hi, h.hash64_lo AS hash64Lo FROM image_hashes ref @@ -61,7 +67,10 @@ public interface ImageSimilarityRepository f.user_id AS userId, f.original_file_name AS originalFileName, f.file_size AS fileSize, - f.stored_file_name AS similarFileId + f.stored_file_name AS similarFileId, + f.support_id AS supportId, + f.created_at AS uploadDate, + f.protection_status AS protectionStatus, FROM image_hashes h JOIN file_entities f ON f.id = h.file_id WHERE h.hash64_hi = :hash64Hi diff --git a/src/main/java/ru/soune/nocopy/repository/SimilarImageProjection.java b/src/main/java/ru/soune/nocopy/repository/SimilarImageProjection.java index 4bd444e..9b9c8e7 100644 --- a/src/main/java/ru/soune/nocopy/repository/SimilarImageProjection.java +++ b/src/main/java/ru/soune/nocopy/repository/SimilarImageProjection.java @@ -1,5 +1,9 @@ package ru.soune.nocopy.repository; +import ru.soune.nocopy.entity.file.ProtectionStatus; + +import java.time.LocalDateTime; + public interface SimilarImageProjection { Long getHash64Hi(); Long getHash64Lo(); @@ -7,4 +11,7 @@ public interface SimilarImageProjection { Long getUserId(); String getOriginalFileName(); Long getFileSize(); + Long getSupportId(); + LocalDateTime getUploadDate(); + ProtectionStatus getProtectionStatus(); } \ No newline at end of file diff --git a/src/main/java/ru/soune/nocopy/service/FileSimilarityService.java b/src/main/java/ru/soune/nocopy/service/FileSimilarityService.java index adae2bd..f222925 100644 --- a/src/main/java/ru/soune/nocopy/service/FileSimilarityService.java +++ b/src/main/java/ru/soune/nocopy/service/FileSimilarityService.java @@ -1,7 +1,10 @@ package ru.soune.nocopy.service; +import com.vrt.fileprotection.image.phash.PHash; +import com.vrt.fileprotection.image.phash.PerceptualHashHelper; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; @@ -31,6 +34,9 @@ public class FileSimilarityService { private final AuthTokenRepository authTokenRepository; + @Value("${server.baseurl}") + private String baseUrl; + public List findSimilarFiles(String fileId) { var imageHashEntity = hashRepository.findById(fileId) .orElseThrow(() -> new RuntimeException("Hash not found")); @@ -167,11 +173,11 @@ public class FileSimilarityService { Long hash64Hi, Long hash64Lo) { Long imageProjectionHash64Hi = similarImageProjection.getHash64Hi(); Long similarImageProjectionHash64Lo = similarImageProjection.getHash64Lo(); - - int hamming = fileUtil.hamming64(hash64Hi, hash64Lo, imageProjectionHash64Hi, similarImageProjectionHash64Lo); + int hamming = PerceptualHashHelper.INSTANCE.hammingDistance(new PHash(hash64Hi, hash64Lo), + new PHash(imageProjectionHash64Hi, similarImageProjectionHash64Lo)); String level; - if (hamming <= 5) { + if (hamming <= 6) { level = "DUPLICATE"; } else if (hamming <= 12) { level = "SIMILAR"; @@ -186,6 +192,10 @@ public class FileSimilarityService { .fileSize(similarImageProjection.getFileSize()) .hammingDistance(hamming) .similarityLevel(level) + .supportId(similarImageProjection.getSupportId()) + .uploadDate(similarImageProjection.getUploadDate()) + .status(similarImageProjection.getProtectionStatus()) + .url(baseUrl + "/api/files/public/" + similarImageProjection.getId()) .build(); } } diff --git a/src/main/java/ru/soune/nocopy/service/file/FileStorageService.java b/src/main/java/ru/soune/nocopy/service/file/FileStorageService.java new file mode 100644 index 0000000..a7d0603 --- /dev/null +++ b/src/main/java/ru/soune/nocopy/service/file/FileStorageService.java @@ -0,0 +1,36 @@ +package ru.soune.nocopy.service.file; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.io.Resource; +import org.springframework.core.io.UrlResource; +import org.springframework.stereotype.Service; +import ru.soune.nocopy.configuration.file.FileStorageConfig; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; + +@Service +@Slf4j +public class FileStorageService { + + @Autowired + private FileStorageConfig storageConfig; + + public Resource loadFileAsResource(String filePath) throws IOException { + Path path = Paths.get(storageConfig.getBasePath()).resolve(filePath).normalize(); + Resource resource = new UrlResource(path.toUri()); + + if (!resource.exists()) { + throw new FileNotFoundException("File not found: " + filePath); + } + + if (!resource.isReadable()) { + throw new IOException("File is not readable: " + filePath); + } + + return resource; + } +} diff --git a/src/main/resources/application-dev.yaml b/src/main/resources/application-dev.yaml new file mode 100644 index 0000000..bd1102b --- /dev/null +++ b/src/main/resources/application-dev.yaml @@ -0,0 +1,2 @@ +server: + baseurl: "http://localhost" \ No newline at end of file diff --git a/src/main/resources/application-prod.yaml b/src/main/resources/application-prod.yaml new file mode 100644 index 0000000..2a35858 --- /dev/null +++ b/src/main/resources/application-prod.yaml @@ -0,0 +1,2 @@ +server: + baseurl: "http://92.242.61.23" \ No newline at end of file diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 0a3f966..742b7f7 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -1,4 +1,8 @@ spring: + config: + activate: + on-profile: ${SPRING_PROFILES_ACTIVE:dev} + mail: host: smtp.gmail.com port: 587 @@ -11,15 +15,15 @@ spring: starttls: enable: true -# mail: -# host: postfix - # port: 25 - # username: noreply@nocopy.com - # password: nocopy!nocopy! - # properties: - # mail.smtp.auth: false - # mail.smtp.starttls.enable: false - # mail.debug: true + # mail: + # host: postfix + # port: 25 + # username: noreply@nocopy.com + # password: nocopy!nocopy! + # properties: + # mail.smtp.auth: false + # mail.smtp.starttls.enable: false + # mail.debug: true flyway: enabled: true @@ -87,5 +91,5 @@ app: email: verification: standart-subject: NO COPY - Подтверждение email адреса - standart-address-from: NO COPY + standart-address-from: vlad.popovtsev@gmail.com user: Пользователь \ No newline at end of file