本文基於羅召勇老師的教程加上自己的理解整理
本文源碼已上傳至我的碼雲: https://gitee.com/heliufang/wx
微信公眾號開發整體不難,主要是熟悉微信公眾號常用的一些接口文檔,然后會一門后端語言(比如java)即可。
羅召勇老師教程:微信公眾號開發-Java版(藍橋羅召勇)
微信公眾號文檔:微信公眾號官方文檔
1 微信公眾號介紹
賬號分為服務號
、訂閱號
、小程序
服務號和訂閱號開發類似,但是申請服務號必須是企業,所以學習的話申請一個訂閱號+測試賬號即可。為啥要申請測試賬號呢?因為訂閱號的接口功能有限,為了學習開發以及熟悉更多的接口,所以還需要申請一個測試號。
2 注冊訂閱號
第一步:訪問:https://mp.weixin.qq.com/ 點擊立即注冊
按鈕
第二步:注冊類型頁面選擇訂閱號
第三步:填寫相關信息,點擊注冊即可
3 注冊測試號
因為訂閱號的接口權限是有限的,為了熟悉更多的微信公眾號接口,所以需要申請一個測試號。
第一步:用注冊的訂閱號登錄
第二步:在目錄中【設置與開發】--->【開發者工具】下選擇公眾平台測試賬號,點擊進入后申請即可。
申請成功之后,就可以配置相關信息進行開發了,具體怎么配置后面再解釋
4 程序運行流程
用戶在公眾號發送請求到微信服務器
微信服務器
將請求轉發到我們自己的服務器
我們自己的服務器
處理完之后再把結果發送到微信服務器
最后微信服務器
再把結果響應給客戶
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
瀏覽器看到如下效果說明搭建成功
6 內外網穿透
外網默認是訪問不到自己電腦上的項目的,為了讓外網能夠訪問,所以需要做內外網穿透.這個不需要自己實現,可以借助一些工具,如花生殼、ngrok.這里用的是ngrok.
第一步:訪問ngrok官網,注冊ngrok賬號。
第二步:使用注冊的賬號登錄
第三步:【隧道管理--->開通隧道】立即購買,可以購買最后那個免費的,也可以花10塊錢買一個。免費的有時候不穩定,可以買一個10塊。
開通之后在隧道管理下就可以看到剛剛開通的隧道
第四步:下載客戶端工具,我電腦是windows的所以下載windows版
各版本工具下載地址:https://www.ngrok.cc/download.html
第五步:啟動ngrok客戶端工具,運行bat,輸入隧道id,回車
看到下面這個狀態為【online】表示啟動成功
然后就可以通過http://heliufang.vipgz4.idcfengye.com這個域名訪問本地8080端口上的項目了,比如訪問之前搭建的wx項目
7 開發接入
接入之后微信服務器和我們自己的項目就接通了。那么如何接入呢?
- 第一步:登錄微信公眾測試號的管理界面,填寫好相關信息
上圖中的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);
}
}
啟動項目,點擊提交按鈕,出現下面這個代表接入成功。
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);
}
然后用測試號發消息,公眾號都會回復一個 【你好】
這樣寫代碼功能是可以實現,但是這樣拼接字符串,再回復消息很不方便.然后自然就想到可以用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 測試
前面已經將基礎消息和圖文消息封裝好了,現在用封裝好的消息類來回復
第一步:將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);
//處理完將響應一個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和圖文
9.6 自動回復機器人
羅老師教程中的圖靈機器人已經要收費.我使用的是阿里雲的阿里雲小蜜
這個機器人來做的回復.
阿里雲小蜜機器人可以免費體驗三個月。
具體代碼可以查看阿里雲小蜜的文檔:阿里雲產品服務協議(雲小蜜)
10 ★access token的獲取
access_token是公眾號的全局唯一接口調用憑據,公眾號調用各接口時都需使用access_token。開發者需要進行妥善保存.access_token的有效期目前為2個小時,需定時刷新,重復獲取將導致上次獲取的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 自定義菜單
請注意:
- 自定義菜單最多包括3個一級菜單,每個一級菜單最多包含5個二級菜單。
- 一級菜單最多4個漢字,二級菜單最多8個漢字,多出來的部分將會以“...”代替。
- 創建自定義菜單后,菜單的刷新策略是,在用戶進入公眾號會話頁或公眾號profile頁時,如果發現上一次拉取菜單的請求在5分鍾以前,就會拉取一下菜單,如果菜單有更新,就會刷新客戶端的菜單。測試時可以嘗試取消關注公眾賬號后再次關注,則可以看到創建后的效果。
自定義菜單接口可實現多種類型按鈕,如下:
- click:點擊推事件用戶點擊click類型按鈕后,微信服務器會通過消息接口推送消息類型為event的結構給開發者(參考消息接口指南),並且帶上按鈕中開發者填寫的key值,開發者可以通過自定義的key值與用戶進行交互;
- view:跳轉URL用戶點擊view類型按鈕后,微信客戶端將會打開開發者在按鈕中填寫的網頁URL,可與網頁授權獲取用戶基本信息接口結合,獲得用戶基本信息。
- scancode_push:掃碼推事件用戶點擊按鈕后,微信客戶端將調起掃一掃工具,完成掃碼操作后顯示掃描結果(如果是URL,將進入URL),且會將掃碼的結果傳給開發者,開發者可以下發消息。
- scancode_waitmsg:掃碼推事件且彈出“消息接收中”提示框用戶點擊按鈕后,微信客戶端將調起掃一掃工具,完成掃碼操作后,將掃碼的結果傳給開發者,同時收起掃一掃工具,然后彈出“消息接收中”提示框,隨后可能會收到開發者下發的消息。
- pic_sysphoto:彈出系統拍照發圖用戶點擊按鈕后,微信客戶端將調起系統相機,完成拍照操作后,會將拍攝的相片發送給開發者,並推送事件給開發者,同時收起系統相機,隨后可能會收到開發者下發的消息。
- pic_photo_or_album:彈出拍照或者相冊發圖用戶點擊按鈕后,微信客戶端將彈出選擇器供用戶選擇“拍照”或者“從手機相冊選擇”。用戶選擇后即走其他兩種流程。
- pic_weixin:彈出微信相冊發圖器用戶點擊按鈕后,微信客戶端將調起微信相冊,完成選擇操作后,將選擇的相片發送給開發者的服務器,並推送事件給開發者,同時收起相冊,隨后可能會收到開發者下發的消息。
- location_select:彈出地理位置選擇器用戶點擊按鈕后,微信客戶端將調起地理位置選擇工具,完成選擇操作后,將選擇的地理位置發送給開發者的服務器,同時收起位置選擇工具,隨后可能會收到開發者下發的消息。
- media_id:下發消息(除文本消息)用戶點擊media_id類型按鈕后,微信服務器會將開發者填寫的永久素材id對應的素材下發給用戶,永久素材類型可以是圖片、音頻、視頻、圖文消息。請注意:永久素材id必須是在“素材管理/新增永久素材”接口上傳后獲得的合法id。
- 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);
}
}
運行效果如下:
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}}
創建好之后是下面這個樣子
第二步:編寫代碼
@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);
}
測試結果如下
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 {
}
}
訪問頁面,點擊按鈕就可以看到如下效果
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());
}
掃碼之后效果如下:
16 獲取用戶信息
一般在做網頁授權的時候,會用到這個功能。
16.1 獲取已關注的用戶信息
在關注者與公眾號產生消息交互后,公眾號可獲得關注者的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可以登錄測試號管理界面獲取,對應關注者的微信號
測試代碼
@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/公眾號開發文檔
最后多說一句只有把原生的基礎打好了,才能更好的理解和使用框架,所以建議先學原生的公眾號開發,再上手框架。