SpringBoot集成微信支付V3版本流程(商户平台篇)

一、前言

微信支付账号类型分为商户平台和合作伙伴平台,今天主要是梳理商品平台微信支付流程。

商品平台文档地址,(在接入前建议仔细阅读这份文档,会少走很多弯路!!!)

小程序下单 - 小程序支付 | 微信支付商户文档中心

二、接入流程

以下是我SpringBoot项目接入微信支付V3流程,如果还有更好的集成方式,欢迎留言探讨。

2.1、pom.xml导入微信支付SDK

<!--微信支付SDK-->
<dependency>
    <groupId>com.github.wechatpay-apiv3</groupId>
    <artifactId>wechatpay-apache-httpclient</artifactId>
    <version>0.3.0</version>
</dependency>

2.2、application.yaml配置微信支付相关配置

# 微信支付相关参数
wxpay: 
# 商户号
  mch-id: 1888888888
# 商户API证书序列号
  mch-serial-no: 188D8D888888888A476B7DEB5F820082570CA4BA  
# APIv3密钥
  api-v3-key: 88888888
# APPID
  appId: wx88888ff5ee888f88
# 微信服务器地址
  domain: https://api.mch.weixin.qq.com
# 微信支付回调地址 https和外网可以访问的地址
  notify-domain: https://35p3811d81.yicp.fun
# 商户私钥 此处注意换行,回车等不可见字符
  private-key: BIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDV9Hl2qVkpVaAfBiH4SCv6dFbWS/Rem+HhFwQ8mV1XtT7kZUmaSEbrfOE3NSHx5oI+zpnr/nNHSh9vTYUbbH0LcE7Ad+oLtLNVTsZXLOrUPoEgcxSokaNHXd9gfGpKX/i8rkdNnOKTESBxGf8tGrQniBycWwGGJMUAngVeaJQKVN3PC+4WrLFm/x1gWUeGOvXNuKuAyu7VPgN+UKAUC1BuadwM6eokA

2.3、WxPayController.java(回调通知接口一定要从权限拦截器中剔除)

  /**
     * 微信支付API
     */
    @PostMapping("/save")
    @Resubmit
    public ApiResponse<?> saveWxPayOrder(@RequestBody OrderWxPayAppReq orderWxPayAppReq) throws Exception {
        orderWxPayAppReq.setUserId(getUserId());
        return ApiResponse.ok(orderWxPayService.saveWxPayOrder(orderWxPayAppReq));
    }

    /**
     * 微信退款API
     */
    @PostMapping("/refunds/apply")
    @Resubmit
    public ApiResponse<?> refunds(@RequestBody OrderWxPayAppReq orderWxPayAppReq) throws Exception {
        log.info("申请退款");
        orderWxPayAppReq.setUserId(getUserId());
        return ApiResponse.ok(orderWxPayService.wxRefund(orderWxPayAppReq));
    }
    
    /**
     * 微信支付回调通知
     */
    @PostMapping("/callback/pay/notify")
    public ApiResponse<?> payCallBack(HttpServletRequest request) {
        Gson gson = new Gson();
        try {
            //处理通知参数
            String body = HttpUtils.readData(request);
            Map<String, Object> bodyMap = gson.fromJson(body, HashMap.class);
            String requestId = (String) bodyMap.get("id");
            //签名的验证
            WechatPay2ValidatorForRequest wechatPay2ValidatorForRequest
                    = new WechatPay2ValidatorForRequest(verifier, requestId, body);
            if (!wechatPay2ValidatorForRequest.validate(request)) {
                //失败应答
                return ApiResponse.error("通知验签失败");
            }
            //处理订单
            orderWxPayService.processOrder(bodyMap);
            //成功应答
            return ApiResponse.ok("成功");
        } catch (Exception e) {
            e.printStackTrace();
            //失败应答
            return ApiResponse.error("失败");
        }
    }
    
     /**
     * 微信退款结果通知
     */
    @PostMapping("/callback/refunds/notify")
    public ApiResponse<?> refundsNotify(HttpServletRequest request) {
        log.info("退款通知执行");
        try {
            //处理通知参数
            String body = HttpUtils.readData(request);
            Gson gson = new Gson();
            Map<String, Object> bodyMap = gson.fromJson(body, HashMap.class);
            String requestId = (String) bodyMap.get("id");
            //签名的验证
            WechatPay2ValidatorForRequest wechatPay2ValidatorForRequest
                    = new WechatPay2ValidatorForRequest(verifier, requestId, body);
            if (!wechatPay2ValidatorForRequest.validate(request)) {
                return ApiResponse.error("通知验签失败");
            }
            //处理退款单
            orderWxPayService.processRefund(bodyMap);
            return ApiResponse.ok("成功");
        } catch (Exception e) {
            e.printStackTrace();
            //失败应答
            return ApiResponse.error("失败");
        }
    }

2.4、WxPayService.java

    @Override
    public Map<String, String> saveWxPayOrder(OrderWxPayAppReq orderWxPayAppReq) throws IOException {
        log.info("调用统一下单API");
        List<Long> orderIdList = orderWxPayAppReq.getOrderIdList();
        if (CollUtil.isEmpty(orderIdList)) {
            throw new BaseRuntimeException(OrderErrorCodeEnums.BASE_003);
        }
        //查询订单
        LambdaQueryWrapper<OrderInfo> orderQueryWrapper = new LambdaQueryWrapper<OrderInfo>()
                .in(OrderInfo::getOrderId, orderIdList)
                .eq(OrderInfo::getDeleted, Boolean.FALSE);
        List<OrderInfo> orderInfoList = orderInfoMapper.selectList(orderQueryWrapper);
        if (CollUtil.isEmpty(orderInfoList)) {
            throw new BaseRuntimeException(OrderErrorCodeEnums.ORDER_001);
        }
        AtomicReference<Long> totalPayMoney = new AtomicReference<>(0L);
        String orderIdStr = orderInfoList.stream().map(o -> {
            totalPayMoney.updateAndGet(v -> v + o.getPayMoney());
            return String.valueOf(o.getOrderId());
        }).collect(Collectors.joining(","));

        //调用统一下单API
        HttpPost httpPost = new HttpPost(wxPayConfig.getDomain().concat(WxApiType.JSAPI_PAY.getType()));
        String paymentKey = OrderNoUtils.createOrderNo(OrderNoUtils.PAYMENT_SERIAL_NUMBER_PREFIX);
        // key值有效期为1小时
        redisUtil.set(RedisKeys.OrderKeys.ORDER_PAYMENT_KEY + paymentKey, orderIdStr, 3600);
        // 请求body参数
        Gson gson = new Gson();
        Map<String, Object> paramsMap = new HashMap<>(CommonConstants.DEFAULT_MAP_CAPACITY);
        paramsMap.put("appid", wxPayConfig.getAppId());
        paramsMap.put("mchid", wxPayConfig.getMchId());
        paramsMap.put("description", orderIdStr);
        paramsMap.put("out_trade_no", paymentKey);
        paramsMap.put("notify_url", wxPayConfig.getNotifyDomain().concat(WxNotifyType.JSAPI_NOTIFY.getType()));

        Map<String, Object> amountMap = new HashMap<>(CommonConstants.DEFAULT_MAP_CAPACITY);
        Long totalMoney = totalPayMoney.get();
        Boolean enableVirtualPay = Optional.ofNullable(enabled.getEnableVirtualPay()).orElse(Boolean.TRUE);
        if (enableVirtualPay) {
            totalMoney = 1L;
        }
        amountMap.put("total", totalMoney);
        amountMap.put("currency", "CNY");
        paramsMap.put("amount", amountMap);

        Map<String, String> payerMap = new HashMap<>(CommonConstants.DEFAULT_MAP_CAPACITY);
        final UserThirdInfoApiVo userThirdInfoApiVo = userFeignApi.getUserThirdInfoRsp(orderWxPayAppReq.getUserId(), "WX", wxPayConfig.getManagerAppId());
        payerMap.put("openid", userThirdInfoApiVo.getOpenId());
        paramsMap.put("payer", payerMap);

        //将参数转换成json字符串
        String jsonParams = gson.toJson(paramsMap);
        StringEntity entity = new StringEntity(jsonParams, "utf-8");
        entity.setContentType("application/json");
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept", "application/json");

        //完成签名并执行请求
        CloseableHttpResponse response = wxPayClient.execute(httpPost);
        //返回二维码
        Map<String, String> map = new HashMap<>(CommonConstants.DEFAULT_MAP_CAPACITY);
        try {
            //响应体
            String bodyAsString = EntityUtils.toString(response.getEntity());
            log.info("成功, 支付接口返回结果:{} ", bodyAsString);
            //响应状态码
            int statusCode = response.getStatusLine().getStatusCode();
            //响应结果
            Map<String, String> resultMap = gson.fromJson(bodyAsString, HashMap.class);
            //处理成功
            if (statusCode != 200 && statusCode != 204) {
                throw new BaseRuntimeException(OrderErrorCodeEnums.ORDER_002.code, resultMap.get("message"));
            }

            //预支付交易会话标识
            String prePayId = resultMap.get("prepay_id");
            String prePayIdStr = "prepay_id=" + prePayId;
            long epochSecond = LocalDateTime.now().toEpochSecond(ZoneOffset.of("+8"));
            String timestamp = String.valueOf(epochSecond);
            String nonceStr = NonceUtil.createNonce(32);

            //关联的公众号的appid
            map.put("appId", wxPayConfig.getAppId());
            //时间戳
            map.put("timeStamp", timestamp);
            //生成随机字符串
            map.put("nonceStr", nonceStr);
            map.put("package", prePayIdStr);
            map.put("signType", "RSA");
            map.put("paySign", wxPayConfig.getPaySign(timestamp, nonceStr, prePayIdStr));
        } finally {
            response.close();
        }
        return map;
    }
    
    @Transactional(rollbackFor = Exception.class)
    @Override
    public Boolean processOrder(Map<String, Object> bodyMap) throws GeneralSecurityException {
        //将明文转换成map
        HashMap plainTextMap = new Gson().fromJson(decryptFromResource(bodyMap), HashMap.class);
        String paymentKey = String.valueOf(plainTextMap.get("out_trade_no"));
        Object orderIdObj = redisUtil.get(RedisKeys.OrderKeys.ORDER_PAYMENT_KEY + paymentKey);
        if (ObjectUtil.isNull(orderIdObj)) {
            return Boolean.FALSE;
        }
        // 处理支付成功后业务数据
        ... ...
        return Boolean.TRUE;
    }
    
      /**
     * 退款
     *
     * @param orderWxPayAppReq
     * @return
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public Boolean wxRefund(OrderWxPayAppReq orderWxPayAppReq) throws IOException {
        if (ObjectUtil.isEmpty(orderWxPayAppReq.getAfterOrderId())) {
            throw new BaseRuntimeException(OrderErrorCodeEnums.BASE_003.code, OrderErrorCodeEnums.BASE_003.desc);
        }
        OrderAfter orderAfter = orderAfterMapper.selectById(orderWxPayAppReq.getAfterOrderId());
        OrderInfo orderInfo = orderInfoMapper.selectById(orderAfter.getOrderId());

        if (ObjectUtil.isNull(orderInfo.getPaySerialNumber())) {
            return Boolean.FALSE;
        }
        // 请求body参数
        Gson gson = new Gson();
        Map<String, Object> paramsMap = new HashMap<>(CommonConstants.DEFAULT_MAP_CAPACITY);
        paramsMap.put("out_trade_no", orderInfo.getPaySerialNumber());
        paramsMap.put("out_refund_no", String.valueOf(orderAfter.getAfterOrderId()));
        paramsMap.put("reason", orderWxPayAppReq.getRefundReason());
        paramsMap.put("notify_url", wxPayConfig.getNotifyDomain().concat(WxNotifyType.REFUND_NOTIFY.getType()));

        Map<String, Object> amountMap = new HashMap<>(CommonConstants.DEFAULT_MAP_CAPACITY);
        final Boolean enableVirtualPay = Optional.ofNullable(enabled.getEnableVirtualPay()).orElse(Boolean.TRUE);
        Long refund = orderAfter.getApplyRefundAmount();
        Long total = orderInfo.getPayMoney();
        if (enableVirtualPay) {
            refund = 1L;
            total = 1L;
        }
        amountMap.put("refund", refund);
        amountMap.put("total", total);
        amountMap.put("currency", "CNY");
        paramsMap.put("amount", amountMap);

        //将参数转换成json字符串
        String jsonParams = gson.toJson(paramsMap);
        StringEntity entity = new StringEntity(jsonParams, "utf-8");
        entity.setContentType("application/json");

        //调用统一下单API
        String url = wxPayConfig.getDomain().concat(WxApiType.DOMESTIC_REFUNDS.getType());
        HttpPost httpPost = new HttpPost(url);
        httpPost.setEntity(entity);
        httpPost.setHeader("Accept", "application/json");

        //完成签名并执行请求,并完成验签
        CloseableHttpResponse response = wxPayClient.execute(httpPost);
        try {
            //解析响应结果
            String bodyAsString = EntityUtils.toString(response.getEntity());
            int statusCode = response.getStatusLine().getStatusCode();
            Map<String, Object> bodyMap = gson.fromJson(bodyAsString, HashMap.class);
            if (statusCode != 200 && statusCode != 204) {
                throw new BaseRuntimeException(OrderErrorCodeEnums.BASE_003.getCode(), String.valueOf(bodyMap.get("message")));
            }
            //保存退款日志
            return saveRefundPaymentBillInfo(bodyMap);
        } finally {
            response.close();
        }
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean processRefund(Map<String, Object> bodyMap) throws GeneralSecurityException, InterruptedException {
        //解密报文
        String plainText = decryptFromResource(bodyMap);
        //将明文转换成map
        HashMap plainTextMap = new Gson().fromJson(plainText, HashMap.class);
        String paySerialNumber = (String) plainTextMap.get("out_trade_no");
        String outRefundNo = (String) plainTextMap.get("out_refund_no");
        // 处理退款后的业务逻辑
        ... ...
        return Boolean.TRUE;
    }

    /**
     * 对称解密
     *
     * @param bodyMap
     * @return
     */
    private String decryptFromResource(Map<String, Object> bodyMap) throws GeneralSecurityException {
        log.info("密文解密");
        //通知数据
        Map<String, String> resourceMap = (Map) bodyMap.get("resource");
        //数据密文
        String ciphertext = resourceMap.get("ciphertext");
        //随机串
        String nonce = resourceMap.get("nonce");
        //附加数据
        String associatedData = resourceMap.get("associated_data");

        AesUtil aesUtil = new AesUtil(wxPayConfig.getApiV3Key().getBytes(StandardCharsets.UTF_8));
        String plainText = aesUtil.decryptToString(associatedData.getBytes(StandardCharsets.UTF_8),
                nonce.getBytes(StandardCharsets.UTF_8),
                ciphertext);
        return plainText;
    }

2.5、WxApiType.java

@AllArgsConstructor
@Getter
public enum WxApiType {

	/**
	 * JSAPI下单
	 * https://api.mch.weixin.qq.com/v3/pay/transactions/jsapi
	 */
	JSAPI_PAY("/v3/pay/transactions/jsapi"),
	/**
	 * 申请退款
	 * https://api.mch.weixin.qq.com/v3/refund/domestic/refunds
	 */
	DOMESTIC_REFUNDS("/v3/refund/domestic/refunds");

	/**
	 * 类型
	 */
	private final String type;
}

2.6、WxPayConfig.java

/**
* 微信支付配置类
*/
@Configuration
@ConfigurationProperties(prefix = "wxpay")
@Data
@Slf4j
public class WxPayConfig {

    /**
     * 商户号
     */
    private String mchId;
    
    /**
     * 商户API证书序列号
     */
    private String mchSerialNo;

    /**
     * APIv3密钥
     */
    private String apiV3Key;

    /**
     * APPID
     */
    private String appId;

    /**
     * 微信服务器地址
     */
    private String domain;

    /**
     * 微信回调通知地址
     */
    private String notifyDomain;

    /**
     * 商户密钥
     */
    private String privateKey;

    /**
     * 获取商户的私钥
     *
     * @return
     */
    public PrivateKey getPrivateKey(String privateKey) {
        return PemUtil.loadPrivateKey(privateKey);
    }

    /**
     * 获取签名验证器
     *
     * @return
     */
    @Bean
    public ScheduledUpdateCertificatesVerifier getVerifier() {
        log.info("获取签名验证器");
        //获取商户私钥
        PrivateKey privateKeyObj = getPrivateKey(privateKey);
        //私钥签名对象
        PrivateKeySigner privateKeySigner = new PrivateKeySigner(mchSerialNo, privateKeyObj);
        //身份认证对象
        WechatPay2Credentials wechatPay2Credentials = new WechatPay2Credentials(mchId, privateKeySigner);
        // 使用定时更新的签名验证器,不需要传入证书
        ScheduledUpdateCertificatesVerifier verifier = new ScheduledUpdateCertificatesVerifier(
                wechatPay2Credentials,
                apiV3Key.getBytes(StandardCharsets.UTF_8));
        return verifier;
    }

    /**
     * 获取http请求对象
     *
     */
    @Bean(name = "wxPayClient")
    public CloseableHttpClient getWxPayClient(ScheduledUpdateCertificatesVerifier verifier) throws FileNotFoundException {
        log.info("获取httpClient");
        //获取商户私钥
        PrivateKey privateKeyObj = getPrivateKey(privateKey);
        WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                .withMerchant(mchId, mchSerialNo, privateKeyObj)
                .withValidator(new WechatPay2Validator(verifier));
        // ... 接下来,你仍然可以通过builder设置各种参数,来配置你的HttpClient
        // 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
        CloseableHttpClient httpClient = builder.build();
        return httpClient;
    }

    public String getPaySign(String timestamp, String nonceStr, String prePayIdStr) {
        String paySign = "";
        try {
            Signature sign = Signature.getInstance("SHA256withRSA");
            sign.initSign(getPrivateKey(privateKey));
            String message = appId + "\n" + timestamp + "\n" + nonceStr + "\n" + prePayIdStr + "\n";
            sign.update(message.getBytes(StandardCharsets.UTF_8));
            paySign = Base64.getEncoder().encodeToString(sign.sign());
        } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) {
            e.printStackTrace();
        }
        return paySign;
    }

    public static void main(String[] args) throws SignatureException, NoSuchAlgorithmException, InvalidKeyException {
        WxPayConfig wxPayConfig = new WxPayConfig();
        long timestamp = 1414561699L;
        String nonceStr = "5K8264ILTKCH16CQ2502SI8ZNMTM67VS";
        String packageVal = "prepay_id=wx201410272009395522657a690389285100";
        String appId = "wx8b888b888e888d8a";
        String sign = wxPayConfig.getPaySign(String.valueOf(timestamp), nonceStr, packageVal);
        System.out.println(sign);
    }
}

2.7、WxNotifyType.java

/**
 * 微信支付,退款回调地址
 * 1.此处注意接口路径地址
 * 2.在权限控制拦截器中放开接口限制
 */

@AllArgsConstructor
@Getter
public enum WxNotifyType {

    /**
     * 支付通知
     */
    JSAPI_NOTIFY("/app/order/wx/pay/callback/pay/notify"),

    /**
     * 退款结果通知
     */
    REFUND_NOTIFY("/app/order/wx/pay/callback/refunds/notify");

    /**
     * 类型
     */
    private final String type;
}

三、参考资料

产品介绍 - JSAPI支付 | 微信支付商户文档中心

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

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

相关文章

qml 和 c++类的数据交互

1、 新建一个需要交互的C++类 1)添加QObject头文件 2)添加自QObject的继承 3)添加Q_OBJECT宏 4)使用Q_PROPERTY,定义两个交互的属性,并设置读写的方法和变更属性的信号。 5)添加方法、槽函数和变量 2、在main.cpp中添加实例化对象的QML上下文 1)添加需要QML交互的…

多个.C文件被编译为一个可执行文件的详细过程

多个.C文件被编译为一个可执行文件的详细过程 文章目录 多个.C文件被编译为一个可执行文件的详细过程前言一、一个.C文件的编译过程二、多个.C文件的链接过程1.文件信息2.链接过程3.makefile 总结 前言 C语言经典的 “hello world ” 程序从编写、编译到运行&#xff0c;看到屏…

html实现网页插入音频

前言 欢迎来到我的博客 个人主页:北岭敲键盘的荒漠猫-CSDN博客 本文主要介绍html中 如何插入音乐和视频 视频插入 标签:<video></video> 兼容格式:mp4,因为别的浏览器都有不兼容的格式&#xff0c;唯一对mp4全都兼容。所以尽量使用mp4格式。 属性: 属性属性值…

249 基于matlab的MED、OMEDA、MOMEDA、MCKD信号处理方法

基于matlab的MED、OMEDA、MOMEDA、MCKD信号处理方法。最小熵反褶积(MED)&#xff0c;最优最小熵反卷积调整卷积 (OMEDA),多点最优最小熵解卷积调整&#xff08;Multipoint Optimal Minimum Entropy Deconvolution Adjusted&#xff0c;MOMEDA&#xff09;&#xff0c;最大相关峭…

【Shell脚本】Shell编程之循环语句

目录 一.循环语句 1.for语句的结构 1.1.格式 1.2.实操案例 案例1. 案例2. 案例3. 案例4. 2.while语句的结构 2.1.格式 2.2.实操案例 案例1. 案例2. 案例3. 案例4. 3.until循环命令 3.1.格式 3.2.实操案例 案例1. 二.补充 1.常用转义符 一.循环语句 1.for…

JAVA 双亲委派之一

JAVA 双亲委派之一 JVM类加载流程 java语言系统内置了众多类加载器&#xff0c;从一定程度上讲&#xff0c;只存在两种不同的类加载器&#xff1a;一种是启动类加载器&#xff0c;此类加载由C实现&#xff0c;是JVM的一部分&#xff1b;另一种就是所有其他的类加载器&#xf…

IF:23.2|从实验室到田间,微生物干预提高植物抗逆

期刊&#xff1a;Nature Food 影响因子&#xff1a;23.2 发表时间&#xff1a;2023年10月 本研究介绍了一种名为SynCom的微生物组合&#xff0c;该组合Rhodococcus erythropolis和Pseudomonas aeruginosa两种微生物组成。这两种微生物能够帮助水稻抵抗铝毒害和磷缺乏&…

springboot3项目练习详细步骤(第二部分:文章分类模块)

新增文章分类 接口文档 业务实现 参数校验 文章分类列表 接口文档 业务实现 获取文章分类详情 接口文档 业务实现 更新文章分类 接口文档 业务实现 分组校验 问题 概念 实现步骤 总结 删除文章分类 接口文档 业务实现 该模块大部分请求的路径相同&…

mac安装禅道

前提已安装&#xff1a;phpapacheMySQL mac安装 php7.1/apache-CSDN博客 安装MySQL 一、禅道下载 安装官方文档 源码包下载地址&#xff1a;禅道下载 - 禅道开源项目管理软件 。 1. 解压禅道源码包 2. 将解压后的文件复制到Apache访问目录下 &#xff08;默认路径为 /Libra…

【进程替换】多进程程序替换原理 | 进程程序替换函数 | execlexecv | execlpexecvp

目录 多进程程序替换 多进程程序替换原理 进程程序替换函数详解 execl&execv execlp&execvp execle&execvpe execve 多进程程序替换 我们想要进程替换的同时不影响旧的进程&#xff08;使用多进程版&#xff09;fork创建子进程&#xff0c;让子进程去替换执…

Neuralink首个脑机接口患者:打游戏、搞研究两不误,重获自主能力

今年1月28日&#xff0c;Neuralink首次将侵入式脑机接口植入人类患者Noland Arbaugh的大脑。100天后&#xff0c;这家由埃隆马斯克创立的公司公布了最新的进展。从Neuralink的更新中我们可以看到&#xff0c;Arbaugh的恢复情况超出预期&#xff0c;他的用户体验也非常积极。 原…

回溯算法—组合问题

文章目录 介绍应用问题基本流程算法模版例题&#xff08;1&#xff09;组合&#xff08;2&#xff09;电话号码的字母组合 介绍 回溯算法实际上是 一个类似枚举的搜索尝试过程&#xff0c;主要是在搜索尝试过程中寻找问题的解&#xff0c;当发现已不满足求解条件时&#xff0c;…

CSS-页面导航栏实现-每文一言(过有意义的生活,做最好的自己)

&#x1f390;每文一言 过有意义的生活,做最好的自己 目录 &#x1f390;每文一言 &#x1f6d2;盒子模型 &#x1f453;外间距 (margin) &#x1f97c;边框 &#x1f45c;内边距 切换盒子模型计算方案&#xff1a; &#x1f3a2; 浮动布局 浮动特点 &#x1f3c6;导航…

并行执行的4种类别——《OceanBase 并行执行》系列 4

OceanBase 支持多种类型语句的并行执行。在本篇博客中&#xff0c;我们将根据并行执行的不同类别&#xff0c;分别详细阐述&#xff1a;并行查询、并行数据操作语言&#xff08;DML&#xff09;、并行数据定义语言&#xff08;DDL&#xff09;以及并行 LOAD DATA 。 《并行执行…

Docker部署Metabase

文章目录 Docker安装MetabaseCentOS7安装Docker获取最新的 Docker 镜像启动Metabase容器在Metabase初始化时查看日志访问Metabase Metabase 的 ClickHouse 驱动程序安装环境简介删除容器创建容器下载click house驱动放入驱动重启容器将元数据库连接到 ClickHouse报错解决 Docke…

轻松管理文件夹批量重命名:学会用关键词批量替换文件夹名称技巧

随着计算机中存储的文件和文件夹数量不断增加&#xff0c;如何有效地管理和组织它们变得尤为重要。批量重命名文件夹是提升文件管理效率的关键步骤之一。而利用云炫文件管理器中关键词批量替换文件夹名称的技巧&#xff0c;则可以帮助我们更快速地完成这一任务。 关键词批量替…

LeetCode例题讲解:876.链表的中间结点

给你单链表的头结点 head &#xff0c;请你找出并返回链表的中间结点。 如果有两个中间结点&#xff0c;则返回第二个中间结点。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5] 输出&#xff1a;[3,4,5] 解释&#xff1a;链表只有一个中间结点&#xff0c;值为 3 。…

KMeans,KNN,Mean-shift算法的学习

1.KMeans算法是什么&#xff1f; 在没有标准标签的情况下&#xff0c;以空间的k个节点为中心进行聚类&#xff0c;对最靠近他们的对象进行归类。 2.KMeans公式&#xff1a; 2. 1.关键分为三个部分&#xff1a; 1.一开始会定义n个中心点&#xff0c;然后计算各数据点与中心点…

浅谈云计算资源和服务

目录 前言 正文 专有名词及其首字母缩写 轻量级应用服务器 云服务器ECS 专有网络VPC 其他类服务 尾声 &#x1f52d; Hi,I’m Pleasure1234&#x1f331; I’m currently learning Vue.js,SpringBoot,Computer Security and so on.&#x1f46f; I’m studying in University o…

如何用Python创建目录以及自动创建缺失的父目录

在Python中&#xff0c;创建目录&#xff08;也称为文件夹&#xff09;是一个常见的任务&#xff0c;尤其是当你需要为文件系统上的某些文件创建存储位置时。幸运的是&#xff0c;Python的os模块提供了一个非常方便的函数os.makedirs()&#xff0c;它允许你创建一个目录&#x…