我们现在使用SpringBoot 做Web 开发已经比之前SprngMvc 那一套强大很多了。 但是 用SpringBoot Web 做API 开发还是不够简洁有一些。
每次Web API常用功能都需要重新写一遍。或者复制之前项目代码。于是我封装了这么一个

抽出SpringBoot Web API 每个项目必备需要重复写的模块,和必备功能。 并且扩展了我工作中用到的 所有工具库。
基于它,你可以轻松开发SpringBoot WEB API,提高效率。不在去关心一些繁琐。重复工作,而是把重点聚焦到业务。
目前更新版本到1.5.2 功能如下
- 支持一键配置自定义RestFull API 统一格式返回
- 支持RestFull API 错误国际化
- 支持全局异常处理,全局参数验证处理
- 业务错误断言工具封装,遵循错误优先返回原则
- 封装Redis key,value 操作工具类。统一key管理 spring cache缓存实现
- RestTemplate 封装 POST,GET 请求工具
- 日志集成。自定义日志路径,按照日志等级分类,支持压缩和文件大小分割。按时间显示
- 工具库集成 集成了lombok,hutool,commons-lang3,guava。不需要自己单个引入
- 集成mybatisPlus一键代码生成
- 日志记录,服务监控,支持日志链路查询。自定义数据源
- OpenApi3文档一键配置。支持多种文档和自动配置
- 接口限流,Ip城市回显
- HttpUserAgent请求设备工具封装
- RequestUtil参数解析封装工具
后续会持续更新。项目中重复使用,必备模块和工具。
rest-api-spring-boot-starter 适用于SpringBoot Web API 快速构建让开发人员快速构建统一规范的业务RestFull API 不在去关心一些繁琐。重复工作,而是把重点聚焦到业务。
快速开始
- 项目pom中引入依赖
1<dependency> 2 <groupId>cn.soboys</groupId> 3 <artifactId>rest-api-spring-boot-starter</artifactId> 4 <version>1.5.0</version> 5</dependency>
- 在SpringBoot启动类或者配置类上通过 @EnableRestFullApi注解开启rest-api
1 2@SpringBootApplication 3@EnableRestFullApi 4public class SuperaideApplication { 5 6 public static void main(String[] args) { 7 SpringApplication.run(SuperaideApplication.class, args); 8 } 9}
到此你项目中就可以使用所有的功能了。
RestFull API
在Controller中我们写普通的请求接口如:
1@PostMapping("/chat") 2public HashMap chatDialogue() { 3 HashMap m = new HashMap(); 4 m.put("age", 26); 5 m.put("name", "Judy"); 6 return m; 7}
返回的就是全局统一RestFull API
1{ 2 "success": true, 3 "code": "OK", 4 "msg": "操作成功", 5 "requestId": "IPbHLE5SZ1fqI0lgNXlB", 6 "timestamp": "2023-07-09 02:39:40", 7 "data": { 8 "name": "judy", 9 "hobby": "swing", 10 "age": 18 11 } 12}
也可以基于Result构建
1@PostMapping("/chat") 2public Result chatDialogue(@Validated EntityParam s) { 3 return Result.buildSuccess(s); 4}
分页支持
我们在日常中分页是一个比较特殊返回。也是非常常用的。
1@PostMapping("/page") 2@Log("分页查询用户数据") 3public Result page(@Validated EntityParam s) { 4 ResultPage<List<EntityParam>> resultPage=new ResultPage<>(); 5 List a=new ArrayList(); 6 a.add(s); 7 resultPage.setPageData(a); 8 return ResultPage.buildSuccess(resultPage); 9}
- 构建自定义自己的分页数据
1ResultPage<List<EntityParam>> resultPage=new ResultPage<>();
- 通过
ResultPage.buildSuccess(resultPage)进行构建返回
返回统一响应格式
1{ 2 "previousPage": 1, 3 "nextPage": 1, 4 "pageSize": 1, 5 "totalPageSize": 1, 6 "hasNext": "false", 7 "success": true, 8 "code": "OK", 9 "msg": "操作成功", 10 "requestId": "D9AMALgkZ6gVfe6Pi0Oh", 11 "timestamp": "2023-07-09 02:39:40", 12 "data": [ 13 { 14 "name": "judy", 15 "hobby": "swing", 16 "age": 18 17 } 18 ] 19}
自定义返回格式
1{ 2 "previousPage": 1, 3 "nextPage": 1, 4 "pageSize": 1, 5 "totalPageSize": 1, 6 "hasNext": "false", 7 "success": true, 8 "code": "OK", 9 "msg": "操作成功", 10 "requestId": "D9AMALgkZ6gVfe6Pi0Oh", 11 "timestamp": "2023-07-09 02:39:40", 12 "data": [ 13 { 14 "name": "judy", 15 "hobby": "swing", 16 "age": 18 17 } 18 ] 19}
上述统一返回格式,可能不符合你项目中接口统一格式如:
1{ 2 "success": true, 3 "code": "OK", 4 "msg": "操作成功", 5 "requestId": "ztf4S-lP9yrtKPSiwldZ", 6 "timestamp": "2023-07-11 13:46:53", 7 "data": { 8 "previousPage": 1, 9 "nextPage": 1, 10 "pageSize": 1, 11 "totalPageSize": 1, 12 "hasNext": "false", 13 "pageData": [ 14 { 15 "name": "judy", 16 "hobby": "swing", 17 "age": 18 18 } 19 ] 20 } 21}
page分页数据是在data里面你可以定义pageWrap属性true包装返回定义pageData的key值如records等
你需要自定义key如 msg你可能对应message,success你可能对应status只需要在配置文件中配置自定义key
自定义返回成功值你的成功返回可能是200你可以配置code-success-value值
1rest-api: 2 enabled: false 3 msg: msg 4 code: code 5 code-success-value: OK 6 success: success 7 previousPage: previousPage 8 nextPage: nextPage 9 pageSize: pageSize 10 hasNext: hasNext 11 totalPageSize: totalPageSize 12 data: info
当 enabled开启后会读取你自定义配置的key 如
1rest-api: 2 enabled: true 3 msg: msg1 4 code: code1 5 code-success-value: 200 6 success: success1 7 previousPage: previousPage1 8 nextPage: nextPage1 9 pageSize: pageSize1 10 hasNext: hasNext1 11 totalPageSize: totalPageSize1 12 data: info
对应返回内容
1{ 2 "success": true, 3 "code": "OK", 4 "msg": "操作成功", 5 "requestId": "ztf4S-lP9yrtKPSiwldZ", 6 "timestamp": "2023-07-11 13:46:53", 7 "data": { 8 "previousPage": 1, 9 "nextPage": 1, 10 "pageSize": 1, 11 "totalPageSize": 1, 12 "hasNext": "false", 13 "pageData": [ 14 { 15 "name": "judy", 16 "hobby": "swing", 17 "age": 18 18 } 19 ] 20 } 21}
自定义返回
有时候我们需要自定义返回。不去包装统一响应RestFull API格式
- 可以通过注解
@NoRestFulApi实现如
1@GetMapping("/test") 2@NoRestFulApi 3public Map chatDialogue() { 4 Map m= new HashMap<>(); 5 m.put("name","judy"); 6 m.put("age",26); 7 return m; 8}
- 通过类扫描去实现
默认会过滤
String类型认为是页面路径。
通过属性配置文件include-packages需要统一返回包。exclude-packages不需统一返回的包
1include-packages: cn.soboys.superaide.controller 2exclude-packages: xx.xxx.xxx
OpenApi文档生成
已经内置自动支持。swagger文档。和最新的OpenApi3 文档。项目启动后即可访问。
-
swagger-ui.html 文档。路径
/swagger-ui.html -
基于spring-doc 文档UI增强 路径
/doc.html -
接口文档属性信息
1 openapi: 2 description: 3 title: 4 version: 5 license: 6 contact: 7 name: 8 email: 9 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 的引入。
1springdoc: 2 swagger-ui: 3 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 的指定。
1# urls 2springdoc: 3 swagger-ui: 4 urls: 5 - { name: 'sample', url: '/v3/api-docs' }
或者
1#分组 2springdoc: 3 group-configs: 4 - { group: 'sample', packages-to-scan: 'com.example' }
Knife4j 的 UI 访问地址有所不同,页面映射在 doc.html 路径下,启动项目后,访问 http://server:port/context-path/doc.html
即可进入 Knife4j 的 Swagger UI 页面。
全局错误拦截,参数校验
帮你封装好了所有http常见错误,和所有请求参数验证错误。
如请求错误
1{ 2 "success": false, 3 "code": "405", 4 "msg": "方法不被允许", 5 "timestamp": "2023-07-03 22:36:47", 6 "data": "Request method 'GET' not supported" 7}
请求资源不存在等
1{ 2 "success": false, 3 "code": "404", 4 "msg": "请求资源不存在", 5 "timestamp": "2023-07-03 22:42:35", 6 "data": "/api" 7}
如果需要拦截上面错误请在springboot 配置文件中加入
1#出现错误时, 直接抛出异常 2spring.mvc.throw-exception-if-no-handler-found=true 3#不要为我们工程中的资源文件建立映射 4spring.web.resources.add-mappings=false
参数校验错误
验证Studen对象参数
1/** 2 * @author 公众号 程序员三时 3 * @version 1.0 4 * @date 2023/6/26 22:10 5 * @webSite https://github.com/coder-amiao 6 */ 7@Data 8public class Student { 9 @NotBlank 10 private String nam; 11 @NotBlank 12 private String hobby; 13}
1 @PostMapping("/chat") 2 public HashMap chatDialogue(@Validated Student student) { 3 HashMap m = new HashMap(); 4 m.put("age", 26); 5 m.put("name", "Judy"); 6 return m; 7 }
请求结果

JSON Body参数
1 @PostMapping("/chat") 2 public HashMap chatDialogue(@RequestBody @Validated Student student) { 3 HashMap m = new HashMap(); 4 m.put("age", 26); 5 m.put("name", "Judy"); 6 return m; 7 }


错误国际化
内置封装错误默认支持英文和中文两种国际化。你不做任何配置自动支持
如果需要内置支持更多语言,覆盖即可。
自定义自己错误国际化和语言
1 i18n: 2 # 若前端无header传参则返回中文信息 3 i18n-header: Lang 4 default-lang: cn 5 message: 6 # admin 7 internal_server_error: 8 en: Internal Server Error 9 cn: 系统错误 10 not_found: 11 en: Not Found 12 cn: 请求资源不存在
message 对应错误提示 对应internal_server_error 自定义 下面语言自己定义 和前端传入i18n-header 对应上,就显你定义错误语言
我不传错误国际化默认就是中文在 default-lang: cn 进行配置

当我传入 指定语言 就会按照你配置的国际化自定义返回错误提示

日志链路追踪
RestFull API 统一返回有一个requestId 它是每个接口唯一标识。用于接口请求日志链路追踪。日志查询。 如:
1{ 2 "msg": "操作成功", 3 "code": "OK", 4 "previousPage": 1, 5 "success": true, 6 "requestId": "udYNdbbMFE45R84OPu9m", 7 "nextPage": 1, 8 "pageSize": 1, 9 "totalPageSize": 1, 10 "hasNext": "false", 11 "timestamp": "2023-07-09 03:00:27", 12 "info": [ 13 { 14 "name": "judy", 15 "hobby": "swing", 16 "age": 18 17 } 18 ] 19}
通过requestId你可以很轻松的在你的日志文件查询定位到每次错误的请求。
通过Log注解记录你想要记录请求
1@PostMapping("/page") 2@Log(value = "查询用户数据",apiType= LogApiTypeEnum.USER,CURDType= LogCURDTypeEnum.RETRIEVE) 3public Result page(@Validated EntityParam s) { 4 ResultPage<List<EntityParam>> resultPage=new ResultPage<>(); 5 List a=new ArrayList(); 6 a.add(s); 7 resultPage.setPageData(a); 8 return ResultPage.buildSuccess(resultPage); 9}
系统默认日志记录数据源为日志文件。如
12023-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],描述为 [接口限流] 的接口 22023-07-13 11:21:26 INFO http-nio-8888-exec-2 cn.soboys.restapispringbootstarter.log.LogFileDefaultDataSource { 3 "description": "日志记录测试", 4 "method": "cn.soboys.restapispringbootstarter.controller.ApiRestController.chatDialogue()", 5 "params": { 6 }, 7 "logType": "INFO", 8 "requestIp": "192.168.1.8", 9 "path": "/chat", 10 "address": "0|0|0|内网IP|内网IP", 11 "time": 128, 12 "os": "Mac", 13 "browser": "Chrome", 14 "result": { 15 "success": true, 16 "code": "OK", 17 "msg": "操作成功", 18 "requestId": "5RgKzWGFNa9XSPwhw2Pi", 19 "timestamp": "2023-07-13 11:21:25", 20 "data": "接口限流测试" 21 }, 22 "apiType": "USER", 23 "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" 24}
你可以自定义自己的日志数据源实现LogDataSource接口 日志操作支持异步。需要在配置类。或者启动类加上@EnableAsync
注解
1package cn.soboys.restapispringbootstarter.log; 2 3import org.springframework.scheduling.annotation.Async; 4 5import java.util.Map; 6 7/** 8 * @Author: kenx 9 * @Since: 2021/6/23 13:55 10 * @Description: 11 */ 12public interface LogDataSource { 13 14 /** 15 * 获取拓展数据 16 * @return 17 * @param logEntry 18 */ 19 @Async 20 void save(LogEntry logEntry); 21} 22
或者你可以继承我默认的日志数据源实现类LogFileDefaultDataSource 重写save(LogEntry logEntry)方法。
1@Slf4j 2public class LogFileDefaultDataSource implements LogDataSource { 3 4 /** 5 * 自定义保存数据源 6 * 7 * @param 8 * @return LogEntry 9 */ 10 @Override 11 public void save(LogEntry logEntry) { 12 log.info(JSONUtil.toJsonPrettyStr(logEntry)); 13 } 14}
如果是自定义日志数据源实现需要再配置文件,配置日志数据源。如:
1logging: 2 path: ./logs #日志存储路径(服务器上绝对) 3 max-history: 90 # 保存多少天 4 max-file-size: 3MB # 每个文件大小 5 max-total-size-cap: 1GB #总文件大小超过多少压缩 6 level-root: INFO # 这里的INFO可以替换为其他日志等级,如DEBUG, WARN, ERROR, TRACE, FATAL, OFF等。 日志等级由低到高分别是debugger-info-warn-error 7 logDataSourceClass: cn.soboys.restapispringbootstarter.log.LogFileDefaultDataSource # 日志数据源
Ip城市记录
日志记录提供Ip城市回显记录
1@PostMapping("/page") 2@Log(value = "查询用户数据", apiType = LogApiTypeEnum.USER, CURDType = LogCURDTypeEnum.RETRIEVE,ipCity = true) 3public Result page(@Validated EntityParam s) { 4 ResultPage<List<EntityParam>> resultPage = new ResultPage<>(); 5 List a = new ArrayList(); 6 a.add(s); 7 resultPage.setPageData(a); 8 return ResultPage.buildSuccess(resultPage); 9}
通过配置ipCity属性,默认是true会记录IP对应物理地址详细信息即国家城市等
Ip城市查询通过ip2region获取。
你可配置属性 location 配置自己的ip2region.xdb文件。
1 ip2region: 2 external: false 3 location: classpath:ip2region/ip2region.xdb
默认不配置会帮你自动生成一个。基于2.7.x最新的数据 获取最新自定义ip数据 Github 仓库
当然也帮你封装了。你可以通过工具类HttpUserAgent的静态方法getIpToCityInfo(String ip) 去获取查询ip对应城市信息
属性配置
配置语言国际化,日志等,
::: tip 默认不用配置任何参数。会使用默认的,配置了会使用你项目中的配置。 :::
默认配置
1rest-api: 2 enabled: false 3 msg: msg 4 code: code 5 code-success-value: OK 6 success: success 7 previousPage: previousPage 8 nextPage: nextPage 9 pageSize: pageSize 10 hasNext: hasNext 11 totalPageSize: totalPageSize 12 data: info 13 include-packages: cn.soboys.superaide.controller 14 exclude-packages: xx.xxx.xxx 15 redis: 16 key-prefix: rest 17 openapi: 18 description: 19 title: 20 version: 21 license: 22 contact: 23 name: 24 email: 25 url: 26 logging: 27 path: ./logs #日志存储路径(服务器上绝对) 28 max-history: 90 # 保存多少天 29 max-file-size: 3MB # 每个文件大小 30 max-total-size-cap: 1GB #总文件大小超过多少压缩 31 level-root: INFO # 这里的INFO可以替换为其他日志等级,如DEBUG, WARN, ERROR, TRACE, FATAL, OFF等。 日志等级由低到高分别是debugger-info-warn-error 32 logDataSourceClass: cn.soboys.restapispringbootstarter.log.LogFileDefaultDataSource # 日志数据源 33 i18n: 34 # 若前端无header传参则返回中文信息 35 i18n-header: Lang 36 default-lang: cn 37 message: 38 # admin 39 internal_server_error: 40 en: Internal Server Error 41 cn: 系统错误 42 bad_gateway: 43 en: Bad Gateway 44 cn: 错误的请求 45 unauthorized: 46 en: Unauthorized 47 cn: 未授权 48 forbidden: 49 en: Forbidden 50 cn: 资源禁止访问 51 method_not_allowed: 52 en: Method Not Allowed 53 cn: 方法不被允许 54 request_timeout: 55 en: Request Timeout 56 cn: 请求超时 57 invalid_argument: 58 en: Invalid Argument {} 59 cn: 参数错误 {} 60 argument_analyze: 61 en: Argument Analyze {} 62 cn: 参数解析异常 {} 63 business_exception: 64 en: Business Exception 65 cn: 业务错误 66 not_found: 67 en: Not Found 68 cn: 请求资源不存在 69
代码生成配置
支持MybatisPlus代码一键生成 默认不引入MybatisPlus生成依赖需要手动引入
1package cn.soboys.restapispringbootstarter.config; 2 3import lombok.Data; 4 5/** 6 * @author 公众号 程序员三时 7 * @version 1.0 8 * @date 2023/7/5 00:05 9 * @webSite https://github.com/coder-amiao 10 */ 11@Data 12public class GenerateCodeConfig { 13 /** 14 * 数据库驱动 15 */ 16 private String driverName; 17 /** 18 * 数据库连接用户名 19 */ 20 private String username; 21 /** 22 * 数据库连接密码 23 */ 24 private String password; 25 /** 26 * 数据库连接url 27 */ 28 private String url; 29 /** 30 * 生成代码 保存路径。默认当前项目下。 31 * 如需修改,使用觉得路径 32 */ 33 private String projectPath; 34 /** 35 * 代码生成包位置 36 */ 37 private String packages; 38} 39
RestFull API
Controller中直接使用
1@PostMapping("/chat") 2public HashMap chatDialogue() { 3 HashMap m = new HashMap(); 4 m.put("age", 26); 5 m.put("name", "Judy"); 6 return m; 7}
Result构建返回
1@PostMapping("/chat") 2public Result chatDialogue() { 3 HashMap m = new HashMap(); 4 m.put("age", 26); 5 m.put("name", "Judy"); 6 return Result.buildSuccess(m); 7}
分页支持
我们在日常中分页是一个比较特殊返回。也是非常常用的。
1@PostMapping("/page") 2@Log("分页查询用户数据") 3public Result page(@Validated EntityParam s) { 4 ResultPage<List<EntityParam>> resultPage=new ResultPage<>(); 5 List a=new ArrayList(); 6 a.add(s); 7 resultPage.setPageData(a); 8 return ResultPage.buildSuccess(resultPage); 9}
- 构建自定义自己的分页数据
1ResultPage<List<EntityParam>> resultPage=new ResultPage<>();
- 通过
ResultPage.buildSuccess(resultPage)进行构建返回
返回统一响应格式
1{ 2 "previousPage": 1, 3 "nextPage": 1, 4 "pageSize": 1, 5 "totalPageSize": 1, 6 "hasNext": "false", 7 "success": true, 8 "code": "OK", 9 "msg": "操作成功", 10 "requestId": "D9AMALgkZ6gVfe6Pi0Oh", 11 "timestamp": "2023-07-09 02:39:40", 12 "data": [ 13 { 14 "name": "judy", 15 "hobby": "swing", 16 "age": 18 17 } 18 ] 19}
自定义返回格式
1{ 2 "previousPage": 1, 3 "nextPage": 1, 4 "pageSize": 1, 5 "totalPageSize": 1, 6 "hasNext": "false", 7 "success": true, 8 "code": "OK", 9 "msg": "操作成功", 10 "requestId": "D9AMALgkZ6gVfe6Pi0Oh", 11 "timestamp": "2023-07-09 02:39:40", 12 "data": [ 13 { 14 "name": "judy", 15 "hobby": "swing", 16 "age": 18 17 } 18 ] 19}
上述统一返回格式,可能不符合你项目中接口统一格式如:
1{ 2 "success": true, 3 "code": "OK", 4 "msg": "操作成功", 5 "requestId": "ztf4S-lP9yrtKPSiwldZ", 6 "timestamp": "2023-07-11 13:46:53", 7 "data": { 8 "previousPage": 1, 9 "nextPage": 1, 10 "pageSize": 1, 11 "totalPageSize": 1, 12 "hasNext": "false", 13 "pageData": [ 14 { 15 "name": "judy", 16 "hobby": "swing", 17 "age": 18 18 } 19 ] 20 } 21}
page分页数据是在data里面你可以定义pageWrap属性true包装返回定义pageData的key值如records等
你需要自定义key如 msg你可能对应message,success你可能对应status只需要在配置文件中配置自定义key
自定义返回成功值你的成功返回可能是200你可以配置code-success-value值
1rest-api: 2 enabled: false 3 msg: msg 4 code: code 5 code-success-value: OK 6 success: success 7 previousPage: previousPage 8 nextPage: nextPage 9 pageSize: pageSize 10 hasNext: hasNext 11 totalPageSize: totalPageSize 12 data: info
当 enabled开启后会读取你自定义配置的key 如
1rest-api: 2 enabled: true 3 msg: msg1 4 code: code1 5 code-success-value: 200 6 success: success1 7 previousPage: previousPage1 8 nextPage: nextPage1 9 pageSize: pageSize1 10 hasNext: hasNext1 11 totalPageSize: totalPageSize1 12 data: info
对应返回内容
1{ 2 "success": true, 3 "code": "OK", 4 "msg": "操作成功", 5 "requestId": "ztf4S-lP9yrtKPSiwldZ", 6 "timestamp": "2023-07-11 13:46:53", 7 "data": { 8 "previousPage": 1, 9 "nextPage": 1, 10 "pageSize": 1, 11 "totalPageSize": 1, 12 "hasNext": "false", 13 "pageData": [ 14 { 15 "name": "judy", 16 "hobby": "swing", 17 "age": 18 18 } 19 ] 20 } 21}
自定义返回
有时候我们需要自定义返回。不去包装统一响应RestFull API格式
- 可以通过注解
@NoRestFulApi实现如
1@GetMapping("/test") 2@NoRestFulApi 3public Map chatDialogue() { 4 Map m= new HashMap<>(); 5 m.put("name","judy"); 6 m.put("age",26); 7 return m; 8}
- 通过类扫描去实现
默认会过滤
String类型认为是页面路径。
通过属性配置文件include-packages需要统一返回包。exclude-packages不需统一返回的包
1include-packages: cn.soboys.superaide.controller 2exclude-packages: xx.xxx.xxx
错误国际化支持
内置常见的错误。可以看HttpStatus。默认错误支持中文和英文两种国际化。配置如下
1 i18n: 2 # 若前端无header传参则返回中文信息 3 i18n-header: Lang 4 default-lang: cn 5 message: 6 # admin 7 internal_server_error: 8 en: Internal Server Error 9 cn: 系统错误 10 bad_gateway: 11 en: Bad Gateway 12 cn: 错误的请求 13 unauthorized: 14 en: Unauthorized 15 cn: 未授权 16 forbidden: 17 en: Forbidden 18 cn: 资源禁止访问 19 method_not_allowed: 20 en: Method Not Allowed 21 cn: 方法不被允许 22 request_timeout: 23 en: Request Timeout 24 cn: 请求超时 25 invalid_argument: 26 en: Invalid Argument {} 27 cn: 参数错误 {} 28 argument_analyze: 29 en: Argument Analyze {} 30 cn: 参数解析异常 {} 31 business_exception: 32 en: Business Exception 33 cn: 业务错误 34 not_found: 35 en: Not Found 36 cn: 请求资源不存在
可以自行覆盖扩充
全局错误拦截和响应
默认拦所有未知错误异常和validation参数校验失败异常,以及Http请求异常。
还有全局自定义BusinessException 业务异常 自动集成spring-boot-starter-validation 你项目中不需要再单独引入
1<!--参数校验--> 2<dependency> 3 <groupId>org.springframework.boot</groupId> 4 <artifactId>spring-boot-starter-validation</artifactId> 5</dependency>
也内置扩展了许多自定义参数校验参考

第三方请求
有时候我们项目中需要调用第三方接口服务。基于RestTemplate 进一步封装了直接的POST,GET,请求。
在需要使用地方注入RestFulTemp
1@Resource 2private RestFulTemp restFulTemp;
GET请求
1@GetMapping("/doGet") 2public Result doGet() { 3 ResponseEntity<String> response = restFulTemp.doGet("http://127.0.0.1:9000/redis/get"); 4 return Result.buildSuccess(); 5}
POST 请求
1/** 2 * POST 请求参 数为body json体格式 3 * @return 4 */ 5@PostMapping("/doPost") 6public Result doPost() { 7 Student s=new Student(); 8 s.setHobby("swing"); 9 s.setNam("judy"); 10 //自动把对象转换为JSON 11 ResponseEntity<String> response = 12 restFulTemp.doPost("http://127.0.0.1:9000/redis/get",s); 13 return Result.buildSuccess(); 14}
1/** 2 * POST请求 参数为FORM 表单参数 3 * @return 4 */ 5@PostMapping("/doPost") 6public Result doPostForm() { 7 EntityParam s=new EntityParam(); 8 s.setAge(19); 9 s.setHobby("swing"); 10 s.setName("judy"); 11 12 ResponseEntity<String> response = 13 restFulTemp.doPostForm("http://127.0.0.1:8000/chat", BeanUtil.beanToMap(s)); 14 return Result.buildSuccess(response.getBody()); 15}
DELETE请求
1@GetMapping("/doDelete") 2public Result doDelete() { 3 restFulTemp.doDelete("http://127.0.0.1:8000/chat"); 4 return Result.buildSuccess(); 5}
PUT请求
1@GetMapping("/doPut") 2public Result doPut() { 3 EntityParam s=new EntityParam(); 4 restFulTemp.doPut("http://127.0.0.1:8000/chat",s); 5 return Result.buildSuccess(s); 6}
错误异常自定义
我内置错误异常和业务异常可能无法满足你自身接口业务异常需要。你可以自定义错误异常类,和错误响应枚举码。
自定义错误枚举 需要实现ResultCode接口
1package cn.soboys.restapispringbootstarter; 2 3import cn.soboys.restapispringbootstarter.i18n.I18NKey; 4 5/** 6* @author 公众号 程序员三时 7* @version 1.0 8* @date 2023/6/26 10:21 9* @webSite https://github.com/coder-amiao 10* 响应码接口,自定义响应码,实现此接口 11*/ 12public interface ResultCode extends I18NKey { 13 14 String getCode(); 15 16 String getMessage(); 17 18}
如果要支持国际化还需要实现国际化接口I18NKey 参考我内部HttpStatus实现即可
1package cn.soboys.restapispringbootstarter; 2 3import cn.soboys.restapispringbootstarter.i18n.I18NKey; 4 5/** 6 * @author 公众号 程序员三时 7 * @version 1.0 8 * @date 2023/6/26 11:01 9 * @webSite https://github.com/coder-amiao 10 */ 11public enum HttpStatus implements ResultCode, I18NKey { 12 /** 13 * 系统内部错误 14 */ 15 INTERNAL_SERVER_ERROR("500", "internal_server_error"), 16 BAD_GATEWAY("502", "bad_gateway"), 17 NOT_FOUND("404", "not_found"), 18 UNAUTHORIZED("401", "unauthorized"), 19 FORBIDDEN("403", "forbidden"), 20 METHOD_NOT_ALLOWED("405", "method_not_allowed"), 21 REQUEST_TIMEOUT("408", "request_timeout"), 22 23 INVALID_ARGUMENT("10000", "invalid_argument"), 24 ARGUMENT_ANALYZE("10001", "argument_analyze"), 25 BUSINESS_EXCEPTION("20000", "business_exception"); 26 27 28 private final String value; 29 30 private final String message; 31 32 HttpStatus(String value, String message) { 33 this.value = value; 34 this.message = message; 35 } 36 37 38 @Override 39 public String getCode() { 40 return value; 41 } 42 43 @Override 44 public String getMessage() { 45 return message; 46 } 47 48 49 @Override 50 public String key() { 51 return message; 52 } 53} 54 55
1rest-api: 2 enabled: false 3 i18n: 4 # 若前端无header传参则返回中文信息 5 i18n-header: Lang 6 default-lang: cn 7 message: 8 # admin 9 internal_server_error: 10 en: Internal Server Error 11 cn: 系统错误 12 bad_gateway: 13 en: Bad Gateway 14 cn: 错误的请求 15 unauthorized: 16 en: Unauthorized 17 cn: 未授权 18 forbidden: 19 en: Forbidden 20 cn: 资源禁止访问 21 method_not_allowed: 22 en: Method Not Allowed 23 cn: 方法不被允许 24 request_timeout: 25 en: Request Timeout 26 cn: 请求超时 27 invalid_argument: 28 en: Invalid Argument {} 29 cn: 参数错误 {} 30 argument_analyze: 31 en: Argument Analyze {} 32 cn: 参数解析异常 {} 33 business_exception: 34 en: Business Exception 35 cn: 业务错误 36 not_found: 37 en: Not Found 38 cn: 请求资源不存在 39




业务断言
封装了业务错误断言工具。Assert 遵循错误优先返回原则。
你要自定义自己的业务异常。继承BusinessException
重写对应方法
1package cn.soboys.restapispringbootstarter.exception; 2 3import cn.soboys.restapispringbootstarter.HttpStatus; 4import cn.soboys.restapispringbootstarter.ResultCode; 5import lombok.Data; 6 7/** 8 * @author 公众号 程序员三时 9 * @version 1.0 10 * @date 2023/6/26 16:45 11 * @webSite https://github.com/coder-amiao 12 */ 13@Data 14public class BusinessException extends RuntimeException { 15 16 /** 17 * 错误码 18 */ 19 private String code="20000"; 20 21 /** 22 * 错误提示 23 */ 24 private String message; 25 26 27 public BusinessException(String message) { 28 this.message = message; 29 30 } 31 32 public BusinessException(String message, String code) { 33 this.message = message; 34 this.code = code; 35 36 } 37 38 public BusinessException(ResultCode resultCode) { 39 this.message = resultCode.getMessage(); 40 this.code = resultCode.getCode(); 41 42 } 43} 44
项目中日志是非常常用的,而且还是必须的。已经自动配置集成spring-boot-starter-logging 你不需要在项目中单独引入
1<!--日志集成--> 2<dependency> 3 <groupId>org.springframework.boot</groupId> 4 <artifactId>spring-boot-starter-logging</artifactId> 5</dependency>
默认日志配置
1logging: 2 path: ./logs #日志存储路径(服务器上绝对) 3 max-history: 90 # 保存多少天 4 max-file-size: 3MB # 每个文件大小 5 max-total-size-cap: 1GB #总文件大小超过多少压缩 6 level-root: INFO # 这里的INFO可以替换为其他日志等级,如DEBUG, WARN, ERROR, TRACE, FATAL, OFF等。 日志等级由低到高分别是debugger-info-warn-error 7 logDataSourceClass: cn.soboys.restapispringbootstarter.log.LogFileDefaultDataSource # 日志数据源
日志记录与追踪
RestFull API 统一返回有一个requestId 它是每个接口唯一标识。用于接口请求日志链路追踪。日志查询。 如:
1{ 2 "msg": "操作成功", 3 "code": "OK", 4 "previousPage": 1, 5 "success": true, 6 "requestId": "udYNdbbMFE45R84OPu9m", 7 "nextPage": 1, 8 "pageSize": 1, 9 "totalPageSize": 1, 10 "hasNext": "false", 11 "timestamp": "2023-07-09 03:00:27", 12 "info": [ 13 { 14 "name": "judy", 15 "hobby": "swing", 16 "age": 18 17 } 18 ] 19}
通过requestId你可以很轻松的在你的日志文件查询定位到每次错误的请求。
通过Log注解记录你想要记录请求
1@PostMapping("/page") 2@Log(value = "查询用户数据",apiType= LogApiTypeEnum.USER,CURDType= LogCURDTypeEnum.RETRIEVE) 3public Result page(@Validated EntityParam s) { 4 ResultPage<List<EntityParam>> resultPage=new ResultPage<>(); 5 List a=new ArrayList(); 6 a.add(s); 7 resultPage.setPageData(a); 8 return ResultPage.buildSuccess(resultPage); 9}
系统默认日志记录数据源为日志文件。如
12023-07-09 03:00:32 INFO http-nio-8000-exec-2 cn.soboys.restapispringbootstarter.log.LogFileDefaultDataSource { 2 "description": "查询用户数据", 3 "method": "cn.soboys.restapispringbootstarter.controller.ApiRestController.page()", 4 "logType": "INFO", 5 "time": 3, 6 "result": { 7 "success": true, 8 "code": "OK", 9 "msg": "操作成功", 10 "requestId": "udYNdbbMFE45R84OPu9m", 11 "timestamp": "2023-07-09 03:00:27", 12 "data": { 13 "previousPage": 1, 14 "nextPage": 1, 15 "pageSize": 1, 16 "totalPageSize": 1, 17 "hasNext": "false", 18 "pageData": [ 19 { 20 "name": "judy", 21 "hobby": "swing", 22 "age": 18 23 } 24 ], 25 "requestId": "qJTOejQmY-OOf7fagegB", 26 "timestamp": "2023-07-09 03:00:27" 27 } 28 }, 29 "apiType": "USER" 30} 312023-07-09 03:08:03 INFO http-nio-8000-exec-4 cn.soboys.restapispringbootstarter.log.LogFileDefaultDataSource { 32 "description": "查询用户数据", 33 "method": "cn.soboys.restapispringbootstarter.controller.ApiRestController.page()", 34 "logType": "INFO", 35 "time": 1, 36 "result": { 37 "success": true, 38 "code": "OK", 39 "msg": "操作成功", 40 "requestId": "kP3yPP-H7wI2x1ak6YFA", 41 "timestamp": "2023-07-09 03:00:27", 42 "data": { 43 "previousPage": 1, 44 "nextPage": 1, 45 "pageSize": 1, 46 "totalPageSize": 1, 47 "hasNext": "false", 48 "pageData": [ 49 { 50 "name": "judy", 51 "hobby": "swing", 52 "age": 18 53 } 54 ], 55 "requestId": "pGbbiEj8GQ1eTxQpF2Jr", 56 "timestamp": "2023-07-09 03:00:27" 57 } 58 }, 59 "apiType": "USER" 60}
你可以自定义自己的日志数据源实现LogDataSource接口 日志操作支持异步。需要在配置类。或者启动类加上@EnableAsync
注解
1package cn.soboys.restapispringbootstarter.log; 2 3import org.springframework.scheduling.annotation.Async; 4 5import java.util.Map; 6 7/** 8 * @Author: kenx 9 * @Since: 2021/6/23 13:55 10 * @Description: 11 */ 12public interface LogDataSource { 13 14 /** 15 * 获取拓展数据 16 * @return 17 * @param logEntry 18 */ 19 @Async 20 void save(LogEntry logEntry); 21} 22
或者你可以继承我默认的日志数据源实现类LogFileDefaultDataSource 重写save(LogEntry logEntry)方法。
1@Slf4j 2public class LogFileDefaultDataSource implements LogDataSource { 3 4 /** 5 * 自定义保存数据源 6 * 7 * @param 8 * @return LogEntry 9 */ 10 @Override 11 public void save(LogEntry logEntry) { 12 log.info(JSONUtil.toJsonPrettyStr(logEntry)); 13 } 14}
如果是自定义日志数据源实现需要再配置文件,配置日志数据源。如:
1logging: 2 path: ./logs #日志存储路径(服务器上绝对) 3 max-history: 90 # 保存多少天 4 max-file-size: 3MB # 每个文件大小 5 max-total-size-cap: 1GB #总文件大小超过多少压缩 6 level-root: INFO # 这里的INFO可以替换为其他日志等级,如DEBUG, WARN, ERROR, TRACE, FATAL, OFF等。 日志等级由低到高分别是debugger-info-warn-error 7 logDataSourceClass: cn.soboys.restapispringbootstarter.log.LogFileDefaultDataSource # 日志数据源
缓存和redis
项目中缓存使用是非常常见的。用的最多的是基于Redis缓存。于是我封装了对于RedisKey和Value常用操作。
::: tip 默认不引入Redis依赖,如果要使用Redis需要自己单独引入 :::
1<dependency> 2 <groupId>org.springframework.boot</groupId> 3 <artifactId>spring-boot-starter-data-redis</artifactId> 4</dependency>
项目使用
注入redisTempUtil
1@Autowired 2private RedisTempUtil redisTempUtil;
如示列
1@Autowired 2private RedisTempUtil redisTempUtil; 3 4@GetMapping("/redis") 5public Result chatDialogue( ) { 6 redisTempUtil.set("test","111"); 7 redisTempUtil.get("test"); 8 redisTempUtil.set("user","userObj",7200l); 9 redisTempUtil.getAllKey("xx"); //*表达式 10 redisTempUtil.clean(); 11 redisTempUtil.deleteObject("test"); 12 redisTempUtil.hasKey(""); 13 return Result.buildSuccess(); 14}
统一缓存管理
上面我们是直接通过工具类redisTempUtil直接自己定义key然后去存储,这种方式是不可取的如果key很多随意定义就会很混乱。我提供了统一缓存key管理接口CacheTmp 参考实现CacheKey 基于枚举形式,把所有key集中管理
1package cn.soboys.restapispringbootstarter.cache; 2 3import lombok.Getter; 4 5/** 6 * @author 公众号 程序员三时 7 * @version 1.0 8 * @date 2023/7/2 11:04 9 * @webSite https://github.com/coder-amiao 10 * 缓存枚举 11 */ 12@Getter 13public enum CacheKey implements CacheTmp { 14 15 16 // 密码的重置码 17 PWD_RESET_CODE("reset:code:", true), 18 ; 19 20 private String key; 21 22 /** 23 * Key是否是Key前缀, true时直接取key=key,如果false时key=key+suffix 24 */ 25 private boolean hasPrefix; 26 27 CacheKey(String key, boolean hasPrefix) { 28 this.key = key; 29 this.hasPrefix = hasPrefix; 30 } 31 32 33 @Override 34 public Boolean getHasPrefix() { 35 return this.hasPrefix; 36 } 37 38 @Override 39 public String getKey() { 40 return this.key; 41 } 42 43}
使用
- 存储对于key
1@GetMapping("/redis") 2public Result chatDialogue() { 3 CacheKey.PWD_RESET_CODE.valueSetAndExpire("test", 60l, TimeUnit.SECONDS, "judy"); 4 return Result.buildSuccess(); 5}
- 获取对应的key
1@GetMapping("/redis/get") 2public Result redisGet() { 3 String a = CacheKey.PWD_RESET_CODE.valueGet("judy"); 4 return Result.buildSuccess(a); 5}
spring Cache实现
封装了spring Cache进一步使用 项目中在配置类或者启动类通过注解@EnableCaching开启直接使用即可
1@Cacheable(cacheNames = "testCache", keyGenerator = "keyGeneratorStrategy") 2@GetMapping("/redis/springCache") 3public Result springCache() { 4 String a = "test cache"; 5 return Result.buildSuccess(a); 6}
工具类使用springCacheUtil 支持提供不是基于注解的使用方式
1@GetMapping("/redis/springCache") 2public Result redisSpringCache() { 3 String a = "111344"; 4 springCacheUtil.putCache("test","key","121e1"); 5 return Result.buildSuccess(a); 6}
::: tip 默认不引入Redis依赖,缓存基于内存实现(你项目引入redis依赖后会自定切换数据源为Redis缓存) :::
redis配置
多个项目或者模块使用一个key可能会造成混乱,于是提供了一个全局配置key。
1 redis: 2 key-prefix: rest
代码中添加一个 String 类型的 key:testKey,其实际在 redis 中存储的 key name 为 rest:testKey
全局 key 前缀的配置,并不影响对 key 的其他操作,例如获取对应的 value 时,依然是传入 testKey,而不是 rest:testKey
1String key = "testKey"; 2String value = redisTempUtil.get(key); 3String value1 = CacheKey.PWD_RESET_CODE.valueGet(key);
OpenApi文档生成
已经内置自动支持。swagger文档。和最新的OpenApi3 文档。项目启动后即可访问。
-
swagger-ui.html 文档。路径
/swagger-ui.html -
基于spring-doc 文档UI增强 路径
/doc.html -
接口文档属性信息
1 openapi: 2 description: 3 title: 4 version: 5 license: 6 contact: 7 name: 8 email: 9 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 的引入。
1springdoc: 2 swagger-ui: 3 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 的指定。
1# urls 2springdoc: 3 swagger-ui: 4 urls: 5 - { name: 'sample', url: '/v3/api-docs' }
或者
1#分组 2springdoc: 3 group-configs: 4 - { 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代码生成需自行单独引入
:::
1<dependency> 2 <groupId>com.baomidou</groupId> 3 <artifactId>mybatis-plus-generator</artifactId> 4 <version>3.4.1</version> 5</dependency> 6<!-- MySQL --> 7<dependency> 8 <groupId>mysql</groupId> 9 <artifactId>mysql-connector-java</artifactId> 10 <version>8.0.28</version> 11</dependency> 12<!--代码生成依赖的模板引擎--> 13<dependency> 14 <groupId>org.freemarker</groupId> 15 <artifactId>freemarker</artifactId> 16 <version>2.3.31</version> 17</dependency>
项目使用
- 代码生成配置类
1package cn.soboys.restapispringbootstarter.config; 2 3import lombok.Data; 4 5/** 6 * @author 公众号 程序员三时 7 * @version 1.0 8 * @date 2023/7/5 00:05 9 * @webSite https://github.com/coder-amiao 10 */ 11@Data 12public class GenerateCodeConfig { 13 /** 14 * 数据库驱动 15 */ 16 private String driverName; 17 /** 18 * 数据库连接用户名 19 */ 20 private String username; 21 /** 22 * 数据库连接密码 23 */ 24 private String password; 25 /** 26 * 数据库连接url 27 */ 28 private String url; 29 /** 30 * 生成代码 保存路径。默认当前项目下。 31 * 如需修改,使用绝对路径 32 */ 33 private String projectPath; 34 /** 35 * 代码生成包位置 36 */ 37 private String packages; 38}
示列如:
1public class Test { 2 public static void main(String[] args) { 3 GenerateCodeConfig config=new GenerateCodeConfig(); 4 config.setDriverName("com.mysql.cj.jdbc.Driver"); 5 config.setUsername("root"); 6 config.setPassword("root"); 7 config.setUrl("jdbc:mysql://127.0.0.1:3306/ry?useUnicode=true&useSSL=false&characterEncoding=utf8"); 8 //config.setProjectPath("superaide"); 9 config.setPackages("cn.soboys.superaide"); 10 MyBatisPlusGenerator.generate(config); 11 } 12}
常见问题
在使用过程中尽量使用最新版本。我会持续更新更多的内容。 会第一时间发布在我的公众号 程序员三时。全网同名
可以关注 公众号 程序员三时。用心分享持续输出优质内容。希望可以给你带来一点帮助
