重构拦截器

This commit is contained in:
czp3009 2019-02-18 23:54:41 +08:00
parent 6acaad0445
commit ca86c44995
13 changed files with 138 additions and 76 deletions

View File

@ -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'
}

View File

@ -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
}
}
/**

View File

@ -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")

View File

@ -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
) {

View File

@ -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"
}

View File

@ -1,10 +0,0 @@
package com.hiczp.bilibili.api.retrofit
enum class HttpMethod {
GET,
POST,
PATCH,
PUT,
DELETE,
OPTION
}

View File

@ -1,4 +1,4 @@
package com.hiczp.bilibili.api.retrofit.interceptor
package com.hiczp.bilibili.api.retrofit
enum class ParamType {
QUERY,

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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)
}
}
}

View 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!!
}

View 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)
}
}
}

View File

@ -0,0 +1,5 @@
{
"username": "123456789",
"password": "123456",
"roomId": "3"
}