无规矩不成方圆,任何一个软件,如果刚开始没有定义好规范,任由各个开发进行按照自己的喜好进行开发,后面运维的兄弟,估计整天就要骂娘了。
开发一时爽,运维火葬场,运维一个软件,往往比开发一个软件要辛苦好多,毕竟很多时候,运维都要从不明白需求,不理解系统架构,不理解数据结构的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 { @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日 */@Slf4jpublic 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日 */@RestControllerAdvicepublic class CommonExceptionHandler { /** * 对错误信息进行处理 * @author 溪云阁 * @param e 拦截到的错误信息 * @return * @throws Exception ResponseMsg */ @ExceptionHandler(value = Exception.class) public ResponseMsg commonErrorHandler(Exception e) throws Exception { System.out.println("进入到异常测试"); ResponseMsg 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 errors = result.getFieldErrors(); for (final FieldError error : errors) { json.put(error.getField(), error.getDefaultMessage()); } } return json; }}
构建拦截的信息体
1、日志拦截
/** * 请求日志 * @author:溪云阁 * @date:2020年5月2日 */@Aspect@Component@Slf4jpublic 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@Slf4jpublic 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@Configurationpublic 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,即可看到我们的接口,在这里进行接口调试。
源码请关注后私信
--END--
作者:@溪云阁
如需要源码,转发,关注后私信我。
部分图片或代码来源网络,如侵权请联系删除,谢谢!