diff --git a/server/handler/advanceSearch.go b/server/handler/advanceSearch.go index ad6d8fb5b8a53a5c84ad4a9a6aaa9fe85c54ffab..7ebbb34177582417955cef20fd55deb19c18661d 100644 --- a/server/handler/advanceSearch.go +++ b/server/handler/advanceSearch.go @@ -3,6 +3,7 @@ package handler import ( "encoding/json" "fmt" + "strings" "gitee.com/openeuler/PilotGo-plugin-elk/server/elasticClient" "gitee.com/openeuler/PilotGo-plugin-elk/server/errormanager" @@ -143,3 +144,118 @@ func buildElasticQuery1(conditions map[string]interface{}) (string, error) { return string(jsonQuery), nil } + +// Condition:条件树的接口 +type Condition interface { + Evaluate(context map[string]string) bool + String() string +} + +// ConditionNode:条件节点 +type ConditionNode struct { + Key string + Value string +} + +func (c ConditionNode) Evaluate(context map[string]string) bool { + return context[c.Key] == c.Value +} + +func (c ConditionNode) String() string { + return fmt.Sprintf("%s=%s", c.Key, c.Value) +} + +// AndNode:与逻辑 +type AndNode struct { + Children []Condition +} + +func (a AndNode) Evaluate(context map[string]string) bool { + for _, child := range a.Children { + if !child.Evaluate(context) { + return false + } + } + return true +} + +func (a AndNode) String() string { + parts := make([]string, len(a.Children)) + for i, child := range a.Children { + parts[i] = child.String() + } + return fmt.Sprintf("(%s)", strings.Join(parts, ",")) +} + +// OrNode:或逻辑 +type OrNode struct { + Children []Condition +} + +func (o OrNode) Evaluate(context map[string]string) bool { + for _, child := range o.Children { + if child.Evaluate(context) { + return true + } + } + return false +} + +func (o OrNode) String() string { + parts := make([]string, len(o.Children)) + for i, child := range o.Children { + parts[i] = child.String() + } + return fmt.Sprintf("(%s)", strings.Join(parts, "|")) +} + +// 解析单个条件 +func parseCondition(expr string) (Condition, error) { + parts := strings.SplitN(expr, "=", 2) + if len(parts) != 2 { + return nil, errors.New("invalid condition format") + } + return ConditionNode{Key: parts[0], Value: parts[1]}, nil +} + +// 解析整个表达式并返回构建的条件树 +func parseExpression1(expr string) (Condition, error) { + + // 解析为单个条件 + cond, err := parseCondition(expr) + if err == nil { + return cond, nil + } + + // 解析为 And 表达式 + if strings.HasPrefix(expr, "(") && strings.HasSuffix(expr, ")") && strings.Contains(expr, ",") { + innerExpr := expr[1 : len(expr)-1] // 去掉外层的括号 + parts := strings.Split(innerExpr, ",") + children := make([]Condition, len(parts)) + for i, part := range parts { + child, err := parseExpression1(strings.TrimSpace(part)) // 递归解析每个部分 + if err != nil { + return nil, err + } + children[i] = child + } + return AndNode{Children: children}, nil + } + + // 解析为 Or 表达式 + if strings.Contains(expr, "|") { + parts := strings.Split(expr, "|") + children := make([]Condition, len(parts)) + for i, part := range parts { + child, err := parseExpression1(strings.TrimSpace(part)) // 递归解析每个部分 + if err != nil { + return nil, err + } + children[i] = child + } + return OrNode{Children: children}, nil + } + + // 无匹配项,返回错误 + return nil, errors.New("invalid expression format") +} diff --git a/server/handler/advanceSearch_test.go b/server/handler/advanceSearch_test.go new file mode 100644 index 0000000000000000000000000000000000000000..1132b8bdda75b03414af132992bb8185a720a593 --- /dev/null +++ b/server/handler/advanceSearch_test.go @@ -0,0 +1,340 @@ +package handler + +import ( + "encoding/json" + "fmt" + "strings" + "testing" + + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" +) + +func Test_buildESQuery(t *testing.T) { + type args struct { + conditions string + } + tests := []struct { + name string + args args + want string + wantErr bool + }{ + { + name: "test-1", + args: args{ + conditions: "name:John", + }, + want: `{"query":{"bool":{"must":[{"term":{"name":"John"}}]}}}`, + }, + { + name: "test-2", + args: args{ + conditions: "name:John,age:25", + }, + want: `{"query":{"bool":{"must":[{"term":{"name":"John"}},{"term":{"age":25}}]}}}`, + }, + { + name: "test-3", + args: args{ + conditions: "name:John,age:", + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := buildESQuery(tt.args.conditions) + if (err != nil) != tt.wantErr { + t.Errorf("buildESQuery() error = %v, wantErr %v", err, tt.wantErr) + return + } + assert.JSONEq(t, tt.want, got) + }) + } +} + +func Test_convertToESTermsQuery(t *testing.T) { + queryStr := "A:1|2|3" + esQuery, err := convertToESTermsQuery(queryStr) + if err != nil { + fmt.Println("Error:", err) + return + } + fmt.Println("ES Query:", esQuery) + + // 另一个示例 + queryStr2 := "B:a|b|c" + esQuery2, err := convertToESTermsQuery(queryStr2) + if err != nil { + fmt.Println("Error:", err) + return + } + fmt.Println("ES Query 2:", esQuery2) +} + +func Test_parseExpression1(t *testing.T) { + // expr := "A=a,B=(C=c|d)|e" + // cond, err := parseExpression(expr) + // if err != nil { + // fmt.Println("Error:", err) + // return + // } + // fmt.Println(cond) + // fmt.Println(cond.Evaluate(map[string]string{"A": "a", "B": "e", "C": "d"})) // 应该返回 true + expr := "(A=a,B=b)|(C=c|D=d)" + cond, err := parseExpression1(expr) + if err != nil { + fmt.Println("Error:", err) + return + } + fmt.Println(cond) // 这将打印出表达式树的字符串表示,但可能不是您期望的格式 +} + +/** +* 字符串分割查询 +* 格式:key1:value1,key2:value2... + */ + func buildESQuery(conditions string) (string, error) { + // 使用strings.Split分割conditions字符串 + pairs := strings.Split(conditions, ",") + // 初始化一个map来存储键值对 + queryMap := make(map[string]interface{}) + mustClause := make([]map[string]interface{}, 0) + for _, pair := range pairs { + // 进一步分割键值对 + keyVal := strings.Split(pair, ":") + if len(keyVal) != 2 { + return "", fmt.Errorf("invalid condition format: %s", pair) + } + // 构建一个匹配条件的map + condition := make(map[string]interface{}) + condition["term"] = map[string]interface{}{keyVal[0]: keyVal[1]} + // 将这个条件添加到mustClause中 + mustClause = append(mustClause, condition) + } + // 构建最终的查询 + queryMap["query"] = map[string]interface{}{ + "bool": map[string][]map[string]interface{}{ + "must": mustClause, + }, + } + // 将map转换为JSON字符串 + queryJSON, err := json.Marshal(queryMap) + if err != nil { + return "", err + } + return string(queryJSON), nil +} + +// QueryBuilder 结构体用于构建Elasticsearch查询 +type QueryBuilder struct { + Bool struct { + Must []map[string]interface{} `json:"must"` + } `json:"bool"` +} + +func parseAndBuildQuery(input string) ([]byte, error) { + // 分离键值对 + pairs := strings.Split(input, ",") + var mustConditions []map[string]interface{} + + for _, pair := range pairs { + keyVal := strings.SplitN(pair, ":", 2) + if len(keyVal) != 2 { + return nil, fmt.Errorf("invalid key-value pair: %s", pair) + } + + key := keyVal[0] + values := strings.Split(keyVal[1], "|") + + // 为每个值构建一个"or"查询 + var orConditions []map[string]interface{} + for _, value := range values { + orConditions = append(orConditions, map[string]interface{}{ + "term": map[string]interface{}{ + key: value, + }, + }) + } + + // 将"or"条件包装为"must" + mustConditions = append(mustConditions, map[string]interface{}{ + "bool": map[string]interface{}{ + "should": orConditions, + "minimum_should_match": 1, // 至少匹配一个 + }, + }) + } + + // 构建最终查询 + query := QueryBuilder{ + Bool: struct { + Must []map[string]interface{} `json:"must"` + }{ + Must: mustConditions, + }, + } + + // 转换为JSON + return json.MarshalIndent(query, "", " ") +} + +// 定义一个结构体,表示ES的terms查询 +type TermsQuery struct { + Terms struct { + Field string `json:"field"` + Values []string `json:"values"` + } `json:"terms"` +} + +// 将形如"A:1|2|3"或"A:a|b|c"的字符串转换为ES的terms查询 +func convertToESTermsQuery(queryStr string) (string, error) { + // 分割字符串以获取字段名和值 + parts := strings.SplitN(queryStr, ":", 2) + if len(parts) != 2 { + return "", errors.New("invalid query format") + } + fieldName := parts[0] + valuesStr := parts[1] + + // 分割值字符串以获取值的数组 + values := strings.Split(valuesStr, "|") + + // 构建ES查询 + esQuery := TermsQuery{ + Terms: struct { + Field string `json:"field"` + Values []string `json:"values"` + }{ + Field: fieldName, + Values: values, + }, + } + + // 将查询转换为JSON字符串 + queryJSON, err := json.Marshal(esQuery) + if err != nil { + return "", err + } + + return string(queryJSON), nil +} + +// 假设这是一个递归地构建的ES查询的一部分 +type ESQueryPart interface{} + +// 一个简单的ES查询条件 +type TermQuery struct { + Term struct { + Field string `json:"field"` + Value string `json:"value"` + } `json:"term"` +} + +// 一个嵌套的ES查询(假设它已经被解析为某种结构) +// 在实际应用中,这个可能是一个更复杂的结构体 +type NestedQuery struct { + // 这里只是简单示例,实际中可能包含多个字段和查询条件 + Bool struct { + Must []ESQueryPart `json:"must,omitempty"` + } `json:"bool,omitempty"` +} + +// 递归函数,解析查询字符串并构建ES查询 +func parseQueryString(queryStr string) ([]ESQueryPart, error) { + // 假设查询字符串是由逗号分隔的字段=值对,可能包含嵌套查询 + parts := strings.Split(queryStr, ",") + var queries []ESQueryPart + + for _, partStr := range parts { + // 检查是否为嵌套查询的格式(这里只是简单模拟) + if strings.HasPrefix(partStr, "=") && strings.HasSuffix(partStr, ")") { + // 假设我们有一个函数可以从字符串中提取嵌套查询(这里只是添加一个占位符) + // ... + // 由于我们不能直接从字符串中解析,这里只是添加一个NestedQuery的实例 + queries = append(queries, NestedQuery{}) + } else { + // 处理简单的字段=值对 + parts := strings.SplitN(partStr, "=", 2) + if len(parts) != 2 { + return nil, errors.New("invalid query format") + } + field, value := parts[0], parts[1] + queries = append(queries, TermQuery{ + Term: struct { + Field string `json:"field"` + Value string `json:"value"` + }{ + Field: field, + Value: value, + }, + }) + } + } + + return queries, nil +} + +// parseExpression 解析整个表达式 +// func parseExpression(expr string) (Condition, error) { +// // 使用正则表达式来简化处理,这里假设表达式格式正确 +// // 更好的实现可能需要一个完整的解析器 +// andRE := regexp.MustCompile(`\([^()\|,]+,([^()\|,]+)\)`) +// orRE := regexp.MustCompile(`\([^()\|,]+\|([^()\|,]+)\)`) + +// // 处理或逻辑 +// for orRE.MatchString(expr) { +// matches := orRE.FindStringSubmatch(expr) +// if len(matches) > 1 { +// left, err := parseExpression(expr[:orRE.FindStringIndex(expr)[0]]) +// if err != nil { +// return nil, err +// } +// right, err := parseExpression(matches[1]) +// if err != nil { +// return nil, err +// } +// expr = expr[orRE.FindStringIndex(expr)[1]:] +// expr = fmt.Sprintf("%s|%s%s", left.String(), right.String(), expr) +// if strings.HasPrefix(expr, "|") { +// expr = expr[1:] +// } +// orNode := OrNode{ +// Children: []Condition{left, right}, +// } +// return parseExpression(fmt.Sprintf("(%s)", expr)) +// } +// } + +// // 处理与逻辑 +// for andRE.MatchString(expr) { +// matches := andRE.FindStringSubmatch(expr) +// if len(matches) > 1 { +// left, err := parseExpression(expr[:andRE.FindStringIndex(expr)[0]]) +// if err != nil { +// return nil, err +// } +// right, err := parseExpression(matches[1]) +// if err != nil { +// return nil, err +// } +// expr = expr[andRE.FindStringIndex(expr)[1]:] +// expr = fmt.Sprintf("%s,%s%s", left.String(), right.String(), expr) +// if strings.HasPrefix(expr, ",") { +// expr = expr[1:] +// } +// andNode := AndNode{ +// Children: []Condition{left, right}, +// } +// return parseExpression(fmt.Sprintf("(%s)", expr)) +// } +// } + +// // 处理单个条件 +// cond, err := parseCondition(expr) +// if err != nil { +// return nil, err +// } +// return cond, nil +// } \ No newline at end of file diff --git a/server/handler/search.go b/server/handler/search.go index 39809c7a5d40f47dd01ac8662167c17c37cf6a40..10a15ee0c0efa85ab80849adabfbb26d4ecee0e0 100644 --- a/server/handler/search.go +++ b/server/handler/search.go @@ -82,5 +82,5 @@ func QueryHandler(ctx *gin.Context) { } bodyBytes, _ := io.ReadAll(res.Body) fmt.Printf("Response body: %s\n", bodyBytes) - response.Success(ctx, res, "") + response.Success(ctx, bodyBytes, "") } diff --git a/server/handler/test.json b/server/handler/test.json new file mode 100644 index 0000000000000000000000000000000000000000..699f59ef17de6827b4fbdd9c8abe39b7fd3fceb2 --- /dev/null +++ b/server/handler/test.json @@ -0,0 +1,69 @@ +map[string +]interface {}{ + "bool":map[string + ]interface {}{ + "must": []interface {}{map[string + ]interface {}{ + "bool":map[string + ]interface {}{ + "minimum_should_match": 1, + "should": []interface {}{map[string + ]interface {}{ + "term":map[string + ]interface {}{ + "name": "John" + } + }, map[string + ]interface {}{ + "term":map[string + ]interface {}{ + "name": "Tom" + } + }, map[string + ]interface {}{ + "term":map[string + ]interface {}{ + "name": "Bob" + } + } + } + } + }, map[string + ]interface {}{ + "term":map[string + ]interface {}{ + "age": "25" + } + } + } + } +} + + +map[string +]interface {}{ + "term":map[string + ]interface {}{ + "name": "Bob" + } +} +} +} +}, map[string +]interface {}{ +"bool":map[string +]interface {}{ +"minimum_should_match": 1, +"should": []interface {}{map[string +]interface {}{ + "term":map[string + ]interface {}{ + "age": 25 + } +} +} +} +} +} +} +} \ No newline at end of file diff --git a/server/service/dao/indexManager.go b/server/service/dao/indexManager.go new file mode 100644 index 0000000000000000000000000000000000000000..1e42c0a091b02b506ade88d1c77aff9efdad9211 --- /dev/null +++ b/server/service/dao/indexManager.go @@ -0,0 +1,128 @@ +package dao + +import ( + "context" + "encoding/json" + "fmt" + "io" + "log" + "strings" + + "gitee.com/openeuler/PilotGo-plugin-elk/server/elasticClient" + "gitee.com/openeuler/PilotGo-plugin-elk/server/errormanager" + "gitee.com/openeuler/PilotGo-plugin-elk/server/pluginclient" + "gitee.com/openeuler/PilotGo/sdk/response" + "github.com/elastic/go-elasticsearch/v7/esapi" + "github.com/gin-gonic/gin" + "github.com/pkg/errors" +) + +const ( + mapping = ` + { + "mappings": { + "properties": { + "v": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "normalizer": "lowercase" + } + } + } + } + } + }` +) + +// 创建索引请求 +func CreateeIndex(ctx *gin.Context, indexName string) { + defer ctx.Request.Body.Close() + req := esapi.IndicesCreateRequest{ + Index: indexName, + // 其它参数 + } + res, err := req.Do(context.Background(), elasticClient.Global_elastic.Client) + + // 关闭响应体 + defer res.Body.Close() + // 检查响应状态 + if err != nil { + err = errors.Errorf("%+v **warn**0", err.Error()) + errormanager.ErrorTransmit(pluginclient.Global_Context, err, false) + response.Fail(ctx, nil, errors.Cause(err).Error()) + return + } + if res.IsError() { + err = errors.Errorf("%+v **warn**0", res.String()) + errormanager.ErrorTransmit(pluginclient.Global_Context, err, false) + response.Fail(ctx, nil, res.String()) + return + } else { + resp_body_data := map[string]interface{}{} + resp_body_bytes, err := io.ReadAll(res.Body) + if err != nil { + err = errors.Errorf("%+v **warn**0", err.Error()) + errormanager.ErrorTransmit(pluginclient.Global_Context, err, false) + response.Fail(ctx, nil, res.String()) + return + } + json.Unmarshal(resp_body_bytes, &resp_body_data) + response.Success(ctx, resp_body_data, "") + } +} + +/* +* +创建索引 +*/ +func CreateNewIndex(indexName string) bool { + exists, err := elasticClient.Global_elastic.Client.Indices.Exists([]string{indexName}) + if err != nil { + fmt.Printf("indexName Existed ! err is %s\n", err) + return false + } + /** + 不存在就创建 + */ + if exists.StatusCode != 200 { + res, err := elasticClient.Global_elastic.Client.Indices.Create(indexName, elasticClient.Global_elastic.Client.Indices.Create.WithBody(strings.NewReader(mapping))) + if err != nil { + log.Fatal(err) + return false + } + defer res.Body.Close() + + var createRes map[string]interface{} + if err := json.NewDecoder(res.Body).Decode(&createRes); err != nil { + log.Fatal(err) + return false + } + + if acknowledged, ok := createRes["acknowledged"].(bool); ok && acknowledged { + fmt.Printf("indexName create success: %s\n", res.String()) + } else { + fmt.Printf("indexName create fail: %s\n", res.String()) + return false + } + } + return true +} + +// DeleteIndex 删除索引 +func DeleteIndex(ctx *gin.Context, indexName []string) { + // _, err := EsClient.Indices.Delete(indexName).Do(context.Background()) v8方式不适用 + req := esapi.IndicesDeleteRequest{ + Index: indexName, + } + _, err := req.Do(context.Background(), elasticClient.Global_elastic.Client) + if err != nil { + err = errors.Errorf("%+v **warn**0", err.Error()) + errormanager.ErrorTransmit(pluginclient.Global_Context, err, false) + response.Fail(ctx, nil, errors.Cause(err).Error()) + return + } + // fmt.Printf("delete index successed,indexName:%s", indexName) + return +} diff --git a/server/service/dao/searchtest.go b/server/service/dao/searchtest.go new file mode 100644 index 0000000000000000000000000000000000000000..323f1764b5db7965e82f414d9a2ac817e067ca23 --- /dev/null +++ b/server/service/dao/searchtest.go @@ -0,0 +1,59 @@ +package dao + +// import ( +// "context" +// "encoding/json" +// "fmt" +// "io" +// "log" + +// "github.com/elastic/go-elasticsearch/v7" +// "github.com/elastic/go-elasticsearch/v7/esapi" +// ) + +// // Search performs a search request with the given parameters +// func SearchTest(es *elasticsearch.Client, index string, queryBody io.Reader) (string, error) { +// // Prepare the search request +// req := esapi.SearchRequest{ +// Index: []string{index}, +// Body: queryBody, // Use the io.Reader directly +// Pretty: true, // pretty print the JSON response +// TrackTotalHits: &esapi.TrackTotalHits{ +// Relation: "eq", +// Value: true, +// }, +// } + +// // Perform the search request +// res, err := req.Do(context.Background(), es) +// if err != nil { +// log.Fatalf("Error getting response: %s", err) +// return "", err +// } +// defer res.Body.Close() + +// if res.IsError() { +// var e map[string]interface{} +// if err := json.NewDecoder(res.Body).Decode(&e); err != nil { +// log.Fatalf("Error parsing the response body: %s", err) +// return "", err +// } +// // Print the response status and error information. +// log.Fatalf("[%s] %s: %s", +// res.Status(), +// e["error"].(map[string]interface{})["type"], +// e["error"].(map[string]interface{})["reason"], +// ) +// return "", fmt.Errorf("Elasticsearch error: %s", e["error"].(map[string]interface{})["reason"]) +// } + +// // Read and return the entire response body +// var buf []byte +// if _, err := io.ReadAll(res.Body, &buf); err != nil { +// log.Fatalf("Error reading the response body: %s", err) +// return "", err +// } + +// return string(buf), nil +// } +// ;