1. 简介

Canal是阿里巴巴旗下的一款开源项目,纯Java开发。基于数据库增量日志解析,提供增量数据订阅&消费功能。

工作原理

Mysql主备复制原理

  • MySQL master 将数据变更写入二进制日志( binary log, 其中记录叫做二进制日志事件binary log events,可以通过 show binlog events 进行查看)
  • MySQL slave 将 master 的 binary log events 拷贝到它的中继日志(relay log)
  • MySQL slave 重放 relay log 中事件,将数据变更反映它自己的数据

canal工作原理

  • canal 模拟 MySQL slave 的交互协议,伪装自己为 MySQL slave ,向 MySQL master 发送 dump 协议
  • MySQL master 收到 dump 请求,开始推送 binary log 给 slave (即 canal )
  • canal 解析 binary log 对象(原始为 byte 流)

mysql的binlog

它记录了所有的DDL和DML(除了数据查询语句)语句,以事件形式记录,还包含语句所执行的消耗的时间。主要用来备份和数据同步。

binlog有三种模式:STATEMENTROWMIXED

  1. STATEMENT 记录的是执行的sql语句
  2. ROW 记录的是真实的行数据记录
  3. MIXED 记录的是1+2,优先按照1的模式记录

比如:

update user set age = 33

对应STATEMENT模式只是记录了当前执行的sql,而对应ROW模式则有可能有成千上万条记录(当然这取决于你user表的记录数)

2. 可以干什么

基于日志增量订阅和消费的业务包括

  • 数据库镜像
  • 数据库实时备份
  • 索引构建和实时维护(拆分异构索引、倒排索引等)
  • 业务 cache 刷新
  • 带业务逻辑的增量数据处理

当前的 canal 支持源端 MySQL 版本包括 5.1.x , 5.5.x , 5.6.x , 5.7.x , 8.0.x

3. 安装

原生安装请参考:https://github.com/alibaba/canal/wiki/QuickStart

3.1 docker-compose安装

3.1.1 创建同步用户

如果想查看mysql server的相关配置可以参考 https://www.cnblogs.com/ludangxin/p/16358928.html 中的master配置

CREATE USER canal IDENTIFIED BY 'canal';
GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'canal'@'%';
-- GRANT ALL PRIVILEGES ON *.* TO 'canal'@'%' ;
FLUSH PRIVILEGES;

3.1.2 修改配置文件

首先启动一个零时的容器用于 将容器中的配置文件信息copy到宿主机

# run 零时容器
docker run --name canal-temp -d --rm canal/canal-server:v1.1.6
# 执行copy操作 copy配置文件到当前目录中
docker cp canal-temp:/home/admin/canal-server/conf ./canal-server/conf

canal-server/conf配置文件目录结构如下


canal-server/conf
├── canal.properties # canal server 的配置文件参数信息 例如:服务的端口/集群参数/server 模式(# tcp, kafka, rocketMQ, rabbitMQ, pulsarMQ)等
├── canal_local.properties
├── example # canal 实例相关配置信息,如果想要监听多个mysql server 可以copy此文件进行配置,当然也要在canal.properties的 canal.destinations 中添加对应的文件夹名称
│   ├── h2.mv.db
│   ├── instance.properties
│   └── meta.dat
├── logback.xml
...

修改配置文件信息

canal.properties我们使用默认的配置信息 即:canal.serverMode = tcp

example/instance.properties中配置mysql server连接信息 如下:

# position info
# mysql url 我这里直接使用的是 mysql容器name
canal.instance.master.address=my_mysql:3306
# 监听的binlog 文件名称 例:mysql-bin.000007
canal.instance.master.journal.name=
# 日志文件的Offset
canal.instance.master.position= # username/password
canal.instance.dbUsername=canal
canal.instance.dbPassword=canal
canal.instance.connectionCharset = UTF-8 # table regex
# 默认配置是同步所有的库和表
#canal.instance.filter.regex=.*\\..*
# 配置只监听test库的user表,如果需要读取多个表可以使用正则表达式或者用逗号隔开
canal.instance.filter.regex=test.user

mysql 数据解析关注的表,Perl正则表达式.

多个正则之间以逗号(,)分隔,转义符需要双斜杠()

常见例子:

所有表:.* or .\…

canal schema下所有表: canal\…*

canal下的以canal打头的表:canal.canal.*

canal schema下的一张表:canal.test1

多个规则组合使用:canal\…*,mysql.test1,mysql.test2 (逗号分隔)

注意:此过滤条件只针对row模式的数据有效(ps. mixed/statement因为不解

析sql,所以无法准确提取tableName进行过滤)

3.1.3 启动canal

docker-compose.yaml 如下

因为canal需要读取mysql server的bin-log所以需要设置加入到mysql server的网络中去

version: '3'
services:
canal:
image: canal/canal-server:v1.1.6
hostname: canal
container_name: canal
restart: "no"
ports:
- "11111:11111"
volumes:
- "./canal-server/conf:/home/admin/canal-server/conf"
- "./canal-server/logs:/home/admin/canal-server/logs"
networks:
- mysql_mysql
networks:
mysql_mysql:
external: true

4. springboot 测试

tips:可参考 https://github.com/NormanGyllenhaal/canal-client

4.1 添加依赖

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency> <dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency> <!-- https://mvnrepository.com/artifact/top.javatool/canal-spring-boot-starter -->
<dependency>
<groupId>top.javatool</groupId>
<artifactId>canal-spring-boot-starter</artifactId>
<version>1.2.1-RELEASE</version>
</dependency>

4.2 添加配置

canal:
server: localhost:11111
destination: example logging:
level:
top.javatool.canal.client.client: OFF

4.3 监听canal数据

package com.ldx.canaldemo.handler;

import com.ldx.canaldemo.domain.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import top.javatool.canal.client.annotation.CanalTable;
import top.javatool.canal.client.handler.EntryHandler; @Slf4j
@Component
// 监听user表
@CanalTable(value = "user")
public class UserHandler implements EntryHandler<User> { @Override
public void insert(User user) {
log.info("insert info {}", user);
} @Override
public void update(User before, User after) {
log.info("update before {} ", before);
log.info("update after {}", after);
} @Override
public void delete(User user) {
log.info("delete {}", user);
}
}
package com.ldx.canaldemo.domain;

import lombok.Data;

import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import java.io.Serializable; @Data
@Table(name = "user")
public class User implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Integer id; @Column(name = "username")
private String username; @Column(name = "password")
private String password; @Column(name = "sex")
private Integer sex;
}

4.4 测试

user表信息如下

CREATE TABLE `user` (
`id` int(11) NOT NULL,
`username` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
`password` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
`age` int(1) DEFAULT NULL,
`sex` int(1) COLLATE utf8mb4_bin DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;

测试新增

INSERT INTO `test`.`user`(`id`, `username`, `password`, `age`, `sex`) VALUES (5, 'zhangtieniu', '123456', 28, 1);

查看控制台输出如下

2022-06-14 13:50:38.144  INFO 71053 --- [xecute-thread-1] com.ldx.canaldemo.handler.UserHandler    : insert info User(id=5, username=zhangtieniu, password=123456, age=28, sex=1)

测试修改

UPDATE `test`.`user` SET `username` = 'zhangsan', age = 23 WHERE id = 5;

查看控制台输出如下

2022-06-14 13:54:55.997  INFO 71053 --- [xecute-thread-2] com.ldx.canaldemo.handler.UserHandler    : update before User(id=null, username=zhangtieniu, password=null, age=28, sex=null)
2022-06-14 13:54:55.997 INFO 71053 --- [xecute-thread-2] com.ldx.canaldemo.handler.UserHandler : update after User(id=5, username=zhangsan, password=123456, age=23, sex=1)

测试删除

DELETE FROM `test`.`user` WHERE id = 5;

查看控制台输出如下

2022-06-14 13:56:46.359  INFO 71053 --- [xecute-thread-3] com.ldx.canaldemo.handler.UserHandler    : delete User(id=5, username=zhangsan, password=123456, age=23, sex=1)

5. rabbit mq 测试

5.1 修改canal配置

canal.properties 修改如下

# 将serverMode 修改成rabbitMQ
canal.serverMode = rabbitMQ # 添加rabbitmq 配置信息
rabbitmq.host = rabbitmq:5672
rabbitmq.virtual.host = /
rabbitmq.exchange = canal.exchange
rabbitmq.username = admin
rabbitmq.password = admin123

example/instance.properties 添加路由规则

canal.mq.topic=canal_routing_key

5.2 springboot consumer

5.2.1 添加依赖

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

5.2.2 添加配置

spring:
rabbitmq:
host: localhost
port: 5672
# rabbit 默认的虚拟主机
virtual-host: /
# rabbit 用户名密码
username: admin
password: admin123
listener:
simple:
# manual 手动确认
acknowledge-mode: manual

5.2.3 添加 consumer

package com.ldx.canaldemo.rabbitmq;

import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component; import java.io.IOException; /**
* @author ludangxin
* @date 2022/6/14
*/
@Slf4j
@Component
public class RabbitMQConsumer {
@RabbitListener(bindings =
{@QueueBinding(
value = @Queue(value = MqConstant.CANAL_QUEUE, durable = "true"),
exchange = @Exchange(value = MqConstant.CANAL_EXCHANGE),
key = MqConstant.CANAL_ROUTING_KEY)
})
public void helloRabbitMq(Message message, Channel channel) throws IOException {
MessageProperties messageProperties = message.getMessageProperties();
try {
log.info(message.toString());
log.info(new String(message.getBody()));
channel.basicAck(messageProperties.getDeliveryTag(), false);
} catch(Exception e) {
// 当前的消息是否重新投递的消息,也就是该消息是重新回到队列里的消息
if(messageProperties.getRedelivered()) {
log.info("消息已重复处理失败,拒绝再次接收...");
// 拒绝消息Ò
channel.basicReject(messageProperties.getDeliveryTag(), false);
}
else {
log.info("消息即将再次返回队列处理...");
channel.basicNack(messageProperties.getDeliveryTag(), false, true);
}
}
}
}
package com.ldx.canaldemo.rabbitmq;

/**
* @author ludangxin
* @date 2022/6/14
*/
public interface MqConstant {
String CANAL_EXCHANGE = "canal.exchange";
String CANAL_QUEUE = "canal_queue";
String CANAL_ROUTING_KEY = "canal_routing_key";
}

5.3 启动测试

先启动项目让程序自动建立所需mq中的交换机和队列

测试新增

INSERT INTO `test`.`user`(`id`, `username`, `password`, `age`, `sex`) VALUES (8, 'zhangtieniu', '123456', 28, 1);

查看控制台输出如下

2022-06-14 14:42:04.818  INFO 73549 --- [ntContainer#0-1] c.l.canaldemo.rabbitmq.RabbitMQConsumer  : (Body:'[B@189a76a(byte[414])' MessageProperties [headers={}, contentLength=0, redelivered=false, receivedExchange=canal.exchange, receivedRoutingKey=canal_routing_key, deliveryTag=4, consumerTag=amq.ctag-KXSHZ8D0wMQo7z2_L2LKsg, consumerQueue=canal_queue])
2022-06-14 14:42:04.818 INFO 73549 --- [ntContainer#0-1] c.l.canaldemo.rabbitmq.RabbitMQConsumer : {"data":[{"id":"8","username":"zhangtieniu","password":"123456","age":"28","sex":"1"}],"database":"test","es":1655188924000,"id":6,"isDdl":false,"mysqlType":{"id":"int(11)","username":"varchar(255)","password":"varchar(255)","age":"int(11)","sex":"varchar(255)"},"old":null,"pkNames":["id"],"sql":"","sqlType":{"id":4,"username":12,"password":12,"age":4,"sex":12},"table":"user","ts":1655188924822,"type":"INSERT"}

测试修改

UPDATE `test`.`user` SET `username` = 'zhangsan', age = 23 WHERE id = 8;

查看控制台输出如下

2022-06-14 14:56:23.471  INFO 73549 --- [ntContainer#0-1] c.l.canaldemo.rabbitmq.RabbitMQConsumer  : (Body:'[B@6a3a1f0(byte[446])' MessageProperties [headers={}, contentLength=0, redelivered=false, receivedExchange=canal.exchange, receivedRoutingKey=canal_routing_key, deliveryTag=5, consumerTag=amq.ctag-KXSHZ8D0wMQo7z2_L2LKsg, consumerQueue=canal_queue])
2022-06-14 14:56:23.471 INFO 73549 --- [ntContainer#0-1] c.l.canaldemo.rabbitmq.RabbitMQConsumer : {"data":[{"id":"8","username":"zhangsan","password":"123456","age":"23","sex":"1"}],"database":"test","es":1655189783000,"id":7,"isDdl":false,"mysqlType":{"id":"int(11)","username":"varchar(255)","password":"varchar(255)","age":"int(11)","sex":"varchar(255)"},"old":[{"username":"zhangtieniu","age":"28"}],"pkNames":["id"],"sql":"","sqlType":{"id":4,"username":12,"password":12,"age":4,"sex":12},"table":"user","ts":1655189783493,"type":"UPDATE"}

测试删除

DELETE FROM `test`.`user` WHERE id = 8;

查看控制台输出如下

2022-06-14 14:57:06.407  INFO 73549 --- [ntContainer#0-1] c.l.canaldemo.rabbitmq.RabbitMQConsumer  : (Body:'[B@628caa50(byte[411])' MessageProperties [headers={}, contentLength=0, redelivered=false, receivedExchange=canal.exchange, receivedRoutingKey=canal_routing_key, deliveryTag=6, consumerTag=amq.ctag-KXSHZ8D0wMQo7z2_L2LKsg, consumerQueue=canal_queue])
2022-06-14 14:57:06.408 INFO 73549 --- [ntContainer#0-1] c.l.canaldemo.rabbitmq.RabbitMQConsumer : {"data":[{"id":"8","username":"zhangsan","password":"123456","age":"23","sex":"1"}],"database":"test","es":1655189826000,"id":8,"isDdl":false,"mysqlType":{"id":"int(11)","username":"varchar(255)","password":"varchar(255)","age":"int(11)","sex":"varchar(255)"},"old":null,"pkNames":["id"],"sql":"","sqlType":{"id":4,"username":12,"password":12,"age":4,"sex":12},"table":"user","ts":1655189826419,"type":"DELETE"}

6. canal admin管理canal

详情查看:https://gitee.com/zhengqingya/docker-compose

使用手册:https://github.com/alibaba/canal/wiki/Canal-Admin-Guide

原生安装:https://github.com/alibaba/canal/wiki/Canal-Admin-QuickStart

  1. 创建canal admin 数据库 canal_manager

  2. 运行初始化sqlLiunx/canal/canal_admin/canal_manager.sql

    文件内容如下:

    CREATE DATABASE /*!32312 IF NOT EXISTS*/ `canal_manager` /*!40100 DEFAULT CHARACTER SET utf8 COLLATE utf8_bin */;
    
    USE `canal_manager`;
    
    SET NAMES utf8;
    SET FOREIGN_KEY_CHECKS = 0; -- ----------------------------
    -- Table structure for canal_adapter_config
    -- ----------------------------
    DROP TABLE IF EXISTS `canal_adapter_config`;
    CREATE TABLE `canal_adapter_config` (
    `id` bigint(20) NOT NULL AUTO_INCREMENT,
    `category` varchar(45) NOT NULL,
    `name` varchar(45) NOT NULL,
    `status` varchar(45) DEFAULT NULL,
    `content` text NOT NULL,
    `modified_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- ----------------------------
    -- Table structure for canal_cluster
    -- ----------------------------
    DROP TABLE IF EXISTS `canal_cluster`;
    CREATE TABLE `canal_cluster` (
    `id` bigint(20) NOT NULL AUTO_INCREMENT,
    `name` varchar(63) NOT NULL,
    `zk_hosts` varchar(255) NOT NULL,
    `modified_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- ----------------------------
    -- Table structure for canal_config
    -- ----------------------------
    DROP TABLE IF EXISTS `canal_config`;
    CREATE TABLE `canal_config` (
    `id` bigint(20) NOT NULL AUTO_INCREMENT,
    `cluster_id` bigint(20) DEFAULT NULL,
    `server_id` bigint(20) DEFAULT NULL,
    `name` varchar(45) NOT NULL,
    `status` varchar(45) DEFAULT NULL,
    `content` text NOT NULL,
    `content_md5` varchar(128) NOT NULL,
    `modified_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`),
    UNIQUE KEY `sid_UNIQUE` (`server_id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- ----------------------------
    -- Table structure for canal_instance_config
    -- ----------------------------
    DROP TABLE IF EXISTS `canal_instance_config`;
    CREATE TABLE `canal_instance_config` (
    `id` bigint(20) NOT NULL AUTO_INCREMENT,
    `cluster_id` bigint(20) DEFAULT NULL,
    `server_id` bigint(20) DEFAULT NULL,
    `name` varchar(45) NOT NULL,
    `status` varchar(45) DEFAULT NULL,
    `content` text NOT NULL,
    `content_md5` varchar(128) DEFAULT NULL,
    `modified_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`),
    UNIQUE KEY `name_UNIQUE` (`name`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- ----------------------------
    -- Table structure for canal_node_server
    -- ----------------------------
    DROP TABLE IF EXISTS `canal_node_server`;
    CREATE TABLE `canal_node_server` (
    `id` bigint(20) NOT NULL AUTO_INCREMENT,
    `cluster_id` bigint(20) DEFAULT NULL,
    `name` varchar(63) NOT NULL,
    `ip` varchar(63) NOT NULL,
    `admin_port` int(11) DEFAULT NULL,
    `tcp_port` int(11) DEFAULT NULL,
    `metric_port` int(11) DEFAULT NULL,
    `status` varchar(45) DEFAULT NULL,
    `modified_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -- ----------------------------
    -- Table structure for canal_user
    -- ----------------------------
    DROP TABLE IF EXISTS `canal_user`;
    CREATE TABLE `canal_user` (
    `id` bigint(20) NOT NULL AUTO_INCREMENT,
    `username` varchar(31) NOT NULL,
    `password` varchar(128) NOT NULL,
    `name` varchar(31) NOT NULL,
    `roles` varchar(31) NOT NULL,
    `introduction` varchar(255) DEFAULT NULL,
    `avatar` varchar(255) DEFAULT NULL,
    `creation_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8; SET FOREIGN_KEY_CHECKS = 1; -- ----------------------------
    -- Records of canal_user
    -- ----------------------------
    BEGIN;
    INSERT INTO `canal_user` VALUES (1, 'admin', '6BB4837EB74329105EE4568DDA7DC67ED2CA2AD9', 'Canal Manager', 'admin', NULL, NULL, '2019-07-14 00:05:28');
    COMMIT; SET FOREIGN_KEY_CHECKS = 1;
  3. docker-compose.yaml如下

    version: '3'
    services:
    canal_admin:
    image: canal/canal-admin:v1.1.6
    container_name: canal_admin
    restart: unless-stopped
    volumes:
    - "./canal/canal-admin/logs:/home/admin/canal-admin/logs"
    environment:
    TZ: Asia/Shanghai
    LANG: en_US.UTF-8
    canal.adminUser: admin
    canal.adminPasswd: 123456
    spring.datasource.address: my_mysql:3306
    spring.datasource.database: canal_manager
    spring.datasource.username: root
    spring.datasource.password: 123456
    ports:
    - "8089:8089"
    networks:
    - canal
    - mysql_mysql
    canal_server:
    image: canal/canal-server:v1.1.6
    container_name: canal_server
    restart: unless-stopped
    volumes:
    - "./canal/canal-server/logs:/home/admin/canal-server/logs"
    environment:
    TZ: Asia/Shanghai
    LANG: en_US.UTF-8
    canal.admin.manager: canal_admin:8089
    canal.admin.port: 11110
    canal.admin.user: admin
    canal.admin.passwd: 6BB4837EB74329105EE4568DDA7DC67ED2CA2AD9
    ports:
    - "11110:11110"
    - "11111:11111"
    - "11112:11112"
    depends_on:
    - canal_admin
    links:
    - canal_admin
    networks:
    - canal
    - mysql_mysql
    networks:
    canal:
    mysql_mysql:
    external: true
  4. 启动服务/访问http://localhost:8089/#/canalServer/nodeServers

    登陆 用户名/密码:admin/123456

Canal-监听数据库表的变化的更多相关文章

  1. SqlDependency C#代码监听数据库表的变化

    SqlDependency提供了这样一种能力:当被监测的数据库中的数据发生变化时,SqlDependency会自动触发OnChange事件来通知应用程序,从而达到让系统自动更新数据(或缓存)的目的. ...

  2. 老技术记录-C#+SqlServer使用SqlDependency监听数据库表变化

    开发环境: .net / C# (.net core理论上也可以) 数据库:MS SQL Server 2005 以上 (我用的sqlserver2012) 功能:SqlDependency提供了一种 ...

  3. Web重温系列(三):OracleDependency实现监听数据库变化

    有个小项目(后来由另一个小组以Java开发了),内容是监控一个Oracle数据库.如果其中一个表A有数据变动,则需要将相关内容重组后通过接口发送给B. 通常的解决办法是定时查询,时间间隔可以小一点,还 ...

  4. js 实时监听input中值变化

    注意:用到了jquery需要引入jquery.min.js. 需求: 1.每个地方需要分别打分,总分为100; 2.第一个打分总分为40; 3.第二个打分总分为60. 注意:需要判断null.&quo ...

  5. javascript --- 实时监听输入框值的变化

    实时监听文本框值变化是非常常见的功能,通常最简单的办法就是用keyup,keydown来实现,但是这种方法有两个问题,一个是当直接复制粘贴的时候没法监听到事件,另外一个问题是在移动端,使用删除键删除输 ...

  6. Android监听WIFI网络的变化并且获得当前信号强度

    MainActivity如下: package cc.testwifi; import android.os.Bundle; import android.app.Activity; /** * De ...

  7. MySQL监听数据库存储过程出现异常

    DELIMITER $$ DROP PROCEDURE IF EXISTS `proc_ordertourist_cancel`$$ CREATE PROCEDURE proc_ordertouris ...

  8. js监听文本框值变化事件,就oninput & onpropertychange & onkeyup & onchange的区别

    在Web前端项目中实时监听文本框的值变化是非常常见的功能,通常最简单最容易想到的是onkeyup和onchange事件,但是在使用onkeyup来监听键盘事件的时候,监听不到鼠标右键的粘贴.复制的操作 ...

  9. 用jquery监听输入数字的变化

    <!DOCTYPE html><html> <head> <meta charset="UTF-8"> <title>& ...

随机推荐

  1. 解决关于ARM_MATH数学库宏定义的报错

    昨天在建立新工程的时候发现加入含有ARM_MATH库的时候出现了宏定义报错. #error directive:"Define according the used Cortex core ...

  2. 用来创建用户docker registry认证的Secret

    集群环境:1.k8s用的是二进制方式安装2.操作系统是linux (centos)3.操作系统版本为 7.4/7.94.k8s的应用管理.node管理.pod管理等用rancher.k8s令牌以及ma ...

  3. 记录:替换线上springboot项目可执行jar包中依赖jar里的class文件

    问题背景: 项目组发现线上版本问题后,定位是由于项目依赖的某个jar包中有个小BUG. 解决方案: 在修改了对应的java文件后,编译出对应的class文件.从生产环境下载项目jar包,解压后,找到对 ...

  4. 图片杂乱无章、分享麻烦?HMS Core图片分类服务教你快速筛选、分类、整合相册

    如今手机摄影越来越方便,随手一拍就能记录美好生活.但照片越多,整理越麻烦,有的时候我们想对照片进行二次加工.分享,需要不停翻找相册.HMS Core机器学习服务(ML Kit)提供了图片分类服务,方便 ...

  5. Codeforces Round #703 (Div. 2)__ B. Eastern Exhibition__ 纯纯的思维

    原题链接https://codeforces.com/contest/1486/problem/B 题目 解题思路 这是个思维题,  算是货仓选址的变式, 想要到达各个点距离最小,我们的目标可以化为先 ...

  6. 【HarmonyOS学习笔记】记第一次使用IDE

    哈喽大家好我是脸皮贼厚的小威 愚人节刚过先给大家拜个早年吧 最近在HarmonyOS官网下载了IDE,并抱着学(wan)习(wan)的心态试着跑出了Hello World,并安装到手机上 这是一个简单 ...

  7. jointJS初使用随记

    jointJs使用随记 1.下载与安装 前提:一个健康良好且干净的vue脚手架项目. 还是普遍的安装方式 yarn:yarn add jointjs npm:npm install jointjs 还 ...

  8. Electron 从零创建一个 Windows/OS X/Linux 的桌面可执行程序

    [外链图片转存失败(img-3RucrgcX-1562556984462)(http://7vzsvy.com1.z0.glb.clouddn.com/electron-1-2.png "E ...

  9. 手把手教你 bash中给变量赋值时 ' 和 " 和 ` 和 $() 的使用

    1.赋值指令 var='变量内容' var="变量内容" var=`command` var=$(command) var=变量内容 2.格式要求 =两边不能有空白字符 错误示例 ...

  10. 438. Find All Anagrams in a String - LeetCode

    Question 438. Find All Anagrams in a String Solution 题目大意:给两个字符串,s和p,求p在s中出现的位置,p串中的字符无序,ab=ba 思路:起初 ...