《JSR303参数校验》

一、基础概述

1.简介

Java API 规范 (JSR303) 定义了 Bean 校验的标准 validation-api,但没有提供实现。hibernate validation 是对这个规范的实现,并增加了校验注解如 @Email、@Length 等。Spring Validation 是对 hibernate validation 的二次封装,用于支持 spring mvc 参数自动校验。

2.依赖

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

如果spring-boot 版本小于 2.3.x,spring-boot-starter-web 会自动传入 hibernate-validator依赖。如果 spring-boot 版本大于 2.3.x,则需要手动引入依赖
在这里插入图片描述

二、参数效验

对于 web 服务来说,为防止非法参数对业务造成影响,在 Controller 层一定要做参数校验的!大部分情况下,请求参数分为如下两种形式:

POSTPUT 请求,使用@RequestBody传递参数
GET 请求,使用 @RequestParam / @PathVariable 传递参数

1.@RequestParam参数校验

@RestController
// 代表需要参数验证,一定要加
@Validated
public class HelloController {

    // @Min代表参数不能小于10
    @RequestMapping(value = "/test1")
    public Object m1(@RequestParam(value = "number") @Min(value = 10) Integer number) {
        System.out.println(number);
        return UUID.randomUUID() + "----" + number;
    }

}

【测试】

http://localhost:8081/test1?number=9 参数为9即报错,异常为ConstraintViolationException

http://localhost:8081/test1?number=12 参数为12即正常

2.@PathVariable参数效验

@RestController
@Validated
public class HelloController {

    @RequestMapping(value = "/test2/{number}")
    public Object m2(@PathVariable(value = "number") @Max(value = 20) String number) {
        System.out.println(number);
        return UUID.randomUUID() + "----" + number;
    }

}

3.@RequestBody参数效验

在实体类上生命效验字段

package com.h3c.entity;

import lombok.Data;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotNull;

@Data
public class UserParam {

    @NotNull
    private String userName;

    @Length(min = 6, max = 20, message = "长度范围为6~20")
    private String account;

    @Length(min = 6, max = 20)
    private String password;

}

在方法参数上声明校验注解@Validated

@RestController
public class HelloController {

    // 需要设置声明,@Valid和@Validated都可以
    @PostMapping(value = "/test3")
    public Object m3(@RequestBody @Validated UserParam param) {
        System.out.println(param);
        return UUID.randomUUID() + "----" + param;
    }
}

参数错误会报异常MethodArgumentNotValidException

4.全局异常处理

前面说过,如果校验失败,会抛出MethodArgumentNotValidException 或者ConstraintViolationException异常。在实际项目开发中,通常会用统一异常处理来返回一个更友好的提示。

package com.h3c.exception;

import com.h3c.entity.Result;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.validation.ConstraintViolationException;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

@RestControllerAdvice
public class SystemExceptionHandler {
	
    // MethodArgumentNotValidException异常可以获取到异常字段
    @ExceptionHandler({MethodArgumentNotValidException.class})
    public Result handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
        ex.printStackTrace();
        // 获取所有的错误字段
        List<FieldError> errors = ex.getBindingResult().getFieldErrors();
        Map<String, String> map = new LinkedHashMap<>();
        // 把错误信息存入一个map,然后返回
        errors.forEach(item -> {
            map.put(item.getField(), item.getDefaultMessage());
        });
        return Result.error(map);
    }

    @ExceptionHandler({ConstraintViolationException.class})
    public Result handleConstraintViolationException(ConstraintViolationException ex) {
        ex.printStackTrace();
        return Result.error(ex.getMessage());
    }
}

【POST请求参数异常返回示例】
在这里插入图片描述

【GET请求参数异常返回示例】

{"flag":false,"code":500,"message":"m1.number: 最小不能小于10","data":null}

三、效验注解

https://www.cnblogs.com/jinzlblog/p/16635043.html 注解

注解用法适用类型
@Null被注解的字段必须为空
@NotNull被注解的字段必须不为空
@NotBlank带注解的元素不能为null,并且必须至少包含一个非空白字符
@NotEmpty带注解的元素不能为null也不能为空String(长度)集合(大小)数组(长度)
@AssertTrue检查该字段必须为TrueBoolean
@AssertFalse检查该字段必须为FalseBoolean
@Min(value)被注解的字段必须大于等于指定的最小值
@Max(value)被注解的字段必须小于等于指定的最大值
@Negative带注解的元素必须是严格的负数(0被认为是无效值)BigDecimal,BigInteger,byte,short,int,long及其包装类
@NegativeOrZero带注解的元素必须是严格的负数或0BigDecimal,BigInteger,byte,short,int,long及其包装类
@Positive带注解的元素必须是严格的正数(0被认为是无效值)BigDecimal,BigInteger,byte,short,int,long及其包装类
@PositiveOrZero带注解的元素必须是严格的正数或0BigDecimal,BigInteger,byte,short,int,long及其包装类
@DecimalMin被注解的字段必须大于等于指定的最小值BigDecimal,BigInteger,byte,short,int,long及其包装类
@DecimalMax被注解的字段必须小于等于指定的最大值BigDecimal,BigInteger,byte,short,int,long及其包装类
@Size(min=,max=)被注解的字段的size必须在min和max之间,不需要判空字符串、数组、集合
@Digits(integer, fraction)被注解的字段必须在指定范围内,整数部分长度小于integer,小数部分长度小于fraction字符串、数组、集合
@Past被注解的字段必须是一个过去的日期时间
@PastOrPresent被注解的字段必须是过去的或现在的日期时间
@Future被注解的字段必须是一个将来的日期时间
@FutureOrPresent被注解的字段必须是现在的或将来的日期时间
@Email字符串必须是格式正确的电子邮件地址String
@Pattern(value)被注解的字段必须符合指定的正则表达式

四、高级使用

1.分组校验

在实际项目中,可能多个接口需要使用同一个 DTO 类来接收参数,而不同方法的校验规则很可能是不一样的。这个时候,简单地在 DTO 类的字段上加约束注解无法解决这个问题。因此,spring-validation支持了分组校验的功能,专门用来解决这类问题。

还是上面的例子,比如保存 User 的时候,UserId 是可空的,但是更新 User 的时候,UserId 的值必须存在,其它字段的校验规则在两种情况下一样。这个时候就需要使用分组校验

简单点说:就是根据设置的条件来执行参数校验,其实就是一个判断,筛选设置了分组的参数进行校验,不过在这里叫做分组

【声明分组】

// 保存的时候校验分组
public interface InsertValidGroup {
}

// 更新的时候校验分组
public interface UpdateValidGroup {
}

【实体类】

需要注意的是,这里面只有参数Id设置了分组,其它参数没有设置则不会进行校验

@Data
public class UserParam {

    // 代表在属于更新的时候,校验id
    @NotNull(groups = UpdateValidGroup.class)
    private Integer id;

    @NotNull
    private String userName;

    @Length(min = 6, max = 20, message = "长度范围为6~20")
    private String account;

    @Length(min = 6, max = 20)
    private String password;

}

【接口】

@RestController
@Validated
public class HelloController {

    @PostMapping(value = "/test3")
    public Object m3(@RequestBody @Validated(UpdateValidGroup.class) UserParam param) {
        System.out.println(param);
        return UUID.randomUUID() + "----" + param;
    }

}

【测试】

请求体id为空,则出现异常,请求体id存在,则正常执行
在这里插入图片描述

【其余参数设置分组】

如上所示只有设置了分组的参数才会校验,那么其它的参数怎么办呢,只能挨个写

@Data
public class UserParam {

    @NotNull(groups = UpdateValidGroup.class)
    private Integer id;

    // 其余的参数把全部的分组都设置上
    @NotNull(groups = {UpdateValidGroup.class, InsertValidGroup.class})
    private String userName;

    @NotNull(groups = {UpdateValidGroup.class, InsertValidGroup.class})
    @Length(min = 6, max = 20, message = "长度范围为6~20")
    private String account;

    @NotNull(groups = {UpdateValidGroup.class, InsertValidGroup.class})
    @Length(min = 6, max = 20)
    private String password;
}

2.嵌套校验

当我们实体类中某个字段是对象,这种情况下,可以使用嵌套校验

@Data
public class UserParam {

    @NotNull(groups = UpdateValidGroup.class)
    private Integer id;

    @NotNull(groups = {UpdateValidGroup.class, InsertValidGroup.class})
    private String userName;

    @NotNull(groups = {UpdateValidGroup.class, InsertValidGroup.class})
    @Length(min = 6, max = 20, message = "长度范围为6~20")
    private String account;

    @NotNull(groups = {UpdateValidGroup.class, InsertValidGroup.class})
    @Length(min = 6, max = 20)
    private String password;

    // 可以针对对象参数校验
    @NotNull(groups = {UpdateValidGroup.class, InsertValidGroup.class})
    @Valid
    private Job job;

    // 可以针对集合对象参数校验
    @NotNull(groups = {UpdateValidGroup.class, InsertValidGroup.class})
    @Valid
    private List<Job> jobs;

    @Data
    public static class Job {

        @NotNull(groups = UpdateValidGroup.class)
        @Min(value = 10, groups = UpdateValidGroup.class)
        private Long jobId;

        @NotNull(groups = {UpdateValidGroup.class, InsertValidGroup.class})
        private String jobName;

        @NotNull(groups = {UpdateValidGroup.class, InsertValidGroup.class})
        private String position;
    }

}

【接口情况】
在这里插入图片描述

3.集合校验

【参数类】

@Data
public class ValidList {

    // 集合校验
    @Valid
    @NotNull
    @Size(min = 3, max = 10)
    private List<String> list;

}

【接口】

@RestController
@Validated
public class HelloController {

    @PostMapping(value = "/test4")
    public Object m4(@RequestBody @Validated ValidList list) {
        System.out.println(list);
        return UUID.randomUUID() + "----" + list;
    }

}

【测试】
在这里插入图片描述

4.自定义校验

业务需求总是比框架提供的这些简单校验要复杂的多,我们可以自定义校验来满足我们的需求。

例如性别参数只能是0或者1,某个字段必须是{“aa”,“bb”,"cc}中的一个

【自定义注解】

@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@Documented
// 这里需要注意标明校验类
@Constraint(validatedBy = {ProcessValidator.class})
public @interface CheckValid {

    String message() default "数据错误";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

}

【校验实现类】

需要实现ConstraintValidator接口,然后重写isValid(),验证该参数必须属于集合内

public class ProcessValidator implements ConstraintValidator<CheckValid, String> {

    private List<String> list = Arrays.asList("aa", "bb", "cc");

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if (value != null) {
            if (list.contains(value)) {
                return true;
            }
        }
        return false;
    }

}

【实体类参数】

@Data
public class ForumParam {
	
    // 通过正则表达式验证
    @NotNull
    @Pattern(regexp = "^(男|女){1}$")
    private String sex;

    // 通过自定义注解校验
    @NotNull
    @CheckValid
    private String pms;

}

【接口】

@RestController
@Validated
public class HelloController {

    @PostMapping(value = "/test4")
    public Object m4(@RequestBody @Validated ForumParam param) {
        System.out.println(param);
        return UUID.randomUUID() + "----" + param;
    }

}

【测试】
在这里插入图片描述

5.编程式校验

上面的示例都是基于注解来实现自动校验的,在某些情况下,我们可能希望以编程方式调用验证。这个时候可以注入 javax.validation.Validator 对象,然后再调用其 api。

@Autowired
private javax.validation.Validator globalValidator;

// 编程式校验
@PostMapping(value = "/test5")
public Object m5(@RequestBody UserParam param) {
    Set<ConstraintViolation<UserParam>> set = validator.validate(param, UpdateValidGroup.class);
    // 如果校验通过,set;否则,set包含未校验通过项
    if (set.isEmpty()) {
        // 校验通过,才会执行业务逻辑处理
    } else {
        // 遍历出现异常的字段
        for (ConstraintViolation<UserParam> violation : set) {
            System.out.println(violation.getPropertyPath() + "---" + violation.getMessage());
        }
    }

    System.out.println(param);
    return UUID.randomUUID() + "----" + param;
}

6.快速失败

Spring Validation 默认会校验完所有字段,然后才抛出异常。可以通过一些简单的配置,开启 Fali Fast 模式,一旦校验失败就立即返回。

package com.h3c.config;

import org.hibernate.validator.HibernateValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;

/**
 * @version JDK11
 * @author: wys4822
 * @date: 2022年09月07日
 */
@Configuration
public class ValidConfig {

    @Bean
    public Validator validator() {
        ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
                .configure()
                // 快速失败模式
                .failFast(true)
                .buildValidatorFactory();
        return validatorFactory.getValidator();
    }

}

【测试】
在这里插入图片描述

7.@Valid 和 @Validated 区别

image-20210525021620127

五、源码分析

1.@RequestBody参数校验实现原理

a>RequestResponseBodyMethodProcessor

Spring-MVC框架中,RequestResponseBodyMethodProcessor是用于解析 @RequestBody标注的参数以及处理 @ResponseBody 标注方法的返回值的。显然,执行参数校验的逻辑肯定就在解析参数的方法 resolveArgument() 中:

public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {
	
    @Override
	public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

		parameter = parameter.nestedIfOptional();
         // 根据请求体转换参数
		Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
		String name = Conventions.getVariableNameForParameter(parameter);

		if (binderFactory != null) {
			WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
			if (arg != null) {
                 // 执行参数校验
				validateIfApplicable(binder, parameter);
				if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
					throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
				}
			}
			if (mavContainer != null) {
				mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
			}
		}

		return adaptArgumentIfNecessary(arg, parameter);
	}
}

b>validateIfApplicable()

protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {
   // 获取参数前面设置的注解,注解中肯定会包含@RequestBody
   Annotation[] annotations = parameter.getParameterAnnotations();
   for (Annotation ann : annotations) {
      // 判断是否存在@Validated,通过一个工具类来实现
      Object[] validationHints = ValidationAnnotationUtils.determineValidationHints(ann);
      if (validationHints != null) {
         binder.validate(validationHints);
         break;
      }
   }
}

在这里插入图片描述

c>determineValidationHints()

需要声明的是,在接口参数前面增加校验注解,注解可以为@Validated或者@Valid,2个都可以

@Valid注解判断存在即可通过判断,而@Validated注解判断存在后,还需要尝试获取里面的分组校验

public abstract class ValidationAnnotationUtils {

   private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];

   @Nullable
   public static Object[] determineValidationHints(Annotation ann) {
      Class<? extends Annotation> annotationType = ann.annotationType();
      String annotationName = annotationType.getName();
      // 通过注解路径判断是否为@Valid,原因是因为该注解有很多重名的
      if ("javax.validation.Valid".equals(annotationName)) {
         return EMPTY_OBJECT_ARRAY;
      }
      // 判断@Validated注解
      Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
      // 因为@Validated可以设置分组校验,所以这里需要获取value,封装成数组返回
      if (validatedAnn != null) {
         Object hints = validatedAnn.value();
         return convertValidationHints(hints);
      }
      if (annotationType.getSimpleName().startsWith("Valid")) {
         Object hints = AnnotationUtils.getValue(ann);
         return convertValidationHints(hints);
      }
      return null;
   }

   private static Object[] convertValidationHints(@Nullable Object hints) {
      if (hints == null) {
         return EMPTY_OBJECT_ARRAY;
      }
      return (hints instanceof Object[] ? (Object[]) hints : new Object[]{hints});
   }

}

d>validate()

上面验证了参数是否标注了@Validated或者@Valid,代表该参数需要执行校验逻辑,那么接下来肯定就是遍历字段校验了,那么该validate()在上面的validateIfApplicable被调用

public void validate(Object... validationHints) {
   // 获取到参数对象
   Object target = getTarget();
   // 参数为空,就报错了
   Assert.state(target != null, "No target to validate");
   // 获取默认的绑定结果
   BindingResult bindingResult = getBindingResult();
   // 遍历校验
   for (Validator validator : getValidators()) {
      if (!ObjectUtils.isEmpty(validationHints) && validator instanceof SmartValidator) {
         // 开启核心的校验
         ((SmartValidator) validator).validate(target, bindingResult, validationHints);
      }
      else if (validator != null) {
         validator.validate(target, bindingResult);
      }
   }
}

2.@RequestParam参数校验实现原理


{
   // 获取到参数对象
   Object target = getTarget();
   // 参数为空,就报错了
   Assert.state(target != null, "No target to validate");
   // 获取默认的绑定结果
   BindingResult bindingResult = getBindingResult();
   // 遍历校验
   for (Validator validator : getValidators()) {
      if (!ObjectUtils.isEmpty(validationHints) && validator instanceof SmartValidator) {
         // 开启核心的校验
         ((SmartValidator) validator).validate(target, bindingResult, validationHints);
      }
      else if (validator != null) {
         validator.validate(target, bindingResult);
      }
   }
}

2.@RequestParam参数校验实现原理

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

用邮件及时获取变更的公网IP--------python爬虫+打包成exe文件

参考获取PC机公网IP并发送至邮箱 零、找一个发送邮件的邮箱 本文用QQ邮箱为发送邮箱&#xff0c;网易等邮箱一般也有这个功能&#xff0c;代码也是通用的。 第一步&#xff1a;在设置中找到账户&#xff0c;找到POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服务&#xff0c;点击获…

基于springboot+vue零食商城管理系统

摘要 基于Spring Boot Vue的零食商城管理系统是一项集成了先进技术的电商解决方案。此系统以Spring Boot为后端框架&#xff0c;结合Vue.js构建前端&#xff0c;致力于实现零食商城的高效管理和用户友好的界面体验。首先&#xff0c;Spring Boot框架作为后端核心&#xff0c;为…

12月,全国各地电子签推广应用政策汇总

12月&#xff0c;国务院及各地政府办公厅、市监局、住建委等机关部门&#xff0c;持续推动电子印章、电子合同等功能在“政府采购、工程项目审批、企业开办等”领域深化应用&#xff0c;加快实现电子签章互信互认&#xff0c;不断简化办事流程&#xff0c;让越来越多高频常办事…

2_并发编程同步锁(synchronized)

并发编程带来的安全性同步锁(synchronized) 1.他的背景 当多个线程同时访问&#xff0c;公共共享资源的时候&#xff0c;这时候就会出现线程安全&#xff0c;代码如&#xff1a; public class AtomicDemo {int i0;//排他锁、互斥锁public void incr(){ //synchronizedi; …

动手学深度学习一:环境安装与数据学习

2024&#xff0c;重新开始深度学习。 第一步&#xff1a;李沐动手学深度学习 课程网址&#xff1a;https://courses.d2l.ai/zh-v2/ 包含教材和视频网址链接 Jupyter notebook安装 目前在本地先使用cpu版本pytorch&#xff0c;我的本地已经安装好conda&#xff0c;跟着教材创建…

Vue 中的 ref 与 reactive:让你的应用更具响应性(中)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

Spring高手之路-Spring中Bean的五大作用域

目录 Singleton&#xff08;单例&#xff09;&#xff1a;默认的作用域 Prototype&#xff08;原型&#xff09; Request&#xff08;请求&#xff09; Session&#xff08;会话&#xff09; Global Session&#xff08;全局会话&#xff09; 五大作用域范围对比 作用域…

Jenkins持续集成(下篇)

&#xff08;四&#xff09;Jenkins 配置构建执行状态 运行构建 在项目 左侧列表点击 “立即构建” &#xff0c;在 “Build History” 列表&#xff0c;你会得到一个红色的小圆点&#xff0c;表示构建失败。 点击 构建失败的任务&#xff08;红色的小圆点&#xff09;。然后点…

Python用selenium实现自动登录和下单的项目实战

前言 学python对selenium应该不陌生吧 Selenium 是最广泛使用的开源 Web UI&#xff08;用户界面&#xff09;自动化测试套件之一。Selenium 支持的语言包括C#&#xff0c;Java&#xff0c;Perl&#xff0c;PHP&#xff0c;Python 和 Ruby。目前&#xff0c;Selenium Web 驱动…

Java 语言概述

Java 概述 是 SUN&#xff08;Stanford University Network&#xff0c;斯坦福大学网络公司&#xff09;1995年推出的一门高级编程语言 是一种面向 Internet 的编程语言。Java 一开始富有吸引力是因为 Java 程序可以在 Web 浏览器中运行。这些 Java 程序被称为 Java 小程序&am…

专题一_双指针(一)

文章目录 283.移动零题目解析讲解算法原理扩展编写代码 1089.复习零题目解析讲解算法原理编写代码 202.快乐数题目解析讲解算法原理证明编写代码 11.盛最多水的容器题目解析讲解算法原理暴力解法优秀的解法时间复杂度分析 编写代码 283.移动零 题目链接 题目解析 题目还是比较…

成为一名合格的前端架构师,前端知识技能与项目实战教学

一、教程描述 本套前端架构师教程&#xff0c;大小35.94G&#xff0c;共有672个文件。 二、教程目录 01.node介绍和环境配置&#xff08;共6课时&#xff09; 02.ES6语法&#xff08;共5课时&#xff09; 03.node基础&#xff08;共29课时&#xff09; 04.Express框架&am…

大数据Doris(四十九):Doris数据导出介绍

文章目录 Doris数据导出介绍 一、​​​​​​​使用示例

Hive生产调优介绍

1.Fetch抓取 Fetch抓取是指&#xff0c;Hive中对某些情况的查询可以不必使用MapReduce计算。例如&#xff1a;SELECT * FROM employees;在这种情况下&#xff0c;Hive可以简单地读取employee对应的存储目录下的文件&#xff0c;然后输出查询结果到控制台。 在hive-default.xml…

SpingBoot的项目实战--模拟电商【4.订单及订单详情的生成】

&#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 接下来看看由辉辉所写的关于SpringBoot电商项目的相关操作吧 目录 &#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 一.功能需求 二.代码编写 …

2023最大技术潮:大模型冲击下的智能汽车

作者 |德新 编辑 |王博 过去这年最大的技术潮&#xff0c;非大模型莫属。 2023年初&#xff0c;由ChatGPT掀起的浪花&#xff0c;迅速地演变成了席卷全球的AI科技浪潮。汽车行业在其中也不可避免。各大车企纷纷投入与大模型相关的布局。 长城官宣成立了AI Lab&#xff0c;到…

Python从入门到精通之元类

系列 Python从入门到精通之安装与快速入门-CSDN博客 Python从入门到精通之基本数据类型和变量-CSDN博客 Python从入门到精通之集合&#xff08;List列表、Tuple元组、Dict字典、Set&#xff09;-CSDN博客 Python从入门到精通之条件语句、循环语句和函数-CSDN博客 Python从…

Hi5 2.0 虚拟手与追踪器(Tracker)的位置修正

问题描述 使用环境与工具&#xff1a;Unity 2022.3.4fc1&#xff0c;steam VR(2.7.3)&#xff0c;steamvrSDK&#xff08;1.14.15&#xff09;&#xff0c;HTC vive pro专业版&#xff0c;Hi5 2.0数据手套 首先按照Hi5 2.0的使用说明&#xff08;可参考&#xff1a;HI5 2.0 交…

Character Controller Smooth

流畅的角色控制器 Unity的FPS解决方案&#xff01; 它是一种具有非常平滑运动和多种设置的解决方案&#xff1a; - 移动和跳跃 - 坐的能力 - 侧翻角度 - 不平整表面的处理 - 惯性守恒 - 重力 - 与物理物体的碰撞。 - 支持没有家长控制的平台 此解决方案适用于那些需要角色控制器…

蓝桥圣诞树(C++)

问题描述 输入样例&#xff1a; 1 3 101 1 2 2 3 输出样例&#xff1a; YES 思路&#xff1a; 这道题还是比较好想的&#xff0c;因为它构造的二叉树是用边连接起来的&#xff0c;不是像之前一样从上到下从左到右按编号构造的&#xff0c;所以可以用邻接表来存每个点还有边&am…