SpringBoot(JAVA)整合微信公眾號消息推送完整步驟(文本、圖片/視頻推送)
網(wǎng)上那些都是零零碎碎的,不完整,重新整理下,代碼可直接使用。微信公眾號消息推送大致分為兩類,一是文本推送,二是帶圖片/視頻推送。
文本推送很好理解,可以用模板消息以及自定義消息推送。
圖文/視頻推送就稍微麻煩些步驟分為 上傳素材到臨時/永久庫->上傳圖文消息->消息推送。
貼幾個官方文檔,有總比沒有好。
群發(fā)推送官網(wǎng)文檔:https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Batch_Sends_and_Originality_Checks.html
上傳素材官方文檔:https://developers.weixin.qq.com/doc/offiaccount/Asset_Management/New_temporary_materials.html
一、文本推送
這里文本推送,可以采取模板和自定義推送內(nèi)容。以下是模板方式推送,在圖片/視頻推送中會使用自定義內(nèi)容推送演示。
首先需要在微信公眾號上把測試的環(huán)境弄好。
點(diǎn)擊開發(fā)者工具->公眾平臺測試賬號。進(jìn)去創(chuàng)建好對應(yīng)的消息模板以及關(guān)注該測試的公眾號。里面會有appID/appsecret,用戶,模板以及能體驗(yàn)接口的信息,沒有認(rèn)證的微信號,有些接口是沒有權(quán)限的,而且部分接口在沒有認(rèn)證的情況下每天都會有調(diào)用次數(shù)限制。



添加依賴,因?yàn)轫?xiàng)目里面用了自己的http封裝類,需替換下
<dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> <version>5.0.0-alpha.14</version> </dependency> <dependency> <groupId>com.squareup.okio</groupId> <artifactId>okio</artifactId> <version>3.6.0</version> </dependency>
WxToken
用于生成請求接口token
/**
* 存儲微信公眾號Token的POJO類
*
* @author zjw
* @description
*/
public class WxToken {
// 存儲token信息
private String accessToken;
// 10:00:00
// 12:00:00
// 存儲當(dāng)前的token有效期到什么時間點(diǎn)
private Long expire;
public String getAccessToken() {
// 獲取token之前,需要先判斷生存時間到了沒
return expire == null || expire < (System.currentTimeMillis() / 1000) ? null : this.accessToken;
}
public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
}
public Long getExpire() {
return expire;
}
public void setExpire(Long expire) {
this.expire = System.currentTimeMillis() / 1000 + expire;
}
}
WxMagPushReq
這里要注意下模板的填充值data的格式,keyword1就是你在模板里面所需要替換的參數(shù)名稱,后面value就是參數(shù)的值,模板里面的參數(shù)是和傳入的參數(shù)需一一對應(yīng)
@Data
@Schema(description = "微信消息推送部分用戶實(shí)體")
@JsonInclude(JsonInclude.Include.NON_NULL) //這個注解是用于實(shí)體轉(zhuǎn)JSON的時候,空值就在轉(zhuǎn)換的時候排除掉
public class WxMagPushReq {
@Schema(description = "用戶openid")
private List<String> openIdList;
@NotBlank(message = "模板id不能為空")
@Schema(description = "模板id")
private String templateId;
@Schema(description = "模板需填充的值,keyword1就是模板里面需替換的參數(shù)名 如:{" +
" \"keyword1\":{\n" +
" \"value\":\"巧克力\"\n" +
" },\n" +
" \"keyword2\": {\n" +
" \"value\":\"39.8元\"\n" +
" },\n"+
" }")
@NotBlank(message = "模板需填充的值不能為空")
private String data;
}
controller
Result 是自定義的返回類,換成自己的即可
/**
* 微信公眾號消息推送--需選用戶openId發(fā)送
*
* @return
*/
@Operation(summary = "微信公眾號消息推送--需選用戶openId發(fā)送")
@PostMapping("/wxMsgPush")
public Result wxMsgPush(@RequestBody @Valid WxMagPushReq wxMagPushReq) {
return messageService.wxMsgPush(wxMagPushReq);
}
service
Result wxMsgPush(WxMagPushReq wxMagPushReq);
serviceImpl
因?yàn)槲凑J(rèn)證的微信群發(fā)接口無法請求,采用循環(huán)發(fā)送的方式。
@Value("${weixin.msg.secret}")
private String secret;
@Value("${weixin.msg.appid}")
private String appid;
private static WxToken wxToken = new WxToken();
@Override
public Result wxMsgPush(WxMagPushReq wxMagPushReq) {
//1、拿到請求路徑
String url = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=" + getTokenString();
if (wxMagPushReq == null || wxMagPushReq.getOpenIdList().size() == 0 || StringUtils.isEmpty(wxMagPushReq.getData()) || StringUtils.isEmpty(wxMagPushReq.getTemplateId())) {
throw ServiceException.error(ErrorCode.PARAM_EXCEPTION, "參數(shù)為空");
}
//裝推送失敗的openId
List<String> list = new LinkedList<>();
//傳入的openId去重
List<String> collect = wxMagPushReq.getOpenIdList().stream().distinct().collect(Collectors.toList());
for (String openId : collect) {
//2、請求參數(shù)
String params = "{\n" +
" \"touser\":\"" + openId + "\",\n" +
" \"template_id\":\"" + wxMagPushReq.getTemplateId() + "\",\n" +
" \"data\":" + wxMagPushReq.getData() +
// " \"data\":{\n" +
// " \"tel\":{\n" +
// " \"value\":\"18700000000\"\n" +
// " }\n" +
// " }\n" +
" }";
HttpRequest request = HttpUtil.createPost(url);
request.body(params);
String str = request.execute().body();
JSONObject json = JSONObject.parseObject(str);
Integer errcode = json.getInteger("errcode");
if (0 != errcode) {
list.add(openId);
}
}
return Result.ok(list);
}
//用于生成認(rèn)證token
private String getToken() {
String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + appid + "&secret=" + secret;
//2、基于doGet方法,調(diào)用地址獲取Token
HttpRequest request = HttpUtil.createGet(url);
String resultJSON = request.execute().body();
JSONObject jsonObject = JSONObject.parseObject(resultJSON);
String accessToken = jsonObject.getString("access_token");
Long expiresIn = jsonObject.getLong("expires_in");
//3、存儲到WxToken對象里
wxToken.setAccessToken(accessToken);
wxToken.setExpire(expiresIn);
//4、返回Token
return wxToken.getAccessToken();
}
public String getTokenString() {
// 從對象中獲取accessToken
String accessToken = wxToken.getAccessToken();
// 獲取的accessToken為null,可能之前沒獲取,可能過期了
if (accessToken == null) {
// 加鎖
synchronized (wxToken) {
// 再次判斷
if (wxToken.getAccessToken() == null) {
getToken();
}
}
}
return wxToken.getAccessToken();
}
代碼中注釋掉的tel就是模板中對應(yīng)的參數(shù)名稱。

啟動項(xiàng)目就可以測試了,請求成功,所關(guān)注的公眾號會發(fā)一條推送的信息過來。

二、圖文推送
把圖片/視頻稱為素材,帶素材推送步驟。上傳素材到臨時/永久庫->上傳圖文消息->消息推送。之前看社區(qū)說永久的素材庫不能使用,下面的示例采用的是臨時庫。
注:上傳素材的時候會返回media_id這個ID在上傳圖文消息的時候需要用到,上傳圖文消息的時候也會返回media_id,這個ID在消息推送的時候也會用到。

WxMagPushAllReq
@Data
@Schema(description = "微信消息推送全部用戶實(shí)體")
@JsonInclude(JsonInclude.Include.NON_NULL)
public class WxMagPushAllReq {
@Schema(description = "1-文本,2圖文,當(dāng)發(fā)送圖文消息時,mediaId不能為空")
private String type;
// @NotBlank(message = "消息內(nèi)容不能為空")
@Schema(description = "圖文mediaId")
private String mediaId;
// @NotBlank(message = "消息內(nèi)容不能為空")
@Schema(description = "消息內(nèi)容")
private String content;
}
WxTuwen
@Data
public class WxTuwen {
//圖片media_id
@Schema(description = "圖片media_id")
private String thumb_media_id;
@Schema(description = "圖文消息的作者")
//圖文消息的作者
private String author;
@Schema(description = "標(biāo)題")
//標(biāo)題
private String title;
@Schema(description = "在圖文消息頁面點(diǎn)擊“閱讀原文”后的頁面,受安全限制,如需跳轉(zhuǎn)Appstore,可以使用itun.es或appsto.re的短鏈服務(wù),并在短鏈后增加 #wechat_redirect 后綴")
//在圖文消息頁面點(diǎn)擊“閱讀原文”后的頁面,受安全限制,如需跳轉(zhuǎn)Appstore,可以使用itun.es或appsto.re的短鏈服務(wù),并在短鏈后增加 #wechat_redirect 后綴。
private String content_source_url;
@Schema(description = "圖文消息頁面的內(nèi)容")
//圖文消息頁面的內(nèi)容,支持HTML標(biāo)簽。
private String content;
@Schema(description = "圖文消息的描述")
//圖文消息的描述,如本字段為空
private String digest;
@Schema(description = "是否顯示封面,1為顯示,0為不顯示")
//是否顯示封面,1為顯示,0為不顯示
private Integer show_cover_pic;
}
controller
@Operation(summary = "微信公眾號消息推送--推送全部用戶")
@PostMapping("/wxMsgPushAll")
public Result wxMsgPushAll(@RequestBody @Valid WxMagPushAllReq wxMagPushAllReq) {
return messageService.wxMsgPushAll(wxMagPushAllReq);
}
@Operation(summary = "微信公眾號消息推送--上傳臨時素材")
@PostMapping("/addMaterial")
public Result addMaterial(@RequestParam("media") MultipartFile media, @RequestParam("type") String type) {
return messageService.addMaterial(media,type);
}
@Operation(summary = "微信公眾號消息推送--上傳圖文消息素材")
@PostMapping("/uploadnews")
public Result uploadnews(@RequestBody @Valid WxTuwen wxTuwen) {
return messageService.uploadnews(wxTuwen);
}
serveice
Result wxMsgPushAll(WxMagPushAllReq wxMagPushAllReq);
Result addMaterial(MultipartFile media, String type);
Result uploadnews(WxTuwen wxTuwen);
serviceImpl
@Value("${weixin.msg.secret}")
private String secret;
@Value("${weixin.msg.appid}")
private String appid;
private static WxToken wxToken = new WxToken();
@Override
public Result wxMsgPushAll(WxMagPushAllReq wxMagPushAllReq) {
String list = getWxUserOpenid(getTokenString(), "", "");
if (StringUtils.isEmpty(list)) {
return Result.ok();
}
String url = "https://api.weixin.qq.com/cgi-bin/message/mass/send?access_token=" + getTokenString();
//預(yù)覽接口,可以看推送的效果
// String url = " https://api.weixin.qq.com/cgi-bin/message/mass/preview?access_token=" + getTokenString();
String params = "";
if ("1".equals(wxMagPushAllReq.getType())) {
//自定義推送內(nèi)容
params = "{\n" +
" \"touser\":[" + list.substring(0, list.length() - 1) + "],\n" +
" \"msgtype\": \"text\",\n" +
" \"text\": { \"content\": \"" + wxMagPushAllReq.getContent() + "\"}" +
" }";
} else {
//帶圖文推送消息
params = "{\n" +
" \"touser\":[" + list.substring(0, list.length() - 1) + "],\n" +
" \"mpnews\":{\n" +
" \"media_id\":\"" + wxMagPushAllReq.getMediaId() + "\"\n" +
" },\n" +
" \"msgtype\":\"mpnews\",\n" +
" \"send_ignore_reprint\":0\n" +
"}";
}
HttpRequest request = HttpUtil.createPost(url);
request.body(params);
String str = request.execute().body();
System.out.println(str);
return Result.ok(str);
}
@Override
public Result addMaterial(MultipartFile media, String type) {
try {
String mediaId = uploadFile(transferToFile(media), getTokenString(), type);
return Result.ok(mediaId);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public Result uploadnews(WxTuwen wxTuwen) {
//如需處理多個圖文,修改為循環(huán)處理
String url = "https://api.weixin.qq.com/cgi-bin/media/uploadnews?access_token=" + getTokenString();
String str = JSONObject.toJSONString(wxTuwen);
str = "{" + "\"articles\":[" + str + "]" + "}";
HttpRequest request = HttpUtil.createPost(url);
request.body(str);
String body = request.execute().body();
JSONObject jsonObject = JSONObject.parseObject(body);
return Result.ok(jsonObject.getString("media_id"));
}
//將類型MultipartFile轉(zhuǎn)為file類型
public File transferToFile(MultipartFile file) {
try {
File convFile = new File(file.getOriginalFilename());
convFile.createNewFile();
InputStream in = file.getInputStream();
OutputStream out = new FileOutputStream(convFile);
byte[] bytes = new byte[1024];
int read;
while ((read = in.read(bytes)) != -1) {
out.write(bytes, 0, read);
}
return convFile;
} catch (Exception e) {
throw new RuntimeException();
}
}
//上傳到臨時庫,返回一個ID
public String uploadFile(File file, String accessToken, String type) throws Exception {
//臨時素材地址
String url1 = "https://api.weixin.qq.com/cgi-bin/media/upload?access_token=" + accessToken + "&type=" + type;
//永久素材的地址
// String url1 = "https://api.weixin.qq.com/cgi-bin/material/add_material?access_token=" + accessToken + "&type=" + type;
if (!file.exists() || !file.isFile()) {
throw new IOException("文件不存在!");
}
URL urlObj = new URL(url1);
//連接
HttpURLConnection conn = (HttpURLConnection) urlObj.openConnection();
conn.setRequestMethod("POST");
conn.setDoInput(true);
conn.setDoOutput(true);
conn.setUseCaches(false);
//請求頭
conn.setRequestProperty("Connection", "Keep-Alive");
conn.setRequestProperty("Charset", "UTF-8");
//conn.setRequestProperty("Content-Type","multipart/form-data;");
//設(shè)置邊界
String BOUNDARY = "----------" + System.currentTimeMillis();
conn.setRequestProperty("Content-Type", "multipart/form-data;boundary=" + BOUNDARY);
StringBuilder sb = new StringBuilder();
sb.append("--");
sb.append(BOUNDARY);
sb.append("\r\n");
sb.append("Content-Disposition:form-data;name=\"file\";filename=\"" + file.getName() + "\"\r\n");
sb.append("Content-Type:application/octet-stream\r\n\r\n");
System.out.println(sb);
byte[] head = sb.toString().getBytes("UTF-8");
//輸出流
OutputStream out = new DataOutputStream(conn.getOutputStream());
out.write(head);
//文件正文部分
DataInputStream in = new DataInputStream(new FileInputStream(file));
int bytes = 0;
byte[] bufferOut = new byte[1024];
while ((bytes = in.read(bufferOut)) != -1) {
out.write(bufferOut, 0, bytes);
}
in.close();
//結(jié)尾
byte[] foot = ("\r\n--" + BOUNDARY + "--\r\n").getBytes("utf-8");
out.write(foot);
out.flush();
out.close();
//獲取響應(yīng)
StringBuffer buffer = new StringBuffer();
BufferedReader reader = null;
String result = null;
reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String line = null;
while ((line = reader.readLine()) != null) {
buffer.append(line);
}
if (result == null) {
result = buffer.toString();
}
reader.close();
//需要添加json-lib jar包
JSONObject json = JSONObject.parseObject(result);
System.out.println(json);
String mediaId = json.getString("thumb_media_id");
return result;
}
//根據(jù)appid和secretaccess_token
private String getToken() {
String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + appid + "&secret=" + secret;
//2、基于doGet方法,調(diào)用地址獲取Token
HttpRequest request = HttpUtil.createGet(url);
String resultJSON = request.execute().body();
JSONObject jsonObject = JSONObject.parseObject(resultJSON);
String accessToken = jsonObject.getString("access_token");
Long expiresIn = jsonObject.getLong("expires_in");
//3、存儲到WxToken對象里
wxToken.setAccessToken(accessToken);
wxToken.setExpire(expiresIn);
//4、返回Token
return wxToken.getAccessToken();
}
public String getTokenString() {
// 從對象中獲取accessToken
String accessToken = wxToken.getAccessToken();
// 獲取的accessToken為null,可能之前沒獲取,可能過期了
if (accessToken == null) {
// 加鎖
synchronized (wxToken) {
// 再次判斷
if (wxToken.getAccessToken() == null) {
getToken();
}
}
}
return wxToken.getAccessToken();
}總結(jié)
到此這篇關(guān)于SpringBoot(JAVA)整合微信公眾號消息推送的文章就介紹到這了,更多相關(guān)SpringBoot整合微信公眾號消息推送內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringDataElasticsearch與SpEL表達(dá)式實(shí)現(xiàn)ES動態(tài)索引
這篇文章主要介紹了SpringDataElasticsearch與SpEL表達(dá)式實(shí)現(xiàn)ES動態(tài)索引,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價值,需要的朋友可以參考一下2022-09-09
詳解SpringBoot開發(fā)案例之整合定時任務(wù)(Scheduled)
本篇文章主要介紹了詳解SpringBoot開發(fā)案例之整合定時任務(wù)(Scheduled),具有一定的參考價值,有興趣的可以了解一下2017-07-07
Java基礎(chǔ)學(xué)習(xí)之Swing事件監(jiān)聽
今天學(xué)習(xí)java的Swing庫,創(chuàng)建桌面應(yīng)用的時候,突然發(fā)現(xiàn)有些按鈕需要特定的功能響應(yīng),故來研究一番Swing的事件監(jiān)聽,文中有非常詳細(xì)的介紹及代碼示例,需要的朋友可以參考下2021-05-05
Spring Boot中使用Spring-data-jpa的配置方法詳解
今天小編就為大家分享一篇關(guān)于Spring Boot中使用Spring-data-jpa的配置方法詳解,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2019-03-03
java使用計(jì)算md5校驗(yàn)碼方式比較兩個文件是否相同
MD5文件效驗(yàn)碼是一個判斷文件是否是相同文件的途徑,通過比較兩個文件的Md5效驗(yàn)碼是否相同來精確判斷兩個文件是否相同2014-04-04

