MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Kotlin脚本自动化任务应用案例

2021-03-185.8k 阅读

Kotlin 脚本基础

Kotlin 脚本是一种允许你直接运行 Kotlin 代码而无需将其编译成完整 Java 类的方式。它为快速原型开发、自动化任务等场景提供了极大的便利。

在 Kotlin 脚本中,代码结构相对简洁。例如,一个简单的 Kotlin 脚本用于打印 “Hello, World!” 可能如下:

println("Hello, World!")

你可以将上述代码保存为 hello.kts 文件,然后在命令行中通过 kotlinc -script hello.kts 来运行它。

脚本依赖管理

在实际应用中,我们的脚本可能需要依赖外部库。Kotlin 脚本支持使用 @file:DependsOn 注解来声明依赖。例如,如果我们想要在脚本中使用 OkHttp 库来进行 HTTP 请求:

@file:DependsOn("com.squareup.okhttp3:okhttp:4.9.1")

import okhttp3.OkHttpClient
import okhttp3.Request

val client = OkHttpClient()
val request = Request.Builder()
  .url("https://www.example.com")
  .build()
client.newCall(request).execute().use { response ->
    if (!response.isSuccessful) throw IOException("Unexpected code $response")
    println(response.body?.string())
}

这里通过 @file:DependsOn 引入了 OkHttp 库的特定版本,使得我们可以在脚本中使用 OkHttp 提供的功能。

自动化文件处理任务

批量重命名文件

假设我们有一个目录,里面包含大量图片文件,命名格式不太规范,我们希望将它们批量重命名为统一格式,例如 image_001.jpg, image_002.jpg 等。

首先,我们需要获取目录下的所有文件,然后对每个文件进行重命名操作。以下是实现该功能的 Kotlin 脚本:

import java.io.File

fun main() {
    val directory = File("/path/to/your/images")
    var counter = 1
    directory.listFiles()?.forEach { file ->
        if (file.isFile && file.name.endsWith(".jpg") || file.name.endsWith(".png")) {
            val newFileName = "image_${"%03d".format(counter)}${file.extension}"
            val newFile = File(directory, newFileName)
            file.renameTo(newFile)
            counter++
        }
    }
}

在上述代码中,我们定义了目标目录 directory。然后通过 listFiles 获取目录下的所有文件。对于每个满足图片格式(.jpg.png)的文件,我们生成一个新的文件名,使用 renameTo 方法将文件重命名。

筛选特定文件并复制

有时我们需要在一个复杂的目录结构中筛选出特定类型的文件,并将它们复制到另一个目录。比如,我们要从一个项目目录中筛选出所有的 *.kt 文件,并将它们复制到一个备份目录。

import java.io.File
import java.nio.file.Files
import java.nio.file.StandardCopyOption

fun main() {
    val sourceDirectory = File("/path/to/your/project")
    val targetDirectory = File("/path/to/backup")
    targetDirectory.mkdirs()
    sourceDirectory.walkTopDown()
      .filter { it.isFile && it.name.endsWith(".kt") }
      .forEach { file ->
            val relativePath = sourceDirectory.toPath().relativize(file.toPath())
            val targetFile = File(targetDirectory, relativePath.toString())
            targetFile.parentFile?.mkdirs()
            Files.copy(file.toPath(), targetFile.toPath(), StandardCopyOption.REPLACE_EXISTING)
        }
}

这里我们使用 walkTopDown 方法遍历源目录及其子目录。筛选出所有的 Kotlin 源文件(.kt)。对于每个筛选出的文件,我们计算其相对于源目录的相对路径,并在目标目录中创建相应的路径结构,最后使用 Files.copy 方法将文件复制过去。

自动化构建与部署任务

基于 Kotlin 脚本的 Gradle 辅助构建

Gradle 是一个强大的构建工具,而 Kotlin 脚本可以很好地与之配合。假设我们有一个多模块的 Kotlin 项目,我们希望通过一个 Kotlin 脚本来自动化执行一些 Gradle 任务,比如编译、测试和打包。

首先,我们可以在项目根目录下创建一个 build_helpers.kts 脚本:

import org.gradle.tooling.*

fun executeGradleTask(taskName: String) {
    val builder = GradleConnector.newConnector()
      .forProjectDirectory(File("."))
    val connection = builder.connect()
    try {
        connection.newBuild()
          .forTasks(taskName)
          .run()
    } finally {
        connection.close()
    }
}

fun main() {
    executeGradleTask("clean")
    executeGradleTask("build")
    executeGradleTask("test")
}

在上述脚本中,我们使用 GradleConnector 连接到当前项目的 Gradle 构建系统。通过 executeGradleTask 函数,我们可以执行任意的 Gradle 任务。在 main 函数中,我们依次执行了 cleanbuildtest 任务,实现了项目的清理、构建和测试自动化。

自动化部署到服务器

假设我们已经完成了项目的构建,生成了可部署的工件(如 JAR 文件),现在我们要将其部署到远程服务器。我们可以使用 SSH 协议来实现这一过程。

首先,我们需要添加 SSH 相关的依赖到我们的 Kotlin 脚本:

@file:DependsOn("com.jcraft:jsch:0.1.55")

import com.jcraft.jsch.*

fun main() {
    val user = "your_username"
    val host = "your_server_host"
    val password = "your_password"
    val localFilePath = "/path/to/your/build/output.jar"
    val remoteFilePath = "/path/to/deploy/on/server/output.jar"

    val jsch = JSch()
    val session: Session = jsch.getSession(user, host, 22)
    session.setPassword(password)
    val config = java.util.Properties()
    config.put("StrictHostKeyChecking", "no")
    session.setConfig(config)
    session.connect()

    val sftpChannel = session.openChannel("sftp") as ChannelSftp
    sftpChannel.connect()
    sftpChannel.put(localFilePath, remoteFilePath)
    sftpChannel.disconnect()

    session.disconnect()
}

在这段代码中,我们使用 JSch 库来建立 SSH 连接。通过 Session 进行身份验证并连接到远程服务器。然后打开一个 SFTP 通道,将本地构建生成的 JAR 文件上传到远程服务器的指定路径,完成自动化部署的一部分工作。

自动化测试任务

单元测试自动化执行

在 Kotlin 项目中,我们通常使用 JUnit 或其他测试框架来编写单元测试。我们可以编写一个 Kotlin 脚本来自动化执行这些单元测试。

假设我们的项目使用 JUnit 5,并且测试类位于 src/test/kotlin 目录下。以下是一个自动化执行单元测试的 Kotlin 脚本:

import org.junit.platform.launcher.*
import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder
import org.junit.platform.launcher.core.LauncherFactory
import org.junit.platform.launcher.listeners.SummaryGeneratingListener
import java.net.URI

fun main() {
    val testClassPath = URI.create("file://${System.getProperty("user.dir")}/build/classes/kotlin/test")
    val request = LauncherDiscoveryRequestBuilder.request()
      .selectors(Selectors.selectPackage("com.example.yourpackage"))
      .classpath(testClassPath)
      .build()
    val launcher: Launcher = LauncherFactory.create()
    val listener = SummaryGeneratingListener()
    launcher.execute(request, listener)
    val summary = listener.summary()
    println("Tests executed: ${summary.totalTestsCount}")
    println("Tests failed: ${summary.failedTestCount}")
}

在上述脚本中,我们首先构建了一个 LauncherDiscoveryRequest,指定要测试的包(com.example.yourpackage)以及测试类所在的类路径。然后创建一个 Launcher 并执行测试请求,通过 SummaryGeneratingListener 来收集测试结果并打印出执行的测试总数和失败的测试数。

集成测试自动化流程

集成测试通常涉及多个组件之间的交互,并且可能需要启动一些服务。例如,我们有一个基于 Spring Boot 的微服务项目,并且使用 Testcontainers 来进行集成测试。

我们可以编写一个 Kotlin 脚本来自动化整个集成测试流程,包括启动数据库容器、启动微服务应用和执行集成测试。

@file:DependsOn("org.testcontainers:testcontainers:1.16.3")
@file:DependsOn("org.testcontainers:postgresql:1.16.3")
@file:DependsOn("org.springframework.boot:spring-boot-starter-test:2.6.7")

import org.springframework.boot.builder.SpringApplicationBuilder
import org.springframework.boot.test.context.SpringBootTest
import org.testcontainers.containers.PostgreSQLContainer
import org.testcontainers.junit.jupiter.Container
import org.testcontainers.junit.jupiter.Testcontainers
import java.util.concurrent.TimeUnit

@Testcontainers
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class IntegrationTest {
    @Container
    val postgresContainer: PostgreSQLContainer<*> = PostgreSQLContainer("postgres:14.1")
      .withDatabaseName("test_db")
      .withUsername("test_user")
      .withPassword("test_password")

    companion object {
        @JvmStatic
        fun main(args: Array<String>) {
            postgresContainer.start()
            val hikariConfig = HikariConfig()
            hikariConfig.jdbcUrl = postgresContainer.jdbcUrl
            hikariConfig.username = postgresContainer.username
            hikariConfig.password = postgresContainer.password
            val dataSource = HikariDataSource(hikariConfig)

            val app = SpringApplicationBuilder(YourSpringBootApplication::class.java)
              .properties("spring.datasource.url" to postgresContainer.jdbcUrl,
                           "spring.datasource.username" to postgresContainer.username,
                           "spring.datasource.password" to postgresContainer.password)
              .run(*args)

            // 等待应用启动完成
            TimeUnit.SECONDS.sleep(5)

            // 执行集成测试,可以通过反射调用测试类的方法
            // 这里假设存在一个 IntegrationTestClass 类包含集成测试方法
            val testClass = Class.forName("com.example.IntegrationTestClass")
            val testMethod = testClass.getDeclaredMethod("integrationTestMethod")
            testMethod.invoke(testClass.newInstance())

            app.close()
            postgresContainer.stop()
        }
    }
}

在这个脚本中,我们使用 Testcontainers 启动了一个 PostgreSQL 数据库容器。然后通过 SpringApplicationBuilder 启动 Spring Boot 应用,并配置其连接到刚刚启动的数据库容器。等待应用启动后,通过反射调用集成测试方法,最后关闭应用和数据库容器。

自动化数据处理任务

从 CSV 文件读取并处理数据

CSV(逗号分隔值)文件是一种常见的数据存储格式。我们可以编写 Kotlin 脚本来读取 CSV 文件中的数据,并进行一些处理,比如计算平均值、筛选特定数据等。

假设我们有一个 data.csv 文件,内容如下:

name,age
Alice,25
Bob,30
Charlie,28

以下是读取该 CSV 文件并计算平均年龄的 Kotlin 脚本:

import java.io.File

fun main() {
    val file = File("data.csv")
    var totalAge = 0
    var count = 0
    file.bufferedReader().useLines { lines ->
        lines.drop(1).forEach { line ->
            val parts = line.split(',')
            val age = parts[1].toInt()
            totalAge += age
            count++
        }
    }
    val averageAge = if (count > 0) totalAge / count.toFloat() else 0f
    println("Average age: $averageAge")
}

在上述代码中,我们使用 bufferedReader 逐行读取文件内容。通过 drop(1) 跳过 CSV 文件的表头行。然后对每一行数据进行拆分,提取年龄字段并累加到 totalAge 中,同时记录数据行数 count。最后计算平均年龄并打印出来。

数据转换并写入新文件

有时我们需要对读取到的数据进行转换,然后将转换后的数据写入到新的文件中。例如,我们从一个 JSON 文件中读取数据,将其中的日期格式进行转换,并写入到另一个 JSON 文件。

假设我们有一个 input.json 文件,内容如下:

[
    {
        "name": "Alice",
        "birthDate": "2023-01-01"
    },
    {
        "name": "Bob",
        "birthDate": "2023-02-01"
    }
]

我们希望将 birthDate 格式从 yyyy - MM - dd 转换为 MM/dd/yyyy,并写入 output.json。以下是实现该功能的 Kotlin 脚本:

import com.google.gson.Gson
import com.google.gson.GsonBuilder
import java.io.File
import java.text.SimpleDateFormat
import java.util.*

fun main() {
    val inputFile = File("input.json")
    val outputFile = File("output.json")
    val gson = Gson()
    val dateFormat = SimpleDateFormat("yyyy - MM - dd")
    val newDateFormat = SimpleDateFormat("MM/dd/yyyy")

    val data = gson.fromJson(inputFile.readText(), Array<Map<String, String>>::class.java)
    val transformedData = data.map { item ->
        val birthDate = dateFormat.parse(item["birthDate"]!!)
        val newBirthDate = newDateFormat.format(birthDate)
        mapOf("name" to item["name"], "birthDate" to newBirthDate)
    }

    val newGson = GsonBuilder().setPrettyPrinting().create()
    outputFile.writeText(newGson.toJson(transformedData))
}

在这个脚本中,我们首先使用 Gson 库读取 input.json 文件中的数据,并将其转换为 Array<Map<String, String>> 类型。然后对每个数据项中的 birthDate 进行日期格式转换。最后,使用 GsonBuilder 将转换后的数据以漂亮的格式写入 output.json 文件。

自动化系统监控任务

监控系统资源使用情况

我们可以使用 Kotlin 脚本来监控系统的资源使用情况,如 CPU 使用率、内存使用率等。在 Java 中,我们可以通过 ManagementFactory 来获取这些信息,Kotlin 同样可以利用这一点。

以下是一个监控 CPU 和内存使用率的 Kotlin 脚本:

import com.sun.management.OperatingSystemMXBean
import java.lang.management.ManagementFactory

fun getCpuUsage(): Double {
    val osBean = ManagementFactory.getOperatingSystemMXBean() as OperatingSystemMXBean
    val startTime = System.nanoTime()
    val startCpuTime = osBean.processCpuTime
    Thread.sleep(100)
    val endTime = System.nanoTime()
    val endCpuTime = osBean.processCpuTime
    val cpuUsage = (endCpuTime - startCpuTime).toDouble() / (endTime - startTime)
    return cpuUsage
}

fun getMemoryUsage(): Double {
    val runtime = Runtime.getRuntime()
    val usedMemory = runtime.totalMemory() - runtime.freeMemory()
    val totalMemory = runtime.totalMemory()
    return usedMemory.toDouble() / totalMemory * 100
}

fun main() {
    while (true) {
        val cpuUsage = getCpuUsage()
        val memoryUsage = getMemoryUsage()
        println("CPU Usage: ${cpuUsage * 100}%")
        println("Memory Usage: $memoryUsage%")
        Thread.sleep(5000)
    }
}

在上述代码中,getCpuUsage 函数通过记录一段时间内的 CPU 时间和系统时间来计算 CPU 使用率。getMemoryUsage 函数通过 Runtime 获取已使用内存和总内存,从而计算出内存使用率。在 main 函数中,我们每隔 5 秒打印一次 CPU 和内存使用率。

监控文件系统变化

有时我们需要监控文件系统中特定目录的变化,比如文件的创建、修改或删除。在 Kotlin 中,我们可以使用 Java 的 WatchService 来实现这一功能。

以下是一个监控指定目录文件变化的 Kotlin 脚本:

import java.nio.file.*
import java.nio.file.attribute.BasicFileAttributes
import java.util.concurrent.TimeUnit

fun main() {
    val watchService = FileSystems.getDefault().newWatchService()
    val directoryToWatch = Paths.get("/path/to/watch")
    directoryToWatch.register(watchService,
        StandardWatchEventKinds.ENTRY_CREATE,
        StandardWatchEventKinds.ENTRY_MODIFY,
        StandardWatchEventKinds.ENTRY_DELETE)

    while (true) {
        val watchKey = watchService.poll(1, TimeUnit.SECONDS)
        if (watchKey == null) continue
        watchKey.pollEvents().forEach { event ->
            val kind = event.kind()
            if (kind == StandardWatchEventKinds.OVERFLOW) return@forEach
            val affectedPath = event.context() as Path
            println("Event kind: $kind, Affected path: $affectedPath")
        }
        watchKey.reset()
    }
}

在这个脚本中,我们创建了一个 WatchService,并将指定目录注册到该服务中,监听文件的创建、修改和删除事件。在循环中,我们不断轮询 WatchService 获取事件,并打印出事件类型和受影响的文件路径。

通过以上各种 Kotlin 脚本自动化任务的应用案例,我们可以看到 Kotlin 脚本在不同领域的强大功能和灵活性,能够帮助我们提高工作效率,实现各种复杂任务的自动化。无论是文件处理、构建部署、测试还是数据和系统监控,Kotlin 脚本都提供了简洁且有效的解决方案。在实际应用中,我们可以根据具体需求进一步优化和扩展这些脚本,以满足更复杂的业务场景。