微信公眾號開發Java版-學習總結


本文基於羅召勇老師的教程加上自己的理解整理

本文源碼已上傳至我的碼雲: https://gitee.com/heliufang/wx

微信公眾號開發整體不難,主要是熟悉微信公眾號常用的一些接口文檔,然后會一門后端語言(比如java)即可。

羅召勇老師教程:微信公眾號開發-Java版(藍橋羅召勇)

微信公眾號文檔:微信公眾號官方文檔

1 微信公眾號介紹

賬號分為服務號訂閱號、小程序

image-20210808110213141

服務號和訂閱號開發類似,但是申請服務號必須是企業,所以學習的話申請一個訂閱號+測試賬號即可。為啥要申請測試賬號呢?因為訂閱號的接口功能有限,為了學習開發以及熟悉更多的接口,所以還需要申請一個測試號。

2 注冊訂閱號

第一步:訪問:https://mp.weixin.qq.com/ 點擊立即注冊按鈕

image-20210808110839008

第二步:注冊類型頁面選擇訂閱號

image-20210808110933591

第三步:填寫相關信息,點擊注冊即可

image-20210808111031011

3 注冊測試號

因為訂閱號的接口權限是有限的,為了熟悉更多的微信公眾號接口,所以需要申請一個測試號。

第一步:用注冊的訂閱號登錄

第二步:在目錄中【設置與開發】--->【開發者工具】下選擇公眾平台測試賬號,點擊進入后申請即可。

image-20210808112917414

申請成功之后,就可以配置相關信息進行開發了,具體怎么配置后面再解釋

image-20210808113103181

4 程序運行流程

用戶在公眾號發送請求到微信服務器

微信服務器將請求轉發到我們自己的服務器

我們自己的服務器處理完之后再把結果發送到微信服務器

最后微信服務器再把結果響應給客戶

image-20210808114624893

5 搭建開發環境

羅老師用的是eclipse並且沒有用maven環境,我用的是eclipse+maven+jdk7+tomcat8.0。maven的話可以兼容idea,而且下載依賴方便。

新建一個名為wx的maven項目(這個項目名字任意都行),pom.xml的依賴如下:

<dependencies>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.12</version>
			<scope>test</scope>
		</dependency>
		<!-- 阿里雲小蜜-自動回復機器人 -->
		<dependency>
			<groupId>com.aliyun</groupId>
			<artifactId>aliyun-java-sdk-chatbot</artifactId>
			<version>1.0.0</version>
		</dependency>
		<dependency>
			<groupId>com.aliyun</groupId>
			<artifactId>aliyun-java-sdk-core</artifactId>
			<version>4.5.2</version>
		</dependency>
		<!-- xml操作相關依賴 -->
		<dependency>
			<groupId>com.thoughtworks.xstream</groupId>
			<artifactId>xstream</artifactId>
			<version>1.4.11.1</version>
		</dependency>
		<dependency>
			<groupId>org.dom4j</groupId>
			<artifactId>dom4j</artifactId>
			<version>2.0.0</version>
		</dependency>
		<!-- 阿里json解析 -->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>fastjson</artifactId>
			<version>1.2.28</version>
		</dependency>
		<!-- 這個是編碼解碼的 -->
		<dependency>
			<groupId>commons-codec</groupId>
			<artifactId>commons-codec</artifactId>
			<version>1.10</version>
		</dependency>
	</dependencies>

編寫一個測試的servlet

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/test") 
public class TestServlet extends HttpServlet{

	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		System.out.println("請求到達了");
		resp.getWriter().write("hello weixin");
	}

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		
	}

}

啟動項目訪問:http://localhost:8080/wx/test

瀏覽器看到如下效果說明搭建成功

image-20210808120644851

6 內外網穿透

外網默認是訪問不到自己電腦上的項目的,為了讓外網能夠訪問,所以需要做內外網穿透.這個不需要自己實現,可以借助一些工具,如花生殼、ngrok.這里用的是ngrok.

ngrok文檔

第一步:訪問ngrok官網,注冊ngrok賬號。

第二步:使用注冊的賬號登錄

第三步:【隧道管理--->開通隧道】立即購買,可以購買最后那個免費的,也可以花10塊錢買一個。免費的有時候不穩定,可以買一個10塊。

image-20210808195028874

image-20210808195438170

開通之后在隧道管理下就可以看到剛剛開通的隧道

image-20210808195618721

第四步:下載客戶端工具,我電腦是windows的所以下載windows版

各版本工具下載地址:https://www.ngrok.cc/download.html

第五步:啟動ngrok客戶端工具,運行bat,輸入隧道id,回車

image-20210808195948516

image-20210808200101402

看到下面這個狀態為【online】表示啟動成功
image-20210808200116096

然后就可以通過http://heliufang.vipgz4.idcfengye.com這個域名訪問本地8080端口上的項目了,比如訪問之前搭建的wx項目

image-20210808200322739

7 開發接入

接入之后微信服務器和我們自己的項目就接通了。那么如何接入呢?

接入的官方文檔

image-20210808213637686

上圖中的url就是自己電腦的項目

點擊上圖的提交按鈕之后,微信會向上圖中的url發送一個get請求,請求參數如下:

參數 描述
signature 微信加密簽名,signature結合了開發者填寫的token參數和請求中的timestamp參數、nonce參數。
timestamp 時間戳
nonce 隨機數
echostr 隨機字符串
  • 第二步:編寫代碼校驗,用代碼實現下面的邏輯

1)將token、timestamp、nonce三個參數進行字典序排序

2)將三個參數字符串拼接成一個字符串進行sha1加密

3)開發者獲得加密后的字符串可與signature對比,標識該請求來源於微信,如果比對成功,請原樣返回echostr參數內容

在之前搭建的名為wx的項目中新建一個【WxServlet.java】

import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.qy.service.WxService;

@WebServlet("/api")
public class WxServlet extends HttpServlet{

	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		System.out.println("請求到達了");
		//取出微信服務器傳過來的參數
		String signature = req.getParameter("signature");
		String timestamp = req.getParameter("timestamp");
		String nonce = req.getParameter("nonce");
		String echostr = req.getParameter("echostr");
        //自定義一個check方法用來校驗接入
		boolean success = WxService.check(timestamp, nonce, signature);
		if(success){
			System.out.println("接入成功");
			PrintWriter writer = resp.getWriter();
			writer.write(echostr);//接入成功需要原樣返回echostr
		}else{
			System.out.println("接入失敗");
		}
	}

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		
	}
}

新建一個【WxService.java】並添加一個check工具方法

import java.util.Arrays;
import org.apache.commons.codec.digest.DigestUtils;

public class WxService {
	
	public static final String TOKEN = "hlf";//在微信配置界面自定義的token
	
	/**
	 * 接入校驗
	 * @param timestamp
	 * @param nonce
	 * @param signature
	 * @return
	 */
	public static boolean check(String timestamp, String nonce, String signature) {
		//1.將token、timestamp、nonce三個參數進行字典序排序
		String[] arr = new String[]{TOKEN,timestamp,nonce};
		Arrays.sort(arr);
		//2.將三個參數字符串拼接成一個字符串進行sha1加密  https://www.cnblogs.com/2333/p/6405386.html
		String str = arr[0]+arr[1]+arr[2];
		str = DigestUtils.sha1Hex(str);//sha1加密,這里沒有像羅老師那樣手寫,直接用的commons-codec包的工具類
		System.out.println("str:"+str);
		//3.將加密后的字符串和signature比較
		System.out.println(signature);
		return str.equalsIgnoreCase(signature);
	}
}

啟動項目,點擊提交按鈕,出現下面這個代表接入成功。

image-20210808220055857

8 接收用戶消息

官方文檔:接受普通消息

當普通微信用戶向公眾賬號發消息時,微信服務器將POST消息的XML數據包到開發者填寫的URL上。

也就是說用戶發消息給微信服務器,微信服務器會發送post請求到我們自己的服務器,並且傳送一個xml的數據給我們自己的服務器。

例如文本消息是這樣的

<xml>
  <ToUserName><![CDATA[toUser]]></ToUserName>
  <FromUserName><![CDATA[fromUser]]></FromUserName>
  <CreateTime>1348831860</CreateTime>
  <MsgType><![CDATA[text]]></MsgType>
  <Content><![CDATA[this is a test]]></Content>
  <MsgId>1234567890123456</MsgId>
</xml>
參數 描述
ToUserName 開發者微信號
FromUserName 發送方帳號(一個OpenID)
CreateTime 消息創建時間 (整型)
MsgType 消息類型,文本為text
Content 文本消息內容
MsgId 消息id,64位整型

java中這樣的數據讀取並不方便。可以轉換一下,先通過dom4j這個包轉成dom對象,再把標簽名和對應的標簽的值保存到HashMap集合中,這樣后面處理數據就很方便了,具體代碼實現如下:

在【WxServlet】中編寫doPost方法,在測試號管理界面,掃碼關注測試公眾號

@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		Map<String,String> map = WxService.parseRequest(req.getInputStream());
		System.out.println(map);//關注測試號,給測試公眾號發消息,就可以看到打印結果了
	}

在【WxService】中添加parseRequest方法

/**
	 * 將接受到的消息轉化成map
	 * @param req
	 * @return
	 */
	public static  Map<String, String> parseRequest(InputStream is) {
		Map<String,String> map = new HashMap<String,String>();
		//1.通過io流得到文檔對象
		SAXReader saxReader = new SAXReader();
		Document document = null;
		try {
			document = saxReader.read(is);
		} catch (DocumentException e) {
			e.printStackTrace();
		}
		//2.通過文檔對象得到根節點對象
		Element root = document.getRootElement();
		//3.通過根節點對象獲取所有子節點對象
		List<Element> elements = root.elements();
		//4.將所有節點放入map
		for (Element element : elements) {
			map.put(element.getName(), element.getStringValue());
		}
		return map;
	}

9 回復用戶消息封裝

官方文檔:被動回復用戶消息

當用戶發送消息給公眾號時(或某些特定的用戶操作引發的事件推送時),會產生一個POST請求,開發者可以在響應包(Get)中返回特定XML結構,來對該消息進行響應(現支持回復文本、圖片、圖文、語音、視頻、音樂)。嚴格來說,發送被動響應消息其實並不是一種接口,而是對微信服務器發過來消息的一次回復。

一旦遇到以下情況,微信都會在公眾號會話中,向用戶下發系統提示“該公眾號暫時無法提供服務,請稍后再試”:

1、開發者在5秒內未回復任何內容 2、開發者回復了異常數據,比如JSON數據等

上面這段文字來自官方,可以看出

  • 回復必須是xml的類型

  • 可以回復多種類型的xml(文本、圖片、圖文、語音、視頻、音樂)

  • 接收到消息沒有做出響應就會拋出:該公眾號暫時無法提供服務,請稍后再試

9.1 回復消息入門demo

這個demo就是給用戶回復一個文本消息

回復的xml格式如下:

<xml>
  <ToUserName><![CDATA[toUser]]></ToUserName>
  <FromUserName><![CDATA[fromUser]]></FromUserName>
  <CreateTime>12345678</CreateTime>
  <MsgType><![CDATA[text]]></MsgType>
  <Content><![CDATA[你好]]></Content>
</xml>
參數 是否必須 描述
ToUserName 接收方帳號(收到的OpenID)
FromUserName 開發者微信號
CreateTime 消息創建時間 (整型)
MsgType 消息類型,文本為text
Content 回復的消息內容(換行:在content中能夠換行,微信客戶端就支持換行顯示)

在wxservlet中doPost編寫如下代碼

@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		//設置編碼格式,不然中文會亂碼
		req.setCharacterEncoding("UTF-8");
		resp.setCharacterEncoding("UTF-8");
        //將請求中的xml參數轉成map
		Map<String,String> map = WxService.parseRequest(req.getInputStream());
		System.out.println(map);
		//回復消息
		String textMsg = "<xml><ToUserName><![CDATA["+map.get("FromUserName")+"]]></ToUserName><FromUserName><![CDATA["+map.get("ToUserName")+"]]></FromUserName><CreateTime>12345678</CreateTime><MsgType><![CDATA[text]]></MsgType><Content><![CDATA[你好]]></Content></xml>";
		resp.getWriter().print(textMsg);
	}

然后用測試號發消息,公眾號都會回復一個 【你好】

image-20210810221633028

這樣寫代碼功能是可以實現,但是這樣拼接字符串,再回復消息很不方便.然后自然就想到可以用java類來封裝消息,響應的時候將java類轉成xml(通過xstream這個工具包實現)。下面就以文本消息和圖文消息為例進行封裝,其它消息類似。

9.2 基礎消息類的封裝

把公共的屬性放到基礎消息類中,然后其它消息類繼承即可。

@XStreamAlias 這個注解配置的就是轉成xml時對應的節點名字

public class BaseMsg {
	@XStreamAlias("ToUserName")
	private String toUserName;//接收方的賬號(收到的openid)
	@XStreamAlias("FromUserName")
	private String fromUserName;//開發者的微信號
	@XStreamAlias("CreateTime")
	private String createTime;//消息創建時間
	@XStreamAlias("MsgType")
	private String msgType;//消息類型

	public BaseMsg(Map<String,String> requestMap) {
		super();
		this.toUserName = requestMap.get("FromUserName");
		this.fromUserName = requestMap.get("ToUserName");
		this.createTime = requestMap.get("CreateTime");
	}
    
    //get and set ...
}

9.3 文本消息類封裝

回復的xml的格式說明可以參考9.1入門demo.回復文本的封裝類如下:

@XStreamAlias("xml") //xml指的就是xml這個根節點名稱
public class TextMsg extends BaseMsg {
	@XStreamAlias("Content")
	private String content;//回復的文本內容
	
	public TextMsg(Map<String,String> requestMap,String content) {
		super(requestMap);
		this.setMsgType("text");
		this.content = content;
	}
    
    //get and set ...
}

9.4 圖文消息封裝

圖文消息格式說明

<xml>
  <ToUserName><![CDATA[toUser]]></ToUserName>
  <FromUserName><![CDATA[fromUser]]></FromUserName>
  <CreateTime>12345678</CreateTime>
  <MsgType><![CDATA[news]]></MsgType>
  <ArticleCount>1</ArticleCount>
  <Articles>
    <item>
      <Title><![CDATA[title1]]></Title>
      <Description><![CDATA[description1]]></Description>
      <PicUrl><![CDATA[picurl]]></PicUrl>
      <Url><![CDATA[url]]></Url>
    </item>
  </Articles>
</xml>
參數 是否必須 說明
ToUserName 接收方帳號(收到的OpenID)
FromUserName 開發者微信號
CreateTime 消息創建時間 (整型)
MsgType 消息類型,圖文為news
ArticleCount 圖文消息個數;當用戶發送文本、圖片、語音、視頻、圖文、地理位置這六種消息時,開發者只能回復1條圖文消息;其余場景最多可回復8條圖文消息
Articles 圖文消息信息,注意,如果圖文數超過限制,則將只發限制內的條數
Title 圖文消息標題
Description 圖文消息描述
PicUrl 圖片鏈接,支持JPG、PNG格式,較好的效果為大圖360200,小圖200200
Url 點擊圖文消息跳轉鏈接

首先封裝一個article類,對應就是xml中的item這個節點

@XStreamAlias("item")//映射到xml中的item這個節點
public class Article {
	@XStreamAlias("Title")
	private String title;//圖文消息標題
	@XStreamAlias("Description")
	private String description;//圖文消息描述
	@XStreamAlias("PicUrl")
	private String picUrl;//圖片鏈接
	@XStreamAlias("Url")
	private String url;//點擊圖文消息跳轉鏈接
    
    //get and set ...
}

然后再封裝一個圖文消息類

@XStreamAlias("xml")
public class NewsMsg extends BaseMsg {
	
	@XStreamAlias("ArticleCount")
	private String articleCount;//圖文消息個數
	@XStreamAlias("Articles")
	private List<Article> articles;

	public NewsMsg(Map<String, String> requestMap,List<Article> articles) {
		super(requestMap);
		this.setMsgType("news");
		this.articles = articles;
		this.setArticleCount(this.articles.size()+"");
	}
    //get and set ...
}

9.5 測試

前面已經將基礎消息和圖文消息封裝好了,現在用封裝好的消息類來回復

第一步:將wxservletdoPost方法改成如下

@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		//設置編碼格式,不然中文會亂碼
		req.setCharacterEncoding("UTF-8");
		resp.setCharacterEncoding("UTF-8");
        //將請求中的xml參數轉成map
		Map<String,String> map = WxService.parseRequest(req.getInputStream());
		System.out.println(map);
        //處理完將響應一個xml給微信
		String respXml = WxService.getRespose(map);
		System.out.println(respXml);
		resp.getWriter().print(respXml);
	}

第二步:WxService添加如下方法:

/**
	 * 事件消息回復
	 */
	public static String getRespose(Map<String, String> requestMap) {
		BaseMsg msg = null;
		// 根據用戶發送消息的類型,做不同的處理
		String msgType = requestMap.get("MsgType");
		switch (msgType) {
		case "text":
			msg = dealTextMsg(requestMap);
			break;
		case "news":
			break;
		default:
			break;
		}
		// System.out.println(msg);
		// 將處理結果轉化成xml的字符串返回
		if (null != msg) {
			return beanToXml(msg);
		}
		return null;
	}

	/**
	 * 將回復的消息類轉成xml字符串
	 * 
	 * @param msg
	 * @return
	 */
	public static String beanToXml(BaseMsg msg) {
		XStream stream = new XStream();
		stream.processAnnotations(TextMsg.class);
		stream.processAnnotations(NewsMsg.class);
		String xml = stream.toXML(msg);
		return xml;
	}

	/**
	 * 當用戶發送是文本消息的處理邏輯
	 * 
	 * @param map
	 * @return
	 */
	private static BaseMsg dealTextMsg(Map<String, String> requestMap) {
		// 獲取用戶發送的消息內容
		String msg = requestMap.get("Content");
		// 如果是圖文回復一個圖文消息
		if (msg.equals("圖文")) {
			List<Article> articles = new ArrayList<Article>();
			articles.add(new Article("碼雲博客", "這個是我個人的碼雲博客,基於hexo搭建,里面的文章都是使用markdown編寫",
					"https://heliufang.gitee.io/uploads/banner.jpg", "https://heliufang.gitee.io/"));
			return new NewsMsg(requestMap, articles);
		}
		//否則回復一個文本消息,文本內容為'當前時間+你好'
        //當然這個內容可以自定義,在這里也可以接入自動回復機器人
		TextMsg textMsg = new TextMsg(requestMap, new Date(System.currentTimeMillis()).toLocaleString() + "你好");
		return textMsg;
	}

然后分別給公眾號發一個1和圖文

image-20210810231459572

9.6 自動回復機器人

羅老師教程中的圖靈機器人已經要收費.我使用的是阿里雲的阿里雲小蜜這個機器人來做的回復.

阿里雲小蜜機器人可以免費體驗三個月。

具體代碼可以查看阿里雲小蜜的文檔:阿里雲產品服務協議(雲小蜜)

10 ★access token的獲取

access_token是公眾號的全局唯一接口調用憑據,公眾號調用各接口時都需使用access_token。開發者需要進行妥善保存.access_token的有效期目前為2個小時,需定時刷新,重復獲取將導致上次獲取的access_token失效

access token文檔

目前access_token的有效期通過返回的expire_in來傳達,目前是7200秒之內的值。中控服務器需要根據這個有效時間提前去刷新新access_token

總結:調用很多接口需要access_token,獲取access_token之后需要保存起來,過期了再重新獲取,而不是每次都重新獲取。

接口調用請求說明

https請求方式: GET https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET

參數說明

參數 是否必須 說明
grant_type 獲取access_token填寫client_credential
appid 第三方用戶唯一憑證
secret 第三方用戶唯一憑證密鑰,即appsecret

返回說明

正常情況下,微信會返回下述JSON數據包給公眾號:

{"access_token":"ACCESS_TOKEN","expires_in":7200}

參數說明

參數 說明
access_token 獲取到的憑證
expires_in 憑證有效時間,單位:秒

10.1 ★封裝請求工具類

因為需要發送請求給微信服務器,所以需要有請求的工具類。羅老師用的是java自帶的請求類,相對來說比較繁瑣。所以我這里采用的是Apache HttpClient,這個用起來更加的簡單。

第一步:pom.xml中導入依賴

<!--httpClient需要的依賴-->
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.2</version>
</dependency>
<!--//httpclient緩存-->
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient-cache</artifactId>
    <version>4.5</version>
</dependency>
<!--//http的mime類型都在這里面-->
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpmime</artifactId>
    <version>4.3.2</version>
</dependency>

第二步:基於Apache HttpClient封裝HttpUtils工具類,我封裝了4個方法,可以支持get請求和post請求。后面很多需要用的地方直接調用即可。

可以參考這個博客:HttpClient發送get/post請求

public class HttpUtils {

	public static void main(String[] args) {
		// 1.測試get請求
		/*
		 String getUrl = "http://localhost:8080/user/searchPage?pageNum=1&pageSize=2";
		 System.out.println(sendGet(getUrl));
		 */
		
		// 2.測試post請求 攜帶x-www-form-urlencoded數據格式
		/*String postUrlForm = "http://localhost:8080/user";
		Map paramMap = new HashMap();
		paramMap.put("name", "傑克");
		paramMap.put("age", "20");
		paramMap.put("gender", "1");
		System.out.println(sendPost(postUrlForm, paramMap));*/
		
		//3.測試post請求 攜帶json數據格式
		/*String postUrlJson = "http://localhost:8080/user";
		String jsonParam = "{\"name\":\"jack\",\"age\":\"18\",\"gender\":\"2\"}";
		System.out.println(sendPost(postUrlJson,jsonParam));*/
		
		//4 測試post 攜帶文件
		String postUrlFile = "http://localhost:8080/user/upload";
		Map paramMap = new HashMap();
		paramMap.put("name", "tom");
		String localFile = "d:\\logo.png";
		String fileParamName = "file";
		System.out.println(sendPost(postUrlFile, paramMap,localFile,fileParamName));
	}

	// 1.httpClient發送get請求
	public static String sendGet(String url) {
		String result = "";
		CloseableHttpResponse response = null;
		try {
			// 根據地址獲取請求
			HttpGet request = new HttpGet(url);// 這里發送get請求
			// 獲取當前客戶端對象
			CloseableHttpClient httpClient = HttpClients.createDefault();
			// 通過請求對象獲取響應對象
			response = httpClient.execute(request);
			// 判斷網絡連接狀態碼是否正常(0--200都數正常)
			if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
				result = EntityUtils.toString(response.getEntity(), "utf-8");
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (null != response) {
				try {
					response.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		return result;
	}

	// 2.httpClient發送post請求 攜帶x-www-form-urlencoded數據格式
	public static String sendPost(String url, Map<String, String> map) {
		CloseableHttpResponse httpResponse = null;
		String result = "";
		try {
			// 1、創建一個httpClient客戶端對象
			CloseableHttpClient httpClient = HttpClients.createDefault();
			// 2、創建一個HttpPost請求
			HttpPost httpPost = new HttpPost(url);
			// 設置請求頭
			httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded"); // 設置傳輸的數據格式
			// 攜帶普通的參數params的方式
			List<BasicNameValuePair> params = new ArrayList<BasicNameValuePair>();
			Set<String> keys = map.keySet();
			for (String key : keys) {
				params.add(new BasicNameValuePair(key, map.get(key)));
			}
			String str = EntityUtils.toString(new UrlEncodedFormEntity(params, Consts.UTF_8));
			// 這里就是:username=kylin&password=123456
			System.out.println(str);

			// 放參數進post請求里面 從名字可以知道 這個類是專門處理x-www-form-urlencoded 添加參數的
			httpPost.setEntity(new UrlEncodedFormEntity(params, "UTF-8"));

			// 7、執行post請求操作,並拿到結果
			httpResponse = httpClient.execute(httpPost);
			// 獲取結果實體
			HttpEntity entity = httpResponse.getEntity();
			if (entity != null) {
				result = EntityUtils.toString(entity, "UTF-8");
			} else {
				EntityUtils.consume(entity);//// 如果entity為空,那么直接消化掉即可
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (null != httpResponse) {
				try {
					httpResponse.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		return result;
	}

	// 3.httpClient發送post請求 攜帶json數據格式
	public static String sendPost(String url, String jsonStr) {
		CloseableHttpResponse httpResponse = null;
		String result = "";
		try {
			// 1.創建httpClient
			CloseableHttpClient httpClient = HttpClients.createDefault();
			// 2.創建post請求方式實例
			HttpPost httpPost = new HttpPost(url);

			// 2.1設置請求頭 發送的是json數據格式
			httpPost.setHeader("Content-type", "application/json;charset=utf-8");
			httpPost.setHeader("Connection", "Close");

			// 3.設置參數---設置消息實體 也就是攜帶的數據
			/*
			 * 比如傳遞: { "username": "aries", "password": "666666" }
			 */
			//String jsonStr = " {\"username\":\"aries\",\"password\":\"666666\"}";
			StringEntity entity = new StringEntity(jsonStr.toString(), Charset.forName("UTF-8"));
			entity.setContentEncoding("UTF-8"); // 設置編碼格式
			// 發送Json格式的數據請求
			entity.setContentType("application/json");
			// 把請求消息實體塞進去
			httpPost.setEntity(entity);

			// 4.執行http的post請求
			// 4.執行post請求操作,並拿到結果
			httpResponse = httpClient.execute(httpPost);
			// 獲取結果實體
			HttpEntity httpEntity = httpResponse.getEntity();
			if (httpEntity != null) {
				result = EntityUtils.toString(httpEntity, "UTF-8");
			} else {
				EntityUtils.consume(httpEntity);//// 如果httpEntity為空,那么直接消化掉即可
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (null != httpResponse) {
				try {
					httpResponse.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		return result;
	}

	// 4.httpClient發送post請求 攜帶文件
	public static String sendPost(String url, Map<String, String> map,String localFile, String fileParamName) {
		HttpPost httpPost = new HttpPost(url);
		CloseableHttpClient httpClient = HttpClients.createDefault();
        String resultString = "";
        CloseableHttpResponse response = null;
        try {
            // 把文件轉換成流對象FileBody
            FileBody bin = new FileBody(new File(localFile));

            MultipartEntityBuilder builder = MultipartEntityBuilder.create();

            // 相當於<input type="file" name="fileParamName"/> 其中fileParamName以傳進來的為准
            builder.addPart(fileParamName, bin);
            // 相當於<input type="text" name="userName" value=userName>
            /*builder.addPart("filesFileName",
                    new StringBody(fileParamName, ContentType.create("text/plain", Consts.UTF_8)));*/
            if (map != null) {
                for (String key : map.keySet()) {
                    builder.addPart(key,
                            new StringBody(map.get(key), ContentType.create("text/plain", Consts.UTF_8)));
                }
            }
            HttpEntity reqEntity = builder.build();
            httpPost.setEntity(reqEntity);
            // 發起請求 並返回請求的響應
            response = httpClient.execute(httpPost, HttpClientContext.create());
            resultString = EntityUtils.toString(response.getEntity(), "utf-8");
        }  catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (response != null)
                    response.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return resultString;
	}
}

10.2 創建AccessToken類

public class AccessToken {
	private String token;
	private long expiresTime;//過期時間
	
	public AccessToken(String token, String expiresIn) {
		super();
		this.token = token;
		//當前時間+有效期 = 過期時間
		this.expiresTime = System.currentTimeMillis()+Integer.parseInt(expiresIn);
	}
	
	/**
	 * 判斷token是否過期
	 * @return
	 */
	public boolean isExpire() {
		return System.currentTimeMillis() > expiresTime;
	}
    //get and set ...
}

10.3 WxService中添加獲取AccessToken的方法

private static AccessToken at;//token獲取的次數有限,有效期也有限,所以需要保存起來
private static String GET_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";
	
	//登錄測試號管理界面-測試號信息下面可以得到你的APPID和APPSECRET
	private static String APPID = "wx7bf783afc5150a5a";
	private static String APPSECRET = "8d9930d60717c7aaa0620ad993d984d8";
/**
	 * 發送get請求獲取AccessToken
	 */
	private static void getToken() {
		String url = GET_TOKEN_URL.replace("APPID", APPID).replace("APPSECRET", APPSECRET);
		String tokenStr = HttpUtils.sendGet(url);//調用工具類發get請求
		System.out.println(tokenStr);
		JSONObject jsonObject = JSONObject.parseObject(tokenStr);
		String token = jsonObject.getString("access_token");
		String expiresIn = jsonObject.getString("expires_in");
		at = new AccessToken(token, expiresIn);
	}
	
	/**
	 * 獲取AccessToken  向外提供
	 */
	public static String getAccessToken() {
		//過期了或者沒有值再去發送請求獲取
		if(at == null || at.isExpire()) {
			getToken();
		}
		return at.getToken();
	}

編寫一個測試類獲取AccessToken

import org.junit.Test;

import com.qy.service.WxService;

public class TestToken {

	@Test
	public void getAccessToken() {
        //可以看到下面兩次獲取的值一致
		System.out.println(WxService.getAccessToken());
		System.out.println(WxService.getAccessToken());
	}
}

11 自定義菜單

自定義菜單文檔

請注意:

  1. 自定義菜單最多包括3個一級菜單,每個一級菜單最多包含5個二級菜單。
  2. 一級菜單最多4個漢字,二級菜單最多8個漢字,多出來的部分將會以“...”代替。
  3. 創建自定義菜單后,菜單的刷新策略是,在用戶進入公眾號會話頁或公眾號profile頁時,如果發現上一次拉取菜單的請求在5分鍾以前,就會拉取一下菜單,如果菜單有更新,就會刷新客戶端的菜單。測試時可以嘗試取消關注公眾賬號后再次關注,則可以看到創建后的效果。

自定義菜單接口可實現多種類型按鈕,如下:

  1. click:點擊推事件用戶點擊click類型按鈕后,微信服務器會通過消息接口推送消息類型為event的結構給開發者(參考消息接口指南),並且帶上按鈕中開發者填寫的key值,開發者可以通過自定義的key值與用戶進行交互;
  2. view:跳轉URL用戶點擊view類型按鈕后,微信客戶端將會打開開發者在按鈕中填寫的網頁URL,可與網頁授權獲取用戶基本信息接口結合,獲得用戶基本信息。
  3. scancode_push:掃碼推事件用戶點擊按鈕后,微信客戶端將調起掃一掃工具,完成掃碼操作后顯示掃描結果(如果是URL,將進入URL),且會將掃碼的結果傳給開發者,開發者可以下發消息。
  4. scancode_waitmsg:掃碼推事件且彈出“消息接收中”提示框用戶點擊按鈕后,微信客戶端將調起掃一掃工具,完成掃碼操作后,將掃碼的結果傳給開發者,同時收起掃一掃工具,然后彈出“消息接收中”提示框,隨后可能會收到開發者下發的消息。
  5. pic_sysphoto:彈出系統拍照發圖用戶點擊按鈕后,微信客戶端將調起系統相機,完成拍照操作后,會將拍攝的相片發送給開發者,並推送事件給開發者,同時收起系統相機,隨后可能會收到開發者下發的消息。
  6. pic_photo_or_album:彈出拍照或者相冊發圖用戶點擊按鈕后,微信客戶端將彈出選擇器供用戶選擇“拍照”或者“從手機相冊選擇”。用戶選擇后即走其他兩種流程。
  7. pic_weixin:彈出微信相冊發圖器用戶點擊按鈕后,微信客戶端將調起微信相冊,完成選擇操作后,將選擇的相片發送給開發者的服務器,並推送事件給開發者,同時收起相冊,隨后可能會收到開發者下發的消息。
  8. location_select:彈出地理位置選擇器用戶點擊按鈕后,微信客戶端將調起地理位置選擇工具,完成選擇操作后,將選擇的地理位置發送給開發者的服務器,同時收起位置選擇工具,隨后可能會收到開發者下發的消息。
  9. media_id:下發消息(除文本消息)用戶點擊media_id類型按鈕后,微信服務器會將開發者填寫的永久素材id對應的素材下發給用戶,永久素材類型可以是圖片、音頻、視頻、圖文消息。請注意:永久素材id必須是在“素材管理/新增永久素材”接口上傳后獲得的合法id。
  10. view_limited:跳轉圖文消息URL用戶點擊view_limited類型按鈕后,微信客戶端將打開開發者在按鈕中填寫的永久素材id對應的圖文消息URL,永久素材類型只支持圖文消息。請注意:永久素材id必須是在“素材管理/新增永久素材”接口上傳后獲得的合法id。

接口調用請求說明

http請求方式:POST(請使用https協議) https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN

url中的ACCESS_TOKEN就是之前獲取的,調用這個接口需要帶上

請求需攜帶json參數

{
 "button":[
 	{
 		  "type":"click",
          "name":"一級點擊",
          "key":"1"
 	},
 	{
 		  "type":"view",
          "name":"個人博客",
          "url":"https://heliufang.gitee.io/"
 	},
 	{
          "name":"有子菜單",
          "sub_button":[
          	{
                  "type":"click",
                  "name":"三一點擊",
                  "key":"31"
            },
            {
                  "type":"view",
                  "name":"碼雲博客",
                  "url":"https://heliufang.gitee.io/"
            },
            {
            	"type":"pic_photo_or_album",
            	"name":"拍照或發圖",
            	"key":"33"
            }
          ]
 	}
 ]
}

參數說明

參數 是否必須 說明
button 一級菜單數組,個數應為1~3個
sub_button 二級菜單數組,個數應為1~5個
type 菜單的響應動作類型,view表示網頁類型,click表示點擊類型,miniprogram表示小程序類型
name 菜單標題,不超過16個字節,子菜單不超過60個字節
key click等點擊類型必須 菜單KEY值,用於消息接口推送,不超過128字節
url view、miniprogram類型必須 網頁 鏈接,用戶點擊菜單可打開鏈接,不超過1024字節。 type為miniprogram時,不支持小程序的老版本客戶端將打開本url。
media_id media_id類型和view_limited類型必須 調用新增永久素材接口返回的合法media_id
appid miniprogram類型必須 小程序的appid(僅認證公眾號可配置)
pagepath miniprogram類型必須 小程序的頁面路徑

返回結果

正確時的返回JSON數據包如下:

{"errcode":0,"errmsg":"ok"}

錯誤時的返回JSON數據包如下(示例為無效菜單名長度):

{"errcode":40018,"errmsg":"invalid button name size"}

和前面xml的類似,我們需要對着請求的json數據封裝按鈕類,這樣后面操作起來就比較方便,而且也方便維護。

11.1 封裝菜單類

<1>AbstractButton類

//所有菜單(按鈕)的父類
public abstract class AbstractButton {
	private String name;//按鈕標題

	public String getName() {
		return this.name;
	}

	public void setName(final String name) {
		this.name = name;
	}

	public AbstractButton(final String name) {
		this.name = name;
	}
}

<2>Button類

//一級菜單對象
public class Button {
	private List<AbstractButton> button;

	public Button() {
		this.button = new ArrayList<AbstractButton>();
	}

	public List<AbstractButton> getButton() {
		return this.button;
	}

	public void setButton(final List<AbstractButton> button) {
		this.button = button;
	}
}

<3>ClickButton類

//點擊類型的菜單
public class ClickButton extends AbstractButton {
	private String type;
	private String key;

	public String getType() {
		return this.type;
	}

	public void setType(final String type) {
		this.type = type;
	}

	public String getKey() {
		return this.key;
	}

	public void setKey(final String key) {
		this.key = key;
	}

	public ClickButton(final String name, final String key) {
		super(name);
		this.type = "click";//點擊類型
		this.key = key;
	}
}

<4>ViewButton類

//網頁類型的菜單
public class ViewButton extends AbstractButton {
	private String type;
	private String url;

	public String getType() {
		return this.type;
	}

	public void setType(final String type) {
		this.type = type;
	}

	public String getUrl() {
		return this.url;
	}

	public void setUrl(final String url) {
		this.url = url;
	}

	public ViewButton(final String name, final String url) {
		super(name);
		this.type = "view";//網頁類型
		this.url = url;
	}
}

<5> PhotoOrAlbumButton

//拍照或傳圖菜單
public class PhotoOrAlbumButton extends AbstractButton{
	private String type;
	private String key;

	public PhotoOrAlbumButton(String name,String key) {
		super(name);
		this.type = "pic_photo_or_album";//拍照獲取傳圖
		this.key = key;
	}

	public String getType() {
		return type;
	}

	public void setType(String type) {
		this.type = type;
	}

	public String getKey() {
		return key;
	}

	public void setKey(String key) {
		this.key = key;
	}
}

<6>SubButton

import java.util.ArrayList;
import java.util.List;

//二級菜單對象
public class SubButton extends AbstractButton {
	private List<AbstractButton> sub_button;

	public List<AbstractButton> getSub_button() {
		return this.sub_button;
	}

	public void setSub_button(final List<AbstractButton> sub_button) {
		this.sub_button = sub_button;
	}

	public SubButton(final String name) {
		super(name);
		this.sub_button = new ArrayList<AbstractButton>();
	}
}

11.2 測試

新增一個Test方法

package com.qy.test;

import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
import com.alibaba.fastjson.JSONObject;
import com.qy.entity.button.AbstractButton;
import com.qy.entity.button.Button;
import com.qy.entity.button.ClickButton;
import com.qy.entity.button.PhotoOrAlbumButton;
import com.qy.entity.button.SubButton;
import com.qy.entity.button.ViewButton;
import com.qy.service.WxService;
import com.qy.utils.HttpUtils;

public class TestButton {

	@Test
	public void setButton() {
		//創建一級菜單
		Button button = new Button();
		//在第三個菜單中創建二級菜單
		SubButton subButton = new SubButton("有子菜單");
		List<AbstractButton> list2 = new ArrayList();
		list2.add(new ClickButton("三一點擊", "31"));
		list2.add(new ViewButton("碼雲博客", "https://heliufang.gitee.io/"));
		list2.add(new PhotoOrAlbumButton("拍照或發圖","33"));
		subButton.setSub_button(list2);
		//在一級菜單中添加三個按鈕,
		List<AbstractButton> list = new ArrayList();
		list.add(new ClickButton("一級點擊", "1"));
		list.add(new ViewButton("個人博客", "https://heliufang.gitee.io/"));
		list.add(subButton);
		button.setButton(list);
		//轉成json格式字符串
		String jsonString = JSONObject.toJSONString(button);
		//System.out.println(jsonString);
		//發送請求
		String url = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN";
		url = url.replace("ACCESS_TOKEN", WxService.getAccessToken());//把token帶上
		String result = HttpUtils.sendPost(url, jsonString);
		System.out.println(result);
	}
}

運行效果如下:

image-20210813000312449

12 設置和獲取行業信息

12.1 設置行業信息

如果要發送模板消息,那么首先就得設置行業信息,如何設置和獲取可以看下面接口。

模板消息文檔

設置行業可在微信公眾平台后台完成,每月可修改行業1次,帳號僅可使用所屬行業中相關的模板,為方便第三方開發者,提供通過接口調用的方式來修改賬號所屬行業,具體如下:

接口調用請求說明

http請求方式: POST https://api.weixin.qq.com/cgi-bin/template/api_set_industry?access_token=ACCESS_TOKEN

POST數據說明

POST數據示例如下:

{
    "industry_id1":"1",
    "industry_id2":"4"
}

參數說明

參數 是否必須 說明
access_token 接口調用憑證
industry_id1 公眾號模板消息所屬行業編號-主行業
industry_id2 公眾號模板消息所屬行業編號-副行業

行業代碼查詢,更多代碼可以查詢文檔

主行業 副行業 代碼
IT科技 互聯網/電子商務 1
IT科技 IT軟件與服務 2
IT科技 IT硬件與設備 3
... ... ...

編寫測試代碼

@Test
	public void setIndustry() {
		String url = "https://api.weixin.qq.com/cgi-bin/template/api_set_industry?access_token=ACCESS_TOKEN";
		url = url.replace("ACCESS_TOKEN", WxService.getAccessToken());
		String jsonStr = "{\"industry_id1\":\"1\",\"industry_id2\":\"4\"}";
		String rString = HttpUtils.sendPost(url, jsonStr);
		System.out.println(rString);
	}

12.2 獲取行業信息

獲取帳號設置的行業信息。可登錄微信公眾平台,在公眾號后台中查看行業信息。為方便第三方開發者,提供通過接口調用的方式來獲取帳號所設置的行業信息,具體如下:

接口調用請求說明

http請求方式:GET https://api.weixin.qq.com/cgi-bin/template/get_industry?access_token=ACCESS_TOKEN

參數說明

參數 是否必須 說明
access_token 接口調用憑證

返回說明

正確調用后的返回示例:

{
    "primary_industry":{"first_class":"運輸與倉儲","second_class":"快遞"},
    "secondary_industry":{"first_class":"IT科技","second_class":"互聯網|電子商務"}
}

返回參數說明

參數 是否必填 說明
access_token 接口調用憑證
primary_industry 帳號設置的主營行業
secondary_industry 帳號設置的副營行業

編寫測試代碼

@Test
	public void getIndustry() {
		String url = "https://api.weixin.qq.com/cgi-bin/template/get_industry?access_token=ACCESS_TOKEN";
		url = url.replace("ACCESS_TOKEN", WxService.getAccessToken());
		String string = HttpUtils.sendGet(url);
		System.out.println(string);
	}

13 發送模板消息

模板消息接口

就是微信主動給用戶推送消息,不需要像之前那樣被動(用戶發送之后再回復).

接口調用請求說明

http請求方式: POST https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=ACCESS_TOKEN

POST數據如下:

{
	"touser": "oQxvI51GI5t9wBaBjmBXgJZZVM3A",
	"template_id": "tQ0G9Pmd_n_ylmplYsEnexgabkJXH1S3J7BXahK454g",
	"url": "https://heliufang.gitee.io/",
	"data": {
		"first": {
			"value": "您好!您投遞的簡歷有新的反饋",
			"color": "#173177"
		},
		"company": {
			"value": "廣州壹新網絡科技有限公司",
			"color": "#173177"
		},
		"time": {
			"value": "2021-8-5 23:31:23",
			"color": "#173177"
		},
		"result": {
			"value": "已通過",
			"color": "#ff0000"
		},
		"remark": {
			"value": "帶身份證",
			"color": "#173177"
		}
	}
}

參數說明

參數 是否必填 說明
touser 接收者openid
template_id 模板ID,這個需要在管理界面配置
url 模板跳轉鏈接(海外帳號沒有跳轉能力)
data 模板數據
color 模板內容字體顏色,不填默認為黑色

返回碼說明

在調用模板消息接口后,會返回JSON數據包。正常時的返回JSON數據包示例:

{"errcode":0,"errmsg":"ok","msgid":200228332}

★第一步:在微信測試號管理后台配置模板:

  • 模板標題: 簡歷反饋提醒

  • 模板內容:

{{first.DATA}} 
公司名:{{company.DATA}} 
投遞時間:{{time.DATA}} 
反饋結果:{{result.DATA}} {{remark.DATA}}

創建好之后是下面這個樣子

image-20210814170124702

第二步:編寫代碼

@Test
	public void sendTemplateMsg() {
		String url = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=ACCESS_TOKEN";
		url = url.replace("ACCESS_TOKEN", WxService.getAccessToken());
		//實際開發中應封裝成java類,再把java對象轉成類似下面的jsonstr
		String jsonStr = "{\r\n" + 
				"	\"touser\": \"oQxvI51GI5t9wBaBjmBXgJZZVM3A\",\r\n" + 
				"	\"template_id\": \"tQ0G9Pmd_n_ylmplYsEnexgabkJXH1S3J7BXahK454g\",\r\n" + 
				"	\"url\": \"https://heliufang.gitee.io/\",\r\n" + 
				"	\"data\": {\r\n" + 
				"		\"first\": {\r\n" + 
				"			\"value\": \"您好!您投遞的簡歷有新的反饋\",\r\n" + 
				"			\"color\": \"#173177\"\r\n" + 
				"		},\r\n" + 
				"		\"company\": {\r\n" + 
				"			\"value\": \"廣州壹新網絡科技有限公司\",\r\n" + 
				"			\"color\": \"#173177\"\r\n" + 
				"		},\r\n" + 
				"		\"time\": {\r\n" + 
				"			\"value\": \"2021-8-5 23:31:23\",\r\n" + 
				"			\"color\": \"#173177\"\r\n" + 
				"		},\r\n" + 
				"		\"result\": {\r\n" + 
				"			\"value\": \"已通過\",\r\n" + 
				"			\"color\": \"#ff0000\"\r\n" + 
				"		},\r\n" + 
				"		\"remark\": {\r\n" + 
				"			\"value\": \"帶身份證\",\r\n" + 
				"			\"color\": \"#173177\"\r\n" + 
				"		}\r\n" + 
				"	}\r\n" + 
				"}";
		String rString = HttpUtils.sendPost(url, jsonStr);
		System.out.println(rString);
	}

測試結果如下

image-20210814171316722

14 新增和獲取臨時素材

公眾號經常有需要用到一些臨時性的多媒體素材的場景,例如在使用接口特別是發送消息時,對多媒體文件、多媒體消息的獲取和調用等操作,是通過media_id來進行的。素材管理接口對所有認證的訂閱號和服務號開放。

注意點:

1、臨時素材media_id是可復用的。

2、媒體文件在微信后台保存時間為3天,即3天后media_id失效。

3、上傳臨時素材的格式、大小限制與公眾平台官網一致。

圖片(image): 10M,支持PNG\JPEG\JPG\GIF格式

語音(voice):2M,播放長度不超過60s,支持AMR\MP3格式

視頻(video):10MB,支持MP4格式

縮略圖(thumb):64KB,支持JPG格式

14.1 新增臨時素材

新增臨時素材文檔

羅老師用的是java自帶的文件類上傳,代碼比較繁瑣。而我使用HttpClient封裝的HttpUtils上傳就很簡單了。

接口調用請求說明

http請求方式: POST https://api.weixin.qq.com/cgi-bin/media/upload?access_token=ACCESS_TOKEN&type=TYPE

參數說明

參數 是否必須 說明
access_token 調用接口憑證
type 媒體文件類型,分別有圖片(image)、語音(voice)、視頻(video)和縮略圖(thumb)
media form-data中媒體文件標識,有filename、filelength、content-type等信息

返回說明

正確情況下的返回JSON數據包結果如下:

{"type":"image","media_id":"atL80WWRNpMWhivoIGf9KTUUUO5pm6RxML8OPEUd7cbfb1Rs0kl2Yv0319KMQI-0","created_at":1628933345,"item":[]}
參數 描述
type 媒體文件類型,分別有圖片(image)、語音(voice)、視頻(video)和縮略圖(thumb,主要用於視頻與音樂格式的縮略圖)
media_id 媒體文件上傳后,獲取標識
created_at 媒體文件上傳時間戳

編寫測試代碼

//上傳圖片
	@Test
	public void uploadMedia() {
		String url = "https://api.weixin.qq.com/cgi-bin/media/upload?access_token=ACCESS_TOKEN&type=TYPE";
		url = url.replace("ACCESS_TOKEN", WxService.getAccessToken());
		url = url.replace("TYPE", "image");
		String string = HttpUtils.sendPost(url, null, "C:\\Users\\Administrator\\Desktop\\2.jpg", "");
		System.out.println(string);
	}

14.2 獲取臨時素材

獲取臨時素材文檔

接口調用請求說明

http請求方式: GET,https調用 https://api.weixin.qq.com/cgi-bin/media/get?access_token=ACCESS_TOKEN&media_id=MEDIA_ID 請求示例(示例為通過curl命令獲取多媒體文件) curl -I -G "https://api.weixin.qq.com/cgi-bin/media/get?access_token=ACCESS_TOKEN&media_id=MEDIA_ID"

把ACCESS_TOKEN和MEDIA_ID替換到url的位置,然后瀏覽器打開就可以下載了

參數說明

參數 是否必須 說明
access_token 調用接口憑證
media_id 媒體文件ID

返回說明

正確情況下的返回HTTP頭如下:

HTTP/1.1 200 OK
Connection: close
Content-Type: image/jpeg
Content-disposition: attachment; filename="MEDIA_ID.jpg"
Date: Sun, 06 Jan 2013 10:20:18 GMT
Cache-Control: no-cache, must-revalidate
Content-Length: 339721
curl -G "https://api.weixin.qq.com/cgi-bin/media/get?access_token=ACCESS_TOKEN&media_id=MEDIA_ID"

15 二維碼生成和掃描

15.1 生成帶參數的臨時二維碼

為了滿足用戶渠道推廣分析和用戶帳號綁定等場景的需要,公眾平台提供了生成帶參數二維碼的接口。使用該接口可以獲得多個帶不同場景值的二維碼,用戶掃描后,公眾號可以接收到事件推送。

目前有2種類型的二維碼:

1、臨時二維碼,是有過期時間的,最長可以設置為在二維碼生成后的30天(即2592000秒)后過期,但能夠生成較多數量。臨時二維碼主要用於帳號綁定等不要求二維碼永久保存的業務場景 2、永久二維碼,是無過期時間的,但數量較少(目前為最多10萬個)。永久二維碼主要用於適用於帳號綁定、用戶來源統計等場景。

獲取帶參數的二維碼的過程包括兩步,首先創建二維碼ticket,然后憑借ticket到指定URL換取二維碼。

生成帶參數的二維碼文檔

測試代碼將實現下面這樣一個功能,點擊頁面上的生成按鈕,在頁面展示生成好的二維碼

【index.jsp】

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>二維碼測試頁面</title>
</head>
<body>
   <button type="button">生成二維碼</button><br>
   <img alt="暫無圖片" src="">
</body>
<script src="jquery.js"></script>
<script>
   $("button").click(function(){
   	$.ajax({
   		url: "/wx/getQrCode",
   		type: "get",
   		dataType: "json",
   		success: function(resp){
   			console.log(resp);
               //通過ticket獲取圖片
   			var src = "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket="+resp.ticket;
   			$("img").attr("src",src)
   		}
   	})
   })
</script>
</html>

【后端servlet】

@WebServlet("/getQrCode")
public class QrCodeServlet extends HttpServlet {

	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		//設置編碼格式,不然中文會亂碼
		req.setCharacterEncoding("UTF-8");
		resp.setCharacterEncoding("UTF-8");
		//發送post請求獲取ticket,頁面通過ticket就可以展示二維碼圖片了
		String url = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=TOKEN";
		url = url.replace("TOKEN", WxService.getAccessToken());
		/*600表示10分鍾有效  scene_str是一個唯一標識,類似點擊事件的key, QR_STR_SCENE表示臨時二維碼
		 * {
				"expire_seconds": 600, 
				"action_name": "QR_STR_SCENE", 
				"action_info": {
					"scene": {
						"scene_str": "test"
						
					}
				}
			}
		 */
		String jsonStr = "{\r\n" + 
				"	\"expire_seconds\": 600, \r\n" + 
				"	\"action_name\": \"QR_STR_SCENE\", \r\n" + 
				"	\"action_info\": {\r\n" + 
				"		\"scene\": {\r\n" + 
				"			\"scene_str\": \"test\"\r\n" + 
				"			\r\n" + 
				"		}\r\n" + 
				"	}\r\n" + 
				"}";
		String string = HttpUtils.sendPost(url, jsonStr);
		JSONObject object = JSONObject.parseObject(string);
		//將響應結果返回頁面,用於顯示二維碼
		resp.getWriter().write(string);
	}

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
	}
}

訪問頁面,點擊按鈕就可以看到如下效果

image-20210814182247464

15.2 掃描二維碼

用戶掃描帶場景值二維碼時,可能推送以下兩種事件:

如果用戶還未關注公眾號,則用戶可以關注公眾號,關注后微信會將帶場景值關注事件推送給開發者。

如果用戶已經關注公眾號,在用戶掃描后會自動進入會話,微信也會將帶場景值掃描事件推送給開發者

掃描臨時二維碼之后,會向服務器推送一個xml數據包,解析之后打印效果如下:

{
Ticket=gQFr8DwAAAAAAAAAAS5odHRwOi8vd2VpeGluLnFxLmNvbS9xLzAya1JKeDQ2M3JmOEQxOGlybk54Y08AAgS6mBdhAwRYAgAA, 
FromUserName=oQxvI51GI5t9wBaBjmBXgJZZVM3A,
EventKey=test, 
Event=SCAN, 
CreateTime=1628936703, 
ToUserName=gh_c8af0521f09a, 
MsgType=event
}

實現掃碼之后給用戶回復一個[你掃碼了]

修改【WxService】的代碼,修改getRespose方法,新增dealEvent和dealScanEvent方法

/**
	 * 事件消息回復
	 */
	public static String getRespose(Map<String, String> requestMap) {
		BaseMsg msg = null;
		// 根據用戶發送消息的類型,做不同的處理
		String msgType = requestMap.get("MsgType");
		switch (msgType) {
		case "text":
			msg = dealTextMsg(requestMap);
			break;
		case "news":
			break;
		case "event":
		    //新增處理事件的方法
			msg = dealEvent(requestMap);
			break;
		default:
			break;
		}
		// System.out.println(msg);
		// 將處理結果轉化成xml的字符串返回
		if (null != msg) {
			return beanToXml(msg);
		}
		return null;
	}

	//處理事件
	private static BaseMsg dealEvent(Map<String, String> requestMap) {
		String event = requestMap.get("Event");
		BaseMsg msg = null;
        //switch分發到具體事件
		switch (event) {
            case "SCAN":
                msg = dealScanEvent(requestMap);
                break;
            default:
                break;
		}
		return msg;
	}

	//處理SCAN事件
	private static BaseMsg dealScanEvent(Map<String, String> requestMap) {
		String eventKey = requestMap.get("EventKey");
		if("test".equals(eventKey)) {
			return new TextMsg(requestMap, "你掃碼了");
		}
		return new TextMsg(requestMap, requestMap.toString());
	}

掃碼之后效果如下:

image-20210814184046627

16 獲取用戶信息

一般在做網頁授權的時候,會用到這個功能。

16.1 獲取已關注的用戶信息

獲取用戶基本信息(UnionID機制)

在關注者與公眾號產生消息交互后,公眾號可獲得關注者的OpenID(加密后的微信號,每個用戶對每個公眾號的OpenID是唯一的。對於不同公眾號,同一用戶的openid不同)。公眾號可通過本接口來根據OpenID獲取用戶基本信息,包括昵稱、頭像、性別、所在城市、語言和關注時間。

獲取用戶基本信息(包括UnionID機制)

開發者可通過OpenID來獲取用戶基本信息。請使用https協議。

接口調用請求說明 http請求方式: GET https://api.weixin.qq.com/cgi-bin/user/info?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN

參數說明

參數 是否必須 說明
access_token 調用接口憑證
openid 普通用戶的標識,對當前公眾號唯一
lang 返回國家地區語言版本,zh_CN 簡體,zh_TW 繁體,en 英語

openid可以登錄測試號管理界面獲取,對應關注者的微信號

image-20210814185850181

測試代碼

@Test
	public void getUserInfo() {
		String url = "https://api.weixin.qq.com/cgi-bin/user/info?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN";
		url = url.replace("ACCESS_TOKEN", WxService.getAccessToken());
		url = url.replace("OPENID", "oQxvI51GI5t9wBaBjmBXgJZZVM3A");
		String string = HttpUtils.sendGet(url);
		System.out.println(string);//這里就可以看到打印的用戶信息了
	}

16.2 網頁授權

可以獲取未關注的用戶信息,這部分需要有域名才能測試,后面再完善.先把文檔放上

網頁授權

17 微信公眾號開發框架

前面的開發都是原生的寫法,github上有很多現成的公眾號開發框架。

比如這個基於springboot的公眾號開發框架:

倉庫:https://github.com/binarywang/weixin-java-mp-demo

文檔:https://github.com/Wechat-Group/WxJava/wiki/公眾號開發文檔

最后多說一句只有把原生的基礎打好了,才能更好的理解和使用框架,所以建議先學原生的公眾號開發,再上手框架。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM