使用canal和openfire实现Mysql的实时数据订阅

news2024/11/26 3:12:37

文章目录

    • 1、Openfire插件接收binlog数据
      • 1.1、创建用户组
      • 1.2、接口实现
    • 2、Canal客户端开发
    • 3、Smack消息客户端实现。

mysql的binlog的实时数据订阅
(1) canal安装与客户端使用
(2) openfire 4.7.5 Web插件开发
(3) 使用canal和openfire实现Mysql的实时数据订阅

业务系统每天产生的数据在5000-1万之间,数据量不大,但是订阅这些数据的用户量比较多,当前要支持100多用户,并且要求数据实时同步。产生的数据是包含类别的,用户按实际购买情况订阅自己需要的数据,但是类别总数不多,可以用户分成不同的组,同一组用户订阅相同的类别数据。
简单的构思了一下实现:
1、要实现数据实时同步,最好的方式是基于binlog日志同步,可选的读取binlog工具:canal、floinkcdc等。
2、要把数据同步到用户的数据库,可选方案:
(1)binlog数据保存到kafka,在用户的服务器上安装读取kafka的客户端,实现数据入库。但是这方案在用户管理不方便。
(2)基于netty实现数据同步的服务器端和客户端,binlog数据发送到netty服务器端,netty服务器端再通过传输协议把数据同步到netty客户端入库。但是这方案比较可行,但是开发工作量比较大。
(3)使用开源openfire即时消息服务器和客户端Smack库实现数据同步的服务器端和客户端,这方案开发工作量不大,而且Openfire自带用户和组管理,单机也支持1万用户的并发量,后期用户增长也没压力。但是需要开发人员先学习openfire相关技术,需要一定的学习时间。
本文使用方案(3)测试数据同步方案。
数据同步链路如下:
在这里插入图片描述

1、Openfire插件接收binlog数据

1.1、创建用户组

在openfire后台创建用户组data-group,组内添加用户test1、test2,这个组内的数据只同步给test1、test2两个用户。
在这里插入图片描述

1.2、接口实现

实现代码是在前文openfire 4.7.5 Web插件开发 的Jersey接口例子基础上,增加MessageService类,实现接收数据的http接口,接收来自canal客户端的sql数据,并将数据发送到Smack客户端。

package org.igniterealtime.openfire.service;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.igniterealtime.openfire.exampleplugin.DbCdcPlugin;
import org.jivesoftware.admin.AuthCheckFilter;
import org.jivesoftware.openfire.MessageRouter;
import org.jivesoftware.openfire.XMPPServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmpp.packet.JID;
import org.xmpp.packet.Message;
import javax.annotation.PostConstruct;
import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import java.util.*;

@Path("dbcdc/v1/message")
public class MessageService {
    private static final Logger log = LoggerFactory.getLogger(MessageService.class);
    private DbCdcPlugin plugin;
    @PostConstruct
    public void init() {
        plugin = (DbCdcPlugin) XMPPServer.getInstance().getPluginManager().getPlugin( "dbcdc" );
        AuthCheckFilter.addExclude("dbcdc/v1/message/sendMsgToOne");
    }

    // http://localhost:9090/plugins/dbcdc/v1/message/sendMsgToOne?msg=aaaaabbbbbb
    @POST
    @Path("/sendMsgToOne")
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
    public Map<String, Object> sendMsgToOne(@FormParam("msg") String msg) throws Exception
    {
        List<String> jids = getMembers();
        // 给组内用户同步binlog
        Map<String, Object> data = new HashMap<>();
        data.put("msg", msg);
        String from = "admin@21doc.net";
        jids.forEach(jid->{
            String to = jid;
            Message message = new Message();
            message.setFrom(new JID(from));
            message.setTo(new JID(to));
            message.setID(nextID());
            message.setType(Message.Type.chat);
            message.setBody(msg);
            MessageRouter messageRouter = plugin.server.getMessageRouter();
            messageRouter.route(message);
            data.put(jid, "success");
        });
        return data;
    }

    private List<String> getMembers(){
        List<String> list = new ArrayList();
        try{
            // 测试只发给data-group用户组
            Collection<JID> jids = plugin.groupManager.getGroup("data-group").getMembers();
            for(JID jid:jids){
                list.add(jid.toBareJID());
            }
        }
        catch(Exception e){
           log.error("getMembers error=====",e);
        }
        return list;
    }
    
    public static synchronized String nextID() {
        Random random = new Random();
        int number1 = random.nextInt(899) + 100;
        int number2 = random.nextInt(899) + 100;
        return new StringBuffer().append(number1).append("-").append(number2).toString();
    }
}

2、Canal客户端开发

Canal客户端接收Canal服务器端传输的binlog数据,并转成对应的sql语句,再发送到Openfire服务器端分发给用户。
是在前文canal安装与客户端使用的基础上,重新实现了CanalClient类,例子只处理了删除、更新、插入三种SQL操作。

package com.penngo.canal.component;

import com.alibaba.otter.canal.client.CanalConnector;
import com.alibaba.otter.canal.protocol.CanalEntry.*;
import com.alibaba.otter.canal.protocol.Message;
import com.google.protobuf.InvalidProtocolBufferException;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.jsoup.Connection;
import org.jsoup.Jsoup;
import org.springframework.stereotype.Component;
import java.util.List;

@Component
@Slf4j
public class CanalClient {

    private final static String imUrl = "http://localhost:9090/plugins/dbcdc/v1/message/sendMsgToOne";
    @Resource
    private CanalConnector canalConnector;
    /**
     * canal入库方法
     */
    public void run() {

        int batchSize = 1000;
        try {
            canalConnector.connect();
            canalConnector.subscribe("flinktest\\..*");
            canalConnector.rollback();
            try {
                while (true) {
                    Message message = canalConnector.getWithoutAck(batchSize);
                    long batchId = message.getId();
                    int size = message.getEntries().size();
                    System.out.println("batchId=======" + batchId + ",size:" + size);
                    if (batchId == -1 || size == 0) {
                        Thread.sleep(1000);
                    } else {
                        dataHandle(message.getEntries());
                    }
                    canalConnector.ack(batchId);

                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (InvalidProtocolBufferException e) {
                e.printStackTrace();
            }
        } finally {
            canalConnector.disconnect();
        }
    }



    /**
     * 数据处理
     * @param entrys
     */
    private void dataHandle(List<Entry> entrys) throws InvalidProtocolBufferException {
        for (Entry entry : entrys) {
            if (EntryType.ROWDATA == entry.getEntryType()) {
                RowChange rowChange = RowChange.parseFrom(entry.getStoreValue());
                EventType eventType = rowChange.getEventType();
                if (eventType == EventType.DELETE) {
                    genDeleteSql(entry);
                } else if (eventType == EventType.UPDATE) {
                    genUpdateSql(entry);
                } else if (eventType == EventType.INSERT) {
                    genInsertSql(entry);
                }
            }
        }
    }

    private void genUpdateSql(Entry entry) {
        try {
            RowChange rowChange = RowChange.parseFrom(entry.getStoreValue());
            List<RowData> rowDatasList = rowChange.getRowDatasList();
            for (RowData rowData : rowDatasList) {
                List<Column> newColumnList = rowData.getAfterColumnsList();
                StringBuffer sql = new StringBuffer("update " + entry.getHeader().getTableName() + " set ");
                for (int i = 0; i < newColumnList.size(); i++) {
                    sql.append(" " + newColumnList.get(i).getName()
                            + " = '" + newColumnList.get(i).getValue() + "'");
                    if (i != newColumnList.size() - 1) {
                        sql.append(",");
                    }
                }
                sql.append(" where ");
                List<Column> oldColumnList = rowData.getBeforeColumnsList();
                for (Column column : oldColumnList) {
                    if (column.getIsKey()) {
                        sql.append(column.getName() + "=" + column.getValue());
                        break;
                    }
                }
                this.sqlToIm(sql.toString());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void genDeleteSql(Entry entry) {
        try {
            RowChange rowChange = RowChange.parseFrom(entry.getStoreValue());
            List<RowData> rowDatasList = rowChange.getRowDatasList();
            for (RowData rowData : rowDatasList) {
                List<Column> columnList = rowData.getBeforeColumnsList();
                StringBuffer sql = new StringBuffer("delete from " + entry.getHeader().getTableName() + " where ");
                for (Column column : columnList) {
                    if (column.getIsKey()) {
                        sql.append(column.getName() + "=" + column.getValue());
                        break;
                    }
                }
                this.sqlToIm(sql.toString());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void genInsertSql(Entry entry) {
        try {
            RowChange rowChange = RowChange.parseFrom(entry.getStoreValue());
            List<RowData> rowDatasList = rowChange.getRowDatasList();
            for (RowData rowData : rowDatasList) {
                List<Column> columnList = rowData.getAfterColumnsList();
                StringBuffer sql = new StringBuffer("insert into " + entry.getHeader().getTableName() + " (");
                for (int i = 0; i < columnList.size(); i++) {
                    sql.append(columnList.get(i).getName());
                    if (i != columnList.size() - 1) {
                        sql.append(",");
                    }
                }
                sql.append(") VALUES (");
                for (int i = 0; i < columnList.size(); i++) {
                    sql.append("'" + columnList.get(i).getValue() + "'");
                    if (i != columnList.size() - 1) {
                        sql.append(",");
                    }
                }
                sql.append(")");
                this.sqlToIm(sql.toString());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 同步到openfire服务器端
     * @param sql
     */
    public void sqlToIm(String sql) throws Exception {
        String body = Jsoup.connect(imUrl)
                .ignoreContentType(true)
                .header("Content-Type", "application/x-www-form-urlencoded")
                .data("msg", sql)
                .method(Connection.Method.POST)
                .execute().body();
        log.info("sqlToIm result=======" + body);
    }
}

3、Smack消息客户端实现。

Smack消息客户端接收来自Openfire服务器的消息,并把数据同步到数据库。实现代码
pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.penngo.example</groupId>
    <artifactId>Smack-Test</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <java.version>8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.igniterealtime.smack/smack-core -->
        <dependency>
            <groupId>org.igniterealtime.smack</groupId>
            <artifactId>smack-core</artifactId>
            <version>4.4.6</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.igniterealtime.smack/smack-extensions -->
        <dependency>
            <groupId>org.igniterealtime.smack</groupId>
            <artifactId>smack-extensions</artifactId>
            <version>4.4.6</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.igniterealtime.smack/smack-im -->
        <dependency>
            <groupId>org.igniterealtime.smack</groupId>
            <artifactId>smack-im</artifactId>
            <version>4.4.6</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.igniterealtime.smack/smack-tcp -->
        <dependency>
            <groupId>org.igniterealtime.smack</groupId>
            <artifactId>smack-tcp</artifactId>
            <version>4.4.6</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.igniterealtime.smack/smack-debug -->
        <dependency>
            <groupId>org.igniterealtime.smack</groupId>
            <artifactId>smack-debug</artifactId>
            <version>4.4.6</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.igniterealtime.smack/smack-experimental -->
        <dependency>
            <groupId>org.igniterealtime.smack</groupId>
            <artifactId>smack-experimental</artifactId>
            <version>4.4.6</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.igniterealtime.smack/smack-legacy -->
        <dependency>
            <groupId>org.igniterealtime.smack</groupId>
            <artifactId>smack-legacy</artifactId>
            <version>4.4.6</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.igniterealtime.smack/smack-bosh -->
        <dependency>
            <groupId>org.igniterealtime.smack</groupId>
            <artifactId>smack-bosh</artifactId>
            <version>4.4.6</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.igniterealtime.smack/smack-resolver-minidns -->
        <dependency>
            <groupId>org.igniterealtime.smack</groupId>
            <artifactId>smack-resolver-minidns</artifactId>
            <version>4.4.6</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.igniterealtime.smack/smack-resolver-javax -->
        <dependency>
            <groupId>org.igniterealtime.smack</groupId>
            <artifactId>smack-resolver-javax</artifactId>
            <version>4.4.6</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.igniterealtime.smack/smack-resolver-dnsjava -->
        <dependency>
            <groupId>org.igniterealtime.smack</groupId>
            <artifactId>smack-resolver-dnsjava</artifactId>
            <version>4.4.6</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.igniterealtime.smack/smack-xmlparser-xpp3 -->
        <dependency>
            <groupId>org.igniterealtime.smack</groupId>
            <artifactId>smack-xmlparser-xpp3</artifactId>
            <version>4.4.6</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.igniterealtime.smack/smack-sasl-javax -->
        <dependency>
            <groupId>org.igniterealtime.smack</groupId>
            <artifactId>smack-sasl-javax</artifactId>
            <version>4.4.6</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.igniterealtime.smack/smack-java8 -->
        <dependency>
            <groupId>org.igniterealtime.smack</groupId>
            <artifactId>smack-java8</artifactId>
            <version>4.4.6</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.23</version>
        </dependency>
        <dependency>
            <groupId>commons-dbutils</groupId>
            <artifactId>commons-dbutils</artifactId>
            <version>1.6</version>
        </dependency>
    </dependencies>
    <repositories>
        <repository>
            <id>alimaven</id>
            <name>Maven Aliyun Mirror</name>
            <url>https://maven.aliyun.com/repository/central</url>
        </repository>
    </repositories>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.22.2</version>
                <configuration>
                    <skip>true</skip>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

业务逻辑实现SqlAgentIM.java

package com.penngo.example;

import org.jivesoftware.smack.AbstractXMPPConnection;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.SmackConfiguration;
import org.jivesoftware.smack.SmackException;

import org.jivesoftware.smack.chat.Chat;
import org.jivesoftware.smack.chat.ChatManager;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.tcp.XMPPTCPConnection;
import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration;
import org.jxmpp.jid.EntityBareJid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.commons.dbutils.DbUtils;
import org.apache.commons.dbutils.QueryRunner;
import java.sql.Connection;
import java.sql.DriverManager;

public class SqlAgentIM {
    private static final Logger log = LoggerFactory.getLogger(SqlAgentIM.class);
    private String dburl = "jdbc:mysql://localhost:3306/flinktest2?serverTimezone=Asia/Shanghai&characterEncoding=utf-8&useSSL=false";
    private AbstractXMPPConnection conn = null;
    private DbDao dbDao = null;
    public SqlAgentIM(){

    }

    public void run(){
        dbDao = new DbDao(dburl, "root", "test123");
        login();
    }

    public void login(){
        try{
            // 打开调试窗口
            SmackConfiguration.DEBUG = true;
            XMPPTCPConnectionConfiguration config = XMPPTCPConnectionConfiguration.builder()
                    .setUsernameAndPassword("test2", "123456")
                    .setXmppDomain("21doc.net")
                    .setHost("127.0.0.1")
                    .setPort(5222)
                    .setSecurityMode(ConnectionConfiguration.SecurityMode.disabled)
                    .build();

            conn = new XMPPTCPConnection(config);
            conn.connect().login();

            setOnlineStatus();

            ChatManager chatManager = ChatManager.getInstanceFor(conn);
            chatManager.addChatListener((chat, b) -> chat.addMessageListener((chat1, message) ->
            {
                String sql = message.getBody();
                //System.out.println("New message from " + chat1 + ": " + message.getBody());
                System.out.println("New message from " + chat1.getParticipant().asEntityBareJidString() + ": " + sql);
                try{
                    int successCount = dbDao.execute(sql);
                    log.info("execute result====successCount:" + successCount);
                }
                catch(Exception e){
                    e.printStackTrace();
                    log.error("execute error", e);
                }
            }));
        }
        catch(Exception e){
            e.printStackTrace();
        }
    }

    public void setOnlineStatus() throws SmackException.NotConnectedException, InterruptedException {
        Presence presence = new Presence(Presence.Type.available, "online", 1, Presence.Mode.available);
        conn.sendStanza(presence);
        Presence presence2 = new Presence(Presence.Type.subscribe, "online", 1, Presence.Mode.available);
        conn.sendStanza(presence2);
        Presence presence3 = new Presence(Presence.Type.subscribed, "online", 1, Presence.Mode.available);
        conn.sendStanza(presence3);
    }

    class DbDao {
    
        private String jdbc_driver;
        private String jdbc_url;
        private String jdbc_username;
        private String jdbc_password;
        private Connection conn = null;
        public DbDao(String url, String username, String password){
            if(conn == null){
                try{
                    if(jdbc_driver == null){
                        jdbc_driver = "";
                        jdbc_url = url;
                        jdbc_username = username;
                        jdbc_password = password;
                        
                    }
                    DbUtils.loadDriver(jdbc_driver);
                    
                    conn = DriverManager.getConnection(jdbc_url, jdbc_username, jdbc_password);
                }
                catch(Exception e){
                    e.printStackTrace();
                }
            }
        }
    
        public int execute(String sql, Object... params) throws Exception{
            QueryRunner qr = new QueryRunner();
            int i = qr.update(conn, sql, params);
            return i;
        }
        
        public void close(){
            DbUtils.closeQuietly(conn);
        }
    }

    public static void main(String[] args) {
        SqlAgentIM sqlAgentIM = new SqlAgentIM();
        sqlAgentIM.run();
    }
}

本文仅实现了用户在线时的mysql实现更新,未实现内容:

  • 1、客户端离线时,离线数据的同步;
  • 2、用户第一次订阅数据时的数据全量更新。
    后续可以根据方案采用情况再优化完善。

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

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

相关文章

gitee提交代码Commit和Push时窗口不小心关了,怎么继续推送提交?

gitee提交代码Commit和Push时窗口不小心关了&#xff0c;怎么继续推送提交&#xff1f; 一、commit关了解决办法 二、Push关了解决办法 一、commit关了 在我们使用gitee提交代码时&#xff0c;原本是commit或push但不小心按到ok关了&#xff0c;导致代码提交失败。 解决办法…

分享73个Python源代码总有一个是你想要的

分享73个Python源代码总有一个是你想要的 下载链接&#xff1a;https://pan.baidu.com/s/15qO16Vdcy6BxK5AzbXFpOw?pwd8888 提取码&#xff1a;8888 AI悦创Python小项目代码 AI视频创作ffmpegopenai-whispertts flask大屏展示Python项目 flask框架下的前后端分离的房屋租…

关于字符拼接

当然&#xff0c;以下是加入了幽默注释的代码和对应的逻辑树&#xff1a; # 提示用户输入input和txt内容&#xff0c;期待用户真有输入 input_text input("请输入input文本&#xff1a;") # 好了&#xff0c;快点输入吧 txt_text input("请输入txt文本&#…

OpenGLES:绘制一个彩色、旋转的3D圆柱

一.概述 上一篇博文讲解了怎么绘制一个彩色旋转的立方体 这一篇讲解怎么绘制一个彩色旋转的圆柱 圆柱的顶点创建主要基于2D圆进行扩展&#xff0c;与立方体没有相似之处 圆柱绘制的关键点就是将圆柱拆解成&#xff1a;两个Z坐标不为0的圆 一个长方形的圆柱面 绘制2D圆的…

不做静态化,当部署到服务器上的项目刷新出现404【已解决】

当线上项目刷新出现404页面解决方法&#xff1a; 在nginx配置里加入这样一段代码 try_files $uri $uri/ /index.html; 它的作用是尝试按照给定的顺序访问文件 变量解释 try_files 固定语法 $uri 指代home文件(ip地址后面的路径&#xff0c;假如是127.0.0.1/index/a.png&…

Oracle SQL Developer 中查看表的数据和字段属性、录入数据

在Oracle SQL Developer中&#xff0c;选中一个表时&#xff0c;右侧会列出表的情况&#xff1b;第一个tab是字段的名称、数据类型等属性&#xff1b; 切换到第二个tab&#xff0c;显示表的数据&#xff1b; 这和sql server management studio不一样的&#xff1b; 看一下部门…

Oracle is和as 关键字学习

之前写的Oracle存储过程中都有is和as关键字&#xff1b;下面学习这二个关键字&#xff1b; Oracle中is可用于以下情况&#xff1a; 判断某个值是否为null。在Oracle中&#xff0c;null表示一个未知或不适用的值。因此&#xff0c;我们需要使用is null或is not null语句来检查某…

程序员的重复劳动陷阱

https://kb.cnblogs.com/page/627035/ 同样是一样的计算机专业毕业&#xff0c;进入职场的职位和工作都差不多&#xff0c;为何有些程序员短短几年就成长为全能选手或领域专家&#xff0c;有些程序员还在做CRUD&#xff1f; 程序员的重复劳动陷阱 不知道大家有没有这样的感觉…

cygwin编译haproxy

下载安装cygwin cygwin下载、安装-CSDN博客 编译haproxy 打开cygwin终端 下载程序 haproxy程序 OpenPKG Project: Download 输入下面命令下载程序 wget http://download.openpkg.org/components/cache/haproxy/haproxy-2.8.3.tar.gz 解压 tar -zxvf haproxy-2.8.3.tar.gz…

2023年中国反射膜产量及市场规模分析:随着太阳能产业快速发展,规模持续扩大[图]

反射膜是一种具有高反射能力的薄膜材料&#xff0c;能够将光线反射回源头&#xff0c;起到增强反射效果的作用。反射膜广泛应用于太阳能电池板、建筑玻璃、车窗、显示器、光学仪器等领域。 反射膜产业链 资料来源&#xff1a;共研产业咨询&#xff08;共研网&#xff09; 随着…

Python中aiohttp和aiofiles模块的安装

Python中aiohttp和aiofiles模块的安装 前言 在进行asyncio多任务爬取的时候&#xff0c;配合着aiohttp和aiofiles的使用是必不可少的&#xff0c;那么我们现在就安装这两个模块到pycharm上 安装 将下面两行代码放入到pycharm上的终端就会开始下载 pip install aiohttp pip in…

Python字符串索引解码乱码谜题

输入数行“数字字母”字符组成的乱码字符串&#xff0c;根据谜题规则解码出乱码字符串中隐藏的单词信息。 (本笔记适合熟悉python字符串索引操作的 coder 翻阅) 【学习的细节是欢悦的历程】 Python 官网&#xff1a;https://www.python.org/ Free&#xff1a;大咖免费“圣经”…

我用PYQT5做的第一个实用的上位机项目(三)

基本的程序框架&#xff1a; 因为自己不是专业的程序员&#xff0c;只是一个搞电气控制的“票友”&#xff0c;所以尽量减少手动输入 代码量&#xff0c;能在Qt Dsigner里面完成的组态就不要放在代码里面完成。 在框架的建设方面&#xff0c;尽量做到集中和整合&#xff0c;位…

数据结构之双链表

双链表 1.复杂方法的图分析2.My_LinkedList代码3.接口MY_lIST4.测试类 1.复杂方法的图分析 2.My_LinkedList代码 package My_liNKEDlIST;public class My_LinkedList implements MY_lIST{static class ListNode{public int val;public ListNode prev;public ListNode next;pub…

数据结构 - 线段树的运用

数据结构 - 线段树的运用 前言一. 线段树的运用1.1 区间和 - 线段树节点的成员变量1.2 线段树的构建1.3 线段树的区间和查询1.4 线段树的区间和更新1.5 完整代码 二. 线段树的动态扩建2.1 向下递推2.2 向上递推2.3 更新操作2.4 查询操作2.5 完整代码 三. 线段树的使用案例3.1 定…

c++学习之优先级队列

目录 1.初识优先级队列 库中的实现 使用优先级队列 2.优先级队列的实现 3.仿函数 利用仿函数实现的优先级队列 迭代器区间构造&#xff08;建堆&#xff09; 1.初识优先级队列 如果我们给每个元素都分配一个数字来标记其优先级&#xff0c;不妨设较小的数字具有较…

2023年中国新能源汽车电动助力转向系统行业现状分析:随着新能源汽车的发展,产品渗透率的提升[图]

电动助力转向(EPS)系统是传统转向系统&#xff08;如液压和电动液压系统&#xff09;的替代品。自动驾驶汽车的日益普及正在推动全球电动助力转向系统市场的需求增长。配备电动助力转向系统的车辆总重量趋于减轻&#xff0c;从而进一步提高燃油效率&#xff0c;其中2022年中国新…

Nginx之Openresty基本使用解读

目录 Openresty基本介绍 Openresty源码编译安装 Openresty基本使用 测试lua脚本 外部分文件导入 关闭缓存&#xff0c;开启热部署 用lua代码获取系统变量 Openresty基本介绍 OpenResty是一个基于 Nginx 与 Lua 的高性能 Web 平台&#xff0c;其内部集成了大量精良的 Lua…

2023年中国纯棉纱行业现状及发展前景分析[图]

棉纱是棉纤维经纺纱工艺加工而成的纱&#xff0c;经合股加工后称为棉线。根据纺纱的不同工艺&#xff0c;可分为普梳纱和精梳纱。精梳纱选用优质原料&#xff0c;成纱中纤维伸直平行、结杂少、光泽好、条干匀、强力高&#xff0c;这类棉纱多用于织造高档。 棉纱分类 资料来源&…

2023年中国汽车座舱行业发展现状及趋势分析:高级人机交互(HMI)系统将逐步提升[图]

2022年有22.3%的汽车用户认为座舱内车载娱乐功能成为影响使用体验的关键因素。当前智能电动汽车的用户画像与娱乐、游戏等应用的用户画像相似&#xff0c;均以年轻人作为目标用户。年轻化的用户将娱乐功能的使用习惯延伸至汽车座舱内&#xff0c;对于座舱功能的需求不再局限于导…