我们现在使用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. 【Spring注解驱动】(三)servlet3.0

    前言 今天是7.21日,终于是看完了..暑假在家学习是真的差点意思 1 Servlet 3.0简介 Servlet 2.0是在web.xml中配置servlet filter.listener.Dis ...

  2. Tomcat长轮询原理与源码解析

    Tomcat长轮询原理与源码解析 系列文章目录和关于我 零丶长轮询的引入 最近在看工作使用到的diamond配置中心原理,发现大多数配置中心在推和拉模型上做的选择出奇的一致选择了基于长轮询的拉模型 基 ...

  3. 一天吃透SpringMVC面试八股文

    说说你对 SpringMVC 的理解 SpringMVC是一种基于 Java 的实现MVC设计模型的请求驱动类型的轻量级Web框架,属于Spring框架的一个模块. 它通过一套注解,让一个简单的Jav ...

  4. 搞懂Python正则表达式,这一篇就够了

    本文代码基于Python3.11解释器,除了第一次示例,代码将省略 import re 这个语句 所有示例代码均可以在我的github仓库中的 code.py文件内查看 [我的仓库](PythonLe ...

  5. Python 列表定义

    列表定义 由一系列按特定排序排列的元素组成,各元素之间无任何关系 用方括号[]来表示列表,并用逗号分隔其中的元素 访问列表元素 列表是有序集合,访问列表元素时,只需将该元素的位置或索引告知python ...

  6. Win YAPI + Jenkins 实现接口自动化测试

    自动化测试 传统的接口自动化测试成本高,大量的项目没有使用自动化测试保证接口的质量,仅仅依靠手动测试,是非常不可靠和容易出错的. 为了解决这个问题,使用YAPI接口自动化测试功能,只需要配置每个接口的 ...

  7. KMP字符串匹配问题

    KMP算法 本文参考资料:https://www.zhihu.com/question/21923021 KMP算法是一种字符串匹配算法,可以在 \(O(n+m)\) 的时间复杂度内实现两个字符串的匹 ...

  8. boot-admin整合Liquibase实现数据库版本管理

    Liquibase 和 Flyway 是两款成熟的.优秀的.开源/商业版的数据库版本管理工具,鉴于 Flyway 的社区版本对 Oracle 数据库支持存在限制,所以 boot-admin 选择整合 ...

  9. 2022-03-20:给定一棵多叉树的头节点head, 每个节点的颜色只会是0、1、2、3中的一种, 任何两个节点之间的都有路径, 如果节点a和节点b的路径上,包含全部的颜色,这条路径算达标路径, (

    2022-03-20:给定一棵多叉树的头节点head, 每个节点的颜色只会是0.1.2.3中的一种, 任何两个节点之间的都有路径, 如果节点a和节点b的路径上,包含全部的颜色,这条路径算达标路径, ( ...

  10. Selenium - 浏览器配置(2) - 浏览器默认下载路径设置

    Selenium - 浏览器配置 浏览器下载路径设置 我们默认使用谷歌浏览器下载文件的时候,总会弹出窗口指定文件下载路径: 但是selenium不能对浏览器窗口以外的窗口做操作: 可以在谷歌配置信息中 ...