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
一、文本推送
這里文本推送,可以采取模板和自定義推送內容。以下是模板方式推送,在圖片/視頻推送中會使用自定義內容推送演示。
首先需要在微信公眾號上把測試的環(huán)境弄好。
點擊開發(fā)者工具->公眾平臺測試賬號。進去創(chuàng)建好對應的消息模板以及關注該測試的公眾號。里面會有appID/appsecret,用戶,模板以及能體驗接口的信息,沒有認證的微信號,有些接口是沒有權限的,而且部分接口在沒有認證的情況下每天都會有調用次數(shù)限制。
添加依賴,因為項目里面用了自己的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 // 存儲當前的token有效期到什么時間點 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ù)需一一對應
@Data @Schema(description = "微信消息推送部分用戶實體") @JsonInclude(JsonInclude.Include.NON_NULL) //這個注解是用于實體轉JSON的時候,空值就在轉換的時候排除掉 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
因為未認證的微信群發(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); } //用于生成認證token private String getToken() { String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + appid + "&secret=" + secret; //2、基于doGet方法,調用地址獲取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就是模板中對應的參數(shù)名稱。
啟動項目就可以測試了,請求成功,所關注的公眾號會發(fā)一條推送的信息過來。
二、圖文推送
把圖片/視頻稱為素材,帶素材推送步驟。上傳素材到臨時/永久庫->上傳圖文消息->消息推送。之前看社區(qū)說永久的素材庫不能使用,下面的示例采用的是臨時庫。
注:上傳素材的時候會返回media_id這個ID在上傳圖文消息的時候需要用到,上傳圖文消息的時候也會返回media_id,這個ID在消息推送的時候也會用到。
WxMagPushAllReq
@Data @Schema(description = "微信消息推送全部用戶實體") @JsonInclude(JsonInclude.Include.NON_NULL) public class WxMagPushAllReq { @Schema(description = "1-文本,2圖文,當發(fā)送圖文消息時,mediaId不能為空") private String type; // @NotBlank(message = "消息內容不能為空") @Schema(description = "圖文mediaId") private String mediaId; // @NotBlank(message = "消息內容不能為空") @Schema(description = "消息內容") 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 = "標題") //標題 private String title; @Schema(description = "在圖文消息頁面點擊“閱讀原文”后的頁面,受安全限制,如需跳轉Appstore,可以使用itun.es或appsto.re的短鏈服務,并在短鏈后增加 #wechat_redirect 后綴") //在圖文消息頁面點擊“閱讀原文”后的頁面,受安全限制,如需跳轉Appstore,可以使用itun.es或appsto.re的短鏈服務,并在短鏈后增加 #wechat_redirect 后綴。 private String content_source_url; @Schema(description = "圖文消息頁面的內容") //圖文消息頁面的內容,支持HTML標簽。 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(); //預覽接口,可以看推送的效果 // String url = " https://api.weixin.qq.com/cgi-bin/message/mass/preview?access_token=" + getTokenString(); String params = ""; if ("1".equals(wxMagPushAllReq.getType())) { //自定義推送內容 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轉為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;"); //設置邊界 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(); //結尾 byte[] foot = ("\r\n--" + BOUNDARY + "--\r\n").getBytes("utf-8"); out.write(foot); out.flush(); out.close(); //獲取響應 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方法,調用地址獲取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(); }
總結
到此這篇關于SpringBoot(JAVA)整合微信公眾號消息推送的文章就介紹到這了,更多相關SpringBoot整合微信公眾號消息推送內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
SpringDataElasticsearch與SpEL表達式實現(xiàn)ES動態(tài)索引
這篇文章主要介紹了SpringDataElasticsearch與SpEL表達式實現(xiàn)ES動態(tài)索引,文章圍繞主題展開詳細的內容介紹,具有一定的參考價值,需要的朋友可以參考一下2022-09-09詳解SpringBoot開發(fā)案例之整合定時任務(Scheduled)
本篇文章主要介紹了詳解SpringBoot開發(fā)案例之整合定時任務(Scheduled),具有一定的參考價值,有興趣的可以了解一下2017-07-07Spring Boot中使用Spring-data-jpa的配置方法詳解
今天小編就為大家分享一篇關于Spring Boot中使用Spring-data-jpa的配置方法詳解,小編覺得內容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2019-03-03