笔记报警管理

news2024/9/25 13:16:45
1. IOT创建新产品  睡眠检测带  
2. 养老后台  添加了一个设备  睡眠检测带_201_1
3. 新增了模拟器(3个模拟js运行)
4. 创建了消费者组(默认DEFAULT)
5. 创建订阅(3个产品的上报信息 传给DEFAULT)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

消息处理

前面我们已经完成了设备的管理,现在,我们就来处理设备联网之后上报数据的处理

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

从图中我们可以看到,当设备上报数据到IOT平台之后,业务系统可以从IOT平台中拉取数据到自己的数据库,方便在业务应用中去展示。

在IOT中数据流转是这样的,如下图

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在IOT官方文档中,已经提供了对应的接收数据的解决方案,如下链接:

https://help.aliyun.com/zh/iot/developer-reference/connect-a-client-to-iot-platform-by-using-the-sdk-for-java?spm=a2c4g.11186623.0.0.7d7234bdQCi7MQ

官网SDK接入

导入依赖

<!-- amqp 1.0 qpid client -->
 <dependency>
   <groupId>org.apache.qpid</groupId>
   <artifactId>qpid-jms-client</artifactId>
   <version>0.57.0</version>
 </dependency>
 <!-- util for base64-->
 <dependency>
   <groupId>commons-codec</groupId>
  <artifactId>commons-codec</artifactId>
  <version>1.10</version>
</dependency>

下载Demo

下载地址:https://linkkit-export.oss-cn-shanghai.aliyuncs.com/amqp/amqp-demo.zip

接收数据

我们可以修改里面的参数,包含以下几个重要参数:

  • accessKey 秘钥key

  • accessSecret 秘钥

  • iotInstanceId 公共实例ID

  • clientId:InetAddress.getLocalHost().getHostAddress(); 获取本机ip作为clientId

  • consumerGroupId 消费者组

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

修改之后的代码:

package com.aliyun.iotx.demo;

import java.net.InetAddress;
import java.net.URI;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageListener;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.naming.Context;
import javax.naming.InitialContext;

import org.apache.commons.codec.binary.Base64;
import org.apache.qpid.jms.JmsConnection;
import org.apache.qpid.jms.JmsConnectionListener;
import org.apache.qpid.jms.message.JmsInboundMessageDispatch;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class AmqpClient {
    private final static Logger logger = LoggerFactory.getLogger(AmqpClient.class);
    /**
     * 工程代码泄露可能会导致 AccessKey 泄露,并威胁账号下所有资源的安全性。以下代码示例使用环境变量获取 AccessKey 的方式进行调用,仅供参考
     */
    private static String accessKey = "LTAI5tDQKg9F61aJhbmhqVRK";
    private static String accessSecret = "LYUKZH7HQGBoD025pmSq0fQsREaOYD";;
    private static String consumerGroupId = "eraicKJm98cQR0hHgsxb000100";

    //iotInstanceId:实例ID。若是2021年07月30日之前(不含当日)开通的公共实例,请填空字符串。
    private static String iotInstanceId = "iot-06z00frq8umvkx2";

    //控制台服务端订阅中消费组状态页客户端ID一栏将显示clientId参数。
    //建议使用机器UUID、MAC地址、IP等唯一标识等作为clientId。便于您区分识别不同的客户端。
    private static String clientId;

    static {
        try {
            clientId = InetAddress.getLocalHost().getHostAddress();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
    }

    //${YourHost}为接入域名,请参见AMQP客户端接入说明文档。
    private static String host = "iot-06z00frq8umvkx2.amqp.iothub.aliyuncs.com";

    // 指定单个进程启动的连接数
    // 单个连接消费速率有限,请参考使用限制,最大64个连接
    // 连接数和消费速率及rebalance相关,建议每500QPS增加一个连接
    private static int connectionCount = 4;

    //业务处理异步线程池,线程池参数可以根据您的业务特点调整,或者您也可以用其他异步方式处理接收到的消息。
    private final static ExecutorService executorService = new ThreadPoolExecutor(
        Runtime.getRuntime().availableProcessors(),
        Runtime.getRuntime().availableProcessors() * 2, 60, TimeUnit.SECONDS,
        new LinkedBlockingQueue(50000));

    public static void main(String[] args) throws Exception {
        List<Connection> connections = new ArrayList<>();

        //参数说明,请参见AMQP客户端接入说明文档。
        for (int i = 0; i < connectionCount; i++) {
            long timeStamp = System.currentTimeMillis();
            //签名方法:支持hmacmd5、hmacsha1和hmacsha256。
            String signMethod = "hmacsha1";

            //userName组装方法,请参见AMQP客户端接入说明文档。
            String userName = clientId + "-" + i + "|authMode=aksign"
                + ",signMethod=" + signMethod
                + ",timestamp=" + timeStamp
                + ",authId=" + accessKey
                + ",iotInstanceId=" + iotInstanceId
                + ",consumerGroupId=" + consumerGroupId
                + "|";
            //计算签名,password组装方法,请参见AMQP客户端接入说明文档。
            String signContent = "authId=" + accessKey + "&timestamp=" + timeStamp;
            String password = doSign(signContent, accessSecret, signMethod);
            String connectionUrl = "failover:(amqps://" + host + ":5671?amqp.idleTimeout=80000)"
                + "?failover.reconnectDelay=30";

            Hashtable<String, String> hashtable = new Hashtable<>();
            hashtable.put("connectionfactory.SBCF", connectionUrl);
            hashtable.put("queue.QUEUE", "default");
            hashtable.put(Context.INITIAL_CONTEXT_FACTORY, "org.apache.qpid.jms.jndi.JmsInitialContextFactory");
            Context context = new InitialContext(hashtable);
            ConnectionFactory cf = (ConnectionFactory)context.lookup("SBCF");
            Destination queue = (Destination)context.lookup("QUEUE");
            // 创建连接。
            Connection connection = cf.createConnection(userName, password);
            connections.add(connection);

            ((JmsConnection)connection).addConnectionListener(myJmsConnectionListener);
            // 创建会话。
            // Session.CLIENT_ACKNOWLEDGE: 收到消息后,需要手动调用message.acknowledge()。
            // Session.AUTO_ACKNOWLEDGE: SDK自动ACK(推荐)。
            Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);

            connection.start();
            // 创建Receiver连接。
            MessageConsumer consumer = session.createConsumer(queue);
            consumer.setMessageListener(messageListener);
        }

        logger.info("amqp demo is started successfully, and will exit after 60s ");

        // 结束程序运行 
        Thread.sleep(6000 * 1000);
        logger.info("run shutdown");

        connections.forEach(c-> {
            try {
                c.close();
            } catch (JMSException e) {
                logger.error("failed to close connection", e);
            }
        });

        executorService.shutdown();
        if (executorService.awaitTermination(10, TimeUnit.SECONDS)) {
            logger.info("shutdown success");
        } else {
            logger.info("failed to handle messages");
        }
    }

    private static MessageListener messageListener = new MessageListener() {
        @Override
        public void onMessage(final Message message) {
            try {
                //1.收到消息之后一定要ACK。
                // 推荐做法:创建Session选择Session.AUTO_ACKNOWLEDGE,这里会自动ACK。
                // 其他做法:创建Session选择Session.CLIENT_ACKNOWLEDGE,这里一定要调message.acknowledge()来ACK。
                // message.acknowledge();
                //2.建议异步处理收到的消息,确保onMessage函数里没有耗时逻辑。
                // 如果业务处理耗时过程过长阻塞住线程,可能会影响SDK收到消息后的正常回调。
                executorService.submit(new Runnable() {
                    @Override
                    public void run() {
                        processMessage(message);
                    }
                });
            } catch (Exception e) {
                logger.error("submit task occurs exception ", e);
            }
        }
    };

    /**
     * 在这里处理您收到消息后的具体业务逻辑。
     */
    private static void processMessage(Message message) {
        try {
            byte[] body = message.getBody(byte[].class);
            String content = new String(body);
            String topic = message.getStringProperty("topic");
            String messageId = message.getStringProperty("messageId");
            logger.info("receive message"
                + ",\n topic = " + topic
                + ",\n messageId = " + messageId
                + ",\n content = " + content);
        } catch (Exception e) {
            logger.error("processMessage occurs error ", e);
        }
    }

    private static JmsConnectionListener myJmsConnectionListener = new JmsConnectionListener() {
        /**
         * 连接成功建立。
         */
        @Override
        public void onConnectionEstablished(URI remoteURI) {
            logger.info("onConnectionEstablished, remoteUri:{}", remoteURI);
        }

        /**
         * 尝试过最大重试次数之后,最终连接失败。
         */
        @Override
        public void onConnectionFailure(Throwable error) {
            logger.error("onConnectionFailure, {}", error.getMessage());
        }

        /**
         * 连接中断。
         */
        @Override
        public void onConnectionInterrupted(URI remoteURI) {
            logger.info("onConnectionInterrupted, remoteUri:{}", remoteURI);
        }

        /**
         * 连接中断后又自动重连上。
         */
        @Override
        public void onConnectionRestored(URI remoteURI) {
            logger.info("onConnectionRestored, remoteUri:{}", remoteURI);
        }

        @Override
        public void onInboundMessage(JmsInboundMessageDispatch envelope) {}

        @Override
        public void onSessionClosed(Session session, Throwable cause) {}

        @Override
        public void onConsumerClosed(MessageConsumer consumer, Throwable cause) {}

        @Override
        public void onProducerClosed(MessageProducer producer, Throwable cause) {}
    };

    /**
     * 计算签名,password组装方法,请参见AMQP客户端接入说明文档。
     */
    private static String doSign(String toSignString, String secret, String signMethod) throws Exception {
        SecretKeySpec signingKey = new SecretKeySpec(secret.getBytes(), signMethod);
        Mac mac = Mac.getInstance(signMethod);
        mac.init(signingKey);
        byte[] rawHmac = mac.doFinal(toSignString.getBytes());
        return Base64.encodeBase64String(rawHmac);
    }
}

以上代码启动之后,并不能接收到数据,因为设备并没有绑定topic,所以需要在物联网IOT平台设置topic,也就是消费者的消费者组

第一:找到 消息转发->服务端订阅->消费者组列表

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

创建一个自己的消费者组

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

创建好之后可以查看到已经创建好的消费者组,并且自动生成了消费者组id

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

进入刚刚创建的消费者组,然后点击订阅产品,然后创建订阅

需要选择消费者组与推送消息类型(设备上报数据),如下图

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

修改demo代码中的消费者组,改为自己创建的消费者组ID

private static String consumerGroupId = "eraicKJm98cQR0hHgsxb000100";

测试

找一个设备进行数据上报

demo代码中绑定对应的消费者组

启动后台代码,可以在日志中查看消费者到的数据

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

接收设备端数据

SDK提供好的这个工具类,我们需要改造这个类,改造内容如下:

  • 让spring进行管理和监听,一旦有数据变化之后,就可以马上消费,可以让这个类实现ApplicationRunner接口,重新run方法
  • 可以在项目中自己配置线程池的使用
  • 所有的可变参数,如实例id、accessKey、accessSecret、consumerGroupId这些统一在配置文件中维护

改造AmqpClient

package com.zzyl.listener;

import com.zzyl.properties.AliIoTConfigProperties;
import org.apache.commons.codec.binary.Base64;
import org.apache.qpid.jms.JmsConnection;
import org.apache.qpid.jms.JmsConnectionListener;
import org.apache.qpid.jms.message.JmsInboundMessageDispatch;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.jms.*;
import javax.naming.Context;
import javax.naming.InitialContext;
import java.net.InetAddress;
import java.net.URI;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.concurrent.ExecutorService;

@Component
public class AmqpClient implements ApplicationRunner {
    private final static Logger logger = LoggerFactory.getLogger(AmqpClient.class);

    @Autowired
    private AliIoTConfigProperties aliIoTConfigProperties;

    //控制台服务端订阅中消费组状态页客户端ID一栏将显示clientId参数。
    //建议使用机器UUID、MAC地址、IP等唯一标识等作为clientId。便于您区分识别不同的客户端。
    private static String clientId;

    static {
        try {
            clientId = InetAddress.getLocalHost().getHostAddress();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
    }

    // 指定单个进程启动的连接数
    // 单个连接消费速率有限,请参考使用限制,最大64个连接
    // 连接数和消费速率及rebalance相关,建议每500QPS增加一个连接
    private static int connectionCount = 64;

    //业务处理异步线程池,线程池参数可以根据您的业务特点调整,或者您也可以用其他异步方式处理接收到的消息。
    @Autowired
    private ExecutorService executorService;

    public void start() throws Exception {
        List<Connection> connections = new ArrayList<>();

        //参数说明,请参见AMQP客户端接入说明文档。
        for (int i = 0; i < connectionCount; i++) {
            long timeStamp = System.currentTimeMillis();
            //签名方法:支持hmacmd5、hmacsha1和hmacsha256。
            String signMethod = "hmacsha1";

            //userName组装方法,请参见AMQP客户端接入说明文档。
            String userName = clientId + "-" + i + "|authMode=aksign"
                    + ",signMethod=" + signMethod
                    + ",timestamp=" + timeStamp
                    + ",authId=" + aliIoTConfigProperties.getAccessKeyId()
                    + ",iotInstanceId=" + aliIoTConfigProperties.getIotInstanceId()
                    + ",consumerGroupId=" + aliIoTConfigProperties.getConsumerGroupId()
                    + "|";
            //计算签名,password组装方法,请参见AMQP客户端接入说明文档。
            String signContent = "authId=" + aliIoTConfigProperties.getAccessKeyId() + "&timestamp=" + timeStamp;
            String password = doSign(signContent, aliIoTConfigProperties.getAccessKeySecret(), signMethod);
            String connectionUrl = "failover:(amqps://" + aliIoTConfigProperties.getHost() + ":5671?amqp.idleTimeout=80000)"
                    + "?failover.reconnectDelay=30";

            Hashtable<String, String> hashtable = new Hashtable<>();
            hashtable.put("connectionfactory.SBCF", connectionUrl);
            hashtable.put("queue.QUEUE", "default");
            hashtable.put(Context.INITIAL_CONTEXT_FACTORY, "org.apache.qpid.jms.jndi.JmsInitialContextFactory");
            Context context = new InitialContext(hashtable);
            ConnectionFactory cf = (ConnectionFactory) context.lookup("SBCF");
            Destination queue = (Destination) context.lookup("QUEUE");
            // 创建连接。
            Connection connection = cf.createConnection(userName, password);
            connections.add(connection);

            ((JmsConnection) connection).addConnectionListener(myJmsConnectionListener);
            // 创建会话。
            // Session.CLIENT_ACKNOWLEDGE: 收到消息后,需要手动调用message.acknowledge()。
            // Session.AUTO_ACKNOWLEDGE: SDK自动ACK(推荐)。
            Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);

            connection.start();
            // 创建Receiver连接。
            MessageConsumer consumer = session.createConsumer(queue);
            consumer.setMessageListener(messageListener);
        }

        logger.info("amqp  is started successfully, and will exit after server shutdown ");
    }

    private MessageListener messageListener = message -> {
        try {
            //异步处理收到的消息,确保onMessage函数里没有耗时逻辑
            executorService.submit(() -> processMessage(message));
        } catch (Exception e) {
            logger.error("submit task occurs exception ", e);
        }
    };

    /**
     * 在这里处理您收到消息后的具体业务逻辑。
     */
    private void processMessage(Message message) {
        try {
            byte[] body = message.getBody(byte[].class);
            String contentStr = new String(body);
            String topic = message.getStringProperty("topic");
            String messageId = message.getStringProperty("messageId");
            logger.info("receive message"
                    + ",\n topic = " + topic
                    + ",\n messageId = " + messageId
                    + ",\n content = " + contentStr);

        } catch (Exception e) {
            logger.error("processMessage occurs error ", e);
        }
    }

    private JmsConnectionListener myJmsConnectionListener = new JmsConnectionListener() {
        /**
         * 连接成功建立。
         */
        @Override
        public void onConnectionEstablished(URI remoteURI) {
            logger.info("onConnectionEstablished, remoteUri:{}", remoteURI);
        }

        /**
         * 尝试过最大重试次数之后,最终连接失败。
         */
        @Override
        public void onConnectionFailure(Throwable error) {
            logger.error("onConnectionFailure, {}", error.getMessage());
        }

        /**
         * 连接中断。
         */
        @Override
        public void onConnectionInterrupted(URI remoteURI) {
            logger.info("onConnectionInterrupted, remoteUri:{}", remoteURI);
        }

        /**
         * 连接中断后又自动重连上。
         */
        @Override
        public void onConnectionRestored(URI remoteURI) {
            logger.info("onConnectionRestored, remoteUri:{}", remoteURI);
        }

        @Override
        public void onInboundMessage(JmsInboundMessageDispatch envelope) {
        }

        @Override
        public void onSessionClosed(Session session, Throwable cause) {
        }

        @Override
        public void onConsumerClosed(MessageConsumer consumer, Throwable cause) {
        }

        @Override
        public void onProducerClosed(MessageProducer producer, Throwable cause) {
        }
    };

    /**
     * 计算签名,password组装方法,请参见AMQP客户端接入说明文档。
     */
    private static String doSign(String toSignString, String secret, String signMethod) throws Exception {
        SecretKeySpec signingKey = new SecretKeySpec(secret.getBytes(), signMethod);
        Mac mac = Mac.getInstance(signMethod);
        mac.init(signingKey);
        byte[] rawHmac = mac.doFinal(toSignString.getBytes());
        return Base64.encodeBase64String(rawHmac);
    }

    @Override
    public void run(ApplicationArguments args) throws Exception {
        start();
    }
}

设备消息订阅

在接收消息之前,我们需要让设备绑定消费组列表,这样才能通过消费组去接收消息

第一:找到 消息转发->服务端订阅->消费者组列表

目前有一个默认的消费组

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

第二:创建订阅,让产品与消费组进行关联

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

服务端订阅页面的订阅列表页签下,单击创建订阅

创建订阅对话框,设置参数后单击确认

参数说明
产品选择自己的产品
订阅类型选择AMQP
消费组选择默认消费组
推送消息类型选择设备上报消息

保存设备端数据

思路分析

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

功能实现

接收到的数据格式

{
    "deviceType":"ActiveInfraredIntrusionDetectors",
    "iotId":"DoXPJsxUkV0Kcw9zrLRaj0rk00",
    "requestId":"1699948275310",
    "checkFailedData":{

    },
    "productKey":"j0rkHpZoAQ3",
    "gmtCreate":1699948275451,
    "deviceName":"yangan_09",
    "items":{
        "CurrentHumidity":{
            "value":75,
            "time":1699948275447
        },
        "BatteryLevel":{
            "value":75,
            "time":1699948275447
        },
        "SmokeSensorState":{
            "value":0,
            "time":1699948275447
        },
        "IndoorTemperature":{
            "value":27,
            "time":1699948275447
        }
    }
}

修改AmqpClient类中的processMessage,方法

    @Autowired
    private DeviceMapper deviceMapper;

    @Autowired
    private DeviceDataService deviceDataService;

    /**
     * 在这里处理您收到消息后的具体业务逻辑。
     */
    private void processMessage(Message message) {
        try {
            byte[] body = message.getBody(byte[].class);
            String contentStr = new String(body);
            String topic = message.getStringProperty("topic");
            String messageId = message.getStringProperty("messageId");
            logger.info("receive message"
                    + ",\n topic = " + topic
                    + ",\n messageId = " + messageId
                    + ",\n content = " + contentStr);

            //解析数据
            Content content = JSONUtil.toBean(contentStr, Content.class);

            //查询设备是否存在
            LambdaQueryWrapper<Device> wrapper = new LambdaQueryWrapper<>();
            wrapper.eq(Device::getIotId,content.getIotId());
            Device device = deviceMapper.selectOne(wrapper);
            if (device == null){
                logger.error(content.getIotId()+"设备不存在");
                return;//结束本次调用
            }

            //从content中获取设备数据,保存到monitor_device_data表
            List<DeviceData> list = new ArrayList<>();
            content.getItems().forEach((k,v)->{
                DeviceData deviceData = new DeviceData();
                deviceData.setDeviceName(device.getDeviceName());//设备名称
                deviceData.setIotId(content.getIotId());//iotId
                deviceData.setNickname(device.getNickname());//备注名称
                deviceData.setProductKey(device.getProductKey());//设备key
                deviceData.setProductName(device.getProductName());//设备名称
                deviceData.setFunctionId(k);//功能名称
                deviceData.setAccessLocation(device.getRemark());//接入位置
                deviceData.setLocationType(device.getLocationType());//位置类型
                deviceData.setPhysicalLocationType(device.getPhysicalLocationType());//物理位置类型
                deviceData.setDeviceDescription(device.getDeviceDescription());//设备描述
                deviceData.setAlarmTime(LocalDateTimeUtil.of(v.getTime()));//数据上报时间
                deviceData.setDataValue(v.getValue()+"");//数据值
                list.add(deviceData);
            });
            //批量保存
            deviceDataService.saveBatch(list);
        } catch (Exception e) {
            logger.error("processMessage occurs error ", e);
        }
    }

测试

(1)启动模拟设备进行上报

(2)启动后端项目,查看数据库是否保存数据成功

查看设备的物模型数据

需求分析

我们先打开原型图,如下:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

其中的数据值,是从IOT平台实时获取到的数据值

当点击了某个功能(物模型)的查看数据按钮,则会显示这个功能的历史数据,可以按照时间范围进行检索,如下图:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

时间范围=1小时、24小时、7天、自定义;当选择自定义时,出时间选择器文本框

目前需要两个接口,都是查询

  • 查询物模型列表及其数据:实时查询IOT平台,昨天已经实现,我们现在只需要实现第二个即可
  • 查询单个物模型的历史数据

DeviceDataController

package com.zzyl.controller.web;

import com.zzyl.base.PageResponse;
import com.zzyl.base.ResponseResult;
import com.zzyl.controller.BaseController;
import com.zzyl.dto.DeviceDataPageReqDto;
import com.zzyl.entity.DeviceData;
import com.zzyl.service.DeviceDataService;
import com.zzyl.vo.DeviceDataVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

//设备数据
@RestController
public class DeviceDataController extends BaseController {

    @Autowired
    private DeviceDataService deviceDataService;

    //获取设备数据分页结果
    @GetMapping("/device-data/get-page")
    public ResponseResult getDeviceDataPage(DeviceDataPageReqDto deviceDataPageReqDto){
        PageResponse<DeviceData> pageResponse = deviceDataService.findByPage(deviceDataPageReqDto);
        return success(pageResponse);
    }
}

DeviceDataService

    //条件分页查询设备数据
    PageResponse<DeviceData> findByPage(DeviceDataPageReqDto deviceDataPageReqDto);

DeviceDataServiceImpl

    @Override
    public PageResponse<DeviceData> findByPage(DeviceDataPageReqDto dto) {
        //1. 分页参数
        Page<DeviceData> page = new Page<>(dto.getPageNum(), dto.getPageSize());

        //2. 业务参数
        LambdaQueryWrapper<DeviceData> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(StrUtil.isNotEmpty(dto.getDeviceName()), DeviceData::getDeviceName, dto.getDeviceName())
                .eq(StrUtil.isNotEmpty(dto.getFunctionId()), DeviceData::getFunctionId, dto.getFunctionId())
                .between(dto.getStartDate() != null && dto.getEndDate() != null, DeviceData::getAlarmTime, dto.getStartDate(), dto.getEndDate());

        //3. 执行查询
        page = getBaseMapper().selectPage(page, wrapper);
        return new PageResponse(page);
    }

测试

打开设备详情,找到物模型数据,点击查看数据按钮,查询数据,是否正常

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

智能床位

需求分析

在床位管理里面有个智能床位,可以展示绑定智能设备的房间或床位以及设备对应的数据

其中数据有每分钟动态展示一次,如果某个设备的数据异常,则会在tab选项卡中进行提示

获取所有带智能设备的楼层

查询包含智能设备的楼层(房间和床位只有有一项有智能设备就要查)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

-- 查询出所有带有智能设备的楼层(楼层中至少一间房有智能设备 或者 楼层中至少某个床绑定了智能设备)
select bf.code,br.code,md.device_name,bb.bed_number,md2.device_name from base_floor bf
    left join base_room br on br.floor_id = bf.id
    left join monitor_device md on md.physical_location_type = 1 and md.binding_location = br.id
    left join base_bed bb on bb.room_id = br.id
    left join monitor_device md2 on md2.physical_location_type = 2 and md2.binding_location = bb.id
where (md.id is not null or md2.id is not null)
group by bf.code
order by bf.code


-- 如果楼层的智能设备参与
select bf.code,br.code,md.device_name,bb.bed_number,md2.device_name from base_floor bf
    left join base_room br on br.floor_id = bf.id
    left join monitor_device md on md.physical_location_type = 1 and md.binding_location = br.id
    left join base_bed bb on bb.room_id = br.id
    left join monitor_device md2 on md2.physical_location_type = 2 and md2.binding_location = bb.id
    left join monitor_device md3 on md3.physical_location_type = 0 and md2.binding_location = bf.id
where (md.id is not null or md2.id is not null or md3.id is not null)
group by bf.code
order by bf.code

FloorController

    //获取所有楼层 (智能楼层)
    @GetMapping("/floor/getAllFloorsWithDevice")
    public ResponseResult getAllFloorsWithDevice(){
        List<Floor> list = floorService.getAllFloorsWithDevice();
        return success(list);
    }

FloorService

    //查询包含智能设备的楼层
    List<Floor> getAllFloorsWithDevice();

FloorServiceImpl

    //查询包含智能设备的楼层
    @Override
    public List<Floor> getAllFloorsWithDevice() {
        return getBaseMapper().getAllFloorsWithDevice();
    }

FloorMapper

    //查询包含智能设备的楼层
    List<Floor> getAllFloorsWithDevice();

FloorMapper.xml

    <select id="getAllFloorsWithDevice" resultType="com.zzyl.entity.Floor">
        select bf.*
        from base_floor bf
                 left join base_room br on br.floor_id = bf.id
                 left join monitor_device md on md.physical_location_type = 1 and md.binding_location = br.id
                 left join base_bed bb on bb.room_id = br.id
                 left join monitor_device md2 on md2.physical_location_type = 2 and md2.binding_location = bb.id
        where md.id is not null
           or md2.id is not null
        group by bf.code
        order by bf.code
    </select>
  • device d关联的是房间的设备,限定条件 d.location_type = 1 and d.physical_location_type = 1,位置类型为固定,物理位置类为房间
  • device dd关联的是床位的设备,限定条件 d.location_type = 1 and d.physical_location_type = 2,位置类型为固定,物理位置类为床位

获取房间中的智能设备及数据

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

-- 查询指定楼层下  房间中的设备信息   和 床位中设备信息
select *
    from base_room br
    left join monitor_device md on md.physical_location_type = 1 and md.binding_location = br.id
    left join base_bed bb on bb.room_id = br.id
    left join monitor_device md2 on md2.physical_location_type = 2 and md2.binding_location = bb.id
    left join elder e on e.bed_id = bb.id
where (md.id is not null or md2.id is not null) and br.floor_id = 1

思路分析

根据楼层查询房间或者是床位的设备数据(最新的一条数据),实现方案有两种:

  • 通过楼层关联房间、床位、设备表2次、设备数据表2次共6张表查询对应的数据,此方案设备数据表数据较多,效率不高,不推荐

  • 使用缓存,因为只需要查询最近一次的设备数据,可以把最近一次采集的数据存储到redis中,然后让房间或者床位进行匹配

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

    修改AmqpClient类中的processMessage方法,把设备的数据保存到缓存中

        @Autowired
        private DeviceMapper deviceMapper;
    
        @Autowired
        private DeviceDataService deviceDataService;
    
        @Autowired
        private RedisTemplate redisTemplate;
    
        /**
         * 在这里处理您收到消息后的具体业务逻辑。
         */
        private void processMessage(Message message) {
            try {
                byte[] body = message.getBody(byte[].class);
                String contentStr = new String(body);
                String topic = message.getStringProperty("topic");
                String messageId = message.getStringProperty("messageId");
                logger.info("receive message"
                        + ",\n topic = " + topic
                        + ",\n messageId = " + messageId
                        + ",\n content = " + contentStr);
    
                //解析数据
                Content content = JSONUtil.toBean(contentStr, Content.class);
    
                //查询设备是否存在
                LambdaQueryWrapper<Device> wrapper = new LambdaQueryWrapper<>();
                wrapper.eq(Device::getIotId, content.getIotId());
                Device device = deviceMapper.selectOne(wrapper);
                if (device == null) {
                    logger.error(content.getIotId() + "设备不存在");
                    return;//结束本次调用
                }
    
                //从content中获取设备数据,保存到monitor_device_data表
                List<DeviceData> list = new ArrayList<>();
                content.getItems().forEach((k, v) -> {
                    DeviceData deviceData = new DeviceData();
                    deviceData.setDeviceName(device.getDeviceName());//设备名称
                    deviceData.setIotId(content.getIotId());//iotId
                    deviceData.setNickname(device.getNickname());//备注名称
                    deviceData.setProductKey(device.getProductKey());//设备key
                    deviceData.setProductName(device.getProductName());//设备名称
                    deviceData.setFunctionId(k);//功能名称
                    deviceData.setAccessLocation(device.getRemark());//接入位置
                    deviceData.setLocationType(device.getLocationType());//位置类型
                    deviceData.setPhysicalLocationType(device.getPhysicalLocationType());//物理位置类型
                    deviceData.setDeviceDescription(device.getDeviceDescription());//设备描述
                    deviceData.setAlarmTime(LocalDateTimeUtil.of(v.getTime()));//数据上报时间
                    deviceData.setDataValue(v.getValue() + "");//数据值
                    list.add(deviceData);
                });
    
                //批量保存
                deviceDataService.saveBatch(list);
    
                //将最新数据覆盖到Redis中
                redisTemplate.opsForHash().put("DEVICE_LAST_DATA", content.getIotId(), JSONUtil.toJsonStr(list));
                
            } catch (Exception e) {
                logger.error("processMessage occurs error ", e);
            }
        }
    

RoomController

    //获取所有房间(智能床位)
    @GetMapping("/room/getRoomsWithDeviceByFloorId/{floorId}")
    public ResponseResult getRoomsWithDeviceByFloorId(@PathVariable(name = "floorId") Long floorId){
        List<RoomVo> list  = roomService.getRoomsWithDeviceByFloorId(floorId);
        return success(list);
    }

RoomService

    //查询房间及关联的床位和设备
    List<RoomVo> getRoomsWithDeviceByFloorId(Long floorId);

RoomServiceImpl

 @Autowired
    private RedisTemplate redisTemplate;

    @Override
    public List<RoomVo> getRoomsWithDeviceByFloorId(Long floorId) {

        List<RoomVo> roomVoList = getBaseMapper().getRoomsWithDeviceByFloorId(floorId);

        roomVoList.forEach(roomVo -> {
            //寻找房间中的设备的数据
            List<DeviceVo> deviceVos = roomVo.getDeviceVos();
            deviceVos.forEach(deviceVo -> {
                //查redis中是否包含数据
                String deviceDataStr = (String) redisTemplate.opsForHash().get("DEVICE_LAST_DATA", deviceVo.getIotId());
                if(StrUtil.isEmpty(deviceDataStr)){
                    return;//在foreach中return是跳过当前循环
                }
                List<DeviceDataVo> deviceDataVos = JSONUtil.toList(deviceDataStr, DeviceDataVo.class);
                deviceVo.setDeviceDataVos(deviceDataVos);
                //设置一个状态,告诉前端设备中有数据
                roomVo.setStatus(2);
            });


            //房间中的床位中的设备的数据
            List<BedVo> bedVoList = roomVo.getBedVoList();
            bedVoList.forEach(bedVo -> {
                bedVo.getDeviceVos().forEach(deviceVo -> {
                    //查redis中是否包含数据
                    String deviceDataStr = (String) redisTemplate.opsForHash().get("DEVICE_LAST_DATA", deviceVo.getIotId());
                    if(StrUtil.isEmpty(deviceDataStr)){
                        return;//在foreach中return是跳过当前循环
                    }
                    List<DeviceDataVo> deviceDataVos = JSONUtil.toList(deviceDataStr, DeviceDataVo.class);
                    deviceVo.setDeviceDataVos(deviceDataVos);
                    //设置一个状态,告诉前端设备中有数据
                    roomVo.setStatus(2);
                    bedVo.setStatus(2);
                });
            });
        });
        return roomVoList;
    }

RoomMapper

    //查询楼层中的房间和床位设备及数据
    List<RoomVo> getRoomsWithDeviceByFloorId(Long floorId);

RoomMapper.xml

    <resultMap id="RoomsWithDeviceResult" type="com.zzyl.vo.RoomVo">
        <id column="id" property="id"></id>
        <result property="code" column="code"/>
        <result property="sort" column="sort"/>
        <result property="sort" column="sort"/>
        <result property="floorId" column="floor_id"/>
        <result property="floorName" column="fname"/>
        <result column="create_by" property="createBy"/>
        <result column="update_by" property="updateBy"/>
        <result column="remark" property="remark"/>
        <result column="create_time" property="createTime"/>
        <result column="update_time" property="updateTime"/>
        <result column="price" property="price"/>
        <collection property="bedVoList" ofType="com.zzyl.vo.BedVo">
            <id column="bid" property="id"/>
            <result column="bed_number" property="bedNumber"/>
            <result column="bed_status" property="bedStatus"/>
            <result column="room_id" property="roomId"/>
            <result column="ename" property="name"/>
            <result column="eid" property="elderId"/>
            <collection property="deviceVos" ofType="com.zzyl.vo.DeviceVo">
                <id column="b_did" property="id"></id>
                <result column="b_iot_id" property="iotId"/>
                <result column="b_device_name" property="deviceName"/>
                <result column="b_product_key" property="productKey"/>
                <result column="b_product_name" property="productName"/>
            </collection>
        </collection>
        <collection property="deviceVos" ofType="com.zzyl.vo.DeviceVo">
            <id column="r_did" jdbcType="BIGINT" property="id"/>
            <result column="iot_id" jdbcType="VARCHAR" property="iotId"/>
            <result column="device_name" jdbcType="VARCHAR" property="deviceName"/>
            <result column="product_key" jdbcType="VARCHAR" property="productKey"/>
            <result column="product_name" jdbcType="BIT" property="productName"/>
        </collection>
    </resultMap>

    <select id="getRoomsWithDeviceByFloorId" resultMap="RoomsWithDeviceResult">
        select br.*,
               bb.id            as bed_id,
               bb.bed_number,
               bb.bed_status,
               bb.room_id,
               e.name           as ename,
               e.id             as eid,
               md.id            as r_did,
               md.iot_id,
               md.product_key   as product_key,
               md.device_name,
               md.product_name,
               md2.id           as b_did,
               md2.iot_id          b_iot_id,
               md2.product_key  as b_product_key,
               md2.device_name  as b_device_name,
               md2.product_name as b_product_name
        from base_room br
                 left join monitor_device md on md.physical_location_type = 1 and md.binding_location = br.id
                 left join base_bed bb on bb.room_id = br.id
                 left join monitor_device md2 on md2.physical_location_type = 2 and md2.binding_location = bb.id
                 left join elder e on e.bed_id = bb.id
        where (md.id is not null or md2.id is not null) and br.floor_id = #{floorId}
    </select>

测试

在绑定设备的床位或房间中,启动设备模拟上报数据

在页面中查询上报之后的数据

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

小程序数据展示

需求分析

我们先来看原型图

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在小程序端,绑定了老人之后,就可以查看老人的健康数据。

  • 在家人列表中,点击【健康数据】,跳转到【健康数据】,这里展示的是老人健康数据概览和当下最新指标数据
  • 当点击了某一个指标之后,可以查看某一个指标的历史数据,可以按天或周进行报表展示
  • 异常数据,后边讲完告警模块之后,再来完善异常数据展示

思路分析

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

当查看家人列表的时候,需要查询当前登录人的老人列表,同时老人的其他信息也要查询出来(房间、床位、设备)

当点击健康数据的时候,需要根据当前老人绑定的产品和设备名称到IOT平台查询物模型数据

当点击某项指标数据,比如心率,可以查看这个指标数据的报表信息,报表包含了两种

  • 按照天查询,其中心率、血压、血氧、体温的传输频次:每3小时测量传输一次,所以一天展示8次
  • 按周查询,周数据值显示近七天,天数据为当天数据的平均值

根据上面的思路分析,我们共需要开发4个接口,分别是:家人列表、查询健康数据、按天统计、按周统计

其中的家人列表,在代码中已经提供,我们重点实现剩下的3个

查询健康数据

根据产品ID和设备名称查询物模型数据,我们在前一天已经实现过一次

小程序的查询需要定义在客户管理的控制层,CustomerUserController中实现,如下代码:

    @Autowired
    private Client client;
    @Autowired
    private AliIoTConfigProperties aliIoTConfigProperties;
    
    //查询指定设备属性状态
    @PostMapping("/customer/user/QueryDevicePropertyStatus")
    public ResponseResult QueryDevicePropertyStatus(@RequestBody QueryDevicePropertyStatusRequest request) throws Exception {
        request.setIotInstanceId(aliIoTConfigProperties.getIotInstanceId());
        QueryDevicePropertyStatusResponse deviceStatus = client.queryDevicePropertyStatus(request);
        return ResponseResult.success(deviceStatus.getBody().getData());
    }

测试条件有两个:1)家属端登录人需要绑定带有智能手表设备的老人 2)启动模拟数据上报

目前前端只开发了心率的动态展示和报表展示,大家看效果主要看心率这个物模型

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

按天统计查询指标数据

sql–mapper-controller-service

sql

select DATE_FORMAT(alarm_time,'%H') as date_time, round(avg(data_value),0) as data_value  from monitor_device_data
where iot_id = 'ip3hTvIkH8Lmejlad8qkk0sjl0'
and function_id='HeartRate'
and alarm_time between '2024-08-25 00:00:00' and  '2024-08-25 23:59:59'
group by date_time

DeviceDataMapper

	//按日查询设备数据
    List<DeviceDataGraphVo> queryDeviceDataListByDay(String iotId, String functionId, LocalDateTime minCreateTime, LocalDateTime maxCreateTime);

DeviceDataMapper.xml

    <select id="queryDeviceDataListByDay" resultType="com.zzyl.vo.DeviceDataGraphVo">
        SELECT
            DATE_FORMAT(alarm_time, '%H:00') AS date_time,
            ROUND(AVG(data_value),0) AS data_value
        FROM
            monitor_device_data
        WHERE
            iot_id = #{iotId}
          AND function_id = #{functionId}
          AND alarm_time BETWEEN #{minCreateTime} and #{maxCreateTime}
        GROUP BY
            date_time
        order by date_time
    </select>

AppMemberController

    @Autowired
    private DeviceDataService deviceDataService;

	//按日查询设备数据
    @GetMapping("/customer/user/queryDeviceDataListByDay")
    public ResponseResult queryDeviceDataListByDay(String iotId, String functionId, Long startTime, Long endTime) {
        List<DeviceDataGraphVo> list = deviceDataService.queryDeviceDataListByDay(iotId, functionId, startTime, endTime);
        return ResponseResult.success(list);
    }

DeviceDataService

	//按日查询设备数据
    List<DeviceDataGraphVo> queryDeviceDataListByDay(String iotId, String functionId, Long startTime, Long endTime);

DeviceDataServiceImpl

    @Override
    public List<DeviceDataGraphVo> queryDeviceDataListByDay(String iotId, String functionId, Long startTime, Long endTime) {
        //按日聚合数据
        List<DeviceDataGraphVo> deviceDataGraphVoList = getBaseMapper().queryDeviceDataListByDay(iotId, functionId, LocalDateTimeUtil.of(startTime), LocalDateTimeUtil.of(endTime));
        //转换为map
        Map<String, Double> map = deviceDataGraphVoList.stream().collect(Collectors.toMap(DeviceDataGraphVo::getDateTime, DeviceDataGraphVo::getDataValue));
        
        //构建一个24小时的集合
        List<DeviceDataGraphVo> list = DeviceDataGraphVo.dayInstance(LocalDateTimeUtil.of(startTime));
        list.forEach(d -> {
            //从map获取值,如果为空,则给默认为0
            Double val = map.get(d.getDateTime()) == null ? 0.0 : map.get(d.getDateTime());
            d.setDataValue(val);
        });
        return list;
    }

测试:在小程序端点击心率,可以查看报表数据,也可以选择时间来进行检索,如下图

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

按周统计查询指标数据

AppMemberController

    //按周查询设备数据
    @GetMapping("/customer/user/queryDeviceDataListByWeek")
    public ResponseResult queryDeviceDataListByWeek(String iotId, String functionId, Long startTime, Long endTime) {
        List<DeviceDataGraphVo> list = deviceDataService.queryDeviceDataListByWeek(iotId, functionId, startTime, endTime);
        return ResponseResult.success(list);
    }

DeviceDataService

    //按周查询设备数据
    List<DeviceDataGraphVo> queryDeviceDataListByWeek(String iotId, String functionId, Long startTime, Long endTime);

DeviceDataServiceImpl

    @Override
    public List<DeviceDataGraphVo> queryDeviceDataListByWeek(String iotId, String functionId, Long startTime, Long endTime) {
        //按周聚合数据
        List<DeviceDataGraphVo> deviceDataGraphVoList = getBaseMapper().queryDeviceDataListByWeek(iotId, functionId, LocalDateTimeUtil.of(startTime), LocalDateTimeUtil.of(endTime));
        //转换为map
        Map<String, Double> map = deviceDataGraphVoList.stream().collect(Collectors.toMap(DeviceDataGraphVo::getDateTime, DeviceDataGraphVo::getDataValue));
        
        //构建一个7天的集合
        List<DeviceDataGraphVo> list = DeviceDataGraphVo.weekInstance(LocalDateTimeUtil.of(startTime));
        list.forEach(d -> {
            //获取数据,如果为空,则补为0
            Double dataValue = map.get(d.getDateTime()) == null ? 0.0 : map.get(d.getDateTime());
            d.setDataValue(dataValue);
        });
        return list;

DeviceDataMapper

    //按周查询设备数据
    List<DeviceDataGraphVo> queryDeviceDataListByWeek(String iotId, String functionId, LocalDateTime minCreateTime, LocalDateTime maxCreateTime);

DeviceDataMapper.xml

    <select id="queryDeviceDataListByWeek" resultType="com.zzyl.vo.DeviceDataGraphVo">
        SELECT
            DATE_FORMAT(alarm_time, '%m.%d') AS date_time,
            ROUND(AVG(data_value),0) AS data_value
        FROM
            monitor_device_data
        WHERE
            iot_id = #{iotId}
          AND function_id = #{functionId}
          AND alarm_time BETWEEN #{minCreateTime} and #{maxCreateTime}
        GROUP BY
            date_time
        order by date_time
    </select>

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

作业

Map<String, Double> map = deviceDataGraphVoList.stream().collect(Collectors.toMap(DeviceDataGraphVo::getDateTime, DeviceDataGraphVo::getDataValue));
    
    //构建一个7天的集合
    List<DeviceDataGraphVo> list = DeviceDataGraphVo.weekInstance(LocalDateTimeUtil.of(startTime));
    list.forEach(d -> {
        //获取数据,如果为空,则补为0
        Double dataValue = map.get(d.getDateTime()) == null ? 0.0 : map.get(d.getDateTime());
        d.setDataValue(dataValue);
    });
    return list;
### DeviceDataMapper

```java
    //按周查询设备数据
    List<DeviceDataGraphVo> queryDeviceDataListByWeek(String iotId, String functionId, LocalDateTime minCreateTime, LocalDateTime maxCreateTime);

DeviceDataMapper.xml

    <select id="queryDeviceDataListByWeek" resultType="com.zzyl.vo.DeviceDataGraphVo">
        SELECT
            DATE_FORMAT(alarm_time, '%m.%d') AS date_time,
            ROUND(AVG(data_value),0) AS data_value
        FROM
            monitor_device_data
        WHERE
            iot_id = #{iotId}
          AND function_id = #{functionId}
          AND alarm_time BETWEEN #{minCreateTime} and #{maxCreateTime}
        GROUP BY
            date_time
        order by date_time
    </select>

[外链图片转存中…(img-nLTHAHPo-1724850575368)]

作业

定时任务 每天凌晨一点 删除上报时间超过7天

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

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

相关文章

50.x86游戏实战-XXX副本内瞬图

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 本次游戏没法给 内容参考于&#xff1a;微尘网络安全 工具下载&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1rEEJnt85npn7N38Ai0_F2Q?pwd6tw3 提…

《前端攻城狮 · Snowflake 雪花算法》

&#x1f4e2; 大家好&#xff0c;我是 【战神刘玉栋】&#xff0c;有10多年的研发经验&#xff0c;致力于前后端技术栈的知识沉淀和传播。 &#x1f497; &#x1f33b; CSDN入驻不久&#xff0c;希望大家多多支持&#xff0c;后续会继续提升文章质量&#xff0c;绝不滥竽充数…

Ruff :是一个用Rust编写的极快的 Python linter 和代码格式化程序

安装 pip install ruff 语法检查 要对代码运行 linter&#xff0c;我们使用 ruff check . 如果你想在单个文件上运行它&#xff0c;请这样做 ruff check <filename.py> 总共有 415 个错误。其中 33 个可以修复&#xff01; 为了修复它们&#xff0c;我们使用标志--f…

深度学习实用方法 - 选择超参数篇

序言 在深度学习的浩瀚领域中&#xff0c;超参数的选择无疑是通往卓越模型性能的一把关键钥匙。超参数&#xff0c;作为训练前设定的、用于控制学习过程而非通过学习自动获得的参数&#xff0c;如学习率、批量大小、网络层数及节点数等&#xff0c;直接影响着模型的收敛速度、…

LLMOP 面向LLM编程,让你的接口和代码对LLM更友好!

LLM Oriented Programming ApiHug Lint 检查报告 - ApiHugApihug quality lint tools to check api design violationshttps://apihug.com/zhCN-docs/tool/apihug-lint ​LLMOP 面向LLM编程&#xff08;LLMOP&#xff09;代表了我们构思和设计API的范式转变。该方法旨在创建不…

TRIZ理论在传感技术创新中的应用优势

传感技术作为连接物理世界与数字世界的桥梁&#xff0c;正以前所未有的速度推动着社会进步与产业升级。而TRIZ&#xff08;发明问题解决理论&#xff09;这一强大的创新方法论&#xff0c;也开始悄然在传感技术领域绽放异彩&#xff0c;为创新者提供了前所未有的设计灵感与解决…

CCF CSP题解:因子化简(202312-2)

链接和思路 OJ链接&#xff1a;传送门。 问题重述 本题基于一个基本事实&#xff0c;即任何一个大整数 n n n都可以唯一地分解为如下形式 n p 1 t 1 p 2 t 2 ⋯ p m t m n p_1^{t_1} \times p_2^{t_2} \times \cdots \times p_m^{t_m} np1t1​​p2t2​​⋯pmtm​​其中…

软件测试 | 测试用例Ⅱ

基于需求的设计方法 测试人员接到需求后&#xff0c;要对需求进行分析和验证&#xff0c;从合理的需求中进一步分析细化需求&#xff0c;从细化的需求中找出测试点&#xff0c;根据这些测试点再去设计测试用例。 上面就是根据需求文档先设计出来的初步的测试用例&#xff0c;而…

数据库表太多,如何快速定位到需要字段

MySQL数据库表太多&#xff0c;如何快速定位到需要字段 问题&#xff1a; 数据库的表太多&#xff0c;自己只通过后端代码&#xff0c;知道数据字段名。现在想搜索数据库中所有表&#xff0c;查到对应字段&#xff0c;实现报表开发。 一、方案 在MySQL数据库中&#xff0c;如…

【web网页制作】中国传统文化书法主题html网页制作开发div+css(6页面附效果源码)

HTMLCSS传统文化主题书法网页制作 &#x1f354;涉及知识&#x1f964;写在前面&#x1f367;一、网页主题&#x1f333;二、网页效果菜单切换效果PageA、整体页Page1、主页Page2、行书页Page3、楷书页Page4、隶书页Page5、篆书页Page6、草书页 &#x1f40b;三、网页架构与技术…

微前端集成优化:让所有子应用体积更小,加载更快!

简介 随着前端的日益发展&#xff0c;微前端架构越来越受到青睐。它通过将前端应用拆分为多个独立的子应用&#xff0c;每个子应用可以独立开发、部署和运行&#xff0c;从而提升了开发效率和团队协作。目前主流的微前端方案应该是qiankun了。 以笔者公司为例&#xff0c;采用…

AI在医学领域:基础模型和视觉-语言模型在计算病理学应用概述

近年来&#xff0c;人工智能&#xff08;AI&#xff09;基于方法在计算病理学&#xff08;CPath&#xff09;领域中的应用迅速增长&#xff0c;这主要得益于数字幻灯片扫描仪的广泛采用。因此&#xff0c;大规模的全幻灯片图像&#xff08;Whole Slide Images, WSIs&#xff09…

Linux内存管理(1)——MMU内存管理单元及Buddy算法详解

文章目录 1.内存管理单元&#xff08;MMU&#xff09;1.1 MMU的作用1.2 MMU相关概念1.3 ARM处理器访问内存过程 2.内存优化之buddy伙伴算法2.1 背景2.2 优缺点2.2 原理 最近复习Linux内存管理部分&#xff0c;简单将自己的理解记录一下 内存&#xff1a;包括随机存储器&#xf…

委托 by 操作符

属性委托给属性 使用 by:: 操作符&#xff0c;代表调用系统内置的方法。 读&#xff1a;getVaule 写&#xff1a;setValue 共享同一个数据。 /*** 属性委托给属性* by:: 系统内置的方法&#xff0c;非自定义的* 共享同一个数据*/ var num:Float 9999.0f/*** newNum属性委托…

黑神话悟空-画质提升+帧数提升+超宽屏+写实画质补丁+无光追也可拥有的超强画质

超真实画质提升&#xff01;&#xff01;&#xff01; 下载地址&#xff1a;https://pan.quark.cn/s/529202fbbbef

Python | Leetcode Python题解之第381题O(1)时间插入、删除和获取随机元素-允许重复

题目&#xff1a; 题解&#xff1a; import random class RandomizedCollection:def __init__(self):"""Initialize your data structure here."""self.table {}self.ns []def insert(self, val: int) -> bool:"""Inserts …

WPF 手撸插件 七 日志记录(二)

1、本文使用Serilog进行记录日志&#xff0c;目前想只用log4net进行日志记录&#xff0c;但是Serilog有想学习一下&#xff0c;所有这里使用控制台项目来学习一下Serilog。我使用的还是.Net Framework 4.5. 2、NuGet 安装Serilog 2.12.0、Serilog.Sinks.Console 4.1.0&#xf…

智能学习辅助系统——后端部分

目录 前言 一、准备工作 1.需求&环境搭建 1.1需求说明 1.2环境搭建 2.开发规范 2.1 开发规范-REST 2.2 开发规范-统一响应结果 3.开发流程 二、部门管理 1.查询部门 &#xff08;1&#xff09;原型和需求 &#xff08;2&#xff09;接口文档 &#xff08;3&…

阿里巴巴1688 阿里228滑块 网站分析(不涉及逆向)

声明: 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01; 有相关问题请第一时间头像私信联系我…

R语言统计分析——回归模型深层次分析

参考资料&#xff1a;R语言实战【第2版】 本文主要讨论回归模型的泛化能力和变量相对重要性的方法。 1、交叉验证 从定义上看&#xff0c;回归方法就是从一堆数据中获取最优模型参数。对于OLS&#xff08;普通最小二乘&#xff09;回归&#xff0c;通过使得预测误差&#xff0…