esoe
6 months ago
15 changed files with 330 additions and 5 deletions
@ -1,8 +1,12 @@
@@ -1,8 +1,12 @@
|
||||
FROM openjdk:17-jdk-alpine |
||||
RUN apk update |
||||
RUN apk upgrade |
||||
COPY target/explorer_rs-0.1.jar /app/explorer_rs-0.1.jar |
||||
WORKDIR /app |
||||
COPY target/explorer_rs-0.1.jar /app/explorer_rs/explorer_rs-0.1.jar |
||||
WORKDIR /app/explorer_rs |
||||
# ENTRYPOINT ["java","-jar","/app/resource-service-api-0.1.jar"] |
||||
# docker image build -t resource-service-api:latest . |
||||
# docker run -d -p80:8181 resource-service-api:latest |
||||
# docker run -d -p80:8181 resource-service-api:latest |
||||
|
||||
# Путь к загружаемым на сервер данным: |
||||
# /app/explorer_rs/uploads |
||||
# Нужно создать для него том, чтобы получить доступ к загружаемым на сервер данным |
@ -0,0 +1,17 @@
@@ -0,0 +1,17 @@
|
||||
package ru.molokoin.explorer_rs.config; |
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties; |
||||
|
||||
@ConfigurationProperties(prefix = "storage") |
||||
public class StorageProperties { |
||||
private String location; |
||||
|
||||
public String getLocation() { |
||||
return location; |
||||
} |
||||
|
||||
public void setLocation(String location) { |
||||
this.location = location; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,73 @@
@@ -0,0 +1,73 @@
|
||||
package ru.molokoin.explorer_rs.controller; |
||||
|
||||
import java.util.Arrays; |
||||
import java.util.List; |
||||
import java.util.stream.Collectors; |
||||
|
||||
import org.springframework.core.io.Resource; |
||||
import org.springframework.http.HttpHeaders; |
||||
import org.springframework.http.ResponseEntity; |
||||
import org.springframework.stereotype.Controller; |
||||
import org.springframework.ui.Model; |
||||
import org.springframework.web.bind.annotation.GetMapping; |
||||
import org.springframework.web.bind.annotation.PathVariable; |
||||
import org.springframework.web.bind.annotation.PostMapping; |
||||
import org.springframework.web.bind.annotation.RequestParam; |
||||
import org.springframework.web.bind.annotation.ResponseBody; |
||||
import org.springframework.web.multipart.MultipartFile; |
||||
import org.springframework.web.servlet.support.ServletUriComponentsBuilder; |
||||
|
||||
import ru.molokoin.explorer_rs.entity.FileResponse; |
||||
import ru.molokoin.explorer_rs.repository.StorageService; |
||||
|
||||
@Controller |
||||
public class FileController { |
||||
private StorageService storageService; |
||||
|
||||
public FileController(StorageService storageService) { |
||||
this.storageService = storageService; |
||||
} |
||||
|
||||
@GetMapping("/") |
||||
public String listAllFiles(Model model) { |
||||
model.addAttribute("files", storageService.loadAll().map( |
||||
path -> ServletUriComponentsBuilder.fromCurrentContextPath() |
||||
.path("/download/") |
||||
.path(path.getFileName().toString()) |
||||
.toUriString()) |
||||
.collect(Collectors.toList())); |
||||
return "listFiles"; |
||||
} |
||||
|
||||
@GetMapping("/download/{filename:.+}") |
||||
@ResponseBody |
||||
public ResponseEntity<Resource> downloadFile(@PathVariable String filename) { |
||||
Resource resource = storageService.loadAsResource(filename); |
||||
return ResponseEntity.ok() |
||||
.header(HttpHeaders.CONTENT_DISPOSITION, |
||||
"attachment; filename=\"" + resource.getFilename() + "\"") |
||||
.body(resource); |
||||
} |
||||
|
||||
@PostMapping("/upload-file") |
||||
@ResponseBody |
||||
public FileResponse uploadFile(@RequestParam("file") MultipartFile file) { |
||||
String name = storageService.store(file); |
||||
|
||||
String uri = ServletUriComponentsBuilder.fromCurrentContextPath() |
||||
.path("/download/") |
||||
.path(name) |
||||
.toUriString(); |
||||
|
||||
return new FileResponse(name, uri, file.getContentType(), file.getSize()); |
||||
} |
||||
|
||||
@PostMapping("/upload-multiple-files") |
||||
@ResponseBody |
||||
public List<FileResponse> uploadMultipleFiles(@RequestParam("files") MultipartFile[] files) { |
||||
return Arrays.stream(files) |
||||
.map(file -> uploadFile(file)) |
||||
.collect(Collectors.toList()); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,18 @@
@@ -0,0 +1,18 @@
|
||||
package ru.molokoin.explorer_rs.entity; |
||||
|
||||
import lombok.Data; |
||||
|
||||
@Data |
||||
public class FileResponse { |
||||
private String name; |
||||
private String uri; |
||||
private String type; |
||||
private long size; |
||||
|
||||
public FileResponse(String name, String uri, String type, long size) { |
||||
this.name = name; |
||||
this.uri = uri; |
||||
this.type = type; |
||||
this.size = size; |
||||
} |
||||
} |
@ -0,0 +1,15 @@
@@ -0,0 +1,15 @@
|
||||
package ru.molokoin.explorer_rs.exception; |
||||
|
||||
import org.springframework.http.HttpStatus; |
||||
import org.springframework.web.bind.annotation.ResponseStatus; |
||||
|
||||
@ResponseStatus(HttpStatus.NOT_FOUND) |
||||
public class FileNotFoundException extends StorageException{ |
||||
public FileNotFoundException(String message) { |
||||
super(message); |
||||
} |
||||
|
||||
public FileNotFoundException(String message, Throwable cause) { |
||||
super(message, cause); |
||||
} |
||||
} |
@ -0,0 +1,11 @@
@@ -0,0 +1,11 @@
|
||||
package ru.molokoin.explorer_rs.exception; |
||||
|
||||
public class StorageException extends RuntimeException{ |
||||
public StorageException(String message) { |
||||
super(message); |
||||
} |
||||
|
||||
public StorageException(String message, Throwable cause) { |
||||
super(message, cause); |
||||
} |
||||
} |
@ -0,0 +1,107 @@
@@ -0,0 +1,107 @@
|
||||
package ru.molokoin.explorer_rs.repository; |
||||
|
||||
import java.io.IOException; |
||||
import java.io.InputStream; |
||||
import java.net.MalformedURLException; |
||||
import java.nio.file.Files; |
||||
import java.nio.file.Path; |
||||
import java.nio.file.Paths; |
||||
import java.nio.file.StandardCopyOption; |
||||
import java.util.stream.Stream; |
||||
|
||||
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 org.springframework.util.FileSystemUtils; |
||||
import org.springframework.util.StringUtils; |
||||
import org.springframework.web.multipart.MultipartFile; |
||||
|
||||
import jakarta.annotation.PostConstruct; |
||||
import ru.molokoin.explorer_rs.config.StorageProperties; |
||||
import ru.molokoin.explorer_rs.exception.StorageException; |
||||
import ru.molokoin.explorer_rs.exception.FileNotFoundException; |
||||
|
||||
@Service |
||||
public class FileSystemStorageService implements StorageService{ |
||||
private final Path rootLocation; |
||||
|
||||
@Autowired |
||||
public FileSystemStorageService(StorageProperties properties) { |
||||
this.rootLocation = Paths.get(properties.getLocation()); |
||||
} |
||||
|
||||
@Override |
||||
@PostConstruct |
||||
public void init() { |
||||
try { |
||||
Files.createDirectories(rootLocation); |
||||
} catch (IOException e) { |
||||
throw new StorageException("Could not initialize storage location", e); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public String store(MultipartFile file) { |
||||
String filename = StringUtils.cleanPath(file.getOriginalFilename()); |
||||
try { |
||||
if (file.isEmpty()) { |
||||
throw new StorageException("Failed to store empty file " + filename); |
||||
} |
||||
if (filename.contains("..")) { |
||||
// This is a security check
|
||||
throw new StorageException( |
||||
"Cannot store file with relative path outside current directory " |
||||
+ filename); |
||||
} |
||||
try (InputStream inputStream = file.getInputStream()) { |
||||
Files.copy(inputStream, this.rootLocation.resolve(filename), |
||||
StandardCopyOption.REPLACE_EXISTING); |
||||
} |
||||
} |
||||
catch (IOException e) { |
||||
throw new StorageException("Failed to store file " + filename, e); |
||||
} |
||||
return filename; |
||||
} |
||||
|
||||
@Override |
||||
public Stream<Path> loadAll() { |
||||
try { |
||||
return Files.walk(this.rootLocation, 1) |
||||
.filter(path -> !path.equals(this.rootLocation)) |
||||
.map(this.rootLocation::relativize); |
||||
} |
||||
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 FileNotFoundException( |
||||
"Could not read file: " + filename); |
||||
} |
||||
}catch (MalformedURLException e) { |
||||
throw new FileNotFoundException("Could not read file: " + filename, e); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void deleteAll() { |
||||
FileSystemUtils.deleteRecursively(rootLocation.toFile()); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,16 @@
@@ -0,0 +1,16 @@
|
||||
package ru.molokoin.explorer_rs.repository; |
||||
|
||||
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(); |
||||
String store(MultipartFile file); |
||||
Stream<Path> loadAll(); |
||||
Path load(String filename); |
||||
Resource loadAsResource(String filename); |
||||
void deleteAll(); |
||||
} |
@ -0,0 +1,5 @@
@@ -0,0 +1,5 @@
|
||||
{"properties": [{ |
||||
"name": "storage.location", |
||||
"type": "java.lang.String", |
||||
"description": "Path to local storage for uploaded files" |
||||
}]} |
@ -0,0 +1,33 @@
@@ -0,0 +1,33 @@
|
||||
<!doctype html> |
||||
<html lang="en" xmlns:th="http://www.thymeleaf.org"> |
||||
<body> |
||||
|
||||
<h1>Spring Boot File Upload Example</h1> |
||||
|
||||
<hr/> |
||||
|
||||
<h4>Upload Single File:</h4> |
||||
<form method="POST" enctype="multipart/form-data" th:action="@{/upload-file}"> |
||||
<input type="file" name="file"> <br/><br/> |
||||
<button type="submit">Submit</button> |
||||
</form> |
||||
|
||||
<hr/> |
||||
|
||||
<h4>Upload Multiple Files:</h4> |
||||
<form method="POST" enctype="multipart/form-data" th:action="@{/upload-multiple-files}"> |
||||
<input type="file" name="files" multiple> <br/><br/> |
||||
<button type="submit">Submit</button> |
||||
</form> |
||||
|
||||
<hr/> |
||||
|
||||
<h2>All Uploaded Files:</h2> |
||||
<ul> |
||||
<li th:each="file : ${files}"> |
||||
<a th:href="${file}" target="_blank" th:text="${file}"></a> |
||||
</li> |
||||
</ul> |
||||
|
||||
</body> |
||||
</html> |
Loading…
Reference in new issue