|
|
|
|
@ -1,264 +1,325 @@
|
|
|
|
|
package cn.org.alan.exam.service.impl;
|
|
|
|
|
|
|
|
|
|
import cn.hutool.captcha.CaptchaUtil;
|
|
|
|
|
import cn.hutool.captcha.LineCaptcha;
|
|
|
|
|
import cn.org.alan.exam.common.result.Result;
|
|
|
|
|
import cn.org.alan.exam.converter.UserConverter;
|
|
|
|
|
import cn.org.alan.exam.mapper.RoleMapper;
|
|
|
|
|
import cn.org.alan.exam.mapper.UserDailyLoginDurationMapper;
|
|
|
|
|
import cn.org.alan.exam.mapper.UserMapper;
|
|
|
|
|
import cn.org.alan.exam.model.entity.User;
|
|
|
|
|
import cn.org.alan.exam.model.entity.UserDailyLoginDuration;
|
|
|
|
|
import cn.org.alan.exam.model.form.Auth.LoginForm;
|
|
|
|
|
import cn.org.alan.exam.model.form.UserForm;
|
|
|
|
|
import cn.org.alan.exam.security.SysUserDetails;
|
|
|
|
|
import cn.org.alan.exam.service.IAuthService;
|
|
|
|
|
import cn.org.alan.exam.util.DateTimeUtil;
|
|
|
|
|
import cn.org.alan.exam.util.JwtUtil;
|
|
|
|
|
import cn.org.alan.exam.util.SecretUtils;
|
|
|
|
|
import cn.org.alan.exam.util.SecurityUtil;
|
|
|
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
|
|
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
|
|
|
|
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
|
|
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
|
|
|
import jakarta.annotation.Resource;
|
|
|
|
|
import jakarta.servlet.ServletOutputStream;
|
|
|
|
|
import jakarta.servlet.http.HttpServletRequest;
|
|
|
|
|
import jakarta.servlet.http.HttpServletResponse;
|
|
|
|
|
import jakarta.servlet.http.HttpSession;
|
|
|
|
|
import lombok.SneakyThrows;
|
|
|
|
|
import org.springframework.data.redis.core.StringRedisTemplate;
|
|
|
|
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
|
|
|
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
|
|
|
|
import org.springframework.security.core.context.SecurityContextHolder;
|
|
|
|
|
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
|
|
|
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
|
|
|
|
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
|
|
|
|
import org.springframework.stereotype.Service;
|
|
|
|
|
|
|
|
|
|
import java.io.IOException;
|
|
|
|
|
import java.time.Duration;
|
|
|
|
|
import java.time.LocalDate;
|
|
|
|
|
import java.time.LocalDateTime;
|
|
|
|
|
import java.time.ZoneOffset;
|
|
|
|
|
import java.util.ArrayList;
|
|
|
|
|
import java.util.List;
|
|
|
|
|
import java.util.Objects;
|
|
|
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
|
|
|
|
|
|
package cn.org.alan.exam.service.impl; // 声明包路径
|
|
|
|
|
import cn.hutool.captcha.CaptchaUtil; // 引入Hutool工具类库中的验证码工具
|
|
|
|
|
import cn.hutool.captcha.LineCaptcha; // 引入Hutool工具类库中的线性验证码
|
|
|
|
|
import cn.org.alan.exam.common.result.Result; // 引入项目中的通用返回结果类
|
|
|
|
|
import cn.org.alan.exam.converter.UserConverter; // 引入用户转换器
|
|
|
|
|
import cn.org.alan.exam.mapper.RoleMapper; // 引入角色Mapper
|
|
|
|
|
import cn.org.alan.exam.mapper.UserDailyLoginDurationMapper; // 引入用户每日登录时长Mapper
|
|
|
|
|
import cn.org.alan.exam.mapper.UserMapper; // 引入用户Mapper
|
|
|
|
|
import cn.org.alan.exam.model.entity.User; // 引入用户实体类
|
|
|
|
|
import cn.org.alan.exam.model.entity.UserDailyLoginDuration; // 引入用户每日登录时长实体类
|
|
|
|
|
import cn.org.alan.exam.model.form.Auth.LoginForm; // 引入登录表单类
|
|
|
|
|
import cn.org.alan.exam.model.form.UserForm; // 引入用户表单类
|
|
|
|
|
import cn.org.alan.exam.security.SysUserDetails; // 引入系统用户详情类
|
|
|
|
|
import cn.org.alan.exam.service.IAuthService; // 引入认证服务接口
|
|
|
|
|
import cn.org.alan.exam.util.DateTimeUtil; // 引入日期时间工具类
|
|
|
|
|
import cn.org.alan.exam.util.JwtUtil; // 引入JWT工具类
|
|
|
|
|
import cn.org.alan.exam.util.SecretUtils; // 引入加密工具类
|
|
|
|
|
import cn.org.alan.exam.util.SecurityUtil; // 引入安全工具类
|
|
|
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; // 引入MyBatis Plus查询条件包装类
|
|
|
|
|
import com.baomidou.mybatisplus.core.toolkit.StringUtils; // 引入MyBatis Plus字符串工具类
|
|
|
|
|
import com.fasterxml.jackson.core.JsonProcessingException; // 引入Jackson JSON处理异常类
|
|
|
|
|
import com.fasterxml.jackson.databind.ObjectMapper; // 引入Jackson对象映射器
|
|
|
|
|
import jakarta.annotation.Resource; // 引入Jakarta注解的依赖注入注解
|
|
|
|
|
import jakarta.servlet.ServletOutputStream; // 引入Servlet输出流类
|
|
|
|
|
import jakarta.servlet.http.HttpServletRequest; // 引入Servlet HTTP请求类
|
|
|
|
|
import jakarta.servlet.http.HttpServletResponse; // 引入Servlet HTTP响应类
|
|
|
|
|
import jakarta.servlet.http.HttpSession; // 引入Servlet会话类
|
|
|
|
|
import lombok.SneakyThrows; // 引入Lombok异常处理注解
|
|
|
|
|
import org.springframework.data.redis.core.StringRedisTemplate; // 引入Spring Data Redis字符串模板类
|
|
|
|
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; // 引入Spring Security用户名密码认证令牌类
|
|
|
|
|
import org.springframework.security.core.authority.SimpleGrantedAuthority; // 引入Spring Security简单权限授权类
|
|
|
|
|
import org.springframework.security.core.context.SecurityContextHolder; // 引入Spring Security安全上下文持有者类
|
|
|
|
|
import org.springframework.security.core.userdetails.UsernameNotFoundException; // 引入Spring Security用户名未找到异常类
|
|
|
|
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; // 引入Spring Security BCrypt密码编码器类
|
|
|
|
|
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; // 引入Spring Security Web认证详情源类
|
|
|
|
|
import org.springframework.stereotype.Service; // 引入Spring服务注解
|
|
|
|
|
|
|
|
|
|
import java.io.IOException; // 引入IO异常类
|
|
|
|
|
import java.time.Duration; // 引入Java时间库中的持续时间类
|
|
|
|
|
import java.time.LocalDate; // 引入Java时间库中的本地日期类
|
|
|
|
|
import java.time.LocalDateTime; // 引入Java时间库中的本地日期时间类
|
|
|
|
|
import java.time.ZoneOffset; // 引入Java时间库中的时区偏移量类
|
|
|
|
|
import java.util.ArrayList; // 引入Java集合框架中的ArrayList类
|
|
|
|
|
import java.util.List; // 引入Java集合框架中的List接口
|
|
|
|
|
import java.util.Objects; // 引入Java工具类中的Objects类
|
|
|
|
|
import java.util.concurrent.TimeUnit; // 引入Java并发库中的时间单位枚举
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @Author Alan
|
|
|
|
|
* @Version
|
|
|
|
|
* @Date 2024/3/28 1:33 PM
|
|
|
|
|
*/
|
|
|
|
|
@Service
|
|
|
|
|
public class AuthServiceImpl implements IAuthService {
|
|
|
|
|
private static final String HEARTBEAT_KEY_PREFIX = "user:heartbeat:";
|
|
|
|
|
private static final long HEARTBEAT_INTERVAL_MILLIS = 10 * 60 * 1000; // 10分钟
|
|
|
|
|
@Service // 声明这是一个Spring服务组件
|
|
|
|
|
public class AuthServiceImpl implements IAuthService { // 实现认证服务接口
|
|
|
|
|
private static final String HEARTBEAT_KEY_PREFIX = "user:heartbeat:"; // 定义心跳键前缀
|
|
|
|
|
private static final long HEARTBEAT_INTERVAL_MILLIS = 10 * 60 * 1000; // 定义心跳间隔时间为10分钟
|
|
|
|
|
|
|
|
|
|
@Resource
|
|
|
|
|
@Resource // 自动注入StringRedisTemplate对象
|
|
|
|
|
private StringRedisTemplate stringRedisTemplate;
|
|
|
|
|
@Resource
|
|
|
|
|
@Resource // 自动注入UserMapper对象
|
|
|
|
|
private UserMapper userMapper;
|
|
|
|
|
@Resource
|
|
|
|
|
@Resource // 自动注入RoleMapper对象
|
|
|
|
|
private RoleMapper roleMapper;
|
|
|
|
|
@Resource
|
|
|
|
|
@Resource // 自动注入UserConverter对象
|
|
|
|
|
private UserConverter userConverter;
|
|
|
|
|
@Resource
|
|
|
|
|
@Resource // 自动注入ObjectMapper对象
|
|
|
|
|
private ObjectMapper objectMapper;
|
|
|
|
|
@Resource
|
|
|
|
|
@Resource // 自动注入JwtUtil对象
|
|
|
|
|
private JwtUtil jwtUtil;
|
|
|
|
|
@Resource
|
|
|
|
|
@Resource // 自动注入UserDailyLoginDurationMapper对象
|
|
|
|
|
private UserDailyLoginDurationMapper userDailyLoginDurationMapper;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 登录
|
|
|
|
|
* @param request
|
|
|
|
|
* @param loginForm 入参
|
|
|
|
|
* @return 响应
|
|
|
|
|
* @param request HTTP请求对象
|
|
|
|
|
* @param loginForm 登录表单对象,作为入参
|
|
|
|
|
* @return 响应结果,包含JWT令牌或错误信息
|
|
|
|
|
*/
|
|
|
|
|
@SneakyThrows(JsonProcessingException.class)
|
|
|
|
|
@Override
|
|
|
|
|
@SneakyThrows(JsonProcessingException.class) // 声明可能抛出的异常
|
|
|
|
|
@Override // 重写父类或接口中的方法
|
|
|
|
|
public Result<String> login(HttpServletRequest request, LoginForm loginForm) {
|
|
|
|
|
// 先判断用户是否通过校验
|
|
|
|
|
// 先判断用户是否通过校验(如验证码校验)
|
|
|
|
|
String s = stringRedisTemplate.opsForValue().get("isVerifyCode" + request.getSession().getId());
|
|
|
|
|
if (StringUtils.isBlank(s)) {
|
|
|
|
|
return Result.failed("请先验证验证码");
|
|
|
|
|
if (StringUtils.isBlank(s)) { // 如果验证码校验未通过
|
|
|
|
|
return Result.failed("请先验证验证码"); // 返回错误信息
|
|
|
|
|
}
|
|
|
|
|
// 根据用户名获取用户信息
|
|
|
|
|
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
|
|
|
|
|
wrapper.eq(User::getUserName, loginForm.getUsername());
|
|
|
|
|
User user = userMapper.selectOne(wrapper);
|
|
|
|
|
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>(); // 创建查询条件包装对象
|
|
|
|
|
wrapper.eq(User::getUserName, loginForm.getUsername()); // 设置查询条件为用户名等于登录表单中的用户名
|
|
|
|
|
User user = userMapper.selectOne(wrapper); // 执行查询并获取用户对象
|
|
|
|
|
|
|
|
|
|
// 判读用户名是否存在
|
|
|
|
|
if (Objects.isNull(user)) {
|
|
|
|
|
throw new UsernameNotFoundException("该用户不存在");
|
|
|
|
|
if (Objects.isNull(user)) { // 如果用户不存在
|
|
|
|
|
throw new UsernameNotFoundException("该用户不存在"); // 抛出用户名未找到异常
|
|
|
|
|
}
|
|
|
|
|
if(user.getIsDeleted() == 1){
|
|
|
|
|
return Result.failed("该用户已注销");
|
|
|
|
|
if(user.getIsDeleted() == 1){ // 如果用户已被注销
|
|
|
|
|
return Result.failed("该用户已注销"); // 返回错误信息
|
|
|
|
|
}
|
|
|
|
|
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
|
|
|
|
|
if (!encoder.matches(SecretUtils.desEncrypt(loginForm.getPassword()), user.getPassword())) {
|
|
|
|
|
return Result.failed("密码错误");
|
|
|
|
|
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); // 创建BCrypt密码编码器对象
|
|
|
|
|
if (!encoder.matches(SecretUtils.desEncrypt(loginForm.getPassword()), user.getPassword())) { // 如果密码不匹配
|
|
|
|
|
return Result.failed("密码错误"); // 返回错误信息
|
|
|
|
|
}
|
|
|
|
|
user.setPassword(null);
|
|
|
|
|
user.setPassword(null); // 清空用户密码(出于安全考虑)
|
|
|
|
|
// 根据用户Id获取权限
|
|
|
|
|
List<String> permissions = roleMapper.selectCodeById(user.getRoleId());
|
|
|
|
|
List<String> permissions = roleMapper.selectCodeById(user.getRoleId()); // 获取用户角色对应的权限列表
|
|
|
|
|
|
|
|
|
|
// 数据库获取的权限是字符串springSecurity需要实现GrantedAuthority接口类型,所有这里做一个类型转换
|
|
|
|
|
// 数据库获取的权限是字符串,Spring Security需要实现GrantedAuthority接口类型,所以这里做一个类型转换
|
|
|
|
|
List<SimpleGrantedAuthority> userPermissions = permissions.stream()
|
|
|
|
|
.map(permission -> new SimpleGrantedAuthority("role_" + permission)).toList();
|
|
|
|
|
.map(permission -> new SimpleGrantedAuthority("role_" + permission)).toList(); // 将权限字符串转换为SimpleGrantedAuthority对象列表
|
|
|
|
|
|
|
|
|
|
// 创建一个sysUserDetails对象,该类实现了UserDetails接口
|
|
|
|
|
SysUserDetails sysUserDetails = new SysUserDetails(user);
|
|
|
|
|
SysUserDetails sysUserDetails = new SysUserDetails(user); // 使用用户对象创建系统用户详情对象
|
|
|
|
|
// 把转型后的权限放进sysUserDetails对象
|
|
|
|
|
sysUserDetails.setPermissions(userPermissions);
|
|
|
|
|
sysUserDetails.setPermissions(userPermissions); // 设置用户权限
|
|
|
|
|
|
|
|
|
|
// 将用户信息转为字符串(此行代码缺少具体操作,可能是后续代码被省略或遗漏)
|
|
|
|
|
|
|
|
|
|
// 将用户信息转为字符串
|
|
|
|
|
String userInfo = objectMapper.writeValueAsString(user);
|
|
|
|
|
// 将user对象转换为JSON字符串,用于JWT载荷部分。
|
|
|
|
|
|
|
|
|
|
String token = jwtUtil.createJwt(userInfo, userPermissions.stream().map(String::valueOf).toList());
|
|
|
|
|
// 把token放到redis中
|
|
|
|
|
// 使用JWT工具创建JWT令牌,载荷为用户信息和用户权限列表(转换为字符串列表)。
|
|
|
|
|
|
|
|
|
|
// 把token放到redis中
|
|
|
|
|
stringRedisTemplate.opsForValue().set("token" + request.getSession().getId(), token, 30, TimeUnit.MINUTES);
|
|
|
|
|
// 将生成的JWT令牌存储到Redis中,键为"token"加上session ID,有效期为30分钟。
|
|
|
|
|
|
|
|
|
|
// 封装用户的身份信息,为后续的身份验证和授权操作提供必要的输入
|
|
|
|
|
// 创建UsernamePasswordAuthenticationToken 参数:用户信息,密码,权限列表
|
|
|
|
|
// 封装用户的身份信息,为后续的身份验证和授权操作提供必要的输入
|
|
|
|
|
// 创建UsernamePasswordAuthenticationToken 参数:用户信息,密码,权限列表
|
|
|
|
|
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
|
|
|
|
|
new UsernamePasswordAuthenticationToken(sysUserDetails, user.getPassword(), userPermissions);
|
|
|
|
|
// 创建一个UsernamePasswordAuthenticationToken对象,包含用户详细信息、密码(通常不存储明文密码,此处可能是示例)和权限列表。
|
|
|
|
|
|
|
|
|
|
// 可选,添加Web认证细节
|
|
|
|
|
// 可选,添加Web认证细节
|
|
|
|
|
usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
|
|
|
|
// 为认证令牌添加Web认证细节,如远程地址、会话ID等。
|
|
|
|
|
|
|
|
|
|
// 用户信息存放进上下文
|
|
|
|
|
// 用户信息存放进上下文
|
|
|
|
|
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
|
|
|
|
|
//用户信息放入
|
|
|
|
|
// 将认证令牌设置到安全上下文(SecurityContextHolder)中,供后续的身份验证和授权操作使用。
|
|
|
|
|
|
|
|
|
|
// 清除redis通过校验表示
|
|
|
|
|
// 清除redis通过校验表示
|
|
|
|
|
stringRedisTemplate.delete("isVerifyCode" + request.getSession().getId());
|
|
|
|
|
// 删除Redis中标记验证码已验证的键,避免重复使用。
|
|
|
|
|
|
|
|
|
|
return Result.success("登录成功", token);
|
|
|
|
|
}
|
|
|
|
|
// 返回登录成功的结果和JWT令牌。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public Result<String> logout(HttpServletRequest request) {
|
|
|
|
|
@Override
|
|
|
|
|
public Result<String> logout(HttpServletRequest request) {
|
|
|
|
|
// 重写logout方法。
|
|
|
|
|
|
|
|
|
|
// 清除session
|
|
|
|
|
HttpSession session = request.getSession(false);
|
|
|
|
|
// 清除session
|
|
|
|
|
HttpSession session = request.getSession(false);
|
|
|
|
|
// 获取当前请求的会话,如果不存在则返回null。
|
|
|
|
|
|
|
|
|
|
if (session != null) {
|
|
|
|
|
// 清除redis
|
|
|
|
|
stringRedisTemplate.delete("token" + session.getId());
|
|
|
|
|
session.invalidate();
|
|
|
|
|
}
|
|
|
|
|
if (session != null) {
|
|
|
|
|
// 清除redis
|
|
|
|
|
stringRedisTemplate.delete("token" + session.getId());
|
|
|
|
|
// 删除Redis中存储的JWT令牌。
|
|
|
|
|
session.invalidate();
|
|
|
|
|
// 使会话失效。
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return Result.success("退出成功");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@SneakyThrows(IOException.class)
|
|
|
|
|
@Override
|
|
|
|
|
public void getCaptcha(HttpServletRequest request, HttpServletResponse response) {
|
|
|
|
|
// 生成线性图形验证码的静态方法,参数:图片宽,图片高,验证码字符个数 干扰个数
|
|
|
|
|
LineCaptcha captcha = CaptchaUtil
|
|
|
|
|
.createLineCaptcha(200, 100, 4, 300);
|
|
|
|
|
|
|
|
|
|
// 把验证码存放进redis
|
|
|
|
|
// 获取验证码
|
|
|
|
|
String code = captcha.getCode();
|
|
|
|
|
String key = "code" + request.getSession().getId();
|
|
|
|
|
stringRedisTemplate.opsForValue().set(key, code, 5, TimeUnit.MINUTES);
|
|
|
|
|
// 把图片响应到输出流
|
|
|
|
|
response.setContentType("image/jpeg");
|
|
|
|
|
ServletOutputStream os = response.getOutputStream();
|
|
|
|
|
captcha.write(os);
|
|
|
|
|
os.close();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public Result<String> verifyCode(HttpServletRequest request, String code) {
|
|
|
|
|
String key = "code" + request.getSession().getId();
|
|
|
|
|
String rightCode = stringRedisTemplate.opsForValue().get(key);
|
|
|
|
|
if (StringUtils.isBlank(rightCode)) {
|
|
|
|
|
return Result.failed("验证码已过期");
|
|
|
|
|
return Result.success("退出成功");
|
|
|
|
|
// 返回退出成功的结果。
|
|
|
|
|
}
|
|
|
|
|
if (!rightCode.equalsIgnoreCase(code)) {
|
|
|
|
|
return Result.failed("验证码错误");
|
|
|
|
|
}
|
|
|
|
|
// 验证码校验后redis清除验证码,避免重复使用
|
|
|
|
|
stringRedisTemplate.delete(key);
|
|
|
|
|
// 验证码校验后redis存入校验成功,避免用户登录和注册时不验证验证码直接提交
|
|
|
|
|
stringRedisTemplate.opsForValue().set("isVerifyCode" + request.getSession().getId(), "1", 5, TimeUnit.MINUTES);
|
|
|
|
|
return Result.success("验证码校验成功");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public Result<String> register(HttpServletRequest request, UserForm userForm) {
|
|
|
|
|
|
|
|
|
|
String s = stringRedisTemplate.opsForValue().get("isVerifyCode" + request.getSession().getId());
|
|
|
|
|
if (StringUtils.isBlank(s)) {
|
|
|
|
|
return Result.failed("请先验证验证码");
|
|
|
|
|
}
|
|
|
|
|
if (!SecretUtils.desEncrypt(userForm.getPassword()).equals(SecretUtils.desEncrypt(userForm.getCheckedPassword()))) {
|
|
|
|
|
return Result.failed("两次密码不一致");
|
|
|
|
|
@SneakyThrows(IOException.class)
|
|
|
|
|
@Override
|
|
|
|
|
public void getCaptcha(HttpServletRequest request, HttpServletResponse response) {
|
|
|
|
|
// 重写getCaptcha方法,用于生成验证码。
|
|
|
|
|
|
|
|
|
|
// 生成线性图形验证码的静态方法,参数:图片宽,图片高,验证码字符个数 干扰个数
|
|
|
|
|
LineCaptcha captcha = CaptchaUtil
|
|
|
|
|
.createLineCaptcha(200, 100, 4, 300);
|
|
|
|
|
// 创建一个线性图形验证码。
|
|
|
|
|
|
|
|
|
|
// 把验证码存放进redis
|
|
|
|
|
// 获取验证码
|
|
|
|
|
String code = captcha.getCode();
|
|
|
|
|
// 获取生成的验证码字符串。
|
|
|
|
|
String key = "code" + request.getSession().getId();
|
|
|
|
|
// 构造Redis键,包含会话ID。
|
|
|
|
|
stringRedisTemplate.opsForValue().set(key, code, 5, TimeUnit.MINUTES);
|
|
|
|
|
// 将验证码存储到Redis中,有效期为5分钟。
|
|
|
|
|
|
|
|
|
|
// 把图片响应到输出流
|
|
|
|
|
response.setContentType("image/jpeg");
|
|
|
|
|
// 设置响应内容类型为JPEG图像。
|
|
|
|
|
ServletOutputStream os = response.getOutputStream();
|
|
|
|
|
// 获取响应的输出流。
|
|
|
|
|
captcha.write(os);
|
|
|
|
|
// 将验证码图片写入输出流。
|
|
|
|
|
os.close();
|
|
|
|
|
// 关闭输出流。
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
User user = userConverter.fromToEntity(userForm);
|
|
|
|
|
@Override
|
|
|
|
|
public Result<String> verifyCode(HttpServletRequest request, String code) {
|
|
|
|
|
// 重写verifyCode方法,用于验证用户输入的验证码。
|
|
|
|
|
|
|
|
|
|
user.setPassword(new BCryptPasswordEncoder().encode(SecretUtils.desEncrypt(user.getPassword())));
|
|
|
|
|
user.setRoleId(1);
|
|
|
|
|
userMapper.insert(user);
|
|
|
|
|
// 注册成功把redis的是否通过校验验证码删除,防止用户注册后立马登录,还可以使用
|
|
|
|
|
stringRedisTemplate.delete("isVerifyCode" + request.getSession().getId());
|
|
|
|
|
return Result.success("注册成功");
|
|
|
|
|
}
|
|
|
|
|
String key = "code" + request.getSession().getId();
|
|
|
|
|
// 构造Redis键,包含会话ID。
|
|
|
|
|
String rightCode = stringRedisTemplate.opsForValue().get(key);
|
|
|
|
|
// 从Redis中获取正确的验证码。
|
|
|
|
|
|
|
|
|
|
if (StringUtils.isBlank(rightCode)) {
|
|
|
|
|
return Result.failed("验证码已过期");
|
|
|
|
|
}
|
|
|
|
|
// 如果Redis中没有验证码,则返回验证码已过期的结果。
|
|
|
|
|
|
|
|
|
|
if (!rightCode.equalsIgnoreCase(code)) {
|
|
|
|
|
return Result.failed("验证码错误");
|
|
|
|
|
}
|
|
|
|
|
// 如果用户输入的验证码与Redis中的不匹配,则返回验证码错误的结果。
|
|
|
|
|
|
|
|
|
|
// 验证码校验后redis清除验证码,避免重复使用
|
|
|
|
|
stringRedisTemplate.delete(key);
|
|
|
|
|
// 删除Redis中的验证码。
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 用户发送心跳,更新最后活跃时间。
|
|
|
|
|
*
|
|
|
|
|
* @return
|
|
|
|
|
*/
|
|
|
|
|
@Override
|
|
|
|
|
@SneakyThrows(value = JsonProcessingException.class)
|
|
|
|
|
public Result<String> sendHeartbeat(HttpServletRequest request) {
|
|
|
|
|
|
|
|
|
|
String key = HEARTBEAT_KEY_PREFIX + SecurityUtil.getUserId();
|
|
|
|
|
String lastHeartbeatStr = stringRedisTemplate.opsForValue().getAndDelete(key);
|
|
|
|
|
LocalDateTime now = LocalDateTime.now(ZoneOffset.UTC);
|
|
|
|
|
stringRedisTemplate.opsForValue().set(key, now.toString());
|
|
|
|
|
if (lastHeartbeatStr != null) {
|
|
|
|
|
LocalDateTime lastHeartbeat = LocalDateTime.parse(lastHeartbeatStr);
|
|
|
|
|
Duration durationSinceLastHeartbeat = Duration.between(lastHeartbeat, LocalDateTime.now(ZoneOffset.UTC));
|
|
|
|
|
LocalDate date = DateTimeUtil.getDate();
|
|
|
|
|
// 实现累加逻辑,比如更新数据库中的记录
|
|
|
|
|
LambdaQueryWrapper<UserDailyLoginDuration> userDailyLoginDurationLambdaQueryWrapper = new LambdaQueryWrapper<>();
|
|
|
|
|
userDailyLoginDurationLambdaQueryWrapper.eq(UserDailyLoginDuration::getUserId,SecurityUtil.getUserId())
|
|
|
|
|
.eq(UserDailyLoginDuration::getLoginDate, date);
|
|
|
|
|
List<UserDailyLoginDuration> userDailyLoginDurations =
|
|
|
|
|
userDailyLoginDurationMapper.selectList(userDailyLoginDurationLambdaQueryWrapper);
|
|
|
|
|
if(userDailyLoginDurations.isEmpty()){
|
|
|
|
|
UserDailyLoginDuration userDailyLoginDuration = new UserDailyLoginDuration();
|
|
|
|
|
userDailyLoginDuration.setUserId(SecurityUtil.getUserId());
|
|
|
|
|
userDailyLoginDuration.setLoginDate(date);
|
|
|
|
|
userDailyLoginDuration.setTotalSeconds(0);
|
|
|
|
|
userDailyLoginDurationMapper.insert(userDailyLoginDuration);
|
|
|
|
|
}else {
|
|
|
|
|
UserDailyLoginDuration userDailyLoginDuration = new UserDailyLoginDuration();
|
|
|
|
|
userDailyLoginDuration.setTotalSeconds(userDailyLoginDurations.get(0)
|
|
|
|
|
.getTotalSeconds()+(int)durationSinceLastHeartbeat.getSeconds());
|
|
|
|
|
userDailyLoginDuration.setId(userDailyLoginDurations.get(0).getId());
|
|
|
|
|
userDailyLoginDurationMapper.updateById(userDailyLoginDuration);
|
|
|
|
|
// 验证码校验后redis存入校验成功,避免用户登录和注册时不验证验证码直接提交
|
|
|
|
|
stringRedisTemplate.opsForValue().set("isVerifyCode" + request.getSession().getId(), "1", 5, TimeUnit.MINUTES);
|
|
|
|
|
// 在Redis中标记验证码已验证,有效期为5分钟。
|
|
|
|
|
|
|
|
|
|
return Result.success("验证码校验成功");
|
|
|
|
|
// 返回验证码校验成功的结果。
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public Result<String> register(HttpServletRequest request, UserForm userForm) {
|
|
|
|
|
// 重写register方法,用于用户注册。
|
|
|
|
|
|
|
|
|
|
String s = stringRedisTemplate.opsForValue().get("isVerifyCode" + request.getSession().getId());
|
|
|
|
|
// 检查Redis中是否有验证码已验证的标记。
|
|
|
|
|
|
|
|
|
|
if (StringUtils.isBlank(s)) {
|
|
|
|
|
return Result.failed("请先验证验证码");
|
|
|
|
|
}
|
|
|
|
|
// 如果没有,则返回请先验证验证码的结果。
|
|
|
|
|
|
|
|
|
|
if (!SecretUtils.desEncrypt(userForm.getPassword()).equals(SecretUtils.desEncrypt(userForm.getCheckedPassword()))) {
|
|
|
|
|
return Result.failed("两次密码不一致");
|
|
|
|
|
}
|
|
|
|
|
// 验证用户输入的两次密码是否一致。
|
|
|
|
|
|
|
|
|
|
User user = userConverter.fromToEntity(userForm);
|
|
|
|
|
// 将用户表单转换为用户实体。
|
|
|
|
|
|
|
|
|
|
user.setPassword(new BCryptPasswordEncoder().encode(SecretUtils.desEncrypt(user.getPassword())));
|
|
|
|
|
// 对用户密码进行加密。
|
|
|
|
|
|
|
|
|
|
user.setRoleId(1);
|
|
|
|
|
// 设置用户角色ID(此处为示例,通常根据用户选择或系统策略设置)。
|
|
|
|
|
|
|
|
|
|
userMapper.insert(user);
|
|
|
|
|
// 将用户实体插入数据库。
|
|
|
|
|
|
|
|
|
|
// 注册成功把redis的是否通过校验验证码删除,防止用户注册后立马登录,还可以使用
|
|
|
|
|
stringRedisTemplate.delete("isVerifyCode" + request.getSession().getId());
|
|
|
|
|
// 删除Redis中验证码已验证的标记,防止重复注册。
|
|
|
|
|
|
|
|
|
|
return Result.success("注册成功");
|
|
|
|
|
// 返回注册成功的结果。
|
|
|
|
|
}
|
|
|
|
|
ArrayList<String> permissions = new ArrayList<>();
|
|
|
|
|
permissions.add(SecurityUtil.getRole());
|
|
|
|
|
SysUserDetails principal = (SysUserDetails) (SecurityContextHolder.getContext().getAuthentication().getPrincipal());
|
|
|
|
|
User user = principal.getUser();
|
|
|
|
|
String string = objectMapper.writeValueAsString(user);
|
|
|
|
|
String jwt = jwtUtil.createJwt(string, permissions);
|
|
|
|
|
stringRedisTemplate.opsForValue().set("token" + request.getSession().getId(), jwt, 30, TimeUnit.MINUTES);
|
|
|
|
|
return Result.success("请求成功",jwt);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 用户发送心跳,更新最后活跃时间。
|
|
|
|
|
*
|
|
|
|
|
* @return
|
|
|
|
|
*/
|
|
|
|
|
@Override
|
|
|
|
|
@SneakyThrows(value = JsonProcessingException.class)
|
|
|
|
|
public Result<String> sendHeartbeat(HttpServletRequest request) {
|
|
|
|
|
// 重写sendHeartbeat方法,用于用户发送心跳以更新最后活跃时间。
|
|
|
|
|
|
|
|
|
|
String key = HEARTBEAT_KEY_PREFIX + SecurityUtil.getUserId();
|
|
|
|
|
// 构造Redis键,包含用户ID和前缀。
|
|
|
|
|
|
|
|
|
|
String lastHeartbeatStr = stringRedisTemplate.opsForValue().getAndDelete(key);
|
|
|
|
|
// 获取并删除Redis中存储的上一次心跳时间。
|
|
|
|
|
|
|
|
|
|
LocalDateTime now = LocalDateTime.now(ZoneOffset.UTC);
|
|
|
|
|
// 获取当前时间(UTC时区)。
|
|
|
|
|
|
|
|
|
|
stringRedisTemplate.opsForValue().set(key, now.toString());
|
|
|
|
|
// 将当前时间存储到Redis中作为新的心跳时间。
|
|
|
|
|
|
|
|
|
|
if (lastHeartbeatStr != null) {
|
|
|
|
|
LocalDateTime lastHeartbeat = LocalDateTime.parse(lastHeartbeatStr);
|
|
|
|
|
// 解析上一次心跳时间。
|
|
|
|
|
|
|
|
|
|
Duration durationSinceLastHeartbeat = Duration.between(lastHeartbeat, LocalDateTime.now(ZoneOffset.UTC));
|
|
|
|
|
// 计算上一次心跳到现在的持续时间。
|
|
|
|
|
|
|
|
|
|
LocalDate date = DateTimeUtil.getDate();
|
|
|
|
|
// 获取当前日期。
|
|
|
|
|
|
|
|
|
|
// 实现累加逻辑,比如更新数据库中的记录
|
|
|
|
|
LambdaQueryWrapper<UserDailyLoginDuration> userDailyLoginDurationLambdaQueryWrapper = new LambdaQueryWrapper<>();
|
|
|
|
|
// 构造查询条件。
|
|
|
|
|
|
|
|
|
|
userDailyLoginDurationLambdaQueryWrapper.eq(UserDailyLoginDuration::getUserId,SecurityUtil.getUserId())
|
|
|
|
|
.eq(UserDailyLoginDuration::getLoginDate, date);
|
|
|
|
|
// 设置查询条件为用户ID和登录日期。
|
|
|
|
|
|
|
|
|
|
List<UserDailyLoginDuration> userDailyLoginDurations =
|
|
|
|
|
userDailyLoginDurationMapper.selectList(userDailyLoginDurationLambdaQueryWrapper);
|
|
|
|
|
// 查询用户当日的登录时长记录。
|
|
|
|
|
|
|
|
|
|
if(userDailyLoginDurations.isEmpty()){
|
|
|
|
|
UserDailyLoginDuration userDailyLoginDuration = new UserDailyLoginDuration();
|
|
|
|
|
// 如果没有记录,则创建新记录。
|
|
|
|
|
|
|
|
|
|
userDailyLoginDuration.setUserId(SecurityUtil.getUserId());
|
|
|
|
|
userDailyLoginDuration.setLoginDate(date);
|
|
|
|
|
userDailyLoginDuration.setTotalSeconds(0);
|
|
|
|
|
userDailyLoginDurationMapper.insert(userDailyLoginDuration);
|
|
|
|
|
// 设置用户ID、登录日期和初始时长,并插入数据库。
|
|
|
|
|
}else {
|
|
|
|
|
UserDailyLoginDuration userDailyLoginDuration = new UserDailyLoginDuration();
|
|
|
|
|
// 如果有记录
|