write sth. upfront
Blogger recently did a data service project, and the core of this data service is the external exposure of the API, it is worthwhile to be happy that this is a project from 0, so finally do not have to be subject to "some historical" factors to continue to write a variety of styles of Controller, you can be in the beginning of the project to standardize the technology and unify the form to build the API. Take this opportunity to sort out and summarize the SpringBoot project based on the development of REST API technical points and specification points.
Interface services are mainly composed of two parts, namely, the parameters (input) part, the response (output) part. Which in SpringBoot is mainly Controller layer as the API development, in fact, at the architectural level.Controlleritself is a top application layer, its responsibility is to call, assemble the lower layer of interface service data, the core is assembled and called, should not be mixed with other related logic.
But often many projects for the Controller part of the code are very confusing, some of theControllerJuggling various if else parameter checks, some even directly in the Controller for business code writing; for theControlleroutput, some roughly add a wrapper, some even directly throw the service layer structure directly out; for the handling of exceptions are also various.
The above is true forControllerrelated issues, which are harmonized here with a series ofControllerThe encapsulation process to provide optimization ideas. The following steps are required to develop a REST API in an elegant and standardized manner:
- interface version control
- parameter calibration
- Exception Catch Handling
- Unified Response Package
- Maintenance and updating of interface documentation
@RestController annotation
Look directly at the @RestController source code
@Target({}) @Retention() @Documented @Controller @ResponseBody public @interface RestController { @AliasFor( annotation = Controller.class ) String value() default ""; }
The @RestController annotation is equivalent to @Controller and @@ResponseBody. The @ResponseBody annotation serves to tell the Spring MVC framework that the method's return value should be written directly into the HTTP response body instead of returning a View. When a controller method is tagged with@ResponseBody
When you do, Spring MVC serializes the return value of the method into a format such as JSON or XML and sends it to the client. It is more suitable for REST API construction.
So for the development of the Controller interface, theIt is better to use @RestController directly. It will automatically convert the return content of the methods under the Controller to REST API form。
Example:
@RestController @RequestMapping("/dataserver/manage") public class DataServerController{ @PostMapping("/search") public Response searchData(@RequestBody SearchTaskDto param){ return ((param)); } }
Interface Versioning
For the API, generally speaking, is the basis for external services, can not be changed at will, but with the changing needs and business, the interface and parameters will change accordingly. At this time, as far as possible to ensure that the "open and close principle" to add new interfaces or enhance the interface function to support, at this time it is necessary to maintain the version of the API to determine the version number of the same interface of the different capabilities, the general version of the control based on the url!
Example:
http://localhost:8080/dataserver/v1/queryAccount
http://localhost:8080/dataserver/v2/queryAccount: Enhanced parameter query flexibility compared to v1
There are three main steps to perform API version control:
- Define the version number annotation
- Write a version number matching logic processor
- Registration Processor
Define the version number annotation
/** * :: API version control annotations */ @Target({}) @Retention() public @interface ApiVersion { /** *Version number, default is 1 */ int value() default 1; }
This annotation can be used directly on the Controller class
@RestController @RequestMapping("dataserver/{version}/account") @ApiVersion(2)//Enter the version number, which corresponds to {version}. public class AccountController{ @GetMapping("/test") public String test() { return "XXXX"; } }
Write a version number matching logic processor
First define a conditional matching class that parses the version and ApiVersion annotations in the Url.
/** * Implements Request's conditional matching interface * **/ public class ApiVersionCondition implements RequestCondition<ApiVersionCondition> { private final static Pattern VERSION_PREFIX_PATTERN = (".*v(\\d+).*"); private int apiVersion; ApiVersionCondition(int apiVersion) { this.apiVersion = apiVersion; } private int getApiVersion() { return apiVersion; } @Override public ApiVersionCondition combine(ApiVersionCondition apiVersionCondition) { return new ApiVersionCondition(()); } @Override public ApiVersionCondition getMatchingCondition(HttpServletRequest httpServletRequest) { Matcher m = VERSION_PREFIX_PATTERN.matcher(()); if (()) { Integer version = ((1)); if (version >= this.apiVersion) { return this; } } return null; } @Override public int compareTo(ApiVersionCondition apiVersionCondition, HttpServletRequest httpServletRequest) { return () - this.apiVersion; } }
Here's something to addRequestCondition<ApiVersionCondition>Related concepts:
It is part of the Spring framework for request mapping processing. In Spring MVC, the
RequestCondition
The interface allows developers to define custom request matching logic, which can be based on any attribute of the request, such as paths, parameters, HTTP methods, headers, and so on. Relevant application scenarios include:
Path Matching: Use
PatternsRequestCondition
to define the request's path pattern, with support for Ant-style path pattern matching, such as/api/*
can match all/api
The request path that begins with .Request Method Matching (RMM): By
RequestMethodsRequestCondition
to restrict the HTTP method of a request, such as allowing only GET or POST requests.Request Params Matching: Use
ParamsRequestCondition
to define the parameters that must be included in the request, e.g. some interfaces may require specific query parameters to be accessed .Request Headers Matching:
HeadersRequestCondition
Allows the definition of request header conditions, e.g. some interfaces may require specific authentication headers for access .Consumes Media Type Matching (CMTM):
ConsumesRequestCondition
is used to define the types of requestor media that a controller method can handle, typically used in RESTful APIs, such as handling only theapplication/json
Type of request body .Produces Media Type Matching:
ProducesRequestCondition
defines the type of media that can be returned by the controller method, which is usually the same as theAccept
The request header is used in combination to determine the format of the response .Customized Conditional Matching: The developer can achieve this by implementing the
RequestCondition
interface to define its own matching logic, such as routing to different versions of the API based on the version number in the request, realizing version control of the API.Composite Conditions Matching (CCM): In some cases, it may be necessary to match requests based on more than one condition.
CompositeRequestCondition
It is possible to combine multipleRequestCondition
Combine them into one condition to match .Priority Selection for Request Mapping (PREM): When multiple matching processor methods exist, the
RequestCondition
(used form a nominal expression)compareTo
method is used to determine which condition has higher priority to select the most appropriate processor method .
Create a version-mapping processor using theApiVersionCondition
Handles request mapping as a custom condition. When Spring MVC processes a request, it uses this custom mapping handler to determine which version of the API should handle the request.
public class ApiRequestMappingHandlerMapping extends RequestMappingHandlerMapping { private static final String VERSION_FLAG = "{version}"; /** * Checks if there is a @RequestMapping annotation on the class, and if there is, it constructs a URL for the request mapping. if the URL contains the version * identifier VERSION_FLAG and there is an ApiVersion annotation on the class, it will create and return an ApiVersionCondition * instance representing the version of the API associated with the class. **/ private static RequestCondition<ApiVersionCondition> createCondition(Class<?> clazz) { RequestMapping classRequestMapping = (RequestMapping.class); if (classRequestMapping == null) { return null; } StringBuilder mappingUrlBuilder = new StringBuilder(); if (().length > 0) { (()[0]); } String mappingUrl = (); if (!(VERSION_FLAG)) { return null; } ApiVersion apiVersion = (ApiVersion.class); return apiVersion == null ? new ApiVersionCondition(1) : new ApiVersionCondition(()); } @Override protected RequestCondition<?> getCustomMethodCondition(Method method) { return createCondition(()); } @Override protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) { return createCondition(handlerType); } }
Registration Processor
Register the above processor with SpringMvc's processing flow
@Configuration public class WebMvcRegistrationsConfig implements WebMvcRegistrations { @Override public RequestMappingHandlerMapping getRequestMappingHandlerMapping() { return new ApiRequestMappingHandlerMapping(); } }
Validation:
@RestController @RequestMapping("dataserver/{version}/account") @ApiVersion(1) public class AccountOneController { @GetMapping("/test") public String test() { return "Test interface, version 1"; } @GetMapping("/extend") public String extendTest() { return "Test interface extension for version 1."; } } @RestController @RequestMapping("dataserver/{version}/account") @ApiVersion(2) public class AccountTwoController { @GetMapping("/test") public String test() { return "Test interface, version 2."; } }
Different versions of the request are made against the test interface:
Calling the previous version of the interface for the Account extension version
When an interface does not exist for the corresponding version of the request, it will match the previous version of the interface, i.e., the request for the/v2/account/extend
interface, it automatically matches the interface in the v1 version since it is not implemented in the v2 controller. This implements theAPI version inheritance。
parameter calibration
@Validated annotation
@Validated
is an annotation used in Java applications, especially in the Spring framework, to indicate that the target object or method needs to be validated. This annotation is typically used in conjunction with the JSR 303/JSR 380 specification of the Bean Validation API to ensure the legitimacy and integrity of data.
Three uses of the @Validated annotation:
Method level validation(coll.) ding dong@Validated
When the annotation is used on a method, it instructs Spring to perform validation of the parameters before calling the method. If the parameter does not meet the specified validation criteria, theMethodArgumentNotValidException
。
@PostMapping("/user") @Validated public ResVo createUser(@RequestBody @Valid User user) { // method implementation }
Class Level Validation: Will@Validated
Annotations are used on classes to indicate that all methods of the class that handle requests are validated. This reduces the need to duplicate annotations on each method.
@RestController @Validated public class UserController { // All methods in the class are validated }
composite annotation: Spring also provides@Valid
annotation, which is@Validated
In a simpler form, only validation is triggered and no specific Validation Groups are specified.@Validated
Allows you to specify one or more validation groups, which is useful when you need to enforce different validation rules depending on the situation.
@Validated(OnCreate.class) public void createUser(User user) { // Validation rules using only OnCreate groups }
Parameter validation using annotations
Parameter validation in REST APIs generally uses method-level validation, i.e., the validation of information within the class of the parameter Dto, such as a paged query parameter class:
@Data public class BaseParam implements Serializable { @NotNull(message = "Must contain keywords") private String keyFilter; @Min(value = 1,message = "The page number may not be less than 1.") private int pageNo; @Max(value = 100,message = "For performance reasons, the number of entries per page should not exceed 100.") private int pageSize; }
Used in conjunction with @Validated in the Controller:
@PostMapping("/findProductByVo") public PageData findByVo(@Validated ProductParam param) { //...... business logic return (data); }
At this point, if the front-end incoming parameters are not legal, such as pageNo is 0 or the productType does not exist, then it will throw theMethodArgumentNotValidException
The exception will be handled later. The validation of the parameters is done by handling the exception later.
Here's @Max.
、@Min
cap (a poem)@NotNull
Annotations are part of the Bean Validation API, a JSR 303/JSR 380 specification for providing declarative validation capabilities in Java applications. These annotations are used to constrain the range and non-nullability of field values. Similar annotations are available:
explanatory note | corresponds English -ity, -ism, -ization |
@NotNull |
The value of a field in a validation annotation cannot benull 。 |
@NotEmpty | together with@NotNull Similar, but for collections or strings, the field value of the validation annotation cannot benull and for strings, the length cannot be 0. |
@NotBlank | The value of a field in a validation annotation cannot benull , and cannot be a blank string (blanks include spaces, tabs, etc.). |
@Min(value) | Verifies that the annotation's field value is greater than or equal to the specified minimum value.value parameter accepts an integer. |
@Max(value) | Verifies that the annotation's field value is less than or equal to the specified maximum value.value parameter accepts an integer. |
@Size(min, max) | Verifies that the size of the string or collection is between the specified minimum and maximum values. |
@Pattern(regex) | Verifies that the field value matches the specified regular expression. |
Note: SpringBoot version 2.3.1 removed the checksum function by default, if you want to enable it, you need to add the above dependencies
<dependency> <groupId></groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>
Unified Exception Catching
@RestControllerAdvice annotation
@RestControllerAdvice
is a collection annotation for @ResponseBody + @ControllerAdvice that defines a controller-level exception handling class. It is generally used for global exception handling in the@RestControllerAdvice
After handling the exception in the class, you can directly return an object that will be converted into a JSON or XML response body and returned to the client.
Handling parameter exceptions with the @RestControllerAdvice annotation
@RestControllerAdvice
is a collection annotation for @ResponseBody + @ControllerAdvice that defines a controller-level exception handling class. It is generally used for global exception handling in the@RestControllerAdvice
After handling the exception in the class, you can directly return an object that will be converted into a JSON or XML response body and returned to the client.
existAfter using @Validated and the Bean Validation API annotations for parameter validation, non-conforming parameters will throw aMethodArgumentNotValidException
exceptionsHere you can use the @RestControllerAdvice annotation to create a global Controller exception blocking class to unify the handling of all types of exceptions
@RestControllerAdvice public class ControllerExceptionAdvice { @ExceptionHandler({MethodArgumentNotValidException .class})//Here you can bind the relevant exception classes according to the various types of parameter exceptions public String MethodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) { // Get the ObjectError object from the exception object ObjectError objectError = ().getAllErrors().get(0); return "Parameter exception error"; } }
Here's just a sample of theMethodArgumentNotValidException The exception is intercepted in the
Multiple methods can be created within the @RestControllerAdvice class to customize the handling of different exceptions via @ExceptionHandler, so that when an exception occurs within the Controller, it can all be intercepted, handled, and returned to the client safely within the @RestControllerAdvice class. The @RestControllerAdvice class.
@RestControllerAdvice public class ControllerExceptionAdvice { //HttpMessageNotReadableException for webJSON parsing error! @ExceptionHandler({HttpMessageNotReadableException.class}) public String MethodArgumentNotValidExceptionHandler(HttpMessageNotReadableException e) { return "Parameter error."; } @ExceptionHandler({XXXXXException .class}) public String otherExceptionHandler(Exception e) { ObjectError objectError = ().getAllErrors().get(0); return objectError..getDefaultMessage(); } }
Unified Response Package
First, a uniform response format is performed, where a structure object with a fixed return format needs to be encapsulated:ResponseData
public class Response<T> implements Serializable { private Integer code; private String msg; private T data; public Response() { this.code = 200; this.msg = "ok"; this.data = null; } public Response(Integer code, String msg, T data) { this.code = code; this.msg = msg; this.data = data; } public Response(String msg, T data) { this(200, msg, data); } public Response(T data) { this("ok", data); } public static <T> Response<T> success(T data) { return new Response(data); } public Integer getCode() { return this.code; } public void setCode(Integer code) { this.code = code; } public String getMsg() { return this.msg; } public void setMsg(String msg) { this.msg = msg; } public T getData() { return this.data; } public void setData(T data) { this.data = data; } public String toJsonString() { String out = ""; try { out = (this); } catch (Exception var3) { this.setData(null); (); } return out; } }
Unicode
One of the best things to do is to create a unified encapsulation of the relevant state codes to facilitate future development and create state enumerations:
//For interface-oriented development, first define the interface public interface StatusCode { Integer getCode(); String getMessage(); } //Creating Enumerated Classes public enum ResponseStatus implements StatusCode{ //normal response SUCCESS(200, "success"), //Internal server error FAILED(500, " Server Error"), //Parameter calibration error VALIDATE_ERROR(400, "Bad Request"); //...... Supplement other internal engagement status private int code; private String msg; ResponseStatus(int code, String msg) { this.code = code; this.msg = msg; } @Override public Integer getCode() { return this.code; } @Override public String getMessage() { return this.msg; } }
Harmonization of return structures
premise sth.ResponseDataReplace the state class correlation with an enumeration in
public class Response<T> implements Serializable { private Integer code; private String msg; private T data; public Response() { this.code = 200; this.msg = "success"; this.data = null; } public Response(StatusCode status, T data) { this.code = (); this.msg = (); this.data = data; } public Response(T data) { this(, data); } public static <T> Response<T> success(T data) { return new Response(data); } public Integer getCode() { return this.code; } public void setCode(Integer code) { this.code = code; } public String getMsg() { return this.msg; } public void setMsg(String msg) { this.msg = msg; } public T getData() { return this.data; } public void setData(T data) { this.data = data; } public String toJsonString() { String out = ""; try { out = (this); } catch (Exception var3) { this.setData(null); (); } return out; } }
This way Controller's interface uniform return format is the standard structure.
{ "code":200, "msg":"success", "data":{ "total":123, "record":[] } }
Unified Encapsulation of Controller Responses
A Controller with a unified response body can be written like this on return:
@PostMapping("/search") @Operation(summary = "Paging query task") public Response searchData(@RequestBody SearchParam param){ return ((param)); }
Even so, the team may also appear in the development of a new person to write a new Controller do not know that there is a unified return to the body of this matter, in order to be more secure, you can unify the results of the package through the AOP, regardless of what the Controller to return to the client's data are included in a wrapper.
This is achieved by using the@RestControllerAdvice classrealizationResponseBodyAdviceinterface to accomplish this.
@RestControllerAdvice public class ControllerResponseAdvice implements ResponseBodyAdvice<Object> { @Override public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) { // The return structure is the Response type which is not wrapped. return !().isAssignableFrom(Response.class); } @Override public Object beforeBodyWrite(Object data, MethodParameter returnType, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest request, ServerHttpResponse response) { // String types cannot be wrapped directly if (().equals(String.class)) { ObjectMapper objectMapper = new ObjectMapper(); try { // Wrap the data in ResultVo and convert it to a json string for return. return ((data)); } catch (JsonProcessingException e) { (); } } // All other results are uniformly wrapped into a Response to return return (data); } }
Let's take the test interface as an example. The test interface originally returns a String, while the toint returns an Integer.
@RestController @RequestMapping("dataserver/{version}/account") @ApiVersion(1) public class AccountOneController { @GetMapping("/test") public String test() { return "Test interface, version 1"; } @GetMapping("/toint") public Integer toint() { return 1; } }
But the page returns a JSON string and a return body:
Documentation: Debugging and Maintaining APIs - Swagger
interface development is complete, debugging, most are using Postman simulation request debugging or directly with the front-end code call debugging, in fact, these two are more troublesome, especially in the face of copying the parameters, Postman to be recorded interface by interface, very troublesome, in fact, here you can introduce the Swagger interface documentation components in SpringBoot, interface documentation and interface debugging and debugging interfaces can be solved together.
Introducing dependencies
Introduce the relevant dependencies directly in maven:
<!-- swagger 3 --> <dependency> <groupId></groupId> <artifactId>springfox-boot-starter</artifactId> <version>3.0.0</version> </dependency> <dependency> <groupId></groupId> <artifactId>knife4j-spring-boot-starter</artifactId> <version>3.0.3</version> </dependency>
The standard swagger3 introduces the above two dependencies, the relevant versions are optional
Assembly Configuration Class
Here are the swagger configuration classes and some swagger related pages to configure
@Configuration public class SwaggerConfig { @Bean public Docket testApis(){ return new Docket(DocumentationType.OAS_30) .apiInfo(apidoc()) .select() .apis((""))
.paths(()) .build() .groupName("Testing services") .enable(true); } private ApiInfo apidoc(){ return new ApiInfoBuilder() .title("Test Interface") .description("Interface Documentation") .contact(new Contact("GCC", "#", "XXXX")) .version("1.0") .build(); } }
Using Annotations
Swagger-related annotations
explanatory note | Location of use | corresponds English -ity, -ism, -ization |
@Api | acting on the class.Controllerresemble | Indicates a description of the class, usually used to describe the role and classification of the Controller, such as@Api(tags = "user management"), which will be followed up in the Swagger docs with theCatalog formshowcase
|
@ApiOperation | Acting on the methodology, generallySpecific methods in Controller | Describe the specific operations and functions of the API interface, for example@ApiOperation(value = "Get a list of users", notes = "Get a list of users based on conditions") The content of the catalog is reflected in the swagger documentation. |
@ApiModel | Acting on classes is generallyparameterized entity class | Indicates that this is a model class, usually associated with@ApiModelProperty Used in combination to describe model properties . |
@ApiModelProperty | used on the attributes of the model class.Member variables of the parameter class | Information describing the attributes, such as@ApiModelProperty(value = "user ID", required = true)
|
@ApiImplicitParams cap (a poem)@ApiImplicitParam | Used in the methodology, it is generallySpecific methods in Controller | Implicit parameters describing the interface, such as request parameters or request headers |
@ApiResponses cap (a poem)@ApiResponse | Used in the methodology, it is generallySpecific methods in Controller | Describe the response information of the interface. you can specify different response status codes and corresponding description information . |
@ApiIgnore | Used on classes or methods | Indicates that the class or method is ignored and not displayed in the Swagger document. |
@Apicap (a poem)@ApiOperationutilization
@RestController @RequestMapping("/dataserver/{version}/manage") @Api(tags = "Data source management services", description = "for managing data source information") @ApiVersion public class DataServerController { @PostMapping("/search") @ApiOperation(summary = "Paged query data source") public IPage<DataSourceEntity> searchData(@RequestBody SearchParam param){ //XXXX logic return new IPage<DataSourceEntity>(); } }
@ApiModecap (a poem)@ApiModelProperty
@Data @ApiModel(value = "Basic parameters") public class BaseParam implements Serializable { @ApiModelProperty(value = "Keywords", required = true) @NotNull(message = "Must contain keywords") private String keyFilter; @ApiModelProperty(value = "Page number", required = true) @Min(value = 1,message = "The page number may not be less than 1.") private int pageNo; @ApiModelProperty(value = "Size per page", required = true) @Max(value = 100,message = "For performance reasons, the number of entries per page should not exceed 100.") private int pageSize; }
@ApiImplicitParams cap (a poem)@ApiImplicitParam
Same function as ApiMode and ApiModeProperty, generally used for parameter descriptions in get requests
@GetMapping("/extend") @ApiOperation(value = "Account Role",notes = "Test Version 1 Extension Interface") @ApiImplicitParams({ @ApiImplicitParam(value = "accountId",name = "Account ID"), @ApiImplicitParam(value = "role",name = "Role.") } ) public String extendTest(String accountId,String role) { return new JSONObject().set("account",accountId).set("role",role).toJSONString(0); }
effect
After using swagger, visit http://127.0.0.1:8080/XXX/Access to the interface page directly on the page
Don't make complicated postman calls, local debugging can be done directly using the debugging function
Supplementary: complete Controller class code template
@RestController
@RequestMapping("/dataserver/{version}/manage")
@Api(tags = "Data Source Management Service V1")
@ApiVersion
public class DataServerController {
@PostMapping("/search")
@ApiOperation(value = "Paged query data source",notes = "Testing.")
public PageVo<DataSourceVo> searchData(@RequestBody BaseParam param){
//XXXX logic
return new PageVo<DataSourceVo>();
}
//get requests, use the ApiImplicitParams annotation to indicate the parameters
@GetMapping("/searchAccountAndRole")
@ApiOperation(value = "Account Role",notes = "Query Account Role")
@ApiImplicitParams({
@ApiImplicitParam(value = "accountId",name = "Account ID"),
@ApiImplicitParam(value = "role",name = "Role.")
})
public String extendTest(String accountId,String role) {
return new JSONObject().set("account",accountId).set("role",role).toJSONString(0);
}
}
//Part of the parameter code:
@Data
@ApiModel
public class BaseParam implements Serializable {
@NotNull(message = "Must contain keywords")
@ApiModelProperty("Keyword filtering")
private String keyFilter;
@Min(value = 1,message = "The page number may not be less than 1.")
@ApiModelProperty("Page breaks")
private int pageNo;
@Max(value = 100,message = "For performance reasons, the number of entries per page should not exceed 100.")
@ApiModelProperty("Number of entries per page for pagination")
private int pageSize;
}
//Response section code
@Data
@ApiModel
public class DataSourceVo implements Serializable {
@ApiModelProperty("id")
private String id;
@ApiModelProperty("Data source name")
private String name;
@ApiModelProperty("Data source url")
private String url;
}
@Data
@ApiModel
public class PageVo<V> {
@ApiModelProperty("Total number")
private int total;
@ApiModelProperty("Specifics")
private List<V> rows;
}
Addendum: Complete @RestControllerAdvice class code template
@RestController @RequestMapping("/dataserver/{version}/manage") @Api(tags = "Data Source Management Service V1") @ApiVersion public class DataServerController { @PostMapping("/search") @ApiOperation(value = "Paged query data source",notes = "Testing.") public PageVo<DataSourceVo> searchData(@RequestBody BaseParam param){ //XXXX logic return new PageVo<DataSourceVo>(); } //get requests, use the ApiImplicitParams annotation to indicate the parameters @GetMapping("/searchAccountAndRole") @ApiOperation(value = "Account Role",notes = "Query Account Role") @ApiImplicitParams({ @ApiImplicitParam(value = "accountId",name = "Account ID"), @ApiImplicitParam(value = "role",name = "Role.") }) public String extendTest(String accountId,String role) { return new JSONObject().set("account",accountId).set("role",role).toJSONString(0); } } //Part of the parameter code: @Data @ApiModel public class BaseParam implements Serializable { @NotNull(message = "Must contain keywords") @ApiModelProperty("Keyword filtering") private String keyFilter; @Min(value = 1,message = "The page number may not be less than 1.") @ApiModelProperty("Page breaks") private int pageNo; @Max(value = 100,message = "For performance reasons, the number of entries per page should not exceed 100.") @ApiModelProperty("Number of entries per page for pagination") private int pageSize; } //Response section code @Data @ApiModel public class DataSourceVo implements Serializable { @ApiModelProperty("id") private String id; @ApiModelProperty("Data source name") private String name; @ApiModelProperty("Data source url") private String url; } @Data @ApiModel public class PageVo<V> { @ApiModelProperty("Total number") private int total; @ApiModelProperty("Specifics") private List<V> rows; }
Regarding the exception handling and uniform return structure for parameter validation, this can be done using a single class, and the following is the complete template:
@RestControllerAdvice(basePackages = "") public class ControllerExceptionAdvice implements ResponseBodyAdvice<Object> { @Override public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) { // The return structure is the Response type which is not wrapped. return !().isAssignableFrom(Response.class); } @Override public Object beforeBodyWrite(Object data, MethodParameter returnType, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest request, ServerHttpResponse response) { // String types cannot be wrapped directly if (().equals(String.class)) { ObjectMapper objectMapper = new ObjectMapper(); try { // Wrap the data in ResultVo and convert it to a json string for return. return ((data)); } catch (JsonProcessingException e) { (); } } //System Specific Errors if(data instanceof LinkedHashMap && ((LinkedHashMap<?, ?>) data).containsKey("status") && ((LinkedHashMap<?, ?>) data).containsKey("message") &&((LinkedHashMap<?, ?>) data).containsKey("error")){ int code = (((LinkedHashMap<?, ?>) data).get("status").toString()); String mssage = ((LinkedHashMap<?, ?>) data).get("error").toString(); return new Response<>(code,mssage,null); } // All other results are uniformly wrapped into a Response to return return (data); } @ExceptionHandler({MethodArgumentNotValidException.class}) public Response MethodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) { // Default uniform return response body, fill in the parameter error code, from the exception object to get the error message return new Response(401,().getAllErrors().get(0).getDefaultMessage(),""); } //HttpMessageNotReadableException for webJSON parsing error! @ExceptionHandler({HttpMessageNotReadableException.class}) public Response HttpNotReqadableExceptionHandler(HttpMessageNotReadableException e) { // Default uniform return response body, fill in the parameter error code, from the exception object to get the error message return new Response(401,"Parameter parsing error.",""); } }