SpringBoot MySQL BinLog 监听数据变化(多库多表)

news2024/12/1 0:41:17

开始

1:引入mysql-binlog-connector-java.jar

    <!-- binlog -->
        <dependency>
            <groupId>com.zendesk</groupId>
            <artifactId>mysql-binlog-connector-java</artifactId>
            <version>0.27.1</version>
        </dependency>
        <!-- guava -->
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>31.1-jre</version>
        </dependency>

2:配置文件

#用户必须要有权限
binlog:
  # 服务器地址
  host: localhost
  port: 3306
  username: root
  password: 123456
  # 监听数据库与表,隔开,格式[库.表,,,]
  dbTable: 库.表,库1.表1,库1.表2
  serverId: 1

:1:mysql8.0之后binlog是默认开启的

        2:需要一个mysql查看binlog的权限用户

获取配置文件参数 BinLogConfig

/**
 * @Description binlog配置
 * @Author WangKun
 * @Date 2024/8/8 15:01
 * @Version
 */
@Data
@Component
public class BinLogConfig {

    /**
     * mysql服务地址
     **/
    @Value("${binlog.host}")
    private String host;

    /**
     * mysql数据库端口号
     **/
    @Value("${binlog.port}")
    private int port;

    /**
     * 查看BinLog权限用户名
     **/
    @Value("${binlog.username}")
    private String username;

    /**
     * 查看BinLog权限密码
     **/
    @Value("${binlog.password}")
    private String password;

    /**
     * 库表
     **/
    @Value("${binlog.dbTable}")
    private String dbTable;

    /**
     * 服务标识
     **/
    @Value("${binlog.serverId}")
    private Integer serverId;

    /**
     * 获取所有库表,并转化
     **/
    private List<String> tables;

    public List<String> getTables() {
        if (StringUtils.hasText(dbTable)){
            tables = Arrays.asList(dbTable.split(BinLogUtils.COMMA));
        }
        return tables;
    }

}

BinLog与字段类型实体对象

/**
 * @Description Binlog实体对象
 * @Author WangKun
 * @Date 2024/8/8 16:56
 * @Version
 */
@Data
public class BinLog implements Serializable {

    /**
     * 库表
     **/
    private String dbTable;
    /**
     * 事件类型
     **/
    private EventType eventType;
    /**
     * 存储字段-之前的值
     **/
    private Map<String, Serializable> before;
    /**
     * 存储字段-之后的值
     **/
    private Map<String, Serializable> after;
    /**
     * 存储字段--类型
     **/
    private Map<String, Field> fields;


}
/**
 * @Description 字段
 * @Author WangKun
 * @Date 2024/8/8 16:33
 * @Version
 */
@AllArgsConstructor
@Data
public class Field implements Serializable {

    /**
     * 数据库
     **/
    public String schema;

    /**
     * 表
     **/
    public String table;

    /**
     * 列索引位置
     **/
    public int inx;
    /**
     * 列名
     **/
    public String colName;
    /**
     * 类型
     **/
    public String dataType;


}

BinLog事件类型枚举(新增,修改,删除)

/**
 * @Description BinLog事件枚举
 * @Author WangKun
 * @Date 2024/8/19 15:23
 * @Version
 */
@Getter
@AllArgsConstructor
public enum BinLogEventEnum {

    WRITE("WRITE"),UPDATE("UPDATE"),DELETE("DELETE");

    /**
     * 获取key
     **/
    private final String key;

}

BinLog工具与BinLog数据操作工具

/**
 * @Description Binlog工具
 * @Author WangKun
 * @Date 2024/8/8 17:09
 * @Version
 */
@Slf4j
public class BinLogUtils {

    /**
     * 逗号
     **/
    public final static String COMMA = ",";
    /**
     * 点
     **/
    public final static String POINT = ".";

    /**
     * 双斜线
     **/
    public final static String D_SLASH = "\\";



    public static final long QUEUE_SLEEP = 1000;

    /**
     * @param db
     * @param table
     * @Description 拼接DB与Table
     * @Throws
     * @Return java.lang.String
     * @Date 2024-08-12 16:09:10
     * @Author WangKun
     **/
    public static String getDbTable(String db, String table) {
        return db + "-" + table;
    }

}
/**
 * @Description BinLog数据工具
 * @Author WangKun
 * @Date 2024/8/12 16:40
 * @Version
 */
@Slf4j
public class BinLogDataUtils {


    /**
     * @param db
     * @param table
     * @Description 获取columns集合
     * @Throws
     * @Return java.util.Map<java.lang.String, com.harmonywisdom.binlog.entity.Field>
     * @Date 2024-08-12 16:10:08
     * @Author WangKun
     **/
    public static Map<String, Field> getColumnsMap(String db, String table) {
        PreparedStatement ps = null;
        ResultSet rs = null;
        Connection connection = null;
        try {
            //获取数据源
            DataSource dataSource = SpringUtil.getBean(DataSource.class);
            connection = dataSource.getConnection();
            // 执行sql获取表数据
            String preSql = "SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, DATA_TYPE, ORDINAL_POSITION FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = ? and TABLE_NAME = ?";
            ps = connection.prepareStatement(preSql);
            ps.setString(1, db);
            ps.setString(2, table);
            rs = ps.executeQuery();
            Map<String, Field> map = new HashMap<>(rs.getRow());
            while (rs.next()) {
                String column = rs.getString("COLUMN_NAME");
                int idx = rs.getInt("ORDINAL_POSITION");
                if (column != null && idx >= 1) {
                    // sql的位置从1开始
                    map.put(column, new Field(rs.getString("TABLE_SCHEMA"), rs.getString("TABLE_NAME"), idx - 1, column, rs.getString("DATA_TYPE")));
                }
            }
            ps.close();
            rs.close();
            connection.close();
            return map;
        } catch (SQLException e) {
            log.error("加载BinLog监控配置库.表字段错误, db_table={}.{} ", db, table, e);
        } finally {
            try {
                if (ps != null) {
                    ps.close();
                }
                if (rs != null) {
                    rs.close();
                }
                if (connection != null) {
                    connection.close();
                }
            } catch (SQLException e) {
                log.error("加载BinLog监控配置库.表字段错误关闭连接失败, db_table={}.{} ", db, table, e);
            }
        }
        return null;
    }

    /**
     * @param row
     * @param dbTable
     * @param columMap
     * @param eventType
     * @Description 新增或删除操作数据格式化
     * @Throws
     * @Return com.harmonywisdom.binlog.entity.BinLog
     * @Date 2024-08-12 16:53:07
     * @Author WangKun
     **/
    private static BinLog insertOrDeletedColum(Serializable[] row, String dbTable, Map<String, Field> columMap, EventType eventType) {
        if (null == row || null == columMap || row.length != columMap.size()) {
            return null;
        }
        // 初始化Item
        BinLog item = new BinLog();
        item.setEventType(eventType);
        item.setFields(columMap);
        Map<String, Serializable> beOrAf = new HashMap<>();
        columMap.forEach((key, colum) -> {
            Serializable serializable = row[colum.inx];
            if (serializable instanceof byte[]) {
                beOrAf.put(key, new String((byte[]) serializable));
            } else {
                beOrAf.put(key, serializable);
            }
        });
        // 写操作放after,删操作放before
        if (isWrite(eventType)) {
            item.setAfter(beOrAf);
        }
        if (isDelete(eventType)) {
            item.setBefore(beOrAf);
        }
        return item;
    }

    /**
     * @param mapEntry
     * @param columMap
     * @param eventType
     * @Description 更新操作数据格式化
     * @Throws
     * @Return com.harmonywisdom.binlog.entity.BinLog
     * @Date 2024-08-12 16:52:46
     * @Author WangKun
     **/
    private static BinLog updateColum(Map.Entry<Serializable[], Serializable[]> mapEntry, Map<String, Field> columMap, EventType eventType) {
        if (null == mapEntry || null == columMap) {
            return null;
        }
        BinLog item = new BinLog();
        item.setEventType(eventType);
        item.setFields(columMap);
        Map<String, Serializable> be = new HashMap<>();
        Map<String, Serializable> af = new HashMap<>();
        columMap.forEach((key, colum) -> {
            Serializable serializableKey = mapEntry.getKey()[colum.inx];
            Serializable serializableValue = mapEntry.getValue()[colum.inx];
            if (serializableKey instanceof byte[]) {
                be.put(key, new String((byte[]) serializableKey));
            } else {
                be.put(key, serializableKey);
            }
            if (serializableValue instanceof byte[]) {
                af.put(key, new String((byte[]) serializableValue));
            } else {
                af.put(key, serializableValue);
            }
        });
        item.setBefore(be);
        item.setAfter(af);
        return item;
    }

    /**
     * @param data
     * @param dbTableIdCols
     * @param dbTableCols
     * @param eventType
     * @param queue
     * @Description 更新数据
     * @Throws
     * @Return void
     * @Date 2024-08-14 17:35:49
     * @Author WangKun
     **/
    public static void updateData(UpdateRowsEventData data, Map<Long, String> dbTableIdCols, Map<String, Map<String, Field>> dbTableCols, EventType eventType, BlockingQueue<BinLog> queue) {
        for (Map.Entry<Serializable[], Serializable[]> row : data.getRows()) {
            if (dbTableIdCols.containsKey(data.getTableId())) {
                String dbTable = dbTableIdCols.get(data.getTableId());
                BinLog item = updateColum(row, dbTableCols.get(dbTable), eventType);
                item.setDbTable(dbTable);
                try {
                    queue.put(item);
                } catch (InterruptedException e) {
                    log.error("BinLog 更新数据添加阻塞队列异常:{}", e.getMessage(), e);
                }
            }
        }
    }

    /**
     * @param eventType
     * @param rows
     * @param tableId
     * @param dbTableIdCols
     * @param dbTableCols
     * @param queue
     * @Description 新增与删除数据
     * @Throws
     * @Return void
     * @Date 2024-08-13 17:30:30
     * @Author WangKun
     **/
    public static void insertOrDeletedData(EventType eventType, List<Serializable[]> rows, long tableId, Map<Long, String> dbTableIdCols, Map<String, Map<String, Field>> dbTableCols, BlockingQueue<BinLog> queue) {
        for (Serializable[] row : rows) {
            if (dbTableIdCols.containsKey(tableId)) {
                String dbTable = dbTableIdCols.get(tableId);
                BinLog item = insertOrDeletedColum(row, dbTable, dbTableCols.get(dbTable), eventType);
                item.setDbTable(dbTable);
                try {
                    queue.put(item);
                } catch (InterruptedException e) {
                    log.error("BinLog 新增或者删除数据添加阻塞队列异常:{}", e.getMessage(), e);
                }
            }
        }
    }

}

BinLog监听

/**
 * @Description 监听(@FunctionalInterface确保该接口只有以一个抽象方法)
 * @Author WangKun
 * @Date 2024/8/8 17:31
 * @Version
 */
@FunctionalInterface
public interface BinLogListener {

    void onEvent(BinLog binLog);

}
/**
 * @Description MySQL监听
 * @Author WangKun
 * @Date 2024/8/8 17:32
 * @Version
 */
@Slf4j
public class MySQLBinLogListener implements BinaryLogClient.EventListener {


    /**
     * BinLog连接信息
     **/
    private final BinaryLogClient client;

    /**
     * 阻塞队列,存放信息
     **/
    private final BlockingQueue<BinLog> queue;

    /**
     * 线程池
     **/
    private final ExecutorService executorService;

    /**
     * 存放每张数据表对应的listener器,允许将多个值存储在单个键下(每张表一个监听器)
     **/
    private final Multimap<String, BinLogListener> listeners;

    /**
     * 存放监控所有库表结构
     **/
    private final Map<String, Map<String, Field>> dbTableCols;

    /**
     * 存放改变的库表结构
     **/
    private final Map<Long, String> dbTableIdCols;

    /**
     * @param conf
     * @Description 监听器初始化配置
     * @Throws
     * @Return
     * @Date 2024-08-13 16:53:18
     * @Author WangKun
     **/
    public MySQLBinLogListener(BinLogConfig conf) {
        BinaryLogClient client = new BinaryLogClient(conf.getHost(), conf.getPort(), conf.getUsername(), conf.getPassword());
        EventDeserializer eventDeserializer = new EventDeserializer();
        // 序列化
        eventDeserializer.setCompatibilityMode(
                EventDeserializer.CompatibilityMode.DATE_AND_TIME_AS_LONG,
                EventDeserializer.CompatibilityMode.CHAR_AND_BINARY_AS_BYTE_ARRAY
        );
        client.setEventDeserializer(eventDeserializer);
        client.setServerId(conf.getServerId());
        this.client = client;
        this.queue = new ArrayBlockingQueue<>(ThreadPoolConfig.queueCapacity);
        this.listeners = ArrayListMultimap.create();
        this.dbTableCols = new ConcurrentHashMap<>();
        this.dbTableIdCols = new ConcurrentHashMap<>();
        // 开启线程池
        this.executorService = ThreadPoolUtils.create().setPrefixName("Binlog-Listener-Thread").setCorePoolSize(6).build();
    }

    /**
     * @param event
     * @Description 监听处理, 只支持MySQL中BinLog的ROW模式的
     * @Throws
     * @Return void
     * @Date 2024-08-13 16:54:01
     * @Author WangKun
     **/
    @Override
    public void onEvent(Event event) {
        EventType eventType = event.getHeader().getEventType();
        // 装配库表结构
        if (eventType == EventType.TABLE_MAP) {
            TableMapEventData tableData = event.getData();
            String dbTable = BinLogUtils.getDbTable(tableData.getDatabase(), tableData.getTable());
            if (dbTableCols.containsKey(dbTable)) {
                dbTableIdCols.put(tableData.getTableId(), dbTable);
            }
        }
        //新增数据
        if (EventType.isWrite(eventType)) {
            WriteRowsEventData data = event.getData();
            BinLogDataUtils.insertOrDeletedData(eventType, data.getRows(), data.getTableId(), dbTableIdCols, dbTableCols, queue);
        } else if (EventType.isUpdate(eventType)) {
            // 更新数据
            UpdateRowsEventData data = event.getData();
            BinLogDataUtils.updateData(data, dbTableIdCols, dbTableCols, eventType, queue);
        } else if (EventType.isDelete(eventType)) {
            // 删除数据
            DeleteRowsEventData data = event.getData();
            BinLogDataUtils.insertOrDeletedData(eventType, data.getRows(), data.getTableId(), dbTableIdCols, dbTableCols, queue);
        }
    }

    /**
     * @param db
     * @param table
     * @param listener
     * @Description 注册监听
     * @Throws
     * @Return void
     * @Date 2024-08-13 17:32:44
     * @Author WangKun
     **/
    public void registerListener(String db, String table, BinLogListener listener) {
        String dbTable = BinLogUtils.getDbTable(db, table);
        // 连接获取字段集合
        Map<String, Field> cols = BinLogDataUtils.getColumnsMap(db, table);
        // 保存字段信息
        dbTableCols.put(dbTable, cols);
        // 保存当前注册的listener
        listeners.put(dbTable, listener);
    }

    /**
     * @param
     * @Description 开启异步多线程消费
     * @Throws
     * @Return void
     * @Date 2024-08-13 18:02:48
     * @Author WangKun
     **/
    @Async
    public void openThreadConsumeBinLog(){
        client.registerEventListener(this);
        for (int i = 0; i < ThreadPoolConfig.corePoolSize*ThreadPoolConfig.CPU_NUMS; i++) {
            executorService.execute(() -> {
                // 轮询监控
                while (true) {
                    if (!queue.isEmpty()) {
                        try {
                            BinLog binLogQueue = queue.take();
                            listeners.get(binLogQueue.getDbTable()).forEach(binLogListener -> binLogListener.onEvent(binLogQueue));
                        } catch (InterruptedException e) {
                            log.error("BinLog多线程消费异常:{}", e.getMessage(), e);
                        }
                    }
                }
            });
        }
        try {
            //连接(不设置时间将会使用主线程)
            client.connect(BinLogUtils.QUEUE_SLEEP);
        } catch (Exception e) {
            log.error("BinLog多线程连接消费异常:{}", e.getMessage(), e);
        }
    }
}
/**
 * @Description 初始化Binlog监听
 * @Author WangKun
 * @Date 2024/8/9 10:36
 * @Version
 */
@Slf4j
@RequiredArgsConstructor
@Component
@Order(value = 1)
public class BinLogInitListener implements CommandLineRunner {

    /**
     * 资源注入
     **/
    private final BinLogConfig config;

    /**
     * @param args
     * @Description 初始化
     * @Throws
     * @Return void
     * @Date 2024-08-13 14:07:49
     * @Author WangKun
     **/
    @Override
    public void run(String... args) throws Exception {
        try {
            // 初始化监听器
            MySQLBinLogListener mySqlBinLogListener = new MySQLBinLogListener(config);
            this.getListMap().forEach((db, tables) -> {
                tables.forEach(table -> {
                    mySqlBinLogListener.registerListener(db, table, info -> {
                        if(info.getEventType().name().contains(BinLogEventEnum.UPDATE.getKey())){
                            log.info("库.表: {}, 修改之前:{}" ,db+"."+table,info.getBefore().toString());
                            log.info("库.表: {}, 修改之后:{}" ,db+"."+table,info.getAfter().toString());
                        }
                        if(info.getEventType().name().contains(BinLogEventEnum.WRITE.getKey())){
                            log.info("库.表: {}, 新增: {}" ,db+"."+table,info.getAfter().toString());
                        }
                        if(info.getEventType().name().contains(BinLogEventEnum.DELETE.getKey())){
                            log.info("库.表: {}, 删除: {}" ,db+"."+table,info.getBefore().toString());
                        }
                    });
                });
            });
            // 开启多线程消费
            mySqlBinLogListener.openThreadConsumeBinLog();
        } catch (Exception e) {
            log.error("BinLog初始化监听异常:{}", e.getMessage(), e);
        }
    }

    /**
     * @param
     * @Description 初始化监听库表
     * @Throws
     * @Return java.util.Map<java.lang.String, java.util.List < java.lang.String>>
     * @Date 2024-08-12 16:19:32
     * @Author WangKun
     **/
    private Map<String, List<String>> getListMap() {
        Map<String, List<String>> map = new ConcurrentHashMap<>();
        try {
            for (String key : config.getTables()) {
                // 正则转义,要加双斜线
                String[] split = key.split(BinLogUtils.D_SLASH + BinLogUtils.POINT);
                if (split.length != 2) {
                    log.error("BinLog配置同步,类型错误 [库名.表名]。请正确配置:{}", key);
                    throw new Exception("BinLog配置同步,类型错误 [库名.表名]。请正确配置:" + key);
                }
                map.computeIfAbsent(split[0], k -> new ArrayList<>()).add(split[1]);
            }
            return map;
        } catch (Exception e) {
            log.error("BinLog配置同步,类型错误 [库名.表名]。请正确配置:{}", e.getMessage(), e);
        }
        return map;
    }

}

目录结构

启动IDEA,在控制台出现以下信息,成功

2024-08-19 17:40:47.129  INFO 493984 --- [       blc-localhost:3306] c.g.shyiko.mysql.binlog.BinaryLogClient  : Connected to localhost:3306 at log.000004/7294671 (sid:1, cid:800)

效果:

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

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

相关文章

亦菲喊你来学习之机器学习(6)--逻辑回归算法

逻辑回归 逻辑回归&#xff08;Logistic Regression&#xff09;是一种广泛使用的统计方法&#xff0c;用于解决分类问题&#xff0c;尤其是二分类问题。尽管名字中有“回归”二字&#xff0c;但它实际上是一种分类算法&#xff0c;因为它试图通过线性回归的方式去预测一个事件…

【计算机组成原理】二、数据的表示和运算:3.算术逻辑单元ALU(逻辑运算、加法器)

4.运算器ALU 文章目录 4.运算器ALU4.1逻辑运算非&#xff08;NOT&#xff09;与&#xff08;AND&#xff09;或&#xff08;OR&#xff09;异或&#xff08;XOR&#xff09;同或&#xff08;XNOR&#xff09; 4.2加法器4.2.1一位全加器4.2.2串行加法器4.2.3并行加法器 4.3ALU功…

金九银十简历石沉大海?别投了,软件测试岗位饱和了....

各大互联网公司的接连裁员&#xff0c;政策限制的行业接连消失&#xff0c;让今年的求职雪上加霜&#xff0c;想躺平却没有资本&#xff0c;还有人说软件测试岗位饱和了&#xff0c;对此很多求职者深信不疑&#xff0c;因为投出去的简历回复的越来越少了。 另一面企业招人真的…

IDEA翻译插件-Translation

简介 Translation是一个为IntelliJ IDEA和其他基于JetBrains的IDE&#xff08;如 PyCharm、WebStorm 等&#xff09;设计的插件。这个插件的主要功能是帮助开发者在编写代码或文档时快速翻译文本。它集成了谷歌翻译、微软翻译、DeepL 翻译、OpenAI 翻译、有道翻译等众多翻译引…

CISAW安全运维认证考试重点内容介绍

CISAW安全运维认证是信息、运维方面非常重要的证书&#xff0c;从事与信息安全以及运维方向的人员都会考这个证书&#xff0c;其持有证书在工作上带来极大的帮助。 那么&#xff0c;CISAW安全运维认证考试重点内容是什么&#xff1f;就目前的问题给大家一些列讲解&#xff0c;…

vue-element-admin解决三级目录的KeepAlive缓存问题(详情版)

vue-element-admin解决三级目录的KeepAlive缓存问题&#xff08;详情版&#xff09; 本文章将从问题出现的角度看看KeepAlive的缓存问题&#xff0c;然后提出两种解决方法。本文章比较详细&#xff0c;如果只是看怎么解决&#xff0c;代码怎么改&#xff0c;请前往配置版。 一…

2007-2022年上市公司资源节约数据

2007-2022年上市公司资源节约数据 1、时间&#xff1a;2007-2022年 2、来源&#xff1a;上市公司年报、社会责任报告、上市公司网站信息 3、指标&#xff1a;水资源节约、电力节约、原煤节约、天然气节约、汽油节约、柴油节约、集中供热节约、折算成统一标准煤共计节约 4、…

stl容器适配器 stack与queue,priority_queue

目录 一.stack 1.stack的使用 2.适配器 3.stack相关的题目 最小栈. - 力扣&#xff08;LeetCode&#xff09; ​编辑 栈的弹出压入序列栈的压入、弹出序列_牛客题霸_牛客网 用两个栈实现队列. - 力扣&#xff08;LeetCode&#xff09; 4.stack的模拟实现 二.queue队列…

一起学习LeetCode热题100道(48/100)

48.路径总和 III(学习) 给定一个二叉树的根节点 root &#xff0c;和一个整数 targetSum &#xff0c;求该二叉树里节点值之和等于 targetSum 的 路径 的数目。 路径 不需要从根节点开始&#xff0c;也不需要在叶子节点结束&#xff0c;但是路径方向必须是向下的&#xff08;只…

前端获取主流浏览器的信息进行判断 实现自适应内容(360浏览器)

我一般都是用谷歌浏览器进行开发&#xff0c;在开发大屏可视化的时候出现了浏览器不适应的问题&#xff0c;需要不同的浏览器进行判断&#xff0c;360返回 Chrome 内核&#xff0c; 获取的信息无法跟谷歌浏览器区别 这个是中国的主流浏览器&#xff1a; 比如谷歌可以正常显示&…

十要素超声波气象传感器

十要素微型气象传感器&#xff08;也称为全要素微型气象传感器&#xff09;通常具有以下几个基本功能&#xff1a; 温度测量&#xff1a;测量环境的温度&#xff0c;并提供实时温度数据。 湿度测量&#xff1a;测量环境的湿度水平&#xff0c;并提供实时湿度数据。 大气压力测…

【安全靶场】-DC-5

❤️博客主页&#xff1a; iknow181&#x1f525;系列专栏&#xff1a; 网络安全、 Python、JavaSE、JavaWeb、CCNP&#x1f389;欢迎大家点赞&#x1f44d;收藏⭐评论✍ 一、收集信息 1.用burp测试穷尽文件名 使用两个字典 发现footer页面 可能存在文件包含&#xff0c;因为co…

记一次 MIGO 短缺BA 非限制使用 35,713.970 USD : 100002919-Z040 100Z 1000 99991231

mb52数量 一想到该物料的启用了批次管理 &#xff0c; 应该去查一下 批次管理的底表 MCHB &#xff08;各种库存地表见 SAP MM 库存分类及对应的存储表-CSDN博客&#xff09; 实际调用migo的参数 这下明确了&#xff0c;总共一起60000多是足够调出的&#xff0c;但是99991231这…

zabbix通过snmp监控物理服务器硬件信息

背景&#xff1a;公司的华三服务器周末的时候市电跳闸&#xff0c;监控没有设置告警&#xff0c;幸好有UPS供电&#xff0c;工作日发现问题后市电恢复。 方法&#xff1a; 1、登陆物理服务器带外&#xff0c;开放snmp并设置团体名 2、找一台安装了nmap的机器&#xff0c;查看…

python使用gurobi用法解析和案例

文章目录 1. Gurobi Python接口的基本使用2. 变量类型3. 目标函数4. 约束条件5. 模型求解和结果分析6. 常见注意事项7. gurobi代码示例 1. Gurobi Python接口的基本使用 在Python中使用Gurobi进行优化&#xff0c;通常需要按以下步骤操作&#xff1a; 导入Gurobi包 &#xff…

【Java】—— 使用Java在控制台实现海豚记账软件

目录 1. 项目背景 2. 代码思路 2.1 主要功能 2.2 数据结构 2.3 控制流程 3. 实现步骤 3.1 初始化变量 3.2 显示菜单 3.3 处理用户输入 3.4 退出程序 4. 知识点解析 4.1 Scanner类 4.2 字符与数字转换 4.3 循环与条件判断 5.完整代码 6.运行结果展示 1. 项目背景…

【秋招笔试】8.18字节跳动秋招(第一场)-三语言题解

🍭 大家好这里是 春秋招笔试突围,一起备战大厂笔试 💻 ACM金牌团队🏅️ | 多次AK大厂笔试 | 编程一对一辅导 ✨ 本系列打算持续跟新 春秋招笔试题 👏 感谢大家的订阅➕ 和 喜欢💗 和 手里的小花花🌸 ✨ 笔试合集传送们 -> 🧷春秋招笔试合集 🍒 本专栏已收…

IOS 12 自定义用户协议对话框

实现效果 实现逻辑 本文使用QMUI里面提供的控制器 自定义控件 实现。 添加依赖 #腾讯开源的UI框架&#xff0c;提供了很多功能&#xff0c;例如&#xff1a;圆角按钮&#xff0c;空心按钮&#xff0c;TextView支持placeholder #https://github.com/QMUI/QMUIDemo_iOS #http…

《向量数据库指南》——解决方案:采用安全、高性能的Milvus Cloud向量数据库,赋能Dopple AI的创新与发展

解决方案:采用安全、高性能的Milvus Cloud向量数据库,赋能Dopple AI的创新与发展 在当今这个数据驱动的时代,向量数据库作为机器学习、人工智能等领域的重要基础设施,正发挥着越来越关键的作用。对于Dopple AI这样一个致力于创新的前沿团队来说,选择一个合适的向量数据库…

盘点8大跨境电商平台发展前景及选品分析(亚马逊、速卖通篇)

跨境电商行业在全球范围内持续发展&#xff0c;各大平台各有特色&#xff0c;针对不同的市场和消费者群体提供多元化的服务。以下是亚马逊、Shopee、TikTok、TEMU、速卖通、eBay、Lazada、SHEIN这八大跨境电商平台的背景、主要针对群体、消费者购物偏好及选品建议的简要介绍&am…