初步加上帳號服務
This commit is contained in:
parent
7c2839cade
commit
169278fc1d
@ -31,4 +31,6 @@ dependencies {
|
||||
testImplementation("io.ktor:ktor-server-tests-jvm:$ktor_version")
|
||||
testImplementation("org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version")
|
||||
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")
|
||||
}
|
||||
@ -1,12 +1,31 @@
|
||||
package com.ray650128
|
||||
|
||||
import com.ray650128.dto.UserDto
|
||||
import com.ray650128.model.User
|
||||
import com.ray650128.plugins.configureRouting
|
||||
import com.ray650128.plugins.configureSerialization
|
||||
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.*
|
||||
|
||||
fun main() {
|
||||
embeddedServer(Netty, port = 8080, host = "0.0.0.0") {
|
||||
install(Authentication) {
|
||||
jwt {
|
||||
verifier(JwtConfig.verifier)
|
||||
realm = JwtConfig.myRealm
|
||||
validate {
|
||||
val name = it.payload.getClaim("account").asString()
|
||||
if (name != null) {
|
||||
UserDto(account = name, password = "")
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
configureRouting()
|
||||
configureSerialization()
|
||||
}.start(wait = true)
|
||||
|
||||
35
src/main/kotlin/com/ray650128/JwtConfig.kt
Normal file
35
src/main/kotlin/com/ray650128/JwtConfig.kt
Normal file
@ -0,0 +1,35 @@
|
||||
package com.ray650128
|
||||
|
||||
import com.auth0.jwt.JWT
|
||||
import com.auth0.jwt.JWTVerifier
|
||||
import com.auth0.jwt.algorithms.Algorithm
|
||||
import com.ray650128.dto.UserDto
|
||||
import java.util.*
|
||||
|
||||
object JwtConfig {
|
||||
val secret = "my-secret"
|
||||
val issuer = "com.ray650128"
|
||||
val myRealm = "com.ray650128"
|
||||
private val validityInMs = 36_000_00 * 24 // 1 day
|
||||
private val algorithm = Algorithm.HMAC512(secret)
|
||||
|
||||
val verifier: JWTVerifier = JWT
|
||||
.require(algorithm)
|
||||
.withIssuer(issuer)
|
||||
.build()
|
||||
|
||||
/**
|
||||
* Produce a token for this combination of name and password
|
||||
*/
|
||||
fun generateToken(user: UserDto): String = JWT.create()
|
||||
.withSubject("Authentication")
|
||||
.withIssuer(issuer)
|
||||
.withClaim("account", user.account)
|
||||
//.withExpiresAt(getExpiration()) // optional
|
||||
.sign(algorithm)
|
||||
|
||||
/**
|
||||
* Calculate the expiration Date based on current time + the given validity
|
||||
*/
|
||||
private fun getExpiration() = Date(System.currentTimeMillis() + validityInMs)
|
||||
}
|
||||
@ -1,10 +0,0 @@
|
||||
package com.ray650128.dto
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class PersonDto(
|
||||
val id: String? = null,
|
||||
val name: String,
|
||||
val age: Int
|
||||
)
|
||||
15
src/main/kotlin/com/ray650128/dto/UserDto.kt
Normal file
15
src/main/kotlin/com/ray650128/dto/UserDto.kt
Normal file
@ -0,0 +1,15 @@
|
||||
package com.ray650128.dto
|
||||
|
||||
import io.ktor.server.auth.*
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class UserDto(
|
||||
val id: String? = null,
|
||||
val account: String,
|
||||
val password: String,
|
||||
val name: String? = null,
|
||||
var token: String? = null,
|
||||
var createAt: Long? = null,
|
||||
var updatedAt: Long? = null
|
||||
): Principal
|
||||
@ -1,17 +0,0 @@
|
||||
package com.ray650128.extension
|
||||
|
||||
import com.ray650128.dto.PersonDto
|
||||
import com.ray650128.model.Person
|
||||
|
||||
fun Person.toDto(): PersonDto =
|
||||
PersonDto(
|
||||
id = this.id.toString(),
|
||||
name = this.name,
|
||||
age = this.age
|
||||
)
|
||||
|
||||
fun PersonDto.toPerson(): Person =
|
||||
Person(
|
||||
name = this.name,
|
||||
age = this.age
|
||||
)
|
||||
25
src/main/kotlin/com/ray650128/extension/UserExtension.kt
Normal file
25
src/main/kotlin/com/ray650128/extension/UserExtension.kt
Normal file
@ -0,0 +1,25 @@
|
||||
package com.ray650128.extension
|
||||
|
||||
import com.ray650128.dto.UserDto
|
||||
import com.ray650128.model.User
|
||||
|
||||
fun User.toDto(): UserDto =
|
||||
UserDto(
|
||||
id = this.id.toString(),
|
||||
account = this.account,
|
||||
password = this.password,
|
||||
name = this.name,
|
||||
token = this.token,
|
||||
createAt = this.createAt,
|
||||
updatedAt = this.updatedAt
|
||||
)
|
||||
|
||||
fun UserDto.toUser(): User =
|
||||
User(
|
||||
account = this.account,
|
||||
password = this.password,
|
||||
name = this.name,
|
||||
token = this.token,
|
||||
createAt = this.createAt,
|
||||
updatedAt = this.updatedAt
|
||||
)
|
||||
9
src/main/kotlin/com/ray650128/model/LoginResult.kt
Normal file
9
src/main/kotlin/com/ray650128/model/LoginResult.kt
Normal file
@ -0,0 +1,9 @@
|
||||
package com.ray650128.model
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class LoginResult(
|
||||
var account: String,
|
||||
var token: String
|
||||
)
|
||||
@ -1,11 +0,0 @@
|
||||
package com.ray650128.model
|
||||
|
||||
import org.bson.codecs.pojo.annotations.BsonId
|
||||
import org.litote.kmongo.Id
|
||||
|
||||
data class Person(
|
||||
@BsonId
|
||||
val id: Id<Person>? = null,
|
||||
val name: String,
|
||||
val age: Int
|
||||
)
|
||||
15
src/main/kotlin/com/ray650128/model/User.kt
Normal file
15
src/main/kotlin/com/ray650128/model/User.kt
Normal file
@ -0,0 +1,15 @@
|
||||
package com.ray650128.model
|
||||
|
||||
import org.bson.codecs.pojo.annotations.BsonId
|
||||
import org.litote.kmongo.Id
|
||||
|
||||
data class User(
|
||||
@BsonId
|
||||
val id: Id<User>? = null,
|
||||
val account: String,
|
||||
val password: String,
|
||||
var name: String? = null,
|
||||
var token: String? = null,
|
||||
var createAt: Long? = null,
|
||||
var updatedAt: Long? = null
|
||||
)
|
||||
@ -1,38 +1,92 @@
|
||||
package com.ray650128.plugins
|
||||
|
||||
import com.ray650128.dto.PersonDto
|
||||
import com.ray650128.JwtConfig
|
||||
import com.ray650128.dto.UserDto
|
||||
import com.ray650128.extension.toDto
|
||||
import com.ray650128.extension.toPerson
|
||||
import com.ray650128.extension.toUser
|
||||
import com.ray650128.model.ErrorResponse
|
||||
import com.ray650128.model.Person
|
||||
import com.ray650128.service.PersonService
|
||||
import com.ray650128.model.LoginResult
|
||||
import com.ray650128.model.User
|
||||
import com.ray650128.service.UserService
|
||||
import io.ktor.http.*
|
||||
import io.ktor.server.routing.*
|
||||
import io.ktor.server.response.*
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.server.auth.*
|
||||
import io.ktor.server.request.*
|
||||
|
||||
fun Application.configureRouting() {
|
||||
val service = PersonService()
|
||||
val service = UserService()
|
||||
|
||||
routing {
|
||||
get("/") {
|
||||
call.respondText("Hello World!")
|
||||
}
|
||||
|
||||
route("/person") {
|
||||
post {
|
||||
val request = call.receive<PersonDto>()
|
||||
val person = request.toPerson()
|
||||
service.create(person)
|
||||
?.let { userId ->
|
||||
call.response.headers.append("My-User-Id-Header", userId.toString())
|
||||
call.respond(HttpStatusCode.Created)
|
||||
} ?: call.respond(HttpStatusCode.BadRequest, ErrorResponse.BAD_REQUEST_RESPONSE)
|
||||
route("/api") {
|
||||
route("/v1") {
|
||||
post("/register") {
|
||||
val request = call.receive<UserDto>()
|
||||
val newToken = JwtConfig.generateToken(request)
|
||||
val user = request.toUser().apply {
|
||||
token = newToken
|
||||
createAt = System.currentTimeMillis()
|
||||
}
|
||||
service.create(user)
|
||||
?.let { userId ->
|
||||
call.response.headers.append("My-User-Id-Header", userId.toString())
|
||||
call.respond(HttpStatusCode.Created, LoginResult(request.account, newToken))
|
||||
} ?: call.respond(HttpStatusCode.BadRequest, ErrorResponse.BAD_REQUEST_RESPONSE)
|
||||
}
|
||||
|
||||
post("/login") {
|
||||
val request = call.receive<UserDto>()
|
||||
val user = service.findByLoginInfo(request.account, request.password)
|
||||
if (user != null) {
|
||||
var token = user.token
|
||||
if (token.isNullOrEmpty()) {
|
||||
token = JwtConfig.generateToken(request)
|
||||
user.token = token
|
||||
user.updatedAt = System.currentTimeMillis()
|
||||
service.updateById(user.id.toString(), user)
|
||||
}
|
||||
call.respond(HttpStatusCode.OK, LoginResult(request.account, token!!))
|
||||
} else {
|
||||
call.respond(
|
||||
status = HttpStatusCode.Unauthorized,
|
||||
message = mapOf("message" to "Account or Password wrong.")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
authenticate {
|
||||
post("/logout") {
|
||||
val account = call.authentication.principal<UserDto>()?.account ?: run {
|
||||
call.respond(
|
||||
status = HttpStatusCode.Unauthorized,
|
||||
message = mapOf("message" to "token wrong")
|
||||
)
|
||||
return@post
|
||||
}
|
||||
val user = service.findByAccount(account) ?: run {
|
||||
call.respond(
|
||||
status = HttpStatusCode.Unauthorized,
|
||||
message = mapOf("message" to "token wrong")
|
||||
)
|
||||
return@post
|
||||
}
|
||||
user.apply {
|
||||
token = null
|
||||
updatedAt = System.currentTimeMillis()
|
||||
}
|
||||
service.updateById(user.id.toString(), user)
|
||||
call.respond(HttpStatusCode.OK, "User has logged out")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get {
|
||||
val peopleList = service.findAll().map(Person::toDto)
|
||||
val peopleList = service.findAll().map(User::toDto)
|
||||
call.respond(peopleList)
|
||||
}
|
||||
|
||||
@ -45,15 +99,15 @@ fun Application.configureRouting() {
|
||||
|
||||
get("/search") {
|
||||
val name = call.request.queryParameters["name"].toString()
|
||||
val foundPeople = service.findByName(name).map(Person::toDto)
|
||||
val foundPeople = service.findByName(name).map(User::toDto)
|
||||
call.respond(foundPeople)
|
||||
}
|
||||
|
||||
put("/{id}") {
|
||||
val id = call.parameters["id"].toString()
|
||||
val personRequest = call.receive<PersonDto>()
|
||||
val person = personRequest.toPerson()
|
||||
val updatedSuccessfully = service.updateById(id, person)
|
||||
val userRequest = call.receive<UserDto>()
|
||||
val user = userRequest.toUser()
|
||||
val updatedSuccessfully = service.updateById(id, user)
|
||||
if (updatedSuccessfully) {
|
||||
call.respond(HttpStatusCode.NoContent)
|
||||
} else {
|
||||
|
||||
@ -1,40 +0,0 @@
|
||||
package com.ray650128.service
|
||||
|
||||
import com.ray650128.model.Person
|
||||
import org.bson.types.ObjectId
|
||||
import org.litote.kmongo.*
|
||||
import org.litote.kmongo.id.toId
|
||||
|
||||
class PersonService {
|
||||
private val client = KMongo.createClient("mongodb://www.ray650128.com:27017")
|
||||
private val database = client.getDatabase("person")
|
||||
private val personCollection = database.getCollection<Person>()
|
||||
|
||||
fun create(person: Person): Id<Person>? {
|
||||
personCollection.insertOne(person)
|
||||
return person.id
|
||||
}
|
||||
|
||||
fun findAll(): List<Person> = personCollection.find().toList()
|
||||
|
||||
fun findById(id: String): Person? {
|
||||
val bsonId: Id<Person> = ObjectId(id).toId()
|
||||
return personCollection.findOne(Person::id eq bsonId)
|
||||
}
|
||||
|
||||
fun findByName(name: String): List<Person> {
|
||||
val caseSensitiveTypeSafeFilter = Person::name regex name
|
||||
return personCollection.find(caseSensitiveTypeSafeFilter).toList()
|
||||
}
|
||||
|
||||
fun updateById(id: String, request: Person): Boolean =
|
||||
findById(id)?.let { person ->
|
||||
val updateResult = personCollection.replaceOne(person.copy(name = request.name, age = request.age))
|
||||
updateResult.modifiedCount == 1L
|
||||
} ?: false
|
||||
|
||||
fun deleteById(id: String): Boolean {
|
||||
val deleteResult = personCollection.deleteOneById(ObjectId(id))
|
||||
return deleteResult.deletedCount == 1L
|
||||
}
|
||||
}
|
||||
57
src/main/kotlin/com/ray650128/service/UserService.kt
Normal file
57
src/main/kotlin/com/ray650128/service/UserService.kt
Normal file
@ -0,0 +1,57 @@
|
||||
package com.ray650128.service
|
||||
|
||||
import com.ray650128.model.User
|
||||
import org.bson.types.ObjectId
|
||||
import org.litote.kmongo.*
|
||||
import org.litote.kmongo.id.toId
|
||||
|
||||
class UserService {
|
||||
private val client = KMongo.createClient("mongodb://www.ray650128.com:27017")
|
||||
private val database = client.getDatabase("users")
|
||||
private val userCollection = database.getCollection<User>()
|
||||
|
||||
fun create(user: User): Id<User>? {
|
||||
userCollection.insertOne(user)
|
||||
return user.id
|
||||
}
|
||||
|
||||
fun findAll(): List<User> = userCollection.find().toList()
|
||||
|
||||
fun findById(id: String): User? {
|
||||
val bsonId: Id<User> = ObjectId(id).toId()
|
||||
return userCollection.findOne(User::id eq bsonId)
|
||||
}
|
||||
|
||||
fun findByName(name: String): List<User> {
|
||||
val caseSensitiveTypeSafeFilter = User::name regex name
|
||||
return userCollection.find(caseSensitiveTypeSafeFilter).toList()
|
||||
}
|
||||
|
||||
fun findByAccount(account: String): User? {
|
||||
return userCollection.findOne(User::account eq account)
|
||||
}
|
||||
|
||||
fun findByLoginInfo(account: String, password: String): User? {
|
||||
return userCollection.findOne(
|
||||
User::account eq account,
|
||||
User::password eq password
|
||||
)
|
||||
}
|
||||
|
||||
fun updateById(id: String, request: User): Boolean =
|
||||
findById(id)?.let { user ->
|
||||
val updateResult = userCollection.replaceOne(
|
||||
user.copy(
|
||||
name = request.name,
|
||||
token = request.token,
|
||||
updatedAt = request.updatedAt
|
||||
)
|
||||
)
|
||||
updateResult.modifiedCount == 1L
|
||||
} ?: false
|
||||
|
||||
fun deleteById(id: String): Boolean {
|
||||
val deleteResult = userCollection.deleteOneById(ObjectId(id))
|
||||
return deleteResult.deletedCount == 1L
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user