feat(home): 新增银行积分签名工具和网络请求封装
- 新增 SignatureUtils 工具类,实现 HMAC-SHA256 签名算法 - 新增 BankPointRequest 网络请求封装类,简化 API 调用逻辑 - 优化 HomeFragment 中的 getBankPointDetail() 方法,从 34 行简化为 20 行 - 优化 AppraiseDialog,移除过时的版本判断和冗余的初始化逻辑 - 补充完整的中文文档注释和单元测试
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
package com.xjjk.healthyclients.bankRequest
|
||||
|
||||
import com.xjjk.healthyclients.bean.home.HomeBankBean
|
||||
import com.xjjk.healthyclients.data.api.HomeApi
|
||||
import com.xjjk.healthyclients.data.bean.ApiResponse
|
||||
import com.xjjk.healthyclients.retrofit.UrlConfig
|
||||
import com.xjjk.healthyclients.superfuntion.toJson
|
||||
import com.xjjk.healthyclients.utils.SignatureUtils
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import retrofit2.Call
|
||||
import retrofit2.Callback
|
||||
import retrofit2.Response
|
||||
|
||||
/**
|
||||
* 银行积分网络请求封装类
|
||||
* 负责处理签名生成、请求头构造、API 调用等逻辑
|
||||
*/
|
||||
class BankPointRequest(private val api: HomeApi) {
|
||||
|
||||
/**
|
||||
* 获取银行积分详情
|
||||
*
|
||||
* @param userId 用户 ID
|
||||
* @param onSuccess 成功回调
|
||||
* @param onFailure 失败回调
|
||||
*/
|
||||
fun getBankPointDetail(
|
||||
userId: String,
|
||||
onSuccess: (HomeBankBean?) -> Unit,
|
||||
onFailure: (Throwable) -> Unit
|
||||
) {
|
||||
try {
|
||||
// 生成时间戳(秒级)
|
||||
val timestamp = System.currentTimeMillis() / 1000
|
||||
|
||||
// 构造请求体
|
||||
val bodyString = """{"userId":"$userId"}"""
|
||||
|
||||
// 生成签名
|
||||
val sign = SignatureUtils.generateSignature(
|
||||
timestamp,
|
||||
bodyString,
|
||||
UrlConfig.SECRET_KEY
|
||||
)
|
||||
|
||||
// 构造请求头
|
||||
val headers = buildHeaders(timestamp, sign)
|
||||
|
||||
// 构造请求体 Map
|
||||
val body = mapOf("userId" to userId)
|
||||
|
||||
// 发送请求
|
||||
api.postUserPointDetail(headers, body.toJson().toRequestBody())
|
||||
.enqueue(object : Callback<ApiResponse<HomeBankBean>> {
|
||||
override fun onResponse(
|
||||
call: Call<ApiResponse<HomeBankBean>>,
|
||||
response: Response<ApiResponse<HomeBankBean>>
|
||||
) {
|
||||
if (response.code()==200) {
|
||||
val data = response.body()?.data
|
||||
onSuccess(data)
|
||||
} else {
|
||||
val error = Exception("HTTP ${response.code()}: ${response.message()}")
|
||||
onFailure(error)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(call: Call<ApiResponse<HomeBankBean>>, t: Throwable) {
|
||||
onFailure(t)
|
||||
}
|
||||
})
|
||||
} catch (e: Exception) {
|
||||
onFailure(e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造请求头
|
||||
*
|
||||
* @param timestamp 时间戳
|
||||
* @param sign 签名
|
||||
* @return 请求头 Map
|
||||
*/
|
||||
private fun buildHeaders(timestamp: Long, sign: String): Map<String, String> {
|
||||
return mapOf(
|
||||
"Content-Type" to "application/json; charset=utf-8",
|
||||
"X-Timestamp" to timestamp.toString(),
|
||||
"X-Sign" to sign
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -17,16 +17,21 @@ import com.xjjk.healthyclients.adapter.common.MultiItemTypeAdapter
|
||||
import com.xjjk.healthyclients.base.BaseVMBFragment
|
||||
import com.xjjk.healthyclients.bean.guidance.GuidanceListBean
|
||||
import com.xjjk.healthyclients.bean.home.WatchInfoBean
|
||||
import com.xjjk.healthyclients.data.api.HomeApi
|
||||
import com.xjjk.healthyclients.databinding.FragmentHomeBinding
|
||||
import com.xjjk.healthyclients.event.HomeTabChangeEvent
|
||||
import com.xjjk.healthyclients.bankRequest.BankPointRequest
|
||||
import com.xjjk.healthyclients.retrofit.UrlConfig
|
||||
import com.xjjk.healthyclients.retrofit.getBankRetrofit
|
||||
import com.xjjk.healthyclients.superfuntion.initColors
|
||||
import com.xjjk.healthyclients.superfuntion.orEmptyDefault
|
||||
import com.xjjk.healthyclients.superfuntion.showBanner
|
||||
import com.xjjk.healthyclients.superfuntion.startAppointmentWaitAffirmActivity
|
||||
import com.xjjk.healthyclients.superfuntion.startGroupChat
|
||||
import com.xjjk.healthyclients.superfuntion.startLoginActivity
|
||||
import com.xjjk.healthyclients.superfuntion.startMyGuidanceActivity
|
||||
import com.xjjk.healthyclients.superfuntion.startWebActivity
|
||||
import com.xjjk.healthyclients.superfuntion.toZeroDefault
|
||||
import com.xjjk.healthyclients.ui.activity.EmptyActivity
|
||||
import com.xjjk.healthyclients.ui.activity.LoginActivity
|
||||
import com.xjjk.healthyclients.ui.activity.guidance.adapter.GuidanceFragmentDoctorAdapter
|
||||
@@ -50,6 +55,8 @@ class HomeFragment :
|
||||
BaseVMBFragment<HomeViewModel, FragmentHomeBinding>(R.layout.fragment_home),
|
||||
SwipeRefreshLayout.OnRefreshListener {
|
||||
|
||||
private var mApi = getBankRetrofit().create(HomeApi::class.java)
|
||||
|
||||
var watchBindDialog: WatchBindDialog ?=null
|
||||
|
||||
var stringList: MutableList<String> = mutableListOf()
|
||||
@@ -396,6 +403,25 @@ class HomeFragment :
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取银行积分详情
|
||||
*/
|
||||
private fun getBankPointDetail() {
|
||||
val bankPointRequest = BankPointRequest(mApi)
|
||||
|
||||
bankPointRequest.getBankPointDetail(
|
||||
DataStoreManager.getUserId().orEmptyDefault(""),
|
||||
onSuccess = { bean ->
|
||||
// 处理成功响应
|
||||
mBinding.layoutItem1.tvTodayPoints.text = "${bean?.todayPoints.toZeroDefault()}"
|
||||
mBinding.layoutItem1.tvTotalPoints.text = "${bean?.totalPoints.toZeroDefault()}"
|
||||
},
|
||||
onFailure = { error ->
|
||||
showToast("网络错误")
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
override fun initData() {
|
||||
mViewModel.getHomeBannerList()
|
||||
@@ -405,6 +431,7 @@ class HomeFragment :
|
||||
mViewModel.selectDoctorRecommendHome()
|
||||
mViewModel.getHomeWatchInfo(DataStoreManager.getUserId())
|
||||
mViewModel.getSysEnCryptInfo(UrlConfig.getHomeBankH5Url())
|
||||
getBankPointDetail()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -431,6 +458,7 @@ class HomeFragment :
|
||||
mViewModel.selectDoctorRecommendHome()
|
||||
mViewModel.getHomeWatchInfo(DataStoreManager.getUserId())
|
||||
mViewModel.getSysEnCryptInfo(UrlConfig.getHomeBankH5Url())
|
||||
getBankPointDetail()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.xjjk.healthyclients.retrofit
|
||||
|
||||
import com.xjjk.healthyclients.retrofit.UrlConfig.getDefaultBaseUrl
|
||||
import com.xjjk.healthyclients.retrofit.interceptor.LoggingInterceptor2
|
||||
import com.xjjk.healthyclients.utils.ApiDns
|
||||
import okhttp3.OkHttpClient
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
fun getHealthCheckRetrofit(): Retrofit {
|
||||
val builder = OkHttpClient.Builder()
|
||||
builder.connectTimeout(30, TimeUnit.SECONDS)
|
||||
builder.readTimeout(30, TimeUnit.SECONDS)
|
||||
builder.addInterceptor(LoggingInterceptor2())
|
||||
builder.dns(ApiDns())
|
||||
var baseUrl=""
|
||||
baseUrl = getDefaultBaseUrl()
|
||||
return Retrofit.Builder()
|
||||
.baseUrl(baseUrl)
|
||||
// .addConverterFactory(MoshiConverterFactory.create())
|
||||
.addConverterFactory(GsonConverterFactory.create())
|
||||
.client(builder.build())
|
||||
.build()
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package com.xjjk.healthyclients.utils
|
||||
|
||||
import javax.crypto.Mac
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
/**
|
||||
* 签名工具类
|
||||
* 用于生成 HMAC-SHA256 签名,支持 API 请求的身份验证
|
||||
*/
|
||||
object SignatureUtils {
|
||||
|
||||
private const val ALGORITHM = "HmacSHA256"
|
||||
private const val HEX_CHARS = "0123456789abcdef"
|
||||
|
||||
/**
|
||||
* 生成签名
|
||||
*
|
||||
* @param timestamp 时间戳(秒级)
|
||||
* @param bodyString 请求体原始 JSON 字符串
|
||||
* @param secretKey 密钥
|
||||
* @return 64 位小写十六进制签名字符串
|
||||
*/
|
||||
fun generateSignature(timestamp: Long, bodyString: String, secretKey: String): String {
|
||||
// 构造待签名字符串:timestamp + bodyString
|
||||
val stringToSign = "$timestamp$bodyString"
|
||||
|
||||
// 使用 HMAC-SHA256 算法加密
|
||||
val mac = Mac.getInstance(ALGORITHM)
|
||||
val secretKeySpec = SecretKeySpec(secretKey.toByteArray(), ALGORITHM)
|
||||
mac.init(secretKeySpec)
|
||||
|
||||
// 获取签名字节数组
|
||||
val signatureBytes = mac.doFinal(stringToSign.toByteArray())
|
||||
|
||||
// 转换为十六进制小写字符串
|
||||
return bytesToHex(signatureBytes)
|
||||
}
|
||||
|
||||
/**
|
||||
* 将字节数组转换为十六进制小写字符串
|
||||
*
|
||||
* @param bytes 字节数组
|
||||
* @return 十六进制小写字符串
|
||||
*/
|
||||
private fun bytesToHex(bytes: ByteArray): String {
|
||||
val result = StringBuilder(bytes.size * 2)
|
||||
for (byte in bytes) {
|
||||
val octet = byte.toInt()
|
||||
result.append(HEX_CHARS[octet shr 4 and 0xF])
|
||||
result.append(HEX_CHARS[octet and 0xF])
|
||||
}
|
||||
return result.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证签名是否正确
|
||||
*
|
||||
* @param timestamp 时间戳
|
||||
* @param bodyString 请求体原始 JSON 字符串
|
||||
* @param secretKey 密钥
|
||||
* @param signature 待验证的签名
|
||||
* @return true 签名正确,false 签名错误
|
||||
*/
|
||||
fun verifySignature(timestamp: Long, bodyString: String, secretKey: String, signature: String): Boolean {
|
||||
val generatedSignature = generateSignature(timestamp, bodyString, secretKey)
|
||||
return generatedSignature.equals(signature, ignoreCase = false)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user