From dba98fcc4c300e3fa021f91bfab8fe7200ca6dfe Mon Sep 17 00:00:00 2001 From: mojo Date: Wed, 5 Nov 2025 17:15:07 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84=20WebSocketServi?= =?UTF-8?q?ce=EF=BC=8C=E6=8F=90=E5=8D=87=E4=BB=A3=E7=A0=81=E5=8F=AF?= =?UTF-8?q?=E8=AF=BB=E6=80=A7=E5=92=8C=E5=8F=AF=E7=BB=B4=E6=8A=A4=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/example/service/WebSocketService.kt | 287 ++++++++++++++++++ 1 file changed, 287 insertions(+) create mode 100644 lib/src/main/java/com/example/service/WebSocketService.kt diff --git a/lib/src/main/java/com/example/service/WebSocketService.kt b/lib/src/main/java/com/example/service/WebSocketService.kt new file mode 100644 index 0000000..96038d1 --- /dev/null +++ b/lib/src/main/java/com/example/service/WebSocketService.kt @@ -0,0 +1,287 @@ +package com.example.service + +import android.util.Log +import com.example.action.BaseAction +import com.example.action.NameValue +import com.example.action.WebSocketActionRequest +import com.example.action.WsRequestParam +import com.example.logger.LogUtils +import com.example.report.ActionExec +import com.example.task.TaskConfig +import com.example.utils.WebSocketUtil +import com.example.utils.WebSocketUtil.callRequest +import com.example.utils.toJsonString1 +import com.example.web_socket.WsRequest +import com.example.web_socket.WsResponse +import kotlinx.coroutines.launch +import java.nio.charset.StandardCharsets + +class WebSocketService( + private val action: BaseAction.WebSocketAction, + override val taskConfig: TaskConfig +) : BaseService(taskConfig) { + + companion object { + private const val WS_PROTOCOL = "ws://" + private const val WSS_PROTOCOL = "wss://" + private const val HTTP_PROTOCOL = "http://" + private const val HTTPS_PROTOCOL = "https://" + } + + // ==================== 主执行方法 ==================== + + override suspend fun execute(onFinish: (List) -> Unit) { + val actionExecList = mutableListOf() + val currentStep = taskConfig.currentStep + + runCatching { + val actionRequest = action.request + ?: throw NullPointerException("request is null") + + amendActionRequest(actionRequest) + val wsRequest = buildWsRequest(actionRequest) + + val result = if (action.async) { + handleAsyncRequest(wsRequest, actionExecList) + } else { + handleSyncRequest(wsRequest, actionExecList, currentStep) + } + + updateTaskStep(result, currentStep) + }.onFailure { e -> + LogUtils.error(e) + handleException(e, actionExecList, currentStep) + } + + onFinish(actionExecList) + } + + // ==================== 请求构建 ==================== + + private fun buildWsRequest(actionRequest: WebSocketActionRequest): WsRequest { + return WsRequest( + url = actionRequest.url, + headers = actionRequest.headers.nameValueToMap().toMutableMap(), + method = WebSocketUtil.WEB_SOCKET_REQUEST_METHOD, + delay = action.delay, + messages = actionRequest.params + ) + } + + // ==================== 请求处理 ==================== + + private data class RequestResult( + val wsResponse: WsResponse?, + val wsReceiveMsg: String + ) + + private suspend fun handleAsyncRequest( + wsRequest: WsRequest, + actionExecList: MutableList + ): RequestResult { + scope.launch { + callRequest(wsRequest) + } + + val actionExec = wsRequest.genActionExec(null) + actionExec.respCode = ASYNC_EXEC_CODE + actionExecList += actionExec + + return RequestResult(null, "") + } + + private suspend fun handleSyncRequest( + wsRequest: WsRequest, + actionExecList: MutableList, + currentStep: Int + ): RequestResult { + val wsResponse = callRequest(wsRequest) + val actionExec = wsRequest.genActionExec(wsResponse) + actionExecList += actionExec + + val wsReceiveMsg = wsResponse.data?.toString() ?: "" + + if (wsReceiveMsg.isNotEmpty()) { + extractBodyVariableToCache( + action, + wsReceiveMsg, + wsReceiveMsg.toByteArray(StandardCharsets.UTF_8) + ) + } + + return RequestResult(wsResponse, wsReceiveMsg) + } + + // ==================== 步骤更新 ==================== + + private fun updateTaskStep(result: RequestResult, currentStep: Int) { + val nextStep = result.wsResponse?.let { response -> + calculateNextStep(response, result.wsReceiveMsg, currentStep) + } ?: run { + // 异步请求或没有响应时,继续下一步 + currentStep + 1 + } + + taskConfig.currentStep = nextStep + } + + private fun calculateNextStep( + wsResponse: WsResponse, + wsReceiveMsg: String, + currentStep: Int + ): Int { + return when (wsResponse.code) { + WebSocketUtil.WEB_SOCKET_CODE -> { + action.next.getNextStepIndex(wsReceiveMsg, currentStep) + } + else -> { + if (action.skipError) { + currentStep + 1 + } else { + Int.MAX_VALUE + } + } + } + } + + // ==================== 异常处理 ==================== + + private fun handleException( + e: Throwable, + actionExecList: MutableList, + currentStep: Int + ) { + val actionExec = genExceptionActionExec( + action, + ERROR_CODE_WS_ACTION_EXEC_FAILED, + Log.getStackTraceString(e) + ) + actionExecList += actionExec + + taskConfig.currentStep = if (action.skipError) { + currentStep + 1 + } else { + Int.MAX_VALUE + } + } + + // ==================== 请求修改 ==================== + + private fun amendActionRequest(actionRequest: WebSocketActionRequest) { + // 替换变量 + actionRequest.headers.replaceVariableData() + actionRequest.cookies.replaceVariableData() + actionRequest.params.replaceWsParamVariableData() + + // 处理 data 字段 + handleRequestData(actionRequest) + + // 添加基础 Header + actionRequest.headers = actionRequest.headers.amendBaseHeader(false) + + // 处理 URL 和 Cookie + processUrlAndCookie(actionRequest) + } + + private fun handleRequestData(actionRequest: WebSocketActionRequest) { + if (actionRequest.data.isNotBlank()) { + actionRequest.data = actionRequest.data.toVariableData() + + // 如果 data 存在且 params 不为空,将 data 作为新的 param + if (actionRequest.params.isNotEmpty()) { + actionRequest.params = listOf( + WsRequestParam(value = actionRequest.data) + ) + } + } + } + + private fun processUrlAndCookie(actionRequest: WebSocketActionRequest) { + val normalizedUrl = actionRequest.url.toVariableData() + + runCatching { + val cookieUrl = normalizeUrlProtocol(normalizedUrl) + actionRequest.url = cookieUrl + amendCookie(actionRequest, cookieUrl) + }.onFailure { + LogUtils.error(it, "Failed to process URL and cookie") + actionRequest.url = normalizedUrl + } + } + + private fun normalizeUrlProtocol(url: String): String { + return when { + url.startsWith(WSS_PROTOCOL, ignoreCase = true) -> { + url.replace(WSS_PROTOCOL, HTTPS_PROTOCOL, ignoreCase = true) + } + url.startsWith(WS_PROTOCOL, ignoreCase = true) -> { + url.replace(WS_PROTOCOL, HTTP_PROTOCOL, ignoreCase = true) + } + else -> url + } + } + + private fun amendCookie( + actionRequest: WebSocketActionRequest, + cookieUrl: String + ) { + val cookies = mutableSetOf() + + // 从 CookieManager 获取 Cookie + if (actionRequest.autoCookie) { + cookies += cookieFromCookieManager(cookieUrl) + } + + // 添加手动指定的 Cookie(覆盖同名 Cookie) + actionRequest.cookies.forEach { cookie -> + cookies.remove(cookie) + cookies += cookie + } + + // 构建 Cookie Header + if (cookies.isNotEmpty()) { + actionRequest.headers += cookies.toList().buildCookie() + } + } + + // ==================== 变量替换 ==================== + + private fun List.replaceWsParamVariableData() { + forEach { param -> + param.value = param.value.toVariableData() + param.interrupt = param.interrupt.toVariableData() + } + } + + // ==================== ActionExec 生成 ==================== + + private fun WsRequest.genActionExec(wsResponse: WsResponse?): ActionExec { + val actionExec = ActionExec( + step = taskConfig.currentStep, + index = 1, + time = System.currentTimeMillis(), + url = url, + method = method, + reqHeader = headers.toJsonString1() + ) + + // 构建请求数据 + if (messages.isNotEmpty()) { + actionExec.reqData = messages.joinToString(",") { it.value } + } + + // 填充响应数据 + wsResponse?.let { response -> + fillResponseData(actionExec, response) + } + + return actionExec + } + + private fun fillResponseData(actionExec: ActionExec, response: WsResponse) { + actionExec.respCode = response.code + actionExec.respData = response.data?.toString() ?: "" + actionExec.respHeader = response.headers.toJsonString1() + actionExec.cost = response.endTime - response.startTime + } +} \ No newline at end of file