跳到主要内容

⚡ Kotlin

声明

本 SDK 由 AI 生成,仅供参考使用。建议在实际生产环境使用前进行充分测试。

概述

平台 Kotlin Server SDK 提供了便捷的服务端接入方式,支持签名生成、请求加密、响应验签和解密等核心功能。

功能特性

  • 支持 SHA256withRSA 签名算法
  • 支持 AES 对称加密/解密
  • 灵活的 HTTP 客户端接口设计,支持多种 HTTP 客户端实现
  • 自动处理签名和加密逻辑
  • 完整的响应验签和解密
  • 简洁优雅的 Kotlin 代码风格

环境要求

  • Kotlin 1.6+
  • JDK 8+
  • Maven 3.x 或 Gradle 6.x

安装

Maven 依赖


<dependency>
<groupId>com.kudian</groupId>
<artifactId>kudian-sdk-kotlin</artifactId>
<version>1.0.0</version>
</dependency>

<!-- HTTP客户端(OkHttp) -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.12.0</version>
</dependency>

<!-- 协程支持 -->
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines-core</artifactId>
<version>1.7.3</version>
</dependency>

Gradle 依赖

implementation 'com.kudian:kudian-sdk-kotlin:1.0.0'

// HTTP客户端(OkHttp)
implementation 'com.squareup.okhttp3:okhttp:4.12.0'

// 协程支持
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3'

快速开始

1. 创建配置

import com.kudian.sdk.KudianClient
import com.kudian.sdk.http.OkHttpClient
import com.kudian.sdk.model.clientConfig

// 创建配置
val config = clientConfig {
gatewayUrl = "https://pay.kudianvip.com" // 正式环境
// gatewayUrl = "https://pay.test.kudianvip.com" // 测试环境
mchId = "your_mch_id"
appId = "your_app_id"
appPrivateKey = "your_app_private_key" // 应用私钥(Base64格式)
appSecretKey = "your_secret_key" // AES加密密钥
apiPublicKey = "your_api_public_key" // API平台公钥(Base64格式)
}

// 创建客户端(使用OkHttp)
val client = KudianClient(config, OkHttpClient())

2. 发起API请求

import kotlinx.coroutines.runBlocking
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonObject

fun main() = runBlocking {
// 构造业务参数
val bizParams = mapOf(
"param1" to "value1",
"param2" to "value2"
)

// 发送请求
val response = client.post("/api/path", bizParams)

// 解析响应
val json = Json.parseToJsonElement(response).jsonObject
val code = json["code"]?.toString()?.toIntOrNull() ?: -1

if (code == 0) {
println("业务数据: ${json["data"]}")
} else {
println("请求失败: ${json["msg"]}")
}
}

核心 API

KudianClient

主客户端类,提供所有API调用功能。

配置参数

参数类型必填说明
gatewayUrlString网关地址,正式环境: https://pay.kudianvip.com,测试环境: https://pay.test.kudianvip.com
mchIdString商户编号
appIdString应用ID
appPrivateKeyString应用私钥(Base64编码格式),用于请求签名
appSecretKeyStringAES加密密钥,用于请求/响应内容加密
apiPublicKeyStringAPI平台公钥(Base64编码格式),用于响应验签

主要方法

suspend fun post(apiPath: String, bizParams: Map<String, Any>): String

发送POST请求到指定API路径(协程版本)。

参数:

  • apiPath: API路径,如 /api/pay/create
  • bizParams: 业务参数Map

返回:

  • 自动解密后的业务数据JSON字符串

示例:

val params = mapOf(
"order_no" to "ORDER202401010001",
"amount" to 10000,
"subject" to "测试订单"
)

val result = runBlocking {
client.post("/api/pay/create", params)
}

fun postBlocking(apiPath: String, bizParams: Map<String, Any>): String

发送POST请求到指定API路径(阻塞版本)。

参数:

  • apiPath: API路径,如 /api/pay/create
  • bizParams: 业务参数Map

返回:

  • 自动解密后的业务数据JSON字符串

示例:

val params = mapOf(
"order_no" to "ORDER202401010001",
"amount" to 10000,
"subject" to "测试订单"
)

val result = client.postBlocking("/api/pay/create", params)

fun verifyNotify(notifyParams: Map<String, String>): Boolean

验证回调通知的签名。

参数:

  • notifyParams: 回调参数Map

返回:

  • true: 验签成功
  • false: 验签失败

示例:

// 在你的Controller中
@PostMapping("/notify")
fun handleNotify(@RequestBody params: Map<String, String>): String {
// 验证签名
if (!client.verifyNotify(params)) {
return "FAIL"
}

// 获取业务数据(已自动解密)
val bizData = client.decryptNotify(params["result"] ?: "")
println("回调数据: $bizData")

// 处理业务逻辑...

return "SUCCESS"
}

完整代码实现

1. ClientConfig.kt - 配置类

package com.kudian.sdk.model

/**
* SDK配置类
*/
data class ClientConfig(
/**
* 网关地址
*/
val gatewayUrl: String,

/**
* 商户编号
*/
val mchId: String,

/**
* 应用ID
*/
val appId: String,

/**
* 应用私钥(Base64格式)
*/
val appPrivateKey: String,

/**
* API平台公钥(Base64格式)
*/
val apiPublicKey: String,

/**
* AES加密密钥
*/
val appSecretKey: String,

/**
* 连接超时时间(秒),默认30秒
*/
val connectTimeout: Int = 30,

/**
* 读取超时时间(秒),默认30秒
*/
val readTimeout: Int = 30
)

/**
* DSL构建器
*/
fun clientConfig(block: ClientConfigBuilder.() -> Unit): ClientConfig {
return ClientConfigBuilder().apply(block).build()
}

class ClientConfigBuilder {
private var gatewayUrl: String? = null
private var mchId: String? = null
private var appId: String? = null
private var appPrivateKey: String? = null
private var apiPublicKey: String? = null
private var appSecretKey: String? = null
private var connectTimeout: Int = 30
private var readTimeout: Int = 30

fun gatewayUrl(value: String) { this.gatewayUrl = value }
fun mchId(value: String) { this.mchId = value }
fun appId(value: String) { this.appId = value }
fun appPrivateKey(value: String) { this.appPrivateKey = value }
fun apiPublicKey(value: String) { this.apiPublicKey = value }
fun appSecretKey(value: String) { this.appSecretKey = value }
fun connectTimeout(value: Int) { this.connectTimeout = value }
fun readTimeout(value: Int) { this.readTimeout = value }

fun build(): ClientConfig {
return ClientConfig(
gatewayUrl = requireNotNull(gatewayUrl) { "gatewayUrl is required" },
mchId = requireNotNull(mchId) { "mchId is required" },
appId = requireNotNull(appId) { "appId is required" },
appPrivateKey = requireNotNull(appPrivateKey) { "appPrivateKey is required" },
apiPublicKey = requireNotNull(apiPublicKey) { "apiPublicKey is required" },
appSecretKey = requireNotNull(appSecretKey) { "appSecretKey is required" },
connectTimeout = connectTimeout,
readTimeout = readTimeout
)
}
}

2. CryptoUtil.kt - 加密工具类

package com.kudian.sdk.util

import java.nio.charset.StandardCharsets
import java.security.*
import java.security.spec.PKCS8EncodedKeySpec
import java.security.spec.X509EncodedKeySpec
import java.util.Base64
import javax.crypto.Cipher
import javax.crypto.spec.SecretKeySpec

/**
* 加密工具类
*/
object CryptoUtil {

private const val AES_ALGORITHM = "AES/ECB/PKCS5Padding"
private const val RSA_SIGNATURE_ALGORITHM = "SHA256withRSA"

/**
* AES加密
*
* @param plainText 明文
* @param key 密钥
* @return Base64编码的密文
* @throws Exception 加密异常
*/
fun aesEncrypt(plainText: String, key: String): String {
// 确保密钥是32字节(AES-256)
val keyBytes = key.toByteArray(StandardCharsets.UTF_8)
val keyBytes32 = ByteArray(32)
val length = minOf(keyBytes.size, 32)
System.arraycopy(keyBytes, 0, keyBytes32, 0, length)

val cipher = Cipher.getInstance(AES_ALGORITHM)
val keySpec = SecretKeySpec(keyBytes32, "AES")
cipher.init(Cipher.ENCRYPT_MODE, keySpec)
val encrypted = cipher.doFinal(plainText.toByteArray(StandardCharsets.UTF_8))
return Base64.getEncoder().encodeToString(encrypted)
}

/**
* AES解密
*
* @param cipherText Base64编码的密文
* @param key 密钥
* @return 明文
* @throws Exception 解密异常
*/
fun aesDecrypt(cipherText: String, key: String): String {
// 确保密钥是32字节(AES-256)
val keyBytes = key.toByteArray(StandardCharsets.UTF_8)
val keyBytes32 = ByteArray(32)
val length = minOf(keyBytes.size, 32)
System.arraycopy(keyBytes, 0, keyBytes32, 0, length)

val cipher = Cipher.getInstance(AES_ALGORITHM)
val keySpec = SecretKeySpec(keyBytes32, "AES")
cipher.init(Cipher.DECRYPT_MODE, keySpec)
val decoded = Base64.getDecoder().decode(cipherText)
val decrypted = cipher.doFinal(decoded)
return String(decrypted, StandardCharsets.UTF_8)
}

/**
* SHA256withRSA签名
*
* @param message 待签名消息
* @param privateKeyBytes 私钥字节数组(Base64解码后)
* @return Base64编码的签名
* @throws Exception 签名异常
*/
fun signSHA256withRSA(message: ByteArray, privateKeyBytes: ByteArray): String {
val keySpec = PKCS8EncodedKeySpec(privateKeyBytes)
val keyFactory = KeyFactory.getInstance("RSA")
val privateKey = keyFactory.generatePrivate(keySpec)

val signature = Signature.getInstance(RSA_SIGNATURE_ALGORITHM)
signature.initSign(privateKey)
signature.update(message)
val signBytes = signature.sign()

return Base64.getEncoder().encodeToString(signBytes)
}

/**
* SHA256withRSA验签
*
* @param message 待验签消息
* @param publicKeyBytes 公钥字节数组(Base64解码后)
* @param sign Base64编码的签名
* @return true-验签成功,false-验签失败
* @throws Exception 验签异常
*/
fun verifySHA256withRSA(message: ByteArray, publicKeyBytes: ByteArray, sign: String): Boolean {
val keySpec = X509EncodedKeySpec(publicKeyBytes)
val keyFactory = KeyFactory.getInstance("RSA")
val publicKey = keyFactory.generatePublic(keySpec)

val signature = Signature.getInstance(RSA_SIGNATURE_ALGORITHM)
signature.initVerify(publicKey)
signature.update(message)
val signBytes = Base64.getDecoder().decode(sign)

return signature.verify(signBytes)
}
}

3. SignUtil.kt - 签名工具类

package com.kudian.sdk.util

/**
* 签名工具类
*/
object SignUtil {

/**
* 生成签名字符串
* 将参数按ASCII码排序后拼接成 key1=value1&key2=value2 格式
*
* @param params 参数Map
* @return 待签名字符串
*/
fun makeSignString(params: Map<String, String>): String {
// 过滤sign字段和空值
val filteredParams = params
.filter { (key, value) ->
key != "sign" && !value.isNullOrEmpty()
}
.keys
.sorted()

// 拼接字符串
return filteredParams.joinToString("&") { key ->
"$key=${params[key]}"
}
}
}

4. HttpClient.kt - HTTP客户端接口

package com.kudian.sdk.http

/**
* HTTP客户端接口
* 支持多种HTTP客户端实现
*/
interface HttpClient {
/**
* 发送POST请求
*
* @param url 请求URL
* @param headers 请求头
* @param requestBody 请求体(JSON字符串)
* @return 响应体(JSON字符串)
* @throws Exception 请求异常
*/
fun post(url: String, headers: Map<String, String>, requestBody: String): String
}

5. KudianClient.kt - 主客户端类

package com.kudian.sdk

import com.kudian.sdk.http.HttpClient
import com.kudian.sdk.model.ClientConfig
import com.kudian.sdk.util.CryptoUtil
import com.kudian.sdk.util.SignUtil
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.util.Base64
import java.util.UUID

/**
* 酷点支付SDK主客户端
*/
class KudianClient(
private val config: ClientConfig,
private val httpClient: HttpClient
) {

private val objectMapper: com.fasterxml.jackson.databind.ObjectMapper =
com.fasterxml.jackson.databind.ObjectMapper()

/**
* 发送POST请求(协程版本)
*
* @param apiPath API路径
* @param bizParams 业务参数
* @return 解密后的业务数据JSON字符串
* @throws Exception 请求异常
*/
suspend fun post(apiPath: String, bizParams: Map<String, Any>): String =
withContext(Dispatchers.IO) {
doPost(apiPath, bizParams)
}

/**
* 发送POST请求(阻塞版本)
*
* @param apiPath API路径
* @param bizParams 业务参数
* @return 解密后的业务数据JSON字符串
* @throws Exception 请求异常
*/
fun postBlocking(apiPath: String, bizParams: Map<String, Any>): String {
return doPost(apiPath, bizParams)
}

private fun doPost(apiPath: String, bizParams: Map<String, Any>): String {
// 1. 构造完整请求URL
val url = config.gatewayUrl + apiPath

// 2. 将业务参数转换为JSON并加密
val bizJson = objectMapper.writeValueAsString(bizParams)
val encryptedContent = CryptoUtil.aesEncrypt(bizJson, config.appSecretKey)

// 3. 构造公共参数
val publicParams = mutableMapOf(
"mch_id" to config.mchId,
"app_id" to config.appId,
"timestamp" to System.currentTimeMillis().toString(),
"nonce_str" to generateNonceStr(),
"sign_type" to "SHA",
"content" to encryptedContent,
"version" to "2.0"
)

// 4. 生成签名
val signString = SignUtil.makeSignString(publicParams)
val privateKeyBytes = Base64.getDecoder().decode(config.appPrivateKey)
val sign = CryptoUtil.signSHA256withRSA(
signString.toByteArray(Charsets.UTF_8),
privateKeyBytes
)
publicParams["sign"] = sign

// 5. 发送请求
val requestBody = objectMapper.writeValueAsString(publicParams)
println("请求URL: $url")
println("请求参数: $requestBody")

val headers = mapOf(
"Content-Type" to "application/json"
)

val responseBody = httpClient.post(url, headers, requestBody)
println("响应结果: $responseBody")

// 6. 解析响应并验签
@Suppress("UNCHECKED_CAST")
val response = objectMapper.readValue(responseBody, Map::class.java) as Map<String, Any>
val code = (response["code"] as? Double)?.toInt() ?: -1

if (code == 0) {
// 成功响应,验证签名
val responseSign = response["sign"] as? String ?: ""
val verifyParams = response
.filterKeys { it != "sign" && response[it] != null }
.mapValues { it.value.toString() }

val verifyString = SignUtil.makeSignString(verifyParams)
val publicKeyBytes = Base64.getDecoder().decode(config.apiPublicKey)
val verifyResult = CryptoUtil.verifySHA256withRSA(
verifyString.toByteArray(Charsets.UTF_8),
publicKeyBytes,
responseSign
)

if (!verifyResult) {
throw Exception("响应签名验证失败")
}

// 解密业务数据
val encryptedResult = response["result"] as? String ?: ""
val decryptedData = CryptoUtil.aesDecrypt(encryptedResult, config.appSecretKey)
println("解密后的业务数据: $decryptedData")

return decryptedData
} else {
// 失败响应
val msg = response["msg"] as? String ?: "未知错误"
throw Exception("请求失败: code=$code, msg=$msg")
}
}

/**
* 验证回调通知签名
*
* @param notifyParams 回调参数
* @return true-验签成功,false-验签失败
* @throws Exception 验签异常
*/
fun verifyNotify(notifyParams: Map<String, String>): Boolean {
val sign = notifyParams["sign"]
if (sign.isNullOrEmpty()) {
return false
}

val verifyParams = notifyParams.toMap()
val verifyString = SignUtil.makeSignString(verifyParams)

val publicKeyBytes = Base64.getDecoder().decode(config.apiPublicKey)
return CryptoUtil.verifySHA256withRSA(
verifyString.toByteArray(Charsets.UTF_8),
publicKeyBytes,
sign
)
}

/**
* 解密回调通知的业务数据
*
* @param encryptedResult 加密的业务数据
* @return 解密后的业务数据JSON字符串
* @throws Exception 解密异常
*/
fun decryptNotify(encryptedResult: String): String {
return CryptoUtil.aesDecrypt(encryptedResult, config.appSecretKey)
}

/**
* 生成随机字符串
*
* @return 随机字符串
*/
private fun generateNonceStr(): String {
return UUID.randomUUID().toString().replace("-", "").take(32)
}
}

可选实现

OkHttpClient.kt - OkHttp 客户端实现

package com.kudian.sdk.http

import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import java.util.concurrent.TimeUnit

/**
* 基于OkHttp的HTTP客户端实现
*/
class OkHttpClient(
private val connectTimeout: Int = 30,
private val readTimeout: Int = 30
) : HttpClient {

private val client = OkHttpClient.Builder()
.connectTimeout(connectTimeout.toLong(), TimeUnit.SECONDS)
.readTimeout(readTimeout.toLong(), TimeUnit.SECONDS)
.build()

override fun post(url: String, headers: Map<String, String>, requestBody: String): String {
val mediaType = "application/json; charset=utf-8".toMediaType()
val body = requestBody.toRequestBody(mediaType)

val requestBuilder = Request.Builder()
.url(url)
.post(body)

// 设置请求头
headers.forEach { (key, value) ->
requestBuilder.addHeader(key, value)
}

val request = requestBuilder.build()

client.newCall(request).execute().use { response ->
if (!response.isSuccessful) {
throw Exception("请求失败: ${response.code}")
}
return response.body?.string() ?: throw Exception("响应体为空")
}
}
}

ApacheHttpClient.kt - Apache HttpClient 实现

package com.kudian.sdk.http

import org.apache.http.HttpEntity
import org.apache.http.client.methods.HttpPost
import org.apache.http.entity.StringEntity
import org.apache.http.impl.client.CloseableHttpClient
import org.apache.http.impl.client.HttpClients
import org.apache.http.util.EntityUtils

/**
* 基于Apache HttpClient的HTTP客户端实现
*/
class ApacheHttpClient(
private val connectTimeout: Int = 30,
private val readTimeout: Int = 30
) : HttpClient {

private val client: CloseableHttpClient = HttpClients.custom()
.build()

override fun post(url: String, headers: Map<String, String>, requestBody: String): String {
val httpPost = HttpPost(url)

// 设置请求头
headers.forEach { (key, value) ->
httpPost.setHeader(key, value)
}

// 设置请求体
val entity: HttpEntity = StringEntity(requestBody, "UTF-8")
httpPost.entity = entity

return client.execute(httpPost).use { response ->
val statusCode = response.statusLine.statusCode
if (statusCode != 200) {
throw Exception("请求失败: $statusCode")
}
EntityUtils.toString(response.entity, "UTF-8")
}
}
}

使用示例

完整示例:创建支付订单

package com.kudian.sdk.example

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
import com.fasterxml.jackson.module.kotlin.registerKotlinModule
import com.kudian.sdk.KudianClient
import com.kudian.sdk.http.OkHttpClient
import com.kudian.sdk.model.clientConfig
import kotlinx.coroutines.runBlocking
import java.util.*

fun main() = runBlocking {
// 1. 创建配置
val config = clientConfig {
gatewayUrl = "https://pay.test.kudianvip.com"
mchId = "your_mch_id"
appId = "your_app_id"
appPrivateKey = "MIIEvQIBADANBgkqhkiG9w0BAQE..." // Base64格式的私钥
apiPublicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA..." // Base64格式的公钥
appSecretKey = "your_secret_key_32_bytes" // 32字节的AES密钥
}

// 2. 创建客户端
val client = KudianClient(config, OkHttpClient())

// 3. 构造业务参数
val bizParams = mapOf(
"order_no" to "ORDER${System.currentTimeMillis()}",
"amount" to 10000,
"currency" to "CNY",
"subject" to "测试商品",
"notify_url" to "https://your-domain.com/notify"
)

try {
// 4. 发送请求
val result = client.post("/api/pay/create", bizParams)

// 5. 解析响应
val objectMapper = ObjectMapper().registerKotlinModule()
val data: Map<String, Any> = objectMapper.readValue(result)

println("支付订单创建成功!")
println("订单号: ${data["order_no"]}")
println("支付URL: ${data["pay_url"]}")

} catch (e: Exception) {
println("订单创建失败: ${e.message}")
e.printStackTrace()
}
}

Spring Boot 回调通知处理示例

package com.kudian.sdk.example.controller

import com.kudian.sdk.KudianClient
import com.kudian.sdk.http.OkHttpClient
import com.kudian.sdk.model.clientConfig
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RestController

@RestController
@RequestMapping("/notify")
class PayNotifyController {

private val client: KudianClient by lazy {
val config = clientConfig {
gatewayUrl = "https://pay.kudianvip.com"
mchId = "your_mch_id"
appId = "your_app_id"
appPrivateKey = "your_app_private_key"
appSecretKey = "your_secret_key"
apiPublicKey = "your_api_public_key"
}
KudianClient(config, OkHttpClient())
}

@PostMapping("/pay")
fun handlePayNotify(@RequestBody params: Map<String, String>): String {
return try {
// 1. 验证签名
if (!client.verifyNotify(params)) {
System.err.println("签名验证失败")
return "FAIL"
}

// 2. 解密业务数据
val bizData = client.decryptNotify(params["result"] ?: "")
println("收到支付回调: $bizData")

// 3. 解析业务数据并处理
// TODO: 根据业务逻辑处理支付结果
// 更新订单状态、发货等

// 4. 返回成功
"SUCCESS"
} catch (e: Exception) {
e.printStackTrace()
"FAIL"
}
}
}

Ktor 回调通知处理示例

package com.kudian.sdk.example

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.registerKotlinModule
import com.kudian.sdk.KudianClient
import com.kudian.sdk.http.OkHttpClient
import com.kudian.sdk.model.clientConfig
import io.ktor.server.application.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*

// 创建客户端
val client = KudianClient(
clientConfig {
gatewayUrl = "https://pay.kudianvip.com"
mchId = "your_mch_id"
appId = "your_app_id"
appPrivateKey = "your_app_private_key"
appSecretKey = "your_secret_key"
apiPublicKey = "your_api_public_key"
},
OkHttpClient()
)

fun Route.notifyRoutes() {
val objectMapper = ObjectMapper().registerKotlinModule()

post("/notify/pay") {
try {
// 1. 解析请求参数
val params = call.receive<Map<String, String>>()

// 2. 验证签名
if (!client.verifyNotify(params)) {
println("签名验证失败")
call.respondText("FAIL")
return@post
}

// 3. 解密业务数据
val bizData = client.decryptNotify(params["result"] ?: "")
println("收到支付回调: $bizData")

// 4. 解析业务数据并处理
val data = objectMapper.readValue<Map<String, Any>>(bizData)
// TODO: 根据业务逻辑处理支付结果
// 更新订单状态、发货等

// 5. 返回成功
call.respondText("SUCCESS")
} catch (e: Exception) {
e.printStackTrace()
call.respondText("FAIL")
}
}
}

协程异步请求示例

package com.kudian.sdk.example

import com.kudian.sdk.KudianClient
import com.kudian.sdk.http.OkHttpClient
import com.kudian.sdk.model.clientConfig
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.Channel

fun main() = runBlocking {
// 创建客户端
val config = clientConfig {
gatewayUrl = "https://pay.test.kudianvip.com"
mchId = "your_mch_id"
appId = "your_app_id"
appPrivateKey = "your_app_private_key"
appSecretKey = "your_secret_key"
apiPublicKey = "your_api_public_key"
}
val client = KudianClient(config, OkHttpClient())

// 1. 串行请求
val result1 = client.post("/api/path1", mapOf("key" to "value1"))
val result2 = client.post("/api/path2", mapOf("key" to "value2"))
println("串行请求完成")

// 2. 并行请求
val deferred1 = async { client.post("/api/path1", mapOf("key" to "value1")) }
val deferred2 = async { client.post("/api/path2", mapOf("key" to "value2")) }
val (parallelResult1, parallelResult2) = awaitAll(deferred1, deferred2)
println("并行请求完成")

// 3. 批量请求
val urls = listOf("/api/path1", "/api/path2", "/api/path3")
val results = urls.map { url ->
async { client.post(url, mapOf("key" to "value")) }
}.awaitAll()

println("批量请求完成: ${results.size} 个请求")
}

异常处理

SDK可能抛出的异常:

异常类型说明处理建议
IllegalArgumentException配置参数错误检查配置参数是否完整和正确
Exception签名验签失败检查密钥是否正确
Exception加解密失败检查appSecretKey是否正确
ExceptionHTTP请求失败检查网络连接和网关地址

Kotlin 异常处理最佳实践

import com.kudian.sdk.KudianClient
import com.kudian.sdk.http.OkHttpClient
import com.kudian.sdk.model.clientConfig

fun handleRequestSafely(client: KudianClient) {
// 方式1: try-catch
try {
val result = client.postBlocking("/api/path", mapOf())
println("请求成功: $result")
} catch (e: Exception) {
println("请求失败: ${e.message}")
}

// 方式2: runCatching(Kotlin惯用方式)
val result = runCatching {
client.postBlocking("/api/path", mapOf())
}.onSuccess { response ->
println("请求成功: $response")
}.onFailure { exception ->
println("请求失败: ${exception.message}")
}
}

常见问题

1. 如何获取密钥?

  • app_private_key: 商户自行生成RSA密钥对,将公钥上传到商户后台
  • api_public_key: 从商户后台获取平台公钥
  • secret_key: 从商户后台获取或设置

2. 如何生成RSA密钥对?

可以使用OpenSSL命令生成:

# 生成私钥
openssl genrsa -out app_private_key.pem 2048

# 提取公钥
openssl rsa -in app_private_key.pem -pubout -out app_public_key.pem

# 转换为PKCS8格式(Kotlin需要)
openssl pkcs8 -topk8 -inform PEM -in app_private_key.pem -outform PEM -nocrypt -out app_private_key_pkcs8.pem

# 转换为Base64格式
base64 -w 0 app_private_key_pkcs8.pem
base64 -w 0 app_public_key.pem

3. 如何使用自定义HTTP客户端?

SDK支持自定义HTTP客户端实现,只需实现 HttpClient 接口:

// 使用内置的OkHttpClient
val client1 = KudianClient(config, OkHttpClient())

// 自定义实现
class CustomHttpClient : HttpClient {
override fun post(url: String, headers: Map<String, String>, requestBody: String): String {
// 自定义HTTP客户端实现
// 例如使用其他HTTP库或添加特殊逻辑
return ""
}
}

val client2 = KudianClient(config, CustomHttpClient())

4. 日志配置

SDK使用 println 记录日志,可以通过以下方式自定义:

// 1. 使用SLF4J+Logback
import org.slf4j.LoggerFactory

private val logger = LoggerFactory.getLogger("KudianSDK")

class KudianClient(...) {
private fun log(message: String) {
logger.debug(message)
// 或者根据需要使用不同的日志级别
}
}

// 2. 在应用启动时配置日志
// logback.xml
<configuration>
<logger name="KudianSDK" level="DEBUG"/>
</configuration>

5. 协程与阻塞版本的选择

// 协程版本:适用于异步场景(Web框架、批量处理等)
val client = KudianClient(config, OkHttpClient())
val result = runBlocking {
client.post("/api/path", params) // 自动在IO线程执行
}

// 阻塞版本:适用于同步场景(命令行应用、传统Servlet等)
val client = KudianClient(config, OkHttpClient())
val result = client.postBlocking("/api/path", params) // 阻塞调用

// 推荐在Spring Boot中使用协程版本配合协程控制器
@Suspended
@PostMapping("/pay")
suspend fun createPay(@RequestBody params: Map<String, Any>): String {
return client.post("/api/pay/create", params)
}

6. 项目结构建议

kudian-sdk-kotlin/
├── src/
│ └── main/
│ └── kotlin/
│ └── com/
│ └── kudian/
│ └── sdk/
│ ├── http/
│ │ ├── HttpClient.kt
│ │ ├── OkHttpClient.kt
│ │ └── ApacheHttpClient.kt
│ ├── model/
│ │ └── ClientConfig.kt
│ ├── util/
│ │ ├── CryptoUtil.kt
│ │ └── SignUtil.kt
│ └── KudianClient.kt
├── build.gradle.kts
└── README.md
预约咨询