diff --git a/go.mod b/go.mod index 862c6ebd..fd58d3af 100644 --- a/go.mod +++ b/go.mod @@ -74,7 +74,7 @@ require ( go.opentelemetry.io/otel/trace v1.14.0 // indirect go.opentelemetry.io/proto/otlp v0.19.0 // indirect go.uber.org/automaxprocs v1.5.2 // indirect - golang.org/x/net v0.12.0 // indirect + golang.org/x/net v0.12.0 golang.org/x/sys v0.10.0 // indirect golang.org/x/text v0.11.0 google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc // indirect diff --git a/model/gmodel/fs_product_logic.go b/model/gmodel/fs_product_logic.go index 16081b58..009181db 100755 --- a/model/gmodel/fs_product_logic.go +++ b/model/gmodel/fs_product_logic.go @@ -73,7 +73,7 @@ func (p *FsProductModel) GetRandomProductList(ctx context.Context, limit int) (r return resp, err } -func (p *FsProductModel) FindAllOnlyByIds(ctx context.Context, ids []int64) (resp []*FsProduct, err error) { +func (p *FsProductModel) FindAllOnlyByIds(ctx context.Context, ids []int64) (resp []FsProduct, err error) { err = p.db.WithContext(ctx).Model(&FsProduct{}).Where("`id` IN (?)", ids).Find(&resp).Error return resp, err } diff --git a/server/product/internal/logic/gettagproductlistlogic.go b/server/product/internal/logic/gettagproductlistlogic.go index fd89b6eb..3043ec68 100644 --- a/server/product/internal/logic/gettagproductlistlogic.go +++ b/server/product/internal/logic/gettagproductlistlogic.go @@ -2,6 +2,7 @@ package logic import ( "errors" + "fmt" "fusenapi/model/gmodel" "fusenapi/utils/auth" "fusenapi/utils/basic" @@ -74,107 +75,193 @@ func (l *GetTagProductListLogic) GetTagProductList(req *types.GetTagProductListR for _, v := range tagList { typeIds = append(typeIds, v.Id) } - //查询符合的产品列表 - pIsDel := int64(0) - pStatus := int64(1) - pIsShelf := int64(1) - pReq := gmodel.GetProductListByParamsReq{ - Type: typeIds, - IsDel: &pIsDel, - IsShelf: &pIsShelf, - Status: &pStatus, - OrderBy: "`sort` DESC", - } - //获取产品列表 - productList, err := l.svcCtx.AllModels.FsProduct.GetProductListByParams(l.ctx, pReq) - if err != nil { - logx.Error(err) - return resp.SetStatusWithMessage(basic.CodeServiceErr, "failed to get product list") - } - //提取产品ids - productIds := make([]int64, 0, len(productList)) - for _, v := range productList { - productIds = append(productIds, v.Id) - } - productPriceList, err := l.svcCtx.AllModels.FsProductPrice.GetSimplePriceListByProductIds(l.ctx, productIds) - if err != nil { - logx.Error(err) - return resp.SetStatusWithMessage(basic.CodeServiceErr, "failed to get product min price list") - } - //存储产品最小价格 - mapProductMinPrice := make(map[int64]int64) - for _, v := range productPriceList { - priceStrSlic := strings.Split(v.Price, ",") - priceSlice, err := format.StrSlicToIntSlice(priceStrSlic) + var ( + productList []gmodel.FsProduct //产品列表(select 字段需要看查询的地方) + recommendProductList []gmodel.FsProduct //tag推荐产品列表(select 字段需要看查询的地方) + mapProduct = make(map[int64]int) //产品map + productPriceList []gmodel.GetPriceListByProductIdsRsp //产品价格列表(select 字段需要看查询的地方) + mapProductMinPrice = make(map[int64]int64) //产品最小价格map + productTemplatesV2 []gmodel.FsProductTemplateV2 //产品模板列表(select 字段需要看查询的地方) + productSizeCountList []gmodel.CountProductSizeByStatusRsp //产品尺寸数量列表(select 字段需要看查询的地方) + mapProductSizeCount = make(map[int64]int64) //产品尺寸数量map + mapProductTemplate = make(map[int64]struct{}) //产品模板map + ) + //携带推荐产品 + if req.WithRecommendProduct{ + //提取tag推荐产品 + allTagRecommendProductIds := make([]int64, 0, len(tagList)*5) + for _, v := range tagList { + if v.RecommendProduct == nil || *v.RecommendProduct == "" { + continue + } + sl, err := format.StrSlicToInt64Slice(strings.Split(*v.RecommendProductSort, ",")) + if err != nil { + logx.Error(err) + return resp.SetStatusWithMessage(basic.CodeServiceErr, fmt.Sprintf("failed to parse recommend product ids,id=%d", v.Id)) + } + allTagRecommendProductIds = append(allTagRecommendProductIds, sl...) + } + //获取推荐的产品列表 + recommendProductList, err = l.svcCtx.AllModels.FsProduct.FindAllOnlyByIds(l.ctx, allTagRecommendProductIds) if err != nil { logx.Error(err) - return resp.SetStatusWithMessage(basic.CodeServiceErr, err.Error()) + return resp.SetStatusWithMessage(basic.CodeDbSqlErr, "failed to get tag recommend products") } - if len(priceSlice) == 0 { - continue + } + //携带产品 + if req.WithProduct { + //查询符合的产品列表 + pIsDel := int64(0) + pStatus := int64(1) + pIsShelf := int64(1) + pReq := gmodel.GetProductListByParamsReq{ + Type: typeIds, + IsDel: &pIsDel, + IsShelf: &pIsShelf, + Status: &pStatus, + OrderBy: "`sort` DESC", + } + //获取产品列表 + productList, err = l.svcCtx.AllModels.FsProduct.GetProductListByParams(l.ctx, pReq) + if err != nil { + logx.Error(err) + return resp.SetStatusWithMessage(basic.CodeServiceErr, "failed to get product list") } - sort.Ints(priceSlice) - mapProductMinPrice[v.ProductId] = int64(priceSlice[0]) } - //获取模板(只是获取产品product_id) - productTemplatesV2, err := l.svcCtx.AllModels.FsProductTemplateV2.FindAllByProductIds(l.ctx, productIds, "product_id") - if err != nil { - logx.Error(err) - return resp.SetStatusWithMessage(basic.CodeServiceErr, "get product template_v2 err") - } - mapProductTemplate := make(map[int64]struct{}) - for _, v := range productTemplatesV2 { - mapProductTemplate[*v.ProductId] = struct{}{} - } - //获取产品尺寸数量 - productSizeCountList, err := l.svcCtx.AllModels.FsProductSize.GetGroupProductSizeByStatus(l.ctx, productIds, 1) - if err != nil { - logx.Error(err) - return resp.SetStatusWithMessage(basic.CodeServiceErr, "get product size count err") - } - mapProductSizeCount := make(map[int64]int64) - for _, v := range productSizeCountList { - mapProductSizeCount[v.ProductId] = v.Num + //任意一个不为空 + if len(productList) != 0 || len(recommendProductList) != 0{ + //提取产品ids + productIds := make([]int64, 0, len(productList)) + for k, v := range productList { + productIds = append(productIds, v.Id) + mapProduct[v.Id] = k + } + //合并产品列表 + for _, v := range recommendProductList { + //存在则不并入 + if _, ok := mapProduct[v.Id]; ok { + continue + } + productList = append(productList, v) + mapProduct[v.Id] = len(productList) - 1 + } + //获取产品价格列表 + productPriceList, err = l.svcCtx.AllModels.FsProductPrice.GetSimplePriceListByProductIds(l.ctx, productIds) + if err != nil { + logx.Error(err) + return resp.SetStatusWithMessage(basic.CodeServiceErr, "failed to get product min price list") + } + //存储产品最小价格 + for _, v := range productPriceList { + priceStrSlic := strings.Split(v.Price, ",") + priceSlice, err := format.StrSlicToIntSlice(priceStrSlic) + if err != nil { + logx.Error(err) + return resp.SetStatusWithMessage(basic.CodeServiceErr, err.Error()) + } + if len(priceSlice) == 0 { + continue + } + sort.Ints(priceSlice) + mapProductMinPrice[v.ProductId] = int64(priceSlice[0]) + } + //获取模板(只是获取产品product_id) + productTemplatesV2, err = l.svcCtx.AllModels.FsProductTemplateV2.FindAllByProductIds(l.ctx, productIds, "product_id") + if err != nil { + logx.Error(err) + return resp.SetStatusWithMessage(basic.CodeServiceErr, "get product template_v2 err") + } + for _, v := range productTemplatesV2 { + mapProductTemplate[*v.ProductId] = struct{}{} + } + //获取产品尺寸数量 + productSizeCountList, err = l.svcCtx.AllModels.FsProductSize.GetGroupProductSizeByStatus(l.ctx, productIds, 1) + if err != nil { + logx.Error(err) + return resp.SetStatusWithMessage(basic.CodeServiceErr, "get product size count err") + } + for _, v := range productSizeCountList { + mapProductSizeCount[v.ProductId] = v.Num + } } + //map tag菜单 mapTagLevel := make(map[string]*types.TagItem) - minLevel := int64(0) //记录最小等级数字 for _, tagInfo := range tagList { - if minLevel == 0 && *tagInfo.Level > 0 { - minLevel = *tagInfo.Level - } - if minLevel > *tagInfo.Level { - minLevel = *tagInfo.Level - } - //获取分类产品列表 - productListRsp := l.getTagProducts(getTagProductsReq{ - TagId: tagInfo.Id, - ProductList: productList, - MapProductMinPrice: mapProductMinPrice, - MapProductTemplate: mapProductTemplate, - MapProductSizeCount: mapProductSizeCount, - Size: req.Size, - User: user, - }) - //加入分类 tagTem := types.TagItem{ - TagProductList: productListRsp, - TypeName: *tagInfo.Title, - TypeId: tagInfo.Id, - Level: *tagInfo.Level, - LevelPrefix: *tagInfo.LevelPrefix, - Icon: *tagInfo.Icon, - Sort: *tagInfo.Sort, - Description: *tagInfo.Description, - ChildTagList: make([]*types.TagItem, 0, 100), + TagProductList: nil, + TagRecommendProductList: nil, + TypeName: *tagInfo.Title, + TypeId: tagInfo.Id, + Level: *tagInfo.Level, + LevelPrefix: *tagInfo.LevelPrefix, + Icon: *tagInfo.Icon, + Sort: *tagInfo.Sort, + Description: *tagInfo.Description, + ChildTagList: make([]*types.TagItem, 0, 100), } - //当前tag保存入map + //携带产品 + if req.WithProduct { + //获取分类产品列表 + productListRsp := l.getTagProducts(getTagProductsReq{ + TagId: tagInfo.Id, + ProductList: productList, + MapProductMinPrice: mapProductMinPrice, + MapProductTemplate: mapProductTemplate, + MapProductSizeCount: mapProductSizeCount, + Size: req.Size, + User: user, + }) + //赋值 + tagTem.TagProductList = productListRsp + } + //获取推荐产品列表 + if req.WithRecommendProduct && tagInfo.RecommendProduct != nil && *tagInfo.RecommendProduct != "" { + //上面解析过,这里就无需判断错误 + recommendProductIds, _ := format.StrSlicToInt64Slice(strings.Split(*tagInfo.RecommendProduct, ",")) + //推荐产品的排序 + recommendProductIdsSort, err := format.StrSlicToInt64Slice(strings.Split(*tagInfo.RecommendProductSort, ",")) + if err != nil{ + logx.Error(err) + return nil + } + if len(recommendProductIds) != len(recommendProductIdsSort){ + return resp.SetStatusWithMessage(basic.CodeServiceErr,fmt.Sprintf("length of recommend product id is neq length of recommend sort,id= %d",tagInfo.Id)) + } + recommendProductListRsp := l.getTagRecommendProducts(getTagRecommendProductsReq{ + TagInfo: tagInfo, + ProductList: productList, + MapProduct: mapProduct, + MapProductMinPrice: mapProductMinPrice, + MapProductTemplate: mapProductTemplate, + MapProductSizeCount: mapProductSizeCount, + RecommendProductIds: recommendProductIds, + RecommendProductIdsSort: recommendProductIdsSort, + Size: req.Size, + User: user, + }) + //赋值 + tagTem.TagRecommendProductList = recommendProductListRsp + } + //加入分类 mapTagLevel[*tagInfo.LevelPrefix] = &tagTem } - //组装等级从属关系 + return resp.SetStatusWithMessage(basic.CodeOK, "success", types.GetTagProductListRsp{ + TotalCategory: len(mapTagLevel), + TagList: l.organizationLevelRelation(mapTagLevel),//组装等级从属关系 + }) +} +//排序推荐产品结构体 +type sortRecommendProduct struct { + ProductId int64 + Sort int64 +} +//组织等级从属关系 +func (l *GetTagProductListLogic)organizationLevelRelation(mapTagLevel map[string]*types.TagItem)[]types.TagItem{ + mapTop := make(map[string]struct{}) for prefix, tagItem := range mapTagLevel { - prefix = strings.Trim(prefix, " ") //最上级没有父级 if !strings.Contains(prefix, "/") { + mapTop[prefix] = struct{}{} continue } prefixSlice := strings.Split(prefix, "/") @@ -183,30 +270,98 @@ func (l *GetTagProductListLogic) GetTagProductList(req *types.GetTagProductListR if parent, ok := mapTagLevel[parentPrefix]; ok { parent.ChildTagList = append(parent.ChildTagList, tagItem) //排序 - sort.Slice(parent.ChildTagList, func(i, j int) bool { + sort.SliceStable(parent.ChildTagList, func(i, j int) bool { return parent.ChildTagList[i].Sort < parent.ChildTagList[j].Sort }) mapTagLevel[parentPrefix] = parent } } //最终值提取最高级别那一层出来 - tagListRsp := make([]types.TagItem, 0, len(mapTagLevel)) - for _, v := range mapTagLevel { - if v.Level != minLevel { - continue - } - tagListRsp = append(tagListRsp, *v) + rspList := make([]types.TagItem, 0, len(mapTagLevel)) + for prefix, _ := range mapTop { + rspList = append(rspList,*mapTagLevel[prefix]) } //排序 - sort.Slice(tagListRsp, func(i, j int) bool { - return tagListRsp[i].Sort < tagListRsp[j].Sort - }) - return resp.SetStatusWithMessage(basic.CodeOK, "success", types.GetTagProductListRsp{ - TotalCategory: len(mapTagLevel), - TagList: tagListRsp, + sort.SliceStable(rspList, func(i, j int) bool { + return rspList[i].Sort < rspList[j].Sort }) + return rspList +} +//获取tag推荐产品列表 +type getTagRecommendProductsReq struct { + TagInfo gmodel.FsTags + ProductList []gmodel.FsProduct + MapProduct map[int64]int + MapProductMinPrice map[int64]int64 + MapProductTemplate map[int64]struct{} + MapProductSizeCount map[int64]int64 + RecommendProductIds []int64 + RecommendProductIdsSort []int64 + Size uint32 + User gmodel.FsUser } +func (l *GetTagProductListLogic)getTagRecommendProducts(req getTagRecommendProductsReq)(productListRsp []types.TagProduct){ + //排序 + sortList := make([]sortRecommendProduct,0,len(req.RecommendProductIds)) + for sortIndex,pid := range req.RecommendProductIds{ + sortList = append(sortList,sortRecommendProduct{ + ProductId: pid, + Sort: req.RecommendProductIdsSort[sortIndex], + }) + } + sort.SliceStable(sortList, func(i, j int) bool { + return sortList[i].Sort < sortList[j].Sort + }) + productListRsp = make([]types.TagProduct,0,len(sortList)) + for _,sortVal := range sortList{ + productIndex,ok := req.MapProduct[sortVal.ProductId] + if !ok{ + continue + } + productInfo := req.ProductList[productIndex] + minPrice, ok := req.MapProductMinPrice[productInfo.Id] + _, tmpOk := req.MapProductTemplate[productInfo.Id] + //无最小价格则不显示 || 没有模板也不显示 + if !ok || !tmpOk { + continue + } + sizeNum := int64(0) + if mapSizeNum, ok := req.MapProductSizeCount[productInfo.Id]; ok { + sizeNum = mapSizeNum + } + item := types.TagProduct{ + ProductId: productInfo.Id, + Sn: *productInfo.Sn, + Title: *productInfo.Title, + Intro: *productInfo.Intro, + IsEnv: *productInfo.IsProtection, + IsMicro: *productInfo.IsMicrowave, + SizeNum: uint32(sizeNum), + MiniPrice: minPrice, + } + //千人千面处理 + r := image.ThousandFaceImageFormatReq{ + Size: int(req.Size), + IsThousandFace: 0, + Cover: *productInfo.Cover, + CoverImg: *productInfo.CoverImg, + CoverDefault: *productInfo.CoverImg, + ProductId: productInfo.Id, + UserId: req.User.Id, + } + if req.User.Id != 0 { + r.IsThousandFace = int(*req.User.IsThousandFace) + } + image.ThousandFaceImageFormat(&r) + item.Cover = r.Cover + item.CoverImg = r.CoverImg + item.CoverDefault = r.CoverDefault + //加入切片 + productListRsp = append(productListRsp,item) + } + return productListRsp +} // 获取对应tag的产品列表 type getTagProductsReq struct { TagId int64 diff --git a/server/product/internal/types/types.go b/server/product/internal/types/types.go index 926bc0e5..5f5e3768 100644 --- a/server/product/internal/types/types.go +++ b/server/product/internal/types/types.go @@ -245,8 +245,10 @@ type GetRecommandProductListRsp struct { } type GetTagProductListReq struct { - Cid int64 `form:"cid,optional"` //分类id - Size uint32 `form:"size,optional"` //尺寸 + Cid int64 `form:"cid,optional"` //分类id + Size uint32 `form:"size,optional"` //尺寸 + WithProduct bool `form:"with_product,optional"` //是否携带分类下的产品 + WithRecommendProduct bool `form:"with_recommend_product"` //是否携带分类推荐产品 } type GetTagProductListRsp struct { @@ -255,15 +257,16 @@ type GetTagProductListRsp struct { } type TagItem struct { - TypeName string `json:"type_name"` - TypeId int64 `json:"type_id"` - Description string `json:"description"` - Level int64 `json:"level"` - LevelPrefix string `json:"level_prefix"` - Icon string `json:"icon"` - Sort int64 `json:"sort"` - TagProductList []TagProduct `json:"tag_product_list"` - ChildTagList []*TagItem `json:"child_tag_list"` + TypeName string `json:"type_name"` + TypeId int64 `json:"type_id"` + Description string `json:"description"` + Level int64 `json:"level"` + LevelPrefix string `json:"level_prefix"` + Icon string `json:"icon"` + Sort int64 `json:"sort"` + TagProductList []TagProduct `json:"tag_product_list"` //分类下的产品 + TagRecommendProductList []TagProduct `json:"tag_recommend_product_list"` //分类推荐产品 + ChildTagList []*TagItem `json:"child_tag_list"` } type TagProduct struct { diff --git a/server_api/product.api b/server_api/product.api index 96b2b5d7..b1e80c00 100644 --- a/server_api/product.api +++ b/server_api/product.api @@ -291,23 +291,26 @@ type GetRecommandProductListRsp { } //获取分类产品列表 type GetTagProductListReq { - Cid int64 `form:"cid,optional"` //分类id - Size uint32 `form:"size,optional"` //尺寸 + Cid int64 `form:"cid,optional"` //分类id + Size uint32 `form:"size,optional"` //尺寸 + WithProduct bool `form:"with_product,optional"` //是否携带分类下的产品 + WithRecommendProduct bool `form:"with_recommend_product"` //是否携带分类推荐产品 } type GetTagProductListRsp { TotalCategory int `json:"total_category"` TagList []TagItem `json:"tag_list"` } type TagItem { - TypeName string `json:"type_name"` - TypeId int64 `json:"type_id"` - Description string `json:"description"` - Level int64 `json:"level"` - LevelPrefix string `json:"level_prefix"` - Icon string `json:"icon"` - Sort int64 `json:"sort"` - TagProductList []TagProduct `json:"tag_product_list"` - ChildTagList []*TagItem `json:"child_tag_list"` + TypeName string `json:"type_name"` + TypeId int64 `json:"type_id"` + Description string `json:"description"` + Level int64 `json:"level"` + LevelPrefix string `json:"level_prefix"` + Icon string `json:"icon"` + Sort int64 `json:"sort"` + TagProductList []TagProduct `json:"tag_product_list"` //分类下的产品 + TagRecommendProductList []TagProduct `json:"tag_recommend_product_list"` //分类推荐产品 + ChildTagList []*TagItem `json:"child_tag_list"` } type TagProduct { ProductId int64 `json:"product_id"`