diff --git a/app/global/variable/variable.go b/app/global/variable/variable.go index c963c74..355b1cc 100644 --- a/app/global/variable/variable.go +++ b/app/global/variable/variable.go @@ -64,3 +64,20 @@ func init() { log.Fatal(my_errors.ErrorsBasePath) } } + +func init() { + // 1. 初始化程序根目录 + if curPath, err := os.Getwd(); err == nil { + // 路径进行处理,兼容单元测试程序启动时的奇怪路径 + if len(os.Args) > 1 && strings.HasPrefix(os.Args[1], "-test") { + // 替换 \ 为 /,然后移除 /test 及其后的内容 + curPath = strings.ReplaceAll(curPath, "\\", "/") + parts := strings.Split(curPath, "/test") + BasePath = parts[0] + } else { + BasePath = curPath + } + } else { + log.Fatal(my_errors.ErrorsBasePath) + } +} diff --git a/app/http/controller/web/encounter_controller.go b/app/http/controller/web/encounter_controller.go index 45bbb6f..72111c3 100644 --- a/app/http/controller/web/encounter_controller.go +++ b/app/http/controller/web/encounter_controller.go @@ -6,6 +6,7 @@ import ( "catface/app/global/variable" "catface/app/http/validator/core/data_transfer" "catface/app/model" + "catface/app/model_es" "catface/app/service/encounter/curd" "catface/app/service/upload_file" "catface/app/utils/response" @@ -67,19 +68,21 @@ func (e *Encounters) Create(context *gin.Context) { // STAGE -3: Real Insert - 1: ENC animals_id := data_transfer.GetFloat64Slice(context, "animals_id") // 由于是 Slice 就交给 EAlink 内部遍历时处理。 // Real Insert - 2: EA LINK - if encounter_id, ok := model.CreateEncounterFactory("").InsertDate(context); ok && encounter_id > 0 { - // 2: EA Links - if !model.CreateEncounterAnimalLinkFactory("").Insert(int64(encounter_id), animals_id) { - response.Fail(context, errcode.ErrEaLinkInstert, errcode.ErrMsg[errcode.ErrEaLinkInstert], errcode.ErrMsgForUser[errcode.ErrEaLinkInstert]) - return - } + if encounter, ok := model.CreateEncounterFactory("").InsertDate(context); ok { + // 2: EA Links; // TIP 感觉直接使用 go 会直接且清晰。 + go model.CreateEncounterAnimalLinkFactory("").Insert(encounter.Id, animals_id) + // if !model.CreateEncounterAnimalLinkFactory("").Insert(int64(encounter_id), animals_id) { + // response.Fail(context, errcode.ErrEaLinkInstert, errcode.ErrMsg[errcode.ErrEaLinkInstert], errcode.ErrMsgForUser[errcode.ErrEaLinkInstert]) + // return + // } + // 3. ES speed if level := int(context.GetFloat64(consts.ValidatorPrefix + "level")); level > 1 { - // TODO + go model_es.CreateEncounterESFactory(&encounter).InsertDocument() } response.Success(context, consts.CurdStatusOkMsg, gin.H{ - "encounter_id": encounter_id, + "encounter_id": encounter.Id, }) } else { response.Fail(context, consts.CurdCreatFailCode, consts.CurdCreatFailMsg+", 新增错误", "") diff --git a/app/model/encounter.go b/app/model/encounter.go index 9e4303d..e007da1 100644 --- a/app/model/encounter.go +++ b/app/model/encounter.go @@ -44,18 +44,22 @@ func (e *Encounter) TableName() string { return "encounters" } -func (e *Encounter) InsertDate(c *gin.Context) (int64, bool) { - var tmp Encounter +/** + * @description: + * @param {*gin.Context} c + * @return {*} 返回创建的绑定对象,之后 model_es 的利用。 + */ +func (e *Encounter) InsertDate(c *gin.Context) (tmp Encounter, ok bool) { if err := data_bind.ShouldBindFormDataToModel(c, &tmp); err == nil { if res := e.Create(&tmp); res.Error == nil { - return tmp.Id, true + return tmp, true } else { variable.ZapLog.Error("Encounter 数据新增出错", zap.Error(res.Error)) } } else { variable.ZapLog.Error("Encounter 数据绑定出错", zap.Error(err)) } - return 0, false + return tmp, false } func formatEncounterList(rows *gorm.DB) (temp []EncounterList, err error) { diff --git a/app/model_es/encounter.go b/app/model_es/encounter.go new file mode 100644 index 0000000..bc877a2 --- /dev/null +++ b/app/model_es/encounter.go @@ -0,0 +1,220 @@ +package model_es + +import ( + "bytes" + "catface/app/global/variable" + "catface/app/model" + "context" + "encoding/json" + "fmt" + "strings" + + "github.com/elastic/go-elasticsearch/v8" + "github.com/elastic/go-elasticsearch/v8/esapi" +) + +func CreateEncounterESFactory(encounter *model.Encounter) *Encounter { + // 我把数值绑定到了工厂创建当中。 + return &Encounter{ + Id: encounter.Id, + Title: encounter.Title, + Content: encounter.Content, + Tags: encounter.TagsSlice, // TODO 暂时没有对此字段的查询。 + } +} + +// INFO 存储能够作为索引存在的数据。 +type Encounter struct { + Id int64 `json:"id"` + Title string `json:"title"` + Content string `json:"content"` + Tags []string `json:"tags"` +} + +func (e *Encounter) IndexName() string { + return "catface_encounters" +} + +func (e *Encounter) InsertDocument() error { + ctx := context.Background() + + // 将结构体转换为 JSON 字符串 + data, err := json.Marshal(e) + if err != nil { + return err + } + + // 创建请求 + req := esapi.IndexRequest{ + Index: e.IndexName(), + DocumentID: fmt.Sprintf("%d", e.Id), + Body: bytes.NewReader(data), + Refresh: "true", + } + + // 发送请求 + res, err := req.Do(ctx, variable.ElasticClient) + if err != nil { + return err + } + defer res.Body.Close() + + if res.IsError() { + var e map[string]interface{} + if err := json.NewDecoder(res.Body).Decode(&e); err != nil { + return fmt.Errorf("error parsing the response body: %s", err) + } else { + return fmt.Errorf("[%s] %s: %s", + res.Status(), + e["error"].(map[string]interface{})["type"], + e["error"].(map[string]interface{})["reason"], + ) + } + } + + return nil +} + +func (e *Encounter) UpdateDocument(client *elasticsearch.Client, encounter *Encounter) error { + ctx := context.Background() + + // 将结构体转换为 JSON 字符串 + data, err := json.Marshal(map[string]interface{}{ + "doc": encounter, + }) + if err != nil { + return err + } + + // 创建请求 + req := esapi.UpdateRequest{ + Index: encounter.IndexName(), + DocumentID: fmt.Sprintf("%d", encounter.Id), + Body: bytes.NewReader(data), + Refresh: "true", + } + + // 发送请求 + res, err := req.Do(ctx, client) + if err != nil { + return err + } + defer res.Body.Close() + + if res.IsError() { + var e map[string]interface{} + if err := json.NewDecoder(res.Body).Decode(&e); err != nil { + return fmt.Errorf("error parsing the response body: %s", err) + } else { + return fmt.Errorf("[%s] %s: %s", + res.Status(), + e["error"].(map[string]interface{})["type"], + e["error"].(map[string]interface{})["reason"], + ) + } + } + + return nil +} + +/** + * @description: 粗略地包含各种关键词匹配, + * @param {*elasticsearch.Client} client + * @param {string} query + * @return {*} 对应 Encounter 的 id,然后交给 MySQL 来查询详细的信息? + */ +func (e *Encounter) QueryDocumentsMatchAll(client *elasticsearch.Client, query string) ([]int64, error) { + ctx := context.Background() + + // 创建查询请求 + req := esapi.SearchRequest{ // UPDATE 同时实现查询高亮? + Index: []string{e.IndexName()}, + Body: strings.NewReader(fmt.Sprintf(`{ + "_source": ["id"], + "query": { + "bool": { + "should": [ + { + "match": { + "title": "%s" + } + }, + { + "match": { + "content": "%s" + } + } + ] + } + } + }`, query, query)), + } + + // 发送请求 + res, err := req.Do(ctx, client) + 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") + } + + 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 + + // // 转换为 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 +} diff --git a/test/es/encounter_test.go b/test/es/encounter_test.go new file mode 100644 index 0000000..3eaac97 --- /dev/null +++ b/test/es/encounter_test.go @@ -0,0 +1,50 @@ +package test + +import ( + "catface/app/global/variable" + "catface/app/model" + "catface/app/model_es" + _ "catface/bootstrap" + "fmt" + "testing" +) + +func TestEncounterEs(t *testing.T) { + // 示例数据 + encounterOri := &model.Encounter{ + BaseModel: model.BaseModel{ + Id: 4, + }, + Title: "猪皮伤势轻,需静养猪皮伤势轻,需静养", + Content: "猪皮被带到医院检查了,拍片结果显示损伤不严重,静养即可自愈。建议这段时间不要折腾他,让老登好好休息。", + TagsSlice: []string{"猪皮", "脚伤", "骗保"}, + } + + encounter := model_es.CreateEncounterESFactory(encounterOri) + + // 插入文档 + // if err := encounter.InsertDocument(); err != nil { + // t.Fatalf("插入文档时出错: %s", err) + // } + go encounter.InsertDocument() + + // // 更新文档 + // encounter.Content = "更新: 猪皮被带到医院检查了,拍片结果显示损伤不严重,静养即可自愈。建议这段时间不要折腾他,让老登好好休息。" + // if err := encounter.UpdateDocument(variable.ElasticClient, encounter); err != nil { + // t.Fatalf("更新文档时出错: %s", err) + // } + + // 查询文档 + encounters, err := encounter.QueryDocumentsMatchAll(variable.ElasticClient, "猪皮") + if err != nil { + t.Fatalf("查询文档时出错: %s", err) + } + + // for _, e := range encounters { + // fmt.Printf("ID: %d, 标题: %s, 内容: %s, 标签: %v\n", e.Id, e.Title, e.Content, e.Tags) + // } + + for _, e := range encounters { + fmt.Printf("ID: %d\n", e) + } +}