添加spring security、user实体类、数项测试
This commit is contained in:
parent
027774c545
commit
3ee64a5f69
14
build.gradle
14
build.gradle
@ -14,6 +14,12 @@ java {
|
||||
}
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvmToolchain {
|
||||
languageVersion = JavaLanguageVersion.of(21)
|
||||
}
|
||||
}
|
||||
|
||||
configurations {
|
||||
compileOnly {
|
||||
extendsFrom annotationProcessor
|
||||
@ -29,14 +35,22 @@ allprojects {
|
||||
maven { url 'https://maven.aliyun.com/repository/gradle-plugin' }
|
||||
maven { url 'https://maven.aliyun.com/repository/public' }
|
||||
maven { url 'https://maven.aliyun.com/repository/jcenter' }
|
||||
maven { url "https://maven.noelware.org" }
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'org.springframework.boot:spring-boot-starter-web'
|
||||
implementation("org.jetbrains.exposed:exposed-spring-boot-starter:0.52.0")
|
||||
implementation 'commons-codec:commons-codec'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-validation'
|
||||
implementation "org.noelware.charted.snowflake:snowflake:0.1-beta"
|
||||
implementation 'org.springframework.boot:spring-boot-starter-security'
|
||||
testImplementation 'org.springframework.security:spring-security-test'
|
||||
compileOnly 'org.projectlombok:lombok'
|
||||
annotationProcessor 'org.projectlombok:lombok'
|
||||
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||
runtimeOnly 'org.postgresql:postgresql'
|
||||
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
|
||||
}
|
||||
|
||||
|
6
src/main/java/dev/surl/surl/cfg/BaseConfiguration.kt
Normal file
6
src/main/java/dev/surl/surl/cfg/BaseConfiguration.kt
Normal file
@ -0,0 +1,6 @@
|
||||
package dev.surl.surl.cfg
|
||||
|
||||
import org.springframework.context.annotation.Configuration
|
||||
|
||||
@Configuration
|
||||
open class BaseConfiguration(val site: String = "https://surl.org")
|
@ -0,0 +1,27 @@
|
||||
package dev.surl.surl.cfg.security
|
||||
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
|
||||
import org.springframework.security.web.SecurityFilterChain
|
||||
import org.springframework.security.config.annotation.web.invoke
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
open class WebSecurityConfig {
|
||||
@Bean
|
||||
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
||||
|
||||
println("loading")
|
||||
http {
|
||||
authorizeHttpRequests {
|
||||
authorize(anyRequest, authenticated)
|
||||
}
|
||||
formLogin { }
|
||||
httpBasic {
|
||||
}
|
||||
}
|
||||
return http.build()
|
||||
}
|
||||
}
|
7
src/main/java/dev/surl/surl/common/Msg.kt
Normal file
7
src/main/java/dev/surl/surl/common/Msg.kt
Normal file
@ -0,0 +1,7 @@
|
||||
package dev.surl.surl.common
|
||||
|
||||
data class Msg(
|
||||
val code: Int = 0,
|
||||
val msg: String? = null,
|
||||
val value: Any? = null
|
||||
)
|
@ -6,5 +6,5 @@ import org.springframework.web.bind.annotation.RestController
|
||||
@RestController(value = "/")
|
||||
class DefaultFcontroller {
|
||||
@RequestMapping
|
||||
fun defaultMessage() = "Hello World!"
|
||||
fun defaultMessage() = 123
|
||||
}
|
29
src/main/java/dev/surl/surl/controller/RedirectController.kt
Normal file
29
src/main/java/dev/surl/surl/controller/RedirectController.kt
Normal file
@ -0,0 +1,29 @@
|
||||
package dev.surl.surl.controller
|
||||
|
||||
import dev.surl.surl.cfg.BaseConfiguration
|
||||
import dev.surl.surl.service.SurlService
|
||||
import jakarta.validation.Valid
|
||||
import org.hibernate.validator.constraints.Length
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.PathVariable
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import java.net.URI
|
||||
|
||||
@RestController
|
||||
class RedirectController {
|
||||
@GetMapping("/{key}")
|
||||
fun redirect(
|
||||
@PathVariable @Valid @Length(min = 11, max = 11, message = "Key must be 11 characters long") key: String,
|
||||
@Autowired service: SurlService,
|
||||
@Autowired cfg: BaseConfiguration
|
||||
): ResponseEntity<Any> {
|
||||
val redirectUrl = service.getUrlByKey(key)
|
||||
return if(redirectUrl.isBlank()) {
|
||||
ResponseEntity.status(302).location(URI.create(cfg.site)).build()
|
||||
} else {
|
||||
ResponseEntity.status(302).location(URI.create(redirectUrl)).build()
|
||||
}
|
||||
}
|
||||
}
|
31
src/main/java/dev/surl/surl/controller/SurlAddController.kt
Normal file
31
src/main/java/dev/surl/surl/controller/SurlAddController.kt
Normal file
@ -0,0 +1,31 @@
|
||||
package dev.surl.surl.controller
|
||||
|
||||
import dev.surl.surl.cfg.BaseConfiguration
|
||||
import dev.surl.surl.common.Msg
|
||||
import dev.surl.surl.dto.Surl
|
||||
import dev.surl.surl.service.SurlService
|
||||
import jakarta.validation.Valid
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
|
||||
import org.springframework.web.bind.annotation.GetMapping
|
||||
import org.springframework.web.bind.annotation.PostMapping
|
||||
import org.springframework.web.bind.annotation.RequestBody
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
|
||||
@RestController
|
||||
@EnableWebSecurity
|
||||
class SurlAddController {
|
||||
@PostMapping("/surl/add")
|
||||
fun addSurl(
|
||||
@Valid @RequestBody body: Surl, @Autowired service: SurlService, @Autowired cfg: BaseConfiguration
|
||||
): Any {
|
||||
return ResponseEntity(Msg(code = 0, value = "${cfg.site}/${service.addSurl(body.url ?: "")}"), HttpStatus.OK)
|
||||
}
|
||||
|
||||
@GetMapping("/login")
|
||||
fun login():String {
|
||||
return "loged in"
|
||||
}
|
||||
}
|
12
src/main/java/dev/surl/surl/dao/Surl.kt
Normal file
12
src/main/java/dev/surl/surl/dao/Surl.kt
Normal file
@ -0,0 +1,12 @@
|
||||
package dev.surl.surl.dao
|
||||
|
||||
import dev.surl.surl.dsl.Surls
|
||||
import org.jetbrains.exposed.dao.Entity
|
||||
import org.jetbrains.exposed.dao.EntityClass
|
||||
import org.jetbrains.exposed.dao.id.EntityID
|
||||
|
||||
class Surl(id: EntityID<Long>): Entity<Long>(id) {
|
||||
companion object: EntityClass<Long, Surl>(Surls)
|
||||
var url by Surls.url
|
||||
var user by User optionalReferencedOn Surls.user
|
||||
}
|
12
src/main/java/dev/surl/surl/dao/User.kt
Normal file
12
src/main/java/dev/surl/surl/dao/User.kt
Normal file
@ -0,0 +1,12 @@
|
||||
package dev.surl.surl.dao
|
||||
|
||||
import dev.surl.surl.dsl.Users
|
||||
import org.jetbrains.exposed.dao.LongEntity
|
||||
import org.jetbrains.exposed.dao.LongEntityClass
|
||||
import org.jetbrains.exposed.dao.id.EntityID
|
||||
|
||||
class User(id: EntityID<Long>): LongEntity(id) {
|
||||
companion object EntityClass: LongEntityClass<User>(Users)
|
||||
var username by Users.username
|
||||
var passwd by Users.passwd
|
||||
}
|
10
src/main/java/dev/surl/surl/dsl/Surls.kt
Normal file
10
src/main/java/dev/surl/surl/dsl/Surls.kt
Normal file
@ -0,0 +1,10 @@
|
||||
package dev.surl.surl.dsl
|
||||
|
||||
import org.jetbrains.exposed.dao.id.IdTable
|
||||
|
||||
object Surls: IdTable<Long>("surl") {
|
||||
override val id = long("id").entityId()
|
||||
val url = varchar("url", 2048).uniqueIndex()
|
||||
val user = reference("user", Users).nullable()
|
||||
override val primaryKey = PrimaryKey(id, name = "PK_surl_id")
|
||||
}
|
10
src/main/java/dev/surl/surl/dsl/Users.kt
Normal file
10
src/main/java/dev/surl/surl/dsl/Users.kt
Normal file
@ -0,0 +1,10 @@
|
||||
package dev.surl.surl.dsl
|
||||
|
||||
import org.jetbrains.exposed.dao.id.IdTable
|
||||
|
||||
object Users: IdTable<Long>("users") {
|
||||
override val id = long("id").entityId()
|
||||
val username = varchar("username", 256).uniqueIndex()
|
||||
val passwd = varchar("passwd", 256)
|
||||
override val primaryKey = PrimaryKey(id, name = "PK_user_id")
|
||||
}
|
12
src/main/java/dev/surl/surl/dto/SurlDto.kt
Normal file
12
src/main/java/dev/surl/surl/dto/SurlDto.kt
Normal file
@ -0,0 +1,12 @@
|
||||
package dev.surl.surl.dto
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import jakarta.validation.constraints.NotNull
|
||||
import org.hibernate.validator.constraints.Length
|
||||
|
||||
data class Surl(
|
||||
@JsonProperty("url")
|
||||
@get:NotNull(message = "url cannot be empty")
|
||||
@get:Length(min = 11, max = 2048, message = "url length invalid")
|
||||
val url: String?
|
||||
)
|
@ -0,0 +1,39 @@
|
||||
package dev.surl.surl.handler
|
||||
|
||||
import dev.surl.surl.common.Msg
|
||||
import org.springframework.http.HttpHeaders
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.http.HttpStatusCode
|
||||
import org.springframework.http.ResponseEntity
|
||||
import org.springframework.validation.method.MethodValidationException
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler
|
||||
import org.springframework.web.context.request.WebRequest
|
||||
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler
|
||||
|
||||
@ControllerAdvice
|
||||
class DefaultExceptionHandler : ResponseEntityExceptionHandler() {
|
||||
override fun handleMethodValidationException(
|
||||
ex: MethodValidationException, headers: HttpHeaders, status: HttpStatus, request: WebRequest
|
||||
): ResponseEntity<Any> {
|
||||
return ResponseEntity(Msg(code = -1, msg = ex.allValidationResults.joinToString(";")), status)
|
||||
}
|
||||
|
||||
override fun handleMethodArgumentNotValid(
|
||||
ex: MethodArgumentNotValidException, headers: HttpHeaders, status: HttpStatusCode, request: WebRequest
|
||||
): ResponseEntity<Any> {
|
||||
return ResponseEntity(
|
||||
Msg(code = -1, msg = ex.allErrors.joinToString(";") {
|
||||
it.defaultMessage?.toString() ?: "unknown invalid method argument"
|
||||
}), status
|
||||
)
|
||||
}
|
||||
|
||||
@ExceptionHandler(value = [Exception::class])
|
||||
fun handleException(
|
||||
ex: Exception, headers: HttpHeaders, status: HttpStatusCode, request: WebRequest
|
||||
): ResponseEntity<Msg> {
|
||||
return ResponseEntity(Msg(code = -1, msg = ex.message ?: "unknown error"), status)
|
||||
}
|
||||
}
|
33
src/main/java/dev/surl/surl/service/SurlService.kt
Normal file
33
src/main/java/dev/surl/surl/service/SurlService.kt
Normal file
@ -0,0 +1,33 @@
|
||||
package dev.surl.surl.service
|
||||
|
||||
import dev.surl.surl.dao.Surl
|
||||
import dev.surl.surl.dsl.Surls
|
||||
import dev.surl.surl.util.*
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
import org.springframework.stereotype.Service
|
||||
|
||||
@Service
|
||||
class SurlService {
|
||||
fun addSurl(baseurl: String): String = runBlocking {
|
||||
val id = genSnowflakeUID()
|
||||
Surl.new {
|
||||
this.id._value = id
|
||||
url = baseurl
|
||||
}
|
||||
return@runBlocking numberToKey(id)
|
||||
}
|
||||
|
||||
fun getUrlByKey(key: String): String {
|
||||
return transaction {
|
||||
val results = Surls.select(Surls.url).where {
|
||||
Surls.id eq keyToNumber(key)
|
||||
}.toList()
|
||||
if (results.isNotEmpty()) {
|
||||
return@transaction results.first()[Surls.url]
|
||||
} else {
|
||||
return@transaction ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
3
src/main/java/dev/surl/surl/service/UserService.kt
Normal file
3
src/main/java/dev/surl/surl/service/UserService.kt
Normal file
@ -0,0 +1,3 @@
|
||||
package dev.surl.surl.service
|
||||
|
||||
class UserService
|
@ -1,8 +1,11 @@
|
||||
package dev.surl.surl.util
|
||||
|
||||
import org.springframework.util.DigestUtils
|
||||
import org.apache.commons.codec.digest.DigestUtils
|
||||
|
||||
fun md5(str: String): String {
|
||||
val cksum = DigestUtils.md5Digest(str.toByteArray())
|
||||
return cksum.joinToString("") { "%02x".format(it) }
|
||||
return DigestUtils.md5Hex(str)
|
||||
}
|
||||
|
||||
fun sha1(str: String): String {
|
||||
return DigestUtils.sha1Hex(str)
|
||||
}
|
@ -1,15 +1,36 @@
|
||||
package dev.surl.surl.util
|
||||
|
||||
import java.util.concurrent.ThreadLocalRandom
|
||||
import org.noelware.charted.snowflake.Snowflake
|
||||
import kotlin.math.pow
|
||||
|
||||
private val CHARS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".toCharArray()
|
||||
private const val LENGTH = 16
|
||||
private val CHARS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!*().-_~".toCharArray()
|
||||
private const val LENGTH = 11
|
||||
private val snowflake = Snowflake()
|
||||
|
||||
fun genSurlUID(): String {
|
||||
return genSurlUID(LENGTH)
|
||||
fun numberToKey(number: Long): String {
|
||||
var num = number
|
||||
val sb = StringBuilder()
|
||||
for(i in LENGTH - 1 downTo 0) {
|
||||
val remainder = num % CHARS.size
|
||||
sb.append(CHARS[remainder.toInt()])
|
||||
num /= CHARS.size
|
||||
if(num == 0L) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return sb.reverse().toString()
|
||||
}
|
||||
|
||||
fun genSurlUID(length: Int): String {
|
||||
val random = ThreadLocalRandom.current()
|
||||
return CharArray(length) { CHARS[random.nextInt(CHARS.size)] }.concatToString()
|
||||
}
|
||||
fun keyToNumber(key: String): Long {
|
||||
var sum = 0L
|
||||
for(i in key.indices) {
|
||||
val char = key[i]
|
||||
val index = CHARS.indexOf(char)
|
||||
sum += index * (CHARS.size.toDouble().pow(key.length - i - 1).toLong())
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
suspend fun genSnowflakeUID(): Long {
|
||||
return snowflake.generate().value
|
||||
}
|
||||
|
@ -1,7 +1,37 @@
|
||||
server:
|
||||
port: 18888 #服务端口
|
||||
port: 18888
|
||||
spring:
|
||||
profiles:
|
||||
active: default
|
||||
application:
|
||||
name: surl # 应用名称
|
||||
name: surl
|
||||
main:
|
||||
banner-mode: off # 关闭banner
|
||||
banner-mode: off
|
||||
datasource:
|
||||
name: postgres
|
||||
driver-class-name: org.postgresql.Driver
|
||||
url: jdbc:postgresql://localhost:5432/postgres?reWriteBatchedInserts=true
|
||||
username: postgres
|
||||
password: f6059e64-889b-4f31-97a7-deaeb1a36d58
|
||||
hikari:
|
||||
connection-test-query: SELECT 1
|
||||
connection-timeout: 30000
|
||||
maximum-pool-size: 10
|
||||
jackson:
|
||||
deserialization:
|
||||
use-big-decimal-for-floats: true
|
||||
use-big-integer-for-ints: true
|
||||
time-zone: Asia/Shanghai
|
||||
serialization:
|
||||
indent-output: true
|
||||
exposed:
|
||||
show-sql: false
|
||||
generate-ddl: true
|
||||
security:
|
||||
user:
|
||||
name: admin
|
||||
password: 123
|
||||
logging:
|
||||
level:
|
||||
root: info
|
||||
Exposed: warn
|
28
src/test/java/dev/surl/surl/Benchmark.kt
Normal file
28
src/test/java/dev/surl/surl/Benchmark.kt
Normal file
@ -0,0 +1,28 @@
|
||||
package dev.surl.surl
|
||||
|
||||
import dev.surl.surl.service.SurlService
|
||||
import dev.surl.surl.util.genSnowflakeUID
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.boot.test.context.SpringBootTest
|
||||
|
||||
@SpringBootTest
|
||||
class Benchmark {
|
||||
@Test
|
||||
fun tpsTest(@Autowired service: SurlService) {
|
||||
val now = System.currentTimeMillis()
|
||||
runBlocking {
|
||||
for(i in 1..128) {
|
||||
launch {
|
||||
for (j in 1..100) {
|
||||
service.addSurl("https://surl.org/${genSnowflakeUID()}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
val timeEllapsed = System.currentTimeMillis() - now
|
||||
println("TPS: ${128 * 100.0 * 1000 / timeEllapsed}")
|
||||
}
|
||||
}
|
18
src/test/java/dev/surl/surl/SecurityTests.kt
Normal file
18
src/test/java/dev/surl/surl/SecurityTests.kt
Normal file
@ -0,0 +1,18 @@
|
||||
package dev.surl.surl
|
||||
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
|
||||
import org.springframework.boot.test.context.SpringBootTest
|
||||
import org.springframework.test.web.servlet.MockMvc
|
||||
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.*
|
||||
import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated
|
||||
|
||||
@SpringBootTest
|
||||
@AutoConfigureMockMvc
|
||||
class SecurityTests(@Autowired val mvc: MockMvc) {
|
||||
@Test
|
||||
fun `surl add page`() {
|
||||
mvc.perform(formLogin("/login").user("user").password("1233")).andExpect(authenticated())
|
||||
}
|
||||
}
|
@ -1,7 +1,8 @@
|
||||
package dev.surl.surl
|
||||
|
||||
import dev.surl.surl.util.genSurlUID
|
||||
import dev.surl.surl.util.keyToNumber
|
||||
import dev.surl.surl.util.md5
|
||||
import dev.surl.surl.util.numberToKey
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class UtilTests {
|
||||
@ -11,8 +12,8 @@ class UtilTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun randomUIDTest() {
|
||||
println(genSurlUID())
|
||||
println(genSurlUID(8))
|
||||
fun numberToUIDTest() {
|
||||
println(numberToKey(1810519439309799440))
|
||||
println(keyToNumber("0IYJbl*DiYE"))
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user