一、接入自有服务端
介绍以及配置
通常情况下,微信公众号直接使用微信平台提供的功能就能完成大部分场景下的应用,但是如果你想实现一些高级功能,比如通过输入来查询指定内容、订阅成功后返回通知、自定义发送信息等一些基础消息能力,那么就可以通过对接自己的服务端,调用微信平台提供的opanApi接口就可以实现。
官方文档:https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Access_Overview.html
接入微信公众平台开发,开发者需要按照如下步骤完成:
1、填写服务器配置
2、验证服务器地址的有效性
3、依据接口文档实现业务逻辑
使用测试公众号测试
通常情况下,我们开发注册一个自有的公众号测试号,就能完成,微信测试平台地址:
https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login
注册成功以后,获取到自己的测试公众号的appid和秘钥等信息
验证服务端配置信息,与自有服务端绑定
本地环境需要连接上公网IP,以便与微信服务端能够访问,这样才能绑定成功。
(一)验证消息的确来自微信服务器接口
/**
* 校验微信公众号接入服务器配置
*
* @Param [signature, timestamp, nonce, echostr] 签名 时间戳 随机数 随机字符串
* @Author: Smily清禾酥酒
* @Date: 2023/6/26 13:51
*/
@RequestMapping(value = "checkSign.html", method = RequestMethod.GET)
public void checkSignByGet(@RequestParam(defaultValue = "") String signature,
@RequestParam(defaultValue = "") String timestamp,
@RequestParam(defaultValue = "") String nonce,
@RequestParam(defaultValue = "") String echostr,
HttpServletResponse response) {
try{
if (SignUtil.checkSignature(signature, timestamp, nonce)) {
//如果检验成功原样返回echostr,微信服务器接收到此输出,才会确认检验完成。
PrintWriter out = null;
try {
out = response.getWriter();
// 通过检验signature对请求进行校验,若校验成功则原样返回echostr,否则接入失败
out.print(echostr);
} catch (IOException e) {
e.printStackTrace();
} finally {
out.close();
}
logger.error("验证公众号token成功,返回:" + echostr);
}
} catch (Exception e) {
logger.error("验证公众号token失败", e);
}
}
(二)签名校验
/**
* 微信签名校验
* 用于微信公众号使用自己的服务器进行权限校验
*
* @Author Smily清禾酥酒
* @Date 2023/6/26 13:51
*/
public class SignUtil {
// 与微信公众号上的token一致,是服务器令牌(token)
private static String token = "123456789";
/**
* 校验签名
*
* @param signature 签名
* @param timestamp 时间戳
* @param nonce 随机数
* @return 布尔值
*/
public static boolean checkSignature(String signature, String timestamp, String nonce) {
String checktext = null;
if (null != signature) {
//对ToKen,timestamp,nonce 按字典排序
String[] paramArr = new String[]{token, timestamp, nonce};
Arrays.sort(paramArr);
//将排序后的结果拼成一个字符串
String content = paramArr[0].concat(paramArr[1]).concat(paramArr[2]);
try {
MessageDigest md = MessageDigest.getInstance("SHA-1");
//对接后的字符串进行sha1加密
byte[] digest = md.digest(content.getBytes());
checktext = byteToStr(digest);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
//将加密后的字符串与signature进行对比
return checktext != null ? checktext.equals(signature.toUpperCase()) : false;
}
/**
* 将字节数组转化我16进制字符串
*
* @param byteArrays 字符数组
* @return 字符串
*/
private static String byteToStr(byte[] byteArrays) {
String str = "";
for (int i = 0; i < byteArrays.length; i++) {
str += byteToHexStr(byteArrays[i]);
}
return str;
}
/**
* 将字节转化为十六进制字符串
*
* @param myByte 字节
* @return 字符串
*/
private static String byteToHexStr(byte myByte) {
char[] Digit = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
char[] tampArr = new char[2];
tampArr[0] = Digit[(myByte >>> 4) & 0X0F];
tampArr[1] = Digit[myByte & 0X0F];
String str = new String(tampArr);
return str;
}
}
点击测试公众号上提交配置接口信息的按钮,就能顺利绑定了,切记:你的服务端一定要能够公网访问,接口配置信息中的URL就是上面checkSignByGet的接口地址
(三)依据接口文档实现业务逻辑(参考官方文档,我这里不再赘述)
二、自定义公众号菜单
我们也是通过接口来完成,我自己本地服务端定义了一个修改公众号菜单的接口,需要调整菜单的时候直接就调用就可以了。
公众号菜单调整接口
/**
* 自定义公众号菜单信息
*
* @Author: Smily清禾酥酒
* @Date:2023/6/26 13:51
*/
@ApiOperation("自定义公众号菜单信息")
@RequestMapping(value = "customMenus.html", method = RequestMethod.POST)
public Object customMenus(@RequestParam(defaultValue = "") String appId,
@RequestParam(defaultValue = "") String secret,
@RequestParam(defaultValue = "") String configJsonMenu) {
String message = WxPublicUtil.customMenu(appId, secret, configJsonMenu);
return “调用成功”;
}
configJsonMenu参数:
{
"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"
}]
}]
}
WxPublicUtil工具类和全局accessToken获取
因为accessToken2小时过期,我直接缓存在redis中,快到期了才去微信服务器获取新的accessToken,redis的代码我就不贴出来了,这里主要教大家怎么获取,你也可以不用redis,使用全局map做缓存也可以
/**
* 微信公众号工具类
* @Author: Smily清禾酥酒
* @Date 2023/6/26 13:51
*/
@Component
public class WxPublicUtil {
public static final Logger logger = LoggerFactory.getLogger(WxPublicUtil.class);
private static WxPublicUtil utils;
@Resource
private RedisUtil redisCacheManager;
@PostConstruct
public void init() {
utils = this;
}
/**
* 自定义公众号菜单信息
*
* @Author: Smily清禾酥酒
* @Date: 2023/6/26 13:51
*/
public static String customMenu(String APPID, String APPSecret,String json){
//先从全局获取access_token
String accessToken = this.getAccessToken(APPID,APPSecret);
String url="https://api.weixin.qq.com/cgi-bin/menu/create?access_token=" + accessToken;
String result = HttpMathod.sendPostByJson(url,json);
logger.info("自定义公众号菜单信息返回信息:" + result);
if (StringUtils.isNotBlank(result)){
JSONObject object= JSON.parseObject(result);
String errcode = object.getString("errcode");
if (StringUtils.isNotBlank(errcode) && "0".equals(errcode)){
//返回成功
logger.info("自定义公众号菜单信息成功");
return "自定义菜单成功";
}else {
return "自定义公众号菜单信息失败:" + object.getString("errmsg");
}
}
return "";
}
/**
* 获取微信统一认证accessToken
*
* access_token 的有效期目前为 2 个小时,需定时刷新
* 不同需要用到accessToken的业务应该使用同一个accessToken,不应该都去刷新
* 所以将accessToken存入缓存中
*
* @Author: Smily清禾酥酒
* @Date: 2023/6/26 13:51
*/
public static String getAccessToken(String APPID, String APPSecret){
String key = APPID + "-access_token";
//缓存中如果存在直接返回缓存中的,不存在则更新缓存
if(utils.redisCacheManager.hasKey(RedisDbConstant.dataCache, key)){
return utils.redisCacheManager.get(RedisDbConstant.dataCache, key).toString();
}
String url="https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid="
+ APPID
+ "&secret="
+ APPSecret;
String result = HttpMathod.sendGet(url);
logger.info("appId:"+ APPID + ",获取accessToken返回信息:" + result);
if (StringUtils.isNotBlank(result)){
JSONObject object= JSON.parseObject(result);
String errcode = object.getString("errcode");
if (errcode == null){
//返回成功
String access_token = object.getString("access_token");
int expires_in = Integer.parseInt(object.getString("expires_in"));
//缓存中不存在则重新插入
utils.redisCacheManager.setEx(RedisDbConstant.dataCache, APPID + "-access_token", access_token, expires_in - 500, TimeUnit.SECONDS);
return access_token;
}
if ("-1".equals(errcode)){
//系统繁忙,此时请开发者稍候再试
logger.error("小程序ADDID:"+ APPID + "获取access_token失败,返回:系统繁忙,此时请开发者稍候再试");
}
if ("40001".equals(errcode)){
//系统繁忙,此时请开发者稍候再试
logger.error("小程序ADDID:"+ APPID + "获取access_token失败,返回:AppSecret 错误或者 AppSecret 不属于这个小程序,请开发者确认 AppSecret 的正确性");
}
if ("40002".equals(errcode)){
//系统繁忙,此时请开发者稍候再试
logger.error("小程序ADDID:"+ APPID + "获取access_token失败,返回:请确保 grant_type 字段值为 client_credential");
}
if ("40013".equals(errcode)){
//系统繁忙,此时请开发者稍候再试
logger.error("小程序ADDID:"+ APPID + "获取access_token失败,返回:不合法的 AppID,请开发者检查 AppID 的正确性,避免异常字符,注意大小写");
}
if ("40164".equals(errcode)){
//系统繁忙,此时请开发者稍候再试
logger.error("小程序ADDID:"+ APPID + "获取access_token失败,返回:调用接口的IP地址不在白名单中,请在接口IP白名单中进行设置。(小程序及小游戏调用不要求IP地址在白名单内。)");
}
}
return "";
}
}
下一遍我们讲,公众号怎么根据用户发送的消息来进行回复