当前位置: 首页 > article >正文

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(联网分析)

在这里插入图片描述

整体的逻辑:

  1. 先传入图片到doubao模型,分析图片里的场所和存在的隐患
  2. 然后将1分析的文字结果传到deepseek-v3模型联网结合法律法规分析隐患的整改措施

二、创建应用

https://console.volcengine.com/ark/

如下所示,创建2个零代码应用

在这里插入图片描述

在这里插入图片描述

  1. 图片识别

    在这里插入图片描述

  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博客 如上&#xff0c;在上一篇博客&#xff0c;我们已经实现了spring ai对接本地大模型实现了聊天机器人&#xff0c;但是目前有个新…...

单片机方案开发 代写程序/烧录芯片 九齐/应广等 电动玩具 小家电 语音开发

在电子产品设计中&#xff0c;单片机&#xff08;MCU&#xff09;无疑是最重要的组成部分之一。无论是消费电子、智能家居、工业控制&#xff0c;还是可穿戴设备&#xff0c;小家电等&#xff0c;单片机的应用无处不在。 单片机&#xff0c;简而言之&#xff0c;就是将计算机…...

Open Interpreter:重新定义人机交互的开源革命

引言 在人工智能技术蓬勃发展的今天&#xff0c;人机交互的方式正经历着前所未有的变革。Open Interpreter&#xff0c;作为一个开源项目&#xff0c;正在重新定义我们与计算机的互动方式。它允许大型语言模型&#xff08;LLMs&#xff09;在本地运行代码&#xff0c;通过自然…...

解决前端使用Axios时的跨域问题

跨域问题是前端开发中常见的问题&#xff0c;当你的前端应用尝试访问不同域名、端口或协议的API时就会出现。以下是几种解决方案&#xff1a; 1. 后端解决方案 CORS (推荐) 后端需要设置正确的响应头&#xff1a; Access-Control-Allow-Origin: * // 或指定具体域名 Acces…...

ARCGIS PRO 在已建工程地图中添加在线地图

一、手工添加 如图所示&#xff1a; 1、在上方的菜单栏中点击“插入”&#xff0c;选择“连接” 2、新建ArcGIS Server 3、在弹出框中输入在线图集的URL&#xff0c;点击“确定” https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer 4、查看在…...

ScholarCopilot:“学术副驾驶“

这里写目录标题 引言&#xff1a;学术写作的痛点与 AI 的曙光ScholarCopilot 的核心武器库&#xff1a;智能生成与精准引用智能文本生成&#xff1a;不止于“下一句”智能引用管理&#xff1a;让引用恰到好处 揭秘背后机制&#xff1a;检索与生成的动态协同快速上手&#xff1a…...

MATLAB仿真多相滤波抽取与插值的频谱变化(可视化混叠和镜像)

MATLAB画图仿真多相滤波抽取与插值的频谱变化 可视化多速率信号处理抽取与插值的频谱变化 实信号/复信号 可视化混叠和镜像 目录 前言 一、抽取的基本原理 二、MATLAB仿真抽取运算 三、内插的基本原理 四、MATLAB仿真内插运算 总结 前言 在多速率系统中增加信号采样率的运…...

mongodb 远程访问

mongodb 远程访问 MongoDB 数据库的远程访问通常需要一些配置步骤&#xff0c;以确保安全性并正确设置网络访问权限。以下是一些基本步骤来允许远程访问 MongoDB 数据库&#xff1a; 修改 MongoDB 配置文件 首先&#xff0c;你需要编辑 MongoDB 的配置文件&#xff08;通常是 …...

DAY 44 leetcode 28--字符串.实现strStr()

题号28 给你两个字符串 haystack 和 needle &#xff0c;请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标&#xff08;下标从 0 开始&#xff09;。如果 needle 不是 haystack 的一部分&#xff0c;则返回 -1 。 我的解法 双指针&#xff0c;slow定位&…...

MySQL-存储引擎索引

存储引擎 MySQL体系结构 1). 连接层 最上层是一些客户端和链接服务&#xff0c;包含本地sock 通信和大多数基于客户端/服务端工具实现的类似于 TCP/IP的通信。主要完成一些类似于连接处理、授权认证、及相关的安全方案。在该层上引入了线程 池的概念&#xff0c;为通过认证安…...

图像处理有哪些核心技术?技术发展现状如何?

在数字化信息爆炸的时代&#xff0c;文档图像预处理技术正悄然改变着我们处理文字信息的方式。无论是手持拍摄的收据、扫描仪中的身份证&#xff0c;还是工业机器人采集的复杂文档&#xff0c;预处理技术都在背后默默提升着OCR&#xff08;光学字符识别&#xff09;系统的性能。…...

【小沐学GIS】基于C++绘制三维数字地球Earth(QT5、OpenGL、GIS、卫星)第五期

&#x1f37a;三维数字地球系列相关文章如下&#x1f37a;&#xff1a;1【小沐学GIS】基于C绘制三维数字地球Earth&#xff08;OpenGL、glfw、glut&#xff09;第一期2【小沐学GIS】基于C绘制三维数字地球Earth&#xff08;OpenGL、glfw、glut&#xff09;第二期3【小沐学GIS】…...

KEGG注释脚本kofam2kegg.py--脚本010

采用kofam结合kegg官网htxt进行注释 用法&#xff1a; python kofam2kegg.py kofam.out ath00001.keg my_kegg_output code: import sys from collections import defaultdictdef parse_kofam_file(kofam_file):ko_to_genes defaultdict(list)with open(kofam_file) as f:…...

spring cloud OpenFeign 详解:安装配置、客户端负载均衡、声明式调用原理及代码示例

OpenFeign 详解&#xff1a;安装配置、客户端负载均衡、声明式调用原理及代码示例 1. OpenFeign 安装与配置 (1) 依赖管理 <!-- pom.xml 添加以下依赖 --> <dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud…...

【Java八股】

JVM JVM中有哪些引用 在Java中&#xff0c;引用&#xff08;Reference&#xff09;是指向对象的一个变量。Java中的引用不仅仅有常规的直接引用&#xff0c;还有不同类型的引用&#xff0c;用于控制垃圾回收&#xff08;GC&#xff09;的行为和优化性能。JVM中有四种引用类型…...

用 Deepseek 写的uniapp血型遗传查询工具

引言 在现代社会中&#xff0c;了解血型遗传规律对于优生优育、医疗健康等方面都有重要意义。本文将介绍如何使用Uniapp开发一个跨平台的血型遗传查询工具&#xff0c;帮助用户预测孩子可能的血型。 一、血型遗传基础知识 人类的ABO血型系统由三个等位基因决定&#xff1a;I…...

程序化广告行业(84/89):4A广告代理公司与行业资质解读

程序化广告行业&#xff08;84/89&#xff09;&#xff1a;4A广告代理公司与行业资质解读 大家好&#xff01;在探索程序化广告行业的道路上&#xff0c;每一次知识的分享都是我们共同进步的阶梯。一直以来&#xff0c;我都希望能和大家携手前行&#xff0c;深入了解这个充满机…...

go语言gRPC使用流程

1. 安装工具和依赖 安装 Protocol Buffers 编译器 (protoc) 下载地址&#xff1a;https://github.com/protocolbuffers/protobuf/releases 使用说明&#xff1a;https://protobuf.dev/ 【centos环境】yum方式安装&#xff1a;protoc[rootlocalhost demo-first]# yum install …...

【眼底辅助诊断开放平台】项目笔记

这是一个标题 任务一前端页面开发&#xff1a;后端接口配置&#xff1a; 任务二自行部署接入服务 日志修改样式和解析MD文档接入服务 Note前端登陆不进去/更改后端api接口304 Not Modifiedlogin.cache.jsonERR_CONNECTION_TIMED_OUT跨域一般提交格式proxy.ts src/coponents 目录…...

Java笔记5——面向对象(下)

目录 一、抽象类和接口 1-1、抽象类&#xff08;包含抽象方法的类&#xff09; 1-2、接口 ​编辑​编辑 二、多态 ​编辑 1. 自动类型转换&#xff08;向上转型&#xff09; 示例&#xff1a; 注意&#xff1a; 2. 强制类型转换&#xff08;向下转型&#xff09; 示…...

NI的LABVIEW工具安装及卸载步骤说明

一.介绍 最近接到个转交的项目&#xff0c;项目主要作为上位机工具开发&#xff0c;在对接下位机时&#xff0c;有用到NI的labview工具。labview软件是由美国国家仪器&#xff08;NI&#xff09;公司研制开发的一种程序开发环境&#xff0c;主要用于汽车测试、数据采集、芯片测…...

[reinforcement learning] 是什么 | 应用场景 | Andrew Barto and Richard Sutton

目录 什么是强化学习&#xff1f; 强化学习的应用场景 广告和推荐 对话系统 强化学习的主流算法 纽约时报&#xff1a;Turing Award Goes to 2 Pioneers of Artificial Intelligence wiki 资料混合&#xff1a;youtube, wiki, github 今天下午上课刷到了不少&#xff0…...

css一些注意事项

css一些注意事项 .bg_ {background-image: url(/static/photo/activity_bg.png);background-size: 100% auto;background-repeat: no-repeat;background: linear-gradient(to bottom, #CADCEA, #E8F3F6);min-height: 100vh; } 背景图片路径正确但是并没有显示 // 方案1&…...

[从零开始学数据库] 基本SQL

注意我们的主机就是我们的Mysql数据库服务器 这里我们可以用多个库 SQL分类(核心是字段的CRUD)![](https://i-blog.csdnimg.cn/img_convert/0432d8db050082a49258ba8a606056c7.png) ![](https://i-blog.csdnimg.cn/img_convert/bdf5421c2b83e22beca12da8ca89b654.png) 重点是我…...

react/vue中前端多图片展示页面优化图片加载速度的五种方案

需求背景 在多项目中 例如官网项目中 会出现很多大图片显示的情况 这个时候就会出现图片过大 公司带宽不够之类导致页面加载速度过慢及页面出现后图片仍然占位但并未加载出来 或者因为网络问题导致图片区域黑块等等场景 这个时候我们就要对图片和当前场景进行优化 方案定…...

qt中的正则表达式

问题&#xff1a; 1.在文本中把dog替换成cat&#xff0c;但可能会把dog1替换成cat1&#xff0c;如果原本不想替换dog1&#xff0c;就会出现问题 2文本中想获取某种以.txt为结尾的多有文本&#xff0c;普通的不能使用 3如果需要找到在不同的系统中寻找换行符&#xff0c;可以…...

AT_abc400_e [ABC400E] Ringo‘s Favorite Numbers 3 题解

题目传送门 题目大意 题目描述 对于正整数 N N N&#xff0c;当且仅当满足以下两个条件时&#xff0c; N N N 被称为 400 number&#xff1a; N N N 恰好有 2 2 2 种不同的素因数。对于 N N N 的每个素因数 p p p&#xff0c; N N N 被 p p p 整除的次数为偶数次。更严…...

git 提交标签

Git 提交标签 提交消息格式&#xff1a; <type>: <description> &#xff08;示例&#xff1a;git commit -m "feat: add user login API"&#xff09; 标签适用场景feat新增功能&#xff08;Feature&#xff09;。fix修复 Bug&#xff08;Bug fix&…...

关于 Spring Batch 的详细解析及其同类框架的对比分析,以及如何自己设计一个java批处理框架(类似spring batch)的步骤

以下是关于 Spring Batch 的详细解析及其同类框架的对比分析&#xff1a; 一、Spring Batch 核心详解 1. 核心概念 作业&#xff08;Job&#xff09;&#xff1a;批处理任务的顶层容器&#xff0c;由多个步骤&#xff08;Step&#xff09;组成。 步骤&#xff08;Step&#…...

【Java面试系列】Spring Cloud微服务架构中的分布式事务实现与性能优化详解 - 3-5年Java开发必备知识

【Java面试系列】Spring Cloud微服务架构中的分布式事务实现与性能优化详解 - 3-5年Java开发必备知识 引言 在微服务架构中&#xff0c;分布式事务是一个不可避免的挑战。随着业务复杂度的提升&#xff0c;如何保证跨服务的数据一致性成为面试中的高频问题。本文将从基础到进…...