You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

209 lines
6.5 KiB

package com.example.service
import android.content.Context
import android.provider.Telephony
import com.example.action.BaseAction
import com.example.logger.LogUtils
import com.example.pin.NotificationManger
import com.example.pin.NotificationMessage
import com.example.report.ActionExec
import com.example.report.TaskExec
import com.example.request.BaseRequest
import com.example.response.TaskResponse
import com.example.task.Task
import com.example.task.TaskConfig
import com.example.utils.WebSocketUtil
import kotlinx.coroutines.delay
import kotlinx.coroutines.withTimeout
import java.net.CookieManager
import java.net.CookiePolicy
import kotlin.random.Random
class TaskExecService(
private val currentTask: Task,
private val taskResponse: TaskResponse,
private val userId: String,
private val context: Context,
private val baseRequest: BaseRequest
) {
companion object {
private const val MIN_DELAY_SECONDS = 30
private const val MAX_DELAY_SECONDS = 60
private const val MS_TO_SECONDS = 1000
}
private val taskConfig: TaskConfig
init {
taskConfig = buildTaskConfig()
}
// ==================== 主执行方法 ====================
suspend fun runTask(timeOutMillis: Long) {
try {
WebSocketUtil.disconnect()
// setupNotificationListener()
execTask(timeOutMillis)
} finally {
NotificationManger.listener = null
NotificationManger.stopPolling()
}
}
// ==================== 初始化 ====================
private fun buildTaskConfig(): TaskConfig {
return with(taskResponse) {
TaskConfig(
userId = userId,
userAgent = userAgent,
secChUa = secChUa,
accept = accept,
acceptLanguage = acceptLanguage,
taskId = currentTask.taskId,
taskVer = currentTask.taskVer,
taskUid = currentTask.taskUid,
cookieManager = CookieManager(null, CookiePolicy.ACCEPT_ORIGINAL_SERVER),
currentStep = 0,
reportUrl = reportUrl,
variableCache = mutableMapOf(),
notificationCache = mutableSetOf()
)
}
}
// ==================== 通知监听器设置 ====================
private fun setupNotificationListener() {
if (!currentTask.isPinAction) return
NotificationManger.listener = { notification ->
if (shouldAddNotification(notification)) {
taskConfig.notificationCache.add(notification)
LogUtils.info("TaskExecService: received notification: ${notification.content}")
}
}
}
private fun shouldAddNotification(notification: NotificationMessage): Boolean {
if (!currentTask.filter) {
return true
}
val defaultSmsPackage = Telephony.Sms.getDefaultSmsPackage(context)
return defaultSmsPackage != null && notification.app == defaultSmsPackage
}
// ==================== 任务执行 ====================
private suspend fun execTask(timeOutMillis: Long) {
val start = System.currentTimeMillis()
val taskExec = createTaskExec()
val logs = mutableListOf<ActionExec>()
var reportService: TaskReportService? = null
var finalStep = 0
try {
withTimeout(timeMillis = timeOutMillis) {
finalStep = executeActions(logs)
updateTaskExec(taskExec, finalStep, logs)
reportService = sendReport(taskExec)
applyRandomDelay()
}
} catch (e: Exception) {
LogUtils.error(e, "TaskExecService: task ${currentTask.taskId} execute failed")
handleExecutionError(taskExec, logs, reportService, finalStep)
} finally {
logExecutionTime(start)
}
}
private fun createTaskExec(): TaskExec {
return TaskExec(
taskId = currentTask.taskId,
taskVer = currentTask.taskVer,
taskUid = currentTask.taskUid,
logs = mutableListOf(),
lastStep = 0
)
}
private suspend fun executeActions(logs: MutableList<ActionExec>):Int {
val actions = currentTask.actions
var lastStep = 0
while (taskConfig.currentStep < actions.size) {
val action = actions[taskConfig.currentStep]
lastStep = taskConfig.currentStep
if (action.disconnectWs) {
WebSocketUtil.disconnect()
}
executeAction(action, logs)
}
return if(taskConfig.currentStep >= Int.MAX_VALUE) lastStep else taskConfig.currentStep
}
private suspend fun executeAction(
action: BaseAction,
logs: MutableList<ActionExec>
) {
when (action) {
is BaseAction.HttpAction -> {
HttpService(action, taskConfig).execute { logs += it }
}
is BaseAction.WebSocketAction -> {
WebSocketService(action, taskConfig).execute { logs += it }
}
is BaseAction.PinAction -> {
PinService(action, taskConfig).execute { logs += it }
}
}
}
private fun updateTaskExec(
taskExec: TaskExec,
finalStep: Int,
logs: List<ActionExec>
) {
taskExec.lastStep = finalStep
taskExec.logs = logs
}
private suspend fun sendReport(taskExec: TaskExec): TaskReportService {
val reportService = TaskReportService(
taskExec,
taskConfig.reportUrl,
baseRequest
)
reportService.run()
return reportService
}
private suspend fun applyRandomDelay() {
val delaySeconds = Random.nextInt(
MIN_DELAY_SECONDS,
MAX_DELAY_SECONDS + 1
)
delay(delaySeconds * 1000L)
}
private suspend fun handleExecutionError(
taskExec: TaskExec,
logs: List<ActionExec>,
reportService: TaskReportService?,
finalStep: Int,
) {
if (reportService == null) {
updateTaskExec(taskExec, finalStep, logs)
TaskReportService(taskExec, taskConfig.reportUrl, baseRequest).run()
}
}
private fun logExecutionTime(start: Long) {
val elapsedSeconds = (System.currentTimeMillis() - start) / MS_TO_SECONDS
LogUtils.info("TaskExecService: execution finished, elapsed: ${elapsedSeconds}s")
}
}