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

SpringBoot + 通义千问 + 自定义React组件,支持EventStream数据解析!

一、前言

大家好!我是sum墨,一个一线的底层码农,平时喜欢研究和思考一些技术相关的问题并整理成文,限于本人水平,如果文章和代码有表述不当之处,还请不吝赐教。

最近ChatGPT非常受欢迎,尤其是在编写代码方面,我每天都在使用。随着使用时间的增长,我开始对其原理产生了一些兴趣。虽然我无法完全理解这些AI大型模型的算法和模型,但我认为可以研究一下其中的交互逻辑。特别是,我想了解它是如何实现在发送一个问题后不需要等待答案完全生成,而是通过不断追加的方式实现实时回复的。

F12打开控制台后,我发现在点击发送后,它会发送一个普通的请求。但是回复的方式却不同,它的类型是eventsource。一次请求会不断地获取数据,然后前端的聊天组件会动态地显示回复内容,回复的内容是用Markdown格式来展示的。

在了解了前面的这些东西后我就萌生了自己写一个小demo的想法。起初,我打算使用openai的接口,并写一个小型的UI组件。然而,由于openai账号申请复杂且存在网络问题,很多人估计搞不定,所以我最终选择了通义千问。通义千问有两个优点:一是它是国内的且目前调用是免费的,二是它提供了Java-SDK和API文档,开发起来容易。

作为后端开发人员,按照API文档调用模型并不难,但真正难到我的是前端UI组件的编写。我原以为市面上会有很多支持EventStream的现成组件,但事实上并没有。不知道是因为这个功能太容易还是太难,总之,对接通义千问只花了不到一小时,而编写一个UI对话组件却花了整整两天的时间!接下来,我将分享一些我之前的经验,希望可以帮助大家少走坑。

首先展示一下我的成品效果
在这里插入图片描述

二、通义千问开发Key申请

1. 登录阿里云,搜索通义千问

2. 点击"开通DashScope"

3. 创建一个API-KEY

4. 对接流程

(1)API文档地址

https://help.aliyun.com/zh/dashscope/developer-reference/api-details

(2)Java-SDK依赖

<dependency><groupId>com.alibaba</groupId><artifactId>dashscope-sdk-java</artifactId><version>2.8.2</version>
</dependency>

三、支持EventStream格式的接口

1. 什么是EventStream

EventStream是一种流式数据格式,用于实时传输事件数据。它是基于HTTP协议的,但与传统的请求-响应模型不同,它是一个持续的、单向的数据流。它可用于推送实时数据、日志、通知等,所以EventStream很适合这种对话式的场景。在Spring Boot中,主要有以下框架和模块支持EventStream格式:

  • Spring WebFlux:Spring WebFlux是Spring框架的一部分,用于构建反应式Web应用程序。
  • Reactor:Reactor是一个基于响应式流标准的库,是Spring WebFlux的核心组件。
  • Spring Cloud Stream:Spring Cloud Stream是一个用于构建消息驱动的微服务应用的框架。

这次我使用的是reactor-core框架。

2. 写一个例子

maven依赖

<!-- Reactor Core -->
<dependency><groupId>io.projectreactor</groupId><artifactId>reactor-core</artifactId><version>3.4.6</version>
</dependency>

代码如下

import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;import java.time.Duration;
import java.time.LocalTime;@RestController
@RequestMapping("/event-stream")
public class EventStreamController {@GetMapping(produces = MediaType.TEXT_EVENT_STREAM_VALUE)public Flux<String> getEventStream() {return Flux.interval(Duration.ofSeconds(1)).map(sequence -> "Event " + sequence + " at " + LocalTime.now());}
}

调用一下接口后就可以看到浏览器上在不断地打印时间戳了

四、项目实现

这个就不BB了,直接贴代码!

1. 项目结构

2. pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.17</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.chatrobot</groupId><artifactId>demo</artifactId><version>0.0.1-SNAPSHOT</version><name>demo</name><description>Demo project for Spring Boot</description><properties><java.version>1.8</java.version></properties><dependencies><!-- 通义千问SDK --><dependency><groupId>com.alibaba</groupId><artifactId>dashscope-sdk-java</artifactId><version>2.8.2</version></dependency><!-- Reactor Core --><dependency><groupId>io.projectreactor</groupId><artifactId>reactor-core</artifactId><version>3.4.6</version></dependency><!-- Web组件 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><exclusions><exclusion><artifactId>logback-classic</artifactId><groupId>ch.qos.logback</groupId></exclusion></exclusions></dependency></dependencies></project>

3. 代码

(1)后端代码

DemoApplication.java
package com.chatrobot;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class DemoApplication {public static void main(String[] args) {SpringApplication.run(DemoApplication.class, args);}}
EventController.java
package com.chatrobot.controller;import java.time.Duration;
import java.time.LocalTime;
import java.util.Arrays;import com.alibaba.dashscope.aigc.generation.Generation;
import com.alibaba.dashscope.aigc.generation.GenerationResult;
import com.alibaba.dashscope.aigc.generation.models.QwenParam;
import com.alibaba.dashscope.common.Message;
import com.alibaba.dashscope.common.Role;
import com.alibaba.dashscope.exception.ApiException;
import com.alibaba.dashscope.exception.InputRequiredException;
import com.alibaba.dashscope.exception.NoApiKeyException;import io.reactivex.Flowable;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.http.codec.ServerSentEvent;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;@RestController
@RequestMapping("/events")
@CrossOrigin
public class EventController {@Value("${api.key}")private String apiKey;@GetMapping(value = "/streamAsk", produces = MediaType.TEXT_EVENT_STREAM_VALUE)public Flux<ServerSentEvent<String>> streamAsk(String q) throws Exception {Generation gen = new Generation();// 创建用户消息对象Message userMsg = Message.builder().role(Role.USER.getValue()).content(q).build();// 创建QwenParam对象,设置参数QwenParam param = QwenParam.builder().model(Generation.Models.QWEN_PLUS).messages(Arrays.asList(userMsg)).resultFormat(QwenParam.ResultFormat.MESSAGE).topP(0.8).enableSearch(true).apiKey(apiKey)// get streaming output incrementally.incrementalOutput(true).build();// 调用生成接口,获取Flowable对象Flowable<GenerationResult> result = gen.streamCall(param);// 将Flowable转换成Flux<ServerSentEvent<String>>并进行处理return Flux.from(result)// add delay between each event.delayElements(Duration.ofMillis(1000)).map(message -> {String output = message.getOutput().getChoices().get(0).getMessage().getContent();System.out.println(output); // print the outputreturn ServerSentEvent.<String>builder().data(output).build();}).concatWith(Flux.just(ServerSentEvent.<String>builder().comment("").build())).doOnError(e -> {if (e instanceof NoApiKeyException) {// 处理 NoApiKeyException} else if (e instanceof InputRequiredException) {// 处理 InputRequiredException} else if (e instanceof ApiException) {// 处理其他 ApiException} else {// 处理其他异常}});}@GetMapping(value = "test", produces = MediaType.TEXT_EVENT_STREAM_VALUE)public Flux<String> testEventStream() {return Flux.interval(Duration.ofSeconds(1)).map(sequence -> "Event " + sequence + " at " + LocalTime.now());}
}

(2)前端代码

chat.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>ChatBot</title><style>body {background: #f9f9f9;/* 替换为您想要的背景颜色或图片 */}.chat-bot {display: flex;flex-direction: column;width: 100%;max-width: 800px;margin: 50px auto;box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);border-radius: 8px;overflow: hidden;font-family: "Roboto", sans-serif;background: #f5f5f5;}.chat-bot-header {background: linear-gradient(to right, #1791ee, #9fdbf1);color: white;text-align: center;padding: 15px;font-size: 24px;font-weight: 500;}.chat-bot-messages {flex: 1;padding: 20px;min-height: 400px;overflow-y: auto;}.userName {margin: 0 10px;}.message-wrapper {display: flex;align-items: flex-start;margin-bottom: 10px;border-radius: 20px;}.message-wrapper.user {justify-content: flex-end;border-radius: 20px;}.message-avatar {width: 30px;height: 30px;border-radius: 50%;background-color: #ccc;margin-right: 10px;margin-bottom: 10px;/* 添加这一行 */order: -1;/* 添加这一行 */text-align: right;}.message-avatar.user {background-color: transparent;display: flex;justify-content: flex-end;width: 100%;margin-right: 0;align-items: center;}.message-avatar.bot {background-color: transparent;display: flex;justify-content: flex-start;width: 100%;margin-right: 0;align-items: center;}.message-avatar-inner.user {background-image: url("./luge.jpeg");background-size: cover;background-position: center;width: 30px;height: 30px;border-radius: 50%;}.message-avatar-inner.bot {background-image: url("./logo.svg");background-size: cover;background-position: center;width: 30px;height: 30px;border-radius: 50%;}.message {padding: 10px 20px;border-radius: 15px;font-size: 16px;background-color: #d9edf7;order: 1;/* 添加这一行 */}.bot {background-color: #e9eff5;/* 添加这一行 */}.user {background-color: #d9edf7;color: #111111;order: 1;/* 添加这一行 */}.chat-bot-input {display: flex;align-items: center;border-top: 1px solid #ccc;padding: 10px;background-color: #fff;}.chat-bot-input input {flex: 1;padding: 10px 15px;border: none;font-size: 16px;outline: none;}.chat-bot-input button {padding: 10px 20px;background-color: #007bff;border: none;border-radius: 50px;color: white;font-weight: 500;cursor: pointer;transition: background-color 0.3s;}.chat-bot-input button:hover {background-color: #0056b3;}@media (max-width: 768px) {.chat-bot {margin: 20px;}.chat-bot-header {font-size: 20px;}.message {font-size: 14px;}}@keyframes spin {0% {transform: rotate(0deg);}100% {transform: rotate(360deg);}}.loading-spinner {width: 15px;height: 15px;border-radius: 50%;border: 2px solid #d9edf7;border-top-color: transparent;animation: spin 1s infinite linear;}</style>
</head>
<body>
<div class="chat-bot"><div class="chat-bot-header"><img src="./logo.svg" alt="Logo" class="logo" />通义千问</div><div class="chat-bot-messages"></div><div class="chat-bot-input"><input type="text" placeholder="输入你想问的问题" /><button id="sendButton">Send</button></div>
</div>
<scriptsrc="https://cdnjs.cloudflare.com/ajax/libs/markdown-it/13.0.2/markdown-it.min.js"integrity="sha512-ohlWmsCxOu0bph1om5eDL0jm/83eH09fvqLDhiEdiqfDeJbEvz4FSbeY0gLJSVJwQAp0laRhTXbUQG+ZUuifUQ=="crossorigin="anonymous"referrerpolicy="no-referrer"
></script>
<script>const userName = "summo";document.addEventListener("DOMContentLoaded", function () {const input = document.querySelector(".chat-bot-input input");const messagesContainer = document.querySelector(".chat-bot-messages");const sendButton = document.getElementById("sendButton");function appendToMessage(messageTxt, sender, md, message) {let messageElement = messagesContainer.querySelector(`.message-wrapper.${sender}:last-child .message`);if (!messageElement) {if (sender === "bot") {messageElement = document.createElement("div");messageElement.classList.add("message-avatar", sender);messageElement.innerHTML = `<div class="message-avatar-inner ${sender}"></div><div class="userName">通义千问</div>`;messagesContainer.appendChild(messageElement);} else {messageElement = document.createElement("div");messageElement.classList.add("message-avatar", sender);messageElement.innerHTML = `<div class="message-avatar-inner ${sender}"></div><div class="userName"">${userName}</div>`;messagesContainer.appendChild(messageElement);}messageElement = document.createElement("div");messageElement.classList.add("message-wrapper", sender);messageElement.innerHTML = `<div class="message ${sender}"></div>`;messagesContainer.appendChild(messageElement);messageElement = messageElement.querySelector(".message");}// messageElement.textContent += messageTxt; // 追加文本// messagesContainer.scrollTop = messagesContainer.scrollHeight; // 滚动到底部let result = (message += messageTxt);const html = md.renderInline(messageTxt);messageElement.innerHTML += html;messagesContainer.scrollTop = messagesContainer.scrollHeight;}function handleSend() {const inputValue = input.value.trim();if (inputValue) {input.disabled = true;sendButton.disabled = true;sendButton.innerHTML = '<div class="loading-spinner"></div>';const md = new markdownit();// 修改按钮文本内容为"Loading..."let message = "";appendToMessage(inputValue, "user", md, message);input.value = "";const eventSource = new EventSource(`http://localhost:8080/events/streamAsk?q=${encodeURIComponent(inputValue)}`);eventSource.onmessage = function (event) {console.log(event.data);appendToMessage(event.data, "bot", md, message);};eventSource.onerror = function () {eventSource.close();input.disabled = false;sendButton.disabled = false;sendButton.innerHTML = "Send";};}}document.querySelector(".chat-bot-input button").addEventListener("click", handleSend);input.addEventListener("input", function () {sendButton.disabled = input.value.trim() === "";});input.addEventListener("keypress", function (event) {if (event.key === "Enter" && !sendButton.disabled) {handleSend();}});});
</script>
</body>
</html>

另外还有两个头像,大家可以替换成自己喜欢的,好了文章到这里也就结束了,再秀一下我的成品👉

相关文章:

SpringBoot + 通义千问 + 自定义React组件,支持EventStream数据解析!

一、前言 大家好&#xff01;我是sum墨&#xff0c;一个一线的底层码农&#xff0c;平时喜欢研究和思考一些技术相关的问题并整理成文&#xff0c;限于本人水平&#xff0c;如果文章和代码有表述不当之处&#xff0c;还请不吝赐教。 最近ChatGPT非常受欢迎&#xff0c;尤其是…...

Redis中文结果查看方式

背景 当使用redis时我们存储到缓存中可能会包含一些中文,例如下面命令 set test 中国 当执行查看时,发现客户端显示的并不是中文而是乱码,例如下面结果 get test \xe4\xb8\xad\xe5\x9b\xbd 现对【\xe4\xb8\xad\xe5\x9b\xbd】的查看有如下几个方式 方式一:通过客户端直…...

计算机组成原理-磁盘存储器

文章目录 总览外存储器磁盘存储器磁盘的性能指标磁盘地址磁盘的工作过程磁盘阵列 总结 总览 外存储器 磁盘存储器 写是利用电流产生磁场从而写磁盘 读是利用载磁体移动时产生的电场从而得到数据 磁性材质易受外界磁场干扰 下图中 载磁体上N S的前后顺序代表对应存储二进制的比…...

连接docker swarm和凌鲨

docker swarm相比k8s而言&#xff0c;部署和使用都要简单很多&#xff0c;比较适合中小研发团队。 通过连接docker swarm和凌鲨&#xff0c;可以让研发过程中的常用操作更加方便。 更新容器镜像调整部署规模查看日志运行命令 使用步骤 部署swarm proxy 你可以通过linksaas…...

Qt实现画的图片移动

要实现左键点击鼠标时图片跟着鼠标移动&#xff0c;可以通过以下步骤来实现&#xff1a;1. 在QGraphicsView的构造函数中设置鼠标跟踪属性&#xff0c;以便能够捕获鼠标事件。cpp QGraphicsView::QGraphicsView(QWidget *parent) : QGraphicsView(parent) {setMouseTracking(tr…...

比较2个点的3种结构在不规则平面上的占比

2 2 2 1 2 2 2 2 2 1 2 2 2 2 2 1 2 2 3 3 3 x 3 3 2 2 2 1 2 2 2 2 2 1 2 2 在平面上有一个点x&#xff0c;再增加一个点,11的操作把平面分成了3部分2a1&#xff0c;2a2&#xff0c;2a3&#xff0c;3部分的比值是 2a1 2a2 2a3 5 25 …...

最小二乘线性回归

​ 线性回归&#xff08;linear regression&#xff09;&#xff1a;试图学得一个线性模型以尽可能准确地预测实际值的输出。 以一个例子来说明线性回归&#xff0c;假设银行贷款会根据 年龄 和 工资 来评估可放款的额度。即&#xff1a; ​ 数据&#xff1a;工资和年龄&…...

【django+vue】连接数据库、登录功能

笔记为自我总结整理的学习笔记&#xff0c;若有错误欢迎指出哟~ 【djangovue专栏】 1.【djangovue】项目搭建、解决跨域访问 【djangovue】连接数据库、登录功能 django连接数据库1.安装MySQL驱动程序2.创建数据库3.配置settings.py文件4.创建表5.添加数据 登录功能1.django实现…...

NX二次开发UF_CSYS_edit_matrix_of_object 函数介绍

文章作者&#xff1a;里海 来源网站&#xff1a;https://blog.csdn.net/WangPaiFeiXingYuan UF_CSYS_edit_matrix_of_object Defined in: uf_csys.h int UF_CSYS_edit_matrix_of_object(tag_t object_id, tag_t matrix_id ) overview 概述 Updates the specified coordinat…...

封装公共el-form表单

1.公共表单组件 //commonForm.vue <script> import {TEXT,SELECT,PASSWORD,TEXTAREA,RADIO,DATE_PICKER } from /conf/uiTypes import { deepClone } from /utils export default {name: GFormCreator,props: {config: { // title/itemstype: Object,required: true}}…...

OpenHarmony-4.0-Release 源码编译记录

本文基于 Ubuntu 20.04.4 LTS 1、环境准备 这个没啥好说的&#xff0c;都是搞机的&#xff0c;用之前编译 aosp 的 linux 环境就行&#xff0c;有小伙伴担心会把之前的环境搞崩&#xff0c; 也有用 docker 编译的&#xff0c;我这里就直接在 aosp 环境下搞了&#xff0c;还省…...

HTML新手入门笔记整理:块元素和行内元素

块元素 在HTML中&#xff0c;块元素在浏览器显示状态下独占一行&#xff0c;并且排斥其他元素与其位于一行。一般情况下&#xff0c;块元素内部可以容纳其他块元素和行内元素。 常见块元素 块元素 说明 h1~h6 标题元素 p 段落元素 div div元素 hr 水平线 ol 有序列…...

Hook+jsdom 解决cookie逆向

前言 记录下如何破cookie逆向 目标 目标网址:https://q.10jqka.com.cn/ 目标接口:http://q.10jqka.com.cn/index/index/board/all/field/zdf/order/desc/page/2/ajax/1/ 对抗:cookie反爬虫处理,关键字v,如图 解决步骤 1、JS中关键字查找 如上,我们找到了关键字 v,…...

【封装UI组件库系列】封装Icon图标组件

封装UI组件库系列第三篇封装Icon图标组件 &#x1f31f;前言 &#x1f31f;封装Icon 1.创建Icon组件 2.引用svg图标库 第一步 第二步 第三步 3.二次封装 4.封装自定义属性 &#x1f31f;总结 &#x1f31f;前言 在前端开发中&#xff0c;大家可能已经用过各种各样的UI组…...

STM32:基本定时器原理和定时程序

一、初识定时器TIM 定时器就是计数器&#xff0c;定时器的作用就是设置一个时间&#xff0c;然后时间到后就会通过中断等方式通知STM32执行某些程序。定时器除了可以实现普通的定时功能&#xff0c;还可以实现捕获脉冲宽度&#xff0c;计算PWM占空比&#xff0c;输出PWM波形&am…...

EntityFramework 批量删除操作

刚刚开始使用Entityframwork 来操作数据库。遇到了批量删除数据。 EF内部用的方法是&#xff1a;dbcontext.Datas.RemoveRange(list); 这总方法&#xff0c;少量数据是可行的。 但遇到大数据量的时候&#xff0c;这个方法完全不能用。 所以找了另一种方法&#xff1a; stri…...

springboot使用的设计模式

设计模式是在软件设计中常用的一些通用解决方案。在开发商城编码时&#xff0c;以下设计模式可能会有用&#xff1a; 工厂模式&#xff08;Factory Pattern&#xff09;&#xff1a; 用于创建对象的模式。在商城中&#xff0c;可能会涉及到创建不同类型的商品、订单等对象&…...

IP地址定位技术发展与未来趋势

随着互联网的快速发展&#xff0c;人们对网络的需求和依赖程度越来越高。在海量的网络数据传输中&#xff0c;IP地址定位技术作为网络安全与信息追踪的重要手段&#xff0c;其精准度一直备受关注。近年来&#xff0c;随着技术的不断进步&#xff0c;IP地址定位的精准度得到了显…...

AI与交通运输

人工智能&#xff08;AI&#xff09;正在改变几乎所有行业&#xff0c;交通运输也不例外。 虽然与某些行业相比&#xff0c;我们运输货物和乘客的方式在过去 50 年里变化相对较小&#xff0c;但人工智能有望引发一场运输革命—如果你正在寻找人工智能的机会&#xff0c;不要错过…...

window.requestAnimationFrame+localStorage+canvas实现跨窗口小球连线效果

文章目录 前言效果代码后言 前言 hello world欢迎来到前端的新世界 &#x1f61c;当前文章系列专栏&#xff1a;前端系列文章 &#x1f431;‍&#x1f453;博主在前端领域还有很多知识和技术需要掌握&#xff0c;正在不断努力填补技术短板。(如果出现错误&#xff0c;感谢大家…...

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站&#xff0c;会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后&#xff0c;网站没有变化的情况。 不熟悉siteground主机的新手&#xff0c;遇到这个问题&#xff0c;就很抓狂&#xff0c;明明是哪都没操作错误&#x…...

从零实现富文本编辑器#5-编辑器选区模型的状态结构表达

先前我们总结了浏览器选区模型的交互策略&#xff0c;并且实现了基本的选区操作&#xff0c;还调研了自绘选区的实现。那么相对的&#xff0c;我们还需要设计编辑器的选区表达&#xff0c;也可以称为模型选区。编辑器中应用变更时的操作范围&#xff0c;就是以模型选区为基准来…...

Debian系统简介

目录 Debian系统介绍 Debian版本介绍 Debian软件源介绍 软件包管理工具dpkg dpkg核心指令详解 安装软件包 卸载软件包 查询软件包状态 验证软件包完整性 手动处理依赖关系 dpkg vs apt Debian系统介绍 Debian 和 Ubuntu 都是基于 Debian内核 的 Linux 发行版&#xff…...

爬虫基础学习day2

# 爬虫设计领域 工商&#xff1a;企查查、天眼查短视频&#xff1a;抖音、快手、西瓜 ---> 飞瓜电商&#xff1a;京东、淘宝、聚美优品、亚马逊 ---> 分析店铺经营决策标题、排名航空&#xff1a;抓取所有航空公司价格 ---> 去哪儿自媒体&#xff1a;采集自媒体数据进…...

推荐 github 项目:GeminiImageApp(图片生成方向,可以做一定的素材)

推荐 github 项目:GeminiImageApp(图片生成方向&#xff0c;可以做一定的素材) 这个项目能干嘛? 使用 gemini 2.0 的 api 和 google 其他的 api 来做衍生处理 简化和优化了文生图和图生图的行为(我的最主要) 并且有一些目标检测和切割(我用不到) 视频和 imagefx 因为没 a…...

面向无人机海岸带生态系统监测的语义分割基准数据集

描述&#xff1a;海岸带生态系统的监测是维护生态平衡和可持续发展的重要任务。语义分割技术在遥感影像中的应用为海岸带生态系统的精准监测提供了有效手段。然而&#xff0c;目前该领域仍面临一个挑战&#xff0c;即缺乏公开的专门面向海岸带生态系统的语义分割基准数据集。受…...

Redis:现代应用开发的高效内存数据存储利器

一、Redis的起源与发展 Redis最初由意大利程序员Salvatore Sanfilippo在2009年开发&#xff0c;其初衷是为了满足他自己的一个项目需求&#xff0c;即需要一个高性能的键值存储系统来解决传统数据库在高并发场景下的性能瓶颈。随着项目的开源&#xff0c;Redis凭借其简单易用、…...

计算机基础知识解析:从应用到架构的全面拆解

目录 前言 1、 计算机的应用领域&#xff1a;无处不在的数字助手 2、 计算机的进化史&#xff1a;从算盘到量子计算 3、计算机的分类&#xff1a;不止 “台式机和笔记本” 4、计算机的组件&#xff1a;硬件与软件的协同 4.1 硬件&#xff1a;五大核心部件 4.2 软件&#…...

从 GreenPlum 到镜舟数据库:杭银消费金融湖仓一体转型实践

作者&#xff1a;吴岐诗&#xff0c;杭银消费金融大数据应用开发工程师 本文整理自杭银消费金融大数据应用开发工程师在StarRocks Summit Asia 2024的分享 引言&#xff1a;融合数据湖与数仓的创新之路 在数字金融时代&#xff0c;数据已成为金融机构的核心竞争力。杭银消费金…...

【从零开始学习JVM | 第四篇】类加载器和双亲委派机制(高频面试题)

前言&#xff1a; 双亲委派机制对于面试这块来说非常重要&#xff0c;在实际开发中也是经常遇见需要打破双亲委派的需求&#xff0c;今天我们一起来探索一下什么是双亲委派机制&#xff0c;在此之前我们先介绍一下类的加载器。 目录 ​编辑 前言&#xff1a; 类加载器 1. …...