Spring MVC【七】静态资源放行&父子容器&REST

目录

一、静态资源的放行

1.配置Servlet处理静态资源

2.配置放行动态资源

3.配置放行动态资源

二、父子容器

1.Spring的父子容器

2.Spring MVC的父子容器

2.1 根SpringWeb容器(Root WebApplication)

2.2 Servlet SpringWeb容器(Servlet WebApplicationContext)

3.Spring MVC父子容器的配置

三、SpringMVC与REST

1.REST风格定义

2.请求方法的幂等性

3.请求响应状态码

4.SpringMVC REST服务端:@RestController


一、静态资源的放行

中央控制器DispatcherServlet配置在应用的入口配置中(一般配置在web.xml)在Servlet3.0之后,可以实现WebApplicationInitializer接口替代。DispatcherServlet中需要配置拦截的请求匹配,出啊同配置可拦截*.do*.action后缀的请求,结合参数可以实现多层级的匹配,比如在控制器类中配置.do的请求映射,在方法中通过参数进行第二层匹配。如下:

@Controller
@RequestMapping("user.do")
public class UserController {
	@RequestMapping(params = "mytype=create")
	public ModelAndView create() {
		return new ModelAndView("user");
	}
}

浏览器请求user.do?mytype=create即可访问user.jsp,这种配置方式对.jsp及其他静态文件是不拦截的,但是存在安全隐患。在前后端分离框架中,特别是REST风格的资源请求方式流行后,推荐使用“/”作为DispatcherServlet拦截的地址匹配,改配置方式不会拦截,jsp文件和.jspx文件,不过这种配置方式会对js,*.png等静态文件的访问进行拦截。虽然会拦截静态资源,但是这种配置方式会先在应用入口配置(比如web.xml)中查找静态资源匹配的Servlet处理,如果没有找到,才会将请求交给DispatcherServlet处理。所以使用“/”作为DispatcherServlet的映射匹配,才可以配合其他设置对静态资源放行,比如使用服务器本身的静态资源处理的defaultServlet或者使用SpringMVC提供的方式。

1.配置Servlet处理静态资源

Java Web服务器本身维护了一个默认的Servlet用来处理请求,配置路径映射让静态资源交由默认的Servlet处理,而不经由DispatcherServlet拦截处理。以运行在Tomcat服务器的应用配置为例,在web.xml中增加<servlet-mapping>配置,servlet-name为default,url-pattern配置为需要放行的静态资源的后缀名匹配,如下:

<servlet-mapping>
    <servlet-name>default</servlet-name><!--默认的Servlet名字-->
    <url-pattern>*.jpg</url-pattern>    <!--静态资源后缀名匹配-->
    <url-pattern>*.js</url-pattern>
    <url-pattern>*.html</url-pattern>
    <url-pattern>*.css</url-pattern>
    <url-pattern>*.gif</url-pattern>
</servlet-mapping>
<!--该配置需要配置在DispatcherServlet前面,让defaultServlet先拦截请求-->

这种配置方式比较直接,由Web服务器直接处理,不经过Spring,性能也好。但不同的服务器,默认Servlet名字不同。常见的Web服务器默认的Servlet名字如下:

  • Tomcat、Jetty、JBoss和GlassFish:default
  • Google App Engine:ah_default
  • Resin:resin-file
  • WebLogic:FileServlet
  • WebSphere:SimpleFileServlet

如果应用程序需要部署在不同类型的服务器上,或者可能出现服务器转换,那么这种配置方式存在兼容性问题。为了兼容不同的服务器,SpringMVC提供了统一的处理方式。

2.配置<mvc:default-servlet-handler />放行动态资源

SpringMVC为了兼容不同的服务器,对defaultServlet封装了统一的接口,直接在SpringMVC配置文件中增加这一条配置即可。

<mvc:default-servlet-handler />

上面的这种配置方式会把“/**”的URL注册到SimpleUrlHandlerMapping的URLMap中,静态资源的访问由HandlerMapping转到DefaultServletHttpRequestHandler处理并返回。这种方式其实最终也是由DefaultServlet来处理,只是统一使用Spring提供的DefaultServletHttpRequestHandler来查找对应服务器默认defaultServlet。通过这种方式,就不用担心应用部署在不同服务器上时会出现兼容性问题了。

3.配置<mvc:resources>放行动态资源

<mvc:resources location="/images/" mapping="/images/**"></mvc:resources>
<mvc:resources location="/css/" mapping="/css/**"></mvc:resources>
<mvc:resources location="/js/" mapping="/js/**"></mvc:resources>

从Spring3.0.4开始提供了<mvc:resources>标签来解决静态资源无法访问的问题,这种方式请求会交予ResourceHttpRequestHandler类来处理,这种方式更加灵活、细化,而且对资源访问路径和实际路径做了一层映射(location指定的是资源的实际的路径,mapping为映射路径,就是url访问时的路径),更加安全。

二、父子容器

在基于Spring核心框架的应用中,使用不同的配置文件可以初始化多个容器对象,并且可以将某个容器对象作为另一个容器创建的参数来设定两者之间的父子层关系。子容器能够获取父容器中管理的Bean,父容器则无法使用子容器的Bean。在SpringMVC中默认提供了父子容器的设定方式,用来实现不同层的Bean进行管理。

1.Spring的父子容器

Spring容器的主要作用是对Bean进行生命周期的管理。在同一个应用中可以同时存在多个容器对象。以ClassPathXmlApplicationContext初始化容器的方式为例:

public static void main(String[] args) {
    ApplicationContext applicationContext1 = new ClassPathXmlApplicationContext("applicationContext1.xml");
    ApplicationContext applicationContext2 = new ClassPathXmlApplicationContext("applicationContext2.xml");
}

以上两个容器是平行关系,也可以设置容器的层级关系。可通过如下构造方法构造父子层级结构的容器:

public ClassPathXmlApplicationContext(String[] configLocations, @Nullable ApplicationContext parent) throws BeansException {
    this(configLocations, true, parent);
}

设定父子容器可以实现应用上下文的隔离。在桌面应用中,重写第三方库是父子容器的应用场景之一。

下面的内容为XXX.jar中的内容

@Service       
public class UserService {
    @Autowired
    private UserDao userDao;

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    public String get() {
        return userDao.getUserName("");
    }
}
public interface UserDao {
    String getUserName(String id);
}
@Repository
public class ParentUsrDao implements UserDao {
    @Override
    public String getUserName(String id) {
        return "Parent User Name";
    }
}

如下为该XXX.jar文件中的Spring核心文件配置:

<beans>	<!--省略命名空间及文档位置定义的配置-->
    <context:component-scan base-package="com.mec.spring"></context:component-scan>
</beans>

如果我们现在需要使用该第三方包,只需要在当前项目中使用import导入第三方包的Spring配置文件即可,如下:

<import resource="classpath*:parent.xml"/>
<!--通过classpath查找.jar文件时需要加星号,也就是classpath*-->

如果此时需要改写UserService中的get()方法的逻辑,也就是修改ParentUsrDao中的get()方法的逻辑。在一般应用中有如下两种处理方式:

  • 如果有源码,修改ParentUsrDao类后,将修改后的类编译后替换原.jar文件中的ParentUsrDao.class文件。
  • 如果没有源码,则解压.jar文件并反编译后取得源码,然后再进行修改和替换。

如果是基于Spring依赖注入的容器应用中,自定义一个继承自UserDao接口的实现类,让UserService使用这个类的Bean,则问题就可以得到解决。但是如果直接定义一个UserDao接口的实现类然后启动容器,那么容器可能会启动失败。(因为在UserService中是使用@Autowired自动装配UserDao接口的实现类,@Autowired是通过byType的方式注入的,之前只有一个实现类ParentUsrDao的时候注入没问题,当我们又自定义了一个UserDao实现类的时候,Spring就会报错,因为他不知道该注入哪一个实现类)。我们不更改第三方库中的代码,此时就可以使用父子容器来解决。

因为子容器对父容器不可视,所以在子容器中注册的组件不会和父容器中发生冲突。这里需要对配置文件做一些调整,当前Spring项目的核心配置文件中不再使用import导入parent.xml,而是通过容器对象初始化代码设置容器的层级关系,在子容器中获取UserService的Bean之后,设置其依赖的UserDao的Bean。如下:

public static void main(String[] args) {
    ApplicationContext parentContext = new ClassPathXmlApplicationContext("classpath*:parent.xml");
    ApplicationContext childContext = new ClassPathXmlApplicationContext(new String[] { "child.xml" }, parentContext);
    UserService userService = (UserService) childContext.getBean("userService");
    UserDao childUserDao = (UserDao) childContext.getBean("childUserDao");
    userService.setUserDao(childUserDao);
}

容器本身也是对象,也可以通过配置Bean的方式进行配置。父子容器也可以通过XML进行配置,比如配置文件名为parent-child.xml,配置示例如下:

<bean id="parentContext" class="org.springframework.context.support.ClassPathXmlApplicationContext">
    <constructor-arg>
        <value>
            <!-- 构造器方式指定配置文件 -->
	    classpath*:parent.xml
	</value>
    </constructor-arg>
</bean>		
<bean id="childContext" class="org.springframework.context.support.ClassPathXmlApplicationContext">
    <constructor-arg>
        <value>
	    <!-- 构造器方式指定配置文件 -->
	    classpath:child.xml
	</value>
    </constructor-arg>
    <constructor-arg>
        <!-- 注入父容器 -->
        <ref bean="parentContext" />
    </constructor-arg>
</bean>	

2.Spring MVC的父子容器

在平常练习开发中对于SSM项目我们可以只是用一个Spring核心配置文件,对应一个容器实例来初始化即可,但是有时候我们可能需要两个Spring核心配置文件,其中一个来配置管理控制层的Bean,另一个来配置管理其他层的Bean,这两个配置文件,就可以对应两个容器对象实例,那么这两个容器对象就可以构建父子关系,所以这个时候我们就需要SpringMVC的父子容器。在SpringMVC项目中,通过DispatcherServlet的contextConfigLocationcontextClass参数指定配置文件或配置类来初始化Spring Web容器(这个可以用于管理控制层的Bean),还可以在web.xml中使用<listener>元素来初始化容器(这个可以用于管理其他层的Bean)。这两个容器都是WebApplicationContext类的实例(SpringMVC项目中我们都是使用它的实例来加载配置初始化容器)。为了区分这两个容器,将它们分别取名为Servlet Spring Web容器根 Spring Web容器。Spring MVC父子容器的关系如下图(下图展示的是依靠web.xml来配置两个容器的方式,当然还有不使用web.xml的配置方式):

2.1 根SpringWeb容器(Root WebApplication)

根SpringWeb容器也叫SpringWeb父容器。该容器通过在项目入口配置(比如web.xml)中添加监听器(<listener>)进行配置。监听器类使用的是ContextLoaderListener,该类由Spring提供,继承自ServletContextListener标准接口(用于监听ServletContext的生命周期,该接口定义了ServletContext初始化和销毁的回调方法)和上下文加载类ContextLoader。ContextLoaderListener在ServletContext初始化时进行容器初始化,初始化配置文件使用contextConfigLocation参数配置,多个配置文件使用逗号分隔,如果不进行该参数的配置,则默认会查找/WEB-INF/applicationContext.xml

<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"  
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaeehttp://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">  
    
  	<!-- 上下文加载监听器 -->
    <listener>  
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>  
    </listener>  
	
	<!-- 创建Root WebApplicationContext -->
    <context-param>  
        <param-name>contextConfigLocation</param-name>  
        <param-value>/WEB-INF/spring/applicationContext.xml</param-value>  
    </context-param>  
</web-app>

容器被加载后,会被放入ServletContext对象中,键的值为org.springframework.web.context.WebApplicationContext.ROOT

2.2 Servlet SpringWeb容器(Servlet WebApplicationContext)

Servlet Spring Web容器也称为Spring Web子容器,在中央控制器DispatcherServlet加载时初始化。DispatcherServlet在创建该容器时,会先从ServletContext查找根容器。如果找到的话,则设为父容器;如果没有找到,则不设置父容器。该容器对象默认维护在DispatcherServlet中,也可以维护在ServletContext中。

维护在DispatcherServlet对象中,DispatcherServlet在处理请求时,会把这个子上下文保存到Request对象中,键:org.springframework.web.servlet.DispatcherServlet.CONTEXT。通过设置publishContext属性的值可以将其放入ServletContext对象中,键:org.springframework.web.servlet.FrameworkServlet.CONTEXT.{Servlet名字}

DispatcherServlet同样使用contextConfigLocation参数设置XML的Spring配置文件,如果不指定,默认使用/WEB-INF/{dispatcherServletName}-servlet.xml,而dispatcherServletName是在web.xml中配置的<servlet-name>的值。在web.xml中配置示例如下:

<servlet>  
    <servlet-name>dispatcher</servlet-name>  
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>  
    <init-param>  
        <param-name>contextConfigLocation</param-name>  
        <param-value>/WEB-INF/spring/springmvc.xml</param-value>  
    </init-param>  
    <load-on-startup>1</load-on-startup>  <!--默认启动-->
</servlet>  
<servlet-mapping>  
    <servlet-name>dispatcher</servlet-name>  
    <url-pattern>/*</url-pattern>  
</servlet-mapping>  

3.Spring MVC父子容器的配置

Spring MVC的父子容器都可以用来配置组件Bean,遵循子容器可以调用父容器的Bean,但父容器不可以调用子容器的Bean的规则。通过父子容器的上下文隔离特性,实现分层解耦。基于Spring+SpringMVC的应用,也可以不使用父子容器,而只在子容器。为了保持简洁性,中小型项目使用DispatcherServlet层级的容器即可;大型项目则进行父子容器的拆分。

SpringMVC中父子容器根据Web层和非Web层进行设定,两个容器管理不同类型的Bean。

  • 父容器主要存放数据源、Dao层、服务层和事务等非Web的组件;
  • 子容器存放控制器、处理器映射,处理器适配等Web层的组件。

可以通过<context:component-scan />的组件扫描配置元素结合<context:exclude-filter><context:include-filter> 的子元素对组件类进行筛选。以基于注解的开发为例,在父容器的核心配置文件(默认名为applicationContext.xml)中可以进行如下配置:

<context:component-scan base-package="com.mec.springmvc">
    <!-- 排除@Controller注解组件 -->
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>	

在子容器的配置(默认名是{dispatcherServletName}-servlet.xml)中,仅包含@Controller注解的控制类,配置如下:

<context:component-scan base-package="com.mec.springmvc">
    <!-- 仅包含@Controller注解组件 -->
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>	
<mvc:annotation-driven />    <!--web注解驱动开启,处理请求映射等注解-->

在使用Java替换web.xml的入口配置方式中,通过继承AbstractDispatcherServletInitializer类可以分别实现父子容器的初始化方法,如下:

//入口配置类
public class MvcWebApplicationInitializer extends AbstractDispatcherServletInitializer {

	// 初始化子容器
	@Override
	protected WebApplicationContext createServletApplicationContext() {
		XmlWebApplicationContext applicationContext = new XmlWebApplicationContext();
		applicationContext.setConfigLocation("/WEB-INF/springdispatcherconfig.xml");
		return applicationContext;
	}

	// 路径拦截匹配
	@Override
	protected String[] getServletMappings() {
		return new String[] { "/" };
	}

	// 初始化父容器
	@Override
	protected WebApplicationContext createRootApplicationContext() {
		XmlWebApplicationContext applicationContext = new XmlWebApplicationContext();
		applicationContext.setConfigLocation("/WEB-INF/spring/applicationContext.xml");
		return applicationContext;
	}

}

如果应用的入口配置也使用Java类,也就是实现零XML配置,则入口配置类继承类AbstractAnnotationConfigDispatcherServletInitializer,对应父子容器的方法返回容器配置类即可,如下:

public class MyAnnoWebApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
	// 返回父容器配置类集合
	@Override
	protected Class<?>[] getRootConfigClasses() {
		return new Class<?>[] { MyAppConfig.class };
	}

	@Override
	protected Class<?>[] getServletConfigClasses() {
		return new Class<?>[] { MyAppWebConfig.class };
	}

	@Override
	protected String[] getServletMappings() {
		return new String[] { "/" };
	}
}

三、SpringMVC与REST

REST是Resource Representational State Transfer这几个单词的缩写,表述起来就是资源在网络中以某种表现形式进行状态转移,该概念在前后端分离概念中非常适用:

  • Resource:资源,也就是数据
  • Representational:表现层,也就是表现形式。类似于JSON和XML等格式的表现形式
  • State Transfer:状态变化对应到HTTP的GET、POST、PUT和DELETE等请求方法

REST是一种软件架构风格,其本质上是使用URL来访问资源的风格定义。我们先来看一下HTTP的请求方法,HTTP协议定义了不同类型的请求方法(HTTP动词),包括GET、POST、HEAD、OPTIONS、PUT、DELETE、TRACE和CONNECT。目的在于用这些不同的请求方法来表示不同的数据(资源)操作,比如增、删、改、查等操作。在基于REST风格的应用中,我们需要正确使用HTTP动词,来表明我们要如何操作资源。常用的请求方法及数据库方法的对应关系如下:

HTTP请求方法方法名数据库操作方法描述
GETREDA从服务器取出资源(一项或多项)
POSTCREATE在服务器上新建一个资源
PUTUPDATE在服务器上更新资源(某个资源的完整更新)
PATCHUPDATE在服务器上更新资源(某个资源的局部更新,比如部分属性)
DELETEDELETE从服务器上删除指定资源

相比数据库使用SQL语句对资源进行读取等操作,REST定义的是URL来访问资源的风格,但REST不是强制的规范和接口,它不是协议,也不是规范,而是一种基于URL资源的访问风格,也就是想让我们按照这种风格去合理使用URL。

1.REST风格定义

REST很好地利用了HTTP请求类型和URL地址定义对资源进行访问。URL路径中的资源名使用复数,路径中不使用动词。以某个企业应用中对公司部门数据的操作为例,REST风格的服务URL如下表:

方法URL描述
GET/depts查找所有部门
GET/depts/{deptId}获取某个部门的信息
POST/depts创建一个新的部门
PATCH/depts/{deptId}更新某个指定部门的部分信息
PUT/depts/{deptId}更新某个部门的所有信息
DELETE/depts/{deptId}删除某个部门

注意:PUT和PATCH虽然都是更新资源,但PUT方法是更新整个资源,而PATCH方法是更新资源的局部信息。例如,上表中,PUT需要更新的话,则需要将Dept的所有信息都传入,没有的字段就应该被清空,而PATCH则只会更新传入的字段,没有的不变。

有时需要对该资源关联的子资源进行操作,则可以定义关联资源操作的URL。以部门下的用户操作为例,如下表:

方法URL描述
GET/depts/{deptId}/users查找某个部门的所有用户
GET/depts/{deptId}/users/{userId}获取某个部门的某个用户
POST/depts/{deptId}/users创建某个部门的新用户
PATCH/depts/{deptId}/users/{userId}局部更新某个部门的某个用户
PUT/depts/{deptId}/users/{userId}更新某个部门中某个用户的所有信息
DELETE/depts/{deptId}/users/{userId}删除某个部门的某个用户

2.请求方法的幂等性

幂等性这个概念对应HTTP的请求操作就是不管执行多少次,最后资源的结果都是一样的

方法是否幂等说明
GET获取资源,不管调用多少次接口,结果的内容都是一样的
POST每次调用都将产生新的资源
PATCH比如部门有一个栏位是更新时间,这个栏位是由系统自动设置为当前时间,每次调用PATCH方法时这个栏位的结果不一样
PUT多次调用,所有栏位都全部更新
DELETE对某个资源删除多少次,结果都是一样的

3.请求响应状态码

200是常见的操作成功的响应状态码。除200之外,不同的请求方法成功返回的状态码是不一样的。REST常见的请求方法返回的成功状态码如下表:

请求方法成功状态码
GET200
POST201
PATCH201
PUT200
DELETE204

除了成功的状态码,不同类型的请求在发生客户端或服务端错误时,也会返回不同的响应状态码和状态信息,由此可以快速地对问题进行定位和解决。常见地HTTP请求状态码和状态信息如下表

4.SpringMVC REST服务端:@RestController

在控制器类上使用@RestController注解来定义REST风格地请求服务,该注解是@Controller和@ResponseBody的组合注解,除了请求方法返回JSON格式数据外,开发中最好做到控制器类的请求映射地址符合REST风格,即每个控制器类对应一种资源类型,请求映射方法包括查询、添加、修改和删除。以用户操作的控制类为例,如下:

@RestController
public class PersonController {
    public List<Person> personList = new ArrayList<>();

    // 构造方法中初始化数据
    public PersonController() {
        personList.add(new Person(1, "Zhang"));
    }

    @GetMapping("/persons")
    public Object getAll() {
        return personList;
    }

    @GetMapping("/persons/{id}")
    public Object getOne(@PathVariable("id") Integer id) {
        Person person = null;
	for (Person p : personList) {
	    if (p.getId() == id) {
		person = p;
		break;
	    }
	}
	return person;
    }

    @PostMapping("/persons")
    public Object addPerson(@RequestBody Person p) {
        personList.add(p);
	return personList;
    }

    @PutMapping("/persons/{id}") // 修改该用户的所有属性
    public Object modify(@PathVariable("id") Integer id, @RequestBody Person person) {
        for (Person p : personList) {
	    if (p.getId() == id) {
		p.setName(person.getName());
	    }
	}
	return person;
    }
    
    @DeleteMapping("/persons/{id}")
    public Object delete(@PathVariable("id") Integer id) {
        for (Person p : personList) {
	    if (p.getId() == id) {
		personList.remove(p);
	    }
	}
	return personList;
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
如果在 Spring MVC 中无法获取到静态资源,可能是由于以下几种情况导致: 1. 静态资源目录不正确:确保你的静态资源文件位于正确的目录中。默认情况下,Spring Boot 会将静态资源放置在 `src/main/resources/static` 目录下。你可以检查该目录下是否存在你的静态资源文件。 2. 静态资源路径配置错误:如果你将静态资源放置在除了默认路径以外的其他位置,确保在配置类中正确配置了资源处理器。 - 如果你使用的是 Spring Boot,你可以在 `application.properties` 或 `application.yml` 文件中添加以下配置: ```properties spring.resources.static-locations=classpath:/static/, file:/path/to/custom/resources/ ``` 上述配置将静态资源的位置设置为默认的 `classpath:/static/` 目录以及自定义的 `/path/to/custom/resources/` 目录。 - 如果你使用的是自定义的 Spring MVC 配置类,你可以在 `addResourceHandlers` 方法中添加对应的资源处理器配置。例如: ```java @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/custom-resources/**") .addResourceLocations("classpath:/custom-resources/"); } ``` 上述配置将 `"/custom-resources/**"` 映射到 `classpath:/custom-resources/` 目录。 3. 静态资源请求路径不正确:确保你在浏览器中正确访问了静态资源的 URL。例如,如果你的静态资源文件名为 `styles.css`,你应该可以通过访问 `http://localhost:8080/styles.css` 来访问它。 4. 静态资源缓存问题:如果你曾经访问过相同的静态资源,并且浏览器缓存了该资源,可能导致无法获取到最新的资源。你可以尝试在浏览器中使用强制刷新(通常是按下 Ctrl + F5)来加载最新的静态资源。 请仔细检查以上情况,并逐一解决问题。如果问题仍然存在,请提供更多详细信息,以便我能够更好地帮助你解决问题。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值