SpringAI从入门到熟练

news2025/1/5 9:15:54

学习SpringAI的记录情况

文章目录


前言

因公司需要故而学习SpringAI文档,故将自己所见所想写成文章,供大佬们参考

主要是为什么这么写呢,为何不抽出来呢,还是希望可以用的时候更加方便一点,如果大家有需求可以自行去优化。

SrpingAI入门

这里我用到的是智普AI,大家可以根据自己喜欢用的AI自由进行切换

 至于API-KEY可自行去智谱AI开放平台可自行去找,这里新用户注册目前是还送2000万TOKEN的

引包

        <!--智普AI-->
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-zhipuai-spring-boot-starter</artifactId>
        </dependency>

 配置文件

 至于API-KEY可自行去智谱AI开放平台可自行去找,这里新用户注册目前是还送2000万TOKEN的

配置类

获取API-KEY

@Component
public class ChatConfig {
    
    //获取配置文件中的API-KEY
    @Value("${spring.ai.zhipuai.api-key}")
    private String apiKey;
    
    public String getApiKey() {
        return apiKey;
    }
}

 测试用例代码

    //为啥用这个模型呢,因为免费
    private static final String default_model = "GLM-4-Flash";

    //temperature 是一个超参数,用于控制生成文本的多样性和随机性。
    //低温度(例如:0.1 到 0.3):模型会生成更确定、常规和一致的输出。低温度通常会使模型产生更 
    加保守的回答,重复性较高,生成的内容更加准确、接近训练数据中的常见模式。
    //高温度(例如:0.7 到 1.0):模型的输出会更加随机、多样、创意性强。高温度会导致生成的内容 
    更加多样化,可能包括一些不太常见或创新性的回答,但也可能带来不太准确或不太连贯的结果。
    private static final double default_temperature = 0.7;   

 @GetMapping("/AIchat")
    public BaseResponse<String> AIGeneration(
            @RequestParam(value = "message", defaultValue = "你是谁呢") String message) {

        // 创建ZhiPuAiChatOptions对象,设置模型和温度
        ZhiPuAiChatOptions options = ZhiPuAiChatOptions.builder()
                .withModel(default_model)
                .withTemperature(default_temperature)
                .build();
        // 创建ZhiPuAiChatModel对象,传入ZhiPuAiApi和options
        ChatModel chatModel = new ZhiPuAiChatModel(new                 
        ZhiPuAiApi(chatConfig.getApiKey()), options);
        // 创建ChatClient对象,传入chatModel
        ChatClient build = ChatClient.builder(chatModel).build();
        String substring = "";
        String content = null;
        try {
            // 调用build.prompt()方法,传入用户消息,调用call()方法,获取返回内容
            content = build.prompt()
                    .user(message)
                    .call()
                    .content();
        } catch (Exception e) {
            // 获取异常信息
            String errorMessage = e.getMessage();
            // 获取异常信息中冒号后面的内容
            int i = errorMessage.lastIndexOf(":");
            String newString = errorMessage.substring(i + 2);
            // 去掉异常信息中的最后一个字符
            substring = newString.substring(0, newString.length() - 3);
        }
        // 如果异常信息不为空,抛出BusinessException异常
        if(!substring.isEmpty()){
            throw new BusinessException(ErrorCode.PARAMS_ERROR,substring);
        }
        // 返回成功结果
        return ResultUtils.success(content);
    }

 测试AI返回结果

 全参数响应结果

这里是对token的消耗做了一个统计,查看用户的所剩token情况的一个返回结果展示

其实也就只是把String类型的返回结果转成了ChatResponse类型仅此而已

测试用例代码

 @GetMapping("/AIchat")
    public BaseResponse<ChatResponse> AIGeneration(
            @RequestParam(value = "message", defaultValue = "你是谁呢") String message) {

        // 创建ZhiPuAiChatOptions对象,设置模型和温度
        ZhiPuAiChatOptions options = ZhiPuAiChatOptions.builder()
                .withModel(default_model)
                .withTemperature(default_temperature)
                .build();
        // 创建ZhiPuAiChatModel对象,传入ZhiPuAiApi和options
        ChatModel chatModel = new ZhiPuAiChatModel(new ZhiPuAiApi(chatConfig.getApiKey()), options);
        // 创建ChatClient对象,传入chatModel
        ChatClient build = ChatClient.builder(chatModel).build();
        String substring = "";
        ChatResponse chatResponse = null;
        try {
            chatResponse = build.prompt()
                    .user(message)
                    .call()
                    .chatResponse();
        } catch (Exception e) {
            // 获取异常信息
            String errorMessage = e.getMessage();
            // 获取异常信息中冒号后面的内容
            int i = errorMessage.lastIndexOf(":");
            String newString = errorMessage.substring(i + 2);
            // 去掉异常信息中的最后一个字符
            substring = newString.substring(0, newString.length() - 3);
        }
        // 如果异常信息不为空,抛出BusinessException异常
        if(!substring.isEmpty()){
            throw new BusinessException(ErrorCode.PARAMS_ERROR,substring);
        }
        // 返回成功结果
        return ResultUtils.success(chatResponse);
    }

返回结果

 前端传递不同的模型测试方法

测试用例代码

    @GetMapping("/chat")
    public BaseResponse<String> generation(
            @RequestParam(value = "message", defaultValue = "你是谁呢") String message,
            @RequestParam(value = "model", defaultValue = "GLM-4-Flash") String model,
            @RequestParam(value = "temperature", defaultValue = "0.7") double temperature) {

        // 创建新的 ZhiPuAiChatOptions 实例,根据请求的参数动态设置
        ZhiPuAiChatOptions options = ZhiPuAiChatOptions.builder()
                .withModel(model)
                .withTemperature(temperature)
                .build();
        // 获取 ChatModel 实例,使用新的配置
        ChatModel chatModel = new ZhiPuAiChatModel(new 
        ZhiPuAiApi(chatConfig.getApiKey()), options);
        ChatClient build = ChatClient.builder(chatModel).build();
        String substring = "";
        // 使用新的 ChatModel 进行对话生成
        String content = null;
        try {
            content = build.prompt()
                    .user(message)
                    .call()
                    .content();
        } catch (Exception e) {
            // 获取错误消息
            String errorMessage = e.getMessage();
            int i = errorMessage.lastIndexOf(":");
            String newString = errorMessage.substring(i + 2);
            substring = newString.substring(0, newString.length() - 3);
        }
        if(!substring.isEmpty()){
            throw new BusinessException(ErrorCode.PARAMS_ERROR,substring);
        }
        return ResultUtils.success(content);
    }

 其实跟上面的代码差不多,只不过是从前端传递过来一个模型,当然这里我并没有对于上传上来的模型做校验,如果感兴趣的话,可自行查看,并解决

 测试结果

 这里可以看到我们的异常返回起到效果了,原谅我是一个穷鬼,不想花钱在测试这个上面

 测试到这里,相信你已经知道了点什么,就是如果你问的问题较长的话,实际使用GET传输是有问题的,因为默认GET请求的长度是有限制的,建议自行换成POST请求,然后再继续往下

流式响应

测试用例代码

@GetMapping(value = "/AIStream",produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> AIStream(
            @RequestParam(value = "message", defaultValue = "你是谁呢") String message) {

        // 创建ZhiPuAiChatOptions对象,设置模型和温度
        ZhiPuAiChatOptions options = ZhiPuAiChatOptions.builder()
                .withModel(default_model)
                .withTemperature(default_temperature)
                .build();
        // 创建ZhiPuAiChatModel对象,传入ZhiPuAiApi和options
        ChatModel chatModel = new ZhiPuAiChatModel(new ZhiPuAiApi(chatConfig.getApiKey()), options);
        // 创建ChatClient对象,传入chatModel
        ChatClient chatClient = ChatClient.builder(chatModel).build();
        Flux<String> content = chatClient.prompt()
                .user(message)
                .stream()
                .content();
        // 返回成功结果
        return content;
    }

响应结果

这里使用的是SSE,SSE是一种服务端向客户端推送的一种技术,需要的可自行去浏览这个技术

 流式响应全参数

测试用例代码

// 使用GetMapping注解,指定请求路径为/AIChatResponseStream,返回类型为TEXT_EVENT_STREAM_VALUE
    @GetMapping(value = "/AIChatResponseStream",produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    // 定义一个方法,返回类型为Flux<ChatResponse>,参数为String类型的message,默认值为"你是谁呢"
    public Flux<ChatResponse> AIChatResponseStream(
            @RequestParam(value = "message", defaultValue = "你是谁呢") String message) {
        
        // 创建ZhiPuAiChatOptions对象,设置模型和温度
        ZhiPuAiChatOptions options = ZhiPuAiChatOptions.builder()
                .withModel(default_model)
                .withTemperature(default_temperature)
                .build();
        // 创建ChatModel对象,传入ZhiPuAiApi和options
        ChatModel chatModel = new ZhiPuAiChatModel(new ZhiPuAiApi(chatConfig.getApiKey()), options);
        // 创建ChatClient对象,传入chatModel
        ChatClient chatClient = ChatClient.builder(chatModel).build();
        // 调用chatClient的prompt方法,传入message,返回一个Flux<ChatResponse>对象
        Flux<ChatResponse> chatResponseFlux = chatClient.prompt()
                .user(message)
                .stream()
                .chatResponse();
        // 返回Flux<ChatResponse>对象
        return chatResponseFlux;
    }

响应结果

 至于这里如何查看内容流式响应全参数输出完毕,有一个STOP的停止参数可以使用

位置贴到下面

//全参数流失响应地址接口停止标志--------------当然这里如果你要配合前端可以通过这个STOP来观察结果是否输出完毕,可以通过这个标志来停掉SSE连接
parsedData.results[0].metadata.finishReason === "STOP"

 默认角色回复实现

测试用例代码

// 使用GetMapping注解,指定请求路径为/AIChatDefaultStream,返回类型为文本流
    @GetMapping(value = "/AIChatDefaultStream",produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> AIChatDefaultStream(
            // 使用@RequestParam注解,指定请求参数名为message,默认值为"你是谁呢"
            @RequestParam(value = "message", defaultValue = "你是谁呢") String message) {
        // 创建ZhiPuAiChatOptions对象,设置模型和温度
        ZhiPuAiChatOptions options = ZhiPuAiChatOptions.builder()
                .withModel(default_model)
                .withTemperature(default_temperature)
                .build();
        // 创建ChatModel对象,使用ZhiPuAiChatModel和ZhiPuAiApi
        ChatModel chatModel = new ZhiPuAiChatModel(new ZhiPuAiApi(chatConfig.getApiKey()), options);
        // 创建ChatClient对象,设置默认系统
        ChatClient chatClient = ChatClient.builder(chatModel).defaultSystem("你现在的身份是一个星轨会议系统的客服助手,请以友好、乐于助人且愉快的方式来回复。").build();
        // 调用chatClient的prompt方法,设置用户消息,并返回内容流
        Flux<String> content = chatClient.prompt()
                .user(message)
                .stream()
                .content();
        // 返回内容流
        return content;
    }

 响应结果

 这里就可以看到响应结果他已经换成了星轨会议系统的客服助手,如果你希望做某一个角色的内容,你可以通过预训练这个参数,使得这个AI客服助手的回复更加友好。

AI知晓日期

测试用例代码

// 使用GetMapping注解,指定请求路径为/AIChatDefaultStream,返回类型为文本流
    @GetMapping(value = "/AIChatDefaultStream",produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> AIChatDefaultStream(
            // 使用@RequestParam注解,指定请求参数名为message,默认值为"你是谁呢"
            @RequestParam(value = "message", defaultValue = "你是谁呢") String message) {
        // 创建ZhiPuAiChatOptions对象,设置模型和温度
        ZhiPuAiChatOptions options = ZhiPuAiChatOptions.builder()
                .withModel(default_model)
                .withTemperature(default_temperature)
                .build();
        // 创建ChatModel对象,使用ZhiPuAiChatModel和ZhiPuAiApi
        ChatModel chatModel = new ZhiPuAiChatModel(new ZhiPuAiApi(chatConfig.getApiKey()), options);
        // 创建ChatClient对象,设置默认系统
        ChatClient chatClient = ChatClient.builder(chatModel).defaultSystem("你现在的身份是一个星轨会议系统的客服助手,请以友好、乐于助人且愉快的方式来回复。我希望你可以以中文的方式给我回答。今天的日期是 {current_date}.")
                .build();
        // 调用chatClient的prompt方法,设置用户消息,并返回内容流
        Flux<String> content = chatClient.prompt()
                .user(message)
                .system(s->s.param("current_date", LocalDate.now().toString()))
                .stream()
                .content();
        // 返回内容流
        return content;
    }

响应结果

 这样的话AI就知道当前的日期

 记忆对话

测试用例代码

创建一个配置类引入ChatMemory

@Configuration
public class ZhiPuChatMemoryConfig {

    @Bean
    @Qualifier("customMemory")
    public ChatMemory chatMemory() {
        return new InMemoryChatMemory();
    }

}

 引入ChatMemory

// 使用GetMapping注解,指定请求路径为/AIChatDefaultStream,返回类型为文本流
    @GetMapping(value = "/AIChatDefaultStream",produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> AIChatDefaultStream(
            // 使用RequestParam注解,指定请求参数名为message,类型为String
            @RequestParam(value = "message") String message) {
        // 创建ZhiPuAiChatOptions对象,设置模型和温度
        ZhiPuAiChatOptions options = ZhiPuAiChatOptions.builder()
                .withModel(default_model)
                .withTemperature(default_temperature)
                .build();
        // 创建ChatModel对象,传入ZhiPuAiApi和options
        ChatModel chatModel = new ZhiPuAiChatModel(new ZhiPuAiApi(chatConfig.getApiKey()), options);
        // 创建ChatClient对象,传入chatModel,设置默认系统消息和默认顾问
        ChatClient chatClient = ChatClient.builder(chatModel).defaultSystem("你现在的身份是一个星轨会议系统的客服助手,请以友好、乐于助人且愉快的方式来回复。我希望你可以以中文的方式给我回答。这里我问你日期的话再回答,否则的话你不要回答,今天的日期是 {current_date}.")
                
                //.defaultAdvisors(new PromptChatMemoryAdvisor(chatMemory))
                .defaultAdvisors(new MessageChatMemoryAdvisor(chatMemory))
                .build();
        // 调用chatClient的prompt方法,设置用户消息、系统消息参数、顾问参数,返回内容流
        Flux<String> content = chatClient.prompt()
                .user(message)
                .system(s->s.param("current_date", LocalDate.now().toString()))
                .advisors(a -> a.param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 100))
                .stream()
                .content();
        // 返回内容流
        return content;
    }

 响应结果

这样的话就会记住你最近的100条对话,当然这里你也可以自行实现ChatMemory,使用Redis去进行获取

 上面两种记忆是不同的,具体详情,请看下表

特性MessageChatMemoryAdvisorPromptChatMemoryAdvisor
记忆管理粒度基于消息(Message)级别的记忆基于提示(Prompt)级别的记忆
记忆的存储方式将每一条消息存储到记忆中生成合适的提示,将历史对话作为提示提供给模型
适用场景适用于需要精细管理每一条消息的场景适用于需要生成合适提示来为模型提供上下文的场景
如何影响对话直接影响聊天记录,确保每一条消息都被记住影响提示构造,确保生成的提示与上下文一致
内存管理的灵活性依赖于每个消息的存储提示的构造和上下文的动态管理

打印日志功能

 测试用例代码

其实也就是再次添加一个Advisors,再控制台打印一下就可以

public class LoggingAdvisors implements RequestResponseAdvisor {

    @Override
    public AdvisedRequest adviseRequest(AdvisedRequest request, Map<String, Object> adviseContext) {
        System.out.println("Request"+request);
        return request;
    }

    @Override
    public int getOrder() {
        return 0;
    }
}
// 使用GetMapping注解,指定请求路径为/AIChatDefaultStream,返回类型为文本流
    @GetMapping(value = "/AIChatDefaultLoggingStream",produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> AIChatDefaultLoggingStream(
            // 使用RequestParam注解,指定请求参数名为message,类型为String
            @RequestParam(value = "message") String message) {
        // 创建ZhiPuAiChatOptions对象,设置模型和温度
        ZhiPuAiChatOptions options = ZhiPuAiChatOptions.builder()
                .withModel(default_model)
                .withTemperature(default_temperature)
                .build();
        // 创建ChatModel对象,传入ZhiPuAiApi和options
        ChatModel chatModel = new ZhiPuAiChatModel(new ZhiPuAiApi(chatConfig.getApiKey()), options);
        // 创建ChatClient对象,传入chatModel,设置默认系统消息和默认顾问
        ChatClient chatClient = ChatClient.builder(chatModel).defaultSystem("你现在的身份是一个星轨会议系统的客服助手,请以友好、乐于助人且愉快的方式来回复。我希望你可以以中文的方式给我回答。这里我问你日期的话再回答,否则的话你不要回答,今天的日期是 {current_date}.")
                //.defaultAdvisors(new PromptChatMemoryAdvisor(chatMemory))
                .defaultAdvisors(new MessageChatMemoryAdvisor(chatMemory),new LoggingAdvisors())
                .build();
        // 调用chatClient的prompt方法,设置用户消息、系统消息参数、顾问参数,返回内容流
        Flux<String> content = chatClient.prompt()
                .user(message)
                .system(s->s.param("current_date", LocalDate.now().toString()))
                .advisors(a -> a.param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 100))
                .stream()
                .content();
        // 返回内容流
        return content;
    }

 响应结果

 

 FunctionCall回调

测试用例代码

package com.hhh.springai_test.Client;

import com.hhh.springai_test.config.ChatConfig;
import jakarta.annotation.Resource;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.zhipuai.ZhiPuAiChatModel;
import org.springframework.ai.zhipuai.ZhiPuAiChatOptions;
import org.springframework.ai.zhipuai.api.ZhiPuAiApi;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

@Component
public class ZhipuChatClient {

    @Resource
    private ChatConfig chatConfig;

    @Autowired
    @Qualifier("customMemory")  // 明确指定使用你自定义的 ChatMemory 实现
    private ChatMemory chatMemory;


    public ChatClient getZhipuChatClient() {
        ZhiPuAiChatOptions options = ZhiPuAiChatOptions.builder()
                .withModel("GLM-4-Flash")
                .withTemperature(0.95F)
                .build();

        // 创建ChatModel对象,传入ZhiPuAiApi和options
        ChatModel chatModel = new ZhiPuAiChatModel(new ZhiPuAiApi(chatConfig.getApiKey()), options);
        // 创建ChatClient对象,传入chatModel,设置默认系统消息和默认顾问
        ChatClient chatClient = ChatClient.builder(chatModel).defaultSystem("你现在的身份是一个星轨会议系统的客服助手,请以友好、乐于助人且愉快的方式来回复。我希望你可以以中文的方式给我回答。这里我问你日期的话再回答,否则的话你不要回答")
                .defaultAdvisors(new MessageChatMemoryAdvisor(chatMemory))
                .build();
        return chatClient;
    }
}
 @GetMapping(value = "/AIChatDefaultLoggingStream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> AIChatDefaultLoggingStream(@RequestParam(value = "message") String message) {
        Flux<String> content = zhipuChatClient.getZhipuChatClient().prompt()
                .user(message)
                .function("report", "举报", new Function<MyController.Request, Flux<String>>() {
                    @Override
                    public Flux<String> apply(MyController.Request request) {
                        System.out.println("举报名字是" + request.name);
                        return Flux.just("星轨");
                    }
                })
                .stream()
                .content();
        return content;
    }

 测试用例结果

 

RAG知识库

 测试用例代码

package com.hhh.springai_test.Advisor;

import org.springframework.ai.document.Document;
import org.springframework.ai.reader.tika.TikaDocumentReader;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.core.io.FileSystemResource;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

@Component
public class ZhiPuVectorStore implements VectorStore {

    private final List<Document> documentList = new ArrayList<>();


    @Override
    public void add(List<Document> documents) {
        if(documents != null && !documents.isEmpty()){
            if (documents != null && !documents.isEmpty()) {
                documentList.addAll(documents);
                documentList.forEach(document -> System.out.println("Document added: " + document.getContent()));
                System.out.println("Documents added successfully: " + documents.size());
            } else {
                System.out.println("No documents to add.");
            }
        }
    }

    @Override
    public Optional<Boolean> delete(List<String> idList) {
        if (idList == null || idList.isEmpty()) {
            return Optional.of(false);
        }
        boolean deleted = documentList.removeIf(doc -> idList.contains(doc.getId()));
        return Optional.of(deleted);
    }

    @Override
    public List<Document> similaritySearch(SearchRequest request) {
        System.out.println("Performing similarity search: " + request.getQuery());
        return new ArrayList<>();
    }

    public void readAndAddDocuments(String filePath) {
        try {
            FileSystemResource resource = new FileSystemResource(filePath);
            List<Document> documents = new TikaDocumentReader(resource).read();
            this.add(documents);
        } catch (Exception e) {
            System.err.println("Error reading and adding documents: " + e.getMessage());
        }
    }
}

 

    private final ZhiPuVectorStore zhiPuVectorStore;

    public MyController(ZhiPuVectorStore zhiPuVectorStore) {
        this.zhiPuVectorStore = zhiPuVectorStore;
    }

    @GetMapping(value = "/AIChatRagStream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> AIChatRagStream(@RequestParam(value = "message") String message) {
        String path = "src/main/resources/static/jubao.txt";
         zhiPuVectorStore.readAndAddDocuments(path);
        Flux<String> content = zhipuChatClient.getZhipuChatClient().prompt()
                .user(message)
                .advisors(new QuestionAnswerAdvisor(zhiPuVectorStore, SearchRequest.defaults()))
                .stream()
                .content();
        return content;
    }

 

 测试用例结果

可以看到日志也已经解决了,在这里遇到了整整两天的bug,一直在尝试使用functions,通过传递functionBean来进行解决,但是一直是有问题的,但是一直报错,只能使用这种方式来解决,当然,如果大佬们有好的文章,记得可以推荐我使用一下,谢谢各位大佬们

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2270010.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

嵌入式系统中C++的基本使用方法

大家好,今天主要给大家分享一下,最近操作C++代码的控制方法。 什么是构造函数?构造函数在对象实例化时被系统自动调用,仅且调用一次。 什么是析构函数?与构造函数相反, 在对象结束其生命周期时系统自动执行析构函数。 第一个:析构函数与构造函数区别 实例代码: #inclu…

【Qt】多元素控件:QListWidget、QTableWidget、QTreeWidget

目录 QListWidget 核心属性&#xff1a; 核心方法&#xff1a; 核心信号&#xff1a; 例子&#xff1a; QListWidgetItem QTableWidget 核心方法&#xff1a; 核心信号 QTableWidgetItem 例子&#xff1a; QTreeWidget 核心方法&#xff1a; 核心信号&#xff1a…

HTML5 标签输入框(Tag Input)详解

HTML5 标签输入框&#xff08;Tag Input&#xff09;详解 标签输入框&#xff08;Tag Input&#xff09;是一种用户界面元素&#xff0c;允许用户输入多个标签或关键词&#xff0c;通常用于表单、搜索框或内容分类等场景。以下是实现标签输入框的详细讲解。 1. 任务概述 标…

前端加载自己制作的栅格切片服务充当底图

注意mapview的center属性和tilelayer.fullExtent的区别。 前者是设置mapview显示的中心点坐标&#xff0c; const view new MapView({ container: "viewDiv", map: map, center:[100,25] }); 后者是读…

Windows 安装Mysql 8.1.0版本,并迁移数据库

一、下载MySQL压缩包 进入MySQL官网&#xff1a;https://downloads.mysql.com/archives/community/ 下载zip包到本地&#xff0c;然后解压缩。 二、安装MySQL 1、 创建my.ini文件 新创建一个my.ini文件&#xff0c;文件内容如下&#xff0c;记得修改【basedir】和【datadir…

uniapp——微信小程序,从客户端会话选择文件

微信小程序选择文件 文章目录 微信小程序选择文件效果图选择文件返回数据格式 API文档&#xff1a; chooseMessageFile 微信小程序读取文件&#xff0c;请查看 效果图 选择文件 /*** description 从客户端会话选择文件* returns {String} 文件路径*/ const chooseFile () &g…

学习threejs,导入CTM格式的模型

&#x1f468;‍⚕️ 主页&#xff1a; gis分享者 &#x1f468;‍⚕️ 感谢各位大佬 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍⚕️ 收录于专栏&#xff1a;threejs gis工程师 文章目录 一、&#x1f340;前言1.1 ☘️THREE.ColladaLoader DAE模…

1、pycharm、python下载与安装

1、去官网下载pycharm 官网&#xff1a;https://www.jetbrains.com/pycharm/download/?sectionwindows 2、在等待期间&#xff0c;去下载python 进入官网地址&#xff1a;https://www.python.org/downloads/windows/ 3、安装pycharm 桌面会出现快捷方式 4、安装python…

虚拟机Centos下安装Mysql完整过程(图文详解)

目录 一. 准备工作 1. 设置虚拟机静态IP 2. 卸载Mysql 3. 给CentOS添加rpm源 二. 安装MySQL 1. 安装mysql服务 2. 启动mysql服务 3. 开启MySQL开机自启动 4. 查看mysql服务状态 5. 查看mysql初始密码 6. 登录mysql &#xff0c;修改密码 7. 允许外部访问MySQL数据库…

SwiftUI:多语言实现富文本插值

实现的UI需求&#xff1a; 要求&#xff1a; 英文显示&#xff1a;3068 people have joined this plan today! 中文显示&#xff1a;今日有 3068 人已加入此计划&#xff01; 实现代码&#xff1a; Text(AttributedString(localized:"**\(payPeoples)** people have joi…

中巨伟业推出高安全高性能32位智能卡内核可编程加密芯片SMEC88SP/ST

1、产品特性  以最高安全等级的智能卡芯片内核为基础&#xff0c;具有极高的软硬件安全性  实现客户关键功能或算法代码下载&#xff0c;用户可以灵活实现自有知识产权的保护  标准 SOP8、SOT23-6 封装形式&#xff0c;器件封装小  标准 I2C 接口&#xff0c;具有接…

部署SenseVoice

依赖 Conda cuda pythor 查看GPU版本-CSDN博客 创建虚拟conda环境 conda create --name deeplearn python3.10 conda activate deeplearn git clone https://github.com/FunAudioLLM/SenseVoice.git cd SenseVoice pip install -r requirements.txt pip install gradio pip …

微信流量主挑战:用户数30!新增文档转化功能,解决docker运行jar包报错SimSun找不到的问题(新纪元5)

哎呀&#xff0c;今天忙到飞起&#xff0c;文章晚点更新啦&#xff01;不过好消息是&#xff0c;我们的小程序用户终于突破30啦&#xff0c;感谢大家的支持&#xff01;而且&#xff0c;大家期待已久的文档转化功能明天就要上线啦&#xff0c;目前支持word转pdf&#xff0c;pdf…

操作系统课后题总复习

目录 一、第一章 1.1填空题 1.2单项选择题 1.3多项选择题 1.4判断题 1.5名词解释 1.6简答题 二、第二章 2.1填空题 2.2单项选择题 2.3 多项选择题 2.4判断题 2.5名词解释 2.6简答题 三、第三章 3.1填空题 3.2单项选择题 3.3多项选择题 3.4判断题 3.5名词解…

C语言期末复习笔记(下)

目录 九、指针 1.指针变量的定义和初始化 2.间接寻址符* 3.按值调用和按址调用 4.实例 5.函数指针 6.指针变量和其它类型变量的对比 十、字符串 1.字符串常量 2.字符串的存储 3.字符指针 4.字符串的访问和输入/输出 5.字符串处理函数 &#xff08;1&#xff09;str…

保姆级教程Docker部署ClickHouse镜像

目录 1、安装Docker及可视化工具 2、创建挂载目录 3、获取配置文件 4、运行ClickHouse容器 5、Compose运行ClickHouse容器 6、查看ClickHouse运行状态 7、安装包部署 1、安装Docker及可视化工具 Docker及可视化工具的安装可参考&#xff1a;Ubuntu上安装 Docker及可视化…

飞牛私有云APP结合cpolar内网穿透技术实现远程连接本地fnOS NAS

文章目录 前言1. 本地连接测试2. 飞牛云安装Cpolar3. 配置公网连接地址4. 飞牛云APP连接测试5. 固定APP远程地址6. 固定APP地址测试 前言 现在生活和工作中的各种设备都变得越来越智能&#xff0c;而数据存储的需求也随之剧增。想象一下&#xff1a;你正在外地出差&#xff0c…

计算机网络 (17)点对点协议PPP

一、PPP协议的基本概念 PPP协议最初设计是为两个对等节点之间的IP流量传输提供一种封装协议&#xff0c;它替代了原来非标准的第二层协议&#xff08;如SLIP&#xff09;。在TCP/IP协议集中&#xff0c;PPP是一种用来同步调制连接的数据链路层协议&#xff08;OSI模式中的第二层…

RC充电电路仿真与分析

RC充电原理 下图是一个常见的RC充电电路&#xff1a;&#xff08;假设R10K&#xff0c;C100nF&#xff09; SW断开时&#xff0c;这个电路处于断路状态&#xff0c;C既没有充电也没有放电&#xff1b;SW闭合时&#xff0c;直流电源5V为电容C充电&#xff1b; 充电时电容两端…

全新免押租赁系统助力商品流通高效安全

内容概要 全新免押租赁系统的推出&#xff0c;可以说是一场商品流通领域的小革命。想象一下&#xff0c;不再为押金烦恼&#xff0c;用户只需通过一个简单的信用评估&#xff0c;就能快速租到所需商品&#xff0c;这种体验简直令人惊喜&#xff01;这个系统利用代扣支付技术&a…