package service import ( "encoding/json" "fmt" "github.com/gin-gonic/gin" "github.com/gogf/gf/util/gconv" "github.com/guonaihong/gout" "github.com/syyongx/php2go" "go_sku_server/model" "go_sku_server/model/saveModel" "go_sku_server/pkg/common" "go_sku_server/pkg/config" "go_sku_server/pkg/e" "go_sku_server/pkg/logger" "go_sku_server/pkg/mysql" "go_sku_server/service/sorter" "reflect" "sort" "time" ) type LySaveService struct { } /** sku基本数据增加和修改 @author wangsong @param GoodsName string `json:"goods_name" binding:"required"` BrandName string `json:"brand_name" binding:"required"` SupplierId int `json:"supplier_id" binding:"required"` Moq int `json:"moq" binding:"required"`//起订量 Mpq int `json:"mpq" binding:"required"`//标准包装量 Canal int `json:"canal" `//渠道标签 BatchSn int `json:"batch_sn" `//批次 Stock int `json:"stock" `//库存 HkDeliveryTime string `json:"hk_delivery_time" `//香港货期 CnDeliveryTime string `json:"cn_delivery_time" `//大陆货期 LadderPrice string `json:"ladder_price" `//阶梯价钱 CpTime int `json:"cp_time" `//茂则成本添加时间 GoodsImages int `json:"goods_images" `//商品图片 GoodsSn int `json:"goods_sn" ` Encap int `json:"encap" ` Pdf int `json:"pdf" ` SpuBrief int `json:"spu_brief" ` Attrs int `json:"attrs" ` */ var skuCache SkuCacheService func (S *LySaveService) SaveSku(lySaveRequest saveModel.LySaveRequest, ctx *gin.Context) (error, string) { lySkuEntity := lySaveRequest.ToLySkuEntity() //sku实体 //供应商处理(处理后,将[新增/修改sku]所需供应商相关字段 赋值给 &lySkuEntity) err := S.supplierHandle(lySaveRequest.SupplierId, lySkuEntity.Canal, &lySkuEntity) if err != nil { return e.NewApiError("供应商处理失败" + err.Error()), "" } //阶梯价格处理 err = S.ladderPriceHandle(lySaveRequest.LadderPrice, &lySkuEntity) if err != nil { return e.NewApiError("阶梯价格处理失败" + err.Error()), "" } //上下架状态处理 S.statusHandle(&lySkuEntity) //spu处理(请求spu_server saveSpu接口,将spuId赋值给 &lySkuEntity) lySpuRequest := lySaveRequest.ToLySpuRequest() err = S.spuHandle(lySpuRequest, &lySkuEntity) if err != nil { return err, "" } //mongoDb 获取sku 唯一信息 err, mongoSkuInfo := skuCache.MongoGetOnlySkuInfo(lySkuEntity.SpuId, lySkuEntity.SupplierId, lySkuEntity.Moq, lySkuEntity.Encoded, lySkuEntity.Canal) if err != nil { return e.NewApiError("mongo 获取sku唯一信息失败" + err.Error()), "" } if mongoSkuInfo.GoodsId != 0 { //更新 lySkuEntity.GoodsId = gconv.String(mongoSkuInfo.GoodsId) err := S.update(lySkuEntity, lySaveRequest) if err != nil { return e.NewApiError("skuSave 修改sku失败" + err.Error()), "" } return nil, lySkuEntity.GoodsId } else { //插入sku err, skuId := S.insert(lySkuEntity, lySaveRequest) if err != nil { return e.NewApiError("skuSave insert sku失败" + err.Error()), "" } logger.Select("lysku_save").Info("新增完成:brand_name:" + lySaveRequest.BrandName + ";sku_id:" + skuId + ";good_name:" + lySaveRequest.GoodsName) return nil, skuId } } /** 供应商处理流程(内部方法) 1.判断供应商是否存在,是否被禁用 2.如果供应商为专卖,参数 cannal(供应商编码)是必传的 3.得到 新增sku所需的 TypeId 和 Encoded(渠道员内部编码) */ func (S *LySaveService) supplierHandle(SupplierId int, Canal string, lySkuEntity *saveModel.LySkuEntity) error { lySupplier := LySupplier{} has, err, supplierInfo := lySupplier.GetSupplirInfo(SupplierId) if err != nil { return e.NewApiError("获取基石供应商详情失败" + err.Error()) } if !has { fmt.Sprintf("没有在基石获取到此供应商,供应商ID为:%s", SupplierId) } if supplierInfo.Status != 1 { return e.NewApiError("供应商被禁用") } //专卖处理(供应商系统) if supplierInfo.TypeId == 2 { if Canal == "" { return e.NewApiError("专卖 cannal参数为必传") } has, err, poolSupplierInfo := lySupplier.GetPoolSupplirInfo(Canal) if err != nil { return e.NewApiError("获取联营供应商详情失败" + err.Error()) } if !has { errmsg := fmt.Sprintf("没有获取到渠道%s的内部编码", Canal) return e.NewApiError(errmsg) } lySkuEntity.Encoded = poolSupplierInfo.ChannelUid //渠道开发员 内部编码赋值给sku } lySkuEntity.GoodsType = supplierInfo.TypeId return nil } /** 阶梯价格处理 1.对阶梯价格排序 2.获取 ladderPrice 3.获取最便宜的价格 */ func (S *LySaveService) ladderPriceHandle(ladderPrice []model.LadderPrice, lySkuEntity *saveModel.LySkuEntity) error { num := len(ladderPrice) if len(ladderPrice) > 0 { //有传阶梯价 //获取最便宜的价格 sort.Sort(sorter.LadderPriceSorter(ladderPrice)) //按照购买数量,小到大排序,数量越大优惠越多,所以[num-1]是最便宜的 if ladderPrice[num-1].PriceUs >= 0 { //如果有最低美元价,就直接读最低美元价 lySkuEntity.SinglePrice = ladderPrice[num-1].PriceUs //获取最便宜的价钱 } else { //没有美元价,就将最低人民币折算成美元 lySkuEntity.SinglePrice = php2go.Round(ladderPrice[num-1].PriceCn / 6.8) } //将阶梯json字符串给到sku实体里,sku只需要字符串阶梯 存数据库 priceBytes, err := json.Marshal(ladderPrice) if err != nil { return e.NewApiError("阶梯价格json化报错" + err.Error()) } else { lySkuEntity.LadderPrice = string(priceBytes) } //状态处理 } return nil } /** spu处理(内部方法) 1.调用spu_server saveSpu接口,获取到spuId 返回参数 | 参数 | 示例值 | 参数描述 | | :-------- | :----- | :---- | | errorcode | 10001 | 错误码 0代表成功响应 | | errmsg | | 错误提示 | | data | | 返回的data | */ func (S *LySaveService) spuHandle(lySpuRequest saveModel.LySpuRequest, lySkuEntity *saveModel.LySkuEntity) error { lySpuResponse := saveModel.LySpuResponse{} err := gout.POST(config.Get("spu_server.api_domain").String() + "/saveSpu") /*.Debug(true)*/ .SetJSON(&lySpuRequest).BindJSON(&lySpuResponse).Do() //err:=gout.POST("http://192.168.1.237:8005/saveSpu").Debug(false).SetJSON(&lySpuRequest).BindJSON(&lySpuResponse).Do() if err != nil { return e.NewApiError("调用spu server saveSpu接口报错" + err.Error()) } if lySpuResponse.ErrorCode == 0 { lySkuEntity.SpuId = lySpuResponse.Data.SpuId } else { return e.NewApiError("sku Throw out" + lySpuResponse.ErrMsg) } return nil } /** 上下架状态处理 处理所需字段 moq 起订量 stock 库存 ladder_price 阶梯价 处理流程 满足 字段 moq stock ladder_price都有值 ,并且 moq < =stock ; 那么 sku状态 就是 审核通过(status=1) 否则 就是下架状态(status=3) */ func (S *LySaveService) statusHandle(lySkuEntity *saveModel.LySkuEntity) { lySkuEntity.GoodsStatus = 1 if lySkuEntity.LadderPrice == "" { lySkuEntity.GoodsStatus = 3 //下架 } if lySkuEntity.Moq == 0 || lySkuEntity.Stock == 0 || lySkuEntity.Moq > lySkuEntity.Stock { lySkuEntity.GoodsStatus = 3 //下架 } } //更新 func (S *LySaveService) update(lySkuEntity saveModel.LySkuEntity, lySaveRequest saveModel.LySaveRequest) error { db, table := common.ResolveSpu(lySkuEntity.GoodsId) //解析skuID,返回对应的表和库,应该在生成skuID的时候返回表和库 //修改数据库 affected, err := mysql.Conn(db).Table(table).Where("goods_id=?", lySkuEntity.GoodsId).Update(lySkuEntity) if err != nil { return e.NewApiError("修改sku mysql数据失败 msg:" + err.Error()) } if affected <= 0 { //没有什么可以修改的,直接完成就好了 fmt.Println("没什么可以修改的") logger.Select("lysku_save").Info("不需要修改:brand_name:" + lySaveRequest.BrandName + ";sku_id:" + lySkuEntity.GoodsId + ";good_name:" + lySaveRequest.GoodsName) return nil } else { err = S.skuCacheSave(false, lySkuEntity.GoodsId, lySkuEntity, lySaveRequest) if err != nil { logger.Select("lysku_save").Info("修改完成:brand_name:" + lySaveRequest.BrandName + ";sku_id:" + lySkuEntity.GoodsId + ";good_name:" + lySaveRequest.GoodsName) return e.NewApiError("缓存处理失败" + err.Error()) } } return nil } //插入 func (S *LySaveService) insert(lySkuEntity saveModel.LySkuEntity, lySaveRequest saveModel.LySaveRequest) (error, string) { skuId := common.StructureNumber("sku") //生成skuID lySkuEntity.GoodsId = skuId db, table := common.ResolveSpu(lySkuEntity.GoodsId) //解析skuID,返回对应的表和库,应该在生成skuID的时候返回表和库 //插入到数据库 _, err := mysql.Conn(db).Table(table).Insert(lySkuEntity) if err != nil { return e.NewApiError("sku插入数据库失败" + err.Error()), "" } err = S.skuCacheSave(true, skuId, lySkuEntity, lySaveRequest) if err != nil { return e.NewApiError("缓存处理失败" + err.Error()), "" } return nil, skuId } /** 更新完sku mysql数据库后,需要对sku相关的 mongo 和redis进行处理 1.更新 redis skuinfo (1.新增直接插入,2.修改需要将老的redis sku数据合并新的) 2.插入mongoDB sku唯一(修改不需要动) 3.推送(就是插入redis list update_list_sku) */ func (S *LySaveService) skuCacheSave(isAdd bool, skuId string, lySkuEntity saveModel.LySkuEntity, lySaveRequest saveModel.LySaveRequest) error { //插入redis,redis修改和新增代码量有点大,单独封装一个函数 err := S.saveRedisSkuInfo(isAdd, skuId, lySkuEntity, lySaveRequest) if err != nil { return e.NewApiError("插入redis失败" + err.Error()) } if isAdd { //插入到mongo skuMongo := lySkuEntity.ToMongoSku() err = skuCache.MongoInsertOnlySkuInfo(skuMongo) if err != nil { return e.NewApiError("插入redis失败" + err.Error()) } } //实时推送(插入到 redis list update_list_sku ) err = skuCache.RedisPushEsList(skuId) if err != nil { return e.NewApiError("实时推送失败" + err.Error()) } return nil } /** 修改redis hash key为 sku的数据(其实就是插入) 为什么要合并?:有其他地方会存这个redis,会出现 len不是固定的,里面的字段类型也会不一样,要分开处理 1.新增 新增,比较简单,就直接组装SkuRedis结构就行 2.修改 读取之前(老的)的redis数据,跟新的redisSku合并 合并规则:需要修改的,覆盖之前的,不需要修改的保持原样 */ func (S *LySaveService) saveRedisSkuInfo(isAdd bool, skuId string, lySkuEntity saveModel.LySkuEntity, lySaveRequest saveModel.LySaveRequest) error { redisSkuInfo := lySkuEntity.ToRedisSku() //组装需要更新的redisSku数据 redisSkuInfo.GoodsImages = lySaveRequest.GoodsImages redisSkuInfo.Canal = lySaveRequest.Canal redisSkuInfo.UpdateTime = int(time.Now().Unix()) redisSkuInfo.LadderPrice = lySaveRequest.LadderPrice //LadderPrice 如果已经是字符串,json处理会有斜划线,所以再赋值一次 if isAdd { oldByte, err := json.Marshal(redisSkuInfo) if err != nil { return nil } return skuCache.RedisInsertSkuInfo(skuId, string(oldByte)) } else { //将老的redis的数据转成map var oldSkuInfoMap map[string]interface{} oldSkuInfoStr, err := skuCache.RedisGetSkuInfo(skuId) //获取老的sku数据 json.Unmarshal([]byte(oldSkuInfoStr), &oldSkuInfoMap) if err != nil { return nil } //is_expire 给清理掉 if _, ok := oldSkuInfoMap["is_expire"]; ok { delete(oldSkuInfoMap, "is_expire") } //合并,得到新的map err, newMap := S.structMergeMap(redisSkuInfo, oldSkuInfoMap) if err != nil { return e.NewApiError("结构和map合并失败" + err.Error()) } //转化成json字符串 newMapBytes, err := json.Marshal(newMap) if err != nil { return err } //插入redisSku err = skuCache.RedisInsertSkuInfo(skuId, string(newMapBytes)) if err != nil { return nil } } return nil } /** struct 跟map Map合并,返回一个新的map(后续可以整理成公共函数) */ func (S *LySaveService) structMergeMap(redisSkuInfo model.LySkuRedisInfo, oldSkuInfoMap map[string]interface{}) (error, map[string]interface{}) { //将新的redisSku数据struct 和 老的redis的数据map 合并成map并转换成json返回 GetValue := func(fieldValue reflect.Value) interface{} { switch fieldValue.Kind() { case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int, reflect.Int64: return fieldValue.Int() case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint, reflect.Uint64: return fieldValue.Uint() case reflect.Float32, reflect.Float64: return fieldValue.Float() case reflect.String: return fieldValue.String() case reflect.Bool: return fieldValue.Bool() default: return "other" //case reflect.Slice,: } } //组装需要更新的redisSku数据 struct var newMap = make(map[string]interface{}) v := reflect.ValueOf(redisSkuInfo) t := reflect.TypeOf(redisSkuInfo) for i := 0; i < v.NumField(); i++ { name := t.Field(i).Tag.Get("json") fieldValue := v.Field(i) //name:=v.Type().Name() _, ok := oldSkuInfoMap[name] if v.Field(i).IsZero() && ok { // struct 有些字段为0值,还是要用老的redis的数据 newMap[name] = oldSkuInfoMap[name] } else { if name == "ladder_price" { newMap[name] = redisSkuInfo.LadderPrice //ladder_price 单独处理 } else { mapValue := GetValue(fieldValue) newMap[name] = mapValue } } delete(oldSkuInfoMap, name) //删除老的数据已经合并的字段 } //将老数据独有的字段,放进newMap if len(oldSkuInfoMap) > 0 { for oldk, oldv := range oldSkuInfoMap { newMap[oldk] = oldv } } return nil, newMap } /** 修改 先写的SkuSave ,SkuEdit 基本是调用 SkuSave 之前写好的方法 */ func (S *LySaveService) SkuEdit(lyEditRequest saveModel.LyEditRequest) (error, string) { lySkuEntity := lyEditRequest.ToLySkuEntity() //sku实体 lySkuEntity.GoodsId = lyEditRequest.GoodsId lySaveRequest := lyEditRequest.ToLySaveRequest() //转换成lySaveRequest 因为公用 savesku封装的的方法,公用方法需要传lySaveRequest(后续参数改成接口) //阶梯价格处理 err := S.ladderPriceHandle(lySaveRequest.LadderPrice, &lySkuEntity) if err != nil { return e.NewApiError("SkuEdit 阶梯价格处理失败" + err.Error()), "" } oldRedisStr, err := skuCache.RedisGetSkuInfo(lyEditRequest.GoodsId) if err != nil { return e.NewApiError("修改sku:查询redis sku失败" + err.Error()), "" } if oldRedisStr == "" { //判断是否存在 return e.NewApiError("sku不存在,skuID:" + lyEditRequest.GoodsId + err.Error()), "" } err = S.update(lySkuEntity, lySaveRequest) if err != nil { return e.NewApiError("SkuEdit 修改sku失败" + err.Error()), "" } return nil, lySkuEntity.GoodsId }