springboot2.2.X手冊:構建多元化的API接口,我們這樣子設計

springboot2.2.X手冊:構建多元化的API接口,我們這樣子設計

 

無規矩不成方圓,任何一個軟件,如果剛開始沒有定義好規範,任由各個開發進行按照自己的喜好進行開發,後面運維的兄弟,估計整天就要罵娘了。

開發一時爽,運維火葬場,運維一個軟件,往往比開發一個軟件要辛苦好多,畢竟很多時候,運維都要從不明白需求,不理解系統架構,不理解數據結構的0開始。

今天來做一個定義多業務的接口規範,考慮到每家企業的業務不一樣,只提供參考


目錄

定義多業務接口

統一接口消息體

定義全局異常

構建攔截的信息體

整合swagger接口可視化

運行


 

springboot2.2.X手冊:構建多元化的API接口,我們這樣子設計

 

 

定義多業務接口

在後端的服務中,我們的服務只會提供API接口,以便提供數據,根據業務需求,我們把接口分爲三類

1、後端系統服務接口view,主要提供給後臺管理系統使用,面向B端

2、前端系統服務接口application,主要提供給小程序,H5使用,面向C端

3、第三方服務接口external,主要提供給第三方使用

 

springboot2.2.X手冊:構建多元化的API接口,我們這樣子設計

 

 

統一接口消息體

/**
 * 消息影響實體
 * @author:溪雲閣
 * @date:2020年5月2日
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ApiModel(value = "響應類", description = "響應類")
public class ResponseMsg<T> {

    @ApiModelProperty(value = "響應狀態", example = "00,成功;01,失敗")
    private String respStatus;

    @ApiModelProperty(value = "響應描述", example = "例如:找不到用戶信息")
    private Object respDesc;

    @ApiModelProperty(value = "響應實體")
    private T data;

}

定義全局異常

1、統一異常

/**
 * 全局公共異常類
 * @author:溪雲閣
 * @date:2020年5月2日
 */
@Slf4j
public class CommonRuntimeException extends RuntimeException {

    private static final long serialVersionUID = 1L;

    @Setter
    @Getter
    private Object[] params;

    public CommonRuntimeException(Throwable e, Object... params) {
        super(e);
        this.params = params;
    }

    public CommonRuntimeException(Object... params) {
        this(null, params);
    }

    public CommonRuntimeException(Throwable e) {
        this(null, e);
    }

    public CommonRuntimeException() {
    }

    @Override
    public String getMessage() {
        log.error("獲取到的錯誤信息:{}", super.getMessage());
        log.error("獲取到的錯誤內容:{}", super.fillInStackTrace());
        return super.getMessage();
    }

    /**
     * 重寫fillInStackTrace 業務異常不需要堆棧信息,提高效率.
     * @author 溪雲閣
     * @return
     */
    @Override
    public Throwable fillInStackTrace() {
        return this;
    }
}

2、統一異常處理

/**
 * 統一異常處理
 * @author:溪雲閣
 * @date:2020年5月2日
 */
@RestControllerAdvice
public class CommonExceptionHandler {

    /**
     * 對錯誤信息進行處理
     * @author 溪雲閣
     * @param e 攔截到的錯誤信息
     * @return
     * @throws Exception ResponseMsg<Object>
     */
    @ExceptionHandler(value = Exception.class)
    public ResponseMsg<Object> commonErrorHandler(Exception e) throws Exception {
        System.out.println("進入到異常測試");
        ResponseMsg<Object> msg = new ResponseMsg<>();
        if (e instanceof CommonRuntimeException) {
            final CommonRuntimeException common = (CommonRuntimeException) e;
            msg = MsgUtils.buildFailureMsg(common.getMessage());
        } else if (e instanceof MethodArgumentNotValidException) {
            final MethodArgumentNotValidException valid = (MethodArgumentNotValidException) e;
            msg = MsgUtils.buildFailureMsg(buildParamError(valid.getBindingResult()));
        } else {
            msg = MsgUtils.buildFailureMsg(e.getMessage());
        }
        return msg;
    }

    /**
     * 構建參數錯誤
     * @author 溪雲閣
     * @param result
     * @return JSONObject
     */
    private JSONObject buildParamError(BindingResult result) {
        JSONObject json = null;
        if (result.hasErrors()) {
            json = new JSONObject();
            final List<FieldError> errors = result.getFieldErrors();
            for (final FieldError error : errors) {
                json.put(error.getField(), error.getDefaultMessage());
            }
        }
        return json;
    }
}

構建攔截的信息體

1、日誌攔截

/**
 * 請求日誌
 * @author:溪雲閣
 * @date:2020年5月2日
 */
@Aspect
@Component
@Slf4j
public class RequestLogs {

    /**
     * 攔截手機端請求接口
     * @author 溪雲閣 void
     */
    @Pointcut("execution( * com.boots.*.application..application.*.*(..))")
    public void applicationPointCut() {
    }

    /**
     * 攔截電腦端請求接口
     * @author 溪雲閣 void
     */
    @Pointcut("execution( * com.boots.*.view..view.*.*(..))")
    public void webPointCut() {
    }

    /**
     * 攔截外部/第三方請求接口
     * @author 溪雲閣 void
     */
    @Pointcut("execution( * com.boots.*.external..external.*.*(..))")
    public void externalPoint() {

    }

    /**
     * 請求前記錄信息
     * @author 溪雲閣
     * @param joinPoint
     * @throws Throwable void
     */
    @Before("applicationPointCut() || webPointCut() || externalPoint()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {
        // 接收到請求,記錄請求內容
        final HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
        // 記錄下請求內容
        log.info("請求地址 : " + request.getRequestURL().toString());
        log.info("請求方法 : " + request.getMethod());
        log.info("類方法 : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
        log.info("參數 : " + Arrays.toString(joinPoint.getArgs()));

    }

    /**
     * 請求後獲取返回值
     * @author 溪雲閣
     * @param ret
     * @throws Throwable void
     */
    @AfterReturning(returning = "ret", pointcut = "applicationPointCut() || webPointCut() || externalPoint()")
    public void doAfterReturning(Object ret) throws Throwable {
        // 處理完請求,返回內容(返回值太複雜時,打印的是物理存儲空間的地址)
        log.info("返回值 : " + ret);
    }

    /**
     * 請求打印耗時時間,方便出問題的時候做定位
     * @author 溪雲閣
     * @param pjp
     * @return
     * @throws Throwable Object
     */
    @Around("applicationPointCut() || webPointCut() || externalPoint()")
    public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
        final long startTime = System.currentTimeMillis();
        // ob 爲方法的返回值
        final Object ob = pjp.proceed();
        log.info("耗時 : " + (System.currentTimeMillis() - startTime));
        return ob;
    }

}

2、限流攔截

/**
 * 請求限流
 * @author:溪雲閣
 * @date:2020年5月2日
 */
@Aspect
@Component
@Slf4j
public class RequestLimit {

    // 每秒產生150個令牌
    private static final RateLimiter RATE_LIMITER = RateLimiter.create(150);

    /**
     * 攔截移動端請求接口
     * @author 溪雲閣
     * void
     */
    @Pointcut("execution( * com.atomic.*.application..application.*.*(..))")
    public void applicationPointCut() {
    }

    /**
     * 攔截電腦端請求接口
     * @author 溪雲閣
     * void
     */
    @Pointcut("execution( * com.atomic.*.web..controller.*.*(..))")
    public void webPointCut() {
    }

    /**
     * 攔截外部/第三方請求接口
     * @author 溪雲閣
     * void
     */
    @Pointcut("execution( * com.atomic.*.external..external.*.*(..))")
    public void externalPoint() {

    }

    @Before("applicationPointCut() || webPointCut() || externalPoint()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {

    }

    @AfterReturning(returning = "ret", pointcut = "applicationPointCut() || webPointCut() || externalPoint()")
    public void doAfterReturning(Object ret) throws Throwable {
    }

    @Around("applicationPointCut() || webPointCut() || externalPoint()")
    public Object doAround(ProceedingJoinPoint pjp) {
        Object obj = null;
        try {
            final HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
            final HttpServletResponse response = HttpContextUtils.getHttpServletResponse();
            if (RATE_LIMITER.tryAcquire()) {
                // 執行方法
                obj = pjp.proceed();
            } else {
                // 拒絕了請求(服務降級)
                log.info("拒絕了請求:" + request.getRequestURI());
                response.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
                response.setContentType("application/json;charset=UTF-8");
                response.getOutputStream().write(MsgUtils.buildFailureMsg("服務器繁忙,稍後再試").toString().getBytes("utf-8"));
            }
        }
        catch (final Throwable e) {
            log.error("限流出現問題:", e.fillInStackTrace());
            throw new CommonRuntimeException(e.getMessage());
        }
        return obj;
    }

}

整合swagger接口可視化

/**
 * swagger配置
 * @author:溪雲閣
 * @date:2020年5月2日
 */
@EnableSwagger2
@Configuration
public class SwaggerConfig implements WebMvcConfigurer {

    /**
     * 後臺服務接口
     * @author 溪雲閣
     * @date 2019年5月22日
     * @return Docket
     */
    @Bean
    public Docket webApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .groupName(ApiConstant.SW_TITLE_WEB)
                .genericModelSubstitutes(DeferredResult.class)
                .useDefaultResponseMessages(false)
                .forCodeGeneration(true)
                .select()
                .apis(RequestHandlerSelectors.basePackage(ApiConstant.SW_PACKPATH_WEB))
                .paths(PathSelectors.regex(".*/view/.*"))
                .build()
                .apiInfo(new ApiInfoBuilder()
                        // 頁面標題
                        .title(ApiConstant.SW_TITLE_WEB)
                        // 創建人
                        .contact(new Contact(ApiConstant.SW_AUTHOR, ApiConstant.SW_EMAIL, ApiConstant.SW_URL))
                        // 版本號
                        .version(ApiConstant.SW_VERSION)
                        // 描述
                        .description(ApiConstant.SW_DESCRIPTION_WEB)
                        .build());
    }

    /**
     * 前臺服務接口
     * @author 溪雲閣
     * @return Docket
     */
    @Bean
    public Docket appApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .groupName(ApiConstant.SW_TITLE_APPLICATION)
                .genericModelSubstitutes(DeferredResult.class)
                .useDefaultResponseMessages(false)
                .forCodeGeneration(true)
                .select()
                .apis(RequestHandlerSelectors.basePackage(ApiConstant.SW_PACKPATH_APPLICATION))
                .paths(PathSelectors.regex(".*/application/.*"))
                .build()
                .apiInfo(new ApiInfoBuilder()
                        // 頁面標題
                        .title(ApiConstant.SW_TITLE_APPLICATION)
                        // 創建人
                        .contact(new Contact(ApiConstant.SW_AUTHOR, ApiConstant.SW_EMAIL, ApiConstant.SW_URL))
                        // 版本號
                        .version(ApiConstant.SW_VERSION)
                        // 描述
                        .description(ApiConstant.SW_DESCRIPTION_APPLICATION)
                        .build());
    }

    /**
     * 第三方服務接口
     * @author 溪雲閣
     * @return Docket
     */
    @Bean
    public Docket externalApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .groupName(ApiConstant.SW_TITLE_EXTERNAL)
                .genericModelSubstitutes(DeferredResult.class)
                .useDefaultResponseMessages(false)
                .forCodeGeneration(true)
                .select()
                .apis(RequestHandlerSelectors.basePackage(ApiConstant.SW_PACKPATH_EXTERNAL))
                .paths(PathSelectors.regex(".*/external/.*"))
                .build()
                .apiInfo(new ApiInfoBuilder()
                        // 頁面標題
                        .title(ApiConstant.SW_TITLE_EXTERNAL)
                        // 創建人
                        .contact(new Contact(ApiConstant.SW_AUTHOR, ApiConstant.SW_EMAIL, ApiConstant.SW_URL))
                        // 版本號
                        .version(ApiConstant.SW_VERSION)
                        // 描述
                        .description(ApiConstant.SW_DESCRIPTION_EXTERNAL)
                        .build());
    }

    /**
     * 所有服務接口
     * @author 溪雲閣
     * @return Docket
     */
    @Bean
    public Docket allApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .groupName(ApiConstant.SW_TITLE_ALL)
                .genericModelSubstitutes(DeferredResult.class)
                .useDefaultResponseMessages(false)
                .forCodeGeneration(true)
                .select()
                .apis(RequestHandlerSelectors.basePackage(ApiConstant.SW_PACKPATH_ALL))
                .paths(PathSelectors.any())
                .build()
                .apiInfo(new ApiInfoBuilder()
                        // 頁面標題
                        .title(ApiConstant.SW_TITLE_ALL)
                        // 創建人
                        .contact(new Contact(ApiConstant.SW_AUTHOR, ApiConstant.SW_EMAIL, ApiConstant.SW_URL))
                        // 版本號
                        .version(ApiConstant.SW_VERSION)
                        // 描述
                        .description(ApiConstant.SW_DESCRIPTION_ALL)
                        .build());
    }

    /**
     * 把swagger的靜態頁面添加到靜態資源中,不然會出現404錯誤
     * @author 溪雲閣
     * @date 2018年12月15日
     * @param registry
     */
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("swagger-ui.html").addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("doc.html").addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**").addResourceLocations("classpath:/META-INF/resources/webjars/");
    }
}

運行

我們直接輸入:localhost:8080/doc.html,即可看到我們的接口,在這裏進行接口調試。

springboot2.2.X手冊:構建多元化的API接口,我們這樣子設計

 

源碼請關注後私信

 

歡迎關注頭條:溪雲閣

部分圖片或代碼來源網絡,如侵權請聯繫刪除,謝謝!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章