一、注册公众号
- 官网网址:https://developers.weixin.qq.com/doc/offiaccount/Getting_Started/Overview.html
- 微信公众平台,点击注册,通过邮箱注册成功后会看到如下画面
- 在这里,选择类型时要注意下。如果你是个人开发的话只能选择订阅号,订阅号没有自定义菜单等接口,具体接口权限你可以登录公众平台后在开发—>接口权限中看到。如果你想拥有自定义菜单等接口,需要注册服务号,但是服务号只能企业、组织等注册
二、配置公众号
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:随机字符串
- 开发者提交信息后,微信服务器将发送GET请求到填写的服务器地址URL上,GET请求携带参数如下表所示
- 验证接口需要后台服务器自定义编写
- 开发者通过检验signature对请求进行校验。若确认此次GET请求来自微信服务器,请原样返回echostr参数内容,则接入生效,成为开发者成功,否则接入失败
- 将token、timestamp、nonce三个参数进行字典序排序
- 将三个参数字符串拼接成一个字符串进行sha1加密
- 开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
- 开发者通过检验signature对请求进行校验。若确认此次GET请求来自微信服务器,请原样返回echostr参数内容,则接入生效,成为开发者成功,否则接入失败
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><![CDATA[toUser]]></ToUserName> <FromUserName><![CDATA[fromUser]]></FromUserName> <CreateTime>1348831860</CreateTime> <MsgType><![CDATA[text]]></MsgType> <Content><![CDATA[this is a test]]></Content> <MsgId>1234567890123456</MsgId> </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><![CDATA[toUser]]></ToUserName> <FromUserName><![CDATA[fromUser]]></FromUserName> <CreateTime>12345678</CreateTime> <MsgType><![CDATA[image]]></MsgType> <Image> <MediaId><![CDATA[media_id]]></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/template/del_private_template?access_token=ACCESS_TOKEN
-
发送模板消息
- 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(非必填):模板内容字体颜色,不填默认为黑色
- http请求方式: POST https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=ACCESS_TOKEN
{
"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:事件为模板消息发送结束 <![CDATA[TEMPLATESENDJOBFINISH]]>
- 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像素

-
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<String, Object> 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();
}
}
1232

被折叠的 条评论
为什么被折叠?



