POST用户登录

GET获取用户登录信息
GET获取验证码
GET用户退出
响应拦截器完成
This commit is contained in:
Bunny 2023-12-13 11:00:10 +08:00
parent f93ca911cf
commit e3ffa18eff
16 changed files with 372 additions and 34 deletions

View File

@ -1,3 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="MavenProjectsManager">

View File

@ -0,0 +1,17 @@
package cn.bunny.exception;
import cn.bunny.spzx.model.vo.common.ResultCodeEnum;
import lombok.Data;
@Data
public class BunnyException extends RuntimeException {
private Integer code;
private String message;
private ResultCodeEnum resultCodeEnum;
public BunnyException(ResultCodeEnum resultCodeEnum) {
this.resultCodeEnum = resultCodeEnum;
this.code = resultCodeEnum.getCode();
this.message = resultCodeEnum.getMessage();
}
}

View File

@ -0,0 +1,25 @@
package cn.bunny.exception;
import cn.bunny.spzx.model.vo.common.Result;
import cn.bunny.spzx.model.vo.common.ResultCodeEnum;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
@ControllerAdvice
public class GlobalExceptionHandler {
// 全局异常处理
@ExceptionHandler(Exception.class)
@ResponseBody
public Result error() {
return Result.build(null, ResultCodeEnum.SYSTEM_ERROR);
}
// 自定义异常处理
@ExceptionHandler
@ResponseBody
public Result error(BunnyException bunnyException) {
return Result.build(null, bunnyException.getResultCodeEnum());
}
}

View File

@ -0,0 +1,23 @@
package cn.bunny;
import cn.bunny.spzx.model.entity.system.SysUser;
public class AuthContextUtil {
// 创建ThreadLocal
private static final ThreadLocal<SysUser> threadLocal = new ThreadLocal<>();
// 添加数据库
public static void set(SysUser sysUser) {
threadLocal.set(sysUser);
}
// 获取数据
public static SysUser get() {
return threadLocal.get();
}
// 删除数据
public static void remove() {
threadLocal.remove();
}
}

View File

@ -0,0 +1,33 @@
package cn.bunny.config;
import cn.bunny.interceptor.LoginAuthInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Component
public class WebMvcConfiguration implements WebMvcConfigurer {
@Autowired
private LoginAuthInterceptor loginAuthInterceptor;
// 拦截器注册
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginAuthInterceptor)
.excludePathPatterns("/admin/system/index/login", "/admin.system/index/generateValidateCode")
.addPathPatterns("/**");
}
// 解决跨域
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowCredentials(true)
.allowedOriginPatterns("*")
.allowedMethods("*")
.allowedHeaders("*");
}
}

View File

@ -1,17 +1,17 @@
package cn.bunny.controller;
import cn.bunny.service.SysUserService;
import cn.bunny.service.ValidateCodeService;
import cn.bunny.spzx.model.dto.system.LoginDto;
import cn.bunny.spzx.model.entity.system.SysUser;
import cn.bunny.spzx.model.vo.common.Result;
import cn.bunny.spzx.model.vo.common.ResultCodeEnum;
import cn.bunny.spzx.model.vo.system.LoginVo;
import cn.bunny.spzx.model.vo.system.ValidateCodeVo;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
@Tag(name = "用户接口")
@RestController
@ -20,11 +20,40 @@ public class IndexController {
@Autowired
private SysUserService sysUserService;
@Autowired
private ValidateCodeService validateCodeService;
// 用户登录
@Operation(summary = "用户登录")
@Operation(summary = "用户登录", description = "用户登录获取token")
@PostMapping("login")
public Result login(@RequestBody LoginDto loginDto) {
LoginVo loginVo = sysUserService.login(loginDto);
return Result.build(loginVo, ResultCodeEnum.SUCCESS);
return Result.build(loginVo, ResultCodeEnum.LOGIN_SUCCESS);
}
// 生成验证码
@Operation(summary = "获取验证码", description = "会获得两个值codeKey + codeValue")
@GetMapping(value = "/generateValidateCode")
public Result<ValidateCodeVo> generateValidateCode() {
ValidateCodeVo validateCodeVo = validateCodeService.generateValidateCode();
return Result.build(validateCodeVo, ResultCodeEnum.SUCCESS);
}
// 获取用户登录信息
@Operation(summary = "获取用户登录信息", description = "获取token")
@GetMapping(value = "getUserInfo")
// public Result getUserInfo(HttpServletRequest httpServletRequest) {
// String token = httpServletRequest.getHeader("token");
public Result getUserInfo(@RequestHeader(name = "token") String token) {
SysUser sysUser = sysUserService.getUserInfo(token);
return Result.build(sysUser, ResultCodeEnum.SUCCESS);
}
// 用户退出
@Operation(summary = "用户退出", description = "清除Redis中token")
@GetMapping(".logout")
public Result logout(@RequestHeader(name = "token") String token) {
sysUserService.logout(token);
return Result.build(null, ResultCodeEnum.SUCCESS);
}
}

View File

@ -0,0 +1,99 @@
package cn.bunny.interceptor;
import cn.bunny.AuthContextUtil;
import cn.bunny.spzx.model.entity.system.SysUser;
import cn.bunny.spzx.model.vo.common.Result;
import cn.bunny.spzx.model.vo.common.ResultCodeEnum;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.concurrent.TimeUnit;
@Component
public class LoginAuthInterceptor implements HandlerInterceptor {
@Autowired
private RedisTemplate<String, String> redisTemplate;
/**
* 响应208给前端
*
* @param response
*/
public void respondNoLoginInfo(HttpServletResponse response) {
Result<Object> result = Result.build(null, ResultCodeEnum.LOGIN_AUTH);
PrintWriter writer = null;
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html; charset=utf-8");
try {
writer = response.getWriter();
writer.println(JSON.toJSONString(result));
} catch (IOException e) {
e.printStackTrace();
} finally {
if (writer != null) writer.close();
}
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
/**
* 1. 获取骑牛方式
* - 如果请求方式是option 预检请求直接做放行
* 2. 从请求中获取token
* 3. 如果token为空返回错误提示
* 4. 如果token不为空拿着token查询redis
* 5. 如果redis查询不到数据返回错误提示
* 6. 如果redis查询用户信息把用户信息放到ThreadLocal
* 7. 更新redis更新时间
* 放行
*/
// 1. 获取骑牛方式
String method = request.getMethod();
if ("OPTIONS".equals(method)) {
return true;
}
// 2. 从请求中获取token
String token = request.getHeader("token");
// 3. 如果token为空返回错误提示
if (StrUtil.isEmpty(token)) {
respondNoLoginInfo((response));
return false;
}
// 4. 如果token不为空拿着token查询redis
String userinfoString = redisTemplate.opsForValue().get("user:login" + token);
// 如果redis查询不到数据返回错误提示
if (StrUtil.isEmpty(userinfoString)) {
respondNoLoginInfo(response);
return false;
}
// 如果redis查询到用户信息将信息放到ThreadLocal中
SysUser sysUser = JSON.parseObject(userinfoString, SysUser.class);
AuthContextUtil.set(sysUser);
// 吧redis用户信息数据更新过期时间
redisTemplate.expire("user:login" + token, 30, TimeUnit.MINUTES);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
AuthContextUtil.remove();
}
}

View File

@ -0,0 +1,12 @@
package cn.bunny;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.List;
@Data
@ConfigurationProperties(prefix = "spzx.auth")
public class properties {
private List<String> noAuthUrls;
}

View File

@ -1,9 +1,16 @@
package cn.bunny.service;
import cn.bunny.spzx.model.dto.system.LoginDto;
import cn.bunny.spzx.model.entity.system.SysUser;
import cn.bunny.spzx.model.vo.system.LoginVo;
public interface SysUserService {
// 用户登录
LoginVo login(LoginDto loginDto);
// 获取用户token
SysUser getUserInfo(String token);
// 用户退出
void logout(String token);
}

View File

@ -0,0 +1,7 @@
package cn.bunny.service;
import cn.bunny.spzx.model.vo.system.ValidateCodeVo;
public interface ValidateCodeService {
ValidateCodeVo generateValidateCode();
}

View File

@ -1,11 +1,14 @@
package cn.bunny.service.impl;
import cn.bunny.exception.BunnyException;
import cn.bunny.mapper.SysUserMapper;
import cn.bunny.service.SysUserService;
import cn.bunny.spzx.model.dto.system.LoginDto;
import cn.bunny.spzx.model.entity.system.SysUser;
import cn.bunny.spzx.model.vo.common.ResultCodeEnum;
import cn.bunny.spzx.model.vo.system.LoginVo;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
@ -23,9 +26,26 @@ public class SysUserServiceImpl implements SysUserService {
@Autowired
private RedisTemplate<String, String> redisTemplate;
// 用户登录
@Override
public LoginVo login(LoginDto loginDto) {
// 判断验证码是否正确
String captcha = loginDto.getCaptcha();
String codeKey = loginDto.getCodeKey();
String redisCode = redisTemplate.opsForValue().get("user:validate" + codeKey);
System.out.println("redisCode" + redisCode);
System.out.println("captcha" + captcha);
// 比较验证码
if (StrUtil.isEmpty(redisCode) || !StrUtil.equalsIgnoreCase(redisCode, captcha)) {
redisTemplate.delete("user:validate" + codeKey);
throw new BunnyException(ResultCodeEnum.VALIDATECODE_ERROR);
}
// 如果一致删除Redis中验证码
redisTemplate.delete("user:validate" + codeKey);
// 获取用户提交用户名LoginDto获取到
String userName = loginDto.getUserName();
@ -34,24 +54,36 @@ public class SysUserServiceImpl implements SysUserService {
// 如果用户名查询不到对应信息用户不存在返回错误信息
if (sysUser == null) {
throw new RuntimeException("用户名不存在");
throw new BunnyException(ResultCodeEnum.LOGIN_ERROR_USERNAME);// 用户名不存在
}
// 获取输入密码比较输入密码和数据库密码是否一致
String databases_password = sysUser.getPassword();
String input_password = DigestUtils.md5DigestAsHex(loginDto.getPassword().getBytes());
if (!input_password.equals(databases_password)) {
throw new RuntimeException("密码不正确");
throw new BunnyException(ResultCodeEnum.LOGIN_ERROR_PASSWORD);// 密码不正确
}
String token = UUID.randomUUID().toString().replaceAll("-", "");
redisTemplate.opsForValue().set("user:login" + token, JSON.toJSONString(sysUser), 7, TimeUnit.DAYS);
LoginVo loginVo = new LoginVo();
loginVo.setToken(token);
return loginVo;
}
// 获取用户token
@Override
public SysUser getUserInfo(String token) {
String userJson = redisTemplate.opsForValue().get("user:login" + token);
SysUser sysUser = JSON.parseObject(userJson, SysUser.class);
return sysUser;
}
// 用户退出
@Override
public void logout(String token) {
redisTemplate.delete("user:login" + token);
}
}

View File

@ -0,0 +1,37 @@
package cn.bunny.service.impl;
import cn.bunny.service.ValidateCodeService;
import cn.bunny.spzx.model.vo.system.ValidateCodeVo;
import cn.hutool.captcha.CaptchaUtil;
import cn.hutool.captcha.CircleCaptcha;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@Service
public class ValidateCodeServiceIImpl implements ValidateCodeService {
@Autowired
private RedisTemplate<String, String> redisTemplate;
// 生成验证码
@Override
public ValidateCodeVo generateValidateCode() {
// 使用hutool生成验证码
CircleCaptcha circleCaptcha = CaptchaUtil.createCircleCaptcha(150, 40, 4, 20);
String code = circleCaptcha.getCode();
String imageBase64 = circleCaptcha.getImageBase64();
// 放到Redis
String uuid = UUID.randomUUID().toString().replaceAll("-", "");
redisTemplate.opsForValue().set("user:validate" + uuid, code, 5, TimeUnit.MINUTES);
// 返回 validateCodeVo
ValidateCodeVo validateCodeVo = new ValidateCodeVo();
validateCodeVo.setCodeKey(uuid);
validateCodeVo.setCodeValue("data:image/png;base64," + imageBase64);
return validateCodeVo;
}
}

View File

@ -3,3 +3,13 @@ spring:
name: server-manager
profiles:
active: dev
server:
servlet:
context-path: /api
spzx:
auth:
noAuthUrls:
- /admin/system/index/login
- /admin/system/index/generateValidateCode

View File

@ -1,30 +1,33 @@
package cn.bunny.spzx.model.vo.common;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
@Getter // 提供获取属性值的getter方法
public enum ResultCodeEnum {
SUCCESS(200 , "操作成功") ,
LOGIN_ERROR(201 , "用户名或者密码错误"),
VALIDATECODE_ERROR(202 , "验证码错误") ,
LOGIN_AUTH(208 , "用户未登录"),
USER_NAME_IS_EXISTS(209 , "用户名已经存在"),
SYSTEM_ERROR(9999 , "您的网络有问题请稍后重试"),
NODE_ERROR( 217, "该节点下有子节点,不可以删除"),
SUCCESS(200, "操作成功"),
LOGIN_SUCCESS(200, "登录成功"),
LOGIN_ERROR(201, "用户名或者密码错误"),
LOGIN_ERROR_USERNAME(201, "用户名错误"),
LOGIN_ERROR_PASSWORD(201, "密码错误"),
VALIDATECODE_ERROR(202, "验证码错误"),
LOGIN_AUTH(208, "用户未登录"),
USER_NAME_IS_EXISTS(209, "用户名已经存在"),
SYSTEM_ERROR(9999, "您的网络有问题请稍后重试"),
NODE_ERROR(217, "该节点下有子节点,不可以删除"),
DATA_ERROR(204, "数据异常"),
ACCOUNT_STOP( 216, "账号已停用"),
ACCOUNT_STOP(216, "账号已停用"),
STOCK_LESS(219, "库存不足");
STOCK_LESS( 219, "库存不足"),
@Schema(description = "业务状态码")
private final Integer code; // 业务状态码
@Schema(description = "响应消息")
private final String message; // 响应消息
;
private Integer code ; // 业务状态码
private String message ; // 响应消息
private ResultCodeEnum(Integer code , String message) {
this.code = code ;
this.message = message ;
ResultCodeEnum(Integer code, String message) {
this.code = code;
this.message = message;
}
}

View File

@ -2,14 +2,17 @@ package cn.bunny.spzx.model.vo.h5;
import cn.bunny.spzx.model.entity.product.Category;
import cn.bunny.spzx.model.entity.product.ProductSku;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
@Data
@Schema(description = "商品类")
public class IndexVo {
private List<Category> categoryList ; // 一级分类的类别数据
private List<ProductSku> productSkuList ; // 畅销商品列表数据
@Schema(description = "一级分类数据")
private List<Category> categoryList; // 一级分类的类别数据
@Schema(description = "畅销商品列表数据")
private List<ProductSku> productSkuList; // 畅销商品列表数据
}

View File

@ -8,9 +8,9 @@ import lombok.Data;
public class ValidateCodeVo {
@Schema(description = "验证码key")
private String codeKey ; // 验证码的key
private String codeKey;
@Schema(description = "验证码value")
private String codeValue ; // 图片验证码对应的字符串数据
private String codeValue;
}