gpt4 book ai didi

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

转载 作者:我是一只小鸟 更新时间:2023-07-20 14:31:50 33 4
gpt4 key购买 nike

我们现在使用 SpringBoot 做Web 开发已经比之前SprngMvc 那一套强大很多了。 但是 用SpringBoot Web 做API 开发还是不够简洁有一些.

每次Web API常用功能都需要重新写一遍。或者复制之前项目代码。于是我封装了这么一个 。

抽出 SpringBoot Web API 每个项目必备需要重复写的模块,和必备功能。 并且扩展了我工作中用到的 所有工具库.

基于它,你可以轻松开发SpringBoot WEB API,提高效率。不在去关心一些繁琐。重复工作,而是把重点聚焦到业务.

目前更新版本到1.5.2 功能如下 。

  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文档一键配置。支持多种文档和自动配置
  12. 接口限流,Ip城市回显
  13. HttpUserAgent请求设备工具封装
  14. RequestUtil参数解析封装工具

后续会持续更新。项目中重复使用,必备模块和工具.

  • 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 包装返回定义 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 格式 。

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

                        
                      

请求结果 。

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城市记录

日志记录提供 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;
}


                        
                      

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 包装返回定义 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 格式 。

  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>

                        
                      

也内置扩展了许多自定义参数校验参考 。

第三方请求

有时候我们项目中需要调用第三方接口服务。基于 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: 请求资源不存在


                        
                      

业务断言

封装了业务错误断言工具。 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 缓存。于是我封装了对于 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;
    }

}

                        
                      

使用 。

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

                        
                      

常见问题

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

可以关注 公众号 程序员三时 。用心分享持续输出优质内容。希望可以给你带来一点帮助 。

最后此篇关于我开源了团队内部基于SpringBootWeb快速开发的API脚手架stater的文章就讲到这里了,如果你想了解更多关于我开源了团队内部基于SpringBootWeb快速开发的API脚手架stater的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。

33 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com