- Java锁的逻辑(结合对象头和ObjectMonitor)
- 还在用饼状图?来瞧瞧这些炫酷的百分比可视化新图形(附代码实现)⛵
- 自动注册实体类到EntityFrameworkCore上下文,并适配ABP及ABPVNext
- 基于Sklearn机器学习代码实战
我们现在使用 SpringBoot 做Web 开发已经比之前SprngMvc 那一套强大很多了。 但是 用SpringBoot Web 做API 开发还是不够简洁有一些.
每次Web API常用功能都需要重新写一遍。或者复制之前项目代码。于是我封装了这么一个 。
抽出 SpringBoot Web API 每个项目必备需要重复写的模块,和必备功能。 并且扩展了我工作中用到的 所有工具库.
基于它,你可以轻松开发SpringBoot WEB API,提高效率。不在去关心一些繁琐。重复工作,而是把重点聚焦到业务.
目前更新版本到1.5.2 功能如下 。
后续会持续更新。项目中重复使用,必备模块和工具.
rest-api-spring-boot-starter 适用于SpringBoot Web API 快速构建让开发人员快速构建统一规范的业务RestFull API 不在去关心一些繁琐。重复工作,而是把重点聚焦到业务.
<dependency>
<groupId>cn.soboys</groupId>
<artifactId>rest-api-spring-boot-starter</artifactId>
<version>1.5.0</version>
</dependency>
@SpringBootApplication
@EnableRestFullApi
public class SuperaideApplication {
public static void main(String[] args) {
SpringApplication.run(SuperaideApplication.class, args);
}
}
到此你项目中就可以使用所有的功能了.
在 Controller 中我们写普通的请求接口如
@PostMapping("/chat")
public HashMap chatDialogue() {
HashMap m = new HashMap();
m.put("age", 26);
m.put("name", "Judy");
return m;
}
返回的就是全局统一RestFull API 。
{
"success": true,
"code": "OK",
"msg": "操作成功",
"requestId": "IPbHLE5SZ1fqI0lgNXlB",
"timestamp": "2023-07-09 02:39:40",
"data": {
"name": "judy",
"hobby": "swing",
"age": 18
}
}
也可以基于 Result 构建 。
@PostMapping("/chat")
public Result chatDialogue(@Validated EntityParam s) {
return Result.buildSuccess(s);
}
我们在日常中分页是一个比较特殊返回。也是非常常用的.
@PostMapping("/page")
@Log("分页查询用户数据")
public Result page(@Validated EntityParam s) {
ResultPage<List<EntityParam>> resultPage=new ResultPage<>();
List a=new ArrayList();
a.add(s);
resultPage.setPageData(a);
return ResultPage.buildSuccess(resultPage);
}
ResultPage<List<EntityParam>> resultPage=new ResultPage<>();
ResultPage.buildSuccess(resultPage)
进行构建返回 返回统一响应格式 。
{
"previousPage": 1,
"nextPage": 1,
"pageSize": 1,
"totalPageSize": 1,
"hasNext": "false",
"success": true,
"code": "OK",
"msg": "操作成功",
"requestId": "D9AMALgkZ6gVfe6Pi0Oh",
"timestamp": "2023-07-09 02:39:40",
"data": [
{
"name": "judy",
"hobby": "swing",
"age": 18
}
]
}
{
"previousPage": 1,
"nextPage": 1,
"pageSize": 1,
"totalPageSize": 1,
"hasNext": "false",
"success": true,
"code": "OK",
"msg": "操作成功",
"requestId": "D9AMALgkZ6gVfe6Pi0Oh",
"timestamp": "2023-07-09 02:39:40",
"data": [
{
"name": "judy",
"hobby": "swing",
"age": 18
}
]
}
上述统一返回格式,可能不符合你项目中接口统一格式如:
{
"success": true,
"code": "OK",
"msg": "操作成功",
"requestId": "ztf4S-lP9yrtKPSiwldZ",
"timestamp": "2023-07-11 13:46:53",
"data": {
"previousPage": 1,
"nextPage": 1,
"pageSize": 1,
"totalPageSize": 1,
"hasNext": "false",
"pageData": [
{
"name": "judy",
"hobby": "swing",
"age": 18
}
]
}
}
page 分页数据是在 data 里面你可以定义 pageWrap 属性 true 包装返回定义 pageData 的 key 值如 records 等 。
你需要自定义 key 如 msg 你可能对应 message , success 你可能对应 status 只需要在配置文件中配置自定义 key 。
自定义返回成功值你的成功返回可能是 200 你可以配置 code-success-value 值 。
rest-api:
enabled: false
msg: msg
code: code
code-success-value: OK
success: success
previousPage: previousPage
nextPage: nextPage
pageSize: pageSize
hasNext: hasNext
totalPageSize: totalPageSize
data: info
当 enabled 开启后会读取你自定义配置的 key 如 。
rest-api:
enabled: true
msg: msg1
code: code1
code-success-value: 200
success: success1
previousPage: previousPage1
nextPage: nextPage1
pageSize: pageSize1
hasNext: hasNext1
totalPageSize: totalPageSize1
data: info
对应返回内容 。
{
"success": true,
"code": "OK",
"msg": "操作成功",
"requestId": "ztf4S-lP9yrtKPSiwldZ",
"timestamp": "2023-07-11 13:46:53",
"data": {
"previousPage": 1,
"nextPage": 1,
"pageSize": 1,
"totalPageSize": 1,
"hasNext": "false",
"pageData": [
{
"name": "judy",
"hobby": "swing",
"age": 18
}
]
}
}
有时候我们需要自定义返回。不去包装统一响应 RestFull API 格式 。
@NoRestFulApi
实现如
@GetMapping("/test")
@NoRestFulApi
public Map chatDialogue() {
Map m= new HashMap<>();
m.put("name","judy");
m.put("age",26);
return m;
}
String
类型认为是页面路径。 通过属性配置文件 include-packages 需要统一返回包。 exclude-packages 不需统一返回的包 。
include-packages: cn.soboys.superaide.controller
exclude-packages: xx.xxx.xxx
已经内置自动支持。 swagger 文档。和最新的 OpenApi3 文档。项目启动后即可访问.
swagger-ui.html 文档。路径 /swagger-ui.html 。
基于spring-doc 文档UI增强 路径 /doc.html 。
接口文档属性信息 。
openapi:
description:
title:
version:
license:
contact:
name:
email:
url:
如果嫌弃官方提供的 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 城市回显记录 。
@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;
}
@PostMapping("/chat")
public HashMap chatDialogue() {
HashMap m = new HashMap();
m.put("age", 26);
m.put("name", "Judy");
return m;
}
@PostMapping("/chat")
public Result chatDialogue() {
HashMap m = new HashMap();
m.put("age", 26);
m.put("name", "Judy");
return Result.buildSuccess(m);
}
我们在日常中分页是一个比较特殊返回。也是非常常用的.
@PostMapping("/page")
@Log("分页查询用户数据")
public Result page(@Validated EntityParam s) {
ResultPage<List<EntityParam>> resultPage=new ResultPage<>();
List a=new ArrayList();
a.add(s);
resultPage.setPageData(a);
return ResultPage.buildSuccess(resultPage);
}
ResultPage<List<EntityParam>> resultPage=new ResultPage<>();
ResultPage.buildSuccess(resultPage)
进行构建返回 返回统一响应格式 。
{
"previousPage": 1,
"nextPage": 1,
"pageSize": 1,
"totalPageSize": 1,
"hasNext": "false",
"success": true,
"code": "OK",
"msg": "操作成功",
"requestId": "D9AMALgkZ6gVfe6Pi0Oh",
"timestamp": "2023-07-09 02:39:40",
"data": [
{
"name": "judy",
"hobby": "swing",
"age": 18
}
]
}
{
"previousPage": 1,
"nextPage": 1,
"pageSize": 1,
"totalPageSize": 1,
"hasNext": "false",
"success": true,
"code": "OK",
"msg": "操作成功",
"requestId": "D9AMALgkZ6gVfe6Pi0Oh",
"timestamp": "2023-07-09 02:39:40",
"data": [
{
"name": "judy",
"hobby": "swing",
"age": 18
}
]
}
上述统一返回格式,可能不符合你项目中接口统一格式如:
{
"success": true,
"code": "OK",
"msg": "操作成功",
"requestId": "ztf4S-lP9yrtKPSiwldZ",
"timestamp": "2023-07-11 13:46:53",
"data": {
"previousPage": 1,
"nextPage": 1,
"pageSize": 1,
"totalPageSize": 1,
"hasNext": "false",
"pageData": [
{
"name": "judy",
"hobby": "swing",
"age": 18
}
]
}
}
page 分页数据是在 data 里面你可以定义 pageWrap 属性 true 包装返回定义 pageData 的 key 值如 records 等 。
你需要自定义 key 如 msg 你可能对应 message , success 你可能对应 status 只需要在配置文件中配置自定义 key 。
自定义返回成功值你的成功返回可能是 200 你可以配置 code-success-value 值 。
rest-api:
enabled: false
msg: msg
code: code
code-success-value: OK
success: success
previousPage: previousPage
nextPage: nextPage
pageSize: pageSize
hasNext: hasNext
totalPageSize: totalPageSize
data: info
当 enabled 开启后会读取你自定义配置的 key 如 。
rest-api:
enabled: true
msg: msg1
code: code1
code-success-value: 200
success: success1
previousPage: previousPage1
nextPage: nextPage1
pageSize: pageSize1
hasNext: hasNext1
totalPageSize: totalPageSize1
data: info
对应返回内容 。
{
"success": true,
"code": "OK",
"msg": "操作成功",
"requestId": "ztf4S-lP9yrtKPSiwldZ",
"timestamp": "2023-07-11 13:46:53",
"data": {
"previousPage": 1,
"nextPage": 1,
"pageSize": 1,
"totalPageSize": 1,
"hasNext": "false",
"pageData": [
{
"name": "judy",
"hobby": "swing",
"age": 18
}
]
}
}
有时候我们需要自定义返回。不去包装统一响应 RestFull API 格式 。
@NoRestFulApi
实现如
@GetMapping("/test")
@NoRestFulApi
public Map chatDialogue() {
Map m= new HashMap<>();
m.put("name","judy");
m.put("age",26);
return m;
}
String
类型认为是页面路径。 通过属性配置文件 include-packages 需要统一返回包。 exclude-packages 不需统一返回的包 。
include-packages: cn.soboys.superaide.controller
exclude-packages: xx.xxx.xxx
内置常见的错误。可以看HttpStatus。默认错误支持中文和英文两种国际化。配置如下 。
i18n:
# 若前端无header传参则返回中文信息
i18n-header: Lang
default-lang: cn
message:
# admin
internal_server_error:
en: Internal Server Error
cn: 系统错误
bad_gateway:
en: Bad Gateway
cn: 错误的请求
unauthorized:
en: Unauthorized
cn: 未授权
forbidden:
en: Forbidden
cn: 资源禁止访问
method_not_allowed:
en: Method Not Allowed
cn: 方法不被允许
request_timeout:
en: Request Timeout
cn: 请求超时
invalid_argument:
en: Invalid Argument {}
cn: 参数错误 {}
argument_analyze:
en: Argument Analyze {}
cn: 参数解析异常 {}
business_exception:
en: Business Exception
cn: 业务错误
not_found:
en: Not Found
cn: 请求资源不存在
可以自行覆盖扩充 。
默认拦所有未知错误异常和 validation 参数校验失败异常,以及Http请求异常。 还有全局自定义 BusinessException 业务异常 自动集成 spring-boot-starter-validation 你项目中不需要再单独引入 。
<!--参数校验-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
也内置扩展了许多自定义参数校验参考 。
有时候我们项目中需要调用第三方接口服务。基于 RestTemplate 进一步封装了直接的POST,GET,请求.
在需要使用地方注入RestFulTemp 。
@Resource
private RestFulTemp restFulTemp;
@GetMapping("/doGet")
public Result doGet() {
ResponseEntity<String> response = restFulTemp.doGet("http://127.0.0.1:9000/redis/get");
return Result.buildSuccess();
}
/**
* 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());
}
@GetMapping("/doDelete")
public Result doDelete() {
restFulTemp.doDelete("http://127.0.0.1:8000/chat");
return Result.buildSuccess();
}
@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 Key和Value常用操作.
::: tip 默认不引入Redis依赖,如果要使用Redis需要自己单独引入 ::
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
注入 redisTempUtil 。
@Autowired
private RedisTempUtil redisTempUtil;
如示列 。
@Autowired
private RedisTempUtil redisTempUtil;
@GetMapping("/redis")
public Result chatDialogue( ) {
redisTempUtil.set("test","111");
redisTempUtil.get("test");
redisTempUtil.set("user","userObj",7200l);
redisTempUtil.getAllKey("xx"); //*表达式
redisTempUtil.clean();
redisTempUtil.deleteObject("test");
redisTempUtil.hasKey("");
return Result.buildSuccess();
}
上面我们是直接通过工具类 redisTempUtil 直接自己定义 key 然后去存储,这种方式是不可取的如果 key很多随意定义就会很混乱 。我提供了统一缓存key管理接口 CacheTmp 参考实现 CacheKey 基于枚举形式,把所有key集中管理 。
package cn.soboys.restapispringbootstarter.cache;
import lombok.Getter;
/**
* @author 公众号 程序员三时
* @version 1.0
* @date 2023/7/2 11:04
* @webSite https://github.com/coder-amiao
* 缓存枚举
*/
@Getter
public enum CacheKey implements CacheTmp {
// 密码的重置码
PWD_RESET_CODE("reset:code:", true),
;
private String key;
/**
* Key是否是Key前缀, true时直接取key=key,如果false时key=key+suffix
*/
private boolean hasPrefix;
CacheKey(String key, boolean hasPrefix) {
this.key = key;
this.hasPrefix = hasPrefix;
}
@Override
public Boolean getHasPrefix() {
return this.hasPrefix;
}
@Override
public String getKey() {
return this.key;
}
}
使用 。
@GetMapping("/redis")
public Result chatDialogue() {
CacheKey.PWD_RESET_CODE.valueSetAndExpire("test", 60l, TimeUnit.SECONDS, "judy");
return Result.buildSuccess();
}
@GetMapping("/redis/get")
public Result redisGet() {
String a = CacheKey.PWD_RESET_CODE.valueGet("judy");
return Result.buildSuccess(a);
}
封装了 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缓存) ::
多个项目或者模块使用一个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);
已经内置自动支持。 swagger 文档。和最新的 OpenApi3 文档。项目启动后即可访问.
swagger-ui.html 文档。路径 /swagger-ui.html 。
基于spring-doc 文档UI增强 路径 /doc.html 。
接口文档属性信息 。
openapi:
description:
title:
version:
license:
contact:
name:
email:
url:
如果嫌弃官方提供的 swagger-ui 不美观,或者使用不顺手,可以选择关闭 ui,还可以剔除掉 ui 相关的 webjar 的引入.
springdoc:
swagger-ui:
enabled: false
OpenAPI 文档信息,默认可在此 url 中获取: http://server :port/context-path/v3/api-docs。 可以利用其他支持 OpenAPI 协议的工具,通过此地址,进行 API 展示,如 Apifox。 ( Postman 的 api 测试也可以利用此地址进行导入生成 ) 。
Knife4j (原 swagger-bootstrap-ui) 3.x 版本提供了对于 OpenAPI 协议的部分支持.
::: tip Knife4j 很多地方没有按照协议规范实现,所以使用起来会有很多问题,另外项目也很久没有维护了,不推荐使用。 ::
由于 knife4j 对于规范支持的不全面,无法直接使用单文档源数据,所以必须进行分组或者 urls 的指定.
# urls
springdoc:
swagger-ui:
urls:
- { name: 'sample', url: '/v3/api-docs' }
或者 。
#分组
springdoc:
group-configs:
- { group: 'sample', packages-to-scan: 'com.example' }
Knife4j 的 UI 访问地址有所不同,页面映射在 doc.html 路径下,启动项目后,访问 http://server:port/context-path/doc.html 。
即可进入 Knife4j 的 Swagger UI 页面.
项目中我们使用 mybatis 或者 mybatisPlus 一些简单的单表业务代码,增删改成。我们可以一键生成。不需要重复写。 我封装了 mybatisPlus 代码生成工具 。
::: tip 默认不引入 mybatisPlus 代码生成依赖,如果要使用 mybatisPlus 代码生成需自行单独引入 ::
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.4.1</version>
</dependency>
<!-- MySQL -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
<!--代码生成依赖的模板引擎-->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.31</version>
</dependency>
package cn.soboys.restapispringbootstarter.config;
import lombok.Data;
/**
* @author 公众号 程序员三时
* @version 1.0
* @date 2023/7/5 00:05
* @webSite https://github.com/coder-amiao
*/
@Data
public class GenerateCodeConfig {
/**
* 数据库驱动
*/
private String driverName;
/**
* 数据库连接用户名
*/
private String username;
/**
* 数据库连接密码
*/
private String password;
/**
* 数据库连接url
*/
private String url;
/**
* 生成代码 保存路径。默认当前项目下。
* 如需修改,使用绝对路径
*/
private String projectPath;
/**
* 代码生成包位置
*/
private String packages;
}
示列如:
public class Test {
public static void main(String[] args) {
GenerateCodeConfig config=new GenerateCodeConfig();
config.setDriverName("com.mysql.cj.jdbc.Driver");
config.setUsername("root");
config.setPassword("root");
config.setUrl("jdbc:mysql://127.0.0.1:3306/ry?useUnicode=true&useSSL=false&characterEncoding=utf8");
//config.setProjectPath("superaide");
config.setPackages("cn.soboys.superaide");
MyBatisPlusGenerator.generate(config);
}
}
在使用过程中尽量使用最新版本。我会持续更新更多的内容。 会第一时间发布在我的公众号 程序员三时。全网同名 。
可以关注 公众号 程序员三时 。用心分享持续输出优质内容。希望可以给你带来一点帮助 。
最后此篇关于我开源了团队内部基于SpringBootWeb快速开发的API脚手架stater的文章就讲到这里了,如果你想了解更多关于我开源了团队内部基于SpringBootWeb快速开发的API脚手架stater的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
$ flutter ios build --release 它显示多个可用的有效开发证书(您的选择将被保存): 但是它没有显示分发证书选择,不过我使用了--release标志,,, 我检查过我已经在本
我有一个 Jenkins Bitbucket 团队/项目工作。 在我的存储库中的 Jenkinsfile 中,我使用“git describe”来获取当前标签。 在我更新到最新的 Jenkins 版本
您会在新的 .NET 开发团队中实现哪些最佳实践和方法? 干杯 最佳答案 仅使用 Visual Studio 如果您需要数据库,请使用服务器(尽早减少 SQL 问题) 使用版本控制 关于c# - .N
我有 3 个表用户、团队、组。每个团队可以有多个组。每个组中都有一个或多个用户。用户可以属于多个组。您认为对所有用户使用组会更好吗(这样每个团队至少有一个名为所有用户的组)还是引入另一个表 team_
问题: 让我们考虑以下场景: 让T={t_1, t_2, ..., t_h}成为一组不同的游戏。每场比赛都是一对一的(它们是单人游戏)。 设n为players的个数,每个游戏都有一个已知的性能度量这个
我们有 5 个人在从事同一个项目,并且在 Bitbucket 中有多个 GIT 存储库。每个用户都有自己的 Bitbucket 帐户。我正在寻找拥有某种团队或组织功能的最佳实践方法,以便我们都可以在相
我们在实现 Team Foundation Build Server 时遇到了性能问题,而且我对如何加快速度没有任何想法。我们已经添加了一些 PropertyGroup 元素来提高几个步骤(SkipC
关闭。这个问题不符合Stack Overflow guidelines .它目前不接受答案。 想改进这个问题?将问题更新为 on-topic对于堆栈溢出。 8年前关闭。 Improve this qu
我为我是所有者的团队 channel 设置了 Azure DevOps 连接器。它一直工作正常,但现在我需要调整一些设置,因为我不希望它宣布取消部署。因此,我打开连接器、已配置、AzureDevops
我们有一个团队机器人,可以在 MS 团队中发布消息。新对话的第一个事件总是一张自适应卡片,我们偶尔会用一张新卡片更新它。这一切正常,直到我用这个机器人组建了一个新团队。 我们正在尝试使用 Update
What do I need to install to activate this button Share Project in Eclipse?I need to transfer the pr
我创建了一个解决方案,其中包含我的所有项目,包括 Dotfuscator 项目和设置项目。 某些 Dotfuscator 项目仅对某些程序集进行了模糊处理,而不会影响它们的任何引用。 从 Visual
如何观察Variable来自一组团队 ( Variable ),以便每次 Game出现( Variable 由于游戏数组被修改而改变),数组 Team是否应该进行相应更新,例如比赛两支球队的得分、胜利
我的问题是我所有的应用程序 ID 都相同。我认为它们应该是不同的以识别它们。即使我尝试创建新的配置文件,它也不给我更改 ID 或生成新配置文件的选项。我正在尝试为我的一个应用程序设置 ICloud,我
熬夜工作到上午 1030 点并完成作业后,我决定在格式化并提交作业之前小睡一会儿。不用说,午睡变成了 5 小时的 sleep 时间。 醒来后,我将代码格式化为 java 的 eclipse googl
我是 3 个注册 iOS 开发团队的成员: 我的个人 iOS 开发者帐户。 我的企业 iOS 开发者客户团队。 我客户的 iOS 开发者客户团队。 我现在想使用 iOS Provisioning Po
目标:MS Teams 在双显示器上的可访问性行为,显示器设置为不同的比例,例如 100% 和 125%,分辨率为 1920*1080。我使用的工具是 Accessibility Insight。 问
最近注意到在 MS CRM 2011 中无法从工作流步骤创建/更新业务部门或团队。 为什么要制作它,是否有任何解决方法? 最佳答案 遗憾的是这是设计使然。您需要创建自定义工作流事件来创建/更新业务部门
你们这里有些奇怪。 我们有一个相当复杂的解决方案(在asp.net,silverlight,WFC,Ria Services等中分布了111个项目)解决方案,该解决方案可以在我的开发盒中正确构建(20
我们的技术总监希望测试运行后,能够在 Microsoft 团队 channel 中更新当前 testng 报告中的测试结果。 我们目前正在使用 testng 报告来分析测试运行后的报告。 我们可以将
我是一名优秀的程序员,十分优秀!