Jorgen's blog Jorgen's blog
首页
  • 平台架构
  • 混合式开发记录
  • 推送服务
  • 数据分析
  • 实时调度
  • 架构思想

    • 分布式
  • 编程框架工具

    • 编程语言
    • 框架
    • 开发工具
  • 数据存储与处理

    • 数据库
    • 大数据
  • 消息、缓存与搜索

    • 消息队列
    • 搜索与日志分析
  • 前端与跨端开发

    • 前端技术
    • Android
  • 系统与运维

    • 操作系统
    • 容器化与 DevOps
  • 物联网与安全

    • 通信协议
    • 安全
    • 云平台
收藏
  • 关于我
  • 终身学习
  • 关于时间的感悟
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

jorgen

Love it, make mistakes, learn, keep grinding.
首页
  • 平台架构
  • 混合式开发记录
  • 推送服务
  • 数据分析
  • 实时调度
  • 架构思想

    • 分布式
  • 编程框架工具

    • 编程语言
    • 框架
    • 开发工具
  • 数据存储与处理

    • 数据库
    • 大数据
  • 消息、缓存与搜索

    • 消息队列
    • 搜索与日志分析
  • 前端与跨端开发

    • 前端技术
    • Android
  • 系统与运维

    • 操作系统
    • 容器化与 DevOps
  • 物联网与安全

    • 通信协议
    • 安全
    • 云平台
收藏
  • 关于我
  • 终身学习
  • 关于时间的感悟
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 平台架构
  • 技术选型
  • 开发脚手架
  • UI规范
  • 开发规范
  • 代码分支管理模型
  • 需求分析与管理
  • 权限设计
  • 树形组织设计
  • 协议设计
  • 指令交互
  • OTA
  • 规则引擎
  • 数据流转
  • 报告生成与导出
  • 监控设备接入
  • 时序数据库
  • 平台监控
  • 云⛈
  • 接口设计
    • 介绍
    • 参数校验
      • 常用注解
      • 示例
      • 优缺点
    • 幂等性
      • 什么是幂等?
      • 如何设计幂等
      • token令牌
    • 重复提交
  • 安全传输
  • CI&CD
  • 缓存
  • 消息处理引擎
  • 性能调优🔥
  • 线上事故🔥
  • 混合式开发记录
  • 推送服务
  • 机器人通信协议
  • 数据分析
  • flink模板工程
  • 实时调度
  • 机器人模块化设计
  • STM32入门
  • 开发日志
Jorgen
2023-02-18
目录

接口设计

# 介绍

现代接口的设计,都需要遵循这么几点的要求:参数校验、幂等性、放置重复提交。才能保证安全性,可用性。

# 参数校验

平时在开发接口的时候,常常会需要对参数进行校验,这里提供两种处理校验逻辑的方式。使用Hibernate Validator来处理。 #Hibernate Validator Hibernate Validator是SpringBoot内置的校验框架,只要集成了SpringBoot就自动集成了它,我们可以通过在对象上面使用它提供的注解来完成参数校验。

# 常用注解

我们先来了解下常用的注解,对Hibernate Validator所提供的校验功能有个印象。

  @Null:被注释的属性必须为null;
  @NotNull:被注释的属性不能为null;
  @AssertTrue:被注释的属性必须为true;
  @AssertFalse:被注释的属性必须为false;
  @Min:被注释的属性必须大于等于其value值;
  @Max:被注释的属性必须小于等于其value值;
  @Size:被注释的属性必须在其min和max值之间;
  @Pattern:被注释的属性必须符合其regexp所定义的正则表达式;
  @NotBlank:被注释的字符串不能为空字符串;
  @NotEmpty:被注释的属性不能为空;
  @Email:被注释的属性必须符合邮箱格式。
1
2
3
4
5
6
7
8
9
10
11

# 示例

@Getter
@Setter
public class TroubleRecordParam  {
  
    /**
     * 问题描述
     */
    @Size(max = 256, message = "troubleDesc 长度不能超过 256")
    private String troubleDesc;


    /**
     * 设备IMEI
     */
    @Size(max = 50, message = "imei 长度不能超过 50")
    private String imei;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

然后在添加的接口中添加@Validated注解,并注入一个BindingResult参数

@Controller
@RequestMapping("/resultRecord")
public class ResultRecordController {
    @Autowired
    private ResultRecordService rrService;

  
    @RequestMapping(value = "/create", method = RequestMethod.POST)
    public CommonResult create(@Validated @RequestBody TroubleRecordParam troubleRecordParam, BindingResult result) {
        CommonResult commonResult;
        int count = rrService.createResultRecord(pmsBrand);
        if (count == 1) {
            commonResult = CommonResult.success(count);
        } else {
            commonResult = CommonResult.failed();
        }
        return commonResult;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

然后在整个Controller层创建一个切面,在其环绕通知中获取到注入的BindingResult对象,通过hasErrors方法判断校验是否通过,如果有错误信息直接返回错误信息,验证通过则放行;

@Aspect
@Component
@Order(2)
public class BindingResultAspect {
    @Pointcut("execution(public * com.demo.test.controller.*.*(..))")
    public void BindingResult() {
    }

    @Around("BindingResult()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
        Object[] args = joinPoint.getArgs();
        for (Object arg : args) {
            if (arg instanceof BindingResult) {
                BindingResult result = (BindingResult) arg;
                if (result.hasErrors()) {
                    FieldError fieldError = result.getFieldError();
                    if(fieldError!=null){
                        return CommonResult.validateFailed(fieldError.getDefaultMessage());
                    }else{
                        return CommonResult.validateFailed();
                    }
                }
            }
        }
        return joinPoint.proceed();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

使用切面的话,由于每个校验方法中都需要注入BindingResult对象,这样会导致很多重复工作,其实当校验失败时,SpringBoot默认会抛出MethodArgumentNotValidException或BindException异常,我们只要全局处理该异常依然可以得到校验失败信息。


@ControllerAdvice
public class GlobalExceptionHandler {
    
   @ResponseBody
   @ExceptionHandler(value = MethodArgumentNotValidException.class)
   public R handleValidException(MethodArgumentNotValidException e) {
       BindingResult bindingResult = e.getBindingResult();
       String message = null;
       if (bindingResult.hasErrors()) {
           FieldError fieldError = bindingResult.getFieldError();
           if (fieldError != null) {
               message = fieldError.getField()+fieldError.getDefaultMessage();
           }
       }
       return CommonResult.failed(message);
   }
   
    @ResponseBody
    @ExceptionHandler(value = BindException.class)
    public R handleValidException(BindException e) {
        BindingResult bindingResult = e.getBindingResult();
        String message = null;
        if (bindingResult.hasErrors()) {
            FieldError fieldError = bindingResult.getFieldError();
            if (fieldError != null) {
                message = fieldError.getField()+fieldError.getDefaultMessage();
            }
        }
        return Response.failed(message);
    }   
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

由于SpringBoot 2.3版本默认移除了校验功能,如果想要开启的话需要添加如下依赖。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>
1
2
3
4

# 优缺点

这种方式的优点是可以使用注解来实现参数校验,不需要一些重复的校验逻辑,但是也有一些缺点,比如需要在Controller的方法中额外注入一个BindingResult对象,只支持一些简单的校验,涉及到要查询数据库的校验就无法满足了。

# 幂等性

# 什么是幂等?

幂等是一个数学与计算机科学概念。

在数学中,幂等用函数表达式就是:f(x) = f(f(x))。比如求绝对值的函数,就是幂等的,abs(x) = abs(abs(x))。 计算机科学中,幂等表示一次和多次请求某一个资源应该具有同样的副作用,或者说,多次请求所产生的影响与一次请求执行的影响效果相同。

# 如何设计幂等

幂等意味着一条请求的唯一性。不管是你哪个方案去设计幂等,都需要一个全局唯一的ID,去标记这个请求是独一无二的。

# token令牌

token 令牌方案一般包括两个请求阶段:

  • 客户端请求申请获取token,服务端生成token返回
  • 客户端带着token请求,服务端校验token

流程图如下:

流程图

  1. 客户端发起请求,申请获取token。
  2. 服务端生成全局唯一的token,保存到redis中(一般会设置一个过期时间),然后返回给客户端。
  3. 客户端带着token,发起请求。
  4. 服务端去redis确认token是否存在,一般用 redis.del(token)的方式,如果存在会删除成功,即处理业务逻辑,如果删除失败不处理业务逻辑,直接返回结果。

| 这边文章里面,作者总结了8中接口幂等的方法 (opens new window)。总之,根据实际情况可以选择合适的方法进行实现。

# 重复提交

重复提交的思路其实也差不多雷同。单位时间内的唯一用户的唯一接口表示。

以下是一些常见的避免接口重复提交的方法:

  1. Token 验证:在每个请求中添加一个 token,服务器验证 token 是否有效。如果 token 有效,则可以处理请求。如果 token 无效,则返回错误信息。当一个请求处理成功后,服务器可以将 token 标记为已使用,以防止同一请求被重复提交。

  2. 前端防重复提交:前端可以在表单提交时,禁用提交按钮,或者在提交时使用一些防止重复提交的技术,例如通过 AJAX 请求进行提交,或者在提交前使用一个标识符来防止重复提交。

  3. 服务端幂等性校验:服务端可以通过一些技术来判断接口的幂等性,从而避免重复提交。例如,可以在请求头中添加一个唯一标识符,或者在请求中添加一个时间戳,从而判断是否为同一请求。

  4. 接口限流:接口限流可以限制用户的请求速度和数量,从而避免接口被过多的请求打爆。可以使用一些开源的限流组件,例如 Guava、Hystrix 等。

  5. 使用 Post/Redirect/Get(PRG)模式:当用户提交表单时,服务端可以在成功处理请求后,重定向用户到一个新的页面,从而避免用户在刷新页面时,再次提交相同的表单数据。

#参数校验#幂等性#去重
上次更新: 2023/02/19, 11:42:06
云⛈
安全传输

← 云⛈ 安全传输→

最近更新
01
STM32入门
03-09
02
ADB调试
03-09
03
微信小程序学习记录
02-09
更多文章>
Theme by Vdoing | Copyright © 2019-2025 Jorgen | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式