java实现微信公众号图文、视频一键发布

微信公众号开发

一、准备工作

在微信开发平台申请一个测试账号(优先选择这个),如果可以使用认证过的订阅号最好,不过我们在开发阶段很多功能都是测试用的,申请一个测试账号完全够用
个人的订阅号是不可以申请认证的,所以很多微信API不可以用

二、开始开发

写在前面

项目所需依赖

  <!-- SpringBoot Web容器 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--httpclient工具类https://github.com/Arronlong/httpclientutil-->
        <dependency>
            <groupId>com.arronlong</groupId>
            <artifactId>httpclientutil</artifactId>
            <version>1.0.4</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.10</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.72</version>
        </dependency>
        <!--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>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.8.0</version>
        </dependency>
        <dependency>
            <groupId>com.hankcs</groupId>
            <artifactId>hanlp</artifactId>
            <version>portable-1.2.8</version>
        </dependency>
所有涉及到的微信api接口地址
    private static ObjectMapper objectMapper = new ObjectMapper();
    private static final String DRAFTADD = "https://api.weixin.qq.com/cgi-bin/draft/add?access_token=";//新建草稿文章
    private static final String PUBLISH = "https://api.weixin.qq.com/cgi-bin/freepublish/submit?access_token="; //正式发布文章
    private static final String PUBLISHRESULT = "https://api.weixin.qq.com/cgi-bin/freepublish/get?access_token=";//获取发布文章结果
    private static final String GETMATERIAL = "https://api.weixin.qq.com/cgi-bin/material/get_material?access_token="; //获取上传素材信息
    private static final String ARTICLEDELETE = "https://api.weixin.qq.com/cgi-bin/freepublish/delete?access_token=";//删除已发布文章
    private static final String UPLOADVIDEO = "https://api.weixin.qq.com/cgi-bin/media/uploadvideo?access_token=";//发送预览视频(视频群发)时的上传视频素材
    private static final String PREVIEW = "https://api.weixin.qq.com/cgi-bin/message/mass/preview?access_token=";//视频预览
    private static final String SENDALL = "https://api.weixin.qq.com/cgi-bin/message/mass/sendall?access_token=";//视频、图文群发
    private static final String SENDALLDELETE = "https://api.weixin.qq.com/cgi-bin/message/mass/delete?access_token=";//群发视频删除

1:开发准备工作

(1):由于我调用微信API是通过http请求来的,所以需要一个HTTP请求工具类

​ 具体代码如下:

参数说明:json,传的参数

​ url:请求api地址

@Slf4j
public class HttpUtil {
    
    

 
    /**
     * 发送post请求
     * @param json 参数体
     * @param URL 请求地址
     * @return 返回结果
     */
    public static String sendPost(JSONObject json, String URL) {
    
    
        CloseableHttpClient client = HttpClients.createDefault();
        HttpPost post = new HttpPost(URL);
        post.setHeader("Content-Type", "application/json");
        post.setHeader("User-Agent", "Apipost client Runtime/+https://www.apipost.cn/");
        // post.addHeader("Authorization", "Basic YWRtaW46");
        String result;
        try {
    
    
            StringEntity s = new StringEntity(json.toString(), "utf-8");
            s.setContentType(new BasicHeader(HTTP.CONTENT_TYPE,
                    "application/json"));
            post.setEntity(s);
            // 发送请求
            HttpResponse httpResponse = client.execute(post);
            // 获取响应输入流
            InputStream inStream = httpResponse.getEntity().getContent();
            BufferedReader reader = new BufferedReader(new InputStreamReader( inStream, "utf-8"));
            StringBuilder strBuilder = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null)
                strBuilder.append(line + "\n");
            inStream.close();
            result = strBuilder.toString();
            if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
    
    
                log.info("请求服务器SUCCESS");
                // System.out.println("请求服务器成功,做相应处理");
            } else {
    
    
                log.info("请求服务端FALSE");
                // System.out.println("请求服务端失败");
            }
        } catch (Exception e) {
    
    
            log.error("请求异常EXCEPTION:"+e.getMessage());
            throw new RuntimeException(e);
        }
        return result;
    }
 
    /**
     * 发送get请求
     * @param url 请求URL
     * @param param 请求参数 key:value url携带参数 或者无参可不填
     * @return
     */
    public static String sendGet(String url, Map<String, String> param) {
    
    
 
        // 创建Httpclient对象
        CloseableHttpClient httpclient = HttpClients.createDefault();
 
        String resultString = "";
        CloseableHttpResponse response = null;
        try {
    
    
            // 创建uri
            URIBuilder builder = new URIBuilder(url);
            if (param != null) {
    
    
                for (String key : param.keySet()) {
    
    
                    builder.addParameter(key, param.get(key));
                }
            }
            URI uri = builder.build();
 
            // 创建http GET请求
            HttpGet httpGet = new HttpGet(uri);
 
            // 执行请求
            response = httpclient.execute(httpGet);
            // 判断返回状态是否为200
            if (response.getStatusLine().getStatusCode() == 200) {
    
    
                resultString = EntityUtils.toString(response.getEntity(), "UTF-8");
            }
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            try {
    
    
                if (response != null) {
    
    
                    response.close();
                }
                httpclient.close();
            } catch (IOException e) {
    
    
                e.printStackTrace();
            }
        }
        return resultString;
    }

 
}
(2)每个需要请求的接口都会用到token,所以我们需要一个获取token的公用方法,

获取token 所需要数据:APPID,SECRET(这两个参数会在申请过测试账号后得到)

获取token方法如下

  private static final String APPID = "wx0d1f0bdf5254710b";

    private static final String SECRET = "8af23658e88c12a087d27a531a9a86a9";

    private static final String TOKENURL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential";

/**
     * 获取token
     */
    public static String getAccessToken() throws HttpProcessException {
    
    
        String url=TOKENURL+"&appid="+APPID+"&secret="+SECRET;
        System.out.println(url);
        String result = HttpClientUtil.get(HttpConfig.custom().url(url));
        JSONObject resultObj = JSON.parseObject(result);
        if (!resultObj.containsKey("access_token")) {
    
    
            throw new RuntimeException(result);
        }
        String token = resultObj.getString("access_token");
        return token;
    }

2:开始开发

所涉及请求的api以及需要传的参数可以根据微信公众号开发文档进行借鉴,

参考地址:链接地址

特别说明:

微信公众号所涉及的图文群发,新闻等里面所引用的图片地址必须是微信服务器下的才能识别到,如果图片或则视频存在其他服务器上是访问不到的,需要把这些素材上传到微信公众号素材里面,然后根据这些返回的mediaid,链接地址在内容里面进行替换

扫描二维码关注公众号,回复: 16624861 查看本文章
(1):上传素材(分为临时素材和永久素材,由于临时素材上传后在内容里面也无法引用,所以只展示上传永久素材的方法)
  /**
     * 上传永久素材
     * @param	file
     * @param	type
     * @param	title type为video时需要,其他类型设null
     * @param	introduction type为video时需要,其他类型设null
     * @return	{"media_id":MEDIA_ID,"url":URL}
     */
    public static String uploadPermanentMaterial(File file, String type, String title, String introduction) throws HttpProcessException {
    
    

        String access_token = getAccessToken();
        String url = ADDMATERIAL + access_token + "&type=" + type;
        String result = null;
        try {
    
    
            URL uploadURL = new URL(url);

            HttpURLConnection conn = (HttpURLConnection) uploadURL.openConnection();
            conn.setConnectTimeout(5000);
            conn.setReadTimeout(30000);
            conn.setDoOutput(true);
            conn.setDoInput(true);
            conn.setUseCaches(false);
            conn.setRequestMethod("POST");
            conn.setRequestProperty("Connection", "Keep-Alive");
            conn.setRequestProperty("Cache-Control", "no-cache");
            String boundary = "-----------------------------" + System.currentTimeMillis();
            conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);

            OutputStream output = conn.getOutputStream();
            output.write(("--" + boundary + "\r\n").getBytes());
            output.write(String.format("Content-Disposition: form-data; name=\"media\"; filename=\"%s\"\r\n", file.getName()).getBytes());
            output.write("Content-Type: video/mp4 \r\n\r\n".getBytes());

            byte[] data = new byte[1024];
            int len = 0;
            FileInputStream input = new FileInputStream(file);
            while ((len = input.read(data)) > -1) {
    
    
                output.write(data, 0, len);
            }

            /*对类型为video的素材进行特殊处理*/
            if ("video".equals(type)) {
    
    
                output.write(("--" + boundary + "\r\n").getBytes());
                output.write("Content-Disposition: form-data; name=\"description\";\r\n\r\n".getBytes());
                output.write(String.format("{\"title\":\"%s\", \"introduction\":\"%s\"}", title, introduction).getBytes());
            }

            output.write(("\r\n--" + boundary + "--\r\n\r\n").getBytes());
            output.flush();
            output.close();
            input.close();

            InputStream resp = conn.getInputStream();

            StringBuffer sb = new StringBuffer();

            while ((len = resp.read(data)) > -1)
                sb.append(new String(data, 0, len, "utf-8"));
            resp.close();
            result = sb.toString();
        } catch (IOException e) {
    
    
            //....
        }

        return result;
    }

2:新闻、图文具体发布过程

1:需要借用新建草稿api将内容给创建到草稿箱里(在这个过程中,如果文章包含图片,需要把图片路径通过正则表达式给匹配出来,根据这些图片的地址将图片从公司服务器上传到微信服务器,得到一个图片地址,然后替换新闻内容,将图片引用到微信服务器的地址),然后通过草稿箱id进行发布、群发,这样我们才能看到

一键发布传参实体类

/**
 * TODO 类描述
 *
 * @author zhaoqilong
 * @version 1.0
 * @date 2022/10/26 16:05
 */
@Data
public class ContentEntity {
    
    

    /***
     * 标题
     */
    private String title;

    /***
     *  作者
     */
    private String author;

    /***
     * 图文消息的摘要,仅有单图文消息才有摘要,多图文此处为空。如果本字段为没有填写,则默认抓取正文前54个字。
     */
    private String digest;

    /***
     * 图文消息的具体内容,支持 HTML 标签,必须少于2万字符,小于1M,且此处会去除 JS ,涉及图片 url
     *  必须来源 "上传图文消息内的图片获取URL"接口获取。外部图片 url 将被过滤
     */
    private String content;

    /***
     * *图文消息的原文地址,即点击“阅读原文”后的URL
     */
    private String contentSourceUrl;

    /***
     * 封面图片服务器地址
     */
    private String thumbPath;

    /***
     *  图文消息的封面图片素材id(必须是永久MediaID)
     */
    private String thumbMediaId;

    /***
     * *Uint32 是否打开评论,0不打开(默认),1打开
     */
    private String needOpenComment;

    /***
     * *Uint32 是否粉丝才可评论,0所有人可评论(默认),1粉丝才可评论
     */
    private String onlyFansCanComment;


}

一键发布:

private static ObjectMapper objectMapper = new ObjectMapper();
    private static final String DRAFTADD = "https://api.weixin.qq.com/cgi-bin/draft/add?access_token=";//新建草稿文章
    private static final String PUBLISH = "https://api.weixin.qq.com/cgi-bin/freepublish/submit?access_token="; //正式发布文章
    private static final String PUBLISHRESULT = "https://api.weixin.qq.com/cgi-bin/freepublish/get?access_token=";//获取发布文章结果
    private static final String GETMATERIAL = "https://api.weixin.qq.com/cgi-bin/material/get_material?access_token="; //获取上传素材信息
    private static final String ARTICLEDELETE = "https://api.weixin.qq.com/cgi-bin/freepublish/delete?access_token=";//删除已发布文章
    private static final String UPLOADVIDEO = "https://api.weixin.qq.com/cgi-bin/media/uploadvideo?access_token=";//发送预览视频(视频群发)时的上传视频素材
    private static final String PREVIEW = "https://api.weixin.qq.com/cgi-bin/message/mass/preview?access_token=";//视频预览
    private static final String SENDALL = "https://api.weixin.qq.com/cgi-bin/message/mass/sendall?access_token=";//视频群发
    private static final String SENDALLDELETE = "https://api.weixin.qq.com/cgi-bin/message/mass/delete?access_token=";//群发视频删除

    /***
     * 一键新闻发布
     * 具体分为以下几个步骤
     * 1:先获取到新闻的图片地址
     * 2:通过图片地址将图片下载下来,然后上传到微信服务器
     * 3:将上传后返回的图文地址在新闻内容中进行替换
     * 4:替换后发布到草稿箱,获取到一个meia_id
     * 5:最后通过这个草稿id(media_id) 进行新闻发布
     * @param contentEntity
     * @return
     * @throws Exception
     */
    @RequestMapping(value = "/one-click-release", method = RequestMethod.POST)
    public String oneClickRelease(ContentEntity contentEntity) throws Exception {
    
    
        JSONArray eventArray = new JSONArray();
        JSONObject eventTraceInput = new JSONObject();
        String accessToken = WeiXinUtil.getAccessToken();
        String url = DRAFTADD + accessToken;
        String thumbPath = "";
        if (StringUtils.isNotBlank(contentEntity.getThumbPath())) {
    
    
            thumbPath = contentEntity.getThumbPath();
            Map<String, String> map = uploadImageLast(thumbPath);
            contentEntity.setThumbMediaId(map.get("media_id"));
            log.info("***************" + map.get("media_id"));
        } else {
    
    
            String content = contentEntity.getContent();
            if (StringUtils.isNotBlank(content)) {
    
    
                List<String> imgUrlList = ImageToolsUtil.listContentImgUrl(content);
                if (imgUrlList.size() > 0) {
    
    
                    for (String s : imgUrlList) {
    
    
                        Map<String, String> map = uploadImageLast(s);
                        contentEntity.setThumbMediaId(map.get("media_id"));
                        log.info("***************没有首页图" + map.get("media_id"));
                        break;
                    }
                }
            }
        }
        String content = contentEntity.getContent();
        if (StringUtils.isNotBlank(content)) {
    
    
            List<String> imgUrlList = ImageToolsUtil.listContentImgUrl(content);
            for (String s : imgUrlList) {
    
    
                log.info("*****************" + s);
                String s1 = s.replace(">", "");
                Map<String, String> map = uploadImageLast(s1);
                if (StringUtils.isNotBlank(map.get("url"))) {
    
    
                    content = content.replace(s, map.get("url"));
                }
            }
            contentEntity.setContent(content);
            log.info("替换内容后为" + content);
        }
        JSONObject jsonArray = new JSONObject();
        jsonArray.put("title", contentEntity.getTitle());//新闻标题
        jsonArray.put("author", contentEntity.getAuthor());//作者
        if(StringUtils.isBlank(contentEntity.getDigest())){
    
    
            contentEntity.setDigest(ContentUtil.summary(contentEntity.getContent()));
        }
        jsonArray.put("digest", contentEntity.getDigest());//摘要
        jsonArray.put("content", contentEntity.getContent());//内容
        jsonArray.put("content_source_url", contentEntity.getContentSourceUrl());//内容链接
        if (StringUtils.isNotBlank(contentEntity.getThumbMediaId())) {
    
    
            jsonArray.put("thumb_media_id", contentEntity.getThumbMediaId());//封面图片素材id
        }
        jsonArray.put("need_open_comment", contentEntity.getNeedOpenComment());//Uint32 是否打开评论,0不打开(默认),1打开
        jsonArray.put("only_fans_can_comment", contentEntity.getOnlyFansCanComment());//Uint32 是否粉丝才可评论,0所有人可评论(默认),1粉丝才可评论

        eventArray.add(jsonArray);
        eventTraceInput.put("articles", eventArray);
        // 发送请求
        String s1 = HttpUtil.sendPost(eventTraceInput, url);
        log.info("strResult:{}", s1);
        Gson gson1 = new Gson();
        Map<String, String> map1 = new HashMap<>();
        JSONObject strJson1 = JSONObject.parseObject(s1);
        map1 = gson1.fromJson(strJson1.toString(), map1.getClass());
//        String reslutCode = rePublish(map1.get("media_id"));//正式发布方法
        String result = sendNewsAll(map1.get("media_id"));//图文群发方法,参数是草稿id
        return result;
    }

    /***
     * * 群发图文
     * @param mediaId  草稿箱id
     * @return
     * @throws Exception
     */
    private String sendNewsAll(String mediaId) throws Exception {
    
    
        //获取access_token
        String accessToken = WeiXinUtil.getAccessToken();
        //根据media_id进行发送
        String url = SENDALL + accessToken;
        JSONObject eventTraceInput = new JSONObject();
        JSONObject eventTraceInput2 = new JSONObject();
        eventTraceInput2.put("is_to_all", true);
//        eventTraceInput2.put("tag_id","");//如果是全部群发,则这个群发标签不需要设置
        eventTraceInput.put("filter", eventTraceInput2);
        JSONObject eventTraceInput3 = new JSONObject();
        eventTraceInput3.put("media_id", mediaId);
        eventTraceInput.put("mpnews", eventTraceInput3);
        eventTraceInput.put("msgtype", "mpnews");
        eventTraceInput.put("send_ignore_reprint", "0");
        String result = HttpUtil.sendPost(eventTraceInput, url);
        return result;
    }

    /***
     * *正式发布新闻
     * @param mediaId 草稿箱文章id
     * @return
     * @throws HttpProcessException
     */
    private String rePublish(String mediaId) throws HttpProcessException {
    
    
        String accessToken = WeiXinUtil.getAccessToken();
        // 构造参数消息体
        JSONObject eventTraceInput = new JSONObject();
        String url = PUBLISH + accessToken;
        eventTraceInput.put("media_id", mediaId);
        // 发送请求
        String strResult = HttpUtil.sendPost(eventTraceInput, url);
        System.out.println(strResult);
        try {
    
    
            Map mapResult = objectMapper.readValue(strResult, Map.class);
            System.out.println(mapResult);

            if (null != mapResult && StringUtils.isNotBlank(mapResult.get("errcode").toString())) {
    
    
                if ("0".equals(mapResult.get("errcode").toString())) {
    
    
                    log.info("发布文章(草稿)SUCCESS,ERRCODE状态码:{}", 0);
                    return "发布成功";
                }
                if ("40001".equals(mapResult.get("errcode").toString())) {
    
    
                    return mapResult.get("errmsg").toString();
                } else {
    
    
                    return mapResult.get("errcode").toString() + ",错误信息:" + mapResult.get("errmsg").toString();
                }
            }
        } catch (IOException e) {
    
    
            log.error("解析失败...");
            e.printStackTrace();
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
        return "发布失败";
    }

    /***
     * 将文章内容中的图片上传到微信服务器
     * @param path 图片网络地址
     * @return
     * @throws Exception
     */
    private Map<String, String> uploadImageLast(String path) throws Exception {
    
    
        //上传素材
        //上传素材
        String pathLast = path;
        if (!path.endsWith(".jpg") && !path.endsWith(".png")) {
    
    
            pathLast = path.substring(0, path.lastIndexOf(".") + 4);
        }
        String suffix = pathLast.substring(pathLast.lastIndexOf(".") + 1, pathLast.length());
        File fileByUrl = WeiXinUtil.getFileByUrl(pathLast, suffix);
        String result = WeiXinUtil.uploadPermanentMaterial(fileByUrl, "image", null, null);
        Gson gson = new Gson();
        Map<String, String> map = new HashMap<>();
        JSONObject strJson = JSONObject.parseObject(result);
        System.out.println(result);
        map = gson.fromJson(strJson.toString(), map.getClass());
        return map;
    }

3: 群发视频(这个有个比较特殊的点就是,群发视频时上传视频的api地址并不是单单上传永久视频素材的那个api,在上传永久视频素材后会返回一个mediaId,然后根据这个mediaId再进行一次操作,在这个完成后会再得到一个meidaid,这个mediaid才是可以用来群发视频的有效mediaid)

具体可参考:

 /***
     * * 群发视频
     * @param videoPath  视频网络地址
     * @param title 视频标题
     * @param introduction 视频简介
     * @return
     * @throws Exception
     */
    @RequestMapping(value = "/sendall", method = RequestMethod.POST)
    public String sendvideoAll(@RequestParam String videoPath,
                               @RequestParam String title,
                               @RequestParam String introduction) throws Exception {
    
    
        //获取access_token
        String accessToken = WeiXinUtil.getAccessToken();
        String suffix = videoPath.substring(videoPath.lastIndexOf(".") + 1, videoPath.length());
        File fileByUrl = WeiXinUtil.getFileByUrl(videoPath, suffix);
        String postresult = WeiXinUtil.uploadVideo(fileByUrl, "video", title, introduction);
        Map maps = (Map) JSON.parse(postresult);
        String media1 = maps.get("media_id").toString();
        //通过media_id获取发送media_id
        String url = UPLOADVIDEO + accessToken;
        JSONObject eventTraceInput = new JSONObject();
        eventTraceInput.put("media_id", media1);
        eventTraceInput.put("title", title);
        eventTraceInput.put("description", introduction);
        String result = HttpUtil.sendPost(eventTraceInput, url);
        Map maps2 = (Map) JSON.parse(result);
        String media2 = maps2.get("media_id").toString();
        //根据media_id进行发送
        String url2 = SENDALL + accessToken;
        JSONObject eventTraceInput1 = new JSONObject();
        JSONObject eventTraceInput2 = new JSONObject();
        eventTraceInput2.put("is_to_all", true);
//        eventTraceInput2.put("tag_id","");//如果是全部群发,则这个群发标签不需要设置
        eventTraceInput1.put("filter", eventTraceInput2);
        JSONObject eventTraceInput3 = new JSONObject();
        eventTraceInput3.put("media_id", media2);
        eventTraceInput1.put("mpvideo", eventTraceInput3);
        eventTraceInput1.put("msgtype", "mpvideo");
        String result2 = HttpUtil.sendPost(eventTraceInput1, url2);
        return result2;
    }

4:群发视频、文章删除

 /***
     * 删除群发消息
     * @param msgId 消息id  当和文章地址连用时,以消息id为准
     * @param videoUrl 文章地址
     * @return
     * @throws HttpProcessException
     */
    @RequestMapping(value = "/sendall-delete", method = RequestMethod.POST)
    public String sendAllDelete(@RequestParam String msgId, @RequestParam(required = false, defaultValue = "") String videoUrl) throws HttpProcessException {
    
    
        //获取access_token
        String accessToken = WeiXinUtil.getAccessToken();
        String url = SENDALLDELETE + accessToken;
        JSONObject eventTraceInput = new JSONObject();
        eventTraceInput.put("msg_id", msgId);
//        eventTraceInput.put("article_idx",1); //要删除的文章在图文消息中的位置
        eventTraceInput.put("url", videoUrl);
        String result = HttpUtil.sendPost(eventTraceInput, url);
        return result;
    }

猜你喜欢

转载自blog.csdn.net/qq_44794782/article/details/129791336
今日推荐