下单高并发总结

最近写了一个在测试环境吞吐量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
				}
			}
		}
	}
}



当时总体思路是“PHP不行”——认为PHP效率低应当换成并发支持更好的Golang,同时能异步处理的就异步处理。但测试说其他组的PHP项目吞吐量有1000多(6台服务器),感觉PHP并没有那么不堪。优化思路不明确而已。


最近又抛弃了之前的折扣促销那套流程,因为业务需要购物车,于是又一次重构了下单并用了领域模型。各领域负责自己的业务和数据,比如下单只操作订单相关的表,商品数据查询支付唤起都由其他领域提供服务。换句话说就是用了大量的缓存。缓存了商品基本信息,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的库存扣减因为用到了消息中间件,没有出现异常。



总之,缓存和消息中间件的合理使用提高了下单接口的并发能力。


相关文档

没有数据

评论0条