From ca86c449956526f80ed76ec00562f559897cf665 Mon Sep 17 00:00:00 2001 From: czp3009 Date: Mon, 18 Feb 2019 23:54:41 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=9E=84=E6=8B=A6=E6=88=AA=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 12 +++- .../com/hiczp/bilibili/api/BilibiliClient.kt | 19 ++++--- .../api/passport/model/GetKeyResponse.kt | 2 +- .../api/passport/model/LoginResponse.kt | 4 +- .../bilibili/api/retrofit/HttpConstant.kt | 15 +++++ .../hiczp/bilibili/api/retrofit/HttpMethod.kt | 10 ---- .../retrofit/{interceptor => }/ParamType.kt | 2 +- .../api/retrofit/RetrofitExtension.kt | 10 ++-- .../interceptor/CommonParamInterceptor.kt | 47 ++++++++------- .../interceptor/SortAndSignInterceptor.kt | 57 ++++++++++--------- .../com/hiczp/bilibili/api/test/Config.kt | 15 +++++ .../com/hiczp/bilibili/api/test/LoginTest.kt | 16 ++++++ src/test/resources/_config.json | 5 ++ 13 files changed, 138 insertions(+), 76 deletions(-) create mode 100644 src/main/kotlin/com/hiczp/bilibili/api/retrofit/HttpConstant.kt delete mode 100644 src/main/kotlin/com/hiczp/bilibili/api/retrofit/HttpMethod.kt rename src/main/kotlin/com/hiczp/bilibili/api/retrofit/{interceptor => }/ParamType.kt (52%) create mode 100644 src/test/kotlin/com/hiczp/bilibili/api/test/Config.kt create mode 100644 src/test/kotlin/com/hiczp/bilibili/api/test/LoginTest.kt create mode 100644 src/test/resources/_config.json diff --git a/build.gradle b/build.gradle index 6c34be2..9707a81 100644 --- a/build.gradle +++ b/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' +} diff --git a/src/main/kotlin/com/hiczp/bilibili/api/BilibiliClient.kt b/src/main/kotlin/com/hiczp/bilibili/api/BilibiliClient.kt index 852e5e2..5d773c5 100644 --- a/src/main/kotlin/com/hiczp/bilibili/api/BilibiliClient.kt +++ b/src/main/kotlin/com/hiczp/bilibili/api/BilibiliClient.kt @@ -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 + } + } /** diff --git a/src/main/kotlin/com/hiczp/bilibili/api/passport/model/GetKeyResponse.kt b/src/main/kotlin/com/hiczp/bilibili/api/passport/model/GetKeyResponse.kt index 2a9d959..a82262b 100644 --- a/src/main/kotlin/com/hiczp/bilibili/api/passport/model/GetKeyResponse.kt +++ b/src/main/kotlin/com/hiczp/bilibili/api/passport/model/GetKeyResponse.kt @@ -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") diff --git a/src/main/kotlin/com/hiczp/bilibili/api/passport/model/LoginResponse.kt b/src/main/kotlin/com/hiczp/bilibili/api/passport/model/LoginResponse.kt index b19862d..4158c72 100644 --- a/src/main/kotlin/com/hiczp/bilibili/api/passport/model/LoginResponse.kt +++ b/src/main/kotlin/com/hiczp/bilibili/api/passport/model/LoginResponse.kt @@ -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 ) { diff --git a/src/main/kotlin/com/hiczp/bilibili/api/retrofit/HttpConstant.kt b/src/main/kotlin/com/hiczp/bilibili/api/retrofit/HttpConstant.kt new file mode 100644 index 0000000..e4e06fa --- /dev/null +++ b/src/main/kotlin/com/hiczp/bilibili/api/retrofit/HttpConstant.kt @@ -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" +} diff --git a/src/main/kotlin/com/hiczp/bilibili/api/retrofit/HttpMethod.kt b/src/main/kotlin/com/hiczp/bilibili/api/retrofit/HttpMethod.kt deleted file mode 100644 index 9d4e966..0000000 --- a/src/main/kotlin/com/hiczp/bilibili/api/retrofit/HttpMethod.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.hiczp.bilibili.api.retrofit - -enum class HttpMethod { - GET, - POST, - PATCH, - PUT, - DELETE, - OPTION -} diff --git a/src/main/kotlin/com/hiczp/bilibili/api/retrofit/interceptor/ParamType.kt b/src/main/kotlin/com/hiczp/bilibili/api/retrofit/ParamType.kt similarity index 52% rename from src/main/kotlin/com/hiczp/bilibili/api/retrofit/interceptor/ParamType.kt rename to src/main/kotlin/com/hiczp/bilibili/api/retrofit/ParamType.kt index 786037f..301270a 100644 --- a/src/main/kotlin/com/hiczp/bilibili/api/retrofit/interceptor/ParamType.kt +++ b/src/main/kotlin/com/hiczp/bilibili/api/retrofit/ParamType.kt @@ -1,4 +1,4 @@ -package com.hiczp.bilibili.api.retrofit.interceptor +package com.hiczp.bilibili.api.retrofit enum class ParamType { QUERY, diff --git a/src/main/kotlin/com/hiczp/bilibili/api/retrofit/RetrofitExtension.kt b/src/main/kotlin/com/hiczp/bilibili/api/retrofit/RetrofitExtension.kt index fab7e47..a91b1c2 100644 --- a/src/main/kotlin/com/hiczp/bilibili/api/retrofit/RetrofitExtension.kt +++ b/src/main/kotlin/com/hiczp/bilibili/api/retrofit/RetrofitExtension.kt @@ -3,8 +3,8 @@ package com.hiczp.bilibili.api.retrofit import okhttp3.FormBody inline fun FormBody.forEach(block: (Pair) -> 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 } diff --git a/src/main/kotlin/com/hiczp/bilibili/api/retrofit/interceptor/CommonParamInterceptor.kt b/src/main/kotlin/com/hiczp/bilibili/api/retrofit/interceptor/CommonParamInterceptor.kt index dc7f985..89e6be2 100644 --- a/src/main/kotlin/com/hiczp/bilibili/api/retrofit/interceptor/CommonParamInterceptor.kt +++ b/src/main/kotlin/com/hiczp/bilibili/api/retrofit/interceptor/CommonParamInterceptor.kt @@ -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 ?: ""}. " + + "Request: ${request.method()} ${request.url()}" + } return chain.proceed(request) } } diff --git a/src/main/kotlin/com/hiczp/bilibili/api/retrofit/interceptor/SortAndSignInterceptor.kt b/src/main/kotlin/com/hiczp/bilibili/api/retrofit/interceptor/SortAndSignInterceptor.kt index 49f9adb..c976d71 100644 --- a/src/main/kotlin/com/hiczp/bilibili/api/retrofit/interceptor/SortAndSignInterceptor.kt +++ b/src/main/kotlin/com/hiczp/bilibili/api/retrofit/interceptor/SortAndSignInterceptor.kt @@ -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 ?: ""}. " + + "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) + } + } } diff --git a/src/test/kotlin/com/hiczp/bilibili/api/test/Config.kt b/src/test/kotlin/com/hiczp/bilibili/api/test/Config.kt new file mode 100644 index 0000000..c51be7f --- /dev/null +++ b/src/test/kotlin/com/hiczp/bilibili/api/test/Config.kt @@ -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!! +} diff --git a/src/test/kotlin/com/hiczp/bilibili/api/test/LoginTest.kt b/src/test/kotlin/com/hiczp/bilibili/api/test/LoginTest.kt new file mode 100644 index 0000000..7cd313f --- /dev/null +++ b/src/test/kotlin/com/hiczp/bilibili/api/test/LoginTest.kt @@ -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) + } + } +} diff --git a/src/test/resources/_config.json b/src/test/resources/_config.json new file mode 100644 index 0000000..c9e388e --- /dev/null +++ b/src/test/resources/_config.json @@ -0,0 +1,5 @@ +{ + "username": "123456789", + "password": "123456", + "roomId": "3" +}