Ant实践

 
Ant实践

eric (ericwq@263.net)


1. Ant
是什么?
2.
安装 Ant
3.
运行 Ant
4.
编写 build.xml
5.
内置 task(internet)
6. EAR task(internet)
7. WAR task(internet)
8. JUnit task(internet)

--------------------------------------------------------------------------------
1.Ant是什么?
--------------------------------------------------------------------------------

Ant
是一种基于 Java build 工具。理论上来说,它有些类似于( Unix C 中的 make ,但没有 make 的缺陷。

既然我们已经有了 make, gnumake, nmake, jam 以及其他的 build 工具为什么还要要一种新的 build 工具呢?因为 Ant 的原作者在多种 ( 硬件 ) 平台上开发软件时,无法忍受这些工具的限制和不便。类似于 make 的工具本质上是基于 shell (语言)的:他们计算依赖关系,然后执行命令(这些命令与你在命令行敲的命令没太大区别)。这就意味着你可以很容易地通过使用 OS 特有的或编写新的(命令)程序扩展该工具;然而,这也意味着你将自己限制在了特定的 OS ,或特定的 OS 类型上,如 Unix

Makefile
也很可恶。任何使用过他们的人都碰到过可恶的 tab 问题。 Ant 的原作者经常这样问自己: 是否我的命令不执行只是因为在我的 tab 前有一个空格?!! 。类似于 jam 的工具很好地处理了这类问题,但是(用户)必须记住和使用一种新的格式。

Ant
就不同了。与基于 shell 命令的扩展模式不同, Ant Java 的类来扩展。(用户)不必编写 shell 命令,配置文件是基于 XML 的,通过调用 target 树,就可执行各种 task 。每个 task 由实现了一个实现了特定 Task 接口的对象来运行。(如果你对 Ant 一点概念都没有的话,可能看不懂这一节,没有关系,后面会对 target,task 做详细的介绍。你如果没有太多的时间甚至可以略过这一节,然后再回来浏览一下这里的介绍,那时你就会看懂了。同样,如果你对 make 之类的工具不熟悉也没关系,下面的介绍根本不会用到 make 中的概念。)

必须承认,这样做,在构造 shell 命令时会失去一些特有的表达能力。如 `find . -name foo -exec rm {}` ,但却给了你跨平台的能力-你可以在任何地方工作。如果你真的需要执行一些 shell 命令, Ant 有一个 <exec> task ,这个 task 允许执行特定 OS 上的命令。
2.安装Ant
--------------------------------------------------------------------------------

由于 Ant 是一个 Open Source 的软件,所以有两种安装 Ant 的方式,一种是用已编译好的 binary 文件安装 Ant ,另一种是用源代码自己 build Ant

binary
形式的 Ant 可以从 http://jakarta.apache.org/builds/ant/release/v1.4.1/bin 下载。如果你希望你能自己编译 Ant ,则可从 http://jakarta.apache.org/builds/ant/release/v1.4.1/src 。注意所列出的连接都是最新发行版的 Ant 。如果你读到此文时,发现已经有了更新的版本,那么请用新版本。如果你是一个疯狂的技术追求者,你也可以从 Ant CVS repository 下载最新版本的 Ant

系统需求

要想自己 build Ant 。你需要一个 JAXP 兼容的 XML 解析器( parser )放在你的 CLASSPATH 系统变量中。

binary
形式的 Ant 包括最新版的 Apache Crimson XML 解析器。你可以从 http://java.sun.com/xml/ 得到更多的关于 JAXP 的信息。如果你希望使用其他的 JAXP 兼容的解析器。你要从 Ant lib 目录中删掉 jaxp.jar 以及 crimson.jar 。然后你可将你心爱的解析器的 jar 文件放到 Ant lib 目录中或放在你的 CLASSPATH 系统变量中。

对于当前版本的 Ant ,需要你的系统中有 JDK 1.1 版或更高。未来的 Ant 版本会要求使用 JDK 1.2 或更高版本。

安装 Ant

binary
版的 Ant 包括三个目录 :bin, docs lib 。只有 bin lib 目录是运行 Ant 所需的。要想安装 Ant ,选择一个目录并将发行版的文件拷贝到该目录下。这个目录被称作 ANT_HOME

在你运行 Ant 之前需要做一些配置工作。

bin 目录加入 PATH 环境变量。
设定 ANT_HOME 环境变量,指向你安装 Ant 的目录。在一些 OS 上, Ant 的脚本可以猜测 ANT_HOME Unix Windos NT/2000 )-但最好不要依赖这一特性。
可选地,设定 JAVA_HOME 环境变量(参考下面的高级小节),该变量应该指向你安装 JDK 的目录。

注意:不要将 Ant ant.jar 文件放到 JDK/JRE lib/ext 目录下。 Ant 是个应用程序,而 lib/ext 目录是为 JDK 扩展使用的(如 JCE JSSE 扩展)。而且通过扩展装入的类会有安全方面的限制。

可选 Task

Ant
支持一些可选 task 。一个可选 task 一般需要额外的库才能工作。可选 task Ant 的内置 task 分开,单独打包。这个可选包可以从你下载 Ant 的同一个地方下载。目前包含可选 task jar 文件名叫 jakarta-ant-1.4.1-optional.jar 。这个 jar 文件应该放到 Ant 安装目录的 lib 目录下。

每个可选 task 所需的外部库可参看依赖库小节。这些外部库可以放到 Ant lib 目录下,这样 Ant 就能自动装入,或者将其放入环境变量中。

Windows

假定 Ant 安装在 c:/ant/ 目录下。下面是设定环境的命令:

set ANT_HOME=c:/ant
set JAVA_HOME=c:/jdk1.2.2
set PATH=%PATH%;%ANT_HOME%/bin
Unix (bash)

假定 Ant 安装在 /usr/local/ant 目录下。下面是设定环境的命令:

export ANT_HOME=/usr/local/ant
export JAVA_HOME=/usr/local/jdk-1.2.2
export PATH=${PATH}:${ANT_HOME}/bin
高级

要想运行 Ant 必须使用很多的变量。你至少参考需要下面的内容:

Ant
CLASSPATH 必须包含 ant.jar 以及你所选的 JAXP 兼容的 XML 解析器的 jar 文件。
当你需要 JDK 的功能(如 javac rmic task )时,对于 JDK 1.1 JDK classes.zip 文件必须放入 CLASSPATH 中;对于 JDK 1.2 JDK 1.3 ,则必须加入 tools.jar 。如果设定了正确的 JAVA_HOME 环境变量, Ant 所带的脚本,在 bin 目录下,会自动加入所需的 JDK 类。
当你执行特定平台的程序(如 exec task cvs task )时,必须设定 ant.home 属性指向 Ant 的安装目录。同样, Ant 所带的脚本利用 ANT_HOME 环境变量自动设置该属性。
Building Ant

要想从源代码 build Ant ,你要先安装 Ant 源代码发行版或从 CVS checkout jakarta-ant 模块。

安装好源代码后,进入安装目录。

设定 JAVA_HOME 环境变量指向 JDK 的安装目录。要想知道怎么做请参看安装 Ant 小节。

确保你已下载了任何辅助 jar 文件,以便 build 你所感兴趣的 task 。这些 jar 文件可以放在 CLASSPATH 中,也可以放在 lib/optional 目录下。参看依赖库小节可知不同的 task 需要那些 jar 文件。注意这些 jar 文件只是用作 build Ant 之用。要想运行 Ant ,你还要像安装 Ant 小节中所做的那样设定这些 jar 文件。

现在你可以 build Ant 了:

build -Ddist.dir=<directory_to_contain_Ant_distribution> dist (Windows)
build.sh -Ddist.dir=<directory_to_contain_Ant_distribution> dist (Unix)

这样就可你指定的目录中创建一个 binary 版本。

上面的命令执行下面的动作:

如果有必要可以 bootstrap Ant 的代码。 bootstrap 包括手工编辑一些 Ant 代码以便运行 Ant bootstrap 用于下面的 build 步骤。
build 脚本传递参数以调用 bootstrap Ant 。参数定义了 Ant 的属性值并指定了 Ant 自己的 build.xml 文件的 "dist" target

大多数情况下,你不必直接 bootstrap Ant ,因为 build 脚本为你完成这一切。运行 bootstrap.bat (Windows) bootstrap.sh (UNIX) 可以 build 一个新的 bootstrap Ant


如果你希望将 Ant 安装到 ANT_HOME 目录下,你可以使用:

build install (Windows)
build.sh install (Unix)

如果你希望跳过冗长的 Javadoc 步骤,可以用:

build install-lite (Windows)
build.sh install-lite (Unix)

这样就只会安装 bin lib 目录。

注意 install install-lite 都会覆盖 ANT_HOME 中的当前 Ant 版本。

依赖库

如果你需要执行特定的 task ,你需要将对应的库放入 CLASSPATH 或放到 Ant 安装目录的 lib 目录下。注意使用 mapper 时只需要一个 regexp 库。同时,你也要安装 Ant 的可选 jar 包,它包含了 task 的定义。参考上面的安装 Ant 小节。

Jar Name Needed For Available At
An XSL transformer like Xalan or XSL:P style task http://xml.apache.org/xalan-j/index.html or http://www.clc-marketing.com/xslp/
jakarta-regexp-1.2.jar regexp type with mappers jakarta.apache.org/regexp/
jakarta-oro-2.0.1.jar regexp type with mappers and the perforce tasks jakarta.apache.org/oro/
junit.jar junit tasks www.junit.org
stylebook.jar stylebook task CVS repository of xml.apache.org
testlet.jar test task java.apache.org/framework
antlr.jar antlr task www.antlr.org
bsf.jar script task oss.software.ibm.com/developerworks/projects/bsf
netrexx.jar netrexx task www2.hursley.ibm.com/netrexx
rhino.jar javascript with script task www.mozilla.org
jpython.jar python with script task www.jpython.org
netcomponents.jar ftp and telnet tasks www.savarese.org/oro/downloads
3.运行Ant
--------------------------------------------------------------------------------

运行 Ant 非常简单,当你正确地安装 Ant 后,只要输入 ant 就可以了。

没有指定任何参数时, Ant 会在当前目录下查询 build.xml 文件。如果找到了就用该文件作为 buildfile 。如果你用 -find 选项。 Ant 就会在上级目录中寻找 buildfile ,直至到达文件系统的根。要想让 Ant 使用其他的 buildfile ,可以用参数 -buildfile file ,这里 file 指定了你想使用的 buildfile

你也可以设定一些属性,以覆盖 buildfile 中指定的属性值(参看 property task )。可以用 -Dproperty=value 选项,这里 property 是指属性的名称,而 value 则是指属性的值。也可以用这种办法来指定一些环境变量的值。你也可以用 property task 来存取环境变量。只要将 -DMYVAR=%MYVAR% (Windows) -DMYVAR=$MYVAR (Unix) 传递给 Ant -你就可以在你的 buildfile 中用 ${MYVAR} 来存取这些环境变量。

还有两个选项 -quite ,告诉 Ant 运行时只输出少量的必要信息。而 -verbose ,告诉 Ant 运行时要输出更多的信息。

可以指定执行一个或多个 target 。当省略 target 时, Ant 使用标签 <project> default 属性所指定的 target

如果有的话, -projecthelp 选项输出项目的描述信息和项目 target 的列表。先列出那些有描述的,然后是没有描述的 target

命令行选项总结:

ant [options] [target [target2 [target3] ...]]
Options:
-help print this message
-projecthelp print project help information
-version print the version information and exit
-quiet be extra quiet
-verbose be extra verbose
-debug print debugging information
-emacs produce logging information without adornments
-logfile file use given file for log output
-logger classname the class that is to perform logging
-listener classname add an instance of class as a project listener
-buildfile file use specified buildfile
-find file search for buildfile towards the root of the filesystem and use the first one found
-Dproperty=value set property to value
例子

ant

使用当前目录下的 build.xml 运行 Ant ,执行缺省的 target

ant -buildfile test.xml

使用当前目录下的 test.xml 运行 Ant ,执行缺省的 target

ant -buildfile test.xml dist

使用当前目录下的 test.xml 运行 Ant ,执行一个叫做 dist target

ant -buildfile test.xml -Dbuild=build/classes dist

使用当前目录下的 test.xml 运行 Ant ,执行一个叫做 dist target ,并设定 build 属性的值为 build/classes

文件

Unix 上, Ant 的执行脚本在做任何事之前都会 source (读并计算值) ~/.antrc 文件;在 Windows 上, Ant 的批处理文件会在开始时调用 %HOME%/antrc_pre.bat ,在结束时调用 %HOME%/antrc_post.bat 。你可以用这些文件配置或取消一些只有在运行 Ant 时才需要的环境变量。看下面的例子。

环境变量

包裹脚本( wrapper scripts )使用下面的环境变量(如果有的话):

JAVACMD Java
可执行文件的绝对路径。用这个值可以指定一个不同于 JAVA_HOME/bin/java(.exe) JVM
ANT_OPTS
传递给 JVM 的命令行变量-例如,你可以定义属性或设定 Java 堆的最大值

手工运行 Ant

如果你自己动手安装( DIY Ant ,你可以用下面的命令启动 Ant:

java -Dant.home=c:/ant org.apache.tools.ant.Main [options] [target]

这个命令与前面的 ant 命令一样。选项和 target 也和用 ant 命令时一样。这个例子假定你的 CLASSPATH 包含 :

ant.jar

jars/classes for your XML parser

the JDK's required jar/zip files
4.编写build.xml
--------------------------------------------------------------------------------

Ant
buildfile 是用 XML 写的。每个 buildfile 含有一个 project

buildfile
中每个 task 元素可以有一个 id 属性,可以用这个 id 值引用指定的任务。这个值必须是唯一的。(详情请参考下面的 Task 小节)

Projects

project
有下面的属性:

Attribute Description Required
name
项目名称 . No
default
当没有指定 target 时使用的缺省 target Yes
basedir
用于计算所有其他路径的基路径。该属性可以被 basedir property 覆盖。当覆盖时,该属性被忽略。如果属性和 basedir property 都没有设定,就使用 buildfile 文件的父目录。 No

项目的描述以一个顶级的 <description> 元素的形式出现(参看 description 小节)。

一个项目可以定义一个或多个 target 。一个 target 是一系列你想要执行的。执行 Ant 时,你可以选择执行那个 target 。当没有给定 target 时,使用 project default 属性所确定的 target

Targets

一个 target 可以依赖于其他的 target 。例如,你可能会有一个 target 用于编译程序,一个 target 用于生成可执行文件。你在生成可执行文件之前必须先编译通过,所以生成可执行文件的 target 依赖于编译 target Ant 会处理这种依赖关系。

然而,应当注意到, Ant depends 属性只指定了 target 应该被执行的顺序-如果被依赖的 target 无法运行,这种 depends 对于指定了依赖关系的 target 就没有影响。

Ant
会依照 depends 属性中 target 出现的顺序(从左到右)依次执行每个 target 。然而,要记住的是只要某个 target 依赖于一个 target ,后者就会被先执行。

<target name="A"/>
<target name="B" depends="A"/>
<target name="C" depends="B"/>
<target name="D" depends="C,B,A"/>

假定我们要执行 target D 。从它的依赖属性来看,你可能认为先执行 C ,然后 B ,最后 A 被执行。错了, C 依赖于 B B 依赖于 A ,所以先执行 A ,然后 B ,然后 C ,最后 D 被执行。

一个 target 只能被执行一次,即时有多个 target 依赖于它(看上面的例子)。

如果(或如果不)某些属性被设定,才执行某个 target 。这样,允许根据系统的状态( java version, OS, 命令行属性定义等等)来更好地控制 build 的过程。要想让一个 target 这样做,你就应该在 target 元素中,加入 if (或 unless )属性,带上 target 因该有所判断的属性。例如:

<target name="build-module-A" if="module-A-present"/>
<target name="build-own-fake-module-A" unless="module-A-present"/>

如果没有 if unless 属性, target 总会被执行。

可选的 description 属性可用来提供关于 target 的一行描述,这些描述可由 -projecthelp 命令行选项输出。

将你的 tstamp task 在一个所谓的初始化 target 是很好的做法,其他的 target 依赖这个初始化 target 。要确保初始化 target 是出现在其他 target 依赖表中的第一个 target 。在本手册中大多数的初始化 target 的名字是 "init"

target
有下面的属性:

Attribute Description Required
name target
的名字 Yes
depends
用逗号分隔的 target 的名字列表,也就是依赖表。 No
if
执行 target 所需要设定的属性名。 No
unless
执行 target 需要清除设定的属性名。 No
description
关于 target 功能的简短描述。 No

Tasks

一个 task 是一段可执行的代码。

一个 task 可以有多个属性(如果你愿意的话,可以将其称之为变量)。属性只可能包含对 property 的引用。这些引用会在 task 执行前被解析。

下面是 Task 的一般构造形式:

<name attribute1="value1" attribute2="value2" ... />

这里 name task 的名字, attributeN 是属性名, valueN 是属性值。

有一套内置的( built-in task ,以及一些可选 task ,但你也可以编写自己的 task

所有的 task 都有一个 task 名字属性。 Ant 用属性值来产生日志信息。

可以给 task 赋一个 id 属性:

<taskname id="taskID" ... />

这里 taskname task 的名字,而 taskID 是这个 task 的唯一标识符。通过这个标识符,你可以在脚本中引用相应的 task 。例如,在脚本中你可以这样:

<script ... >
task1.setFoo("bar");
</script>

设定某个 task 实例的 foo 属性。在另一个 task 中(用 java 编写),你可以利用下面的语句存取相应的实例。

project.getReference("task1").

注意 1 :如果 task1 还没有运行,就不会被生效(例如:不设定属性),如果你在随后配置它,你所作的一切都会被覆盖。

注意 2 :未来的 Ant 版本可能不会兼容这里所提的属性,因为很有可能根本没有 task 实例,只有 proxies

Properties

一个 project 可以有很多的 properties 。可以在 buildfile 中用 property task 来设定,或在 Ant 之外设定。一个 property 有一个名字和一个值。 property 可用于 task 的属性值。这是通过将属性名放在 "${" "}" 之间并放在属性值的位置来实现的。例如如果有一个 property builddir 的值是 "build" ,这个 property 就可用于属性值: ${builddir}/classes 。这个值就可被解析为 build/classes

内置属性

如果你使用了 <property> task 定义了所有的系统属性, Ant 允许你使用这些属性。例如, ${os.name} 对应操作系统的名字。

要想得到系统属性的列表可参考 the Javadoc of System.getProperties

除了 Java 的系统属性, Ant 还定义了一些自己的内置属性:
basedir project
基目录的绝对路径 ( <project> basedir 属性一样 )
ant.file buildfile
的绝对路径。
ant.version Ant
的版本。
ant.project.name
当前执行的 project 的名字;由 <project> name 属性设定 .
ant.java.version Ant
检测到的 JVM 的版本; 目前的值有 "1.1", "1.2", "1.3" and "1.4".
   
例子

<project name="MyProject" default="dist" basedir=".">

<!-- set global properties for this build -->
<property name="src" value="."/>
<property name="build" value="build"/>
<property name="dist" value="dist"/>
   
<target name="init">
<!-- Create the time stamp -->
<tstamp/>
<!-- Create the build directory structure used by compile -->
<mkdir dir="${build}"/>
</target>
 
<target name="compile" depends="init">
<!-- Compile the java code from ${src} into ${build} -->
<javac srcdir="${src}" destdir="${build}"/>
</target>

<target name="dist" depends="compile">
<!-- Create the distribution directory -->
<mkdir dir="${dist}/lib"/>
<!-- Put everything in ${build} into the MyProject-${DSTAMP}.jar file -->
<jar jarfile="${dist}/lib/MyProject-${DSTAMP}.jar" basedir="${build}"/>
</target>

<target name="clean">
<!-- Delete the ${build} and ${dist} directory trees -->
<delete dir="${build}"/>
<delete dir="${dist}"/>
</target>

</project>

Token Filters

一个 project 可以有很多 tokens ,这些 tokens 在文件拷贝时会被自动扩展,这要求在支持这一行为的 task 中选择过滤拷贝功能。这一功能可用 filter task buildfile 中设定。

既然这很可能是一个有危害的行为,文件中的 tokens 必须采取 @token@ 的形式,这里 token filter task 中设定的 token 名。这种 token 语法与其他 build 系统执行类似 filtering 的语法相同,而且与大多数的编程和脚本语言以及文档系统并不冲突,

注意:如果在一个文件中发现了一个 @token@ 形式的 token ,但没有 filter 与这个 token 关连,则不会发生任何事;因此,没有转义方法-但只要你为 token 选择合适的名字,就不会产生问题。

警告:如果你在拷贝 binary 文件时打开 filtering 功能,你有可能破坏文件。这个功能只针对文本文件。

Path-like Structures
你可以用 ":" ";" 作为分隔符,指定类似 PATH CLASSPATH 的引用。 Ant 会把分隔符转换为当前系统所用的分隔符。

当需要指定类似路径的值时,可以使用嵌套元素。一般的形式是

<classpath>
<pathelement path="${classpath}"/>
<pathelement location="lib/helper.jar"/>
</classpath>
location
属性指定了相对于 project 基目录的一个文件和目录,而 path 属性接受逗号或分号分隔的一个位置列表。 path 属性一般用作预定义的路径--其他情况下,应该用多个 location 属性。

为简洁起见, classpath 标签支持自己的 path location 属性。所以:

<classpath>
<pathelement path="${classpath}"/>
</classpath>
可以被简写作:

<classpath path="${classpath}"/>
也可通过 <fileset> 元素指定路径。构成一个 fileset 的多个文件加入 path-like structure 的顺序是未定的。

<classpath>
<pathelement path="${classpath}"/>
<fileset dir="lib">
<include name="**/*.jar"/>
</fileset>
<pathelement location="classes"/>
</classpath>
上面的例子构造了一个路径值包括: ${classpath} 的路径,跟着 lib 目录下的所有 jar 文件,接着是 classes 目录。

如果你想在多个 task 中使用相同的 path-like structure ,你可以用 <path> 元素定义他们(与 target 同级),然后通过 id 属性引用--参考 Referencs 例子。

path-like structure
可能包括对另一个 path-like structurede 的引用(通过嵌套 <path> 元素):

<path id="base.path">
<pathelement path="${classpath}"/>
<fileset dir="lib">
<include name="**/*.jar"/>
</fileset>
<pathelement location="classes"/>
</path>
<path id="tests.path">
<path refid="base.path"/>
<pathelement location="testclasses"/>
</path>

前面所提的关于 <classpath> 的简洁写法对于 <path> 也是有效的,如:

<path id="tests.path">
  <path refid="base.path"/>
<pathelement location="testclasses"/>
</path>
可写成:

<path id="base.path" path="${classpath}"/>
命令行变量

有些 task 可接受参数,并将其传递给另一个进程。为了能在变量中包含空格字符,可使用嵌套的 arg 元素。

Attribute Description Required
value
一个命令行变量;可包含空格字符。 只能用一个
line
空格分隔的命令行变量列表。
file
作为命令行变量的文件名;会被文件的绝对名替代。
path
一个作为单个命令行变量的 path-like 的字符串;或作为分隔符, Ant 会将其转变为特定平台的分隔符。

例子

<arg value="-l -a"/>
是一个含有空格的单个的命令行变量。

<arg line="-l -a"/>
是两个空格分隔的命令行变量。

<arg path="/dir;/dir2:/dir3"/>
是一个命令行变量,其值在 DOS 系统上为 /dir;/dir2;/dir3 ;在 Unix 系统上为 /dir:/dir2:/dir3

References

buildfile
元素的 id 属性可用来引用这些元素。如果你需要一遍遍的复制相同的 XML 代码块,这一属性就很有用--如多次使用 <classpath> 结构。

下面的例子:

<project ... >
<target ... >    
<rmic ...>      
<classpath>        
<pathelement location="lib/"/>        
<pathelement path="${java.class.path}/"/>        
<pathelement path="${additional.path}"/>      
</classpath>    
</rmic>  
</target>
<target ... >
<javac ...>
<classpath>
<pathelement location="lib/"/>
<pathelement path="${java.class.path}/"/>
<pathelement path="${additional.path}"/>
</classpath>
</javac>
</target>
</project>
可以写成如下形式:

<project ... >
<path id="project.class.path">  
<pathelement location="lib/"/>
<pathelement path="${java.class.path}/"/>  
<pathelement path="${additional.path}"/>
</path>
<target ... >
<rmic ...>
<classpath refid="project.class.path"/>
</rmic>
</target>
<target ... >
<javac ...>
<classpath refid="project.class.path"/>
</javac>
</target>
</project>
所有使用 PatternSets, FileSets path-like structures 嵌套元素的 task 也接受这种类型的引用。
 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值