在Java开发微信公众号的基础上进行学习。学习素材管理、创建接口等操作。
所有的操作都是在获取access_token的基础上进行操作,access_token是根据appid和addsecret进行获取。(这两个参数对于个人公众号是没有的)
文中的代码只是取关键代码,完整的代码会在文章最后表明git地址。
0.获取测试账号与接口获取AccessToken
在公众号后台的开发者工具点击公众平台测试账号可以获取。

接下来以进入配置即可进行测试获取access_token:

公众平台的API调用所需的access_token的使用及生成方式说明:
1、建议公众号开发者使用中控服务器统一获取和刷新access_token,其他业务逻辑服务器所使用的access_token均来自于该中控服务器,不应该各自去刷新,否则容易造成冲突,导致access_token覆盖而影响业务;
2、目前access_token的有效期通过返回的expire_in来传达,目前是7200秒之内的值。中控服务器需要根据这个有效时间提前去刷新新access_token。在刷新过程中,中控服务器可对外继续输出的老access_token,此时公众平台后台会保证在5分钟内,新老access_token都可用,这保证了第三方业务的平滑过渡;
3、access_token的有效时间可能会在未来有调整,所以中控服务器不仅需要内部定时主动刷新,还需要提供被动刷新access_token的接口,这样便于业务服务器在API调用获知access_token已超时的情况下,可以触发access_token的刷新流程。
接口地址:
https请求方式: GET
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 |
代码如下:
public static String getAccessToken() {
Map<String, Object> param = new HashMap<>();
param.put("grant_type", "client_credential");
param.put("appid", "appid");
param.put("appid", "appid");
String responseStr = HttpUtils.doGetWithParams(ACCESS_TOKEN_URL, param);
if (StringUtils.isNotBlank(responseStr)) {
JSONObject parseObject = JSONObject.parseObject(responseStr);
if (parseObject.containsKey("access_token")) {
return parseObject.getString("access_token");
}
return "";
}
return "";
}
1.素材管理
微信官方提供的素材管理的接口如下:

下面研究几个素材接口的使用。
0.基于HttpClient封装的工具类:
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;
}
}
1.新增临时素材
公众号经常有需要用到一些临时性的多媒体素材的场景,例如在使用接口特别是发送消息时,对多媒体文件、多媒体消息的获取和调用等操作,是通过media_id来进行的。素材管理接口对所有认证的订阅号和服务号开放。
注意点:
1、临时素材media_id是可复用的。
2、媒体文件在微信后台保存时间为3天,即3天后media_id失效。
3、上传临时素材的格式、大小限制与公众平台官网一致。
图片(image): 2M,支持PNG\JPEG\JPG\GIF格式
语音(voice):2M,播放长度不超过60s,支持AMR\MP3格式
视频(video):10MB,支持MP4格式
缩略图(thumb):64KB,支持JPG格式
4、需使用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"}
2. 新增永久素材
对于常用的素材,开发者可通过本接口上传到微信服务器,永久使用。新增的永久素材也可以在公众平台官网素材管理模块中查询管理。
请注意:
1、永久图片素材新增后,将带有URL返回给开发者,开发者可以在腾讯系域名内使用(腾讯系域名外使用,图片将被屏蔽)。
2、公众号的素材库保存总数量有上限:图文消息素材、图片素材上限为100000,其他类型为1000。
3、素材的格式大小等要求与公众平台官网一致:
图片(image): 2M,支持bmp/png/jpeg/jpg/gif格式
语音(voice):2M,播放长度不超过60s,mp3/wma/wav/amr格式
视频(video):10MB,支持MP4格式
缩略图(thumb):64KB,支持JPG格式
4、图文消息的具体内容中,微信后台将过滤外部的图片链接,图片url需通过"上传图文消息内的图片获取URL"接口上传图片获取。
5、"上传图文消息内的图片获取URL"接口所上传的图片,不占用公众号的素材库中图片数量的100000个的限制,图片仅支持jpg/png格式,大小必须在1MB以下。
6、图文消息支持正文中插入自己帐号和其他公众号已群发文章链接的能力。
2.1 新增永久媒体素材
代码:
/**
* 上传永久素材文件
*
* @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也可以直接从浏览器解析
2.2 新增永久图文消息素材
也支持新增永久图文素材
接口描述:
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"}
3. 获取永久素材
(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;
}
测试结果:

2. 用户管理
用户管理提供的接口如下:

在这里测试获取用户基本信息:
在关注者与公众号产生消息交互后,公众号可获得关注者的OpenID(加密后的微信号,每个用户对每个公众号的OpenID是唯一的。对于不同公众号,同一用户的openid不同)。公众号可通过本接口来根据OpenID获取用户基本信息,包括昵称、头像、性别、所在城市、语言和关注时间。
(1)单个获取
接口如下:
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"}
(2)批量获取
接口如下:
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;
}
3.自定义菜单
微信提供的自定义菜单的接口如下:

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}
手机公众号查看效果如下:

3.2 获取自定义菜单配置接口
使用接口创建自定义菜单后,开发者还可使用接口查询自定义菜单的结构。另外请注意,在设置了个性化菜单后,使用本自定义菜单查询接口可以获取默认菜单和全部个性化菜单信息。
接口说明:
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"}]}]}}
3.3 删除自定义菜单
使用接口创建自定义菜单后,开发者还可使用接口删除当前使用的自定义菜单。
代码:
/**
* 删除自定义菜单
*
* @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;
}