# 微信服務接入教程文檔
[TOC]
**前言**
本人不喜歡網絡上的各種文章,上來就把完整代碼放臉上,讓你琢磨不懂,這篇文章會盡量讓你不要出現這種情況。
# 微信開發技術文檔官網
[https://mp.weixin.qq.com/](https://mp.weixin.qq.com/)
# 注冊成為微信開發者
* 普通用戶建議注冊訂閱號
* 打開基本配置菜單,成為開發者
## 配置接受推送消息服務器

* URL必須有域名,且必須在外網可以訪問到,因此我們需要**natapp**來進行**內網穿透**,簡單點說就是把外網的IP映射到你當前的內網下,外網ip接受的內容會被轉到你映射的內網ip:端口下。
* Token 隨便填
* key隨便填
* 建議選擇明文模式
* **此時提交之后由于后端沒有對應的服務,所有無法成功,待下文可以開始提交的時候建議大家再進行提交**
# natapp下載,使用
官網:[https://natapp.cn](https://natapp.cn)

1. 點擊客戶端下載即可,先不用急著點教程/文檔,里面并沒有我們當前想要的

2. 選擇系統對應的版本,這里使用的是win64,之后點擊箭頭指向的地方進行快速的安裝使用教程!
3. 安裝教程里面第一步是需要注冊
1. 
*購買隧道是有免費的和付費的,但是免費的需要進行支付寶實名認證,乍一看似乎能白嫖,但是我本人使用的時候,一到支付寶登陸的時候就顯示我支付寶賬號有危險,要我改密碼,死活上不去,無奈只好購買付費的。*
*我也對客服進行了郵箱反饋,但是只是建議我進行付費的購買,并沒有說排查錯誤什么的!*
2. 
隧道協議選擇Web即可
二級域名如果沒有先選擇不需要,如果你有自然更好。
本地端口填寫一個即可,本人這里選擇的80
帶寬&流量選擇**小流量即可**
購買完之后進行注冊域名
4. 
選擇一個可用于微信開發的即可
之后再回到我的隧道里面進行綁定域名即可
# 后臺構建
**構建之前,請大家大致的看微信開發平臺文檔一遍,比避免有些地方不懂**(強烈告誡至少看一遍)

**本教程只需要大家看以上四個大章即可**
**本教程以大家都有一個springboot的基本啟動程序,為前提(能啟動,controller能返回個hello,word即可)**
先給出本教程需要的全部**maven**包 ---jdk 1.8
開發工具 **idea**
```
<!--devtools熱部署-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
<scope>true</scope>
</dependency>
<!-- xml -->
<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>2.1.1</version>
</dependency>
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.11.1</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.4</version>
</dependency>
<!-- fastJSON -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web-services</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-core</artifactId>
</dependency>
```
## 后臺配置微信推送消息服務器
```
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import java.io.IOException;
import com.alibaba.fastjson.JSON;
//引用的其他自建類包,這里就不列出了
@Slf4j
@RestController
@RequestMapping("/springsecurity/test")
public class HelloController {
@GetMapping("/weixin")
public String weixin(HttpServletRequest request, HttpServletResponse response){
return null;
}
}
```
先定義這樣的一個類,加上一個接受微信推送消息服務器接口匹配的方法(暫時不需要定義其他類)

* 按要求填寫這些輸入框
* 建議大家先點擊提交,查看是否已經進入此方法
完整代碼:
```
@GetMapping("/weixin")
public String weixin(HttpServletRequest request, HttpServletResponse response){
String echostr = null;
//token驗證代碼段
try{
log.info("請求已到達,開始校驗token");
//這里對應的文檔里面的幾個參數,如果不清楚,請查看文檔
if (StringUtils.isNotBlank(request.getParameter("signature"))) {
String signature = request.getParameter("signature");
String timestamp = request.getParameter("timestamp");
String nonce = request.getParameter("nonce");
echostr = request.getParameter("echostr");
log.info("signature[{}], timestamp[{}], nonce[{}], echostr[{}]", signature, timestamp, nonce, echostr);
if (SignUtil.checkSignature(signature, timestamp, nonce)) {
log.info("數據源為微信后臺,將echostr[{}]返回!", echostr);
response.getOutputStream().println(echostr);
return echostr;
}
}
}catch (IOException e){
log.error("校驗出錯");
e.printStackTrace();
}
return echostr;
}
```
## 接受消息推送
接受信息推送這里需要進行新加類:
1. 新建**utils** package(包)
2. 在utils包下新建**messagehandle**包
3. 在messagehandle包下,新建**ParseXml**類內容為:
```
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
//引用的其他自建類包,這里就不列出了
//此部分代碼借鑒自網絡
@Slf4j
public class ParseXml {
/**
* @author: wwy
* @description: 解析微信發來的請求(XML)
* @date 2021/1/20
* @param request
* @return java.util.Map<java.lang.String,java.lang.String>
** java.util.Map<java.lang.String,java.lang.String>
**/
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());
log.info("name:" + e.getName() + " value:"+map.get(e.getName()));
}
// 釋放資源
inputStream.close();
return map;
}
}
```
4. 然后在上文的**HelloController**中新建方法如下:
```
@PostMapping("/weixin")
public void message(HttpServletRequest request, HttpServletResponse response){
try {
Map<String, String> paramMap = ParseXml.parseXml(request);
log.info(JSON.toJSONString(paramMap));
//這里把request參數傳入parsexml方法進行xml到map對象的轉換。
//使用map接受返回值即可
//建議大家先發給公眾號消息,查看查看這玩意長什么樣子!
//當然我們parseXml里面已經打印出來了
} catch (Exception e) {
e.printStackTrace();
}
}
```

附圖供大家看!
根據官方文檔可知:

基本每一個消息都會有一個MsgType來確定是什么類型的,所以我們這里要獲取它:
```
Map<String, String> paramMap = ParseXml.parseXml(request);
String type = paramMap.get("MsgType");
```

看**被動回復用戶消息**章節文檔,我們最后是需要返回xml形式的給微信服務器。
所以我們的大體步驟是:
1. **獲取微信服務器的請求,解析xml為對象**
2. **操作對象,獲取請求類型(比如文本、圖片等)進行相應的處理,進行數據的變更**
3. **把對象變為xml形式返回**
## *操作對象,獲取請求類型(比如文本、圖片等)進行相應的處理,進行數據的變更處理*
**操作對象**
* 首先每個返回的事件類型有一個自己的返回格式,但是都有幾個共同的字段,所以我們可以先定一個實體基類,然后其他的各種格式來繼承它,獲取共同的屬性(**這里的思路出自網絡代碼,但是網絡代碼完整性,實用性比較低,擴展性也不行所以我進行了大幅的修改**)。
* 實體基類**BaseMessage**代碼如下(請自行新建包來進行放置,建議不要和utils放置在一起):
```
import lombok.Data;
@Data
public class BaseMessage {
/**
* 開發者微信號
*/
private String ToUserName;
/**
* 發送方帳號(一個OpenID)
*/
private String FromUserName;
/**
* 消息創建時間 (整型)
*/
private Long CreateTime;
/**
* 消息類型(鏈接-link /地理位置-location /小視頻-shortvideo/視頻-video /語音-voice /圖片-image /文本-text)
*/
private String MsgType;
/**
* 消息id,64位整型
*/
private Long MsgId;
}
```
* 文字消息實體類**TextMessage**:
```
@Data
public class TextMessage extends BaseMessage{
/**
* 消息內容
*/
private String Content;
//由于打印本類toString時只會打印本類有的屬性,不會打印父類的,所以我們需要重寫類的toString,加上本類屬性和父類屬性
public String toString(){
return super.toString() + "[TextMessage]:" + " Content:" + this.Content;
}
}
```
* 圖片消息實體類**ImageMessage**:
```
@Data
public class ImageMessage extends BaseMessage {
/**
* 圖片鏈接
*/
private String PicUrl;
/**
* 圖片消息媒體id,可以調用獲取臨時素材接口拉取數據。
*/
private String MediaId;
public String toString(){
return super.toString() + "[ImageMessage]:" + " PriUrl:" + this.PicUrl
+ " MediaId:" + this.MediaId;
}
}
```
* 語音消息實體類**VoiceMessage**:
```
@Data
public class VoiceMessage extends BaseMessage {
/**
* 語音消息媒體id,可以調用獲取臨時素材+接口拉取數據。
*/
private String MediaId;
/**
* 語音格式,如amr,speex等
*/
private String Format;
public String toString(){
return super.toString() + "[VoiceMessage]:" + " MediaId:" + this.MediaId
+ " Format:" + this.Format;
}
}
```
* 視頻消息實體類**VideoMessage**:
```
@Data
public class VideoMessage extends BaseMessage{
/**
* 視頻消息媒體id,可以調用獲取臨時素材接口拉取數據。
*/
private String MediaId;
/**
* 視頻消息縮略圖的媒體id,可以調用多媒體文件下載接口拉取數據
*/
private String ThumbMediaId;
public String toString(){
return super.toString() + "[VideoMessage]:" + " MediaId:" + this.MediaId
+ " ThumbMediaId:" + this.ThumbMediaId;
}
}
```
* 暫時不提供小視頻處理類,請自行添加
* 地理位置處理類**LocationMessage**:
```
@Data
public class LocationMessage extends BaseMessage {
/**
* 地理位置維度
*/
private String Location_X;
/**
* 地理位置經度
*/
private String Location_Y;
/**
* 地圖縮放大小
*/
private String Scale;
/**
* 地理位置信息
*/
private String Label;
public String toString(){
return super.toString() + "[LocationMessage]:" + " Location_X:" + this.Location_X
+ " Location_Y:" + this.Location_Y + " Scale:" + this.Scale
+ " Lable:" + this.Label;
}
}
```
* 鏈接消息實體類**LinkMessage**:
```
@Data
public class LinkMessage extends BaseMessage{
/**
* 消息標題
*/
private String Title;
/**
* 消息描述
*/
private String Description;
/**
* 消息鏈接
*/
private String Url;
public String toString(){
return super.toString() + "[LinkMessage]:" + " Title:" + this.Title
+ " Description:" + this.Description + " Url:" + this.Url;
}
}
```
* 現在有了對象之間的映射但是還少一個判斷是什么事件類型的枚舉類,請在**utils**包下新建**code**包
* 在**code**包下新建**MessageCode**類
* 類如下(這個其實還少一兩個,但是懶的加了,有需要的自己加上,此代碼完全來自網上,沒有多少需要修改的):
```
@Slf4j
public class MessageCode {
/**
* 請求消息類型:文本
*/
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_VOICE = "voice";
/**
* 請求消息類型:視頻
*/
public static final String REQ_MESSAGE_TYPE_VIDEO = "video";
/**
* 請求消息類型:小視頻
*/
public static final String REQ_MESSAGE_TYPE_SHORTVIDEO = "shortvideo";
/**
* 請求消息類型:地理位置
*/
public static final String REQ_MESSAGE_TYPE_LOCATION = "location";
/**
* 請求消息類型:鏈接
*/
public static final String REQ_MESSAGE_TYPE_LINK = "link";
/**
* 請求消息類型:推送
*/
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";
/**
* 事件類型:VIEW(掃描二維碼事件)
*/
public static final String EVENT_TYPE_SCAN = "SCAN";
/**
* 事件類型:LOCATION(位置上報事件)
*/
public static final String EVENT_TYPE_LOCATION = "LOCATION";
/**
* 事件類型:VIEW(自定義菜單View事件)
*/
public static final String EVENT_TYPE_VIEW = "VIEW";
}
```
* 此時我們就可以在helloController里面進行事件類型的判斷了,代碼如下:
```
try { //try-catch后面會用到
Map<String, String> paramMap = ParseXml.parseXml(request);
String type = paramMap.get("MsgType");
//處理消息事件
if(MessageCode.REQ_MESSAGE_TYPE_TEXT.equals(type)){
log.warn("進入消息事件!");
}
} catch (Exception e) {
e.printStackTrace();
}
}
```
* 這里我們已經進行成功的判斷了,那么接下來就需要根據傳來的參數對象,進行返回參數xml的構建
* **utils**包新建**msghandle**包
* msghandle包下,新建**MsgHandle**,**MsgHelpClass**2個類
* MsgHandle : 處理消息的分發,因為每一個消息類型的返回值,各有個的特點
* MsgHelpClass:每一個消息類型的返回值,雖然各有各的特點,但是依然有共同的地方,用于輔助**MsgHandle**類


* **MsgHandle** 代碼如下:
```
import java.util.Date;
import java.util.Map;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import com.alibaba.fastjson.JSONObject;
//自建包請自導入
@Data
@Slf4j
public class MsgHandle {
/**
* 發送方賬號(一個openId)
*/
private String FromUserName;
/**
* 開發者微信號
*/
private String ToUserName;
/**
* 消息創建時間
*/
private long CreateTime;
/**
* 消息類型:
* 文本:text
* 圖片:image
* 語音:voice
* 視頻:video
* 小視頻:shortvideo
* 地理位置:location
* 鏈接:link
*/
private String MsgType;
/**
* 消息id,64位整數
*/
private long MsgId;
}
```
* 此類有4個共同的屬性,下面的代碼直接加在此類中即可,分開只是為了更好理解,避免第一眼看上去太多,難以理解
```
public String processMessage(Map<String, String> map) throws InstantiationException, IllegalAccessException{
//首先對自己的私有屬性進行賦值,接著創建基類實體對象,
this.FromUserName = map.get("ToUserName"); //!!!!!這里是調換的
//特別說明這里,看上面兩個圖中,我們會發現關于ToUserName的說明是收到openId,所以這里是調換的!
this.ToUserName = map.get("FromUserName");
this.MsgType = map.get("MsgType");
this.CreateTime = Long.valueOf(map.get("CreateTime"));
this.MsgId = Long.valueOf(map.get("MsgId"));
BaseMessage baseMessage = null;
//目前只支持文字消息回復
//用枚舉獲取是什么類型,再進入里面進行具體操作
if (this.MsgType.equals(MessageCode.REQ_MESSAGE_TYPE_TEXT)) { // 文本消息
log.info("這是文本消息!");
//這里用到了MsgHelpClass的方法,請看下文的此方法代碼
//參數1:this processMessage對象即可
//參數2:對應消息處理類即可
baseMessage = MsgHelpClass.setAttribute(this, TextMessage.class);
//向下轉型,小心,如果報了什么class異常,就是這里的問題。
TextMessage textMessage = (TextMessage) baseMessage;
//向文字消息實體類添加私有的屬性數據
textMessage.setContent("這里是測試回復");
//這里為生成xml數據的類,需要我們提供一個要生成xml數據的實體類,下文放代碼
return ParseXml.textMessageToXml(textMessage);
}
if (this.MsgType.equals(MessageCode.REQ_MESSAGE_TYPE_IMAGE)) { // 圖片消息
log.info("這是圖片消息!");
}
if (this.MsgType.equals(MessageCode.REQ_MESSAGE_TYPE_LINK)) { // 鏈接消息
log.info("這是鏈接消息!");
}
if (this.MsgType.equals(MessageCode.REQ_MESSAGE_TYPE_LOCATION)) { // 位置消息
log.info("這是位置消息!");
}
if (this.MsgType.equals(MessageCode.REQ_MESSAGE_TYPE_VIDEO)) { // 視頻/小視頻消息
log.info("這是視頻消息!");
}
if (this.MsgType.equals(MessageCode.REQ_MESSAGE_TYPE_VOICE)) { // 語音消息
log.info("這是語音消息!");
}
return "";
}
```
* **MsgHelpClass**類代碼如下:
```
import java.util.Date;
import lombok.extern.slf4j.Slf4j;
//自建類請自行導入
@Slf4j
public class MsgHelpClass {
//其實這里多加一個這個方法,而不是在processmessage里面直接進行轉型,是因為會報轉型錯誤的異常,用instanceof這里不太管用。
//方法講解:
//規定第一個參數必須為 MsgHandle對象,用他的私有屬性給實體基類進行賦值
//規定第二個參數必須為繼承自BaseMessage基類的子類,用來向下轉型
public static <E extends BaseMessage>E setAttribute(MsgHandle msgHandle,Class<E> eClass) throws IllegalAccessException, InstantiationException {
//newInstance 獲取對象,相當于new 對象
BaseMessage baseMessage = eClass.newInstance();
baseMessage.setCreateTime(new Date().getTime());
baseMessage.setFromUserName(msgHandle.getFromUserName());
baseMessage.setMsgId(msgHandle.getMsgId());
baseMessage.setToUserName(msgHandle.getToUserName());
baseMessage.setMsgType(msgHandle.getMsgType());
log.warn("MsgHelpClass-setAttribute方法返回值如下:\n" + baseMessage.toString());
return (E) baseMessage;
}
}
```
* 上面兩個類就是處理分發的類,但是上面我們也提到了一個把數據處理返回為xml,ParseXml類的靜態方法
* 此方法在ParseXml類中為:
```
public static String textMessageToXml(TextMessage textMessage) {
log.warn("ParseXml類TextMressage對象值如下:\n" + textMessage.toString());
//真正用來處理的方法,也在本類中
return XmlHandleFun(textMessage);
}
//圖片處理 暫時無實現,請后續自行實現
public static String imageMessageToXml(ImageMessage imageMessage) {
log.warn("ParseXml類ImageMessage對象值如下:\n" + imageMessage.toString());
return "";
}
//音頻處理 暫時無實現,請后續自行實現
public static String voiceMessageToXml(VoiceMessage voiceMessage) {
log.warn("ParseXml類VoiceMessage對象值如下:\n" + voiceMessage.toString());
return "";
}
//視頻處理 暫時無實現,請后續自行實現
public static String videoMessageToXml(VideoMessage videoMessage) {
log.warn("ParseXml類VideoMessage對象值如下:\n" + videoMessage.toString());
return "";
}
//其余無寫的,請自行添加
```
* 上文提到的**XmlHandleFun**方法代碼如下:
```
//本類講解,參數必須是一個繼承自baseMessage基類的子類對象
//返回值為String,生成的xml數據我們需要的string類型的,返回給服務器的也是String類型的
//本文需要一定的dom4j知識,推薦大家去https://www.cnblogs.com/qqran/p/12520901.html這里進行速成,很快幾分鐘
//當然不想看的話,后面也有簡單的介紹
private static <T extends BaseMessage> String XmlHandleFun(T object){
//Document對象,后續用他生成xml結構,并調用他的方法進行string類型數據返回
Document dou = null;
//用來判斷下文的if是否還繼續判斷,當然這里這個判斷是重復的,大家可以在看懂此代碼塊之后,自己決定是否刪除
boolean isif = true;
try {
//開始創建dom結構
dou = DocumentHelper.createDocument();
//dou.addElement 創建唯一的全局父節點,根據官方文檔的格式,返回的xml'格式基本只有最多3級
Element root = dou.addElement("xml");
//root.addElement 在root節點下,創建一個節點,相當于二級節點<ToUserName></ToUserName>
//attText 為添加二級節點的內容,<ToUserName>內容</ToUserName>
//補充知識:如果要給此行添加xml屬性,使用如下代碼-> root.addAttribute("id", "屬性");
//值為<xml id="屬性"></xml>
//獲取對象的值,進行字符串拼接
Element emp = root.addElement("ToUserName").addText("<![CDATA[" + object.getToUserName() + "]]>");
Element emp1 = root.addElement("FromUserName").addText("<![CDATA[" + object.getFromUserName() + "]]>");
Element emp2 = root.addElement("CreateTime").addText(String.valueOf(object.getCreateTime()));
Element emp3 = root.addElement("MsgType").addText("<![CDATA[" + object.getMsgType() + "]]>");
//判斷傳入的對象是否是它的實例
//是的話進行轉型,并添加屬于自己類的特有的屬性!
if(object instanceof TextMessage && isif){
TextMessage textMessage = (TextMessage) object;
Element emp4 = root.addElement("Content").addText("<![CDATA[" + textMessage.getContent() + "]]>");
isif = false;
}
if(object instanceof ImageMessage && isif){
ImageMessage imageMessage = (ImageMessage) object;
Element emp4 = root.addElement("Image");
emp4.addElement("MediaId").addText("<![CDATA[" + imageMessage.getMediaId() + "]]>");
isif = false;
}
if(object instanceof VoiceMessage && isif){
VoiceMessage voiceMessage = (VoiceMessage) object;
Element emp4 = root.addElement("Voice");
emp4.addElement("MediaId").addText("<![CDATA[" + voiceMessage.getMediaId() + "]]>");
isif = false;
}
if(object instanceof VideoMessage && isif) {
VideoMessage videoMessage = (VideoMessage) object;
Element emp4 = root.addElement("Video");
emp4.addElement("MediaId").addText("<![CDATA[" + videoMessage.getMediaId() + "]]>");
emp4.addElement("Title").addText("<![CDATA[" + videoMessage.getTitle() + "]]>");
emp4.addElement("Description").addText("<![CDATA[" + videoMessage.getDescription() + "]]>");
isif = false;
}
}catch(Exception e){
log.error("出現錯誤!XmlHandleFun");
e.printStackTrace();
}
//生成的xml是附帶<?xml version="1.0" encoding="UTF-8"?>此行的,我還并沒有測試帶上返回給微信服務器是否可行,當前沒被注釋的是去除此行的,如果使用注釋的一行則是直接返回生成的,帶上此頭部的
int count = "encoding=\"UTF-8\"?".length();
String result = dou.asXML();
result = result.substring(result.indexOf("encoding=\"UTF-8\"?") + count + 1);
return result.trim();
// return dou.asXML();
}
```
## 數據返回
以上配置完成之后我們就可以進行HelloController剩下的部分了
```
//我們只需要在原有的判斷條件下增加如下的代碼:
if(MessageCode.REQ_MESSAGE_TYPE_TEXT.equals(type)){
MsgHandle msgHandle = new MsgHandle();
ResultRes.response(msgHandle.processMessage(paramMap),response);
}
```
* 看到這里你可以會說這**ResultRes.response**又是個什么東西,都配置怎么多了怎么還有,我答應你們這真的是最后一個了,別罵了別罵了!
* 在**utilsl**包新建**reresult**包(我承認我這個名字不規范,不合適 ......)
* 在**reresult**包下新建**ResultRes**類
* ResultRes類代碼如下:
```
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringReader;
import java.io.StringWriter;
import javax.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringEscapeUtils;
import org.dom4j.Document;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;
import org.thymeleaf.util.StringUtils;
@Slf4j
public class reresult {
public static void response(String data, HttpServletResponse response) {
//用此方法進行xml文件的轉義和format效果類似,因為上文的代碼中,返回的string數據里面的,< >符合已經被轉義無法正常傳輸給微信服務器識別
data = StringEscapeUtils.unescapeXml(data);
log.info(data);
//如果數據為空,直接返回
if(StringUtils.isEmpty(data)){
log.error("數據為空!");
return;
}
try {
//進行編碼規定,返回數據!
response.setCharacterEncoding("UTF-8");
PrintWriter printWriter = response.getWriter();
printWriter.print(data);
printWriter.close();
}catch (IOException io){
log.error(io.getMessage());
io.printStackTrace();
}
}
/**
* @Title: format
* @Description: 格式化輸出xml字符串
* @param str
* @return String
* @throws Exception
* 這里的代碼是網上的,本來要用的,但是也最后沒用,是用來解決xml里面的<>符合傳輸的時候被轉義的問題
*/
public static String format(String str) throws Exception {
SAXReader reader = new SAXReader();
// 創建一個串的字符輸入流
StringReader in = new StringReader(str);
Document doc = reader.read(in);
// 創建輸出格式
OutputFormat formater = OutputFormat.createPrettyPrint();
// 設置xml的輸出編碼
formater.setEncoding("utf-8");
// 創建輸出(目標)
StringWriter out = new StringWriter();
// 創建輸出流
XMLWriter writer = new XMLWriter(out, formater);
// 輸出格式化的串到目標中,執行后。格式化后的串保存在out中。
writer.write(doc);
writer.close();
// 返回格式化后的結果
return out.toString();
}
}
```
到這里正常情況下,已經可以對用戶輸入的普通文本消息進行自動回復了!
## 自定義菜單
* 在**HelloController**類**message(get)**方法里,`String type = paramMap.get("MsgType");下`加入
* ~~~
String event = null;
//獲取自定義點擊/推送事件
if(paramMap.get("Event") != null){
event ?= paramMap.get("Event");
}
~~~
* 獲取事件字符串之后,我們需要進行匹配,來進入相應的處理
### 訂閱消息事件,返回創建自定義菜單json數據
* 在**message(get)**方法里加入
```
//訂閱消息事件
if(MessageCode.EVENT_TYPE_SUBSCRIBE.equals(event)){
String token = GetBodyMessage.getAcces_Token("wx59fa3e56c3448f46");
String body = GetBodyMessage.getBodyJson();
String s = httpsRequest.httpsRequests("https://api.weixin.qq.com/cgi-bin/menu/create?access_token=" + token, "POST", body);
System.err.println(s);
}
```
* 我們需要新建**GetBodyMessage**類,代碼如下:
```
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import java.util.HashMap;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class GetBodyMessage {
//添加菜單自定義json數據
public static String getBodyJson(){
Map<String, Object> mapbutton = new HashMap<>();
Map<String, Object> mapbody = new HashMap<>();
Map [] strargs = new Map[1];
mapbody.put("type","view");
mapbody.put("name","成績查詢!");
mapbody.put("key","message");
mapbody.put("url","http://www.baidu.com");
strargs[0] = mapbody;
mapbutton.put("button", strargs);
String body = JSON.toJSONString(mapbutton);
log.error(body);
//自定義菜單官網返回數據為json數據!
return body;
}
//獲取access_token
public static String getAcces_Token(String appid){
String token = "grant_type=client_credential&appid=" + appid +"&secret=xxxxxxxxxxxxxxxxxxxxxx";
//httpRequest代碼在下文
String result = httpRequest.httpRequests("https://api.weixin.qq.com/cgi-bin/token","GET",token);
Map map = JSONArray.parseObject(result);
System.out.println(map.get("access_token"));
return (String) map.get("access_token");
}
}
```
* **getBodyJson**方法返回的json結構如下:**

* **官網token獲取請求實例如下:**

* 創建**httpRequest**類和**httpsRequest**類,代碼如下(代碼源自網絡):
* 網址:[鏈接](https://www.cnblogs.com/ncy1/p/9684330.html)
```
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
public class httpRequest {
//處理http請求 requestUrl為請求地址 requestMethod請求方式,值為"GET"或"POST"
public static String httpRequests(String requestUrl,String requestMethod,String outputStr){
StringBuffer buffer=null;
try{
URL url=new URL(requestUrl);
HttpURLConnection conn=(HttpURLConnection)url.openConnection();
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setRequestMethod(requestMethod);
conn.connect();
//往服務器端寫內容 也就是發起http請求需要帶的參數
if(null!=outputStr){
OutputStream os=conn.getOutputStream();
os.write(outputStr.getBytes("utf-8"));
os.close();
}
//讀取服務器端返回的內容
InputStream is=conn.getInputStream();
InputStreamReader isr=new InputStreamReader(is,"utf-8");
BufferedReader br=new BufferedReader(isr);
buffer=new StringBuffer();
String line=null;
while((line=br.readLine())!=null){
buffer.append(line);
}
}catch(Exception e){
e.printStackTrace();
}
return buffer.toString();
}
}
```
```
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.URL;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
public class httpsRequest {
/*
* 處理https GET/POST請求
* 請求地址、請求方法、參數
*
*/
public static String httpsRequests(String requestUrl,String requestMethod,String outputStr){
StringBuffer buffer=null;
try{
//創建SSLContext
SSLContext sslContext=SSLContext.getInstance("SSL");
TrustManager[] tm={new MyX509TrustManager()};
//初始化
sslContext.init(null, tm, new java.security.SecureRandom());;
//獲取SSLSocketFactory對象
SSLSocketFactory ssf=sslContext.getSocketFactory();
URL url=new URL(requestUrl);
HttpsURLConnection conn=(HttpsURLConnection)url.openConnection();
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setUseCaches(false);
conn.setRequestMethod(requestMethod);
//設置當前實例使用的SSLSoctetFactory
conn.setSSLSocketFactory(ssf);
conn.connect();
//往服務器端寫內容
if(null!=outputStr){
OutputStream os=conn.getOutputStream();
os.write(outputStr.getBytes("utf-8"));
os.close();
}
//讀取服務器端返回的內容
InputStream is=conn.getInputStream();
InputStreamReader isr=new InputStreamReader(is,"utf-8");
BufferedReader br=new BufferedReader(isr);
buffer=new StringBuffer();
String line=null;
while((line=br.readLine())!=null){
buffer.append(line);
}
}catch(Exception e){
e.printStackTrace();
}
return buffer.toString();
}
}
```
https請求如果報錯,可能還需要導入服務端的安全證書步驟如下:
1. 例:點擊下圖中紅線指向的小鎖(谷歌瀏覽器)

2. 點擊**證書**一行
3. 點擊**詳細信息**

4. 點擊**復制到文件**

5. 先**下一步**然后來到下圖頁面

6. 選擇**DER編碼二進制X.509**后點擊**下一步**
7. 隨意選擇一個文件名和文件路徑。
8. 打開cmd進入剛剛創建的文件路徑,命令行輸入Keytool -import -alias 文件名 -file 文件名.cer -keystore cacerts
9. 回車之后會讓輸入口令,一般口令默認是**changeit**。輸入密鑰時并不會顯示在界面上。
10. 輸入正確之后會讓你選擇是否信任該證書,輸入y,會提示導入成功!
* 完成之后,用戶訂閱該公眾號之后就會出現菜單。
### 用戶點擊自定義菜單事件 && 用戶點擊自定義鏈接事件
* 由于上文新建的菜單里面包括了**用戶點擊自定義菜單事件 && 用戶點擊自定義鏈接事件**這兩個,所以我們只需要添加如下代碼,觀察到用戶的操作即可:
```
//用戶點擊自定義菜單事件
if(MessageCode.EVENT_TYPE_CLICK.equals(event)){
//事件KEY值,與自定義菜單接口中KEY值對應
String eventKey = "message";
log.warn(JSON.toJSONString(paramMap));
if(eventKey.equals(paramMap.get("EventKey"))){
log.warn("進入成績查詢點擊事件,開始處理并返回!");
}
}
//用戶點擊自定義鏈接事件
if(MessageCode.EVENT_TYPE_VIEW.equals(event)){
log.warn("進入百度頁面跳轉事件!");
}
```
