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

使用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>() {@Overrideprotected 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;}@Overrideprotected 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;}// 将消息传递给 iotPushServiceiotPushService.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("来自于物联网云平台设备协议的1885端口的提取外部数据值: " + 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<>();// 如果记录不为空,获取最新记录的 externalValuelist.stream().findFirst() // 获取最新的一条记录.map(latestRecord -> {// 处理最新记录的逻辑return ""; // 需要返回的值是 externalValue});// 获取最新的一条记录return null; // 如果没有找到,返回 null}@Overridepublic 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线程安全mappublic 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);}}/*** 连接建立成功调用的方法*/@OnOpenpublic 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);}}}/*** 连接关闭调用的方法*/@OnClosepublic 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 客户端发送过来的消息*/@OnMessagepublic 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 + "");}}/*** 发生错误时调用*/@OnErrorpublic 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*/@Overridepublic 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*/@Beanpublic ServerEndpointExporter serverEndpointExporter() {return new ServerEndpointExporter();}
}

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

相关文章:

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

引入最新版本 <!--websocket--> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId> </dependency>启动类加入 //netty 协议服务端口启动 NettyTcpHandler.start()…...

4. 设计模式分类

4.1 创建型模式 这类模式提供创建对象的机制,能够提升已有代码的灵活性和可复用性。 序 号 类 型 业务场景 实现要点 1 工 厂 方 法 多种类型商品不同接口,统一发奖服 务搭建场景 定义一个创建对象的接口,让其子类自 己决定实例化哪一个工厂类,工厂模式 使其创建过程延迟…...

Hive分区值的插入

对于Hive分区表&#xff0c;在我们插入数据的时候需要指定对应的分区值&#xff0c;而这里就会涉及很多种情况。比如静态分区插入、动态分区插入、提供的分区值和分区字段类型不一致&#xff0c;或者提供的分区值是NULL的情况&#xff0c;下面我们依次来展现下不同情况下的表现…...

【多个图片合并成PDF】

因工作安排,小编最近参加了几场学术会议,被多名业界大佬的汇报所震撼。当然也不是白来的,好东西要留存下来回来分享给科室。因此,小编变成了幻灯片专职摄影师,参会的同时对着大牛的PPT就是一顿咔咔咔。回来后,面对手机里数百张照片却犯了难,就这样一张张发到群里么?还是…...

Flutter动画(三)内建显式动画Widget

常见的内建显式动画Widget&#xff1a; ListenableBuilder&#xff1a; AnimatedBuilder AnimatedWidget AlignTransition DecoratedBoxTransition DefaultTextStyleTransition PositionedTransition RelativePositionedTransition RotationTransition ScaleTransiti…...

本地运行打包好的dist

首先输入打包命令 每个人设置不一样 一般人 是npm run build如果不知道可以去package.json里去看。 打包好文件如下 命令行输入 :npm i -g http-server 进入到dist目录下输入 命令cmd 输入 http-server 成功...

什么是Layer Normalization?

一、概念 前面的文章中&#xff0c;我们介绍了Batch Normalization。BN的目的是使得每个batch的输入数据在每个维度上的均值为0、方差为1&#xff08;batch内&#xff0c;数据维度A的所有数值均值为0、方差为1&#xff0c;维度B、C等以此类推&#xff09;&#xff0c;这是由于神…...

17. Threejs案例-Three.js创建多个立方体

17. Threejs案例-Three.js创建多个立方体 实现效果 知识点 WebGLRenderer (WebGL渲染器) WebGLRenderer 是 Three.js 中用于渲染 WebGL 场景的核心类。它负责将场景中的对象渲染到画布上。 构造器 new THREE.WebGLRenderer(parameters) 参数类型描述parametersObject可选…...

RK3568 Android14 打开蓝牙时默认同意

1、最近给一个项目做了一款基础功能的自动测试&#xff0c;在打开蓝牙时&#xff0c;有一个是否同意的提示框要去掉&#xff0c;即默认同意打开蓝牙。 2、路径&#xff1a; packages/apps/Settings/src/com/android/settings/bluetooth/RequestPermissionActivity.java// Sho…...

多模态视频大模型Aria在Docker部署

多模态视频大模型Aria在Docker部署 契机 ⚙ 闲逛HuggingFace的时候发现一个25.3B的多模态大模型&#xff0c;支持图片和视频。刚好我有H20的GPU所以部署来看看效果&#xff0c;因为我的宿主机是cuda-12.1所以为了防止环境污染采用docker部署&#xff0c;通过一系列的披荆斩棘…...

Ant-Design-Vue 全屏下拉日期框无法显示,能显示后小屏又位置错乱

问题1&#xff1a;在全屏后 日期选择器的下拉框无法显示。 解决&#xff1a;在Ant-Design-Vue的文档中&#xff0c;很多含下拉框的组件都有一个属性 getPopupContainer可以用来指定弹出层的挂载节点。 在该组件上加上 getPopupContainer 属性,给挂载到最外层盒子上。 <temp…...

AMR移动机器人赋能制造业仓储自动化升级

在当今制造业的激烈竞争中&#xff0c;智能化、数字化已成为企业转型升级的关键路径。一家制造业巨头&#xff0c;凭借其庞大的生产体系和多个仓库资源&#xff0c;正以前所未有的决心和行动力&#xff0c;在制造业智能化浪潮中勇立潮头&#xff0c;开启了降本增效的新篇章。这…...

【PHP项目实战】活动报名系统

目录 项目介绍 开发语言 后端 前端 项目截图&#xff08;部分&#xff09; 首页 列表 详情 个人中心 后台管理 项目演示 项目介绍 本项目是一款基于手机浏览器的活动报名系统。它提供了一个方便快捷的活动报名解决方案&#xff0c;无需下载和安装任何APP&#xff0c…...

【HarmonyOS】Component组件引入报错 does not meet UI component syntax.

【HarmonyOS】Component组件引入报错 一、问题背景 有时会碰到引入组件时&#xff0c;无法import引入组件&#xff0c;导致引入的组件报错。 或者提示does not meet UI component syntax. &#xff08;不符合UI组件语法。&#xff09; 如下图所示&#xff0c;在引入组件时&a…...

vue3项目最新eslint9+prettier+husky+stylelint+vscode配置

一、eslint9和prettier通用配置 安装必装插件 ESlint9.x pnpm add eslintlatest -DESlint配置 vue 规则 , typescript解析器 pnpm add eslint-plugin-vue typescript-eslint -DESlint配置 JavaScript 规则 pnpm add eslint/js -D配置所有全局变量 globals pnpm add globa…...

备赛蓝桥杯--算法题目(3)

1. 2的幂 231. 2 的幂 - 力扣&#xff08;LeetCode&#xff09; class Solution { public:bool isPowerOfTwo(int n) {return n>0&&n(n&(-n));} }; 2. 3的幂 326. 3 的幂 - 力扣&#xff08;LeetCode&#xff09; class Solution { public:bool isPowerOfT…...

CSS中要注意的样式效果

1. 应用过渡效果 transition: var(--aa); 2.告诉浏览器元素可能会发生变换&#xff0c;从而优化性能。 will-change: transform; 3.使元素不响应鼠标事件。 pointer-events: none; 4.隐藏水平方向上的溢出内容 overflow-x: hidden; 5.定义一个元素的宽度和高度之间的比…...

【NIPS2024】Unique3D:从单张图像高效生成高质量的3D网格

背景&#xff08;现有方法的不足&#xff09;&#xff1a; 基于Score Distillation Sampling &#xff08;SDS&#xff09;的方法&#xff1a;从大型二维扩散模型中提取3D知识&#xff0c;生成多样化的3D结果&#xff0c;但存在每个案例长时间优化问题/不一致问题。 目前通过微…...

使用Kubernetes部署Spring Boot项目

目录 前提条件 新建Spring Boot项目并编写一个接口 新建Maven工程 导入 Spring Boot 相关的依赖 启动项目 编写Controller 测试接口 构建镜像 打jar包 新建Dockerfile文件 Linux目录准备 上传Dockerfile和target目录到Linux 制作镜像 查看镜像 测试镜像 上传镜…...

基于VTX356语音识别合成芯片的智能语音交互闹钟方案

一、方案概述 本方案旨在利用VTX356语音识别合成芯片强大的语音处理能力&#xff0c;结合蓝牙功能、APP或小程序&#xff0c;打造一款功能全面且智能化程度高的闹钟产品。除了基本的时钟显示和闹钟提醒功能外&#xff0c;还拥有正计时、倒计时、日程安排、重要日提醒以及番茄钟…...

Nginx server_name 配置说明

Nginx 是一个高性能的反向代理和负载均衡服务器&#xff0c;其核心配置之一是 server 块中的 server_name 指令。server_name 决定了 Nginx 如何根据客户端请求的 Host 头匹配对应的虚拟主机&#xff08;Virtual Host&#xff09;。 1. 简介 Nginx 使用 server_name 指令来确定…...

反射获取方法和属性

Java反射获取方法 在Java中&#xff0c;反射&#xff08;Reflection&#xff09;是一种强大的机制&#xff0c;允许程序在运行时访问和操作类的内部属性和方法。通过反射&#xff0c;可以动态地创建对象、调用方法、改变属性值&#xff0c;这在很多Java框架中如Spring和Hiberna…...

智能仓储的未来:自动化、AI与数据分析如何重塑物流中心

当仓库学会“思考”&#xff0c;物流的终极形态正在诞生 想象这样的场景&#xff1a; 凌晨3点&#xff0c;某物流中心灯火通明却空无一人。AGV机器人集群根据实时订单动态规划路径&#xff1b;AI视觉系统在0.1秒内扫描包裹信息&#xff1b;数字孪生平台正模拟次日峰值流量压力…...

pikachu靶场通关笔记22-1 SQL注入05-1-insert注入(报错法)

目录 一、SQL注入 二、insert注入 三、报错型注入 四、updatexml函数 五、源码审计 六、insert渗透实战 1、渗透准备 2、获取数据库名database 3、获取表名table 4、获取列名column 5、获取字段 本系列为通过《pikachu靶场通关笔记》的SQL注入关卡(共10关&#xff0…...

AI,如何重构理解、匹配与决策?

AI 时代&#xff0c;我们如何理解消费&#xff1f; 作者&#xff5c;王彬 封面&#xff5c;Unplash 人们通过信息理解世界。 曾几何时&#xff0c;PC 与移动互联网重塑了人们的购物路径&#xff1a;信息变得唾手可得&#xff0c;商品决策变得高度依赖内容。 但 AI 时代的来…...

Android第十三次面试总结(四大 组件基础)

Activity生命周期和四大启动模式详解 一、Activity 生命周期 Activity 的生命周期由一系列回调方法组成&#xff0c;用于管理其创建、可见性、焦点和销毁过程。以下是核心方法及其调用时机&#xff1a; ​onCreate()​​ ​调用时机​&#xff1a;Activity 首次创建时调用。​…...

安宝特方案丨船舶智造的“AR+AI+作业标准化管理解决方案”(装配)

船舶制造装配管理现状&#xff1a;装配工作依赖人工经验&#xff0c;装配工人凭借长期实践积累的操作技巧完成零部件组装。企业通常制定了装配作业指导书&#xff0c;但在实际执行中&#xff0c;工人对指导书的理解和遵循程度参差不齐。 船舶装配过程中的挑战与需求 挑战 (1…...

C#中的CLR属性、依赖属性与附加属性

CLR属性的主要特征 封装性&#xff1a; 隐藏字段的实现细节 提供对字段的受控访问 访问控制&#xff1a; 可单独设置get/set访问器的可见性 可创建只读或只写属性 计算属性&#xff1a; 可以在getter中执行计算逻辑 不需要直接对应一个字段 验证逻辑&#xff1a; 可以…...

DingDing机器人群消息推送

文章目录 1 新建机器人2 API文档说明3 代码编写 1 新建机器人 点击群设置 下滑到群管理的机器人&#xff0c;点击进入 添加机器人 选择自定义Webhook服务 点击添加 设置安全设置&#xff0c;详见说明文档 成功后&#xff0c;记录Webhook 2 API文档说明 点击设置说明 查看自…...

Rust 开发环境搭建

环境搭建 1、开发工具RustRover 或者vs code 2、Cygwin64 安装 https://cygwin.com/install.html 在工具终端执行&#xff1a; rustup toolchain install stable-x86_64-pc-windows-gnu rustup default stable-x86_64-pc-windows-gnu ​ 2、Hello World fn main() { println…...