refactor: 重构 BaseService 代码,提升可维护性和可读性

main
mojo 1 month ago
parent 9934938dac
commit 63ab06eefa

@ -0,0 +1,329 @@
package com.example.service
import android.os.Build
import com.example.action.BaseAction
import com.example.action.NameValue
import com.example.action.Next
import com.example.action.VarExtractRule
import com.example.http.HttpClient
import com.example.logger.LogUtils
import com.example.report.ActionExec
import com.example.task.TaskConfig
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import org.json.JSONArray
import java.net.URI
import java.net.URL
import java.net.URLEncoder
import java.util.Base64
import com.example.http.HttpClient.Params
import com.example.http.Request
import com.example.utils.WebSocketUtil
import com.example.utils.toJsonString
abstract class BaseService(
protected open val taskConfig: TaskConfig,
protected val scope: CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
) {
abstract suspend fun execute(onFinish: (List<ActionExec>) -> Unit)
companion object {
// HTTP 错误码
const val ERROR_CODE_HTTP_ACTION_EXEC_FAILED = 600
const val ERROR_CODE_HTTP_BUILD_REDIRECT_FAILED = 615
// PIN 动作错误码
const val ERROR_CODE_PIN_ACTION_EXEC_FAILED = 700
const val ERROR_CODE_PIN_ACTION_TIMEOUT = 702
// WebSocket 错误码
const val WEB_SOCKET_CODE = 101
const val ERROR_CODE_WS_ACTION_EXEC_FAILED = 800
// 通用异步执行错误码
const val ASYNC_EXEC_CODE = 900
}
// ==================== 变量处理 ====================
protected fun String.toVariableData(): String {
if (isBlank()) return this
var result = this
taskConfig.variableCache.forEach { (key, value) ->
if (value.isNotBlank()) {
val placeholder = "{$key}"
result = result.replace(placeholder, value)
}
}
return result
}
protected fun List<NameValue>.replaceVariableData(): List<NameValue> {
return map {
NameValue(
it.name.toVariableData(),
it.value.toVariableData()
)
}
}
// ==================== Cookie 处理 ====================
protected fun cookieFromCookieManager(url: String): Set<NameValue> {
val cookiesCache = mutableSetOf<NameValue>()
runCatching {
val urlObj = URL(url)
val uri = URI(urlObj.protocol, urlObj.host, urlObj.path, urlObj.query, null)
val headers = mutableMapOf<String, List<String>>()
taskConfig.cookieManager.get(uri, headers)?.let { responseHeaders ->
val cookieList = responseHeaders.getOrElse(Params.REQUEST_HEADER_COOKIE) { emptyList() }
val cookies = cookieList.firstOrNull()
if (!cookies.isNullOrBlank()) {
cookies.split("; ").forEach { cookie ->
cookie.split("=", limit = 2).takeIf { it.size >= 2 }?.let { kv ->
cookiesCache += NameValue(kv[0], kv[1])
}
}
}
}
}
LogUtils.info("getCookie $url : $cookiesCache")
return cookiesCache
}
protected fun List<NameValue>.buildCookie() = NameValue(
name = Params.REQUEST_HEADER_COOKIE,
value = joinToString("; ") { "${it.name}=${it.value}" }
)
// ==================== URL 处理 ====================
protected fun String.urlEncode(): String? = runCatching {
URLEncoder.encode(this, "UTF-8")
}.getOrNull()
protected fun String.isHttps(): Boolean = startsWith("https", ignoreCase = true)
// ==================== Header 处理 ====================
protected fun List<NameValue>.nameValueToMap(): Map<String, String> = associate {
it.name to it.value
}
protected fun List<NameValue>.amendBaseHeader(
includeAccept: Boolean
): List<NameValue> {
val headers = toMutableList()
if (headers.isEmpty()) {
headers.addDefaultHeaders(includeAccept)
} else {
headers.ensureDefaultHeaders(includeAccept)
}
return headers
}
private fun MutableList<NameValue>.addDefaultHeaders(includeAccept: Boolean) {
add(NameValue(Params.REQUEST_HEADER_USER_AGENT, taskConfig.userAgent))
if (includeAccept) {
add(NameValue(Params.REQUEST_HEADER_ACCEPT, taskConfig.accept))
}
add(NameValue(Params.REQUEST_HEADER_ACCEPT_LANGUAGE, taskConfig.acceptLanguage))
}
private fun MutableList<NameValue>.ensureDefaultHeaders(includeAccept: Boolean) {
if (!hasHeader(Params.REQUEST_HEADER_USER_AGENT)) {
add(NameValue(Params.REQUEST_HEADER_USER_AGENT, taskConfig.userAgent))
}
if (includeAccept && !hasHeader(Params.REQUEST_HEADER_ACCEPT)) {
add(NameValue(Params.REQUEST_HEADER_ACCEPT, taskConfig.accept))
}
if (!hasHeader(Params.REQUEST_HEADER_ACCEPT_LANGUAGE)) {
add(NameValue(Params.REQUEST_HEADER_ACCEPT_LANGUAGE, taskConfig.acceptLanguage))
}
}
protected fun List<NameValue>.addSecChUa(): MutableList<NameValue> {
val headers = toMutableList()
if (!headers.hasHeader(Params.REQUEST_HEADER_SEC_CH_UA)) {
headers.add(NameValue(
Params.REQUEST_HEADER_SEC_CH_UA,
taskConfig.secChUa.ifBlank { "\"\"" }
))
}
if (!headers.hasHeader(Params.REQUEST_HEADER_SEC_CH_UA_MOBILE)) {
headers.add(NameValue(Params.REQUEST_HEADER_SEC_CH_UA_MOBILE, "?1"))
}
if (!headers.hasHeader(Params.REQUEST_HEADER_SEC_CH_UA_PLATFORM)) {
headers.add(NameValue(Params.REQUEST_HEADER_SEC_CH_UA_PLATFORM, "\"Android\""))
}
return headers
}
private fun List<NameValue>.hasHeader(headerName: String): Boolean {
return any { it.name.contentEquals(headerName, ignoreCase = true) }
}
protected fun Request.genBaseHeaderMap(): Map<String, String> = headers.filterKeys {
Params.REQUEST_HEADER_USER_AGENT.equals(it, true) ||
Params.REQUEST_HEADER_ACCEPT_LANGUAGE.equals(it, true) ||
Params.REQUEST_HEADER_ACCEPT.equals(it, true)
}
// ==================== 变量提取 ====================
protected fun extractBodyVariableToCache(
action: BaseAction,
wholeResponseData: String,
responseBody: ByteArray
) = runCatching {
val params = getExtractParams(action) ?: return@runCatching
params.forEach { extractRule ->
val value = extractValue(extractRule, wholeResponseData, responseBody)
taskConfig.variableCache[extractRule.variable] = value
}
}
private fun getExtractParams(action: BaseAction): List<VarExtractRule>? {
return when (action) {
is BaseAction.HttpAction -> action.response?.params
is BaseAction.PinAction -> action.params
is BaseAction.WebSocketAction -> action.response?.params
}
}
private fun extractValue(
extractRule: VarExtractRule,
wholeResponseData: String,
responseBody: ByteArray
): String {
return when (extractRule) {
is VarExtractRule.Base64 -> encodeBase64(responseBody)
is VarExtractRule.Between -> extractBetween(extractRule.expr, wholeResponseData)
is VarExtractRule.Regexp -> extractRegex(extractRule.expr, wholeResponseData)
is VarExtractRule.Response -> String(responseBody)
is VarExtractRule.Whole -> wholeResponseData
}
}
private fun encodeBase64(data: ByteArray): String {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Base64.getEncoder().encodeToString(data)
} else {
@Suppress("DEPRECATION")
android.util.Base64.encodeToString(data, android.util.Base64.DEFAULT)
}
}
private fun extractBetween(expr: String, data: String): String {
val parts = expr.split("|").take(2)
if (parts.size < 2) return ""
return data.substringAfter(parts[0], missingDelimiterValue = "")
.substringBefore(parts[1], missingDelimiterValue = "")
}
private fun extractRegex(pattern: String, data: String): String {
return Regex(pattern).find(data)?.groupValues?.getOrNull(1) ?: ""
}
// ==================== 错误处理 ====================
protected fun genExceptionActionExec(
action: BaseAction,
errorCode: Int,
exceptionMessage: String
): ActionExec {
return ActionExec().apply {
step = taskConfig.currentStep
index = 1
time = System.currentTimeMillis()
respCode = errorCode
cost = 0
fillActionDetails(action, exceptionMessage)
}
}
private fun ActionExec.fillActionDetails(action: BaseAction, exceptionMessage: String) {
when (action) {
is BaseAction.HttpAction -> {
action.request?.let { request ->
url = request.url
method = request.method.value
reqHeader = request.headers.toJsonString()
reqData = request.data.takeIf { it.isNotBlank() } ?: exceptionMessage
} ?: run {
reqData = exceptionMessage
}
}
is BaseAction.PinAction -> {
// PinAction 特定处理可以在这里添加
}
is BaseAction.WebSocketAction -> {
action.request?.let { request ->
url = request.url
method = WebSocketUtil.WEB_SOCKET_REQUEST_METHOD
reqHeader = request.headers.toJsonString()
if (request.params.isNotEmpty()) {
val messages = JSONArray().apply {
request.params.forEach { param ->
put(param.value)
}
}
reqData = messages.toString()
} else {
reqData = exceptionMessage
}
} ?: run {
reqData = exceptionMessage
}
}
else -> {
LogUtils.info("unknown action type: ${action::class.java.simpleName}")
reqData = exceptionMessage
}
}
}
// ==================== 步骤控制 ====================
protected fun List<Next>.getNextStepIndex(
wholeResponse: String,
currentStep: Int
): Int {
if (isEmpty() || wholeResponse.isBlank()) {
return currentStep + 1
}
filter { it.step > 0 }.forEach { next ->
// 检查包含匹配
if (next.contain.isNotBlank() && wholeResponse.contains(next.contain)) {
return next.step
}
// 检查正则匹配
if (next.regexp.isNotBlank()) {
Regex(next.regexp).find(wholeResponse)?.let {
return next.step
}
}
}
return Int.MAX_VALUE
}
}

@ -0,0 +1,4 @@
package com.example.service
class HttpService {
}
Loading…
Cancel
Save