文章目錄
- 【微信開發】SpringBoot 集成微信小程式授權登錄
- 1、SprinBoot 后端
- (1)準備作業
- (2)相關配置類
- (3)相關物體類
- (4)處理后端邏輯
- 2、Uniapp 前端
- (1)授權登錄
- (2)效果樣式
- 微信公眾號
【微信開發】SpringBoot 集成微信小程式授權登錄
我這里采用了第三方的依賴,目前是最火的微信開發工具吧,WxJava
1、SprinBoot 后端
(1)準備作業
引入相關依賴
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-pay</artifactId>
<version>4.1.0</version>
</dependency>
配置application.yml
# ----------------------系統配置
# 業務配置
pay-platform:
# 微信
wx:
pay:
appId:
secret:
mchId:
mchKey:
keyPath:
notifyUrl:
(2)相關配置類
屬性類
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* wxpay pay properties.
*
* @author Binary Wang
*/
@Data
@ConfigurationProperties(prefix = "pay-platform.wx.pay")
public class WxPayProperties {
/**
* 設定微信公眾號或者小程式等的appid
*/
private String appId;
private String secret;
/**
* 微信支付商戶號
*/
private String mchId;
/**
* 微信支付商戶密鑰
*/
private String mchKey;
/**
* 服務商模式下的子商戶公眾賬號ID,普通模式請不要配置,請在組態檔中將對應項洗掉
*/
private String subAppId;
/**
* 服務商模式下的子商戶號,普通模式請不要配置,最好是請在組態檔中將對應項洗掉
*/
private String subMchId;
/**
* apiclient_cert.p12檔案的絕對路徑,或者如果放在專案中,請以classpath:開頭指定
*/
private String keyPath;
/**
* 支付回呼地址
*/
private String notifyUrl;
}
屬性配置類
import com.github.binarywang.wxpay.config.WxPayConfig;
import com.github.binarywang.wxpay.service.WxPayService;
import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
import lombok.AllArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author Binary Wang
*/
@Configuration
@ConditionalOnClass(WxPayService.class)
@EnableConfigurationProperties(WxPayProperties.class)
@AllArgsConstructor
public class WxPayConfiguration {
private WxPayProperties properties;
@Bean
@ConditionalOnMissingBean
public WxPayService wxService() {
WxPayConfig payConfig = new WxPayConfig();
payConfig.setAppId(StringUtils.trimToNull(this.properties.getAppId()));
payConfig.setMchId(StringUtils.trimToNull(this.properties.getMchId()));
payConfig.setMchKey(StringUtils.trimToNull(this.properties.getMchKey()));
payConfig.setSubAppId(StringUtils.trimToNull(this.properties.getSubAppId()));
payConfig.setSubMchId(StringUtils.trimToNull(this.properties.getSubMchId()));
payConfig.setKeyPath(StringUtils.trimToNull(this.properties.getKeyPath()));
// 可以指定是否使用沙箱環境
payConfig.setUseSandboxEnv(false);
WxPayService wxPayService = new WxPayServiceImpl();
wxPayService.setConfig(payConfig);
return wxPayService;
}
}
(3)相關物體類
相關物體類,都可以使用json的map格式來處理,我這里是個人習慣
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 介面呼叫憑證
* https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/access-token/auth.getAccessToken.html
*
* @author Tellsea
* @date 2021/05/20
*/
@Data
@Accessors(chain = true)
public class AccessToken {
private String accessToken;
/**
* 憑證有效時間,單位:秒,目前是7200秒之內的值,
*/
private Integer expiresIn;
private Integer errCode;
private String errMsg;
}
import lombok.Data;
import lombok.experimental.Accessors;
/**
* code換取openId
* https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/login/auth.code2Session.html
*
* @author Tellsea
* @date 2021/05/20
*/
@Data
@Accessors(chain = true)
public class Code2Session {
private String openId;
private String sessionKey;
private String unionId;
private Integer errCode;
private String errMsg;
}
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 微信登錄
*
* @author Tellsea
* @date 2021/05/19
*/
@Data
@Accessors(chain = true)
public class WeiXinLogin {
private String code;
private String encryptedData;
private String iv;
private String nickName;
private String avatarUrl;
private Integer gender;
}
lombok.Data;
import lombok.experimental.Accessors;
/**
* 微信 token
*
* @author Tellsea
* @date 2021/05/20
*/
@Data
@Accessors(chain = true)
public class WeiXinToken {
public static String token;
}
(4)處理后端邏輯
控制層
package com.ruoyi.business.appuser.controller;
import cn.hutool.core.lang.Validator;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ruoyi.business.appuser.service.WeiXinService;
import com.ruoyi.business.appuser.vo.wx.Code2Session;
import com.ruoyi.business.appuser.vo.wx.OrderInfo;
import com.ruoyi.business.appuser.vo.wx.WeiXinLogin;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.framework.web.service.SysLoginService;
import com.ruoyi.framework.web.service.SysPermissionService;
import com.ruoyi.framework.web.service.TokenService;
import com.ruoyi.system.service.ISysUserService;
import com.zhhy.tool.utils.IntegerUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.spec.AlgorithmParameterSpec;
import java.util.List;
import java.util.Set;
/**
* @author Tellsea
* @date 2021/11/09
*/
@Slf4j
@Api(value = "微信API", tags = {"微信API"})
@RestController
@RequestMapping("/au/weiXin")
public class AuWeiXinController {
@Autowired
private ISysUserService userService;
@Autowired
private SysLoginService loginService;
@Autowired
private WeiXinService weiXinService;
@Autowired
private SysPermissionService permissionService;
@Autowired
private TokenService tokenService;
@ApiOperation("微信用戶登錄")
@PostMapping("login")
public AjaxResult login(@RequestBody WeiXinLogin dto) {
Code2Session code2Session = weiXinService.code2Session(dto.getCode());
if (StringUtils.isNotEmpty(code2Session.getOpenId())) {
// 解析電話號碼
String phoneNumber;
byte[] byEncrypdata = Base64.decodeBase64(dto.getEncryptedData());
byte[] byIvdata = Base64.decodeBase64(dto.getIv());
byte[] bySessionkey = Base64.decodeBase64(code2Session.getSessionKey());
AlgorithmParameterSpec ivSpec = new IvParameterSpec(byIvdata);
try {
SecretKeySpec keySpec = new SecretKeySpec(bySessionkey, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
String phoneResult = new String(cipher.doFinal(byEncrypdata), StandardCharsets.UTF_8);
JSONObject phoneObject = JSONObject.parseObject(phoneResult);
phoneNumber = phoneObject.getString("phoneNumber");
} catch (Exception e) {
e.printStackTrace();
return AjaxResult.error("手機號碼解密失敗");
}
// 根據openId查詢是否存在這個用戶
List<SysUser> list = userService.list(new LambdaQueryWrapper<SysUser>().eq(SysUser::getOpenId, code2Session.getOpenId())
.or().eq(SysUser::getUserName, phoneNumber).or().eq(SysUser::getPhonenumber, phoneNumber));
AjaxResult ajax = AjaxResult.success();
if (CollectionUtils.isEmpty(list)) {
// 添加新用戶
String defaultPassword = "111111";
SysUser user = new SysUser()
.setOpenId(code2Session.getOpenId())
.setUserName(phoneNumber)
.setNickName(dto.getNickName())
.setDeptId(0L)
.setPassword(defaultPassword)
.setPhonenumber(phoneNumber)
.setAvatar(dto.getAvatarUrl());
if (IntegerUtils.eq(dto.getGender(), 0)) {
user.setSex("2");
} else if (IntegerUtils.eq(dto.getGender(), 1)) {
user.setSex("0");
} else if (IntegerUtils.eq(dto.getGender(), 2)) {
user.setSex("1");
}
if (UserConstants.NOT_UNIQUE.equals(userService.checkUserNameUnique(user.getUserName()))) {
return AjaxResult.error("手機號已被注冊");
} else if (Validator.isNotEmpty(user.getPhonenumber())
&& UserConstants.NOT_UNIQUE.equals(userService.checkPhoneUnique(user))) {
return AjaxResult.error("手機號已被使用");
}
user.setCreateBy(SecurityUtils.getUsername());
user.setPassword(SecurityUtils.encryptPassword(user.getPassword()));
// 默認給角色用戶
user.setRoleIds(new Long[]{1L});
userService.insertUser(user);
String token = loginService.login(user.getUserName(), defaultPassword);
ajax.put(Constants.TOKEN, token);
return ajax;
} else if (list.size() == 1) {
// 更新用戶資訊:這里查詢出的一個資訊,可能是openId、userName、phonenumber三個欄位其中某個查出來的
SysUser sysUser = list.get(0);
sysUser.setNickName(dto.getNickName());
sysUser.setAvatar(dto.getAvatarUrl());
if (IntegerUtils.eq(dto.getGender(), 0)) {
sysUser.setSex("2");
} else if (IntegerUtils.eq(dto.getGender(), 1)) {
sysUser.setSex("0");
} else if (IntegerUtils.eq(dto.getGender(), 2)) {
sysUser.setSex("1");
}
if (StringUtils.isEmpty(sysUser.getOpenId())) {
sysUser.setOpenId(code2Session.getOpenId());
}
userService.updateById(sysUser);
SysUser user = userService.selectUserByUserName(sysUser.getUserName());
LoginUser loginUser = new LoginUser(user, permissionService.getMenuPermission(user));
String token = tokenService.createToken(loginUser);
ajax.put(Constants.TOKEN, token);
return ajax;
} else {
return AjaxResult.error("用戶資訊例外,存在多個openId或電話號碼");
}
} else {
return AjaxResult.error(code2Session.getErrMsg());
}
}
@ApiOperation("獲取用戶資訊")
@GetMapping("getInfo")
public AjaxResult getInfo() {
LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
SysUser user = loginUser.getUser();
AjaxResult ajax = AjaxResult.success();
Set<String> permission = permissionService.getRolePermission(user);
ajax.put("user", user);
ajax.put("roles", permission);
ajax.put("permissions", permissionService.getMenuPermission(user));
return ajax;
}
}
業務處理層
import com.ruoyi.business.appuser.vo.wx.AccessToken;
import com.ruoyi.business.appuser.vo.wx.Code2Session;
import com.ruoyi.business.appuser.vo.wx.OrderInfo;
import com.ruoyi.common.core.domain.AjaxResult;
/**
* @author Tellsea
* @date 2021/05/20
*/
public interface WeiXinService {
/**
* code換取openId
* https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/login/auth.code2Session.html
*
* @param code
* @return
*/
Code2Session code2Session(String code);
/**
* 介面呼叫憑據
* https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/access-token/auth.getAccessToken.html
*
* @return
*/
AccessToken getAccessToken();
}
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.github.binarywang.wxpay.bean.notify.WxPayNotifyResponse;
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
import com.github.binarywang.wxpay.bean.request.BaseWxPayRequest;
import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderRequest;
import com.github.binarywang.wxpay.bean.result.BaseWxPayResult;
import com.github.binarywang.wxpay.constant.WxPayConstants;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.WxPayService;
import com.ruoyi.business.appuser.config.WxPayProperties;
import com.ruoyi.business.appuser.service.WeiXinService;
import com.ruoyi.business.appuser.vo.wx.AccessToken;
import com.ruoyi.business.appuser.vo.wx.Code2Session;
import com.ruoyi.business.appuser.vo.wx.OrderInfo;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.utils.ServletUtils;
import com.zhhy.tool.utils.IntegerUtils;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.UUID;
/**
* @author Tellsea
* @date 2021/05/20
*/
@Slf4j
@Service
@AllArgsConstructor
public class WeiXinServiceImpl implements WeiXinService {
private WxPayService wxPayService;
private WxPayProperties wxPayProperties;
@Override
public Code2Session code2Session(String code) {
String url = "https://api.weixin.qq.com/sns/jscode2session?" +
"appid=" + wxPayProperties.getAppId() +
"&secret=" + wxPayProperties.getSecret() +
"&js_code=" + code +
"&grant_type=authorization_code";
String result = HttpUtil.get(url);
JSONObject jsonObject = JSONObject.parseObject(result);
Code2Session code2Session = new Code2Session().setOpenId(jsonObject.getString("openid"))
.setSessionKey(jsonObject.getString("session_key"))
.setUnionId(jsonObject.getString("unionid"))
.setErrCode(jsonObject.getInteger("errcode"))
.setErrMsg(jsonObject.getString("errmsg"));
if (StringUtils.isEmpty(code2Session.getOpenId())) {
code2Session.setErrMsg("OpenId為空");
} else if (IntegerUtils.eq(code2Session.getErrCode(), -1)) {
code2Session.setErrMsg("系統繁忙,此時請開發者稍候再試");
} else if (IntegerUtils.eq(code2Session.getErrCode(), 40029)) {
code2Session.setErrMsg("code 無效");
} else if (IntegerUtils.eq(code2Session.getErrCode(), 45011)) {
code2Session.setErrMsg("頻率限制,每個用戶每分鐘100次");
} else {
code2Session.setErrMsg("其他錯誤");
}
return code2Session;
}
@Override
public AccessToken getAccessToken() {
String url = "https://api.weixin.qq.com/cgi-bin/token?" +
"grant_type=client_credential" +
"&appid=" + wxPayProperties.getAppId() +
"&secret=" + wxPayProperties.getSecret();
String result = HttpUtil.get(url);
JSONObject jsonObject = JSONObject.parseObject(result);
AccessToken accessToken = new AccessToken().setAccessToken(jsonObject.getString("access_token"))
.setExpiresIn(jsonObject.getInteger("expires_in"))
.setErrCode(jsonObject.getInteger("errcode"))
.setErrMsg(jsonObject.getString("errmsg"));
if (StringUtils.isEmpty(accessToken.getAccessToken())) {
} else if (IntegerUtils.eq(accessToken.getErrCode(), -1)) {
accessToken.setErrMsg("系統繁忙,此時請開發者稍候再試");
} else if (IntegerUtils.eq(accessToken.getErrCode(), 40001)) {
accessToken.setErrMsg("AppSecret 錯誤或者 AppSecret 不屬于這個小程式,請開發者確認 AppSecret 的正確性");
} else if (IntegerUtils.eq(accessToken.getErrCode(), 40002)) {
accessToken.setErrMsg("請確保 grant_type 欄位值為 client_credential");
} else if (IntegerUtils.eq(accessToken.getErrCode(), 40013)) {
accessToken.setErrMsg("不合法的 AppID,請開發者檢查 AppID 的正確性,避免例外字符,注意大小寫");
} else {
accessToken.setErrMsg("其他錯誤");
}
return accessToken;
}
}
2、Uniapp 前端
(1)授權登錄
一個頁面直接搞定,授權登錄,換取token,通過token查詢用戶資訊,請求使用的是uview的官網工具類
<template>
<view>
<view class="header">
<image src="/static/login/wx_login.png" mode=""></image>
</view>
<view class='content'>
<view>申請獲取以下權限</view>
<text>獲得你的公開資訊(昵稱,頭像、地區等)</text>
<text>獲得你微信系結的手機號</text>
</view>
<button class="bottom" type="primary" open-type="getPhoneNumber" @getphonenumber="getPhoneNumber">
微授權登錄
</button>
<u-toast ref="uToast" />
</view>
</template>
<script>
let that;
export default {
data() {
return {
form: {
// 換取openId
code: '',
// 解密手機號
encryptedData: '',
iv: '',
// 更新用戶資訊
nickName: '',
avatarUrl: '',
// 性別 0:未知、1:男、2:女
gender: '',
}
};
},
onLoad() {
that = this;
this.getCode();
},
methods: {
getCode() {
wx.login({
success(res) {
if (res.code) {
that.form.code = res.code;
} else {
that.$msg('登錄失敗:' + res.errMsg);
}
},
fail(err) {
that.$msg('code獲取失敗');
},
});
},
getPhoneNumber: function(e) {
that.getCode();
uni.showLoading({
title: '登錄中...'
});
if (e.detail.errMsg != 'getPhoneNumber:ok') {
that.$refs.uToast.show({type: 'error', title: '未授權手機號'});
uni.hideLoading();
return false;
}
that.form.encryptedData = e.detail.encryptedData;
that.form.iv = e.detail.iv;
// 檢查登錄態是否過期
wx.checkSession({
success() {
// 用戶資訊
wx.getUserInfo({
success: function(res) {
let userInfo = res.userInfo;
that.form.nickName = userInfo.nickName;
that.form.avatarUrl = userInfo.avatarUrl;
that.form.gender = userInfo.gender;
that.$u.post('/au/weiXin/login', that.form).then(res => {
uni.setStorageSync(that.$config.cachePrefix + 'token', res.token);
that.$u.get('/au/weiXin/getInfo').then(result => {
uni.setStorageSync(that.$config.cachePrefix + 'user', result.user);
that.$refs.uToast.show({
type: 'success',
title: '登錄成功',
url: '/pages/index/index',
isTab: true
});
});
});
}
});
},
fail(err) {
wx.login({
success: res => {
that.form.code = res.code
}
});
}
})
},
}
}
</script>
<style scoped>
.header {
margin: 90rpx 0 90rpx 50rpx;
border-bottom: 1px solid #ccc;
text-align: center;
width: 650rpx;
height: 300rpx;
line-height: 450rpx;
}
.header image {
width: 200rpx;
height: 200rpx;
}
.content {
margin-left: 50rpx;
margin-bottom: 90rpx;
}
.content text {
display: block;
color: #9d9d9d;
margin-top: 40rpx;
}
.bottom {
border-radius: 80rpx;
margin: 70rpx 50rpx;
font-size: 35rpx;
}
</style>
(2)效果樣式

微信公眾號

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/356103.html
標籤:其他
