全部的操做都是在获取access_token的基础上进行操做,access_token是根据appid和addsecret进行获取。(这两个参数对于我的公众号是没有的)html
文中的代码只是取关键代码,完整的代码会在文章最后代表git地址。java
在公众号后台的开发者工具点击公众平台测试帐号能够获取一个测试帐号,该测试帐号能够测试公众号提供的高级接口。git
接下来以进入配置便可进行测试获取access_token:github
公众平台的API调用所需的access_token的使用及生成方式说明:web
一、建议公众号开发者使用中控服务器统一获取和刷新access_token,其余业务逻辑服务器所使用的access_token均来自于该中控服务器,不该该各自去刷新,不然容易形成冲突,致使access_token覆盖而影响业务;面试
二、目前access_token的有效期经过返回的expire_in来传达,目前是7200秒以内的值。中控服务器须要根据这个有效时间提早去刷新新access_token。在刷新过程当中,中控服务器可对外继续输出的老access_token,此时公众平台后台会保证在5分钟内,新老access_token均可用,这保证了第三方业务的平滑过渡;spring
三、access_token的有效时间可能会在将来有调整,因此中控服务器不只须要内部定时主动刷新,还须要提供被动刷新access_token的接口,这样便于业务服务器在API调用获知access_token已超时的状况下,能够触发access_token的刷新流程。数据库
接口地址:apache
https请求方式: GETjson
https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
参数 | 是否必须 | 说明 |
---|---|---|
grant_type | 是 | 获取access_token填写client_credential |
appid | 是 | 第三方用户惟一凭证 |
secret | 是 | 第三方用户惟一凭证密钥,即appsecret |
代码以下:
// 用于管理token /** * 获取到的accessToken */ private static String accessToken; /** * 最后一次获取Access_Token的时间 */ private static Date lastGetAccessTokenTime; public static String getAccessToken() { if (StringUtils.isBlank(accessToken) || isExpiredAccessToken()) { accessToken = null; lastGetAccessTokenTime = null; Map<String, Object> param = new HashMap<>(); param.put("grant_type", "client_credential"); param.put("appid", "appid"); param.put("secret", "appsecret"); String responseStr = HttpUtils.doGetWithParams(ACCESS_TOKEN_URL, param); if (StringUtils.isNotBlank(responseStr)) { JSONObject parseObject = JSONObject.parseObject(responseStr); if (parseObject != null && parseObject.containsKey("access_token")) { accessToken = parseObject.getString("access_token"); lastGetAccessTokenTime = new Date(); LOGGER.debug("调用接口获取accessToken,获取到的信息为: {}", parseObject.toString()); } } } else { LOGGER.debug("使用未过期的accessToken: {}", accessToken); } return accessToken; } private static boolean isExpiredAccessToken() { if (lastGetAccessTokenTime == null) { return true; } // 1.5小时之后的就算失效 long existTime = 5400000L; long now = System.currentTimeMillis(); if (now - lastGetAccessTokenTime.getTime() > existTime) { return true; } return false; }
注意:获取access_token的接口每日调用次数上限为2000次,因此要妥善管理该token。 上面是若是1.5小时就从新获取token,目前公众号的持续时长是7200s,也就是2小时。
微信官方提供的素材管理的接口以下:
下面研究几个素材接口的使用。
package cn.qlq.utils; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.net.URI; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import org.apache.commons.io.IOUtils; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.NameValuePair; import org.apache.http.StatusLine; import org.apache.http.client.HttpClient; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; import org.apache.http.entity.mime.HttpMultipartMode; import org.apache.http.entity.mime.MultipartEntityBuilder; import org.apache.http.entity.mime.content.FileBody; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.client.HttpClients; import org.apache.http.message.BasicNameValuePair; import org.apache.http.protocol.HTTP; import org.apache.http.util.EntityUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * http工具类的使用 * * @author Administrator * */ public class HttpUtils { private static Logger logger = LoggerFactory.getLogger(HttpUtils.class); /** * get请求 * * @return */ public static String doGet(String url, Map params) { try { HttpClient client = new DefaultHttpClient(); // 发送get请求 HttpGet request = new HttpGet(url); HttpResponse response = client.execute(request); /** 请求发送成功,并获得响应 **/ if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { /** 读取服务器返回过来的json字符串数据 **/ String strResult = EntityUtils.toString(response.getEntity(), "utf-8"); return strResult; } } catch (IOException e) { logger.debug("get data error"); } return null; } /** * get请求携带参数(注意只能是英文参数) * * @return */ public static String doGetWithParams(String url, Map params) { try { HttpClient client = new DefaultHttpClient(); // 若是携带参数,从新拼接get参数 if (params != null && params.size() > 0) { StringBuffer sb = new StringBuffer(url); sb.append("?"); for (Object key : params.keySet()) { sb.append(key + "=" + params.get(key) + "&"); } url = sb.toString().substring(0, sb.length() - 1); } // 发送get请求 HttpGet request = new HttpGet(url); HttpResponse response = client.execute(request); /** 请求发送成功,并获得响应 **/ if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { /** 读取服务器返回过来的json字符串数据 **/ String strResult = EntityUtils.toString(response.getEntity(), "utf-8"); return strResult; } } catch (IOException e) { logger.error("get data error"); } return null; } /** * post请求(用于key-value格式的参数) * * @param url * @param params * @return */ public static String doPost(String url, Map params) { BufferedReader in = null; try { // 定义HttpClient HttpClient client = new DefaultHttpClient(); // 实例化HTTP方法 HttpPost request = new HttpPost(); request.setURI(new URI(url)); // 设置参数 List<NameValuePair> nvps = new ArrayList<NameValuePair>(); for (Iterator iter = params.keySet().iterator(); iter.hasNext();) { String name = (String) iter.next(); String value = String.valueOf(params.get(name)); nvps.add(new BasicNameValuePair(name, value)); } request.setEntity(new UrlEncodedFormEntity(nvps, HTTP.UTF_8)); HttpResponse response = client.execute(request); int code = response.getStatusLine().getStatusCode(); if (code == 200) { // 请求成功 in = new BufferedReader(new InputStreamReader(response.getEntity().getContent(), "utf-8")); StringBuffer sb = new StringBuffer(""); String line = ""; String NL = System.getProperty("line.separator"); while ((line = in.readLine()) != null) { sb.append(line + NL); } in.close(); return sb.toString(); } else { // System.out.println("状态码:" + code); return null; } } catch (Exception e) { e.printStackTrace(); return null; } } /** * post请求(用于请求json格式的参数) * * @param url * @param params * @return */ public static String doPost(String url, String params) throws Exception { CloseableHttpClient httpclient = HttpClients.createDefault(); HttpPost httpPost = new HttpPost(url);// 建立httpPost httpPost.setHeader("Accept", "application/json"); httpPost.setHeader("Content-Type", "application/json"); String charSet = "UTF-8"; StringEntity entity = new StringEntity(params, charSet); httpPost.setEntity(entity); CloseableHttpResponse response = null; try { response = httpclient.execute(httpPost); StatusLine status = response.getStatusLine(); int state = status.getStatusCode(); if (state == HttpStatus.SC_OK) { HttpEntity responseEntity = response.getEntity(); String jsonString = EntityUtils.toString(responseEntity); return jsonString; } else { logger.error("请求返回:" + state + "(" + url + ")"); } } finally { if (response != null) { try { response.close(); } catch (IOException e) { e.printStackTrace(); } } try { httpclient.close(); } catch (IOException e) { e.printStackTrace(); } } return null; } /** * * @param url * 上传的URL * @param filePath * 本地路径 * @param fileName * 上传的name(至关于input框的name属性) * @return */ public static String uploadFile(String url, String filePath, String fileName) { CloseableHttpClient httpclient = HttpClientBuilder.create().build(); CloseableHttpResponse response = null; String result = ""; try { HttpPost httppost = new HttpPost(url); // 能够选择文件,也能够选择附加的参数 HttpEntity req = MultipartEntityBuilder.create().setMode(HttpMultipartMode.BROWSER_COMPATIBLE) .addPart(fileName, new FileBody(new File(filePath)))// 上传文件,若是不须要上传文件注掉此行 .build(); httppost.setEntity(req); response = httpclient.execute(httppost); HttpEntity re = response.getEntity(); if (re != null) { result = new BufferedReader(new InputStreamReader(re.getContent())).readLine(); } EntityUtils.consume(re); } catch (Exception e) { } finally { IOUtils.closeQuietly(response); } return result; } }
公众号常常有须要用到一些临时性的多媒体素材的场景,例如在使用接口特别是发送消息时,对多媒体文件、多媒体消息的获取和调用等操做,是经过media_id来进行的。素材管理接口对全部认证的订阅号和服务号开放。
注意点:
一、临时素材media_id是可复用的。
二、媒体文件在微信后台保存时间为3天,即3天后media_id失效。
三、上传临时素材的格式、大小限制与公众平台官网一致。
图片(image): 2M,支持PNG\JPEG\JPG\GIF格式
语音(voice):2M,播放长度不超过60s,支持AMR\MP3格式
视频(video):10MB,支持MP4格式
缩略图(thumb):64KB,支持JPG格式
四、需使用https调用本接口。
代码以下:
/** * 上传临时素材文件 * * @param filePath * 本地资源路径 * @param type * 类型:有图片(image)、语音(voice)、视频(video)和缩略图(thumb) * @return {"type":"TYPE","media_id":"MEDIA_ID","created_at":123456789} */ public static JSONObject uploadTemporaryMaterial(String filePath, String type) { String replacedUrl = URL_UPLOAD__TEMPEORARY_MATERIAL.replace("ACCESS_TOKEN", getAccessToken()).replace("TYPE", type); String uploadFileResult = HttpUtils.uploadFile(replacedUrl, filePath, "media"); if (StringUtils.isNotBlank(uploadFileResult)) { return JSONObject.parseObject(uploadFileResult); } return null; }
测试代码:
private static void uploadTemporaryMaterialTest() { JSONObject uploadPermanentMaterial = uploadTemporaryMaterial("G:/yzm.png", "image"); System.out.println(uploadPermanentMaterial); }
结果:
{"item":[],"media_id":"4PJ0BWSquFXwV6p9abthc8V4-CWhDYJxEOH-LaGvI9cfcwmCwWP9wBH-3iojYNYY","created_at":1572357820,"type":"image"}
对于经常使用的素材,开发者可经过本接口上传到微信服务器,永久使用。新增的永久素材也能够在公众平台官网素材管理模块中查询管理。
请注意:
一、永久图片素材新增后,将带有URL返回给开发者,开发者能够在腾讯系域名内使用(腾讯系域名外使用,图片将被屏蔽)。
二、公众号的素材库保存总数量有上限:图文消息素材、图片素材上限为100000,其余类型为1000。
三、素材的格式大小等要求与公众平台官网一致:
图片(image): 2M,支持bmp/png/jpeg/jpg/gif格式
语音(voice):2M,播放长度不超过60s,mp3/wma/wav/amr格式
视频(video):10MB,支持MP4格式
缩略图(thumb):64KB,支持JPG格式
四、图文消息的具体内容中,微信后台将过滤外部的图片连接,图片url需经过"上传图文消息内的图片获取URL"接口上传图片获取。
五、"上传图文消息内的图片获取URL"接口所上传的图片,不占用公众号的素材库中图片数量的100000个的限制,图片仅支持jpg/png格式,大小必须在1MB如下。
六、图文消息支持正文中插入本身账号和其余公众号已群发文章连接的能力。
代码:
/** * 上传永久素材文件 * * @param filePath * 本地资源路径 * @param type * 类型:有图片(image)、语音(voice)、视频(video)和缩略图(thumb) * @return {"type":"TYPE","media_id":"MEDIA_ID","created_at":123456789} */ public static JSONObject uploadPermanentMaterial(String filePath, String type) { String replacedUrl = URL_UPLOAD__PERMANENT_MATERIAL.replace("ACCESS_TOKEN", getAccessToken()).replace("TYPE", type); String uploadFileResult = HttpUtils.uploadFile(replacedUrl, filePath, "media"); if (StringUtils.isNotBlank(uploadFileResult)) { return JSONObject.parseObject(uploadFileResult); } return null; }
测试代码:
private static void uploadPermanentMaterialTest() { JSONObject uploadPermanentMaterial = uploadPermanentMaterial("G:/yzm.png", "image"); System.out.println(uploadPermanentMaterial); }
结果:
{"item":[],"media_id":"56gT8viUy_wLQ8Q2s5H6L2ozaMo33Iojusr8Xfmyy_I","url":"http://mmbiz.qpic.cn/mmbiz_png/2bviaegGtHCF6iaCgWciblPGzBUtBZCKiaiaG4OXsnf4KWrzteHK5ZLibE9AwicUO7qu3hhgkZGvowN4u6z6PxU80aPoQ/0?wx_fmt=png"}
回传的URL也能够直接从浏览器解析
也支持新增永久图文素材
接口描述:
http请求方式: POST,https协议
https://api.weixin.qq.com/cgi-bin/material/add_news?access_token=ACCESS_TOKE
传递参数实例:
{ "articles": [{ "title": TITLE, "thumb_media_id": THUMB_MEDIA_ID, "author": AUTHOR, "digest": DIGEST, "show_cover_pic": SHOW_COVER_PIC(0 / 1), "content": CONTENT, "content_source_url": CONTENT_SOURCE_URL, "need_open_comment":1, "only_fans_can_comment":1 }, //若新增的是多图文素材,则此处应还有几段articles结构 ] }
代码以下:
/** * * @param title * 标题 * @param thumb_media_id * 图文消息的封面图片素材id(必须是永久mediaID) * @param author * 做者 * @param digest * 图文消息的摘要,仅有单图文消息才有摘要,多图文此处为空。若是本字段为没有填写,则默认抓取正文前64个字。 * @param show_cover_pic * 是否显示封面,0为false,即不显示,1为true,即显示 * @param content * 图文消息的具体内容,支持HTML标签,必须少于2万字符,小于1M,且此处会去除JS,涉及图片url必须来源 * "上传图文消息内的图片获取URL"接口获取。外部图片url将被过滤。 * @param ontent_source_url * 图文消息的原文地址,即点击“阅读原文”后的URL * @param need_open_comment * Uint32 是否打开评论,0不打开,1打开 * @param only_fans_can_comment * Uint32 是否粉丝才可评论,0全部人可评论,1粉丝才可评论 * @return */ public static JSONObject uploadPermanentNews(String title, String thumb_media_id, String author, String digest, String show_cover_pic, String content, String ontent_source_url, String need_open_comment, String only_fans_can_comment) { Map<String, Object> params = new HashMap<>(); params.put("title", title); params.put("thumb_media_id", thumb_media_id); params.put("author", author); params.put("digest", digest); params.put("title", title); params.put("show_cover_pic", show_cover_pic); params.put("content", content); params.put("ontent_source_url", ontent_source_url); params.put("need_open_comment", need_open_comment); params.put("only_fans_can_comment", only_fans_can_comment); List<Map<String, Object>> articles = new ArrayList<>(); articles.add(params); HashMap<Object, Object> param = new HashMap<>(); param.put("articles", articles); String jsonString = JSONObject.toJSONString(param); System.out.println(jsonString); String replacedUrl = URL_UPLOAD__PERMANENT_NEWS.replace("ACCESS_TOKEN", getAccessToken()); String doPost = HttpUtils.doPost(replacedUrl, jsonString); if (StringUtils.isNotBlank(doPost)) { return JSONObject.parseObject(doPost); } return null; }
测试代码:
private static void uploadPermanentNewsTest() { JSONObject uploadFile = uploadPermanentNews("18年写的面试心得", "56gT8viUy_wLQ8Q2s5H6LxrbLx9I-ystQZ1Dv7IkNRo", "qiaozhi", "摘要", "1", "<html><body>111222</body><html>", "https://www.cnblogs.com/qlqwjy/p/9194434.html", "1", "1"); System.out.println(uploadFile); }
结果:
{"item":[],"media_id":"56gT8viUy_wLQ8Q2s5H6L2DeqyqigQj3EdOsec4pRdw"}
(1)获取素材列表
接口描述以下:
http请求方式: POST
https://api.weixin.qq.com/cgi-bin/material/batchget_material?access_token=ACCESS_TOKEN
参数以下:
{ "type":TYPE, "offset":OFFSET, "count":COUNT }
参数 | 是否必须 | 说明 |
---|---|---|
type | 是 | 素材的类型,图片(image)、视频(video)、语音 (voice)、图文(news) |
offset | 是 | 从所有素材的该偏移位置开始返回,0表示从第一个素材 返回 |
count | 是 | 返回素材的数量,取值在1到20之间 |
代码以下:
/** * 获取素材列表 * * @param type * 素材的类型,图片(image)、视频(video)、语音 (voice)、图文(news) * @param offset * 从所有素材的该偏移位置开始返回,0表示从第一个素材 返回 * @param count * 返回素材的数量,取值在1到20之间 * @return */ public static JSONObject listPermanentMaterial(String type, int offset, int count) { String replacedUrl = URL_GET__PERMANENT_MATERIAL_LIST.replace("ACCESS_TOKEN", getAccessToken()); Map<String, Object> params = new LinkedHashMap<>(); params.put("type", type); params.put("offset", offset); params.put("count", count); String uploadFileResult = HttpUtils.doPost(replacedUrl, JSONObject.toJSONString(params)); if (StringUtils.isNotBlank(uploadFileResult)) { return JSONObject.parseObject(uploadFileResult); } return null; }
测试代码以下:
private static void listPermanentMaterialTest() { JSONObject listPermanentMaterial = listPermanentMaterial("news", 0, 20); System.out.println(listPermanentMaterial); }
结果:
{ "item": [{ "content": { "news_item": [{ "content": "<html>111222<html></html></html>", "author": "qiaozhi", "title": "18å¹´å\u0086\u0099ç\u009A\u0084é\u009D¢è¯\u0095å¿\u0083å¾\u0097", "thumb_media_id": "56gT8viUy_wLQ8Q2s5H6LxrbLx9I-ystQZ1Dv7IkNRo", "need_open_comment": 1, "thumb_url": "http://mmbiz.qpic.cn/mmbiz_jpg/2bviaegGtHCGCKPWT3SDPic5I3O7qHeMmKVyP8HdfOYB6mcCBx9Laib5VhkcSqvscYlu9YksLKvFSLVyJU99wLz5A/0?wx_fmt=jpeg", "show_cover_pic": 1, "content_source_url": "", "digest": "æ\u0091\u0098è¦\u0081", "only_fans_can_comment": 1, "url": "http://mp.weixin.qq.com/s?__biz=MzAxNTQxODYyMA==&mid=100000007&idx=1&sn=19cb106633ee6fd8cf5035ccc7c97bc2&chksm=1b8514942cf29d82b21954855b21ffb3dd47b110306e67481b4a98dc50a78fb247b47b4bdf61#rd" }], "update_time": 1572401773, "create_time": 1572401773 }, "update_time": 1572401773, "media_id": "56gT8viUy_wLQ8Q2s5H6L2DeqyqigQj3EdOsec4pRdw" }], "item_count": 1, "total_count": 1 }
(2)获取永久素材信息:
代码以下:
/** * 获取永久素材信息 * * @param MEDIA_ID * mediaId * @return */ public static JSONObject getPermanentMaterial(String mediaId) { String replacedUrl = URL_GET__PERMANENT_MATERIAL.replace("ACCESS_TOKEN", getAccessToken()); Map<String, Object> params = new LinkedHashMap<>(); params.put("media_id", mediaId); String uploadFileResult = HttpUtils.doPost(replacedUrl, JSONObject.toJSONString(params)); if (StringUtils.isNotBlank(uploadFileResult)) { return JSONObject.parseObject(uploadFileResult); } return null; }
测试代码:
private static void getPermanentMaterialTest() { JSONObject permanentMaterial = getPermanentMaterial("56gT8viUy_wLQ8Q2s5H6L2DeqyqigQj3EdOsec4pRdw"); System.out.println(permanentMaterial); }
结果:
{ "news_item": [{ "content": "<html>111222<html></html></html>", "author": "qiaozhi", "title": "18å¹´å\u0086\u0099ç\u009A\u0084é\u009D¢è¯\u0095å¿\u0083å¾\u0097", "thumb_media_id": "56gT8viUy_wLQ8Q2s5H6LxrbLx9I-ystQZ1Dv7IkNRo", "need_open_comment": 1, "thumb_url": "http://mmbiz.qpic.cn/mmbiz_jpg/2bviaegGtHCGCKPWT3SDPic5I3O7qHeMmKVyP8HdfOYB6mcCBx9Laib5VhkcSqvscYlu9YksLKvFSLVyJU99wLz5A/0?wx_fmt=jpeg", "show_cover_pic": 1, "content_source_url": "", "digest": "æ\u0091\u0098è¦\u0081", "only_fans_can_comment": 1, "url": "http://mp.weixin.qq.com/s?__biz=MzAxNTQxODYyMA==&mid=100000007&idx=1&sn=19cb106633ee6fd8cf5035ccc7c97bc2&chksm=1b8514942cf29d82b21954855b21ffb3dd47b110306e67481b4a98dc50a78fb247b47b4bdf61#rd" }], "update_time": 1572401773, "create_time": 1572401773 }
返回参数说明:
参数 | 描述 |
---|---|
title | 图文消息的标题 |
thumb_media_id | 图文消息的封面图片素材id(必须是永久mediaID) |
show_cover_pic | 是否显示封面,0为false,即不显示,1为true,即显示 |
author | 做者 |
digest | 图文消息的摘要,仅有单图文消息才有摘要,多图文此处为空 |
content | 图文消息的具体内容,支持HTML标签,必须少于2万字符,小于1M,且此处会去除JS |
url | 图文页的URL |
content_source_url | 图文消息的原文地址,即点击“阅读原文”后的URL |
补充:素材也能够做为消息进行回传,好比回传图片消息的时候使用素材
例如修改咱们上篇回传图片消息的mediaId为上面的永久素材的ID:
/** * 处理图片消息(回复一条图片消息) * * @param message * @return */ private static AbstractResponseMessage handleImageMessage(Map<String, Object> message) { ImageMessage imageMessage = BeanUtils.map2Bean(message, ImageMessage.class, true); String url = imageMessage.getPicUrl(); // 能够用图片路径作其余操做 if (StringUtils.isNotBlank(url)) { System.out.println("您接收到的图片消息url为: " + url); } // 回传一条图片消息 ImageResponseMessage responseMessage = new ImageResponseMessage(); responseMessage.setCreateTime(System.currentTimeMillis()); responseMessage.setFromUserName(imageMessage.getToUserName()); responseMessage.setToUserName(imageMessage.getFromUserName()); responseMessage.setMediaId("56gT8viUy_wLQ8Q2s5H6L2ozaMo33Iojusr8Xfmyy_I"); responseMessage.setMsgType(MESSAGE_IMAGE); return responseMessage; }
测试结果:
用户管理提供的接口以下:
在这里测试获取用户基本信息:
在关注者与公众号产生消息交互后,公众号可得到关注者的OpenID(加密后的微信号,每一个用户对每一个公众号的OpenID是惟一的。对于不一样公众号,同一用户的openid不一样)。公众号可经过本接口来根据OpenID获取用户基本信息,包括昵称、头像、性别、所在城市、语言和关注时间。
接口以下:
http请求方式: GET
https://api.weixin.qq.com/cgi-bin/user/info?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN
参数 | 是否必须 | 说明 |
---|---|---|
access_token | 是 | 调用接口凭证 |
openid | 是 | 普通用户的标识,对当前公众号惟一 |
lang | 否 | 返回国家地区语言版本,zh_CN 简体,zh_TW 繁体,en 英语 |
openId能够在用户关注的时候获取到,或者在接收消息的时候也能够获取到。
代码以下:
/** * 获取用户信息 * * @param openId * 普通用户的标识,对当前公众号惟一 * @return JSON数据格式的用户信息 */ public static JSONObject userInfo(String openId) { String replacedUrl = URL_GET__USER_INFO.replace("ACCESS_TOKEN", getAccessToken()).replace("OPENID", openId); String uploadFileResult = HttpUtils.doGet(replacedUrl, null); if (StringUtils.isNotBlank(uploadFileResult)) { return JSONObject.parseObject(uploadFileResult); } return null; }
测试代码:
private static void userInfoTest() { JSONObject userInfo = userInfo("openId"); System.out.println(userInfo); }
结果:
{"sex":1,"qr_scene":0,"nickname":"空城、旧梦","remark":"","qr_scene_str":"","city":"西城","country":"中国","subscribe_time":1572242301,"tagid_list":[],"subscribe_scene":"ADD_SCENE_QR_CODE","subscribe":1,"province":"北京","openid":"openId","language":"zh_CN","groupid":0,"headimgurl":"http://thirdwx.qlogo.cn/mmopen/qiaZvpkPgmiaNGLEXrgb7C9RQf8JYxrHFgPiaZHx8AoAdxwx78m3v9Vwhkaa4NrZxGwVniatWEWjU0xl3FxICnpjVZs7IkuyrO2t/132"}
接口以下:
http请求方式: POST
https://api.weixin.qq.com/cgi-bin/user/info/batchget?access_token=ACCESS_TOKEN
POST数据示例:
{
"user_list": [
{
"openid": "otvxTs4dckWG7imySrJd6jSi0CWE",
"lang": "zh_CN"
},
{
"openid": "otvxTs_JZ6SEiP0imdhpi50fuSZg",
"lang": "zh_CN"
}
]
}
代码以下:
/** * 批量获取用户信息的 * * @return JSON数据格式的用户信息 */ public static JSONObject batchGetUserInfo(String... openIds) { List<Map<String, Object>> userOpenIds = new LinkedList<>(); Map<String, Object> tmpMap = null; for (String openId : openIds) { tmpMap = new HashMap<>(); tmpMap.put("openid", openId); // 默认就是zh_CN,能够不传 tmpMap.put("lang", "zh_CN"); userOpenIds.add(tmpMap); } Map<String, Object> params = new HashMap<>(); params.put("user_list", userOpenIds); String param = JSONObject.toJSONString(params); System.out.println(param); String replacedUrl = URL_GET__USER_INFO_BATCH.replace("ACCESS_TOKEN", getAccessToken()); String uploadFileResult = HttpUtils.doPost(replacedUrl, param); if (StringUtils.isNotBlank(uploadFileResult)) { return JSONObject.parseObject(uploadFileResult); } return null; }
微信提供的自定义菜单的接口以下:
1.自定义菜单最多包括3个一级菜单,每一个一级菜单最多包含5个二级菜单。
2.一级菜单最多4个汉字,二级菜单最多7个汉字,多出来的部分将会以“...”代替。
3.建立自定义菜单后,菜单的刷新策略是,在用户进入公众号会话页或公众号profile页时,若是发现上一次拉取菜单的请求在5分钟之前,就会拉取一下菜单,若是菜单有更新,就会刷新客户端的菜单。测试时能够尝试取消关注公众帐号后再次关注,则能够看到建立后的效果。
自定义菜单接口可实现多种类型按钮,以下:(最经常使用的就是Click和View类型的按钮)
1.click:点击推事件用户点击click类型按钮后,微信服务器会经过消息接口推送消息类型为event的结构给开发者(参考消息接口指南),而且带上按钮中开发者填写的key值,开发者能够经过自定义的key值与用户进行交互;
2.view:跳转URL用户点击view类型按钮后,微信客户端将会打开开发者在按钮中填写的网页URL,可与网页受权获取用户基本信息接口结合,得到用户基本信息。
3.scancode_push:扫码推事件用户点击按钮后,微信客户端将调起扫一扫工具,完成扫码操做后显示扫描结果(若是是URL,将进入URL),且会将扫码的结果传给开发者,开发者能够下发消息。
4.scancode_waitmsg:扫码推事件且弹出“消息接收中”提示框用户点击按钮后,微信客户端将调起扫一扫工具,完成扫码操做后,将扫码的结果传给开发者,同时收起扫一扫工具,而后弹出“消息接收中”提示框,随后可能会收到开发者下发的消息。
5.pic_sysphoto:弹出系统拍照发图用户点击按钮后,微信客户端将调起系统相机,完成拍照操做后,会将拍摄的相片发送给开发者,并推送事件给开发者,同时收起系统相机,随后可能会收到开发者下发的消息。
6.pic_photo_or_album:弹出拍照或者相册发图用户点击按钮后,微信客户端将弹出选择器供用户选择“拍照”或者“从手机相册选择”。用户选择后即走其余两种流程。
7.pic_weixin:弹出微信相册发图器用户点击按钮后,微信客户端将调起微信相册,完成选择操做后,将选择的相片发送给开发者的服务器,并推送事件给开发者,同时收起相册,随后可能会收到开发者下发的消息。
8.location_select:弹出地理位置选择器用户点击按钮后,微信客户端将调起地理位置选择工具,完成选择操做后,将选择的地理位置发送给开发者的服务器,同时收起位置选择工具,随后可能会收到开发者下发的消息。(有用)
9.media_id:下发消息(除文本消息)用户点击media_id类型按钮后,微信服务器会将开发者填写的永久素材id对应的素材下发给用户,永久素材类型能够是图片、音频、视频、图文消息。请注意:永久素材id必须是在“素材管理/新增永久素材”接口上传后得到的合法id。
10.view_limited:跳转图文消息URL用户点击view_limited类型按钮后,微信客户端将打开开发者在按钮中填写的永久素材id对应的图文消息URL,永久素材类型只支持图文消息。请注意:永久素材id必须是在“素材管理/新增永久素材”接口上传后得到的合法id。
请注意,3到8的全部事件,仅支持微信iPhone5.4.1以上版本,和Android5.4以上版本的微信用户,旧版本微信用户点击后将没有回应,开发者也不能正常接收到事件推送。9和10,是专门给第三方平台旗下未微信认证(具体而言,是资质认证未经过)的订阅号准备的事件类型,它们是没有事件推送的,能力相对受限,其余类型的公众号没必要使用。
接口说明:
http请求方式:POST(请使用https协议)
https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN
click和view的请求示例:
{
"button":[
{
"type":"click",
"name":"今日歌曲",
"key":"V1001_TODAY_MUSIC"
},
{
"name":"菜单",
"sub_button":[
{
"type":"view",
"name":"搜索",
"url":"http://www.soso.com/"
},
{
"type":"miniprogram",
"name":"wxa",
"url":"http://mp.weixin.qq.com",
"appid":"wx286b93c14bbf93aa",
"pagepath":"pages/lunar/index"
},
{
"type":"click",
"name":"赞一下咱们",
"key":"V1001_GOOD"
}]
}]
}
其余新增按钮类型的请求示例:
{
"button": [
{
"name": "扫码",
"sub_button": [
{
"type": "scancode_waitmsg",
"name": "扫码带提示",
"key": "rselfmenu_0_0",
"sub_button": [ ]
},
{
"type": "scancode_push",
"name": "扫码推事件",
"key": "rselfmenu_0_1",
"sub_button": [ ]
}
]
},
{
"name": "发图",
"sub_button": [
{
"type": "pic_sysphoto",
"name": "系统拍照发图",
"key": "rselfmenu_1_0",
"sub_button": [ ]
},
{
"type": "pic_photo_or_album",
"name": "拍照或者相册发图",
"key": "rselfmenu_1_1",
"sub_button": [ ]
},
{
"type": "pic_weixin",
"name": "微信相册发图",
"key": "rselfmenu_1_2",
"sub_button": [ ]
}
]
},
{
"name": "发送位置",
"type": "location_select",
"key": "rselfmenu_2_0"
},
{
"type": "media_id",
"name": "图片",
"media_id": "MEDIA_ID1"
},
{
"type": "view_limited",
"name": "图文消息",
"media_id": "MEDIA_ID2"
}
]
}
代码以下:
菜单实体类:
package cn.qlq.bean.weixin.menu; public class Menu { private Button[] button; public Button[] getButton() { return button; } public void setButton(Button[] button) { this.button = button; } }
package cn.qlq.bean.weixin.menu; public class Button { private String type; private String name; private Button[] sub_button; public String getType() { return type; } public void setType(String type) { this.type = type; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Button[] getSub_button() { return sub_button; } public void setSub_button(Button[] sub_button) { this.sub_button = sub_button; } }
package cn.qlq.bean.weixin.menu; public class ClickButton extends Button { private String key; public String getKey() { return key; } public void setKey(String key) { this.key = key; } }
package cn.qlq.bean.weixin.menu; public class ViewButton extends Button { private String url; public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } }
建立菜单工具类:
/** * 建立菜单 * * @param menu * JSON格式的菜单数据 * @return */ public static JSONObject createMenu(String menu) { String replacedUrl = URL_CREATE_MENU.replace("ACCESS_TOKEN", getAccessToken()); String uploadFileResult = HttpUtils.doPost(replacedUrl, menu); if (StringUtils.isNotBlank(uploadFileResult)) { return JSONObject.parseObject(uploadFileResult); } return null; } /** * 组装菜单 * * @return */ private static Menu initMenu() { Menu menu = new Menu(); ClickButton button11 = new ClickButton(); button11.setName("click菜单"); button11.setType("click"); button11.setKey("11"); ViewButton button21 = new ViewButton(); button21.setName("view菜单"); button21.setType("view"); button21.setUrl("http://b4a819d0.ngrok.io/index.html"); ClickButton button31 = new ClickButton(); button31.setName("扫码事件"); button31.setType("scancode_push"); button31.setKey("31"); ClickButton button32 = new ClickButton(); button32.setName("地理位置"); button32.setType("location_select"); button32.setKey("32"); Button button = new Button(); button.setName("菜单"); button.setSub_button(new Button[] { button31, button32 }); menu.setButton(new Button[] { button11, button21, button }); return menu; }
修改接收事件的代码(在前面入门写的处理消息),处理CLICK事件和VIEW事件。(CLICK和VIEW事件会多传一个EventKey参数,对于CLICK事件是按钮的key,对于VIEW事件是跳转的URL)
/** * 处理事件消息(订阅和取消订阅) * * @param messageMap * @return */ private static AbstractResponseMessage handleEventMessage(Map<String, Object> messageMap) { EventMessage message = BeanUtils.map2Bean(messageMap, EventMessage.class, true); String event = message.getEvent(); if (StringUtils.isNotBlank(event)) { System.out.println("您接收到事件消息, 事件类型为: " + event); } // 关注的时候 if (MESSAGE_SUBSCRIBE.equals(event)) { System.out.println("这里能够向数据库插入数据"); String responseMsg = MessageUtils.subscribeWelcomeText(); return MessageUtils.initTextMessage(message.getToUserName(), message.getFromUserName(), responseMsg); } // 取消关注(不用回传消息.须要将用户产生的数据删除) if (MESSAGE_SUBSCRIBE.equals(event)) { System.out.println("这时须要从数据删除 " + message.getFromUserName() + " 用户产生的相关数据"); return null; } ClickViewEventMessage map2Bean = BeanUtils.map2Bean(messageMap, ClickViewEventMessage.class, true); // 点击自定义的点击菜单事件 if (MESSAGE_CLICK.equals(event)) { String eventKey = map2Bean.getEventKey(); String content = "您点击的按钮的key为: " + eventKey; return MessageUtils.initTextMessage(map2Bean.getToUserName(), map2Bean.getFromUserName(), content); } // VIEW菜单的事件 if (MESSAGE_VIEW.equals(event)) { String eventKey = map2Bean.getEventKey(); String content = "您点击的按钮跳转的URL为: " + eventKey; return MessageUtils.initTextMessage(map2Bean.getToUserName(), map2Bean.getFromUserName(), content); } return null; }
测试代码:
private static void createMenuTest() { Menu initMenu = initMenu(); JSONObject createMenu = createMenu(JSONObject.toJSONString(initMenu)); System.out.println(createMenu); }
结果:
{"errmsg":"ok","errcode":0}
手机公众号查看效果以下:
使用接口建立自定义菜单后,开发者还可以使用接口查询自定义菜单的结构。另外请注意,在设置了个性化菜单后,使用本自定义菜单查询接口能够获取默认菜单和所有个性化菜单信息。
接口说明:
http请求方式:GET
https://api.weixin.qq.com/cgi-bin/menu/get?access_token=ACCESS_TOKEN
代码:
/** * 获取自定义菜单 * * @return */ public static JSONObject getMenu() { String replacedUrl = URL_GET_MENU.replace("ACCESS_TOKEN", getAccessToken()); String uploadFileResult = HttpUtils.doGet(replacedUrl, null); if (StringUtils.isNotBlank(uploadFileResult)) { return JSONObject.parseObject(uploadFileResult); } return null; }
结果:
{"menu":{"button":[{"name":"click菜单","sub_button":[],"type":"click","key":"11"},{"name":"view菜单","sub_button":[],"type":"view","url":"http://b4a819d0.ngrok.io/index.html"},{"name":"菜单","sub_button":[{"name":"扫码事件","sub_button":[],"type":"scancode_push","key":"31"},{"name":"地理位置","sub_button":[],"type":"location_select","key":"32"}]}]}}
使用接口建立自定义菜单后,开发者还可以使用接口删除当前使用的自定义菜单。该接口会删除全部菜单。
代码:
/** * 删除自定义菜单 * * @return */ public static JSONObject deleteMenu() { String replacedUrl = URL_DELETE_MENU.replace("ACCESS_TOKEN", getAccessToken()); String uploadFileResult = HttpUtils.doGet(replacedUrl, null); if (StringUtils.isNotBlank(uploadFileResult)) { return JSONObject.parseObject(uploadFileResult); } return null; }
完整的代码接收消息与回传消息代码参考:https://github.com/qiao-zhi/springboot-ssm.git