diff --git a/model/gmodel/fs_guest_logic.go b/model/gmodel/fs_guest_logic.go index 1b88e95d..c939d26e 100755 --- a/model/gmodel/fs_guest_logic.go +++ b/model/gmodel/fs_guest_logic.go @@ -34,7 +34,7 @@ func (m *FsGuestModel) GenerateGuestID(ctx context.Context, AccessSecret uint64) } uinfo := &FsUserInfo{ - Module: FsString("module"), + Module: FsString("profile"), UserId: FsInt64(0), GuestId: &record.GuestId, Metadata: FsBytes("{}"), diff --git a/model/gmodel/fs_product_model3d_logic.go b/model/gmodel/fs_product_model3d_logic.go index 92a54a39..ced105c5 100755 --- a/model/gmodel/fs_product_model3d_logic.go +++ b/model/gmodel/fs_product_model3d_logic.go @@ -173,12 +173,7 @@ func (d *FsProductModel3dModel) GetAllByProductIdsTags(ctx context.Context, prod } // 获取每个产品最低价格 -func (d *FsProductModel3dModel) GetProductMinPrice(ctx context.Context, productIds []int64, mapProductMinPrice map[int64]int64) error { - //获取产品模型价格列表 - modelList, err := d.GetAllByProductIdsTags(ctx, productIds, []int{constants.TAG_MODEL, constants.TAG_PARTS}, "id,product_id,price,tag,part_id,step_price") - if err != nil { - return errors.New("failed to get model list") - } +func (d *FsProductModel3dModel) GetProductMinPrice(modelList []FsProductModel3d, mapProductMinPrice map[int64]int64) error { mapModelMinPrice := make(map[int64]int64) //每个模型/配件存储最小价格 for _, modelInfo := range modelList { @@ -189,7 +184,7 @@ func (d *FsProductModel3dModel) GetProductMinPrice(ctx context.Context, productI continue } var stepPrice StepPriceJsonStruct - if err = json.Unmarshal(*modelInfo.StepPrice, &stepPrice); err != nil { + if err := json.Unmarshal(*modelInfo.StepPrice, &stepPrice); err != nil { return errors.New(fmt.Sprintf("failed to parse model step price:%d", modelInfo.Id)) } lenRange := len(stepPrice.PriceRange) @@ -226,3 +221,11 @@ func (d *FsProductModel3dModel) GetProductMinPrice(ctx context.Context, productI } return nil } +func (d *FsProductModel3dModel) GetAllByProductIdTags(ctx context.Context, productId int64, tags []int64, fields ...string) (resp []FsProductModel3d, err error) { + db := d.db.WithContext(ctx).Model(&FsProductModel3d{}).Where("`product_id`= ? and `tag` in(?) and `status` = ?", productId, tags, 1) + if len(fields) != 0 { + db = db.Select(fields[0]) + } + err = db.Find(&resp).Error + return resp, err +} diff --git a/model/gmodel/fs_product_template_v2_logic.go b/model/gmodel/fs_product_template_v2_logic.go index 1462d7b9..3bacf94b 100755 --- a/model/gmodel/fs_product_template_v2_logic.go +++ b/model/gmodel/fs_product_template_v2_logic.go @@ -18,15 +18,16 @@ func (t *FsProductTemplateV2Model) FindAllByProductIds(ctx context.Context, prod err = db.Find(&resp).Error return resp, err } -func (t *FsProductTemplateV2Model) FindAllByIds(ctx context.Context, ids []int64) (resp []FsProductTemplateV2, err error) { +func (t *FsProductTemplateV2Model) FindAllByIds(ctx context.Context, ids []int64, fields ...string) (resp []FsProductTemplateV2, err error) { if len(ids) == 0 { return } - err = t.db.WithContext(ctx).Model(&FsProductTemplateV2{}).Where("`id` in (?) and `is_del` = ? and `status` = ?", ids, 0, 1).Find(&resp).Error - if err != nil { - return nil, err + db := t.db.WithContext(ctx).Model(&FsProductTemplateV2{}).Where("`id` in (?) and `is_del` = ? and `status` = ?", ids, 0, 1) + if len(fields) != 0 { + db = db.Select(fields[0]) } - return + err = db.Find(&resp).Error + return resp, err } func (t *FsProductTemplateV2Model) FindAllByIdsWithoutStatus(ctx context.Context, ids []int64, fields ...string) (resp []FsProductTemplateV2, err error) { if len(ids) == 0 { @@ -179,3 +180,11 @@ func (t *FsProductTemplateV2Model) FindAllByProductIdsTemplateTag(ctx context.Co err = db.Find(&resp).Error return resp, err } +func (t *FsProductTemplateV2Model) FindAllByFittingIds(ctx context.Context, fittingIds []int64, fields ...string) (resp []FsProductTemplateV2, err error) { + db := t.db.WithContext(ctx).Model(&FsProductTemplateV2{}).Where("`model_id` in(?) and `status` = ? and `is_del` = ? ", fittingIds, 1, 0) + if len(fields) != 0 { + db = db.Select(fields[0]) + } + err = db.Find(&resp).Error + return resp, err +} diff --git a/model/gmodel/fs_user_info_logic.go b/model/gmodel/fs_user_info_logic.go index 0521b912..ce76d7cb 100644 --- a/model/gmodel/fs_user_info_logic.go +++ b/model/gmodel/fs_user_info_logic.go @@ -79,9 +79,9 @@ func (m *FsUserInfoModel) GetProfile(ctx context.Context, pkey string, userId in } return info, nil } -func (m *FsUserInfoModel) FindOneByUser(ctx context.Context, userId, guestId int64) (resp *FsUserInfo, err error) { +func (m *FsUserInfoModel) FindOneByUser(ctx context.Context, userId, guestId int64, module string) (resp *FsUserInfo, err error) { if userId > 0 { - err = m.db.WithContext(ctx).Model(&FsUserInfo{}).Where("user_id = ?", userId).Take(&resp).Error + err = m.db.WithContext(ctx).Model(&FsUserInfo{}).Where("user_id = ? and module = ?", userId, module).Take(&resp).Error } else { err = m.db.WithContext(ctx).Model(&FsUserInfo{}).Where("user_id = ? and guest_id = ?", userId, guestId).Take(&resp).Error } diff --git a/server/product-template-tag/internal/logic/getproducttemplatetagslogic.go b/server/product-template-tag/internal/logic/getproducttemplatetagslogic.go index 47803452..31e0576f 100644 --- a/server/product-template-tag/internal/logic/getproducttemplatetagslogic.go +++ b/server/product-template-tag/internal/logic/getproducttemplatetagslogic.go @@ -70,7 +70,7 @@ func (l *GetProductTemplateTagsLogic) GetProductTemplateTags(req *types.GetProdu return resp.SetStatusWithMessage(basic.CodeDbSqlErr, "logo info`s metadata is not set") } //获取userInfo信息 - userProfile, err := l.svcCtx.AllModels.FsUserInfo.FindOneByUser(l.ctx, userinfo.UserId, userinfo.GuestId) + userProfile, err := l.svcCtx.AllModels.FsUserInfo.FindOneByUser(l.ctx, userinfo.UserId, userinfo.GuestId, "profile") if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return resp.SetStatusWithMessage(basic.CodeDbRecordNotFoundErr, "user profile info is not found") diff --git a/server/product/internal/handler/getproductdetailhandler.go b/server/product/internal/handler/getproductdetailhandler.go new file mode 100644 index 00000000..ef0a1725 --- /dev/null +++ b/server/product/internal/handler/getproductdetailhandler.go @@ -0,0 +1,35 @@ +package handler + +import ( + "net/http" + "reflect" + + "fusenapi/utils/basic" + + "fusenapi/server/product/internal/logic" + "fusenapi/server/product/internal/svc" + "fusenapi/server/product/internal/types" +) + +func GetProductDetailHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + + var req types.GetProductDetailReq + userinfo, err := basic.RequestParse(w, r, svcCtx, &req) + if err != nil { + return + } + + // 创建一个业务逻辑层实例 + l := logic.NewGetProductDetailLogic(r.Context(), svcCtx) + + rl := reflect.ValueOf(l) + basic.BeforeLogic(w, r, rl) + + resp := l.GetProductDetail(&req, userinfo) + + if !basic.AfterLogic(w, r, rl, resp) { + basic.NormalAfterLogic(w, r, resp) + } + } +} diff --git a/server/product/internal/handler/routes.go b/server/product/internal/handler/routes.go index 2d0b468c..589462eb 100644 --- a/server/product/internal/handler/routes.go +++ b/server/product/internal/handler/routes.go @@ -72,6 +72,11 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) { Path: "/api/product/home_page_recommend", Handler: HomePageRecommendProductListHandler(serverCtx), }, + { + Method: http.MethodGet, + Path: "/api/product/get_product_detail", + Handler: GetProductDetailHandler(serverCtx), + }, }, ) } diff --git a/server/product/internal/logic/getproductdetaillogic.go b/server/product/internal/logic/getproductdetaillogic.go new file mode 100644 index 00000000..724e6797 --- /dev/null +++ b/server/product/internal/logic/getproductdetaillogic.go @@ -0,0 +1,405 @@ +package logic + +import ( + "encoding/json" + "errors" + "fusenapi/constants" + "fusenapi/model/gmodel" + "fusenapi/utils/auth" + "fusenapi/utils/basic" + "fusenapi/utils/color_list" + "fusenapi/utils/format" + "fusenapi/utils/s3url_to_s3id" + "fusenapi/utils/template_switch_info" + "gorm.io/gorm" + "strings" + + "context" + + "fusenapi/server/product/internal/svc" + "fusenapi/server/product/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type GetProductDetailLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewGetProductDetailLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetProductDetailLogic { + return &GetProductDetailLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +// 处理进入前逻辑w,r +// func (l *GetProductDetailLogic) BeforeLogic(w http.ResponseWriter, r *http.Request) { +// } + +func (l *GetProductDetailLogic) GetProductDetail(req *types.GetProductDetailReq, userinfo *auth.UserInfo) (resp *basic.Response) { + if req.ProductId <= 0 { + return resp.SetStatusWithMessage(basic.CodeRequestParamsErr, "err param:product id is invalid") + } + req.TemplateTag = strings.Trim(req.TemplateTag, " ") + if req.TemplateTag == "" { + return resp.SetStatusWithMessage(basic.CodeRequestParamsErr, "err param:template tag is invalid") + } + //获取产品信息 + productInfo, err := l.svcCtx.AllModels.FsProduct.FindOne(l.ctx, req.ProductId) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return resp.SetStatusWithMessage(basic.CodeDbRecordNotFoundErr, "the product is not exists") + } + logx.Error(err) + return resp.SetStatusWithMessage(basic.CodeDbSqlErr, "failed to get product info") + } + if *productInfo.Status != 1 { + return resp.SetStatusWithMessage(basic.CodeDbSqlErr, "the product status is unNormal") + } + if *productInfo.IsShelf != 1 { + return resp.SetStatusWithMessage(basic.CodeDbSqlErr, "the product status is off shelf") + } + if *productInfo.IsDel == 1 { + return resp.SetStatusWithMessage(basic.CodeDbSqlErr, "the product status is deleted") + } + //获取产品类型 + productTag, err := l.svcCtx.AllModels.FsTags.FindOne(l.ctx, *productInfo.Type) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return resp.SetStatusWithMessage(basic.CodeDbRecordNotFoundErr, "the product`s tag is not exists") + } + logx.Error(err) + return resp.SetStatusWithMessage(basic.CodeDbSqlErr, "failed to get product tag info") + } + //获取模板标签颜色选择信息 + templateTagColorInfo, err := l.GetTemplateTagColor(req, userinfo) + if err != nil { + return resp.SetStatusWithMessage(basic.CodeServiceErr, err.Error()) + } + //获取产品尺寸列表 + sizeList, err := l.svcCtx.AllModels.FsProductSize.GetAllByProductIds(l.ctx, []int64{req.ProductId}, "is_hot DESC,sort ASC") + if err != nil { + logx.Error(err) + return resp.SetStatusWithMessage(basic.CodeDbSqlErr, "failed to get size list") + } + //获取模型+配件信息 + modelList, err := l.svcCtx.AllModels.FsProductModel3d.GetAllByProductIdTags(l.ctx, req.ProductId, []int64{constants.TAG_MODEL, constants.TAG_PARTS}) + if err != nil { + logx.Error(err) + return resp.SetStatusWithMessage(basic.CodeDbSqlErr, "failed to get model list") + } + mapSizeKeyModel := make(map[int64]int) //模型(不包含配件)sizeId为key + mapFitting := make(map[int64]int) //配件 + publicFittingOptionTemplateIds := make([]int64, 0, len(modelList)) //配件配置了公共模板的模板id + notPublicFittingOptionTemplateFittingIds := make([]int64, 0, len(modelList)) //配件没有配置公共模板的配件id + lightIds := make([]int64, 0, len(modelList)) + for k, v := range modelList { + switch *v.Tag { + case constants.TAG_MODEL: //模型的 + mapSizeKeyModel[*v.SizeId] = k + if *v.Light > 0 { + lightIds = append(lightIds, *v.Light) + } + case constants.TAG_PARTS: //配件的 + mapFitting[v.Id] = k + if *v.OptionTemplate > 0 { + publicFittingOptionTemplateIds = append(publicFittingOptionTemplateIds, *v.OptionTemplate) + } else { + notPublicFittingOptionTemplateFittingIds = append(notPublicFittingOptionTemplateFittingIds, v.Id) + } + } + } + //获取没有绑定公共模板的配件贴图 + notPublicFittingOptionTemplateList, err := l.svcCtx.AllModels.FsProductTemplateV2.FindAllByFittingIds(l.ctx, notPublicFittingOptionTemplateFittingIds, "id,model_id,material_img") + if err != nil { + logx.Error(err) + return resp.SetStatusWithMessage(basic.CodeDbSqlErr, "failed to get fitting material image list") + } + mapNotPublicFittingOptionTemplateMaterialImage := make(map[int64]string) //model_id为key + for _, v := range notPublicFittingOptionTemplateList { + mapNotPublicFittingOptionTemplateMaterialImage[*v.ModelId] = *v.MaterialImg + } + //获取配件绑定的公共模板列表 + publicFittingOptionTemplateList, err := l.svcCtx.AllModels.FsProductTemplateV2.FindAllByIds(l.ctx, publicFittingOptionTemplateIds, "id,model_id,material_img") + if err != nil { + logx.Error(err) + return resp.SetStatusWithMessage(basic.CodeDbSqlErr, "failed to get fitting optional template list") + } + mapPublicFittingOptionTemplate := make(map[int64]string) //模板id为key + for _, v := range publicFittingOptionTemplateList { + mapPublicFittingOptionTemplate[v.Id] = *v.MaterialImg + } + //获取灯光列表 + lightList, err := l.svcCtx.AllModels.FsProductModel3dLight.GetAllByIds(l.ctx, lightIds) + if err != nil { + logx.Error(err) + return resp.SetStatusWithMessage(basic.CodeDbSqlErr, "failed to get light list") + } + mapLight := make(map[int64]int) + for k, v := range lightList { + mapLight[v.Id] = k + } + //获取产品模板列表 + templateList, err := l.svcCtx.AllModels.FsProductTemplateV2.FindAllByProductIdsTemplateTag(l.ctx, []int64{req.ProductId}, req.TemplateTag, "") + if err != nil { + logx.Error(err) + return resp.SetStatusWithMessage(basic.CodeDbSqlErr, "failed to get template list") + } + mapModelIdKeyTemplate := make(map[int64]int) + for k, v := range templateList { + mapModelIdKeyTemplate[*v.ModelId] = k + } + //记录产品最低价 + mapProductMinPrice := make(map[int64]int64) + if err = l.svcCtx.AllModels.FsProductModel3d.GetProductMinPrice(modelList, mapProductMinPrice); err != nil { + logx.Error(err) + return resp.SetStatusWithMessage(basic.CodeServiceErr, "failed to get product min price") + } + //获取默认渲染的尺寸 + defaultSize, err := l.getRenderDefaultSize(req.ProductId, req.TemplateTag) + if err != nil { + logx.Error(err) + return resp.SetStatusWithMessage(basic.CodeDbSqlErr, "failed to get default size") + } + //整理返回 + rspSizeList := make([]types.SizeInfo, 0, len(sizeList)) + for _, sizeInfo := range sizeList { + var sizeTitle interface{} + if err = json.Unmarshal([]byte(*sizeInfo.Title), &sizeTitle); err != nil { + logx.Error(err) + return resp.SetStatusWithMessage(basic.CodeJsonErr, "failed to parse size title") + } + //尺寸下最低价 + minPrice := "" + if price, ok := mapProductMinPrice[*sizeInfo.ProductId]; ok { + minPrice = format.CentitoDollar(price, 3) + } + var modelInfoRsp types.ModelInfo + var TemplateInfoRsp interface{} + var FittingListRsp []types.FittingInfo + if modelIndex, ok := mapSizeKeyModel[sizeInfo.Id]; ok { + modelInfo := modelList[modelIndex] + //模板信息 + if templateIndex, ok := mapModelIdKeyTemplate[modelInfo.Id]; ok { + templateInfo := templateList[templateIndex] + //获取开关信息 + TemplateInfoRsp = template_switch_info.GetTemplateSwitchInfo(templateInfo.Id, templateInfo.TemplateInfo, *templateInfo.MaterialImg) + } + //赋值id + modelInfoRsp.Id = modelInfo.Id + //模型设计信息 + var modelDesignInfo interface{} + if modelInfo.ModelInfo != nil && *modelInfo.ModelInfo != "" { + if err = json.Unmarshal([]byte(*modelInfo.ModelInfo), &modelDesignInfo); err != nil { + logx.Error(err) + return resp.SetStatusWithMessage(basic.CodeJsonErr, "failed to parse model design info") + } + //赋值 + modelInfoRsp.ModelDesignInfo = modelDesignInfo + } + //灯光信息 + if lightIndex, ok := mapLight[*modelInfo.Light]; ok { + lightInfo := lightList[lightIndex] + var lightDesignInfo interface{} + if lightInfo.Info != nil && *lightInfo.Info != "" { + if err = json.Unmarshal([]byte(*lightInfo.Info), &lightDesignInfo); err != nil { + logx.Error(err) + return resp.SetStatusWithMessage(basic.CodeJsonErr, "failed to parse light design info") + } + //赋值 + modelInfoRsp.LightInfo = types.LightInfo{ + Id: lightInfo.Id, + LightName: *lightInfo.Name, + LightDesignInfo: lightDesignInfo, + } + } + } + optionalFittingIdsStr := strings.Trim(*modelInfo.PartList, " ") + optionalFittingIdsStr = strings.Trim(optionalFittingIdsStr, ",") + //配件信息 + FittingListRsp, err = l.GetModelOptionalFittings(l.ctx, optionalFittingIdsStr, mapFitting, mapPublicFittingOptionTemplate, mapNotPublicFittingOptionTemplateMaterialImage, modelInfo, modelList) + if err != nil { + return resp.SetStatusWithMessage(basic.CodeServiceErr, err.Error()) + } + } + isDefaultSize := int64(0) + if sizeInfo.Id == defaultSize { + isDefaultSize = 1 + } + rspSizeList = append(rspSizeList, types.SizeInfo{ + Id: sizeInfo.Id, + IsDefault: isDefaultSize, + Title: sizeTitle, + Capacity: *sizeInfo.Capacity, + PartsCanDeleted: *sizeInfo.PartsCanDeleted, + IsHot: *sizeInfo.IsHot, + MinPrice: minPrice, + TemplateInfo: TemplateInfoRsp, + ModelInfo: modelInfoRsp, + FittingList: FittingListRsp, + }) + } + return resp.SetStatusWithMessage(basic.CodeOK, "success", types.GetProductDetailRsp{ + TemplateTagColorInfo: templateTagColorInfo, + ProductInfo: types.ProductInfo{ + Id: productInfo.Id, + ProductType: *productInfo.Type, + ProductTypeName: *productTag.Title, + Title: *productInfo.Title, + IsEnv: *productInfo.IsProtection, + IsMicro: *productInfo.IsMicrowave, + IsCustomization: *productInfo.IsCustomization, + }, + BaseColors: color_list.GetColor(), + SizeList: rspSizeList, + }) +} + +// 获取模型可选配件列表 +func (l *GetProductDetailLogic) GetModelOptionalFittings(ctx context.Context, optionalFittingIdsStr string, mapFitting map[int64]int, mapPublicFittingOptionTemplate, mapNotPublicFittingOptionTemplateMaterialImage map[int64]string, modelInfo gmodel.FsProductModel3d, modelList []gmodel.FsProductModel3d) (resp []types.FittingInfo, err error) { + if optionalFittingIdsStr == "" { + return + } + optionalFittingIds, err := format.StrSlicToInt64Slice(strings.Split(optionalFittingIdsStr, ",")) + if err != nil { + logx.Error(err) + return nil, errors.New("failed to split optional fitting list") + } + resp = make([]types.FittingInfo, 0, len(optionalFittingIds)) + //可选配件 + for _, optionFittingId := range optionalFittingIds { + fittingIndex, ok := mapFitting[optionFittingId] + if !ok { + continue + } + fittingInfo := modelList[fittingIndex] + var fittingDesignInfo interface{} + if fittingInfo.ModelInfo != nil && *fittingInfo.ModelInfo != "" { + if err = json.Unmarshal([]byte(*fittingInfo.ModelInfo), &fittingDesignInfo); err != nil { + logx.Error(err) + return nil, errors.New("failed to parse fitting design info") + } + } + //是否默认显示配件 + isDefault := int64(0) + if optionFittingId == *modelInfo.PartId { + isDefault = 1 + } + //配件贴图 + FittingMaterialImg := "" + //贴图,如果绑定了公共模板,则获取公共模板的贴图数据(待优化) + if *fittingInfo.OptionTemplate > 0 { + if image, ok := mapPublicFittingOptionTemplate[*fittingInfo.OptionTemplate]; ok { + FittingMaterialImg = image + } + } else { //否则取该配件下的模板贴图 + if image, ok := mapNotPublicFittingOptionTemplateMaterialImage[fittingInfo.Id]; ok { + FittingMaterialImg = image + } + } + resp = append(resp, types.FittingInfo{ + Id: fittingInfo.Id, + IsHot: *fittingInfo.IsHot, + MaterialImage: FittingMaterialImg, + DesignInfo: fittingDesignInfo, + Price: format.CentitoDollar(*fittingInfo.Price, 3), + Name: *fittingInfo.Name, + IsDefault: isDefault, + }) + } + return resp, nil +} + +// 获取列表页默认渲染的尺寸(同列表页) +func (l *GetProductDetailLogic) getRenderDefaultSize(productId int64, templateTag string) (sizeId int64, err error) { + //获取模板 + productTemplate, err := l.svcCtx.AllModels.FsProductTemplateV2.FindOneCloudRenderByProductIdTemplateTag(l.ctx, productId, templateTag, "sort ASC", "model_id") + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return 0, errors.New("找不到对应开启云渲染模板") + } + logx.Error(err) + return 0, errors.New("获取对应开启云渲染模板失败") + } + //根据模板找到模型 + model3d, err := l.svcCtx.AllModels.FsProductModel3d.FindOne(l.ctx, *productTemplate.ModelId, "size_id") + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return 0, errors.New("找不到对应模型") + } + logx.Error(err) + return 0, errors.New("获取对应模型失败") + } + return *model3d.SizeId, nil +} + +// 获取对应模板标签颜色信息 +func (l *GetProductDetailLogic) GetTemplateTagColor(req *types.GetProductDetailReq, userinfo *auth.UserInfo) (resp types.TemplateTagColorInfo, err error) { + if req.SelectColorIndex < 0 { + return types.TemplateTagColorInfo{}, errors.New("param selected_color_index is invalid") + } + //根据logo查询素材资源 + resourceId := s3url_to_s3id.GetS3ResourceIdFormUrl(req.Logo) + if resourceId == "" { + return types.TemplateTagColorInfo{}, errors.New("param logo is invalid") + } + var ( + userMaterial *gmodel.FsUserMaterial + templateTagInfo *gmodel.FsProductTemplateTags + ) + //获取模板标签信息 + templateTagInfo, err = l.svcCtx.AllModels.FsProductTemplateTags.FindOneByTagName(l.ctx, req.TemplateTag) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return types.TemplateTagColorInfo{}, errors.New("the template tag is not exists") + } + logx.Error(err) + return types.TemplateTagColorInfo{}, errors.New("failed to get template tag info") + } + userMaterial, err = l.svcCtx.AllModels.FsUserMaterial.FindOneByLogoResourceId(l.ctx, resourceId) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return types.TemplateTagColorInfo{}, errors.New("the logo is not found") + } + logx.Error(err) + return types.TemplateTagColorInfo{}, errors.New("failed to get user material") + } + if userMaterial.Metadata == nil || len(*userMaterial.Metadata) == 0 { + return types.TemplateTagColorInfo{}, errors.New("the user material is empty") + } + //解析用户素材元数据 + var metaData map[string]interface{} + if err = json.Unmarshal(*userMaterial.Metadata, &metaData); err != nil { + logx.Error(err) + return types.TemplateTagColorInfo{}, errors.New("failed to parse user metadata") + } + var mapMaterialTemplateTag map[string][][]string + b, _ := json.Marshal(metaData["template_tag"]) + if err = json.Unmarshal(b, &mapMaterialTemplateTag); err != nil { + logx.Error(err) + return types.TemplateTagColorInfo{}, errors.New("invalid format of metadata`s template_tag") + } + colors, ok := mapMaterialTemplateTag[req.TemplateTag] + if !ok { + return types.TemplateTagColorInfo{}, errors.New("the template tag is not found from this logo material`s metadata") + } + if req.SelectColorIndex >= len(colors) { + return types.TemplateTagColorInfo{}, errors.New("select color index is out of range !!") + } + var templateTagGroups interface{} + if templateTagInfo.Groups != nil && *templateTagInfo.Groups != "" { + if err = json.Unmarshal([]byte(*templateTagInfo.Groups), &templateTagGroups); err != nil { + logx.Error(err) + return types.TemplateTagColorInfo{}, errors.New("failed to parse template tag`s groups info") + } + } + return types.TemplateTagColorInfo{ + Colors: colors, + SelectedColorIndex: req.SelectColorIndex, + TemplateTagGroups: templateTagGroups, + }, nil +} diff --git a/server/product/internal/logic/getrecommandproductlistlogic.go b/server/product/internal/logic/getrecommandproductlistlogic.go index 2877ef95..0debe6e8 100644 --- a/server/product/internal/logic/getrecommandproductlistlogic.go +++ b/server/product/internal/logic/getrecommandproductlistlogic.go @@ -3,6 +3,7 @@ package logic import ( "encoding/json" "errors" + "fusenapi/constants" "fusenapi/model/gmodel" "fusenapi/utils/auth" "fusenapi/utils/basic" @@ -93,7 +94,12 @@ func (l *GetRecommandProductListLogic) GetRecommandProductList(req *types.GetRec } //获取产品最低价 mapProductMinPrice := make(map[int64]int64) - if err = l.svcCtx.AllModels.FsProductModel3d.GetProductMinPrice(l.ctx, productIds, mapProductMinPrice); err != nil { + modelList, err := l.svcCtx.AllModels.FsProductModel3d.GetAllByProductIdsTags(l.ctx, []int64{productInfo.Id}, []int{constants.TAG_MODEL, constants.TAG_PARTS}, "id,size_id,product_id,price,tag,part_id,step_price") + if err != nil { + logx.Error(err) + return resp.SetStatusWithMessage(basic.CodeDbSqlErr, "failed to get model list") + } + if err = l.svcCtx.AllModels.FsProductModel3d.GetProductMinPrice(modelList, mapProductMinPrice); err != nil { logx.Error(err) return resp.SetStatusWithMessage(basic.CodeDbSqlErr, "failed to get product min price") } diff --git a/server/product/internal/logic/getsizebypidlogic.go b/server/product/internal/logic/getsizebypidlogic.go index 46d81e4f..d83e4dd7 100644 --- a/server/product/internal/logic/getsizebypidlogic.go +++ b/server/product/internal/logic/getsizebypidlogic.go @@ -74,25 +74,26 @@ func (l *GetSizeByPidLogic) GetSizeByPid(req *types.GetSizeByPidReq, userinfo *a return resp.SetStatusWithMessage(basic.CodeDbSqlErr, "failed to get size list") } sizeIds := make([]int64, 0, len(sizeList)) - productIds := make([]int64, 0, len(sizeList)) for _, v := range sizeList { sizeIds = append(sizeIds, v.Id) - productIds = append(productIds, *v.ProductId) } //获取产品价格列表 mapProductMinPrice := make(map[int64]int64) - if err = l.svcCtx.AllModels.FsProductModel3d.GetProductMinPrice(l.ctx, productIds, mapProductMinPrice); err != nil { - logx.Error(err) - return resp.SetStatusWithMessage(basic.CodeDbSqlErr, "failed to get product min price") - } - //获取对应模型数据 - modelList, err := l.svcCtx.AllModels.FsProductModel3d.GetAllBySizeIdsTag(l.ctx, sizeIds, constants.TAG_MODEL, "id,size_id") + //获取产品模型 + modelList, err := l.svcCtx.AllModels.FsProductModel3d.GetAllByProductIdsTags(l.ctx, []int64{productInfo.Id}, []int{constants.TAG_MODEL, constants.TAG_PARTS}, "id,size_id,product_id,price,tag,part_id,step_price") if err != nil { logx.Error(err) return resp.SetStatusWithMessage(basic.CodeDbSqlErr, "failed to get model list") } + if err = l.svcCtx.AllModels.FsProductModel3d.GetProductMinPrice(modelList, mapProductMinPrice); err != nil { + logx.Error(err) + return resp.SetStatusWithMessage(basic.CodeDbSqlErr, "failed to get product min price") + } mapSizeModel := make(map[int64]int) //size id为key for k, v := range modelList { + if *v.Tag != constants.TAG_MODEL { + continue + } mapSizeModel[*v.SizeId] = k } //处理 diff --git a/server/product/internal/logic/gettagproductlistlogic.go b/server/product/internal/logic/gettagproductlistlogic.go index eae2abfb..4ffae478 100644 --- a/server/product/internal/logic/gettagproductlistlogic.go +++ b/server/product/internal/logic/gettagproductlistlogic.go @@ -3,6 +3,7 @@ package logic import ( "encoding/json" "errors" + "fusenapi/constants" "fusenapi/model/gmodel" "fusenapi/utils/auth" "fusenapi/utils/basic" @@ -228,8 +229,13 @@ func (l *GetTagProductListLogic) getProductRelationInfo(req getProductRelationIn CoverMetadata: req.MapResourceMetadata[*v.Cover], }) } + modelList, err := l.svcCtx.AllModels.FsProductModel3d.GetAllByProductIdsTags(l.ctx, productIds, []int{constants.TAG_MODEL, constants.TAG_PARTS}, "id,size_id,product_id,price,tag,part_id,step_price") + if err != nil { + logx.Error(err) + return nil, errors.New("failed to get model list") + } //获取产品最低价格 - if err = l.svcCtx.AllModels.FsProductModel3d.GetProductMinPrice(l.ctx, productIds, req.MapProductMinPrice); err != nil { + if err = l.svcCtx.AllModels.FsProductModel3d.GetProductMinPrice(modelList, req.MapProductMinPrice); err != nil { logx.Error(err) return nil, errors.New("failed to get product min price") } diff --git a/server/product/internal/logic/homepagerecommendproductlistlogic.go b/server/product/internal/logic/homepagerecommendproductlistlogic.go index a4447811..f0bf6206 100644 --- a/server/product/internal/logic/homepagerecommendproductlistlogic.go +++ b/server/product/internal/logic/homepagerecommendproductlistlogic.go @@ -3,6 +3,7 @@ package logic import ( "encoding/json" "errors" + "fusenapi/constants" "fusenapi/model/gmodel" "fusenapi/utils/auth" "fusenapi/utils/basic" @@ -113,7 +114,12 @@ func (l *HomePageRecommendProductListLogic) HomePageRecommendProductList(req *ty } //获取产品最低价格 mapProductMinPrice := make(map[int64]int64) - if err = l.svcCtx.AllModels.FsProductModel3d.GetProductMinPrice(l.ctx, productIds, mapProductMinPrice); err != nil { + modelList, err := l.svcCtx.AllModels.FsProductModel3d.GetAllByProductIdsTags(l.ctx, productIds, []int{constants.TAG_MODEL, constants.TAG_PARTS}, "id,size_id,product_id,price,tag,part_id,step_price") + if err != nil { + logx.Error(err) + return resp.SetStatusWithMessage(basic.CodeServiceErr, "get product model list err") + } + if err = l.svcCtx.AllModels.FsProductModel3d.GetProductMinPrice(modelList, mapProductMinPrice); err != nil { logx.Error(err) return resp.SetStatusWithMessage(basic.CodeServiceErr, "failed to get product min price") } diff --git a/server/product/internal/types/types.go b/server/product/internal/types/types.go index 05455455..cb6e6448 100644 --- a/server/product/internal/types/types.go +++ b/server/product/internal/types/types.go @@ -191,6 +191,71 @@ type HomePageRecommendProductListRsp struct { IsCustomization int64 `json:"is_customization"` } +type GetProductDetailReq struct { + ProductId int64 `form:"product_id"` //产品id + TemplateTag string `form:"template_tag"` //模板标签 + SelectColorIndex int `form:"select_color_index"` //模板标签颜色索引 + Logo string `form:"logo"` //logo地址 +} + +type GetProductDetailRsp struct { + TemplateTagColorInfo TemplateTagColorInfo `json:"template_tag_color_info"` //标签颜色信息 + ProductInfo ProductInfo `json:"product_info"` //产品基本信息 + BaseColors interface{} `json:"base_colors"` //一些返回写死的颜色 + SizeList []SizeInfo `json:"size_list"` //尺寸相关信息 +} + +type SizeInfo struct { + Id int64 `json:"id"` //尺寸id + IsDefault int64 `json:"is_default"` //是否默认显示 + Title interface{} `json:"title"` //尺寸标题信息 + Capacity string `json:"capacity"` //尺寸名称 + PartsCanDeleted int64 `json:"parts_can_deleted"` //配件是否可删除 + IsHot int64 `json:"is_hot"` //是否热门 + MinPrice string `json:"min_price"` //最低价 + TemplateInfo interface{} `json:"template_info"` //模板相关信息 + ModelInfo ModelInfo `json:"model_info"` //模型相关信息 + FittingList []FittingInfo `json:"fitting_list"` //配件相关信息 +} + +type FittingInfo struct { + Id int64 `json:"id"` //配件id + IsHot int64 `json:"is_hot"` //是否热门 + MaterialImage string `json:"material_image"` //配件材质图 + DesignInfo interface{} `json:"design_info"` //配件设计信息 + Price string `json:"price"` //配件价格 + Name string `json:"name"` //配件名 + IsDefault int64 `json:"is_default"` //是否默认的配件 +} + +type ModelInfo struct { + Id int64 `json:"id"` //模型id + ModelDesignInfo interface{} `json:"design_info"` //模型设计信息 + LightInfo LightInfo `json:"light_info"` //灯光信息 +} + +type LightInfo struct { + Id int64 `json:"id"` //灯光id + LightName string `json:"light_name"` //灯光组名称 + LightDesignInfo interface{} `json:"light_design_info"` //灯光设计信息 +} + +type ProductInfo struct { + Id int64 `json:"id"` //产品id + ProductType int64 `json:"product_type"` //产品类型id + ProductTypeName string `json:"product_type_name"` //产品类型名称 + Title string `json:"title"` //产品标题 + IsEnv int64 `json:"is_env"` //是否环保 + IsMicro int64 `json:"is_micro"` //是否可微波炉 + IsCustomization int64 `json:"is_customization"` //是否可定制产品 +} + +type TemplateTagColorInfo struct { + Colors [][]string `json:"colors"` //传入logo对应的算法颜色组 + SelectedColorIndex int `json:"selected_color_index"` //选择的模板标签的颜色索引值 + TemplateTagGroups interface{} `json:"template_tag_groups"` //模板标签分组信息 +} + type Request struct { } diff --git a/server/websocket/internal/handler/closewebsockethandler.go b/server/websocket/internal/handler/closewebsockethandler.go new file mode 100644 index 00000000..b6f755e8 --- /dev/null +++ b/server/websocket/internal/handler/closewebsockethandler.go @@ -0,0 +1,35 @@ +package handler + +import ( + "net/http" + "reflect" + + "fusenapi/utils/basic" + + "fusenapi/server/websocket/internal/logic" + "fusenapi/server/websocket/internal/svc" + "fusenapi/server/websocket/internal/types" +) + +func CloseWebsocketHandler(svcCtx *svc.ServiceContext) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + + var req types.CloseWebsocketReq + userinfo, err := basic.RequestParse(w, r, svcCtx, &req) + if err != nil { + return + } + + // 创建一个业务逻辑层实例 + l := logic.NewCloseWebsocketLogic(r.Context(), svcCtx) + + rl := reflect.ValueOf(l) + basic.BeforeLogic(w, r, rl) + + resp := l.CloseWebsocket(&req, userinfo) + + if !basic.AfterLogic(w, r, rl, resp) { + basic.NormalAfterLogic(w, r, resp) + } + } +} diff --git a/server/websocket/internal/handler/routes.go b/server/websocket/internal/handler/routes.go index 8a2c5bd1..4eefa507 100644 --- a/server/websocket/internal/handler/routes.go +++ b/server/websocket/internal/handler/routes.go @@ -27,6 +27,11 @@ func RegisterHandlers(server *rest.Server, serverCtx *svc.ServiceContext) { Path: "/api/websocket/common_notify", Handler: CommonNotifyHandler(serverCtx), }, + { + Method: http.MethodPost, + Path: "/api/websocket/close_websocket", + Handler: CloseWebsocketHandler(serverCtx), + }, }, ) } diff --git a/server/websocket/internal/logic/closewebsocketlogic.go b/server/websocket/internal/logic/closewebsocketlogic.go new file mode 100644 index 00000000..72a5ef7c --- /dev/null +++ b/server/websocket/internal/logic/closewebsocketlogic.go @@ -0,0 +1,50 @@ +package logic + +import ( + "fusenapi/utils/auth" + "fusenapi/utils/basic" + + "context" + + "fusenapi/server/websocket/internal/svc" + "fusenapi/server/websocket/internal/types" + + "github.com/zeromicro/go-zero/core/logx" +) + +type CloseWebsocketLogic struct { + logx.Logger + ctx context.Context + svcCtx *svc.ServiceContext +} + +func NewCloseWebsocketLogic(ctx context.Context, svcCtx *svc.ServiceContext) *CloseWebsocketLogic { + return &CloseWebsocketLogic{ + Logger: logx.WithContext(ctx), + ctx: ctx, + svcCtx: svcCtx, + } +} + +// 处理进入前逻辑w,r +// func (l *CloseWebsocketLogic) BeforeLogic(w http.ResponseWriter, r *http.Request) { +// } + +func (l *CloseWebsocketLogic) CloseWebsocket(req *types.CloseWebsocketReq, userinfo *auth.UserInfo) (resp *basic.Response) { + //获取连接 + value, ok := mapConnPool.Load(req.Wid) + if !ok { + return resp.SetStatusAddMessage(basic.CodeRequestParamsErr, "wid connection is not exists") + } + ws, ok := value.(wsConnectItem) + if !ok { + return resp.SetStatusWithMessage(basic.CodeServiceErr, "渲染回调断言websocket连接失败") + } + ws.close() + return resp.SetStatus(basic.CodeOK, "断开成功") +} + +// 处理逻辑后 w,r 如:重定向, resp 必须重新处理 +// func (l *CloseWebsocketLogic) AfterLogic(w http.ResponseWriter, r *http.Request, resp *basic.Response) { +// // httpx.OkJsonCtx(r.Context(), w, resp) +// } diff --git a/server/websocket/internal/logic/datatransferlogic.go b/server/websocket/internal/logic/datatransferlogic.go index ce77f9b8..b7dd3978 100644 --- a/server/websocket/internal/logic/datatransferlogic.go +++ b/server/websocket/internal/logic/datatransferlogic.go @@ -472,7 +472,7 @@ func (w *wsConnectItem) allocationProcessing(data []byte) { //获取工厂实例 processor := w.newAllocationProcessor(parseInfo.T) if processor == nil { - logx.Error("未知消息类型:", string(data)) + //logx.Error("未知消息类型:", string(data)) return } //执行工厂方法 diff --git a/server/websocket/internal/logic/ws_render_image.go b/server/websocket/internal/logic/ws_render_image.go index 7b3d2f7d..f9c96331 100644 --- a/server/websocket/internal/logic/ws_render_image.go +++ b/server/websocket/internal/logic/ws_render_image.go @@ -145,10 +145,6 @@ func (w *wsConnectItem) consumeRenderImageData() { // 执行渲染任务 func (w *wsConnectItem) renderImage(renderImageData websocket_data.RenderImageReqMsg) { - /* if renderImageData.RenderData.Logo == "" { - w.renderErrResponse(renderImageData.RenderId, renderImageData.RequestId, renderImageData.RenderData.TemplateTag, "", "请传入logo", renderImageData.RenderData.ProductId, w.userId, w.guestId, 0, 0, 0, 0) - return - }*/ if !strings.Contains(renderImageData.RenderData.Logo, "storage.fusenpack.com") { w.renderErrResponse(renderImageData.RenderId, renderImageData.RequestId, renderImageData.RenderData.TemplateTag, "", "非法的logo", renderImageData.RenderData.ProductId, w.userId, w.guestId, 0, 0, 0, 0) return diff --git a/server/websocket/internal/types/types.go b/server/websocket/internal/types/types.go index 1bcd95d5..316ac30d 100644 --- a/server/websocket/internal/types/types.go +++ b/server/websocket/internal/types/types.go @@ -25,6 +25,10 @@ type CommonNotifyReq struct { Data map[string]interface{} `json:"data"` //后端与前端约定好的数据 } +type CloseWebsocketReq struct { + Wid string `json:"wid"` +} + type Request struct { } diff --git a/server_api/product.api b/server_api/product.api index a2a371bb..8180ee6b 100644 --- a/server_api/product.api +++ b/server_api/product.api @@ -46,6 +46,9 @@ service product { //获取列表页推荐产品列表 @handler HomePageRecommendProductListHandler get /api/product/home_page_recommend(HomePageRecommendProductListReq) returns (response); + //获取产品详情(重构版) + @handler GetProductDetailHandler + get /api/product/get_product_detail(GetProductDetailReq) returns (response); } //获取详情页推荐产品列表 @@ -221,4 +224,63 @@ type HomePageRecommendProductListRsp { CoverDefault []CoverDefaultItem `json:"cover_default"` HaveOptionalFitting bool `json:"have_optional_fitting"` IsCustomization int64 `json:"is_customization"` +} + +//获取产品详情(重构版) +type GetProductDetailReq { + ProductId int64 `form:"product_id"` //产品id + TemplateTag string `form:"template_tag"` //模板标签 + SelectColorIndex int `form:"select_color_index"` //模板标签颜色索引 + Logo string `form:"logo"` //logo地址 +} +type GetProductDetailRsp { + TemplateTagColorInfo TemplateTagColorInfo `json:"template_tag_color_info"` //标签颜色信息 + ProductInfo ProductInfo `json:"product_info"` //产品基本信息 + BaseColors interface{} `json:"base_colors"` //一些返回写死的颜色 + SizeList []SizeInfo `json:"size_list"` //尺寸相关信息 +} +type SizeInfo { + Id int64 `json:"id"` //尺寸id + IsDefault int64 `json:"is_default"` //是否默认显示 + Title interface{} `json:"title"` //尺寸标题信息 + Capacity string `json:"capacity"` //尺寸名称 + PartsCanDeleted int64 `json:"parts_can_deleted"` //配件是否可删除 + IsHot int64 `json:"is_hot"` //是否热门 + MinPrice string `json:"min_price"` //最低价 + TemplateInfo interface{} `json:"template_info"` //模板相关信息 + ModelInfo ModelInfo `json:"model_info"` //模型相关信息 + FittingList []FittingInfo `json:"fitting_list"` //配件相关信息 +} +type FittingInfo { + Id int64 `json:"id"` //配件id + IsHot int64 `json:"is_hot"` //是否热门 + MaterialImage string `json:"material_image"` //配件材质图 + DesignInfo interface{} `json:"design_info"` //配件设计信息 + Price string `json:"price"` //配件价格 + Name string `json:"name"` //配件名 + IsDefault int64 `json:"is_default"` //是否默认的配件 +} +type ModelInfo { + Id int64 `json:"id"` //模型id + ModelDesignInfo interface{} `json:"design_info"` //模型设计信息 + LightInfo LightInfo `json:"light_info"` //灯光信息 +} +type LightInfo { + Id int64 `json:"id"` //灯光id + LightName string `json:"light_name"` //灯光组名称 + LightDesignInfo interface{} `json:"light_design_info"` //灯光设计信息 +} +type ProductInfo { + Id int64 `json:"id"` //产品id + ProductType int64 `json:"product_type"` //产品类型id + ProductTypeName string `json:"product_type_name"` //产品类型名称 + Title string `json:"title"` //产品标题 + IsEnv int64 `json:"is_env"` //是否环保 + IsMicro int64 `json:"is_micro"` //是否可微波炉 + IsCustomization int64 `json:"is_customization"` //是否可定制产品 +} +type TemplateTagColorInfo { + Colors [][]string `json:"colors"` //传入logo对应的算法颜色组 + SelectedColorIndex int `json:"selected_color_index"` //选择的模板标签的颜色索引值 + TemplateTagGroups interface{} `json:"template_tag_groups"` //模板标签分组信息 } \ No newline at end of file diff --git a/server_api/websocket.api b/server_api/websocket.api index b3c38975..43a39910 100644 --- a/server_api/websocket.api +++ b/server_api/websocket.api @@ -18,6 +18,9 @@ service websocket { //通用回调接口 @handler CommonNotifyHandler post /api/websocket/common_notify(CommonNotifyReq) returns (response); + //关闭某个连接 + @handler CloseWebsocketHandler + post /api/websocket/close_websocket(CloseWebsocketReq) returns (response); } //websocket数据交互[ @@ -39,4 +42,8 @@ type CommonNotifyReq { UserId int64 `json:"user_id,optional"` //用户id GuestId int64 `json:"guest_id,optional"` //游客id Data map[string]interface{} `json:"data"` //后端与前端约定好的数据 +} +//关闭连接 +type CloseWebsocketReq { + Wid string `json:"wid"` } \ No newline at end of file