From 7e7c8b0698ab493354ad3ccfd4b2a6b831044780 Mon Sep 17 00:00:00 2001 From: Raymond Yang Date: Wed, 21 Jun 2023 15:47:52 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AF=A6=E4=BD=9C=E6=96=B0=E5=A2=9E=E3=80=81?= =?UTF-8?q?=E6=9F=A5=E8=A9=A2=E3=80=81=E5=88=AA=E9=99=A4=E8=A7=92=E8=89=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/com/ray650128/JwtConfig.kt | 8 +- .../ray650128/model/dto/GameCharacterDto.kt | 36 ++++++++ .../com/ray650128/model/pojo/GameCharacter.kt | 17 ++++ .../com/ray650128/model/pojo/NewCharacter.kt | 10 +++ .../com/ray650128/plugins/CharacterRouting.kt | 90 +++++++++++++++++-- .../kotlin/com/ray650128/plugins/Modules.kt | 7 +- .../com/ray650128/plugins/UserRouting.kt | 4 +- .../com/ray650128/service/CharacterService.kt | 45 ++++++++++ 8 files changed, 205 insertions(+), 12 deletions(-) create mode 100644 src/main/kotlin/com/ray650128/model/dto/GameCharacterDto.kt create mode 100644 src/main/kotlin/com/ray650128/model/pojo/GameCharacter.kt create mode 100644 src/main/kotlin/com/ray650128/model/pojo/NewCharacter.kt create mode 100644 src/main/kotlin/com/ray650128/service/CharacterService.kt diff --git a/src/main/kotlin/com/ray650128/JwtConfig.kt b/src/main/kotlin/com/ray650128/JwtConfig.kt index 1de625b..9eee23b 100644 --- a/src/main/kotlin/com/ray650128/JwtConfig.kt +++ b/src/main/kotlin/com/ray650128/JwtConfig.kt @@ -11,12 +11,13 @@ object JwtConfig { val jwtDomain = "com.ray650128" val jwtRealm = "pc re:dive rank table" val jwtSecret = "secret" - //private val validityInMs = 36_000_00 * 24 // 1 day - private const val validityInMs = 300000 // 5 min + private val validityInMs = 36_000_00 * 24 // 1 day + //private const val validityInMs = 300000 // 5 min private val algorithm = Algorithm.HMAC512(jwtSecret) val verifier: JWTVerifier = JWT .require(algorithm) + .withAudience(jwtAudience) .withIssuer(jwtDomain) .build() @@ -24,9 +25,10 @@ object JwtConfig { * Produce a token for this combination of name and password */ fun generateToken(user: User): String = JWT.create() - .withSubject("Authentication") + .withAudience(jwtAudience) .withIssuer(jwtDomain) .withClaim("account", user.account) + .withClaim("password", user.password) .withExpiresAt(getExpiration()) // optional .sign(algorithm) diff --git a/src/main/kotlin/com/ray650128/model/dto/GameCharacterDto.kt b/src/main/kotlin/com/ray650128/model/dto/GameCharacterDto.kt new file mode 100644 index 0000000..9f28a21 --- /dev/null +++ b/src/main/kotlin/com/ray650128/model/dto/GameCharacterDto.kt @@ -0,0 +1,36 @@ +package com.ray650128.model.dto + +import com.ray650128.model.pojo.GameCharacter +import kotlinx.serialization.Serializable + +@Serializable +data class CharacterDto( + val _id: String? = null, + val name: String, + val imgSrc: String, + val position: Int, + val standIndex: Int, + var createAt: Long? = null, + var updatedAt: Long? = null +) + +fun GameCharacter.toDto(baseUrl: String? = null): CharacterDto = + CharacterDto( + _id = this._id.toString(), + name = this.name, + imgSrc = if (baseUrl.isNullOrEmpty()) this.imgSrc else "$baseUrl${this.imgSrc}", + position = this.position, + standIndex = this.standIndex, + createAt = this.createAt, + updatedAt = this.updatedAt + ) + +fun CharacterDto.toGameCharacter(): GameCharacter = + GameCharacter( + name = this.name, + imgSrc = this.imgSrc, + position = this.position, + standIndex = this.standIndex, + createAt = this.createAt, + updatedAt = this.updatedAt + ) diff --git a/src/main/kotlin/com/ray650128/model/pojo/GameCharacter.kt b/src/main/kotlin/com/ray650128/model/pojo/GameCharacter.kt new file mode 100644 index 0000000..9ec208e --- /dev/null +++ b/src/main/kotlin/com/ray650128/model/pojo/GameCharacter.kt @@ -0,0 +1,17 @@ +package com.ray650128.model.pojo + +import kotlinx.serialization.Contextual +import kotlinx.serialization.Serializable +import org.litote.kmongo.Id +import org.litote.kmongo.newId + +@Serializable +data class GameCharacter( + @Contextual val _id: Id? = newId(), + val name: String, + val imgSrc: String, + val position: Int, + val standIndex: Int, + var createAt: Long? = null, + var updatedAt: Long? = null +) diff --git a/src/main/kotlin/com/ray650128/model/pojo/NewCharacter.kt b/src/main/kotlin/com/ray650128/model/pojo/NewCharacter.kt new file mode 100644 index 0000000..e8b8580 --- /dev/null +++ b/src/main/kotlin/com/ray650128/model/pojo/NewCharacter.kt @@ -0,0 +1,10 @@ +package com.ray650128.model.pojo + +import kotlinx.serialization.Serializable + +@Serializable +data class NewCharacter( + val name: String, + val position: Int, + val standIndex: Int +) diff --git a/src/main/kotlin/com/ray650128/plugins/CharacterRouting.kt b/src/main/kotlin/com/ray650128/plugins/CharacterRouting.kt index 4e2adfa..89b7910 100644 --- a/src/main/kotlin/com/ray650128/plugins/CharacterRouting.kt +++ b/src/main/kotlin/com/ray650128/plugins/CharacterRouting.kt @@ -1,28 +1,101 @@ package com.ray650128.plugins -import io.ktor.serialization.gson.* -import io.ktor.server.plugins.contentnegotiation.* +import com.google.gson.Gson +import com.ray650128.extensions.sendBadRequest +import com.ray650128.extensions.sendNotFound +import com.ray650128.extensions.sendSuccess +import com.ray650128.extensions.sendUnauthorized +import com.ray650128.model.ErrorResponse +import com.ray650128.model.User +import com.ray650128.model.dto.toDto +import com.ray650128.model.pojo.GameCharacter +import com.ray650128.model.pojo.NewCharacter +import com.ray650128.service.CharacterService +import io.ktor.http.content.* import io.ktor.server.response.* import io.ktor.server.application.* import io.ktor.server.auth.* +import io.ktor.server.request.* import io.ktor.server.routing.* +import io.ktor.server.util.* +import java.io.File fun Application.configureCharacter() { routing { route("/api") { route("/v1") { get("/characters") { - call.respond(mapOf("hello" to "world")) + val gameCharacterList = CharacterService.findAll().map(GameCharacter::toDto) + call.sendSuccess(gameCharacterList) } route("/character") { get("/{id}") { - call.respond(mapOf("hello" to "world")) + val id = call.parameters["id"].toString() + val character = CharacterService.findById(id)?.toDto() ?: run { + call.sendNotFound() + return@get + } + call.sendSuccess(character) } authenticate { post { + call.authentication.principal() ?: run { + call.sendUnauthorized() + return@post + } + var characterId: String? = null + // retrieve all multipart data (suspending) + val multipart = call.receiveMultipart() + var params: NewCharacter? = null + multipart.forEachPart { part -> + // Check part type + when (part) { + is PartData.FormItem -> { + params = Gson().fromJson(part.value, NewCharacter::class.java) + println(part.value) + } + is PartData.FileItem -> { + val extensionName = 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 tmpCharacter = GameCharacter( + name = params!!.name, + imgSrc = "/upload/$name", + position = params!!.position, + standIndex = params!!.standIndex, + createAt = System.currentTimeMillis() + ) + characterId = CharacterService.create(tmpCharacter).toString() + } + + else -> {} + } + // make sure to dispose of the part after use to prevent leaks + part.dispose() + } + if (characterId != null) { + val data = CharacterService.findById(characterId!!)?.toDto() + call.sendSuccess(data) + } else { + call.sendBadRequest(ErrorResponse("Add material fail.")) + } } put { @@ -30,7 +103,14 @@ fun Application.configureCharacter() { } delete("/{id}") { - + call.authentication.principal() ?: run { + call.sendUnauthorized() + return@delete + } + val id = call.parameters["id"].toString() + CharacterService.deleteById(id) + val gameCharacterList = CharacterService.findAll().map(GameCharacter::toDto) + call.sendSuccess(gameCharacterList) } } } diff --git a/src/main/kotlin/com/ray650128/plugins/Modules.kt b/src/main/kotlin/com/ray650128/plugins/Modules.kt index 15e610e..887c1fb 100644 --- a/src/main/kotlin/com/ray650128/plugins/Modules.kt +++ b/src/main/kotlin/com/ray650128/plugins/Modules.kt @@ -3,6 +3,7 @@ package com.ray650128.plugins import io.ktor.server.auth.* import io.ktor.server.auth.jwt.* import com.ray650128.JwtConfig +import com.ray650128.model.User import io.ktor.http.* import io.ktor.serialization.gson.* import io.ktor.server.application.* @@ -16,8 +17,10 @@ fun Application.configureModules() { realm = JwtConfig.jwtRealm verifier(JwtConfig.verifier) validate { credential -> - if (credential.payload.audience.contains(JwtConfig.jwtAudience)) { - JWTPrincipal(credential.payload) + val account = credential.payload.getClaim("account").asString() + val password = credential.payload.getClaim("password").asString() + if (account != "" && password != "") { + User(account = account, password = password) } else null } } diff --git a/src/main/kotlin/com/ray650128/plugins/UserRouting.kt b/src/main/kotlin/com/ray650128/plugins/UserRouting.kt index 6e17c9a..3bbf65e 100644 --- a/src/main/kotlin/com/ray650128/plugins/UserRouting.kt +++ b/src/main/kotlin/com/ray650128/plugins/UserRouting.kt @@ -43,8 +43,8 @@ fun Application.configureUser() { val password = md5encode(request.password) val user = UserService.findByLoginInfo(request.account, password) if (user != null) { - val token = JwtConfig.generateToken(request) - call.sendSuccess(LoginResult(request.account, token)) + val token = JwtConfig.generateToken(user) + call.sendSuccess(LoginResult(user.account, token)) } else { call.sendUnauthorized() } diff --git a/src/main/kotlin/com/ray650128/service/CharacterService.kt b/src/main/kotlin/com/ray650128/service/CharacterService.kt new file mode 100644 index 0000000..3e229a6 --- /dev/null +++ b/src/main/kotlin/com/ray650128/service/CharacterService.kt @@ -0,0 +1,45 @@ +package com.ray650128.service + +import com.ray650128.model.pojo.GameCharacter +import org.bson.types.ObjectId +import org.litote.kmongo.* +import org.litote.kmongo.id.toId + +object CharacterService { + private val client = KMongo.createClient("mongodb://ray650128:Zx650128!@www.ray650128.com:27017") + private val database = client.getDatabase("test_system") + private val characterCollection = database.getCollection() + + fun create(character: GameCharacter): Id? { + characterCollection.insertOne(character) + return character._id + } + + fun findAll(): List = characterCollection.find().toList() + + fun findById(id: String): GameCharacter? { + val bsonId: Id = ObjectId(id).toId() + return characterCollection.findOne(GameCharacter::_id eq bsonId) + } + + fun findByName(name: String): List { + val caseSensitiveTypeSafeFilter = GameCharacter::name regex name + return characterCollection.find(caseSensitiveTypeSafeFilter).toList() + } + + fun updateById(id: String, request: GameCharacter): Boolean = + findById(id)?.let { character -> + val updateResult = characterCollection.replaceOne( + character.copy( + name = request.name, + updatedAt = request.updatedAt + ) + ) + updateResult.modifiedCount == 1L + } ?: false + + fun deleteById(id: String): Boolean { + val deleteResult = characterCollection.deleteOneById(ObjectId(id)) + return deleteResult.deletedCount == 1L + } +} \ No newline at end of file