Spring AI 2.0 源码解析(三):ChatClient 的 Fluent API 如何构建请求?
Spring AI 2.0 æºç è§£æï¼ä¸ï¼ï¼ChatClient ç Fluent API å¦ä½æå»ºè¯·æ±ï¼
ä¸åªå«ç ¤ççç« 2026-08-21 0 é 读9åéè¿æ¯ä»è¿å è¡ä»£ç å¼å§
String content = chatClient.prompt()
.system(s -> s.text("ä½ æ¯ {role}")
.param("role", "Java 婿"))
.user(u -> u.text("ç¨ {language} è§£é {topic}")
.param("language", "䏿")
.param("topic", "Builder"))
.options(OpenAiChatOptions.builder().temperature(0.2))
.call()
.content();
åä¸¤ç« å·²ç»æè¿æ¡è°ç¨é¾è·¯ä»å¤´å°å°¾çäºä¸éï¼ä¸é´çäºå 个é®é¢ï¼ç°å¨æä»¬å头æ¥çï¼åªç prompt() å° call() ä¹é´åçäºä»ä¹ã
è¿æ®µä»£ç ä¸åºç°ç system()ãuser()ãmessages()ãadvisors()ãtools() ç Fluent APIï¼å¤§å¤æ°å¹¶ä¸ä¼ç«å³å建æ°ç请æ±å¯¹è±¡ï¼èæ¯å¨æç»ä¿®æ¹åä¸ä¸ª DefaultChatClientRequestSpecã
模æ¿åæ°ä¹ä¸ä¼å¨è°ç¨ param() æ¶é©¬ä¸å®ææ¿æ¢ã
æ´ä¸ª Fluent é¶æ®µåªæ¯æ¶é请æ±ç¶æï¼ç´å° call() æ stream() æ§è¡æ¶ï¼è¿äºåæ£ä¿åçä¿¡æ¯æä¼è¢«ç»ä¸ç»è£
为æç»ç Prompt + contextã
æä»¥è¿ä¸ç« è¦è§£å³å ä¸ªå ·ä½é®é¢ï¼
- Builder çé»è®¤é ç½®ä¿åå¨åªéï¼
build()å°åºå¤å¶äºä»ä¹ï¼- 为ä»ä¹æ¯æ¬¡
prompt()é½è¦å¤å¶ RequestSpecï¼ - é»è®¤ systemãuser åæ¬æ¬¡ systemãuser æ¯è¿½å è¿æ¯è¦çï¼
- messagesãmediaãoptionsãadvisorsãtoolsãtoolContext å嫿¾å¨åªéï¼
param()ä»ä¹æ¶åçæ£æ¸²æï¼- åªäºå¯¹è±¡å¯ä»¥å¤ç¨ï¼åªäºå¯¹è±¡åªè½å±äºä¸æ¬¡è¯·æ±ã
æå°å¯è¿è¡ç¤ºä¾
æ¬ç« ä¸éè¦çå®è°ç¨ OpenAIçæ¨¡åè¿è¡å¯¹è¯ï¼å ·ä½å¯ä»¥çä¸é¢ç代ç å®ä¾ã
ä¸é¢ç ChatModel åªæ¯ææç» Prompt æå°åºæ¥ï¼ç¶åè¿åä¸ä¸ªåºå®ç»æãè¿æ ·å¯ä»¥åç¬è§å¯ Fluent API çç»è£
è¡ä¸ºï¼ä¸æ¶åä»»ä½ä¾åºåç SDK ã
import java.util.List;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.model.Generation;
import org.springframework.ai.chat.prompt.ChatOptions;
import org.springframework.ai.chat.prompt.Prompt;
public class FluentApiDemo {
public static void main(String[] args) {
ChatModel chatModel = new InspectingChatModel();
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultSystem(s -> s.text("ä½ æ¯ {role}").param("role", "Java 婿"))
.build();
String content = chatClient.prompt()
.user(u -> u.text("ç¨ {language} è§£é {topic}")
.param("language", "䏿")
.param("topic", "Builder"))
.call()
.content();
System.out.println(content);
}
static final class InspectingChatModel implements ChatModel {
private final ChatOptions options = ChatOptions.builder().build();
@Override
public ChatResponse call(Prompt prompt) {
prompt.getInstructions().forEach(message ->
System.out.println(message.getMessageType() + ": " + message.getText()));
return new ChatResponse(List.of(new Generation(new AssistantMessage("å·²æ¶å°"))));
}
@Override
public ChatOptions getOptions() {
return this.options;
}
}
}
è¾åºçæ ¸å¿å 容æ¯ï¼
SYSTEM: ä½ æ¯ Java 婿
USER: ç¨ä¸æè§£é Builder
å·²æ¶å°
éè¿è¿æ¡è°ç¨é¾å¯ä»¥ç¡®è®¤ä¸¤ä¸ªå
³é®ç¹ï¼é»è®¤ system ä¼è¿å
¥å®é
请æ±ï¼è模æ¿åéæ¿æ¢åç卿¨¡åè°ç¨ä¹åï¼æ¨¡åæ¥æ¶å°çå·²ç»æ¯æ¸²æåç Promptã
å çæ´ä½ç»æ
flowchart LR
A["DefaultChatClientBuilder<br>ææ defaultRequest"] --> B["defaultSystem / defaultUser / defaultOptions"]
B --> C["build()"]
C --> D["DefaultChatClient<br>ææåä¸ä¸ª defaultRequest å¼ç¨"]
D --> E["prompt()"]
E --> F["å¤å¶ DefaultChatClientRequestSpec"]
F --> G["system / user / messages / advisors / tools"]
G --> H["call() æ stream()"]
H --> I["DefaultChatClientUtils.toChatClientRequest()"]
I --> J["ChatClientRequest<br>Prompt + Advisor context"]
è¿éé¢æä¸ªç»èè¦æ³¨æï¼build() 没æå¤å¶ defaultRequestã
é»è®¤é ç½®å¦ä½è¿å ¥å次请æ±
Builder ä¿åçå 容
DefaultChatClientBuilder èªå·±åªæä¸ä¸ªçæ£å
³é®çæåï¼
protected final DefaultChatClientRequestSpec defaultRequest;
æé æ¹æ³ä¼åå§åä¸ä»½ç©ºç RequestSpecï¼å¹¶å° ChatModelãObservationãæ¨¡æ¿æ¸²æå¨ãTool Calling Advisor Builder çè¯·æ±æ§è¡æéçåºç¡ç»ä»¶æ³¨å
¥è¿å»ã
è½ç¶æé åæ°æ°éè¾å¤ï¼ä½ä»èè´£ä¸çï¼å¯ä»¥å½çº³ä¸ºå ç±»ï¼
| åç» | RequestSpec ä¸çåæ®µ | ç¨é |
|---|---|---|
| 模åä¸è§æµ | chatModelãobservationRegistryã两个 convention | åç»æ§è¡åè§æµ |
| System | systemTextãsystemParamsãsystemMetadata | é»è®¤ææ¬æ¬¡ system message |
| User | userTextãuserParamsãuserMetadataãmedia | é»è®¤ææ¬æ¬¡ user message |
| æ¾å¼æ¶æ¯ | messages | å·²ç»æé 好ç Message å表 |
| Options | optionsCustomizer | 卿¨¡åé»è®¤ Options ä¸ç»§ç»åå¹¶ |
| Advisor | advisorsãadvisorParams | Advisor å表åé¾è·¯ context |
| Tool | toolCallbacksãtoolCallbackProvidersãtoolContext | å·¥å ·å®ä¹åæ§è¡ä¸ä¸æ |
| æ¨¡æ¿ | templateRenderer | system/user ææ¬çå»¶è¿æ¸²æ |
Builder 䏿æ defaultXxx æ¹æ³ï¼å ä¹é½æ¯æåæ°è½¬äº¤ç»è¿ä»½ defaultRequestï¼
public Builder defaultSystem(String text) {
this.defaultRequest.system(text);
return this;
}
public Builder defaultAdvisors(Advisor... advisors) {
this.defaultRequest.advisors(advisors);
return this;
}
public Builder defaultToolContext(Map<String, Object> toolContext) {
this.defaultRequest.toolContext(toolContext);
return this;
}
è¿éæä¸ä¸ªå®¹æè¯¯è§£çå°æ¹ï¼Builder 并䏿¯ä¸ä¸ªç®åçé 置容å¨ã
å®ä»åå§åé¶æ®µå¼å§ï¼å°±ææå¹¶ç»´æ¤ä¸ä»½é»è®¤ RequestSpecï¼build() åçåªæ¯åºäºè¿ä»½é»è®¤è¯·æ±ç¶æå建 ChatClientã
build() 没æå»ç»é ç½®
DefaultChatClientBuilder#build() åªæä¸è¡ï¼
public ChatClient build() {
return new DefaultChatClient(this.defaultRequest);
}
DefaultChatClient æé æ¹æ³åæ ·æ²¡æå¤å¶ãBuilder åå®åå»ºåºæ¥ç DefaultChatClient ææåä¸ä»½é»è®¤ RequestSpec å¼ç¨ã
ChatClient.Builder builder = ChatClient.builder(chatModel)
.defaultSystem("第ä¸ç");
ChatClient client = builder.build();
builder.defaultSystem("第äºç");
åç»å次è°ç¨ client.prompt() æ¶ï¼å¤å¶çé»è®¤ RequestSpec å¯è½å·²ç»å
å« Builder ä¿®æ¹åçâ第äºçâé
ç½®ã
å æ¤ï¼Builder æ´éååªå¨åå§åé¶æ®µä½¿ç¨ï¼build() 宿åä¸è¦åç»§ç»ä¿®æ¹åä¸ä¸ª Builderãè¥éè¦åºäºå·²æé
ç½®æ´¾çæ°ç客æ·ç«¯ï¼åºä½¿ç¨ builder.clone() æ chatClient.mutate() å建æ°çé
ç½®å¯æ¬ã
èªå¨é
ç½®å° Builder 声æä¸º prototypeï¼è§£å³çæ¯ä¸å Bean è·å Builder æ¶çé离é®é¢ï¼å¹¶ä¸æå³çåä¸ä¸ª Builder å¯ä»¥å¨è¿è¡æé´è¢«å¤ä¸ªçº¿ç¨åæ¶ä¿®æ¹åå¤ç¨ã
prompt() 为ä»ä¹å¿ é¡»å¤å¶
DefaultChatClient#prompt() ææ¯åæ¬¡è¯·æ±éç¦»çæ£åççä½ç½®ï¼
@Override
public ChatClientRequestSpec prompt() {
return new DefaultChatClientRequestSpec(this.defaultChatClientRequest);
}
å¤å¶æé å¨ä¼éè¿ putAll() å addAll() å建 MapãList ç坿¬ï¼è Options Builder åéè¿ clone() å¤å¶èªèº«ç¶æã
å æ¤ï¼æ¬æ¬¡è¯·æ±ä¸ç userãmediaãadvisor params å tools ä¿®æ¹é½ä¼ä½ç¨äºè¯·æ±å¯æ¬ï¼ä¸ä¼ååå½±å Builder ä¿åçé»è®¤é
ç½®ã
this.optionsCustomizer = customizer != null ? customizer.clone() : null;
this.userParams.putAll(userParams);
this.systemParams.putAll(systemParams);
this.toolCallbacks.addAll(toolCallbacks);
this.messages.addAll(messages);
this.media.addAll(media);
this.advisors.addAll(advisors);
this.advisorParams.putAll(advisorParams);
this.toolContext.putAll(toolContext);
æå¤æçè°ç¨é¾æ½æï¼åªçå¯¹è±¡ç¶æååï¼å¯ä»¥ç®å为ï¼
flowchart TD
Default["Builder.defaultRequest"]
ClientDefault["Client.defaultChatClientRequest"]
A["RequestSpec A"]
B["RequestSpec B"]
RA["ChatClientRequest A"]
RB["ChatClientRequest B"]
Default -->|"build ä¼ éåä¸å¼ç¨"| ClientDefault
ClientDefault -->|"ç¬¬ä¸æ¬¡ prompt å¤å¶"| A
ClientDefault -->|"ç¬¬äºæ¬¡ prompt å¤å¶"| B
A -->|"call ç»è£
"| RA
B -->|"call ç»è£
"| RB
Fluent API ææ°æ®ä¿åå°åªé
é»è®¤ systemãuser åæ¬æ¬¡å¼çå ³ç³»
RequestSpec å
é¨åªç»´æ¤ä¸ä»½ systemText åä¸ä»½ userTextã
Builder å建 ChatClient æ¶ï¼ä¼å
å°é»è®¤ç systemãuser å
容åå
¥ RequestSpecãä¹åæ¯æ¬¡è°ç¨ prompt()ï¼é½ä¼åºäºé»è®¤ RequestSpec å建æ°ç坿¬ã
æ¬æ¬¡è¯·æ±éè¿ system(String) æ user(String) 设置çå
容ï¼ä¼ç´æ¥æ¿æ¢å¯æ¬ä¸ç对åºå段ï¼ä¸ä¼èªå¨çæä¸¤æ¡æ¶æ¯ã
Consumer åæ³çå¤çé»è¾ç¥æä¸åï¼å¦ææ¬æ¬¡è®¾ç½®äº textï¼åè¦çåæå
容ï¼å¦ææ²¡æè®¾ç½®ï¼åç»§ç»æ²¿ç¨é»è®¤å¼ãè paramsãmetadata å media åä¼å¨åæåºç¡ä¸ç»§ç»åå¹¶ã
this.systemText = StringUtils.hasText(systemSpec.text())
? systemSpec.text()
: this.systemText;
this.systemParams.putAll(systemSpec.params());
this.systemMetadata.putAll(systemSpec.metadata());
defaultUser çå¤çè§åå®å
¨ä¸è´ã
è¿éæä¸ä¸ªå®¹æå¿½ç¥çç»èï¼text çè¦çå params çåå¹¶æ¯ä¸¤ä¸ªç¬ç«è¡ä¸ºã
彿°ç text æ¿æ¢æ§å
容æ¶ï¼åæç params å¹¶ä¸ä¼è¢«åæ¥æ¸
空ãå³ä½¿è¿äºåæ°æç»æ²¡æåä¸ææ¬æ¸²æï¼å®ä»¬ä»ç¶å¯è½ä¿çå¨å½å RequestSpec ä¸ã
messagesãmediaãoptions ä¿åå¨åªé
messages ä¿åå¨ RequestSpec çæ¶æ¯å表ä¸ãåå
¥æ¶ä½¿ç¨ addAll()ï¼å æ¤ä¸ä¼è¦çå·²æç systemText æ userTextãæç»æé Prompt æ¶ï¼æ¶æ¯é¡ºåºåºå®ä¸ºï¼
- é»è®¤ææ¬æ¬¡è¯·æ±çæç
SystemMessage - æ¾å¼ä¼ å
¥ç
messages userTextçæçUserMessage
media ä¸ä¼åç¬çæ Messageï¼èæ¯æåå¨ PromptUserSpec ä¸ï¼æç»ä¸ userText ä¸èµ·ç»è£
æä¸ä¸ª UserMessageã
options(...) ä¿åçæ¯ ChatOptions.Builderï¼æ¤æ¶è¿æ²¡æçææç»ç ChatOptionsï¼
ChatOptions.Builder<?> builder = inputRequest.getChatModel()
.getOptions()
.mutate();
if (inputRequest.getOptionsCustomizer() != null) {
builder = builder.combineWith(inputRequest.getOptionsCustomizer());
}
ChatOptions processedOptions = builder.build();
æç» options çæ¥æºæä¸¤å±ï¼åºå± ChatModel æä¾é»è®¤å¼ï¼æ¬æ¬¡è¯·æ±ç options ç¨äºè¦ç对åºå段ãå
·ä½å段åå¹¶è§åä¼å¨ä¸ä¸ç« å±å¼ã
advisorsãtools å toolContext åæ¾å¨åªé
Advisor ç¸å
³ç¶æå为两é¨åï¼
advisorsä¿åå®é çAdvisorå®ä¾ï¼ç¨äºåç»æå»º Advisor Chainï¼advisorParamsä¿å Advisor 使ç¨çåæ°ï¼å¹¶å¨æç»è½¬æ¢æ¶åå ¥ChatClientRequest.contextã
tools(Object...) æ¯æå¤ç§è¾å
¥å½¢å¼ï¼å
æ¬ ToolCallbackãToolCallbackProviderãæ°ç»ãéå以忮é POJOãå¯¹äºæ®é POJOï¼ä¼éè¿ ToolCallbacks.from(...) 转æ¢ä¸ºç»ä¸ç ToolCallbackãå æ¤ï¼RequestSpec æç»ä¿åçæ¯å½ä¸ååç callback/provider ä¿¡æ¯ã
toolContext åå
ç¬ç«ä¿åå¨ä¸ä¸ª Map ä¸ãç´å° DefaultChatClientUtils ç»è£
æç»è¯·æ±æ¶ï¼å¦æå½å ChatOptions ç±»åæ¯ ToolCallingChatOptions.Builderï¼æä¼å°å
¶åå
¥å·¥å
·è°ç¨ç¸å
³é
ç½®ã
éè¦åºåä¸¤ä¸ªå®¹ææ··æ·çæ¦å¿µï¼
toolContextï¼ä¼ éç»å·¥å ·æ§è¡è¿ç¨ä½¿ç¨ï¼Advisor contextï¼åæ¾å¨ChatClientRequest.contextä¸ï¼ä¾ Advisor Chain å é¨å ±äº«ã
两è åç§°ç¸ä¼¼ï¼ä½ä½ç¨èå´å®å ¨ä¸åã
param() ä»ä¹æ¶åæ¿æ¢
param() åªæ¯å¨ Map éåå¼ï¼
public PromptUserSpec param(String key, Object value) {
this.params.put(key, value);
return this;
}
çæ£æ¸²æåçå¨ DefaultChatClientUtils#toChatClientRequest()ï¼
String processedUserText = inputRequest.getUserText();
if (StringUtils.hasText(processedUserText)
&& !CollectionUtils.isEmpty(inputRequest.getUserParams())) {
processedUserText = PromptTemplate.builder()
.template(processedUserText)
.variables(inputRequest.getUserParams())
.renderer(inputRequest.getTemplateRenderer())
.build()
.render();
}
System text åèµ°åä¸å¥é»è¾ãé»è®¤æ¸²æå¨æ¯ StTemplateRendererï¼æ¬æ¬¡ä¹å¯ä»¥ç¨ templateRenderer(...) æ¿æ¢ãæ¨¡æ¿ææ¬ãåéå renderer å¨ Fluent API é¶æ®µåå¼ä¿åï¼å°ç»è£
ChatClientRequest æ¶æç¢°å°ä¸èµ·ã
call() å¦ä½æ¶å£
toChatClientRequest() ææ ·ç»è£ æç»è¯·æ±
è¿ä¸æ¥å¯ä»¥åæä¸ä¸ªé¶æ®µï¼
- 渲æ system åæ°ï¼å¹¶çæå¯¹åºç
SystemMessageï¼ - å°æ¾å¼ä¼ å
¥ç
messages追å å°æ¶æ¯åè¡¨ï¼ - 渲æ user åæ°ï¼çæå
å«
mediaåmetadataçUserMessageã
æ¶æ¯å表æå»ºå®æåï¼å¼å§å¤çæ¬æ¬¡è¯·æ±ç Optionsï¼
- ä»
ChatModel.getOptions().mutate()è·åé»è®¤ Optionsï¼ - åå¹¶æ¬æ¬¡è¯·æ±ç Optionsï¼
- å±å¼ provider æä¾ç callbacksï¼
- åå
¥
toolContextã
æç»åå»ºä¸¤ä¸ªæ ¸å¿å¯¹è±¡ï¼
Prompt prompt = Prompt.builder()
.messages(processedMessages)
.chatOptions(processedChatOptions)
.build();
return ChatClientRequest.builder()
.prompt(prompt)
.context(new ConcurrentHashMap<>(inputRequest.getAdvisorParams()))
.build();
å°è¿éï¼Fluent API é¶æ®µåªè´è´£æ¶éåç»è£ 请æ±ï¼å°æªè§¦åçæ£çæ¨¡åè°ç¨ã
Prompt å Advisor context 为ä»ä¹åå¼
Prompt 表示æç»åéç» ChatModel çæ¨¡åè¾å
¥ï¼å
å« messages å ChatOptionsã
Advisor context åå±äºæ¡æ¶è°ç¨é¾çä¸ä¸æç¶æï¼ç¨äºå¨ Advisor ä¹é´ä¼ éä¿¡æ¯ï¼ä¾å¦ conversation idãæ£ç´¢åæ°ãstructured output æ§å¶ä¿¡æ¯çã
两è èè´£ä¸åï¼å æ¤ä¸è½æ··åï¼
- 妿 context ç´æ¥è¿å ¥ Promptï¼æ¡æ¶å é¨åæ°å¯è½ä¼æå¤æ´é²ç»æ¨¡åï¼
- 妿 Advisor ä¿®æ¹ä¸ä¸æéè¦åæ¥ä¿®æ¹ Promptï¼è°ç¨é¾ç¶æå模åè¾å ¥ä¼äº§ç强è¦åã
ChatClientRequest(Prompt, context) å°æ¨¡åçå°çå
容忡æ¶å
鍿µè½¬ç¶æå离ï¼ä½¿è¯·æ±æ¨¡åå Advisor Chain åèªç»´æ¤èªå·±çç¶æè¾¹çã
容æè¯¯è§£çå°æ¹
defaultSystemä¸ä¼åæ¬æ¬¡systemå å çæä¸¤æ¡æ¶æ¯ãæ¬æ¬¡systemä¼ç´æ¥è¦çRequestSpecä¸çsystemTextã- Fluent API é¾å¼è°ç¨è¿ç¨ä¸ä¸ä¼ä¸æå建æ°ç
RequestSpecï¼çæ£å建请æ±å¯æ¬çæ¶æºæ¯prompt()ã param()åªæ¯ä¿å模æ¿åæ°ï¼ä¸ä¼ç«å³æ§è¡åéæ¿æ¢ãçæ£çæ¸²æåçå¨toChatClientRequest()é¶æ®µãtoolsåtoolContext卿¶éé¶æ®µä¿æç¬ç«ï¼åªæè¯·æ±ç»è£ æ¶æä¼åå¹¶å°ToolCallingChatOptionsãbuild()å¹¶ä¸ä¼è®© Builder ä¸ Client å®å ¨é离ãBuilder ä»ç¶ææé»è®¤ç¶æï¼å æ¤ä¸åºè¯¥ä½ä¸ºè¿è¡æå ±äº«é 置对象使ç¨ã
åå°å¼å¤´
éæ°æ¢³çæ´ä¸ªæµç¨ï¼å¯ä»¥çå° Fluent API çæ ¸å¿å¹¶ä¸æ¯ç«å³æ§è¡è¯·æ±ï¼èæ¯å¨ä¸åé¶æ®µéæ¥æå»ºè¯·æ±ç¶æï¼
Builder ç»´æ¤é»è®¤
RequestSpecï¼ChatClient卿¯æ¬¡prompt()æ¶åºäºå®å建请æ±å¯æ¬ï¼æ¬æ¬¡ Fluent è°ç¨ç»§ç»ä¿®æ¹è¯¥å¯æ¬ï¼æç»ç±call()æstream()å°å¯æ¬è½¬æ¢ä¸ºPrompt + Advisor contextã
è¿å¥è®¾è®¡è®© ChatClient å¯ä»¥è¢«é¿æå¤ç¨ï¼åæ¶å
许é»è®¤é
ç½®ä¸æ¬æ¬¡è¯·æ±åæ°éè¿åä¸å¥ API ç»åã
å½ç¶ä¹ä¼æä»£ä»·ï¼å°±æ¯è¯·æ±æé è¿ç¨ä¸åå¨å¤å±ä¸é´ç¶æï¼å¹¶ä¸ build() å¹¶ä¸ä¼åä¼ ç» Builder 模å¼é£æ ·çæä¸ä¸ªå®å
¨ç¬ç«çä¸å¯åå¿«ç
§ã
å æ¤ï¼å¨å®é 项ç®ä¸ï¼Builder æ´éåä½ä¸ºåå§åé¶æ®µçé 置对象ï¼è䏿¯è¿è¡æåå¤ä¿®æ¹çå ±äº«ç¶æï¼å¦åå¤ä¸ªè¯·æ±ä¹é´å¯è½äº§çé¾ä»¥ææ¥çç¶æé®é¢ã
ä¸ä¸ç¯ç»§ç»åæ PromptãMessage å ChatOptionsï¼çç Fluent API æ¶å£åï¼æç»äº¤ç»æ¨¡åç请æ±å¯¹è±¡å
·ä½ç±åªäºé¨åç»æã
Aitishiku.com