Spring AI 实现智能对话
1. 实现效果
2. Spring Boot 3 后端
2.1 pom.xml
<!-- 管理包依赖。通过 Spring AI 的 BOM 文件统一管理所有 Spring AI 相关依赖的版本,确保版本一致性,减少冲突 --><dependencyManagement><dependencies><!-- Spring AI --><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-bom</artifactId><version>1.0.0-M6</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement><!-- Spring Ai OpenAI 模型依赖 --><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-openai-spring-boot-starter</artifactId></dependency>
2.2 application.yml
spring:ai:openai:base-url: https://dashscope.aliyuncs.com/compatible-mode # 默认使用 v1 版本号,如果添加会变成 /compatible-mode/v1/v1api-key: xxx # 阿里云百炼平台获取chat:options:model: qwen-max # 模型名称temperature: 0.8 # 模型温度,值越大,输出结果越随机embedding:options:model: text-embedding-v1 # RAG 指定文本向量化嵌入模型
2.3 AI 对话表,存储对话信息
CREATE TABLE `ai_conversation` (`id` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '主键',`title` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '标题',`content` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci COMMENT '内容',`creator_id` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '创建人ID',`create_time` datetime NOT NULL COMMENT '创建时间',`ts` datetime NOT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',`delete_flag` int NOT NULL DEFAULT '0' COMMENT '逻辑删除字段 0.正常 1.删除',PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='AI对话表';
2.4 Config(AI对话配置类)
package com.dragon.springboot3vue3.config;import com.dragon.springboot3vue3.tools.UserTools;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;
import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.memory.InMemoryChatMemory;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.ai.reader.TextReader;
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
import org.springframework.ai.vectorstore.SimpleVectorStore;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;@Configuration
public class OpenAIConfig {@Autowiredprivate UserTools userTools;/*** 配置会话存储方式:内存* 建议存储在 Redis* @return*/@Beanpublic ChatMemory chatMemory() {return new InMemoryChatMemory(); // 将历史会话临时存储在应用内存中(重启应用后数据丢失)。生产环境需要持久化存储(如 Redis、数据库)}/*** 配置向量数据库存储方式:内存* 建议存储在 Redis* @param embeddingModel* @return*/@Beanpublic VectorStore vectorStore(EmbeddingModel embeddingModel) {return SimpleVectorStore.builder(embeddingModel).build();}/*** Spring Boot 启动时运行,将知识库存入向量数据库* @param vectorStore* @param resource* @return*/@Beanpublic CommandLineRunner commandLineRunner(VectorStore vectorStore,@Value("classpath:/rag/knowledge.txt") Resource resource) {return args -> {vectorStore.write( // 3. 写入向量数据库new TokenTextSplitter().transform( // 2. 转换为向量数据库文档new TextReader(resource).read() // 1. 读取知识库));};}@Beanpublic ChatClient chatClient(OpenAiChatModel model, ChatMemory chatMemory) {return ChatClient.builder(model).defaultSystem("""你是韶君,一个温柔可爱机智的智能助手。请遵守以下规则:1. 当用户问"你是谁"时,必须且只能回答:"我是韶君,你的智能助手,有什么可以帮你的吗?"2. 不要提及RAG、检索或其他技术实现细节3. 使用自然、友好的语气回答问题4. 数学计算使用标准符号(如 18 ÷ 6 = 3)""").defaultAdvisors(new SimpleLoggerAdvisor(), // 配置日志 Advisor,需在 yml 开启日志级别(默认:debug)new MessageChatMemoryAdvisor(chatMemory) // 配置会话记忆).defaultTools(userTools) // Tool Calling.build();}
}
2.5 Controller
package com.dragon.springboot3vue3.controller;import cn.dev33.satoken.util.SaResult;
import com.dragon.springboot3vue3.controller.dto.entityDto.AiConversationDto;
import com.dragon.springboot3vue3.controller.dto.entityDto.AiConversationHistoryDto;
import com.dragon.springboot3vue3.controller.dto.entityDto.ChatDto;
import com.dragon.springboot3vue3.entity.AiConversation;
import com.dragon.springboot3vue3.service.IAiConversationService;
import com.dragon.springboot3vue3.tools.UserTools;
import com.dragon.springboot3vue3.utils.StringDTO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.AbstractChatMemoryAdvisor;
import org.springframework.ai.chat.client.advisor.QuestionAnswerAdvisor;
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
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.Prompt;
import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import java.util.List;@Tag(name = "Spring AI 对话接口")
@RestController
@RequestMapping("/aiConversation")
public class AiConversationController {@Autowiredprivate IAiConversationService aiConversationService;@Autowiredprivate ChatMemory chatMemory;@Autowiredprivate ChatClient chatClient;@Autowiredprivate UserTools userTools;@Autowiredprivate VectorStore vectorStore;/*** 流式输出(要设置流式输出格式)*/@Operation(summary = "对话")@PostMapping(value = "/conversation",produces = "text/html;charset=utf-8")public Flux<String> chatClient(@RequestBody ChatDto chatDto) {return chatClient.prompt().user(chatDto.getPrompt()).advisors(new QuestionAnswerAdvisor(vectorStore, SearchRequest.builder().query(chatDto.getPrompt()).build())) // 对话时,使用向量数据库.advisors(item -> item.param(AbstractChatMemoryAdvisor.CHAT_MEMORY_CONVERSATION_ID_KEY, chatDto.getConversationId())) // 根据会话ID,在ChatMemory存取会话.stream().content();}@Operation(summary = "新增或更新对话")@PostMapping("/saveOrUpdate")public SaResult saveOrUpdate(@RequestBody @Validated AiConversationDto aiConversationDto){AiConversation aiConversation=new AiConversation();BeanUtils.copyProperties(aiConversationDto,aiConversation);aiConversationService.saveOrUpdate(aiConversation);return SaResult.ok();}@Operation(summary = "所有对话列表")@GetMapping("/getAll")public SaResult getAll(){List<AiConversation> list = aiConversationService.lambdaQuery().orderByDesc(AiConversation::getCreateTime).list();return SaResult.ok().setData(list);}@Operation(summary = "对话的历史记录")@PostMapping("/history")public SaResult history(@RequestBody StringDTO stringDTO){List<Message> messages = chatMemory.get(stringDTO.getStr(), Integer.MAX_VALUE);if(messages == null || messages.isEmpty()){return SaResult.ok().setData(List.of());}return SaResult.ok().setData(messages.stream().map(AiConversationHistoryDto::new).toList());}
}
2.6 Service
package com.dragon.springboot3vue3.service;import com.dragon.springboot3vue3.entity.AiConversation;
import com.baomidou.mybatisplus.extension.service.IService;public interface IAiConversationService extends IService<AiConversation> {}
2.7 ServiceImpl
package com.dragon.springboot3vue3.service.impl;import com.dragon.springboot3vue3.entity.AiConversation;
import com.dragon.springboot3vue3.mapper.AiConversationMapper;
import com.dragon.springboot3vue3.service.IAiConversationService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;@Service
public class AiConversationServiceImpl extends ServiceImpl<AiConversationMapper, AiConversation> implements IAiConversationService {}
2.8 ChatDto、AiConversationDto 、StringDTO
package com.dragon.springboot3vue3.controller.dto.entityDto;import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;import java.io.Serializable;@Data
public class ChatDto implements Serializable {private static final long serialVersionUID = 1L;@Schema(description = "会话ID")String conversationId;@Schema(description = "提示词")String prompt;
}
package com.dragon.springboot3vue3.controller.dto.entityDto;import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;@Schema(description = "前端请求参数封装")
@Data
public class AiConversationDto {@Schema(description = "主键")private String id;@Schema(description = "标题")private String title;
}
package com.dragon.springboot3vue3.utils;import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;@Data
public class StringDTO {@NotEmpty@Schema(description = "字符串")private String str;
}
2.9 rag -> knowledge.txt
3. Vue 3 + ElementPlus 前端
3.1 安装 marked(将 Markdown 转换为 HTML)、dompurify(防止XXS [跨站脚本攻击])
npm i marked
npm i dompurify
3.2 AiConversation.vue
<template><div class="container"><el-card class="left"><div class="newConversationBox"><el-button class="newConversationBtn" @click="saveOrUpdate()" type="primary" :icon="Plus" round >开启新对话</el-button></div><div class="conversation" :class="{ 'active': item.id === conversationModel.id }" v-for="item in conversationList" :key="item.id" ><el-text class="text" @click="selectConversation(item)" line-clamp="1"> {{ item.title == "" ? item.id : item.title }} </el-text><el-dropdown trigger="click"><el-icon class="moreFilled" :size="20"> <MoreFilled /> </el-icon><template #dropdown><el-dropdown-menu><el-dropdown-item :icon="EditPen" @click="rename(item)">重命名</el-dropdown-item></el-dropdown-menu></template></el-dropdown><!-- 重命名弹窗 --><el-dialog v-model="visible" width="30%" @close="close"><template #header><h1>重命名</h1></template><el-form :model="conversationModel" ref="formRef" label-width="100px"><el-form-item label="id" prop="id"><el-input v-model="conversationModel.id" placeholder="请输入会话标题"></el-input></el-form-item><el-form-item label="会话标题" prop="title"><el-input v-model="conversationModel.title" placeholder="暂无会话标题"></el-input></el-form-item><el-form-item><el-button @click="visible = false">取消</el-button><el-button type="primary" @click="saveOrUpdate()">确认</el-button></el-form-item></el-form></el-dialog></div></el-card><el-card class="right"><div class="conversation-content" ><el-scrollbar ref="scrollbarRef" height="600px"><div class="history" v-for="(item, index) in history" :key="index"><div class="user" v-if="item.role === 'user'" ><span class="userContent" >{{ item.content }}</span><el-avatar :icon="UserFilled" /></div><div class="assistant" v-if="item.role === 'assistant'"><span><el-avatar :icon="Platform" /></span><div class="assistantContent" v-html="item.content"></div></div></div></el-scrollbar></div><div class="sendBox" v-if="conversationModel.id"><el-input class="custom-textarea"v-model="model.prompt"@keydown.enter="send":disabled="loadingFlag"type="textarea":rows="3":autosize="{ minRows: 2, maxRows: 6 }":placeholder="loadingFlag ? '加载中...' : '给 AI 发送消息'" /><el-button @click="send" :disabled="loadingFlag" :loading="loadingFlag" type="primary" :icon="Position" circle size="large"></el-button></div></el-card></div>
</template><script setup lang="ts">import { ref,reactive,onMounted,nextTick } from 'vue'import { Plus,Position,MoreFilled,EditPen,Platform,UserFilled } from '@element-plus/icons-vue'import conversationApi from '@/api/ai/conversation';import { ElMessage } from 'element-plus'import { marked } from 'marked' // 将 Markdown 转换为 HTMLimport DOMPurify from 'dompurify' // 防止XXS(跨站脚本攻击)// 发送消息需要的 modellet model = reactive({conversationId: '',prompt: ''})// 新增会话需要的 modellet conversationModel = reactive({id: '',title: ''})const initConversationModel = { ...conversationModel }// 对话历史 Refconst scrollbarRef = ref()// 新增、编辑弹窗是否可见const visible = ref(false)// 是否加载中const loadingFlag = ref(false)// 重命名表单 Refconst formRef=ref()// 存储当前对话的历史记录const history = ref([]) as any// 对话列表let conversationList = ref([]) as any// 发送消息const send = async()=>{// 去除前后空格后检查是否为空if (!model.prompt || !model.prompt.trim()) {ElMessage.warning("发送的消息不能为空!");return;}loadingFlag.value = true;history.value.push({role: "user",content: model.prompt})scrollToBottom()// 先添加一条空的 assistant 消息history.value.push({role: "assistant",content: ""})// 获取最后一条消息的引用const lastMessage = history.value[history.value.length - 1];try {// 获取流式响应const reader = await conversationApi.chat(model) as any;// 清空发送的消息model.prompt = ""// 创建解码器(用于解析二进制流)const decoder = new TextDecoder('utf-8');while (true) {const { done, value } = await reader.read(); // 读取一块数据if (done) break; // 流结束时退出 // 解码数据并追加到最后一条消息的contentlastMessage.content += decoder.decode(value);// 强制触发Vue的响应式更新(重要!)history.value = [...history.value];}// 使用 marked 将 Markdown 转换为 HTML// 使用 DOMPurify 清理 HTML,防止 XSS 攻击lastMessage.content = DOMPurify.sanitize(marked(lastMessage.content) as any);history.value = [...history.value];} catch (error) {console.error('API Error:', error);alert('发送消息失败,请稍后重试');} finally {loadingFlag.value = false;scrollToBottom()}}// 滚动到底部const scrollToBottom = async() => {// 等待DOM更新await nextTick();// 滚动到底部scrollbarRef.value.setScrollTop(scrollbarRef.value.wrapRef.scrollHeight || 0);}// 新增或更新会话const saveOrUpdate = async()=>{const response = await conversationApi.saveOrUpdate(conversationModel) as any;ElMessage.success(response.msg);// 刷新对话列表getAll();}// 所有会话列表const getAll = async()=>{const response = await conversationApi.getAll();conversationList.value = response.dataif(conversationList.value != null){conversationModel.id = conversationList.value[0].id // 选中第1个对话model.conversationId = conversationModel.id}getHistoryToHtml(model.conversationId)}// 选择对话const selectConversation = async(item:any)=>{history.value = []conversationModel.id = item.idif(item.title != ""){conversationModel.title = item.title}model.conversationId = conversationModel.idgetHistoryToHtml(item.id)}// 根据对话ID请求后端,将后端响应数据转为 HTMLconst getHistoryToHtml = async(conversationId:string)=>{const res = await conversationApi.getHistory(conversationId);// 数组判空if(Array.isArray(res.data) && res.data.length !=0 ){// 会话历史转为 HTMLhistory.value = res.data.map(((item:any) => ({...item,content: item.role === 'assistant' ? DOMPurify.sanitize(marked(item.content) as any) : item.content})))scrollToBottom()}}// 对话重命名const rename = (item:any)=>{visible.value = true;conversationModel.title = item.title}// 关闭重命名弹窗const close= ()=>{// 清空数据Object.assign(model, initConversationModel);visible.value = false}onMounted(()=>{getAll()})</script><style scoped lang="less">
// 覆盖用户代理样式
body div#app {display: flex;
}.container{height: 100%;box-sizing: border-box; display: flex;justify-content: space-between;
}
.left{height: 100%;width: 20%;
}
.right{height: 100%;width: 79%;display: flex;flex-direction: column;.conversation-content{// height: 100%;flex: 1;.history{margin: 0 20px;.user{display: flex;justify-content: flex-end;margin: 20px 0;.userContent{margin-right: 10px;}}.assistant{display: flex;margin: 20px 0;.assistantContent{margin-left: 10px;}}}}.sendBox{display: flex;justify-content: center;align-items: center;margin-top: 20px;}.custom-textarea{margin: 0 10px;}.custom-textarea ::v-deep .el-textarea__inner {resize: none; /* 禁用手动调整大小 */}
}.conversation{display: flex;align-items: center;height: 30px;margin: 5px 0;border-radius: 20px;padding: 0 8px;justify-content: space-between;.text{flex: 1;}&.active{background-color: #f3f2ff;.text{color: black;}.moreFilled{color: black;}}
}
.newConversationBox{display: flex;justify-content: center;align-items: center;margin-bottom: 20px;
}</style>
3.3 api -> ai -> conversation.ts
import request from "@/utils/request";
import {useTokenStore} from '@/stores/token'// 聊天 API
// fetch API 来处理流式响应
export default{async chat(value:any){try{const response = await fetch(`${request.defaults.baseURL}/aiConversation/conversation`, {method: 'POST',headers: {'Content-Type': 'application/json','X-Token': useTokenStore().token,},body: JSON.stringify(value) // 显式转换为JSON字符串})return response.body?.getReader()} catch (error) {console.error('Conversation API Error:', error)throw error}},// 新增或更新会话saveOrUpdate(value:any){return request.post('/aiConversation/saveOrUpdate',value);},// 所有会话列表getAll(){return request.get('/aiConversation/getAll');},// 所有会话列表getHistory(str:string){return request.post('/aiConversation/history',{ str });},
}
相关文章:
Spring AI 实现智能对话
1. 实现效果 2. Spring Boot 3 后端 2.1 pom.xml <!-- 管理包依赖。通过 Spring AI 的 BOM 文件统一管理所有 Spring AI 相关依赖的版本,确保版本一致性,减少冲突 --><dependencyManagement><dependencies><!-- Spring AI -->…...
2025 网络安全技术深水区探索:从 “攻防对抗” 到 “数字韧性” 的范式跃迁
引言:当攻击成本趋近于零,防御逻辑必须重构 2025 年,网络安全领域正经历三重根本性变革: 攻击者门槛坍缩:生成式 AI 将网络钓鱼开发效率提升 300%,勒索软件即服务(RaaS)订阅用户突破…...
学习笔记—双指针算法—移动零
双指针算法 移动零 283. 移动零 - 力扣(LeetCode) 题目描述: 给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。 请注意 ,必须在不复制数组的情况下原地对数组进…...
计算机网络全栈精讲:从 TCP/UDP 原理到 Socket 编程与 HTTP 协议实战(含代码实现)
计算机网络作为现代信息技术的基石,支撑着互联网、物联网、云计算等众多领域的发展。无论是前端开发者、后端工程师,还是运维人员,深入理解计算机网络原理都至关重要。本文将从网络分层模型出发,逐步深入讲解 TCP/UDP 协议、Socke…...
IP地址如何切换到国内别的省份?一步步指导
使用换IP工具的主要目的是通过更换设备的公网IP地址来满足特定需求,例如绕过限制、保护隐私或完成特定任务。以下是常见的应用场景和原因: 一、ip应用场景 1. 绕过IP限制 访问地域限制内容:某些网站或服务(如游戏、社交平台 &am…...
c++流对象
核心概念回顾: C 的流库 (<iostream>, <fstream>, <sstream>) 提供了一种统一的方式来处理输入和输出,无论数据是来自键盘、文件还是内存中的字符串。它们都基于 std::istream (输入流基类) 和 std::ostream (输出流基类),…...
华为IP(5)
交换机的堆叠与集群 堆叠和集群指的是同一件事 前言: 随着企业的发展,企业网络的规模越来越大,这对企业网络提出了更高的要求:更高的可靠性、更低的故障恢复时间、设备更加易于管理等。 传统的园区网高可靠性技术出现故障时很难做到毫秒级…...
零信任架构下的等保 2.0 与密评密改双合规
随着《网络安全等级保护2.0》和《商用密码应用安全性评估》的深入实施,企业面临双重合规压力:既要满足等保2.0对“主动防御”和“动态防护”的要求,又要通过密评密改强化密码技术的合规性。传统安全架构依赖边界防护和静态密码策略࿰…...
华为 MRAG:多模态检索增强生成技术论文阅读
GitHub项目链接:https://github.com/PanguIR/MRAGSurvey 总览 多模态检索增强生成(MRAG)通过将文本、图像、视频等多模态数据整合到检索与生成过程中,显著提升了多模态大语言模型(MLLM)的性能。传统检索增…...
文字光影扫过动效
列子1 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>光影扫过文字动效</title><styl…...
SpringBoot配置RestTemplate并理解单例模式详解
在日常开发中,RestTemplate 是一个非常常用的工具,用来发起HTTP请求。今天我们通过一个小例子,不仅学习如何在SpringBoot中配置RestTemplate,还会深入理解单例模式在Spring中的实际应用。 1. 示例代码 我们首先来看一个基础的配置…...
计算机网络应用层(5)-- P2P文件分发视频流和内容分发网
💓个人主页:mooridy 💓专栏地址:《计算机网络:自顶向下方法》 大纲式阅读笔记_mooridy的博客-CSDN博客 💓本博客内容为《计算机网络:自顶向下方法》第二章应用层第五、六节知识梳理 关注我&…...
C++ TCP通信原理与实现
C 中 TCP 通信的原理基于 TCP/IP 协议栈的实现,以下是核心原理和关键步骤的详细说明: 一、TCP 通信核心原理 面向连接 通信双方需通过 三次握手 建立可靠连接,确保通信通道稳定。通过 四次挥手 断开连接,保证数据完整性。 可靠传…...
计算机网络-运输层(1)
计算机网络-运输层(1) 文章目录 计算机网络-运输层(1)5.1 运输层概述5.2 运输层端口号、复用与分用端口号基本概念端口号特性端口号分类重要说明 5.3 UDP与TCP协议对比关键区别说明 5.1 运输层概述 计算机网络体系结构中的物理层、数据链路层以及网络层共同解决了主机通过异构…...
学习spark-streaming收获
1.流处理的核心概念 •实时 vs微批处理:理解了 Spark Streaming 的微批处理(Micro-Batch)模型,将流数据切分为小批次(如1秒间隔)进行处理,与真正的流处理(如Flink)的区…...
蓝桥杯 14. 奇怪的数列
奇怪的数列 原题目链接 题目描述 从 X 星截获一份电码,是一些数字,如下: 13 1113 3113 132113 1113122113 ⋯⋯YY 博士经彻夜研究,发现了规律: 第一行的数字随便是什么,以后每一行都是对上一行 “读出…...
前端高频面试题day2
如何在vue3中使用defineAsyncComponent实现异步组件加载 在 Vue 3 中,使用 defineAsyncComponent 实现异步组件加载的步骤如下: 引入方法:从 Vue 中导入 defineAsyncComponent。定义异步组件:通过 defineAsyncComponent 包装一个…...
Linux系统之设置开机启动运行桌面环境
Linux 开机运行级别介绍与 Ubuntu 桌面环境配置指南 一、Linux 开机运行级别(Runlevel) 在传统的 Linux 系统(如 SysV init 初始化系统)中,运行级别定义了系统启动时加载的服务和资源。常见的运行级别如下: 运行级别模式用途0Halt(停机模式)关闭系统1Single User Mode…...
Python PyAutoGUI库【GUI 自动化库】深度解析与实战指南
一、核心工作原理 底层驱动机制: 通过操作系统原生API模拟输入使用ctypes库调用Windows API/Mac Cocoa/Xlib屏幕操作依赖Pillow库进行图像处理 事件模拟流程: #mermaid-svg-1CGDRNzFNEffhvSa {font-family:"trebuchet ms",verdana,arial,sans…...
【MobaXterm】win10下载v25.1安装流程
【下载地址】 官网: https://mobaxterm.mobatek.net/ 下载安装版,解压使用更快一些 【v20.0中文安装包】 夸克网盘:https://pan.quark.cn/s/2ad5b59e6d8e#/list/share 对应的指导教程: MobaXterm中文版安装使用教程-附安装包…...
2025.4.22 JavaScript 常用事件学习笔记
一、事件概述 JavaScript 事件是指在用户与网页交互或网页状态发生变化时所触发的操作。通过使用事件,可以为网页添加丰富的动态功能,实现用户与页面之间的互动,让网页不再只是静态的展示内容。 二、常见鼠标事件 click 事件 简介 …...
Android 13.0 MTK Camera2 设置默认拍照尺寸功能实现
Android 13.0 MTK Camera2 设置默认拍照尺寸功能实现 文章目录 需求:参考资料架构图了解Camera相关专栏零散知识了解部分相机源码参考,学习API使用,梳理流程,偏应用层Camera2 系统相关 修改文件-修改方案修改文件:修改…...
Linux:基础IO 文件系统
Linux:基础IO && 文件系统 一、系统IO(一)系统文件操作接口1、open2、write3、read (二)文件描述符1、概念2、标准输入、标准输出、标准错误 (三)dup系统调用(重定向原理) 二…...
近期有哪些断链危机?如何提升供应链风险管理能力?
全球供应链格局正经历深刻变革,其网络架构愈发复杂,涉及多国企业主体且涵盖多个节点与复杂环节,管理难度显著增大。从原材料采购到终端交付,运输、仓储、加工等任一环节均存在潜在风险,单一环节效率滞后易引发系统性连…...
知识科普|褪黑素的发展历程及应用研究进展
睡眠作为维持人体健康的重要生理机制,其节律性受到精密调控。在昼夜节律系统的调控下,人类普遍遵循周期性单次睡眠模式,这一过程涉及复杂的神经-体液调控网络。其中神经元活动、神经递质传导、激素分泌及遗传调控机制共同作用于睡眠觉醒系统&…...
企业如何构建一个全面的Web安全防护体系
企业如何构建一个全面的Web安全防护体系 企业构建全面的Web安全防护体系需融合战略规划、技术防御、持续运营和风险治理四大维度,以下是基于行业最佳实践的系统化方案: 一、顶层设计:治理架构与安全战略 战略规划与合规驱动 制定网络安全愿…...
T8332FN凯钰LED驱动芯片多拓扑车规级AEC-Q100
T8332FN是一款支持多拓扑结构的恒流LED驱动控制芯片,适用于汽车照明及高功率LED应用,具备宽电压输入、高精度调光及多重保护功能。 核心特性 - 输入与拓扑:支持5-60V宽电压输入,适配Boost、Buck、Buck-Boost、SEPIC四种拓扑结构&…...
Redis一些小记录
Redis一些小记录 SpringData Redis:RedisTemplate配置与数据操作 操作String类型数据 String是Redis中最基本的数据类型,可以存储字符串、整数或浮点数。RedisTemplate提供了ValueOperations接口来操作String类型的数据,支持设置值、获取值、…...
Win10安装 P104-100 驱动
安装完之后总结一下, 之前做了不少功课, 在网上搜了很多教程, 视频的文字的, 但是很多已经比较陈旧了. 最后发现的这个 GitHub 项目 NVIDIA-patcher 是最有用的, 因为这是现在这些魔改驱动的来源. NVIDIA-patcher 仓库地址: https://github.com/dartraiden/NVIDIA-patcher 安…...
Android开机动画资源包制作(测试使用)
开机动画资源包需要采用仅存储的方式进行压缩,不能使用压缩软件直接压缩生成。 如果是系统开发人员,可以在源码目录中,采用bootanim程序提供的制作方式进行,下面可供测试人员自行制作。 制作流程 1)基于设备中已有开…...
PWN基础-利用格式化字符串漏洞泄露canary结合栈溢出getshell
测试源码: #include<stdio.h> void exploit() {system("/bin/sh"); } void func() {char str[0x20];read(0, str, 0x50);printf(str);read(0, str, 0x50); } int main() {func();return 0; } 编译,开启 canary 保护,关闭 p…...
Kafka HA集群配置搭建与SpringBoot使用示例总结
Kafka HA集群配置搭建与SpringBoot使用示例总结 一、Kafka高可用(HA)集群搭建 1. 环境准备 至少3台服务器(推荐奇数台,如3、5、7)已安装Java环境(JDK 1.8)下载Kafka二进制包(如kafka_2.13-3.2.1.tgz&…...
MSO-Player:基于vlc的Unity直播流播放器,支持主流RTSP、RTMP、HTTP等常见格式
MSO-Player 基于libVLC的Unity视频播放解决方案 支持2D视频和360度全景视频播放的Unity插件 📑 目录 🎥 MSO-Player 📋 功能概述🚀 快速入门📚 关键组件📝 使用案例🔌 依赖项📋 注意…...
97A6-ASEMI无人机专用功率器件97A6
编辑:ll 97A6-ASEMI无人机专用功率器件97A6 型号:97A6 品牌:ASEMI 封装:SOT-23 批号:最新 引脚数量:3 特性:双向可控硅 工作温度:-40℃~150℃ 97A6双向可控硅:…...
body Param Query 三个 不同的入参 分别是什么意思 在前端 要怎么传 这三种不同的参数
在 NestJS 中,Body()、Param() 和 Query() 用于处理不同类型的请求参数。以下是它们的含义及前端传递方式: Body():请求体参数 • 含义:用于获取请求体中的数据(如 POST/PUT 请求中提交的 JSON、表单数据等)…...
生成式人工智能认证(GAI认证)含金量怎么样?
当生成式人工智能(Generative AI)的浪潮以摧枯拉朽之势重塑职业版图时,一个尖锐的问题正悬在无数人的心头:在技术迭代比眨眼更快的时代,如何证明自己具备驾驭AI的核心能力? 这场认知革命的背后,一张认证证书的价值早已超越了纸面——它既是个人能力的“信用背书”,也是…...
环境DNA宏条形码技术,鱼类检测引物如何选择?
环境DNA(eDNA)宏条形码技术在鱼类多样性调查研究中的优势明显,相比于传统调查方式,eDNA宏条形码技术灵敏度更高,能够更好地揭示鱼类的丰富度,并且具有高时效性。然而,在使用这个技术的过程中&am…...
Scala集合操作与WordCount案例实战总结
集合计算简单函数 1、说明 (1)求和 (2)求乘积 (3)最大值 (4)最小值 (5)排序 2、案例实操 object demo29{ def main(args: Array[String]): Unit { val…...
Spark-Streaming核心编程(四)总结
有状态转化操作 - UpdateStateByKey 功能描述 UpdateStateByKey原语用于在DStream中跨批次维护状态,例如流计算中的累加wordcount。 它允许对一个状态变量进行访问和更新,适用于键值对形式的DStream。 工作原理 给定一个由(键,事…...
关系型数据库PostgreSQL for Mac 保姆级使用教程
第一部分:安装PostgreSQL 方法一:使用Postgres.app(最简单) 访问 Postgres.app官网 下载最新版本,将 Postgres.app 移动到 “Applications” 文件夹。 双击Postgres.app打开应用,点击"Initialize&q…...
新增 29 个专业,科技成为关键赛道!
近日,教育部正式发布《普通高等学校本科专业目录(2025年)》,新增 29 个本科专业,包括区域国别学、碳中和科学与工程、海洋科学与技术、健康与医疗保障、智能分子工程、医疗器械与装备工程、时空信息工程、国际邮轮管理…...
云计算市场的重新分类研究
云计算市场传统分类方式,比如按服务类型分为IaaS、PaaS、SaaS,或者按部署模式分为公有云、私有云、混合云。主要提供计算资源、存储和网络等基础设施。 但随着AI大模型的出现,云计算市场可以分为计算云和智算云,智算云主要是AI模…...
大模型时代的具身智能:从虚拟到现实的智能体进化革命
一、具身智能:重新定义 AI 与物理世界的交互范式 (一)概念解析:从 "离身" 到 "具身" 的认知革命 具身智能(Embodied AI)是融合大模型决策能力与物理实体执行能力的新型智能系统&…...
鸿蒙NEXT开发正则工具类(ArkTs)
import { FormatUtil } from ./FormatUtil;/*** 正则工具类* author CSDN-鸿蒙布道师* since 2025/04/27*/ export class RegexUtil {/*** 英文字母、数字和下划线*/static readonly REG_GENERAL "^\\w$";/*** 数字*/static readonly REG_NUMBERS "^\\d$"…...
Flink维表深度解析
一、维表的概念与作用 维表(Dimension Table) 是数据仓库中的核心概念,通常用于存储静态或缓慢变化的业务实体信息(如用户资料、商品信息、地理位置等)。在实时流处理场景中,维表的作用是为主数据流&#…...
基于ArcGIS的洪水灾害普查、风险评估及淹没制图技术研究
一、洪水普查技术规范解读 1.1 全国水旱灾害风险普查实施方案解读 1.2 洪水风险区划及防治区划编制技术要求解读 1.3 山丘区中小河流洪水淹没图编制技术要求解读 二、ArcGIS介绍及数据管理 2.1 ArcGIS界面及数据加载 2.2 ArcGIS常见数据格式 2.3基于Geodatabase的洪水灾…...
初识数据结构——二叉树从基础概念到实践应用
数据结构专栏 ⬅(click) 初识二叉树:从基础概念到实践应用🌳 一、树型结构基础 1.1 树的基本概念 树是一种非线性的数据结构,由n(n>0)个有限节点组成一个具有层次关系的集合。它看起来像一棵倒挂的树,根朝上而叶朝下。 关键特…...
手搓传染病模型(SEIR)
先看模型 在本模型中,人群有四种自然史状态:易感者(S),暴露者(E),感染者(I)以及康复者(R) 2.模型假设人群分布是同质均匀的,未考虑人群出生、死亡、迁入迁出对疾病传播的影响 3.康复者永久免疫:康复者永久免…...
企业数据赋能 | 应用模板分享:汽车销售仪表板
实时监控销售数据,比较车型、地区业绩~ 今天,小编向大家分享 Tableau 应用分析模板:由 Imran Shaikh 搭建的汽车销售仪表板。借助此仪表板,企业可以实时跟踪销售情况,了解市场趋势,并比较不同车型、地区和销…...
C++?动态内存管理!!!
一、引言 之前我们一起讨论了类和对象的相关知识,接下来我们将继续完善我们的知识体系,为以后继续深入学习C知识添砖加瓦,在本期我们将一起学习C中关于动态内存管理的相关知识,在学习之前将要先回顾C语言中是如何进行动态内存管理…...