init
This commit is contained in:
commit
2e6fb71ae2
36
.gitignore
vendored
Normal file
36
.gitignore
vendored
Normal file
|
@ -0,0 +1,36 @@
|
|||
.gradle
|
||||
build/
|
||||
!gradle/wrapper/gradle-wrapper.jar
|
||||
!**/src/main/**/build/
|
||||
!**/src/test/**/build/
|
||||
|
||||
### STS ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
bin/
|
||||
!**/src/main/**/bin/
|
||||
!**/src/test/**/bin/
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
out/
|
||||
!**/src/main/**/out/
|
||||
!**/src/test/**/out/
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
||||
|
||||
### VS Code ###
|
||||
.vscode/
|
11
README.md
Normal file
11
README.md
Normal file
|
@ -0,0 +1,11 @@
|
|||
# Ktor 项目模板
|
||||
|
||||
> Kotlin + Gradle + Ktor + Exposed + Docker
|
||||
|
||||
配置好啦
|
||||
|
||||
- Database
|
||||
- Logging
|
||||
- Routing
|
||||
- Session
|
||||
- WebSocket
|
68
build.gradle.kts
Normal file
68
build.gradle.kts
Normal file
|
@ -0,0 +1,68 @@
|
|||
val ktor_version: String by project
|
||||
val kotlin_version: String by project
|
||||
val logback_version: String by project
|
||||
val exposed_version: String by project
|
||||
|
||||
plugins {
|
||||
application
|
||||
kotlin("jvm") version "1.7.20"
|
||||
id("io.ktor.plugin") version "2.1.2"
|
||||
id("org.jetbrains.kotlin.plugin.serialization") version "1.7.20"
|
||||
id("com.github.johnrengelman.shadow") version "5.0.0"
|
||||
}
|
||||
|
||||
|
||||
group = "cn.fadinglight"
|
||||
version = "0.0.1"
|
||||
application {
|
||||
mainClass.set("cn.fadinglight.ApplicationKt")
|
||||
|
||||
val isDevelopment: Boolean = project.ext.has("development")
|
||||
applicationDefaultJvmArgs = listOf("-Dio.ktor.development=$isDevelopment")
|
||||
}
|
||||
|
||||
|
||||
tasks.withType<Jar> {
|
||||
manifest {
|
||||
attributes(
|
||||
mapOf(
|
||||
"Main-Class" to application.mainClass
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven("https://maven.aliyun.com/repository/public/")
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// Exposed ORM library
|
||||
implementation("org.jetbrains.exposed:exposed-core:$exposed_version")
|
||||
implementation("org.jetbrains.exposed:exposed-dao:$exposed_version")
|
||||
implementation("org.jetbrains.exposed:exposed-jdbc:$exposed_version")
|
||||
|
||||
implementation("com.zaxxer:HikariCP:5.0.1") // JDBC Connection Pool
|
||||
implementation("org.mariadb.jdbc:mariadb-java-client:3.0.8") // JDBC Connector for Mariadb
|
||||
|
||||
// Session
|
||||
implementation("io.ktor:ktor-server-sessions:$ktor_version")
|
||||
// Ktor
|
||||
implementation("io.ktor:ktor-server-call-logging:$ktor_version")
|
||||
implementation("io.ktor:ktor-server-content-negotiation-jvm:$ktor_version")
|
||||
implementation("io.ktor:ktor-server-core-jvm:$ktor_version")
|
||||
implementation("io.ktor:ktor-serialization-kotlinx-json-jvm:$ktor_version")
|
||||
implementation("io.ktor:ktor-server-compression-jvm:$ktor_version")
|
||||
implementation("io.ktor:ktor-server-netty-jvm:$ktor_version")
|
||||
implementation("io.ktor:ktor-server-locations:$ktor_version")
|
||||
|
||||
|
||||
implementation("ch.qos.logback:logback-classic:$logback_version")
|
||||
implementation("io.ktor:ktor-server-websockets-jvm:2.1.1")
|
||||
implementation("io.ktor:ktor-server-locations-jvm:2.1.1")
|
||||
|
||||
testImplementation("io.ktor:ktor-server-tests-jvm:$ktor_version")
|
||||
testImplementation("org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version")
|
||||
}
|
19
docker-compose.yaml
Normal file
19
docker-compose.yaml
Normal file
|
@ -0,0 +1,19 @@
|
|||
services:
|
||||
db:
|
||||
image: mariadb
|
||||
restart: always
|
||||
environment:
|
||||
MARIADB_ROOT_PASSWORD: 123456
|
||||
ports:
|
||||
- 3306:3306
|
||||
volumes:
|
||||
- db-data:/var/lib/mysql
|
||||
|
||||
adminer:
|
||||
image: adminer
|
||||
restart: always
|
||||
ports:
|
||||
- 8081:8080
|
||||
|
||||
volumes:
|
||||
db-data:
|
5
gradle.properties
Normal file
5
gradle.properties
Normal file
|
@ -0,0 +1,5 @@
|
|||
ktor_version=2.1.2
|
||||
kotlin_version=1.7.20
|
||||
logback_version=1.4.1
|
||||
kotlin.code.style=official
|
||||
exposed_version=0.39.2
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
5
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
5
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-7.5-all.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
1
settings.gradle.kts
Normal file
1
settings.gradle.kts
Normal file
|
@ -0,0 +1 @@
|
|||
rootProject.name = "cn.fadinglight.bill-ktor"
|
18
src/main/kotlin/cn/fadinglight/Application.kt
Normal file
18
src/main/kotlin/cn/fadinglight/Application.kt
Normal file
|
@ -0,0 +1,18 @@
|
|||
package cn.fadinglight
|
||||
|
||||
import cn.fadinglight.plugins.*
|
||||
import io.ktor.server.application.*
|
||||
|
||||
|
||||
fun main(args: Array<String>): Unit = io.ktor.server.netty.EngineMain.main(args)
|
||||
|
||||
fun Application.module() {
|
||||
configureAdministration()
|
||||
configureSerialization()
|
||||
configureHTTP()
|
||||
configureLogging()
|
||||
configureRouting()
|
||||
configureSession()
|
||||
configureWebSocket()
|
||||
configureDatabase()
|
||||
}
|
19
src/main/kotlin/cn/fadinglight/mapers/Schemas.kt
Normal file
19
src/main/kotlin/cn/fadinglight/mapers/Schemas.kt
Normal file
|
@ -0,0 +1,19 @@
|
|||
package cn.fadinglight.mapers
|
||||
|
||||
import org.jetbrains.exposed.dao.id.IntIdTable
|
||||
|
||||
object Bills : IntIdTable() {
|
||||
val type = integer("type")
|
||||
val date = varchar("date", 15)
|
||||
val money = integer("money")
|
||||
val cls = varchar("cls", 31)
|
||||
val label = varchar("label", 31)
|
||||
val options = varchar("options", 255)
|
||||
}
|
||||
|
||||
object Labels : IntIdTable() {
|
||||
val name = varchar("name", 15)
|
||||
val type = integer("type")
|
||||
val relativeId = integer("relative_id")
|
||||
val nums = integer("nums")
|
||||
}
|
15
src/main/kotlin/cn/fadinglight/models/Bill.kt
Normal file
15
src/main/kotlin/cn/fadinglight/models/Bill.kt
Normal file
|
@ -0,0 +1,15 @@
|
|||
package cn.fadinglight.models
|
||||
|
||||
enum class BillType {
|
||||
Consume,
|
||||
Income,
|
||||
}
|
||||
|
||||
data class Bill(
|
||||
var id: Int?,
|
||||
var type: BillType,
|
||||
val money: Int,
|
||||
val cls: String,
|
||||
val label: String,
|
||||
val options: String?,
|
||||
)
|
14
src/main/kotlin/cn/fadinglight/plugins/Administration.kt
Normal file
14
src/main/kotlin/cn/fadinglight/plugins/Administration.kt
Normal file
|
@ -0,0 +1,14 @@
|
|||
package cn.fadinglight.plugins
|
||||
|
||||
import io.ktor.server.engine.*
|
||||
import io.ktor.server.application.*
|
||||
|
||||
fun Application.configureAdministration() {
|
||||
install(ShutDownUrl.ApplicationCallPlugin) {
|
||||
// The URL that will be intercepted (you can also use the application.conf's ktor.deployment.shutdown.url key)
|
||||
shutDownUrl = "/ktor/application/shutdown"
|
||||
// A function that will be executed to get the exit code of the process
|
||||
exitCodeSupplier = { 0 } // ApplicationCall.() -> Int
|
||||
}
|
||||
|
||||
}
|
29
src/main/kotlin/cn/fadinglight/plugins/Database.kt
Normal file
29
src/main/kotlin/cn/fadinglight/plugins/Database.kt
Normal file
|
@ -0,0 +1,29 @@
|
|||
package cn.fadinglight.plugins
|
||||
|
||||
import cn.fadinglight.Bills
|
||||
import cn.fadinglight.Labels
|
||||
import com.zaxxer.hikari.HikariConfig
|
||||
import com.zaxxer.hikari.HikariDataSource
|
||||
import io.ktor.server.application.*
|
||||
import org.jetbrains.exposed.sql.Database
|
||||
import org.jetbrains.exposed.sql.SchemaUtils
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
|
||||
const val HIKARI_CONFIG_KEY = "ktor.hikariConfig"
|
||||
|
||||
fun Application.configureDatabase() {
|
||||
val configPath = environment.config.property(HIKARI_CONFIG_KEY).getString()
|
||||
val dbConfig = HikariConfig(configPath)
|
||||
val dataSource = HikariDataSource(dbConfig)
|
||||
Database.connect(dataSource)
|
||||
createTables()
|
||||
log.info("Initialized database")
|
||||
}
|
||||
|
||||
private fun createTables() = transaction {
|
||||
SchemaUtils.create(
|
||||
Bills,
|
||||
Labels,
|
||||
)
|
||||
}
|
||||
|
17
src/main/kotlin/cn/fadinglight/plugins/HTTP.kt
Normal file
17
src/main/kotlin/cn/fadinglight/plugins/HTTP.kt
Normal file
|
@ -0,0 +1,17 @@
|
|||
package cn.fadinglight.plugins
|
||||
|
||||
import io.ktor.server.plugins.compression.*
|
||||
import io.ktor.server.application.*
|
||||
|
||||
fun Application.configureHTTP() {
|
||||
install(Compression) {
|
||||
gzip {
|
||||
priority = 1.0
|
||||
}
|
||||
deflate {
|
||||
priority = 10.0
|
||||
minimumSize(1024) // condition
|
||||
}
|
||||
}
|
||||
|
||||
}
|
11
src/main/kotlin/cn/fadinglight/plugins/Logging.kt
Normal file
11
src/main/kotlin/cn/fadinglight/plugins/Logging.kt
Normal file
|
@ -0,0 +1,11 @@
|
|||
package cn.fadinglight.plugins
|
||||
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.server.plugins.callloging.*
|
||||
import org.slf4j.event.Level
|
||||
|
||||
fun Application.configureLogging() {
|
||||
install(CallLogging) {
|
||||
level = Level.INFO
|
||||
}
|
||||
}
|
15
src/main/kotlin/cn/fadinglight/plugins/Routing.kt
Normal file
15
src/main/kotlin/cn/fadinglight/plugins/Routing.kt
Normal file
|
@ -0,0 +1,15 @@
|
|||
package cn.fadinglight.plugins
|
||||
|
||||
import cn.fadinglight.routes.billRoute
|
||||
import cn.fadinglight.routes.labelRoute
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.server.routing.*
|
||||
|
||||
fun Application.configureRouting() {
|
||||
routing {
|
||||
route("/api/v1") {
|
||||
billRoute()
|
||||
labelRoute()
|
||||
}
|
||||
}
|
||||
}
|
11
src/main/kotlin/cn/fadinglight/plugins/Serialization.kt
Normal file
11
src/main/kotlin/cn/fadinglight/plugins/Serialization.kt
Normal file
|
@ -0,0 +1,11 @@
|
|||
package cn.fadinglight.plugins
|
||||
|
||||
import io.ktor.serialization.kotlinx.json.*
|
||||
import io.ktor.server.plugins.contentnegotiation.*
|
||||
import io.ktor.server.application.*
|
||||
|
||||
fun Application.configureSerialization() {
|
||||
install(ContentNegotiation) {
|
||||
json()
|
||||
}
|
||||
}
|
23
src/main/kotlin/cn/fadinglight/plugins/Session.kt
Normal file
23
src/main/kotlin/cn/fadinglight/plugins/Session.kt
Normal file
|
@ -0,0 +1,23 @@
|
|||
package cn.fadinglight.plugins
|
||||
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.server.response.*
|
||||
import io.ktor.server.routing.*
|
||||
import io.ktor.server.sessions.*
|
||||
|
||||
fun Application.configureSession() {
|
||||
install(Sessions) {
|
||||
cookie<UserSession>("user_session", SessionStorageMemory()) {
|
||||
cookie.path = "/"
|
||||
cookie.maxAgeInSeconds = 10
|
||||
}
|
||||
}
|
||||
routing {
|
||||
get("/sessions") {
|
||||
call.sessions.set(UserSession(id = "0", count = 0))
|
||||
call.respondText("Hello world!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class UserSession(val id: String, val count: Int)
|
37
src/main/kotlin/cn/fadinglight/plugins/WebSocket.kt
Normal file
37
src/main/kotlin/cn/fadinglight/plugins/WebSocket.kt
Normal file
|
@ -0,0 +1,37 @@
|
|||
package cn.fadinglight.plugins
|
||||
|
||||
import io.ktor.serialization.kotlinx.*
|
||||
import io.ktor.server.application.*
|
||||
import io.ktor.server.routing.*
|
||||
import io.ktor.server.websocket.*
|
||||
import io.ktor.websocket.*
|
||||
import kotlinx.coroutines.channels.consumeEach
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.time.Duration
|
||||
|
||||
fun Application.configureWebSocket() {
|
||||
install(WebSockets) {
|
||||
contentConverter = KotlinxWebsocketSerializationConverter(Json)
|
||||
|
||||
pingPeriod = Duration.ofSeconds(15)
|
||||
timeout = Duration.ofSeconds(15)
|
||||
maxFrameSize = Long.MAX_VALUE
|
||||
masking = false
|
||||
}
|
||||
|
||||
routing {
|
||||
webSocket("/customers/1") {
|
||||
sendSerialized(Customer(id = 1, firstName = "John", lastName = "Smith"))
|
||||
incoming.consumeEach {
|
||||
when (it) {
|
||||
is Frame.Text -> println(it.readText())
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class Customer(val id: Int, val firstName: String, val lastName: String)
|
13
src/main/kotlin/cn/fadinglight/routes/BillRoutes.kt
Normal file
13
src/main/kotlin/cn/fadinglight/routes/BillRoutes.kt
Normal file
|
@ -0,0 +1,13 @@
|
|||
package cn.fadinglight.routes
|
||||
|
||||
import io.ktor.server.routing.*
|
||||
|
||||
|
||||
fun Route.billRoute() {
|
||||
route("/bill") {
|
||||
get("/") {}
|
||||
get("/{year}/{month}") {}
|
||||
post("/create") {}
|
||||
delete("/{year}/{month}/{day}") {}
|
||||
}
|
||||
}
|
9
src/main/kotlin/cn/fadinglight/routes/LabelRoutes.kt
Normal file
9
src/main/kotlin/cn/fadinglight/routes/LabelRoutes.kt
Normal file
|
@ -0,0 +1,9 @@
|
|||
package cn.fadinglight.routes
|
||||
|
||||
import io.ktor.server.routing.*
|
||||
|
||||
fun Route.labelRoute() {
|
||||
route("/label") {
|
||||
|
||||
}
|
||||
}
|
15
src/main/kotlin/cn/fadinglight/services/BillService.kt
Normal file
15
src/main/kotlin/cn/fadinglight/services/BillService.kt
Normal file
|
@ -0,0 +1,15 @@
|
|||
package cn.fadinglight.services
|
||||
|
||||
import cn.fadinglight.models.Bill
|
||||
|
||||
interface BillService {
|
||||
fun getMonthBills(year: String, month: String): List<Bill>
|
||||
|
||||
}
|
||||
|
||||
|
||||
class BillServiceImpl : BillService {
|
||||
override fun getMonthBills(year: String, month: String): List<Bill> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
15
src/main/resources/application.conf
Normal file
15
src/main/resources/application.conf
Normal file
|
@ -0,0 +1,15 @@
|
|||
ktor {
|
||||
development = true
|
||||
|
||||
deployment {
|
||||
port = 8080
|
||||
port = ${?PORT}
|
||||
watch = [ bill-ktor ]
|
||||
}
|
||||
|
||||
hikariConfig = "/db.properties"
|
||||
|
||||
application {
|
||||
modules = [ cn.fadinglight.ApplicationKt.module ]
|
||||
}
|
||||
}
|
4
src/main/resources/db.properties
Normal file
4
src/main/resources/db.properties
Normal file
|
@ -0,0 +1,4 @@
|
|||
driverClassName=org.mariadb.jdbc.Driver
|
||||
jdbcUrl=jdbc:mariadb://localhost:3306/bill
|
||||
dataSource.user=root
|
||||
dataSource.password=123456
|
12
src/main/resources/logback.xml
Normal file
12
src/main/resources/logback.xml
Normal file
|
@ -0,0 +1,12 @@
|
|||
<configuration>
|
||||
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
<root level="trace">
|
||||
<appender-ref ref="STDOUT"/>
|
||||
</root>
|
||||
<logger name="io.netty" level="INFO"/>
|
||||
<logger name="com.zaxxer.hikari" level="ERROR"/>
|
||||
</configuration>
|
20
src/test/kotlin/cn/fadinglight/ApplicationTest.kt
Normal file
20
src/test/kotlin/cn/fadinglight/ApplicationTest.kt
Normal file
|
@ -0,0 +1,20 @@
|
|||
package cn.fadinglight
|
||||
|
||||
import io.ktor.http.*
|
||||
import io.ktor.client.request.*
|
||||
import kotlin.test.*
|
||||
import io.ktor.server.testing.*
|
||||
import cn.fadinglight.plugins.*
|
||||
|
||||
class ApplicationTest {
|
||||
@Test
|
||||
fun testRoot() = testApplication {
|
||||
application {
|
||||
configureRouting()
|
||||
}
|
||||
client.get("/").apply {
|
||||
assertEquals(HttpStatusCode.OK, status)
|
||||
// assertEquals("Hello World!", bodyAsText())
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user