ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

公众平台--扫描微信二维码,关注后自动登录

2021-10-27 16:02:39  阅读:167  来源: 互联网

标签:Map String get -- 微信 token 二维码 put null


准备

使用微信公众号平台测试号测试
登录地址:https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login

接口配置信息:填写url和token

url作用1:

url为你的接口地址,当你配置的时候,微信会通过 get请求 自动调用接口,这一步的作用就是为了校验token,并且获取它带过去的随机字符串参数echostr。

url作用2:

还是这个接口,当你扫码关注公众号后,它会重新回调该url接口,只是这一次它发的是 post请求 ,并携带xml格式的参数,需要解析才能获取openid等参数。

所以该url实际可以分成两个路径一样请求方式不一样的接口。

注意:

url的域名必须是可以外网访问的到的,也可以使用内网穿透工具,如natapp。
配置失败和成功都会有相应的提示!

配置成功如图所示:

在这里插入图片描述

写代码

<一> 获取二维码代码:

  /**
   * 公众号扫码登录:会返回一张二维码图片
   */
  // Result 返回给前端的工具类,可自己定义
  @ApiOperation(value = "公众号-扫码登录(PC端)", notes = "公众号-扫码登录(PC端)")
  @PostMapping(value = "/public/wechat/login/scan")
  public Result wechatMpLogin() throws Exception {
    Map<String, Object> data = PublicUtil.getQrCodeUrl();
    return Result.Builder.newBuilder(AppTipStatusEmum.SUCCESS_STATUS.getCode(), AppTipMsgEnum.SUCCESS.getCode()).setMessage("ok").setData(data).build();
  }

涉及的PublicUtil工具类如下(重要):

import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import javax.servlet.http.HttpServletRequest;
import java.io.InputStream;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;

// 公众号工具
public class PublicUtil {

  private final static Logger log = LogManager.getLogger(PublicUtil.class);

  // 凭证获取(GET)
  public final static String token_url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";


  /**
   * 获取接口访问凭证
   * 
   * @return
   */
  public static Map getToken() throws Exception {

	// ThirdPartyLoginEnum是自己定义的接口,PublicWeChatEnum是自己定义的枚举,AppID就是第一张图喝奶茶表情的appid
	// AppSecret就是上图喝奶茶的appsecret
	// HttpClientUtil就是发送http请求的工具类,JsonUtil就是bean和json转换的工具类,可以自己定义,或者晚上下载类似的jar包也可。
	
    String requestUrl = token_url.replace("APPID", ThirdPartyLoginEnum.PublicWeChatEnum.AppID.getCode())
    .replace("APPSECRET", ThirdPartyLoginEnum.PublicWeChatEnum.AppSecret.getCode());
    Map token = null;

    // 发起GET请求获取凭证
    String s = HttpClientUtil.httpGet(requestUrl, null);

    if (StringUtils.isNotBlank(s)) {
      token = JsonUtil.fromJson(s, Map.class);
    }
    return token;
  }

  /**
   * 发送模板消息 appId 公众账号的唯一标识 appSecret 公众账号的密钥 openId 用户标识
   * 
   * @return
   */
  public static void send_template_message(String openId, String templateId, String json, String goUrl) throws Exception {
    // 因为我申请的模板是需要填写当前时间戳的,所以在这里我获取了当前的时间
    Map token = getToken();// 这里要注意,如果你是申请的正式公众号的话,获取token的时候,一定要在后台加上你的ip,不然获取token的时候会报错

    if (token == null) {
      log.error("发送失败:token==null");
    }

    String access_token = (String) token.get("access_token");
    String url = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=" + access_token;

    String jsonParam = json.replace("{{openid}}", openId).replace("{{templateId}}", templateId).replace("{{goUrl}}", goUrl);
    String s = HttpClientUtil.httpPost(url, jsonParam);

    if (StringUtils.isNotBlank(s)) {
      Map map = JsonUtil.fromJson(s, Map.class);
      String errcode = map.get("errcode").toString();
      if (!Objects.equals(errcode, "0") && !Objects.equals(errcode, "0.0")) {
        String errmsg = map.get("errmsg").toString();
        log.error("发送失败:errcode==" + errcode + ",errmsg==" + errmsg);
      }
    } else {
      log.error("发送失败:模板消息响应为null或''");
    }
    log.info("发送成功");
  }

  // 通过openid获取用户信息
  public static Map<String, Object> getUserInfoByOpenid(String openid) throws Exception {
    Map token = getToken();
    String access_tocken = (String) token.get("access_token");
    String url = "https://api.weixin.qq.com/cgi-bin/user/info?access_token=" + access_tocken + "&openid=" + openid;
    String s = HttpClientUtil.httpGet(url, null);
    Map<String, Object> map = JsonUtil.fromJson(s, Map.class);
    return map;
  }

  // 获取二维码
  public static Map<String, Object> getQrCodeUrl() throws Exception {
    Map token = PublicUtil.getToken();
    String access_token = (String) token.get("access_token");
    String url = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=" + access_token;
    String scene_str = UUID.randomUUID().toString().replaceAll("-", "") + new Date().getTime();
    String params = "{\"expire_seconds\":120, \"action_name\":\"QR_STR_SCENE\", \"action_info\":{\"scene\":{\"scene_str\":\"" + scene_str + "\"}}}";
    String s = HttpClientUtil.httpPost(url, params);
    Map<String, Object> resultMap = JsonUtil.fromJson(s, Map.class);

    Map<String, Object> data = new HashMap<>();

    if (resultMap.get("ticket") != null) {
      String qrcodeUrl = "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=" + resultMap.get("ticket");
      data.put("qrcodeUrl", qrcodeUrl);
    }
    data.put("scene_str", scene_str);

    Map<String, Object> m = new HashMap<>();
    m.put("code", ScanLoginTypeEnum.UN_LOGIN.getCode());
    m.put("key", scene_str);
    CacheUtil.set("uwa_cato" + scene_str, JsonUtil.toJson(m), 120);
    return data;
  }

  // 解析xml
  public static Map<String, String> xmlToMap(HttpServletRequest httpServletRequest) {
    Map<String, String> map = new HashMap<String, String>();
    try {
      InputStream inputStream = httpServletRequest.getInputStream();
      SAXReader reader = new SAXReader(); // 读取输入流
      org.dom4j.Document document = reader.read(inputStream);
      Element root = document.getRootElement(); // 得到xml根元素
      List<Element> elementList = root.elements(); // 得到根元素的所有子节点
      // 遍历所有子节点
      for (Element e : elementList)
        map.put(e.getName(), e.getText());
      // 释放资源
      inputStream.close();
      inputStream = null;
      return map;
    } catch (Exception e) {
      e.getMessage();
    }
    return null;
  }
}

<二> 检测是否登录:

  // 检测登录
  @GetMapping("/public/wechat/is/login")
  @ApiOperation(value = "公众号-检查微信是否登录(PC端)", notes = "公众号-检查微信是否登录(PC端)")
  public Result wechatMpCheckLogin(@RequestParam("scene_str") String scene_str) throws AssertException {
  	// CacheUtil缓存工具类,封装了redis
    // 根据scene_str查询redis,获取对应记录
    // 后面涉及( 0未登录  1关注--未绑定    2扫码-未绑定  3已绑定,发token)
    String loginInfo = CacheUtil.get("ycj" + scene_str);
    AssertException.isNull(loginInfo, AppTipMsgEnum.ERROR.getCode(), "已过期!");

    Map map = JsonUtil.fromJson(loginInfo, Map.class);
    return Result.Builder.newBuilder(AppTipStatusEmum.SUCCESS_STATUS.getCode(), AppTipMsgEnum.SUCCESS.getCode()).setMessage("ok").setData(map).build();
  }

<三> 检查回调:上面的接口配置信息的url接口:get请求回调

  @ApiOperation(value = "公众号检查token回调", notes = "公众号检查token回调")
  @GetMapping("/public/wx/check")
  public void get(HttpServletRequest request, HttpServletResponse response) throws Exception {
    // 微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。
    String signature = request.getParameter("signature");
    // 时间戳
    String timestamp = request.getParameter("timestamp");
    // 随机数
    String nonce = request.getParameter("nonce");
    // 随机字符串
    String echostr = request.getParameter("echostr");

    PrintWriter out = null;
    try {
      out = response.getWriter();
      // 通过检验signature对请求进行校验,若校验成功则原样返回echostr,否则接入失败
      if (SignUtil.checkSignature(signature, timestamp, nonce)) {
        out.print(echostr);
      }
    } catch (IOException e) {
      e.printStackTrace();
    } finally {
      out.close();
      out = null;
    }
  }

这里涉及到SignUtil工具类如下:

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;

/**
 * 验证签名
 *
 */
public class SignUtil {
	
 
	/**
	 * 验证签名
	 * @param signature
	 * @param timestamp
	 * @param nonce
	 * @return
	 */
	public static boolean checkSignature(String signature, String timestamp, String nonce) {
		String[] arr = new String[] { "123456", timestamp, nonce };
		// 将token、timestamp、nonce三个参数进行字典排序
		Arrays.sort(arr);
		StringBuilder content = new StringBuilder();
		for (int i = 0; i < arr.length; i++) {
			content.append(arr[i]);
		}
		MessageDigest md = null;
		String tmpStr = null;
 
		try {
			md = MessageDigest.getInstance("SHA-1");
			// 将三个参数字符串拼接成一个字符串进行sha1加密
			byte[] digest = md.digest(content.toString().getBytes());
			tmpStr = byteToStr(digest);
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		}

		// 将sha1加密后的字符串可与signature对比
		return tmpStr != null ? tmpStr.equals(signature.toUpperCase()) : false;
	}
 
	/**
	 * 将字节数组转换为十六进制字符串
	 * 
	 * @param byteArray
	 * @return
	 */
	private static String byteToStr(byte[] byteArray) {
		String strDigest = "";
		for (int i = 0; i < byteArray.length; i++) {
			strDigest += byteToHexStr(byteArray[i]);
		}
		return strDigest;
	}
 
	/**
	 * 将字节转换为十六进制字符串
	 * 
	 * @param mByte
	 * @return
	 */
	private static String byteToHexStr(byte mByte) {
		char[] Digit = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
		char[] tempArr = new char[2];
		tempArr[0] = Digit[(mByte >>> 4) & 0X0F];
		tempArr[1] = Digit[mByte & 0X0F];
 
		String s = new String(tempArr);
		return s;
	}
}

<四> 扫码关注回调:上面的接口配置信息的url接口:post请求回调

  @ApiOperation(value = "公众号-扫码登录回调函数(PC端)", notes = "公众号-扫码登录回调函数(PC端)")
  @PostMapping("/public/wx/check")
  public void post(HttpServletRequest request) {
    publicThirdLoginService.publicWxCallback(request);
  }

上面调用的publicThirdLoginService.publicWxCallback的代码如下:

 @Transactional(rollbackFor = Exception.class)
  public void publicWxCallback(HttpServletRequest request) {
    try {
      Map<String, String> map = PublicUtil.xmlToMap(request);

      String userOpenId = (String) map.get("FromUserName");
      String event = (String) map.get("Event");

      // 获取用户信息
      Map<String, Object> userMap = PublicUtil.getUserInfoByOpenid(userOpenId);
      // 获取二维码字串
      String qrSceneStr = userMap.get("qr_scene_str") != null ? userMap.get("qr_scene_str").toString() : null;

      if ("subscribe".equals(event)) { // 点击关注|订阅
        // 写你自己需要的代码
        checkLoginInfo(request, userOpenId, userMap, qrSceneStr, "subscribe");

      } else if ("SCAN".equals(event)) { // 扫码 -- 已经关注的情况
        // 写你自己需要的代码
        checkLoginInfo(request, userOpenId, userMap, qrSceneStr, "SCAN");

      } else if ("unsubscribe".equals(event)) { // 取消订阅|取消关注
        logger.info(userMap.get("nickname").toString() + "用户取消了关注");
      }
    } catch (Exception e) {
      logger.error("公众号微信回调出错了!");
      e.printStackTrace();
    }
  }

上面涉及的checkLoginInfo接口如下(这个方法主要是操作自己的数据库等逻辑代码):

  @Transactional(rollbackFor = Exception.class)
  public void checkLoginInfo(HttpServletRequest request, String userOpenId, Map<String, Object> userMap, String qrSceneStr, String type) throws AssertException {
    // 查询之前是否关注过
    // 检查数据库表UserThird (用你自己的逻辑代码操作)
    UserThird userThirdParam = new UserThird();
    userThirdParam.setThirdCode(userOpenId);
    userThirdParam.setRegType(RegisterTypeEnum.WX_MP.getCode());
    UserThird userThirdSelect = userThirdService.selectByObj(userThirdParam);

    // 新增
    if (userThirdSelect == null || userThirdSelect.getId() == null) {
      UserThird userThird = new UserThird();
      userThird.setNickName(userMap.get("nickname").toString());
      userThird.setImgUrl(userMap.get("headimgurl").toString());
      userThird.setCreateTime(new Date());
	  // 省略......

      if (StringUtils.isNotBlank(qrSceneStr)) {
        Map<String, Object> m = new HashMap<>();
        m.put("code", "SCAN".equals(type) ? 2 : 1);
        m.put("nickname", userMap.get("nickname").toString());
        m.put("avatar", userMap.get("headimgurl").toString());
        m.put("key", qrSceneStr);

        CacheUtil.set("uwa_cato" + qrSceneStr, JsonUtil.toJson(m), 120);
        CacheUtil.set("uwa_cato_openid" + qrSceneStr, userOpenId, 300);
      }
    } else if (userThirdSelect.getUserId() == null) { // 未绑定

      Map<String, Object> m = new HashMap<>();
      m.put("code", "SCAN".equals(type) ? 2 : 1;
      m.put("nickname", userMap.get("nickname").toString());
      m.put("avatar", userMap.get("headimgurl").toString());
      m.put("key", qrSceneStr);

      CacheUtil.set("uwa_cato" + qrSceneStr, JsonUtil.toJson(m), 120);
      CacheUtil.set("uwa_cato_openid" + qrSceneStr, userOpenId, 300);
    } else {

      User user = userService.select(userThirdSelect.getUserId());
      if (user == null || user.getId() == null) { // 未绑定
        Map<String, Object> m = new HashMap<>();
        m.put("code", "SCAN".equals(type) ? ScanLoginTypeEnum.SCAN_UNBIND.getCode() : ScanLoginTypeEnum.SUBSCRIBE_UNBIND.getCode());
        m.put("nickname", userMap.get("nickname").toString());
        m.put("avatar", userMap.get("headimgurl").toString());
        m.put("key", qrSceneStr);

        CacheUtil.set("uwa_cato" + qrSceneStr, JsonUtil.toJson(m), 120);
        CacheUtil.set("uwa_cato_openid" + qrSceneStr, userOpenId, 300);
      } else { // 绑定了
        // 判断状态正常与否
        AssertException.assertException(user.getStatus() == AccountStatusEnum.NOT_ACTIVE.getCode(), AppTipMsgEnum.LOGIN_FAIL.getCode(), "账号未激活!");
        AssertException.assertException(Objects.equals(user.getIsDel(), DeleteStatusEnum.DELETED.getCode()), AppTipMsgEnum.LOGIN_FAIL.getCode(), "账号不存在!");
        AssertException.assertException(user.getStatus() == AccountStatusEnum.BLACKLIST.getCode(), AppTipMsgEnum.LOGIN_FAIL.getCode(), "账号已被加入黑名单,请联系管理员!");

        // 登录成功,发放token
        user.setPasswd("");
        String token = commonService.createToken(user);

        Map<String, Object> m = new HashMap<>();
        m.put("code", ScanLoginTypeEnum.BIND.getCode());
        m.put("userInfo", user);
        m.put("key", qrSceneStr);
        m.put("token", token);
        CacheUtil.set("uwa_cato" + qrSceneStr, JsonUtil.toJson(m), 120);

        // 添加登录日志
        try {
          Date now = new Date();
          UserLoginLog userLoginLog = new UserLoginLog();
          {
            userLoginLog.setLoginDate(now);
            userLoginLog.setUserId(Long.valueOf(user.getId()));
            userLoginLog.setCurrentReqIp(IpUtil.fetchAccessIp(request));
            userLoginLog.setCurrentReqIpAddr(IpToAddressUtil.getCityInfo(IpUtil.fetchAccessIp(request)));
            userLoginLog.setType(LoginStatusEnum.LOGIN.getCode());
            userLoginLog.setCreateTime(now);
          }
          userLoginLogService.insert(userLoginLog);
        } catch (Exception e) {
          logger.error("登录日志报错");
        }
      }
    }
  }

说明:
对于网站使用微信登录来说,不管是公众平台的微信登录还是开放平台的微信登录,都不能获取到手机号等私密的信息,如果需要绑定手机号,需要使用其他方式,如用户扫码后跳转到绑定手机号的页面等。

另外,代码有不足之处欢迎指教,谢谢观看!

开放平台的登录请看另外一篇:开放平台–扫描微信二维码登录

标签:Map,String,get,--,微信,token,二维码,put,null
来源: https://blog.csdn.net/jingxin_123/article/details/120993187

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有