Java微信公眾號掃描帶引數二維碼事件(訊息加密)
阿新 • • 發佈:2019-01-02
最近又開始了微信二維碼的開發,開個帖子記錄一下
首先我們需要獲取access_token,有效期7200s,一天獲取上限為2000次
public static String getWXAccessToken() {
String accessTokenUrl = "https://api.weixin.qq.com/cgi-bin/token?" +
"grant_type=client_credential" +
// 此處填寫你自己的appid
"&appid=" + WXConstants.APPID +
// 此處填寫你自己的appsecret
"&secret=" + WXConstants.APPSECRET;
JSONObject jsonObject = HttpUtils.httpsRequest(accessTokenUrl, "GET", null);
return (String) jsonObject.get("access_token");
}
HttpUtils
/**
* 傳送https請求
* @param requestUrl 請求地址
* @param requestMethod 請求方式(GET、POST)
* @param data 提交的資料
* @return JSONObject(通過JSONObject.get(key)的方式獲取json物件的屬性值)
*/
public static JSONObject httpsRequest(String requestUrl, String requestMethod, String data) {
JSONObject jsonObject = null;
InputStream inputStream = null ;
InputStreamReader inputStreamReader = null;
BufferedReader bufferedReader = null;
try {
URL url = new URL(requestUrl);
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setUseCaches(false);
// 設定請求方式(GET/POST)
conn.setRequestMethod(requestMethod);
conn.connect();
// 當data不為null時向輸出流寫資料
if (null != data) {
// getOutputStream方法隱藏了connect()方法
OutputStream outputStream = conn.getOutputStream();
// 注意編碼格式
outputStream.write(data.getBytes("UTF-8"));
outputStream.close();
}
// 從輸入流讀取返回內容
inputStream = conn.getInputStream();
inputStreamReader = new InputStreamReader(inputStream, "utf-8");
bufferedReader = new BufferedReader(inputStreamReader);
String str = null;
StringBuffer buffer = new StringBuffer();
while ((str = bufferedReader.readLine()) != null) {
buffer.append(str);
}
conn.disconnect();
jsonObject = JSONObject.fromObject(buffer.toString());
return jsonObject;
} catch (Exception e) {
logger.error("傳送https請求失敗,失敗", e);
return null;
} finally {
// 釋放資源
try {
if(null != inputStream) {
inputStream.close();
}
if(null != inputStreamReader) {
inputStreamReader.close();
}
if(null != bufferedReader) {
bufferedReader.close();
}
} catch (IOException e) {
logger.error("釋放資源失敗,失敗", e);
}
}
}
重點內容
- 我們需要現在微信公眾平臺設定 IP白名單,也就是本機的外網IP地址,否則無法獲取access_token
- 微信提供了兩種二維碼
- 一種是臨時二維碼,沒有個數限制
- 一種是永久二維碼,有個數限制,最多為10w個
/**
* 獲取微信公眾號二維碼
* @param codeType 二維碼型別 "1": 臨時二維碼 "2": 永久二維碼
* @param sceneId 場景值ID
* @param fileName 圖片名稱
*/
public static void getWXPublicQRCode(String codeType, Integer sceneId, String fileName) {
String wxAccessToken = getWXAccessToken();
Map<String, Object> map = new HashMap<>();
if ("1".equals(codeType)) { // 臨時二維碼
map.put("expire_seconds", 604800);
map.put("action_name", "QR_SCENE");
Map<String, Object> sceneMap = new HashMap<>();
Map<String, Object> sceneIdMap = new HashMap<>();
sceneIdMap.put("scene_id", sceneId);
sceneMap.put("scene", sceneIdMap);
map.put("action_info", sceneMap);
} else if ("2".equals(codeType)) { // 永久二維碼
map.put("action_name", "QR_LIMIT_SCENE");
Map<String, Object> sceneMap = new HashMap<>();
Map<String, Object> sceneIdMap = new HashMap<>();
sceneIdMap.put("scene_id", sceneId);
sceneMap.put("scene", sceneIdMap);
map.put("action_info", sceneMap);
}
String data = JSON.toJSONString(map);
// 得到ticket票據,用於換取二維碼圖片
JSONObject jsonObject = HttpUtils.httpsRequest("https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=" + wxAccessToken, "POST", data);
String ticket = (String) jsonObject.get("ticket");
// WXConstants.QRCODE_SAVE_URL: 填寫存放圖片的路徑
HttpUtils.httpsRequestPicture("https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=" + URLEncoder.encode(ticket),
"GET", null, WXConstants.QRCODE_SAVE_URL, fileName, "png");
}
HttpUtils
/**
* 傳送https請求,返回二維碼圖片
* @param requestUrl 請求地址
* @param requestMethod 請求方式(GET、POST)
* @param data 提交的資料
* @param savePath 圖片儲存路徑
* @param fileName 圖片名稱
* @param fileType 圖片型別
*/
public static void httpsRequestPicture(String requestUrl, String requestMethod, String data, String savePath, String fileName, String fileType) {
InputStream inputStream = null;
try {
URL url = new URL(requestUrl);
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setUseCaches(false);
//設定請求方式(GET/POST)
conn.setRequestMethod(requestMethod);
conn.connect();
//當data不為null時向輸出流寫資料
if (null != data) {
//getOutputStream方法隱藏了connect()方法
OutputStream outputStream = conn.getOutputStream();
//注意編碼格式
outputStream.write(data.getBytes("UTF-8"));
outputStream.close();
}
// 從輸入流讀取返回內容
inputStream = conn.getInputStream();
logger.info("開始生成微信二維碼...");
WXPayUtils.inputStreamToMedia(inputStream, savePath, fileName, fileType);
logger.info("微信二維碼生成成功!!!");
conn.disconnect();
} catch (Exception e) {
logger.error("傳送https請求失敗,失敗", e);
}finally {
//釋放資源
try {
if(null != inputStream) {
inputStream.close();
}
} catch (IOException e) {
logger.error("釋放資源失敗,失敗", e);
}
}
}
WXPayUtils
/**
* 將輸入流轉換為圖片
* @param input 輸入流
* @param savePath 圖片需要儲存的路徑
* @param fileType 圖片型別
*/
public static void inputStreamToMedia(InputStream input, String savePath, String fileName, String fileType) throws Exception {
String filePath = savePath + "/" + fileName + "." + fileType;
File file = new File(filePath);
FileOutputStream outputStream = new FileOutputStream(file);
int length;
byte[] data = new byte[1024];
while ((length = input.read(data)) != -1) {
outputStream.write(data, 0, length);
}
outputStream.flush();
outputStream.close();
}
重點內容
- 上文中的 sceneId 即為我們可以攜帶的引數
- 臨時二維碼時為32位非0整型,永久二維碼時最大值為100000(目前引數只支援1–100000)
- 首先我們需要開啟伺服器配置,可以見我上一篇文章java微信公眾號伺服器配置-驗證Token,設定好之後我們就可以把驗證的程式碼註釋掉,然後開始編寫相關的業務邏輯
/**
* 處理微信公眾號請求資訊
* @param request
* @return
*/
@RequestMapping("/wxpublic/verify_wx_token")
@ResponseBody
public String handlePublicMsg(HttpServletRequest request) throws Exception {
// 獲得微信端返回的xml資料
InputStream is = null;
InputStreamReader isr = null;
BufferedReader br = null;
try {
is = request.getInputStream();
isr = new InputStreamReader(is, "utf-8");
br = new BufferedReader(isr);
String str = null;
StringBuffer returnXml= new StringBuffer();
while ((str = br.readLine()) != null) {
//返回的是xml資料
returnXml.append(str);
}
Map<String, String> encryptMap = WXPayUtils.xmlToMap(returnXml);
// 得到公眾號傳來的加密資訊並解密,得到的是明文xml資料
String decryptXml = WXPublicUtils.decrypt(encryptMap.get("Encrypt"));
// 將xml資料轉換為map
Map<String, String> decryptMap = WXPayUtils.xmlToMap(decryptXml);
// 區分訊息型別
String msgType = decryptMap.get("MsgType");
// 普通訊息
if ("text".equals(msgType)) { // 文字訊息
// todo 處理文字訊息
} else if ("image".equals(msgType)) { // 圖片訊息
// todo 處理圖片訊息
} else if ("voice".equals(msgType)) { //語音訊息
// todo 處理語音訊息
} else if ("video".equals(msgType)) { // 視訊訊息
// todo 處理視訊訊息
} else if ("shortvideo".equals(msgType)) { // 小視訊訊息
// todo 處理小視訊訊息
} else if ("location".equals(msgType)) { // 地理位置訊息
// todo 處理地理位置訊息
} else if ("link".equals(msgType)) { // 連結訊息
// todo 處理連結訊息
}
// 事件推送
else if ("event".equals(msgType)) { // 事件訊息
// 區分事件推送
String event = decryptMap.get("Event");
if ("subscribe".equals(event)) { // 訂閱事件 或 未關注掃描二維碼事件
// 返回訊息時ToUserName的值與FromUserName的互換
Map<String, String> returnMap = new HashMap<>();
returnMap.put("ToUserName", decryptMap.get("FromUserName"));
returnMap.put("FromUserName", decryptMap.get("ToUserName"));
returnMap.put("CreateTime", new Date().getTime()+"");
returnMap.put("MsgType", "text");
returnMap.put("Content", "https://www.baidu.com");
String encryptMsg = WXPublicUtils.encryptMsg(WXPayUtils.mapToXml(returnMap), new Date().getTime()+"", WXPublicUtils.getRandomStr());
return encryptMsg;
} else if ("unsubscribe".equals(event)) { // 取消訂閱事件
// todo 處理取消訂閱事件
} else if ("SCAN".equals(event)) { // 已關注掃描二維碼事件
// 返回訊息時ToUserName的值與FromUserName的互換
Map<String, String> returnMap = new HashMap<>();
returnMap.put("ToUserName", decryptMap.get("FromUserName"));
returnMap.put("FromUserName", decryptMap.get("ToUserName"));
returnMap.put("CreateTime", new Date().getTime()+"");
returnMap.put("MsgType", "text");
returnMap.put("Content", "https://www.baidu.com");
String encryptMsg = WXPublicUtils.encryptMsg(WXPayUtils.mapToXml(returnMap), new Date().getTime()+"", WXPublicUtils.getRandomStr());
return encryptMsg;
} else if ("LOCATION".equals(event)) { // 上報地理位置事件
// todo 處理上報地理位置事件
} else if ("CLICK".equals(event)) { // 點選選單拉取訊息時的事件推送事件
// todo 處理點選選單拉取訊息時的事件推送事件
} else if ("VIEW".equals(event)) { // 點選選單跳轉連結時的事件推送
// todo 處理點選選單跳轉連結時的事件推送
}
}
} catch (Exception e) {
logger.error("處理微信公眾號請求資訊,失敗", e);
} finally {
if (null != is) {
is.close();
}
if (null != isr) {
isr.close();
}
if (null != br) {
br.close();
}
}
return null;
}
WXPayUtils
/**
* 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<>();
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder = documentBuilderFactory.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) {
WXPayUtils.getLogger().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 {
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder= documentBuilderFactory.newDocumentBuilder();
org.w3c.dom.Document document = documentBuilder.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(); //.replaceAll("\n|\r", "");
try {
writer.close();
} catch (Exception ex) {
}
return output;
}
重點內容
- 由於我開啟了資訊加密,所以微信傳送給我的訊息是加密的,同理,我這邊返回給微信的訊息也需要加密,如何嫌麻煩的話,可以將 訊息加解密方式 設定為 明文模式
- 訊息加解密的方法有點多,我把它壓縮上傳了,有需要的小夥伴可以自行下載
- 通過 decryptMap.get(“EventKey”) 即可得到二維碼攜帶的引數
四、最終效果
至此,就大功告成了!!!