前言:最近接触的新项目是关于微信公众号的相关开发,其中会涉及到菜单栏的创建,配置跳转页面等等,在这过程中也是一边查资料一边摸索,也遇到一些坑,在这里分享下,如果有错误的地方或者写的不好的还请指点,也欢迎有疑问可以一起探讨。
开发不一定就是自己造轮子,有优秀的开源的SDK那我们完全可以使用,这样可以避免采坑,还有繁杂的封装,从而提高开发效率 。这里我选择的是一位大佬的优秀SDK,作者一直在更新,目前已经来到了3.8.0版本。
<dependency>
<groupId>com.github.binarywanggroupId>
<artifactId>(根据不同需求引入参考以下模板)artifactId>
<version>3.8.0version>
dependency>
1.3.1. 配置application.yml(详细信息及对应公众平台配置值见目录 2.公众平台相关配置)
business:
config:
wechat:
appId: # 公众号appid
secret: # 公众号的appsecret
token: # 接口配置里的Token值
aesKey: # 接口配置里的EncodingAESKey值
authUrl:
1.3.2. 配置SDK相关配置类
WeChatAppConfig.java 用来加载yml中的配置信息
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Getter
@Setter
@Configuration
@ConfigurationProperties(prefix = "business.config.wechat")
public class WeChatAppConfig {
/**
* 设置微信公众号的appId
*/
private String appId;
/**
* 设置微信公众号的app secret
*/
private String secret;
/**
* 设置微信公众号的token
*/
private String token;
/*
* 设置微信公众号的 EncodingAESKey
*/
private String aesKey;
/**
* 权限URL
*/
private String authUrl;
}
SDK微信服务核心配置 WeChatMpConfig.java
import me.chanjar.weixin.common.api.WxConsts;
import me.chanjar.weixin.mp.api.WxMpMessageRouter;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;
import me.chanjar.weixin.mp.config.WxMpConfigStorage;
import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConditionalOnClass(WxMpService.class)
@EnableConfigurationProperties(WeChatAppConfig.class)
public class WeChatMpConfig {
@Autowired
private WeChatAppConfig weChatAppConfig;
/**
* 用户取消关注事件处理服务
*/
@Autowired //如果不需要用到微信的相关事件可以不用
private UnsubscribeHandler unsubscribeHandler;
@Bean
@ConditionalOnMissingBean
public WxMpConfigStorage wxMpConfigStorage() {
WxMpDefaultConfigImpl configStorage = new WxMpDefaultConfigImpl();
configStorage.setAppId(weChatAppConfig.getAppId());
configStorage.setSecret(weChatAppConfig.getSecret());
configStorage.setToken(weChatAppConfig.getToken());
configStorage.setAesKey(weChatAppConfig.getAesKey());
return configStorage;
}
@Bean
@ConditionalOnMissingBean
public WxMpService wxMpService(WxMpConfigStorage configStorage) {
WxMpService wxMpService = new WxMpServiceImpl();
wxMpService.setWxMpConfigStorage(configStorage);
return wxMpService;
}
@Bean //如果不需要用到微信的相关事件可以不用配置此项
public WxMpMessageRouter router(WxMpService wxMpService) {
final WxMpMessageRouter newRouter = new WxMpMessageRouter(wxMpService);
// 取消关注事件
newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT).event(WxConsts.EventType.UNSUBSCRIBE)
.handler(this.getUnsubscribeHandler()).end();
return newRouter;
}
protected UnsubscribeHandler getUnsubscribeHandler() {
return this.unsubscribeHandler;
}
}
微信公众号平台登录: 链接地址.
这里只是一个简单里范例,看个人业务需求选择,SDK中的WxMpService 服务封装了各种常用的微信接口,可以参考git中的文档使用。这里的重定向使用的是MVC中的重定向方式:return "redirect:+url, 而Spring Boot使用了@RestController注解,以下写法只能返回字符串,可以将一个HttpServletResponse参数添加到处理程序方法然后调用response.sendRedirect(url)进行重定向。
package com.wechat.controller;
import me.chanjar.weixin.common.api.WxConsts;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.result.WxMpOAuth2AccessToken;
import me.chanjar.weixin.mp.bean.result.WxMpUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.net.URLEncoder;
@Controller
@RequestMapping("/wechat")
public class WechatAuthController {
/**
* sdk服务
*/
@Autowired
private WxMpService wxMpService;
/**
* 请求微信服务器认证授权(网页授权)
*
* 用户在公众号中访问第三方的网页,公众号通过当前接口调用微信网页授权,用户确认同意授权后来获取用户基本信息。
*
* 回调机制可根据个人业务需求:1.服务端->网页回调 2.服务端->服务端回调,我这里采用的是2方式,
* 回调服务端来获取用户信息,也可以回调前端,前端根据返回的code在来调用获取用户信息接口
*
* @param returnUrl 跳转回调页面地址
* @return
*/
@GetMapping("/authorize")
public String authorize(@RequestParam("returnUrl") String returnUrl) throws Exception {
// 网页授权回调地址(这里可以是服务端接口,也可以是回调页面地址)
String url = "https://你的域名/wechat/userInfo";
String redirectURL = wxMpService
.oauth2buildAuthorizationUrl(
url,//网页授权回调地址,不管是页面回调,还是服务端接口的回调地址必须在微信平台配置的网页授权域名下,不然会回调失败
WxConsts.OAuth2Scope.SNSAPI_USERINFO,//网页授权两种scope,一种是静默授权,一种是非静默方式
URLEncoder.encode(returnUrl));//state参数,微信回调重定向会带上该参数,值是不变的,一般做校验用,这里我放的是前端的回调地址。
return "redirect:" + redirectURL;//由于网页授权回调地址 url我这里是服务端的接口地址,会重定向到获取用户信息接口,获取相关用户信息
}
/**
* 获取用户信息
*
* @param code 用户code,使用后即失效,不使用5分钟失效,用于获取用户access_token的
* @param returnUrl 网页授权时传入的state参数,回调时会原原本本返回
* @return
* @throws Exception
*/
@GetMapping("/userInfo")
public String userInfo(@RequestParam("code") String code,
@RequestParam("state") String returnUrl) throws Exception {
WxMpOAuth2AccessToken wxMpOAuth2AccessToken;
try {
// 获取用户accessToken
wxMpOAuth2AccessToken = wxMpService.oauth2getAccessToken(code);
// 获取用户信息
WxMpUser wxMpUser = wxMpService.oauth2getUserInfo(wxMpOAuth2AccessToken, null);
} catch (WxErrorException e) {
e.printStackTrace();
throw new Exception(e.getError().getErrorMsg());
}
// 获取openId,用户在一个公众号中的唯一标识
String openId = wxMpOAuth2AccessToken.getOpenId();
return "redirect:" + returnUrl + "?openid=" + openId;
}
}
虽然SDK中封装部分常用的接口,但由于是个人开发有时候会比微信开发api慢一些,或者有些不常用的接口是没有提供支持,当然作者在 开发文档也提到,并提供了3个急救方案,这里举个栗子。开发中可能需要知道用户是否关注了公众号,刚好该接口SDK中尚未支持,以下是简单的实现
/**
* 获取用户信息
*
* @param openid 用户唯一标识
* @return
*/
private WxMpUser getUserInfo(String openid) {
try {
String responseContent = wxMpService.get(WxMpApiUrl.User.USER_INFO_URL, "openid=" + openid + "&lang=zh_CN");
WxMpUser wxMpUser = WxMpUser.fromJson(responseContent);
return wxMpUser;
} catch (WxErrorException e) {
log.error("get user info error", e);
}
throw new CustomException(ResultEnum.LOGIN_FAILED);
}
这里需要注意的是该接口中,如果用户尚未关注公众号,获取的只有openId信息,而关注了才能获取用户的基本信息。但不管关注未关注返回结果中都有一个 Boolean类型 subscribe字段,可以根据该字段来判断用户是否关注。
在网页中常常需要调用微信开放的JS,在调用前需要先获取签名信息,根据签名信息才能成功调用微信的JS-SDK,详细可以参考 微信JS-SDK说明文档.,这里我是在服务端提供一个获取签名信息的接口,前端在调用前可以先进行签名信息的获取。
import me.chanjar.weixin.common.bean.WxJsapiSignature;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.api.WxMpService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* 获取微信jsdk签名信息
*/
@Controller
@RequestMapping("/wechat/jsdk")
public class WxJsdkController {
/**
* sdk服务
*/
@Autowired
private WxMpService wxMpService;
@ResponseBody
@RequestMapping(value = "/getTicket")
public WxJsapiSignature getTicket(@RequestParam("url") String url) {
try {
WxJsapiSignature jsapi = wxMpService.createJsapiSignature(url);
return jsapi;
} catch (WxErrorException e) {
e.printStackTrace();
}
// 获取失败
return null;
}
}
SDK中的创建签名方法中已经封装好,包括获取签名需要的时间戳,随机串,并且按照开发文档中的要求排序生成签名,保存ticket信息等。以上只是简单的案例,可以根据个人业务需求去封装一个结果,对获取失败进行处理,返回相关错误信息等。一下为WxJsapiSignature 类的数据格式:
{
"appId": "appid",
"nonceStr": "Rr5RUodeRn8BTArc",
"signature": "16f72c24f53d69199ac8af56c2a0786ff8baf04e",
"timestamp": 1596000000,
"url": "https://www.xx.cn/test/test.html"
}
在微信公众平台开发中,由于功能还在测试阶段需要调试,且微信公众号注册也有一定的门槛,微信公众平台提供了: 接口测试账号申请.用户使用微信登录后就有一个可测试的账号。
根据申请到的测试号的相关配置 appId,secret,url,token,js域名,在平台中及项目中配置好,如果项目在本地,需要微信回调到本地服务,这时我们就需要下载安装一个ngrok.exe内网穿透工具.将本地项目对应的端口映射到一个可以访问的公网地址。下载安装后,工具的命令很简单,假如本地项目端口为8080,如下:
使用cmd切换到ngrok.exe目录,执行命令 ngrok http + 需要映射的端口
ngrok.exe http 8080
回车后结果如下,会生成带时效的 session Expire 这里是 8小时的公网地址,有http和https格式,其域名都是一样的,切记当前窗口要保持开启才有效。将以下的域名配置到测试号平台中如js域名等,这样在请求微信网页授权时候传的回调地址里使用该地址+具体项目接口即可收到微信正常回调。
毕竟是测试账号,不是正规军,它没有提供可视化的公众号菜单或按钮的编辑,所以如果你需要在测试号上添加相应菜单栏,那你只能通过在线通过 微信公众平台在线调试工具页面.去做添加和删除
填写好测试号的appid与secret,点击检查问题请求获取
请求结果如下,根据返回的access_token就可以去调用其它接口了
根据1.2.1请求结果获取的access_token,调用菜单栏创建接口,菜单栏请求body为json格式,可参考自定义菜单创建接口文档中的格式.创建想要的菜单或按钮格式,微信规定最多只能创建3个菜单,每个菜单最多只能有5个子菜单,以下为创建示例,编辑和删除大同小异,这里就不展开了
请求结果
申请的微信测试号即使是通过自己微信号申请,自己也可以扫码关注,通过微信扫码关注就可以看到菜单栏创建的效果,点击可以根据body参数中配置url跳转
项目测试其,曾把回调地址配置成服务端地址,报错了:redirect_uri域名与后台配置不一致,提示已经很明显了,如果是在PC端访问或者安卓端访问会出现页面跳转不过去,只有ios会有这个错误的弹窗提示,所以如果是非ios可能还不好定位到问题。由于项目是前后端分离,且前端部署在公众平台配置的域名服务器中,而服务端部署在另外的服务器下导致,这种情况解决的办法有两种,将回调改成前端页面回调方式,或者配置nginx代理。
String ticket = wxMpService.getTicket(TicketType.JSAPI, false);
通过验证,生成的签名和接口中返回的签名是一致的,排除签名错误的可能
获取access_token服务端的IP和前端的IP需要配置在白名单中,否则也会出现该为题
排除里以上问题,可以说基本是url的问题了,一开始核实了以上都没有问题,且在微信的开发文档中按照要求也不觉得是url问题,文档中也说明 url不进行转义,所以在创建时我一直是原参数url创建,此时已经要脑裂,也翻阅了好多网上资料,没有找到问题,最后试了下 将url encode一次后再进行签名创建,结果成功了。也有可能我的url后面是带参数的问题导致,但是文档中并没有提示要进行encode。