mirror of
https://github.com/black-ant/Ant-Browser.git
synced 2026-07-14 18:48:55 +08:00
338 lines
9.6 KiB
Go
338 lines
9.6 KiB
Go
package backend
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"ant-chrome/backend/internal/automation"
|
|
)
|
|
|
|
const dualInstanceRuntimeDefaultTimeoutMs = 45000
|
|
|
|
type dualInstanceRuntimeParams struct {
|
|
Browsers []dualInstanceRuntimeBrowserInput `json:"browsers"`
|
|
TimeoutMs int `json:"timeoutMs"`
|
|
SkipDefaultStartURLs *bool `json:"skipDefaultStartUrls"`
|
|
PrimaryCode string `json:"primaryCode"`
|
|
SecondaryCode string `json:"secondaryCode"`
|
|
}
|
|
|
|
type dualInstanceRuntimeBrowserInput struct {
|
|
Code string `json:"code"`
|
|
LaunchCode string `json:"launchCode"`
|
|
SkipDefaultStartURLs *bool `json:"skipDefaultStartUrls"`
|
|
StartURLs []string `json:"startUrls"`
|
|
LaunchArgs []string `json:"launchArgs"`
|
|
}
|
|
|
|
type dualInstanceRuntimeBrowser struct {
|
|
Code string
|
|
SkipDefaultStartURLs bool
|
|
StartURLs []string
|
|
LaunchArgs []string
|
|
}
|
|
|
|
func (a *App) runLaunchAPIScript(ctx context.Context, script automation.ScriptRecord, input automation.ScriptRunRequest) (string, string, string) {
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
if err := ctx.Err(); err != nil {
|
|
return "", "脚本执行失败", automationRunContextErrorMessage(err)
|
|
}
|
|
paramsText := resolveAutomationRunJSONText(input.ParamsText, script.ParamsText, input.UseScriptParams)
|
|
if script.ID == automation.DualInstanceRuntimeScriptID {
|
|
return a.runDualInstanceRuntimeLaunchAPIScript(ctx, paramsText)
|
|
}
|
|
|
|
selector, targetSummary, err := a.resolveAutomationEffectiveSelector(script, input, true)
|
|
if err != nil {
|
|
return "", "脚本执行失败", err.Error()
|
|
}
|
|
params, err := parseAutomationJSONObject(paramsText, false)
|
|
if err != nil {
|
|
return "", "脚本执行失败", err.Error()
|
|
}
|
|
|
|
body := make(map[string]any, len(params)+1)
|
|
body["selector"] = selector
|
|
for key, value := range params {
|
|
body[key] = value
|
|
}
|
|
|
|
status, payload, reqErr := a.automationDemoRequestWithContext(ctx, http.MethodPost, automationDemoLaunchPath, body)
|
|
if reqErr != nil {
|
|
if err := ctx.Err(); err != nil {
|
|
return "", "Launch API 请求失败", automationRunContextErrorMessage(err)
|
|
}
|
|
return "", "Launch API 请求失败", reqErr.Error()
|
|
}
|
|
|
|
responseText := marshalAutomationResultText(payload)
|
|
ok := status >= http.StatusOK && status < http.StatusMultipleChoices
|
|
if rawOK, exists := payload["ok"]; exists {
|
|
if payloadOK, valid := rawOK.(bool); valid {
|
|
ok = ok && payloadOK
|
|
}
|
|
}
|
|
|
|
summary := appendAutomationRunSummary(fmt.Sprintf("Launch API 响应 HTTP %d", status), targetSummary)
|
|
if ok {
|
|
return responseText, summary, ""
|
|
}
|
|
|
|
errorText := mapStringValue(payload, "error")
|
|
if errorText == "" {
|
|
errorText = fmt.Sprintf("launch api returned http %d", status)
|
|
}
|
|
return responseText, summary, errorText
|
|
}
|
|
|
|
func (a *App) runDualInstanceRuntimeLaunchAPIScript(ctx context.Context, paramsText string) (string, string, string) {
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
browsers, timeoutMs, err := parseDualInstanceRuntimeParams(paramsText)
|
|
if err != nil {
|
|
return "", "脚本执行失败", err.Error()
|
|
}
|
|
|
|
sessions := make([]map[string]interface{}, 0, len(browsers))
|
|
browserCodes := make([]string, 0, len(browsers))
|
|
|
|
for _, browser := range browsers {
|
|
if err := ctx.Err(); err != nil {
|
|
return buildDualInstanceRuntimeFailureResult(
|
|
sessions,
|
|
browserCodes,
|
|
"双实例流程超时",
|
|
automationRunContextErrorMessage(err),
|
|
)
|
|
}
|
|
sessionStatus, sessionPayload, reqErr := a.automationDemoRequestWithContext(
|
|
ctx,
|
|
http.MethodPost,
|
|
automationDemoRuntimeSessionPath,
|
|
map[string]any{
|
|
"selector": map[string]any{
|
|
"code": browser.Code,
|
|
"matchMode": "unique",
|
|
},
|
|
"skipDefaultStartUrls": browser.SkipDefaultStartURLs,
|
|
"startUrls": browser.StartURLs,
|
|
"launchArgs": browser.LaunchArgs,
|
|
"timeoutMs": timeoutMs,
|
|
},
|
|
)
|
|
sessionPayload = ensureAutomationPayload(sessionPayload, browser.Code)
|
|
sessions = append(sessions, sessionPayload)
|
|
if reqErr != nil {
|
|
if err := ctx.Err(); err != nil {
|
|
return buildDualInstanceRuntimeFailureResult(
|
|
sessions,
|
|
browserCodes,
|
|
"双实例流程超时",
|
|
automationRunContextErrorMessage(err),
|
|
)
|
|
}
|
|
return buildDualInstanceRuntimeFailureResult(
|
|
sessions,
|
|
browserCodes,
|
|
fmt.Sprintf("准备 %s Runtime 失败", browser.Code),
|
|
reqErr.Error(),
|
|
)
|
|
}
|
|
if !isAutomationDemoRequestOK(sessionStatus, sessionPayload) {
|
|
errText := mapStringValue(sessionPayload, "error")
|
|
if errText == "" {
|
|
errText = fmt.Sprintf("runtime session api returned http %d", sessionStatus)
|
|
}
|
|
return buildDualInstanceRuntimeFailureResult(
|
|
sessions,
|
|
browserCodes,
|
|
fmt.Sprintf("准备 %s Runtime 失败", browser.Code),
|
|
errText,
|
|
)
|
|
}
|
|
|
|
browserCodes = append(browserCodes, browser.Code)
|
|
}
|
|
|
|
summary := fmt.Sprintf(
|
|
"%d 个浏览器已就绪:%s",
|
|
len(browserCodes),
|
|
strings.Join(browserCodes, " / "),
|
|
)
|
|
result := map[string]any{
|
|
"ok": true,
|
|
"summary": summary,
|
|
"browserCodes": browserCodes,
|
|
"sessions": sessions,
|
|
}
|
|
return marshalAutomationResultText(result), summary, ""
|
|
}
|
|
|
|
func parseDualInstanceRuntimeParams(paramsText string) ([]dualInstanceRuntimeBrowser, int, error) {
|
|
normalizedText := strings.TrimSpace(paramsText)
|
|
if normalizedText == "" {
|
|
normalizedText = "{}"
|
|
}
|
|
|
|
var payload dualInstanceRuntimeParams
|
|
if err := json.Unmarshal([]byte(normalizedText), &payload); err != nil {
|
|
return nil, 0, fmt.Errorf("invalid json object: %w", err)
|
|
}
|
|
|
|
defaultSkipDefaultStartURLs := true
|
|
if payload.SkipDefaultStartURLs != nil {
|
|
defaultSkipDefaultStartURLs = *payload.SkipDefaultStartURLs
|
|
}
|
|
|
|
browsers := make([]dualInstanceRuntimeBrowser, 0, len(payload.Browsers))
|
|
for index, item := range payload.Browsers {
|
|
code := normalizeDualInstanceRuntimeCode(item.Code)
|
|
if code == "" {
|
|
code = normalizeDualInstanceRuntimeCode(item.LaunchCode)
|
|
}
|
|
if code == "" && index < 2 {
|
|
code = dualInstanceRuntimeDefaultCode(index)
|
|
}
|
|
if code == "" {
|
|
continue
|
|
}
|
|
|
|
skipDefaultStartURLs := defaultSkipDefaultStartURLs
|
|
if item.SkipDefaultStartURLs != nil {
|
|
skipDefaultStartURLs = *item.SkipDefaultStartURLs
|
|
}
|
|
startURLs := normalizeDualInstanceRuntimeStrings(item.StartURLs)
|
|
if len(startURLs) == 0 {
|
|
startURLs = dualInstanceRuntimeDefaultStartURLs(index)
|
|
}
|
|
|
|
browsers = append(browsers, dualInstanceRuntimeBrowser{
|
|
Code: code,
|
|
SkipDefaultStartURLs: skipDefaultStartURLs,
|
|
StartURLs: startURLs,
|
|
LaunchArgs: normalizeDualInstanceRuntimeStrings(item.LaunchArgs),
|
|
})
|
|
}
|
|
|
|
if len(browsers) == 0 {
|
|
for index, code := range []string{
|
|
normalizeDualInstanceRuntimeCode(payload.PrimaryCode),
|
|
normalizeDualInstanceRuntimeCode(payload.SecondaryCode),
|
|
} {
|
|
if code == "" {
|
|
continue
|
|
}
|
|
browsers = append(browsers, dualInstanceRuntimeBrowser{
|
|
Code: code,
|
|
SkipDefaultStartURLs: defaultSkipDefaultStartURLs,
|
|
StartURLs: dualInstanceRuntimeDefaultStartURLs(index),
|
|
})
|
|
}
|
|
}
|
|
|
|
if len(browsers) == 0 {
|
|
browsers = append(browsers,
|
|
dualInstanceRuntimeBrowser{
|
|
Code: dualInstanceRuntimeDefaultCode(0),
|
|
SkipDefaultStartURLs: defaultSkipDefaultStartURLs,
|
|
StartURLs: dualInstanceRuntimeDefaultStartURLs(0),
|
|
},
|
|
dualInstanceRuntimeBrowser{
|
|
Code: dualInstanceRuntimeDefaultCode(1),
|
|
SkipDefaultStartURLs: defaultSkipDefaultStartURLs,
|
|
StartURLs: dualInstanceRuntimeDefaultStartURLs(1),
|
|
},
|
|
)
|
|
}
|
|
|
|
timeoutMs := dualInstanceRuntimeDefaultTimeoutMs
|
|
if payload.TimeoutMs > 0 {
|
|
timeoutMs = payload.TimeoutMs
|
|
if timeoutMs < 1000 {
|
|
timeoutMs = 1000
|
|
}
|
|
}
|
|
|
|
return browsers, timeoutMs, nil
|
|
}
|
|
|
|
func normalizeDualInstanceRuntimeCode(value string) string {
|
|
return strings.ToUpper(strings.TrimSpace(value))
|
|
}
|
|
|
|
func normalizeDualInstanceRuntimeStrings(values []string) []string {
|
|
result := make([]string, 0, len(values))
|
|
for _, value := range values {
|
|
normalized := strings.TrimSpace(value)
|
|
if normalized != "" {
|
|
result = append(result, normalized)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func dualInstanceRuntimeDefaultCode(index int) string {
|
|
switch index {
|
|
case 0:
|
|
return "BUYER_001"
|
|
case 1:
|
|
return "BUYER_002"
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
func dualInstanceRuntimeDefaultStartURLs(index int) []string {
|
|
switch index {
|
|
case 0:
|
|
return []string{"https://finance.sina.com.cn/"}
|
|
case 1:
|
|
return []string{"https://map.baidu.com/"}
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func ensureAutomationPayload(payload map[string]interface{}, requestedCode string) map[string]interface{} {
|
|
if payload == nil {
|
|
payload = map[string]interface{}{}
|
|
}
|
|
if strings.TrimSpace(requestedCode) != "" {
|
|
payload["requestedCode"] = strings.ToUpper(strings.TrimSpace(requestedCode))
|
|
}
|
|
return payload
|
|
}
|
|
|
|
func isAutomationDemoRequestOK(status int, payload map[string]interface{}) bool {
|
|
ok := status >= http.StatusOK && status < http.StatusMultipleChoices
|
|
if rawOK, exists := payload["ok"]; exists {
|
|
if payloadOK, valid := rawOK.(bool); valid {
|
|
ok = ok && payloadOK
|
|
}
|
|
}
|
|
return ok
|
|
}
|
|
|
|
func buildDualInstanceRuntimeFailureResult(
|
|
sessions []map[string]interface{},
|
|
browserCodes []string,
|
|
step string,
|
|
errorText string,
|
|
) (string, string, string) {
|
|
result := map[string]any{
|
|
"ok": false,
|
|
"summary": "双实例流程执行失败",
|
|
"error": errorText,
|
|
"step": step,
|
|
"browserCodes": browserCodes,
|
|
"sessions": sessions,
|
|
}
|
|
return marshalAutomationResultText(result), "双实例流程执行失败", errorText
|
|
}
|