1.授权步骤开发
参考牛人的文档,按照步骤集成。
- 配置微信第三方平台中的授权事件接收URL:http://my-domain/notify/receive_ticket
- 配置微信第三方平台中的公众号消息与事件接收URL http://my-domain/notify/APPID/callback
- 首次启动后需要 等待收到 微信推送的 component_verify_ticket 后才可以使用接口 (在第三方平台创建审核通过后,微信服务器每隔10分钟会向第三方的消息接收地址推送一次component_verify_ticket,用于获取第三方平台接口调用凭据)
- 浏览器访问:http://my-domain/api/auth/goto_auth_url_show 点击 go 跳转到微信授权页面 扫码授权
2.开发步骤
增加依赖
<dependency><groupId>com.github.binarywang</groupId><artifactId>weixin-java-open</artifactId><version>3.3.0</version> </dependency>
- 授权获取component_verify_ticket方式
package com.test.wechat.controller;import com.test.wechat.consts.WechatOpenConst;
import com.test.wechat.service.WechatOpenService;
import com.test.wechat.util.ServletUtil;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.mp.bean.kefu.WxMpKefuMessage;
import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
import me.chanjar.weixin.open.bean.message.WxOpenXmlMessage;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.web.bind.annotation.*;import java.util.concurrent.TimeUnit;/*** @author <a href="https://github.com/007gzs">007</a>*/
@RestController
@RequestMapping("/wechatOpen/platform")
public class WechatOpenNotifyController {private final Logger logger = LoggerFactory.getLogger(this.getClass());@Autowiredprotected WechatOpenService wxOpenService;@Autowiredprivate ValueOperations<String, Object> valOps;@AutowiredRedisTemplate redisTemplate;@RequestMapping("/receive_ticket")public Object receiveTicket(String key,@RequestBody(required = false) String requestBody, @RequestParam("timestamp") String timestamp,@RequestParam("nonce") String nonce, @RequestParam("signature") String signature,@RequestParam(name = "encrypt_type", required = false) String encType,@RequestParam(name = "msg_signature", required = false) String msgSignature) throws WxErrorException {logger.info("\n接收微信请求:[key=[{}],[signature=[{}], encType=[{}], msgSignature=[{}],"+ " timestamp=[{}], nonce=[{}], requestBody=[\n{}\n] ",key,signature, encType, msgSignature, timestamp, nonce, requestBody);ServletUtil.setKey(key);if (!StringUtils.equalsIgnoreCase("aes", encType)|| !wxOpenService.getWxOpenComponentService().checkSignature(timestamp, nonce, signature)) {throw new IllegalArgumentException("非法请求,可能属于伪造的请求!");}// aes加密的消息WxOpenXmlMessage inMessage = WxOpenXmlMessage.fromEncryptedXml(requestBody,wxOpenService.getWxOpenConfigStorage(), timestamp, nonce, msgSignature);logger.debug("\n消息解密后内容为:\n{} ", inMessage.toString());String componentVerifyTicket=inMessage.getComponentVerifyTicket();if(redisTemplate.getExpire(WechatOpenConst.OPEN_PLATFORM_TICKET + key)<10*60) {valOps.set(WechatOpenConst.OPEN_PLATFORM_TICKET + key, componentVerifyTicket, 60 * 60 * 12, TimeUnit.SECONDS);}wxOpenService.getComponentAccessToken(key, componentVerifyTicket);try {String out = wxOpenService.getWxOpenComponentService().route(inMessage);logger.debug("\n组装回复信息:{}", out);} catch (WxErrorException e) {logger.error("receive_ticket", e);}return "success";}@RequestMapping("{appId}/callback")public Object callback(String key,@RequestBody(required = false) String requestBody,@PathVariable("appId") String appId,@RequestParam("signature") String signature,@RequestParam("timestamp") String timestamp,@RequestParam("nonce") String nonce,@RequestParam("openid") String openid,@RequestParam("encrypt_type") String encType,@RequestParam("msg_signature") String msgSignature) {logger.info("\n接收微信请求:[appId=[{}], openid=[{}], signature=[{}], encType=[{}], msgSignature=[{}],"+ " timestamp=[{}], nonce=[{}], requestBody=[\n{}\n] ",appId, openid, signature, encType, msgSignature, timestamp, nonce, requestBody);ServletUtil.setKey(key);if (!StringUtils.equalsIgnoreCase("aes", encType)|| !wxOpenService.getWxOpenComponentService().checkSignature(timestamp, nonce, signature)) {throw new IllegalArgumentException("非法请求,可能属于伪造的请求!");}String out = "";// aes加密的消息WxMpXmlMessage inMessage = WxOpenXmlMessage.fromEncryptedMpXml(requestBody,wxOpenService.getWxOpenConfigStorage(), timestamp, nonce, msgSignature);logger.info("\n消息解密后内容为:\n{} ", inMessage.toString());// 全网发布测试用例if (StringUtils.equalsAnyIgnoreCase(appId, "wx6c317d44449bfd92", "wx570bc396a51b8ff8")) {try {if (StringUtils.equals(inMessage.getMsgType(), "text")) {if (StringUtils.equals(inMessage.getContent(), "TESTCOMPONENT_MSG_TYPE_TEXT")) {out = WxOpenXmlMessage.wxMpOutXmlMessageToEncryptedXml(WxMpXmlOutMessage.TEXT().content("TESTCOMPONENT_MSG_TYPE_TEXT_callback").fromUser(inMessage.getToUser()).toUser(inMessage.getFromUser()).build(),wxOpenService.getWxOpenConfigStorage());} else if (StringUtils.startsWith(inMessage.getContent(), "QUERY_AUTH_CODE:")) {String msg = inMessage.getContent().replace("QUERY_AUTH_CODE:", "") + "_from_api";WxMpKefuMessage kefuMessage = WxMpKefuMessage.TEXT().content(msg).toUser(inMessage.getFromUser()).build();wxOpenService.getWxOpenComponentService().getWxMpServiceByAppid(appId).getKefuService().sendKefuMessage(kefuMessage);}} else if (StringUtils.equals(inMessage.getMsgType(), "event")) {WxMpKefuMessage kefuMessage = WxMpKefuMessage.TEXT().content(inMessage.getEvent() + "from_callback").toUser(inMessage.getFromUser()).build();wxOpenService.getWxOpenComponentService().getWxMpServiceByAppid(appId).getKefuService().sendKefuMessage(kefuMessage);}} catch (WxErrorException e) {logger.error("callback", e);}}else{WxMpXmlOutMessage outMessage = wxOpenService.route(inMessage);if(outMessage != null){out = WxOpenXmlMessage.wxMpOutXmlMessageToEncryptedXml(outMessage, wxOpenService.getWxOpenConfigStorage());}}return out;}
}
- 编写授权代码-授权地址
package com.test.wechat.controller;import com.test.wechat.service.WechatOpenService;
import com.test.wechat.util.ServletUtil;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.open.bean.result.WxOpenAuthorizerInfoResult;
import me.chanjar.weixin.open.bean.result.WxOpenQueryAuthResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.ValueOperations;
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 org.springframework.web.bind.annotation.ResponseBody;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;/*** @author <a href="https://github.com/007gzs">007</a>*/
@Controller
@RequestMapping("/wechatOpen/open/api")
public class WechatOpenApiController {private final Logger logger = LoggerFactory.getLogger(getClass());@Autowiredprivate WechatOpenService wxOpenService;@Autowiredprivate ValueOperations<String, Object> valOps;@GetMapping("/auth/goto_auth_url_show")@ResponseBodypublic String gotoPreAuthUrlShow(String key) {String hrefUrl="<a href='goto_auth_url?key="+key+"'>go</a>";return hrefUrl;}@GetMapping("/auth/goto_auth_url")public void gotoPreAuthUrl(HttpServletRequest request, HttpServletResponse response) {String key = request.getParameter("key");ServletUtil.setKey(key);String host = request.getHeader("host");String url = "https://" + host + "/wechatOpen/open/api/auth/jump?key=" + key;try {url = wxOpenService.getWxOpenComponentService().getPreAuthUrl(url);// 添加来源,解决302跳转来源丢失的问题response.addHeader("Referer", "https://" + host);response.sendRedirect(url);} catch (WxErrorException | IOException e) {logger.error("gotoPreAuthUrl", e);throw new RuntimeException(e);}}@GetMapping("/auth/jump")@ResponseBodypublic WxOpenQueryAuthResult jump(String key, @RequestParam("auth_code") String authorizationCode) {try {ServletUtil.setKey(key);WxOpenQueryAuthResult queryAuthResult = wxOpenService.getWxOpenComponentService().getQueryAuth(authorizationCode);logger.info("getQueryAuth", queryAuthResult);//存储授权appId,和authorCodevalOps.set("wxOpen:" + queryAuthResult.getAuthorizationInfo().getAuthorizerAppid(), authorizationCode);wxOpenService.updateAuthAccount(key,authorizationCode);return queryAuthResult;} catch (WxErrorException e) {logger.error("gotoPreAuthUrl", e);throw new RuntimeException(e);}}@GetMapping("/get_authorizer_info")@ResponseBodypublic WxOpenAuthorizerInfoResult getAuthorizerInfo(@RequestParam String appId) {try {return wxOpenService.getWxOpenComponentService().getAuthorizerInfo(appId);} catch (WxErrorException e) {logger.error("getAuthorizerInfo", e);throw new RuntimeException(e);}}
}
- 这个用到的工具类
public class ServletUtil {private static final ThreadLocal<String> tl = new ThreadLocal<String>();public static String getKey() {// ServletRequestAttributes attributes=(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();// return (String) attributes.getAttribute("key", RequestAttributes.SCOPE_REQUEST);//attributes.getRequest().getAttribute("key").toString();return tl.get();}public static void setKey(String key) {// ServletRequestAttributes attributes=(ServletRequestAttributes) RequestContextHolder.getRequestAttributes();// attributes.setAttribute("key",key, RequestAttributes.SCOPE_REQUEST);// //attributes.getRequest().setAttribute("key",key);tl.set(key);}
授权中获取ticket通过上面的receiveTicket这个方法
- component_verify_ticket 的有效时间为12小时,比 component_access_token 更长,建议保存最近可用的component_verify_ticket,在 component_access_token 过期之前都可以直接使用该 component_verify_ticket 进行更新,避免出现因为 component_verify_ticket 接收失败而无法更新 component_access_token 的情况。其中component_access_token获取发送消息的token时需要用到,由于这个wx-java-open没有实现发消息功能,需要集成weixin-java-mp通用发消息功能。
- weixin-java-mp依赖,通过这个就可以使用微信的通用发送消息接口,这里的access_token需要使用授权的auth_access_token来发送消息。
<dependency><groupId>com.github.binarywang</groupId><artifactId>weixin-java-mp</artifactId><version>3.3.0</version> </dependency>
微信公众平台接口
客服账号管理 | 微信开放文档
authorizer_access_token获取方式 ,这个比较复杂,需要五步,其中component_access_token,authorizer_access_token有效期都有两小时。
Token生成说明 | 微信开放文档
其中authcode在授权后会拿到,需要保存下来。
ps:下面继续讲解下一步代码