實作素材上傳

This commit is contained in:
Raymond Yang 2023-05-16 10:02:29 +08:00
parent acf72d0aa7
commit 50dbcf60e1
9 changed files with 240 additions and 9 deletions

View File

@ -33,4 +33,5 @@ dependencies {
implementation("org.litote.kmongo:kmongo:$kmongo_version")
implementation("io.ktor:ktor-server-auth-jvm:$ktor_version")
implementation("io.ktor:ktor-server-auth-jwt-jvm:$ktor_version")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.1.0")
}

View File

@ -2,15 +2,20 @@ package com.ray650128
import com.ray650128.model.dto.UserDto
import com.ray650128.plugins.configureUserRouting
import com.ray650128.plugins.configureSerialization
import com.ray650128.plugins.configureArMaterialRouting
import io.ktor.serialization.kotlinx.json.*
import io.ktor.server.application.*
import io.ktor.server.auth.*
import io.ktor.server.auth.jwt.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import io.ktor.server.plugins.contentnegotiation.*
fun main() {
embeddedServer(Netty, port = 8080, host = "0.0.0.0") {
install(ContentNegotiation) {
json()
}
install(Authentication) {
jwt {
verifier(JwtConfig.verifier)
@ -26,6 +31,6 @@ fun main() {
}
}
configureUserRouting()
configureSerialization()
configureArMaterialRouting()
}.start(wait = true)
}

View File

@ -0,0 +1,28 @@
package com.ray650128.extension
import com.ray650128.model.dto.MaterialDto
import com.ray650128.model.dto.UserDto
import com.ray650128.model.pojo.Material
import com.ray650128.model.pojo.User
fun Material.toDto(): MaterialDto =
MaterialDto(
id = this.id.toString(),
ownerId = this.ownerId.toString(),
name = this.name,
path = this.path,
contentType = this.contentType,
fileTag = this.fileTag,
createAt = this.createAt,
updatedAt = this.updatedAt
)
fun MaterialDto.toMaterial(): Material =
Material(
name = this.name,
path = this.path,
contentType = this.contentType,
fileTag = this.fileTag,
createAt = this.createAt,
updatedAt = this.updatedAt
)

View File

@ -7,5 +7,6 @@ data class ErrorResponse(val message: String) {
companion object {
val NOT_FOUND_RESPONSE = ErrorResponse("Person was not found")
val BAD_REQUEST_RESPONSE = ErrorResponse("Invalid request")
val UNAUTHORIZED_RESPONSE = ErrorResponse("Unauthorized")
}
}

View File

@ -0,0 +1,16 @@
package com.ray650128.model.dto
import io.ktor.server.auth.*
import kotlinx.serialization.Serializable
@Serializable
data class MaterialDto(
val id: String? = null,
var ownerId: String? = null,
var name: String,
val path: String,
val contentType: String,
var fileTag: ArrayList<String>? = null,
var createAt: Long? = null,
var updatedAt: Long? = null
): Principal

View File

@ -0,0 +1,15 @@
package com.ray650128.model.pojo
import org.bson.codecs.pojo.annotations.BsonId
import org.litote.kmongo.Id
data class Material(
@BsonId var id: Id<Material>? = null,
var ownerId: Id<User>? = null,
var name: String,
var path: String,
var contentType: String,
var fileTag: ArrayList<String>? = null,
var createAt: Long? = null,
var updatedAt: Long? = null
)

View File

@ -0,0 +1,8 @@
package com.ray650128.model.pojo
import kotlinx.serialization.Serializable
@Serializable
data class NewMaterial(
var name: String
)

View File

@ -1,16 +1,128 @@
package com.ray650128.plugins
import io.ktor.serialization.kotlinx.json.*
import io.ktor.server.plugins.contentnegotiation.*
import io.ktor.server.response.*
import com.ray650128.extension.*
import com.ray650128.model.ErrorResponse
import com.ray650128.model.dto.MaterialDto
import com.ray650128.model.dto.UserDto
import com.ray650128.model.pojo.Material
import com.ray650128.model.pojo.NewMaterial
import com.ray650128.service.MaterialService
import com.ray650128.service.UserService
import io.ktor.http.*
import io.ktor.http.content.*
import io.ktor.server.application.*
import io.ktor.server.auth.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import kotlinx.serialization.json.Json
import org.litote.kmongo.Id
import java.io.File
fun Application.configureArMaterialRouting() {
val userService = UserService()
val materialService = MaterialService()
fun Application.configureSerialization() {
install(ContentNegotiation) {
json()
}
routing {
route("/upload") {
get("/{name}") {
val fileName = call.parameters["name"] ?: run {
call.sendNotFound(ErrorResponse.NOT_FOUND_RESPONSE)
return@get
}
println("[File]filename: $fileName")
val file = File("./upload/$fileName")
println("[File]file: ${file.absolutePath}")
if(!file.exists()) {
call.sendNotFound(ErrorResponse.NOT_FOUND_RESPONSE)
return@get
}
call.respondFile(file)
}
}
authenticate {
route("/api") {
route("/v1") {
route("/materials") {
post("/upload") {
val account = call.authentication.principal<UserDto>()?.account ?: run {
call.sendUnauthorized(ErrorResponse.UNAUTHORIZED_RESPONSE)
return@post
}
val user = userService.findByAccount(account) ?: run {
call.sendUnauthorized(ErrorResponse.UNAUTHORIZED_RESPONSE)
return@post
}
var materialId: String? = null
// retrieve all multipart data (suspending)
val multipart = call.receiveMultipart()
var params: NewMaterial? = null
multipart.forEachPart { part ->
// Check part type
when (part) {
is PartData.FormItem -> {
params = Json.decodeFromString(NewMaterial.serializer(), part.value)
println(part.value)
}
is PartData.FileItem -> {
// retrieve file name of upload
println("File content type: ${part.contentType?.contentType}/${part.contentType?.contentSubtype}")
val extensionName = when (part.contentType?.contentSubtype) {
"gltf-binary" -> "glb"
"gltf+json" -> "gltf"
else -> part.contentType?.contentSubtype
}
val name = "${System.currentTimeMillis()}.$extensionName"
val directory = File("./upload/")
if (!directory.exists()) {
directory.mkdir()
}
val file = File("${directory.path}/$name")
// use InputStream from part to save file
part.streamProvider().use { its ->
// copy the stream to the file with buffering
file.outputStream().buffered().use {
// note that this is blocking
its.copyTo(it)
}
}
val tmpMaterial = MaterialDto(
name = if (params == null) {
part.originalFileName!!
} else {
params!!.name
},
path = "/upload/$name",
contentType = "${part.contentType}",
createAt = System.currentTimeMillis()
).toMaterial().apply {
ownerId = user.id!!
}
materialId = materialService.create(tmpMaterial).toString()
}
else -> {}
}
// make sure to dispose of the part after use to prevent leaks
part.dispose()
}
if (materialId != null) {
val data = materialService.findById(materialId!!)?.toDto()
call.sendSuccess(data)
} else {
call.sendBadRequest(ErrorResponse("Add material fail."))
}
}
}
}
}
}
get("/json/kotlinx-serialization") {
call.respond(mapOf("hello" to "world"))
}

View File

@ -0,0 +1,45 @@
package com.ray650128.service
import com.ray650128.model.pojo.Material
import org.bson.types.ObjectId
import org.litote.kmongo.*
import org.litote.kmongo.id.toId
class MaterialService {
private val client = KMongo.createClient("mongodb://www.ray650128.com:27017")
private val database = client.getDatabase("ar_system")
private val userCollection = database.getCollection<Material>()
fun create(user: Material): Id<Material>? {
userCollection.insertOne(user)
return user.id
}
fun findAll(): List<Material> = userCollection.find().toList()
fun findById(id: String): Material? {
val bsonId: Id<Material> = ObjectId(id).toId()
return userCollection.findOne(Material::id eq bsonId)
}
fun findByName(name: String): List<Material> {
val caseSensitiveTypeSafeFilter = Material::name regex name
return userCollection.find(caseSensitiveTypeSafeFilter).toList()
}
fun updateById(id: String, request: Material): Boolean =
findById(id)?.let { user ->
val updateResult = userCollection.replaceOne(
user.copy(
name = request.name,
updatedAt = request.updatedAt
)
)
updateResult.modifiedCount == 1L
} ?: false
fun deleteById(id: String): Boolean {
val deleteResult = userCollection.deleteOneById(ObjectId(id))
return deleteResult.deletedCount == 1L
}
}