實作新增、查詢、刪除角色

This commit is contained in:
Raymond Yang 2023-06-21 15:47:52 +08:00
parent d537ec1f76
commit 7e7c8b0698
8 changed files with 205 additions and 12 deletions

View File

@ -11,12 +11,13 @@ object JwtConfig {
val jwtDomain = "com.ray650128" val jwtDomain = "com.ray650128"
val jwtRealm = "pc re:dive rank table" val jwtRealm = "pc re:dive rank table"
val jwtSecret = "secret" val jwtSecret = "secret"
//private val validityInMs = 36_000_00 * 24 // 1 day private val validityInMs = 36_000_00 * 24 // 1 day
private const val validityInMs = 300000 // 5 min //private const val validityInMs = 300000 // 5 min
private val algorithm = Algorithm.HMAC512(jwtSecret) private val algorithm = Algorithm.HMAC512(jwtSecret)
val verifier: JWTVerifier = JWT val verifier: JWTVerifier = JWT
.require(algorithm) .require(algorithm)
.withAudience(jwtAudience)
.withIssuer(jwtDomain) .withIssuer(jwtDomain)
.build() .build()
@ -24,9 +25,10 @@ object JwtConfig {
* Produce a token for this combination of name and password * Produce a token for this combination of name and password
*/ */
fun generateToken(user: User): String = JWT.create() fun generateToken(user: User): String = JWT.create()
.withSubject("Authentication") .withAudience(jwtAudience)
.withIssuer(jwtDomain) .withIssuer(jwtDomain)
.withClaim("account", user.account) .withClaim("account", user.account)
.withClaim("password", user.password)
.withExpiresAt(getExpiration()) // optional .withExpiresAt(getExpiration()) // optional
.sign(algorithm) .sign(algorithm)

View File

@ -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
)

View File

@ -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<GameCharacter>? = newId(),
val name: String,
val imgSrc: String,
val position: Int,
val standIndex: Int,
var createAt: Long? = null,
var updatedAt: Long? = null
)

View File

@ -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
)

View File

@ -1,28 +1,101 @@
package com.ray650128.plugins package com.ray650128.plugins
import io.ktor.serialization.gson.* import com.google.gson.Gson
import io.ktor.server.plugins.contentnegotiation.* 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.response.*
import io.ktor.server.application.* import io.ktor.server.application.*
import io.ktor.server.auth.* import io.ktor.server.auth.*
import io.ktor.server.request.*
import io.ktor.server.routing.* import io.ktor.server.routing.*
import io.ktor.server.util.*
import java.io.File
fun Application.configureCharacter() { fun Application.configureCharacter() {
routing { routing {
route("/api") { route("/api") {
route("/v1") { route("/v1") {
get("/characters") { get("/characters") {
call.respond(mapOf("hello" to "world")) val gameCharacterList = CharacterService.findAll().map(GameCharacter::toDto)
call.sendSuccess(gameCharacterList)
} }
route("/character") { route("/character") {
get("/{id}") { 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 { authenticate {
post { post {
call.authentication.principal<User>() ?: 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 { put {
@ -30,7 +103,14 @@ fun Application.configureCharacter() {
} }
delete("/{id}") { delete("/{id}") {
call.authentication.principal<User>() ?: run {
call.sendUnauthorized()
return@delete
}
val id = call.parameters["id"].toString()
CharacterService.deleteById(id)
val gameCharacterList = CharacterService.findAll().map(GameCharacter::toDto)
call.sendSuccess(gameCharacterList)
} }
} }
} }

View File

@ -3,6 +3,7 @@ package com.ray650128.plugins
import io.ktor.server.auth.* import io.ktor.server.auth.*
import io.ktor.server.auth.jwt.* import io.ktor.server.auth.jwt.*
import com.ray650128.JwtConfig import com.ray650128.JwtConfig
import com.ray650128.model.User
import io.ktor.http.* import io.ktor.http.*
import io.ktor.serialization.gson.* import io.ktor.serialization.gson.*
import io.ktor.server.application.* import io.ktor.server.application.*
@ -16,8 +17,10 @@ fun Application.configureModules() {
realm = JwtConfig.jwtRealm realm = JwtConfig.jwtRealm
verifier(JwtConfig.verifier) verifier(JwtConfig.verifier)
validate { credential -> validate { credential ->
if (credential.payload.audience.contains(JwtConfig.jwtAudience)) { val account = credential.payload.getClaim("account").asString()
JWTPrincipal(credential.payload) val password = credential.payload.getClaim("password").asString()
if (account != "" && password != "") {
User(account = account, password = password)
} else null } else null
} }
} }

View File

@ -43,8 +43,8 @@ fun Application.configureUser() {
val password = md5encode(request.password) val password = md5encode(request.password)
val user = UserService.findByLoginInfo(request.account, password) val user = UserService.findByLoginInfo(request.account, password)
if (user != null) { if (user != null) {
val token = JwtConfig.generateToken(request) val token = JwtConfig.generateToken(user)
call.sendSuccess(LoginResult(request.account, token)) call.sendSuccess(LoginResult(user.account, token))
} else { } else {
call.sendUnauthorized() call.sendUnauthorized()
} }

View File

@ -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<GameCharacter>()
fun create(character: GameCharacter): Id<GameCharacter>? {
characterCollection.insertOne(character)
return character._id
}
fun findAll(): List<GameCharacter> = characterCollection.find().toList()
fun findById(id: String): GameCharacter? {
val bsonId: Id<GameCharacter> = ObjectId(id).toId()
return characterCollection.findOne(GameCharacter::_id eq bsonId)
}
fun findByName(name: String): List<GameCharacter> {
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
}
}