SpringBoot系列-- SpringBoot自定义异常类封装

1. 项目环境

  • IDEA 2020.1.4
  • Maven 3.6
  • JDK 1.8
  • SpringBoot 2.x

项目文件在GitHub(欢迎star⭐):
https://github.com/Gang-bb/Gangbb-SpringBoot

注意:此节内容包含SpringBoot系列-- SpringBoot统一对象返回小节内容
SpringBoot系列-- SpringBoot统一对象返回
如有疑问或是建议,欢迎评论区留言或者QQ:949526365

2. 对Java异常的一些理解

  • CheckedException: 必须在编码时进行处理,不处理程序是无法通过编译的。可以向上throws抛出或者就地try/catch解决,比如记日志。(程序员可以处理)

    举例:1. B.C() B调用C方法,但C没有定义。 2. 读取文件,文件不存在。此时可能文件路径不对,改对就处理完成了

  • RunTimeException:运行时异常,可以通过编译,不强制要求要处理。

    举例:比如用户输入2000 id号。数据库找不到,就是一种RunTimeException

3. 处理异常

目标是每次返回的异常都是下面这种格式:

image-20210123095521770

3.1 定义统一异常类

public class ApiException extends RuntimeException{
    private final Integer code;
    private final String message;

    public ApiException(Integer code, String message) {
        this.code = code;
        this.message = message;
    }

    public ApiException(ApiExceptionEnum exceptionEnum) {
        this(exceptionEnum.getCode(), exceptionEnum.getMsg());
    }

    public Integer getCode() {
        return code;
    }

    @Override
    public String getMessage() {
        return message;
    }
}

3.2 定义全局异常拦截

import com.gangbb.gangbbspringbootexception.common.ApiRestResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;

import javax.servlet.ServletException;
import java.util.ArrayList;
import java.util.List;

/**
 * @author : Gangbb
 * @ClassName : GlobalExceptionHandler
 * @Description :
 * @Date : 2021/1/23 10:04
 */
//拦截异常注解 @ControllerAdvice表明这时一个异常处理类
@ControllerAdvice
public class GlobalExceptionHandler {

    private final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    @ResponseBody
    public Object handleHttpException(ServletException e) {
        log.error("ServletException: ", e);
        return ApiRestResponse.error(ApiExceptionEnum.HTTP_ERROR.getCode(), e.getMessage());
    }

    @ExceptionHandler(Exception.class)
    @ResponseBody
    public Object handleException(Exception e) {
        log.error("Default Exception: ", e);
        return ApiRestResponse.error(ApiExceptionEnum.SYSTEM_ERROR);
    }

    @ExceptionHandler(ApiException.class)
    @ResponseBody
    public Object handleFirstMallException(ApiException e) {
        log.error("ApiException: ", e);
        return ApiRestResponse.error(e.getCode(), e.getMessage());
    }

    /**
     * 参数校验异常处理
     * @param e
     * @return
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseBody
    public ApiRestResponse handleMethodArgumentNotValidException(
            MethodArgumentNotValidException e) {
        log.error("MethodArgumentNotValidException: ", e);
        return handleBindingResult(e.getBindingResult());
    }
    //参数类型错误
    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    @ResponseBody
    public Object handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e) {
        log.error("MethodArgumentTypeMismatchException: ", e);
        return ApiRestResponse.error(ApiExceptionEnum.PARAM_TYPE_ERROR.getCode(), "参数"+e.getName() + ":" + "字段类型错误");
    }
    //参数缺少
    @ExceptionHandler(MissingServletRequestParameterException.class)
    @ResponseBody
    public Object handleMissingServletRequestParameterException(MissingServletRequestParameterException e) {
        log.error("MissingServletRequestParameterException: ", e);
        return ApiRestResponse.error(ApiExceptionEnum.PARAM_TYPE_ERROR.getCode(), e.getMessage());
    }

    private ApiRestResponse handleBindingResult(BindingResult result) {
        //把异常处理为对外暴露的提示
        List<String> list = new ArrayList<>();
        if (result.hasErrors()) {
            List<ObjectError> allErrors = result.getAllErrors();
            for (ObjectError objectError : allErrors) {
                String message = objectError.getDefaultMessage();
                list.add(message);
            }
        }
        if (list.size() == 0) {
            return ApiRestResponse.error(ApiExceptionEnum.REQUEST_PARAM_ERROR);
        }
        return ApiRestResponse
                .error(ApiExceptionEnum.REQUEST_PARAM_ERROR.getCode(), list.toString());
    }
}

3.3 测试使用

image-20210123101757499 image-20210123101857987

4. 后期迭代思路

  • 全局错误拦截类中,可以根据请求的Content-Type为json或html做不同处理。返回json或通用模板页面

  • 后期项目如果做大,错误码的定义可以用一个专门的配置文件记录,再通过一个类映射相应的值。可以方便国际化!

  • 关于错误码的定义和优化可以参照阿里开发手册中的一些设计思路。

    image-20210123102457804
<think>好的,用户之前问过关于ag-Grid的本地过滤实现,现在他想了解如何用Spring Boot和MyBatis-Plus来封装ag-Grid的过滤功能。我需要先回顾之前的对话,确保这次回答能衔接上。 首先,用户可能已经熟悉了ag-Grid的客户端过滤,现在想结合后端技术实现更复杂的服务端过滤。可能需要将前端的过滤条件传递到后端,由Spring Boot处理,利用MyBatis-Plus进行数据库查询。 需要确定用户的具体需求。是纯服务端过滤,还是混合模式?比如,某些列在前端过滤,某些在服务端。或者整个过滤逻辑都在后端处理,适用于大数据量。 然后,考虑如何将ag-Grid的过滤参数转换成MyBatis-Plus的查询条件。ag-Grid的过滤器可能有不同的类型,比如文本、数字、日期,每种类型对应的过滤条件结构不同,需要解析这些参数并生成对应的SQL条件。 接下来,设计后端的API接口。可能需要接收过滤参数,处理分页、排序等,同时整合MyBatis-Plus的Lambda查询来构建动态SQL。这里要注意分页插件的使用,以及如何将过滤条件转换为QueryWrapper的条件。 另外,用户可能希望知道前后端的数据交互格式,比如如何从ag-Grid的过滤器模型获取参数,并传递给Spring Boot的Controller。需要给出具体的示例代码,比如如何接收过滤参数,并转换为MyBatis-Plus的查询条件。 还要考虑可能的性能问题,比如当数据量很大时,服务端过滤的效率如何优化,可能需要数据库索引的支持,或者分页处理来减少数据传输量。 最后,应该提醒用户注意参数验证和安全性,防止SQL注入,确保过滤参数的合法性。同时,提供一个完整的实现步骤,从前端配置到后端处理,确保用户能够逐步实现功能。 总结,用户需要的是一个整合Spring Boot、MyBatis-Plus和ag-Grid的过滤方案,重点在于参数传递、条件转换和动态查询构建。需要详细讲解每个步骤,并提供代码示例,确保用户能够理解和实施。</think>以下是使用 Spring Boot 与 MyBatis-Plus 封装 ag-Grid 过滤功能的完整实现方案: --- ### 一、技术栈整合原理 ```mermaid sequenceDiagram ag-grid->>前端: 触发过滤事件 前端->>后端: 发送过滤参数(JSON) 后端->>MyBatis-Plus: 动态生成SQL MyBatis-Plus->>数据库: 执行过滤查询 数据库-->>后端: 返回过滤数据 后端-->>前端: 返回分页结果 前端->>ag-grid: 更新渲染数据 ``` --- ### 二、前端封装(ag-Grid 配置) #### 1. 过滤器参数格式 ```javascript // ag-Grid过滤器模型示例 const filterModel = { name: { filterType: 'text', type: 'contains', filter: '张三' }, age: { filterType: 'number', type: 'greaterThan', filter: 25 } }; ``` #### 2. 请求参数封装 ```typescript // 分页+过滤请求DTO interface GridRequest { page: number; pageSize: number; sortModel: SortModel[]; // 排序字段 filterModel: Record<string, any>; // 过滤条件 } ``` --- ### 三、后端核心实现 #### 1. Controller 层 ```java @PostMapping("/data") public PageResult<Employee> getGridData(@RequestBody GridRequest request) { return employeeService.getFilteredData(request); } ``` #### 2. 请求参数对象 ```java @Data public class GridRequest { private Integer page; private Integer pageSize; private List<SortModel> sortModel; private Map<String, Object> filterModel; } @Data public class SortModel { private String colId; // 对应数据库字段 private String sort; // asc/desc } ``` #### 3. Service 实现(核心) ```java public PageResult<Employee> getFilteredData(GridRequest request) { Page<Employee> page = new Page<>(request.getPage(), request.getPageSize()); LambdaQueryWrapper<Employee> wrapper = new LambdaQueryWrapper<>(); // 动态构建过滤条件 request.getFilterModel().forEach((field, condition) -> { buildCondition(wrapper, field, condition); }); // 处理排序 if (CollectionUtils.isNotEmpty(request.getSortModel())) { request.getSortModel().forEach(sort -> { wrapper.orderBy(true, "asc".equals(sort.getSort()), getColumnName(sort.getColId())); }); } return new PageResult<>(baseMapper.selectPage(page, wrapper)); } // 条件构建工具方法 private void buildCondition(LambdaQueryWrapper<Employee> wrapper, String field, Object condition) { if (condition instanceof Map) { Map<String, Object> filter = (Map<String, Object>) condition; String type = (String) filter.get("type"); Object value = filter.get("filter"); switch (type) { case "contains": wrapper.like(getColumnName(field), value); break; case "equals": wrapper.eq(getColumnName(field), value); break; case "greaterThan": wrapper.gt(getColumnName(field), value); break; // 可扩展其他过滤类型... } } } // 字段名转换(前端字段转数据库列名) private String getColumnName(String field) { // 实现字段映射逻辑,例如驼峰转下划线 return StringUtils.camelToUnderline(field); } ``` --- ### 四、MyBatis-Plus 增强配置 #### 1. 自定义分页结果 ```java @Data public class PageResult<T> { private List<T> data; private Long total; public PageResult(Page<T> page) { this.data = page.getRecords(); this.total = page.getTotal(); } } ``` #### 2. SQL 性能优化 ```yaml # application.yml mybatis-plus: configuration: default-scripting-language: freemarker log-impl: org.apache.ibatis.logging.stdout.StdOutImpl ``` --- ### 五、高级过滤场景处理 #### 1. 复合条件过滤 ```java // 处理 AND/OR 逻辑 if (filter.containsKey("operator")) { String operator = (String) filter.get("operator"); List<Map<String, Object>> conditions = (List<Map<String, Object>>) filter.get("conditions"); if ("AND".equals(operator)) { wrapper.and(qw -> { conditions.forEach(c -> buildCondition(qw, field, c)); }); } else if ("OR".equals(operator)) { wrapper.or(qw -> { conditions.forEach(c -> buildCondition(qw, field, c)); }); } } ``` #### 2. 日期范围过滤 ```java case "inRange": List<String> dates = (List<String>) value; wrapper.between(getColumnName(field), LocalDate.parse(dates.get(0)), LocalDate.parse(dates.get(1))); break; ``` --- ### 六、安全注意事项 1. **字段白名单校验** ```java private static final Set<String> ALLOW_FIELDS = Set.of("name", "age", "department"); // 允许过滤的字段 if (!ALLOW_FIELDS.contains(field)) { throw new IllegalArgumentException("非法过滤字段"); } ``` 2. **SQL注入防护** - 使用 MyBatis-Plus 的 LambdaQueryWrapper - 禁止直接拼接 SQL 语句 - 对输入值进行类型校验 --- ### 七、性能优化建议 1. **索引策略** ```sql CREATE INDEX idx_name ON employee(name); CREATE INDEX idx_age ON employee(age); ``` 2. **分页限制** ```java // 限制最大分页大小 request.setPageSize(Math.min(request.getPageSize(), 1000)); ``` --- ### 八、调试技巧 1. 打印最终 SQL ```java System.out.println(wrapper.getSqlSegment()); // 输出: WHERE name LIKE ? AND age > ? ``` 2. 参数日志 ```java wrapper.getParamNameValuePairs().forEach((k,v) -> System.out.println(k + " => " + v)); ``` --- 通过这种封装方式,可以实现: 1. 前端过滤参数与后端查询的自动映射 2. 动态条件组合的灵活处理 3. 分页与排序的标准实现 4. 安全边界的有效控制 建议根据具体业务需求扩展`buildCondition`方法中的过滤类型处理逻辑。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值