使用springboot-3.4.1搭建一个netty服务并且WebSocket消息通知(适用于设备直连操作,以及回复操作)

引入最新版本

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

启动类加入

//netty 协议服务端口启动
NettyTcpHandler.start();
package com.cqcloud.platform.handler;

import com.cqcloud.platform.service.IotMqttService;
import com.cqcloud.platform.service.impl.IotMqttServiceImpl;
import org.springframework.stereotype.Component;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

/**
 * @author weimeilayer@gmail.com ✨
 * @date 💓💕 2022年9月23日 🐬🐇 💓💕
 */
@Component
public class NettyTcpHandler {
    /**
     * IoT设备协议端口
     */
    private static int PORT = 1883;

    /**
     * 使用方法在启动类
     * 加上 NettyTcpHandler.start();
     * @throws Exception
     */
    public static void start() throws Exception {
        final NioEventLoopGroup bossGroup = new NioEventLoopGroup();
        final NioEventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            // 创建 IotPushService 实例
            IotMqttService iotPushService = new IotMqttServiceImpl();

            bootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel ch) {
                    ChannelPipeline pipeline = ch.pipeline();
                    // 添加处理器,处理所有连接的业务逻辑
                    pipeline.addLast(new TcpMqttServerHandler(iotPushService));
                }
            });
            // 绑定端口并启动
            ChannelFuture future = bootstrap.bind(PORT).sync();
            // 等待服务器关闭
            future.channel().closeFuture().sync();
        } finally {
            // 优雅地关闭线程池
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }
}

package com.cqcloud.platform.handler;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;

import cn.hutool.json.JSONObject;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;

/**
 * @author weimeilayer@gmail.com ✨
 * @date 💓💕 2022年10月06日 🐬🐇 💓💕
 */
@Slf4j
@Component
public class TcpEventHandler {

    // 使用 BiConsumer 来处理两个参数
    private final static Map<String, BiConsumer<String, String>> eventActions = new HashMap<>();

    public void registerEventAction(String eventCode, BiConsumer<String, String> action) {
        // 动态注册事件处理逻辑
        eventActions.put(eventCode, action);
    }

    public static void handleEvent(String evt, String imei, String reportContent) {
        // 根据事件类型找到对应的处理逻辑,并执行
        eventActions.getOrDefault(evt, TcpEventHandler::handleUnknownEvent).accept(imei, reportContent);
    }

    private static void handleUnknownEvent(String imei, String reportContent) {
        // 处理未知事件的逻辑
        System.out.println("imei: " + imei + ", 报告内容: " + reportContent);
    }

    public static void handleAlarm(String imei, String reportContent) {
        log.info("内容: {}", reportContent);
        // 获取目标用户列表(包括固定的用户 ID)
        List<String> targetUsers = buildTargetUsersList();
        // 构建消息并发送
        sendAlarmMessage(targetUsers, imei, reportContent);
    }


    private static List<String> buildTargetUsersList() {
        List<String> targetUsers = new ArrayList<>();
        targetUsers.add("1");
        targetUsers.add("2");
        //实际根据业务查询数据
        targetUsers.add("26967563820859392");
        return targetUsers;
    }

    private static void sendAlarmMessage(List<String> targetUsers, String imei, String reportContent) {
        // 将目标用户列表转换为逗号分隔的字符串
        String users = String.join(",", targetUsers);
        // 构建 JSON 消息
        JSONObject obj = new JSONObject();
        obj.set("imei", imei);
        obj.set("message", reportContent);
        obj.set("userId", users);
        // 发送消息
        WebSocketHandler.sendMessageToUser(users, obj.toString());
    }
}

package com.cqcloud.platform.handler;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import com.cqcloud.platform.service.IotMqttService;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

/**
 * 物联网云平台设备协议
 * @author weimeilayer@gmail.com ✨
 * @date 💓💕 2022年9月23日 🐬🐇 💓💕
 */
public class TcpMqttServerHandler extends SimpleChannelInboundHandler<ByteBuf>  {

	// 接口注入
	private final IotMqttService iotPushService;

	public TcpMqttServerHandler(IotMqttService iotPushService) {
		this.iotPushService = iotPushService;
	}
	@Override
	protected void channelRead0(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
		byte[] byteArray;
		if (in.readableBytes() <= 0) {
			in.release();
			return;
		}
		byteArray = new byte[in.readableBytes()];
		in.readBytes(byteArray);
		if (byteArray.length <= 0) {
			in.release();
			return;
		}
		// 将消息传递给 iotPushService
		iotPushService.pushMessageArrived(byteArray);
		// 下发指令,假设返回的是多个指令
		List<String> externalValues = extractExternalValue("deviceId");
		// 转换为十六进制字符串
		String hexString = bytesToHex(byteArray);
		System.out.println("来自于物联网云平台设备协议的数据: " + hexString);
		// 使用 Optional 判断外部值
		// 使用 Optional 判断外部值,如果不为空则逐一处理每条指令
		Optional.ofNullable(externalValues).ifPresent(values -> {
			values.forEach(value -> {
				// 提取外部数据值并发送
				System.out.println("来自于物联网云平台设备协议的1883端口的提取外部数据值: " + value);
				sendResponse(ctx, value);
			});
		});
	}
	// 辅助方法:将字节数组转换为十六进制字符串
	private static String bytesToHex(byte[] bytes) {
		StringBuilder hexString = new StringBuilder();
		for (byte b : bytes) {
			String hex = Integer.toHexString(0xFF & b);
			if (hex.length() == 1) {
				hexString.append('0'); // 确保每个字节都为两位
			}
			hexString.append(hex);
		}
		return hexString.toString().toUpperCase(); // 返回大写格式
	}
	// 发送响应的统一辅助方法
	private void sendResponse(ChannelHandlerContext ctx, String hexResponse) {
		byte[] responseBytes = hexStringToByteArray(hexResponse);
		ByteBuf responseBuffer = Unpooled.copiedBuffer(responseBytes);
		ctx.writeAndFlush(responseBuffer);
	}
	// 将响应消息转换为字节数组
	public static byte[] hexStringToByteArray(String s) {
		int len = s.length();
		byte[] data = new byte[len / 2];
		for (int i = 0; i < len; i += 2) {
			data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16));
		}
		return data;
	}

	// 查询数据库当前设备号下需要下发的命令
	private List<String> extractExternalValue(String deviceId) {
		//这里自行查询数据库数据库,这里只模拟一个list集合
		ArrayList<Object> list = new ArrayList<>();
		// 如果记录不为空,获取最新记录的 externalValue
        list.stream().findFirst() // 获取最新的一条记录
			.map(latestRecord -> {
				// 处理最新记录的逻辑
				return ""; // 需要返回的值是 externalValue
			});// 获取最新的一条记录
        return null; // 如果没有找到,返回 null
	}
	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
		// 打印异常堆栈跟踪,便于调试和错误排查
		cause.printStackTrace();
		// 关闭当前的通道,释放相关资源
		ctx.close();
	}
}

package com.cqcloud.platform.handler;

import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;

import org.springframework.stereotype.Component;

import cn.hutool.json.JSONUtil;
import io.micrometer.common.util.StringUtils;
import jakarta.websocket.OnClose;
import jakarta.websocket.OnError;
import jakarta.websocket.OnMessage;
import jakarta.websocket.OnOpen;
import jakarta.websocket.Session;
import jakarta.websocket.server.PathParam;
import jakarta.websocket.server.ServerEndpoint;

/**
 * @author weimeilayer@gmail.com ✨
 * @date 💓💕 2022年4月12日 🐬🐇 💓💕
 */
@Component
@ServerEndpoint("/websocket/{username}")
public class WebSocketHandler {

	public static synchronized int getOnlineCount() {
		return onlineCount;
	}

	public static synchronized void addOnlineCount() {
		WebSocketHandler.onlineCount++;
	}

	public static synchronized void subOnlineCount() {
		WebSocketHandler.onlineCount--;
	}
	// 静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
	private static int onlineCount = 0;

	// 根据名字存储websocket对象CopyOnWriteArraySet线程安全set,ConcurrentHashMap线程安全map
	public static Map<String, CopyOnWriteArraySet<WebSocketHandler>> webSocketMap = new ConcurrentHashMap<>();

	// 与某个客户端的连接会话,需要通过它来给客户端发送数据
	public Session session;

	// 心跳时间,长时间没心跳踢掉连接
	public long heartBeatTime;

	// 初次连接时间,用于控制连接时间过长,踢掉连接
	public long beginTime;

	/**
	 * 用户名称
	 */
	public String username;

	/**
	 * 发送消息
	 * @param username
	 * @param message
	 */
	public static void sendMessageToUser(String username, String message) {
		// 检查用户名是否在 map 中存在
		if (webSocketMap.containsKey(username)) {
			// 获取该用户的 WebSocketHandler 集合
			CopyOnWriteArraySet<WebSocketHandler> userHandlers = webSocketMap.get(username);

			// 遍历该用户的所有连接(每个用户可能有多个 WebSocket 连接)
			for (WebSocketHandler handler : userHandlers) {
				// 通过 WebSocketHandler 实例发送消息
				handler.sendMessageOne(message, username);
			}
		} else {
			System.out.println("并无在线用户: " + username);
		}
	}
	/**
	 * 连接建立成功调用的方法
	 */
	@OnOpen
	public void onOpen(@PathParam("username") String username, Session session) {
		this.username = username;
		this.session = session;
		this.heartBeatTime = System.currentTimeMillis();
		this.beginTime = System.currentTimeMillis();
		// 登陆用户必须按照用户id 格式登陆
		if (!"server".equals(username) && username.split(",").length < 3) {
			return;
		}
		// 将用户添加到websocket,支持单用户多出链接
		if (webSocketMap.containsKey(username)) {
			webSocketMap.get(username).add(this);
		} else {
			CopyOnWriteArraySet websocketSet = new CopyOnWriteArraySet();
			websocketSet.add(this);
			webSocketMap.put(username, websocketSet);
			addOnlineCount(); // 在线数加1
		}
		//注释掉 会退出
		Map<String, Object> messageMap = new ConcurrentHashMap<>();
		messageMap.put("type", "0");
		messageMap.put("message", username + "加入8000端口的的当前在线人数为" + getOnlineCount());
		messageMap.put("to", "all");
		messageMap.put("users", webSocketMap.keySet());
		messageMap.put("username", "server");
		sendMessageAll(JSONUtil.toJsonStr(messageMap));
	}
	/**
	 * 发送消息给所有用户
	 *
	 * @param message
	 * @throws IOException
	 */
	public void sendMessageAll(String message) {
		for (String key : webSocketMap.keySet()) {
			for (WebSocketHandler websocket : webSocketMap.get(key)) {
				websocket.session.getAsyncRemote().sendText(message);
			}
		}
	}
	/**
	 * 连接关闭调用的方法
	 */
	@OnClose
	public void onClose() {
		if (StringUtils.isNotEmpty(this.username)) {
			try {
				if (this.session.isOpen()) {
					this.session.close();// 强制关闭
				}
				webSocketMap.get(username).remove(this);// 删除链接
				if (webSocketMap.get(username).isEmpty()) {
					webSocketMap.remove(username);
					subOnlineCount(); // 在线数减1
					// 刷新用户列表
					Map<String, Object> messageMap = new ConcurrentHashMap<>();
					messageMap.put("type", 0);
					messageMap.put("message", username + "退出!当前在线人数为" + getOnlineCount());
					messageMap.put("users", webSocketMap.keySet());
					sendMessageAll(JSONUtil.toJsonStr(messageMap));
				}
			} catch (Exception e) {
				System.err.println("关闭连接出错 : " + e.getLocalizedMessage());
			}
		}
	}

	/**
	 * 收到客户端消息后调用的方法
	 *
	 * @param message 客户端发送过来的消息
	 */
	@OnMessage
	public void onMessage(String message) {
		// 刷新心跳时间
		this.heartBeatTime = System.currentTimeMillis();
		// 群发消息
		cn.hutool.json.JSONObject messageJson = JSONUtil.parseObj(message);
		Object type = messageJson.get("type");// 消息类型
		Object toUser = messageJson.get("to");// 接收对象
		// 心跳检测
		if ("999".equals(type)) {
			Map<String, Object> messageMap = new ConcurrentHashMap<>();
			messageMap.put("type", "1");
			messageMap.put("message", "pong");
			messageMap.put("username", "服务器");
			messageMap.put("to", this.username);
			sendMessageOne(JSONUtil.toJsonStr(messageMap), this.username);
			return;
		}
		// 发送消息
		if ("All".equalsIgnoreCase(type + "")) {
			sendMessageAll(message);
		}else {
			sendMessageOne(message, toUser + "");
		}
	}

	/**
	 * 发生错误时调用
	 */
	@OnError
	public void onError(Throwable error) {
		error.printStackTrace();
	}

	/**
	 * 发送消息
	 *
	 * @param message
	 * @throws IOException
	 */
	public void sendMessage(String message){
		//this.session.getBasicRemote().sendText(message);//同步
		this.session.getAsyncRemote().sendText(message);// 异步
	}
	
	/**
	 * 发送消息给指定用户
	 *
	 * @param message
	 * @param toUserName
	 */
	public void sendMessageOne(String message, String toUserName) {
		webSocketMap.keySet().forEach(e -> {
			if (e.equals(toUserName)) {
				webSocketMap.get(e).forEach(f -> {
					try {
						f.session.getAsyncRemote().sendText(message);
					} catch (Exception e2) {
						f.session.getAsyncRemote().sendText(message);
					}
				});
			}
		});
	}
}

package com.cqcloud.platform.service;

/**
 * @author weimeilayer@gmail.com
 * @date 💓💕2022年9月8日🐬🐇💓💕
 */
public interface IotMqttService {
	/**
	 * 扩展传输原文
	 * @param message
	 */
	void pushMessageArrived(byte[] message);
}

package com.cqcloud.platform.service.impl;

import org.springframework.stereotype.Service;

import com.cqcloud.platform.service.IotMqttService;

import lombok.AllArgsConstructor;

/**
 * @author weimeilayer@gmail.com
 * @date 💓💕2022年9月8日🐬🐇💓💕
 */
@Service
@AllArgsConstructor
public class IotMqttServiceImpl implements IotMqttService {
	/**
	 * 获取拓展接口原文值
	 * @param message
	 */
	@Override
	public void pushMessageArrived(byte[] message) {
		// 拓展方法
		TcpEventHandler.handleAlarm("设备号","告警信息");
	}
}

package com.cqcloud.platform.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

/**
 * @author weimeilayer@gmail.com ✨
 * @date 💓💕 2022年4月12日 🐬🐇 💓💕
 */
@Configuration
public class WebSocketConfig {

	/**
	 * 注入ServerEndpointExporter, 这个bean会自动注册使用了@ServerEndpoint注解声明的Websocket endpoint
	 * @return
	 */
	@Bean
	public ServerEndpointExporter serverEndpointExporter() {
		return new ServerEndpointExporter();
	}
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
进行批量用户的id进行下发webscoket信息

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

vulnhub靶场【hacksudo】之2

前言 靶机&#xff1a;hacksudo-2 攻击&#xff1a;kali 都是采用虚拟机&#xff0c;网卡为桥接模式 主机发现 使用arp-scan -l或者netdiscover -r 192.168.1.1/24或者nmap和masscan等可以扫描网段的扫描工具&#xff0c;不过建议使用前两个即可&#xff0c;因为靶机与攻击…

Spring Boot助力,一键解锁招聘全流程信息精细化管理

2系统相关技术 2.1 Java语言介绍 Java是由SUN公司推出&#xff0c;该公司于2010年被oracle公司收购。Java本是印度尼西亚的一个叫做爪洼岛的英文名称&#xff0c;也因此得来java是一杯正冒着热气咖啡的标识。Java语言在移动互联网的大背景下具备了显著的优势和广阔的前景&#…

【设计模式系列】策略模式(二十四)

一、什么是策略模式 策略模式&#xff08;Strategy Pattern&#xff09;是软件设计模式中的一种行为型模式。它定义了一系列算法&#xff0c;并将每一个算法封装起来&#xff0c;使它们可以互换使用&#xff0c;算法的变化不会影响使用算法的用户。策略模式让算法的变化独立于…

《Java核心技术I》映射条目的原子更新

映射条目的原子更新 ConcurrentHashMap只有部分原子更新。 JavaAPI提供了一些新方法&#xff0c;例如&#xff1a;compute方法可以提供一个键和一个计算新值的函数。 map.compute(word,(k,v)->v null ? 1 : v1) 注释&#xff1a;ConcurrentHashMap中不允许有null值。很…

【Rive】波动文字

1 前言 本文将使用文本修改器&#xff08;Text Modifiers&#xff09;做文字动画&#xff0c;实现文字波动效果。 按以下步骤可以创建一个 Modifier Group 和 Range。 部分参数的释义如下。 Range: Modifier 作用的范围。Falloff: Modifier 在最大值时的范围&#xff0c;Fallo…

《庐山派从入门到...》初见

《庐山派从入门到...》初见 庐山派简介立创庐山派资源网站 庐山派个人分享&#xff0c;主要内容放到视频中&#xff0c;视频主要流程截图在博客上&#xff0c;所使用链接和代码也会放到博客中方便提取。希望小伙伴给我的视频点个关注谢谢小伙伴们。 《庐山派从入门到...》初见 …

现代软件开发技术 | 第2章:SpringMVC基础

文章目录 📚Spring MVC的工作原理📚Spring MVC的工作环境📚基于注解的控制器📚表单标签库与数据绑定🐇表单标签库🐇数据绑定📚JSON数据交互🐇JSON数据结构🐇JSON数据转换📚Spring MVC的基本配置🐇静态资源配置🐇拦截器配置🐇文件上传配置📚Spring …

【JavaWeb后端学习笔记】Java上传文件到阿里云对象存储服务

阿里云对象存储 1、创建阿里云对象存储节点2、上传文件2.1 修改项目配置文件2.2 定义一个Properties类获取配置信息2.3 准备一个alioss工具类2.4 创建注册类&#xff0c;将AliOssUtil 注册成Bean2.5 使用AliOssUtil 工具类上传文件2.6 注意事项 使用阿里云对象存储服务分为以下…

大模型 LMDeploy 量化部署

1 模型部署 定义&#xff1a; 在软件工程中&#xff0c;部署通常指的是将开发完毕的软件投入使用的过程。在人工智能领域&#xff0c;模型部署是实现深度学习算法落地应用的关键步骤。简单来说&#xff0c;模型部署就是将训练好的深度学习模型在特定环境中运行的过程。 场景…

Github 2024-12-01 开源项目月报 Top20

根据Github Trendings的统计,本月(2024-12-01统计)共有20个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Python项目10TypeScript项目9Go项目2HTML项目1Shell项目1Jupyter Notebook项目1屏幕截图转代码应用 创建周期:114 天开发语言:TypeScript, Py…

调试玲珑应用

文章目录 一、在终端中使用 gdb 进行调试二、QtCreator 配置 以下教程以“构建工具”一节中提到的 linglong-builder-demo 项目为例。我们将项目放在 /path/to/project。参考教程操作时注意对路径进行替换。 由于玲珑应用运行在容器中&#xff0c;想要在宿主机上对其进行调试&…

Linux笔记-现场实施记录(找网口、挂载u盘)

2024-10-08 在项目现场&#xff0c;进行实施&#xff0c;在此记录下&#xff0c;方便以后查阅。记录2个点&#xff1b; 找网口 服务器开机后查下ifconfig。 看下网卡配的标识如eth0 再使用如下命令 ethtool -p eth0 30 此时物理网口会闪烁&#xff0c;此时再看下是哪一个…

C# (WinForms) 使用 iTextSharp 库将图片转换为 PDF

iTextSharp简介 iTextSharp 是一个开源的 .NET 库&#xff0c;主要用于创建和操作 PDF 文档。它是 iText 的 .NET 版本&#xff0c;iText 是一个广泛使用的 Java 库。iTextSharp 继承了 iText 的核心功能并进行了适应 .NET 平台的调整。 iTextSharp 的主要功能包括&#xff1a…

2020 年“泰迪杯”数据分析职业技能大赛A 题教育平台的线上课程智能推荐策略

2020 年“泰迪杯”数据分析职业技能大赛A 题教育平台的线上课程智能推荐策略 完整代码请私聊 博主 一、 背景 近年来&#xff0c;随着互联网与通信技术的高速发展&#xff0c;学习资源的建设与共享呈现出新的发展趋势&#xff0c;各种网课、慕课、直播课等层出不穷&#xff0c…

QT 中基于 TCP 的网络通信

基础 基于 TCP 的套接字通信需要用到两个类&#xff1a; 1&#xff09;QTcpServer&#xff1a;服务器类&#xff0c;用于监听客户端连接以及和客户端建立连接。 2&#xff09;QTcpSocket&#xff1a;通信的套接字类&#xff0c;客户端、服务器端都需要使用。 这两个套接字通信类…

企业级日志分析系统ELK之ELK概述

ELK 概述 ELK 介绍 什么是 ELK 早期IT架构中的系统和应用的日志分散在不同的主机和文件&#xff0c;如果应用出现问题&#xff0c;开发和运维人员想排 查原因&#xff0c;就要先找到相应的主机上的日志文件再进行查找和分析&#xff0c;所以非常不方便&#xff0c;而且还涉及…

SpringBoot教程(十四) SpringBoot之集成Redis

SpringBoot教程&#xff08;十四&#xff09; | SpringBoot之集成Redis 一、Redis集成简介二、集成步骤 2.1 添加依赖2.2 添加配置2.3 项目中使用之简单使用 &#xff08;举例讲解&#xff09;2.4 项目中使用之工具类封装 &#xff08;正式用这个&#xff09;2.5 序列化 &…

【Transformer序列预测】Pytorch中构建Transformer对序列进行预测源代码

Python&#xff0c;Pytorch中构建Transformer进行序列预测源程序。包含所有的源代码和数据&#xff0c;程序能够一键运行。此程序是完整的Transformer&#xff0c;即使用了Encoder、Decoder和Embedding所有模块。源程序是用jupyterLab所写&#xff0c;建议分块运行。也整理了.p…

基于LLM智能问答系统【阿里云:天池比赛】

流程&#xff1a; 1、分别识别问题及提供的资料文件中的公司名实体&#xff0c;有公司名的走语义检索&#xff0c;无公司名的走结构化召回 2、结构化召回&#xff1a;Qwen根据问题生成sql&#xff0c;执行sql获取结果数值&#xff0c;把结果数值与问题给到Qwen生成最终结果 …

商品期权开户条件是什么?

商品期权开户条件是什么&#xff1f; 商品期权是一种金融衍生品&#xff0c;它赋予期权持有者在特定日期&#xff08;欧式期权&#xff09;或在特定日期之前&#xff08;美式期权&#xff09;&#xff0c;以特定价格&#xff08;行权价格&#xff09;买入或卖出一定数量的某种…