备份代码

This commit is contained in:
Superdandan 2024-05-19 23:07:25 +08:00
parent 27e398ac73
commit 15ddb83d2d
15 changed files with 397 additions and 115 deletions

View File

@ -33,16 +33,28 @@ dependencies {
implementation("io.ktor:ktor-serialization-gson-jvm") implementation("io.ktor:ktor-serialization-gson-jvm")
implementation("io.ktor:ktor-server-freemarker-jvm") implementation("io.ktor:ktor-server-freemarker-jvm")
implementation("io.ktor:ktor-server-netty-jvm") implementation("io.ktor:ktor-server-netty-jvm")
implementation("io.insert-koin:koin-ktor:3.2.0")
implementation("io.insert-koin:koin-core:3.2.0")
implementation("ch.qos.logback:logback-classic:$logback_version") implementation("ch.qos.logback:logback-classic:$logback_version")
implementation("io.ktor:ktor-server-config-yaml:2.3.10") implementation("io.ktor:ktor-server-config-yaml:2.3.10")
// Exposed // Exposed
implementation("org.jetbrains.exposed:exposed-core:$exposed_version") implementation("org.jetbrains.exposed:exposed-core:$exposed_version")
implementation("org.jetbrains.exposed:exposed-dao:$exposed_version") implementation("org.jetbrains.exposed:exposed-dao:$exposed_version")
implementation("org.jetbrains.exposed:exposed-crypt:$exposed_version")
implementation("org.jetbrains.exposed:exposed-jdbc:$exposed_version") implementation("org.jetbrains.exposed:exposed-jdbc:$exposed_version")
implementation("org.jetbrains.exposed:exposed-java-time:$exposed_version") implementation("org.jetbrains.exposed:exposed-java-time:$exposed_version")
implementation("org.jetbrains.exposed:exposed-json:$exposed_version")
implementation("org.jetbrains.exposed:exposed-money:$exposed_version")
implementation("com.h2database:h2:$h2_version") implementation("com.h2database:h2:$h2_version")
//aliyunOss
implementation("com.aliyun.oss:aliyun-sdk-oss:3.17.4")
implementation("javax.xml.bind:jaxb-api:2.3.1")
implementation("javax.activation:activation:1.1.1")
implementation("org.glassfish.jaxb:jaxb-runtime:2.3.3")
//pool //pool
implementation("com.zaxxer:HikariCP:$hikaricp_version") implementation("com.zaxxer:HikariCP:$hikaricp_version")
implementation("org.ehcache:ehcache:$ehcache_version") implementation("org.ehcache:ehcache:$ehcache_version")

View File

@ -2,7 +2,7 @@ ktor_version=2.3.10
kotlin_version=1.9.23 kotlin_version=1.9.23
logback_version=1.4.14 logback_version=1.4.14
kotlin.code.style=official kotlin.code.style=official
exposed_version = 0.41.1 exposed_version = 0.50.1
h2_version = 2.2.224 h2_version = 2.2.224
hikaricp_version = 5.1.0 hikaricp_version = 5.1.0
ehcache_version = 3.10.8 ehcache_version = 3.10.8

View File

@ -1,34 +1,34 @@
package echo.org package echo.org
import com.zaxxer.hikari.HikariDataSource import echo.org.application.route.DocumentModule
import echo.org.instructure.AliyunOss
import echo.org.instructure.DatabaseSingleton import echo.org.instructure.DatabaseSingleton
import echo.org.plugins.configureRouting import echo.org.plugins.configureRouting
import echo.org.plugins.configureSecurity import echo.org.plugins.configureSecurity
import echo.org.plugins.configureSerialization import echo.org.plugins.configureSerialization
import echo.org.plugins.configureTemplating import echo.org.plugins.configureTemplating
import io.ktor.server.application.* import io.ktor.server.application.*
import org.jetbrains.exposed.sql.Database import org.koin.ktor.ext.inject
import org.koin.ktor.plugin.Koin
fun main(args: Array<String>) { fun main(args: Array<String>) {
io.ktor.server.netty.EngineMain.main(args) io.ktor.server.netty.EngineMain.main(args)
} }
fun Application.module() { fun Application.module() {
// Initialize Koin
install(Koin) {
modules(DocumentModule)
}
AliyunOss.init()
configureSecurity() configureSecurity()
configureSerialization() configureSerialization()
configureTemplating()
configureRouting() configureRouting()
configureTemplating()
initDatabase() initDatabase()
} }
fun Application.initDatabase() { fun Application.initDatabase() {
val config = environment.config.config("storage") DatabaseSingleton.init(environment.config)
val dateSourceConfig = DatabaseSingleton.createHikariDataSource(
url = config.property("jdbcURL").getString(),
driver = config.property("driverClassName").getString(),
usernameInput = config.property("username").getString(),
passwordInput = config.property("password").getString()
)
val dataSource = HikariDataSource(dateSourceConfig)
Database.connect(dataSource)
} }

View File

@ -0,0 +1,100 @@
package echo.org.application.route
import echo.org.domain.*
import echo.org.instructure.AliyunOssFileServiceImpl
import echo.org.instructure.DocumentRepositoryImpl
import io.ktor.http.*
import io.ktor.http.content.*
import io.ktor.server.application.*
import io.ktor.server.freemarker.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import org.koin.dsl.module
import org.koin.ktor.ext.inject
import java.io.File
import java.net.URLEncoder
import java.nio.charset.StandardCharsets
import java.time.LocalDateTime
val DocumentModule = module {
single<DocumentRepository> { DocumentRepositoryImpl() }
single<FileOSS> { AliyunOssFileServiceImpl() }
}
fun Route.handleDocuments() {
val documentRepository by inject<DocumentRepository>()
val fileOSS by inject<FileOSS>()
route("/documents") {
get("") {
val res = documentRepository.pageQuery(DocumentQueryParam())
call.respond(FreeMarkerContent("index.ftl", res.results, "e"))
}
post("/add") {
// 添加一个文件
val multipart = call.receiveMultipart()
var fileSize: String?
var fileType: String?
var uploadName: String?
multipart.forEachPart { part ->
when (part) {
is PartData.FileItem -> {
val fileName = part.originalFileName as String
fileType = fileName.split(".").last()
uploadName = System.currentTimeMillis().toString() + "." + fileType
val file = File(uploadName!!)
part.streamProvider()
.use { its ->
file.outputStream().buffered()
.use { its.copyTo(it) }
}
fileSize = file.length().toString()
if (fileOSS.upload(file)) {
file.delete()
documentRepository.create(
DocumentVO(
fileName, "someAuthorId",
LocalDateTime.now(), LocalDateTime.now(),
fileType ?: "", uploadName ?: "",
fileSize ?: "", ""
)
)
}
}
else -> {}
}
part.dispose()
}
call.respond(Result.success(null))
}
get("/download") {
// 获取下载地址
val documentQueryParam = DocumentQueryParam()
documentQueryParam.id = 1
documentQueryParam.id?.let {
documentRepository.findById(it)?.let { document ->
val file = fileOSS.download(document.uploadName)
if (file.exists()) {
val encodedFileName = URLEncoder.encode(document.title, StandardCharsets.UTF_8.toString())
call.response.header(
HttpHeaders.ContentDisposition,
"attachment; filename*=UTF-8''$encodedFileName"
)
call.response.header(HttpHeaders.ContentType, document.fileType)
call.respondFile(file)
} else {
call.respond(HttpStatusCode.NotFound, mapOf("error" to "File not found"))
}
} ?: call.respond(HttpStatusCode.NotFound, mapOf("error" to "Document not found"))
} ?: call.respond(HttpStatusCode.BadRequest, mapOf("error" to "Missing or invalid document ID"))
}
post("/query") {
val documentQueryParam = call.receive<DocumentQueryParam>()
val pageQuery = documentRepository.pageQuery(documentQueryParam)
call.respond(Result.success(pageQuery))
}
}
}

View File

@ -6,23 +6,40 @@ import org.jetbrains.exposed.dao.LongEntityClass
import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.dao.id.EntityID
import java.time.LocalDateTime import java.time.LocalDateTime
class Document(id: EntityID<Long>) : LongEntity(id) {
companion object : LongEntityClass<Document>(Documents) data class Document(
var title: String by Documents.title val id: Long,
var content: String by Documents.content val title: String,
var authorId: String by Documents.authorId val uploadName: String,
var createdAt: LocalDateTime by Documents.createdAt val fileType: String,
var updatedAt: LocalDateTime by Documents.updatedAt val authorId: String,
var fileSize: String by Documents.fileSize val createdAt: LocalDateTime,
} val updatedAt: LocalDateTime,
val fileSize: String,
val filePath: String
)
data class DocumentVO(
val title: String, val authorId: String,
val updatedAt: LocalDateTime,
val createdAt: LocalDateTime,
val fileType: String,
val uploadName: String,
val fileSize: String,
val filePath: String
)
interface DocumentRepository { interface DocumentRepository {
suspend fun save(document: Document): Document? suspend fun create(documentVO: DocumentVO): Document?
suspend fun edit(document: Document): Document?
suspend fun findById(id: Long): Document? suspend fun findById(id: Long): Document?
suspend fun deleteById(id: Long): Boolean suspend fun deleteById(id: Long): Boolean
suspend fun pageQuery(documentQueryParam: DocumentQueryParam): PageResult<Document> suspend fun pageQuery(documentQueryParam: DocumentQueryParam): PageResult<Document>
} }
data class DocumentQueryParam(var title: String? = null, data class DocumentQueryParam(
var authorId: String? = null) : BasePageQueryParm() var id: Long? = null,
var title: String? = null,
var authorId: String? = null
) : BasePageQueryParm()

View File

@ -0,0 +1,9 @@
package echo.org.domain
import java.io.File
interface FileOSS {
fun upload(file: File): Boolean
fun download(fileName: String): File
fun delete(fileName: String): Boolean
}

View File

@ -0,0 +1,89 @@
package echo.org.instructure
import com.aliyun.oss.OSS
import com.aliyun.oss.OSSClientBuilder
import com.aliyun.oss.common.auth.CredentialsProvider
import com.aliyun.oss.common.auth.CredentialsProviderFactory
import com.aliyun.oss.common.auth.DefaultCredentialProvider
import com.aliyun.oss.common.auth.EnvironmentVariableCredentialsProvider
import com.aliyun.oss.model.Bucket
import echo.org.domain.FileOSS
import java.io.File
import java.io.FileOutputStream
//获取本地环境的oss访问凭证信息
fun credentialsProvider(): EnvironmentVariableCredentialsProvider =
CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider()
object AliyunOss {
private lateinit var ossClient: OSS
private const val ENDPOINT = "https://oss-cn-hangzhou.aliyuncs.com"
private const val BUCKET_NAME = "echolaw"
fun init(): OSS {
val accessKeyId = System.getenv("OSS_ACCESS_KEY_ID")
val accessKeySecret = System.getenv("OSS_ACCESS_KEY_SECRET")
val credentialsProvider: CredentialsProvider = DefaultCredentialProvider(accessKeyId, accessKeySecret)
ossClient = OSSClientBuilder().build(ENDPOINT, credentialsProvider)
return ossClient
}
fun shutdown() {
ossClient.shutdown()
}
fun client(): OSS {
return ossClient
}
fun bucket(): Bucket {
return ossClient.createBucket(BUCKET_NAME)
}
fun bucketName(): String {
return BUCKET_NAME
}
}
class AliyunOssFileServiceImpl : FileOSS {
override fun upload(file: File): Boolean {
try {
AliyunOss.client().putObject(
AliyunOss.bucketName(),
file.name,
file
)
} catch (e: Exception) {
e.printStackTrace()
return false
}
return true
}
override fun download(fileName: String): File {
val ossObject = AliyunOss.client().getObject(AliyunOss.bucketName(), fileName)
val file = File(fileName)
val outputStream = FileOutputStream(file)
ossObject.objectContent?.use { input ->
outputStream.use { output ->
input.copyTo(output)
}
}
return file
}
override fun delete(fileName: String): Boolean {
val client = AliyunOss.client()
try {
client.deleteObject(AliyunOss.bucketName(), fileName)
} catch (e: Exception) {
e.printStackTrace()
return false
}
return true
}
}

View File

@ -4,18 +4,32 @@ import com.zaxxer.hikari.HikariConfig
import com.zaxxer.hikari.HikariDataSource import com.zaxxer.hikari.HikariDataSource
import io.ktor.server.config.* import io.ktor.server.config.*
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.SchemaUtils
import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction
import org.jetbrains.exposed.sql.transactions.transaction
object DatabaseSingleton { object DatabaseSingleton {
fun init(config: ApplicationConfig) { lateinit var dataSource: HikariDataSource
val driverClassName = config.property("storage.driverClassName").getString()
val jdbcURL = config.property("storage.jdbcURL").getString() +
config.propertyOrNull("storage.database")?.getString()
fun init(config: ApplicationConfig) {
val jdbcUrl = config.property("storage.jdbcURL").getString()
val database = config.property("storage.database").getString()
val dateSourceConfig = createHikariDataSource(
url = "$jdbcUrl/$database",
driver = config.property("storage.driverClassName").getString(),
usernameInput = config.property("storage.username").getString(),
passwordInput = config.property("storage.password").getString()
)
dataSource = HikariDataSource(dateSourceConfig)
Database.connect(dataSource)
transaction {
SchemaUtils.create(Documents)
}
} }
fun createHikariDataSource( private fun createHikariDataSource(
url: String, url: String,
driver: String, driver: String,
usernameInput: String, usernameInput: String,

View File

@ -1,66 +1,95 @@
package echo.org.instructure package echo.org.instructure
import echo.org.domain.Document
import echo.org.domain.DocumentQueryParam import echo.org.domain.*
import echo.org.domain.DocumentRepository
import echo.org.domain.PageResult
import echo.org.instructure.DatabaseSingleton.dbQuery import echo.org.instructure.DatabaseSingleton.dbQuery
import org.jetbrains.exposed.dao.id.LongIdTable import echo.org.instructure.Documents.id
import org.jetbrains.exposed.sql.Column import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.andWhere
import org.jetbrains.exposed.sql.deleteWhere
import org.jetbrains.exposed.sql.javatime.datetime import org.jetbrains.exposed.sql.javatime.datetime
import org.jetbrains.exposed.sql.selectAll
import java.time.LocalDateTime import java.time.LocalDateTime
object Documents : LongIdTable() { object Documents : Table() {
val title: Column<String> = varchar("title", 255) val id = long("id").autoIncrement().uniqueIndex()
val content: Column<String> = text("content") val title = varchar("title", 255)
val authorId: Column<String> = varchar("author_id", 50) val uploadName = varchar("upload_name", 255)
val createdAt: Column<LocalDateTime> = datetime("created_at") val fileType = varchar("file_type", 50)
val updatedAt: Column<LocalDateTime> = datetime("updated_at") val authorId = varchar("author_id", 255)
val fileSize: Column<String> = varchar("file_size", 255) val createdAt = datetime("created_at")
val updatedAt = datetime("updated_at")
val fileSize = varchar("file_size", 100)
val filePath = varchar("file_path", 255)
override val primaryKey = PrimaryKey(id)
// Convert a ResultRow to a Document
fun toDocument(row: ResultRow): Document {
return Document(
id = row[id],
title = row[title],
authorId = row[authorId],
createdAt = row[createdAt],
updatedAt = row[updatedAt],
fileType = row[fileType],
uploadName = row[uploadName],
fileSize = row[fileSize],
filePath = row[filePath]
)
}
} }
class DocumentRepositoryImpl : DocumentRepository { class DocumentRepositoryImpl : DocumentRepository {
override suspend fun save(document: Document): Document = dbQuery { override suspend fun edit(document: Document): Document? = dbQuery {
// 检查文档是否有有效的ID // 检查文档是否有有效的ID
val existingDocument = document.id.let { val existingDocument = Documents.selectAll().where { id eq document.id }.singleOrNull()
Document.findById(it) // 如果文档存在,则更新,否则返回 null
existingDocument?.let {
Documents.update({ id eq document.id }) {
it[title] = document.title
it[authorId] = document.authorId
it[createdAt] = document.createdAt
it[updatedAt] = LocalDateTime.now() // 更新时间应为当前时间
it[fileSize] = document.fileSize
} }
// 如果文档存在,则更新,否则创建新文档 // 返回更新后的 Document 对象
existingDocument?.apply { Documents.selectAll().where { id eq document.id }
title = document.title .map { Documents.toDocument(it) }
content = document.content .singleOrNull()
authorId = document.authorId
createdAt = document.createdAt
updatedAt = LocalDateTime.now() // 更新时间应为当前时间
fileSize = document.fileSize
} ?: Document.new {
title = document.title
content = document.content
authorId = document.authorId
createdAt = document.createdAt
updatedAt = document.updatedAt
fileSize = document.fileSize
} }
} }
override suspend fun create(
documentVO: DocumentVO
): Document = dbQuery {
Documents.insert {
it[title] = documentVO.title
it[authorId] = documentVO.authorId
it[updatedAt] = documentVO.updatedAt
it[createdAt] = documentVO.createdAt
it[fileType] = documentVO.fileType
it[uploadName] = documentVO.uploadName
it[fileSize] = documentVO.fileSize
it[filePath] = documentVO.filePath
}
Documents.selectAll().where { id eq id }
.map { Documents.toDocument(it) }
.single()
}
override suspend fun findById(id: Long): Document? = dbQuery { override suspend fun findById(id: Long): Document? = dbQuery {
Document.find { Documents.id eq id }.firstOrNull() Documents.selectAll().where { Documents.id eq id }
.mapNotNull { Documents.toDocument(it) }
.singleOrNull()
} }
override suspend fun deleteById(id: Long): Boolean { override suspend fun deleteById(id: Long): Boolean = dbQuery {
Document.find { Documents.id eq id } Documents.deleteWhere { Documents.id eq id } > 0
.firstOrNull()?.delete()
return Documents.deleteWhere { Documents.id eq id } > 0
} }
override suspend fun pageQuery(documentQueryParam: DocumentQueryParam): PageResult<Document> { override suspend fun pageQuery(documentQueryParam: DocumentQueryParam): PageResult<Document> = dbQuery {
// 构建查询条件 // 构建查询条件
val query = Documents.selectAll().apply { val query = Documents.selectAll().apply {
documentQueryParam.authorId?.let { documentQueryParam.authorId?.let {
@ -70,12 +99,13 @@ class DocumentRepositoryImpl : DocumentRepository {
andWhere { Documents.title like "%${documentQueryParam.title}%" } andWhere { Documents.title like "%${documentQueryParam.title}%" }
} }
} }
val pageSize = documentQueryParam.pageSize ?: 10 // 默认页面大小 val pageSize = documentQueryParam.pageSize // 默认页面大小
val pageIndex = documentQueryParam.pageNum ?: 0 // 默认页码 val pageIndex = documentQueryParam.pageNum - 1 // 默认页码
val offset = pageSize * pageIndex.toLong() val offset = pageSize * pageIndex.toLong()
// 执行查询 // 执行查询
val count = query.count() val count = query.count()
val documentList = query.limit(pageSize, offset).map { Document.wrapRow(it) } val documents = query.limit(pageSize, offset)
return PageResult(documentList, count) .map { Documents.toDocument(it) }
PageResult(documents, count)
} }
} }

View File

@ -1,47 +1,15 @@
package echo.org.plugins package echo.org.plugins
import echo.org.domainDocuments import echo.org.application.route.handleDocuments
import echo.org.domain.Result
import io.ktor.http.content.*
import io.ktor.server.application.* import io.ktor.server.application.*
import io.ktor.server.freemarker.*
import io.ktor.server.http.content.* import io.ktor.server.http.content.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.* import io.ktor.server.routing.*
import java.io.File
fun Application.configureRouting() { fun Application.configureRouting() {
routing { routing {
staticResources("/files", "files") staticResources("/files", "files")
route("/documents") { handleDocuments()
get("") {
call.respond(FreeMarkerContent("index.ftl", model, "e"))
}
post("/add") {
// 添加一个文件
val multipart = call.receiveMultipart()
multipart.forEachPart { part ->
when (part) {
is PartData.FileItem -> {
val fileName = part.originalFileName as String
val file = File("upload-directory/$fileName")
part.streamProvider()
.use { its -> file.outputStream().buffered()
.use { its.copyTo(it) } }
}
else -> {}
}
part.dispose()
}
call.respond(Result.success(""))
}
post("/download"){
// 获取下载第
}
}
} }
} }

View File

@ -1,14 +1,20 @@
package echo.org.plugins package echo.org.plugins
import com.google.gson.TypeAdapter
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonWriter
import io.ktor.serialization.gson.* import io.ktor.serialization.gson.*
import io.ktor.server.application.* import io.ktor.server.application.*
import io.ktor.server.plugins.contentnegotiation.* import io.ktor.server.plugins.contentnegotiation.*
import io.ktor.server.response.* import io.ktor.server.response.*
import io.ktor.server.routing.* import io.ktor.server.routing.*
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
fun Application.configureSerialization() { fun Application.configureSerialization() {
install(ContentNegotiation) { install(ContentNegotiation) {
gson { gson {
registerTypeAdapter(LocalDateTime::class.java, LocalDateTimeAdapter())
} }
} }
routing { routing {
@ -17,3 +23,15 @@ fun Application.configureSerialization() {
} }
} }
} }
class LocalDateTimeAdapter : TypeAdapter<LocalDateTime>() {
private val formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME
override fun write(out: JsonWriter, value: LocalDateTime) {
out.value(value.format(formatter))
}
override fun read(input: JsonReader): LocalDateTime {
return LocalDateTime.parse(input.nextString(), formatter)
}
}

View File

@ -3,12 +3,12 @@ ktor:
modules: modules:
- echo.org.ApplicationKt.module - echo.org.ApplicationKt.module
deployment: deployment:
port: 8080 port: 8012
storage: storage:
driverClassName: "com.mysql.cj.jdbc.Driver" driverClassName: "com.mysql.cj.jdbc.Driver"
jdbcURL: "jdbc:mysql://47.97.21.20:3306/" jdbcURL: "jdbc:mysql://47.97.21.20:3306"
database: "document" database: "document"
username: "documentAdmin" username: "documentAdmin"
password: "123456" password: "123456"

View File

@ -4,7 +4,7 @@
<pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern> <pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder> </encoder>
</appender> </appender>
<root level="trace"> <root level="INFO">
<appender-ref ref="STDOUT"/> <appender-ref ref="STDOUT"/>
</root> </root>
<logger name="org.eclipse.jetty" level="INFO"/> <logger name="org.eclipse.jetty" level="INFO"/>

View File

@ -1,10 +1,17 @@
package echo.org package echo.org
import echo.org.application.route.DocumentModule
import echo.org.instructure.Documents
import echo.org.plugins.* import echo.org.plugins.*
import io.ktor.client.request.* import io.ktor.client.request.*
import io.ktor.client.statement.* import io.ktor.client.statement.*
import io.ktor.http.* import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.testing.* import io.ktor.server.testing.*
import org.jetbrains.exposed.sql.SchemaUtils
import org.jetbrains.exposed.sql.transactions.transaction
import org.koin.dsl.module
import org.koin.ktor.plugin.Koin
import kotlin.test.* import kotlin.test.*
class ApplicationTest { class ApplicationTest {

View File

@ -0,0 +1,18 @@
package echo.org
import echo.org.instructure.AliyunOss
import io.ktor.server.testing.*
import java.io.File
import kotlin.test.Test
class ApplicationOSSTest {
@Test
fun testRoot() = testApplication {
AliyunOss.init()
AliyunOss.client().putObject(AliyunOss.bucketName(),
"测试.txt",
File("D:\\code\\blog\\upload-directory\\新建 文本文档 (3).txt"))
}
}