版本 2.0.1.RELEASE
© 2017 Pivotal Software, Inc.
本文档的副本可以供您自己使用和分发给其他人,前提是您不对此类副本收取任何费用,并且进一步前提是每份副本都包含本版权声明,无论是印刷版还是电子版。
什么是弹簧壳?
并非所有应用程序都需要精美的 Web 用户界面!有时,使用交互式终端与应用程序交互是完成工作的最合适方式。
Spring Shell 允许人们轻松地创建这样一个可运行的应用程序,用户将在其中输入将执行的文本命令,直到程序终止。Spring Shell 项目提供了创建这样一个 REPL(读取、评估、打印循环)的基础设施,允许开发人员使用熟悉的 Spring 编程模型专注于命令实现。
解析、完成、输出着色、精美的 ascii-art 表格显示、输入转换和验证等高级功能TAB都是免费的,开发人员只需关注核心命令逻辑。
使用 Spring Shell
入门
要了解 Spring Shell 提供了什么,让我们编写一个简单的 shell 应用程序,它有一个简单的命令来将两个数字相加。
让我们编写一个简单的启动应用程序
从版本 2 开始,Spring Shell 已经从头开始重写,并考虑了各种增强功能,其中之一是与 Spring Boot 的轻松集成,尽管它不是一个强烈的要求。出于本教程的目的,让我们创建一个简单的 Boot 应用程序,例如使用start.spring.io。这个最小的应用程序只依赖spring-boot-starter
和配置spring-boot-maven-plugin
,生成一个可执行的 über-jar:
...
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
...
在 Spring Shell 上添加依赖项
使用 Spring Shell 的最简单方法是依赖spring-shell-starter
工件。这包含了使用 Spring Shell 所需的一切,并且可以很好地与 Boot 配合使用,只根据需要配置必要的 bean:
...
<dependency>
<groupId>org.springframework.shell</groupId>
<artifactId>spring-shell-starter</artifactId>
<version>2.0.1.RELEASE</version>
</dependency>
...
鉴于 Spring Shell 将通过存在此依赖关系启动并启动 REPL,您将需要 |
你的第一个命令
是时候添加我们的第一个命令了。创建一个新类(根据需要命名)并使用注释@ShellComponent
(其变体@Component
用于限制扫描候选命令的类集)。
然后,创建一个add
接受两个整数(a
和b
)并返回它们的总和的方法。使用注释对其进行注释@ShellMethod
并在注释中提供对命令的描述(唯一需要的信息):
package com.example.demo;
import org.springframework.shell.standard.ShellMethod;
import org.springframework.shell.standard.ShellComponent;
@ShellComponent
public class MyCommands {
@ShellMethod("Add two integers together.")
public int add(int a, int b) {
return a + b;
}
}
让我们试一试吧!
构建应用程序并运行生成的 jar,如下所示;
./mvnw clean install -DskipTests
[...]
java -jar target/demo-0.0.1-SNAPSHOT.jar
您将看到以下屏幕(横幅来自 Spring Boot,并且可以 像往常一样自定义):
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v1.5.6.RELEASE)
shell:>
下面是一个黄色shell:>
提示,邀请您输入命令。add 1 2
然后输入ENTER并欣赏魔法!
shell:>add 1 2
3
尝试使用 shell(提示:有一个help
命令),完成后,键入exit
ENTER.
本文档的其余部分深入探讨了整个 Spring Shell 编程模型。
编写自己的命令
Spring Shell 决定将方法转换为实际 shell 命令的方式是完全可插入的(请参阅扩展 Spring Shell),但从 Spring Shell 2.x 开始,推荐的编写命令的方式是使用本节中描述的新 API(所谓的标准API)。
使用标准API,bean 上的方法将转换为可执行命令,前提是
-
bean 类带有
@ShellComponent
注释。这用于限制所考虑的 bean 集。 -
该方法带有
@ShellMethod
注释。
是一个原型注解, 可以使用 |
一切都与文档有关!
@ShellMethod
注释唯一需要的属性是它的value
属性,应该用它来写一个简短的一句话,描述命令的作用。这一点很重要,这样您的用户就可以在不离开 shell 的情况下获得有关命令的一致帮助(请参阅使用命令的集成文档help
)。
您的命令描述应该简短,只有一两句话。为了更好的一致性,建议以大写字母开头,以点结尾。 |
自定义命令名称
默认情况下,不需要为您的命令指定键(即在 shell 中应该用来调用它的单词)。方法的名称将用作命令键,将驼峰式名称转换为虚线、gnu 样式的名称(sayHello()
即将变为say-hello
)。
但是,可以使用key
注释的属性显式设置命令键,如下所示:
@ShellMethod(value = "Add numbers.", key = "sum")
public int add(int a, int b) {
return a + b;
}
该 |
命令键可以包含几乎任何字符,包括空格。但是,在提出名称时,请记住用户通常会欣赏一致性(即避免将破折号名称与空格名称混合等) |
调用你的命令
按名称与位置参数
如上所示,装饰方法@ShellMethod
是创建命令的唯一要求。这样做时,用户可以通过两种可能的方式设置所有方法参数的值:
-
使用参数键(例如
--arg value
)。这种方法称为“按名称”参数 -
或者没有键,只需按照它们出现在方法签名中的相同顺序设置参数值(“位置”参数)。
这两种方法可以混合和匹配,命名参数总是优先(因为它们不太容易产生歧义)。因此,给定以下命令
@ShellMethod("Display stuff.")
public String echo(int a, int b, int c) {
return String.format("You said a=%d, b=%d, c=%d", a, b, c);
}
那么以下调用都是等效的,如输出所示:
shell:>echo 1 2 3 (1)
You said a=1, b=2, c=3
shell:>echo --a 1 --b 2 --c 3 (2)
You said a=1, b=2, c=3
shell:>echo --b 2 --c 3 --a 1 (3)
You said a=1, b=2, c=3
shell:>echo --a 1 2 3 (4)
You said a=1, b=2, c=3
shell:>echo 1 --c 3 2 (5)
You said a=1, b=2, c=3
1 | 这使用位置参数 |
2 | 这是完整的按名称参数的示例 |
3 | 按名称参数可以根据需要重新排序 |
4 | 您可以混合使用这两种方法 |
5 | 非按名称参数按它们出现的顺序解析 |
自定义命名参数键
如上所示,为命名参数派生密钥的默认策略是使用方法签名的 java 名称并在其前面加上两个破折号 ( --
)。这可以通过两种方式进行定制:
-
要更改整个方法的默认前缀,请使用注解的
prefix()
属性@ShellMethod
-
要以每个参数的方式覆盖整个键,请使用注释对参数进行
@ShellOption
注释。
看看下面的例子:
@ShellMethod(value = "Display stuff.", prefix="-")
public String echo(int a, int b, @ShellOption("--third") int c) {
return String.format("You said a=%d, b=%d, c=%d", a, b, c);
}
对于这样的设置,可能的参数键将-a
是-b
和--third
。
可以为单个参数指定多个键。如果是这样,这些将是指定相同参数的互斥方式(因此只能使用其中一种)。例如,这里是内置
|
可选参数和默认值
Spring Shell 提供了给参数默认值的能力,这将允许用户省略这些参数:
@ShellMethod("Say hello.")
public String greet(@ShellOption(defaultValue="World") String who) {
return "Hello " + who;
}
现在,该greet
命令仍然可以作为greet Mother
(或greet --who Mother
)调用,但以下也是可能的:
shell:>greet
Hello World
参数Arity
到目前为止,一直假设每个参数映射到用户输入的单个单词。但是,当参数值应该是多值时,可能会出现情况。这是由注释的arity()
属性驱动的。@ShellOption
只需使用集合或数组作为参数类型,并指定需要多少个值:
@ShellMethod("Add Numbers.")
public float add(@ShellOption(arity=3) float[] numbers) {
return numbers[0] + numbers[1] + numbers[2];
}
然后可以使用以下任何语法调用该命令:
shell:>add 1 2 3.3
6.3
shell:>add --numbers 1 2 3.3
6.3
使用按名称参数方法时,不应重复键。以下不起作用:
|
无限量
将要执行
布尔参数的特殊处理
当涉及到参数数量时,有一种默认情况下会接受特殊处理的参数,这在命令行实用程序中很常见。布尔(即boolean
以及java.lang.Boolean
)参数的行为就像默认情况下具有arity()
of一样0
,允许用户使用“标志”方法设置其值。看看以下内容:
@ShellMethod("Terminate the system.")
public String shutdown(boolean force) {
return "You said " + force;
}
这允许以下调用:
shell:>shutdown
You said false
shell:>shutdown --force
You said true
这种特殊处理与默认值规范配合得很好。尽管布尔参数的默认值为 |
具有这种隐式行为会
|
报价处理
Spring Shell 接受用户输入并将其标记为words,分割空格字符。如果用户想要提供包含空格的参数值,则需要引用该值。支持单 ( '
) 和双 ( "
) 引号,并且这些引号不会是值的一部分:
@ShellMethod("Prints what has been entered.")
public String echo(String what) {
return "You said " + what;
}
shell:>echo Hello
You said Hello
shell:>echo 'Hello'
You said Hello
shell:>echo 'Hello World'
You said Hello World
shell:>echo "Hello World"
You said Hello World
同时支持单引号和双引号允许用户轻松地将一种类型的引号嵌入到值中:
shell:>echo "I'm here!"
You said I'm here!
shell:>echo 'He said "Hi!"'
You said He said "Hi!"
如果用户需要嵌入用于引用整个参数的相同类型的引号,则转义序列使用反斜杠 ( \
) 字符:
shell:>echo 'I\'m here!'
You said I'm here!
shell:>echo "He said \"Hi!\""
You said He said "Hi!"
shell:>echo I\'m here!
You said I'm here!
不使用封闭引号时也可以转义空格字符,例如:
shell:>echo This\ is\ a\ single\ value
You said This is a single value
与外壳交互
Spring Shell 项目构建在JLine库之上,因此带来了许多不错的交互功能,本节将详细介绍其中的一些功能。
首先,Spring ShellTAB几乎在所有可能的地方都支持完成。因此,如果有echo
命令并且用户按下e, c,TAB则会echo
出现。如果有多个以 开头的命令,ec
则会提示用户进行选择(使用TAB或
Shift+TAB进行导航和ENTER选择。)
但是完成不会在命令键处停止。如果应用程序开发人员注册了适当的 bean,它也适用于参数键 ( --arg
) 甚至参数值(请参阅提供 TAB 完成建议)。
Spring Shell 应用程序的另一个不错的特性是支持行延续。如果一个命令及其参数太长并且不能很好地显示在屏幕上,用户可以将其分块并用反斜杠 ( \
) 字符终止一行,然后点击ENTER并继续下一行。提交整个命令后,这将被解析为好像用户在换行符处输入了一个空格。
shell:>register module --type source --name foo \ (1)
> --uri file:///tmp/bar
Successfully registered module 'source:foo'
1 | 命令在下一行继续 |
如果用户打开了引号(请参阅引号处理)并且ENTER仍在引号中时点击,则行继续也会自动触发:
shell:>echo "Hello (1)
dquote> World"
You said Hello World
1 | 用户按ENTER这里 |
最后,Spring Shell 应用程序受益于许多您在使用从 Emacs 借来的常规 OS Shell 时可能已经熟悉的键盘快捷键。值得注意的快捷方式包括Ctrl+r执行反向搜索,Ctrl+a和Ctrl+e分别移动到行首和行尾,或者Esc f一次 Esc b向前(或向后)移动一个单词。
提供 TAB 完成提案
待定
验证命令参数
Spring Shell 与Bean Validation API集成以支持对命令参数的自动和自记录约束。
在命令参数上找到的注释以及方法级别的注释将被接受并在命令执行之前触发验证。给定以下命令:
@ShellMethod("Change password.")
public String changePassword(@Size(min = 8, max = 40) String password) {
return "Password successfully set to " + password;
}
您将免费获得此行为:
外壳:>更改密码你好 未满足以下限制: --password 字符串:大小必须在 8 到 40 之间(你传递了 'hello')
适用于所有命令实现
重要的是要注意,通过使用适配器,bean 验证适用于所有命令实现,无论它们使用“标准”API 还是任何其他 API(请参阅支持其他 API) |
动态命令可用性
由于应用程序的内部状态,有时注册的命令可能没有意义。例如,可能有一个download
命令,但它只有在用户connect
在远程服务器上使用后才有效。现在,如果用户尝试使用该download
命令,shell 应该优雅地解释该命令确实存在,但当时它不可用。Spring Shell 允许开发人员这样做,甚至提供命令不可用原因的简短解释。
命令可以通过三种可能的方式来指示可用性。它们都利用了一个无参数方法,该方法返回一个Availability
. 让我们从一个简单的例子开始:
@ShellComponent
public class MyCommands {
private boolean connected;
@ShellMethod("Connect to the server.")
public void connect(String user, String password) {
[...]
connected = true;
}
@ShellMethod("Download the nuclear codes.")
public void download() {
[...]
}
public Availability downloadAvailability() {
return connected
? Availability.available()
: Availability.unavailable("you are not connected");
}
}
在这里您可以看到该connect
方法用于连接到服务器(细节省略),connected
完成后通过布尔值更改命令的状态。在用户连接之前,该download
命令将被标记为不可用,这要归功于存在与download
命令方法完全相同且Availability
名称中带有后缀的方法。Availability
该方法返回一个由两个工厂方法之一构造的实例。如果命令不可用,则必须提供解释。现在,如果用户在未连接时尝试调用命令,会发生以下情况:
shell:>download
Command 'download' exists but is not currently available because you are not connected.
Details of the error have been omitted. You can use the stacktrace command to print the full stacktrace.
当命令不可用时提供的原因如果附加在“因为……”之后应该很好读 最好不要以大写开头,也不要添加最后一个点。 |
如果由于某种原因在命令方法的名称之后命名可用性方法不适合您,您可以使用 提供明确的名称@ShellMethodAvailability
,如下所示:
@ShellMethod("Download the nuclear codes.")
@ShellMethodAvailability("availabilityCheck") (1)
public void download() {
[...]
}
public Availability availabilityCheck() { (1)
return connected
? Availability.available()
: Availability.unavailable("you are not connected");
}
1 | 名称必须匹配 |
最后,通常情况下,同一类中的多个命令共享相同的内部状态,因此应该全部可用或不可用。Spring Shell无需坚持@ShellMethodAvailability
所有命令方法,而是允许用户翻转事物并将@ShellMethodAvailabilty
注释放在可用性方法上,指定它控制的命令的名称:
@ShellMethod("Download the nuclear codes.")
public void download() {
[...]
}
@ShellMethod("Disconnect from the server.")
public void disconnect() {
[...]
}
@ShellMethodAvailability({"download", "disconnect"})
public Availability availabilityCheck() {
return connected
? Availability.available()
: Availability.unavailable("you are not connected");
}
该
|
Spring Shell 并没有对如何编写命令和如何组织类施加太多限制。但是将相关命令放在同一个类中通常是一种很好的做法,可用性指标可以从中受益。 |
组织命令
当您的 shell 开始提供很多功能时,您可能会遇到很多命令,这可能会让您的用户感到困惑。键入help
他们会看到一个令人生畏的命令列表,按字母顺序排列,这可能并不总是有意义的。
为了缓解这种情况,Spring Shell 提供了将命令组合在一起的能力,并具有合理的默认值。然后相关的命令将在同一个组中结束(例如 User Management Commands
)并一起显示在帮助屏幕和其他地方。
默认情况下,命令将根据实现它们的类进行分组,将驼峰式类名转换为单独的单词(因此URLRelatedCommands
变为URL Related Commands
)。这是一个非常明智的默认设置,因为相关命令通常已经在类中,因为它们需要使用相同的协作对象。
但是,如果此行为不适合您,您可以通过以下方式覆盖命令的组,按优先级顺序排列:
-
group()
在@ShellMethod
注释中指定 a -
将 a
@ShellCommandGroup
放在定义命令的类上。这将为该类中定义的所有命令应用组(除非如上所述被覆盖) -
将 a
@ShellCommandGroup
放在定义命令的包(viapackage-info.java
)上。这将适用于包中定义的所有命令(除非如上所述在方法或类级别被覆盖)
这是一个简短的例子:
public class UserCommands {
@ShellMethod(value = "This command ends up in the 'User Commands' group")
public void foo() {}
@ShellMethod(value = "This command ends up in the 'Other Commands' group",
group = "Other Commands")
public void bar() {}
}
...
@ShellCommandGroup("Other Commands")
public class SomeCommands {
@ShellMethod(value = "This one is in 'Other Commands'")
public void wizz() {}
@ShellMethod(value = "And this one is 'Yet Another Group'",
group = "Yet Another Group")
public void last() {}
}
内置命令
spring-shell-starter
使用工件(或更准确地说,依赖项)构建的任何应用程序spring-shell-standard-commands
都带有一组内置命令。这些命令可以单独覆盖或禁用(请参阅覆盖或禁用内置命令),但如果不是,本节将描述它们的行为。
help
使用命令集成文档
运行 shell 应用程序通常意味着用户处于图形受限的环境中。尽管在移动电话时代,我们始终保持连接,但访问 Web 浏览器或任何其他丰富的 UI 应用程序(例如 pdf 查看器)可能并不总是可行的。这就是为什么正确地自我记录 shell 命令很重要的原因,这就是help
命令的用武之地。
键入help
+ENTER将列出所有已知的 shell 命令(包括不可用的命令)以及它们的作用的简短描述:
shell:>help
AVAILABLE COMMANDS
add: Add numbers together.
* authenticate: Authenticate with the system.
* blow-up: Blow Everything up.
clear: Clear the shell screen.
connect: Connect to the system
disconnect: Disconnect from the system.
exit, quit: Exit the shell.
help: Display help about available commands.
register module: Register a new module.
script: Read and execute commands from a file.
stacktrace: Display the full stacktrace of the last error.
Commands marked with (*) are currently unavailable.
Type `help <command>` to learn more.
键入help <command>
将显示有关命令的更详细信息,包括可用参数、它们的类型以及它们是否强制等。
这是help
应用于自身的命令:
外壳:>帮助帮助 姓名 help - 显示有关可用命令的帮助。 概要 帮助 [[-C] 字符串] 选项 -C 或 --command 字符串 获取帮助的命令。[可选,默认 = <无>]
清屏
该clear
命令执行您所期望的操作并清除屏幕,重置左上角的提示。
退出壳牌
该quit
命令(也别名为exit
)只是请求 shell 退出,优雅地关闭 Spring 应用程序上下文。如果没有被覆盖,JLine History
bean 会将执行的所有命令的历史记录写入磁盘,以便它们在下次启动时再次可用(请参阅与 Shell 交互)。
显示有关错误的详细信息
当命令代码内部发生异常时,它会被 shell 捕获并显示一条简单的单行消息,以免向用户提供过多的信息。但在某些情况下,了解究竟发生了什么很重要(尤其是在异常有嵌套原因的情况下)。
为此,Spring Shell 会记住上次发生的异常,用户稍后可以使用该stacktrace
命令在控制台上打印所有血腥细节。
运行一批命令
该script
命令接受一个本地文件作为参数,并将重放在那里找到的命令,一次一个。
从文件中读取的行为与交互式 shell 中的行为完全相同,因此以开头的//
行将被视为注释并被忽略,而以结尾的\
行将触发行继续。
自定义外壳
覆盖或禁用内置命令
Spring Shell 提供了内置命令,以实现许多(如果不是所有)shell 应用程序都需要的日常任务。如果您对它们的行为方式不满意,您可以禁用或覆盖它们,如本节所述。
禁用所有内置命令
如果您根本不需要内置命令,那么有一种简单的方法可以“禁用”它们:不要包含它们!要么使用 maven 排除,
|
禁用特定命令
要禁用单个内置命令,只需在应用程序
中将spring.shell.command.<command>.enabled
属性设置为。一种简单的方法是将额外的参数传递给入口点中的 Boot 应用程序:false
Environment
main()
public static void main(String[] args) throws Exception {
String[] disabledCommands = {"--spring.shell.command.help.enabled=false"}; (1)
String[] fullArgs = StringUtils.concatenateStringArrays(args, disabledCommands);
SpringApplication.run(MyApp.class, fullArgs);
}
1 | 这将禁用集成help 命令 |
覆盖特定命令
如果您宁愿提供自己的实现而不是禁用命令,那么您可以
-
禁用上述命令,并使用相同的名称注册您的实现
-
让您的实现类实现
<Command>.Command
接口。例如,以下是如何覆盖clear
命令:public class MyClear implements Clear.Command { @ShellMethod("Clear the screen, only better.") public void clear() { // ... } }
请考虑贡献您的更改
如果您觉得您的标准命令的实现可能对社区有价值,请考虑在github.com/spring-projects/spring-shell上打开一个拉取请求。 或者,在您自己进行任何更改之前,您可以打开项目的问题。随时欢迎反馈! |
结果处理程序
提示提供者
每次调用命令后,shell 都会等待用户的新输入,并以黄色显示提示:
shell:>
可以通过注册一个 bean 类型来定制这种行为PromptProvider
。这样的 bean 可以使用内部状态来决定向用户显示什么(例如,它可以对应用程序事件做出反应)并且可以使用 JLineAttributedCharSequence
来显示花哨的 ANSI 文本。
这是一个虚构的例子:
@Component
public class CustomPromptProvider implements PromptProvider {
private ConnectionDetails connection;
@Override
public AttributedString getPrompt() {
if (connection != null) {
return new AttributedString(connection.getHost() + ":>",
AttributedStyle.DEFAULT.foreground(AttributedStyle.YELLOW));
}
else {
return new AttributedString("server-unknown:>",
AttributedStyle.DEFAULT.foreground(AttributedStyle.RED));
}
}
@EventListener
public void handle(ConnectionUpdatedEvent event) {
this.connection = event.getConnectionDetails();
}
}
自定义命令行选项行为
Spring Shell 带有两个默认的 Spring Boot ApplicationRunners
:
-
InteractiveShellApplicationRunner
引导 Shell REPL。它设置 JLine 基础设施并最终调用Shell.run()
-
ScriptShellApplicationRunner
查找以 开头的程序参数,假定它们是本地文件名并尝试运行包含在这些文件中的命令(与脚本命令@
具有相同的语义),然后退出进程(通过有效禁用,见下文)。InteractiveShellApplicationRunner
如果此行为不适合您,只需提供一个(或多个)类型的 beanApplicationRunner
并可选择禁用标准的 bean。您将希望从以下方面获得灵感ScriptShellApplicationRunner
:
@Order(InteractiveShellApplicationRunner.PRECEDENCE - 100) // Runs before InteractiveShellApplicationRunner
public class ScriptShellApplicationRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
List<File> scriptsToRun = args.getNonOptionArgs().stream()
.filter(s -> s.startsWith("@"))
.map(s -> new File(s.substring(1)))
.collect(Collectors.toList());
boolean batchEnabled = environment.getProperty(SPRING_SHELL_SCRIPT_ENABLED, boolean.class, true);
if (!scriptsToRun.isEmpty() && batchEnabled) {
InteractiveShellApplicationRunner.disable(environment);
for (File file : scriptsToRun) {
try (Reader reader = new FileReader(file);
FileInputProvider inputProvider = new FileInputProvider(reader, parser)) {
shell.run(inputProvider);
}
}
}
}
...
自定义参数转换
从文本输入到实际方法参数的转换使用标准的 Spring
转换机制。Spring Shell 安装一个新的DefaultConversionService
(启用了内置转换器)并向其注册任何类型的 bean Converter<S, T>
,GenericConverter
或者
ConverterFactory<S, T>
它在应用程序上下文中找到的任何 bean。
这意味着自定义转换为自定义类型的对象非常容易Foo
:只需在上下文中安装一个Converter<String, Foo>
bean。
@ShellComponent
class ConversionCommands {
@ShellMethod("Shows conversion using Spring converter")
public String conversionExample(DomainObject object) {
return object.getClass();
}
}
class DomainObject {
private final String value;
DomainObject(String value) {
this.value = value;
}
public String toString() {
return value;
}
}
@Component
class CustomDomainConverter implements Converter<String, DomainObject> {
@Override
public DomainObject convert(String source) {
return new DomainObject(source);
}
}
注意你的字符串表示
如上例所示,如果您可以让您的
有关详细信息,请参阅验证命令参数。 |
如果你想
|