用 Java 实现 Syslog 功能

news2024/11/24 13:24:53

1、业务场景

用一个 Spring Boot 的项目去实现对管控设备的监控、日志收集等。同时需要将接收到的日志进行入库,每天存一张表,如device_log_20231026…


2、Syslog客户端(接收日志的服务器,即运行Java程序的服务器)

2.1 导包

Syslog需要用到的jar包:

<dependency>
    <groupId>org.graylog2</groupId>
    <artifactId>syslog4j</artifactId>
    <version>0.9.61</version>
</dependency>

Mybatis Plus 需要的jar包:(因为笔者此处使用MP进行数据库的交互)

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.2</version>
</dependency>

2.2 Syslog 接收端

数据库实体类 DeviceSyslog.java

package com.haitai.domain.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;

import java.io.Serializable;
import java.util.Date;

/**
 * 设备syslog
 *
 * @Author xincheng.du
 * @Date 2023/10/24 13:50
 */
@Data
@TableName("device_log")
public class DeviceSyslog implements Serializable {

    /**
     * 主键id
     */
    @TableId(type = IdType.AUTO)
    private Long id;

    /**
     * 设备ip
     */
    @TableField("ip")
    private String ip;

    /**
     * 日志信息
     */
    @TableField("message")
    private String message;

    /**
     * 日志级别
     */
    @TableField("level")
    private Integer level;

    /**
     * 日志时间
     */
    @TableField("log_time")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date logTime;

    /**
     * 入库时间
     */
    @TableField("create_time")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date createTime;

}

DeviceSyslogMapper.java

package com.haitai.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.haitai.domain.entity.DeviceSyslog;

/**
 * 设备syslog Mapper
 *
 * @Author xincheng.du
 * @Date 2023/5/25 14:10
 */
public interface DeviceSyslogMapper extends BaseMapper<DeviceSyslog> {
}

SyslogUdpUtil.java
此处的 IP 和 Port 填写你运行Java程序的 IP 和你需要开放的端口。因为我的接收端和发送端在同一个局域网,所以我此处是 192.168.110.28 ,端口我此处设置为 3780.

package com.haitai.syslog;

import com.haitai.service.DeviceSyslogService;
import lombok.extern.slf4j.Slf4j;
import org.graylog2.syslog4j.SyslogConstants;
import org.graylog2.syslog4j.server.SyslogServer;
import org.graylog2.syslog4j.server.SyslogServerEventIF;
import org.graylog2.syslog4j.server.SyslogServerIF;
import org.graylog2.syslog4j.server.SyslogServerSessionlessEventHandlerIF;
import org.graylog2.syslog4j.server.impl.net.udp.UDPNetSyslogServerConfig;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.net.SocketAddress;
import java.util.Objects;

/**
 * Syslog 接收端
 *
 * @Author xincheng.du
 * @Date 2023/10/25 15:13
 */
@Slf4j
@Component
public class SyslogUdpUtil {
	
    @Value("${syslog.udp.ip}")
    private String ip;

    @Value("${syslog.udp.port}")
    private int port;

    @Resource
    private DeviceSyslogService deviceSyslogService;


    /**
     * 项目启动时,启动一个Syslog服务端监听指定端口,接收设备的日志消息
     */
    @PostConstruct
    public void init() {
        new Thread(() -> {
            log.info("服务端({})监听设备syslog日志", ip + ":" + port);
            // 服务端
            SyslogServerIF serverInstance = SyslogServer.getInstance(SyslogConstants.UDP);
            UDPNetSyslogServerConfig serverConfig = (UDPNetSyslogServerConfig) serverInstance.getConfig();
            serverConfig.setHost(ip);
            serverConfig.setPort(port);
            // 防止数据过大被截取导致不完整
            serverConfig.setMaxMessageSize(SyslogConstants.SYSLOG_BUFFER_SIZE * 10);
            serverConfig.addEventHandler(new SyslogServerSessionlessEventHandlerIF() {
                @Override
                public void event(SyslogServerIF syslogServerIF, SocketAddress socketAddress, SyslogServerEventIF syslogServerEventIF) {
                    if (Objects.nonNull(syslogServerEventIF)) {
                        deviceSyslogService.saveSyslog(socketAddress, syslogServerEventIF);
                    }
                }

                @Override
                public void exception(SyslogServerIF syslogServerIF, SocketAddress socketAddress, Exception e) {}

                @Override
                public void initialize(SyslogServerIF syslogServerIF) {}

                @Override
                public void destroy(SyslogServerIF syslogServerIF) {}
            });
            // 启动服务端
            serverInstance.run();
        }).start();
    }

}

DeviceSyslogService.java

package com.haitai.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.haitai.domain.entity.DeviceSyslog;
import com.haitai.domain.param.syslog.DeviceSyslogQueryParam;
import com.haitai.domain.vo.TableDataInfo;
import org.graylog2.syslog4j.server.SyslogServerEventIF;

import java.net.SocketAddress;

/**
 * 设备syslog 相关接口
 *
 * @Author xincheng.du
 * @Date 2023/10/25 11:06
 */
public interface DeviceSyslogService extends IService<DeviceSyslog> {

    /**
     * 日志分页
     *
     * @param param 参数
     * @return      分页列表
     */
    TableDataInfo pageSyslog(DeviceSyslogQueryParam param);


    /**
     * 日志入库
     *
     * @param socketAddress         socket地址
     * @param syslogServerEventIF   syslog事件拓展接口
     */
    void saveSyslog(SocketAddress socketAddress, SyslogServerEventIF syslogServerEventIF);


    /**
     * 创建日志表
     *
     * @param tableName 表名
     */
    void createTable(String tableName);


    /**
     * 判断表是否存在
     *
     * @param tableName 表名
     */
    boolean existTable(String tableName);

}

DeviceSyslogServiceImpl.java
其中 Constants.DEVICE_LOG_BASE_TABLE_NAME = “device_log”

package com.haitai.service.impl;

import cn.hutool.core.date.DateUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.haitai.config.mybatis.RequestDataHelper;
import com.haitai.constant.Constants;
import com.haitai.constant.HttpStatus;
import com.haitai.domain.entity.DeviceSyslog;
import com.haitai.domain.param.syslog.DeviceSyslogQueryParam;
import com.haitai.domain.vo.TableDataInfo;
import com.haitai.mapper.DeviceSyslogMapper;
import com.haitai.service.DeviceSyslogService;
import com.haitai.utils.LocalCache;
import lombok.extern.slf4j.Slf4j;
import org.graylog2.syslog4j.server.SyslogServerEventIF;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.sql.*;
import java.util.Date;
import java.util.HashMap;
import java.util.Objects;

/**
 * 设备syslog 相关逻辑处理
 *
 * @Author xincheng.du
 * @Date 2023/10/24 14:18
 */
@Slf4j
@Service
public class DeviceSyslogServiceImpl extends ServiceImpl<DeviceSyslogMapper, DeviceSyslog> implements DeviceSyslogService {

    /**
     * 数据库用户名
     */
    @Value("${spring.datasource.druid.master.username}")
    private String username;

    /**
     * 数据库密码
     */
    @Value("${spring.datasource.druid.master.password}")
    private String password;

    /**
     * 数据库url
     */
    @Value("${spring.datasource.druid.master.url}")
    private String url;

    @Override
    public TableDataInfo pageSyslog(DeviceSyslogQueryParam param) {
        HashMap<String, Object> map = new HashMap<>();
        map.put("date", Objects.nonNull(param.getLogTime()) ? param.getLogTime() : new Date());
        RequestDataHelper.setRequestData(map);
        LambdaQueryWrapper<DeviceSyslog> deviceLambdaQueryWrapper = new LambdaQueryWrapper<>();
        deviceLambdaQueryWrapper
                .eq(Objects.nonNull(param.getId()), DeviceSyslog::getId, param.getId())
                .like(Objects.nonNull(param.getIp()), DeviceSyslog::getIp, param.getIp())
                .like(Objects.nonNull(param.getMessage()), DeviceSyslog::getMessage, param.getMessage())
                .eq(Objects.nonNull(param.getLevel()), DeviceSyslog::getLevel, param.getLevel())
                // 根据创建时间倒序排列
                .orderByDesc(DeviceSyslog::getCreateTime);
        Page<DeviceSyslog> page = new Page<>(param.getPage().longValue(), param.getLimit().longValue());
        Page<DeviceSyslog> result = baseMapper.selectPage(page, deviceLambdaQueryWrapper);
        TableDataInfo rspData = new TableDataInfo();
        rspData.setCode(HttpStatus.SUCCESS);
        rspData.setMsg("查询成功");
        rspData.setRows(result.getRecords());
        rspData.setTotal(result.getTotal());
        return rspData;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void saveSyslog(SocketAddress socketAddress, SyslogServerEventIF syslogServerEventIF) {
        java.util.Date now = new Date();
        String yyyyMMdd = DateUtil.format(now, "yyyyMMdd");
        String tableName = Constants.DEVICE_LOG_BASE_TABLE_NAME + "_" + yyyyMMdd;
        // 表不存在先创建
        if (!existTable(tableName)) {
            createTable(tableName);
        }
        DeviceSyslog deviceSyslog = new DeviceSyslog();
        if (socketAddress instanceof InetSocketAddress) {
            InetSocketAddress inetSocketAddress = (InetSocketAddress) socketAddress;
            deviceSyslog.setIp(inetSocketAddress.getHostName());
        }
        deviceSyslog.setLevel(syslogServerEventIF.getLevel());
        deviceSyslog.setMessage(syslogServerEventIF.getMessage());
        deviceSyslog.setLogTime(syslogServerEventIF.getDate());
        deviceSyslog.setCreateTime(now);
        save(deviceSyslog);
    }


    @Override
    public void createTable(String tableName) {
        try (Connection connection = DriverManager.getConnection(url, username, password);
             Statement statement = connection.createStatement()) {
            String createTableSQL = "CREATE TABLE " + tableName + " (" +
                    "  `id` bigint NOT NULL AUTO_INCREMENT," +
                    "  `ip` varchar(50) DEFAULT NULL," +
                    "  `message` varchar(1000) DEFAULT NULL," +
                    "  `level` int DEFAULT NULL," +
                    "  `log_time` datetime DEFAULT NULL," +
                    "  `create_time` datetime DEFAULT NULL," +
                    "  PRIMARY KEY (`id`)" +
                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;";
            statement.execute(createTableSQL);
            log.info("系统日志表创建成功,表名:{}", tableName);
            LocalCache.set(tableName, Boolean.TRUE);
        } catch (Exception e) {
            LocalCache.set(tableName, Boolean.FALSE);
            log.error("系统日志表创建失败,表名:{}", tableName);
            e.printStackTrace();
        }
    }


    @Override
    public boolean existTable(String tableName) {
        Object o = LocalCache.get(tableName);
        if (Objects.nonNull(o)) {
            return (Boolean) o;
        }
        try (Connection connection = DriverManager.getConnection(url, username, password)) {
            DatabaseMetaData databaseMetaData = connection.getMetaData();
            ResultSet resultSet = databaseMetaData.getTables(null, null, tableName, null);
            if (resultSet.next()) {
                LocalCache.set(tableName, Boolean.TRUE);
                return true;
            } else {
                LocalCache.set(tableName, Boolean.FALSE);
                return false;
            }
        } catch (Exception e) {
            log.error("判断表名:{} 是否存在出错,原因:{}", tableName, e.getMessage());
            e.printStackTrace();
            return false;
        }
    }

}

缓存工具类 LocalCache.java

package com.haitai.utils;

import cn.hutool.cache.CacheUtil;
import cn.hutool.cache.impl.TimedCache;
import cn.hutool.core.date.DateUnit;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * 本地缓存工具类
 *
 * @Author xincheng.du
 * @Date 2023/7/4 13:52
 */
@SuppressWarnings("unused")
public class LocalCache {

    private LocalCache() {}

    /**
     * 默认缓存时长
     */
    private static final long DEFAULT_TIMEOUT = 5 *DateUnit.MINUTE.getMillis();

    /**
     * 默认清理间隔时间
     */
    private static final long CLEAN_TIMEOUT = 5 * DateUnit.MINUTE.getMillis();

    /**
     * 缓存对象
     */
    private static final TimedCache<String, Object> TIMED_CACHE = CacheUtil.newTimedCache(DEFAULT_TIMEOUT);

    static {
        // 启动定时任务
        TIMED_CACHE.schedulePrune(CLEAN_TIMEOUT);
    }

    /**
     * 存值
     *
     * @param key   键
     * @param value 值
     */
    public static void set(String key, Object value) {
        TIMED_CACHE.put(key, value);
    }

    /**
     * 设置缓存并设置过期时间
     *
     * @param key       缓存key
     * @param value     缓存值
     * @param expire    过期时间,单位:ms
     */
    public static void set(String key, Object value, long expire) {
        TIMED_CACHE.put(key, value, expire);
    }

    /**
     * 设置缓存并设置过期时间(自定义单位)
     *
     * @param key       缓存key
     * @param value     缓存值
     * @param expire    过期时间,单位:ms
     * @param unit      过期时间单位,默认毫秒
     */
    public static void set(String key, Object value, long expire, TimeUnit unit) {
        if (Objects.nonNull(unit)) {
            switch (unit) {
                case NANOSECONDS:
                    TIMED_CACHE.put(key, value, expire / 1000000);
                    break;
                case MICROSECONDS:
                    TIMED_CACHE.put(key, value, expire / 1000);
                    break;
                case SECONDS:
                    TIMED_CACHE.put(key, value, expire * DateUnit.SECOND.getMillis());
                    break;
                case MINUTES:
                    TIMED_CACHE.put(key, value, expire * DateUnit.MINUTE.getMillis());
                    break;
                case HOURS:
                    TIMED_CACHE.put(key, value, expire * DateUnit.HOUR.getMillis());
                    break;
                case DAYS:
                    TIMED_CACHE.put(key, value, expire * DateUnit.DAY.getMillis());
                    break;
                case MILLISECONDS:
                default:
                    TIMED_CACHE.put(key, value, expire);
                    break;
            }
        } else {
            TIMED_CACHE.put(key, value, expire);
        }
    }

    /**
     * 获取并重新计算过期时间
     *
     * @param key   键
     * @return      值
     */
    public static Object getWithUpdateLastAccess(String key) {
        return TIMED_CACHE.get(key);
    }

    /**
     * 取值
     *
     * @param key   键
     * @return      值
     */
    public static Object get(String key) {
        return TIMED_CACHE.get(key, false);
    }

    /**
     * 获取所有缓存的key
     *
     * @return  key集合
     */
    public static Set<String> keySet() {
        return TIMED_CACHE.keySet();
    }

    /**
     * 单个删除
     *
     * @param key   键
     */
    public static void remove(String key) {
        TIMED_CACHE.remove(key);
    }

    /**
     * 批量删除
     *
     * @param keys  键集合
     */
    public static void removeAll(List<String> keys) {
        for (String key : keys) {
            TIMED_CACHE.remove(key);
        }
    }

    /**
     * 获取所有key
     *
     * @return  key集合
     */
    public static Set<String> getAllKeys() {
        return TIMED_CACHE.keySet();
    }

    /**
     * 获取包含关键词的所有key
     *
     * @return  key集合
     */
    public static List<String> getContainsKeys(String keyword) {
        Set<String> allKeys = getAllKeys();
        List<String> list = new ArrayList<>();
        allKeys.forEach(item -> {
            if (item.contains(keyword)) {
                list.add(item);
            }
        });
        return list;
    }

    /**
     * 清空缓存
     */
    public static void clear() {
        TIMED_CACHE.clear();
    }

}

动态表名拦截器 DynamicDateTableNameHandler.java:

package com.haitai.config.mybatis;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import com.baomidou.mybatisplus.extension.plugins.handler.TableNameHandler;
import com.haitai.constant.Constants;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.StringUtils;

import java.util.Date;
import java.util.Map;
import java.util.Objects;

/**
 * 动态日期表名替换处理器
 *
 * @Author xincheng.du
 * @Date 2023/10/25 10:25
 */
@NoArgsConstructor
public class DynamicDateTableNameHandler implements TableNameHandler {

    @Override
    public String dynamicTableName(String sql, String tableName) {
        if (StringUtils.isNotBlank(tableName) && Constants.DEVICE_LOG_BASE_TABLE_NAME.equals(tableName)) {
            Date date;
            Map<String, Object> paramMap = RequestDataHelper.getRequestData();
            if (CollUtil.isNotEmpty(paramMap) && Objects.nonNull(paramMap.get("date"))) {
                date = (Date) paramMap.get("date");
            } else {
                // 为空日期取当天
                date = new Date();
            }
            String tableNameSuffix = "_" + DateUtil.format(date, "yyyyMMdd");
            return Constants.DEVICE_LOG_BASE_TABLE_NAME + tableNameSuffix;
        }
        return tableName;
    }

}

将动态表名拦截器添加到MP的拦截器链中 MybatisPlusConfig.java :

package com.haitai.config.mybatis;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.DynamicTableNameInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author dideng.zhang
 * @version 1.0
 * @date 2023/5/19 14:29
 */
@Configuration
public class MybatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
        paginationInnerInterceptor.setDbType(DbType.MYSQL);
        interceptor.addInnerInterceptor(paginationInnerInterceptor);
        // 配置动态表名拦截器
        DynamicTableNameInnerInterceptor dynamicTableNameInnerInterceptor = new DynamicTableNameInnerInterceptor();
        DynamicDateTableNameHandler dynamicDateTableNameHandler = new DynamicDateTableNameHandler();
        dynamicTableNameInnerInterceptor.setTableNameHandler(dynamicDateTableNameHandler);
        interceptor.addInnerInterceptor(dynamicTableNameInnerInterceptor);
        return interceptor;
    }
}

请求参数传递辅助类

package com.haitai.config.mybatis;

import cn.hutool.core.collection.CollUtil;

import java.util.Map;

/**
 * 请求参数传递辅助类
 */
public class RequestDataHelper {

    private RequestDataHelper() {}

    /**
     * 请求参数存取
     */
    private static final ThreadLocal<Map<String, Object>> REQUEST_DATA = new ThreadLocal<>();

    /**
     * 设置请求参数
     *
     * @param requestData 请求参数 MAP 对象
     */
    public static void setRequestData(Map<String, Object> requestData) {
        REQUEST_DATA.set(requestData);
    }

    /**
     * 获取请求参数
     *
     * @param param 请求参数
     * @return 请求参数 MAP 对象
     */
    public static <T> T getRequestData(String param) {
        Map<String, Object> dataMap = getRequestData();
        if (CollUtil.isNotEmpty(dataMap)) {
            return (T) dataMap.get(param);
        }
        return null;
    }

    /**
     * 获取请求参数
     *
     * @return 请求参数 MAP 对象
     */
    public static Map<String, Object> getRequestData() {
        return REQUEST_DATA.get();
    }
}

3、Syslog服务端(发送日志的服务器)

3.1 rsyslog.conf配置修改

(1)打开服务器的 /etc/rsyslog.conf 配置文件:

vi /etc/rsyslog.conf

(2)在文件末尾添加:

*.* @192.168.110.28:3780

此处的 ip 和 port 和你上述代码中的对应即可,同时还要保证这两台设备能相互访问。这里的一个“@”代表使用UDP通信,两个“@”代表使用TCP通信。
(3)保存并关闭文件,先按 esc,然后输入:

:wq

(4)防火墙开放3780端口:

firewall-cmd --permanent --add-port=3780/udp

(5)重置防火墙

firewall-cmd --reload

(6)重启 syslog 服务:

systemctl restart rsyslog

4. 测试

启动 Java 项目,过一会儿,可以看到我们的表已经创建成功了,同时已经有数据入库。

在这里插入图片描述

在这里插入图片描述

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

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

相关文章

JavaScript 基础 - 第4天

理解封装的意义&#xff0c;能够通过函数的声明实现逻辑的封装&#xff0c;知道对象数据类型的特征&#xff0c;结合数学对象实现简单计算功能。 理解函数的封装的特征掌握函数声明的语法理解什么是函数的返回值知道并能使用常见的内置函数 函数 理解函数的封装特性&#xff0c…

软件测试/测试开发丨UbuntuServer环境准备

点此获取更多相关资料 前提 现有设备是一套 i54090 的组合&#xff0c;安装了 Ubuntu 22.04.3 LTS Server 版本&#xff0c;后文的安装步骤都是基于这套系统和配置进行操作。 系统准备 查看是否安装了 gcc 命令行中执行 gcc -v 正常输入如图效果的&#xff0c;说明已经成功…

kubeadm部署kubernetes1.28

k8s在1.24版本以后删除了内置dockershim插件&#xff0c;原生不再支持docker运行时&#xff0c;需要使用第三方cri接口cri-docker https://github.com/Mirantis/cri-dockerd.git 安装前&#xff0c;需要先升级systemd和主机内核&#xff0c;本操作文档安装的是最新的版本kube…

微信小程序渲染的富文本里面除了img标签外什么都没有,该如何设置img的大小

微信小程序富文本渲染&#xff1a; <rich-text nodes"{{content}}"style"{{style}}" ></rich-text> content是接口得到的值 let cont object.contentlet a cont.replace(/<img/gi,<img style"max-width:94%;height:auto;margi…

大厂面试题-什么是IO的多路复用机制?

IO多路复用机制&#xff0c;核心思想是让单个线程去监视多个连接&#xff0c;一旦某个连接就绪&#xff0c;也就是触发了读/写事件。 就通知应用程序&#xff0c;去获取这个就绪的连接进行读写操作。 也就是在应用程序里面可以使用单个线程同时处理多个客户端连接&#xff0c…

四川竹哲电子商务有限公司服务怎么样?

随着抖音电商的日益崛起&#xff0c;越来越多的商家开始关注这个充满无限商机的平台。四川竹哲电子商务有限公司作为一家专业的抖音电商服务公司&#xff0c;凭借其丰富的经验和优秀的服务&#xff0c;成为了众多商家在抖音电商领域中的重要合作伙伴。 一、专业实力 作为一家专…

同为科技(TOWE)国标10A电瓶车专用智能定时桌面PDU插线板

电动车作为现在国内保有量较高的一种交通工具&#xff0c;因其价格亲民、环保便捷、使用成本低等原因受到广大人民群众的欢迎。然而&#xff0c;电瓶车充电问题长期以来是广受关注的社会性话题&#xff0c;过充短路爆充引起火灾事故的新闻时有发生&#xff0c;80%的电动车火灾都…

将流程作为战略丨看看流程智能在食品巨头“百事”的应用场景

“更快、更好、更强”是百事可乐的使命。百事公司GPEX&#xff08;全球流程卓越组织&#xff09;的使命则是&#xff1a;改造、数字化和加强百事公司的端到端流程&#xff0c;使每个流程都具备相关的可衡量价值。 目前&#xff0c;百事公司正在使用预测分析、人工智能、机器人…

电脑防泄密软件有什么作用,安企神文件加密软件功能详解

电脑防泄密软件有什么作用&#xff0c;安企神文件加密软件功能详解 安企神数据防泄密系统下载使用 什么是加密软件 用于将数据或信息转化为加密形式&#xff0c;从而限制数据在未授权的访问或窃取。加密是一种安全措施&#xff0c;通过将数据转化为密文&#xff0c;只有授权…

Dubbo中的负载均衡算法之平滑加权轮询算法源码解析

Dubbo中的负载均衡算法之一致性哈希算法 哈希算法 假设这样一个场景&#xff0c;我们申请一台服务器来缓存100万的数据&#xff0c;这个时候是单机环境&#xff0c;所有的请求都命中到这台服务器。后来业务量上涨&#xff0c;我们的数据也从100万上升到了300万&#xff0c;原…

激光雷达和人工智能

几十年来&#xff0c;激光雷达一直是许多行业中非常有用的工具&#xff0c;但直到最近&#xff0c;随着人工智能&#xff08;AI&#xff09;解决方案的引入&#xff0c;我们才开始认识到它的真正潜力。激光雷达&#xff0c;又称光探测和测距&#xff0c;是一种遥感技术。它利用…

性能测试知多少----性能测试分类之我见

从这一篇开始&#xff0c;虫师向性能方面发力。翻看自己的博客&#xff0c;最早的时候热衷于jmeter&#xff0c;于是写了几篇图文并茂的文章&#xff08;其实&#xff0c;主要是操作截图加文字描述&#xff09;&#xff0c;之后&#xff0c;由于看到好多朋友关于性能的知识什么…

【RtpSeqNumOnlyRefFinder】webrtc m98: ManageFrameInternal 的帧决策过程分析

Jitterbuffer(FrameBuffer)需要组帧以后GOP内的参考关系 JeffreyLau 大神分析 了组帧原理而参考关系(RtpFrameReferenceFinder)的生成伴随了帧决策 FrameDecisionFrameDecision 影响力 帧的缓存。调用 OnAssembledFrame 传递已经拿到的RtpFrameObject 那么,RtpFrameObject…

出海营销必看:如何避免邮件被识别为垃圾邮件

对于现在的商业环境来说&#xff0c;邮件通信已经成为企业与客户、合作伙伴以及员工之间沟通和交流的重要方式。然而&#xff0c;尽管企业发送的邮件通常都是正常的、合规的&#xff0c;有时候却会被系统错误地标记为营销邮件。这个情况给企业带来了很多困扰。 如果企业的邮件…

软件测试工作的价值体现在哪里呢?

QA 的绩效如何考核&#xff1f;测试工作的价值体现在哪里&#xff1f; 这两个是大家比较关注&#xff0c;也是比较难的问题。确实&#xff0c;业务分析人员会产出需求文档&#xff0c;开发人员会产出软件&#xff0c;而 QA 的工作则很难定义明确的产出&#xff0c;很难被量化。…

服务号改订阅号怎么弄

服务号和订阅号有什么区别&#xff1f;服务号转为订阅号有哪些作用&#xff1f;很多小伙伴想把服务号改为订阅号&#xff0c;但是不知道改了之后具体有什么作用&#xff0c;今天跟大家具体讲解一下。首先我们知道服务号一个月只能发四次文章&#xff0c;但是订阅号每天都可以发…

Java通过cellstyle属性设置Excel单元格常用样式全面总结

最近做了一个导出Excel的功能&#xff0c;导出是个常规导出&#xff0c;但是拿来模板一看&#xff0c;有一些单元格的样式设置&#xff0c;包括合并&#xff0c;背景色&#xff0c;字体等等&#xff0c;毕竟不是常用的东西&#xff0c;需要查阅资料完成&#xff0c;但是搜遍全网…

小程序day01

简介: 小程序项目的基本结构 页面的组成部分 一个页面对应一个文件夹&#xff0c;所有有关的内容都放在一起。 JSON配置文件 2.app.json文件 3.project.config.json文件 4.sitemap.json文件 5.页面的.json配置文件 6. 新建小程序页面 7.修改项目首页 小程序代码构成 小程序的宿…

java 数据结构 ArrayList源码底层 LinkedList 底层源码 迭代器底层

文章目录 数据结构总结ArrayList源码底层LinkedList底层源码 迭代器底层 数据结构 对于数据结构我这边只告诉你右边框框里的 栈的特点:后进先出,先进后出,入栈也成为压栈,出栈也成为弹栈 栈就像一个弹夹 队列先进先出后进后出 队列像排队 链表查询满 但是增删快(相对于数组而…