本文为丢失的博客 地址:https://blog.csdn.net/Dinneratnight/article/details/123074026

# IJPay微信企业付款到零钱协议不正确 No appropriate protocol

### 问题发现

项目中使用了微信企业付款到零钱的功能,因为自己去封装过于麻烦,就使用了LJpay,原本使用的是jdk11的环境去运行一切正常,但是因为云托管没有固定的IP所以转移到了公司服务器,公司服务器的环境是jdk8,所以我把环境换到了jdk8之后报了如下错误

```java

Servlet.service() for servlet [dispatcherServlet] in context with path [/server] threw exception [Request processing failed; nested exception is java.lang.RuntimeException: cn.hutool.core.io.IORuntimeException: SSLHandshakeException: No appropriate protocol (protocol is disabled or cipher suites are inappropriate)] with root cause

javax.net.ssl.SSLHandshakeException: No appropriate protocol (protocol is disabled or cipher suites are inappropriate)

```

### 问题研究

经过在各搜索引擎搜索,网上也有出现相同错误的,原因很直接,jdk的版本原因,这也是和我的猜想一样,因为只有环境变了代码没有做出改变。很多答案都是让我去把useSSL给禁用了了,更有甚者 让我去\jdk安装包目录下的\jre\lib\security中的java.security文件,将对应的SSLv3删除了,还有它后缀的两个一样的算法一起删除保存再重启服务,很明显这不现实,因为服务器里面跑着另一个服务。

### 解决方案

然后我再去查找LJpay相关的这个问题的话题只找到了微信退款协议不正确。随后我去看LJpay的源码。发现 transfers 在AbstractHttpDelegate里面的post是写死的 SSLSocketFactoryBuilder.TLSv1 的协议,想到两个解决方法,一个删除环境安装包的配置(否决了),还有一个是禁用了useSSL,发现作者写的LJpay的WxpayApi里面提供了自定义协议的方法transfersByProtocol。

```java

/**

* 企业付款到零钱

*

* @param params 请求参数

* @param certFile 证书文件的 InputStream

* @param certPass 证书密码

* @return {@link String} 请求返回的结果

*/

public static String transfers(Map<String, String> params, InputStream certFile, String certPass) {

return execution(getReqUrl(WxApiType.TRANSFER, null, false), params, certFile, certPass);

}

/**

* 企业付款到零钱

*

* @param params 请求参数

* @param certFile 证书文件的 InputStream

* @param certPass 证书密码

* @param protocol 协议

* @return {@link String} 请求返回的结果

*/

public static String transfersByProtocol(Map<String, String> params, InputStream certFile, String certPass, String protocol) {

return executionByProtocol(getReqUrl(WxApiType.TRANSFER, null, false), params, certFile, certPass, protocol);

}

```

### 适配优化

但是 我发现我这里就使用到了企业付款到零钱这个功能,而且我个人觉得LJpay封装了太多,为了减少打包的重量,我决定重新实现LJpay企业付款到零钱这个功能。

```java

// 自己的业务代码

String refundStr = post(WxPayApi.getReqUrl(WxApiType.TRANSFER

, null, false), WxPayKit.toXml(params), stream, wxPayV3Bean.getMchId());

// 实际调用AbstractHttpDelegate类中的方法

public String post(String url, String data, InputStream certFile, String certPass) {

try {

return HttpRequest

.post(url)

.setSSLSocketFactory(SSLSocketFactoryBuilder.create()

.setProtocol("")

.setKeyManagers(this.getKeyManager(certPass, null, certFile))

.setSecureRandom(new SecureRandom())

.build())

.body(data)

.execute()

.body();

} catch (Exception var7) {

throw new RuntimeException(var7);

}

}

private KeyManager[] getKeyManager(String certPass, String certPath, InputStream certFile) throws Exception {

KeyStore clientStore = KeyStore.getInstance("PKCS12");

if (certFile != null) {

clientStore.load(certFile, certPass.toCharArray());

} else {

clientStore.load(new FileInputStream(certPath), certPass.toCharArray());

}

KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());

kmf.init(clientStore, certPass.toCharArray());

return kmf.getKeyManagers();

}

```

```java

// 重写生成模板类,因为很多方法都抽象的继承了,为了方便直接调用。

/**

* @Description:

* 重写的这个类主要有两个功能

* 1. 通过Builder创建Map。

* 2. 排序并参与字符拼接的参数组,来实现签名算法

* 3. 构造签名Map加入参数sign

* @author: zwy

* @date: 2022年02月22日 15:23

*/

@Builder

@AllArgsConstructor

@Getter

@Setter

public class TransferModel {

private String mch_appid;

private String mchid;

private String device_info;

private String nonce_str;

private String sign;

private String partner_trade_no;

private String openid;

private String check_name;

private String re_user_name;

private String amount;

private String desc;

private String spbill_create_ip;

//************************继承BaseModel实现类****************************************

/**

* 将建构的 builder 转为 Map

*

* @return 转化后的 Map

*/

public Map<String, String> toMap() {

String[] fieldNames = getFiledNames(this);

HashMap<String, String> map = new HashMap<String, String>(fieldNames.length);

for (String name : fieldNames) {

String value = (String) getFieldValueByName(name, this);

if (StrUtil.isNotEmpty(value)) {

map.put(name, value);

}

}

return map;

}

/**

* 根据属性名获取属性值

*

* @param fieldName 属性名称

* @param obj 对象

* @return 返回对应属性的值

*/

public Object getFieldValueByName(String fieldName, Object obj) {

try {

String firstLetter = fieldName.substring(0, 1).toUpperCase();

String getter = new StringBuffer().append("get")

.append(firstLetter)

.append(fieldName.substring(1))

.toString();

Method method = obj.getClass().getMethod(getter);

return method.invoke(obj);

} catch (Exception e) {

return null;

}

}

/**

* 获取属性名数组

*

* @param obj 对象

* @return 返回对象属性名数组

*/

public String[] getFiledNames(Object obj) {

Field[] fields = obj.getClass().getDeclaredFields();

String[] fieldNames = new String[fields.length];

for (int i = 0; i < fields.length; i++) {

fieldNames[i] = fields[i].getName();

}

return fieldNames;

}

//************************ 排序并参与字符拼接的参数组 ****************************************

/*形参:

params – 需要排序并参与字符拼接的参数组

connStr – 连接符号

encode – 是否进行URLEncoder

返回值:

拼接后字符串*/

public static String createLinkString(Map<String, String> params, String connStr, boolean encode, boolean quotes) {

List<String> keys = new ArrayList<>(params.keySet());

Collections.sort(keys);

StringBuilder content = new StringBuilder();

for (int i = 0; i < keys.size(); i++) {

String key = keys.get(i);

String value = params.get(key);

// 拼接时,不包括最后一个&字符

if (i == keys.size() - 1) {

if (quotes) {

content.append(key).append("=").append('"').append(encode ? urlEncode(value) : value).append('"');

} else {

content.append(key).append("=").append(encode ? urlEncode(value) : value);

}

} else {

if (quotes) {

content.append(key).append("=").append('"').append(encode ? urlEncode(value) : value).append('"').append(connStr);

} else {

content.append(key).append("=").append(encode ? urlEncode(value) : value).append(connStr);

}

}

}

return content.toString();

}

/**

* URL 编码

*

* @param src 需要编码的字符串

* @return 编码后的字符串

*/

public static String urlEncode(String src) {

try {

return URLEncoder.encode(src, CharsetUtil.UTF_8).replace("+", "%20");

} catch (UnsupportedEncodingException e) {

e.printStackTrace();

return null;

}

}

//************************ 构造签名Map ****************************************

/**

* 构建签名 Map

*

* @param partnerKey API KEY

* @return 构建签名后的 Map

*/

public Map<String, String> createSign(String partnerKey) {

return buildSign(toMap(), partnerKey);

}

/**

* 构建签名

*

* @param params 需要签名的参数

* @param partnerKey 密钥

* @return 签名后的 Map

*/

public static Map<String, String> buildSign(Map<String, String> params, String partnerKey) {

String sign = createSign(params, partnerKey);

params.put("sign", sign);

return params;

}

/**

* 生成签名

*

* @param params 需要签名的参数

* @param partnerKey 密钥

* @return 签名后的数据

*/

public static String createSign(Map<String, String> params, String partnerKey) {

// 生成签名前先去除sign

params.remove("sign");

String tempStr = createLinkString(params, "&", false, false);

String stringSignTemp = tempStr + "&key=" + partnerKey;

return SecureUtil.md5(stringSignTemp).toUpperCase();

}

}

```