百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术资源 > 正文

HibernatorValidator校验框架的应用

lipiwang 2024-10-22 15:53 7 浏览 0 评论



  • 一、背景
  • 二、是什么?
  • 三、为什么使用?
  • 四、怎么集成?
  • 五、怎么使用?

一、背景

我们验证数据是一项常见任务,从表示层到持久层的所有应用程序层。通常,在每个层中都实现相同的验证逻辑,这既耗时又容易出错。

为了避免重复这些验证,开发人员通常将验证逻辑直接捆绑到领域模型中,从而使领域类与验证代码杂乱无章,而验证代码实际上是有关类本身的元数据。Jakarta Bean Validation 2.0 用于定义实体和方法验证的元数据模型和API。

默认的元数据源是注释,能够通过使用XML覆盖和扩展元数据。API不受特定应用程序层或编程模型的约束。它特别不与Web层或持久层绑定在一起,并且可用于服务器端应用程序编程以及富客户端Swing应用程序开发人员。

二、是什么?

hibernate-validator 与持久层框hibernate 没有什么关系,hibernate-validator是 hibernate 组织下的一个开源项目 。

hibernate-validator 是 JSR 380(Bean Validation 2.0)、JSR 303(Bean Validation 1.0)规范的实现。

JSR 380 - Bean Validation 2.0 定义了一个实体和方法验证的元数据模型和 API。

JavaEE(改名为:Jakarta EE)中制定了validation规范,即:javax.validation-api(现为 jakarta.validation-api.jar 包的名字改变,包里面的包名、类名未变,因此使用方式不变)包,spring-boot-starter-web、spring-boot-starter-webflux 包都已引入此依赖,直接使用即可。

有点类似于slf4j 与 logback(log4j2)的关系,使用的时候,代码中使用 javax.validate 提供的接口规范功能,加载的时候,根据 SPI 规范加载对应的规范实现类。

它和hibernate 没什么关系,可以放心大胆的使用。

三、为什么使用?

hibernate-validator 官方有如下说明。

使用hibernate-validator以前的校验如下:



使用 hibernate-validator 后,校验逻辑如下:


controller、service、dao 层相同的校验逻辑都可以使用同一个数据校验模型。

四、怎么集成?

1、如果是springboot,引入下面的依赖即可

spring-boot-starter-web有继承hibernate-validator,spring-boot-starter-web已引入此依赖,直接使用即可。

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

2、如果不是springboot,引入如下依赖

<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>6.0.7.Final</version>
</dependency>

3、Hibernate-validator配置类引入

/**
 * HibernateValidator 校验配置
 * @author zy
 * @version $Id: HibernateValidatorConfig.java,v 0.1 2020年05月11日 14:43 $Exp
 */
@Configuration
public class HibernateValidatorConfig {
 
    /**
     * hibernate的校验模式
     * 1、普通模式(默认是这个模式)
     *   普通模式(会校验完所有的属性,然后返回所有的验证失败信息)。
     *     但是在使用组序列验证的时候,如果序列前边的组验证失败,则后面的组将不再给予验证。
     *
     * 2、快速失败返回模式
     *   快速失败返回模式(只要有一个验证失败,则返回)
     *      如果需要配置为快速失败返回,则编写配置文件
     *      (hibernate.validator.fail_fast:true 快速失败返回模式 false 普通模式   .addProperty( "hibernate.validator.fail_fast", "true" ))
     *       failFast:true  快速失败返回模式    false 普通模式
     */
    @Bean
    public Validator validator(){
        ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
                .configure()
                .failFast(true)
                .buildValidatorFactory();
        return validatorFactory.getValidator();
    }
}

4、增加springboot处理HibernateValidator异常的全局异常拦截器

异常类型

描述

ConstraintViolationException

违反约束,javax扩展定义

BindException

绑定失败,如表单对象参数违反约束

MethodArgumentNotValidException

参数无效,如JSON请求参数违反约束

MissingServletRequestParameterException

参数缺失

TypeMismatchException

参数类型不匹配

@RestControllerAdvice
public class CustomExceptionHandler extends ResponseEntityExceptionHandler {
 
    /**
     * 这个是公用异常,可以不拦截
     * @param e 异常
     * @return 返回
     */
    @ExceptionHandler(value = {Exception.class})
    public ResponseEntity<Object> handleException(Exception e) {
        return new ResponseEntity<>(Response.failOfMessage(e.getMessage()), HttpStatus.BAD_REQUEST);
    }
 
    /**
     * ConstraintViolationException
     * 违反约束,javax扩展定义
     *
     * @param e 异常
     * @return 返回
     */
    @ExceptionHandler(value = {ConstraintViolationException.class})
    public ResponseEntity<Object> handleConstraintViolationException(ConstraintViolationException e) {
        Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
        if(CollectionUtils.isEmpty(violations)){
            return new ResponseEntity<>(Response.failOfMessage(e.getMessage()), HttpStatus.BAD_REQUEST);
        }
        ConstraintViolation cv = violations.iterator().next();
        String fieldName = cv.getPropertyPath().toString();
        String fieldErrMsg = cv.getMessage();
        String result = String.format("【%s】%s", fieldName, fieldErrMsg);
        return new ResponseEntity<>(Response.failOfMessage(result), HttpStatus.BAD_REQUEST);
    }
 
    /**
     * BindException
     * 处理绑定失败异常,如表单对象参数违反约束
     *  fieldError.getField() 读取参数字段
     *  fieldError.getDefaultMessage() 读取验证注解中的message值
     *
     * @param ex 异常
     * @param headers 请求头
     * @param status 请求状态
     * @param request 请求体
     * @return 返回
     */
    @Override
    protected ResponseEntity<Object> handleBindException(BindException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
        return new ResponseEntity<>(this.buildErrorMessage(ex.getBindingResult()), status);
    }
 
    /**
     * MethodArgumentNotValidException
     * 参数无效,如JSON请求参数违反约束
     *
     * @param ex 异常
     * @param headers 请求头
     * @param status 请求状态
     * @param request 请求体
     * @return 返回
     */
    @Override
    protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
        return new ResponseEntity<>(this.buildErrorMessage(ex.getBindingResult()), status);
    }
 
    /**
     * MissingServletRequestParameter
     * 参数缺失
     *
     * @param ex 异常
     * @param headers 请求头
     * @param status 请求状态
     * @param request 请求体
     * @return 返回
     */
    @Override
    public ResponseEntity<Object> handleMissingServletRequestParameter(MissingServletRequestParameterException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
        return new ResponseEntity<>(Response.failOfMessage(ex.getMessage()), status);
    }
 
    /**
     * TypeMismatch
     * 参数类型不匹配
     *
     * @param ex 异常
     * @param headers 请求头
     * @param status 请求状态
     * @param request 请求体
     * @return 返回
     */
    @Override
    protected ResponseEntity<Object> handleTypeMismatch(TypeMismatchException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
        return new ResponseEntity<>(Response.failOfMessage(ex.getMessage()), status);
    }
 
    /**
     * 构建处理属性校验错误
     * @param bindingResult 属性校验错误信息
     * @return 返回处理结果
     */
    private Response buildErrorMessage(BindingResult bindingResult) {
        FieldError fieldError = bindingResult.getFieldError();
        if (null == fieldError) {
            return Response.failOfMessage("参数非法!");
        }
        String fieldName = fieldError.getField();
        String fieldErrMsg = fieldError.getDefaultMessage();
        return Response.failOfMessage(String.format("【%s】%s", fieldName, fieldErrMsg));
    }
}

五、怎么使用?

1、内置校验注解列表如下

Bean Validation 中内置的 constraint

注解

释义

@Valid

被注释的元素是一个对象,需要检查此对象的所有字段值

@Null

被注释的元素必须为 null

@NotNull

被注释的元素必须不为 null

@AssertTrue

被注释的元素必须为 true

@AssertFalse

被注释的元素必须为 false

@Min(value)

被注释的元素必须是一个数字,其值必须大于等于指定的最小值

@Max(value)

被注释的元素必须是一个数字,其值必须小于等于指定的最大值

@DecimalMin(value)

被注释的元素必须是一个数字,其值必须大于等于指定的最小值

@DecimalMax(value)

被注释的元素必须是一个数字,其值必须小于等于指定的最大值

@Size(max, min)

被注释的元素的大小必须在指定的范围内

@Digits (integer, fraction)

被注释的元素必须是一个数字,其值必须在可接受的范围内

@Past

被注释的元素必须是一个过去的日期

@PastOrPresent

被注释的元素必须是一个现在或者过去的日期

@Future

被注释的元素必须是一个将来的日期

@FutureOrPresent

被注释的元素必须是一个现在或者将来的日期

@Pattern(regex=, flags=)

被注释的元素必须符合指定的正则表达式

@Negative

被注释的元素必须必须为负数

@NegativeOrZero

被注释的元素必须必须为负数或者为0

@Positive

被注释的元素必须必须为正数(不包含0)

@PositiveOrZero

被注释的元素必须必须为正数或者是0


Hibernate Validator 附加的 constraint(最后3个不常用,可忽略)

注解

释义

@Email

被注释的元素必须是电子邮箱地址

@Length(min=, max=)

被注释的字符串的大小必须在指定的范围内

@NotEmpty

被注释的字符串的必须非空

@Range(min=, max=)

被注释的元素必须在合适的范围内

@NotBlank

被注释的字符串的必须非空

@URL(protocol=,host=,port=,regexp=,flags=)

被注释的字符串必须是一个有效的url

@CreditCardNumber

被注释的字符串必须通过Luhn校验算法,银行卡,信用卡等号码一般都用Luhn计算合法性

@ScriptAssert(lang=, script=, alias=)

要有Java Scripting API 即JSR 223 ("Scripting for the JavaTM Platform")的实现

@SafeHtml(whitelistType=, additionalTags=)

classpath中要有jsoup包


主要区分下@NotNull @NotEmpty @NotBlank 3个注解的区别:

@NotNull

任何对象的value不能为null

@NotEmpty

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

@NotBlank

只能用于字符串不为null,并且字符串trim()以后length要大于0

2、使用示例

2.1、Validator接口

@Autowired
private Validator validator;
 
@Override
public Response<PageInfo<ProductDto>> getProductDto(ProductVo productVo) {
   try {
      //1-入参校验
      Set<ConstraintViolation<ProductVo>> violations = validator.validate(productVo, Default.class);
      if(CollectionUtils.isNotEmpty(violations)){
         ConstraintViolation cv = violations.iterator().next();
         return Response.fail(ErrorCodeScenarioEnum.CHOICE_ACTIVITY.getCode(), String.format("【%s】%s", cv.getPropertyPath(), cv.getMessage()));
      }
      ...
}

2.2、Validator中的方法

public interface Validator {
 
    /**
     * 用途:校验一个对象的指定的一个或多个校验组的属性。
     */
    <T> Set<ConstraintViolation<T>> validate(T object, Class<?>... groups);
 
    /**
     * 用途:校验一个对象指定校验组中的一个指定的属性值。
     */
    <T> Set<ConstraintViolation<T>> validateProperty(T object,
                                                     String propertyName,
                                                     Class<?>... groups);
 
    /**
     * 用途:校验一个value是否符合指定类的指定校验组下的某一个属性值。
     */
    <T> Set<ConstraintViolation<T>> validateValue(Class<T> beanType,
                                                  String propertyName,
                                                  Object value,
                                                  Class<?>... groups);
 
    /**
     *
     */
    BeanDescriptor getConstraintsForClass(Class<?> clazz);
 
    /**
     *
     */
    <T> T unwrap(Class<T> type);
 
    /**
     *
     */
    ExecutableValidator forExecutables();
}

2.3、声明 Java Bean 约束

(1)字段级别的约束

@AssertTrue(message = "用户 isRich 必须为 true")
private Boolean isRich;

(2)属性级别约束

@NotBlank
public String getManufacturer(){
   return manufacturer;
}

(3)容器级别约束

private Map<@NotNull FuelConsumption, @MaxAllowedFuelConsumption Integer> fuelConsumption = new HashMap<>();

4)类级别约束

在这种情况下,验证的对象不是单个属性,而是完整的对象。如果验证依赖于对象的多个属性之间的相关性,则类级约束非常有用。
如:密码修改场景中,新旧密码需要一致才能校验通过。

@Data
@PasswordEquals(message = "新旧密码不一致,请检查。")
public class RegisterFormDTO {
    @NotNull
    @Length(min = 5, max = 30)
    private String username;
 
    @NotNull
    private String password;
 
    @NotNull
    private String passwordConfirm;
}

(5)约束继承
当一个类继承/实现另一个类时,父类声明的所有约束也会应用在子类继承的对应属性上。
如果方法重写,约束注解将会聚合,也就是此方法父类和子类声明的约束都会起作用。

(6)级联验证
Bean Validation API 不仅允许验证单个类实例,也支持级联验证。
只需使用 @Valid 修饰对象属性的引用,则对象属性中声明的所有约束也会起作用。
如以下示例,当验证 userDTO 实例时,addressDTO 对象中的 addressName 字段也会验证。

@AllArgsConstructor
@NoArgsConstructor
@Builder
@Data
public class UserDTO implements Serializable {
    @AssertTrue(message = "用户 isRich 必须为 true")
    private Boolean isRich;
 
    @NotNull(message = "用户地址不能为空")
    @Size(min = 1, message = "用户地址不能为空")
    @Valid
    private List<AddressDTO> addressDTOList;
}
 
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Data
public class AddressDTO {
   
    @NotBlank(message = "地址名称不能为空")
    private String addressName;
}

2.4、声明方法约束

(1)参数约束

通过向方法或构造函数的参数添加约束注解来指定方法或构造函数的前置条件,官方示例如下:

public RentalStation(@NotNull String name){}
 
public void rentCar(@NotNull Customer customer,
                          @NotNull @Future Date startDate,
                          @Min(1) int durationInDays){}

(2)返回值约束
通过在方法体上添加约束注解来给方法或构造函数指定后置条件,官方示例如下:

public class RentalStation {
    @ValidRentalStation
    public RentalStation() {
        //...
    }
     
    @NotNull
    @Size(min = 1)
    public List<@NotNull Customer> getCustomers() {
        //...
        return null;
    }
}

此示例指定了三个约束:

  • 任何新创建的 RentalStation 对象都必须满足 @validRentalStation 约束
  • getCustomers() 返回的客户列表不能为空,并且必须至少包含 1 个元素
  • getCustomers() 返回的客户列表不能包含空对象


(3)级联约束
类似于 JavaBeans 属性的级联验证,@Valid 注解可用于标记方法参数和返回值的级联验证。

类似于 javabeans 属性的级联验证,@valid 注释可用于标记可执行参数和级联验证的返回值。当验证用@valid 注释的参数或返回值时,也会验证在参数或返回值对象上声明的约束。
而且,也可用在容器元素中。

public class Garage {
    public boolean checkCars(@NotNull List<@Valid Car> cars) {
        //...
        return false;
    }
}


(4)继承验证
当在继承体系中声明方法约束时,必须了解两个规则:

  • 方法调用方要满足前置条件不能在子类型中得到加强
  • 方法调用方要保证后置条件不能在子类型中被削弱

这些规则是由子类行为概念所决定的:在使用类型 T 的任何地方,也能在不改变程序行为的情况下使用 T 的子类。

很难理解?比如,当两个类分别有一个同名且形参列表相同的方法,而另一个类用一个方法重写/实现上述两个类的同名方法时,这两个父类的同名方法上不能有任何参数约束,因为不管怎样都会与上述规则冲突。

示例(反例)如下:

public interface Vehicle {
  void drive(@Max(75) int speedInMph);
}
 
 
public interface Car {
  void drive(int speedInMph);
}
 
 
//实现如上两个接口
public class RacingCar implements Car, Vehicle {
  @Override
  public void drive(int speedInMph) {
      //...
  }
}

2.5、分组约束

(1)请求组
注意:上述的 22 个约束注解都有 groups 属性。当不指定 groups 时,默认为 Default 分组。
JSR 规范支持手动校验,不直接支持使用注解校验,不过 spring 提供了分组校验注解扩展支持,即:@Validated,参数为 group 类集合

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

(2)分组继承
在某些场景下,需要定义一个组,它包含其它组的约束,可以用分组继承。

public interface RaceCarChecks extends Default {}
@Data
public class SuperCar extends Car {
 
    @AssertTrue(message = "Race car must have a safety belt",groups = RaceCarChecks.class)
    private boolean safetyBelt;
}

3)定义分组序列
默认情况下,不管约束是属于哪个分组,它们的计算是没有特定顺序的,而在某些场景下,控制约束的计算顺序是有用的。
如:先检查汽车的默认约束,再检查汽车的性能约束,最后在开车前,检查驾驶员的实际约束。
可以定义一个接口,并用 @GroupSequence 来定义需要验证的分组的序列。

@GroupSequence({ Default.class, CarChecks.class, DriverChecks.class })
public interface OrderedChecks {}

4)重新定义默认分组序列

@GroupSequence
@GroupSequence 除了定义分组序列外,还允许重新定义指定类的默认分组。为此,只需将@GroupSequence 添加到类中,并在注解中用指定序列的分组替换 Default 默认分组。

@GroupSequence({ RentalChecks.class, CarChecks.class, RentalCar.class })
public class RentalCar extends Car {}

在验证约束时,直接把其当做默认分组方式来验证

@GroupSequenceProvider(注意:此为 hibernate-validator 提供,JSR 规范不支持)

可用于根据对象状态动态地重新定义默认分组序列。
需要做两步:

  • 实现接口:DefaultGroupSequenceProvider
  • 在指定类上使用 @GroupSequenceProvider,并指定 value 为上一步的类
public class RentalCarGroupSequenceProvider implements DefaultGroupSequenceProvider<RentalCar> {
    @Override
    public List<Class<?>> getValidationGroups(RentalCar car) {
        List<Class<?>> defaultGroupSequence = new ArrayList<Class<?>>();
        defaultGroupSequence.add( RentalCar.class );
        if ( car != null && !car.isRented() ) {
            defaultGroupSequence.add( CarChecks.class );
        }
        return defaultGroupSequence;
    }
}
 
@Data
@GroupSequenceProvider(RentalCarGroupSequenceProvider.class)
public class RentalCar extends Car {
    
    @AssertFalse(message = "The car is currently rented out", groups = RentalChecks.class)
    private boolean rented;
}

2.6、分组转换
如果你想把与汽车相关的检查和驾驶员检查一起验证呢?当然,您可以显式地指定验证多个组,但是如果您希望将这些验证作为默认组验证的一部分进行,该怎么办?

在这里@ConvertGroup 开始使用,它允许您在级联验证期间使用与最初请求的组不同的组,在可以使用 @Valid 的任何地方,都能定义分组转换,也可以在同一个元素上定义多个分组转换(@ConvertGroup.List)

必须满足以下限制:

  • @ConvertGroup 只能与 @Valid 结合使用。如果不是,则抛出 ConstraintDeclarationException。
  • 在同一元素上有多个 from 值相同的转换规则是不合法的。在这种情况下,将抛出 ConstraintDeclarationException。
  • from 属性不能引用分组序列。在这种情况下会抛出 ConstraintDeclarationException

警告:规则不是递归执行的。将使用第一个匹配的转换规则,并忽略后续规则。例如,如果一组@ConvertGroup 声明将组 a 链接到 b,将组 b 链接到 c,则组 a 将被转换到 b,而不是 c。

// 当 driver 为 null 时,不会级联验证,使用的是默认分组,当级联验证时,使用的是 DriverChecks 分组
@Valid
@ConvertGroup(from = Default.class, to = DriverChecks.class)
private Driver driver;
// 规则不是递归执行的。将使用第一个匹配的转换规则,并忽略后续规则。例如,如果一组@ConvertGroup 声明将组 a 链接到 b,将组 b 链接到 c,则组 a 将被转换到 b,而不是 c
@Valid
@ConvertGroup(from = Default.class, to = One.class)
@ConvertGroup(from = Two.class, to = One.class)
@ConvertGroup(from = One.class, to = Default.class)
private Driver driver;

3、自定义约束错误消息

3.1 在错误消息中,获取到当前的校验对象的值

@Length(min = 1, max = 2, message = "当前对象的值为${validatedValue}")
private String name;//当前对象的值为Ssss

3.2 在错误消息中,获取到当前的校验注解的参数

@Length(min = 1, max = 2, message = "长度最在{min}和{max}之间!${validatedValue}")
private String name;//长度最在1和2之间!Ssss

3.3 在错误消息中,获取到当前的校验注解的参数对象的方法的返回值

public enum CaseModeEnum {
    UPPER("字母全部大写"),
    LOWER("字母全部小写");
 
    CaseModeEnum(String desc) {
        this.desc = desc;
    }
 
    private String desc;
 
    public String getDesc() {
        return this.desc;
    }
}
 
@CheckCase(value = CaseModeEnum.LOWER, message = "名字必须为${value.getDesc()}")
private String name;//名字必须为字母全部小写

3.4 自定义校验,添加自定义错误信息获取

if ( constraintContext instanceof HibernateConstraintValidatorContext ) {
    constraintContext.unwrap( HibernateConstraintValidatorContext.class ).addMessageParameter( "regexp", "22" );
}
if ( constraintContext instanceof HibernateConstraintValidatorContext ) {
    constraintContext.unwrap( HibernateConstraintValidatorContext.class ).addExpressionVariable("expression","11");
}

相关推荐

前端入门——css 网格轨道详细介绍

上篇前端入门——cssGrid网格基础知识整体大概介绍了cssgrid的基本概念及使用方法,本文将介绍创建网格容器时会发生什么?以及在网格容器上使用行、列属性如何定位元素。在本文中,将介绍:...

Islands Architecture(孤岛架构)在携程新版首页的实践

一、项目背景2022,携程PC版首页终于迎来了首次改版,完成了用户体验与技术栈的全面升级。作为与用户连接的重要入口,旧版PC首页已经陪伴携程走过了22年,承担着重要使命的同时,也遇到了很多问题:维护/...

HTML中script标签中的那些属性

HTML中的<script>标签详解在HTML中,<script>标签用于包含或引用JavaScript代码,是前端开发中不可或缺的一部分。通过合理使用<scrip...

CSS 中各种居中你真的玩明白了么

页面布局中最常见的需求就是元素或者文字居中了,但是根据场景的不同,居中也有简单到复杂各种不同的实现方式,本篇就带大家一起了解下,各种场景下,该如何使用CSS实现居中前言页面布局中最常见的需求就是元...

CSS样式更改——列表、表格和轮廓

上篇文章主要介绍了CSS样式更改篇中的字体设置Font&边框Border设置,这篇文章分享列表、表格和轮廓,一起来看看吧。1.列表List1).列表的类型<ulstyle='list-...

一文吃透 CSS Flex 布局

原文链接:一文吃透CSSFlex布局教学游戏这里有两个小游戏,可用来练习flex布局。塔防游戏送小青蛙回家Flexbox概述Flexbox布局也叫Flex布局,弹性盒子布局。它决定了...

css实现多行文本的展开收起

背景在我们写需求时可能会遇到类似于这样的多行文本展开与收起的场景:那么,如何通过纯css实现这样的效果呢?实现的难点(1)位于多行文本右下角的展开收起按钮。(2)展开和收起两种状态的切换。(3)文本...

css 垂直居中的几种实现方式

前言设计是带有主观色彩的,同样网页设计中的css一样让人摸不头脑。网上列举的实现方式一大把,或许在这里你都看到过,但既然来到这里我希望这篇能让你看有所收获,毕竟这也是前端面试的基础。实现方式备注:...

WordPress固定链接设置

WordPress设置里的最后一项就是固定链接设置,固定链接设置是决定WordPress文章及静态页面URL的重要步骤,从站点的SEO角度来讲也是。固定链接设置决定网站URL,当页面数少的时候,可以一...

面试发愁!吃透 20 道 CSS 核心题,大厂 Offer 轻松拿

前端小伙伴们,是不是一想到面试里的CSS布局题就发愁?写代码时布局总是对不齐,面试官追问兼容性就卡壳,想跳槽却总被“多列等高”“响应式布局”这些问题难住——别担心!从今天起,咱们每天拆解一...

3种CSS清除浮动的方法

今天这篇文章给大家介绍3种CSS清除浮动的方法。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。首先,这里就不讲为什么我们要清楚浮动,反正不清除浮动事多多。下面我就讲3种常用清除浮动的...

2025 年 CSS 终于要支持强大的自定义函数了?

大家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发!1.什么是CSS自定义属性CSS自...

css3属性(transform)的一个css3动画小应用

闲言碎语不多讲,咱们说说css3的transform属性:先上效果:效果说明:当鼠标移到a标签的时候,从右上角滑出二维码。实现方法:HTML代码如下:需要说明的一点是,a链接的跳转需要用javasc...

CSS基础知识(七)CSS背景

一、CSS背景属性1.背景颜色(background-color)属性值:transparent(透明的)或color(颜色)2.背景图片(background-image)属性值:none(没有)...

CSS 水平居中方式二

<divid="parent"><!--定义子级元素--><divid="child">居中布局</div>...

取消回复欢迎 发表评论: