Sample code for Spring Boot and Kotlin upload files
- 2021-01-02 21:53:19
- OfStack
If we do a small web site, and just choose the kotlin and Spring Boot technology stack, then uploading files is essential. Of course, if you do a medium to large web site, it is recommended that you use cloud storage, which can save a lot of work.
This article explains how to upload files using kotlin and Spring Boot
Building engineering
If you're not already familiar with build engineering, check out My first Kotlin Application.
Complete build.gradle file
group 'name.quanke.kotlin'
version '1.0-SNAPSHOT'
buildscript {
ext.kotlin_version = '1.2.10'
ext.spring_boot_version = '1.5.4.RELEASE'
repositories {
mavenCentral()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath("org.springframework.boot:spring-boot-gradle-plugin:$spring_boot_version")
// Kotlin integration SpringBoot The default parameter-free constructor, which sets all classes by default open Class plug-in
classpath("org.jetbrains.kotlin:kotlin-noarg:$kotlin_version")
classpath("org.jetbrains.kotlin:kotlin-allopen:$kotlin_version")
}
}
apply plugin: 'kotlin'
apply plugin: "kotlin-spring" // See https://kotlinlang.org/docs/reference/compiler-plugins.html#kotlin-spring-compiler-plugin
apply plugin: 'org.springframework.boot'
jar {
baseName = 'chapter11-5-6-service'
version = '0.1.0'
}
repositories {
mavenCentral()
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
compile "org.springframework.boot:spring-boot-starter-web:$spring_boot_version"
compile "org.springframework.boot:spring-boot-starter-thymeleaf:$spring_boot_version"
testCompile "org.springframework.boot:spring-boot-starter-test:$spring_boot_version"
testCompile "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version"
}
compileKotlin {
kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
kotlinOptions.jvmTarget = "1.8"
}
Create file upload controller
import name.quanke.kotlin.chaper11_5_6.storage.StorageFileNotFoundException
import name.quanke.kotlin.chaper11_5_6.storage.StorageService
import org.springframework.beans.factory.annotation.Autowired
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.*
import org.springframework.web.multipart.MultipartFile
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder
import org.springframework.web.servlet.mvc.support.RedirectAttributes
import java.io.IOException
import java.util.stream.Collectors
/**
* File upload controller
* Created by http://quanke.name on 2018/1/12.
*/
@Controller
class FileUploadController @Autowired
constructor(private val storageService: StorageService) {
@GetMapping("/")
@Throws(IOException::class)
fun listUploadedFiles(model: Model): String {
model.addAttribute("files", storageService
.loadAll()
.map { path ->
MvcUriComponentsBuilder
.fromMethodName(FileUploadController::class.java, "serveFile", path.fileName.toString())
.build().toString()
}
.collect(Collectors.toList()))
return "uploadForm"
}
@GetMapping("/files/{filename:.+}")
@ResponseBody
fun serveFile(@PathVariable filename: String): ResponseEntity<Resource> {
val file = storageService.loadAsResource(filename)
return ResponseEntity
.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + file.filename + "\"")
.body(file)
}
@PostMapping("/")
fun handleFileUpload(@RequestParam("file") file: MultipartFile,
redirectAttributes: RedirectAttributes): String {
storageService.store(file)
redirectAttributes.addFlashAttribute("message",
"You successfully uploaded " + file.originalFilename + "!")
return "redirect:/"
}
@ExceptionHandler(StorageFileNotFoundException::class)
fun handleStorageFileNotFound(exc: StorageFileNotFoundException): ResponseEntity<*> {
return ResponseEntity.notFound().build<Any>()
}
}
Upload file service interface
import org.springframework.core.io.Resource
import org.springframework.web.multipart.MultipartFile
import java.nio.file.Path
import java.util.stream.Stream
interface StorageService {
fun init()
fun store(file: MultipartFile)
fun loadAll(): Stream<Path>
fun load(filename: String): Path
fun loadAsResource(filename: String): Resource
fun deleteAll()
}
Upload file service
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 java.io.IOException
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
@Service
class FileSystemStorageService @Autowired
constructor(properties: StorageProperties) : StorageService {
private val rootLocation: Path
init {
this.rootLocation = Paths.get(properties.location)
}
override fun store(file: MultipartFile) {
val filename = StringUtils.cleanPath(file.originalFilename)
try {
if (file.isEmpty) {
throw StorageException("Failed to store empty file " + filename)
}
if (filename.contains("..")) {
// This is a security check
throw StorageException(
"Cannot store file with relative path outside current directory " + filename)
}
Files.copy(file.inputStream, this.rootLocation.resolve(filename),
StandardCopyOption.REPLACE_EXISTING)
} catch (e: IOException) {
throw StorageException("Failed to store file " + filename, e)
}
}
override fun loadAll(): Stream<Path> {
try {
return Files.walk(this.rootLocation, 1)
.filter { path -> path != this.rootLocation }
.map { path -> this.rootLocation.relativize(path) }
} catch (e: IOException) {
throw StorageException("Failed to read stored files", e)
}
}
override fun load(filename: String): Path {
return rootLocation.resolve(filename)
}
override fun loadAsResource(filename: String): Resource {
try {
val file = load(filename)
val resource = UrlResource(file.toUri())
return if (resource.exists() || resource.isReadable) {
resource
} else {
throw StorageFileNotFoundException(
"Could not read file: " + filename)
}
} catch (e: MalformedURLException) {
throw StorageFileNotFoundException("Could not read file: " + filename, e)
}
}
override fun deleteAll() {
FileSystemUtils.deleteRecursively(rootLocation.toFile())
}
override fun init() {
try {
Files.createDirectories(rootLocation)
} catch (e: IOException) {
throw StorageException("Could not initialize storage", e)
}
}
}
Custom exception
open class StorageException : RuntimeException {
constructor(message: String) : super(message)
constructor(message: String, cause: Throwable) : super(message, cause)
}
class StorageFileNotFoundException : StorageException {
constructor(message: String) : super(message)
constructor(message: String, cause: Throwable) : super(message, cause)
}
Profile upload directory
import org.springframework.boot.context.properties.ConfigurationProperties
@ConfigurationProperties("storage")
class StorageProperties {
/**
* Folder location for storing files
*/
var location = "upload-dir"
}
Start the Spring Boot
/**
* Created by http://quanke.name on 2018/1/9.
*/
@SpringBootApplication
@EnableConfigurationProperties(StorageProperties::class)
class Application {
@Bean
internal fun init(storageService: StorageService) = CommandLineRunner {
storageService.deleteAll()
storageService.init()
}
companion object {
@Throws(Exception::class)
@JvmStatic
fun main(args: Array<String>) {
SpringApplication.run(Application::class.java, *args)
}
}
}
Create a simple template src html/main resources/templates/uploadForm html
<html xmlns:th="http://www.thymeleaf.org">
<body>
<div th:if="${message}">
<h2 th:text="${message}"/>
</div>
<div>
<form method="POST" enctype="multipart/form-data" action="/">
<table>
<tr>
<td>File to upload:</td>
<td><input type="file" name="file"/></td>
</tr>
<tr>
<td></td>
<td><input type="submit" value="Upload"/></td>
</tr>
</table>
</form>
</div>
<div>
<ul>
<li th:each="file : ${files}">
<a th:href="${file}" rel="external nofollow" th:text="${file}"/>
</li>
</ul>
</div>
</body>
</html>
Configuration file ES54en. yml
spring:
http:
multipart:
max-file-size: 128KB
max-request-size: 128KB
For more Spring Boot and kotlin, please follow Spring Boot and kotlin.
Source:
https://github.com/quanke/spring-boot-with-kotlin-in-action/
Reference:
https://spring.io/guides/gs/uploading-files/