微信小程序获取UnionId - Java后台解决

UnionId 是微信平台用户的唯一标识,对于后台开发来说,UnionId 无疑是一个非常好用的东西。

前言

在获取 UnionId 之前,首先需要做下面的准备:

  • 在小程序平台完善自己的微信小程序信息

  • 在微信开放平台将自己的微信小程序进行绑定
    微信小程序获取UnionId - Java后台解决_第1张图片
    (在绑定小程序中使用的小程序账号就是上一步注册小程序时所用的账号/邮箱)

一、编写微信小程序前端代码

从 wx.getUserInfo接口文档 中可以知道,UnionId 被加密在 wx.getUserInfo的回调函数返回值 encryptedData 中,所以我们只需要将 encryptedData 解密就可以获取到 UnionId 的值了。

1.编写 wxml button组件

编写 button 按钮且存在open-type="getUserInfo"属性,bindgetuserinfo 指定函数来接收回调函数返回值:

<view class="order_auth_wrap">
    <button type="primary" plain open-type="getUserInfo" 
    bindgetuserinfo="handleGetUserInfo">
        获取授权
    button>
view>

2.编写 js 请求发送逻辑代码

在js代码中,我封装了 wx.request 接口函数,但是参数类型是相同的(其中 url 就是将encryptedData, iv, code发送到自己后台服务器进行解密的请求路径):

import { login, request } from "../../utils/asyncWx.js";
import regeneratorRuntime from '../../lib/runtime/runtime';

//获取应用实例
const app = getApp()

Page({
  // 获取用户信息回调函数
  async handleGetUserInfo(e) {
    try {
      // 1.获取用户信息
      const { encryptedData, iv } = e.detail;
      // 2.获取小程序成功登陆后的code
      const { code } = await login();

      // 3.向后台发送 encryptedData, iv, code,后台通过解密之后获取 UserInfo
      const params = { encryptedData, iv, code };
      const header = { 'content-type': 'application/x-www-form-urlencoded' };
      const result = await request({ url: "***", data: params, method: "POST", header });
      console.log(result);
      const { unionId } = result.data;
      // 4.将 unionId 保存到全局变量中
      app.globalData.unionId = unionId;
      // 5.跳转回上一层页面
      wx.navigateBack({
        delta: 1
      });
    } catch (error) {
      console.log(error);
    }
  }
})

二、后台解密Controller实现

1.处理前端发送的 url 请求 controller:

package com.kiger.controller;

import com.alibaba.fastjson.JSONObject;
import com.kiger.Utils.AesCbcUtil;
import com.kiger.Utils.HttpRequest;
import com.kiger.entities.CommonResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Map;

/**
 * @author zk_kiger
 * @date 2020/4/18
 */

@RestController
@Slf4j
@RequestMapping("/api")
public class ConsumerController {

    @PostMapping("/getUserInfo")
    public CommonResult<Map> getUserInfo(String encryptedData, String iv, String code) throws UnsupportedEncodingException {
        encryptedData = encryptedData.replaceAll(" ", "+");
        iv = iv.replaceAll(" ", "+");
        // 判断登录凭证不为空
        if (code == null || code.length() == 0) {
            return new CommonResult<>(0, "code 不能为空!");
        }

        // APPId
        String appId = "wxacb2455649f74388";
        // AppSecret
        String appSecret = "3b49f7095369a48d33303c7d95c41ce5";
        // grant_type(固定内容)
        String grant_type = "authorization_code";

        // =================== 1.向微信服务器使用登录凭证 code 获取 session_key 和 openid ==============
        // 请求参数
        String params = "appid=" + appId + "&secret=" + appSecret + "&js_code=" + code + "&grant_type=" + grant_type;
        // 发送请求
        String res = HttpRequest.sendGet("https://api.weixin.qq.com/sns/jscode2session", params);
        System.out.println("res: " + res);
        // 解析响应的内容转换为JSON对象
        JSONObject json = JSONObject.parseObject(res);
        //获取会话密钥(session_key)
        String session_key = json.get("session_key").toString();
        //用户的唯一标识(openid)
        String openid = (String) json.get("openid");

        // =================== 2.对encryptedData加密数据进行AES解密 ===================
        try {
            String result = AesCbcUtil.decrypt(encryptedData, session_key, iv, "UTF-8");
            System.out.println(result);
            if (result != null && result.length() > 0) {
                HashMap<String, Object> userInfoMap = new HashMap<>();
                JSONObject userInfo = JSONObject.parseObject(result);
                userInfoMap.put("openId", userInfo.get("openId"));
                userInfoMap.put("nickName", userInfo.get("nickName"));
                userInfoMap.put("gender", userInfo.get("gender"));
                userInfoMap.put("city", userInfo.get("city"));
                userInfoMap.put("province", userInfo.get("province"));
                userInfoMap.put("country", userInfo.get("country"));
                userInfoMap.put("avatarUrl", userInfo.get("avatarUrl"));
                userInfoMap.put("unionId", userInfo.get("unionId"));
                return new CommonResult<>(1, "解密成功!", userInfoMap);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return new CommonResult<>(0, "解密失败!");
    }
}

需要注意:在接收参数之后,代码中对encryptedData和iv中字符串进行替换,这是因为在传输中,把原本的 ‘+’ 变为了 ‘ ’。

2.发送HTTP请求工具类

package com.kiger.Utils;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.URL;
import java.net.URLConnection;
import java.util.List;
import java.util.Map;

public class HttpRequest {

    /**
     * 向指定URL发送GET方法的请求
     *
     * @param url   发送请求的URL
     * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
     * @return URL 所代表远程资源的响应结果
     */
    public static String sendGet(String url, String param) {
        String result = "";
        BufferedReader in = null;
        try {
            String urlNameString = url + "?" + param;
            URL realUrl = new URL(urlNameString);
            // 打开和URL之间的连接
            URLConnection connection = realUrl.openConnection();
            // 设置通用的请求属性
            connection.setRequestProperty("accept", "*/*");
            connection.setRequestProperty("connection", "Keep-Alive");
            connection.setRequestProperty("user-agent",
                    "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
            // 建立实际的连接
            connection.connect();
            // 获取所有响应头字段
            Map<String, List<String>> map = connection.getHeaderFields();
            // 遍历所有的响应头字段
            for (String key : map.keySet()) {
                System.out.println(key + "--->" + map.get(key));
            }
            // 定义 BufferedReader输入流来读取URL的响应
            in = new BufferedReader(new InputStreamReader(
                    connection.getInputStream()));
            String line;
            while ((line = in.readLine()) != null) {
                result += line;
            }
        } catch (Exception e) {
            System.out.println("发送GET请求出现异常!" + e);
            e.printStackTrace();
        }
        // 使用finally块来关闭输入流
        finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (Exception e2) {
                e2.printStackTrace();
            }
        }
        return result;
    }

    /**
     * 向指定 URL 发送POST方法的请求
     *
     * @param url   发送请求的 URL
     * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
     * @return 所代表远程资源的响应结果
     */
    public static String sendPost(String url, String param) {
        PrintWriter out = null;
        BufferedReader in = null;
        String result = "";
        try {
            URL realUrl = new URL(url);
            // 打开和URL之间的连接
            URLConnection conn = realUrl.openConnection();
            // 设置通用的请求属性
            conn.setRequestProperty("accept", "*/*");
            conn.setRequestProperty("connection", "Keep-Alive");
            conn.setRequestProperty("user-agent",
                    "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
            // 发送POST请求必须设置如下两行
            conn.setDoOutput(true);
            conn.setDoInput(true);
            // 获取URLConnection对象对应的输出流
            out = new PrintWriter(conn.getOutputStream());
            // 发送请求参数

            out.print(param);
            // flush输出流的缓冲
            out.flush();
            // 定义BufferedReader输入流来读取URL的响应
            in = new BufferedReader(
                    new InputStreamReader(conn.getInputStream()));
            String line;
            while ((line = in.readLine()) != null) {
                result += line;
            }
        } catch (Exception e) {
            System.out.println("发送 POST 请求出现异常!" + e);
            e.printStackTrace();
        }
        //使用finally块来关闭输出流、输入流
        finally {
            try {
                if (out != null) {
                    out.close();
                }
                if (in != null) {
                    in.close();
                }
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
        return result;
    }
}

3.AES解密工具类

package com.kiger.Utils;

import org.apache.commons.codec.binary.Base64;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.security.*;
import java.security.spec.InvalidParameterSpecException;

/**
 * Created by yfs on 2017/2/6.
 * 

* AES-128-CBC 加密方式 * 注: * AES-128-CBC可以自己定义“密钥”和“偏移量“。 * AES-128是jdk自动生成的“密钥”。 */ public class AesCbcUtil { static { // BouncyCastle是一个开源的加解密解决方案,主页在http://www.bouncycastle.org/ Security.addProvider(new BouncyCastleProvider()); } /** * AES解密 * * @param data //密文,被加密的数据 * @param key //秘钥 * @param iv //偏移量 * @param encodingFormat //解密后的结果需要进行的编码 * @return * @throws Exception */ public static String decrypt(String data, String key, String iv, String encodingFormat) throws Exception { // 被加密的数据 byte[] dataByte = Base64.decodeBase64(data); // 加密秘钥 byte[] keyByte = Base64.decodeBase64(key); // 偏移量 byte[] ivByte = Base64.decodeBase64(iv); try { Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding"); SecretKeySpec spec = new SecretKeySpec(keyByte, "AES"); AlgorithmParameters parameters = AlgorithmParameters.getInstance("AES"); parameters.init(new IvParameterSpec(ivByte)); cipher.init(Cipher.DECRYPT_MODE, spec, parameters);// 初始化 byte[] resultByte = cipher.doFinal(dataByte); if (null != resultByte && resultByte.length > 0) { String result = new String(resultByte, encodingFormat); return result; } return null; } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (NoSuchPaddingException e) { e.printStackTrace(); } catch (InvalidParameterSpecException e) { e.printStackTrace(); } catch (InvalidKeyException e) { e.printStackTrace(); } catch (InvalidAlgorithmParameterException e) { e.printStackTrace(); } catch (IllegalBlockSizeException e) { e.printStackTrace(); } catch (BadPaddingException e) { e.printStackTrace(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return null; } }

4.包装返回结果entities类

package com.kiger.entities;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @author zk_kiger
 * @date 2020/4/17
 */

@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonResult<T> {
    private Integer status;
    private String message;
    private T data;

    public CommonResult(Integer status, String message) {
        this(status, message, null);
    }
}

你可能感兴趣的:(小程序后台开发)