- 1.公众平台概述
- 1.1 公众平台概述
- 1.2 入门指引
- 2.对接流程
- 2.1 接入概述
- 2.2 填写服务器配置
- 2.3 接口域名说明
- 2.4 获取Access token
- 3.项目实现
- 3.1 创建项目
- 3.2 项目依赖
- 3.3 项目配置
- 3.4 加解密工具
- 3.5 代码实现
- 4.测试验证
微信公众平台是运营者通过公众号为微信用户提供资讯和服务的平台,而公众平台开发接口则是提供服务的基础,开发者在公众平台网站中创建公众号、获取接口权限后,实现后台接口对接微信公众号的。
官方文档:
https://developers.weixin.qq.com/doc/offiaccount/Getting_Started/Overview.html
为了降低门槛,弥补不足,我们编写了《开发者指引》来讲解微信开放平台的基础常见功能,旨在帮助大家入门微信开放平台的开发者模式。
已熟知接口使用或有一定公众平台开发经验的开发者,请直接跳过本文。这篇文章不会给你带来厉害的编码技巧亦或接口的深层次讲解。对于现有接口存在的疑问,可访问 #公众号社区 发帖交流、联系腾讯客服或使用微信反馈。
对接公众平台与对接开放平台相比比较简单,整个过程只需要配置好开发配置,通过AppID与AppSecret获取token即可,后续所有接口都是通过token调用。
开发者指引:
https://developers.weixin.qq.com/doc/offiaccount/Getting_Started/Getting_Started_Guide.html
接入微信公众平台开发,开发者需要按照如下步骤完成:
1、填写服务器配置
2、验证服务器地址的有效性
3、依据接口文档实现业务逻辑
注意:
1.填写服务器配置前提是注册公众号,并拥有相关的权限,注册不同公众号类型,是否认证权限不同。
2.配置服务器还需要能够对接外网回调,这个需要提前内网穿透(Sunny-Ngrok使用教程),或者云服务器也可。
不同类型公众号权限说明:
https://developers.weixin.qq.com/doc/offiaccount/Getting_Started/Explanation_of_interface_privileges.html
全局返回码说明:
https://developers.weixin.qq.com/doc/offiaccount/Getting_Started/Global_Return_Code.html
开发者工具
https://mp.weixin.qq.com/cgi-bin/frame?t=advanced/dev_tools_frame&nav=10049&token=809046757&lang=zh_CN
1.配置说明
登录微信公众平台官网后,在公众平台官网的开发-基本设置页面,勾选协议成为开发者,点击“修改配置”按钮,填写服务器地址(URL)、Token和EncodingAESKey,其中URL是开发者用来接收微信消息和事件的接口URL。Token可由开发者可以任意填写,用作生成签名(该Token会和接口URL中包含的Token进行比对,从而验证安全性)。EncodingAESKey由开发者手动填写或随机生成,将用作消息体加解密密钥。
同时,开发者可选择消息加解密方式:明文模式、兼容模式和安全模式。模式的选择与服务器配置在提交后都会立即生效,请开发者谨慎填写及选择。加解密方式的默认状态为明文模式,选择兼容模式和安全模式需要提前配置好相关加解密代码,详情请参考消息体签名及加解密部分的文档 。
填写服务器配置说明:
https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Access_Overview.html
2.配置信息状态
3.注意事项
服务器配置开启状态则菜单配置不可用,关闭状态才能后台配置,也就是接口调用与后台配置互斥。
因为个人订阅号无法认证,所以可以使用添加测试账号。
路径:公众号 - 设置与开发 - 开发者工具 – 公众平台测试账号
https://mp.weixin.qq.com/debug/cgi-bin/sandboxinfo?action=showinfo&t=sandbox/index
公众平台接口域名说明
开发者可以根据自己的服务器部署情况,选择最佳的接入域名(延时更低,稳定性更高)。除此之外,可以将其他接入域名用作容灾用途,当网络链路发生故障时,可以考虑选择备用域名来接入。请开发者使用域名进行API接口请求,不要使用IP作为访问。若有需要开通网络策略,开发者可以从获取微信服务器IP地址定期获取最新的IP信息。
通用域名(api.weixin.qq.com),使用该域名将访问官方指定就近的接入点;
通用异地容灾域名(api2.weixin.qq.com),当上述域名不可访问时可改访问此域名;
上海域名(sh.api.weixin.qq.com),使用该域名将访问上海的接入点;
深圳域名(sz.api.weixin.qq.com),使用该域名将访问深圳的接入点;
香港域名(hk.api.weixin.qq.com),使用该域名将访问香港的接入点。
公众平台接口域名说明:
https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Interface_field_description.html
access_token是公众号的全局唯一接口调用凭据,公众号调用各接口时都需使用access_token。开发者需要进行妥善保存。access_token的存储至少要保留512个字符空间。access_token的有效期目前为2个小时,需定时刷新,重复获取将导致上次获取的access_token失效。
获取Access token:
https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.html
创建springboot项目,代码还有很多可以优化的地方,比如消息可以做策略,代码中的枚举类型,常量,授权信息可以放缓存等等,因为这里只做测试,可以结合测试自行优化。
为了方便测试,这些授权参数放到集合内存里面了,所以每次重启丢失,后期是要优化到redis缓存持久化的,验证票据有效期12小时所以为了测试可以写死,其他的从内存取就可以了。
另外可以有许多开源的微信开发框架可以参考学习使用,这里依赖引入微信开发框架weixin-Java-tools binarywang只是为了学习研究,并为使用。
weixin-Java-tools:https://gitee.com/itwu/weixin-java-tools#https://mp.weixin.qq.com/s/nIk_xOf6dxkhKfqq830Cuw
3.3 项目配置4.0.0 org.springframework.boot spring-boot-starter-parent 2.5.4 com.zrj 0.0.1-SNAPSHOT Demo project for Spring Boot 1.8 org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-test test org.mybatis.spring.boot mybatis-spring-boot-starter 2.1.4 mysql mysql-connector-java runtime org.springframework.boot spring-boot-starter-jdbc com.google.guava guava 30.1.1-jre cn.hutool hutool-all 5.6.3 org.projectlombok lombok true com.alibaba fastjson 1.2.72 com.google.code.gson gson 2.8.6 io.springfox springfox-swagger2 2.6.1 io.springfox springfox-swagger-ui 2.6.1 commons-codec commons-codec 1.9 junit junit 4.13.2 org.dom4j dom4j 2.1.1 javax.validation validation-api 2.0.1.Final org.hibernate.validator hibernate-validator 6.0.13.Final org.apache.httpcomponents httpcore 4.4.14 org.apache.httpcomponents httpclient 4.5.13 com.github.binarywang weixin-java-common 4.1.0 com.github.binarywang weixin-java-mp 4.1.0 com.github.binarywang weixin-java-open 4.1.0 org.springframework.boot spring-boot-maven-plugin org.projectlombok lombok
这里暂时没用到
3.4 加解密工具消息加解密说明:
https://developers.weixin.qq.com/doc/oplatform/Third-party_Platforms/2.0/api/Before_Develop/Message_encryption_and_decryption.html
加解密工具下载地址:https://res.wx.qq.com/op_res/-serEQ6xSDVIjfoOHcX78T1JAYX-pM_fghzfiNYoD8uHVd3fOeC0PC_pvlg4-kmP
1.下载解压后根据对应的语言选择对应的工具类,直接打成jar包放入私服引用或者直接放入项目引用即可。
2.需要导入架包commons-codec-1.9(或commons-codec-1.8等其他版本)
3.这里需要注意的是加解密的时候与公众号加解密参数不同,因为返回的xml不一样,如果使用公众号的加解密会出现空指针,需要根据返回结果调整,公众号加密后xml是ToUser与Encrypt标签,开放平台的返回是AppId与Encrypt标签,其中Encrypt是加密内容。
依赖替换
加解密调整类XMLParse
// ------------------------------------------------------------------------
package com.zrj.wechat.aes;
import java.io.StringReader;
import javax.xml.parsers.documentBuilder;
import javax.xml.parsers.documentBuilderFactory;
import org.w3c.dom.document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
class XMLParse {
public static Object[] extract(String xmltext) throws AesException {
Object[] result = new Object[3];
try {
documentBuilderFactory dbf = documentBuilderFactory.newInstance();
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
dbf.setXIncludeAware(false);
dbf.setExpandEntityReferences(false);
documentBuilder db = dbf.newdocumentBuilder();
StringReader sr = new StringReader(xmltext);
InputSource is = new InputSource(sr);
document document = db.parse(is);
Element root = document.getdocumentElement();
//公众平台解密字段
NodeList nodelist1 = root.getElementsByTagName("Encrypt");
NodeList nodelist2 = root.getElementsByTagName("ToUserName");
//开放平台解密字段
//NodeList nodelist1 = root.getElementsByTagName("Encrypt");
//NodeList nodelist2 = root.getElementsByTagName("AppId");
result[0] = 0;
result[1] = nodelist1.item(0).getTextContent();
result[2] = nodelist2.item(0).getTextContent();
return result;
} catch (Exception e) {
e.printStackTrace();
throw new AesException(AesException.ParseXmlError);
}
}
public static Object[] extractOP(String xmltext) throws AesException {
Object[] result = new Object[3];
try {
documentBuilderFactory dbf = documentBuilderFactory.newInstance();
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
dbf.setFeature("http://xml.org/sax/features/external-general-entities", false);
dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
dbf.setXIncludeAware(false);
dbf.setExpandEntityReferences(false);
documentBuilder db = dbf.newdocumentBuilder();
StringReader sr = new StringReader(xmltext);
InputSource is = new InputSource(sr);
document document = db.parse(is);
Element root = document.getdocumentElement();
NodeList nodelist1 = root.getElementsByTagName("Encrypt");
//NodeList nodelist2 = root.getElementsByTagName("ToUserName");
NodeList nodelist2 = root.getElementsByTagName("AppId");
result[0] = 0;
result[1] = nodelist1.item(0).getTextContent();
result[2] = nodelist2.item(0).getTextContent();
return result;
} catch (Exception e) {
e.printStackTrace();
throw new AesException(AesException.ParseXmlError);
}
}
public static String generate(String encrypt, String signature, String timestamp, String nonce) {
String format = "n" + " n"
+ " n"
+ "%3$s n" + " n" + " ";
return String.format(format, encrypt, signature, timestamp, nonce);
}
}
3.5 代码实现
WechatUtils
package com.zrj.wechat.utils;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.dom4j.document;
import org.dom4j.documentHelper;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;
import java.io.ByteArrayInputStream;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.apache.http.HttpEntity;
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.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
@Slf4j
public class WechatUtils {
private static final String AUTHORIZER_ACCESS_TOKEN = "ACCESS_TOKEN";
private static final String COMPONENT_ACCESS_TOKEN = "COMPONENT_ACCESS_TOKEN";
public static String getAuthorizerAccessToken() {
// 从缓存获取授权信息,使用授权码获取授权信息
String authorizerAccessToken = "";
return authorizerAccessToken;
}
public static String replaceComponentAccessToken(String url, String componentAccessToken) {
if (StrUtil.isEmpty(componentAccessToken)) {
return url;
}
return url.replaceAll(COMPONENT_ACCESS_TOKEN, componentAccessToken);
}
public static String replaceAuthorizerAccessToken(String url, String authorizerAccessToken) {
return url.replaceAll(AUTHORIZER_ACCESS_TOKEN, authorizerAccessToken);
}
public static boolean validWechatResult(JSONObject jsonObject) {
if (jsonObject == null) {
log.error("执行失败,响应结果为空,jsonObject:{}", jsonObject);
return false;
}
int errcode = jsonObject.getIntValue("errcode");
String errmsg = jsonObject.getString("errmsg");
if (0 != errcode) {
log.error("执行失败,响应信息 errcode:{} errmsg:{}", errcode, errmsg);
return false;
}
log.error("执行成功,响应信息 errcode:{} errmsg:{}", errcode, errmsg);
return true;
}
public static JSONObject doGetstr(String url) {
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpGet = new HttpGet(url);
JSONObject jsonObject = null;
try {
CloseableHttpResponse response = httpclient.execute(httpGet);
HttpEntity entity = response.getEntity();
if (entity != null) {
String result = EntityUtils.toString(entity);
//jsonObject =JSONObject.fromObject(result);
jsonObject = JSONObject.parseObject(result);
}
} catch (Exception e) {
e.printStackTrace();
}
return jsonObject;
}
public static JSONObject doPoststr(String url, String outStr) {
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpPost httpPost = new HttpPost(url);
JSONObject jsonObject = null;
try {
httpPost.setEntity(new StringEntity(outStr, "utf-8"));
CloseableHttpResponse response = httpclient.execute(httpPost);
String result = EntityUtils.toString(response.getEntity(), "utf-8");
//jsonObject =JSONObject.fromObject(result);
jsonObject = JSONObject.parseObject(result);
} catch (Exception e) {
e.printStackTrace();
}
return jsonObject;
}
public static Map xmlToMap(String xml) throws Exception {
Map respMap = new HashMap<>(16);
SAXReader reader = new SAXReader();
document doc = reader.read(new ByteArrayInputStream(xml.getBytes("utf-8")));
Element root = doc.getRootElement();
xmlToMap(root, respMap);
return respMap;
}
public static String mapToXml(Map map) throws Exception {
document d = documentHelper.createdocument();
Element root = d.addElement("xml");
mapToXml(root, map);
StringWriter sw = new StringWriter();
XMLWriter xw = new XMLWriter(sw);
xw.setEscapeText(false);
xw.write(d);
return sw.toString();
}
private static Element mapToXml(Element root, Map map) {
for (Map.Entry entry : map.entrySet()) {
if (entry.getValue() instanceof Map) {
Element element = root.addElement(entry.getKey());
mapToXml(element, (Map) entry.getValue());
} else {
root.addElement(entry.getKey()).addText(entry.getValue().toString());
}
}
return root;
}
private static Map xmlToMap(Element tmpElement, Map respMap) {
if (tmpElement.isTextOnly()) {
respMap.put(tmpElement.getName(), tmpElement.getText());
return respMap;
}
Iterator eItor = tmpElement.elementIterator();
while (eItor.hasNext()) {
Element element = eItor.next();
xmlToMap(element, respMap);
}
return respMap;
}
}
WechatMpService
package com.zrj.wechat.service;
import com.zrj.wechat.entity.Response;
public interface WechatMpService {
String event(String signature, String timestamp, String nonce, String echostr, String encryptType, String msgSignature, String openid, String postData);
Response getAccessToken();
Response getCurrentAutoreplyInfo();
Response getApiDomainIp();
Response check();
Response batchGetFreePublish();
Response
WechatMpServiceImpl
package com.zrj.wechat.service.impl;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.zrj.wechat.aes.WXBizMsgCrypt;
import com.zrj.wechat.entity.*;
import com.zrj.wechat.service.WechatMpService;
import com.zrj.wechat.utils.WechatUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@Service("WechatMpServiceImpl")
public class WechatMpServiceImpl implements WechatMpService {
//第三方用户唯一凭证
public static final String APP_ID = "wx1xxxxxx441";
//第三方用户唯一凭证密钥,即appSecret
public static final String APPSECRET = "b3ecxxxxxxxxxxxx900205d6feae7c";
//公众号 令牌(Token),可以优化到配置中心,这个是自定义的,与公众平台配置一致即可
public static final String MP_TOKEN = "zrj";
//公众号 消息加解密密钥 EncodingAESKey
public static final String MP_ENCODING_AES_KEY = "tVNZZP2WuEJxxxxxxAcnZxUAYHvKbl6";
//获取access_token填写client_credential
public static final String GRANT_TYPE = "client_credential";
//获取access_token接口地址
public static final String GET_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token";
//TODO 令牌access_token,可以放缓存Redis,方便存取,定时刷新
public static final String ACCESS_TOKEN = "50_WeVdh9Flt0V5r040BB4M0jr7Db1rfts6YjbZELiSFrcworPhhSWYl6-FfDQqRywN6R-ydN5ejTXwmk7f5u6xKUy2_p1EX3Ku9omS-xfAOgjmGczWtgqFf8BZ-ykWA8gph23wWuSgOjuFLvzVENJiADALUQ";
//获取公众号的自动回复规则
public static final String GET_CURRENT_AUTOREPLY_INFO = "https://api.weixin.qq.com/cgi-bin/get_current_autoreply_info";
//获取微信服务器IP地址
public static final String GET_API_DOMAIN_IP = "https://api.weixin.qq.com/cgi-bin/get_api_domain_ip";
//网络检测
public static final String CHECK = "https://api.weixin.qq.com/cgi-bin/callback/check?access_token=ACCESS_TOKEN";
//发布能力 获取成功发布列表
public static final String BATCH_GET_FREE_PUBLISH = "https://api.weixin.qq.com/cgi-bin/freepublish/batchget?access_token=ACCESS_TOKEN";
//获取自定义菜单配置(获取默认菜单和全部个性化菜单信息),自定义菜单查询接口则仅能查询到使用API设置的菜单配置 GET
private static final String getMenuUrl = "https://api.weixin.qq.com/cgi-bin/menu/get";
//当前使用的自定义菜单(网站功能发布菜单和过API调用设置的菜单) GET
private static final String getCurrentMenuUrl = "https://api.weixin.qq.com/cgi-bin/get_current_selfmenu_info";
//创建菜单 POST
private static final String createMenuUrl = "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN";
//删除菜单 GET
private static final String deleteMenuUrl = "https://api.weixin.qq.com/cgi-bin/menu/delete";
@Override
public String event(String signature, String timestamp, String nonce, String echostr, String encryptType, String msgSignature, String openid, String postData) {
log.info("【微信公众平台消息事件接收服务】请求参数:signature:【{}】,timestamp:【{}】,nonce:【{}】,echostr:【{}】,encryptType:【{}】,msgSignature:【{}】,openid:【{}】,postData:【{}】", signature, timestamp, nonce, echostr, encryptType, msgSignature, openid, "");
System.out.println(postData);
//1.配置url验证
if (StrUtil.isEmpty(postData)) {
log.info("【微信公众平台消息事件接收服务】消息体为空直接返回验证成功,返回随机数,echostr:【{}】", echostr);
return echostr;
}
//2.接收事件消息
try {
//方式一
//假如服务器无法保证在五秒内处理并回复,必须做出下述回复,这样微信服务器才不会对此作任何处理,并且不会发起重试(这种情况下,可以使用客服消息接口进行异步回复),否则,将出现严重的错误提示。
//1、直接回复success(推荐方式) 2、直接回复空串(指字节长度为0的空字符串,而不是XML结构体中content字段的内容为空)
//测试中3秒故障提示,“该公众号暂时无法提供服务,请稍后再试”,直接返回"success",无异常
//if (true) {
// return "success";
//}
//方式二:正常返回,超时会提示用户:该公众号提供的服务出现故障,请稍后再试
//这个类是微信官网提供的解密类,需要用到消息校验Token 消息加密Key和服务平台appid
WXBizMsgCrypt pc = new WXBizMsgCrypt(MP_TOKEN, MP_ENCODING_AES_KEY, APP_ID);
//加密模式:需要解密
String xml = pc.decryptMsg(msgSignature, timestamp, nonce, postData);
// 将xml转为map
Map resultMap = WechatUtils.xmlToMap(xml);
log.info("【微信公众平台消息事件接收服务】解密后消息:【{}】", resultMap);
//构建消息体返回,回复文本消息
Map respMsgMap = new HashMap<>(16);
//MsgType类型说明:text:回复文本消息,image:回复图片消息,voice:回复语音消息,video:回复视频消息,music:回复音乐消息,news:回复图文消息
String msgType = resultMap.get("MsgType");
if ("text".equals(msgType)) {
log.info("【微信公众平台消息事件接收服务】文本消息类型");
respMsgMap.put("ToUserName", "");
respMsgMap.put("FromUserName", "");
respMsgMap.put("CreateTime", resultMap.get("CreateTime"));
respMsgMap.put("MsgType", "");
respMsgMap.put("Content", "");
} else {
log.info("【微信公众平台消息事件接收服务】其他类型消息,暂不处理");
return "success";
}
String respMsgXml = WechatUtils.mapToXml(respMsgMap);
log.info("【微信公众平台消息事件接收服务成功】消息回复:{}", respMsgXml);
return respMsgXml;
} catch (Exception e) {
log.info("【微信公众平台消息事件接收服务】事件接收异常:", e);
return "success";
}
}
@Override
public Response getAccessToken() {
log.info("【微信公众平台获取AccessToken服务】");
try {
//构建请求参数
Map paramMap = new HashMap<>(16);
paramMap.put("grant_type", GRANT_TYPE);
paramMap.put("appid", APP_ID);
paramMap.put("secret", APPSECRET);
log.info("【微信公众平台获取AccessToken接口】请求参数:【{}】,请求地址:【{}】", JSON.toJSONString(paramMap), GET_ACCESS_TOKEN_URL);
//执行请求,获取结果
String result = HttpUtil.get(GET_ACCESS_TOKEN_URL, paramMap);
JSONObject resultJsonObject = JSON.parseObject(result);
log.info("【微信公众平台获取AccessToken接口】响应结果:【{}】", resultJsonObject);
return Response.success("", resultJsonObject);
} catch (Exception e) {
log.info("【微信公众平台消息事件接收服务】事件接收异常:", e);
return Response.fail("微信公众平台消息事件接收服务异常");
}
}
@Override
public Response getCurrentAutoreplyInfo() {
log.info("【获取公众号的自动回复规则】");
try {
//构建请求参数
Map paramMap = new HashMap<>(16);
paramMap.put("access_token", ACCESS_TOKEN);
log.info("【获取公众号的自动回复规则】请求参数:【{}】,请求地址:【{}】", JSON.toJSONString(paramMap), GET_ACCESS_TOKEN_URL);
//执行请求,获取结果
String result = HttpUtil.get(GET_CURRENT_AUTOREPLY_INFO, paramMap);
JSONObject resultJsonObject = JSON.parseObject(result);
log.info("【获取公众号的自动回复规则】响应结果:【{}】", resultJsonObject);
return Response.success("", resultJsonObject);
} catch (Exception e) {
log.info("【获取公众号的自动回复规则】处理异常:", e);
return Response.fail("获取公众号的自动回复规则异常");
}
}
@Override
public Response getApiDomainIp() {
log.info("【微信公众平台获取微信服务器IP地址】");
try {
//构建请求参数
Map paramMap = new HashMap<>(16);
paramMap.put("access_token", ACCESS_TOKEN);
log.info("【微信公众平台获取微信服务器IP地址】请求参数:【{}】,请求地址:【{}】", JSON.toJSONString(paramMap), GET_API_DOMAIN_IP);
//执行请求,获取结果
String result = HttpUtil.get(GET_API_DOMAIN_IP, paramMap);
JSONObject resultJsonObject = JSON.parseObject(result);
log.info("【微信公众平台获取微信服务器IP地址】响应结果:【{}】", resultJsonObject);
return Response.success("", resultJsonObject);
} catch (Exception e) {
log.info("【微信公众平台获取微信服务器IP地址】处理异常:", e);
return Response.fail("微信公众平台获取微信服务器IP地址异常");
}
}
@Override
public Response check() {
log.info("【微信公众平台网络检测服务】");
try {
//构建请求地址 access_token 是 调用接口凭证
String getCheckUrl = CHECK.replaceAll("ACCESS_TOKEN", ACCESS_TOKEN);
//构建请求参数
Map paramMap = new HashMap<>(16);
//action是 执行的检测动作,允许的值:dns(做域名解析)、ping(做ping检测)、all(dns和ping都做)
paramMap.put("action", "all");
//check_operator是 指定平台从某个运营商进行检测,允许的值:CHINANET(电信出口)、UNICOM(联通出口)、CAP(腾讯自建出口)、DEFAULT(根据ip来选择运营商)
paramMap.put("check_operator", "DEFAULT");
String jsonString = JSON.toJSONString(paramMap);
log.info("【微信公众平台网络检测】请求参数:【{}】,请求地址:【{}】", jsonString, getCheckUrl);
//执行请求,获取结果
String result = HttpUtil.post(getCheckUrl, jsonString);
JSONObject resultJsonObject = JSON.parseObject(result);
log.info("【微信公众平台网络检测】响应结果:【{}】", resultJsonObject);
return Response.success("", resultJsonObject);
} catch (Exception e) {
log.info("【微信公众平台网络检测】处理异常:", e);
return Response.fail("微信公众平台网络检测异常");
}
}
@Override
public Response batchGetFreePublish() {
log.info("【微信公众平台获取成功发布列表】");
try {
//构建请求地址 access_token 是 调用接口凭证
String getFreePublishUrl = BATCH_GET_FREE_PUBLISH.replaceAll("ACCESS_TOKEN", ACCESS_TOKEN);
//构建请求参数
Map paramMap = new HashMap<>(16);
//offset 是 从全部素材的该偏移位置开始返回,0表示从第一个素材返回
paramMap.put("offset", 0);
//count 是 返回素材的数量,取值在1到20之间
paramMap.put("count", 20);
//no_content 否 1 表示不返回 content 字段,0 表示正常返回,默认为 0
paramMap.put("no_content", 0);
String jsonString = JSON.toJSONString(paramMap);
log.info("【微信公众平台获取成功发布列表】请求参数:【{}】,请求地址:【{}】", jsonString, getFreePublishUrl);
//执行请求,获取结果
String result = HttpUtil.post(getFreePublishUrl, jsonString);
JSONObject resultJsonObject = JSON.parseObject(result);
log.info("【微信公众平台获取成功发布列表】响应结果:【{}】", resultJsonObject);
return Response.success("", resultJsonObject);
} catch (Exception e) {
log.info("【微信公众平台获取成功发布列表】处理异常:", e);
return Response.fail("微信公众平台获取成功发布列表异常");
}
}
@Override
public Response getMenu() {
log.info("【微信公众平台获取自定义菜单配置】");
try {
//构建请求参数
Map paramMap = new HashMap<>(16);
paramMap.put("access_token", ACCESS_TOKEN);
log.info("【微信公众平台获取自定义菜单配置】请求参数:【{}】,请求地址:【{}】", JSON.toJSONString(paramMap), getMenuUrl);
//执行请求,获取结果
String result = HttpUtil.get(getMenuUrl, paramMap);
JSONObject resultJsonObject = JSON.parseObject(result);
log.info("【微信公众平台获取自定义菜单配置】响应结果:【{}】", resultJsonObject);
return Response.success("", resultJsonObject);
} catch (Exception e) {
log.info("【微信公众平台获取自定义菜单配置】处理异常:", e);
return Response.fail("获取自定义菜单配置异常");
}
}
@Override
public Response getCurrentMenu() {
log.info("【微信公众平台获取当前自定义菜单】");
try {
//构建请求参数
Map paramMap = new HashMap<>(16);
paramMap.put("access_token", ACCESS_TOKEN);
log.info("【微信公众平台获取当前自定义菜单】请求参数:【{}】,请求地址:【{}】", JSON.toJSONString(paramMap), getCurrentMenuUrl);
//执行请求,获取结果
String result = HttpUtil.get(getCurrentMenuUrl, paramMap);
JSONObject resultJsonObject = JSON.parseObject(result);
log.info("【微信公众平台获取当前自定义菜单】响应结果:【{}】", resultJsonObject);
return Response.success("", resultJsonObject);
} catch (Exception e) {
log.info("【微信公众平台获取当前自定义菜单】处理异常:", e);
return Response.fail("微信公众平台获取当前自定义菜单异常");
}
}
@Override
public Response createMenu() {
log.info("【微信公众平台自定义菜单/创建菜单】");
try {
//构建请求地址 access_token 是 调用接口凭证
String getCreateMenuUrl = createMenuUrl.replaceAll("ACCESS_TOKEN", ACCESS_TOKEN);
//构建请求对象
String jsonString = JSON.toJSONString(buildMenuResp());
log.info("【微信公众平台自定义菜单/创建菜单】请求参数:【{}】,请求地址:【{}】", jsonString, getCreateMenuUrl);
//执行请求,获取结果
String result = HttpUtil.post(getCreateMenuUrl, jsonString);
JSONObject resultJsonObject = JSON.parseObject(result);
log.info("【微信公众平台自定义菜单/创建菜单】响应结果:【{}】", resultJsonObject);
if (0 != resultJsonObject.getIntValue("errcode")) {
log.info("【微信公众平台自定义菜单/创建菜单】创建菜单失败");
return Response.fail("自定义菜单/创建菜单失败");
}
log.info("【微信公众平台自定义菜单/创建菜单成功】");
return Response.success("自定义菜单/创建菜单成功", resultJsonObject);
} catch (Exception e) {
log.info("【微信公众平台自定义菜单/创建菜单】处理异常:", e);
return Response.fail("微信公众平台自定义菜单/创建菜单成功异常");
}
}
@Override
public Response deleteMenu() {
log.info("【微信公众平台自定义菜单/删除菜单】");
try {
//构建请求参数
Map paramMap = new HashMap<>(16);
paramMap.put("access_token", ACCESS_TOKEN);
log.info("【微信公众平台自定义菜单/删除菜单】请求参数:【{}】,请求地址:【{}】", JSON.toJSONString(paramMap), deleteMenuUrl);
//执行请求,获取结果
String result = HttpUtil.get(deleteMenuUrl, paramMap);
JSONObject resultJsonObject = JSON.parseObject(result);
log.info("【微信公众平台自定义菜单/删除菜单】响应结果:【{}】", resultJsonObject);
if (0 != resultJsonObject.getIntValue("errcode")) {
log.info("【微信公众平台自定义菜单/创建菜单】删除菜单失败");
return Response.fail("自定义菜单/删除菜单失败");
}
log.info("【微信公众平台自定义菜单/删除菜单】删除菜单成功");
return Response.success("删除菜单成功", resultJsonObject);
} catch (Exception e) {
log.info("【微信公众平台自定义菜单/删除菜单】处理异常:", e);
return Response.fail("微信公众平台自定义菜单/删除菜单异常");
}
}
public static Menu buildMenuResp() {
Menu menu = new Menu();
BasicButton[] basicButtons = new BasicButton[0];
ClickButton clickButton = new ClickButton();
clickButton.setType("click");
clickButton.setName("今日歌曲");
clickButton.setKey("V1001_TODAY_MUSIC");
clickButton.setSub_button(basicButtons);
ClickButton clickButton1 = new ClickButton();
clickButton1.setType("click");
clickButton1.setName("歌手简介");
clickButton1.setKey("V1001_TODAY_SINGER");
clickButton1.setSub_button(basicButtons);
ComplexButton complexButton = new ComplexButton();
complexButton.setName("菜单");
ViewButton viewButton = new ViewButton();
viewButton.setType("view");
viewButton.setName("搜索");
viewButton.setUrl("https://www.baidu.com/");
viewButton.setSub_button(basicButtons);
ViewButton viewButton1 = new ViewButton();
viewButton1.setType("view");
viewButton1.setName("视频");
viewButton1.setUrl("https://v.qq.com/");
viewButton1.setSub_button(basicButtons);
ClickButton clickButton2 = new ClickButton();
clickButton2.setType("click");
clickButton2.setName("赞一下我们");
clickButton2.setKey("V1001_GOOD");
clickButton2.setSub_button(basicButtons);
BasicButton[] complexSubButton = {viewButton, viewButton1, clickButton2};
complexButton.setSub_button(complexSubButton);
BasicButton[] subMenu = {clickButton, clickButton1, complexButton};
menu.setButton(subMenu);
return menu;
}
}
WechatMpController
package com.zrj.wechat.controller;
import com.zrj.wechat.entity.Response;
import com.zrj.wechat.service.WechatMpService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
@Slf4j
@RestController
@RequestMapping("/wechat/mp")
@Api(tags = "微信公众平台服务", description = "微信公众平台服务")
public class WechatMpController {
@Resource
private WechatMpService wechatMpService;
@GetMapping("/test")
@ApiOperation(value = "微信公众平台服务测试服务", notes = "测试方法的备注说明", httpMethod = "GET")
public Response test() {
log.info("【微信公众平台服务测试成功】");
return Response.success("微信公众平台服务测试成功", null);
}
@RequestMapping(value = "/event")
@ApiOperation(value = "微信公众平台授权事件接收URL验证票据")
public String event(@RequestParam("signature") String signature,
@RequestParam("timestamp") String timestamp,
@RequestParam("nonce") String nonce,
@RequestParam(name = "echostr", required = false) String echostr,
@RequestParam(name = "encrypt_type", required = false) String encryptType,
@RequestParam(name = "msg_signature", required = false) String msgSignature,
@RequestParam(name = "openid", required = false) String openid,
@RequestBody(required = false) String postData) {
return wechatMpService.event(signature, timestamp, nonce, echostr, encryptType, msgSignature, openid, postData);
}
@GetMapping("/token")
@ApiOperation(value = "微信公众平台获取AccessToken")
public Response getAccessToken() {
return wechatMpService.getAccessToken();
}
@GetMapping("/getCurrentAutoreplyInfo")
@ApiOperation(value = "获取公众号的自动回复规则")
public Response getCurrentAutoreplyInfo() {
return wechatMpService.getCurrentAutoreplyInfo();
}
@GetMapping("/getApiDomainIp")
@ApiOperation(value = "获取微信服务器IP地址")
public Response getApiDomainIp() {
return wechatMpService.getApiDomainIp();
}
@PostMapping("/check")
@ApiOperation(value = "网络检测")
public Response check() {
return wechatMpService.check();
}
@GetMapping("/batchGetFreePublish")
@ApiOperation(value = "发布能力/获取成功发布列表")
public Response batchGetFreePublish() {
return wechatMpService.batchGetFreePublish();
}
@GetMapping("/getMenu")
@ApiOperation(value = "自定义菜单/获取自定义菜单", httpMethod = "GET")
public Response getMenu() {
return wechatMpService.getMenu();
}
@GetMapping("/getCurrentMenu")
@ApiOperation(value = "自定义菜单/获取当前自定义菜单", httpMethod = "GET")
public Response getCurrentMenu() {
return wechatMpService.getCurrentMenu();
}
@PostMapping("/createMenu")
@ApiOperation(value = "自定义菜单/创建菜单")
public Response createMenu() {
return wechatMpService.createMenu();
}
@GetMapping("/deleteMenu")
@ApiOperation(value = "自定义菜单/删除菜单")
public Response deleteMenu() {
return wechatMpService.deleteMenu();
}
}
4.测试验证
测试网页:https://mp.weixin.qq.com/debug/cgi-bin/apiinfo



