commit 2e6fb71ae219b2b04ec885e569401dad99db3fb7 Author: 车厘子 Date: Sun Oct 9 13:15:00 2022 +0800 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c426c32 --- /dev/null +++ b/.gitignore @@ -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/ \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..34cb194 --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +# Ktor 项目模板 + +> Kotlin + Gradle + Ktor + Exposed + Docker + +配置好啦 + +- Database +- Logging +- Routing +- Session +- WebSocket \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..4ab3ea1 --- /dev/null +++ b/build.gradle.kts @@ -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 { + 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") +} \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..073c36d --- /dev/null +++ b/docker-compose.yaml @@ -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: \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..bb09206 --- /dev/null +++ b/gradle.properties @@ -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 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..7454180 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..06f10b1 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -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 diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..218c00f --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1 @@ +rootProject.name = "cn.fadinglight.bill-ktor" diff --git a/src/main/kotlin/cn/fadinglight/Application.kt b/src/main/kotlin/cn/fadinglight/Application.kt new file mode 100644 index 0000000..6bb89ac --- /dev/null +++ b/src/main/kotlin/cn/fadinglight/Application.kt @@ -0,0 +1,18 @@ +package cn.fadinglight + +import cn.fadinglight.plugins.* +import io.ktor.server.application.* + + +fun main(args: Array): Unit = io.ktor.server.netty.EngineMain.main(args) + +fun Application.module() { + configureAdministration() + configureSerialization() + configureHTTP() + configureLogging() + configureRouting() + configureSession() + configureWebSocket() + configureDatabase() +} \ No newline at end of file diff --git a/src/main/kotlin/cn/fadinglight/mapers/Schemas.kt b/src/main/kotlin/cn/fadinglight/mapers/Schemas.kt new file mode 100644 index 0000000..5c0dac3 --- /dev/null +++ b/src/main/kotlin/cn/fadinglight/mapers/Schemas.kt @@ -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") +} diff --git a/src/main/kotlin/cn/fadinglight/models/Bill.kt b/src/main/kotlin/cn/fadinglight/models/Bill.kt new file mode 100644 index 0000000..cb61df6 --- /dev/null +++ b/src/main/kotlin/cn/fadinglight/models/Bill.kt @@ -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?, +) diff --git a/src/main/kotlin/cn/fadinglight/plugins/Administration.kt b/src/main/kotlin/cn/fadinglight/plugins/Administration.kt new file mode 100644 index 0000000..c13483a --- /dev/null +++ b/src/main/kotlin/cn/fadinglight/plugins/Administration.kt @@ -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 + } + +} diff --git a/src/main/kotlin/cn/fadinglight/plugins/Database.kt b/src/main/kotlin/cn/fadinglight/plugins/Database.kt new file mode 100644 index 0000000..133fe66 --- /dev/null +++ b/src/main/kotlin/cn/fadinglight/plugins/Database.kt @@ -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, + ) +} + diff --git a/src/main/kotlin/cn/fadinglight/plugins/HTTP.kt b/src/main/kotlin/cn/fadinglight/plugins/HTTP.kt new file mode 100644 index 0000000..29276b5 --- /dev/null +++ b/src/main/kotlin/cn/fadinglight/plugins/HTTP.kt @@ -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 + } + } + +} diff --git a/src/main/kotlin/cn/fadinglight/plugins/Logging.kt b/src/main/kotlin/cn/fadinglight/plugins/Logging.kt new file mode 100644 index 0000000..85ca957 --- /dev/null +++ b/src/main/kotlin/cn/fadinglight/plugins/Logging.kt @@ -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 + } +} \ No newline at end of file diff --git a/src/main/kotlin/cn/fadinglight/plugins/Routing.kt b/src/main/kotlin/cn/fadinglight/plugins/Routing.kt new file mode 100644 index 0000000..9346e81 --- /dev/null +++ b/src/main/kotlin/cn/fadinglight/plugins/Routing.kt @@ -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() + } + } +} diff --git a/src/main/kotlin/cn/fadinglight/plugins/Serialization.kt b/src/main/kotlin/cn/fadinglight/plugins/Serialization.kt new file mode 100644 index 0000000..bc543f3 --- /dev/null +++ b/src/main/kotlin/cn/fadinglight/plugins/Serialization.kt @@ -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() + } +} diff --git a/src/main/kotlin/cn/fadinglight/plugins/Session.kt b/src/main/kotlin/cn/fadinglight/plugins/Session.kt new file mode 100644 index 0000000..53b3df1 --- /dev/null +++ b/src/main/kotlin/cn/fadinglight/plugins/Session.kt @@ -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("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) diff --git a/src/main/kotlin/cn/fadinglight/plugins/WebSocket.kt b/src/main/kotlin/cn/fadinglight/plugins/WebSocket.kt new file mode 100644 index 0000000..0c8b877 --- /dev/null +++ b/src/main/kotlin/cn/fadinglight/plugins/WebSocket.kt @@ -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) diff --git a/src/main/kotlin/cn/fadinglight/routes/BillRoutes.kt b/src/main/kotlin/cn/fadinglight/routes/BillRoutes.kt new file mode 100644 index 0000000..eee5cea --- /dev/null +++ b/src/main/kotlin/cn/fadinglight/routes/BillRoutes.kt @@ -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}") {} + } +} \ No newline at end of file diff --git a/src/main/kotlin/cn/fadinglight/routes/LabelRoutes.kt b/src/main/kotlin/cn/fadinglight/routes/LabelRoutes.kt new file mode 100644 index 0000000..0568644 --- /dev/null +++ b/src/main/kotlin/cn/fadinglight/routes/LabelRoutes.kt @@ -0,0 +1,9 @@ +package cn.fadinglight.routes + +import io.ktor.server.routing.* + +fun Route.labelRoute() { + route("/label") { + + } +} \ No newline at end of file diff --git a/src/main/kotlin/cn/fadinglight/services/BillService.kt b/src/main/kotlin/cn/fadinglight/services/BillService.kt new file mode 100644 index 0000000..53c4cd7 --- /dev/null +++ b/src/main/kotlin/cn/fadinglight/services/BillService.kt @@ -0,0 +1,15 @@ +package cn.fadinglight.services + +import cn.fadinglight.models.Bill + +interface BillService { + fun getMonthBills(year: String, month: String): List + +} + + +class BillServiceImpl : BillService { + override fun getMonthBills(year: String, month: String): List { + TODO("Not yet implemented") + } +} \ No newline at end of file diff --git a/src/main/resources/application.conf b/src/main/resources/application.conf new file mode 100644 index 0000000..b819589 --- /dev/null +++ b/src/main/resources/application.conf @@ -0,0 +1,15 @@ +ktor { + development = true + + deployment { + port = 8080 + port = ${?PORT} + watch = [ bill-ktor ] + } + + hikariConfig = "/db.properties" + + application { + modules = [ cn.fadinglight.ApplicationKt.module ] + } +} \ No newline at end of file diff --git a/src/main/resources/db.properties b/src/main/resources/db.properties new file mode 100644 index 0000000..f21e646 --- /dev/null +++ b/src/main/resources/db.properties @@ -0,0 +1,4 @@ +driverClassName=org.mariadb.jdbc.Driver +jdbcUrl=jdbc:mariadb://localhost:3306/bill +dataSource.user=root +dataSource.password=123456 \ No newline at end of file diff --git a/src/main/resources/logback.xml b/src/main/resources/logback.xml new file mode 100644 index 0000000..61231ba --- /dev/null +++ b/src/main/resources/logback.xml @@ -0,0 +1,12 @@ + + + + %d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + diff --git a/src/test/kotlin/cn/fadinglight/ApplicationTest.kt b/src/test/kotlin/cn/fadinglight/ApplicationTest.kt new file mode 100644 index 0000000..fad4eab --- /dev/null +++ b/src/test/kotlin/cn/fadinglight/ApplicationTest.kt @@ -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()) + } + } +} \ No newline at end of file