微信公眾號自動回復-底部菜單欄-關鍵字回復-回復2條消息(1文字,1圖片)
主要實現:
1.關注后自動回復文字內容
2.關鍵字回復圖文消息
3.實現公眾號自定義底部菜單欄 - 點擊菜單欄進入鏈接, 點擊菜單欄回復圖文消息, 點擊菜單欄回復2條消息(一條文字, 一條圖片)
主要參考: https://segmentfault.com/a/1190000015715950
1. 前提准備
需要可以接收微信發來的XML
可以參考上一篇博文:
https://www.cnblogs.com/Annago/p/15411077.html
2. 關注后自動回復文字
/**
* 接收微信的事件
*/
@PostMapping(value = "/weixinVerify", produces = "application/xml")
public void weixinVerify(HttpServletRequest request, HttpServletResponse response) throws Exception {
response.setContentType("text/html; charset=utf-8");
response.setContentType("application/xml; charset=utf-8");
PageData pageData = this.getPageData();
String method = request.getMethod();
if ("POST".equals(method)) {
String xmlStr = "";
String msgrsp = "";
PrintWriter out = response.getWriter();
try {
//解析微信發來的XML
xmlStr = XmlUtil.inputStream2StringNew(request.getInputStream());
Map<String, String> requestMap = XmlUtil.parseXml(xmlStr);
//消息類型 -- 事件(event)
if (MessageUtil.REQ_MESSAGE_TYPE_EVENT.equals(requestMap.get("msgType"))) {
//關注(subscribe)
if (MessageUtil.EVENT_TYPE_SUBSCRIBE.equals(requestMap.get("event"))) {
String openid = requestMap.get("fromUserName"); //用戶openid
String mpid = requestMap.get("toUserName"); //公眾號原始ID
//普通文本消息
BaseMessage txtmsg = new BaseMessage();
txtmsg.setToUserName(openid);
txtmsg.setFromUserName(mpid);
txtmsg.setCreateTime(new Date().getTime());
txtmsg.setMsgType(MessageUtil.RESP_MESSAGE_TYPE_TEXT);
txtmsg.setContent("您好,歡迎關注!");
//文本轉xml
String msgrsp = MessageUtil.textMessageToXml(txtmsg);
}
}
out.print(msgrsp);
out.close();
} catch (Exception e) {
logger.error("消息事件接收失敗");
e.printStackTrace();
}
}
}
需要的jar
<!-- hutool工具集 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.3.9</version>
</dependency>
<!-- 阿里JSON解析器 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.70</version>
</dependency>
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.9</version>
</dependency>
BaseMessage
@Data
public class BaseMessage {
//接收方賬號(收到的OpenID)
private String ToUserName;
//開發者微信號(公眾號)
private String FromUserName;
//消息創建時間(整形)
private long CreateTime;
//消息類型(text,image,location,link,voice)
private String MsgType;
//返回內容
private String Content;
//返回的圖片id
private String MediaId;
}
BaseButton
@Data
public class BasicButton {
private String name;
private String type;
}
ViewButton
/**
* @Description: 直接進入鏈接的按鈕
*/
@Data
public class ViewButton extends BasicButton {
private String url;
}
CommonButton
/**
* @Description: 點擊的按鈕
*/
@Data
public class CommonButton extends BasicButton {
private String key;
}
父按鈕
/**
* @Description: 父按鈕
*/
@Data
public class ComplexButton extends BasicButton {
private BasicButton[] sub_button;
}
菜單
/**
* @Description: 菜單
*/
@Data
public class Menu {
private BasicButton[] button;
}
MessageUtil
import java.io.*;
import java.net.URL;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.net.ssl.HttpsURLConnection;
import javax.servlet.http.HttpServletRequest;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.ruoyi.common.utils.weixin.*;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.core.util.QuickWriter;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
import com.thoughtworks.xstream.io.xml.PrettyPrintWriter;
import com.thoughtworks.xstream.io.xml.XppDriver;
public class MessageUtil {
/**
* 返回消息類型:文本
*/
public static final String RESP_MESSAGE_TYPE_TEXT = "text";
/**
* 返回消息類型:音樂
*/
public static final String RESP_MESSAGE_TYPE_MUSIC = "music";
/**
* 返回消息類型:圖文
*/
public static final String RESP_MESSAGE_TYPE_NEWS = "news";
/**
* 請求消息類型:文本
*/
public static final String REQ_MESSAGE_TYPE_TEXT = "text";
/**
* 請求消息類型:圖片
*/
public static final String REQ_MESSAGE_TYPE_IMAGE = "image";
/**
* 請求消息類型:鏈接
*/
public static final String REQ_MESSAGE_TYPE_LINK = "link";
/**
* 請求消息類型:地理位置
*/
public static final String REQ_MESSAGE_TYPE_LOCATION = "location";
/**
* 請求消息類型:音頻
*/
public static final String REQ_MESSAGE_TYPE_VOICE = "voice";
/**
* 請求消息類型:推送
*/
public static final String REQ_MESSAGE_TYPE_EVENT = "event";
/**
* 事件類型:subscribe(訂閱)
*/
public static final String EVENT_TYPE_SUBSCRIBE = "subscribe";
/**
* 事件類型:unsubscribe(取消訂閱)
*/
public static final String EVENT_TYPE_UNSUBSCRIBE = "unsubscribe";
/**
* 事件類型:CLICK(自定義菜單點擊事件)
*/
public static final String EVENT_TYPE_CLICK = "CLICK";
/**
* 事件類型:自己設置的KEY
*/
public static final String KEY = "設置什么KEY, 監聽到什么值";
// 菜單創建(POST) 限100(次/天)
public static String MENU_CREATE_RUL = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN";
/**
* 解析微信發來的請求(XML)
*
* @param request
* @return
* @throws Exception
*/
@SuppressWarnings("unchecked")
public static Map<String, String> parseXml(HttpServletRequest request) throws Exception {
// 將解析結果存儲在HashMap中
Map<String, String> map = new HashMap<String, String>();
// 從request中取得輸入流
InputStream inputStream = request.getInputStream();
// 讀取輸入流
SAXReader reader = new SAXReader();
Document document = reader.read(inputStream);
// 得到xml根元素
Element root = document.getRootElement();
// 得到根元素的所有子節點
List<Element> elementList = root.elements();
// 遍歷所有子節點
for (Element e : elementList)
map.put(e.getName(), e.getText());
// 釋放資源
inputStream.close();
inputStream = null;
return map;
}
/**
* 擴展xstream,使其支持CDATA塊
*
* @date 2016-01-15
*/
private static XStream xstream = new XStream(new XppDriver() {
public HierarchicalStreamWriter createWriter(Writer out) {
return new PrettyPrintWriter(out) {
// 對所有xml節點的轉換都增加CDATA標記
boolean cdata = true;
@SuppressWarnings("unchecked")
public void startNode(String name, Class clazz) {
super.startNode(name, clazz);
}
protected void writeText(QuickWriter writer, String text) {
if (cdata) {
writer.write("<![CDATA[");
writer.write(text);
writer.write("]]>");
} else {
writer.write(text);
}
}
};
}
});
/**
* 文本轉xml
*/
public static String textMessageToXml(BaseMessage baseMessage) {
xstream.alias("xml", baseMessage.getClass());
return xstream.toXML(baseMessage);
}
/**
* 圖文轉xml
*/
public static String newsMessageToXml(BaseMessage baseMessage) {
xstream.alias("xml", baseMessage.getClass());
xstream.alias("item", new Article().getClass());
return xstream.toXML(baseMessage);
}
/**
* 圖片轉xml
*/
public static String imageMessageToXml(ImageMessage imageMessage) {
xstream.alias("xml", imageMessage.getClass());
return xstream.toXML(imageMessage);
}
/**
* 發送https請求
*
* @param requestUrl 請求地址
* @param requestMethod 請求方式(GET、POST)
* @param outputStr 提交的數據
* @return JSONObject(通過JSONObject.get(key)的方式獲取json對象的屬性值)
*/
public static JSONObject httpsRequest(String requestUrl, String requestMethod, String outputStr) {
JSONObject jsonObject = 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);
// 當outputStr不為null時向輸出流寫數據
if (null != outputStr) {
OutputStream outputStream = conn.getOutputStream();
// 注意編碼格式
outputStream.write(outputStr.getBytes("UTF-8"));
outputStream.close();
}
// 從輸入流讀取返回內容
InputStream inputStream = conn.getInputStream();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String str = null;
StringBuffer buffer = new StringBuffer();
while ((str = bufferedReader.readLine()) != null) {
buffer.append(str);
}
// 釋放資源
bufferedReader.close();
inputStreamReader.close();
inputStream.close();
inputStream = null;
conn.disconnect();
jsonObject = JSONObject.parseObject(buffer.toString());
} catch (Exception e) {
e.printStackTrace();
}
return jsonObject;
}
/**
* 組裝菜單數據(父子菜單欄), 直接拉到底下看父菜單
* 1.如果菜單類型是:view, 點擊菜單則會進入到url中(進入鏈接)
* 2.類型是:click, 點擊, 微信會傳入XML, 里面有"eventKey"字段, 對應你設置的MessageUtil.KEY(監聽到"eventKey"= 你設置的key)
*/
public static Menu getMenu() {
//左邊的菜單欄------------------
ViewButton btn11 = new ViewButton();
btn11.setName("資訊月刊");
btn11.setType("view");
btn11.setUrl("https://blog.csdn.net/yuanpeij/article/details/120776759");
CommonButton btn12 = new CommonButton();
btn12.setName("行業動態");
btn12.setType("click");
btn12.setKey(MessageUtil.KEY);
CommonButton btn13 = new CommonButton();
btn13.setName("XX動態");
btn13.setType("click");
btn13.setKey(MessageUtil.KEY);
//中間的菜單欄-------------
ViewButton btn21 = new ViewButton();
btn21.setName("專業研究");
btn21.setType("view");
btn21.setUrl("https://blog.csdn.net/yuanpeij/article/details/120776759");
ViewButton btn22 = new ViewButton();
btn22.setName("XX觀點");
btn22.setType("view");
btn22.setUrl("https://blog.csdn.net/yuanpeij/article/details/120776759");
ViewButton btn23 = new ViewButton();
btn23.setName("主題演講");
btn23.setType("view");
btn23.setUrl("https://blog.csdn.net/yuanpeij/article/details/120776759");
//右邊的菜單欄-------------
CommonButton btn31 = new CommonButton();
btn31.setName("XX簡介");
btn31.setType("click");
btn31.setKey(MessageUtil.KEY);
CommonButton btn32 = new CommonButton();
btn32.setName("業務類型");
btn32.setType("click");
btn32.setKey(MessageUtil.KEY);
CommonButton btn33 = new CommonButton();
btn33.setName("招賢納士");
btn33.setType("click");
btn33.setKey(MessageUtil.KEY);
CommonButton btn34 = new CommonButton();
btn34.setName("XX路線");
btn34.setType("click");
btn34.setKey(MessageUtil.KEY);
CommonButton btn35 = new CommonButton();
btn35.setName("XXXX");
btn35.setType("click");
btn35.setKey(MessageUtil.KEY);
//三個主菜單欄, 底下包含各自的子菜單
ComplexButton mainBtn1 = new ComplexButton();
mainBtn1.setName("XX資訊");
mainBtn1.setSub_button(new BasicButton[] { btn11, btn12, btn13});
ComplexButton mainBtn2 = new ComplexButton();
mainBtn2.setName("專業分享");
mainBtn2.setSub_button(new BasicButton[] { btn21, btn22, btn23});
ComplexButton mainBtn3 = new ComplexButton();
mainBtn3.setName("關於XX");
mainBtn3.setSub_button(new BasicButton[] { btn31, btn32, btn33, btn34, btn35});
Menu menu = new Menu();
menu.setButton(new BasicButton[] { mainBtn1, mainBtn2, mainBtn3});
return menu;
}
/**
* 創建菜單
*
* @param menu 菜單實例
* @param accessToken 有效的access_token
* @return 0表示成功,其他值表示失敗
*/
public static int createMenu(Menu menu, String accessToken) {
int result = 0;
// 拼裝創建菜單的url
String url = MENU_CREATE_RUL.replace("ACCESS_TOKEN", accessToken);
// 將菜單對象轉換成json字符串
String jsonMenu = JSON.toJSON(menu).toString();
// 調用接口創建菜單
JSONObject jsonObject = httpsRequest(url, "POST", jsonMenu);
if (null != jsonObject) {
if (0 != jsonObject.getInteger("errcode")) {
result = jsonObject.getInteger("errcode");
System.out.println("創建菜單失敗 errcode:{} errmsg:{}" + jsonObject.getInteger("errcode") + jsonObject.getString("errmsg"));
}
}
return result;
}
}
3.自定義菜單欄
@Test
public void t() throws Exception {
JSONObject accessToken = GetReqUtil.getAccess_token();
MessageUtil.createMenu(MessageUtil.getMenu(), accessToken.getString("access_token"));
}
public static JSONObject getAccess_token() {
String url = "https://sz.api.weixin.qq.com/cgi-bin/token";
Map<String,Object> paramMap = new HashMap<>();
paramMap.put("appid", "微信公眾號的appid");
paramMap.put("secret", "微信公眾號的secret");
paramMap.put("grant_type", "client_credential");
String result2 = HttpRequest.get(url).charset(CharsetUtil.CHARSET_GBK)
.header(Header.USER_AGENT, "Hutool http")//頭信息,多個頭信息多次調用此方法即可
.form(paramMap)//表單內容
.timeout(20000)//超時,毫秒
.execute().body();
JSONObject jsonObject = JSONObject.parseObject(result2); //字符串轉Map
return jsonObject;
}
1.自定義菜單欄示例圖:
2.點擊自定義菜單欄回復圖文
//消息類型 -- 事件(event)
if (MessageUtil.REQ_MESSAGE_TYPE_EVENT.equals(requestMap.get("msgType"))) {
if (MessageUtil.KEY.equals(requestMap.get("eventKey"))) {
String openid = requestMap.get("fromUserName"); //用戶openid
String mpid = requestMap.get("toUserName"); //公眾號原始ID
//對圖文消息
NewsMessage newmsg = new NewsMessage();
newmsg.setToUserName(openid);
newmsg.setFromUserName(mpid);
newmsg.setCreateTime(new Date().getTime());
newmsg.setMsgType(MessageUtil.RESP_MESSAGE_TYPE_NEWS);
List<Article> articleList = new ArrayList<>();
Article article = new Article();
if (MessageUtil.INDUSTRY_TRENDS.equals(requestMap.get("eventKey"))) {
//行業動態
article.setTitle("標題");
article.setDescription("描述");
article.setPicUrl("圖片URL, 可以把圖片保存到微信公眾號里的素材庫, 然后使用圖片的路徑, 或者使用絕對路徑");
article.setUrl("鏈接URL");
}
articleList.add(article);
// 設置圖文消息個數
newmsg.setArticleCount(articleList.size());
// 設置圖文消息包含的圖文集合
newmsg.setArticles(articleList);
String msgrsp = MessageUtil.newsMessageToXml(newmsg);
out.print(msgrsp);
out.close();
}
}
NewMessage
public class NewsMessage extends BaseMessage {
/**
* 圖文消息個數,限制為10條以內
*/
private Integer ArticleCount;
/**
* 多條圖文消息信息,默認第一個item為大圖
*/
private List<Article> Articles;
public Integer getArticleCount() {
return ArticleCount;
}
public NewsMessage setArticleCount(Integer articleCount) {
ArticleCount = articleCount;
return this;
}
public List<Article> getArticles() {
return Articles;
}
public NewsMessage setArticles(List<Article> articles) {
Articles = articles;
return this;
}
Article
@Data
public class Article {
/**
* 圖文消息描述
*/
private String Description;
/**
* 圖片鏈接,支持JPG、PNG格式,<br>
* 較好的效果為大圖640*320,小圖80*80
*/
private String PicUrl;
/**
* 圖文消息名稱
*/
private String Title;
/**
* 點擊圖文消息跳轉鏈接
*/
private String Url;
}
回復圖文示例圖:
微信現在不支持默認回復大圖了, 如果需要大圖得展示2條數據-> 一條文字, 一張圖片
4.關鍵字回復
//消息類型 -- 文本(text)
if (MessageUtil.REQ_MESSAGE_TYPE_TEXT.equals(requestMap.get("msgType"))) {
//自定義文本
if ("資訊".equals(requestMap.get("content"))) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("XXXX年9月)" + "\n");
stringBuilder.append("鏈接:XXXXX" + "\n");
//可以讓文字變藍, 用戶點擊一下藍色,會自己打出提取碼, 然后容易拷貝
stringBuilder.append("提取碼:<a href=\"weixin://bizmsgmenu?msgmenuid=1&msgmenucontent=9tdy\">9tdy</a>" + "\n");
String openid = requestMap.get("fromUserName"); //用戶openid
String mpid = requestMap.get("toUserName"); //公眾號原始ID
//普通文本消息
BaseMessage txtmsg = new BaseMessage();
txtmsg.setToUserName(openid);
txtmsg.setFromUserName(mpid);
txtmsg.setCreateTime(new Date().getTime());
txtmsg.setMsgType(MessageUtil.RESP_MESSAGE_TYPE_TEXT);
txtmsg.setContent(stringBuilder.toString());
String msgrsp = MessageUtil.textMessageToXml(txtmsg);
out.print(msgrsp);
out.close();
}
}
示例圖:
5.回復2條消息(1文字, 1圖片)
示例圖:
微信限制我們每次只能給用戶被動回復一條消息, 所以回復2條只能使用客服推送這個功能
1.客服推送文字
public static void processAppletTextMessage(Map<String, String> requestMap) {
String openid = requestMap.get("fromUserName"); //用戶openid
JSONObject accessToken = GetReqUtil.getAccess_token();
JSONObject jsonObject = new JSONObject();
JSONObject jsonObject2 = new JSONObject();
jsonObject2.put("content", "請聯系工作人員進入小程序體驗");
jsonObject.put("touser", openid);
jsonObject.put("msgtype", "text");
jsonObject.put("text", jsonObject2);
HttpUtil.post("https://sz.api.weixin.qq.com/cgi-bin/message/custom/send?access_token=" + accessToken.getString("access_token"), jsonObject.toString());
}
2.回復圖片信息
public static String processAppletPictureMessage(Map<String, String> requestMap) {
String openid = requestMap.get("fromUserName"); //用戶openid
String mpid = requestMap.get("toUserName"); //公眾號原始ID
ImageMessage imageMsg = new ImageMessage();
Image image = new Image();
image.setMediaId("上傳圖片到微信服務器, 獲取MediaId, 下面有");
//普通文本消息
imageMsg.setToUserName(openid);
imageMsg.setFromUserName(mpid);
imageMsg.setCreateTime(new Date().getTime());
imageMsg.setMsgType(MessageUtil.REQ_MESSAGE_TYPE_IMAGE);
imageMsg.setImage(image);
return MessageUtil.imageMessageToXml(imageMsg);
}
ImageMessage
@Data
public class ImageMessage extends BaseMessage{
private Image Image;
}
把圖片上傳到微信素材庫(永久上傳)
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
@Test
public void t2() throws Exception {
JSONObject accessToken = GetReqUtil.getAccess_token();
System.out.println("accessToken = " + accessToken.getString("access_token"));
JSONObject jsonObject = GetReqUtil.addMaterialEver("C:\\Users\\Jaime\\Desktop\\工作人員.jpg", "image", accessToken.getString("access_token"));
boolean b=jsonObject.containsKey("media_id");
if (b==true) {
String media_id=jsonObject.getString("media_id");
System.out.println("jsonObject = " + jsonObject);
System.out.println("media_id:"+media_id);
}
}
addMaterialEver
/**
* 上傳其他永久素材(圖片素材的上限為5000,其他類型為1000)
*
* @return
* @throws Exception
*/
public static JSONObject addMaterialEver(String fileurl, String type, String token) {
try {
File file = new File(fileurl);
//上傳素材
String path = "https://api.weixin.qq.com/cgi-bin/material/add_material?access_token=" + token + "&type=" + type;
String result = connectHttpsByPost(path, null, file);
result = result.replaceAll("[\\\\]", "");
JSONObject resultJSON = JSONObject.parseObject(result);
if (resultJSON != null) {
if (resultJSON.get("media_id") != null) {
System.out.println("true");
return resultJSON;
} else {
System.out.println("false");
}
}
return null;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static String connectHttpsByPost(String path, String KK, File file) throws IOException, NoSuchAlgorithmException, NoSuchProviderException, KeyManagementException {
URL urlObj = new URL(path);
//連接
HttpURLConnection con = (HttpURLConnection) urlObj.openConnection();
String result = null;
con.setDoInput(true);
con.setDoOutput(true);
con.setUseCaches(false); // post方式不能使用緩存
// 設置請求頭信息
con.setRequestProperty("Connection", "Keep-Alive");
con.setRequestProperty("Charset", "UTF-8");
// 設置邊界
String BOUNDARY = "----------" + System.currentTimeMillis();
con.setRequestProperty("Content-Type",
"multipart/form-data; boundary="
+ BOUNDARY);
// 請求正文信息
// 第一部分:
StringBuilder sb = new StringBuilder();
sb.append("--"); // 必須多兩道線
sb.append(BOUNDARY);
sb.append("\r\n");
sb.append("Content-Disposition: form-data;name=\"media\";filelength=\"" + file.length() + "\";filename=\""
+ file.getName() + "\"\r\n");
sb.append("Content-Type:application/octet-stream\r\n\r\n");
byte[] head = sb.toString().getBytes("utf-8");
// 獲得輸出流
OutputStream out = new DataOutputStream(con.getOutputStream());
// 輸出表頭
out.write(head);
// 文件正文部分
// 把文件已流文件的方式 推入到url中
DataInputStream in = new DataInputStream(new FileInputStream(file));
int bytes = 0;
byte[] bufferOut = new byte[1024];
while ((bytes = in.read(bufferOut)) != -1) {
out.write(bufferOut, 0, bytes);
}
in.close();
// 結尾部分
byte[] foot = ("\r\n--" + BOUNDARY + "--\r\n").getBytes("utf-8");// 定義最后數據分隔線
out.write(foot);
out.flush();
out.close();
StringBuffer buffer = new StringBuffer();
BufferedReader reader = null;
try {
// 定義BufferedReader輸入流來讀取URL的響應
reader = new BufferedReader(new InputStreamReader(con.getInputStream()));
String line = null;
while ((line = reader.readLine()) != null) {
buffer.append(line);
}
if (result == null) {
result = buffer.toString();
}
} catch (IOException e) {
} finally {
if (reader != null) {
reader.close();
}
}
return result;
}