websocket (@ServerEndpoint)基本使用指南

概述

websocket 介绍

  • WebSocket 是一种通信协议,通过单个 TCP 连接提供全双工通信通道。它允许客户端和服务器之间进行双向通信、实时交互,比如在线聊天、实时数据展示等。

    与传统的 HTTP 协议不同,WebSocket 连接是持久的,可以在服务器和客户端之间发送实时数据。

  • WebSocket的 一些关键特点

    • 全双工通信: WebSocket 支持全双工通信,允许服务器和客户端同时发送和接收数据,而无需等待对方的响应。
    • 持久连接: 一旦建立 WebSocket 连接,它可以保持打开状态,允许实时数据的即时传输,而无需为每个通信步骤重新建立连接。
    • 低延迟: WebSocket 连接通常比传统的 HTTP 请求-响应模型更具有低延迟。这使得它非常适合实时应用程序,如在线游戏、聊天应用和股票市场数据更新。
    • 简单握手过程: WebSocket 连接的建立通过简单的握手过程进行,使用 HTTP 协议进行初始握手后,连接就升级为WebSocket 连接。
    • 协议标准: WebSocket 定义了一个标准的通信协议,包括数据帧格式、握手过程和关闭连接的规范。
    • 适用于各种应用: WebSocket 广泛用于各种实时应用程序,包括在线聊天、协作工具、实时通知、在线游戏和金融应用等。
  • 基本工作原理:

    • 握手过程: 客户端发起一个 HTTP 请求,表明希望升级为 WebSocket 连接。服务器接受请求后,进行握手过程,确认协议升级。
    • 数据帧传输: 一旦握手成功,双方可以通过发送数据帧进行通信。数据帧可以包含文本、二进制数据等。
    • 保持连接: WebSocket 连接是持久的,保持打开状态,直到一方发起关闭握手。
    • 关闭连接: 任何一方都可以发起关闭握手,双方会交换关闭帧,最终关闭连接。

Java 实现 websocket 服务端的方式

主要有两种:

  • Tomcat 7 的 @ServerEndpoint 注解方式
  • springboot 集成 websocket 方式

@ServerEndpoint 注解方式

@ServerEndpoint 注解

介绍

  • @ServerEndpoint 注解是 tomcat 7 中新增加的一个注解,用于标识一个类为 WebSocket 服务端点

    WebSocket 服务端点监听客户端的 WebSocket 连接,并将连接管理起来,供客户端和服务端进行实时通信

  • 一个标注有 @ServerEndpoint 的类必须包含一个无参构造函数,并且可以有一个或多个注解为 @OnOpen、@OnClose、@OnMessage、@OnError 的方法。

    • @OnOpen:当 WebSocket 连接建立时,会调用标注有 @onOpen 的方法

    • @OnClose:当 WebSocket 连接关闭时,会调用标注有 @OnClose 的方法

    • @OnMessage:当收到客户端发送的消息时,会调用标注有 @OnMessage 的方法

      在 @OnMessage 方法中,可以通过参数文本、二进制、PongMessage 等方式来接收客户端发送的消息。

      同样可以通过 Session 给客户端发送消息,以实现双向通信。

    • @OnError:当出现错误时,会调用标注有 @OnError 的方法

  • 注意:标注 @ServerEndpoint 注解的类对象是多例的,即每个连接都会创建一个新的对象


@ServerEndpoint 注解的参数配置

  • value 参数:必选参数,用于指定 WebSocket 服务端点的 URI 地址

  • **decoders **参数:数组类型,指定解码器

    包含用于将 WebSocket 消息解码为 Java 对象的解码器(Decoder)的类

    解码器帮助将原始消息转换为 Java 对象

  • encoders 参数:数组类型,指定编解码器

    包含用于将 Java 对象编码为 WebSocket 消息的编码器(Encoder)的类。

    编码器帮助将 Java 对象转换为可以发送到客户端的消息

  • subprotocols 参数:用于指定一个或多个 WebSocket 的子协议

  • configurator 参数:一个类,用于提供配置 WebSocket 端点的自定义配置器

    这允许在 WebSocket 端点创建和配置过程中进行额外的设置


WebSocket 端点类常用对象

在一个使用@ServerEndpoint注解定义的 WebSocket 端点类中,可以自动注入以下类型的对象:

  • javax.websocket.Session:WebSocket 的一个会话对象,代表了 WebSocket 的一个客户端和服务器的连接。

    可以使用 Session 对象来获取 WebSocket 中的各种状态和信息,比如获得客户端的地址、判断客户端是否已经关闭等。

  • javax.websocket.EndpointConfig: 用于在 WebSocket 端点的生命周期内共享配置信息的对象

  • 其他 Spring Bean: 如果 WebSocket 端点类是一个 Spring 管理的 Bean,可以通过使用 @Autowired 注解或构造函数注入其他 Spring Bean,以便在 WebSocket 端点中使用它们。

  • Servlet API 对象: 如果 WebSocket 应用程序与 Servlet 容器集成,可以注入 Servlet API 的相关对象,例如HttpServletRequestHttpServletResponse 等,以便在 WebSocket 处理中访问Web请求和响应

    @Autowired
    private HttpServletRequest request;@OnOpen
    public void onOpen(Session session) {// 使用注入的HttpServletRequest对象
    }
    
  • 其他常用对象

    • RemoteEndpoint :WebSocket 的一个远程端点对象,代表了客户端和服务器的一个连接通道。

      通过 RemoteEndpoint 对象,可以向客户端发送消息、关闭连接等。

      RemoteEndpoint 对象获取方法:通过 Session 对象的 basicRemote 属性获取

      RemoteEndpoint.Basic remote = session.getBasicRemote();
      

使用注意事项

  • 多线程:WebSocket 是一种基于事件驱动的编程模型,因此需要注意多线程问题(线程安全性和同步)
  • 长连接:WebSocket 是一种长连接的通信模型,需要特别注意长时间连接的问题(资源占用和关闭连接)
  • 消息大小:WebSocket 的消息大小是有限制的,需要特别注意消息大小的问题(消息的大小和格式)

基本使用

依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

核心处理器(@ServerEndpoint)

import com.blackcrow.common.utils.UUIDUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;/*** websocket 服务端点。注意:websocket对象是多例的*/
@Slf4j
@Service
//@ServerEndpoint(value = "/chat")
@ServerEndpoint(value = "/chat", configurator = WebSocketServerConfigurator.class)
public class WebSocketServer {// 用于存储每个用户客户端对象public static Map<String, WebsocketServer> onlineUserMap = new ConcurrentHashMap<>();// 用户idprivate String userId;// 会话private Session session;@OnOpenpublic void onOpen(Session session, EndpointConfig config){this.session = session;this.userId = UUIDUtil.get4UUID();log.info("收到来自窗口的连接,userId={}", this.userId);onlineUserMap.put(this.userId, this);Object aaaa = config.getUserProperties().get("aaaa");log.info("aaaa={}", aaaa);}@OnMessagepublic void onMessage(String message, Session session){log.info("收到来自窗口[{}]的的信息: {}", this.userId, message);}@OnClosepublic void onClose(Session session){onlineUserMap.remove(this.userId);log.info("有一连接[{}]关闭!当前连接数为 {}", this.userId, onlineUserMap.size());}@OnErrorpublic void onError(Session session, Throwable throwable){log.error("WebsocketServer 连接发生错误", throwable);}/*** 給session连接推送消息*/private void sendMessage(Object message) {try {this.session.getBasicRemote().sendObject(message);} catch (Exception e) {e.printStackTrace();log.error("向客户端推送数据发生错误", e);}}/*** 向所有连接群发消息*/public static void sendMessageToAll(Object message){for (WebsocketServer item : onlineUserMap.values()) {try {item.sendMessage(message);} catch (Exception e) {log.error("向客户端推送数据发生错误", e);}}}
}

配置类(ServerEndpointExporter )

注:

  • 如果是在 SpringBoot 环境,则必须注册 ServerEndpointExporter 对象,用于扫描 @ServerEndpoint 注解的配置,不然在客户端连接的时候会一直连不上。若不是在 SpringBoot 下开发的可以跳过这一环节。
  • ServerEndpointExporter 类是 spring-boot-starter-websocket 提供的
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;@Configuration
public class WebSocketConfig {/*** ServerEndpointExporter 将会扫描所有使用 @ServerEndpoint 注解标记的类,并将它们注册为 WebSocket 服务端点*/@Beanpublic ServerEndpointExporter serverEndpointExporter() {return new ServerEndpointExporter();}
}

握手连接处理类

  • 这个类是一个自定义的 WebSocket 配置器,它实现了 javax.websocket.server.ServerEndpointConfig.Configurator 接口。它的作用是在 WebSocket 握手期间修改握手请求和响应。

    具体来说,这个类重写了 modifyHandshake 方法,在 WebSocket 握手期间被调用。

    modifyHandshake 方法允许在这个握手阶段进行干预,以便:

    • 修改请求头:可以添加、修改或删除 HTTP 请求头中的信息。这对于传递身份验证信息、自定义参数或修改其他请求属性非常有用。
    • 定制子协议:WebSocket 支持客户端和服务器协商使用哪个子协议。通过 modifyHandshake,可以根据客户端的请求或其他条件来选择要使用的子协议。
    • 处理跨域问题:在某些情况下,可能需要处理跨域 WebSocket 连接。modifyHandshake 可以配置跨域相关的设置,如允许的来源、凭据模式等。
    • 添加自定义逻辑:可以使用 modifyHandshake 来添加任何自定义逻辑,这些逻辑在握手阶段需要执行。
    • 拒绝连接:可以在 modifyHandshake 方法中添加逻辑来检查客户端的请求,并据此决定是否继续握手过程。如果决定拒绝连接,可以抛出一个异常来中断握手。
  • 注:在 WebSocket 的生命周期中,握手是客户端和服务器建立连接的第一步。这一步通常涉及 HTTP 请求和响应,以便双方能够就将要建立的 WebSocket 连接达成协议。

import javax.websocket.HandshakeResponse;
import javax.websocket.server.HandshakeRequest;
import javax.websocket.server.ServerEndpointConfig;
import java.util.List;
import java.util.Map;/*** 握手处理器*/
public class WebSocketServerConfigurator extends ServerEndpointConfig.Configurator {@Overridepublic void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {// 获取客户端发送的HTTP请求头信息Map<String, List<String>> headers = request.getHeaders();// 检查某个特定的请求头是否存在或是否符合要求List<String> customHeaderValues = headers.get("Custom-Header");if (customHeaderValues == null || customHeaderValues.isEmpty() || !customHeaderValues.get(0).equals("ExpectedValue")) {// 如果请求头不符合要求,则拒绝握手throw new RuntimeException("Custom header is missing or invalid");}// 如果请求头符合要求,则继续握手过程super.modifyHandshake(sec, request, response);}
}

springboot 集成 websocket 方式

WebSocket 端点类常用对象

WebSocketMessage

public interface WebSocketMessage<T> {/*** 消息载荷*/T getPayload();/*** 消息字节长度*/int getPayloadLength();/*** 当org.springframework.web.socket.WebSocketHandler#supportsPartialMessages()配置允许分片消息时,* 如果当前消息是客户端本次送达消息的最后一部分时,该方法返回true。如果分片消息不可用或是被禁用,放回false*/boolean isLast();
}

WebSocketSession

常用方法:

  • 主动推送消息:void sendMessage(WebSocketMessage<?> message);
  • 关闭连接:void close();
public interface WebSocketSession extends Closeable {/*** 会话标识*/String getId();/*** WebSocket 连接的URI*/@NullableURI getUri();/*** 返回握手请求中使用的Headers*/HttpHeaders getHandshakeHeaders();/***返回WebSocke会话关联的属性。*在服务端,可以使用org.springframework.web.socket.server.HandshakeInterceptor填充属性*在客户端,可以使用org.springframework.web.socket.client.WebSocketClient的握手方法填充属性*/Map<String, Object> getAttributes();/*** 返回一个包含已验证的用户名称的java.security.Principal实例,如果用户没有验证成功返回null*/@NullablePrincipal getPrincipal();/*** 返回请求接收方的地址*/@NullableInetSocketAddress getLocalAddress();/*** 返回客户端的地址*/@NullableInetSocketAddress getRemoteAddress();/***返回约定的子协议,如果没有协议或是协议失败返回null*/@NullableString getAcceptedProtocol();/*** 配置一次接收文本消息最大值*/void setTextMessageSizeLimit(int messageSizeLimit);/*** 获取一次接收文本消息最大值*/int getTextMessageSizeLimit();/*** 配置一次接收二进制消息最大值*/void setBinaryMessageSizeLimit(int messageSizeLimit);/*** 获取一次接收二进制消息最大值*/int getBinaryMessageSizeLimit();/*** 获取约定的扩展*/List<WebSocketExtension> getExtensions();/*** 发送消息,WebSocket会话底层协议不支持并发发送消息,因此发送必须是同步的。* 保证信息发送同步进行,一种方法是使用org.springframework.web.socket.handler.ConcurrentWebSocketSessionDecorator* 包装WebSocketSession*/void sendMessage(WebSocketMessage<?> message) throws IOException;/*** 底层连接是否打开*/boolean isOpen();/*** 使用状态码1000关闭WebSocket连接*/@Overridevoid close() throws IOException;/*** 使用指定状态码WebSocket连接*/void close(CloseStatus status) throws IOException;
}

基本使用

依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

核心处理器(WebSocketHandler)

  • 可以实现 WebSocketHandler 接口,也可以继承 AbstractWebSocketHandler 类来创建 WebSocketHandler 实例

  • WebSocketHandler 接口提供了五个方法:

    • afterConnectionEstablished:连接成功后调用

    • handleMessage:处理发送来的消息

      handleMessage 方法中有一个 WebSocketMessage 参数,是一个接口,但一般不直接使用这个接口而是使用它的实现类,它有以下几个实现类:

      • BinaryMessage:二进制消息体
      • TextMessage:文本消息体
      • PingMessage: Ping 消息体
      • PongMessage: Pong 消息体

      但是由于 handleMessage 方法的参数是WebSocketMessage 接口,所以实际使用中可能需要判断一下当前来的消息具体是它的哪个子类,比如这样:

      public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {if (message instanceof TextMessage) {this.handleTextMessage(session, (TextMessage)message);} else if (message instanceof BinaryMessage) {this.handleBinaryMessage(session, (BinaryMessage)message);}
      }
      

      可以直接继承 AbstractWebSocketHandler 类,然后重写想要处理的消息类型,它已经封装了这些重复劳动,

    • handleTransportError: WS 连接出错时调用

    • afterConnectionClosed:连接关闭后调用

    • supportsPartialMessages:是否支持分片消息。没什么用,返回 false 就完事了

import org.springframework.web.socket.*;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;/*** websocket核心处理器*/
public class MyWebSocketHandler implements WebSocketHandler {private static final Map<String, WebSocketSession> SESSIONS = new ConcurrentHashMap<>();@Overridepublic void afterConnectionEstablished(WebSocketSession session) throws Exception {String userName = session.getAttributes().get("userName").toString();SESSIONS.put(userName, session);System.out.println(String.format("成功建立连接~ userName: %s", userName));}@Overridepublic void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {String msg = message.getPayload().toString();System.out.println(msg);}@Overridepublic void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {System.out.println("连接出错");if (session.isOpen()) {session.close();}}@Overridepublic void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {System.out.println("连接已关闭,status:" + closeStatus);}@Overridepublic boolean supportsPartialMessages() {return false;}/*** 指定发消息*/public static void sendMessage(String userName, String message) {WebSocketSession webSocketSession = SESSIONS.get(userName);if (webSocketSession == null || !webSocketSession.isOpen()) return;try {webSocketSession.sendMessage(new TextMessage(message));} catch (IOException e) {e.printStackTrace();}}/*** 群发消息*/public static void fanoutMessage(String message) {SESSIONS.keySet().forEach(us -> sendMessage(us, message));}
}

核心配置类(WebSocketConfigurer)

  • 可以配置 websocket 入口,允许访问的域、注册 Handler、定义拦截器等

  • 注意 WebSocketHandlerRegistry .addHandler(WebSocketHandler webSocketHandler, String… paths) 的第二个参数是一个字符串类型的参数列表,说明可以为多个端点指定同样配置的 WebSocketHandler 处理

  • WebSocketHandlerRegistry.addHandler() 方法注册 WebSocketHandler 之后会返回 WebSocketHandlerRegistration 用于配置 WebSocketHandler

    public interface WebSocketHandlerRegistration {// 继续添加消息处理器WebSocketHandlerRegistration addHandler(WebSocketHandler handler, String... paths);// 添加握手处理器,处理握手事件WebSocketHandlerRegistration setHandshakeHandler(HandshakeHandler handshakeHandler);// 添加握手拦截器,可以在处理握手前和握手后处理一些业务逻辑WebSocketHandlerRegistration addInterceptors(HandshakeInterceptor... interceptors);// 配置允许的浏览器跨源请求类型WebSocketHandlerRegistration setAllowedOrigins(String... origins);// 配置允许的浏览器跨源请求类型WebSocketHandlerRegistration setAllowedOriginPatterns(String... originPatterns);// 允许使用SockJS应急选项SockJsServiceRegistration withSockJS();
    }
import org.springframework.context.annotation.Configuration;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import org.springframework.web.socket.server.HandshakeInterceptor;
import java.util.Map;/*** websocket 核心配置类*/
@Configuration
@EnableWebSocket  // 开启注解接收和发送消息
public class WebSocketConfig implements WebSocketConfigurer {/*** 配置 websocket 入口,允许访问的域、注册 Handler、定义拦截器等* 注;配置注册的处理器和拦截器是单例的,无论多少连接进来,都是用相同的对象处理。*/@Overridepublic void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {registry.addHandler(new MyWebSocketHandler(), "/ws")    // 设置连接路径和处理.setAllowedOrigins("*").addInterceptors(new MyWebSocketInterceptor());     // 设置拦截器}/*** 自定义拦截器拦截WebSocket请求*/class MyWebSocketInterceptor implements HandshakeInterceptor {/*** 握手前置拦截。一般用来注册用户信息,绑定 WebSocketSession*/@Overridepublic boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response,WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {System.out.println("握手前置拦截~~");if (!(request instanceof ServletServerHttpRequest)) return true;
//            HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest();
//            String userName = (String) servletRequest.getSession().getAttribute("userName");String userName = "Koishipyb";attributes.put("userName", userName);Object userName1 = attributes.get("userName");return true;}/*** 握手后置拦截*/@Overridepublic void afterHandshake(ServerHttpRequest request, ServerHttpResponse response,WebSocketHandler wsHandler, Exception exception) {System.out.println("握手后置拦截~~");}}
}

nginx 配置 WebSocket

server{# 监听的端口号listen      9095;server_name robotchat.lukeewin.top; # 这里填写的是访问的域名location / {proxy_pass http://127.0.0.1:9090; # 这里填写的是代理的路径和端口proxy_set_header Host $host;proxy_set_header X-Real_IP $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;}# 以下配置针对websocketlocation /ws { # onlineCount为websocket的访问uriproxy_redirect off;proxy_pass http://127.0.0.1:9090;proxy_set_header Host $host;proxy_set_header X-Real_IP $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_http_version 1.1;proxy_read_timeout 36000s;proxy_send_timeout 36000s;proxy_set_header Upgrade $http_upgrade;   # 升级协议头 websocketproxy_set_header Connection "upgrade";}
}

注:

  • 添加如下三行语句,才能在后台中拿到真实的 ip 地址

    proxy_set_header Host $host;
    proxy_set_header X-Real_IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    

    获取真实 ip 地址代码如下:

    public String getRealIp(HttpServletRequest request) {String ip = request.getHeader("X-Forwarded-For");if (StringUtils.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) {int index = ip.indexOf(",");if (index != -1) {return ip.substring(0, index);} else {return ip;}}ip = request.getHeader("X-Real-IP");if (StringUtils.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) {return ip;}return request.getRemoteAddr();
    }
    

参考

  • 传统@ServerEndpoint方式开发WebSocket应用和SpringBoot构建WebSocket应用程序
  • 一文搞懂四种 WebSocket 使用方式
  • Websocket学习(@EnableWebSocket&@ServerEndpoint&websocket认证&nginx配置WebSocket&gateway配置websocket)

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

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

相关文章

godis源码分析——TCP服务

前言 Godis 是一个用 Go 语言实现的 Redis 服务器。 地址&#xff1a;https://github.com/HDT3213/godis?tabreadme-ov-file 简单架构描述 godis是一个中心服务&#xff0c;是TCP服务。流程大概是&#xff1a;godis开启服务&#xff0c;客户端通过TCP建立连接。客户端发起…

【网络安全】修改Host文件实现域名解析

场景 开发一个网站或者服务&#xff0c;需要在本地测试时&#xff0c;可以将线上的域名指向本地开发环境的IP地址。从而模拟真实环境中的域名访问&#xff0c;方便调试和开发。 步骤 1、以管理员身份打开命令提示符 2、编辑hosts文件&#xff1a; 输入以下命令打开hosts文…

Suno: AI音乐创作的新时代

名人说:一点浩然气,千里快哉风。 ——苏轼 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 目录 一、什么是Suno?1、Suno2、应用场景二、如何使用Suno制作音乐?步骤1:注册并登录Suno平台步骤2:创建音乐项目步骤3:生成音乐片段三、Suno的影响很高兴你打开了…

【第六节】C/C++静态查找算法

目录 前言 一、搜索查找 二、查找算法 1. 线性查找&#xff08;Linear Search&#xff09; 2. 二分查找&#xff08;Binary Search&#xff09; 3. 插值查找&#xff08;Interpolation Search&#xff09; 4. 哈希查找&#xff08;Hash Search&#xff09; 5. Fibonacc…

C++感受12-Hello Object 派生版

不变的功能&#xff0c;希望直接复用原有代码&#xff1b;变化的功能&#xff0c;希望在分开的代码里实现。 派生的基本概念和目的如何定义派生类以及创建派生对象派生对象的生死过程 0. 课堂视频 ff14-HelloObject-派生版 1. 派生的基本概念与目的 编程&#xff0c;或者说软…

python 音频和视频合并自动裁剪

为了将音频和视频合并并自动裁剪&#xff0c;我们可以使用Python中的moviepy库。moviepy是一个强大的视频处理库&#xff0c;它允许我们进行剪辑、裁剪、合并等操作。 以下是一个详细的步骤和代码示例&#xff0c;说明如何使用moviepy来合并音频和视频&#xff0c;并自动裁剪它…

vue中的坑·

常规 1.使用watch时&#xff0c;immediate true会在dom挂载前执行 2.使用this.$attrs和props 可以获取上层非原生属性&#xff08;class/id&#xff09; 多层次嵌套引用 设置的时候直接赋值&#xff0c;修改的时候即使用的双向绑定加上$set / nextick / fouceUpdate都不会同步…

FastGPT 错误:Embedding API is not responding

一、FastGPT 报错 在调用 Embedding 模型对文档切片向量化的时候 FastGPT 出现如下错误。 [Error] 2024-07-01 08:41:00 Embedding API is not responding {message: <!doctype html><html lang="zh-CN"><head><meta charset="utf-8&qu…

HiBit Uninstaller:软件批量卸载,一触即得

名人说&#xff1a;莫道谗言如浪深&#xff0c;莫言迁客似沙沉。 ——刘禹锡《浪淘沙》 创作者&#xff1a;Code_流苏(CSDN)&#xff08;一个喜欢古诗词和编程的Coder&#x1f60a;&#xff09; 目录 一、软件介绍1、HiBit Uninstaller2、核心功能 二、下载安装1、下载2、安装 …

【基础篇】第2章 Elasticsearch安装与配置

2.1 环境准备 2.1.1 操作系统选择 Elasticsearch作为一个跨平台的搜索引擎&#xff0c;理论上支持所有能运行Java虚拟机的操作系统&#xff0c;包括但不限于Linux、macOS和Windows。Linux是生产环境中最为推荐的选择&#xff0c;因为它提供了更好的性能和稳定性。macOS适合开…

javascript/js中Array、Set、Map数据结构特性及用法

前言 本文温习前端开发中常见的3种数据结构&#xff1a;数组Array、有序集合Set、有序映射Map&#xff0c;并分别介绍其特性、用法示例 数组-Array 适用于存放和读取有序集合、不要求集合元素唯一性时&#xff1b;可通过索引快速访问元素&#xff0c;实现元素增删改查时 使…

山东省安管人员考核报名流程及免冠证件照处理方法

随着《交通运输工程施工单位主要负责人、项目负责人和专职安全生产管理人员安全生产考核管理办法》&#xff08;以下简称《办法》&#xff09;的发布&#xff0c;山东省的安管人员迎来了新的考核要求。本文将为您详细解读山东省安管人员考核的报名流程&#xff0c;并提供免冠证…

【MotionCap】搭建wsl2的pytorch环境

参考大神:wsl2-ubuntu版本 cuda下周cuda11.3 wget https://developer.download.nvidia.com/compute/cuda/11.3.0/local_installers/cuda_11.3.0_465.19.01_linux.run sudo sh cuda_11.3.0_465.19.01_linux.run cuda是开源的么?下15分钟

1、什么是SSD?

概念 SSD&#xff08;Solid State Drive&#xff09;固态硬盘&#xff0c;是以闪存为介质的存储设备&#xff1b;这里突出的重点是闪存。 闪存&#xff0c;也就是常说的flash&#xff0c;分为NOR 和 NAND&#xff1b; NOR的地址线和数据线分开&#xff0c;所以NOR芯片可以像…

vue html2canvas 将html转图片时遇到的问题解决

问题1&#xff1a; 场景为将富文本组件tinymce里的html内容转为图片&#xff0c;出现的问题是vue获取不到tinymce元素&#xff0c;无法直接使用html2canvas 解决1&#xff1a; 将富文本内容渲染出来&#xff0c;推荐做法是将提交按钮改为预览按钮&#xff0c;然后在另外的弹…

“一带一路”再奏强音!秘鲁总统博鲁阿尔特参访苏州金龙

6月27日下午&#xff0c;首次访华的秘鲁共和国总统博鲁阿尔特一行到苏州金龙参观访问&#xff0c;受到了苏州金龙总经理黄书平的热情接待。 黄书平&#xff08;左二&#xff09;向博鲁阿尔特&#xff08;右一&#xff09;介绍苏州金龙发展情况 从苏州金龙发展历程、产品技术研…

Python中的爬虫实战:百度知道爬虫

python作为一种强大的编程语言&#xff0c;可以帮助我们更便捷地获取互联网上的大量数据。其中&#xff0c;爬虫技术是极具代表性的一部分。爬虫可以在互联网上获取各种数据并进行分析&#xff0c;为我们提供大量的有价值的信息。在python中&#xff0c;爬虫技术也能够得到广泛…

使用Nginx反向代理KKFileView遇到问题

使用KKFileView 4.0 以上版本 在KKFileView官网上&#xff0c;关于使用Nginx代理&#xff0c;建议配置如下 一、修改Nacos 在Nginx的conf文件夹中修改 nginx.conf ,新加 红框内的IP地址为代理服务器地址&#xff08;即安装KKFileView的服务器地址&#xff09; 二、修改KKFil…

小程序打包

一、manifest.json文件添加小程序id 二、接口校验&#xff0c;后端接口添加正式上线&#xff0c;有域名的地址 然后到微信公众平台-开发管理-服务器域名处配置request合法域名&#xff0c;在 此处能够看到后端的baseUrl 三、项目部署 四、发版 在小程序编辑器里 此处可以在…

Android Studio 2023版本切换DNK版本

选择自己需要的版本下载 根目录下的配置路劲注意切换 build.gradle文件下的ndkVersion也要配好对应版本