SpringMVC(二)

FORM标签:

一般情况下,通过GET请求获取表单页面,而通过POST请求提交表单页面,因此获取表单页面和提交表单页面的URL时相同的。只要满足该最佳条件的契约,<form:form>标签就无需通过action属性指定表单提交的URL;

可以通过modelAttribute属性指定绑定的模型属性,若没有指定该属性,则默认从request域对象中读取command属性的bean,如果该属性值也不存在,则会发生错误;

SpringMVC提供了多个表单组件标签,如<form:input/>、<form;select>等,用以绑定表单字段的属性值,它们的共有属性如下:

-path:表单字段,对应html元素的name属性,支持级联属性;

-htmlEscape:是否对表单值的HTML特殊字符进行转换,默认值为true;

-cssClass:表单组件对应的css样式类名

-cssErrorClass:表单组件的数据存在错误时,采取的CSS样式;

表单标签

form:input,form:password,form:hidden,form:textarea分别对应HTML表单的text,password,hidden,textarea标签;

form:radiobutton:单选框组件标签,当表单bean对应的属性和value属性相等时,单选框被选中;

form:radiobuttons:单选框组标签,用于构造多个单选框;

-items:可以是一个List,String[],或Map;

-itemValue:指定radio的value值。可以是集合中bean的一个属性值;

-itemLabel:指定radio的label值;

delimiter:多个单选框可以通过delimiter指定的分隔符;

form:checkbox:复选框组件。用于构造单个复选框;

form:checkboxs:用于构造多个复选框。使用方法同form:radiobuttons标签

form:select:用于构造下拉框组件。使用方式同form:radiobuttons标签

form:option:指定select的option的值

form:options指定其他所有的option,用法同form:radiobuttons标签

form:errors:显示表单组件或数据校验所对应的错误

-<form:errors path="*"/>显示表单所有的错误

-<form:errorrs path="user*"/>:显示所有以user为前缀的属性对应的错误

-<form:errors path="username"/>显示特定表单对象属性的错误

处理静态资源

优雅的REST风格的资源URL不希望带.html或.do等后缀,若将DispatcherServlet请求映射配置为/,则SpringMVC将捕获WEB容器的所有请求,包括静态资源的请求,SpringMVC将会将他们当成一个普通请求处理,因此早不到对应处理器将导致错误。

可以在SpringMVC的配置文件中配置<mvc:default-servlet-handler/>的方式解决静态资源的问题:

-<mvc:default-sevlet-handler/>将会在SpringMVC上下文中定义一个DefaultServletHttpRequestHandler,它将会对进入DispatcherServlet的请求进行筛查,如果发现是没有经过映射的请求,就将该请求交由WEB应用服务器默认的Servlet处理,如果不是静态资源的请求,才由DispatcherServlet继续处理;(是用该注解后要加上<mvc:annotation-driven />,不然处理不了映射);

一般WEB应用服务器默认的Servlet的名称都是default(tomcat的内部配置中xml)。若使用的WEB服务器默认Servlet名称不是default,则需要通过default-servlet-name属性显示指定

数据绑定流程

SpringMVC框架将ServletRequest对象及目标方法的入参实例传递给WebDataBinderFactory实例,以创建DataBinder实例对象;

DataBinder调用装配在SpringMVC上下文中的ConversionService组件进行数据类型转换,数据格式化工作。将Servlet中的请求信息填充到入参对象中

调用Validator组件对已经绑定了请求消息的入参对象惊醒数据合法性校验,并最终生产数据绑定结果BindingData对象;

SpringMVC抽取BingdingResult中的入参对象和校验错误对象,将它们赋给处理方法的相应入参;

Spring MVC通过反射机制对目标处理方法进行解析,将请求消息绑定到处理方法中的入参中。数据绑定的核心部件是DataBinder,运行机制如下:

数据绑定流程

 

数据转换

SpringMVC上下文中内建了很多转换器,可完成大多数Java类型的转化工作。

内部转换器

自定义类型转换器

ConversionService是Spring类型转换体系的核心接口;

可以利用ConversionServiceFactoryBean在Spring的IOC容器中定义一个ConversionService。Spring将自动识别出IOC容器中的ConversionService,并在Bean属性配置及SpringMVC处理方法入参绑定等场合使用它进行数据的转换;

可通过ConversionServiceFactoryBean的converters属性注册自定义的类型转化器;(只实现转换是下图中可以是ConversionServiceFactoryBean)

自定义转换器

 Spring支持的转换器

Spring定义了3中类型的转换器接口,实现任意一个转换器器接口都可以作为自定义转换器注册到ConversionServiceFactoryBean中:

-Converter<S,T>:将S类型对象转为T类型对象

-ConverterFactory:将相同系列多个同质Converter封装在一起,如果希望将一种类型对象转换为另一种类型及其子类的对象可使用该转换器工厂类;

-GenericConver:会根据源类对象及目标类对象所在的宿主类中的上下文信息类型进行类型转换;

<mvc:annotation-driven conversion-service="conversionService"/>会将自定义的ConversionService注册到SpringMVC上下文中

关于mvc:annotation-driven

<mvc:annotation-driven/>会自动注册RequestMappingHandlerMapping、RequestMappingHandlerAdapter与ExceptionHandlerExceptionResovler三个bean。

还将提供以下支持:

-支持使用ConversionService实例对表单参数进行类型转换

-支持使用@NumberFormat annotation、@DataTimeFormate annotation完成数据类型的格式化

-支持使用@Valid注解对JavaBean实例进行JSR303验证

-支持使用@RequestBody和@ResponseBody注解

 @IntBinder

由@标识的方法,可以对WebDataBinder对象进行初始化。WebDataBinder是DataBinder的子类,用于完成由表单字段到JavaBean属性的绑定

@initBingder方法不能由返回值,它必须声明为void。

@InitBinder方法的参数通常是WebDataBinder

binder

数据格式化

对属性对象的输入/输出进行格式化,从其本质上讲依然属于“类型转换的范畴”

Spring在格式化模块中定义了一个实现ConversionService接口的FormattingConversionService实现类,该实现类扩展了GenericConversionService,因此它既具有支持类型转换的功能,又具有格式化的功能

FormattingConversionService拥有一个FormattingConcersionServiceFactoryBean工厂l类,后者用于在Spring上下问中构造前者; 

日期格式化

@DateTimeFormat注解可对java.util.Date、java.util.Calendar、java.long.Long时间类型进行标注

-parrern属性:类型为字符串。指定解析/格式化字段数据的模式,如"yyyy-MM-dd hh:mm:ss"

-iso属性:类型为DateTimeFormat.ISO。指定解析/格式化字段数据的ISO模式,包括四种:ISO.NONE--默认、ISO.DATE(yyyy-MM-dd)、ISO.Time(hh:mm:ss.SSSZ)、ISO.DATE_TIME(yyyy-MM-dd hh:mm:ss.SSSZ)

-style属性:字符串类型。通过样式指定日期时间的格式,由两位字符组成,第一位表示日期的格式,第二位表示时间的格式:S:短日期/时间格式。M:中日期/时间格式、L:长时间/时间格式、F:完整日期/时间格式、-:忽略时间或格式

数值格式化

@NumberFormat可对类似数字类型的属性进行标注,它拥有两个互斥的属性:

-style:类型为NumberFormat.Style。用于指定样式类型,包括三种:Style.NUMBER(正常数字类型)、Style.CURRENCY(货币类型)、Style.PERCENT(百分数类型)

-pattern:类型为String,自定义样式,如pattern=“#,###”

使用日期格式和数值格式化时,需要配置<mvc:annotation-derven>

如果需要自定义转换器,用FormattingConcersionServiceFactoryBean

数据校验

JSR303

JSR303是java为bean数据合法性校验提供的标准框架,他已经包含在JavaEE6.0中。

JSR303通过在Bean属性上标注类似于@NotNull、@Max等标准的注解指定检验规则,并通过标准的验证接口对Bean进行校验规则,并通过标准的验证接口对Bean进行验证

JSR303

Hibernate  Validator 扩展注解

Hibernate Validator 是Jsr303的一个参考实现,除支持所有标准的校验注解外,它还支持一下的扩展注解

@Email-->被注释的元素必须是电子邮箱地址

@Length-->被注释的字符串的长度必须在指定的范围内

@NotEmpty-->被注释的字符串必须非空

@Range-->被注释的元素必须在核实的范围内

Spring MVC数据校验

Spring拥有自己独立的数据校验框架,同时支持JSR303标准的校验框架。

Spring在进行数据绑定时,可同时调用校验框架完成数据校验工作,在SpringMVC中,可直接通过注解驱动的方式进行数据校验

Spring的LocalValidatorFactoryBean既实现了Spring的Validator接口,也实现了JSR303的Validator接口,只要在Spring容器中定义一个LocalValidatorFactoryBean,即可将其注入到需要数据校验的Bean中。

Spring本身并没有提供JSR303的实现,所以必须将JSR303的实现者的Jar包放在类路径下。

<mvc:annotation-driven/>会默认装配好一个LocalValidatorFactoryBean,通过在处理方法的入参标准@valid注解即可让Spring MVC在完成数据绑定后指定数据校验工作;

在已经标注了JSR303注解的表单/命令对象前标注一个@Valid,SpringMVC框架在将请求参数绑定到入参对象后,就会调用校验框架根据注解声明的校验规则实现校验

Spring MVC时通过对处理方法签名的规约来保存校验结果的,前一个表单/命令对象的校验结果保存到随后的入参中,这个保存校验结果的入参必须时BindingResult或Errors类型(实际上BingResult就时Errors类型),这两个类都位于org.springframework.validation包中;

许校验的Bean对象和其绑定结果对象或错误对象成对出现,它们之间不允许声明其他入参;

Errors接口提供了获取错误信息的方法,在getErrorCount()或getFiledErrors(String field)

BindingResult扩展了Errors接口

valid

在目标方法中获取校验结果

在表单/命令对象类的属性中标注校验注解,在处理方法对应的入参前添加@Valid,SpringMVC就会实施校验并将校验结果保存在被校验入参对象之后的BindingResult或Errors的入参中。

常用的方法:

-FieldError getFieldError(String field)

-List<FileError> getFieldErrors()

-Object getFieldValue(String field)

-int getErrorCount()

在页面上显示错误

Spring MVC除了会将表单/命令对象的校验结果保存到对应的BindingResult或Errors对象中外,还会将校验结果保存到“隐含模型”

即使处理方法的签名中没有对应于编导/命令对象的结果入参,校验结果也会保存在“隐含对象”中。

隐含模型中的所有数据最终将通过HttpServletRequest的属性列表暴露给JSP视图对象,因此在JSP中可以获取错误信息;

在JSP页面上可通过<form:errors path="userName">显示错误消息

提示消息的国际化

每个属性在数据绑定和数据校验发生错误时,都会生产一个对应的FileError对象。

当一个属性校验失败后,校验框架会为该属性生产4个消息代码,这些代码以校验注解类名为前缀,结合modelAttribute、属性名以及属性类型名生成多个对应的消息代码:例如User类中的password属性标注了一个@Pattern注解,当属性值不满足@pattern所定义的规则时,就会残生一下4个错误代码:

-Pattern.user.password

-Pattern.password

-Pattern.java.lang.String

-Pattern

当使用SpringMVC标签显示错误消息时,SpringMVC会看WEB上下文是否装配了对应的国际化消息,如果没有,则会显示默认的错误消息,否者使用国际化消息。

若数据类型转换或数据格式化转换时发生错误,或该有的参数不存在,在调用处理方法时发生错误,就会在隐含模型中创建错误消息。其错误代码前缀说明如下:

-required:必要的参数不存在。@RequiredParam(“param1”) 标注了一个入参,但是该参数不存在

-typeMismatch:在数据绑定时,发生数据类型不匹配的问题

-methodIvocation:Spring MVC在调用方法时发生了错误;

配置国际化文件:

处理JSON:使用HttpMessageConverter

使用方法:

1,加入Jar包:jackon

2,在目标方法上,使用返回为JSON对应的对象或者集合作为返回值;

3,在方法上添加@ReponseBody注解

HttpMessageConverter<T>

HttpMessageConverter<T>时Spring3.0新添加的一个借口,负责将请求信息转换为一个对象(类型为T),将对象<类型为TT>输出为响应信息

HttpMessageConverter<T>接口定义的方法:

- Boolean canRead(Class<?> clazz,MediaType mediaType): 指定转换器可以读取的对象类型,即转换器是否可将请求信息转换为 clazz 类型的对象,同时指定支持 MIME 类型(text/html,applaiction/json等);

-Boolean canWrite(Class<?> clazz,MediaType mediaType):指定转换器是否可将 clazz 类型的对象写到响应流中,响应流支持的媒体类型在MediaType 中定义。

-LIst<MediaType> getSupportMediaTypes():该转换器支持的媒体类型。

-T read(Class<? extends T> clazz,HttpInputMessage inputMessage):将请求信息流转换为 T 类型的对象。

-void write(T t,MediaType contnetType,HttpOutputMessgaeoutputMessage):将T类型的对象写到响应流中,同时指定相应的媒体类型为 contentType。

DispatcherServlet默认装配RequestMappingHandlerAdapter,而ReequestMappingHandlerAdapter默认装配了一些HttpMessageConverter(默认的和配置的);当加入jackson.jar后,增加一个MappingJackson2HttpMessageConverter

使用HttpMessageConverter<T>将请求信息转化并绑定到处理方法的入参中或将相应结果转为对一个的类型的相应结果,Spring提供了两种途径:

-使用@RequestBody/@ResponseBody对处理方法进行标注

-使用HttpEntity<T>/Response<T>作为处理方法的入参或返回值

当控制器处理方法使用到@RequestBody/@ResponseBody或HttpEntity<T>/ResponseEntity<T>时,Spring首先根据请求头或响应头的Accept属性选择匹配的HttpMessageConverter,进而根据参数类型或泛型类型的过滤器得到匹配的HttpMessageConverter,若找不到可用的HttpMessageConverter将报错

@RequestBody和@ResponseBody不需要成对出现。

示例:

http

其中以byte[]类型返回的实际表现为文件的下载;

国际化

默认情况下,SpringMVC根据Accept-Language参数判断客户端的本地化类型,表现形式是浏览器的语言设置的第一个;

当接受到请求时,SpringMVC会在上下文中查找一个本地化解析器(LocalResolver),找到后使用它获取请求所对应的本地化类型信息;

SpringMVC还允许装配一个动态更改本地化类型的拦截器,这样通过一个请求参数就可以控制单个请求的本地化类型;

本地化解析器和本地化拦截器

AcceptHeaderLocaleResolver:根据HTTP请求头的Accept-Language参数确定本地化类型,如果没有显式定义本地化解析器,SpringMVC使用该解析器;

CookieLocaleResolver:根据指定的Cookie值确定本地化类型

SessionLocaleResolver:根据Session中特定的属性确定本地化类型

LocaleChangeInterceptor:从请求参数中获取本次请求对应的本地化类型;

配置:

locale

 代码:

    @Autowired
	public ResourceBundleMessageSource messageSource;

	@RequestMapping("/i18n")
	public String i18n(Locale locale) {
		String message = messageSource.getMessage("i18n.user", null, locale);
		System.out.println(message);
		return "i18n";
	}


<a href="i18n?locale=zh_CH">中文</a>
	
	<br><br>
	<a href="i18n?locale=en_US">英文</a>

文件的上传

Spring MVC为文件上传提供了直接的支持,这种竹枝词时通过即插即用的MutipartResolver实现的。Spring用Jakarta Commons FileUpload(Apache公司的)技术实现了一个MutipartResolver实现类;CommonsMutipartResovler;(使用时要添加fileuploadjar包);

Spring MVC上下文中默认没有装配MutipartResovler,因此默认情况下,不能处理文件的上传工作,如果想使用Spring的文件上传功能,需要在上下文中配置MutipartResolver

配置MultipartResolver

defaultEncoding:必须和用户的pageEncoding属性一致,以便正确解析表单的内容;

为了让CommonsMutipartResovler正确工作,必须先将JakartaCommonsFile Upload以及Jakarta Commons io的类包添加到类路径下。

使用文件的上传,表单需要加上属性“enctype='multipart/form-data'”

在接受方法中使用MutipartFile的参数进行接受,可以使用MutipartFile的相应的方法,获取文件信息或进行上传;

使用拦截器

自定义拦截器

Spring MVC也可以使用拦截器对请求进行拦截处理,用户可以自定义拦截器来实现特定的功能,自定义的拦截器必须实现HandlerInterceptor接口;

-preHandle():这个方法在业务处理器处理请求之前被调用,在该方法中对用户请求request进行处理。如果程序员决定该拦截器对请求进行拦截处理后还要调用其它的拦截器,或者是业务处理器去进行处理,则返回true;如果程序员决定不需要再调用其他的组件去处理请求,则返回false

-postHandle():这个方法在业务处理器处理完请求后,单是DispatcherServlet向客户端返回响应前被调用,在该方法中对用户请求request进行处理。

-afterCompletion():这个方法在DispatcheServlet完全处理完请求后被调用(不论perHandler返回的是true还是false),可以在该方法中进行一些资源清理的工作。

拦截器方法执行的顺序

拦截器请求的顺序

配置自定义拦截器

拦截器配置

 多个拦截器的顺序

正常处理的顺序

正常情况下多个拦截器顺序

 preHandle方法返回false时的情况

preHandler返回false

 异常处理

Spring MVC通过HandlerExceptionResolver处理程序的异常,包括Handler映射、数据绑定以及目标方法执行时发生的异常;

当使用<mvc:annotation-driven>配置时,Spring MVC会多装配一个ExceptionHandlerExceptionResolver,可以对异常进行处理;

ExceptionHandlerExceptionResolver

exceptionHandlerExceptionResolver主要处理Handler中用@ExceptionHandler注解定义的方法;

@ExceptionHandler({ArithmeticException.class})
	public ModelAndView handleException(Exception ex) {
		ModelAndView mv = new ModelAndView("error");
		mv.addObject("exception", ex);
		return mv;
	}

@ExceptionHandler注解定义的方法优先级问题:例如发生的时NullPointerException,但是声明的异常由RuntimeException和Exception,此时会根据异常的最近继承关系找到继承深度最浅的那个@ExceptionHandler注解方法,即标注了RuntimeException方法

ExceptionHandlerExceptionResolver在Handler内部若找不到对应@ExceptionHandler注解的话,会找@ControllerAdvice标注类的@ExceptionHandler注解的方法;

ResponseStatusExceptionResolver

本异常解析器在异常以及异常的父类中找到@ResponseStatus注解,让后使用这个注解的属性进行处理。

定义一个@ResponseStatus注解修饰的异常类

@ResponseStatus(value = HttpStatus.NOT_FOUND , reason = "没有该用户")
public class UserException extends RuntimeException{
	private static final long serialVersionUID = 9180115111612493477L;
}

若在处理器方法中抛出的上述异常:且ExceptionHandlerExceptionResolver不解析上述异常。由于触发的异常带有@ResponseStatus注解。最后响应HttpStatus代码给客户端。关于响应码参考HttpStatus枚举类型源码;

DefaultHandlerExceptionResolver

对一些特殊的异常进行处理(Spring内部定义的),如NoSuchRequestHandlingMethodException、HttpReques
tMethodNotSupportedException、HttpMediaTypeNotSupportedException、HttpMediaTypeNotAcceptableException等。

SimpleMappingExceptionResolver

如果希望对所有异常进行统一处理,可以使用SimpleMappingExceptionReolver,他将异常类名映射为视图名,即发生异常时使用对应的视图报告异常;

simplemappingexceptionresolver

SpringMVC运行流程

 其中HandlerMapping(接口),定义了Mapping到Handler方法的映射;

HandlerExecutionChain包含了拦截器和Handler方法;

在Spring的环境下使用SpringMVC

spring的配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">

	<context:component-scan base-package="com.atguigu.springmvc">
		<context:exclude-filter type="annotation" 
			expression="org.springframework.stereotype.Controller"/>
		<context:exclude-filter type="annotation" 
			expression="org.springframework.web.bind.annotation.ControllerAdvice"/>
	</context:component-scan>

	<!-- 配置数据源, 整合其他框架, 事务等. -->

</beans>

springmvc中的配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:mvc="http://www.springframework.org/schema/mvc"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
		http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd">

	<!--  
		需要进行 Spring 整合 SpringMVC 吗 ?
		还是否需要再加入 Spring 的 IOC 容器 ?
		是否需要再 web.xml 文件中配置启动 Spring IOC 容器的 ContextLoaderListener ?
		
		1. 需要: 通常情况下, 类似于数据源, 事务, 整合其他框架都是放在 Spring 的配置文件中(而不是放在 SpringMVC 的配置文件中).
		实际上放入 Spring 配置文件对应的 IOC 容器中的还有 Service 和 Dao. 
		2. 不需要: 都放在 SpringMVC 的配置文件中. 也可以分多个 Spring 的配置文件, 然后使用 import 节点导入其他的配置文件
	-->
	
	<!--  
		问题: 若 Spring 的 IOC 容器和 SpringMVC 的 IOC 容器扫描的包有重合的部分, 就会导致有的 bean 会被创建 2 次.
		解决:
		1. 使 Spring 的 IOC 容器扫描的包和 SpringMVC 的 IOC 容器扫描的包没有重合的部分. 
		2. 使用 exclude-filter 和 include-filter 子节点来规定只能扫描的注解
	-->
	
	<!--  
		SpringMVC 的 IOC 容器中的 bean 可以来引用 Spring IOC 容器中的 bean. 
		返回来呢 ? 反之则不行. Spring IOC 容器中的 bean 却不能来引用 SpringMVC IOC 容器中的 bean!
	-->
	
	<context:component-scan base-package="com.atguigu.springmvc" use-default-filters="false">
		<context:include-filter type="annotation" 
			expression="org.springframework.stereotype.Controller"/>
		<context:include-filter type="annotation" 
			expression="org.springframework.web.bind.annotation.ControllerAdvice"/>
	</context:component-scan>

	<!-- 配置视图解析器 -->
	<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<property name="prefix" value="/WEB-INF/views/"></property>
		<property name="suffix" value=".jsp"></property>
	</bean>
	
	<mvc:default-servlet-handler/>
	<mvc:annotation-driven></mvc:annotation-driven>
	
</beans>

 web.xml配置

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://java.sun.com/xml/ns/javaee"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
	id="WebApp_ID" version="2.5">
	
	<!-- 配置启动 Spring IOC 容器的 Listener -->
	<!-- needed for ContextLoaderListener -->
	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>classpath:beans.xml</param-value>
	</context-param>

	<listener>
		<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
	</listener>
	
	<servlet>
		<servlet-name>springDispatcherServlet</servlet-name>
		<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
		<init-param>
			<param-name>contextConfigLocation</param-name>
			<param-value>classpath:springmvc.xml</param-value>
		</init-param>
		<load-on-startup>1</load-on-startup>
	</servlet>

	<servlet-mapping>
		<servlet-name>springDispatcherServlet</servlet-name>
		<url-pattern>/</url-pattern>
	</servlet-mapping>
	
</web-app>

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值