|
|
@@ -0,0 +1,695 @@
|
|
|
+package com.fs.live.service.impl;
|
|
|
+
|
|
|
+import cn.hutool.core.util.NumberUtil;
|
|
|
+import cn.hutool.core.util.ObjectUtil;
|
|
|
+import cn.hutool.json.JSONUtil;
|
|
|
+import com.alibaba.fastjson.JSON;
|
|
|
+import com.fs.common.constant.LiveKeysConstant;
|
|
|
+import com.fs.common.core.domain.R;
|
|
|
+import com.fs.common.core.redis.RedisCache;
|
|
|
+import com.fs.common.core.redis.service.StockDeductService;
|
|
|
+import com.fs.common.exception.CustomException;
|
|
|
+import com.fs.common.utils.PhoneUtils;
|
|
|
+import com.fs.common.utils.StringUtils;
|
|
|
+import com.fs.live.domain.Live;
|
|
|
+import com.fs.live.domain.LiveCouponUser;
|
|
|
+import com.fs.live.domain.LiveGoods;
|
|
|
+import com.fs.live.domain.LiveOrder;
|
|
|
+import com.fs.live.domain.LiveOrderItem;
|
|
|
+import com.fs.live.domain.LiveOrderPayment;
|
|
|
+import com.fs.live.domain.LiveUserFirstEntry;
|
|
|
+import com.fs.live.dto.LiveOrderItemDTO;
|
|
|
+import com.fs.live.mapper.LiveOrderOptMapper;
|
|
|
+import com.fs.live.param.FsUserLiveOrderPayUParam;
|
|
|
+import com.fs.live.service.ILiveCouponUserService;
|
|
|
+import com.fs.live.service.ILiveOrderOptService;
|
|
|
+import com.fs.live.service.ILiveUserFirstEntryService;
|
|
|
+import com.fs.live.utils.CrossServiceRsaUtil;
|
|
|
+import com.fs.store.service.IFsUserService;
|
|
|
+import com.fs.live.vo.LiveGoodsUploadMqVo;
|
|
|
+import com.fs.store.config.StoreConfig;
|
|
|
+import com.fs.store.domain.FsPayConfig;
|
|
|
+import com.fs.store.domain.FsShippingTemplates;
|
|
|
+import com.fs.store.domain.FsShippingTemplatesRegion;
|
|
|
+import com.fs.store.domain.FsStoreProduct;
|
|
|
+import com.fs.store.domain.FsStoreProductAttrValue;
|
|
|
+import com.fs.store.domain.FsUser;
|
|
|
+import com.fs.store.dto.LiveOrderComputeDTO;
|
|
|
+import com.fs.store.dto.TemplateDTO;
|
|
|
+import com.fs.store.enums.OrderInfoEnum;
|
|
|
+import com.fs.store.enums.ShippingTempEnum;
|
|
|
+import com.fs.store.param.LiveOrderComputedParam;
|
|
|
+import com.fs.store.service.channel.PaymentHandler;
|
|
|
+import com.fs.store.service.channel.PaymentHandlerHolder;
|
|
|
+import com.fs.store.service.channel.param.PayProcessContext;
|
|
|
+import com.fs.system.config.SnowflakeUtils;
|
|
|
+import com.fs.system.service.ISysConfigService;
|
|
|
+import lombok.Data;
|
|
|
+import lombok.extern.slf4j.Slf4j;
|
|
|
+import org.apache.rocketmq.spring.core.RocketMQTemplate;
|
|
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
+import org.springframework.data.redis.core.StringRedisTemplate;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+import org.springframework.transaction.annotation.Propagation;
|
|
|
+import org.springframework.transaction.annotation.Transactional;
|
|
|
+
|
|
|
+import java.math.BigDecimal;
|
|
|
+import java.math.RoundingMode;
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.Date;
|
|
|
+import java.util.HashMap;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
+import java.util.concurrent.CompletableFuture;
|
|
|
+import java.util.concurrent.ExecutionException;
|
|
|
+import java.util.concurrent.TimeUnit;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 直播订单高并发优化实现:读多走 Redis + OptMapper,写路径独立 SQL,不影响原 LiveOrderServiceImpl
|
|
|
+ */
|
|
|
+@Slf4j
|
|
|
+@Service
|
|
|
+public class LiveOrderOptServiceImpl implements ILiveOrderOptService {
|
|
|
+
|
|
|
+ private static final String OPT_PRODUCT_KEY = "opt:live:order:product:%s";
|
|
|
+ private static final String OPT_LIVE_GOODS_KEY = "opt:live:order:goods:%s:%s";
|
|
|
+ private static final String OPT_LIVE_EXISTS_KEY = "opt:live:order:live:exists:%s";
|
|
|
+ private static final String OPT_ATTR_ID_KEY = "product:attr:value:id:%s";
|
|
|
+ private static final String OPT_ATTR_PRODUCT_KEY = "product:attr:value:productId:%s";
|
|
|
+ private static final String OPT_USER_PAY_KEY = "opt:live:order:user:pay:%s";
|
|
|
+ private static final String OPT_PAY_CONFIG_KEY = "opt:live:order:store:pay";
|
|
|
+ private static final String OPT_STORE_CONFIG_KEY = "opt:live:order:store:config";
|
|
|
+ private static final String LIVE_APP_USER_SYNC_PENDING_KEY_PREFIX = "live:app_user_sync:pending:";
|
|
|
+
|
|
|
+ @Autowired
|
|
|
+ private LiveOrderOptMapper liveOrderOptMapper;
|
|
|
+ @Autowired
|
|
|
+ private RedisCache redisCache;
|
|
|
+ @Autowired
|
|
|
+ private StringRedisTemplate stringRedisTemplate;
|
|
|
+ @Autowired
|
|
|
+ private StockDeductService stockDeductService;
|
|
|
+ @Autowired
|
|
|
+ private RocketMQTemplate rocketMQTemplate;
|
|
|
+ @Autowired
|
|
|
+ private ILiveUserFirstEntryService liveUserFirstEntryService;
|
|
|
+ @Autowired
|
|
|
+ private ILiveCouponUserService liveCouponUserService;
|
|
|
+ @Autowired
|
|
|
+ private ISysConfigService configService;
|
|
|
+ @Autowired
|
|
|
+ private IFsUserService userService;
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public LiveOrderComputeDTO computedOrder(long userId, LiveOrderComputedParam param) {
|
|
|
+ String orderKey = redisCache.getCacheObject("orderKey:" + param.getOrderKey());
|
|
|
+ if (StringUtils.isEmpty(orderKey)) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ FsStoreProduct fsStoreProduct = getProductCached(param.getProductId());
|
|
|
+ if (fsStoreProduct == null) {
|
|
|
+ log.error("[opt] 商品不存在 productId={}", param.getProductId());
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ FsStoreProductAttrValue attrValue = getAttrValueByIdCached(param.getAttrValueId());
|
|
|
+
|
|
|
+ BigDecimal totalPrice = fsStoreProduct.getPrice().multiply(new BigDecimal(param.getTotalNum()));
|
|
|
+ if (attrValue != null) {
|
|
|
+ totalPrice = attrValue.getPrice().multiply(new BigDecimal(param.getTotalNum()));
|
|
|
+ }
|
|
|
+
|
|
|
+ BigDecimal payPostage = BigDecimal.ZERO;
|
|
|
+ if (param.getCityId() != null) {
|
|
|
+ payPostage = handleDeliveryMoney(param.getCityId(), fsStoreProduct, param.getTotalNum(), attrValue);
|
|
|
+ }
|
|
|
+
|
|
|
+ BigDecimal payPrice = totalPrice.add(payPostage);
|
|
|
+ BigDecimal deductionPrice = BigDecimal.ZERO;
|
|
|
+ if (param.getCouponUserId() != null) {
|
|
|
+ LiveCouponUser couponUser = liveCouponUserService.selectLiveCouponUserById(param.getCouponUserId());
|
|
|
+ if (couponUser != null && couponUser.getStatus() == 0) {
|
|
|
+ if (couponUser.getUseMinPrice().compareTo(payPrice) < 1) {
|
|
|
+ deductionPrice = couponUser.getCouponPrice();
|
|
|
+ payPrice = payPrice.subtract(deductionPrice);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return LiveOrderComputeDTO.builder()
|
|
|
+ .totalPrice(totalPrice)
|
|
|
+ .payPrice(payPrice)
|
|
|
+ .payPostage(payPostage)
|
|
|
+ .payDelivery(payPostage)
|
|
|
+ .deductionPrice(deductionPrice)
|
|
|
+ .build();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Throwable.class, propagation = Propagation.REQUIRED)
|
|
|
+ public R createLiveOrder(LiveOrder liveOrder) {
|
|
|
+ String orderKey = redisCache.getCacheObject("orderKey:" + liveOrder.getOrderKey());
|
|
|
+ if (StringUtils.isEmpty(orderKey)) {
|
|
|
+ return R.error("订单已过期");
|
|
|
+ }
|
|
|
+ if (liveOrder.getLiveId() == null) return R.error("直播ID不能为空");
|
|
|
+ if (liveOrder.getProductId() == null) return R.error("购物商品ID不能为空");
|
|
|
+ if (liveOrder.getUserName() == null) return R.error("用户名不能为空");
|
|
|
+ if (liveOrder.getUserPhone() == null) return R.error("用户手机号不能为空");
|
|
|
+ if (liveOrder.getUserAddress() == null) return R.error("用户地址不能为空");
|
|
|
+ if (liveOrder.getTotalNum() == null) return R.error("商品数量不能为空");
|
|
|
+
|
|
|
+ if (!liveExistsCached(liveOrder.getLiveId())) {
|
|
|
+ return R.error("当前直播不存在");
|
|
|
+ }
|
|
|
+ FsStoreProduct fsStoreProduct = getProductCached(liveOrder.getProductId());
|
|
|
+ LiveGoods goods = getLiveGoodsCached(liveOrder.getLiveId(), liveOrder.getProductId());
|
|
|
+ if (goods == null) return R.error("当前商品不存在");
|
|
|
+ if (fsStoreProduct == null) return R.error("店铺已下架商品,购买失败");
|
|
|
+ if (fsStoreProduct.getIsShow() == 0 || goods.getStatus() == 0) return R.error("商品已下架,购买失败");
|
|
|
+
|
|
|
+ CompletableFuture<Boolean> completableFuture = stockDeductService.deductStockAsync(
|
|
|
+ liveOrder.getProductId(), liveOrder.getLiveId(),
|
|
|
+ Integer.parseInt(liveOrder.getTotalNum()), Long.parseLong(liveOrder.getUserId()));
|
|
|
+ try {
|
|
|
+ if (!completableFuture.get()) {
|
|
|
+ return R.error("抱歉,这款商品已被抢光,暂时无库存~");
|
|
|
+ }
|
|
|
+ } catch (InterruptedException | ExecutionException e) {
|
|
|
+ log.error("[opt] 高并发库存扣减失败", e);
|
|
|
+ return R.error("订单创建失败:" + e.getMessage());
|
|
|
+ }
|
|
|
+ if (goods.getStock() == null) return R.error("直播间商品库存不足");
|
|
|
+
|
|
|
+ resolveMaskedPhoneBeforeStore(liveOrder);
|
|
|
+ liveOrder.setUserPhone(PhoneUtils.normalizePhoneForStorage(liveOrder.getUserPhone()));
|
|
|
+ if (fsStoreProduct.getProductId() != null && (fsStoreProduct.getProductId().equals(3168L)
|
|
|
+ || fsStoreProduct.getProductId().equals(3184L)
|
|
|
+ || fsStoreProduct.getProductId().equals(3185L))) {
|
|
|
+ liveOrder.setStoreHouseCode("YDSP001");
|
|
|
+ } else {
|
|
|
+ liveOrder.setStoreHouseCode("CQDS001");
|
|
|
+ }
|
|
|
+
|
|
|
+ LiveGoodsUploadMqVo vo = LiveGoodsUploadMqVo.builder()
|
|
|
+ .goodsId(goods.getGoodsId())
|
|
|
+ .goodsNum(Integer.parseInt(liveOrder.getTotalNum()))
|
|
|
+ .build();
|
|
|
+ try {
|
|
|
+ rocketMQTemplate.syncSend("live-goods-upload-fhhx", JSON.toJSONString(vo));
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("[opt] 更新库存 MQ 失败!{}", vo, e);
|
|
|
+ }
|
|
|
+
|
|
|
+ LiveUserFirstEntry liveUserFirstEntry = liveUserFirstEntryService
|
|
|
+ .selectEntityByLiveIdUserId(liveOrder.getLiveId(), Long.parseLong(liveOrder.getUserId()));
|
|
|
+ Long companyId = liveUserFirstEntry.getCompanyId();
|
|
|
+ Long companyUserId = liveUserFirstEntry.getCompanyUserId();
|
|
|
+ if ((companyId != null && companyId == -1L && companyUserId != null && companyUserId == -1L)
|
|
|
+ || (companyId != null && companyId == -2L && companyUserId != null
|
|
|
+ && companyUserId.equals(Long.valueOf(liveOrder.getUserId())))) {
|
|
|
+ StoreConfig storeConfig = loadStoreConfigCached();
|
|
|
+ companyId = storeConfig.getDefaultLiveCompanyId() != null ? storeConfig.getDefaultLiveCompanyId() : 309L;
|
|
|
+ companyUserId = storeConfig.getDefaultLiveCompanyUserId() != null ? storeConfig.getDefaultLiveCompanyUserId() : 8647L;
|
|
|
+ }
|
|
|
+ liveOrder.setCompanyId(companyId);
|
|
|
+ liveOrder.setCompanyUserId(companyUserId);
|
|
|
+ liveOrder.setTuiUserId(companyUserId);
|
|
|
+
|
|
|
+ String orderSn = SnowflakeUtils.nextId();
|
|
|
+ liveOrder.setOrderCode(orderSn);
|
|
|
+
|
|
|
+ FsStoreProductAttrValue fsStoreProductAttrValue = getAttrValueByIdCached(liveOrder.getAttrValueId());
|
|
|
+
|
|
|
+ BigDecimal totalPrice = fsStoreProduct.getPrice().multiply(new BigDecimal(liveOrder.getTotalNum()));
|
|
|
+ if (fsStoreProductAttrValue != null) {
|
|
|
+ totalPrice = fsStoreProductAttrValue.getPrice().multiply(new BigDecimal(liveOrder.getTotalNum()));
|
|
|
+ }
|
|
|
+ liveOrder.setTotalPrice(totalPrice);
|
|
|
+
|
|
|
+ BigDecimal payPostage = handleDeliveryMoney(liveOrder.getCityId(), fsStoreProduct, liveOrder.getTotalNum(), fsStoreProductAttrValue);
|
|
|
+ liveOrder.setPayPostage(payPostage);
|
|
|
+ liveOrder.setPayDelivery(payPostage);
|
|
|
+ liveOrder.setDiscountMoney(BigDecimal.ZERO);
|
|
|
+
|
|
|
+ if (liveOrder.getCouponUserId() != null) {
|
|
|
+ LiveCouponUser couponUser = liveCouponUserService.selectLiveCouponUserById(liveOrder.getCouponUserId());
|
|
|
+ if (couponUser != null && couponUser.getStatus() == 0) {
|
|
|
+ if (!couponUser.getUserId().toString().equals(liveOrder.getUserId())) {
|
|
|
+ return R.error("非法操作");
|
|
|
+ }
|
|
|
+ BigDecimal payPriceBeforeCoupon = totalPrice.add(payPostage);
|
|
|
+ if (couponUser.getUseMinPrice().compareTo(payPriceBeforeCoupon) < 1) {
|
|
|
+ liveOrder.setUserCouponId(couponUser.getId());
|
|
|
+ liveOrder.setDiscountMoney(couponUser.getCouponPrice());
|
|
|
+ couponUser.setStatus(1);
|
|
|
+ couponUser.setUseTime(new Date());
|
|
|
+ liveCouponUserService.updateLiveCouponUser(couponUser);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ BigDecimal payPrice = totalPrice.add(payPostage)
|
|
|
+ .subtract(liveOrder.getDiscountMoney() != null ? liveOrder.getDiscountMoney() : BigDecimal.ZERO);
|
|
|
+ liveOrder.setPayPrice(payPrice);
|
|
|
+ liveOrder.setPayMoney(payPrice);
|
|
|
+ liveOrder.setItemJson(JSON.toJSONString(fsStoreProduct));
|
|
|
+ liveOrder.setCreateTime(new Date());
|
|
|
+ liveOrder.setUpdateTime(new Date());
|
|
|
+ liveOrder.setProductId(fsStoreProduct.getProductId());
|
|
|
+ liveOrder.setStatus(OrderInfoEnum.STATUS_1.getValue());
|
|
|
+ liveOrder.setPayType("1");
|
|
|
+
|
|
|
+ try {
|
|
|
+ if (liveOrderOptMapper.insertLiveOrder(liveOrder) > 0) {
|
|
|
+ LiveOrderItemDTO dto = new LiveOrderItemDTO();
|
|
|
+ dto.setImage(fsStoreProduct.getImage());
|
|
|
+ dto.setSku(String.valueOf(fsStoreProduct.getStock()));
|
|
|
+
|
|
|
+ FsStoreProductAttrValue attrValueForBarCode = fsStoreProductAttrValue;
|
|
|
+ if (attrValueForBarCode == null || StringUtils.isEmpty(attrValueForBarCode.getBarCode())) {
|
|
|
+ List<FsStoreProductAttrValue> attrList = getAttrValueByProductCached(fsStoreProduct.getProductId());
|
|
|
+ attrValueForBarCode = attrList.stream()
|
|
|
+ .filter(a -> StringUtils.isNotEmpty(a.getBarCode()))
|
|
|
+ .findFirst()
|
|
|
+ .orElse(null);
|
|
|
+ }
|
|
|
+ if (attrValueForBarCode != null) {
|
|
|
+ dto.setBarCode(attrValueForBarCode.getBarCode());
|
|
|
+ dto.setGroupBarCode(attrValueForBarCode.getGroupBarCode());
|
|
|
+ }
|
|
|
+ dto.setPrice(fsStoreProduct.getPrice());
|
|
|
+ dto.setProductName(fsStoreProduct.getProductName());
|
|
|
+ dto.setNum(Long.valueOf(liveOrder.getTotalNum()));
|
|
|
+
|
|
|
+ LiveOrderItem liveOrderItem = new LiveOrderItem();
|
|
|
+ liveOrderItem.setOrderCode(liveOrder.getOrderCode());
|
|
|
+ liveOrderItem.setOrderId(liveOrder.getOrderId());
|
|
|
+ liveOrderItem.setProductId(liveOrder.getProductId());
|
|
|
+ liveOrderItem.setNum(Long.valueOf(liveOrder.getTotalNum()));
|
|
|
+ liveOrderItem.setJsonInfo(JSON.toJSONString(dto));
|
|
|
+
|
|
|
+ BigDecimal productTotal = fsStoreProduct.getPrice().multiply(new BigDecimal(liveOrder.getTotalNum()));
|
|
|
+ BigDecimal netPrice = BigDecimal.ZERO;
|
|
|
+ if (productTotal.compareTo(BigDecimal.ZERO) > 0 && liveOrder.getPayPrice() != null) {
|
|
|
+ BigDecimal discountRate = liveOrder.getPayPrice().divide(productTotal, 10, RoundingMode.HALF_UP);
|
|
|
+ netPrice = fsStoreProduct.getPrice().multiply(discountRate).setScale(2, RoundingMode.HALF_UP);
|
|
|
+ }
|
|
|
+ liveOrderItem.setNetPrice(netPrice);
|
|
|
+ liveOrderOptMapper.insertLiveOrderItem(liveOrderItem);
|
|
|
+ redisCache.deleteObject("orderKey:" + liveOrder.getOrderKey());
|
|
|
+ cachePendingAppUserSync(liveOrder);
|
|
|
+ return R.ok("下单成功").put("order", liveOrder);
|
|
|
+ }
|
|
|
+ return R.error("订单创建失败");
|
|
|
+ } catch (Exception e) {
|
|
|
+ return R.error("订单创建失败:" + e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ @Transactional(rollbackFor = Throwable.class, propagation = Propagation.REQUIRED)
|
|
|
+ public R wxPayment(FsUserLiveOrderPayUParam param) {
|
|
|
+ LiveOrder order = liveOrderOptMapper.selectLiveOrderByOrderId(String.valueOf(param.getOrderId()));
|
|
|
+ if (order == null) {
|
|
|
+ return R.error("订单不存在");
|
|
|
+ }
|
|
|
+ if (order.getStatus() != 1) {
|
|
|
+ return R.error("非法操作");
|
|
|
+ }
|
|
|
+ String payingOrderId = redisCache.getCacheObject("isPaying:" + order.getOrderId());
|
|
|
+ if (StringUtils.isNotEmpty(payingOrderId) && payingOrderId.equals(order.getOrderId().toString())) {
|
|
|
+ return R.error("正在支付中...");
|
|
|
+ }
|
|
|
+ FsUser user = getUserForPayCached(param.getUserId());
|
|
|
+ if (user != null && StringUtils.isNotEmpty(user.getMaOpenId())) {
|
|
|
+ if (order.getPayMoney().compareTo(BigDecimal.ZERO) == 0) {
|
|
|
+ return R.ok().put("isPay", 1);
|
|
|
+ }
|
|
|
+ FsPayConfig fsPayConfig = loadPayConfigCached();
|
|
|
+ String payCode = SnowflakeUtils.nextId();
|
|
|
+ if (StringUtils.isEmpty(payCode)) {
|
|
|
+ return R.error("订单生成失败,请重试");
|
|
|
+ }
|
|
|
+ LiveOrderPayment storePayment = new LiveOrderPayment();
|
|
|
+ storePayment.setStatus(0);
|
|
|
+ storePayment.setPayMode(fsPayConfig.getType());
|
|
|
+ storePayment.setBusinessCode(order.getOrderCode());
|
|
|
+ storePayment.setPayCode(payCode);
|
|
|
+ storePayment.setPayMoney(order.getPayMoney());
|
|
|
+ storePayment.setCreateTime(new Date());
|
|
|
+ storePayment.setPayTypeCode("weixin");
|
|
|
+ storePayment.setBusinessType(5);
|
|
|
+ storePayment.setRemark("直播商品订单支付");
|
|
|
+ storePayment.setOpenId(user.getMaOpenId());
|
|
|
+ storePayment.setUserId(user.getUserId());
|
|
|
+ storePayment.setBusinessId(order.getOrderId().toString());
|
|
|
+ if (liveOrderOptMapper.insertLiveOrderPayment(storePayment) > 0) {
|
|
|
+ liveOrderOptMapper.updateLiveOrderPayPrice(order.getOrderId(), order.getPayMoney());
|
|
|
+ if ("1".equals(order.getPayType())) {
|
|
|
+ PayProcessContext processContext = new PayProcessContext();
|
|
|
+ processContext.setOrderId(order.getOrderId());
|
|
|
+ processContext.setPayType(1);
|
|
|
+ processContext.setPaymentId(storePayment.getPaymentId());
|
|
|
+ processContext.setPayCode(storePayment.getPayCode());
|
|
|
+ processContext.setFsPayConfig(fsPayConfig);
|
|
|
+ processContext.setUserId(user.getUserId());
|
|
|
+ processContext.setGoodsInfo("直播商品订单支付");
|
|
|
+ processContext.setOrderPrefix("live-");
|
|
|
+ PaymentHandler payment = PaymentHandlerHolder.findBest(fsPayConfig.getType());
|
|
|
+ if (ObjectUtil.isNull(payment)) {
|
|
|
+ throw new CustomException("支付方式不存在");
|
|
|
+ }
|
|
|
+ R process = payment.createLiveOrder(processContext);
|
|
|
+ if ("200".equals(String.valueOf(process.get("code")))) {
|
|
|
+ redisCache.setCacheObject("isPaying:" + order.getOrderId(), order.getOrderId().toString(), 1, TimeUnit.MINUTES);
|
|
|
+ return process;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ return R.error("用户OPENID不存在");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return R.ok();
|
|
|
+ }
|
|
|
+
|
|
|
+ private FsStoreProduct getProductCached(Long productId) {
|
|
|
+ if (productId == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ String productDetailKey = String.format(LiveKeysConstant.PRODUCT_DETAIL_CACHE, productId);
|
|
|
+ Map<String, Object> detailCache = redisCache.getCacheObject(productDetailKey);
|
|
|
+ if (detailCache != null && detailCache.get("product") instanceof FsStoreProduct) {
|
|
|
+ return (FsStoreProduct) detailCache.get("product");
|
|
|
+ }
|
|
|
+ String key = String.format(OPT_PRODUCT_KEY, productId);
|
|
|
+ String json = stringRedisTemplate.opsForValue().get(key);
|
|
|
+ if (StringUtils.isNotEmpty(json)) {
|
|
|
+ try {
|
|
|
+ return JSON.parseObject(json, FsStoreProduct.class);
|
|
|
+ } catch (Exception e) {
|
|
|
+ stringRedisTemplate.delete(key);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ FsStoreProduct product = liveOrderOptMapper.selectProductById(productId);
|
|
|
+ if (product != null) {
|
|
|
+ stringRedisTemplate.opsForValue().set(key, JSON.toJSONString(product), 5, TimeUnit.MINUTES);
|
|
|
+ }
|
|
|
+ return product;
|
|
|
+ }
|
|
|
+
|
|
|
+ private LiveGoods getLiveGoodsCached(Long liveId, Long productId) {
|
|
|
+ String key = String.format(OPT_LIVE_GOODS_KEY, liveId, productId);
|
|
|
+ String json = stringRedisTemplate.opsForValue().get(key);
|
|
|
+ if (StringUtils.isNotEmpty(json)) {
|
|
|
+ try {
|
|
|
+ return JSON.parseObject(json, LiveGoods.class);
|
|
|
+ } catch (Exception e) {
|
|
|
+ stringRedisTemplate.delete(key);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ LiveGoods goods = liveOrderOptMapper.selectLiveGoodsByLiveAndProduct(liveId, productId);
|
|
|
+ if (goods != null) {
|
|
|
+ stringRedisTemplate.opsForValue().set(key, JSON.toJSONString(goods), 60, TimeUnit.SECONDS);
|
|
|
+ }
|
|
|
+ return goods;
|
|
|
+ }
|
|
|
+
|
|
|
+ private boolean liveExistsCached(Long liveId) {
|
|
|
+ String key = String.format(OPT_LIVE_EXISTS_KEY, liveId);
|
|
|
+ String cached = stringRedisTemplate.opsForValue().get(key);
|
|
|
+ if ("1".equals(cached)) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ if ("0".equals(cached)) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ String detailKey = String.format(LiveKeysConstant.LIVE_HOME_PAGE_DETAIL, liveId);
|
|
|
+ Object liveDetail = redisCache.getCacheObject(detailKey);
|
|
|
+ if (liveDetail != null) {
|
|
|
+ stringRedisTemplate.opsForValue().set(key, "1", 5, TimeUnit.MINUTES);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ Live live = liveOrderOptMapper.selectLiveExistsById(liveId);
|
|
|
+ boolean exists = live != null;
|
|
|
+ stringRedisTemplate.opsForValue().set(key, exists ? "1" : "0", 5, TimeUnit.MINUTES);
|
|
|
+ return exists;
|
|
|
+ }
|
|
|
+
|
|
|
+ private FsStoreProductAttrValue getAttrValueByIdCached(Long attrValueId) {
|
|
|
+ if (attrValueId == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ String cacheKey = String.format(OPT_ATTR_ID_KEY, attrValueId);
|
|
|
+ String cacheJson = stringRedisTemplate.opsForValue().get(cacheKey);
|
|
|
+ if (StringUtils.isNotEmpty(cacheJson)) {
|
|
|
+ try {
|
|
|
+ return JSON.parseObject(cacheJson, FsStoreProductAttrValue.class);
|
|
|
+ } catch (Exception e) {
|
|
|
+ stringRedisTemplate.delete(cacheKey);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ FsStoreProductAttrValue attrValue = liveOrderOptMapper.selectAttrValueById(attrValueId);
|
|
|
+ if (attrValue != null) {
|
|
|
+ stringRedisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(attrValue), 12, TimeUnit.HOURS);
|
|
|
+ }
|
|
|
+ return attrValue;
|
|
|
+ }
|
|
|
+
|
|
|
+ private List<FsStoreProductAttrValue> getAttrValueByProductCached(Long productId) {
|
|
|
+ String cacheKey = String.format(OPT_ATTR_PRODUCT_KEY, productId);
|
|
|
+ String cacheJson = stringRedisTemplate.opsForValue().get(cacheKey);
|
|
|
+ if (StringUtils.isNotEmpty(cacheJson)) {
|
|
|
+ try {
|
|
|
+ return JSON.parseArray(cacheJson, FsStoreProductAttrValue.class);
|
|
|
+ } catch (Exception e) {
|
|
|
+ stringRedisTemplate.delete(cacheKey);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ List<FsStoreProductAttrValue> list = liveOrderOptMapper.selectAttrValueByProductId(productId);
|
|
|
+ if (list == null) {
|
|
|
+ list = new ArrayList<>();
|
|
|
+ }
|
|
|
+ stringRedisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(list), 12, TimeUnit.HOURS);
|
|
|
+ return list;
|
|
|
+ }
|
|
|
+
|
|
|
+ private FsUser getUserForPayCached(Long userId) {
|
|
|
+ String key = String.format(OPT_USER_PAY_KEY, userId);
|
|
|
+ String json = stringRedisTemplate.opsForValue().get(key);
|
|
|
+ if (StringUtils.isNotEmpty(json)) {
|
|
|
+ try {
|
|
|
+ return JSON.parseObject(json, FsUser.class);
|
|
|
+ } catch (Exception e) {
|
|
|
+ stringRedisTemplate.delete(key);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ FsUser user = liveOrderOptMapper.selectUserForPay(userId);
|
|
|
+ if (user != null) {
|
|
|
+ stringRedisTemplate.opsForValue().set(key, JSON.toJSONString(user), 5, TimeUnit.MINUTES);
|
|
|
+ }
|
|
|
+ return user;
|
|
|
+ }
|
|
|
+
|
|
|
+ private FsPayConfig loadPayConfigCached() {
|
|
|
+ String json = stringRedisTemplate.opsForValue().get(OPT_PAY_CONFIG_KEY);
|
|
|
+ if (StringUtils.isNotEmpty(json)) {
|
|
|
+ try {
|
|
|
+ return JSON.parseObject(json, FsPayConfig.class);
|
|
|
+ } catch (Exception e) {
|
|
|
+ stringRedisTemplate.delete(OPT_PAY_CONFIG_KEY);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ String configJson = configService.selectConfigByKey("store.pay");
|
|
|
+ FsPayConfig config = JSON.parseObject(configJson, FsPayConfig.class);
|
|
|
+ if (config != null) {
|
|
|
+ stringRedisTemplate.opsForValue().set(OPT_PAY_CONFIG_KEY, JSON.toJSONString(config), 5, TimeUnit.MINUTES);
|
|
|
+ }
|
|
|
+ return config;
|
|
|
+ }
|
|
|
+
|
|
|
+ private StoreConfig loadStoreConfigCached() {
|
|
|
+ String json = stringRedisTemplate.opsForValue().get(OPT_STORE_CONFIG_KEY);
|
|
|
+ if (StringUtils.isNotEmpty(json)) {
|
|
|
+ try {
|
|
|
+ return JSONUtil.toBean(json, StoreConfig.class);
|
|
|
+ } catch (Exception e) {
|
|
|
+ stringRedisTemplate.delete(OPT_STORE_CONFIG_KEY);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ String configJson = configService.selectConfigByKey("store.config");
|
|
|
+ if (StringUtils.isEmpty(configJson)) {
|
|
|
+ return new StoreConfig();
|
|
|
+ }
|
|
|
+ stringRedisTemplate.opsForValue().set(OPT_STORE_CONFIG_KEY, configJson, 10, TimeUnit.MINUTES);
|
|
|
+ return JSONUtil.toBean(configJson, StoreConfig.class);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void cachePendingAppUserSync(LiveOrder liveOrder) {
|
|
|
+ if (liveOrder.getOrderId() == null || liveOrder.getAppUserId() == null) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ String key = LIVE_APP_USER_SYNC_PENDING_KEY_PREFIX + liveOrder.getOrderId();
|
|
|
+ redisCache.setCacheObject(key, String.valueOf(liveOrder.getAppUserId()), 15, TimeUnit.DAYS);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void resolveMaskedPhoneBeforeStore(LiveOrder liveOrder) {
|
|
|
+ if (liveOrder.getUserPhone() == null || !PhoneUtils.isMaskedPhone(liveOrder.getUserPhone())) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ FsUser fsUser = userService.selectFsUserById(Long.parseLong(liveOrder.getUserId()));
|
|
|
+ if (fsUser != null && StringUtils.isNotEmpty(fsUser.getPhone())) {
|
|
|
+ String maskedPhone = PhoneUtils.toMaskedPhone(fsUser.getPhone());
|
|
|
+ if (maskedPhone != null && maskedPhone.equals(liveOrder.getUserPhone())) {
|
|
|
+ liveOrder.setUserPhone(fsUser.getPhone());
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (liveOrder.getAddressId() != null) {
|
|
|
+ getPhoneByHttp(liveOrder);
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("[opt] 处理脱敏手机号替换异常, userId:{}", liveOrder.getUserId(), e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void getPhoneByHttp(LiveOrder liveOrder) {
|
|
|
+ try {
|
|
|
+ String encryptedStr = CrossServiceRsaUtil.encryptForRequest("phone");
|
|
|
+ String url = "http://42.194.245.189:8010/app/common/getEncryptedPhone";
|
|
|
+ org.springframework.web.client.RestTemplate restTemplate = new org.springframework.web.client.RestTemplate();
|
|
|
+ Map<String, Object> paramMap = new HashMap<>();
|
|
|
+ paramMap.put("encryptedStr", encryptedStr);
|
|
|
+ paramMap.put("addressId", liveOrder.getAddressId());
|
|
|
+ org.springframework.http.HttpHeaders headers = new org.springframework.http.HttpHeaders();
|
|
|
+ headers.setContentType(org.springframework.http.MediaType.APPLICATION_JSON);
|
|
|
+ org.springframework.http.HttpEntity<Map<String, Object>> requestEntity =
|
|
|
+ new org.springframework.http.HttpEntity<>(paramMap, headers);
|
|
|
+ String responseBody = restTemplate.postForObject(url, requestEntity, String.class);
|
|
|
+ liveOrder.setChannel(responseBody + "|" + liveOrder.getAddressId());
|
|
|
+ if (StringUtils.isNotEmpty(responseBody)) {
|
|
|
+ com.alibaba.fastjson.JSONObject respObj = JSON.parseObject(responseBody);
|
|
|
+ if (respObj != null && "200".equals(respObj.getString("code"))) {
|
|
|
+ String encryptedPhone = respObj.getString("data");
|
|
|
+ if (StringUtils.isNotEmpty(encryptedPhone)) {
|
|
|
+ String realPhone = CrossServiceRsaUtil.decryptResponse(encryptedPhone);
|
|
|
+ if (StringUtils.isNotEmpty(realPhone)) {
|
|
|
+ liveOrder.setUserPhone(realPhone);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (Exception ex) {
|
|
|
+ log.error("[opt] 请求加密手机号失败, userId:{}, addressId:{}", liveOrder.getUserId(), liveOrder.getAddressId(), ex);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Data
|
|
|
+ public static class ShippingTemplateCacheData {
|
|
|
+ private Map<Long, Integer> shippingTemplatesMap;
|
|
|
+ private Map<Long, FsShippingTemplatesRegion> shippingTemplatesRegionMap;
|
|
|
+
|
|
|
+ public ShippingTemplateCacheData() {
|
|
|
+ }
|
|
|
+
|
|
|
+ public ShippingTemplateCacheData(Map<Long, Integer> shippingTemplatesMap,
|
|
|
+ Map<Long, FsShippingTemplatesRegion> shippingTemplatesRegionMap) {
|
|
|
+ this.shippingTemplatesMap = shippingTemplatesMap;
|
|
|
+ this.shippingTemplatesRegionMap = shippingTemplatesRegionMap;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private BigDecimal handleDeliveryMoney(Long cityId, FsStoreProduct fsStoreProduct, String totalNumSize,
|
|
|
+ FsStoreProductAttrValue fsStoreProductAttrValue) {
|
|
|
+ if (cityId == null || fsStoreProduct == null || fsStoreProduct.getTempId() == null) {
|
|
|
+ return BigDecimal.ZERO;
|
|
|
+ }
|
|
|
+ BigDecimal storePostage = BigDecimal.ZERO;
|
|
|
+ String ids = String.valueOf(fsStoreProduct.getTempId());
|
|
|
+ if (4190L == cityId) {
|
|
|
+ cityId = 419001L;
|
|
|
+ }
|
|
|
+ String cityIds = cityId + ",0";
|
|
|
+ String cacheKey = String.format("shipping:template:%s:city:%s", ids, cityIds);
|
|
|
+
|
|
|
+ String cacheJson = stringRedisTemplate.opsForValue().get(cacheKey);
|
|
|
+ Map<Long, Integer> shippingTemplatesMap = null;
|
|
|
+ Map<Long, FsShippingTemplatesRegion> shippingTemplatesRegionMap = null;
|
|
|
+
|
|
|
+ if (StringUtils.isNotEmpty(cacheJson)) {
|
|
|
+ try {
|
|
|
+ ShippingTemplateCacheData cacheData = JSON.parseObject(cacheJson, ShippingTemplateCacheData.class);
|
|
|
+ if (cacheData != null) {
|
|
|
+ shippingTemplatesMap = cacheData.getShippingTemplatesMap();
|
|
|
+ shippingTemplatesRegionMap = cacheData.getShippingTemplatesRegionMap();
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ stringRedisTemplate.delete(cacheKey);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (shippingTemplatesMap == null || shippingTemplatesRegionMap == null) {
|
|
|
+ List<FsShippingTemplates> shippingTemplatesList = liveOrderOptMapper.selectShippingTemplatesByIds(ids);
|
|
|
+ List<FsShippingTemplatesRegion> shippingTemplatesRegionList =
|
|
|
+ liveOrderOptMapper.selectShippingTemplatesRegionByTempAndCity(ids, cityIds);
|
|
|
+ shippingTemplatesMap = new HashMap<>(shippingTemplatesList.size());
|
|
|
+ for (FsShippingTemplates template : shippingTemplatesList) {
|
|
|
+ shippingTemplatesMap.put(template.getId(), template.getType());
|
|
|
+ }
|
|
|
+ shippingTemplatesRegionMap = new HashMap<>(shippingTemplatesRegionList.size());
|
|
|
+ for (FsShippingTemplatesRegion region : shippingTemplatesRegionList) {
|
|
|
+ shippingTemplatesRegionMap.put(region.getTempId(), region);
|
|
|
+ }
|
|
|
+ ShippingTemplateCacheData cacheData = new ShippingTemplateCacheData(shippingTemplatesMap, shippingTemplatesRegionMap);
|
|
|
+ stringRedisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(cacheData), 12, TimeUnit.HOURS);
|
|
|
+ }
|
|
|
+
|
|
|
+ Long tempId = Long.valueOf(fsStoreProduct.getTempId());
|
|
|
+ double num = 0d;
|
|
|
+ Integer templateType = shippingTemplatesMap.get(tempId);
|
|
|
+ FsStoreProductAttrValue productAttrValue = fsStoreProductAttrValue;
|
|
|
+ if (productAttrValue == null) {
|
|
|
+ List<FsStoreProductAttrValue> productAttrValues = getAttrValueByProductCached(fsStoreProduct.getProductId());
|
|
|
+ if (productAttrValues.isEmpty()) {
|
|
|
+ return storePostage;
|
|
|
+ }
|
|
|
+ productAttrValue = productAttrValues.get(0);
|
|
|
+ }
|
|
|
+ Integer totalNum = Integer.valueOf(totalNumSize);
|
|
|
+ if (ShippingTempEnum.TYPE_1.getValue().equals(templateType)) {
|
|
|
+ num = totalNum.doubleValue();
|
|
|
+ } else if (ShippingTempEnum.TYPE_2.getValue().equals(templateType)) {
|
|
|
+ num = NumberUtil.mul(totalNum, productAttrValue.getWeight()).doubleValue();
|
|
|
+ } else if (ShippingTempEnum.TYPE_3.getValue().equals(templateType)) {
|
|
|
+ num = NumberUtil.mul(totalNum, productAttrValue.getVolume()).doubleValue();
|
|
|
+ }
|
|
|
+ FsShippingTemplatesRegion shippingTemplatesRegion = shippingTemplatesRegionMap.get(tempId);
|
|
|
+ if (shippingTemplatesRegion == null) {
|
|
|
+ throw new CustomException("当前地址,商品不在快递配送范围内!");
|
|
|
+ }
|
|
|
+ BigDecimal price = NumberUtil.round(NumberUtil.mul(totalNum, fsStoreProduct.getPrice()), 2);
|
|
|
+ TemplateDTO templateDTO = TemplateDTO.builder()
|
|
|
+ .number(num)
|
|
|
+ .price(price)
|
|
|
+ .first(shippingTemplatesRegion.getFirst().doubleValue())
|
|
|
+ .firstPrice(shippingTemplatesRegion.getFirstPrice())
|
|
|
+ .continues(shippingTemplatesRegion.getContinues().doubleValue())
|
|
|
+ .continuePrice(shippingTemplatesRegion.getContinuePrice())
|
|
|
+ .tempId(tempId)
|
|
|
+ .cityId(cityId)
|
|
|
+ .build();
|
|
|
+ if (Double.compare(templateDTO.getNumber(), templateDTO.getFirst()) <= 0) {
|
|
|
+ storePostage = NumberUtil.round(NumberUtil.add(storePostage, templateDTO.getFirstPrice()), 2);
|
|
|
+ } else {
|
|
|
+ BigDecimal firstPrice = NumberUtil.add(storePostage, templateDTO.getFirstPrice());
|
|
|
+ if (templateDTO.getContinues() <= 0) {
|
|
|
+ storePostage = firstPrice;
|
|
|
+ } else {
|
|
|
+ double average = Math.ceil(NumberUtil.div(NumberUtil.sub(templateDTO.getNumber(), templateDTO.getFirst()),
|
|
|
+ templateDTO.getContinues().doubleValue()));
|
|
|
+ storePostage = NumberUtil.add(firstPrice, NumberUtil.mul(average, templateDTO.getContinuePrice()));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return storePostage;
|
|
|
+ }
|
|
|
+}
|