🎏 finish search ALl
This commit is contained in:
parent
723dbae21c
commit
5ff73e318e
@ -2,8 +2,9 @@ package web
|
||||
|
||||
import (
|
||||
"catface/app/global/consts"
|
||||
"catface/app/model"
|
||||
"catface/app/model_es"
|
||||
animal_curd "catface/app/service/animals/curd"
|
||||
encouner_curd "catface/app/service/encounter/curd"
|
||||
"catface/app/utils/response"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
@ -20,18 +21,12 @@ type Search struct {
|
||||
func (s *Search) SearchAll(context *gin.Context) {
|
||||
query := context.GetString(consts.ValidatorPrefix + "query")
|
||||
|
||||
var animals []model.Animal
|
||||
var encounters []model.Encounter
|
||||
|
||||
// 1. Animal Name // TODO 增加字段的过滤,看前端了。
|
||||
animals = model.CreateAnimalFactory("").ShowByName(query)
|
||||
// animals = model.CreateAnimalFactory("").ShowByName(query)
|
||||
animals := animal_curd.CreateAnimalsCurdFactory().MatchAll(query, 3)
|
||||
|
||||
// 2. Encounter
|
||||
_, _ = model_es.CreateEncounterESFactory(nil).QueryDocumentsMatchAll(query)
|
||||
|
||||
// if len(encounterIds) > 0 {
|
||||
// encounters = model.CreateEncounterFactory("").ShowByIDs(encounterIds)
|
||||
// }
|
||||
encounters := encouner_curd.CreateEncounterCurdFactory().MatchAll(query, 3)
|
||||
|
||||
// 3. Knowledge
|
||||
knowledges, _ := model_es.CreateKnowledgeESFactory().QueryDocumentsMatchAll(query, 3)
|
||||
|
@ -15,19 +15,20 @@ func CreateAnimalFactory(sqlType string) *Animal {
|
||||
|
||||
type Animal struct {
|
||||
// UPDATE 或者这里都应该采取外键连接?
|
||||
BaseModel // 假设 BaseModel 中不需要添加 omitempty 标签
|
||||
Name string `gorm:"type:varchar(20)" json:"name,omitempty"` // 名称
|
||||
Birthday string `gorm:"size:10" json:"birthday,omitempty"` // 生日;就简单存string就好
|
||||
Gender uint8 `gorm:"default:1" json:"gender,omitempty"` // 性别
|
||||
Breed uint8 `gorm:"default:1" json:"breed,omitempty"` // 品种
|
||||
Sterilization uint8 `gorm:"default:1" json:"sterilization,omitempty"` // 1 不明 2 未绝育 3 已绝育
|
||||
Vaccination uint8 `gorm:"default:1" json:"vaccination,omitempty"` // 免疫状态
|
||||
Deworming uint8 `gorm:"default:1" json:"deworming,omitempty"` // 驱虫状态
|
||||
NickNames string `gorm:"type:varchar(31)" json:"nick_names,omitempty"` // 别称,辅助查询;存储上采取 , 间隔符的方式; VARCHAR 会比较合适
|
||||
NickNamesList []string `gorm:"-" json:"nick_names_list,omitempty"`
|
||||
Status uint8 `gorm:"default:1" json:"status,omitempty"` // 状态
|
||||
Description string `gorm:"column:description;type:varchar(255)" json:"description,omitempty"` // 简明介绍
|
||||
Tags string `json:"tags,omitempty"`
|
||||
BaseModel // 假设 BaseModel 中不需要添加 omitempty 标签
|
||||
Name string `gorm:"type:varchar(20)" json:"name,omitempty"` // 名称
|
||||
Birthday string `gorm:"size:10" json:"birthday,omitempty"` // 生日;就简单存string就好
|
||||
Gender uint8 `gorm:"default:1" json:"gender,omitempty"` // 性别
|
||||
Breed uint8 `gorm:"default:1" json:"breed,omitempty"` // 品种
|
||||
Sterilization uint8 `gorm:"default:1" json:"sterilization,omitempty"` // 1 不明 2 未绝育 3 已绝育
|
||||
Vaccination uint8 `gorm:"default:1" json:"vaccination,omitempty"` // 免疫状态
|
||||
Deworming uint8 `gorm:"default:1" json:"deworming,omitempty"` // 驱虫状态
|
||||
NickNames string `gorm:"type:varchar(31)" json:"nick_names,omitempty"` // 别称,辅助查询;存储上采取 , 间隔符的方式; VARCHAR 会比较合适
|
||||
NickNamesList []string `gorm:"-" json:"nick_names_list,omitempty"`
|
||||
NickNamesHighlight []string `gorm:"-" json:"nick_names_highlight,omitempty"` // INFO 配合 ES
|
||||
Status uint8 `gorm:"default:1" json:"status,omitempty"` // 状态
|
||||
Description string `gorm:"column:description;type:varchar(255)" json:"description,omitempty"` // 简明介绍
|
||||
Tags string `json:"tags,omitempty"`
|
||||
// TAG imaegs
|
||||
Avatar string `gorm:"type:varchar(50)" json:"avatar,omitempty"` // 缩略图 url,为 Go 获取 Photo 之后压缩处理后的图像,单独存储。
|
||||
AvatarHeight uint16 `json:"avatar_height,omitempty"` // 为了方便前端在加载图像前的骨架图 & 瀑布流展示。 // INFO 暂时没用到
|
||||
|
@ -22,11 +22,12 @@ type Encounter struct { // Encounter 或者称为 post,指的就是 Human 单
|
||||
UsersModel *UsersModel `json:"users_model,omitempty"` // INFO 由于 Detail 返回空子段有些麻烦,先尝试采用指针。
|
||||
// AnimalsId string `gorm:"size:20" json:"animals_id"` // 关联对象存在上限 // INFO 还是采取分表,方便查询。
|
||||
|
||||
Title string `gorm:"size:20;column:title" json:"title"`
|
||||
Content string `json:"content"`
|
||||
Level uint8 `json:"level" gorm:"column:level;default:1"`
|
||||
Tags string `json:"tags,omitempty" gorm:"column:tags;size:50"`
|
||||
TagsList []string `gorm:"-" json:"tags_list,omitempty"`
|
||||
Title string `gorm:"size:20;column:title" json:"title"`
|
||||
Content string `json:"content"`
|
||||
Level uint8 `json:"level" gorm:"column:level;default:1"`
|
||||
Tags string `json:"tags,omitempty" gorm:"column:tags;size:50"`
|
||||
TagsList []string `gorm:"-" json:"tags_list,omitempty"`
|
||||
TagsHighlight []string `gorm:"-" json:"tags_highlight,omitempty"`
|
||||
|
||||
// TAG Avatar 最好是压缩后的备份图像
|
||||
Avatar string `gorm:"type:varchar(50)" json:"avatar,omitempty"` // 缩略图 url,为 Go 获取 Photo 之后压缩处理后的图像,单独存储。
|
||||
|
@ -362,3 +362,14 @@ func (u *UsersModel) ShowByID(id int64, attrs ...string) (temp *UsersModel, err
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (u *UsersModel) ShowByIDs(ids []int64, attrs ...string) (temp []UsersModel) {
|
||||
db := u.DB.Table(u.TableName())
|
||||
|
||||
if len(attrs) > 0 {
|
||||
db = db.Select(attrs)
|
||||
}
|
||||
|
||||
db.Where("id in (?)", ids).Find(&temp)
|
||||
return
|
||||
}
|
||||
|
@ -4,6 +4,8 @@ import (
|
||||
"bytes"
|
||||
"catface/app/global/variable"
|
||||
"catface/app/model"
|
||||
"catface/app/utils/data_bind"
|
||||
"catface/app/utils/model_handler"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
@ -28,6 +30,9 @@ type Animal struct {
|
||||
Name string `json:"name"`
|
||||
NickNames []string `json:"nick_names"`
|
||||
Description string `json:"description"`
|
||||
|
||||
// After handler
|
||||
NickNamesHighlight []string `json:"nick_names_highlight"`
|
||||
}
|
||||
|
||||
func (a *Animal) IndexName() string {
|
||||
@ -70,3 +75,51 @@ func (a *Animal) InsertDocument() error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *Animal) QueryDocumentsMatchAll(query string, num int) ([]Animal, error) {
|
||||
body := fmt.Sprintf(`{
|
||||
"size": %d,
|
||||
"query": {
|
||||
"bool": {
|
||||
"should": [
|
||||
{ "match": {"name": "%s" }},
|
||||
{ "match": {"nick_names": "%s" }},
|
||||
{ "match": {"description": "%s" }}
|
||||
]
|
||||
}
|
||||
},
|
||||
"highlight": {
|
||||
"pre_tags": ["<em>"],
|
||||
"post_tags": ["</em>"],
|
||||
"fields": {
|
||||
"name": {},
|
||||
"nick_names": {
|
||||
"pre_tags": [""],
|
||||
"post_tags": [""]
|
||||
},
|
||||
"description": {
|
||||
"fragment_size" : 15
|
||||
}
|
||||
}
|
||||
}
|
||||
}`, num, query, query, query)
|
||||
|
||||
hits, err := model_handler.SearchRequest(body, a.IndexName())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var animals []Animal
|
||||
for _, hit := range hits {
|
||||
data := model_handler.MergeSouceWithHighlight(hit.(map[string]interface{}))
|
||||
|
||||
var animal Animal
|
||||
if err := data_bind.ShouldBindFormMapToModel(data, &animal); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
animals = append(animals, animal)
|
||||
}
|
||||
|
||||
return animals, nil
|
||||
}
|
||||
|
@ -4,10 +4,11 @@ import (
|
||||
"bytes"
|
||||
"catface/app/global/variable"
|
||||
"catface/app/model"
|
||||
"catface/app/utils/data_bind"
|
||||
"catface/app/utils/model_handler"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/elastic/go-elasticsearch/v8"
|
||||
"github.com/elastic/go-elasticsearch/v8/esapi"
|
||||
@ -33,6 +34,8 @@ type Encounter struct {
|
||||
Title string `json:"title"`
|
||||
Content string `json:"content"`
|
||||
Tags []string `json:"tags"`
|
||||
|
||||
TagsHighlight []string `json:"tags_highlight"`
|
||||
}
|
||||
|
||||
func (e *Encounter) IndexName() string {
|
||||
@ -128,92 +131,50 @@ func (e *Encounter) UpdateDocument(client *elasticsearch.Client, encounter *Enco
|
||||
* @param {string} query
|
||||
* @return {*} 对应 Encounter 的 id,然后交给 MySQL 来查询详细的信息?
|
||||
*/
|
||||
func (e *Encounter) QueryDocumentsMatchAll(query string) ([]Encounter, error) {
|
||||
ctx := context.Background()
|
||||
func (e *Encounter) QueryDocumentsMatchAll(query string, num int) ([]Encounter, error) {
|
||||
body := fmt.Sprintf(`{
|
||||
"size": %d,
|
||||
"query": {
|
||||
"bool": {
|
||||
"should": [
|
||||
{"match": {"tags": "%s"}},
|
||||
{"match": {"content": "%s"}},
|
||||
{"match": {"title": "%s"}}
|
||||
]
|
||||
}
|
||||
},
|
||||
"highlight": {
|
||||
"pre_tags": ["<em>"],
|
||||
"post_tags": ["</em>"],
|
||||
"fields": {
|
||||
"title": {},
|
||||
"content": {
|
||||
"fragment_size" : 15
|
||||
},
|
||||
"tags": {
|
||||
"pre_tags": [""],
|
||||
"post_tags": [""]
|
||||
}
|
||||
}
|
||||
}
|
||||
}`, num, query, query, query)
|
||||
|
||||
// 创建查询请求
|
||||
req := esapi.SearchRequest{ // UPDATE 同时实现查询高亮?
|
||||
Index: []string{e.IndexName()},
|
||||
// INFO 采取高光的设定,所以还是用 ES 的返回值会比较好; "_source": ["id"],
|
||||
Body: strings.NewReader(fmt.Sprintf(`{
|
||||
"query": {
|
||||
"bool": {
|
||||
"should": [
|
||||
{
|
||||
"match": { "title": "%s" }
|
||||
},
|
||||
{
|
||||
"match": { "content": "%s" }
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}`, query, query)),
|
||||
}
|
||||
|
||||
// 发送请求
|
||||
res, err := req.Do(ctx, variable.ElasticClient)
|
||||
hits, err := model_handler.SearchRequest(body, e.IndexName())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
if res.IsError() {
|
||||
var e map[string]interface{}
|
||||
if err := json.NewDecoder(res.Body).Decode(&e); err != nil {
|
||||
return nil, fmt.Errorf("error parsing the response body: %s", err)
|
||||
} else {
|
||||
return nil, fmt.Errorf("[%s] %s: %s",
|
||||
res.Status(),
|
||||
e["error"].(map[string]interface{})["type"],
|
||||
e["error"].(map[string]interface{})["reason"],
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 解析响应
|
||||
var r map[string]interface{}
|
||||
if err := json.NewDecoder(res.Body).Decode(&r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 提取命中结果
|
||||
hits, ok := r["hits"].(map[string]interface{})["hits"].([]interface{})
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("error extracting hits from response")
|
||||
}
|
||||
|
||||
// // 转换为 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
|
||||
|
||||
// 转换为 Encounter 切片
|
||||
var encounters []*Encounter
|
||||
var encounters []Encounter
|
||||
for _, hit := range hits {
|
||||
hitMap := hit.(map[string]interface{})
|
||||
source := hitMap["_source"].(map[string]interface{})
|
||||
// highlight := hitMap["highlight"].(map[string]interface{})
|
||||
data := model_handler.MergeSouceWithHighlight(hit.(map[string]interface{}))
|
||||
|
||||
// TIP 将 []interface{} 转换为 []string
|
||||
tagsInterface := source["tags"].([]interface{})
|
||||
tags := make([]string, len(tagsInterface))
|
||||
for i, tag := range tagsInterface {
|
||||
tags[i] = tag.(string)
|
||||
var encounter Encounter
|
||||
if err := data_bind.ShouldBindFormMapToModel(data, &encounter); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
encounter := &Encounter{
|
||||
Id: int64(source["id"].(float64)),
|
||||
Title: source["title"].(string),
|
||||
Content: source["content"].(string),
|
||||
Tags: tags,
|
||||
}
|
||||
encounters = append(encounters, encounter)
|
||||
}
|
||||
|
||||
return []Encounter{}, nil
|
||||
return encounters, nil
|
||||
}
|
||||
|
@ -218,6 +218,7 @@ func (k *Knowledge) QueryDocumentsMatchAll(query string, num int) ([]Knowledge,
|
||||
highlight := hitMap["highlight"].(map[string]interface{})
|
||||
|
||||
for k, v := range highlight {
|
||||
// INFO Knowledge 暂时不涉及 keywords 类型,就先这样处理。
|
||||
source[k] = model_handler.TransStringSliceToString(v.([]interface{}))
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@ package curd
|
||||
|
||||
import (
|
||||
"catface/app/model"
|
||||
"catface/app/model_es"
|
||||
"catface/app/utils/gorm_v2"
|
||||
"catface/app/utils/model_handler"
|
||||
"catface/app/utils/query_handler"
|
||||
@ -122,3 +123,35 @@ func (a *AnimalsCurd) Detail(id string) *model.Animal {
|
||||
|
||||
return model.CreateAnimalFactory("mysql").ShowByID(int64(idInt))
|
||||
}
|
||||
|
||||
func (a *AnimalsCurd) MatchAll(query string, num int) (tmp []model.Animal) {
|
||||
// STAGE 1. ES 查询
|
||||
animalsFromES, err := model_es.CreateAnimalESFactory(nil).QueryDocumentsMatchAll(query, num)
|
||||
if err != nil {
|
||||
fmt.Println("ES Query error:", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
var ids []int64
|
||||
for _, animal := range animalsFromES {
|
||||
ids = append(ids, animal.Id)
|
||||
}
|
||||
|
||||
// STAGE 2. MySQL 补充信息
|
||||
animalsFromSQL := model.CreateAnimalFactory("").ShowByIDs(ids, "id", "avatar")
|
||||
|
||||
// 3. 合并信息
|
||||
for _, animalFromES := range animalsFromES {
|
||||
for _, animal := range animalsFromSQL {
|
||||
if animal.Id == animalFromES.Id {
|
||||
animal.NickNamesList = animalFromES.NickNames
|
||||
animal.NickNamesHighlight = animalFromES.NickNamesHighlight
|
||||
animal.Description = animalFromES.Description
|
||||
animal.Name = animalFromES.Name
|
||||
tmp = append(tmp, animal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package curd
|
||||
|
||||
import (
|
||||
"catface/app/model"
|
||||
"catface/app/model_es"
|
||||
"catface/app/utils/query_handler"
|
||||
"strconv"
|
||||
)
|
||||
@ -64,3 +65,45 @@ func (e *EncounterCurd) Detail(id string) *model.EncounterDetail {
|
||||
Animals: animals,
|
||||
}
|
||||
}
|
||||
|
||||
func (e *EncounterCurd) MatchAll(query string, num int) (tmp []model.Encounter) {
|
||||
// 1. encounter ES
|
||||
encountersFromES, err := model_es.CreateEncounterESFactory(nil).QueryDocumentsMatchAll(query, num)
|
||||
if err != nil || len(encountersFromES) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var ids []int64
|
||||
for _, encounter := range encountersFromES {
|
||||
ids = append(ids, encounter.Id)
|
||||
}
|
||||
|
||||
// 2. encounter SQL
|
||||
encountersFromSQL := model.CreateEncounterFactory("").ShowByIDs(ids, "id", "avatar", "user_id")
|
||||
|
||||
// 3. users
|
||||
ids = nil
|
||||
for _, encounter := range encountersFromSQL {
|
||||
ids = append(ids, encounter.UsersModelId)
|
||||
}
|
||||
users := model.CreateUserFactory("").ShowByIDs(ids, "user_avatar", "user_name", "id")
|
||||
|
||||
// end. Merge
|
||||
for _, enencountersFromES := range encountersFromES {
|
||||
for _, encounter := range encountersFromSQL {
|
||||
for _, user := range users {
|
||||
if encounter.Id == enencountersFromES.Id && encounter.UsersModelId == user.Id {
|
||||
encounter.TagsList = enencountersFromES.Tags
|
||||
encounter.TagsHighlight = enencountersFromES.TagsHighlight
|
||||
encounter.Title = enencountersFromES.Title
|
||||
encounter.Content = enencountersFromES.Content
|
||||
|
||||
encounter.UsersModel = &user
|
||||
tmp = append(tmp, encounter)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
@ -109,7 +109,7 @@ func ShouldBindFormMapToModel(m map[string]interface{}, modelStruct interface{})
|
||||
|
||||
func fieldSetValueByMap(m map[string]interface{}, valueOf reflect.Value, typeOf reflect.Type, colIndex int) {
|
||||
relaKey := typeOf.Field(colIndex).Tag.Get("json")
|
||||
if relaKey != "-" {
|
||||
if relaKey != "-" && m[relaKey] != nil {
|
||||
switch typeOf.Field(colIndex).Type.Kind() {
|
||||
case reflect.Int, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
valueOf.Field(colIndex).SetInt(int64(m[relaKey].(float64)))
|
||||
|
@ -1,9 +1,19 @@
|
||||
package model_handler
|
||||
|
||||
import (
|
||||
"catface/app/global/variable"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/elastic/go-elasticsearch/v8/esapi"
|
||||
)
|
||||
|
||||
/**
|
||||
* @description: 用于处理 ES-highlight 模块分析出来的 []String.
|
||||
* @param {[]interface{}} strs
|
||||
* @return {*}
|
||||
* @return {*} 将 []String 还原为 String,这里就是简单的拼接了一下。
|
||||
*/
|
||||
func TransStringSliceToString(strs []interface{}) string {
|
||||
var result string
|
||||
@ -14,3 +24,71 @@ func TransStringSliceToString(strs []interface{}) string {
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// func concatKeywordsToSlice(highlightKeyword string, oriKeywords []string) []string {
|
||||
// return append(oriKeywords, highlightKeyword)
|
||||
// }
|
||||
|
||||
func MergeSouceWithHighlight(hit map[string]interface{}) map[string]interface{} {
|
||||
// 1. Get data
|
||||
source := hit["_source"].(map[string]interface{})
|
||||
highlight := hit["highlight"].(map[string]interface{})
|
||||
|
||||
// 2. Merge data
|
||||
for k, v := range highlight {
|
||||
if _, ok := source[k]; ok {
|
||||
switch source[k].(type) {
|
||||
case string:
|
||||
source[k] = TransStringSliceToString(v.([]interface{}))
|
||||
case []interface{}:
|
||||
source[k+"_highlight"] = v // TODO 过滤,交给前端?
|
||||
}
|
||||
}
|
||||
}
|
||||
return source
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 对 ES 发送的请求,返回结果。
|
||||
* @param {string} body
|
||||
* @param {string} index
|
||||
* @return {*}
|
||||
*/
|
||||
func SearchRequest(body string, index string) ([]interface{}, error) {
|
||||
ctx := context.Background()
|
||||
|
||||
req := esapi.SearchRequest{
|
||||
Index: []string{index},
|
||||
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")
|
||||
}
|
||||
return hits, nil
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user