咱们现在运用SpringBoot 做Web 开发现已比之前SprngMvc 那一套强大许多了。 但是 用SpringBoot Web 做API 开发还是不行简练有一些。
每次Web API常用功用都需求从头写一遍。或许仿制之前项目代码。所以我封装了这么一个
抽出SpringBoot Web API 每个项目必备需求重复写的模块,和必备功用。 并且扩展了我作业中用到的 一切东西库。
根据它,你能够轻松开发SpringBoot WEB API,提高功率。不在去关心一些繁琐。重复作业,而是把要点聚集到事务。
现在更新版别到1.5.1 功用如下
- 支撑一键装备自界说RestFull API 一致格局回来
- 支撑RestFull API 过错国际化
- 支撑大局反常处理,大局参数验证处理
- 事务过错断言东西封装,遵从过错优先回来准则
- 封装Redis key,value 操作东西类。一致key办理 spring cache缓存完成
- RestTemplate 封装 POST,GET 恳求东西
- 日志集成。自界说日志途径,依照日志等级分类,支撑紧缩和文件巨细切割。按时间显示
- 东西库集成 集成了lombok,hutool,commons-lang3,guava。不需求自己单个引进
- 集成mybatisPlus一键代码生成
- 日志记载,服务监控,支撑日志链路查询。自界说数据源
- OpenApi3文档一键装备。支撑多种文档和主动装备
后续会持续更新。项目中重复运用,必备模块和东西。
- GitHub 地址
- gitee 地址
rest-api-spring-boot-starter 适用于SpringBoot Web API 快速构建让开发人员快速构建一致标准的事务RestFull API 不在去关心一些繁琐。重复作业,而是把要点聚集到事务。
快速开端
- 项目pom中引进依靠
<dependency>
<groupId>cn.soboys</groupId>
<artifactId>rest-api-spring-boot-starter</artifactId>
<version>1.5.0</version>
</dependency>
- 在SpringBoot发动类或许装备类上经过 @EnableRestFullApi注解敞开rest-api
@SpringBootApplication
@EnableRestFullApi
public class SuperaideApplication {
public static void main(String[] args) {
SpringApplication.run(SuperaideApplication.class, args);
}
}
到此你项目中就能够运用一切的功用了。
RestFull API
在Controller
中咱们写一般的恳求接口如:
@PostMapping("/chat")
public HashMap chatDialogue() {
HashMap m = new HashMap();
m.put("age", 26);
m.put("name", "Judy");
return m;
}
回来的便是大局一致RestFull API
{
"success": true,
"code": "OK",
"msg": "操作成功",
"requestId": "IPbHLE5SZ1fqI0lgNXlB",
"timestamp": "2023-07-09 02:39:40",
"data": {
"name": "judy",
"hobby": "swing",
"age": 18
}
}
也能够根据Result
构建
@PostMapping("/chat")
public Result chatDialogue(@Validated EntityParam s) {
return Result.buildSuccess(s);
}
分页支撑
咱们在日常中分页是一个比较特殊回来。也是十分常用的。
@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);
}
- 构建自界说自己的分页数据
ResultPage<List<EntityParam>> resultPage=new ResultPage<>();
- 经过
ResultPage.buildSuccess(resultPage)
进行构建回来
回来一致呼应格局
{
"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
}
]
}
自界说回来格局
{
"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
}
]
}
上述一致回来格局,或许不符合你项目中接口一致格局如:
{
"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
分页数据是在data
里面你能够界说pageWrap
特点true
包装回来界说pageData
的key
值如records
等
你需求自界说key
如 msg
你或许对应message
,success
你或许对应status
只需求在装备文件中装备自界说key
自界说回来成功值你的成功回来或许是200
你能够装备code-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
当 enabled
敞开后会读取你自界说装备的key
如
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
对应回来内容
{
"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
}
]
}
}
自界说回来
有时候咱们需求自界说回来。不去包装一致呼应RestFull API
格局
- 能够经过注解
@NoRestFulApi
完成如
@GetMapping("/test")
@NoRestFulApi
public Map chatDialogue() {
Map m= new HashMap<>();
m.put("name","judy");
m.put("age",26);
return m;
}
- 经过类扫描去完成
默许会过滤
String
类型以为是页面途径。
经过特点装备文件include-packages
需求一致回来包。exclude-packages
不需一致回来的包
include-packages: cn.soboys.superaide.controller
exclude-packages: xx.xxx.xxx
OpenApi文档生成
现已内置主动支撑。swagger
文档。和最新的OpenApi3
文档。项目发动后即可拜访。
-
swagger-ui.html 文档。途径
/swagger-ui.html
-
根据spring-doc 文档UI增强 途径
/doc.html
-
接口文档特点信息
openapi:
description:
title:
version:
license:
contact:
name:
email:
url:
- 发动项目后,拜访 http://server:port/context-path/swagger-ui.html 即可进入 Swagger UI 页面,OpenAPI 描述将在以下 json 格局的 url 中 供给:http://server:port/context-path/v3/api-docs
- server:域名 或 IP
- port:服务器端口
- context-path:应用程序的上下文途径,springboot 默以为空
- 文档也能够 yaml 格局供给,坐落以下途径:/v3/api-docs.yaml
假如厌弃官方供给的 swagger-ui 不美观,或许运用不顺手,能够挑选封闭 ui,还能够剔除去 ui 相关的 webjar 的引进。
springdoc:
swagger-ui:
enabled: false
OpenAPI 文档信息,默许可在此 url 中获取: http://server:port/context-path/v3/api-docs。 能够运用其他支撑 OpenAPI 协议的东西,经过此地址,进行 API 展现,如 Apifox。 ( Postman 的 api 测验也能够运用此地址进行导入生成 )
Knife4j (原 swagger-bootstrap-ui) 3.x 版别供给了关于 OpenAPI 协议的部分支撑。
::: tip Knife4j 许多当地没有依照协议标准完成,所以运用起来会有许多问题,另外项目也好久没有保护了,不引荐运用。 :::
因为 knife4j 关于标准支撑的不全面,无法直接运用单文档源数据,所以有必要进行分组或许 urls 的指定。
# urls
springdoc:
swagger-ui:
urls:
- { name: 'sample', url: '/v3/api-docs' }
或许
#分组
springdoc:
group-configs:
- { group: 'sample', packages-to-scan: 'com.example' }
Knife4j 的 UI 拜访地址有所不同,页面映射在 doc.html
途径下,发动项目后,拜访 http://server:port/context-path/doc.html
即可进入 Knife4j 的 Swagger UI 页面。
大局过错阻拦,参数校验
帮你封装好了一切http常见过错,和一切恳求参数验证过错。
如恳求过错
{
"success": false,
"code": "405",
"msg": "办法不被答应",
"timestamp": "2023-07-03 22:36:47",
"data": "Request method 'GET' not supported"
}
恳求资源不存在等
{
"success": false,
"code": "404",
"msg": "恳求资源不存在",
"timestamp": "2023-07-03 22:42:35",
"data": "/api"
}
假如需求阻拦上面过错请在springboot 装备文件中参加
#呈现过错时, 直接抛出反常
spring.mvc.throw-exception-if-no-handler-found=true
#不要为咱们工程中的资源文件建立映射
spring.web.resources.add-mappings=false
参数校验过错
验证Studen目标参数
/**
* @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;
}
恳求结果
JSON Body参数
@PostMapping("/chat")
public HashMap chatDialogue(@RequestBody @Validated Student student) {
HashMap m = new HashMap();
m.put("age", 26);
m.put("name", "Judy");
return m;
}
过错国际化
内置封装过错默许支撑英文和中文两种国际化。你不做任何装备主动支撑
假如需求内置支撑更多言语,覆盖即可。
自界说自己过错国际化和言语
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 对应过错提示 对应internal_server_error 自界说 下面言语自己界说 和前端传入i18n-header 对应上,就显你界说过错言语
我不传过错国际化默许便是中文在 default-lang: cn 进行装备
当我传入 指定言语 就会依照你装备的国际化自界说回来过错提示
日志链路追寻
RestFull API 一致回来有一个requestId
它是每个接口仅有标识。用于接口恳求日志链路追寻。日志查询。 如:
{
"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
}
]
}
经过requestId你能够很轻松的在你的日志文件查询定位到每次过错的恳求。
经过Log
注解记载你想要记载恳求
@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);
}
体系默许日志记载数据源为日志文件。如
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"
}
你能够自界说自己的日志数据源完成LogDataSource
接口 日志操作支撑异步。需求在装备类。或许发动类加上@EnableAsync
注解
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);
}
或许你能够承继我默许的日志数据源完成类LogFileDefaultDataSource
重写save(LogEntry logEntry)
办法。
@Slf4j
public class LogFileDefaultDataSource implements LogDataSource {
/**
* 自界说保存数据源
*
* @param
* @return LogEntry
*/
@Override
public void save(LogEntry logEntry) {
log.info(JSONUtil.toJsonPrettyStr(logEntry));
}
}
假如是自界说日志数据源完成需求再装备文件,装备日志数据源。如:
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 # 日志数据源
特点装备
装备言语国际化,日志等,
::: tip 默许不用装备任何参数。会运用默许的,装备了会运用你项目中的装备。 :::
默许装备
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: 恳求资源不存在
代码生成装备
支撑MybatisPlus
代码一键生成 默许不引进MybatisPlus
生成依靠需求手动引进
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
Controller中直接运用
@PostMapping("/chat")
public HashMap chatDialogue() {
HashMap m = new HashMap();
m.put("age", 26);
m.put("name", "Judy");
return m;
}
Result构建回来
@PostMapping("/chat")
public Result chatDialogue() {
HashMap m = new HashMap();
m.put("age", 26);
m.put("name", "Judy");
return Result.buildSuccess(m);
}
分页支撑
咱们在日常中分页是一个比较特殊回来。也是十分常用的。
@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);
}
- 构建自界说自己的分页数据
ResultPage<List<EntityParam>> resultPage=new ResultPage<>();
- 经过
ResultPage.buildSuccess(resultPage)
进行构建回来
回来一致呼应格局
{
"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
}
]
}
自界说回来格局
{
"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
}
]
}
上述一致回来格局,或许不符合你项目中接口一致格局如:
{
"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
分页数据是在data
里面你能够界说pageWrap
特点true
包装回来界说pageData
的key
值如records
等
你需求自界说key
如 msg
你或许对应message
,success
你或许对应status
只需求在装备文件中装备自界说key
自界说回来成功值你的成功回来或许是200
你能够装备code-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
当 enabled
敞开后会读取你自界说装备的key
如
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
对应回来内容
{
"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
}
]
}
}
自界说回来
有时候咱们需求自界说回来。不去包装一致呼应RestFull API
格局
- 能够经过注解
@NoRestFulApi
完成如
@GetMapping("/test")
@NoRestFulApi
public Map chatDialogue() {
Map m= new HashMap<>();
m.put("name","judy");
m.put("age",26);
return m;
}
- 经过类扫描去完成
默许会过滤
String
类型以为是页面途径。
经过特点装备文件include-packages
需求一致回来包。exclude-packages
不需一致回来的包
include-packages: cn.soboys.superaide.controller
exclude-packages: xx.xxx.xxx
过错国际化支撑
内置常见的过错。能够看HttpStatus。默许过错支撑中文和英文两种国际化。装备如下
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: 恳求资源不存在
能够自行覆盖扩充
大局过错阻拦和呼应
默许拦一切不知道过错反常和validation
参数校验失利反常,以及Http恳求反常。
还有大局自界说BusinessException 事务反常 主动集成spring-boot-starter-validation
你项目中不需求再独自引进
<!--参数校验-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
也内置扩展了许多自界说参数校验参阅
第三方恳求
有时候咱们项目中需求调用第三方接口服务。根据RestTemplate
进一步封装了直接的POST,GET,恳求。
在需求运用当地注入RestFulTemp
@Resource
private RestFulTemp restFulTemp;
GET恳求
@GetMapping("/doGet")
public Result doGet() {
ResponseEntity<String> response = restFulTemp.doGet("http://127.0.0.1:9000/redis/get");
return Result.buildSuccess();
}
POST 恳求
/**
* 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恳求
@GetMapping("/doDelete")
public Result doDelete() {
restFulTemp.doDelete("http://127.0.0.1:8000/chat");
return Result.buildSuccess();
}
PUT恳求
@GetMapping("/doPut")
public Result doPut() {
EntityParam s=new EntityParam();
restFulTemp.doPut("http://127.0.0.1:8000/chat",s);
return Result.buildSuccess(s);
}
过错反常自界说
我内置过错反常和事务反常或许无法满意你自身接口事务反常需求。你能够自界说过错反常类,和过错呼应枚举码。
自界说过错枚举 需求完成ResultCode接口
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();
}
假如要支撑国际化还需求完成国际化接口I18NKey 参阅我内部HttpStatus完成即可
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: 恳求资源不存在
事务断言
封装了事务过错断言东西。Assert
遵从过错优先回来准则。
你要自界说自己的事务反常。承继BusinessException
重写对应办法
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();
}
}
项目中日志是十分常用的,并且还是有必要的。现已主动装备集成spring-boot-starter-logging
你不需求在项目中独自引进
<!--日志集成-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
默许日志装备
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 # 日志数据源
日志记载与追寻
RestFull API 一致回来有一个requestId
它是每个接口仅有标识。用于接口恳求日志链路追寻。日志查询。 如:
{
"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
}
]
}
经过requestId你能够很轻松的在你的日志文件查询定位到每次过错的恳求。
经过Log
注解记载你想要记载恳求
@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);
}
体系默许日志记载数据源为日志文件。如
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"
}
你能够自界说自己的日志数据源完成LogDataSource
接口 日志操作支撑异步。需求在装备类。或许发动类加上@EnableAsync
注解
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);
}
或许你能够承继我默许的日志数据源完成类LogFileDefaultDataSource
重写save(LogEntry logEntry)
办法。
@Slf4j
public class LogFileDefaultDataSource implements LogDataSource {
/**
* 自界说保存数据源
*
* @param
* @return LogEntry
*/
@Override
public void save(LogEntry logEntry) {
log.info(JSONUtil.toJsonPrettyStr(logEntry));
}
}
假如是自界说日志数据源完成需求再装备文件,装备日志数据源。如:
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 # 日志数据源
缓存和redis
项目中缓存运用是十分常见的。用的最多的是根据Redis
缓存。所以我封装了关于Redis
Key和Value常用操作。
::: tip 默许不引进Redis依靠,假如要运用Redis需求自己独自引进 :::
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
项目运用
注入redisTempUtil
@Autowired
private RedisTempUtil redisTempUtil;
如示列
@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();
}
一致缓存办理
上面咱们是直接经过东西类redisTempUtil
直接自己界说key
然后去存储,这种方法是不可取的假如key许多随意界说就会很紊乱
。我供给了一致缓存key办理接口CacheTmp
参阅完成CacheKey
根据枚举形式,把一切key会集办理
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;
}
}
运用
- 存储关于key
@GetMapping("/redis")
public Result chatDialogue() {
CacheKey.PWD_RESET_CODE.valueSetAndExpire("test", 60l, TimeUnit.SECONDS, "judy");
return Result.buildSuccess();
}
- 获取对应的key
@GetMapping("/redis/get")
public Result redisGet() {
String a = CacheKey.PWD_RESET_CODE.valueGet("judy");
return Result.buildSuccess(a);
}
spring Cache完成
封装了spring Cache
进一步运用 项目中在装备类或许发动类经过注解@EnableCaching
敞开直接运用即可
@Cacheable(cacheNames = "testCache", keyGenerator = "keyGeneratorStrategy")
@GetMapping("/redis/springCache")
public Result springCache() {
String a = "test cache";
return Result.buildSuccess(a);
}
东西类运用springCacheUtil
支撑供给不是根据注解的运用方法
@GetMapping("/redis/springCache")
public Result redisSpringCache() {
String a = "111344";
springCacheUtil.putCache("test","key","121e1");
return Result.buildSuccess(a);
}
::: tip 默许不引进Redis依靠,缓存根据内存完成(你项目引进redis依靠后会自定切换数据源为Redis缓存) :::
redis装备
多个项目或许模块运用一个key或许会造成紊乱,所以供给了一个大局装备key。
redis:
key-prefix: rest
代码中增加一个 String 类型的 key:testKey
,其实践在 redis 中存储的 key name 为 rest:testKey
大局 key 前缀的装备,并不影响对 key 的其他操作,例如获取对应的 value 时,依然是传入 testKey
,而不是 rest:testKey
String key = "testKey";
String value = redisTempUtil.get(key);
String value1 = CacheKey.PWD_RESET_CODE.valueGet(key);
OpenApi文档生成
现已内置主动支撑。swagger
文档。和最新的OpenApi3
文档。项目发动后即可拜访。
-
swagger-ui.html 文档。途径
/swagger-ui.html
-
根据spring-doc 文档UI增强 途径
/doc.html
-
接口文档特点信息
openapi:
description:
title:
version:
license:
contact:
name:
email:
url:
- 发动项目后,拜访 http://server:port/context-path/swagger-ui.html 即可进入 Swagger UI 页面,OpenAPI 描述将在以下 json 格局的 url 中 供给:http://server:port/context-path/v3/api-docs
- server:域名 或 IP
- port:服务器端口
- context-path:应用程序的上下文途径,springboot 默以为空
- 文档也能够 yaml 格局供给,坐落以下途径:/v3/api-docs.yaml
假如厌弃官方供给的 swagger-ui 不美观,或许运用不顺手,能够挑选封闭 ui,还能够剔除去 ui 相关的 webjar 的引进。
springdoc:
swagger-ui:
enabled: false
OpenAPI 文档信息,默许可在此 url 中获取: http://server:port/context-path/v3/api-docs。 能够运用其他支撑 OpenAPI 协议的东西,经过此地址,进行 API 展现,如 Apifox。 ( Postman 的 api 测验也能够运用此地址进行导入生成 )
Knife4j (原 swagger-bootstrap-ui) 3.x 版别供给了关于 OpenAPI 协议的部分支撑。
::: tip Knife4j 许多当地没有依照协议标准完成,所以运用起来会有许多问题,另外项目也好久没有保护了,不引荐运用。 :::
因为 knife4j 关于标准支撑的不全面,无法直接运用单文档源数据,所以有必要进行分组或许 urls 的指定。
# urls
springdoc:
swagger-ui:
urls:
- { name: 'sample', url: '/v3/api-docs' }
或许
#分组
springdoc:
group-configs:
- { group: 'sample', packages-to-scan: 'com.example' }
Knife4j 的 UI 拜访地址有所不同,页面映射在 doc.html
途径下,发动项目后,拜访 http://server:port/context-path/doc.html
即可进入 Knife4j 的 Swagger UI 页面。
代码主动生成
项目中咱们运用mybatis
或许mybatisPlus
一些简略的单表事务代码,增删改成。咱们能够一键生成。不需求重复写。 我封装了mybatisPlus
代码生成东西
::: tip
默许不引进mybatisPlus
代码生成依靠,假如要运用mybatisPlus
代码生成需自行独自引进
:::
<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>
项目运用
- 代码生成装备类
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;
}
示列如:
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);
}
}
常见问题
在运用过程中尽量运用最新版别。我会持续更新更多的内容。 会第一时间发布在我的大众号 程序员三时。全网同名
能够关注 大众号 程序员三时。用心分享持续输出优质内容。期望能够给你带来一点协助