从支付或退款之回调处理的设计,看一看抽象类的使用场景

news2025/2/28 6:16:27

一、背景

抽象类,包含抽象方法和实例方法,抽象方法待继承类去实例化,正是利用该特性,以满足不同支付渠道的差异化需求。
我们在做多渠道支付的时候,接收支付或退款的回调报文,然后去处理。这就意味着,我们往往会定义多组回调接口,把微信官方、支付宝官方、杭州银行等区分开来。
同时,他们之间又存在着许多共性,比如都需要验签,对比回调金额和本地金额是否一致,以及更新本地支付记录的状态等。

本文先会梳理,处理回调的一般逻辑,配合代码设计,尝试让你体会到在编程中,使用抽象类的魅力所在。

二、系统设计

在这里插入图片描述
我们针对不同的支付渠道,定义不同的回调接口,以区分报文的差异。

这里,以微信官方、支付宝官方和杭州银行三个渠道为示例。其实,我们实际对接的支付渠道比这多得多。

三、回调处理流程

在这里插入图片描述

四、抽象类的设计

在这里插入图片描述

  • 源码截图见下:

在这里插入图片描述

五、支付渠道的实现

支付回调处理和退款回调处理,不同的支付渠道会有不同的处理逻辑。有些支付渠道返回的报文,可能需要先进行解密。

0、验签

入参必须有account,然后我们会根据account取出所需要的密钥等信息,去对回调报文进行计算签名。 计算出来的签名和回调报文中的签名,如果不一致,则说明验签失败。

1、杭州银行

  • 支付回调处理
private static final String ERROR_CODE = "comm error";
private static final String SUCCESS_CODE = "got it";
    
String requestResultJson = IOUtils.toString(request.getInputStream(), request.getCharacterEncoding());
        if (log.isInfoEnabled()) {
            log.info("杭州银行支付回调通知, 回调报文内容是:{}", requestResultJson);
        }
        if (StringUtils.isEmpty(requestResultJson)) {
            return ERROR_CODE;
        }
        Map<String, Object> resultMap = JSON.parseObject(requestResultJson, HashMap.class);

        if (HzBankSignUtil.SUCC_CODE.equalsIgnoreCase((String) resultMap.get(HzBankSignUtil.RESP_CODE))) {
            return lockPayNotify(resultMap);
        }
        return ERROR_CODE;
  • 退款回调处理

因为杭州银行的退款是同步的,所以这里没有对应实现。

  • 验签
    @Override
    protected boolean doSign(Map<String, Object> resultMap, String account) {
        return HzBankSignUtil.dataVerifyByAccount(resultMap, CharsetUtil.UTF_8, account);
    }
  • 其他实例方法
    @Override
    protected boolean enableSign() {
        return true;
    }

    @Override
    protected String payChannelName() {
        return "HzBank";
    }

/**
     * 获取平台支付流水号
     *
     * @param resultMap
     * @return
     */
    @Override
    protected String getChannelTradeNo(Map<String, Object> resultMap) {
        return (String) resultMap.get("txnOrderId");
    }

    /**
     * 获取第三方支付流水号
     *
     * @param resultMap
     * @return
     */
    @Override
    protected String getOutTradeNo(Map<String, Object> resultMap) {
        return (String) resultMap.get("respTxnSsn");
    }

    @Override
    protected String getRefundTradeNo(Map<String, Object> resultMap) {
        return null;
    }

    @Override
    protected String getOutRefundNo(Map<String, Object> resultMap) {
        return null;
    }

    /**
     * 获取支付金额
     *
     * @param resultMap
     * @return
     */
    @Override
    protected Integer getPayAmt(Map<String, Object> resultMap) {
        return Integer.parseInt((String) resultMap.get("settleAmt"));
    }

    @Override
    protected String getRefundAmt(Map<String, Object> resultMap) {
        return null;
    }

    @Override
    protected Date getPayOkDate(Map<String, Object> resultMap) {
        return DateUtils.getDate(String.valueOf(resultMap.get("respTxnTime")), DateUtils.DATE_FORMAT_1);
    }

    @Override
    protected String getRefundStatus(Map<String, Object> resultMap) {
        return null;
    }

    @Override
    protected Date getRefundOkDate(Map<String, Object> resultMap) {
        return null;
    }

2、微信官方

它是一个xml格式的报文,我们使用到了一个三方jar包, com.github.binarywang 下的一个工具包weixin-java-pay。

  • 支付回调处理

    • 1.打印回调报文
    • 2.判断返回状态码
    • 3.统一转换为Map<String,Object>类型
  • 退款回调处理

    • 1.打印回调报文
    • 2.判断返回状态码
    • 3.根据返回报文中的mch_id查询出对应的商户
    • 4.根据上一步的商户密钥,将xml转换为bean对象
    • 5.如果退款成功,则锁定该退款记录,准备处理
    • 6.统一转换为Map<String,Object>类型
  • 验签

    @Override
    protected boolean doSign(Map<String, Object> paramMap, String account) {
        //根据account查询商户api密钥
        ChannelAccount channelAccount = channelAccountService.findByAccount(account);
        if (Objects.isNull(channelAccount)) {
            log.error("微信支付回调处理, 交易记录中的账户未配置账户的支付信息, [channelAccount={}]", account);
            return false;
        }

        return SignStrengthenUtils.checkSign(WxPayOrderNotifyResult.fromXML((String) paramMap.get("xmlString")), "MD5", channelAccount.getMchApiSecret());
    }
  • 其他实例方法
    @Override
    protected boolean enableSign() {
        return wxPayConfiguration.isSignEnabled();
    }

    @Override
    protected String payChannelName() {
        return "WX";
    }

    @Override
    protected String getChannelTradeNo(Map<String, Object> resultMap) {
        return (String) resultMap.get("out_trade_no");
    }

    @Override
    protected String getOutTradeNo(Map<String, Object> resultMap) {
        return (String) resultMap.get("transaction_id");
    }

    @Override
    protected String getRefundTradeNo(Map<String, Object> resultMap) {
        return (String) resultMap.get("outRefundNo");
    }

    @Override
    protected String getOutRefundNo(Map<String, Object> resultMap) {
        return (String) resultMap.get("refundId");
    }

    @Override
    protected Integer getPayAmt(Map<String, Object> resultMap) {
        return Integer.parseInt((String) resultMap.get("total_fee"));
    }

    @Override
    protected String getRefundAmt(Map<String, Object> resultMap) {
        return String.valueOf(resultMap.get("refundFee"));
    }

    @Override
    protected Date getPayOkDate(Map<String, Object> resultMap) {
        return DateUtils.getDate(String.valueOf(resultMap.get("time_end")), DateUtils.DATE_FORMAT_1);
    }

    @Override
    protected String getRefundStatus(Map<String, Object> resultMap) {
        return (String) resultMap.get("refundStatus");
    }

    @Override
    protected Date getRefundOkDate(Map<String, Object> resultMap) {
        return DateUtils.getDate(String.valueOf(resultMap.get("successTime")), DateUtils.DATE_FORMAT_2);
    }

3、农行

  • 支付回调处理
private String doAbcPayRes(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String requestMsg = request.getParameter(AbcBankConfig.MSG);
        if (log.isInfoEnabled()) {
            log.info("农业银行支付回调通知, 回调报文内容是, 解密前:{}", requestMsg);
        }
        if (StringUtils.isEmpty(requestMsg)) {
            return JSON.toJSONString(IcbcNotifyResponseDTO.error("回调报文不能为空"));
        }

        final String decodeMessage = Base64Code.Decode64(requestMsg);
        if (log.isInfoEnabled()) {
            log.info("农业银行支付回调通知, 回调报文内容是, 解密后:{}", decodeMessage);
        }

        Map<String, Object> resultMap = XmlUtil.xmlToMap(decodeMessage);

        Map<String, Object> messageMap = (Map<String, Object>) resultMap.get(AbcBankConfig.MESSAGE);

        Map<String, Object> trxResponseMap = (Map<String, Object>) messageMap.get(AbcBankConfig.TRX_RESPONSE);

        //将原文透传下去,供校验签名
        trxResponseMap.put(AbcBankConfig.MSG, decodeMessage);

        if (AbcBankConfig.RC_SUCCESS.equalsIgnoreCase((String) trxResponseMap.get(AbcBankConfig.RETURN_CODE))) {
            return lockPayNotify(trxResponseMap);
        }
        return JSON.toJSONString(IcbcNotifyResponseDTO.error("支付回调处理失败"));
    }
  • 退款回调处理

农行的退款是同步的,不是采用异步通知的方式。

  • 验签
@Override
    protected boolean doSign(Map<String, Object> trxResponseMap, String account) {
        String msg = (String) trxResponseMap.get(AbcBankConfig.MSG);
        return AbcBankSignUtil.verifySignByAccount(new XMLDocument(msg), account);
    }
  • 其他实例方法
    @Override
    protected boolean enableSign() {
        return true;
    }

    @Override
    protected String payChannelName() {
        return "ABC";
    }

    /**
     * 获取平台支付流水号
     *
     * @param resultMap
     * @return
     */
    @Override
    protected String getChannelTradeNo(Map<String, Object> resultMap) {
        return (String) resultMap.get("OrderNo");
    }

    /**
     * 获取第三方支付流水号.
     * <p>
     * <p>upay流水号</p>
     *
     * @param resultMap
     * @return
     */
    @Override
    protected String getOutTradeNo(Map<String, Object> resultMap) {
        return (String) resultMap.get("iRspRef");
    }

    @Override
    protected String getRefundTradeNo(Map<String, Object> resultMap) {
        return null;
    }

    @Override
    protected String getOutRefundNo(Map<String, Object> resultMap) {
        return null;
    }

    /**
     * 获取支付金额
     *
     * @param resultMap
     * @return
     */
    @Override
    protected Integer getPayAmt(Map<String, Object> resultMap) {
        String amount = (String) resultMap.get("Amount");
        Precondition.notEmpty(amount, "支付回调金额不能为空");

        return AmountUtils.changeY2F(amount);
    }

    @Override
    protected String getRefundAmt(Map<String, Object> resultMap) {
        return null;
    }

    @Override
    protected Date getPayOkDate(Map<String, Object> resultMap) {
        //格式: YYYY/MM/DD
        String txDate = String.valueOf(resultMap.get("HostDate")).replaceAll("/", "-");
        //格式:HH:MM:SS
        String txTime = String.valueOf(resultMap.get("HostTime"));

        return DateUtil.parseDateTime(txDate + " " + txTime);
    }

    @Override
    protected String getRefundStatus(Map<String, Object> resultMap) {
        return null;
    }

    @Override
    protected Date getRefundOkDate(Map<String, Object> resultMap) {
        return null;
    }

4、工行

  • 支付回调处理

注意,工行的回调参数,是在query参数里,不是在requestBody。虽然是post接口,但是接口的Content-Type是application/x-www-form-urlencoded。这一点和其他支付方式的回调有较大差异。

    /**
     * 支付回调的参数
     */
    public final static String APIGW_RSPDATA = "apigw_rspdata";
    /**
     * 支付回调的签名
     */
    public final static String APIGW_SIGN = "apigw_sign";
    /**
     * 支付回调的证书ID
     */
    public final static String APIGW_CERTID = "apigw_certid";
    
private String doIcbcPayRes(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // Content-Type:application/x-www-form-urlencoded
        String sign = request.getParameter(IcbcConfig.APIGW_SIGN);
        String certId = request.getParameter(IcbcConfig.APIGW_CERTID);
        String rspData = request.getParameter(IcbcConfig.APIGW_RSPDATA);

        if (log.isInfoEnabled()) {
            log.info("工商银行支付回调通知, 回调报文内容是:[rspData={}, sign={}, certId={}]", rspData, sign, certId);
        }
        if (StringUtils.isEmpty(sign) || StringUtils.isEmpty(certId) || StringUtils.isEmpty(rspData)) {
            return JSON.toJSONString(IcbcNotifyResponseDTO.error("回调报文不能为空"));
        }

        Map<String, Object> resultMap = JSON.parseObject(rspData, HashMap.class);

        resultMap.put(IcbcConfig.APIGW_SIGN, sign);
        resultMap.put(IcbcConfig.APIGW_CERTID, certId);
        resultMap.put(IcbcConfig.APIGW_RSPDATA, rspData);

        if (IcbcConfig.SUCC_CODE.equalsIgnoreCase((String) resultMap.get(IcbcConfig.RESULT_CODE))) {
            return lockPayNotify(resultMap);
        }
        return JSON.toJSONString(IcbcNotifyResponseDTO.error("支付回调处理失败"));
    }
  • 退款回调处理

  • 验签
    protected boolean doSign(Map<String, Object> resultMap, String account) {
        // 校验签名
        ApiClient apiClient = IcbcBankApiClientCache.getApiClientByAccount(account);
        try {
            return apiClient.doVerifyWithExit((String) resultMap.get(IcbcConfig.APIGW_RSPDATA),
                    (String) resultMap.get(IcbcConfig.APIGW_CERTID),
                    (String) resultMap.get(IcbcConfig.APIGW_SIGN),
                    "UTF-8");
        } catch (Exception e) {
            log.error("{}签名出现异常,[resultMap={}, certId={}]", payChannelName(),
                    JSON.toJSONString(resultMap), resultMap.get(IcbcConfig.APIGW_CERTID), e);
            return false;
        }
    }
  • 其他实例方法
@Override
    protected boolean enableSign() {
        return true;
    }

    @Override
    protected String payChannelName() {
        return "ICBC";
    }

    /**
     * 获取平台支付流水号
     *
     * @param resultMap
     * @return
     */
    @Override
    protected String getChannelTradeNo(Map<String, Object> resultMap) {
        return (String) resultMap.get("orderNo");
    }

    /**
     * 获取第三方支付流水号.
     * <p>
     * <p>upay流水号</p>
     *
     * @param resultMap
     * @return
     */
    @Override
    protected String getOutTradeNo(Map<String, Object> resultMap) {
        return (String) resultMap.get("serialNo");
    }

    @Override
    protected String getRefundTradeNo(Map<String, Object> resultMap) {
        return null;
    }

    @Override
    protected String getOutRefundNo(Map<String, Object> resultMap) {
        return null;
    }

    /**
     * 获取支付金额
     *
     * @param resultMap
     * @return
     */
    @Override
    protected Integer getPayAmt(Map<String, Object> resultMap) {
        return AmountUtils.changeY2F((String) resultMap.get("totalAmount"));
    }

    @Override
    protected String getRefundAmt(Map<String, Object> resultMap) {
        return null;
    }

    @Override
    protected Date getPayOkDate(Map<String, Object> resultMap) {
        String txDate = String.valueOf(resultMap.get("txDate"));
        String txTime = String.valueOf(resultMap.get("txTime"));

        return DateUtils.getDate(txDate + txTime, DateUtils.DATE_FORMAT_1);
    }

    @Override
    protected String getRefundStatus(Map<String, Object> resultMap) {
        return null;
    }

    @Override
    protected Date getRefundOkDate(Map<String, Object> resultMap) {
        return null;
    }

六、处理支付/退款记录

上文列举了杭州银行、微信官方、农行、工行等四种支付渠道的实例,相信你后续接入其他支付渠道也是轻轻松松。

下面,我们将介绍公共的处理实现,因为退款逻辑和支付逻辑大同小异,所以我这里只说下支付的实现。

1、抽象回调的返回报文

    /**
     * 封装回调响应失败的报文
     *
     * @param msg
     * @return
     */
    protected abstract String assemblerResponseErrorMsg(String msg);

    /**
     * 封装回调响应成功的报文
     *
     * @param msg
     * @return
     */
    protected abstract String assemblerResponseSuccessMsg(String msg);

2、非空校验

public String lockPayNotify(Map<String, Object> paramMap) {
        // 平台订单号
        String channelTradeNo = getChannelTradeNo(paramMap);
        String outTradeNo = getOutTradeNo(paramMap);
        Integer payAmt = getPayAmt(paramMap);

        if (StringUtils.isEmpty(channelTradeNo) || StringUtils.isEmpty(outTradeNo) || payAmt <= 0) {
            log.error("{}支付回调通知失败, 平台支付流水号/第三方支付流水号/回调金额均不能为空![channelTradeNo={},outTradeNo={},payAmt={}]",
                    payChannelName(), channelTradeNo, outTradeNo, payAmt);
            return assemblerResponseErrorMsg("outTradeNo is null Or channelTradeNo is null Or settleAmt is null");
        }

        try {
            return handlePayResult(paramMap, channelTradeNo);
        } catch (Exception e) {
            log.error("{}支付回调通知, 处理出现异常,详细错误:", payChannelName(), e);
            return assemblerResponseErrorMsg(e.getMessage());
        }
    }

3、分布式锁

在这里插入图片描述

4、核心逻辑

try {
            String outTradeNo = getOutTradeNo(paramMap);
            Integer payAmt = getPayAmt(paramMap);

            // 支付成功时间
            Date notifyPayOkDate = getPayOkDate(paramMap);

            //查找支付订单和判断支付状态
            PayTrade payTrade = checkPayTradeIsExist(channelTradeNo);
            if (null == payTrade) {
                return assemblerResponseErrorMsg("channelTradeNo:[" + channelTradeNo + "] not exist");
            }
            if (PayConstants.TRADESTATUS.SUCCESS == payTrade.getStatus()) {
                return assemblerResponseErrorMsg("channelTradeNo:[" + channelTradeNo + "] has paid, please do not repeat invoke");
            }

            //校验签名
            if (!checkSign(paramMap, payTrade, outTradeNo)) {
                return assemblerResponseErrorMsg("channelTradeNo:[" + channelTradeNo + "] sign error");
            }

            //校验金额
            if (!checkAmountEqual(payAmt, payTrade, outTradeNo)) {
                return assemblerResponseErrorMsg("channelTradeNo:[" + channelTradeNo + "] amount not equal");
            }

            // 处理订单
            if (payTradeAppService.handlePayStatus(payTrade, channelTradeNo, outTradeNo, notifyPayOkDate)) {
                return assemblerResponseSuccessMsg("deal payNotify Success");
            }
            return assemblerResponseErrorMsg("channelTradeNo:[" + channelTradeNo + "] update payTrade fail");
        } catch (Exception e) {
            if (log.isWarnEnabled()) {
                log.warn("处理支付回调出现异常", e);
            }
            throw new IllegalArgumentException("处理支付回调出现异常", e);
        }

七、总结

本文以支付和退款回调的实际业务为例,在使用抽象类的情况下,程序代码变得更加易懂,且大大提升了程序的拓展性。
每次接入新的支付渠道,对程序改动的影响和风险降低不少,比如你要接入连连支付,只需要新定义一个连连支付的实现类,并不会改动到其他原有支付的代码逻辑。

其实,我们在实现对账逻辑的时候,也会使用大量的设计模式。(有空梳理下对账逻辑的程序实现)
换言之,抽象类的使用,正是设计模式的一个基石。

我们使用了抽象类,却搞不清是使用了什么设计模式。这倒没什么,怕的是,你没想去减少代码的冗余。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/862377.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

对话音视频牛哥:如何设计功能齐全的跨平台低延迟RTMP播放器

开发背景 2015年&#xff0c;我们在做移动单兵应急指挥项目的时候&#xff0c;推送端采用了RTMP方案&#xff0c;这在当时算是介入RTMP比较早的了&#xff0c;RTMP推送模块做好以后&#xff0c;我们找了市面上VLC还有Vitamio&#xff0c;来测试整体延迟&#xff0c;实际效果真…

大数据传输的定义与大数据传输解决方案的选择

当我们需要处理大量的数据时&#xff0c;我们就要把数据从一个地方移动到另一个地方。这个过程就叫做大数据传输。它通常需要用到高速的网络连接、分散的存储系统和数据传输协议&#xff0c;以保证数据的快速、可靠和安全的移动。常用的大数据传输技术有Hadoop分布式文件系统&a…

servlet三大类HttpSevlet,HttpServletRequest,HttpServletResponse介绍

一、HttpServlet HttpServlet类是一个被继承的方法&#xff0c;可以看做一个专门用来响应http请求的类&#xff0c;这个类的所有方法都是为响应http请求服务的&#xff0c;要对一个某个路径谁知http响应时&#xff0c;需要写一个类来继承HttpServlet类&#xff0c;并重写里面的…

BGP基础建邻+宣告实验

实验要求及拓扑 一、实验思路 1.编写静态路由使R1、R2之间可通和使R4、R5之间可通。 2.使用OSPF使R2、R3、R4之间可通。 3.各自宣告AS区域&#xff0c;中间区域两两之间建邻。 4.注意建邻所使用的端口&#xff0c;外部BGP邻居关系和内部BGP邻居关系的区别。 二、上虚拟机操…

企业微信web登录(扫二维码登录)

记录一下企业微信web扫码登录的使用过程。 按惯例&#xff0c;先看登录流程&#xff1a; 步骤 首先&#xff0c; 企业微信后台开启“企业微信授权登陆功能”&#xff0c;“设置授权回调域名” ,授权回调域名必须与访问链接的域名完全一致。&#xff08;访问链接的域名就是扫码…

【Kubernetes】Kubernetes的调度

K8S调度 一、Kubernetes 调度1. Pod 调度介绍2. Pod 启动创建过程3. Kubernetes 的调度过程3.1 调度需要考虑的问题3.2 具体调度过程 二、影响kubernetes调度的因素1. nodeName2. nodeSelector3. 亲和性3.1 三种亲和性的区别3.2 键值运算关系3.3 节点亲和性3.4 Pod 亲和性3.5 P…

高忆管理:创业板股票涨跌幅?

创业板股票涨跌幅限制大于主板商场&#xff0c;为何呈现这样的现象&#xff1f;从多个角度剖析&#xff0c;其中包含方针因素、商场走势、职业危险等多个方面。 首要&#xff0c;方针因素是导致股票涨跌幅波动的一个重要因素。在新的方针环境下&#xff0c;相关部门关于创业板股…

ModaHub魔搭社区——Milvus Cloud向量数据库

向量数据库:在AI时代的快速发展与应用 摘要: 随着人工智能技术的不断进步,向量数据库在处理大规模数据方面发挥着越来越重要的作用。本文介绍了向量数据库的基本概念、应用场景和技术挑战,并详细阐述了Milvus Cloud作为典型的向量数据库产品的技术特点、性能优化和应用案例…

拼多多秋招 考试内容详解和备考技巧

拼多多秋招内容简介 作为线上销售行业的知名企业之一&#xff0c;拼多多的销售模式也得到了越来越多的人认可&#xff0c;而伴随着企业规模的不断扩大&#xff0c;拼多多也需要能力杰出、认可自己公司文化的新员工&#xff0c;从目前的招聘情况来看&#xff0c;拼多多的岗位需…

拿下美团校招:MySQL InnoDB非聚簇索引知识点解析!

大家好&#xff0c;我是你们的小米&#xff0c;在这里欢迎大家来到《小米的技术小屋》&#xff01;今天&#xff0c;我将和大家一起来揭开一个有趣且有深度的话题&#xff0c;那就是来自美团校招面试的一道问题&#xff1a;“MySQL中的InnoDB在什么情况下使用非聚簇索引&#x…

SpringBoot禁用Swagger3

Swagger3默认是启用的&#xff0c;即引入包就启用。 <dependency><groupId>io.springfox</groupId><artifactId>springfox-boot-starter</artifactId><version>3.0.0</version> </dependency> <dependency><groupId…

纤维素衍生物辅料行业分析-市场规模达15.67亿美元

纤维素衍生物辅料行业分析&#xff1a;2022年全球纤维素合成生物辅料市场规模达15.67亿美元 关注医药行业的纤维素衍生物辅料。药用辅料是生产药品和调配处方时所用的赋形剂和附加剂&#xff0c;是药物制剂的重要组成部分。纤维素衍生物作为天然高分子衍生材料&#xff0c;具有…

Uniapp使用腾讯地图并进行标点创建和设置保姆教程

使用Uniapp内置地图 首先我们需要创建一个uniapp项目 首先我们需要创建一个uniapp项目 我们在HBuilder左上角点击文件新建创建一个项目 然后下面这张图的话就是uniapp创建项目过程当中需要注意的一些点和具体的操作 然后我们创建完项目之后进入到项目pages文件夹下&#xff…

面试热题(二叉树的锯齿形层次遍历)

给你二叉树的根节点 root &#xff0c;返回其节点值的 锯齿形层序遍历 。&#xff08;即先从左往右&#xff0c;再从右往左进行下一层遍历&#xff0c;以此类推&#xff0c;层与层之间交替进行&#xff09; 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;[[3…

Spring @Profile注解使用和源码解析

使用 带有Profile的注解的bean的不会被注册进IOC容器&#xff0c;需要为其设置环境变量激活&#xff0c;才能注册进IOC容器&#xff0c;如下通过setActiveProfiles设置了dev值&#xff0c;那么这三个值所对应的Bean会被注册进IOC容器。当然&#xff0c;我们在实际使用中&#…

Grafana技术文档--基本安装-docker安装并挂载数据卷-《十分钟搭建》-附带监控服务器

阿丹&#xff1a; Prometheus技术文档--基本安装-docker安装并挂载数据卷-《十分钟搭建》_一单成的博客-CSDN博客 在正确安装了Prometheus之后开始使用并安装Grafana作为Prometheus的仪表盘。 一、拉取镜像 搜索可拉取版本 docker search Grafana拉取镜像 docker pull gra…

cmake (更新中)

概述 关于 CMake CMake 是一个可扩展的开源系统&#xff0c;以一种与操作系统和编译器无关的方式来管理构建过程。与许多跨平台系统不同&#xff0c;CMake 被设计为与本机构建环境配合使用。在每个源代码目录中放置简单的配置文件&#xff08;称为 CMakeLists.txt 文件&#xf…

VLOOKUP函数使用

在Excel中&#xff0c;VLOOKUP函数用于在一个范围内查找某个值&#xff0c;并返回该值所在行的指定列的内容。VLOOKUP函数的基本语法如下&#xff1a; VLOOKUP(lookup_value, table_array, col_index_num, [range_lookup])参数说明&#xff1a; lookup_value&#xff1a;要查…

MySQL_事务学习笔记

事务 注意&#xff1a;一定要使用 Innodb 存储引擎 概述&#xff1a;一组操作的集合&#xff0c;是不可分割的工作单元&#xff0c;会把一个部分当成一个整体来处理&#xff0c;事务会把操作同时提交或者是撤销。要么同时成功&#xff0c;要么同时失败。 比如&#xff1a;上云…

汽车制造业上下游协作时 外发数据如何防泄露?

数据文件是制造业企业的核心竞争力&#xff0c;一旦发生数据外泄&#xff0c;就会给企业造成经济损失&#xff0c;严重的&#xff0c;可能会带来知识产权剽窃损害、名誉伤害等。汽车制造业&#xff0c;会涉及到重要的汽车设计图纸&#xff0c;像小米发送汽车设计图纸外泄事件并…