SpringBoot + Websocket实现系统用户消息通知
1、引入依赖
<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.7</version><relativePath/>
</parent>
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency>
</dependencies>
2、保存登录信息实体类
@Data
public class ClientInfoEntity {/*** 客户端连接的session*/private Session session;/*** 连接存活时间*/private LocalDateTime existTime;
}
3、SpringContextHolder
@Component
public class SpringContextHolder implements ApplicationContextAware, DisposableBean {private static ApplicationContext applicationContext = null;private static Logger logger = LoggerFactory.getLogger(SpringContextHolder.class);public SpringContextHolder() {}public static <T> T getBean(Class<T> requiredType) {assertContextInjected();return applicationContext.getBean(requiredType);}@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {if (SpringContextHolder.applicationContext != null) {logger.info("SpringContextHolder中的ApplicationContext被覆盖, 原有ApplicationContext为:" + SpringContextHolder.applicationContext);}SpringContextHolder.applicationContext = applicationContext;}@Overridepublic void destroy() throws Exception {if (logger.isDebugEnabled()) {logger.debug("清除SpringContextHolder中的ApplicationContext:" + applicationContext);}applicationContext = null;}private static void assertContextInjected() {}
}
4、创建ServerEndpoint
import org.springframework.stereotype.Component;import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;// 该类负责监听客户端的连接、断开连接、接收消息、发送消息等操作
@Slf4j
@Component
@CrossOrigin(origins = "*")
@ServerEndpoint(value = "/notice/websocket")
public class WsServerEndpoint {//key:accountName、sessionIdprivate static Table<String, String, ClientInfoEntity> table = HashBasedTable.create();private static final int EXIST_TIME_HOUR = 2;// 注:spring bean生命周期与websocket不一样,不能直接注入@Autowiredprivate TmsgNoticeRecordService tmsgNoticeRecordService = null;@Autowiredprivate AuthClientUtils authClientUtils = null;/*** 连接建立成功调用的方法*/@OnOpenpublic void onOpen(Session session, EndpointConfig config) {if (null == authClientUtils) {authClientUtils = SpringContextHolder.getBean(AuthClientUtils.class);}String accountName = null;Map<String, List<String>> params = session.getRequestParameterMap();List<String> tokens = params.get("token");if (CollectionUtils.isNotEmpty(tokens)) {try {accountName = authClientUtils.getCurrentAccountName(tokens.get(0));} catch (Exception e) {log.error("webSocket:", e);}}if (null == accountName) {log.info("WebSocket 连接建立失败");throw new RuntimeException("accountName获取失败,WebSocket 连接建立失败");}if (table.containsColumn(session.getId())) {log.info("token已建立连接");return;}if (null == tmsgNoticeRecordService) {tmsgNoticeRecordService = SpringContextHolder.getBean(TmsgNoticeRecordService.class);}//业务逻辑try {// 通过账号查询当前登录人接收到所有未读的消息List<Map<String,Object> list = tmsgNoticeRecordService.pushDataList(accountName);if (CollectionUtils.isNotEmpty(list)) {for (int i = 0; i < list.size(); i++) {JSONObject jsonObject = new JSONObject();jsonObject.put("msg", list.get(i).get("msg"));// 发送消息session.getAsyncRemote().sendText(jsonObject.toJSONString());}}} catch (Exception e) {log.info("业务逻辑异常", e);}//把成功建立连接的会话在实体类中保存ClientInfoEntity entity = new ClientInfoEntity();entity.setSession(session);entity.setExistTime(LocalDateTime.now().plusHours(EXIST_TIME_HOUR));table.put(accountName, session.getId(), entity);log.info("WebSocket 连接建立成功: " + accountName);}/*** 当断开连接时调用该方法** @param session*/@OnClosepublic void onClose(Session session) {if (!table.isEmpty() && table.containsColumn(session.getId())) {Iterator<Map.Entry<String, ClientInfoEntity>> iterator = table.column(session.getId()).entrySet().iterator();while (iterator.hasNext()) {Map.Entry<String, ClientInfoEntity> rowValue = iterator.next();table.remove(rowValue.getKey(), session.getId());log.info("WebSocket 连接关闭成功");}}}/*** 接受消息* 这是接收和处理来自用户的消息的地方。我们需要在这里处理消息逻辑,可能包括广播消息给所有连接的用户。*/@OnMessagepublic void onMessage(Session session, String message) throws IOException {if (!table.isEmpty() && table.containsColumn(session.getId())) {Iterator<Map.Entry<String, ClientInfoEntity>> iterator = table.column(session.getId()).entrySet().iterator();while (iterator.hasNext()) {Map.Entry<String, ClientInfoEntity> cell = iterator.next();//如果是心跳包if ("heartbeat".equals(message)) {//只要接受到客户端的消息就进行续命(时间)cell.getValue().setExistTime(LocalDateTime.now().plusHours(EXIST_TIME_HOUR));if (cell.getValue().getSession().isOpen()) {cell.getValue().getSession().getBasicRemote().sendText("heartbeat");}return;}//只要接受到客户端的消息就进行续命(时间)cell.getValue().setExistTime(LocalDateTime.now().plusHours(EXIST_TIME_HOUR));if (cell.getValue().getSession().isOpen()) {cell.getValue().getSession().getBasicRemote().sendText("heartbeat");}}}}/*** 处理WebSocket中发生的任何异常。可以记录这些错误或尝试恢复。*/@OnErrorpublic void onError(Throwable error) {log.error("webSocket报错信息:" + error.toString());}/*** 心跳 每20秒发送一次*/@XxlJob("message.heartbeat")public ReturnT<String> heartbeat() {XxlJobHelper.log("心跳");if (!table.isEmpty()) {//超过存活时间进行删除Iterator<Table.Cell<String, String, ClientInfoEntity>> iterator = table.cellSet().iterator();while (iterator.hasNext()) {Table.Cell<String, String, ClientInfoEntity> cell = iterator.next();if (cell.getValue().getSession().isOpen()) {cell.getValue().getSession().getAsyncRemote().sendText("heartbeat");} else {// 已经断开连接table.remove(cell.getRowKey(), cell.getColumnKey());}}}return ReturnT.SUCCESS;}/*** @Description: 同账号多次登录,通过账号发送消息*/private void send(String rowKey, String msg) {Iterator<Map.Entry<String, ClientInfoEntity>> columnIterator = table.row(rowKey).entrySet().iterator();while (columnIterator.hasNext()) {Map.Entry<String, ClientInfoEntity> column = columnIterator.next();if (column.getValue().getSession().isOpen()) {column.getValue().getSession().getAsyncRemote().sendText(msg);}}}
}
WebSocket配置类
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;@EnableWebSocket
@Configuration
public class WebSocketConfig {/*** 该方法用来创建并返回一个ServerEndpointExporter实例。* 这个实例的作用是扫描并自动配置所有使用@ServerEndpoint注解标记的WebSocket端点*/@Beanpublic ServerEndpointExporter serverEndpointExporter() {return new ServerEndpointExporter();}
}
启动应用程序并测试
1、启动类加@EnableWebSocket注解2、使用websocket测试工具访问: ws://127.0.0.1:8080//notice/websocket?token=xxxxx
相关文章:
SpringBoot + Websocket实现系统用户消息通知
1、引入依赖 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.7</version><relativePath/> </parent> <dependencies><dependency>…...
基于 HTML5 Canvas 制作一个精美的 2048 小游戏--day 1
基于 HTML5 Canvas 制作一个精美的 2048 小游戏 在这个快节奏的生活中,简单而富有挑战性的游戏总能给我们带来乐趣。2048 是一款受欢迎的益智游戏,不仅考验智力,还能让人回味无穷。今天,我带领大家将一起学习如何使用 HTML5 Canv…...
macos arm 本地/docker/本地k8s 安装jupyterhub 并登陆
概述 很多文章写的启动官方docker镜像后,新建linux用户即可直接登录,不知道是否版本原因,总之目前最新版我亲测不可以,踩坑两天,这里记录下解决过程,以及各种细节在文档中的位置.以及为什么官方镜像不能直接使用的原因. part1 本地安装jupyterhub https://jupyterhub.readth…...
go采集注册表
package mainimport ("fmt""golang.org/x/sys/windows/registry""log""os""strconv""strings" )func USBSTOR_Enum() {// 打开注册表键keyPath : SYSTEM\CurrentControlSet\Services\USBSTOR\Enumk, err : regist…...
Java连接TDengine和MySQL双数据源
git文件地址:项目首页 - SpringBoot连接TDengine和MySQL双数据源:SpringBoot连接TDengine和MySQL双数据源 - GitCode 1、yml配置 spring:datasource:druid:mysql:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/testusername: roo…...
配置AOSP下载环境
1#curl https://storage.googleapis.com/git-repo-downloads/repo > ~/bin/repo 2#repo init -u https://android.googlesource.com/platform/manifest 3#清华镜像站帮助页 https://mirrors.tuna.tsinghua.edu.cn/help/AOSP/ 4#同步安卓AOSP 这里是安卓13 repo init -u htt…...
SpringBoot源码解析(七):应用上下文结构体系
SpringBoot源码系列文章 SpringBoot源码解析(一):SpringApplication构造方法 SpringBoot源码解析(二):引导上下文DefaultBootstrapContext SpringBoot源码解析(三):启动开始阶段 SpringBoot源码解析(四):解析应用参数args Sp…...
5 分钟复刻你的声音,一键实现 GPT-Sovits 模型部署
想象一下,只需简单几步操作,就能生成逼真的语音效果,无论是为客户服务还是为游戏角色配音,都能轻松实现。GPT-Sovits 模型,其高效的语音生成能力为实现自然、流畅的语音交互提供了强有力的技术支持。本文将详细介绍如何…...
数字化时代,传统代理模式的变革之路
在数字化飞速发展的今天,线上线下融合(O2O)成了商业领域的大趋势。这股潮流,正猛烈冲击着传统代理模式,给它带来了新的改变。 咱们先看看线上线下融合现在啥情况。线上渠道那是越来越多,企业纷纷在电商平台…...
python爬虫爬取淘宝商品比价||淘宝商品详情API接口
最近在学习北京理工大学的爬虫课程,其中一个实例是讲如何爬取淘宝商品信息,现整理如下: 功能描述:获取淘宝搜索页面的信息,提取其中的商品名称和价格 探讨:淘宝的搜索接口 翻页的处理 技术路线:requests…...
HunyuanVideo 文生视频模型实践
HunyuanVideo 文生视频模型实践 flyfish 运行 HunyuanVideo 模型使用文本生成视频的推荐配置(batch size 1): 模型分辨率(height/width/frame)峰值显存HunyuanVideo720px1280px129f60GHunyuanVideo544px960px129f45G 本项目适用于使用 N…...
CSRF攻击XSS攻击
概述 在 HTML 中,<a>, <form>, <img>, <script>, <iframe>, <link> 等标签以及 Ajax 都可以指向一个资源地址,而所谓的跨域请求就是指:当前发起请求的域与该请求指向的资源所在的域不一样。这里的域指…...
vue3学习日记8 - 一级分类
最近发现职场前端用的框架大多为vue,所以最近也跟着黑马程序员vue3的课程进行学习,以下是我的学习记录 视频网址: Day2-17.Layout-Pinia优化重复请求_哔哩哔哩_bilibili 学习日记: vue3学习日记1 - 环境搭建-CSDN博客 vue3学…...
Notepad++移除所有空格
1.打开Notepad。 2.打开你想要编辑的文件。 3.按下 Ctrl H 打开查找和替换对话框,并选择 “正则表达式”。 4.在 “查找目标” 框中输入 \s。 5.在 “替换为” 框中留空,不填写任何内容。 6.点击 “全部替换” 按钮。...
JavaSE第八天
一、继承之super关键字 super关键字: 一个引用变量,用于引用父类对象 父类和子类都具有相同的命名方法,要调用父类方法时使用 父类和子类都具有相同的命名属性,要调用父类中的属性时使用 super也是父类的构造函数,…...
ideal jdk报错如何解决
例如: 可能一:环境变量中未配置 请在Path中加入并将要使用的最好置顶,如 可能二:项目结构中语言级别错误: 可能三:Maven工程中,对于模块要单独设置jdk: 如: 未设置则为默认,在博主本次展示中为:...
嵌入式Linux ntpclient的使用
ntpclient是一个用于与NTP(Network Time Protocol,网络时间协议)服务器通信并测量系统时间的工具。我这里用的是"ntpclient_2024_132"。下载源码编译后会得到一个ntpclient程序。 下面是对ntpclient每个选项的解释: -…...
25/1/15 嵌入式笔记 初学STM32F108
GPIO初始化函数 GPIO_Ini:初始化GPIO引脚的模式,速度和引脚号 GPIO_Init(GPIOA, &GPIO_InitStruct); // 初始化GPIOA的引脚0 GPIO输出控制函数 GPIO_SetBits:将指定的GPIO引脚设置为高电平 GPIO_SetBits(GPIOA, GPIO_Pin_0); // 将GPIO…...
【练习】力扣热题100 字符串解码
题目 给定一个经过编码的字符串,返回它解码后的字符串。 编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k保证为正整数。 你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,…...
1.快慢指针-力扣-283-移动零
题目描述 给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。 请注意 ,必须在不复制数组的情况下原地对数组进行操作。 用例 示例 1: 输入: nums [0,1,0,3,12] 输出: [1,3,12,0,0] 示例 2: 输入: nu…...
5. 使用springboot做一个音乐播放器软件项目【业务逻辑开发】
#万物oop 上一章文章 我们做了音乐播放器 数据表的创建。参加地址: https://blog.csdn.net/Drug_/article/details/145093705 今天分享的这篇文章就是根据数据表 来写 业务逻辑 。 今天我们主要是实现管理后台的功能。 对于这篇文章 的理解 需要小伙伴有 springbo…...
配置正确spring-boot工程启动的时候报错dynamic-datasource Please check the setting of primary
一个两年没有碰的spring-boot工程,启动时报错。因为用了baomidou的多源数据库配置,因此启动时报错primary没有正确配置。经过检查,确定配置文件配置正确。 报错原因是没有读到正确的配置文件。pom文件里的resources标签重定义,把…...
Freeswitch使用media_bug能力实现回铃音检测
利用freeswitch的media bug能力来在智能外呼时通过websocket对接智能中心的声音检测接口,来实现回铃音检测,来判断用户当前是否已响应,拒接,关机等。 1.回铃音处理流程 2.模块源码目录结构 首先新建一个freeswitch的源码的src/a…...
Kubernetes(k8s)和Docker Compose本质区别
Kubernetes(简称 k8s)和 Docker Compose 是容器编排领域的两大重要工具,虽然它们都用于管理和编排容器化应用,但在设计目标、功能特性、使用场景和复杂度上存在显著差异。以下将从多个方面详细探讨 Kubernetes 和 Docker Compose …...
OSI七层协议——分层网络协议
OSI七层协议,顾名思义,分为七层,实际上七层是不存在的,是人为的进行划分,让人更好的理解 七层协议包括,物理层(我),数据链路层(据),网络层(网),传输层(传输),会话层(会),表示层(表),应用层(用)(记忆口诀->我会用表…...
RabbitMQ 客户端 连接、发送、接收处理消息
RabbitMQ 客户端 连接、发送、接收处理消息 一. RabbitMQ 的机制跟 Tcp、Udp、Http 这种还不太一样 RabbitMQ 服务,不是像其他服务器一样,负责逻辑处理,然后转发给客户端 而是所有客户端想要向 RabbitMQ服务发送消息, 第一步&a…...
SQL ON与WHERE区别
在 MySQL 中,ON 和 WHERE 都用于过滤数据,但它们的使用场景和作用有所不同,尤其是在涉及 JOIN 操作时。下面通过具体的例子来说明它们的区别。 1. ON 的作用 ON 用于指定表之间的连接条件,决定哪些行应该被连接在一起。它在 JOI…...
[创业之路-254]:《华为数字化转型之道》-1-华为是一个由客户需求牵引、高度数字化、高度智能化、由无数个闭环流程组成的价值创造、评估、分配系统。
前言: 华为是一个由客户需求牵引、高度数字化、高度智能化、由无数个闭环流程组成的价值创造、评估、分配系统。华为的流程大到战略,小到日常工作,是由无数个自我调节自我优化的数字化闭环控制流程组成,大闭环套小闭环࿰…...
免费为企业IT规划WSUS:Windows Server 更新服务 (WSUS) 之快速入门教程(一)
哈喽大家好,欢迎来到虚拟化时代君(XNHCYL),收不到通知请将我点击星标!“ 大家好,我是虚拟化时代君,一位潜心于互联网的技术宅男。这里每天为你分享各种你感兴趣的技术、教程、软件、资源、福利…...
异步任务与定时任务
一、异步任务 基于TaskExecutionAutoConfiguration配置类中,注册的ThreadPoolTaskExecutor线程池对象进行异步任务执行。 (一)手动执行异步任务 在yml中配置线程池参数 spring: task:execution:pool:core-size: 5 # 核心线程数max-size: 20 # 最大线…...
大模型WebUI:Gradio全解11——Chatbot:融合大模型的多模态聊天机器人(5)
大模型WebUI:Gradio全解11——Chatbot:融合大模型的多模态聊天机器人(5) 前言本篇摘要11. Chatbot:融合大模型的多模态聊天机器人11.5 Chatbot的特殊Events11.5.1 各事件总演示11.5.2 详解.undo、.retry、.like和.edit…...
32单片机综合应用案例——基于GPS的车辆追踪器(三)(内附详细代码讲解!!!)
困难不会永远存在,只要你勇于面对,坚持努力,就一定能够战胜一切困难。每一次挑战都是一次成长的机会,不要害怕失败,失败是成功之母。只有经历过失败,你才能更加明白自己的不足,并不断改进自己&a…...
扣除价格因素与剔除季节性因素:统计数据中的“真实”增长(中英双语)
扣除价格因素与剔除季节性因素:统计数据中的“真实”增长 在经济统计分析中,我们经常会听到“扣除价格因素”和“剔除季节性因素”这两个概念。这两者都是为了排除外部干扰因素,真实反映经济活动的增长情况。它们分别针对价格波动和季节性波…...
网卡接收报文的过程
网卡接收报文的过程通常包括以下几个关键步骤: 1. 硬件接收: • 网卡硬件首先接收到从网络传输过来的数据包。网络接口卡(NIC)负责将接收到的电信号转换为数字信号,并存储到一个硬件缓冲区中。 2. DMA传输ÿ…...
Windows图形界面(GUI)-QT-C/C++ - QT 对话窗口
公开视频 -> 链接点击跳转公开课程博客首页 -> 链接点击跳转博客主页 目录 模态对话框 非模态对话框 文件对话框 基本概念 静态函数 常见属性 颜色对话框 基本概念 静态函数 常见属性 字体对话框 基本概念 静态函数 常见属性 输入对话框 基本概念 …...
bypass--2025春秋杯冬季赛
漏洞点 题目不难,这个循环赋值的结束条件是s[i]0,并且s和key再栈上的位置是挨着的 那么很容易想到,第二次循环赋值的时候,有一个溢出,溢出部分的值是第一次写入的key的值。 那么基本思路就是,利用这段溢出…...
学习微信小程序的下拉列表控件-picker
1、创建一个空白工程 2、index.wxml中写上picker布局: <!--index.wxml--> <view class"container"><picker mode"selector" range"{{array}}" bindchange"bindPickerChange"><view class"pick…...
【17】Word:林楚楠-供应链❗
目录 题目 NO1.2 NO3 NO4 NO5 NO6 NO7 NO89 题目 NO1.2 另存为:文件→另存为→文档→文件名/考生文件夹F12/FnF12→文件名/考生文件夹 插入→分节符→文本框→输入文件→排版_居中对齐→间距/回车去掉文本框的边框→选中文本框→格式:形状轮廓…...
父子盒子滑动事件穿透问题
问题描述 当父子盒子都有滚动条时,在子盒子内滚动时,父盒子滚动子盒子无法滚动,直到父盒子滚动到底部,子盒子才滚动 解决 如果是vue的项目,直接在子盒子上添加 wheel.stop""...
vue-amap、leaflet、融汇 离线地图瓦片使用情况分析
vue-amap: vue3写的,使用文档 -> 文档地址 <-的离线jsApi里的demo,发现 tile-url不能读取本地项目文件夹里的瓦片,文档里写的其实还是要互联网读取的高德瓦片......... 作者在git库回复tile-url要么放项目里使用绝对路…...
leetcode - 1055. Shortest Way to Form String
Description A subsequence of a string is a new string that is formed from the original string by deleting some (can be none) of the characters without disturbing the relative positions of the remaining characters. (i.e., “ace” is a subsequence of “abcd…...
【HarmonyOS之旅】基于ArkTS开发(二) -> UI开发三
目录 1 -> 绘制图形 1.1 -> 绘制基本几何图形 1.2 -> 绘制自定义几何图形 2 -> 添加动画效果 2.1 -> animateTo实现闪屏动画 2.2 -> 页面转场动画 3 -> 常见组件说明 1 -> 绘制图形 绘制能力主要是通过框架提供的绘制组件来支撑,支…...
RabbitMQ 进阶
文章目录 一、发送者的可靠性 1.1 生产者重试机制:1.2 生产者确认机制: 1.2.1 开启生产者确认:1.2.2 定义 ReturnCallback:1.2.3 定义 ConfirmCallback: 二、MQ 的可靠性 2.1 数据持久化: 2.1.1 交换机持…...
RabbitMQ---TTL与死信
(一)TTL 1.TTL概念 TTL又叫过期时间 RabbitMQ可以对队列和消息设置TTL,当消息到达过期时间还没有被消费时就会自动删除 注:这里我们说的对队列设置TTL,是对队列上的消息设置TTL并不是对队列本身,不是说队列过期时间…...
uniapp(小程序、app、微信公众号、H5)预览下载文件(pdf)
1. 小程序、app 在uniapp开发小程序环境或者app环境中,都可以使用以下方式预览文件 之前其实写过一篇,就是使用uniapp官网提供文件下载、文件保存、文件打开的API, uniapp文件下载 感兴趣也可以去看下 uni.downloadFile({// baseURL 是...
Spring Boot经典面试题及答案
一、Spring Boot基础知识 什么是Spring Boot? 答案: Spring Boot是Spring开源组织下的子项目,是Spring组件一站式解决方案。它简化了Spring应用程序的初始化和开发过程,通过“约定大于配置”的原则,减少了手动配置的繁…...
usb通过hdc连接鸿蒙next的常用指令
参考官方 注册报名https://www.hiascend.com/developer/activities/details/44de441ef599450596131c8cb52f7f8c/signup?channelCodeS1&recommended496144 hdc-调试命令-调测调优-系统 - 华为HarmonyOS开发者https://developer.huawei.com/consumer/cn/doc/harmonyos-guid…...
FPGA:Quartus软件与操作系统版本对照表
文章目录 1.软件概述2.软件版本3.设计流程4.支持的设备5.新特性6.版本对照 1.软件概述 Quartus软件是由英特尔(Intel)公司开发的一款功能强大的FPGA(现场可编程逻辑门阵列)设计工具,广泛应用于数字电路设计、仿真、综…...
RustDesk ID更新脚本
RustDesk ID更新脚本 此PowerShell脚本自动更新RustDesk ID和密码,并将信息安全地存储在Bitwarden中。 特点 使用以下选项更新RustDesk ID: 使用系统主机名生成一个随机的9位数输入自定义值 为RustDesk生成新的随机密码将RustDesk ID和密码安全地存储…...
[C语言]字符串分离
题目 从标准输入流(控制台)中获取一行字符串 str,字符串中可能会存在空格,现在需要将字符串进行分离,规则如下: (1)将 str 中位于 偶数下标 的元素放置在字符串 str1 之中 (2)将 str 中位于 奇数下标 的…...