We are now using SpringBoot for web development, which is much more powerful than the previous SprngMvc set.
However, using SpringBoot Web for API development is still not concise enough.
Common functions of Web API need to be rewritten every time. Or copy the previous project code. So I encapsulated such a
Extract the modules and necessary functions that must be rewritten for each project of SpringBoot Web API .
And it expands all the tool libraries I use in my work.
Based on it, you can easily develop SpringBoot WEB API and improve efficiency. Don't care about some cumbersome things anymore. Duplicate work, but focus on business.
The current updated version to 1.5.2 features are as follows
- Support one-click configuration to customize RestFull API uniform format return
- Support RestFull API error internationalization
- Support global exception handling, global parameter verification processing
- Encapsulation of business error assertion tools, following the principle of returning errors first
- Encapsulate Redis key, value operation tool class. Unified key management spring cache cache implementation
- RestTemplate encapsulates POST, GET request tool
- Log integration. Customize the log path, classify according to the log level, support compression and file size segmentation. display by time
- The tool library integrates lombok, hutool, commons-lang3, and guava. No need to import them individually
- Integrated mybatisPlus one-click code generation
- Log records, service monitoring, support log link query. custom data source
- One-click configuration of OpenApi3 documents. Support for multiple documents and auto-configuration
- Interface current limit, IP city echo
- HttpUserAgent request device tool package
- RequestUtil parameter parsing and encapsulation tool
It will continue to be updated in the future. Reusable, must-have modules and tools in projects.
rest-api-spring-boot-starter is suitable for the rapid construction of SpringBoot Web API, allowing developers to quickly build a unified and standardized business RestFull API without worrying about some tediousness. Duplicate work, but focus on business.
quick start
- Introduce dependencies into the project pom
<dependency>
<groupId>cn.soboys</groupId>
<artifactId>rest-api-spring-boot-starter</artifactId>
<version>1.5.0</version>
</dependency>
- Enable rest-api via the @EnableRestFullApi annotation on the SpringBoot startup class or configuration class
@SpringBootApplication
@EnableRestFullApi
public class SuperaideApplication {
public static void main(String[] args) {
SpringApplication.run(SuperaideApplication.class, args);
}
}
At this point you can use all the functions in your project.
RestFull API
In Controller
it, we write common request interfaces such as:
@PostMapping("/chat")
public HashMap chatDialogue() {
HashMap m = new HashMap();
m.put("age", 26);
m.put("name", "Judy");
return m;
}
What is returned is the global unified RestFull API
{
"success": true,
"code": "OK",
"msg": "操作成功",
"requestId": "IPbHLE5SZ1fqI0lgNXlB",
"timestamp": "2023-07-09 02:39:40",
"data": {
"name": "judy",
"hobby": "swing",
"age": 18
}
}
Can also be Result
built based on
@PostMapping("/chat")
public Result chatDialogue(@Validated EntityParam s) {
return Result.buildSuccess(s);
}
pagination support
Our daily pagination is a special return. Also very commonly used.
@PostMapping("/page")
@Log("分页查询用户数据")
public Result page(@Validated EntityParam s) {
ResultPage<List<EntityParam>> resultPage=new ResultPage<>();
List a=new ArrayList();
a.add(s);
resultPage.setPageData(a);
return ResultPage.buildSuccess(resultPage);
}
- Build your own custom pagination data
ResultPage<List<EntityParam>> resultPage=new ResultPage<>();
ResultPage.buildSuccess(resultPage)
return by doing a build
Return the unified response format
{
"previousPage": 1,
"nextPage": 1,
"pageSize": 1,
"totalPageSize": 1,
"hasNext": "false",
"success": true,
"code": "OK",
"msg": "操作成功",
"requestId": "D9AMALgkZ6gVfe6Pi0Oh",
"timestamp": "2023-07-09 02:39:40",
"data": [
{
"name": "judy",
"hobby": "swing",
"age": 18
}
]
}
Custom return format
{
"previousPage": 1,
"nextPage": 1,
"pageSize": 1,
"totalPageSize": 1,
"hasNext": "false",
"success": true,
"code": "OK",
"msg": "操作成功",
"requestId": "D9AMALgkZ6gVfe6Pi0Oh",
"timestamp": "2023-07-09 02:39:40",
"data": [
{
"name": "judy",
"hobby": "swing",
"age": 18
}
]
}
The above unified return format may not conform to the unified format of the interface in your project, such as:
{
"success": true,
"code": "OK",
"msg": "操作成功",
"requestId": "ztf4S-lP9yrtKPSiwldZ",
"timestamp": "2023-07-11 13:46:53",
"data": {
"previousPage": 1,
"nextPage": 1,
"pageSize": 1,
"totalPageSize": 1,
"hasNext": "false",
"pageData": [
{
"name": "judy",
"hobby": "swing",
"age": 18
}
]
}
}
page
The pagination data is data
inside you can define pageWrap
the property true
wrapper to return the defined pageData
value key
such as records
etc.
You need to customize key
as msg
you may correspond message
, success
you may correspond status
only need to configure custom in the configuration filekey
Custom Return Success Value Your success return may be a value 200
you can configurecode-success-value
rest-api:
enabled: false
msg: msg
code: code
code-success-value: OK
success: success
previousPage: previousPage
nextPage: nextPage
pageSize: pageSize
hasNext: hasNext
totalPageSize: totalPageSize
data: info
When enabled
enabled, it will read your custom configuration key
such as
rest-api:
enabled: true
msg: msg1
code: code1
code-success-value: 200
success: success1
previousPage: previousPage1
nextPage: nextPage1
pageSize: pageSize1
hasNext: hasNext1
totalPageSize: totalPageSize1
data: info
corresponding return content
{
"success": true,
"code": "OK",
"msg": "操作成功",
"requestId": "ztf4S-lP9yrtKPSiwldZ",
"timestamp": "2023-07-11 13:46:53",
"data": {
"previousPage": 1,
"nextPage": 1,
"pageSize": 1,
"totalPageSize": 1,
"hasNext": "false",
"pageData": [
{
"name": "judy",
"hobby": "swing",
"age": 18
}
]
}
}
custom return
Sometimes we need custom returns. Do not wrap the unified response RestFull API
format
@NoRestFulApi
It can be achieved through annotations such as
@GetMapping("/test")
@NoRestFulApi
public Map chatDialogue() {
Map m= new HashMap<>();
m.put("name","judy");
m.put("age",26);
return m;
}
- By class scanning to achieve
by default, the filterString
type is considered to be the page path.
Configuration files via properties include-packages
need to return packages uniformly. exclude-packages
Packages that do not need to be returned uniformly
include-packages: cn.soboys.superaide.controller
exclude-packages: xx.xxx.xxx
OpenApi document generation
Automatic support is already built in. swagger
document. and the latest OpenApi3
documentation. Accessible after project launch.
-
swagger-ui.html documentation. path
/swagger-ui.html
-
Based on spring-doc document UI enhancement path
/doc.html
-
Interface document attribute information
openapi:
description:
title:
version:
license:
contact:
name:
email:
url:
- After starting the project, visit http://server:port/context-path/swagger-ui.html to enter the Swagger UI page, and the OpenAPI description will be provided in the following url in json format: http://server:port/context -path/v3/api-docs
- server: domain name or IP
- port: server port
- context-path: the context path of the application, springboot is empty by default
- Documentation is also available in yaml format at the following path: /v3/api-docs.yaml
If you don't like the official swagger-ui that is not beautiful, or you can't use it smoothly, you can choose to close the ui, and you can also eliminate the introduction of ui-related webjars.
springdoc:
swagger-ui:
enabled: false
OpenAPI document information can be obtained from this url by default: http://server:port/context-path/v3/api-docs.
Other tools that support the OpenAPI protocol can be used to display the API through this address, such as Apifox.
(Postman's api test can also use this address for import generation)
Knife4j (formerly swagger-bootstrap-ui) 3.x version provides partial support for the OpenAPI protocol.
::: tip
Knife4j is not implemented in accordance with the protocol specifications in many places, so there will be many problems when using it. In addition, the project has not been maintained for a long time, so it is not recommended to use.
:::
Since knife4j does not fully support specifications, it cannot directly use single-document source data, so grouping or urls must be specified.
# urls
springdoc:
swagger-ui:
urls:
- {
name: 'sample', url: '/v3/api-docs' }
or
#分组
springdoc:
group-configs:
- {
group: 'sample', packages-to-scan: 'com.example' }
The UI access address of Knife4j is different. The page is mapped under doc.html
the path. After starting the project, visithttp://server:port/context-path/doc.html
You can enter the Swagger UI page of Knife4j.
Global error interception, parameter verification
Help you encapsulate all common http errors, and all request parameter validation errors.
If the request is wrong
{
"success": false,
"code": "405",
"msg": "方法不被允许",
"timestamp": "2023-07-03 22:36:47",
"data": "Request method 'GET' not supported"
}
The requested resource does not exist, etc.
{
"success": false,
"code": "404",
"msg": "请求资源不存在",
"timestamp": "2023-07-03 22:42:35",
"data": "/api"
}
If you need to intercept the above error, please add it to the springboot configuration file
#出现错误时, 直接抛出异常
spring.mvc.throw-exception-if-no-handler-found=true
#不要为我们工程中的资源文件建立映射
spring.web.resources.add-mappings=false
Parameter validation error
Validate Studen object parameters
/**
* @author 公众号 程序员三时
* @version 1.0
* @date 2023/6/26 22:10
* @webSite https://github.com/coder-amiao
*/
@Data
public class Student {
@NotBlank
private String nam;
@NotBlank
private String hobby;
}
@PostMapping("/chat")
public HashMap chatDialogue(@Validated Student student) {
HashMap m = new HashMap();
m.put("age", 26);
m.put("name", "Judy");
return m;
}
request result
JSON Body parameter
@PostMapping("/chat")
public HashMap chatDialogue(@RequestBody @Validated Student student) {
HashMap m = new HashMap();
m.put("age", 26);
m.put("name", "Judy");
return m;
}
wrong internationalization
The built-in encapsulation error supports both English and Chinese internationalization by default. You do not do any configuration to automatically support
If you need built-in support for more languages, just override it.
Customize your own error internationalization and language
i18n:
# 若前端无header传参则返回中文信息
i18n-header: Lang
default-lang: cn
message:
# admin
internal_server_error:
en: Internal Server Error
cn: 系统错误
not_found:
en: Not Found
cn: 请求资源不存在
message corresponds to the error prompt
and corresponds to internal_server_error customization.
The following language is defined by yourself and the front-end passes in the i18n-header. It will show that you have defined the wrong language
I don't pass the error. The default internationalization is Chinese. Configure it in default-lang: cn
When I pass in the specified language, it will return an error message according to the internationalization customization you configured
Log Link Tracking
The RestFull API uniformly returns a requestId
unique identifier for each interface. Used for interface request log link tracking. Log query. like:
{
"msg": "操作成功",
"code": "OK",
"previousPage": 1,
"success": true,
"requestId": "udYNdbbMFE45R84OPu9m",
"nextPage": 1,
"pageSize": 1,
"totalPageSize": 1,
"hasNext": "false",
"timestamp": "2023-07-09 03:00:27",
"info": [
{
"name": "judy",
"hobby": "swing",
"age": 18
}
]
}
By requestId you can easily locate each wrong request in your log file query.
Document that you want to document the request via Log
annotation
@PostMapping("/page")
@Log(value = "查询用户数据",apiType= LogApiTypeEnum.USER,CURDType= LogCURDTypeEnum.RETRIEVE)
public Result page(@Validated EntityParam s) {
ResultPage<List<EntityParam>> resultPage=new ResultPage<>();
List a=new ArrayList();
a.add(s);
resultPage.setPageData(a);
return ResultPage.buildSuccess(resultPage);
}
The default logging data source of the system is the log file. like
2023-07-13 11:21:25 INFO http-nio-8888-exec-2 cn.soboys.restapispringbootstarter.aop.LimitAspect IP:192.168.1.8 第 1 次访问key为 [_kenx:chat192.168.1.8],描述为 [接口限流] 的接口
2023-07-13 11:21:26 INFO http-nio-8888-exec-2 cn.soboys.restapispringbootstarter.log.LogFileDefaultDataSource {
"description": "日志记录测试",
"method": "cn.soboys.restapispringbootstarter.controller.ApiRestController.chatDialogue()",
"params": {
},
"logType": "INFO",
"requestIp": "192.168.1.8",
"path": "/chat",
"address": "0|0|0|内网IP|内网IP",
"time": 128,
"os": "Mac",
"browser": "Chrome",
"result": {
"success": true,
"code": "OK",
"msg": "操作成功",
"requestId": "5RgKzWGFNa9XSPwhw2Pi",
"timestamp": "2023-07-13 11:21:25",
"data": "接口限流测试"
},
"apiType": "USER",
"device": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36"
}
You can customize your own log data source to implement LogDataSource
the interface log operation supports asynchronous. Needed in configuration class. Or add @EnableAsync
annotations to the startup class
package cn.soboys.restapispringbootstarter.log;
import org.springframework.scheduling.annotation.Async;
import java.util.Map;
/**
* @Author: kenx
* @Since: 2021/6/23 13:55
* @Description:
*/
public interface LogDataSource {
/**
* 获取拓展数据
* @return
* @param logEntry
*/
@Async
void save(LogEntry logEntry);
}
Or you can inherit my default log data source implementation class LogFileDefaultDataSource
to override save(LogEntry logEntry)
the method.
@Slf4j
public class LogFileDefaultDataSource implements LogDataSource {
/**
* 自定义保存数据源
*
* @param
* @return LogEntry
*/
@Override
public void save(LogEntry logEntry) {
log.info(JSONUtil.toJsonPrettyStr(logEntry));
}
}
If it is a custom log data source implementation, you need to reconfigure the file and configure the log data source. like:
logging:
path: ./logs #日志存储路径(服务器上绝对)
max-history: 90 # 保存多少天
max-file-size: 3MB # 每个文件大小
max-total-size-cap: 1GB #总文件大小超过多少压缩
level-root: INFO # 这里的INFO可以替换为其他日志等级,如DEBUG, WARN, ERROR, TRACE, FATAL, OFF等。 日志等级由低到高分别是debugger-info-warn-error
logDataSourceClass: cn.soboys.restapispringbootstarter.log.LogFileDefaultDataSource # 日志数据源
Ip City Records
Logging provides Ip
city echo records
@PostMapping("/page")
@Log(value = "查询用户数据", apiType = LogApiTypeEnum.USER, CURDType = LogCURDTypeEnum.RETRIEVE,ipCity = true)
public Result page(@Validated EntityParam s) {
ResultPage<List<EntityParam>> resultPage = new ResultPage<>();
List a = new ArrayList();
a.add(s);
resultPage.setPageData(a);
return ResultPage.buildSuccess(resultPage);
}
By configuring ipCity
the properties, by default, true
the detailed information of the physical address corresponding to the IP will be recorded, that is, the country, city and other
Ip
cities can be obtained through ip2region
query.
You can configure properties location
to configure your own ip2region.xdb
files.
ip2region:
external: false
location: classpath:ip2region/ip2region.xdb
If it is not configured by default, it will automatically generate one for you. Based on the latest data of 2.7.x,
get the latest custom ip data Github warehouse
Of course, it also helps you package. You can use HttpUserAgent
the static method of the tool class getIpToCityInfo(String ip)
to obtain and query the city information corresponding to the ip
attribute configuration
Configure language internationalization, logs, etc.,
::: tip
does not need to configure any parameters by default. The default will be used, and the configuration in your project will be used if configured.
:::
default allocation
rest-api:
enabled: false
msg: msg
code: code
code-success-value: OK
success: success
previousPage: previousPage
nextPage: nextPage
pageSize: pageSize
hasNext: hasNext
totalPageSize: totalPageSize
data: info
include-packages: cn.soboys.superaide.controller
exclude-packages: xx.xxx.xxx
redis:
key-prefix: rest
openapi:
description:
title:
version:
license:
contact:
name:
email:
url:
logging:
path: ./logs #日志存储路径(服务器上绝对)
max-history: 90 # 保存多少天
max-file-size: 3MB # 每个文件大小
max-total-size-cap: 1GB #总文件大小超过多少压缩
level-root: INFO # 这里的INFO可以替换为其他日志等级,如DEBUG, WARN, ERROR, TRACE, FATAL, OFF等。 日志等级由低到高分别是debugger-info-warn-error
logDataSourceClass: cn.soboys.restapispringbootstarter.log.LogFileDefaultDataSource # 日志数据源
i18n:
# 若前端无header传参则返回中文信息
i18n-header: Lang
default-lang: cn
message:
# admin
internal_server_error:
en: Internal Server Error
cn: 系统错误
bad_gateway:
en: Bad Gateway
cn: 错误的请求
unauthorized:
en: Unauthorized
cn: 未授权
forbidden:
en: Forbidden
cn: 资源禁止访问
method_not_allowed:
en: Method Not Allowed
cn: 方法不被允许
request_timeout:
en: Request Timeout
cn: 请求超时
invalid_argument:
en: Invalid Argument {
}
cn: 参数错误 {
}
argument_analyze:
en: Argument Analyze {
}
cn: 参数解析异常 {
}
business_exception:
en: Business Exception
cn: 业务错误
not_found:
en: Not Found
cn: 请求资源不存在
Code generation configuration
Supports MybatisPlus
one-click generation of code, does not introduce MybatisPlus
generation dependencies by default and needs to be manually introduced
package cn.soboys.restapispringbootstarter.config;
import lombok.Data;
/**
* @author 公众号 程序员三时
* @version 1.0
* @date 2023/7/5 00:05
* @webSite https://github.com/coder-amiao
*/
@Data
public class GenerateCodeConfig {
/**
* 数据库驱动
*/
private String driverName;
/**
* 数据库连接用户名
*/
private String username;
/**
* 数据库连接密码
*/
private String password;
/**
* 数据库连接url
*/
private String url;
/**
* 生成代码 保存路径。默认当前项目下。
* 如需修改,使用觉得路径
*/
private String projectPath;
/**
* 代码生成包位置
*/
private String packages;
}
RestFull API
Directly used in Controller
@PostMapping("/chat")
public HashMap chatDialogue() {
HashMap m = new HashMap();
m.put("age", 26);
m.put("name", "Judy");
return m;
}
Result build returns
@PostMapping("/chat")
public Result chatDialogue() {
HashMap m = new HashMap();
m.put("age", 26);
m.put("name", "Judy");
return Result.buildSuccess(m);
}
pagination support
Our daily pagination is a special return. Also very commonly used.
@PostMapping("/page")
@Log("分页查询用户数据")
public Result page(@Validated EntityParam s) {
ResultPage<List<EntityParam>> resultPage=new ResultPage<>();
List a=new ArrayList();
a.add(s);
resultPage.setPageData(a);
return ResultPage.buildSuccess(resultPage);
}
- Build your own custom pagination data
ResultPage<List<EntityParam>> resultPage=new ResultPage<>();
ResultPage.buildSuccess(resultPage)
return by doing a build
Return the unified response format
{
"previousPage": 1,
"nextPage": 1,
"pageSize": 1,
"totalPageSize": 1,
"hasNext": "false",
"success": true,
"code": "OK",
"msg": "操作成功",
"requestId": "D9AMALgkZ6gVfe6Pi0Oh",
"timestamp": "2023-07-09 02:39:40",
"data": [
{
"name": "judy",
"hobby": "swing",
"age": 18
}
]
}
Custom return format
{
"previousPage": 1,
"nextPage": 1,
"pageSize": 1,
"totalPageSize": 1,
"hasNext": "false",
"success": true,
"code": "OK",
"msg": "操作成功",
"requestId": "D9AMALgkZ6gVfe6Pi0Oh",
"timestamp": "2023-07-09 02:39:40",
"data": [
{
"name": "judy",
"hobby": "swing",
"age": 18
}
]
}
The above unified return format may not conform to the unified format of the interface in your project, such as:
{
"success": true,
"code": "OK",
"msg": "操作成功",
"requestId": "ztf4S-lP9yrtKPSiwldZ",
"timestamp": "2023-07-11 13:46:53",
"data": {
"previousPage": 1,
"nextPage": 1,
"pageSize": 1,
"totalPageSize": 1,
"hasNext": "false",
"pageData": [
{
"name": "judy",
"hobby": "swing",
"age": 18
}
]
}
}
page
The pagination data is data
inside you can define pageWrap
the property true
wrapper to return the defined pageData
value key
such as records
etc.
You need to customize key
as msg
you may correspond message
, success
you may correspond status
only need to configure custom in the configuration filekey
Custom Return Success Value Your success return may be a value 200
you can configurecode-success-value
rest-api:
enabled: false
msg: msg
code: code
code-success-value: OK
success: success
previousPage: previousPage
nextPage: nextPage
pageSize: pageSize
hasNext: hasNext
totalPageSize: totalPageSize
data: info
When enabled
enabled, it will read your custom configuration key
such as
rest-api:
enabled: true
msg: msg1
code: code1
code-success-value: 200
success: success1
previousPage: previousPage1
nextPage: nextPage1
pageSize: pageSize1
hasNext: hasNext1
totalPageSize: totalPageSize1
data: info
corresponding return content
{
"success": true,
"code": "OK",
"msg": "操作成功",
"requestId": "ztf4S-lP9yrtKPSiwldZ",
"timestamp": "2023-07-11 13:46:53",
"data": {
"previousPage": 1,
"nextPage": 1,
"pageSize": 1,
"totalPageSize": 1,
"hasNext": "false",
"pageData": [
{
"name": "judy",
"hobby": "swing",
"age": 18
}
]
}
}
custom return
Sometimes we need custom returns. Do not wrap the unified response RestFull API
format
@NoRestFulApi
It can be achieved through annotations such as
@GetMapping("/test")
@NoRestFulApi
public Map chatDialogue() {
Map m= new HashMap<>();
m.put("name","judy");
m.put("age",26);
return m;
}
- By class scanning to achieve
by default, the filterString
type is considered to be the page path.
Configuration files via properties include-packages
need to return packages uniformly. exclude-packages
Packages that do not need to be returned uniformly
include-packages: cn.soboys.superaide.controller
exclude-packages: xx.xxx.xxx
error internationalization support
Common mistakes built in. You can see HttpStatus. The default error supports both Chinese and English internationalization. The configuration is as follows
i18n:
# 若前端无header传参则返回中文信息
i18n-header: Lang
default-lang: cn
message:
# admin
internal_server_error:
en: Internal Server Error
cn: 系统错误
bad_gateway:
en: Bad Gateway
cn: 错误的请求
unauthorized:
en: Unauthorized
cn: 未授权
forbidden:
en: Forbidden
cn: 资源禁止访问
method_not_allowed:
en: Method Not Allowed
cn: 方法不被允许
request_timeout:
en: Request Timeout
cn: 请求超时
invalid_argument:
en: Invalid Argument {
}
cn: 参数错误 {
}
argument_analyze:
en: Argument Analyze {
}
cn: 参数解析异常 {
}
business_exception:
en: Business Exception
cn: 业务错误
not_found:
en: Not Found
cn: 请求资源不存在
Can be self-covered and extended
Global error interception and response
By default, all unknown error exceptions, validation
parameter verification failure exceptions, and Http request exceptions are blocked. There is also the automatic integration of
global custom BusinessExceptionspring-boot-starter-validation
business exceptions , and you do not need to introduce them separately in your project
<!--参数校验-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
There are also built-in extensions for many custom parameter verification references
third party request
Sometimes we need to call third-party interface services in our projects. Based on RestTemplate
further encapsulation of direct POST, GET, requests.
Inject RestFulTemp where it needs to be used
@Resource
private RestFulTemp restFulTemp;
GET request
@GetMapping("/doGet")
public Result doGet() {
ResponseEntity<String> response = restFulTemp.doGet("http://127.0.0.1:9000/redis/get");
return Result.buildSuccess();
}
POST request
/**
* POST 请求参 数为body json体格式
* @return
*/
@PostMapping("/doPost")
public Result doPost() {
Student s=new Student();
s.setHobby("swing");
s.setNam("judy");
//自动把对象转换为JSON
ResponseEntity<String> response =
restFulTemp.doPost("http://127.0.0.1:9000/redis/get",s);
return Result.buildSuccess();
}
/**
* POST请求 参数为FORM 表单参数
* @return
*/
@PostMapping("/doPost")
public Result doPostForm() {
EntityParam s=new EntityParam();
s.setAge(19);
s.setHobby("swing");
s.setName("judy");
ResponseEntity<String> response =
restFulTemp.doPostForm("http://127.0.0.1:8000/chat", BeanUtil.beanToMap(s));
return Result.buildSuccess(response.getBody());
}
DELETE request
@GetMapping("/doDelete")
public Result doDelete() {
restFulTemp.doDelete("http://127.0.0.1:8000/chat");
return Result.buildSuccess();
}
PUT request
@GetMapping("/doPut")
public Result doPut() {
EntityParam s=new EntityParam();
restFulTemp.doPut("http://127.0.0.1:8000/chat",s);
return Result.buildSuccess(s);
}
Error exception customization
My built-in error exception and business exception may not be able to meet your own interface business exception needs. You can customize error exception class, and error response enumeration code.
Custom error enumeration needs to implement ResultCode interface
package cn.soboys.restapispringbootstarter;
import cn.soboys.restapispringbootstarter.i18n.I18NKey;
/**
* @author 公众号 程序员三时
* @version 1.0
* @date 2023/6/26 10:21
* @webSite https://github.com/coder-amiao
* 响应码接口,自定义响应码,实现此接口
*/
public interface ResultCode extends I18NKey {
String getCode();
String getMessage();
}
If you want to support internationalization, you need to implement the internationalization interface I18NKey. Please refer to my internal HttpStatus implementation.
package cn.soboys.restapispringbootstarter;
import cn.soboys.restapispringbootstarter.i18n.I18NKey;
/**
* @author 公众号 程序员三时
* @version 1.0
* @date 2023/6/26 11:01
* @webSite https://github.com/coder-amiao
*/
public enum HttpStatus implements ResultCode, I18NKey {
/**
* 系统内部错误
*/
INTERNAL_SERVER_ERROR("500", "internal_server_error"),
BAD_GATEWAY("502", "bad_gateway"),
NOT_FOUND("404", "not_found"),
UNAUTHORIZED("401", "unauthorized"),
FORBIDDEN("403", "forbidden"),
METHOD_NOT_ALLOWED("405", "method_not_allowed"),
REQUEST_TIMEOUT("408", "request_timeout"),
INVALID_ARGUMENT("10000", "invalid_argument"),
ARGUMENT_ANALYZE("10001", "argument_analyze"),
BUSINESS_EXCEPTION("20000", "business_exception");
private final String value;
private final String message;
HttpStatus(String value, String message) {
this.value = value;
this.message = message;
}
@Override
public String getCode() {
return value;
}
@Override
public String getMessage() {
return message;
}
@Override
public String key() {
return message;
}
}
rest-api:
enabled: false
i18n:
# 若前端无header传参则返回中文信息
i18n-header: Lang
default-lang: cn
message:
# admin
internal_server_error:
en: Internal Server Error
cn: 系统错误
bad_gateway:
en: Bad Gateway
cn: 错误的请求
unauthorized:
en: Unauthorized
cn: 未授权
forbidden:
en: Forbidden
cn: 资源禁止访问
method_not_allowed:
en: Method Not Allowed
cn: 方法不被允许
request_timeout:
en: Request Timeout
cn: 请求超时
invalid_argument:
en: Invalid Argument {
}
cn: 参数错误 {
}
argument_analyze:
en: Argument Analyze {
}
cn: 参数解析异常 {
}
business_exception:
en: Business Exception
cn: 业务错误
not_found:
en: Not Found
cn: 请求资源不存在
business assertion
Encapsulates the business error assertion tool. Assert
Follow the error first return principle.
You need to customize your own business exceptions. Inheritance BusinessException
Override the corresponding method
package cn.soboys.restapispringbootstarter.exception;
import cn.soboys.restapispringbootstarter.HttpStatus;
import cn.soboys.restapispringbootstarter.ResultCode;
import lombok.Data;
/**
* @author 公众号 程序员三时
* @version 1.0
* @date 2023/6/26 16:45
* @webSite https://github.com/coder-amiao
*/
@Data
public class BusinessException extends RuntimeException {
/**
* 错误码
*/
private String code="20000";
/**
* 错误提示
*/
private String message;
public BusinessException(String message) {
this.message = message;
}
public BusinessException(String message, String code) {
this.message = message;
this.code = code;
}
public BusinessException(ResultCode resultCode) {
this.message = resultCode.getMessage();
this.code = resultCode.getCode();
}
}
Logs are very commonly used in projects, and they are still necessary. The integration has been automatically configured and spring-boot-starter-logging
you don't need to introduce it separately in the project
<!--日志集成-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
Default log configuration
logging:
path: ./logs #日志存储路径(服务器上绝对)
max-history: 90 # 保存多少天
max-file-size: 3MB # 每个文件大小
max-total-size-cap: 1GB #总文件大小超过多少压缩
level-root: INFO # 这里的INFO可以替换为其他日志等级,如DEBUG, WARN, ERROR, TRACE, FATAL, OFF等。 日志等级由低到高分别是debugger-info-warn-error
logDataSourceClass: cn.soboys.restapispringbootstarter.log.LogFileDefaultDataSource # 日志数据源
Logging and Tracing
The RestFull API uniformly returns a requestId
unique identifier for each interface. Used for interface request log link tracking. Log query. like:
{
"msg": "操作成功",
"code": "OK",
"previousPage": 1,
"success": true,
"requestId": "udYNdbbMFE45R84OPu9m",
"nextPage": 1,
"pageSize": 1,
"totalPageSize": 1,
"hasNext": "false",
"timestamp": "2023-07-09 03:00:27",
"info": [
{
"name": "judy",
"hobby": "swing",
"age": 18
}
]
}
By requestId you can easily locate each wrong request in your log file query.
Document that you want to document the request via Log
annotation
@PostMapping("/page")
@Log(value = "查询用户数据",apiType= LogApiTypeEnum.USER,CURDType= LogCURDTypeEnum.RETRIEVE)
public Result page(@Validated EntityParam s) {
ResultPage<List<EntityParam>> resultPage=new ResultPage<>();
List a=new ArrayList();
a.add(s);
resultPage.setPageData(a);
return ResultPage.buildSuccess(resultPage);
}
The default logging data source of the system is the log file. like
2023-07-09 03:00:32 INFO http-nio-8000-exec-2 cn.soboys.restapispringbootstarter.log.LogFileDefaultDataSource {
"description": "查询用户数据",
"method": "cn.soboys.restapispringbootstarter.controller.ApiRestController.page()",
"logType": "INFO",
"time": 3,
"result": {
"success": true,
"code": "OK",
"msg": "操作成功",
"requestId": "udYNdbbMFE45R84OPu9m",
"timestamp": "2023-07-09 03:00:27",
"data": {
"previousPage": 1,
"nextPage": 1,
"pageSize": 1,
"totalPageSize": 1,
"hasNext": "false",
"pageData": [
{
"name": "judy",
"hobby": "swing",
"age": 18
}
],
"requestId": "qJTOejQmY-OOf7fagegB",
"timestamp": "2023-07-09 03:00:27"
}
},
"apiType": "USER"
}
2023-07-09 03:08:03 INFO http-nio-8000-exec-4 cn.soboys.restapispringbootstarter.log.LogFileDefaultDataSource {
"description": "查询用户数据",
"method": "cn.soboys.restapispringbootstarter.controller.ApiRestController.page()",
"logType": "INFO",
"time": 1,
"result": {
"success": true,
"code": "OK",
"msg": "操作成功",
"requestId": "kP3yPP-H7wI2x1ak6YFA",
"timestamp": "2023-07-09 03:00:27",
"data": {
"previousPage": 1,
"nextPage": 1,
"pageSize": 1,
"totalPageSize": 1,
"hasNext": "false",
"pageData": [
{
"name": "judy",
"hobby": "swing",
"age": 18
}
],
"requestId": "pGbbiEj8GQ1eTxQpF2Jr",
"timestamp": "2023-07-09 03:00:27"
}
},
"apiType": "USER"
}
You can customize your own log data source to implement LogDataSource
the interface log operation supports asynchronous. Needed in configuration class. Or add @EnableAsync
annotations to the startup class
package cn.soboys.restapispringbootstarter.log;
import org.springframework.scheduling.annotation.Async;
import java.util.Map;
/**
* @Author: kenx
* @Since: 2021/6/23 13:55
* @Description:
*/
public interface LogDataSource {
/**
* 获取拓展数据
* @return
* @param logEntry
*/
@Async
void save(LogEntry logEntry);
}
Or you can inherit my default log data source implementation class LogFileDefaultDataSource
to override save(LogEntry logEntry)
the method.
@Slf4j
public class LogFileDefaultDataSource implements LogDataSource {
/**
* 自定义保存数据源
*
* @param
* @return LogEntry
*/
@Override
public void save(LogEntry logEntry) {
log.info(JSONUtil.toJsonPrettyStr(logEntry));
}
}
If it is a custom log data source implementation, you need to reconfigure the file and configure the log data source. like:
logging:
path: ./logs #日志存储路径(服务器上绝对)
max-history: 90 # 保存多少天
max-file-size: 3MB # 每个文件大小
max-total-size-cap: 1GB #总文件大小超过多少压缩
level-root: INFO # 这里的INFO可以替换为其他日志等级,如DEBUG, WARN, ERROR, TRACE, FATAL, OFF等。 日志等级由低到高分别是debugger-info-warn-error
logDataSourceClass: cn.soboys.restapispringbootstarter.log.LogFileDefaultDataSource # 日志数据源
cache and redis
Caching usage in projects is very common. The most used is based on Redis
caching. So I encapsulated Redis
common operations for Key and Value.
::: tip
does not introduce Redis dependencies by default. If you want to use Redis, you need to import it separately
:::
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
project use
injectionredisTempUtil
@Autowired
private RedisTempUtil redisTempUtil;
as shown
@Autowired
private RedisTempUtil redisTempUtil;
@GetMapping("/redis")
public Result chatDialogue( ) {
redisTempUtil.set("test","111");
redisTempUtil.get("test");
redisTempUtil.set("user","userObj",7200l);
redisTempUtil.getAllKey("xx"); //*表达式
redisTempUtil.clean();
redisTempUtil.deleteObject("test");
redisTempUtil.hasKey("");
return Result.buildSuccess();
}
Unified Cache Management
In the above, we directly redisTempUtil
define key
and store by ourselves directly through the tool class, which is not advisable if key很多随意定义就会很混乱
. I provide a unified cache key management interface CacheTmp
reference implementation CacheKey
Based on the enumeration form, all keys are managed centrally
package cn.soboys.restapispringbootstarter.cache;
import lombok.Getter;
/**
* @author 公众号 程序员三时
* @version 1.0
* @date 2023/7/2 11:04
* @webSite https://github.com/coder-amiao
* 缓存枚举
*/
@Getter
public enum CacheKey implements CacheTmp {
// 密码的重置码
PWD_RESET_CODE("reset:code:", true),
;
private String key;
/**
* Key是否是Key前缀, true时直接取key=key,如果false时key=key+suffix
*/
private boolean hasPrefix;
CacheKey(String key, boolean hasPrefix) {
this.key = key;
this.hasPrefix = hasPrefix;
}
@Override
public Boolean getHasPrefix() {
return this.hasPrefix;
}
@Override
public String getKey() {
return this.key;
}
}
use
- store for key
@GetMapping("/redis")
public Result chatDialogue() {
CacheKey.PWD_RESET_CODE.valueSetAndExpire("test", 60l, TimeUnit.SECONDS, "judy");
return Result.buildSuccess();
}
- Get the corresponding key
@GetMapping("/redis/get")
public Result redisGet() {
String a = CacheKey.PWD_RESET_CODE.valueGet("judy");
return Result.buildSuccess(a);
}
Spring Cache implementation
Encapsulates spring Cache
the configuration class or startup class in the project for further use @EnableCaching
and can be used directly through annotations
@Cacheable(cacheNames = "testCache", keyGenerator = "keyGeneratorStrategy")
@GetMapping("/redis/springCache")
public Result springCache() {
String a = "test cache";
return Result.buildSuccess(a);
}
Use of tool classes springCacheUtil
Supports the use of methods that are not based on annotations
@GetMapping("/redis/springCache")
public Result redisSpringCache() {
String a = "111344";
springCacheUtil.putCache("test","key","121e1");
return Result.buildSuccess(a);
}
::: tip
does not introduce Redis dependencies by default, and the cache is implemented based on memory (after your project introduces redis dependencies, it will automatically switch the data source to Redis cache)
:::
redis configuration
Using one key for multiple projects or modules may cause confusion, so a global configuration key is provided.
redis:
key-prefix: rest
Add a String type to the code key:testKey
, and its actual key name stored in redis isrest:testKey
The configuration of the global key prefix does not affect other operations on the key. For example, when obtaining the corresponding value, it is still passed in testKey
instead ofrest:testKey
String key = "testKey";
String value = redisTempUtil.get(key);
String value1 = CacheKey.PWD_RESET_CODE.valueGet(key);
OpenApi document generation
Automatic support is already built in. swagger
document. and the latest OpenApi3
documentation. Accessible after project launch.
-
swagger-ui.html documentation. path
/swagger-ui.html
-
Based on spring-doc document UI enhancement path
/doc.html
-
Interface document attribute information
openapi:
description:
title:
version:
license:
contact:
name:
email:
url:
- After starting the project, visit http://server:port/context-path/swagger-ui.html to enter the Swagger UI page, and the OpenAPI description will be provided in the following url in json format: http://server:port/context -path/v3/api-docs
- server: domain name or IP
- port: server port
- context-path: the context path of the application, springboot is empty by default
- Documentation is also available in yaml format at the following path: /v3/api-docs.yaml
If you don't like the official swagger-ui that is not beautiful, or you can't use it smoothly, you can choose to close the ui, and you can also eliminate the introduction of ui-related webjars.
springdoc:
swagger-ui:
enabled: false
OpenAPI document information can be obtained from this url by default: http://server:port/context-path/v3/api-docs.
Other tools that support the OpenAPI protocol can be used to display the API through this address, such as Apifox.
(Postman's api test can also use this address for import generation)
Knife4j (formerly swagger-bootstrap-ui) 3.x version provides partial support for the OpenAPI protocol.
::: tip
Knife4j is not implemented in accordance with the protocol specifications in many places, so there will be many problems when using it. In addition, the project has not been maintained for a long time, so it is not recommended to use.
:::
Since knife4j does not fully support specifications, it cannot directly use single-document source data, so grouping or urls must be specified.
# urls
springdoc:
swagger-ui:
urls:
- {
name: 'sample', url: '/v3/api-docs' }
or
#分组
springdoc:
group-configs:
- {
group: 'sample', packages-to-scan: 'com.example' }
The UI access address of Knife4j is different. The page is mapped under doc.html
the path. After starting the project, visithttp://server:port/context-path/doc.html
You can enter the Swagger UI page of Knife4j.
code generation
In the project, we use mybatis
or mybatisPlus
some simple single-table business codes, adding, deleting, and changing. We can generate it with one click. No need to rewrite. I encapsulate mybatisPlus
the code generation tool
::: tip
does not introduce mybatisPlus
code generation dependencies by default. If you want to use mybatisPlus
code generation, you need to import it separately
:::
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.4.1</version>
</dependency>
<!-- MySQL -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
<!--代码生成依赖的模板引擎-->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.31</version>
</dependency>
project use
- Code Generation Configuration Class
package cn.soboys.restapispringbootstarter.config;
import lombok.Data;
/**
* @author 公众号 程序员三时
* @version 1.0
* @date 2023/7/5 00:05
* @webSite https://github.com/coder-amiao
*/
@Data
public class GenerateCodeConfig {
/**
* 数据库驱动
*/
private String driverName;
/**
* 数据库连接用户名
*/
private String username;
/**
* 数据库连接密码
*/
private String password;
/**
* 数据库连接url
*/
private String url;
/**
* 生成代码 保存路径。默认当前项目下。
* 如需修改,使用绝对路径
*/
private String projectPath;
/**
* 代码生成包位置
*/
private String packages;
}
For example:
public class Test {
public static void main(String[] args) {
GenerateCodeConfig config=new GenerateCodeConfig();
config.setDriverName("com.mysql.cj.jdbc.Driver");
config.setUsername("root");
config.setPassword("root");
config.setUrl("jdbc:mysql://127.0.0.1:3306/ry?useUnicode=true&useSSL=false&characterEncoding=utf8");
//config.setProjectPath("superaide");
config.setPackages("cn.soboys.superaide");
MyBatisPlusGenerator.generate(config);
}
}
common problem
Try to use the latest version during use. I will continue to update more content. It will be posted on my official account
Programmer Three Hours as soon as possible. Whole network with the same name
You can pay attention to the official account programmer three hours . Share with your heart and continue to output high-quality content. I hope it can bring you a little help