diff --git a/app/http/controller/web/search_controller.go b/app/http/controller/web/search_controller.go index 13a8e25..64795de 100644 --- a/app/http/controller/web/search_controller.go +++ b/app/http/controller/web/search_controller.go @@ -27,14 +27,18 @@ func (s *Search) SearchAll(context *gin.Context) { animals = model.CreateAnimalFactory("").ShowByName(query) // 2. Encounter - encounterIds, _ := model_es.CreateEncounterESFactory(nil).QueryDocumentsMatchAll(query) + _, _ = model_es.CreateEncounterESFactory(nil).QueryDocumentsMatchAll(query) - if len(encounterIds) > 0 { - encounters = model.CreateEncounterFactory("").ShowByIDs(encounterIds) - } + // if len(encounterIds) > 0 { + // encounters = model.CreateEncounterFactory("").ShowByIDs(encounterIds) + // } + + // 3. Knowledge + knowledges, _ := model_es.CreateKnowledgeESFactory().QueryDocumentsMatchAll(query, 3) response.Success(context, consts.CurdStatusOkMsg, gin.H{ "animals": animals, "encounters": encounters, + "knowledges": knowledges, }) } diff --git a/app/model_es/encounter.go b/app/model_es/encounter.go index e4fdcba..2a25152 100644 --- a/app/model_es/encounter.go +++ b/app/model_es/encounter.go @@ -128,26 +128,22 @@ func (e *Encounter) UpdateDocument(client *elasticsearch.Client, encounter *Enco * @param {string} query * @return {*} 对应 Encounter 的 id,然后交给 MySQL 来查询详细的信息? */ -func (e *Encounter) QueryDocumentsMatchAll(query string) ([]int64, error) { +func (e *Encounter) QueryDocumentsMatchAll(query string) ([]Encounter, error) { ctx := context.Background() // 创建查询请求 req := esapi.SearchRequest{ // UPDATE 同时实现查询高亮? Index: []string{e.IndexName()}, + // INFO 采取高光的设定,所以还是用 ES 的返回值会比较好; "_source": ["id"], Body: strings.NewReader(fmt.Sprintf(`{ - "_source": ["id"], "query": { "bool": { "should": [ { - "match": { - "title": "%s" - } + "match": { "title": "%s" } }, { - "match": { - "content": "%s" - } + "match": { "content": "%s" } } ] } @@ -187,39 +183,37 @@ func (e *Encounter) QueryDocumentsMatchAll(query string) ([]int64, error) { return nil, fmt.Errorf("error extracting hits from response") } - fmt.Println(hits) + // // 转换为 id 切片 + // var ids []int64 + // for _, hit := range hits { + // hitMap := hit.(map[string]interface{})["_source"].(map[string]interface{}) + // id := int64(hitMap["id"].(float64)) + // ids = append(ids, id) + // } + // return ids, nil - // 转换为 id 切片 - var ids []int64 + // 转换为 Encounter 切片 + var encounters []*Encounter for _, hit := range hits { - hitMap := hit.(map[string]interface{})["_source"].(map[string]interface{}) - id := int64(hitMap["id"].(float64)) - ids = append(ids, id) + hitMap := hit.(map[string]interface{}) + source := hitMap["_source"].(map[string]interface{}) + // highlight := hitMap["highlight"].(map[string]interface{}) + + // TIP 将 []interface{} 转换为 []string + tagsInterface := source["tags"].([]interface{}) + tags := make([]string, len(tagsInterface)) + for i, tag := range tagsInterface { + tags[i] = tag.(string) + } + + encounter := &Encounter{ + Id: int64(source["id"].(float64)), + Title: source["title"].(string), + Content: source["content"].(string), + Tags: tags, + } + encounters = append(encounters, encounter) } - return ids, nil - - // // 转换为 Encounter 切片 - // var encounters []*Encounter - // for _, hit := range hits { - // hitMap := hit.(map[string]interface{}) - // source := hitMap["_source"].(map[string]interface{}) - - // // TIP 将 []interface{} 转换为 []string - // tagsInterface := source["tags"].([]interface{}) - // tags := make([]string, len(tagsInterface)) - // for i, tag := range tagsInterface { - // tags[i] = tag.(string) - // } - - // encounter := &Encounter{ - // Id: int64(source["id"].(float64)), - // Title: source["title"].(string), - // Content: source["content"].(string), - // Tags: tags, - // } - // encounters = append(encounters, encounter) - // } - - // return encounters, nil + return []Encounter{}, nil } diff --git a/app/model_es/knowledge.go b/app/model_es/knowledge.go index 5b51820..71d8594 100644 --- a/app/model_es/knowledge.go +++ b/app/model_es/knowledge.go @@ -3,6 +3,8 @@ package model_es import ( "bytes" "catface/app/global/variable" + "catface/app/utils/data_bind" + "catface/app/utils/model_handler" "context" "encoding/json" "fmt" @@ -142,3 +144,89 @@ func (k *Knowledge) RandomDocuments(num int) ([]*Knowledge, error) { return documents, nil } + +/** + * @description: 使用 ES 的 match 匹配虽有字段,同时处理 highlight + * @param {string} query 查询字符串 + * @param {int} num 查询数量 + * @return {*} + */ +func (k *Knowledge) QueryDocumentsMatchAll(query string, num int) ([]Knowledge, error) { + ctx := context.Background() + + body := fmt.Sprintf(`{ + "size": %d, + "query": { + "bool": { + "should": [ + { "match": {"title": "%s" }}, + { "match": {"content": "%s" }} + ] + } + }, + "highlight": { + "pre_tags": [""], + "post_tags": [""], + "fields": { + "title": {}, + "content": { + "fragment_size" : 20 + } + } + } +}`, num, query, query) // TODO dirs 我还没想好如何处理 + // 创建请求 + req := esapi.SearchRequest{ + Index: []string{k.IndexName()}, + Body: strings.NewReader(body), + } + + // 发送请求 + res, err := req.Do(ctx, variable.ElasticClient) + if err != nil { + return nil, err + } + defer res.Body.Close() + + if res.IsError() { + var k map[string]interface{} + if err := json.NewDecoder(res.Body).Decode(&k); err != nil { + return nil, fmt.Errorf("error parsing the response body: %s", err) + } else { + return nil, fmt.Errorf("[%s] %s: %s", + res.Status(), + k["error"].(map[string]interface{})["type"], + k["error"].(map[string]interface{})["reason"], + ) + } + } + + var result map[string]interface{} + if err := json.NewDecoder(res.Body).Decode(&result); err != nil { + return nil, err + } + + hits, ok := result["hits"].(map[string]interface{})["hits"].([]interface{}) + if !ok { + return nil, fmt.Errorf("error extracting hits from response") + } + + var knowledges []Knowledge + for _, hit := range hits { + hitMap := hit.(map[string]interface{}) + source := hitMap["_source"].(map[string]interface{}) + highlight := hitMap["highlight"].(map[string]interface{}) + + for k, v := range highlight { + source[k] = model_handler.TransStringSliceToString(v.([]interface{})) + } + + var k Knowledge + if err := data_bind.ShouldBindFormMapToModel(source, &k); err != nil { + return nil, err + } + knowledges = append(knowledges, k) + } + + return knowledges, nil +} diff --git a/app/utils/data_bind/formdata_to_model.go b/app/utils/data_bind/formdata_to_model.go index 55e2db5..640d434 100644 --- a/app/utils/data_bind/formdata_to_model.go +++ b/app/utils/data_bind/formdata_to_model.go @@ -80,3 +80,64 @@ func fieldSetValue(c *gin.Context, valueOf reflect.Value, typeOf reflect.Type, c } } } + +/** + * @description: + * @param {map[string]interface{}} m + * @param {interface{}} modelStruct + * @return {*} + */ +func ShouldBindFormMapToModel(m map[string]interface{}, modelStruct interface{}) error { + mTypeOf := reflect.TypeOf(modelStruct) + if mTypeOf.Kind() != reflect.Ptr { + return errors.New(modelStructMustPtr) + } + mValueOf := reflect.ValueOf(modelStruct) + + mValueOfEle := mValueOf.Elem() + mtf := mValueOf.Elem().Type() + fieldNum := mtf.NumField() + for i := 0; i < fieldNum; i++ { + if !mtf.Field(i).Anonymous && mtf.Field(i).Type.Kind() != reflect.Struct { + fieldSetValueByMap(m, mValueOfEle, mtf, i) + } else if mtf.Field(i).Type.Kind() == reflect.Struct { // INFO 处理结构体。 + // TODO 处理结构体(有名+匿名) + } + } + return nil +} + +func fieldSetValueByMap(m map[string]interface{}, valueOf reflect.Value, typeOf reflect.Type, colIndex int) { + relaKey := typeOf.Field(colIndex).Tag.Get("json") + if relaKey != "-" { + switch typeOf.Field(colIndex).Type.Kind() { + case reflect.Int, reflect.Int16, reflect.Int32, reflect.Int64: + valueOf.Field(colIndex).SetInt(int64(m[relaKey].(float64))) + return + case reflect.Float32, reflect.Float64: + valueOf.Field(colIndex).SetFloat(m[relaKey].(float64)) + return + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + valueOf.Field(colIndex).SetUint(uint64(m[relaKey].(float64))) + return + case reflect.String: + valueOf.Field(colIndex).SetString(m[relaKey].(string)) + return + case reflect.Bool: + valueOf.Field(colIndex).SetBool(m[relaKey].(bool)) + return + case reflect.Slice: + interfaceSlice := m[relaKey].([]interface{}) + stringSlice := make([]string, len(interfaceSlice)) + // 遍历并进行类型断言 + for i, v := range interfaceSlice { + stringSlice[i] = v.(string) + } + // 设置字段值 + valueOf.Field(colIndex).Set(reflect.ValueOf(stringSlice)) + return + default: + return + } + } +} diff --git a/app/utils/model_handler/model_es_handler.go b/app/utils/model_handler/model_es_handler.go new file mode 100644 index 0000000..3985be2 --- /dev/null +++ b/app/utils/model_handler/model_es_handler.go @@ -0,0 +1,11 @@ +package model_handler + +func TransStringSliceToString(strs []interface{}) string { + var result string + for _, str := range strs { + if s, ok := str.(string); ok { + result += s + } + } + return result +}