统一异常拦截
平时我们都会用try catch 或throw的方式去处理异常,统一异常拦截可以进行统一处理将后端的异常,将没有try,catch住的给拦截住,不用每次编码都进行try,catch,降低了开发的复杂度。
如果不统一异常拦截,我们需要写很多try catch
实现
了解两个注解
@RestControllerAdvice
可以扫描到所有的controller,所有controller中都有@RestController这个注解
Spring在扫描容器的时候,将
类归到了Controller中,扫描Controller即可
@ExceptionHandler
扫描到Controller后报错了就会走这个注解下面的代码逻辑
创建config包
定义一个ExceptionAdvice类
package com.example.demo.config;
import com.example.emos.wx.exception.EmosException;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authz.UnauthorizedException;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* 捕获全局异常,并做处理
* @author wanglong
* @ControllerAdvice 和 @RestControllerAdvice都是对Controller进行增强的,可以全局捕获spring mvc抛的异常。
* RestControllerAdvice = ControllerAdvice + ResponseBody
*/
@Slf4j
@RestControllerAdvice
public class ExceptionAdvice {
@ResponseBody
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler(Exception.class)
public String exceptionHandler(Exception e){
log.error("执行异常",e);
if(e instanceof MethodArgumentNotValidException){
MethodArgumentNotValidException exception= (MethodArgumentNotValidException) e;
//返回精简消息(具体某个字段没有通过校验的原因)
return exception.getBindingResult().getFieldError().getDefaultMessage();
}
else if(e instanceof MyException){
MyException exception= (MyException) e;
return exception.getMsg();
}
else if(e instanceof UnauthorizedException){
return "你不具备相关权限";
}
else{
return "后端执行异常";
}
}
}
异常分为系统异常(不可预知,例如:程序空指针 )和业务异常(可预知,例如:传参失败)
自己定义一个异常
extends RuntimeException
RuntimeException,运行时异常,在项目运行之后出错则直接中止运行,异常由JVM虚拟机处理。
package com.example.yiruo.user.util.exception;
/**
* 业务异常
*
* @author zs
*/
public final class ServiceException extends RuntimeException
{
private static final long serialVersionUID = 1L;
/**
* 错误码
*/
private Integer code;
/**
* 错误提示
*/
private String message;
/**
* 错误明细,内部调试错误
*
* 和 {@link CommonResult#getDetailMessage()} 一致的设计
*/
private String detailMessage;
/**
* 空构造方法,避免反序列化问题
*/
public ServiceException()
{
}
public ServiceException(String message)
{
this.message = message;
}
public ServiceException(String message, Integer code)
{
this.message = message;
this.code = code;
}
public String getDetailMessage()
{
return detailMessage;
}
@Override
public String getMessage()
{
return message;
}
public Integer getCode()
{
return code;
}
public ServiceException setMessage(String message)
{
this.message = message;
return this;
}
public ServiceException setDetailMessage(String detailMessage)
{
this.detailMessage = detailMessage;
return this;
}
}
运行后测试
如果这里不加上log.error不输出e,控制台不会输出异常
aop切面实现操作记录(操作留痕)
用户在进行更新操作的时候形成的操作记录(修改密码等等)
实现原理:ioc+aop
切面本身是一个动作 ,将通知切入到切入点的过程
1.连接点:类当中有哪些方法可以被增强那么这些方法就是连接点
2.切入点:实际被增强的方法就是切入点
3.增强(通知):实质上被增强的逻辑部分被称为增强
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
定义注解
import java.lang.annotation.*;
/**
* 自定义操作日志记录注解
*
* @author wyh
*
*/
@Target({ ElementType.PARAMETER, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log
{
/**
* 处理类型
*
* @return {@link String}
*/
String handleType() default "";
/**
* 操作功能
*
* @return {@link String}
*/
String czgn() default "";
}
定义切点
@Aspect
@Component
@DependsOn("springFactoryUtils")
public class CzjlMark {
// 定义切点
@Pointcut("@annotation(com.shineyue.zzjg.config.annotation.SyMark)")
public void annotationPointcut() {}
// 切点执行前需要的操作
@Before("annotationPointcut() && @annotation(syMark)")
public void beforePointcut(JoinPoint joinPoint,SyMark syMark) {
Object[] args = joinPoint.getArgs();
CzjlMarkHandle czjlMarkHandle = CzjlMarkHandleMap.get(syMark.handle());
czjlMarkHandle.Before(args,syMark);
}
// 相关接口特定逻辑,可以不用管
@Around("annotationPointcut()")
public Object Interceptor (ProceedingJoinPoint joinPoint) {
Object result = null;
try {
result = joinPoint.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
return result;
}
/**
* 在切入点return内容之后切入内容(可以用来对处理返回值做一些加工处理),加入异常捕获目的是,若操作记录报错,不影响之前接口的返回值
* @param joinPoint
*/
@AfterReturning(returning="rvt",pointcut="@annotation(com.shineyue.zzjg.config.annotation.SyMark)")
public void doAfterReturning(JoinPoint joinPoint,Object rvt) {
JSONObject outJson = (JSONObject) rvt;
try{
if("200".equals(outJson.get("state"))){
// 获取注解
MethodSignature signature = (MethodSignature)joinPoint.getSignature();
// 获取当前方法的注解对象
SyMark syMark = signature.getMethod().getAnnotation(SyMark.class);
CzjlMarkHandle czjlMarkHandle = CzjlMarkHandleMap.get(syMark.handle());
Object[] args = joinPoint.getArgs();
czjlMarkHandle.after(rvt,syMark,args);
}
}catch (Exception e){
return;
}
}
定义抽象类
@Component
public abstract class CzjlMarkHandle {
@Autowired
ZzjgczjlService zzjgczjlService;
/**
* 在操作之前执行
* @param object
* @return
*/
public abstract void Before(Object[] object, SyMark syMark);
/**
* 在操作之后执行
* @param object
* @return
*/
public abstract void after(Object object,SyMark syMark,Object[] param);
}
加上@Log注解
怎么让Spring知道@Log注解下两个参数的意义?
先定义一个service
import com.example.yiruo.user.annotation.Log;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
/**
* 操作记录实现类
*
* @author
* @date 2022/12/03
*/
@Component
@Slf4j
public abstract class CzjlMarkService {
/**
* 之前
*
* @param args arg游戏
*/
public void before(Object[] args, Log loga){
log.info("handle:{},入参:{}",loga.handleType(), args);
};
/**
* 后
*
* @param args arg游戏
* @param signature 签名
*/
public abstract void after(Object[] args, MethodSignature signature);
}
新建一个类,让新建的类去继承从而实现方法
@Component("bmcx")
@Slf4j
public class BmcxHandle extends CzjlMarkService {
@Override
public void after(Object[] args, MethodSignature signature) {
DeptVo dept = (DeptVo) args[0];
log.info("操作功能:{},操作值:{}",signature.getMethod().getAnnotation(Log.class).czgn(),String.valueOf(dept));
}
在CzjlMark中加上
// 通过注解传入的handle来确定走哪个具体的handle代码。
private static Map<String, CzjlMarkHandle> CzjlMarkHandleMap = SpringFactoryUtils.getBeanMap(CzjlMarkHandle.class);
将所有的子类形成一个Map,用哪个子类就get哪个子类
非法sql拦截
当我们的sql语句没有where条件时,全表都会被更新,这样是有风险的
非法sql拦截主要拦截的是不带where的update和delete语句
new一个MybatisPlusConfig
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
return interceptor;
}
}
加上配置后
在进行sql语句执行的时候会被拦截住并提示不允许进行全表的更新