Appearance
快速开始
- 项目 pom 中引入依赖
xml
<dependency>
<groupId>cn.soboys</groupId>
<artifactId>simplest-api-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>
- 在 SpringBoot 启动类或者配置类上通过 @EnableRestFullApi注解开启 rest-api
java
@SpringBootApplication
@EnableRestFullApi
public class SuperaideApplication {
public static void main(String[] args) {
SpringApplication.run(SuperaideApplication.class, args);
}
}
到此你项目中就可以使用所有的功能了。
RestFull API
在Controller
中我们写普通的请求接口如:
java
@PostMapping("/chat")
public HashMap chatDialogue() {
HashMap m = new HashMap();
m.put("age", 26);
m.put("name", "Judy");
return m;
}
返回的就是全局统一 RestFull API
json
{
"success": true,
"code": "OK",
"msg": "操作成功",
"requestId": "IPbHLE5SZ1fqI0lgNXlB",
"timestamp": "2023-07-09 02:39:40",
"data": {
"name": "judy",
"hobby": "swing",
"age": 18
}
}
也可以基于Result
构建
java
@PostMapping("/chat")
public Result chatDialogue(@Validated EntityParam s) {
return Result.buildSuccess(s);
}
分页支持
我们在日常中分页是一个比较特殊返回。也是非常常用的。
java
@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);
}
- 构建自定义自己的分页数据
java
ResultPage<List<EntityParam>> resultPage=new ResultPage<>();
- 通过
ResultPage.buildSuccess(resultPage)
进行构建返回
返回统一响应格式
json
{
"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
}
]
}
自定义返回格式
json
{
"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
}
]
}
上述统一返回格式,可能不符合你项目中接口统一格式如:
yml
{
"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
值
yml
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
如
yml
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
对应返回内容
json
{
"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
实现如
java
@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
不需统一返回的包
yml
include-packages: cn.soboys.superaide.controller
exclude-packages: xx.xxx.xxx
接口限流
在正式项目中有些接口需要做限流的。比如 短信接口
。登录接口
等。 我封装了@Limit
注解
java
@GetMapping("/chat")
@Log("日志记录测试")
@Limit(key = "chat",name="接口限流",period=10,count=3)
public Result chatDialogue() {
return Result.buildSuccess("接口限流测试");
}
period
时间范围,单位秒count
限制访问次数 LimitType
限制类型
OpenApi 文档生成
已经内置自动支持。swagger
文档。和最新的OpenApi3
文档。项目启动后即可访问。
swagger-ui.html 文档。路径
/swagger-ui.html
基于 spring-doc 文档 UI 增强 路径
/doc.html
接口文档属性信息
yml
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 的引入。
yml
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 的指定。
yml
# urls
springdoc:
swagger-ui:
urls:
- { name: "sample", url: "/v3/api-docs" }
或者
yml
#分组
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 常见错误,和所有请求参数验证错误。
如请求错误
java
{
"success": false,
"code": "405",
"msg": "方法不被允许",
"timestamp": "2023-07-03 22:36:47",
"data": "Request method 'GET' not supported"
}
请求资源不存在等
java
{
"success": false,
"code": "404",
"msg": "请求资源不存在",
"timestamp": "2023-07-03 22:42:35",
"data": "/api"
}
如果需要拦截上面错误请在 springboot 配置文件中加入
xml
#出现错误时, 直接抛出异常
spring.mvc.throw-exception-if-no-handler-found=true
#不要为我们工程中的资源文件建立映射
spring.web.resources.add-mappings=false
参数校验错误
验证 Studen 对象参数
java
/**
* @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;
}
JAVA
@PostMapping("/chat")
public HashMap chatDialogue(@Validated Student student) {
HashMap m = new HashMap();
m.put("age", 26);
m.put("name", "Judy");
return m;
}
请求结果
JSON Body 参数
java
@PostMapping("/chat")
public HashMap chatDialogue(@RequestBody @Validated Student student) {
HashMap m = new HashMap();
m.put("age", 26);
m.put("name", "Judy");
return m;
}
错误国际化
内置封装错误默认支持英文和中文两种国际化。你不做任何配置自动支持
如果需要内置支持更多语言,覆盖即可。
自定义自己错误国际化和语言
yml
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
它是每个接口唯一标识。用于接口请求日志链路追踪。日志查询。 如:
json
{
"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
注解记录你想要记录请求
java
@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);
}
系统默认日志记录数据源为日志文件。如
java
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
注解
java
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)
方法。
java
@Slf4j
public class LogFileDefaultDataSource implements LogDataSource {
/**
* 自定义保存数据源
*
* @param
* @return LogEntry
*/
@Override
public void save(LogEntry logEntry) {
log.info(JSONUtil.toJsonPrettyStr(logEntry));
}
}
如果是自定义日志数据源实现需要再配置文件,配置日志数据源。如:
yml
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
城市回显记录
java
@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
文件。
yml
ip2region:
external: false
location: classpath:ip2region/ip2region.xdb
默认不配置会帮你自动生成一个。基于 2.7.x 最新的数据 获取最新自定义 ip 数据 Github 仓库
当然也帮你封装了。你可以通过工具类HttpUserAgent
的静态方法getIpToCityInfo(String ip)
去获取查询 ip 对应城市信息
JWT Web Token
可以轻松自定义生成自己JWT Web Token
.和基于 JWT 的userJwtToken
通过userJwtToken
你可以轻松生成基于用户登录认证的Token
java
@Autowired
private UserJwtToken userJwtToken;
@GetMapping("/login")
public Result login() {
UserEntry userEntry = new UserEntry();
userEntry.setUserId("2");
userEntry.setUsername("billy");
userEntry.setHobby("eat");
userJwtToken.rememberMe=true;
String token = userJwtToken.createdToken(userEntry.getUserId(), userEntry.getUsername(), userEntry);
return Result.buildSuccess(token);
}
解析token
获取用户信息
java
@GetMapping("/user")
public Result getUser() {
String token = "eyJhbGciOiJIUzI1NiIsInppcCI6IkRFRiJ9.eNqqViouTVKyUkrKzMmpVNJRyiwuBvKMgKyskkwoK7WiQMnK0MzC0tTUwsDEWEeptDi1SMmqGkx7pkBVgTh5ibmpSIZl5CclVQL5qYklSrW1AAAAAP__.8nWRs40LbRTIQBhJ8jVaANPcvsmX0zoLR66R-b2Uc4M";
String userName=userJwtToken.getUserName(token);
String userId= userJwtToken.getUserId(token);
UserEntry userEntry=userJwtToken.parseUserToken(token,UserEntry.class);
return Result.buildSuccess(userId);
}
自定义 Token 秘钥和签名配置
yml
jwt:
secret: 123456 # 秘钥 建议加密后秘钥如md5 不要使用明文长度大于6位
expiration: 86400 # token 过期时间(单位秒 1天后过期)
token-header: Token #header token 名称
remember-me-expiration: 604800 #记住我 token过期时间(单位秒 7天后过期)
user-sign: true # 是否自定义签名。为true需要实现加密接口。和 配置 jwtCfg注入对应bean
自定义签名认证和动态秘钥授权需要实现UserSign
接口配置UserJwtConfig
配置类注入自定义签名bean
也可以通过继承默认实现UserSignWith
类重写对应签名和秘钥方法。建议使用此种
java
package cn.soboys.superaide.config;
import cn.soboys.restapispringbootstarter.authorization.UserSign;
import io.jsonwebtoken.SignatureAlgorithm;
/**
* @author 公众号 程序员三时
* @version 1.0
* @date 2023/7/16 00:20
* @webSite https://github.com/coder-amiao
*/
public class MyUserSign implements UserSign {
@Override
public SignatureAlgorithm sign() {
return SignatureAlgorithm.HS256;
}
@Override
public String AuthKey() {
return null;
}
}
AuthKey 返回
null
时候会使用你在属性文件配置的秘钥。没有会使用默认的
java
package cn.soboys.superaide.config;
import cn.soboys.restapispringbootstarter.authorization.*;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
/**
* @author 公众号 程序员三时
* @version 1.0
* @date 2023/7/15 09:49
* @webSite https://github.com/coder-amiao
* 用户jwt token生成配置
*/
@Configuration
public class UserJwtConfig {
@Bean
public UserSign MyUserSign() {
return new MyUserSign();
}
@Bean
public UserJwtToken userJwtToken(UserSign MyUserSign) {
UserJwtToken userJwtToken = new UserJwtToken();
userJwtToken.setUserSign(MyUserSign);
return userJwtToken;
}
}
权限认证
基于JWT Web Token
也帮你封装了权限登录认证。 你只需要在属性文件配置开启即可。
yml
jwt:
authorization:
has-authorization: true
includes-url: /user # 需要认证请求 多个用逗号隔开
excludes-url: /login,/register/** # 配置无需认证的
全局帮你自动处理Token
过期异常。和错误异常你只需要在 heard 中配置你自己的Token
就行
json
{
"success": false,
"code": "401",
"msg": "未授权 ",
"requestId": "9a3ytEtOX0UuojSaA2LD",
"timestamp": "2023-07-17 17:08:05",
"data": null
}
如果需要自定义自己认证授权逻辑,实现LoginAuthorization
接口即可 并且在UserJwtConfig
配置类中注入对应LoginAuthorization
bean
也可以通过继承默认实现LoginAuthorizationSubject
类重写授权方法。建议此种。
如:
java
package cn.soboys.superaide.config;
import cn.soboys.restapispringbootstarter.Assert;
import cn.soboys.restapispringbootstarter.HttpStatus;
import cn.soboys.restapispringbootstarter.authorization.LoginAuthorization;
import cn.soboys.restapispringbootstarter.authorization.UserJwtToken;
import org.dromara.hutool.core.text.StrUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author 公众号 程序员三时
* @version 1.0
* @date 2023/7/16 11:00
* @webSite https://github.com/coder-amiao
*/
@Component
public class MyLoginAuthorization implements LoginAuthorization {
@Autowired
private UserJwtToken userJwtToken;
@Override
public Boolean authorization(HttpServletRequest request, HttpServletResponse response, Object handler) {
String token = request.getHeader("Token");
Assert.isFalse(StrUtil.isEmpty(token),HttpStatus.UNAUTHORIZED);
String userId = userJwtToken.getUserId(token); //验证token有效合法性。
//其他数据库 或者业务操作
return true;
}
}
在配置类中注入 bean
java
package cn.soboys.superaide.config;
import cn.soboys.restapispringbootstarter.authorization.*;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
/**
* @author 公众号 程序员三时
* @version 1.0
* @date 2023/7/15 09:49
* @webSite https://github.com/coder-amiao
* 用户jwt token生成配置
*/
@Configuration
public class UserJwtConfig {
@Bean
public UserSign MyUserSign() {
return new MyUserSign();
}
@Bean
@Primary
public LoginAuthorization loginAuthorizationSubject() {
return new MyLoginAuthorization();
}
@Bean
public UserJwtToken userJwtToken(UserSign MyUserSign) {
UserJwtToken userJwtToken = new UserJwtToken();
userJwtToken.setUserSign(MyUserSign);
return userJwtToken;
}
}
JSON 序列化
现在 WEB API 的通讯都是基于 WEB JSON 进行通讯数据交互。JSON 比较轻量。格式清晰。 所以项目中经常需要用到对 JSON 返回处理。
对 Bean 的序列化。对时间格式返回。对空处理。于是我封装了全局 JSON 序列化
属性
yml
rest-api:
json:
number-form: ".00" # 浮点数格式化 ,000,00 不足补零。,###.## 不会补零 (四舍五入)
date-form: yyyy-MM-dd HH:mm:ss.SSS #时间格式化 timestamp 时间戳
null-able: # 对空返回处理
has-null-able: false # 默认不处理 original 类型代表单个不做任何处理
number-type: number # 处理number 空返回 "0"
array-type: array # 处理集合空返回 []
double-type: double # 处理double 类型空返回 "0.00"