⚡ 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调用功能。
配置参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| gatewayUrl | String | 是 | 网关地址,正式环境: https://pay.kudianvip.com,测试环境: https://pay.test.kudianvip.com |
| mchId | String | 是 | 商户编号 |
| appId | String | 是 | 应用ID |
| appPrivateKey | String | 是 | 应用私钥(Base64编码格式),用于请求签名 |
| appSecretKey | String | 是 | AES加密密钥,用于请求/响应内容加密 |
| apiPublicKey | String | 是 | API平台公钥(Base64编码格式),用于响应验签 |
主要方法
suspend fun post(apiPath: String, bizParams: Map<String, Any>): String
发送POST请求到指定API路径(协程版本)。
参数:
apiPath: API路径,如/api/pay/createbizParams: 业务参数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/createbizParams: 业务参数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是否正确 |
| Exception | HTTP请求失败 | 检查网络连接和网关地址 |
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