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/


Related articles: