SpringBoot对接火山引擎大模型api实现图片识别与分析
文章目录
- 一、前言
- 二、创建应用
- 三、后端
- 1.SDK集成
- 2.调用Rest API
- 四、前端
一、前言
Spring AI实战初体验——实现可切换模型AI聊天助手-CSDN博客
如上,在上一篇博客,我们已经实现了spring ai对接本地大模型实现了聊天机器人,但是目前有个新需求:
- 上传某场所的图片,通过AI进行分析,描述图片里的内容以及存在的安全隐患
- 进一步通过AI分析场所的安全隐患如何治理,需要依据法律法规(联网)分析
最终效果如下所示:
由于目前了解到的本地大模型都无法实现上述的需求,于是这次借助了火山引擎平台来实现
https://console.volcengine.com/ark/
火山引擎目前新用户会赠送每个模型50万token的体验量,对于学习、测试用还是足够的
如下所示,本次对接的模型有 doubao-vision-pro(图片识别)
和 deepseek-v3(联网分析)
整体的逻辑:
- 先传入图片到doubao模型,分析图片里的场所和存在的隐患
- 然后将1分析的文字结果传到deepseek-v3模型联网结合法律法规分析隐患的整改措施
二、创建应用
https://console.volcengine.com/ark/
如下所示,创建2个零代码应用
-
图片识别
-
联网分析
三、后端
1.SDK集成
如下图所示,火山引擎里部分模型像deepseek-v3是可以直接集成SDK来对接的
代码示例
@RestController
@RequestMapping("/huoShan")
public class HuoShanController {private final ArkService service;private final String imageAnalyzeBotId;public HuoShanController(@Value("${ai.ark.apiKey}") String apiKey, @Value("${ai.ark.base-url}") String baseUrl, @Value("${ai.ark.image-analyze-botId}") String imageAnalyzeBotId) {this.imageAnalyzeBotId = imageAnalyzeBotId;this.service = ArkService.builder().dispatcher(new Dispatcher()).connectionPool(new ConnectionPool(5, 1, TimeUnit.SECONDS)).baseUrl(baseUrl).apiKey(apiKey).build();}@PostMapping("/image/chat")public ResponseEntity<String> imageChat(@RequestBody String userMessage) {List<ChatMessage> messages = new ArrayList<>();messages.add(ChatMessage.builder().role(ChatMessageRole.SYSTEM).content("你是一个对中国法律法规有深入理解的专家").build());messages.add(ChatMessage.builder().role(ChatMessageRole.USER).content(userMessage).build());BotChatCompletionRequest chatCompletionRequest = BotChatCompletionRequest.builder().botId(imageAnalyzeBotId).messages(messages).build();BotChatCompletionResult chatCompletionResult = service.createBotChatCompletion(chatCompletionRequest);StringBuilder result = new StringBuilder();chatCompletionResult.getChoices().forEach(choice -> result.append(choice.getMessage().getContent()));return ResponseEntity.ok(result.toString());}
}
相关的apiKey、base-url、botId都可以从火山的API调用指南获取,获取完我们配置在application.yml里就可以从上面的代码获取
2.调用Rest API
有些模型例如doubao-pro-vision就没提供java SDK,所以需要采用直接调用rest api的方式来对接
代码示例
@PostMapping("/notStream")public Mono<String> imageAnalysis(MultipartFile file) {return imageAiService.imageAnalysisNotStream(file);}
@Service
@Slf4j
public class ImageAiService {@Value("${ai.ark.image-botId}")private String MODEL;private final WebClient webClient;public ImageAiService(@Value("${ai.ark.apiKey}") String apiKey, @Value("${ai.ark.base-url}") String baseUrl) {this.webClient = WebClient.builder().clientConnector(new ReactorClientHttpConnector(HttpClient.create())).baseUrl(baseUrl).defaultHeader("Authorization", "Bearer "+ apiKey).build();public Mono<String> imageAnalysisNotStream(MultipartFile file) {// 压缩图片并转成 Base64 格式String base64 = ImageCompressor.compressImageFileToBase64UnderSize(file, 400, 400, 100);if (base64 == null || base64.isEmpty()) {log.error("图片压缩失败");return Mono.just("图片压缩失败");}// 构造请求体Map<String, Object> body = new HashMap<>();body.put("model", MODEL);// 非流式返回body.put("stream", false);body.put("stream_options", Map.of("include_usage", true));Map<String, Object> imageContent = Map.of("type", "image_url","image_url", Map.of("url", base64));Map<String, Object> message = Map.of("role", "user","content", List.of(imageContent));body.put("messages", List.of(message));// 调用非流式接口,直接返回拼接后的完整结果字符串return webClient.post().uri("/bots/chat/completions").contentType(MediaType.APPLICATION_JSON).bodyValue(body)// 此时接口返回的是 JSON 数据,所以指定 JSON 类型.accept(MediaType.APPLICATION_JSON).retrieve().bodyToMono(String.class).map(responseStr -> {try {// 解析返回结果,取出 assistant 返回的内容JsonNode jsonNode = new ObjectMapper().readTree(responseStr);// 此处根据实际返回结构调整解析逻辑JsonNode contentNode = jsonNode.path("choices").get(0).path("message").path("content");return contentNode.asText();} catch (Exception e) {log.error("解析返回结果异常", e);return "解析返回结果异常";}});} }
注意:
上述调用火山引擎api都是非流式的,如果流式输出就把stream
设置成true,再使用Flux类或SseEmitter类去接收返回就行,但是由于我流式输出得到的结果前端进行格式处理时候总是有问题,所以改用了非流式,等完整答案出来后再一次性处理格式化
/*** 压缩到不超过100KB的Base64编码*/
public static String compressImageFileToBase64UnderSize(MultipartFile file, int maxWidth, int maxHeight, int maxSizeKB) {try {// 读取 MultipartFile 图片BufferedImage originalImage = ImageIO.read(file.getInputStream());// 按比例缩放图片Image scaledImage = originalImage.getScaledInstance(maxWidth, maxHeight, Image.SCALE_SMOOTH);BufferedImage resizedImage = new BufferedImage(maxWidth, maxHeight, BufferedImage.TYPE_INT_RGB);Graphics2D g2d = resizedImage.createGraphics();g2d.drawImage(scaledImage, 0, 0, null);g2d.dispose();// 获取JPEG写入器Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName("jpg");if (!writers.hasNext()) throw new IllegalStateException("No writers found for jpg");ImageWriter writer = writers.next();ByteArrayOutputStream baos = new ByteArrayOutputStream();MemoryCacheImageOutputStream output = new MemoryCacheImageOutputStream(baos);writer.setOutput(output);// 设置初始压缩质量float quality = 1.0f;byte[] imageBytes;do {baos.reset();ImageWriteParam param = writer.getDefaultWriteParam();param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);param.setCompressionQuality(quality);writer.write(null, new IIOImage(resizedImage, null, null), param);output.flush();imageBytes = baos.toByteArray();quality -= 0.05f; // 每次降低压缩质量} while (imageBytes.length > maxSizeKB * 1024 && quality > 0.05f);writer.dispose();output.close();//System.out.println("Final image size: " + (imageBytes.length / 1024) + " KB, final quality: " + quality);return "data:image/jpeg;base64," + Base64.getEncoder().encodeToString(imageBytes);} catch (IOException e) {e.printStackTrace();return null;}
}
注意:
Map<String, Object> imageContent = Map.of("type", "image_url","image_url", Map.of("url", base64)
);
这里的图片可以传递http/https网络地址或者图片的base64编码,由于我想是用电脑本地的文件来测试,所以采用图片转base64编码的方式来传递
四、前端
基于上次的html,新增了图片上传,图片回显,调用新接口等处理
直接放完整代码:
<!DOCTYPE html>
<html lang="zh">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>AI 聊天</title><style>html, body {height: 100%;width: 100%;margin: 0;background-color: #f9f9f9;display: flex;align-items: center;justify-content: center;}.container {display: flex;flex-direction: column;height: 90vh;max-width: 800px;width: 100%;margin: auto;}.chat-container {flex: 1;display: flex;flex-direction: column;background: white;border-radius: 10px;padding: 20px;box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);overflow-y: auto; /* 确保内容超出时显示滚动条 */min-height: 0; /* 防止 flex 容器压缩子元素 */}.chat-container::-webkit-scrollbar {width: 8px;}.chat-container::-webkit-scrollbar-track {background: #f1f1f1;border-radius: 4px;}.chat-container::-webkit-scrollbar-thumb {background: #888;border-radius: 4px;}.chat-container::-webkit-scrollbar-thumb:hover {background: #555;}.ai-message h3 {font-size: 1.2em;margin-top: 1em;}.ai-message ul {padding-left: 1.5em;}.ai-message li {margin-bottom: 0.5em;}.ai-message {white-space: pre-wrap;}.loading-spinner {border: 4px solid #f3f3f3;border-top: 4px solid #007bff;border-radius: 50%;width: 30px;height: 30px;animation: spin 1s linear infinite;margin: 10px auto;}@keyframes spin {0% { transform: rotate(0deg); }100% { transform: rotate(360deg); }}.message {padding: 10px 15px;border-radius: 15px;margin: 5px 0;max-width: 80%;word-wrap: break-word;}.user-message {background-color: #007bff;color: white;align-self: flex-end;}.ai-message {background-color: #e5e5e5;color: black;align-self: flex-start;}.think-message {background-color: #add8e6;color: black;border-radius: 10px;padding: 10px;margin: 5px 0;max-width: 80%;align-self: flex-start;font-style: italic;}.think-content {flex: 1; /* 允许内容自由扩展 */overflow-y: auto; /* 内容过多时显示滚动条 */padding: 5px;}.think-title {font-weight: bold;margin-bottom: 5px;display: flex;align-items: center;}.toggle-button {padding: 5px 10px;background-color: #007bff;color: white;border: none;border-radius: 5px;cursor: pointer;margin-right: 10px;}.toggle-button:hover {background-color: #0056b3;}.input-container {display: flex;flex-direction: column;padding: 10px;background: white;box-shadow: 0 -2px 5px rgba(0, 0, 0, 0.1);}.model-container {display: flex;align-items: center;margin-bottom: 5px;}.model-label {margin-right: 10px;font-weight: bold;}.model-select {padding: 5px;border-radius: 5px;border: 1px solid #ccc;}.input-box-container {display: flex;align-items: center;}.input-box {flex: 1;padding: 10px;border: 1px solid #ccc;border-radius: 5px;}.send-button, .clear-button, .stop-button {padding: 10px 20px;margin-left: 10px;color: white;border: none;border-radius: 5px;cursor: pointer;}.send-button { background-color: #007bff; }.send-button:hover { background-color: #0056b3; }.send-button:disabled { background-color: #a0c4ff; cursor: not-allowed; }.clear-button { background-color: #dc3545; }.clear-button:hover { background-color: #a71d2a; }.clear-button:disabled { background-color: #f5a6a6; cursor: not-allowed; }.stop-button { background-color: #ff9800; }.stop-button:hover { background-color: #e68900; }.stop-button:disabled { background-color: #ffb74d; cursor: not-allowed; }</style><script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
</head>
<body>
<div class="container"><div class="chat-container" id="chatContainer"><div class="message ai-message">👋 你好,我是你的 AI 助手!</div></div><div class="input-container"><div class="model-container"><span class="model-label">选择模型:</span><select id="modelSelect" class="model-select" onchange="changeModel()"><option value="deepseek-r1:latest">DeepSeek-R1(推理)</option><option value="qwen:7b">Qwen</option><option value="image-analysis">火山引擎-Doubao(场所图片分析,无记忆)</option></select></div><div class="input-box-container"><input id="userInput" class="input-box" placeholder="请输入消息..."><input id="imageUpload" type="file" accept="image/jpeg, image/png" style="display: none;" onchange="validateFile()" /><button id="sendButton" class="send-button" onclick="handleSend()">发送</button><button id="clearButton" class="clear-button" onclick="clearMemory()">清除上下文</button><button id="stopButton" class="stop-button" onclick="stopAIResponse()">停止回答</button></div></div>
</div>
<script>const chatContainer = document.getElementById('chatContainer');const userInput = document.getElementById('userInput');const modelSelect = document.getElementById('modelSelect');const sendButton = document.getElementById('sendButton');const clearButton = document.getElementById('clearButton');const stopButton = document.getElementById('stopButton');let userId = '1';let currentModel = modelSelect.value;let eventSource = null;function validateFile() {const fileInput = document.getElementById('imageUpload');const file = fileInput.files[0];if (file) {const fileSize = file.size / 1024; // 文件大小,单位为KBconst fileType = file.type.toLowerCase();// 判断文件大小是否小于200KB,格式是否为JPG或PNGif (fileSize > 200) {alert('文件大小必须小于200KB。');fileInput.value = ''; // 清空选择框return;}if (fileType !== 'image/jpeg' && fileType !== 'image/png') {alert('仅允许上传JPG和PNG格式的图片。');fileInput.value = ''; // 清空选择框return;}}}function handleSend() {if (currentModel === 'image-analysis') {const fileInput = document.getElementById('imageUpload');const file = fileInput.files[0];if (!file) {alert("请选择图片文件");return;}uploadAndAnalyzeImage(file);} else {sendMessage();}}function uploadAndAnalyzeImage(file) {const formData = new FormData();formData.append('file', file);// 回显图片const reader = new FileReader();reader.onload = function(e) {const imgElement = document.createElement('img');imgElement.src = e.target.result;imgElement.style.maxWidth = '200px';imgElement.style.borderRadius = '10px';imgElement.style.margin = '10px 0';const userImgMessage = document.createElement('div');userImgMessage.classList.add('message', 'user-message');userImgMessage.appendChild(imgElement);chatContainer.appendChild(userImgMessage);toggleAllButtons(false); // 禁用按钮showLoadingSpinner(); // 显示加载动画chatContainer.scrollTop = chatContainer.scrollHeight;};reader.readAsDataURL(file);fetch('http://192.168.100.72:8081/image/notStream', {method: 'POST',body: formData}).then(response => response.text()).then(text => {const fixedText = text.replace(/\\n/g, '\n');const html = marked.parse(fixedText);const aiMessage = document.createElement('div');aiMessage.classList.add('message', 'ai-message');aiMessage.innerHTML = html;chatContainer.appendChild(aiMessage);// 添加进一步分析提示与按钮const followUp = document.createElement('div');followUp.classList.add('message', 'ai-message');followUp.innerHTML = `<div style="display: flex; align-items: center;"><span style="margin-right: 10px;">是否进一步分析风险隐患及对应整改措施?</span><button class="send-button" onclick="startRiskAnalysis(\`${text.replace(/`/g, '\\`')}\`)">是</button></div>
`;chatContainer.appendChild(followUp);}).catch(err => {console.error("图片分析请求失败:", err);appendMessage("❌ 图片分析失败", "ai-message");}).finally(() => {hideLoadingSpinner(); // 移除加载动画toggleAllButtons(true); // 启用按钮chatContainer.scrollTop = chatContainer.scrollHeight;});}function startRiskAnalysis(content) {toggleAllButtons(false); // 禁用按钮// 显示转圈动画showLoadingSpinner();fetch('http://192.168.100.72:8081/huoShan/image/chat', {method: 'POST',headers: {'Content-Type': 'text/plain'},body: content}).then(response => response.text()) // 获取文本响应.then(result => {// 隐藏转圈动画hideLoadingSpinner()// 格式化结果并解析为 HTMLconst fixedText = result.replace(/\\n/g, '\n'); // 解除转义const html = marked.parse(fixedText);// 创建新的 AI 消息并添加到界面const aiMessage = document.createElement('div');aiMessage.classList.add('message', 'ai-message');aiMessage.innerHTML = html;chatContainer.appendChild(aiMessage);chatContainer.scrollTop = chatContainer.scrollHeight; // 滚动到底部toggleAllButtons(true); // 启用按钮}).catch(err => {console.error("风险分析请求失败:", err);// 隐藏转圈动画并显示失败消息hideLoadingSpinner()appendMessage("❌ 风险分析请求失败", "ai-message");toggleAllButtons(true); // 启用按钮});}function showLoadingSpinner() {const spinner = document.createElement('div');spinner.id = 'loadingSpinner';spinner.className = 'loading-spinner';chatContainer.appendChild(spinner);chatContainer.scrollTop = chatContainer.scrollHeight;}function hideLoadingSpinner() {const spinner = document.getElementById('loadingSpinner');if (spinner) spinner.remove();}function sendMessage() {let message = userInput.value.trim();if (!message) return;appendMessage(message, 'user-message');streamAIResponse(userId, message);userInput.value = '';}function appendMessage(text, type) {const messageElement = document.createElement('div');messageElement.classList.add('message', type);messageElement.textContent = text;chatContainer.appendChild(messageElement);chatContainer.scrollTop = chatContainer.scrollHeight;}function streamAIResponse(userId, message) {// 先终止可能存在的旧 eventSourceif (eventSource) {eventSource.close();}eventSource = new EventSource(`http://192.168.100.72:8081/ai/chatStreamWithMemory?userId=${encodeURIComponent(userId)}&message=${encodeURIComponent(message)}&model=${encodeURIComponent(currentModel)}`);let aiMessage = null;let thinkMode = false;let thinkMessage = null;eventSource.onmessage = event => {let response = event.data;if (response.includes('<think>') && currentModel === 'deepseek-r1:latest') {thinkMode = true;response = response.replace('<think>', '');// 创建思考过程气泡thinkMessage = document.createElement('div');thinkMessage.classList.add('think-message');thinkMessage.innerHTML = `<div class="think-title"><button class="toggle-button" onclick="toggleThinkMessage(this)">折叠</button><span class="think-title-text">思考过程:</span></div><div class="think-content" style="display: block;"></div>`;chatContainer.appendChild(thinkMessage);}if (thinkMode) {const thinkContent = thinkMessage.querySelector('.think-content');if (response.includes('</think>')) {response = response.replace('</think>', '');thinkMode = false;aiMessage = document.createElement('div');aiMessage.classList.add('message', 'ai-message');chatContainer.appendChild(aiMessage);}thinkContent.innerHTML += response;} else {if (!aiMessage) {aiMessage = document.createElement('div');aiMessage.classList.add('message', 'ai-message');chatContainer.appendChild(aiMessage);}aiMessage.textContent += response;}chatContainer.scrollTop = chatContainer.scrollHeight;};eventSource.onerror = () => {eventSource.close();toggleButtons(true);};eventSource.onopen = () => {toggleButtons(false);};eventSource.addEventListener("close", () => {toggleButtons(true);});}function toggleAllButtons(enabled) {sendButton.disabled = !enabled;clearButton.disabled = !enabled;stopButton.disabled = !enabled;}function toggleButtons(enabled) {sendButton.disabled = !enabled;clearButton.disabled = !enabled;}function toggleThinkMessage(button) {const thinkMessage = button.closest('.think-message');const thinkContent = thinkMessage.querySelector('.think-content');if (thinkContent.style.display === 'none') {thinkContent.style.display = 'block';button.textContent = '折叠';} else {thinkContent.style.display = 'none';button.textContent = '展开';}}function stopAIResponse() {if (eventSource) {eventSource.close();eventSource = null;}fetch(`http://192.168.100.72:8081/ai/stopChat?userId=${userId}`, { method: 'GET' }).then(() => appendMessage('AI 回答已停止。', 'ai-message')).catch(error => console.error(error));}function clearMemory() {fetch(`http://192.168.100.72:8081/ai/clearMemory?userId=${userId}`, { method: 'GET' }).then(() => appendMessage('上下文已清除。', 'ai-message')).catch(error => console.error(error));}function changeModel() {currentModel = modelSelect.value;let currentModelName = modelSelect.options[modelSelect.selectedIndex].text;appendMessage(`已切换模型为 ${currentModelName}`, 'ai-message');if (currentModel === 'image-analysis') {userInput.disabled = true;userInput.placeholder = '请选择图片进行分析...';sendButton.textContent = '分析图片';document.getElementById('imageUpload').style.display = 'block';} else {userInput.disabled = false;userInput.placeholder = '请输入消息...';sendButton.textContent = '发送';document.getElementById('imageUpload').style.display = 'none';}}userInput.addEventListener('keypress', event => {if (event.key === 'Enter') {sendMessage();}});
</script>
</body>
</html>
相关文章:
SpringBoot对接火山引擎大模型api实现图片识别与分析
文章目录 一、前言二、创建应用三、后端1.SDK集成2.调用Rest API 四、前端 一、前言 Spring AI实战初体验——实现可切换模型AI聊天助手-CSDN博客 如上,在上一篇博客,我们已经实现了spring ai对接本地大模型实现了聊天机器人,但是目前有个新…...
Java ---成员,局部变量与就近原则
成员变量 声明在类内部,但在方法、构造器或代码块之外的变量。 属于类的实例(对象)或类本身(静态变量)。 实例变量(非静态成员变量): public class Person {private String name…...
基于libevent写一个服务器(附带源码)
使用libevent搭建服务器 服务器源码二级目录 使用开源框架,目的是减少程序员对一些精细的操作的误操作,也是为了让程序员能更好的对接业务而不是底层api的使用。 为何使用libevent,因为libevent开源已经有十几年了,能很好的承受数…...
2.2.3 Spark Standalone集群
搭建Spark Standalone集群需要完成多个步骤。首先,配置主机名、IP地址映射、关闭防火墙和SeLinux,并设置免密登录。接着,配置JDK和Hadoop环境,并在所有节点上分发配置。然后,下载并安装Spark,配置环境变量和…...
每天记录一道Java面试题---day38
说说类加载器双亲委派模型 回答重点 AppClassLoader的父加载器是ExtClassLoader,ExtClassLoader的父加载器是BootStrapClassLoader。JVM在加载一个类时,会调用AppClassLoader的laodClass方法来加载这个类,不过在这个方法中,会先…...
[ctfshow web入门] web33
信息收集 相较于上一题,这题多了双引号的过滤。我猜测这一题的主要目的可能是为了不让使用$_GET[a]之类的语句,但是$_GET[a]也是一样的 没有括号可以使用include,没有引号可以使用$_GET 可以参考[ctfshow web入门] web32,其中的所…...
【时时三省】(C语言基础)用switch语句实现多分支选择结构
山不在高,有仙则名。水不在深,有龙则灵。 ----CSDN 时时三省 if语句只有两个分支可供选择,而实际问题中常常需要用到多分支的选择。例如,学生成绩分类(85分以上为A等,70 ~ 84分为B等,60 ~ 69分为C等)&…...
为您的 Web 应用选择最佳文档阅读器
为显示选择合适的文档查看器是开发 Web 应用过程中至关重要的一步。文档查看器应能在提供功能性的同时,确保用户体验的流畅性。 开发人员必须评估多种因素,以确保效率、性能和兼容性。本文将帮助您了解影响用户文档浏览体验成功与否的关键指标。 渲染质…...
js逆向入门图灵爬虫练习平台第六题
地址:aHR0cHM6Ly9zdHUudHVsaW5ncHl0b24uY24vcHJvYmxlbS1kZXRhaWwvNi8 观察可以发现请求头有有字段加密和响应结果加密 查看启动器 开始断点调试 直接复制里面的js内容,测试函数...
招商蛇口 | 回归生活本身,革新CID的143㎡改善标准
时光流转,城市向前。在西安这片千年文脉的沃土之上,招商蛇口已深耕11载,用21座标杆作品,为17000余户家庭筑就理想栖居。从曲江到高新,从城市更新到人居焕新,每一座作品都是对“美好生活承载者”使命的践行。…...
第6课:分布式多智能体系统架构
分布式多智能体系统架构:从算力协同到微服务部署的工程化实践 一、引言:当智能体规模突破百级:分布式架构为何成为必选项? 在多智能体系统(MAS)从“实验室Demo”走向“工业级应用”的过程中,传…...
Vue3 Teleport 深度解析与面试技巧
Vue3 Teleport 深度解析与面试技巧 一、Teleport 核心价值解析 1.1 诞生背景与设计哲学 DOM层级困境:传统组件树与视觉层级的矛盾样式污染问题:z-index层级管理的世纪难题逻辑解耦需求:业务逻辑与DOM结构的正交性要求 1.2 核心能力矩阵 能…...
断言与反射——以golang为例
断言 x.(T) 检查x的动态类型是否是T,其中x必须是接口值。 简单使用 func main() {var x interface{}x 100value1, ok : x.(int)if ok {fmt.Println(value1)}value2, ok : x.(string)if ok {//未打印fmt.Println(value2)} }需要注意如果不接受第二个参数就是OK,这…...
react函数组件中,className字符串、style对象如何在父子组件之间传递
一、需要传递的样式在父组件的scss文件中提前写好 子组件的dom解析后: 二、向子组件直接传递style对象...
WHAT - React Portal 机制:将子组件渲染到 DOM 的指定节点
文章目录 适合场景基本语法示例:Modal 弹窗1. 创建一个简单的 Modal.tsx2. 在 App 中使用 为什么要用 Portal?TypeScript 中 Portal 类型定义? 适合场景 React Portal 是 React 提供的一种机制,让你可以将子组件渲染到 DOM 的指定…...
企业绿电消纳比例不达标?安科瑞微电网智慧能源平台助力企业低碳转型
方案配置支持请联系安科瑞电气周女士 企业绿电消纳政策是国家推动能源转型和实现“双碳”目标的重要抓手,近年来政策体系逐步完善。企业通过建设“源网荷储”一体化项目、虚拟电厂等技术,提升绿电消纳能力。 一、安科瑞提供解决方案 深耕用电业务&…...
uni-app初学
文章目录 1. pages.json 页面路由2. 图标3. 全局 CSS4. 首页4.1 整体框架4.2 完整代码4.3 轮播图 swiper4.3.1 image 4.4 公告4.4.1 uni-icons 4.5 分类 uni-row、uni-col4.6 商品列表 小程序开发网址: 注册小程序账号 微信开发者工具下载 uniapp 官网 HbuilderX 下…...
网络划分vlan隔离
隔离划分 比如我们想要将pc1和pc2隔离,我们只需在lsw1交换机中,如下配置: sys 先进入系统视图 先后输入 代表创建2个隔离区 vlan 10 vlan 20 然后进入0/0/1、0/0/2设置隔离类型,并划分隔离区域 int gi0/0/01 port l…...
HDCP(四)
HDCP驱动开发实战深度解析 以下从协议栈架构、核心模块实现、安全设计到硬件集成,结合HDCP 2.x规范与主流硬件平台(如ARM、FPGA)特性,系统拆解驱动开发关键环节: 1. 协议栈架构与模块划分 驱动分层设计 硬件抽象层&…...
大数据(7.4)Kafka存算分离架构深度实践:解锁对象存储的无限潜能
目录 一、传统架构的存储困境与破局1.1 数据爆炸时代的存储挑战1.2 存算分离的核心价值矩阵 二、对象存储集成架构设计2.1 分层存储核心组件2.2 关键配置参数优化 三、深度集成实践方案3.1 冷热数据分层策略3.1.1 存储策略性能对比 3.2 跨云数据湖方案 四、企业级应用案例4.1 金…...
SLAM文献之SuMa++: Efficient LiDAR-based Semantic SLAM
SuMa是基于Surfel的SLAM算法SuMa的改进版本,通过引入语义分割信息提升动态环境下的鲁棒性和回环检测性能。以下从算法原理和公式推导两方面详细阐述: 一、SuMa算法原理 1. 基础:SuMa算法 SuMa使用Surfel(表面元素)构…...
react中通过 EventEmitter 在组件间传递状态
要在 Reply 组件中通过 statusChangeEvent 发送状态值,并在 Select 组件中接收这个状态值 status,你可以按照以下步骤实现: //Event.jsimport EventEmitter from events;export const statusChangeEvent new EventEmitter();// 工单状态切换…...
机器学习 从入门到精通 day_03
1. KNN算法-分类 1.1 样本距离判断 明可夫斯基距离:欧式距离,明可夫斯基距离的特殊情况;曼哈顿距离,明可夫斯基距离的特殊情况。 两个样本的距离公式可以通过如下公式进行计算,又称为欧式距离。 (…...
WHAT - React 两个重要的 Typescript 类型:ReactNode vs JSX.Element
文章目录 ReactNode 是什么?示例用途 JSX.Element 是什么?示例用途 ReactNode vs JSX.Element 对比使用建议其他相关类型例子总结 这两个类型 ReactNode 和 JSX.Element 在 React TypeScript 中经常出现,但它们含义不同,适用场景…...
了解 DeFi:去中心化金融的入门指南与未来展望
去中心化金融,或 DeFi,代表着全球金融体系运作方式的革命性转变。它是一个总称,指的是一个不断增长的去中心化应用程序(dapp)、协议和平台生态系统,这些生态系统构建在公共区块链网络上,无需传统…...
四旋翼无人机手动模式
无人机的手动模式(Manual Mode)是指飞手完全通过遥控器手动控制无人机的飞行姿态、高度、方向和速度,无需依赖自动稳定系统或辅助功能(如GPS定位、气压计定高、视觉避障等)。这种模式赋予操作者最大的操控自由度&a…...
航电系统之驱动系统篇
航电系统的驱动系统是航空电子系统中负责为各类电子设备、传感器、执行机构及控制模块提供稳定、可靠电能的关键部分。其核心功能在于将飞机电源系统的电能转换为适合航电设备使用的形式,确保航电系统在各种飞行条件下正常运行。以下从组成结构、工作原理、技术特点…...
《嵌入式开发实战:基于Linux串口的LED屏显系统设计与实现》
一、项目概述 本文介绍如何通过Linux系统的串口通信,驱动工业级LED显示屏实现动态数据展示。项目采用C语言开发,包含气象数据显示和实时时钟两大核心功能,涉及以下关键技术点: 串口通信协议配置 自定义数据帧封装 CRC16校验算法…...
记录一下移动端uView动态表单校验
uni-app开发uView必不可少,uView是uni-app生态专用的UI框架。 像element-ui一样uView也有自己的表单组件u-form。 对于下图这种列表数据该如何做表单校验,官方文档好像没有具体的案例,记录一下。 问题: 主要实现步骤:…...
Django项目入门二
Django项目入门二 目录 1.修改部门数据 2.新增员工数据 3.修改员工数据 4.删除员工数据 一、修改部门数据 上一篇文章, 我们只剩下修改功能没有做了, 那在这篇文章, 我们给它补上。 在做之前, 我们需要对views.py文件进行调整, 由于我们考虑到有部门信息和员工信息, 如…...
Java创建Android自用证书
在 Android 开发中,如果需要创建一个自用的证书,可以使用 Java 开发工具包(JDK)自带的 keytool 工具。 KeystoreGenerator.java import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.…...
Redis——实现消息队列
目录 前言 基于List结构模拟消息队列 基于List实现消息队列优缺点 基于PubSub(订阅者)实现消息队列 示例 基于PubSub的消息队列的优缺点 基于Stream的消息队列 STREAM类型消息队列的XREAD命令特点: 基于Stream的消息队列-消费者组 基于…...
学习51单片机Day01---做实验前置一些内容
目录 一、前面要看的: 1.下载软件 2.如何开始做? 3.基本框架: 4.如何编译运行: 5.可以运行的样子: 6.怎么生成hex: 7.滴滴放到单片机上: 二、过程中可能出现的问题(一直会更…...
pipe匿名管道实操(Linux)
管道相关函数 1 pipe 是 Unix/Linux 系统中的一个系统调用,用于创建一个匿名管道 #include <unistd.h> int pipe(int pipefd[2]); 参数说明: pipefd[2]:一个包含两个整数的数组,用于存储管道的文件描述符: pi…...
vscode 异常关闭后无法远程连接服务器
笔记本没关机只是合上,结果第二天上班整台笔记本高度发热发烧,吓坏了。。。 强制关机后再开机,幸好能用。但是vscode连接服务器一直不对。 解决方式: 解决一:打开VS Code菜单"View"->“Command Palatt…...
Rust主流框架性能比拼: Actix vs Axum vs Rocket
本内容是对知名性能评测博主 Anton Putra Actix (Rust) vs Axum (Rust) vs Rocket (Rust): Performance Benchmark in Kubernetes 内容的翻译与整理, 有适当删减, 相关指标和结论以原作为准 在以下中,我们将比较 Rust 生态中最受欢迎的几个框架。我会将三个应用程序…...
二氧化铪(HfO2)市场发展分析:从基础到前沿应用
引言:探索二氧化铪的重要性与市场潜力 在现代材料科学中,二氧化铪(HfO2)作为一种关键的高介电常数(High-k)材料,正逐渐成为半导体行业不可或缺的一部分。二氧化铪是一种白色的固体,…...
写一个简单的demo来理解数据库外键
准备工作 安装MySQL 确保已安装MySQL,并启动服务。可以通过命令行或工具(如MySQL Workbench)操作。 创建数据库 sql 复制 CREATE DATABASE school; USE school;创建父表和子表 步骤 1:创建父表(students)…...
Prompt-to-prompt image editing with cross attention control
Project Page: https://prompt-to-prompt.github.io Paper: https://arxiv.org/abs/2208.01626 Code: https://github.com/google/prompt-to-prompt 文章目录 1. Introduction2. Method2.1 Cross-attention in text-conditioned Diffusion Models2.2 Controlling the Cross-att…...
C++Cherno 学习笔记day18 [71]-[75] C++安全、PCH、dynamic_cast、基准测试、结构化绑定
b站Cherno的课[71]-[75] 一、现代C中的安全以及如何教授二、C的预编译头文件PCH三、C的dynamic_cast四、C的基准测试五、C的结构化绑定 一、现代C中的安全以及如何教授 安全编程,或者说C编程中,降低崩溃,内存泄露、非法访问等问题 C11&#…...
根据 PID 找到对应的 Docker 容器
引言 在日常运维与调试过程中,我们常常需要查找某个进程所属的 Docker 容器。当系统出现问题或资源异常时,根据进程的 PID 找到其所属容器可以帮助我们迅速定位问题。本文将介绍如何利用 Linux 的 cgroup 机制,以及 Docker 提供的工具来完成…...
传统项目纯前端实现导出excel之xlsx.bundle.js
传统项目纯前端实现导出excel之xlsx.js 自从vue问世后,使得前端开发更加简洁从容,极大的丰富组件样式和页面渲染效果,使得前端功能的可扩展性得到极大地加强。虽然vue的使用对于前后端分离的项目对于功能实现与扩展有了质的飞跃,但…...
大型手游 DDoS 攻击怎么防护?游戏盾 SDK 技术解剖实录
一、重灾区警报:大型手游为何成为 DDoS 靶心? 1. 血淋淋的行业数据 攻击规模暴涨:2024 年全球手游遭受 > 300Gbps 攻击次数同比激增 173%(Akamai 报告)经济杀伤链:1 小时 500Gbps 攻击可造成ÿ…...
【Harmony】状态管理(V1)
一、概述 文章目录 一、概述二、组件状态管理1、State1.1、State简介1.2、State简单示例 2、Prop2.1、Prop简介2.2、Prop底层实现原理2.3、Prop简单示例 3、Link3.1、Link简介3.2、Link底层实现原理3.3、Link简单示例 4、Provide Consume4.1、Provide Consume简介4.2、Provide …...
udev规则实例:监听usb插拔事件并做出相应
在 Linux 和 Android 系统中,USB 插拔事件的判断涉及从内核到用户空间的多层协作。以下是源码中关键判断点的梳理: 事件流程 内核层:UEvent 机制 USB 插拔事件首先由内核通过 UEvent 机制 上报。内核中的 USB 驱动(如 drivers/…...
【算法】【蓝桥23国A软件C】四版代码思路分析与逐步优化
题目来源:第十四届蓝桥杯大赛软件赛国赛C/C 大学 A 组 题目描述: 问题描述 给定一个 WH 的长方形,两边长度均为整数。小蓝想把它切割为很多个边长为整数的小正方形。假设切割没有任何损耗,正方形的边长至少为 2,不允…...
程序设计竞赛1
题目1 2025年春节期间,DeepSeek作为“AI界的天降紫微星”成为新晋效率神器,热度席卷全球,其团队主创成员也迅速引起了大家的关注。 DeepSeek之所以能在短时间内取得如此不凡成绩,与其团队成员的背景密不可分。团队汇聚了来自清华…...
android studio 2022打开了v1 签名但是生成的apk没有v1签名问题
我使用了Android Studio Flamingo | 2022.2.1 Patch 2版本的IDE编译了一个apk,但是apksigner查看apk的签名信息时,发现只有v2签名,没有v1签名。 apksigner verify -v app-debug.apk Verifies Verified using v1 scheme (JAR signing): false Verified usin…...
EPGAN:融合高效注意力的生成对抗网络图像修复算法
简介 简介:利用掩码设计来遮掉输入图像的一部分,将这类图像输入给生成器。生成器结合ECA注意力机制架构,利用感知损失、对抗损失和均方误差损失的加权和来作为生成器的损失计算。鉴别器分别对应掩码和整张图做损失计算。 论文题目:融合高效注意力的生成对抗网络图像修复算…...
使用模板报错:_G.unicode.len(orgline.text_stripped:gsub(“ “,““))
使用aegisub制作歌词特效,白嫖大佬的自动化模板时,经常会遇到如下报错: Runtime error in template code: Expected 1 arguments, got 2 Code producing error: ci {0,0}; cn _G.unicode.len(orgline.text_stripped:gsub(" ",&q…...