微信小程序04: 获取openId和unionId

全文目录,一步到位

  • 1.前言简介
    • [1.1 专栏传送门](#1.1 专栏传送门)
      • [1.1.1 上文小总结](#1.1.1 上文小总结)
      • [1.1.2 上文传送门](#1.1.2 上文传送门)
  • [2. 获取openId和unionId操作](#2. 获取openId和unionId操作)
    • [2.1 准备工作](#2.1 准备工作)
      • [2.1.1 请先复制00篇的统一封装代码](#2.1.1 请先复制00篇的统一封装代码)
      • [2.1.2 微信登录请求dto](#2.1.2 微信登录请求dto)
    • [2.2 具体代码使用与注释如下](#2.2 具体代码使用与注释如下)
      • [2.2.1 业务代码](#2.2.1 业务代码)
      • [2.2.2 代码解释(一)[无需复制]](#2.2.2 代码解释(一)[无需复制])
      • [2.2.3 获取的map使用方式](#2.2.3 获取的map使用方式)
      • [2.2.4 解密功能(iv+encryptedData)](#2.2.4 解密功能(iv+encryptedData))
    • [2.2.5 微信接口返回值校验](#2.2.5 微信接口返回值校验)
      • [2.2.6 实际使用方式(一行代码)](#2.2.6 实际使用方式(一行代码))
  • [3. 文章的总结与预告](#3. 文章的总结与预告)
    • [3.1 本文总结](#3.1 本文总结)
    • [3.2 下文预告](#3.2 下文预告)

1.前言简介

1.1 专栏传送门

=> 小程序相关操作专栏 <=

1.1.1 上文小总结

上文主要是大多数微信小程序的整体封装, 代码共用, 而本篇只需要关心业务本身即可, 使用前请先复制上文的代码后使用(请看1.1.2文章传送门)

1.1.2 上文传送门

===> 微信小程序00: 公共封装配置(核心篇)

2. 获取openId和unionId操作

2.1 准备工作

2.1.1 请先复制00篇的统一封装代码

这里强调一下
请先复制核心篇: ===> 微信小程序-00 小程序统一封装类
请先阅读上一篇: ===> 微信小程序01: springboot获取accessToken方式

2.1.2 微信登录请求dto

前三个为参数 后面为业务

java 复制代码
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

import javax.validation.constraints.NotBlank;

/**
 * @author pzy
 * @version 0.1.0
 * @description: TODO
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class WxCommonReqDto {

    /**
     * 微信code
     */
    @NotBlank(message = "微信code不能为空")
    private String wxCode;

    /**
     * 微信加密数据
     */
    private String encryptedData;

    /**
     * 加密算法的初始向量
     */
    private String iv;

    /**
     * 推广标识码")
     */
    private String promotionCode;

    /**
     * 手机号
     */
    private String phone;
}

2.2 具体代码使用与注释如下

2.2.1 业务代码

流程解释:

通过wxcode获取的openid作为唯一表示 用于写登录认证逻辑

ps: 一个小程序内的openid是唯一的, 如果是多个小程序并且没有unionId, 数据库存储openid是多对一关系

java 复制代码
public AjaxResult wxMiniLogin(WxMiniLoginReqDTO wxMiniLoginReqDTO) {

        log.info("登录参数:" + JSON.toJSONString(wxMiniLoginReqDTO));

        String phone = wxMiniLoginReqDTO.getPhone();

        /*获取微信具体信息*/
        Map<String, String> wxMiniAuthMap = wechatServiceUtils.getWxMiniAuth(wxMiniLoginReqDTO);

        //如果前端只传wxCode 没有unionId的需求 只要openId的(直接登录不需要注册)

		//TODO 业务层代码 返回登录信息token等

        log.info("===> 当前登录人token信息: {}", JSON.toJSONString(map));

        return AjaxResult.success("登录成功!", map);
    }

2.2.2 代码解释(一)[无需复制]

内置两套逻辑 openid和unionId获取信息

用type区分1是unionId 2是openId

decryptResult是字符串类型

1返回的是json字符串 2是纯字符串

java 复制代码
public Map<String, String> getWxMiniAuth(WxCommonReqDto wxCommonReqDto) {
        String code = wxCommonReqDto.getWxCode();
        //秘钥
        String encryptedIv = wxCommonReqDto.getIv();
        //加密数据
        String encryptedData = wxCommonReqDto.getEncryptedData();

        JSONObject wxAuthResponse = null;
        String sessionKey = "";
        String openid = "";
        try {
            //1. 想微信服务器发送请求获取用户信息
            String url = wechatConfigProperties.getWxLoginUrl(code);
            log.info("===> 请求微信url是: {}", url);

            //2. 远程调用微信接口
            String res = restTemplate.getForObject(url, String.class);
            wxAuthResponse = JSONObject.parseObject(res);

            //3. 解析返回参数 报错则进行对应处理
            /*校验1: wx请求不是null*/
            if (wxAuthResponse != null) {

                /*校验2: 响应对象是否正确*/
                CheckUtils.responseCheck(wxAuthResponse);

                //3.1 获取session_key和openid
                sessionKey = wxAuthResponse.getString("session_key");
                openid = wxAuthResponse.getString("openid");
                log.info("===> openid:  {}", openid);

                /*校验3: 响应信息是否正常*/
                if (StringUtils.isBlank(sessionKey) || StringUtils.isBlank(openid)) {
                    log.error("小程序授权失败,session_key或open_id是空!");
                    throw new ServiceException("抱歉, 小程序授权失败,缺少关键返回参数!");
                }
                log.info("===> 微信回调信息: {}", wxAuthResponse);
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new ServiceException("抱歉, 小程序授权失败!");
        }

        //4. 获取微信信息并制作token
        /*校验:(选用)如果获取union_id 需要进行解密*/
        Map<String, String> map = new HashMap<>();
        String token = "";
        if (StringUtils.isNotBlank(encryptedIv) && StringUtils.isNotBlank(encryptedData)) {
            String decryptResult = "";
            try {
                //如果没有绑定微信开放平台,解析结果是没有unionid的。
                decryptResult = AESUtils.decrypt(sessionKey, encryptedIv, encryptedData);

                if (StringUtils.hasText(decryptResult)) {
                    //如果解析成功,获取token
                    map.put("type", String.valueOf(1));
                    map.put("decryptResult", decryptResult);
                }
            } catch (Exception e) {
                e.printStackTrace();
                throw new ServiceException("微信登录失败!");
            }
        } else {
            //如果前端只传wxCode 没有unionId的需求 只要openId的
            map.put("type", String.valueOf(2));
            map.put("decryptResult", openid);
        }

        /*校验: 数据为空的情况*/
        if (StringUtils.isBlank(map.get("type")) || StringUtils.isBlank(map.get("decryptResult"))) {
            throw new ServiceException(ResponseEnum.A10007);
        }


        return map;
    }

2.2.3 获取的map使用方式

需要使用unionId的 将注释打开

获取后可以进行后续操作, 比如注册, 登录 等

wx.login()获取的code使用一次即为失效, 切记

java 复制代码
        String openId = "";
        String nickName = "";
        String unionId = "";
        String avatarUrl = "";

        String typeStr = wxMiniAuthMap.get("type");//1 unionId 2 openId
        String decryptResult = wxMiniAuthMap.get("decryptResult");
        if (StringUtils.isBlank(typeStr) || StringUtils.isBlank(decryptResult)) {
            throw new ServiceException(ResponseEnum.A10007);
        }

        int type = Integer.parseInt(typeStr);

        /*校验: 带解密的 需要union_id的*/
        if (type == 1) {
            //字符串转json
            JSONObject jsonObject = JSONObject.parseObject(decryptResult);
            openId = jsonObject.getString("openId");//openId
            //TODO 后续支持的功能
            //      nickName = jsonObject.getString("nickName");//获取nickName
            //      unionId = jsonObject.getString("unionId");//unionId
            //      avatarUrl = jsonObject.getString("avatarUrl");//获取头像
        } else {
            openId = decryptResult;
        }
unionId的解密效果图如下:

2.2.4 解密功能(iv+encryptedData)

AES对称性(CBC)加密 iv偏移量
=> 传送门: AES加密简介与使用

java 复制代码
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import javax.validation.constraints.NotNull;
import java.security.Security;
import java.security.spec.AlgorithmParameterSpec;

/**
 * @author pzy
 * @description TODO
 * @version 0.1.0
 */
@Slf4j
public class AESUtils {
    // 加密模式
    private static final String ALGORITHM = "AES/CBC/PKCS7Padding";
    private static final String CHARSET_NAME = "UTF-8";
    private static final String AES_NAME = "AES";

    //解决java.security.NoSuchAlgorithmException: Cannot find any provider supporting AES/CBC/PKCS7Padding
    static {
        Security.addProvider(new BouncyCastleProvider());
    }

    /**
     * 解密
     *
     * @param content 目标密文
     * @param key     秘钥
     * @param iv      偏移量
     * @return
     */
    public static String decrypt(@NotNull String content, @NotNull String key, @NotNull String iv) {
        try {
            Cipher cipher = Cipher.getInstance(ALGORITHM);
            byte[] sessionKey = java.util.Base64.getDecoder().decode(key);
            SecretKeySpec keySpec = new SecretKeySpec(sessionKey, AES_NAME);
            byte[] ivByte = java.util.Base64.getDecoder().decode(iv);
            AlgorithmParameterSpec paramSpec = new IvParameterSpec(ivByte);
            cipher.init(Cipher.DECRYPT_MODE, keySpec, paramSpec);
            return new String(cipher.doFinal(Base64.decodeBase64(content)), CHARSET_NAME);
        } catch (Exception e) {
            log.error("解密失败:{}", e);
            e.printStackTrace();
        }
        return StringUtils.EMPTY;
    }
    
    public static void main(String[] args) {
        //接口传入的参数
        String appid = "";
        String sessionKey = "";
        String encryptedData = "";
        String iv = "";

        String decrypt = AESUtils.decrypt(encryptedData, sessionKey, iv);
        System.out.println("解密后:" + decrypt);
    }

2.2.5 微信接口返回值校验

可以持续维护, 异常码一大堆, 这里仅有40029的响应值校验

java 复制代码
    /**
     * 微信返回值校验
     *
     * @param wxAuthResponse
     */
    public static void responseCheck(JSONObject wxAuthResponse) {
        if (wxAuthResponse.containsKey("errcode")) {

            log.error("===> 异常回调信息: {} ", wxAuthResponse);

            /*40029 错误的wxCode*/
            if ("40029".equals(wxAuthResponse.get("errcode"))) {
                throw new ServiceException("错误的微信code!");
            }

            throw new ServiceException("抱歉, 小程序授权失败!");
        }
    }

2.2.6 实际使用方式(一行代码)

java 复制代码
/*获取微信具体信息*/
Map<String, String> wxMiniAuthMap = wechatServiceUtils.getWxMiniAuth(wxMiniLoginReqDTO);

3. 文章的总结与预告

3.1 本文总结

  • AES加密解密
  • 微信code

3.2 下文预告

  • link码
  • 支付
  • RSA加密

@author: pingzhuyan
@description: ok
@year: 2024

相关推荐
黎明丶之前3 分钟前
Spring Cloud Gateway 升级与 Bucket4j 限流实践
java·spring cloud
程序员木圭8 分钟前
05-告别逻辑混乱!Java 流程控制让代码学会"判断和循环"
java·后端
MmeD UCIZ12 分钟前
Skywalking介绍,Skywalking 9.4 安装,SpringBoot集成Skywalking
spring boot·后端·skywalking
ward RINL12 分钟前
Spring boot启动原理及相关组件
数据库·spring boot·后端
yaaakaaang12 分钟前
三、抽象工厂模式
java·抽象工厂模式
kongba00713 分钟前
复刻 Claude Code 项目御马术缰绳系统 harness engineering 落地蓝图
java·linux·服务器
tERS ERTS15 分钟前
Spring Cloud gateway 路由规则
java
ZUNr115 分钟前
手写一个迷你版 @Column:注解到底是怎么工作的?
java
Treh UNFO20 分钟前
MySQL中的通配符
java
AI茶水间管理员20 分钟前
线程池核心线程数设为 0 会怎样?(附源码解析)
java·后端·面试