当前位置:   article > 正文

SpringBoot(JAVA)整合微信公众号消息推送_springboot微信公众号推送消息

springboot微信公众号推送消息

网上那些都是零零碎碎的,不完整,重新整理下,代码可直接使用。
微信公众号消息推送大致分为两类,一是文本推送,二是带图片/视频推送
文本推送很好理解,可以用模板消息以及自定义消息推送。
图文/视频推送就稍微麻烦些步骤分为 上传素材到临时/永久库->上传图文消息->消息推送
贴几个官方文档,有总比没有好。
群发推送官网文档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
官方素材上传调试平台https://mp.weixin.qq.com/debug/cgi-bin/apiinfo?t=index&type=%E5%9F%BA%E7%A1%80%E6%94%AF%E6%8C%81&form=%E5%A4%9A%E5%AA%92%E4%BD%93%E6%96%87%E4%BB%B6%E4%B8%8A%E4%BC%A0%E6%8E%A5%E5%8F%A3%20/media/upload

一、文本推送

这里文本推送,可以采取模板和自定义推送内容。以下是模板方式推送,在图片/视频推送中会使用自定义内容推送演示。

首先需要在微信公众号上把测试的环境弄好。
点击开发者工具->公众平台测试账号。进去创建好对应的消息模板以及关注该测试的公众号。里面会有appID/appsecret,用户,模板以及能体验接口的信息,没有认证的微信号,有些接口是没有权限的,而且部分接口在没有认证的情况下每天都会有调用次数限制
在这里插入图片描述
在这里插入图片描述在这里插入图片描述
添加依赖,因为项目里面用了自己的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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

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;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

WxMagPushReq
这里要注意下模板的填充值data的格式,keyword1就是你在模板里面所需要替换的参数名称,后面value就是参数的值,模板里面的参数是和传入的参数需一一对应

@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就是模板里面需替换的参数名 如:{" +
            "                   \"keyword1\":{\n" +
            "                       \"value\":\"巧克力\"\n" +
            "                   },\n" +
            "                   \"keyword2\": {\n" +
            "                       \"value\":\"39.8元\"\n" +
            "                   },\n"+
            "                   }")

    @NotBlank(message = "模板需填充的值不能为空")
    private String data;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

controller
Result 是自定义的返回类,换成自己的即可

 /**
     * 微信公众号消息推送--需选用户openId发送
     *
     * @return
     */
    @Operation(summary = "微信公众号消息推送--需选用户openId发送")
    @PostMapping("/wxMsgPush")
    public Result wxMsgPush(@RequestBody @Valid WxMagPushReq wxMagPushReq) {
        return messageService.wxMsgPush(wxMagPushReq);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

service

    Result wxMsgPush(WxMagPushReq wxMagPushReq);
  • 1

serviceImpl
因为未认证的微信群发接口无法请求,采用循环发送的方式

@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, "参数为空");
        }
        //装推送失败的openId
        List<String> list = new LinkedList<>();
        //传入的openId去重
        List<String> collect = wxMagPushReq.getOpenIdList().stream().distinct().collect(Collectors.toList());
        for (String openId : collect) {
            //2、请求参数
            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();
    }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73

代码中注释掉的tel就是模板中对应的参数名称。
在这里插入图片描述
启动项目就可以测试了,请求成功,所关注的公众号会发一条推送的信息过来。
在这里插入图片描述

二、图文推送

把图片/视频称为素材,带素材推送步骤。上传素材到临时/永久库->上传图文消息->消息推送。
之前看社区说永久的素材库不能使用,下面的示例采用的是临时库

注:上传素材的时候会返回media_id这个ID在上传图文消息的时候需要用到,上传图文消息的时候也会返回media_id,这个ID在消息推送的时候也会用到。
在这里插入图片描述

WxMagPushAllReq

@Data
@Schema(description = "微信消息推送全部用户实体")
@JsonInclude(JsonInclude.Include.NON_NULL)
public class WxMagPushAllReq {

    @Schema(description = "1-文本,2图文,当发送图文消息时,mediaId不能为空")
    private String type;
//    @NotBlank(message = "消息内容不能为空")
    @Schema(description = "图文mediaId")
    private String mediaId;
//    @NotBlank(message = "消息内容不能为空")
    @Schema(description = "消息内容")
    private String content;

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

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);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

serveice

    Result wxMsgPushAll(WxMagPushAllReq wxMagPushAllReq);
    Result addMaterial(MultipartFile media, String type);
    Result uploadnews(WxTuwen wxTuwen);
  • 1
  • 2
  • 3

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) {
    	//如需处理多个图文,修改为循环处理
        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;
    }

//根据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();
    }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/天景科技苑/article/detail/981022
推荐阅读
相关标签
  

闽ICP备14008679号