尚硅谷金融宝
代码生成器
逻辑删除
封装统一返回结果
自定义断言处理异常
数据字典
表设计优化
vhr微人事
苍穹外卖
JWT登录
JWT完整流程如下:
- 用户使用账号和密码发起 POST 请求;
- 服务器使用私钥创建一个 JWT;
- 服务器返回这个 JWT 给浏览器;
- 浏览器将该 JWT 串在请求头中像服务器发送请求;
- 服务器验证该 JWT;
- 返回响应的资源给浏览器。
JWT的优点
- 不需要在服务端保存会话信息,特别适用于分布式微服务。
- 自包含(Self-contained):负载中包含了所有用户所需要的信息,避免了多次查询数据
- 简洁(Compact):可以通过URL, POST参数或者在HTTP header发送,因为数据量小,传输速度也很快
- 因为Token是以JSON加密的形式保存在客户端的,所以JWT是跨语言的,原则上任何web形式都支持。
注意事项
- JWT 默认是不加密,但也是可以加密的。生成原始 Token 以后,可以用密钥再加密一次。
- JWT 不加密的情况下,不能将秘密数据写入 JWT。
- JWT 不仅可以用于认证,也可以用于交换信息。有效使用 JWT,可以降低服务器查询数据库的次数。
- JWT 的最大缺点是,由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。
- JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。
- 为了减少盗用,JWT 不应该使用 HTTP 协议明码传输,要使用 HTTPS 协议传输。
全局异常处理
ThreadLocal线程局部变量
ThreadLocal不是一个Thread,而是Thread的局部变量
ThreadLocal为每个线程单独提供一个存储空间,具有线程隔离效果,只有在线程内才能获取到对应的值,线程外则不能访问。
客户端每一次访问,tomacat都会开辟一个线程
所以当我们保证在线程的生命周期只内,能访问到,就可以把值放到里面。
用户每次访问会被拦截器拦截,从请求携带的token,解析JWT出当前登录员工id,并放入线程局部变量
解决原理 代码中已经提供了封装好的ThreadLocal,名字是BaseContext 因为每次请求都会用开辟一个线程 而且每次请求前端都会带token过来,所以它发送保存用户的时候也会带token过来进行验证 所以将jwt验证出来的token放置在ThreadLocal中 因为这次保存和他是一个线程,所以ThreadLocal还是一个,所以保存的时候直接从ThreadLocal中获取即可
解决方式 在jwt验证的代码中放置token到ThreadLocal 在service中获取ThreadLocal将id放进去
请求返回结果时间转换
由mybatis查询的结果时间由字符串转换为"yyyy-MM-dd HH:mm:ss"
方式一 单独处理 @JsonFormat
方式二 全局处理 扩展WebMvcConfiguration消息转换器
在spring中配置webMvc有两种方法,一种是继承WebMvcConfigurationSupport,另一种方式就是继承WebMvcConfigurer,但是要多加一个@EnableWebMvc注解。
WebMvcConfigurer是一个接口,用于配置全局的SpringMVC的相关属性,采用JAVABEAN的方式来代替传统的XML配置文件,提供了跨域设置、静态资源处理器、类型转化器、自定义拦截器、页面跳转等能力。WebMvcConfigurationSupport类是SpringMVC提供的扩展类,用于提供拦截器、资源处理器等注册功能。
自动填充公共创建时间和修改时间
使用自定义注解,搭配AOP和反射
/**
* 自定义注解,用于标识某个方法需要进行功能字段自动填充处理
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
//数据库操作类型:UPDATE INSERT
OperationType value();
}
/**
* 自定义切面,实现公共字段自动填充处理逻辑
*/
@Aspect
@Component
@Slf4j
public class AutoFillAspect {
/**
* 切入点
*/
@Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")
public void autoFillPointCut(){}
/**
* 前置通知,在通知中进行公共字段的赋值
*/
@Before("autoFillPointCut()")
public void autoFill(JoinPoint joinPoint){
log.info("开始进行公共字段自动填充...");
//获取到当前被拦截的方法上的数据库操作类型
MethodSignature signature = (MethodSignature) joinPoint.getSignature();//方法签名对象
AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);//获得方法上的注解对象
OperationType operationType = autoFill.value();//获得数据库操作类型
//获取到当前被拦截的方法的参数--实体对象
Object[] args = joinPoint.getArgs();
if(args == null || args.length == 0){
return;
}
Object entity = args[0];
//准备赋值的数据
LocalDateTime now = LocalDateTime.now();
Long currentId = BaseContext.getCurrentId();
//根据当前不同的操作类型,为对应的属性通过反射来赋值
if(operationType == OperationType.INSERT){
//为4个公共字段赋值
try {
Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);
Method setCreateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);
Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
//通过反射为对象属性赋值
setCreateTime.invoke(entity,now);
setCreateUser.invoke(entity,currentId);
setUpdateTime.invoke(entity,now);
setUpdateUser.invoke(entity,currentId);
} catch (Exception e) {
e.printStackTrace();
}
}else if(operationType == OperationType.UPDATE){
//为2个公共字段赋值
try {
Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);
//通过反射为对象属性赋值
setUpdateTime.invoke(entity,now);
setUpdateUser.invoke(entity,currentId);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
黑马头条
表字段设置
tinyint类型