封装web启动装置(sweet-boot-web-starter)

一、sweet-boot-web-starter是什么?

sweet-boot-web-starter 是基于spring-boot和swagger组件二次封装的组件,是sweet-boot中重要的一个启动装置。方便快速搭建web开发工程,快速生成接口的API文档,API统一响应格式和API统一异常处理。

二、封装

1.创建sweet-boot-web-starter模块,编辑pom文件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.lyd.sweet</groupId>
        <artifactId>sweet-boot-starter</artifactId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>

    <artifactId>sweet-boot-web-starter</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.lyd.sweet</groupId>
            <artifactId>sweet-boot-common</artifactId>
        </dependency>

        <!--org.springframework.boot-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-actuator-autoconfigure</artifactId>
        </dependency>

        <!--swagger接口-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
        </dependency>
        <dependency>
            <groupId>io.swagger</groupId>
            <artifactId>swagger-models</artifactId>
        </dependency>
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-spring-boot-starter</artifactId>
        </dependency>

        <!--validation-->
        <dependency>
            <groupId>javax.persistence</groupId>
            <artifactId>persistence-api</artifactId>
        </dependency>
        <dependency>
            <groupId>javax.validation</groupId>
            <artifactId>validation-api</artifactId>
        </dependency>

        <!--lombok插件-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>
</project>

2.创建swagger配置类

/**
 * swagger2配置文件,集成swagger,通过配置可设置swagger是否开启,标题,扫描路径等
 * 配置示例
 * sweet:
 *   api-doc:
 *     enabled: true
 *     title: api文档
 *     description: 文档描述
 *     basePackage: com.biz.xxx
 *
 * @author 木木
 */
@EnableSwagger2
@ConditionalOnProperty(prefix = "sweet.api-doc", name = "enabled", havingValue = "true")
@ConfigurationProperties(prefix = "sweet.api-doc")
public class SwaggerConfig implements WebMvcConfigurer {
    private static Logger logger = LoggerFactory.getLogger(SwaggerConfig.class);

    /**
     * 是否启用api 文档,如swagger
     */
    private boolean enabled = false;

    /**
     * api文档标题
     */
    private String title = "sweet-web-api";

    /**
     * api文档描述
     */
    private String description = "api文档描述";

    /**
     * 自动扫描文档注解的根路径
     */
    private String basePackage = "com.lyd";

    /**
     * api版本
     */
    private String version = "1.0.0";

    @Bean
    public Docket createRestApi() {
        logger.info("sweet swagger ApiDoc Init {}", enabled);
        return new Docket(DocumentationType.SWAGGER_2)
                //正式服务器设置为false
                .enable(enabled)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage(basePackage))
                .paths(PathSelectors.any())
                .build();
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        if (enabled) {
            registry.addResourceHandler("swagger-ui.html")
                    .addResourceLocations("classpath:/META-INF/resources/");
            registry.addResourceHandler("/webjars/**")
                    .addResourceLocations("classpath:/META-INF/resources/webjars/");
        }
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title(title + " Restful API")
                .description(description)
                .contact(new Contact("木木", "http://localhost", ""))
                .version(version)
                .build();

    }

    public boolean isEnabled() {
        return enabled;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public String getBasePackage() {
        return basePackage;
    }

    public void setBasePackage(String basePackage) {
        this.basePackage = basePackage;
    }

    public String getVersion() {
        return version;
    }

    public void setVersion(String version) {
        this.version = version;
    }
}

3.创建API统一响应格式处理程序类

/**
 * 针对RestController的统一返回值包装。
 * 引用微服务在controller中都直接返回原始结果,由该类统一包装成ResultObject对象返回。
 * 确保所有api返回格式一致。
 *
 * @author 木木
 **/
@RestControllerAdvice(basePackages = "com")
public class ResponseResultHandler implements ResponseBodyAdvice<Object> {

    private static Logger logger = LoggerFactory.getLogger(ResponseResultHandler.class);

    /**
     * Handler配置
     */
    private final SweetHandlerProperties handlerProperties;

    public ResponseResultHandler(SweetHandlerProperties handlerProperties) {
        this.handlerProperties = handlerProperties;
    }

    @Override
    public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
        //如果RestController的返回值已经是ResponseObject则不处理。
        return !methodParameter.getGenericParameterType().equals(ResponseObject.class);
    }

    /**
     * 重写结构体输出写处理
     *
     * @param o
     * @param methodParameter
     * @param mediaType
     * @param aClass
     * @param serverHttpRequest
     * @param serverHttpResponse
     * @return
     */
    @Override
    public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType,
                                  Class<? extends HttpMessageConverter<?>> aClass,
                                  ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {

        Object reObj = this.bodyWrite(o, methodParameter);
        if (methodParameter.getGenericParameterType().equals(String.class)) {
            try {
                ObjectMapper objectMapper = new ObjectMapper();
                //返回值为String时Content-Type为text/html。这里强制转为application/json, 编码为UTF-8
                if (reObj instanceof String) {
                    return o;
                } else {
                    serverHttpResponse.getHeaders().set("Content-Type", "application/json;charset=UTF-8");
                    return objectMapper.writeValueAsString(ResponseObject.success(o));
                }
            } catch (Throwable e) {
                logger.warn("响应结果处理程序返回结果失败。返回值为:{},error-msg:{}" , o.toString(), e.getMessage());
                return o;
            }
        } else if (o instanceof InputStreamResource) {
            return o;
        }

        //非String对象的返回值,直接包装成ResultObject返回,如果是JSON,强制加上UTF-8编码
        if (mediaType.toString().equals(MediaType.APPLICATION_JSON_VALUE)) {
            serverHttpResponse.getHeaders().set("Content-Type", "application/json;charset=UTF-8");
        }

        return reObj;
    }

    /**
     * 输出具体的对象信息
     *
     * @param o
     * @param methodParameter
     * @return
     */
    public Object bodyWrite(Object o, MethodParameter methodParameter) {
        if (!this.isFormatRequest(methodParameter)) {
            return o;
        }

        if (o instanceof ResponseObject) {
            return o;
        }

        return ResponseObject.success(o);
    }

    /**
     * 判断是否包装
     *
     * @param methodParameter
     * @return true 要包装HEAD信息 false不需要包装
     */
    public boolean isFormatRequest(MethodParameter methodParameter) {
        boolean re = handlerProperties.isResultHandlerEnabled();
        // 处理类上注解
        SweetResponseHandler classAnnotation = methodParameter.getContainingClass().getAnnotation(SweetResponseHandler.class);
        SweetResponseHandler methodAnnotation = methodParameter.getMethodAnnotation(SweetResponseHandler.class);

        // 如果类上有注解,先按类处理
        if (!ObjectUtils.isEmpty(classAnnotation)) {
            re = classAnnotation.sFormat();
        }

        // 如果方法上有注解,按方法上处理
        if (!ObjectUtils.isEmpty(methodAnnotation)) {
            re = methodAnnotation.sFormat();
        }

        return re;
    }
}

4.创建API统一异常处理程序类

/**
 * Rest服务统一、通用的异常处理
 * 针对引用此的rest微服务抛出的所有异常统一在此处理,统一在此记录日志。
 * 使用@RestControllerAdvice注解,针对RestController ,Response Content-Type值统一为application/json;charset=UTF-8
 * http请求的状态码统一返回:400. HttpStatus.BAD_REQUEST
 * 原则上RestController代码只负责记录好异常信息抛出合适的异常,不需要考虑异常日志的记录。
 * 对于异常发生后业务代码要进行补救处理的(如:重试,回滚等特殊处理),这类异常应该在业务代码中处理,不在本类中处理。
 * <p>
 * 具体异常处理设计原则:
 * 1、所有异常都会在本类中捕获。
 * 2、如果RestController中的方法已经被执行到了,所有产生的异常我们认为是中台的业务异常,统一封装成ResponseObject抛出并记录日志。
 * 3、RestController中的方法被执行之前产生的异常,统一交给Spring框架或web服务中间件去进行原生的处理。
 * 比如404 405等各类HTTP异常。如果个别有需要处理,单独捕获进行处理。
 *
 * @author 木木
 **/
@RestControllerAdvice
@ResponseStatus(HttpStatus.BAD_REQUEST)
public class GlobalExceptionHandler {

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

    /**
     * 异常时日志是否显示详细请求内容
     */
    private final Boolean showRequestDetailWhenErr;

    public GlobalExceptionHandler(SweetHandlerProperties handlerProperties) {
        this.showRequestDetailWhenErr = handlerProperties.isResultHandlerEnabled();
    }

    /**
     * ServletException通用处理
     * 所有http请求的各类异常都继承于ServletException。这里捕获后统一不做处理,直接抛出,交给Spring和web容器去处理。
     *
     * @param e
     * @return
     * @throws ServletException
     */
    @ExceptionHandler(ServletException.class)
    public ResponseObject ServletExceptionHandle(ServletException e) throws ServletException {
        throw e;
    }

    /**
     * Exception 异常统一处理
     *
     * @param e
     * @return
     */
    @ExceptionHandler(Exception.class)
    public ResponseObject exceptionHandler(Exception e) {
        logger.error("异常处理程序-未知异常!{},{}", e.getMessage(), getReqMessage(), e);
        return ResponseObject.fail(ServiceStatus.FAIL);
    }

    /**
     * RuntimeException 运行时异常统一处理
     *
     * @param e
     * @return
     */
    @ExceptionHandler(RuntimeException.class)
    public ResponseObject runtimeExceptionHandler(RuntimeException e) {
        logger.error("异常处理程序-未知运行时异常!{},{}", e.getMessage(), getReqMessage(), e);
        return ResponseObject.fail(ServiceStatus.FAIL);
    }

    /**
     * 服务参数异常的统一处理
     *
     * @param e
     * @return
     */
    @ExceptionHandler({MethodArgumentNotValidException.class})
    public ResponseObject methodArgumentNotValidExceptionHandle(MethodArgumentNotValidException e) {
        String paramsFailMsg = this.getParamsFailMsg(e.getBindingResult());
        logger.error("异常处理程序-服务方法参数校验异常!{},{}", paramsFailMsg, getReqMessage(), e);
        return ResponseObject.fail(String.format("服务方法参数校验异常%s", paramsFailMsg));
    }

    /**
     * 服务参数异常的统一处理(表单方式处理)
     *
     * @param result
     * @return
     */
    @ExceptionHandler(BindException.class)
    public ResponseObject methodArgumentNotValidExceptionHandle(BindingResult result) {
        String paramsFailMsg = this.getParamsFailMsg(result);
        logger.error("异常处理程序-服务方法参数校验异常!{},{}", paramsFailMsg, getReqMessage());
        return ResponseObject.fail(String.format("服务方法参数校验异常%s", paramsFailMsg));
    }

    /**
     * 请求参数缺失
     *
     * @param e
     * @return
     */
    @ExceptionHandler(MissingServletRequestParameterException.class)
    public ResponseObject methodArgumentNotValidExceptionHandle(MissingServletRequestParameterException e) {
        logger.error("异常处理程序-缺少请求参数!{}", getReqMessage(), e);
        return ResponseObject.fail("缺少请求参数");
    }

    /**
     * 类型转换异常
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(ClassCastException.class)
    public ResponseObject classCastExceptionHandler(ClassCastException ex) {
        logger.error("异常处理程序-类型转换异常!{}", getReqMessage(), ex);
        return ResponseObject.fail("类型转换异常");
    }

    /**
     * IO异常
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(IOException.class)
    public ResponseObject iOExceptionHandler(IOException ex) {
        logger.error("异常处理程序-IO异常!{}", getReqMessage(), ex);
        return ResponseObject.fail("IO异常");
    }

    /**
     * 数组越界异常
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(IndexOutOfBoundsException.class)
    public ResponseObject indexOutOfBoundsExceptionHandler(IndexOutOfBoundsException ex) {
        logger.error("异常处理程序-数组越界异常!{}", getReqMessage(), ex);
        return ResponseObject.fail("数组越界异常");
    }

    /**
     * 方法参数类型不匹配异常
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    public ResponseObject methodArgumentTypeMismatchException(MethodArgumentTypeMismatchException ex) {
        logger.error("异常处理程序-方法参数类型不匹配异常!{}", getReqMessage());
        return ResponseObject.fail("方法参数类型不匹配异常");
    }

    /**
     * 自定义根异常的默认处理
     *
     * @param e
     * @return
     */
    @ExceptionHandler(SweetBasicException.class)
    public ResponseObject sweetBaseExceptionHandler(SweetBasicException e) {
        logger.error("异常处理程序-自定义异常!{}", getReqMessage(), e);
        //对于自定义异常,如果有传入message则使用,没有则使用ServiceStatus对应的描述
        String message = (e.getMessage() != null && !e.getMessage().equals("")) ? e.getMessage() : e.getServiceStatus().getDesc();
        return ResponseObject.fail(e.getServiceStatus(), message);
    }

    /**
     * sql异常的通用处理
     *
     * @param e
     * @return
     */
    @ExceptionHandler(SQLException.class)
    public ResponseObject sqlExceptionHandle(SQLException e) {
        logger.error("异常处理程序-SQL处理异常!{}", getReqMessage(), e);
        return ResponseObject.fail("SQL处理异常");
    }

    /**
     * 解析错误的通用处理
     *
     * @param e
     * @return
     */
    @ExceptionHandler(ParseException.class)
    public ResponseObject parseExceptionHandle(ParseException e) {
        logger.error("异常处理程序-方法解析异常!{}", getReqMessage(), e);
        return ResponseObject.fail("方法解析异常");
    }

    /**
     * 空指针异常
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(NullPointerException.class)
    public ResponseObject nullPointerExceptionHandler(NullPointerException e) {
        logger.error("异常处理程序-空指针异常!{}", e.getMessage(), e);
        return ResponseObject.fail("空指针异常");
    }

    /**
     * 找不到方法异常
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(NoSuchMethodException.class)
    public ResponseObject noSuchMethodExceptionHandler(NoSuchMethodException ex) {
        logger.error("异常处理程序-找不到方法异常!{}", getReqMessage(), ex);
        return ResponseObject.fail("找不到方法异常");
    }

    /**
     * 获取api请求详细
     *
     * @return
     */
    private String getReqMessage() {
        if (showRequestDetailWhenErr) {
            ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            if (ObjectUtils.isEmpty(servletRequestAttributes)) {
                return "";
            }
            HttpServletRequest request = servletRequestAttributes.getRequest();
            String uri = request.getRequestURI();
            String showFormat = "service run err, uri is : [%s], params is : [%s]";
            Map<String, String[]> params = request.getParameterMap();
            Map<String, String> showRes = new HashMap<>(16);
            params.forEach((key, value) -> {
                String valueStr = value == null || value.length == 0 ?
                        "" : String.join(",", value);
                showRes.put(key, valueStr);
            });
            String paramsStr = showRes.toString();
            return String.format(showFormat, uri, paramsStr);
        }
        return "";
    }

    /**
     * 获取参数异常信息
     *
     * @param bindingResult
     * @return
     */
    private String getParamsFailMsg(BindingResult bindingResult) {
        if (!ObjectUtils.isEmpty(bindingResult)
                && !bindingResult.getAllErrors().isEmpty()) {
            List<String> list = new ArrayList<>(8);
            for (ObjectError error : bindingResult.getAllErrors()) {
                String msg = error.getDefaultMessage();
                list.add(msg);
            }
            return CollectionUtils.isEmpty(list) ? "" : Arrays.toString(list.toArray());
        }
        return "";
    }
}

5.创建/resource/META-INF/spring.factories

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.lyd.sweet.config.SwaggerConfig,\
com.lyd.sweet.config.WebMvcEndpointConfig,\
com.lyd.sweet.properties.SweetHandlerProperties,\
com.lyd.sweet.handler.GlobalExceptionHandler,\
com.lyd.sweet.handler.ResponseResultHandler

三、使用步骤

1.引入库

<dependency>
     <groupId>com.lyd.sweet</groupId>
     <artifactId>sweet-boot-web-starter</artifactId>
</dependency>

2.示例

sweet-boot-web-starter: 使用示例 - Gitee.com


源码:sweet-boot: 二次封装spring boot和spring cloud等组件,使开发变得像吃糖果一样甜美。 - Gitee.com

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值