Итак, я создаю базовое приложение для социальных сетей для изучения lombok и thymeleaf, и у меня возникают проблемы, когда я пытаюсь удалить запись из mongoDB. Мое приложение может загружать изображения просто отлично, но нажатие клавиши удаления отправит запрос POST вместо запроса DELETE.
index.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8" />
<title>Spring-a-Gram</title>
<link rel="stylesheet" href="/main.css" />
</head>
<body>
<h1>Spring Boot - Social</h1>
<div>
<table>
<thead>
<tr>
<th>Id</th><th>Name</th><th>Image</th><th></th>
</tr>
</thead>
<tbody>
<tr th:each="image : ${images}">
<td th:text="${image.id}" />
<td th:text="${image.name}" />
<td>
<a th:href="@{'/images/' + ${image.name} + '/raw'}">
<img th:src="@{'/images/'+${image.name}+'/raw'}"
class="thumbnail" />
</a>
</td>
<td>
<form th:method="delete"
th:action="@{'/images/' + ${image.name}}">
<input type="submit" value="Delete" />
</form>
</td>
</tr>
</tbody>
</table>
<form method="post" enctype="multipart/form-data"
action="/images">
<p><input type="file" name="file" /></p>
<p><input type="submit" value="Upload" /></p>
</form>
</div>
</body>
</html>
HomeController.java
package com.alexander.springsocial;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.codec.multipart.FilePart;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.io.IOException;
/**
* @author Alexander Hartson
*/
@Controller
public class HomeController {
private static final String BASE_PATH = "/images";
private static final String FILENAME = "{filename:.+}";
private final ImageService imageService;
public HomeController(ImageService imageService) {
this.imageService = imageService;
}
@GetMapping("/")
public Mono<String> index(Model model) {
model.addAttribute("images", imageService.findAllImages());
return Mono.just("index");
}
@GetMapping(value = BASE_PATH + "/" + FILENAME + "/raw",
produces = MediaType.IMAGE_JPEG_VALUE)
@ResponseBody
public Mono<ResponseEntity<?>> oneRawImage(
@PathVariable String filename) {
return imageService.findOneImage(filename).map(resource -> {
try {
return ResponseEntity.ok()
.contentLength(resource.contentLength())
.body(new InputStreamResource(
resource.getInputStream()));
} catch (IOException e) {
return ResponseEntity.badRequest()
.body("Couldn't find " + filename +
" => " + e.getMessage());
}
});
}
@PostMapping(value = BASE_PATH)
public Mono<String> createFile(@RequestPart(name = "file")
Flux<FilePart> files) {
return imageService.createImage(files)
.then(Mono.just("redirect:/"));
}
@DeleteMapping(BASE_PATH + "/" + FILENAME)
public Mono<String> deleteFile(@PathVariable String filename) {
return imageService.deleteImage(filename)
.then(Mono.just("redirect:/"));
}
}
ImageService.java
package com.alexander.springsocial;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.UUID;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.http.codec.multipart.FilePart;
import org.springframework.stereotype.Service;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.FileSystemUtils;
/**
* @author Alexander Hartson
*/
@Service
public class ImageService {
public static String UPLOAD_ROOT = "upload-dir";
// tag::injection[]
private final ResourceLoader resourceLoader;
private final ImageRepository imageRepository;
public ImageService(ResourceLoader resourceLoader,
ImageRepository imageRepository) {
this.resourceLoader = resourceLoader;
this.imageRepository = imageRepository;
}
// end::injection[]
// tag::1[]
public Flux<Image> findAllImages() {
return imageRepository.findAll()
.log("findAll");
}
// end::1[]
public Mono<Resource> findOneImage(String filename) {
return Mono.fromSupplier(() ->
resourceLoader.getResource(
"file:" + UPLOAD_ROOT + "/" + filename))
.log("findOneImage");
}
// tag::2[]
public Mono<Void> createImage(Flux<FilePart> files) {
return files
.log("createImage-files")
.flatMap(file -> {
Mono<Image> saveDatabaseImage = imageRepository.save(
new Image(
UUID.randomUUID().toString(),
file.filename()))
.log("createImage-save");
Mono<Void> copyFile = Mono.just(
Paths.get(UPLOAD_ROOT, file.filename())
.toFile())
.log("createImage-picktarget")
.map(destFile -> {
try {
destFile.createNewFile();
return destFile;
} catch (IOException e) {
throw new RuntimeException(e);
}
})
.log("createImage-newfile")
.flatMap(file::transferTo)
.log("createImage-copy");
return Mono.when(saveDatabaseImage, copyFile)
.log("createImage-when");
})
.log("createImage-flatMap")
.then()
.log("createImage-done");
}
// end::2[]
// tag::3[]
public Mono<Void> deleteImage(String filename) {
Mono<Void> deleteDatabaseImage = imageRepository
.findByName(filename)
.log("deleteImage-find")
.flatMap(imageRepository::delete)
.log("deleteImage-record");
Mono<Object> deleteFile = Mono.fromRunnable(() -> {
try {
Files.deleteIfExists(
Paths.get(UPLOAD_ROOT, filename));
} catch (IOException e) {
throw new RuntimeException(e);
}
})
.log("deleteImage-file");
return Mono.when(deleteDatabaseImage, deleteFile)
.log("deleteImage-when")
.then()
.log("deleteImage-done");
}
// end::3[]
/**
* Pre-load some test images
*
* @return Spring Boot {@link CommandLineRunner} automatically
* run after app context is loaded.
*/
@Bean
CommandLineRunner setUp() throws IOException {
return (args) -> {
FileSystemUtils.deleteRecursively(new File(UPLOAD_ROOT));
Files.createDirectory(Paths.get(UPLOAD_ROOT));
FileCopyUtils.copy("Test file",
new FileWriter(UPLOAD_ROOT +
"/image1.jpg"));
FileCopyUtils.copy("Test file2",
new FileWriter(UPLOAD_ROOT +
"/image2.jpg"));
FileCopyUtils.copy("Test file3",
new FileWriter(UPLOAD_ROOT + "/image3.jpg"));
};
}
}
Gradle
buildscript {
ext {
springBootVersion = '2.0.4.RELEASE'
}
repositories {
mavenCentral()
maven { url "https://repo.spring.io/snapshot" }
maven { url "https://repo.spring.io/milestone" }
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
group = 'com.alexander.spring-social'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8
repositories {
mavenCentral()
maven { url "https://repo.spring.io/snapshot" }
maven { url "https://repo.spring.io/milestone" }
maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }
}
dependencies {
// tag::netty
compile('org.springframework.boot:spring-boot-starter-webflux') {
exclude group: 'org.springframework.boot',
module: 'spring-boot-starter-reactor-netty'
}
compile('org.springframework.boot:spring-boot-starter-tomcat')
compile('org.springframework.boot:spring-boot-starter-thymeleaf')
compile('org.springframework.boot:spring-boot-starter-data-mongodb-reactive')
compile('org.synchronoss.cloud:nio-multipart-parser')
// Specify lombok version
compileOnly('org.projectlombok:lombok:1.18.2')
testCompile('org.springframework.boot:spring-boot-starter-test')
testCompile("io.projectreactor:reactor-test")
}
Из моей отладки на этом снимке экрана похоже, что метод формы определяет действие как "сообщение", хотя я указал "удалить"? Мне не имеет смысла, почему он это делает. Любые идеи были бы хорошы.