目錄
1. 概念說明:
2. 開發(fā)準備:
3. 測試demo(更改配置信息即可使用)
3.1.?服務(wù)器配置
?3.1.1.配置填寫說明
3.1.2.校驗服務(wù)器有效性:
3.1.3.URL后端接口代碼和校驗代碼(servlet)
?3.1.4.配置內(nèi)網(wǎng)穿透,完成本地調(diào)試
?3.1.5. 可能存在的問題
3.2 模板消息
3.2.1. 搞定?template_id?即模板消息id:
3.2.2. 搞定?touser 即openid
3.2.3.?從獲取openid的請求中我們發(fā)現(xiàn)需要access_token:
3.2.4. 發(fā)送模板消息的url參數(shù)
3.2.5. topcolor
3.2.5. data
3.3. 源碼
3.3.1?模板消息DTO
3.3.2.?模板消息內(nèi)容DTO
3.3.3. access_token緩存類:
3.3.4.http請求工具類:
3.3.5.?最終的Servlet(controller自行轉(zhuǎn)換)(為方便觀看所有邏輯都寫在這里了,自行優(yōu)化):
3.4.測試
官方文檔:
微信公眾平臺開發(fā)概述 | 微信開放文檔
全局返回碼文檔 :微信開放文檔
1. 概念說明:
-
access_token:是公眾號的全局唯一接口調(diào)用憑據(jù),公眾號調(diào)用各接口的必要參數(shù)(2小時內(nèi)有效,過期需要重新獲取,但1天內(nèi)獲取次數(shù)有限,需自行存儲)
-
OpenID :為了識別用戶每個公眾號針對,每個用戶會產(chǎn)生一個OpenID(用戶id:對用戶的操作需要用到)
-
UnionID: 同一開放平臺賬號下不同公眾號或應(yīng)用下用戶的共同id(這里不需要用到)
-
消息會話(這里用到模板消息)
-
公眾號是以微信用戶的一個聯(lián)系人形式存在的,消息會話是公眾號與用戶交互的基礎(chǔ)。
-
公眾號內(nèi)主要有這樣幾類消息服務(wù)的類型,分別用于不同的場景:
-
群發(fā)消息:訂閱號為每天1次,服務(wù)號為每月4次
-
被動回復(fù)消息:在用戶給公眾號發(fā)消息后,公眾號可以回復(fù)一個消息
-
客服消息:用戶在公眾號內(nèi)發(fā)消息/觸發(fā)特定行為后,公眾號可以給用戶發(fā)消息
-
模板消息:在需要對用戶發(fā)送服務(wù)通知(如刷卡提醒、服務(wù)預(yù)約成功通知等)時,公眾號可以用特定內(nèi)容模板,主動向用戶發(fā)送消息。
-
-
2. 開發(fā)準備:
- 開發(fā)者在公眾平臺網(wǎng)站中創(chuàng)建服務(wù)號、獲取接口權(quán)限后方可開始
https://kf.qq.com/faq/120911VrYVrA150918fMZ77R.html?scene_id=kf3386
-
個人研究測試:通過手機微信掃描二維碼獲得測試號
https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login
3. 測試demo(更改配置信息即可使用)
3.1.?服務(wù)器配置
這里坑比較多,服務(wù)器配置不是隨便填一個url就完事了,需要后端接口配合校驗。
界面:
?測試號界面:
?3.1.1.配置填寫說明
-
URL:服務(wù)器地址--是開發(fā)者用來接收微信消息和事件的接口URL (在提交配置修改時微信會向該URL接口發(fā)送請求驗證服務(wù)器地址的有效性)
-
Token:任意填寫,用作生成簽名(微信向上述URL接口發(fā)送的請求是攜帶token的,需要在接口中校驗token一致以確保安全性)這個token與上述的access_token不是一回事。這個token只用于驗證開發(fā)者服務(wù)器。
-
EncodingAESKey: 由開發(fā)者手動填寫或隨機生成,將用作消息體加解密密鑰
-
消息加解密方式 :明文模式、兼容模式和安全模式
3.1.2.校驗服務(wù)器有效性:
這是重點:點擊完提交修改后,微信會向url發(fā)起一個請求并將token攜帶過去,這個請求要能正確被你的后端服務(wù)器所響應(yīng)并返回正確的結(jié)果,服務(wù)器配置才算修改成功
校驗請求說明:
- 開發(fā)者提交信息后,微信服務(wù)器將發(fā)送GET請求到填寫的服務(wù)器地址URL上,GET請求攜帶參數(shù)如下表所示:
-
signature:微信加密簽名,signature結(jié)合了開發(fā)者填寫的token參數(shù)和請求中的timestamp參數(shù)、nonce參數(shù)。
-
timestamp:時間戳
-
nonce :隨機數(shù)
-
echostr:隨機字符串
-
-
開發(fā)者通過檢驗signature對請求進行校驗(下面有校驗方式)。若確認此次GET請求來自微信服務(wù)器,請原樣返回echostr參數(shù)內(nèi)容,則接入生效,成為開發(fā)者成功,否則接入失敗。
3.1.3.URL后端接口代碼和校驗代碼(servlet)
@WebServlet(urlPatterns = {
"/wx"})
public class WxServlet extends HttpServlet {
// 服務(wù)器配置填寫的token
private static final String wxToken = "888888";
@Override
protected void doGET(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGetWx(request, response);
}
/**
* @Description 校驗配置URL服務(wù)器的合法性
* @date 2023年5月29日下午4:17:40
* @param request
* @param response
* @throws ServletException
* @throws IOException
*/
public void doGetWx(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String signature = request.getParameter("signature");
String timestamp = request.getParameter("timestamp");
String nonce = request.getParameter("nonce");
String echostr = request.getParameter("echostr");
// 將微信echostr返回給微信服務(wù)器
try (OutputStream os = response.getOutputStream()) {
String sha1 = getSHA1(wxToken, timestamp, nonce, "");
// 和signature進行對比
if (sha1.equals(signature)) {
// 返回echostr給微信
os.write(URLEncoder.encode(echostr, "UTF-8").getBytes());
os.flush();
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 用SHA1算法生成安全簽名
*
* @param token 票據(jù)
* @param timestamp 時間戳
* @param nonce 隨機字符串
* @param encrypt 密文
* @return 安全簽名
* @throws Exception
*/
public static String getSHA1(String token, String timestamp, String nonce, String encrypt) throws Exception {
try {
String[] array = new String[] { token, timestamp, nonce, encrypt };
StringBuffer sb = new StringBuffer();
// 字符串排序
Arrays.sort(array);
for (int i = 0; i < 4; i++) {
sb.append(array[i]);
}
String str = sb.toString();
// SHA1簽名生成
MessageDigest md = MessageDigest.getInstance("SHA-1");
md.update(str.getBytes());
byte[] digest = md.digest();
StringBuffer hexstr = new StringBuffer();
String shaHex = "";
for (int i = 0; i < digest.length; i++) {
shaHex = Integer.toHexString(digest[i] & 0xFF);
if (shaHex.length() < 2) {
hexstr.append(0);
}
hexstr.append(shaHex);
}
return hexstr.toString();
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
}
項目路徑是/xjsrm,因此服務(wù)器url地址就是:http://localhost:8080/xjsrm/wx
直接將http://localhost:8080/xjsrm/wx地址填到服務(wù)配置的url可以嗎?答案是不可以!
?3.1.4.配置內(nèi)網(wǎng)穿透,完成本地調(diào)試
?3.1.4.1. 內(nèi)網(wǎng)穿透的必要性:
- 微信需要檢驗服務(wù)器有效性,因此這個url必須是公網(wǎng)資源,就是外網(wǎng)能訪問的,不支持本地127.0.0.1/localhost ,而且這樣調(diào)試起來非常的不方便。
- 因此這里使用內(nèi)網(wǎng)穿透將本地資源映射到公網(wǎng)。(直接部署到服務(wù)器上調(diào)試的可以跳過)
?3.1.4.2. 用到的工具cpolar:
- ?也可以使用花生殼、natapp、ngrok等;但不建議使用natapp、ngrok,這兩個工具都不能直接訪問到服務(wù)資源,中間會多一層手動校驗(提示用戶是否要訪問該網(wǎng)站),會造成不必要的麻煩。
?3.1.4.3. cpolar配置內(nèi)網(wǎng)穿透的教程
- 參考大佬的博文 從 2.內(nèi)網(wǎng)穿透開始看到3.測試公網(wǎng)訪問 即可微信公眾號本地開發(fā)調(diào)試 - 無公網(wǎng)IP,內(nèi)網(wǎng)穿透_微信公眾號服務(wù)器調(diào)試_熱愛編程的小K的博客-CSDN博客微信公眾號本地開發(fā)調(diào)試 - 無公網(wǎng)IP,內(nèi)網(wǎng)穿透https://blog.csdn.net/qq_72157449/article/details/130237603
?3.1.4.4. 獲取本地項目的公網(wǎng)路徑
? ? ? ? 配置完cpolar后在在線隧道列表中找到本地項目的地址,我項目是localhost:8080,因此公網(wǎng)地址對應(yīng)(最好用https協(xié)議)https://22717eef.r6.vip.cpolar.cn 、
????????因此完整的服務(wù)器Url就是: https://22717eef.r6.vip.cpolar.cn/xjsrm/wx
????????瀏覽器訪問該url,看后端是否接受到了請求,如果接收到說明接口沒問題,此時將URL填到對應(yīng)的配置欄中點擊提交即可。
?3.1.5. 可能存在的問題
????????如果你在使用點擊修改配置的提交發(fā)現(xiàn)微信發(fā)送的請求根本就沒有進到后端的項目中
????????此時查看sandboxinfo這個包。如果出現(xiàn) errorcode=-1多半是內(nèi)網(wǎng)穿透工具的問題。
????????出現(xiàn)其他錯誤代碼可以去錯誤大全中根據(jù)信息排查
? ? ? ? ?ngrok錯誤排查示例:? ? ? ? ?
3.2 模板消息
?模板消息的官方文檔:微信公眾平臺 (qq.com)
從官方給出的請求示例入手,看需要準備的接口和數(shù)據(jù):
POST請求
https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=ACCESS_TOKEN
請求包為一個json:
{
"template_id":"ngqIpbwh8bUfcSsECmogfXcV14J0tQlEpBO27izEYtY",
"touser":"OPENID",
"url":"http://weixin.qq.com/download",
"topcolor":"#FF0000",
"data":{
"User": {
"value":"黃先生",
"color":"#173177"
},
"Date":{
"value":"06月07日 19時24分",
"color":"#173177"
},
"CardNumber": {
"value":"0426",
"color":"#173177"
},
"Type":{
"value":"消費",
"color":"#173177"
},
"Money":{
"value":"人民幣260.00元",
"color":"#173177"
},
"DeadTime":{
"value":"06月07日19時24分",
"color":"#173177"
},
"Left":{
"value":"6504.09",
"color":"#173177"
}
}
}
3.2.1. 搞定?template_id?即模板消息id:
新增模板消息(以測試號為例)
模板內(nèi)容可設(shè)置參數(shù)(模板標題不可),供接口調(diào)用時使用,參數(shù)需以{{開頭,以.DATA}}結(jié)尾(具體傳參看后續(xù)代碼)
3.2.2. 搞定?touser 即openid
查看用戶管理的官方文檔;微信開放文檔 (qq.com),通過官方接口獲取
因為我們是公眾號,所以選用獲取用戶列表的接口:微信開放文檔 (qq.com)
http請求方式: GET(請使用https協(xié)議)
https://api.weixin.qq.com/cgi-bin/user/get?access_token=ACCESS_TOKEN&next_openid=NEXT_OPENID
參數(shù) 是否必須 說明
access_token 是 調(diào)用接口憑證
next_openid 是 第一個拉取的OPENID,不填默認從頭開始拉取
返回說明
正確時返回JSON數(shù)據(jù)包:
{
"total":2,
"count":2,
"data":{
"openid":["OPENID1","OPENID2"]},
"next_openid":"NEXT_OPENID"
}
3.2.3.?從獲取openid的請求中我們發(fā)現(xiàn)需要access_token:
獲取access_token官方文檔:微信開放文檔 (qq.com)
https請求方式: GET https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
參數(shù)說明
參數(shù) 是否必須 說明
grant_type 是 獲取access_token填寫client_credential
appid 是 第三方用戶唯一憑證
secret 是 第三方用戶唯一憑證密鑰,即appsecret
返回說明
正常情況下,微信會返回下述JSON數(shù)據(jù)包給公眾號:
{"access_token":"ACCESS_TOKEN","expires_in":7200}
3.2.4. 發(fā)送模板消息的url參數(shù)
? ? ? ? 這個是發(fā)送消息后用戶點擊卡片跳轉(zhuǎn)的地址(自定義)可以不填
3.2.5. topcolor
? ? ? ?消息卡片的頂部顏色(自定義)
3.2.5. data
? ? ? ?消息的內(nèi)容(了解結(jié)構(gòu)即可,后續(xù)代碼中會體現(xiàn))
3.3. 源碼
拷貝完再理解
3.3.1?模板消息DTO
import java.util.Map;
/**
* @Description 微信公眾號模板消息請求對象
* @author isymi
* @version
* @date 2023年5月29日下午4:28:09
*
*/
public class TemplateMessage {
/**
* 發(fā)送消息用戶的openid
*/
private String touser;
/*
* 模板消息id
*/
private String template_id;
/**
* 點擊模板信息跳轉(zhuǎn)地址;置空:則在發(fā)送后,點擊模板消息會進入一個空白頁面(ios),或無法點擊(android)
*/
private String url;
/**
* 卡片頂部顏色
*/
private String topcolor;
/**
* key為模板中參數(shù)內(nèi)容"xx.DATA"的xx,value為參數(shù)對應(yīng)具體的值和顏色
*/
private Map<String, WeChatTemplateMsg> data;
// private String data;
public TemplateMessage() {
}
public TemplateMessage(String touser, String template_id, String url, String topcolor, Map<String, WeChatTemplateMsg> data) {
this.touser = touser;
this.template_id = template_id;
this.url = url;
this.topcolor = topcolor;
this.data = data;
}
public String getTouser() {
return touser;
}
public void setTouser(String touser) {
this.touser = touser;
}
public String gettemplate_id() {
return template_id;
}
public void settemplate_id(String template_id) {
this.template_id = template_id;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getTopcolor() {
return topcolor;
}
public void setTopcolor(String topcolor) {
this.topcolor = topcolor;
}
public Map<String, WeChatTemplateMsg> getData() {
return data;
}
public void setData(Map<String, WeChatTemplateMsg> data) {
this.data = data;
}
@Override
public String toString() {
return "TemplateMessage [touser=" + touser + ", template_id=" + template_id + ", url=" + url + ", topcolor="
+ topcolor + ", data=" + data + "]";
}
}
3.3.2.?模板消息內(nèi)容DTO
import java.io.Serializable;
/**
* @Description 模板消息內(nèi)容類
* @author isymi
* @version
* @date 2023年5月29日下午4:33:27
*
*/
public class WeChatTemplateMsg implements Serializable{
/**
* 消息實參
*/
private String value;
/**
* 消息顏色
*/
private String color;
public WeChatTemplateMsg(String value) {
this.value = value;
this.color = "#173177";
}
public WeChatTemplateMsg(String value, String color) {
this.value = value;
this.color = color;
}
@Override
public String toString() {
return "WeChatTemplateMsg [value=" + value + ", color=" + color + "]";
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
}
3.3.3. access_token緩存類:
/**
* @Description access_token緩存類
* @author
* @version
* @date 2023年5月30日上午10:40:08
*
*/
public class AccessToken {
private String accessToken;
//過期時間 當(dāng)前系統(tǒng)時間+微信傳來的過期時間
private Long expiresTime;
public AccessToken(String accessToken, String expiresIn) {
this.accessToken = accessToken;
this.expiresTime = System.currentTimeMillis()+Integer.parseInt(expiresIn)*1000;
}
/**
* 判斷token是否過期
* @return
*/
public boolean isExpired(){
return System.currentTimeMillis()>expiresTime;
}
public String getAccessToken() {
return accessToken;
}
public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
}
public Long getExpiresTime() {
return expiresTime;
}
public void setExpiresTime(Long expiresTime) {
this.expiresTime = expiresTime;
}
public AccessToken(String accessToken, Long expiresTime) {
this.accessToken = accessToken;
this.expiresTime = expiresTime;
}
public AccessToken() {
}
}
3.3.4.http請求工具類:
import java.io.BufferedReader;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.xx.xx.pojo.TemplateMessage;
/**
* @Description 微信公眾號http請求工具類
* @author isymi
* @version
* @date 2023年5月29日下午4:07:39
*
*/
public class WXPublicAccountHttpUtil {
/**
* @Description 根據(jù)請求獲取返回結(jié)果字符串(根據(jù)請求獲取accessToken)
* @date 2023年5月29日下午4:04:21
* @param url
* @return
* @throws IOException
*/
public static String get(String url) throws IOException {
HttpURLConnection connection = null;
BufferedReader reader = null;
try {
URL requestUrl = new URL(url);
connection = (HttpURLConnection) requestUrl.openConnection();
connection.setRequestMethod("GET");
int responseCode = connection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
return response.toString();
} else {
// Handle error response
System.out.println("HTTP GET request failed with response code: " + responseCode);
return null;
}
} finally {
if (reader != null) {
reader.close();
}
if (connection != null) {
connection.disconnect();
}
}
}
/**
* @Description 根據(jù)URl獲取JSONObject:根據(jù)請求獲取關(guān)注用戶列表數(shù)據(jù)
* @date 2023年5月29日下午4:02:16
* @param url
* @return
* @throws IOException
*/
public static JSONObject getJsonObject(String url) throws IOException {
HttpURLConnection connection = null;
BufferedReader reader = null;
try {
URL urlObj = new URL(url);
connection = (HttpURLConnection) urlObj.openConnection();
connection.setRequestMethod("GET");
StringBuilder response = new StringBuilder();
reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
/*
* 正確返回的格式
* {
"total":2,
"count":2,
"data":{
"openid":["OPENID1","OPENID2"]},
"next_openid":"NEXT_OPENID"
}
*/
return JSON.parseObject(response.toString());
} finally {
if (reader != null) {
reader.close();
}
if (connection != null) {
connection.disconnect();
}
}
}
/**
* @Description 獲取關(guān)注用戶的 openid 集合
* @date 2023年5月29日下午4:04:02
* @param url
* @return
* @throws IOException
*/
public static List<String> getOpenidList(String url) throws IOException {
// 獲取關(guān)注用戶列表數(shù)據(jù)
JSONObject jsonObject = getJsonObject(url);
System.out.println(jsonObject);
// 錯誤情況
if (jsonObject.containsKey("errcode")) {
int errcode = jsonObject.getIntValue("errcode");
String errmsg = jsonObject.getString("errmsg");
throw new RuntimeException("Failed to get openid list. errcode: " + errcode + ", errmsg: " + errmsg);
}
int total = jsonObject.getIntValue("total");
// 無用戶關(guān)注 {"total":0,"count":0,"next_openid":""}
if (total == 0) {
throw new RuntimeException("No openid found. Total is 0.");
}
// 有用戶關(guān)注:
/**
* {"total":1,
* "data":{
* "openid":["o-tgG5-VaQfsgdjerHA-z2PeZFls"]},
* "count":1,
* "next_openid":"o-tgG5-VaQfsgdjerHA-z2PeZFls"}
*/
JSONObject dataObject = jsonObject.getJSONObject("data");
int count = dataObject.getIntValue("count");
System.out.println("關(guān)注總?cè)藬?shù):"+count);
JSONArray openidArray = dataObject.getJSONArray("openid");
// 將 openid 數(shù)組封裝為 List 集合
List<String> openidList = new ArrayList<>();
for (int i = 0; i < openidArray.size(); i++) {
String openid = openidArray.getString(i);
openidList.add(openid);
}
return openidList;
}
/**
* @Description 發(fā)送消息
* @date 2023年5月29日下午4:58:02
* @param accessToken
* @param templateMessage
* @return
* @throws IOException
*/
public static String sendMessage( String accessToken, TemplateMessage templateMessage) throws IOException {
String requestUrl ="https://api.weixin.qq.com/cgi-bin/message/template/send" + "?access_token=" + accessToken;
URL urlObject = new URL(requestUrl);
HttpURLConnection connection = (HttpURLConnection) urlObject.openConnection();
connection.setRequestMethod("POST");
connection.setDoOutput(true);
connection.setRequestProperty("Content-Type", "application/json");
String requestBody = JSON.toJSONString(templateMessage);
byte[] requestBodyBytes = requestBody.getBytes(StandardCharsets.UTF_8);
connection.setRequestProperty("Content-Length", String.valueOf(requestBodyBytes.length));
OutputStream outputStream = connection.getOutputStream();
outputStream.write(requestBodyBytes);
outputStream.close();
int responseCode = connection.getResponseCode();
BufferedReader reader;
if (responseCode >= 200 && responseCode <= 299) {
reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
} else {
reader = new BufferedReader(new InputStreamReader(connection.getErrorStream()));
}
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
reader.close();
connection.disconnect();
System.out.println("Response Code: " + responseCode);
System.out.println("Response Body: " + response.toString());
return response.toString();
}
}
3.3.5.?最終的Servlet(controller自行轉(zhuǎn)換)(為方便觀看所有邏輯都寫在這里了,自行優(yōu)化):
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
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 javax.servlet.http.HttpSession;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.xx.srm.pojo.AccessToken;
import com.xx.xx.pojo.TemplateMessage;
import com.xx.xx.pojo.WeChatTemplateMsg;
import com.xx.xx.utils.WXPublicAccountHttpUtil;
@WebServlet(urlPatterns = {
"/wx",
"/wx/message" })
public class WxServlet extends HttpServlet {
// 必須替換
private static final String wxToken = "xxxxxm8";
// 必須替換
public static final String APPID = "wx3xxxxxx1795fa";
// 必須替換
public static final String SECRET = "57b96fxxxxxxxxeab62bfe3";
// 必須替換
public static final String MESSAGE_TEMPLATE_ID = "N6MyyAF0Ucxxxxxxxxxxxxxxxxp-OGsWnQut_niUAaY";
/**
* 全局AccessToken
*/
private static AccessToken at;
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
if ("/wx".equals(request.getServletPath())) {
doGetWx(request, response);
} else if ("/wx/message".equals(request.getServletPath())) {
doSendMessage(request, response);
}
}
/**
* @Description 校驗配置URL服務(wù)器的合法性
* @date 2023年5月29日下午4:17:40
* @param request
* @param response
* @throws ServletException
* @throws IOException
*/
public void doGetWx(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String signature = request.getParameter("signature");
String timestamp = request.getParameter("timestamp");
String nonce = request.getParameter("nonce");
String echostr = request.getParameter("echostr");
// 將微信echostr返回給微信服務(wù)器
try (OutputStream os = response.getOutputStream()) {
String sha1 = getSHA1(wxToken, timestamp, nonce, "");
// 和signature進行對比
if (sha1.equals(signature)) {
// 返回echostr給微信
os.write(URLEncoder.encode(echostr, "UTF-8").getBytes());
os.flush();
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* @Description 發(fā)送模板消息
* @date 2023年5月30日上午10:57:45
* @param request
* @param response
* @throws IOException
*/
private void doSendMessage(HttpServletRequest request, HttpServletResponse response) throws IOException {
String token = getToken();
String url = "https://api.weixin.qq.com/cgi-bin/user/get?" + "access_token=" + token;
// 獲取 openid 數(shù)組
List<String> userOpenids = WXPublicAccountHttpUtil.getOpenidList(url);
// 主要的業(yè)務(wù)邏輯:
for (String openId : userOpenids) {
TemplateMessage templateMessage = new TemplateMessage();
templateMessage.setTouser(openId);
templateMessage.settemplate_id(MESSAGE_TEMPLATE_ID);
templateMessage.setTopcolor("#FF0000");
// key對應(yīng)創(chuàng)建模板內(nèi)容中的形參
//{{title.DATA}} {{username.DATA}} {{quote.DATA}} {{date.DATA}}
// WeChatTemplateMsg對應(yīng)實參和字體顏色
Map<String, WeChatTemplateMsg> data = new HashMap<String, WeChatTemplateMsg>();
data.put("title", new WeChatTemplateMsg("你有一條新的消息", "#173177"));
data.put("username", new WeChatTemplateMsg("黃先生", "#173177"));
data.put("date", new WeChatTemplateMsg("2023年05月29日 16時24分", "#173177"));
data.put("quote", new WeChatTemplateMsg("你好", "#173177"));
templateMessage.setData(data);
System.out.println(templateMessage);
WXPublicAccountHttpUtil.sendMessage(getToken(), templateMessage);
}
}
/**
* @Description 獲取token,本地緩存有就直接返回,沒有就發(fā)送請求獲?。╳x官方api獲取token每天有限制,因此需做緩存)
* @date 2023年5月29日下午4:13:17
* @return
*/
public static String getToken() {
if (at == null || at.isExpired()) {
getAccessToken();
}
return at.getAccessToken();
}
/**
* 給AccessToken賦值
*/
private static void getAccessToken() {
// 發(fā)送請求獲取token
String token = null;
try {
String url ="https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential"+ "&appid=" + APPID + "&secret=" + SECRET;
token = WXPublicAccountHttpUtil.get(url);
} catch (Exception e) {
e.printStackTrace();
}
JSONObject jsonObject = JSONObject.parseObject(token);
String accessToken = (String) jsonObject.get("access_token");
Integer expiresIn = (Integer) jsonObject.get("expires_in");
// 創(chuàng)建token對象,并存儲
at = new AccessToken(accessToken, String.valueOf(expiresIn));
System.out.println(token);
}
/**
* 用SHA1算法生成安全簽名
*
* @param token 票據(jù)
* @param timestamp 時間戳
* @param nonce 隨機字符串
* @param encrypt 密文
* @return 安全簽名
* @throws Exception
*/
public static String getSHA1(String token, String timestamp, String nonce, String encrypt) throws Exception {
try {
String[] array = new String[] { token, timestamp, nonce, encrypt };
StringBuffer sb = new StringBuffer();
// 字符串排序
Arrays.sort(array);
for (int i = 0; i < 4; i++) {
sb.append(array[i]);
}
String str = sb.toString();
// SHA1簽名生成
MessageDigest md = MessageDigest.getInstance("SHA-1");
md.update(str.getBytes());
byte[] digest = md.digest();
StringBuffer hexstr = new StringBuffer();
String shaHex = "";
for (int i = 0; i < digest.length; i++) {
shaHex = Integer.toHexString(digest[i] & 0xFF);
if (shaHex.length() < 2) {
hexstr.append(0);
}
hexstr.append(shaHex);
}
return hexstr.toString();
} catch (Exception e) {
e.printStackTrace();
}
return "";
}
}
3.4.測試
關(guān)注公眾號
瀏覽器訪問?http://22717eef.r6.vip.cpolar.cn/xjsrm/wx/message(替換為自己的)文章來源:http://www.zghlxwxcb.cn/news/detail-555795.html
查看微信消息文章來源地址http://www.zghlxwxcb.cn/news/detail-555795.html
到了這里,關(guān)于Java微信公眾號發(fā)送消息-保姆級教程附源碼的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!