Beacon/utils/parameter_processor.go
2025-06-27 01:01:58 +08:00

485 lines
12 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package utils
import (
"encoding/json"
"fmt"
"go.uber.org/zap"
"regexp"
"strconv"
"strings"
"beacon/pkg/pb"
)
// ParameterProcessor 参数处理器用于处理activity之间的参数传递
type ParameterProcessor struct {
globalVariables map[string]interface{}
activityResults map[int64]*ActivityResult
}
// ActivityResult 用于存储每个activity的执行结果
type ActivityResult struct {
StepID int64 `json:"step_id"`
StepName string `json:"step_name"`
Success bool `json:"success"`
Data interface{} `json:"data"`
}
// NewParameterProcessor 创建新的参数处理器
func NewParameterProcessor(globalVariables map[string]interface{}) *ParameterProcessor {
return &ParameterProcessor{
globalVariables: globalVariables,
activityResults: make(map[int64]*ActivityResult),
}
}
// AddActivityResult 添加activity执行结果
func (p *ParameterProcessor) AddActivityResult(stepID int64, result *ActivityResult) {
p.activityResults[stepID] = result
}
// ProcessTemplate 处理参数模板,替换其中的变量引用
func (p *ParameterProcessor) ProcessTemplate(template string) (string, error) {
if template == "" {
return template, nil
}
result := template
// 1. 替换全局变量引用 ${global.key}
result = p.replaceGlobalVariables(result)
// 2. 替换步骤结果变量引用 ${step.stepId.field}
result = p.replaceStepVariables(result)
// 3. 替换条件变量引用 ${if.condition:value1:value2}
result = p.replaceConditionalVariables(result)
// 4. 替换函数调用 ${func:arg1:arg2}
result = p.replaceFunctionCalls(result)
return result, nil
}
// replaceGlobalVariables 替换全局变量引用
func (p *ParameterProcessor) replaceGlobalVariables(template string) string {
// 使用正则表达式匹配 ${global.key} 格式
re := regexp.MustCompile(`\$\{global\.([^}]+)\}`)
return re.ReplaceAllStringFunc(template, func(match string) string {
// 提取变量名
matches := re.FindStringSubmatch(match)
if len(matches) < 2 {
return match // 保持原样
}
key := matches[1]
if value, exists := p.globalVariables[key]; exists {
return fmt.Sprintf("%v", value)
}
return match // 变量不存在,保持原样
})
}
// replaceStepVariables 替换步骤结果变量引用
func (p *ParameterProcessor) replaceStepVariables(template string) string {
// 匹配 ${step.stepId.field} 格式
re := regexp.MustCompile(`\$\{step\.(\d+)\.([^}]+)\}`)
return re.ReplaceAllStringFunc(template, func(match string) string {
matches := re.FindStringSubmatch(match)
if len(matches) < 3 {
return match
}
stepIDStr := matches[1]
field := matches[2]
stepID, err := strconv.ParseInt(stepIDStr, 10, 64)
if err != nil {
return match
}
result, exists := p.activityResults[stepID]
if !exists {
return match
}
return p.extractFieldValue(result, field)
})
}
// replaceConditionalVariables 替换条件变量引用
func (p *ParameterProcessor) replaceConditionalVariables(template string) string {
// 匹配 ${if.condition:value1:value2} 格式
re := regexp.MustCompile(`\$\{if\.([^:]+):([^:]*):([^}]*)\}`)
return re.ReplaceAllStringFunc(template, func(match string) string {
matches := re.FindStringSubmatch(match)
if len(matches) < 4 {
return match
}
condition := matches[1]
valueIfTrue := matches[2]
valueIfFalse := matches[3]
if p.evaluateCondition(condition) {
return valueIfTrue
}
return valueIfFalse
})
}
// replaceFunctionCalls 替换函数调用
func (p *ParameterProcessor) replaceFunctionCalls(template string) string {
// 匹配 ${func:arg1:arg2} 格式
re := regexp.MustCompile(`\$\{([^:]+):([^}]*)\}`)
return re.ReplaceAllStringFunc(template, func(match string) string {
matches := re.FindStringSubmatch(match)
if len(matches) < 3 {
return match
}
funcName := matches[1]
args := matches[2]
return p.executeFunction(funcName, args)
})
}
// extractFieldValue 从activity结果中提取指定字段的值
func (p *ParameterProcessor) extractFieldValue(result *ActivityResult, field string) string {
switch result.Data.(type) {
case *pb.ApiTestResult:
return p.extractApiResultField(result.Data.(*pb.ApiTestResult), field)
case *pb.UiTestResult:
return p.extractUiResultField(result.Data.(*pb.UiTestResult), field)
default:
return ""
}
}
// extractApiResultField 从API测试结果中提取字段值
func (p *ParameterProcessor) extractApiResultField(result *pb.ApiTestResult, field string) string {
switch field {
case "response_body":
// 保证输出是 JSON 字符串格式
b, err := json.Marshal(result.ResponseBody)
if err == nil {
return string(b[1 : len(b)-1]) // 去掉外层引号
}
return result.ResponseBody
case "actual_status_code":
return strconv.Itoa(int(result.ActualStatusCode))
case "success":
return strconv.FormatBool(result.BaseResult.Success)
case "message":
return result.BaseResult.Message
default:
zap.L().Debug("未知字段", zap.String("field", field), zap.String("ResponseBody", result.ResponseBody))
// 检查是否是响应头
if strings.HasPrefix(field, "headers.") {
headerKey := strings.TrimPrefix(field, "headers.")
if headerValue, exists := result.Headers[headerKey]; exists {
return headerValue
}
}
// 检查是否是JSON字段
if strings.HasPrefix(field, "json.") {
jsonField := strings.TrimPrefix(field, "json.")
return p.extractJsonField(result.ResponseBody, jsonField)
}
return ""
}
}
// extractUiResultField 从UI测试结果中提取字段值
func (p *ParameterProcessor) extractUiResultField(result *pb.UiTestResult, field string) string {
switch field {
case "screenshot_url":
return result.ScreenshotUrl
case "html_report_url":
return result.HtmlReportUrl
case "success":
return strconv.FormatBool(result.BaseResult.Success)
case "message":
return result.BaseResult.Message
default:
return ""
}
}
// extractJsonField 从JSON响应体中提取指定字段
func (p *ParameterProcessor) extractJsonField(responseBody, field string) string {
if responseBody == "" {
return ""
}
var data map[string]interface{}
if err := json.Unmarshal([]byte(responseBody), &data); err != nil {
return ""
}
// 支持嵌套字段,如 "user.id"
keys := strings.Split(field, ".")
current := data
for i, key := range keys {
if i == len(keys)-1 {
// 最后一个键
if value, exists := current[key]; exists {
return fmt.Sprintf("%v", value)
}
} else {
// 中间键,需要继续深入
if next, exists := current[key]; exists {
if nextMap, ok := next.(map[string]interface{}); ok {
current = nextMap
} else {
return ""
}
} else {
return ""
}
}
}
return ""
}
// evaluateCondition 评估条件表达式
func (p *ParameterProcessor) evaluateCondition(condition string) bool {
// 支持简单的条件表达式
// 例如step.123.success, global.environment == "production"
// 检查步骤成功状态
if strings.HasSuffix(condition, ".success") {
stepIDStr := strings.TrimSuffix(condition, ".success")
if strings.HasPrefix(stepIDStr, "step.") {
stepIDStr = strings.TrimPrefix(stepIDStr, "step.")
stepID, err := strconv.ParseInt(stepIDStr, 10, 64)
if err == nil {
if result, exists := p.activityResults[stepID]; exists {
return result.Success
}
}
}
}
// 检查全局变量
if strings.HasPrefix(condition, "global.") {
key := strings.TrimPrefix(condition, "global.")
if value, exists := p.globalVariables[key]; exists {
// 转换为布尔值
switch v := value.(type) {
case bool:
return v
case string:
return v != "" && v != "false" && v != "0"
case int, int64, float64:
return fmt.Sprintf("%v", v) != "0"
default:
return value != nil
}
}
}
// 检查相等性
if strings.Contains(condition, "==") {
parts := strings.Split(condition, "==")
if len(parts) == 2 {
left := strings.TrimSpace(parts[0])
right := strings.TrimSpace(strings.Trim(parts[1], `"'`))
// 检查全局变量
if strings.HasPrefix(left, "global.") {
key := strings.TrimPrefix(left, "global.")
if value, exists := p.globalVariables[key]; exists {
return fmt.Sprintf("%v", value) == right
}
}
}
}
return false
}
// executeFunction 执行函数调用
func (p *ParameterProcessor) executeFunction(funcName, args string) string {
argList := strings.Split(args, ":")
switch funcName {
case "concat":
// 连接字符串
return strings.Join(argList, "")
case "upper":
// 转换为大写
if len(argList) > 0 {
return strings.ToUpper(argList[0])
}
case "lower":
// 转换为小写
if len(argList) > 0 {
return strings.ToLower(argList[0])
}
case "substring":
// 子字符串
if len(argList) >= 3 {
str := argList[0]
start, _ := strconv.Atoi(argList[1])
end, _ := strconv.Atoi(argList[2])
if start < len(str) && end <= len(str) && start < end {
return str[start:end]
}
}
case "default":
// 默认值
if len(argList) >= 2 {
value := argList[0]
defaultValue := argList[1]
if value == "" {
return defaultValue
}
return value
}
}
return ""
}
// GetAvailableVariables 获取所有可用的变量列表
func (p *ParameterProcessor) GetAvailableVariables() map[string][]string {
variables := make(map[string][]string)
// 全局变量
var globalVars []string
for key := range p.globalVariables {
globalVars = append(globalVars, key)
}
variables["global"] = globalVars
// 步骤变量
for stepID, result := range p.activityResults {
stepKey := fmt.Sprintf("step.%d", stepID)
var stepVars []string
switch result.Data.(type) {
case *pb.ApiTestResult:
stepVars = []string{
"response_body",
"actual_status_code",
"success",
"message",
"headers.*",
"json.*",
}
case *pb.UiTestResult:
stepVars = []string{
"screenshot_url",
"html_report_url",
"success",
"message",
}
}
variables[stepKey] = stepVars
}
return variables
}
// ValidateTemplate 验证模板中的变量引用是否有效
func (p *ParameterProcessor) ValidateTemplate(template string) (bool, []string) {
var errors []string
// 检查全局变量引用
globalRe := regexp.MustCompile(`\$\{global\.([^}]+)\}`)
matches := globalRe.FindAllStringSubmatch(template, -1)
for _, match := range matches {
if len(match) >= 2 {
key := match[1]
if _, exists := p.globalVariables[key]; !exists {
errors = append(errors, fmt.Sprintf("全局变量 '%s' 不存在", key))
}
}
}
// 检查步骤变量引用
stepRe := regexp.MustCompile(`\$\{step\.(\d+)\.([^}]+)\}`)
matches = stepRe.FindAllStringSubmatch(template, -1)
for _, match := range matches {
if len(match) >= 3 {
stepIDStr := match[1]
field := match[2]
stepID, err := strconv.ParseInt(stepIDStr, 10, 64)
if err != nil {
errors = append(errors, fmt.Sprintf("无效的步骤ID: %s", stepIDStr))
continue
}
if _, exists := p.activityResults[stepID]; !exists {
errors = append(errors, fmt.Sprintf("步骤 %d 不存在", stepID))
continue
}
// 检查字段是否有效
if !p.isValidField(stepID, field) {
errors = append(errors, fmt.Sprintf("步骤 %d 不支持字段: %s", stepID, field))
}
}
}
return len(errors) == 0, errors
}
// isValidField 检查字段是否有效
func (p *ParameterProcessor) isValidField(stepID int64, field string) bool {
result, exists := p.activityResults[stepID]
if !exists {
return false
}
switch result.Data.(type) {
case *pb.ApiTestResult:
validFields := map[string]bool{
"response_body": true,
"actual_status_code": true,
"success": true,
"message": true,
}
if validFields[field] {
return true
}
// 检查是否是响应头
if strings.HasPrefix(field, "headers.") {
return true
}
// 检查是否是JSON字段
if strings.HasPrefix(field, "json.") {
return true
}
case *pb.UiTestResult:
validFields := map[string]bool{
"screenshot_url": true,
"html_report_url": true,
"success": true,
"message": true,
}
return validFields[field]
}
return false
}