最近写了一个在测试环境吞吐量200的下单接口。跟最开始的10几个并发比起来有些许进步。下面将以总结的心态且从并发优化开始到最近一次上线的接口为基础记录一下,以备后用。
6月初的时候有个下单接口,需要开发,叫做折扣促销,抛弃了原来的一套下单流程(牵涉部门比较多,流程长)。活动有并发需求,于是在开发完下单接口之后压测了一下。结果是10几个吞吐量,尴尬。当时,下单接口开发语言是PHP,TCP调用了一个积分扣减接口,同步修改了商品的库存数据。
之后换成了GO语言重构该接口,积分扣减和库存扣减由协程处理。库存修改先是取redis数据然后及时更新到mysql,mysql加了悲观锁来解决并发情况下的库存扣减异常。在生产环境(三台服务器)的压测结果是200多的吞吐量,实际业务的并发是10个以内,这块上线之后没有出现问题。
func SalesOrder(c *gin.Context) { claims, _ := c.Get("claims") cVal, _ := claims.(*middleware.CustomClaims) productId := c.PostForm("product_id") activityId := c.PostForm("activity_id") //收货人信息 name := c.PostForm("name") phone := c.PostForm("phone") province := c.PostForm("province") city := c.PostForm("city") area := c.PostForm("area") address := c.PostForm("address") rq := c.PostForm("rq") cookieId := c.PostForm("cookieId") payType := c.PostForm("pay_type") SpecialPayType, _ := utils.StringToInt64(payType) qty := c.PostForm("qty") SpecialQty, _ := utils.StringToInt64(qty) SpecialQty_ := utils.StringToFloat64(qty) //推广参数 referrerSource := c.PostForm("referrer") idCard := c.PostForm("id_card") ownForm := c.PostForm("ownForm") referrer := "" if len(referrerSource) > 0 { keyStr := base.Config().GetString("settings.param.shen_que_bump") referrer = utils.AesDecrypt(referrerSource, keyStr) } var response serializer.PressureMeasurementResponse var OrderNo = "" var invitationId int64 var receiverType string var amountIntegral float64 var amountPrice float64 var usableQty int64 var lockedQty int64 var soldQty int64 var SpecialProductId int64 var uid = cVal.Uid var storeId = cVal.StoreId var resParams = "" var itemParams = "" response.Code = "0" response.Msg = "失败!" if len(productId) == 0 { response.Msg = "商品编号是必填项!" c.JSON(http.StatusOK, response) return } else if len(activityId) == 0 { response.Msg = "活动id是必填项!" c.JSON(http.StatusOK, response) return } else { //检查活动是否存在 SpecialActivityId, _ := utils.StringToInt64(activityId) activity, err := models.GetActivityInfo(SpecialActivityId) if err != nil { response.Msg = ErrorResponse(err).Msg c.JSON(http.StatusOK, response) return } else { //活动存在 商品是否存 SpecialProductId, _ = utils.StringToInt64(productId) product, err := models.GetProductInfo(SpecialProductId) //TODO:err if err != nil { response.Msg = "商品信息查询失败!" c.JSON(http.StatusOK, response) return } if product.IsDel { response.Msg = "商品已删除!" c.JSON(http.StatusOK, response) return } usableQty, err = models.GetProductUsableQtyInfo(SpecialProductId) //TODO:err if err != nil { response.Msg = "商品可用库存查询失败!" c.JSON(http.StatusOK, response) return } //推送订单信息给推广中心 add by xjn 2021.07.30 productSub, err := models.GetProductInfoSub(SpecialProductId) //TODO:err if err != nil { response.Msg = "商品信息查询失败!" c.JSON(http.StatusOK, response) return } else { if productSub.SalesStatus == 2 { product.Discount = 1 } if productSub.DiscountModeType == 1 || productSub.DiscountModeType == 2 { //计算商品价格 switch productSub.DiscountModeType { case 1: amountIntegral = product.Integral * product.Discount * SpecialQty_ amountPrice = 0.00 break case 2: amountIntegral = 0.00 amountPrice = product.Price * product.Discount * SpecialQty_ break } } else { switch activity.DiscountModeType { case 1: amountIntegral = product.Integral * product.Discount * SpecialQty_ amountPrice = 0.00 break case 2: amountIntegral = 0.00 amountPrice = product.Price * product.Discount * SpecialQty_ break } } lockedQty, err = models.GetProductLockedQtyInfo(SpecialProductId) //TODO:err if err != nil { response.Msg = "商品冻结库存查询失败!" c.JSON(http.StatusOK, response) return } soldQty, err = models.GetProductSoldQtyInfo(SpecialProductId) //TODO:err if err != nil { response.Msg = "商品已售库存查询失败!" c.JSON(http.StatusOK, response) return } //判断收货人信息 fudeStoreId := base.Config().GetString("settings.param.store_info.fdsm_store_id") specialStoreId, _ := utils.StringToInt64(fudeStoreId) if storeId == specialStoreId { switch product.ReceivingWay { case 0: if len(name) == 0 || len(phone) == 0 { response.Msg = "收货方式为实物,收货人姓名,手机号,为必填项!" c.JSON(http.StatusOK, response) return } receiverType = "physics" break case 1: if len(name) == 0 || len(phone) == 0 { response.Msg = "收货方式为虚拟,收货人姓名,手机号为必填项!" c.JSON(http.StatusOK, response) return } receiverType = "virtual" break case 2: receiverType = "virtual" break case 3: receiverType = "virtual" break case 4: receiverType = "virtual" break } } else { type itemData []string var item itemData json.Unmarshal([]byte(productSub.OrderFormItems), &item) for i := range item { if item[i] == "recipients" && len(name) <= 0 { response.Msg = "收货人不能为空!" c.JSON(http.StatusOK, response) return } else if item[i] == "tel" && len(phone) <= 0 { response.Msg = "收货人电话不能为空!" c.JSON(http.StatusOK, response) return } else if item[i] == "id_card" && len(idCard) <= 0 { response.Msg = "身份证不能为空!" c.JSON(http.StatusOK, response) return } else if item[i] == "totalAdress" && (len(province) <= 0 || len(city) <= 0 || len(area) <= 0) { response.Msg = "收货地址,省市区县不能为空!" c.JSON(http.StatusOK, response) return } else if item[i] == "address" && len(address) <= 0 { response.Msg = "收货地址,详细地址不能为空!" c.JSON(http.StatusOK, response) return } else if item[i] == "ownForm" && len(ownForm) <= 0 { response.Msg = "备注不能为空!" c.JSON(http.StatusOK, response) return } } } currentTimestamp := time.Now().Unix() startTimestamp := activity.StartTime.Unix() endTimestamp := activity.EndTime.Unix() //检查权益是否已兑换 orderedNumber, _ := models.CheckOrderNumber(utils.Int64ToString(uid), productId, activityId) fudeStoreId = base.Config().GetString("settings.param.store_info.fdsm_store_id") if err != nil { response.Msg = ErrorResponse(err).Msg c.JSON(http.StatusOK, response) return } else if product.ActivityID != activity.ID { response.Msg = "商品与活动不匹配!" c.JSON(http.StatusOK, response) return } else if activity.StoreID != storeId { response.Msg = "商品与店铺不匹配!" c.JSON(http.StatusOK, response) return } else if usableQty < 1 { response.Msg = "商品可用库存不足!" c.JSON(http.StatusOK, response) return } else if lockedQty < 0 { response.Msg = "商品冻结库存异常,不能下单" c.JSON(http.StatusOK, response) return } else if soldQty < 0 { response.Msg = "商品已售库存异常,不能下单" c.JSON(http.StatusOK, response) return } else if !(currentTimestamp >= startTimestamp && currentTimestamp <= endTimestamp) { response.Msg = "活动已结束!" c.JSON(http.StatusOK, response) return } else if len(fudeStoreId) > 0 && orderedNumber > 0 && fudeStoreId == utils.Int64ToString(storeId) && (product.ReceivingWay != 3 && product.ReceivingWay != 4) { response.Msg = "每项权益最多只能兑换一次!" c.JSON(http.StatusOK, response) return } else { //邀约活动查询 invitation, err := models.GetStoreActivityInvitationTool() if err != nil { invitationId = 0 } else { invitationId = invitation.ID } //操作缓存可用库存减,锁定库存加 result := HandleProductQty(SpecialProductId) if result { //操作mysql可用库存减,锁定库存加 e := models.UpdateProductUsableQtyAndLockedQty(SpecialProductId, 1, OrderNo) if e != nil { //缓存数据恢复 HandleProductQtyFail(SpecialProductId) response.Msg = "可用库存和冻结库存操作失败!" c.JSON(http.StatusOK, response) return } else { OrderNo = utils.GenerateOrderCode() //保存订单 order := models.XmallActivityOrder{ InvitationActivityID: invitationId, ActivityID: activity.ID, OrderNo: OrderNo, UId: uid, ProductID: SpecialProductId, PayType: SpecialPayType, ReceiveType: product.ReceivingWay, AmountPrice: amountPrice, AmountIntegral: amountIntegral, ProductPrice: product.Price, ProductIntegral: product.Integral, Discount: product.Discount, Qty: SpecialQty, Status: 0, CreatedAt: time.Now(), Rest: "{\"receiver_name\":\"" + name + "\",\"receiver_phone\":\"" + phone + "\",\"province\":\"" + province + "\",\"city\":\"" + city + "\",\"area\":\"" + area + "\",\"address\":\"" + address + "\",\"id_card\":\"" + idCard + "\",\"ownForm\":\"" + ownForm + "\"}", } store, err := models.GetStoreSettingInfo(storeId) if err != nil && (activity.DiscountModeType == 1 || productSub.DiscountModeType == 1) { //店铺配置缺失,需要返还库存,add by xjn 2021.07.26 折扣模式是积分才需要设置积分现金汇率 HandleProductQtyFail(SpecialProductId) err := models.UpdateProductUsableQtyAndLockedQtyFail(SpecialProductId, 1, OrderNo) if err != nil { log.Info("现金支付失败,库存维护失败") } else { log.Info("现金支付失败,库存维护成功") } response.Msg = "店铺配置信息不存在!" c.JSON(http.StatusOK, response) return } var ratio float64 if activity.DiscountModeType == 1 || productSub.DiscountModeType == 1 { //折扣模式是积分才需要取积分现金比计算商品价格 var ratioValue []string _ = json.Unmarshal([]byte(store.Value), &ratioValue) ratio1, _ := strconv.Atoi(ratioValue[0]) ratio2, _ := strconv.Atoi(ratioValue[1]) ratio = float64(ratio1) / float64(ratio2) } else { ratio = 0 } productPrice := product.Price/100 + product.Integral*product.Discount*ratio //构造推送数据 var normalParams serializer.NormalForNormalParams normalParams.TotalPrice = utils.Float64ToString(productPrice * SpecialQty_) normalParams.GoodsPrice = utils.Float64ToString(productPrice) normalParams.PayMoney = utils.Float64ToString(productPrice * SpecialQty_) normalParams.Freight = "0.00" normalParams.ReceiverType = receiverType normalParams.ReceiverName = name normalParams.ReceiverPhone = phone normalParams.ReceiverProvince = province normalParams.ReceiverCity = city normalParams.ReceiverArea = area normalParams.ReceiverAddress = address normalParams.Products = make([]serializer.NormalForNormalOfProducts, 1) normalParams.Products[0].ProId = utils.Int64ToString(product.ID) normalParams.Products[0].ProName = product.Title normalParams.Products[0].Price = utils.Float64ToString(productPrice) normalParams.Products[0].Count = qty normalParams.Products[0].Freight = "0" normalParams.Products[0].CustomerProductIdentifier = product.CustomerProductIdentifier resParams_, err := json.Marshal(normalParams) resParams = string(resParams_) if err != nil { response.Msg = ErrorResponse(err).Msg c.JSON(http.StatusOK, response) return } itemParams_, err := json.Marshal(normalParams.Products) itemParams = string(itemParams_) if err != nil { response.Msg = ErrorResponse(err).Msg c.JSON(http.StatusOK, response) return } orderId, err := models.CreateSalesOrder(order) if err != nil { response.Msg = ErrorResponse(err).Msg c.JSON(http.StatusOK, response) return } else { var orderSub models.XmallActivityOrderSub if len(referrer) > 0 { //保存子订单 orderSub = models.XmallActivityOrderSub{ OrderId: orderId, Referrer: referrer, AllotableProfit: productSub.AllotableProfit * SpecialQty_, CreatedAt: time.Now(), } } else { //保存子订单 orderSub = models.XmallActivityOrderSub{ OrderId: orderId, AllotableProfit: productSub.AllotableProfit * SpecialQty_, CreatedAt: time.Now(), } } subOrderId, err := models.CreateSalesOrderSub(orderSub) if err != nil { response.Msg = ErrorResponse(err).Msg c.JSON(http.StatusOK, response) return } logs.Info("子订单创建成功,订单编号:" + utils.Int64ToString(subOrderId)) response.Code = "1" response.Msg = "参与活动成功" } } } else { response.Msg = "商品可用库存不足或者冻结库存异常!" c.JSON(http.StatusOK, response) return } } if payType == "1" { var order services.PayParam order.Rq = rq order.Uid = uid order.StoreId = storeId order.OrderCode = OrderNo order.TotalPrice = amountPrice / 100 order.Mobile = phone order.CookieId = cookieId order.ProductId = productId order.AllotProfit = productSub.AllotableProfit / 100 res, url, time := order.DoPayPrepare() if res == "ok" { //插入延时队列(配置delay_rbmq_xmall_order) var data = models.AutoCancelOrder{ IsSuccess: "fail", OrderNo: OrderNo, } jsonStr, err := json.Marshal(data) if err != nil { return } err = base.NewSQQueue("delay_rbmq_xmall_order").PublishDelay(string(jsonStr), int(time)) if err != nil { logs.Errorf("订单自动取消推入延迟队列失败,data:%s", data) } response.Data = url response.BizId = OrderNo //add by xjn 2021.07.22 var h5RedirectType string h5RedirectType = getStoreSettingByKey(activity.StoreID, "h5_redirect_type") response.RedirectType = h5RedirectType c.JSON(http.StatusOK, response) return } else { response.Msg = url c.JSON(http.StatusOK, response) return } } else { //协程 go func(OrderNo string, productId int64, resParams string, itemParams string) { msg, errMsg := XmallOrderSubIntegral(uid, storeId, OrderNo, utils.Float64ToString(amountIntegral), "折扣兑换活动", resParams) if msg == "ok" { //修改缓存中的库存数据 lockedQty, err = models.GetProductLockedQtyInfo(SpecialProductId) //TODO:err if err != nil { log.Info("商品冻结库存查询失败,订单状态维护失败!") return } else { cacheKeyLockedQty := "storeActivityProductInfo" + utils.Int64ToString(productId) + "LockedQty" count, err := base.SQRds().Decr(cacheKeyLockedQty).Result() //冻结库存减 if err != nil || count < 0 { base.SQRds().Incr(cacheKeyLockedQty) //冻结出现负数,扣减冻结库存操作需要回滚 add by xjn 2021.07.27 log.Info("扣减积分成功,扣减冻结库存失败,订单状态维护失败!") return } else { soldQty, err = models.GetProductSoldQtyInfo(SpecialProductId) //TODO:err if err != nil { log.Info("商品已售库存查询失败,订单状态维护失败!") return } else { cacheKeySoldQty := "storeActivityProductInfo" + utils.Int64ToString(productId) + "SoldQty" count3, errs := base.SQRds().Incr(cacheKeySoldQty).Result() //已售库存加 if errs != nil || count3 < 0 { log.Info("商品已售库存新增失败,订单状态维护失败!") return } //已兑换加缓存 cacheKey := "storeActivityOrder" + utils.Int64ToString(uid) + utils.Int64ToString(productId) + activityId if err := base.SQRds().Set(cacheKey, int(1), time.Second*60*60*7).Err(); err != nil { log.Info("添加已兑校验缓存|" + err.Error() + cacheKey) return } else { log.Info("添加已兑校验成功|" + cacheKey) } //扣减积分成功 数据持久化到mysql 锁定减 已售加 err := HandleProductQtySuccess(SpecialProductId, 1, OrderNo) if err != nil { log.Info("积分扣减成功库存维护失败(mysql):" + ErrorResponse(err).Msg) return } else { log.Info("积分扣减成功库存维护成功(mysql)!") } _, err = models.EditStatus(1, OrderNo) if err != nil { log.Info("积分扣减成功订单状态维护失败!") return } else { msg, errMsg := XmallOrderChange(uid, storeId, OrderNo, utils.Float64ToString(amountIntegral), resParams, 1, itemParams) if msg == "ok" { log.Info("修改订单状态消息推送成功!") } else { log.Info("修改订单状态消息推送失败:" + errMsg) } log.Info("积分扣减成功订单状态维护成功!") strInt64 := strconv.FormatInt(storeId, 10) id16, _ := strconv.Atoi(strInt64) orderList := models.GbStoreOrderList{ OrderCode: OrderNo, OrderType: 2, OrderName: product.Title, CreatedAt: time.Now().Unix(), UpdatedAt: time.Now().Unix(), UserID: uid, StoreID: id16, OrderImg: product.Banner, } err = models.CreateOrder(orderList) if err != nil { log.Info("订单同步执行失败") } else { log.Info("订单同步执行成功") } } } //推送订单信息给推广中心 add by xjn 2021.07.30 productSub, err := models.GetProductInfoSub(SpecialProductId) //TODO:err if err != nil { response.Msg = "商品子表查询失败!" c.JSON(http.StatusOK, response) return } order, err := models.GetOrderInfo(OrderNo) if err != nil { log.Info("订单信息获取失败!") return } specialOrderId := utils.Int64ToString(order.ID) orderSub, err := models.GetOrderInfoSub(specialOrderId) if err != nil { log.Info("订单子表信息获取失败!") return } var orderData = models.OrderNotify{ StoreId: storeId, Uid: uid, Type: "order", OrderNo: OrderNo, PayPrice: utils.Float64ToString(amountPrice), IntegralPrice: utils.Float64ToString(amountIntegral), ProfitPrice: utils.Float64ToString(productSub.AllotableProfit), ProId: utils.Int64ToString(productId), Count: int(SpecialQty), PromotionParams: referrer, Status: 1, OrderTime: order.CreatedAt.Format("2006-01-02 15:04:05"), BonusAmount: utils.Float64ToString(orderSub.AllotableProfit), } jsonStrSub, err := json.Marshal(orderData) logs.Info("推送给推广系统的订单数据(积分)" + string(jsonStrSub)) res01 := pushOrderNotifyToPromotionSystem(string(jsonStrSub)) if !res01 { logs.Errorf("支付成功,订单信息推送失败!") } } } } else { //扣减积分失败 _, err = models.EditStatus(3, OrderNo) if err != nil { log.Info("订单状态修改失败!") } res := HandleProductQtyFail(SpecialProductId) if res { err := models.UpdateProductUsableQtyAndLockedQtyFail(SpecialProductId, 1, OrderNo) if err != nil { log.Info("积分扣减失败,库存维护失败") } else { log.Info("积分扣减失败,库存维护成功") } } log.Info("积分开发平台调用错误:" + errMsg) } }(OrderNo, SpecialProductId, resParams, itemParams) //最后成功返回 获得店铺积分比例 add by xjn 2021.06.28 var h5RedirectType string h5RedirectType, err = models.GetStoreSettingInfoByKey(activity.StoreID, "h5_redirect_type") if err != nil { h5RedirectType = "1" //店铺配置不存在默认值是 1 跳到首页 } response.Data = "{\"redirect_type\":" + h5RedirectType + "}" c.JSON(http.StatusOK, response) return } } } } }
最近又抛弃了之前的折扣促销那套流程,因为业务需要购物车,于是又一次重构了下单并用了领域模型。各领域负责自己的业务和数据,比如下单只操作订单相关的表,商品数据查询支付唤起都由其他领域提供服务。换句话说就是用了大量的缓存。缓存了商品基本信息,sku信息等等。
func (o orderService) SaveOrder(dto service.OrderParamsInDto) (item service.GbStoreOrderMainOutDto, err error) { domain := orderDomain{} obj, err := domain.saveOrder(dto) if err != nil { return item, err } var orderMsgInfo service.OrderMsgInfo orderMsgInfo.MainOrderNo = obj.OrderMainNo go func(m service.OrderMsgInfo) { orderJsonStr, err := json.Marshal(m) if err != nil { log.Errorf("订单创建,推送创建信息失败,step 1,data:%s", err) return } err = base.NewSQQueue("sub_order_create").PublishSub(string(orderJsonStr)) if err != nil { log.Errorf("订单创建,推送创建信息失败,step 2,data:%s", err) return } log.Infof("订单创建,推送创建信息成功,data:%s", m) return }(orderMsgInfo) userServices := service.GetUserService() thirdPartyAccount := userServices.GetThirdPartyAccountByStoreIdUid(dto.StoreId, dto.UId) payServices := service.GetPayService() var payDto service.PayDTO payDto.Rq = dto.RQ payDto.CookieId = dto.CookieId payDto.Uid = dto.UId payDto.Mobile = dto.Phone payDto.StoreId = dto.StoreId payDto.StoreCode = dto.StoreCode payDto.OrderCode = obj.OrderCode payDto.TotalPrice = obj.TotalPrice payDto.AllotProfit = obj.AllotProfit payDto.ThirdPartyAccount = thirdPartyAccount payDto.H5RedirectType = "2" payDto.ProductInfo = obj.PayProductInfos payItem, err := payServices.GetPayPrepare(&payDto) if err != nil { return item, err } item.PaymentInfo = payItem item.UId = obj.UId item.StoreID = obj.StoreID item.OrderMainNo = obj.OrderMainNo //推送订单自动取消信息开始 var autoCancelDto service.AutoCancelOrderInDto autoCancelDto.IsSuccess = "fail" autoCancelDto.OrderNo = obj.OrderMainNo delayTimeSecond := int(payItem.CloseTime) / 1000 go func(delayTimeSecond int, autoCancelDto service.AutoCancelOrderInDto) { jsonStr, err := json.Marshal(autoCancelDto) if err != nil { log.Errorf("订单自动取消,推入延迟队列失败,step 1,data:%s", autoCancelDto) return } err = base.NewSQQueue("delay_rabbit_order").PublishDelay(string(jsonStr), delayTimeSecond) if err != nil { log.Errorf("订单自动取消,推入延迟队列失败,step 1,data:%s", autoCancelDto) return } log.Infof("订单自动取消,推入延迟队列成功,data:%s", orderMsgInfo, delayTimeSecond) return }(delayTimeSecond, autoCancelDto) //推送订单自动取消信息结束 return item, nil } //记录扣减了sku库存的商品 storeId,skuId,productId func (domain *orderDomain) saveOrder(data service.OrderParamsInDto) (item service.GbStoreOrderMainOutDto, err error) { var clearCart service.ClearCartParamsOutDto orderInfo := domain.OrderParamsInToSaveOrder(data) dao := OrderDao{} //获取商品信息价格 var totalAmount decimal.Decimal var integrationAmount decimal.Decimal var allotProfitAmount decimal.Decimal //订单物品表数组 itemArrayLength := len(orderInfo.Products) orderChildItems := make([]GbStoreOrderChildItem, itemArrayLength) //遍历购物车商品 payProductInfos := make([]service.PayDTOOfProductInfo, itemArrayLength) var payProductInfo service.PayDTOOfProductInfo skuIds := make([]int64, itemArrayLength) stockHandlerObject := make([]service.SkuStockSaleInfoInDto, itemArrayLength) nameLength := len(data.Name) phoneLength := len(data.Phone) verifyCodeLength := len(data.VerifyCode) idCardLength := len(data.IdCard) provinceLength := len(data.Province) cityLength := len(data.City) areaLength := len(data.Area) addressLength := len(data.Address) for key, product := range orderInfo.Products { //获取sku信息 skuCacheKey := "sku:stock:" + utils.Int64ToString(orderInfo.StoreId) + "_" + product.ProductId + ":" + product.SkuId + ":string" //sku状态,sock判断 res := base.SQRds().Exists(skuCacheKey).Val() if res == 0 { err := dao.recoverSkuInfo(stockHandlerObject) if err != nil { return item, err } return item, errors.New("商品已下架") } skuInfo := base.SQRds().HMGet(skuCacheKey, "stock", "status", "price", "integration", "id", "allotable_profit", "sp1", "pic", "supplier_price", "sku_code").Val() stock := skuInfo[0] //sku stock status := skuInfo[1] //sku status price := skuInfo[2] //sku price integration := skuInfo[3] //sku 积分 skuId := skuInfo[4] //sku id skuCode := skuInfo[9] //sku code qty, _ := utils.StringToInt64(product.Qty) stocks, _ := utils.StringToInt64(stock.(string)) status_, _ := utils.StringToInt64(status.(string)) if stocks < qty || stocks <= 0 { err := dao.recoverSkuInfo(stockHandlerObject) if err != nil { return item, err } return item, errors.New("sku库存不足") } if status_ != 1 { err := dao.recoverSkuInfo(stockHandlerObject) if err != nil { return item, err } return item, errors.New("商品已下架") } //扣减库存 base.SQRds().HIncrBy(skuCacheKey, "stock", -qty) base.SQRds().HIncrBy(skuCacheKey, "sale", qty) stockHandlerObject[key].SkuID = product.SkuId stockHandlerObject[key].StoreId = utils.Int64ToString(orderInfo.StoreId) stockHandlerObject[key].ProductID = product.ProductId stockHandlerObject[key].Quantity = product.Qty qtyDecimal := decimal.NewFromInt(qty) prices, _ := decimal.NewFromString(price.(string)) integrals, _ := decimal.NewFromString(integration.(string)) //金额累加 totalAmount = totalAmount.Add(qtyDecimal.Mul(prices)) //积分累加 integrationAmount = integrationAmount.Add(qtyDecimal.Mul(integrals)) //商品主表 productItemKey := "product:main:" + utils.Int64ToString(orderInfo.StoreId) + "_" + product.ProductId + ":" + product.ProductId + ":string" result := base.SQRds().Exists(productItemKey).Val() if result == 0 { err := dao.recoverSkuInfo(stockHandlerObject) if err != nil { return item, err } return item, errors.New("商品信息不存在") } productInfo := base.SQRds().HMGet(productItemKey, "rest", "title", "delivery_place").Val() allotProfit := skuInfo[5] // sku allotable_profit rest := productInfo[0] title := productInfo[1] deliveryPlace := productInfo[2] sp1 := skuInfo[6] //sku sp1 规格 pic := skuInfo[7] //sku pic productPrice := skuInfo[2] //sku价格 supplierPrice := skuInfo[8] //sku商品供货商价格 allotProfits := utils.StringToFloat64(allotProfit.(string)) rests := rest.(string) titles := title.(string) sp1s := sp1.(string) pics := pic.(string) deliveryPlaces, _ := utils.StringToInt64(deliveryPlace.(string)) productPrices := utils.StringToFloat64(productPrice.(string)) supplierPrices := utils.StringToFloat64(supplierPrice.(string)) productId, _ := utils.StringToInt64(product.ProductId) skuId_, _ := utils.StringToInt64(skuId.(string)) amountSku, _ := qtyDecimal.Mul(prices).Float64() //sku价格 integralSku, _ := qtyDecimal.Mul(integrals).Float64() //sku积分 skuPrice := utils.StringToFloat64(price.(string)) skuIntegration := utils.StringToFloat64(integration.(string)) //商品可分配利润 allotProfit_ := decimal.NewFromFloat(allotProfits) allotProfitAmount = allotProfitAmount.Add(qtyDecimal.Mul(allotProfit_)) //rest信息获取 取到Rest中的order_form_items var productInfoRest service.ProductInfoRest json.Unmarshal([]byte(rests), &productInfoRest) var formItems []string json.Unmarshal(productInfoRest.OrderFormItems, &formItems) var message string checkResult := false for _, value := range formItems { if value == "recipients" && nameLength <= 0 { message = "购物车有,收货人姓名,为必填项的商品" checkResult = true } else if value == "tel" && phoneLength <= 0 { message = "购物车有,收货人电话,为必填项的商品" checkResult = true } else if value == "verify_code" && verifyCodeLength <= 0 { message = "购物车有,验证码,为必填项的商品" checkResult = true } else if value == "id_card" && idCardLength <= 0 { message = "购物车有,身份证,为必填项的商品" checkResult = true } else if value == "receiving_info" && (nameLength <= 0 || phoneLength <= 0 || provinceLength <= 0 || cityLength <= 0 || areaLength <= 0 || addressLength <= 0) { message = "购物车有,收货信息,为必填项的商品" checkResult = true } if checkResult { //最后到这里执行 如果当前sku库存维护失败,程序终止,修改过库存的sku应对恢复库存 err := dao.recoverSkuInfo(stockHandlerObject) if err != nil { return item, err } return item, errors.New(message) } } //订单物品表数据准备 orderChildItem := GbStoreOrderChildItem{ StoreID: orderInfo.StoreId, ProductID: productId, ProductTitle: titles, ProductPic: pics, SkuID: skuId_, Quantity: int(qty), PayPriceTotal: amountSku, OriginalPriceTotal: amountSku, PayIntegrationTotal: integralSku, OriginalIntegrationTotal: integralSku, SinglePrice: skuPrice, SingleIntegration: skuIntegration, CreatedAt: time.Now(), SkuName: sp1s, SkuCode: skuCode.(string), DeliveryPlace: int(deliveryPlaces), } payProductInfo.ProductID, _ = utils.StringToInt64(product.ProductId) payProductInfo.ProductPrice = productPrices payProductInfo.ProductAllotProfit = allotProfits payProductInfo.ProductCount = int(qty) payProductInfo.SupplierPrice = supplierPrices orderChildItems[key] = orderChildItem payProductInfos[key] = payProductInfo //清购物车所需参数 skuIds skuIds[key] = skuId_ } totalAmounts, _ := totalAmount.Float64() integrationAmounts, _ := integrationAmount.Float64() allotProfitAmounts, _ := allotProfitAmount.Float64() order := GbStoreOrderMain{ StoreID: orderInfo.StoreId, OrderMainNo: utils.GenerateOrderCode(), UId: orderInfo.UId, TotalAmount: totalAmounts, PayAmount: totalAmounts, IntegrationAmount: integrationAmounts, UseIntegration: integrationAmounts, PayStatus: 0, CreatedAt: time.Now(), AllotProfit: allotProfitAmounts, } orderChild := GbStoreOrderChild{ StoreID: orderInfo.StoreId, OrderNo: utils.GenerateOrderCode(), OrderMainNo: order.OrderMainNo, UId: orderInfo.UId, TotalAmount: order.TotalAmount, PayAmount: order.PayAmount, FreightAmount: order.FreightAmount, PromotionAmount: order.PromotionAmount, IntegrationAmount: order.IntegrationAmount, HumanAdjustAmount: order.HumanAdjustAmount, UseIntegration: order.UseIntegration, Status: 0, ConfirmStatus: 0, PayStatus: 0, PayType: "", CreatedAt: time.Now(), AllotProfit: allotProfitAmounts, } if idCardLength > 0 { var restInfo RestInfo restInfo.IdCard = data.IdCard str, _ := json.Marshal(restInfo) orderChild.Rest = str } //订单地址表数据准备 orderAddress := GbStoreOrderUserAddress{ StoreID: orderInfo.StoreId, } if nameLength > 0 { orderAddress.ReceiverName = data.Name } if phoneLength > 0 { orderAddress.ReceiverPhone = data.Phone } if provinceLength > 0 { orderAddress.ReceiverProvince = data.Province } if cityLength > 0 { orderAddress.ReceiverCity = data.City } if areaLength > 0 { orderAddress.ReceiverRegion = data.Area } if addressLength > 0 { orderAddress.ReceiverDetailAddress = data.Address } err = dao.createOrder(order, orderChild, orderChildItems, orderAddress) if err != nil { errs := dao.recoverSkuInfo(stockHandlerObject) if errs != nil { return item, errs } return item, err } item.UId = order.UId item.StoreID = order.StoreID item.OrderMainNo = order.OrderMainNo item.TotalPrice = order.TotalAmount item.AllotProfit = order.AllotProfit item.OrderCode = order.OrderMainNo item.PayProductInfos = payProductInfos clearCart.SkuIds = skuIds //清空购物车 go func(OrderMainNo string, OrderNo string, StoreId int64, Uid int64, clearCart service.ClearCartParamsOutDto) { err := base.TX(func(db *gorm.DB) error { //查订单主表和子表id order, err := dao.GetOrderInfoByOrderNo(OrderMainNo, db) if err != nil { return err } clearCart.OrderMainId = order.ID orderChild, err = dao.GetOrderChildInfoByOrderNo(OrderMainNo, db) if err != nil { return err } clearCart.OrderId = orderChild.ID clearCart.StoreId = StoreId clearCart.Uid = Uid return nil }) if err != nil { logs.Info("拼接清购物车所需参数报的错:" + err.Error()) return } services := service.GetPromotionService() err = services.ClearCartBySkuIds(clearCart.Uid, clearCart.StoreId, clearCart.OrderId, clearCart.OrderMainId, clearCart.SkuIds) if err != nil { logs.Info("清购物车接口返回的错误信息:" + err.Error()) return } }(order.OrderMainNo, orderChild.OrderNo, order.StoreID, order.UId, clearCart) return item, nil }
正常的下单流程是:根据购物车信息,1,检查商品状态,2,sku库存,3,扣减库存, 4,创建订单,5,唤起支付,6,接收回调信息,7,支付失效自动取消订单。
第3步扣减的是缓存中的数据,同步到mysql跟自动取消一样用的是消息中间件由对应业务领域处理。第2步的sku信息使用redis的string数据结构时,会出现库存扣减异常,hash能避免这个问题。mysql的库存扣减因为用到了消息中间件,没有出现异常。
没有数据