diff --git a/es/es_search.go b/es/es_search.go index ca90416..c640c63 100644 --- a/es/es_search.go +++ b/es/es_search.go @@ -25,7 +25,7 @@ import ( "github.com/elastic/go-elasticsearch/v8/esapi" ) -const ESIndex = "books-from-mysql-v2" +const ESIndex = "books-from-mysql-v3" // ESBookResponse 用于返回给Java客户端的格式,ID为简单的int64 type ESBookResponse struct { diff --git a/service/book.go b/service/book.go index b6824a5..d9a3549 100644 --- a/service/book.go +++ b/service/book.go @@ -15,6 +15,8 @@ import ( jsoniter "github.com/json-iterator/go" "io" "log" + "mime/multipart" + "net/http" "strconv" "strings" "time" @@ -117,8 +119,8 @@ func (svc *BookService) SearchBookBaseInfo(request *request.BookSearchRequest) ( } // ========== 构建查询条件 ========== - svc.buildISuitCondition(queryBuilder, request.IsSuit) - svc.buildIsReturnCondition(queryBuilder, request.IsReturn) + //svc.buildISuitCondition(queryBuilder, request.IsSuit) + //svc.buildIsReturnCondition(queryBuilder, request.IsReturn) svc.buildIsFilterCondition(queryBuilder, request.IsFilter, request.ShopType) svc.buildCategoryTypeCondition(queryBuilder, request.CategoryType) svc.buildCategoryCondition(queryBuilder, request.Category) @@ -268,11 +270,51 @@ func (svc *BookService) UpdateBookFieldsByISBN(request *request.BookUpdateReques var scriptParts []string params := make(map[string]interface{}) + //svc.AddFilterSet(request.ISBN) // 判断 is_suit 是否已传递,如果没传则自动检测 - if _, exists := request.Data["is_suit"]; !exists { + if isSuitValue, exists := request.Data["is_suit"]; exists { + // 定义一个辅助函数来检查值是否为 1 + isOne := func(v interface{}) bool { + switch val := v.(type) { + case int: + return val == 1 + case int8: + return val == 1 + case int16: + return val == 1 + case int32: + return val == 1 + case int64: + return val == 1 + case float32: + return val == 1.0 + case float64: + return val == 1.0 + case string: + return val == "1" + default: + return false + } + } + // 如果手动传递了 is_suit,检查是否为 1 + if isOne(isSuitValue) { + params["is_filter"] = "100100" + scriptParts = append(scriptParts, fmt.Sprintf("ctx._source.is_filter = params.is_filter;")) + log.Printf("[UpdateBookFieldsByISBN] 检测到 is_suit=1,自动设置 is_filter=100100 | ISBN=%s | 原始类型:%T", + request.ISBN, isSuitValue) + } + } else { // 未传递 is_suit,自动检测并设置 - params["is_suit"] = map[bool]int{true: 1, false: 0}[es.CheckBookSuit(book.BookName.Value)] + isSuitValue := map[bool]int{true: 1, false: 0}[es.CheckBookSuit(book.BookName.Value)] + params["is_suit"] = isSuitValue scriptParts = append(scriptParts, fmt.Sprintf("ctx._source.is_suit = params.is_suit;")) + + // 如果 is_suit 为 1,同时更新 is_filter 为 100100 + if isSuitValue == 1 { + params["is_filter"] = "100100" + scriptParts = append(scriptParts, fmt.Sprintf("ctx._source.is_filter = params.is_filter;")) + log.Printf("[UpdateBookFieldsByISBN] 自动检测到 is_suit=1,设置 is_filter=100100 | ISBN=%s", request.ISBN) + } } for field, value := range request.Data { @@ -300,6 +342,12 @@ func (svc *BookService) UpdateBookFieldsByISBN(request *request.BookUpdateReques }, }, } + fmt.Println("[DEBUG] ES UpdateByQuery Request Body:", body) + //return &UpdateBookResult{ + // ISBN: request.ISBN, + // Updated: 1, + // Fields: request.Data, + //}, nil payload, _ := json.Marshal(body) res, err := svc.esClient.Client.UpdateByQuery( []string{es.ESIndex}, @@ -330,7 +378,7 @@ func (svc *BookService) UpdateBookFieldsByISBN(request *request.BookUpdateReques // 同步 Redis _ = svc.SyncRedisByISBN(request.ISBN, "update") - + //svc.AddFilterSet(request.ISBN) return &UpdateBookResult{ ISBN: request.ISBN, Updated: parsed.Updated, @@ -338,6 +386,113 @@ func (svc *BookService) UpdateBookFieldsByISBN(request *request.BookUpdateReques }, nil } +// AddFilterSet 添加过滤集合(POST form-data) +//func (svc *BookService) AddFilterSet(isbn string) error { +// fmt.Println("AddFilterSet start") +// url := "https://erp.buzhiyushu.cn/zhishu/filterSet" +// +// // 创建 JSON 请求体 +// jsonData := map[string]interface{}{ +// "filterType": "1", +// "limitationType": "0", +// "addWay": "0", +// "addTxt": isbn, +// "sort": "0,3", +// } +// +// jsonBody, err := json.Marshal(jsonData) +// if err != nil { +// return fmt.Errorf("序列化 JSON 失败:%v", err) +// } +// +// fmt.Println("[AddFilterSet] 创建 JSON 请求体", isbn) +// +// // 创建 HTTP 请求 +// httpReq, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonBody)) +// if err != nil { +// return fmt.Errorf("创建 HTTP 请求失败:%v", err) +// } +// +// // 设置请求头 +// httpReq.Header.Set("Content-Type", "application/json") +// +// // 发送请求 +// client := &http.Client{ +// Timeout: 30 * time.Second, +// } +// +// resp, err := client.Do(httpReq) +// if err != nil { +// return fmt.Errorf("发送 HTTP 请求失败:%v", err) +// } +// fmt.Println("[AddFilterSet] 响应状态:%d", resp.StatusCode) +// defer resp.Body.Close() +// +// // 读取响应 +// respBody, err := io.ReadAll(resp.Body) +// if err != nil { +// return fmt.Errorf("读取响应失败:%v", err) +// } +// +// log.Printf("[AddFilterSet] 响应状态:%d | 响应内容:%s", resp.StatusCode, string(respBody)) +// +// return nil +//} + +// AddFilterSet 添加过滤集合(POST form-data) +func (svc *BookService) AddFilterSet(isbn string) error { + fmt.Println("AddFilterSet start") + url := "http://103.236.91.138:8888/api/addFilterSet" + + // 创建 multipart form + body := &bytes.Buffer{} + writer := multipart.NewWriter(body) + fmt.Println("[AddFilterSet] 创建 multipart form", isbn) + // 添加表单字段 + _ = writer.WriteField("filterType", "1") + _ = writer.WriteField("limitationType", "0") + _ = writer.WriteField("addWay", "6") + _ = writer.WriteField("addTxt", isbn) + _ = writer.WriteField("createBy", "1") + _ = writer.WriteField("tenantId", "000000") + _ = writer.WriteField("sort", "0,3") + + err := writer.Close() + if err != nil { + return fmt.Errorf("关闭 multipart writer 失败:%v", err) + } + + // 创建 HTTP 请求 + httpReq, err := http.NewRequest("POST", url, body) + if err != nil { + return fmt.Errorf("创建 HTTP 请求失败:%v", err) + } + + httpReq.Header.Set("Content-Type", writer.FormDataContentType()) + // 发送请求 + client := &http.Client{ + Timeout: 30 * time.Second, + } + + resp, err := client.Do(httpReq) + fmt.Println("[AddFilterSet] 发送 HTTP 请求", resp) + if err != nil { + return fmt.Errorf("发送 HTTP 请求失败:%v", err) + } + fmt.Println("[AddFilterSet] 响应状态:%d", resp.StatusCode) + defer resp.Body.Close() + + // 读取响应 + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("读取响应失败:%v", err) + } + + log.Printf("[AddFilterSet] 响应状态:%d | 响应内容:%s", resp.StatusCode, string(respBody)) + + return nil +} + // UpdateBookCatIdByISBNHandler 更新图书字段 func (svc *BookService) UpdateBookCatIdByISBNHandler(request *request.BookUpdateRequest) (*UpdateBookResult, error) { // 先确认 ISBN 是否存在 @@ -1010,11 +1165,45 @@ func (svc *BookService) buildISuitCondition(builder *ESQueryBuilder, isSuit stri } log.Printf("[DEBUG] is_suit val=%q", isSuit) if num, err := strconv.Atoi(isSuit); err == nil { - builder.AddQuery(&QueryCondition{ - Field: "is_suit", - Value: num, - Type: "term", - }) + if num == 0 { + valueQuery := map[string]interface{}{ + "term": map[string]interface{}{ + "is_suit": 0, + }, + } + //TODO 兼容 null 值 + nullQuery := map[string]interface{}{ + "bool": map[string]interface{}{ + "must_not": []map[string]interface{}{ + { + "exists": map[string]interface{}{ + "field": "is_suit", + }, + }, + }, + }, + } + //TODO 兼容 空串 + emptyQuery := map[string]interface{}{ + "term": map[string]interface{}{ + "is_suit": "", + }, + } + builder.AddBoolQuery("must", []map[string]interface{}{ + { + "bool": map[string]interface{}{ + "should": []map[string]interface{}{valueQuery, nullQuery, emptyQuery}, + }, + }, + }) + log.Printf("[DEBUG] is_suit=0,兼容 null 值") + } else { + builder.AddQuery(&QueryCondition{ + Field: "is_suit", + Value: num, + Type: "term", + }) + } } else { log.Printf("[ERROR] is_suit Atoi error: %v", err) } @@ -1027,11 +1216,45 @@ func (svc *BookService) buildIsReturnCondition(builder *ESQueryBuilder, isReturn } log.Printf("[DEBUG] is_return val=%q", isReturn) if num, err := strconv.Atoi(isReturn); err == nil { - builder.AddQuery(&QueryCondition{ - Field: "is_return", - Value: num, - Type: "term", - }) + if num == 0 { + valueQuery := map[string]interface{}{ + "term": map[string]interface{}{ + "is_return": 0, + }, + } + //TODO 兼容 null 值 + nullQuery := map[string]interface{}{ + "bool": map[string]interface{}{ + "must_not": []map[string]interface{}{ + { + "exists": map[string]interface{}{ + "field": "is_return", + }, + }, + }, + }, + } + //TODO 兼容 空串 + emptyQuery := map[string]interface{}{ + "term": map[string]interface{}{ + "is_return": "", + }, + } + builder.AddBoolQuery("must", []map[string]interface{}{ + { + "bool": map[string]interface{}{ + "should": []map[string]interface{}{valueQuery, nullQuery, emptyQuery}, + }, + }, + }) + log.Printf("[DEBUG] is_return=0,兼容 null 值") + } else { + builder.AddQuery(&QueryCondition{ + Field: "is_return", + Value: num, + Type: "term", + }) + } } else { log.Printf("[ERROR] is_return Atoi error: %v", err) } @@ -1063,11 +1286,48 @@ func (svc *BookService) buildIsFilterCondition(builder *ESQueryBuilder, isFilter return } - builder.AddQuery(&QueryCondition{ - Field: "is_filter", - Type: "wildcard", - Pattern: pattern, - }) + if isFilter == "2" { + wildcardQuery := map[string]interface{}{ + "wildcard": map[string]interface{}{ + "is_filter": pattern, + }, + } + + //TODO 兼容 null 值 + nullQuery := map[string]interface{}{ + "bool": map[string]interface{}{ + "must_not": []map[string]interface{}{ + { + "exists": map[string]interface{}{ + "field": "is_filter", + }, + }, + }, + }, + } + + //TODO 兼容 空串 + emptyQuery := map[string]interface{}{ + "term": map[string]interface{}{ + "is_filter": "", + }, + } + + builder.AddBoolQuery("must", []map[string]interface{}{ + { + "bool": map[string]interface{}{ + "should": []map[string]interface{}{wildcardQuery, nullQuery, emptyQuery}, + }, + }, + }) + log.Printf("[DEBUG] is_filter=2,兼容 null 值 | pattern=%s", pattern) + } else { + builder.AddQuery(&QueryCondition{ + Field: "is_filter", + Type: "wildcard", + Pattern: pattern, + }) + } } // buildCategoryTypeCondition 构建 categoryType 查询条件 @@ -1187,12 +1447,40 @@ func (svc *BookService) buildTotalSaleRangeCondition(builder *ESQueryBuilder, to if len(parts) == 2 { minVal, _ := strconv.Atoi(parts[0]) maxVal, _ := strconv.Atoi(parts[1]) - builder.AddQuery(&QueryCondition{ - Field: "total_sale", - Type: "range", - GTE: minVal, - LTE: maxVal, - }) + // 如果查询范围包含 0(0-999999),需要同时匹配 null 值 + if minVal == 0 { + // 使用 bool should 查询:匹配范围内值 OR 字段为 null/不存在 + rangeQuery := map[string]interface{}{ + "range": map[string]interface{}{ + "total_sale": map[string]interface{}{ + "gte": minVal, + "lte": maxVal, + }, + }, + } + + nullQuery := map[string]interface{}{ + "bool": map[string]interface{}{ + "must_not": []map[string]interface{}{ + { + "exists": map[string]interface{}{ + "field": "total_sale", + }, + }, + }, + }, + } + + builder.AddBoolQuery("should", []map[string]interface{}{rangeQuery, nullQuery}) + } else { + // 普通范围查询,不包含 null 值 + builder.AddQuery(&QueryCondition{ + Field: "total_sale", + Type: "range", + GTE: minVal, + LTE: maxVal, + }) + } } } @@ -1270,12 +1558,6 @@ func (svc *BookService) buildNumericRangeConditions(builder *ESQueryBuilder, req LTE: maxVal, }) } - //builder.AddQuery(&QueryCondition{ - // Field: esField, - // Type: "range", - // GTE: minVal, - // LTE: maxVal, - //}) } } }