微信公众号开发

一、注册公众号

  • 官网网址:https://developers.weixin.qq.com/doc/offiaccount/Getting_Started/Overview.html
  • 微信公众平台,点击注册,通过邮箱注册成功后会看到如下画面
    微信公众号开发_第1张图片
    • 在这里,选择类型时要注意下。如果你是个人开发的话只能选择订阅号,订阅号没有自定义菜单等接口,具体接口权限你可以登录公众平台后在开发—>接口权限中看到。如果你想拥有自定义菜单等接口,需要注册服务号,但是服务号只能企业、组织等注册

二、配置公众号

1、成为开发者

  • 开发-基本设置
    • 勾选协议成为开发者
    • 点击“修改配置”按钮
    • 服务器地址(URL):http://fil.tsingzou.com:80/officialAccounts/verify 验证接口
    • 令牌(Token):zzx 开发者自定的验证口令,用作生成签名(该Token会和接口URL中包含的Token进行比对,从而验证安全性)
    • 消息加解密密钥(EncodingAESKey):随机字符串,如果消息加解密方式采用安全模式将用作消息体加解密密钥
    • 消息加解密方式:明文模式、兼容模式和安全模式
    • 当点击确定,会调用填写的服务器地址接口(get),用于验证消息是否可以实互通
  • 验证消息的确来自微信服务器
    • 开发者提交信息后,微信服务器将发送GET请求到填写的服务器地址URL上,GET请求携带参数如下表所示
      • signature:微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。
      • timestamp:时间戳
      • nonce:随机数
      • echostr:随机字符串
  • 验证接口需要后台服务器自定义编写
    • 开发者通过检验signature对请求进行校验。若确认此次GET请求来自微信服务器,请原样返回echostr参数内容,则接入生效,成为开发者成功,否则接入失败
      • 将token、timestamp、nonce三个参数进行字典序排序
      • 将三个参数字符串拼接成一个字符串进行sha1加密
      • 开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
package com.grea.qz.controller.other;

import org.apache.log4j.Logger;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.util.Date;

@RestController
@RequestMapping("/officialAccounts")
public class OfficialAccountsController {

    private static Logger logger = Logger.getLogger(OfficialAccountsController.class);

    /**
     * 验证微信公众号接入
     * @param signature 签名
     * @param timestamp 时间戳
     * @param nonce 随机数
     * @param echostr 随机字符串
     * @param response
     */
    @GetMapping(value = "verify")
    public void verify(String signature, String timestamp, String nonce, String echostr, HttpServletResponse response) {
        try {
            if (SignUtil.checkSignature(signature, timestamp, nonce)) {
                PrintWriter out = response.getWriter();
                out.print(echostr);
                out.close();
            } else {
                logger.error("这里存在非法请求!");
            }
        } catch (Exception e) {
            logger.error(e, e);
        }
    }

    /**
     * 接受用户发送的消息
     * @param officialAccountsMessageVo 解析传递回来的xml
     * @param request
     * @param response
     */
    @PostMapping(value = "verify")
    public Object  verify(@RequestBody OfficialAccountsMessageVo officialAccountsMessageVo, HttpServletRequest request, HttpServletResponse response) {
        Object messageVo = new Object();
        String msgType = officialAccountsMessageVo.getMsgType();
        //根据类型设置不同的消息数据
        if("text".equals(msgType)){
//            messageVo = setTextMessage("文本内容", officialAccountsMessageVo);
        }else if("image".equals(msgType)){
            messageVo = setPictureMessage(officialAccountsMessageVo);
        } else if("subscribe".equals(msgType)){
//            messageVo = setTextMessage("感谢你的关注!", officialAccountsMessageVo);
        }
        return messageVo;
    }

    /**
     * 具体返回的内容根据实际业务处理
     * @param officialAccountsMessageVo
     * @return
     */
    public OfficialAccountsSendPictureMessageVo setPictureMessage(OfficialAccountsMessageVo officialAccountsMessageVo) {
        OfficialAccountsSendPictureMessageVo messageVo = new OfficialAccountsSendPictureMessageVo();  //创建消息响应对象
        messageVo.setToUserName(officialAccountsMessageVo.getFromUserName());
        messageVo.setFromUserName(officialAccountsMessageVo.getToUserName());
        messageVo.setMsgType(officialAccountsMessageVo.getMsgType());
        messageVo.setCreateTime(new Date().getTime());
        messageVo.setMediaId(new String[]{officialAccountsMessageVo.getMediaId()});
        messageVo.setMediaId();
        return messageVo;
    }

//    public OfficialAccountsSendTextMessageVo setTextMessage(String content, OfficialAccountsMessageVo officialAccountsMessageVo) {
//        OfficialAccountsSendTextMessageVo messageVo = new OfficialAccountsSendTextMessageVo();  //创建消息响应对象
//        messageVo.setToUserName(officialAccountsMessageVo.getFromUserName());
//        messageVo.setFromUserName(officialAccountsMessageVo.getToUserName());
//        messageVo.setMsgType(officialAccountsMessageVo.getMsgType());
//        messageVo.setCreateTime(new Date().getTime());
//        messageVo.setContent(content);
//        return messageVo;
//    }
}

2、 依据接口文档实现业务逻辑

  • 验证 URL 有效性成功后即接入生效,成为开发者。你可以在公众平台网站中申请微信认证,认证成功后,将获得更多接口权限,满足更多业务需求。
  • 成为开发者后,用户每次向公众号发送消息、或者产生自定义菜单、或产生微信支付订单等情况时,开发者填写的服务器配置 URL 将得到微信服务器推送过来的消息和事件,开发者可以依据自身业务逻辑进行响应,如回复消息。
  • 公众号调用各接口时,一般会获得正确的结果,具体结果可见对应接口的说明。返回错误时,可根据返回码来查询错误原因。全局返回码说明
  • 用户向公众号发送消息时,公众号方收到的消息发送者是一个OpenID,是使用用户微信号加密后的结果,每个用户对每个公众号有一个唯一的OpenID。
  • 此外,由于开发者经常有需在多个平台(移动应用、网站、公众帐号)之间共通用户帐号,统一帐号体系的需求,微信开放平台(open.weixin.qq.com)提供了 UnionID 机制。开发者可通过 OpenID 来获取用户基本信息,而如果开发者拥有多个应用(移动应用、网站应用和公众帐号,公众帐号只有在被绑定到微信开放平台帐号下后,才会获取UnionID),可通过获取用户基本信息中的 UnionID 来区分用户的唯一性,因为只要是同一个微信开放平台帐号下的移动应用、网站应用和公众帐号,用户的 UnionID 是唯一的。换句话说,同一用户,对同一个微信开放平台帐号下的不同应用,UnionID是相同的。详情请在微信开放平台的资源中心 - 移动应用开发 - 微信登录 - 授权关系接口调用指引 - 获取用户个人信息(UnionID机制)中查看。
  • 另请注意,微信公众号接口必须以http://或https://开头,分别支持80端口和443端口。

三、消息接收能力

3.1、接收普通消息

当普通微信用户向公众账号发消息时,微信服务器将POST消息的XML数据包到开发者填写的URL(验证接口)上

  • 关于重试的消息排重,推荐使用msgid排重

  • 微信服务器在五秒内收不到响应会断掉连接,并且重新发起请求,总共重试三次。假如服务器无法保证在五秒内处理并回复,可以直接回复空串(被动回复消息),微信服务器不会对此作任何处理,并且不会发起重试。

  • 消息样例

    <xml>
      	<ToUserName>ToUserName>
     	 <FromUserName>FromUserName>
     	 <CreateTime>1348831860CreateTime>
     	 <MsgType>MsgType>
     	 <Content>Content>
     	 <MsgId>1234567890123456MsgId>
       xml>
    
    • 公用的参数
      • ToUserName:开发者微信号(可以理解为要发送到公众号)
      • FromUserName:发送方账号(一个openID)
        • openID:用户与当前公众号交互的唯一标志
      • CreateTime:消息创建时间
      • MsgType:用户发送的消息的类型
        • text 文本消息 image 图片消息 voice 语音消息 video 视频消息 music 音乐消息 event 事件
      • MsgId:消息id,64位整型可以用于消息排重,如果不做消息排重,那么用户可能就收到多条相同的响应消息
  • 消息类型

    • 文本消息
    • 图片消息
      • PicUrl:图片链接(由系统生成)
      • MediaId:图片消息媒体id,可以调用获取临时素材接口拉取数据
    • 语音消息
      • MediaId:语音消息媒体id,可以调用获取临时素材接口拉取数据
      • Format:语音格式,如amr,speex等
      • Recognition:语音识别结果,UTF8编码
    • 视频消息
      • MediaId:视频消息媒体id,可以调用获取临时素材接口拉取数据。
      • ThumbMediaId:视频消息缩略图的媒体id,可以调用多媒体文件下载接口拉取数据
    • 小视频消息
      • MediaId:视频消息媒体id,可以调用获取临时素材接口拉取数据。
      • ThumbMediaId:视频消息缩略图的媒体id,可以调用多媒体文件下载接口拉取数据
    • 地理位置消息
      • Location_X:地理位置纬度
      • Location_Y:地理位置经度
      • Scale:地图缩放大小
      • Label:地理位置信息
    • 连接消息
      • Title:消息标题
      • Description:消息描述
      • Url:消息链接

公众号消息Vo

package com.grea.qz.controller.other;

import lombok.Data;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

/**
 * 公众号消息Vo
 *  可以抽象一个父类,定义多种不同类型的子类Vo
 */
@Data
@XmlRootElement(name="xml") //用于知名xml的根元素
@XmlAccessorType(XmlAccessType.FIELD) //映射所有字段到XML
public class OfficialAccountsMessageVo {

///事件&内容通用部分//
    // 开发者微信号
    @XmlElement(name="FromUserName") //如果定义的字段名和xml的元素名不一致,可以使用此注解
    protected String FromUserName;

    // 发送方帐号(一个OpenID)
    protected String ToUserName;

    // 消息创建时间
    protected Long CreateTime;

    /**
     * 消息类型
     * text 文本消息
     * image 图片消息
     * voice 语音消息
     * video 视频消息
     * music 音乐消息
     *
     * 事件的类型
     * subscribe 订阅
     * unsubscribe  取消订阅
     * subscribe  未关注扫描带参数的二维码
     * SCAN  已关注扫描带参数的二维
     * LOCATION  报地理位置
     * CLICK  点击菜单拉取消息时的事件推送
     * VIEW  点击菜单跳转链接时的事件推送
     */
    protected String MsgType;


///内容部分

    // 消息id
    protected Long MsgId;

    // 文本内容
    private String Content;

    // 图片链接(由系统生成)
    private String PicUrl;

    // 图片(语音、视频)消息媒体id,可以调用获取临时素材接口拉取数据
    private String MediaId;

    //语音格式,如amr,speex等
    private String Format;

    //语音识别结果,UTF8编码
    private String Recognition;

    //视频消息缩略图的媒体id,可以调用多媒体文件下载接口拉取数据
    private String ThumbMediaId;

    //地理位置纬度
    private String Location_X;

    //地理位置经度
    private String Location_Y;

    //地图缩放大小
    private String Scale;

    //地理位置信息
    private String Label;

    //消息标题
    private String Title;

    //消息描述
    private String Description;

    //消息链接
    private String Url;

事件部分///
    /**
     * 事件类型
     * subscribe(订阅)
     * unsubscribe(取消订阅)
     * subscribe(未关注扫描带参数的二维码)
     * SCAN(已关注扫描带参数的二维码)
     * LOCATION(上报地理位置)
     * CLICK(点击菜单拉取消息时的事件推送)
     * VIEW(点击菜单跳转链接时的事件推送)
     */
    private String Event;

    //1、用户未关:事件KEY值,qrscene_为前缀,后面为二维码的参数值
    //2、用户已关注:事件KEY值,是一个32位无符号整数,即创建二维码时的二维码scene_id
    //3、点击菜单拉取消息:事件KEY值,与自定义菜单接口中KEY值对应
    //4、点击菜单跳转链接:事件KEY值,设置的跳转URL
    private String EventKey;

    //二维码的ticket,可用来换取二维码图片
    private String Ticket;

    //地理位置纬度
    private String Latitude;

    //地理位置经度
    private String Longitude;

    //地理位置精度
    private String Precision;
}

公众号签名验证

package com.grea.qz.controller.other;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;

/**
 * @author zzx
 * @version date:2021年1月22日 下午2:50:43
 * @description :公众号签名验证
 */
public class SignUtil {
    // 与接口配置信息中的 Token 要一致
    private final static String token = "zzx";

    /**
     * 验证签名
     *
     * @param signature
     * @param timestamp
     * @param nonce
     * @return
     */
    public static boolean checkSignature(String signature, String timestamp, String nonce) throws NoSuchAlgorithmException {
        String[] arr = new String[]{token, timestamp, nonce};
        // 将 token、timestamp、nonce 三个参数进行字典序排序
        Arrays.sort(arr);
        StringBuilder content = new StringBuilder();
        for (int i = 0; i < arr.length; i++) {
            content.append(arr[i]);
        }
        MessageDigest md;
        String tmpStr = null;
        md = MessageDigest.getInstance("SHA-1");
        // 将三个参数字符串拼接成一个字符串进行 sha1 加密
        byte[] digest = md.digest(content.toString().getBytes());
        tmpStr = byteToStr(digest);

        // 将 sha1 加密后的字符串可与 signature 对比,标识该请求来源于微信
        return tmpStr != null ? tmpStr.equals(signature.toUpperCase()) : false;
    }

    /**
     * 将字节数组转换为十六进制字符串
     *
     * @param byteArray
     * @return
     */
    private static String byteToStr(byte[] byteArray) {
        String strDigest = "";
        for (int i = 0; i < byteArray.length; i++) {
            strDigest += byteToHexStr(byteArray[i]);
        }
        return strDigest;
    }

    /**
     * 将字节转换为十六进制字符串
     *
     * @param mByte
     * @return
     */
    private static String byteToHexStr(byte mByte) {
        char[] Digit = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
        char[] tempArr = new char[2];
        tempArr[0] = Digit[(mByte >>> 4) & 0X0F];
        tempArr[1] = Digit[mByte & 0X0F];
        String s = new String(tempArr);
        return s;
    }
}

3.2、接收事件推送

  • 公用的参数
    • ToUserName:开发者微信号(可以理解为要发送到公众号)
    • FromUserName:发送方账号(一个openID)
    • CreateTime:消息创建时间
    • MsgType:用户发送的消息的类型
    • Event:事件类型
      • subscribe(订阅)、unsubscribe(取消订阅)、subscribe(未关注扫描带参数的二维码)、SCAN(已关注扫描带参数的二维码)
      • LOCATION(上报地理位置)、CLICK(点击菜单拉取消息时的事件推送)、VIEW(点击菜单跳转链接时的事件推送)
  • 关注/取消关注事件
  • 扫描带参数二维码事件
    • 如果用户还未关注公众号,则用户可以关注公众号,关注后微信会将带场景值关注事件推送给开发者。
    • 如果用户已经关注公众号,则微信会将带场景值扫描事件推送给开发者
    • 用户未关注时,进行关注后的事件推送
      • EventKey:事件KEY值,qrscene_为前缀,后面为二维码的参数值
      • Ticket:二维码的ticket,可用来换取二维码图片
    • 用户已关注时的事件推送
      • EventKey:事件KEY值,是一个32位无符号整数,即创建二维码时的二维码scene_id
      • Ticket:二维码的ticket,可用来换取二维码图片
  • 上报地理位置事件
    • Latitude:地理位置纬度
    • Longitude:地理位置经度
    • Precision:地理位置精度
  • 自定义菜单事件
    • 点击菜单拉取消息时的事件推送
      • EventKey:事件KEY值,与自定义菜单接口中KEY值对应
    • 点击菜单跳转链接时的事件推送
      • EventKey:事件KEY值,设置的跳转URL

四、消息推送能力

4.1、被动回复用户消息

  • 公用的参数

    • ToUserName:接收方帐号(收到的OpenID)
    • FromUserName:开发者微信号
    • CreateTime:消息创建时间 (整型)
    • MsgType:消息类型:和消息接受一致,可以直接返回接收到消息的类型
  • 回复文本消息

    • Content:回复的消息内容(换行:在content中能够换行,微信客户端就支持换行显示)
  • 回复图片消息

    • 回复的图片、语音等xml格式稍有不同,含有三级子标签(具体参考官方文档)
    <xml>
      	<ToUserName>ToUserName>
      	<FromUserName>FromUserName>
    	  <CreateTime>12345678CreateTime>
    	  <MsgType>MsgType>
    	  <Image>
      	     <MediaId>MediaId>
     	 Image>
        xml>
    
    • Image-MediaId:通过素材管理中的接口上传多媒体文件,得到的id
  • 回复语音消息

    • Voice-MediaId:通过素材管理中的接口上传多媒体文件,得到的id
  • 回复视频消息

    • Video-MediaId:通过素材管理中的接口上传多媒体文件,得到的id
    • Video-Title:视频消息的标题
    • Video-Description:视频消息的描述
  • 回复音乐消息

    • Music-Title:音乐标题
    • Music-Description:音乐描述
    • Music-MusicURL:音乐链接
    • Music-HQMusicUrl:高质量音乐链接,WIFI环境优先使用该链接播放音乐
    • Music-ThumbMediaId:缩略图的媒体id,通过素材管理中的接口上传多媒体文件,得到的id
  • 回复图文消息

    • ArticleCount:图文消息个数;当用户发送文本、图片、语音、视频、图文、地理位置这六种消息时,开发者只能回复1条图文消息;其余场景可回复8条
    • Articles:图文消息信息,注意,如果图文数超过限制,则将只发限制内的条数
    • Title:图文消息标题
    • Description:图文消息描述
    • PicUrl:图片链接,支持JPG、PNG格式,较好的效果为大图360200,小图200200
    • Url:点击图文消息跳转链接

4.2、主动模板消息推送

  • 说明

    • 服务号订阅通知功能开启灰度测试,模板消息能力可正常使用
    • 可以在微信后台修改,也可以通过接口修改
  • 设置所属行业

    • http请求方式: POST https://api.weixin.qq.com/cgi-bin/template/api_set_industry?access_token=ACCESS_TOKEN
    • 行业编号参考网址:https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Template_Message_Interface.html#0
      • 参数1:industry_id1:公众号模板消息所属行业编号
      • 参数2:industry_id2:公众号模板消息所属行业编号
    • 行业编号参考网址:https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Template_Message_Interface.html#0
  • 获取设置的行业信息

    • http请求方式:GET https://api.weixin.qq.com/cgi-bin/template/get_industry?access_token=ACCESS_TOKEN
  • 获得模板ID

    • http请求方式: POST https://api.weixin.qq.com/cgi-bin/template/api_add_template?access_token=ACCESS_TOKEN
    • 参数:template_id_short:模板库中模板的编号,有“TM**”和“OPENTMTM**”等形式
  • 获取模板列表

    • http请求方式:GET https://api.weixin.qq.com/cgi-bin/template/get_all_private_template?access_token=ACCESS_TOKEN

      {	
           "template_list": [{
            "template_id": "iPk5sOIt5X_flOVKn5GrTFpncEYTojx6ddbt8WYoV5s",
            "title": "恭喜你购买成功!",
            "primary_industry": "消费品",
            "deputy_industry": "消费品",
            "content": "{{first.value}}\n名称: {{keyword1.value}}\n消费金额: {{keyword2.value}}\n购买时间:  {{keyword3.value}}\n{{remark.value}}",
            "example": "恭喜你购买成功!\n名称:巧克力\n消费金额:39.8元\n购买时间:2013-10-10 12:22:22\n欢迎再次购买!"
         }]
      }
      
  • 删除模板

    • http请求方式:POST https://api.weixin.qq.com/cgi-bin/template/del_private_template?access_token=ACCESS_TOKEN
      • 参数:template_id:公众帐号下模板消息ID
  • 发送模板消息

    • http请求方式: POST https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=ACCESS_TOKEN
      • touser:接收者openid
      • template_id:模板ID
      • url(非必填):模板跳转链接(海外帐号没有跳转能力)
      • miniprogram(非必填):跳小程序所需数据,不需跳小程序可不用传该数据
      • appid:所需跳转到的小程序appid(该小程序appid必须与发模板消息的公众号是绑定关联关系,暂不支持小游戏)
      • pagepath(非必填):所需跳转到小程序的具体页面路径,支持带参数,(示例index?foo=bar),要求该小程序已发布,暂不支持小游戏
      • data:模板数据
      • color(非必填):模板内容字体颜色,不填默认为黑色
{
           "touser":"OPENID",
           "template_id":"ngqIpbwh8bUfcSsECmogfXcV14J0tQlEpBO27izEYtY",
           "url":"http://weixin.qq.com/download",  
           "miniprogram":{
             "appid":"xiaochengxuappid12345",
             "pagepath":"index?foo=bar"
           },          
           "data":{
                   "first": {
                       "value":"恭喜你购买成功!",
                       "color":"#173177"
                   },
                   "keyword1":{
                       "value":"巧克力",
                       "color":"#173177"
                   },
                   "keyword2": {
                       "value":"39.8元",
                       "color":"#173177"
                   },
                   "keyword3": {
                       "value":"2013-10-10 12:22:22",
                       "color":"#173177"
                   },
                   "remark":{
                       "value":"欢迎再次购买!",
                       "color":"#173177"
                   }
           }
       }
  • 事件推送
    • 在模版消息发送任务完成后,微信服务器会将是否送达成功作为通知,发送到开发者中心中填写的服务器配置地址中
      • 除了特定的几个元素之外
        • Event:事件为模板消息发送结束
        • Status:发送状态

4.3、 消息群发

  • 上传图文消息素材
  • http请求方式: POST https://api.weixin.qq.com/cgi-bin/media/uploadnews?access_token=ACCESS_TOKEN
    • Articles:图文消息,一个图文消息支持1到8条图文

    • thumb_media_id:图文消息缩略图的media_id,可以在素材管理-新增素材中获得

    • author:图文消息的作者

    • title:图文消息的标题

    • content_source_url:在图文消息页面点击“阅读原文”后的页面,受安全限制,如需跳转Appstore,可以使用itun.es或appsto.re的短链服务,并在短链后增加 #wechat_redirect 后缀。

    • content:图文消息页面的内容,支持HTML标签、插入小程序卡片,具备微信支付权限的公众号,可以使用a标签

      • data-miniprogram-appid:程序的AppID
      • data-miniprogram-path:小程序要打开的路径
      • data-miniprogram-title:小程序卡片的标题,不超过35个字
      • data-miniprogram-imageurl:小程序卡片的封面图链接,图片必须为1080*864像素
        微信公众号开发_第2张图片
    • digest:图文消息的描述,如本字段为空,则默认抓取正文前64个字

    • show_cover_pic:是否显示封面,1为显示,0为不显示

    • need_open_comment:Uint32 是否打开评论,0不打开,1打开

    • only_fans_can_comment:Uint32 是否粉丝才可评论,0所有人可评论,1粉丝才可评论

五、素材管理

  • 详见MediaIdGenerateUtil
  • 主要是用于获取返回的media_id
  • 上传临时、永久素材
    • 上传素材也相当于群发
  • 下载素材
  • 删除永久素材
  • 修改素材

MediaIdGenerateUtil

package com.grea.qz.controller.other;

import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;

import javax.net.ssl.HttpsURLConnection;
import java.io.*;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 公众号发送的图片、视频信息所需要的media的id
 */

public class MediaIdGenerateUtil {

    /**
     * 上传永久图文素材
     * Map map = new HashMap<>();
     *         map.put("title", "标题");
     *         map.put("thumb_media_id", "J49eq_VE823b_wZH3Op4DFkLa4Lm4jkTSxX_VbiBWhY"); //上传图片中获取到的media_id
     *         map.put("author", "作者");
     *         map.put("digest", "图文消息的摘要,仅有单图文消息才有摘要,多图文此处为空。如果本字段为没有填写,则默认抓取正文前64个字。");
     *         map.put("show_cover_pic", "1");//显示封面 0/1
     *         map.put("content", "\"图文消息的具体内容,支持HTML标签,必须少于2万字符,小于1M," +
     *                 "且此处会去除JS,涉及图片url必须来源 \"上传图文消息内的图片获取URL\"接口获取。" +
     *                 "外部图片url将被过滤。\"");
     *         map.put("content_source_url", "https://www.baidu.com/");//图文消息的原文地址,即点击“阅读原文”后的URL
     *         map.put("need_open_comment", "1");//Uint32  是否打开评论,0不打开,1打开
     *         map.put("only_fans_can_comment", "1");//Uint32 是否粉丝才可评论,0所有人可评论,1粉丝才可评论
     * @param articles 内容体
     * @param accessToken
     * @param spammers true:群发   false:上传素材
     * @return
     * @throws Exception
     */
    public static String uploadPermanentMaterial(List<Map<String, Object>> articles, String accessToken, boolean spammers) throws Exception {
        Map<String, Object> body = new HashMap<>();
        body.put("articles", articles);
        String url = "https://api.weixin.qq.com/cgi-bin/material/add_news?access_token=%s";
        if(spammers) {
            url = "https://api.weixin.qq.com/cgi-bin/media/uploadnews?access_token=%s";
        }
        url = String.format(url, accessToken);
        String result = HttpUtil.post(url, body);
        return result;
    }

    /**
     * 上传图文消息内的图片获取URL
     *          可用于后续群发中,放置到图文消息中
     *
     * @param filePath    文件路径
     * @param accessToken
     * @return
     * @throws Exception
     */
    public static String uploadMaterial(String filePath, String accessToken) throws Exception {
        String urlStr = "https://api.weixin.qq.com/cgi-bin/media/uploadimg?access_token=%s";
        urlStr = String.format(urlStr, accessToken);
        return getUploadResult(filePath, urlStr, false);
    }

    /**
     * 上传素材
     *
     * @param filePath    文件路径
     *                    媒体文件类型,分别有图片(image)、语音(voice)、视频(video)和缩略图(thumb)
     * @param type        素材类型
     * @param accessToken
     * @param temp 临时素材
     * @return
     * @throws Exception
     */
    public static String uploadMaterial(String filePath, String type, String accessToken, Boolean temp) throws Exception {
        String urlStr = "https://api.weixin.qq.com/cgi-bin/media/upload?access_token=%s&type=%s";
       if(!temp) {
           urlStr = "https://api.weixin.qq.com/cgi-bin/material/add_material?access_token=%s&type=%s";
           if("video".equals(type)) {
               urlStr = String.format(urlStr, accessToken, type);
               return getUploadResult(filePath, urlStr, true);
           }
       }
        urlStr = String.format(urlStr, accessToken, type);
        return getUploadResult(filePath, urlStr, false);
    }

    private static String getUploadResult(String filePath, String urlStr, boolean PermanentVideo) throws Exception {
        String result = null;
        File file = new File(filePath);
        if (!file.exists() || !file.isFile()) throw new IOException("文件不存在");
        URL url = new URL(urlStr);
        HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
        conn.setRequestMethod("POST");//以POST方式提交表单
        conn.setDoInput(true);
        conn.setDoOutput(true);
        conn.setUseCaches(false);//POST方式不能使用缓存
        //设置请求头信息
        conn.setRequestProperty("Connection", "Keep-Alive");
        conn.setRequestProperty("Charset", "UTF-8");
        //设置边界
        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=\"media\"; filename=\"" + file.getName() + "\"\r\n");
        sb.append("Content-Type: application/octet-stream\r\n\r\n");
        if(PermanentVideo) {
            sb.append("Content-Disposition: form-data; name=\"description\";\r\n\r\n");
            //title:视频素材的标题  introduction:视频素材的描述
            sb.append(String.format("{\"title\":\"%s\", \"introduction\":\"%s\"}","title", "introduction"));
            sb.append(("\r\n--" + boundary + "--\r\n\r\n"));
        }
        //获得输出流
        OutputStream out = new DataOutputStream(conn.getOutputStream());
        //输出表头
        out.write(sb.toString().getBytes("UTF-8"));
        //文件正文部分
        //把文件以流的方式 推送道URL中
        DataInputStream din = new DataInputStream(new FileInputStream(file));
        int bytes = 0;
        byte[] buffer = new byte[1024];
        while ((bytes = din.read(buffer)) != -1) {
            out.write(buffer, 0, bytes);
        }
        din.close();
        //结尾部分
        byte[] foot = ("\r\n--" + boundary + "--\r\n").getBytes("UTF-8");//定义数据最后分割线
        out.write(foot);
        out.flush();
        out.close();
        if (HttpsURLConnection.HTTP_OK == conn.getResponseCode()) {
            StringBuffer strBuffer = new StringBuffer();
            BufferedReader reader = null;
            String lineString;
            try {
                reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
                while ((lineString = reader.readLine()) != null) {
                    strBuffer.append(lineString);
                }
                if (result == null) {
                    result = strBuffer.toString();
                    System.out.println("result:" + result);
                }
            } catch (IOException e) {
                System.out.println("发送POST请求出现异常!" + e);
                e.printStackTrace();
            } finally {
                if (reader != null) {
                    reader.close();
                }
            }
        }
        return result;
    }

    /**
     * 获取临时素材
     *
     * @param accessToken
     * @param type        媒体类型
     *                    媒体文件类型,分别有图片(image)、语音(voice)、视频(video)和缩略图(thumb)
     * @param mediaId     素材id
     * @return
     */
    public static String getMaterial(String accessToken, String type, String mediaId) {
        String url = "https://api.weixin.qq.com/cgi-bin/media/get?access_token=%s&media_id=%s";
        if ("voice".equals(type)) {
            url = "https://api.weixin.qq.com/cgi-bin/media/get/jssdk?access_token=%s&media_id=%s";
        }
        url = String.format(url, accessToken, mediaId);
        String response = HttpUtil.get(url, 60000);
        JSONObject object = JSON.parseObject(response);
        return object.toString();
    }

    /**
     * 获取永久素材
     *
     * @param accessToken
     * @param mediaId 素材id
     * @return
     */
    public static String getPermanentMaterial(String accessToken, String mediaId) {
        String url = "https://api.weixin.qq.com/cgi-bin/material/get_material?access_token=%s";
        url = String.format(url, accessToken);
        Map<String, Object> map = new HashMap<>();
        map.put("media_id", mediaId);
        String response = HttpUtil.post(url, map);
        JSONObject object = JSON.parseObject(response);
        return object.toString();
    }

    /**
     * 删除永久素材
     *
     * @param accessToken
     * @param mediaId 素材id
     * @return
     */
    public static String delMaterial(String accessToken, String mediaId) {
        String url = "https://api.weixin.qq.com/cgi-bin/material/del_material?access_token=%s";
        url = String.format(url, accessToken);
        Map<String, Object> map = new HashMap<>();
        map.put("media_id", mediaId);
        String response = HttpUtil.post(url, map);
        JSONObject object = JSON.parseObject(response);
        return object.toString();
    }
}

你可能感兴趣的:(微信,微信,微信公众平台)