咱们现在运用SpringBoot 做Web 开发现已比之前SprngMvc 那一套强大许多了。 但是 用SpringBoot Web 做API 开发还是不行简练有一些。

每次Web API常用功用都需求从头写一遍。或许仿制之前项目代码。所以我封装了这么一个

我开源了团队内部基于SpringBoot Web快速开发的API脚手架stater

抽出SpringBoot Web API 每个项目必备需求重复写的模块,和必备功用。 并且扩展了我作业中用到的 一切东西库。

根据它,你能够轻松开发SpringBoot WEB API,提高功率。不在去关心一些繁琐。重复作业,而是把要点聚集到事务。

现在更新版别到1.5.1 功用如下

  1. 支撑一键装备自界说RestFull API 一致格局回来
  2. 支撑RestFull API 过错国际化
  3. 支撑大局反常处理,大局参数验证处理
  4. 事务过错断言东西封装,遵从过错优先回来准则
  5. 封装Redis key,value 操作东西类。一致key办理 spring cache缓存完成
  6. RestTemplate 封装 POST,GET 恳求东西
  7. 日志集成。自界说日志途径,依照日志等级分类,支撑紧缩和文件巨细切割。按时间显示
  8. 东西库集成 集成了lombok,hutool,commons-lang3,guava。不需求自己单个引进
  9. 集成mybatisPlus一键代码生成
  10. 日志记载,服务监控,支撑日志链路查询。自界说数据源
  11. OpenApi3文档一键装备。支撑多种文档和主动装备

后续会持续更新。项目中重复运用,必备模块和东西。

  • GitHub 地址
  • gitee 地址

rest-api-spring-boot-starter 适用于SpringBoot Web API 快速构建让开发人员快速构建一致标准的事务RestFull API 不在去关心一些繁琐。重复作业,而是把要点聚集到事务。

快速开端

  1. 项目pom中引进依靠
<dependency>
    <groupId>cn.soboys</groupId>
    <artifactId>rest-api-spring-boot-starter</artifactId>
    <version>1.5.0</version>
</dependency>
  1. 在SpringBoot发动类或许装备类上经过 @EnableRestFullApi注解敞开rest-api

@SpringBootApplication
@EnableRestFullApi
public class SuperaideApplication {
    public static void main(String[] args) {
        SpringApplication.run(SuperaideApplication.class, args);
    }
}

到此你项目中就能够运用一切的功用了。

RestFull API

Controller中咱们写一般的恳求接口如:

@PostMapping("/chat")
public HashMap chatDialogue() {
    HashMap m = new HashMap();
    m.put("age", 26);
    m.put("name", "Judy");
    return m;
}

回来的便是大局一致RestFull API

{
    "success": true,
    "code": "OK",
    "msg": "操作成功",
    "requestId": "IPbHLE5SZ1fqI0lgNXlB",
    "timestamp": "2023-07-09 02:39:40",
    "data": {
        "name": "judy",
        "hobby": "swing",
        "age": 18
    }
}

也能够根据Result构建

@PostMapping("/chat")
public Result chatDialogue(@Validated EntityParam s) {
    return Result.buildSuccess(s);
}

分页支撑

咱们在日常中分页是一个比较特殊回来。也是十分常用的。

@PostMapping("/page")
@Log("分页查询用户数据")
public Result page(@Validated EntityParam s) {
    ResultPage<List<EntityParam>> resultPage=new ResultPage<>();
    List a=new ArrayList();
    a.add(s);
    resultPage.setPageData(a);
    return ResultPage.buildSuccess(resultPage);
}
  1. 构建自界说自己的分页数据
ResultPage<List<EntityParam>> resultPage=new ResultPage<>();
  1. 经过ResultPage.buildSuccess(resultPage)进行构建回来

回来一致呼应格局

{
    "previousPage": 1,
    "nextPage": 1,
    "pageSize": 1,
    "totalPageSize": 1,
    "hasNext": "false",
    "success": true,
    "code": "OK",
    "msg": "操作成功",
    "requestId": "D9AMALgkZ6gVfe6Pi0Oh",
    "timestamp": "2023-07-09 02:39:40",
    "data": [
        {
            "name": "judy",
            "hobby": "swing",
            "age": 18
        }
    ]
}

自界说回来格局

{
    "previousPage": 1,
    "nextPage": 1,
    "pageSize": 1,
    "totalPageSize": 1,
    "hasNext": "false",
    "success": true,
    "code": "OK",
    "msg": "操作成功",
    "requestId": "D9AMALgkZ6gVfe6Pi0Oh",
    "timestamp": "2023-07-09 02:39:40",
    "data": [
        {
            "name": "judy",
            "hobby": "swing",
            "age": 18
        }
    ]
}

上述一致回来格局,或许不符合你项目中接口一致格局如:

{
    "success": true,
    "code": "OK",
    "msg": "操作成功",
    "requestId": "ztf4S-lP9yrtKPSiwldZ",
    "timestamp": "2023-07-11 13:46:53",
    "data": {
        "previousPage": 1,
        "nextPage": 1,
        "pageSize": 1,
        "totalPageSize": 1,
        "hasNext": "false",
        "pageData": [
            {
                "name": "judy",
                "hobby": "swing",
                "age": 18
            }
        ]
    }
}

page分页数据是在data里面你能够界说pageWrap特点true包装回来界说pageDatakey值如records

你需求自界说keymsg你或许对应message,success你或许对应status只需求在装备文件中装备自界说key

自界说回来成功值你的成功回来或许是200你能够装备code-success-value

rest-api:
  enabled: false
  msg: msg
  code: code
  code-success-value: OK
  success: success
  previousPage: previousPage
  nextPage: nextPage
  pageSize: pageSize
  hasNext: hasNext
  totalPageSize: totalPageSize
  data: info

enabled敞开后会读取你自界说装备的key

rest-api:
  enabled: true
  msg: msg1
  code: code1
  code-success-value: 200
  success: success1
  previousPage: previousPage1
  nextPage: nextPage1
  pageSize: pageSize1
  hasNext: hasNext1
  totalPageSize: totalPageSize1
  data: info

对应回来内容

{
    "success": true,
    "code": "OK",
    "msg": "操作成功",
    "requestId": "ztf4S-lP9yrtKPSiwldZ",
    "timestamp": "2023-07-11 13:46:53",
    "data": {
        "previousPage": 1,
        "nextPage": 1,
        "pageSize": 1,
        "totalPageSize": 1,
        "hasNext": "false",
        "pageData": [
            {
                "name": "judy",
                "hobby": "swing",
                "age": 18
            }
        ]
    }
}

自界说回来

有时候咱们需求自界说回来。不去包装一致呼应RestFull API格局

  1. 能够经过注解@NoRestFulApi完成如
@GetMapping("/test")
@NoRestFulApi
public Map chatDialogue() {
    Map  m= new HashMap<>();
    m.put("name","judy");
    m.put("age",26);
    return m;
}
  1. 经过类扫描去完成 默许会过滤String类型以为是页面途径。

经过特点装备文件include-packages需求一致回来包。exclude-packages不需一致回来的包

include-packages: cn.soboys.superaide.controller
exclude-packages: xx.xxx.xxx

OpenApi文档生成

现已内置主动支撑。swagger文档。和最新的OpenApi3 文档。项目发动后即可拜访。

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

  2. 根据spring-doc 文档UI增强 途径/doc.html

  3. 接口文档特点信息

  openapi:
    description:
    title:
    version:
    license: 
    contact:
      name:
      email:
      url: 
  • 发动项目后,拜访 http://server:port/context-path/swagger-ui.html 即可进入 Swagger UI 页面,OpenAPI 描述将在以下 json 格局的 url 中 供给:http://server:port/context-path/v3/api-docs
  • server:域名 或 IP
  • port:服务器端口
  • context-path:应用程序的上下文途径,springboot 默以为空
  • 文档也能够 yaml 格局供给,坐落以下途径:/v3/api-docs.yaml

假如厌弃官方供给的 swagger-ui 不美观,或许运用不顺手,能够挑选封闭 ui,还能够剔除去 ui 相关的 webjar 的引进。

springdoc:
  swagger-ui:
    enabled: false

OpenAPI 文档信息,默许可在此 url 中获取: http://server:port/context-path/v3/api-docs。 能够运用其他支撑 OpenAPI 协议的东西,经过此地址,进行 API 展现,如 Apifox。 ( Postman 的 api 测验也能够运用此地址进行导入生成 )

Knife4j (原 swagger-bootstrap-ui) 3.x 版别供给了关于 OpenAPI 协议的部分支撑。

::: tip Knife4j 许多当地没有依照协议标准完成,所以运用起来会有许多问题,另外项目也好久没有保护了,不引荐运用。 :::

因为 knife4j 关于标准支撑的不全面,无法直接运用单文档源数据,所以有必要进行分组或许 urls 的指定。

# urls
springdoc:
  swagger-ui:
    urls:
      - { name: 'sample', url: '/v3/api-docs' }

或许

#分组
springdoc:
  group-configs:
    - { group: 'sample', packages-to-scan: 'com.example' }

Knife4j 的 UI 拜访地址有所不同,页面映射在 doc.html 途径下,发动项目后,拜访 http://server:port/context-path/doc.html

即可进入 Knife4j 的 Swagger UI 页面。

大局过错阻拦,参数校验

帮你封装好了一切http常见过错,和一切恳求参数验证过错。

如恳求过错

{
    "success": false,
    "code": "405",
    "msg": "办法不被答应",
    "timestamp": "2023-07-03 22:36:47",
    "data": "Request method 'GET' not supported"
}

恳求资源不存在等

{
    "success": false,
    "code": "404",
    "msg": "恳求资源不存在",
    "timestamp": "2023-07-03 22:42:35",
    "data": "/api"
}

假如需求阻拦上面过错请在springboot 装备文件中参加

#呈现过错时, 直接抛出反常
spring.mvc.throw-exception-if-no-handler-found=true
#不要为咱们工程中的资源文件建立映射
spring.web.resources.add-mappings=false

参数校验过错

验证Studen目标参数

/**
 * @author 大众号 程序员三时
 * @version 1.0
 * @date 2023/6/26 22:10
 * @webSite https://github.com/coder-amiao
 */
@Data
public class Student {
    @NotBlank
    private String nam;
    @NotBlank
    private String hobby;
}
    @PostMapping("/chat")
    public HashMap chatDialogue(@Validated  Student student) {
        HashMap m = new HashMap();
        m.put("age", 26);
        m.put("name", "Judy");
        return m;
    }

恳求结果

我开源了团队内部基于SpringBoot Web快速开发的API脚手架stater

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;
    }

我开源了团队内部基于SpringBoot Web快速开发的API脚手架stater

我开源了团队内部基于SpringBoot Web快速开发的API脚手架stater

过错国际化

内置封装过错默许支撑英文和中文两种国际化。你不做任何装备主动支撑

假如需求内置支撑更多言语,覆盖即可。

自界说自己过错国际化和言语

  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 进行装备

我开源了团队内部基于SpringBoot Web快速开发的API脚手架stater

当我传入 指定言语 就会依照你装备的国际化自界说回来过错提示

我开源了团队内部基于SpringBoot Web快速开发的API脚手架stater

日志链路追寻

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 # 日志数据源

特点装备

装备言语国际化,日志等,

::: tip 默许不用装备任何参数。会运用默许的,装备了会运用你项目中的装备。 :::

默许装备

rest-api:
  enabled: false
  msg: msg
  code: code
  code-success-value: OK
  success: success
  previousPage: previousPage
  nextPage: nextPage
  pageSize: pageSize
  hasNext: hasNext
  totalPageSize: totalPageSize
  data: info
  include-packages: cn.soboys.superaide.controller
  exclude-packages: xx.xxx.xxx
  redis:
    key-prefix: rest
  openapi:
    description:
    title:
    version:
    license: 
    contact:
      name:
      email:
      url:
  logging:
    path: ./logs   #日志存储途径(服务器上肯定)
    max-history: 90 # 保存多少天
    max-file-size: 3MB  # 每个文件巨细
    max-total-size-cap: 1GB  #总文件巨细超越多少紧缩
    level-root: INFO    # 这儿的INFO能够替换为其他日志等级,如DEBUG, WARN, ERROR, TRACE, FATAL, OFF等。 日志等级由低到高分别是debugger-info-warn-error
    logDataSourceClass: cn.soboys.restapispringbootstarter.log.LogFileDefaultDataSource # 日志数据源
  i18n:
    # 若前端无header传参则回来中文信息
    i18n-header: Lang
    default-lang: cn
    message:
      # admin
      internal_server_error:
        en: Internal Server Error
        cn: 体系过错
      bad_gateway:
        en: Bad Gateway
        cn: 过错的恳求
      unauthorized:
        en: Unauthorized
        cn: 未授权
      forbidden:
        en: Forbidden
        cn: 资源制止拜访
      method_not_allowed:
        en: Method Not Allowed
        cn: 办法不被答应
      request_timeout:
        en: Request Timeout
        cn: 恳求超时
      invalid_argument:
        en: Invalid Argument {}
        cn: 参数过错 {}
      argument_analyze:
        en: Argument Analyze {}
        cn: 参数解析反常 {}
      business_exception:
        en: Business Exception
        cn: 事务过错
      not_found:
        en: Not Found
        cn: 恳求资源不存在

代码生成装备

支撑MybatisPlus代码一键生成 默许不引进MybatisPlus生成依靠需求手动引进

package cn.soboys.restapispringbootstarter.config;
import lombok.Data;
/**
 * @author 大众号 程序员三时
 * @version 1.0
 * @date 2023/7/5 00:05
 * @webSite https://github.com/coder-amiao
 */
@Data
public class GenerateCodeConfig {
    /**
     * 数据库驱动
     */
    private String driverName;
    /**
     * 数据库衔接用户名
     */
    private String username;
    /**
     * 数据库衔接暗码
     */
    private String password;
    /**
     * 数据库衔接url
     */
    private String url;
    /**
     * 生成代码 保存途径。默许当前项目下。
     * 如需修正,运用觉得途径
     */
    private String projectPath;
    /**
     * 代码生成包方位
     */
    private String packages;
}

RestFull API

Controller中直接运用

@PostMapping("/chat")
public HashMap chatDialogue() {
    HashMap m = new HashMap();
    m.put("age", 26);
    m.put("name", "Judy");
    return m;
}

Result构建回来

@PostMapping("/chat")
public Result chatDialogue() {
    HashMap m = new HashMap();
    m.put("age", 26);
    m.put("name", "Judy");
    return Result.buildSuccess(m);
}

分页支撑

咱们在日常中分页是一个比较特殊回来。也是十分常用的。

@PostMapping("/page")
@Log("分页查询用户数据")
public Result page(@Validated EntityParam s) {
    ResultPage<List<EntityParam>> resultPage=new ResultPage<>();
    List a=new ArrayList();
    a.add(s);
    resultPage.setPageData(a);
    return ResultPage.buildSuccess(resultPage);
}
  1. 构建自界说自己的分页数据
ResultPage<List<EntityParam>> resultPage=new ResultPage<>();
  1. 经过ResultPage.buildSuccess(resultPage)进行构建回来

回来一致呼应格局

{
    "previousPage": 1,
    "nextPage": 1,
    "pageSize": 1,
    "totalPageSize": 1,
    "hasNext": "false",
    "success": true,
    "code": "OK",
    "msg": "操作成功",
    "requestId": "D9AMALgkZ6gVfe6Pi0Oh",
    "timestamp": "2023-07-09 02:39:40",
    "data": [
        {
            "name": "judy",
            "hobby": "swing",
            "age": 18
        }
    ]
}

自界说回来格局

{
    "previousPage": 1,
    "nextPage": 1,
    "pageSize": 1,
    "totalPageSize": 1,
    "hasNext": "false",
    "success": true,
    "code": "OK",
    "msg": "操作成功",
    "requestId": "D9AMALgkZ6gVfe6Pi0Oh",
    "timestamp": "2023-07-09 02:39:40",
    "data": [
        {
            "name": "judy",
            "hobby": "swing",
            "age": 18
        }
    ]
}

上述一致回来格局,或许不符合你项目中接口一致格局如:

{
    "success": true,
    "code": "OK",
    "msg": "操作成功",
    "requestId": "ztf4S-lP9yrtKPSiwldZ",
    "timestamp": "2023-07-11 13:46:53",
    "data": {
        "previousPage": 1,
        "nextPage": 1,
        "pageSize": 1,
        "totalPageSize": 1,
        "hasNext": "false",
        "pageData": [
            {
                "name": "judy",
                "hobby": "swing",
                "age": 18
            }
        ]
    }
}

page分页数据是在data里面你能够界说pageWrap特点true包装回来界说pageDatakey值如records

你需求自界说keymsg你或许对应message,success你或许对应status只需求在装备文件中装备自界说key

自界说回来成功值你的成功回来或许是200你能够装备code-success-value

rest-api:
  enabled: false
  msg: msg
  code: code
  code-success-value: OK
  success: success
  previousPage: previousPage
  nextPage: nextPage
  pageSize: pageSize
  hasNext: hasNext
  totalPageSize: totalPageSize
  data: info

enabled敞开后会读取你自界说装备的key

rest-api:
  enabled: true
  msg: msg1
  code: code1
  code-success-value: 200
  success: success1
  previousPage: previousPage1
  nextPage: nextPage1
  pageSize: pageSize1
  hasNext: hasNext1
  totalPageSize: totalPageSize1
  data: info

对应回来内容

{
    "success": true,
    "code": "OK",
    "msg": "操作成功",
    "requestId": "ztf4S-lP9yrtKPSiwldZ",
    "timestamp": "2023-07-11 13:46:53",
    "data": {
        "previousPage": 1,
        "nextPage": 1,
        "pageSize": 1,
        "totalPageSize": 1,
        "hasNext": "false",
        "pageData": [
            {
                "name": "judy",
                "hobby": "swing",
                "age": 18
            }
        ]
    }
}

自界说回来

有时候咱们需求自界说回来。不去包装一致呼应RestFull API格局

  1. 能够经过注解@NoRestFulApi完成如
@GetMapping("/test")
@NoRestFulApi
public Map chatDialogue() {
    Map  m= new HashMap<>();
    m.put("name","judy");
    m.put("age",26);
    return m;
}
  1. 经过类扫描去完成 默许会过滤String类型以为是页面途径。

经过特点装备文件include-packages需求一致回来包。exclude-packages不需一致回来的包

include-packages: cn.soboys.superaide.controller
exclude-packages: xx.xxx.xxx

过错国际化支撑

内置常见的过错。能够看HttpStatus。默许过错支撑中文和英文两种国际化。装备如下

  i18n:
    # 若前端无header传参则回来中文信息
    i18n-header: Lang
    default-lang: cn
    message:
      # admin
      internal_server_error:
        en: Internal Server Error
        cn: 体系过错
      bad_gateway:
        en: Bad Gateway
        cn: 过错的恳求
      unauthorized:
        en: Unauthorized
        cn: 未授权
      forbidden:
        en: Forbidden
        cn: 资源制止拜访
      method_not_allowed:
        en: Method Not Allowed
        cn: 办法不被答应
      request_timeout:
        en: Request Timeout
        cn: 恳求超时
      invalid_argument:
        en: Invalid Argument {}
        cn: 参数过错 {}
      argument_analyze:
        en: Argument Analyze {}
        cn: 参数解析反常 {}
      business_exception:
        en: Business Exception
        cn: 事务过错
      not_found:
        en: Not Found
        cn: 恳求资源不存在

能够自行覆盖扩充

大局过错阻拦和呼应

默许拦一切不知道过错反常和validation参数校验失利反常,以及Http恳求反常。 还有大局自界说BusinessException 事务反常 主动集成spring-boot-starter-validation 你项目中不需求再独自引进

<!--参数校验-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

也内置扩展了许多自界说参数校验参阅

我开源了团队内部基于SpringBoot Web快速开发的API脚手架stater

第三方恳求

有时候咱们项目中需求调用第三方接口服务。根据RestTemplate 进一步封装了直接的POST,GET,恳求。

在需求运用当地注入RestFulTemp

@Resource
private RestFulTemp restFulTemp;

GET恳求

@GetMapping("/doGet")
public Result doGet() {
    ResponseEntity<String> response = restFulTemp.doGet("http://127.0.0.1:9000/redis/get");
    return Result.buildSuccess();
}

POST 恳求

/**
 * POST 恳求参 数为body json体格局
 * @return
 */
@PostMapping("/doPost")
public Result doPost() {
    Student s=new Student();
    s.setHobby("swing");
    s.setNam("judy");
    //主动把目标转换为JSON
    ResponseEntity<String> response = 
            restFulTemp.doPost("http://127.0.0.1:9000/redis/get",s);
    return Result.buildSuccess();
}
/**
 * POST恳求 参数为FORM 表单参数
 * @return
 */
@PostMapping("/doPost")
public Result doPostForm() {
    EntityParam s=new EntityParam();
    s.setAge(19);
    s.setHobby("swing");
    s.setName("judy");
    ResponseEntity<String> response =
            restFulTemp.doPostForm("http://127.0.0.1:8000/chat", BeanUtil.beanToMap(s));
    return Result.buildSuccess(response.getBody());
}

DELETE恳求

@GetMapping("/doDelete")
public Result doDelete() {
    restFulTemp.doDelete("http://127.0.0.1:8000/chat");
    return Result.buildSuccess();
}

PUT恳求

@GetMapping("/doPut")
public Result doPut() {
    EntityParam s=new EntityParam();
    restFulTemp.doPut("http://127.0.0.1:8000/chat",s);
    return Result.buildSuccess(s);
}

过错反常自界说

我内置过错反常和事务反常或许无法满意你自身接口事务反常需求。你能够自界说过错反常类,和过错呼应枚举码。

自界说过错枚举 需求完成ResultCode接口

package cn.soboys.restapispringbootstarter;
import cn.soboys.restapispringbootstarter.i18n.I18NKey;
/**
* @author 大众号 程序员三时
* @version 1.0
* @date 2023/6/26 10:21
* @webSite https://github.com/coder-amiao
* 呼应码接口,自界说呼应码,完成此接口
*/
public interface ResultCode extends I18NKey {
   String getCode();
   String getMessage();
}

假如要支撑国际化还需求完成国际化接口I18NKey 参阅我内部HttpStatus完成即可

package cn.soboys.restapispringbootstarter;
import cn.soboys.restapispringbootstarter.i18n.I18NKey;
/**
 * @author 大众号 程序员三时
 * @version 1.0
 * @date 2023/6/26 11:01
 * @webSite https://github.com/coder-amiao
 */
public enum HttpStatus implements ResultCode, I18NKey {
    /**
     * 体系内部过错
     */
    INTERNAL_SERVER_ERROR("500", "internal_server_error"),
    BAD_GATEWAY("502", "bad_gateway"),
    NOT_FOUND("404", "not_found"),
    UNAUTHORIZED("401", "unauthorized"),
    FORBIDDEN("403", "forbidden"),
    METHOD_NOT_ALLOWED("405", "method_not_allowed"),
    REQUEST_TIMEOUT("408", "request_timeout"),
    INVALID_ARGUMENT("10000", "invalid_argument"),
    ARGUMENT_ANALYZE("10001", "argument_analyze"),
    BUSINESS_EXCEPTION("20000", "business_exception");
    private final String value;
    private final String message;
    HttpStatus(String value, String message) {
        this.value = value;
        this.message = message;
    }
    @Override
    public String getCode() {
        return value;
    }
    @Override
    public String getMessage() {
        return message;
    }
    @Override
    public String key() {
        return message;
    }
}
rest-api:
  enabled: false
  i18n:
    # 若前端无header传参则回来中文信息
    i18n-header: Lang
    default-lang: cn
    message:
      # admin
      internal_server_error:
        en: Internal Server Error
        cn: 体系过错
      bad_gateway:
        en: Bad Gateway
        cn: 过错的恳求
      unauthorized:
        en: Unauthorized
        cn: 未授权
      forbidden:
        en: Forbidden
        cn: 资源制止拜访
      method_not_allowed:
        en: Method Not Allowed
        cn: 办法不被答应
      request_timeout:
        en: Request Timeout
        cn: 恳求超时
      invalid_argument:
        en: Invalid Argument {}
        cn: 参数过错 {}
      argument_analyze:
        en: Argument Analyze {}
        cn: 参数解析反常 {}
      business_exception:
        en: Business Exception
        cn: 事务过错
      not_found:
        en: Not Found
        cn: 恳求资源不存在

我开源了团队内部基于SpringBoot Web快速开发的API脚手架stater

我开源了团队内部基于SpringBoot Web快速开发的API脚手架stater

我开源了团队内部基于SpringBoot Web快速开发的API脚手架stater

我开源了团队内部基于SpringBoot Web快速开发的API脚手架stater

事务断言

封装了事务过错断言东西。Assert 遵从过错优先回来准则。

你要自界说自己的事务反常。承继BusinessException 重写对应办法

package cn.soboys.restapispringbootstarter.exception;
import cn.soboys.restapispringbootstarter.HttpStatus;
import cn.soboys.restapispringbootstarter.ResultCode;
import lombok.Data;
/**
 * @author 大众号 程序员三时
 * @version 1.0
 * @date 2023/6/26 16:45
 * @webSite https://github.com/coder-amiao
 */
@Data
public class BusinessException extends RuntimeException {
    /**
     * 过错码
     */
    private String code="20000";
    /**
     * 过错提示
     */
    private String message;
    public BusinessException(String message) {
        this.message = message;
    }
    public BusinessException(String message, String code) {
        this.message = message;
        this.code = code;
    }
    public BusinessException(ResultCode resultCode) {
        this.message = resultCode.getMessage();
        this.code = resultCode.getCode();
    }
}

项目中日志是十分常用的,并且还是有必要的。现已主动装备集成spring-boot-starter-logging 你不需求在项目中独自引进

<!--日志集成-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-logging</artifactId>
</dependency>

默许日志装备

logging:
    path: ./logs   #日志存储途径(服务器上肯定)
    max-history: 90 # 保存多少天
    max-file-size: 3MB  # 每个文件巨细
    max-total-size-cap: 1GB  #总文件巨细超越多少紧缩
    level-root: INFO    # 这儿的INFO能够替换为其他日志等级,如DEBUG, WARN, ERROR, TRACE, FATAL, OFF等。 日志等级由低到高分别是debugger-info-warn-error
    logDataSourceClass: cn.soboys.restapispringbootstarter.log.LogFileDefaultDataSource # 日志数据源

日志记载与追寻

RestFull API 一致回来有一个requestId 它是每个接口仅有标识。用于接口恳求日志链路追寻。日志查询。 如:

{
    "msg": "操作成功",
    "code": "OK",
    "previousPage": 1,
    "success": true,
    "requestId": "udYNdbbMFE45R84OPu9m",
    "nextPage": 1,
    "pageSize": 1,
    "totalPageSize": 1,
    "hasNext": "false",
    "timestamp": "2023-07-09 03:00:27",
    "info": [
        {
            "name": "judy",
            "hobby": "swing",
            "age": 18
        }
    ]
}

经过requestId你能够很轻松的在你的日志文件查询定位到每次过错的恳求。

经过Log注解记载你想要记载恳求

@PostMapping("/page")
@Log(value = "查询用户数据",apiType= LogApiTypeEnum.USER,CURDType= LogCURDTypeEnum.RETRIEVE)
public Result page(@Validated EntityParam s) {
    ResultPage<List<EntityParam>> resultPage=new ResultPage<>();
    List a=new ArrayList();
    a.add(s);
    resultPage.setPageData(a);
    return ResultPage.buildSuccess(resultPage);
}

体系默许日志记载数据源为日志文件。如

2023-07-09 03:00:32 INFO  http-nio-8000-exec-2 cn.soboys.restapispringbootstarter.log.LogFileDefaultDataSource {
    "description": "查询用户数据",
    "method": "cn.soboys.restapispringbootstarter.controller.ApiRestController.page()",
    "logType": "INFO",
    "time": 3,
    "result": {
        "success": true,
        "code": "OK",
        "msg": "操作成功",
        "requestId": "udYNdbbMFE45R84OPu9m",
        "timestamp": "2023-07-09 03:00:27",
        "data": {
            "previousPage": 1,
            "nextPage": 1,
            "pageSize": 1,
            "totalPageSize": 1,
            "hasNext": "false",
            "pageData": [
                {
                    "name": "judy",
                    "hobby": "swing",
                    "age": 18
                }
            ],
            "requestId": "qJTOejQmY-OOf7fagegB",
            "timestamp": "2023-07-09 03:00:27"
        }
    },
    "apiType": "USER"
}
2023-07-09 03:08:03 INFO  http-nio-8000-exec-4 cn.soboys.restapispringbootstarter.log.LogFileDefaultDataSource {
    "description": "查询用户数据",
    "method": "cn.soboys.restapispringbootstarter.controller.ApiRestController.page()",
    "logType": "INFO",
    "time": 1,
    "result": {
        "success": true,
        "code": "OK",
        "msg": "操作成功",
        "requestId": "kP3yPP-H7wI2x1ak6YFA",
        "timestamp": "2023-07-09 03:00:27",
        "data": {
            "previousPage": 1,
            "nextPage": 1,
            "pageSize": 1,
            "totalPageSize": 1,
            "hasNext": "false",
            "pageData": [
                {
                    "name": "judy",
                    "hobby": "swing",
                    "age": 18
                }
            ],
            "requestId": "pGbbiEj8GQ1eTxQpF2Jr",
            "timestamp": "2023-07-09 03:00:27"
        }
    },
    "apiType": "USER"
}

你能够自界说自己的日志数据源完成LogDataSource接口 日志操作支撑异步。需求在装备类。或许发动类加上@EnableAsync 注解

package cn.soboys.restapispringbootstarter.log;
import org.springframework.scheduling.annotation.Async;
import java.util.Map;
/**
 * @Author: kenx
 * @Since: 2021/6/23 13:55
 * @Description:
 */
public interface LogDataSource {
    /**
     * 获取拓展数据
     * @return
     * @param logEntry
     */
    @Async
    void  save(LogEntry logEntry);
}

或许你能够承继我默许的日志数据源完成类LogFileDefaultDataSource 重写save(LogEntry logEntry)办法。

@Slf4j
public class LogFileDefaultDataSource implements LogDataSource {
    /**
     * 自界说保存数据源
     *
     * @param
     * @return LogEntry
     */
    @Override
    public void save(LogEntry logEntry) {
        log.info(JSONUtil.toJsonPrettyStr(logEntry));
    }
}

假如是自界说日志数据源完成需求再装备文件,装备日志数据源。如:

logging:
    path: ./logs   #日志存储途径(服务器上肯定)
    max-history: 90 # 保存多少天
    max-file-size: 3MB  # 每个文件巨细
    max-total-size-cap: 1GB  #总文件巨细超越多少紧缩
    level-root: INFO    # 这儿的INFO能够替换为其他日志等级,如DEBUG, WARN, ERROR, TRACE, FATAL, OFF等。 日志等级由低到高分别是debugger-info-warn-error
    logDataSourceClass: cn.soboys.restapispringbootstarter.log.LogFileDefaultDataSource # 日志数据源

缓存和redis

项目中缓存运用是十分常见的。用的最多的是根据Redis缓存。所以我封装了关于RedisKey和Value常用操作。

::: tip 默许不引进Redis依靠,假如要运用Redis需求自己独自引进 :::

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

项目运用

注入redisTempUtil

@Autowired
private RedisTempUtil redisTempUtil;

如示列

@Autowired
private RedisTempUtil redisTempUtil;
@GetMapping("/redis")
public Result chatDialogue( ) {
    redisTempUtil.set("test","111");
    redisTempUtil.get("test");
    redisTempUtil.set("user","userObj",7200l);
    redisTempUtil.getAllKey("xx");  //*表达式
    redisTempUtil.clean();
    redisTempUtil.deleteObject("test");
    redisTempUtil.hasKey("");
    return Result.buildSuccess();
}

一致缓存办理

上面咱们是直接经过东西类redisTempUtil直接自己界说key然后去存储,这种方法是不可取的假如key许多随意界说就会很紊乱。我供给了一致缓存key办理接口CacheTmp 参阅完成CacheKey 根据枚举形式,把一切key会集办理

package cn.soboys.restapispringbootstarter.cache;
import lombok.Getter;
/**
 * @author 大众号 程序员三时
 * @version 1.0
 * @date 2023/7/2 11:04
 * @webSite https://github.com/coder-amiao
 * 缓存枚举
 */
@Getter
public enum CacheKey implements CacheTmp {
    // 暗码的重置码
    PWD_RESET_CODE("reset:code:", true),
    ;
    private String key;
    /**
     * Key是否是Key前缀, true时直接取key=key,假如false时key=key+suffix
     */
    private boolean hasPrefix;
    CacheKey(String key, boolean hasPrefix) {
        this.key = key;
        this.hasPrefix = hasPrefix;
    }
    @Override
    public Boolean getHasPrefix() {
        return this.hasPrefix;
    }
    @Override
    public String getKey() {
        return this.key;
    }
}

运用

  1. 存储关于key
@GetMapping("/redis")
public Result chatDialogue() {
    CacheKey.PWD_RESET_CODE.valueSetAndExpire("test", 60l, TimeUnit.SECONDS, "judy");
    return Result.buildSuccess();
}
  1. 获取对应的key
@GetMapping("/redis/get")
public Result redisGet() {
    String a = CacheKey.PWD_RESET_CODE.valueGet("judy");
    return Result.buildSuccess(a);
}

spring Cache完成

封装了spring Cache进一步运用 项目中在装备类或许发动类经过注解@EnableCaching敞开直接运用即可

@Cacheable(cacheNames = "testCache", keyGenerator = "keyGeneratorStrategy")
@GetMapping("/redis/springCache")
public Result springCache() {
    String a = "test cache";
    return Result.buildSuccess(a);
}

东西类运用springCacheUtil 支撑供给不是根据注解的运用方法

@GetMapping("/redis/springCache")
public Result redisSpringCache() {
    String a = "111344";
    springCacheUtil.putCache("test","key","121e1");
    return Result.buildSuccess(a);
}

::: tip 默许不引进Redis依靠,缓存根据内存完成(你项目引进redis依靠后会自定切换数据源为Redis缓存) :::

redis装备

多个项目或许模块运用一个key或许会造成紊乱,所以供给了一个大局装备key。

  redis:
    key-prefix: rest 

代码中增加一个 String 类型的 key:testKey,其实践在 redis 中存储的 key name 为 rest:testKey

大局 key 前缀的装备,并不影响对 key 的其他操作,例如获取对应的 value 时,依然是传入 testKey,而不是 rest:testKey

String key = "testKey";
String value = redisTempUtil.get(key);
String value1 = CacheKey.PWD_RESET_CODE.valueGet(key);

OpenApi文档生成

现已内置主动支撑。swagger文档。和最新的OpenApi3 文档。项目发动后即可拜访。

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

  2. 根据spring-doc 文档UI增强 途径/doc.html

  3. 接口文档特点信息

  openapi:
    description:
    title:
    version:
    license: 
    contact:
      name:
      email:
      url: 
  • 发动项目后,拜访 http://server:port/context-path/swagger-ui.html 即可进入 Swagger UI 页面,OpenAPI 描述将在以下 json 格局的 url 中 供给:http://server:port/context-path/v3/api-docs
  • server:域名 或 IP
  • port:服务器端口
  • context-path:应用程序的上下文途径,springboot 默以为空
  • 文档也能够 yaml 格局供给,坐落以下途径:/v3/api-docs.yaml

假如厌弃官方供给的 swagger-ui 不美观,或许运用不顺手,能够挑选封闭 ui,还能够剔除去 ui 相关的 webjar 的引进。

springdoc:
  swagger-ui:
    enabled: false

OpenAPI 文档信息,默许可在此 url 中获取: http://server:port/context-path/v3/api-docs。 能够运用其他支撑 OpenAPI 协议的东西,经过此地址,进行 API 展现,如 Apifox。 ( Postman 的 api 测验也能够运用此地址进行导入生成 )

Knife4j (原 swagger-bootstrap-ui) 3.x 版别供给了关于 OpenAPI 协议的部分支撑。

::: tip Knife4j 许多当地没有依照协议标准完成,所以运用起来会有许多问题,另外项目也好久没有保护了,不引荐运用。 :::

因为 knife4j 关于标准支撑的不全面,无法直接运用单文档源数据,所以有必要进行分组或许 urls 的指定。

# urls
springdoc:
  swagger-ui:
    urls:
      - { name: 'sample', url: '/v3/api-docs' }

或许

#分组
springdoc:
  group-configs:
    - { group: 'sample', packages-to-scan: 'com.example' }

Knife4j 的 UI 拜访地址有所不同,页面映射在 doc.html 途径下,发动项目后,拜访 http://server:port/context-path/doc.html

即可进入 Knife4j 的 Swagger UI 页面。

代码主动生成

项目中咱们运用mybatis 或许mybatisPlus 一些简略的单表事务代码,增删改成。咱们能够一键生成。不需求重复写。 我封装了mybatisPlus 代码生成东西

::: tip 默许不引进mybatisPlus代码生成依靠,假如要运用mybatisPlus代码生成需自行独自引进 :::

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-generator</artifactId>
    <version>3.4.1</version>
</dependency>
<!-- MySQL -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.28</version>
</dependency>
<!--代码生成依靠的模板引擎-->
<dependency>
    <groupId>org.freemarker</groupId>
    <artifactId>freemarker</artifactId>
    <version>2.3.31</version>
</dependency>

项目运用

  1. 代码生成装备类
package cn.soboys.restapispringbootstarter.config;
import lombok.Data;
/**
 * @author 大众号 程序员三时
 * @version 1.0
 * @date 2023/7/5 00:05
 * @webSite https://github.com/coder-amiao
 */
@Data
public class GenerateCodeConfig {
    /**
     * 数据库驱动
     */
    private String driverName;
    /**
     * 数据库衔接用户名
     */
    private String username;
    /**
     * 数据库衔接暗码
     */
    private String password;
    /**
     * 数据库衔接url
     */
    private String url;
    /**
     * 生成代码 保存途径。默许当前项目下。
     * 如需修正,运用肯定途径
     */
    private String projectPath;
    /**
     * 代码生成包方位
     */
    private String packages;
}

示列如:

public class Test {
    public static void main(String[] args) {
        GenerateCodeConfig config=new GenerateCodeConfig();
        config.setDriverName("com.mysql.cj.jdbc.Driver");
        config.setUsername("root");
        config.setPassword("root");
        config.setUrl("jdbc:mysql://127.0.0.1:3306/ry?useUnicode=true&useSSL=false&characterEncoding=utf8");
        //config.setProjectPath("superaide");
        config.setPackages("cn.soboys.superaide");
        MyBatisPlusGenerator.generate(config);
    }
}

常见问题

在运用过程中尽量运用最新版别。我会持续更新更多的内容。 会第一时间发布在我的大众号 程序员三时。全网同名

能够关注 大众号 程序员三时。用心分享持续输出优质内容。期望能够给你带来一点协助