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

news2024/11/19 8:21:23

我们现在使用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包装返回定义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;
    }

请求结果

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.81 次访问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包装返回定义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>

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

第三方请求

有时候我们项目中需要调用第三方接口服务。基于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缓存。于是我封装了对于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);
    }
}

常见问题

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/756185.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【100天精通python】Day3:python的基本数据类型和数据类型转换

一. python的基本数据类型 以下是一些常见的举例和详细解释&#xff1a; 整数&#xff08;int&#xff09;&#xff1a;表示整数值。例如&#xff1a; x 5&#xff1a;将整数值5赋给变量x。 y -10&#xff1a;将整数值-10赋给变量y。 浮点数&#xff08;float&#xff09;&a…

【聚焦机器学习与实践经验的实用指南——《Python机器学习项目实战》】

《Python机器学习项目实战》引领大家在构建实际项目的过程中&#xff0c;掌握关键的机器学习概念!使用机器学习&#xff0c;我们可完成客户行为分析、价格趋势预测、风险评估等任务。要想掌握机器学习&#xff0c;需要有优质的范例、清晰的讲解和大量的练习。本书完全满足这三点…

漏洞复现-CVE-2022-24112原理与复现

目录 漏洞原理漏洞描述影响范围 apisix学习漏洞复现config.yaml环境搭建exp代码 入侵检测与修复总结参考 漏洞原理 漏洞描述 An attacker can abuse the batch-requests plugin to send requests to bypass the IP restriction of Admin API. A default configuration of Apa…

基于JavaSwing+MySQL的电影票购票管理系统

点击以下链接获取源码&#xff1a; https://download.csdn.net/download/qq_64505944/88051172?spm1001.2014.3001.5503 JDK1.8 MySQL5.7 功能&#xff1a;管理员与用户两个角色登录&#xff0c;管理员可以对电影进行增删改查处理&#xff0c;可以对影院增删改查管理&#x…

【C语言】类型转换和优先级

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前正在回炉重造C语言&#xff08;2023暑假&#xff09; ✈️专栏&#xff1a;【C语言航路】 &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章对你…

【Linux系统编程】Linux第一个小程序——进度条

文章目录 1. 对回车&#xff08;\r&#xff09;和换行&#xff08;\n&#xff09;的理解1.1 概念理解1.2 测试 2. 缓冲区的理解2.1 观察现象2.2 原因解释 3. 倒计时小程序4. 进度条小程序4.1 基本思路及实现4.2 改进及优化4.3 增加百分比显示4.4 增加旋转光标4.5 给进度条配色 …

基于FT232HL的USB2.0转ARINC429板卡

基于FT232HL的USB2.0转ARINC429板卡 1 概述 《USB2.0转ARINC429板卡》采用底板子板&#xff0c;层叠安装的结构&#xff1b;使用同样的底板&#xff0c;变换不同功能的子板实现不同的功能版本。 a) 降低硬件设计复杂度&#xff1a;新板卡设计只需要设计子板&#xff0c;子板的…

类和对象(C++)( static成员、explicit、友元、内部类、匿名对象)

类和对象 static成员概念static成员“登场”特性static成员使用 注意 explicit从一段代码引入explicit和explicit相关特性 友元友元函数引入问题解决 小结 友元类 内部类概念特性 匿名对象引入使用 static成员 概念 类的静态成员&#xff1a;声明为static的类成员。 静态成员变…

【黑客】网络安全靠自学?只会毁了你!

1️⃣网安现状 ❗本文面向所有 想要涉足网安领域 或 已经涉足但仍处在迷茫期 的伙伴&#xff0c;如果你月薪达到了3w&#xff0c;那么请你离开。 如果没有&#xff0c;希望你继续看下去&#xff0c;因为你人生的转折点将从这篇文章开始。 ✈️网络安全&#xff0c;一个近几年大…

5 个能出色完成数据恢复的免费数据恢复软件知识分享。

有时&#xff0c;由于意外删除或某些问题&#xff0c;您可能会丢失 Windows 10 笔记本电脑或台式机上的重要数据。Windows 操作系统不提供任何内部工具来恢复已删除的数据。但是有一些非常好的数据恢复软件可以更专业地完成这项工作。最好的人总是有报酬的&#xff0c;但不用担…

按键精灵、auto.js等一些移动端脚本 如何连接云服务器的数据库, 进行读写操作

一、技术背景 按键手机版和auto.js&#xff0c;只支持连接本地数据库sqllite&#xff0c;该数据库只存在本地 其他设备无法读写&#xff0c;就像本地的txt一样。 而很多脚本作者的需求是&#xff1a;多个脚本&#xff0c;甚至在全国不同城市的脚本也能读取和写入同一批数据&…

AJAX-day01

(创作不易&#xff0c;感谢有你&#xff0c;你的支持&#xff0c;就是我前行的最大动力&#xff0c;如果看完对你有帮助&#xff0c;请留下您的足迹&#xff09; 目录 AJAX 概念和 axios 使用 什么是 AJAX 怎么用 AJAX &#xff1f; axios 使用 认识 URL 什么是 URL&…

韦东山Linux教学视频中的makefile文件详细介绍

前言 &#xff08;1&#xff09;在学习韦东山Linux教学视频的时候&#xff0c;他的makefile并没有做详细的介绍。以至于我学了很长时间对他的makefile文件不理解。所以本文将会详细介绍韦东山Linux教学视频中的makefile文件含义。 &#xff08;2&#xff09;注意&#xff1a;我…

使用 docker-compose 部署 Jenkins

注&#xff1a;我是在虚拟机&#xff08;Ubuntu&#xff09;上部署了 docker-compose&#xff0c;然后才使用 docker-compose 部署 Jenkins&#xff01; 关于如何在 Ubuntu 部署 docker-compose&#xff0c;可以看我其它的文章。 本文目录 1. 创建 docker_jenkins_compose 目录…

【NI USRP】每一个USRP是如何命名的呢,和原厂Ettus型号有什么关联呢?

详细的硬件配置&#xff0c;非常有助于设备的选型。 如果您采购了X310子板&#xff0c;是可以将其转化为对应的USRP型号的设备。 B系列 EttusNI-USRP频段最大带宽通道B200mini无70 MHZ - 6 GHZ56 MHz1X1B200mini-i无70 MHZ - 6 GHZ56 MHz1X1B205mini-i无70 MHZ - 6 GHZ56 MHz…

三菱以太网通讯模块在哪

捷米特JM-ETH-FX采用工业级设计&#xff0c;导轨安装&#xff0c;带通讯线。不占用PLC编程口&#xff0c;上位机通过以太网对PLC数据监控的同时&#xff0c;触摸屏可以通过复用接口X2与PLC进行通讯。捷米特JM-ETH-FX支持工控领域内绝大多数SCADA软件&#xff0c;支持三菱MC以太…

C#开发的OpenRA游戏之维修按钮

C#开发的OpenRA游戏之维修按钮 前面分析物品的变卖按钮,如果理解这个流程,再看其它按钮的流程,其实是一样的,所以前面的文章是关键,只有理解通透的基础之上,才能继续往下。 维修按钮的存在价值,就是当建筑物受到敌方破坏,还没有完全倒掉之前,可以使用金币来进行修理。…

java项目之电子商城系统(ssm+mysql+jsp)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于ssm的电子商城系统。技术交流和部署相关看文章末尾&#xff01; 开发环境&#xff1a; 后端&#xff1a; 开发语言&#xff1a;Java 框架&…

对抗业务逻辑攻击:传统安全工具为何失效

随着数字环境的不断发展&#xff0c;不良行为者寻求利用应用程序漏洞的策略也在不断发展。最阴险的攻击类型之一是业务逻辑攻击 (BLA)。与可以通过签名或模式识别的已知攻击&#xff08;例如 SQL 注入攻击&#xff09;不同&#xff0c;BLA 针对应用程序内的核心功能和决策过程。…

python_股票增加控制人与流通股东等筛选条件

目录 写字前面&#xff1a; 结果展示 获取数据 行业数据 控制人数据 十大流通股东数据 开始合并 1 从行业数据中提取证券股的行业数据 2 合并控制人数据 3 合并十大流通股东 4 把三个结果按列合并 写字前面&#xff1a; 在分析数据的时候&#xff0c;常常需要的字段…