SpringBoot使用注解实现参数校验


参数校验

Spring Boot框架默认使用Hibernate Validator作为校验器实现。参数校验使用JSR-303规范定义的校验注解来实现的。

实际上,在Spring项目中,参数校验并仅支持用在Controller层上面,其实是支持用在所有的Spring Bean上(Controller、Service、Repository等)。

配置依赖:

  1. SpringMVC项目:

    <dependency>
        <groupId>org.hibernate.validator</groupId>
        <artifactId>hibernate-validator</artifactId>
        <version>6.0.18.Final</version>
    </dependency>
    
  2. SpringBoot项目:

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

- Validation

javax.validation 并不是 JDK 的一部分,它是 Java EE 的一部分,它提供了基于注释和 API 的对象校验功能,即 JSR 303 规范。JSR 303 规范定义了一套用于在 JavaBean 中定义验证约束的元数据模型并提供用于编程方式和声明式验证的 API。

  • 在 JavaEE6 中,这个规范被定义为 JSR 303
  • 在 JavaEE7 中则成为了 JSR 349
  • 在 JavaEE8 中成为 JSR 380

javax.validation 包中包含了很多注解,如@NotNull、@Size、@Pattern,它们可以用来对 JavaBean 中的属性进行各种验证,以保证数据的有效性和安全性。

需要注意的是,虽然 JSR-303 规范是 Java EE 的一部分,但我们仍然可以在非 Java EE 的应用程序中使用它,例如 Spring Boot、Spring MVC 等。在使用时需要导入 javax.validation 的 jar 包或者其他支持JSR 303规范的第三方库。

- Hibernate Validator

hibernate-validator是一个流行的Java数据校验器库,它基于JSR 303规范实现了Bean Validation 2.0 API,并提供了许多常用的校验注解和校验器。

校验注解

注解通用属性

  • message:错误提示信息

    可以使用 {} 占位符来引用注解中的属性值或自定义的文本。可以在错误消息中动态地插入变量的值,使错误消息更具体和有意义。

    使用方式:

    • 自定义文本
      @Size(min = 5, max = 10, message = "用户名长度不正确")
      private String username;
      
    • 引用注解中的属性值
      @Size(min = 5, max = 10, message = "用户名长度必须介于 {min} 到 {max} 之间")
      private String username;
      
    • 引用配置文件配置参数的值
      @Size(min = 5, max = 10, message = "{javax.validation.constraints.Size.message}")
      private String username;
      
      为了提供国际化和本地化支持,错误消息通常会使用特定的键(key)来标识。这些键将在校验过程中与消息资源包(message bundle)中的实际错误消息文本进行映射。
  • groups:所属的校验组

    校验组是一种逻辑分组,可以根据不同的场景或需求对校验规则进行分组。默认情况下,参数校验不属于任何校验组,即属于默认组(Default)。

  • payload:携带额外的信息负载

    这些信息负载可以在校验失败时获得。通常情况下,我们不需要使用该属性。

- Validator 内置注解

注解注解的其它属性注解描述
@Null验证对象是否为 null
@NotNull验证对象不为 null
@NotBlank验证字符串不为空,去除前后空格后长度大于 0
@NotEmpty验证对象不为 null 且不为空(如字符串、集合、数组等),去除前后空格后长度大于 0
@Sizemin:最小值
max:最大值
验证对象(字符串、集合、数组等)长度在 min 和 max 之间
@Maxvalue:最大值验证数值(byte、short、int、long 等)是否小于等于指定的最大值
@Minvalue:最小值验证数值(byte、short、int、long 等)是否大于等于指定的最小值
@DecimalMaxvalue:最大值
inclusive:是否包含边界
验证 BigDecimal 和 BigInteger 的值是否小于等于指定的最大值
@DecimalMinvalue:最小值
inclusive:是否包含边界
验证 BigDecimal 和 BigInteger 的值是否大于等于指定的最小值
@Digitsinteger:整数位数
fraction:小数位数
验证数值是否符合整数位数和小数位数要求
@Patternregexp:正则表达式验证字符串是否符合指定的正则表达式
@Email验证字符串是否符合 Email 格式
@Future验证日期是否为将来某个时间
@FutureOrPresent验证日期是否为将来某个时间或当前时间
@Past验证日期是否为过去某个时间
@PastOrPresent验证日期是否为过去某个时间或当前时间
@AssertTrue验证 boolean 类型是否为 true
@AssertFalse验证 boolean 类型是否为 false
@Positive验证数值是否为正数
@PositiveOrZero验证数值是否为正数或零
@Negative验证数值是否为负数
@NegativeOrZero验证数值是否为负数或零

@Max(value)、@Min(value)、@DecimalMax(value)、@DecimalMin(Value)区别:

  • @Max、@Min接受一个Long类型的值
  • @DecimalMax、@DecimalMin接受一个字符串类型的值(BigDecimal的字符串表示形式,因此可以是小数)
  • 数字超过Long.MAX_VALUE或Long.MIN_VALUE以下或者数字是小数,@DecimalMax、@DecimalMin是唯一的选择。

- Hibernate Validator 附加注解

注解注解的其它属性注解描述
@Lengthmin:最小长度
max:最大长度
验证字符串的长度是否在指定范围内
@Rangemin:最小值
max:最大值
验证数字(BigDecimal, BigInteger, String, byte, short, int, long和原始类型的包装类 )是否在指定范围内
@UniqueElements验证集合中的元素是否唯一
@URLprotocol:协议
host:主机
port:端口
regexp:正则表达式
flags:标志
验证字符串是否符合 URL 格式要求
@Hexadecimal验证字符串是否是十六进制的格式
@ISBNtype:ISBN类型验证字符串是否是有效的ISBN码
@CreditCardNumberignoreNonDigitCharacters:是否忽略非数字字符验证字符串是否是有效的信用卡号码
@Currencyvalue:货币代码验证字符串是否是有效的货币代码
@CodePointLengthmin:最小码点长度
max:最大码点长度
验证字符串的码点长度是否在指定范围内
@Mod10Check验证字符串是否通过Mod 10算法校验
@Mod11Check验证字符串是否通过Mod 11算法校验
@LuhnCheck验证字符串是否通过Luhn算法校验
@EANtype:EAN 类型验证字符串是否是有效的 EAN(欧洲文章编号)码
@ScriptAssertlang:脚本语言
script:脚本内容
alias:脚本别名
reportOn:指定将错误报告应用于哪些属性
验证对象的属性是否满足指定的脚本表达式
@ParameterScriptAssertlang:脚本语言
script:脚本内容
验证对象的属性是否满足指定的脚本表达式,可以在脚本中引用方法参数

校验开启

@Valid和@Validated是用于Spring框架中数据校验的注解。

@Valid和@Validated

对比@Valid@Validated
校验规则JavaEE JSR-303(Bean Validation)规范定义的Spring定义的,对@Valid进行了二次封装。
所以既支持使用JSR-303规范的注解,也可以使用Spring提供的额外校验规则
使用范围用在方法、参数、字段上用在类、参数上
分组校验不支持支持
嵌套校验支持
需要在嵌套的字段上面也加上@Valid
不支持
异常处理校验失败时抛出异常MethodArgumentNotValidException校验失败时抛出异常ConstraintViolationException

校验分类

Spring支持校验范围可分为以下两类:

  • Controller校验
  • Spring Bean校验(包括Controller)

- Controller校验

Spring对Controller层的参数校验进行了专门处理,当请求到达Controller层前会先找到匹配的方法参数解析器(HandlerMethodArgumentResolver)进行参数解析,参数解析的过程中包含参数校验。

HandlerMethodArgumentResolver分析可参考:https://blog.csdn.net/JokerLJG/article/details/134754757

源码分析:

HandlerMethodArgumentResolver接口:

public interface HandlerMethodArgumentResolver {
	
	// 判断该解析器是否支持解析给定的方法参数
	boolean supportsParameter(MethodParameter parameter);
	
	// 解析给定的方法参数,并返回解析后的参数值
	Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
}

下面以HandlerMethodArgumentResolver的其中一个实现类来分析。
RequestResponseBodyMethodProcessor类:

	@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);
	}

	protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {
		// 遍历方法参数上的注解
		Annotation[] annotations = parameter.getParameterAnnotations();
		for (Annotation ann : annotations) {
			// 如果注解是Validated或注解以Valid开头时,进行参数校验
			Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
			if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {
				Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann));
				Object[] validationHints = (hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
				binder.validate(validationHints);
				break;
			}
		}
	}
  • 获取方法参数上的所有注解,遍历注解
  • 当注解是Validated或注解以Valid开头时,进行参数校验

- Spring Bean校验

Spring Bean校验底层是通过AOP的方式实现的,通过MethodValidationPostProcessor动态注册AOP切面(以Validated注解为切点),然后使用MethodValidationInterceptor对切点方法织入增强。

注意:Spring Bean校验对Controller及其他Spring Bean都适用。

MethodValidationPostProcessor类:

	public void afterPropertiesSet() {
		// 为所有`@Validated`标注的Bean创建切面
		Pointcut pointcut = new AnnotationMatchingPointcut(this.validatedAnnotationType, true);
		// 创建Advisor进行增强
		this.advisor = new DefaultPointcutAdvisor(pointcut, createMethodValidationAdvice(this.validator));
	}

	// 创建Advice,本质就是一个方法拦截器(拦截Bean的所有方法)
	protected Advice createMethodValidationAdvice(@Nullable Validator validator) {
		return (validator != null ? new MethodValidationInterceptor(validator) : new MethodValidationInterceptor());
	}

MethodValidationInterceptor类:

	@Override
	@SuppressWarnings("unchecked")
	public Object invoke(MethodInvocation invocation) throws Throwable {
		// 无需增强的方法,直接跳过
		if (isFactoryBeanMetadataMethod(invocation.getMethod())) {
			return invocation.proceed();
		}
		// 获取校验分组信息
		Class<?>[] groups = determineValidationGroups(invocation);
		
		ExecutableValidator execVal = this.validator.forExecutables();
		Method methodToValidate = invocation.getMethod();
		Set<ConstraintViolation<Object>> result;
		// 方法参数校验
		try {
			result = execVal.validateParameters(invocation.getThis(), methodToValidate, invocation.getArguments(), groups);
		} catch (IllegalArgumentException ex) {
			methodToValidate = BridgeMethodResolver.findBridgedMethod(ClassUtils.getMostSpecificMethod(invocation.getMethod(), invocation.getThis().getClass()));
			result = execVal.validateParameters(invocation.getThis(), methodToValidate, invocation.getArguments(), groups);
		}
		if (!result.isEmpty()) {
			throw new ConstraintViolationException(result);
		}
		Object returnValue = invocation.proceed();
		// 方法返回值校验
		result = execVal.validateReturnValue(invocation.getThis(), methodToValidate, returnValue, groups);
		if (!result.isEmpty()) {
			throw new ConstraintViolationException(result);
		}
		return returnValue;
	}
  • 方法参数校验:当方法参数上有@Valid或参数校验注解(@NotNull、@NotBlank、@Length等)时,会进行校验。
  • 方法返回值校验:当方法参数上有@Valid或参数校验注解(@NotNull、@NotBlank、@Length等)时,会进行校验。

校验使用

校验分类校验范围
Controller方法参数对象校验Controller适用
Spring Bean方法返回值对象校验Controller及其他Spring Bean都适用
Spring Bean方法参数对象校验Controller及其他Spring Bean都适用

注意:下面的示例中,项目做了全局异常处理。

- Controller:参数对象校验

对Controller方法参数对象进行校验。

@Data
public class User {

    @NotNull(message = "userId不能为空")
    public String userId;
}
  • 使用@Valid+校验注解:

    方法参数上加@Valid,参数对象中使用校验注解

    代码

    @RestController
    @RequestMapping("valid")
    public class ValidController {
    
        @PostMapping("test11")
        public User test11(@RequestBody @Valid User user) {
            return user;
        }
    }
    

    请求

    curl 'http://127.0.0.1:8888/valid/test11' -H 'Content-Type: application/json' -d '{}'
    

    结果

    {
        "code": 501,
        "msg": "userId不能为空",
        "data": null
    }
    
  • 使用@Validated+校验注解:

    方法参数上加@Validated,参数对象中使用校验注解

    代码

    @RestController
    @RequestMapping("valid")
    public class ValidController {
    
        @PostMapping("test12")
        public User test12(@RequestBody @Validated User user) {
            return user;
        }
    }
    

    请求

    curl 'http://127.0.0.1:8888/valid/test12' -H 'Content-Type: application/json' -d '{}'
    

    结果

    {
        "code": 501,
        "msg": "userId不能为空",
        "data": null
    }
    

- Spring Bean:方法返回值对象校验

对Spring Bean(包括Controller)方法返回值对象进行校验。

注意:方法返回值对象校验时,注解放到方法前面或整个方法上面都是相同的效果。

@Data
public class User {

    @NotNull(message = "userId不能为空")
    public String userId;
}
  • 使用@Validated+@Valid+校验注解

    Spring Bean类上加@Validated,方法返回值上加@Valid,返回值对象中使用校验注解。

    代码

    @RestController
    @RequestMapping("valid")
    public class ValidController {
    
        @Autowired
        private ValidService validService;
        
        @PostMapping("test13")
        public User test13(@RequestBody User user) {
            return validService.test13(user);
        }
    }
    
    @Validated
    @Service
    public class ValidService {
    
        public @Valid User test13(User user) {
            return user;
        }
    }
    
    

    请求

    curl 'http://127.0.0.1:8888/valid/test13' -H 'Content-Type: application/json' -d '{}'
    

    结果

    {
        "code": 501,
        "msg": "userId不能为空",
        "data": null
    }
    
  • 使用@Validated+校验注解

    Spring Bean类上加@Validated,方法返回值上直接使用校验注解。

    代码

    @RestController
    @RequestMapping("valid")
    public class ValidController {
        @Autowired
        private ValidService validService;
        
        @PostMapping("test14")
        public @NotNull User test14(@RequestBody User user) {
            return validService.test14(user);
        }
    }
    
    @Validated
    @Service
    public class ValidService {
    
        public @NotNull User test14(User user) {
            return null;
        }
    }
    

    请求

    curl 'http://127.0.0.1:8888/valid/test14' -H 'Content-Type: application/json' -d '{}'
    

    结果

    {
        "code": 501,
        "msg": "不能为null",
        "data": null
    }
    

- Spring Bean:方法参数对象校验

对Spring Bean(包括Controller)方法参数对象进行校验。

@Data
public class User {

    @NotNull(message = "userId不能为空")
    public String userId;
}
  • 使用@Validated+@Valid+校验注解

    Spring Bean类上加@Validated,方法参数上加@Valid,参数对象中使用校验注解。

    代码

    @RestController
    @RequestMapping("valid")
    public class ValidController {
    
        @Autowired
        private ValidService validService;
        
        @PostMapping("test15")
        public User test15(@RequestBody User user) {
            return validService.test15(user);
        }
    }
    
    @Validated
    @Service
    public class ValidService {
    
        public User test15(@Valid User user) {
            return user;
        }
    }
    

    请求

    curl 'http://127.0.0.1:8888/valid/test15' -H 'Content-Type: application/json' -d '{}'
    

    结果

    {
        "code": 501,
        "msg": "userId不能为空",
        "data": null
    }
    
  • 使用@Validated+校验注解

    Spring Bean类上加@Validated,方法参数上直接中使用校验注解。

    代码

    @RestController
    @RequestMapping("valid")
    public class ValidController {
    
        @Autowired
        private ValidService validService;
        
        @PostMapping("test16")
        public User test16(@RequestBody User user) {
            return validService.test16(null);
        }
    }
    
    @Validated
    @Service
    public class ValidService {
    
        public User test16(@NotNull User user) {
            return user;
        }
    }
    
    

    请求

    curl 'http://127.0.0.1:8888/valid/test16' -H 'Content-Type: application/json' -d '{}'
    

    结果

    {
        "code": 501,
        "msg": "不能为null",
        "data": null
    }
    

分组校验

@Data
public class User {

    @NotNull(message = "userId不能为空")
    public String userId;

    @NotNull(message = "userName不能为空", groups = {AddGroup.class})
    public String userName;

    @NotNull(message = "age不能为空", groups = {UpdateGroup.class})
    public String age;
}

- Controller:方法参数分组校验

对Controller方法参数对象进行分组校验。

  • 使用@Validated+@Valid+校验注解

    方法参数上加@Validated,并指定分组value属性,参数对象中的字段加校验注解并指定分组groups属性。

    代码

    @RestController
    @RequestMapping("valid")
    public class ValidController {
    
        @PostMapping("test17")
        public User test17(@RequestBody @Validated({AddGroup.class}) User user) {
            return user;
        }
    
        @PostMapping("test18")
        public User test18(@RequestBody @Validated({UpdateGroup.class}) User user) {
            return user;
        }
    }
    

    请求

    curl 'http://127.0.0.1:8888/valid/test17' -H 'Content-Type: application/json' -d '{}'
    
    curl 'http://127.0.0.1:8888/valid/test18' -H 'Content-Type: application/json' -d '{}'
    

    结果

    {
        "code": 501,
        "msg": "userName不能为空",
        "data": null
    }
    
    {
        "code": 501,
        "msg": "age不能为空",
        "data": null
    }
    

嵌套校验

@Data
public class User {

    @NotNull(message = "userId不能为空")
    public String userId;

    @Valid
    public Sex sex;
}

@Data
public class Sex {

    @NotNull(message = "sexName不能为空")
    public String sexName;
}

- Controller:方法参数嵌套校验

对Controller方法参数对象进行嵌套校验。

  • 使用@Validated+@Valid+校验注解

    方法参数上加@Validated,参数对象中的字段加@Valid,字段对象内再使用校验注解。

    代码

    @RestController
    @RequestMapping("valid")
    public class ValidController {
    
        @PostMapping("test19")
        public User test19(@RequestBody @Validated User user) {
            return user;
        }
    }
    
    

    请求

    curl 'http://127.0.0.1:8888/valid/test19' -H 'Content-Type: application/json' -d '{
      "userId": "userId_a7699554a547",
      "sex": {}
    }'
    

    结果

    {
        "code": 501,
        "msg": "sexName不能为空",
        "data": null
    }
    
  • 使用@Valid+@Valid+校验注解

    方法参数上加@Valid,参数对象中的字段加@Valid,字段对象内再使用校验注解。

    代码

    @RestController
    @RequestMapping("valid")
    public class ValidController {
    
        @PostMapping("test20")
        public User test20(@RequestBody @Valid User user) {
            return user;
        }
    }
    

    请求

    curl 'http://127.0.0.1:8888/valid/test20' -H 'Content-Type: application/json' -d '{
      "userId": "userId_a7699554a547",
      "sex": {}
    }'
    

    结果

    {
        "code": 501,
        "msg": "sexName不能为空",
        "data": null
    }
    

- Spring Bean:方法返回值嵌套校验

对Spring Bean(包括Controller)方法返回值对象进行嵌套校验。

  • 使用@Validated+@Valid+@Valid+校验注解

    Spring Bean类上加@Validated,方法返回值上加@Valid,返回值对象中字段加@Valid,字段内使用校验注解。

    代码

    @RestController
    @RequestMapping("valid")
    public class ValidController {
    
        @Autowired
        private ValidService validService;
    
        @PostMapping("test21")
        public User test21(@RequestBody User user) {
            return validService.test21(user);
        }
    }
    
    @Validated
    @Service
    public class ValidService {
    
        public @Valid User test21(User user) {
            return user;
        }
    }
    
    

    请求

    curl 'http://127.0.0.1:8888/valid/test21' -H 'Content-Type: application/json' -d '{
      "userId": "userId_a7699554a547",
      "sex": {}
    }'
    

    结果

    {
        "code": 501,
        "msg": "sexName不能为空",
        "data": null
    }
    

- Spring Bean:方法参数嵌套校验

对Spring Bean(包括Controller)方法参数对象进行嵌套校验。

  • 使用@Validated+@Valid+@Valid+校验注解

    Spring Bean类上加@Validated,方法参数上加@Valid,参数对象中字段加@Valid,字段内使用校验注解。

    代码

    @RestController
    @RequestMapping("valid")
    public class ValidController {
    
        @Autowired
        private ValidService validService;
        
        @PostMapping("test22")
        public User test22(@RequestBody User user) {
            return validService.test22(user);
        }
    }
    
    @Validated
    @Service
    public class ValidService {
    
        public User test22(@Valid User user) {
            return user;
        }
    }
    
    curl 'http://127.0.0.1:8888/valid/test22' -H 'Content-Type: application/json' -d '{
      "userId": "userId_a7699554a547",
      "sex": {}
    }'
    

    结果

    {
        "code": 501,
        "msg": "sexName不能为空",
        "data": null
    }
    

自定义注解

自定义参数校验工具的步骤:

  1. 自定义注解
  2. 自定义Validator

1. 自定义注解

定义一个IPV4地址的校验注解。

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Documented
@Target({ METHOD, FIELD, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Repeatable(Ipv4.List.class)
@Constraint(validatedBy = {Ipv4Validator.class})
public @interface Ipv4 {

    String message() default "not is Ipv4";

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

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

    @Target({ METHOD, FIELD, PARAMETER, TYPE_USE })
    @Retention(RUNTIME)
    @Documented
    @interface List {
        Ipv4[] value();
    }
}

2. 自定义Validator

定义一个校验器,用于验证是否为IPV4地址。

import cn.hutool.core.lang.Validator;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

public class Ipv4Validator implements ConstraintValidator<Ipv4, String> {
    
    @Override
    public void initialize(Ipv4 constraintAnnotation) {
        ConstraintValidator.super.initialize(constraintAnnotation);
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        // 对象为空验证通过
        if (value == null) {
            return true;
        }
        return Validator.isIpv4(value);
    }
}

使用示例:

代码

@Validated
@RestController
@RequestMapping("valid")
public class ValidController {

	@GetMapping("test23")
    public String test23(@Ipv4(message = "ip不是IPV4地址") String ip) {
        return ip;
    }
}

请求

curl 'http://127.0.0.1:8888/valid/test23?ip=1.1.1'

结果

{
    "code": 501,
    "msg": "ip不是IPV4地址",
    "data": null
}
  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值