Skip to content
On this page

快速开始

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

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

  1. 可以通过注解@NoRestFulApi实现如
java
@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不需统一返回的包

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 文档。项目启动后即可访问。

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

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

  3. 接口文档属性信息

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"