springboot搭建一个后台管理(三):全局异常处理、aop、validator校验

  基于前几篇写的springboot环境搭建,引入了mybatis、generator等相关依赖。

springboot搭建一个后台管理(一):环境配置及搭建 :https://blog.csdn.net/LiLrui/article/details/87916534

springboot搭建一个后台管理(二):最基础的CRUD测试:https://blog.csdn.net/LiLrui/article/details/87920644

这篇以做一个部门管理相关的业务为例,引入全局异常处理、aop、validator校验等,先看下业务关系的图表:

  接下来先定义aop相关的代码,我需要在用户发送请求时通过切面来获取信息并打印日志,创建一个HttpAspect类:

/**
 * Created by Lilrui.
 * Aspect切面来获取请求,这个类丢给spring管理
 */
@Aspect
@Component
@Slf4j
public class HttpAspect {

    //需要将切面植入到哪个包/类
    @Pointcut("execution(public * com.sansheng.controller..*(..))")
    public void print() {
    }
    
    //请求之前打印
    @Before("print()")
    public void doBefore(JoinPoint joinPoint) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
//        Enumeration<String> enumeration = request.getParameterNames();
//        Map<String,Object> parameterMap = new HashMap<String,Object>();
        Object[] obj = joinPoint.getArgs();
        //添加请求的ip
        RequestHolder.add(request);

        log.info("url={}", request.getRequestURL());
        log.info("method={}", request.getMethod());
        log.info("ip={}", request.getRemoteAddr());
        log.info("class_method={}{}",joinPoint.getSignature().getName(),"()");
//        if (obj.length==1){
//            log.info("args={}",joinPoint.getArgs());
//        }else {
//            for (Object o:obj) {
//
//             parameterMap.put(enumeration.nextElement(),o);
//
//            }
//            log.info("args={}", parameterMap);
//        }

    }

      //请求之后打印
//    @After("print()")
//    public void doAfter() {
//
//    }

    //不管成功失败,总是打印
    @AfterReturning(returning = "object", pointcut = "print()")
    public void doAfterReturning(Object object) {
        log.info("response={}", object.toString());
    }

以test测试为例,测试下切面是否被植入:用postman工具测试 http://localhost/Test/findAll 接口查询全部

然后看到控制台打印了日志信息

 

  切面的植入是没有问题的,我的业务只需要它在日志中打印信息,如果你需要切面做更多的事情,比如请求之后的处理,就在doAfter()去添加,具体根据自己的业务需要去拓展。

  接下来做全局异常捕获处理,做这个是为了把异常做统一监听并处理,而不是在代码中到处的 try catch捕获, 这样会显的代码非常的臃肿,而且不利于维护。java中有一个叫 RuntimeException 的类,这个类是运行时的异常类,也就是它并不强制性的要求我们去捕获,而是在我们觉的某个业务运行时可能会出现异常,比如:我传入了一个空字段。这时系统会报运行时异常。我们可以利用它来自定义我们的异常。

我们需要先创建一个自定义的枚举类,来管理我们参数出异常的信息:

/**
 * Created by Lilrui.
 */
@Getter
public enum  ErrorEnum  {

    PARAMETER_VALIDATION_ERROR(10001,"参数不合法"),
    USER_IS_NOT_EXIST(10002,"用户不存在"),
    DEPT_IS_NOT_EXIST(10003,"部门不存在"),
    DEPTNAME_IS_EXIST(10004,"同一层级下存在相同名称的部门"),
    UNKNOW_ERROR(10004,"系统异常:未知错误")
            ;


    private Integer code;

    private String msg;

    ErrorEnum(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }


}

里面的枚举根据自己的业务需要去定义。这里先添加部门相关的异常信息,然后我需要把自定义的enum类 跟我们的异常绑定,

怎么绑定,首先要创建一个自定义异常类 BusinessException去继承我们的RuntimeException ,然后把enum给注入到类里:

/**
 * Created by Lilrui.
 */
@Setter
@Getter
public class BusinessException extends RuntimeException {
    private Integer code;
    private ErrorEnum errorEnum;

    
    public BusinessException(ErrorEnum errorEnum) {
        super(errorEnum.getMsg());
        this.code = errorEnum.getCode();
    }


    public BusinessException() {
        super();
    }

    public BusinessException(ErrorEnum errorEnum,String message) {
        super(message);
        this.code = errorEnum.getCode();
    }

    public BusinessException(String message, Throwable cause) {
        super(message, cause);
    }

    public BusinessException(Throwable cause) {
        super(cause);
    }

    protected BusinessException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
}

这时候已经绑定好了,接下来需要把异常跟我们的web层进行结合,因为所有的请求入口都是来自web层,我们需要一个Handle来吸收我们的异常信息。所以要创建一个ExceptionHandle类来绑定web层:

/**
 * Created by Lilrui.
 */
@ControllerAdvice
@Slf4j
public class ExceptionHandle {

    @ExceptionHandler(value = Exception.class)
    @ResponseBody
    public JsonResult handle(Exception e) {
        Map<String,Object> responseData = new HashMap<>();
        if (e instanceof BusinessException) {
            BusinessException businessException = (BusinessException) e;
            responseData.put("errCode",businessException.getCode());
            responseData.put("errMsg",businessException.getMessage());

        }else {
            log.error("系统异常:", e);
            responseData.put("errCode", ErrorEnum.UNKNOW_ERROR.getCode());
            responseData.put("errMsg",ErrorEnum.UNKNOW_ERROR.getMsg());

        }
        return  ResultUtil.error(responseData);
    }
}

这里我还需要介入一个工具类:BindingResult,用来在web层捕获异常并将所有异常依次返回

/**
 * Created by Lilrui.
 */
@Slf4j
public class BindingResultUtil {

    //这个类作用是把捕获到的所有校验异常都捕获,并依次输出。返回的可能不止一个异常
    public static String toErrMsg(BindingResult bindingResult) {
        List<String> errorMsg = Lists.newArrayList();
        bindingResult.getAllErrors().forEach(error -> errorMsg.add(error.getDefaultMessage()));
        return  JSON.toJSON(errorMsg).toString();

    }
}

 

绑定成功了其实还有一个问题,就是我们要把异常信息能正确的返回给客户端,而且是一个统一的格式,就想上篇讲过的,通过自定义JsonResult来 讲数据格式进行统一,我希望如果请求成功,返回一个正常信息,请求失败,则返回异常信息。那么接下来再写一个工具类,来包装JsonResult。

/**
 * Created by Lilrui.
 * 通过包装JsonResult来保证 正常情况返回数据信息,异常情况返回异常信息
 */
public class ResultUtil {

    public static JsonResult success(Object object) {
        JsonResult result = new JsonResult(true,"success");
        result.setData(object);
        return result;
    }

    public static JsonResult success() {
        return success(null);
    }

    public static JsonResult error(Object object) {

        JsonResult result = new JsonResult(false,"fail");
        result.setData(object);
        return result;
    }
   
}

定义好全局异常跟aop切面后,接下来写部门相关的业务:

添加部门:id自增

                  name:部门名称不能为空

                  seq:展示顺序不能为空

                 remark:备注在200字以内

修改部门:id:传入的id查询不到则报部门不存在异常

                 name:同一层级下部门名称不能相同

删除部门:id:传入的部门主键查询不到则报部门不存在异常

以上业务都有传入参数的限制,那么先来来定义参数校验,单独把需要校验的参数给封装成一个param类:

/**
 * Created by Lilrui.
 *这些是我要校验的参数
 */
@Data
public class DeptParam {
    private Integer id;
    @NotBlank(message = "部门名称不能为空")
    private String name;

    private Integer parentId;
    @NotNull(message = "展示顺序不能为空")
    private Integer seq;
    @Length(max = 200,message = "备注必须在0~200字以内")
    private String remark;
}

然后之前generator已经帮我们生成好了mapper接口,我们这里需要把dao层和sercvice给分开,保证dao只做crud。而sercvice做业务相关操作:

public interface SysDeptMapper {
    int deleteByPrimaryKey(@Param("id")Integer id) throws Exception;//根据id删除部门

    int insert(SysDept record);//插入一个部门
    int insertSelective(SysDept record);//插入一个部门,若只插入了部分字段,只写入插入的值

    SysDept selectByPrimaryKey(@Param("id")Integer id) throws Exception;//根据id查询部门

    int updateByPrimaryKeySelective(SysDept record);//修改部门,若只修改了部分字段,只更新修改过的值
    List<SysDept> selectAllDept();//查询所有部门
    int selectNameAndParentId(@Param("parentId") Integer parentId, @Param("name") String name, @Param("id") Integer id);//查询同层级下的部门是否存在
    int updateByPrimaryKey(SysDept record);//修改部门
}

 然后定义一个接口继承mapper,并加上事务

/**
 * Created by Lilrui.
 */
@Service
public class DeptMapperImpl implements SysDeptMapper{
    @Resource
    private SysDeptMapper sysDeptMapper;

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public int deleteByPrimaryKey(Integer id)throws Exception {
        //id不存在则抛出部门不存在异常
        if (null==sysDeptMapper.selectByPrimaryKey(id)){
            throw  new BusinessException(ErrorEnum.DEPT_IS_NOT_EXIST);
        }
        return sysDeptMapper.deleteByPrimaryKey(id);
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public int insert(SysDept dept) {
        return sysDeptMapper.insert(dept);
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public int insertSelective(SysDept dept) {
        return sysDeptMapper.insertSelective(dept);
    }

    @Override
    @Transactional(propagation = Propagation.SUPPORTS)
    public SysDept selectByPrimaryKey(Integer id)  throws Exception{

        if (null==sysDeptMapper.selectByPrimaryKey(id)){
           throw  new BusinessException(ErrorEnum.DEPT_IS_NOT_EXIST);
        }
        return sysDeptMapper.selectByPrimaryKey(id);
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public int updateByPrimaryKeySelective(SysDept dept) {
        return sysDeptMapper.updateByPrimaryKeySelective(dept);
    }

    @Override
    @Transactional(propagation = Propagation.SUPPORTS)
    public List<SysDept> selectAllDept() {
        return sysDeptMapper.selectAllDept();
    }

    @Override
    @Transactional(propagation = Propagation.SUPPORTS)
    public int selectNameAndParentId(Integer parentId,String name,Integer id) {
        return sysDeptMapper.selectNameAndParentId(parentId,name,id);
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public int updateByPrimaryKey(SysDept dept) {
        return sysDeptMapper.updateByPrimaryKey(dept);
    }
}

接下来就是写我们的业务代码了:

/**
 * Created by Lilrui.
 */
@Service
@Slf4j
public class DeptService {
    @Autowired
    private DeptMapperImpl deptMapper;
    /**
     * 保存部门业务
     * @param param
     */
    public void save(DeptParam param) throws Exception{

        if(checkExist(param.getParentId(), param.getName(), param.getId())) {
            log.error("同一层级下存在相同的部门:{}",param.getName());
            throw new BusinessException(ErrorEnum.DEPTNAME_IS_EXIST);
        }

        //level,operator暂时写个固定值,后续业务再做开发
        SysDept dept = SysDept.builder()
                .name(param.getName())
                .parentId(param.getParentId())
                .seq(param.getSeq())
                .remark(param.getRemark())
                .level("1")
                .operateIp(IpUtil.getRemoteIp(RequestHolder.getIp()))
                .operator("admin")
                .operateTime(new Date())
                .build();

        deptMapper.insertSelective(dept);
        RequestHolder.remove();
    }

    /**
     * 更新部门业务
      */
    public void update(DeptParam param)throws Exception{
        
        if(checkExist(param.getParentId(), param.getName(), param.getId())) {
            log.error("同一层级下存在相同的部门:{}",param.getName());
            throw new BusinessException(ErrorEnum.DEPTNAME_IS_EXIST);
        }
        SysDept oldDept =deptMapper.selectByPrimaryKey(param.getId());
        
        SysDept afterDept = SysDept.builder()
                .id(param.getId())//更新需传入主键
                .name(param.getName())
                .parentId(param.getParentId())
                .seq(param.getSeq())
                .remark(param.getRemark())
                .level("0.1.2")
                .operateIp(IpUtil.getRemoteIp(RequestHolder.getIp()))
                .operator("admin")
                .operateTime(new Date())
                .build();
        deptMapper.updateByPrimaryKeySelective(afterDept);

    }
    /**
     * 查询同一部门下是否有相同的部门
     * @param parentId
     * @param deptName
     * @param deptId
     * @return
     */
    private boolean checkExist(Integer parentId, String deptName, Integer deptId) {

        return deptMapper.selectNameAndParentId(parentId, deptName, deptId) > 0;
    }
}

最后写web层,添加controller接口

/**
 * Created by Lilrui.
 */
@Controller
@RequestMapping("/dept")
@Slf4j
public class DeptController {

    private final DeptService deptService;
    private final DeptMapperImpl deptMapper;

    /**
     * 构造器注入
     * @param deptService
     * @param deptMapper
     */
    public DeptController(DeptService deptService, DeptMapperImpl deptMapper) {
        this.deptService = deptService;
        this.deptMapper = deptMapper;
    }

    @PostMapping("/insert")
    @ResponseBody
    public JsonResult insert(@Valid DeptParam deptParam, BindingResult bindingResult) throws Exception {
        //通过注入BindingResult 来获取异常,如果有异常则捕获
        if (bindingResult.hasErrors()) {
         throw new BusinessException(ErrorEnum.PARAMETER_VALIDATION_ERROR,BindingResultUtil.toErrMsg(bindingResult));
        }

        deptService.save(deptParam);

        return JsonResult.success();
    }

    @PostMapping("/update")
    @ResponseBody
    public JsonResult update(DeptParam deptParam)throws Exception{

        deptService.update(deptParam);
        return JsonResult.success();
    }


    @PostMapping("/getAll")
    @ResponseBody
    public JsonResult selectAll() {
        return JsonResult.success(deptMapper.selectAllDept());
    }

    @PostMapping("/getOne")
    @ResponseBody
    public JsonResult getOne(@Param(value = "id")Integer id)  throws Exception{
        return JsonResult.success(deptMapper.selectByPrimaryKey(id));
    }

    @PostMapping("/coutNameAndParentId")
    @ResponseBody
    public JsonResult selectNameAndParentId(@Param(value = "parentId") Integer parentId,
                                            @Param(value = "name") String name,
                                            @Param(value = "id") Integer id) {
        return JsonResult.success(deptMapper.selectNameAndParentId(parentId, name, id));
    }

    @PostMapping("/delete")
    @ResponseBody
    public JsonResult delete(@Param(value = "id") Integer id)throws Exception{

        deptMapper.deleteByPrimaryKey(id);
        return JsonResult.success();
    }
}

最后我们来测试下我们写的业务:使用postman 测试添加部门业务:http://localhost/dept/insert?name=测试&parentId=1&seq=2&remark=1

看数据库是否有这条信息:

这是正常添加,接下来我们添加几个空字段试试:

然后我再添加一条重复的数据:

接下来测试修改业务:http://localhost/dept/update?id=17&name=测试233&parentId=1&seq=2&remark=测试2333

查看数据库是否修改成功:

然后我们重复修改试试:

我们查询一个部门:http://localhost/dept/getOne?id=17

输入一个不存在的部门id:

这些都是我们自定义的异常,最后测试下系统异常,我在代码里手动加个错误:

然后测试这个接口:

以上就是我们的全局异常处理,字段校验,以及aop测试。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值