mirror of
https://github.com/czp3009/bilibili-api.git
synced 2025-02-19 20:50:28 +08:00
重构拦截器
This commit is contained in:
parent
6acaad0445
commit
ca86c44995
12
build.gradle
12
build.gradle
@ -26,7 +26,7 @@ repositories {
|
||||
//kotlin
|
||||
dependencies {
|
||||
// https://mvnrepository.com/artifact/org.jetbrains.kotlin/kotlin-stdlib-jdk8
|
||||
compile group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jdk8', version: '1.3.21'
|
||||
compile group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jdk8'
|
||||
// https://mvnrepository.com/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core
|
||||
compile group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: '1.1.1'
|
||||
}
|
||||
@ -44,8 +44,8 @@ compileTestKotlin {
|
||||
dependencies {
|
||||
// https://mvnrepository.com/artifact/io.github.microutils/kotlin-logging
|
||||
compile group: 'io.github.microutils', name: 'kotlin-logging', version: '1.6.25'
|
||||
// https://mvnrepository.com/artifact/org.slf4j/slf4j-log4j12
|
||||
testCompile group: 'org.slf4j', name: 'slf4j-log4j12', version: '1.7.25'
|
||||
// https://mvnrepository.com/artifact/org.slf4j/slf4j-simple
|
||||
testCompile group: 'org.slf4j', name: 'slf4j-simple', version: '1.7.25'
|
||||
}
|
||||
|
||||
//http
|
||||
@ -59,3 +59,9 @@ dependencies {
|
||||
// https://mvnrepository.com/artifact/com.squareup.okhttp3/logging-interceptor
|
||||
compile group: 'com.squareup.okhttp3', name: 'logging-interceptor', version: '3.13.1'
|
||||
}
|
||||
|
||||
//unit test
|
||||
dependencies {
|
||||
// https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter
|
||||
testCompile group: 'org.junit.jupiter', name: 'junit-jupiter', version: '5.4.0'
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
package com.hiczp.bilibili.api
|
||||
|
||||
import com.hiczp.bilibili.api.passport.PassportAPI
|
||||
import com.hiczp.bilibili.api.retrofit.ParamType
|
||||
import com.hiczp.bilibili.api.retrofit.interceptor.CommonHeaderInterceptor
|
||||
import com.hiczp.bilibili.api.retrofit.interceptor.CommonParamInterceptor
|
||||
import com.hiczp.bilibili.api.retrofit.interceptor.ParamType
|
||||
import com.hiczp.bilibili.api.retrofit.interceptor.SortAndSignInterceptor
|
||||
import com.jakewharton.retrofit2.adapter.kotlin.coroutines.CoroutineCallAdapterFactory
|
||||
import mu.KotlinLogging
|
||||
@ -22,13 +22,13 @@ private val logger = KotlinLogging.logger {}
|
||||
*
|
||||
* @param billingClientProperties 客户端的固有属性, 是一种常量
|
||||
* @param autoRefreshToken 当 Token 过期时是否自动重新登录
|
||||
* @param debug 是否打印请求日志
|
||||
* @param logLevel 日志打印等级
|
||||
*/
|
||||
class BilibiliClient(
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
val billingClientProperties: BilibiliClientProperties = BilibiliClientProperties(),
|
||||
private val autoRefreshToken: Boolean = true,
|
||||
private val debug: Boolean = false
|
||||
private val logLevel: HttpLoggingInterceptor.Level = HttpLoggingInterceptor.Level.NONE
|
||||
) {
|
||||
/**
|
||||
* 客户端被打开的时间(BilibiliClient 被实例化的时间)
|
||||
@ -66,9 +66,9 @@ class BilibiliClient(
|
||||
))
|
||||
addInterceptor(SortAndSignInterceptor(ParamType.FORM_URL_ENCODED, billingClientProperties.appSecret))
|
||||
|
||||
//debug
|
||||
if (debug) {
|
||||
addNetworkInterceptor(HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))
|
||||
//log
|
||||
if (logLevel != HttpLoggingInterceptor.Level.NONE) {
|
||||
addNetworkInterceptor(HttpLoggingInterceptor().setLevel(logLevel))
|
||||
}
|
||||
}.build()
|
||||
|
||||
@ -84,8 +84,11 @@ class BilibiliClient(
|
||||
/**
|
||||
* 登陆
|
||||
*/
|
||||
fun login(username: String, password: String) {
|
||||
TODO()
|
||||
suspend fun login(username: String, password: String) {
|
||||
val (hash, key) = passportAPI.getKey().await().data.let {
|
||||
it.hash to it.key
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -6,7 +6,7 @@ data class GetKeyResponse(
|
||||
@SerializedName("code")
|
||||
var code: Int, // 0
|
||||
@SerializedName("message")
|
||||
var message: String,
|
||||
var message: String?,
|
||||
@SerializedName("data")
|
||||
var `data`: Data,
|
||||
@SerializedName("ts")
|
||||
|
@ -5,10 +5,10 @@ import com.google.gson.annotations.SerializedName
|
||||
data class LoginResponse(
|
||||
@SerializedName("code")
|
||||
var code: Int, // 0
|
||||
@SerializedName("message")
|
||||
var message: String?,
|
||||
@SerializedName("data")
|
||||
var `data`: Data,
|
||||
@SerializedName("message")
|
||||
var message: String,
|
||||
@SerializedName("ts")
|
||||
var ts: Int // 1550219689
|
||||
) {
|
||||
|
@ -0,0 +1,15 @@
|
||||
package com.hiczp.bilibili.api.retrofit
|
||||
|
||||
object Method {
|
||||
const val GET = "GET"
|
||||
const val POST = "POST"
|
||||
const val PATCH = "PATCH"
|
||||
const val PUT = "PUT"
|
||||
const val DELETE = "DELETE"
|
||||
const val OPTION = "OPTION"
|
||||
}
|
||||
|
||||
object ContentType {
|
||||
const val JSON = "application/json"
|
||||
const val FORM_URLENCODED = "application/x-www-form-urlencoded; charset=utf-8"
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
package com.hiczp.bilibili.api.retrofit
|
||||
|
||||
enum class HttpMethod {
|
||||
GET,
|
||||
POST,
|
||||
PATCH,
|
||||
PUT,
|
||||
DELETE,
|
||||
OPTION
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package com.hiczp.bilibili.api.retrofit.interceptor
|
||||
package com.hiczp.bilibili.api.retrofit
|
||||
|
||||
enum class ParamType {
|
||||
QUERY,
|
@ -3,8 +3,8 @@ package com.hiczp.bilibili.api.retrofit
|
||||
import okhttp3.FormBody
|
||||
|
||||
inline fun FormBody.forEach(block: (Pair<String, String>) -> Unit) {
|
||||
for (i in 0..size()) {
|
||||
block(encodedName(i) to encodedValue(i))
|
||||
repeat(size()) {
|
||||
block(encodedName(it) to encodedValue(it))
|
||||
}
|
||||
}
|
||||
|
||||
@ -27,8 +27,10 @@ fun FormBody.sortedRaw(): String {
|
||||
}
|
||||
|
||||
fun FormBody.Builder.addAll(formBody: FormBody): FormBody.Builder {
|
||||
formBody.forEach { (encodedName, encodedValue) ->
|
||||
addEncoded(encodedName, encodedValue)
|
||||
with(formBody) {
|
||||
repeat(size()) {
|
||||
addEncoded(encodedName(it), encodedValue(it))
|
||||
}
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package com.hiczp.bilibili.api.retrofit.interceptor
|
||||
|
||||
import com.hiczp.bilibili.api.retrofit.HttpMethod
|
||||
import com.hiczp.bilibili.api.retrofit.Method
|
||||
import com.hiczp.bilibili.api.retrofit.ParamType
|
||||
import com.hiczp.bilibili.api.retrofit.addAll
|
||||
import mu.KotlinLogging
|
||||
import okhttp3.FormBody
|
||||
@ -22,6 +23,7 @@ class CommonParamInterceptor(
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val request = chain.request()
|
||||
|
||||
//如果欲添加的参数类型为 Query 则直接添加
|
||||
if (paramType == ParamType.QUERY) {
|
||||
val httpUrl = request.url().newBuilder().apply {
|
||||
additionParams.forEach { (paramName, paramValueExpression) ->
|
||||
@ -31,29 +33,34 @@ class CommonParamInterceptor(
|
||||
return chain.proceed(
|
||||
request.newBuilder().url(httpUrl).build()
|
||||
)
|
||||
} else if (paramType == ParamType.FORM_URL_ENCODED && request.body() is FormBody ||
|
||||
paramType == ParamType.FORM_URL_ENCODED && request.method() == HttpMethod.POST.toString() && request.body()?.contentType() == null
|
||||
) {
|
||||
//TODO 原有的 Body 为空的情况
|
||||
val formBody = FormBody.Builder().apply {
|
||||
//添加原有的参数
|
||||
addAll(request.body() as FormBody)
|
||||
//添加公共参数
|
||||
additionParams.forEach { (paramName, paramValueExpression) ->
|
||||
add(paramName, paramValueExpression())
|
||||
}
|
||||
|
||||
//如果请求方式为 POST(只要方式为 POST, body 就一定不为 null)
|
||||
if (request.method() == Method.POST) {
|
||||
val body = request.body()!!
|
||||
val newFormBody = {
|
||||
FormBody.Builder().apply {
|
||||
additionParams.forEach { (paramName, paramValueExpression) ->
|
||||
add(paramName, paramValueExpression())
|
||||
}
|
||||
}
|
||||
}.build()
|
||||
return chain.proceed(
|
||||
request.newBuilder().post(formBody).build()
|
||||
)
|
||||
} else {
|
||||
//如果 body 不为 FormBody 将无法添加 FormUrlEncoded 参数
|
||||
logger.error {
|
||||
"Impossible add FormUrlEncoded params to ${request.body()?.javaClass}. Request: " +
|
||||
"${request.method()} ${request.url()}"
|
||||
}
|
||||
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().addAll(body).build()).build()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
//其他情况, 例如请求方式为 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,5 +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.addAll
|
||||
import com.hiczp.bilibili.api.retrofit.sortedRaw
|
||||
import mu.KotlinLogging
|
||||
@ -17,8 +19,9 @@ class SortAndSignInterceptor(private val paramType: ParamType, private val appSe
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val request = chain.request()
|
||||
|
||||
//如果欲签名的参数类型为 Query
|
||||
if (paramType == ParamType.QUERY) {
|
||||
//这里认为 Query 一定不为 null
|
||||
//如果代码是正确的, 那么传到这里的 Query 参数一定包含了公共参数
|
||||
val sortedEncodedQuery = request.url().encodedQuery()!!
|
||||
.split('&')
|
||||
.sorted()
|
||||
@ -32,35 +35,35 @@ class SortAndSignInterceptor(private val paramType: ParamType, private val appSe
|
||||
)
|
||||
.build()
|
||||
)
|
||||
} else if (paramType == ParamType.FORM_URL_ENCODED && request.body() is FormBody) {
|
||||
val sign = calculateSign((request.body() as FormBody).sortedRaw(), appSecret)
|
||||
val formBody = FormBody.Builder().apply {
|
||||
//添加原有的参数
|
||||
addAll(request.body() as FormBody)
|
||||
//添加 sign
|
||||
addEncoded("sign", sign)
|
||||
}.build()
|
||||
return chain.proceed(
|
||||
request.newBuilder().post(formBody).build()
|
||||
)
|
||||
} else {
|
||||
//如果 body 不为 FormBody 将无法添加签名
|
||||
logger.error {
|
||||
"Impossible add sign to ${request.body()?.javaClass}. Request: " +
|
||||
"${request.method()} ${request.url()}"
|
||||
}
|
||||
}
|
||||
|
||||
val body = request.body()
|
||||
if (request.method() == Method.POST && body is FormBody) {
|
||||
val sign = calculateSign(body.sortedRaw(), appSecret)
|
||||
val newFormBody = FormBody.Builder().apply {
|
||||
addAll(body)
|
||||
addEncoded("sign", sign)
|
||||
}.build()
|
||||
return chain.proceed(request.newBuilder().post(newFormBody).build())
|
||||
}
|
||||
|
||||
//其他情况, 例如请求方式为 GET 或 Body 为 Json
|
||||
logger.error {
|
||||
"Impossible add sign to ${request.body()?.javaClass?.simpleName ?: "<NullBody>"}. " +
|
||||
"Request: ${request.method()} ${request.url()}"
|
||||
}
|
||||
return chain.proceed(request)
|
||||
}
|
||||
|
||||
/**
|
||||
* 签名算法为 "$排序后的参数字符串$appSecret".md5()
|
||||
*/
|
||||
private fun calculateSign(string: String, appSecret: String) =
|
||||
MessageDigest.getInstance("MD5")
|
||||
.digest((string + appSecret).toByteArray())
|
||||
.joinToString(separator = "") {
|
||||
String.format("%02x", it)
|
||||
}
|
||||
companion object {
|
||||
/**
|
||||
* 签名算法为 "$排序后的参数字符串$appSecret".md5()
|
||||
*/
|
||||
private fun calculateSign(string: String, appSecret: String) =
|
||||
MessageDigest.getInstance("MD5")
|
||||
.digest((string + appSecret).toByteArray())
|
||||
.joinToString(separator = "") {
|
||||
String.format("%02x", it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
15
src/test/kotlin/com/hiczp/bilibili/api/test/Config.kt
Normal file
15
src/test/kotlin/com/hiczp/bilibili/api/test/Config.kt
Normal file
@ -0,0 +1,15 @@
|
||||
package com.hiczp.bilibili.api.test
|
||||
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.JsonObject
|
||||
|
||||
object Config {
|
||||
private val config = Gson().fromJson(
|
||||
Config::class.java.getResourceAsStream("/config.json").reader(),
|
||||
JsonObject::class.java
|
||||
)
|
||||
|
||||
val username = config["username"].asString!!
|
||||
|
||||
val password = config["password"].asString!!
|
||||
}
|
16
src/test/kotlin/com/hiczp/bilibili/api/test/LoginTest.kt
Normal file
16
src/test/kotlin/com/hiczp/bilibili/api/test/LoginTest.kt
Normal file
@ -0,0 +1,16 @@
|
||||
package com.hiczp.bilibili.api.test
|
||||
|
||||
import com.hiczp.bilibili.api.BilibiliClient
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class LoginTest {
|
||||
@Test
|
||||
fun login() {
|
||||
runBlocking {
|
||||
BilibiliClient(logLevel = HttpLoggingInterceptor.Level.BODY)
|
||||
.login(Config.username, Config.password)
|
||||
}
|
||||
}
|
||||
}
|
5
src/test/resources/_config.json
Normal file
5
src/test/resources/_config.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"username": "123456789",
|
||||
"password": "123456",
|
||||
"roomId": "3"
|
||||
}
|
Loading…
Reference in New Issue
Block a user