主要是推介代码的规划
测试用例
func (this *TestGeneralTestRecommendSuite) Test007_twiceRedisQueryRecommend2Es() { //this.inst.Reindex() var q = generaldto.FindBeanObjectQueryRequest() q.ObjectType = "brand" q.ObjectId = 112233 var ret = this.inst.RedisQueryRecommends(q) golog.Info(ret) ret = this.inst.RedisQueryRecommends(q) golog.Info(ret) }
2025-01-13 22:50:18.915 [INFO] {
"code": 200,
"msg": "成功",
"data": {
"list": [
{
"object_type": "brand",
"object_id": 112233,
"created_at": "2025-01-13T22:50:17.394265+08:00",
"id": "brand|112233|988495986230165507",
"publishing_id": 988495986230165507,
"content_id": 988495986039422979,
"description": "",
"name": "AC测试文稿FFDD",
"shop_id": 917044700211642371,
"shop_name": "黄金时代CM",
"column_id": 952438963584630787,
"column_name": "产品管理",
"type": 100,
"recommend_type": 0,
"publishing_at": "2024-07-23T11:50:34.857646Z",
"publishing_at_int": 1721735434857646000,
"author": "",
"author_id": 0,
"is_top": false,
"thumbnail": "[]",
"read_count": 0,
"like_count": 0,
"collect_count": 0,
"share_count": 0,
"comment_count": 0
},
请求
package generaldto import ( "git.ichub.com/general/webcli120/goconfig/base/basedto" "github.com/gogf/gf/v2/util/gconv" "icd/errors" "icd/general-srv/handler/consts" "strings" "time" ) type ObjectQueryRequest struct { basedto.BaseEntity ObjectType string `json:"object_type"` //shop+spu, spu, categ, brand ObjectId int64 `json:"object_id"` // ObjectSpuRequest //spu shop-spu Brand ObjectBrandRequest `json:"brand"` //brand Categ ObjectCategRequest `json:"categ"` //categ } func NewObjectQueryRequest() *ObjectQueryRequest { return &ObjectQueryRequest{} } func (self *ObjectQueryRequest) IfSpu() bool { return self.ObjectType == consts.RECOMMEND_OBJECT_TYPE_SHOP_SPU || self.ObjectType == consts.RECOMMEND_OBJECT_TYPE_SPU } func (self *ObjectQueryRequest) IfBrand() bool { return self.ObjectType == consts.RECOMMEND_OBJECT_TYPE_BRAND } func (self *ObjectQueryRequest) IfCateg() bool { return self.ObjectType == consts.RECOMMEND_OBJECT_TYPE_CATEG } func (self *ObjectQueryRequest) spuBuildMap2Cms() map[string]string { fields := map[string]string{} fields["name:1"] = self.PartNumber fields["abstract:1"] = self.PartNumber fields["name:2"] = self.BrandName fields["abstract:2"] = self.CategName fields["name:3"] = self.CategName fields["abstract:3"] = self.BrandName if self.Description != "" { fields["name:4"] = self.Description fields["abstract:4"] = self.Description fields["description"] = self.Description } return fields } func (self *ObjectQueryRequest) BuildMap2Cms() map[string]string { fields := map[string]string{} //fields["name"] = "" //fields["abstract"] = "" fields["description"] = "" //return fields if self.IfSpu() { return self.spuBuildMap2Cms() } else if self.IfBrand() { return self.brandBuildMap2Cms() } else if self.IfCateg() { return self.categBuildMap2Cms() } return fields } func (self *ObjectQueryRequest) brandBuildMap2Cms() map[string]string { fields := map[string]string{} fields["name:1"] = self.Brand.Name fields["abstract:1"] = self.Brand.Name fields["name:2"] = self.Brand.FullName fields["abstract:2"] = self.Brand.FullName fields["name:3"] = self.Brand.Alias fields["abstract:3"] = self.Brand.Alias fields["name:4"] = self.Brand.Description fields["abstract:4"] = self.Brand.Description fields["description"] = self.Brand.Description return fields } func (self *ObjectQueryRequest) categBuildMap2Cms() map[string]string { fields := map[string]string{} fields["name:1"] = self.Categ.Name fields["abstract:1"] = self.Categ.Name fields["name:2"] = self.Categ.FullName fields["abstract:2"] = self.Categ.FullName fields["name:3"] = self.Categ.CategType fields["abstract:3"] = self.Categ.CategType fields["name:4"] = self.Categ.CategParent fields["abstract:4"] = self.Categ.CategParent fields["description"] = self.Categ.FullName return fields } func (self *ObjectQueryRequest) Check() error { if self.ObjectType == "" { return errors.New("ObjectType is empty!") } if self.ObjectId == 0 { return errors.New("ObjectId is zero!") } return nil } func (self *ObjectQueryRequest) ToRedisKey() string { var today = time.Now().Format(time.DateOnly) var keys = []any{consts.REDIS_KEY_GeneralRecommendCms, self.ObjectType, self.ObjectId, today} return strings.Join(gconv.SliceStr(keys), "|") } type ObjectSpuRequest struct { PartNumber string `json:"part_number"` //型号 BrandName string `json:"brand_name"` //厂牌 CategName string `json:"categ_name"` //类目 Description string `json:"description"` //描述 } type ObjectBrandRequest struct { Name string `json:"name"` //简称 FullName string `json:"full_name"` //全称 Description string `json:"description"` //描述 Alias string `json:"alias"` //别名 } type ObjectCategRequest struct { Name string `json:"name"` //简称 FullName string `json:"full_name"` //全称 CategParent string `json:"categ_parent"` //父类 CategType string `json:"categ_type"` //类型字段 } //根据厂牌的简称、全称、描述、别名字段,匹配公开的文稿,按计算的相似度由高到低返回推荐文稿(只返回100篇) //类目详情页 //根据类目的名称、简称、父类、类型字段,匹配公开的文稿,按计算的相似度由高到低返回推荐文稿(只返回100篇) 推荐代码
package generalrecommend import ( "context" "git.ichub.com/general/webcli120/goconfig/base/basedto" "git.ichub.com/general/webcli120/goconfig/base/jsonutils" "git.ichub.com/general/webcli120/goconfig/ichublog/golog" "git.ichub.com/general/webcli120/goweb/pagemodel" "git.ichub.com/general/webcli120/goweb/pageuimodel" "git.ichub.com/general/webcli120/goweb/webclient/eswebclient/webfacade" "github.com/gogf/gf/v2/util/gconv" "github.com/olivere/elastic/v7" "icd/basic/common" "icd/general-srv/handler" "icd/general-srv/handler/consts" "icd/general-srv/handler/general/generaldto" "icd/general-srv/handler/general/generales" proto "icd/general-srv/proto" //cmsProto "icd/proto/cms-proto" ) const RECOMMEND_RETURN_FIELD = "content_id" type GeneralRecommend struct { basedto.BaseEntitySingle *handler.Service RedisCmd } func NewGeneralRecommend() *GeneralRecommend { return &GeneralRecommend{ Service: handler.FindBeanService(), } } func (self *GeneralRecommend) RedisQueryRecommends(req *generaldto.ObjectQueryRequest) *pageuimodel.PageuiResult[*generales.GeneralRecommendEs] { if self.ExistRedisKey(req) { return self.queryOnlyPageui(req) } var err = self.SaveRecommend2Es(req) if err != nil { golog.Error("GeneralRecommend [RedisQueryRecommends]-[SaveRecommend2Es] err:", err) var ret = pageuimodel.FailedMsg[*generales.GeneralRecommendEs](err.Error()) return ret } else { self.SaveRedisKey(req) } return self.queryOnlyPageui(req) } func (self *GeneralRecommend) QueryRecommends(req *generaldto.ObjectQueryRequest) *pagemodel.PageResult[*generales.GeneralRecommendEs] { var err = self.SaveRecommend2Es(req) if err != nil { golog.Error("GeneralRecommend [QueryRecommends]-[SaveRecommend2Es] err:", err) return pagemodel.ResultFailPageResult[*generales.GeneralRecommendEs](err.Error()) } return self.QueryOnly(req) } func (self *GeneralRecommend) queryOnlyPageui(req *generaldto.ObjectQueryRequest) *pageuimodel.PageuiResult[*generales.GeneralRecommendEs] { var ret = self.QueryOnly(req) return pageuimodel.FromPageResult(ret) } func (self *GeneralRecommend) QueryOnly(req *generaldto.ObjectQueryRequest) *pagemodel.PageResult[*generales.GeneralRecommendEs] { var err = req.Check() if err != nil { golog.Error("GeneralRecommend [QueryOnly]", err) return pagemodel.ResultFailPageResult[*generales.GeneralRecommendEs](err.Error()) } var q = elastic.NewBoolQuery() q.Filter(elastic.NewTermQuery(consts.ES_FIELD_OBJECT_TYPE, req.ObjectType)) q.Filter(elastic.NewTermQuery(consts.ES_FIELD_OBJECT_ID, req.ObjectId)) var qq = webfacade.DefaultOf[*generales.GeneralRecommendEs](q) qq.SetPageSize(consts.RECOMMEND_RET_PAGESIZE) var ret = qq.GeneralQuery() if ret.IsFailed() { golog.Error("GeneralRecommend [QueryCmsList] err:", ret) } return ret } func (self *GeneralRecommend) BulkUpsert(list []*generales.GeneralRecommendEs) *basedto.IchubResult { if len(list) == 0 { golog.Info("list len=0") return basedto.ResultSuccessData("{}") } //check var qq = webfacade.Default[*generales.GeneralRecommendEs]() for _, item := range list { if err := item.Check(); err != nil { golog.Error("BulkUpsertCcheck", err) } else { item.ComputeFields() qq.Cmd.AddDocStru(item.Id, item) } } var ret = qq.Cmd.BulkUpsert() if ret.IsFailed() { golog.Error("GeneralRecommend [BulkUpsert] ret:", ret) } if m, ok := ret.Data.(map[string]any); ok { if m["errors"] != nil && m["errors"].(bool) { golog.Error(" [BulkUpsert] ret errors =", jsonutils.ToJsonStr(m["errors"])) ret.Code = 500 ret.Msg = jsonutils.ToJsonStr(m["errors"]) return ret } } return ret } func (self *GeneralRecommend) DeleteGeneralRecommendEs(req *generaldto.ObjectQueryRequest) *basedto.IchubResult { var qq = webfacade.Default[*generales.GeneralRecommendEs]() qq.Cmd.EsFilter().EsTerm("object_type", req.ObjectType) qq.Cmd.EsFilter().EsTerm("object_id", req.ObjectId) var ret = qq.Cmd.DeleteByQuery() if ret.IsFailed() { golog.Error("GeneralRecommend [DeleteGeneralRecommendEs] ret:", ret) } return ret } func (self *GeneralRecommend) QueryCmsList(ids []string) *pagemodel.PageResult[*generales.ContentListEs] { var queryIds = gconv.SliceInt(ids) var q = elastic.NewBoolQuery() q.Filter(elastic.NewTermsQuery(RECOMMEND_RETURN_FIELD, gconv.SliceAny(queryIds)...)) var qq = webfacade.DefaultOf[*generales.ContentListEs](q) qq.SetPageSize(len(ids)) var ret = qq.GeneralQuery() if ret.IsFailed() { golog.Error("GeneralRecommend [QueryCmsList] err:", ret) } return ret } func (self *GeneralRecommend) MakeRecommends(req *generaldto.ObjectQueryRequest, pageSize int32) ([]*generaldto.RecommendResult, error) { //先从服务域查询推荐稿 fields := req.BuildMap2Cms() recommendList, err := self.SimilarContentQueryV2(fields, pageSize*3) if err != nil { golog.Error("RecommendList err:", err) return nil, err } var ret = self.QueryCmsList(recommendList.Ids) if ret.IsFailed() { return nil, ret.Result2Error() } var data = []*generales.ContentListEs{} for _, content := range ret.Data { if self.Filter(content) { continue } data = append(data, content) } var list = []*generaldto.RecommendResult{} err = jsonutils.Decode2StruList(data, &list) return list, err } func (self *GeneralRecommend) Filter(content *generales.ContentListEs) bool { 与主内容相同,不作为推荐 //if similarContentId == contentId { // continue //} 已经删除的不再推荐 //if cms[similarContentId] > 0 { // continue //} //判断是否下架,下架不作为推荐 //content, err := s.CmsContentDAO.FindGroupNumberByIdEs(similarContentId) //if err != nil { // return nil, 0, err //} if content.State == 102 { //int32(enums.ContentStateEnumDown) { return true } //判断类型是否为答,答不作为推荐 if content.Type > 500 { //int32(enums.ContentTypeEnumAdvertisement) { return true } //排除异常数据 if content.PublishingId < 1 || content.PublishingAt.Unix() < 1 || content.ColumnName == "" { return true } return false } // select publishing_id ,content_id ,group_number ,* from release_cms_content where group_number=1 limit 4 func (self *GeneralRecommend) initCond(FieldCode, Symbol, Value string) *proto.Condition { c3 := new(proto.Condition) c3.FieldCode = FieldCode c3.Symbol = Symbol c3.Value = Value return c3 } func (self *GeneralRecommend) SimilarContentQueryV2(fields map[string]string, pageSize int32) (*proto.SimilarContentResult, error) { req := new(proto.SimilarContentRequestV2) req.IndexName = common.Env + "_cms_content" req.ReturnField = RECOMMEND_RETURN_FIELD req.Size = uint32(pageSize) var fieldRequests []*proto.FieldRequest for k, v := range fields { field := new(proto.FieldRequest) field.FieldCode = k field.Value = v fieldRequests = append(fieldRequests, field) } req.Fields = fieldRequests var conditions []*proto.Condition c := self.initCond("type", "<=", "500") //conditions = append(conditions, c) c1 := self.initCond("state", "=", "300") //c1 := new(proto.Condition) //c1.FieldCode = "state" //c1.Symbol = "=" //c1.Value = "300" c2 := self.initCond("is_off", "=", "false") //c2 := new(proto.Condition) //c2.FieldCode = "is_off" //c2.Symbol = "=" //c2.Value = "false" c3 := self.initCond("is_public", "=", "true") //c3 := new(proto.Condition) //c3.FieldCode = "is_public" //c3.Symbol = "=" //c3.Value = "true" c4 := self.initCond("source", "!=", "1") //c4 := new(proto.Condition) //c4.FieldCode = "source" //c4.Symbol = "!=" //c4.Value = "1" c5 := self.initCond("group_number", "=", "1") //c5 := new(proto.Condition) //c5.FieldCode = "group_number" //c5.Symbol = "=" //c5.Value = "1" conditions = append(conditions, c, c1, c2, c3, c4, c5) req.Cons = conditions var out = &proto.SimilarContentResult{} var err = self.Service.SimilarContentQueryV2(context.Background(), req, out) return out, err } func (self *GeneralRecommend) SaveRecommend2Es(req *generaldto.ObjectQueryRequest) error { if err := req.Check(); err != nil { golog.Error("SaveRecommend2Es", err) return err } var data, err = self.MakeRecommends(req, consts.RECOMMEND_RET_PAGESIZE) if err != nil { return err } var ret = self.DeleteGeneralRecommendEs(req) if ret.IsFailed() { golog.Error("ret:", ret) } var records = self.buildRecommend2Es(req, data) ret = self.BulkUpsert(records) if ret.IsFailed() { golog.Error("ret:", ret) } return ret.Result2Error() } func (self *GeneralRecommend) buildRecommend2Es(req *generaldto.ObjectQueryRequest, data []*generaldto.RecommendResult) []*generales.GeneralRecommendEs { var i = 0 var generalRecommendEs = []*generales.GeneralRecommendEs{} for _, item := range data { var recommendEs = generales.FindBeanGeneralRecommendEs() recommendEs.ObjectId = req.ObjectId recommendEs.ObjectType = req.ObjectType recommendEs.RecommendResult = *item recommendEs.ComputeFields() generalRecommendEs = append(generalRecommendEs, recommendEs) i = i + 1 if i >= consts.RECOMMEND_RET_PAGESIZE { break } } return generalRecommendEs } func (self *GeneralRecommend) Reindex() *basedto.IchubResult { self.DropEs() return self.CreateEs() } func (self *GeneralRecommend) CreateEs() *basedto.IchubResult { return generales.FindBeanGeneralRecommendEs().MetaCreateIndex() } func (self *GeneralRecommend) DropEs() *basedto.IchubResult { return generales.FindBeanGeneralRecommendEs().MetaDropIndex() }
// 相似文章检索 func SimilarContentQueryV2(ctx context.Context, req *proto.SimilarContentRequestV2, out *proto.SimilarContentResult) error { if req.IndexName == "" || len(req.Fields) == 0 { return errors.NewBadRequestError("索引和字段必传") } //add rmd 20250113 for _, f := range req.Fields { if strings.Contains(f.FieldCode, ":") { f.FieldCode = strings.Split(f.FieldCode, ":")[0] } } //add by rmd if req.ReturnField == "" { req.ReturnField = "id" } q := elastic.NewBoolQuery() for _, con := range req.Cons { switch con.Symbol { case "=": q.Must(elastic.NewTermQuery(con.FieldCode, con.Value)) case "!=": q.MustNot(elastic.NewTermQuery(con.FieldCode, con.Value)) case ">": q.Must(elastic.NewRangeQuery(con.FieldCode).Gt(con.Value)) case ">=": q.Must(elastic.NewRangeQuery(con.FieldCode).Gte(con.Value)) case "<": q.Must(elastic.NewRangeQuery(con.FieldCode).Lt(con.Value)) case "<=": q.Must(elastic.NewRangeQuery(con.FieldCode).Lte(con.Value)) } } //查索引id indexs, err := IndexQueryByCode([]string{req.IndexName}) if err != nil { logger.Errorf("[ES.IndexQueryByCode.search] ES查询doc失败,err:%s", err) return errors.NewInternalServerError("查询失败") } if len(indexs) == 0 { logger.Errorf("[ES.IndexQueryByCode.search] ES查询doc失败,err:%s", err) return errors.NewInternalServerError("【%s】索引不存在", req.IndexName) } //根据字段code查字段id var fieldCodes []string for _, f := range req.Fields { fieldCodes = append(fieldCodes, f.FieldCode) } fields, err := FieldQueryByCode(fieldCodes) if err != nil { logger.Errorf("[ES.FieldQueryByCode.search] ES查询doc失败,err:%s", err) return errors.NewInternalServerError("查询失败") } if len(fields) == 0 { logger.Errorf("[ES.FieldQueryByCode.search] ES查询doc失败,err:%s", err) return errors.NewInternalServerError("【%s】索引不存在", req.IndexName) } //查索引下的所有字段 indexFields, err := IndexFieldQueryByIndexId(&proto.IDRequest{ ID: utils.ParseInt(indexs[0].Id), }, "index") //通过字段编码,找出在该索引中的Boost、Analyzer配置 fn := func(fieldcode string) *proto.IndexField { field := &proto.IndexField{} for _, f := range fields { if f.Code == fieldcode { field.Id = f.Id } } for _, f := range indexFields { if f.FieldId == field.Id { field.Boost = f.Boost field.Analyzer = f.Analyzer } } return field } //查询索引-字段的关系表,取分析器和权重 for _, f := range req.Fields { child := elastic.NewMatchQuery(f.FieldCode, f.Value) field := fn(f.FieldCode) if field.Boost > 0 { child.Boost(float64(field.Boost)) } if field.Analyzer != "" { child.Analyzer(field.Analyzer) } q.Should(child) } //q := elastic.NewMoreLikeThisQuery().LikeItems( // elastic.NewMoreLikeThisQueryItem().Id(req.Id), //).Field(req.Fields...). // MinTermFreq(1).MaxQueryTerms(25).MinDocFreq(1).MinWordLength(1) //bq = bq.Must(q) //src, err := q.Source() //data, err := json.Marshal(src) //logger.Info("ss:", string(data)) c := es.GetElastic() defer c.Stop() searchResult, err := c.Search(). Index(req.IndexName). Query(q). Pretty(true). Size(int(req.Size)). Do(context.Background()) if err != nil { logger.Errorf("[ES.SimilarContentQuery.search] ES查询doc失败,err:%s", err) return errors.NewInternalServerError("查询失败") } if searchResult == nil { logger.Infof("[ES.SimilarContentQuery.search] ES查询doc返回为空: %v", searchResult) return errors.NewInternalServerError("ES查询doc返回为空") } var ids []string if searchResult.TotalHits() > 0 { for _, hit := range searchResult.Hits.Hits { m := make(map[string]string) err := jsoniter.Unmarshal(hit.Source, &m) if err != nil { logger.Infof("[ES.SimilarContentQueryV2.search] ES查询doc结果 json失败,err:%s", err) } else { ids = append(ids, m[req.ReturnField]) } } } out.Ids = ids return nil }标签:err,return,string,elastic,fields,self,req,相似性,go From: https://blog.csdn.net/leijmdas/article/details/145125812