package com.dsx.wechat;
import com.alibaba.fastjson.JSONObject;
import com.dsx.HttpClientHelper;
import com.dsx.v3.SignatureUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
/**
* @author : tianwenqing
* @version : 1.0
* @date : 2020-08-12 16:56
* @description :
**/
@Slf4j
@Service
public class WechatV3Api {
@Autowired
private SignatureUtil signatureUtil = new SignatureUtil() ;
@Value("${wechat.channel.prikey}")
private String privateKey = "服务商私钥";
@Value("${wechat.channel.merchantId}")
private String merchantId="服务上id";
@Value("${wechat.channel.certificateSerialNumber}")
private String certificateSerialNumber="商户API证书序列号serial_no";
@Autowired
private HttpClientHelper httpClientHelper ;
public void updateImage(File file){
try {
// 换行符
String LINE_END ="\r\n";
String PREFIX ="--";
// 定义数据分隔线
String BOUNDARY ="boundary";
String filename = file.getName();//文件名
FileInputStream fileInputStream = new FileInputStream(file);
String fileSha256 = DigestUtils.sha256Hex(fileInputStream);//文件sha256值
JSONObject jsonObject = new JSONObject();
jsonObject.put("filename",filename);
jsonObject.put("sha256",fileSha256);
String url = "https://api.mch.weixin.qq.com/v3/merchant/media/upload";
log.debug("request url:{}", url);
HttpPost httpPost = new HttpPost(url);
StringEntity json = new StringEntity(jsonObject.toString(), Charset.forName("UTF-8"));
httpPost.setEntity(json);
String authorization = signatureUtil.getAuthorization(httpPost, privateKey, merchantId, certificateSerialNumber);
Map headerMap = new HashMap<>();
headerMap.put("Charsert","UTF-8");
headerMap.put("Accept","application/json");
headerMap.put("Content-Type","multipart/form-data; boundary=" + BOUNDARY);
headerMap.put("Authorization", authorization);
String result = httpClientHelper.post(url, BOUNDARY, file,filename, fileSha256,headerMap);
log.info("updateImage result ->{}", JSONObject.toJSONString(result));
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
File file = new File("/Users/xxx/Downloads/你好.png");
WechatV3Api d = new WechatV3Api();
d.updateImage(file);
}
}
SignatureUtil.java
package com.dsx.v3;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.util.EntityUtils;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.net.URI;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
/**
* @author : tianwenqing
* @version : 1.0
* @date : 2020-05-15 16:44
* @description :
**/
@Slf4j
@Component
public class SignatureUtil {
private static final String SYMBOLS =
"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
private static final SecureRandom RANDOM = new SecureRandom();
private static final String SCHEME = "WECHATPAY2-SHA256-RSA2048 ";
protected String generateNonceStr() {
char[] nonceChars = new char[32];
for (int index = 0; index < nonceChars.length; ++index) {
nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));
}
return new String(nonceChars);
}
protected long generateTimestamp() {
return System.currentTimeMillis() / 1000;
}
/**
* 生成v3请求头信息
* @param request 请求url
* @param privateKey 私钥
* @param merchantId 微信商户号
* @param certificateSerialNumber 商户API证书序列号serial_no,用于声明所使用的证书
* @return
* @throws IOException
*/
public String getAuthorization(HttpUriRequest request, PrivateKey privateKey, String merchantId, String certificateSerialNumber){
try {
String nonceStr = generateNonceStr();
long timestamp = generateTimestamp();
String message = buildMessage(nonceStr, timestamp, request);
log.debug("authorization message=[{}]", message);
String sign = sign(message.getBytes("utf-8"), privateKey);
String token = "mchid=\"" + merchantId + "\","
+ "nonce_str=\"" + nonceStr + "\","
+ "timestamp=\"" + timestamp + "\","
+ "serial_no=\"" + certificateSerialNumber + "\","
+ "signature=\"" + sign + "\"";
log.debug("authorization token=[{}]", token);
String authorization = SCHEME + token;
log.debug("authorization [{}]", authorization);
return authorization;
} catch (Exception e) {
log.error("Exception",e);
throw new RuntimeException(e);
}
}
/**
* 生成v3请求头信息
* @param request 请求url
* @param privateKey 私钥
* @param merchantId 微信商户号
* @param certificateSerialNumber 商户API证书序列号serial_no,用于声明所使用的证书
* @return
* @throws IOException
*/
public String getAuthorization(HttpUriRequest request, String privateKey, String merchantId, String certificateSerialNumber){
try {
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
byte[] decode = com.yunzong.parking.common.utils.rsa.Base64.decode(privateKey);
PrivateKey key = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(decode));
String nonceStr = generateNonceStr();
long timestamp = generateTimestamp();
String message = buildMessage(nonceStr, timestamp, request);
log.debug("authorization message=[{}]", message);
String sign = sign(message.getBytes("utf-8"), key);
String token = "mchid=\"" + merchantId + "\","
+ "nonce_str=\"" + nonceStr + "\","
+ "timestamp=\"" + timestamp + "\","
+ "serial_no=\"" + certificateSerialNumber + "\","
+ "signature=\"" + sign + "\"";
log.debug("authorization token=[{}]", token);
String authorization = SCHEME + token;
log.debug("authorization [{}]", authorization);
return authorization;
} catch (NoSuchAlgorithmException e) {
log.error("NoSuchAlgorithmException",e);
throw new RuntimeException(e);
} catch (Exception e) {
log.error("Exception",e);
throw new RuntimeException(e);
}
}
/**
* 生成v3请求头信息
* @param requestUrl 请求url
* @param privateKey 私钥
* @param merchantId 微信商户号
* @param certificateSerialNumber 商户API证书序列号serial_no,用于声明所使用的证书
* @param httpMethod 请求方式: GET/POST
* @return
* @throws IOException
*/
public String getAuthorization(String requestUrl, String privateKey, String merchantId, String certificateSerialNumber,String httpMethod){
HttpUriRequest request =null ;
try {
if(httpMethod.equalsIgnoreCase("GET")){
request = new HttpGet(requestUrl);
}else if(httpMethod.equalsIgnoreCase("POST")){
request = new HttpPost(requestUrl);
}
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
byte[] decode = com.yunzong.parking.common.utils.rsa.Base64.decode(privateKey);
PrivateKey key = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(decode));
String nonceStr = generateNonceStr();
long timestamp = generateTimestamp();
String message = buildMessage(nonceStr, timestamp, request);
log.debug("authorization message=[{}]", message);
String sign = sign(message.getBytes("utf-8"), key);
String token = "mchid=\"" + merchantId + "\","
+ "nonce_str=\"" + nonceStr + "\","
+ "timestamp=\"" + timestamp + "\","
+ "serial_no=\"" + certificateSerialNumber + "\","
+ "signature=\"" + sign + "\"";
log.debug("authorization token=[{}]", token);
String authorization = SCHEME + token;
log.debug("authorization [{}]", authorization);
return authorization;
} catch (NoSuchAlgorithmException e) {
log.error("NoSuchAlgorithmException",e);
throw new RuntimeException(e);
} catch (Exception e) {
log.error("Exception",e);
throw new RuntimeException(e);
}
}
/**
* SHA256withRSA 签名
*
* @param message
* @param privateKey
* @return
*/
private String sign(byte[] message, PrivateKey privateKey) {
try {
Signature sign = Signature.getInstance("SHA256withRSA");
sign.initSign(privateKey);
sign.update(message);
return Base64.getEncoder().encodeToString(sign.sign());
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("当前Java环境不支持SHA256withRSA", e);
} catch (InvalidKeyException e) {
throw new RuntimeException("签名计算失败", e);
} catch (SignatureException e) {
throw new RuntimeException("无效的私钥", e);
}
}
protected final String buildMessage(String nonce, long timestamp, HttpUriRequest request)
throws IOException {
URI uri = request.getURI();
String canonicalUrl = uri.getRawPath();
if (uri.getQuery() != null) {
canonicalUrl += "?" + uri.getRawQuery();
}
String body = "";
// PATCH,POST,PUT
if (request instanceof HttpEntityEnclosingRequestBase) {
body = EntityUtils.toString(((HttpEntityEnclosingRequestBase) request).getEntity());
}
return request.getRequestLine().getMethod() + "\n"
+ canonicalUrl + "\n"
+ timestamp + "\n"
+ nonce + "\n"
+ body + "\n";
}
}
HttpClientHelper.java
package com.dsx.util;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntity;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* @author : tianwenqing
* @version : 1.0
* @date : 2020-05-07 21:19
* @description : httpclient 辅助类
**/
@Slf4j
@Component
public class HttpClientHelper {
public static final String ACCEPT_TYPE_JSON = "application/json";
public static final String ACCEPT_TYPE_HTML = "text/html";
public static final String ACCEPT_TYPE_XML = "application/xml";
public static final String ACCEPT_TYPE_X_WWW = "application/x-www-form-urlencoded";
public static final int DEFAULT_SO_TIMEOUT = 30000;
public static final int DEFAULT_CONN_TIMEOUT = 6000;
public static final int DEFAULT_CONN_REQ_TIMEOUT = 5000;
private static PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
private RequestConfig requestConfig;
@PostConstruct
public void init() {
this.requestConfig = RequestConfig.custom().setSocketTimeout(DEFAULT_SO_TIMEOUT).setConnectTimeout(DEFAULT_CONN_TIMEOUT).setConnectionRequestTimeout(DEFAULT_CONN_REQ_TIMEOUT).build();
}
@PreDestroy
public void destory() {
if (cm != null) {
log.info("关闭HttpClient线程池");
cm.shutdown();
}
}
public static enum POST_MEDIA_TYPE {
JSON,
FORM,
JSONSTR,
XML;
private POST_MEDIA_TYPE() {
}
}
public String post(String url, Map<String, Object> params, POST_MEDIA_TYPE mediaType) {
String result = null;
HttpEntity resEntity = null;
CloseableHttpResponse response = null;
if (null == mediaType) {
mediaType = POST_MEDIA_TYPE.JSON;
}
List<NameValuePair> list = new ArrayList();
Iterator iterator = params.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, String> elem = (Map.Entry) iterator.next();
list.add(new BasicNameValuePair((String) elem.getKey(), (String) elem.getValue()));
}
CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(cm).setConnectionManagerShared(true).build();
HttpPost httpPost = new HttpPost(url);
httpPost.setConfig(this.requestConfig);
if (list.size() > 0) {
UrlEncodedFormEntity entity = null;
try {
entity = new UrlEncodedFormEntity(list, "UTF-8");
} catch (UnsupportedEncodingException e) {
log.error(e.getMessage(), e);
}
httpPost.setEntity(entity);
}
if (POST_MEDIA_TYPE.JSON == mediaType) {
httpPost.setHeader("Accept", ACCEPT_TYPE_JSON);
httpPost.setHeader("Content-type", ACCEPT_TYPE_JSON);
} else if (POST_MEDIA_TYPE.FORM == mediaType) {
httpPost.setHeader("Content-Type", ACCEPT_TYPE_X_WWW);
}
try {
response = httpClient.execute(httpPost);
if (response != null) {
resEntity = response.getEntity();
if (resEntity != null) {
result = EntityUtils.toString(resEntity, "UTF-8");
}
}
} catch (IOException var10) {
log.error(var10.getMessage(), var10);
} finally {
if (resEntity != null) {
EntityUtils.consumeQuietly(resEntity);
}
if (response != null) {
try {
response.close();
} catch (IOException var20) {
log.error("关闭HttpResponse出错,错误信息:" + var20.getMessage(), var20);
}
}
// if (httpClient != null) {
// try {
// httpClient.close();
// } catch (IOException var19) {
// log.error("关闭HttpClient出错,错误信息:" + var19.getMessage(), var19);
// }
// }
}
return result;
}
public String post(String url, String boundary, File file , String filename, String fileSha256, Map<String, String> headers) {
String result = null;
HttpEntity resEntity = null;
CloseableHttpResponse response = null;
CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(cm).setConnectionManagerShared(true).build();
HttpPost httpPost = new HttpPost(url);
httpPost.setConfig(this.requestConfig);
if (headers != null && headers.size() > 0) {
for (String key : headers.keySet()) {
httpPost.setHeader(key, headers.get(key));
}
}
//创建MultipartEntityBuilder
MultipartEntityBuilder multipartEntityBuilder = MultipartEntityBuilder.create().setMode(HttpMultipartMode.RFC6532);
//设置boundary
multipartEntityBuilder.setBoundary(boundary);
multipartEntityBuilder.setCharset(Charset.forName("UTF-8"));
//设置meta内容
multipartEntityBuilder.addTextBody("meta","{\"filename\":\""+filename+"\",\"sha256\":\""+fileSha256+"\"}", ContentType.APPLICATION_JSON);
//设置图片内容
multipartEntityBuilder.addBinaryBody("file", file, ContentType.create("image/jpg"), filename);
//放入内容
httpPost.setEntity(multipartEntityBuilder.build());
try {
response = httpClient.execute(httpPost);
if (response != null) {
resEntity = response.getEntity();
if (resEntity != null) {
result = EntityUtils.toString(resEntity, "UTF-8");
}
}
} catch (IOException var10) {
log.error(var10.getMessage(), var10);
} finally {
if (resEntity != null) {
EntityUtils.consumeQuietly(resEntity);
}
if (response != null) {
try {
response.close();
} catch (IOException var20) {
log.error("关闭HttpResponse出错,错误信息:" + var20.getMessage(), var20);
}
}
}
return result;
}
public String get(String url, Map<String, Object> params, POST_MEDIA_TYPE mediaType, Map<String, String> headers) {
String result = null;
HttpEntity resEntity = null;
CloseableHttpResponse response = null;
if (null == mediaType) {
mediaType = POST_MEDIA_TYPE.JSON;
}
StringBuilder paramSB = new StringBuilder();
if (params != null) {
for (Map.Entry<String, Object> entry : params.entrySet()) {
paramSB.append("&").append(entry.getKey()).append("=").append(entry.getValue());
}
}
if (paramSB.length() > 0) {
url = url + "?" + paramSB.substring(1);
}
CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(cm).setConnectionManagerShared(true).build();
HttpGet httpGet = new HttpGet(url);
httpGet.setConfig(this.requestConfig);
if (POST_MEDIA_TYPE.JSON == mediaType) {
httpGet.setHeader("Accept", ACCEPT_TYPE_JSON);
httpGet.setHeader("Content-type", ACCEPT_TYPE_JSON);
} else if (POST_MEDIA_TYPE.FORM == mediaType) {
httpGet.setHeader("Content-Type", ACCEPT_TYPE_X_WWW);
}
if (headers != null && headers.size() > 0) {
for (String key : headers.keySet()) {
httpGet.setHeader(key, headers.get(key));
}
}
try {
response = httpClient.execute(httpGet);
if (response != null) {
resEntity = response.getEntity();
if (resEntity != null) {
result = EntityUtils.toString(resEntity, "UTF-8");
}
}
} catch (IOException var10) {
log.error(var10.getMessage(), var10);
} finally {
if (resEntity != null) {
EntityUtils.consumeQuietly(resEntity);
}
if (response != null) {
try {
response.close();
} catch (IOException var20) {
log.error("关闭HttpResponse出错,错误信息:" + var20.getMessage(), var20);
}
}
// if (httpClient != null) {
// try {
// httpClient.close();
// } catch (IOException var19) {
// log.error("关闭HttpClient出错,错误信息:" + var19.getMessage(), var19);
// }
// }
}
return result;
}
}
上述代码不算是完成代码,上传图片的流程是完整的,由于公司要求,自定义的包名稍有改动。再微信分类中有两个版本均可以上传成功