微信小程序java后台读取秘钥文件,win本地没问题, 部署到linux线上出现项目无法启动,报秘钥无法找到的错误

我的方法

package com.ruoyi.business.config;

import com.wechat.pay.contrib.apache.httpclient.WechatPayHttpClientBuilder;
import com.wechat.pay.contrib.apache.httpclient.auth.PrivateKeySigner;
import com.wechat.pay.contrib.apache.httpclient.auth.ScheduledUpdateCertificatesVerifier;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Credentials;
import com.wechat.pay.contrib.apache.httpclient.auth.WechatPay2Validator;
import com.wechat.pay.contrib.apache.httpclient.util.PemUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.impl.client.CloseableHttpClient;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.ResourceUtils;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;


/**
 * 微信支付配置类
 * jdk参考地址: ...
 * 目前用到的jdk版本是: v0.3.0
 */
@Configuration
@ConfigurationProperties(prefix = "wxpay")
@Data // 使用set方法将wxpay节点中的值填充到当前类的属性中
@Slf4j
public class WxPayConfig {

    // 商户号
    private String mchId;

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

    // 商户私钥文件
    private String privateKeyPath;

    // APIv3密钥
    private String apiV3Key;

    // APPID
    private String appId;

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

    // 接收结果通知地址
    private String notifyDomain;

    // APIv2密钥
    private String partnerKey;

    /**
     * 获取商户的私钥文件
     *
     * @param filename the filename
     * @return private key
     */
    public PrivateKey getPrivateKey(String filename) {
        try {
            /*
            ResourceUtils.getFile(filename) 来获取文件的。这是 Spring 的一个工具类,它主要用于获取 classpath 中的资源文件。
            在应用打成jar或war包之后,classpath中的资源文件会被打包进jar或war中,这时候你使用
            ResourceUtils.getFile(filename) 是无法获取到文件的,因为它不是一个独立的文件系统中的文件了,而是被打包在jar或war包内部。

            解决方案

            1. 将私钥文件放在应用的外部,然后通过文件路径来读取。例如:
            privateKeyPath = "/etc/myapp/apiclient_key.pem";
            File file = new File(privateKeyPath);
            return PemUtil.loadPrivateKey(new FileInputStream(file));
            这样你就可以将 privateKeyPath 设置为任意位置的文件路径。


            2.如果你确实需要将私钥文件打包到应用中,你可以使用 ResourceUtils.getURL(filename).openStream() 来获取文件内容,例如:
            privateKeyPath = "classpath:cert/apiclient_key.pem";
            return PemUtil.loadPrivateKey(ResourceUtils.getURL(privateKeyPath).openStream());
            这样你就可以从应用的 classpath 中读取文件内容。

            ResourceUtils.getURL(filename).openStream() 来获取资源,这是与平台无关的,所以在Windows、Linux以及其他任何支持Java的平台上都能正常使用。只要你的资源文件(在这个例子中是私钥文件)被正确的包含在了你的应用的classpath中,那么你就可以在任何平台上使用这个方法来读取资源文件的内容。
            这里需要注意的是,classpath: 是一个特殊的协议前缀,它表示资源是从classpath中获取的。当你的资源文件被打包进jar或war时,它们就位于应用的classpath中,因此你可以使用 classpath: 前缀来获取这些文件。
            因此,无论你的应用运行在Windows还是Linux,或者是其他任何操作系统上,使用方法2都不会有问题。只要你的资源文件被正确地打包进了应用,那么就可以使用这种方法来读取文件内容。
             */

			// 原始取秘钥的方法
            // File file = ResourceUtils.getFile(filename);
            // return PemUtil.loadPrivateKey(new FileInputStream(file));

			// 修改后的方法
            return PemUtil.loadPrivateKey(ResourceUtils.getURL(filename).openStream());

        } catch (FileNotFoundException e) {
            throw new RuntimeException("私钥文件不存在", e);
        } catch (IOException e) {
            throw new RuntimeException("私钥文件流失败", e);
        }
    }

    /**
     * 获取签名验证器
     *
     * @return verifier verifier
     */
    @Bean
    public ScheduledUpdateCertificatesVerifier getVerifier() {

        log.info("获取签名验证器");

        // 获取商户私钥
        PrivateKey privateKey = getPrivateKey(privateKeyPath);

        // 私钥签名对象
        PrivateKeySigner privateKeySigner = new PrivateKeySigner(mchSerialNo, privateKey);

        // 身份认证对象
        WechatPay2Credentials wechatPay2Credentials = new WechatPay2Credentials(mchId, privateKeySigner);

        // 使用定时更新的签名验证器,不需要传入证书
        return new ScheduledUpdateCertificatesVerifier(wechatPay2Credentials, apiV3Key.getBytes(StandardCharsets.UTF_8));
    }


    /**
     * 获取http请求对象
     *
     * @param verifier the verifier
     * @return wx pay client
     */
    @Bean(name = "wxPayClient")
    public CloseableHttpClient getWxPayClient(ScheduledUpdateCertificatesVerifier verifier) {

        log.info("获取httpClient");

        // 获取商户私钥
        PrivateKey privateKey = getPrivateKey(privateKeyPath);

        WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                .withMerchant(mchId, mchSerialNo, privateKey)
                .withValidator(new WechatPay2Validator(verifier));
        // ... 接下来,你仍然可以通过builder设置各种参数,来配置你的HttpClient

        // 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
        return builder.build();
    }

    /**
     * 获取HttpClient,无需进行应答签名验证,跳过验签的流程
     *
     * @return the wx pay no sign client
     */
    @Bean(name = "wxPayNoSignClient")
    public CloseableHttpClient getWxPayNoSignClient() {

        // 获取商户私钥
        PrivateKey privateKey = getPrivateKey(privateKeyPath);

        // 用于构造HttpClient
        WechatPayHttpClientBuilder builder = WechatPayHttpClientBuilder.create()
                // 设置商户信息
                .withMerchant(mchId, mchSerialNo, privateKey)
                // 无需进行签名验证、通过withValidator((response) -> true)实现
                .withValidator((response) -> true);

        // 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新
        CloseableHttpClient httpClient = builder.build();

        log.info("== getWxPayNoSignClient END ==");

        return httpClient;
    }


}

你可能感兴趣的:(java,微信小程序,linux)