自学内容网 自学内容网

JAVA实现公众号扫码登录和关注功能实战

前言

使用第三方插件
<dependency>
    <groupId>com.github.binarywang</groupId>
    <artifactId>weixin-java-mp</artifactId>
    <version>4.6.0</version>
</dependency>

准备APPID和appSecet

  • 登录微信公众号后台,复制appid和appsecuret。切记如果开发者秘钥已经在使用,请勿重置,否则导致已上线应用无法使用。
  • 微信配置,习惯用yml格式
    wechat:
      appId: wx472934ed71cXXX
      secret: 5cf9107ede023fa45ab626c444123a2f
      token: f84542aa3ca2f7e92984dd123683fdac
      aesKey: M5jSic8xaq5YKb8aMMgUo4oaZAs23kLMJ61BcX8Q123
    
  • 初始化配置

    @Slf4j
    @Configuration
    public class WxConfiguration {
    
      @Autowired
      private WechatAccountConfig wechatAccountConfig;
    
      @Bean
      public WxMpService wxMpService() {
          WxMpService wxMpService = new WxMpServiceImpl();
          wxMpService.setWxMpConfigStorage(wxMpConfigStorage());
          return wxMpService;
      }
    
      @Bean
      public WxMpConfigStorage wxMpConfigStorage() {
          WxMpDefaultConfigImpl wxMpDefaultConfig = new WxMpDefaultConfigImpl();
          log.info("微信配置文件 : {}", JSON.toJSONString(wechatAccountConfig));
          wxMpDefaultConfig.setAppId(wechatAccountConfig.getAppId());
          wxMpDefaultConfig.setSecret(wechatAccountConfig.getSecret());
          wxMpDefaultConfig.setToken(wechatAccountConfig.getToken());
          wxMpDefaultConfig.setAesKey(wechatAccountConfig.getAesKey());
          return wxMpDefaultConfig;
      }
    
    @Data
    @Component
    @ConfigurationProperties(value = "wechat")
    public class WechatAccountConfig {
    
      private String appId;
    
      private String secret;
    
      private String token;
    
      private String aesKey;
    }
    

    生成二维码

  • 获取二维码方法和扫描状态检查接口

    @Slf4j
    @Api(value = "网关公众号接口", tags = "网关公众号接口")
    @RestController
    @RequestMapping("/gateway/wechat")
    public class WechatSubController {
      @Autowired
      private WxMpService wxMpService;
      @Autowired
      private IWeiXinService weiXinService;
    
      /**
       * 获取微信生成二维码
       */
      @ApiOperation(value = "获取微信二维码")
      @GetMapping(value = "/qrcode/{codeType}")
      public AjaxResult getQrCode(@PathVariable("codeType") String codeType) {
          return AjaxResult.success(weiXinService.getQrCode(MsgEventTypeEnum.getTypeEnum(codeType)));
      }
    
      /**
       * 获取微信生成二维码
       */
      @ApiOperation(value = "获取扫码结果")
      @GetMapping(value = "/getScanResult/{uuid}")
      public AjaxResult getScanResult(@PathVariable("uuid") String uuid) {
          return AjaxResult.success(weiXinService.getScanResult(uuid));
      }
    
  • 接口层

    public interface IWeiXinService {
      TicketVo getQrCode(MsgEventTypeEnum msgEventTypeEnum);
    
      String scanQRCodesCallBack(WxMpXmlMessage px);
    
      NatMemberWechat getScanResult(String uuid);
    }
    
  • 实现层

    @Slf4j
    @Service
    public class WeiXinServiceImpl implements IWeiXinService {
    
      @Autowired
      private StringRedisTemplate stringRedisTemplate;
      @Autowired
      private WxMpService wxMpService;
      @Autowired
      private INatMemberWechatService natMemberWechatService;
    
      @Override
      public TicketVo getQrCode(MsgEventTypeEnum msgEventTypeEnum) {
          // 这里生成uuid 等下扫码验证微信时,就知道是那个用户扫的码
          // 这个uuid就是上面声明的全局私有变量
          String uuid = UUID.randomUUID().toString();
          Map<String, Object> map = new HashMap<>();
          map.put("uuid", uuid);
          map.put("eventType", msgEventTypeEnum.getEventType());
          stringRedisTemplate.opsForValue().set(RedisKey.WECHAT_SCAN_UUID_FLAG + uuid, JSON.toJSONString(map), 120L, TimeUnit.SECONDS);
    
          WxMpQrCodeTicket ticket = null;
          try {
              // 获取 ticket
              ticket = wxMpService.getQrcodeService().qrCodeCreateTmpTicket(JSON.toJSONString(map), 6400);
          } catch (WxErrorException e) {
              throw new RuntimeException(e);
          }
          String qrUrl = null;
          try {
              // 根据 ticket 换取二维码链接
              qrUrl = wxMpService.getQrcodeService().qrCodePictureUrl(ticket.getTicket());
          } catch (WxErrorException e) {
              throw new RuntimeException(e);
          }
          // 返回二维码链接,我们系统是返回base64编码的,但是代码太长了,我这里直接返回图片的url 和
          //生成的uuid
          TicketVo vo = new TicketVo();
          vo.setImageBase(qrUrl);
          vo.setUuid(uuid);
          return vo;
      }
    
      @Override
      public NatMemberWechat getScanResult(String uuid) {
          String redisData = Objects.toString(stringRedisTemplate.opsForValue().get(RedisKey.WECHAT_SCAN_UUID_RESULT_FLAG + uuid), null);
          if (StringUtils.isEmpty(redisData)) {
              log.debug("redisData is null");
              return null;
          }
          JSONObject jsonObject = JSONObject.parseObject(redisData);
          NatMemberWechat natMemberWechat = natMemberWechatService.selectNatMemberWechatByOpenId(jsonObject.getString("openid"));
          if(natMemberWechat==null){
              natMemberWechat=new NatMemberWechat();
              natMemberWechat.setOpenId(jsonObject.getString("openid"));
              natMemberWechat.setUserId(null);
          }
          return natMemberWechat;
      }
    
    到此,扫码和扫描检查的代码已经写完,接下来配置回调。
    由于回调是微信服务器外网回调服务器,在本地开发是内网,怎么才能让微信调用本地电脑呢,我们就要借助内网穿透。

    内网穿透

  • NatCross内网穿透,推荐的原因是免费。可以将局域网个人电脑、服务器映射到公网的内网穿透工具。
  • NatCross官网(http://www.natcross.com/)
  • 账号注册
  • 登录首页如图​​​​​​​
  • 配置映射,需要实名认证
  • 配置映射,获取生成的访问域名地址​​​​​​​  
  • 下载并启动客户端
    下载方式2:windows和linux 版本下载,请点击连接 https://pan.baidu.com/s/16k2jFiWuvtNN5Y8CGNUkzw (提取码:hs9d)
    运行步骤:
    windows版本步骤:
    1、下载window客户端
    2、注册natcross账号
    3、修改config.properties配置文件,修改client.key值改为自己注册的客户端秘钥
    4、双击执行启动client-start.bat
    5、配置添加内网映射或场景映射后,自动连接。
    提示:自带jre,无效安装运行环境。
    linux版本步骤:
    1、下载linux客户端
    2、注册natcross账号
    3、vi config.properties配置文件,修改client.key值改为自己注册的客户端秘钥
    4、执行chmod 777 client-start.sh 授权
    5、执行:启动 ./client-start.sh start ,停止 ./client-start.sh stop ,重启 ./client-start.sh restart
    6、查看logs日志 tail -100f natcross-client-3.0.0.jar.log
    7、配置添加内网映射或场景映射后,自动连接。
    提示: 自带jre,无需再次安装运行环境。
    如有疑问,可以加QQ客服:2496727282 (早9点-晚10点)
    
  • 启动成功截图:

    公众号接口配置,将上一步获取到的地址+请求方式配置到微信后台,如图

扫码登录

公众号回调

  • 回调代码块实现

    /**
       * 验证微信服务器 此接口不调用
       */
      @ApiOperation(value = "验证微信服务器 此接口不调用")
      @GetMapping(value = "/callback")
      public String checkSign(HttpServletRequest request) {
          String signature = request.getParameter("signature");
          String timestamp = request.getParameter("timestamp");
          String nonce = request.getParameter("nonce");
          String echostr = request.getParameter("echostr");
          log.info("\n接收到来自微信服务器的认证消息:[signature:{}, timestamp:{}, nonce:{}, echostr:{}]", signature, timestamp, nonce, echostr);
          if (!wxMpService.checkSignature(timestamp, nonce, signature)) {
              log.error("【无效的请求】");
              throw new ServiceException("无效的请求", -1);
          }
          return echostr;
      }
    
      /**
       * 响应微信,这一步是关注微信后,响应微信获取信息
       */
      @ApiOperation(value = "微信扫码响应微信服务器")
      @PostMapping("/callback")
      public String scanQRCodesCallBack(HttpServletRequest request, @RequestBody String requestBody) {
          String signature = request.getParameter("signature");
          String timestamp = request.getParameter("timestamp");
          String nonce = request.getParameter("nonce");
          String echostr = request.getParameter("echostr");
          String openid = request.getParameter("openid");
          String encType = request.getParameter("encType");
          String msgSignature = request.getParameter("msgSignature");
          log.info("\n接收到来自微信服务器的认证消息:[signature:{}, timestamp:{}, nonce:{}, echostr:{},encType:{},msgSignature:{}]", signature, timestamp, nonce, echostr, encType, msgSignature);
          if (!wxMpService.checkSignature(timestamp, nonce, signature)) {
              log.error("【无效的请求】");
              throw new ServiceException("无效的请求", -1);
          }
          if (encType == null) {
              // 明文传输的消息
              WxMpXmlMessage inMessage = WxMpXmlMessage.fromXml(requestBody);
              log.debug("\n消息内容为:\n{} ", inMessage.toString());
              return weiXinService.scanQRCodesCallBack(inMessage);
          } else if ("aes".equalsIgnoreCase(encType)) {
              // aes加密的消息
              WxMpXmlMessage inMessage = WxMpXmlMessage.fromEncryptedXml(requestBody, wxMpService.getWxMpConfigStorage(),
                      timestamp, nonce, msgSignature);
              log.debug("\n消息解密后内容为:\n{} ", inMessage.toString());
              return weiXinService.scanQRCodesCallBack(inMessage);
          }
          return "";
      }
    
    复制@Override
      public String scanQRCodesCallBack(WxMpXmlMessage message) {
          log.info("总的message:" + JSON.toJSONString(message));
          // content 公众号回复用户的文本内容
          String content = "欢迎关注公众号!NatCross是内网穿透工具,也是免费的端口映射软件。解决80被封/动态IP/无公网ip问题;适用于发布网站、访问局域网服务器和应用服务。";
          String messageType = message.getMsgType();                                //消息类型
          String messageEvent = message.getEvent();                                    //消息事件
          String fromUser = message.getFromUser();                                 //发送者帐号
          String toUser = message.getToUser();                                       //开发者微信号
          String text = message.getContent();                                        //文本消息  文本内容
          String eventKey = message.getEventKey();                                    //二维码参数
          JSONObject businessParams = JSON.parseObject(eventKey);                    //从二维码参数中获取uuid通过该uuid可通过websocket前端传数据
    
          log.info("消息类型:{},消息事件:{},发送者账号:{},接收者微信:{},文本消息:{},二维码参数:{}", messageType, messageEvent, fromUser, toUser, text, eventKey);
          WxMpUser wxMpUser = null;
          try {
              wxMpUser = wxMpService.getUserService().userInfo(fromUser);
          } catch (WxErrorException e) {
              e.printStackTrace();
          }
          log.info("通过用户openid获取用户信息:" + JSON.toJSONString(wxMpUser));
          if (StringUtils.equalsAnyIgnoreCase(WechatEventEnum.unsubscribe.getType(), messageEvent)) {
              //取消订阅
              log.info("取消订阅");
              return this.msgStr(message, "取消关注成功!");
          }
          String uuid = businessParams.containsKey("uuid") ? businessParams.getString("uuid") : null;
          if (StringUtils.isEmpty(uuid)) {
              //未知关注公众号,默认提示
              log.info("未知关注公众号,默认提示");
              return this.msgStr(message, content);
          }
          String redisData = Objects.toString(stringRedisTemplate.opsForValue().get(RedisKey.WECHAT_SCAN_UUID_FLAG + uuid), null);
          if (StringUtils.isNotEmpty(redisData) && wxMpUser != null) {
              if (StringUtils.equalsAnyIgnoreCase(WechatEventEnum.SCAN.getType(), messageEvent)) {
                  String eventType = businessParams.getString("eventType");
                  if (StringUtils.equalsAnyIgnoreCase(MsgEventTypeEnum.ADD_WARNING_RECEIVER.getEventType(), eventType)) {
                      content = DateUtils.dateTimeNow(DateUtils.YYYY_MM_DD_HH_MM_SS) + "添加告警接收人成功!";
                  } else if (StringUtils.equalsAnyIgnoreCase(MsgEventTypeEnum.USER_LOGIN.getEventType(), eventType)) {
                      content = DateUtils.dateTimeNow(DateUtils.YYYY_MM_DD_HH_MM_SS) + "扫描登录成功!";
                  }
                  JSONObject dataMap = JSON.parseObject(redisData);
                  dataMap.put("openid", wxMpUser.getOpenId());
                  dataMap.put("unionid", wxMpUser.getUnionId());
                  dataMap.put("nickName", wxMpUser.getNickname());
                  dataMap.put("headImgUrl", wxMpUser.getHeadImgUrl());
                  stringRedisTemplate.opsForValue().set(RedisKey.WECHAT_SCAN_UUID_RESULT_FLAG + uuid, dataMap.toString());
              }
          }
          // 根据来时的信息格式,重组返回。(注意中间不能有空格)
          final String msgStr = this.msgStr(message, content);
          return msgStr;
      }
    
      @Override
      public NatMemberWechat getScanResult(String uuid) {
          String redisData = Objects.toString(stringRedisTemplate.opsForValue().get(RedisKey.WECHAT_SCAN_UUID_RESULT_FLAG + uuid), null);
          if (StringUtils.isEmpty(redisData)) {
              log.debug("redisData is null");
              return null;
          }
          JSONObject jsonObject = JSONObject.parseObject(redisData);
          NatMemberWechat natMemberWechat = natMemberWechatService.selectNatMemberWechatByOpenId(jsonObject.getString("openid"));
          if(natMemberWechat==null){
              natMemberWechat=new NatMemberWechat();
              natMemberWechat.setOpenId(jsonObject.getString("openid"));
              natMemberWechat.setUserId(null);
          }
          return natMemberWechat;
      }
    
      private String msgStr(WxMpXmlMessage message, String content) {
          // 根据来时的信息格式,重组返回。(注意中间不能有空格)
          final String msgStr = "<xml>"
                  + "<ToUserName><![CDATA[" + message.getFromUser() + "]]></ToUserName>"
                  + "<FromUserName><![CDATA[" + message.getToUser() + "]]></FromUserName>"
                  + "<CreateTime>" + new Date().getTime() + "</CreateTime>"
                  + "<MsgType><![CDATA[text]]></MsgType>"
                  + "<Content><![CDATA[" + content + "]]></Content>"
                  + "</xml>";
          return msgStr;
      }
    
  • 回调日志​​​​​​​测试通过后,如果是vue前后端分离,可以把接口给前端调用。
    以上是JAVA实现公众号扫码登录和关注的开发流程。

原文地址:https://blog.csdn.net/sxp2558/article/details/142797917

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!