spring AI 使用


Spring AI

主要从chatclient、chat、struct、tools、vector、advisor、rag、mcp几个方面。


提示:以下是本篇文章正文内容,下面案例可供参考

一、ChatClient的认识

1. 常用的属性

  • api-key:用于身份验证的唯一密钥,调用API时必须通过请求头(如Authorization: Bearer )传递。需严格保密,避免泄露或硬编码38。
  • base-url:指定API服务端点地址。例如:
    OpenAI默认URL:https://api.openai.com/v1
    DeepSeek自定义URL:https://api.deepseek.com/v1/chat/completions2
    企业部署时可指向私有化服务地址。
  • model:指定调用的模型名称,决定模型能力和成本。常见选项:
    • gpt-4o:最新多模态模型(文本/图像)
    • gpt-4:高性能通用模型
    • gpt-3.5-turbo:经济型轻量模型
      不同模型的并发度、计费规则不同,需按需选择。
  • temperature:控制输出随机性的核心参数(范围0~2):
    • 低值(0~0.3)‌:保守输出,选择概率最高的词,适合代码生成、事实问题
    • 中等温度(0.5-0.9)‌:平衡确定性与多样性,适合日常对话或商业写作。‌‌
    • 高温度(1.0-2.0)‌:提升低概率词汇的权重,增强创造性和随机性,适合诗歌、故事生成等创意任务,但可能降低连贯性
  • system::对话消息数组,每条消息需包含 role 和 content 字段:
  • user:作用‌:用户输入的问题或指令
  • assistant :作用‌:模型历史回复,用于维持多轮对话上下文。
  • message :一个完整的User message是一个json对象,包含以下字段:
    • content:必须提供的string或array类型,二选一,表示user的消息内容
      • string类型时,表示消息的文本内容
      • 为array类型时,一般用于调用多模态模型,用来包含多个内容部分的数组,一般是一个文本内容的json对象和一个或多个图片内容的json对象。仅当使用 gpt-4-visual-preview 这样的多模态模型时才支持图像输入。具体字段如下:
        • 文本内容部分,是一个json对象:
          • type:必须提供的string类型,表示内容部分的类型,一般是“text”
          • text:必须提供的string类型,文字内容
        • 图片内容部分,是一个json对象:
          • type:必须提供的string类型,表示内容部分的类型,一般是"image_url"
          • image_url:必须提供的json对象类型,字段有:
            url:必须提供的string类型,图像的 URL 或 Base64 编码的图像数据
            detail:可选的string类型,一般默认是“auto”
    • role:必须提供的string类型,表示消息作者的角色,对于user message应该是"user"
    • name:可选的string类型,表示对话参与者的名称

‌关键注意‌:多轮对话需严格按 system → user → assistant → user 顺序组织消息列表。

2. Assistant

Assistant 这个是维持多轮会话和上下文一个关键的角色。但是Spring AI是没有设置的入口的。

原因说明
抽象、屏蔽复杂性Spring AI 更关注用户输入(user)和系统提示(system),以保持接口简洁
兼容性考虑不同 LLM 对 assistant 的实现略有差异(如 Claude 用 assistant,Anthropic 用 human / assistant)
主要使用场景限制ChatClient 更适用于一次交互式问答,而非多轮训练数据构造。

但是当使用了MessageWindowChatMemory,框架会默认把历史记录的信息放到AssistantMessage中的。
在这里插入图片描述

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

3. 案例展示

{
  "model": "gpt-4o",
  "temperature": 0.5,
  "messages": [
    {"role": "system", "content": "你是一位历史学家"},
    {"role": "user", "content": "罗马帝国灭亡的原因是什么?"}
    {"role": "assistant", "content": "罗马帝国是一个强大的国家"}
  ]
}
curl https://api.openai.com/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  -d '{
    "model": "gpt-4-turbo",
    "messages": [
      {
        "role": "user",
        "content": [
          {
            "type": "text",
            "text": "What'\''s in this image?"
          },
          {
            "type": "image_url",
            "image_url": {
              "url": "https://pic4.zhimg.com/v2-4d9e9f936b9968f53be22b594aafa74f_r.jpg"
            }
          }
        ]
      }
    ],
    "max_tokens": 300
  }

        // java 调用 open AI
        OpenAIClient client = OpenAIOkHttpClient.builder()
                .apiKey("sk-rNst1bBrT4P7rzD6OLpqgbwNedeQLw9KtwfS0DGpUDCe3GtY")
                .baseUrl("https://www.dmxapi.cn/v1")
                .build();

        //usermessage 消息组装
        List<ChatCompletionContentPart> list = new ArrayList<>();
        ChatCompletionContentPart text =
                ChatCompletionContentPart.ofText(
                        ChatCompletionContentPartText
                                .builder()
                                .text(ScreenPromptConfig.SCREENSHOT_TO_CODE_PROMPT)
                                .build());

        ChatCompletionContentPart imageUrl =
                ChatCompletionContentPart.ofImageUrl(
                        ChatCompletionContentPartImage
                                .builder()
                                .imageUrl(
                                        ChatCompletionContentPartImage
                                                .ImageUrl
                                                .builder()
                                                .url(url)
                                                .build())
                                .build());

        list.add(text);
        list.add(imageUrl);

        //助理:历史记录消息
        ChatCompletionAssistantMessageParam assistantMessageParam =
                ChatCompletionAssistantMessageParam
                        .builder()
                        .content(ChatCompletionAssistantMessageParam.Content.ofText("我是一个助理消息")).build();

        ChatCompletionCreateParams params =
                ChatCompletionCreateParams
                        .builder()
                        .model("llama-3.3-70b-instruct")
                        .addUserMessage(
                                ChatCompletionUserMessageParam
                                        .Content.ofArrayOfContentParts(list))
                        //设置助理消息
                        .addMessage(assistantMessageParam)
                        .temperature(0.2)
                        .maxCompletionTokens(1000)
                        .build();
        // 发送请求并接收响应
        ChatCompletion chatCompletion = client.chat().completions().create(params);
        Optional<String> content = chatCompletion.choices().get(0).message().content();
        if (content.isEmpty()) {
            return "none";

        }
        return content.get();
});

二、Chat对话

1.引入库

代码如下(示例):

 <dependency>
	<groupId>org.springframework.ai</groupId>
	<artifactId>spring-ai-autoconfigure-model-openai</artifactId>
</dependency>

<dependency>
	<groupId>org.springframework.ai</groupId>
	<artifactId>spring-ai-autoconfigure-model-chat-client</artifactId>
</dependency>		

2. 配置文件

spring.ai.openai.api-key=YOUR_API_KEY
spring.ai.openai.base-url=YOUR_BASE_URL
spring.ai.openai.chat.options.model=YOUR_MODEL_NAME

3. 普通调用

    @GetMapping("/call")
    public String call(@RequestParam(value = "msg")String msg) {
        return chatClient.prompt(msg).call().content();
    }

在这里插入图片描述

4. 流式调用

    @GetMapping("/stream")
    public Flux<String> stream(@RequestParam(value = "msg")String msg, HttpServletResponse response) {
        response.setCharacterEncoding("UTF-8");
        return chatClient.prompt(msg).stream().content();
    }

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

5. Option使用

    @GetMapping("/call/temperature")
    public String callOption(@RequestParam(value = "msg")String msg, HttpServletResponse response) {
        response.setCharacterEncoding("UTF-8");
        return chatClient.prompt(msg)
                .options(
                        OpenAiChatOptions.builder()
                                .temperature(0.2)
                                .build()
                )
                .call().content();
    }

在这里插入图片描述

三、struct结构化数据

1. Bean结构化

@JsonPropertyOrder({ "title", "date", "author", "content"})
public record BeanEntity (String title, String date, String author, String content){

}
private final BeanOutputConverter<BeanEntity> converter;

    public BeanController(ChatClient.Builder builder) {
        this.converter = new BeanOutputConverter<>(
                new ParameterizedTypeReference<BeanEntity>() {
                }
        );
        this.chatClient = builder.build();
    }

1. 格式化前的结果

@GetMapping("/call")
    public String call(@RequestParam(value = "msg") String msg){
        String result = chatClient.prompt(msg).call().content();
        log.info("result: {}", result);
        assert result != null;
        try {
            BeanEntity convert = converter.convert(result);
            log.info("反序列成功, convert: {}", convert);
        } catch (Exception e){
            log.error("反序列失败");
        }
        return result;
    }

![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/c9ccc2a2086f45b7b0b3f4187a019373.png

2. 格式化后的结果

@GetMapping("/call/format")
    public String callFormat(@RequestParam(value = "msg") String msg){
        String template = msg + "{format}";

        PromptTemplate promptTemplate = PromptTemplate.builder()
                .template(template)
                .variables(Map.of("format", converter.getFormat()))
                .renderer(StTemplateRenderer.builder().build())
                .build();

        Prompt prompt = promptTemplate.create();
        String result = chatClient.prompt(prompt).call().content();

        log.info("result: {}", result);
        assert result != null;
        try {
            BeanEntity convert = converter.convert(result);
            log.info("反序列成功, convert: {}", convert);
        } catch (Exception e){
            log.error("反序列失败");
        }
        return result;
    }

在这里插入图片描述

2. Map-List结构化

1. 格式化前

在这里插入图片描述

2. 格式化后的map

    private final MapOutputConverter mapConverter;
    private final ListOutputConverter listConverter;

    public MapListController(ChatClient.Builder builder) {
        // map转换器
        this.mapConverter = new MapOutputConverter();
        // list转换器
        this.listConverter = new ListOutputConverter(new DefaultConversionService());

        this.chatClient = builder
                .build();
    }
    @GetMapping("/map")
    public Map<String, Object> map(@RequestParam(value = "msg", defaultValue = "请为我描述下春天的特性") String msg) {
        String promptUserSpec = """
                format: key为描述的东西,value为对应的值
                outputExample: {format};
                """;
        String format = mapConverter.getFormat();
        logger.info("map format: {}",format);

        String result = chatClient.prompt(msg)
                .user(u -> u.text(promptUserSpec)
                        .param("format", format))
                .call().content();
        logger.info("result: {}", result);
        assert result != null;
        Map<String, Object> convert = null;
        try {
            convert = mapConverter.convert(result);
            logger.info("反序列成功,convert: {}", convert);
        } catch (Exception e) {
            logger.error("反序列化失败");
        }
        return convert;
    }

在这里插入图片描述

3. 格式化后的list

    @GetMapping("/list")
    public List<String> list(@RequestParam(value = "msg", defaultValue = "请为我描述下春天的特性") String msg) {
        String promptUserSpec = """
                format: value为对应的值
                outputExample: {format};
                """;
        String format = listConverter.getFormat();
        logger.info("list format: {}",format);
        String result = chatClient.prompt(msg)
                .user(u -> u.text(promptUserSpec)
                        .param("format", format))
                .call().content();
        logger.info("result: {}", result);
        assert result != null;
        List<String> convert = null;
        try {
            convert = listConverter.convert(result);
            logger.info("反序列成功,convert: {}", convert);
        } catch (Exception e) {
            logger.error("反序列化失败");
        }
        return convert;
    }

在这里插入图片描述

四、tools工具

调用本地的方法和工具类
假如我想要查询现在几点,如果不联网的话,大模型是不知道的。
假如我想知道现在今天的天气,或者大模型执行到某个节点的时候,我想把这个时间的状态记录下载,存储到mysql或者redis,如何做。

1. 引入包

        <dependency>
			<groupId>org.springframework.ai</groupId>
			<artifactId>spring-ai-autoconfigure-model-tool</artifactId>
		</dependency>

2. 调用前

在这里插入图片描述

3. 调用后

    @Tool(description = "Get the time of a specified city.")
    public String  getCityTimeMethod(@ToolParam(description = "Time zone id, such as Asia/Shanghai") String timeZoneId) {
        logger.info("The current time zone is {}", timeZoneId);
        return String.format("The current time zone is %s and the current time is " + "%s", timeZoneId,
                TimeUtils.getTimeByZoneId(timeZoneId));
    }

public static String getTimeByZoneId(String zoneId) {
        // Get the time zone using ZoneId
        ZoneId zid = ZoneId.of(zoneId);
        // Get the current time in this time zone
        ZonedDateTime zonedDateTime = ZonedDateTime.now(zid);
        // Defining a formatter
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss z");
        // Format ZonedDateTime as a string
        String formattedDateTime = zonedDateTime.format(formatter);
        return formattedDateTime;
    }
/**
     * 调用工具版 - method
     */
    @GetMapping("/call/tool-method")
    public String callToolMethod(@RequestParam(value = "msg", defaultValue = "请告诉我现在北京时间几点了") String msg) {
        return chatClient.prompt(msg).tools(new TimeTools()).call().content();
    }

在这里插入图片描述

4. 其他-调用天气前

在这里插入图片描述

5. 其他-调用天气后

在这里插入图片描述

五、vector向量库

文本数据存入向量库,然后通过向量库进行检索出来。

1. 引入包

        <dependency>
			<groupId>org.springframework.ai</groupId>
			<artifactId>spring-ai-starter-vector-store-elasticsearch</artifactId>
		</dependency>

		<!--        es版本匹配-->
		<dependency>
			<groupId>co.elastic.clients</groupId>
			<artifactId>elasticsearch-java</artifactId>
			<version>8.17.4</version>
		</dependency>
		<dependency>
			<groupId>org.elasticsearch.client</groupId>
			<artifactId>elasticsearch-rest-high-level-client</artifactId>
			<version>7.10.1</version>
		</dependency>
@GetMapping("/import")
    public void importData() {
        logger.info("start import data");

        HashMap<String, Object> map = new HashMap<>();
        map.put("id", "12345");
        map.put("year", 2025);
        map.put("name", "lyl");
        List<Document> documents = List.of(
                new Document("当第一缕晨光刺穿云层时,整个校园突然被镀上蜂蜜色的光泽。我站在三楼走廊,看见悬铃木的新叶在风中翻飞,那些半透明的嫩绿像极了实验室里的硫酸铜溶液,在光线折射下荡漾出翡翠般的波纹。"),
                new Document("操场东南角的樱花树总是最早感知春讯。淡粉色的花瓣边缘带着细微的锯齿,每片都承托着晨露制成的棱镜。物理老师曾说露珠的曲率半径约0.2毫米,这个数字此刻具象为无数个颠倒的微型世界——蚂蚁在露珠里变成庞然大物,而远处的教学楼则扭曲成梵高笔下的星空。", Map.of("year", 2024)),
                new Document("正午的食堂后墙爬满蔷薇的枝条。那些猩红的花苞尚未绽放,却已吸引早醒的蜜蜂前来勘探。它们毛茸茸的后腿沾满金色花粉,飞行轨迹画出布朗运动般的折线。生物课本里\"虫媒花蜜腺分泌速率0.03ml/h\"的知识点,在此刻化作瓷砖地上滴落的黏稠光斑。", map));
                new Document("黄昏时分的篮球场最具戏剧性。西晒的阳光下,飞扬的尘埃变成金色的蜉蝣,而褪色的篮板在夕照中泛起铁锈红。最动人的是墙角那排冬青树新抽的嫩芽,在暮色里依然倔强地闪烁着磷火般的微光,仿佛要把白天储存的阳光在夜间慢慢释放。");
                new Document("晚自习的窗口飘进混合着青草与合欢花香的夜风。对面居民楼晾晒的床单在月光下起伏,像极了解剖图里舒展的肺泡。而真正让人怔忡的,是偶然抬头看见玉兰花瓣飘落的轨迹——那些旋转下坠的白色弧线,完美复现了数学课上抛物线方程的图像。");
                new Document("当保安大叔开始巡视空荡的走廊,春日就在荧光笔标记的重点里悄悄更迭。这个发现让我突然理解:原来最动人的春色,从来不在远方,而在我们习以为常的日常褶皱里。");
        elasticsearchVectorStore.add(documents);
    }

在这里插入图片描述

六、Advisor

Spring AI Advisor 是 Spring 框架生态系统中的一个工具或组件,用于在应用程序中引入人工智能(AI)功能。它通常用于提供智能化的建议、决策支持或行为预测等功能。

1. 引入包

        <dependency>
			<groupId>com.alibaba.cloud.ai</groupId>
			<artifactId>spring-ai-alibaba-starter-memory</artifactId>
			<version>${spring-ai-alibaba.version}</version>
		</dependency>

		<dependency>
			<groupId>com.alibaba.cloud.ai</groupId>
			<artifactId>spring-ai-alibaba-starter-memory-jdbc</artifactId>
			<version>${spring-ai-alibaba.version}</version>
		</dependency>

2. 代码哪里

    @Bean
    public MysqlChatMemoryRepository mysqlChatMemoryRepository(){
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName(mysqlDriverClassName);
        dataSource.setUrl(mysqlJdbcUrl);
        dataSource.setUsername(mysqlUsername);
        dataSource.setPassword(mysqlPassword);
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
        return MysqlChatMemoryRepository.mysqlBuilder()
                .jdbcTemplate(jdbcTemplate)
                .build();
    }
private final ChatClient chatClient;
    private final int MAX_MESSAGES = 100;
    private final MessageWindowChatMemory messageWindowChatMemory;

    public MysqlAdvisorController(ChatClient.Builder builder, MysqlChatMemoryRepository mysqlChatMemoryRepository) {
        this.messageWindowChatMemory = MessageWindowChatMemory.builder()
                .chatMemoryRepository(mysqlChatMemoryRepository)
                .maxMessages(MAX_MESSAGES)
                .build();

        this.chatClient = builder
                .defaultAdvisors(
                        MessageChatMemoryAdvisor.builder(messageWindowChatMemory)
                                .build()
                )
                .build();
    }

3. 效果演示

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

七、RAG检索生成增强

1. 引入包

        <dependency>
			<groupId>org.springframework.ai</groupId>
			<artifactId>spring-ai-milvus-store</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.ai</groupId>
			<artifactId>spring-ai-autoconfigure-vector-store-milvus</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.ai</groupId>
			<artifactId>spring-ai-advisors-vector-store</artifactId>
		</dependency>

2. 实例化bean

@Bean
    public RestClient restClient(){

        logger.info("create elasticsearch rest client");
        // 构建RestClient
        return RestClient.builder(HttpHost.create(url))
                .setDefaultHeaders(new Header[]{
                        new BasicHeader("Authorization", "ApiKey " + apikey)
                }).build();
    }

    @Bean
    @Qualifier("elasticsearchVectorStore")
    public ElasticsearchVectorStore vectorStore(RestClient restClient, EmbeddingModel embeddingModel) {
        logger.info("create elasticsearch vector store");

        ElasticsearchVectorStoreOptions options = new ElasticsearchVectorStoreOptions();
        options.setIndexName(indexName);    // Optional: defaults to "spring-ai-document-index"
        options.setSimilarity(similarityFunction);           // Optional: defaults to COSINE
        options.setDimensions(dimensions);             // Optional: defaults to model dimensions or 1536

        return ElasticsearchVectorStore.builder(restClient, embeddingModel)
                .options(options)                     // Optional: use custom options
                .initializeSchema(true)               // Optional: defaults to false
                .batchingStrategy(new TokenCountBatchingStrategy())// Optional: defaults to TokenCountBatchingStrategy
                .build();
    }

3. 方法调用


@GetMapping("/add")
    public void add() {
        logger.info("start add data");
        HashMap<String, Object> map = new HashMap<>();
        map.put("year", 2025);
        map.put("name", "lyl");
        List<Document> documents = List.of(
                new Document("你的姓名是春天,湖南邵阳人,25年硕士毕业于北京科技大学,曾先后在百度、理想、快手实习,曾发表过一篇自然语言处理的sci,现在是一名AI研发工程师"),
                new Document("你的姓名是春天,专业领域包含的数学、前后端、大数据、自然语言处理", Map.of("year", 2024)),
                new Document("你姓名是春天,爱好是发呆、思考、运动", map));
        elasticsearchVectorStore.add(documents);
    }


@GetMapping("/chat-rag-advisor")
    public String chatRagAdvisor(@RequestParam(value = "msg", defaultValue = "你好,请告诉我春天这个人的身份信息") String msg) {
        logger.info("start chat with rag-advisor");
        RetrievalAugmentationAdvisor retrievalAugmentationAdvisor = RetrievalAugmentationAdvisor.builder()
                .documentRetriever(VectorStoreDocumentRetriever.builder()
                        .vectorStore(elasticsearchVectorStore)
                        .build())
                .queryAugmenter(ContextualQueryAugmenter.builder()
                        .allowEmptyContext(true)
                        .build())
                .build();

        return chatClient.prompt(msg)
                .advisors(retrievalAugmentationAdvisor)
                .call().content();
    }

4. 效果展示

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

提示:这里对文章进行总结:
例如:以上就是今天要讲的内容,本文仅仅简单介绍了pandas的使用,而pandas提供了大量能使我们快速便捷地处理数据的函数和方法。

### 如何在Spring框架中集成AI技术及相关库 #### 集成概述 Spring Framework 提供了一个灵活的开发环境,支持多种架构模式和技术栈。通过其模块化设计,开发者可以轻松地将第三方库和工具引入到项目中[^1]。对于人工智能AI)领域的需求,Spring 社区已经提供了专门的支持,例如 Spring AI 工具包以及与其他开源项目的协作。 --- #### 使用Spring AI支持ChatGPT和其他OpenAI模型 Spring AI 是一个新兴的技术方向,旨在简化与主流AI服务的集成过程。它可以通过依赖管理机制快速接入像 OpenAI 这样的外部API。具体来说,`spring-ai-openai` 模块能够帮助开发者调用 ChatGPT 和 DALL·E 等功能[^2]。以下是实现的一个基本示例: ```java import com.example.spring.ai.openai.OpenAiService; @RestController public class AiController { private final OpenAiService openAiService; public AiController(OpenAiService openAiService) { this.openAiService = openAiService; } @GetMapping("/chat") public String getResponse(@RequestParam String message) { return openAiService.generateText(message); } } ``` 上述代码展示了如何创建一个简单的REST接口来接收用户输入并返回由ChatGPT生成的结果。 --- #### 利用Apache Spark进行大规模数据处理 当涉及到复杂的机器学习任务时,通常需要强大的计算能力和高效的算法支持。此时可以选择 Apache Spark 来完成大数据集上的训练工作[^3]。Spark 的 MLlib 库包含了丰富的分类器、回归分析方法以及其他统计建模组件,这些都可以无缝嵌入到基于Spring的应用程序当中去。 为了使两者协同运作起来更加便捷高效,在实际操作过程中可能还需要考虑以下几个方面: - **配置文件优化**:合理设置内存分配参数以适应不同规模的数据量; - **分布式部署方案**:利用YARN或者Kubernetes编排集群资源调度; - **监控日志记录**:及时发现潜在性能瓶颈所在位置以便调整策略; 下面是一个关于加载本地CSV文件作为DataFrame对象的例子: ```scala val sparkSession = SparkSession.builder().appName("Example").getOrCreate() val df = spark.read.format("csv").option("header", "true").load("path/to/file.csv") // 执行某些转换逻辑... df.show() sparkSession.stop() ``` 注意这里采用的是Scala语法编写脚本形式运行的任务实例,当然也可以切换至Java版本如果更倾向于后者的话。 --- #### 整合其他类型的自动化测试工具或持续交付流程 除了前面提到的内容之外,还有许多额外的选择可以帮助进一步增强整个系统的智能化水平。比如借助特定插件把质量保障环节融入日常构建活动中——即所谓的DevOps实践之一部分[^4]。这样一来不仅提高了生产效率同时也降低了人为错误发生的概率。 --- #### 总结说明 综上所述,无论是针对自然语言理解还是图像识别等领域内的应用需求,亦或是面对海量结构化半结构化的信息挖掘挑战,Spring生态体系都能够提供相应的解决方案满足业务场景下的多样化诉求。与此同时不断涌现的新特性也使得未来充满无限可能性值得期待! ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值