先好好把官方文档看一看,链接https://developers.weixin.qq.com/miniprogram/dev/framework/server-ability/message-push.html#option-url
主要用到的maven依赖:
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.2</version>
</dependency>
<dependency>
<groupId>net.sf.json-lib</groupId>
<artifactId>json-lib</artifactId>
<version>2.2.3</version>
<classifier>jdk15</classifier>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.2</version>
</dependency>
- Controller代码(把主要代码都写在这个类里了,读者可以自己做代码功能分离)
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.annotations.SerializedName;
import com.**.constant.WeChatConstants;
import com.**.util.HttpUtil;
import com.**.util.WeChatUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import net.sf.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @description: 要把controller的echo方法对应的url配置到消息推送的URL(服务器地址)配置信息中,我们采用的是json格式,明文模式
* @author: pilaf
* @create: 2019-07-29 11:47
*/
@Slf4j
@RestController
@RequestMapping("/wechat")
public class WeChatController {
/**
* 要设置disableHtmlEscaping,否则会在转换成json的时候自作多情地转义一些特殊字符,如"="
*/
private final Gson GSON = new GsonBuilder().disableHtmlEscaping().create();
/**
* 文本消息
*/
private final String MSG_TYPE_TEXT = "text";
/**
* 苹果安卓通用的app下载链接
*/
private final String APP_DOWNLOAD_URL = "做了脱敏处理啦。。。";
@Autowired
private WeChatUtil weChatUtil;
@Autowired
private HttpUtil httpUtil;
@RequestMapping(value = "/chat/echo", method = {RequestMethod.GET, RequestMethod.POST})
public String echo(HttpServletRequest request, HttpServletResponse response) {
log.info("echo method invoked");
boolean isGetRequest = request.getMethod().toLowerCase().equals("get");
if (isGetRequest) {
//用于给微信服务器调用,以开通消息推送功能
doGet(request, response);
} else {
//用于接收用户输入信息,其实也是给微信服务器调用的,它会将用户输入转发到这儿来
doPost(request, response);
}
return null;
}
/**
* 处理来自用户的信息
*
* @param request
* @param response
*/
private void doPost(HttpServletRequest request, HttpServletResponse response) {
try {
BufferedReader streamReader = new BufferedReader(new InputStreamReader(request.getInputStream(), "UTF-8"));
StringBuilder requestStr = new StringBuilder();
String line;
while ((line = streamReader.readLine()) != null) {
requestStr.append(line);
}
log.info("doPost receive user post message:{}", requestStr);
UserTextRequest userTextRequest = GSON.fromJson(requestStr.toString(), UserTextRequest.class);
if (MSG_TYPE_TEXT.equals(userTextRequest.getMsgType())) {
// 用户输入1,返回app下载链接
if ("1".equals(userTextRequest.getContent())) {
log.info("用户输入1");
UserTextResponse userTextResponse = new UserTextResponse();
userTextResponse.setToUser(userTextRequest.getFromUserName());
userTextResponse.setMsgType(MSG_TYPE_TEXT);
Map<String, String> textContent = new HashMap<>();
textContent.put("content", APP_DOWNLOAD_URL);
userTextResponse.setText(textContent);
String accessToken = weChatUtil.getAccessToken();
String url = MessageFormat.format(WeChatConstants.CUSTOM_SEND_URL, accessToken);
JSONObject result = httpUtil.sendJsonPost(url, GSON.toJson(userTextResponse));
log.info("result:{}", result);
if (result.getInt("errcode") == 0) {
ResponseWriterTemplate.execute(response, (writer) -> {
writer.print("success");
writer.flush();
});
}
}
}
} catch (IOException e) {
log.error("doPost failed", e);
}
}
/**
* 处理来自微信服务端的消息
*
* @param request
* @param response
*/
private void doGet(HttpServletRequest request, HttpServletResponse response) {
log.info("doGet receive WeChat server request parameters:{}", request.getParameterMap());
String signature = request.getParameter("signature");
String timestamp = request.getParameter("timestamp");
String nonce = request.getParameter("nonce");
String echoStr = request.getParameter("echostr");
//必须与在小程序后台->开发->开发配置->消息推送中填写的token一致,
String token = "mytoken996";
String[] arr = new String[] {token, timestamp, nonce};
String result = weChatUtil.encodeUsingSHA1(arr);
ResponseWriterTemplate.execute(response, (writer) -> {
if (result.equalsIgnoreCase(signature)) {
writer.print(echoStr);
writer.flush();
log.info("echo {} back to WeChat Server", echoStr);
} else {
writer.print("");
}
});
}
/**
* 对PrintWriter输出内容做一个模版类,避免每次调用都要写一边try catch finally
*/
private static class ResponseWriterTemplate {
public static void execute(HttpServletResponse response, PrintCallback callback) {
PrintWriter writer = null;
try {
writer = response.getWriter();
log.info("ResponseWriterTemplate doPrint");
callback.doPrint(writer);
} catch (IOException e) {
log.error("getWriter failed", e);
throw new RuntimeException("getWriter failed");
} finally {
if (writer != null) {
writer.close();
}
}
}
}
private interface PrintCallback {
void doPrint(PrintWriter writer);
}
/**
* 用户通过小程序发送的消息经过微信服务器转发到后端之后的结构
* 微信发送的json字段名字都是大写字母开头
*/
@Data
private static class UserTextRequest {
/**
* 小程序的原始id
*/
@SerializedName("ToUserName")
private String toUserName;
/**
* 用户openid
*/
@SerializedName("FromUserName")
private String fromUserName;
/**
* 事件创建时间
*/
@SerializedName("CreateTime")
private Long createTime;
@SerializedName("MsgType")
private String msgType;
/**
* 用户发送的文本字段
*/
@SerializedName("Content")
private String content;
@SerializedName("MsgId")
private String msgId;
}
/**
* 响应用户的文本信息
*/
@Data
private static class UserTextResponse {
@SerializedName("touser")
private String toUser;
@SerializedName("msgtype")
private String msgType;
@SerializedName("text")
private Map<String, String> text;
}
}
- WeChatUtil工具类
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;
import com.thoughtworks.xstream.io.xml.XmlFriendlyNameCoder;
import com.**.WeChatProperties;
import com.**.WeChatConstants;
import lombok.extern.slf4j.Slf4j;
import net.sf.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.text.MessageFormat;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
/**
* @description: 微信工具类
* @author: pilaf
* @create: 2019-04-22 11:05
*/
@Slf4j
@Component
public class WeChatUtil {
private static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
private final String ACCESS_TOKEN_KEY = "wechat:access_token";
/**
* 日期格式化格式
*/
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
private static final Random RANDOM = new SecureRandom();
/**
* 要设置disableHtmlEscaping,否则会在转换成json的时候自作多情地转义一些特殊字符,如"="
*/
private final Gson GSON = new GsonBuilder().disableHtmlEscaping().create();
@Autowired
private WeChatProperties weChatProperties;
@Autowired
private HttpUtil httpUtil;
@Autowired
private RestTemplate restTemplate;
@Autowired
private StringRedisTemplate stringRedisTemplate;
/**
* XML格式字符串转换为Map
*
* @param strXML XML字符串
* @return XML数据转换后的Map
* @throws Exception
*/
public static Map<String, String> xmlToMap(String strXML) throws Exception {
try {
Map<String, String> data = new HashMap<String, String>();
DocumentBuilder documentBuilder = newDocumentBuilder();
InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
org.w3c.dom.Document doc = documentBuilder.parse(stream);
doc.getDocumentElement().normalize();
NodeList nodeList = doc.getDocumentElement().getChildNodes();
for (int idx = 0; idx < nodeList.getLength(); ++idx) {
Node node = nodeList.item(idx);
if (node.getNodeType() == Node.ELEMENT_NODE) {
org.w3c.dom.Element element = (org.w3c.dom.Element) node;
data.put(element.getNodeName(), element.getTextContent());
}
}
try {
stream.close();
} catch (Exception ex) {
// do nothing
}
return data;
} catch (Exception ex) {
log.warn("Invalid XML, can not convert to map. Error message: {}. XML content: {}", ex.getMessage(), strXML);
throw ex;
}
}
/**
* 将Map转换为XML格式的字符串
*
* @param data Map类型数据
* @return XML格式的字符串
* @throws Exception
*/
public static String mapToXml(Map<String, String> data) throws Exception {
org.w3c.dom.Document document = newDocument();
org.w3c.dom.Element root = document.createElement("xml");
document.appendChild(root);
for (String key : data.keySet()) {
String value = data.get(key);
if (value == null) {
value = "";
}
value = value.trim();
org.w3c.dom.Element filed = document.createElement(key);
filed.appendChild(document.createTextNode(value));
root.appendChild(filed);
}
TransformerFactory tf = TransformerFactory.newInstance();
Transformer transformer = tf.newTransformer();
DOMSource source = new DOMSource(document);
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
StringWriter writer = new StringWriter();
StreamResult result = new StreamResult(writer);
transformer.transform(source, result);
String output = writer.getBuffer().toString();
try {
writer.close();
} catch (Exception ex) {
log.error("mapToXml failed", ex);
}
return output;
}
/**
* 将对象转换成xml,对象属性为xml节点,xml根结点是"xml"
*
* @param obj
* @return
*/
public static String objToXml(Object obj) {
if (obj == null) {
throw new IllegalArgumentException("objToXml obj is null");
}
//要把两个下划线换成一个
XStream xStream = new XStream(new DomDriver(StandardCharsets.UTF_8.name(), new XmlFriendlyNameCoder("-_", "_")));
xStream.alias("xml", obj.getClass());
String xml = xStream.toXML(obj);
return xml;
}
public static <T> T xmlToObject(String xml, Class<T> clazz) {
if (StringUtils.isEmpty(xml)) {
throw new IllegalArgumentException("xmlToObject xml is empty");
}
XStream xStream = new XStream();
xStream.alias("xml", clazz);
T result = (T) xStream.fromXML(xml);
return result;
}
/**
* 生成签名
*
* @param data 待签名数据
* @param key API密钥
* @return 签名
*/
public static String generateSignature(final Map<String, String> data, String key) throws Exception {
return generateSignature(data, key, WeChatConstants.MD5);
}
/**
* 生成签名. 注意,若含有sign_type字段,必须和signType参数保持一致。
*
* @param data 待签名数据
* @param key API密钥
* @param signType 签名方式
* @return 签名
*/
public static String generateSignature(final Map<String, String> data, String key, String signType) {
Set<String> keySet = data.keySet();
String[] keyArray = keySet.toArray(new String[keySet.size()]);
Arrays.sort(keyArray);
StringBuilder sb = new StringBuilder();
for (String k : keyArray) {
if (k.equals(WeChatConstants.FIELD_SIGN)) {
continue;
}
// 参数值为空,则不参与签名
if (data.get(k).trim().length() > 0) {
sb.append(k).append("=").append(data.get(k).trim()).append("&");
}
}
sb.append("key=").append(key);
return generateBySignType(sb.toString(), key, signType);
}
/**
* 生成签名
*
* @param obj 要参与签名的数据对象
* @return
* @throws IllegalAccessException
*/
public static String generateSignature(Object obj, String key, String signType) {
ArrayList<String> list = new ArrayList<>();
Class clazz = obj.getClass();
Field[] fields = clazz.getDeclaredFields();
try {
for (Field field : fields) {
field.setAccessible(true);
//只有非空字段才参与签名计算
if (field.get(obj) != null && field.get(obj) != "") {
list.add(field.getName() + "=" + field.get(obj));
}
}
} catch (IllegalAccessException e) {
log.error("生成签名失败", e);
throw ServerException.create("生成签名失败");
}
int size = list.size();
String[] arrayToSort = list.toArray(new String[size]);
//对参与签名计算的参数进行升序排序
Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < size; i++) {
sb.append(arrayToSort[i]);
//非最后一个参数项要加&符号
if (i != size - 1) {
sb.append("&");
}
}
String result = sb.toString();
result += "&key=" + key;
return generateBySignType(result, key, signType);
}
/**
* 获取随机字符串 Nonce Str
*
* @return String 随机字符串
*/
public static 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);
}
private static String generateBySignType(String str, String key, String signType) {
if (WeChatConstants.MD5.equals(signType)) {
return MD5(str).toUpperCase();
} else if (WeChatConstants.HMACSHA256.equals(signType)) {
return HMACSHA256(str, key);
} else {
throw ServerException.create(String.format("Invalid sign_type: %s", signType));
}
}
/**
* 生成 MD5
*
* @param data 待处理数据
* @return MD5结果
*/
public static String MD5(String data) {
byte[] array = new byte[0];
try {
MessageDigest md = MessageDigest.getInstance("MD5");
array = md.digest(data.getBytes("UTF-8"));
} catch (Exception e) {
log.error("MD5 Exception", e);
throw ServerException.create("MD5加密失败");
}
StringBuilder sb = new StringBuilder();
for (byte item : array) {
sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
}
return sb.toString().toUpperCase();
}
/**
* 生成 HMACSHA256
*
* @param data 待处理数据
* @param key 密钥
* @return 加密结果
* @throws Exception
*/
public static String HMACSHA256(String data, String key) {
byte[] array = new byte[0];
try {
Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
sha256_HMAC.init(secret_key);
array = sha256_HMAC.doFinal(data.getBytes("UTF-8"));
} catch (Exception e) {
log.error("HMACSHA256 Exception", e);
throw ServerException.create("HMACSHA256加密失败");
}
StringBuilder sb = new StringBuilder();
for (byte item : array) {
sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
}
return sb.toString().toUpperCase();
}
/**
* 获取当前时间戳,单位秒
*
* @return
*/
public static long getCurrentTimestamp() {
return System.currentTimeMillis() / 1000;
}
/**
* 获取当前时间戳,单位毫秒
*
* @return
*/
public static long getCurrentTimestampMs() {
return System.currentTimeMillis();
}
/**
* 获取交易单号前面是日期后面是随机字符串
*
* @return
*/
public static String generateTradeNo() {
return getRandomStringByLength(32);
}
/**
* 获取一定长度的随机字符串
*
* @param length 指定字符串长度
* @return 一定长度的字符串
*/
private static String getRandomStringByLength(int length) {
StringBuffer orderSNBuffer = new StringBuffer();
//14是上面日期格式化的14位长度
orderSNBuffer.append(getRandomString(length - 14));
String result = LocalDateTime.now().format(FORMATTER) + orderSNBuffer.toString();
return result;
}
private static String getRandomString(int len) {
StringBuilder sb = new StringBuilder(len);
int length = SYMBOLS.length();
for (int i = 0; i < length; i++) {
Random random = new Random();
sb.append(SYMBOLS.charAt(random.nextInt(length)));
}
//14+18=32
return sb.toString().substring(0, 18);
}
/**
* 获取小程序access_token
*
* @return
*/
public String getAccessToken() {
//先从redis缓存中取,如果取不到再去微信服务端获取
String accessToken = stringRedisTemplate.opsForValue().get(ACCESS_TOKEN_KEY);
if (!StringUtils.isEmpty(accessToken)) {
return accessToken;
}
log.info("access token 过期了,将重新获取");
String url = MessageFormat.format(WeChatConstants.TOKEN_URL, weChatProperties.getAppId(), weChatProperties.getSecret());
JSONObject jsonObject = httpUtil.sendGet(url);
String token = jsonObject.getString(WeChatConstants.ACCESS_TOKEN);
Integer expireSeconds = jsonObject.getInt("expires_in");
if (StringUtils.isEmpty(token)) {
log.error("未获取到access token:{}", jsonObject);
throw ServerException.create("cannot get access token.");
}
log.info("获取到access token:{}", token);
stringRedisTemplate.opsForValue().set(ACCESS_TOKEN_KEY, token, Duration.ofSeconds(expireSeconds));
return token;
}
/**
* 如果通过别的途径获取了access_token,将导致redis上的access_token invalid,此时就要把invalid的token删掉
*/
private void deleteInvalidAccessToken() {
stringRedisTemplate.opsForValue().getOperations().delete(ACCESS_TOKEN_KEY);
}
/**
* 使用SHA1算法对字符串数组进行加密
*
* @param strList
* @return
*/
public String encodeUsingSHA1(String... strList) {
//将strList的值进行字典排序
Arrays.sort(strList);
StringBuilder content = new StringBuilder();
for (int i = 0; i < strList.length; i++) {
content.append(strList[i]);
}
String tmpStr = doEncodeUsingSHA1(content.toString());
return tmpStr;
}
/**
* SHA1实现
*
* @return sha1加密后的字符串
*/
private static String doEncodeUsingSHA1(String inStr) {
byte[] byteArray ;
try {
MessageDigest sha = MessageDigest.getInstance("SHA-1");
byteArray = sha.digest(inStr.getBytes("UTF-8"));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("no sha-1 algorithm");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("unsupported utf-8 encoding");
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < byteArray.length; i++) {
sb.append(Integer.toString((byteArray[i] & 0xff) + 0x100, 16).substring(1));
}
return sb.toString();
}
}
- HttpUtil工具类
import com.**.config.WeChatProperties;
import com.**.constant.WeChatConstants;
import lombok.extern.slf4j.Slf4j;
import net.sf.json.JSONObject;
import org.apache.http.HttpEntity;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.ResponseHandler;
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.MultipartEntityBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
/**
* @description:
* @author: pilaf
* @create: 2019-04-23 15:10
*/
@Slf4j
@Component
public class HttpUtil {
@Autowired
private CertUtil certUtil;
@Autowired
private WeChatProperties weChatProperties;
/**
* Json类型响应处理器
*/
private ResponseHandler<JSONObject> jsonResponseHandler = response -> {
int statusCode = response.getStatusLine().getStatusCode();
//正常响应
if (statusCode >= 200 && statusCode < 300) {
HttpEntity entity = response.getEntity();
if (entity != null) {
return getJsonObject(entity);
} else {
return null;
}
} else {
throw new ClientProtocolException("Unexpected response status:" + statusCode);
}
};
/**
* String类型响应处理器
*/
private ResponseHandler<String> stringResponseHandler = response -> {
int statusCode = response.getStatusLine().getStatusCode();
//正常响应
if (statusCode >= 200 && statusCode < 300) {
HttpEntity entity = response.getEntity();
if (entity != null) {
return EntityUtils.toString(entity, WeChatConstants.UTF8);
} else {
return null;
}
} else {
throw new ClientProtocolException("Unexpected response status:" + statusCode);
}
};
/**
* 发送get请求
*
* @param url 请求url
* @return 响应JSON
*/
public JSONObject sendGet(String url) {
//创建连接
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
//创建HttpGet实例
HttpGet httpGet = new HttpGet(url);
JSONObject responseBody = httpClient.execute(httpGet, jsonResponseHandler);
return responseBody;
} catch (IOException e) {
log.error("sendGet failed, url:{}", url, e);
return null;
}
}
/**
* 发送get请求
*
* @param url 请求url
* @param responseClazz 响应体对应的类
* @param <T>
* @return
*/
public <T> T sendGet(String url, Class<T> responseClazz) {
//创建连接
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
//创建HttpGet实例
HttpGet httpGet = new HttpGet(url);
JSONObject responseBody = httpClient.execute(httpGet, jsonResponseHandler);
T resultObj = (T) JSONObject.toBean(responseBody, responseClazz);
return resultObj;
} catch (IOException e) {
log.error("sendGet failed, url:{},responseClazz:{}", url, responseClazz, e);
return null;
}
}
/**
* 发送http post请求,请求体是json
*
* @param url 请求url
* @param obj 请求体对象
* @return
*/
public JSONObject sendJsonPost(String url, Object obj) {
String jsonStr = JSONObject.fromObject(obj).toString();
return sendJsonPost(url, jsonStr);
}
/**
* 发送http post请求,请求体是json
*
* @param url 请求url
* @param jsonContext 请求体json
* @return
*/
public JSONObject sendJsonPost(String url, String jsonContext) {
log.info("sendJsonPost,url:{},json:{}", url, jsonContext);
HttpPost httpPost = new HttpPost(url);
StringEntity stringEntity = new StringEntity(jsonContext, ContentType.create("text/plain", WeChatConstants.UTF8));
//设置需要传递的数据
httpPost.setEntity(stringEntity);
//创建一个HttpClient连接
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
JSONObject responseBody = httpClient.execute(httpPost, jsonResponseHandler);
return responseBody;
} catch (IOException e) {
log.error("sendJsonPost failed", e);
return null;
}
}
private JSONObject getJsonObject(HttpEntity entity) throws IOException {
String result = EntityUtils.toString(entity);
//根据字符串生成JSONObject对象
JSONObject resultObj = JSONObject.fromObject(result);
return resultObj;
}
/**
* 发送post请求,请求内容是xml
*
* @param url 请求url
* @param xmlStr 请求的xml字符串
* @param isLoadCert 是否使用证书(企业付款到个人需要使用证书)
* @return 响应字符串
* @throws Exception
*/
public String sendXmlPost(String url, String xmlStr, boolean isLoadCert) {
HttpPost httpPost = new HttpPost(url);
// 得指明使用UTF-8编码,否则到API服务器XML的中文不能被成功识别
StringEntity stringEntity = new StringEntity(xmlStr, ContentType.create("text/xml", WeChatConstants.UTF8));
httpPost.setEntity(stringEntity);
if (isLoadCert) {
//加载含有证书的http请求,创建一个HttpClient连接
try (CloseableHttpClient httpClient = HttpClients.custom().setSSLSocketFactory(certUtil.initCert()).build()) {
String responseBody = httpClient.execute(httpPost, stringResponseHandler);
return responseBody;
} catch (IOException e) {
log.error("sendXmlPost with cert file failed", e);
return null;
}
} else {
try (CloseableHttpClient httpClient = HttpClients.custom().build()) {
String responseBody = httpClient.execute(httpPost, stringResponseHandler);
return responseBody;
} catch (IOException e) {
log.error("sendXmlPost without cert file failed", e);
return null;
}
}
}
}
- WechatConstants常量类
/**
* @description: 微信相关常量类
* @author: pilaf
* @create: 2019-04-22 10:06
*/
public interface WeChatConstants {
/**
* 企业付款的微信url
*/
String COMPANY_PAY_URL = "https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers";
/**
* 获取企业付款结果的url
*/
String GET_TRANSFER_INFO_URL = "https://api.mch.weixin.qq.com/mmpaymkttransfers/gettransferinfo";
/**
* 小程序登录校验
*/
String WX_MINI_LOGIN = "https://api.weixin.qq.com/sns/jscode2session?appid={0}&secret={1}&js_code={2}&grant_type=authorization_code";
/**
* 获取小程序access_token的url
*/
String TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={0}&secret={1}";
/**
* 用于向微信服务端申请二维码的url
*/
String URL_QR_CODE = "https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token={0}";
/**
* 用于聊天时向用户发送消息的url
*/
String CUSTOM_SEND_URL = "https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token={0}";
/**
* 企业付款到个人时,不校验真实姓名
*/
String NO_CHECK = "NO_CHECK";
/**
* 企业付款到个人时,强校验真实姓名
*/
String FORCE_CHECK = "FORCE_CHECK";
/**
* 成功
*/
String SUCCESS = "SUCCESS";
/**
* 微信系统错误
*/
String SYSTEMERROR = "SYSTEMERROR";
/**
* 失败 (注意:微信有的接口返回的失败用FAIL字符串表示,有的接口用FAILED表示)
*/
String FAIL = "FAIL";
/**
* 微信企业付款到个人失败 (注意:微信有的接口返回的失败用FAIL字符串表示,有的接口用FAILED表示)
*/
String FAILED = "FAILED";
/**
* 微信企业付款到个人正在处理中
*/
String PROCESSING = "PROCESSING";
String HMACSHA256 = "HMAC-SHA256";
String MD5 = "MD5";
String UTF8 = "UTF-8";
String FIELD_SIGN = "sign";
String FIELD_SIGN_TYPE = "sign_type";
String RESULT_CODE = "result_code";
/**
* 通信标识,非交易标识,交易是否成功需要查看result_code来判断
*/
String RETURN_CODE = "return_code";
String OPENID = "openid";
String STATUS = "status";
String REASON = "reason";
String ACCESS_TOKEN = "access_token";
}
- WeChatProperties配置类(用于映射application.yml中的配置)
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* @description: 微信相关的配置信息
* @author: pilaf
* @create: 2019-04-22 19:49
*/
@Data
@Component
@ConfigurationProperties(prefix = "wxconfig")
public class WeChatProperties {
/**
* 微信小程序appid
*/
private String appId;
/**
* 微信支付商户号
*/
private String mchId;
/**
* api密钥,交易过程中生成签名的密钥,仅保留在商户系统和微信支付后台,不会在网络中传播
*/
private String key;
/**
* Appsecret,是appid对应的接口密码,用于获取接口调用凭证access_token时使用
*/
private String secret;
/**
* API证书路径
*/
private String certPath
}
- application.yml配置文件
wxconfig:
#微信小程序appid
appId: 你的小程序appid
#微信支付商户号,如果没有企业付款的功能可以不用加这个配置
mchId: 微信支付商户号
#api密钥,交易过程中生成签名的密钥,仅保留在商户系统和微信支付后台,不会在网络中传播
key: 你的api密钥
#Appsecret,是appid对应的接口密码,用于获取接口调用凭证access_token时使用。
secret: 你的appsecret
#api证书路径,放在java/main/resources目录下打到jar包中,或者放到服务器指定路径
certPath: cert/apiclient_cert.p12
参考文章:
1.https://developers.weixin.qq.com/miniprogram/dev/framework/server-ability/message-push.html#option-url 2. 3.https://developers.weixin.qq.com/community/develop/article/doc/00066a67324e70bdf0981381b5c813