-
Notifications
You must be signed in to change notification settings - Fork 0
File Upload
There are several instances on the website where file uploads are required, such as images for member profiles, projects, and events. This section provides an overview of how the upload functionality is implemented and how it can be utilized.
To facilitate the implementation of file uploads across various services without the need for extensive code modifications, we've established a shared abstract class. Each service extends this common class to harness its file upload capabilities. Currently, the class only features a method for image uploads, but its functionality can be further expanded as necessary to include tasks like file deletion.
abstract class FileUploader {
abstract fun uploadImage(folder: String, fileName: String, image: ByteArray): String
fun buildFileName(photoFile: MultipartFile, prefix: String = ""): String {
val limitedPrefix = prefix.take(100) // File name length has a limit of 256 characters
return "$limitedPrefix-${UUID.randomUUID()}.${photoFile.filenameExtension()}"
}
}
The application operates with a single service, and this decision is determined during startup. Further details on this are elaborated in the Configuration section.
The static file uploader simply utilizes the server's file system for local storage of files. While this may not be the preferred production setup, it serves as a reliable alternative during testing and in case any issues arise with cloud storage.
class StaticFileUploader(private val storePath: String, private val servePath: String) : FileUploader() {
override fun uploadImage(folder: String, fileName: String, image: ByteArray): String {
val file = File("$storePath/$folder/$fileName")
file.parentFile.mkdirs()
file.createNewFile()
file.writeBytes(image)
return "$servePath/$folder/$fileName"
}
}
- The
storePath
refers to the directory where files are stored on the server. - The
servePath
represents the URL used to serve static files. -
file.parentFile.mkdirs()
has been included to ensure the creation of all necessary directories.
Cloudinary is the cloud storage service we utilize. Employing a service like this is essential to safeguard crucial data and prevent any potential loss that might occur if the data were stored solely on the server.
class CloudinaryFileUploader(private val basePath: String, private val cloudinary: Cloudinary) : FileUploader() {
override fun uploadImage(folder: String, fileName: String, image: ByteArray): String {
val path = "$basePath/$folder/$fileName"
val imageTransformation = Transformation().width(250).height(250).crop("thumb").chain()
val result = cloudinary.uploader().upload(
image,
mapOf(
"public_id" to path,
"overwrite" to true,
"transformation" to imageTransformation
)
)
return result["url"]?.toString() ?: ""
}
}
- The
folder
parameter specifies the directory where files are stored on Cloudinary. - We utilize the
Cloudinary
class, which is part of an external library, to establish communication with the Cloudinary service. - Prior to uploading, the image undergoes transformation to prevent excessive storage consumption.
Having learned how to implement various types of file uploaders, the remaining aspect is understanding how the application determines which uploader to use. To properly understand this, please refer to the Configuration wiki page.
To begin, a set of configuration properties is utilized to inform the application about the chosen file-uploading service:
@ConfigurationProperties(prefix = "upload")
class UploadConfigProperties(
val provider: String?,
val cloudinaryUrl: String?,
val cloudinaryBasePath: String?,
val staticPath: String?,
val staticServe: String?
)
-
provider
is the name of the service provider in use, which can currently be either static or cloudinary. -
cloudinaryUrl
andcloudinaryBasePath
represent the URL of your Cloudinary dashboard and the directory where files will be stored. -
staticPath
andstaticServe
correspond to the directory where files are stored on the server and the URL used for serving them.
The decision regarding which uploader to employ at startup is made by defining a custom bean for FileUploader
. The specified provider
is examined and the respective service is configured accordingly:
@Configuration
class UploadConfig(
private val uploadConfigProperties: UploadConfigProperties
) {
@Bean
fun fileUploader(): FileUploader {
return when (uploadConfigProperties.provider) {
"cloudinary" -> CloudinaryFileUploader(
uploadConfigProperties.cloudinaryBasePath ?: "/",
Cloudinary(uploadConfigProperties.cloudinaryUrl ?: throw Error("Cloudinary URL not provided"))
)
else -> StaticFileUploader(
uploadConfigProperties.staticPath?.let { ResourceUtils.getFile(it).absolutePath } ?: "",
uploadConfigProperties.staticServe ?: "localhost:8080"
)
}
}
}
Getting Started
Architecture Details
Implementation Details
Testing
Documentation
Deployment