feat: 支持第三方账号登录

Just Auth(开箱即用的整合第三方登录的开源组件,脱离繁琐的第三方登录 SDK,让登录变得 So easy!)
This commit is contained in:
2023-10-14 23:58:13 +08:00
parent 71e20e9f84
commit 05cb609780
41 changed files with 826 additions and 49 deletions

View File

@@ -20,6 +20,8 @@ import java.util.List;
import top.charles7c.cnadmin.auth.model.vo.RouteVO;
import me.zhyd.oauth.model.AuthUser;
/**
* 登录业务接口
*
@@ -39,6 +41,15 @@ public interface LoginService {
*/
String login(String username, String password);
/**
* 社交身份登录
*
* @param authUser
* 社交身份信息
* @return 令牌
*/
String socialLogin(AuthUser authUser);
/**
* 构建路由树
*

View File

@@ -16,6 +16,7 @@
package top.charles7c.cnadmin.auth.service.impl;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;
@@ -23,32 +24,38 @@ import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.lang.tree.TreeNodeConfig;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.json.JSONUtil;
import top.charles7c.cnadmin.auth.model.vo.MetaVO;
import top.charles7c.cnadmin.auth.model.vo.RouteVO;
import top.charles7c.cnadmin.auth.service.LoginService;
import top.charles7c.cnadmin.auth.service.PermissionService;
import top.charles7c.cnadmin.common.annotation.TreeField;
import top.charles7c.cnadmin.common.constant.RegexConsts;
import top.charles7c.cnadmin.common.constant.SysConsts;
import top.charles7c.cnadmin.common.enums.DisEnableStatusEnum;
import top.charles7c.cnadmin.common.enums.GenderEnum;
import top.charles7c.cnadmin.common.enums.MenuTypeEnum;
import top.charles7c.cnadmin.common.model.dto.LoginUser;
import top.charles7c.cnadmin.common.util.SecureUtils;
import top.charles7c.cnadmin.common.util.TreeUtils;
import top.charles7c.cnadmin.common.util.helper.LoginHelper;
import top.charles7c.cnadmin.common.util.validate.CheckUtils;
import top.charles7c.cnadmin.system.model.entity.RoleDO;
import top.charles7c.cnadmin.system.model.entity.UserDO;
import top.charles7c.cnadmin.system.model.entity.UserSocialDO;
import top.charles7c.cnadmin.system.model.vo.DeptDetailVO;
import top.charles7c.cnadmin.system.model.vo.MenuVO;
import top.charles7c.cnadmin.system.service.DeptService;
import top.charles7c.cnadmin.system.service.MenuService;
import top.charles7c.cnadmin.system.service.RoleService;
import top.charles7c.cnadmin.system.service.UserService;
import top.charles7c.cnadmin.system.service.*;
import me.zhyd.oauth.model.AuthUser;
/**
* 登录业务实现
@@ -65,6 +72,8 @@ public class LoginServiceImpl implements LoginService {
private final RoleService roleService;
private final MenuService menuService;
private final PermissionService permissionService;
private final UserRoleService userRoleService;
private final UserSocialService userSocialService;
@Override
public String login(String username, String password) {
@@ -72,16 +81,44 @@ public class LoginServiceImpl implements LoginService {
CheckUtils.throwIfNull(user, "用户名或密码错误");
Long userId = user.getId();
CheckUtils.throwIfNotEqual(SecureUtils.md5Salt(password, userId.toString()), user.getPassword(), "用户名或密码错误");
CheckUtils.throwIfEqual(DisEnableStatusEnum.DISABLE, user.getStatus(), "此账号已被禁用,如有疑问,请联系管理员");
DeptDetailVO deptDetailVO = deptService.get(user.getDeptId());
CheckUtils.throwIfEqual(DisEnableStatusEnum.DISABLE, deptDetailVO.getStatus(), "此账号部门已被禁用,如有疑问,请联系管理员");
// 登录并缓存用户信息
LoginUser loginUser = BeanUtil.copyProperties(user, LoginUser.class);
loginUser.setPermissions(permissionService.listPermissionByUserId(userId));
loginUser.setRoleCodes(permissionService.listRoleCodeByUserId(userId));
loginUser.setRoles(roleService.listByUserId(userId));
LoginHelper.login(loginUser);
return StpUtil.getTokenValue();
this.checkUserStatus(user);
return this.login(user);
}
@Override
public String socialLogin(AuthUser authUser) {
String source = authUser.getSource();
String openId = authUser.getUuid();
UserSocialDO userSocial = userSocialService.getBySourceAndOpenId(source, openId);
UserDO user;
if (null == userSocial) {
String username = authUser.getUsername();
boolean isMatch = ReUtil.isMatch(RegexConsts.USERNAME, username);
UserDO existsUser = userService.getByUsername(username);
if (null != existsUser || !isMatch) {
username = RandomUtil.randomString(RandomUtil.BASE_CHAR, 5) + IdUtil.fastSimpleUUID();
}
user = new UserDO();
user.setUsername(username);
user.setNickname(authUser.getNickname());
user.setGender(GenderEnum.valueOf(authUser.getGender().name()));
user.setAvatar(authUser.getAvatar());
user.setDeptId(SysConsts.SUPER_DEPT_ID);
Long userId = userService.save(user);
RoleDO role = roleService.getByCode(SysConsts.ADMIN_ROLE_CODE);
userRoleService.save(Collections.singletonList(role.getId()), userId);
userSocial = new UserSocialDO();
userSocial.setUserId(userId);
userSocial.setSource(source);
userSocial.setOpenId(openId);
} else {
user = BeanUtil.toBean(userService.get(userSocial.getUserId()), UserDO.class);
}
this.checkUserStatus(user);
userSocial.setMetaJson(JSONUtil.toJsonStr(authUser));
userSocial.setLastLoginTime(LocalDateTime.now());
userSocialService.saveOrUpdate(userSocial);
return this.login(user);
}
@Override
@@ -120,4 +157,32 @@ public class LoginServiceImpl implements LoginService {
});
return BeanUtil.copyToList(treeList, RouteVO.class);
}
/**
* 登录并缓存用户信息
*
* @param user
* 用户信息
* @return 令牌
*/
private String login(UserDO user) {
Long userId = user.getId();
LoginUser loginUser = BeanUtil.copyProperties(user, LoginUser.class);
loginUser.setPermissions(permissionService.listPermissionByUserId(userId));
loginUser.setRoleCodes(permissionService.listRoleCodeByUserId(userId));
loginUser.setRoles(roleService.listByUserId(userId));
return LoginHelper.login(loginUser);
}
/**
* 检查用户状态
*
* @param user
* 用户信息
*/
private void checkUserStatus(UserDO user) {
CheckUtils.throwIfEqual(DisEnableStatusEnum.DISABLE, user.getStatus(), "此账号已被禁用,如有疑问,请联系管理员");
DeptDetailVO deptDetailVO = deptService.get(user.getDeptId());
CheckUtils.throwIfEqual(DisEnableStatusEnum.DISABLE, deptDetailVO.getStatus(), "此账号部门已被禁用,如有疑问,请联系管理员");
}
}

View File

@@ -0,0 +1,42 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.charles7c.cnadmin.system.mapper;
import org.apache.ibatis.annotations.Param;
import top.charles7c.cnadmin.common.base.BaseMapper;
import top.charles7c.cnadmin.system.model.entity.UserSocialDO;
/**
* 用户社会化关联 Mapper
*
* @author Charles7c
* @since 2023/10/11 22:10
*/
public interface UserSocialMapper extends BaseMapper<UserSocialDO> {
/**
* 根据来源和开放 ID 查询
*
* @param source
* 来源
* @param openId
* 开放 ID
* @return 用户社会化关联信息
*/
UserSocialDO selectBySourceAndOpenId(@Param("source") String source, @Param("openId") String openId);
}

View File

@@ -0,0 +1,70 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.charles7c.cnadmin.system.model.entity;
import java.io.Serializable;
import java.time.LocalDateTime;
import lombok.Data;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
/**
* 用户社会化关联实体
*
* @author Charles7c
* @since 2023/10/11 22:10
*/
@Data
@TableName("sys_user_social")
public class UserSocialDO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 用户 ID
*/
private Long userId;
/**
* 来源
*/
private String source;
/**
* 开放 ID
*/
private String openId;
/**
* 附加信息
*/
private String metaJson;
/**
* 最后登录时间
*/
private LocalDateTime lastLoginTime;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
}

View File

@@ -22,6 +22,7 @@ import java.util.Set;
import top.charles7c.cnadmin.common.base.BaseService;
import top.charles7c.cnadmin.common.model.dto.RoleDTO;
import top.charles7c.cnadmin.common.model.vo.LabelValueVO;
import top.charles7c.cnadmin.system.model.entity.RoleDO;
import top.charles7c.cnadmin.system.model.query.RoleQuery;
import top.charles7c.cnadmin.system.model.request.RoleRequest;
import top.charles7c.cnadmin.system.model.vo.RoleDetailVO;
@@ -70,4 +71,13 @@ public interface RoleService extends BaseService<RoleVO, RoleDetailVO, RoleQuery
* @return 角色集合
*/
Set<RoleDTO> listByUserId(Long userId);
/**
* 根据角色编码查询
*
* @param code
* 角色编码
* @return 角色信息
*/
RoleDO getByCode(String code);
}

View File

@@ -37,6 +37,15 @@ import top.charles7c.cnadmin.system.model.vo.UserVO;
*/
public interface UserService extends BaseService<UserVO, UserDetailVO, UserQuery, UserRequest> {
/**
* 保存用户信息
*
* @param user
* 用户信息
* @return ID
*/
Long save(UserDO user);
/**
* 上传头像
*

View File

@@ -0,0 +1,47 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.charles7c.cnadmin.system.service;
import top.charles7c.cnadmin.system.model.entity.UserSocialDO;
/**
* 用户社会化关联业务接口
*
* @author Charles7c
* @since 2023/10/11 22:10
*/
public interface UserSocialService {
/**
* 根据来源和开放 ID 查询
*
* @param source
* 来源
* @param openId
* 开放 ID
* @return 用户社会化关联信息
*/
UserSocialDO getBySourceAndOpenId(String source, String openId);
/**
* 保存
*
* @param userSocial
* 用户社会化关联信息
*/
void saveOrUpdate(UserSocialDO userSocial);
}

View File

@@ -178,6 +178,11 @@ public class RoleServiceImpl extends BaseServiceImpl<RoleMapper, RoleDO, RoleVO,
return new HashSet<>(BeanUtil.copyToList(roleList, RoleDTO.class));
}
@Override
public RoleDO getByCode(String code) {
return baseMapper.lambdaQuery().eq(RoleDO::getCode, code).one();
}
/**
* 检查名称是否存在
*

View File

@@ -18,9 +18,7 @@ package top.charles7c.cnadmin.system.service.impl;
import java.io.File;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.*;
import javax.annotation.Resource;
@@ -82,6 +80,13 @@ public class UserServiceImpl extends BaseServiceImpl<UserMapper, UserDO, UserVO,
@Resource
private DeptService deptService;
@Override
public Long save(UserDO user) {
user.setStatus(DisEnableStatusEnum.ENABLE);
baseMapper.insert(user);
return user.getId();
}
@Override
@Transactional(rollbackFor = Exception.class)
public Long add(UserRequest request) {

View File

@@ -0,0 +1,57 @@
/*
* Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package top.charles7c.cnadmin.system.service.impl;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import top.charles7c.cnadmin.system.mapper.UserSocialMapper;
import top.charles7c.cnadmin.system.model.entity.UserSocialDO;
import top.charles7c.cnadmin.system.service.UserSocialService;
/**
* 用户社会化关联业务实现
*
* @author Charles7c
* @since 2023/10/11 22:10
*/
@Service
@RequiredArgsConstructor
public class UserSocialServiceImpl implements UserSocialService {
private final UserSocialMapper baseMapper;
@Override
public UserSocialDO getBySourceAndOpenId(String source, String openId) {
return baseMapper.selectBySourceAndOpenId(source, openId);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void saveOrUpdate(UserSocialDO userSocial) {
if (null == userSocial.getCreateTime()) {
baseMapper.insert(userSocial);
} else {
baseMapper.lambdaUpdate().set(UserSocialDO::getMetaJson, userSocial.getMetaJson())
.set(UserSocialDO::getLastLoginTime, userSocial.getLastLoginTime())
.eq(UserSocialDO::getSource, userSocial.getSource()).eq(UserSocialDO::getOpenId, userSocial.getOpenId())
.update();
}
}
}

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="top.charles7c.cnadmin.system.mapper.UserSocialMapper">
<select id="selectBySourceAndOpenId"
resultType="top.charles7c.cnadmin.system.model.entity.UserSocialDO">
SELECT t1.*
FROM `sys_user_social` AS t1
LEFT JOIN `sys_user` AS t2 ON t2.`id` = t1.`user_id`
WHERE t1.`source` = #{source} AND t1.`open_id` = #{openId}
</select>
</mapper>