refactor(auth):重构认证服务,分离用户逻辑到独立服务

- 将用户相关业务迁移至用户模块,通过OpenFeign远程调用。
This commit is contained in:
Hanserwei
2025-10-04 22:03:09 +08:00
parent 19457b5638
commit 534a49a358
48 changed files with 697 additions and 521 deletions

View File

@@ -1,11 +1,11 @@
package com.hanserwei.hannote.auth;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@MapperScan("com.hanserwei.hannote.auth.domain.mapper")
@EnableFeignClients(basePackages = "com.hanserwei.hannote")
public class HanNoteAuthApplication {
public static void main(String[] args) {

View File

@@ -4,7 +4,7 @@ import com.hanserwei.framework.biz.operationlog.aspect.ApiOperationLog;
import com.hanserwei.framework.common.response.Response;
import com.hanserwei.hannote.auth.model.vo.user.UpdatePasswordReqVO;
import com.hanserwei.hannote.auth.model.vo.user.UserLoginReqVO;
import com.hanserwei.hannote.auth.service.UserService;
import com.hanserwei.hannote.auth.service.AuthService;
import jakarta.annotation.Resource;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -16,26 +16,26 @@ import org.springframework.web.bind.annotation.RestController;
@RestController
@Slf4j
@RequiredArgsConstructor
public class UserController {
public class AuthController {
@Resource
private UserService userService;
private AuthService authService;
@PostMapping("/login")
@ApiOperationLog(description = "用户登录/注册")
public Response<String> loginAndRegister(@Validated @RequestBody UserLoginReqVO userLoginReqVO) {
return userService.loginAndRegister(userLoginReqVO);
return authService.loginAndRegister(userLoginReqVO);
}
@PostMapping("/logout")
@ApiOperationLog(description = "账号登出")
public Response<?> logout() {
return userService.logout();
return authService.logout();
}
@PostMapping("/password/update")
@ApiOperationLog(description = "修改密码")
public Response<?> updatePassword(@Validated @RequestBody UpdatePasswordReqVO updatePasswordReqVO) {
return userService.updatePassword(updatePasswordReqVO);
return authService.updatePassword(updatePasswordReqVO);
}
}

View File

@@ -1,91 +0,0 @@
package com.hanserwei.hannote.auth.domain.dataobject;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.util.Date;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 权限表
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "t_permission")
public class PermissionDO {
/**
* 主键ID
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 父ID
*/
@TableField(value = "parent_id")
private Long parentId;
/**
* 权限名称
*/
@TableField(value = "`name`")
private String name;
/**
* 类型(1目录 2菜单 3按钮)
*/
@TableField(value = "`type`")
private Byte type;
/**
* 菜单路由
*/
@TableField(value = "menu_url")
private String menuUrl;
/**
* 菜单图标
*/
@TableField(value = "menu_icon")
private String menuIcon;
/**
* 管理系统中的显示顺序
*/
@TableField(value = "sort")
private Integer sort;
/**
* 权限标识
*/
@TableField(value = "permission_key")
private String permissionKey;
/**
* 状态(0启用1禁用)
*/
@TableField(value = "`status`")
private Byte status;
/**
* 创建时间
*/
@TableField(value = "create_time")
private Date createTime;
/**
* 更新时间
*/
@TableField(value = "update_time")
private Date updateTime;
/**
* 逻辑删除(0未删除 1已删除)
*/
@TableField(value = "is_deleted")
private Boolean isDeleted;
}

View File

@@ -1,73 +0,0 @@
package com.hanserwei.hannote.auth.domain.dataobject;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.util.Date;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 角色表
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "t_role")
public class RoleDO {
/**
* 主键ID
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 角色名
*/
@TableField(value = "role_name")
private String roleName;
/**
* 角色唯一标识
*/
@TableField(value = "role_key")
private String roleKey;
/**
* 状态(0启用 1禁用)
*/
@TableField(value = "`status`")
private Byte status;
/**
* 管理系统中的显示顺序
*/
@TableField(value = "sort")
private Integer sort;
/**
* 备注
*/
@TableField(value = "remark")
private String remark;
/**
* 创建时间
*/
@TableField(value = "create_time")
private Date createTime;
/**
* 最后一次更新时间
*/
@TableField(value = "update_time")
private Date updateTime;
/**
* 逻辑删除(0未删除 1已删除)
*/
@TableField(value = "is_deleted")
private Boolean isDeleted;
}

View File

@@ -1,55 +0,0 @@
package com.hanserwei.hannote.auth.domain.dataobject;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.util.Date;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 用户权限表
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "t_role_permission_rel")
public class RolePermissionDO {
/**
* 主键ID
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 角色ID
*/
@TableField(value = "role_id")
private Long roleId;
/**
* 权限ID
*/
@TableField(value = "permission_id")
private Long permissionId;
/**
* 创建时间
*/
@TableField(value = "create_time")
private Date createTime;
/**
* 更新时间
*/
@TableField(value = "update_time")
private Date updateTime;
/**
* 逻辑删除(0未删除 1已删除)
*/
@TableField(value = "is_deleted")
private Boolean isDeleted;
}

View File

@@ -1,107 +0,0 @@
package com.hanserwei.hannote.auth.domain.dataobject;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
* 用户表
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "t_user")
public class UserDO {
/**
* 主键ID
*/
@TableId(value = "id", type = IdType.ASSIGN_ID)
private Long id;
/**
* 小憨书号(唯一凭证)
*/
@TableField(value = "han_note_id")
private String hanNoteId;
/**
* 密码
*/
@TableField(value = "`password`")
private String password;
/**
* 昵称
*/
@TableField(value = "nickname")
private String nickname;
/**
* 头像
*/
@TableField(value = "avatar")
private String avatar;
/**
* 生日
*/
@TableField(value = "birthday")
private LocalDate birthday;
/**
* 背景图
*/
@TableField(value = "background_img")
private String backgroundImg;
/**
* 邮箱
*/
@TableField(value = "email")
private String email;
/**
* 性别(0女 1男)
*/
@TableField(value = "sex")
private Integer sex;
/**
* 状态(0启用 1禁用)
*/
@TableField(value = "`status`")
private Integer status;
/**
* 个人简介
*/
@TableField(value = "introduction")
private String introduction;
/**
* 创建时间
*/
@TableField(value = "create_time")
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(value = "update_time")
private LocalDateTime updateTime;
/**
* 逻辑删除(0未删除 1已删除)
*/
@TableField(value = "is_deleted")
private Boolean isDeleted;
}

View File

@@ -1,58 +0,0 @@
package com.hanserwei.hannote.auth.domain.dataobject;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
/**
* 用户角色表
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@TableName(value = "t_user_role_rel")
public class UserRoleDO {
/**
* 主键ID
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 用户ID
*/
@TableField(value = "user_id")
private Long userId;
/**
* 角色ID
*/
@TableField(value = "role_id")
private Long roleId;
/**
* 创建时间
*/
@TableField(value = "create_time")
private LocalDateTime createTime;
/**
* 更新时间
*/
@TableField(value = "update_time")
private LocalDateTime updateTime;
/**
* 逻辑删除(0未删除 1已删除)
*/
@TableField(value = "is_deleted")
private Boolean isDeleted;
}

View File

@@ -1,15 +0,0 @@
package com.hanserwei.hannote.auth.domain.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.hanserwei.hannote.auth.domain.dataobject.PermissionDO;
import java.util.List;
public interface PermissionDOMapper extends BaseMapper<PermissionDO> {
/**
* 查询 APP 端所有被启用的权限
*
* @return 权限列表
*/
List<PermissionDO> selectAppEnabledList();
}

View File

@@ -1,18 +0,0 @@
package com.hanserwei.hannote.auth.domain.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.hanserwei.hannote.auth.domain.dataobject.RoleDO;
import java.util.List;
public interface RoleDOMapper extends BaseMapper<RoleDO> {
/**
* 查询所有被启用的角色
*
* @return 角色列表
*/
List<RoleDO> selectEnabledList();
RoleDO selectByPrimaryKey(Long commonUserRoleId);
}

View File

@@ -1,17 +0,0 @@
package com.hanserwei.hannote.auth.domain.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.hanserwei.hannote.auth.domain.dataobject.RolePermissionDO;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface RolePermissionDOMapper extends BaseMapper<RolePermissionDO> {
/**
* 根据角色 ID 集合批量查询
*
* @param roleIds 角色 ID 集合
* @return 角色权限关系
*/
List<RolePermissionDO> selectByRoleIds(@Param("roleIds") List<Long> roleIds);
}

View File

@@ -1,7 +0,0 @@
package com.hanserwei.hannote.auth.domain.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.hanserwei.hannote.auth.domain.dataobject.UserDO;
public interface UserDOMapper extends BaseMapper<UserDO> {
}

View File

@@ -1,7 +0,0 @@
package com.hanserwei.hannote.auth.domain.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.hanserwei.hannote.auth.domain.dataobject.UserRoleDO;
public interface UserRoleDOMapper extends BaseMapper<UserRoleDO> {
}

View File

@@ -18,6 +18,7 @@ public enum ResponseCodeEnum implements BaseExceptionInterface {
LOGIN_TYPE_ERROR("AUTH-20002", "登录类型错误"),
USER_NOT_FOUND("AUTH-20003", "该用户不存在"),
MAIL_OR_PASSWORD_ERROR("AUTH-20004", "邮箱号或密码错误"),
LOGIN_FAIL("AUTH-20005", "登录失败"),

View File

@@ -0,0 +1,68 @@
package com.hanserwei.hannote.auth.rpc;
import com.hanserwei.framework.common.response.Response;
import com.hanserwei.hannote.user.api.UserFeignApi;
import com.hanserwei.hannote.user.dto.req.FindUserByEmailReqDTO;
import com.hanserwei.hannote.user.dto.req.RegisterUserReqDTO;
import com.hanserwei.hannote.user.dto.req.UpdateUserPasswordReqDTO;
import com.hanserwei.hannote.user.dto.resp.FindUserByEmailRspDTO;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Component;
@Component
public class UserRpcService {
@Resource
private UserFeignApi userFeignApi;
/**
* 用户注册
*
* @param email 邮箱
* @return 用户ID
*/
public Long registerUser(String email) {
RegisterUserReqDTO registerUserReqDTO = new RegisterUserReqDTO();
registerUserReqDTO.setEmail(email);
Response<Long> response = userFeignApi.registerUser(registerUserReqDTO);
if (!response.isSuccess()) {
return null;
}
return response.getData();
}
/**
* 根据邮箱号查询用户信息
*
* @param email 邮箱
* @return 用户信息
*/
public FindUserByEmailRspDTO findUserByEmail(String email) {
FindUserByEmailReqDTO findUserByEmailReqDTO = new FindUserByEmailReqDTO();
findUserByEmailReqDTO.setEmail(email);
Response<FindUserByEmailRspDTO> response = userFeignApi.findByPhone(findUserByEmailReqDTO);
if (!response.isSuccess()) {
return null;
}
return response.getData();
}
/**
* 密码更新
*
* @param encodePassword 加密后的密码
*/
public void updatePassword(String encodePassword) {
UpdateUserPasswordReqDTO updateUserPasswordReqDTO = new UpdateUserPasswordReqDTO();
updateUserPasswordReqDTO.setEncodePassword(encodePassword);
userFeignApi.updatePassword(updateUserPasswordReqDTO);
}
}

View File

@@ -1,108 +0,0 @@
package com.hanserwei.hannote.auth.runner;
import cn.hutool.core.collection.CollUtil;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.hanserwei.framework.common.utils.JsonUtils;
import com.hanserwei.hannote.auth.constant.RedisKeyConstants;
import com.hanserwei.hannote.auth.domain.dataobject.PermissionDO;
import com.hanserwei.hannote.auth.domain.dataobject.RoleDO;
import com.hanserwei.hannote.auth.domain.dataobject.RolePermissionDO;
import com.hanserwei.hannote.auth.domain.mapper.PermissionDOMapper;
import com.hanserwei.hannote.auth.domain.mapper.RoleDOMapper;
import com.hanserwei.hannote.auth.domain.mapper.RolePermissionDOMapper;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@Slf4j
@Component
public class PushRolePermissions2RedisRunner implements ApplicationRunner {
@Resource
private RedisTemplate<String, String> redisTemplate;
@Resource
private RoleDOMapper roleDOMapper;
@Resource
private PermissionDOMapper permissionDOMapper;
@Resource
private RolePermissionDOMapper rolePermissionDOMapper;
// 权限同步标记 Key
private static final String PUSH_PERMISSION_FLAG = "push.permission.flag";
@Override
public void run(ApplicationArguments args) {
try {
// 是否能够同步数据: 原子操作,只有在键 PUSH_PERMISSION_FLAG 不存在时,才会设置该键的值为 "1",并设置过期时间为 1 天
boolean canPushed = Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(PUSH_PERMISSION_FLAG, "1", 1, TimeUnit.DAYS));
// 如果无法同步权限数据
if (!canPushed) {
log.warn("==> 角色权限数据已经同步至 Redis 中,不再同步...");
return;
}
// 查询出所有角色
List<RoleDO> roleDOS = roleDOMapper.selectEnabledList();
if (CollUtil.isNotEmpty(roleDOS)) {
// 拿到所有角色的 ID
List<Long> roleIds = roleDOS.stream().map(RoleDO::getId).toList();
// 根据角色 ID, 批量查询出所有角色对应的权限
List<RolePermissionDO> rolePermissionDOS = rolePermissionDOMapper.selectByRoleIds(roleIds);
// 按角色 ID 分组, 每个角色 ID 对应多个权限 ID
Map<Long, List<Long>> roleIdPermissionIdsMap = rolePermissionDOS.stream().collect(
Collectors.groupingBy(RolePermissionDO::getRoleId,
Collectors.mapping(RolePermissionDO::getPermissionId, Collectors.toList()))
);
// 查询 APP 端所有被启用的权限
List<PermissionDO> permissionDOS = permissionDOMapper.selectAppEnabledList();
// 权限 ID - 权限 DO
Map<Long, PermissionDO> permissionIdDOMap = permissionDOS.stream().collect(
Collectors.toMap(PermissionDO::getId, permissionDO -> permissionDO)
);
// 组织 角色ID-权限 关系
Map<String, List<String>> roleKeyPermissionMap = Maps.newHashMap();
// 循环所有角色
roleDOS.forEach(roleDO -> {
// 当前角色 roleKey
String roleKey = roleDO.getRoleKey();
// 当前角色 ID
Long roleId = roleDO.getId();
// 当前角色 ID 对应的权限 ID 集合
List<Long> permissionIds = roleIdPermissionIdsMap.get(roleId);
if (CollUtil.isNotEmpty(permissionIds)) {
List<String> permissionKeys = Lists.newArrayList();
permissionIds.forEach(permissionId -> {
// 根据权限 ID 获取具体的权限 DO 对象
PermissionDO permissionDO = permissionIdDOMap.get(permissionId);
permissionKeys.add(permissionDO.getPermissionKey());
});
roleKeyPermissionMap.put(roleKey, permissionKeys);
}
});
// 同步至 Redis 中,方便后续网关查询鉴权使用
roleKeyPermissionMap.forEach((roleId, permissions) -> {
String key = RedisKeyConstants.buildRolePermissionsKey(roleId);
redisTemplate.opsForValue().set(key, JsonUtils.toJsonString(permissions));
});
}
log.info("==> 服务启动,成功同步角色权限数据到 Redis 中...");
} catch (Exception e) {
log.error("==> 同步角色权限数据到 Redis 中失败: ", e);
}
}
}

View File

@@ -1,12 +1,10 @@
package com.hanserwei.hannote.auth.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.hanserwei.framework.common.response.Response;
import com.hanserwei.hannote.auth.domain.dataobject.UserDO;
import com.hanserwei.hannote.auth.model.vo.user.UpdatePasswordReqVO;
import com.hanserwei.hannote.auth.model.vo.user.UserLoginReqVO;
public interface UserService extends IService<UserDO> {
public interface AuthService {
/**
* 登录与注册

View File

@@ -0,0 +1,130 @@
package com.hanserwei.hannote.auth.service.impl;
import cn.dev33.satoken.stp.SaTokenInfo;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.util.StrUtil;
import com.google.common.base.Preconditions;
import com.hanserwei.framework.biz.context.holder.LoginUserContextHolder;
import com.hanserwei.framework.common.exception.ApiException;
import com.hanserwei.framework.common.response.Response;
import com.hanserwei.hannote.auth.constant.RedisKeyConstants;
import com.hanserwei.hannote.auth.enums.LoginTypeEnum;
import com.hanserwei.hannote.auth.enums.ResponseCodeEnum;
import com.hanserwei.hannote.auth.model.vo.user.UpdatePasswordReqVO;
import com.hanserwei.hannote.auth.model.vo.user.UserLoginReqVO;
import com.hanserwei.hannote.auth.rpc.UserRpcService;
import com.hanserwei.hannote.auth.service.AuthService;
import com.hanserwei.hannote.user.dto.resp.FindUserByEmailRspDTO;
import jakarta.annotation.Resource;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.Objects;
@Slf4j
@Service
@RequiredArgsConstructor
public class AuthServiceImpl implements AuthService {
private final RedisTemplate<String, Object> redisTemplate;
private final UserRpcService userRpcService;
@Resource(name = "authTaskExecutor")
private ThreadPoolTaskExecutor authTaskExecutor;
private final PasswordEncoder passwordEncoder;
@Override
public Response<String> loginAndRegister(UserLoginReqVO reqVO) {
Integer loginType = reqVO.getType();
String email = reqVO.getEmail();
LoginTypeEnum loginTypeEnum = LoginTypeEnum.valueOf(loginType);
Long userId = null;
//noinspection DataFlowIssue
switch (loginTypeEnum) {
case VERIFICATION_CODE:
String verificationCode = reqVO.getCode();
//校验参数是否为空
Preconditions.checkArgument(StringUtils.isNotBlank(verificationCode), "验证码不能为空");
//构建验证码的RedisKey
String key = RedisKeyConstants.buildVerificationCodeKey(email);
// 查询存储在 Redis 中该用户的登录验证码
String sentCode = (String) redisTemplate.opsForValue().get(key);
// 判断用户提交的验证码,与 Redis 中的验证码是否一致
if (!StrUtil.equals(verificationCode, sentCode)) {
throw new ApiException(ResponseCodeEnum.VERIFICATION_CODE_ERROR);
}
// RPC: 调用用户服务,注册用户
Long userIdTmp = userRpcService.registerUser(email);
// 若调用用户服务,返回的用户 ID 为空,则提示登录失败
if (Objects.isNull(userIdTmp)) {
throw new ApiException(ResponseCodeEnum.LOGIN_FAIL);
}
userId = userIdTmp;
break;
case PASSWORD:
String password = reqVO.getPassword();
// RPC: 调用用户服务,通过手机号查询用户
FindUserByEmailRspDTO findUserByEmailRspDTO = userRpcService.findUserByEmail(email);
// 判断该手机号是否注册
if (Objects.isNull(findUserByEmailRspDTO)) {
throw new ApiException(ResponseCodeEnum.USER_NOT_FOUND);
}
// 拿到密文密码
String encodePassword = findUserByEmailRspDTO.getPassword();
// 匹配密码是否一致
boolean isPasswordCorrect = passwordEncoder.matches(password, encodePassword);
// 如果不正确,则抛出业务异常,提示用户名或者密码不正确
if (!isPasswordCorrect) {
throw new ApiException(ResponseCodeEnum.MAIL_OR_PASSWORD_ERROR);
}
userId = findUserByEmailRspDTO.getId();
break;
default:
break;
}
// SaToken 登录用户,并返回 token 令牌
StpUtil.login(userId);
SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
return Response.success(tokenInfo.getTokenValue());
}
@Override
public Response<?> logout() {
Long userId = LoginUserContextHolder.getUserId();
authTaskExecutor.submit(() -> {
Long userId2 = LoginUserContextHolder.getUserId();
log.info("==> 异步线程中获取 userId: {}", userId2);
});
StpUtil.logout(userId);
return Response.success();
}
@Override
public Response<?> updatePassword(UpdatePasswordReqVO updatePasswordReqVO) {
// 新密码
String newPassword = updatePasswordReqVO.getNewPassword();
// 加密后的密码
String encodePassword = passwordEncoder.encode(newPassword);
// RPC: 调用用户服务:更新密码
userRpcService.updatePassword(encodePassword);
return Response.success();
}
}

View File

@@ -1,196 +0,0 @@
package com.hanserwei.hannote.auth.service.impl;
import cn.dev33.satoken.stp.SaTokenInfo;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.base.Preconditions;
import com.hanserwei.framework.biz.context.holder.LoginUserContextHolder;
import com.hanserwei.framework.common.enums.DeletedEnum;
import com.hanserwei.framework.common.enums.StatusEnum;
import com.hanserwei.framework.common.exception.ApiException;
import com.hanserwei.framework.common.response.Response;
import com.hanserwei.framework.common.utils.JsonUtils;
import com.hanserwei.hannote.auth.constant.RedisKeyConstants;
import com.hanserwei.hannote.auth.constant.RoleConstants;
import com.hanserwei.hannote.auth.domain.dataobject.RoleDO;
import com.hanserwei.hannote.auth.domain.dataobject.UserDO;
import com.hanserwei.hannote.auth.domain.dataobject.UserRoleDO;
import com.hanserwei.hannote.auth.domain.mapper.RoleDOMapper;
import com.hanserwei.hannote.auth.domain.mapper.UserDOMapper;
import com.hanserwei.hannote.auth.domain.mapper.UserRoleDOMapper;
import com.hanserwei.hannote.auth.enums.LoginTypeEnum;
import com.hanserwei.hannote.auth.enums.ResponseCodeEnum;
import com.hanserwei.hannote.auth.model.vo.user.UpdatePasswordReqVO;
import com.hanserwei.hannote.auth.model.vo.user.UserLoginReqVO;
import com.hanserwei.hannote.auth.service.UserService;
import jakarta.annotation.Resource;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@Slf4j
@Service
@RequiredArgsConstructor
public class UserServiceImpl extends ServiceImpl<UserDOMapper, UserDO> implements UserService {
private final RedisTemplate<String, Object> redisTemplate;
private final UserRoleDOMapper userRoleDOMapper;
private final TransactionTemplate transactionTemplate;
private final RoleDOMapper roleDOMapper;
@Resource(name = "authTaskExecutor")
private ThreadPoolTaskExecutor authTaskExecutor;
private final PasswordEncoder passwordEncoder;
@Override
public Response<String> loginAndRegister(UserLoginReqVO reqVO) {
Integer loginType = reqVO.getType();
String email = reqVO.getEmail();
LoginTypeEnum loginTypeEnum = LoginTypeEnum.valueOf(loginType);
Long userId = null;
//noinspection DataFlowIssue
switch (loginTypeEnum) {
case VERIFICATION_CODE:
String verificationCode = reqVO.getCode();
//校验参数是否为空
Preconditions.checkArgument(StringUtils.isNotBlank(verificationCode), "验证码不能为空");
//构建验证码的RedisKey
String key = RedisKeyConstants.buildVerificationCodeKey(email);
// 查询存储在 Redis 中该用户的登录验证码
String sentCode = (String) redisTemplate.opsForValue().get(key);
// 判断用户提交的验证码,与 Redis 中的验证码是否一致
if (!StrUtil.equals(verificationCode, sentCode)) {
throw new ApiException(ResponseCodeEnum.VERIFICATION_CODE_ERROR);
}
//通过邮箱查询用户
UserDO userDO = this.getOne(new QueryWrapper<UserDO>().eq("email", email));
log.info("==> 用户是否注册, email: {}, userDO: {}", email, JsonUtils.toJsonString(userDO));
// 判断是否注册
if (Objects.isNull(userDO)) {
// 若此用户还没有注册,系统自动注册该用户
userId = registerUser(email);
} else {
// 已注册,则获取其用户 ID
userId = userDO.getId();
}
break;
case PASSWORD:
String password = reqVO.getPassword();
// 根据邮箱号查询
UserDO userDO1 = this.getOne(new QueryWrapper<UserDO>().eq("email", email));
if (Objects.isNull(userDO1)){
throw new ApiException(ResponseCodeEnum.USER_NOT_FOUND);
}
// 拿到密文密码
String encodePassword = userDO1.getPassword();
boolean isPasswordCorrect = passwordEncoder.matches(password, encodePassword);
if (!isPasswordCorrect) {
throw new ApiException(ResponseCodeEnum.MAIL_OR_PASSWORD_ERROR);
}
userId = userDO1.getId();
break;
default:
break;
}
// SaToken 登录用户,并返回 token 令牌
StpUtil.login(userId);
SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
return Response.success(tokenInfo.getTokenValue());
}
public Long registerUser(String email) {
return transactionTemplate.execute(status -> {
try {
// 获取全局自增的小憨书ID
Long hanNoteId = redisTemplate.opsForValue().increment(RedisKeyConstants.HAN_NOTE_ID_GENERATOR_KEY);
UserDO userDO = UserDO.builder()
.hanNoteId(String.valueOf(hanNoteId)) // 自动生成小红书号 ID
.nickname("小憨憨" + hanNoteId) // 自动生成昵称, 如小憨憨10000
.status(StatusEnum.ENABLE.getValue()) // 状态为启用
.email(email)
.createTime(LocalDateTime.now())
.updateTime(LocalDateTime.now())
.isDeleted(DeletedEnum.NO.getValue()) // 逻辑删除
.build();
// 添加入库
this.save(userDO);
// 获取入库的用户ID
Long userId = userDO.getId();
// 添加默认用户角色
UserRoleDO userRoleDO = UserRoleDO.builder()
.userId(userId)
.roleId(RoleConstants.COMMON_USER_ROLE_ID)
.createTime(LocalDateTime.now())
.updateTime(LocalDateTime.now())
.isDeleted(DeletedEnum.NO.getValue())
.build();
userRoleDOMapper.insert(userRoleDO);
RoleDO roleDO = roleDOMapper.selectByPrimaryKey(RoleConstants.COMMON_USER_ROLE_ID);
// 将该用户的角色 ID 存入 Redis 中,指定初始容量为 1这样可以减少在扩容时的性能开销
List<String> roles = new ArrayList<>(1);
roles.add(roleDO.getRoleKey());
String userRolesKey = RedisKeyConstants.buildUserRoleKey(userId);
redisTemplate.opsForValue().set(userRolesKey, JsonUtils.toJsonString(roles));
return userId;
} catch (Exception e) {
status.setRollbackOnly(); // 标记事务为回滚
log.error("==> 系统注册用户异常: ", e);
return null;
}
});
}
@Override
public Response<?> logout() {
Long userId = LoginUserContextHolder.getUserId();
authTaskExecutor.submit(() -> {
Long userId2 = LoginUserContextHolder.getUserId();
log.info("==> 异步线程中获取 userId: {}", userId2);
});
StpUtil.logout(userId);
return Response.success();
}
@Override
public Response<?> updatePassword(UpdatePasswordReqVO updatePasswordReqVO) {
// 新密码
String newPassword = updatePasswordReqVO.getNewPassword();
// 加密后的密码
String encodePassword = passwordEncoder.encode(newPassword);
// 获取用户ID
Long userId = LoginUserContextHolder.getUserId();
UserDO userDO = UserDO.builder()
.id(userId)
.password(encodePassword)
.updateTime(LocalDateTime.now())
.build();
// 更新用户密码
this.updateById(userDO);
return Response.success();
}
}

View File

@@ -34,13 +34,6 @@ spring:
mail.smtp.starttls.required: true
server:
port: 8080 # 项目启动的端口
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
global-config:
banner: false
mapper-locations: classpath*:/mapperxml/*.xml
logging:
level:
com.hanserwei.hannote.auth.domain.mapper: debug
@@ -62,5 +55,3 @@ sa-token:
token-style: random-128
# 是否输出操作日志
is-log: true
alarm:
type: mail # 告警类型

View File

@@ -1,33 +0,0 @@
<?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="com.hanserwei.hannote.auth.domain.mapper.PermissionDOMapper">
<resultMap id="BaseResultMap" type="com.hanserwei.hannote.auth.domain.dataobject.PermissionDO">
<!--@mbg.generated-->
<!--@Table t_permission-->
<id column="id" jdbcType="BIGINT" property="id" />
<result column="parent_id" jdbcType="BIGINT" property="parentId" />
<result column="name" jdbcType="VARCHAR" property="name" />
<result column="type" jdbcType="TINYINT" property="type" />
<result column="menu_url" jdbcType="VARCHAR" property="menuUrl" />
<result column="menu_icon" jdbcType="VARCHAR" property="menuIcon" />
<result column="sort" jdbcType="INTEGER" property="sort" />
<result column="permission_key" jdbcType="VARCHAR" property="permissionKey" />
<result column="status" jdbcType="TINYINT" property="status" />
<result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
<result column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
<result column="is_deleted" jdbcType="BIT" property="isDeleted" />
</resultMap>
<sql id="Base_Column_List">
<!--@mbg.generated-->
id, parent_id, `name`, `type`, menu_url, menu_icon, sort, permission_key, `status`,
create_time, update_time, is_deleted
</sql>
<select id="selectAppEnabledList" resultMap="BaseResultMap">
select id, name, permission_key
from t_permission
where status = 0
and type = 3
and is_deleted = 0
</select>
</mapper>

View File

@@ -1,37 +0,0 @@
<?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="com.hanserwei.hannote.auth.domain.mapper.RoleDOMapper">
<resultMap id="BaseResultMap" type="com.hanserwei.hannote.auth.domain.dataobject.RoleDO">
<!--@mbg.generated-->
<!--@Table t_role-->
<id column="id" jdbcType="BIGINT" property="id" />
<result column="role_name" jdbcType="VARCHAR" property="roleName" />
<result column="role_key" jdbcType="VARCHAR" property="roleKey" />
<result column="status" jdbcType="TINYINT" property="status" />
<result column="sort" jdbcType="INTEGER" property="sort" />
<result column="remark" jdbcType="VARCHAR" property="remark" />
<result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
<result column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
<result column="is_deleted" jdbcType="BIT" property="isDeleted" />
</resultMap>
<sql id="Base_Column_List">
<!--@mbg.generated-->
id, role_name, role_key, `status`, sort, remark, create_time, update_time, is_deleted
</sql>
<select id="selectEnabledList" resultMap="BaseResultMap">
select id, role_key, role_name
from t_role
where status = 0
and is_deleted = 0;
</select>
<select id="selectByPrimaryKey" resultMap="BaseResultMap">
select
<include refid="Base_Column_List"/>
from t_role
where id = #{id,jdbcType=BIGINT}
and is_deleted = 0
and status = 0;
</select>
</mapper>

View File

@@ -1,27 +0,0 @@
<?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="com.hanserwei.hannote.auth.domain.mapper.RolePermissionDOMapper">
<resultMap id="BaseResultMap" type="com.hanserwei.hannote.auth.domain.dataobject.RolePermissionDO">
<!--@mbg.generated-->
<!--@Table t_role_permission_rel-->
<id column="id" jdbcType="BIGINT" property="id" />
<result column="role_id" jdbcType="BIGINT" property="roleId" />
<result column="permission_id" jdbcType="BIGINT" property="permissionId" />
<result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
<result column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
<result column="is_deleted" jdbcType="BIT" property="isDeleted" />
</resultMap>
<sql id="Base_Column_List">
<!--@mbg.generated-->
id, role_id, permission_id, create_time, update_time, is_deleted
</sql>
<select id="selectByRoleIds" resultMap="BaseResultMap">
select role_id, permission_id
from t_role_permission_rel
where role_id in
<foreach collection="roleIds" item="roleId" separator="," open="(" close=")">
#{roleId}
</foreach>
</select>
</mapper>

View File

@@ -1,27 +0,0 @@
<?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="com.hanserwei.hannote.auth.domain.mapper.UserDOMapper">
<resultMap id="BaseResultMap" type="com.hanserwei.hannote.auth.domain.dataobject.UserDO">
<!--@mbg.generated-->
<!--@Table t_user-->
<id column="id" jdbcType="BIGINT" property="id" />
<result column="han_note_id" jdbcType="VARCHAR" property="hanNoteId" />
<result column="password" jdbcType="VARCHAR" property="password" />
<result column="nickname" jdbcType="VARCHAR" property="nickname" />
<result column="avatar" jdbcType="VARCHAR" property="avatar" />
<result column="birthday" jdbcType="DATE" property="birthday" />
<result column="background_img" jdbcType="VARCHAR" property="backgroundImg" />
<result column="email" jdbcType="VARCHAR" property="email" />
<result column="sex" jdbcType="TINYINT" property="sex" />
<result column="status" jdbcType="TINYINT" property="status" />
<result column="introduction" jdbcType="VARCHAR" property="introduction" />
<result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
<result column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
<result column="is_deleted" jdbcType="BIT" property="isDeleted" />
</resultMap>
<sql id="Base_Column_List">
<!--@mbg.generated-->
id, han_note_id, `password`, nickname, avatar, birthday, background_img, email, sex,
`status`, introduction, create_time, update_time, is_deleted
</sql>
</mapper>

View File

@@ -1,18 +0,0 @@
<?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="com.hanserwei.hannote.auth.domain.mapper.UserRoleDOMapper">
<resultMap id="BaseResultMap" type="com.hanserwei.hannote.auth.domain.dataobject.UserRoleDO">
<!--@mbg.generated-->
<!--@Table t_user_role_rel-->
<id column="id" jdbcType="BIGINT" property="id" />
<result column="user_id" jdbcType="BIGINT" property="userId" />
<result column="role_id" jdbcType="BIGINT" property="roleId" />
<result column="create_time" jdbcType="TIMESTAMP" property="createTime" />
<result column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
<result column="is_deleted" jdbcType="BIT" property="isDeleted" />
</resultMap>
<sql id="Base_Column_List">
<!--@mbg.generated-->
id, user_id, role_id, create_time, update_time, is_deleted
</sql>
</mapper>

View File

@@ -1,34 +0,0 @@
package com.hanserwei.hannote.auth;
import com.alibaba.druid.filter.config.ConfigTools;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
@Slf4j
class DruidTests {
/**
* Druid 密码加密
*/
@Test
@SneakyThrows
void testEncodePassword() {
// 你的密码
String password = "mysql";
String[] arr = ConfigTools.genKeyPair(512);
// 私钥
log.info("privateKey: {}", arr[0]);
// 公钥
log.info("publicKey: {}", arr[1]);
// 通过私钥加密密码
String encodePassword = ConfigTools.encrypt(arr[0], password);
log.info("password: {}", encodePassword);
}
}

View File

@@ -1,14 +0,0 @@
package com.hanserwei.hannote.auth;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
@Slf4j
class HanNoteAuthApplicationTests {
}

View File

@@ -1,49 +0,0 @@
package com.hanserwei.hannote.auth;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
@SpringBootTest
@Slf4j
class RedisTests {
@Resource
private RedisTemplate<String, Object> redisTemplate;
/**
* set key value
*/
@Test
void testSetKeyValue() {
// 添加一个 key 为 name, value 值为 Hanserwei
redisTemplate.opsForValue().set("name", "Hanserwei");
}
/**
* 判断某个 key 是否存在
*/
@Test
void testHasKey() {
log.info("key 是否存在:{}", redisTemplate.hasKey("name"));
}
/**
* 获取某个 key 的 value
*/
@Test
void testGetValue() {
log.info("value 值:{}", redisTemplate.opsForValue().get("name"));
}
/**
* 删除某个 key
*/
@Test
void testDelete() {
redisTemplate.delete("name");
}
}

View File

@@ -1,23 +0,0 @@
package com.hanserwei.hannote.auth;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
@SpringBootTest
@Slf4j
public class ThreadPoolTaskExecutorTests {
@Resource
private ThreadPoolTaskExecutor threadPoolTaskExecutor;
/**
* 测试线程池
*/
@Test
void testSubmit() {
threadPoolTaskExecutor.submit(() -> log.info("异步线程中说: Hanserwei是傻逼"));
}
}