refactor(脚本,代码调整): 预期希望通过配置加载模块
仍然在重构整个项目中 BREAKING CHANGE: 代码调整,模块抽象化
This commit is contained in:
parent
e45f2f6272
commit
4da8263b45
|
|
@ -0,0 +1,2 @@
|
||||||
|
package top.r3944realms.ltdmanager.blessingskin.data
|
||||||
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
package top.r3944realms.ltdmanager.chevereto.data
|
|
||||||
|
|
||||||
import kotlinx.serialization.SerialName
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class CheveretoResponse(
|
|
||||||
@SerialName("status_code")
|
|
||||||
val statusCode: Int,
|
|
||||||
val success: Success? = null,
|
|
||||||
val image: CheveretoImage? = null,
|
|
||||||
@SerialName("status_txt")
|
|
||||||
val statusTxt:String ?= null
|
|
||||||
)
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
package top.r3944realms.ltdmanager.chevereto.data
|
||||||
|
|
||||||
|
class CheveretoSource {
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
package top.r3944realms.ltdmanager.chevereto.request
|
||||||
|
|
||||||
|
class CheveretoRequest {
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
package top.r3944realms.ltdmanager.chevereto.request.v1
|
||||||
|
|
||||||
|
class CheveretoUploadRequest {
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
package top.r3944realms.ltdmanager.chevereto.response
|
||||||
|
|
||||||
|
class CheveretoSuccessResponse {
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
package top.r3944realms.ltdmanager.chevereto.response
|
||||||
|
|
||||||
|
class FailedCheveretoResponse {
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
package top.r3944realms.ltdmanager.chevereto.response.v1
|
||||||
|
|
||||||
|
class CheveretoUploadResponse {
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
package top.r3944realms.ltdmanager.basic
|
||||||
|
|
||||||
|
interface IClient {
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
package top.r3944realms.ltdmanager.core.client
|
||||||
|
|
||||||
|
import top.r3944realms.ltdmanager.core.client.request.IRequest
|
||||||
|
import top.r3944realms.ltdmanager.core.client.response.IFailedResponse
|
||||||
|
import top.r3944realms.ltdmanager.core.client.response.IResponse
|
||||||
|
import java.util.concurrent.CompletableFuture
|
||||||
|
|
||||||
|
interface IQueueItem<out T:IResponse, out F:IFailedResponse> : Comparable<IQueueItem<@UnsafeVariance T, @UnsafeVariance F>> {
|
||||||
|
fun getRequest(): IRequest<T,F>
|
||||||
|
fun getDeferred(): CompletableFuture<*>
|
||||||
|
fun getRetries(): Int
|
||||||
|
fun getPriority(): Int
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true 表示返回 BlessingSkinResponse, false 表示 Unit
|
||||||
|
*/
|
||||||
|
fun expectsResponse(): Boolean
|
||||||
|
override fun compareTo(other: IQueueItem<@UnsafeVariance T, @UnsafeVariance F>): Int = getPriority().compareTo(other.getPriority())
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
package top.r3944realms.ltdmanager.core.client.request
|
||||||
|
|
||||||
|
interface IRequest {
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
package top.r3944realms.ltdmanager.core.client.response
|
||||||
|
|
||||||
|
class IFailedResponse {
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
package top.r3944realms.ltdmanager.core.client.response
|
||||||
|
|
||||||
|
interface IResponse {
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
package top.r3944realms.ltdmanager.core.client.response
|
||||||
|
|
||||||
|
class ResponseResult {
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
package top.r3944realms.ltdmanager.core.config
|
||||||
|
|
||||||
|
class McsmConfig {
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
package top.r3944realms.ltdmanager.core.init
|
||||||
|
|
||||||
|
class DependencyResolver {
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
package top.r3944realms.ltdmanager.core.config
|
||||||
|
|
||||||
|
|
||||||
|
data class ModuleConfig(
|
||||||
|
val name: String,
|
||||||
|
val type: ModuleType,
|
||||||
|
val enabled: Boolean,
|
||||||
|
val
|
||||||
|
) {
|
||||||
|
data class Dependency(
|
||||||
|
val moduleName: String, // 依赖的模块名称
|
||||||
|
val type: DependencyType, // 依赖类型
|
||||||
|
val required: Boolean = true // 是否必需
|
||||||
|
)
|
||||||
|
enum class ModuleType {
|
||||||
|
GROUP_MESSAGE_POLLING_MODULE,
|
||||||
|
GROUP_REQUEST_HANDLER_MODULE,
|
||||||
|
MAIL_MODULE,
|
||||||
|
BAN_MODULE,
|
||||||
|
DG_LAB_MODULE,
|
||||||
|
INVITE_MODULE,
|
||||||
|
MC_SERVER_STATUS_MODULE,
|
||||||
|
RCON_PLAYER_LIST_MODULE,
|
||||||
|
STATE_MODULE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
package top.r3944realms.ltdmanager.core.init
|
||||||
|
|
||||||
|
object ModuleFactory {
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
package top.r3944realms.ltdmanager.core.init
|
||||||
|
|
||||||
|
object ModuleLoader {
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
package top.r3944realms.ltdmanager.core.init
|
||||||
|
|
||||||
|
object ModuleRegistry {
|
||||||
|
}
|
||||||
212
src/main/kotlin/top/r3944realms/ltdmanager/mcms/MCSMClient.kt
Normal file
212
src/main/kotlin/top/r3944realms/ltdmanager/mcms/MCSMClient.kt
Normal file
|
|
@ -0,0 +1,212 @@
|
||||||
|
package top.r3944realms.ltdmanager.blessingskin
|
||||||
|
|
||||||
|
import io.ktor.client.*
|
||||||
|
import io.ktor.client.call.*
|
||||||
|
import io.ktor.client.engine.cio.*
|
||||||
|
import io.ktor.client.plugins.*
|
||||||
|
import io.ktor.client.request.*
|
||||||
|
import io.ktor.http.*
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
import kotlinx.coroutines.sync.Semaphore
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
import kotlinx.coroutines.sync.withPermit
|
||||||
|
import top.r3944realms.ltdmanager.blessingskin.request.BlessingSkinRequest
|
||||||
|
import top.r3944realms.ltdmanager.blessingskin.response.BlessingSkinResponse
|
||||||
|
import top.r3944realms.ltdmanager.blessingskin.response.FailedBlessingSkinResponse
|
||||||
|
import top.r3944realms.ltdmanager.blessingskin.response.ResponseResult
|
||||||
|
import top.r3944realms.ltdmanager.core.config.YamlConfigLoader
|
||||||
|
import top.r3944realms.ltdmanager.utils.Environment
|
||||||
|
import top.r3944realms.ltdmanager.utils.LoggerUtil
|
||||||
|
import java.net.URLEncoder
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class BlessingSkinClient private constructor() : AutoCloseable {
|
||||||
|
private val client = HttpClient(CIO) {
|
||||||
|
expectSuccess = false
|
||||||
|
|
||||||
|
// 安装 HttpTimeout 插件
|
||||||
|
install(HttpTimeout) {
|
||||||
|
// 默认超时配置,会被具体请求的配置覆盖
|
||||||
|
requestTimeoutMillis = 30000
|
||||||
|
connectTimeoutMillis = 10000
|
||||||
|
socketTimeoutMillis = 15000
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private val blessingSkinServerConfig = YamlConfigLoader.loadBlessingSkinServerConfig()
|
||||||
|
|
||||||
|
// 限流控制
|
||||||
|
private val semaphore = Semaphore(5)
|
||||||
|
private val requestMutex = Mutex()
|
||||||
|
private val requestQueue = PriorityQueue<BlessingSkinQueueItem<BlessingSkinResponse, FailedBlessingSkinResponse>>(compareBy { it.priority })
|
||||||
|
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||||
|
|
||||||
|
init {
|
||||||
|
startQueueProcessor()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 提交请求
|
||||||
|
*/
|
||||||
|
suspend fun <T : BlessingSkinResponse, F : FailedBlessingSkinResponse> submitRequest(
|
||||||
|
request: BlessingSkinRequest<T, F>,
|
||||||
|
priority: Int = 5,
|
||||||
|
maxRetries: Int = 3
|
||||||
|
): ResponseResult<T, F> {
|
||||||
|
val deferred = CompletableDeferred<ResponseResult<T, F>>()
|
||||||
|
requestMutex.withLock {
|
||||||
|
requestQueue.add(BlessingSkinQueueItem(request, deferred, priority, maxRetries, true))
|
||||||
|
}
|
||||||
|
return deferred.await()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动队列处理器
|
||||||
|
*/
|
||||||
|
private fun startQueueProcessor() {
|
||||||
|
scope.launch {
|
||||||
|
while (isActive) {
|
||||||
|
val item = requestMutex.withLock {
|
||||||
|
requestQueue.poll()
|
||||||
|
}
|
||||||
|
if (item == null) {
|
||||||
|
delay(50)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
processQueueItem(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理队列项
|
||||||
|
*/
|
||||||
|
private suspend fun processQueueItem(item: BlessingSkinQueueItem<BlessingSkinResponse, FailedBlessingSkinResponse>) {
|
||||||
|
semaphore.withPermit {
|
||||||
|
val (request, deferred, _, maxRetries, _) = item
|
||||||
|
var attempt = 0
|
||||||
|
var lastError: Exception? = null
|
||||||
|
|
||||||
|
while (attempt < maxRetries) {
|
||||||
|
try {
|
||||||
|
// 构建完整的URL,包括查询参数
|
||||||
|
val fullUrl = buildFullUrlWithQueryParams(request)
|
||||||
|
|
||||||
|
if (!Environment.isProduction()) {
|
||||||
|
LoggerUtil.logger.debug("发送请求到: $fullUrl")
|
||||||
|
LoggerUtil.logger.debug("请求方法: {}", request.method())
|
||||||
|
}
|
||||||
|
|
||||||
|
val response = client.request(fullUrl) {
|
||||||
|
method = request.method()
|
||||||
|
|
||||||
|
|
||||||
|
// 设置请求头
|
||||||
|
headers {
|
||||||
|
request.headers().invoke(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 对于非GET请求,设置请求体
|
||||||
|
if (request.method() != HttpMethod.Get) {
|
||||||
|
setBody(request.toJSON())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val responseText: String = response.body()
|
||||||
|
|
||||||
|
if (!Environment.isProduction()) {
|
||||||
|
LoggerUtil.logger.debug("响应状态: {}", response.status)
|
||||||
|
LoggerUtil.logger.debug("响应内容: $responseText")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否是HTML响应(重定向)
|
||||||
|
if (isHtmlResponse(responseText)) {
|
||||||
|
throw IllegalStateException("接收到HTML重定向响应,请检查API URL配置")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析响应
|
||||||
|
val result = request.getResponse(responseText, response.status)
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
(deferred as CompletableDeferred<ResponseResult<BlessingSkinResponse, FailedBlessingSkinResponse>>).complete(result)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
} catch (e: Exception) {
|
||||||
|
lastError = e
|
||||||
|
attempt++
|
||||||
|
|
||||||
|
if (!request.shouldRetryOnFailure() || attempt >= maxRetries) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
LoggerUtil.logger.warn("BlessingSkin请求失败 (尝试 $attempt/$maxRetries): ${e.message}")
|
||||||
|
delay((attempt * 1000L)) // 指数退避
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 所有重试都失败或不应重试
|
||||||
|
val errorResponse = createFailureResponse(lastError, request)
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
(deferred as CompletableDeferred<ResponseResult<BlessingSkinResponse, FailedBlessingSkinResponse>>).complete(
|
||||||
|
ResponseResult.Failure(errorResponse)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建完整的URL,包含查询参数
|
||||||
|
*/
|
||||||
|
private fun buildFullUrlWithQueryParams(request: BlessingSkinRequest<*, *>): String {
|
||||||
|
val baseUrl = blessingSkinServerConfig.url?.removeSuffix("/")
|
||||||
|
val path = request.path().removePrefix("/")
|
||||||
|
|
||||||
|
// 构建基础URL
|
||||||
|
val urlBuilder = StringBuilder("$baseUrl/$path")
|
||||||
|
|
||||||
|
// 添加查询参数
|
||||||
|
val queryParams = request.queryParameters().entries.joinToString("&") { (key, value) ->
|
||||||
|
"${URLEncoder.encode(key, "UTF-8")}=${URLEncoder.encode(value, "UTF-8")}"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (queryParams.isNotEmpty()) {
|
||||||
|
urlBuilder.append("?").append(queryParams)
|
||||||
|
}
|
||||||
|
|
||||||
|
return urlBuilder.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否是HTML响应
|
||||||
|
*/
|
||||||
|
private fun isHtmlResponse(text: String): Boolean {
|
||||||
|
return text.contains("<!DOCTYPE html>", ignoreCase = true) ||
|
||||||
|
text.contains("<html>", ignoreCase = true) ||
|
||||||
|
text.contains("Redirecting", ignoreCase = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建失败响应
|
||||||
|
*/
|
||||||
|
private fun createFailureResponse(
|
||||||
|
exception: Exception?,
|
||||||
|
request: BlessingSkinRequest<*, *>
|
||||||
|
): FailedBlessingSkinResponse {
|
||||||
|
return FailedBlessingSkinResponse.Default(
|
||||||
|
failedResult = exception?.message ?: "未知错误",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun close() {
|
||||||
|
scope.cancel()
|
||||||
|
runBlocking {
|
||||||
|
client.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun create(): BlessingSkinClient = BlessingSkinClient()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
package top.r3944realms.ltdmanager.blessingskin
|
||||||
|
|
||||||
|
import kotlinx.coroutines.CompletableDeferred
|
||||||
|
import top.r3944realms.ltdmanager.blessingskin.request.BlessingSkinRequest
|
||||||
|
import top.r3944realms.ltdmanager.blessingskin.response.BlessingSkinResponse
|
||||||
|
import top.r3944realms.ltdmanager.blessingskin.response.FailedBlessingSkinResponse
|
||||||
|
|
||||||
|
data class BlessingSkinQueueItem<out T:BlessingSkinResponse,out F:FailedBlessingSkinResponse>(
|
||||||
|
val request: BlessingSkinRequest<T,F>,
|
||||||
|
val deferred: CompletableDeferred<*>,
|
||||||
|
var retries: Int,
|
||||||
|
val priority: Int,
|
||||||
|
val expectsResponse: Boolean // true 表示返回 BlessingSkinResponse, false 表示 Unit
|
||||||
|
) : Comparable<BlessingSkinQueueItem<@UnsafeVariance T, @UnsafeVariance F>> {
|
||||||
|
override fun compareTo(other: BlessingSkinQueueItem<@UnsafeVariance T, @UnsafeVariance F>): Int = priority.compareTo(other.priority)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
package top.r3944realms.ltdmanager.blessingskin.request
|
||||||
|
|
||||||
|
import io.ktor.http.*
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.Transient
|
||||||
|
import top.r3944realms.ltdmanager.blessingskin.response.BlessingSkinResponse
|
||||||
|
import top.r3944realms.ltdmanager.blessingskin.response.FailedBlessingSkinResponse
|
||||||
|
import top.r3944realms.ltdmanager.blessingskin.response.ResponseResult
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
abstract class BlessingSkinRequest<out T : BlessingSkinResponse, out F : FailedBlessingSkinResponse>(
|
||||||
|
@Transient
|
||||||
|
open val createTime: Long = System.currentTimeMillis()
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* 转换为JSON字符串
|
||||||
|
*/
|
||||||
|
abstract fun toJSON(): String
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取API路径(不包含基础URL)
|
||||||
|
* 例如: "invitation-codes/generate"
|
||||||
|
*/
|
||||||
|
abstract fun path(): String
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取HTTP方法,默认为GET(因为大多数API使用GET+查询参数)
|
||||||
|
*/
|
||||||
|
open fun method(): HttpMethod = HttpMethod.Get
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自定义请求头
|
||||||
|
*/
|
||||||
|
open fun headers(): HeadersBuilder.() -> Unit = {
|
||||||
|
// 默认添加Content-Type
|
||||||
|
append(HttpHeaders.ContentType, ContentType.Application.Json.toString())
|
||||||
|
// 添加Accept头
|
||||||
|
append(HttpHeaders.Accept, "application/json")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取查询参数(用于URL参数)
|
||||||
|
* 例如: mapOf("token" to "abc123", "amount" to "1")
|
||||||
|
*/
|
||||||
|
open fun queryParameters(): Map<String, String> = emptyMap()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取请求体参数(用于POST请求的JSON body)
|
||||||
|
* 例如: mapOf("token" to "abc123", "amount" to 1)
|
||||||
|
*/
|
||||||
|
open fun bodyParameters(): Map<String, Any> = emptyMap()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取请求体内容类型,默认为Application.Json
|
||||||
|
*/
|
||||||
|
open fun contentType(): ContentType = ContentType.Application.Json
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析响应JSON字符串
|
||||||
|
* @param responseJson 响应JSON字符串
|
||||||
|
* @param httpStatusCode HTTP状态码
|
||||||
|
*/
|
||||||
|
abstract fun getResponse(responseJson: String, httpStatusCode: HttpStatusCode): ResponseResult<T, F>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取预期的成功响应类型名称(用于日志和调试)
|
||||||
|
*/
|
||||||
|
abstract fun expectedResponseType(): String
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取预期的失败响应类型名称(用于日志和调试)
|
||||||
|
*/
|
||||||
|
abstract fun expectedFailureType(): String
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否需要在失败时重试(默认重试)
|
||||||
|
*/
|
||||||
|
open fun shouldRetryOnFailure(): Boolean = true
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
package top.r3944realms.ltdmanager.mcms.request.instance
|
||||||
|
|
||||||
|
class GetInstanceListRequest {
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
package top.r3944realms.ltdmanager.mcms.request.instance
|
||||||
|
|
||||||
|
class StartInstanceRequest {
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
package top.r3944realms.ltdmanager.blessingskin.response
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.Transient
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
abstract class FailedBlessingSkinResponse: BlessingSkinResponse() {
|
||||||
|
abstract fun failedMessage(): String
|
||||||
|
@Serializable
|
||||||
|
class Default(@Transient val failedResult: String? = "未知错误") : FailedBlessingSkinResponse() {
|
||||||
|
override fun failedMessage(): String = failedResult!!
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
package top.r3944realms.ltdmanager.blessingskin.response
|
||||||
|
|
||||||
|
import io.ktor.http.*
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.Transient
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlinx.serialization.modules.SerializersModule
|
||||||
|
import kotlinx.serialization.modules.polymorphic
|
||||||
|
import top.r3944realms.ltdmanager.blessingskin.response.invitecode.InvitationCodeGenerationResponse
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
abstract class BlessingSkinResponse (
|
||||||
|
@Transient
|
||||||
|
open val httpStatusCode: HttpStatusCode = HttpStatusCode.OK,
|
||||||
|
@Transient
|
||||||
|
open val createTime: Long = System.currentTimeMillis()
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
// 通用的反序列化方法
|
||||||
|
inline fun <reified T : BlessingSkinResponse> decode(jsonString: String): T {
|
||||||
|
return json.decodeFromString(jsonString)
|
||||||
|
}
|
||||||
|
val json: Json by lazy {
|
||||||
|
Json {
|
||||||
|
ignoreUnknownKeys = true
|
||||||
|
serializersModule = SerializersModule {
|
||||||
|
polymorphic(BlessingSkinResponse::class) {
|
||||||
|
subclass(FailedBlessingSkinResponse.Default::class, FailedBlessingSkinResponse.Default.serializer())
|
||||||
|
subclass(InvitationCodeGenerationResponse::class, InvitationCodeGenerationResponse.serializer())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
package top.r3944realms.ltdmanager.blessingskin.response
|
||||||
|
|
||||||
|
// 响应结果封装
|
||||||
|
sealed class ResponseResult<out T : BlessingSkinResponse, out F : FailedBlessingSkinResponse> {
|
||||||
|
data class Success<T : BlessingSkinResponse>(val response: T) : ResponseResult<T, Nothing>()
|
||||||
|
data class Failure<F : FailedBlessingSkinResponse>(val failure: F) : ResponseResult<Nothing, F>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否成功
|
||||||
|
*/
|
||||||
|
fun isSuccess(): Boolean = this is Success
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取成功响应(如果存在)
|
||||||
|
*/
|
||||||
|
fun getSuccessResponse(): T? = (this as? Success)?.response
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取失败响应(如果存在)
|
||||||
|
*/
|
||||||
|
fun getFailureResponse(): F? = (this as? Failure)?.failure
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 成功时执行操作
|
||||||
|
*/
|
||||||
|
inline fun onSuccess(action: (T) -> Unit): ResponseResult<T, F> {
|
||||||
|
if (this is Success) action(response)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 失败时执行操作
|
||||||
|
*/
|
||||||
|
inline fun onFailure(action: (F) -> Unit): ResponseResult<T, F> {
|
||||||
|
if (this is Failure) action(failure)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
package top.r3944realms.ltdmanager.mcms.response.instance
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import top.r3944realms.ltdmanager.mcms.response.MCSMResponse
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class InstanceListResponse(
|
||||||
|
val status: Int,
|
||||||
|
val data: InstanceListData?,
|
||||||
|
val time: Long
|
||||||
|
) : MCSMResponse
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
package top.r3944realms.ltdmanager.mcms.response.instance
|
||||||
|
|
||||||
|
class StartInstanceResponse {
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
package top.r3944realms.ltdmanager.module
|
||||||
|
|
||||||
|
class ApplyWhiteListModule {
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
package top.r3944realms.ltdmanager.module
|
||||||
|
|
||||||
|
object Modules {
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
package top.r3944realms.ltdmanager.module.exception
|
||||||
|
|
||||||
|
class InvalidConfigException: Exception() {
|
||||||
|
enum class Type(template: String) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,114 +0,0 @@
|
||||||
package top.r394realms.ltdmanagertest.util
|
|
||||||
|
|
||||||
|
|
||||||
import okhttp3.*
|
|
||||||
import okhttp3.MediaType.Companion.toMediaType
|
|
||||||
import okhttp3.RequestBody.Companion.asRequestBody
|
|
||||||
import okhttp3.logging.HttpLoggingInterceptor
|
|
||||||
import top.r3944realms.ltdmanager.utils.LoggerUtil
|
|
||||||
import java.io.File
|
|
||||||
import java.io.IOException
|
|
||||||
|
|
||||||
object ImageUploader {
|
|
||||||
|
|
||||||
private val client = OkHttpClient().newBuilder()
|
|
||||||
.addInterceptor(HttpLoggingInterceptor().apply {
|
|
||||||
level = HttpLoggingInterceptor.Level.BODY // 查看完整的请求和响应
|
|
||||||
})
|
|
||||||
.build()
|
|
||||||
|
|
||||||
fun uploadImage(filePath: String, apiKey: String): String {
|
|
||||||
val file = File(filePath)
|
|
||||||
|
|
||||||
// 检查文件是否存在
|
|
||||||
if (!file.exists()) {
|
|
||||||
throw IllegalArgumentException("文件不存在: $filePath")
|
|
||||||
}
|
|
||||||
|
|
||||||
LoggerUtil.logger.info("开始上传文件: ${file.name}, 大小: ${file.length()} bytes")
|
|
||||||
|
|
||||||
// 创建 multipart 请求体
|
|
||||||
val requestBody = MultipartBody.Builder()
|
|
||||||
.setType(MultipartBody.FORM)
|
|
||||||
.addFormDataPart(
|
|
||||||
"source",
|
|
||||||
file.name,
|
|
||||||
file.asRequestBody("image/png".toMediaType())
|
|
||||||
)
|
|
||||||
.addFormDataPart("format", "json")
|
|
||||||
.build()
|
|
||||||
|
|
||||||
// 创建请求
|
|
||||||
val request = Request.Builder()
|
|
||||||
.url("https://pic.xiaobuawa.top/api/1/upload")
|
|
||||||
.header("X-API-Key", apiKey.trim()) // 重要:去除空格
|
|
||||||
.header("User-Agent", "OkHttp/4.12.0") // 添加 User-Agent
|
|
||||||
.post(requestBody)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
// 执行请求
|
|
||||||
val response = client.newCall(request).execute()
|
|
||||||
try {
|
|
||||||
if (!response.isSuccessful) {
|
|
||||||
throw IOException("上传失败,状态码: ${response.code}, 响应: ${response.body?.string()}")
|
|
||||||
}
|
|
||||||
|
|
||||||
val responseBody = response.body?.string()
|
|
||||||
LoggerUtil.logger.info("上传成功: $responseBody")
|
|
||||||
return responseBody ?: throw IOException("响应体为空")
|
|
||||||
} finally {
|
|
||||||
response.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 异步版本(推荐用于生产环境)
|
|
||||||
fun uploadImageAsync(filePath: String, apiKey: String, callback: (Result<String>) -> Unit) {
|
|
||||||
val file = File(filePath)
|
|
||||||
|
|
||||||
if (!file.exists()) {
|
|
||||||
callback(Result.failure(IllegalArgumentException("文件不存在: $filePath")))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val requestBody = MultipartBody.Builder()
|
|
||||||
.setType(MultipartBody.FORM)
|
|
||||||
.addFormDataPart(
|
|
||||||
"source",
|
|
||||||
file.name,
|
|
||||||
file.asRequestBody("image/png".toMediaType())
|
|
||||||
)
|
|
||||||
.addFormDataPart("format", "json")
|
|
||||||
.build()
|
|
||||||
|
|
||||||
val request = Request.Builder()
|
|
||||||
.url("https://pic.xiaobuawa.top/api/1/upload")
|
|
||||||
.header("X-API-Key", apiKey.trim())
|
|
||||||
.header("User-Agent", "OkHttp/4.12.0")
|
|
||||||
.post(requestBody)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
client.newCall(request).enqueue(object : Callback {
|
|
||||||
override fun onFailure(call: Call, e: IOException) {
|
|
||||||
callback(Result.failure(e))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResponse(call: Call, response: Response) {
|
|
||||||
try {
|
|
||||||
if (!response.isSuccessful) {
|
|
||||||
callback(Result.failure(IOException("上传失败,状态码: ${response.code}")))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val responseBody = response.body?.string()
|
|
||||||
if (responseBody != null) {
|
|
||||||
callback(Result.success(responseBody))
|
|
||||||
} else {
|
|
||||||
callback(Result.failure(IOException("响应体为空")))
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
callback(Result.failure(e))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
package top.r394realms.ltdmanagertest.util
|
|
||||||
|
|
||||||
import top.r3944realms.ltdmanager.GlobalManager
|
|
||||||
import java.io.ByteArrayInputStream
|
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
fun main() = GlobalManager.runBlockingMain {
|
|
||||||
val client = GlobalManager.cheveretoClient;
|
|
||||||
client.use { cheveretoClient ->
|
|
||||||
// 1. 测试 File 上传
|
|
||||||
val file = File("data/temp/icons8-postgresql-96.png")
|
|
||||||
val resp1 = cheveretoClient.uploadFile(file, title = "PostgreSQL Logo", tags = "db,icon,test")
|
|
||||||
println("File 上传结果: ${resp1.statusCode} -> ${resp1.image?.url}")
|
|
||||||
|
|
||||||
// 2. 测试 ByteArrayInputStream 上传
|
|
||||||
val bytes = file.readBytes()
|
|
||||||
val inputStream = ByteArrayInputStream(bytes)
|
|
||||||
val resp2 = cheveretoClient.uploadStream(inputStream, fileName = "test", title = "From Stream", description = "测试 ByteArrayInputStream 上传")
|
|
||||||
println("Stream 上传结果: ${resp2.statusCode} -> ${resp2.image?.url}")
|
|
||||||
|
|
||||||
// 3. 测试 URL 上传
|
|
||||||
val testUrl = "https://img.icons8.com/color/96/postgresql.png"
|
|
||||||
val resp3 = cheveretoClient.uploadUrl(testUrl)
|
|
||||||
println("URL 上传结果: ${resp3.statusCode} -> ${resp3.image?.url}")
|
|
||||||
if (resp3.statusCode == 400) {
|
|
||||||
println(resp3.statusTxt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,82 +0,0 @@
|
||||||
package top.r394realms.ltdmanagertest.util
|
|
||||||
|
|
||||||
import io.ktor.client.*
|
|
||||||
import io.ktor.client.engine.cio.*
|
|
||||||
import io.ktor.client.plugins.contentnegotiation.*
|
|
||||||
import io.ktor.client.request.*
|
|
||||||
import io.ktor.client.request.forms.*
|
|
||||||
import io.ktor.client.statement.*
|
|
||||||
import io.ktor.http.*
|
|
||||||
import io.ktor.http.content.*
|
|
||||||
import io.ktor.serialization.kotlinx.json.*
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import top.r3944realms.ltdmanager.GlobalManager
|
|
||||||
import top.r3944realms.ltdmanager.chevereto.data.CheveretoResponse
|
|
||||||
import java.io.File
|
|
||||||
import kotlin.io.encoding.Base64
|
|
||||||
import kotlin.io.encoding.ExperimentalEncodingApi
|
|
||||||
|
|
||||||
@OptIn(ExperimentalEncodingApi::class)
|
|
||||||
fun main() = GlobalManager.runBlockingMain {
|
|
||||||
val client = HttpClient(CIO) {
|
|
||||||
install(ContentNegotiation) {
|
|
||||||
json(Json { ignoreUnknownKeys = true })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val filePath = "./data/temp/icons8-postgresql-96.png"
|
|
||||||
val file = File(filePath)
|
|
||||||
if (!file.exists()) {
|
|
||||||
println("文件不存在: ${file.absolutePath}")
|
|
||||||
return@runBlockingMain
|
|
||||||
}
|
|
||||||
|
|
||||||
val apiKey = "XXXX"
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 构建 multipart/form-data
|
|
||||||
val formDataContent = formData {
|
|
||||||
append("source", file.readBytes(), Headers.build {
|
|
||||||
append(HttpHeaders.ContentDisposition, "filename=\"${file.name}\"")
|
|
||||||
append(HttpHeaders.ContentType, ContentType.Image.PNG.toString())
|
|
||||||
})
|
|
||||||
append("format", "json")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 调试输出每个 part
|
|
||||||
formDataContent.forEach { part ->
|
|
||||||
println("Part Headers: ${part.headers}")
|
|
||||||
when (part) {
|
|
||||||
is PartData.FileItem -> println("Part File: ${part.originalFileName}, size=${part.provider()} bytes")
|
|
||||||
is PartData.FormItem -> println("Part Form: ${part.value}")
|
|
||||||
else -> println("Part Other: $part")
|
|
||||||
}
|
|
||||||
part.dispose()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 发送 POST 请求
|
|
||||||
val response: HttpResponse = client.submitFormWithBinaryData(
|
|
||||||
url = "https://pic.xiaobuawa.top/api/1/upload",
|
|
||||||
formData = formDataContent
|
|
||||||
) {
|
|
||||||
header ("X-API-Key", apiKey.trim())
|
|
||||||
}
|
|
||||||
|
|
||||||
val responseText = response.bodyAsText()
|
|
||||||
println("服务器返回原始内容:\n$responseText")
|
|
||||||
|
|
||||||
if (response.status.isSuccess()) {
|
|
||||||
val parsed = Json { ignoreUnknownKeys = true }
|
|
||||||
.decodeFromString(CheveretoResponse.serializer(), responseText)
|
|
||||||
println("上传成功,图片 URL: ${parsed.image?.url}")
|
|
||||||
} else {
|
|
||||||
println("上传失败,HTTP 状态码: ${response.status}")
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (e: Exception) {
|
|
||||||
println("上传过程中出现异常:")
|
|
||||||
e.printStackTrace()
|
|
||||||
} finally {
|
|
||||||
client.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,126 +0,0 @@
|
||||||
package top.r394realms.ltdmanagertest.util
|
|
||||||
|
|
||||||
import io.ktor.client.*
|
|
||||||
import io.ktor.client.engine.cio.*
|
|
||||||
import io.ktor.client.request.*
|
|
||||||
import io.ktor.client.request.forms.*
|
|
||||||
import io.ktor.client.statement.*
|
|
||||||
import io.ktor.http.*
|
|
||||||
import io.ktor.utils.io.core.*
|
|
||||||
import top.r3944realms.ltdmanager.GlobalManager
|
|
||||||
import top.r3944realms.ltdmanager.utils.LoggerUtil
|
|
||||||
import java.io.File
|
|
||||||
import kotlin.io.use
|
|
||||||
|
|
||||||
suspend fun uploadImageWithKtor(filePath: String, apiKey: String): String {
|
|
||||||
val client = HttpClient(CIO) {
|
|
||||||
// 添加引擎配置
|
|
||||||
engine {
|
|
||||||
// 增加超时设置
|
|
||||||
requestTimeout = 60000
|
|
||||||
}
|
|
||||||
// 添加日志拦截器来调试
|
|
||||||
expectSuccess = false // 不自动抛出异常,让我们自己处理
|
|
||||||
}
|
|
||||||
|
|
||||||
return client.use { httpClient ->
|
|
||||||
try {
|
|
||||||
val file = File(filePath)
|
|
||||||
|
|
||||||
// 检查文件是否存在
|
|
||||||
if (!file.exists()) {
|
|
||||||
throw Exception("文件不存在: $filePath")
|
|
||||||
}
|
|
||||||
|
|
||||||
LoggerUtil.logger.info("开始上传文件: ${file.name}, 大小: ${file.length()} bytes")
|
|
||||||
|
|
||||||
val response = httpClient.post("https://pic.xiaobuawa.top/api/1/upload") {
|
|
||||||
// 设置头信息
|
|
||||||
headers {
|
|
||||||
append("X-API-Key", apiKey.trim()) // 去除前后空格
|
|
||||||
append("User-Agent", "Mozilla/5.0 (compatible; MyApp/1.0)")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 使用正确的 multipart 格式
|
|
||||||
setBody(MultiPartFormDataContent(
|
|
||||||
formData {
|
|
||||||
// 使用 appendInput 而不是 append,更接近 curl 的行为
|
|
||||||
appendInput(
|
|
||||||
"source",
|
|
||||||
Headers.build {
|
|
||||||
append(HttpHeaders.ContentType, "image/png")
|
|
||||||
append(HttpHeaders.ContentDisposition, "filename=\"${file.name}\"")
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
buildPacket {
|
|
||||||
writeFully(file.readBytes())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
append("format", "json")
|
|
||||||
}
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
val statusCode = response.status.value
|
|
||||||
val responseText = response.bodyAsText()
|
|
||||||
|
|
||||||
LoggerUtil.logger.info("响应状态码: $statusCode")
|
|
||||||
LoggerUtil.logger.info("响应内容: $responseText")
|
|
||||||
|
|
||||||
if (statusCode != 200) {
|
|
||||||
throw Exception("上传失败,状态码: $statusCode, 响应: $responseText")
|
|
||||||
}
|
|
||||||
|
|
||||||
return@use responseText
|
|
||||||
} catch (e: Exception) {
|
|
||||||
LoggerUtil.logger.error("上传过程中发生错误: ${e.message}", e)
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 或者使用另一种更简单的方法
|
|
||||||
suspend fun uploadImageWithKtorSimple(filePath: String, apiKey: String): String {
|
|
||||||
val client = HttpClient(CIO)
|
|
||||||
|
|
||||||
return client.use { httpClient ->
|
|
||||||
val file = File(filePath)
|
|
||||||
|
|
||||||
val response = httpClient.submitFormWithBinaryData(
|
|
||||||
url = "https://pic.xiaobuawa.top/api/1/upload",
|
|
||||||
formData = formData {
|
|
||||||
append("source", file.readBytes(), Headers.build {
|
|
||||||
append(HttpHeaders.ContentType, "image/png")
|
|
||||||
append(HttpHeaders.ContentDisposition, "filename=\"${file.name}\"")
|
|
||||||
})
|
|
||||||
append("format", "json")
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
header("X-API-Key", apiKey.trim())
|
|
||||||
}
|
|
||||||
|
|
||||||
val responseText = response.bodyAsText()
|
|
||||||
LoggerUtil.logger.info("简单方法响应: $responseText")
|
|
||||||
responseText
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun main() = GlobalManager.runBlockingMain {
|
|
||||||
// 注意:API Key 前面不要有空格!
|
|
||||||
val apiKey = "XXXX"
|
|
||||||
val filePath = "./data/temp/icons8-postgresql-96.png"
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 先尝试简单方法
|
|
||||||
val result = uploadImageWithKtorSimple(filePath, apiKey)
|
|
||||||
println("上传成功: $result")
|
|
||||||
} catch (e: Exception) {
|
|
||||||
println("简单方法失败,尝试详细方法: ${e.message}")
|
|
||||||
try {
|
|
||||||
val result = uploadImageWithKtor(filePath, apiKey)
|
|
||||||
println("详细方法上传成功: $result")
|
|
||||||
} catch (e2: Exception) {
|
|
||||||
println("所有方法都失败: ${e2.message}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,82 +0,0 @@
|
||||||
package top.r394realms.ltdmanagertest.util
|
|
||||||
|
|
||||||
import top.r3944realms.ltdmanager.GlobalManager
|
|
||||||
|
|
||||||
fun main() = GlobalManager.runBlockingMain {
|
|
||||||
// 测试配置
|
|
||||||
val apiKey = "XXX"
|
|
||||||
val filePath = "./data/temp/icons8-postgresql-96.png"
|
|
||||||
|
|
||||||
println("=== 开始测试图片上传 ===")
|
|
||||||
println("API Key: ${apiKey.take(10)}...")
|
|
||||||
println("文件路径: $filePath")
|
|
||||||
|
|
||||||
// 测试1: 同步上传
|
|
||||||
println("\n--- 测试同步上传 ---")
|
|
||||||
try {
|
|
||||||
val result = ImageUploader.uploadImage(filePath, apiKey)
|
|
||||||
println("✅ 同步上传成功!")
|
|
||||||
println("响应结果: ${result.take(200)}...") // 只显示前200个字符
|
|
||||||
} catch (e: Exception) {
|
|
||||||
println("❌ 同步上传失败: ${e.message}")
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 测试2: 异步上传
|
|
||||||
println("\n--- 测试异步上传 ---")
|
|
||||||
ImageUploader.uploadImageAsync(filePath, apiKey) { result ->
|
|
||||||
result.onSuccess { response ->
|
|
||||||
println("✅ 异步上传成功!")
|
|
||||||
println("响应结果: ${response.take(200)}...")
|
|
||||||
}.onFailure { error ->
|
|
||||||
println("❌ 异步上传失败: ${error.message}")
|
|
||||||
error.printStackTrace()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 等待异步操作完成
|
|
||||||
println("等待异步操作完成...")
|
|
||||||
Thread.sleep(10000)
|
|
||||||
println("=== 测试结束 ===")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 使用 GlobalManager 的测试版本(如果需要)
|
|
||||||
fun mainWithGlobalManager() = GlobalManager.runBlockingMain {
|
|
||||||
val apiKey = "XXXX"
|
|
||||||
val filePath = "./data/temp/icons8-postgresql-96.png"
|
|
||||||
|
|
||||||
println("=== 使用 GlobalManager 测试图片上传 ===")
|
|
||||||
|
|
||||||
// 测试同步上传
|
|
||||||
try {
|
|
||||||
val result = ImageUploader.uploadImage(filePath, apiKey)
|
|
||||||
println("✅ 上传成功!")
|
|
||||||
println("响应: $result")
|
|
||||||
} catch (e: Exception) {
|
|
||||||
println("❌ 上传失败: ${e.message}")
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 简单的单元测试函数
|
|
||||||
fun testImageUpload() {
|
|
||||||
val testCases = listOf(
|
|
||||||
// (文件路径, API Key, 期望结果)
|
|
||||||
"./data/temp/icons8-postgresql-96.png" to "chv_YmZ_12a0828fd88823ad4ef16a0c551b4a10ae5ce1b3e3eb65b07d87eb30162cbc91ed520334018fce2d6ba06f9d58724cef66d30ab7f6292bd4e33ad5e0d96c6499",
|
|
||||||
"./data/temp/nonexistent.png" to "chv_YmZ_12a0828fd88823ad4ef16a0c551b4a10ae5ce1b3e3eb65b07d87eb30162cbc91ed520334018fce2d6ba06f9d58724cef66d30ab7f6292bd4e33ad5e0d96c6499", // 不存在的文件
|
|
||||||
"./data/temp/icons8-postgresql-96.png" to "invalid_key" // 无效的 API Key
|
|
||||||
)
|
|
||||||
|
|
||||||
testCases.forEachIndexed { index, (filePath, apiKey) ->
|
|
||||||
println("\n测试用例 ${index + 1}:")
|
|
||||||
println("文件: $filePath")
|
|
||||||
println("API Key: ${apiKey.take(10)}...")
|
|
||||||
|
|
||||||
try {
|
|
||||||
val result = ImageUploader.uploadImage(filePath, apiKey)
|
|
||||||
println("✅ 成功: ${result.take(100)}...")
|
|
||||||
} catch (e: Exception) {
|
|
||||||
println("❌ 失败: ${e.message}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue
Block a user