This commit is contained in:
车厘子 2022-10-09 13:15:00 +08:00
commit 2e6fb71ae2
26 changed files with 442 additions and 0 deletions

36
.gitignore vendored Normal file
View 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
View File

@ -0,0 +1,11 @@
# Ktor 项目模板
> Kotlin + Gradle + Ktor + Exposed + Docker
配置好啦
- Database
- Logging
- Routing
- Session
- WebSocket

68
build.gradle.kts Normal file
View 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
View 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
View 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

Binary file not shown.

View 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
View File

@ -0,0 +1 @@
rootProject.name = "cn.fadinglight.bill-ktor"

View 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()
}

View 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")
}

View 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?,
)

View 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
}
}

View 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,
)
}

View 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
}
}
}

View 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
}
}

View 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()
}
}
}

View 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()
}
}

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

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

View 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}") {}
}
}

View File

@ -0,0 +1,9 @@
package cn.fadinglight.routes
import io.ktor.server.routing.*
fun Route.labelRoute() {
route("/label") {
}
}

View 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")
}
}

View 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 ]
}
}

View File

@ -0,0 +1,4 @@
driverClassName=org.mariadb.jdbc.Driver
jdbcUrl=jdbc:mariadb://localhost:3306/bill
dataSource.user=root
dataSource.password=123456

View 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>

View 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())
}
}
}