微信公众号-java上传图片素材到微信服务器&基于事件推送两条消息
场景:基于微信扫码登录/关注事件,向用户推送两条消息。
分析:翻阅了微信开发者文档和一些其他资料发现,公众号针对用户关注/扫码/取关等特定事件,允许开发者回复用户(详细描述参看公众号开发文档)。但是,对于事件允许回复用户一条消息(被动回复),那么发送两条消息的话,需要主动向用户推送一条消息。另外,推送图片、语音、视频等素材需要先将素材上传至微信服务器。下面是一个demo:
提示:以下是本篇文章正文内容,下面案例可供参考
一、上传图片素材至微信服务器
1.前端(bootstrap)
<body style="width: 100px;height: 100px">
<div >
<table class="table" id="specialInfoTable" style="">
<tbody>
<#--上传文件区域-->
<tr id="copyContainer">
<td>
<div class="col-md-12">
<div class="form-group">
<label class="col-sm-3 control-label no-padding-right">上传图片</label>
<div class="col-sm-9" style="width:80.5%">
<input id="imgUpload" type="file" name="media" class="btn btn-primary"/>
<label style="margin-top:5px;"/>
</div>
</div>
</div>
<div class="col-md-12">
<label class="col-sm-3 control-label no-padding-right">转换内容</label>
<input type="text" id="specialInfoBgimg" readonly="readonly" class="form-control" name="bgimg" id="" value="" />
</div>
</td>
</tr>
</tbody>
</table>
</div>
</body>
<script src="https://cdn.bootcss.com/bootstrap-fileinput/4.4.7/js/fileinput.min.js"></script>
<script>
var uploadDefaults = {
language: 'zh', //设置语言
uploadAsync : false, //设置异步上传
showUpload: false, //是否显示上传按钮
showCaption: false,//是否显示标题
maxFileSize : 20000, //上传文件大小(KB)
maxPreviewFileSize : 10000,
previewFileType : ['image','text','pdf'],
showUploadedThumbs : false,
allowedFileTypes : ['image','audio','object'],//允许上传的文件类型
allowedFileExtensions : ["mp3",'jpg', 'png','svg','ttf','woff']//接收的文件后缀
};
//初始化fileinput控件(第一次初始化)
function initFileInput(id,options) {
var control = $('#' + id);
if(!options.uploadUrl){
console.error('uploadUrl must not be null!');
return false;
}
control.fileinput($.extend({},uploadDefaults,options));
}
$(function() {
var options = {
uploadUrl: mpath + "/ie/mp/uploadMediaToWechat",
allowedFileExtensions: ['jpg', 'jpeg', 'png', 'gif', 'swf'],
allowedFileTypes: null,
showRemove: false,
showClose: false
};
initFileInput('imgUpload',options);
$('#imgUpload').on('fileuploaded', function(event, data, previewId, index) {
var form = data.form, files = data.files, extra = data.extra,
data = data.response, reader = data.reader;
if(data.success){
//key = data.preFilePath;
key = data.obj;
$('#specialInfoBgimg').val(key);
success(data.msg);
success(data.msg);
}else{
error(data.msg);
}
});
});
</script>
2.后端
@RequestMapping("/m/ie/mp")
@RestController
public class WXContentController {
// 上传微信永久素材的url
private static final String UPLOAD_FOREVER_MEDIA_URL = "https://api.weixin.qq.com/cgi-bin/material/add_material?access_token=ACCESS_TOKEN&type=TYPE";
private static final long MAX_SIZE = 1024 * 1024 * 10;
private static final String SUPPORTED_FORMAT = "bmp,png,jpeg,jpg,gif";
@Autowired
private WeiXinServiceApi weiXinServiceApi;
@Value("${weixin.button.appid}")
private String MP_APPID;
/**
* 上传素材到微信素材库
*
* @param file
*/
@PostMapping(value = "uploadMediaToWechat")
public ResultData uploadMediaToWechat(@RequestParam("media") MultipartFile file) throws Exception {
String[] split = file.getOriginalFilename().split("\\.");
String suffixName = split[split.length - 1];
boolean validPic = this.isValidPic(file.getSize(), suffixName);
if (!validPic) {
return ResultData.result(false).setMsg("图片素材上传失败");
}
// 获取access_token,如果你不会获取,那么这篇博客不适合你。
// 这步在你的IDE上会提示报错,请修改为你自己获取access_token的方式
AccessToken accessToken = weiXinServiceApi.getAccessToken(MP_APPID);
String token = accessToken.getAccessToken();
System.out.println("token===="+token);
String replacedUrl = UPLOAD_FOREVER_MEDIA_URL
.replace("ACCESS_TOKEN", token)
.replace("TYPE", "image");
JSONObject jsonObject = UpoladWXFileUtil.uploadFile(replacedUrl,
file.getInputStream(), file.getOriginalFilename());
System.out.println("微信素材上传结果:[{}]=" + jsonObject.toString());
if (jsonObject != null && jsonObject.containsKey("media_id")) {
return ResultData.result(true).setMsg("上传成功").setObj(jsonObject.getString("media_id"));
}
return ResultData.result(false).setMsg("图片素材转换失败");
}
/**
* 图片是否符合微信规范
*
* @param size
* @param suffixName
* @return
*/
public Boolean isValidPic(long size, String suffixName) {
if (size > MAX_SIZE) {
System.out.println("文件太大,文件的大小最大为2M,请重新上传!");
return false;
}
if (!Arrays.asList(SUPPORTED_FORMAT.split(",")).contains(suffixName)) {
System.out.println("图片格式不支持,请选择bmp/png/jpeg/jpg/gif的任意一种!");
return false;
}
return true;
}
}
二、消息推送
1.controller
/**
*
* @description 接收事件并处理微信通知。<br/>
* 有多种微信通知类型,可以在在业务中实现统计、发送消息等操作.
*/
@RequestMapping(value = "/event", method = { RequestMethod.POST })
public void handleEventNofify(@RequestBody String content, HttpServletResponse resp) throws Exception {
resp.setCharacterEncoding("utf-8");
String respTxt = weiXinServiceApi.processNotify(content);
logger.info("handleEventNofify content = {}",content);
if (respTxt != null)
resp.getWriter().write(respTxt);
}
2.service
//处理通知的业务处理方法
@Override
public String processNotify(String content) throws Exception {
Map<String, String> root = WXPayUtil.xmlToMap(content);
String fromUserName = root.get("FromUserName");
// 获取接收方
String toUserName = root.get("ToUserName");
// 获取消息的类型
String msgType = root.get("MsgType");
logger.info("receive wx msg {}", root);
if (msgType.equals(BaseEvent.MESSAGE_TYPE_EVENT)) {
String eventType = root.get("Event").toLowerCase();
// 关注事件,用户未关注时,进行关注后的事件推送
String now = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
if (eventType.equals(BaseEvent.EVENT_TYPE_SUBSCRIBE)) {
String eventKey = root.get("EventKey");
handlePostMessage(194,fromUserName);
return processSubscribe(fromUserName, toUserName, now);
} else if (eventType.equals(BaseEvent.EVENT_TYPE_SCAN)) {
// 扫描二维码事件,用户已关注时的事件推送
String eventKey = root.get("EventKey");
handlePostMessage(195,fromUserName);
return handleScan(fromUserName, toUserName, now);
} else if (eventType.equals(BaseEvent.EVENT_TYPE_UNSUBSCRIBE)) {
return "";
}
}
return "";
}
//处理关注的方法
private String processSubscribe(String fromUserName, String toUserName, String now) {
//从db中获取消息内容,封装成微信的格式
WeixinReply reply = weixinReplyDao.selectByPrimaryKey(2);
return "<xml><ToUserName><![CDATA[" + fromUserName + "]]></ToUserName><FromUserName><![CDATA[" + toUserName
+ "]]></FromUserName><CreateTime>12345678</CreateTime><MsgType><![CDATA[text]]></MsgType><Content><![CDATA["
+ reply.getReplyContent() + "]]></Content></xml>";
}
//处理扫码
private String handleScan( String fromUserName, String toUserName, String now) {
return "<xml><ToUserName><![CDATA[" + fromUserName + "]]></ToUserName><FromUserName><![CDATA[" + toUserName
+ "]]></FromUserName><CreateTime>" + System.currentTimeMillis()
+ "</CreateTime><MsgType><![CDATA[text]]></MsgType><Content><![CDATA[" + reply.getReplyContent()
+ "]]></Content></xml>";
}
//主动推送消息(图片消息,其他消息类型参考公众号文档进行扩展)
private void handlePostMessage(Integer applyId, String fromUserName) {
try {
WeixinReplyExample example = new WeixinReplyExample();
example.createCriteria().andIdEqualTo(applyId);
List<WeixinReply> replies = weixinReplyDao.selectByExample(example);
String respMessage = WXReplyMessageUtil.initiativeMsgData(fromUserName, replies.get(0));
logger.info("图片信息{}", respMessage);
AccessToken accessToken = getAccessToken(MP_APPID);
System.out.println("accessToken===" + accessToken.getAccessToken());
//推送给关注者
String post = HttpUtils.post("https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=" + accessToken.getAccessToken(), respMessage);
System.out.println("关注/扫码事件后推送信息======" + post);
} catch (Exception e) {
logger.error("事件后触发消息异常={}",e);
}
}
util
public class WXPayUtil {
/**
* XML格式字符串转换为Map
*/
public static Map<String, String> xmlToMap(String strXML) throws Exception {
try {
Map<String, String> data = new HashMap<String, String>();
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
org.w3c.dom.Document doc = documentBuilder.parse(stream);
doc.getDocumentElement().normalize();
NodeList nodeList = doc.getDocumentElement().getChildNodes();
for (int idx = 0; idx < nodeList.getLength(); ++idx) {
Node node = nodeList.item(idx);
if (node.getNodeType() == Node.ELEMENT_NODE) {
org.w3c.dom.Element element = (org.w3c.dom.Element) node;
data.put(element.getNodeName(), element.getTextContent());
}
}
try {
stream.close();
} catch (Exception ex) {
// do nothing
}
return data;
} catch (Exception ex) {
WXPayUtil.getLogger().warn("Invalid XML, can not convert to map. Error message: {}. XML content: {}",
ex.getMessage(), strXML);
throw ex;
}
}
}
public class WXReplyMessageUtil {
/**
* 主动推送消息
*
* @param fromUserName
* @param wr
* @return
*/
public static String initiativeMsgData(String fromUserName, WeixinReply wr) {
String respMessage = "";
//图片消息
ImageMsg iMsg = new ImageMsg();
iMsg.setTouser(fromUserName);
iMsg.setMsgtype("image");
HashMap<String, String> media = new HashMap<>(8);
media.put("media_id", wr.getReplyContent());
iMsg.setImage(media);
respMessage = JSON.toJSONString(iMsg);
return respMessage;
}
}
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import warnings
warnings.filterwarnings('ignore')
import ssl
ssl._create_default_https_context = ssl._create_unverified_context