Struts2入门+提升

什么是Struts

是流行和成熟的基于MVC设计模式的Web应用程序开源框架。

Struts使用目的

为了帮助我们减少在运用MVC设计模型来开发Web应用时间。

MVC模式

  1. JSP+JavaBean = Model1:适用于小型网站的开发
  2. JSP+Servlet+JavaBean = Model2:最典型的MVC模式
  3. MVC是模型(Model)、视图(View)和控制器(Controller)的缩写;MVC是一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。


MVC流程图

Struts2发展历史


Struts工作原理


1 客户端初始化一个指向Servlet容器(例如Tomcat)的请求
2 这个请求经过一系列的过滤器(Filter)(这些过滤器中有一个叫做ActionContextCleanUp的可选过滤器,这个过滤器对于Struts2和其他框架的集成很有帮助,例如:SiteMesh Plugin)
3 接着FilterDispatcher被调用,FilterDispatcher询问ActionMapper来决定这个请是否需要调用某个Action
4 如果ActionMapper决定需要调用某个Action,FilterDispatcher把请求的处理交给ActionProxy
5 ActionProxy通过Configuration Manager询问框架的配置文件,找到需要调用的Action类
6 ActionProxy创建一个ActionInvocation的实例。
7 ActionInvocation实例使用命名模式来调用,在调用Action的过程前后,涉及到相关拦截器(Intercepter)的调用。
8 一旦Action执行完毕,ActionInvocation负责根据struts.xml中的配置找到对应的返回结果。返回结果通常是(但不总是,也可 能是另外的一个Action链)一个需要被表示的JSP或者FreeMarker的模版。在表示的过程中可以使用Struts2 框架中继承的标签。在这个过程中需要涉及到ActionMapper

Struts2工作流程

1、客户端浏览器发出HTTP请求.
2、根据web.xml配置,该请求被FilterDispatcher接收
3、根据struts.xml配置,找到需要调用的Action类和方法, 并通过IoC方式,将值注入给Aciton
4、Action调用业务逻辑组件处理业务逻辑,这一步包含表单验证。
5、Action执行完毕,根据struts.xml中的配置找到对应的返回结果result,并跳转到相应页面
6、返回HTTP响应到客户端浏览器

Struts2核心文件

核心文件一:web.xml



核心文件二:struts.xml



核心文件三:struts.properties



Struts2入门

1.使用步骤:

(1)下载相关jar包
(2)创建Web项目
(3)创建并完善相关配置文件
(4)创建Action并测试启动

2.Structs官方网站

参考:

http://www.yiibai.com/struts_2/struts_examples.html

http://struts.apache.org/
http://people.apache.org/builds/struts/

3.导入的包:(共9个)

commons-fileupload(上传下载包)
commons-io(输入输出包)
commons-lang 3-3.2(基础包)
commons-logging(日志包)
freemarker(模板引擎,通过模板生成文本输出的通用工具)
structs2-core(核心包)
xwork-core(一些类基于xwork)
ognl(表达式)
javassist-3.11.0.GA.jar(解析java类文件的一个包)

引用包



4.配置web.xml文档

<!-- 定义过滤器 -->
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

5.创建struts.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
"http://struts.apache.org/dtds/struts-2.3.dtd">
<struts>
<package name="default" namespace="/" extends="struts-default">
<action name="helloworld" class="com.czq.action.HelloWorldAction">
<result>/result.jsp</result>
</action>
</package>
</struts>

6.创建action类

public class HelloWorldAction extends ActionSupport {
@Override
public String execute() throws Exception {
// TODO Auto-generated method stub
System.out.println("执行Action");
return SUCCESS;
}	
}
7.创建result.jsp页面

Action搜索顺序

Eg:http://localhost:8080/struts2/path1/path2/path3/student.action
第一步:判断package是否存在,如:path1/path2/path3/
如果package存在
第二步:则判断该package中action是否存在,如果不存在则去默认namespace的package里面寻找action
第三步:如果没有,则报错
如果package不存在:
第二步:检查上一级路径的package是否存在(直到默认namespace),重复第一步
第三步:如果没有则报错

动态方法调用

动态方法调用是为了解决一个Action对应多个请求的处理,以免Action太多。
三种方式:指定method属性(少用)、感叹号方式(不推荐)、通配符方式(建议使用)
第1种(指定method属性):
struts.xml
<action name="addAction" method="add" class="com.czq.action.HelloWorldAction">
<result>/add.jsp</result>
</action>
<action name="updateAction" method="update" class="com.czq.action.HelloWorldAction">
<result>/update.jsp</result>
</action>
第2种(感叹号方式):
<constant name="struts.enable.DynamicMethodInvocation" value="true"></constant>

<action name="helloworld" class="com.czq.action.HelloWorldAction">
<result>/result.jsp</result>
<result name="add">/add.jsp</result>
<result name="update">/update.jsp</result>
</action>

public String add(){
request.setAttribute("path", "update");
return "add";
}
public String update(){
return "update";
}


访问地址:http://localhost:8080/HelloWorld/helloworld!add(或update).action
第3种(通配符方式):
一个*代替
<action name="helloworld_*" method="{1}" class="com.czq.action.HelloWorldAction">
<result>/result.jsp</result>
<result name="add">/{1}.jsp</result>
<result name="update">/{1}.jsp</result>
</action>

访问地址:http://localhost:8080/HelloWorld/helloworld_add(或update).action
两个*代替
<action name="*_*" method="{2}" class="com.czq.action.{1}Action">
<result>/result.jsp</result>
<result name="add">/{2}.jsp</result>
<result name="update">/{2}.jsp</result>
</action>

访问地址:http://localhost:8080/HelloWorld/HelloWorld_update(或update).action
三个*代替
<action name="*_*_*" method="{2}" class="com.czq.{3}.{1}Action">
<result>/result.jsp</result>
<result name="add">/{2}.jsp</result>
<result name="update">/{2}.jsp</result>
</action>

访问地址:http://localhost:8080/HelloWorld/HelloWorld_add_action.action

默认Action

<default-action-ref name="index"></default-action-ref>
<action name="index">
<result>/error.jsp</result>
</action>

访问地址:http://localhost:8080/HelloWorld/aaas.action(随意的action)

Struts2后缀

1.strts.xml配置

<constant name = "strts.action.extension" value = "xxx"></constant>//name是固定的,value是指定的后缀,可以是空值
<constant name="struts.action.extension" value="html"></constant>

2.web.xml配置

<!-- 定义过滤器 -->
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
<init-param>
<param-name>struts.action.extension</param-name>
<param-value>do</param-value>
</init-param>
</filter>

3.struts.properties配置

struts.action.extension = xxxx,xxx,xx//可以配置多个,以,号分隔

访问地址:http://localhost:8080/HelloWorld/HelloWorld_add_action.html

接收参数

1、使用Action属性接受参数

login.jsp页面:



Action类:
public class LoginAction extends ActionSupport {

private String username;
private String password;
public String login(){
System.out.println(username);
return SUCCESS;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}

}

2, 使用DomainModel接受参数

login.jsp页面



Action类
public class LoginAction extends ActionSupport {

private User user;
public String login(){
System.out.println(user.getUsername());
return SUCCESS;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}	
}

3, 使用ModelDriver接受参数

login.jsp页面



User类
public class User {
private String username;
private String password;
private List<User> bookList;

public List<User> getBookList() {
return bookList;
}
public void setBookList(List<User> bookList) {
this.bookList = bookList;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}

}

Action类
public class LoginAction extends ActionSupport implements ModelDriven<User>{

private User user=new User();
public String login(){
System.out.println(user.getUsername());
System.out.println(user.getBookList().get(0));
System.out.println(user.getBookList().get(1));
return SUCCESS;
}

public User getModel() {
// TODO Auto-generated method stub
return user;
}	
}


Login.jsp页面



Action类
public class LoginAction extends ActionSupport implements ModelDriven<User>{

private User user=new User();
public String login(){
System.out.println(user.getUsername());
System.out.println(user.getBookList().get(0).getUsername());
System.out.println(user.getBookList().get(1).getUsername());
return SUCCESS;
}

public User getModel() {
// TODO Auto-generated method stub
return user;
}	
}

处理结果类型

处理流程



Struts2返回String,提供代码复用性,有利于框架分离



Action五种内置属性



Input结果类型

1.当参数类型转换错误时,如:age输入框中的类型是字母等情况,方法自动返回input
年龄:
<input type="text" name="age">

<result name="input">/login.jsp</result>
2.当action中存在addFiledError时
1)addFileError放在一般执行方法,addFieldError("", "");
语句后面有返回input的语句
if (user.getUsername()==null||"".equals(user.getUsername())) {
this.addFieldError("username", "用户名不能为空");
return INPUT;
}

2)addFileError放在validate()中
public void validate() {
// TODO Auto-generated method stub
if (user.getUsername()==null||"".equals(user.getUsername())) {
this.addFieldError("username", "用户名不能为空");
//	return INPUT;
}
}	

注:login.jsp页面
导入标签库:
<%@ taglib prefix="s" uri="/struts-tags" %>

用户名:
<input type="text" name="username">
<s:fielderror name="username"></s:fielderror>

处理结果的两种类型(位置):
1.局部结果:将<result/>作为<action/>元素的子元素配置
2.全局结果:将<result/>作为<global-result/>元素的子元素配置

result子标签



例:
<result name="add">
<param name="location">/${#request.path}.jsp</param>
<param name="parse">true</param>
</result>

private HttpServletRequest request=ServletActionContext.getRequest();
public String add(){
request.setAttribute("path", "update");
return "add";
}
public String update(){
return "update";
}

Result的属性type类型
type的默认值为dispatcher(转发),这个类型支持JSP视图技术
常用三个:chain,redirect,plaintext。
1、chain:将action和另外一个action链接起来。
2、redirect:重定向(会丢失请求参数)。
3、plaintext:返回网页源代码。
4、stream:返回inputstream用于文件下载。

什么是拦截器

Struts2大多数核心功能是通过拦截器实现的,每个拦截器完成某项功能。
拦截器方法在Action执行之前或者之后执行。

拦截器栈

从结构上看,拦截器栈相当于多个拦截器的组合。
从功能上看,拦截器栈也是拦截器。

拦截器的工作原理

拦截器的执行过程是一个递归的过程



自定义拦截器



拦截器示例

计算Action的执行时间:
思路:执行之后的时间-执行之前的时间= 执行Action消耗的时间
实现步骤:
创建拦截器
在配置文件中定义拦截器并引用它
1.定义拦截器
1.1.创建一个拦截器类继承自AbstractInterceptor类
1.2.实现intercept方法
public class TimerInterceptor extends AbstractInterceptor{

//自动调用此方法,进行拦截操作
@Override
public String intercept(ActionInvocation invocation) throws Exception {
// TODO Auto-generated method stub
//1.执行action之前的时间
long start=System.currentTimeMillis();
//2.执行下一个拦截器,如果已经是最后一个拦截器,则执行目标action
String result = invocation.invoke();
//3.执行action之后时间
long end=System.currentTimeMillis();
System.out.println("执行action花费的时间"+(end-start)+"ms");
return result;
}

}


2.<!-- 注册拦截器 -->
<interceptors>
<interceptor name="mytimer" class="com.czq.interceptor.TimerInterceptor"></interceptor>
</interceptors>
<action name="timer" class="com.czq.action.TimerAction">
<result>/success.jsp</result>

3.<!-- 引用拦截器 -->
<interceptor-ref name="mytimer"></interceptor-ref>
</action>

struts2内建拦截器




默认拦截器栈



注:默认的拦截器写在自定义拦截器前面
<!-- 为Action显示引用拦截器后,默认的拦截器defaultStack不再生效,需手工引用 -->
<interceptor-ref name="defaultStack"></interceptor-ref>
<!-- 引用拦截器 -->
<interceptor-ref name="mytimer"></interceptor-ref>

开发模式常用配置

<!-- 开启使用开发模式,详细错误提示 -->
<constant name="struts.devMode" value="true"/>
<!-- 指定每次请求到达,重新加载资源文件 -->
<constant name="struts.i18n.reload" value="true"/>
<!-- 指定每次配置文件更改后,自动重新加载 -->
<constant name="struts.configuration.xml.reload" value="true"/>
<!-- 指定XSLT Result使用样式表缓存 -->
<constant name="struts.xslt.nocache" value="true"/>


开发权限验证拦截器

1. 创建被访问的资源(如后台管理页面manager.jsp),将其放到WEB-INF下的page文件夹中

2. 创建auth的Action将请求转发到被访问资源(manager.jsp)
<action name="auth">
<result>/WEB-INF/page/manager.jsp</result>
<result name="login">/login.jsp</result>
<!-- 引用自定义的拦截器栈 -->
<interceptor-ref name="myStack"></interceptor-ref>
</action>

3. 创建登陆页面login.jsp接收用户登录信息
<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme() + "://"
+ request.getServerName() + ":" + request.getServerPort()
+ path + "/";
%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<base href="<%=basePath%>">

<title>用户登陆</title>

<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="expires" content="0">
<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
<meta http-equiv="description" content="This is my page">
<!--
<link rel="stylesheet" type="text/css" href="styles.css">
-->

</head>

<body>
${loginError}
<form action="LoginAction.action" method="post"> 
用户名:<input type="text" name="username">
<s:fielderror name="username"></s:fielderror>
密码:<input type="password" name="password"> 
<input type="submit" value="提交">
</form>
</body>
</html>


4.创建loginAction处理登陆请求,校验登录信息并将有效登陆信息或错误信息放到session中
public class LoginAction extends ActionSupport implements SessionAware{
private User user=new User();
private Map<String, Object> session;
public void setSession(Map<String, Object> session) {
// TODO Auto-generated method stub
this.session=session;
}	
//处理登陆请求
public String login(){	
if ("admin".equals(user.getUsername())&&"123".equals(user.getPassword())) {
session.put("loginInfo", user.getUsername());
return SUCCESS;
} else {
session.put("loginError", "用户名或密码不正确!");
return ERROR;
}
}
}


<action name="LoginAction" method="login" class="com.czq.action.LoginAction">
<result>/WEB-INF/page/manager.jsp</result>
<result name="error">/login.jsp</result>
</action>


5.创建拦截器authInterceptor对访问authAction的请求进行拦截处理,通过ActionContext获取会话session,并校验session的登陆信息,如果不为空,即获取权限,放行,否则拦截并转发到登陆界面
<!-- 注册拦截器 -->
<interceptors>
<interceptor name="auth" class="com.czq.interceptor.AuthInterceptor"></interceptor>
<interceptor-stack name="myStack">
<!-- 自定义拦截器栈myStack,组合了default和auth -->
<interceptor-ref name="defaultStack"></interceptor-ref>
<interceptor-ref name="auth"></interceptor-ref>
</interceptor-stack>	
</interceptors>

public class AuthInterceptor extends AbstractInterceptor {
@Override
public String intercept(ActionInvocation invocation) throws Exception {
// TODO Auto-generated method stub
ActionContext context=ActionContext.getContext();
Map<String, Object> session=context.getSession();
if (session.get("loginInfo")!=null) {
String result=invocation.invoke();
return result;
} else {
return "login";
}	
}
}

本文章的案例源码:链接: https://pan.baidu.com/s/1qXItba8 密码: i4d4






  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
完整版:https://download.csdn.net/download/qq_27595745/89522468 【课程大纲】 1-1 什么是java 1-2 认识java语言 1-3 java平台的体系结构 1-4 java SE环境安装和配置 2-1 java程序简介 2-2 计算机中的程序 2-3 java程序 2-4 java类库组织结构和文档 2-5 java虚拟机简介 2-6 java的垃圾回收器 2-7 java上机练习 3-1 java语言基础入门 3-2 数据的分类 3-3 标识符、关键字和常量 3-4 运算符 3-5 表达式 3-6 顺序结构和选择结构 3-7 循环语句 3-8 跳转语句 3-9 MyEclipse工具介绍 3-10 java基础知识章节练习 4-1 一维数组 4-2 数组应用 4-3 多维数组 4-4 排序算法 4-5 增强for循环 4-6 数组和排序算法章节练习 5-0 抽象和封装 5-1 面向过程的设计思想 5-2 面向对象的设计思想 5-3 抽象 5-4 封装 5-5 属性 5-6 方法的定义 5-7 this关键字 5-8 javaBean 5-9 包 package 5-10 抽象和封装章节练习 6-0 继承和多态 6-1 继承 6-2 object类 6-3 多态 6-4 访问修饰符 6-5 static修饰符 6-6 final修饰符 6-7 abstract修饰符 6-8 接口 6-9 继承和多态 章节练习 7-1 面向对象的分析与设计简介 7-2 对象模型建立 7-3 类之间的关系 7-4 软件的可维护与复用设计原则 7-5 面向对象的设计与分析 章节练习 8-1 内部类与包装器 8-2 对象包装器 8-3 装箱和拆箱 8-4 练习题 9-1 常用类介绍 9-2 StringBuffer和String Builder类 9-3 Rintime类的使用 9-4 日期类简介 9-5 java程序国际化的实现 9-6 Random类和Math类 9-7 枚举 9-8 练习题 10-1 java异常处理 10-2 认识异常 10-3 使用try和catch捕获异常 10-4 使用throw和throws引发异常 10-5 finally关键字 10-6 getMessage和printStackTrace方法 10-7 异常分类 10-8 自定义异常类 10-9 练习题 11-1 Java集合框架和泛型机制 11-2 Collection接口 11-3 Set接口实现类 11-4 List接口实现类 11-5 Map接口 11-6 Collections类 11-7 泛型概述 11-8 练习题 12-1 多线程 12-2 线程的生命周期 12-3 线程的调度和优先级 12-4 线程的同步 12-5 集合类的同步问题 12-6 用Timer类调度任务 12-7 练习题 13-1 Java IO 13-2 Java IO原理 13-3 流类的结构 13-4 文件流 13-5 缓冲流 13-6 转换流 13-7 数据流 13-8 打印流 13-9 对象流 13-10 随机存取文件流 13-11 zip文件流 13-12 练习题 14-1 图形用户界面设计 14-2 事件处理机制 14-3 AWT常用组件 14-4 swing简介 14-5 可视化开发swing组件 14-6 声音的播放和处理 14-7 2D图形的绘制 14-8 练习题 15-1 反射 15-2 使用Java反射机制 15-3 反射与动态代理 15-4 练习题 16-1 Java标注 16-2 JDK内置的基本标注类型 16-3 自定义标注类型 16-4 对标注进行标注 16-5 利用反射获取标注信息 16-6 练习题 17-1 顶目实战1-单机版五子棋游戏 17-2 总体设计 17-3 代码实现 17-4 程序的运行与发布 17-5 手动生成可执行JAR文件 17-6 练习题 18-1 Java数据库编程 18-2 JDBC类和接口 18-3 JDBC操作SQL 18-4 JDBC基本示例 18-5 JDBC应用示例 18-6 练习题 19-1 。。。
完整版:https://download.csdn.net/download/qq_27595745/89522468 【课程大纲】 1-1 什么是java 1-2 认识java语言 1-3 java平台的体系结构 1-4 java SE环境安装和配置 2-1 java程序简介 2-2 计算机中的程序 2-3 java程序 2-4 java类库组织结构和文档 2-5 java虚拟机简介 2-6 java的垃圾回收器 2-7 java上机练习 3-1 java语言基础入门 3-2 数据的分类 3-3 标识符、关键字和常量 3-4 运算符 3-5 表达式 3-6 顺序结构和选择结构 3-7 循环语句 3-8 跳转语句 3-9 MyEclipse工具介绍 3-10 java基础知识章节练习 4-1 一维数组 4-2 数组应用 4-3 多维数组 4-4 排序算法 4-5 增强for循环 4-6 数组和排序算法章节练习 5-0 抽象和封装 5-1 面向过程的设计思想 5-2 面向对象的设计思想 5-3 抽象 5-4 封装 5-5 属性 5-6 方法的定义 5-7 this关键字 5-8 javaBean 5-9 包 package 5-10 抽象和封装章节练习 6-0 继承和多态 6-1 继承 6-2 object类 6-3 多态 6-4 访问修饰符 6-5 static修饰符 6-6 final修饰符 6-7 abstract修饰符 6-8 接口 6-9 继承和多态 章节练习 7-1 面向对象的分析与设计简介 7-2 对象模型建立 7-3 类之间的关系 7-4 软件的可维护与复用设计原则 7-5 面向对象的设计与分析 章节练习 8-1 内部类与包装器 8-2 对象包装器 8-3 装箱和拆箱 8-4 练习题 9-1 常用类介绍 9-2 StringBuffer和String Builder类 9-3 Rintime类的使用 9-4 日期类简介 9-5 java程序国际化的实现 9-6 Random类和Math类 9-7 枚举 9-8 练习题 10-1 java异常处理 10-2 认识异常 10-3 使用try和catch捕获异常 10-4 使用throw和throws引发异常 10-5 finally关键字 10-6 getMessage和printStackTrace方法 10-7 异常分类 10-8 自定义异常类 10-9 练习题 11-1 Java集合框架和泛型机制 11-2 Collection接口 11-3 Set接口实现类 11-4 List接口实现类 11-5 Map接口 11-6 Collections类 11-7 泛型概述 11-8 练习题 12-1 多线程 12-2 线程的生命周期 12-3 线程的调度和优先级 12-4 线程的同步 12-5 集合类的同步问题 12-6 用Timer类调度任务 12-7 练习题 13-1 Java IO 13-2 Java IO原理 13-3 流类的结构 13-4 文件流 13-5 缓冲流 13-6 转换流 13-7 数据流 13-8 打印流 13-9 对象流 13-10 随机存取文件流 13-11 zip文件流 13-12 练习题 14-1 图形用户界面设计 14-2 事件处理机制 14-3 AWT常用组件 14-4 swing简介 14-5 可视化开发swing组件 14-6 声音的播放和处理 14-7 2D图形的绘制 14-8 练习题 15-1 反射 15-2 使用Java反射机制 15-3 反射与动态代理 15-4 练习题 16-1 Java标注 16-2 JDK内置的基本标注类型 16-3 自定义标注类型 16-4 对标注进行标注 16-5 利用反射获取标注信息 16-6 练习题 17-1 顶目实战1-单机版五子棋游戏 17-2 总体设计 17-3 代码实现 17-4 程序的运行与发布 17-5 手动生成可执行JAR文件 17-6 练习题 18-1 Java数据库编程 18-2 JDBC类和接口 18-3 JDBC操作SQL 18-4 JDBC基本示例 18-5 JDBC应用示例 18-6 练习题 19-1 。。。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值