2019-01-26 13:43:21 chenwill3 阅读数 329
  • PHP开发微信公众平台——天气查询实战项目

    从基础到项目的深入化讲解,手把手带你玩儿转微信公众平台 开发基础准备、 开发模式接入、消息的接收与响应、 被关注回复与关键词回复、图文消息的发送与响应 、获取access_token、上传素材与图片消息回复 、自定义菜单实现、第三方API接口调用 天气查询项目实战

    517 人正在学习 去看看 陈世平

接着上次分享的服务器被动回复文本消息,这次分享的是图文消息(也叫文章消息),文本消息虽然可以处理大部分的业务需求,但美中不足的是消息过于死板不够活泼生动,为了丰富消息的内容和展现微信提供了图文消息供我们使用,这次分享的图文消息围绕必应的每日一图接口进行

一 bing的图片信息获取接口

这里我们只需要url属性即可,下面的工具类用于提取url

<?php
namespace  util;

include_once __DIR__.DIRECTORY_SEPARATOR."RequestUtil.php";

class EveryDayImageUtil{
    function getTodayImage(){
        
        $reqUtil=new RequestUtil();
        $res=$reqUtil->http_request("http://cn.bing.com/HPImageArchive.aspx?idx=0&n=1");
        $xml=simplexml_load_string($res);
        $children=$xml->children()->children();
        foreach ($children as $key => $value ) {
            if("url"==$key){
                return "http://www.bing.com".$value;
            }
            
        }
    }
  
}

?>

二  流程和事件推送处理

我们知道被动回复消息需要先请求微信服务器,这里为了简便把获取图文消息设计成一个有事件推送性质的自定义菜单,点击菜单使用事件推送回复图文消息给用户

if($postObj->Event=="CLICK"){
       if("today_picture"==$postObj->EventKey){
          $imageUtil=new \util\EveryDayImageUtil();
                        
          $defaultMsgType="news";
          $item1=array("title"=>"今日图片","description"=>"bing精选片","picurl"=>$imageUtil->getTodayImage(),"url"=>"http://cn.bing.com/");
          $article=array($item1);
          $result=$msgUtil->articleMessageToXml($fromUsername, $toUsername, $article);
                        
          $resultStr=$result;
                       
       }
 }else{
       $typeResult="事件推送消息";
 }

一起欣赏下效果

2019-07-25 14:29:05 shuaicenglou3032 阅读数 391
  • PHP开发微信公众平台——天气查询实战项目

    从基础到项目的深入化讲解,手把手带你玩儿转微信公众平台 开发基础准备、 开发模式接入、消息的接收与响应、 被关注回复与关键词回复、图文消息的发送与响应 、获取access_token、上传素材与图片消息回复 、自定义菜单实现、第三方API接口调用 天气查询项目实战

    517 人正在学习 去看看 陈世平

最近遇到个需求,对方搞了个公众号,在上面发布了一些图文消息,他们想在子菜单里搞个第三方网页,点击进去能看到这个公众号发布的所有图文消息,我研究了下,具体实现如下:

0x00准备工作

根据公众平台技术文档所说的,首先需要在微信公众平台上开启开发者密码,

登录微信公众平台官网后,在公众平台官网的开发-基本设置页面,勾选协议成为开发者,点击“修改配置”按钮,填写服务器地址(URL)、Token和EncodingAESKey,其中URL是开发者用来接收微信消息和事件的接口URL。Token可由开发者可以任意填写,用作生成签名(该Token会和接口URL中包含的Token进行比对,从而验证安全性)。EncodingAESKey由开发者手动填写或随机生成,将用作消息体加解密密钥。

在这里插入图片描述

我们也可以不需要实际公众号,直接去申请一个测试公众号,但是测试公众号有一个缺点就是获取图文的时候图文数量为0,不方便后续的实验,所以有条件可以先申请一个公众号。

然后第三方服务器要验证消息来自微信服务器,此处给出springmvc的验证消息的Controller代码如下:

@Controller
public class MainController {
	
	/**
	 * 检验signature对请求进行校验,确认此次请求来自微信服务器,若确认此次GET请求来自微信服务器,原样返回echostr参数内容,接入生效
	 * @param request
	 * @return
	 */
	@RequestMapping(value="/signatureCheck.do")
	@ResponseBody
    public String writeByHand(HttpServletRequest request)
    {
		String signature = request.getParameter("signature");
		String timestamp = request.getParameter("timestamp");
		String nonce = request.getParameter("nonce");
		String echostr = request.getParameter("echostr");
		String token = "aaa";    //token自己设置
		//对timestamp、nonce、token进行字典序排序
		String[] parameters = {timestamp,nonce,token};
		ArrayList<String> list = new ArrayList<>();
		for(String s:parameters) list.add(s);
		Collections.sort(list, new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                try {
                    // 取得比较对象的汉字编码,并将其转换成字符串
                    String s1 = new String(o1.toString().getBytes("GB2312"), "ISO-8859-1");
                    String s2 = new String(o2.toString().getBytes("GB2312"), "ISO-8859-1");
                    // 运用String类的 compareTo()方法对两对象进行比较
                    return s1.compareTo(s2);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return 0;
            }
        });// 根据元素的自然顺序 对指定列表按升序进行排序。
		//将上述三个字符串拼接后sha1加密
		StringBuilder finalString  = new StringBuilder();
		for(String s:list) finalString.append(s);
		String result = null;
		try {
			result = Enc_decTools.SHA1Encode(finalString.toString());
		} catch (Exception e) {
			e.printStackTrace();
		}
		if(signature==null) {
			return "fail";  //非微信服务器发来的消息
		}else {
			if(signature.equals(result)) {
				return echostr;
			}else {
				return "fail";
			}
		}		
    }
}

其中,SHA1加密的代码如下:

/**
	 * SHA1对字符串进行加密
	 * @param inStr
	 * @return
	 * @throws Exception
	 */
	public static String SHA1Encode(String inStr) throws Exception {
        MessageDigest sha = null;
        try {
            sha = MessageDigest.getInstance("SHA");
        } catch (Exception e) {
            System.out.println(e.toString());
            e.printStackTrace();
            return "";
        }
 
        byte[] byteArray = inStr.getBytes("UTF-8");
        byte[] md5Bytes = sha.digest(byteArray);
        StringBuffer hexValue = new StringBuffer();
        for (int i = 0; i < md5Bytes.length; i++) {
            int val = ((int) md5Bytes[i]) & 0xff;
            if (val < 16) {
                hexValue.append("0");
            }
            hexValue.append(Integer.toHexString(val));
        }
        return hexValue.toString();
    }

0x01 AccessToken

准备工作完成后,我们的服务器就可以通过微信的验证,从它那里拿到accesstoken,这个accesstoken等于是获取信息的凭证,目前有效期2小时。2小时后需要重新获取。
此外,获取AccessToken是通过https get请求,此处给出通过httpClient向微信服务器发起https get请求的代码:

/**
	 * 发起https get请求并获得返回值,值为字符串
	 * @param url
	 * @return
	 * @throws IOException
	 */
	public static String getSSL(String url) throws IOException {
	    CloseableHttpClient httpclient = createSSLInsecureClient();
	    CloseableHttpResponse response = null;
	    String content = null;
	    try {
	        HttpGet httpget = new HttpGet(url);
	        RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(5000).setConnectTimeout(5000)
	                    .setConnectionRequestTimeout(5000).build();
	        httpget.setConfig(requestConfig);
	        response = httpclient.execute(httpget);
	        HttpEntity entity = response.getEntity();
	        if (entity != null) {
	            content = EntityUtils.toString(entity);
	            EntityUtils.consume(entity);
	        }
	    } catch (ParseException e) {
	        throw e;
	    } catch (IOException e) {
	        throw e;
	    } finally {
	        if (response != null) {
	            try {
	                response.close();
	            } catch (IOException e) {
	                throw e;
	            }
	        }
	        if (httpclient != null) {
	            try {
	                httpclient.close();
	            } catch (IOException e) {
	            	throw e;
	            }
	        }
	    }
	    return content;
	}
	 
	private static CloseableHttpClient createSSLInsecureClient() {
	    try {
	        SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
	            @Override
	            public boolean isTrusted(java.security.cert.X509Certificate[] arg0, String arg1) {
	                return true;
	            }
	        }).build();
	        SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext);
	        return HttpClients.custom().setSSLSocketFactory(sslsf).build();
	    } catch (KeyManagementException e) {
	        e.printStackTrace();
	    } catch (NoSuchAlgorithmException e) {
	        e.printStackTrace();
	    } catch (KeyStoreException e) {
	        e.printStackTrace();
	    }
	    return HttpClients.createDefault();
	}

调用getSSL方法即可。此外,要使用httpClient需要导入相应的jar包:
jar包
哦忘了说,现在微信公众号修改了规则,还得在微信公众平台设置IP白名单才能拿到AccessToken,这点也需要注意设置,否则会出错。

0x02 获取素材列表

拿到AccessToken就可以把它拿来获取图文了。获取图文的逻辑如下:
用AccessToken获取图文列表
随后计算是否超过一次获取图文的最大限制数量
根据图文列表中的id逐一获取图文消息
现在图文列表的结构体里已经包含了图文的url,不需要像以前一样再根据素材的id请求一次获取永久素材。
具体代码如下:

private static final int MAXARTICLENUM = 20;   //每次最多获取的图文列表数量,一次只能拉取20条图文
	private static final String NEWS = "news";     //获取图文类素材
	private static final String APPID = "yongbubiandexinshishabi";  //此处填你的开发者ID(APPID)
	private static final String SECRET = "xxxxxxx";  //开发者密码
	private static final String ACCESSTOKENURL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid="+APPID+"&secret="+SECRET; //获取accessToken的url
	public List<Article> getArticleList(){
		String accessJson = null;
	    try {
	    	accessJson = HttpsRequest.getSSL(ACCESSTOKENURL);
	    } catch (IOException e) {
			e.printStackTrace();
		}
	    if(accessJson!=null) {
	    	HashMap<String, String> token =  JSON.parseObject(accessJson, HashMap.class);
	    	String access_token = token.get("access_token");
	    	//获取素材总数,若图文数量为0则停止下一步操作
	    	String url = "https://api.weixin.qq.com/cgi-bin/material/get_materialcount?access_token="+access_token;
	    	String jsonArticleCount = null;
	    	try {
				jsonArticleCount = HttpsRequest.getSSL(url);
			} catch (IOException e) {
				e.printStackTrace();
			}
	    	if(jsonArticleCount!=null) {
	    		HashMap<String, Integer> mediaCount = JSON.parseObject(jsonArticleCount, HashMap.class);
	    		//获取图文总数
	    		int articleNum = mediaCount.get("news_count");
	    		if(articleNum>0) { //若有图文则循环拉取图文列表(一次最多只能获取20个图文的列表)
	    			url = "https://api.weixin.qq.com/cgi-bin/material/batchget_material?access_token="+access_token;
	    			if(articleNum<=MAXARTICLENUM) {   //总数小于等于20条,只需获取一次
	    				JSONObject parameters = new JSONObject();
	    				parameters.put("type", NEWS);
	    				parameters.put("offset",0);
	    				parameters.put("count", articleNum);
	    				JSONObject jsonArticleList = null;  //Json格式的图文列表
	    				try {
							jsonArticleList = HttpsRequest.sendJsonByPost(url, parameters);
						} catch (Exception e) {
							e.printStackTrace();
						}
	    				List<Article> articleList = null;
	    				//此处默认拆分多图文消息,数据库里仅存储单图文,如需显示多图文需在前端另作处理,将素材id相同的图文集合起来。
	    				if(jsonArticleList!=null) {
	    					articleList = new ArrayList<>();
	    					JSONArray items = (JSONArray)jsonArticleList.get("item");
	    					for(int i=0;i<items.size();i++) {    //循环遍历每个图文消息
	    						JSONObject item = (JSONObject) items.get(i);
	    						int update_timestamp = (Integer)item.get("update_time");
	    						Date update_time = DateTools.timestampToDate(Integer.toString(update_timestamp), null);
	    						JSONObject itemContent = (JSONObject) item.get("content");
	    						String media_id = (String)item.get("media_id");
	    						JSONArray newsItem = (JSONArray) itemContent.get("news_item");
	    						for(int j=0;j<newsItem.size();j++) {
	    							JSONObject articleItem = (JSONObject) newsItem.get(j);
	    							Article article = new Article();
	    							article.setArticle_id(UUID.randomUUID().toString());   //随机分配一个id作为主键
		    						article.setCover_address((String)articleItem.get("thumb_url"));  //封面图片地址
		    						article.setDigest((String)articleItem.get("digest"));                //摘要
		    						article.setThumb_media_id((String)articleItem.get("thumb_media_id"));//封面图片id
		    						article.setTitle((String)articleItem.get("title"));                  //标题
		    						article.setUrl((String)articleItem.get("url"));               //图文地址
		    						article.setMedia_id(media_id);            //图文所属的素材id
		    						article.setUpdate_time(update_time);      //图文所属素材的最后编辑时间
		    						articleList.add(article);
	    						}
	    					}
	    					}
	    				if(articleList!=null) {
	    					return articleList;
	    				}else {     //图文列表获取失败
							return null;
						}
	    			}else {     //图文数量大于20,计算获取次数和偏移量,循环拉取数据
	    				//计算获取次数
	    				int getNum = articleNum%MAXARTICLENUM==0?articleNum/MAXARTICLENUM:articleNum/MAXARTICLENUM+1;
	    				List<Article> articleList = new ArrayList<>();
	    				for (int i = 0; i < getNum; i++) {
							//计算偏移量,即post请求参数里的offset
	    					int offset = i*MAXARTICLENUM;
	    					System.out.println("偏移量:"+offset);
	    					JSONObject parameters = new JSONObject();
		    				parameters.put("type", NEWS);
		    				parameters.put("offset",offset);
		    				parameters.put("count", MAXARTICLENUM);
		    				JSONObject jsonArticleList = null;  //Json格式的图文列表
		    				try {
								jsonArticleList = HttpsRequest.sendJsonByPost(url, parameters);
							} catch (Exception e) {
								e.printStackTrace();
							}
		    				//此处默认拆分多图文消息,数据库里仅存储单图文,如需显示多图文需在前端另作处理,将素材id相同的图文集合起来。
		    				if(jsonArticleList!=null) {
		    					JSONArray items = (JSONArray)jsonArticleList.get("item");
		    					for(int j=0;j<items.size();j++) {    //循环遍历每个图文消息
		    						JSONObject item = (JSONObject) items.get(j);
		    						int update_timestamp = (Integer)item.get("update_time");
		    						Date update_time = DateTools.timestampToDate(Integer.toString(update_timestamp), null);
		    						JSONObject itemContent = (JSONObject) item.get("content");
		    						String media_id = (String)item.get("media_id");
		    						JSONArray newsItem = (JSONArray) itemContent.get("news_item");
		    						for(int k=0;k<newsItem.size();k++) {
		    							JSONObject articleItem = (JSONObject) newsItem.get(k);
		    							Article article = new Article();
		    							article.setArticle_id(UUID.randomUUID().toString());   //随机分配一个id作为主键
			    						article.setCover_address((String)articleItem.get("thumb_url"));  //封面图片地址
			    						article.setDigest((String)articleItem.get("digest"));                //摘要
			    						article.setThumb_media_id((String)articleItem.get("thumb_media_id"));//封面图片id
			    						article.setTitle((String)articleItem.get("title"));                  //标题
			    						article.setUrl((String)articleItem.get("url"));               //图文地址
			    						article.setMedia_id(media_id);            //图文所属的素材id
			    						article.setUpdate_time(update_time);      //图文所属素材的最后编辑时间
			    						articleList.add(article);
		    						}
		    					}
		    				}else {        //图文列表获取失败
		    					return null;
		    				}
						}
	    				return articleList;
	    			}
	    		}else {    //图文数量为0
					return new ArrayList<>();   //ArrayList初始容量为0,故不再显式指定初始容量
				}
	    	}else {        //图文总数获取失败
				return null;
			}
	    }else {      //accessToken获取失败
			return null;
		}
	}

此外Json转换方面我是用的是fastjson,就是阿里开发的那个
由于需要发起https post请求,因此给出发起https post请求的代码:

/**
	 * 以Post方式发送参数为json形式的请求并获取返回值后转成json对象
	 * @param url
	 * @param parameters
	 * @return
	 * @throws Exception
	 */
	public static JSONObject sendJsonByPost(String url,JSONObject parameters) throws Exception {
		CloseableHttpClient httpClient = HttpClients.createDefault();
		HttpPost httpPost = new HttpPost(url);
		httpPost.addHeader(HTTP.CONTENT_TYPE, "application/json");
		httpPost.setHeader("Accept","application/json");
		StringEntity se = new StringEntity(parameters.toString());
		se.setContentType("text/json");
		httpPost.setEntity(se);
		HttpResponse response=httpClient.execute(httpPost);
		//输出调用结果
		if(response != null && response.getStatusLine().getStatusCode() == 200) {
		String result = EntityUtils.toString(response.getEntity(),"UTF-8");//此处不加UTF-8会导致响应结果出现中文乱码
		JSONObject obj = JSONObject.parseObject(result);
		return obj;
		}else {
			return null;
		}
	}

最后附java文件以及jar包:
代码

2018-03-31 12:12:32 Programming_NewBie 阅读数 67
  • PHP开发微信公众平台——天气查询实战项目

    从基础到项目的深入化讲解,手把手带你玩儿转微信公众平台 开发基础准备、 开发模式接入、消息的接收与响应、 被关注回复与关键词回复、图文消息的发送与响应 、获取access_token、上传素材与图片消息回复 、自定义菜单实现、第三方API接口调用 天气查询项目实战

    517 人正在学习 去看看 陈世平


在微信公众平台-消息管理-客服消息里获取帮助文档


在微信公众平台-开发者工具-公众平台测试账号注册测试账号-获得APPID和APPsecret,下滑在测试号二维码扫二维码得到openid


在微信公众平台-开发者工具-在线借口测试工具里编写代码,发送消息




在接口类型中选择基础服务,接口列表中选择上传logo,填写token,获得图片url




发送图文消息
{
    "touser": "oG1XL0ZMIc291aRgablK6P_SadZc", 
    "msgtype": "news", 
    "news": {
        "articles": [
            {
                "title": "Happy Day", 
                "description": "Is Really A Happy Day", 
                "url": "URL", 
                "picurl": "http://mmbiz.qpic.cn/mmbiz_jpg/iaPJc3uk8HfQxI8Nljt7jiawZiaCBf2UIBtAm00RtTcKiaR7FMfEhdgCdUia8e34fhvengmUmflM0rejpic7pq0VsDIA/0"
            }
        ]
    }
}






发送普通消息


{
    "touser":"oG1XL0ZMIc291aRgablK6P_SadZc",
    "msgtype":"text",
    "text":
    {
         "content":"Hello World"
    }
}
2016-09-07 15:57:30 frankcheng5143 阅读数 17542
  • PHP开发微信公众平台——天气查询实战项目

    从基础到项目的深入化讲解,手把手带你玩儿转微信公众平台 开发基础准备、 开发模式接入、消息的接收与响应、 被关注回复与关键词回复、图文消息的发送与响应 、获取access_token、上传素材与图片消息回复 、自定义菜单实现、第三方API接口调用 天气查询项目实战

    517 人正在学习 去看看 陈世平

今天实现了一下微信公众号的消息群发功能,整个过程还是比较麻烦的,而且有些坑。记录一下。

微信官方文档–高级群发接口

在公众平台网站上,为订阅号提供了每天一条的群发权限,为服务号提供每月(自然月)4条的群发权限。而对于某些具备开发能力的公众号运营者,可以通过高级群发接口,实现更灵活的群发能力。

由于群发的次数非常有限。这里是以预览的方式发送给自己。

先看效果

效果

这里写图片描述

这里写图片描述

发送成功的返回结果

{"errcode":0,"errmsg":"preview success"}

这个是预览,群发和预览差不多。

正式环境的效果(2016年9月21日补图)

这里写图片描述

思路

微信的正确思路

这里写图片描述

我的具体做法

这里写图片描述
这里写图片描述

一些代码

接口地址

    // 图文内的图片地址获取接口地址
    private String uploadImageUrl = "https://api.weixin.qq.com/cgi-bin/media/uploadimg?access_token=ACCESS_TOKEN";
    // 图文封面图片获取接口地址
    private String postImageMediaUrl = "http://file.api.weixin.qq.com/cgi-bin/media/upload?access_token=ACCESS_TOKEN&type=image";
    // 图文素材上传接口地址
    private String postNewsUrl = "https://api.weixin.qq.com/cgi-bin/media/uploadnews?access_token=ACCESS_TOKEN";
    // 群发接口地址
    private String sendToAllUrl = "https://api.weixin.qq.com/cgi-bin/message/mass/sendall?access_token=ACCESS_TOKEN";
    // 预览接口地址
    private String sendToPreviewUrl = "https://api.weixin.qq.com/cgi-bin/message/mass/preview?access_token=ACCESS_TOKEN";

关键代码

    @Test
    public void testSendToAll() {
        String accessToken = accessTokenUtil.getAccessToken();
        // 获取详情的新闻列表
        List<NewsArticle> articleList = new ArrayList<>();
        // 1.原始的新闻列表
        List<NewsArticle> newsList = getArticleList();
        if(newsList.size()==0){
            logger.info("----------今天没有发文章,任务结束----------");
            return;
        }
        // 2.封装新闻详情,并得到新闻列表
        for (NewsArticle article : newsList) {
            NewsArticle articleNew = getArticleDetial(article.getUrl());
            try {
                // 下载封面
                String localImage = imageService.saveImageToDisk(articleNew.getImagUrl(), imageSavePath);
                // 上传封面
                String jsonStr = HttpUtil.sendPost(postImageMediaUrl.replace("ACCESS_TOKEN", accessToken),
                        new File(localImage));
                JSONObject object = new JSONObject();
                logger.info("----------上传封面返回结果:{}----------" + jsonStr);
                try {
                    object = new JSONObject(jsonStr);
                    logger.info("----------得到的图片media_id:{}----------", object.get("media_id"));
                    articleNew.setImageMediaId((String) object.get("media_id"));
                } catch (Exception e) {
                    logger.error("----------上传封面发生错误:{}", e.getMessage());
                }
            } catch (Exception e) {
                logger.error("----------下载封面上传封面过程发生错误:{}", e.getMessage());
            }
            articleList.add(articleNew);
        }
        logger.info("----------最终图文列表:{}----------", articleList);
        // 3.群发图文消息封装
        List<GroupActicle> articles = new ArrayList<>();
        for (NewsArticle NewsArticle : articleList) {
            GroupActicle article = new GroupActicle();
            article.setThumb_media_id(NewsArticle.getImageMediaId());
            article.setAuthor(NewsArticle.getAuthor());
            article.setTitle(NewsArticle.getTitle());
            article.setContent_source_url(NewsArticle.getUrl());
            article.setContent(NewsArticle.getContent());
            article.setShow_cover_pic(0);
            articles.add(article);
        }
        logger.info("----------图文消息:{}----------", articles);
        Map<String, Object> jsonMap = new HashMap<>();
        jsonMap.put("articles", articles);
        logger.info("----------最终的图文消息json:{}----------", gson.toJson(jsonMap));
        // 4.上传群发图文素材
        String postNewsResult = HttpUtil.sendPost(postNewsUrl.replace("ACCESS_TOKEN", accessToken),
                gson.toJson(jsonMap));
        logger.info("----------上传图文素材的返回结果:{}----------", postNewsResult);
        JSONObject object = new JSONObject();
        String mediaId = "";
        try {
            object = new JSONObject(postNewsResult);
            logger.info("----------得到的media_id:{}----------", object.get("media_id"));
            mediaId = (String) object.get("media_id");
        } catch (Exception e) {
            logger.error("----------上传图文消息发生错误:{}----------", e.getMessage());
        }
        // 5.选择发送的用户并发送
        if (StringUtils.isNotEmpty(mediaId)) {
            Filter filter = new Filter(true);
            Mpnews mpnews = new Mpnews(mediaId);
            SendToAllNews sendAll = new SendToAllNews();
            sendAll.setFilter(filter);
            sendAll.setMpnews(mpnews);
            sendAll.setMsgtype("mpnews");

            // TODO 正式环境用sendToAll
            SendToOpenIdPreview sendToPreview = new SendToOpenIdPreview("omSsruLEtGPgxjaJTyurGQwQNh8Q");
            sendToPreview.setMpnews(mpnews);
            sendToPreview.setMsgtype("mpnews");

            logger.info("----------预览的json:{}----------", gson.toJson(sendToPreview));
            // TODO 正式环境换成sendToAllUrl
            String sentAllResult = HttpUtil.sendPost(sendToPreviewUrl.replace("ACCESS_TOKEN", accessToken),
                    gson.toJson(sendToPreview));
            logger.info("----------预览结果:{}----------", sentAllResult);
        }

    }

注意

微信群发接口图文消息里面包含图片地址,群发之后图文消息内图片无法显示

官方解释

请注意,在图文消息的具体内容中,将过滤外部的图片链接,开发者可以通过下述接口上传图片得到URL,放到图文内容中使用。
上传图文消息内的图片获取URL 请注意,本接口所上传的图片不占用公众号的素材库中图片数量的5000个的限制。图片仅支持jpg/png格式,大小必须在1MB以下。

所以图文消息中图片的src属性必须是腾讯的域名
也就是说必须是以
http://mmbiz.qpic.cn/
开头的,这个需要我们调用如下接口地址

    // 图文内的图片上传接口地址
    private String uploadImageUrl = "https://api.weixin.qq.com/cgi-bin/media/uploadimg?access_token=ACCESS_TOKEN";

如果遇到如下错误

{"errcode":40007,"errmsg":"invalid media_id hint: [51TBDa0350sz63]"}

上传图片的接口地址为下面的

    // 图文封面图片上传接口地址
    private String postImageMediaUrl = "http://file.api.weixin.qq.com/cgi-bin/media/upload?access_token=ACCESS_TOKEN&type=image";

参考下面的文章

微信上传图文消息invalid media_id hint,thumb_media_id怎么获取

2017年7月23日更新

今天我的群发消息遇到问题了,好久没有整理了,好多人都说群发消息有问题,我今天把我的代码贴出来供大家参考,敏感信息我已经屏蔽,有些实体类也没给出,只是一个思路,仅供参考

package com.jrbac.service.wechat;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang3.StringUtils;
import org.json.JSONException;
import org.json.JSONObject;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.jrbac.dao.MessagePushDao;
import com.jrbac.model.wechat.message.GroupSendMessage;
import com.jrbac.model.wechat.messsend.CdptsArticle;
import com.jrbac.model.wechat.messsend.SendToAllNews;
import com.jrbac.model.wechat.messsend.SendToOpenIdPreview;
import com.jrbac.util.HttpUtil;
import com.jrbac.util.ImageUtil;
import com.jrbac.util.PropertiesUtil;
import com.jrbac.util.wechat.AccessTokenUtil;

@Service
public class SendToAllService {
    // 获取accessToken的
    @Autowired
    private AccessTokenUtil accessTokenUtil;
    // 读取配置文件的
    @Autowired
    private PropertiesUtil prop;
    // 将图片下载到本地的
    @Autowired
    private WechatImageDownloadService imageService;
    // 已群发的消息
    @Autowired
    private MessagePushDao messagePush;
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    private final Gson gson = new GsonBuilder().disableHtmlEscaping().create();
    // 图文内的图片上传接口地址
    private String uploadImageUrl = "https://api.weixin.qq.com/cgi-bin/media/uploadimg?access_token=ACCESS_TOKEN";
    // 图文封面图片上传接口地址
    private String postImageMediaUrl = "http://file.api.weixin.qq.com/cgi-bin/media/upload?access_token=ACCESS_TOKEN&type=image";
    // 图文素材上传接口地址
    private String postNewsUrl = "https://api.weixin.qq.com/cgi-bin/media/uploadnews?access_token=ACCESS_TOKEN";
    // 群发接口地址
    private String sendToAllUrl = "https://api.weixin.qq.com/cgi-bin/message/mass/sendall?access_token=ACCESS_TOKEN";
    // 群发预览接口地址
    private String sendToPreviewUrl = "https://api.weixin.qq.com/cgi-bin/message/mass/preview?access_token=ACCESS_TOKEN";
    // 照片下载路径
    private String imageSavePath = "/usr/tmp/images/";

    public void doSend() {
        List<CdptsArticle> toSendNewsList = new ArrayList<>();
        // 1.获取新闻列表
        toSendNewsList = getArticleListFromCdpts();
        // 再去获取一次,有时候第一次获取不到,不知道为什么
        if (toSendNewsList.size() == 0) {
            toSendNewsList = getArticleListFromCdpts();
        }
        // 2.移除已发送的新闻
        toSendNewsList = removeAlreadySentArticle(toSendNewsList);
        if (toSendNewsList.size() == 0) {
            logger.info("----------没有文章需要群发----------");
            return;
        }
        // 最多群发8条消息
        if (toSendNewsList.size() > 8) {
            toSendNewsList = toSendNewsList.subList(0, 8);
        }
        logger.info("----------群发的消息----------");
        for (CdptsArticle article : toSendNewsList) {
            logger.info("----------{}----------", article.getTitle());
        }
        // 3.上传并设置封面图片
        toSendNewsList = setCoverImage(toSendNewsList);
        // 4.群发图文消息json封装
        String groupSendMessageJsonStr = getGroupSendMessageJsonStr(toSendNewsList);
        // 5.上传群发图文消息
        String mediaId = uploadGroupSendMessage(groupSendMessageJsonStr);
        // 5.群发
        if (StringUtils.isNotEmpty(mediaId)) {

            String mode = prop.get("active");
            if (StringUtils.isNotBlank(mode) && mode.equals("dev")) {
                logger.info("测试环境,预览");
                String openId = "你的测试openid";
                sendToPreview(toSendNewsList, openId, mediaId);
            } else {
                logger.info("正式环境,群发");
                sendToAll(toSendNewsList, mediaId);
            }

        }
    }

    public void sendToPreview(List<CdptsArticle> toSendNewsList, String openId, String mediaId) {
        String accessToken = accessTokenUtil.getAccessToken();
        SendToOpenIdPreview sendToPreview = new SendToOpenIdPreview(openId, mediaId);
        String sendToPreviewJsonStr = gson.toJson(sendToPreview);
        logger.info("----------预览的的json:{}----------", sendToPreviewJsonStr);
        String sentToPreviewResult = HttpUtil.sendPost(sendToPreviewUrl.replace("ACCESS_TOKEN", accessToken),
                sendToPreviewJsonStr);
        logger.info("----------预览发送结果:{}----------", sentToPreviewResult);
        checkSendResult(toSendNewsList, sentToPreviewResult);
    }

    public void sendToAll(List<CdptsArticle> toSendNewsList, String mediaId) {
        String accessToken = accessTokenUtil.getAccessToken();
        SendToAllNews sendToAll = new SendToAllNews(mediaId);
        String sendToAllJsonStr = gson.toJson(sendToAll);
        logger.info("----------群发的json数据:{}----------", sendToAllJsonStr);
        String sentToAllResult = HttpUtil.sendPost(sendToAllUrl.replace("ACCESS_TOKEN", accessToken), sendToAllJsonStr);
        logger.info("----------群发结果:{}----------", sentToAllResult);
        checkSendResult(toSendNewsList, sentToAllResult);
    }

    /**
     * 查看消息群发结果,并保存群发数据
     * 
     * @param toSendNewsList
     * @param sentToAllResult
     */
    private void checkSendResult(List<CdptsArticle> toSendNewsList, String sentToAllResult) {
        try {
            JSONObject object = new JSONObject(sentToAllResult);
            int errorCode = (Integer) object.get("errcode");
            if (errorCode == 0) {
                for (CdptsArticle articleSend : toSendNewsList) {
                    messagePush.addArticleToPushed(articleSend);
                }
            } else {
                logger.error("---------群发发生了异常{},---------", sentToAllResult);
            }
        } catch (JSONException e) {
            logger.error("---------群发结果json解析异常:{}\n----------jsonStr:{}---------", e.getMessage(), sentToAllResult);
        }
    }

    private String uploadGroupSendMessage(String groupSendMessageJsonStr) {
        String accessToken = accessTokenUtil.getAccessToken();
        String postNewsResult = HttpUtil.sendPost(postNewsUrl.replace("ACCESS_TOKEN", accessToken),
                groupSendMessageJsonStr);
        logger.info("----------上传群发图文消息的返回结果:{}----------", postNewsResult);
        String mediaId = "";
        try {
            JSONObject object = new JSONObject(postNewsResult);
            mediaId = (String) object.get("media_id");
        } catch (JSONException e) {
            logger.error("上传群发图文消息发生错误{}", postNewsResult);
        }
        return mediaId;
    }

    private String getGroupSendMessageJsonStr(List<CdptsArticle> toSendNewsList) {
        List<GroupSendMessage> groupSendMessageList = new ArrayList<>();
        for (CdptsArticle cdptsArticle : toSendNewsList) {
            GroupSendMessage groupSendMessage = new GroupSendMessage();
            groupSendMessage.setThumb_media_id(cdptsArticle.getImageMediaId());
            groupSendMessage.setAuthor(cdptsArticle.getAuthor());
            groupSendMessage.setTitle(cdptsArticle.getTitle());
            groupSendMessage.setContent_source_url(cdptsArticle.getUrl());
            groupSendMessage.setContent(cdptsArticle.getContent());
            groupSendMessage.setShow_cover_pic(0);
            groupSendMessageList.add(groupSendMessage);
        }
        logger.info("----------最终封装的图文消息列表:{}----------", groupSendMessageList);
        Map<String, Object> jsonMap = new HashMap<>();
        jsonMap.put("articles", groupSendMessageList);
        String groupSendMessageJsonStr = gson.toJson(jsonMap);
        logger.info("----------最终要发送的图文消息json数据:{}----------", groupSendMessageJsonStr);
        return groupSendMessageJsonStr;
    }

    /**
     * 设置图文消息的封面图片
     * 
     * @param newsList
     * @return
     */
    private List<CdptsArticle> setCoverImage(List<CdptsArticle> newsList) {
        String accessToken = accessTokenUtil.getAccessToken();
        // 3.将待发送的新闻填充
        List<CdptsArticle> detialArticleList = new ArrayList<>();
        // 4.封装新闻详情,并得到新闻列表
        for (CdptsArticle article : newsList) {
            CdptsArticle articleWithDetial = getArticleDetialAndReplaceImage(article.getUrl());
            try {
                // 下载封面图片到本地
                String localImage = imageService.saveImageToDisk(articleWithDetial.getImageUrl(), imageSavePath);
                ImageUtil.thumbImage(localImage);
                // 上传封面到微信
                String jsonStr = HttpUtil.sendPost(postImageMediaUrl.replace("ACCESS_TOKEN", accessToken),
                        new File(localImage));
                logger.info("----------上传封面图片返回结果:{}----------" + jsonStr);
                try {
                    JSONObject object = new JSONObject(jsonStr);
                    logger.info("----------得到的封面图片media_id:{}----------", object.get("media_id"));
                    articleWithDetial.setImageMediaId((String) object.get("media_id"));
                } catch (Exception e) {
                    logger.error("----------上传封面发生错误:{}", e.getMessage());
                }
            } catch (Exception e) {
                logger.error("----------下载封面,上传封面到微信过程发生错误:{}", e.getMessage());
            }
            detialArticleList.add(articleWithDetial);
        }
        logger.info("----------最终群发图文列表:{}----------", detialArticleList);
        return detialArticleList;
    }

    /**
     * 从新闻列表中删除已发送的文章
     * 
     * @return
     */
    public List<CdptsArticle> removeAlreadySentArticle(List<CdptsArticle> articleList) {

        List<CdptsArticle> messagePushed = messagePush.queryMessagePushed();

        // 已发送的
        List<String> urlPushedList = new ArrayList<>();
        for (CdptsArticle article : messagePushed) {
            urlPushedList.add(article.getUrl());
        }
        // 删除已发送的
        Iterator<CdptsArticle> iterator = articleList.iterator();
        while (iterator.hasNext()) {
            CdptsArticle article = iterator.next();
            if (urlPushedList.contains(article.getUrl())) {
                iterator.remove();
            }
        }
        return articleList;
    }

    /**
     * 官网获取新闻列表
     * 
     * @return
     */
    public List<CdptsArticle> getArticleListFromCdpts() {
        // 新闻列表地址
        String url = prop.get("allNewsHome");
        logger.info("----------获取群发消息列表地址:{}----------", url);
        List<CdptsArticle> articleList = new ArrayList<>();
        // 得到页面
        String result = HttpUtil.sendGet(url);
        Document doc = Jsoup.parse(result);
        String weiSchoolPrefix = prop.get("weischool");
        // 内容页面
        Elements elements = doc.select("div.m_right");
        for (Element element : elements) {
            Elements contentElements = element.getElementsByTag("ul");
            for (Element contents : contentElements) {
                // System.out.println("内容:");
                Elements liContent = contents.getElementsByTag("li");
                for (Element content : liContent) {
                    CdptsArticle article = new CdptsArticle();
                    // System.out.println(content);
                    String time = content.getElementsByTag("span").first().text();
                    // System.out.println(content.getElementsByTag("span").first().text());
                    article.setTime(time);
                    String title = content.getElementsByTag("a").first().text();
                    article.setTitle(title);
                    String originalUrl = content.select("a[href]").attr("href");
                    article.setUrl(weiSchoolPrefix + originalUrl);
                    articleList.add(article);
                }
            }
        }
        logger.info("----------获取到的新闻列表:{}----------", articleList);
        return articleList;
    }

    // 图片校验
    private static boolean isImage(String imageSrc) {
        String reg = ".+(.JPEG|.jpeg|.JPG|.jpg|.GIF|.gif|.BMP|.bmp|.PNG|.png)$";
        Pattern pattern = Pattern.compile(reg);
        Matcher matcher = pattern.matcher(imageSrc.toLowerCase());
        return (matcher.find());
    }

    /**
     * 获取新闻详情并替换图片为微信的
     * 
     * @param articleUrl
     * @return
     */
    private CdptsArticle getArticleDetialAndReplaceImage(String articleUrl) {
        String accessToken = accessTokenUtil.getAccessToken();
        CdptsArticle article = new CdptsArticle();
        article.setUrl(articleUrl);
        // 具体页面
        String remoteUrl = prop.get("remoteDomain") + "/Article/Index/" + articleUrl.split("/Article/Index/")[1];
        logger.info("----------获取页面详情url:{}----------", remoteUrl);
        // 得到页面
        String result = HttpUtil.sendGet(remoteUrl);
        Document doc = Jsoup.parse(result);
        // 内容页面
        Elements elements = doc.select("div.m_right");
        for (Element element : elements) {
            // System.out.println("原始内容:\n" + element);
            // 作者信息
            Element pageInfo = element.select("p.D_infor").remove(0);
            // 标题
            Element titleInfo = element.select("h1.D_title").remove(0);
            article.setTitle(titleInfo.text());
            String page = pageInfo.toString();
            // System.out.println("作者相关信息:\n" + page);
            // 所在板块
            String module = element.getElementsByTag("h2").first().select("a[href]").text();
            // 页面内容
            String content = element.select("div.r_cont").html();
            // 替换所有的图片路径
            Elements images = element.select("div.r_cont").select("img");
            for (Element image : images) {
                String imageUrl = image.attr("src");
                if(!isImage(imageUrl)){// 不是图片格式,直接删掉
                    // image.attr("src", prop.get("localhost") + "/assets/image/school/bg.jpg");
                    image.remove();
                    continue;
                }else{
                    if (imageUrl.startsWith("http://")) {
                        continue;
                    } else if (imageUrl.startsWith("/Upload")) {
                        image.attr("src", prop.get("remoteDomain") + image.attr("src"));
                    } else {
                        image.attr("src", prop.get("localhost") + "/assets/image/school/bg.jpg");
                    }
                }
                // if (!image.attr("src").startsWith("http://")) {
                // image.attr("src", prop.get("remoteDomain") +
                // image.attr("src"));
                // }
            }
            content = element.select("div.r_cont").html();
            // 作者相关
            // 作者相关
            Elements info = element.getElementsByTag("p").first().getElementsByTag("span");
            article.setTime(info.get(0).text().replace("发表时间: ", ""));
            article.setAuthor(info.get(1).text().replace("发布人:", ""));
            article.setCount(info.get(2).text().replace("点击率:", ""));
            article.setModule(module);
            // System.out.println("[所在板块]\t" + module);
            String contents = content.replace(page, "").replace(titleInfo.toString(), "");
            // System.out.println("[页面内容]\t" + contents);
            Document docImage = Jsoup.parse(contents);
            Elements elementImage = docImage.select("img");
            if (elementImage.size() == 0) {
                article.setImageUrl(prop.get("localhost") + "/assets/image/school/bg.jpg");
            } else {
                article.setImageUrl(elementImage.get(0).attr("src"));
            }
            for (Element imageElement : elementImage) {
                try {
                    // 下载文件
                    logger.info("----------下载页面中的图片src:{}----------", imageElement.attr("src"));
                    String imageRealPath = imageService.saveImageToDisk(imageElement.attr("src"), imageSavePath);
                    logger.info("----------图片保存路径:{}----------", imageRealPath);
                    // 压缩处理
                    ImageUtil.thumbImage(imageRealPath);
                    // 上传到微信
                    logger.info("----------上传到微信----------");
                    String responseStr = HttpUtil.sendPost(uploadImageUrl.replace("ACCESS_TOKEN", accessToken),
                            new File(imageRealPath));
                    logger.info("----------上传结果:{}----------", responseStr);
                    JSONObject object = new JSONObject();
                    try {
                        object = new JSONObject(responseStr);
                        logger.info("----------得到上传图片的url:{}----------", object.get("url"));
                        imageElement.attr("src", (String) object.get("url"));
                    } catch (Exception e) {
                        logger.error("----------上传图片到微信发生了错误:{}" + responseStr);
                    }
                } catch (Exception e) {
                    logger.error("----------下载图片并上传到微信过程发生了错误:{}" + e.getMessage());
                }
            }
            String contentFinal = docImage.select("div.D_cont").html();
            article.setContent(contentFinal);
        }
        logger.info("----------获取页面详情返回结果:{}----------", article);
        return article;
    }

}

参考文献

微信开发文档–群发接口

微信上传图文消息invalid media_id hint,thumb_media_id怎么获取

2017-04-24 17:20:19 lBovinl 阅读数 643
  • PHP开发微信公众平台——天气查询实战项目

    从基础到项目的深入化讲解,手把手带你玩儿转微信公众平台 开发基础准备、 开发模式接入、消息的接收与响应、 被关注回复与关键词回复、图文消息的发送与响应 、获取access_token、上传素材与图片消息回复 、自定义菜单实现、第三方API接口调用 天气查询项目实战

    517 人正在学习 去看看 陈世平

*、一个小小记录的流水账

微信公众号头像设置
微信公众号项目工程搭建
微信公众号接口验证功能
微信公众号令牌获取功能
微信公众号数据库交互设计及其实现
微信公众号消息接收及其回应框架搭建
微信公众号文本消息回应消息功能
微信公众号图片信息回应消息功能
微信公众号图文信息回应消息功能
微信公众号订阅回应消息功能
微信公众号自定义菜单创建功能
微信公众号自定义菜单删除功能
微信公众号获取用户分组功能
微信公众号群发文本功能
微信公众号群发图片功能
微信公众号群发图文功能
微信公众号数据库业务处理功能
微信公众号参数全配置功能
微信公众号定时任务功能
微信公众号系统日志功能

 

革命尚未成功,仍需努力加油啊~

没有更多推荐了,返回首页