485 lines
12 KiB
Go
485 lines
12 KiB
Go
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
|
||
}
|