最近遇到个需求,对方搞了个公众号,在上面发布了一些图文消息,他们想在子菜单里搞个第三方网页,点击进去能看到这个公众号发布的所有图文消息,我研究了下,具体实现如下:
根据公众平台技术文档所说的,首先需要在微信公众平台上开启开发者密码,
登录微信公众平台官网后,在公众平台官网的开发-基本设置页面,勾选协议成为开发者,点击“修改配置”按钮,填写服务器地址(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 list = new ArrayList<>();
for(String s:parameters) list.add(s);
Collections.sort(list, new Comparator() {
@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();
}
准备工作完成后,我们的服务器就可以通过微信的验证,从它那里拿到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包:
哦忘了说,现在微信公众号修改了规则,还得在微信公众平台设置IP白名单才能拿到AccessToken,这点也需要注意设置,否则会出错。
拿到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 getArticleList(){
String accessJson = null;
try {
accessJson = HttpsRequest.getSSL(ACCESSTOKENURL);
} catch (IOException e) {
e.printStackTrace();
}
if(accessJson!=null) {
HashMap 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 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 articleList = null;
//此处默认拆分多图文消息,数据库里仅存储单图文,如需显示多图文需在前端另作处理,将素材id相同的图文集合起来。
if(jsonArticleList!=null) {
articleList = new ArrayList<>();
JSONArray items = (JSONArray)jsonArticleList.get("item");
for(int i=0;i 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(); //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包:
代码