Java enables one-click publishing of images, texts and videos on WeChat public accounts

WeChat public account development

1. Preparation work

Apply for a test account on the WeChat development platform (prefer this one). It would be best if you can use a certified subscription account. However, many of our functions are for testing during the development stage. Applying for a test account is completely sufficient. Your personal subscription account
is You cannot apply for certification, so many WeChat APIs cannot be used.

2. Start development

written in front

Project dependencies

  <!-- 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>
All involved WeChat API interface addresses
    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: Development preparations

(1): Since I call the WeChat API through http requests, I need an HTTP request tool class

The specific code is as follows:

Parameter description: json, passed parameters

url: request api address

@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) Every interface that requires a request will use token, so we need a public method to obtain token.

Data required to obtain token: APPID, SECRET (these two parameters will be obtained after applying for a test account)

The method to obtain the token is as follows

  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: Start development

The API involved in the request and the parameters that need to be passed can be used for reference based on the WeChat official account development documents.

Reference address: link address

Special Note:

The image addresses referenced in the mass posting of pictures and texts, news, etc. involved in the WeChat public account must be under the WeChat server to be recognized. If the pictures or videos are stored on other servers, they will not be accessible. These materials need to be uploaded to WeChat. In the public account material, and then replace it in the content based on the returned mediaid and link address.

(1): Upload materials (divided into temporary materials and permanent materials. Since temporary materials cannot be referenced in the content after uploading, only the method of uploading permanent materials is shown)
  /**
     * 上传永久素材
     * @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: Specific release process of news, pictures and texts

1: You need to use the new draft API to create the content into the draft box (in this process, if the article contains pictures, you need to match the picture paths through regular expressions, and upload the pictures from the company server to WeChat server, get a picture address, then replace the news content, reference the picture to the WeChat server address), and then publish and mass-send through the draft box ID so that we can see it

Publish parameter-passing entity classes with one click

/**
 * 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;


}

One-click publishing:

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: Group video (a special point of this is that when group video is uploaded, the api address of the video upload is not the api that only uploads the permanent video material. After uploading the permanent video material, a mediaId will be returned, and then the process will be carried out based on this mediaId. One operation, after this is completed, you will get another meidaid. This mediaid is the effective mediaid that can be used to send videos in groups)

For details, please refer to:

 /***
     * * 群发视频
     * @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: Group videos and articles deleted

 /***
     * 删除群发消息
     * @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;
    }

Guess you like

Origin blog.csdn.net/qq_44794782/article/details/129791336