package logic import ( "context" "encoding/json" "errors" "fmt" "fusenapi/constants" "fusenapi/model/gmodel" "fusenapi/utils/auth" "fusenapi/utils/basic" "fusenapi/utils/color_list" "fusenapi/utils/encryption_decryption" "fusenapi/utils/format" "fusenapi/utils/image" "fusenapi/utils/step_price" "gorm.io/gorm" "strconv" "strings" "fusenapi/server/product/internal/svc" "fusenapi/server/product/internal/types" "github.com/zeromicro/go-zero/core/logx" ) type GetProductInfoLogic struct { logx.Logger ctx context.Context svcCtx *svc.ServiceContext } func NewGetProductInfoLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetProductInfoLogic { return &GetProductInfoLogic{ Logger: logx.WithContext(ctx), ctx: ctx, svcCtx: svcCtx, } } func (l *GetProductInfoLogic) GetProductInfo(req *types.GetProductInfoReq, userinfo *auth.UserInfo) (resp *basic.Response) { //获取产品信息 productInfo, err := l.svcCtx.AllModels.FsProduct.FindOneBySn(l.ctx, req.Pid) 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 req.Size > 0 { req.Size = image.GetCurrentSize(req.Size) } materialIdSlice, err := format.StrSlicToInt64Slice(strings.Split(*productInfo.MaterialIds, ",")) if err != nil { logx.Error(err) return resp.SetStatusWithMessage(basic.CodeServiceErr, "failed to parse product material Id list") } //材料列表 materials := make([]types.MaterialItem, 0, len(materialIdSlice)) for _, v := range materialIdSlice { if title, ok := constants.MAP_MATERIAL[v]; ok { materials = append(materials, types.MaterialItem{ Id: v, Title: title, }) } } //尺寸列表 sizeList, err := l.svcCtx.AllModels.FsProductSize.GetAllByStatus(l.ctx, int64(constants.FAQ_STATUS_ON), 1, "id,title,capacity,cover,sort,parts_can_deleted") if err != nil { logx.Error(err) return resp.SetStatusWithMessage(basic.CodeDbSqlErr, "failed to get product size list") } sizeIds := make([]int64, 0, len(sizeList)) for _, v := range sizeList { sizeIds = append(sizeIds, v.Id) } //获取产品标签 tagInfo, err := l.svcCtx.AllModels.FsTags.FindOne(l.ctx, *productInfo.Type) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return resp.SetStatusWithMessage(basic.CodeDbRecordNotFoundErr, "tag info is not exists") } logx.Error(err) return resp.SetStatusWithMessage(basic.CodeDbSqlErr, "failed to get tag info") } typeName := *tagInfo.Title //获取该尺寸下的模型数据 model3dList, err := l.svcCtx.AllModels.FsProductModel3d.GetAllBySizeIdsTag(l.ctx, sizeIds, constants.TAG_MODEL) if err != nil { logx.Error(err) return resp.SetStatusWithMessage(basic.CodeDbSqlErr, "failed to get product 3d model list") } model3dIds := make([]int64, 0, len(model3dList)) mapModel3dWithSizeIdIndex := make(map[int64]gmodel.FsProductModel3d) //sizeid为key for _, v := range model3dList { model3dIds = append(model3dIds, v.Id) mapModel3dWithSizeIdIndex[*v.SizeId] = v } //通过产品id和模型id获取模板信息 productTemplateList, err := l.svcCtx.AllModels.FsProductTemplateV2.FindAllByProductIdModelIds(l.ctx, model3dIds, productInfo.Id) if err != nil { logx.Error(err) return resp.SetStatusWithMessage(basic.CodeDbSqlErr, "failed to get product template list") } //获取模板包含的model_id mapTemplateModelId := make(map[int64]struct{}) tagIds := make([]int64, 0, len(productTemplateList)) for _, v := range productTemplateList { mapTemplateModelId[*v.ModelId] = struct{}{} if v.Tag != nil && *v.Tag != "" { tagId, err := strconv.ParseInt(*v.Tag, 10, 64) if err != nil { logx.Error(err) return resp.SetStatusWithMessage(basic.CodeServiceErr, "tag is not a number") } tagIds = append(tagIds, tagId) } } //过滤没有模板的尺寸数据 sizeListRsp := make([]types.SizeItem, 0, len(sizeList)) for _, v := range sizeList { model3dInfo, ok := mapModel3dWithSizeIdIndex[v.Id] if !ok { continue } if _, ok = mapTemplateModelId[model3dInfo.Id]; !ok { continue } var title types.SizeTitle if err = json.Unmarshal([]byte(*v.Title), &title); err != nil { logx.Error(err) return resp.SetStatusWithMessage(basic.CodeJsonErr, "failed to parse size info`s title") } var modelInfo map[string]interface{} if model3dInfo.ModelInfo != nil && *model3dInfo.ModelInfo != "" { if err = json.Unmarshal([]byte(*model3dInfo.ModelInfo), &modelInfo); err != nil { logx.Error(err) return resp.SetStatusWithMessage(basic.CodeJsonErr, "failed to parse model info") } } cover := "" if modelInfo["cover"] != nil && modelInfo["cover"].(string) != "" { cover = modelInfo["cover"].(string) if req.Size >= 200 { coverArr := strings.Split(modelInfo["cover"].(string), ".") cover = fmt.Sprintf("%s_%d.%s", coverArr[0], req.Size, coverArr[1]) } } sizeListRsp = append(sizeListRsp, types.SizeItem{ Id: v.Id, Title: title, Capacity: *v.Capacity, Cover: cover, Sort: *v.Sort, PartsCanDeleted: *v.PartsCanDeleted > 0, }) } //获取标签列表 tagList, err := l.svcCtx.AllModels.FsTags.GetAllByIdsWithoutStatus(l.ctx, tagIds) if err != nil { logx.Error(err) return resp.SetStatusWithMessage(basic.CodeDbSqlErr, "failed to get tag list") } mapTag := make(map[string]gmodel.FsTags) for _, v := range tagList { mapTag[fmt.Sprintf("%d", v.Id)] = v } //获取全部模型信息 allModel3dList, err := l.svcCtx.AllModels.FsProductModel3d.GetAll(l.ctx) if err != nil { logx.Error(err) return resp.SetStatusWithMessage(basic.CodeDbSqlErr, "failed to get all 3d model list") } mapAllmodel3d := make(map[int64]gmodel.FsProductModel3d) optionTemplateIds := make([]int64, 0, len(allModel3dList)) lightIds := make([]int64, 0, len(allModel3dList)) for _, v := range allModel3dList { mapAllmodel3d[v.Id] = v optionTemplateIds = append(optionTemplateIds, *v.OptionTemplate) lightIds = append(lightIds, *v.Light) } //获取公共模板信息 optionTemplateList, err := l.svcCtx.AllModels.FsProductTemplateV2.FindAllByIdsWithoutStatus(l.ctx, optionTemplateIds, "id,material_img") if err != nil { logx.Error(err) return resp.SetStatusWithMessage(basic.CodeDbSqlErr, "failed to get option template list") } mapOptionTemplate := make(map[int64]gmodel.FsProductTemplateV2) for _, v := range optionTemplateList { mapOptionTemplate[v.Id] = v } //获取灯光信息 lightList, err := l.svcCtx.AllModels.FsProductModel3dLight.GetAllByIdsWithoutStatus(l.ctx, lightIds) if err != nil { logx.Error(err) return resp.SetStatusWithMessage(basic.CodeDbSqlErr, "failed to get light list") } mapLight := make(map[int64]gmodel.FsProductModel3dLight) for _, v := range lightList { mapLight[v.Id] = v } //材料尺寸模板 mapMaterialSizeTmp := make(map[string][]interface{}) //循环阶梯价计算 type MaterialSizePrice struct { Items []interface{} `json:"items"` MinPrice float64 `json:"min_price"` MaxPrice float64 `json:"max_price"` } mapMaterialSizePrice := make(map[string]*MaterialSizePrice) //循环处理组装模板信息 for _, tmp := range productTemplateList { model3dInfo, ok := mapAllmodel3d[*tmp.ModelId] if !ok { continue } //如果是配件信息就跳过,不返回 if *model3dInfo.Tag == constants.TAG_PARTS { continue } //未编辑模板信息的数据跳过 if tmp.TemplateInfo == nil || *tmp.TemplateInfo == "" { continue } //解码template info var templateInfoRsp map[string]interface{} if tmp.TemplateInfo != nil && *tmp.TemplateInfo != "" { if err = json.Unmarshal([]byte(*tmp.TemplateInfo), &templateInfoRsp); err != nil { logx.Error(err) return resp.SetStatusWithMessage(basic.CodeJsonErr, "failed to parse template info") } } if templateInfoRsp["cover"] != nil && templateInfoRsp["cover"].(string) != "" { cover := templateInfoRsp["cover"].(string) if req.Size >= 200 { coverArr := strings.Split(templateInfoRsp["cover"].(string), ".") cover = fmt.Sprintf("%s_%d.%s", coverArr[0], req.Size, coverArr[1]) } templateInfoRsp["cover"] = cover delete(templateInfoRsp, "isPublic") delete(templateInfoRsp, "name") } //解码模型数据 var modelInfoRsp map[string]interface{} if model3dInfo.ModelInfo != nil && *model3dInfo.ModelInfo != "" { if err = json.Unmarshal([]byte(*model3dInfo.ModelInfo), &modelInfoRsp); err != nil { logx.Error(err) return resp.SetStatusWithMessage(basic.CodeJsonErr, "failed to parse model info") } } modelInfoRsp["id"] = model3dInfo.Id //解码灯光数据 var lightInfoRsp interface{} lightInfo, ok := mapLight[*model3dInfo.Light] if ok && lightInfo.Info != nil && *lightInfo.Info != "" { if err = json.Unmarshal([]byte(*lightInfo.Info), &lightInfoRsp); err != nil { logx.Error(err) return resp.SetStatusWithMessage(basic.CodeJsonErr, "failed to parse light info") } } //配件备选项 modelPartIds, err := format.StrSlicToInt64Slice(strings.Split(*model3dInfo.PartList, ",")) if err != nil { logx.Error(err) return resp.SetStatusWithMessage(basic.CodeServiceErr, "failed to parse 3d model`s part_list") } partListRsp := make([]interface{}, 0, len(modelPartIds)) for _, partId := range modelPartIds { //判断配件信息是否正常 temModelInfo, ok := mapAllmodel3d[partId] if !ok || *temModelInfo.Status != 1 { continue } var thisInfo map[string]interface{} temBytes, _ := json.Marshal(temModelInfo) _ = json.Unmarshal(temBytes, &thisInfo) thisInfo["material_img"] = "" if *temModelInfo.OptionTemplate != 0 { if optionTemplateInfo, ok := mapOptionTemplate[*temModelInfo.OptionTemplate]; ok { thisInfo["material_img"] = *optionTemplateInfo.MaterialImg } } else { tmpv2, err := l.svcCtx.AllModels.FsProductTemplateV2.FindOneByModelId(l.ctx, partId) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { } else { logx.Error(err) return resp.SetStatusWithMessage(basic.CodeDbSqlErr, "failed to get product template") } } thisInfo["material_img"] = *tmpv2.MaterialImg } partListRsp = append(partListRsp, thisInfo) } tagName := "" if tagData, ok := mapTag[*tmp.Tag]; ok { tagName = *tagData.Title } //按照材质和尺寸来存放模板信息 mapMaterialSizeTmpKey := l.getMapMaterialSizeTmpKey(*productInfo.MaterialIds, *model3dInfo.SizeId) mapMaterialSizeTmp[mapMaterialSizeTmpKey] = append(mapMaterialSizeTmp[mapMaterialSizeTmpKey], map[string]interface{}{ "id": tmp.Id, "title": *tmp.Title, "templateData": templateInfoRsp, "modelData": modelInfoRsp, "lightData": lightInfoRsp, "partList": partListRsp, "tag_name": tagName, }) } //产品价格查询 productPriceList, err := l.svcCtx.AllModels.FsProductPrice.GetAllByProductIdStatus(l.ctx, productInfo.Id, 1) if err != nil { logx.Error(err) return resp.SetStatusWithMessage(basic.CodeDbSqlErr, "failed to get product price list") } for _, priceItem := range productPriceList { stepNumSlice, err := format.StrSlicToIntSlice(strings.Split(*priceItem.StepNum, ",")) if err != nil { logx.Error(err) return resp.SetStatusWithMessage(basic.CodeServiceErr, "split step num err") } stepPriceSlice, err := format.StrSlicToIntSlice(strings.Split(*priceItem.StepPrice, ",")) if err != nil { logx.Error(err) return resp.SetStatusWithMessage(basic.CodeServiceErr, "split step price err") } lenStepNum := len(stepNumSlice) lenStepPrice := len(stepPriceSlice) if lenStepNum == 0 { return resp.SetStatusWithMessage(basic.CodeServiceErr, "count of step num is empty") } for *priceItem.MinBuyNum < int64(stepNumSlice[lenStepNum-1]+5) { price := step_price.GetCentStepPrice(int(*priceItem.MinBuyNum), stepNumSlice, stepPriceSlice) mapMaterialSizePriceKey := l.getMapMaterialSizePriceKey(*priceItem.MaterialId, *priceItem.SizeId) minPriceStr := fmt.Sprintf("%.2f", float64(stepPriceSlice[lenStepPrice-1])/100) minPrice, _ := strconv.ParseFloat(minPriceStr, 64) maxPriceStr := fmt.Sprintf("%.2f", float64(stepPriceSlice[0])/100) maxPrice, _ := strconv.ParseFloat(maxPriceStr, 64) if _, ok := mapMaterialSizePrice[mapMaterialSizePriceKey]; ok { mapMaterialSizePrice[mapMaterialSizePriceKey].Items = append(mapMaterialSizePrice[mapMaterialSizePriceKey].Items, map[string]interface{}{ "num": *priceItem.MinBuyNum, "total_num": *priceItem.MinBuyNum * (*priceItem.EachBoxNum), "price": price, }) mapMaterialSizePrice[mapMaterialSizePriceKey].MinPrice = minPrice mapMaterialSizePrice[mapMaterialSizePriceKey].MaxPrice = maxPrice } else { items := map[string]interface{}{ "num": *priceItem.MinBuyNum, "total_num": *priceItem.MinBuyNum * (*priceItem.EachBoxNum), "price": price, } mapMaterialSizePrice[mapMaterialSizePriceKey] = &MaterialSizePrice{ Items: []interface{}{items}, MinPrice: minPrice, MaxPrice: maxPrice, } } *priceItem.MinBuyNum++ } } isLowRendering := false isRemoveBg := false var lastDesign interface{} if userinfo.UserId != 0 { //获取用户信息 user, err := l.svcCtx.AllModels.FsUser.FindUserById(l.ctx, userinfo.UserId) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return resp.SetStatusWithMessage(basic.CodeDbRecordNotFoundErr, "user info not found") } logx.Error(err) return resp.SetStatusWithMessage(basic.CodeDbSqlErr, "failed to get user info") } isLowRendering = *user.IsLowRendering > 0 isRemoveBg = *user.IsRemoveBg > 0 lastDesign = l.getLastDesign(user) } var renderDesign interface{} if req.HaveCloudRendering == true { renderDesign = l.getRenderDesign(req.ClientNo) } //查询是否是加密的(不太合理) encryptWebsetting, err := l.svcCtx.AllModels.FsWebSet.FindValueByKey(l.ctx, "is_encrypt") if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return resp.SetStatusWithMessage(basic.CodeDbRecordNotFoundErr, "web setting is_encrypt is not exists") } logx.Error(err) return resp.SetStatusWithMessage(basic.CodeDbSqlErr, "failed to get web setting") } //不加密 rspData := types.GetProductInfoRsp{ Id: productInfo.Id, Type: *productInfo.Type, Title: *productInfo.Title, IsEnv: *productInfo.IsProtection, IsMicro: *productInfo.IsMicrowave, TypeName: typeName, IsLowRendering: isLowRendering, IsRemoveBg: isRemoveBg, Materials: materials, Sizes: sizeListRsp, Templates: mapMaterialSizeTmp, Prices: mapMaterialSizePrice, LastDesign: lastDesign, RenderDesign: renderDesign, Colors: color_list.GetColor(), } if encryptWebsetting.Value == nil || *encryptWebsetting.Value == "0" { return resp.SetStatusWithMessage(basic.CodeOK, "success", rspData) } bytesData, _ := json.Marshal(rspData) enData, err := encryption_decryption.CBCEncrypt(string(bytesData)) if err != nil { logx.Error(err) return resp.SetStatusWithMessage(basic.CodeServiceErr, "failed to encryption response data") } return resp.SetStatusWithMessage(basic.CodeOK, "success", enData) } // 获取渲染设计 func (l *GetProductInfoLogic) getRenderDesign(clientNo string) interface{} { if clientNo == "" { return nil } renderDesign, err := l.svcCtx.AllModels.FsProductRenderDesign.FindOneRenderDesignByParams(l.ctx, gmodel.FindOneRenderDesignByParamsReq{ ClientNo: &clientNo, Fields: "id,info,material_id,optional_id,size_id,template_id", OrderBy: "`id` DESC", }) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil } logx.Error(err) return nil } var info interface{} if renderDesign.Info != nil && *renderDesign.Info != "" { if err = json.Unmarshal([]byte(*renderDesign.Info), &info); err != nil { logx.Error(err) return nil } } return map[string]interface{}{ "id": renderDesign.Id, "info": info, "material_id": *renderDesign.MaterialId, "optional_id": *renderDesign.OptionalId, "size_id": *renderDesign.SizeId, "template_id": *renderDesign.TemplateId, } } // 获取用户最新设计 func (l *GetProductInfoLogic) getLastDesign(userInfo gmodel.FsUser) interface{} { //查询用户最近下单成功的数据 orderInfo, err := l.svcCtx.AllModels.FsOrder.FindLastSuccessOneOrder(l.ctx, userInfo.Id, int64(constants.STATUS_NEW_NOT_PAY)) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil } logx.Error(err) return nil } //获取该订单相关设计信息 orderDetail, err := l.svcCtx.AllModels.FsOrderDetail.GetOneOrderDetailByOrderId(l.ctx, orderInfo.Id) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil } logx.Error(err) return nil } //获取设计模板详情,便于获得design_id orderDetailTemplate, err := l.svcCtx.AllModels.FsOrderDetailTemplate.FindOne(l.ctx, *orderDetail.OrderDetailTemplateId) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil } logx.Error(err) return nil } //若没打开了个性化渲染按钮或者最后一次设计不存在,则不返回该设计相关数据 if *userInfo.IsOpenRender != 1 || *orderDetailTemplate.DesignId <= 0 { return nil } //获取设计数据 productDesign, err := l.svcCtx.AllModels.FsProductDesign.FindOne(l.ctx, *orderDetailTemplate.DesignId, userInfo.Id) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil } logx.Error(err) return nil } var info interface{} if productDesign.Info != nil && *productDesign.Info != "" { if err := json.Unmarshal([]byte(*productDesign.Info), &info); err != nil { logx.Error(err) return nil } } var logoColor interface{} if productDesign.LogoColor != nil && *productDesign.LogoColor != "" { if err := json.Unmarshal([]byte(*productDesign.LogoColor), &logoColor); err != nil { logx.Error(err) return nil } } return map[string]interface{}{ "id": productDesign.Id, "info": info, "logo_color": logoColor, "material_id": *productDesign.MaterialId, "optional_id": *productDesign.OptionalId, "size_id": *productDesign.SizeId, } } // 获取按照材料跟尺寸分类的模板map key func (l *GetProductInfoLogic) getMapMaterialSizeTmpKey(materialIds string, sizeId int64) string { return fmt.Sprintf("%s_%d", materialIds, sizeId) } // 获取按照材料跟尺寸分类的价格map key func (l *GetProductInfoLogic) getMapMaterialSizePriceKey(materialId int64, sizeId int64) string { return fmt.Sprintf("%d_%d", materialId, sizeId) }