- 【微信开发】SpringBoot 集成微信小程序授权登录
- 1、SprinBoot 后端
- (1)准备工作
- (2)相关配置类
- (3)相关实体类
- (4)处理后端逻辑
- 2、Uniapp 前端
- (1)授权登录
- (2)效果样式
- 微信公众号
我这里采用了第三方的依赖,目前是最火的微信开发工具吧,WxJava
1、SprinBoot 后端 (1)准备工作引入相关依赖
com.github.binarywang weixin-java-pay 4.1.0
配置application.yml
# ----------------------系统配置
# 业务配置
pay-platform:
# 微信
wx:
pay:
appId:
secret:
mchId:
mchKey:
keyPath:
notifyUrl:
(2)相关配置类
属性类
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
@Data
@ConfigurationProperties(prefix = "pay-platform.wx.pay")
public class WxPayProperties {
private String appId;
private String secret;
private String mchId;
private String mchKey;
private String subAppId;
private String subMchId;
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;
@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;
@Data
@Accessors(chain = true)
public class AccessToken {
private String accessToken;
private Integer expiresIn;
private Integer errCode;
private String errMsg;
}
import lombok.Data;
import lombok.experimental.Accessors;
@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;
@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;
@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;
@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 list = userService.list(new LambdaQueryWrapper().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 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;
public interface WeiXinService {
Code2Session code2Session(String code);
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;
@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的官网工具类
申请获取以下权限
获得你的公开信息(昵称,头像、地区等)
获得你微信绑定的手机号
(2)效果样式
微信公众号



