springboot3 web

springboot web配置

springboot web的配置有:

  • SpringMvc配置的前缀为:spring.mvc
  • web场景的通用配置为:spring.web
  • 文件上传的配置为:spring.servlet.multipart
  • 服务器相关配置为:server

接管SpringMVC 的三种方式

方式用法效果
全自动直接编写控制器逻辑全部使用自动配置默认效果
手自一体@Configuration + 配置**WebMvcConfigurer**+ 配置 WebMvcRegistrations不要标注 @**EnableWebMvc**保留自动配置效果 手动设置部分功能 定义MVC底层组件
全手动@Configuration + 配置**WebMvcConfigurer**标注 @**EnableWebMvc**禁用自动配置效果 全手动设置

WebMvcConfigurer 定义扩展SpringMVC底层功能

提供方法核心参数功能默认
addFormattersFormatterRegistry格式化器:支持属性上@NumberFormat和@DatetimeFormat的数据类型转换GenericConversionService
getValidator数据校验:校验 Controller 上使用@Valid标注的参数合法性。需要导入starter-validator
addInterceptorsInterceptorRegistry拦截器:拦截收到的所有请求
configureContentNegotiationContentNegotiationConfigurer内容协商:支持多种数据格式返回。需要配合支持这种类型的HttpMessageConverter支持 json
configureMessageConvertersList<HttpMessageConverter<?>>消息转换器:标注@ResponseBody的返回值会利用MessageConverter直接写出去8 个,支持byte,string,multipart,resource,json
addViewControllersViewControllerRegistry视图映射:直接将请求路径与物理视图映射。用于无 java 业务逻辑的直接视图页渲染无 mvc:view-controller
configureViewResolversViewResolverRegistry视图解析器:逻辑视图转为物理视图ViewResolverComposite
addResourceHandlersResourceHandlerRegistry静态资源处理:静态资源路径映射、缓存控制ResourceHandlerRegistry
configureDefaultServletHandlingDefaultServletHandlerConfigurer默认 Servlet:可以覆盖 Tomcat 的DefaultServlet。让DispatcherServlet拦截/
configurePathMatchPathMatchConfigurer路径匹配:自定义 URL 路径匹配。可以自动为所有路径加上指定前缀,比如 /api
configureAsyncSupportAsyncSupportConfigurer异步支持TaskExecutionAutoConfiguration
addCorsMappingsCorsRegistry跨域
addArgumentResolversList参数解析器mvc 默认提供
addReturnValueHandlersList返回值解析器mvc 默认提供
configureHandlerExceptionResolversList异常处理器默认 3 个 ExceptionHandlerExceptionResolver ResponseStatusExceptionResolver DefaultHandlerExceptionResolver
getMessageCodesResolver消息码解析器:国际化使用

@EnableWebMvc 禁用默认行为

  • @EnableWebMvc给容器中导入 DelegatingWebMvcConfiguration组件,他是 WebMvcConfigurationSupport

  • WebMvcAutoConfiguration有一个核心的条件注解, @ConditionalOnMissingBean(WebMvcConfigurationSupport.class),容器中没有WebMvcConfigurationSupportWebMvcAutoConfiguration才生效.

  • @EnableWebMvc 导入 WebMvcConfigurationSupport 导致 WebMvcAutoConfiguration 失效。导致禁用了默认行为

  • WebMvcConfigurationSupport

    • 这个类提供了很多默认配置
    • 并且判断了系统中是否由相应的类,如果有就启用相应的功能

SpringBoot 默认配置好了 SpringMVC 的所有常用特性。

  • 如果我们需要全面接管SpringMVC的所有配置并禁用默认配置,仅需要编写一个WebMvcConfigurer配置类,并标注 @EnableWebMvc 即可

  • 全手动模式

    • @EnableWebMvc : 禁用默认配置
    • WebMvcConfigurer组件:定义MVC的底层行为

web场景的最佳实践:

  • 导入web的stater, 默认就会使用springboot 的 web配置,包括:

    • 静态资源处理
    • 数据类型转换
    • json类型的处理
    • 国际化等
  • 使用 @Configuration编写一个配置类继承 WebMvcAutoConfiguration,可以自定义一些web的配置,springboot 默认的web配置也能同时生效

  • 但是如果在配置类上加上@EnableWebMvc 就会使springboot所有的默认web配置失效,全部使用自定义配置

静态资源

WebMvcAutoConfiguration定义了静态资源的规则

  • 向容器中放入了两个filter:

    • HiddenHttpMethodFilter 规定了页面表单的rest请求(get、post、put、delete)
    • FormContentFilter 规定了表单内容
      • 因为在http规范里,只有get(数据放在url后)和post(数据放在请求体)请求可以携带数据,put、delete请求体的数据会被忽略
      • FormContentFilter使put、delete请求体也能携带数据
  • 向容器中放入了WebMvcConfigurer组件,提供了配置spring mvc的所有入口

    • 所有的功能会和WebMvcPropertiesWebProperties这两个类进行绑定
    • WebMvcProperties配置的前缀为:spring.mvc
    • WebProperties配置前缀为:spring.web
  • EnableWebMvcConfiguration向容器中放入了WebMvcConfigurationSupport组件

    • 如果我们向容器中放入这个组件,springboot静态资源的配置都不会生效
    • 配置了HandlerMapping相关的配置,会根据请求路径访问对应的Handler,包括:
      • 欢迎页:WelcomePageHandlerMapping会在四个静态文件夹下寻找index.html,只要存在,项目启动访问ip端口号就会访问首页
  • boot会在静态资源目录下找 favicon.ico 图标

WebMvcConfigurer中定义了静态资源的配置

  • 访问/webjars/**路径会去classpath:/META-INF/resources/webjars/下找资源

  • 访问/**路径会去静态资源默认的四个位置,所以只要静态资源放到下面四个文件夹中就能直接访问:

    • classpath:/META-INF/resources/
    • classpath:/resources/
    • classpath:/static/
    • classpath:/public/
  • 静态资源默认都有缓存设置:如果浏览器访问了一个静态资源,并且这个资源没有发生改变,下次访问的时候就直接访问浏览器缓存,不会在向服务器发起请求

    • 关于缓存的配置,可以通过配置文件spring.web来配置
    • CachePeriod:缓存的有效时间,以秒为单位,默认为0
    • CacheControl:与HTTP有关
    • UseLastModified:是否使用最后修改,需要配合HTTP缓存,默认关闭

为什么容器中放一个WebMvcConfigurer就能配置底层行为

  1. WebMvcAutoConfiguration 是一个自动配置类,它里面有一个 EnableWebMvcConfiguration
  2. EnableWebMvcConfiguration继承与 DelegatingWebMvcConfiguration,这两个都生效
  3. DelegatingWebMvcConfiguration利用 DI 把容器中 所有 WebMvcConfigurer 注入进来
  4. 别人调用 DelegatingWebMvcConfiguration 的方法配置底层规则,而它调用所有 WebMvcConfigurer的配置底层方法。

自定义静态资源

配置方式,参考对应配置类的属性

spring:
  mvc:
    # webjars 访问路径前缀
    webjars-path-pattern: /webjars/**
    # 静态资源通用的范文前缀
    static-path-pattern: /** 
  web:
    resources:
      # 是否开启静态资源映射,默认就是开启
      add-mappings: true
      cache:
        # 缓存的时间,单位秒
        period: 60
        cachecontrol:
          # 缓存的时间,是缓存的精确配置,会覆盖上面的配置
          max-age: 100
      # 静态资源文件夹
      static-locations: classpath:/META-INF/resources/,classpath:/resources/, classpath:/static/, classpath:/public/

代码方式

  • 只要给容器中放入一个WebMvcConfigurer组件即可,再通过WebMvcConfigurer组件去设置各种配置

  • 可以实现WebMvcConfigurer

    //EnableWebMvc 注解会禁用掉springboot的默认配置
    //@EnableWebMvc
    //这是一个配置类
    @Configuration
    public class WebMvcConfig implements WebMvcConfigurer {
    
        // 静态资源展示
        @Override
        public void addResourceHandlers(ResourceHandlerRegistry registry) {
            //注意文件路径最后的斜杠(文件分隔符),如果缺少了,就不能够正确的映射到相应的目录
            //这里的意思是,所有带 /static 的请求,都会被路由到 file:H:/upload/
            registry.addResourceHandler("/static/**").
                    addResourceLocations("file:H:/upload/").
                    //设置缓存
                    setCacheControl(CacheControl.maxAge(Duration.ofMillis(100)));
        }
    
    }
    
  • 也可以使用@bean的方式

        @Bean
        public WebMvcConfigurer setWebMvcConfigurer(){
            return new WebMvcConfigurer() {
                @Override
                public void addResourceHandlers(ResourceHandlerRegistry registry) {
                    registry.addResourceHandler("/static/**").
                            addResourceLocations("file:H:/upload/").
                            //设置缓存
                                    setCacheControl(CacheControl.maxAge(Duration.ofMillis(100)));
                }
            };
        }
    
路径匹配

Ant 风格的路径模式语法具有以下规则:

  • *:表示任意数量的字符。
  • ?:表示任意一个字符
  • **:表示任意数量的目录
  • {}:表示一个命名的模式占位符
  • []:表示字符集合,例如[a-z]表示小写字母。

例如:

  • *.html 匹配任意名称,扩展名为.html的文件。
  • /folder1/*/*.java 匹配在folder1目录下的任意两级目录下的.java文件。
  • /folder2/**/*.jsp 匹配在folder2目录下任意目录深度的.jsp文件。
  • /{type}/{id}.html 匹配任意文件名为{id}.html,在任意命名的{type}目录下的文件。

注意:Ant 风格的路径模式语法中的特殊字符需要转义,如:

  • 要匹配文件路径中的星号,则需要转义为\*。
  • 要匹配文件路径中的问号,则需要转义为\?。

Spring5.3 之后加入了更多的请求路径匹配的实现策略;

以前只支持 AntPathMatcher 策略, 现在提供了 PathPatternParser 策略。并且可以让我们指定到底使用那种策略。

AntPathMatcher 与 PathPatternParser

  • PathPatternParser 在 jmh 基准测试下,有 6~8 倍吞吐量提升,降低 30%~40%空间分配率
  • PathPatternParser 兼容 AntPathMatcher语法,并支持更多类型的路径模式
  • PathPatternParser “**” 多段匹配的支持仅允许在模式末尾使用
  • sprngboot 默认使用 PathPatternParser路径匹配规则,但是如果路径中间需要有 **,需要要替换成ant风格路径
内容协商

如果想要一套系统返回多种数据格式,就需要多端内容适配请添加图片描述

SpringBoot 多端内容适配 ,默认规则:

  • 基于请求头内容协商(默认开启):

    • 客户端向服务端发送请求,携带HTTP标准的Accept请求头
    • 例如Accept: application/jsontext/xmltext/yaml
    • 服务端根据客户端请求头期望的数据类型进行动态返回
  • 基于请求参数内容协商(需要开启):

    • 例如发送请求 GET /projects/spring-boot?format=json

    • 匹配到 @GetMapping(“/projects/spring-boot”)

    • 根据参数协商,优先返回 json 类型数据【需要开启参数匹配设置

    • 发送请求 GET /projects/spring-boot?format=xml,优先返回 xml 类型数据

      # 开启基于请求参数的内容协商功能。 默认参数名:format。 默认此功能不开启
      spring.mvc.contentnegotiation.favor-parameter=true
      # 指定内容协商时使用的参数名。默认是 format,可以改成自定义的值,例如 type
      spring.mvc.contentnegotiation.parameter-name=format
      

HttpMessageConverter

@ResponseBody注解

  • 如果controller类上标注了@RestController,因为@RestController包含了@ResponseBody,所以相当于controller所有方法的返回值标注了 @ResponseBody 注解
  • 所有请求进来,都先来到DispatcherServletdoDispatch()进行处理,doDispatch()会找到一个 HandlerAdapter 适配器,利用适配器执行目标方法
  • RequestMappingHandlerAdapter来执行,调用invokeHandlerMethod()来执行目标方法
  • 目标方法执行之前,需要准备好:
    • HandlerMethodArgumentResolver:参数解析器,确定目标方法每个参数值
    • HandlerMethodReturnValueHandler:返回值处理器,确定目标方法的返回值改怎么处理
  • RequestMappingHandlerAdapter 里面的invokeAndHandle()真正执行目标方法
  • 目标方法执行完成,会返回返回值对象
  • 得到返回值对象后,需要找到一个合适的返回值处理器: HandlerMethodReturnValueHandler
  • 最终找到 RequestResponseBodyMethodProcessor能处理 标注了 @ResponseBody注解的方法
  • RequestResponseBodyMethodProcessor 调用writeWithMessageConverters ,利用MessageConverter把返回值写出去

所以由@ResponseBody注解标注的方法,返回值是由MessageConverter 处理的

  • 写出返回结果时,会遍历容器种所有的MessageConverter,然后根据请求头中的Accept找到适配的MessageConverter
  • 所以如果需要输出某种格式,就需要导入对应的HttpMessageConverter

WebMvcAutoConfiguration提供几种默认HttpMessageConverters

  • EnableWebMvcConfiguration通过 addDefaultHttpMessageConverters添加了默认的MessageConverter;如下:

    • ByteArrayHttpMessageConverter: 支持字节数据读写
    • StringHttpMessageConverter: 支持字符串读写
    • ResourceHttpMessageConverter:支持资源读写
    • ResourceRegionHttpMessageConverter: 支持分区资源写出
    • AllEncompassingFormHttpMessageConverter:支持表单xml/json读写
    • MappingJackson2HttpMessageConverter: 支持请求响应体Json读写
  • springboot默认提供的MessageConverter功能有限,只能json和普通的返回,如果需要额外的返回类型,就需要导入其他的HttpMessageConverter

示例

适配xml:

请添加图片描述

如果想要返回 xml 数据,需要先导入依赖,然后在返回的实体上加上@JacksonXmlRootElement注解:

<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-xml</artifactId>
</dependency>

请求头Accept为:application/xml 就能返回xml

请添加图片描述
请求头Accept为:application/json 就能返回 json

在这里插入图片描述

自定义返回格式

以适配yaml为例:

导入依赖:

<dependency>
    <groupId>com.fasterxml.jackson.dataformat</groupId>
    <artifactId>jackson-dataformat-yaml</artifactId>
</dependency>

编写配置

#告知系统增加了一种新的返回格式
spring.mvc.contentnegotiation.media-types.yaml=text/yaml

需要自定义一个HttpMessageConverters

/**
 * 把对象写为yaml的组件,这里的泛型代表支持的格式
 * AbstractHttpMessageConverter是HttpMessageConverter的默认实现
 */
@Component
public class YamlMessageConverter extends AbstractHttpMessageConverter<Object> {

    //设置yaml工厂,可以把对象转为yaml
    private ObjectMapper objectMapper ;

    public YamlMessageConverter() {
        //支持的类型,对应 spring.mvc.contentnegotiation.media-types.yaml=text/yaml 的类型
        //告诉springboot这个MessageConverter支持的类型
        super(new MediaType("type","yaml"));
        this.objectMapper =  new ObjectMapper(new YAMLFactory());;
    }

    /**
     * 是否支持把某种类型写出
     *
     * @param clazz
     * @return
     */
    @Override
    protected boolean supports(Class clazz) {
        //这里可以增加条件判断,直接返回true就是只要是对象类型都支持
        return true;
    }

    /**
     * 配合 @RequestBody 注解,格式化请求参数
     *
     * @param clazz
     * @param inputMessage
     */
    @Override
    protected Object readInternal(Class clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
        return null;
    }

    /**
     * 配合 `@ResponseBody` 写出返回结果
     *
     * @param o 需要写出的对象,也就是方法的返回值
     * @param outputMessage
     */
    @Override
    protected void writeInternal(Object o, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
        //自动关流
        try (OutputStream outputStream = outputMessage.getBody()){
            //写出返回结果
            this.objectMapper.writeValue(outputStream,o);
        }

    }
}

把自定义的HttpMessageConverters添加到容器,WebMvcConfigurer中可以添加HttpMessageConverter

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    
    //配置yaml的消息转换器:HttpMessageConverter
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(new YamlMessageConverter());
    }


}

国际化

国际化的自动配置参照MessageSourceAutoConfiguration

实现步骤:

  • Spring Boot 在类路径根下查找messages资源绑定文件。文件名为:messages.properties
  • 多语言可以定义多个消息文件,命名为messages_区域代码.properties。如:
    • amessages.properties:默认
    • bmessages_zh_CN.properties:中文环境
    • cmessages_en_US.properties:英语环境
  • 在程序中可以自动注入 MessageSource组件,获取国际化的配置项值
  • 在页面中可以使用表达式 #{}获取国际化的配置项值
   @Autowired  //国际化取消息用的组件
    MessageSource messageSource;
    @GetMapping("/haha")
    public String haha(HttpServletRequest request){

        Locale locale = request.getLocale();
        //利用代码的方式获取国际化配置文件中指定的配置项的值
        String login = messageSource.getMessage("login", null, locale);
        return login;
    }

错误处理机制

错误处理的自动配置都在ErrorMvcAutoConfiguration中,两大核心机制:

  • SpringMVC的错误处理机制依然保留,MVC处理不了,才会交给boot进行处理
  • SpringBoot 会自适应处理错误,响应页面或JSON数据

在这里插入图片描述

SpringBoot 会自适应处理错误,同样的错误,客户端和浏览器得到的数据格式是不一样的

在这里插入图片描述

springMvc异常处理方法:

  • @ExceptionHandler注解处理异常:注解的参数为能处理的异常类型,可以标识一个方法处理错误,默认只能处理这个类里发生的指定类型的错误

        /**
         * 错误处理方法,ExceptionHandler注解的参数为能处理的异常类型
         */
        @ExceptionHandler(Exception.class)
        public Object error(Exception e) {
            return "网络异常,请稍后再试:"+e.getMessage()+"**********";
        }
    
  • @ControllerAdvice: 标注一个类,代表这个类会集中处理所有 @Controller 发生的错误

    /**
     * ControllerAdvice 标注一个类,代表这个类会集中处理所有 @Controller 发生的错误
     */
    @ControllerAdvice
    @Slf4j
    public class GlobalExceptionHandler {
        
        @ExceptionHandler(Exception.class)
        public void error(Exception e, HttpServletResponse response) {
            log.info( "网络异常,请稍后再试:{}",e.getMessage());
            response.setContentType("text/html;charset=utf-8");
            PrintWriter writer = null;
            try {
                writer = response.getWriter();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
            writer.write("网络异常,请稍后再试:"+e.getMessage());
            writer.flush();
            writer.close();
        }
    }
    
  • 如果全局和具体类都配置了异常处理,采用就近原则,以类里配置的优先

springBoot 错误处理:

  • 如果发生错误以后,springMvc异常处理机制不生效,转发给springBoot来处理

  • springBoot会把错误转发到/error路径进行处理,并且在底层写好一个 BasicErrorController的组件,专门处理这个请求

  • 既可以返回页面,也可以返回json

    • 如果需要返回页面,需要解析出一个错误页(总结就是:先精确后模糊)

      • 如果发生了500、404等错误

        • 如果有模板引擎,默认会去 classpath:/templates/error/错误码.html
        • 如果没有模板引擎,会在静态资源文件夹下找:精确码.html
      • 如果找不到精确的错误码,会通过5xx4xx 模糊去找

        • 同上,有模板引擎的,去 classpath:/templates/error/5xx.html下找,没有的静态资源文件夹下找
      • 如果依然找不到,会返回error视图

    • 如果是返回json,会返回默认的JSON格式的错误信息

    /**默认会读取server.error.path配置,如果每有配置就会读取默认值:error.path:/error
    */
    @RequestMapping("${server.error.path:${error.path:/error}}")
    

配置文件

server:
  error:
    # 当发生错误以后,错误会转发给这个路径进行处理,所以可以自定义错误页面
    path: /error
    
 spring:
  web:
    resources:
    	#默认配置为 :classpath:/META-INF/resources/,classpath:/resources/, classpath:/static/, classpath:/public/
    	#如果想要访问到指定的错误页,还需要加上以下配置:
      static-locations: classpath:/templates/

如果前后端不分离,需要服务端页面渲染,最佳实践:

  • 对于不可预知的一些,HTTP码表示的服务器或客户端错误

    • classpath:/templates/error/下面,放常用精确的错误码页面。500.html404.html
    • classpath:/templates/error/下面,放通用模糊匹配的错误码页面。 5xx.html4xx.html
  • 如果发生业务错误

    • 核心业务,每一种错误,都应该代码控制,跳转到自己定制的错误页。
    • 通用业务,classpath:/templates/error.html页面,显示错误信息

嵌入式容器

springboot嵌入式容器

  • Servlet容器:用于管理、运行Servlet组件(Servlet、Filter、Listener)的环境,一般指服务器

  • springboot默认嵌入了tomcat服务器,无需自己搭建服务器环境

自动配置原理

  • SpringBoot 默认嵌入Tomcat作为Servlet容器。
  • 自动配置类是ServletWebServerFactoryAutoConfigurationEmbeddedWebServerFactoryCustomizerAutoConfiguration
  • 自动配置类开始分析功能。xxxxAutoConfiguration

ServletWebServerFactoryAutoConfiguration 自动配置了嵌入式容器场景

  • 绑定了ServerProperties配置类,所有和服务器有关的配置都以 server开头
  • ServletWebServerFactoryAutoConfiguration 导入了 嵌入式的三大服务器 TomcatJettyUndertow
    • 导入 TomcatJettyUndertow 都有条件注解,需要系统中有对应类才行(也就是导了包)
    • web场景默认导入了Tomcat的包,所以 Tomcat配置会生效,给容器中放入 TomcatServletWebServerFactory
    • 都给容器中 ServletWebServerFactory放了一个 web服务器工厂(造web服务器的)
    • web服务器工厂的getWebServer方法可以获取web服务器,TomcatServletWebServerFactory 创建了 tomcat。

源代码为:

@AutoConfiguration(after = SslAutoConfiguration.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
//绑定了ServerProperties配置类
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
//导入了 嵌入式的三大服务器
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
      ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
      ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
      ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {}

TomcatServletWebServerFactory getWebServer创建了 tomcat。

ServletWebServerFactory 什么时候会创建 webServer出来。

  • ServletWebServerApplicationContextioc容器,启动的时候会调用创建web服务器
  • Spring容器刷新(启动)的时候,会预留一个时机,刷新子容器。onRefresh()
  • refresh() 容器刷新 十二大步的刷新子容器会调用 onRefresh()

总结:

  • Web场景的Spring容器启动,在onRefresh的时候,会调用创建web服务器的方法。

  • Web服务器的创建是通过WebServerFactory搞定的。容器中又会根据导了什么包条件注解,启动相关的 服务器配置,默认EmbeddedTomcat会给容器中放一个 TomcatServletWebServerFactory,导致项目启动,自动创建出Tomcat。

用法:

  • 修改server下的相关配置就可以修改服务器参数
  • 通过给容器中放一个**ServletWebServerFactory**,来禁用掉SpringBoot默认放的服务器工厂,实现自定义嵌入任意服务器
  • 如果想切换服务器,可以在导入web场景的时候,禁用掉tomcat,导入其他服务器的包(实际意义不大)
  • 14
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值