spring boot 微信小程序 对接 springboot整合微信小程序_微信

一、SpringBoot整合微信登录

小程序的使用已经是一个普遍的现象,对于刚学习SpringBoot的我们来说完全的写好一套微信登录的案例有一定的难度,这里结合自己的一些学习经验,把微信登录这一功能模块附上:

1、准备工作

申请注册一个属于自己的小程序,或者是能够使用的(白嫖别人的…)

spring boot 微信小程序 对接 springboot整合微信小程序_小程序_02

2、创建SpringBootg工程并编写配置

server:
  port: 8087
spring:
  application:
    name: applet
  profiles:
    active: dev
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    locale: zh_CN
    time-zone: GMT+8
  servlet:
    multipart:
      max-file-size: 200MB
      max-request-size: 200MB
weixin:
  login:
    info:
      # appid
      appid: wx*************
      # 秘钥
      appsecret: e**************************

后面会用到的一些依赖

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.76</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.20</version>
        </dependency>

3、微信登录业务处理

这里我们就由外到内的进行,基于业务需求出发,先编写Controller层

@RestController
@RequestMapping(value = "/wx")
public class WeChatLoginController {
    
    @Resource
    private LoginService loginService;
    
    @ApiOperation("授权登录,获取电话号码")
    @PostMapping(value = "/Authorization")
    public R<Object> authorization(@RequestBody Login login) {
        return loginService.AuthorizationLogin(login);
    }
}

具体实现

/**
 * @description: 微信登录
 * @author: XXXLOMG
 * @create: 2021-07-05 22:06
 */
@Service
public class LoginService {

    @Value("${weixin.login.info.appid}")
    private String appid;

    @Value("${weixin.login.info.appsecret}")
    private String appsecret;

    @Resource
    private UserMapper userMapper;

    //微信授权登录获取电话号码
    public R<Object> AuthorizationLogin(Login login) {
        JSONObject jsonObject = getSessionKey(login.getCode());
        String json = wxDecrypt(login.getEncryptedData(), jsonObject.getString("session_key"), login.getIv());
        Phone phoneDTO = JSON.parseObject(json, Phone.class);
        String openid = jsonObject.getString("openid");//openId
        String phone = phoneDTO.getPhoneNumber();//电话号码
        //TODO 自行实现自己的用户逻辑
        ......
            
        JSONObject result = new JSONObject()
        result.put("openid",openid);
        result.put("phone",phone);
        return RUtils.success(result);
    }

     public JSONObject getSessionKey(String code) {
        String params = "appid=" + appid + "&secret=" + appsecret + "&js_code=" + code + "&grant_type=authorization_code";
        String sr = HttpRequestUtils.sendGet(loginUrl, params);
        return JSON.parseObject(sr);
    }
}

其中的DTO

@Data
public class Phone {

    /**
     * 用户绑定的手机号(国外手机号会有区号)
     */
    private String phoneNumber;

    /**
     * 没有区号的手机号
     */
    private String purePhoneNumber;

    /**
     * 区号
     */
    private String countryCode;
}

HttpRequest的请求工具类

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;

/**
 * @author XXXLOMG
 */
public class HttpRequestUtils {
    public static String sendGet(String url, String param) {
        StringBuilder result = new StringBuilder();
        BufferedReader in = null;
        try {
            String urlNameString = url + "?" + param;
            URL realUrl = new URL(urlNameString);
            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));
            }
            in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
            String line;
            while ((line = in.readLine()) != null) {
                result.append(line);
            }
        } catch (Exception e) {
            System.out.println("发出GET请求异常" + e);
            e.printStackTrace();
        } finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (Exception e2) {
                e2.printStackTrace();
            }
        }
        return result.toString();
    }

    public static String sendPost(String url, String param) {
        PrintWriter out = null;
        BufferedReader in = null;
        StringBuilder result = new StringBuilder();
        try {
            URL realUrl = new 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)");
            conn.setDoOutput(true);
            conn.setDoInput(true);
            out = new PrintWriter(conn.getOutputStream());
            out.print(param);
            out.flush();
            in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
            String line;
            while ((line = in.readLine()) != null) {
                result.append(line);
            }
        } catch (Exception e) {
            System.out.println("发出POST请求异常" + e);
            e.printStackTrace();
        } finally {
            try {
                if (out != null) {
                    out.close();
                }
                if (in != null) {
                    in.close();
                }
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
        return result.toString();
    }
}

二、关于微信小程序前端的数据处理

首先先了解springmvc的机制和spring中的注解含义

大多情况下的数据处理都是在controller中用@RequestBody的注解来解析json字符串,用JSONObject加以处理,这里的工具类是阿里的,当然你可以用hutool

JSONObject jsonObject = JSON.parseObject(JsonString);

或是直接用实体类接受 (@RequestBody User user)

1、@RequestBody

注解@RequestBody接收的参数是来自requestBody中,即请求体。一般用于处理非 Content-Type: application/x-www-form-urlencoded编码格式的数据,比如:application/jsonapplication/xml等类型的数据。

application/json类型的数据而言,使用注解@RequestBody可以将body里面所有的json数据传到后端,后端再进行解析。

GET请求中,因为没有HttpEntity,所以@RequestBody并不适用。

POST请求中,通过HttpEntity传递的参数,必须要在请求头中声明数据的类型Content-Type,SpringMVC通过使用

HandlerAdapter 配置的HttpMessageConverters来解析HttpEntity中的数据,然后绑定到相应的bean上。

2、@RequestParam

注解@RequestParam接收的参数是来自HTTP请求体或请求url的QueryString中。

RequestParam可以接受简单类型的属性,也可以接受对象类型。

@RequestParam有三个配置参数:

  • required 表示是否必须,默认为 true,必须。
  • defaultValue 可设置请求参数的默认值。
  • value 为接收url的参数名(相当于key值)。

@RequestParam用来处理 Content-Typeapplication/x-www-form-urlencoded 编码的内容,Content-Type默认为该属性**。

@RequestParam也可用于其它类型的请求,例如:POST、DELETE等请求**。

所以在postman中,要选择body的类型为 x-www-form-urlencoded,这样在headers中就自动变为了 Content-Type : application/x-www-form-urlencoded 编码格式。

小程序在传递表单数据时,通常会创建一个对象,然后把表单数据绑定赋给这个对象上

小程序的表单传递数据是以x-www-form-urlencoded的格式传递的

var form = JSON.stringify(e.detail.value)

在springboot的框架下,获取的x-www-form-urlencoded的数据是需要通过@RequestParam的注解来获取,以获取微信小程序发送的form表单数据为例:

@RequestMapping(value = "/weChatUserInsert")
    public String weChatUserInsert(@RequestParam("form") String form)
    {
        return form;
    }

在微信中的常用的解码工具类,这里针对授权登陆

import org.apache.commons.codec.binary.Base64;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.AlgorithmParameters;
import java.security.Key;
import java.security.Security;

/**
 * @author XXXLOMG
 */
public class AES {

    // 算法名
    public static final String KEY_NAME = "AES";
    // 加解密算法/模式/填充方式
    // ECB模式只用密钥即可对数据进行加密解密,CBC模式需要添加一个iv
    public static final String CIPHER_ALGORITHM = "AES/CBC/PKCS7Padding";

    /**
     * 微信 数据解密<br/>
     * 对称解密使用的算法为 AES-128-CBC,数据采用PKCS#7填充<br/>
     * 对称解密的目标密文:encrypted=Base64_Decode(encryptData)<br/>
     * 对称解密秘钥:key = Base64_Decode(session_key),aeskey是16字节<br/>
     * 对称解密算法初始向量:iv = Base64_Decode(iv),同样是16字节<br/>
     *
     * @param encrypted 目标密文
     * @param session_key 会话ID
     * @param iv 加密算法的初始向量
     */
    public static String wxDecrypt(String encrypted, String session_key, String iv) {
        String json = null;
        byte[] encrypted64 = Base64.decodeBase64(encrypted);
        byte[] key64 = Base64.decodeBase64(session_key);
        byte[] iv64 = Base64.decodeBase64(iv);
        byte[] data;
        try {
            init();
            json = new String(decrypt(encrypted64, key64, generateIV(iv64)));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return json;
    }

    /**
     * 初始化密钥
     */
    public static void init() throws Exception {
        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
        KeyGenerator.getInstance(KEY_NAME).init(128);
    }

    /**
     * 生成iv
     */
    public static AlgorithmParameters generateIV(byte[] iv) throws Exception {
        // iv 为一个 16 字节的数组,这里采用和 iOS 端一样的构造方法,数据全为0
        // Arrays.fill(iv, (byte) 0x00);
        AlgorithmParameters params = AlgorithmParameters.getInstance(KEY_NAME);
        params.init(new IvParameterSpec(iv));
        return params;
    }

    /**
     * 生成解密
     */
    public static byte[] decrypt(byte[] encryptedData, byte[] keyBytes, AlgorithmParameters iv)
            throws Exception {
        Key key = new SecretKeySpec(keyBytes, KEY_NAME);
        Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
        // 设置为解密模式
        cipher.init(Cipher.DECRYPT_MODE, key, iv);
        return cipher.doFinal(encryptedData);
    }

}

结语:

基于SpringBoot微信登录到这里就结束啦!若有不对的地方,欢迎指正。