mirror of
https://github.com/czp3009/bilibili-api.git
synced 2025-02-19 20:50:28 +08:00
重构共用参数的添加和签名算法
This commit is contained in:
parent
fa906093a6
commit
1bc0bf61c2
@ -6,6 +6,7 @@ import com.hiczp.bilibili.api.member.MemberAPI
|
||||
import com.hiczp.bilibili.api.message.MessageAPI
|
||||
import com.hiczp.bilibili.api.passport.PassportAPI
|
||||
import com.hiczp.bilibili.api.passport.model.LoginResponse
|
||||
import com.hiczp.bilibili.api.retrofit.Param
|
||||
import com.hiczp.bilibili.api.retrofit.ParamType
|
||||
import com.hiczp.bilibili.api.retrofit.exception.BilibiliApiException
|
||||
import com.hiczp.bilibili.api.retrofit.interceptor.CommonHeaderInterceptor
|
||||
@ -75,8 +76,8 @@ class BilibiliClient(
|
||||
|
||||
@Suppress("SpellCheckingInspection")
|
||||
private val defaultCommonParamArray = arrayOf(
|
||||
"access_key" to { token },
|
||||
"appkey" to { billingClientProperties.appKey },
|
||||
Param.ACCESS_KEY to { token },
|
||||
Param.APP_KEY to { billingClientProperties.appKey },
|
||||
"build" to { billingClientProperties.build },
|
||||
"channel" to { billingClientProperties.channel },
|
||||
"mobi_app" to { billingClientProperties.platform },
|
||||
@ -85,7 +86,7 @@ class BilibiliClient(
|
||||
)
|
||||
|
||||
private val defaultCommonQueryParamInterceptor = CommonParamInterceptor(ParamType.QUERY, *defaultCommonParamArray)
|
||||
private val defaultQuerySignInterceptor = SortAndSignInterceptor(ParamType.QUERY, billingClientProperties.appSecret)
|
||||
private val defaultSortAndSignInterceptor = SortAndSignInterceptor(billingClientProperties.appSecret)
|
||||
|
||||
/**
|
||||
* 用户鉴权相关的接口
|
||||
@ -95,14 +96,14 @@ class BilibiliClient(
|
||||
createAPI<PassportAPI>(BaseUrl.passport,
|
||||
defaultCommonHeaderInterceptor,
|
||||
CommonParamInterceptor(ParamType.FORM_URL_ENCODED,
|
||||
"appkey" to { billingClientProperties.appKey },
|
||||
Param.APP_KEY to { billingClientProperties.appKey },
|
||||
"build" to { billingClientProperties.build },
|
||||
"channel" to { billingClientProperties.channel },
|
||||
"mobi_app" to { billingClientProperties.platform },
|
||||
"platform" to { billingClientProperties.platform },
|
||||
"ts" to { Instant.now().epochSecond.toString() }
|
||||
),
|
||||
SortAndSignInterceptor(ParamType.FORM_URL_ENCODED, billingClientProperties.appSecret)
|
||||
defaultSortAndSignInterceptor
|
||||
)
|
||||
}
|
||||
|
||||
@ -117,7 +118,7 @@ class BilibiliClient(
|
||||
"actionKey" to { "appkey" },
|
||||
"has_up" to { "1" }
|
||||
),
|
||||
defaultQuerySignInterceptor
|
||||
defaultSortAndSignInterceptor
|
||||
)
|
||||
}
|
||||
|
||||
@ -129,7 +130,7 @@ class BilibiliClient(
|
||||
createAPI<AppAPI>(BaseUrl.app,
|
||||
defaultCommonHeaderInterceptor,
|
||||
defaultCommonQueryParamInterceptor,
|
||||
defaultQuerySignInterceptor
|
||||
defaultSortAndSignInterceptor
|
||||
)
|
||||
}
|
||||
|
||||
@ -147,7 +148,7 @@ class BilibiliClient(
|
||||
"Device-ID" to { billingClientProperties.hardwareId }
|
||||
),
|
||||
defaultCommonQueryParamInterceptor,
|
||||
defaultQuerySignInterceptor
|
||||
defaultSortAndSignInterceptor
|
||||
)
|
||||
}
|
||||
|
||||
@ -166,7 +167,7 @@ class BilibiliClient(
|
||||
"uid" to { userId?.toString() },
|
||||
"version" to { billingClientProperties.version }
|
||||
),
|
||||
defaultQuerySignInterceptor
|
||||
defaultSortAndSignInterceptor
|
||||
)
|
||||
}
|
||||
|
||||
@ -177,7 +178,7 @@ class BilibiliClient(
|
||||
createAPI<MemberAPI>(BaseUrl.member,
|
||||
defaultCommonHeaderInterceptor,
|
||||
defaultCommonQueryParamInterceptor,
|
||||
defaultQuerySignInterceptor
|
||||
defaultSortAndSignInterceptor
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,7 @@
|
||||
package com.hiczp.bilibili.api.retrofit
|
||||
|
||||
//该文件用于防止拼写错误
|
||||
|
||||
object Method {
|
||||
const val GET = "GET"
|
||||
const val POST = "POST"
|
||||
@ -13,3 +15,10 @@ object ContentType {
|
||||
const val JSON = "application/json"
|
||||
const val FORM_URLENCODED = "application/x-www-form-urlencoded; charset=utf-8"
|
||||
}
|
||||
|
||||
object Param {
|
||||
const val ACCESS_KEY = "access_key"
|
||||
@Suppress("SpellCheckingInspection")
|
||||
const val APP_KEY = "appkey"
|
||||
const val SIGN = "sign"
|
||||
}
|
||||
|
@ -26,6 +26,13 @@ fun FormBody.sortedRaw(): String {
|
||||
return nameAndValue.sorted().joinToString(separator = "&")
|
||||
}
|
||||
|
||||
fun FormBody.containsEncodedName(name: String): Boolean {
|
||||
repeat(size()) {
|
||||
if (encodedName(it) == name) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun FormBody.Builder.addAllEncoded(formBody: FormBody): FormBody.Builder {
|
||||
with(formBody) {
|
||||
repeat(size()) {
|
||||
|
@ -1,6 +1,5 @@
|
||||
package com.hiczp.bilibili.api.retrofit.interceptor
|
||||
|
||||
import com.hiczp.bilibili.api.retrofit.Method
|
||||
import com.hiczp.bilibili.api.retrofit.ParamType
|
||||
import com.hiczp.bilibili.api.retrofit.addAllEncoded
|
||||
import com.hiczp.bilibili.api.retrofit.forEachNonNull
|
||||
@ -22,46 +21,49 @@ class CommonParamInterceptor(
|
||||
private vararg val additionParams: Pair<String, () -> String?>
|
||||
) : Interceptor {
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val request = chain.request()
|
||||
var request = chain.request()
|
||||
val body = request.body()
|
||||
|
||||
//如果欲添加的参数类型为 Query 则直接添加
|
||||
if (paramType == ParamType.QUERY) {
|
||||
val httpUrl = request.url().newBuilder().apply {
|
||||
additionParams.forEachNonNull { name, value ->
|
||||
addQueryParameter(name, value)
|
||||
}
|
||||
}.build()
|
||||
return chain.proceed(
|
||||
request.newBuilder().url(httpUrl).build()
|
||||
)
|
||||
}
|
||||
request = when {
|
||||
//如果欲添加参数的位置为 Query 则直接添加, paramType 为 FORM_URL_ENCODED 则继续下面的判断
|
||||
paramType == ParamType.QUERY -> {
|
||||
val httpUrl = request.url().newBuilder().apply {
|
||||
additionParams.forEachNonNull { name, value ->
|
||||
addQueryParameter(name, value)
|
||||
}
|
||||
}.build()
|
||||
request.newBuilder().url(httpUrl).build()
|
||||
}
|
||||
|
||||
//如果请求方式为 POST(只要方式为 POST, body 就一定不为 null)
|
||||
if (request.method() == Method.POST) {
|
||||
val body = request.body()!!
|
||||
val newFormBody = {
|
||||
FormBody.Builder().apply {
|
||||
//BODY 不存在或者是空的
|
||||
body == null || body.contentLength() == 0L -> {
|
||||
val formBody = FormBody.Builder().apply {
|
||||
additionParams.forEachNonNull { name, value ->
|
||||
add(name, value)
|
||||
}
|
||||
}
|
||||
}.build()
|
||||
request.newBuilder().method(request.method(), formBody).build()
|
||||
}
|
||||
if (body.contentType() == null) { //如果 body 为空
|
||||
return chain.proceed(
|
||||
request.newBuilder().post(newFormBody().build()).build()
|
||||
)
|
||||
} else if (body is FormBody) { //如果 body 为 FormBody
|
||||
return chain.proceed(
|
||||
request.newBuilder().post(newFormBody().addAllEncoded(body).build()).build()
|
||||
)
|
||||
|
||||
//只要 BODY 为 FormBody, 那么里面一定有内容
|
||||
body is FormBody -> {
|
||||
val formBody = FormBody.Builder().apply {
|
||||
addAllEncoded(body)
|
||||
additionParams.forEachNonNull { name, value ->
|
||||
add(name, value)
|
||||
}
|
||||
}.build()
|
||||
request.newBuilder().method(request.method(), formBody).build()
|
||||
}
|
||||
|
||||
else -> {
|
||||
logger.error {
|
||||
"Cannot add params to request: ${request.method()} ${request.url()} ${body.javaClass.simpleName}"
|
||||
}
|
||||
request
|
||||
}
|
||||
}
|
||||
|
||||
//其他情况, 例如请求方式为 GET 或 Body 为 Json
|
||||
logger.error {
|
||||
"Impossible add FormUrlEncoded params to ${request.body()?.javaClass?.simpleName ?: "<NullBody>"}. " +
|
||||
"Request: ${request.method()} ${request.url()}"
|
||||
}
|
||||
return chain.proceed(request)
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
package com.hiczp.bilibili.api.retrofit.interceptor
|
||||
|
||||
import com.hiczp.bilibili.api.retrofit.Method
|
||||
import com.hiczp.bilibili.api.retrofit.ParamType
|
||||
import com.hiczp.bilibili.api.retrofit.Param
|
||||
import com.hiczp.bilibili.api.retrofit.containsEncodedName
|
||||
import com.hiczp.bilibili.api.retrofit.sortedRaw
|
||||
import mu.KotlinLogging
|
||||
import okhttp3.FormBody
|
||||
@ -13,60 +13,73 @@ private val logger = KotlinLogging.logger {}
|
||||
|
||||
/**
|
||||
* 排序参数并添加签名
|
||||
* 必须保证在进入此拦截器前, 公共参数已被添加
|
||||
* 此拦截器将自动识别 appKey 在 Query 还是在 FormBody 并添加 sign 到相应位置
|
||||
*
|
||||
* @param appSecret 密钥
|
||||
*/
|
||||
class SortAndSignInterceptor(private val paramType: ParamType, private val appSecret: String) : Interceptor {
|
||||
class SortAndSignInterceptor(private val appSecret: String) : Interceptor {
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val request = chain.request()
|
||||
|
||||
//如果欲签名的参数类型为 Query
|
||||
if (paramType == ParamType.QUERY) {
|
||||
//如果代码是正确的, 那么传到这里的 Query 参数一定包含了公共参数
|
||||
val sortedEncodedQuery = request.url().encodedQuery()!!
|
||||
.split('&')
|
||||
.sorted()
|
||||
.joinToString(separator = "&")
|
||||
val sign = calculateSign(sortedEncodedQuery, appSecret)
|
||||
return chain.proceed(
|
||||
request.newBuilder()
|
||||
.url(request.url().newBuilder()
|
||||
.encodedQuery("$sortedEncodedQuery&sign=$sign")
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
)
|
||||
}
|
||||
|
||||
var request = chain.request()
|
||||
val url = request.url()
|
||||
val body = request.body()
|
||||
if (request.method() == Method.POST && body is FormBody) {
|
||||
val sortedRaw = body.sortedRaw()
|
||||
val sign = calculateSign(sortedRaw, appSecret)
|
||||
val newFormBody = FormBody.Builder().apply {
|
||||
sortedRaw.split('&').forEach {
|
||||
val (name, value) = it.split('=')
|
||||
addEncoded(name, value)
|
||||
}
|
||||
addEncoded("sign", sign)
|
||||
}.build()
|
||||
return chain.proceed(request.newBuilder().post(newFormBody).build())
|
||||
|
||||
request = when {
|
||||
//判断 appKey 是否在 Query 里
|
||||
url.queryParameter(Param.APP_KEY) != null -> {
|
||||
val sortedEncodedQuery = url.encodedQuery()!!
|
||||
.split('&')
|
||||
.sorted()
|
||||
.joinToString(separator = "&")
|
||||
val sign = calculateSign(sortedEncodedQuery, appSecret)
|
||||
request.newBuilder()
|
||||
.url(url.newBuilder()
|
||||
.encodedQuery("$sortedEncodedQuery&${Param.SIGN}=$sign")
|
||||
.build()
|
||||
).build()
|
||||
}
|
||||
|
||||
//在 FormBody 里
|
||||
body is FormBody && body.containsEncodedName(Param.APP_KEY) -> {
|
||||
val sortedRaw = body.sortedRaw()
|
||||
val sign = calculateSign(sortedRaw, appSecret)
|
||||
val formBody = FormBody.Builder().apply {
|
||||
sortedRaw.split('&').forEach {
|
||||
val (name, value) = it.split('=')
|
||||
addEncoded(name, value)
|
||||
}
|
||||
addEncoded(Param.SIGN, sign)
|
||||
}.build()
|
||||
request.newBuilder()
|
||||
.method(request.method(), formBody)
|
||||
.build()
|
||||
}
|
||||
|
||||
//不存在 accessKey
|
||||
else -> {
|
||||
logger.error { "Fail to sign request because no ${Param.APP_KEY} found in request" }
|
||||
request
|
||||
}
|
||||
}
|
||||
|
||||
//其他情况, 例如请求方式为 GET 或 Body 为 Json
|
||||
logger.error {
|
||||
"Impossible add sign to ${request.body()?.javaClass?.simpleName ?: "<NullBody>"}. " +
|
||||
"Request: ${request.method()} ${request.url()}"
|
||||
}
|
||||
return chain.proceed(request)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val md5Instance = MessageDigest.getInstance("MD5")
|
||||
/**
|
||||
* 签名算法为 "$排序后的参数字符串$appSecret".md5()
|
||||
*/
|
||||
private fun calculateSign(string: String, appSecret: String) =
|
||||
MessageDigest.getInstance("MD5")
|
||||
.digest((string + appSecret).toByteArray())
|
||||
.joinToString(separator = "") {
|
||||
"%02x".format(it)
|
||||
}
|
||||
StringBuilder(32).apply {
|
||||
//优化过的 md5 字符串生成算法
|
||||
md5Instance.digest((string + appSecret).toByteArray()).forEach {
|
||||
val value = it.toInt() and 0xFF
|
||||
val high = value / 16
|
||||
val low = value - high * 16
|
||||
append(if (high <= 9) '0' + high else 'a' - 10 + high)
|
||||
append(if (low <= 9) '0' + low else 'a' - 10 + low)
|
||||
}
|
||||
}.toString()
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user