全文目录,一步到位
- 1.前言简介
- 1.1 专栏传送门
- 1.1.1 上文小总结
- 1.1.2 上文传送门
- 2. 获取openId和unionId操作
- 2.1 准备工作
- 2.1.1 请先复制00篇的统一封装代码
- 2.1.2 微信登录请求dto
- 2.2 具体代码使用与注释如下
- 2.2.1 业务代码
- 2.2.2 代码解释(一)[无需复制]
- 2.2.3 获取的map使用方式
- unionId的解密效果图如下:
- 2.2.4 解密功能(iv+encryptedData)
- 2.2.5 微信接口返回值校验
- 2.2.6 实际使用方式(一行代码)
- 3. 文章的总结与预告
- 3.1 本文总结
- 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
前三个为参数 后面为业务
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是多对一关系
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是纯字符串
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使用一次
即为失效, 切记
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加密简介与使用
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的响应值校验
/**
* 微信返回值校验
*
* @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 实际使用方式(一行代码)
/*获取微信具体信息*/
Map<String, String> wxMiniAuthMap = wechatServiceUtils.getWxMiniAuth(wxMiniLoginReqDTO);
3. 文章的总结与预告
3.1 本文总结
- AES加密解密
- 微信code
3.2 下文预告
- link码
- 支付
- RSA加密
@author: pingzhuyan
@description: ok
@year: 2024