無規矩不成方圓,任何一個軟件,如果剛開始沒有定義好規範,任由各個開發進行按照自己的喜好進行開發,後面運維的兄弟,估計整天就要罵娘了。
開發一時爽,運維火葬場,運維一個軟件,往往比開發一個軟件要辛苦好多,畢竟很多時候,運維都要從不明白需求,不理解系統架構,不理解數據結構的0開始。
今天來做一個定義多業務的接口規範,考慮到每家企業的業務不一樣,只提供參考。
目錄
定義多業務接口
在後端的服務中,我們的服務只會提供API接口,以便提供數據,根據業務需求,我們把接口分爲三類
1、後端系統服務接口view,主要提供給後臺管理系統使用,面向B端
2、前端系統服務接口application,主要提供給小程序,H5使用,面向C端
3、第三方服務接口external,主要提供給第三方使用
統一接口消息體
/**
* 消息影響實體
* @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,即可看到我們的接口,在這裏進行接口調試。
源碼請關注後私信
歡迎關注頭條:溪雲閣
部分圖片或代碼來源網絡,如侵權請聯繫刪除,謝謝!