一站式指导:在Neo4j与PostgreSQL间实现高效数据同步

作者:后端小肥肠

🍇 我写过的文章中的相关代码放到了gitee,地址:xfc-fdw-cloud: 公共解决方案

🍊 有疑问可私信或评论区联系我。

🥑  创作不易未经允许严禁转载。

姊妹篇:

数据同步的艺术:探索PostgreSQL和Redis的一致性策略_postgresql redis 同步-CSDN博客
【Neo4j系列】简化Neo4j数据库操作:一个基础工具类的开发之旅_neo4j js-CSDN博客

【Neo4j系列】Neo4j概念简介及整合SpringBoot_neo4j springboot-CSDN博客

【Neo4j系列】Neo4j之CQL语句和函数介绍_neo4j cql-CSDN博客

目录

1. 引言

2. Neo4j与PostgreSQL的基础知识

2.1. Neo4j基本概念与架构

2.2. PostgreSQL基本概念与架构

2.3. 数据处理上的不同与互补

3. 数据同步方案设计

3.1. Neo4j与PostgresSQL对比说明

3.2. 数据同步技术方案设计

3.2.1 全量同步

3.2.2. 增量同步

4. 技术实现

4.1. PostgresSQL表结构设计

4.2. Neo4j 数据模型设计 

4.3. 全量同步代码

4.4. 增量同步代码

5. 结论


1. 引言

在当今数字化时代,数据已成为企业的核心资产。随着业务的不断扩展和技术的快速发展,企业常常需要同时运用多种数据库系统来满足不同的业务需求。在这种背景下,Neo4j作为领先的图数据库,以其高效的关系数据处理能力而备受青睐;而PostgreSQL作为功能强大的关系型数据库,则以其稳定性和可扩展性而闻名。然而,如何在这两种截然不同的数据库系统之间实现高效、可靠的数据同步,成为了许多企业面临的一大挑战。

本文旨在为读者提供一个全面的指南,详细阐述如何在Neo4j和PostgreSQL之间构建高效的数据同步机制。我们将深入探讨数据同步的重要性,分析两种数据库系统的特点,并提供从策略设计到技术实现的完整解决方案。无论您是数据库管理员系统架构师,还是对数据集成感兴趣的技术爱好者,本文都将为您提供宝贵的见解和实用的技巧。

通过阅读本文,您将了解到:

  1. PostgresSQLNeo4j基本概念简介
  2. 如何设计一个既满足业务需求又技术可行的同步策略
  3. 实现数据同步的具体技术步骤和最佳实践
  4. 如何应对同步过程中可能遇到的挑战,并进行性能优化

让我们开始这段探索数据同步世界的旅程,一同揭示Neo4j和PostgreSQL协同工作的无限可能!

2. Neo4j与PostgreSQL的基础知识

2.1. Neo4j基本概念与架构

Neo4j 是一种高性能的图数据库,它使用图结构存储复杂的网络关系,以节点、关系和属性的形式存储和查询数据。Neo4j的主要特点包括:

  • 图存储模型相对于传统的关系数据库,Neo4j直接在图中存储实体之间的关系,能够快速通过关系遍历相关节点。
  • Cypher查询语言专为图数据设计的声明式语言,使得编写查询直观并易于理解。
  • 强调关系Neo4j认为关系是首要的,不仅仅是节点的附属品,这使得关系查询非常高效。
  • ACID事务支持全事务性的操作,保证数据的一致性和完整性。

2.2. PostgreSQL基本概念与架构

PostgreSQL 是一种功能强大的开源关系型数据库系统,以其稳定性、可扩展性和丰富的特性集而著名。其核心特点包括:

  • SQL兼容:遵循SQL标准,支持复杂的查询和多种数据类型。
  • 扩展性支持自定义类型、函数和插件,用户可以根据需要扩展数据库功能。
  • MVCC(多版本并发控制)提供高级别的并发性和性能,同时保持读写操作的一致性。
  • 高可靠性支持点对点复制和热备份,确保数据安全和高可用。

2.3. 数据处理上的不同与互补

Neo4j和PostgreSQL在数据处理上有本质的不同,这些差异为它们在特定应用场景中的互补提供了基础:

  • 处理复杂关系Neo4j在处理高度连接的数据和深层关系网络方面具有优势,适合社交网络分析、推荐系统等场景。而PostgreSQL在处理大规模的结构化数据和事务性要求高的应用中更为有效。
  • 查询性能对于深度连接查询和图遍历,Neo4j表现出色;而PostgreSQL在执行大规模聚合查询和多表连接时更具优势。
  • 数据完整性与安全PostgreSQL提供广泛的数据完整性和安全特性,这在需要严格数据验证和法规遵从的业务中非常重要。Neo4j也提供事务支持,但以不同方式实现。

通过理解这些基本概念和架构的不同,可以更好地设计出符合特定业务需求的数据同步策略,有效地利用两种数据库系统的优势。

3. 数据同步方案设计

3.1. Neo4j与PostgresSQL对比说明

在我们开始讨论如何同步Neo4j和PostgreSQL之间的数据之前,先来看看它们在数据模型上的异同。为了方便理解,我把两者对应的概念列成了一个表格:

关系型数据库(PostgresSQL)Neo4j
节点
属性
约束关系

这张表看起来很简单,但如果我们深入分析,每一行其实都揭示了两种数据库背后完全不同的思维方式。

1. 表 vs 图

在PostgreSQL里,数据是存储在表格中的,每张表有固定的结构和字段,比如用户表可能包含用户ID、姓名、邮箱等信息。
Neo4j则完全不同,它把数据存储在图里。图中包含的是节点关系,比如一个用户是另一用户的朋友就可以通过节点(用户)和关系(朋友)轻松表达。

简单理解:

  • 表是二维的,一行行数据排列得整整齐齐。
  • 图是网络状的,数据之间的连接是它的核心。

2. 行 vs 节点

PostgreSQL中的每一行表示一条具体的记录,比如某个用户的详细信息。
而在Neo4j中,这种记录会被表达成一个节点,节点可以理解为图里的一个点,每个点都有属于它自己的属性。
举个例子:
PostgreSQL的用户表中,一行记录可能是:
{id: 1, name: '张三', email: 'zhangsan@example.com'}
到了Neo4j里,这一行会变成一个用户节点:
(id: 1, name: '张三', email: 'zhangsan@example.com')

3. 列 vs 属性

在PostgreSQL中,表的每一列都是字段,比如姓名、邮箱这些。
而在Neo4j中,节点或关系都有属性,这些属性其实就是PostgreSQL里的列。
换句话说: 列就是属性,属性就是列。同步时,只需要确保列名和属性名一致,就能一一对应。

4. 约束 vs 关系

这一点是PostgreSQLNeo4j最根本的差异。
PostgreSQL中的约束,比如外键,用来表示两张表之间的关联关系。
而在Neo4j里,关系本身就是一等公民,图数据库的设计核心就在于节点之间的关系。
例如:
在PostgreSQL中,如果你有用户表好友关系表

  • 用户表存用户信息。
  • 好友关系表存两个用户的ID,表示谁和谁是好友。

在Neo4j中,你不需要分成两张表,因为好友关系可以直接存在于图中:

(张三)-[:朋友]->(李四)
关系甚至可以有属性,比如加好友的时间、关系强度等,这一点是图数据库的天然优势。

通过这几个对应关系,我们可以看出PostgreSQL更注重结构化的数据存储,而Neo4j更适合表现复杂的数据关系。在设计数据同步方案时,我们的目标就是把PostgreSQL里的表、行、列和约束,巧妙地转换成Neo4j里的图、节点、属性和关系,为下一步的全量同步和增量同步打好基础。简单点说,这就是把二维的表,变成更立体的图,顺便让数据之间的关系更加直观。

3.2. 数据同步技术方案设计

在Neo4j与PostgreSQL之间实现数据同步,通常有两种方式:全量同步和增量同步。全量同步通常用于初次数据迁移,而增量同步适用于实时或近实时的增量更新。增量同步和全量同步实现的方式很多,本章仅仅基于Java实现展开。

3.2.1 全量同步

全量同步是一种将PostgreSQL中的所有数据一次性迁移到Neo4j的方式,适用于初次数据迁移或定期的全量刷新。

实现步骤:

  1. 数据抽取:

    • 使用Java通过JDBC从PostgreSQL数据库中抽取数据。
    • 根据PostgreSQL的表结构,将每一行数据映射为Neo4j中的一个节点,每个列映射为节点的属性。
  2. 数据转换:

    • 在数据抽取后,进行格式转换,将PostgreSQL的行数据转换为符合Neo4j图数据库结构的数据模型。
    • 为每个数据项(如节点、关系)生成唯一标识,以保证在Neo4j中的一致性和准确性。
  3. 批量插入:

    • 使用Neo4j的批量插入功能,通过Cypher语句将转换后的数据批量写入Neo4j数据库。
    • 在插入过程中,采用事务管理来提高插入效率,确保数据一致性。
  4. 数据验证:

    • 全量同步完成后,进行数据校验,确保PostgreSQL与Neo4j之间的数据一致性,检查是否有数据遗漏或错误。
3.2.2. 增量同步

增量同步是一种实时或周期性同步数据变更的方式,适用于数据更新频繁、需要实时反映变动的场景。

实现步骤:

  1. 数据变更捕获:利用PostgreSQL的WAL日志(Write-Ahead Logging)机制来捕获数据的增量变更。

  2. 消息队列传输:

    • 将捕获到的增量变更数据通过消息队列(如RabbitMQ)传输到Neo4j的同步系统。
    • 每个消息中包含了变更的操作类型(如插入、更新、删除)以及相关数据。
  3. 数据应用:

    • 在Neo4j中,根据消息的内容执行相应的Cypher查询,更新图数据库中的数据。
    • 通过Java程序处理从RabbitMQ接收到的增量数据,动态更新Neo4j中的节点和关系。
  4. 容错处理与监控:

    • 设计增量同步的容错机制,确保在增量同步过程中不会丢失数据或发生重复同步。

4. 技术实现

4.1. PostgresSQL表结构设计

在本节PG库到Neo4j数据库同步技术实践中,我设计了4张数据表,分别是教师表(xfc_teacher)、学生表(xfc_student)、班级表(xfc_class)、班级和老师关联中间表(xfc_brid_teacher_and_class)。其表关系如下:

4.2. Neo4j 数据模型设计 

根据提供的关系型数据库表结构,我们设计了如下的 Neo4j 图数据库数据模型

节点(Nodes):

  • Teacher (老师节点)

    • 属性:
      • id (老师的唯一标识)
      • name (老师名字)
      • subject (老师教授的科目)
    • 说明: 表示每位老师的基本信息。
  • Class (班级节点)

    • 属性:
      • id (班级的唯一标识)
      • name (班级名称)
    • 说明: 表示每个班级的基本信息。
  • Student (学生节点)

    • 属性:
      • id (学生的唯一标识)
      • name (学生名字)
      • age (学生年龄)
    • 说明: 表示每位学生的基本信息。

关系(Relationships):

  • TEACHES (教授)

    • 起点节点类型: Teacher
    • 终点节点类型: Class
    • 属性: 无
    • 说明: 表示某位老师教授某个班级的关系。
  • HAS_STUDENT (包含学生)

    • 起点节点类型: Class
    • 终点节点类型: Student
    • 属性: 无
    • 说明: 表示某个班级包含某位学生的关系。

示意模型

  1. 老师和班级:
    (:Teacher {id: "T1", name: "张老师", subject: "数学"})-[:TEACHES]->(:Class {id: "C1", name: "一年级"})
  2. 班级和学生:
    (:Class {id: "C1", name: "一年级"})-[:HAS_STUDENT]->(:Student {id: "S1", name: "李明", age: 12})

对应的 PostgreSQL 数据表和 Neo4j 模型的映射

PostgreSQL 表Neo4j 节点或关系属性映射
xfc_teacherTeacher 节点id, name, subject
xfc_classClass 节点id, name
xfc_studentStudent 节点id, name, age
xfc_brid_teacher_and_classTEACHES 关系teacher_id -> id, class_id -> id
xfc_student.class_id (外键)HAS_STUDENT 关系class_id -> id

通过这种图模型设计,我们将关系型数据库中的结构化表格数据转换为更直观的图数据结构,为后续的数据同步和分析奠定基础。

4.3. 全量同步代码

代码我都放到git仓库了,需要的自己去取。xfc-fdw-cloud: 公共解决方案

1. 全量同步接口:

2. 全量同步方法:

3. 多源事务管理配置类:

@Configuration
public class TransactionConfig {

    @Autowired
    private DataSource dataSource;

    @Autowired
    private Driver neo4jDriver;

    @Bean("postgresTransactionManager")
    public PlatformTransactionManager postgresTransactionManager() {
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean("neo4jTransactionManager")
    public PlatformTransactionManager neo4jTransactionManager() {
        return new Neo4jTransactionManager(neo4jDriver);
    }

    @Bean("transactionManager")
    public PlatformTransactionManager chainedTransactionManager(
            @Qualifier("postgresTransactionManager") PlatformTransactionManager postgresTransactionManager,
            @Qualifier("neo4jTransactionManager") PlatformTransactionManager neo4jTransactionManager) {
        return new ChainedTransactionManager(
                postgresTransactionManager,
                neo4jTransactionManager
        );
    }
}

这段代码是一个Spring配置类,通过定义多个事务管理器(分别用于PostgreSQLNeo4j)以及一个链式事务管理器(ChainedTransactionManager),实现对多数据源的分布式事务管理,确保事务在多个数据库之间的一致性和原子性。

我就不讲解了,很简单。 

4.4. 增量同步代码

1. 编写ChangeLogProcessor

代码太长了,我这里就不粘贴了,只粘贴主干代码,要看全都代码可以去仓库。

    public void processChangeLog(String changeLog) {
        try {
            String operation = extractOperation(changeLog);
            String table = extractTable(changeLog);
            String id = extractId(changeLog);

            log.debug("Processing change: operation={}, table={}, id={}", operation, table, id);

            switch (table) {
                case "public.xfc_teacher":
                    processTeacherChange(operation, id, changeLog);
                    break;
                case "public.xfc_class":
                    processClassChange(operation, id, changeLog);
                    break;
                case "public.xfc_student":
                    processStudentChange(operation, id, changeLog);
                    break;
                case "public.xfc_brid_teacher_and_class":
                    processTeacherClassRelation(operation, changeLog);
                    break;
                default:
                    log.warn("未知的表操作: {}", table);
            }
        } catch (Exception e) {
            log.error("处理变更日志时发生错误: {}", changeLog, e);
        }
    }

processChangeLog 函数是一个变更日志处理器,主要功能是接收并处理 PostgreSQL 数据库的变更日志(比如插入、更新、删除操作),然后将这些变更同步到 Neo4j 图数据库中 

2. 编写DatabaseChangeService

public class DatabaseChangeService {

    private final JdbcTemplate jdbcTemplate;
    private final ChangeLogProcessor changeLogProcessor;

    private static final String SLOT_NAME = "neo4j_replication_slot";
    private static final long POLL_INTERVAL = 1000; // 1秒
    private static final int MAX_RETRIES = 3;
    private volatile boolean running = true;

    @PostConstruct
    public void startListening() {
        new Thread(this::initializeReplicationSlot, "ReplicationListener").start();
    }

    private void initializeReplicationSlot() {
        try {
            if (!isSlotExists(SLOT_NAME)) {
                createReplicationSlot(SLOT_NAME);
                log.info("Created new replication slot: {}", SLOT_NAME);
            } else {
                log.info("Using existing replication slot: {}", SLOT_NAME);
            }
            listenToReplicationSlot();
        } catch (Exception e) {
            log.error("初始化复制槽时发生错误", e);
        }
    }

    private boolean isSlotExists(String slotName) {
        String query = "SELECT COUNT(*) FROM pg_replication_slots WHERE slot_name = ?";
        Integer count = jdbcTemplate.queryForObject(query, Integer.class, slotName);
        return count != null && count > 0;
    }

    private void createReplicationSlot(String slotName) {
        String query = "SELECT pg_create_logical_replication_slot(?, 'test_decoding')";
        jdbcTemplate.update(query, slotName);
    }

    public void listenToReplicationSlot() {
        String query = "SELECT data FROM pg_logical_slot_get_changes(?, NULL, NULL)";
        int retryCount = 0;

        while (running) {
            try {
                List<String> changes = jdbcTemplate.queryForList(query, String.class, SLOT_NAME);

                for (String change : changes) {
                    try {
                        changeLogProcessor.processChangeLog(change);
                    } catch (Exception e) {
                        log.error("处理变更时发生错误: {}", change, e);
                    }
                }

                retryCount = 0; // 重置重试计数
                Thread.sleep(POLL_INTERVAL);

            } catch (InterruptedException e) {
                log.info("复制监听器被中断");
                Thread.currentThread().interrupt();
                break;
            } catch (Exception e) {
                log.error("监听复制槽时发生错误", e);
                retryCount++;

                if (retryCount >= MAX_RETRIES) {
                    log.error("达到最大重试次数,停止监听");
                    break;
                }

                try {
                    Thread.sleep(POLL_INTERVAL * retryCount); // 指数退避
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt();
                    break;
                }
            }
        }
    }

    public void stop() {
        running = false;
    }
}

DatabaseChangeService 是一个数据库变更监听服务,它通过 PostgreSQL 的逻辑复制功能(使用复制槽 replication slot)来捕获数据库的变更事件(如插入、更新、删除),当检测到变更时,会通过 ChangeLogProcessor 处理这些变更并将其同步到 Neo4j 图数据库中,从而实现 PostgreSQL 到 Neo4j 的实时数据同步。这个服务在启动时会自动创建并监听复制槽,并通过循环轮询的方式持续获取变更日志,同时包含了错误处理和重试机制以确保同步的可靠性。 

5. 结论

本文详细介绍了如何在 Neo4j 与 PostgreSQL 两种数据库之间实现高效数据同步,从基础概念到全量与增量同步的实现策略,结合具体代码与实践案例,为开发者提供了全面的指导。通过充分利用 Neo4j 的关系处理优势与 PostgreSQL 的结构化数据支持,这种同步机制能够满足复杂业务需求,为数据整合和分析提供坚实基础。希望本文能为技术从业者提供清晰的思路,助力多数据库协作的实现与优化。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

AI一键生成原创圣诞印花图案

一、引言 随着科技的飞速发展&#xff0c;AI 已经深入到我们生活和工作的各个角落&#xff0c;为创意设计领域带来了前所未有的变革。在圣诞即将来临之际&#xff0c;想要设计独特的圣诞印花图案却又担心缺乏灵感或专业技能&#xff1f;别担心&#xff0c;千鹿 AI 为我们提供了…

视频 的 音频通道提取 以及 视频转URL 的在线工具!

视频 的 音频通道提取 以及 视频转URL 的在线工具&#xff01; 工具地址: https://www.lingyuzhao.top/toolsPage/VideoTo.html 它提供了便捷的方法来处理视频文件&#xff0c;具体来说是帮助用户从视频中提取音频轨道&#xff0c;并将视频转换为可以通过网络访问的URL链接。无…

图片的懒加载

目录 懒加载的来源 事件监听 IntersectionObserver 懒加载的来源 图片的来加载其实就是延迟加载&#xff0c;我们知道浏览器的可视范围是有限的&#xff0c;现在网页的内容越来越丰富&#xff0c;一般网页的内容都是需要滚动才能完成浏览 如果网页有很多图片&#xff0c;然…

【每日刷题】Day162

【每日刷题】Day162 &#x1f955;个人主页&#xff1a;开敲&#x1f349; &#x1f525;所属专栏&#xff1a;每日刷题&#x1f34d; &#x1f33c;文章目录&#x1f33c; 1. 3302. 字典序最小的合法序列 - 力扣&#xff08;LeetCode&#xff09; 2. 44. 通配符匹配 - 力扣&…

Hadoop生态圈框架部署 伪集群版(四)- Zookeeper单机部署

文章目录 前言一、Zookeeper单机部署&#xff08;手动部署&#xff09;1. 下载Zookeeper安装包到Linux2. 解压zookeeper安装包3. 配置zookeeper配置文件4. 配置Zookeeper系统环境变量5. 启动Zookeeper6. 停止Zookeeper在这里插入图片描述 注意 前言 本文将详细介绍Zookeeper的…

捷联惯导原理和算法预备知识

原理和算法预备知识 牛顿第一运动定律-惯性定律&#xff1a;如一物体不受外力作用&#xff0c;它将保持静止状态或匀速直线运动状态不变。 牛顿第二运动定律&#xff1a;表达式为Fma,。其中F为物体所受的合力&#xff0c;m为物体的质量&#xff0c;a为物体的加速度。这个公式…

工业—使用Flink处理Kafka中的数据_ChangeRecord1

使用 Flink 消费 Kafka 中 ChangeRecord 主题的数据,当某设备 30 秒状态连续为 “ 预警 ” ,输出预警 信息。当前预警信息输出后,最近30

简单注册器

简单注册器 还是想查壳 发现这是apx文件 放入JEB里进行反编译 在JEB里使用快捷键Tab 可以反编译 转化成java语言 我们在搜索一下字符串flag 得到了下面这一串字符串 这里他对这串字符串进行了一系列的加密算法 img src"C:\Users\22069\Pictures\Screenshots\屏幕截图 20…

MySQL两阶段提交目的

阶段提交的过程 事务执行阶段&#xff1a;事务开始执行&#xff0c;InnoDB执行SQL语句的具体操作&#xff0c;如数据修改、删除等&#xff0c;并将这些操作记录在内存中。写入Redo Log&#xff08;准备阶段&#xff09;&#xff1a;事务即将提交时&#xff0c;首先将事务相关的…

33 基于单片机的智能窗帘控制系统

目录 一、主要功能 二、硬件资源 三、程序编程 四、实现现象 一、主要功能 基于51单片机&#xff0c;采用DHT11温湿度传感器检测温湿度&#xff0c;滑动变阻器连接ADC0832数模转换器转换模拟,光敏传感器&#xff0c;采用GP2D12红外传感器&#xff0c;通过LCD1602显示屏显示…

Qt关于padding设置不起作用的的解决办法

观察以下的代码&#xff1a; MyWidget::MyWidget(QWidget *parent): QWidget{parent},m_btn(new QToolButton(this)) {this->setFixedSize(500,500);m_btn->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);m_btn->setIcon(QIcon("F:tabIcon/person-white.s…

zookeeper在确认config无误后仍处于standalone模式的解决方法

jps查看是否有QuorumPeerMain进程 停止服务后该进程仍然存在&#xff0c;输入&#xff1a; ps -ef | grep QuorumPeerMain | grep -v grep | awk {print $2} | xargs kill 之后再启动一次进程 bin/zkServer.sh start 查看状态 bin/zkServer.sh status 发现报错解决&#…

Electron-vue 框架升级 Babel7 并支持electron-preload webapck 4 打包过程记录

前言 我这边一直用的electron-vue框架是基于electron 21版本的&#xff0c;electron 29版本追加了很多新功能&#xff0c;但是这些新功能对开发者不友好&#xff0c;对electron构建出来的软件&#xff0c;使用者更安全&#xff0c;所以&#xff0c;我暂时不想研究electron 29版…

Gartner报告解读(四)| 如何运用上升期的基础设施自动化(IA)为企业数字化转型赋能?

近期&#xff0c;Gartner发布的《2024年中国基础设施战略技术成熟度曲线》显示&#xff0c;未来5-10年&#xff0c;大量具有颠覆性或较高影响力的创新技术可能会实现主流采用&#xff0c;其中就包括基础设施自动化&#xff08;IA&#xff09;。 基础设施自动化Gartner评估情况 …

博泽Brose EDI项目案例

Brose 是一家德国的全球性汽车零部件供应商&#xff0c;主要为全球汽车制造商提供机电一体化系统和组件&#xff0c;涵盖车门、座椅调节系统、空调系统以及电动驱动装置等。Brose 以其高质量的创新产品闻名&#xff0c;在全球拥有多个研发和生产基地&#xff0c;是全球第五大家…

springboot读取tif图片转为png在前端预览

springboot读取tif图片转为png在前端预览 我这里是读取tif后转为png,再转为base64直接传给前端。 在线预览base64的地址&#xff1a;http://www.ecomcn.com/tool/Base64/ 文件目录结构&#xff1a; 代码&#xff1a; Overridepublic List<YbglSetYbSPlitListVo> ybgl…

学习日志020---qt信号与槽

作业 import sysfrom PySide6.QtWidgets import QApplication, QWidget,QPushButton,QLineEditfrom Form import Ui_Form from second import Ui_second from PySide6.QtCore import Qtclass MyWidget(QWidget,Ui_Form):def __init__(self):super().__init__()self.setupUi(se…

Lua元表和元方法的使用

元表是一个普通的 Lua 表&#xff0c;包含一组元方法&#xff0c;这些元方法与 Lua 中的事件相关联。事件发生在 Lua 执行某些操作时&#xff0c;例如加法、字符串连接、比较等。元方法是普通的 Lua 函数&#xff0c;在特定事件发生时被调用。 元表包含了以下元方法&#xff1…

【HarmonyOS】鸿蒙应用使用lottie动画

【HarmonyOS】鸿蒙应用使用lottie动画 一、lottie动画是什么&#xff1f; https://airbnb.design/lottie Lottie是由Airbnb团队开发的一个适用于iOS、Android、React Native、Web和Windows的开源动画库&#xff0c;用于解析使用Bodymovin导出为JSON的Adobe After Effects动…

前缀和(四)除自身以外数组的乘积

238. 除自身以外数组的乘积 给你一个整数数组 nums&#xff0c;返回 数组 answer &#xff0c;其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法&…