[3] 微信公众号开发 - 结合UEditor实现图文消息群发功能
0、写在前面的话
- 通过类似表单的形式,将文章各部分内容提交到后台,封装成一个实体类,并持久化到数据库中
- 需要推送的时候,将不同的文章选择取出交给后台,由后台组装成规范化的数据结构,调用微信的图文消息素材上传和群发接口
- 其中文章的主体部分,我们采用UEditor富文本编辑器
1、什么是图文消息

{
"articles": [
{
"thumb_media_id":"qI6_Ze_6PtV7svjolgs-rN6stStuHIjs9_DidOHaj0Q-mwvBelOXCFZiq2OsIU-p",
"author":"xxx",
"title":"Happy Day",
"content_source_url":"www.qq.com",
"content":"content",
"digest":"digest",
"show_cover_pic":1
},
{
"thumb_media_id":"qI6_Ze_6PtV7svjolgs-rN6stStuHIjs9_DidOHaj0Q-mwvBelOXCFZiq2OsIU-p",
"author":"xxx",
"title":"Happy Day",
"content_source_url":"www.qq.com",
"content":"content",
"digest":"digest",
"show_cover_pic":0
}
]
}
{
"articles": [
{
"thumb_media_id":"qI6_Ze_6PtV7svjolgs-rN6stStuHIjs9_DidOHaj0Q-mwvBelOXCFZiq2OsIU-p",
"author":"xxx",
"title":"Happy Day",
"content_source_url":"www.qq.com",
"content":"content",
"digest":"digest",
"show_cover_pic":1
},
{
"thumb_media_id":"qI6_Ze_6PtV7svjolgs-rN6stStuHIjs9_DidOHaj0Q-mwvBelOXCFZiq2OsIU-p",
"author":"xxx",
"title":"Happy Day",
"content_source_url":"www.qq.com",
"content":"content",
"digest":"digest",
"show_cover_pic":0
}
]
}
| 参数 | 是否必须 | 说明 |
| Articles | 是 | 图文消息,一个图文消息支持1到8条图文 |
| thumb_media_id | 是 | 图文消息缩略图的media_id,可以在基础支持-上传多媒体文件接口中获得 |
| author | 否 | 图文消息的作者 |
| title | 是 | 图文消息的标题 |
| content_source_url | 否 | 在图文消息页面点击“阅读原文”后的页面,受安全限制,如需跳转Appstore,可以使用itun.es或appsto.re的短链服务,并在短链后增加 #wechat_redirect 后缀。 |
| content | 是 | 图文消息页面的内容,支持HTML标签。具备微信支付权限的公众号,可以使用a标签,其他公众号不能使用 |
| digest | 否 | 图文消息的描述 |
| show_cover_pic | 否 | 是否显示封面,1为显示,0为不显示 |
2、群发图文消息的过程和UEditor使用
- 首先,调用接口上传封面缩略图素材,获取thumb_media_id;(目标1)
- 然后将图文消息中需要用到的图片,使用上传图文消息内图片接口,上传成功并获得图片 URL;(目标2)
- 上传图文消息素材,需要用到图片时,请使用上一步获取的图片 URL;
- 使用对用户标签的群发,或对 OpenID 列表的群发,将图文消息群发出去,群发时微信会进行原创校验,并返回群发操作结果;
- 在上述过程中,如果需要,还可以预览图文消息、查询群发状态,或删除已群发的消息等。
2.1 启用UEditor

2.2 封面和文章内图片上传
2.2.1 上传封面缩略图永久素材
http请求方式: POST,需使用https
https://api.weixin.qq.com/cgi-bin/material/add_material?access_token=ACCESS_TOKEN&type=TYPE
调用示例(使用curl命令,用FORM表单方式新增一个其他类型的永久素材,curl命令的使用请自行查阅资料)
http请求方式: POST,需使用https
https://api.weixin.qq.com/cgi-bin/material/add_material?access_token=ACCESS_TOKEN&type=TYPE
调用示例(使用curl命令,用FORM表单方式新增一个其他类型的永久素材,curl命令的使用请自行查阅资料)
- access_token(必需) 调用接口凭证
- type(必需) 媒体文件类型,分别有图片(image)、语音(voice)、视频(video)和缩略图(thumb)
- media(必需) form-data中媒体文件标识,有filename、filelength、content-type等信息
public static String uploadTemp(File file, String url) throws IOException {
String REQUEST_METHOD = "POST";
String result = null;
//请求地址
URL urlObj = new URL(url);
//连接
HttpURLConnection connection = (HttpURLConnection) urlObj.openConnection();
//设置关键值
//POST提交表单,默认是GET
connection.setRequestMethod(REQUEST_METHOD);
connection.setDoInput(true);
connection.setDoOutput(true);
//POST方式无法使用缓存
connection.setUseCaches(false);
//设置请求头信息
connection.setRequestProperty("Connection", "Keep-Alive");
connection.setRequestProperty("Charset", "UTF-8");
//设置边界
String BOUNDARY = "----------" + System.currentTimeMillis();
connection.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + BOUNDARY);
//请求正文信息
//part1 开头
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("--"); // 必须多两道线
stringBuilder.append(BOUNDARY);
stringBuilder.append("\r\n");
stringBuilder.append("Content-Disposition: form-data;name=\"file\";filename=\"" + file.getName() + "\"\r\n");
stringBuilder.append("Content-Type:application/octet-stream\r\n\r\n");
byte[] head = stringBuilder.toString().getBytes("utf-8");
//获得输出流
OutputStream out = new DataOutputStream(connection.getOutputStream());
//输出表头
out.write(head);
//part2 文件正文
//把文件以流文件的方式,推入到url中
DataInputStream in = new DataInputStream(new FileInputStream(file));
int bytes = 0;
byte[] bufferOut = new byte[1024];
while ((bytes = in.read(bufferOut)) != -1) {
out.write(bufferOut, 0, bytes);
}
in.close();
//part3 结尾部分
byte[] foot = ("\r\n--" + BOUNDARY + "--\r\n").getBytes("utf-8");// 定义最后数据分隔线
out.write(foot);
out.flush();
out.close();
StringBuffer buffer = new StringBuffer();
BufferedReader reader = null;
try {
// 定义BufferedReader输入流来读取URL的响应
reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line = null;
while ((line = reader.readLine()) != null) {
buffer.append(line);
}
if (result == null) {
result = buffer.toString();
}
} catch (IOException e) {
log.error("发送POST请求出现异常!" + e);
e.printStackTrace();
throw new IOException("数据读取异常");
} finally {
if(reader!=null) {
reader.close();
}
}
return result;
}
public static String uploadTemp(File file, String url) throws IOException {
String REQUEST_METHOD = "POST";
String result = null;
//请求地址
URL urlObj = new URL(url);
//连接
HttpURLConnection connection = (HttpURLConnection) urlObj.openConnection();
//设置关键值
//POST提交表单,默认是GET
connection.setRequestMethod(REQUEST_METHOD);
connection.setDoInput(true);
connection.setDoOutput(true);
//POST方式无法使用缓存
connection.setUseCaches(false);
//设置请求头信息
connection.setRequestProperty("Connection", "Keep-Alive");
connection.setRequestProperty("Charset", "UTF-8");
//设置边界
String BOUNDARY = "----------" + System.currentTimeMillis();
connection.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + BOUNDARY);
//请求正文信息
//part1 开头
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("--"); // 必须多两道线
stringBuilder.append(BOUNDARY);
stringBuilder.append("\r\n");
stringBuilder.append("Content-Disposition: form-data;name=\"file\";filename=\"" + file.getName() + "\"\r\n");
stringBuilder.append("Content-Type:application/octet-stream\r\n\r\n");
byte[] head = stringBuilder.toString().getBytes("utf-8");
//获得输出流
OutputStream out = new DataOutputStream(connection.getOutputStream());
//输出表头
out.write(head);
//part2 文件正文
//把文件以流文件的方式,推入到url中
DataInputStream in = new DataInputStream(new FileInputStream(file));
int bytes = 0;
byte[] bufferOut = new byte[1024];
while ((bytes = in.read(bufferOut)) != -1) {
out.write(bufferOut, 0, bytes);
}
in.close();
//part3 结尾部分
byte[] foot = ("\r\n--" + BOUNDARY + "--\r\n").getBytes("utf-8");// 定义最后数据分隔线
out.write(foot);
out.flush();
out.close();
StringBuffer buffer = new StringBuffer();
BufferedReader reader = null;
try {
// 定义BufferedReader输入流来读取URL的响应
reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
String line = null;
while ((line = reader.readLine()) != null) {
buffer.append(line);
}
if (result == null) {
result = buffer.toString();
}
} catch (IOException e) {
log.error("发送POST请求出现异常!" + e);
e.printStackTrace();
throw new IOException("数据读取异常");
} finally {
if(reader!=null) {
reader.close();
}
}
return result;
}
2.2.2 结合UEditor实现图文内图片消息的图片上传
ue = UE.getEditor("editor");
//重写UEDITOR的getActionUrl 方法,定义自己的Action,上传图片保存至相应的位置
UE.Editor.prototype._bkGetActionUrl = UE.Editor.prototype.getActionUrl;
UE.Editor.prototype.getActionUrl = function (action) {
if (action == 'uploadimage' || action == 'uploadfile') {
var id = $('#carInfoId').val();
//修改定义自己的Action
return '/admin/news/do/ueditor_uploadNewsImage.q';
} else {
return this._bkGetActionUrl.call(this, action);
}
};
ue = UE.getEditor("editor");
//重写UEDITOR的getActionUrl 方法,定义自己的Action,上传图片保存至相应的位置
UE.Editor.prototype._bkGetActionUrl = UE.Editor.prototype.getActionUrl;
UE.Editor.prototype.getActionUrl = function (action) {
if (action == 'uploadimage' || action == 'uploadfile') {
var id = $('#carInfoId').val();
//修改定义自己的Action
return '/admin/news/do/ueditor_uploadNewsImage.q';
} else {
return this._bkGetActionUrl.call(this, action);
}
};
/**
* ueditor编辑器上传图片到微信服务器
* <p>该部分是使用了百度的富文本web编辑器UEditor,以及微信的图文消息内图片上传接口</p>
*
* @throws IOException
*/
public void ueditor_uploadNewsImage() throws IOException {
WeChatUtil weChatUtil = new WeChatUtil();
String accessToken = weChatUtil.getAccessToken();
HttpServletResponse response = ServletActionContext.getResponse();
//struts请求 包装过滤器
MultiPartRequestWrapper wrapper = (MultiPartRequestWrapper) ServletActionContext.getRequest();
//获取文件过滤器
File tmp = wrapper.getFiles("upfile")[0];
//获得上传的文件名
String filename = wrapper.getFileNames("upfile")[0];
int suffixIndex = filename.indexOf(".");
//文件后缀确定
if (suffixIndex == filename.lastIndexOf(".")) {
String suffix = filename.substring(suffixIndex);
File file = File.createTempFile("tmp", suffix);
FileCopyUtils.copy(tmp, file);
String imageUrl = weChatUtil.uploadImg(accessToken, file); //这里最终还是调用的表单模拟的那个方法
//如果获取到了有效的imageUrl
if (imageUrl != null && imageUrl.substring(0, 4).equals("http")) {
String json = "{\"state\":\"SUCCESS\", \"url\":\"%s\", \"title\":\"%s\", \"original\":\"%s\"}";
json = String.format(json, imageUrl, filename, filename);
log.debug("上传图片返回信息:" + json);
response.getWriter().write(json);
}
}
}
/**
* ueditor编辑器上传图片到微信服务器
* <p>该部分是使用了百度的富文本web编辑器UEditor,以及微信的图文消息内图片上传接口</p>
*
* @throws IOException
*/
public void ueditor_uploadNewsImage() throws IOException {
WeChatUtil weChatUtil = new WeChatUtil();
String accessToken = weChatUtil.getAccessToken();
HttpServletResponse response = ServletActionContext.getResponse();
//struts请求 包装过滤器
MultiPartRequestWrapper wrapper = (MultiPartRequestWrapper) ServletActionContext.getRequest();
//获取文件过滤器
File tmp = wrapper.getFiles("upfile")[0];
//获得上传的文件名
String filename = wrapper.getFileNames("upfile")[0];
int suffixIndex = filename.indexOf(".");
//文件后缀确定
if (suffixIndex == filename.lastIndexOf(".")) {
String suffix = filename.substring(suffixIndex);
File file = File.createTempFile("tmp", suffix);
FileCopyUtils.copy(tmp, file);
String imageUrl = weChatUtil.uploadImg(accessToken, file); //这里最终还是调用的表单模拟的那个方法
//如果获取到了有效的imageUrl
if (imageUrl != null && imageUrl.substring(0, 4).equals("http")) {
String json = "{\"state\":\"SUCCESS\", \"url\":\"%s\", \"title\":\"%s\", \"original\":\"%s\"}";
json = String.format(json, imageUrl, filename, filename);
log.debug("上传图片返回信息:" + json);
response.getWriter().write(json);
}
}
}
public class MyStrutsFilter extends StrutsPrepareAndExecuteFilter {
public void doFilter(ServletRequest req, ServletResponse res,FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
//不过滤的url
String url = request.getRequestURI();
System.out.println(url);
try {
if (url.contains("/controller.jsp")) {
System.out.println("使用自定义的过滤器");
chain.doFilter(req, res);
} else {
System.out.println("使用默认的过滤器");
super.doFilter(req, res, chain);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class MyStrutsFilter extends StrutsPrepareAndExecuteFilter {
public void doFilter(ServletRequest req, ServletResponse res,FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
//不过滤的url
String url = request.getRequestURI();
System.out.println(url);
try {
if (url.contains("/controller.jsp")) {
System.out.println("使用自定义的过滤器");
chain.doFilter(req, res);
} else {
System.out.println("使用默认的过滤器");
super.doFilter(req, res, chain);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
2.3 图文素材上传和发送
{
"articles": [
{
"thumb_media_id":"qI6_Ze_6PtV7svjolgs-rN6stStuHIjs9_DidOHaj0Q-mwvBelOXCFZiq2OsIU-p",
"author":"xxx",
"title":"Happy Day",
"content_source_url":"www.qq.com",
"content":"content",
"digest":"digest",
"show_cover_pic":1
},
{
"thumb_media_id":"qI6_Ze_6PtV7svjolgs-rN6stStuHIjs9_DidOHaj0Q-mwvBelOXCFZiq2OsIU-p",
"author":"xxx",
"title":"Happy Day",
"content_source_url":"www.qq.com",
"content":"content",
"digest":"digest",
"show_cover_pic":0
}
]
}
{
"articles": [
{
"thumb_media_id":"qI6_Ze_6PtV7svjolgs-rN6stStuHIjs9_DidOHaj0Q-mwvBelOXCFZiq2OsIU-p",
"author":"xxx",
"title":"Happy Day",
"content_source_url":"www.qq.com",
"content":"content",
"digest":"digest",
"show_cover_pic":1
},
{
"thumb_media_id":"qI6_Ze_6PtV7svjolgs-rN6stStuHIjs9_DidOHaj0Q-mwvBelOXCFZiq2OsIU-p",
"author":"xxx",
"title":"Happy Day",
"content_source_url":"www.qq.com",
"content":"content",
"digest":"digest",
"show_cover_pic":0
}
]
}
- thumb_media_id 上传封面缩略图后返回的media_id,我们已经说明了
- author 这个写个input就可以搞定
- title 同上
- content_source_url 非必需
- content 这里就是我们用UEditor编辑器中的内容了,其中重点是图片上传,我们已经说明了
- digest 摘要,加input解决
- show_cover_pic 非必需
[3] 微信公众号开发 - 结合UEditor实现图文消息群发功能的更多相关文章
- 微信公众号开发C#系列-7、消息管理-接收事件推送
1.概述 在微信用户和公众号产生交互的过程中,用户的某些操作会使得微信服务器通过事件推送的形式通知到开发者在开发者中心处设置的服务器地址,从而开发者可以获取到该信息.其中,某些事件推送在发生后,是允许 ...
- 微信公众号开发 [03] 结合UEditor实现图文消息群发功能
0.写在前面的话 如何实现微信平台后台管理中的,图文消息发送功能? 大概的过程如下: 通过类似表单的形式,将文章各部分内容提交到后台,封装成一个实体类,并持久化到数据库中 需要推送的时候,将不同的文章 ...
- C#微信公众号开发系列教程三(消息体签名及加解密)
http://www.cnblogs.com/zskbll/p/4139039.html C#微信公众号开发系列教程一(调试环境部署) C#微信公众号开发系列教程一(调试环境部署续:vs远程调试) C ...
- 微信公众号开发C#系列-6、消息管理-普通消息接受处理
1.概述 通过前面章节的学习,我们已经对微信的开发有了基本的掌握与熟悉,基本可以上手做复杂的应用了.本篇我们将详细讲解微信消息管理中普通消息的接收与处理.当普通微信用户向公众账号发消息时,微信服务器将 ...
- C#微信公众号开发-高级接口-之模板消息开发,附源码
个人觉得模板消息功能的增加对公众号的作用非常大,可以说是真正意义上的实现了所谓的轻app,商家可以通过模板消息给用户发送重要的信息,交易.预约.消费.邮件.物流等信息.之前我做过的系统通过邮件发送订单 ...
- Java微信公众平台开发(五)--文本及图文消息回复的实现
转自:http://www.cuiyongzhi.com/post/43.html 上篇我们说到回复消息可以根据是否需要上传文件到微信服务器可划分为[普通消息]和[多媒体消息],这里我们来讲述普通消息 ...
- C#微信公众号开发系列教程六(被动回复与上传下载多媒体文件)
微信公众号开发系列教程一(调试环境部署) 微信公众号开发系列教程一(调试环境部署续:vs远程调试) C#微信公众号开发系列教程二(新手接入指南) C#微信公众号开发系列教程三(消息体签名及加解密) C ...
- C#微信公众号开发系列教程五(接收事件推送与消息排重)
微信公众号开发系列教程一(调试环境部署) 微信公众号开发系列教程一(调试环境部署续:vs远程调试) C#微信公众号开发系列教程二(新手接入指南) C#微信公众号开发系列教程三(消息体签名及加解密) C ...
- C#微信公众号开发系列教程四(接收普通消息)
微信公众号开发系列教程一(调试环境部署) 微信公众号开发系列教程一(调试环境部署续:vs远程调试) C#微信公众号开发系列教程二(新手接入指南) C#微信公众号开发系列教程三(消息体签名及加解密) C ...
随机推荐
- UnitOfWork知多少
1. 引言 Maintains a list of objects affected by a business transaction and coordinates the writing out ...
- 云计算之路-阿里云上-容器难容:自建docker swarm集群遭遇无法解决的问题
我们从今年6月开始在生产环境进行 docker 容器化部署,将已经迁移至 ASP.NET Core 的站点部署到 docker swarm 集群上.开始我们选用的阿里云容器服务,但是在使用过程中我们遭 ...
- .net core 2.0学习笔记(六):Remoting核心类库RealProxy迁移
在学习.net core的过程中,我们已经明确被告知,Remoting将不会被支持.官方的解释是,.net framework 类型包含了太多的Runtime的内容,是一个非常重量级的服务实现,已被确 ...
- Maven详解(八)------ 继承和聚合
1.继承 需求场景: 有三个 Maven 工程,每个工程都依赖某个 jar 包,比如 Junit,由于 test 范围的依赖不能传递,它必然会分散在每个工程中,而且每个工程的jar 包版本可能不一致. ...
- 【.net 深呼吸】实时获取计算结果
上次老周介绍了在 UWP 应用中通过 x:Bind 标记来绑定到方法,以实现实时获取计算结果.今天,咱们来耍耍WPF上面的实现方法. 虽然,WPF 没有 x:Bind 标记(暂时没有,以后不好说),但 ...
- 标准的Java编码规范手册
编码规范体现出一个开发者的基本素质,良好的编码规范可以提高团队编码的效率,避免很多不必要的问题.今天分享一个标准的Java编码规范给大家,希望对于大家今后的开发工作带来帮助. 编码规范的意义 ...
- [知了堂学习笔记]_纯JS制作《飞机大战》游戏_第2讲(对象的实现及全局变量的定义)
整体展示: 一.全局变量 /*===================玩家参数==========================*/ var myPlane; //英雄对象 var leftbtn = ...
- BackgroundWorker类中主要属性、方法和事件
属性: 1.CancellationPending 获取一个值,指示应用程序是否已请求取消后台操作.通过在DoWork事件中判断CancellationPending属性可以认 ...
- 前端开发【第4篇:JavaScript基础】
JavaScript简述 上一篇文章已经聊过JavaScript的历史了这里不再复述了直接切入正题,JavaScript是一门解释型.动态类型.弱类型语言. 解释型语言和编译型语言就类似看一本书,编译 ...
- C-C++到底支不支持VLA以及两种语言中const的区别
C-C++到底支不支持VLA以及两种语言中const的区别 到底支不支持VLA VLA就是variable-length array,也就是变长数组. 最近写程序的时候无意间发现,gcc中竟然支持下面 ...