From 27755c2d4a79c507b3c5d5f05e20964c6ad69356 Mon Sep 17 00:00:00 2001 From: Anton Romanov Date: Thu, 3 Apr 2025 22:54:22 +0400 Subject: [PATCH] #21 -- Fix file services --- .vscode/launch.json | 15 +++ .vscode/settings.json | 3 + .../java/ru/ulstu/file/model/FileData.java | 64 +++++++++ .../java/ru/ulstu/file/model/FileDataDto.java | 96 ++++++++++++++ .../ulstu/file/repository/FileRepository.java | 7 + .../ru/ulstu/file/service/FileService.java | 123 ++++++++++++++++++ .../ulstu/files/FileSystemStorageService.java | 80 ------------ src/main/java/ru/ulstu/files/FileUtil.java | 29 ----- .../java/ru/ulstu/files/FilesController.java | 28 ---- .../java/ru/ulstu/files/StorageException.java | 12 -- .../files/StorageFileNotFoundException.java | 12 -- .../java/ru/ulstu/files/StorageService.java | 23 ---- 12 files changed, 308 insertions(+), 184 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 src/main/java/ru/ulstu/file/model/FileData.java create mode 100644 src/main/java/ru/ulstu/file/model/FileDataDto.java create mode 100644 src/main/java/ru/ulstu/file/repository/FileRepository.java create mode 100644 src/main/java/ru/ulstu/file/service/FileService.java delete mode 100644 src/main/java/ru/ulstu/files/FileSystemStorageService.java delete mode 100644 src/main/java/ru/ulstu/files/FileUtil.java delete mode 100644 src/main/java/ru/ulstu/files/FilesController.java delete mode 100644 src/main/java/ru/ulstu/files/StorageException.java delete mode 100644 src/main/java/ru/ulstu/files/StorageFileNotFoundException.java delete mode 100644 src/main/java/ru/ulstu/files/StorageService.java diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..05b076f --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "java", + "name": "SeminarApplication", + "request": "launch", + "mainClass": "ru.ulstu.SeminarApplication", + "projectName": "seminar" + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..0ca4d0b --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "java.compile.nullAnalysis.mode": "automatic" +} \ No newline at end of file diff --git a/src/main/java/ru/ulstu/file/model/FileData.java b/src/main/java/ru/ulstu/file/model/FileData.java new file mode 100644 index 0000000..b85884f --- /dev/null +++ b/src/main/java/ru/ulstu/file/model/FileData.java @@ -0,0 +1,64 @@ +package ru.ulstu.file.model; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; +import ru.ulstu.model.BaseEntity; + +import java.util.Date; + +@Entity +@Table(name = "file") +public class FileData extends BaseEntity { + private String name; + + private long size; + + @Column(name = "create_date") + private Date createDate; + + private byte[] data; + + @Column(name = "is_latex_attach") + private Boolean isLatexAttach; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public long getSize() { + return size; + } + + public void setSize(long size) { + this.size = size; + } + + public Date getCreateDate() { + return createDate; + } + + public void setCreateDate(Date createDate) { + this.createDate = createDate; + } + + public byte[] getData() { + return data; + } + + public void setData(byte[] data) { + this.data = data; + } + + public Boolean isLatexAttach() { + return isLatexAttach; + } + + public void setLatexAttach(Boolean latexAttach) { + isLatexAttach = latexAttach; + } +} diff --git a/src/main/java/ru/ulstu/file/model/FileDataDto.java b/src/main/java/ru/ulstu/file/model/FileDataDto.java new file mode 100644 index 0000000..d33e14a --- /dev/null +++ b/src/main/java/ru/ulstu/file/model/FileDataDto.java @@ -0,0 +1,96 @@ +package ru.ulstu.file.model; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class FileDataDto { + private Integer id; + private String name; + private String fileName; + private String tmpFileName; + private boolean deleted; + private Boolean isLatexAttach; + + public FileDataDto() { + } + + @JsonCreator + public FileDataDto(@JsonProperty("id") Integer id, + @JsonProperty("name") String name, + @JsonProperty("isLatexAttach") Boolean isLatexAttach, + @JsonProperty("fileName") String fileName, + @JsonProperty("tmpFileName") String tmpFileName) { + this.id = id; + this.name = name; + this.fileName = fileName; + this.tmpFileName = tmpFileName; + this.isLatexAttach = isLatexAttach; + } + + public FileDataDto(FileData fileData) { + this.id = fileData.getId(); + this.name = fileData.getName(); + this.isLatexAttach = fileData.isLatexAttach(); + } + + public FileDataDto(String fileName, String tmpFileName) { + this.fileName = fileName; + this.tmpFileName = tmpFileName; + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getFileName() { + return fileName; + } + + public void setFileName(String fileName) { + this.fileName = fileName; + } + + public String getTmpFileName() { + return tmpFileName; + } + + public void setTmpFileName(String tmpFileName) { + this.tmpFileName = tmpFileName; + } + + public boolean isDeleted() { + return deleted; + } + + public void setDeleted(boolean deleted) { + this.deleted = deleted; + } + + public Boolean isLatexAttach() { + return isLatexAttach; + } + + public Boolean getIsLatexAttach() { + return isLatexAttach; + } + + public void setLatexAttach(Boolean latexAttach) { + isLatexAttach = latexAttach; + } + + public void setIsLatexAttach(Boolean latexAttach) { + isLatexAttach = latexAttach; + } +} diff --git a/src/main/java/ru/ulstu/file/repository/FileRepository.java b/src/main/java/ru/ulstu/file/repository/FileRepository.java new file mode 100644 index 0000000..509cc1b --- /dev/null +++ b/src/main/java/ru/ulstu/file/repository/FileRepository.java @@ -0,0 +1,7 @@ +package ru.ulstu.file.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import ru.ulstu.file.model.FileData; + +public interface FileRepository extends JpaRepository { +} diff --git a/src/main/java/ru/ulstu/file/service/FileService.java b/src/main/java/ru/ulstu/file/service/FileService.java new file mode 100644 index 0000000..de2bbeb --- /dev/null +++ b/src/main/java/ru/ulstu/file/service/FileService.java @@ -0,0 +1,123 @@ +package ru.ulstu.file.service; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; +import ru.ulstu.file.model.FileData; +import ru.ulstu.file.model.FileDataDto; +import ru.ulstu.file.repository.FileRepository; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.UUID; + +import static java.nio.charset.StandardCharsets.UTF_8; + +@Service +public class FileService { + private final static int META_FILE_NAME_INDEX = 0; + private final static int META_FILE_SIZE_INDEX = 1; + + private final String tmpDir; + private final FileRepository fileRepository; + + public FileService(FileRepository fileRepository) { + tmpDir = System.getProperty("java.io.tmpdir"); + this.fileRepository = fileRepository; + } + + private FileData createFileFromTmp(String tmpFileName) throws IOException { + FileData fileData = new FileData(); + fileData.setData(getTmpFile(tmpFileName)); + fileData.setSize(getTmpFileSize(tmpFileName)); + fileData.setCreateDate(new Date()); + return fileRepository.save(fileData); + } + + private String uploadToTmpDir(MultipartFile multipartFile) throws IOException { + String tmpFileName = String.valueOf(System.currentTimeMillis()) + UUID.randomUUID(); + Files.write(getTmpFilePath(tmpFileName), multipartFile.getBytes()); + String meta = multipartFile.getOriginalFilename() + "\n" + multipartFile.getSize(); + Files.write(getTmpFileMetaPath(tmpFileName), meta.getBytes(UTF_8)); + return tmpFileName; + } + + private String[] getMeta(String tmpFileName) throws IOException { + return new String(Files.readAllBytes(getTmpFileMetaPath(tmpFileName)), UTF_8) + .split("\n"); + } + + private long getTmpFileSize(String tmpFileName) throws IOException { + return Long.valueOf(getMeta(tmpFileName)[META_FILE_SIZE_INDEX]).longValue(); + } + + public String getTmpFileName(String tmpFileName) throws IOException { + return getMeta(tmpFileName)[META_FILE_NAME_INDEX]; + } + + public byte[] getTmpFile(String tmpFileName) throws IOException { + return Files.readAllBytes(getTmpFilePath(tmpFileName)); + } + + public FileData getFile(Integer fileId) { + return fileRepository.getOne(fileId); + } + + public void deleteTmpFile(String tmpFileName) throws IOException { + Files.delete(getTmpFilePath(tmpFileName)); + } + + private Path getTmpFilePath(String tmpFileName) { + return Paths.get(tmpDir, tmpFileName); + } + + private Path getTmpFileMetaPath(String tmpFileName) { + return Paths.get(getTmpFilePath(tmpFileName) + ".meta"); + } + + public void deleteFile(FileData fileData) { + fileRepository.delete(fileData); + } + + public List saveOrCreate(List fileDtos) throws IOException { + List files = new ArrayList<>(); + for (FileDataDto file : fileDtos) { + files.add(file.getId() != null ? update(file) : create(file)); + } + return files; + } + + @Transactional + private FileData update(FileDataDto fileDataDto) { + FileData file = fileRepository.findById(fileDataDto.getId()) + .orElseThrow(() -> new RuntimeException("File not found by id")); + return fileRepository.save(copyFromDto(file, fileDataDto)); + } + + @Transactional + private FileData create(FileDataDto fileDataDto) throws IOException { + FileData newFile = createFileFromTmp(fileDataDto.getTmpFileName()); + copyFromDto(newFile, fileDataDto); + return fileRepository.save(newFile); + } + + private FileData copyFromDto(FileData fileData, FileDataDto fileDataDto) { + fileData.setName(fileDataDto.getName()); + fileData.setLatexAttach(fileDataDto.isLatexAttach()); + return fileData; + } + + @Transactional + public void delete(Integer fileId) { + fileRepository.deleteById(fileId); + } + + public FileDataDto createFromMultipartFile(MultipartFile multipartFile) throws IOException { + return new FileDataDto(multipartFile.getOriginalFilename(), uploadToTmpDir(multipartFile)); + } +} diff --git a/src/main/java/ru/ulstu/files/FileSystemStorageService.java b/src/main/java/ru/ulstu/files/FileSystemStorageService.java deleted file mode 100644 index 805aa2f..0000000 --- a/src/main/java/ru/ulstu/files/FileSystemStorageService.java +++ /dev/null @@ -1,80 +0,0 @@ -package ru.ulstu.files; - -import org.springframework.core.io.Resource; -import org.springframework.core.io.UrlResource; -import org.springframework.stereotype.Service; -import org.springframework.util.FileSystemUtils; -import org.springframework.web.multipart.MultipartFile; - -import java.io.IOException; -import java.net.MalformedURLException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.stream.Stream; - -@Service -public class FileSystemStorageService implements StorageService { - public static final String UPLOAD_DIR = "upload"; - - private final Path rootLocation = Paths.get(UPLOAD_DIR); - - @Override - public void store(MultipartFile file) { - try { - if (file.isEmpty()) { - throw new StorageException("Failed to store empty file " + file.getOriginalFilename()); - } - Files.copy(file.getInputStream(), this.rootLocation.resolve(file.getOriginalFilename())); - } catch (IOException e) { - throw new StorageException("Failed to store file " + file.getOriginalFilename(), e); - } - } - - @Override - public Stream loadAll() { - try { - return Files.walk(this.rootLocation, 1) - .filter(path -> !path.equals(this.rootLocation)) - .map(path -> this.rootLocation.relativize(path)); - } catch (IOException e) { - throw new StorageException("Failed to read stored files", e); - } - - } - - @Override - public Path load(String filename) { - return rootLocation.resolve(filename); - } - - @Override - public Resource loadAsResource(String filename) { - try { - Path file = load(filename); - Resource resource = new UrlResource(file.toUri()); - if (resource.exists() || resource.isReadable()) { - return resource; - } else { - throw new StorageFileNotFoundException("Could not read file: " + filename); - - } - } catch (MalformedURLException e) { - throw new StorageFileNotFoundException("Could not read file: " + filename, e); - } - } - - @Override - public void deleteAll() { - FileSystemUtils.deleteRecursively(rootLocation.toFile()); - } - - @Override - public void init() { - try { - Files.createDirectory(rootLocation); - } catch (IOException e) { - throw new StorageException("Could not initialize storage", e); - } - } -} diff --git a/src/main/java/ru/ulstu/files/FileUtil.java b/src/main/java/ru/ulstu/files/FileUtil.java deleted file mode 100644 index e2ccfc2..0000000 --- a/src/main/java/ru/ulstu/files/FileUtil.java +++ /dev/null @@ -1,29 +0,0 @@ -package ru.ulstu.files; - -import org.springframework.web.multipart.MultipartFile; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.StandardCopyOption; - -public class FileUtil { - - public static void saveFile(String uploadDir, String fileName, - MultipartFile multipartFile) throws IOException { - Path uploadPath = Paths.get(uploadDir); - - if (!Files.exists(uploadPath)) { - Files.createDirectories(uploadPath); - } - - try (InputStream inputStream = multipartFile.getInputStream()) { - Path filePath = uploadPath.resolve(fileName); - Files.copy(inputStream, filePath, StandardCopyOption.REPLACE_EXISTING); - } catch (IOException ioe) { - throw new IOException("Could not save image file: " + fileName, ioe); - } - } -} diff --git a/src/main/java/ru/ulstu/files/FilesController.java b/src/main/java/ru/ulstu/files/FilesController.java deleted file mode 100644 index 5df8814..0000000 --- a/src/main/java/ru/ulstu/files/FilesController.java +++ /dev/null @@ -1,28 +0,0 @@ -package ru.ulstu.files; - -import org.springframework.core.io.Resource; -import org.springframework.http.HttpHeaders; -import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.ResponseBody; - -@Controller -public class FilesController { - private final StorageService storageService; - - public FilesController(StorageService storageService) { - this.storageService = storageService; - } - - @GetMapping("/files/{filename:.+}") - @ResponseBody - public ResponseEntity serveFile(@PathVariable String filename) { - Resource file = storageService.loadAsResource((filename == null || filename.equals("null") || filename.isEmpty()) - ? "logo.png" - : filename); - return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION, - "filename=\"" + file.getFilename() + "\"").body(file); - } -} diff --git a/src/main/java/ru/ulstu/files/StorageException.java b/src/main/java/ru/ulstu/files/StorageException.java deleted file mode 100644 index eb7da9f..0000000 --- a/src/main/java/ru/ulstu/files/StorageException.java +++ /dev/null @@ -1,12 +0,0 @@ -package ru.ulstu.files; - -public class StorageException extends RuntimeException { - - public StorageException(String message) { - super(message); - } - - public StorageException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/src/main/java/ru/ulstu/files/StorageFileNotFoundException.java b/src/main/java/ru/ulstu/files/StorageFileNotFoundException.java deleted file mode 100644 index 085c94d..0000000 --- a/src/main/java/ru/ulstu/files/StorageFileNotFoundException.java +++ /dev/null @@ -1,12 +0,0 @@ -package ru.ulstu.files; - -public class StorageFileNotFoundException extends StorageException { - - public StorageFileNotFoundException(String message) { - super(message); - } - - public StorageFileNotFoundException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/src/main/java/ru/ulstu/files/StorageService.java b/src/main/java/ru/ulstu/files/StorageService.java deleted file mode 100644 index 48e3461..0000000 --- a/src/main/java/ru/ulstu/files/StorageService.java +++ /dev/null @@ -1,23 +0,0 @@ -package ru.ulstu.files; - -import org.springframework.core.io.Resource; -import org.springframework.web.multipart.MultipartFile; - -import java.nio.file.Path; -import java.util.stream.Stream; - -public interface StorageService { - - void init(); - - void store(MultipartFile file); - - Stream loadAll(); - - Path load(String filename); - - Resource loadAsResource(String filename); - - void deleteAll(); - -}