前端代码
前端使用的uniapp,我这边只写了个示例
<template>
<view class="content">
<image class="logo" src="/static/logo.png"></image>
<view class="text-area">
<text class="title">{{ title }}</text>
<button type="default" @tap="appLoginWx" >微信登录</button>
<button open-type="getPhoneNumber" @getphonenumber="getPhoneNumber">获取手机号</button>
</view>
</view>
</template>
<script>
export default {
data() {
return {
title: "Hello",
};
},
onLoad() {
// todo 微信小程序登录授权
uni.login({
provider: "weixin",
success: (result) => {
console.log("🚀🚀🚀 ~ onLoad ~ result:", result);
},
fail: (error) => {},
});
},
methods: {
appLoginWx(){
// 获取用户信息
// 注意 getUserProfile 不支持在事件中使用异步操作
// 否则会触发错误:{errMsg: "getUserProfile:fail can only be invoked by user TAP gesture."}
uni.getUserInfo({
lang: 'zh_CN',
desc:'获取用户信息',
success: userInfo=> {
console.log(userInfo,'userInfo');
uni.login({
provider: 'weixin',
success: loginInfo=> {
console.log(loginInfo,'loginInfo');
}
});
},
fail:err=>{
console.log(err,'err')
}
});
},
//
getPhoneNumber(e) {
console.log('phoneCode:'+e.detail.code)
},
},
};
</script>
<style>
.content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.logo {
height: 200rpx;
width: 200rpx;
margin-top: 200rpx;
margin-left: auto;
margin-right: auto;
margin-bottom: 50rpx;
}
.text-area {
display: flex;
justify-content: center;
}
.title {
font-size: 36rpx;
color: #8f8f94;
}
</style>
后端代码以及配置
微信相关配置
wx:
appId: wx199139f6xxxxx
appSecret: 7b9ac2dcbbfa930xxxx
微信相关service
相关的Result,RedisUtil,YlCheckUser使用自己的
package com.applets.manager.core.service;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.applets.manager.core.model.Result;
import com.applets.manager.core.model.dto.WxPhoneInfoRes;
import com.applets.manager.core.model.dto.WxUserInfo;
import com.applets.manager.core.model.entity.YlCheckUser;
import com.applets.manager.core.util.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
* 微信能力
* @author zr 2024/4/16
*/
@Slf4j
@Service
public class WxService {
@Value("${wx.appId}")
private String appId;
@Value("${wx.appSecret}")
private String appSecret;
private final String ACCESS_TOKEN_KEY = "applets:accessToken";//redis使用
private final String LOGIN = "https://api.weixin.qq.com/sns/jscode2session";
private final String GET_ACCESS_TOKEN = "https://api.weixin.qq.com/cgi-bin/token";
private final String GET_USER_PHONE_NUMBER = "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=%s";
/**
* 微信用户登录
* @param code
* @return
*/
public Result<YlCheckUser> wxLogin(String code) {
HashMap<String, Object> parameter = new HashMap<>();
parameter.put("appid", appId);
parameter.put("secret", appSecret);
parameter.put("js_code", code);
parameter.put("grant_type", "authorization_code");
HttpResponse response = httpGet(LOGIN, null, parameter);
String res = response.body();
JSONObject jsonObject = JSON.parseObject(res);
Integer errcode = jsonObject.getInteger("errcode");
if (ObjectUtils.isEmpty(errcode)) {
String sessionKey = jsonObject.getString("session_key");
String openid = jsonObject.getString("openid");
YlCheckUser checkUser = new YlCheckUser();
checkUser.setSessionKey(sessionKey);
checkUser.setOpenId(openid);
return Result.success(checkUser);
} else {
return Result.failure("微信错误:" + jsonObject.getString("errmsg"));
}
}
/**
* 获取用户手机号信息
* @param code
* @return
*/
public Result getUserPhoneNumber(String code) {
HashMap<String, Object> parameter = new HashMap<>();
parameter.put("code", code);
String url = String.format(GET_USER_PHONE_NUMBER, getAccessToken().getResult());
String res = httpPost(url, JSON.toJSONString(parameter));
JSONObject jsonObject = JSON.parseObject(res);
Integer errcode = jsonObject.getInteger("errcode");
if (errcode ==0) {
WxPhoneInfoRes wxPhoneInfoRes = jsonObject.getObject("phone_info", WxPhoneInfoRes.class);
YlCheckUser checkUser = new YlCheckUser();
checkUser.setPhone(wxPhoneInfoRes.getPhoneNumber());
return Result.success(checkUser);
} else {
RedisUtil.KeyOps.delete(ACCESS_TOKEN_KEY);
getAccessToken();
return Result.failure("微信错误:" + jsonObject.getString("errmsg"));
}
}
/**
* 更新或刷新accessToken
* @return
*/
public Result<String> getAccessToken() {
String accessToken = RedisUtil.StringOps.get(ACCESS_TOKEN_KEY);
if (StringUtils.isNotEmpty(accessToken)){
return Result.success(accessToken);
}
HashMap<String, Object> parameter = new HashMap<>();
parameter.put("appid", appId);
parameter.put("secret", appSecret);
parameter.put("grant_type", "client_credential");
HttpResponse response = httpGet(GET_ACCESS_TOKEN, null, parameter);
String res = response.body();
JSONObject jsonObject = JSON.parseObject(res);
Integer errcode = jsonObject.getInteger("errcode");
if (ObjectUtils.isEmpty(errcode)) {
accessToken = jsonObject.getString("access_token");
Integer expires = jsonObject.getInteger("expires_in");
//微信默认过期时间7200s,即2h,提前200s
RedisUtil.StringOps.setEx(ACCESS_TOKEN_KEY, accessToken, expires-200, TimeUnit.SECONDS);
return Result.success(accessToken);
} else {
return Result.failure("微信错误:" + jsonObject.getString("errmsg"));
}
}
public Result<YlCheckUser> getUserInfo(String encryptedData, String sessionKey, String iv){
if (sessionKey.length() != 24) {
return Result.failure("sessionKey长度错误");
}
byte[] aesKey = java.util.Base64.getDecoder().decode(sessionKey);
if (iv.length() != 24) {
return Result.failure("iv长度错误");
}
byte[] aesIV = java.util.Base64.getDecoder().decode(iv);
byte[] aesCipher = java.util.Base64.getDecoder().decode(encryptedData);
try {
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES");
IvParameterSpec ivSpec = new IvParameterSpec(aesIV);
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
byte[] resultBytes = cipher.doFinal(aesCipher);
String result = new String(resultBytes, "UTF-8");
JSONObject dataObj = JSONObject.parseObject(result);
if (!appId.equals(dataObj.getJSONObject("watermark").getString("appid"))) {
return null;
}
WxUserInfo wxUserInfo = dataObj.toJavaObject(WxUserInfo.class);
YlCheckUser checkUser = new YlCheckUser();
checkUser.setUnionId(wxUserInfo.getUnionId());
checkUser.setOpenId(wxUserInfo.getOpenId());
// return ylCheckUserService.saveUnionInfo(checkUser);
return Result.success(checkUser);
} catch (Exception e) {
e.printStackTrace();
return Result.failure("解密失败:{}"+e.getMessage());
}
}
private HttpResponse httpGet(String url, Map<String, String> headers, Map<String, Object> parameter) {
HttpRequest httpRequest = HttpUtil.createGet(url).form(parameter).addHeaders(headers);
HttpResponse response = httpRequest.execute();
log.info("Url: "+httpRequest.getUrl());
log.info("head: "+JSON.toJSONString(httpRequest.headers()));
log.info("parame: "+JSON.toJSONString(httpRequest.form()));
log.info("res: "+response.body());
return response;
}
private String httpPost(String url,String body) {
String res = HttpUtil.post(url, body);
log.info("Url: "+url);
log.info("body: "+body);
log.info("res: "+res);
return res;
}
}
微信相关controller
package com.applets.manager.api.controller.estimate;
import com.applets.manager.core.model.Result;
import com.applets.manager.core.model.entity.YlCheckUser;
import com.applets.manager.core.service.WxService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* @author zr 2024/4/15
*/
@Api(tags = "微信")
@RestController("/wx")
@Slf4j
public class WxController {
@Autowired
private WxService wxService;
@GetMapping("/wxLogin")
@ApiOperation(value = "微信登录")
public Result<YlCheckUser> wxLogin(@RequestParam("code") String code) {
try {
return wxService.wxLogin(code);
} catch (Exception e) {
log.info(e.getMessage());
return Result.failure(e.getMessage());
}
}
@GetMapping("/getUserPhoneNumber")
@ApiOperation(value = "获取用户电话信息")
public Result<YlCheckUser> getUserPhoneNumber(@RequestParam("code") String code) {
try {
return wxService.getUserPhoneNumber(code);
} catch (Exception e) {
log.info(e.getMessage());
return Result.failure(e.getMessage());
}
}
@GetMapping("/getUserInfo")
@ApiOperation(value = "获取用户信息")
public Result<YlCheckUser> getUserInfo(@RequestParam("encryptedData") String encryptedData,
@RequestParam("iv") String iv,
@RequestParam("sessionKey") String sessionKey) {
try {
return wxService.getUserInfo(encryptedData,sessionKey,iv);
} catch (Exception e) {
log.info(e.getMessage());
return Result.failure(e.getMessage());
}
}
}
微信返回用户信息对象
package com.applets.manager.core.model.dto;
import lombok.Data;
/**
* @author zr 2024/4/23
*/
@Data
public class WxUserInfo {
private String openId;
private String nickName;
private int gender;
private String city;
private String province;
private String country;
private String avatarUrl;
private String unionId;
private Watermark watermark;
@Data
public static class Watermark {
private String appid;
private long timestamp;
}
}
测试
微信小程序登录
- 每次进入小程序后会自动小程序登录获取小程序授权码(开发者工具中即重新编译)
uni.login后会拿到小程序授权码每个码只能用一次
- 拿到授权码调用/wx/wxLogin接口,可以拿到openId和sessionKey
微信获取手机号
- 点击获取手机号,同意后获取到phoneCode
- 拿到授权码调用/wx/getUserPhoneNumber接口,可以拿到phone
获取用户信息和unionId
- 点击微信登录获取到用户信息(这里微信登录按钮文字写错了,应该是获取用户信息按钮)
- 拿到iv和encryptedData和3.1步骤得到的sessionKey调用/wx/getUserInfo接口
- 用户信息解密后可以拿到以下数据,可以自行封装