我们现在使用SpringBoot 做Web 开发已经比之前SprngMvc 那一套强大很多了。

但是 用SpringBoot Web 做API 开发还是不够简洁有一些。

每次Web API常用功能都需要重新写一遍。或者复制之前项目代码。于是我封装了这么一个

抽出SpringBoot Web API 每个项目必备需要重复写的模块,和必备功能。

并且扩展了我工作中用到的 所有工具库。

基于它,你可以轻松开发SpringBoot WEB API,提高效率。不在去关心一些繁琐。重复工作,而是把重点聚焦到业务。

目前更新版本到1.5.2 功能如下

  1. 支持一键配置自定义RestFull API 统一格式返回
  2. 支持RestFull API 错误国际化
  3. 支持全局异常处理,全局参数验证处理
  4. 业务错误断言工具封装,遵循错误优先返回原则
  5. 封装Redis key,value 操作工具类。统一key管理 spring cache缓存实现
  6. RestTemplate 封装 POST,GET 请求工具
  7. 日志集成。自定义日志路径,按照日志等级分类,支持压缩和文件大小分割。按时间显示
  8. 工具库集成 集成了lombok,hutool,commons-lang3,guava。不需要自己单个引入
  9. 集成mybatisPlus一键代码生成
  10. 日志记录,服务监控,支持日志链路查询。自定义数据源
  11. OpenApi3文档一键配置。支持多种文档和自动配置
  12. 接口限流,Ip城市回显
  13. HttpUserAgent请求设备工具封装
  14. RequestUtil参数解析封装工具

后续会持续更新。项目中重复使用,必备模块和工具。

rest-api-spring-boot-starter 适用于SpringBoot Web API 快速构建让开发人员快速构建统一规范的业务RestFull API 不在去关心一些繁琐。重复工作,而是把重点聚焦到业务。

快速开始

  1. 项目pom中引入依赖
<dependency>
<groupId>cn.soboys</groupId>
<artifactId>rest-api-spring-boot-starter</artifactId>
<version>1.5.0</version>
</dependency>
  1. 在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);
}
  1. 构建自定义自己的分页数据
ResultPage<List<EntityParam>> resultPage=new ResultPage<>();
  1. 通过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包装返回定义pageDatakey值如records

你需要自定义keymsg你可能对应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格式

  1. 可以通过注解@NoRestFulApi实现如
@GetMapping("/test")
@NoRestFulApi
public Map chatDialogue() {
Map m= new HashMap<>();
m.put("name","judy");
m.put("age",26);
return m;
}
  1. 通过类扫描去实现

    默认会过滤String类型认为是页面路径。

通过属性配置文件include-packages需要统一返回包。exclude-packages不需统一返回的包

include-packages: cn.soboys.superaide.controller
exclude-packages: xx.xxx.xxx

OpenApi文档生成

已经内置自动支持。swagger文档。和最新的OpenApi3 文档。项目启动后即可访问。

  1. swagger-ui.html 文档。路径/swagger-ui.html

  2. 基于spring-doc 文档UI增强 路径/doc.html

  3. 接口文档属性信息

  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-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"
}

你可以自定义自己的日志数据源实现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 # 日志数据源

Ip城市记录

日志记录提供Ip城市回显记录

@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);
}

通过配置ipCity属性,默认是true会记录IP对应物理地址详细信息即国家城市等

Ip城市查询通过ip2region获取。

你可配置属性 location 配置自己的ip2region.xdb文件。

  ip2region:
external: false
location: classpath:ip2region/ip2region.xdb

默认不配置会帮你自动生成一个。基于2.7.x最新的数据

获取最新自定义ip数据 Github 仓库

当然也帮你封装了。你可以通过工具类HttpUserAgent的静态方法getIpToCityInfo(String ip) 去获取查询ip对应城市信息

属性配置

配置语言国际化,日志等,

::: 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);
}
  1. 构建自定义自己的分页数据
ResultPage<List<EntityParam>> resultPage=new ResultPage<>();
  1. 通过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包装返回定义pageDatakey值如records

你需要自定义keymsg你可能对应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格式

  1. 可以通过注解@NoRestFulApi实现如
@GetMapping("/test")
@NoRestFulApi
public Map chatDialogue() {
Map m= new HashMap<>();
m.put("name","judy");
m.put("age",26);
return m;
}
  1. 通过类扫描去实现

    默认会过滤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缓存。于是我封装了对于RedisKey和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;
} }

使用

  1. 存储对于key
@GetMapping("/redis")
public Result chatDialogue() {
CacheKey.PWD_RESET_CODE.valueSetAndExpire("test", 60l, TimeUnit.SECONDS, "judy");
return Result.buildSuccess();
}
  1. 获取对应的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 文档。项目启动后即可访问。

  1. swagger-ui.html 文档。路径/swagger-ui.html

  2. 基于spring-doc 文档UI增强 路径/doc.html

  3. 接口文档属性信息

  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>

项目使用

  1. 代码生成配置类
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);
}
}

常见问题

在使用过程中尽量使用最新版本。我会持续更新更多的内容。 会第一时间发布在我的公众号

程序员三时。全网同名

可以关注 公众号 程序员三时。用心分享持续输出优质内容。希望可以给你带来一点帮助

我开源了团队内部基于SpringBoot Web快速开发的API脚手架stater的更多相关文章

  1. Spring Boot 是 Spring 的一套快速配置脚手架,可以基于Spring Boot 快速开发单个微服务

    Spring Boot 是 Spring 的一套快速配置脚手架,可以基于Spring Boot 快速开发单个微服务,Spring Cloud是一个基于Spring Boot实现的云应用开发工具:Spr ...

  2. JAVA WEB快速入门之从编写一个基于SpringBoot+Mybatis快速创建的REST API项目了解SpringBoot、SpringMVC REST API、Mybatis等相关知识

    JAVA WEB快速入门系列之前的相关文章如下:(文章全部本人[梦在旅途原创],文中内容可能部份图片.代码参照网上资源) 第一篇:JAVA WEB快速入门之环境搭建 第二篇:JAVA WEB快速入门之 ...

  3. 5款最好用的开源Web快速开发工具

    1.Aptana Studio Aptana是一个用于HTML,CSS和JavaScript的网站开发工具.目前在社区里有成千上万的人在开发Aptana的插件. Apatana Studio官网:ht ...

  4. JEECG-Boot 项目介绍——基于代码生成器的快速开发平台(Springboot前后端分离)

    Jeecg-Boot 是一款基于代码生成器的智能开发平台!采用前后端分离架构:SpringBoot,Mybatis,Shiro,JWT,Vue&Ant Design.强大的代码生成器让前端和后 ...

  5. 如何提高码农产量,基于java的web快速开发平台之自定义表单开发随笔

    老板 :下班前一定写完? 程序猿:可以,下班前能一定给! 第二天早上上班~~~ 老板:这都第二天了,怎么没写完? 程序猿:我还没有下班呢! 哎!程序猿的痛啊 公司上线的项目有不少销售记录表,又是报价单 ...

  6. 基于代码生成器的快速开发平台 JEECG

    JEECG是一款基于代码生成器的J2EE快速开发平台,开源界“小普元”超越传统商业企业级开发平台.引领新的开发模式(Online Coding模式(在线开发)->代码生成器模式->手工ME ...

  7. 基于MINA框架快速开发网络应用程序

    1.MINA框架简介 MINA(Multipurpose Infrastructure for Network Applications)是用于开发高性能和高可用性的网络应用程序的基础框架.通过使用M ...

  8. 使用springBoot进行快速开发

    springBoot项目是spring的一个子项目,使用约定由于配置的思想省去了以往在开发过程中许多的配置工作(其实使用springBoot并不是零配置,只是使用了注解完全省去了XML文件的配置),达 ...

  9. 玩转 SpringBoot 2 快速搭建 | RESTful Api 篇

    概述 RESTful 是一种架构风格,任何符合 RESTful 风格的架构,我们都可以称之为 RESTful 架构.我们常说的 RESTful Api 是符合 RESTful 原则和约束的 HTTP ...

  10. .NETCore 基于 dbfirst 体验快速开发项目

    简介 今天出场是进化了多年的生成器工具,根据数据库结构(表.视图.存储过程.外键.各种类型.备注)快速生成一个项目,并自带后台管理系统.篇幅有限本文只讲解快速开发的使用过程,具体开发中的细节日后有空再 ...

随机推荐

  1. Redis(七)缓存穿透、缓存击穿、缓存雪崩以及分布式锁

    应用问题解决 1 缓存穿透 1.1 访问结构 正常情况下,服务器接收到浏览器发来的web服务请求,会先去访问redis缓存,如果缓存中存在数据则直接返回,否则会去查询数据库里面的数据,然后保存在red ...

  2. Android Studio 样式和主题背景

    样式和主题背景 转载自   Styles and Themes  |  Android Developers 借助 Android 中的样式和主题背景,您可以将应用设计的细节与界面的结构和行为分开,其 ...

  3. mysql 自动挂掉

    今天在看后台的时候,发现登录不上去了,登录页面是可以访问,但是就是登录不上去,上了后台看了一下,说mysql连接超时,然后我重启了一下服务器,发现依然报mysql的错误,我尝试连接mysql, 报了一 ...

  4. HashMap实现原理和自动扩容

    HashMap实现原理: JDK1.7:数组+单向链表(头插) 在并发情况下头插可能出现循环链表(死循环)问题.原因:因为头插,在新数组中链表的元素顺序发生了变化, 如上图,假设线程1在扩容,刚刚调整 ...

  5. 海思码率控制相关参数调优(CBR/VBR)

    1.CBR 海思相关参数调整(在Hisi板,cat /proc/umap/rc 可查看相关参数变化) 1.1 RC参数 1.2 VENC参数 VENC_PARAM_H264_CBR_S/VENC_PA ...

  6. 我的第一个项目(十二) :分数和生命值的更新(后端增删查改的"改")

    好家伙,写后端,这多是一件美逝. 关于这个项目的代码前面的博客有写  我的第一个独立项目 - 随笔分类 - 养肥胖虎 - 博客园 (cnblogs.com) 现在,我们登陆进去了,我开始和敌人战斗,诶 ...

  7. [C++核心编程] 4.3、类和对象-C++对象模型和this指针

    文章目录 4.3 C++对象模型和this指针 4.3.1 成员变量和成员函数分开存储 4.3.2 this指针概念 4.3.3 空指针访问成员函数 4.3.4 const修饰成员函数 4.3 C++ ...

  8. TOF和结构光

    文章目录 TOF和结构光 一.ToF 二.结构光 三.测量距离.分辨率.开发周期的对比 TOF和结构光 一.ToF ToF(Time of Flight)飞行时间 字面理解就是通过光的飞行时间来计算距 ...

  9. 自建CA和公共CA有什么不同?

    据统计,全球有数百个公共CA,通常它们是按国家地区进行划分的.这类CA受大众的广泛认可和使用,也被称为公共信任的证书颁发机构.但是由于一些大型企业拥有许多站点,为了更轻松高效的管理以及考虑到维护成本, ...

  10. 2020-11-19:go中,defer原理是什么?

    福哥答案2020-11-19:- - 什么是defer - defer是go语言提供的一种用于注册延迟调用的机制:让函数或者语句在当前函数执行完毕(包括return正常结束或者panic导致的异常结束 ...