#添加微信小程序依赖
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-miniapp</artifactId>
<version>4.4.8.B</version>
</dependency>
#微信配置
import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl;
import cn.binarywang.wx.miniapp.config.WxMaConfig;
import cn.binarywang.wx.miniapp.config.WxMaInMemoryConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class WxConfig {
@Autowired
private WxProperties properties;
/**
* 微信小程序获取accessToken
*/
@Bean
public WxMaConfig wxMaConfig(){
WxMaInMemoryConfig config = new WxMaInMemoryConfig();
config.setAppid(properties.getAppId());
config.setSecret(properties.getAppSecret());
return config;
}
/**
* 微信小程序获取accessToken
*/
@Bean
public WxMaService wxMaService(WxMaConfig maConfig){
WxMaService service = new WxMaServiceImpl();
service.setWxMaConfig(maConfig);
return service;
}
}
#微信参数属性类
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* 微信参数
*/
@Configuration
@ConfigurationProperties(prefix = "xx.wx")
@Data
public class WxProperties {
private String appId;
private String appSecret;
private String mchId;
private String mchKey;
private String notifyUrl;
private String keyPath;
private String templateId;
/**
*设置微信小程序消息服务器配置的token
* */
private String token;
/**
* 设置微信小程序消息服务器配置的EncodingAESKey
* */
private String aesKey;
/**
* 消息格式,XML或者JSON
*/
private String msgDataFormat;
}
#yml环境配置文件里设置
xx
wx:
app-id: xx
app-secret: xx
template-id: xx
token: xx #微信小程序消息服务配置的token
aesKey: xx #微信小程序消息服务器配置的EncodingAESKey
msgDataFormat: JSON
#请求类
import lombok.Data;
import javax.validation.constraints.NotBlank;
/**
* 微信登录请求参数
*/
@Data
public class WxLoginRequest {
/**
* 小程序登录需要的code
*/
@NotBlank(message = "code不能为空")
private String code;
/**
* 包括敏感数据在内的完整用户信息的加密数据
*/
@NotBlank(message = "参数错误")
private String encryptedData;
/**
* 加密算法的初始向量
*/
@NotBlank(message = "参数错误")
private String iv;
/**
* 手机号包括敏感数据在内的完整用户信息的加密数据
*/
@NotBlank(message = "参数错误")
private String mobileEncryptedData;
/**
* 手机号加密算法的初始向量
*/
@NotBlank(message = "参数错误")
private String mobileIv;
/**
* 邀请人id
*/
private Long inviteeId;
/**
* 用户id
*/
private Long userId;
}
#表现层
/**
* 微信授权
* @return
*/
@PostMapping("/wxAuthorization")
public ResponseUtil<Object> wxAuthorization(@RequestBody WxLoginRequest wxLoginRequest) throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
return tMemberService.wxAuthorization(wxLoginRequest);
}
#实现层
@Transactional
@Override
public ResponseUtil<Object> wxAuthorization(WxLoginRequest wxLoginRequest) throws InvalidAlgorithmParameterException, NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException {
String code = wxLoginRequest.getCode();
if (ObjectUtil.isNull(code)){
return ResponseUtil.paramError();
}
String sessionKey = null;
String openId = null;
try{
WxMaJscode2SessionResult result = this.wxMaService.getUserService().getSessionInfo(code);
sessionKey = result.getSessionKey();
openId = result.getOpenid();
}catch (Exception e){
log.error(e.getMessage());
}
if (sessionKey == null || openId == null){
ResponseUtil.fail();
}
//用户信息解密
String decryptUserInfo = WXUtil.decrypt(sessionKey,wxLoginRequest.getIv(),wxLoginRequest.getEncryptedData());
JSONObject userInfo = JSONObject.parseObject(decryptUserInfo);
//用户手机号信息解密
String decryptPhoneInfo = WXUtil.decrypt(sessionKey,wxLoginRequest.getMobileIv(),wxLoginRequest.getMobileEncryptedData());
JSONObject phoneInfo = JSONObject.parseObject(decryptPhoneInfo);
//没有区号的手机号
String purePhoneNumber = phoneInfo.getString("purePhoneNumber");
String avatarUrl = userInfo.getString("avatarUrl");
//如果openid存在或者已经导入过手机号的用户
TMember openidisnotnull = memberTypeRepository.findByOpenId(openId);
TMemberDto member = findById(wxLoginRequest.getUserId());
TMember user = new TMember();
if (ObjectUtil.isNotNull(openidisnotnull) || ObjectUtil.isNotNull(member) ){
if (openidisnotnull != null && openidisnotnull.getId() != null){
user.setId(openidisnotnull.getId());
}else if (member != null && member.getId() != null){
user.setId(member.getId());
}
if (StrUtil.isNotBlank(openId)){
user.setOpenId(openId);
}
if (StrUtil.isNotBlank(avatarUrl)){
user.setAvatar(avatarUrl);
}
if (StrUtil.isNotBlank(purePhoneNumber)){
user.setPhone(purePhoneNumber);
}
memberService.update(user);
}
Map<String,Object> claims = new HashMap<>();
claims.put(Constants.MINI_APP_LOGIN_USER_KEY,user.getId());
return ResponseUtil.ok(tokenProvider.createMinAppToken(claims));
}
public class TokenProvider{
// 令牌秘钥
@Value("${token.secret}")
private String secret;
/**
* 从数据声明生成令牌
*
* @param claims 数据声明
* @return 令牌
*/
public String createMinAppToken(Map<String, Object> claims)
{
String token = Jwts.builder()
.setClaims(claims)
.signWith(SignatureAlgorithm.HS512, secret).compact();
return token;
}
}
#工具类
import com.lawfirms.utils.sign.Base64;
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.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.AlgorithmParameterSpec;
public class WXUtil {
/**
* 解密信息
*/
public static String decrypt(String keyStr,String ivStr,String encDataStr) throws NoSuchPaddingException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, InvalidKeyException {
byte[] encData = Base64.decode(encDataStr);
byte[] iv = Base64.decode(ivStr);
byte[] key = Base64.decode(keyStr);
AlgorithmParameterSpec ivSpec = new IvParameterSpec(iv);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec keySpec = new SecretKeySpec(key,"AES");
cipher.init(Cipher.DECRYPT_MODE,keySpec,ivSpec);
return new String(cipher.doFinal(encData), StandardCharsets.UTF_8);
}
}
/**
* Base64工具类
*
* @author cartreasure
*/
public final class Base64
{
static private final int BASELENGTH = 128;
static private final int LOOKUPLENGTH = 64;
static private final int TWENTYFOURBITGROUP = 24;
static private final int EIGHTBIT = 8;
static private final int SIXTEENBIT = 16;
static private final int FOURBYTE = 4;
static private final int SIGN = -128;
static private final char PAD = '=';
static final private byte[] base64Alphabet = new byte[BASELENGTH];
static final private char[] lookUpBase64Alphabet = new char[LOOKUPLENGTH];
static
{
for (int i = 0; i < BASELENGTH; ++i)
{
base64Alphabet[i] = -1;
}
for (int i = 'Z'; i >= 'A'; i--)
{
base64Alphabet[i] = (byte) (i - 'A');
}
for (int i = 'z'; i >= 'a'; i--)
{
base64Alphabet[i] = (byte) (i - 'a' + 26);
}
for (int i = '9'; i >= '0'; i--)
{
base64Alphabet[i] = (byte) (i - '0' + 52);
}
base64Alphabet['+'] = 62;
base64Alphabet['/'] = 63;
for (int i = 0; i <= 25; i++)
{
lookUpBase64Alphabet[i] = (char) ('A' + i);
}
for (int i = 26, j = 0; i <= 51; i++, j++)
{
lookUpBase64Alphabet[i] = (char) ('a' + j);
}
for (int i = 52, j = 0; i <= 61; i++, j++)
{
lookUpBase64Alphabet[i] = (char) ('0' + j);
}
lookUpBase64Alphabet[62] = (char) '+';
lookUpBase64Alphabet[63] = (char) '/';
}
private static boolean isWhiteSpace(char octect)
{
return (octect == 0x20 || octect == 0xd || octect == 0xa || octect == 0x9);
}
private static boolean isPad(char octect)
{
return (octect == PAD);
}
private static boolean isData(char octect)
{
return (octect < BASELENGTH && base64Alphabet[octect] != -1);
}
/**
* Encodes hex octects into Base64
*
* @param binaryData Array containing binaryData
* @return Encoded Base64 array
*/
public static String encode(byte[] binaryData)
{
if (binaryData == null)
{
return null;
}
int lengthDataBits = binaryData.length * EIGHTBIT;
if (lengthDataBits == 0)
{
return "";
}
int fewerThan24bits = lengthDataBits % TWENTYFOURBITGROUP;
int numberTriplets = lengthDataBits / TWENTYFOURBITGROUP;
int numberQuartet = fewerThan24bits != 0 ? numberTriplets + 1 : numberTriplets;
char encodedData[] = null;
encodedData = new char[numberQuartet * 4];
byte k = 0, l = 0, b1 = 0, b2 = 0, b3 = 0;
int encodedIndex = 0;
int dataIndex = 0;
for (int i = 0; i < numberTriplets; i++)
{
b1 = binaryData[dataIndex++];
b2 = binaryData[dataIndex++];
b3 = binaryData[dataIndex++];
l = (byte) (b2 & 0x0f);
k = (byte) (b1 & 0x03);
byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0);
byte val3 = ((b3 & SIGN) == 0) ? (byte) (b3 >> 6) : (byte) ((b3) >> 6 ^ 0xfc);
encodedData[encodedIndex++] = lookUpBase64Alphabet[val1];
encodedData[encodedIndex++] = lookUpBase64Alphabet[val2 | (k << 4)];
encodedData[encodedIndex++] = lookUpBase64Alphabet[(l << 2) | val3];
encodedData[encodedIndex++] = lookUpBase64Alphabet[b3 & 0x3f];
}
// form integral number of 6-bit groups
if (fewerThan24bits == EIGHTBIT)
{
b1 = binaryData[dataIndex];
k = (byte) (b1 & 0x03);
byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
encodedData[encodedIndex++] = lookUpBase64Alphabet[val1];
encodedData[encodedIndex++] = lookUpBase64Alphabet[k << 4];
encodedData[encodedIndex++] = PAD;
encodedData[encodedIndex++] = PAD;
}
else if (fewerThan24bits == SIXTEENBIT)
{
b1 = binaryData[dataIndex];
b2 = binaryData[dataIndex + 1];
l = (byte) (b2 & 0x0f);
k = (byte) (b1 & 0x03);
byte val1 = ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0);
byte val2 = ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0);
encodedData[encodedIndex++] = lookUpBase64Alphabet[val1];
encodedData[encodedIndex++] = lookUpBase64Alphabet[val2 | (k << 4)];
encodedData[encodedIndex++] = lookUpBase64Alphabet[l << 2];
encodedData[encodedIndex++] = PAD;
}
return new String(encodedData);
}
/**
* Decodes Base64 data into octects
*
* @param encoded string containing Base64 data
* @return Array containind decoded data.
*/
public static byte[] decode(String encoded)
{
if (encoded == null)
{
return null;
}
char[] base64Data = encoded.toCharArray();
// remove white spaces
int len = removeWhiteSpace(base64Data);
if (len % FOURBYTE != 0)
{
return null;// should be divisible by four
}
int numberQuadruple = (len / FOURBYTE);
if (numberQuadruple == 0)
{
return new byte[0];
}
byte decodedData[] = null;
byte b1 = 0, b2 = 0, b3 = 0, b4 = 0;
char d1 = 0, d2 = 0, d3 = 0, d4 = 0;
int i = 0;
int encodedIndex = 0;
int dataIndex = 0;
decodedData = new byte[(numberQuadruple) * 3];
for (; i < numberQuadruple - 1; i++)
{
if (!isData((d1 = base64Data[dataIndex++])) || !isData((d2 = base64Data[dataIndex++]))
|| !isData((d3 = base64Data[dataIndex++])) || !isData((d4 = base64Data[dataIndex++])))
{
return null;
} // if found "no data" just return null
b1 = base64Alphabet[d1];
b2 = base64Alphabet[d2];
b3 = base64Alphabet[d3];
b4 = base64Alphabet[d4];
decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4);
decodedData[encodedIndex++] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
decodedData[encodedIndex++] = (byte) (b3 << 6 | b4);
}
if (!isData((d1 = base64Data[dataIndex++])) || !isData((d2 = base64Data[dataIndex++])))
{
return null;// if found "no data" just return null
}
b1 = base64Alphabet[d1];
b2 = base64Alphabet[d2];
d3 = base64Data[dataIndex++];
d4 = base64Data[dataIndex++];
if (!isData((d3)) || !isData((d4)))
{// Check if they are PAD characters
if (isPad(d3) && isPad(d4))
{
if ((b2 & 0xf) != 0)// last 4 bits should be zero
{
return null;
}
byte[] tmp = new byte[i * 3 + 1];
System.arraycopy(decodedData, 0, tmp, 0, i * 3);
tmp[encodedIndex] = (byte) (b1 << 2 | b2 >> 4);
return tmp;
}
else if (!isPad(d3) && isPad(d4))
{
b3 = base64Alphabet[d3];
if ((b3 & 0x3) != 0)// last 2 bits should be zero
{
return null;
}
byte[] tmp = new byte[i * 3 + 2];
System.arraycopy(decodedData, 0, tmp, 0, i * 3);
tmp[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4);
tmp[encodedIndex] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
return tmp;
}
else
{
return null;
}
}
else
{ // No PAD e.g 3cQl
b3 = base64Alphabet[d3];
b4 = base64Alphabet[d4];
decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4);
decodedData[encodedIndex++] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf));
decodedData[encodedIndex++] = (byte) (b3 << 6 | b4);
}
return decodedData;
}
/**
* remove WhiteSpace from MIME containing encoded Base64 data.
*
* @param data the byte array of base64 data (with WS)
* @return the new length
*/
private static int removeWhiteSpace(char[] data)
{
if (data == null)
{
return 0;
}
// count characters that's not whitespace
int newSize = 0;
int len = data.length;
for (int i = 0; i < len; i++)
{
if (!isWhiteSpace(data[i]))
{
data[newSize++] = data[i];
}
}
return newSize;
}
}
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
/**
* Md5加密方法
*
* @author cartreasure
*/
public class Md5Utils
{
private static final Logger log = LoggerFactory.getLogger(Md5Utils.class);
private static byte[] md5(String s)
{
MessageDigest algorithm;
try
{
algorithm = MessageDigest.getInstance("MD5");
algorithm.reset();
algorithm.update(s.getBytes("UTF-8"));
byte[] messageDigest = algorithm.digest();
return messageDigest;
}
catch (Exception e)
{
log.error("MD5 Error...", e);
}
return null;
}
private static final String toHex(byte hash[])
{
if (hash == null)
{
return null;
}
StringBuffer buf = new StringBuffer(hash.length * 2);
int i;
for (i = 0; i < hash.length; i++)
{
if ((hash[i] & 0xff) < 0x10)
{
buf.append("0");
}
buf.append(Long.toString(hash[i] & 0xff, 16));
}
return buf.toString();
}
public static String hash(String s)
{
try
{
return new String(toHex(md5(s)).getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8);
}
catch (Exception e)
{
log.error("not supported charset...{}", e);
return s;
}
}
}
通过code获取手机号:
//获取手机号的微信地址
private static final String ACCESS_TOKEN= "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=";
@Autowired
private RedisCache redisCache;
@Transactional(rollbackFor = Exception.class)
@Override
public ReceptionUser wxLogin(ReceptionUser receptionUser) {
//先获取access_token,再获取手机号。
//请求地址分别是"https://api.weixin.qg.com/cgi-bin/token"
//和https://api.weixin.gg.com/wxa/business/getuserphonenumber?access token=$ftoken)
String phoneNumber = null;
try{
//因为access_token的有效期为7200秒,所以获取之后需要存储起来,再判断是否过期,过期了就更新
String key = Constants.ACCESS_TOKEN;
String accessToken = "";
if (redisCache.hasKey(key)){
accessToken = redisCache.getCacheObject(key);
}
if (StrUtil.isBlank(accessToken)){
//如果过期了,就重新获取
accessToken = wxMaService.getAccessToken(true);
redisCache.setCacheObject(key,accessToken,7100, TimeUnit.SECONDS);
}
if (StrUtil.isBlank(accessToken)){
return null;
}
String urlphone = ACCESS_TOKEN+accessToken;
JSONObject jsonObject = new JSONObject();
jsonObject.put("code",receptionUser.getCode());
String reqParam = jsonObject.toJSONString();
String result = HttpUtils.sendPost(urlphone,reqParam);
if (StrUtil.isNotBlank(result)){
JSONObject resultJSONObject = JSON.parseObject(result);
System.out.println("结果:"+resultJSONObject);
Integer errcode = resultJSONObject.getInteger("errcode");
if (errcode != null && errcode.equals(0)){
JSONObject phoneInfo = resultJSONObject.getJSONObject("phone_info");
if (phoneInfo != null && phoneInfo.size() > 0){
获取手机号
phoneNumber = phoneInfo.getString("phoneNumber");
}
}else{
return null;
}
}
}catch (Exception e){
e.printStackTrace();
return null;
}
if (StrUtil.isBlank(phoneNumber)){
return null;
}
return null;
}
#Constants类定义常量:
/**
* 微信登录用户access_token
*/
public static final String ACCESS_TOKEN = "access_token:";
注释:
如果获取access_token出现如下错误,请使用稳定版:
{"errcode":40001,"errmsg":"invalid credential, access_token is invalid or not latest, could get access_token by getStableAccessToken, more details at https://mmbizurl.cn/s/JtxxFh33r rid: 65b3294e-6993fa45-745b3def"}
结果:{"errcode":40001,"errmsg":"invalid credential, access_token is invalid or not latest, could get access_token by getStableAccessToken, more details at https://mmbizurl.cn/s/JtxxFh33r rid: 65b3294e-6993fa45-745b3def"}
POST https://api.weixin.qq.com/cgi-bin/stable_token