java学习记录之struts2&struts标签&上传&国际化

本文详细介绍了Struts2框架中OGNL表达式与ActionContext和值栈的关系,以及如何在表单提交中使用。文章还讲解了Struts2的ModelDriven模式,数据标签如的使用,以及标签的功能。此外,文章讨论了调试工具,控制标签如的用法,以及表单标签如,并展示了它们如何简化表单处理和实现自动回显。最后,文章提到了Struts2的国际化支持和文件上传组件的使用。
摘要由CSDN通过智能技术生成

Strtus2 OGNL表达式的结合

描述: struts2为OGNL表达式准备了两个对象
ActionContext: 作为ognl表达式的Context
valueStack: 作为ognl表达式的Root
以上两个对象的创建
都是strutsPrepareAndExecuteFilter中准备好.

Ognl和Struts使用上的结合:

  1. 表单提交,其中提交的键可以看作是ognl表达式
    Action中有User对象,我们想直接将表单参数提交到User对象中封装,做法:
    1>提交的参数的键=> user.name 就会在值栈中查找名为user的对象,并赋值到该对象的name属性
    2>使用ModelDriven,我们的Action在getModel方法中将User对象返回.ModelDriven拦截器会将我们返回的User对象放入值栈中(栈顶),那么 在表单中直接提交name,将会把name值装入栈顶的user对象的name属性

  2. 文件下载中文件名乱码问题解决(配置文件中使用OGNL)

    application/zip
    is

    attachment;filename=“${fileName}”
    10240

  3. <s:debug>标签介绍

  4. Struts2标签中OGNL表达式(<s:properties>)

Struts2标签

打开struts-2.3.7\docs\WW\tag-reference.html可以看到Struts2提供的所有标签。其中分为“通用标签”和“UI标签”两大部分。我们不可能把所有标签都介绍到,我们只会介绍其中比较有用的几个标签介绍一下。

在这里插入图片描述
在这里插入图片描述

1 Struts2通用标签之数据标签

 <s:property>(重要)
<s:property>标签用来执行OGNL表达式,最为常用的方式是使用它在页面中输出ValueStack或ActionContext中的数据。
<s:property value=”#request.hello”/>,等于ActionContext.getContext().getRequest().get(“hello”)。

名称 必需 默认值 类型 说明
default 否 无 String 表示默认值。当结果为null时,输出默认值。
escape 否 true boolean 表示是否进行转义。该值为true时,当结果中包含<、>、”、’、&时,对其进行转义。
value 否 栈顶对象 Object Ognl表达式

 <s:set>
<s:set>标签用来创建一个变量,保存到指定的范围内。
<s:set var=”myVar” value=”#parameters.score[0]” scope=”page”/>,创建一个变量,保存到page范围,key为myVar,值为“#parameters.score[0]”的运算结果。

名称 必需 默认值 类型 说明
var 是 无 String 变量的域名字,默认不是Ognl表达式
value 否 栈顶对象 Object Ognl表达式。
scope 否 Action String 变量的范围。可选值为:application、session、request、page、action

scope的可选值中的action是我们陌生的范围,它是scope的默认值。它表示保存到request和OgnlContext两个范围中。即比request范围多出了一个OgnlContext范围。


<s:set var=”myVar” value=”#parameters.score[0]/>
<s:property value=”#myVar”/>等同于ActionContext.getContext().get(“myVar”);
<s:property value=”#request.myVar”/>等同于ActionContext.getContext.getReuqest().get(“myVar”);

 <s:push>
<s:push>标签是把指定值暂时压入到值栈中,当执行完<s:push>标签后,压入到值栈中的值会被弹出。
<s:push value=”’hello’”/>等于把hello字符串压入到值栈中后,马上又弹出了,相当于什么都没做。
<s:push value=”#session.user”>把user压入到值栈中
<s:property value=”username”/>打印值栈元素的username属性
<s:property value=”password”/>打印值栈元素的password属性
</s:push>把user从值栈中弹出

 <s:url>
<s:url>标签通常用来生成action路径,它与<c:url>标签很相似。
<s:url action=”TestAction”/>在页面中打印/contextpath/TestAction.action。也就是说它与<c:url>一样会生成全路径。而且无论给出后缀“.action”!action属性的值只需要与struts.xml文件中元素的name属性值相同即可。
<s:url action=”TestAction” namspace=”/” />还可以指定名称空间

<s:url action=”TestAction”>
<s:param name=”name” value=”’张三’”/>
</s:url>
页面中打印为:/ognl/TestAction.action?name=%E5%BC%A0%E4%B8%89
还可以为URL指定参数,其中参数包含中文时自动使用URL编码。
其中<s:param>是用来作为子标签的,它的作用是指定参数。它的value属性值为Ognl表达式,所以我们需要把“张三”用单引号引起来,表示Ognl表达式的字符串常量。

名称 必需 默认值 类型 说明
action 否 无 String 指定用于生成URL的action。
value 否 无 String 指定用于生成URL的地址值。通常只使用action或value其中一个属性。
var 否 无 String 如果指定了该属性,那么生成的URL不会输出到页面中,而是被保存到OgnlContext中。
method 否 无 String 指定调用action中的方法。如果使用的是value属性,那么该属性无效。
namespace 否 无 String 指定action所属的名称空间。
forceAddSchemeHostAndPort 否 false Boolean 当该属性为true时,生成的URL为绝对路径,而且会包含主机名及端口号。

 <s:a>
它用来生成超链接,与<s:url>相似!
<s:a action=”TestAction” namespace=”/”>添加用户
<s:param name=”name” value=”’张三’”/>
</s:a>

 <s:debug>
Debug标签用于调试,它在页面中生成一个“[Debug]”超链接,单击这个超链接,可以查看ValueStack和ActionContext中保存的所有对象。

2 Struts2通用标签之控制标签
控制标签很好理解,就是流程控制了。例如if、elseif等,以及iterator等。

 <s:if>、<s:elseif>、<s:else>
这种标签大家应该一看就会用了。我们直接给出个小例子看看。
<!—
在浏览器中输入:http://localhost:8080/tagtest/index.jsp?score=85
–>

<s:set name="score" value="#parameters.score[0]"/>
<s:property value="#score"/>: 
<s:if test="#score > 100 || #score < 0">
	<s:property value="'输入错误'"/>
</s:if>
<s:elseif test="#score >= 90">
	<s:property value="'A'" />
</s:elseif>
<s:elseif test="#score >= 80">
	<s:property value="'B'" />
</s:elseif>
<s:elseif test="#score >= 70">
	<s:property value="'C'" />
</s:elseif>
<s:elseif test="#score >= 60">
	<s:property value="'D'" />
</s:elseif>
<s:else>
	<s:property value="'E'"/>
</s:else>

 <s:iterator>
<s:iterator>标签可以用来迭代一个集合,可以迭代的集合有:Collection、Map、Enumeration、Iterator或者是数组。iterator标签在迭代过程中,会把当前对象暂时压入值栈,这样在子标签中就可以直接访问当前对象的属性(因为当前对象在栈顶),在标签体执行完毕后,位于栈顶的对象就会被删除,在循环的第二圈时,把新的当前对象再压入值栈中。


<s:iterator value="{'zhangSan','liSi','wangWu' }">
	name: <s:property/><br/>
</s:iterator>

如果为<s:iterator>标签指定了var属性,那么当前对象不只是压入到了值栈中,而且还会被添加到OgnlContext中。

<s:iterator value="{'zhangSan','liSi','wangWu' }" var="name">
	name: <s:property value="#name"/><br/>
</s:iterator>

名称 必需 默认值 类型 说明
var 否 无 String 如果指定了该属性,那么迭代的集合中的元素将被保存到OgnlContext中,可以通过该属性的值来引用集合中的元素。该属性几乎不被使用。
value 否 无 Coolection、Map、Enumeration、Iterator 或数组 指定迭代的集合。如果没有指定该属性,那么iterator标签将把位于值栈栈顶的对象放入一个新创建的List中进行迭代。
status 否 无 String 如果指定了该属性,一个IteratorStatus实例将被放入到OgnlContext中,通过该实例可以获取迭代过程中的一些状态信息。

IteratorStatus类在org.apahce.struts2.views.jsp包下。下面是对该类常用方法的介绍:
 public int getCount():得到当前已经迭代的元素的总数。
 public int getIndex():得到当前迭代的元素的索引。
 public boolean isEven():判断当前迭代的元素的个数是否是偶数。
 public boolean isOdd():判断当前迭代的元素的个数是否是奇数。
 public boolean isFirst():判断当前迭代的元素是否是第一个元素。
 public boolean isLast():判断当前迭代的元素是否是最后一个元素。

在OGNL表达式中使用IteratorStatus类的方法时,可以直接使用:count、index、even、odd、first、last属性。

<s:iterator value='{"one", "two", "three"}' status="status">
	<s:property value="#status.count"/>,
	<s:property value="#status.index"/>,
	<s:property value="#status.even"/>,
	<s:property value="#status.odd"/>,
	<s:property value="#status.first"/>,
	<s:property value="#status.last"/><br/>
</s:iterator>
<hr/>
<s:iterator value="#{'1':'one','2':'two','3':'three','4':'four'}" status="st">
<s:property value="key"/>:<s:property value="value"/><br/>
</s:iterator>

3 Struts2标签UI标签之表单标签

Struts2的表单标签还是比较好用的,但它也存在一些败笔,例如主题这一部分就不是很灵活。所以导致开发中没有公司会使用它提供的主题。
Struts2标签的优势:
 简化代码;
 自动数据回显;
 指定主题样式(说是优点,但很多人也会认为这是缺点);

  1. 表单标签入门
    我们首先使用一个登录表单来对比一下html表单和Struts2表单的区别。
<form action="<c:url value='/user/LoginAction.action'/>" method="post">
  用户名 <input type="text" name="username"/><br/>
  密 码 <input type="password" name="password"/><br/>
  <input type="submit" value="登录"/>
</form>
<hr/>
<s:form action="LoginAction" namespace="/user">
	<s:textfield name="username" label="用户名" />
	<s:password name="password" label="密 码" />
	<s:submit value="登录" />
</s:form>
 
<form action="/ognl/user/LoginAction.action" method="post">
  用户名 <input type="text" name="username"/><br/>
  密 码 <input type="password" name="password"/><br/>
  <input type="submit" value="登录"/>
</form>


<hr/>

<form id="LoginAction" name="LoginAction" action="/ognl/user/LoginAction.action" method="post">
<table class="wwFormTable">
	<tr>
    <td class="tdLabel"><label for="LoginAction_username" class="label">用户名:</label></td>
    <td
><input type="text" name="username" value="" id="LoginAction_username"/></td>
</tr>
 
	<tr>
    <td class="tdLabel"><label for="LoginAction_password" class="label">密 码:</label></td>
    <td
><input type="password" name="password" id="LoginAction_password"/></td>
</tr>
 
	<tr>
    <td colspan="2"><div align="right"><input type="submit" id="LoginAction_0" value="登录"/>
</div></td>
</tr>

</table></form>

通过上面生成的代码可以看来: 
 <s:form>
 通过action和namespace两部分来指定请求路径,action直接给元素的name值即可,无需给出后缀“.action”;
 method默认为post;
 会自动添加id属性,值与action属性值相同;
 整个表单会生成在

中。
 <s:textfield>
 对应标签;
 通过lable来生成标签;
 <s:password>
 对应标签;
 通过label来生成标签
 <s:submit>
 对应标签;

  1. 表单主题
    我们发现,整个表单都会在中生成,这也就说明无需为每个表单项添加
    。因为在表格中就无需再换行了。
    生成在表格中是因为<s:form>标签的theme属性的默认值为xhtml,它表示一个主题样式。这个主题样式由Freemarker模板来完成。如果你不希望使用这个xhtml主题,那么有下列3种方法来修改主题:
     在<s:textfield>的theme属性指定为simple,那么这个表单项就使用简单主题;
     在<s:form>的theme属性指定为simple,那么整个表单都使用简单主题;
     设置struts.ui.theme常量为simple,那么所有表单标签的默认主题都是simple。

当表单设置为theme=”simple”之后:
<s:form action=“LoginAction” namespace=“/user” theme=“simple”>
<s:textfield name=“username” label=“用户名” />
<s:password name=“password” label=“密 码” />
<s:submit value=“登录” />
</s:form>

这时没有表格来格式化表单,而且也不能生成,这说明样式都要自来来设定。好处是你重新获得了自由,坏处是一切都要靠自己了。

  1. 自动回显
    我们知道,当表单提交后,再返回到表单页面后,html标签不可能帮我们回显数据。而Struts2的表单标签可以做到这一点。原因是当前Action就在值栈顶,而表单标签会从值栈中获取数据来回显。

4 表单标签之选择性标签

  1. <s:redio>标签

表单标签中简化比较大的标签可以不在需要写循环,还有就是自动回显。我们都知道在下拉列表、单选、复选中,手动回显是比较麻烦的事。但使用Struts2的表单标签就方便多了。

<s:radio list=“#{‘male’:‘男’,‘female’:‘女’}” name=“gender”/>

list指定的是一个Map,默认key为实际值,而value为显示值。
也可以为list属性指定为list:

<s:radio list=“{‘男’,‘女’}” name=“gender”/>

这时实际值和显示值是相同的。

  1. <s:checkboxlist>标签

这个东西也很好用,一下子可以显示很多复选按钮。而且自动回显!
<s:checkboxlist list=“#{‘read’:‘看书’,‘netplay’:‘上网’,‘music’:‘音乐’ }” name=“hobby”/>

  1. <s:select>标签
    下拉列表与上面两个标签也一样,都是选择性的!使用它也是无需循环,无需处理循环。
    <s:select name=“city” list=“#{‘bj’:‘北京’,‘sh’:‘上海’,‘gz’:‘广州’}”/>

国际化

1 什么是国际化

一款软件可以为不同国家的来访者提供不同语言的界面,那么这个软件就是国际化的。这需要为每种上语言提供资源包(很多语言包),程序通过来访者的国家和语言来定位资源包。
基本名称_语言_国家.properties
例如:res_zh_CN.properties、res_en_US.properties

2 Struts2国际化文件分类

 全局国际化文件:整个程序都可以使用(最常用)
 特定包中可以使用(一个包中所有Action可以使用);
 特定Action中可以使用(一个Action可以使用);
 临时信息文件(JSP中i18n标签中可以使用)

3 国际化信息的应用场景

 JSP页面中使用国际化信息;
 Action中使用国际化信息;
 配置文件中使用国际化信息。

4 全局国际化

res_zh_CN.properties
#用户名
username=\u7528\u6237\u540D
#用户名不能为空
error.username=\u7528\u6237\u540D\u4E0D\u80FD\u4E3A\u7A7A

  1. 指定资源文件路径:
    在struts.xml文件中指定资源文件的路径:

    这说明在src目录下需要有res_语言_国家.properties文件。
这说明在src/cn/itcast/action目录下需要有res_语言_国家.properties文件。
  1. 在JSP中使用<s:text>标签获取资源文件信息

在JSP中使用

<s:fielderror />
<form action="<c:url value='Demo1Action.action'/>" method="post">
	<s:text name="username"/><input type="text" name="username"/><br/>
	<input type="submit" value="提交"/>
</form>

在Action中使用

public class Demo1Action extends ActionSupport {
	private String username;
	public String getUsername() {
		return username;
	}
	public void setUsername(String username) {
		this.username = username;
	}
	public String execute() throws Exception {
		System.out.println(this.getText("error.username"));
		return NONE;
	}
}

在配置文件中使用,例如在校验文件中使用。

<validators>
	<field name="username">
		<field-validator type="requiredstring">
			<message key="error.username" />
		</field-validator>
	</field>
</validators>

5 局部国际化文件

全局国际化文件是整个程序中都可以使用的,文件的位置需要在struts.xml文件中指定。而局部国际化文件不需要在struts.xml文件中指定,但文件的名称,以及位置是固定的。
局部国际化文件分为两种:特定Action中可以使用,以及特定包及子包中可以使用。

Action国际化文件
例如想提供一个只能在cn.itcast.action.TestAction中使用的国际化文件,那么就需要在cn.itcast.action包下创建TestAction_zh_CN.properties文件,这个文件的基本名称必须与Action的名称相同。
包国际化文件
例如想提供一个只能在cn.itcast.action包中使用的国际化文件,那么就需要在cn.itcast.action包下创建package_zh_CN.properties文件。这个文件也可以在cn.itcast.action包的子包中被使用。例如有Actoin的命名为:cn.itcast.action.demo1.Demo1Action,在这个Action中也可以cn.itcast.action.package_zh_CN.properties文件。

6 临时国际化文件

临时国际化文件是在<s:i18n>标签中使用。
<s:i18n name=“cn.itcast.demo1.action.res”>
<s:text name=“username” />
</s:i18n>

<s:i18n>标签的name属性指定国际化文件的位置,这说明需要在src/cn.itcast.demo1/action/目录下需要有res_zh_CN.properties文件。

7 在国际化文件中使用占位符

在国际化文件中使用占位符,例如在国际化文件中有如下内容:
login.error={0}或{1}不能为空
其中{0}和{1}就是点位符,这需要指定值来替换占位符。
String s = this.getText(“login.error”, new String[]{“用户名”, “密码”});
System.out.println(s);//用户名或密码不能为空。
上传

1 上传下载组件介绍
 jspSmartUpload(model1的年代);
 apache-commons-fileupload,Struts2默认上传组件;
 Servlet3.0使用的Part,但Servlet3.0还没有普及;
 COS,Struts2支持,不过已经停止更新很久了;
 pell,Struts2支持。

2 fileUpload的拦截器
Struts2默认使用的是commons-fileUpload组件完成上传的,使用Struts2会大量简化上传文件的开发。这一工作由fileUpload拦截器来完成。它会查看当前请求的enctype是否为multipart/form-data,如果不是就会直接“放行”;如果是,那么它会去解析表单,然后把解析的结果传递给Action的属性!
  fileUpload拦截器对会对Action提供很大的“帮助”,同时它也会对Action提出一些“小小的要求”。Action需要提供3个属性:
 File fieldName
 String fieldNameContentType
 String fieldNameFileName;

三个属性的前缀都(fieldName)必须与文件表单项名称一致,例如有文件表单项内容为:,其中表单项名称为:myUpload,那么Action就必须要有如下3个属性:
 private File myUpload
 private String myUploadContentType
 private String myUploadFileName

3 演示上传
上传文件首先我们需要给出一个页面,页面中要有一个表单:
upload.jsp

  <form action="<c:url value='/UploadAction.action'/>" method="post" enctype="multipart/form-data">
    	用户名: <input type="text" name="username"/><br/>
    	文 件:<input type="file" name="myUpload"/><br/>
        <input type="submit" value="Submit"/>
    </form>

UploadAction

public class UploadAction extends ActionSupport {
	private String username;
	
	public void setUsername(String username) {
		this.username = username;
	}

	private File myUpload;
	private String myUploadContentType;
	private String myUploadFileName;
	private String savepath = "/WEB-INF/uploads";

	public void setMyUpload(File myUpload) {
		this.myUpload = myUpload;
	}

	public void setMyUploadContentType(String myUploadContentType) {
		this.myUploadContentType = myUploadContentType;
	}

	public void setMyUploadFileName(String myUploadFileName) {
		this.myUploadFileName = myUploadFileName;
	}

	public String execute() throws Exception {
		System.out.println(username);
		System.out.println(this.myUploadContentType);
		System.out.println(this.myUploadFileName);
		System.out.println(this.myUpload.getAbsolutePath());
		this.savepath = ServletActionContext.getServletContext().getRealPath(savepath);
		File destFile = new File(savepath, myUploadFileName);
		FileUtils.copyFile(myUpload, destFile);
		return NONE;
	}
}

struts.xml

<package name="s8" namespace="/" extends="struts-default">
		<action name="UploadAction" class="cn.itcast.upload.action.UploadAction">
		</action>
	</package>

4 上传配置
可以通过Struts2的常量来完成对上传的配置,下面是与上传相关的常量:
 struts.multipart.parser:指定使用的上传组件,默认值为jakarta,表示使用commons-fileupload组件,Struts2还支持cos和pell;
 struts.multipart.saveDir:临时目录,如果没有指定临时目录,那么临时文件会在Tomcat的work目录中;
 struts.multipart.maxSize:整个大小限制,默认值为2097152,即2M。注意,这个限制是整个请求的大小,而不是单一文件的大小。
当上传的表单超出了限制时,拦截器会向fieldError中添加错误信息!当执行wokflow拦截器时,会发现fieldError中存在错误,这时就会跳转到input视图,所以我们需要为Action指定input视图。

fileUpload拦截器也有3个参数,我们可以给fileUpload拦截器配置这3个参数:
 maximumSize:上传的单个文件的大小限制;
 allowedTypes:允许上传文件的类型,多个类型以逗号隔开;
 allowedExtensions:允许上传文件的扩展名,多个扩展名以逗号隔开;

<struts>
	<constant name="struts.devMode" value="true" />
	<constant name="struts.multipart.maxSize" value="1048576" />
	<package name="s8" namespace="/" extends="struts-default">
		<action name="UploadAction" class="cn.itcast.upload.action.UploadAction">
			<result name="input">/demo1/upload.jsp</result>
			<param name="savepath">/WEB-INF/uploads</param>
			<interceptor-ref name="defaultStack">
				<!-- 限制单个文件大小上限为512K -->
				<param name="fileUpload.maximumSize">524288</param>
				<param name="fileUpload.allowedExtensions">jpg,png,bmp</param>
			</interceptor-ref>
		</action>
	</package>
</struts>

5 上传相关的错误信息国际化
在上传文件时如果出现错误,那么在input视图显示的错误信息都是英文的。如果想替换这些信息,需要知道这些错误信息的资源key,然后在我们自己的国际化资源文件中指定这些key的新值即可。
与上传相关的错误信息都在org.apache.struts2包下的struts-message.properties文件中。
struts.messages.error.uploading=Error uploading: {0}
struts.messages.error.file.too.large=The file is to large to be uploaded: {0} “{1}” “{2}” {3}
struts.messages.error.content.type.not.allowed=Content-Type not allowed: {0} “{1}” “{2}” {3}
struts.messages.error.file.extension.not.allowed=File extension not allowed: {0} “{1}” “{2}” {3}
struts.messages.upload.error.SizeLimitExceededException=Request exceeded allowed size limit! Max size allowed is: {0} but request was: {1}!

我们可以在src下res.properties文件,在这个文件中对象以上资源key进行替换。然后在struts.xml文件中给出即可。

6 多文件上传
当需要上传多个文件时,如果每个对应Action的3个属性,那么这会使Action的属性过多的现象。处理这一问题的方法是在表单中设置所有的名称为相同名称,然后在Action中给出数组属性即可。

uploads.jsp

  <s:fielderror />
    <s:actionerror/>
    <form action="<c:url value='/UploadsAction.action'/>" method="post" enctype="multipart/form-data">
    	用户名: <input type="text" name="username"/><br/>
    	文 件1<input type="file" name="myUpload"/><br/>
    	文 件2<input type="file" name="myUpload"/><br/>
    	文 件3<input type="file" name="myUpload"/><br/>
        <input type="submit" value="Submit"/>
    </form>

UploadsAction

public class UploadsAction extends ActionSupport {
	private File[] myUpload;
	private String[] myUploadContentType;
	private String[] myUploadFileName;
	private String savepath;
	public void setSavepath(String savepath) {
		this.savepath = savepath;
	}
	public void setMyUpload(File[] myUpload) {
		this.myUpload = myUpload;
	}
	public void setMyUploadContentType(String[] myUploadContentType) {
		this.myUploadContentType = myUploadContentType;
	}
	public void setMyUploadFileName(String[] myUploadFileName) {
		this.myUploadFileName = myUploadFileName;
	}
	public String execute() throws Exception {
		savepath = ServletActionContext.getServletContext().getRealPath(savepath);
		for(int i = 0; i < myUpload.length; i++) {
			System.out.println("文件名:" + myUploadFileName[i]);
			System.out.println("文件类型:" + myUploadContentType[i]);
			FileUtils.copyFile(myUpload[i], new File(savepath, myUploadFileName[i]));
		}
		return NONE;
	}
}

其中Struts2也允许我们使用List类型来代替数组属性。
UploadsAction.java

public class UploadsAction extends ActionSupport {
	private List<File> myUpload;
	private List<String> myUploadContentType;
	private List<String> myUploadFileName;}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

paterWang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值