本文正在参加「金石方案」
1 前言
上一年6月份写了《uni-app微信小程序授权登录实操演战》这篇文章,从前端的角度完成了微信小程序授权登陆。
本次结合开源若依结构RuoYi与uni-app集成微信小程序授权登陆,完成前后端数据传输、处理的过程。
注:《uni-app微信小程序授权登录实操演战》这篇文章撰写时,微信登陆接口供给了取得用户昵称和头像,目前该接口已中止供给昵称和头像(小程序用户头像昵称获取规矩调整公告) ,故本文中若依用户创立时的昵称和用户名随机生成,可在个人中心事务开发中微信开发渠道供给(头像昵称填写)能力来完成。
1.1 环境预备
-
**下载源码:**若依结构RuoYi-Vue版别 :gitee.com/y_project/R…
-
**开发东西:**IDEA、HbuilderX、Navicat、RDM
-
**中间件:**Redis、Mysql8.0
1.2 登陆流程
2 前端代码-UniApp
2.1 创立uniapp项目,并开启OAuth登陆鉴权微信登陆
双击manifest.json进行设置
2.1 添加微信登陆授权按钮
<button type="default" @click="wxLogin">微信登陆</button>
2.2 创立wxLogin()办法
wxLogin() {
// 获取服务供货商
uni.getProvider({
service: 'oauth',
success: (res) => {
if (~res.provider.indexOf("weixin")) {
// uni微信登陆
uni.login({
provider: 'weixin',
success: (loginRes) => {
// 获取用户信息
uni.getUserInfo({
success: (resInfo) => {
// 微信登录 code:jscode encryptedIv:偏移量 encryptedData: 加密数据
uni.request({
url: "http://localhost:8080/wxLogin", //后端接口
method: 'POST',
data: {
code: loginRes.code,
encryptedIv: resInfo.iv,
encryptedData: resInfo
.encryptedData
},
success: (wxLoginRes) => {
console.log(
"wxLoginRes: ",
wxLoginRes);
}
})
}
})
}
})
}
}
})
}
3 后端代码-若依
3.1 application.yml配置文件中,添加微信小程序appId和appSecret
ruoyi-admin/src/main/resources/application.yml
wx-app:
appId: wxae5813756948397b
appSecret: 662faf6ab9e7d635bcda30478f10b333
3.2 在数据库sys_user表中,添加open_id和union_id字段
OpenID: 普通用户的标识,对当时开发者帐号仅有。一个openid对应一个公众号或小程序; UnionID: 用户一致标识,针对一个微信敞开渠道帐号下的运用,同一用户的unionid是仅有的。
本例中,我们只用到open_id。
依据事务需要,如多个运用可接入一致标识UnionID。
3.3 在SysUser类中新增两个特点
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java
/** unionId */
private String unionId;
/** openId */
private String openId;
public String getUnionId() {
return unionId;
}
public void setUnionId(String unionId) {
this.unionId = unionId;
}
public String getOpenId() {
return openId;
}
public void setOpenId(String openId) {
this.openId = openId;
}
toString()办法中参加open_id
@Override
public String toString() {
return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
.append("userId", getUserId())
.append("deptId", getDeptId())
.append("userName", getUserName())
.append("nickName", getNickName())
.append("email", getEmail())
.append("phonenumber", getPhonenumber())
.append("sex", getSex())
.append("avatar", getAvatar())
.append("password", getPassword())
.append("status", getStatus())
.append("delFlag", getDelFlag())
.append("loginIp", getLoginIp())
.append("loginDate", getLoginDate())
.append("createBy", getCreateBy())
.append("createTime", getCreateTime())
.append("updateBy", getUpdateBy())
.append("updateTime", getUpdateTime())
.append("remark", getRemark())
.append("dept", getDept())
.append("openId", getOpenId())
.toString();
}
3.4 SysUserMapper新增selectWxUserByOpenId
ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserMapper.java
/**
* 依据openId查询用户信息
* @param openId
* @return
*/
public SysUser selectWxUserByOpenId(String openId);
3.5 SysUserMapper.xml改造
ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml
resultMap添加openId的字段映射
<resultMap type="SysUser" id="SysUserResult">
<id property="userId" column="user_id" />
<result property="deptId" column="dept_id" />
<result property="userName" column="user_name" />
<result property="nickName" column="nick_name" />
<result property="email" column="email" />
<result property="phonenumber" column="phonenumber" />
<result property="sex" column="sex" />
<result property="avatar" column="avatar" />
<result property="password" column="password" />
<result property="status" column="status" />
<result property="delFlag" column="del_flag" />
<result property="loginIp" column="login_ip" />
<result property="loginDate" column="login_date" />
<result property="createBy" column="create_by" />
<result property="createTime" column="create_time" />
<result property="updateBy" column="update_by" />
<result property="updateTime" column="update_time" />
<result property="openId" column="open_id" />
<result property="remark" column="remark" />
<association property="dept" column="dept_id" javaType="SysDept" resultMap="deptResult" />
<collection property="roles" javaType="java.util.List" resultMap="RoleResult" />
</resultMap>
selectUserList添加对u.open_id字段的查询。(可选,供PC端用户列表中查询用户open_id)
<select id="selectUserList" parameterType="SysUser" resultMap="SysUserResult">
select u.user_id, u.dept_id, u.nick_name, u.user_name, u.email, u.avatar, u.phonenumber, u.sex, u.status, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time,u.open_id, u.remark, d.dept_name, d.leader from sys_user u
left join sys_dept d on u.dept_id = d.dept_id
where u.del_flag = '0'
<if test="userId != null and userId != 0">
AND u.user_id = #{userId}
</if>
<if test="userName != null and userName != ''">
AND u.user_name like concat('%', #{userName}, '%')
</if>
<if test="status != null and status != ''">
AND u.status = #{status}
</if>
<if test="phonenumber != null and phonenumber != ''">
AND u.phonenumber like concat('%', #{phonenumber}, '%')
</if>
<if test="params.beginTime != null and params.beginTime != ''"><!-- 开端时刻检索 -->
AND date_format(u.create_time,'%y%m%d') >= date_format(#{params.beginTime},'%y%m%d')
</if>
<if test="params.endTime != null and params.endTime != ''"><!-- 结束时刻检索 -->
AND date_format(u.create_time,'%y%m%d') <= date_format(#{params.endTime},'%y%m%d')
</if>
<if test="deptId != null and deptId != 0">
AND (u.dept_id = #{deptId} OR u.dept_id IN ( SELECT t.dept_id FROM sys_dept t WHERE find_in_set(#{deptId}, ancestors) ))
</if>
<!-- 数据规模过滤 -->
${params.dataScope}
</select>
新增selectWxUserByOpenId
<select id="selectWxUserByOpenId" parameterType="String" resultMap="SysUserResult">
<include refid="selectUserVo" />
where u.open_id = #{openId} and u.del_flag = '0'
</select>
修改insertUser,添加open_id相关句子。
<insert id="insertUser" parameterType="SysUser" useGeneratedKeys="true" keyProperty="userId">
insert into sys_user(
<if test="userId != null and userId != 0">user_id,</if>
<if test="deptId != null and deptId != 0">dept_id,</if>
<if test="userName != null and userName != ''">user_name,</if>
<if test="nickName != null and nickName != ''">nick_name,</if>
<if test="email != null and email != ''">email,</if>
<if test="avatar != null and avatar != ''">avatar,</if>
<if test="phonenumber != null and phonenumber != ''">phonenumber,</if>
<if test="sex != null and sex != ''">sex,</if>
<if test="password != null and password != ''">password,</if>
<if test="status != null and status != ''">status,</if>
<if test="createBy != null and createBy != ''">create_by,</if>
<if test="openId != null and openId != ''">open_id,</if>
<if test="remark != null and remark != ''">remark,</if>
create_time
)values(
<if test="userId != null and userId != ''">#{userId},</if>
<if test="deptId != null and deptId != ''">#{deptId},</if>
<if test="userName != null and userName != ''">#{userName},</if>
<if test="nickName != null and nickName != ''">#{nickName},</if>
<if test="email != null and email != ''">#{email},</if>
<if test="avatar != null and avatar != ''">#{avatar},</if>
<if test="phonenumber != null and phonenumber != ''">#{phonenumber},</if>
<if test="sex != null and sex != ''">#{sex},</if>
<if test="password != null and password != ''">#{password},</if>
<if test="status != null and status != ''">#{status},</if>
<if test="createBy != null and createBy != ''">#{createBy},</if>
<if test="openId != null and openId != ''">#{openId},</if>
<if test="remark != null and remark != ''">#{remark},</if>
sysdate()
)
</insert>
3.5 ApplicationConfig添加 HTTP 恳求东西配置
ruoyi-framework/src/main/java/com/ruoyi/framework/config/ApplicationConfig.java
/**
* 长途调用 HTTP 恳求东西
*/
@Bean
public RestTemplate RestTemplate(){
return new RestTemplate();
}
3.5 新增WxLoginBody类
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/WxLoginBody.java
package com.ruoyi.common.core.domain.model;
/**
* 微信用户登录对象
*
* @author ruoyi
*/
public class WxLoginBody
{
/**
* 暂时登陆凭据 code 只能运用一次
*/
private String code;
/**
* 偏移量
*/
private String encryptedIv;
/**
* 加密数据
*/
private String encryptedData;
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getEncryptedIv() {
return encryptedIv;
}
public void setEncryptedIv(String encryptedIv) {
this.encryptedIv = encryptedIv;
}
public String getEncryptedData() {
return encryptedData;
}
public void setEncryptedData(String encryptedData) {
this.encryptedData = encryptedData;
}
}
3.5 SysLoginController,新增接口wxLogin和AES解密办法
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java
@Autowired
private WxAppConfig wxAppConfig;
@PostMapping("/wxLogin")
public AjaxResult wxLogin(@RequestBody WxLoginBody wxLoginBody) {
String code = wxLoginBody.getCode();
//秘钥
String encryptedIv = wxLoginBody.getEncryptedIv();
//加密数据
String encryptedData = wxLoginBody.getEncryptedData();
//向微信服务器发送恳求获取用户信息
String url = "https://api.weixin.qq.com/sns/jscode2session?appid=" + wxAppConfig.getAppId() + "&secret=" + wxAppConfig.getAppSecret() + "&js_code=" + code + "&grant_type=authorization_code";
String res = restTemplate.getForObject(url, String.class);
JSONObject jsonObject = JSONObject.parseObject(res);
//获取session_key和openid
String sessionKey = jsonObject.getString("session_key");
String openid = jsonObject.getString("openid");
//解密
String decryptResult = "";
try {
//假如没有绑定微信敞开渠道,解析结果是没有unionid的。
decryptResult = decrypt(sessionKey, encryptedIv, encryptedData);
} catch (Exception e) {
e.printStackTrace();
return AjaxResult.error("微信登录失利!");
}
if (StringUtils.hasText(decryptResult)) {
//假如解析成功,获取token
String token = loginService.wxLogin(decryptResult);
AjaxResult ajax = AjaxResult.success();
ajax.put(Constants.TOKEN, token);
return ajax;
} else {
return AjaxResult.error("微信登录失利!");
}
}
/**
* AES解密
*/
private String decrypt(String sessionKey,String encryptedIv,String encryptedData) throws Exception{
// 转化为字节数组
byte[] key = Base64.decode(sessionKey);
byte[] iv = Base64.decode(encryptedIv);
byte[] encData = Base64.decode(encryptedData);
// 假如密钥缺乏16位,那么就补足
int base =16;
if (key.length % base !=0) {
int groups = key.length / base +(key.length % base != 0 ? 1 : 0);
byte[] temp = new byte[groups * base];
Arrays.fill(temp,(byte) 0);
System.arraycopy(key,0,temp,0,key.length);
key = temp;
}
// 假如初始向量缺乏16位,也补足
if (iv.length % base !=0) {
int groups = iv.length / base +(iv.length % base != 0 ? 1 : 0);
byte[] temp = new byte[groups * base];
Arrays.fill(temp,(byte) 0);
System.arraycopy(iv,0,temp,0,iv.length);
iv = temp;
}
AlgorithmParameterSpec ivSpec = new IvParameterSpec(iv);
String resultStr = null;
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec keySpec = new SecretKeySpec(key,"AES");
cipher.init(Cipher.DECRYPT_MODE,keySpec,ivSpec);
resultStr = new String(cipher.doFinal(encData),"UTF-8");
} catch (Exception e){
// logger.info("解析错误");
e.printStackTrace();
}
// 解析加密后的字符串
return resultStr;
}
3.6 SecurityConfig白名单放行Wxlogin接口
ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java
// 关于登录login 注册register 验证码captchaImage 微信登陆wxLogin 允许匿名拜访
.antMatchers("/login","/wxLogin", "/register", "/captchaImage").permitAll()
3.6 SysLoginService新增wxLogin办法
ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/SysLoginService.java
/**
* 微信登录
*
* @param decryptResult 登录凭据 只能用一次
* @return
*/
public String wxLogin(String decryptResult){
//字符串转json
JSONObject jsonObject = JSONObject.parseObject(decryptResult);
// String unionid = jsonObject.getString("unionid");
String openId = jsonObject.getString("openId");
System.out.println("openId"+openId);
//获取nickName
String nickName = getStringRandom(16);// 生成16位随机昵称
//获取头像
// String avatarUrl = jsonObject.getString("avatarUrl");
String avatarUrl = "";
//还可以获取其他信息
//依据openid判别数据库中是否有该用户
//依据openid查询用户信息
SysUser wxUser = userMapper.selectWxUserByOpenId(openId);
//假如查不到,则新增,查到了,则更新
SysUser user = new SysUser();
if (wxUser == null) {
// 新增
user.setUserName(getStringRandom(16));// 生成16位随机用户名
user.setNickName(nickName);
user.setAvatar(avatarUrl);
// wxUser.setUnionId(unionid);
user.setOpenId(openId);
user.setCreateTime(DateUtils.getNowDate());
//新增 用户
userMapper.insertUser(user);
}else {
//更新
user = wxUser;
user.setNickName(nickName);
user.setAvatar(avatarUrl);
user.setUpdateTime(DateUtils.getNowDate());
userMapper.updateUser(user);
}
//组装token信息
LoginUser loginUser = new LoginUser();
loginUser.setOpenId(openId);
//假如有的话设置
loginUser.setUser(user);
loginUser.setUserId(user.getUserId());
// 生成token
return tokenService.createToken(loginUser);
}
//生成随机用户名,数字和字母组成,
public static String getStringRandom(int length) {
String val = "";
Random random = new Random();
//参数length,表明生成几位随机数
for (int i = 0; i < length; i++) {
String charOrNum = random.nextInt(2) % 2 == 0 ? "char" : "num";
//输出字母仍是数字
if ("char".equalsIgnoreCase(charOrNum)) {
//输出是大写字母仍是小写字母
int temp = random.nextInt(2) % 2 == 0 ? 65 : 97;
val += (char) (random.nextInt(26) + temp);
} else if ("num".equalsIgnoreCase(charOrNum)) {
val += String.valueOf(random.nextInt(10));
}
}
return val;
}
4 演示效果
点击微信登陆后,向wxLogin()发起恳求,假如体系用户中有open_id,则回来token;如如该用户open_id,则添加该用户,用户名与昵称随机生成16位字符,调用登陆接口,回来token。