Cookie+Redis+自定义参数解析器+AOP+自定义校验注解实现鉴权+改动CustomException
文章目录
- 1.数据库表设计
- 2.基础环境搭建
- 1.目录
- 2.MD5Util.java 加密加盐工具类
- 3.CookieUtil.java
- 4.其余的都是使用EasyCode自动生成的,不再赘述
- 5.测试是否可以访问
- 3.用户注册
- 1.LoginUserController.java
- 2.UserConstant.java 用户常量
- 3.LoginUserReq.java 请求
- 4.LoginUserService.java
- 5.LoginUserServiceImpl.java
- 6.测试
- 4.用户登录基本实现
- 1.目录
- 2.LoginUserController.java 用户传递用户名和密码
- 2.LoginUserService.java
- 3.LoginUserServiceImpl.java 将token -> userId存储到redis中,并将token也放到cookie中
- 4.常量 UserConstant.java
- 5.测试
- 1.用户登录,cookie会被设置token
- 2.redis中也会被设置token
- 5.使用自定义参数解析器,来校验登录并封装用户信息
- 1.目录
- 2.UserValidation.java 用户参数校验的实体类
- 3.LoginUserController.java 注意不要加@RequestParam、@RequestBody、@PathVariable 等注解
- 4.LoginUserService.java
- 5.LoginUserServiceImpl.java 校验并返回用户信息
- 6.UserArgumentResolver.java 自定义参数解析器
- 7.WebConfig.java 将自定义参数解析器加入到spring容器中
- 8.测试
- 6.使用AOP+自定义注解完成身份校验
- 1.目录
- 2.AuthCheck.java 自定义身份校验注解
- 3.UserRoleEnum.java 用户角色枚举
- 4.AuthInterceptor.java aop拦截注解
- 5.UserRoleEnum.java 用户角色枚举
- 6.CustomException.java 自定义异常类修改
- 7.RespBeanEnum.java 响应枚举
- 8.测试
- 1.登录校验使用UserValidation参数解析器,身份校验使用AuthCheck注解
- 2.校验失败情况
- 3.校验成功情况
1.数据库表设计
-- auto-generated definition
create table login_user
(id bigint auto_increment comment '用户ID'primary key,nickname varchar(255) default '默认昵称' not null comment '用户昵称',password varchar(32) not null comment '密码(MD5)',salt varchar(10) not null comment '盐值',role varchar(10) not null comment '角色',create_by varchar(64) null comment '创建人',create_time timestamp null comment '创建时间',update_by varchar(64) null comment '更新人',update_time timestamp null comment '更新时间',is_deleted tinyint null comment '逻辑删除标识(0.未删除 1.已删除)'
)charset = utf8mb4;
2.基础环境搭建
1.目录
2.MD5Util.java 加密加盐工具类
package com.sunxiansheng.user.validation.utils;import org.apache.commons.codec.digest.DigestUtils;/*** Description: MD5加密工具类** @Author sun* @Create 2024/5/5 14:23* @Version 1.1*/
public class MD5Util {/*** 将一个字符串转换为MD5** @param src 原始字符串* @return 加密后的MD5字符串*/public static String md5(String src) {return DigestUtils.md5Hex(src);}/*** 使用用户提供的盐值进行一次加密加盐处理** @param inputPass 用户输入的密码* @param salt 用户提供的盐值* @return 加盐并加密后的字符串*/public static String encryptWithSalt(String inputPass, String salt) {// 使用盐值对输入的密码进行加盐处理String saltedInput = saltString(inputPass, salt);// 使用MD5对加盐后的字符串进行加密return md5(saltedInput);}/*** 生成加盐后的字符串** @param input 原始字符串* @param salt 用户提供的盐值* @return 加盐后的字符串*/private static String saltString(String input, String salt) {// 简单地使用盐值的第一个字符和最后一个字符与输入字符串拼接return salt.charAt(0) + input + salt.charAt(salt.length() - 1);}/*** 测试* @param args*/public static void main(String[] args) {String password = "123456";String sun = encryptWithSalt(password, "sun");System.out.println("sun = " + sun);}
}
3.CookieUtil.java
package com.sunxiansheng.user.validation.utils;import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;/*** Description: Cookie工具类** @Author sun* @Create 2024/5/6 16:04* @Version 1.0*/public class CookieUtil {/*** 得到 Cookie 的值, 不编码** @param request* @param cookieName* @return*/public static String getCookieValue(HttpServletRequest request, StringcookieName) {return getCookieValue(request, cookieName, false);}/*** 得到 Cookie 的值, *** @param request* @param cookieName* @return*/public static String getCookieValue(HttpServletRequest request, StringcookieName, boolean isDecoder) {Cookie[] cookieList = request.getCookies();if (cookieList == null || cookieName == null) {return null;}String retValue = null;try {for (int i = 0; i < cookieList.length; i++) {if (cookieList[i].getName().equals(cookieName)) {if (isDecoder) {retValue = URLDecoder.decode(cookieList[i].getValue(), "UTF-8");} else {retValue = cookieList[i].getValue();}break;}}} catch (UnsupportedEncodingException e) {e.printStackTrace();}return retValue;}/*** 得到 Cookie 的值, *** @param request* @param cookieName* @param encodeString* @return*/public static String getCookieValue(HttpServletRequest request, StringcookieName, String encodeString) {Cookie[] cookieList = request.getCookies();if (cookieList == null || cookieName == null) {return null;}String retValue = null;try {for (int i = 0; i < cookieList.length; i++) {if (cookieList[i].getName().equals(cookieName)) {retValue = URLDecoder.decode(cookieList[i].getValue(), encodeString);break;}}} catch (UnsupportedEncodingException e) {e.printStackTrace();}return retValue;}/*** 设置 Cookie 的值 不设置生效时间默认浏览器关闭即失效,也不编码*/public static void setCookie(HttpServletRequest request, HttpServletResponseresponse, String cookieName, String cookieValue) {setCookie(request, response, cookieName, cookieValue, -1);}/*** 设置 Cookie 的值 在指定时间内生效,但不编码*/public static void setCookie(HttpServletRequest request, HttpServletResponseresponse, String cookieName, String cookieValue, int cookieMaxage) {setCookie(request, response, cookieName, cookieValue, cookieMaxage,false);}/*** 设置 Cookie 的值 不设置生效时间,但编码*/public static void setCookie(HttpServletRequest request, HttpServletResponseresponse, String cookieName, String cookieValue, boolean isEncode) {setCookie(request, response, cookieName, cookieValue, -1, isEncode);}/*** 设置 Cookie 的值 在指定时间内生效, 编码参数*/public static void setCookie(HttpServletRequest request, HttpServletResponseresponse, String cookieName, String cookieValue, int cookieMaxage, booleanisEncode) {doSetCookie(request, response, cookieName, cookieValue, cookieMaxage,isEncode);}/*** 设置 Cookie 的值 在指定时间内生效, 编码参数(指定编码)*/public static void setCookie(HttpServletRequest request, HttpServletResponseresponse, String cookieName, String cookieValue, int cookieMaxage, StringencodeString) {doSetCookie(request, response, cookieName, cookieValue, cookieMaxage, encodeString);}/*** 删除 Cookie 带 cookie 域名*/public static void deleteCookie(HttpServletRequest request, HttpServletResponse response, String cookieName) {doSetCookie(request, response, cookieName, "", -1, false);}/*** 设置 Cookie 的值,并使其在指定时间内生效** @param cookieMaxage cookie 生效的最大秒数*/private static final void doSetCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, String cookieValue,int cookieMaxage, boolean isEncode) {try {if (cookieValue == null) {cookieValue = "";} else if (isEncode) {cookieValue = URLEncoder.encode(cookieValue, "utf-8");}Cookie cookie = new Cookie(cookieName, cookieValue);if (cookieMaxage > 0) {cookie.setMaxAge(cookieMaxage);}// if (null != request) {// 设置域名的 cookie// String domainName = getDomainName(request);// if (!"localhost".equals(domainName)) {// cookie.setDomain(domainName);// }// }cookie.setPath("/");response.addCookie(cookie);} catch (Exception e) {e.printStackTrace();}}/*** 设置 Cookie 的值,并使其在指定时间内生效** @param cookieMaxage cookie 生效的最大秒数*/private static final void doSetCookie(HttpServletRequest request,HttpServletResponse response, String cookieName, String cookieValue,int cookieMaxage, String encodeString) {try {if (cookieValue == null) {cookieValue = "";} else {cookieValue = URLEncoder.encode(cookieValue, encodeString);}Cookie cookie = new Cookie(cookieName, cookieValue);if (cookieMaxage > 0) {cookie.setMaxAge(cookieMaxage);}if (null != request) {// 设置域名的 cookieString domainName = getDomainName(request);System.out.println(domainName);if (!"localhost".equals(domainName)) {cookie.setDomain(domainName);}}cookie.setPath("/");response.addCookie(cookie);} catch (Exception e) {e.printStackTrace();}}/*** 得到 cookie 的域名*/private static final String getDomainName(HttpServletRequest request) {String domainName = null;// 通过 request 对象获取访问的 url 地址String serverName = request.getRequestURL().toString();if ("".equals(serverName)) {domainName = "";} else {// 将 url 地下转换为小写serverName = serverName.toLowerCase();// 如果 url 地址是以 http://开头 将 http://截取if (serverName.startsWith("http://")) {serverName = serverName.substring(7);}int end = serverName.length();// 判断 url 地址是否包含"/"if (serverName.contains("/")) {// 得到第一个"/"出现的位置end = serverName.indexOf("/");}// 截取serverName = serverName.substring(0, end);// 根据"."进行分割final String[] domains = serverName.split("\\.");int len = domains.length;if (len > 3) {// www.abc.com.cndomainName = domains[len - 3] + "." + domains[len - 2] + "." +domains[len - 1];} else if (len > 1) {// abc.com or abc.cndomainName = domains[len - 2] + "." + domains[len - 1];} else {domainName = serverName;}}if (domainName.indexOf(":") > 0) {String[] ary = domainName.split("\\:");domainName = ary[0];}return domainName;}
}
4.其余的都是使用EasyCode自动生成的,不再赘述
5.测试是否可以访问
3.用户注册
1.LoginUserController.java
package com.sunxiansheng.user.validation.controller;import com.sunxiansheng.response.Result;
import com.sunxiansheng.user.validation.entity.req.LoginUserReq;
import com.sunxiansheng.user.validation.service.LoginUserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;/*** (LoginUser)控制层** @author sun* @since 2024-08-10 13:24:05*/
@RestController
@RequestMapping("/login/user")
@Slf4j
public class LoginUserController {/*** 服务对象*/@Resourceprivate LoginUserService loginUserService;@ResponseBody@RequestMapping("/register")public Result<Boolean> register(@Valid @RequestBody LoginUserReq loginUserReq) {try {Boolean flag = loginUserService.register(loginUserReq);return Result.ok(flag);} catch (Exception e) {if (log.isErrorEnabled()) {log.error("用户注册失败!错误原因{}", e.getMessage(), e);}return Result.fail("注册失败!");}}}
2.UserConstant.java 用户常量
package com.sunxiansheng.user.validation.constant;/*** 用户常量*/
public interface UserConstant {/*** 默认角色*/String DEFAULT_ROLE = "user";/*** 管理员角色*/String ADMIN_ROLE = "admin";/*** 被封号*/String BAN_ROLE = "ban";}
3.LoginUserReq.java 请求
package com.sunxiansheng.user.validation.entity.req;import lombok.Data;
import lombok.experimental.Accessors;import javax.validation.constraints.NotBlank;
import java.io.Serializable;/*** (LoginUser)Req实体类:接受前端请求** @author sun* @since 2024-08-10 13:26:50*/
@Data
@Accessors(chain = true) // 支持链式调用
public class LoginUserReq implements Serializable {private static final long serialVersionUID = 1L;/*** 密码(MD5)*/@NotBlank(message = "密码不能为空")private String password;}
4.LoginUserService.java
package com.sunxiansheng.user.validation.service;import com.sunxiansheng.user.validation.entity.req.LoginUserReq;/*** (LoginUser)service接口** @author sun* @since 2024-08-10 13:24:05*/
public interface LoginUserService {/*** 注册** @param loginUserReq* @return*/public Boolean register(LoginUserReq loginUserReq);}
5.LoginUserServiceImpl.java
package com.sunxiansheng.user.validation.service.impl;import com.sunxiansheng.user.validation.constant.UserConstant;
import com.sunxiansheng.user.validation.entity.po.LoginUserPo;
import com.sunxiansheng.user.validation.entity.req.LoginUserReq;
import com.sunxiansheng.user.validation.mapper.LoginUserMapper;
import com.sunxiansheng.user.validation.service.LoginUserService;
import com.sunxiansheng.user.validation.utils.MD5Util;
import org.springframework.stereotype.Service;import javax.annotation.Resource;
import java.util.Date;
import java.util.UUID;/*** (LoginUser)service实现类** @author sun* @since 2024-08-10 13:24:05*/
@Service("loginUserService")
public class LoginUserServiceImpl implements LoginUserService {@Resourceprivate LoginUserMapper loginUserMapper;/*** 注册** @param loginUserReq* @return*/@Overridepublic Boolean register(LoginUserReq loginUserReq) {// 获取信息String password = loginUserReq.getPassword();// 使用uuid作为用户idString userId = UUID.randomUUID().toString().replace("-", "");// 密码加密加盐// 这里使用固定的盐测试String salt = "sun";String userPassword = MD5Util.encryptWithSalt(password, salt);// 构造poLoginUserPo user = new LoginUserPo().setNickname(userId).setPassword(userPassword).setSalt(salt).setRole(UserConstant.DEFAULT_ROLE).setCreateBy(userId).setCreateTime(new Date()).setIsDeleted(0);// 插入数据库int insert = loginUserMapper.insert(user);return insert > 0;}
}
6.测试
4.用户登录基本实现
1.目录
2.LoginUserController.java 用户传递用户名和密码
/*** 用户登录*/@ResponseBody@RequestMapping("/login")public Result<Boolean> login(@RequestParam String nickname, @RequestParam String password, HttpServletRequest request, HttpServletResponse response) {try {LoginUserReq loginUserReq = new LoginUserReq().setNickname(nickname).setPassword(password);Boolean flag = loginUserService.login(loginUserReq, request, response);return Result.ok(flag);} catch (Exception e) {if (log.isErrorEnabled()) {log.error("用户登录失败!错误原因{}", e.getMessage(), e);}return Result.fail("登录失败!");}}
2.LoginUserService.java
/*** 登录** @param loginUserReq* @return*/Boolean login(LoginUserReq loginUserReq, HttpServletRequest request, HttpServletResponse response);
3.LoginUserServiceImpl.java 将token -> userId存储到redis中,并将token也放到cookie中
/*** 登录** @param loginUserReq* @return*/@Overridepublic Boolean login(LoginUserReq loginUserReq, HttpServletRequest request, HttpServletResponse response) {// 获取信息(这里的nickname就相当于唯一标识了,其实应该是另一个字段比如userId,这里设计的不是很好)String userId = loginUserReq.getNickname();String password = loginUserReq.getPassword();// 首先查询有没有该用户LoginUserPo loginUserPo = new LoginUserPo().setNickname(userId);List<LoginUserPo> loginUserPos = loginUserMapper.queryAllByLimit(loginUserPo);// 如果没有该用户,直接返回if (CollectionUtils.isEmpty(loginUserPos)) {throw new RuntimeException("用户不存在,请先注册!");}LoginUserPo userPo = loginUserPos.get(0);// 日志if (log.isInfoEnabled()) {log.info("login user:{}", userPo);}// 将密码加密后与数据库中的密码比对String encryptWithSalt = MD5Util.encryptWithSalt(password, userPo.getSalt());if (!encryptWithSalt.equals(userPo.getPassword())) {throw new RuntimeException("密码错误!");}// 登录成功则将用户信息存入redis// 1.生成一个随机的uuid作为token来标识用户String token = UUID.randomUUID().toString().replace("-", "");String tokenKey = redisUtil.buildKey(UserConstant.USER_TOKEN_PREFIX, token);// 2.将用户的userId存入redisredisUtil.set(tokenKey, userId);// 3.将这个token放到Cookie中CookieUtil.setCookie(request, response, "token", token);// 解释:目前redis中存着token.uuid -> userId,然后cookie中有token -> uuid// 这样就可以从cookie中拿到uuid从而从redis中拿到userIdreturn true;}
4.常量 UserConstant.java
5.测试
1.用户登录,cookie会被设置token
2.redis中也会被设置token
5.使用自定义参数解析器,来校验登录并封装用户信息
1.目录
2.UserValidation.java 用户参数校验的实体类
package com.sunxiansheng.user.validation.entity.calibration;import lombok.Data;
import lombok.experimental.Accessors;import java.io.Serializable;
import java.util.Date;/*** Description: 用户参数校验的实体类** @Author sun* @Create 2024/8/10 15:53* @Version 1.0*/
@Data
@Accessors(chain = true) // 支持链式调用
public class UserValidation implements Serializable {private static final long serialVersionUID = 1L;/*** 用户ID*/private Long id;/*** 用户昵称*/private String nickname;/*** 密码(MD5)*/private String password;/*** 盐值*/private String salt;/*** 角色*/private String role;/*** 创建人*/private String createBy;/*** 创建时间*/private Date createTime;/*** 更新人*/private String updateBy;/*** 更新时间*/private Date updateTime;/*** 逻辑删除标识(0.未删除 1.已删除)*/private Integer isDeleted;}
3.LoginUserController.java 注意不要加@RequestParam、@RequestBody、@PathVariable 等注解
因为他们的作用是告诉 Spring MVC 以特定方式解析该参数,例如从请求体、请求参数或路径变量中提取数据
/*** 测试使用自定义参数解析器获取用户信息*/@ResponseBody@RequestMapping("/getUserInfo")public Result<UserValidation> test(UserValidation userValidation) {return Result.ok(userValidation);}
4.LoginUserService.java
/*** 校验并返回用户信息*/UserValidation verifyAndGetUserInformation(HttpServletRequest request, HttpServletResponse response);
5.LoginUserServiceImpl.java 校验并返回用户信息
/*** 校验并返回用户信息* @param request* @param response* @return*/@Overridepublic UserValidation verifyAndGetUserInformation(HttpServletRequest request, HttpServletResponse response) {// 获取Cookie中的tokenString token = CookieUtil.getCookieValue(request, "token");// 从redis中获取userIdString tokenKey = redisUtil.buildKey(UserConstant.USER_TOKEN_PREFIX, token);String userId = redisUtil.get(tokenKey, String.class);// 从数据库中获取用户信息LoginUserPo loginUserPo = new LoginUserPo().setNickname(userId);List<LoginUserPo> loginUserPos = loginUserMapper.queryAllByLimit(loginUserPo);// 如果有该用户,则返回UserValidationif (!CollectionUtils.isEmpty(loginUserPos)) {LoginUserPo userPo = loginUserPos.get(0);// 将po转为UserValidationreturn new UserValidation().setId(userPo.getId()).setNickname(userPo.getNickname()).setPassword(userPo.getPassword()).setSalt(userPo.getSalt()).setRole(userPo.getRole()).setCreateBy(userPo.getCreateBy()).setCreateTime(userPo.getCreateTime()).setUpdateBy(userPo.getUpdateBy()).setUpdateTime(userPo.getUpdateTime()).setIsDeleted(userPo.getIsDeleted());}// 如果没有该用户,直接返回return null;}
6.UserArgumentResolver.java 自定义参数解析器
package com.sunxiansheng.user.validation.config;import com.sunxiansheng.exception.CustomException;
import com.sunxiansheng.exception.response.RespBeanEnum;
import com.sunxiansheng.user.validation.entity.calibration.UserValidation;
import com.sunxiansheng.user.validation.service.LoginUserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;/*** Description: 用户参数解析器** @Author sun* @Create 2024/8/10 16:10* @Version 1.0*/
@Component
@Slf4j
public class UserArgumentResolver implements HandlerMethodArgumentResolver {@Resourceprivate LoginUserService loginUserService;/** 判断是否支持要转换的参数类型,简单来说,就是在这里设置要解析的参数类型*/@Overridepublic boolean supportsParameter(MethodParameter methodParameter) {// 只解析UserValidation类型的参数if (methodParameter.getParameterType() == UserValidation.class) {return true;}return false;}/*** 编写参数解析的逻辑** @param methodParameter* @param modelAndViewContainer* @param nativeWebRequest* @param webDataBinderFactory* @return* @throws Exception*/@Overridepublic Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {// 首先获取request和responseHttpServletRequest request = nativeWebRequest.getNativeRequest(HttpServletRequest.class);HttpServletResponse response = nativeWebRequest.getNativeResponse(HttpServletResponse.class);// 进行校验并获取用户信息UserValidation userValidation = loginUserService.verifyAndGetUserInformation(request, response);// 日志log.info("解析参数成功,参数为:{}", userValidation);// 如果校验失败就直接抛出自定义异常if (userValidation == null) {throw new CustomException(RespBeanEnum.USER_NOT_LOGIN);}return userValidation;}}
7.WebConfig.java 将自定义参数解析器加入到spring容器中
package com.sunxiansheng.user.validation.config;import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import javax.annotation.Resource;
import java.util.List;/*** Description: 将自定义参数解析器加入到spring容器中** @Author sun* @Create 2024/8/10 16:18* @Version 1.0*/
@Configuration
public class WebConfig implements WebMvcConfigurer {@Resourceprivate UserArgumentResolver userArgumentResolver;/*** 将自定义参数解析器加入到spring容器中* @param resolvers*/@Overridepublic void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {resolvers.add(userArgumentResolver);}}
8.测试
6.使用AOP+自定义注解完成身份校验
1.目录
2.AuthCheck.java 自定义身份校验注解
package com.sunxiansheng.user.validation.annotation;import com.sunxiansheng.user.validation.enums.UserRoleEnum;import java.lang.annotation.*;/*** 权限校验注解** @author sunxiansheng*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AuthCheck {/*** 必须具有的角色,默认为普通用户*/UserRoleEnum[] mustRoles() default UserRoleEnum.USER;}
3.UserRoleEnum.java 用户角色枚举
package com.sunxiansheng.user.validation.enums;import lombok.Getter;import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;/*** 用户角色枚举** @author sunxiansheng*/
@Getter
public enum UserRoleEnum {USER(0, "普通用户"),ADMIN(1, "管理员"),BAN(2, "被封号"),;private int code;private String desc;UserRoleEnum(int code, String desc) {this.code = code;this.desc = desc;}/*** 将枚举转换为map(静态初始化)*/public static Map<Integer, UserRoleEnum> channelEnumMap = Stream.of(UserRoleEnum.values()).collect(Collectors.toMap(UserRoleEnum::getCode, Function.identity()));/*** 根据code来获取枚举*/public static UserRoleEnum getByCode(int code) {return channelEnumMap.get(code);}}
4.AuthInterceptor.java aop拦截注解
package com.sunxiansheng.user.validation.aop;import com.sunxiansheng.exception.CustomException;
import com.sunxiansheng.exception.response.RespBeanEnum;
import com.sunxiansheng.user.validation.annotation.AuthCheck;
import com.sunxiansheng.user.validation.entity.calibration.UserValidation;
import com.sunxiansheng.user.validation.enums.UserRoleEnum;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;/*** Description: 权限校验AOP** @Author sun* @Create 2024/8/11 12:11* @Version 1.0*/
@Component
@Aspect
@Slf4j
public class AuthInterceptor {/*** 拦截注解所在的方法** @param joinPoint* @param authCheck* @return* @throws Throwable*/@Around("@annotation(authCheck)")public Object doInterceptor(ProceedingJoinPoint joinPoint, AuthCheck authCheck) throws Throwable {// 获取注解中的值UserRoleEnum[] userRoleEnums = authCheck.mustRoles();// 获取方法信息Object[] args = joinPoint.getArgs();for (Object arg : args) {if (arg instanceof UserValidation) {// 存储用户角色UserRoleEnum userRole = null;// 直接获取用户信息UserValidation userValidation = (UserValidation) arg;// 这里其实存储code比较好,由于在数据库中存储的是String,所以这里简单做一下映射,正常情况下应该是使用code来获取对应的枚举String role = userValidation.getRole();// 简单做一下映射,为了测试if ("user".equals(role)) {userRole = UserRoleEnum.USER;} else if ("admin".equals(role)) {userRole = UserRoleEnum.ADMIN;} else if ("ban".equals(role)) {userRole = UserRoleEnum.BAN;}// 日志打印用户应该有的权限和用户的权限,并打印该接口的全类名log.info("用户应该有的权限:{},用户的权限:{},接口:{}", userRoleEnums, userRole, joinPoint.getSignature());// 遍历注解中的值,判断是否有权限for (UserRoleEnum userRoleEnum : userRoleEnums) {if (userRoleEnum == userRole) {return joinPoint.proceed();}}// 如果没有,则打印该接口的全类名以及用户应该有的权限和用户的权限log.error("接口角色权限校验异常:{},接口应有权限:{},用户权限:{}", joinPoint.getSignature(), userRoleEnums, userRole);throw new CustomException(RespBeanEnum.USER_ROLE_VALIDATION_EXCEPTION);}}// 判断是否有UserValidation类型的参数,如果没有则抛出自定义异常,提示未校验用户登录throw new CustomException(RespBeanEnum.UNCHECKED_LOGIN);}}
5.UserRoleEnum.java 用户角色枚举
package com.sunxiansheng.user.validation.enums;import lombok.Getter;import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;/*** 用户角色枚举** @author sunxiansheng*/
@Getter
public enum UserRoleEnum {USER(0, "普通用户"),ADMIN(1, "管理员"),BAN(2, "被封号"),;private int code;private String desc;UserRoleEnum(int code, String desc) {this.code = code;this.desc = desc;}/*** 将枚举转换为map(静态初始化)*/public static Map<Integer, UserRoleEnum> channelEnumMap = Stream.of(UserRoleEnum.values()).collect(Collectors.toMap(UserRoleEnum::getCode, Function.identity()));/*** 根据code来获取枚举*/public static UserRoleEnum getByCode(int code) {return channelEnumMap.get(code);}}
6.CustomException.java 自定义异常类修改
package com.sunxiansheng.exception;import com.sunxiansheng.exception.response.RespBeanEnum;
import lombok.Data;
import lombok.NoArgsConstructor;/*** Description: 自定义异常类,具有响应枚举的属性,并允许传递自定义的消息。** @Author sun* @Create 2024/5/6 15:15* @Version 1.1*/
@Data
@NoArgsConstructor
public class CustomException extends RuntimeException {private RespBeanEnum respBeanEnum;private String customMessage;/*** 如果只传入枚举,那么使用枚举的信息,作为异常信息** @param respBeanEnum*/public CustomException(RespBeanEnum respBeanEnum) {super(respBeanEnum != null ? respBeanEnum.getMessage() : null);this.respBeanEnum = respBeanEnum;}/*** 如果传入枚举和自定义消息,那么使用自定义消息,作为异常信息** @param respBeanEnum* @param customMessage*/public CustomException(RespBeanEnum respBeanEnum, String customMessage) {super(customMessage != null ? customMessage : (respBeanEnum != null ? respBeanEnum.getMessage() : null));this.respBeanEnum = respBeanEnum;this.customMessage = customMessage;}@Overridepublic String getMessage() {if (customMessage != null) {return customMessage;}return respBeanEnum != null ? respBeanEnum.getMessage() : super.getMessage();}@Overridepublic String toString() {return "CustomException{" +"respBeanEnum=" + (respBeanEnum != null ? respBeanEnum.toString() : "null") +", customMessage='" + customMessage + '\'' +'}';}
}
7.RespBeanEnum.java 响应枚举
8.测试
1.登录校验使用UserValidation参数解析器,身份校验使用AuthCheck注解
2.校验失败情况
3.校验成功情况
相关文章:
Cookie+Redis+自定义参数解析器+AOP+自定义校验注解实现鉴权+改动CustomException
文章目录 1.数据库表设计2.基础环境搭建1.目录2.MD5Util.java 加密加盐工具类3.CookieUtil.java4.其余的都是使用EasyCode自动生成的,不再赘述5.测试是否可以访问 3.用户注册1.LoginUserController.java2.UserConstant.java 用户常量3.LoginUserReq.java 请求4.Logi…...
低代码开发 实战转型案例一览
数字浪潮澎湃,企业应用开发需求呈井喷之势。传统全栈开发虽底蕴深厚,然其漫长周期与高昂成本,难以追赶市场快速交付的急切步伐。无代码与低代码平台顺势崛起,宛如暗夜明灯,吸引非技术人员纷至沓来,投身应用…...
Spring Boot实战:构建一个简单的RESTful API
Spring Boot是一个开源框架,旨在简化Spring应用的创建与开发过程。通过Spring Boot,你可以轻松地创建独立、生产级的Spring应用,而不需要复杂的配置。本教程将带领大家一步一步构建一个简单的RESTful API,演示Spring Boot的核心功…...
电力场景配网缺陷系列之销钉缺失检测数据集VOC+YOLO格式3095张2类别
数据集格式:Pascal VOC格式YOLO格式(不包含分割路径的txt文件,仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数):3095 标注数量(xml文件个数):3095 标注数量(txt文件个数):3095 …...
FastAPI中的数据库应用介绍
FastAPI中的数据库应用介绍 FastAPI 是一个高性能和易用的日现应用框架,在构建数据库操作时同样具备高效性和完善性。本文将分为初级和高级应用,帮助你熟练从基础到高级的数据库操作。 一、初级应用 1.安装和配置 FastAPI 通常搭配 ORM 工具来操作数据…...
wordpres当前分类调用父分类的名称和链接
在WordPress中,如果你想在当前分类页面调用并显示父分类的名称和链接,你可以使用以下代码片段: <?php // 获取当前分类的ID $cat_id get_queried_object_id();// 获取当前分类的父分类ID $parent_id get_term($cat_id, category)->…...
自动驾驶3D目标检测综述(六)
停更了好久终于回来了(其实是因为博主去备考期末了hh) 这一篇接着(五)的第七章开始讲述第八章的内容。第八章主要介绍的是三维目标检测的高效标签。 目录 第八章 三维目标检测高效标签 一、域适应 (一)…...
RDFS—RDF模型属性扩展解析
目录 前言1. 什么是RDFS?1.1 RDFS的核心概念1.2 RDFS与RDF的区别 2. RDFS的基础概念2.1 类(Class)2.2 属性(Property)2.3 关系(Relation)2.4 定义域(Domain)2.5 值域&…...
应用层2——FTP文件传输协议
FTP文件传输协议 FTP文件传输协议 TFTP简单文件传输协议 提供不同种类主机系统(软硬件体系不同)之间的文件传输能力 即屏蔽底层操作系统的差异 FTP以C/S模式进行服务,FTP使用TCP协议传输 FTP服务器进程: 一个主进程,n个从属进…...
《探索PyTorch计算机视觉:原理、应用与实践》
《探索PyTorch计算机视觉:原理、应用与实践》 一、PyTorch 与计算机视觉的奇妙相遇二、核心概念解析(一)张量:计算机视觉的数据基石(二)神经网络:视觉任务的智慧大脑(三)…...
【Linux学习五】时间日期指令与查找指令
目录 一、时间日期指令 1.1 显示日期 1.2 设置日期 1.3 日历指令 二、搜索查找类指令 2.1 find指令 2.2 locate指令 2.3 grep指令和管道符号| 一、时间日期指令 1.1 显示日期 date (功能描述:显示当前时间)date %Y࿰…...
程序环境和预处理
程序环境和预处理 1.程序环境1.1如何从test.c到test.exe1.2.翻译环境1.2.1翻译过程1.2.2详细过程 1.3.运行环境 2.预处理2.1 预定义符号2.2 define2.2.1 define定义标识符2.2.2 define定义宏2.2.3 带副作用的宏参数2.2.4 宏和函数的对比 2.3 #undef2.4 命令行定义2.5 条件编译2…...
SpringCloud整合skywalking实现链路追踪和日志采集
1.部署skywalking https://blog.csdn.net/qq_40942490/article/details/144701194 2.添加依赖 <!-- 日志采集 --><dependency><groupId>org.apache.skywalking</groupId><artifactId>apm-toolkit-logback-1.x</artifactId><version&g…...
【Java】面试题 并发安全 (2)
文章目录 可重入锁(ReentrantLock)知识总结1. 可重入锁概念与特点2. 基本语法与使用注意事项3. 底层实现原理4. 面试回答要点 synchronized与lock的区别死锁相关面试题讲解死锁产生的四个条件ConcurrentHashMap2. JDK1.7的ConcurrentHashMap结构添加数据…...
面试场景题系列:设计URL短链
1.场景需求界定 1.缩短URL:提供一个长URL,返回一个短很多的URL。 2.重定向URL:提供一个缩短了的URL,重定向到原URL。 3.高可用、可扩展性和容错性考量。 •写操作:每天生成1亿个URL。 •每秒的写操作数:…...
C++模版
一.函数模版 1.定义:用来产生一组重组的函数,这些函数具有共同的逻辑和操作步骤,但其输入和输出的数据类型可以不一样 2.模版的语法形式 3.例子: template<typename T> T maximum(T value1, T value2, T value3) {T maxi…...
【zookeeper核心源码解析】第二课:俯瞰QuorumPeer启动核心流程,实现选举关键流程
系列文章目录 【zookeeper核心源码解析】第一课:zk启动类核心流程序列图 【zookeeper核心源码解析】第二课:俯瞰QuorumPeer启动核心流程,实现选举关键流程 【zookeeper核心源码解析】第三课:leader与follower何时开始同步&#…...
深度学习中的参数初始化
深度学习中的参数初始化主要是指初始化神经网络中的权重和偏置。权重和偏置通常分开初始化,偏置通常初始化为零或较小的常数值。 没有一种万能的初始化技术,因为最佳初始化可能因具体架构和要解决的问题而异。因此,尝试不同的初始化技术以了解…...
构建全志 T113 Tina SDK
1、环境配置: 准备一个 Ubuntu 系统,可以是 WSL,虚拟机等,建议版本是 20.04。 1.1、安装必要的软件 进入系统后,输入下方命令安装需要的工具 : sudo apt update -y sudo apt full-upgrade -y sudo apt i…...
yolov5 yolov6 yolov7 yolov8 yolov9目标检测、目标分类 目标切割 性能对比
文章目录 YOLOv1-YOLOv8之间的对比如下表所示:一、YOLO算法的核心思想1. YOLO系列算法的步骤2. Backbone、Neck和Head 二、YOLO系列的算法1.1 模型介绍1.2 网络结构1.3 实现细节1.4 性能表现 2. YOLOv2(2016)2.1 改进部分2.2 网络结构 3. YOL…...
经典问题——华测
1、没有 token 的情况下,接口的自动化测试可以通过哪些方式处理? 确认接口是否有其他认证机制: 如果使用的是 Basic Auth、Session ID 等传统方式,可以在请求中直接传递相关认证信息(如用户名和密码、Cookie 等&…...
【视觉惯性SLAM:十二、ORB-SLAM2:局部建图线程】
局部建图线程是ORB-SLAM2的核心模块之一,其主要任务是以局部关键帧为中心,维护和优化局部地图,并不断生成新的地图点,使得地图能够逐渐扩展和保持鲁棒性。以下将从具体的几个方面介绍ORB-SLAM2中的局部建图线程,包括处…...
Spring Boot对访问密钥加解密——HMAC-SHA256
HMAC-SHA256 简介 HMAC-SHA256 是一种基于 哈希函数 的消息认证码(Message Authentication Code, MAC),它结合了哈希算法(如 SHA-256)和一个密钥,用于验证消息的完整性和真实性。 HMAC 是 “Hash-based M…...
Linux高级--2.4.2 linux TCP 系列操作函数 -- 深层理解
一、操作函数简介 在 Linux 中,TCP(传输控制协议)操作涉及多种系统调用和函数,通常用来创建套接字、连接、发送/接收数据、关闭连接等。以下是一些常用的 TCP 操作函数和它们的简要说明: 1. socket() 函数原型: int…...
小程序租赁系统开发指南与实现策略
内容概要 在如今这个快节奏的时代,小程序租赁系统的开发正逐渐成为许多商家提升服务质量与效率的重要选择。在设计这样一个系统时,首先要明白它的核心目标:便捷、安全。用户希望在最短的时间内找到需要的物品,而商家则希望通过这…...
威胁建模助力企业“建防御 抓运营”
本文为安全知识图谱技术白皮书《践行安全知识图谱,携手迈进认知智能》精华解读系列第六篇——威胁建模技术,重点介绍基于知识图谱的威胁建模应用。 如何做好威胁建模 如今,随着技术的变化,攻防技术不对等和攻防双方关注面不同&a…...
soular使用教程
用 soular 配置你的组织,工作更高效!以下是快速上手的简单步骤:  1. 账号管理 可以对账号信息进行多方面管理,包括分配不同的部门、用户组等,从而确保账号权限和职责的清晰分配。  1.1 用…...
WPF编程excel表格操作
WPF编程excel表格操作 摘要NPOI安装封装代码测试代码 摘要 Excel操作几种方式 使用开源库NPOI(常用,操作丰富)使用Microsoft.Office.Interop.Excel COM组件(兼容性问题)使用OpenXml(效率高)使用OleDb(过时) NPOI安装 封装代码 using System; using System.IO; u…...
银河麒麟操作系统安装达梦数据库(超详细)
目录 引言1. 前期准备1.1 安装麒麟系统1.2 下载达梦数据库安装包(DM8)1.3 上传安装包到麒麟系统1.4 挂载安装包(iso)文件1.5 配置安装用户和组1.6 创建安装路径及修改权限1.7 设置临时安装目录 2. 安装达梦数据库(DM8&…...
SpringCloudAlibaba实战入门之路由网关Gateway初体验(十一)
Spring Cloud 原先整合 Zuul 作为网关组件,Zuul 由 Netflix 公司提供的,现在已经不维护了。后面 Netflix 公司又出来了一个 Zuul2.0 网关,但由于一直没有发布稳定版本,所以 Spring Cloud 等不及了就自己推出一个网关,已经不打算整合 zuul2.0 了。 一、什么是网关 1、顾明…...
struct sock
struct sock是套接口在网络层的表示,它包含了套接字在网络通信中的各种状态和参数。以下是对struct sock的详细解析: 定义与位置 struct sock通常在内核源代码的某个头文件中定义,例如include/net/sock.h。它是网络通信中非常重要的数据结构,用于在内核中表示一个套接字。…...
77、将adaface的mtcnn模型npy文件转成atlas310p模型,并进行推理
基本思想:将adaface的mtcnn模型npy文件转成atlas310p模型进行推理。同时比对结果 ubuntu@ubuntu:~$ git clone https://github.com/mk-minchul/AdaFace.git Cloning into AdaFace... remote: Enumerating objects: 236, done. remote: Counting objects: 100% (109/109), don…...
Docker应用-项目部署及DockerCompose
文章目录 Docker应用-项目部署1. 项目部署-后端1.1 修改配置1.2 项目打包1.3 编写Dockerfile1.4 创建镜像1.5 创建并运行容器1.6 测试 2. 项目部署-前端2.1 html前端静态目录2.2 nginx.config编写2.3 部署宿主机服务器2.4 创建容器并挂载2.5 测试 3. DockerCompose3.1 基本语法…...
Java重要面试名词整理(十一):网络编程
文章目录 概念网络协议计算机网络是什么?定义和分类计算机网络发展简史 计算机网络体系结构OSI七层模型TCP/IP模型TCP/IP协议族IP、TCP和UDPARPTCP/IP网络传输中的数据地址和端口号**端口号的确定** TCP特性TCP三次握手TCP四次挥手(分手) UDP…...
html + css 淘宝网实战
之前有小伙伴说,淘宝那么牛逼你会写代码,能帮我做一个一样的淘宝网站吗,好呀,看我接下来如何给你做一个淘宝首页。hahh,开个玩笑。。。学习而已。 在进行html css编写之前 先了解下网页的组成和网页元素的尺寸吧 1.网页的组成 …...
Linux打包压缩解压 --- 打包tar命令(归档)
一、tar命令打包(归档) 在 Linux 中,tar 是一种用于归档文件的工具。通过此命令可将多个文件或目录组合成单个档案文件,可以搭配gzip和bzip等压缩命令让文件体积更小,在配置服务器前备份服务器现有配置,会…...
流架构的读书笔记(2)
流架构的读书笔记(2) 一、建模工具之一沃德利地图 推测技术的发展,交流和辩论思想的最有力的方法是沃德利地图 沃德利地图的制作步骤 1确定范围和用户需求 2确定满足用户需求所需的组件 3在一条范围从全新到被人们接受的演进轴上评估这些组成 部分的演…...
Xshell远程连接提示“找不到匹配的host key算法“问题处理
1.问题描述 Xshell连接远程服务器node1,提示找不到匹配的host key算法;但是用同机房的其他服务器可以使用ssh连接到这个node1机器; 2.问题处理 问题处理尝试了很多方法,可能大家遇到的有所不同,可以尝试一下本文中的…...
Qt C++关于QSpinBox、QDoubleSpinBox的输入框内鼠标点击事件无法触发截取信号的解决办法
项目场景: 基于Qt C 开发鼠标点击spinbox的触发任务时,我基于QSpinBox继承开发了一个新类,用于弹出自定义键盘。 问题描述 在鼠标点击spinbox的边框以及上下键的时候,能够触发覆写的mousePressEvent,但是一旦鼠标点击…...
【ES6复习笔记】Symbol 类型及其应用(9)
一、Symbol 简介 Symbol 是 JavaScript 中的一种基本数据类型,它表示唯一的标识符。Symbol 的主要目的是防止属性名冲突,尤其是在多个代码库或模块中共享对象时。Symbol 值可以用作对象的属性名,这样可以确保属性名是唯一的,不会…...
深度学习笔记(4)——视频理解
视频理解 视频理解的问题:视频太大了 解决方案:在切片上训练,低FPS,低分辨率 测试的时候:在不同的clips上运行模型,取平均预测结果 视频由图片序列组成: 单帧CNN模型 训练普通的2D CNN模型,对每一帧进行分类,通常是视频分类的一个非常强的基线方法。 Late Fusio…...
Flink定时器
flink的定时器都是基于事件时间(event time)或事件处理时间(processing time)的变化来触发响应的。对一部分新手玩家来说,可能不清楚事件时间和事件处理时间的区别。我这里先说一下我的理解,防止下面懵逼。…...
springboot餐厅点餐系统丨源码+数据库+万字文档+PPT
作者简介: 作者:学姐 开发技术:SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等 文末获取“源码数据库万字文档PPT”,支持远程部署调试、运行安装。 技术框架 开发语言:Java 框架:springbo…...
记一个itertools排列组合和列表随机排序的例子
朋友不知道哪里弄来了一长串单词列表,一定要搞个单词不重复的组合。那么这个时候我们就可以想到读书时所学的排列组合知识了,而这个在Python中可以怎么实现呢?我记录如下: 使用itertools模块实现排列组合 在 Python 中ÿ…...
Intent--组件通信
组件通信1 获取子活动的返回值 创建Activity时实现自动注册!【Activity必须要注册才能使用】 默认 LinearLayout 布局,注意 xml 中约束布局的使用; 若需要更改 线性布局 只需要将标签更改为 LinearLayout 即可,记得 设置线性布局…...
数据分析与应用:如何分析7日动销率和滞销率?
目录 0 需求描述 1 数据准备 1.1 订单明细表 1.2 商品信息表 2 SQL实现 3 问题分析与总结...
信息系统管理工程第8章思维导图
软考信管第8章的思维导图也实在是太长了,制作的耗时远超过之前的预计。给你看看思维导图的全貌如下,看看你能够在手机上滚动多少个屏幕 当你看到这段文字的时候,证明你把思维导图从上到下看完了,的确很长吧,第8章的教程…...
5-pandas常用操作2
前言 一、df.max() 计算每列最大值 二、df.apply() 1.可以传函数 代码如下(示例): # lambda 匿名函数自定义 f lambda x:x.max()-x.min() # x参数 冒号后是返回值 df.apply(f) # 默认axis0,所以这里是按列求最大值-最小值2.可以直…...
【HarmonyOS之旅】ArkTS语法(一)
目录 1 -> 基本UI描述 1.1 -> 基本概念 1.2 -> UI描述规范 1.2.1 -> 无参数构造配置 1.2.2 -> 必选参数构造配置 1.2.3 -> 属性配置 1.2.4 -> 事件配置 1.2.5 -> 子组件配置 2 -> 状态管理 2.1 -> 基本概念 2.2 -> 页面级变量的状…...
基于Spring Boot + Vue3实现的在线商品竞拍管理系统源码+文档
前言 基于Spring Boot Vue3实现的在线商品竞拍管理系统是一种现代化的前后端分离架构的应用程序,它结合了Java后端框架Spring Boot和JavaScript前端框架Vue.js的最新版本(Vue 3)。该系统允许用户在线参与商品竞拍,并提供管理后台…...