写在前面

博主最近在做一个数据服务的项目,而这个数据服务的核心就是对外暴露的API,值得高兴的这是一个从0开始的项目,所以终于不用受制于“某些历史”因素去续写各种风格的Controller,可以在项目伊始就以规范的技术和统一形式去搭建API。借此机会,梳理和汇总一下基于SpringBoot项目开发REST API的技术点和规范点。

接口服务主要由两部分组成,即参数(输入)部分,响应(输出)部分。其中在SpringBoot中主要是Controller层作为API的开发处,其实在架构层面来讲,
Controller
本身是一个最高的应用层,它的职责是调用、组装下层的interface服务数据,核心是组装和调用,不应该掺杂其他相关的逻辑。

但是往往很多项目里针对Controller部分的代码都是十分混乱,有的
Controller
兼顾各种if else的参数校验,有的甚至直接在Controller进行业务代码编写;对于
Controller
的输出,有的粗略的加个外包装,有的甚至直接把service层的结构直接丢出去;对于异常的处理也是各种各样。

以上对于
Controller
相关的问题,这里统一用一系列
Controller
的封装处理来提供优化思路。优雅且规范的开发REST API需要做以下几步:

  • 接口版本控制
  • 参数校验
  • 异常捕获处理
  • 统一响应封装
  • 接口文档的维护和更新

@RestController注解

直接来看@RestController源码

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interfaceRestController {
@AliasFor(
annotation
= Controller.class)
String value()
default "";
}

@RestController注解等价于@Controller和@@ResponseBody,@ResponseBody注解的作用是告诉Spring MVC框架,该方法的返回值应该直接写入HTTP响应体中,而不是返回一个视图(View)。当一个控制器方法被标记为
@ResponseBody
时,Spring MVC会将方法的返回值序列化成JSON或XML等格式,然后发送给客户端。更适用于REST API的构建。

所以针对Controller接口的开发,
直接使用@RestController为好。它会自动将Controller下的方法返回内容转为REST API的形式

例如:

@RestController
@RequestMapping(
"/dataserver/manage")public classDataServerController{

@PostMapping(
"/search")publicResponse searchData(@RequestBody SearchTaskDto param){returnResponse.success(taskScheduleManagerService.searchTaskForPage(param));
}
}

接口版本管理

对于API来讲,一般是对外服务的基础,不能随意变更,但是随着需求和业务不断变化,接口和参数也会发生相应的变化。此时尽可能保证“开闭原则”,以新增接口或增强接口功能来支撑,此时就需要对API的版本进行维护,以版本号来确定同一接口的不同能力,一般版本都基于url来控制

例如:

  • http://localhost:8080/dataserver/v1/queryAccount
  • http://localhost:8080/dataserver/v2/queryAccount:相比v1版本增强了参数查询的灵活性

进行API版本控制主要分三步:

  • 定义版本号注解
  • 编写版本号匹配逻辑处理器
  • 注册处理器

定义版本号注解

/*** API版本控制注解*/@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interfaceApiVersion {/***版本号,默认为1*/ int value() default 1;
}

该注解可直接使用在Controller类上

@RestController
@RequestMapping(
"dataserver/{version}/account")
@ApiVersion(
2)//输入版本号,对应{version} public classAccountController{
@GetMapping(
"/test")publicString test() {return "XXXX";
}
}

编写版本号匹配逻辑处理器

首先定义一个条件匹配类,对应解析Url中的version与ApiVersion注解

/***实现Request的条件匹配接口
*
*
*/ public class ApiVersionCondition implements RequestCondition<ApiVersionCondition>{private final static Pattern VERSION_PREFIX_PATTERN = Pattern.compile(".*v(\\d+).*");private intapiVersion;

ApiVersionCondition(
intapiVersion) {this.apiVersion =apiVersion;
}
private intgetApiVersion() {returnapiVersion;
}


@Override
publicApiVersionCondition combine(ApiVersionCondition apiVersionCondition) {return newApiVersionCondition(apiVersionCondition.getApiVersion());
}

@Override
publicApiVersionCondition getMatchingCondition(HttpServletRequest httpServletRequest) {
Matcher m
=VERSION_PREFIX_PATTERN.matcher(httpServletRequest.getRequestURI());if(m.find()) {
Integer version
= Integer.valueOf(m.group(1));if (version >= this.apiVersion) {return this;
}
}
return null;
}

@Override
public intcompareTo(ApiVersionCondition apiVersionCondition, HttpServletRequest httpServletRequest) {return apiVersionCondition.getApiVersion() - this.apiVersion;
}
}

这里补充一下
RequestCondition<ApiVersionCondition>
相关概念:

它是 Spring 框架中用于请求映射处理的一部分。在 Spring MVC 中,
RequestCondition
接口允许开发者定义自定义的请求匹配逻辑,这可以基于请求的任何属性,例如路径、参数、HTTP 方法、头部等。相关的应用场景包括:

  1. 路径匹配(Path Matching)
    :使用
    PatternsRequestCondition
    来定义请求的路径模式,支持 Ant 风格的路径模式匹配,如
    /api/*
    可以匹配所有
    /api
    开头的请求路径 。

  2. 请求方法匹配(Request Method Matching)
    :通过
    RequestMethodsRequestCondition
    来限制请求的 HTTP 方法,例如只允许 GET 或 POST 请求 。

  3. 请求参数匹配(Request Params Matching)
    :使用
    ParamsRequestCondition
    来定义请求必须包含的参数,例如某些接口可能需要特定的查询参数才能访问 。

  4. 请求头匹配(Request Headers Matching)

    HeadersRequestCondition
    允许定义请求头的条件,例如某些接口可能需要特定的认证头部才能访问 。

  5. 消费媒体类型匹配(Consumes Media Type Matching)

    ConsumesRequestCondition
    用来定义控制器方法能够处理的请求体媒体类型,通常用于 RESTful API 中,例如只处理
    application/json
    类型的请求体 。

  6. 产生媒体类型匹配(Produces Media Type Matching)

    ProducesRequestCondition
    定义了控制器方法能够返回的媒体类型,这通常与
    Accept
    请求头结合使用以确定响应的格式 。

  7. 自定义条件匹配
    :开发者可以通过实现
    RequestCondition
    接口来定义自己的匹配逻辑,例如根据请求中的版本号来路由到不同版本的 API,实现 API 的版本控制 。

  8. 组合条件匹配(Composite Conditions Matching)
    :在某些情况下,可能需要根据多个条件来匹配请求,
    CompositeRequestCondition
    可以将多个
    RequestCondition
    组合成一个条件来进行匹配 。

  9. 请求映射的优先级选择(Priority Selection for Request Mapping)
    :当存在多个匹配的处理器方法时,
    RequestCondition

    compareTo
    方法用于确定哪个条件具有更高的优先级,以选择最合适的处理器方法 。

创建一个版本映射处理器,使用
ApiVersionCondition
作为自定义条件来处理请求映射。当 Spring MVC 处理请求时,它会使用这个自定义的映射处理器来确定哪个版本的 API 应该处理请求。

public class ApiRequestMappingHandlerMapping extendsRequestMappingHandlerMapping {private static final String VERSION_FLAG = "{version}";/***检查类上是否有 @RequestMapping 注解,如果有,它会构建请求映射的 URL。如果 URL 中包含版本 
*标识 VERSION_FLAG,并且类上有 ApiVersion 注解,它将创建并返回一个 ApiVersionCondition
*实例,表示这个类关联的 API 版本。
*
*/ private static RequestCondition<ApiVersionCondition> createCondition(Class<?>clazz) {
RequestMapping classRequestMapping
= clazz.getAnnotation(RequestMapping.class);if (classRequestMapping == null) {return null;
}
StringBuilder mappingUrlBuilder
= newStringBuilder();if (classRequestMapping.value().length > 0) {
mappingUrlBuilder.append(classRequestMapping.value()[
0]);
}
String mappingUrl
=mappingUrlBuilder.toString();if (!mappingUrl.contains(VERSION_FLAG)) {return null;
}
ApiVersion apiVersion
= clazz.getAnnotation(ApiVersion.class);return apiVersion == null ? new ApiVersionCondition(1) : newApiVersionCondition(apiVersion.value());
}

@Override
protected RequestCondition<?>getCustomMethodCondition(Method method) {returncreateCondition(method.getClass());
}

@Override
protected RequestCondition<?> getCustomTypeCondition(Class<?>handlerType) {returncreateCondition(handlerType);
}
}

注册处理器

将上述的处理器注册到SpringMvc的处理流程中

@Configurationpublic class WebMvcRegistrationsConfig implementsWebMvcRegistrations {
@Override
publicRequestMappingHandlerMapping getRequestMappingHandlerMapping() {return newApiRequestMappingHandlerMapping();
}
}

验证:

@RestController
@RequestMapping(
"dataserver/{version}/account")
@ApiVersion(
1)public classAccountOneController {

@GetMapping(
"/test")publicString test() {return "测试接口,版本1";
}
@GetMapping(
"/extend")publicString extendTest() {return "版本1的测试接口延申";
}
}


@RestController
@RequestMapping(
"dataserver/{version}/account")
@ApiVersion(
2)public classAccountTwoController {
@GetMapping(
"/test")publicString test() {return "测试接口,版本2";
}
}

针对test接口进行不同版本的请求:

针对Account扩展版本调用上一版本接口

当请求对应的版本不存在接口时,会匹配之前版本的接口,即请求
/v2/account/extend
接口时,由于v2 控制器未实现该接口,所以自动匹配v1 版本中的接口。这就实现了
API版本继承

参数校验

@Validated注解

@Validated
是一个用于 Java 应用程序中的注解,特别是在 Spring 框架中,以指示目标对象或方法需要进行验证。这个注解通常与 JSR 303/JSR 380 规范的 Bean Validation API 结合使用,以确保数据的合法性和完整性。

@Validated注解的三种用法:

方法级别验证
:当
@Validated
注解用在方法上时,它指示 Spring 在调用该方法之前执行参数的验证。如果参数不符合指定的验证条件,将抛出
MethodArgumentNotValidException

@PostMapping("/user")
@Validated
publicResVo createUser(@RequestBody @Valid User user) {//方法实现 }

类级别验证
:将
@Validated
注解用在类上,表示该类的所有处理请求的方法都会进行验证。这可以减少在每个方法上重复注解的需要。

@RestController
@Validated
public classUserController {//类中的所有方法都会进行验证 }

组合注解
:Spring 还提供了
@Valid
注解,它是
@Validated
的一个更简单的形式,只触发验证并不指定特定的验证组(Validation Groups)。
@Validated
允许你指定一个或多个验证组,这在需要根据不同情况执行不同验证规则时非常有用。

@Validated(OnCreate.class)public voidcreateUser(User user) {//只使用 OnCreate 组的验证规则
}

使用注解进行参数校验

在REST API中进行参数验证一般使用方法级别验证即可,即对参数Dto的类内信息进行验证,例如一个分页的查询参数类:

@Datapublic class BaseParam implementsSerializable {

@NotNull(message
= "必须包含关键字")privateString keyFilter;

@Min(value
= 1,message = "页码不可小于1")private intpageNo;

@Max(value
= 100,message = "考虑性能问题,每页条数不可超过100")private intpageSize;

}

在Controller中配合@Validated使用:

  @PostMapping("/findProductByVo")publicPageData findByVo(@Validated ProductParam param) {//……业务逻辑
        returnPageData.success(data);
}

此时如果前端传入参数不合法,例如pageNo为0又或者productType不存在,则会抛出
MethodArgumentNotValidException
的异常。稍后对于异常进行处理即可完成参数的验证。

这里的@Max

@Min

@NotNull
注解属于 Bean Validation API 的一部分,这是一个 JSR 303/JSR 380 规范,用于在 Java 应用程序中提供声明式验证功能。这些注解用于约束字段值的范围和非空性。类似的注解还有:

注解 作用

@NotNull

验证注解的字段值不能为
null
@NotEmpty
@NotNull
类似,但用于集合或字符串,验证注解的字段值不能为
null
,且对于字符串,长度不能为 0。
@NotBlank 验证注解的字段值不能为
null
,且不能是空白字符串(空白包括空格、制表符等)。
@Min(value) 验证注解的字段值是否大于或等于指定的最小值。
value
参数接受一个整数。
@Max(value) 验证注解的字段值是否小于或等于指定的最大值。
value
参数接受一个整数。
@Size(min, max) 验证字符串或集合的大小在指定的最小值和最大值之间。
@Pattern(regex) 验证字段值是否符合指定的正则表达式。

注:SpringBoot 2.3.1 版本默认移除了校验功能,如果想要开启的话需要添加以上依赖

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>

统一异常捕获

@RestControllerAdvice注解

@RestControllerAdvice
是 @ResponseBody+@ControllerAdvice的集合注解,用于定义一个控制器级别的异常处理类。一般用来进行全局异常处理,在
@RestControllerAdvice
类中处理异常后,可以直接返回一个对象,该对象会被转换为 JSON 或 XML 响应体,返回给客户端。

使用@RestControllerAdvice注解处理参数异常


使用@Validated和 Bean Validation API 的注解进行参数校验后,当出现不符合规定的参数会抛出
MethodArgumentNotValidException
异常

,这里就可以使用@RestControllerAdvice注解来创建一个全局Controller异常拦截类,来统一处理各类异常

@RestControllerAdvicepublic classControllerExceptionAdvice {

@ExceptionHandler({MethodArgumentNotValidException .
class})//此处可以根据参数异常的各类情况进行相关异常类的绑定 publicString MethodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {//从异常对象中拿到ObjectError对象 ObjectError objectError = e.getBindingResult().getAllErrors().get(0);return "参数异常错误";
}
}

这里只以
MethodArgumentNotValidException 异常进行拦截,在
@RestControllerAdvice类内可以创建多个方法,通过@ExceptionHandler对不同的异常进行定制化处理,这样当Controller内发生异常,都可以在@RestControllerAdvice类内进行截获、处理、返回给客户端安全的信息。

@RestControllerAdvicepublic classControllerExceptionAdvice {//HttpMessageNotReadableException异常为webJSON解析出错
    @ExceptionHandler({HttpMessageNotReadableException.class})publicString MethodArgumentNotValidExceptionHandler(HttpMessageNotReadableException e) 
{
return "参数错误";
}

@ExceptionHandler({XXXXXException .
class})publicString otherExceptionHandler(Exception e) {

ObjectError objectError
= e.getBindingResult().getAllErrors().get(0);returnobjectError..getDefaultMessage();
}
}

统一响应封装

首先,进行统一的响应格式,这里需要封装一个固定返回格式的结构对象:
ResponseData

public class Response<T> implementsSerializable {privateInteger code;privateString msg;privateT data;publicResponse() {this.code = 200;this.msg = "ok";this.data = null;
}
publicResponse(Integer code, String msg, T data) {this.code =code;this.msg =msg;this.data =data;
}
publicResponse(String msg, T data) {this(200, msg, data);
}
publicResponse(T data) {this("ok", data);
}
public static <T> Response<T>success(T data) {return newResponse(data);
}
publicInteger getCode() {return this.code;
}
public voidsetCode(Integer code) {this.code =code;
}
publicString getMsg() {return this.msg;
}
public voidsetMsg(String msg) {this.msg =msg;
}
publicT getData() {return this.data;
}
public voidsetData(T data) {this.data =data;
}
publicString toJsonString() {
String out
= "";try{
out
= JSONUtil.toJsonPrettyStr(this);
}
catch(Exception var3) {this.setData(null);
var3.printStackTrace();
}
returnout;
}
}

统一状态码

其中对于相关的状态码最好进行统一的封装,便于以后的开发,创建状态枚举:

//面向接口开发,首先定义接口
public interfaceStatusCode {
Integer getCode();
String getMessage();
}
//创建枚举类 public enum ResponseStatus implementsStatusCode{//正常响应 SUCCESS(200, "success"),//服务器内部错误 FAILED(500, " Server Error"),//参数校验错误 VALIDATE_ERROR(400, "Bad Request");//……补充其他内部约定状态 private intcode;privateString msg;

ResponseStatus(
intcode, String msg) {this.code =code;this.msg =msg;
}

@Override
publicInteger getCode() {return this.code;
}

@Override
publicString getMessage() {return this.msg;
}
}

统一返回结构

将上述的
ResponseData
中状态类相关替换为枚举

public class Response<T> implementsSerializable {privateInteger code;privateString msg;privateT data;publicResponse() {this.code = 200;this.msg = "success";this.data = null;
}
publicResponse(StatusCode status, T data) {this.code =status.getCode();this.msg =status.getMssage();this.data =data;
}
publicResponse(T data) {this(ResponseStatus.SUCCESS, data);
}
public static <T> Response<T>success(T data) {return newResponse(data);
}
publicInteger getCode() {return this.code;
}
public voidsetCode(Integer code) {this.code =code;
}
publicString getMsg() {return this.msg;
}
public voidsetMsg(String msg) {this.msg =msg;
}
publicT getData() {return this.data;
}
public voidsetData(T data) {this.data =data;
}
publicString toJsonString() {
String out
= "";try{
out
= JSONUtil.toJsonPrettyStr(this);
}
catch(Exception var3) {this.setData(null);
var3.printStackTrace();
}
returnout;
}
}

这样Controller的接口统一返回格式就是标准的结构了。

{"code":200,"msg":"success","data":{"total":123,"record":[]
}
}

统一封装Controller响应

有了统一响应体的Controller在返回时可以这样写:

    @PostMapping("/search")
@Operation(summary
= "分页查询任务")publicResponse searchData(@RequestBody SearchParam param){returnResponse.success(XXXXService.searchForPage(param));
}

即便如此,团队开发中可能还会出现换个人新写Controller不知道有统一返回体这回事,为了更保险,可以通过AOP进行统一对结果进行封装,不论Controller返回啥,到客户端的数据都包含一个包装体。

具体实现是使用
@RestControllerAdvice类
实现
ResponseBodyAdvice
接口来完成。

@RestControllerAdvicepublic class ControllerResponseAdvice implements ResponseBodyAdvice<Object>{
@Override
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>>aClass) {//返回结构是Response类型都不进行包装 return !methodParameter.getParameterType().isAssignableFrom(Response.class);
}

@Override
public Object beforeBodyWrite(Object data, MethodParameter returnType, MediaType mediaType, Class<? extends HttpMessageConverter<?>>aClass, ServerHttpRequest request, ServerHttpResponse response) {//String类型不能直接包装 if (returnType.getGenericParameterType().equals(String.class)) {
ObjectMapper objectMapper
= newObjectMapper();try{//将数据包装在ResultVo里后转换为json串进行返回 returnobjectMapper.writeValueAsString(Response.success(data));
}
catch(JsonProcessingException e) {
e.printStackTrace();
}
}
//其他所有结果统一包装成Response返回 returnResponse.success(data);
}
}

我们以test接口为例,test接口原本返回的是String,而toint返回的是Integer

@RestController
@RequestMapping(
"dataserver/{version}/account")
@ApiVersion(
1)public classAccountOneController {

@GetMapping(
"/test")publicString test() {return "测试接口,版本1";
}

@GetMapping(
"/toint")publicInteger toint() {return 1;
}

}

但是页面返回是JSON字符串和返回体:

文档:调试维护API利器—Swagger

接口开发完成,调试时,大多数都是使用Postman模拟请求调试或者直接用前端代码调用调试,其实这两种都比较麻烦,尤其面对复制参数时,Postman要逐个接口的录入,十分费事,其实这里可以在SpringBoot中引入Swagger接口文档组件,接口文档和接口调试一并解决。

引入依赖

直接在maven中引入相关依赖:

     <!--swagger 3-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-boot-starter</artifactId>
            <version>3.0.0</version>
        </dependency>
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-spring-boot-starter</artifactId>
            <version>3.0.3</version>
        </dependency>

标准的swagger3引入以上两个依赖即可,相关版本可自行选择

装配配置类

下面进行swagger的配置类和一些swagger相关页面的配置

@Configurationpublic classSwaggerConfig {


@Bean
publicDocket testApis(){return newDocket(DocumentationType.OAS_30)
.apiInfo(apidoc())
.select()
.apis(RequestHandlerSelectors.basePackage(
"net.gcc.webrestapi.controller"))
.paths(PathSelectors.any())
.build()
.groupName(
"测试服务")
.enable(
true);
}
privateApiInfo apidoc(){return newApiInfoBuilder()
.title(
"测试接口")
.description(
"接口文档")
.contact(
new Contact("GCC", "#", "XXXX"))
.version(
"1.0")
.build();
}
}

使用注解

Swagger相关注解明细

注解 使用位置 作用
@Api 作用在类上,
Controller
表示对类的说明,通常用于描述 Controller 的作用和分类,如
@Api(tags = "用户管理"),后续会在Swagger文档中以 目录形式 展示
@ApiOperation 作用在方法上,一般为
Controller中具体方法
描述 API 接口的具体操作和功能,例如
@ApiOperation(value = "获取用户列表", notes = "根据条件获取用户列表")
,在swagger文档中以目录内容体现
@ApiModel 作用于类上,一般是
参数实体类
表示这是一个模型类,通常与
@ApiModelProperty
结合使用来描述模型属性 。
@ApiModelProperty 用于模型类的属性上,
参数类的成员变量
描述属性的信息,如
@ApiModelProperty(value = "用户名", required = true)
@ApiImplicitParams

@ApiImplicitParam
用于方法上,一般为
Controller中具体方法
描述接口的隐含参数,例如请求参数或请求头信息
@ApiResponses

@ApiResponse
用于方法上,一般为
Controller中具体方法
描述接口的响应信息,可以指定不同的响应状态码和对应的描述信息 。
@ApiIgnore 用于类或方法上 表示忽略该类或方法,不将其显示在Swagger文档中。

@Api

@ApiOperation
使用

@RestController
@RequestMapping(
"/dataserver/{version}/manage")
@Api(tags
= "数据源管理服务", description = "用于管理数据源信息")
@ApiVersion
public classDataServerController {

@PostMapping(
"/search")
@ApiOperation(summary
= "分页查询数据源")public IPage<DataSourceEntity>searchData(@RequestBody SearchParam param){//XXXX逻辑 return new IPage<DataSourceEntity>();
}


}

@ApiMode

@ApiModelProperty

@Data
@ApiModel(value
= "基础参数")public class BaseParam implementsSerializable {

@ApiModelProperty(value
= "关键字", required = true)
@NotNull(message
= "必须包含关键字")privateString keyFilter;
@ApiModelProperty(value
= "页码", required = true)
@Min(value
= 1,message = "页码不可小于1")private intpageNo;
@ApiModelProperty(value
= "每页大小", required = true)
@Max(value
= 100,message = "考虑性能问题,每页条数不可超过100")private intpageSize;

}

@ApiImplicitParams

@ApiImplicitParam

与ApiMode和ApiModeProperty功能一致,一般用于get请求中的参数描述

   @GetMapping("/extend")
@ApiOperation(value
= "账号角色",notes = "测试版本1延申接口")
@ApiImplicitParams({
@ApiImplicitParam(value
= "accountId",name = "账号ID"),
@ApiImplicitParam(value
= "role",name = "角色")
}
)
publicString extendTest(String accountId,String role) {return new JSONObject().set("account",accountId).set("role",role).toJSONString(0);
}

效果

使用swagger后,直接在页面访问 http://127.0.0.1:8080/XXX/doc.html即可访问接口页面

不要复杂的postman调用,本地调试可以直接使用调试功能

补充:完整的Controller类代码模板

@RestController
@RequestMapping(
"/dataserver/{version}/manage")
@Api(tags
= "数据源管理服务V1")
@ApiVersion
public classDataServerController {

@PostMapping(
"/search")
@ApiOperation(value
= "分页查询数据源",notes = "测试")public PageVo<DataSourceVo>searchData(@RequestBody BaseParam param){//XXXX逻辑 return new PageVo<DataSourceVo>();
}
//get请求,使用ApiImplicitParams注解标明参数 @GetMapping("/searchAccountAndRole")
@ApiOperation(value
= "账号角色",notes = "查询账号角色")
@ApiImplicitParams({
@ApiImplicitParam(value
= "accountId",name = "账号ID"),
@ApiImplicitParam(value
= "role",name = "角色")
})
publicString extendTest(String accountId,String role) {return new JSONObject().set("account",accountId).set("role",role).toJSONString(0);
}

}
//部分参数代码: @Data
@ApiModel
public class BaseParam implementsSerializable {
@NotNull(message
= "必须包含关键字")
@ApiModelProperty(
"关键字过滤")privateString keyFilter;

@Min(value
= 1,message = "页码不可小于1")
@ApiModelProperty(
"分页页码")private intpageNo;

@Max(value
= 100,message = "考虑性能问题,每页条数不可超过100")
@ApiModelProperty(
"分页每页条数")private intpageSize;

}
//响应部分代码 @Data
@ApiModel
public class DataSourceVo implementsSerializable {
@ApiModelProperty(
"id")privateString id;
@ApiModelProperty(
"数据源名称")privateString name;
@ApiModelProperty(
"数据源url")privateString url;
}

@Data
@ApiModel
public class PageVo<V>{

@ApiModelProperty(
"总数量")private inttotal;
@ApiModelProperty(
"具体内容")private List<V>rows;
}

补充:完整的@RestControllerAdvice类代码模板

关于参数验证的异常处理和统一返回结构,可以使用一个类来完成,以下是完整模板:

@RestControllerAdvice(basePackages = "net.gcc.webrestapi")public class ControllerExceptionAdvice implements ResponseBodyAdvice<Object>{


@Override
public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>>aClass) {//返回结构是Response类型都不进行包装 return !methodParameter.getParameterType().isAssignableFrom(Response.class);
}

@Override
public Object beforeBodyWrite(Object data, MethodParameter returnType, MediaType mediaType, Class<? extends HttpMessageConverter<?>>aClass, ServerHttpRequest request, ServerHttpResponse response) {//String类型不能直接包装 if (returnType.getGenericParameterType().equals(String.class)) {
ObjectMapper objectMapper
= newObjectMapper();try{//将数据包装在ResultVo里后转换为json串进行返回 returnobjectMapper.writeValueAsString(Response.success(data));
}
catch(JsonProcessingException e) {
e.printStackTrace();
}
}
//系统特殊错误 if(data instanceofLinkedHashMap&& ((LinkedHashMap<?, ?>) data).containsKey("status")&& ((LinkedHashMap<?, ?>) data).containsKey("message")&&((LinkedHashMap<?, ?>) data).containsKey("error")){int code = Integer.parseInt(((LinkedHashMap<?, ?>) data).get("status").toString());
String mssage
= ((LinkedHashMap<?, ?>) data).get("error").toString();return new Response<>(code,mssage,null);
}
//其他所有结果统一包装成Response返回 returnResponse.success(data);
}


@ExceptionHandler({MethodArgumentNotValidException.
class})publicResponse MethodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e)
{
//默认统一返回响应体,填写参数错误编码, 从异常对象中拿到错误信息 return new Response(401,e.getBindingResult().getAllErrors().get(0).getDefaultMessage(),"");
}
//HttpMessageNotReadableException异常为webJSON解析出错 @ExceptionHandler({HttpMessageNotReadableException.class})publicResponse HttpNotReqadableExceptionHandler(HttpMessageNotReadableException e)
{
//默认统一返回响应体,填写参数错误编码, 从异常对象中拿到错误信息 return new Response(401,"参数解析错误","");
}

}

标签: none

添加新评论