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

一、背景

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

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

二、系统设计

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

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

三、回调处理流程

在这里插入图片描述

四、抽象类的设计

在这里插入图片描述

  • 源码截图见下:

在这里插入图片描述

五、支付渠道的实现

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

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);
        }

七、总结

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

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

JVM运行时五大数据区域详解

前言&#xff1a; java虚拟机再执行Java程序的时候把它所拥有的内存区域划分了若干个数据区域。这些区域有着不同的功能&#xff0c;各司其职。这些区域不但功能不同&#xff0c;创建、销毁时间也不同。有些区域为线程私有&#xff0c;如&#xff1a;每个线程都有自己的程序计数…

kubernetes基于helm部署gitlab

kubernetes基于helm部署gitlab 这篇博文介绍如何在 Kubernetes 中使用helm部署 GitLab。 先决条件 已运行的 Kubernetes 集群负载均衡器&#xff0c;为ingress-nginx控制器提供EXTERNAL-IP&#xff0c;本示例使用metallb默认存储类&#xff0c;为gitlab pods提供持久化存储&…

“算法详解”系列第3卷贪心算法和动态规划出版

“算法详解”系列图书共有4卷&#xff0c;目前1到3卷已经出版。最新出版的是第3卷—贪心算法和动态规划。 算法详解 卷3 贪心算法和动态规划 “算法详解”系列图书共有4卷&#xff0c;本书是第3卷—贪心算法和动态规划。其中贪心算法主要包括调度、最小生成树、集群、哈夫曼编…

centos7实现负载均衡

目录 一、基于 CentOS 7 构建 LVS-DR 集群。 1.1 配置lvs负载均衡服务 1.1.1 下载ipvsadm 1.1.2 增加vip 1.1.3 配置ipvsadm 1.2 配置rs1 1.2.1 编写测试页面 1.2.2 手工在RS端绑定VIP、添加路由 1.2.3 抑制arp响应 1.3 配置rs2 1.4 测试 二、配置nginx负载…

Jmeter命令行运行实例讲解

1. 简介 使用非 GUI 模式&#xff0c;即命令行模式运行 JMeter 测试脚本能够大大缩减所需要的系统资 本文介绍windows下以命令行模式运行的方法。 1.1. 命令介绍 jmeter -n -t <testplan filename> -l <listener filename> 示例&#xff1a; jmeter -n -t test…

【数学建模】-- Matlab中图的最短路径

前言&#xff1a; 图的基本概念&#xff1a; 若想简单绘制图可以利用此网站&#xff1a; 左上角Undirected/Directed是无向图/有向图 左边 0-index &#xff0c;1-index为0下标&#xff0c;1下标。 Node Count为节点个数 Graph Data&#xff1a;最初尾节点的名称&#xff…

【什么是应变波齿轮又名谐波驱动?机器人应用的完美齿轮组!?】

什么是应变波齿轮又名谐波驱动&#xff1f;机器人应用的完美齿轮组&#xff01;&#xff1f; 1. 什么是应变波齿轮&#xff1f;2. 工作原理3. 应变波齿轮 – 谐波驱动 3D 模型4. 3D 打印应变波齿轮 – 谐波驱动5. 总结 在本教程中&#xff0c;我们将学习什么是应变波齿轮&#…

第五次作业 运维高级 构建 LVS-DR 集群和配置nginx负载均衡

1、基于 CentOS 7 构建 LVS-DR 群集。 LVS-DR模式工作原理 首先&#xff0c;来自客户端计算机CIP的请求被发送到Director的VIP。然后Director使用相同的VIP目的IP地址将请求发送到集群节点或真实服务器。然后&#xff0c;集群某个节点将回复该数据包&#xff0c;并将该数据包…

ResUNet原理与实现

简述 ResNet是一种非常成功的深度卷积神经网络结构&#xff0c;其具有较强的特征表达能力和较浅的网络深度&#xff0c;使得其在图像分类等任务中表现出了出色的性能。因此&#xff0c;将ResNet作为encoder替换U-Net原始结构&#xff0c;可以使U-Net在图像分割任务中获得更好的…

python爬虫实战(1)--爬取新闻数据

想要每天看到新闻数据又不想占用太多时间去整理&#xff0c;萌生自己抓取新闻网站的想法。 1. 准备工作 使用python语言可以快速实现&#xff0c;调用BeautifulSoup包里面的方法 安装BeautifulSoup pip install BeautifulSoup完成以后引入项目 2. 开发 定义请求头&#xf…

【Windows】Windows开机密码重置

文章目录 前言一、问题描述二、操作步骤2.1 安装DaBaiCai_d14_v6.0_2207_Online.exe2.2 插入U盘2.3 打开大白菜&#xff0c;点击“一键制作USB启动盘”2.4 等待进度条走完2.5 重启电脑&#xff0c;开机按“F12”或者“F8”&#xff08;具体百度一下&#xff0c;对应品牌电脑开机…

Java 成功实现通过网址URL截图保存

Java 实现通过网址URL截图 1.DjNativeSwing方式 &#xff08;不好用&#xff09;2.phantomjs方式 &#xff08;截图还是有瑕疵&#xff09;3.selenium方式 &#xff08;满意&#xff0c;成功实现&#xff09;maven 引入下载相关浏览器chrome下载相关浏览器chromedriver驱动后端…

代码随想录算法训练营第53天|动态规划part11|123. 买卖股票的最佳时机 III、188.买卖股票的最佳时机IV

代码随想录算法训练营第53天&#xff5c;动态规划part11&#xff5c;123. 买卖股票的最佳时机 III、 188.买卖股票的最佳时机IV 123. 买卖股票的最佳时机 III 123. 买卖股票的最佳时机 III 思路&#xff1a; 相比买股票的最佳时机II&#xff0c;限制了买股票的次数&#xf…

Oracle 开发篇+Java调用OJDBC访问Oracle数据库

标签&#xff1a;JAVA语言、Oracle数据库、Java访问Oracle数据库释义&#xff1a;OJDBC是Oracle公司提供的Java数据库连接驱动程序 ★ 实验环境 ※ Oracle 19c ※ OJDBC8 ※ JDK 8 ★ Java代码案例 package PAC_001; import java.sql.Connection; import java.sql.ResultSet…

gitblit windows部署

1.官网下载 往死慢&#xff0c;我是从百度找的1.9.1&#xff0c;几乎就是最新版 http://www.gitblit.com/ 2.解压 下载下来是一个zip压缩包&#xff0c;直接解压即可 3.配置 3.1.配置资源库路径 找到data文件下的gitblit.properties文件&#xff0c;用Notepad打开 **注意路…

Android Ble蓝牙App(三)特性和属性

Ble蓝牙App&#xff08;三&#xff09;特性使用 前言正文一、获取属性列表二、属性适配器三、获取特性名称四、特性适配器五、加载特性六、显示特性和属性七、源码 前言 在上一篇中我们完成了连接和发现服务两个动作&#xff0c;那么再发现服务之后要做什么呢&#xff1f;发现服…

【二】数据库系统

数据库系统的分层抽象DBMS 数据的三个层次从 数据 到 数据的结构----模式数据库系统的三级模式&#xff08;三级视图&#xff09;数据库系统的两层映像数据库系统的两个独立性数据库系统的标准结构 数据模型从 模式 到 模式的结构----数据模型三大经典数据模型 数据库的演变与发…

windows使用/服务(13)戴尔电脑怎么设置通电自动开机

戴尔pc机器通电自启动 1、将主机显示器键盘鼠标连接好后&#xff0c;按主机电源键开机 2、在开机过程中按键盘"F12",进入如下界面&#xff0c;选择“BIOS SETUP” 3、选择“Power Management” 4、选择“AC Recovery”&#xff0c;点选“Power On”&#xff0c;点击“…

uniapp 格式化时间刚刚,几分钟前,几小时前,几天前…

效果如图&#xff1a; 根目录下新建utils文件夹&#xff0c;文件夹下新增js文件&#xff0c;文件内容&#xff1a; export const filters {dateTimeSub(data) {if (data undefined) {return;}// 传进来的data必须是日期格式&#xff0c;不能是时间戳//将字符串转换成时间格式…

使用 prometheus client SDK 暴露指标

目录 1. 使用 prometheus client SDK 暴露指标1.1. How Go exposition works1.2. Adding your own metrics1.3. Other Go client features 2. Golang Application monitoring using Prometheus2.1. Metrics and Labels2.2. Metrics Types2.2.1. Counters:2.2.2. Gauges:2.2.3. …