+4
-3
@@ -88,17 +88,18 @@ services:
|
|||||||
# FILE_CHUNK_SIZE: 1048576
|
# FILE_CHUNK_SIZE: 1048576
|
||||||
FILE_CHUNK_SIZE: 1000000
|
FILE_CHUNK_SIZE: 1000000
|
||||||
POSTGRES_DB: no_copy_
|
POSTGRES_DB: no_copy_
|
||||||
# POSTGRES_USER: postgres
|
|
||||||
# POSTGRES_PASSWORD: postgres
|
|
||||||
POSTGRES_USER: ${POSTGRES_USER}
|
POSTGRES_USER: ${POSTGRES_USER}
|
||||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||||
POSTGRES_PORT: 5432
|
POSTGRES_PORT: 5432
|
||||||
POSTGRES_HOST: db
|
POSTGRES_HOST: db
|
||||||
STORAGE_SERVICE_URL: http://storage:8081
|
STORAGE_SERVICE_URL: http://storage:8081
|
||||||
SPRING_PROFILES_ACTIVE: docker
|
SPRING_PROFILES_ACTIVE: prod
|
||||||
YANDEX_API_KEY: AQVNyaVaUmgUb1GMCtf5zSEqFxy0woXrcMOOB43q
|
YANDEX_API_KEY: AQVNyaVaUmgUb1GMCtf5zSEqFxy0woXrcMOOB43q
|
||||||
YANDEX_FOLDER_ID: b1gokpdbm6qfpsou8pcd
|
YANDEX_FOLDER_ID: b1gokpdbm6qfpsou8pcd
|
||||||
YANDEX_SEARCH_URL: "https://searchapi.api.cloud.yandex.net/v2/image/search_by_image"
|
YANDEX_SEARCH_URL: "https://searchapi.api.cloud.yandex.net/v2/image/search_by_image"
|
||||||
|
# POSTGRES_USER: postgres
|
||||||
|
# POSTGRES_PASSWORD: postgres
|
||||||
# MAIL_HOST: postfix
|
# MAIL_HOST: postfix
|
||||||
# MAIL_PORT: 25
|
# MAIL_PORT: 25
|
||||||
# MAIL_USERNAME: noreply@no-copy.ru
|
# MAIL_USERNAME: noreply@no-copy.ru
|
||||||
|
|||||||
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<Resource> 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<Resource> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,9 @@ package ru.soune.nocopy.dto.file;
|
|||||||
|
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.Value;
|
import lombok.Value;
|
||||||
|
import ru.soune.nocopy.entity.file.ProtectionStatus;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
@Value
|
@Value
|
||||||
@Builder
|
@Builder
|
||||||
@@ -12,4 +15,8 @@ public class SimilarFileDTO {
|
|||||||
Integer hammingDistance;
|
Integer hammingDistance;
|
||||||
String similarityLevel;
|
String similarityLevel;
|
||||||
Long ownerId;
|
Long ownerId;
|
||||||
|
Long supportId;
|
||||||
|
ProtectionStatus status;
|
||||||
|
LocalDateTime uploadDate;
|
||||||
|
String url;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,13 +3,10 @@ package ru.soune.nocopy.entity.file;
|
|||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
import jakarta.persistence.*;
|
import jakarta.persistence.*;
|
||||||
import lombok.*;
|
import lombok.*;
|
||||||
import org.hibernate.annotations.GenerationTime;
|
|
||||||
import org.springframework.data.annotation.CreatedDate;
|
import org.springframework.data.annotation.CreatedDate;
|
||||||
import org.springframework.data.annotation.LastModifiedDate;
|
import org.springframework.data.annotation.LastModifiedDate;
|
||||||
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||||
|
|
||||||
import java.sql.DriverManager;
|
|
||||||
import java.sql.SQLException;
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
|
|||||||
@@ -16,7 +16,10 @@ public interface ImageSimilarityRepository
|
|||||||
f.id AS id,
|
f.id AS id,
|
||||||
f.original_file_name AS originalFileName,
|
f.original_file_name AS originalFileName,
|
||||||
f.file_size AS fileSize,
|
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_hi AS hash64Hi,
|
||||||
h.hash64_lo AS hash64Lo
|
h.hash64_lo AS hash64Lo
|
||||||
FROM image_hashes ref
|
FROM image_hashes ref
|
||||||
@@ -37,6 +40,9 @@ public interface ImageSimilarityRepository
|
|||||||
f.original_file_name AS originalFileName,
|
f.original_file_name AS originalFileName,
|
||||||
f.file_size AS fileSize,
|
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_hi AS hash64Hi,
|
||||||
h.hash64_lo AS hash64Lo
|
h.hash64_lo AS hash64Lo
|
||||||
FROM image_hashes ref
|
FROM image_hashes ref
|
||||||
@@ -61,7 +67,10 @@ public interface ImageSimilarityRepository
|
|||||||
f.user_id AS userId,
|
f.user_id AS userId,
|
||||||
f.original_file_name AS originalFileName,
|
f.original_file_name AS originalFileName,
|
||||||
f.file_size AS fileSize,
|
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
|
FROM image_hashes h
|
||||||
JOIN file_entities f ON f.id = h.file_id
|
JOIN file_entities f ON f.id = h.file_id
|
||||||
WHERE h.hash64_hi = :hash64Hi
|
WHERE h.hash64_hi = :hash64Hi
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
package ru.soune.nocopy.repository;
|
package ru.soune.nocopy.repository;
|
||||||
|
|
||||||
|
import ru.soune.nocopy.entity.file.ProtectionStatus;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
public interface SimilarImageProjection {
|
public interface SimilarImageProjection {
|
||||||
Long getHash64Hi();
|
Long getHash64Hi();
|
||||||
Long getHash64Lo();
|
Long getHash64Lo();
|
||||||
@@ -7,4 +11,7 @@ public interface SimilarImageProjection {
|
|||||||
Long getUserId();
|
Long getUserId();
|
||||||
String getOriginalFileName();
|
String getOriginalFileName();
|
||||||
Long getFileSize();
|
Long getFileSize();
|
||||||
|
Long getSupportId();
|
||||||
|
LocalDateTime getUploadDate();
|
||||||
|
ProtectionStatus getProtectionStatus();
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,10 @@
|
|||||||
package ru.soune.nocopy.service;
|
package ru.soune.nocopy.service;
|
||||||
|
|
||||||
|
import com.vrt.fileprotection.image.phash.PHash;
|
||||||
|
import com.vrt.fileprotection.image.phash.PerceptualHashHelper;
|
||||||
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.data.domain.Page;
|
import org.springframework.data.domain.Page;
|
||||||
import org.springframework.data.domain.PageImpl;
|
import org.springframework.data.domain.PageImpl;
|
||||||
import org.springframework.data.domain.Pageable;
|
import org.springframework.data.domain.Pageable;
|
||||||
@@ -31,6 +34,9 @@ public class FileSimilarityService {
|
|||||||
|
|
||||||
private final AuthTokenRepository authTokenRepository;
|
private final AuthTokenRepository authTokenRepository;
|
||||||
|
|
||||||
|
@Value("${server.baseurl}")
|
||||||
|
private String baseUrl;
|
||||||
|
|
||||||
public List<SimilarFileDTO> findSimilarFiles(String fileId) {
|
public List<SimilarFileDTO> findSimilarFiles(String fileId) {
|
||||||
var imageHashEntity = hashRepository.findById(fileId)
|
var imageHashEntity = hashRepository.findById(fileId)
|
||||||
.orElseThrow(() -> new RuntimeException("Hash not found"));
|
.orElseThrow(() -> new RuntimeException("Hash not found"));
|
||||||
@@ -167,11 +173,11 @@ public class FileSimilarityService {
|
|||||||
Long hash64Hi, Long hash64Lo) {
|
Long hash64Hi, Long hash64Lo) {
|
||||||
Long imageProjectionHash64Hi = similarImageProjection.getHash64Hi();
|
Long imageProjectionHash64Hi = similarImageProjection.getHash64Hi();
|
||||||
Long similarImageProjectionHash64Lo = similarImageProjection.getHash64Lo();
|
Long similarImageProjectionHash64Lo = similarImageProjection.getHash64Lo();
|
||||||
|
int hamming = PerceptualHashHelper.INSTANCE.hammingDistance(new PHash(hash64Hi, hash64Lo),
|
||||||
int hamming = fileUtil.hamming64(hash64Hi, hash64Lo, imageProjectionHash64Hi, similarImageProjectionHash64Lo);
|
new PHash(imageProjectionHash64Hi, similarImageProjectionHash64Lo));
|
||||||
|
|
||||||
String level;
|
String level;
|
||||||
if (hamming <= 5) {
|
if (hamming <= 6) {
|
||||||
level = "DUPLICATE";
|
level = "DUPLICATE";
|
||||||
} else if (hamming <= 12) {
|
} else if (hamming <= 12) {
|
||||||
level = "SIMILAR";
|
level = "SIMILAR";
|
||||||
@@ -186,6 +192,10 @@ public class FileSimilarityService {
|
|||||||
.fileSize(similarImageProjection.getFileSize())
|
.fileSize(similarImageProjection.getFileSize())
|
||||||
.hammingDistance(hamming)
|
.hammingDistance(hamming)
|
||||||
.similarityLevel(level)
|
.similarityLevel(level)
|
||||||
|
.supportId(similarImageProjection.getSupportId())
|
||||||
|
.uploadDate(similarImageProjection.getUploadDate())
|
||||||
|
.status(similarImageProjection.getProtectionStatus())
|
||||||
|
.url(baseUrl + "/api/files/public/" + similarImageProjection.getId())
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
server:
|
||||||
|
baseurl: "http://localhost"
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
server:
|
||||||
|
baseurl: "http://92.242.61.23"
|
||||||
@@ -1,4 +1,8 @@
|
|||||||
spring:
|
spring:
|
||||||
|
config:
|
||||||
|
activate:
|
||||||
|
on-profile: ${SPRING_PROFILES_ACTIVE:dev}
|
||||||
|
|
||||||
mail:
|
mail:
|
||||||
host: smtp.gmail.com
|
host: smtp.gmail.com
|
||||||
port: 587
|
port: 587
|
||||||
@@ -11,15 +15,15 @@ spring:
|
|||||||
starttls:
|
starttls:
|
||||||
enable: true
|
enable: true
|
||||||
|
|
||||||
# mail:
|
# mail:
|
||||||
# host: postfix
|
# host: postfix
|
||||||
# port: 25
|
# port: 25
|
||||||
# username: noreply@nocopy.com
|
# username: noreply@nocopy.com
|
||||||
# password: nocopy!nocopy!
|
# password: nocopy!nocopy!
|
||||||
# properties:
|
# properties:
|
||||||
# mail.smtp.auth: false
|
# mail.smtp.auth: false
|
||||||
# mail.smtp.starttls.enable: false
|
# mail.smtp.starttls.enable: false
|
||||||
# mail.debug: true
|
# mail.debug: true
|
||||||
|
|
||||||
flyway:
|
flyway:
|
||||||
enabled: true
|
enabled: true
|
||||||
@@ -87,5 +91,5 @@ app:
|
|||||||
email:
|
email:
|
||||||
verification:
|
verification:
|
||||||
standart-subject: NO COPY - Подтверждение email адреса
|
standart-subject: NO COPY - Подтверждение email адреса
|
||||||
standart-address-from: NO COPY <noreply@nocopy.com>
|
standart-address-from: vlad.popovtsev@gmail.com
|
||||||
user: Пользователь
|
user: Пользователь
|
||||||
Reference in New Issue
Block a user