和 if else说再见,SpringBoot 这样做参数校验才足够优雅!

news2024/12/28 3:14:33

1. 概述

当我们想提供可靠的 API 接口,对参数的校验,以保证最终数据入库的正确性,是 必不可少 的活。比如下图就是 我们一个项目里 新增一个菜单校验 参数的函数,写了一大堆的 if else 进行校验,非常的不优雅,比起枯燥的CRUD来说,参数校验更是枯燥。

这只是一个创建菜单的校验,只需要判断菜单,菜单url 以及菜单的父类id是否为空,上级菜单是否挂载正确,这样已经消耗掉了30,40行代码了,更不要说,管理后台创建商品这种参数贼多的接口。估计要写几百行校验代码了。

/**
  * 验证参数是否正确
  */
 private void verifyForm(SysMenuEntity menu){
  if(StringUtils.isBlank(menu.getName())){
   throw new RRException("菜单名称不能为空");
  }
  
  if(menu.getParentId() == null){
   throw new RRException("上级菜单不能为空");
  }
  
  //菜单
  if(menu.getType() == Constant.MenuType.MENU.getValue()){
   if(StringUtils.isBlank(menu.getUrl())){
    throw new RRException("菜单URL不能为空");
   }
  }
  
  //上级菜单类型
  int parentType = Constant.MenuType.CATALOG.getValue();
  if(menu.getParentId() != 0){
   SysMenuEntity parentMenu = sysMenuService.getById(menu.getParentId());
   parentType = parentMenu.getType();
  }
  
  //目录、菜单
  if(menu.getType() == Constant.MenuType.CATALOG.getValue() ||
    menu.getType() == Constant.MenuType.MENU.getValue()){
   if(parentType != Constant.MenuType.CATALOG.getValue()){
    throw new RRException("上级菜单只能为目录类型");
   }
   return ;
  }
  
  //按钮
  if(menu.getType() == Constant.MenuType.BUTTON.getValue()){
   if(parentType != Constant.MenuType.MENU.getValue()){
    throw new RRException("上级菜单只能为菜单类型");
   }
   return ;
  }
 }

由于国内的开发环境可以说 99.99% 的开发者肯定都在用 spring,且正好 Spring Validation 提供了对 Bean Validation 的内置封装支持,可以使用 @Validated 注解,实现声明式校验,而无需直接调用 Bean Validation 提供的 API 方法。

而在实现原理上,也是基于 Spring AOP 拦截,最终还是调用不同的 Bean Validation 的实现框架。例如说,Hibernate Validator 。实现校验相关的操作这一点,类似 Spring Transaction 事务,通过 @Transactional 注解,实现声明式事务。下面,让我们开始学习如何在 Spring Boot 中,实现参数校验。

2. 注解

在开始入门之前,我们先了解下本文可能会涉及到的注解。javax.validation.constraints 包下,定义了一系列的约束( constraint )注解。共 22个,如下:

大致可以分为以下几类:

2.1 空和非空检查

  • @NotBlank :只能用于字符串不为 null ,并且字符串 #trim() 以后 length 要大于 0 。

  • @NotEmpty :集合对象的元素不为 0 ,即集合不为空,也可以用于字符串不为 null 。

  • @NotNull :不能为 null 。

  • @Null :必须为 null 。

2.2 数值检查

  • @DecimalMax(value) :被注释的元素必须是一个数字,其值必须小于等于指定的最大值。

  • @DecimalMin(value) :被注释的元素必须是一个数字,其值必须大于等于指定的最小值。

  • @Digits(integer, fraction) :被注释的元素必须是一个数字,其值必须在可接受的范围内。

  • @Positive :判断正数。

  • @PositiveOrZero :判断正数或 0 。

  • @Max(value) :该字段的值只能小于或等于该值。

  • @Min(value) :该字段的值只能大于或等于该值。- @Negative :判断负数。

  • @NegativeOrZero :判断负数或 0 。

2.3 Boolean 值检查

  • @AssertFalse :被注释的元素必须为 true 。

  • @AssertTrue :被注释的元素必须为 false 。

2.4 长度检查

  • @Size(max, min) :检查该字段的 size 是否在 min 和 max 之间,可以是字符串、数组、集合、Map 等。

2.5 日期检查

  • @Future :被注释的元素必须是一个将来的日期。

  • @FutureOrPresent :判断日期是否是将来或现在日期。

  • @Past :检查该字段的日期是在过去。

  • @PastOrPresent :判断日期是否是过去或现在日期。

2.6 其它检查

  • @Email :被注释的元素必须是电子邮箱地址。

  • @Pattern(value) :被注释的元素必须符合指定的正则表达式。

2.7 Hibernate Validator 附加的约束注解

org.hibernate.validator.constraints 包下,定义了一系列的约束( constraint )注解。如下:

  • @Range(min=, max=) :被注释的元素必须在合适的范围内。

  • @Length(min=, max=) :被注释的字符串的大小必须在指定的范围内。

  • @URL(protocol=,host=,port=,regexp=,flags=) :被注释的字符串必须是一个有效的 URL 。

  • @SafeHtml :判断提交的 HTML 是否安全。例如说,不能包含 javascript 脚本等等。

2.8 @Valid 和 @Validated

@Valid 注解,是 Bean Validation 所定义,可以添加在普通方法、构造方法、方法参数、方法返回、成员变量上,表示它们需要进行约束校验。

@Validated 注解,是 Spring Validation 锁定义,可以添加在类、方法参数、普通方法上,表示它们需要进行约束校验。同时,@Validated 有 value 属性,支持分组校验。属性如下:

@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Validated {

 /**
  * Specify one or more validation groups to apply to the validation step
  * kicked off by this annotation.
  * <p>JSR-303 defines validation groups as custom annotations which an application declares
  * for the sole purpose of using them as type-safe group arguments, as implemented in
  * {@link org.springframework.validation.beanvalidation.SpringValidatorAdapter}.
  * <p>Other {@link org.springframework.validation.SmartValidator} implementations may
  * support class arguments in other ways as well.
  */
 Class<?>[] value() default {};

}

对于初学的胖友来说,很容易搞混 @Valid(javax.validation包下) 和 @Validated (org.springframework.validation.annotation包下)注解。两者大致有以下的区别:

名称spring Validation是否实现了声明式校验是否支持嵌套校验是否支持分组校验
@Validated
@Valid

@Valid 有嵌套对象校验功能 例如说:如果不在 User.profile 属性上,添加 @Valid 注解,就会导致 UserProfile.nickname 属性,不会进行校验。

// User.java
public class User {
    
    private String id;

    @Valid
    private UserProfile profile;

}

// UserProfile.java
public class UserProfile {

    @NotBlank
    private String nickname;

}

总的来说,绝大多数场景下,我们使用 @Validated 注解即可。而在有嵌套校验的场景,我们使用 @Valid 注解添加到成员属性上。

3. 快速入门

3.1 引入依赖

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

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>


        <!--在一些高版本springboot中默认并不会引入这个依赖,需要手动引入-->
<!--        <dependency>
            <groupId>org.hibernate.validator</groupId>
            <artifactId>hibernate-validator</artifactId>
            <scope>compile</scope>
        </dependency>-->

        <!-- 保证 Spring AOP 相关的依赖包 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
        </dependency>


        <!--lombok相关 方便开发-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>

        <!--knife4j接口文档 方便待会进行接口测试-->
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-spring-boot-starter</artifactId>
            <version>3.0.3</version>
        </dependency>

在 Spring Boot 体系中,也提供了 spring-boot-starter-validation 依赖。在这里,我们并没有引入。为什么呢?因为 spring-boot-starter-web 已经引入了 spring-boot-starter-validation ,而 spring-boot-starter-validation 中也引入了 hibernate-validator 依赖,所以无需重复引入。三者的依赖引入关系可见下图

3.2 创建基本的类

UserAddDTO 实体类:

package com.ratel.validation.entity;

import lombok.Data;
import org.hibernate.validator.constraints.Length;

import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.Pattern;

/**
 * @Description
 * @Author ratelfu
 * @Date 2023/04/07
 * @Version 1.0
 */
@Data
public class UserAddDTO {

    /**
     * 账号
     */
    @NotEmpty(message = "登录账号不能为空")
    @Length(min = 5, max = 16, message = "账号长度为 5-16 位")
    @Pattern(regexp = "^[A-Za-z0-9]+$", message = "账号格式为数字以及字母")
    private String username;
    /**
     * 密码
     */
    @NotEmpty(message = "密码不能为空")
    @Length(min = 4, max = 16, message = "密码长度为 4-16 位")
    private String password;
}

 UserController 用来写接口,在类上,添加 @Validated 注解,表示 UserController 是所有接口都需要进行参数校验。

package com.ratel.validation.cotroller;

import com.ratel.validation.entity.UserAddDTO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import javax.validation.constraints.Min;

@RestController
@RequestMapping("/users")
@Validated
public class UserController {

    private Logger logger = LoggerFactory.getLogger(getClass());

    @GetMapping("/get")
    public UserAddDTO get(@RequestParam("id") @Min(value = 1L, message = "编号必须大于 0") Integer id) {
        logger.info("[get][id: {}]", id);
        UserAddDTO userAddDTO = new UserAddDTO();
        userAddDTO.setUsername("张三");
        userAddDTO.setPassword("123456");
        return userAddDTO;
    }

    @PostMapping("/add")
    public void add(@Valid @RequestBody UserAddDTO addDTO) {
        logger.info("[add][addDTO: {}]", addDTO);
    }

}

3.3 启动程序,进行测试

启动程序,然后再浏览器里我们就可以进行输入: swagger访问地址:http://localhost:8080/doc.html#/home 打开swagger文档 就可以进行测试了:

首先我们访问 http://localhost:8080/users/get?id=-1 进行测试,查看返回结果,果然对我们的 id 进行校验。

接下来我们访问 http://localhost:8080/users/add 进行新增用户的校验:请求体我们写成:

{
  "password": "233",
  "username": "33"
}

然后返回结果的如下:

{
  "timestamp": "2023-04-09T13:33:58.864+0000",
  "status": 400,
  "error": "Bad Request",
  "errors": [
    {
      "codes": [
        "Length.userAddDTO.password",
        "Length.password",
        "Length.java.lang.String",
        "Length"
      ],
      "arguments": [
        {
          "codes": [
            "userAddDTO.password",
            "password"
          ],
          "arguments": null,
          "defaultMessage": "password",
          "code": "password"
        },
        16,
        4
      ],
      "defaultMessage": "密码长度为 4-16 位",
      "objectName": "userAddDTO",
      "field": "password",
      "rejectedValue": "233",
      "bindingFailure": false,
      "code": "Length"
    },
    {
      "codes": [
        "Length.userAddDTO.username",
        "Length.username",
        "Length.java.lang.String",
        "Length"
      ],
      "arguments": [
        {
          "codes": [
            "userAddDTO.username",
            "username"
          ],
          "arguments": null,
          "defaultMessage": "username",
          "code": "username"
        },
        16,
        5
      ],
      "defaultMessage": "账号长度为 5-16 位",
      "objectName": "userAddDTO",
      "field": "username",
      "rejectedValue": "33",
      "bindingFailure": false,
      "code": "Length"
    }
  ],
  "message": "Validation failed for object='userAddDTO'. Error count: 2",
  "path": "/users/add"
}

返回结果的json串中的 errors 字段,参数错误明细数组。每一个数组元素,对应一个参数错误明细。这里,username 违背了 账号长度为 5-16 位规定。password违反了 密码长度为 4-16 位的规定。

返回结果示意图:

3.3 一些疑问

在这里 细心的小伙伴可能会有几个疑问:

3.3.1 疑问一

#get(id) 方法上,我们并没有给 id 添加 @Valid 注解,而 #add(addDTO) 方法上,我们给 addDTO 添加 @Valid 注解。这个差异,是为什么呢?

因为 UserController 使用了 @Validated 注解,那么 Spring Validation 就会使用 AOP 进行切面,进行参数校验。而该切面的拦截器,使用的是 MethodValidationInterceptor 。

  • 对于 #get(id) 方法,需要校验的参数 id ,是平铺开的,所以无需添加 @Valid 注解。

  • 对于 #add(addDTO) 方法,需要校验的参数 addDTO ,实际相当于嵌套校验,要校验的参数的都在 addDTO 里面,所以需要添加 @Valid (其实实测加@Validated也行,暂时不知道为啥 为了好区分就先用 @Valid 吧 )注解。

3.3.2 疑问二

#get(id) 方法的返回的结果是 status = 500 ,而 #add(addDTO) 方法的返回的结果是 status = 400 。

  • 对于 #get(id) 方法,在 MethodValidationInterceptor 拦截器中,校验到参数不正确,会抛出 ConstraintViolationException 异常。

  • 对于 #add(addDTO) 方法,因为 addDTO 是个 POJO 对象,所以会走 SpringMVC 的 DataBinder 机制,它会调用 DataBinder#validate(Object... validationHints) 方法,进行校验。在校验不通过时,会抛出 BindException 。

在 SpringMVC 中,默认使用 DefaultHandlerExceptionResolver 处理异常。

  • 对于 BindException 异常,处理成 400 的状态码。

  • 对于 ConstraintViolationException 异常,没有特殊处理,所以处理成 500 的状态码。

这里,我们在抛个问题,如果 #add(addDTO) 方法,如果参数正确,在走完 DataBinder 中的参数校验后,会不会在走一遍 MethodValidationInterceptor 的拦截器呢?思考 100 毫秒…

答案是会。这样,就会导致浪费。所以 Controller 类里,如果 只有 类似的 #add(addDTO) 方法的 嵌套校验,那么我可以不在 Controller 类上添加 @Validated 注解。从而实现,仅使用 DataBinder 中来做参数校验。

3.3.3 返回提示很不友好,太长了

第三点,无论是 #get(id) 方法,还是 #add(addDTO) 方法,它们的返回提示都非常不友好,那么该怎么办呢?我们将在 第四章节通过 处理校验异常 进行处理。

4. 处理校验异常

4.1 校验不通过的枚举类

 

package com.ratel.validation.enums;

/**
 * 业务异常枚举
 */
public enum ServiceExceptionEnum {

    // ========== 系统级别 ==========
    SUCCESS(0, "成功"),
    SYS_ERROR(2001001000, "服务端发生异常"),
    MISSING_REQUEST_PARAM_ERROR(2001001001, "参数缺失"),
    INVALID_REQUEST_PARAM_ERROR(2001001002, "请求参数不合法"),

    // ========== 用户模块 ==========
    USER_NOT_FOUND(1001002000, "用户不存在"),

    // ========== 订单模块 ==========

    // ========== 商品模块 ==========
    ;

    /**
     * 错误码
     */
    private final int code;
    /**
     * 错误提示
     */
    private final String message;

    ServiceExceptionEnum(int code, String message) {
        this.code = code;
        this.message = message;
    }

    public int getCode() {
        return code;
    }

    public String getMessage() {
        return message;
    }

}

4.2 统一返回结果实体类

package com.ratel.validation.common;

import com.fasterxml.jackson.annotation.JsonIgnore;
import org.springframework.util.Assert;

import java.io.Serializable;

/**
 * 通用返回结果
 *
 * @param <T> 结果泛型
 */
public class CommonResult<T> implements Serializable {

    public static Integer CODE_SUCCESS = 0;

    /**
     * 错误码
     */
    private Integer code;
    /**
     * 错误提示
     */
    private String message;
    /**
     * 返回数据
     */
    private T data;

    /**
     * 将传入的 result 对象,转换成另外一个泛型结果的对象
     *
     * 因为 A 方法返回的 CommonResult 对象,不满足调用其的 B 方法的返回,所以需要进行转换。
     *
     * @param result 传入的 result 对象
     * @param <T> 返回的泛型
     * @return 新的 CommonResult 对象
     */
    public static <T> CommonResult<T> error(CommonResult<?> result) {
        return error(result.getCode(), result.getMessage());
    }

    public static <T> CommonResult<T> error(Integer code, String message) {
        Assert.isTrue(!CODE_SUCCESS.equals(code), "code 必须是错误的!");
        CommonResult<T> result = new CommonResult<>();
        result.code = code;
        result.message = message;
        return result;
    }

    public static <T> CommonResult<T> success(T data) {
        CommonResult<T> result = new CommonResult<>();
        result.code = CODE_SUCCESS;
        result.data = data;
        result.message = "";
        return result;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    @JsonIgnore
    public boolean isSuccess() {
        return CODE_SUCCESS.equals(code);
    }

    @JsonIgnore
    public boolean isError() {
        return !isSuccess();
    }

    @Override
    public String toString() {
        return "CommonResult{" +
                "code=" + code +
                ", message='" + message + '\'' +
                ", data=" + data +
                '}';
    }

}

4.3 增加全局异常处理类 GlobalExceptionHandler

package com.ratel.validation.exception;


import com.ratel.validation.common.CommonResult;
import com.ratel.validation.enums.ServiceExceptionEnum;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.validation.BindException;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;

@ControllerAdvice(basePackages = "com.ratel.validation.cotroller")
public class GlobalExceptionHandler {

    private Logger logger = LoggerFactory.getLogger(getClass());

    /**
     * 处理 MissingServletRequestParameterException 异常
     *
     * SpringMVC 参数不正确
     */
    @ResponseBody
    @ExceptionHandler(value = MissingServletRequestParameterException.class)
    public CommonResult missingServletRequestParameterExceptionHandler(HttpServletRequest req, MissingServletRequestParameterException ex) {
        logger.error("[missingServletRequestParameterExceptionHandler]", ex);
        // 包装 CommonResult 结果
        return CommonResult.error(ServiceExceptionEnum.MISSING_REQUEST_PARAM_ERROR.getCode(),
                ServiceExceptionEnum.MISSING_REQUEST_PARAM_ERROR.getMessage());
    }

    @ResponseBody
    @ExceptionHandler(value = ConstraintViolationException.class)
    public CommonResult constraintViolationExceptionHandler(HttpServletRequest req, ConstraintViolationException ex) {
        logger.error("[constraintViolationExceptionHandler]", ex);
        // 拼接错误
        StringBuilder detailMessage = new StringBuilder();
        for (ConstraintViolation<?> constraintViolation : ex.getConstraintViolations()) {
            // 使用 ; 分隔多个错误
            if (detailMessage.length() > 0) {
                detailMessage.append(";");
            }
            // 拼接内容到其中
            detailMessage.append(constraintViolation.getMessage());
        }
        // 包装 CommonResult 结果
        return CommonResult.error(ServiceExceptionEnum.INVALID_REQUEST_PARAM_ERROR.getCode(),
                ServiceExceptionEnum.INVALID_REQUEST_PARAM_ERROR.getMessage() + ":" + detailMessage.toString());
    }

    @ResponseBody
    @ExceptionHandler(value = BindException.class)
    public CommonResult bindExceptionHandler(HttpServletRequest req, BindException ex) {
        logger.info("========进入了 bindException======");
        logger.error("[bindExceptionHandler]", ex);
        // 拼接错误
        StringBuilder detailMessage = new StringBuilder();
        for (ObjectError objectError : ex.getAllErrors()) {
            // 使用 ; 分隔多个错误
            if (detailMessage.length() > 0) {
                detailMessage.append(";");
            }
            // 拼接内容到其中
            detailMessage.append(objectError.getDefaultMessage());
        }
        // 包装 CommonResult 结果
        return CommonResult.error(ServiceExceptionEnum.INVALID_REQUEST_PARAM_ERROR.getCode(),
                ServiceExceptionEnum.INVALID_REQUEST_PARAM_ERROR.getMessage() + ":" + detailMessage.toString());
    }

    @ResponseBody
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public CommonResult MethodArgumentNotValidExceptionHandler(HttpServletRequest req, MethodArgumentNotValidException ex) {
        logger.info("-----------------进入了 MethodArgumentNotValidException-----------------");
        logger.error("[MethodArgumentNotValidException]", ex);
        // 拼接错误
        StringBuilder detailMessage = new StringBuilder();
        for (ObjectError objectError : ex.getBindingResult().getAllErrors()) {
            // 使用 ; 分隔多个错误
            if (detailMessage.length() > 0) {
                detailMessage.append(";");
            }
            // 拼接内容到其中
            detailMessage.append(objectError.getDefaultMessage());
        }
        // 包装 CommonResult 结果
        return CommonResult.error(ServiceExceptionEnum.INVALID_REQUEST_PARAM_ERROR.getCode(),
                ServiceExceptionEnum.INVALID_REQUEST_PARAM_ERROR.getMessage() + ":" + detailMessage.toString());
    }


    /**
     * 处理其它 Exception 异常
     * @param req
     * @param e
     * @return
     */
    @ResponseBody
    @ExceptionHandler(value = Exception.class)
    public CommonResult exceptionHandler(HttpServletRequest req, Exception e) {
        // 记录异常日志
        logger.error("[exceptionHandler]", e);
        // 返回 ERROR CommonResult
        return CommonResult.error(ServiceExceptionEnum.SYS_ERROR.getCode(),
                ServiceExceptionEnum.SYS_ERROR.getMessage());
    }
}

4.4 测试

访问:http://localhost:8080/users/add 可以看到异常结果已经被拼接成一个字符串,相比之前清新 易懂了不少。

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

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

相关文章

语音验证码短信原理和应用场景分析

引言 随着移动互联网的快速发展&#xff0c;短信验证码成为了许多应用和服务中常用的身份验证方式。然而&#xff0c;有时候用户可能由于各种原因无法接收到短信验证码&#xff0c;或者对于文字验证码不够方便或友好。 为了解决这些问题&#xff0c;语音验证码短信API 应运而…

【论文精读ACL_2021】Prefix-Tuning: Optimizing Continuous Prompts for Generation

【论文精读ACL_2021】Prefix-Tuning: Optimizing Continuous Prompts for Generation 0、前言Abstract1 Introduction2 Related Work2.1 Fine-tuning for natural language generation.2.2 Lightweight fine-tuning2.3 Prompting.2.4 Controllable generation. 3 Problem State…

Python做数据分析需要学什么?

下面分别从这四个方面来带大家学习数据分析&#xff1a; 第一&#xff0c;做数据分析要精通Python吗&#xff1f;第二&#xff0c;数据分析流程是什么&#xff1f;学什么&#xff1f;第三&#xff0c;如何培养数据分析思维&#xff1f;第四&#xff0c;数据分析书籍推荐 一、…

关于linux服务器上生成的图片中文字为的乱码问题

一、功能描述 linux服务器后端生成图表&#xff08;使用了canvas和echarts&#xff09;&#xff0c;并将生成的图片发送到企业微信群里。 二、出现的问题 生成的图表中文展示不出来&#xff0c;是乱码。错误图表展示如下&#xff1a; 三、 文字乱码出现的原因 linux服务器没有…

git在windows及linux(源码编译)环境下安装

git在windows及linux(源码编译)环境下安装 环境信息: 系统版本:CentOS Linux release 7.9.2009 (Core) git指令安装: yum install -y git 一、git在windows下安装 下载地址:https://git-scm.com/ 默认安装即可 验证 git --version 二、git在linux下安装 下载地址…

电子标签服务器搭建说明文档

一、安装运行环境 1.安装jdk 运行JDK_1.8.0_64bit.exe程序,可自定义安装路径 ​ 2.配置环境变量 择计算机图标&#xff0c;鼠标右键选择属性 ​ 选择高级系统设置&#xff0c;在系统属性中找到环境变量 ​ 在系统变量中新建JAVA_HOME环境变量&#xff0c;变量值为jdk的安装路…

跨域请求!!!

1.见过的请求类型都有哪些 getpostputdeleteoptions 和跨域相关的请求头 2.什么是跨域 当主机、域名、端口有任何一个不一致的时候&#xff0c;会出现跨域 3.跨域只有在哪里会出现 浏览器里出现了&#xff0c;它是浏览器的一个行为 4.为什么有跨域 为了安全不同域名之间…

系统编程(3):进程

文章目录 一、概念1.1 什么是进程&#xff1f;1.2 进程ID1.3 进程间通信1.4 进程的三种态的转换 二、进程控制2.1 创建进程函数&#xff1a;fork函数2.1.1 获得pid函数写代码&#xff1a;方式一&#xff1a;最简单的方式二&#xff1a;加入while&#xff08;1&#xff09;死循环…

基于Arduino的LED显示屏驱动实现

基于Arduino的LED显示屏驱动实现可以通过使用Arduino板上的数字引脚和相关库来控制LED显示屏的亮灭和显示效果。LED显示屏加载不出图像怎么办&#xff1f; 下面是基于Arduino的LED显示屏驱动实现的详细介绍&#xff1a; 硬件准备&#xff1a; Arduino开发板&#xff1a;可以使用…

ROS中 mpc_local_planner 局部路径规划器参数配置文件中参数含义

mpc_local_planner与我们比较熟悉的teb_local_planner出自同一研究所&#xff08;多特蒙德大学-控制理论与系统工程研究所&#xff09;&#xff0c;所以参数配置文件中的参数有很多相似之处&#xff0c;很多参数的含义也是相同的&#xff0c;所以熟悉teb_local_planner的参数含…

亚马逊认证考试系列 - 知识点 - 安全组介绍

AWS的安全组是一种虚拟防火墙&#xff0c;可以用于控制进入和离开AWS虚拟私有云(VPC)的流量。安全组是一种网络访问控制列表(NACL)的补充&#xff0c;因为安全组是在实例级别上进行管理的&#xff0c;而NACL是在子网级别上进行管理的。 AWS的安全组可以通过以下方式保护您的资源…

自然语言处理: 第一章N-Gram

一. 理论基础 定义: 语言模型在wiki的定义是统计式的语言模型是一个几率分布&#xff0c;给定一个长度为 m 的字词所组成的字串 W1 , W2 &#xff0c; &#xff0c;Wn &#xff0c;派几率的字符串P(S) P(W1 , W2 , &#xff0c;Wn &#xff0c; )而其中由条件概率公式我们可以…

风景类Midjourney prompt提示词

稳定输出优美风景壁纸的Midjourney prompt提示词。 1\在夏夜&#xff0c;有淡蓝色的星空&#xff0c;海边&#xff0c;流星&#xff0c;烟花&#xff0c;海滩上全是蓝色的玫瑰和绿色的植物&#xff0c;由Ivan Aivazovsky和Dan Mumford&#xff0c;趋势在cgsociety&#xff0c;…

CAN转串口设备在使用时可能遇到的问题和解决方案

上位机软件通过232串口发送数据经过CAN转232设备转换成CAN数据发送到仪表中&#xff0c;仪表接收到指定的数据后&#xff0c;返回特定的CAN数据。 使用过程中可能遇到的问题 1、串口按照规则发送数据后&#xff0c;设备端没有数据返回 遇到这种情况&#xff0c;先检查发送的数…

STM32 Proteu直流电机正反转控制系统限位开关-0035

STM32 Proteu直流电机正反转控制系统限位开关-0035 Proteus仿真小实验&#xff1a; STM32 Proteu直流电机正反转控制系统限位开关-0035 功能&#xff1a; 硬件组成&#xff1a;STM32F103C6单片机 L298N电机控制电路直流电机3个按键&#xff08;正转、反转、停止&#xff09;L…

AM62X+FPGA+AD+vxworks实时操作系统数据采集处理解决方案

Specification Description 处理器 AM6231 at up to 1.2GHz 操作系统 VXWORKS 存储 DDR4,8GB EMMC 接口 •PrPMC接口 •1个USB2.0 •3路RMII •1路RS485 •1路IRIGB •1路RS232 调试接口 JTAG / COP debug port 工业环境监测设备&#xff1a; Specification Desc…

常见优化器详解

优化器 目前有两种主流优化器&#xff1a;随机梯度下降系&#xff08;Stochastic Gradient Descent, SGD&#xff09;和Adam系。 应该认识到的是&#xff0c;优化器并不是某类数学上的优化算法&#xff0c;而是梯度下降&#xff08;一阶迭代法&#xff09;的工程实现方案和包…

VALSE2023-内容总结(正在更新)

博文为精选内容&#xff0c;完整ppt请留言索取 一周内更新完毕&#xff0c;敬请期待 2023年度视觉与学习青年学者研讨会 (Vision And Learning SEminar, VALSE)于6月10日至12日在无锡太湖国际博览中心召开&#xff0c;由中国人工智能学会、中国图象图形学学会主办&#xff0c;…

Transformer与注意力机制

Transformer与注意力机制 1. RNN基础 循环神经网络&#xff08;RNN&#xff09;是专门用来处理自然语言、金融信息等时序数据的一种神经网络。它的结构和运作方式如下图所示&#xff0c;基于马尔可夫决策模型。 图1 循环神经网络的结构和运作方式 应该注意到以下几个性质&a…

周四见 | 物流人的一周资讯

菜鸟618推出“小件3元发全国”服务 6月12日消息&#xff0c;针对美妆商家618期间轻小件的发货需求&#xff0c;菜鸟推出“极致小件3元发全国”服务&#xff0c;利用全国6大基地&#xff0c;100%自营的仓配供应链能力&#xff0c;支持订单高爆发&#xff0c;并提供破损包赔、时…