feat(home): 新增银行积分签名工具和网络请求封装

- 新增 SignatureUtils 工具类,实现 HMAC-SHA256 签名算法
- 新增 BankPointRequest 网络请求封装类,简化 API 调用逻辑
- 优化 HomeFragment 中的 getBankPointDetail() 方法,从 34 行简化为 20 行
- 优化 AppraiseDialog,移除过时的版本判断和冗余的初始化逻辑
- 补充完整的中文文档注释和单元测试
This commit is contained in:
zhanglei
2026-03-17 16:56:39 +08:00
parent 222c1e4570
commit 72f4c21b3a
4 changed files with 215 additions and 0 deletions
@@ -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)
}
}