package com.liquidnet.service.order.service.impl;

import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.liquidnet.commons.lang.util.*;
import com.liquidnet.service.base.ResponseDto;
import com.liquidnet.service.base.SqlMapping;
import com.liquidnet.service.base.UserPathDto;
import com.liquidnet.service.base.constant.MQConst;
import com.liquidnet.service.dragon.dto.DragonPayBaseReqDto;
import com.liquidnet.service.dragon.dto.DragonPayBaseRespDto;
import com.liquidnet.service.dragon.dto.DragonPayOrderQueryRespDto;
import com.liquidnet.service.dragon.service.IDragonOrdersService;
import com.liquidnet.service.goblin.constant.GoblinRedisConst;
import com.liquidnet.service.goblin.constant.GoblinStatusConst;
import com.liquidnet.service.goblin.constant.NftAccStatusEnum;
import com.liquidnet.service.goblin.dto.GoblinUserNftAccInfoVo;
import com.liquidnet.service.goblin.dto.vo.*;
import com.liquidnet.service.goblin.entity.GoblinGoodsSku;
import com.liquidnet.service.goblin.entity.GoblinNftOrder;
import com.liquidnet.service.goblin.param.GoblinNftOrderPayAgainParam;
import com.liquidnet.service.goblin.param.GoblinNftOrderPayCallbackParam;
import com.liquidnet.service.goblin.param.GoblinNftOrderPayParam;
import com.liquidnet.service.goblin.param.GoblinNftOrderRefundCallbackParam;
import com.liquidnet.service.goblin.service.IGoblinNftOrderService;
import com.liquidnet.service.order.utils.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static com.liquidnet.commons.lang.util.DateUtil.DTF_YMD_HMS;

@Service
@Slf4j
public class GoblinNftOrderServiceImpl implements IGoblinNftOrderService {

    @Autowired
    GoblinRedisUtils goblinRedisUtils;
    @Autowired
    GoblinMongoUtils goblinMongoUtils;
    @Autowired
    QueueUtils queueUtils;
    @Autowired
    GoblinOrderUtils goblinOrderUtils;
    @Autowired
    GoblinNftOrderUtils nftOrderUtils;
    @Autowired
    private IDragonOrdersService dragonOrdersService;

    /*@Value("${liquidnet.service.order.url-pay.pay}")
    private String payUrl;*/
    /*@Value("${liquidnet.service.order.url-pay.check}")
    private String checkUrl;*/
    @Value("${liquidnet.service.order.url-pay.nftPayNotify}")
    private String synUrl;

    @Override
    public ResponseDto<GoblinNftPayResultVo> checkOrder(GoblinNftOrderPayParam payParam) {
        String uid = CurrentUtil.getCurrentUid();
        // 加锁
        boolean isLock = nftOrderUtils.setNftOrderBuyLock(uid);
        if (!isLock) {
            return ResponseDto.failure("排队处理中，请勿重复操作～");
        } else {
            try {
                // 认证验证
                GoblinUserNftAccInfoVo openAccountInfo = goblinRedisUtils.getOpenAccountInfo(uid);
                if (null == openAccountInfo || !NftAccStatusEnum.StatusAcc.SUCCESS.getCode().equals(openAccountInfo.getCode())) {
                    return ResponseDto.failure("您还未开通数字账户～");
                }


                // 基础参数
                String mobile = StringUtils.defaultString(((String) CurrentUtil.getTokenClaims().get(CurrentUtil.TOKEN_MOBILE)), "");
                String skuId = payParam.getSkuId();
                int number = 1;
                LocalDateTime nowTime = LocalDateTime.now();
                HashMap<String, Object> map = goblinRedisUtils.getGoodsSkuInfoVo(nowTime, skuId);
                GoblinGoodsSkuInfoVo skuVo = (GoblinGoodsSkuInfoVo) map.get("vo");
                String listId = (String) map.get("listId");
                Integer whiteType = (Integer) map.get("whiteType");

                // 若没有分批购 若开启了预约 判断用户是否预约
                if (null == listId) {
                    boolean userAboutAut = nftOrderUtils.getUserAboutAut(payParam.getSkuId(), uid);
                    if (!userAboutAut) {
                        return ResponseDto.failure("此数字藏品仅限预约用户购买～");
                    }
                }

                // 分批、提前购黑、白名单
                if (null != listId) {
                    Boolean listCanBuy = goblinRedisUtils.getListCanBuy(listId, skuId, mobile, uid, whiteType);
                    if (!listCanBuy) {
                        return ResponseDto.failure("该藏品仅对部分用户开放～");
                    }
                }

                // 是否存在此商品信息 是否隐藏
                if (!nftOrderUtils.getSkuAllStatusShow(skuVo)) {
                    return ResponseDto.failure("该商品不存在～");
                }

                // 单独设置的是否可购买
                if (null != skuVo.getSkuCanbuy() && skuVo.getSkuCanbuy().equals("0")) {
                    return ResponseDto.failure("该商品当前状态不可购买～");
                }
                // 单独设置的售罄
                if (StringUtils.equals("1", skuVo.getSoldoutStatus())) {
                    return ResponseDto.failure("该商品已售罄～");
                }
                // 判断开售、停售时间
                LocalDateTime saleStartTime = skuVo.getSaleStartTime();
                LocalDateTime saleStopTime = skuVo.getSaleStopTime();
                if (nowTime.isBefore(saleStartTime)) {
                    return ResponseDto.failure("该商品还未开始售卖～");
                } else if (null != saleStopTime && nowTime.isAfter(saleStopTime)) {
                    return ResponseDto.failure("该商品已停售～");
                }
                // 判断数量限购 盲盒暂时没限购但是要记录购买数量 所以也放在这里 这样既能都记录限购数量又不至于判断处理麻烦
                Integer limitCount = skuVo.getBuyLimit();
                // 是否限购都记录一个购买数量 防止中间增加限购 开始没加后面判断库存减了也是有问题的 或者放在最后也行 因为对单用户限流了
                int buyCount = goblinRedisUtils.incrSkuCountByUid(uid, skuId, number);
                if (null != limitCount && limitCount > 0) {
                    if (buyCount > limitCount) {
                        goblinRedisUtils.decrSkuCountByUid(uid, skuId, number);
                        return ResponseDto.failure("您已超出限购数量～");
                    }
                }
                // 权限限购 分批购不用这个规则
                if (null == listId) {
                    boolean isVip = nftOrderUtils.isVipMember(uid);
                    String buyFactor = skuVo.getBuyFactor();
                    if (null == buyFactor) {
                        buyFactor = "0";
                    }
                    Boolean isAuthBuy = goblinOrderUtils.judgeOrderRose(isVip, skuId, mobile, Integer.parseInt(buyFactor));
                    if (!isAuthBuy) {
                        return ResponseDto.failure("该商品仅限特定用户购买～");
                    }
                }
                // 判断优惠券不能一起使用
                /*String platVoucherCode = payParam.getPlatVoucherCode();
                String storeVoucherCode = payParam.getStoreVoucherCode();
                if (!StringUtils.isEmpty(platVoucherCode) && !StringUtils.isEmpty(storeVoucherCode)) {
                    return ResponseDto.failure("平台券与店铺券不可一起使用哦～");
                }*/
                String boxSkuId = "";
                // 购买盲盒回滚抽到的库存 购买藏品回滚购买的库存
                String stockSkuId = skuId;
                String spuId = skuVo.getSpuId();
                if (skuVo.getUnbox().equals("1")) {// 盲盒逻辑
                    GoblinGoodsInfoVo spuInfoVo = goblinRedisUtils.getGoodsInfoVo(spuId);
                    List<String> skuIdList = spuInfoVo.getSkuIdList();
                    if (CollectionUtil.isEmpty(skuIdList)) {
                        return ResponseDto.failure("该商品SPU不存在～");
                    } else {
//                        HashMap<String, Object> lotteryDrawMap = lotteryDraw(skuIdList, number, nowTime);
                        GoblinGoodsSkuInfoVo skuInfoVo = lotteryDraw(skuIdList, number, nowTime, listId);
                        if (null == skuInfoVo) {
                            goblinRedisUtils.decrSkuCountByUid(uid, skuId, number);
                            return ResponseDto.failure("盲盒库存不足啦～");
                        }
//                        GoblinGoodsSkuInfoVo skuInfoVo = (GoblinGoodsSkuInfoVo) lotteryDrawMap.get("goodsSkuInfoVo");
                        boxSkuId = skuInfoVo.getSkuId();
                        stockSkuId = boxSkuId;
//                        listId = (String) lotteryDrawMap.get("listId");
                    }
                } else {// 普通藏品逻辑
                    // 判断库存
                    int surplusGeneral = goblinRedisUtils.decrSkuStock(listId, skuId, number);
                    if (surplusGeneral < 0) {
                        nftOrderUtils.backSkuCountAndStock(listId, uid, stockSkuId, skuId, number);
                        return ResponseDto.failure("库存不足啦～");
                    }
                }

                /**
                 * 判断优惠券
                 * 因平台券和店铺券不能同时使用所以不需要相互考虑失败退另一种券的问题,各自处理就好
                 * 回滚好库存和用户已购买数量
                 * 无论平台券还是店铺券都只支持代金券1的类型
                 */
                BigDecimal voucherPrice = BigDecimal.ZERO;
                BigDecimal storeVoucherPrice = BigDecimal.ZERO;
                String orderId = IDGenerator.nextSnowId();
                String orderCode = IDGenerator.storeCode(orderId);
                BigDecimal totalPrice = skuVo.getPrice();
                if (payParam.getPayType().equals("applepay")) {
                    totalPrice = skuVo.getPriceV();
                }
                /*if (!StringUtils.isEmpty(platVoucherCode)) {// 平台券
                    GoblinUseResultVo useResultVo = nftOrderUtils.useCoupon(platVoucherCode, "购买NFT商品[" + orderCode + "]", totalPrice, spuId, uid);
                    String typeVoucher = useResultVo.getCouType();
                    if (typeVoucher.equals("-1")) {
                        nftOrderUtils.backSkuCountAndStock(uid, skuId, number);
                        return ResponseDto.failure("当前平台券不允许使用～");
                    } else {
                        voucherPrice = useResultVo.getValue();
                    }
                }
                if (!StringUtils.isEmpty(storeVoucherCode)) {// 店铺券
                    GoblinUseResultVo storeCouponVo = nftOrderUtils.useStoreCoupon(storeVoucherCode, "购买NFT商品[" + orderCode + "]", totalPrice, spuId, uid);
                    String typeVoucher = storeCouponVo.getCouType();
                    if (typeVoucher.equals("-1")) {
                        nftOrderUtils.backSkuCountAndStock(uid, skuId, number);
                        return ResponseDto.failure("当前店铺券不允许使用～");
                    } else {
                        storeVoucherPrice = storeCouponVo.getValue();
                    }
                }*/

                // 下单数据
                int orderType1 = GoblinStatusConst.NftStatus.ORDER_TYPE_1.getValue();
                GoblinNftOrder nftOrder = order(payParam, skuVo.getStoreId(), uid, spuId, number, orderId, orderCode, totalPrice, voucherPrice, storeVoucherPrice, boxSkuId, skuVo.getName() + "  " + skuVo.getSubtitle(), orderType1, listId, "");
                if (null == nftOrder) {
                    nftOrderUtils.backSkuCountAndStock(listId, uid, stockSkuId, skuId, number);
                    return ResponseDto.failure("下单失败～");
                }

                // 下单唤起支付
                GoblinNftPayResultVo nftPayResultVo = payOrder(nftOrder, payParam, skuVo.getProductId());
                if (null == nftPayResultVo) {
                    nftOrderUtils.backSkuCountAndStock(listId, uid, stockSkuId, skuId, number);
                    return ResponseDto.failure("下单失败啦～");
                }
                return ResponseDto.success(nftPayResultVo);
            } catch (Exception e) {
                log.error("NFT下单异常 e:{}", e);
                return ResponseDto.failure("下单失败，请稍后重试！");
            } finally {
                // 执行完毕。释放锁
                nftOrderUtils.delNftOrderBuyLock(uid);
            }
        }
    }

    private GoblinGoodsSkuInfoVo lotteryDraw(List<String> skuIdList, int number, LocalDateTime nowTime, String listId) {
//    private HashMap<String, Object> lotteryDraw(List<String> skuIdList, int number, LocalDateTime nowTime) {
        try {
            ArrayList<GoblinGoodsSkuInfoVo> skuInfoVos = ObjectUtil.cloneArrayGoblinGoodsSkuInfoListVo();
//            ArrayList<String> listIds = ObjectUtil.cloneArrayListString();
            for (String kid : skuIdList) {
                HashMap<String, Object> map = goblinRedisUtils.getGoodsSkuInfoVo(nowTime, kid);
                GoblinGoodsSkuInfoVo skuInfoVo = (GoblinGoodsSkuInfoVo) map.get("vo");
//                String listId = (String) map.get("listId");
                // 是盲盒的扔掉
                if (skuInfoVo.getUnbox().equals("1")) {
                    continue;
                }
                // 不能购买的 没库存的 概率是0的 过滤
                if (nftOrderUtils.getSkuAllStatusShow(skuInfoVo) && nftOrderUtils.getSkuAllStatusStock(listId, skuInfoVo) > 0) {
                    skuInfoVos.add(skuInfoVo);
//                    listIds.add(listId);
                }
            }
            if (CollectionUtil.isEmpty(skuInfoVos)) {
                return null;
            } else {
                // 计算总概率 和 剩余没填概率的平均概率
                int size = 0;
                BigDecimal sumHitRatio = BigDecimal.ZERO;
                for (GoblinGoodsSkuInfoVo skuInfoVo : skuInfoVos) {
                    if (null == skuInfoVo.getHitRatio()) {
                        size++;
                    } else {
                        sumHitRatio = sumHitRatio.add(skuInfoVo.getHitRatio());
                    }
                }
                /**
                 * 剔除掉没库存的商品再去算未设置概率商品的平均概率 会导致未设置概率的商品的概率增加
                 * 如果不剔除掉没库存的商品去算未设置概率商品的平均概率 会导致总概率小于100 如果随机数还是0-100的话就有可能会中奖溢出
                 * 想保持概率 1.不剔除无库存商品算平均概率 2.随机数最大值为排序后概率的最后一个值
                 */
                BigDecimal avgHitRatio = BigDecimal.ZERO;
                if (size > 0) {// 说明有未设置抽奖概率的
                    avgHitRatio = new BigDecimal(100).subtract(sumHitRatio).divide(new BigDecimal(size), 2, RoundingMode.HALF_UP);
                }

                // 未设置概率的写入概率
                ArrayList<GoblinGoodsSkuInfoVo> newSkuInfoVos = ObjectUtil.cloneArrayGoblinGoodsSkuInfoListVo();
//                ArrayList<String> newListIds = ObjectUtil.cloneArrayListString();
                int skuListSize = skuInfoVos.size();
                for (int i = 0; i < skuListSize; i++) {
                    GoblinGoodsSkuInfoVo infoVo = skuInfoVos.get(i);
                    if (null == infoVo.getHitRatio() || infoVo.getHitRatio().compareTo(BigDecimal.ZERO) < 0) {
                        /**
                         * 算所得平均概率是0 即中不了 剔除掉
                         * 但是这么处理会导致前端页面显示有库存 买的时候显示没有
                         * 所以要么概率加起来必须100 要么前端计算盲盒总概率的时候也要同样处理
                         */
                        if (avgHitRatio.compareTo(BigDecimal.ZERO) <= 0) {
                            continue;
                        } else {
                            infoVo.setHitRatio(avgHitRatio);
                        }
                    }
                    // 等于0的最终概率就是设置的值 大于0最终的概率是自己的+上面的
                    if (i > 0) {
                        infoVo.setHitRatio(skuInfoVos.get(i - 1).getHitRatio().add(infoVo.getHitRatio()));
                    }
                    newSkuInfoVos.add(infoVo);
//                    newListIds.add(listIds.get(i));
                }


                if (CollectionUtil.isEmpty(newSkuInfoVos)) {
                    log.info("该盲盒概率超过100导致不能卖 skuIdList:{}", skuIdList);
                    return null;
                }
                // 按照概率排序 按照上面的运算最后一定是最大的不用排序了
//                List<GoblinGoodsSkuInfoVo> listSort = newSkuInfoVos.stream().sorted(Comparator.comparing(GoblinGoodsSkuInfoVo::getHitRatio)).collect(Collectors.toList());
                List<BigDecimal> hitRatioList = newSkuInfoVos.stream().map(GoblinGoodsSkuInfoVo::getHitRatio).collect(Collectors.toList());
                // 根据区块值来获取抽取到的物品索引
                double nextDouble = Math.random();
                BigDecimal nextDoubleNew = BigDecimal.valueOf(nextDouble);
                nextDoubleNew = nextDoubleNew.multiply(hitRatioList.get(hitRatioList.size() - 1)).setScale(4, RoundingMode.HALF_UP);

                hitRatioList.add(nextDoubleNew);
                Collections.sort(hitRatioList);

                int index = hitRatioList.indexOf(nextDoubleNew);

                GoblinGoodsSkuInfoVo goodsSkuInfoVo = newSkuInfoVos.get(index);
//                String endListId = newListIds.get(index);
                // 判断库存
                int surplusGeneral = goblinRedisUtils.decrSkuStock(listId, goodsSkuInfoVo.getSkuId(), number);
                if (surplusGeneral < 0) {
                    goblinRedisUtils.incrSkuStock(listId, goodsSkuInfoVo.getSkuId(), number);
                    return lotteryDraw(skuIdList, number, nowTime, listId);
                } else {
//                    HashMap<String, Object> map = CollectionUtil.mapStringObject();
//                    map.put("goodsSkuInfoVo", goodsSkuInfoVo);
//                    map.put("listId", endListId);
//                    return map;
                    return goodsSkuInfoVo;
                }
            }
        } catch (Exception e) {
            log.error("NFT下单-抽盲盒异常", e);
            return null;
        }
    }

    private GoblinNftOrder order(
            GoblinNftOrderPayParam payParam, String storeId, String uid, String spuId, int number,
            String orderId, String orderCode,
            BigDecimal totalPrice, BigDecimal voucherPrice, BigDecimal storeVoucherPrice, String boxSkuId, String skuTitle
            , int orderType, String listId, String exCode
    ) {
        try {
            LocalDateTime now = LocalDateTime.now();

            Map token = CurrentUtil.getTokenClaims();
            String headerCliSource = CurrentUtil.getHeaderCliSource(), headerCliVersion = CurrentUtil.getHeaderCliVersion();
            String source = headerCliSource == null ? "" : headerCliSource;
            String version = headerCliVersion == null ? "" : headerCliVersion;

            GoblinStoreInfoVo storeInfoVo = goblinRedisUtils.getStoreInfoVo(storeId);
            String storeName = storeInfoVo.getStoreName();

            listId = (null == listId) ? "" : listId;

            // 生成订单
            GoblinNftOrder nftOrder = GoblinNftOrder.getNew();
            nftOrder.setOrderId(orderId);
            nftOrder.setSpuId(spuId);
            nftOrder.setSkuId(payParam.getSkuId());
            nftOrder.setSkuTitle(skuTitle);
            nftOrder.setBoxSkuId(boxSkuId);
            nftOrder.setNum(number);
            nftOrder.setStoreId(storeId);
            nftOrder.setStoreName(storeName);
            nftOrder.setOrderCode(orderCode);
            nftOrder.setUserId(uid);
            if (null == token) {
                nftOrder.setUserName("");
                nftOrder.setUserMobile("");
            } else {
                nftOrder.setUserName(StringUtils.defaultString(((String) token.get(CurrentUtil.TOKEN_NICKNAME)), ""));
                nftOrder.setUserMobile(StringUtils.defaultString(((String) token.get(CurrentUtil.TOKEN_MOBILE)), ""));
            }
            nftOrder.setListId(listId);
            nftOrder.setExCode(exCode);
            nftOrder.setPriceTotal(totalPrice);
            nftOrder.setPriceCoupon(voucherPrice);
            nftOrder.setStorePriceCoupon(storeVoucherPrice);
            nftOrder.setPriceRedEnvelope(BigDecimal.ZERO);
            nftOrder.setPriceVoucher(voucherPrice.add(storeVoucherPrice));
            BigDecimal priceActual = totalPrice.subtract(voucherPrice).subtract(storeVoucherPrice);
            nftOrder.setPriceActual(priceActual.compareTo(BigDecimal.ZERO) < 0 ? BigDecimal.ZERO : priceActual);
            /*nftOrder.setUcouponId(payParam.getPlatVoucherCode());
            nftOrder.setStoreCouponId(payParam.getStoreVoucherCode());*/
            nftOrder.setUcouponId("");
            nftOrder.setStoreCouponId("");
            nftOrder.setRedEnvelopeCode("");
            nftOrder.setStatus(GoblinStatusConst.NftStatus.ORDER_STATUS_1.getValue());
            nftOrder.setSource(source);
            nftOrder.setOrderType(orderType);
            nftOrder.setPayType(payParam.getPayType());
            nftOrder.setDeviceFrom(payParam.getDeviceFrom());
            nftOrder.setVersion(version);
            nftOrder.setPayCountdownMinute(5);
            nftOrder.setIpAddress(CurrentUtil.getCliIpAddr());
            nftOrder.setCreatedAt(now);

            return nftOrder;
        } catch (Exception e) {
            log.error("NFT下单-整合订单数据异常", e);
            return null;
        }
    }

    private GoblinNftPayResultVo payOrder(GoblinNftOrder nftOrder, GoblinNftOrderPayParam payParam, String productId) {
        try {
            // 是否免费
            boolean isFree = false;
            GoblinNftPayResultVo NftPayResultVo = GoblinNftPayResultVo.getNew();
            NftPayResultVo.setOrderId(nftOrder.getOrderId());
            String payCode;
            String showUrl = payParam.getShowUrl().concat(nftOrder.getOrderCode());
            String returnUrl = payParam.getReturnUrl().concat(nftOrder.getOrderCode());
            if (nftOrder.getPriceActual().compareTo(BigDecimal.valueOf(0)) > 0) {// 调用支付
                if (nftOrder.getPayType().equals("applepay")) {
                    payCode = "";
                    NftPayResultVo.setCode(payCode);
                    NftPayResultVo.setOrderCode(nftOrder.getOrderCode());
                    HashMap<String, Object> payDataMap = CollectionUtil.mapStringObject();
                    payDataMap.put("productId", productId);
                    payDataMap.put("quantity", nftOrder.getNum());
                    NftPayResultVo.setPayData(payDataMap);
                    NftPayResultVo.setPayType(nftOrder.getPayType());
                    NftPayResultVo.setPrice(nftOrder.getPriceActual());
                    NftPayResultVo.setShowUrl(showUrl);
                    NftPayResultVo.setReturnUrl(returnUrl);
                } else {
                    /*LinkedMultiValueMap<String, String> httpData = CollectionUtil.linkedMultiValueMapStringString();
                    httpData.add("type", "NFT");
                    httpData.add("price", nftOrder.getPriceActual().toString());
                    // 测试0.01
                    // httpData.add("price","0.01");
                    httpData.add("name", nftOrder.getStoreName());
                    GoblinGoodsSkuInfoVo skuVo = goblinRedisUtils.getGoodsSkuInfoVo(nftOrder.getSkuId());
                    httpData.add("detail", skuVo.getName());
                    httpData.add("orderCode", nftOrder.getOrderCode());
                    httpData.add("orderId", nftOrder.getOrderId());
                    httpData.add("clientIp", CurrentUtil.getCliIpAddr());
                    httpData.add("notifyUrl", synUrl);
                    httpData.add("createDate", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
                    httpData.add("expireTime", "5");
                    httpData.add("payType", nftOrder.getPayType());
                    httpData.add("deviceFrom", nftOrder.getDeviceFrom());
                    if (nftOrder.getDeviceFrom().equals("js") || nftOrder.getDeviceFrom().equals("applet")) {
                        httpData.add("openId", payParam.getOpenId());
                    }
                    httpData.add("showUrl", showUrl);
                    httpData.add("returnUrl", returnUrl);

                    LinkedMultiValueMap<String, String> header = CollectionUtil.linkedMultiValueMapStringString();
                    header.add("Accept", "application/json;charset=UTF-8");
                    String returnData = HttpUtil.post(payUrl, httpData, header);
                    log.info("NFT支付调用 DRAGON 结果 = " + returnData);
                    ResponseDto<GoblinPayInnerResultVo> dto = JsonUtils.fromJson(returnData, new TypeReference<ResponseDto<GoblinPayInnerResultVo>>() {
                    });
                    GoblinPayInnerResultVo dtoData = dto.getData();*/

                    DragonPayBaseReqDto dragonPayBaseReqDto = DragonPayBaseReqDto.getNew();
                    dragonPayBaseReqDto.setPayType(nftOrder.getPayType());
                    dragonPayBaseReqDto.setDeviceFrom(nftOrder.getDeviceFrom());
                    if (nftOrder.getDeviceFrom().equals("js") || nftOrder.getDeviceFrom().equals("applet")) {
                        dragonPayBaseReqDto.setOpenId(payParam.getOpenId());
                    }
                    dragonPayBaseReqDto.setType("NFT");
                    dragonPayBaseReqDto.setPrice(nftOrder.getPriceActual());
                    String storeName = nftOrder.getStoreName();
                    if (StringUtil.isNotNull(storeName) && storeName.length() >= 32) {
                        storeName = storeName.substring(0, 32);
                    }
                    dragonPayBaseReqDto.setName(storeName);
                    dragonPayBaseReqDto.setDetail("正在现场");
                    dragonPayBaseReqDto.setOrderCode(nftOrder.getOrderCode());
                    dragonPayBaseReqDto.setOrderId(nftOrder.getOrderId());
                    dragonPayBaseReqDto.setClientIp(CurrentUtil.getCliIpAddr());
                    dragonPayBaseReqDto.setNotifyUrl(synUrl);
                    dragonPayBaseReqDto.setReturnUrl(returnUrl);
                    dragonPayBaseReqDto.setShowUrl(showUrl);
                    dragonPayBaseReqDto.setCreateDate(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
                    dragonPayBaseReqDto.setExpireTime("5");
                    ResponseDto<DragonPayBaseRespDto> responseDto = dragonOrdersService.dragonPay(dragonPayBaseReqDto);
                    String returnData = JsonUtils.toJson(responseDto);
                    log.info("NFT支付调用 DRAGON 结果 = " + returnData);
                    ResponseDto<GoblinPayInnerResultVo> dto = JsonUtils.fromJson(returnData, new TypeReference<ResponseDto<GoblinPayInnerResultVo>>() {
                    });
                    GoblinPayInnerResultVo dtoData = dto.getData();

                    payCode = dtoData.getCode();
                    NftPayResultVo.setCode(payCode);
                    NftPayResultVo.setOrderCode(nftOrder.getOrderCode());
                    NftPayResultVo.setPayData(dtoData.getPayData());
                    NftPayResultVo.setPayType(nftOrder.getPayType());
                    NftPayResultVo.setPrice(nftOrder.getPriceActual());
                    NftPayResultVo.setShowUrl(showUrl);
                    NftPayResultVo.setReturnUrl(returnUrl);
                }
            } else {
                isFree = true;
                nftOrder.setPayType("FREE");
                payCode = "FREE_PAY_CODE";
                NftPayResultVo.setPrice(BigDecimal.valueOf(0));
                NftPayResultVo.setPayType(nftOrder.getPayType());
            }

            LinkedList<String> sqls = CollectionUtil.linkedListString();
            sqls.add(SqlMapping.get("goblin_nft_order.insert"));
            LinkedList<Object[]> sqlDataOrder = CollectionUtil.linkedListObjectArr();

            nftOrder.setPayCode(payCode);
            sqlDataOrder.add(new Object[]{
                    nftOrder.getOrderId(), nftOrder.getSpuId(), nftOrder.getSkuId(), nftOrder.getBoxSkuId(),
                    nftOrder.getNum(), nftOrder.getStoreId(), nftOrder.getStoreName(), nftOrder.getOrderCode(), nftOrder.getUserId(), nftOrder.getUserName(), nftOrder.getUserMobile(), nftOrder.getPriceTotal(), nftOrder.getPriceCoupon(),
                    nftOrder.getStorePriceCoupon(), nftOrder.getPriceRedEnvelope(), nftOrder.getPriceVoucher(), nftOrder.getPriceActual(), nftOrder.getUcouponId(), nftOrder.getStoreCouponId(), nftOrder.getRedEnvelopeCode(), nftOrder.getStatus(), nftOrder.getSource(),
                    nftOrder.getOrderType(), nftOrder.getPayType(), nftOrder.getDeviceFrom(), nftOrder.getVersion(), nftOrder.getPayCountdownMinute(), nftOrder.getIpAddress(), nftOrder.getCreatedAt(), nftOrder.getPayCode(),
                    nftOrder.getSkuTitle(), nftOrder.getListId(), nftOrder.getExCode(),"",""
            });

            // 订单vo
            GoblinNftOrderVo orderVo = GoblinNftOrderVo.getNew().copy(nftOrder);
            orderVo.setCreatedAt(nftOrder.getCreatedAt());

            // 待支付发送队列
            queueUtils.sendMsgByRedisGoblinStock(orderVo.getOrderId(), nftOrder.getCreatedAt(), "NFT", 5);

            // redis 订单详情
            nftOrderUtils.setNftOrder(orderVo);
            nftOrderUtils.setNftOrderIdOfCode(nftOrder.getOrderCode(), nftOrder.getOrderId());
            // redis 订单列表
            // nftOrderUtils.addNftOrderList(uid, orderVo.getOrderId());

            // mongo
            goblinOrderUtils.setMongoList(GoblinNftOrderVo.class.getSimpleName(), "orderId", orderVo.getOrderId(), GoblinRedisConst.REDIS_GOBLIN_NFT_ORDER_INFO, 1);
//            goblinMongoUtils.setGoblinNftOrderVo(orderVo);

            // 执行sql
            String sqlData = SqlMapping.gets(sqls, sqlDataOrder);
            queueUtils.sendMsgByRedis(MQConst.GoblinQueue.GOBLIN_NFT_ORDER.getKey(), sqlData);

            log.info(UserPathDto.setData("NFT下单(唤起支付)", nftOrder, NftPayResultVo));
            if (isFree) {// 免费直接回调
                GoblinNftOrderPayCallbackParam NftOrderPayCallbackParam = GoblinNftOrderPayCallbackParam.getNew();
                NftOrderPayCallbackParam.setCode(payCode);
                NftOrderPayCallbackParam.setOrderCode(nftOrder.getOrderCode());
                NftOrderPayCallbackParam.setPaymentAt(DateUtil.format(LocalDateTime.now(), DateUtil.Formatter.yyyyMMddHHmmss));
                NftOrderPayCallbackParam.setPrice(nftOrder.getPriceActual());
                NftOrderPayCallbackParam.setPaymentId("FREE_PAYMENT_ID");
                NftOrderPayCallbackParam.setPaymentType(null);
                NftOrderPayCallbackParam.setStatus(1);
                syncOrder(NftOrderPayCallbackParam);
            }

            return NftPayResultVo;
        } catch (Exception e) {
            log.error("NFT下单-生成订单唤起支付异常", e);
            return null;
        }
    }

    @Override
    public ResponseDto<GoblinNftPayResultVo> payAgain(GoblinNftOrderPayAgainParam param) {
        return ResponseDto.failure("暂不支持此接口");
        /*String uid = CurrentUtil.getCurrentUid();
        LocalDateTime now = LocalDateTime.now();
        GoblinNftOrderVo nftOrder = nftOrderUtils.getNftOrder(param.getOrderId());
        // 检查订单时间
        if (nftOrder == null) {
            return ResponseDto.failure("订单不存在");
        }
        if (!nftOrder.getUserId().equals(uid)) {
            return ResponseDto.failure(ErrorMapping.get("20003"));
        }
        if (nftOrder.getStatus() != GoblinStatusConst.Status.ORDER_STATUS_0.getValue()) {
            return ResponseDto.failure("当前订单状态不支持支付");
        }
        MultiValueMap<String, String> header = CollectionUtil.linkedMultiValueMapStringString();
        header.add("Accept", "application/json;charset=UTF-8");
        String returnCheckData = HttpUtil.get(checkUrl + "?code=" + nftOrder.getPayCode(), null, header);
        ResponseDto<GoblinPayInnerResultVo> checkVo = JsonUtils.fromJson(returnCheckData, new TypeReference<ResponseDto<GoblinPayInnerResultVo>>() {
        });
        if (checkVo.getData().getStatus() == 1) {
            return ResponseDto.failure("订单已支付");
        }
        // 获取支付参数
        GoblinNftPayResultVo NftPayResultVo = GoblinNftPayResultVo.getNew();
        if (!nftOrder.getPayType().equals("FREE")) {
            // 调用支付
            LinkedMultiValueMap<String, String> httpData = CollectionUtil.linkedMultiValueMapStringString();
            httpData.add("type", "NFT");
            httpData.add("price", nftOrder.getPriceActual().toString());
            httpData.add("name", nftOrder.getStoreName());
            httpData.add("detail", nftOrder.getSkuTitle());
            httpData.add("orderCode", nftOrder.getOrderCode());
            httpData.add("clientIp", nftOrder.getIpAddress());
            httpData.add("notifyUrl", synUrl);
            httpData.add("createDate", nftOrder.getCreatedAt().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
            httpData.add("expireTime", nftOrder.getPayCountdownMinute().toString());
            httpData.add("payType", param.getPayType());
            httpData.add("deviceFrom", param.getDeviceFrom());
            if (param.getDeviceFrom().equals("js") || param.getDeviceFrom().equals("applet")) {
                httpData.add("openId", param.getOpenId());
            }
            String showUrl = param.getShowUrl().concat(nftOrder.getOrderCode());
            String returnUrl = param.getReturnUrl().concat(nftOrder.getOrderCode());

            httpData.add("showUrl", showUrl);
            httpData.add("returnUrl", returnUrl);
            String returnData = HttpUtil.post(payUrl, httpData);
            log.info("调用 DRAGON 结果 = " + returnData);
            ResponseDto<GoblinPayInnerResultVo> dto = JsonUtils.fromJson(returnData, new TypeReference<ResponseDto<GoblinPayInnerResultVo>>() {
            });
            GoblinPayInnerResultVo dtoData = dto.getData();

            String payCode = dtoData.getCode();
            NftPayResultVo.setCode(payCode);
            NftPayResultVo.setOrderCode(nftOrder.getOrderCode());
            NftPayResultVo.setPayData(dto.getData().getPayData());
            NftPayResultVo.setOrderId(nftOrder.getOrderId());
            NftPayResultVo.setPayData(dtoData.getPayData());
            NftPayResultVo.setPayType(nftOrder.getPayType());
            NftPayResultVo.setPrice(nftOrder.getPriceActual());
            NftPayResultVo.setShowUrl(showUrl);
            NftPayResultVo.setReturnUrl(returnUrl);
            // redis
            nftOrder.setPayType(param.getPayType());
            nftOrder.setDeviceFrom(param.getDeviceFrom());
            nftOrder.setPayCode(NftPayResultVo.getCode());
            nftOrderUtils.setNftOrder(nftOrder);
            // mongo
            goblinOrderUtils.setMongoList(GoblinNftOrderVo.class.getSimpleName(), "orderId", nftOrder.getOrderId(), GoblinRedisConst.REDIS_GOBLIN_NFT_ORDER_INFO, 2);
            // mysql
            queueUtils.sendMsgByRedis(MQConst.GoblinQueue.GOBLIN_ORDER_AGAIN.getKey(),
                    SqlMapping.get("goblin_order.pay.again", param.getPayType(), param.getDeviceFrom(), NftPayResultVo.getCode(), param.getOrderId(), now, now));
            log.info(UserPathDto.setData("再次支付[新支付方式]", param, NftPayResultVo));
        }
        return ResponseDto.success(NftPayResultVo);*/
    }

    @Override
    public ResponseDto<Integer> checkOrderResult(String orderId) {
        String uid = CurrentUtil.getCurrentUid();
        GoblinNftOrderVo nftOrder = nftOrderUtils.getNftOrder(orderId);
        if (null == nftOrder || !nftOrder.getUserId().equals(uid)) {
            return ResponseDto.failure("订单不存在");
        } else {
            /*String returnCheckData = HttpUtil.get(checkUrl + "?code=" + nftOrder.getPayCode(), null);
            ResponseDto<GoblinNftOrderPayCallbackParam> syncOrderDtoParam = JsonUtils.fromJson(returnCheckData, new TypeReference<ResponseDto<GoblinNftOrderPayCallbackParam>>() {
            });*/
            DragonPayOrderQueryRespDto respDto = dragonOrdersService.checkOrderStatusByCode(nftOrder.getPayCode());
            if (respDto.getStatus() == 1) {
                //处理订单
                GoblinNftOrderPayCallbackParam goblinNftOrderPayCallbackParam = GoblinNftOrderPayCallbackParam.getNew().copy(respDto);
                syncOrder(goblinNftOrderPayCallbackParam);
                return ResponseDto.success(1);
            } else {
                return ResponseDto.success(0);
            }
        }
    }

    @Override
    public String syncOrder(GoblinNftOrderPayCallbackParam syncOrderParam) {
        log.info("NFT支付回调 参数: " + syncOrderParam.toString());

        String orderCode = syncOrderParam.getOrderCode();

        String orderId = nftOrderUtils.getNftOrderIdOfCode(orderCode);
        if (orderId.isEmpty()) {
            log.error("Nft订单不存在1 param:[orderCode:{}]", orderCode);
            return "fail";
        }
        GoblinNftOrderVo orderVo = nftOrderUtils.getNftOrder(orderId);
        if (null == orderVo) {
            log.error("Nft订单不存在2 param:[orderCode:{}]", orderCode);
            return "fail";
        }
        // 苹果支付下单时候没有PayCode
        if (!orderVo.getPayType().equals("applepay") && !orderVo.getPayCode().equals(syncOrderParam.getCode())) {
            log.error("Nft订单payCode不匹配 param:[orderCode:{}, payCode:{}, code:{}]", orderCode, orderVo.getPayCode(), syncOrderParam.getCode());
            return "fail";
        }
        if (
                !Objects.equals(orderVo.getStatus(), GoblinStatusConst.NftStatus.ORDER_STATUS_1.getValue())
                        &&
                        !Objects.equals(orderVo.getStatus(), GoblinStatusConst.NftStatus.ORDER_STATUS_3.getValue())
        ) {
            log.error("Nft订单当前状态不能支付回调 param:[orderCode:{}, status:{}]", orderCode, orderVo.getStatus());
            return "fail";
        }

        if (Objects.equals(1, syncOrderParam.getStatus())) {
            String uid = orderVo.getUserId();
            Integer orderStatusOld = orderVo.getStatus();
            LocalDateTime now = LocalDateTime.now();
            // 更新订单信息
            if (StringUtil.isEmpty(syncOrderParam.getPaymentType())) {
                orderVo.setPaymentType("");
            } else {
                orderVo.setPaymentType(syncOrderParam.getPaymentType());
            }
            if (StringUtil.isEmpty(syncOrderParam.getPaymentId())) {
                orderVo.setPaymentId("");
            } else {
                orderVo.setPaymentId(syncOrderParam.getPaymentId());
            }
            if (StringUtil.isEmpty(syncOrderParam.getCode())) {
                orderVo.setPayCode("");
            } else {
                orderVo.setPayCode(syncOrderParam.getCode());
            }
            if (StringUtil.isEmpty(syncOrderParam.getPaymentAt())) {
                orderVo.setPayTime(now);
            } else {
                LocalDateTime payTime = LocalDateTime.parse(syncOrderParam.getPaymentAt(), DTF_YMD_HMS);
                orderVo.setPayTime(payTime);
            }
            orderVo.setUpdatedAt(now);
            if (Objects.equals(orderStatusOld, GoblinStatusConst.NftStatus.ORDER_STATUS_3.getValue())) {// 超时支付 退款中状态
                orderVo.setStatus(GoblinStatusConst.NftStatus.ORDER_STATUS_4.getValue());
            } else {// 正常流程 已付款状态
                orderVo.setStatus(GoblinStatusConst.NftStatus.ORDER_STATUS_2.getValue());
            }
            // redis
            nftOrderUtils.setNftOrder(orderVo);
            // mongo
            goblinOrderUtils.setMongoList(GoblinNftOrderVo.class.getSimpleName(), "orderId", orderVo.getOrderId(), GoblinRedisConst.REDIS_GOBLIN_NFT_ORDER_INFO, 2);
//            goblinMongoUtils.updateGoblinNftOrderVo(orderVo);
            // mysql
            LinkedList<String> sqls = CollectionUtil.linkedListString();
            LinkedList<Object[]> sqlDataOrder = CollectionUtil.linkedListObjectArr();
            sqls.add(SqlMapping.get("goblin_nft_order.update.pay"));
            sqlDataOrder.add(new Object[]{
                    orderVo.getPaymentType(), orderVo.getPaymentId(), orderVo.getPayCode(),
                    orderVo.getPayTime(), orderVo.getStatus(), orderVo.getUpdatedAt(),
                    orderId, now, now
            });
            queueUtils.sendMsgByRedis(MQConst.GoblinQueue.GOBLIN_NFT_ORDER.getKey(),
                    SqlMapping.gets(sqls, sqlDataOrder));
            // 加积分
            goblinOrderUtils.integral(uid, orderVo.getPriceActual(), "购买数字藏品", 1);
            // 处理订单退款和其他流程
            if (Objects.equals(orderStatusOld, GoblinStatusConst.NftStatus.ORDER_STATUS_3.getValue())) {// 超时支付 退款
                log.info("Nft订单超时支付 param:[orderCode:{}]", orderCode);
                nftOrderUtils.refundOrderSku(orderId, syncOrderParam.getPaymentId(), syncOrderParam.getPaymentType(), orderVo.getPayType());
            } else {// 正常流程
                // 通知生成藏品
//                GoblinQueueBizArtworkGenDto artworkGenDto = GoblinQueueBizArtworkGenDto.getNew();
//                artworkGenDto.setOrderId(orderId);
//                artworkGenDto.setUid(uid);
//                artworkGenDto.setSkuId(orderVo.getSkuId());
//                artworkGenDto.setSource(1);
//                queueUtils.sendMsgByRedis(MQConst.GoblinQueue.BIZ_ARTWORK_GEN.getKey(), artworkGenDto.toJson());
                this.generateUserArtwork(orderVo, 1);
                // 增加销量
                goblinRedisUtils.incrSkuSaleCount(orderVo.getSpuId(), orderVo.getSkuId(), orderVo.getNum());
                // TODO: jxl 2022/4/24  判断技术部NFT记录Redis setBuyNftTec
            }
            // 写入用户订单列表 因取消的订单不展示 所以放在这里
            nftOrderUtils.addNftOrderList(uid, orderVo.getOrderId());
        }

        return "success";
    }

    @Override
    public String refundSyncOrder(GoblinNftOrderRefundCallbackParam refundCallbackParam) {
        LocalDateTime now = LocalDateTime.now();
        log.info("NftRefundCallback订单退款回调参数: [RefundCallbackParam={}]", refundCallbackParam);
        GoblinNftOrderRefundVo nftOrderRefundVo = goblinMongoUtils.getGoblinNftOrderRefundVo(refundCallbackParam.getOrderRefundCode());
        if (nftOrderRefundVo == null) {
            log.info("NftRefundCallback: 退款订单查询失败，编号{}", refundCallbackParam.getOrderRefundCode());
            return "fail";
        }
        if (nftOrderRefundVo.getStatus() == GoblinStatusConst.NftStatus.ORDER_REFUND_STATUS_2.getValue()) {
            log.info("NftRefundCallback: 退款订单已完成，编号{}", refundCallbackParam.getOrderRefundCode());
            return "success";
        }
        Integer status = refundCallbackParam.getStatus();
        if (1 == status) { // 退款成功
            String orderId = nftOrderRefundVo.getOrderId();
            GoblinNftOrderVo nftOrder = nftOrderUtils.getNftOrder(orderId);
            nftOrder.setStatus(GoblinStatusConst.NftStatus.ORDER_STATUS_5.getValue());
            nftOrder.setUpdatedAt(now);
            // 取消订单已经回滚了库存和购买数量 这里不需要处理了
            /*if (StringUtil.isNotEmpty(nftOrder.getUcouponId())) {
                goblinOrderUtils.backCoupon(nftOrder.getUcouponId(), nftOrder.getUserId());
            }

            if (StringUtil.isNotEmpty(nftOrder.getStoreCouponId())) {
                List<BackCouponParam> params = ObjectUtil.getBackCouponParam();
                BackCouponParam backCouponParam = BackCouponParam.getNew();
                backCouponParam.setuCouponIds(nftOrder.getStoreCouponId());
                backCouponParam.setUid(nftOrder.getUserId());
                params.add(backCouponParam);
                goblinOrderUtils.backStoreCoupon(params);
            }*/
            nftOrderRefundVo.setStatus(GoblinStatusConst.NftStatus.ORDER_REFUND_STATUS_2.getValue());
            nftOrderRefundVo.setRefundAt(now);
            nftOrderRefundVo.setUpdatedAt(now);
            //redis
            nftOrderUtils.setNftOrder(nftOrder);
            nftOrderUtils.setBackOrderVo(nftOrderRefundVo);
            //mongo
            goblinOrderUtils.setMongoList(GoblinNftOrderVo.class.getSimpleName(), "orderId", orderId, GoblinRedisConst.REDIS_GOBLIN_NFT_ORDER_INFO, 2);
//            goblinMongoUtils.updateGoblinNftOrderVo(nftOrder);
            HashMap<String, Object> updateRefundMdbMap = CollectionUtil.mapStringObject();
            updateRefundMdbMap.put("status", nftOrderRefundVo.getStatus());
            updateRefundMdbMap.put("refundAt", nftOrderRefundVo.getRefundAt());
            updateRefundMdbMap.put("updatedAt", nftOrderRefundVo.getUpdatedAt());
            goblinMongoUtils.updateGoblinNftOrderRefundVo(nftOrderRefundVo.getOrderRefundId(), updateRefundMdbMap);
            //mysql
            LinkedList<String> sqls = CollectionUtil.linkedListString();
            LinkedList<Object[]> sqlsOrder = CollectionUtil.linkedListObjectArr();
            LinkedList<Object[]> sqlsBackOrder = CollectionUtil.linkedListObjectArr();
            sqls.add(SqlMapping.get("goblin_nft_order.update.refund"));
            sqls.add(SqlMapping.get("goblin_nft_order_refund.refund"));
            sqlsOrder.add(new Object[]{
                    nftOrder.getStatus(), now,
                    orderId, now, now
            });
            sqlsBackOrder.add(new Object[]{
                    nftOrderRefundVo.getStatus(), now, now, nftOrderRefundVo.getOrderRefundId(), now, now
            });
            queueUtils.sendMsgByRedis(MQConst.GoblinQueue.GOBLIN_NFT_ORDER.getKey(),
                    SqlMapping.gets(sqls, sqlsOrder, sqlsBackOrder));
            //减积分
            goblinOrderUtils.integral(nftOrder.getUserId(), nftOrderRefundVo.getPrice(), "数字藏品退款", 2);
        } else if (0 == status) {
            log.info("NftRefundCallback: 支付平台返回状态为失败，编号{}", refundCallbackParam.getOrderRefundCode());
            return "fail";
        }
        return "success";
    }



    @Override
    public ResponseDto<String> exchange(String code, String uid, String deviceFrom, int orderType) {
        if (null == uid) {
            uid = CurrentUtil.getCurrentUid();
        }
        // 去除字符串中的空格、回车、换行符、制表符
        Pattern pattern = Pattern.compile("\\s*|\t|\r|\n");
        Matcher matcher = pattern.matcher(code);
        code = matcher.replaceAll("");

        boolean isLock = nftOrderUtils.setNftOrderExLock(code);
        if (!isLock) {
            return ResponseDto.failure("处理未完成，请稍等～");
        } else {
            try {
                LocalDateTime nowTime = LocalDateTime.now();
                GoblinNftExCodeVo goblinNftExCodeVo = GoblinNftExCodeVo.getNew();
                LinkedList<String> sqls = CollectionUtil.linkedListString();
                sqls.add(SqlMapping.get("goblin_nft_ex_code.update"));
                LinkedList<Object[]> sqlDataCode = CollectionUtil.linkedListObjectArr();
                if (5 == orderType) {
                    orderType = 4;
                    GoblinGoodsSkuInfoVo skuInfoVo = goblinRedisUtils.getGoodsSkuInfoVo(code);
                    goblinNftExCodeVo.setState(2);
                    goblinNftExCodeVo.setRedeemUid(uid);
                    goblinNftExCodeVo.setRedeemAt(nowTime);
                    goblinNftExCodeVo.setUpdatedAt(nowTime);
                    goblinNftExCodeVo.setSkuId(code);
                    goblinNftExCodeVo.setBoxSkuId("");
                    goblinNftExCodeVo.setSpuId(skuInfoVo.getSpuId());
                    goblinNftExCodeVo.setStoreId(skuInfoVo.getStoreId());
                } else {
                    // 验证 1code是否存在 2是否未兑换的状态 3是否在生效日期内 4兑换限购
                    goblinNftExCodeVo = nftOrderUtils.getGoblinNftExCodeVo(code);
                    if (null == goblinNftExCodeVo || goblinNftExCodeVo.getState() != 1) {
                        return ResponseDto.failure("兑换码不正确或已失效");
                    }
                    if (nowTime.isBefore(goblinNftExCodeVo.getExStartTime()) || nowTime.isAfter(goblinNftExCodeVo.getExStopTime())) {
                        return ResponseDto.failure("不在允许兑换时间内");
                    }
                    int buyCount = nftOrderUtils.incrExCountByUid(uid, goblinNftExCodeVo.getSkuId(), goblinNftExCodeVo.getActivityId(), 1);
                    if (null != goblinNftExCodeVo.getExLimit() && goblinNftExCodeVo.getExLimit() > 0) {
                        if (buyCount > goblinNftExCodeVo.getExLimit()) {
                            nftOrderUtils.decrExCountByUid(uid, goblinNftExCodeVo.getSkuId(), goblinNftExCodeVo.getActivityId(), 1);
                            return ResponseDto.failure("您已超出兑换限制数量～");
                        }
                    }
                    // 使用
                    goblinNftExCodeVo.setState(2);
                    goblinNftExCodeVo.setRedeemUid(uid);
                    goblinNftExCodeVo.setRedeemAt(nowTime);
                    goblinNftExCodeVo.setUpdatedAt(nowTime);
                    nftOrderUtils.setGoblinNftExCodeVo(goblinNftExCodeVo);
                    sqlDataCode.add(new Object[]{
                            2, uid, nowTime, nowTime, goblinNftExCodeVo.getCodeId()
                    });
                }
                // 下单
                GoblinNftOrderPayParam payParam = GoblinNftOrderPayParam.getNew();
                payParam.setPayType("");
                payParam.setSkuId(goblinNftExCodeVo.getSkuId());
                payParam.setDeviceFrom(deviceFrom);
                String orderId = IDGenerator.nextSnowId();
                String orderCode = IDGenerator.storeCode(orderId);
                BigDecimal totalPrice = BigDecimal.ZERO;
                String boxSkuId = goblinNftExCodeVo.getBoxSkuId();
                GoblinGoodsSkuInfoVo skuVo = goblinRedisUtils.getGoodsSkuInfoVo(goblinNftExCodeVo.getSkuId());
                GoblinNftOrder nftOrder = order(payParam, goblinNftExCodeVo.getStoreId(), uid, goblinNftExCodeVo.getSpuId(), 1, orderId, orderCode, totalPrice, totalPrice, totalPrice, null == boxSkuId ? "" : boxSkuId, skuVo.getName() + "  " + skuVo.getSubtitle(), orderType, "", code);
                if (null == nftOrder) {
                    return ResponseDto.failure("兑换失败～");
                }
                // redis 订单详情
                GoblinNftOrderVo orderVo = GoblinNftOrderVo.getNew().copy(nftOrder);
                orderVo.setCreatedAt(nftOrder.getCreatedAt());
                orderVo.setPaymentType("");
                orderVo.setPaymentId("");
                orderVo.setPayCode("EXCODE");
                orderVo.setPayType("EXCODE");
                orderVo.setPayTime(nowTime);
                orderVo.setStatus(GoblinStatusConst.NftStatus.ORDER_STATUS_2.getValue());
                nftOrderUtils.setNftOrder(orderVo);
                nftOrderUtils.setNftOrderIdOfCode(nftOrder.getOrderCode(), nftOrder.getOrderId());
                // mongo
                goblinMongoUtils.setGoblinNftOrderVo(orderVo);
                // 执行sql
                sqls.add(SqlMapping.get("goblin_nft_order.insert"));
                LinkedList<Object[]> sqlDataOrder = CollectionUtil.linkedListObjectArr();
                sqlDataOrder.add(new Object[]{
                        nftOrder.getOrderId(), nftOrder.getSpuId(), nftOrder.getSkuId(), nftOrder.getBoxSkuId(),
                        nftOrder.getNum(), nftOrder.getStoreId(), nftOrder.getStoreName(), nftOrder.getOrderCode(), nftOrder.getUserId(), nftOrder.getUserName(), nftOrder.getUserMobile(), nftOrder.getPriceTotal(), nftOrder.getPriceCoupon(),
                        nftOrder.getStorePriceCoupon(), nftOrder.getPriceRedEnvelope(), nftOrder.getPriceVoucher(), nftOrder.getPriceActual(), nftOrder.getUcouponId(), nftOrder.getStoreCouponId(), nftOrder.getRedEnvelopeCode(), orderVo.getStatus(), nftOrder.getSource(),
                        nftOrder.getOrderType(), orderVo.getPayType(), nftOrder.getDeviceFrom(), nftOrder.getVersion(), nftOrder.getPayCountdownMinute(), nftOrder.getIpAddress(), nftOrder.getCreatedAt(), orderVo.getPayCode(),
                        nftOrder.getSkuTitle(), nftOrder.getListId(), nftOrder.getExCode(),"",""
                });
                String sqlData = SqlMapping.gets(sqls, sqlDataCode, sqlDataOrder);
                queueUtils.sendMsgByRedis(MQConst.GoblinQueue.GOBLIN_NFT_ORDER.getKey(), sqlData);
                // 加积分
                goblinOrderUtils.integral(uid, orderVo.getPriceActual(), "购买数字藏品", 1);
                // 生产藏品列表
                if (code.equals(orderVo.getSkuId())) {
                    this.generateUserArtwork(orderVo, 5);
                } else {
                    this.generateUserArtwork(orderVo, orderType == 4 ? 31 : 2);
                }
                // 增加销量
                goblinRedisUtils.incrSkuSaleCount(orderVo.getSpuId(), orderVo.getSkuId(), orderVo.getNum());
                // 写入用户订单列表 因取消的订单不展示 所以放在这里
                nftOrderUtils.addNftOrderList(uid, orderVo.getOrderId());
                // TODO: jxl 2022/4/24  判断技术部NFT记录Redis setBuyNftTec

                return ResponseDto.success(orderVo.getOrderId());
            } catch (Exception e) {
                log.error("NFT兑换异常", e);
                return ResponseDto.failure("兑换失败，请稍后重试！");
            } finally {
                // 执行完毕。释放锁
                nftOrderUtils.delNftOrderExLock(code);
            }
        }
    }

    /* --------------------------------------------------------------------------------- */

    /**
     * 生成我的藏品
     *
     * @param orderVo
     */
    private void generateUserArtwork(GoblinNftOrderVo orderVo, Integer source) {
        String uid = orderVo.getUserId(), skuId = orderVo.getSkuId(), orderId = orderVo.getOrderId(), spuId = orderVo.getSpuId();
        if (goblinRedisUtils.hasGenUserDigitalArtwork(uid, skuId, orderId)) {// 已生成
            log.warn("#GEN_ARTWORK EXIST_ART[uid={},skuId={},orderId={},source={}]", uid, skuId, orderId, source);
            return;
        }
        GoblinGoodsInfoVo goodsInfoVo = goblinRedisUtils.getGoodsInfoVo(spuId);
        LocalDateTime now = LocalDateTime.now();

        GoblinUserDigitalArtworkVo digitalArtworkVo = GoblinUserDigitalArtworkVo.getNew();
//        String artworkId = IDGenerator.nextMilliId2();
        String artworkId = IDGenerator.nextSnowId();
        digitalArtworkVo.setArtworkId(artworkId);
        digitalArtworkVo.setSkuId(skuId);
        digitalArtworkVo.setUid(uid);
        digitalArtworkVo.setOrderId(orderId);
        digitalArtworkVo.setSource(source);
        if (source == 5) {
            digitalArtworkVo.setState(0);
        } else {
            digitalArtworkVo.setState(source == 31 ? 5 : 0);
        }
        digitalArtworkVo.setDelFlg("0");
        digitalArtworkVo.setCreatedAt(now);
        digitalArtworkVo.setAuthor(goodsInfoVo.getAuthor());
        digitalArtworkVo.setPublisher(goodsInfoVo.getPublisher());

        // Redis记录VO
        goblinRedisUtils.setGoblinUserDigitalArtworkVo(digitalArtworkVo);
        // Redis更新藏品ID列表
        goblinRedisUtils.addGoblinUserDigitalArtworkIds(uid, artworkId);
        // 缓存用户藏品数
        goblinRedisUtils.incrGoblinUserDigitalArtworks(uid);
        // Redis生成标记（缓存三天），用于上面的生成检查
        goblinRedisUtils.markGenUserDigitalArtwork(uid, skuId, orderId);
        // Mongo记录VO
        goblinOrderUtils.setMongoList(GoblinUserDigitalArtworkVo.class.getSimpleName(), "artworkId", digitalArtworkVo.getArtworkId(), GoblinRedisConst.USER_DIGITAL_ARTWORK, 1);
//        goblinMongoUtils.insertGoblinUserDigitalArtworkVo(digitalArtworkVo);

        if (source != 31) {// 空投需要领取再发行
            GoblinGoodsSkuInfoVo goodsSkuInfoVo = goblinRedisUtils.getGoodsSkuInfoVo(skuId);
            if ("0".equals(goodsSkuInfoVo.getUnbox())) {// 非盲盒，NFT发行购买
                ObjectNode bizNftBuyNode = JsonUtils.OM().createObjectNode().put("nftOrderPayId", orderId).put("routerType", goodsSkuInfoVo.getRouteType())
                        .put("skuId", skuId).put("userId", uid).put("buyTimestamp", DateUtil.Formatter.yyyyMMddHHmmss.format(now));
                if (source != 5) {
                    queueUtils.sendMsgByRedis(MQConst.GalaxyQueue.JSON_NFT_PUBLISH_AND_BUY.getKey(), bizNftBuyNode.toString());
                }
            }
        }

        // Mysql持久化
        LinkedList<String> toMqSqls = CollectionUtil.linkedListString();
        toMqSqls.add(SqlMapping.get("goblin_user_digital_artwork.insert"));
        LinkedList<Object[]> initUserDigitalArtworkObjs = CollectionUtil.linkedListObjectArr();
        initUserDigitalArtworkObjs.add(new Object[]{digitalArtworkVo.getArtworkId(), skuId, uid, orderId, source, digitalArtworkVo.getState(), now});
        queueUtils.sendMsgByRedis(MQConst.GoblinQueue.SQL_ARTWORK_GEN.getKey(), SqlMapping.gets(toMqSqls, initUserDigitalArtworkObjs));
    }

}
